Problem
When a user sends a contact card (vCard) via WhatsApp to an Omni-routed instance, the agent receives no information about the contact — the message is silently dropped from the dispatch pipeline. From the user's perspective, the agent acts as if nothing was sent.
This is the inbound counterpart to closed issue #330 (which handled outbound vCard waid formatting).
Reproduction
- WhatsApp instance routed to an agent (works fine with text/image/audio).
- From WhatsApp client, share a phone contact card to that instance.
- Agent receives nothing relevant — answers as if no content was provided.
Encountered in production on 2026-05-13 with @automagik/omni 2.260502.2:
User: [contact card]
Agent: "nao consegui ler o contato… manda o nome e o que ele faz que eu adiciono no ClickUp"
Root Cause
The channel-whatsapp handler does decode the vCard correctly (packages/channel-whatsapp/src/handlers/messages.ts:152-161):
{
check: (m) => !!m.contactMessage,
extract: (m) => ({
type: 'contact',
contact: {
name: m.contactMessage?.displayName ?? 'Unknown',
phone: m.contactMessage?.vcard ? extractPhoneFromVcard(m.contactMessage.vcard) : undefined,
},
}),
}
But three sites in packages/api/src/plugins/agent-dispatcher.ts (on origin/main as of commit ceafc507) never handle messageType === 'contact':
1. getMessageContentText (line 692)
switch (msg.messageType) {
case 'audio': /* … */
case 'image': /* … */
case 'video': /* … */
case 'document': /* … */
default:
return msg.textContent; // contact → textContent is null → returns null
}
Type signature also omits rawPayload, so even adding a case can't reach the contact data without widening the type.
2. prepareAgentContent (line 1014)
for (const [index, msg] of messages.entries()) {
const text = msg.payload.content?.text;
if (!text) continue; // contact has content.type='contact' but no .text → skipped
…
}
3. formatMediaContent (line 1504)
function formatMediaContent(msg: { messageType: string; hasMedia?: boolean; … }): string {
if (!msg.hasMedia) {
return msg.textContent || ''; // contact has hasMedia=false → returns empty
}
…
}
All three paths silently swallow the contact instead of rendering a useful representation.
Expected
Agent should receive something like:
[Contato] John Doe — +5511999999999
…composed from the already-decoded content.contact.{name,phone} (and/or the equivalent in rawPayload, depending on which is reachable at each site).
Suggested fix
Render [Contato] <name> — <phone> in all three sites, by either:
- widening the type signatures of
getMessageContentText / formatMediaContent to include the contact payload, and adding a case 'contact' branch; and
- in
prepareAgentContent, synthesise text from content.contact when content.type === 'contact' and content.text is missing, before the if (!text) continue guard.
Happy to send a PR if useful — wanted to surface the bug first.
Impact
- Any inbound contact share to a WhatsApp-routed agent is invisible. The user perceives the agent as broken/ignoring them.
- Affects all releases that include the current
agent-dispatcher.ts (verified on 2.260502.2; bug also present on origin/main at ceafc507).
Problem
When a user sends a contact card (vCard) via WhatsApp to an Omni-routed instance, the agent receives no information about the contact — the message is silently dropped from the dispatch pipeline. From the user's perspective, the agent acts as if nothing was sent.
This is the inbound counterpart to closed issue #330 (which handled outbound vCard
waidformatting).Reproduction
Encountered in production on 2026-05-13 with @automagik/omni 2.260502.2:
Root Cause
The channel-whatsapp handler does decode the vCard correctly (
packages/channel-whatsapp/src/handlers/messages.ts:152-161):But three sites in
packages/api/src/plugins/agent-dispatcher.ts(onorigin/mainas of commitceafc507) never handlemessageType === 'contact':1.
getMessageContentText(line 692)Type signature also omits
rawPayload, so even adding a case can't reach the contact data without widening the type.2.
prepareAgentContent(line 1014)3.
formatMediaContent(line 1504)All three paths silently swallow the contact instead of rendering a useful representation.
Expected
Agent should receive something like:
…composed from the already-decoded
content.contact.{name,phone}(and/or the equivalent inrawPayload, depending on which is reachable at each site).Suggested fix
Render
[Contato] <name> — <phone>in all three sites, by either:getMessageContentText/formatMediaContentto include the contact payload, and adding acase 'contact'branch; andprepareAgentContent, synthesisetextfromcontent.contactwhencontent.type === 'contact'andcontent.textis missing, before theif (!text) continueguard.Happy to send a PR if useful — wanted to surface the bug first.
Impact
agent-dispatcher.ts(verified on2.260502.2; bug also present onorigin/mainatceafc507).