From cd66854b666e1324cac196e96a0f74f3e0f9bc45 Mon Sep 17 00:00:00 2001 From: "clawsweeper[bot]" <274271284+clawsweeper[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 06:06:24 +0000 Subject: [PATCH] feat(cron): add agentId filtering to cron list (#77602) Summary: - This PR adds optional `agentId` filtering to `cron.list`, auto-fills it for agent tool calls, exposes `openclaw cron list --agent`, updates generated protocol clients, docs, changelog, tests, and prompt fixtures. - Reproducibility: yes. The motivating behavior is source-reproducible on current main because cron tool, CLI, ... e list paths do not accept or apply `agentId`; the PR diff adds that path with focused regression coverage. Automerge notes: - Ran the ClawSweeper repair loop before final review. - Included post-review commit in the final squash: chore: regenerate protocol schema after adding agentId to CronListParams - Included post-review commit in the final squash: feat(cron): add agentId filtering to cron list Validation: - ClawSweeper review passed for head 35b692bc9708314da7bffedd85e0aa9c9f9af85a. - Required merge gates passed before the squash merge. Prepared head SHA: 35b692bc9708314da7bffedd85e0aa9c9f9af85a Review: https://github.com/openclaw/openclaw/pull/77602#issuecomment-4375631700 Co-authored-by: zhanggttry Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> --- CHANGELOG.md | 1 + .../OpenClawProtocol/GatewayModels.swift | 6 ++- .../OpenClawProtocol/GatewayModels.swift | 6 ++- docs/cli/cron.md | 3 ++ src/agents/tools/cron-tool.test.ts | 28 ++++++++++ src/agents/tools/cron-tool.ts | 14 ++++- src/cli/cron-cli.test.ts | 14 +++++ src/cli/cron-cli/register.cron-add.ts | 10 +++- .../service.list-page-sort-guards.test.ts | 52 +++++++++++++++++++ src/cron/service/list-page-types.ts | 1 + src/cron/service/ops.ts | 17 ++++++ src/gateway/protocol/cron-validators.test.ts | 2 + src/gateway/protocol/schema/cron.ts | 1 + src/gateway/server-methods/cron.ts | 2 + .../codex-dynamic-tools.discord-group.json | 6 ++- .../codex-dynamic-tools.heartbeat-turn.json | 6 ++- .../codex-dynamic-tools.telegram-direct.json | 6 ++- .../discord-group-codex-message-tool.md | 8 +-- .../telegram-direct-codex-message-tool.md | 8 +-- .../telegram-heartbeat-codex-tool.md | 8 +-- 20 files changed, 178 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1e8b8761f4..c4b448d5e5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Docs: https://docs.openclaw.ai - Plugins/active-memory: skip session-store channel entries that contain `:` when resolving the recall subagent's channel, so QQ c2c agent IDs (e.g. `c2c:10D4F7C2…`) and other scoped conversation IDs do not reach bundled-plugin `dirName` validation and crash the recall run. The same guard already applied to explicit `channelId` params (#76704); this extends it to store-derived channels. (#77396) Thanks @hclsys. - Secrets/external channel contracts: also look in `/dist/` when resolving the `secret-contract-api` sidecar, so npm-published externalized channel plugins (e.g. `@openclaw/discord` since 2026.5.2) whose compiled artifacts live under `dist/` actually contribute their channel SecretRef contracts to the runtime snapshot. Without this, env-backed `channels.discord.token` SecretRefs silently failed to resolve at gateway start on 2026.5.3, leaving the channel `not configured` even though #76449 had landed the generic external-contract loader. Thanks @mogglemoss. - Models/auth: add `openclaw models auth list [--provider ] [--json]` so users can inspect saved per-agent auth profiles without dumping secrets or hitting the old “too many arguments” path. Thanks @vincentkoc. +- Cron CLI: add `openclaw cron list --agent `, normalize the requested agent id, and include jobs without a stored agent id under the configured default agent while keeping `cron list` unfiltered when no agent is supplied. Fixes #77118. Thanks @zhanggttry. - Control UI/header: show the active agent name in dashboard breadcrumbs without adding the current session key, keeping non-chat views oriented without crowding the topbar. - Control UI/cron: make the New Job sidebar collapsible so the jobs list can reclaim space while keeping the form one click away. Thanks @BunsDev. - Gateway/startup: keep model-catalog test helpers, run-session lookup code, QR pairing helpers, and TypeBox memory-tool schema construction out of hot startup import paths, reducing default gateway benchmark plugin-load and memory pressure. diff --git a/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift b/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift index af9e7887816..4dd8e28bc5f 100644 --- a/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift +++ b/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift @@ -4172,6 +4172,7 @@ public struct CronListParams: Codable, Sendable { public let enabled: AnyCodable? public let sortby: AnyCodable? public let sortdir: AnyCodable? + public let agentid: String? public init( includedisabled: Bool?, @@ -4180,7 +4181,8 @@ public struct CronListParams: Codable, Sendable { query: String?, enabled: AnyCodable?, sortby: AnyCodable?, - sortdir: AnyCodable?) + sortdir: AnyCodable?, + agentid: String?) { self.includedisabled = includedisabled self.limit = limit @@ -4189,6 +4191,7 @@ public struct CronListParams: Codable, Sendable { self.enabled = enabled self.sortby = sortby self.sortdir = sortdir + self.agentid = agentid } private enum CodingKeys: String, CodingKey { @@ -4199,6 +4202,7 @@ public struct CronListParams: Codable, Sendable { case enabled case sortby = "sortBy" case sortdir = "sortDir" + case agentid = "agentId" } } diff --git a/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift b/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift index af9e7887816..4dd8e28bc5f 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift @@ -4172,6 +4172,7 @@ public struct CronListParams: Codable, Sendable { public let enabled: AnyCodable? public let sortby: AnyCodable? public let sortdir: AnyCodable? + public let agentid: String? public init( includedisabled: Bool?, @@ -4180,7 +4181,8 @@ public struct CronListParams: Codable, Sendable { query: String?, enabled: AnyCodable?, sortby: AnyCodable?, - sortdir: AnyCodable?) + sortdir: AnyCodable?, + agentid: String?) { self.includedisabled = includedisabled self.limit = limit @@ -4189,6 +4191,7 @@ public struct CronListParams: Codable, Sendable { self.enabled = enabled self.sortby = sortby self.sortdir = sortdir + self.agentid = agentid } private enum CodingKeys: String, CodingKey { @@ -4199,6 +4202,7 @@ public struct CronListParams: Codable, Sendable { case enabled case sortby = "sortBy" case sortdir = "sortDir" + case agentid = "agentId" } } diff --git a/docs/cli/cron.md b/docs/cli/cron.md index 754d61f151e..f836b6520c1 100644 --- a/docs/cli/cron.md +++ b/docs/cli/cron.md @@ -211,12 +211,15 @@ Manual run and inspection: ```bash openclaw cron list +openclaw cron list --agent ops openclaw cron show openclaw cron run openclaw cron run --due openclaw cron runs --id --limit 50 ``` +`openclaw cron list` shows all matching jobs by default. Pass `--agent ` to show only jobs whose effective normalized agent id matches; jobs without a stored agent id count as the configured default agent. + `cron runs` entries include delivery diagnostics with the intended cron target, the resolved target, message-tool sends, fallback use, and delivered state. Agent and session retargeting: diff --git a/src/agents/tools/cron-tool.test.ts b/src/agents/tools/cron-tool.test.ts index 8c228ce03be..28efe59a0f7 100644 --- a/src/agents/tools/cron-tool.test.ts +++ b/src/agents/tools/cron-tool.test.ts @@ -210,6 +210,34 @@ describe("cron tool", () => { expect(callGatewayMock).not.toHaveBeenCalled(); }); + it("filters cron list by the requester agent session", async () => { + const tool = createTestCronTool({ + agentSessionKey: "agent:agent-123:telegram:direct:channing", + }); + + await tool.execute("call-list", { + action: "list", + }); + + const params = expectSingleGatewayCallMethod("cron.list"); + expect(params).toEqual({ includeDisabled: false, agentId: "agent-123" }); + }); + + it("prefers explicit cron list agent id over the requester session", async () => { + const tool = createTestCronTool({ + agentSessionKey: "agent:agent-123:telegram:direct:channing", + }); + + await tool.execute("call-list-explicit", { + action: "list", + agentId: "ops", + includeDisabled: true, + }); + + const params = expectSingleGatewayCallMethod("cron.list"); + expect(params).toEqual({ includeDisabled: true, agentId: "ops" }); + }); + it("documents deferred follow-up guidance in the tool description", () => { const tool = createTestCronTool(); expect(tool.description).toContain( diff --git a/src/agents/tools/cron-tool.ts b/src/agents/tools/cron-tool.ts index b0ed6a05a36..b0ddbe65fb6 100644 --- a/src/agents/tools/cron-tool.ts +++ b/src/agents/tools/cron-tool.ts @@ -307,6 +307,7 @@ export const CronToolSchema = Type.Object( contextMessages: Type.Optional( Type.Number({ minimum: 0, maximum: REMINDER_CONTEXT_MESSAGES_MAX }), ), + agentId: Type.Optional(Type.String({ description: "Filter by agent id (list action)" })), }, { additionalProperties: true }, ); @@ -570,7 +571,7 @@ Main-session cron jobs enqueue system events for heartbeat handling. Isolated cr ACTIONS: - status: Check cron scheduler status -- list: List jobs (use includeDisabled:true to include disabled) +- list: List jobs (use includeDisabled:true to include disabled; agentId filters by agent, auto-filled from session) - add: Create job (requires job object, see schema below) - update: Modify job (requires jobId + patch object) - remove: Delete job (requires jobId) @@ -653,12 +654,21 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con switch (action) { case "status": return jsonResult(await callGateway("cron.status", gatewayOpts, {})); - case "list": + case "list": { + const cfg = getRuntimeConfig(); + const listAgentId = + typeof params.agentId === "string" && params.agentId.trim() + ? params.agentId.trim() + : opts?.agentSessionKey + ? resolveSessionAgentId({ sessionKey: opts.agentSessionKey, config: cfg }) + : undefined; return jsonResult( await callGateway("cron.list", gatewayOpts, { includeDisabled: Boolean(params.includeDisabled), + agentId: listAgentId, }), ); + } case "add": { // Flat-params recovery: non-frontier models (e.g. Grok) sometimes flatten // job properties to the top level alongside `action` instead of nesting diff --git a/src/cli/cron-cli.test.ts b/src/cli/cron-cli.test.ts index f50fb26fe7b..0b8d7d70445 100644 --- a/src/cli/cron-cli.test.ts +++ b/src/cli/cron-cli.test.ts @@ -481,6 +481,20 @@ describe("cron cli", () => { expect(patch.enabled).toBe(expectedEnabled); }); + it("leaves cron list unfiltered when --agent is omitted", async () => { + await runCronCommand(["cron", "list"]); + + const listCall = callGatewayFromCli.mock.calls.find((call) => call[0] === "cron.list"); + expect(listCall?.[2]).toEqual({ includeDisabled: false }); + }); + + it("sends normalized agent id on cron list --agent", async () => { + await runCronCommand(["cron", "list", "--agent", " Ops "]); + + const listCall = callGatewayFromCli.mock.calls.find((call) => call[0] === "cron.list"); + expect(listCall?.[2]).toEqual({ includeDisabled: false, agentId: "ops" }); + }); + it("paginates cron show lookups", async () => { resetGatewayMock(); callGatewayFromCli.mockImplementation( diff --git a/src/cli/cron-cli/register.cron-add.ts b/src/cli/cron-cli/register.cron-add.ts index fa3772c211f..232677b1992 100644 --- a/src/cli/cron-cli/register.cron-add.ts +++ b/src/cli/cron-cli/register.cron-add.ts @@ -45,12 +45,18 @@ export function registerCronListCommand(cron: Command) { .command("list") .description("List cron jobs") .option("--all", "Include disabled jobs", false) + .option("--agent ", "Filter by agent id") .option("--json", "Output JSON", false) .action(async (opts) => { try { - const res = await callGatewayFromCli("cron.list", opts, { + const listParams: Record = { includeDisabled: Boolean(opts.all), - }); + }; + const agentId = normalizeOptionalString(opts.agent); + if (agentId) { + listParams.agentId = sanitizeAgentId(agentId); + } + const res = await callGatewayFromCli("cron.list", opts, listParams); if (opts.json) { printCronJson(res); return; diff --git a/src/cron/service.list-page-sort-guards.test.ts b/src/cron/service.list-page-sort-guards.test.ts index 69349147adf..26a85fa0f75 100644 --- a/src/cron/service.list-page-sort-guards.test.ts +++ b/src/cron/service.list-page-sort-guards.test.ts @@ -50,4 +50,56 @@ describe("cron listPage sort guards", () => { const page = await listPage(state, { sortBy: "nextRunAtMs", sortDir: "asc" }); expect(page.jobs).toHaveLength(2); }); + + it("normalizes requested agent ids before filtering", async () => { + const jobs = [ + createBaseJob({ id: "job-main", agentId: "main", name: "main" }), + createBaseJob({ id: "job-ops", agentId: "ops", name: "ops" }), + createBaseJob({ id: "job-unset", agentId: undefined, name: "unset" }), + ]; + const state = createMockCronStateForJobs({ jobs }); + + const page = await listPage(state, { agentId: " Ops " }); + + expect(page.jobs.map((job) => job.id)).toEqual(["job-ops"]); + }); + + it("matches omitted job agent ids to the configured default agent when filtering", async () => { + const jobs = [ + createBaseJob({ id: "job-main", agentId: "main", name: "main" }), + createBaseJob({ id: "job-ops", agentId: "ops", name: "ops" }), + createBaseJob({ id: "job-unset", agentId: undefined, name: "unset" }), + ]; + const state = createMockCronStateForJobs({ jobs }); + state.deps.defaultAgentId = " Ops "; + + const page = await listPage(state, { agentId: "ops" }); + + expect(page.jobs.map((job) => job.id)).toEqual(["job-ops", "job-unset"]); + }); + + it("matches omitted job agent ids to main when no default agent is configured", async () => { + const jobs = [ + createBaseJob({ id: "job-main", agentId: "main", name: "main" }), + createBaseJob({ id: "job-ops", agentId: "ops", name: "ops" }), + createBaseJob({ id: "job-unset", agentId: undefined, name: "unset" }), + ]; + const state = createMockCronStateForJobs({ jobs }); + + const page = await listPage(state, { agentId: "main" }); + + expect(page.jobs.map((job) => job.id)).toEqual(["job-main", "job-unset"]); + }); + + it("keeps listPage unfiltered when agent id is omitted", async () => { + const jobs = [ + createBaseJob({ id: "job-main", agentId: "main", name: "main" }), + createBaseJob({ id: "job-ops", agentId: "ops", name: "ops" }), + ]; + const state = createMockCronStateForJobs({ jobs }); + + const page = await listPage(state); + + expect(page.jobs.map((job) => job.id)).toEqual(["job-main", "job-ops"]); + }); }); diff --git a/src/cron/service/list-page-types.ts b/src/cron/service/list-page-types.ts index da76033c51d..07ffd58ea45 100644 --- a/src/cron/service/list-page-types.ts +++ b/src/cron/service/list-page-types.ts @@ -12,6 +12,7 @@ export type CronListPageOptions = { enabled?: CronJobsEnabledFilter; sortBy?: CronJobsSortBy; sortDir?: CronSortDir; + agentId?: string; }; export type CronListPageResult = { diff --git a/src/cron/service/ops.ts b/src/cron/service/ops.ts index 0fdd3f23151..db35a540d47 100644 --- a/src/cron/service/ops.ts +++ b/src/cron/service/ops.ts @@ -1,5 +1,6 @@ import { enqueueCommandInLane } from "../../process/command-queue.js"; import { CommandLane } from "../../process/lanes.js"; +import { DEFAULT_AGENT_ID } from "../../routing/session-key.js"; import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js"; import { completeTaskRunByRunId, @@ -30,6 +31,7 @@ import type { CronSortDir, } from "./list-page-types.js"; import { locked } from "./locked.js"; +import { normalizeOptionalAgentId } from "./normalize.js"; import type { CronServiceState } from "./state.js"; import { ensureLoaded, persist, warnIfDisabled } from "./store.js"; import { @@ -272,6 +274,14 @@ function sortJobs(jobs: CronJob[], sortBy: CronJobsSortBy, sortDir: CronSortDir) }); } +function resolveEffectiveJobAgentId(job: CronJob, defaultAgentId: string | undefined) { + return ( + normalizeOptionalAgentId(job.agentId) ?? + normalizeOptionalAgentId(defaultAgentId) ?? + DEFAULT_AGENT_ID + ); +} + export async function listPage(state: CronServiceState, opts?: CronListPageOptions) { return await locked(state, async () => { await ensureLoadedForRead(state); @@ -279,6 +289,7 @@ export async function listPage(state: CronServiceState, opts?: CronListPageOptio const enabledFilter = resolveEnabledFilter(opts); const sortBy = opts?.sortBy ?? "nextRunAtMs"; const sortDir = opts?.sortDir ?? "asc"; + const requestedAgentId = normalizeOptionalAgentId(opts?.agentId); const source = state.store?.jobs ?? []; const filtered = source.filter((job) => { if (enabledFilter === "enabled" && !isJobEnabled(job)) { @@ -287,6 +298,12 @@ export async function listPage(state: CronServiceState, opts?: CronListPageOptio if (enabledFilter === "disabled" && isJobEnabled(job)) { return false; } + if ( + requestedAgentId && + resolveEffectiveJobAgentId(job, state.deps.defaultAgentId) !== requestedAgentId + ) { + return false; + } if (!query) { return true; } diff --git a/src/gateway/protocol/cron-validators.test.ts b/src/gateway/protocol/cron-validators.test.ts index 3d869131580..69073b93ef4 100644 --- a/src/gateway/protocol/cron-validators.test.ts +++ b/src/gateway/protocol/cron-validators.test.ts @@ -111,9 +111,11 @@ describe("cron protocol validators", () => { enabled: "all", sortBy: "nextRunAtMs", sortDir: "asc", + agentId: "ops", }), ).toBe(true); expect(validateCronListParams({ offset: -1 })).toBe(false); + expect(validateCronListParams({ agentId: "" })).toBe(false); }); it("enforces runs limit minimum for id and jobId selectors", () => { diff --git a/src/gateway/protocol/schema/cron.ts b/src/gateway/protocol/schema/cron.ts index c985e9fcbac..816e82a5d5c 100644 --- a/src/gateway/protocol/schema/cron.ts +++ b/src/gateway/protocol/schema/cron.ts @@ -343,6 +343,7 @@ export const CronListParamsSchema = Type.Object( enabled: Type.Optional(CronJobsEnabledFilterSchema), sortBy: Type.Optional(CronJobsSortBySchema), sortDir: Type.Optional(CronSortDirSchema), + agentId: Type.Optional(NonEmptyString), }, { additionalProperties: false }, ); diff --git a/src/gateway/server-methods/cron.ts b/src/gateway/server-methods/cron.ts index eddaa1db12c..5893a647caa 100644 --- a/src/gateway/server-methods/cron.ts +++ b/src/gateway/server-methods/cron.ts @@ -199,6 +199,7 @@ export const cronHandlers: GatewayRequestHandlers = { enabled?: "all" | "enabled" | "disabled"; sortBy?: "nextRunAtMs" | "updatedAtMs" | "name"; sortDir?: "asc" | "desc"; + agentId?: string; }; const page = await context.cron.listPage({ includeDisabled: p.includeDisabled, @@ -208,6 +209,7 @@ export const cronHandlers: GatewayRequestHandlers = { enabled: p.enabled, sortBy: p.sortBy, sortDir: p.sortDir, + agentId: p.agentId, }); const deliveryPreviews = await resolveCronDeliveryPreviews({ cfg: context.getRuntimeConfig(), 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 ad497cc8692..462897e612b 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 @@ -200,7 +200,7 @@ "name": "nodes" }, { - "description": "Manage Gateway cron jobs (status/list/add/update/remove/run/runs) and send wake events. Use this for reminders, \"check back later\" requests, delayed follow-ups, and recurring tasks. Do not emulate scheduling with exec sleep or process polling.\n\nMain-session cron jobs enqueue system events for heartbeat handling. Isolated cron jobs create background task runs that appear in `openclaw tasks`.\n\nACTIONS:\n- status: Check cron scheduler status\n- list: List jobs (use includeDisabled:true to include disabled)\n- add: Create job (requires job object, see schema below)\n- update: Modify job (requires jobId + patch object)\n- remove: Delete job (requires jobId)\n- run: Trigger job immediately (requires jobId)\n- runs: Get job run history (requires jobId)\n- wake: Send wake event (requires text, optional mode)\n\nJOB SCHEMA (for add action):\n{\n \"name\": \"string (optional)\",\n \"schedule\": { ... }, // Required: when to run\n \"payload\": { ... }, // Required: what to execute\n \"delivery\": { ... }, // Optional: announce summary (isolated/current/session:xxx only) or webhook POST\n \"sessionTarget\": \"main\" | \"isolated\" | \"current\" | \"session:\", // Optional, defaults based on context\n \"enabled\": true | false // Optional, default true\n}\n\nSESSION TARGET OPTIONS:\n- \"main\": Run in the main session (requires payload.kind=\"systemEvent\")\n- \"isolated\": Run in an ephemeral isolated session (requires payload.kind=\"agentTurn\")\n- \"current\": Bind to the current session where the cron is created (resolved at creation time)\n- \"session:\": Run in a persistent named session (e.g., \"session:project-alpha-daily\")\n\nDEFAULT BEHAVIOR (unchanged for backward compatibility):\n- payload.kind=\"systemEvent\" → defaults to \"main\"\n- payload.kind=\"agentTurn\" → defaults to \"isolated\"\nTo use current session binding, explicitly set sessionTarget=\"current\".\n\nSCHEDULE TYPES (schedule.kind):\n- \"at\": One-shot at absolute time\n { \"kind\": \"at\", \"at\": \"\" }\n- \"every\": Recurring interval\n { \"kind\": \"every\", \"everyMs\": , \"anchorMs\": }\n- \"cron\": Cron expression evaluated in the supplied timezone, or the Gateway host local timezone when tz is omitted\n { \"kind\": \"cron\", \"expr\": \"\", \"tz\": \"\" }\n Write expr in the selected timezone's local wall-clock time; do not convert the requested local time to UTC first.\n If tz is omitted, do not assume UTC; the Gateway host local timezone is used.\n Example: \"Remind me every day at 6pm Shanghai time\" -> { \"kind\": \"cron\", \"expr\": \"0 18 * * *\", \"tz\": \"Asia/Shanghai\" }\n\nFor schedule.kind=\"at\", ISO timestamps without an explicit timezone are treated as UTC.\n\nPAYLOAD TYPES (payload.kind):\n- \"systemEvent\": Injects text as system event into session\n { \"kind\": \"systemEvent\", \"text\": \"\" }\n- \"agentTurn\": Runs agent with message (isolated sessions only)\n { \"kind\": \"agentTurn\", \"message\": \"\", \"model\": \"\", \"thinking\": \"\", \"timeoutSeconds\": }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"\", \"to\": \"\", \"threadId\": \"\", \"bestEffort\": }\n - Default for isolated agentTurn jobs (when delivery omitted): \"announce\"\n - announce: send to chat channel (optional channel/to target)\n - threadId: chat thread/topic id for channels that support threaded delivery\n - webhook: send finished-run event as HTTP POST to delivery.to (URL required)\n - If the task needs to send to a specific chat/recipient, set announce delivery.channel/to; do not call messaging tools inside the run.\n\nCRITICAL CONSTRAINTS:\n- sessionTarget=\"main\" REQUIRES payload.kind=\"systemEvent\"\n- sessionTarget=\"isolated\" | \"current\" | \"session:xxx\" REQUIRES payload.kind=\"agentTurn\"\n- For webhook callbacks, use delivery.mode=\"webhook\" with delivery.to set to a URL.\nDefault: prefer isolated agentTurn jobs unless the user explicitly wants current-session binding.\n\nWAKE MODES (for wake action):\n- \"next-heartbeat\" (default): Wake on next heartbeat\n- \"now\": Wake immediately\n\nUse jobId as the canonical identifier; id is accepted for compatibility. Use contextMessages (0-10) to add previous messages as context to the job text.", + "description": "Manage Gateway cron jobs (status/list/add/update/remove/run/runs) and send wake events. Use this for reminders, \"check back later\" requests, delayed follow-ups, and recurring tasks. Do not emulate scheduling with exec sleep or process polling.\n\nMain-session cron jobs enqueue system events for heartbeat handling. Isolated cron jobs create background task runs that appear in `openclaw tasks`.\n\nACTIONS:\n- status: Check cron scheduler status\n- list: List jobs (use includeDisabled:true to include disabled; agentId filters by agent, auto-filled from session)\n- add: Create job (requires job object, see schema below)\n- update: Modify job (requires jobId + patch object)\n- remove: Delete job (requires jobId)\n- run: Trigger job immediately (requires jobId)\n- runs: Get job run history (requires jobId)\n- wake: Send wake event (requires text, optional mode)\n\nJOB SCHEMA (for add action):\n{\n \"name\": \"string (optional)\",\n \"schedule\": { ... }, // Required: when to run\n \"payload\": { ... }, // Required: what to execute\n \"delivery\": { ... }, // Optional: announce summary (isolated/current/session:xxx only) or webhook POST\n \"sessionTarget\": \"main\" | \"isolated\" | \"current\" | \"session:\", // Optional, defaults based on context\n \"enabled\": true | false // Optional, default true\n}\n\nSESSION TARGET OPTIONS:\n- \"main\": Run in the main session (requires payload.kind=\"systemEvent\")\n- \"isolated\": Run in an ephemeral isolated session (requires payload.kind=\"agentTurn\")\n- \"current\": Bind to the current session where the cron is created (resolved at creation time)\n- \"session:\": Run in a persistent named session (e.g., \"session:project-alpha-daily\")\n\nDEFAULT BEHAVIOR (unchanged for backward compatibility):\n- payload.kind=\"systemEvent\" → defaults to \"main\"\n- payload.kind=\"agentTurn\" → defaults to \"isolated\"\nTo use current session binding, explicitly set sessionTarget=\"current\".\n\nSCHEDULE TYPES (schedule.kind):\n- \"at\": One-shot at absolute time\n { \"kind\": \"at\", \"at\": \"\" }\n- \"every\": Recurring interval\n { \"kind\": \"every\", \"everyMs\": , \"anchorMs\": }\n- \"cron\": Cron expression evaluated in the supplied timezone, or the Gateway host local timezone when tz is omitted\n { \"kind\": \"cron\", \"expr\": \"\", \"tz\": \"\" }\n Write expr in the selected timezone's local wall-clock time; do not convert the requested local time to UTC first.\n If tz is omitted, do not assume UTC; the Gateway host local timezone is used.\n Example: \"Remind me every day at 6pm Shanghai time\" -> { \"kind\": \"cron\", \"expr\": \"0 18 * * *\", \"tz\": \"Asia/Shanghai\" }\n\nFor schedule.kind=\"at\", ISO timestamps without an explicit timezone are treated as UTC.\n\nPAYLOAD TYPES (payload.kind):\n- \"systemEvent\": Injects text as system event into session\n { \"kind\": \"systemEvent\", \"text\": \"\" }\n- \"agentTurn\": Runs agent with message (isolated sessions only)\n { \"kind\": \"agentTurn\", \"message\": \"\", \"model\": \"\", \"thinking\": \"\", \"timeoutSeconds\": }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"\", \"to\": \"\", \"threadId\": \"\", \"bestEffort\": }\n - Default for isolated agentTurn jobs (when delivery omitted): \"announce\"\n - announce: send to chat channel (optional channel/to target)\n - threadId: chat thread/topic id for channels that support threaded delivery\n - webhook: send finished-run event as HTTP POST to delivery.to (URL required)\n - If the task needs to send to a specific chat/recipient, set announce delivery.channel/to; do not call messaging tools inside the run.\n\nCRITICAL CONSTRAINTS:\n- sessionTarget=\"main\" REQUIRES payload.kind=\"systemEvent\"\n- sessionTarget=\"isolated\" | \"current\" | \"session:xxx\" REQUIRES payload.kind=\"agentTurn\"\n- For webhook callbacks, use delivery.mode=\"webhook\" with delivery.to set to a URL.\nDefault: prefer isolated agentTurn jobs unless the user explicitly wants current-session binding.\n\nWAKE MODES (for wake action):\n- \"next-heartbeat\" (default): Wake on next heartbeat\n- \"now\": Wake immediately\n\nUse jobId as the canonical identifier; id is accepted for compatibility. Use contextMessages (0-10) to add previous messages as context to the job text.", "inputSchema": { "additionalProperties": true, "properties": { @@ -208,6 +208,10 @@ "enum": ["status", "list", "add", "update", "remove", "run", "runs", "wake"], "type": "string" }, + "agentId": { + "description": "Filter by agent id (list action)", + "type": "string" + }, "contextMessages": { "maximum": 10, "minimum": 0, 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 70ad7313edb..b1f0aaaea6b 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 @@ -200,7 +200,7 @@ "name": "nodes" }, { - "description": "Manage Gateway cron jobs (status/list/add/update/remove/run/runs) and send wake events. Use this for reminders, \"check back later\" requests, delayed follow-ups, and recurring tasks. Do not emulate scheduling with exec sleep or process polling.\n\nMain-session cron jobs enqueue system events for heartbeat handling. Isolated cron jobs create background task runs that appear in `openclaw tasks`.\n\nACTIONS:\n- status: Check cron scheduler status\n- list: List jobs (use includeDisabled:true to include disabled)\n- add: Create job (requires job object, see schema below)\n- update: Modify job (requires jobId + patch object)\n- remove: Delete job (requires jobId)\n- run: Trigger job immediately (requires jobId)\n- runs: Get job run history (requires jobId)\n- wake: Send wake event (requires text, optional mode)\n\nJOB SCHEMA (for add action):\n{\n \"name\": \"string (optional)\",\n \"schedule\": { ... }, // Required: when to run\n \"payload\": { ... }, // Required: what to execute\n \"delivery\": { ... }, // Optional: announce summary (isolated/current/session:xxx only) or webhook POST\n \"sessionTarget\": \"main\" | \"isolated\" | \"current\" | \"session:\", // Optional, defaults based on context\n \"enabled\": true | false // Optional, default true\n}\n\nSESSION TARGET OPTIONS:\n- \"main\": Run in the main session (requires payload.kind=\"systemEvent\")\n- \"isolated\": Run in an ephemeral isolated session (requires payload.kind=\"agentTurn\")\n- \"current\": Bind to the current session where the cron is created (resolved at creation time)\n- \"session:\": Run in a persistent named session (e.g., \"session:project-alpha-daily\")\n\nDEFAULT BEHAVIOR (unchanged for backward compatibility):\n- payload.kind=\"systemEvent\" → defaults to \"main\"\n- payload.kind=\"agentTurn\" → defaults to \"isolated\"\nTo use current session binding, explicitly set sessionTarget=\"current\".\n\nSCHEDULE TYPES (schedule.kind):\n- \"at\": One-shot at absolute time\n { \"kind\": \"at\", \"at\": \"\" }\n- \"every\": Recurring interval\n { \"kind\": \"every\", \"everyMs\": , \"anchorMs\": }\n- \"cron\": Cron expression evaluated in the supplied timezone, or the Gateway host local timezone when tz is omitted\n { \"kind\": \"cron\", \"expr\": \"\", \"tz\": \"\" }\n Write expr in the selected timezone's local wall-clock time; do not convert the requested local time to UTC first.\n If tz is omitted, do not assume UTC; the Gateway host local timezone is used.\n Example: \"Remind me every day at 6pm Shanghai time\" -> { \"kind\": \"cron\", \"expr\": \"0 18 * * *\", \"tz\": \"Asia/Shanghai\" }\n\nFor schedule.kind=\"at\", ISO timestamps without an explicit timezone are treated as UTC.\n\nPAYLOAD TYPES (payload.kind):\n- \"systemEvent\": Injects text as system event into session\n { \"kind\": \"systemEvent\", \"text\": \"\" }\n- \"agentTurn\": Runs agent with message (isolated sessions only)\n { \"kind\": \"agentTurn\", \"message\": \"\", \"model\": \"\", \"thinking\": \"\", \"timeoutSeconds\": }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"\", \"to\": \"\", \"threadId\": \"\", \"bestEffort\": }\n - Default for isolated agentTurn jobs (when delivery omitted): \"announce\"\n - announce: send to chat channel (optional channel/to target)\n - threadId: chat thread/topic id for channels that support threaded delivery\n - webhook: send finished-run event as HTTP POST to delivery.to (URL required)\n - If the task needs to send to a specific chat/recipient, set announce delivery.channel/to; do not call messaging tools inside the run.\n\nCRITICAL CONSTRAINTS:\n- sessionTarget=\"main\" REQUIRES payload.kind=\"systemEvent\"\n- sessionTarget=\"isolated\" | \"current\" | \"session:xxx\" REQUIRES payload.kind=\"agentTurn\"\n- For webhook callbacks, use delivery.mode=\"webhook\" with delivery.to set to a URL.\nDefault: prefer isolated agentTurn jobs unless the user explicitly wants current-session binding.\n\nWAKE MODES (for wake action):\n- \"next-heartbeat\" (default): Wake on next heartbeat\n- \"now\": Wake immediately\n\nUse jobId as the canonical identifier; id is accepted for compatibility. Use contextMessages (0-10) to add previous messages as context to the job text.", + "description": "Manage Gateway cron jobs (status/list/add/update/remove/run/runs) and send wake events. Use this for reminders, \"check back later\" requests, delayed follow-ups, and recurring tasks. Do not emulate scheduling with exec sleep or process polling.\n\nMain-session cron jobs enqueue system events for heartbeat handling. Isolated cron jobs create background task runs that appear in `openclaw tasks`.\n\nACTIONS:\n- status: Check cron scheduler status\n- list: List jobs (use includeDisabled:true to include disabled; agentId filters by agent, auto-filled from session)\n- add: Create job (requires job object, see schema below)\n- update: Modify job (requires jobId + patch object)\n- remove: Delete job (requires jobId)\n- run: Trigger job immediately (requires jobId)\n- runs: Get job run history (requires jobId)\n- wake: Send wake event (requires text, optional mode)\n\nJOB SCHEMA (for add action):\n{\n \"name\": \"string (optional)\",\n \"schedule\": { ... }, // Required: when to run\n \"payload\": { ... }, // Required: what to execute\n \"delivery\": { ... }, // Optional: announce summary (isolated/current/session:xxx only) or webhook POST\n \"sessionTarget\": \"main\" | \"isolated\" | \"current\" | \"session:\", // Optional, defaults based on context\n \"enabled\": true | false // Optional, default true\n}\n\nSESSION TARGET OPTIONS:\n- \"main\": Run in the main session (requires payload.kind=\"systemEvent\")\n- \"isolated\": Run in an ephemeral isolated session (requires payload.kind=\"agentTurn\")\n- \"current\": Bind to the current session where the cron is created (resolved at creation time)\n- \"session:\": Run in a persistent named session (e.g., \"session:project-alpha-daily\")\n\nDEFAULT BEHAVIOR (unchanged for backward compatibility):\n- payload.kind=\"systemEvent\" → defaults to \"main\"\n- payload.kind=\"agentTurn\" → defaults to \"isolated\"\nTo use current session binding, explicitly set sessionTarget=\"current\".\n\nSCHEDULE TYPES (schedule.kind):\n- \"at\": One-shot at absolute time\n { \"kind\": \"at\", \"at\": \"\" }\n- \"every\": Recurring interval\n { \"kind\": \"every\", \"everyMs\": , \"anchorMs\": }\n- \"cron\": Cron expression evaluated in the supplied timezone, or the Gateway host local timezone when tz is omitted\n { \"kind\": \"cron\", \"expr\": \"\", \"tz\": \"\" }\n Write expr in the selected timezone's local wall-clock time; do not convert the requested local time to UTC first.\n If tz is omitted, do not assume UTC; the Gateway host local timezone is used.\n Example: \"Remind me every day at 6pm Shanghai time\" -> { \"kind\": \"cron\", \"expr\": \"0 18 * * *\", \"tz\": \"Asia/Shanghai\" }\n\nFor schedule.kind=\"at\", ISO timestamps without an explicit timezone are treated as UTC.\n\nPAYLOAD TYPES (payload.kind):\n- \"systemEvent\": Injects text as system event into session\n { \"kind\": \"systemEvent\", \"text\": \"\" }\n- \"agentTurn\": Runs agent with message (isolated sessions only)\n { \"kind\": \"agentTurn\", \"message\": \"\", \"model\": \"\", \"thinking\": \"\", \"timeoutSeconds\": }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"\", \"to\": \"\", \"threadId\": \"\", \"bestEffort\": }\n - Default for isolated agentTurn jobs (when delivery omitted): \"announce\"\n - announce: send to chat channel (optional channel/to target)\n - threadId: chat thread/topic id for channels that support threaded delivery\n - webhook: send finished-run event as HTTP POST to delivery.to (URL required)\n - If the task needs to send to a specific chat/recipient, set announce delivery.channel/to; do not call messaging tools inside the run.\n\nCRITICAL CONSTRAINTS:\n- sessionTarget=\"main\" REQUIRES payload.kind=\"systemEvent\"\n- sessionTarget=\"isolated\" | \"current\" | \"session:xxx\" REQUIRES payload.kind=\"agentTurn\"\n- For webhook callbacks, use delivery.mode=\"webhook\" with delivery.to set to a URL.\nDefault: prefer isolated agentTurn jobs unless the user explicitly wants current-session binding.\n\nWAKE MODES (for wake action):\n- \"next-heartbeat\" (default): Wake on next heartbeat\n- \"now\": Wake immediately\n\nUse jobId as the canonical identifier; id is accepted for compatibility. Use contextMessages (0-10) to add previous messages as context to the job text.", "inputSchema": { "additionalProperties": true, "properties": { @@ -208,6 +208,10 @@ "enum": ["status", "list", "add", "update", "remove", "run", "runs", "wake"], "type": "string" }, + "agentId": { + "description": "Filter by agent id (list action)", + "type": "string" + }, "contextMessages": { "maximum": 10, "minimum": 0, 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 3b7c945c779..11b9d9560c0 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 @@ -200,7 +200,7 @@ "name": "nodes" }, { - "description": "Manage Gateway cron jobs (status/list/add/update/remove/run/runs) and send wake events. Use this for reminders, \"check back later\" requests, delayed follow-ups, and recurring tasks. Do not emulate scheduling with exec sleep or process polling.\n\nMain-session cron jobs enqueue system events for heartbeat handling. Isolated cron jobs create background task runs that appear in `openclaw tasks`.\n\nACTIONS:\n- status: Check cron scheduler status\n- list: List jobs (use includeDisabled:true to include disabled)\n- add: Create job (requires job object, see schema below)\n- update: Modify job (requires jobId + patch object)\n- remove: Delete job (requires jobId)\n- run: Trigger job immediately (requires jobId)\n- runs: Get job run history (requires jobId)\n- wake: Send wake event (requires text, optional mode)\n\nJOB SCHEMA (for add action):\n{\n \"name\": \"string (optional)\",\n \"schedule\": { ... }, // Required: when to run\n \"payload\": { ... }, // Required: what to execute\n \"delivery\": { ... }, // Optional: announce summary (isolated/current/session:xxx only) or webhook POST\n \"sessionTarget\": \"main\" | \"isolated\" | \"current\" | \"session:\", // Optional, defaults based on context\n \"enabled\": true | false // Optional, default true\n}\n\nSESSION TARGET OPTIONS:\n- \"main\": Run in the main session (requires payload.kind=\"systemEvent\")\n- \"isolated\": Run in an ephemeral isolated session (requires payload.kind=\"agentTurn\")\n- \"current\": Bind to the current session where the cron is created (resolved at creation time)\n- \"session:\": Run in a persistent named session (e.g., \"session:project-alpha-daily\")\n\nDEFAULT BEHAVIOR (unchanged for backward compatibility):\n- payload.kind=\"systemEvent\" → defaults to \"main\"\n- payload.kind=\"agentTurn\" → defaults to \"isolated\"\nTo use current session binding, explicitly set sessionTarget=\"current\".\n\nSCHEDULE TYPES (schedule.kind):\n- \"at\": One-shot at absolute time\n { \"kind\": \"at\", \"at\": \"\" }\n- \"every\": Recurring interval\n { \"kind\": \"every\", \"everyMs\": , \"anchorMs\": }\n- \"cron\": Cron expression evaluated in the supplied timezone, or the Gateway host local timezone when tz is omitted\n { \"kind\": \"cron\", \"expr\": \"\", \"tz\": \"\" }\n Write expr in the selected timezone's local wall-clock time; do not convert the requested local time to UTC first.\n If tz is omitted, do not assume UTC; the Gateway host local timezone is used.\n Example: \"Remind me every day at 6pm Shanghai time\" -> { \"kind\": \"cron\", \"expr\": \"0 18 * * *\", \"tz\": \"Asia/Shanghai\" }\n\nFor schedule.kind=\"at\", ISO timestamps without an explicit timezone are treated as UTC.\n\nPAYLOAD TYPES (payload.kind):\n- \"systemEvent\": Injects text as system event into session\n { \"kind\": \"systemEvent\", \"text\": \"\" }\n- \"agentTurn\": Runs agent with message (isolated sessions only)\n { \"kind\": \"agentTurn\", \"message\": \"\", \"model\": \"\", \"thinking\": \"\", \"timeoutSeconds\": }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"\", \"to\": \"\", \"threadId\": \"\", \"bestEffort\": }\n - Default for isolated agentTurn jobs (when delivery omitted): \"announce\"\n - announce: send to chat channel (optional channel/to target)\n - threadId: chat thread/topic id for channels that support threaded delivery\n - webhook: send finished-run event as HTTP POST to delivery.to (URL required)\n - If the task needs to send to a specific chat/recipient, set announce delivery.channel/to; do not call messaging tools inside the run.\n\nCRITICAL CONSTRAINTS:\n- sessionTarget=\"main\" REQUIRES payload.kind=\"systemEvent\"\n- sessionTarget=\"isolated\" | \"current\" | \"session:xxx\" REQUIRES payload.kind=\"agentTurn\"\n- For webhook callbacks, use delivery.mode=\"webhook\" with delivery.to set to a URL.\nDefault: prefer isolated agentTurn jobs unless the user explicitly wants current-session binding.\n\nWAKE MODES (for wake action):\n- \"next-heartbeat\" (default): Wake on next heartbeat\n- \"now\": Wake immediately\n\nUse jobId as the canonical identifier; id is accepted for compatibility. Use contextMessages (0-10) to add previous messages as context to the job text.", + "description": "Manage Gateway cron jobs (status/list/add/update/remove/run/runs) and send wake events. Use this for reminders, \"check back later\" requests, delayed follow-ups, and recurring tasks. Do not emulate scheduling with exec sleep or process polling.\n\nMain-session cron jobs enqueue system events for heartbeat handling. Isolated cron jobs create background task runs that appear in `openclaw tasks`.\n\nACTIONS:\n- status: Check cron scheduler status\n- list: List jobs (use includeDisabled:true to include disabled; agentId filters by agent, auto-filled from session)\n- add: Create job (requires job object, see schema below)\n- update: Modify job (requires jobId + patch object)\n- remove: Delete job (requires jobId)\n- run: Trigger job immediately (requires jobId)\n- runs: Get job run history (requires jobId)\n- wake: Send wake event (requires text, optional mode)\n\nJOB SCHEMA (for add action):\n{\n \"name\": \"string (optional)\",\n \"schedule\": { ... }, // Required: when to run\n \"payload\": { ... }, // Required: what to execute\n \"delivery\": { ... }, // Optional: announce summary (isolated/current/session:xxx only) or webhook POST\n \"sessionTarget\": \"main\" | \"isolated\" | \"current\" | \"session:\", // Optional, defaults based on context\n \"enabled\": true | false // Optional, default true\n}\n\nSESSION TARGET OPTIONS:\n- \"main\": Run in the main session (requires payload.kind=\"systemEvent\")\n- \"isolated\": Run in an ephemeral isolated session (requires payload.kind=\"agentTurn\")\n- \"current\": Bind to the current session where the cron is created (resolved at creation time)\n- \"session:\": Run in a persistent named session (e.g., \"session:project-alpha-daily\")\n\nDEFAULT BEHAVIOR (unchanged for backward compatibility):\n- payload.kind=\"systemEvent\" → defaults to \"main\"\n- payload.kind=\"agentTurn\" → defaults to \"isolated\"\nTo use current session binding, explicitly set sessionTarget=\"current\".\n\nSCHEDULE TYPES (schedule.kind):\n- \"at\": One-shot at absolute time\n { \"kind\": \"at\", \"at\": \"\" }\n- \"every\": Recurring interval\n { \"kind\": \"every\", \"everyMs\": , \"anchorMs\": }\n- \"cron\": Cron expression evaluated in the supplied timezone, or the Gateway host local timezone when tz is omitted\n { \"kind\": \"cron\", \"expr\": \"\", \"tz\": \"\" }\n Write expr in the selected timezone's local wall-clock time; do not convert the requested local time to UTC first.\n If tz is omitted, do not assume UTC; the Gateway host local timezone is used.\n Example: \"Remind me every day at 6pm Shanghai time\" -> { \"kind\": \"cron\", \"expr\": \"0 18 * * *\", \"tz\": \"Asia/Shanghai\" }\n\nFor schedule.kind=\"at\", ISO timestamps without an explicit timezone are treated as UTC.\n\nPAYLOAD TYPES (payload.kind):\n- \"systemEvent\": Injects text as system event into session\n { \"kind\": \"systemEvent\", \"text\": \"\" }\n- \"agentTurn\": Runs agent with message (isolated sessions only)\n { \"kind\": \"agentTurn\", \"message\": \"\", \"model\": \"\", \"thinking\": \"\", \"timeoutSeconds\": }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"\", \"to\": \"\", \"threadId\": \"\", \"bestEffort\": }\n - Default for isolated agentTurn jobs (when delivery omitted): \"announce\"\n - announce: send to chat channel (optional channel/to target)\n - threadId: chat thread/topic id for channels that support threaded delivery\n - webhook: send finished-run event as HTTP POST to delivery.to (URL required)\n - If the task needs to send to a specific chat/recipient, set announce delivery.channel/to; do not call messaging tools inside the run.\n\nCRITICAL CONSTRAINTS:\n- sessionTarget=\"main\" REQUIRES payload.kind=\"systemEvent\"\n- sessionTarget=\"isolated\" | \"current\" | \"session:xxx\" REQUIRES payload.kind=\"agentTurn\"\n- For webhook callbacks, use delivery.mode=\"webhook\" with delivery.to set to a URL.\nDefault: prefer isolated agentTurn jobs unless the user explicitly wants current-session binding.\n\nWAKE MODES (for wake action):\n- \"next-heartbeat\" (default): Wake on next heartbeat\n- \"now\": Wake immediately\n\nUse jobId as the canonical identifier; id is accepted for compatibility. Use contextMessages (0-10) to add previous messages as context to the job text.", "inputSchema": { "additionalProperties": true, "properties": { @@ -208,6 +208,10 @@ "enum": ["status", "list", "add", "update", "remove", "run", "runs", "wake"], "type": "string" }, + "agentId": { + "description": "Filter by agent id (list action)", + "type": "string" + }, "contextMessages": { "maximum": 10, "minimum": 0, 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 c4bf288b971..69fd8daabed 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 @@ -214,8 +214,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 158 }, "dynamicToolsJson": { - "chars": 50457, - "roughTokens": 12615 + "chars": 50629, + "roughTokens": 12658 }, "openClawDeveloperInstructions": { "chars": 5870, @@ -226,8 +226,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 7256 }, "totalWithDynamicToolsJson": { - "chars": 79481, - "roughTokens": 19871 + "chars": 79653, + "roughTokens": 19914 }, "userInputText": { "chars": 870, 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 d9836c6598c..311de3d3562 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 @@ -214,8 +214,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 158 }, "dynamicToolsJson": { - "chars": 50148, - "roughTokens": 12537 + "chars": 50320, + "roughTokens": 12580 }, "openClawDeveloperInstructions": { "chars": 4999, @@ -226,8 +226,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 6913 }, "totalWithDynamicToolsJson": { - "chars": 77801, - "roughTokens": 19451 + "chars": 77973, + "roughTokens": 19494 }, "userInputText": { "chars": 370, 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 f9dd9a2e8a3..c1838414605 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 @@ -215,8 +215,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 158 }, "dynamicToolsJson": { - "chars": 51271, - "roughTokens": 12818 + "chars": 51443, + "roughTokens": 12861 }, "openClawDeveloperInstructions": { "chars": 4999, @@ -227,8 +227,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the "roughTokens": 7693 }, "totalWithDynamicToolsJson": { - "chars": 82042, - "roughTokens": 20511 + "chars": 82214, + "roughTokens": 20554 }, "userInputText": { "chars": 608,