From 2330c71b633fd6103826f6dfc53fcbb78555e3bc Mon Sep 17 00:00:00 2001 From: Adhish Date: Tue, 3 Mar 2026 01:41:18 +0530 Subject: [PATCH] fix(cron): suppress delivery when multi-payload response contains HEARTBEAT_OK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a cron agent emits multiple text payloads (narration + tool summaries) followed by a final HEARTBEAT_OK, the delivery suppression check `isHeartbeatOnlyResponse` fails because it uses `.every()` — requiring ALL payloads to be heartbeat tokens. In practice, agents narrate their work before signaling nothing needs attention. Fix: check if ANY payload contains HEARTBEAT_OK (`.some()`) while preserving the media delivery exception (if any payload has media, always deliver). This matches the semantic intent: HEARTBEAT_OK is the agent's explicit signal that nothing needs user attention. Real-world example: heartbeat agent returns 3 payloads: 1. "It's 12:49 AM — quiet hours. Let me run the checks quickly." 2. "Emails: Just 2 calendar invites. Not urgent." 3. "HEARTBEAT_OK" Previously: all 3 delivered to Telegram. Now: correctly suppressed. Related: #32013 (fixed a different HEARTBEAT_OK leak path via system events in timer.ts) --- src/cron/isolated-agent/helpers.test.ts | 63 +++++++++++++++++++++++++ src/cron/isolated-agent/helpers.ts | 19 +++++--- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/cron/isolated-agent/helpers.test.ts b/src/cron/isolated-agent/helpers.test.ts index 31e533170..365125764 100644 --- a/src/cron/isolated-agent/helpers.test.ts +++ b/src/cron/isolated-agent/helpers.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { + isHeartbeatOnlyResponse, pickLastDeliverablePayload, pickLastNonEmptyTextFromPayloads, pickSummaryFromPayloads, @@ -84,3 +85,65 @@ describe("pickLastDeliverablePayload", () => { expect(pickLastDeliverablePayload([normal, error])).toBe(normal); }); }); + +describe("isHeartbeatOnlyResponse", () => { + const ACK_MAX = 300; + + it("returns true for empty payloads", () => { + expect(isHeartbeatOnlyResponse([], ACK_MAX)).toBe(true); + }); + + it("returns true for a single HEARTBEAT_OK payload", () => { + expect(isHeartbeatOnlyResponse([{ text: "HEARTBEAT_OK" }], ACK_MAX)).toBe(true); + }); + + it("returns false for a single non-heartbeat payload", () => { + expect(isHeartbeatOnlyResponse([{ text: "Something important happened" }], ACK_MAX)).toBe( + false, + ); + }); + + it("returns true when multiple payloads include narration followed by HEARTBEAT_OK", () => { + // Agent narrates its work then signals nothing needs attention. + expect( + isHeartbeatOnlyResponse( + [ + { text: "It's 12:49 AM — quiet hours. Let me run the checks quickly." }, + { text: "Emails: Just 2 calendar invites. Not urgent." }, + { text: "HEARTBEAT_OK" }, + ], + ACK_MAX, + ), + ).toBe(true); + }); + + it("returns false when media is present even with HEARTBEAT_OK text", () => { + expect( + isHeartbeatOnlyResponse( + [{ text: "HEARTBEAT_OK", mediaUrl: "https://example.com/img.png" }], + ACK_MAX, + ), + ).toBe(false); + }); + + it("returns false when media is in a different payload than HEARTBEAT_OK", () => { + expect( + isHeartbeatOnlyResponse( + [ + { text: "HEARTBEAT_OK" }, + { text: "Here's an image", mediaUrl: "https://example.com/img.png" }, + ], + ACK_MAX, + ), + ).toBe(false); + }); + + it("returns false when no payload contains HEARTBEAT_OK", () => { + expect( + isHeartbeatOnlyResponse( + [{ text: "Checked emails — found 3 urgent messages from your manager." }], + ACK_MAX, + ), + ).toBe(false); + }); +}); diff --git a/src/cron/isolated-agent/helpers.ts b/src/cron/isolated-agent/helpers.ts index c74b65d1b..5e7f92990 100644 --- a/src/cron/isolated-agent/helpers.ts +++ b/src/cron/isolated-agent/helpers.ts @@ -94,13 +94,18 @@ export function isHeartbeatOnlyResponse(payloads: DeliveryPayload[], ackMaxChars if (payloads.length === 0) { return true; } - return payloads.every((payload) => { - // If there's media, we should deliver regardless of text content. - const hasMedia = (payload.mediaUrls?.length ?? 0) > 0 || Boolean(payload.mediaUrl); - if (hasMedia) { - return false; - } - // Use heartbeat mode to check if text is just HEARTBEAT_OK or short ack. + // If any payload has media, deliver regardless — there's real content. + const hasAnyMedia = payloads.some( + (payload) => (payload.mediaUrls?.length ?? 0) > 0 || Boolean(payload.mediaUrl), + ); + if (hasAnyMedia) { + return false; + } + // An agent may emit multiple text payloads (narration, tool summaries) + // before a final HEARTBEAT_OK. If *any* payload is a heartbeat ack token, + // the agent is signaling "nothing needs attention" — the preceding text + // payloads are just internal narration and should not be delivered. + return payloads.some((payload) => { const result = stripHeartbeatToken(payload.text, { mode: "heartbeat", maxAckChars: ackMaxChars,