diff --git a/CHANGELOG.md b/CHANGELOG.md index d4110aa124c..ead00c6eaeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -244,6 +244,7 @@ Docs: https://docs.openclaw.ai - Agents/failover: scope assistant-side fallback classification and surfaced provider errors to the current attempt instead of stale session history, so cross-provider fallback runs stop inheriting the previous provider's failure. (#62907) Thanks @stainlu. - MiniMax/OAuth: write `api: "anthropic-messages"` and `authHeader: true` into the `minimax-portal` config patch during `openclaw configure`, so re-authenticated portal setups keep Bearer auth routing working. (#64964) Thanks @ryanlee666. - Agents/tools: stop repeated unavailable-tool retries from escaping loop detection when the model changes arguments, and rewrite over-threshold unknown tool calls into plain assistant text before dispatch. (#65922) Thanks @dutifulbob. +- Cron/announce delivery: tell isolated cron jobs to return the full response exactly instead of a summary, so structured `--announce` deliveries stop dropping fields nondeterministically. (#65638) Thanks @srinivaspavan9 and @vincentkoc. ## 2026.4.10 diff --git a/src/cron/isolated-agent/run.message-tool-policy.test.ts b/src/cron/isolated-agent/run.message-tool-policy.test.ts index 73cc2dc4db8..3bff3561411 100644 --- a/src/cron/isolated-agent/run.message-tool-policy.test.ts +++ b/src/cron/isolated-agent/run.message-tool-policy.test.ts @@ -165,3 +165,71 @@ describe("runCronIsolatedAgentTurn message tool policy", () => { ); }); }); + +describe("runCronIsolatedAgentTurn delivery instruction", () => { + let previousFastTestEnv: string | undefined; + + beforeEach(() => { + previousFastTestEnv = clearFastTestEnv(); + resetRunCronIsolatedAgentTurnHarness(); + resolveDeliveryTargetMock.mockResolvedValue({ + ok: true, + channel: "telegram", + to: "123", + accountId: undefined, + error: undefined, + }); + }); + + afterEach(() => { + restoreFastTestEnv(previousFastTestEnv); + }); + + it("appends a plain-text delivery instruction to the prompt when delivery is requested", async () => { + mockRunCronFallbackPassthrough(); + resolveCronDeliveryPlanMock.mockReturnValue({ + requested: true, + mode: "announce", + channel: "telegram", + to: "123", + }); + + await runCronIsolatedAgentTurn(makeParams()); + + expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1); + const prompt: string = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.prompt ?? ""; + expect(prompt).toContain("Return your response as plain text"); + expect(prompt).toContain("it will be delivered automatically"); + }); + + it("does not append a delivery instruction when delivery is not requested", async () => { + mockRunCronFallbackPassthrough(); + resolveCronDeliveryPlanMock.mockReturnValue({ requested: false, mode: "none" }); + + await runCronIsolatedAgentTurn(makeParams()); + + expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1); + const prompt: string = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.prompt ?? ""; + expect(prompt).not.toContain("Return your response as plain text"); + expect(prompt).not.toContain("it will be delivered automatically"); + }); + + it("does not instruct the agent to summarize when delivery is requested", async () => { + // Regression for https://github.com/openclaw/openclaw/issues/58535: + // "summary" caused LLMs to condense structured output and drop fields + // non-deterministically on every run. + mockRunCronFallbackPassthrough(); + resolveCronDeliveryPlanMock.mockReturnValue({ + requested: true, + mode: "announce", + channel: "telegram", + to: "123", + }); + + await runCronIsolatedAgentTurn(makeParams()); + + expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1); + const prompt: string = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.prompt ?? ""; + expect(prompt).not.toMatch(/\bsummary\b/i); + }); +}); diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index 4e7af581dde..6b25464c3c8 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -193,7 +193,7 @@ function appendCronDeliveryInstruction(params: { if (!params.deliveryRequested) { return params.commandBody; } - return `${params.commandBody}\n\nReturn your summary as plain text; it will be delivered automatically. If the task explicitly calls for messaging a specific external recipient, note who/where it should go instead of sending it yourself.`.trim(); + return `${params.commandBody}\n\nReturn your response as plain text; it will be delivered automatically. If the task explicitly calls for messaging a specific external recipient, note who/where it should go instead of sending it yourself.`.trim(); } function resolvePositiveContextTokens(value: unknown): number | undefined {