feat(routines): dispatch activity-anticipation cadence for upcoming activities
- Wire {"type": "activity-anticipation", "offset_minutes": N}
cadences into think.routines.check() so routines like
meeting-prep fire at start + offset (±60s) for visible
anticipated activities across all facets on the current
local day.
- _run_routine now accepts an optional
trigger_context={"activity": ..., "facet": ...} and injects
an ## Upcoming Activity block (title, type, facet,
start/end, description, details, attendee names) before the
"Execute this routine now." line; other callers are unchanged.
- Dedup is in-memory only (_fired_triggers, pruned at 2 days
per check() tick). State does not survive supervisor restarts
— acceptable since re-firing a past trigger is worse than
missing a restart-window fire.
- Unknown dict cadence types (e.g. legacy {"type": "event"})
are silently skipped with one INFO log per routine_id via
_logged_unknown_cadence; string-cron behavior is untouched.
- Known limitations: (1) anticipated activities carry no
timezone field — the routine's timezone is assumed. (2)
negative offsets that cross midnight (e.g. a 00:15 activity
with offset_minutes=-30) will miss their trigger because the
dispatcher only scans the activity-day file. (3) dedup is
written before dispatch, so a _run_routine failure will not
auto-retry.
- Follow-up: think/tools/routines.py::_validate_routine_cadence
still rejects non-string cadences, so sol call routines
create --template meeting-prep fails until that validation is
extended to accept dict cadences (and the manual routines run
path updated to supply trigger context).