mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-08 06:54:24 +00:00
fix(cron): recover flat patch params for update action and fix schema (openclaw#23221) thanks @charojo
Verified: - pnpm build - pnpm check - pnpm test:macmini Co-authored-by: charojo <4084797+charojo@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -151,6 +151,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Cron/Failure alerts: add configurable repeated-failure alerting with per-job overrides and Web UI cron editor support (`inherit|disabled|custom` with threshold/cooldown/channel/target fields). (#24789) Thanks @0xbrak.
|
||||
- Cron/Isolated model defaults: resolve isolated cron `subagents.model` (including object-form `primary`) through allowlist-aware model selection so isolated cron runs honor subagent model defaults unless explicitly overridden by job payload model. (#11474) Thanks @AnonO6.
|
||||
- Cron/Isolated sessions list: persist the intended pre-run model/provider on isolated cron session entries so `sessions_list` reflects payload/session model overrides even when runs fail before post-run telemetry persistence. (#21279) Thanks @altaywtf.
|
||||
- Cron tool/update flat params: recover top-level update patch fields when models omit the `patch` wrapper, and allow flattened update keys through tool input schema validation so `cron.update` no longer fails with `patch required` for valid flat payloads. (#23221)
|
||||
- Agents/Message tool scoping: include other configured channels in scoped `message` tool action enum + description so isolated/cron runs can discover and invoke cross-channel actions without schema validation failures. Landed from contributor PR #20840 by @altaywtf. Thanks @altaywtf.
|
||||
- Web UI/Chat sessions: add a cron-session visibility toggle in the session selector, fix cron-key detection across `cron:*` and `agent:*:cron:*` formats, and localize the new control labels/tooltips. (#26976) Thanks @ianderrington.
|
||||
- Web UI/Cron jobs: add schedule-kind and last-run-status filters to the Jobs list, with reset control and client-side filtering over loaded results. (#9510) Thanks @guxu11.
|
||||
|
||||
@@ -512,4 +512,50 @@ describe("cron tool", () => {
|
||||
).rejects.toThrow('delivery.mode="webhook" requires delivery.to to be a valid http(s) URL');
|
||||
expect(callGatewayMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("recovers flat patch params for update action", async () => {
|
||||
callGatewayMock.mockResolvedValueOnce({ ok: true });
|
||||
|
||||
const tool = createCronTool();
|
||||
await tool.execute("call-update-flat", {
|
||||
action: "update",
|
||||
jobId: "job-1",
|
||||
name: "new-name",
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
expect(callGatewayMock).toHaveBeenCalledTimes(1);
|
||||
const call = callGatewayMock.mock.calls[0]?.[0] as {
|
||||
method?: string;
|
||||
params?: { id?: string; patch?: { name?: string; enabled?: boolean } };
|
||||
};
|
||||
expect(call.method).toBe("cron.update");
|
||||
expect(call.params?.id).toBe("job-1");
|
||||
expect(call.params?.patch?.name).toBe("new-name");
|
||||
expect(call.params?.patch?.enabled).toBe(false);
|
||||
});
|
||||
|
||||
it("recovers additional flat patch params for update action", async () => {
|
||||
callGatewayMock.mockResolvedValueOnce({ ok: true });
|
||||
|
||||
const tool = createCronTool();
|
||||
await tool.execute("call-update-flat-extra", {
|
||||
action: "update",
|
||||
id: "job-2",
|
||||
sessionTarget: "main",
|
||||
failureAlert: { after: 3, cooldownMs: 60_000 },
|
||||
});
|
||||
|
||||
const call = callGatewayMock.mock.calls[0]?.[0] as {
|
||||
method?: string;
|
||||
params?: {
|
||||
id?: string;
|
||||
patch?: { sessionTarget?: string; failureAlert?: { after?: number; cooldownMs?: number } };
|
||||
};
|
||||
};
|
||||
expect(call.method).toBe("cron.update");
|
||||
expect(call.params?.id).toBe("job-2");
|
||||
expect(call.params?.patch?.sessionTarget).toBe("main");
|
||||
expect(call.params?.patch?.failureAlert).toEqual({ after: 3, cooldownMs: 60_000 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,23 +28,26 @@ const REMINDER_CONTEXT_TOTAL_MAX = 700;
|
||||
const REMINDER_CONTEXT_MARKER = "\n\nRecent context:\n";
|
||||
|
||||
// Flattened schema: runtime validates per-action requirements.
|
||||
const CronToolSchema = Type.Object({
|
||||
action: stringEnum(CRON_ACTIONS),
|
||||
gatewayUrl: Type.Optional(Type.String()),
|
||||
gatewayToken: Type.Optional(Type.String()),
|
||||
timeoutMs: Type.Optional(Type.Number()),
|
||||
includeDisabled: Type.Optional(Type.Boolean()),
|
||||
job: Type.Optional(Type.Object({}, { additionalProperties: true })),
|
||||
jobId: Type.Optional(Type.String()),
|
||||
id: Type.Optional(Type.String()),
|
||||
patch: Type.Optional(Type.Object({}, { additionalProperties: true })),
|
||||
text: Type.Optional(Type.String()),
|
||||
mode: optionalStringEnum(CRON_WAKE_MODES),
|
||||
runMode: optionalStringEnum(CRON_RUN_MODES),
|
||||
contextMessages: Type.Optional(
|
||||
Type.Number({ minimum: 0, maximum: REMINDER_CONTEXT_MESSAGES_MAX }),
|
||||
),
|
||||
});
|
||||
const CronToolSchema = Type.Object(
|
||||
{
|
||||
action: stringEnum(CRON_ACTIONS),
|
||||
gatewayUrl: Type.Optional(Type.String()),
|
||||
gatewayToken: Type.Optional(Type.String()),
|
||||
timeoutMs: Type.Optional(Type.Number()),
|
||||
includeDisabled: Type.Optional(Type.Boolean()),
|
||||
job: Type.Optional(Type.Object({}, { additionalProperties: true })),
|
||||
jobId: Type.Optional(Type.String()),
|
||||
id: Type.Optional(Type.String()),
|
||||
patch: Type.Optional(Type.Object({}, { additionalProperties: true })),
|
||||
text: Type.Optional(Type.String()),
|
||||
mode: optionalStringEnum(CRON_WAKE_MODES),
|
||||
runMode: optionalStringEnum(CRON_RUN_MODES),
|
||||
contextMessages: Type.Optional(
|
||||
Type.Number({ minimum: 0, maximum: REMINDER_CONTEXT_MESSAGES_MAX }),
|
||||
),
|
||||
},
|
||||
{ additionalProperties: true },
|
||||
);
|
||||
|
||||
type CronToolOptions = {
|
||||
agentSessionKey?: string;
|
||||
@@ -435,6 +438,42 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con
|
||||
if (!id) {
|
||||
throw new Error("jobId required (id accepted for backward compatibility)");
|
||||
}
|
||||
|
||||
// Flat-params recovery for patch
|
||||
if (
|
||||
!params.patch ||
|
||||
(typeof params.patch === "object" &&
|
||||
params.patch !== null &&
|
||||
Object.keys(params.patch as Record<string, unknown>).length === 0)
|
||||
) {
|
||||
const PATCH_KEYS: ReadonlySet<string> = new Set([
|
||||
"name",
|
||||
"schedule",
|
||||
"payload",
|
||||
"delivery",
|
||||
"enabled",
|
||||
"description",
|
||||
"deleteAfterRun",
|
||||
"agentId",
|
||||
"sessionKey",
|
||||
"sessionTarget",
|
||||
"wakeMode",
|
||||
"failureAlert",
|
||||
"allowUnsafeExternalContent",
|
||||
]);
|
||||
const synthetic: Record<string, unknown> = {};
|
||||
let found = false;
|
||||
for (const key of Object.keys(params)) {
|
||||
if (PATCH_KEYS.has(key) && params[key] !== undefined) {
|
||||
synthetic[key] = params[key];
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
params.patch = synthetic;
|
||||
}
|
||||
}
|
||||
|
||||
if (!params.patch || typeof params.patch !== "object") {
|
||||
throw new Error("patch required");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user