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 4fbe5cc31a9..aab583a87ea 100644 --- a/src/cron/isolated-agent/run.message-tool-policy.test.ts +++ b/src/cron/isolated-agent/run.message-tool-policy.test.ts @@ -271,6 +271,28 @@ describe("runCronIsolatedAgentTurn message tool policy", () => { }); }); + it('skips implicit target resolution for bare delivery.mode "none"', async () => { + mockRunCronFallbackPassthrough(); + resolveCronDeliveryPlanMock.mockReturnValue({ + requested: false, + mode: "none", + }); + + await runCronIsolatedAgentTurn({ + ...makeParams(), + job: makeMessageToolPolicyJob({ mode: "none" }), + }); + + expect(resolveDeliveryTargetMock).not.toHaveBeenCalled(); + expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1); + expect(runEmbeddedPiAgentMock.mock.calls[0]?.[0]).toMatchObject({ + disableMessageTool: false, + forceMessageTool: true, + }); + expect(runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.messageChannel).toBeUndefined(); + expect(runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.messageTo).toBeUndefined(); + }); + it('suppresses automatic exec completion notifications when delivery.mode is "none"', async () => { mockRunCronFallbackPassthrough(); resolveCronDeliveryPlanMock.mockReturnValue({ @@ -353,7 +375,7 @@ describe("runCronIsolatedAgentTurn message tool policy", () => { }); }); - it("resolves implicit last-target context for bare delivery.mode none", async () => { + it('does not resolve implicit "last" context for bare delivery.mode none', async () => { mockRunCronFallbackPassthrough(); resolveCronDeliveryPlanMock.mockReturnValue({ requested: false, @@ -373,19 +395,14 @@ describe("runCronIsolatedAgentTurn message tool policy", () => { } as never, }); - expect(resolveDeliveryTargetMock).toHaveBeenCalledTimes(1); - expect(resolveDeliveryTargetMock.mock.calls[0]?.[2]).toMatchObject({ - channel: "last", - sessionKey: undefined, - }); + expect(resolveDeliveryTargetMock).not.toHaveBeenCalled(); expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1); expect(runEmbeddedPiAgentMock.mock.calls[0]?.[0]).toMatchObject({ disableMessageTool: false, forceMessageTool: true, - messageChannel: "messagechat", - messageTo: "123", - currentChannelId: "123", }); + expect(runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.messageChannel).toBeUndefined(); + expect(runEmbeddedPiAgentMock.mock.calls[0]?.[0]?.messageTo).toBeUndefined(); }); it("resolves implicit last-target context for delivery.mode none with only accountId", async () => { @@ -690,7 +707,7 @@ describe("runCronIsolatedAgentTurn message tool policy", () => { ); }); - it("marks no-deliver runs delivered when the message tool sends to the current target", async () => { + it("does not mark bare no-deliver runs delivered when the current target is unresolved", async () => { mockRunCronFallbackPassthrough(); resolveCronDeliveryPlanMock.mockReturnValue({ requested: false, @@ -707,11 +724,12 @@ describe("runCronIsolatedAgentTurn message tool policy", () => { expect(dispatchCronDeliveryMock.mock.calls[0]?.[0]).toEqual( expect.objectContaining({ deliveryRequested: false, - skipMessagingToolDelivery: true, + skipMessagingToolDelivery: false, + unverifiedMessagingToolDelivery: true, }), ); - expect(result.delivered).toBe(true); - expect(result.deliveryAttempted).toBe(true); + expect(result.delivered).toBe(false); + expect(result.deliveryAttempted).toBe(false); }); }); diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index 9b6bdce576b..f1168a700e6 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -301,6 +301,12 @@ function canPromptForMessageTool(params: { return !params.toolsAllow?.length || params.toolsAllow.includes("message"); } +function hasExplicitCronDeliveryTarget(plan: CronDeliveryPlan): boolean { + return Boolean( + (plan.channel && plan.channel !== "last") || plan.to || plan.threadId || plan.accountId, + ); +} + async function resolveCronDeliveryContext(params: { cfg: OpenClawConfig; job: CronJob; @@ -326,6 +332,24 @@ async function resolveCronDeliveryContext(params: { }), }; } + if (deliveryPlan.mode === "none" && !hasExplicitCronDeliveryTarget(deliveryPlan)) { + return { + deliveryPlan, + deliveryRequested: false, + resolvedDelivery: { + ok: false as const, + channel: undefined, + to: undefined, + accountId: undefined, + threadId: undefined, + mode: "implicit" as const, + error: new Error("delivery is disabled"), + }, + toolPolicy: resolveCronToolPolicy({ + deliveryMode: deliveryPlan.mode, + }), + }; + } const { resolveDeliveryTarget } = await loadCronDeliveryRuntime(); const resolvedDelivery = await resolveDeliveryTarget(params.cfg, params.agentId, { channel: deliveryPlan.channel ?? "last",