diff --git a/src/agents/tools/cron-tool.test.ts b/src/agents/tools/cron-tool.test.ts index 0fc5d76c64f..de932a4ae3f 100644 --- a/src/agents/tools/cron-tool.test.ts +++ b/src/agents/tools/cron-tool.test.ts @@ -643,6 +643,18 @@ describe("cron tool", () => { ); }); + it("documents due-by-default cron run mode", () => { + const tool = createTestCronTool(); + const parameters = tool.parameters as SchemaLike; + const runMode = parameters.properties?.runMode; + + expect(tool.description).toContain( + 'run: run only if due by default; needs jobId; pass runMode="force" to trigger now', + ); + expect(runMode?.description).toContain('omitted defaults to "due"'); + expect(runMode?.description).toContain('use "force" to trigger now'); + }); + it("advertises delivery threadId in the tool schema", () => { const tool = createTestCronTool(); const parameters = tool.parameters as SchemaLike; @@ -710,8 +722,8 @@ describe("cron tool", () => { ], ["remove", { action: "remove", jobId: "job-1" }, { id: "job-1" }], ["remove", { action: "remove", id: "job-2" }, { id: "job-2" }], - ["run", { action: "run", jobId: "job-1" }, { id: "job-1", mode: "force" }], - ["run", { action: "run", id: "job-2" }, { id: "job-2", mode: "force" }], + ["run", { action: "run", jobId: "job-1" }, { id: "job-1", mode: "due" }], + ["run", { action: "run", id: "job-2" }, { id: "job-2", mode: "due" }], ["get", { action: "get", jobId: "job-1" }, { id: "job-1" }], ["get", { action: "get", id: "job-2" }, { id: "job-2" }], ["runs", { action: "runs", jobId: "job-1" }, { id: "job-1" }], @@ -732,7 +744,7 @@ describe("cron tool", () => { id: "job-legacy", }); - expect(readGatewayCall().params).toEqual({ id: "job-primary", mode: "force" }); + expect(readGatewayCall().params).toEqual({ id: "job-primary", mode: "due" }); }); it("supports due-only run mode", async () => { diff --git a/src/agents/tools/cron-tool.ts b/src/agents/tools/cron-tool.ts index a417bf8592d..8e58465bc44 100644 --- a/src/agents/tools/cron-tool.ts +++ b/src/agents/tools/cron-tool.ts @@ -314,7 +314,10 @@ export function createCronToolSchema(): TSchema { patch: createCronPatchObjectSchema(), text: Type.Optional(Type.String()), mode: optionalStringEnum(CRON_WAKE_MODES), - runMode: optionalStringEnum(CRON_RUN_MODES), + runMode: optionalStringEnum(CRON_RUN_MODES, { + description: + 'Run mode for action="run": omitted defaults to "due"; use "force" to trigger now.', + }), contextMessages: Type.Optional( Type.Integer({ minimum: 0, maximum: REMINDER_CONTEXT_MESSAGES_MAX }), ), @@ -772,7 +775,7 @@ ACTIONS: - add: create job; needs job object - update: patch job; needs jobId + patch - remove: delete job; needs jobId -- run: trigger now; needs jobId +- run: run only if due by default; needs jobId; pass runMode="force" to trigger now - runs: run history; needs jobId - wake: send wake event; needs text, optional mode; defaults the target to the calling session/agent. Pass top-level sessionKey/agentId to wake a different lane. @@ -1084,7 +1087,7 @@ Use jobId canonical; id accepted compat. contextMessages (0-10) adds previous me throw new Error("jobId required (id accepted for backward compatibility)"); } const runMode = - params.runMode === "due" || params.runMode === "force" ? params.runMode : "force"; + params.runMode === "due" || params.runMode === "force" ? params.runMode : "due"; return jsonResult(await callGateway("cron.run", gatewayOpts, { id, mode: runMode })); } case "runs": { diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.discord-group.json b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.discord-group.json index 81fd8bfafa3..9d7f17d1666 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.discord-group.json +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.discord-group.json @@ -293,7 +293,7 @@ }, { "deferLoading": true, - "description": "Manage Gateway cron jobs and wake events: reminders, check-back-later, delayed follow-ups, recurring work. Do not emulate scheduling with exec sleep/process polling.\n\nMain cron => system events for heartbeat. Isolated cron => background task in `openclaw tasks`.\n\nACTIONS:\n- status: scheduler status\n- list: compact job summaries; includeDisabled true includes disabled; use get for full job details; agentId filter auto-filled from session\n- get: one job; needs jobId\n- add: create job; needs job object\n- update: patch job; needs jobId + patch\n- remove: delete job; needs jobId\n- run: trigger now; needs jobId\n- runs: run history; needs jobId\n- wake: send wake event; needs text, optional mode; defaults the target to the calling session/agent. Pass top-level sessionKey/agentId to wake a different lane.\n\nJOB SCHEMA (for add action):\n{\n \"name\": \"string\",\n \"schedule\": { ... }, // required\n \"payload\": { ... }, // required\n \"delivery\": { ... }, // optional announce for isolated/current/session, webhook for any target\n \"sessionTarget\": \"main\" | \"isolated\" | \"current\" | \"session:\",\n \"enabled\": true | false // default true\n}\n\nSESSION TARGET OPTIONS:\n- \"main\": main session; requires payload.kind=\"systemEvent\"\n- \"isolated\": ephemeral isolated session; requires payload.kind=\"agentTurn\"\n- \"current\": bind current session at creation\n- \"session:\": persistent named session\n\nDEFAULTS:\n- payload.kind=\"systemEvent\" → defaults to \"main\"\n- payload.kind=\"agentTurn\" → defaults to \"isolated\"\nCurrent binding needs sessionTarget=\"current\".\n\nSCHEDULE TYPES (schedule.kind):\n- \"at\": one-shot absolute time\n { \"kind\": \"at\", \"at\": \"\" }\n- \"every\": recurring interval\n { \"kind\": \"every\", \"everyMs\": , \"anchorMs\": }\n- \"cron\": expr in supplied timezone, or Gateway host local timezone when tz omitted\n { \"kind\": \"cron\", \"expr\": \"\", \"tz\": \"\" }\n Write expr in local wall-clock time; do not convert the requested local time to UTC first.\n tz omitted => Gateway host local timezone, not UTC.\n Example 6pm Shanghai daily: { \"kind\": \"cron\", \"expr\": \"0 18 * * *\", \"tz\": \"Asia/Shanghai\" }\n\nFor \"at\", ISO timestamps without timezone are UTC.\n\nPAYLOAD TYPES (payload.kind):\n- \"systemEvent\": inject text as system event\n { \"kind\": \"systemEvent\", \"text\": \"\" }\n- \"agentTurn\": run agent with prompt; isolated/current/session only\n { \"kind\": \"agentTurn\", \"message\": \"\", \"model\": \"\", \"thinking\": \"\", \"timeoutSeconds\": }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"\", \"to\": \"\", \"threadId\": \"\", \"bestEffort\": }\n - isolated agentTurn default when omitted: \"announce\"\n - announce: send to chat channel; isolated/current/session only; optional channel/to\n - threadId: chat thread/topic id\n - webhook: POST finished-run event to delivery.to URL\n - Specific chat/recipient: set announce delivery.channel/to; do not call messaging tools inside run.\n\nCRITICAL CONSTRAINTS:\n- sessionTarget=\"main\" REQUIRES payload.kind=\"systemEvent\"\n- sessionTarget=\"isolated\" | \"current\" | \"session:xxx\" REQUIRES payload.kind=\"agentTurn\"\n- Webhook: delivery.mode=\"webhook\" and delivery.to URL.\nDefault: prefer isolated agentTurn jobs unless the user explicitly wants current-session binding.\n\nRESTRICTED CRON RUNS:\n- Some isolated cron runs get narrow self-cleanup grant: status/list self-only, get/runs current job only, mutation only remove current job.\n\nWAKE MODES (for wake action):\n- \"next-heartbeat\" default: wake next heartbeat\n- \"now\": wake immediately\n\nUse jobId canonical; id accepted compat. contextMessages (0-10) adds previous messages as job context.", + "description": "Manage Gateway cron jobs and wake events: reminders, check-back-later, delayed follow-ups, recurring work. Do not emulate scheduling with exec sleep/process polling.\n\nMain cron => system events for heartbeat. Isolated cron => background task in `openclaw tasks`.\n\nACTIONS:\n- status: scheduler status\n- list: compact job summaries; includeDisabled true includes disabled; use get for full job details; agentId filter auto-filled from session\n- get: one job; needs jobId\n- add: create job; needs job object\n- update: patch job; needs jobId + patch\n- remove: delete job; needs jobId\n- run: run only if due by default; needs jobId; pass runMode=\"force\" to trigger now\n- runs: run history; needs jobId\n- wake: send wake event; needs text, optional mode; defaults the target to the calling session/agent. Pass top-level sessionKey/agentId to wake a different lane.\n\nJOB SCHEMA (for add action):\n{\n \"name\": \"string\",\n \"schedule\": { ... }, // required\n \"payload\": { ... }, // required\n \"delivery\": { ... }, // optional announce for isolated/current/session, webhook for any target\n \"sessionTarget\": \"main\" | \"isolated\" | \"current\" | \"session:\",\n \"enabled\": true | false // default true\n}\n\nSESSION TARGET OPTIONS:\n- \"main\": main session; requires payload.kind=\"systemEvent\"\n- \"isolated\": ephemeral isolated session; requires payload.kind=\"agentTurn\"\n- \"current\": bind current session at creation\n- \"session:\": persistent named session\n\nDEFAULTS:\n- payload.kind=\"systemEvent\" → defaults to \"main\"\n- payload.kind=\"agentTurn\" → defaults to \"isolated\"\nCurrent binding needs sessionTarget=\"current\".\n\nSCHEDULE TYPES (schedule.kind):\n- \"at\": one-shot absolute time\n { \"kind\": \"at\", \"at\": \"\" }\n- \"every\": recurring interval\n { \"kind\": \"every\", \"everyMs\": , \"anchorMs\": }\n- \"cron\": expr in supplied timezone, or Gateway host local timezone when tz omitted\n { \"kind\": \"cron\", \"expr\": \"\", \"tz\": \"\" }\n Write expr in local wall-clock time; do not convert the requested local time to UTC first.\n tz omitted => Gateway host local timezone, not UTC.\n Example 6pm Shanghai daily: { \"kind\": \"cron\", \"expr\": \"0 18 * * *\", \"tz\": \"Asia/Shanghai\" }\n\nFor \"at\", ISO timestamps without timezone are UTC.\n\nPAYLOAD TYPES (payload.kind):\n- \"systemEvent\": inject text as system event\n { \"kind\": \"systemEvent\", \"text\": \"\" }\n- \"agentTurn\": run agent with prompt; isolated/current/session only\n { \"kind\": \"agentTurn\", \"message\": \"\", \"model\": \"\", \"thinking\": \"\", \"timeoutSeconds\": }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"\", \"to\": \"\", \"threadId\": \"\", \"bestEffort\": }\n - isolated agentTurn default when omitted: \"announce\"\n - announce: send to chat channel; isolated/current/session only; optional channel/to\n - threadId: chat thread/topic id\n - webhook: POST finished-run event to delivery.to URL\n - Specific chat/recipient: set announce delivery.channel/to; do not call messaging tools inside run.\n\nCRITICAL CONSTRAINTS:\n- sessionTarget=\"main\" REQUIRES payload.kind=\"systemEvent\"\n- sessionTarget=\"isolated\" | \"current\" | \"session:xxx\" REQUIRES payload.kind=\"agentTurn\"\n- Webhook: delivery.mode=\"webhook\" and delivery.to URL.\nDefault: prefer isolated agentTurn jobs unless the user explicitly wants current-session binding.\n\nRESTRICTED CRON RUNS:\n- Some isolated cron runs get narrow self-cleanup grant: status/list self-only, get/runs current job only, mutation only remove current job.\n\nWAKE MODES (for wake action):\n- \"next-heartbeat\" default: wake next heartbeat\n- \"now\": wake immediately\n\nUse jobId canonical; id accepted compat. contextMessages (0-10) adds previous messages as job context.", "inputSchema": { "additionalProperties": true, "properties": { @@ -885,6 +885,7 @@ "type": "object" }, "runMode": { + "description": "Run mode for action=\"run\": omitted defaults to \"due\"; use \"force\" to trigger now.", "enum": ["due", "force"], "type": "string" }, diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.heartbeat-turn.json b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.heartbeat-turn.json index 56010e1ddf1..90820500948 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.heartbeat-turn.json +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.heartbeat-turn.json @@ -293,7 +293,7 @@ }, { "deferLoading": true, - "description": "Manage Gateway cron jobs and wake events: reminders, check-back-later, delayed follow-ups, recurring work. Do not emulate scheduling with exec sleep/process polling.\n\nMain cron => system events for heartbeat. Isolated cron => background task in `openclaw tasks`.\n\nACTIONS:\n- status: scheduler status\n- list: compact job summaries; includeDisabled true includes disabled; use get for full job details; agentId filter auto-filled from session\n- get: one job; needs jobId\n- add: create job; needs job object\n- update: patch job; needs jobId + patch\n- remove: delete job; needs jobId\n- run: trigger now; needs jobId\n- runs: run history; needs jobId\n- wake: send wake event; needs text, optional mode; defaults the target to the calling session/agent. Pass top-level sessionKey/agentId to wake a different lane.\n\nJOB SCHEMA (for add action):\n{\n \"name\": \"string\",\n \"schedule\": { ... }, // required\n \"payload\": { ... }, // required\n \"delivery\": { ... }, // optional announce for isolated/current/session, webhook for any target\n \"sessionTarget\": \"main\" | \"isolated\" | \"current\" | \"session:\",\n \"enabled\": true | false // default true\n}\n\nSESSION TARGET OPTIONS:\n- \"main\": main session; requires payload.kind=\"systemEvent\"\n- \"isolated\": ephemeral isolated session; requires payload.kind=\"agentTurn\"\n- \"current\": bind current session at creation\n- \"session:\": persistent named session\n\nDEFAULTS:\n- payload.kind=\"systemEvent\" → defaults to \"main\"\n- payload.kind=\"agentTurn\" → defaults to \"isolated\"\nCurrent binding needs sessionTarget=\"current\".\n\nSCHEDULE TYPES (schedule.kind):\n- \"at\": one-shot absolute time\n { \"kind\": \"at\", \"at\": \"\" }\n- \"every\": recurring interval\n { \"kind\": \"every\", \"everyMs\": , \"anchorMs\": }\n- \"cron\": expr in supplied timezone, or Gateway host local timezone when tz omitted\n { \"kind\": \"cron\", \"expr\": \"\", \"tz\": \"\" }\n Write expr in local wall-clock time; do not convert the requested local time to UTC first.\n tz omitted => Gateway host local timezone, not UTC.\n Example 6pm Shanghai daily: { \"kind\": \"cron\", \"expr\": \"0 18 * * *\", \"tz\": \"Asia/Shanghai\" }\n\nFor \"at\", ISO timestamps without timezone are UTC.\n\nPAYLOAD TYPES (payload.kind):\n- \"systemEvent\": inject text as system event\n { \"kind\": \"systemEvent\", \"text\": \"\" }\n- \"agentTurn\": run agent with prompt; isolated/current/session only\n { \"kind\": \"agentTurn\", \"message\": \"\", \"model\": \"\", \"thinking\": \"\", \"timeoutSeconds\": }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"\", \"to\": \"\", \"threadId\": \"\", \"bestEffort\": }\n - isolated agentTurn default when omitted: \"announce\"\n - announce: send to chat channel; isolated/current/session only; optional channel/to\n - threadId: chat thread/topic id\n - webhook: POST finished-run event to delivery.to URL\n - Specific chat/recipient: set announce delivery.channel/to; do not call messaging tools inside run.\n\nCRITICAL CONSTRAINTS:\n- sessionTarget=\"main\" REQUIRES payload.kind=\"systemEvent\"\n- sessionTarget=\"isolated\" | \"current\" | \"session:xxx\" REQUIRES payload.kind=\"agentTurn\"\n- Webhook: delivery.mode=\"webhook\" and delivery.to URL.\nDefault: prefer isolated agentTurn jobs unless the user explicitly wants current-session binding.\n\nRESTRICTED CRON RUNS:\n- Some isolated cron runs get narrow self-cleanup grant: status/list self-only, get/runs current job only, mutation only remove current job.\n\nWAKE MODES (for wake action):\n- \"next-heartbeat\" default: wake next heartbeat\n- \"now\": wake immediately\n\nUse jobId canonical; id accepted compat. contextMessages (0-10) adds previous messages as job context.", + "description": "Manage Gateway cron jobs and wake events: reminders, check-back-later, delayed follow-ups, recurring work. Do not emulate scheduling with exec sleep/process polling.\n\nMain cron => system events for heartbeat. Isolated cron => background task in `openclaw tasks`.\n\nACTIONS:\n- status: scheduler status\n- list: compact job summaries; includeDisabled true includes disabled; use get for full job details; agentId filter auto-filled from session\n- get: one job; needs jobId\n- add: create job; needs job object\n- update: patch job; needs jobId + patch\n- remove: delete job; needs jobId\n- run: run only if due by default; needs jobId; pass runMode=\"force\" to trigger now\n- runs: run history; needs jobId\n- wake: send wake event; needs text, optional mode; defaults the target to the calling session/agent. Pass top-level sessionKey/agentId to wake a different lane.\n\nJOB SCHEMA (for add action):\n{\n \"name\": \"string\",\n \"schedule\": { ... }, // required\n \"payload\": { ... }, // required\n \"delivery\": { ... }, // optional announce for isolated/current/session, webhook for any target\n \"sessionTarget\": \"main\" | \"isolated\" | \"current\" | \"session:\",\n \"enabled\": true | false // default true\n}\n\nSESSION TARGET OPTIONS:\n- \"main\": main session; requires payload.kind=\"systemEvent\"\n- \"isolated\": ephemeral isolated session; requires payload.kind=\"agentTurn\"\n- \"current\": bind current session at creation\n- \"session:\": persistent named session\n\nDEFAULTS:\n- payload.kind=\"systemEvent\" → defaults to \"main\"\n- payload.kind=\"agentTurn\" → defaults to \"isolated\"\nCurrent binding needs sessionTarget=\"current\".\n\nSCHEDULE TYPES (schedule.kind):\n- \"at\": one-shot absolute time\n { \"kind\": \"at\", \"at\": \"\" }\n- \"every\": recurring interval\n { \"kind\": \"every\", \"everyMs\": , \"anchorMs\": }\n- \"cron\": expr in supplied timezone, or Gateway host local timezone when tz omitted\n { \"kind\": \"cron\", \"expr\": \"\", \"tz\": \"\" }\n Write expr in local wall-clock time; do not convert the requested local time to UTC first.\n tz omitted => Gateway host local timezone, not UTC.\n Example 6pm Shanghai daily: { \"kind\": \"cron\", \"expr\": \"0 18 * * *\", \"tz\": \"Asia/Shanghai\" }\n\nFor \"at\", ISO timestamps without timezone are UTC.\n\nPAYLOAD TYPES (payload.kind):\n- \"systemEvent\": inject text as system event\n { \"kind\": \"systemEvent\", \"text\": \"\" }\n- \"agentTurn\": run agent with prompt; isolated/current/session only\n { \"kind\": \"agentTurn\", \"message\": \"\", \"model\": \"\", \"thinking\": \"\", \"timeoutSeconds\": }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"\", \"to\": \"\", \"threadId\": \"\", \"bestEffort\": }\n - isolated agentTurn default when omitted: \"announce\"\n - announce: send to chat channel; isolated/current/session only; optional channel/to\n - threadId: chat thread/topic id\n - webhook: POST finished-run event to delivery.to URL\n - Specific chat/recipient: set announce delivery.channel/to; do not call messaging tools inside run.\n\nCRITICAL CONSTRAINTS:\n- sessionTarget=\"main\" REQUIRES payload.kind=\"systemEvent\"\n- sessionTarget=\"isolated\" | \"current\" | \"session:xxx\" REQUIRES payload.kind=\"agentTurn\"\n- Webhook: delivery.mode=\"webhook\" and delivery.to URL.\nDefault: prefer isolated agentTurn jobs unless the user explicitly wants current-session binding.\n\nRESTRICTED CRON RUNS:\n- Some isolated cron runs get narrow self-cleanup grant: status/list self-only, get/runs current job only, mutation only remove current job.\n\nWAKE MODES (for wake action):\n- \"next-heartbeat\" default: wake next heartbeat\n- \"now\": wake immediately\n\nUse jobId canonical; id accepted compat. contextMessages (0-10) adds previous messages as job context.", "inputSchema": { "additionalProperties": true, "properties": { @@ -885,6 +885,7 @@ "type": "object" }, "runMode": { + "description": "Run mode for action=\"run\": omitted defaults to \"due\"; use \"force\" to trigger now.", "enum": ["due", "force"], "type": "string" }, diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.telegram-direct.json b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.telegram-direct.json index 05d3d228a97..2ff1a4f7d80 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.telegram-direct.json +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.telegram-direct.json @@ -293,7 +293,7 @@ }, { "deferLoading": true, - "description": "Manage Gateway cron jobs and wake events: reminders, check-back-later, delayed follow-ups, recurring work. Do not emulate scheduling with exec sleep/process polling.\n\nMain cron => system events for heartbeat. Isolated cron => background task in `openclaw tasks`.\n\nACTIONS:\n- status: scheduler status\n- list: compact job summaries; includeDisabled true includes disabled; use get for full job details; agentId filter auto-filled from session\n- get: one job; needs jobId\n- add: create job; needs job object\n- update: patch job; needs jobId + patch\n- remove: delete job; needs jobId\n- run: trigger now; needs jobId\n- runs: run history; needs jobId\n- wake: send wake event; needs text, optional mode; defaults the target to the calling session/agent. Pass top-level sessionKey/agentId to wake a different lane.\n\nJOB SCHEMA (for add action):\n{\n \"name\": \"string\",\n \"schedule\": { ... }, // required\n \"payload\": { ... }, // required\n \"delivery\": { ... }, // optional announce for isolated/current/session, webhook for any target\n \"sessionTarget\": \"main\" | \"isolated\" | \"current\" | \"session:\",\n \"enabled\": true | false // default true\n}\n\nSESSION TARGET OPTIONS:\n- \"main\": main session; requires payload.kind=\"systemEvent\"\n- \"isolated\": ephemeral isolated session; requires payload.kind=\"agentTurn\"\n- \"current\": bind current session at creation\n- \"session:\": persistent named session\n\nDEFAULTS:\n- payload.kind=\"systemEvent\" → defaults to \"main\"\n- payload.kind=\"agentTurn\" → defaults to \"isolated\"\nCurrent binding needs sessionTarget=\"current\".\n\nSCHEDULE TYPES (schedule.kind):\n- \"at\": one-shot absolute time\n { \"kind\": \"at\", \"at\": \"\" }\n- \"every\": recurring interval\n { \"kind\": \"every\", \"everyMs\": , \"anchorMs\": }\n- \"cron\": expr in supplied timezone, or Gateway host local timezone when tz omitted\n { \"kind\": \"cron\", \"expr\": \"\", \"tz\": \"\" }\n Write expr in local wall-clock time; do not convert the requested local time to UTC first.\n tz omitted => Gateway host local timezone, not UTC.\n Example 6pm Shanghai daily: { \"kind\": \"cron\", \"expr\": \"0 18 * * *\", \"tz\": \"Asia/Shanghai\" }\n\nFor \"at\", ISO timestamps without timezone are UTC.\n\nPAYLOAD TYPES (payload.kind):\n- \"systemEvent\": inject text as system event\n { \"kind\": \"systemEvent\", \"text\": \"\" }\n- \"agentTurn\": run agent with prompt; isolated/current/session only\n { \"kind\": \"agentTurn\", \"message\": \"\", \"model\": \"\", \"thinking\": \"\", \"timeoutSeconds\": }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"\", \"to\": \"\", \"threadId\": \"\", \"bestEffort\": }\n - isolated agentTurn default when omitted: \"announce\"\n - announce: send to chat channel; isolated/current/session only; optional channel/to\n - threadId: chat thread/topic id\n - webhook: POST finished-run event to delivery.to URL\n - Specific chat/recipient: set announce delivery.channel/to; do not call messaging tools inside run.\n\nCRITICAL CONSTRAINTS:\n- sessionTarget=\"main\" REQUIRES payload.kind=\"systemEvent\"\n- sessionTarget=\"isolated\" | \"current\" | \"session:xxx\" REQUIRES payload.kind=\"agentTurn\"\n- Webhook: delivery.mode=\"webhook\" and delivery.to URL.\nDefault: prefer isolated agentTurn jobs unless the user explicitly wants current-session binding.\n\nRESTRICTED CRON RUNS:\n- Some isolated cron runs get narrow self-cleanup grant: status/list self-only, get/runs current job only, mutation only remove current job.\n\nWAKE MODES (for wake action):\n- \"next-heartbeat\" default: wake next heartbeat\n- \"now\": wake immediately\n\nUse jobId canonical; id accepted compat. contextMessages (0-10) adds previous messages as job context.", + "description": "Manage Gateway cron jobs and wake events: reminders, check-back-later, delayed follow-ups, recurring work. Do not emulate scheduling with exec sleep/process polling.\n\nMain cron => system events for heartbeat. Isolated cron => background task in `openclaw tasks`.\n\nACTIONS:\n- status: scheduler status\n- list: compact job summaries; includeDisabled true includes disabled; use get for full job details; agentId filter auto-filled from session\n- get: one job; needs jobId\n- add: create job; needs job object\n- update: patch job; needs jobId + patch\n- remove: delete job; needs jobId\n- run: run only if due by default; needs jobId; pass runMode=\"force\" to trigger now\n- runs: run history; needs jobId\n- wake: send wake event; needs text, optional mode; defaults the target to the calling session/agent. Pass top-level sessionKey/agentId to wake a different lane.\n\nJOB SCHEMA (for add action):\n{\n \"name\": \"string\",\n \"schedule\": { ... }, // required\n \"payload\": { ... }, // required\n \"delivery\": { ... }, // optional announce for isolated/current/session, webhook for any target\n \"sessionTarget\": \"main\" | \"isolated\" | \"current\" | \"session:\",\n \"enabled\": true | false // default true\n}\n\nSESSION TARGET OPTIONS:\n- \"main\": main session; requires payload.kind=\"systemEvent\"\n- \"isolated\": ephemeral isolated session; requires payload.kind=\"agentTurn\"\n- \"current\": bind current session at creation\n- \"session:\": persistent named session\n\nDEFAULTS:\n- payload.kind=\"systemEvent\" → defaults to \"main\"\n- payload.kind=\"agentTurn\" → defaults to \"isolated\"\nCurrent binding needs sessionTarget=\"current\".\n\nSCHEDULE TYPES (schedule.kind):\n- \"at\": one-shot absolute time\n { \"kind\": \"at\", \"at\": \"\" }\n- \"every\": recurring interval\n { \"kind\": \"every\", \"everyMs\": , \"anchorMs\": }\n- \"cron\": expr in supplied timezone, or Gateway host local timezone when tz omitted\n { \"kind\": \"cron\", \"expr\": \"\", \"tz\": \"\" }\n Write expr in local wall-clock time; do not convert the requested local time to UTC first.\n tz omitted => Gateway host local timezone, not UTC.\n Example 6pm Shanghai daily: { \"kind\": \"cron\", \"expr\": \"0 18 * * *\", \"tz\": \"Asia/Shanghai\" }\n\nFor \"at\", ISO timestamps without timezone are UTC.\n\nPAYLOAD TYPES (payload.kind):\n- \"systemEvent\": inject text as system event\n { \"kind\": \"systemEvent\", \"text\": \"\" }\n- \"agentTurn\": run agent with prompt; isolated/current/session only\n { \"kind\": \"agentTurn\", \"message\": \"\", \"model\": \"\", \"thinking\": \"\", \"timeoutSeconds\": }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"\", \"to\": \"\", \"threadId\": \"\", \"bestEffort\": }\n - isolated agentTurn default when omitted: \"announce\"\n - announce: send to chat channel; isolated/current/session only; optional channel/to\n - threadId: chat thread/topic id\n - webhook: POST finished-run event to delivery.to URL\n - Specific chat/recipient: set announce delivery.channel/to; do not call messaging tools inside run.\n\nCRITICAL CONSTRAINTS:\n- sessionTarget=\"main\" REQUIRES payload.kind=\"systemEvent\"\n- sessionTarget=\"isolated\" | \"current\" | \"session:xxx\" REQUIRES payload.kind=\"agentTurn\"\n- Webhook: delivery.mode=\"webhook\" and delivery.to URL.\nDefault: prefer isolated agentTurn jobs unless the user explicitly wants current-session binding.\n\nRESTRICTED CRON RUNS:\n- Some isolated cron runs get narrow self-cleanup grant: status/list self-only, get/runs current job only, mutation only remove current job.\n\nWAKE MODES (for wake action):\n- \"next-heartbeat\" default: wake next heartbeat\n- \"now\": wake immediately\n\nUse jobId canonical; id accepted compat. contextMessages (0-10) adds previous messages as job context.", "inputSchema": { "additionalProperties": true, "properties": { @@ -885,6 +885,7 @@ "type": "object" }, "runMode": { + "description": "Run mode for action=\"run\": omitted defaults to \"due\"; use \"force\" to trigger now.", "enum": ["due", "force"], "type": "string" }, diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md index 4d2bc859692..5108d5488af 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md @@ -227,8 +227,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 0 }, "dynamicToolsJson": { - "chars": 50642, - "roughTokens": 12661 + "chars": 50816, + "roughTokens": 12704 }, "openClawDeveloperInstructions": { "chars": 2988, @@ -239,8 +239,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 6925 }, "totalWithDynamicToolsJson": { - "chars": 78344, - "roughTokens": 19586 + "chars": 78518, + "roughTokens": 19630 }, "userInputText": { "chars": 1629, diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md index a369dad29d6..44b926ef43f 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md @@ -227,8 +227,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 0 }, "dynamicToolsJson": { - "chars": 50311, - "roughTokens": 12578 + "chars": 50485, + "roughTokens": 12622 }, "openClawDeveloperInstructions": { "chars": 1964, @@ -239,8 +239,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 6544 }, "totalWithDynamicToolsJson": { - "chars": 76489, - "roughTokens": 19123 + "chars": 76663, + "roughTokens": 19166 }, "userInputText": { "chars": 1129, diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md index f3b6f46ba69..f86847f4711 100644 --- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md +++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md @@ -228,8 +228,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 0 }, "dynamicToolsJson": { - "chars": 51601, - "roughTokens": 12901 + "chars": 51775, + "roughTokens": 12944 }, "openClawDeveloperInstructions": { "chars": 1983, @@ -240,8 +240,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 6780 }, "totalWithDynamicToolsJson": { - "chars": 78722, - "roughTokens": 19681 + "chars": 78896, + "roughTokens": 19724 }, "userInputText": { "chars": 1367,