---
name: connect-whatsapp
description: Guide the user through connecting WhatsApp via a typed pairing code, registering admin phones, and selecting the public agent. Complete setup — not just pairing.
---

# Connect WhatsApp

Invoked from `specialists:personal-assistant`.

Conversational flow for linking a WhatsApp account to {{productName}} via a typed WhatsApp pairing code, followed by admin phone registration and public agent selection. All three phases are required for a correctly configured WhatsApp channel.

## When to activate

User says anything about connecting, linking, setting up, or adding WhatsApp.

## Prerequisites

Call `whatsapp-status` first to check the current state. If already connected, tell the user and offer to relink. If connected but admin phones or public agent are not configured, proceed to the relevant post-pairing step.

## Flow

### Phase 1: Pairing Code

1. **Get the WhatsApp number** — ask the operator for the phone number of the WhatsApp account being linked, in international format (e.g. +44 7700 900123). You need it before starting.

2. **Initiate pairing** — call `whatsapp-login-start` with `phone` set to that number. If the account is already linked and the user didn't say "relink", inform them and skip to Phase 2.

3. **Give the operator the code** — the tool returns an 8-character pairing code. Show it and tell the user: "On the phone with that number, open WhatsApp → Settings → Linked Devices → Link a Device → tap 'Link with phone number instead', then enter this code: {code}."

4. **Wait for the link** — call `whatsapp-login-wait`. It polls for up to 60 seconds. The code stays valid and the connection stays alive across polls, so if it returns still-waiting, call it again — do not generate a new code unless the operator asks. While waiting, tell the user you're watching for the link.

5. **Confirm connection** — when pairing completes, the tool returns the linked phone number (the *self phone* — the WhatsApp account {{productName}} now controls). The platform persists the WhatsApp config in `account.json` and registers this phone as an admin phone automatically, driven by the connection event (so it lands even when the link finishes after a restart, not only while you are polling). Confirm: "WhatsApp connected as {phone}. Your phone is registered as admin."

6. **Verify** — call `whatsapp-status` and confirm the linked phone shows as Connected. If status reports "Linked but config was missing — repaired automatically," that is the self-heal: call `whatsapp-status` once more to confirm it now shows Connected. It must never report "No accounts configured" while a phone is linked; if it does, reconnect with `whatsapp-login-start force: true`.

### Phase 2: Additional Admin Phones

The self phone from Phase 1 is automatically registered as an admin phone. If the user has additional phones that should also have admin access (e.g. a partner, co-owner, or second device), register them here.

1. **Offer additional admin phones** — "Would you like to add any other phone numbers as admin? For example, a partner or co-owner who should also have admin access."

2. **Register additional phones** — For each phone the user provides, call `whatsapp-config` with `action: "add-admin-phone"` and `phone: "{E.164 number}"`. The tool validates the format and adds it to the admin list.

3. **Confirm** — "Added {number} as admin. Messages from this number will route to the admin agent." Use `whatsapp-config` with `action: "list-admin-phones"` to show the complete list.

4. **Skip if not needed** — If the user declines, proceed to Phase 3. The self phone is already registered.

### Phase 3: Public Agent Selection

WhatsApp inbound from non-admin phones routes only for **authenticated-public** senders: the sender must be allowed by the DM policy **and** resolve to an enrolled `:Person` (their phone is on a Person record for this account). An allowed-but-unenrolled sender is dropped with `[whatsapp:route] op=dropped reason=public-unresolved`. Each routed sender gets their own per-person, reply-only session. The public-agent setting below is the WhatsApp config's designated public agent; it does not change how an authenticated-public sender is routed.

1. **Check current state** — Call `whatsapp-config` with `action: "get-public-agent"` to see if one is already set. Also call `account-manage` to list the available public agents.

2. **Ask which agent should handle WhatsApp** — "Which of your public agents should respond to WhatsApp messages from customers?"

3. **Set the public agent** — Call `whatsapp-config` with `action: "set-public-agent"` and `slug: "{chosen-slug}"`.

4. **Set the DM policy** — Call `whatsapp-config` with `action: "update-config"`, `dmPolicy: "allowlist"`, and the enrolled customers' phones in `allowFrom`. Without a policy that allows the sender, all non-admin messages are silently dropped regardless of which public agent is configured. Even an allowed sender only routes once enrolled as a `:Person` carrying that phone.

5. **Confirm** — "Enrolled customers on the allowlist will get their own WhatsApp conversation with the agent."

If only one public agent exists, confirm it as the choice rather than asking. If no public agents exist, the public-agent setting can be skipped — it does not gate authenticated-public routing.

## Observe socket (account manager's WhatsApp)

A sub-account can pair its own WhatsApp as a read-only observe socket: the agent reads that WhatsApp in full but the phone never drives the agent as instructions. This is for an account manager's own phone.

Pair it the same way (Phase 1), scoped to the sub-account's own account id, then turn off dispatch with `whatsapp-config action: set-dispatch-inbound accountId: <that account> dispatchInbound: false`. After that, the account's inbound is stored and readable but never spawns an agent turn. The agent can still reply into an existing chat with `whatsapp-reply`, never a new one. A sub-account session may pair and set only its own account. See the manage-whatsapp-config skill for the observe-socket section.

## Relinking

If the user says "relink WhatsApp" or the status shows disconnected with a 401 error:
1. Call `whatsapp-login-start` with `force: true` and the WhatsApp `phone` to clear old credentials and issue a fresh pairing code
2. Follow the same flow from Phase 1

Admin phone registration and public agent selection persist across relinks — no need to repeat Phases 2-3 unless the user wants to change them.

## Troubleshooting

- **Code expired** — generate a new one with `whatsapp-login-start` (with the `phone`)
- **Code not accepted** — ensure the phone has internet, WhatsApp is updated, and the operator used "Link with phone number instead" (not the camera scanner)
- **Keeps disconnecting** — check `whatsapp-status` for error details. A 401 means re-link is needed. Repeated transient errors suggest network issues on the device.
- **Messages routing as public** — the user's personal phone is not in `adminPhones`. Call `whatsapp-config` with `action: "add-admin-phone"` and the user's phone number to register it.

## Language

Use plain English:
- "pairing code" not "Baileys pairing token"
- "Link your phone with a code" not "initiate WebSocket connection"
- "WhatsApp is connected" not "Baileys socket is open"
- "admin phone" not "allowFrom entry"
- "public agent" not "defaultAgent slug"
