Skip to content

feat(whatsapp): WhatsApp concierge channel (shared number + phone routing)#3850

Open
vibegui wants to merge 2 commits into
mainfrom
vibegui/whatsapp
Open

feat(whatsapp): WhatsApp concierge channel (shared number + phone routing)#3850
vibegui wants to merge 2 commits into
mainfrom
vibegui/whatsapp

Conversation

@vibegui

@vibegui vibegui commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

A standalone WhatsApp channel, based off main — independent of the Teams/Discord work. One shared decoCMS concierge number serves every org: a user verifies their phone once in their profile, and inbound messages route by phone → user → org, with the org's agent answering as that real user.

Carries only the minimal channel plumbing it needs (a generic channels table + ChannelStorage + runChannelTurn + channel runtime + enable/list/delete tools). No Teams/Discord adapters, webhooks, wizard, credentials, or bot identity.

How it works

  • Verification (inbound-only): Studio shows a code in the profile; the user texts it to the concierge number; that inbound proves ownership and links the number.
  • Ingest contract: the decocms/concierge worker forwards every inbound to POST /api/whatsapp/ingest; Studio classifies synchronously and returns { handled }. Studio owns the message only for a verification code or a linked user with a WhatsApp-enabled org — otherwise { handled: false } and the concierge runs its own presales bot. The reply is delivered async via the worker's send endpoint.
  • Routing: verify code → resolve phone→user → pick target org (saved default + in-chat numbered pick-list / switch) → run the agent as the real user.
  • Enable-only UI: Settings → Channels → Add WhatsApp → pick the agent. Members link their phone under Profile → Link WhatsApp.
  • Available only when WHATSAPP_WORKER_URL/TOKEN, WHATSAPP_INGEST_SECRET, WHATSAPP_CONCIERGE_NUMBER are set.

Key pieces

  • migrations 109-channels (generic per-org channel: agent + status) + 110-user-phones
  • UserPhoneStorage, phone canonicalization, whatsapp-worker client, whatsapp-ingest route (returns { handled })
  • profile tools PHONE_LINK_START / PHONE_GET / PHONE_DELETE (basic-usage)
  • tools CHANNELS_LIST / CHANNEL_CREATE / CHANNEL_LIST / CHANNEL_DELETE

The worker

The integration target is decocms/concierge (worker decocms-concierge, api/main.ts). It stays the front-door presales bot and gains: a Studio handoff at the top of handleInbound (forward → if handled, stop), and a POST /send endpoint for Studio to deliver replies. Full prompt: .context/whatsapp-worker-integration-prompt.md.

Testing

  • bun run check 0 errors · lint · knip · fmt:check clean · build:server ok.
  • Unit tests: org-selection resolver + phone canonicalization.
  • Manual: bun run --cwd=apps/mesh migrate, then curl the ingest endpoint (asserts {handled}), with a mock /send.

🤖 Generated with Claude Code

… main)

Standalone WhatsApp channel, independent of Teams/Discord. One shared decoCMS
concierge number serves every org: a user verifies their phone once in their
profile (Studio issues a code, the user texts it to the number); inbound
messages route by phone → user → org and the org's agent answers AS that real
user.

Carries the minimal channel plumbing (generic `channels` table + ChannelStorage
+ runChannelTurn + channel runtime + the enable/list/delete tools) — no
Teams/Discord adapters, webhooks, wizard, credentials, or bot identity. Those
can stack on top later.

- migrations: 109-channels (generic per-org channel: agent + status),
  110-user-phones (verified phone link, pending code, selected org)
- UserPhoneStorage + phone canonicalization; ChannelStorage
- settings: WHATSAPP_WORKER_URL/TOKEN, WHATSAPP_INGEST_SECRET, CONCIERGE_NUMBER
- whatsapp-worker client (Studio→worker /send) + global ingest route
  /api/whatsapp/ingest (secret auth, async): inbound-only code verification,
  phone→user→org resolution with default-org + in-chat pick-list, runs the agent
  as the real user via runChannelTurn
- profile MCP tools PHONE_LINK_START / PHONE_GET / PHONE_DELETE (basic-usage)
- enable-only channel tools: CHANNELS_LIST / CHANNEL_CREATE / CHANNEL_LIST /
  CHANNEL_DELETE
- frontend: Channels settings page (WhatsApp-only), "Add WhatsApp" enable dialog
  (pick agent), profile "Link WhatsApp" (show code + poll)
- unit tests: org-selection resolver + phone canonicalization

Worker integration prompt: .context/whatsapp-worker-integration-prompt.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s own bot

The concierge worker is a self-contained presales bot, not a thin relay. The
ingest endpoint now classifies synchronously and returns { handled }: Studio
owns the message only for a verification code or a phone linked to a user with a
WhatsApp-enabled org; otherwise { handled: false } and the concierge runs its
own pipeline. The agent reply is still delivered async via the worker's send
endpoint. Drops the 'not linked' auto-reply (that path is now the concierge's).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant