Skip to content

v0.20 PR 4: Scheduler dashboard tab with create-job and Sonnet describe-assist#74

Merged
mcheemaa merged 8 commits intomainfrom
v0.20-pr-04-scheduler-tab
Apr 17, 2026
Merged

v0.20 PR 4: Scheduler dashboard tab with create-job and Sonnet describe-assist#74
mcheemaa merged 8 commits intomainfrom
v0.20-pr-04-scheduler-tab

Conversation

@mcheemaa
Copy link
Copy Markdown
Member

Summary

  • Read-only Scheduler list with filters, summary strip, and a detail drawer that holds pause/resume/run-now/delete action buttons plus a 20-entry audit history.
  • Create-job side drawer with four templates (canonical 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 the phantom_schedule MCP tool, delivery picker with ARIA-correct radio group.
  • POST /ui/api/scheduler/parse Sonnet describe-assist endpoint. Operator types a plain-English description, Sonnet returns a structured JobCreateInput proposal 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 or ANTHROPIC_API_KEY carries 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."
  • Extracted JobCreateInputSchema to src/scheduler/tool-schema.ts so the MCP tool, the UI create endpoint, and the describe-assist helper all validate through one Zod schema. Non-breaking; existing phantom_schedule tool tests still pass.

Scope

Area LOC
public/dashboard/scheduler.js 1,274
CSS additions 287
src/ui/api/scheduler.ts 377
src/scheduler/parse-with-sonnet.ts 107
src/scheduler/tool-schema.ts 30
src/scheduler/human.ts (cron-to-english helper) 72
src/scheduler/service.ts (pause/resume) +42
src/db/schema.ts (scheduler_audit_log migration) +19
Tests (scheduler.test.ts, parse-with-sonnet.test.ts, service.test.ts addends) 584
Wiring (index.ts, serve.ts, index.html, dashboard.js, tool.ts) 61
Total (net insertions) 2,909

Within the 2,925 ceiling set for this PR. scheduler.js landed 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 is role="alert", the task prompt renders via textContent to defeat XSS, the drawer is focus-trapped and full-width below 720px.

Sonnet describe-assist design (Cardinal Rule preservation)

The /parse endpoint 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 final task text, not the parsed intent.

Auth: the endpoint routes through runtime.judgeQuery from src/agent/judge-query.ts. Both Claude subscription and ANTHROPIC_API_KEY flow through the Agent SDK's subprocess. No raw Anthropic SDK dependency.

Schema extraction (non-breaking)

JobCreateInputSchema now lives in src/scheduler/tool-schema.ts. src/scheduler/tool.ts imports 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 at scheduler.createJob is rejected before the UI surfaces it.

Test plan

Automated:

  • bun test (1,690 pass, 0 fail across 132 files).
  • bun run typecheck clean.
  • bun run lint clean.
  • Scheduler API tests (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.
  • Parse helper tests (src/scheduler/__tests__/parse-with-sonnet.test.ts): 7 cases mock runtime.judgeQuery and cover happy path, malformed output, subprocess throw, cron/at schedule round-trips, and the no-runtime fallback. No real API calls in CI.
  • Service tests (src/scheduler/__tests__/service.test.ts): 6 new cases cover pauseJob and resumeJob including no-op semantics, consecutive_errors reset on resume, and the armTimer exclusion invariant for paused rows.

Manual (recommended before merge):

  • Open #/scheduler, confirm list renders with summary strip.
  • Click "+ New job", select the HN digest template, verify fields populate.
  • Switch schedule kind to Cron, enter 0 */12 * * *, watch the preview update within ~300 ms.
  • Type "alert me when pull requests get reviewed" in the describe textarea, click Fill form, confirm fields pre-fill and banner appears.
  • Save, confirm the row appears in the list and navigates into the detail drawer.
  • Run now, pause, resume, delete from the detail drawer, watch toasts and list updates.
  • Re-do the create flow on dark theme.
  • Re-do at 380px viewport (drawer full-width).

Cardinal Rule audit:

  • /parse is commented as form plumbing, not intent classification.
  • List and detail are read-only over existing data.
  • Create endpoint is UI plumbing for a structured form.
  • Task prompt and last-run-error render via textContent.

🤖 Generated with Claude Code

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.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +1197 to +1201
var payload = {
name: f.name.trim(),
task: f.task,
schedule: buildServerSchedule(f.schedule),
createdBy: "user",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment thread src/scheduler/service.ts Outdated
next_run_at = ?,
consecutive_errors = 0,
updated_at = datetime('now')
WHERE id = ?`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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.
@mcheemaa mcheemaa merged commit 20cd738 into main Apr 17, 2026
1 check passed
mcheemaa added a commit that referenced this pull request Apr 17, 2026
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)
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