Skip to content

Commit a543c39

Browse files
fix(cron): preserve model overrides for text payloads
1 parent 79159f1 commit a543c39

3 files changed

Lines changed: 92 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai
1414
### Fixes
1515

1616
- Security/audit: recognize dangerous node command IDs as valid `gateway.nodes.denyCommands` entries, so audit only warns on real typos or unsupported patterns. (#56923) Thanks @chziyue.
17+
- Cron: treat implicit text payloads with agent-turn overrides as agent turns, preserving model overrides for scheduled text prompts instead of pruning them as system events. Fixes #28905. (#64060) Thanks @liaoandi.
1718
- Telegram/exec approvals: stop treating general Telegram chat allowlists and `defaultTo` routes as native exec approvers; Telegram now uses explicit `execApprovals.approvers` or owner identity from `commands.ownerAllowFrom`, matching the first-pairing owner bootstrap path. Thanks @pashpashpash.
1819
- Chat commands: route sensitive group `/diagnostics` and `/export-trajectory` approvals and results to a private owner route, preferring same-surface DMs before falling back to the first configured owner route, so Discord group invocations can land in Telegram when that is the primary owner interface. Thanks @pashpashpash.
1920
- Plugin SDK/Discord: restore a deprecated `openclaw/plugin-sdk/discord` compatibility facade and the legacy compat group-policy warning export for the published `@openclaw/[email protected]` package, covering its config, account, directory, status, and thread-binding imports while keeping new plugins on generic SDK subpaths. Fixes #73685; supersedes #73703. Thanks @rderickson9 and @SymbolStar.

src/cron/normalize.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,51 @@ describe("normalizeCronJobCreate", () => {
458458
expect(validateCronAddParams(normalized)).toBe(true);
459459
});
460460

461+
it("promotes implicit text payloads with agentTurn hints for create jobs", () => {
462+
const normalized = normalizeCronJobCreate({
463+
name: "nested text model",
464+
schedule: { kind: "every", everyMs: 60_000 },
465+
payload: {
466+
text: " summarize issue status ",
467+
model: " anthropic/claude-sonnet-4-6 ",
468+
thinking: " high ",
469+
},
470+
}) as unknown as Record<string, unknown>;
471+
472+
const payload = normalized.payload as Record<string, unknown>;
473+
expect(payload).toEqual({
474+
kind: "agentTurn",
475+
message: "summarize issue status",
476+
model: "anthropic/claude-sonnet-4-6",
477+
thinking: "high",
478+
});
479+
expect(normalized.sessionTarget).toBe("isolated");
480+
expect(validateCronAddParams(normalized)).toBe(true);
481+
});
482+
483+
it("promotes legacy top-level text with agentTurn hints for create jobs", () => {
484+
const normalized = normalizeCronJobCreate({
485+
name: "legacy text model",
486+
schedule: { kind: "every", everyMs: 60_000 },
487+
text: " summarize issue status ",
488+
model: " openrouter/deepseek/deepseek-r1 ",
489+
fallbacks: [],
490+
toolsAllow: [" read "],
491+
}) as unknown as Record<string, unknown>;
492+
493+
const payload = normalized.payload as Record<string, unknown>;
494+
expect(payload).toEqual({
495+
kind: "agentTurn",
496+
message: "summarize issue status",
497+
model: "openrouter/deepseek/deepseek-r1",
498+
fallbacks: [],
499+
toolsAllow: ["read"],
500+
});
501+
expect(normalized.text).toBeUndefined();
502+
expect(normalized.model).toBeUndefined();
503+
expect(validateCronAddParams(normalized)).toBe(true);
504+
});
505+
461506
it("preserves timeoutSeconds=0 for no-timeout agentTurn payloads", () => {
462507
const normalized = normalizeCronJobCreate({
463508
name: "legacy no-timeout",
@@ -678,6 +723,40 @@ describe("normalizeCronJobPatch", () => {
678723
expect(payload.model).toBe("anthropic/claude-sonnet-4-6");
679724
});
680725

726+
it("promotes implicit text payloads with agentTurn hints for patches", () => {
727+
const normalized = normalizeCronJobPatch({
728+
payload: {
729+
text: " summarize issue status ",
730+
model: "anthropic/claude-sonnet-4-6",
731+
},
732+
}) as unknown as Record<string, unknown>;
733+
734+
const payload = normalized.payload as Record<string, unknown>;
735+
expect(payload).toEqual({
736+
kind: "agentTurn",
737+
message: "summarize issue status",
738+
model: "anthropic/claude-sonnet-4-6",
739+
});
740+
expect(validateCronUpdateParams({ id: "job-1", patch: normalized })).toBe(true);
741+
});
742+
743+
it("promotes legacy top-level text with agentTurn hints for patches", () => {
744+
const normalized = normalizeCronJobPatch({
745+
text: " summarize issue status ",
746+
model: "openrouter/deepseek/deepseek-r1",
747+
}) as unknown as Record<string, unknown>;
748+
749+
const payload = normalized.payload as Record<string, unknown>;
750+
expect(payload).toEqual({
751+
kind: "agentTurn",
752+
message: "summarize issue status",
753+
model: "openrouter/deepseek/deepseek-r1",
754+
});
755+
expect(normalized.text).toBeUndefined();
756+
expect(normalized.model).toBeUndefined();
757+
expect(validateCronUpdateParams({ id: "job-1", patch: normalized })).toBe(true);
758+
});
759+
681760
it("infers agentTurn kind for lightContext-only payload patches", () => {
682761
const normalized = normalizeCronJobPatch({
683762
payload: {

src/cron/normalize.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,13 +170,17 @@ function coercePayload(payload: UnknownRecord) {
170170
next.kind = kindRaw;
171171
}
172172
if (!next.kind) {
173-
const hasMessage = Boolean(normalizeOptionalString(next.message));
174-
const hasText = Boolean(normalizeOptionalString(next.text));
175-
if (hasMessage) {
173+
const message = normalizeOptionalString(next.message);
174+
const text = normalizeOptionalString(next.text);
175+
const hasAgentTurnHint = hasAgentTurnPayloadHint(next);
176+
if (message) {
176177
next.kind = "agentTurn";
177-
} else if (hasText) {
178+
} else if (text && hasAgentTurnHint) {
179+
next.kind = "agentTurn";
180+
next.message = text;
181+
} else if (text) {
178182
next.kind = "systemEvent";
179-
} else if (hasAgentTurnPayloadHint(next)) {
183+
} else if (hasAgentTurnHint) {
180184
// Accept partial agentTurn payload patches that only tweak agent-turn-only fields.
181185
next.kind = "agentTurn";
182186
}
@@ -311,6 +315,9 @@ function inferTopLevelPayload(next: UnknownRecord) {
311315

312316
const text = normalizeOptionalString(next.text) ?? "";
313317
if (text) {
318+
if (hasAgentTurnPayloadHint(next)) {
319+
return { kind: "agentTurn", message: text } satisfies UnknownRecord;
320+
}
314321
return { kind: "systemEvent", text } satisfies UnknownRecord;
315322
}
316323

0 commit comments

Comments
 (0)