diff --git a/docs/superpowers/plans/2026-09-26-scheduler.md b/docs/superpowers/plans/2026-09-26-scheduler.md new file mode 100644 index 0000000..c4ce952 --- /dev/null +++ b/docs/superpowers/plans/2026-09-26-scheduler.md @@ -0,0 +1,4689 @@ +# Scheduler Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** The Telegram bot acts on a clock (one-off and cron reminders, scheduled agent prompts, watcher scripts that wake the agent only when they print something), and every chat has its own time zone that the system prompt, `bash`, watcher scripts, `claude -p` and the timestamp header all follow. + +**Architecture:** A new extension project `extensions/Volcanic.AgenticCore.Extensions.Scheduler` (Abstractions + Cronos only) holds the job model, `JobStore`, `ScriptRunner` (through the DI `IProcessSpawnPolicy`), the process-wide `JobScheduler` on `TimeProvider`, and a per-session `SchedulerExtension` with the `schedule` tool; the host implements `IJobDelivery`. The core gains `AgentSession.SetTimeZone` and a zone-aware environment section in `SystemPromptBuilder`; the Claude Code provider passes the session zone as `TZ` to `claude -p`. The Telegram host adds `ChatSettingsStore` (per-chat zone, `/telegram/chat-settings.json`), `ChatTimeZoneService`, `/timezone`, the `set_timezone` tool, `TelegramJobDelivery`, `SchedulerHostedService`, `/jobs` and `/cancel`. + +**Tech Stack:** .NET 10, C#, xunit 2, Cronos 0.13.0 (new, scheduler extension only), `Microsoft.Extensions.TimeProvider.Testing` 10.9.0 (already in `Volcanic.AgenticCore.Extensions.Tests` and `Volcanic.AgenticCore.Telegram.Tests`; T1 adds it to `Volcanic.AgenticCore.Tests`), Telegram.Bot 22.10.3.1. Spec: `docs/superpowers/specs/2026-09-26-scheduler-design.md`. + +## Global Constraints + +- Work only in the worktree `D:\Projects\Names\wt-scheduler` (branch `feat/scheduler`). Every Bash command starts with `cd /d/Projects/Names/wt-scheduler && ...`; Read/Edit/Write use absolute paths under `D:\Projects\Names\wt-scheduler`. Never switch branches, never `git worktree`/`checkout`/`reset --hard`/`push`. Never touch `D:\Projects\Names\VolcanicAgenticCore` or another `wt-*` directory. +- Wave D runs alone, but keep edits to shared host files (`UpdateRouter`, `ChatRegistry`, `ChatCommands`, `Program.cs`, `TelegramOptions`, `BotSetupService`, `VolcanicAgenticCore.slnx`, test `.csproj` files) focused and minimal. Scheduler-specific host code goes into new files (`hosts/Volcanic.AgenticCore.Telegram/Scheduling/`). +- `Directory.Build.props` sets `TreatWarningsAsErrors=true` for every non-test project, so a new warning fails the build. Watch CS9113 ("parameter is unread") on primary constructors: add a constructor parameter only in the task that reads it. Test projects turn warnings-as-errors off. +- Tests: xunit 2. No test calls a paid API or a real Telegram, Google, OpenAI, npm or other network endpoint. Use `FauxProvider`, `FakeBotClient`, `FakeTimeProvider`, and the fakes this plan adds (`FakeJobDelivery`, `FakeScriptRunner`, `FakeRedactor`). Zones in tests are fixed (`Europe/Berlin`, `Asia/Tokyo`, `America/New_York`), never the machine's, except where a test is explicitly about the `TimeZoneInfo.Local` fallback. +- **Watcher invariant (review this in every scheduler task):** a watcher's silent tick makes zero model calls, creates or wakes no session, and writes nothing to the transcript. Running a watcher script touches only `IScriptRunner`, `IJobDelivery.WorkspaceFor` and `IJobDelivery.TimeZoneFor`; `TelegramJobDelivery.WorkspaceFor`/`TimeZoneFor` never create or look up a chat session. +- Real-process `ScriptRunner` tests are Unix-only (`if (OperatingSystem.IsWindows()) return;` as the first line). The `JobScheduler`, `schedule` tool and Telegram scheduler-logic tests use `FakeScriptRunner` and `FakeTimeProvider` and run on Windows. +- Known baseline on the Windows dev machine: 3 tests in `Volcanic.AgenticCore.Tests` fail on `main` before any change (`SystemPromptBuilderTests.Loads_AGENTS_md_in_preference_to_CLAUDE_md_then_persona_and_memory`, `ExtensionLoaderTests.Discover_loads_a_real_assembly_from_the_user_directory`, `BuiltInToolTests.Ls_find_and_grep`). "Green" means no failures other than these three. T1 has to change the `SystemPromptBuilder` constructor call inside the first one (it must still compile), but must not try to fix its Windows failure. +- Time comes only from `TimeProvider` in the scheduler: no `DateTime.Now`, `DateTimeOffset.Now`, `Task.Delay` or `System.Threading.Timer`; the scheduler's timer is `TimeProvider.CreateTimer`. +- Exact values from the spec (copy verbatim): + - ids: 5 characters from `abcdefghjkmnpqrstuvwxyz23456789`; store `/scheduler/jobs.json`, format `{ "version": 1, "jobs": [...] }` in `VolcanicJson` camelCase; temp file `jobs.json..tmp`; corrupt file renamed `jobs.json.corrupt-`; + - validation: name required, at most **80** characters; exactly one of `at`/`cron`; cron `CronFormat.Standard` (5 fields, macros allowed; seconds and `H` rejected); one-off `at` in the future with **60 s** grace; `text` at most **4000** characters; `timeoutSeconds` **1..3600**, watchers only, default **300**; at most **50** jobs per target (`MaxJobsPerTarget`); + - capture: first **48 KiB** and last **16 KiB** of each stream; stderr tail = last **20** lines; stdout `Trim()`; + - timer capped at **1 h**; late threshold **60 s**; `StopAsync` waits at most **10 s**; + - messages: reminder `⏰ `; prompt `[Scheduled job "" ()]\n`; watcher match `[Scheduled job "" ()] The watcher script printed:\n`; failure `⚠️ Job "" () failed (exit [, timed out]):\n```\n\n```` ; late direct text `(due