v0.20 PR 4: Scheduler dashboard tab with create-job and Sonnet describe-assist#74
v0.20 PR 4: Scheduler dashboard tab with create-job and Sonnet describe-assist#74
Conversation
Single source of truth for the MCP tool, the dashboard create endpoint, and the Sonnet describe-assist endpoint. No behavior change.
Pause flips status to paused; armTimer already excludes paused rows. Resume flips back to active, recomputes next_run_at, and clears consecutive_errors so a job paused mid-backoff gets a clean retry budget.
Single one-shot Messages call with forced tool-use so Sonnet returns a structured JobCreateInput proposal. The operator reviews and edits the proposal before saving; Sonnet never drives the agent at run time. Cardinal Rule preserved: form plumbing, not user-intent classification.
Captures UI-originated mutations (create/pause/resume/run/delete). Agent writes via phantom_schedule bypass this path; future PR may hook the service layer directly.
…e, run, delete, audit, parse)
Nine endpoints, cookie-auth gated, Zod-validated. Create routes through
scheduler.createJob for parity with the phantom_schedule MCP tool. Pause/
resume/run/delete mutate via the corresponding service methods. Preview
returns { nextRunAt, humanReadable, error } for the create form's live
next-run widget. Audit log writes one row per UI mutation.
/parse wraps the Sonnet describe-assist helper. The endpoint accepts a
plain-English description, returns a structured proposal for the operator
to review before saving. Cardinal Rule preserved: this fills a form, it
does not drive the agent.
…plates and describe-assist) List view with filter bar and summary strip. Click a row to open the detail drawer with pause/resume/run-now/delete actions and a 20-entry audit history. + New job opens a create drawer with four templates (canonical hn-digest, daily-standup, pr-review-reminder, weekly-metrics), four schedule kinds (every/daily/cron/once), live next-run preview, and a Sonnet describe-assist textarea that fills the form from plain English. Task prompt and last-error blocks render via textContent so arbitrary agent prompts cannot escape to HTML. Drawer is ARIA-correct, focus-trapped, and full-width below 720px. Also trimmed parse-with-sonnet comments and corrected the migration test indices for the new scheduler_audit_log entry.
Phantom supports both Claude subscription auth and ANTHROPIC_API_KEY; the raw Anthropic SDK would break the subscription path. Route the describe- assist call through runtime.judgeQuery so it inherits the same auth, provider, and base URL as every other LLM call in Phantom. Every failure mode (auth missing, network, timeout, malformed output) now collapses to 422 with 'Could not parse description, please fill the form manually.' The frontend keeps the form blank, shows the inline error, and leaves the button enabled for retry or manual entry.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 19eddea574
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| var payload = { | ||
| name: f.name.trim(), | ||
| task: f.task, | ||
| schedule: buildServerSchedule(f.schedule), | ||
| createdBy: "user", |
There was a problem hiding this comment.
Propagate enabled state when submitting create payload
In submitCreate, the request body is built without state.create.form.enabled, even though the form exposes and updates the #scheduler-enabled toggle. That means an operator can uncheck “Enabled” and still create an active job that starts running, which can trigger unintended executions in production. Either remove this control or wire an enabled field through the UI/API/service so the saved job state matches what the operator selected.
Useful? React with 👍 / 👎.
| next_run_at = ?, | ||
| consecutive_errors = 0, | ||
| updated_at = datetime('now') | ||
| WHERE id = ?`, |
There was a problem hiding this comment.
Guard resume updates to paused jobs only
resumeJob is documented as resuming paused jobs, but the SQL WHERE clause only filters by id, so failed or completed jobs are also force-set back to active. This allows callers to revive terminal jobs (including completed one-shot jobs) and bypass intended status semantics. Add a paused-status guard in the update path (or return a no-op when not paused) to keep lifecycle behavior consistent.
Useful? React with 👍 / 👎.
…code) P1 (Codex): the create-form 'Enabled' toggle was wired to client state but never sent in the POST payload, so unchecking it still produced an active job. Threaded enabled through JobCreateInputSchema, JobCreateInput type, the API handler's serviceInput shaping, the service INSERT, and the scheduler.js submit body. Added round-trip tests for enabled=false and the omitted-defaults-true case. P2 (Codex): resumeJob lacked a status guard, so failed and completed jobs (including one-shots that already deleted themselves) could be force-revived to active and bypass terminal-state semantics. Added 'AND status = paused' to the UPDATE and an early-return for non-paused input. Updated the resumeJob-resets-errors test to pause first, which reflects the actual operator flow. Added a no-op test covering active, failed, and completed inputs. P2 (reviewer): em dash at scheduler.js:238 violated the global no-em-dash rule. Replaced with '-'. P2 (reviewer): unreachable 'daily' branch in applyTemplate. None of the four templates use kind=daily; that kind is UI-only and translated to cron at submit. Removed and noted in a comment. P2 (reviewer): stale serve.ts comment claimed the parser fell back to the raw Anthropic SDK + env var. Updated to reflect the Agent-SDK subprocess path.
Bumps the version to 0.20.0 in every place it's referenced: - package.json (1) - src/core/server.ts VERSION constant - src/mcp/server.ts MCP server identity - src/cli/index.ts phantom --version output - README.md version + tests badges - CLAUDE.md tagline + bun test count - CONTRIBUTING.md test count Tests: 1,799 pass / 10 skip / 0 fail. Typecheck and lint clean. No 0.19.1 or 1,584-tests references remain in source, docs, or badges. v0.20 shipped eight PRs on top of v0.19.1: #71 entrypoint dashboard sync + / redirect + /health HTML #72 Sessions dashboard tab #73 Cost dashboard tab #74 Scheduler tab + create-job + Sonnet describe-assist #75 Evolution Phase A + Memory explorer tabs #76 Settings page restructure (phantom.yaml, 6 sections) #77 Agent avatar upload across 14 identity surfaces #79 Landing page redesign (hero, starter tiles, live pages list)
Summary
hn-digest,daily-standup,pr-review-reminder,weekly-metrics), four schedule kinds (Every interval, Daily time, Cron, Once), live debounced next-run preview, full validation parity with thephantom_scheduleMCP tool, delivery picker with ARIA-correct radio group.POST /ui/api/scheduler/parseSonnet describe-assist endpoint. Operator types a plain-English description, Sonnet returns a structuredJobCreateInputproposal that fills the form. Operator reviews and edits before saving. Cardinal Rule preserved: form plumbing, not intent classification. Routes through the Agent SDK subprocess (runtime.judgeQuery) so the operator's existing Claude subscription orANTHROPIC_API_KEYcarries through the same auth path as every other LLM call. Any failure collapses to 422 with "Could not parse description, please fill the form manually."JobCreateInputSchematosrc/scheduler/tool-schema.tsso the MCP tool, the UI create endpoint, and the describe-assist helper all validate through one Zod schema. Non-breaking; existingphantom_scheduletool tests still pass.Scope
public/dashboard/scheduler.jssrc/ui/api/scheduler.tssrc/scheduler/parse-with-sonnet.tssrc/scheduler/tool-schema.tssrc/scheduler/human.ts(cron-to-english helper)src/scheduler/service.ts(pause/resume)src/db/schema.ts(scheduler_audit_logmigration)index.ts,serve.ts,index.html,dashboard.js,tool.ts)Within the 2,925 ceiling set for this PR.
scheduler.jslanded at 1,274, which is over the 1,000 soft ceiling; the surface (list, detail drawer with four action buttons and an audit list, create drawer with four templates, four per-kind schedule editors, live preview, describe-assist, delivery radio group, validation) drove it. Not a regression in quality: every field has a label, every error isrole="alert", the task prompt renders viatextContentto defeat XSS, the drawer is focus-trapped and full-width below 720px.Sonnet describe-assist design (Cardinal Rule preservation)
The
/parseendpoint helps the operator fill a form. Sonnet proposes name, description, task, schedule, and delivery fields. The frontend pre-fills the form and shows a banner: "Filled from your description. Review and edit before saving." The operator must interact with the form before the Save button does anything meaningful. At run time, the agent that fires the job sees only the operator's finaltasktext, not the parsed intent.Auth: the endpoint routes through
runtime.judgeQueryfromsrc/agent/judge-query.ts. Both Claude subscription andANTHROPIC_API_KEYflow through the Agent SDK's subprocess. No raw Anthropic SDK dependency.Schema extraction (non-breaking)
JobCreateInputSchemanow lives insrc/scheduler/tool-schema.ts.src/scheduler/tool.tsimports it; the MCP tool's input validation is unchanged field-for-field. The UI create endpoint validates the same way. The describe-assist path re-validates Sonnet's output against the same schema, so a proposal that would fail atscheduler.createJobis rejected before the UI surfaces it.Test plan
Automated:
bun test(1,690 pass, 0 fail across 132 files).bun run typecheckclean.bun run lintclean.src/ui/api/__tests__/scheduler.test.ts): 29 cases cover every endpoint: list, detail, audit, create (including duplicate 409, oversized task 413, invalid schedule 400, invalid delivery target 400), preview (happy/error), pause/resume/run/delete, 404s, 405 method guard, parse endpoint via injected parser.src/scheduler/__tests__/parse-with-sonnet.test.ts): 7 cases mockruntime.judgeQueryand cover happy path, malformed output, subprocess throw, cron/at schedule round-trips, and the no-runtime fallback. No real API calls in CI.src/scheduler/__tests__/service.test.ts): 6 new cases coverpauseJobandresumeJobincluding no-op semantics,consecutive_errorsreset on resume, and the armTimer exclusion invariant for paused rows.Manual (recommended before merge):
#/scheduler, confirm list renders with summary strip.0 */12 * * *, watch the preview update within ~300 ms.Cardinal Rule audit:
/parseis commented as form plumbing, not intent classification.textContent.🤖 Generated with Claude Code