From 4f0a978fc2675460c6ef978df1b5873228b6cb5d Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Tue, 21 Apr 2026 11:05:41 +0530 Subject: [PATCH] fix(cron): track implicit message sends --- .../run.message-tool-policy.test.ts | 47 +++++++++++++++++++ src/cron/isolated-agent/run.ts | 31 +++++++++++- 2 files changed, 77 insertions(+), 1 deletion(-) 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 e4487991e00..ec87cc131f9 100644 --- a/src/cron/isolated-agent/run.message-tool-policy.test.ts +++ b/src/cron/isolated-agent/run.message-tool-policy.test.ts @@ -405,6 +405,53 @@ describe("runCronIsolatedAgentTurn message tool policy", () => { ); }); + it("skips cron fallback delivery when the message tool sends to the bound target", async () => { + mockRunCronFallbackPassthrough(); + const params = makeParams(); + const job = { + id: "message-tool-bound-target", + name: "Message Tool Bound Target", + schedule: { kind: "every", everyMs: 60_000 }, + sessionTarget: "isolated", + payload: { kind: "agentTurn", message: "send a message" }, + delivery: { mode: "announce", channel: "telegram", to: "123" }, + } as const; + resolveCronDeliveryPlanMock.mockReturnValue({ + requested: true, + mode: "announce", + channel: "telegram", + to: "123", + }); + runEmbeddedPiAgentMock.mockResolvedValue({ + payloads: [{ text: "sent" }], + didSendViaMessagingTool: true, + messagingToolSentTargets: [], + meta: { agentMeta: { usage: { input: 10, output: 20 } } }, + }); + + const result = await runCronIsolatedAgentTurn({ + ...params, + job: job as never, + }); + + expect(dispatchCronDeliveryMock).toHaveBeenCalledTimes(1); + expect(dispatchCronDeliveryMock.mock.calls[0]?.[0]).toEqual( + expect.objectContaining({ + deliveryRequested: true, + skipMessagingToolDelivery: true, + }), + ); + expect(result.delivery).toEqual( + expect.objectContaining({ + intended: { channel: "telegram", to: "123", source: "explicit" }, + resolved: { ok: true, channel: "telegram", to: "123", source: "explicit" }, + messageToolSentTo: [{ channel: "telegram", to: "123" }], + fallbackUsed: false, + delivered: true, + }), + ); + }); + it("does not mark message tool delivery as matched when cron target resolution failed", async () => { mockRunCronFallbackPassthrough(); resolveCronDeliveryPlanMock.mockReturnValue({ diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index c8b8014451e..9a6e27b199d 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -206,6 +206,32 @@ function buildCronDeliveryTrace(params: { }; } +function resolveMessagingToolSentTargets(params: { + resolvedDelivery: ResolvedCronDeliveryTarget; + runResult: CronExecutionResult["runResult"]; +}): MessagingToolSend[] { + const explicitTargets = params.runResult.messagingToolSentTargets ?? []; + if (explicitTargets.length > 0 || params.runResult.didSendViaMessagingTool !== true) { + return explicitTargets; + } + if (!params.resolvedDelivery.ok) { + return []; + } + return [ + { + tool: "message", + provider: params.resolvedDelivery.channel, + ...(params.resolvedDelivery.accountId + ? { accountId: params.resolvedDelivery.accountId } + : {}), + ...(params.resolvedDelivery.to ? { to: params.resolvedDelivery.to } : {}), + ...(params.resolvedDelivery.threadId + ? { threadId: String(params.resolvedDelivery.threadId) } + : {}), + }, + ]; +} + function resolveCronToolPolicy(params: { deliveryMode: "announce" | "webhook" | "none" }) { const enableMessageTool = params.deliveryMode !== "webhook"; return { @@ -756,7 +782,10 @@ async function finalizeCronRun(params: { matchesMessagingToolDeliveryTarget, resolveCronDeliveryBestEffort, } = await loadCronDeliveryRuntime(); - const messagingToolSentTargets = finalRunResult.messagingToolSentTargets ?? []; + const messagingToolSentTargets = resolveMessagingToolSentTargets({ + resolvedDelivery: prepared.resolvedDelivery, + runResult: finalRunResult, + }); const didSendViaMessagingTool = finalRunResult.didSendViaMessagingTool === true && messagingToolSentTargets.length > 0; const skipMessagingToolDelivery =