From 56245d5646e27233ac7dca41660cb78fcb180ce2 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 23 Dec 2025 03:12:24 +0100 Subject: [PATCH] fix: strip repeated heartbeat ok tails --- CHANGELOG.md | 1 + src/web/auto-reply.test.ts | 23 +++++++++++++++++++++++ src/web/auto-reply.ts | 12 +++++++++--- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fceb6f4ed..3a5785818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Group chat activation modes: per-group `/activation mention|always` command with status visibility. ### Fixes +- Heartbeat replies now strip repeated `HEARTBEAT_OK` tails to avoid accidental “OK OK” spam. - WhatsApp send now preserves existing JIDs (including group `@g.us`) instead of coercing to `@s.whatsapp.net`. (Thanks @arun-8687.) - Telegram/WhatsApp: native replies now target the original inbound message; reply context is appended to `Body` and captured in `ReplyTo*` fields. (Thanks @joshp123 for the PR and follow-up question.) - WhatsApp web creds persistence hardened; credentials are restored before auth checks and QR login auto-restarts if it stalls. diff --git a/src/web/auto-reply.test.ts b/src/web/auto-reply.test.ts index 640c3fb59..a14feed21 100644 --- a/src/web/auto-reply.test.ts +++ b/src/web/auto-reply.test.ts @@ -134,6 +134,29 @@ describe("heartbeat helpers", () => { }); }); + it("strips repeated OK tails after heartbeat token", () => { + expect(stripHeartbeatToken("HEARTBEAT_OK_OK_OK")).toEqual({ + shouldSkip: true, + text: "", + }); + expect(stripHeartbeatToken("HEARTBEAT_OK_OK")).toEqual({ + shouldSkip: true, + text: "", + }); + expect(stripHeartbeatToken("HEARTBEAT_OK _OK")).toEqual({ + shouldSkip: true, + text: "", + }); + expect(stripHeartbeatToken("HEARTBEAT_OK OK")).toEqual({ + shouldSkip: true, + text: "", + }); + expect(stripHeartbeatToken("ALERT HEARTBEAT_OK_OK")).toEqual({ + shouldSkip: false, + text: "ALERT", + }); + }); + it("resolves heartbeat minutes with default and overrides", () => { const cfgBase: ClawdisConfig = { inbound: {}, diff --git a/src/web/auto-reply.ts b/src/web/auto-reply.ts index ea17916e8..2d67c854c 100644 --- a/src/web/auto-reply.ts +++ b/src/web/auto-reply.ts @@ -203,10 +203,16 @@ export function stripHeartbeatToken(raw?: string) { const trimmed = raw.trim(); if (!trimmed) return { shouldSkip: true, text: "" }; if (trimmed === HEARTBEAT_TOKEN) return { shouldSkip: true, text: "" }; - const withoutToken = trimmed.replaceAll(HEARTBEAT_TOKEN, "").trim(); + const hadToken = trimmed.includes(HEARTBEAT_TOKEN); + let withoutToken = trimmed.replaceAll(HEARTBEAT_TOKEN, "").trim(); + if (hadToken && withoutToken) { + // LLMs sometimes echo malformed HEARTBEAT_OK_OK... tails; strip trailing OK runs to avoid spam. + withoutToken = withoutToken.replace(/[\s_]*OK(?:[\s_]*OK)*$/gi, "").trim(); + } + const shouldSkip = withoutToken.length === 0; return { - shouldSkip: withoutToken.length === 0, - text: withoutToken || trimmed, + shouldSkip, + text: shouldSkip ? "" : withoutToken || trimmed, }; }