From b9d2e0f86d0ca4f3097c4f1f253164a902b779fe Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Tue, 21 Apr 2026 11:52:02 +0530 Subject: [PATCH] fix(cron): gate delivery prompt on message tool availability --- .../run.message-tool-policy.test.ts | 30 +++++++++++++++++-- src/cron/isolated-agent/run.ts | 15 +++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) 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 ec87cc131f9..b482e264f1d 100644 --- a/src/cron/isolated-agent/run.message-tool-policy.test.ts +++ b/src/cron/isolated-agent/run.message-tool-policy.test.ts @@ -19,13 +19,16 @@ import { const runCronIsolatedAgentTurn = await loadRunCronIsolatedAgentTurn(); const { createCronPromptExecutor } = await import("./run-executor.js"); -function makeMessageToolPolicyJob(delivery: Record = { mode: "none" }) { +function makeMessageToolPolicyJob( + delivery: Record = { mode: "none" }, + payload: Record = { kind: "agentTurn", message: "send a message" }, +) { return { id: "message-tool-policy", name: "Message Tool Policy", schedule: { kind: "every", everyMs: 60_000 }, sessionTarget: "isolated", - payload: { kind: "agentTurn", message: "send a message" }, + payload, delivery, } as never; } @@ -565,6 +568,29 @@ describe("runCronIsolatedAgentTurn delivery instruction", () => { expect(prompt).not.toContain("note who/where"); }); + it("does not prompt for the message tool when toolsAllow excludes it", async () => { + mockRunCronFallbackPassthrough(); + resolveCronDeliveryPlanMock.mockReturnValue({ + requested: true, + mode: "announce", + channel: "telegram", + to: "123", + }); + + await runCronIsolatedAgentTurn({ + ...makeParams(), + job: makeMessageToolPolicyJob( + { mode: "announce", channel: "telegram", to: "123" }, + { kind: "agentTurn", message: "send a message", toolsAllow: ["read"] }, + ), + }); + + expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1); + const prompt: string = runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.prompt ?? ""; + expect(prompt).not.toContain("Use the message tool"); + expect(prompt).toContain("Return your response as plain text"); + }); + it("does not append a delivery instruction when delivery is not requested", async () => { mockRunCronFallbackPassthrough(); resolveCronDeliveryPlanMock.mockReturnValue({ requested: false, mode: "none" }); diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index 9a6e27b199d..07d5c24271a 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -241,6 +241,16 @@ function resolveCronToolPolicy(params: { deliveryMode: "announce" | "webhook" | }; } +function canPromptForMessageTool(params: { + disableMessageTool: boolean; + toolsAllow?: string[]; +}): boolean { + if (params.disableMessageTool) { + return false; + } + return !params.toolsAllow?.length || params.toolsAllow.includes("message"); +} + async function resolveCronDeliveryContext(params: { cfg: OpenClawConfig; job: CronJob; @@ -561,7 +571,10 @@ async function prepareCronRunContext(params: { commandBody = appendCronDeliveryInstruction({ commandBody, deliveryRequested, - messageToolEnabled: !toolPolicy.disableMessageTool, + messageToolEnabled: canPromptForMessageTool({ + disableMessageTool: toolPolicy.disableMessageTool, + toolsAllow: agentPayload?.toolsAllow, + }), resolvedDeliveryOk: resolvedDelivery.ok, });