fix: resolve WhatsApp LID inbound mapping
This commit is contained in:
@@ -81,6 +81,7 @@
|
|||||||
- **Multi-agent safety:** do **not** switch branches / check out a different branch unless explicitly requested.
|
- **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:** 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.
|
- **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.
|
- 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.
|
- 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.
|
- Do not rebuild the macOS app over SSH; rebuilds must be run directly on the Mac.
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
- Control UI: add Docs link, remove chat composer divider, and add New session button.
|
- 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: retry long-polling conflicts with backoff to avoid fatal exits.
|
||||||
- Telegram: fix grammY fetch type mismatch when injecting `fetch`. (#512) — thanks @YuriNachos
|
- 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.
|
- Agent system prompt: avoid automatic self-updates unless explicitly requested.
|
||||||
- Onboarding: tighten QuickStart hint copy for configuring later.
|
- Onboarding: tighten QuickStart hint copy for configuring later.
|
||||||
- Onboarding: avoid “token expired” for Codex CLI when expiry is heuristic.
|
- Onboarding: avoid “token expired” for Codex CLI when expiry is heuristic.
|
||||||
|
|||||||
@@ -118,6 +118,25 @@ export async function monitorWebInbox(options: {
|
|||||||
{ subject?: string; participants?: string[]; expires: number }
|
{ subject?: string; participants?: string[]; expires: number }
|
||||||
>();
|
>();
|
||||||
const GROUP_META_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
const GROUP_META_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
||||||
|
const lidLookup = sock.signalRepository?.lidMapping;
|
||||||
|
|
||||||
|
const resolveJidToE164 = async (
|
||||||
|
jid: string | null | undefined,
|
||||||
|
): Promise<string | null> => {
|
||||||
|
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 getGroupMeta = async (jid: string) => {
|
||||||
const cached = groupMetaCache.get(jid);
|
const cached = groupMetaCache.get(jid);
|
||||||
@@ -125,9 +144,14 @@ export async function monitorWebInbox(options: {
|
|||||||
try {
|
try {
|
||||||
const meta = await sock.groupMetadata(jid);
|
const meta = await sock.groupMetadata(jid);
|
||||||
const participants =
|
const participants =
|
||||||
meta.participants
|
(
|
||||||
?.map((p) => jidToE164(p.id) ?? p.id)
|
await Promise.all(
|
||||||
.filter(Boolean) ?? [];
|
meta.participants?.map(async (p) => {
|
||||||
|
const mapped = await resolveJidToE164(p.id);
|
||||||
|
return mapped ?? p.id;
|
||||||
|
}) ?? [],
|
||||||
|
)
|
||||||
|
).filter(Boolean) ?? [];
|
||||||
const entry = {
|
const entry = {
|
||||||
subject: meta.subject,
|
subject: meta.subject,
|
||||||
participants,
|
participants,
|
||||||
@@ -159,12 +183,12 @@ export async function monitorWebInbox(options: {
|
|||||||
continue;
|
continue;
|
||||||
const group = isJidGroup(remoteJid);
|
const group = isJidGroup(remoteJid);
|
||||||
const participantJid = msg.key?.participant ?? undefined;
|
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
|
// Skip if we still can't resolve an id to key conversation
|
||||||
if (!from) continue;
|
if (!from) continue;
|
||||||
const senderE164 = group
|
const senderE164 = group
|
||||||
? participantJid
|
? participantJid
|
||||||
? jidToE164(participantJid)
|
? await resolveJidToE164(participantJid)
|
||||||
: null
|
: null
|
||||||
: from;
|
: from;
|
||||||
let groupSubject: string | undefined;
|
let groupSubject: string | undefined;
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ vi.mock("./session.js", () => {
|
|||||||
readMessages: vi.fn().mockResolvedValue(undefined),
|
readMessages: vi.fn().mockResolvedValue(undefined),
|
||||||
updateMediaMessage: vi.fn(),
|
updateMediaMessage: vi.fn(),
|
||||||
logger: {},
|
logger: {},
|
||||||
|
signalRepository: {
|
||||||
|
lidMapping: {
|
||||||
|
getPNForLID: vi.fn().mockResolvedValue(null),
|
||||||
|
},
|
||||||
|
},
|
||||||
user: { id: "123@s.whatsapp.net" },
|
user: { id: "123@s.whatsapp.net" },
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
@@ -136,6 +141,85 @@ describe("web monitor inbox", () => {
|
|||||||
await listener.close();
|
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 () => {
|
it("does not block follow-up messages when handler is pending", async () => {
|
||||||
let resolveFirst: (() => void) | null = null;
|
let resolveFirst: (() => void) | null = null;
|
||||||
const onMessage = vi.fn(async () => {
|
const onMessage = vi.fn(async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user