From 5f159c43c582112d2c684a74c5ed779f8bb8f64d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 13 Dec 2025 12:09:20 +0000 Subject: [PATCH] feat(cli): expand cron commands --- docs/cron.md | 7 +++- src/cli/cron-cli.ts | 90 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/docs/cron.md b/docs/cron.md index bbec6e6f7..012b9a608 100644 --- a/docs/cron.md +++ b/docs/cron.md @@ -76,8 +76,7 @@ Each job is a JSON object with stable keys (unknown keys ignored for forward com - `{"kind":"systemEvent","text":string}` (enqueue as `System:`) - `{"kind":"agentTurn","message":string,"deliver"?:boolean,"channel"?: "last"|"whatsapp"|"telegram","to"?:string,"timeoutSeconds"?:number}` - `isolation` (optional; only meaningful for isolated jobs) - - `{"postToMain"?: boolean, "postToMainPrefix"?: string}` - - Note: `postToMain` is deprecated (no-op). Isolated jobs always post a summary; only the prefix is configurable. + - `{"postToMainPrefix"?: string}` - `runtime` (optional) - `{"maxAttempts"?:number,"retryBackoffMs"?:number}` (best-effort retries; defaults off) - `state` (runtime-maintained) @@ -177,6 +176,7 @@ When due: - Optionally deliver output (`payload.deliver === true`) to the configured channel/to. - Isolated jobs always enqueue a summary system event to the main session when they finish (derived from the last agent text output). - Prefix defaults to `Cron`, and can be customized via `isolation.postToMainPrefix`. +- If `deliver` is omitted/false, nothing is sent to external providers; you still get the main-session summary and can inspect the full isolated transcript in `cron:`. ### “Run in parallel to main” @@ -215,6 +215,8 @@ Path rules: Retention: - Best-effort pruning when the file grows beyond ~2MB; keep the newest ~2000 lines. +Each log line includes (at minimum) job id, status/error, timing, and a `summary` string (systemEvent text for main jobs, and the last agent text output for isolated jobs). + ## Gateway API New methods (names can be bikeshed; `cron.*` is suggested): @@ -269,6 +271,7 @@ Add a `cron` command group (all commands should also support `--json` where sens - `clawdis cron rm ` - `clawdis cron enable ` / `clawdis cron disable ` - `clawdis cron run [--force]` (debug) +- `clawdis cron runs [--id ] [--limit ]` (run history) - `clawdis cron status` (scheduler enabled + next wake) Additionally: diff --git a/src/cli/cron-cli.ts b/src/cli/cron-cli.ts index 42d245715..220e42647 100644 --- a/src/cli/cron-cli.ts +++ b/src/cli/cron-cli.ts @@ -163,7 +163,6 @@ export function registerCronCli(program: Command) { "Do not fail the job if delivery fails", false, ) - .option("--post-to-main", "Deprecated (no-op)", false) .option( "--post-prefix ", "Prefix for summary system event", @@ -261,14 +260,15 @@ export function registerCronCli(program: Command) { }; })(); + if (sessionTarget === "main" && payload.kind !== "systemEvent") { + throw new Error("Main jobs require --system-event (systemEvent)."); + } if (sessionTarget === "isolated" && payload.kind !== "agentTurn") { - throw new Error( - "Isolated jobs require --message (agentTurn payload).", - ); + throw new Error("Isolated jobs require --message (agentTurn)."); } const isolation = - payload.kind === "agentTurn" + sessionTarget === "isolated" ? { postToMainPrefix: typeof opts.postPrefix === "string" && @@ -318,6 +318,71 @@ export function registerCronCli(program: Command) { }), ); + addGatewayClientOptions( + cron + .command("enable") + .description("Enable a cron job") + .argument("", "Job id") + .action(async (id, opts) => { + try { + const res = await callGatewayFromCli("cron.update", opts, { + id, + patch: { enabled: true }, + }); + defaultRuntime.log(JSON.stringify(res, null, 2)); + await warnIfCronSchedulerDisabled(opts); + } catch (err) { + defaultRuntime.error(danger(String(err))); + defaultRuntime.exit(1); + } + }), + ); + + addGatewayClientOptions( + cron + .command("disable") + .description("Disable a cron job") + .argument("", "Job id") + .action(async (id, opts) => { + try { + const res = await callGatewayFromCli("cron.update", opts, { + id, + patch: { enabled: false }, + }); + defaultRuntime.log(JSON.stringify(res, null, 2)); + await warnIfCronSchedulerDisabled(opts); + } catch (err) { + defaultRuntime.error(danger(String(err))); + defaultRuntime.exit(1); + } + }), + ); + + addGatewayClientOptions( + cron + .command("runs") + .description("Show cron run history (JSONL-backed)") + .option("--id ", "Job id (required when store is jobs.json)") + .option("--limit ", "Max entries (default 50)", "50") + .action(async (opts) => { + try { + const limitRaw = Number.parseInt(String(opts.limit ?? "50"), 10); + const limit = + Number.isFinite(limitRaw) && limitRaw > 0 ? limitRaw : 50; + const id = + typeof opts.id === "string" && opts.id.trim() ? opts.id : undefined; + const res = await callGatewayFromCli("cron.runs", opts, { + id, + limit, + }); + defaultRuntime.log(JSON.stringify(res, null, 2)); + } catch (err) { + defaultRuntime.error(danger(String(err))); + defaultRuntime.exit(1); + } + }), + ); + addGatewayClientOptions( cron .command("edit") @@ -347,10 +412,23 @@ export function registerCronCli(program: Command) { "Do not fail job if delivery fails", false, ) - .option("--post-to-main", "Deprecated (no-op)", false) .option("--post-prefix ", "Prefix for summary system event") .action(async (id, opts) => { try { + if (opts.session === "main" && opts.message) { + throw new Error( + "Main jobs cannot use --message; use --system-event or --session isolated.", + ); + } + if (opts.session === "isolated" && opts.systemEvent) { + throw new Error( + "Isolated jobs cannot use --system-event; use --message or --session main.", + ); + } + if (opts.session === "main" && typeof opts.postPrefix === "string") { + throw new Error("--post-prefix only applies to isolated jobs."); + } + const patch: Record = {}; if (typeof opts.name === "string") patch.name = opts.name; if (opts.enable && opts.disable)