(MOT-4503) feat(cron,console): schedules page for the cron worker - #840
(MOT-4503) feat(cron,console): schedules page for the cron worker#840rohitg00 wants to merge 8 commits into
Conversation
The cron worker ships an injectable console page. Schedules are a table, not a list: status, name, target, cadence, next run and fire count, with the detail beside it — the shape an operator scans when they want to know what is about to run and what it will call. Built from the shared primitives rather than local markup: PageShell and PageHeader, PageBody with PageMain and PageSidebar, the Table family, SegmentedControl for the status filter, StatusDot, EmptyState, StatusPanel, Skeleton, DropdownMenu and Dialog. The stylesheet only carries layout, scoped to the worker wrapper, in Console tokens. Status is derived from what a subscription actually reports — its fire count, its cap, its expiry — so it reads Active, Ending soon, or Finished. There is no paused state in the harness surface and no per-fire outcome, so neither is shown; inventing them would put a claim on screen the data cannot support. A pane narrower than 760px drills in rather than splitting: the table is replaced by the detail with a labelled back action, and the focus trap and Escape handling follow it. Scheduling names a model. A conversation that has never taken a turn has none to inherit, and harness::send rejected the request with a raw handler error. The console's own last pick is used when there is one, and when there is not the page says which control to use instead.
`config` is typed as an untyped value, so the schema the model sees says
nothing about its shape, and models routinely serialise it twice: the field
arrives as `"{\"expression\":\"0 0 8 * * Mon\"}"` and the trigger type rejects
a registration that was correct in substance. Observed on three different
models registering the same cron, four attempts in a row on one of them.
Decode a string that parses to an object, leave anything else untouched so the
owning trigger type still reports the real error, and say in the field's doc
that this is an object rather than a string.
The page now reads every schedule the rig owns, not only the open conversation's: sessions are listed by the metadata this page stamps, paged, and read with bounded concurrency. Creating a routine sends a harness turn in a session of its own and the console follows that session as soon as it exists, so the registration is watchable while it runs rather than reported after it ends. Registration is granted, not waved through: the two calls a schedule makes are approved individually and any hold placed before the grants landed is released, instead of putting the session into full-permission mode where every later message would run unattended. Rebuilt on the shared library after comparing against the console conformance inventory: page chrome, table family, tabs, segmented filter, list fallback below 760 px, the field recipe on every form, the shared searchable selector for the function catalog, and the shared input for search with the glyph inside it. Icons come from the shared 16 px recipe. Weekday handling was wrong in two directions. The Rust cron crate counts Sunday as 1, so a numeric weekday meant as Monday fires a day early; the page now asks for names and the cadence and next-run readers understand them. Ranges and unknown tokens read as unknown rather than guessing. The trap is documented in the cron skill and README, where agents and operators meet it. Replacement keeps its guarantee: the new subscription is registered first, and the old one is retired only when a different id comes back.
…-page # Conflicts: # console/web/src/hooks/use-conversations.test.ts
The form now answers "what did I just write" while it is being written: the cron field reports the expression in plain language and the next three literal UTC times underneath it. A six-field slip looks right and normally reveals itself at the first fire; this shows it immediately. Shapes the reader does not model, like weekday ranges, show nothing rather than a confident guess. The count of occurrences is called runs, in the table, the list and the detail pane. A trigger is the registration that stands until it is removed; what the column counts is how many times it has run.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
skill-check — worker0 verified, 61 skipped (no docs/).
Four for four. Nicely done. |
Cron ships a console page. Every schedule on the rig is listed with its cadence in plain language, its next UTC run and how many times it has run, next to the cron jobs workers registered for themselves. New routines are described in a sentence and registered by an agent in a session of their own.
What lands
Schedules. Agent-owned cron subscriptions across every session the page knows about, not just the conversation in front of you: status, cadence, target, next run, runs. Filter by All / Active / Ending soon / Finished, search across label, expression, target and subscription id, open any row for the full record.
System bindings. The cron triggers workers registered for themselves, with their owning worker, so a schedule nobody remembers writing has a name attached.
Describe a routine. The composer sends the request to a harness agent in a session created for that schedule and titled after the request. The console's chat pane follows that session as soon as it exists, so the registration is watchable while it runs instead of reported after it ends.
A form for exact control. Preset or hand-written expression, delivery as a conversation wake or a function call (target function, fixed payload, event pointer), an optional run limit. The cron field reads the expression back in plain language with the next three literal UTC times, so a six-field slip shows up while typing rather than at the first fire.
Replace and unregister. Replacement registers the new subscription first and retires the old one only when a different id comes back, so a failed replacement leaves the schedule standing. Unregister addresses the session that owns the subscription.
Why an agent registers the schedule
The cron worker has no service functions: it owns the
crontrigger type, and session-scoped subscriptions belong to the harness, which interceptsengine::register_triggerinside a turn. A page cannot register a conversation wake on its own behalf, so the schedule is created by the agent that will answer it.Two things make that reliable rather than hopeful:
approval::approve-always, sequentially because both edit one settings record, and any call already held is released. The alternative — putting the session in full-permission mode — would leave a real chat running unattended forever.configis typed as an untyped value, so the schema the model sees says nothing about its shape and models serialise it twice:"{\"expression\":\"0 0 8 * * Mon\"}". Three different models did this; one failed four attempts in a row.engine::register_triggernow decodes a string that parses to an object and leaves anything else untouched, so the trigger type still reports real errors.The weekday trap
The Rust
croncrate counts Sunday as 1, one ahead of the Unix convention. A schedule written0 30 9 * * 1for "Monday" fires on Sunday. The page asks agents to write weekday names, its cadence and next-run readers understand them, and the convention is now documented in the cron skill (which the harness injects) and the worker README.Conformance
Composed from
@iii-dev/console-ui: page chrome, the semantic table family insideTableViewport/TableFramewithTableRow interactive selected,Tabs,SegmentedControl,List/ListItem,Select, the searchableSelectorfor the function catalog,Input(including the search field, with the glyph inside it),CodeEditor,Dialog,DropdownMenu,StatusDot,StatusPanel,EmptyState,Skeleton,IconButton, theuiClasses.fieldrecipe on every form, and the 16 px icon recipe. Sans chrome in natural case; mono only for expressions, ids and counts. Below 760 px the tables become shared lists and the detail pane becomes a full-width page.docs/sops/console-ui-conformance.mdgains thecronrow and records the one local control (a multi-line input, which the library does not have yet —evalandiii-directorycarry the same copy).Reads
Schedules are found by listing sessions filtered on the page's own metadata, paged with the cursor, then read per session with bounded concurrency — rather than pulling the default page of every conversation on the rig and filtering client-side.
Verified on a live rig
Engine 0.22.1, console pane and 420 px phone width, light and dark:
0 0 8 * * Monreads as "Mon at 08:00 UTC" and next-runs on a Monday;0 0 17 * * 5reads as Thursday, matching the crate;Mon-Friandsunsetread as unknown instead of guessingharness::triggers::list: the subscription is gone from the session that owned itDepends on
host.chat.selectConversation(the chat follows the schedule's own session) andhost.chat.composerModel(which model runs the registration). Merged into this branch because the page does not type-check without them.Nothing under
console/is authored here. Every console file in the diff arrives with the #823 merge; the four commits on this branch touch onlycron/**,harness/src/functions/subscribe.rs,docs/sops/console-ui-conformance.mdand the pnpm files. Reviewing commit by commit shows the real change. The diff collapses to cron-only once both dependencies land and this retargets to main.Next
Ideas worth taking after this, roughly in value order: a last-run outcome on every row (needs somewhere to record it), one merged countdown agenda across schedules and bindings, fire-once-now from a row, pause without unregistering, and a distinct state for "fired but could not deliver".
Fixes MOT-4503