From 7173d2e2c42e8bb50e8d550bd38aafe779172a11 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 5 Mar 2026 10:37:06 -0800 Subject: [PATCH] fix: use sender phone number as userId for WhatsApp group messages (#491) --- src/channels/whatsapp/index.ts | 2 +- src/core/formatter.test.ts | 22 +++++++++++++++++++++- src/core/formatter.ts | 29 ++++++++++++++++------------- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/channels/whatsapp/index.ts b/src/channels/whatsapp/index.ts index 4c71cdf..b9ca064 100644 --- a/src/channels/whatsapp/index.ts +++ b/src/channels/whatsapp/index.ts @@ -691,7 +691,7 @@ export class WhatsAppAdapter implements ChannelAdapter { } const { body, from, chatId, pushName, senderE164, chatType, isSelfChat: isExtractedSelfChat } = extracted; - const userId = normalizePhoneForStorage(from); + const userId = normalizePhoneForStorage(senderE164 || from); const isGroup = chatType === "group"; // CRITICAL: Skip messages older than connection time (prevents duplicate responses on reconnect) diff --git a/src/core/formatter.test.ts b/src/core/formatter.test.ts index be08cae..0b79dad 100644 --- a/src/core/formatter.test.ts +++ b/src/core/formatter.test.ts @@ -80,12 +80,32 @@ describe('formatMessageEnvelope', () => { }); describe('sender formatting', () => { - it('uses userName when available', () => { + it('uses userName when available (non-phone channels)', () => { const msg = createMessage({ userName: 'John Doe' }); const result = formatMessageEnvelope(msg); expect(result).toContain('**Sender**: John Doe'); }); + it('includes phone number alongside name for WhatsApp', () => { + const msg = createMessage({ + channel: 'whatsapp', + userName: 'John', + userId: '+15551234567', + }); + const result = formatMessageEnvelope(msg); + expect(result).toContain('**Sender**: John (+1 (555) 123-4567)'); + }); + + it('includes phone number alongside name for Signal', () => { + const msg = createMessage({ + channel: 'signal', + userName: 'Jane', + userId: '+15559876543', + }); + const result = formatMessageEnvelope(msg); + expect(result).toContain('**Sender**: Jane (+1 (555) 987-6543)'); + }); + it('formats Slack users with @ prefix', () => { const msg = createMessage({ channel: 'slack', diff --git a/src/core/formatter.ts b/src/core/formatter.ts index b52797d..69da020 100644 --- a/src/core/formatter.ts +++ b/src/core/formatter.ts @@ -87,34 +87,37 @@ function formatPhoneNumber(phone: string): string { * Format the sender identifier nicely based on channel */ function formatSender(msg: InboundMessage): string { - // Use display name if available - if (msg.userName?.trim()) { - return msg.userName.trim(); - } - + const name = msg.userName?.trim(); + // Format based on channel switch (msg.channel) { case 'slack': // Add @ prefix for Slack usernames/IDs - return msg.userHandle ? `@${msg.userHandle}` : `@${msg.userId}`; + return name || (msg.userHandle ? `@${msg.userHandle}` : `@${msg.userId}`); case 'discord': // Add @ prefix for Discord usernames/IDs - return msg.userHandle ? `@${msg.userHandle}` : `@${msg.userId}`; + return name || (msg.userHandle ? `@${msg.userHandle}` : `@${msg.userId}`); case 'whatsapp': - case 'signal': - // Format phone numbers nicely - if (/^\+?\d{10,}$/.test(msg.userId.replace(/\D/g, ''))) { + case 'signal': { + // For phone-based channels, always include the phone number so the agent + // can uniquely identify senders (pushName is user-chosen and not unique). + const isPhone = /^\+?\d{10,}$/.test(msg.userId.replace(/\D/g, '')); + if (name && isPhone) { + return `${name} (${formatPhoneNumber(msg.userId)})`; + } + if (isPhone) { return formatPhoneNumber(msg.userId); } - return msg.userId; + return name || msg.userId; + } case 'telegram': - return msg.userHandle ? `@${msg.userHandle}` : msg.userId; + return name || (msg.userHandle ? `@${msg.userHandle}` : msg.userId); default: - return msg.userId; + return name || msg.userId; } }