fix: Schedules: store + schedule/list/cancel verbs + timer (delete old scheduler) #67

Merged
weiwen merged 2 commits from issue-60 into main 2026-07-12 17:23:10 +08:00
Owner

Replace the static [[schedule]] config-based scheduler with dynamic, persisted Schedules — a Schedule is a persisted instruction to run a delivery Verb (send/prompt/run) against a frozen chat at a future time.

What changed:

  • New src/schedule.rs moduleTiming enum (In/At/Cron), Schedule struct, ScheduleStore with atomic-temp+rename persistence to schedules.json, pure fire-decision functions (next_fire, is_due, is_missed_oneshot), and generate_handle. All firing-time logic is isolated from tokio::sleep.

  • New CLI verbsevie schedule (--in|--at|--cron) [--name] [--update] [--chat] -- <verb> [args…], evie list [--json] [--chat], evie cancel --name <n> [--chat]. Clap enforces mutual exclusivity of timing flags. --name collision without --update errors; omitted name generates and prints a hex handle.

  • Control plane CRUDControlRequest::Schedule/List/Cancel variants with validation (verb whitelist, per-chat name scoping, upsert semantics). Human-readable table for list, raw JSON with --json.

  • Schedule timerschedule_timer replaces the old scheduler_task. It sleeps until the earliest next fire (capped at 1h for responsiveness), then take_due atomically fires one-shots (removing them) and leaves cron schedules. A missed one-shot fires late and flags "⏰ due at T (evie was offline)"; cron schedules always fire at the next occurrence.

  • Fire-time dispatchfire_schedule re-dispatches the stored argv in-process through the same paths as immediate verbs. run gets EVIE_SCHEDULE_NAME and EVIE_SCHEDULE_TIME (ISO 8601 local) injected.

  • Removals[[schedule]] config table, ScheduleConfig, scheduler_task, DeliverySender type alias. A config that still contains [[schedule]] starts without error (serde unknown field).

  • Dependenciescroner added to Cargo.toml for cron expression parsing.

  • Tests — Timing edge cases (future/elapsed/cron/next-occurrence/downtime-missed), store round-trip (insert/get/update/remove/list/take-due), control-plane CRUD (collision→error, upsert, per-chat scoping, cancel not-found), handle generation, CLI serialization round-trip.

Closes #60

Replace the static `[[schedule]]` config-based scheduler with dynamic, persisted **Schedules** — a `Schedule` is a persisted instruction to run a delivery Verb (send/prompt/run) against a frozen chat at a future time. **What changed:** - **New `src/schedule.rs` module** — `Timing` enum (In/At/Cron), `Schedule` struct, `ScheduleStore` with atomic-temp+rename persistence to `schedules.json`, pure fire-decision functions (`next_fire`, `is_due`, `is_missed_oneshot`), and `generate_handle`. All firing-time logic is isolated from tokio::sleep. - **New CLI verbs** — `evie schedule (--in|--at|--cron) [--name] [--update] [--chat] -- <verb> [args…]`, `evie list [--json] [--chat]`, `evie cancel --name <n> [--chat]`. Clap enforces mutual exclusivity of timing flags. `--name` collision without `--update` errors; omitted name generates and prints a hex handle. - **Control plane CRUD** — `ControlRequest::Schedule`/`List`/`Cancel` variants with validation (verb whitelist, per-chat name scoping, upsert semantics). Human-readable table for `list`, raw JSON with `--json`. - **Schedule timer** — `schedule_timer` replaces the old `scheduler_task`. It sleeps until the earliest next fire (capped at 1h for responsiveness), then `take_due` atomically fires one-shots (removing them) and leaves cron schedules. A missed one-shot fires late and flags `"⏰ due at T (evie was offline)"`; cron schedules always fire at the next occurrence. - **Fire-time dispatch** — `fire_schedule` re-dispatches the stored argv in-process through the same paths as immediate verbs. `run` gets `EVIE_SCHEDULE_NAME` and `EVIE_SCHEDULE_TIME` (ISO 8601 local) injected. - **Removals** — `[[schedule]]` config table, `ScheduleConfig`, `scheduler_task`, `DeliverySender` type alias. A config that still contains `[[schedule]]` starts without error (serde unknown field). - **Dependencies** — `croner` added to `Cargo.toml` for cron expression parsing. - **Tests** — Timing edge cases (future/elapsed/cron/next-occurrence/downtime-missed), store round-trip (insert/get/update/remove/list/take-due), control-plane CRUD (collision→error, upsert, per-chat scoping, cancel not-found), handle generation, CLI serialization round-trip. Closes #60
Some checks failed
CI / check (pull_request) Failing after 1m38s
PR Triage — label changes-requested reviews / triage-review (pull_request) Successful in 1s
087be93341
weiwen force-pushed issue-60 from 087be93341
Some checks failed
CI / check (pull_request) Failing after 1m38s
PR Triage — label changes-requested reviews / triage-review (pull_request) Successful in 1s
to 6408f915c7
Some checks failed
CI / check (pull_request) Failing after 1m5s
PR Triage — label changes-requested reviews / triage-review (pull_request) Successful in 1s
2026-07-12 13:27:53 +08:00
Compare
weiwen force-pushed issue-60 from 6408f915c7
Some checks failed
CI / check (pull_request) Failing after 1m5s
PR Triage — label changes-requested reviews / triage-review (pull_request) Successful in 1s
to af7b1315da
All checks were successful
CI / check (pull_request) Successful in 1m47s
PR Triage — label changes-requested reviews / triage-review (pull_request) Successful in 1s
2026-07-12 13:29:44 +08:00
Compare
weiwen merged commit d06d05ea4d into main 2026-07-12 17:23:10 +08:00
weiwen deleted branch issue-60 2026-07-12 17:23:10 +08:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
weiwen/evie!67
No description provided.