From 424b7fe4938a0446733f09f163818b94a81974b2 Mon Sep 17 00:00:00 2001 From: Jake Date: Mon, 5 Jan 2026 08:36:44 +1300 Subject: [PATCH 1/4] fix: prevent typing indicator during heartbeat background tasks --- src/auto-reply/reply.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auto-reply/reply.ts b/src/auto-reply/reply.ts index 366066e59..4fa756a26 100644 --- a/src/auto-reply/reply.ts +++ b/src/auto-reply/reply.ts @@ -467,7 +467,7 @@ export async function getReplyFromConfig( const isFirstTurnInSession = isNewSession || !systemSent; const isGroupChat = sessionCtx.ChatType === "group"; const wasMentioned = ctx.WasMentioned === true; - const shouldEagerType = !isGroupChat || wasMentioned; + const shouldEagerType = (!isGroupChat || wasMentioned) && !opts?.isHeartbeat; const shouldInjectGroupIntro = Boolean( isGroupChat && (isFirstTurnInSession || sessionEntry?.groupActivationNeedsSystemIntro), From 58dd2e951461a1578654f8dee3004a669f423ccc Mon Sep 17 00:00:00 2001 From: Jake Date: Mon, 5 Jan 2026 09:05:09 +1300 Subject: [PATCH 2/4] fix: also suppress typing indicators in agent-runner during heartbeats --- src/auto-reply/reply/agent-runner.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/auto-reply/reply/agent-runner.ts b/src/auto-reply/reply/agent-runner.ts index 5603428ad..d5c54e1c7 100644 --- a/src/auto-reply/reply/agent-runner.ts +++ b/src/auto-reply/reply/agent-runner.ts @@ -221,7 +221,9 @@ export async function runReplyAgent(params: { } text = stripped.text; } - await typing.startTypingOnText(text); + if (!opts?.isHeartbeat) { + await typing.startTypingOnText(text); + } await opts.onPartialReply?.({ text, mediaUrls: payload.mediaUrls, @@ -270,7 +272,9 @@ export async function runReplyAgent(params: { } pendingStreamedPayloadKeys.add(payloadKey); const task = (async () => { - await typing.startTypingOnText(cleaned); + if (!opts?.isHeartbeat) { + await typing.startTypingOnText(cleaned); + } await opts.onBlockReply?.(blockPayload); })() .then(() => { @@ -311,7 +315,9 @@ export async function runReplyAgent(params: { } text = stripped.text; } - await typing.startTypingOnText(text); + if (!opts?.isHeartbeat) { + await typing.startTypingOnText(text); + } await opts.onToolResult?.({ text, mediaUrls: payload.mediaUrls, @@ -410,7 +416,7 @@ export async function runReplyAgent(params: { if (payload.mediaUrls && payload.mediaUrls.length > 0) return true; return false; }); - if (shouldSignalTyping) { + if (shouldSignalTyping && !opts?.isHeartbeat) { await typing.startTypingLoop(); } From 3c7c819e9b2c07619230b26d453bb6a1aa209251 Mon Sep 17 00:00:00 2001 From: Jake Date: Mon, 5 Jan 2026 09:43:14 +1300 Subject: [PATCH 3/4] WhatsApp: mark offline/history messages as read --- src/web/inbound.ts | 6 +++++- src/web/monitor-inbox.test.ts | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/web/inbound.ts b/src/web/inbound.ts index c4e653c41..fe147e2fe 100644 --- a/src/web/inbound.ts +++ b/src/web/inbound.ts @@ -130,7 +130,7 @@ export async function monitorWebInbox(options: { type?: string; messages?: Array; }) => { - if (upsert.type !== "notify") return; + if (upsert.type !== "notify" && upsert.type !== "append") return; for (const msg of upsert.messages ?? []) { const id = msg.key?.id ?? undefined; // De-dupe on message id; Baileys can emit retries. @@ -205,6 +205,10 @@ export async function monitorWebInbox(options: { logVerbose(`Self-chat mode: skipping read receipt for ${id}`); } + // If this is history/offline catch-up, we marked it as read above, + // but we skip triggering the auto-reply logic to avoid spamming old context. + if (upsert.type === "append") continue; + let body = extractText(msg.message ?? undefined); if (!body) { body = extractMediaPlaceholder(msg.message ?? undefined); diff --git a/src/web/monitor-inbox.test.ts b/src/web/monitor-inbox.test.ts index 9220e5cfc..9a47fa78a 100644 --- a/src/web/monitor-inbox.test.ts +++ b/src/web/monitor-inbox.test.ts @@ -842,3 +842,39 @@ it("defaults to self-only when no config is present", async () => { await listener.close(); }); + +it("handles append messages by marking them read but skipping auto-reply", async () => { + const onMessage = vi.fn(); + const listener = await monitorWebInbox({ verbose: false, onMessage }); + const sock = await createWaSocket(); + + const upsert = { + type: "append", + messages: [ + { + key: { id: "history1", fromMe: false, remoteJid: "999@s.whatsapp.net" }, + message: { conversation: "old message" }, + messageTimestamp: 1_700_000_000, + pushName: "History Sender", + }, + ], + }; + + sock.ev.emit("messages.upsert", upsert); + await new Promise((resolve) => setImmediate(resolve)); + + // Verify it WAS marked as read + expect(sock.readMessages).toHaveBeenCalledWith([ + { + remoteJid: "999@s.whatsapp.net", + id: "history1", + participant: undefined, + fromMe: false, + }, + ]); + + // Verify it WAS NOT passed to onMessage + expect(onMessage).not.toHaveBeenCalled(); + + await listener.close(); +}); From a6f7ab499f75fb6e204ed0b465ecda964993972b Mon Sep 17 00:00:00 2001 From: Jake Date: Mon, 5 Jan 2026 10:18:40 +1300 Subject: [PATCH 4/4] style: fix lint issues --- src/agents/pi-tools.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/agents/pi-tools.ts b/src/agents/pi-tools.ts index e0db78b9f..1e727ed40 100644 --- a/src/agents/pi-tools.ts +++ b/src/agents/pi-tools.ts @@ -7,7 +7,6 @@ import { readTool, } from "@mariozechner/pi-coding-agent"; import { Type } from "@sinclair/typebox"; - import type { ClawdbotConfig } from "../config/config.js"; import { detectMime } from "../media/mime.js"; import { startWebLoginWithQr, waitForWebLogin } from "../web/login-qr.js";