Skip to content

Messaging

Messaging lets you bind an external chat (a Telegram DM, a WhatsApp contact, or a Lark/Feishu chat) to a Craft Agent session. Messages you send in that chat drive the agent; the agent’s output is rendered back into the same chat.

Enable a platform in Settings

Open Settings → Messaging and configure Telegram (paste a bot token) or WhatsApp (scan a QR code). Each workspace has its own messaging config.

Bind a chat to a session

From the external chat, send /new to create a fresh session, /bind to pick from recent sessions, or /pair <code> to redeem a pairing code generated from the app.

Chat drives the agent

Once bound, everything you type is forwarded to the agent as a prompt. The agent’s reply is rendered back into the chat using your chosen response mode.

Available inside any chat — the gateway treats any message starting with / as a command.

CommandWhat it does
/new [name]Create a new session and bind this chat to it. Optional name.
/bindList up to 10 recent sessions. On Telegram, tap an inline button. On WhatsApp, reply with /bind <number>.
/bind <id>Bind directly to a session by its ID or by list index.
/pair <code>Redeem a 6-digit pairing code generated from the session menu in the app.
/unbindDisconnect this chat from its current session.
/statusShow the bound session, approval channel, and response mode.
/stopAbort the current agent run.
/helpShow available commands.

How the agent’s output is rendered back to the chat.

ModeWhat you seeWhen to use
progress (default)One evolving message per run. A ”💭 thinking…” bubble appears on first activity, edits in place as tools run, and is replaced by the final answer when complete. If the run ends on a tool call without emitting a non-intermediate final, the most recent assistant text is used as the answer instead of leaving the thinking bubble behind.Keep the chat tidy — most users.
streamingLive edits during the final turn, plus every intermediate text_complete as its own message. Multiple messages per run.Parity with the in-app streaming experience.
final_onlySilent until the run completes, then one message with the final text. If no non-intermediate final arrived, the most recent assistant text is posted instead; genuinely empty runs stay silent.Quiet chats, batch-style workflows.

Response mode is configured per binding — you can have one chat on progress and another on final_only in the same workspace.

When you already have a session open in the app and want to continue it from your phone, use a pairing code instead of typing the session ID:

Generate a code from the session menu

In the app, open the session you want to bind, click the three-dot menu, and choose Pair to messaging…. A 6-digit code is shown.

Redeem it from the chat

In the external chat, send /pair 123456 (use your real code). The gateway validates the code, binds the chat, and confirms.

Security:

  • Codes expire after a short TTL.
  • The /pair command is rate-limited per sender — wrong guesses still consume the budget.
  • Codes only work inside the workspace that issued them.

Supported on Telegram: photos, documents, voice messages, video, and audio. Files are downloaded to a temp location and forwarded to the session as FileAttachment objects — the same way uploads from the app are handled. Hard cap: 20 MB per attachment.

WhatsApp attachment forwarding follows the same pattern; platform-specific limits and MIME handling are documented in the WhatsApp page.

When the bound session is in Ask permission mode, the agent asks for approval before running a bash command. The approvalChannel per binding decides where that prompt appears:

ValueBehaviour
chat (Telegram default)The approval prompt is posted to the chat — you reply Approve/Deny inline.
app (WhatsApp default, enforced)The prompt is shown only in the desktop app. WhatsApp bindings don’t support inline approvals.

The session’s own permission mode is still authoritative — approvalChannel only controls where the prompt is shown, not whether it happens.

When the agent submits a plan in Explore mode, Telegram bindings get inline ✅ Accept plan / ♻️ Accept & compact buttons (plus the plan content inline or as a plan.md attachment). WhatsApp bindings get a text pointer telling you to open the desktop app — plans can’t be accepted from WhatsApp yet. See WhatsApp → Plan Submission for the reasons.

  • Per-workspace. Each workspace has its own messaging config, bindings, and pairing codes. Binding in workspace A never accepts codes issued by workspace B.
  • Plan-token revocation. Plan tokens (used for bash-approval flows) are keyed by binding — rebinding a chat invalidates outstanding tokens for the old binding.
  • Rate limiting. /pair is throttled per sender. Inbound messages are routed through a per-binding queue so spam doesn’t back-pressure other bindings.
  • No group/channel chats. Telegram group and channel messages are rejected at the adapter boundary — only private DMs can drive a session.

Messaging config is persisted per workspace:

~/.craft-agent/workspaces/{workspaceId}/messaging/
├── config.json # platform enable flags
├── bindings.json # chat → session mappings
└── whatsapp-session/ # Baileys credentials (WhatsApp only)

Edits to bindings.json take effect on the next inbound message. Deleting whatsapp-session/ forces a re-pair.

The gateway also runs inside the standalone headless Bun server (packages/server). Telegram uses webhook mode on the server (you configure a webhook URL), while WhatsApp still runs its Baileys worker subprocess. See Server for deployment details.