From 4933113252531b82c679f3996945e166683f30d6 Mon Sep 17 00:00:00 2001 From: Jake Date: Sat, 10 Jan 2026 18:06:53 +1300 Subject: [PATCH 1/2] fix(whatsapp): preserve group message IDs and normalize reaction participants --- CHANGELOG.md | 2 +- src/web/auto-reply.ts | 33 ++++++++++++++++++++++++++------- src/web/inbound.ts | 2 +- src/web/monitor-inbox.test.ts | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efb246185..36cbf339e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Fixes - Auto-reply: prefer `RawBody` for command/directive parsing (WhatsApp + Discord) and prevent fallback runs from clobbering concurrent session updates. (#643) — thanks @mcinteerj. +- WhatsApp: fix group reactions by preserving message IDs and sender JIDs in history; normalize participant phone numbers to JIDs in outbound reactions. (#640) — thanks @mcinteerj. - Cron: `wakeMode: "now"` waits for heartbeat completion (and retries when the main lane is busy). (#666) — thanks @roshanasingh4. - Agents/OpenAI: fix Responses tool-only → follow-up turn handling (avoid standalone `reasoning` items that trigger 400 “required following item”). - Sandbox: add `clawdbot sandbox explain` (effective policy inspector + fix-it keys); improve “sandbox jail” tool-policy/elevated errors with actionable config key paths; link to docs. @@ -41,7 +42,6 @@ - Telegram: serialize media-group processing to avoid missed albums under load. - Signal: handle `dataMessage.reaction` events (signal-cli SSE) to avoid broken attachment errors. (#637) — thanks @neist. - Docs: showcase entries for ParentPay, R2 Upload, iOS TestFlight, and Oura Health. (#650) — thanks @henrino3. - ## 2026.1.9 ### Highlights diff --git a/src/web/auto-reply.ts b/src/web/auto-reply.ts index 902db7792..5da3376c1 100644 --- a/src/web/auto-reply.ts +++ b/src/web/auto-reply.ts @@ -826,7 +826,13 @@ export async function monitorWebProvider( DEFAULT_GROUP_HISTORY_LIMIT; const groupHistories = new Map< string, - Array<{ sender: string; body: string; timestamp?: number }> + Array<{ + sender: string; + body: string; + timestamp?: number; + id?: string; + senderJid?: string; + }> >(); const groupMemberNames = new Map>(); const sleep = @@ -1104,6 +1110,8 @@ export async function monitorWebProvider( sender: string; body: string; timestamp?: number; + id?: string; + senderJid?: string; }>; suppressGroupHistoryClear?: boolean; }, @@ -1123,14 +1131,17 @@ export async function monitorWebProvider( if (historyWithoutCurrent.length > 0) { const lineBreak = "\\n"; const historyText = historyWithoutCurrent - .map((m) => - formatAgentEnvelope({ + .map((m) => { + const bodyWithId = m.id + ? `${m.body}\n[message_id: ${m.id}]` + : m.body; + return formatAgentEnvelope({ provider: "WhatsApp", from: conversationId, timestamp: m.timestamp, - body: `${m.sender}: ${m.body}`, - }), - ) + body: `${m.sender}: ${bodyWithId}`, + }); + }) .join(lineBreak); combinedBody = buildHistoryContext({ historyText, @@ -1554,11 +1565,19 @@ export async function monitorWebProvider( sender: string; body: string; timestamp?: number; + id?: string; + senderJid?: string; }>); + const sender = + msg.senderName && msg.senderE164 + ? `${msg.senderName} (${msg.senderE164})` + : (msg.senderName ?? msg.senderE164 ?? "Unknown"); history.push({ - sender: msg.senderName ?? msg.senderE164 ?? "Unknown", + sender, body: msg.body, timestamp: msg.timestamp, + id: msg.id, + senderJid: msg.senderJid, }); while (history.length > groupHistoryLimit) history.shift(); groupHistories.set(groupHistoryKey, history); diff --git a/src/web/inbound.ts b/src/web/inbound.ts index aeae8f919..87ea51d88 100644 --- a/src/web/inbound.ts +++ b/src/web/inbound.ts @@ -620,7 +620,7 @@ export async function monitorWebInbox(options: { remoteJid: jid, id: messageId, fromMe, - participant, + participant: participant ? toWhatsappJid(participant) : undefined, }, }, }); diff --git a/src/web/monitor-inbox.test.ts b/src/web/monitor-inbox.test.ts index 285de40ab..6463d5797 100644 --- a/src/web/monitor-inbox.test.ts +++ b/src/web/monitor-inbox.test.ts @@ -1402,4 +1402,36 @@ describe("web monitor inbox", () => { await listener.close(); }); + + it("normalizes participant phone numbers to JIDs in sendReaction", async () => { + const listener = await monitorWebInbox({ + verbose: false, + onMessage: vi.fn(), + accountId: ACCOUNT_ID, + authDir, + }); + const sock = await createWaSocket(); + + await listener.sendReaction( + "12345@g.us", + "msg123", + "👍", + false, + "+6421000000", + ); + + expect(sock.sendMessage).toHaveBeenCalledWith("12345@g.us", { + react: { + text: "👍", + key: { + remoteJid: "12345@g.us", + id: "msg123", + fromMe: false, + participant: "6421000000@s.whatsapp.net", + }, + }, + }); + + await listener.close(); + }); }); From 7e6fa947202290d985e607d606c1a8079b149b25 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 10 Jan 2026 20:41:30 +0100 Subject: [PATCH 2/2] fix: update WhatsApp history assertions (#640) (thanks @mcinteerj) --- src/web/auto-reply.test.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/web/auto-reply.test.ts b/src/web/auto-reply.test.ts index e76333e8d..f1cea72ba 100644 --- a/src/web/auto-reply.test.ts +++ b/src/web/auto-reply.test.ts @@ -1127,7 +1127,8 @@ describe("web auto-reply", () => { expect(resolver).toHaveBeenCalledTimes(1); const payload = resolver.mock.calls[0][0]; expect(payload.Body).toContain("Chat messages since your last reply"); - expect(payload.Body).toContain("Alice: hello group"); + expect(payload.Body).toContain("Alice (+111): hello group"); + expect(payload.Body).toContain("[message_id: g1]"); expect(payload.Body).toContain("@bot ping"); expect(payload.Body).toContain("[from: Bob (+222)]"); }); @@ -1483,7 +1484,8 @@ describe("web auto-reply", () => { expect(resolver).toHaveBeenCalledTimes(2); const payload = resolver.mock.calls[1][0]; expect(payload.Body).toContain("Chat messages since your last reply"); - expect(payload.Body).toContain("Alice: first"); + expect(payload.Body).toContain("Alice (+111): first"); + expect(payload.Body).toContain("[message_id: g-always-1]"); expect(payload.Body).toContain("Bob: second"); expect(reply).toHaveBeenCalledTimes(1); @@ -2213,7 +2215,8 @@ describe("broadcast groups", () => { for (const call of resolver.mock.calls.slice(0, 2)) { const payload = call[0] as { Body: string }; expect(payload.Body).toContain("Chat messages since your last reply"); - expect(payload.Body).toContain("Alice: hello group"); + expect(payload.Body).toContain("Alice (+111): hello group"); + expect(payload.Body).toContain("[message_id: g1]"); expect(payload.Body).toContain("@bot ping"); expect(payload.Body).toContain("[from: Bob (+222)]"); } @@ -2239,7 +2242,7 @@ describe("broadcast groups", () => { expect(resolver).toHaveBeenCalledTimes(4); for (const call of resolver.mock.calls.slice(2, 4)) { const payload = call[0] as { Body: string }; - expect(payload.Body).not.toContain("Alice: hello group"); + expect(payload.Body).not.toContain("Alice (+111): hello group"); expect(payload.Body).not.toContain("Chat messages since your last reply"); }