fix(cron): suppress delivery when multi-payload response contains HEARTBEAT_OK

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)
This commit is contained in:
Adhish
2026-03-03 01:41:18 +05:30
committed by Peter Steinberger
parent 477de545f9
commit 2330c71b63
2 changed files with 75 additions and 7 deletions

View File

@@ -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);
});
});

View File

@@ -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,