diff --git a/CHANGELOG.md b/CHANGELOG.md index 6084a3cf6..a525084be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -143,6 +143,7 @@ - Auto-reply: align `/think` default display with model reasoning defaults. (#751) — thanks @gabriel-trigo. - Auto-reply: flush block reply buffers on tool boundaries. (#750) — thanks @sebslight. - Auto-reply: allow sender fallback for command authorization when `SenderId` is empty (WhatsApp self-chat). (#755) — thanks @juanpablodlc. +- Auto-reply: treat whitespace-only sender ids as missing for command authorization (WhatsApp self-chat). - Heartbeat: refresh prompt text for updated defaults. - Agents/Tools: use PowerShell on Windows to capture system utility output. (#748) — thanks @myfunc. - Docker: tolerate unset optional env vars in docker-setup.sh under strict mode. (#725) — thanks @petradonka. diff --git a/src/auto-reply/command-auth.test.ts b/src/auto-reply/command-auth.test.ts index 099cf552f..850cf97eb 100644 --- a/src/auto-reply/command-auth.test.ts +++ b/src/auto-reply/command-auth.test.ts @@ -27,4 +27,50 @@ describe("resolveCommandAuthorization", () => { expect(auth.senderId).toBe("+123"); expect(auth.isAuthorizedSender).toBe(true); }); + + it("falls back from whitespace SenderId to SenderE164", () => { + const cfg = { + whatsapp: { allowFrom: ["+123"] }, + } as ClawdbotConfig; + + const ctx = { + Provider: "whatsapp", + Surface: "whatsapp", + From: "whatsapp:+999", + SenderId: " ", + SenderE164: "+123", + } as MsgContext; + + const auth = resolveCommandAuthorization({ + ctx, + cfg, + commandAuthorized: true, + }); + + expect(auth.senderId).toBe("+123"); + expect(auth.isAuthorizedSender).toBe(true); + }); + + it("falls back to From when SenderId and SenderE164 are whitespace", () => { + const cfg = { + whatsapp: { allowFrom: ["+999"] }, + } as ClawdbotConfig; + + const ctx = { + Provider: "whatsapp", + Surface: "whatsapp", + From: "whatsapp:+999", + SenderId: " ", + SenderE164: " ", + } as MsgContext; + + const auth = resolveCommandAuthorization({ + ctx, + cfg, + commandAuthorized: true, + }); + + expect(auth.senderId).toBe("+999"); + expect(auth.isAuthorizedSender).toBe(true); + }); }); diff --git a/src/auto-reply/command-auth.ts b/src/auto-reply/command-auth.ts index db268e124..fa6ba919f 100644 --- a/src/auto-reply/command-auth.ts +++ b/src/auto-reply/command-auth.ts @@ -96,7 +96,9 @@ export function resolveCommandAuthorization(params: { } const ownerList = ownerCandidates; - const senderRaw = ctx.SenderId || ctx.SenderE164 || from; + const senderIdCandidate = ctx.SenderId?.trim() ?? ""; + const senderE164Candidate = ctx.SenderE164?.trim() ?? ""; + const senderRaw = senderIdCandidate || senderE164Candidate || from; const senderId = senderRaw ? formatAllowFromList({ dock, diff --git a/src/web/auto-reply.test.ts b/src/web/auto-reply.test.ts index a731a13a2..7d9ae7a89 100644 --- a/src/web/auto-reply.test.ts +++ b/src/web/auto-reply.test.ts @@ -153,6 +153,53 @@ describe("partial reply gating", () => { expect(reply).toHaveBeenCalledWith("final reply"); }); + it("falls back from empty senderJid to senderE164 for SenderId", async () => { + const reply = vi.fn().mockResolvedValue(undefined); + const sendComposing = vi.fn().mockResolvedValue(undefined); + const sendMedia = vi.fn().mockResolvedValue(undefined); + + const replyResolver = vi.fn().mockResolvedValue({ text: "final reply" }); + + const mockConfig: ClawdbotConfig = { + whatsapp: { + allowFrom: ["*"], + }, + }; + + setLoadConfigMock(mockConfig); + + await monitorWebProvider( + false, + async ({ onMessage }) => { + await onMessage({ + id: "m1", + from: "+1000", + conversationId: "+1000", + to: "+2000", + body: "hello", + timestamp: Date.now(), + chatType: "direct", + chatId: "direct:+1000", + senderJid: "", + senderE164: "+1000", + sendComposing, + reply, + sendMedia, + }); + return { close: vi.fn().mockResolvedValue(undefined) }; + }, + false, + replyResolver, + ); + + resetLoadConfigMock(); + + expect(replyResolver).toHaveBeenCalledTimes(1); + const ctx = replyResolver.mock.calls[0]?.[0] ?? {}; + expect(ctx.SenderE164).toBe("+1000"); + expect(ctx.SenderId).toBe("+1000"); + }); + it("updates last-route for direct chats without senderE164", async () => { const now = Date.now(); const mainSessionKey = "agent:main:main"; diff --git a/src/web/auto-reply.ts b/src/web/auto-reply.ts index 21dc06286..869f333d8 100644 --- a/src/web/auto-reply.ts +++ b/src/web/auto-reply.ts @@ -1291,7 +1291,7 @@ export async function monitorWebProvider( msg.senderE164, ), SenderName: msg.senderName, - SenderId: msg.senderJid ?? msg.senderE164, + SenderId: msg.senderJid?.trim() || msg.senderE164, SenderE164: msg.senderE164, WasMentioned: msg.wasMentioned, ...(msg.location ? toLocationContext(msg.location) : {}),