Skip to content

feat(schedule): port scheduled prompts to V2 - #1

Merged
SirWellington merged 29 commits into
wellingtonfrom
sr1/scheduled-prompts
Oct 23, 2026
Merged

SirWellington merged 29 commits into
wellingtonfrom
sr1/scheduled-prompts

Conversation

@SirWellington

Copy link
Copy Markdown
Collaborator

Summary

  • Port scheduled tasks into the V2 Core/Server/Session architecture while preserving the schedule editor, sidebar status, run-now controls, and CLI commands.
  • Store an IANA timezone per task, default legacy schedules to the server-local timezone at migration, and use the existing server-wide auto-accept permissions setting for scheduled runs.
  • Add current V2 Protocol/Promise/Effect endpoints, global auto-accept synchronization, documentation, and assistant message timestamps.
  • Merge Wellington and upstream/dev through d8129ca.

Validation

  • bun run check passed with all 35 package typechecks successful.
  • Schedule tests passed: Core (132), Server (1), App (18), CLI (3), and Session UI component (1).
  • Client, Protocol, and docs generated checks, Core migration check, and both docs builds passed.
  • One separate SDK integration test, embedded client exposes plugin-backed web search, fails with Web search provider not found: embedded-websearch; the schedule changes do not modify that test or web-search provider code.

Upstream note

upstream/dev has since advanced to 1015cb6, 19 commits beyond the upstream tip included here. A dry-run found seven conflicts in app/settings and session code, so those newer commits are not included in this PR.

Create, edit, and delete recurring (daily/weekly/cron) or one-time prompt schedules from the desktop UI and CLI (schedule list/add/remove/enable/disable/run-now). A process-local ticker fires due tasks into delegated sessions in each task's directory.

Every create/update/remove publishes a schedule.changed event scoped to the task directory so open panels update live. Docs page added; locale dictionaries ship English placeholders pending translation.
@SirWellington

Copy link
Copy Markdown
Collaborator Author

PR Review

This PR ports scheduled prompts to V2 Sessions, including per-task IANA timezones and the shared server auto-accept setting. Focused schedule tests pass, but I found correctness issues in one-time schedule recovery, restart status, DST input, and CLI run-now reporting.

Findings (highest priority first)

  1. [Warning] packages/core/src/schedule.ts:175-195 — A claimed one-time task can be permanently lost if the server stops before its run row is recorded.

    • Why it matters: The task is disabled and no longer due, so startup will not retry it or show a run record.
    • Where: packages/core/src/schedule.ts:175-195 advances and disables the task before recordRun inserts the run row; startup only repairs rows already marked running at packages/core/src/schedule.ts:285-291.
    • Fix: Make claiming the task and creating its run record atomic, or persist a claim that startup can recover when the run row is missing.
  2. [Warning] packages/core/src/schedule.ts:285-291 — Restart recovery can resume a scheduled Session while its schedule run remains permanently marked failed.

    • Why it matters: The schedule panel can report failure even after the recovered V2 Session completes successfully.
    • Where: packages/core/src/schedule.ts:285-291 unconditionally marks running runs failed; packages/core/src/session/execution/restart.ts:219-227 resumes sessions with durable execution claims.
    • Fix: Reconcile running schedule rows that have a session ID with the recovered Session's terminal state instead of failing every row at startup.
  3. [Warning] packages/app/src/runtime/server/schedule.ts:29-32 — A one-time time inside a spring-forward gap can be converted to an earlier time.

    • Why it matters: For example, entering 2:30 AM on March 8, 2026 in America/New_York currently schedules 1:30 AM, an hour earlier than intended.
    • Where: packages/app/src/runtime/server/schedule.ts:29-32 parses the local input; packages/app/src/runtime/server/schedule.ts:119-129 iterates offsets and returns the earlier instant for nonexistent local times.
    • Fix: Resolve nonexistent times consistently with Core's next-run calculation by shifting to the next valid local time, or reject the input with a localized message.
  4. [Nit] packages/cli/src/commands/handlers/schedule.ts:107-114 — schedule run-now reports the task's previous error as the outcome of the new run.

    • Why it matters: The server returns before execution finishes, so lastError still describes the prior run and can produce a false success or failure message.
    • Where: packages/cli/src/commands/handlers/schedule.ts:107-114 checks lastError immediately; packages/core/src/schedule.ts:257-259 and 385-388 return the task snapshot after forking execution.
    • Fix: Report that the run was started and use the new run record's status when it becomes available, rather than interpreting the task-level lastError in the immediate response.

@SirWellington

Copy link
Copy Markdown
Collaborator Author

PR Review

All four findings from the previous review are addressed in commit 99123eb. The focused Core, App, and CLI schedule tests pass, and the full repository check passes.

Previous review issues

Status Issue Location
✅ One-time task claim could disable a task before recording its run packages/core/src/schedule.ts:217-266 — claim, run-row insert, and pruning are now atomic
✅ Recovered V2 Sessions could finish while the schedule run stayed failed packages/core/src/schedule.ts:342-390 — schedule status now follows terminal Session events and reconciles persisted Session outcomes at startup
✅ A one-time local time in a spring-forward gap could resolve an hour early packages/app/src/runtime/server/schedule.ts:119-140 — nonexistent local times now shift forward consistently with recurrence calculation; ambiguous times use the first occurrence
✅ CLI run-now could report a previous task error as the result of the new run packages/cli/src/commands/handlers/schedule.ts:107-115 — output now reflects the latest run record instead of task-level lastError

Preserve the server-side global auto-approve (appState.autoApprove +
global.auto-approve endpoint + permission.ts hook) that lets scheduled
prompts run unattended; reconcile the client to upstream's new settings
API (autoApproveDefault/setAutoApproveDefault) in auto-approve.tsx,
general.tsx and schedules/panel.tsx. Union the generated Schedule* and
PermissionSavedNotFoundError types, and align the mobile timeline test
with the merged behavior (agent/model omitted on mobile).
Lift the home schedules panel state into the layout context so the
shell titlebar can reach it. The mobile Home dropdown now lists
Schedules between Home and Settings, matching the projects dropdown;
tapping it from another route opens Home with the panel shown.
@SirWellington

Copy link
Copy Markdown
Collaborator Author

PR Review

This PR ports scheduled prompts to the V2 Core/Server/Session architecture, adding per-task IANA timezones, a schedule editor, sidebar status, run-now controls, and a schedule CLI command group. All four findings from the previous review are fixed and verified in the current code. The remaining issues are a CLI test harness that violates the repo's subprocess-test rules, an empty day list that silently becomes "every Sunday", and a handful of smaller nits.

Findings (highest priority first)

  1. [Warning] packages/cli/test/schedule.test.ts:188-191 — CLI subprocess tests inherit the developer's full environment instead of the repo's isolated fixture.

    • Why it matters: Each spawned CLI boots the developer's real HOME, global config, and OPENCODE_* variables, so test cost and behavior vary by machine. A cold local run failed 2 of the 4 tests in this file under Bun's default 5-second per-test timeout, so a slow CI runner is a realistic flake risk.
    • Where: packages/cli/test/schedule.test.ts:188-191 (cli() spreads process.env), packages/cli/test/schedule.test.ts:8-18 (8 parallel --help spawns), packages/cli/test/schedule.test.ts:66,130,178 (3 more spawns; 11 cold boots total).
    • Fix: Build the child env with isolatedEnv from packages/cli/test/fixture/environment.ts (it also provides a shared transpiler cache), and cut the --help spawns from 8 to 2-3: root help, schedule --help, and one subcommand help cover the flag assertions.
  2. [Warning] packages/schema/src/schedule.ts:52 — A weekly spec with an empty day list is schema-valid, but reads back as "every Sunday".

    • Why it matters: A task created through the API with days: [] runs every Sunday at the configured time. The user asked for no days, and the server silently picked one.
    • Where: packages/schema/src/schedule.ts:52 (days has no minimum-length check), packages/core/src/schedule.ts:171 (specFrom parses the stored empty string as [0], which is Sunday).
    • Fix: Reject an empty day list at the schema boundary with a minimum-length check on WeeklySpec.days, so the server returns InvalidSpecError instead of storing a task that fires on a day nobody asked for. The UI and CLI already guard against empty days.
  3. [Nit] packages/app/src/home/schedules/dialog.tsx:40-47 — Clearing the time input schedules the task at midnight.

    • Why it matters: Number("") is 0, so an emptied time field parses as 00:00 and the 09:00 fallback never triggers. A user who clears the field while thinking submits a midnight schedule with no visible warning.
    • Where: packages/app/src/home/schedules/dialog.tsx:40-47 (readTime), packages/app/src/home/schedules/dialog.tsx:151 (the fallback that is never reached).
    • Fix: Return undefined for an empty value at the top of readTime so the documented 09:00 fallback applies, or surface an explicit validation error.
  4. [Nit] packages/ui/src/icons/icon/additional-icons.ts:112-113 — The new calendar and play icons render about 20% smaller than their neighbors.

    • Why it matters: Both are drawn in a 16x16 coordinate space but declared with the default 20x20 viewBox, so the artwork scales to 80% and the play triangle sits off-center.
    • Where: packages/ui/src/icons/icon/additional-icons.ts:112-113 (art), packages/ui/src/icons/icon/additional-icons.ts:116-128 (additionalIconViewBox 16x16 list).
    • Fix: Add "calendar" and "play" to the 16x16 branch of additionalIconViewBox.
  5. [Nit] packages/core/src/database/migration/20261013030935_add_scheduled_task_timezone.ts:4,10 — The migration backfills legacy timezones from the machine that runs the migration.

    • Why it matters: This is deliberate: legacy V1 tasks ran in server-local time, so the backfill preserves their behavior. But the chosen zone is baked into the DDL without a trace, so nobody can later tell which zone a given database was stamped with.
    • Where: packages/core/src/database/migration/20261013030935_add_scheduled_task_timezone.ts:4,10.
    • Fix: Log the zone being backfilled at migration time so it is auditable.
  6. [Nit] services/www/src/docs/content/schedules.mdx:37 — The www copy of the schedules page omits the one-shot auto-disable behavior.

    • Why it matters: The web copy states "A one-time task disables after it runs", which matches the core fire() path. The www page only says the --at value must be in the future, so site readers never learn that one-time tasks turn themselves off after firing.
    • Where: services/www/src/docs/content/schedules.mdx:37 vs packages/web/src/content/docs/schedules.mdx:63.
    • Fix: Add "A one-time task disables after it runs." to the www page.
  7. [Nit] packages/cli/src/services/server-connection.ts:96 — The 401 health-check message does not mention the new --password flag.

    • Why it matters: A user who passed --username without --password is told only to set OPENCODE_PASSWORD, even though the schedule handler's own 401 message correctly offers both options.
    • Where: packages/cli/src/services/server-connection.ts:96 vs packages/cli/src/commands/handlers/schedule.ts:141.
    • Fix: Update the message to "set OPENCODE_PASSWORD or pass --password".
  8. [Nit] packages/core/src/database/schema.gen.ts:160 — Fresh-database DDL and the migration chain disagree on the timezone default.

    • Why it matters: A database created from the bootstrap schema has no default on timezone, while a migrated database carries a machine-local one. Nothing relies on the default today because task creation always writes the column, but the two paths should agree.
    • Where: packages/core/src/database/schema.gen.ts:160 vs packages/core/src/database/migration/20261013030935_add_scheduled_task_timezone.ts:10.
    • Fix: Mirror the default in schema.gen.ts, or drop the default from the migration.
  9. [Nit] packages/protocol/src/groups/schedule.ts:32 — OpenAPI identifiers use a v2.schedule.* prefix that no other group uses.

    • Why it matters: Every sibling group versions its identifiers as bare group.operation, so the prefix is an unexplained inconsistency in the generated OpenAPI document and SDK surface.
    • Where: packages/protocol/src/groups/schedule.ts:32,46,60,75,89,103.
    • Fix: Rename to schedule.list, schedule.get, and so on, then regenerate.

Previous review issues

Status Issue Location
✅ One-time task claim could disable a task before recording its run packages/core/src/schedule.ts:217-266 — claim and run-row insert are now one transaction; startup records an interrupted run at packages/core/src/schedule.ts:369-372
✅ Recovered V2 Sessions could finish while the schedule run stayed failed packages/core/src/schedule.ts:342-394 — runs now follow terminal Session events and startup reconciles with the session's terminal outcome
✅ A one-time local time in a spring-forward gap could resolve an hour early packages/app/src/runtime/server/schedule.ts:119-140 — nonexistent local times now shift forward to the next valid instant
✅ CLI run-now could report a previous task error as the result of the new run packages/cli/src/commands/handlers/schedule.ts:110-115 — output now reflects the latest run record

An empty day list was schema-valid but core read the stored empty string back as Sunday, so an API-created task silently ran weekly on a day nobody asked for. Reject it at the schema boundary and regenerate the OpenAPI specs.
Spawn the CLI with the isolatedEnv fixture (isolated HOME, config, DB, shared transpiler cache) instead of the developer's full environment, and cut the help-output test from 8 spawns to 3. A cold run previously failed 2 of 4 tests under the 5s per-test timeout.
@SirWellington
SirWellington merged commit 2d0f3a3 into wellington Oct 23, 2026
@SirWellington
SirWellington deleted the sr1/scheduled-prompts branch October 23, 2026 05:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant