diff --git a/CHANGELOG.md b/CHANGELOG.md index 3534d41f0e6..ea5667e5282 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -183,6 +183,7 @@ Docs: https://docs.openclaw.ai - Auth/login lockout recovery: clear stale `auth_permanent` and `billing` disabled state for all profiles matching the target provider when `openclaw models auth login` is invoked, so users locked out by expired or revoked OAuth tokens can recover by re-authenticating instead of waiting for the cooldown timer to expire. (#43057) - Auto-reply/context-engine compaction: persist the exact embedded-run metadata compaction count for main and followup runner session accounting, so metadata-only auto-compactions no longer undercount multi-compaction runs. (#42629) thanks @uf-hy. - Auth/Codex CLI reuse: sync reused Codex CLI credentials into the supported `openai-codex:default` OAuth profile instead of reviving the deprecated `openai-codex:codex-cli` slot, so doctor cleanup no longer loops. (#45353) thanks @Gugu-sugar. +- WhatsApp/group replies: recognize implicit reply-to-bot mentions when WhatsApp sends the quoted sender in `@lid` format, including device-suffixed self identities. (#23029) Thanks @sparkyrider. ## 2026.3.12 diff --git a/extensions/whatsapp/src/auto-reply/monitor/group-gating.ts b/extensions/whatsapp/src/auto-reply/monitor/group-gating.ts index 418d5ebee83..4f40bda112c 100644 --- a/extensions/whatsapp/src/auto-reply/monitor/group-gating.ts +++ b/extensions/whatsapp/src/auto-reply/monitor/group-gating.ts @@ -127,14 +127,17 @@ export function applyGroupGating(params: ApplyGroupGatingParams) { conversationId: params.conversationId, }); const requireMention = activation !== "always"; - const selfJid = params.msg.selfJid?.replace(/:\\d+/, ""); - const replySenderJid = params.msg.replyToSenderJid?.replace(/:\\d+/, ""); + const selfJid = params.msg.selfJid?.replace(/:\d+/, ""); + const selfLid = params.msg.selfLid?.replace(/:\d+/, ""); + // replyToSenderJid may carry either a standard JID or an @lid identifier. + const replySenderJid = params.msg.replyToSenderJid?.replace(/:\d+/, ""); const selfE164 = params.msg.selfE164 ? normalizeE164(params.msg.selfE164) : null; const replySenderE164 = params.msg.replyToSenderE164 ? normalizeE164(params.msg.replyToSenderE164) : null; const implicitMention = Boolean( (selfJid && replySenderJid && selfJid === replySenderJid) || + (selfLid && replySenderJid && selfLid === replySenderJid) || (selfE164 && replySenderE164 && selfE164 === replySenderE164), ); const mentionGate = resolveMentionGating({ diff --git a/extensions/whatsapp/src/auto-reply/web-auto-reply-monitor.test.ts b/extensions/whatsapp/src/auto-reply/web-auto-reply-monitor.test.ts index 412648b3180..474b2215066 100644 --- a/extensions/whatsapp/src/auto-reply/web-auto-reply-monitor.test.ts +++ b/extensions/whatsapp/src/auto-reply/web-auto-reply-monitor.test.ts @@ -112,7 +112,7 @@ describe("applyGroupGating", () => { accountId: "default", body: "following up", timestamp: Date.now(), - selfJid: "15551234567@s.whatsapp.net", + selfJid: "15551234567:1@s.whatsapp.net", selfE164: "+15551234567", replyToId: "m0", replyToBody: "bot said hi", @@ -125,6 +125,29 @@ describe("applyGroupGating", () => { expect(result.shouldProcess).toBe(true); }); + it("treats LID-format reply-to-bot as implicit mention", () => { + const cfg = makeConfig({}); + const { result } = runGroupGating({ + cfg, + msg: createGroupMessage({ + id: "m1-lid", + to: "+15550000", + accountId: "default", + body: "following up", + timestamp: Date.now(), + selfJid: "15551234567@s.whatsapp.net", + selfLid: "1234567890123:1@lid", + selfE164: "+15551234567", + replyToId: "m0", + replyToBody: "bot said hi", + replyToSender: "1234567890123@lid", + replyToSenderJid: "1234567890123@lid", + }), + }); + + expect(result.shouldProcess).toBe(true); + }); + it.each([ { id: "g-new", command: "/new" }, { id: "g-status", command: "/status" }, diff --git a/extensions/whatsapp/src/inbound/monitor.ts b/extensions/whatsapp/src/inbound/monitor.ts index 5337c5d6a43..990d8a65f4a 100644 --- a/extensions/whatsapp/src/inbound/monitor.ts +++ b/extensions/whatsapp/src/inbound/monitor.ts @@ -66,6 +66,7 @@ export async function monitorWebInbox(options: { } const selfJid = sock.user?.id; + const selfLid = sock.user?.lid; const selfE164 = selfJid ? jidToE164(selfJid) : null; const debouncer = createInboundDebouncer({ debounceMs: options.debounceMs ?? 0, @@ -372,6 +373,7 @@ export async function monitorWebInbox(options: { groupParticipants: inbound.groupParticipants, mentionedJids: mentionedJids ?? undefined, selfJid, + selfLid, selfE164, fromMe: Boolean(msg.key?.fromMe), location: enriched.location ?? undefined, diff --git a/extensions/whatsapp/src/inbound/types.ts b/extensions/whatsapp/src/inbound/types.ts index c9c97810bad..25a7edaae0c 100644 --- a/extensions/whatsapp/src/inbound/types.ts +++ b/extensions/whatsapp/src/inbound/types.ts @@ -30,6 +30,7 @@ export type WebInboundMessage = { groupParticipants?: string[]; mentionedJids?: string[]; selfJid?: string | null; + selfLid?: string | null; selfE164?: string | null; fromMe?: boolean; location?: NormalizedLocation; diff --git a/extensions/whatsapp/src/monitor-inbox.streams-inbound-messages.test.ts b/extensions/whatsapp/src/monitor-inbox.streams-inbound-messages.test.ts index 7e8b5c26887..427d7bd1d86 100644 --- a/extensions/whatsapp/src/monitor-inbox.streams-inbound-messages.test.ts +++ b/extensions/whatsapp/src/monitor-inbox.streams-inbound-messages.test.ts @@ -118,7 +118,12 @@ describe("web monitor inbox", () => { await tick(); expect(onMessage).toHaveBeenCalledWith( - expect.objectContaining({ body: "ping", from: "+999", to: "+123" }), + expect.objectContaining({ + body: "ping", + from: "+999", + to: "+123", + selfLid: "123:1@lid", + }), ); expect(sock.readMessages).toHaveBeenCalledWith([ { @@ -181,7 +186,12 @@ describe("web monitor inbox", () => { expect(getPNForLID).toHaveBeenCalledWith("999@lid"); expect(onMessage).toHaveBeenCalledWith( - expect.objectContaining({ body: "ping", from: "+999", to: "+123" }), + expect.objectContaining({ + body: "ping", + from: "+999", + to: "+123", + selfLid: "123:1@lid", + }), ); await listener.close(); diff --git a/extensions/whatsapp/src/monitor-inbox.test-harness.ts b/extensions/whatsapp/src/monitor-inbox.test-harness.ts index 43bc731c459..5dc61e9e4ac 100644 --- a/extensions/whatsapp/src/monitor-inbox.test-harness.ts +++ b/extensions/whatsapp/src/monitor-inbox.test-harness.ts @@ -44,7 +44,7 @@ export type MockSock = { getPNForLID: AnyMockFn; }; }; - user: { id: string }; + user: { id: string; lid?: string }; }; function createResolvedMock() { @@ -66,7 +66,7 @@ function createMockSock(): MockSock { getPNForLID: vi.fn().mockResolvedValue(null), }, }, - user: { id: "123@s.whatsapp.net" }, + user: { id: "123@s.whatsapp.net", lid: "123:1@lid" }, }; }