Skip to main content

Documentation Index

Fetch the complete documentation index at: https://agents.craft.do/docs/llms.txt

Use this file to discover all available pages before exploring further.

Unofficial integration. The WhatsApp adapter uses Baileys, a community-maintained reverse-engineered client for WhatsApp Web. It’s not an official WhatsApp Business API integration. Use it for personal automation and accept that Meta may rate-limit or block the session at their discretion — keep backups of important chats elsewhere.
WhatsApp integration uses a QR-code pairing flow (the same one WhatsApp Web uses) and runs in a subprocess worker to keep Baileys’ global state isolated from the main Electron process.

Pair Your Account

1

Open Settings → Messaging

In Craft Agent, click the WhatsApp tile’s Connect button.
2

Scan the QR code with your phone

A QR code appears in the dialog. On your phone, open WhatsApp → Settings → Linked Devices → Link a Device and scan the code.
3

Wait for connection

The dialog updates to Connected as <your-name>. Your Baileys session is persisted in:
~/.craft-agent/workspaces/{workspaceId}/messaging/whatsapp-session/
Keep this folder — deleting it forces a re-pair.
No second phone needed. Self-chat mode (on by default) lets you message yourself (your own number in WhatsApp) and have the agent drive the reply. See Self-Chat Mode below.

First Conversation

Self-Chat Mode

When enabled (default), messages you send from another device on the same WhatsApp account to your own JID are treated as inbound and routed to the bound session.
BehaviourWhy it matters
Reply prefixAll outbound messages from the worker to self are prefixed with 🤖 (U+1F916). This is how the worker filters its own echoes so it doesn’t drive itself in an infinite loop.
Sent-ID trackingThe worker also tracks message IDs it sent, so replies to those are handled as context rather than fresh prompts.
LID supportLong-ID contacts (the new lid format WhatsApp rolls out gradually) are recognized as “self” when they map to your account.
Toggle self-chat off in Settings → Messaging → WhatsApp if you prefer to drive sessions only from a separate contact.

Attachments

Supported media types: photos, documents, voice messages, video, audio. Same 20 MB cap as Telegram. Files land in the session as FileAttachment objects with the original MIME type.

Approval Channel

For WhatsApp bindings, approvalChannel is always app — the gateway doesn’t support inline approval replies on WhatsApp. When a bound session is in Ask mode and the agent requests approval for a bash command, the prompt appears in the desktop app, not in the chat. Everything else (prompts, replies) flows through WhatsApp as normal.

Plan Submission

When the agent is in Explore mode and submits a plan via SubmitPlan, WhatsApp users see a text-only pointer:
📝 A plan is ready for review. Open the desktop app to inspect and approve it.
You cannot accept or reject a plan from WhatsApp — the approval round-trip happens in the desktop app only. Telegram bindings, by contrast, get two tappable buttons (✅ Accept plan and ♻️ Accept & compact) attached to the plan message, plus the plan content inline (or as an attached plan.md if it exceeds Telegram’s message length cap).
Why the difference? Three stacked reasons:
  1. WhatsApp has no inline buttons in the adapter’s current capability set. Telegram’s flow relies on tappable buttons to carry a signed plan-approval token back; WhatsApp can’t render those.
  2. No text-reply redeem path — plan-approval tokens are one-shot and designed to be consumed on button press. There’s no command like /accept <token> implemented for chats that don’t have buttons.
  3. approvalChannel is hardcoded to app for WhatsApp as belt-and-braces — bash-approval and plan-submission prompts both route to the desktop app.
This is an explicit deferral, not a hard limit. WhatsApp does support quick-reply buttons in its newer protocol (Baileys’ buttonsMessage / interactiveMessage), and a slash-command fallback would work even without protocol support. Track the limitation in the GitHub issues if you need this — it’s not wired up yet.

Architecture (for the curious)

Skip this section unless you’re debugging or deploying headlessly. Day-to-day use doesn’t require understanding the worker.
The WhatsApp adapter is unusual because Baileys holds a lot of global state (credential stores, message queues, reconnect timers) and expects to be the only instance in its process. Running it inside the main Electron process would make crashes contagious. Instead:
┌──────────────────────────┐   NDJSON over stdio   ┌───────────────────────────┐
│ Electron main process    │ ────────────────────► │ messaging-whatsapp-worker │
│  (messaging-gateway      │ ◄──────────────────── │  (Baileys + grammY-like   │
│   adapter client)        │   typed events        │   event loop, single CJS) │
└──────────────────────────┘                       └───────────────────────────┘
        ↑                                                      ↑
        │                                                      │
        └─ sends IncomingMessage                                └─ persists creds to
           to Router                                              whatsapp-session/
  • The worker is a single bundled CJS file (packages/messaging-whatsapp-worker/dist) launched under Electron’s embedded Node via ELECTRON_RUN_AS_NODE.
  • Communication is newline-delimited JSON over stdio — requests from the main process, typed events (QR, connected, message, disconnect) from the worker.
  • On worker exit, the main process drains pending sends with a timeout so nothing is silently dropped.
  • In CI, the worker bundle is built and verified in release artifacts so broken bundles can’t ship.

Troubleshooting

  • Make sure the phone that scans is online.
  • Check that WhatsApp on the phone is up to date — Meta sometimes rotates the linking protocol and Baileys takes a release to catch up.
  • Try Disconnect from the three-dot menu and re-pair from scratch.
The menu label is Disconnect (renamed from the earlier “Forget Device”). It clears the persisted Baileys session and forces a re-pair on next connect.
WhatsApp syncs linked devices lazily. Open WhatsApp on the paired device, wait 10–15 seconds, or send a dummy message from the phone to force a sync.
The worker drains pending outbound sends on exit with a timeout. If the app is force-killed the queue is lost, but all inbound messages are persisted on WhatsApp’s side — you won’t miss anything you sent to the agent; you may miss agent replies that were mid-flight.
  • Confirm self-chat mode is enabled in Settings → Messaging → WhatsApp.
  • If your account recently migrated to a LID, the binding may still reference the old JID. Unbind and re-bind from the self-chat.
Meta periodically expires inactive linked devices. Re-pair from the app — your bindings and session data are preserved.
By design, for now. When the agent submits a plan in Explore mode, WhatsApp bindings get a text pointer to “open the desktop app” instead of an interactive button. See Plan Submission for the reasons and workarounds.