Skip to content

bug(agent-dispatch): contact (vCard) messages received via WhatsApp silently dropped before reaching agent #635

@gstvbatista

Description

@gstvbatista

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

  1. WhatsApp instance routed to an agent (works fine with text/image/audio).
  2. From WhatsApp client, share a phone contact card to that instance.
  3. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions