diff --git a/AGENTS.md b/AGENTS.md index 4ffde6349..f3d6ff36f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -81,6 +81,7 @@ - **Multi-agent safety:** do **not** switch branches / check out a different branch unless explicitly requested. - **Multi-agent safety:** running multiple agents is OK as long as each agent has its own session. - **Multi-agent safety:** when you see unrecognized files, keep going; focus on your changes and commit only those. +- Bug investigations: read source code of relevant npm dependencies and all related local code before concluding; aim for high-confidence root cause. - When asked to open a “session” file, open the Pi session logs under `~/.clawdbot/sessions/*.jsonl` (newest unless a specific ID is given), not the default `sessions.json`. If logs are needed from another machine, SSH via Tailscale and read the same path there. - Menubar dimming + restart flow mirrors Trimmy: use `scripts/restart-mac.sh` (kills all Clawdbot variants, runs `swift build`, packages, relaunches). Icon dimming depends on MenuBarExtraAccess wiring in AppMain; keep `appearsDisabled` updates intact when touching the status item. - Do not rebuild the macOS app over SSH; rebuilds must be run directly on the Mac. diff --git a/CHANGELOG.md b/CHANGELOG.md index 256181675..aed23760e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Control UI: add Docs link, remove chat composer divider, and add New session button. - Telegram: retry long-polling conflicts with backoff to avoid fatal exits. - Telegram: fix grammY fetch type mismatch when injecting `fetch`. (#512) — thanks @YuriNachos +- WhatsApp: resolve @lid JIDs via Baileys mapping to unblock inbound messages. (#415) - Agent system prompt: avoid automatic self-updates unless explicitly requested. - Onboarding: tighten QuickStart hint copy for configuring later. - Onboarding: avoid “token expired” for Codex CLI when expiry is heuristic. diff --git a/src/web/inbound.ts b/src/web/inbound.ts index c70b7ab47..dcd97224c 100644 --- a/src/web/inbound.ts +++ b/src/web/inbound.ts @@ -118,6 +118,25 @@ export async function monitorWebInbox(options: { { subject?: string; participants?: string[]; expires: number } >(); const GROUP_META_TTL_MS = 5 * 60 * 1000; // 5 minutes + const lidLookup = sock.signalRepository?.lidMapping; + + const resolveJidToE164 = async ( + jid: string | null | undefined, + ): Promise => { + if (!jid) return null; + const direct = jidToE164(jid); + if (direct) return direct; + if (!/(@lid|@hosted\.lid)$/.test(jid)) return null; + if (!lidLookup?.getPNForLID) return null; + try { + const pnJid = await lidLookup.getPNForLID(jid); + if (!pnJid) return null; + return jidToE164(pnJid); + } catch (err) { + logVerbose(`LID mapping lookup failed for ${jid}: ${String(err)}`); + return null; + } + }; const getGroupMeta = async (jid: string) => { const cached = groupMetaCache.get(jid); @@ -125,9 +144,14 @@ export async function monitorWebInbox(options: { try { const meta = await sock.groupMetadata(jid); const participants = - meta.participants - ?.map((p) => jidToE164(p.id) ?? p.id) - .filter(Boolean) ?? []; + ( + await Promise.all( + meta.participants?.map(async (p) => { + const mapped = await resolveJidToE164(p.id); + return mapped ?? p.id; + }) ?? [], + ) + ).filter(Boolean) ?? []; const entry = { subject: meta.subject, participants, @@ -159,12 +183,12 @@ export async function monitorWebInbox(options: { continue; const group = isJidGroup(remoteJid); const participantJid = msg.key?.participant ?? undefined; - const from = group ? remoteJid : jidToE164(remoteJid); + const from = group ? remoteJid : await resolveJidToE164(remoteJid); // Skip if we still can't resolve an id to key conversation if (!from) continue; const senderE164 = group ? participantJid - ? jidToE164(participantJid) + ? await resolveJidToE164(participantJid) : null : from; let groupSubject: string | undefined; diff --git a/src/web/monitor-inbox.test.ts b/src/web/monitor-inbox.test.ts index 3ae395d66..fcb1f4b28 100644 --- a/src/web/monitor-inbox.test.ts +++ b/src/web/monitor-inbox.test.ts @@ -50,6 +50,11 @@ vi.mock("./session.js", () => { readMessages: vi.fn().mockResolvedValue(undefined), updateMediaMessage: vi.fn(), logger: {}, + signalRepository: { + lidMapping: { + getPNForLID: vi.fn().mockResolvedValue(null), + }, + }, user: { id: "123@s.whatsapp.net" }, }; return { @@ -136,6 +141,85 @@ describe("web monitor inbox", () => { await listener.close(); }); + it("resolves LID JIDs using Baileys LID mapping store", async () => { + const onMessage = vi.fn(async () => { + return; + }); + + const listener = await monitorWebInbox({ verbose: false, onMessage }); + const sock = await createWaSocket(); + sock.signalRepository.lidMapping.getPNForLID.mockResolvedValueOnce( + "999:0@s.whatsapp.net", + ); + const upsert = { + type: "notify", + messages: [ + { + key: { id: "abc", fromMe: false, remoteJid: "999@lid" }, + message: { conversation: "ping" }, + messageTimestamp: 1_700_000_000, + pushName: "Tester", + }, + ], + }; + + sock.ev.emit("messages.upsert", upsert); + await new Promise((resolve) => setImmediate(resolve)); + + expect(sock.signalRepository.lidMapping.getPNForLID).toHaveBeenCalledWith( + "999@lid", + ); + expect(onMessage).toHaveBeenCalledWith( + expect.objectContaining({ body: "ping", from: "+999", to: "+123" }), + ); + + await listener.close(); + }); + + it("resolves group participant LID JIDs via Baileys mapping", async () => { + const onMessage = vi.fn(async () => { + return; + }); + + const listener = await monitorWebInbox({ verbose: false, onMessage }); + const sock = await createWaSocket(); + sock.signalRepository.lidMapping.getPNForLID.mockResolvedValueOnce( + "444:0@s.whatsapp.net", + ); + const upsert = { + type: "notify", + messages: [ + { + key: { + id: "abc", + fromMe: false, + remoteJid: "123@g.us", + participant: "444@lid", + }, + message: { conversation: "ping" }, + messageTimestamp: 1_700_000_000, + }, + ], + }; + + sock.ev.emit("messages.upsert", upsert); + await new Promise((resolve) => setImmediate(resolve)); + + expect(sock.signalRepository.lidMapping.getPNForLID).toHaveBeenCalledWith( + "444@lid", + ); + expect(onMessage).toHaveBeenCalledWith( + expect.objectContaining({ + body: "ping", + from: "123@g.us", + senderE164: "+444", + chatType: "group", + }), + ); + + await listener.close(); + }); + it("does not block follow-up messages when handler is pending", async () => { let resolveFirst: (() => void) | null = null; const onMessage = vi.fn(async () => {