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 35b692bc97.
- Required merge gates passed before the squash merge.

Prepared head SHA: 35b692bc97
Review: https://github.com/openclaw/openclaw/pull/77602#issuecomment-4375631700

Co-authored-by: zhanggttry <zhanggttry@163.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
This commit is contained in:
clawsweeper[bot]
2026-05-05 06:06:24 +00:00
committed by GitHub
parent b489a62a06
commit cd66854b66
20 changed files with 178 additions and 21 deletions

View File

@@ -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 `<rootDir>/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 <id>] [--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 <id>`, 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.

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -211,12 +211,15 @@ Manual run and inspection:
```bash
openclaw cron list
openclaw cron list --agent ops
openclaw cron show <job-id>
openclaw cron run <job-id>
openclaw cron run <job-id> --due
openclaw cron runs --id <job-id> --limit 50
```
`openclaw cron list` shows all matching jobs by default. Pass `--agent <id>` 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:

View File

@@ -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(

View File

@@ -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

View File

@@ -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(

View File

@@ -45,12 +45,18 @@ export function registerCronListCommand(cron: Command) {
.command("list")
.description("List cron jobs")
.option("--all", "Include disabled jobs", false)
.option("--agent <id>", "Filter by agent id")
.option("--json", "Output JSON", false)
.action(async (opts) => {
try {
const res = await callGatewayFromCli("cron.list", opts, {
const listParams: Record<string, unknown> = {
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;

View File

@@ -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"]);
});
});

View File

@@ -12,6 +12,7 @@ export type CronListPageOptions = {
enabled?: CronJobsEnabledFilter;
sortBy?: CronJobsSortBy;
sortDir?: CronSortDir;
agentId?: string;
};
export type CronListPageResult<TJobs extends readonly CronJob[] = CronJob[]> = {

View File

@@ -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;
}

View File

@@ -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", () => {

View File

@@ -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 },
);

View File

@@ -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(),

View File

@@ -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:<custom-id>\", // 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:<custom-id>\": 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\": \"<ISO-8601 timestamp>\" }\n- \"every\": Recurring interval\n { \"kind\": \"every\", \"everyMs\": <interval-ms>, \"anchorMs\": <optional-start-ms> }\n- \"cron\": Cron expression evaluated in the supplied timezone, or the Gateway host local timezone when tz is omitted\n { \"kind\": \"cron\", \"expr\": \"<cron-expression>\", \"tz\": \"<optional-IANA-timezone>\" }\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\": \"<message>\" }\n- \"agentTurn\": Runs agent with message (isolated sessions only)\n { \"kind\": \"agentTurn\", \"message\": \"<prompt>\", \"model\": \"<optional>\", \"thinking\": \"<optional>\", \"timeoutSeconds\": <optional, 0 means no timeout> }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"<optional>\", \"to\": \"<optional>\", \"threadId\": \"<optional>\", \"bestEffort\": <optional-bool> }\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:<custom-id>\", // 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:<custom-id>\": 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\": \"<ISO-8601 timestamp>\" }\n- \"every\": Recurring interval\n { \"kind\": \"every\", \"everyMs\": <interval-ms>, \"anchorMs\": <optional-start-ms> }\n- \"cron\": Cron expression evaluated in the supplied timezone, or the Gateway host local timezone when tz is omitted\n { \"kind\": \"cron\", \"expr\": \"<cron-expression>\", \"tz\": \"<optional-IANA-timezone>\" }\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\": \"<message>\" }\n- \"agentTurn\": Runs agent with message (isolated sessions only)\n { \"kind\": \"agentTurn\", \"message\": \"<prompt>\", \"model\": \"<optional>\", \"thinking\": \"<optional>\", \"timeoutSeconds\": <optional, 0 means no timeout> }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"<optional>\", \"to\": \"<optional>\", \"threadId\": \"<optional>\", \"bestEffort\": <optional-bool> }\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,

View File

@@ -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:<custom-id>\", // 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:<custom-id>\": 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\": \"<ISO-8601 timestamp>\" }\n- \"every\": Recurring interval\n { \"kind\": \"every\", \"everyMs\": <interval-ms>, \"anchorMs\": <optional-start-ms> }\n- \"cron\": Cron expression evaluated in the supplied timezone, or the Gateway host local timezone when tz is omitted\n { \"kind\": \"cron\", \"expr\": \"<cron-expression>\", \"tz\": \"<optional-IANA-timezone>\" }\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\": \"<message>\" }\n- \"agentTurn\": Runs agent with message (isolated sessions only)\n { \"kind\": \"agentTurn\", \"message\": \"<prompt>\", \"model\": \"<optional>\", \"thinking\": \"<optional>\", \"timeoutSeconds\": <optional, 0 means no timeout> }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"<optional>\", \"to\": \"<optional>\", \"threadId\": \"<optional>\", \"bestEffort\": <optional-bool> }\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:<custom-id>\", // 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:<custom-id>\": 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\": \"<ISO-8601 timestamp>\" }\n- \"every\": Recurring interval\n { \"kind\": \"every\", \"everyMs\": <interval-ms>, \"anchorMs\": <optional-start-ms> }\n- \"cron\": Cron expression evaluated in the supplied timezone, or the Gateway host local timezone when tz is omitted\n { \"kind\": \"cron\", \"expr\": \"<cron-expression>\", \"tz\": \"<optional-IANA-timezone>\" }\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\": \"<message>\" }\n- \"agentTurn\": Runs agent with message (isolated sessions only)\n { \"kind\": \"agentTurn\", \"message\": \"<prompt>\", \"model\": \"<optional>\", \"thinking\": \"<optional>\", \"timeoutSeconds\": <optional, 0 means no timeout> }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"<optional>\", \"to\": \"<optional>\", \"threadId\": \"<optional>\", \"bestEffort\": <optional-bool> }\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,

View File

@@ -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:<custom-id>\", // 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:<custom-id>\": 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\": \"<ISO-8601 timestamp>\" }\n- \"every\": Recurring interval\n { \"kind\": \"every\", \"everyMs\": <interval-ms>, \"anchorMs\": <optional-start-ms> }\n- \"cron\": Cron expression evaluated in the supplied timezone, or the Gateway host local timezone when tz is omitted\n { \"kind\": \"cron\", \"expr\": \"<cron-expression>\", \"tz\": \"<optional-IANA-timezone>\" }\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\": \"<message>\" }\n- \"agentTurn\": Runs agent with message (isolated sessions only)\n { \"kind\": \"agentTurn\", \"message\": \"<prompt>\", \"model\": \"<optional>\", \"thinking\": \"<optional>\", \"timeoutSeconds\": <optional, 0 means no timeout> }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"<optional>\", \"to\": \"<optional>\", \"threadId\": \"<optional>\", \"bestEffort\": <optional-bool> }\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:<custom-id>\", // 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:<custom-id>\": 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\": \"<ISO-8601 timestamp>\" }\n- \"every\": Recurring interval\n { \"kind\": \"every\", \"everyMs\": <interval-ms>, \"anchorMs\": <optional-start-ms> }\n- \"cron\": Cron expression evaluated in the supplied timezone, or the Gateway host local timezone when tz is omitted\n { \"kind\": \"cron\", \"expr\": \"<cron-expression>\", \"tz\": \"<optional-IANA-timezone>\" }\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\": \"<message>\" }\n- \"agentTurn\": Runs agent with message (isolated sessions only)\n { \"kind\": \"agentTurn\", \"message\": \"<prompt>\", \"model\": \"<optional>\", \"thinking\": \"<optional>\", \"timeoutSeconds\": <optional, 0 means no timeout> }\n\nDELIVERY (top-level):\n { \"mode\": \"none|announce|webhook\", \"channel\": \"<optional>\", \"to\": \"<optional>\", \"threadId\": \"<optional>\", \"bestEffort\": <optional-bool> }\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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,