Skip to content

feat: machine-owned scheduled tasks with agent-proposed schedules [risk:high] - #931

Merged
Leeeon233 merged 33 commits into
mainfrom
feat/scheduled-tasks
Sep 26, 2026
Merged

Leeeon233 merged 33 commits into
mainfrom
feat/scheduled-tasks

Conversation

@lodystage

@lodystage lodystage Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Risk: 🔴 high | Confidence: medium — new persisted schedule documents/registry, a new prepared turn status, and machine-side dispatch; covered by deterministic engine/store/UI tests, not yet by a packaged end-to-end run or the adversarial review pipeline.

Related issue

Problem / pressure

People want an Agent to do recurring work (a nightly review, a weekday summary) without typing the prompt each time. A cron line inside one Agent session dies with that session and machine, and nobody can see or pause it. The work has to be owned by a machine, survive restarts, be visible and pausable from any client, and never start more runs than asked for.

Summary

  • Model (packages/shared/src/schedule-*): a schedule is a Loro document plus a row in the workspace Schedule Registry Flock. Triggers are manual | once | interval | cron. A destination is a new chat per run, one chat the schedule owns (created by its first run, id derived from schedule id + epoch), or an existing chat. project is optional, so a schedule can be a plain chat.
  • Execution (apps/cli/src/lib/schedules): the owning machine plans slots from the injected clock. It records each run intent and cursor advance in one SQLite transaction. It freezes a prepared user turn and hands it to the ordinary dispatch watcher. Misfire and overlap policies are bounded. Execution slots are shared per Agent, and a disconnected Registry blocks new handoffs.
  • Commands: lody schedule and MCP list/show/propose/pause. An Agent can only propose. lody_schedule_propose takes a named rule, never cron, and writes a schedule_proposal card into the conversation. Pressing Create on the card is the creation.
  • Machine clock: the CLI publishes its IANA zone as MachineMeta.timeZone at registration (optional, under 50 bytes). Schedules are authored on the owning machine's clock and there is no zone picker; older CLIs fall back to the viewer's zone.
  • UI (packages/components/src/components/schedules, developer + beta gated): list, editor and detail. The editor is shaped like the composer: name, prompt, and a bottom row of destination / Agent / project pills in one box. Save problems appear as marks next to the control that fixes them. The frequency menu covers every day / weekdays / weekly days / monthly dates / every N hours / every N minutes / once / manual. There is no cron editor, and legacy rules are shown read-only with Replace. The editor uses the shared Agent/Project selectors and one save-blocker rule. It follows the new app chrome and one alignment grid.
  • Merged latest main: Tasks was removed upstream (chore: remove the workspace Tasks product [risk:high] #800). Schedules keep their own scheduleToolsEnabled gate and execution slots, and use the new history API.

Visual explanation

sequenceDiagram
    participant UI as App / Agent card
    participant Doc as schedule-<id> doc + Registry
    participant Host as Owning machine (CLI)
    participant DB as schedules.sqlite
    participant S as Session
    UI->>Doc: save definition, publish fingerprint
    Host->>Doc: subscribe, verify fingerprint & owner
    loop each due slot
        Host->>DB: IMMEDIATE tx: advance cursor + run intent
        Host->>S: write prepared user turn
        Host->>S: commit latestUserMsgId (point of no return)
        S-->>Host: ordinary dispatch watcher runs the turn
    end
Loading
scheduleRunIds(runKey, destination)
  new_session       -> session id derived per run
  own_session{e}    -> session id = uuidV8(scheduleId, e)   (never stored)
  existing_session  -> the chosen chat, validated: owner, machine, Agent

Before / after

Before After
Recurring work = a cron inside one live Agent session Machine-owned schedules, visible, pausable and runnable from any client
Agent could not set up automation Agent proposes a card; one click creates it

Test plan

  • tsgo --noEmit clean in packages/shared, packages/components, apps/cli, apps/electron.
  • Vitest (NODE_ENV=test): shared 1222 pass; components 4038 pass; CLI 2907 pass. 5 fail in tests/gh-shim-script.test.ts, which is identical to main and depends on the host's gh setup.
  • Machine-clock conversion across a DST change and save-issue placement are unit-tested.
  • Deterministic engine tests: an owned-chat schedule's two runs land in one Session; a manual trigger is never planned; a cron 0-7 weekday range survives edits; production prepareSessionInput yields no project for a chat-only schedule.
  • check-i18n, platform/public boundary, and Code Collab import guards pass; oxlint 0 errors.
  • Storybook screenshots of list, editor (all rule kinds and destinations, narrow panel), and the proposal card, in light/dark and en/zh_CN. Alignment was measured with Playwright: one left line, one right inset, 44px rows.
  • Not run: pnpm check, a packaged desktop end-to-end run, and the high-risk adversarial review.

🤖 Generated with Claude Code

Leeeon233 and others added 14 commits September 6, 2026 01:54
Implement local-first Schedule definitions and Registry, durable SQLite run recovery, deterministic Session handoff, shared Task and Agent occupancy, bounded MCP and CLI commands, and beta UI across clients. Include synchronization, permission and restart regression coverage, export support, and repository formatting.

Model: gpt-6
Show actionable missing requirements above Save and use the same validation for button state and submission. Add localized copy, a Storybook state, and regression coverage.

Model: gpt-6
…igh]

Risk: high · Confidence: high

The schedules list and editor were a stack of stand-alone form fields, and a
schedule could not exist without a project. This reworks both, and makes the
"no project" case real end to end rather than something the UI allows and the
backend rejects.

Data model (pre-release, deliberately breaking):

- `project` is optional on `ScheduleDefinition`, the Registry row
  (`projectKind`/`projectKey`), the draft schema and the mirrored Loro map.
  An absent project means a chat-only run: no local-project ledger lookup, no
  `project` on the CLI's resolved target, and `buildProjectOptions` contributes
  nothing to the prepared Session. A project that IS set is still validated
  against the owning machine before handoff. Nothing substitutes a default.

Frequency:

- New `packages/shared/src/schedule-recurrence.ts` maps between a named
  recurrence (daily / weekdays / weekly / monthly / interval / once / custom)
  and the persisted trigger, in both directions. The stored union is unchanged:
  cron already expresses all of it, and widening it would make the CLI engine,
  ledger and MCP each learn a second calendar.
- A rule the picker cannot name opens as Custom with the expression verbatim and
  still editable. An untouched rule is re-emitted byte for byte, so opening a
  `MON-FRI` schedule and pressing Save cannot rewrite it to `1-5` or invalidate
  its definition fingerprint.
- New schedules default to every day at 09:00.

Editor:

- Title and prompt carry their guidance in the placeholder; the accessible name
  stays on the field, so nothing takes vertical space to say "Name".
- Frequency and run configuration are grouped property cards (label left, live
  value right) whose rows appear only when they apply. Agent and Project reuse
  the shared composer menus rather than a second set of pickers.
- Removed every repeated confirmation on this path: the automation consent
  checkbox, the directory-scope checkbox (the worktree switch IS that choice,
  with an inline note when off), the full-access warning, and the elevated
  resume dialog. Ownership, machine protocol capability and an explicit
  permission mode still gate save, resume and run, each with a visible reason
  above Save. `collectScheduleSaveBlockers` is the single rule driving the
  button, the explanation and the submission guard.

List:

- Rebuilt as a real table: name, frequency in plain language, next run in the
  rule's own zone, target agent and project (or "Chat only"), and actions.
  Header and rows share one column template with a fixed-width actions column —
  a content-sized one redistributed the `fr` columns per row. Stacks to a
  readable card on narrow screens; empty, loading, paused and offline states
  redesigned.

Tests: recurrence round-trips and lossless re-emission, chat-only persistence
through the repository and Registry, the save-blocker rule, and the editor's
opening/saving behaviour for each rule shape — all on injected clocks.

Model: claude-opus-5[1m]
…dium]

Risk: medium · Confidence: high

Follow-up to 171f37c. "Custom" was still a five-field cron text box, so the
one rule shape the named picker cannot express was also the one shape that
dropped a person into raw cron. It is now edited field by field.

- New `packages/shared/src/schedule-cron-fields.ts` reads a five-field
  expression as five independently editable fields, each in one of
  `every | step (optionally within a range) | range | list | raw`.
  `CronFieldRow` renders one row per field: a mode picker plus only the
  controls that mode needs — an interval box, a from/to pair, a searchable
  value multi-select, and weekday toggles for the day-of-week field.
- Two rules keep this from narrowing what a person can express. Every field
  offers every mode, because a trimmed per-field menu leaves a stored mode
  unlisted — a weekday of `1-5` is a `range`, and a menu without `range`
  rendered that field blank and would have lost it on the next edit. And a
  field is only claimed by a structured mode when re-formatting reproduces its
  original spelling, so `MON#2`, `JAN`, `L` and `00` stay `raw` in their own
  text box, visible and editable. `format(parse(x)) === x` for any five-field
  text, and an untouched rule is still re-emitted byte for byte.
- Changing a field's representation no longer changes the schedule: switching
  hour `9-17` to a list seeds 9…17 rather than resetting to midnight, and the
  raw escape hatch opens on the text that is already there.
- The whole-expression text box remains, but as an opt-in "Edit as text" row
  under the rule rather than the default surface. The list and the editor
  summary now describe a custom rule in words instead of printing cron.
- `withCronField` refuses a non-five-field expression or an absent field, so a
  controlled `<Select>` emitting an empty value while resetting cannot rewrite
  the rule. That path crashed during testing.

Also closes a gap in the previous commit's own claim: nothing asserted the
chat-only DISPATCH path, only its schema. The two decisions that place a run in
a working directory are now `schedule-run-preparation.ts`, and its tests pin
that a run with no project reaches `prepareSessionInput` with no `repo`,
`localProject`, `branch` or `worktree` selector, with no `project` key on the
target, and requiring no local-project ledger lookup.

Model: claude-opus-5[1m]
Risk: medium · Confidence: high

Review findings on 082a6f1..171f37c.

P1 — `schedule-format.ts` passed `i18n.language` straight into `Intl`. Lody
spells Chinese `zh_CN`, which `Intl.DateTimeFormat`, `Intl.ListFormat` and
`Intl.RelativeTimeFormat` all reject with a RangeError, so the schedules list,
the editor preview and the weekday pickers threw for every Chinese user. Every
entry point now normalizes through the repository's existing
`toIntlLocaleOrEn`, and this file stays the only place here that constructs an
`Intl` formatter so a future caller cannot reintroduce it.

`tests/schedule-locale.test.tsx` renders the real list, editor and custom field
pickers under `zh_CN`; it fails on the previous code. The pure formatters were
already individually correct while the pages they back crashed, which is why
the regression is at the component boundary rather than only on the helpers.

Also from the review:

- The target column now names the machine. It was passed and never rendered, so
  two same-named agents on different machines — and two chat-only rows — were
  indistinguishable.
- The last-run action defaulted to `opacity-0` until hover, which no touch
  device can discover. It is visible by default and hidden only behind
  `[@media(hover:hover)]`.
- The narrow-screen New button's only text is `hidden sm:inline`, so it had no
  accessible name. It has an `aria-label` now.
- `PropertyRow` sized its label column from a `sm:` breakpoint, which cannot see
  the panel a schedule renders in. Both tracks are content-driven now
  (`minmax(0,auto)` / `minmax(0,1fr)`), matching `settings/compact-layout.tsx`.
  A hugging control track let the seven weekday toggles truncate the label away
  in a 360px panel; a hugging label track let a long translated label eat the
  row. `EditorInNarrowPanel` renders that case at a desktop viewport, and the
  two Advanced labels are shortened so their translated values fit rather than
  ellipsize.

Finally, the CLI regression the previous commit claimed but did not add.
`schedule-chat-mode-dispatch.test.ts` drives a project-less definition through
the real engine, SQLite ledger and Loro history, asserting that no `repo`,
`branch`, `localProject` or `worktree` selector reaches session preparation,
that the target carries no `project` key, and that the run still hands off one
durable dispatched turn. Both it and the unit test fail when the pre-change
"a schedule always has a project" assumption is reintroduced.

Model: claude-opus-5[1m]
Risk: medium · Confidence: high

Correctness review finding. `parseWeekdayToken` folded 7 onto 0 before the
range it bounds was expanded, so `0-7` collapsed to the range `0-0`. Standard
cron accepts both 0 and 7 for Sunday, which makes `0-7` and `1-7` every day;
the editor read them as "Sundays only".

An untouched rule is re-emitted verbatim, so nothing was wrong until an
unrelated edit. Changing only the time or the time zone on `0 9 * * 1-7` then
rewrote it to `… * * 0` and silently dropped Monday through Saturday. Verified
against croner: `0-7` and `1-7` fire daily, `5-7` fires Fri/Sat/Sun.

Ranges are now expanded over their raw 0..7 bounds and folded onto Sunday once,
afterwards. `5-7` reads as Fri/Sat/Sun, `0-7` and `1-7` as daily, and a bare `7`
still as Sunday.

Coverage is the edit, not just the round trip, because the round trip was
already passing while the rule was being lost: a new test replays each
Sunday-7 rule after a time and a zone change and asserts the same weekdays, and
those expressions joined the exact-instant round-trip corpus. Reintroducing the
fold reproduces the defect in three tests.

Two adjacent guards, since this is a hazard class rather than one typo:
`schedule-cron-fields.ts` deliberately keeps weekday bounds at 0..6 so a
Sunday-7 range stays raw editable text rather than being approximated by
pickers that cannot draw a day 7 (now pinned by a test), and the weekday toggle
derivation folds and de-duplicates so it stays correct if those bounds ever
widen.

Model: claude-opus-5[1m]
Risk: medium · Confidence: high

Three defects with one root cause: the Custom editor re-derived its edit state
from a serialized expression on every render.

1. Switching a field's mode rewrote execution semantics. Choosing "On selected"
   for Day of month replaced `*` with `1,2,…,31`. Only `*` means unrestricted,
   and cron ORs day-of-month with day-of-week, so a weekdays-only rule became a
   daily one. Verified with croner: `*/20 9-17 * * 1-5` does not match Saturday
   2026-09-12T09:00:00Z; the enumerated form does.
2. Clearing the last selection produced a four-field string (`*/20 9-17 * * `).
   The parent failed to parse it, every picker disappeared, and the only way
   back was the raw cron box.
3. Custom text would not stay selected. The parent serialized and re-parsed on
   each keystroke, so a `raw` field snapped back to a range as soon as its text
   happened to parse, unmounting the input mid-edit.

The fix is one design, not three patches. `ScheduleRecurrence['custom']` now
carries the five `CronFields` — the fields ARE the state — plus an optional
`draftText` while the whole expression is being typed. Nothing re-derives an
edit mode from a string.

An incomplete field is first-class rather than a short expression:
`formatCronExpression` throws, `withCronField` returns `null`, and the form
names the unfinished field above a disabled Save. Switching away from `*` leaves
`list` and `range` UNFINISHED instead of inventing an exhaustive value; values
carry over only between already-finite modes, which is semantics-preserving.
When day-of-month and weekday both restrict, the editor states the OR out loud.

Coverage is behavioural and mutation-checked. Reinstating the enumeration, the
short-expression serialization, or the serialize-and-re-parse render each
reproduce their defect in the new tests. Mode switches assert real croner
`match` results rather than text. A driven browser confirms all three: the
pickers survive an emptied selection, Custom text keeps its control AND focus
across keystrokes, and a mode switch blocks Save naming the field.

Two things found while fixing: `*/20 9-17 * * ` also arose from an emptied raw
field, and every field's range inputs shared the accessible name "From", so
they are now named per field.

Also adds the CLI regression this branch previously described too loosely.
`schedule-session-preparation.test.ts` calls the PRODUCTION `prepareSessionInput`
and asserts a chat-only run yields a `SessionMeta` with no `project`,
`repoFullName` or `baseBranch`, and with the engine's frozen session and turn
ids. The earlier test exercised the engine and ledger but stubbed `prepare`, so
it did not cover this.

Model: claude-opus-5[1m]
Keep bounded interval drafts distinct from unrestricted runs, retain cleared inputs, and normalize Chinese validation labels. Cover clearing and restoring fields, and satisfy repository lint and translation checks.

Model: gpt-6
…o cron [risk:high]

Risk: high · Confidence: high

The editor asked ordinary people to think in cron fields. This reduces it to
the questions they actually have — when, and where does the result go — and
adds the two things they asked for that the model could not express.

Trigger:

- A schedule is now timed OR manual (`trigger.kind === 'manual'`). A manual
  task is never planned by the clock; `nextScheduleSlot` and
  `latestScheduleSlot` return nothing for it and only a Run request creates a
  run. The editor is a two-way toggle above the frequency card, and it keeps
  the last timed rule while on Manual so flipping back does not reset it.
- Frequency is a short menu: every day, every weekday, every week (pick days),
  every month (pick dates), every few hours, every few minutes, once. Rows
  slide in as the choice changes (framer-motion, reduced-motion aware). Steps
  that divide the clock are written as aligned cron (`*​/15`, `0 *​/6`) so
  "every 15 minutes" means :00 :15 :30 :45; others become intervals. The
  persisted trigger union is unchanged.
- The field-by-field cron editor is gone. A stored rule the menu cannot name is
  `unsupported`: shown read-only with its summary and a Replace action, and
  re-emitted verbatim on save. There is no cron input left in the UI.

Destination (`definition.destination`, pre-release schema addition):

- `new_session` — a fresh chat per run, as before.
- `own_session` — ONE chat the schedule owns, created by the first run and
  continued by every later run. Its id is derived from the schedule id and an
  `epoch`, so nothing is stored, a retry or restart cannot create a second
  chat, and "Start a new chat" is simply `epoch + 1`.
- `existing_session` — a chat the person picks.

`scheduleRunIds` is the one place that chooses the Session; turn and source
ids stay unique per run. Before handoff, and again on create/edit through the
command service, `destinationSessionProblem` requires a shared chat to be
absent (owned only) or the owner's, on this machine, driven by this Agent —
exactly the fields `materializePreparedSessionInput` refuses to change — and
reports `SESSION_UNAVAILABLE` as a recoverable configuration problem rather
than a late identity conflict. A schedule sending into a chat has no project
of its own; the editor hides the Project row, follows a picked chat's Agent,
and names an Agent or machine mismatch above a disabled Save. The list shows
"Manual" / "On demand" and the destination in place of a project.

Tests: the engine test drives two manual runs of an owned-chat schedule
through the real ledger and Loro history and asserts one Session with two
prepared turns and an advanced dispatch pointer; the destination validator is
covered for absent/deleted/other-owner/other-machine/other-Agent chats; the
recurrence mapping round-trips every named rule to the same instants, keeps
the Sunday-7 fix, and leaves unsupported rules byte-identical; component tests
cover the manual toggle, the monthly date grid, replacing an unsupported rule,
and the save blockers for each destination. A driven browser walks every
frequency option and destination without page errors. That walk caught one
defect fixed here: a controlled Select emits '' while its option list is
swapped (hours → minutes), which read as a zero step.

Model: claude-fable-5-1
…:low]

Risk: low · Confidence: high

3eccfe4 added a Chinese render test for emptying and refilling a cron field
picker. That picker was removed in 8b93859 (Custom is gone; unsupported rules
are read-only), and the test merged in cleanly because it did not conflict
textually. The remaining Chinese coverage still renders the list, the editor,
the monthly date grid and a manual task under `zh_CN`.

Model: claude-fable-5-1
… [risk:medium]

Risk: medium — new persisted `schedule_proposal` system notice and a changed
MCP `lody_schedule_propose` input; no existing schedule data changes shape.
Confidence: high — CLI propose paths, target resolution and the card have
deterministic tests; the card was rendered in Storybook in both languages.

`lody_schedule_propose` now takes a NAMED rule (minutes/hours/daily/weekdays/
weekly/monthly/once/manual), an optional destination and an optional explicit
target, validates the rule as a trigger, and writes one idempotent
`schedule_proposal` system notice into the conversation. The tool description
tells the agent to ask a short question instead of guessing when the prompt,
rule or destination is unclear.

`ScheduleProposalNotice` renders that notice as a card. Create IS the
creation — no form follows — using the proposal id as the schedule id so a
double click cannot make two. `resolveScheduleProposalTarget` defaults the
Agent, effective mode, machine and project to the conversation the proposal
was made in; a named Role / Agent / machine / project overrides only that
part, and a named Agent without a Role gets the builtin default mode rather
than another Agent's. The card runs the same save blockers as the editor and
is behind the same developer + beta gate.

Model: claude-fable-5-1
Main removed the workspace Tasks product (#800). Schedules keep their own
pieces: the `scheduleToolsEnabled` gate now travels alone through turn
config, bootstrap, and the MCP env/header; `AgentExecutionSlots` loses its
Task occupancy; `buildProjectOptions` moves into `schedules/`; the shared
Agent menu's copy moves from `tasks.*` to `agentRunConfig.*`.

Adapted to main's API changes: `prepared` joins `SessionTurnStatus`, the
history write schema, and the seen-regression guard; schedule history access
uses `readSessionHistory` / `appendTurn` since `SessionDocument` no longer
exposes `getHistory` / `updateHistory`; `TaskAgentRef` becomes a local
`AgentRunRef`.

Model: claude-opus-5-5[1m]

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adopt main's #802/#816 rules across the list, editor, detail page and
proposal card: `em` sizes of `--ui-font-size` instead of fixed px/xs,
`font-normal` everywhere, sentence-case section labels, 0.5px edges, and
`SETTINGS_ROW_CARD_CLASS` cards inside `data-settings-surface` so light
themes lift them to the popover fill. The editor's second section is now
"Where it runs" so it no longer repeats its first row's "Send to" label, and
the English beta help no longer describes Tasks.

Model: claude-opus-5-5[1m]

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Measured in Storybook, the rows were centred but nothing shared a line:
labels sat on three left edges, row endings on three right edges, rows were
44 or 32px, and centred list cells put the two-line "Runs with" above its
neighbours. Now title, prompt, section and row labels share one left line;
every row's last mark ends on one inset (ghost triggers bleed their padding,
all menus carry the same 14px chevron, including Agent and Project); rows
are 44px; hint rows pin the label to the control; list cells align to their
first line and a leading status pill lines its text up with the column.

Model: claude-opus-5-5[1m]

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Test User and others added 10 commits September 24, 2026 03:28
…e problem marks [risk:medium]

- Name, prompt and the run choices share one bordered box like the composer;
  destination, chat, Agent, project and worktree are ghost pills along its
  bottom edge instead of four property rows. The name autofocuses on a new
  schedule.
- No time zone control. The CLI publishes its IANA zone as
  `MachineMeta.timeZone` at registration; wall-clock rules and one-off times
  are read on the owning machine's clock (older CLIs: the viewer's zone), and
  proposal cards create their trigger on that clock too.
- Save problems are marked where they are fixed. `collectScheduleSaveIssues`
  tags each blocker with its control and `missing | invalid`; an unfinished
  choice is marked after a save attempt, a real conflict at once, and only
  reasons that belong to no control sit beside Save.
- One type scale and two inks across the editor; `AnimatePresence` rows are
  keyed so exit animations work.
- Adds the scheduled-tasks Agent Note (en/zh) and compacts the schedules
  AGENTS.md around the new editor.

Model: claude-opus-5-5[1m]

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…k:medium]

- The Agent row is the composer's `DesktopRunConfigMenu` and
  `DesktopPermissionModeButton` (`ScheduleAgentControls`), along the bottom
  of the name/prompt box; name and prompt are split by a hairline.
- Machine is its own choice, like the chat landing: `DesktopMachineMenu`,
  the project chip and the worktree checkbox sit under the box. Changing the
  machine clears the Agent and a local project; machine problems get their
  own mark (`machine` issue field).
- Where runs go is its own card above the trigger card again.
- Removes the pill run bar and the now-unused shared `AgentRunConfigMenu`.

Model: claude-opus-5-5[1m]

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…dium]

- A new schedule opens with the chat landing's last machine and Agent and
  that Agent's remembered model, options and permission; a new machine or a
  newly picked Agent is seeded the same way (`seedScheduleAgentRunRef`). The
  seed fills permission exactly where `hasExplicitSchedulePermission` reads
  it (`_permission` option, mode list, or `mode` option) and drops credential
  options.
- No project mark: beside the optional project chip it read as "a project is
  required". A local project on another machine is reported beside Save.
- Time and date are typed only; the native picker button is hidden.
- The prompt has no resize handle: it grows from 4 to 8 lines, then scrolls.
  Field marks float so they never narrow the text.

Model: claude-opus-5-5[1m]

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The list is the Schedules page. On desktop, `/schedules/new` and
`/schedules/$id` open over it in `ScheduleDialog`; on mobile they stay a
pushed page with a back button. Saving and closing both return to the list.
Routes are unchanged, so links from a Session's info bar still land on the
schedule. Removes the tab strip and its atom.

Model: claude-opus-5-5[1m]

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…olumns [risk:low]

- Manual rows get a Run button (Zap; Play stays Resume). Run from the list
  starts at once with a toast; the detail dialog still confirms because its
  edits may be unsaved.
- Every row has a context menu: open, run, pause/resume, last run, delete.
  Delete always confirms.
- Name, Frequency and Next run resize from the header by pointer or arrow
  keys (double-click resets), persisted per device; tracks are
  `minmax(72px, width)` so a narrow panel still fits.
- The list moves to `schedule-list.tsx`, re-exported from `schedule-view.tsx`.

Model: claude-opus-5-5[1m]

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ium]

Schedule list, editor, dialog, proposal card and field marks now use
@lody/ui primitives (Button, Input, Select, Combobox, Tooltip,
ContextMenu, Skeleton, Dialog/AlertDialog); cards use the settingsCard
StyleX. CLI schedule tools follow the new MCP server SDK.

Model: claude-opus-5-5[1m]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The list narrows to names and the schedule slides in like a sidebar. Its
header is close plus compact icon actions (Pause/Resume, Run as Play,
Delete, History); run history moves to a trailing drawer. Destination and
trigger share one card with timed/manual as a Tabs row; the name is
larger and semibold; the prompt grows to 6 lines.

Model: claude-opus-5-5[1m]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…le split

A `schedules` layout route keeps the workspace mounted so opening and
closing a schedule animates instead of remounting. The list keeps its
header and every column (scrolling sideways) and the boundary to the
panel is a drag handle persisted per device. Top-of-pane headers join the
Electron window drag strip with their buttons exempt, so close/new work.

Model: claude-opus-5-5[1m]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The split view now sits under the list header, so title, search and New
stay put when a schedule opens. A click on blank page space closes the
open schedule (rows, controls, the panel and portalled popups excluded).
Frequency and Next run default to 150/140px.

Model: claude-opus-5-5[1m]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The header still never moves; the panel is placed against the page, so it
rises over the header's trailing end instead of starting under it.

Model: claude-opus-5-5[1m]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Test User and others added 7 commits September 25, 2026 11:44
The list defaults to 380px beside an open schedule, so the schedule gets
the rest. Run history returns to the bottom of the panel; the history
button and its separate drawer are gone.

Model: claude-opus-5-5[1m]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…sk:medium]

Mirror hands nested maps back with an inherited `$cid`; Zod's record parser
copied it into `agent.configOptionValues`. The owning machine's fingerprint
then never matched the Registry's, so every schedule with Agent options
stayed blocked on DEFINITION_NOT_COMMITTED - even on the machine that saved
it - and re-saving from the UI would store `$cid` as an Agent option.
Both Mirror readers (ScheduleRepository.read and the renderer's schedule
store) now go through scheduleDocumentFromMirrorState, which rebuilds plain
objects before parsing. Regression test fails on the old read path.

Model: claude-opus-5-5[1m]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Schedules are generally available. The developer-mode + beta atoms and
their settings toggles are gone; navigation, commands, the mobile tab,
the Registry subscription and proposal cards are always on.

The per-turn `scheduleToolsEnabled` flag this branch introduced is
removed end to end (shared schemas, CLI dispatch/ACP runner/MCP host,
session reuse key, UI writes). The Schedule MCP tools are registered for
every Agent session; they can only propose, the person still confirms the
card, and the command service still requires the invoking Session's owner
on this machine. Previously stored turns carrying the key still read.

Model: claude-opus-5-5[1m]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… not

Create threw on most conversations: the card handed the conversation's
ACP option values to the schedule as-is (booleans, and credential ids),
which the definition schema rejects. resolveScheduleProposalTarget now
normalizes every source (conversation or Role) to non-sensitive strings.

The failure was invisible because the card and the schedules page still
toasted through sonner, whose Toaster main removed; both use @/lib/toast
now. Writing the card's outcome no longer returns silently either.

Model: claude-opus-5-5[1m]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflict in main-layout.tsx: keep both shell children — the schedule
Registry subscription and main's compact-desktop layout sync.

Model: claude-opus-5-5[1m]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Earlier merges on this branch reformatted untouched electron/CLI files with
a mismatched oxfmt; they are back to main's bytes (operation-coordinator
keeps its real change, formatted). Lint: exhaustive `never` defaults in
the schedule vocabulary, explicit `return undefined` in effect guards, and
a void act() callback in the blank-click test.

Model: claude-opus-5-5[1m]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The previous commit staged stale local checkouts of these submodules.

Model: claude-opus-5-5[1m]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Test User and others added 2 commits September 26, 2026 07:09
Edit turns the proposal card into a small form for name, prompt and the
time rule (timed/manual + the editor's recurrence controls). Edits stay in
the card, never written back to the notice, so an Agent retrying the same
proposal still matches it; Create uses them. A created card is a receipt
with no edit.

Model: claude-opus-5-5[1m]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Where schedules run (the owning machine's CLI, its local clock), creating
one, triggers, where runs go, managing the list, Agent proposals with
Edit/Create, missed and overlapping runs, and an FAQ.

Model: claude-opus-5-5[1m]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Leeeon233
Leeeon233 merged commit b83e2fd into main Sep 26, 2026
6 checks passed
@Leeeon233
Leeeon233 deleted the feat/scheduled-tasks branch September 26, 2026 12:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant