mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-11 04:10:44 +08:00
重构主控编排与运行时预热链路,统一工作区提示词/专家调度协议并补齐 wiki 记忆注入与写回闭环。
同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。 Made-with: Cursor
This commit is contained in:
parent
4a23b715a2
commit
dbbe3add6a
14438 changed files with 2693620 additions and 2546 deletions
435
openclaw/docs/channels/bluebubbles.md
Normal file
435
openclaw/docs/channels/bluebubbles.md
Normal file
|
|
@ -0,0 +1,435 @@
|
|||
---
|
||||
summary: "iMessage via BlueBubbles macOS server (REST send/receive, typing, reactions, pairing, advanced actions)."
|
||||
read_when:
|
||||
- Setting up BlueBubbles channel
|
||||
- Troubleshooting webhook pairing
|
||||
- Configuring iMessage on macOS
|
||||
title: "BlueBubbles"
|
||||
---
|
||||
|
||||
# BlueBubbles (macOS REST)
|
||||
|
||||
Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **Recommended for iMessage integration** due to its richer API and easier setup compared to the legacy imsg channel.
|
||||
|
||||
## Bundled plugin
|
||||
|
||||
Current OpenClaw releases bundle BlueBubbles, so normal packaged builds do not
|
||||
need a separate `openclaw plugins install` step.
|
||||
|
||||
## Overview
|
||||
|
||||
- Runs on macOS via the BlueBubbles helper app ([bluebubbles.app](https://bluebubbles.app)).
|
||||
- Recommended/tested: macOS Sequoia (15). macOS Tahoe (26) works; edit is currently broken on Tahoe, and group icon updates may report success but not sync.
|
||||
- OpenClaw talks to it through its REST API (`GET /api/v1/ping`, `POST /message/text`, `POST /chat/:id/*`).
|
||||
- Incoming messages arrive via webhooks; outgoing replies, typing indicators, read receipts, and tapbacks are REST calls.
|
||||
- Attachments and stickers are ingested as inbound media (and surfaced to the agent when possible).
|
||||
- Pairing/allowlist works the same way as other channels (`/channels/pairing` etc) with `channels.bluebubbles.allowFrom` + pairing codes.
|
||||
- Reactions are surfaced as system events just like Slack/Telegram so agents can "mention" them before replying.
|
||||
- Advanced features: edit, unsend, reply threading, message effects, group management.
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Install the BlueBubbles server on your Mac (follow the instructions at [bluebubbles.app/install](https://bluebubbles.app/install)).
|
||||
2. In the BlueBubbles config, enable the web API and set a password.
|
||||
3. Run `openclaw onboard` and select BlueBubbles, or configure manually:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
enabled: true,
|
||||
serverUrl: "http://192.168.1.100:1234",
|
||||
password: "example-password",
|
||||
webhookPath: "/bluebubbles-webhook",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
4. Point BlueBubbles webhooks to your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=<password>`).
|
||||
5. Start the gateway; it will register the webhook handler and start pairing.
|
||||
|
||||
Security note:
|
||||
|
||||
- Always set a webhook password.
|
||||
- Webhook authentication is always required. OpenClaw rejects BlueBubbles webhook requests unless they include a password/guid that matches `channels.bluebubbles.password` (for example `?password=<password>` or `x-password`), regardless of loopback/proxy topology.
|
||||
- Password authentication is checked before reading/parsing full webhook bodies.
|
||||
|
||||
## Keeping Messages.app alive (VM / headless setups)
|
||||
|
||||
Some macOS VM / always-on setups can end up with Messages.app going “idle” (incoming events stop until the app is opened/foregrounded). A simple workaround is to **poke Messages every 5 minutes** using an AppleScript + LaunchAgent.
|
||||
|
||||
### 1) Save the AppleScript
|
||||
|
||||
Save this as:
|
||||
|
||||
- `~/Scripts/poke-messages.scpt`
|
||||
|
||||
Example script (non-interactive; does not steal focus):
|
||||
|
||||
```applescript
|
||||
try
|
||||
tell application "Messages"
|
||||
if not running then
|
||||
launch
|
||||
end if
|
||||
|
||||
-- Touch the scripting interface to keep the process responsive.
|
||||
set _chatCount to (count of chats)
|
||||
end tell
|
||||
on error
|
||||
-- Ignore transient failures (first-run prompts, locked session, etc).
|
||||
end try
|
||||
```
|
||||
|
||||
### 2) Install a LaunchAgent
|
||||
|
||||
Save this as:
|
||||
|
||||
- `~/Library/LaunchAgents/com.user.poke-messages.plist`
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.user.poke-messages</string>
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/bin/bash</string>
|
||||
<string>-lc</string>
|
||||
<string>/usr/bin/osascript "$HOME/Scripts/poke-messages.scpt"</string>
|
||||
</array>
|
||||
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
|
||||
<key>StartInterval</key>
|
||||
<integer>300</integer>
|
||||
|
||||
<key>StandardOutPath</key>
|
||||
<string>/tmp/poke-messages.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/tmp/poke-messages.err</string>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- This runs **every 300 seconds** and **on login**.
|
||||
- The first run may trigger macOS **Automation** prompts (`osascript` → Messages). Approve them in the same user session that runs the LaunchAgent.
|
||||
|
||||
Load it:
|
||||
|
||||
```bash
|
||||
launchctl unload ~/Library/LaunchAgents/com.user.poke-messages.plist 2>/dev/null || true
|
||||
launchctl load ~/Library/LaunchAgents/com.user.poke-messages.plist
|
||||
```
|
||||
|
||||
## Onboarding
|
||||
|
||||
BlueBubbles is available in interactive onboarding:
|
||||
|
||||
```
|
||||
openclaw onboard
|
||||
```
|
||||
|
||||
The wizard prompts for:
|
||||
|
||||
- **Server URL** (required): BlueBubbles server address (e.g., `http://192.168.1.100:1234`)
|
||||
- **Password** (required): API password from BlueBubbles Server settings
|
||||
- **Webhook path** (optional): Defaults to `/bluebubbles-webhook`
|
||||
- **DM policy**: pairing, allowlist, open, or disabled
|
||||
- **Allow list**: Phone numbers, emails, or chat targets
|
||||
|
||||
You can also add BlueBubbles via CLI:
|
||||
|
||||
```
|
||||
openclaw channels add bluebubbles --http-url http://192.168.1.100:1234 --password <password>
|
||||
```
|
||||
|
||||
## Access control (DMs + groups)
|
||||
|
||||
DMs:
|
||||
|
||||
- Default: `channels.bluebubbles.dmPolicy = "pairing"`.
|
||||
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
- `openclaw pairing list bluebubbles`
|
||||
- `openclaw pairing approve bluebubbles <CODE>`
|
||||
- Pairing is the default token exchange. Details: [Pairing](/channels/pairing)
|
||||
|
||||
Groups:
|
||||
|
||||
- `channels.bluebubbles.groupPolicy = open | allowlist | disabled` (default: `allowlist`).
|
||||
- `channels.bluebubbles.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
||||
|
||||
### Contact name enrichment (macOS, optional)
|
||||
|
||||
BlueBubbles group webhooks often only include raw participant addresses. If you want `GroupMembers` context to show local contact names instead, you can opt in to local Contacts enrichment on macOS:
|
||||
|
||||
- `channels.bluebubbles.enrichGroupParticipantsFromContacts = true` enables the lookup. Default: `false`.
|
||||
- Lookups run only after group access, command authorization, and mention gating have allowed the message through.
|
||||
- Only unnamed phone participants are enriched.
|
||||
- Raw phone numbers remain as the fallback when no local match is found.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
enrichGroupParticipantsFromContacts: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Mention gating (groups)
|
||||
|
||||
BlueBubbles supports mention gating for group chats, matching iMessage/WhatsApp behavior:
|
||||
|
||||
- Uses `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) to detect mentions.
|
||||
- When `requireMention` is enabled for a group, the agent only responds when mentioned.
|
||||
- Control commands from authorized senders bypass mention gating.
|
||||
|
||||
Per-group configuration:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15555550123"],
|
||||
groups: {
|
||||
"*": { requireMention: true }, // default for all groups
|
||||
"iMessage;-;chat123": { requireMention: false }, // override for specific group
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Command gating
|
||||
|
||||
- Control commands (e.g., `/config`, `/model`) require authorization.
|
||||
- Uses `allowFrom` and `groupAllowFrom` to determine command authorization.
|
||||
- Authorized senders can run control commands even without mentioning in groups.
|
||||
|
||||
## ACP conversation bindings
|
||||
|
||||
BlueBubbles chats can be turned into durable ACP workspaces without changing the transport layer.
|
||||
|
||||
Fast operator flow:
|
||||
|
||||
- Run `/acp spawn codex --bind here` inside the DM or allowed group chat.
|
||||
- Future messages in that same BlueBubbles conversation route to the spawned ACP session.
|
||||
- `/new` and `/reset` reset the same bound ACP session in place.
|
||||
- `/acp close` closes the ACP session and removes the binding.
|
||||
|
||||
Configured persistent bindings are also supported through top-level `bindings[]` entries with `type: "acp"` and `match.channel: "bluebubbles"`.
|
||||
|
||||
`match.peer.id` can use any supported BlueBubbles target form:
|
||||
|
||||
- normalized DM handle such as `+15555550123` or `user@example.com`
|
||||
- `chat_id:<id>`
|
||||
- `chat_guid:<guid>`
|
||||
- `chat_identifier:<identifier>`
|
||||
|
||||
For stable group bindings, prefer `chat_id:*` or `chat_identifier:*`.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "codex",
|
||||
runtime: {
|
||||
type: "acp",
|
||||
acp: { agent: "codex", backend: "acpx", mode: "persistent" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
type: "acp",
|
||||
agentId: "codex",
|
||||
match: {
|
||||
channel: "bluebubbles",
|
||||
accountId: "default",
|
||||
peer: { kind: "dm", id: "+15555550123" },
|
||||
},
|
||||
acp: { label: "codex-imessage" },
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
See [ACP Agents](/tools/acp-agents) for shared ACP binding behavior.
|
||||
|
||||
## Typing + read receipts
|
||||
|
||||
- **Typing indicators**: Sent automatically before and during response generation.
|
||||
- **Read receipts**: Controlled by `channels.bluebubbles.sendReadReceipts` (default: `true`).
|
||||
- **Typing indicators**: OpenClaw sends typing start events; BlueBubbles clears typing automatically on send or timeout (manual stop via DELETE is unreliable).
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
sendReadReceipts: false, // disable read receipts
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced actions
|
||||
|
||||
BlueBubbles supports advanced message actions when enabled in config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
actions: {
|
||||
reactions: true, // tapbacks (default: true)
|
||||
edit: true, // edit sent messages (macOS 13+, broken on macOS 26 Tahoe)
|
||||
unsend: true, // unsend messages (macOS 13+)
|
||||
reply: true, // reply threading by message GUID
|
||||
sendWithEffect: true, // message effects (slam, loud, etc.)
|
||||
renameGroup: true, // rename group chats
|
||||
setGroupIcon: true, // set group chat icon/photo (flaky on macOS 26 Tahoe)
|
||||
addParticipant: true, // add participants to groups
|
||||
removeParticipant: true, // remove participants from groups
|
||||
leaveGroup: true, // leave group chats
|
||||
sendAttachment: true, // send attachments/media
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Available actions:
|
||||
|
||||
- **react**: Add/remove tapback reactions (`messageId`, `emoji`, `remove`)
|
||||
- **edit**: Edit a sent message (`messageId`, `text`)
|
||||
- **unsend**: Unsend a message (`messageId`)
|
||||
- **reply**: Reply to a specific message (`messageId`, `text`, `to`)
|
||||
- **sendWithEffect**: Send with iMessage effect (`text`, `to`, `effectId`)
|
||||
- **renameGroup**: Rename a group chat (`chatGuid`, `displayName`)
|
||||
- **setGroupIcon**: Set a group chat's icon/photo (`chatGuid`, `media`) — flaky on macOS 26 Tahoe (API may return success but the icon does not sync).
|
||||
- **addParticipant**: Add someone to a group (`chatGuid`, `address`)
|
||||
- **removeParticipant**: Remove someone from a group (`chatGuid`, `address`)
|
||||
- **leaveGroup**: Leave a group chat (`chatGuid`)
|
||||
- **upload-file**: Send media/files (`to`, `buffer`, `filename`, `asVoice`)
|
||||
- Voice memos: set `asVoice: true` with **MP3** or **CAF** audio to send as an iMessage voice message. BlueBubbles converts MP3 → CAF when sending voice memos.
|
||||
- Legacy alias: `sendAttachment` still works, but `upload-file` is the canonical action name.
|
||||
|
||||
### Message IDs (short vs full)
|
||||
|
||||
OpenClaw may surface _short_ message IDs (e.g., `1`, `2`) to save tokens.
|
||||
|
||||
- `MessageSid` / `ReplyToId` can be short IDs.
|
||||
- `MessageSidFull` / `ReplyToIdFull` contain the provider full IDs.
|
||||
- Short IDs are in-memory; they can expire on restart or cache eviction.
|
||||
- Actions accept short or full `messageId`, but short IDs will error if no longer available.
|
||||
|
||||
Use full IDs for durable automations and storage:
|
||||
|
||||
- Templates: `{{MessageSidFull}}`, `{{ReplyToIdFull}}`
|
||||
- Context: `MessageSidFull` / `ReplyToIdFull` in inbound payloads
|
||||
|
||||
See [Configuration](/gateway/configuration) for template variables.
|
||||
|
||||
## Block streaming
|
||||
|
||||
Control whether responses are sent as a single message or streamed in blocks:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
blockStreaming: true, // enable block streaming (off by default)
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Media + limits
|
||||
|
||||
- Inbound attachments are downloaded and stored in the media cache.
|
||||
- Media cap via `channels.bluebubbles.mediaMaxMb` for inbound and outbound media (default: 8 MB).
|
||||
- Outbound text is chunked to `channels.bluebubbles.textChunkLimit` (default: 4000 chars).
|
||||
|
||||
## Configuration reference
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
|
||||
- `channels.bluebubbles.enabled`: Enable/disable the channel.
|
||||
- `channels.bluebubbles.serverUrl`: BlueBubbles REST API base URL.
|
||||
- `channels.bluebubbles.password`: API password.
|
||||
- `channels.bluebubbles.webhookPath`: Webhook endpoint path (default: `/bluebubbles-webhook`).
|
||||
- `channels.bluebubbles.dmPolicy`: `pairing | allowlist | open | disabled` (default: `pairing`).
|
||||
- `channels.bluebubbles.allowFrom`: DM allowlist (handles, emails, E.164 numbers, `chat_id:*`, `chat_guid:*`).
|
||||
- `channels.bluebubbles.groupPolicy`: `open | allowlist | disabled` (default: `allowlist`).
|
||||
- `channels.bluebubbles.groupAllowFrom`: Group sender allowlist.
|
||||
- `channels.bluebubbles.enrichGroupParticipantsFromContacts`: On macOS, optionally enrich unnamed group participants from local Contacts after gating passes. Default: `false`.
|
||||
- `channels.bluebubbles.groups`: Per-group config (`requireMention`, etc.).
|
||||
- `channels.bluebubbles.sendReadReceipts`: Send read receipts (default: `true`).
|
||||
- `channels.bluebubbles.blockStreaming`: Enable block streaming (default: `false`; required for streaming replies).
|
||||
- `channels.bluebubbles.textChunkLimit`: Outbound chunk size in chars (default: 4000).
|
||||
- `channels.bluebubbles.chunkMode`: `length` (default) splits only when exceeding `textChunkLimit`; `newline` splits on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.bluebubbles.mediaMaxMb`: Inbound/outbound media cap in MB (default: 8).
|
||||
- `channels.bluebubbles.mediaLocalRoots`: Explicit allowlist of absolute local directories permitted for outbound local media paths. Local path sends are denied by default unless this is configured. Per-account override: `channels.bluebubbles.accounts.<accountId>.mediaLocalRoots`.
|
||||
- `channels.bluebubbles.historyLimit`: Max group messages for context (0 disables).
|
||||
- `channels.bluebubbles.dmHistoryLimit`: DM history limit.
|
||||
- `channels.bluebubbles.actions`: Enable/disable specific actions.
|
||||
- `channels.bluebubbles.accounts`: Multi-account configuration.
|
||||
|
||||
Related global options:
|
||||
|
||||
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`).
|
||||
- `messages.responsePrefix`.
|
||||
|
||||
## Addressing / delivery targets
|
||||
|
||||
Prefer `chat_guid` for stable routing:
|
||||
|
||||
- `chat_guid:iMessage;-;+15555550123` (preferred for groups)
|
||||
- `chat_id:123`
|
||||
- `chat_identifier:...`
|
||||
- Direct handles: `+15555550123`, `user@example.com`
|
||||
- If a direct handle does not have an existing DM chat, OpenClaw will create one via `POST /api/v1/chat/new`. This requires the BlueBubbles Private API to be enabled.
|
||||
|
||||
## Security
|
||||
|
||||
- Webhook requests are authenticated by comparing `guid`/`password` query params or headers against `channels.bluebubbles.password`.
|
||||
- Keep the API password and webhook endpoint secret (treat them like credentials).
|
||||
- There is no localhost bypass for BlueBubbles webhook auth. If you proxy webhook traffic, keep the BlueBubbles password on the request end-to-end. `gateway.trustedProxies` does not replace `channels.bluebubbles.password` here. See [Gateway security](/gateway/security#reverse-proxy-configuration).
|
||||
- Enable HTTPS + firewall rules on the BlueBubbles server if exposing it outside your LAN.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If typing/read events stop working, check the BlueBubbles webhook logs and verify the gateway path matches `channels.bluebubbles.webhookPath`.
|
||||
- Pairing codes expire after one hour; use `openclaw pairing list bluebubbles` and `openclaw pairing approve bluebubbles <code>`.
|
||||
- Reactions require the BlueBubbles private API (`POST /api/v1/message/react`); ensure the server version exposes it.
|
||||
- Edit/unsend require macOS 13+ and a compatible BlueBubbles server version. On macOS 26 (Tahoe), edit is currently broken due to private API changes.
|
||||
- Group icon updates can be flaky on macOS 26 (Tahoe): the API may return success but the new icon does not sync.
|
||||
- OpenClaw auto-hides known-broken actions based on the BlueBubbles server's macOS version. If edit still appears on macOS 26 (Tahoe), disable it manually with `channels.bluebubbles.actions.edit=false`.
|
||||
- For status/health info: `openclaw status --all` or `openclaw status --deep`.
|
||||
|
||||
For general channel workflow reference, see [Channels](/channels) and the [Plugins](/tools/plugin) guide.
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
442
openclaw/docs/channels/broadcast-groups.md
Normal file
442
openclaw/docs/channels/broadcast-groups.md
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
---
|
||||
summary: "Broadcast a WhatsApp message to multiple agents"
|
||||
read_when:
|
||||
- Configuring broadcast groups
|
||||
- Debugging multi-agent replies in WhatsApp
|
||||
status: experimental
|
||||
title: "Broadcast Groups"
|
||||
---
|
||||
|
||||
# Broadcast Groups
|
||||
|
||||
**Status:** Experimental
|
||||
**Version:** Added in 2026.1.9
|
||||
|
||||
## Overview
|
||||
|
||||
Broadcast Groups enable multiple agents to process and respond to the same message simultaneously. This allows you to create specialized agent teams that work together in a single WhatsApp group or DM — all using one phone number.
|
||||
|
||||
Current scope: **WhatsApp only** (web channel).
|
||||
|
||||
Broadcast groups are evaluated after channel allowlists and group activation rules. In WhatsApp groups, this means broadcasts happen when OpenClaw would normally reply (for example: on mention, depending on your group settings).
|
||||
|
||||
## Use Cases
|
||||
|
||||
### 1. Specialized Agent Teams
|
||||
|
||||
Deploy multiple agents with atomic, focused responsibilities:
|
||||
|
||||
```
|
||||
Group: "Development Team"
|
||||
Agents:
|
||||
- CodeReviewer (reviews code snippets)
|
||||
- DocumentationBot (generates docs)
|
||||
- SecurityAuditor (checks for vulnerabilities)
|
||||
- TestGenerator (suggests test cases)
|
||||
```
|
||||
|
||||
Each agent processes the same message and provides its specialized perspective.
|
||||
|
||||
### 2. Multi-Language Support
|
||||
|
||||
```
|
||||
Group: "International Support"
|
||||
Agents:
|
||||
- Agent_EN (responds in English)
|
||||
- Agent_DE (responds in German)
|
||||
- Agent_ES (responds in Spanish)
|
||||
```
|
||||
|
||||
### 3. Quality Assurance Workflows
|
||||
|
||||
```
|
||||
Group: "Customer Support"
|
||||
Agents:
|
||||
- SupportAgent (provides answer)
|
||||
- QAAgent (reviews quality, only responds if issues found)
|
||||
```
|
||||
|
||||
### 4. Task Automation
|
||||
|
||||
```
|
||||
Group: "Project Management"
|
||||
Agents:
|
||||
- TaskTracker (updates task database)
|
||||
- TimeLogger (logs time spent)
|
||||
- ReportGenerator (creates summaries)
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Basic Setup
|
||||
|
||||
Add a top-level `broadcast` section (next to `bindings`). Keys are WhatsApp peer ids:
|
||||
|
||||
- group chats: group JID (e.g. `120363403215116621@g.us`)
|
||||
- DMs: E.164 phone number (e.g. `+15551234567`)
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"120363403215116621@g.us": ["alfred", "baerbel", "assistant3"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Result:** When OpenClaw would reply in this chat, it will run all three agents.
|
||||
|
||||
### Processing Strategy
|
||||
|
||||
Control how agents process messages:
|
||||
|
||||
#### Parallel (Default)
|
||||
|
||||
All agents process simultaneously:
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"strategy": "parallel",
|
||||
"120363403215116621@g.us": ["alfred", "baerbel"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Sequential
|
||||
|
||||
Agents process in order (one waits for previous to finish):
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"strategy": "sequential",
|
||||
"120363403215116621@g.us": ["alfred", "baerbel"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Complete Example
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"list": [
|
||||
{
|
||||
"id": "code-reviewer",
|
||||
"name": "Code Reviewer",
|
||||
"workspace": "/path/to/code-reviewer",
|
||||
"sandbox": { "mode": "all" }
|
||||
},
|
||||
{
|
||||
"id": "security-auditor",
|
||||
"name": "Security Auditor",
|
||||
"workspace": "/path/to/security-auditor",
|
||||
"sandbox": { "mode": "all" }
|
||||
},
|
||||
{
|
||||
"id": "docs-generator",
|
||||
"name": "Documentation Generator",
|
||||
"workspace": "/path/to/docs-generator",
|
||||
"sandbox": { "mode": "all" }
|
||||
}
|
||||
]
|
||||
},
|
||||
"broadcast": {
|
||||
"strategy": "parallel",
|
||||
"120363403215116621@g.us": ["code-reviewer", "security-auditor", "docs-generator"],
|
||||
"120363424282127706@g.us": ["support-en", "support-de"],
|
||||
"+15555550123": ["assistant", "logger"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### Message Flow
|
||||
|
||||
1. **Incoming message** arrives in a WhatsApp group
|
||||
2. **Broadcast check**: System checks if peer ID is in `broadcast`
|
||||
3. **If in broadcast list**:
|
||||
- All listed agents process the message
|
||||
- Each agent has its own session key and isolated context
|
||||
- Agents process in parallel (default) or sequentially
|
||||
4. **If not in broadcast list**:
|
||||
- Normal routing applies (first matching binding)
|
||||
|
||||
Note: broadcast groups do not bypass channel allowlists or group activation rules (mentions/commands/etc). They only change _which agents run_ when a message is eligible for processing.
|
||||
|
||||
### Session Isolation
|
||||
|
||||
Each agent in a broadcast group maintains completely separate:
|
||||
|
||||
- **Session keys** (`agent:alfred:whatsapp:group:120363...` vs `agent:baerbel:whatsapp:group:120363...`)
|
||||
- **Conversation history** (agent doesn't see other agents' messages)
|
||||
- **Workspace** (separate sandboxes if configured)
|
||||
- **Tool access** (different allow/deny lists)
|
||||
- **Memory/context** (separate IDENTITY.md, SOUL.md, etc.)
|
||||
- **Group context buffer** (recent group messages used for context) is shared per peer, so all broadcast agents see the same context when triggered
|
||||
|
||||
This allows each agent to have:
|
||||
|
||||
- Different personalities
|
||||
- Different tool access (e.g., read-only vs. read-write)
|
||||
- Different models (e.g., opus vs. sonnet)
|
||||
- Different skills installed
|
||||
|
||||
### Example: Isolated Sessions
|
||||
|
||||
In group `120363403215116621@g.us` with agents `["alfred", "baerbel"]`:
|
||||
|
||||
**Alfred's context:**
|
||||
|
||||
```
|
||||
Session: agent:alfred:whatsapp:group:120363403215116621@g.us
|
||||
History: [user message, alfred's previous responses]
|
||||
Workspace: /Users/user/openclaw-alfred/
|
||||
Tools: read, write, exec
|
||||
```
|
||||
|
||||
**Bärbel's context:**
|
||||
|
||||
```
|
||||
Session: agent:baerbel:whatsapp:group:120363403215116621@g.us
|
||||
History: [user message, baerbel's previous responses]
|
||||
Workspace: /Users/user/openclaw-baerbel/
|
||||
Tools: read only
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Keep Agents Focused
|
||||
|
||||
Design each agent with a single, clear responsibility:
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"DEV_GROUP": ["formatter", "linter", "tester"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
✅ **Good:** Each agent has one job
|
||||
❌ **Bad:** One generic "dev-helper" agent
|
||||
|
||||
### 2. Use Descriptive Names
|
||||
|
||||
Make it clear what each agent does:
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"security-scanner": { "name": "Security Scanner" },
|
||||
"code-formatter": { "name": "Code Formatter" },
|
||||
"test-generator": { "name": "Test Generator" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Configure Different Tool Access
|
||||
|
||||
Give agents only the tools they need:
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"reviewer": {
|
||||
"tools": { "allow": ["read", "exec"] } // Read-only
|
||||
},
|
||||
"fixer": {
|
||||
"tools": { "allow": ["read", "write", "edit", "exec"] } // Read-write
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Monitor Performance
|
||||
|
||||
With many agents, consider:
|
||||
|
||||
- Using `"strategy": "parallel"` (default) for speed
|
||||
- Limiting broadcast groups to 5-10 agents
|
||||
- Using faster models for simpler agents
|
||||
|
||||
### 5. Handle Failures Gracefully
|
||||
|
||||
Agents fail independently. One agent's error doesn't block others:
|
||||
|
||||
```
|
||||
Message → [Agent A ✓, Agent B ✗ error, Agent C ✓]
|
||||
Result: Agent A and C respond, Agent B logs error
|
||||
```
|
||||
|
||||
## Compatibility
|
||||
|
||||
### Providers
|
||||
|
||||
Broadcast groups currently work with:
|
||||
|
||||
- ✅ WhatsApp (implemented)
|
||||
- 🚧 Telegram (planned)
|
||||
- 🚧 Discord (planned)
|
||||
- 🚧 Slack (planned)
|
||||
|
||||
### Routing
|
||||
|
||||
Broadcast groups work alongside existing routing:
|
||||
|
||||
```json
|
||||
{
|
||||
"bindings": [
|
||||
{
|
||||
"match": { "channel": "whatsapp", "peer": { "kind": "group", "id": "GROUP_A" } },
|
||||
"agentId": "alfred"
|
||||
}
|
||||
],
|
||||
"broadcast": {
|
||||
"GROUP_B": ["agent1", "agent2"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `GROUP_A`: Only alfred responds (normal routing)
|
||||
- `GROUP_B`: agent1 AND agent2 respond (broadcast)
|
||||
|
||||
**Precedence:** `broadcast` takes priority over `bindings`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Agents Not Responding
|
||||
|
||||
**Check:**
|
||||
|
||||
1. Agent IDs exist in `agents.list`
|
||||
2. Peer ID format is correct (e.g., `120363403215116621@g.us`)
|
||||
3. Agents are not in deny lists
|
||||
|
||||
**Debug:**
|
||||
|
||||
```bash
|
||||
tail -f ~/.openclaw/logs/gateway.log | grep broadcast
|
||||
```
|
||||
|
||||
### Only One Agent Responding
|
||||
|
||||
**Cause:** Peer ID might be in `bindings` but not `broadcast`.
|
||||
|
||||
**Fix:** Add to broadcast config or remove from bindings.
|
||||
|
||||
### Performance Issues
|
||||
|
||||
**If slow with many agents:**
|
||||
|
||||
- Reduce number of agents per group
|
||||
- Use lighter models (sonnet instead of opus)
|
||||
- Check sandbox startup time
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Code Review Team
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"strategy": "parallel",
|
||||
"120363403215116621@g.us": [
|
||||
"code-formatter",
|
||||
"security-scanner",
|
||||
"test-coverage",
|
||||
"docs-checker"
|
||||
]
|
||||
},
|
||||
"agents": {
|
||||
"list": [
|
||||
{
|
||||
"id": "code-formatter",
|
||||
"workspace": "~/agents/formatter",
|
||||
"tools": { "allow": ["read", "write"] }
|
||||
},
|
||||
{
|
||||
"id": "security-scanner",
|
||||
"workspace": "~/agents/security",
|
||||
"tools": { "allow": ["read", "exec"] }
|
||||
},
|
||||
{
|
||||
"id": "test-coverage",
|
||||
"workspace": "~/agents/testing",
|
||||
"tools": { "allow": ["read", "exec"] }
|
||||
},
|
||||
{ "id": "docs-checker", "workspace": "~/agents/docs", "tools": { "allow": ["read"] } }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**User sends:** Code snippet
|
||||
**Responses:**
|
||||
|
||||
- code-formatter: "Fixed indentation and added type hints"
|
||||
- security-scanner: "⚠️ SQL injection vulnerability in line 12"
|
||||
- test-coverage: "Coverage is 45%, missing tests for error cases"
|
||||
- docs-checker: "Missing docstring for function `process_data`"
|
||||
|
||||
### Example 2: Multi-Language Support
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"strategy": "sequential",
|
||||
"+15555550123": ["detect-language", "translator-en", "translator-de"]
|
||||
},
|
||||
"agents": {
|
||||
"list": [
|
||||
{ "id": "detect-language", "workspace": "~/agents/lang-detect" },
|
||||
{ "id": "translator-en", "workspace": "~/agents/translate-en" },
|
||||
{ "id": "translator-de", "workspace": "~/agents/translate-de" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Config Schema
|
||||
|
||||
```typescript
|
||||
interface OpenClawConfig {
|
||||
broadcast?: {
|
||||
strategy?: "parallel" | "sequential";
|
||||
[peerId: string]: string[];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
- `strategy` (optional): How to process agents
|
||||
- `"parallel"` (default): All agents process simultaneously
|
||||
- `"sequential"`: Agents process in array order
|
||||
- `[peerId]`: WhatsApp group JID, E.164 number, or other peer ID
|
||||
- Value: Array of agent IDs that should process messages
|
||||
|
||||
## Limitations
|
||||
|
||||
1. **Max agents:** No hard limit, but 10+ agents may be slow
|
||||
2. **Shared context:** Agents don't see each other's responses (by design)
|
||||
3. **Message ordering:** Parallel responses may arrive in any order
|
||||
4. **Rate limits:** All agents count toward WhatsApp rate limits
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Planned features:
|
||||
|
||||
- [ ] Shared context mode (agents see each other's responses)
|
||||
- [ ] Agent coordination (agents can signal each other)
|
||||
- [ ] Dynamic agent selection (choose agents based on message content)
|
||||
- [ ] Agent priorities (some agents respond before others)
|
||||
|
||||
## See Also
|
||||
|
||||
- [Multi-Agent Configuration](/tools/multi-agent-sandbox-tools)
|
||||
- [Routing Configuration](/channels/channel-routing)
|
||||
- [Session Management](/concepts/session)
|
||||
139
openclaw/docs/channels/channel-routing.md
Normal file
139
openclaw/docs/channels/channel-routing.md
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
---
|
||||
summary: "Routing rules per channel (WhatsApp, Telegram, Discord, Slack) and shared context"
|
||||
read_when:
|
||||
- Changing channel routing or inbox behavior
|
||||
title: "Channel Routing"
|
||||
---
|
||||
|
||||
# Channels & routing
|
||||
|
||||
OpenClaw routes replies **back to the channel where a message came from**. The
|
||||
model does not choose a channel; routing is deterministic and controlled by the
|
||||
host configuration.
|
||||
|
||||
## Key terms
|
||||
|
||||
- **Channel**: `telegram`, `whatsapp`, `discord`, `irc`, `googlechat`, `slack`, `signal`, `imessage`, `line`, plus extension channels. `webchat` is the internal WebChat UI channel and is not a configurable outbound channel.
|
||||
- **AccountId**: per‑channel account instance (when supported).
|
||||
- Optional channel default account: `channels.<channel>.defaultAccount` chooses
|
||||
which account is used when an outbound path does not specify `accountId`.
|
||||
- In multi-account setups, set an explicit default (`defaultAccount` or `accounts.default`) when two or more accounts are configured. Without it, fallback routing may pick the first normalized account ID.
|
||||
- **AgentId**: an isolated workspace + session store (“brain”).
|
||||
- **SessionKey**: the bucket key used to store context and control concurrency.
|
||||
|
||||
## Session key shapes (examples)
|
||||
|
||||
Direct messages collapse to the agent’s **main** session:
|
||||
|
||||
- `agent:<agentId>:<mainKey>` (default: `agent:main:main`)
|
||||
|
||||
Groups and channels remain isolated per channel:
|
||||
|
||||
- Groups: `agent:<agentId>:<channel>:group:<id>`
|
||||
- Channels/rooms: `agent:<agentId>:<channel>:channel:<id>`
|
||||
|
||||
Threads:
|
||||
|
||||
- Slack/Discord threads append `:thread:<threadId>` to the base key.
|
||||
- Telegram forum topics embed `:topic:<topicId>` in the group key.
|
||||
|
||||
Examples:
|
||||
|
||||
- `agent:main:telegram:group:-1001234567890:topic:42`
|
||||
- `agent:main:discord:channel:123456:thread:987654`
|
||||
|
||||
## Main DM route pinning
|
||||
|
||||
When `session.dmScope` is `main`, direct messages may share one main session.
|
||||
To prevent the session’s `lastRoute` from being overwritten by non-owner DMs,
|
||||
OpenClaw infers a pinned owner from `allowFrom` when all of these are true:
|
||||
|
||||
- `allowFrom` has exactly one non-wildcard entry.
|
||||
- The entry can be normalized to a concrete sender ID for that channel.
|
||||
- The inbound DM sender does not match that pinned owner.
|
||||
|
||||
In that mismatch case, OpenClaw still records inbound session metadata, but it
|
||||
skips updating the main session `lastRoute`.
|
||||
|
||||
## Routing rules (how an agent is chosen)
|
||||
|
||||
Routing picks **one agent** for each inbound message:
|
||||
|
||||
1. **Exact peer match** (`bindings` with `peer.kind` + `peer.id`).
|
||||
2. **Parent peer match** (thread inheritance).
|
||||
3. **Guild + roles match** (Discord) via `guildId` + `roles`.
|
||||
4. **Guild match** (Discord) via `guildId`.
|
||||
5. **Team match** (Slack) via `teamId`.
|
||||
6. **Account match** (`accountId` on the channel).
|
||||
7. **Channel match** (any account on that channel, `accountId: "*"`).
|
||||
8. **Default agent** (`agents.list[].default`, else first list entry, fallback to `main`).
|
||||
|
||||
When a binding includes multiple match fields (`peer`, `guildId`, `teamId`, `roles`), **all provided fields must match** for that binding to apply.
|
||||
|
||||
The matched agent determines which workspace and session store are used.
|
||||
|
||||
## Broadcast groups (run multiple agents)
|
||||
|
||||
Broadcast groups let you run **multiple agents** for the same peer **when OpenClaw would normally reply** (for example: in WhatsApp groups, after mention/activation gating).
|
||||
|
||||
Config:
|
||||
|
||||
```json5
|
||||
{
|
||||
broadcast: {
|
||||
strategy: "parallel",
|
||||
"120363403215116621@g.us": ["alfred", "baerbel"],
|
||||
"+15555550123": ["support", "logger"],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
See: [Broadcast Groups](/channels/broadcast-groups).
|
||||
|
||||
## Config overview
|
||||
|
||||
- `agents.list`: named agent definitions (workspace, model, etc.).
|
||||
- `bindings`: map inbound channels/accounts/peers to agents.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [{ id: "support", name: "Support", workspace: "~/.openclaw/workspace-support" }],
|
||||
},
|
||||
bindings: [
|
||||
{ match: { channel: "slack", teamId: "T123" }, agentId: "support" },
|
||||
{ match: { channel: "telegram", peer: { kind: "group", id: "-100123" } }, agentId: "support" },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
## Session storage
|
||||
|
||||
Session stores live under the state directory (default `~/.openclaw`):
|
||||
|
||||
- `~/.openclaw/agents/<agentId>/sessions/sessions.json`
|
||||
- JSONL transcripts live alongside the store
|
||||
|
||||
You can override the store path via `session.store` and `{agentId}` templating.
|
||||
|
||||
Gateway and ACP session discovery also scans disk-backed agent stores under the
|
||||
default `agents/` root and under templated `session.store` roots. Discovered
|
||||
stores must stay inside that resolved agent root and use a regular
|
||||
`sessions.json` file. Symlinks and out-of-root paths are ignored.
|
||||
|
||||
## WebChat behavior
|
||||
|
||||
WebChat attaches to the **selected agent** and defaults to the agent’s main
|
||||
session. Because of this, WebChat lets you see cross‑channel context for that
|
||||
agent in one place.
|
||||
|
||||
## Reply context
|
||||
|
||||
Inbound replies include:
|
||||
|
||||
- `ReplyToId`, `ReplyToBody`, and `ReplyToSender` when available.
|
||||
- Quoted context is appended to `Body` as a `[Replying to ...]` block.
|
||||
|
||||
This is consistent across channels.
|
||||
1262
openclaw/docs/channels/discord.md
Normal file
1262
openclaw/docs/channels/discord.md
Normal file
File diff suppressed because it is too large
Load diff
439
openclaw/docs/channels/feishu.md
Normal file
439
openclaw/docs/channels/feishu.md
Normal file
|
|
@ -0,0 +1,439 @@
|
|||
---
|
||||
summary: "Feishu bot overview, features, and configuration"
|
||||
read_when:
|
||||
- You want to connect a Feishu/Lark bot
|
||||
- You are configuring the Feishu channel
|
||||
title: Feishu
|
||||
---
|
||||
|
||||
# Feishu / Lark
|
||||
|
||||
Feishu/Lark is an all-in-one collaboration platform where teams chat, share documents, manage calendars, and get work done together.
|
||||
|
||||
**Status:** production-ready for bot DMs + group chats. WebSocket is the default mode; webhook mode is optional.
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
> **Requires OpenClaw 2026.4.10 or above.** Run `openclaw --version` to check. Upgrade with `openclaw update`.
|
||||
|
||||
<Steps>
|
||||
<Step title="Run the channel setup wizard">
|
||||
```bash
|
||||
openclaw channels login --channel feishu
|
||||
```
|
||||
Scan the QR code with your Feishu/Lark mobile app to create a Feishu/Lark bot automatically.
|
||||
</Step>
|
||||
|
||||
<Step title="After setup completes, restart the gateway to apply the changes">
|
||||
```bash
|
||||
openclaw gateway restart
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
---
|
||||
|
||||
## Access control
|
||||
|
||||
### Direct messages
|
||||
|
||||
Configure `dmPolicy` to control who can DM the bot:
|
||||
|
||||
- `"pairing"` — unknown users receive a pairing code; approve via CLI
|
||||
- `"allowlist"` — only users listed in `allowFrom` can chat (default: bot owner only)
|
||||
- `"open"` — allow all users
|
||||
- `"disabled"` — disable all DMs
|
||||
|
||||
**Approve a pairing request:**
|
||||
|
||||
```bash
|
||||
openclaw pairing list feishu
|
||||
openclaw pairing approve feishu <CODE>
|
||||
```
|
||||
|
||||
### Group chats
|
||||
|
||||
**Group policy** (`channels.feishu.groupPolicy`):
|
||||
|
||||
| Value | Behavior |
|
||||
| ------------- | ------------------------------------------ |
|
||||
| `"open"` | Respond to all messages in groups |
|
||||
| `"allowlist"` | Only respond to groups in `groupAllowFrom` |
|
||||
| `"disabled"` | Disable all group messages |
|
||||
|
||||
Default: `allowlist`
|
||||
|
||||
**Mention requirement** (`channels.feishu.requireMention`):
|
||||
|
||||
- `true` — require @mention (default)
|
||||
- `false` — respond without @mention
|
||||
- Per-group override: `channels.feishu.groups.<chat_id>.requireMention`
|
||||
|
||||
---
|
||||
|
||||
## Group configuration examples
|
||||
|
||||
### Allow all groups, no @mention required
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
feishu: {
|
||||
groupPolicy: "open",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Allow all groups, still require @mention
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
feishu: {
|
||||
groupPolicy: "open",
|
||||
requireMention: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Allow specific groups only
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
feishu: {
|
||||
groupPolicy: "allowlist",
|
||||
// Group IDs look like: oc_xxx
|
||||
groupAllowFrom: ["oc_xxx", "oc_yyy"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Restrict senders within a group
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
feishu: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["oc_xxx"],
|
||||
groups: {
|
||||
oc_xxx: {
|
||||
// User open_ids look like: ou_xxx
|
||||
allowFrom: ["ou_user1", "ou_user2"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get group/user IDs
|
||||
|
||||
### Group IDs (`chat_id`, format: `oc_xxx`)
|
||||
|
||||
Open the group in Feishu/Lark, click the menu icon in the top-right corner, and go to **Settings**. The group ID (`chat_id`) is listed on the settings page.
|
||||
|
||||

|
||||
|
||||
### User IDs (`open_id`, format: `ou_xxx`)
|
||||
|
||||
Start the gateway, send a DM to the bot, then check the logs:
|
||||
|
||||
```bash
|
||||
openclaw logs --follow
|
||||
```
|
||||
|
||||
Look for `open_id` in the log output. You can also check pending pairing requests:
|
||||
|
||||
```bash
|
||||
openclaw pairing list feishu
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common commands
|
||||
|
||||
| Command | Description |
|
||||
| --------- | --------------------------- |
|
||||
| `/status` | Show bot status |
|
||||
| `/reset` | Reset the current session |
|
||||
| `/model` | Show or switch the AI model |
|
||||
|
||||
> Feishu/Lark does not support native slash-command menus, so send these as plain text messages.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Bot does not respond in group chats
|
||||
|
||||
1. Ensure the bot is added to the group
|
||||
2. Ensure you @mention the bot (required by default)
|
||||
3. Verify `groupPolicy` is not `"disabled"`
|
||||
4. Check logs: `openclaw logs --follow`
|
||||
|
||||
### Bot does not receive messages
|
||||
|
||||
1. Ensure the bot is published and approved in Feishu Open Platform / Lark Developer
|
||||
2. Ensure event subscription includes `im.message.receive_v1`
|
||||
3. Ensure **persistent connection** (WebSocket) is selected
|
||||
4. Ensure all required permission scopes are granted
|
||||
5. Ensure the gateway is running: `openclaw gateway status`
|
||||
6. Check logs: `openclaw logs --follow`
|
||||
|
||||
### App Secret leaked
|
||||
|
||||
1. Reset the App Secret in Feishu Open Platform / Lark Developer
|
||||
2. Update the value in your config
|
||||
3. Restart the gateway: `openclaw gateway restart`
|
||||
|
||||
---
|
||||
|
||||
## Advanced configuration
|
||||
|
||||
### Multiple accounts
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
feishu: {
|
||||
defaultAccount: "main",
|
||||
accounts: {
|
||||
main: {
|
||||
appId: "cli_xxx",
|
||||
appSecret: "xxx",
|
||||
name: "Primary bot",
|
||||
},
|
||||
backup: {
|
||||
appId: "cli_yyy",
|
||||
appSecret: "yyy",
|
||||
name: "Backup bot",
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
`defaultAccount` controls which account is used when outbound APIs do not specify an `accountId`.
|
||||
|
||||
### Message limits
|
||||
|
||||
- `textChunkLimit` — outbound text chunk size (default: `2000` chars)
|
||||
- `mediaMaxMb` — media upload/download limit (default: `30` MB)
|
||||
|
||||
### Streaming
|
||||
|
||||
Feishu/Lark supports streaming replies via interactive cards. When enabled, the bot updates the card in real time as it generates text.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
feishu: {
|
||||
streaming: true, // enable streaming card output (default: true)
|
||||
blockStreaming: true, // enable block-level streaming (default: true)
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Set `streaming: false` to send the complete reply in one message.
|
||||
|
||||
### Quota optimization
|
||||
|
||||
Reduce the number of Feishu/Lark API calls with two optional flags:
|
||||
|
||||
- `typingIndicator` (default `true`): set `false` to skip typing reaction calls
|
||||
- `resolveSenderNames` (default `true`): set `false` to skip sender profile lookups
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
feishu: {
|
||||
typingIndicator: false,
|
||||
resolveSenderNames: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### ACP sessions
|
||||
|
||||
Feishu/Lark supports ACP for DMs and group thread messages. Feishu/Lark ACP is text-command driven — there are no native slash-command menus, so use `/acp ...` messages directly in the conversation.
|
||||
|
||||
#### Persistent ACP binding
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "codex",
|
||||
runtime: {
|
||||
type: "acp",
|
||||
acp: {
|
||||
agent: "codex",
|
||||
backend: "acpx",
|
||||
mode: "persistent",
|
||||
cwd: "/workspace/openclaw",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
type: "acp",
|
||||
agentId: "codex",
|
||||
match: {
|
||||
channel: "feishu",
|
||||
accountId: "default",
|
||||
peer: { kind: "direct", id: "ou_1234567890" },
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "acp",
|
||||
agentId: "codex",
|
||||
match: {
|
||||
channel: "feishu",
|
||||
accountId: "default",
|
||||
peer: { kind: "group", id: "oc_group_chat:topic:om_topic_root" },
|
||||
},
|
||||
acp: { label: "codex-feishu-topic" },
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
#### Spawn ACP from chat
|
||||
|
||||
In a Feishu/Lark DM or thread:
|
||||
|
||||
```text
|
||||
/acp spawn codex --thread here
|
||||
```
|
||||
|
||||
`--thread here` works for DMs and Feishu/Lark thread messages. Follow-up messages in the bound conversation route directly to that ACP session.
|
||||
|
||||
### Multi-agent routing
|
||||
|
||||
Use `bindings` to route Feishu/Lark DMs or groups to different agents.
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "main" },
|
||||
{ id: "agent-a", workspace: "/home/user/agent-a" },
|
||||
{ id: "agent-b", workspace: "/home/user/agent-b" },
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
agentId: "agent-a",
|
||||
match: {
|
||||
channel: "feishu",
|
||||
peer: { kind: "direct", id: "ou_xxx" },
|
||||
},
|
||||
},
|
||||
{
|
||||
agentId: "agent-b",
|
||||
match: {
|
||||
channel: "feishu",
|
||||
peer: { kind: "group", id: "oc_zzz" },
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Routing fields:
|
||||
|
||||
- `match.channel`: `"feishu"`
|
||||
- `match.peer.kind`: `"direct"` (DM) or `"group"` (group chat)
|
||||
- `match.peer.id`: user Open ID (`ou_xxx`) or group ID (`oc_xxx`)
|
||||
|
||||
See [Get group/user IDs](#get-groupuser-ids) for lookup tips.
|
||||
|
||||
---
|
||||
|
||||
## Configuration reference
|
||||
|
||||
Full configuration: [Gateway configuration](/gateway/configuration)
|
||||
|
||||
| Setting | Description | Default |
|
||||
| ------------------------------------------------- | ------------------------------------------ | ---------------- |
|
||||
| `channels.feishu.enabled` | Enable/disable the channel | `true` |
|
||||
| `channels.feishu.domain` | API domain (`feishu` or `lark`) | `feishu` |
|
||||
| `channels.feishu.connectionMode` | Event transport (`websocket` or `webhook`) | `websocket` |
|
||||
| `channels.feishu.defaultAccount` | Default account for outbound routing | `default` |
|
||||
| `channels.feishu.verificationToken` | Required for webhook mode | — |
|
||||
| `channels.feishu.encryptKey` | Required for webhook mode | — |
|
||||
| `channels.feishu.webhookPath` | Webhook route path | `/feishu/events` |
|
||||
| `channels.feishu.webhookHost` | Webhook bind host | `127.0.0.1` |
|
||||
| `channels.feishu.webhookPort` | Webhook bind port | `3000` |
|
||||
| `channels.feishu.accounts.<id>.appId` | App ID | — |
|
||||
| `channels.feishu.accounts.<id>.appSecret` | App Secret | — |
|
||||
| `channels.feishu.accounts.<id>.domain` | Per-account domain override | `feishu` |
|
||||
| `channels.feishu.dmPolicy` | DM policy | `allowlist` |
|
||||
| `channels.feishu.allowFrom` | DM allowlist (open_id list) | [BotOwnerId] |
|
||||
| `channels.feishu.groupPolicy` | Group policy | `allowlist` |
|
||||
| `channels.feishu.groupAllowFrom` | Group allowlist | — |
|
||||
| `channels.feishu.requireMention` | Require @mention in groups | `true` |
|
||||
| `channels.feishu.groups.<chat_id>.requireMention` | Per-group @mention override | inherited |
|
||||
| `channels.feishu.groups.<chat_id>.enabled` | Enable/disable a specific group | `true` |
|
||||
| `channels.feishu.textChunkLimit` | Message chunk size | `2000` |
|
||||
| `channels.feishu.mediaMaxMb` | Media size limit | `30` |
|
||||
| `channels.feishu.streaming` | Streaming card output | `true` |
|
||||
| `channels.feishu.blockStreaming` | Block-level streaming | `true` |
|
||||
| `channels.feishu.typingIndicator` | Send typing reactions | `true` |
|
||||
| `channels.feishu.resolveSenderNames` | Resolve sender display names | `true` |
|
||||
|
||||
---
|
||||
|
||||
## Supported message types
|
||||
|
||||
### Receive
|
||||
|
||||
- ✅ Text
|
||||
- ✅ Rich text (post)
|
||||
- ✅ Images
|
||||
- ✅ Files
|
||||
- ✅ Audio
|
||||
- ✅ Video/media
|
||||
- ✅ Stickers
|
||||
|
||||
### Send
|
||||
|
||||
- ✅ Text
|
||||
- ✅ Images
|
||||
- ✅ Files
|
||||
- ✅ Audio
|
||||
- ✅ Video/media
|
||||
- ✅ Interactive cards (including streaming updates)
|
||||
- ⚠️ Rich text (post-style formatting; doesn't support full Feishu/Lark authoring capabilities)
|
||||
|
||||
### Threads and replies
|
||||
|
||||
- ✅ Inline replies
|
||||
- ✅ Thread replies
|
||||
- ✅ Media replies stay thread-aware when replying to a thread message
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
270
openclaw/docs/channels/googlechat.md
Normal file
270
openclaw/docs/channels/googlechat.md
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
---
|
||||
summary: "Google Chat app support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Google Chat channel features
|
||||
title: "Google Chat"
|
||||
---
|
||||
|
||||
# Google Chat (Chat API)
|
||||
|
||||
Status: ready for DMs + spaces via Google Chat API webhooks (HTTP only).
|
||||
|
||||
## Quick setup (beginner)
|
||||
|
||||
1. Create a Google Cloud project and enable the **Google Chat API**.
|
||||
- Go to: [Google Chat API Credentials](https://console.cloud.google.com/apis/api/chat.googleapis.com/credentials)
|
||||
- Enable the API if it is not already enabled.
|
||||
2. Create a **Service Account**:
|
||||
- Press **Create Credentials** > **Service Account**.
|
||||
- Name it whatever you want (e.g., `openclaw-chat`).
|
||||
- Leave permissions blank (press **Continue**).
|
||||
- Leave principals with access blank (press **Done**).
|
||||
3. Create and download the **JSON Key**:
|
||||
- In the list of service accounts, click on the one you just created.
|
||||
- Go to the **Keys** tab.
|
||||
- Click **Add Key** > **Create new key**.
|
||||
- Select **JSON** and press **Create**.
|
||||
4. Store the downloaded JSON file on your gateway host (e.g., `~/.openclaw/googlechat-service-account.json`).
|
||||
5. Create a Google Chat app in the [Google Cloud Console Chat Configuration](https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat):
|
||||
- Fill in the **Application info**:
|
||||
- **App name**: (e.g. `OpenClaw`)
|
||||
- **Avatar URL**: (e.g. `https://openclaw.ai/logo.png`)
|
||||
- **Description**: (e.g. `Personal AI Assistant`)
|
||||
- Enable **Interactive features**.
|
||||
- Under **Functionality**, check **Join spaces and group conversations**.
|
||||
- Under **Connection settings**, select **HTTP endpoint URL**.
|
||||
- Under **Triggers**, select **Use a common HTTP endpoint URL for all triggers** and set it to your gateway's public URL followed by `/googlechat`.
|
||||
- _Tip: Run `openclaw status` to find your gateway's public URL._
|
||||
- Under **Visibility**, check **Make this Chat app available to specific people and groups in <Your Domain>**.
|
||||
- Enter your email address (e.g. `user@example.com`) in the text box.
|
||||
- Click **Save** at the bottom.
|
||||
6. **Enable the app status**:
|
||||
- After saving, **refresh the page**.
|
||||
- Look for the **App status** section (usually near the top or bottom after saving).
|
||||
- Change the status to **Live - available to users**.
|
||||
- Click **Save** again.
|
||||
7. Configure OpenClaw with the service account path + webhook audience:
|
||||
- Env: `GOOGLE_CHAT_SERVICE_ACCOUNT_FILE=/path/to/service-account.json`
|
||||
- Or config: `channels.googlechat.serviceAccountFile: "/path/to/service-account.json"`.
|
||||
8. Set the webhook audience type + value (matches your Chat app config).
|
||||
9. Start the gateway. Google Chat will POST to your webhook path.
|
||||
|
||||
## Add to Google Chat
|
||||
|
||||
Once the gateway is running and your email is added to the visibility list:
|
||||
|
||||
1. Go to [Google Chat](https://chat.google.com/).
|
||||
2. Click the **+** (plus) icon next to **Direct Messages**.
|
||||
3. In the search bar (where you usually add people), type the **App name** you configured in the Google Cloud Console.
|
||||
- **Note**: The bot will _not_ appear in the "Marketplace" browse list because it is a private app. You must search for it by name.
|
||||
4. Select your bot from the results.
|
||||
5. Click **Add** or **Chat** to start a 1:1 conversation.
|
||||
6. Send "Hello" to trigger the assistant!
|
||||
|
||||
## Public URL (Webhook-only)
|
||||
|
||||
Google Chat webhooks require a public HTTPS endpoint. For security, **only expose the `/googlechat` path** to the internet. Keep the OpenClaw dashboard and other sensitive endpoints on your private network.
|
||||
|
||||
### Option A: Tailscale Funnel (Recommended)
|
||||
|
||||
Use Tailscale Serve for the private dashboard and Funnel for the public webhook path. This keeps `/` private while exposing only `/googlechat`.
|
||||
|
||||
1. **Check what address your gateway is bound to:**
|
||||
|
||||
```bash
|
||||
ss -tlnp | grep 18789
|
||||
```
|
||||
|
||||
Note the IP address (e.g., `127.0.0.1`, `0.0.0.0`, or your Tailscale IP like `100.x.x.x`).
|
||||
|
||||
2. **Expose the dashboard to the tailnet only (port 8443):**
|
||||
|
||||
```bash
|
||||
# If bound to localhost (127.0.0.1 or 0.0.0.0):
|
||||
tailscale serve --bg --https 8443 http://127.0.0.1:18789
|
||||
|
||||
# If bound to Tailscale IP only (e.g., 100.106.161.80):
|
||||
tailscale serve --bg --https 8443 http://100.106.161.80:18789
|
||||
```
|
||||
|
||||
3. **Expose only the webhook path publicly:**
|
||||
|
||||
```bash
|
||||
# If bound to localhost (127.0.0.1 or 0.0.0.0):
|
||||
tailscale funnel --bg --set-path /googlechat http://127.0.0.1:18789/googlechat
|
||||
|
||||
# If bound to Tailscale IP only (e.g., 100.106.161.80):
|
||||
tailscale funnel --bg --set-path /googlechat http://100.106.161.80:18789/googlechat
|
||||
```
|
||||
|
||||
4. **Authorize the node for Funnel access:**
|
||||
If prompted, visit the authorization URL shown in the output to enable Funnel for this node in your tailnet policy.
|
||||
|
||||
5. **Verify the configuration:**
|
||||
|
||||
```bash
|
||||
tailscale serve status
|
||||
tailscale funnel status
|
||||
```
|
||||
|
||||
Your public webhook URL will be:
|
||||
`https://<node-name>.<tailnet>.ts.net/googlechat`
|
||||
|
||||
Your private dashboard stays tailnet-only:
|
||||
`https://<node-name>.<tailnet>.ts.net:8443/`
|
||||
|
||||
Use the public URL (without `:8443`) in the Google Chat app config.
|
||||
|
||||
> Note: This configuration persists across reboots. To remove it later, run `tailscale funnel reset` and `tailscale serve reset`.
|
||||
|
||||
### Option B: Reverse Proxy (Caddy)
|
||||
|
||||
If you use a reverse proxy like Caddy, only proxy the specific path:
|
||||
|
||||
```caddy
|
||||
your-domain.com {
|
||||
reverse_proxy /googlechat* localhost:18789
|
||||
}
|
||||
```
|
||||
|
||||
With this config, any request to `your-domain.com/` will be ignored or returned as 404, while `your-domain.com/googlechat` is safely routed to OpenClaw.
|
||||
|
||||
### Option C: Cloudflare Tunnel
|
||||
|
||||
Configure your tunnel's ingress rules to only route the webhook path:
|
||||
|
||||
- **Path**: `/googlechat` -> `http://localhost:18789/googlechat`
|
||||
- **Default Rule**: HTTP 404 (Not Found)
|
||||
|
||||
## How it works
|
||||
|
||||
1. Google Chat sends webhook POSTs to the gateway. Each request includes an `Authorization: Bearer <token>` header.
|
||||
- OpenClaw verifies bearer auth before reading/parsing full webhook bodies when the header is present.
|
||||
- Google Workspace Add-on requests that carry `authorizationEventObject.systemIdToken` in the body are supported via a stricter pre-auth body budget.
|
||||
2. OpenClaw verifies the token against the configured `audienceType` + `audience`:
|
||||
- `audienceType: "app-url"` → audience is your HTTPS webhook URL.
|
||||
- `audienceType: "project-number"` → audience is the Cloud project number.
|
||||
3. Messages are routed by space:
|
||||
- DMs use session key `agent:<agentId>:googlechat:direct:<spaceId>`.
|
||||
- Spaces use session key `agent:<agentId>:googlechat:group:<spaceId>`.
|
||||
4. DM access is pairing by default. Unknown senders receive a pairing code; approve with:
|
||||
- `openclaw pairing approve googlechat <code>`
|
||||
5. Group spaces require @-mention by default. Use `botUser` if mention detection needs the app’s user name.
|
||||
|
||||
## Targets
|
||||
|
||||
Use these identifiers for delivery and allowlists:
|
||||
|
||||
- Direct messages: `users/<userId>` (recommended).
|
||||
- Raw email `name@example.com` is mutable and only used for direct allowlist matching when `channels.googlechat.dangerouslyAllowNameMatching: true`.
|
||||
- Deprecated: `users/<email>` is treated as a user id, not an email allowlist.
|
||||
- Spaces: `spaces/<spaceId>`.
|
||||
|
||||
## Config highlights
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
googlechat: {
|
||||
enabled: true,
|
||||
serviceAccountFile: "/path/to/service-account.json",
|
||||
// or serviceAccountRef: { source: "file", provider: "filemain", id: "/channels/googlechat/serviceAccount" }
|
||||
audienceType: "app-url",
|
||||
audience: "https://gateway.example.com/googlechat",
|
||||
webhookPath: "/googlechat",
|
||||
botUser: "users/1234567890", // optional; helps mention detection
|
||||
dm: {
|
||||
policy: "pairing",
|
||||
allowFrom: ["users/1234567890"],
|
||||
},
|
||||
groupPolicy: "allowlist",
|
||||
groups: {
|
||||
"spaces/AAAA": {
|
||||
allow: true,
|
||||
requireMention: true,
|
||||
users: ["users/1234567890"],
|
||||
systemPrompt: "Short answers only.",
|
||||
},
|
||||
},
|
||||
actions: { reactions: true },
|
||||
typingIndicator: "message",
|
||||
mediaMaxMb: 20,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Service account credentials can also be passed inline with `serviceAccount` (JSON string).
|
||||
- `serviceAccountRef` is also supported (env/file SecretRef), including per-account refs under `channels.googlechat.accounts.<id>.serviceAccountRef`.
|
||||
- Default webhook path is `/googlechat` if `webhookPath` isn’t set.
|
||||
- `dangerouslyAllowNameMatching` re-enables mutable email principal matching for allowlists (break-glass compatibility mode).
|
||||
- Reactions are available via the `reactions` tool and `channels action` when `actions.reactions` is enabled.
|
||||
- Message actions expose `send` for text and `upload-file` for explicit attachment sends. `upload-file` accepts `media` / `filePath` / `path` plus optional `message`, `filename`, and thread targeting.
|
||||
- `typingIndicator` supports `none`, `message` (default), and `reaction` (reaction requires user OAuth).
|
||||
- Attachments are downloaded through the Chat API and stored in the media pipeline (size capped by `mediaMaxMb`).
|
||||
|
||||
Secrets reference details: [Secrets Management](/gateway/secrets).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### 405 Method Not Allowed
|
||||
|
||||
If Google Cloud Logs Explorer shows errors like:
|
||||
|
||||
```
|
||||
status code: 405, reason phrase: HTTP error response: HTTP/1.1 405 Method Not Allowed
|
||||
```
|
||||
|
||||
This means the webhook handler isn't registered. Common causes:
|
||||
|
||||
1. **Channel not configured**: The `channels.googlechat` section is missing from your config. Verify with:
|
||||
|
||||
```bash
|
||||
openclaw config get channels.googlechat
|
||||
```
|
||||
|
||||
If it returns "Config path not found", add the configuration (see [Config highlights](#config-highlights)).
|
||||
|
||||
2. **Plugin not enabled**: Check plugin status:
|
||||
|
||||
```bash
|
||||
openclaw plugins list | grep googlechat
|
||||
```
|
||||
|
||||
If it shows "disabled", add `plugins.entries.googlechat.enabled: true` to your config.
|
||||
|
||||
3. **Gateway not restarted**: After adding config, restart the gateway:
|
||||
|
||||
```bash
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
Verify the channel is running:
|
||||
|
||||
```bash
|
||||
openclaw channels status
|
||||
# Should show: Google Chat default: enabled, configured, ...
|
||||
```
|
||||
|
||||
### Other issues
|
||||
|
||||
- Check `openclaw channels status --probe` for auth errors or missing audience config.
|
||||
- If no messages arrive, confirm the Chat app's webhook URL + event subscriptions.
|
||||
- If mention gating blocks replies, set `botUser` to the app's user resource name and verify `requireMention`.
|
||||
- Use `openclaw logs --follow` while sending a test message to see if requests reach the gateway.
|
||||
|
||||
Related docs:
|
||||
|
||||
- [Gateway configuration](/gateway/configuration)
|
||||
- [Security](/gateway/security)
|
||||
- [Reactions](/tools/reactions)
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
84
openclaw/docs/channels/group-messages.md
Normal file
84
openclaw/docs/channels/group-messages.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
summary: "Behavior and config for WhatsApp group message handling (mentionPatterns are shared across surfaces)"
|
||||
read_when:
|
||||
- Changing group message rules or mentions
|
||||
title: "Group Messages"
|
||||
---
|
||||
|
||||
# Group messages (WhatsApp web channel)
|
||||
|
||||
Goal: let Clawd sit in WhatsApp groups, wake up only when pinged, and keep that thread separate from the personal DM session.
|
||||
|
||||
Note: `agents.list[].groupChat.mentionPatterns` is now used by Telegram/Discord/Slack/iMessage as well; this doc focuses on WhatsApp-specific behavior. For multi-agent setups, set `agents.list[].groupChat.mentionPatterns` per agent (or use `messages.groupChat.mentionPatterns` as a global fallback).
|
||||
|
||||
## Current implementation (2025-12-03)
|
||||
|
||||
- Activation modes: `mention` (default) or `always`. `mention` requires a ping (real WhatsApp @-mentions via `mentionedJids`, safe regex patterns, or the bot’s E.164 anywhere in the text). `always` wakes the agent on every message but it should reply only when it can add meaningful value; otherwise it returns the exact silent token `NO_REPLY` / `no_reply`. Defaults can be set in config (`channels.whatsapp.groups`) and overridden per group via `/activation`. When `channels.whatsapp.groups` is set, it also acts as a group allowlist (include `"*"` to allow all).
|
||||
- Group policy: `channels.whatsapp.groupPolicy` controls whether group messages are accepted (`open|disabled|allowlist`). `allowlist` uses `channels.whatsapp.groupAllowFrom` (fallback: explicit `channels.whatsapp.allowFrom`). Default is `allowlist` (blocked until you add senders).
|
||||
- Per-group sessions: session keys look like `agent:<agentId>:whatsapp:group:<jid>` so commands such as `/verbose on`, `/trace on`, or `/think high` (sent as standalone messages) are scoped to that group; personal DM state is untouched. Heartbeats are skipped for group threads.
|
||||
- Context injection: **pending-only** group messages (default 50) that _did not_ trigger a run are prefixed under `[Chat messages since your last reply - for context]`, with the triggering line under `[Current message - respond to this]`. Messages already in the session are not re-injected.
|
||||
- Sender surfacing: every group batch now ends with `[from: Sender Name (+E164)]` so Pi knows who is speaking.
|
||||
- Ephemeral/view-once: we unwrap those before extracting text/mentions, so pings inside them still trigger.
|
||||
- Group system prompt: on the first turn of a group session (and whenever `/activation` changes the mode) we inject a short blurb into the system prompt like `You are replying inside the WhatsApp group "<subject>". Group members: Alice (+44...), Bob (+43...), … Activation: trigger-only … Address the specific sender noted in the message context.` If metadata isn’t available we still tell the agent it’s a group chat.
|
||||
|
||||
## Config example (WhatsApp)
|
||||
|
||||
Add a `groupChat` block to `~/.openclaw/openclaw.json` so display-name pings work even when WhatsApp strips the visual `@` in the text body:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: {
|
||||
"*": { requireMention: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
groupChat: {
|
||||
historyLimit: 50,
|
||||
mentionPatterns: ["@?openclaw", "\\+?15555550123"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- The regexes are case-insensitive and use the same safe-regex guardrails as other config regex surfaces; invalid patterns and unsafe nested repetition are ignored.
|
||||
- WhatsApp still sends canonical mentions via `mentionedJids` when someone taps the contact, so the number fallback is rarely needed but is a useful safety net.
|
||||
|
||||
### Activation command (owner-only)
|
||||
|
||||
Use the group chat command:
|
||||
|
||||
- `/activation mention`
|
||||
- `/activation always`
|
||||
|
||||
Only the owner number (from `channels.whatsapp.allowFrom`, or the bot’s own E.164 when unset) can change this. Send `/status` as a standalone message in the group to see the current activation mode.
|
||||
|
||||
## How to use
|
||||
|
||||
1. Add your WhatsApp account (the one running OpenClaw) to the group.
|
||||
2. Say `@openclaw …` (or include the number). Only allowlisted senders can trigger it unless you set `groupPolicy: "open"`.
|
||||
3. The agent prompt will include recent group context plus the trailing `[from: …]` marker so it can address the right person.
|
||||
4. Session-level directives (`/verbose on`, `/trace on`, `/think high`, `/new` or `/reset`, `/compact`) apply only to that group’s session; send them as standalone messages so they register. Your personal DM session remains independent.
|
||||
|
||||
## Testing / verification
|
||||
|
||||
- Manual smoke:
|
||||
- Send an `@openclaw` ping in the group and confirm a reply that references the sender name.
|
||||
- Send a second ping and verify the history block is included then cleared on the next turn.
|
||||
- Check gateway logs (run with `--verbose`) to see `inbound web message` entries showing `from: <groupJid>` and the `[from: …]` suffix.
|
||||
|
||||
## Known considerations
|
||||
|
||||
- Heartbeats are intentionally skipped for groups to avoid noisy broadcasts.
|
||||
- Echo suppression uses the combined batch string; if you send identical text twice without mentions, only the first will get a response.
|
||||
- Session store entries will appear as `agent:<agentId>:whatsapp:group:<jid>` in the session store (`~/.openclaw/agents/<agentId>/sessions/sessions.json` by default); a missing entry just means the group hasn’t triggered a run yet.
|
||||
- Typing indicators in groups follow `agents.defaults.typingMode` (default: `message` when unmentioned).
|
||||
413
openclaw/docs/channels/groups.md
Normal file
413
openclaw/docs/channels/groups.md
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
---
|
||||
summary: "Group chat behavior across surfaces (Discord/iMessage/Matrix/Microsoft Teams/Signal/Slack/Telegram/WhatsApp/Zalo)"
|
||||
read_when:
|
||||
- Changing group chat behavior or mention gating
|
||||
title: "Groups"
|
||||
---
|
||||
|
||||
# Groups
|
||||
|
||||
OpenClaw treats group chats consistently across surfaces: Discord, iMessage, Matrix, Microsoft Teams, Signal, Slack, Telegram, WhatsApp, Zalo.
|
||||
|
||||
## Beginner intro (2 minutes)
|
||||
|
||||
OpenClaw “lives” on your own messaging accounts. There is no separate WhatsApp bot user.
|
||||
If **you** are in a group, OpenClaw can see that group and respond there.
|
||||
|
||||
Default behavior:
|
||||
|
||||
- Groups are restricted (`groupPolicy: "allowlist"`).
|
||||
- Replies require a mention unless you explicitly disable mention gating.
|
||||
|
||||
Translation: allowlisted senders can trigger OpenClaw by mentioning it.
|
||||
|
||||
> TL;DR
|
||||
>
|
||||
> - **DM access** is controlled by `*.allowFrom`.
|
||||
> - **Group access** is controlled by `*.groupPolicy` + allowlists (`*.groups`, `*.groupAllowFrom`).
|
||||
> - **Reply triggering** is controlled by mention gating (`requireMention`, `/activation`).
|
||||
|
||||
Quick flow (what happens to a group message):
|
||||
|
||||
```
|
||||
groupPolicy? disabled -> drop
|
||||
groupPolicy? allowlist -> group allowed? no -> drop
|
||||
requireMention? yes -> mentioned? no -> store for context only
|
||||
otherwise -> reply
|
||||
```
|
||||
|
||||
## Context visibility and allowlists
|
||||
|
||||
Two different controls are involved in group safety:
|
||||
|
||||
- **Trigger authorization**: who can trigger the agent (`groupPolicy`, `groups`, `groupAllowFrom`, channel-specific allowlists).
|
||||
- **Context visibility**: what supplemental context is injected into the model (reply text, quotes, thread history, forwarded metadata).
|
||||
|
||||
By default, OpenClaw prioritizes normal chat behavior and keeps context mostly as received. This means allowlists primarily decide who can trigger actions, not a universal redaction boundary for every quoted or historical snippet.
|
||||
|
||||
Current behavior is channel-specific:
|
||||
|
||||
- Some channels already apply sender-based filtering for supplemental context in specific paths (for example Slack thread seeding, Matrix reply/thread lookups).
|
||||
- Other channels still pass quote/reply/forward context through as received.
|
||||
|
||||
Hardening direction (planned):
|
||||
|
||||
- `contextVisibility: "all"` (default) keeps current as-received behavior.
|
||||
- `contextVisibility: "allowlist"` filters supplemental context to allowlisted senders.
|
||||
- `contextVisibility: "allowlist_quote"` is `allowlist` plus one explicit quote/reply exception.
|
||||
|
||||
Until this hardening model is implemented consistently across channels, expect differences by surface.
|
||||
|
||||

|
||||
|
||||
If you want...
|
||||
|
||||
| Goal | What to set |
|
||||
| -------------------------------------------- | ---------------------------------------------------------- |
|
||||
| Allow all groups but only reply on @mentions | `groups: { "*": { requireMention: true } }` |
|
||||
| Disable all group replies | `groupPolicy: "disabled"` |
|
||||
| Only specific groups | `groups: { "<group-id>": { ... } }` (no `"*"` key) |
|
||||
| Only you can trigger in groups | `groupPolicy: "allowlist"`, `groupAllowFrom: ["+1555..."]` |
|
||||
|
||||
## Session keys
|
||||
|
||||
- Group sessions use `agent:<agentId>:<channel>:group:<id>` session keys (rooms/channels use `agent:<agentId>:<channel>:channel:<id>`).
|
||||
- Telegram forum topics add `:topic:<threadId>` to the group id so each topic has its own session.
|
||||
- Direct chats use the main session (or per-sender if configured).
|
||||
- Heartbeats are skipped for group sessions.
|
||||
|
||||
<a id="pattern-personal-dms-public-groups-single-agent"></a>
|
||||
|
||||
## Pattern: personal DMs + public groups (single agent)
|
||||
|
||||
Yes — this works well if your “personal” traffic is **DMs** and your “public” traffic is **groups**.
|
||||
|
||||
Why: in single-agent mode, DMs typically land in the **main** session key (`agent:main:main`), while groups always use **non-main** session keys (`agent:main:<channel>:group:<id>`). If you enable sandboxing with `mode: "non-main"`, those group sessions run in Docker while your main DM session stays on-host.
|
||||
|
||||
This gives you one agent “brain” (shared workspace + memory), but two execution postures:
|
||||
|
||||
- **DMs**: full tools (host)
|
||||
- **Groups**: sandbox + restricted tools (Docker)
|
||||
|
||||
> If you need truly separate workspaces/personas (“personal” and “public” must never mix), use a second agent + bindings. See [Multi-Agent Routing](/concepts/multi-agent).
|
||||
|
||||
Example (DMs on host, groups sandboxed + messaging-only tools):
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
mode: "non-main", // groups/channels are non-main -> sandboxed
|
||||
scope: "session", // strongest isolation (one container per group/channel)
|
||||
workspaceAccess: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
sandbox: {
|
||||
tools: {
|
||||
// If allow is non-empty, everything else is blocked (deny still wins).
|
||||
allow: ["group:messaging", "group:sessions"],
|
||||
deny: ["group:runtime", "group:fs", "group:ui", "nodes", "cron", "gateway"],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Want “groups can only see folder X” instead of “no host access”? Keep `workspaceAccess: "none"` and mount only allowlisted paths into the sandbox:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
mode: "non-main",
|
||||
scope: "session",
|
||||
workspaceAccess: "none",
|
||||
docker: {
|
||||
binds: [
|
||||
// hostPath:containerPath:mode
|
||||
"/home/user/FriendsShared:/data:ro",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Related:
|
||||
|
||||
- Configuration keys and defaults: [Gateway configuration](/gateway/configuration-reference#agentsdefaultssandbox)
|
||||
- Debugging why a tool is blocked: [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated)
|
||||
- Bind mounts details: [Sandboxing](/gateway/sandboxing#custom-bind-mounts)
|
||||
|
||||
## Display labels
|
||||
|
||||
- UI labels use `displayName` when available, formatted as `<channel>:<token>`.
|
||||
- `#room` is reserved for rooms/channels; group chats use `g-<slug>` (lowercase, spaces -> `-`, keep `#@+._-`).
|
||||
|
||||
## Group policy
|
||||
|
||||
Control how group/room messages are handled per channel:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groupPolicy: "disabled", // "open" | "disabled" | "allowlist"
|
||||
groupAllowFrom: ["+15551234567"],
|
||||
},
|
||||
telegram: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["123456789"], // numeric Telegram user id (wizard can resolve @username)
|
||||
},
|
||||
signal: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["+15551234567"],
|
||||
},
|
||||
imessage: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["chat_id:123"],
|
||||
},
|
||||
msteams: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["user@org.com"],
|
||||
},
|
||||
discord: {
|
||||
groupPolicy: "allowlist",
|
||||
guilds: {
|
||||
GUILD_ID: { channels: { help: { allow: true } } },
|
||||
},
|
||||
},
|
||||
slack: {
|
||||
groupPolicy: "allowlist",
|
||||
channels: { "#general": { allow: true } },
|
||||
},
|
||||
matrix: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["@owner:example.org"],
|
||||
groups: {
|
||||
"!roomId:example.org": { enabled: true },
|
||||
"#alias:example.org": { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
| Policy | Behavior |
|
||||
| ------------- | ------------------------------------------------------------ |
|
||||
| `"open"` | Groups bypass allowlists; mention-gating still applies. |
|
||||
| `"disabled"` | Block all group messages entirely. |
|
||||
| `"allowlist"` | Only allow groups/rooms that match the configured allowlist. |
|
||||
|
||||
Notes:
|
||||
|
||||
- `groupPolicy` is separate from mention-gating (which requires @mentions).
|
||||
- WhatsApp/Telegram/Signal/iMessage/Microsoft Teams/Zalo: use `groupAllowFrom` (fallback: explicit `allowFrom`).
|
||||
- DM pairing approvals (`*-allowFrom` store entries) apply to DM access only; group sender authorization stays explicit to group allowlists.
|
||||
- Discord: allowlist uses `channels.discord.guilds.<id>.channels`.
|
||||
- Slack: allowlist uses `channels.slack.channels`.
|
||||
- Matrix: allowlist uses `channels.matrix.groups`. Prefer room IDs or aliases; joined-room name lookup is best-effort, and unresolved names are ignored at runtime. Use `channels.matrix.groupAllowFrom` to restrict senders; per-room `users` allowlists are also supported.
|
||||
- Group DMs are controlled separately (`channels.discord.dm.*`, `channels.slack.dm.*`).
|
||||
- Telegram allowlist can match user IDs (`"123456789"`, `"telegram:123456789"`, `"tg:123456789"`) or usernames (`"@alice"` or `"alice"`); prefixes are case-insensitive.
|
||||
- Default is `groupPolicy: "allowlist"`; if your group allowlist is empty, group messages are blocked.
|
||||
- Runtime safety: when a provider block is completely missing (`channels.<provider>` absent), group policy falls back to a fail-closed mode (typically `allowlist`) instead of inheriting `channels.defaults.groupPolicy`.
|
||||
|
||||
Quick mental model (evaluation order for group messages):
|
||||
|
||||
1. `groupPolicy` (open/disabled/allowlist)
|
||||
2. group allowlists (`*.groups`, `*.groupAllowFrom`, channel-specific allowlist)
|
||||
3. mention gating (`requireMention`, `/activation`)
|
||||
|
||||
## Mention gating (default)
|
||||
|
||||
Group messages require a mention unless overridden per group. Defaults live per subsystem under `*.groups."*"`.
|
||||
|
||||
Replying to a bot message counts as an implicit mention when the channel
|
||||
supports reply metadata. Quoting a bot message can also count as an implicit
|
||||
mention on channels that expose quote metadata. Current built-in cases include
|
||||
Telegram, WhatsApp, Slack, Discord, Microsoft Teams, and ZaloUser.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: {
|
||||
"*": { requireMention: true },
|
||||
"123@g.us": { requireMention: false },
|
||||
},
|
||||
},
|
||||
telegram: {
|
||||
groups: {
|
||||
"*": { requireMention: true },
|
||||
"123456789": { requireMention: false },
|
||||
},
|
||||
},
|
||||
imessage: {
|
||||
groups: {
|
||||
"*": { requireMention: true },
|
||||
"123": { requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
groupChat: {
|
||||
mentionPatterns: ["@openclaw", "openclaw", "\\+15555550123"],
|
||||
historyLimit: 50,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `mentionPatterns` are case-insensitive safe regex patterns; invalid patterns and unsafe nested-repetition forms are ignored.
|
||||
- Surfaces that provide explicit mentions still pass; patterns are a fallback.
|
||||
- Per-agent override: `agents.list[].groupChat.mentionPatterns` (useful when multiple agents share a group).
|
||||
- Mention gating is only enforced when mention detection is possible (native mentions or `mentionPatterns` are configured).
|
||||
- Discord defaults live in `channels.discord.guilds."*"` (overridable per guild/channel).
|
||||
- Group history context is wrapped uniformly across channels and is **pending-only** (messages skipped due to mention gating); use `messages.groupChat.historyLimit` for the global default and `channels.<channel>.historyLimit` (or `channels.<channel>.accounts.*.historyLimit`) for overrides. Set `0` to disable.
|
||||
|
||||
## Group/channel tool restrictions (optional)
|
||||
|
||||
Some channel configs support restricting which tools are available **inside a specific group/room/channel**.
|
||||
|
||||
- `tools`: allow/deny tools for the whole group.
|
||||
- `toolsBySender`: per-sender overrides within the group.
|
||||
Use explicit key prefixes:
|
||||
`id:<senderId>`, `e164:<phone>`, `username:<handle>`, `name:<displayName>`, and `"*"` wildcard.
|
||||
Legacy unprefixed keys are still accepted and matched as `id:` only.
|
||||
|
||||
Resolution order (most specific wins):
|
||||
|
||||
1. group/channel `toolsBySender` match
|
||||
2. group/channel `tools`
|
||||
3. default (`"*"`) `toolsBySender` match
|
||||
4. default (`"*"`) `tools`
|
||||
|
||||
Example (Telegram):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
groups: {
|
||||
"*": { tools: { deny: ["exec"] } },
|
||||
"-1001234567890": {
|
||||
tools: { deny: ["exec", "read", "write"] },
|
||||
toolsBySender: {
|
||||
"id:123456789": { alsoAllow: ["exec"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Group/channel tool restrictions are applied in addition to global/agent tool policy (deny still wins).
|
||||
- Some channels use different nesting for rooms/channels (e.g., Discord `guilds.*.channels.*`, Slack `channels.*`, Microsoft Teams `teams.*.channels.*`).
|
||||
|
||||
## Group allowlists
|
||||
|
||||
When `channels.whatsapp.groups`, `channels.telegram.groups`, or `channels.imessage.groups` is configured, the keys act as a group allowlist. Use `"*"` to allow all groups while still setting default mention behavior.
|
||||
|
||||
Common confusion: DM pairing approval is not the same as group authorization.
|
||||
For channels that support DM pairing, the pairing store unlocks DMs only. Group commands still require explicit group sender authorization from config allowlists such as `groupAllowFrom` or the documented config fallback for that channel.
|
||||
|
||||
Common intents (copy/paste):
|
||||
|
||||
1. Disable all group replies
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: { whatsapp: { groupPolicy: "disabled" } },
|
||||
}
|
||||
```
|
||||
|
||||
2. Allow only specific groups (WhatsApp)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: {
|
||||
"123@g.us": { requireMention: true },
|
||||
"456@g.us": { requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
3. Allow all groups but require mention (explicit)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
4. Only the owner can trigger in groups (WhatsApp)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15551234567"],
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Activation (owner-only)
|
||||
|
||||
Group owners can toggle per-group activation:
|
||||
|
||||
- `/activation mention`
|
||||
- `/activation always`
|
||||
|
||||
Owner is determined by `channels.whatsapp.allowFrom` (or the bot’s self E.164 when unset). Send the command as a standalone message. Other surfaces currently ignore `/activation`.
|
||||
|
||||
## Context fields
|
||||
|
||||
Group inbound payloads set:
|
||||
|
||||
- `ChatType=group`
|
||||
- `GroupSubject` (if known)
|
||||
- `GroupMembers` (if known)
|
||||
- `WasMentioned` (mention gating result)
|
||||
- Telegram forum topics also include `MessageThreadId` and `IsForum`.
|
||||
|
||||
Channel specific notes:
|
||||
|
||||
- BlueBubbles can optionally enrich unnamed macOS group participants from the local Contacts database before populating `GroupMembers`. This is off by default and only runs after normal group gating passes.
|
||||
|
||||
The agent system prompt includes a group intro on the first turn of a new group session. It reminds the model to respond like a human, avoid Markdown tables, minimize empty lines and follow normal chat spacing, and avoid typing literal `\n` sequences.
|
||||
|
||||
## iMessage specifics
|
||||
|
||||
- Prefer `chat_id:<id>` when routing or allowlisting.
|
||||
- List chats: `imsg chats --limit 20`.
|
||||
- Group replies always go back to the same `chat_id`.
|
||||
|
||||
## WhatsApp specifics
|
||||
|
||||
See [Group messages](/channels/group-messages) for WhatsApp-only behavior (history injection, mention handling details).
|
||||
427
openclaw/docs/channels/imessage.md
Normal file
427
openclaw/docs/channels/imessage.md
Normal file
|
|
@ -0,0 +1,427 @@
|
|||
---
|
||||
summary: "Legacy iMessage support via imsg (JSON-RPC over stdio). New setups should use BlueBubbles."
|
||||
read_when:
|
||||
- Setting up iMessage support
|
||||
- Debugging iMessage send/receive
|
||||
title: "iMessage"
|
||||
---
|
||||
|
||||
# iMessage (legacy: imsg)
|
||||
|
||||
<Warning>
|
||||
For new iMessage deployments, use <a href="/channels/bluebubbles">BlueBubbles</a>.
|
||||
|
||||
The `imsg` integration is legacy and may be removed in a future release.
|
||||
</Warning>
|
||||
|
||||
Status: legacy external CLI integration. Gateway spawns `imsg rpc` and communicates over JSON-RPC on stdio (no separate daemon/port).
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="BlueBubbles (recommended)" icon="message-circle" href="/channels/bluebubbles">
|
||||
Preferred iMessage path for new setups.
|
||||
</Card>
|
||||
<Card title="Pairing" icon="link" href="/channels/pairing">
|
||||
iMessage DMs default to pairing mode.
|
||||
</Card>
|
||||
<Card title="Configuration reference" icon="settings" href="/gateway/configuration-reference#imessage">
|
||||
Full iMessage field reference.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Quick setup
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Local Mac (fast path)">
|
||||
<Steps>
|
||||
<Step title="Install and verify imsg">
|
||||
|
||||
```bash
|
||||
brew install steipete/tap/imsg
|
||||
imsg rpc --help
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Configure OpenClaw">
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
imessage: {
|
||||
enabled: true,
|
||||
cliPath: "/usr/local/bin/imsg",
|
||||
dbPath: "/Users/<you>/Library/Messages/chat.db",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Start gateway">
|
||||
|
||||
```bash
|
||||
openclaw gateway
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Approve first DM pairing (default dmPolicy)">
|
||||
|
||||
```bash
|
||||
openclaw pairing list imessage
|
||||
openclaw pairing approve imessage <CODE>
|
||||
```
|
||||
|
||||
Pairing requests expire after 1 hour.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Remote Mac over SSH">
|
||||
OpenClaw only requires a stdio-compatible `cliPath`, so you can point `cliPath` at a wrapper script that SSHes to a remote Mac and runs `imsg`.
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
exec ssh -T gateway-host imsg "$@"
|
||||
```
|
||||
|
||||
Recommended config when attachments are enabled:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
imessage: {
|
||||
enabled: true,
|
||||
cliPath: "~/.openclaw/scripts/imsg-ssh",
|
||||
remoteHost: "user@gateway-host", // used for SCP attachment fetches
|
||||
includeAttachments: true,
|
||||
// Optional: override allowed attachment roots.
|
||||
// Defaults include /Users/*/Library/Messages/Attachments
|
||||
attachmentRoots: ["/Users/*/Library/Messages/Attachments"],
|
||||
remoteAttachmentRoots: ["/Users/*/Library/Messages/Attachments"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
If `remoteHost` is not set, OpenClaw attempts to auto-detect it by parsing the SSH wrapper script.
|
||||
`remoteHost` must be `host` or `user@host` (no spaces or SSH options).
|
||||
OpenClaw uses strict host-key checking for SCP, so the relay host key must already exist in `~/.ssh/known_hosts`.
|
||||
Attachment paths are validated against allowed roots (`attachmentRoots` / `remoteAttachmentRoots`).
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Requirements and permissions (macOS)
|
||||
|
||||
- Messages must be signed in on the Mac running `imsg`.
|
||||
- Full Disk Access is required for the process context running OpenClaw/`imsg` (Messages DB access).
|
||||
- Automation permission is required to send messages through Messages.app.
|
||||
|
||||
<Tip>
|
||||
Permissions are granted per process context. If gateway runs headless (LaunchAgent/SSH), run a one-time interactive command in that same context to trigger prompts:
|
||||
|
||||
```bash
|
||||
imsg chats --limit 1
|
||||
# or
|
||||
imsg send <handle> "test"
|
||||
```
|
||||
|
||||
</Tip>
|
||||
|
||||
## Access control and routing
|
||||
|
||||
<Tabs>
|
||||
<Tab title="DM policy">
|
||||
`channels.imessage.dmPolicy` controls direct messages:
|
||||
|
||||
- `pairing` (default)
|
||||
- `allowlist`
|
||||
- `open` (requires `allowFrom` to include `"*"`)
|
||||
- `disabled`
|
||||
|
||||
Allowlist field: `channels.imessage.allowFrom`.
|
||||
|
||||
Allowlist entries can be handles or chat targets (`chat_id:*`, `chat_guid:*`, `chat_identifier:*`).
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Group policy + mentions">
|
||||
`channels.imessage.groupPolicy` controls group handling:
|
||||
|
||||
- `allowlist` (default when configured)
|
||||
- `open`
|
||||
- `disabled`
|
||||
|
||||
Group sender allowlist: `channels.imessage.groupAllowFrom`.
|
||||
|
||||
Runtime fallback: if `groupAllowFrom` is unset, iMessage group sender checks fall back to `allowFrom` when available.
|
||||
Runtime note: if `channels.imessage` is completely missing, runtime falls back to `groupPolicy="allowlist"` and logs a warning (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
Mention gating for groups:
|
||||
|
||||
- iMessage has no native mention metadata
|
||||
- mention detection uses regex patterns (`agents.list[].groupChat.mentionPatterns`, fallback `messages.groupChat.mentionPatterns`)
|
||||
- with no configured patterns, mention gating cannot be enforced
|
||||
|
||||
Control commands from authorized senders can bypass mention gating in groups.
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Sessions and deterministic replies">
|
||||
- DMs use direct routing; groups use group routing.
|
||||
- With default `session.dmScope=main`, iMessage DMs collapse into the agent main session.
|
||||
- Group sessions are isolated (`agent:<agentId>:imessage:group:<chat_id>`).
|
||||
- Replies route back to iMessage using originating channel/target metadata.
|
||||
|
||||
Group-ish thread behavior:
|
||||
|
||||
Some multi-participant iMessage threads can arrive with `is_group=false`.
|
||||
If that `chat_id` is explicitly configured under `channels.imessage.groups`, OpenClaw treats it as group traffic (group gating + group session isolation).
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## ACP conversation bindings
|
||||
|
||||
Legacy iMessage chats can also be bound to ACP sessions.
|
||||
|
||||
Fast operator flow:
|
||||
|
||||
- Run `/acp spawn codex --bind here` inside the DM or allowed group chat.
|
||||
- Future messages in that same iMessage conversation route to the spawned ACP session.
|
||||
- `/new` and `/reset` reset the same bound ACP session in place.
|
||||
- `/acp close` closes the ACP session and removes the binding.
|
||||
|
||||
Configured persistent bindings are supported through top-level `bindings[]` entries with `type: "acp"` and `match.channel: "imessage"`.
|
||||
|
||||
`match.peer.id` can use:
|
||||
|
||||
- normalized DM handle such as `+15555550123` or `user@example.com`
|
||||
- `chat_id:<id>` (recommended for stable group bindings)
|
||||
- `chat_guid:<guid>`
|
||||
- `chat_identifier:<identifier>`
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "codex",
|
||||
runtime: {
|
||||
type: "acp",
|
||||
acp: { agent: "codex", backend: "acpx", mode: "persistent" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
type: "acp",
|
||||
agentId: "codex",
|
||||
match: {
|
||||
channel: "imessage",
|
||||
accountId: "default",
|
||||
peer: { kind: "group", id: "chat_id:123" },
|
||||
},
|
||||
acp: { label: "codex-group" },
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
See [ACP Agents](/tools/acp-agents) for shared ACP binding behavior.
|
||||
|
||||
## Deployment patterns
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Dedicated bot macOS user (separate iMessage identity)">
|
||||
Use a dedicated Apple ID and macOS user so bot traffic is isolated from your personal Messages profile.
|
||||
|
||||
Typical flow:
|
||||
|
||||
1. Create/sign in a dedicated macOS user.
|
||||
2. Sign into Messages with the bot Apple ID in that user.
|
||||
3. Install `imsg` in that user.
|
||||
4. Create SSH wrapper so OpenClaw can run `imsg` in that user context.
|
||||
5. Point `channels.imessage.accounts.<id>.cliPath` and `.dbPath` to that user profile.
|
||||
|
||||
First run may require GUI approvals (Automation + Full Disk Access) in that bot user session.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Remote Mac over Tailscale (example)">
|
||||
Common topology:
|
||||
|
||||
- gateway runs on Linux/VM
|
||||
- iMessage + `imsg` runs on a Mac in your tailnet
|
||||
- `cliPath` wrapper uses SSH to run `imsg`
|
||||
- `remoteHost` enables SCP attachment fetches
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
imessage: {
|
||||
enabled: true,
|
||||
cliPath: "~/.openclaw/scripts/imsg-ssh",
|
||||
remoteHost: "bot@mac-mini.tailnet-1234.ts.net",
|
||||
includeAttachments: true,
|
||||
dbPath: "/Users/bot/Library/Messages/chat.db",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
exec ssh -T bot@mac-mini.tailnet-1234.ts.net imsg "$@"
|
||||
```
|
||||
|
||||
Use SSH keys so both SSH and SCP are non-interactive.
|
||||
Ensure the host key is trusted first (for example `ssh bot@mac-mini.tailnet-1234.ts.net`) so `known_hosts` is populated.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Multi-account pattern">
|
||||
iMessage supports per-account config under `channels.imessage.accounts`.
|
||||
|
||||
Each account can override fields such as `cliPath`, `dbPath`, `allowFrom`, `groupPolicy`, `mediaMaxMb`, history settings, and attachment root allowlists.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Media, chunking, and delivery targets
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Attachments and media">
|
||||
- inbound attachment ingestion is optional: `channels.imessage.includeAttachments`
|
||||
- remote attachment paths can be fetched via SCP when `remoteHost` is set
|
||||
- attachment paths must match allowed roots:
|
||||
- `channels.imessage.attachmentRoots` (local)
|
||||
- `channels.imessage.remoteAttachmentRoots` (remote SCP mode)
|
||||
- default root pattern: `/Users/*/Library/Messages/Attachments`
|
||||
- SCP uses strict host-key checking (`StrictHostKeyChecking=yes`)
|
||||
- outbound media size uses `channels.imessage.mediaMaxMb` (default 16 MB)
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Outbound chunking">
|
||||
- text chunk limit: `channels.imessage.textChunkLimit` (default 4000)
|
||||
- chunk mode: `channels.imessage.chunkMode`
|
||||
- `length` (default)
|
||||
- `newline` (paragraph-first splitting)
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Addressing formats">
|
||||
Preferred explicit targets:
|
||||
|
||||
- `chat_id:123` (recommended for stable routing)
|
||||
- `chat_guid:...`
|
||||
- `chat_identifier:...`
|
||||
|
||||
Handle targets are also supported:
|
||||
|
||||
- `imessage:+1555...`
|
||||
- `sms:+1555...`
|
||||
- `user@example.com`
|
||||
|
||||
```bash
|
||||
imsg chats --limit 20
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Config writes
|
||||
|
||||
iMessage allows channel-initiated config writes by default (for `/config set|unset` when `commands.config: true`).
|
||||
|
||||
Disable:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
imessage: {
|
||||
configWrites: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="imsg not found or RPC unsupported">
|
||||
Validate the binary and RPC support:
|
||||
|
||||
```bash
|
||||
imsg rpc --help
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
If probe reports RPC unsupported, update `imsg`.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="DMs are ignored">
|
||||
Check:
|
||||
|
||||
- `channels.imessage.dmPolicy`
|
||||
- `channels.imessage.allowFrom`
|
||||
- pairing approvals (`openclaw pairing list imessage`)
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Group messages are ignored">
|
||||
Check:
|
||||
|
||||
- `channels.imessage.groupPolicy`
|
||||
- `channels.imessage.groupAllowFrom`
|
||||
- `channels.imessage.groups` allowlist behavior
|
||||
- mention pattern configuration (`agents.list[].groupChat.mentionPatterns`)
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Remote attachments fail">
|
||||
Check:
|
||||
|
||||
- `channels.imessage.remoteHost`
|
||||
- `channels.imessage.remoteAttachmentRoots`
|
||||
- SSH/SCP key auth from the gateway host
|
||||
- host key exists in `~/.ssh/known_hosts` on the gateway host
|
||||
- remote path readability on the Mac running Messages
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="macOS permission prompts were missed">
|
||||
Re-run in an interactive GUI terminal in the same user/session context and approve prompts:
|
||||
|
||||
```bash
|
||||
imsg chats --limit 1
|
||||
imsg send <handle> "test"
|
||||
```
|
||||
|
||||
Confirm Full Disk Access + Automation are granted for the process context that runs OpenClaw/`imsg`.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Configuration reference pointers
|
||||
|
||||
- [Configuration reference - iMessage](/gateway/configuration-reference#imessage)
|
||||
- [Gateway configuration](/gateway/configuration)
|
||||
- [Pairing](/channels/pairing)
|
||||
- [BlueBubbles](/channels/bluebubbles)
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
50
openclaw/docs/channels/index.md
Normal file
50
openclaw/docs/channels/index.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
summary: "Messaging platforms OpenClaw can connect to"
|
||||
read_when:
|
||||
- You want to choose a chat channel for OpenClaw
|
||||
- You need a quick overview of supported messaging platforms
|
||||
title: "Chat Channels"
|
||||
---
|
||||
|
||||
# Chat Channels
|
||||
|
||||
OpenClaw can talk to you on any chat app you already use. Each channel connects via the Gateway.
|
||||
Text is supported everywhere; media and reactions vary by channel.
|
||||
|
||||
## Supported channels
|
||||
|
||||
- [BlueBubbles](/channels/bluebubbles) — **Recommended for iMessage**; uses the BlueBubbles macOS server REST API with full feature support (bundled plugin; edit, unsend, effects, reactions, group management — edit currently broken on macOS 26 Tahoe).
|
||||
- [Discord](/channels/discord) — Discord Bot API + Gateway; supports servers, channels, and DMs.
|
||||
- [Feishu](/channels/feishu) — Feishu/Lark bot via WebSocket (bundled plugin).
|
||||
- [Google Chat](/channels/googlechat) — Google Chat API app via HTTP webhook.
|
||||
- [iMessage (legacy)](/channels/imessage) — Legacy macOS integration via imsg CLI (deprecated, use BlueBubbles for new setups).
|
||||
- [IRC](/channels/irc) — Classic IRC servers; channels + DMs with pairing/allowlist controls.
|
||||
- [LINE](/channels/line) — LINE Messaging API bot (bundled plugin).
|
||||
- [Matrix](/channels/matrix) — Matrix protocol (bundled plugin).
|
||||
- [Mattermost](/channels/mattermost) — Bot API + WebSocket; channels, groups, DMs (bundled plugin).
|
||||
- [Microsoft Teams](/channels/msteams) — Bot Framework; enterprise support (bundled plugin).
|
||||
- [Nextcloud Talk](/channels/nextcloud-talk) — Self-hosted chat via Nextcloud Talk (bundled plugin).
|
||||
- [Nostr](/channels/nostr) — Decentralized DMs via NIP-04 (bundled plugin).
|
||||
- [QQ Bot](/channels/qqbot) — QQ Bot API; private chat, group chat, and rich media (bundled plugin).
|
||||
- [Signal](/channels/signal) — signal-cli; privacy-focused.
|
||||
- [Slack](/channels/slack) — Bolt SDK; workspace apps.
|
||||
- [Synology Chat](/channels/synology-chat) — Synology NAS Chat via outgoing+incoming webhooks (bundled plugin).
|
||||
- [Telegram](/channels/telegram) — Bot API via grammY; supports groups.
|
||||
- [Tlon](/channels/tlon) — Urbit-based messenger (bundled plugin).
|
||||
- [Twitch](/channels/twitch) — Twitch chat via IRC connection (bundled plugin).
|
||||
- [Voice Call](/plugins/voice-call) — Telephony via Plivo or Twilio (plugin, installed separately).
|
||||
- [WebChat](/web/webchat) — Gateway WebChat UI over WebSocket.
|
||||
- [WeChat](/channels/wechat) — Tencent iLink Bot plugin via QR login; private chats only (external plugin).
|
||||
- [WhatsApp](/channels/whatsapp) — Most popular; uses Baileys and requires QR pairing.
|
||||
- [Zalo](/channels/zalo) — Zalo Bot API; Vietnam's popular messenger (bundled plugin).
|
||||
- [Zalo Personal](/channels/zalouser) — Zalo personal account via QR login (bundled plugin).
|
||||
|
||||
## Notes
|
||||
|
||||
- Channels can run simultaneously; configure multiple and OpenClaw will route per chat.
|
||||
- Fastest setup is usually **Telegram** (simple bot token). WhatsApp requires QR pairing and
|
||||
stores more state on disk.
|
||||
- Group behavior varies by channel; see [Groups](/channels/groups).
|
||||
- DM pairing and allowlists are enforced for safety; see [Security](/gateway/security).
|
||||
- Troubleshooting: [Channel troubleshooting](/channels/troubleshooting).
|
||||
- Model providers are documented separately; see [Model Providers](/providers/models).
|
||||
252
openclaw/docs/channels/irc.md
Normal file
252
openclaw/docs/channels/irc.md
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
---
|
||||
title: IRC
|
||||
summary: "IRC plugin setup, access controls, and troubleshooting"
|
||||
read_when:
|
||||
- You want to connect OpenClaw to IRC channels or DMs
|
||||
- You are configuring IRC allowlists, group policy, or mention gating
|
||||
---
|
||||
|
||||
# IRC
|
||||
|
||||
Use IRC when you want OpenClaw in classic channels (`#room`) and direct messages.
|
||||
IRC ships as an extension plugin, but it is configured in the main config under `channels.irc`.
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Enable IRC config in `~/.openclaw/openclaw.json`.
|
||||
2. Set at least:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
irc: {
|
||||
enabled: true,
|
||||
host: "irc.example.com",
|
||||
port: 6697,
|
||||
tls: true,
|
||||
nick: "openclaw-bot",
|
||||
channels: ["#openclaw"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Prefer a private IRC server for bot coordination. If you intentionally use a public IRC network, common choices include Libera.Chat, OFTC, and Snoonet. Avoid predictable public channels for bot or swarm backchannel traffic.
|
||||
|
||||
3. Start/restart gateway:
|
||||
|
||||
```bash
|
||||
openclaw gateway run
|
||||
```
|
||||
|
||||
## Security defaults
|
||||
|
||||
- `channels.irc.dmPolicy` defaults to `"pairing"`.
|
||||
- `channels.irc.groupPolicy` defaults to `"allowlist"`.
|
||||
- With `groupPolicy="allowlist"`, set `channels.irc.groups` to define allowed channels.
|
||||
- Use TLS (`channels.irc.tls=true`) unless you intentionally accept plaintext transport.
|
||||
|
||||
## Access control
|
||||
|
||||
There are two separate “gates” for IRC channels:
|
||||
|
||||
1. **Channel access** (`groupPolicy` + `groups`): whether the bot accepts messages from a channel at all.
|
||||
2. **Sender access** (`groupAllowFrom` / per-channel `groups["#channel"].allowFrom`): who is allowed to trigger the bot inside that channel.
|
||||
|
||||
Config keys:
|
||||
|
||||
- DM allowlist (DM sender access): `channels.irc.allowFrom`
|
||||
- Group sender allowlist (channel sender access): `channels.irc.groupAllowFrom`
|
||||
- Per-channel controls (channel + sender + mention rules): `channels.irc.groups["#channel"]`
|
||||
- `channels.irc.groupPolicy="open"` allows unconfigured channels (**still mention-gated by default**)
|
||||
|
||||
Allowlist entries should use stable sender identities (`nick!user@host`).
|
||||
Bare nick matching is mutable and only enabled when `channels.irc.dangerouslyAllowNameMatching: true`.
|
||||
|
||||
### Common gotcha: `allowFrom` is for DMs, not channels
|
||||
|
||||
If you see logs like:
|
||||
|
||||
- `irc: drop group sender alice!ident@host (policy=allowlist)`
|
||||
|
||||
…it means the sender wasn’t allowed for **group/channel** messages. Fix it by either:
|
||||
|
||||
- setting `channels.irc.groupAllowFrom` (global for all channels), or
|
||||
- setting per-channel sender allowlists: `channels.irc.groups["#channel"].allowFrom`
|
||||
|
||||
Example (allow anyone in `#tuirc-dev` to talk to the bot):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
irc: {
|
||||
groupPolicy: "allowlist",
|
||||
groups: {
|
||||
"#tuirc-dev": { allowFrom: ["*"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Reply triggering (mentions)
|
||||
|
||||
Even if a channel is allowed (via `groupPolicy` + `groups`) and the sender is allowed, OpenClaw defaults to **mention-gating** in group contexts.
|
||||
|
||||
That means you may see logs like `drop channel … (missing-mention)` unless the message includes a mention pattern that matches the bot.
|
||||
|
||||
To make the bot reply in an IRC channel **without needing a mention**, disable mention gating for that channel:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
irc: {
|
||||
groupPolicy: "allowlist",
|
||||
groups: {
|
||||
"#tuirc-dev": {
|
||||
requireMention: false,
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Or to allow **all** IRC channels (no per-channel allowlist) and still reply without mentions:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
irc: {
|
||||
groupPolicy: "open",
|
||||
groups: {
|
||||
"*": { requireMention: false, allowFrom: ["*"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Security note (recommended for public channels)
|
||||
|
||||
If you allow `allowFrom: ["*"]` in a public channel, anyone can prompt the bot.
|
||||
To reduce risk, restrict tools for that channel.
|
||||
|
||||
### Same tools for everyone in the channel
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
irc: {
|
||||
groups: {
|
||||
"#tuirc-dev": {
|
||||
allowFrom: ["*"],
|
||||
tools: {
|
||||
deny: ["group:runtime", "group:fs", "gateway", "nodes", "cron", "browser"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Different tools per sender (owner gets more power)
|
||||
|
||||
Use `toolsBySender` to apply a stricter policy to `"*"` and a looser one to your nick:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
irc: {
|
||||
groups: {
|
||||
"#tuirc-dev": {
|
||||
allowFrom: ["*"],
|
||||
toolsBySender: {
|
||||
"*": {
|
||||
deny: ["group:runtime", "group:fs", "gateway", "nodes", "cron", "browser"],
|
||||
},
|
||||
"id:eigen": {
|
||||
deny: ["gateway", "nodes", "cron"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `toolsBySender` keys should use `id:` for IRC sender identity values:
|
||||
`id:eigen` or `id:eigen!~eigen@174.127.248.171` for stronger matching.
|
||||
- Legacy unprefixed keys are still accepted and matched as `id:` only.
|
||||
- The first matching sender policy wins; `"*"` is the wildcard fallback.
|
||||
|
||||
For more on group access vs mention-gating (and how they interact), see: [/channels/groups](/channels/groups).
|
||||
|
||||
## NickServ
|
||||
|
||||
To identify with NickServ after connect:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
irc: {
|
||||
nickserv: {
|
||||
enabled: true,
|
||||
service: "NickServ",
|
||||
password: "your-nickserv-password",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Optional one-time registration on connect:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
irc: {
|
||||
nickserv: {
|
||||
register: true,
|
||||
registerEmail: "bot@example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Disable `register` after the nick is registered to avoid repeated REGISTER attempts.
|
||||
|
||||
## Environment variables
|
||||
|
||||
Default account supports:
|
||||
|
||||
- `IRC_HOST`
|
||||
- `IRC_PORT`
|
||||
- `IRC_TLS`
|
||||
- `IRC_NICK`
|
||||
- `IRC_USERNAME`
|
||||
- `IRC_REALNAME`
|
||||
- `IRC_PASSWORD`
|
||||
- `IRC_CHANNELS` (comma-separated)
|
||||
- `IRC_NICKSERV_PASSWORD`
|
||||
- `IRC_NICKSERV_REGISTER_EMAIL`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If the bot connects but never replies in channels, verify `channels.irc.groups` **and** whether mention-gating is dropping messages (`missing-mention`). If you want it to reply without pings, set `requireMention:false` for the channel.
|
||||
- If login fails, verify nick availability and server password.
|
||||
- If TLS fails on a custom network, verify host/port and certificate setup.
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
225
openclaw/docs/channels/line.md
Normal file
225
openclaw/docs/channels/line.md
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
---
|
||||
summary: "LINE Messaging API plugin setup, config, and usage"
|
||||
read_when:
|
||||
- You want to connect OpenClaw to LINE
|
||||
- You need LINE webhook + credential setup
|
||||
- You want LINE-specific message options
|
||||
title: LINE
|
||||
---
|
||||
|
||||
# LINE
|
||||
|
||||
LINE connects to OpenClaw via the LINE Messaging API. The plugin runs as a webhook
|
||||
receiver on the gateway and uses your channel access token + channel secret for
|
||||
authentication.
|
||||
|
||||
Status: bundled plugin. Direct messages, group chats, media, locations, Flex
|
||||
messages, template messages, and quick replies are supported. Reactions and threads
|
||||
are not supported.
|
||||
|
||||
## Bundled plugin
|
||||
|
||||
LINE ships as a bundled plugin in current OpenClaw releases, so normal
|
||||
packaged builds do not need a separate install.
|
||||
|
||||
If you are on an older build or a custom install that excludes LINE, install it
|
||||
manually:
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/line
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./path/to/local/line-plugin
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
1. Create a LINE Developers account and open the Console:
|
||||
[https://developers.line.biz/console/](https://developers.line.biz/console/)
|
||||
2. Create (or pick) a Provider and add a **Messaging API** channel.
|
||||
3. Copy the **Channel access token** and **Channel secret** from the channel settings.
|
||||
4. Enable **Use webhook** in the Messaging API settings.
|
||||
5. Set the webhook URL to your gateway endpoint (HTTPS required):
|
||||
|
||||
```
|
||||
https://gateway-host/line/webhook
|
||||
```
|
||||
|
||||
The gateway responds to LINE’s webhook verification (GET) and inbound events (POST).
|
||||
If you need a custom path, set `channels.line.webhookPath` or
|
||||
`channels.line.accounts.<id>.webhookPath` and update the URL accordingly.
|
||||
|
||||
Security note:
|
||||
|
||||
- LINE signature verification is body-dependent (HMAC over the raw body), so OpenClaw applies strict pre-auth body limits and timeout before verification.
|
||||
- OpenClaw processes webhook events from the verified raw request bytes. Upstream middleware-transformed `req.body` values are ignored for signature-integrity safety.
|
||||
|
||||
## Configure
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
line: {
|
||||
enabled: true,
|
||||
channelAccessToken: "LINE_CHANNEL_ACCESS_TOKEN",
|
||||
channelSecret: "LINE_CHANNEL_SECRET",
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Env vars (default account only):
|
||||
|
||||
- `LINE_CHANNEL_ACCESS_TOKEN`
|
||||
- `LINE_CHANNEL_SECRET`
|
||||
|
||||
Token/secret files:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
line: {
|
||||
tokenFile: "/path/to/line-token.txt",
|
||||
secretFile: "/path/to/line-secret.txt",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
`tokenFile` and `secretFile` must point to regular files. Symlinks are rejected.
|
||||
|
||||
Multiple accounts:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
line: {
|
||||
accounts: {
|
||||
marketing: {
|
||||
channelAccessToken: "...",
|
||||
channelSecret: "...",
|
||||
webhookPath: "/line/marketing",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Access control
|
||||
|
||||
Direct messages default to pairing. Unknown senders get a pairing code and their
|
||||
messages are ignored until approved.
|
||||
|
||||
```bash
|
||||
openclaw pairing list line
|
||||
openclaw pairing approve line <CODE>
|
||||
```
|
||||
|
||||
Allowlists and policies:
|
||||
|
||||
- `channels.line.dmPolicy`: `pairing | allowlist | open | disabled`
|
||||
- `channels.line.allowFrom`: allowlisted LINE user IDs for DMs
|
||||
- `channels.line.groupPolicy`: `allowlist | open | disabled`
|
||||
- `channels.line.groupAllowFrom`: allowlisted LINE user IDs for groups
|
||||
- Per-group overrides: `channels.line.groups.<groupId>.allowFrom`
|
||||
- Runtime note: if `channels.line` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
LINE IDs are case-sensitive. Valid IDs look like:
|
||||
|
||||
- User: `U` + 32 hex chars
|
||||
- Group: `C` + 32 hex chars
|
||||
- Room: `R` + 32 hex chars
|
||||
|
||||
## Message behavior
|
||||
|
||||
- Text is chunked at 5000 characters.
|
||||
- Markdown formatting is stripped; code blocks and tables are converted into Flex
|
||||
cards when possible.
|
||||
- Streaming responses are buffered; LINE receives full chunks with a loading
|
||||
animation while the agent works.
|
||||
- Media downloads are capped by `channels.line.mediaMaxMb` (default 10).
|
||||
|
||||
## Channel data (rich messages)
|
||||
|
||||
Use `channelData.line` to send quick replies, locations, Flex cards, or template
|
||||
messages.
|
||||
|
||||
```json5
|
||||
{
|
||||
text: "Here you go",
|
||||
channelData: {
|
||||
line: {
|
||||
quickReplies: ["Status", "Help"],
|
||||
location: {
|
||||
title: "Office",
|
||||
address: "123 Main St",
|
||||
latitude: 35.681236,
|
||||
longitude: 139.767125,
|
||||
},
|
||||
flexMessage: {
|
||||
altText: "Status card",
|
||||
contents: {
|
||||
/* Flex payload */
|
||||
},
|
||||
},
|
||||
templateMessage: {
|
||||
type: "confirm",
|
||||
text: "Proceed?",
|
||||
confirmLabel: "Yes",
|
||||
confirmData: "yes",
|
||||
cancelLabel: "No",
|
||||
cancelData: "no",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The LINE plugin also ships a `/card` command for Flex message presets:
|
||||
|
||||
```
|
||||
/card info "Welcome" "Thanks for joining!"
|
||||
```
|
||||
|
||||
## ACP support
|
||||
|
||||
LINE supports ACP (Agent Communication Protocol) conversation bindings:
|
||||
|
||||
- `/acp spawn <agent> --bind here` binds the current LINE chat to an ACP session without creating a child thread.
|
||||
- Configured ACP bindings and active conversation-bound ACP sessions work on LINE like other conversation channels.
|
||||
|
||||
See [ACP agents](/tools/acp-agents) for details.
|
||||
|
||||
## Outbound media
|
||||
|
||||
The LINE plugin supports sending images, videos, and audio files through the agent message tool. Media is sent via the LINE-specific delivery path with appropriate preview and tracking handling:
|
||||
|
||||
- **Images**: sent as LINE image messages with automatic preview generation.
|
||||
- **Videos**: sent with explicit preview and content-type handling.
|
||||
- **Audio**: sent as LINE audio messages.
|
||||
|
||||
Generic media sends fall back to the existing image-only route when a LINE-specific path is not available.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Webhook verification fails:** ensure the webhook URL is HTTPS and the
|
||||
`channelSecret` matches the LINE console.
|
||||
- **No inbound events:** confirm the webhook path matches `channels.line.webhookPath`
|
||||
and that the gateway is reachable from LINE.
|
||||
- **Media download errors:** raise `channels.line.mediaMaxMb` if media exceeds the
|
||||
default limit.
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
56
openclaw/docs/channels/location.md
Normal file
56
openclaw/docs/channels/location.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
summary: "Inbound channel location parsing (Telegram/WhatsApp/Matrix) and context fields"
|
||||
read_when:
|
||||
- Adding or modifying channel location parsing
|
||||
- Using location context fields in agent prompts or tools
|
||||
title: "Channel Location Parsing"
|
||||
---
|
||||
|
||||
# Channel location parsing
|
||||
|
||||
OpenClaw normalizes shared locations from chat channels into:
|
||||
|
||||
- human-readable text appended to the inbound body, and
|
||||
- structured fields in the auto-reply context payload.
|
||||
|
||||
Currently supported:
|
||||
|
||||
- **Telegram** (location pins + venues + live locations)
|
||||
- **WhatsApp** (locationMessage + liveLocationMessage)
|
||||
- **Matrix** (`m.location` with `geo_uri`)
|
||||
|
||||
## Text formatting
|
||||
|
||||
Locations are rendered as friendly lines without brackets:
|
||||
|
||||
- Pin:
|
||||
- `📍 48.858844, 2.294351 ±12m`
|
||||
- Named place:
|
||||
- `📍 Eiffel Tower — Champ de Mars, Paris (48.858844, 2.294351 ±12m)`
|
||||
- Live share:
|
||||
- `🛰 Live location: 48.858844, 2.294351 ±12m`
|
||||
|
||||
If the channel includes a caption/comment, it is appended on the next line:
|
||||
|
||||
```
|
||||
📍 48.858844, 2.294351 ±12m
|
||||
Meet here
|
||||
```
|
||||
|
||||
## Context fields
|
||||
|
||||
When a location is present, these fields are added to `ctx`:
|
||||
|
||||
- `LocationLat` (number)
|
||||
- `LocationLon` (number)
|
||||
- `LocationAccuracy` (number, meters; optional)
|
||||
- `LocationName` (string; optional)
|
||||
- `LocationAddress` (string; optional)
|
||||
- `LocationSource` (`pin | place | live`)
|
||||
- `LocationIsLive` (boolean)
|
||||
|
||||
## Channel notes
|
||||
|
||||
- **Telegram**: venues map to `LocationName/LocationAddress`; live locations use `live_period`.
|
||||
- **WhatsApp**: `locationMessage.comment` and `liveLocationMessage.caption` are appended as the caption line.
|
||||
- **Matrix**: `geo_uri` is parsed as a pin location; altitude is ignored and `LocationIsLive` is always false.
|
||||
1062
openclaw/docs/channels/matrix.md
Normal file
1062
openclaw/docs/channels/matrix.md
Normal file
File diff suppressed because it is too large
Load diff
472
openclaw/docs/channels/mattermost.md
Normal file
472
openclaw/docs/channels/mattermost.md
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
---
|
||||
summary: "Mattermost bot setup and OpenClaw config"
|
||||
read_when:
|
||||
- Setting up Mattermost
|
||||
- Debugging Mattermost routing
|
||||
title: "Mattermost"
|
||||
---
|
||||
|
||||
# Mattermost
|
||||
|
||||
Status: bundled plugin (bot token + WebSocket events). Channels, groups, and DMs are supported.
|
||||
Mattermost is a self-hostable team messaging platform; see the official site at
|
||||
[mattermost.com](https://mattermost.com) for product details and downloads.
|
||||
|
||||
## Bundled plugin
|
||||
|
||||
Mattermost ships as a bundled plugin in current OpenClaw releases, so normal
|
||||
packaged builds do not need a separate install.
|
||||
|
||||
If you are on an older build or a custom install that excludes Mattermost,
|
||||
install it manually:
|
||||
|
||||
Install via CLI (npm registry):
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/mattermost
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./path/to/local/mattermost-plugin
|
||||
```
|
||||
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Quick setup
|
||||
|
||||
1. Ensure the Mattermost plugin is available.
|
||||
- Current packaged OpenClaw releases already bundle it.
|
||||
- Older/custom installs can add it manually with the commands above.
|
||||
2. Create a Mattermost bot account and copy the **bot token**.
|
||||
3. Copy the Mattermost **base URL** (e.g., `https://chat.example.com`).
|
||||
4. Configure OpenClaw and start the gateway.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
enabled: true,
|
||||
botToken: "mm-token",
|
||||
baseUrl: "https://chat.example.com",
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Native slash commands
|
||||
|
||||
Native slash commands are opt-in. When enabled, OpenClaw registers `oc_*` slash commands via
|
||||
the Mattermost API and receives callback POSTs on the gateway HTTP server.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
commands: {
|
||||
native: true,
|
||||
nativeSkills: true,
|
||||
callbackPath: "/api/channels/mattermost/command",
|
||||
// Use when Mattermost cannot reach the gateway directly (reverse proxy/public URL).
|
||||
callbackUrl: "https://gateway.example.com/api/channels/mattermost/command",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `native: "auto"` defaults to disabled for Mattermost. Set `native: true` to enable.
|
||||
- If `callbackUrl` is omitted, OpenClaw derives one from gateway host/port + `callbackPath`.
|
||||
- For multi-account setups, `commands` can be set at the top level or under
|
||||
`channels.mattermost.accounts.<id>.commands` (account values override top-level fields).
|
||||
- Command callbacks are validated with the per-command tokens returned by
|
||||
Mattermost when OpenClaw registers `oc_*` commands.
|
||||
- Slash callbacks fail closed when registration failed, startup was partial, or
|
||||
the callback token does not match one of the registered commands.
|
||||
- Reachability requirement: the callback endpoint must be reachable from the Mattermost server.
|
||||
- Do not set `callbackUrl` to `localhost` unless Mattermost runs on the same host/network namespace as OpenClaw.
|
||||
- Do not set `callbackUrl` to your Mattermost base URL unless that URL reverse-proxies `/api/channels/mattermost/command` to OpenClaw.
|
||||
- A quick check is `curl https://<gateway-host>/api/channels/mattermost/command`; a GET should return `405 Method Not Allowed` from OpenClaw, not `404`.
|
||||
- Mattermost egress allowlist requirement:
|
||||
- If your callback targets private/tailnet/internal addresses, set Mattermost
|
||||
`ServiceSettings.AllowedUntrustedInternalConnections` to include the callback host/domain.
|
||||
- Use host/domain entries, not full URLs.
|
||||
- Good: `gateway.tailnet-name.ts.net`
|
||||
- Bad: `https://gateway.tailnet-name.ts.net`
|
||||
|
||||
## Environment variables (default account)
|
||||
|
||||
Set these on the gateway host if you prefer env vars:
|
||||
|
||||
- `MATTERMOST_BOT_TOKEN=...`
|
||||
- `MATTERMOST_URL=https://chat.example.com`
|
||||
|
||||
Env vars apply only to the **default** account (`default`). Other accounts must use config values.
|
||||
|
||||
## Chat modes
|
||||
|
||||
Mattermost responds to DMs automatically. Channel behavior is controlled by `chatmode`:
|
||||
|
||||
- `oncall` (default): respond only when @mentioned in channels.
|
||||
- `onmessage`: respond to every channel message.
|
||||
- `onchar`: respond when a message starts with a trigger prefix.
|
||||
|
||||
Config example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
chatmode: "onchar",
|
||||
oncharPrefixes: [">", "!"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `onchar` still responds to explicit @mentions.
|
||||
- `channels.mattermost.requireMention` is honored for legacy configs but `chatmode` is preferred.
|
||||
|
||||
## Threading and sessions
|
||||
|
||||
Use `channels.mattermost.replyToMode` to control whether channel and group replies stay in the
|
||||
main channel or start a thread under the triggering post.
|
||||
|
||||
- `off` (default): only reply in a thread when the inbound post is already in one.
|
||||
- `first`: for top-level channel/group posts, start a thread under that post and route the
|
||||
conversation to a thread-scoped session.
|
||||
- `all`: same behavior as `first` for Mattermost today.
|
||||
- Direct messages ignore this setting and stay non-threaded.
|
||||
|
||||
Config example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
replyToMode: "all",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Thread-scoped sessions use the triggering post id as the thread root.
|
||||
- `first` and `all` are currently equivalent because once Mattermost has a thread root,
|
||||
follow-up chunks and media continue in that same thread.
|
||||
|
||||
## Access control (DMs)
|
||||
|
||||
- Default: `channels.mattermost.dmPolicy = "pairing"` (unknown senders get a pairing code).
|
||||
- Approve via:
|
||||
- `openclaw pairing list mattermost`
|
||||
- `openclaw pairing approve mattermost <CODE>`
|
||||
- Public DMs: `channels.mattermost.dmPolicy="open"` plus `channels.mattermost.allowFrom=["*"]`.
|
||||
|
||||
## Channels (groups)
|
||||
|
||||
- Default: `channels.mattermost.groupPolicy = "allowlist"` (mention-gated).
|
||||
- Allowlist senders with `channels.mattermost.groupAllowFrom` (user IDs recommended).
|
||||
- Per-channel mention overrides live under `channels.mattermost.groups.<channelId>.requireMention`
|
||||
or `channels.mattermost.groups["*"].requireMention` for a default.
|
||||
- `@username` matching is mutable and only enabled when `channels.mattermost.dangerouslyAllowNameMatching: true`.
|
||||
- Open channels: `channels.mattermost.groupPolicy="open"` (mention-gated).
|
||||
- Runtime note: if `channels.mattermost` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
groupPolicy: "open",
|
||||
groups: {
|
||||
"*": { requireMention: true },
|
||||
"team-channel-id": { requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Targets for outbound delivery
|
||||
|
||||
Use these target formats with `openclaw message send` or cron/webhooks:
|
||||
|
||||
- `channel:<id>` for a channel
|
||||
- `user:<id>` for a DM
|
||||
- `@username` for a DM (resolved via the Mattermost API)
|
||||
|
||||
Bare opaque IDs (like `64ifufp...`) are **ambiguous** in Mattermost (user ID vs channel ID).
|
||||
|
||||
OpenClaw resolves them **user-first**:
|
||||
|
||||
- If the ID exists as a user (`GET /api/v4/users/<id>` succeeds), OpenClaw sends a **DM** by resolving the direct channel via `/api/v4/channels/direct`.
|
||||
- Otherwise the ID is treated as a **channel ID**.
|
||||
|
||||
If you need deterministic behavior, always use the explicit prefixes (`user:<id>` / `channel:<id>`).
|
||||
|
||||
## DM channel retry
|
||||
|
||||
When OpenClaw sends to a Mattermost DM target and needs to resolve the direct channel first, it
|
||||
retries transient direct-channel creation failures by default.
|
||||
|
||||
Use `channels.mattermost.dmChannelRetry` to tune that behavior globally for the Mattermost plugin,
|
||||
or `channels.mattermost.accounts.<id>.dmChannelRetry` for one account.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
dmChannelRetry: {
|
||||
maxRetries: 3,
|
||||
initialDelayMs: 1000,
|
||||
maxDelayMs: 10000,
|
||||
timeoutMs: 30000,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- This applies only to DM channel creation (`/api/v4/channels/direct`), not every Mattermost API call.
|
||||
- Retries apply to transient failures such as rate limits, 5xx responses, and network or timeout errors.
|
||||
- 4xx client errors other than `429` are treated as permanent and are not retried.
|
||||
|
||||
## Reactions (message tool)
|
||||
|
||||
- Use `message action=react` with `channel=mattermost`.
|
||||
- `messageId` is the Mattermost post id.
|
||||
- `emoji` accepts names like `thumbsup` or `:+1:` (colons are optional).
|
||||
- Set `remove=true` (boolean) to remove a reaction.
|
||||
- Reaction add/remove events are forwarded as system events to the routed agent session.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
message action=react channel=mattermost target=channel:<channelId> messageId=<postId> emoji=thumbsup
|
||||
message action=react channel=mattermost target=channel:<channelId> messageId=<postId> emoji=thumbsup remove=true
|
||||
```
|
||||
|
||||
Config:
|
||||
|
||||
- `channels.mattermost.actions.reactions`: enable/disable reaction actions (default true).
|
||||
- Per-account override: `channels.mattermost.accounts.<id>.actions.reactions`.
|
||||
|
||||
## Interactive buttons (message tool)
|
||||
|
||||
Send messages with clickable buttons. When a user clicks a button, the agent receives the
|
||||
selection and can respond.
|
||||
|
||||
Enable buttons by adding `inlineButtons` to the channel capabilities:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
capabilities: ["inlineButtons"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Use `message action=send` with a `buttons` parameter. Buttons are a 2D array (rows of buttons):
|
||||
|
||||
```
|
||||
message action=send channel=mattermost target=channel:<channelId> buttons=[[{"text":"Yes","callback_data":"yes"},{"text":"No","callback_data":"no"}]]
|
||||
```
|
||||
|
||||
Button fields:
|
||||
|
||||
- `text` (required): display label.
|
||||
- `callback_data` (required): value sent back on click (used as the action ID).
|
||||
- `style` (optional): `"default"`, `"primary"`, or `"danger"`.
|
||||
|
||||
When a user clicks a button:
|
||||
|
||||
1. All buttons are replaced with a confirmation line (e.g., "✓ **Yes** selected by @user").
|
||||
2. The agent receives the selection as an inbound message and responds.
|
||||
|
||||
Notes:
|
||||
|
||||
- Button callbacks use HMAC-SHA256 verification (automatic, no config needed).
|
||||
- Mattermost strips callback data from its API responses (security feature), so all buttons
|
||||
are removed on click — partial removal is not possible.
|
||||
- Action IDs containing hyphens or underscores are sanitized automatically
|
||||
(Mattermost routing limitation).
|
||||
|
||||
Config:
|
||||
|
||||
- `channels.mattermost.capabilities`: array of capability strings. Add `"inlineButtons"` to
|
||||
enable the buttons tool description in the agent system prompt.
|
||||
- `channels.mattermost.interactions.callbackBaseUrl`: optional external base URL for button
|
||||
callbacks (for example `https://gateway.example.com`). Use this when Mattermost cannot
|
||||
reach the gateway at its bind host directly.
|
||||
- In multi-account setups, you can also set the same field under
|
||||
`channels.mattermost.accounts.<id>.interactions.callbackBaseUrl`.
|
||||
- If `interactions.callbackBaseUrl` is omitted, OpenClaw derives the callback URL from
|
||||
`gateway.customBindHost` + `gateway.port`, then falls back to `http://localhost:<port>`.
|
||||
- Reachability rule: the button callback URL must be reachable from the Mattermost server.
|
||||
`localhost` only works when Mattermost and OpenClaw run on the same host/network namespace.
|
||||
- If your callback target is private/tailnet/internal, add its host/domain to Mattermost
|
||||
`ServiceSettings.AllowedUntrustedInternalConnections`.
|
||||
|
||||
### Direct API integration (external scripts)
|
||||
|
||||
External scripts and webhooks can post buttons directly via the Mattermost REST API
|
||||
instead of going through the agent's `message` tool. Use `buildButtonAttachments()` from
|
||||
the extension when possible; if posting raw JSON, follow these rules:
|
||||
|
||||
**Payload structure:**
|
||||
|
||||
```json5
|
||||
{
|
||||
channel_id: "<channelId>",
|
||||
message: "Choose an option:",
|
||||
props: {
|
||||
attachments: [
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
id: "mybutton01", // alphanumeric only — see below
|
||||
type: "button", // required, or clicks are silently ignored
|
||||
name: "Approve", // display label
|
||||
style: "primary", // optional: "default", "primary", "danger"
|
||||
integration: {
|
||||
url: "https://gateway.example.com/mattermost/interactions/default",
|
||||
context: {
|
||||
action_id: "mybutton01", // must match button id (for name lookup)
|
||||
action: "approve",
|
||||
// ... any custom fields ...
|
||||
_token: "<hmac>", // see HMAC section below
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Critical rules:**
|
||||
|
||||
1. Attachments go in `props.attachments`, not top-level `attachments` (silently ignored).
|
||||
2. Every action needs `type: "button"` — without it, clicks are swallowed silently.
|
||||
3. Every action needs an `id` field — Mattermost ignores actions without IDs.
|
||||
4. Action `id` must be **alphanumeric only** (`[a-zA-Z0-9]`). Hyphens and underscores break
|
||||
Mattermost's server-side action routing (returns 404). Strip them before use.
|
||||
5. `context.action_id` must match the button's `id` so the confirmation message shows the
|
||||
button name (e.g., "Approve") instead of a raw ID.
|
||||
6. `context.action_id` is required — the interaction handler returns 400 without it.
|
||||
|
||||
**HMAC token generation:**
|
||||
|
||||
The gateway verifies button clicks with HMAC-SHA256. External scripts must generate tokens
|
||||
that match the gateway's verification logic:
|
||||
|
||||
1. Derive the secret from the bot token:
|
||||
`HMAC-SHA256(key="openclaw-mattermost-interactions", data=botToken)`
|
||||
2. Build the context object with all fields **except** `_token`.
|
||||
3. Serialize with **sorted keys** and **no spaces** (the gateway uses `JSON.stringify`
|
||||
with sorted keys, which produces compact output).
|
||||
4. Sign: `HMAC-SHA256(key=secret, data=serializedContext)`
|
||||
5. Add the resulting hex digest as `_token` in the context.
|
||||
|
||||
Python example:
|
||||
|
||||
```python
|
||||
import hmac, hashlib, json
|
||||
|
||||
secret = hmac.new(
|
||||
b"openclaw-mattermost-interactions",
|
||||
bot_token.encode(), hashlib.sha256
|
||||
).hexdigest()
|
||||
|
||||
ctx = {"action_id": "mybutton01", "action": "approve"}
|
||||
payload = json.dumps(ctx, sort_keys=True, separators=(",", ":"))
|
||||
token = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest()
|
||||
|
||||
context = {**ctx, "_token": token}
|
||||
```
|
||||
|
||||
Common HMAC pitfalls:
|
||||
|
||||
- Python's `json.dumps` adds spaces by default (`{"key": "val"}`). Use
|
||||
`separators=(",", ":")` to match JavaScript's compact output (`{"key":"val"}`).
|
||||
- Always sign **all** context fields (minus `_token`). The gateway strips `_token` then
|
||||
signs everything remaining. Signing a subset causes silent verification failure.
|
||||
- Use `sort_keys=True` — the gateway sorts keys before signing, and Mattermost may
|
||||
reorder context fields when storing the payload.
|
||||
- Derive the secret from the bot token (deterministic), not random bytes. The secret
|
||||
must be the same across the process that creates buttons and the gateway that verifies.
|
||||
|
||||
## Directory adapter
|
||||
|
||||
The Mattermost plugin includes a directory adapter that resolves channel and user names
|
||||
via the Mattermost API. This enables `#channel-name` and `@username` targets in
|
||||
`openclaw message send` and cron/webhook deliveries.
|
||||
|
||||
No configuration is needed — the adapter uses the bot token from the account config.
|
||||
|
||||
## Multi-account
|
||||
|
||||
Mattermost supports multiple accounts under `channels.mattermost.accounts`:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
accounts: {
|
||||
default: { name: "Primary", botToken: "mm-token", baseUrl: "https://chat.example.com" },
|
||||
alerts: { name: "Alerts", botToken: "mm-token-2", baseUrl: "https://alerts.example.com" },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- No replies in channels: ensure the bot is in the channel and mention it (oncall), use a trigger prefix (onchar), or set `chatmode: "onmessage"`.
|
||||
- Auth errors: check the bot token, base URL, and whether the account is enabled.
|
||||
- Multi-account issues: env vars only apply to the `default` account.
|
||||
- Native slash commands return `Unauthorized: invalid command token.`: OpenClaw
|
||||
did not accept the callback token. Typical causes:
|
||||
- slash command registration failed or only partially completed at startup
|
||||
- the callback is hitting the wrong gateway/account
|
||||
- Mattermost still has old commands pointing at a previous callback target
|
||||
- the gateway restarted without reactivating slash commands
|
||||
- If native slash commands stop working, check logs for
|
||||
`mattermost: failed to register slash commands` or
|
||||
`mattermost: native slash commands enabled but no commands could be registered`.
|
||||
- If `callbackUrl` is omitted and logs warn that the callback resolved to
|
||||
`http://127.0.0.1:18789/...`, that URL is probably only reachable when
|
||||
Mattermost runs on the same host/network namespace as OpenClaw. Set an
|
||||
explicit externally reachable `commands.callbackUrl` instead.
|
||||
- Buttons appear as white boxes: the agent may be sending malformed button data. Check that each button has both `text` and `callback_data` fields.
|
||||
- Buttons render but clicks do nothing: verify `AllowedUntrustedInternalConnections` in Mattermost server config includes `127.0.0.1 localhost`, and that `EnablePostActionIntegration` is `true` in ServiceSettings.
|
||||
- Buttons return 404 on click: the button `id` likely contains hyphens or underscores. Mattermost's action router breaks on non-alphanumeric IDs. Use `[a-zA-Z0-9]` only.
|
||||
- Gateway logs `invalid _token`: HMAC mismatch. Check that you sign all context fields (not a subset), use sorted keys, and use compact JSON (no spaces). See the HMAC section above.
|
||||
- Gateway logs `missing _token in context`: the `_token` field is not in the button's context. Ensure it is included when building the integration payload.
|
||||
- Confirmation shows raw ID instead of button name: `context.action_id` does not match the button's `id`. Set both to the same sanitized value.
|
||||
- Agent doesn't know about buttons: add `capabilities: ["inlineButtons"]` to the Mattermost channel config.
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
959
openclaw/docs/channels/msteams.md
Normal file
959
openclaw/docs/channels/msteams.md
Normal file
|
|
@ -0,0 +1,959 @@
|
|||
---
|
||||
summary: "Microsoft Teams bot support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Microsoft Teams channel features
|
||||
title: "Microsoft Teams"
|
||||
---
|
||||
|
||||
# Microsoft Teams
|
||||
|
||||
> "Abandon all hope, ye who enter here."
|
||||
|
||||
Updated: 2026-03-25
|
||||
|
||||
Status: text + DM attachments are supported; channel/group file sending requires `sharePointSiteId` + Graph permissions (see [Sending files in group chats](#sending-files-in-group-chats)). Polls are sent via Adaptive Cards. Message actions expose explicit `upload-file` for file-first sends.
|
||||
|
||||
## Bundled plugin
|
||||
|
||||
Microsoft Teams ships as a bundled plugin in current OpenClaw releases, so no
|
||||
separate install is required in the normal packaged build.
|
||||
|
||||
If you are on an older build or a custom install that excludes bundled Teams,
|
||||
install it manually:
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/msteams
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./path/to/local/msteams-plugin
|
||||
```
|
||||
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
|
||||
1. Ensure the Microsoft Teams plugin is available.
|
||||
- Current packaged OpenClaw releases already bundle it.
|
||||
- Older/custom installs can add it manually with the commands above.
|
||||
2. Create an **Azure Bot** (App ID + client secret + tenant ID).
|
||||
3. Configure OpenClaw with those credentials.
|
||||
4. Expose `/api/messages` (port 3978 by default) via a public URL or tunnel.
|
||||
5. Install the Teams app package and start the gateway.
|
||||
|
||||
Minimal config (client secret):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
enabled: true,
|
||||
appId: "<APP_ID>",
|
||||
appPassword: "<APP_PASSWORD>",
|
||||
tenantId: "<TENANT_ID>",
|
||||
webhook: { port: 3978, path: "/api/messages" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
For production deployments, consider using [federated authentication](#federated-authentication-certificate--managed-identity) (certificate or managed identity) instead of client secrets.
|
||||
|
||||
Note: group chats are blocked by default (`channels.msteams.groupPolicy: "allowlist"`). To allow group replies, set `channels.msteams.groupAllowFrom` (or use `groupPolicy: "open"` to allow any member, mention-gated).
|
||||
|
||||
## Goals
|
||||
|
||||
- Talk to OpenClaw via Teams DMs, group chats, or channels.
|
||||
- Keep routing deterministic: replies always go back to the channel they arrived on.
|
||||
- Default to safe channel behavior (mentions required unless configured otherwise).
|
||||
|
||||
## Config writes
|
||||
|
||||
By default, Microsoft Teams is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||
|
||||
Disable with:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: { msteams: { configWrites: false } },
|
||||
}
|
||||
```
|
||||
|
||||
## Access control (DMs + groups)
|
||||
|
||||
**DM access**
|
||||
|
||||
- Default: `channels.msteams.dmPolicy = "pairing"`. Unknown senders are ignored until approved.
|
||||
- `channels.msteams.allowFrom` should use stable AAD object IDs.
|
||||
- UPNs/display names are mutable; direct matching is disabled by default and only enabled with `channels.msteams.dangerouslyAllowNameMatching: true`.
|
||||
- The wizard can resolve names to IDs via Microsoft Graph when credentials allow.
|
||||
|
||||
**Group access**
|
||||
|
||||
- Default: `channels.msteams.groupPolicy = "allowlist"` (blocked unless you add `groupAllowFrom`). Use `channels.defaults.groupPolicy` to override the default when unset.
|
||||
- `channels.msteams.groupAllowFrom` controls which senders can trigger in group chats/channels (falls back to `channels.msteams.allowFrom`).
|
||||
- Set `groupPolicy: "open"` to allow any member (still mention‑gated by default).
|
||||
- To allow **no channels**, set `channels.msteams.groupPolicy: "disabled"`.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["user@org.com"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Teams + channel allowlist**
|
||||
|
||||
- Scope group/channel replies by listing teams and channels under `channels.msteams.teams`.
|
||||
- Keys should use stable team IDs and channel conversation IDs.
|
||||
- When `groupPolicy="allowlist"` and a teams allowlist is present, only listed teams/channels are accepted (mention‑gated).
|
||||
- The configure wizard accepts `Team/Channel` entries and stores them for you.
|
||||
- On startup, OpenClaw resolves team/channel and user allowlist names to IDs (when Graph permissions allow)
|
||||
and logs the mapping; unresolved team/channel names are kept as typed but ignored for routing by default unless `channels.msteams.dangerouslyAllowNameMatching: true` is enabled.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
groupPolicy: "allowlist",
|
||||
teams: {
|
||||
"My Team": {
|
||||
channels: {
|
||||
General: { requireMention: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
1. Ensure the Microsoft Teams plugin is available.
|
||||
- Current packaged OpenClaw releases already bundle it.
|
||||
- Older/custom installs can add it manually with the commands above.
|
||||
2. Create an **Azure Bot** (App ID + secret + tenant ID).
|
||||
3. Build a **Teams app package** that references the bot and includes the RSC permissions below.
|
||||
4. Upload/install the Teams app into a team (or personal scope for DMs).
|
||||
5. Configure `msteams` in `~/.openclaw/openclaw.json` (or env vars) and start the gateway.
|
||||
6. The gateway listens for Bot Framework webhook traffic on `/api/messages` by default.
|
||||
|
||||
## Azure Bot Setup (Prerequisites)
|
||||
|
||||
Before configuring OpenClaw, you need to create an Azure Bot resource.
|
||||
|
||||
### Step 1: Create Azure Bot
|
||||
|
||||
1. Go to [Create Azure Bot](https://portal.azure.com/#create/Microsoft.AzureBot)
|
||||
2. Fill in the **Basics** tab:
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | -------------------------------------------------------- |
|
||||
| **Bot handle** | Your bot name, e.g., `openclaw-msteams` (must be unique) |
|
||||
| **Subscription** | Select your Azure subscription |
|
||||
| **Resource group** | Create new or use existing |
|
||||
| **Pricing tier** | **Free** for dev/testing |
|
||||
| **Type of App** | **Single Tenant** (recommended - see note below) |
|
||||
| **Creation type** | **Create new Microsoft App ID** |
|
||||
|
||||
> **Deprecation notice:** Creation of new multi-tenant bots was deprecated after 2025-07-31. Use **Single Tenant** for new bots.
|
||||
|
||||
3. Click **Review + create** → **Create** (wait ~1-2 minutes)
|
||||
|
||||
### Step 2: Get Credentials
|
||||
|
||||
1. Go to your Azure Bot resource → **Configuration**
|
||||
2. Copy **Microsoft App ID** → this is your `appId`
|
||||
3. Click **Manage Password** → go to the App Registration
|
||||
4. Under **Certificates & secrets** → **New client secret** → copy the **Value** → this is your `appPassword`
|
||||
5. Go to **Overview** → copy **Directory (tenant) ID** → this is your `tenantId`
|
||||
|
||||
### Step 3: Configure Messaging Endpoint
|
||||
|
||||
1. In Azure Bot → **Configuration**
|
||||
2. Set **Messaging endpoint** to your webhook URL:
|
||||
- Production: `https://your-domain.com/api/messages`
|
||||
- Local dev: Use a tunnel (see [Local Development](#local-development-tunneling) below)
|
||||
|
||||
### Step 4: Enable Teams Channel
|
||||
|
||||
1. In Azure Bot → **Channels**
|
||||
2. Click **Microsoft Teams** → Configure → Save
|
||||
3. Accept the Terms of Service
|
||||
|
||||
## Federated Authentication (Certificate + Managed Identity)
|
||||
|
||||
> Added in 2026.3.24
|
||||
|
||||
For production deployments, OpenClaw supports **federated authentication** as a more secure alternative to client secrets. Two methods are available:
|
||||
|
||||
### Option A: Certificate-based authentication
|
||||
|
||||
Use a PEM certificate registered with your Entra ID app registration.
|
||||
|
||||
**Setup:**
|
||||
|
||||
1. Generate or obtain a certificate (PEM format with private key).
|
||||
2. In Entra ID → App Registration → **Certificates & secrets** → **Certificates** → Upload the public certificate.
|
||||
|
||||
**Config:**
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
enabled: true,
|
||||
appId: "<APP_ID>",
|
||||
tenantId: "<TENANT_ID>",
|
||||
authType: "federated",
|
||||
certificatePath: "/path/to/cert.pem",
|
||||
webhook: { port: 3978, path: "/api/messages" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Env vars:**
|
||||
|
||||
- `MSTEAMS_AUTH_TYPE=federated`
|
||||
- `MSTEAMS_CERTIFICATE_PATH=/path/to/cert.pem`
|
||||
|
||||
### Option B: Azure Managed Identity
|
||||
|
||||
Use Azure Managed Identity for passwordless authentication. This is ideal for deployments on Azure infrastructure (AKS, App Service, Azure VMs) where a managed identity is available.
|
||||
|
||||
**How it works:**
|
||||
|
||||
1. The bot pod/VM has a managed identity (system-assigned or user-assigned).
|
||||
2. A **federated identity credential** links the managed identity to the Entra ID app registration.
|
||||
3. At runtime, OpenClaw uses `@azure/identity` to acquire tokens from the Azure IMDS endpoint (`169.254.169.254`).
|
||||
4. The token is passed to the Teams SDK for bot authentication.
|
||||
|
||||
**Prerequisites:**
|
||||
|
||||
- Azure infrastructure with managed identity enabled (AKS workload identity, App Service, VM)
|
||||
- Federated identity credential created on the Entra ID app registration
|
||||
- Network access to IMDS (`169.254.169.254:80`) from the pod/VM
|
||||
|
||||
**Config (system-assigned managed identity):**
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
enabled: true,
|
||||
appId: "<APP_ID>",
|
||||
tenantId: "<TENANT_ID>",
|
||||
authType: "federated",
|
||||
useManagedIdentity: true,
|
||||
webhook: { port: 3978, path: "/api/messages" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Config (user-assigned managed identity):**
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
enabled: true,
|
||||
appId: "<APP_ID>",
|
||||
tenantId: "<TENANT_ID>",
|
||||
authType: "federated",
|
||||
useManagedIdentity: true,
|
||||
managedIdentityClientId: "<MI_CLIENT_ID>",
|
||||
webhook: { port: 3978, path: "/api/messages" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Env vars:**
|
||||
|
||||
- `MSTEAMS_AUTH_TYPE=federated`
|
||||
- `MSTEAMS_USE_MANAGED_IDENTITY=true`
|
||||
- `MSTEAMS_MANAGED_IDENTITY_CLIENT_ID=<client-id>` (only for user-assigned)
|
||||
|
||||
### AKS Workload Identity Setup
|
||||
|
||||
For AKS deployments using workload identity:
|
||||
|
||||
1. **Enable workload identity** on your AKS cluster.
|
||||
2. **Create a federated identity credential** on the Entra ID app registration:
|
||||
|
||||
```bash
|
||||
az ad app federated-credential create --id <APP_OBJECT_ID> --parameters '{
|
||||
"name": "my-bot-workload-identity",
|
||||
"issuer": "<AKS_OIDC_ISSUER_URL>",
|
||||
"subject": "system:serviceaccount:<NAMESPACE>:<SERVICE_ACCOUNT>",
|
||||
"audiences": ["api://AzureADTokenExchange"]
|
||||
}'
|
||||
```
|
||||
|
||||
3. **Annotate the Kubernetes service account** with the app client ID:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: my-bot-sa
|
||||
annotations:
|
||||
azure.workload.identity/client-id: "<APP_CLIENT_ID>"
|
||||
```
|
||||
|
||||
4. **Label the pod** for workload identity injection:
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
labels:
|
||||
azure.workload.identity/use: "true"
|
||||
```
|
||||
|
||||
5. **Ensure network access** to IMDS (`169.254.169.254`) — if using NetworkPolicy, add an egress rule allowing traffic to `169.254.169.254/32` on port 80.
|
||||
|
||||
### Auth type comparison
|
||||
|
||||
| Method | Config | Pros | Cons |
|
||||
| -------------------- | ---------------------------------------------- | ---------------------------------- | ------------------------------------- |
|
||||
| **Client secret** | `appPassword` | Simple setup | Secret rotation required, less secure |
|
||||
| **Certificate** | `authType: "federated"` + `certificatePath` | No shared secret over network | Certificate management overhead |
|
||||
| **Managed Identity** | `authType: "federated"` + `useManagedIdentity` | Passwordless, no secrets to manage | Azure infrastructure required |
|
||||
|
||||
**Default behavior:** When `authType` is not set, OpenClaw defaults to client secret authentication. Existing configurations continue to work without changes.
|
||||
|
||||
## Local Development (Tunneling)
|
||||
|
||||
Teams can't reach `localhost`. Use a tunnel for local development:
|
||||
|
||||
**Option A: ngrok**
|
||||
|
||||
```bash
|
||||
ngrok http 3978
|
||||
# Copy the https URL, e.g., https://abc123.ngrok.io
|
||||
# Set messaging endpoint to: https://abc123.ngrok.io/api/messages
|
||||
```
|
||||
|
||||
**Option B: Tailscale Funnel**
|
||||
|
||||
```bash
|
||||
tailscale funnel 3978
|
||||
# Use your Tailscale funnel URL as the messaging endpoint
|
||||
```
|
||||
|
||||
## Teams Developer Portal (Alternative)
|
||||
|
||||
Instead of manually creating a manifest ZIP, you can use the [Teams Developer Portal](https://dev.teams.microsoft.com/apps):
|
||||
|
||||
1. Click **+ New app**
|
||||
2. Fill in basic info (name, description, developer info)
|
||||
3. Go to **App features** → **Bot**
|
||||
4. Select **Enter a bot ID manually** and paste your Azure Bot App ID
|
||||
5. Check scopes: **Personal**, **Team**, **Group Chat**
|
||||
6. Click **Distribute** → **Download app package**
|
||||
7. In Teams: **Apps** → **Manage your apps** → **Upload a custom app** → select the ZIP
|
||||
|
||||
This is often easier than hand-editing JSON manifests.
|
||||
|
||||
## Testing the Bot
|
||||
|
||||
**Option A: Azure Web Chat (verify webhook first)**
|
||||
|
||||
1. In Azure Portal → your Azure Bot resource → **Test in Web Chat**
|
||||
2. Send a message - you should see a response
|
||||
3. This confirms your webhook endpoint works before Teams setup
|
||||
|
||||
**Option B: Teams (after app installation)**
|
||||
|
||||
1. Install the Teams app (sideload or org catalog)
|
||||
2. Find the bot in Teams and send a DM
|
||||
3. Check gateway logs for incoming activity
|
||||
|
||||
## Setup (minimal text-only)
|
||||
|
||||
1. **Ensure the Microsoft Teams plugin is available**
|
||||
- Current packaged OpenClaw releases already bundle it.
|
||||
- Older/custom installs can add it manually:
|
||||
- From npm: `openclaw plugins install @openclaw/msteams`
|
||||
- From a local checkout: `openclaw plugins install ./path/to/local/msteams-plugin`
|
||||
|
||||
2. **Bot registration**
|
||||
- Create an Azure Bot (see above) and note:
|
||||
- App ID
|
||||
- Client secret (App password)
|
||||
- Tenant ID (single-tenant)
|
||||
|
||||
3. **Teams app manifest**
|
||||
- Include a `bot` entry with `botId = <App ID>`.
|
||||
- Scopes: `personal`, `team`, `groupChat`.
|
||||
- `supportsFiles: true` (required for personal scope file handling).
|
||||
- Add RSC permissions (below).
|
||||
- Create icons: `outline.png` (32x32) and `color.png` (192x192).
|
||||
- Zip all three files together: `manifest.json`, `outline.png`, `color.png`.
|
||||
|
||||
4. **Configure OpenClaw**
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
enabled: true,
|
||||
appId: "<APP_ID>",
|
||||
appPassword: "<APP_PASSWORD>",
|
||||
tenantId: "<TENANT_ID>",
|
||||
webhook: { port: 3978, path: "/api/messages" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
You can also use environment variables instead of config keys:
|
||||
- `MSTEAMS_APP_ID`
|
||||
- `MSTEAMS_APP_PASSWORD`
|
||||
- `MSTEAMS_TENANT_ID`
|
||||
- `MSTEAMS_AUTH_TYPE` (optional: `"secret"` or `"federated"`)
|
||||
- `MSTEAMS_CERTIFICATE_PATH` (federated + certificate)
|
||||
- `MSTEAMS_CERTIFICATE_THUMBPRINT` (optional, not required for auth)
|
||||
- `MSTEAMS_USE_MANAGED_IDENTITY` (federated + managed identity)
|
||||
- `MSTEAMS_MANAGED_IDENTITY_CLIENT_ID` (user-assigned MI only)
|
||||
|
||||
5. **Bot endpoint**
|
||||
- Set the Azure Bot Messaging Endpoint to:
|
||||
- `https://<host>:3978/api/messages` (or your chosen path/port).
|
||||
|
||||
6. **Run the gateway**
|
||||
- The Teams channel starts automatically when the bundled or manually installed plugin is available and `msteams` config exists with credentials.
|
||||
|
||||
## Member info action
|
||||
|
||||
OpenClaw exposes a Graph-backed `member-info` action for Microsoft Teams so agents and automations can resolve channel member details (display name, email, role) directly from Microsoft Graph.
|
||||
|
||||
Requirements:
|
||||
|
||||
- `Member.Read.Group` RSC permission (already in the recommended manifest)
|
||||
- For cross-team lookups: `User.Read.All` Graph Application permission with admin consent
|
||||
|
||||
The action is gated by `channels.msteams.actions.memberInfo` (default: enabled when Graph credentials are available).
|
||||
|
||||
## History context
|
||||
|
||||
- `channels.msteams.historyLimit` controls how many recent channel/group messages are wrapped into the prompt.
|
||||
- Falls back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
||||
- Fetched thread history is filtered by sender allowlists (`allowFrom` / `groupAllowFrom`), so thread context seeding only includes messages from allowed senders.
|
||||
- Quoted attachment context (`ReplyTo*` derived from Teams reply HTML) is currently passed as received.
|
||||
- In other words, allowlists gate who can trigger the agent; only specific supplemental context paths are filtered today.
|
||||
- DM history can be limited with `channels.msteams.dmHistoryLimit` (user turns). Per-user overrides: `channels.msteams.dms["<user_id>"].historyLimit`.
|
||||
|
||||
## Current Teams RSC Permissions (Manifest)
|
||||
|
||||
These are the **existing resourceSpecific permissions** in our Teams app manifest. They only apply inside the team/chat where the app is installed.
|
||||
|
||||
**For channels (team scope):**
|
||||
|
||||
- `ChannelMessage.Read.Group` (Application) - receive all channel messages without @mention
|
||||
- `ChannelMessage.Send.Group` (Application)
|
||||
- `Member.Read.Group` (Application)
|
||||
- `Owner.Read.Group` (Application)
|
||||
- `ChannelSettings.Read.Group` (Application)
|
||||
- `TeamMember.Read.Group` (Application)
|
||||
- `TeamSettings.Read.Group` (Application)
|
||||
|
||||
**For group chats:**
|
||||
|
||||
- `ChatMessage.Read.Chat` (Application) - receive all group chat messages without @mention
|
||||
|
||||
## Example Teams Manifest (redacted)
|
||||
|
||||
Minimal, valid example with the required fields. Replace IDs and URLs.
|
||||
|
||||
```json5
|
||||
{
|
||||
$schema: "https://developer.microsoft.com/en-us/json-schemas/teams/v1.23/MicrosoftTeams.schema.json",
|
||||
manifestVersion: "1.23",
|
||||
version: "1.0.0",
|
||||
id: "00000000-0000-0000-0000-000000000000",
|
||||
name: { short: "OpenClaw" },
|
||||
developer: {
|
||||
name: "Your Org",
|
||||
websiteUrl: "https://example.com",
|
||||
privacyUrl: "https://example.com/privacy",
|
||||
termsOfUseUrl: "https://example.com/terms",
|
||||
},
|
||||
description: { short: "OpenClaw in Teams", full: "OpenClaw in Teams" },
|
||||
icons: { outline: "outline.png", color: "color.png" },
|
||||
accentColor: "#5B6DEF",
|
||||
bots: [
|
||||
{
|
||||
botId: "11111111-1111-1111-1111-111111111111",
|
||||
scopes: ["personal", "team", "groupChat"],
|
||||
isNotificationOnly: false,
|
||||
supportsCalling: false,
|
||||
supportsVideo: false,
|
||||
supportsFiles: true,
|
||||
},
|
||||
],
|
||||
webApplicationInfo: {
|
||||
id: "11111111-1111-1111-1111-111111111111",
|
||||
},
|
||||
authorization: {
|
||||
permissions: {
|
||||
resourceSpecific: [
|
||||
{ name: "ChannelMessage.Read.Group", type: "Application" },
|
||||
{ name: "ChannelMessage.Send.Group", type: "Application" },
|
||||
{ name: "Member.Read.Group", type: "Application" },
|
||||
{ name: "Owner.Read.Group", type: "Application" },
|
||||
{ name: "ChannelSettings.Read.Group", type: "Application" },
|
||||
{ name: "TeamMember.Read.Group", type: "Application" },
|
||||
{ name: "TeamSettings.Read.Group", type: "Application" },
|
||||
{ name: "ChatMessage.Read.Chat", type: "Application" },
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Manifest caveats (must-have fields)
|
||||
|
||||
- `bots[].botId` **must** match the Azure Bot App ID.
|
||||
- `webApplicationInfo.id` **must** match the Azure Bot App ID.
|
||||
- `bots[].scopes` must include the surfaces you plan to use (`personal`, `team`, `groupChat`).
|
||||
- `bots[].supportsFiles: true` is required for file handling in personal scope.
|
||||
- `authorization.permissions.resourceSpecific` must include channel read/send if you want channel traffic.
|
||||
|
||||
### Updating an existing app
|
||||
|
||||
To update an already-installed Teams app (e.g., to add RSC permissions):
|
||||
|
||||
1. Update your `manifest.json` with the new settings
|
||||
2. **Increment the `version` field** (e.g., `1.0.0` → `1.1.0`)
|
||||
3. **Re-zip** the manifest with icons (`manifest.json`, `outline.png`, `color.png`)
|
||||
4. Upload the new zip:
|
||||
- **Option A (Teams Admin Center):** Teams Admin Center → Teams apps → Manage apps → find your app → Upload new version
|
||||
- **Option B (Sideload):** In Teams → Apps → Manage your apps → Upload a custom app
|
||||
5. **For team channels:** Reinstall the app in each team for new permissions to take effect
|
||||
6. **Fully quit and relaunch Teams** (not just close the window) to clear cached app metadata
|
||||
|
||||
## Capabilities: RSC only vs Graph
|
||||
|
||||
### With **Teams RSC only** (app installed, no Graph API permissions)
|
||||
|
||||
Works:
|
||||
|
||||
- Read channel message **text** content.
|
||||
- Send channel message **text** content.
|
||||
- Receive **personal (DM)** file attachments.
|
||||
|
||||
Does NOT work:
|
||||
|
||||
- Channel/group **image or file contents** (payload only includes HTML stub).
|
||||
- Downloading attachments stored in SharePoint/OneDrive.
|
||||
- Reading message history (beyond the live webhook event).
|
||||
|
||||
### With **Teams RSC + Microsoft Graph Application permissions**
|
||||
|
||||
Adds:
|
||||
|
||||
- Downloading hosted contents (images pasted into messages).
|
||||
- Downloading file attachments stored in SharePoint/OneDrive.
|
||||
- Reading channel/chat message history via Graph.
|
||||
|
||||
### RSC vs Graph API
|
||||
|
||||
| Capability | RSC Permissions | Graph API |
|
||||
| ----------------------- | -------------------- | ----------------------------------- |
|
||||
| **Real-time messages** | Yes (via webhook) | No (polling only) |
|
||||
| **Historical messages** | No | Yes (can query history) |
|
||||
| **Setup complexity** | App manifest only | Requires admin consent + token flow |
|
||||
| **Works offline** | No (must be running) | Yes (query anytime) |
|
||||
|
||||
**Bottom line:** RSC is for real-time listening; Graph API is for historical access. For catching up on missed messages while offline, you need Graph API with `ChannelMessage.Read.All` (requires admin consent).
|
||||
|
||||
## Graph-enabled media + history (required for channels)
|
||||
|
||||
If you need images/files in **channels** or want to fetch **message history**, you must enable Microsoft Graph permissions and grant admin consent.
|
||||
|
||||
1. In Entra ID (Azure AD) **App Registration**, add Microsoft Graph **Application permissions**:
|
||||
- `ChannelMessage.Read.All` (channel attachments + history)
|
||||
- `Chat.Read.All` or `ChatMessage.Read.All` (group chats)
|
||||
2. **Grant admin consent** for the tenant.
|
||||
3. Bump the Teams app **manifest version**, re-upload, and **reinstall the app in Teams**.
|
||||
4. **Fully quit and relaunch Teams** to clear cached app metadata.
|
||||
|
||||
**Additional permission for user mentions:** User @mentions work out of the box for users in the conversation. However, if you want to dynamically search and mention users who are **not in the current conversation**, add `User.Read.All` (Application) permission and grant admin consent.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### Webhook timeouts
|
||||
|
||||
Teams delivers messages via HTTP webhook. If processing takes too long (e.g., slow LLM responses), you may see:
|
||||
|
||||
- Gateway timeouts
|
||||
- Teams retrying the message (causing duplicates)
|
||||
- Dropped replies
|
||||
|
||||
OpenClaw handles this by returning quickly and sending replies proactively, but very slow responses may still cause issues.
|
||||
|
||||
### Formatting
|
||||
|
||||
Teams markdown is more limited than Slack or Discord:
|
||||
|
||||
- Basic formatting works: **bold**, _italic_, `code`, links
|
||||
- Complex markdown (tables, nested lists) may not render correctly
|
||||
- Adaptive Cards are supported for polls and arbitrary card sends (see below)
|
||||
|
||||
## Configuration
|
||||
|
||||
Key settings (see `/gateway/configuration` for shared channel patterns):
|
||||
|
||||
- `channels.msteams.enabled`: enable/disable the channel.
|
||||
- `channels.msteams.appId`, `channels.msteams.appPassword`, `channels.msteams.tenantId`: bot credentials.
|
||||
- `channels.msteams.webhook.port` (default `3978`)
|
||||
- `channels.msteams.webhook.path` (default `/api/messages`)
|
||||
- `channels.msteams.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing)
|
||||
- `channels.msteams.allowFrom`: DM allowlist (AAD object IDs recommended). The wizard resolves names to IDs during setup when Graph access is available.
|
||||
- `channels.msteams.dangerouslyAllowNameMatching`: break-glass toggle to re-enable mutable UPN/display-name matching and direct team/channel name routing.
|
||||
- `channels.msteams.textChunkLimit`: outbound text chunk size.
|
||||
- `channels.msteams.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.msteams.mediaAllowHosts`: allowlist for inbound attachment hosts (defaults to Microsoft/Teams domains).
|
||||
- `channels.msteams.mediaAuthAllowHosts`: allowlist for attaching Authorization headers on media retries (defaults to Graph + Bot Framework hosts).
|
||||
- `channels.msteams.requireMention`: require @mention in channels/groups (default true).
|
||||
- `channels.msteams.replyStyle`: `thread | top-level` (see [Reply Style](#reply-style-threads-vs-posts)).
|
||||
- `channels.msteams.teams.<teamId>.replyStyle`: per-team override.
|
||||
- `channels.msteams.teams.<teamId>.requireMention`: per-team override.
|
||||
- `channels.msteams.teams.<teamId>.tools`: default per-team tool policy overrides (`allow`/`deny`/`alsoAllow`) used when a channel override is missing.
|
||||
- `channels.msteams.teams.<teamId>.toolsBySender`: default per-team per-sender tool policy overrides (`"*"` wildcard supported).
|
||||
- `channels.msteams.teams.<teamId>.channels.<conversationId>.replyStyle`: per-channel override.
|
||||
- `channels.msteams.teams.<teamId>.channels.<conversationId>.requireMention`: per-channel override.
|
||||
- `channels.msteams.teams.<teamId>.channels.<conversationId>.tools`: per-channel tool policy overrides (`allow`/`deny`/`alsoAllow`).
|
||||
- `channels.msteams.teams.<teamId>.channels.<conversationId>.toolsBySender`: per-channel per-sender tool policy overrides (`"*"` wildcard supported).
|
||||
- `toolsBySender` keys should use explicit prefixes:
|
||||
`id:`, `e164:`, `username:`, `name:` (legacy unprefixed keys still map to `id:` only).
|
||||
- `channels.msteams.actions.memberInfo`: enable or disable the Graph-backed member info action (default: enabled when Graph credentials are available).
|
||||
- `channels.msteams.authType`: authentication type — `"secret"` (default) or `"federated"`.
|
||||
- `channels.msteams.certificatePath`: path to PEM certificate file (federated + certificate auth).
|
||||
- `channels.msteams.certificateThumbprint`: certificate thumbprint (optional, not required for auth).
|
||||
- `channels.msteams.useManagedIdentity`: enable managed identity auth (federated mode).
|
||||
- `channels.msteams.managedIdentityClientId`: client ID for user-assigned managed identity.
|
||||
- `channels.msteams.sharePointSiteId`: SharePoint site ID for file uploads in group chats/channels (see [Sending files in group chats](#sending-files-in-group-chats)).
|
||||
|
||||
## Routing & Sessions
|
||||
|
||||
- Session keys follow the standard agent format (see [/concepts/session](/concepts/session)):
|
||||
- Direct messages share the main session (`agent:<agentId>:<mainKey>`).
|
||||
- Channel/group messages use conversation id:
|
||||
- `agent:<agentId>:msteams:channel:<conversationId>`
|
||||
- `agent:<agentId>:msteams:group:<conversationId>`
|
||||
|
||||
## Reply Style: Threads vs Posts
|
||||
|
||||
Teams recently introduced two channel UI styles over the same underlying data model:
|
||||
|
||||
| Style | Description | Recommended `replyStyle` |
|
||||
| ------------------------ | --------------------------------------------------------- | ------------------------ |
|
||||
| **Posts** (classic) | Messages appear as cards with threaded replies underneath | `thread` (default) |
|
||||
| **Threads** (Slack-like) | Messages flow linearly, more like Slack | `top-level` |
|
||||
|
||||
**The problem:** The Teams API does not expose which UI style a channel uses. If you use the wrong `replyStyle`:
|
||||
|
||||
- `thread` in a Threads-style channel → replies appear nested awkwardly
|
||||
- `top-level` in a Posts-style channel → replies appear as separate top-level posts instead of in-thread
|
||||
|
||||
**Solution:** Configure `replyStyle` per-channel based on how the channel is set up:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
replyStyle: "thread",
|
||||
teams: {
|
||||
"19:abc...@thread.tacv2": {
|
||||
channels: {
|
||||
"19:xyz...@thread.tacv2": {
|
||||
replyStyle: "top-level",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Attachments & Images
|
||||
|
||||
**Current limitations:**
|
||||
|
||||
- **DMs:** Images and file attachments work via Teams bot file APIs.
|
||||
- **Channels/groups:** Attachments live in M365 storage (SharePoint/OneDrive). The webhook payload only includes an HTML stub, not the actual file bytes. **Graph API permissions are required** to download channel attachments.
|
||||
- For explicit file-first sends, use `action=upload-file` with `media` / `filePath` / `path`; optional `message` becomes the accompanying text/comment, and `filename` overrides the uploaded name.
|
||||
|
||||
Without Graph permissions, channel messages with images will be received as text-only (the image content is not accessible to the bot).
|
||||
By default, OpenClaw only downloads media from Microsoft/Teams hostnames. Override with `channels.msteams.mediaAllowHosts` (use `["*"]` to allow any host).
|
||||
Authorization headers are only attached for hosts in `channels.msteams.mediaAuthAllowHosts` (defaults to Graph + Bot Framework hosts). Keep this list strict (avoid multi-tenant suffixes).
|
||||
|
||||
## Sending files in group chats
|
||||
|
||||
Bots can send files in DMs using the FileConsentCard flow (built-in). However, **sending files in group chats/channels** requires additional setup:
|
||||
|
||||
| Context | How files are sent | Setup needed |
|
||||
| ------------------------ | -------------------------------------------- | ----------------------------------------------- |
|
||||
| **DMs** | FileConsentCard → user accepts → bot uploads | Works out of the box |
|
||||
| **Group chats/channels** | Upload to SharePoint → share link | Requires `sharePointSiteId` + Graph permissions |
|
||||
| **Images (any context)** | Base64-encoded inline | Works out of the box |
|
||||
|
||||
### Why group chats need SharePoint
|
||||
|
||||
Bots don't have a personal OneDrive drive (the `/me/drive` Graph API endpoint doesn't work for application identities). To send files in group chats/channels, the bot uploads to a **SharePoint site** and creates a sharing link.
|
||||
|
||||
### Setup
|
||||
|
||||
1. **Add Graph API permissions** in Entra ID (Azure AD) → App Registration:
|
||||
- `Sites.ReadWrite.All` (Application) - upload files to SharePoint
|
||||
- `Chat.Read.All` (Application) - optional, enables per-user sharing links
|
||||
|
||||
2. **Grant admin consent** for the tenant.
|
||||
|
||||
3. **Get your SharePoint site ID:**
|
||||
|
||||
```bash
|
||||
# Via Graph Explorer or curl with a valid token:
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
"https://graph.microsoft.com/v1.0/sites/{hostname}:/{site-path}"
|
||||
|
||||
# Example: for a site at "contoso.sharepoint.com/sites/BotFiles"
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
"https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/BotFiles"
|
||||
|
||||
# Response includes: "id": "contoso.sharepoint.com,guid1,guid2"
|
||||
```
|
||||
|
||||
4. **Configure OpenClaw:**
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
// ... other config ...
|
||||
sharePointSiteId: "contoso.sharepoint.com,guid1,guid2",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Sharing behavior
|
||||
|
||||
| Permission | Sharing behavior |
|
||||
| --------------------------------------- | --------------------------------------------------------- |
|
||||
| `Sites.ReadWrite.All` only | Organization-wide sharing link (anyone in org can access) |
|
||||
| `Sites.ReadWrite.All` + `Chat.Read.All` | Per-user sharing link (only chat members can access) |
|
||||
|
||||
Per-user sharing is more secure as only the chat participants can access the file. If `Chat.Read.All` permission is missing, the bot falls back to organization-wide sharing.
|
||||
|
||||
### Fallback behavior
|
||||
|
||||
| Scenario | Result |
|
||||
| ------------------------------------------------- | -------------------------------------------------- |
|
||||
| Group chat + file + `sharePointSiteId` configured | Upload to SharePoint, send sharing link |
|
||||
| Group chat + file + no `sharePointSiteId` | Attempt OneDrive upload (may fail), send text only |
|
||||
| Personal chat + file | FileConsentCard flow (works without SharePoint) |
|
||||
| Any context + image | Base64-encoded inline (works without SharePoint) |
|
||||
|
||||
### Files stored location
|
||||
|
||||
Uploaded files are stored in a `/OpenClawShared/` folder in the configured SharePoint site's default document library.
|
||||
|
||||
## Polls (Adaptive Cards)
|
||||
|
||||
OpenClaw sends Teams polls as Adaptive Cards (there is no native Teams poll API).
|
||||
|
||||
- CLI: `openclaw message poll --channel msteams --target conversation:<id> ...`
|
||||
- Votes are recorded by the gateway in `~/.openclaw/msteams-polls.json`.
|
||||
- The gateway must stay online to record votes.
|
||||
- Polls do not auto-post result summaries yet (inspect the store file if needed).
|
||||
|
||||
## Adaptive Cards (arbitrary)
|
||||
|
||||
Send any Adaptive Card JSON to Teams users or conversations using the `message` tool or CLI.
|
||||
|
||||
The `card` parameter accepts an Adaptive Card JSON object. When `card` is provided, the message text is optional.
|
||||
|
||||
**Agent tool:**
|
||||
|
||||
```json5
|
||||
{
|
||||
action: "send",
|
||||
channel: "msteams",
|
||||
target: "user:<id>",
|
||||
card: {
|
||||
type: "AdaptiveCard",
|
||||
version: "1.5",
|
||||
body: [{ type: "TextBlock", text: "Hello!" }],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**CLI:**
|
||||
|
||||
```bash
|
||||
openclaw message send --channel msteams \
|
||||
--target "conversation:19:abc...@thread.tacv2" \
|
||||
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello!"}]}'
|
||||
```
|
||||
|
||||
See [Adaptive Cards documentation](https://adaptivecards.io/) for card schema and examples. For target format details, see [Target formats](#target-formats) below.
|
||||
|
||||
## Target formats
|
||||
|
||||
MSTeams targets use prefixes to distinguish between users and conversations:
|
||||
|
||||
| Target type | Format | Example |
|
||||
| ------------------- | -------------------------------- | --------------------------------------------------- |
|
||||
| User (by ID) | `user:<aad-object-id>` | `user:40a1a0ed-4ff2-4164-a219-55518990c197` |
|
||||
| User (by name) | `user:<display-name>` | `user:John Smith` (requires Graph API) |
|
||||
| Group/channel | `conversation:<conversation-id>` | `conversation:19:abc123...@thread.tacv2` |
|
||||
| Group/channel (raw) | `<conversation-id>` | `19:abc123...@thread.tacv2` (if contains `@thread`) |
|
||||
|
||||
**CLI examples:**
|
||||
|
||||
```bash
|
||||
# Send to a user by ID
|
||||
openclaw message send --channel msteams --target "user:40a1a0ed-..." --message "Hello"
|
||||
|
||||
# Send to a user by display name (triggers Graph API lookup)
|
||||
openclaw message send --channel msteams --target "user:John Smith" --message "Hello"
|
||||
|
||||
# Send to a group chat or channel
|
||||
openclaw message send --channel msteams --target "conversation:19:abc...@thread.tacv2" --message "Hello"
|
||||
|
||||
# Send an Adaptive Card to a conversation
|
||||
openclaw message send --channel msteams --target "conversation:19:abc...@thread.tacv2" \
|
||||
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello"}]}'
|
||||
```
|
||||
|
||||
**Agent tool examples:**
|
||||
|
||||
```json5
|
||||
{
|
||||
action: "send",
|
||||
channel: "msteams",
|
||||
target: "user:John Smith",
|
||||
message: "Hello!",
|
||||
}
|
||||
```
|
||||
|
||||
```json5
|
||||
{
|
||||
action: "send",
|
||||
channel: "msteams",
|
||||
target: "conversation:19:abc...@thread.tacv2",
|
||||
card: {
|
||||
type: "AdaptiveCard",
|
||||
version: "1.5",
|
||||
body: [{ type: "TextBlock", text: "Hello" }],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Note: Without the `user:` prefix, names default to group/team resolution. Always use `user:` when targeting people by display name.
|
||||
|
||||
## Proactive messaging
|
||||
|
||||
- Proactive messages are only possible **after** a user has interacted, because we store conversation references at that point.
|
||||
- See `/gateway/configuration` for `dmPolicy` and allowlist gating.
|
||||
|
||||
## Team and Channel IDs (Common Gotcha)
|
||||
|
||||
The `groupId` query parameter in Teams URLs is **NOT** the team ID used for configuration. Extract IDs from the URL path instead:
|
||||
|
||||
**Team URL:**
|
||||
|
||||
```
|
||||
https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?groupId=...
|
||||
└────────────────────────────┘
|
||||
Team ID (URL-decode this)
|
||||
```
|
||||
|
||||
**Channel URL:**
|
||||
|
||||
```
|
||||
https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=...
|
||||
└─────────────────────────┘
|
||||
Channel ID (URL-decode this)
|
||||
```
|
||||
|
||||
**For config:**
|
||||
|
||||
- Team ID = path segment after `/team/` (URL-decoded, e.g., `19:Bk4j...@thread.tacv2`)
|
||||
- Channel ID = path segment after `/channel/` (URL-decoded)
|
||||
- **Ignore** the `groupId` query parameter
|
||||
|
||||
## Private Channels
|
||||
|
||||
Bots have limited support in private channels:
|
||||
|
||||
| Feature | Standard Channels | Private Channels |
|
||||
| ---------------------------- | ----------------- | ---------------------- |
|
||||
| Bot installation | Yes | Limited |
|
||||
| Real-time messages (webhook) | Yes | May not work |
|
||||
| RSC permissions | Yes | May behave differently |
|
||||
| @mentions | Yes | If bot is accessible |
|
||||
| Graph API history | Yes | Yes (with permissions) |
|
||||
|
||||
**Workarounds if private channels don't work:**
|
||||
|
||||
1. Use standard channels for bot interactions
|
||||
2. Use DMs - users can always message the bot directly
|
||||
3. Use Graph API for historical access (requires `ChannelMessage.Read.All`)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common issues
|
||||
|
||||
- **Images not showing in channels:** Graph permissions or admin consent missing. Reinstall the Teams app and fully quit/reopen Teams.
|
||||
- **No responses in channel:** mentions are required by default; set `channels.msteams.requireMention=false` or configure per team/channel.
|
||||
- **Version mismatch (Teams still shows old manifest):** remove + re-add the app and fully quit Teams to refresh.
|
||||
- **401 Unauthorized from webhook:** Expected when testing manually without Azure JWT - means endpoint is reachable but auth failed. Use Azure Web Chat to test properly.
|
||||
|
||||
### Manifest upload errors
|
||||
|
||||
- **"Icon file cannot be empty":** The manifest references icon files that are 0 bytes. Create valid PNG icons (32x32 for `outline.png`, 192x192 for `color.png`).
|
||||
- **"webApplicationInfo.Id already in use":** The app is still installed in another team/chat. Find and uninstall it first, or wait 5-10 minutes for propagation.
|
||||
- **"Something went wrong" on upload:** Upload via [https://admin.teams.microsoft.com](https://admin.teams.microsoft.com) instead, open browser DevTools (F12) → Network tab, and check the response body for the actual error.
|
||||
- **Sideload failing:** Try "Upload an app to your org's app catalog" instead of "Upload a custom app" - this often bypasses sideload restrictions.
|
||||
|
||||
### RSC permissions not working
|
||||
|
||||
1. Verify `webApplicationInfo.id` matches your bot's App ID exactly
|
||||
2. Re-upload the app and reinstall in the team/chat
|
||||
3. Check if your org admin has blocked RSC permissions
|
||||
4. Confirm you're using the right scope: `ChannelMessage.Read.Group` for teams, `ChatMessage.Read.Chat` for group chats
|
||||
|
||||
## References
|
||||
|
||||
- [Create Azure Bot](https://learn.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) - Azure Bot setup guide
|
||||
- [Teams Developer Portal](https://dev.teams.microsoft.com/apps) - create/manage Teams apps
|
||||
- [Teams app manifest schema](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema)
|
||||
- [Receive channel messages with RSC](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/channel-messages-with-rsc)
|
||||
- [RSC permissions reference](https://learn.microsoft.com/en-us/microsoftteams/platform/graph-api/rsc/resource-specific-consent)
|
||||
- [Teams bot file handling](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/bots-filesv4) (channel/group requires Graph)
|
||||
- [Proactive messaging](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/send-proactive-messages)
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
149
openclaw/docs/channels/nextcloud-talk.md
Normal file
149
openclaw/docs/channels/nextcloud-talk.md
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
---
|
||||
summary: "Nextcloud Talk support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Nextcloud Talk channel features
|
||||
title: "Nextcloud Talk"
|
||||
---
|
||||
|
||||
# Nextcloud Talk
|
||||
|
||||
Status: bundled plugin (webhook bot). Direct messages, rooms, reactions, and markdown messages are supported.
|
||||
|
||||
## Bundled plugin
|
||||
|
||||
Nextcloud Talk ships as a bundled plugin in current OpenClaw releases, so
|
||||
normal packaged builds do not need a separate install.
|
||||
|
||||
If you are on an older build or a custom install that excludes Nextcloud Talk,
|
||||
install it manually:
|
||||
|
||||
Install via CLI (npm registry):
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/nextcloud-talk
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./path/to/local/nextcloud-talk-plugin
|
||||
```
|
||||
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
|
||||
1. Ensure the Nextcloud Talk plugin is available.
|
||||
- Current packaged OpenClaw releases already bundle it.
|
||||
- Older/custom installs can add it manually with the commands above.
|
||||
2. On your Nextcloud server, create a bot:
|
||||
|
||||
```bash
|
||||
./occ talk:bot:install "OpenClaw" "<shared-secret>" "<webhook-url>" --feature reaction
|
||||
```
|
||||
|
||||
3. Enable the bot in the target room settings.
|
||||
4. Configure OpenClaw:
|
||||
- Config: `channels.nextcloud-talk.baseUrl` + `channels.nextcloud-talk.botSecret`
|
||||
- Or env: `NEXTCLOUD_TALK_BOT_SECRET` (default account only)
|
||||
5. Restart the gateway (or finish setup).
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
"nextcloud-talk": {
|
||||
enabled: true,
|
||||
baseUrl: "https://cloud.example.com",
|
||||
botSecret: "shared-secret",
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Bots cannot initiate DMs. The user must message the bot first.
|
||||
- Webhook URL must be reachable by the Gateway; set `webhookPublicUrl` if behind a proxy.
|
||||
- Media uploads are not supported by the bot API; media is sent as URLs.
|
||||
- The webhook payload does not distinguish DMs vs rooms; set `apiUser` + `apiPassword` to enable room-type lookups (otherwise DMs are treated as rooms).
|
||||
|
||||
## Access control (DMs)
|
||||
|
||||
- Default: `channels.nextcloud-talk.dmPolicy = "pairing"`. Unknown senders get a pairing code.
|
||||
- Approve via:
|
||||
- `openclaw pairing list nextcloud-talk`
|
||||
- `openclaw pairing approve nextcloud-talk <CODE>`
|
||||
- Public DMs: `channels.nextcloud-talk.dmPolicy="open"` plus `channels.nextcloud-talk.allowFrom=["*"]`.
|
||||
- `allowFrom` matches Nextcloud user IDs only; display names are ignored.
|
||||
|
||||
## Rooms (groups)
|
||||
|
||||
- Default: `channels.nextcloud-talk.groupPolicy = "allowlist"` (mention-gated).
|
||||
- Allowlist rooms with `channels.nextcloud-talk.rooms`:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
"nextcloud-talk": {
|
||||
rooms: {
|
||||
"room-token": { requireMention: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- To allow no rooms, keep the allowlist empty or set `channels.nextcloud-talk.groupPolicy="disabled"`.
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Feature | Status |
|
||||
| --------------- | ------------- |
|
||||
| Direct messages | Supported |
|
||||
| Rooms | Supported |
|
||||
| Threads | Not supported |
|
||||
| Media | URL-only |
|
||||
| Reactions | Supported |
|
||||
| Native commands | Not supported |
|
||||
|
||||
## Configuration reference (Nextcloud Talk)
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
|
||||
- `channels.nextcloud-talk.enabled`: enable/disable channel startup.
|
||||
- `channels.nextcloud-talk.baseUrl`: Nextcloud instance URL.
|
||||
- `channels.nextcloud-talk.botSecret`: bot shared secret.
|
||||
- `channels.nextcloud-talk.botSecretFile`: regular-file secret path. Symlinks are rejected.
|
||||
- `channels.nextcloud-talk.apiUser`: API user for room lookups (DM detection).
|
||||
- `channels.nextcloud-talk.apiPassword`: API/app password for room lookups.
|
||||
- `channels.nextcloud-talk.apiPasswordFile`: API password file path.
|
||||
- `channels.nextcloud-talk.webhookPort`: webhook listener port (default: 8788).
|
||||
- `channels.nextcloud-talk.webhookHost`: webhook host (default: 0.0.0.0).
|
||||
- `channels.nextcloud-talk.webhookPath`: webhook path (default: /nextcloud-talk-webhook).
|
||||
- `channels.nextcloud-talk.webhookPublicUrl`: externally reachable webhook URL.
|
||||
- `channels.nextcloud-talk.dmPolicy`: `pairing | allowlist | open | disabled`.
|
||||
- `channels.nextcloud-talk.allowFrom`: DM allowlist (user IDs). `open` requires `"*"`.
|
||||
- `channels.nextcloud-talk.groupPolicy`: `allowlist | open | disabled`.
|
||||
- `channels.nextcloud-talk.groupAllowFrom`: group allowlist (user IDs).
|
||||
- `channels.nextcloud-talk.rooms`: per-room settings and allowlist.
|
||||
- `channels.nextcloud-talk.historyLimit`: group history limit (0 disables).
|
||||
- `channels.nextcloud-talk.dmHistoryLimit`: DM history limit (0 disables).
|
||||
- `channels.nextcloud-talk.dms`: per-DM overrides (historyLimit).
|
||||
- `channels.nextcloud-talk.textChunkLimit`: outbound text chunk size (chars).
|
||||
- `channels.nextcloud-talk.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.nextcloud-talk.blockStreaming`: disable block streaming for this channel.
|
||||
- `channels.nextcloud-talk.blockStreamingCoalesce`: block streaming coalesce tuning.
|
||||
- `channels.nextcloud-talk.mediaMaxMb`: inbound media cap (MB).
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
252
openclaw/docs/channels/nostr.md
Normal file
252
openclaw/docs/channels/nostr.md
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
---
|
||||
summary: "Nostr DM channel via NIP-04 encrypted messages"
|
||||
read_when:
|
||||
- You want OpenClaw to receive DMs via Nostr
|
||||
- You're setting up decentralized messaging
|
||||
title: "Nostr"
|
||||
---
|
||||
|
||||
# Nostr
|
||||
|
||||
**Status:** Optional bundled plugin (disabled by default until configured).
|
||||
|
||||
Nostr is a decentralized protocol for social networking. This channel enables OpenClaw to receive and respond to encrypted direct messages (DMs) via NIP-04.
|
||||
|
||||
## Bundled plugin
|
||||
|
||||
Current OpenClaw releases ship Nostr as a bundled plugin, so normal packaged
|
||||
builds do not need a separate install.
|
||||
|
||||
### Older/custom installs
|
||||
|
||||
- Onboarding (`openclaw onboard`) and `openclaw channels add` still surface
|
||||
Nostr from the shared channel catalog.
|
||||
- If your build excludes bundled Nostr, install it manually.
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/nostr
|
||||
```
|
||||
|
||||
Use a local checkout (dev workflows):
|
||||
|
||||
```bash
|
||||
openclaw plugins install --link <path-to-local-nostr-plugin>
|
||||
```
|
||||
|
||||
Restart the Gateway after installing or enabling plugins.
|
||||
|
||||
### Non-interactive setup
|
||||
|
||||
```bash
|
||||
openclaw channels add --channel nostr --private-key "$NOSTR_PRIVATE_KEY"
|
||||
openclaw channels add --channel nostr --private-key "$NOSTR_PRIVATE_KEY" --relay-urls "wss://relay.damus.io,wss://relay.primal.net"
|
||||
```
|
||||
|
||||
Use `--use-env` to keep `NOSTR_PRIVATE_KEY` in the environment instead of storing the key in config.
|
||||
|
||||
## Quick setup
|
||||
|
||||
1. Generate a Nostr keypair (if needed):
|
||||
|
||||
```bash
|
||||
# Using nak
|
||||
nak key generate
|
||||
```
|
||||
|
||||
2. Add to config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
nostr: {
|
||||
privateKey: "${NOSTR_PRIVATE_KEY}",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
3. Export the key:
|
||||
|
||||
```bash
|
||||
export NOSTR_PRIVATE_KEY="nsec1..."
|
||||
```
|
||||
|
||||
4. Restart the Gateway.
|
||||
|
||||
## Configuration reference
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| ------------ | -------- | ------------------------------------------- | ----------------------------------- |
|
||||
| `privateKey` | string | required | Private key in `nsec` or hex format |
|
||||
| `relays` | string[] | `['wss://relay.damus.io', 'wss://nos.lol']` | Relay URLs (WebSocket) |
|
||||
| `dmPolicy` | string | `pairing` | DM access policy |
|
||||
| `allowFrom` | string[] | `[]` | Allowed sender pubkeys |
|
||||
| `enabled` | boolean | `true` | Enable/disable channel |
|
||||
| `name` | string | - | Display name |
|
||||
| `profile` | object | - | NIP-01 profile metadata |
|
||||
|
||||
## Profile metadata
|
||||
|
||||
Profile data is published as a NIP-01 `kind:0` event. You can manage it from the Control UI (Channels -> Nostr -> Profile) or set it directly in config.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
nostr: {
|
||||
privateKey: "${NOSTR_PRIVATE_KEY}",
|
||||
profile: {
|
||||
name: "openclaw",
|
||||
displayName: "OpenClaw",
|
||||
about: "Personal assistant DM bot",
|
||||
picture: "https://example.com/avatar.png",
|
||||
banner: "https://example.com/banner.png",
|
||||
website: "https://example.com",
|
||||
nip05: "openclaw@example.com",
|
||||
lud16: "openclaw@example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Profile URLs must use `https://`.
|
||||
- Importing from relays merges fields and preserves local overrides.
|
||||
|
||||
## Access control
|
||||
|
||||
### DM policies
|
||||
|
||||
- **pairing** (default): unknown senders get a pairing code.
|
||||
- **allowlist**: only pubkeys in `allowFrom` can DM.
|
||||
- **open**: public inbound DMs (requires `allowFrom: ["*"]`).
|
||||
- **disabled**: ignore inbound DMs.
|
||||
|
||||
Enforcement notes:
|
||||
|
||||
- Inbound event signatures are verified before sender policy and NIP-04 decryption, so forged events are rejected early.
|
||||
- Pairing replies are sent without processing the original DM body.
|
||||
- Inbound DMs are rate-limited and oversized payloads are dropped before decrypt.
|
||||
|
||||
### Allowlist example
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
nostr: {
|
||||
privateKey: "${NOSTR_PRIVATE_KEY}",
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["npub1abc...", "npub1xyz..."],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Key formats
|
||||
|
||||
Accepted formats:
|
||||
|
||||
- **Private key:** `nsec...` or 64-char hex
|
||||
- **Pubkeys (`allowFrom`):** `npub...` or hex
|
||||
|
||||
## Relays
|
||||
|
||||
Defaults: `relay.damus.io` and `nos.lol`.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
nostr: {
|
||||
privateKey: "${NOSTR_PRIVATE_KEY}",
|
||||
relays: ["wss://relay.damus.io", "wss://relay.primal.net", "wss://nostr.wine"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Tips:
|
||||
|
||||
- Use 2-3 relays for redundancy.
|
||||
- Avoid too many relays (latency, duplication).
|
||||
- Paid relays can improve reliability.
|
||||
- Local relays are fine for testing (`ws://localhost:7777`).
|
||||
|
||||
## Protocol support
|
||||
|
||||
| NIP | Status | Description |
|
||||
| ------ | --------- | ------------------------------------- |
|
||||
| NIP-01 | Supported | Basic event format + profile metadata |
|
||||
| NIP-04 | Supported | Encrypted DMs (`kind:4`) |
|
||||
| NIP-17 | Planned | Gift-wrapped DMs |
|
||||
| NIP-44 | Planned | Versioned encryption |
|
||||
|
||||
## Testing
|
||||
|
||||
### Local relay
|
||||
|
||||
```bash
|
||||
# Start strfry
|
||||
docker run -p 7777:7777 ghcr.io/hoytech/strfry
|
||||
```
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
nostr: {
|
||||
privateKey: "${NOSTR_PRIVATE_KEY}",
|
||||
relays: ["ws://localhost:7777"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Manual test
|
||||
|
||||
1. Note the bot pubkey (npub) from logs.
|
||||
2. Open a Nostr client (Damus, Amethyst, etc.).
|
||||
3. DM the bot pubkey.
|
||||
4. Verify the response.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Not receiving messages
|
||||
|
||||
- Verify the private key is valid.
|
||||
- Ensure relay URLs are reachable and use `wss://` (or `ws://` for local).
|
||||
- Confirm `enabled` is not `false`.
|
||||
- Check Gateway logs for relay connection errors.
|
||||
|
||||
### Not sending responses
|
||||
|
||||
- Check relay accepts writes.
|
||||
- Verify outbound connectivity.
|
||||
- Watch for relay rate limits.
|
||||
|
||||
### Duplicate responses
|
||||
|
||||
- Expected when using multiple relays.
|
||||
- Messages are deduplicated by event ID; only the first delivery triggers a response.
|
||||
|
||||
## Security
|
||||
|
||||
- Never commit private keys.
|
||||
- Use environment variables for keys.
|
||||
- Consider `allowlist` for production bots.
|
||||
- Signatures are verified before sender policy, and sender policy is enforced before decrypt, so forged events are rejected early and unknown senders cannot force full crypto work.
|
||||
|
||||
## Limitations (MVP)
|
||||
|
||||
- Direct messages only (no group chats).
|
||||
- No media attachments.
|
||||
- NIP-04 only (NIP-17 gift-wrap planned).
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
135
openclaw/docs/channels/pairing.md
Normal file
135
openclaw/docs/channels/pairing.md
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
---
|
||||
summary: "Pairing overview: approve who can DM you + which nodes can join"
|
||||
read_when:
|
||||
- Setting up DM access control
|
||||
- Pairing a new iOS/Android node
|
||||
- Reviewing OpenClaw security posture
|
||||
title: "Pairing"
|
||||
---
|
||||
|
||||
# Pairing
|
||||
|
||||
“Pairing” is OpenClaw’s explicit **owner approval** step.
|
||||
It is used in two places:
|
||||
|
||||
1. **DM pairing** (who is allowed to talk to the bot)
|
||||
2. **Node pairing** (which devices/nodes are allowed to join the gateway network)
|
||||
|
||||
Security context: [Security](/gateway/security)
|
||||
|
||||
## 1) DM pairing (inbound chat access)
|
||||
|
||||
When a channel is configured with DM policy `pairing`, unknown senders get a short code and their message is **not processed** until you approve.
|
||||
|
||||
Default DM policies are documented in: [Security](/gateway/security)
|
||||
|
||||
Pairing codes:
|
||||
|
||||
- 8 characters, uppercase, no ambiguous chars (`0O1I`).
|
||||
- **Expire after 1 hour**. The bot only sends the pairing message when a new request is created (roughly once per hour per sender).
|
||||
- Pending DM pairing requests are capped at **3 per channel** by default; additional requests are ignored until one expires or is approved.
|
||||
|
||||
### Approve a sender
|
||||
|
||||
```bash
|
||||
openclaw pairing list telegram
|
||||
openclaw pairing approve telegram <CODE>
|
||||
```
|
||||
|
||||
Supported channels: `bluebubbles`, `discord`, `feishu`, `googlechat`, `imessage`, `irc`, `line`, `matrix`, `mattermost`, `msteams`, `nextcloud-talk`, `nostr`, `openclaw-weixin`, `signal`, `slack`, `synology-chat`, `telegram`, `twitch`, `whatsapp`, `zalo`, `zalouser`.
|
||||
|
||||
### Where the state lives
|
||||
|
||||
Stored under `~/.openclaw/credentials/`:
|
||||
|
||||
- Pending requests: `<channel>-pairing.json`
|
||||
- Approved allowlist store:
|
||||
- Default account: `<channel>-allowFrom.json`
|
||||
- Non-default account: `<channel>-<accountId>-allowFrom.json`
|
||||
|
||||
Account scoping behavior:
|
||||
|
||||
- Non-default accounts read/write only their scoped allowlist file.
|
||||
- Default account uses the channel-scoped unscoped allowlist file.
|
||||
|
||||
Treat these as sensitive (they gate access to your assistant).
|
||||
|
||||
Important: this store is for DM access. Group authorization is separate.
|
||||
Approving a DM pairing code does not automatically allow that sender to run group commands or control the bot in groups. For group access, configure the channel's explicit group allowlists (for example `groupAllowFrom`, `groups`, or per-group/per-topic overrides depending on the channel).
|
||||
|
||||
## 2) Node device pairing (iOS/Android/macOS/headless nodes)
|
||||
|
||||
Nodes connect to the Gateway as **devices** with `role: node`. The Gateway
|
||||
creates a device pairing request that must be approved.
|
||||
|
||||
### Pair via Telegram (recommended for iOS)
|
||||
|
||||
If you use the `device-pair` plugin, you can do first-time device pairing entirely from Telegram:
|
||||
|
||||
1. In Telegram, message your bot: `/pair`
|
||||
2. The bot replies with two messages: an instruction message and a separate **setup code** message (easy to copy/paste in Telegram).
|
||||
3. On your phone, open the OpenClaw iOS app → Settings → Gateway.
|
||||
4. Paste the setup code and connect.
|
||||
5. Back in Telegram: `/pair pending` (review request IDs, role, and scopes), then approve.
|
||||
|
||||
The setup code is a base64-encoded JSON payload that contains:
|
||||
|
||||
- `url`: the Gateway WebSocket URL (`ws://...` or `wss://...`)
|
||||
- `bootstrapToken`: a short-lived single-device bootstrap token used for the initial pairing handshake
|
||||
|
||||
That bootstrap token carries the built-in pairing bootstrap profile:
|
||||
|
||||
- primary handed-off `node` token stays `scopes: []`
|
||||
- any handed-off `operator` token stays bounded to the bootstrap allowlist:
|
||||
`operator.approvals`, `operator.read`, `operator.talk.secrets`, `operator.write`
|
||||
- bootstrap scope checks are role-prefixed, not one flat scope pool:
|
||||
operator scope entries only satisfy operator requests, and non-operator roles
|
||||
must still request scopes under their own role prefix
|
||||
|
||||
Treat the setup code like a password while it is valid.
|
||||
|
||||
### Approve a node device
|
||||
|
||||
```bash
|
||||
openclaw devices list
|
||||
openclaw devices approve <requestId>
|
||||
openclaw devices reject <requestId>
|
||||
```
|
||||
|
||||
If the same device retries with different auth details (for example different
|
||||
role/scopes/public key), the previous pending request is superseded and a new
|
||||
`requestId` is created.
|
||||
|
||||
Important: an already paired device does not get broader access silently. If it
|
||||
reconnects asking for more scopes or a broader role, OpenClaw keeps the
|
||||
existing approval as-is and creates a fresh pending upgrade request. Use
|
||||
`openclaw devices list` to compare the currently approved access with the newly
|
||||
requested access before you approve.
|
||||
|
||||
### Node pairing state storage
|
||||
|
||||
Stored under `~/.openclaw/devices/`:
|
||||
|
||||
- `pending.json` (short-lived; pending requests expire)
|
||||
- `paired.json` (paired devices + tokens)
|
||||
|
||||
### Notes
|
||||
|
||||
- The legacy `node.pair.*` API (CLI: `openclaw nodes pending|approve|reject|rename`) is a
|
||||
separate gateway-owned pairing store. WS nodes still require device pairing.
|
||||
- The pairing record is the durable source of truth for approved roles. Active
|
||||
device tokens stay bounded to that approved role set; a stray token entry
|
||||
outside the approved roles does not create new access.
|
||||
|
||||
## Related docs
|
||||
|
||||
- Security model + prompt injection: [Security](/gateway/security)
|
||||
- Updating safely (run doctor): [Updating](/install/updating)
|
||||
- Channel configs:
|
||||
- Telegram: [Telegram](/channels/telegram)
|
||||
- WhatsApp: [WhatsApp](/channels/whatsapp)
|
||||
- Signal: [Signal](/channels/signal)
|
||||
- BlueBubbles (iMessage): [BlueBubbles](/channels/bluebubbles)
|
||||
- iMessage (legacy): [iMessage](/channels/imessage)
|
||||
- Discord: [Discord](/channels/discord)
|
||||
- Slack: [Slack](/channels/slack)
|
||||
108
openclaw/docs/channels/qa-channel.md
Normal file
108
openclaw/docs/channels/qa-channel.md
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
---
|
||||
title: "QA Channel"
|
||||
summary: "Synthetic Slack-class channel plugin for deterministic OpenClaw QA scenarios"
|
||||
read_when:
|
||||
- You are wiring the synthetic QA transport into a local or CI test run
|
||||
- You need the bundled qa-channel config surface
|
||||
- You are iterating on end-to-end QA automation
|
||||
---
|
||||
|
||||
# QA Channel
|
||||
|
||||
`qa-channel` is a bundled synthetic message transport for automated OpenClaw QA.
|
||||
|
||||
It is not a production channel. It exists to exercise the same channel plugin
|
||||
boundary used by real transports while keeping state deterministic and fully
|
||||
inspectable.
|
||||
|
||||
## What it does today
|
||||
|
||||
- Slack-class target grammar:
|
||||
- `dm:<user>`
|
||||
- `channel:<room>`
|
||||
- `thread:<room>/<thread>`
|
||||
- HTTP-backed synthetic bus for:
|
||||
- inbound message injection
|
||||
- outbound transcript capture
|
||||
- thread creation
|
||||
- reactions
|
||||
- edits
|
||||
- deletes
|
||||
- search and read actions
|
||||
- Bundled host-side self-check runner that writes a Markdown report
|
||||
|
||||
## Config
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"qa-channel": {
|
||||
"baseUrl": "http://127.0.0.1:43123",
|
||||
"botUserId": "openclaw",
|
||||
"botDisplayName": "OpenClaw QA",
|
||||
"allowFrom": ["*"],
|
||||
"pollTimeoutMs": 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Supported account keys:
|
||||
|
||||
- `baseUrl`
|
||||
- `botUserId`
|
||||
- `botDisplayName`
|
||||
- `pollTimeoutMs`
|
||||
- `allowFrom`
|
||||
- `defaultTo`
|
||||
- `actions.messages`
|
||||
- `actions.reactions`
|
||||
- `actions.search`
|
||||
- `actions.threads`
|
||||
|
||||
## Runner
|
||||
|
||||
Current vertical slice:
|
||||
|
||||
```bash
|
||||
pnpm qa:e2e
|
||||
```
|
||||
|
||||
This now routes through the bundled `qa-lab` extension. It starts the in-repo
|
||||
QA bus, boots the bundled `qa-channel` runtime slice, runs a deterministic
|
||||
self-check, and writes a Markdown report under `.artifacts/qa-e2e/`.
|
||||
|
||||
Private debugger UI:
|
||||
|
||||
```bash
|
||||
pnpm qa:lab:up
|
||||
```
|
||||
|
||||
That one command builds the QA site, starts the Docker-backed gateway + QA Lab
|
||||
stack, and prints the QA Lab URL. From that site you can pick scenarios, choose
|
||||
the model lane, launch individual runs, and watch results live.
|
||||
|
||||
Full repo-backed QA suite:
|
||||
|
||||
```bash
|
||||
pnpm openclaw qa suite
|
||||
```
|
||||
|
||||
That launches the private QA debugger at a local URL, separate from the
|
||||
shipped Control UI bundle.
|
||||
|
||||
## Scope
|
||||
|
||||
Current scope is intentionally narrow:
|
||||
|
||||
- bus + plugin transport
|
||||
- threaded routing grammar
|
||||
- channel-owned message actions
|
||||
- Markdown reporting
|
||||
- Docker-backed QA site with run controls
|
||||
|
||||
Follow-up work will add:
|
||||
|
||||
- provider/model matrix execution
|
||||
- richer scenario discovery
|
||||
- OpenClaw-native orchestration later
|
||||
193
openclaw/docs/channels/qqbot.md
Normal file
193
openclaw/docs/channels/qqbot.md
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
---
|
||||
summary: "QQ Bot setup, config, and usage"
|
||||
read_when:
|
||||
- You want to connect OpenClaw to QQ
|
||||
- You need QQ Bot credential setup
|
||||
- You want QQ Bot group or private chat support
|
||||
title: QQ Bot
|
||||
---
|
||||
|
||||
# QQ Bot
|
||||
|
||||
QQ Bot connects to OpenClaw via the official QQ Bot API (WebSocket gateway). The
|
||||
plugin supports C2C private chat, group @messages, and guild channel messages with
|
||||
rich media (images, voice, video, files).
|
||||
|
||||
Status: bundled plugin. Direct messages, group chats, guild channels, and
|
||||
media are supported. Reactions and threads are not supported.
|
||||
|
||||
## Bundled plugin
|
||||
|
||||
Current OpenClaw releases bundle QQ Bot, so normal packaged builds do not need
|
||||
a separate `openclaw plugins install` step.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Go to the [QQ Open Platform](https://q.qq.com/) and scan the QR code with your
|
||||
phone QQ to register / log in.
|
||||
2. Click **Create Bot** to create a new QQ bot.
|
||||
3. Find **AppID** and **AppSecret** on the bot's settings page and copy them.
|
||||
|
||||
> AppSecret is not stored in plaintext — if you leave the page without saving it,
|
||||
> you'll have to regenerate a new one.
|
||||
|
||||
4. Add the channel:
|
||||
|
||||
```bash
|
||||
openclaw channels add --channel qqbot --token "AppID:AppSecret"
|
||||
```
|
||||
|
||||
5. Restart the Gateway.
|
||||
|
||||
Interactive setup paths:
|
||||
|
||||
```bash
|
||||
openclaw channels add
|
||||
openclaw configure --section channels
|
||||
```
|
||||
|
||||
## Configure
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
qqbot: {
|
||||
enabled: true,
|
||||
appId: "YOUR_APP_ID",
|
||||
clientSecret: "YOUR_APP_SECRET",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Default-account env vars:
|
||||
|
||||
- `QQBOT_APP_ID`
|
||||
- `QQBOT_CLIENT_SECRET`
|
||||
|
||||
File-backed AppSecret:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
qqbot: {
|
||||
enabled: true,
|
||||
appId: "YOUR_APP_ID",
|
||||
clientSecretFile: "/path/to/qqbot-secret.txt",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Env fallback applies to the default QQ Bot account only.
|
||||
- `openclaw channels add --channel qqbot --token-file ...` provides the
|
||||
AppSecret only; the AppID must already be set in config or `QQBOT_APP_ID`.
|
||||
- `clientSecret` also accepts SecretRef input, not just a plaintext string.
|
||||
|
||||
### Multi-account setup
|
||||
|
||||
Run multiple QQ bots under a single OpenClaw instance:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
qqbot: {
|
||||
enabled: true,
|
||||
appId: "111111111",
|
||||
clientSecret: "secret-of-bot-1",
|
||||
accounts: {
|
||||
bot2: {
|
||||
enabled: true,
|
||||
appId: "222222222",
|
||||
clientSecret: "secret-of-bot-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Each account launches its own WebSocket connection and maintains an independent
|
||||
token cache (isolated by `appId`).
|
||||
|
||||
Add a second bot via CLI:
|
||||
|
||||
```bash
|
||||
openclaw channels add --channel qqbot --account bot2 --token "222222222:secret-of-bot-2"
|
||||
```
|
||||
|
||||
### Voice (STT / TTS)
|
||||
|
||||
STT and TTS support two-level configuration with priority fallback:
|
||||
|
||||
| Setting | Plugin-specific | Framework fallback |
|
||||
| ------- | -------------------- | ----------------------------- |
|
||||
| STT | `channels.qqbot.stt` | `tools.media.audio.models[0]` |
|
||||
| TTS | `channels.qqbot.tts` | `messages.tts` |
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
qqbot: {
|
||||
stt: {
|
||||
provider: "your-provider",
|
||||
model: "your-stt-model",
|
||||
},
|
||||
tts: {
|
||||
provider: "your-provider",
|
||||
model: "your-tts-model",
|
||||
voice: "your-voice",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Set `enabled: false` on either to disable.
|
||||
|
||||
Outbound audio upload/transcode behavior can also be tuned with
|
||||
`channels.qqbot.audioFormatPolicy`:
|
||||
|
||||
- `sttDirectFormats`
|
||||
- `uploadDirectFormats`
|
||||
- `transcodeEnabled`
|
||||
|
||||
## Target formats
|
||||
|
||||
| Format | Description |
|
||||
| -------------------------- | ------------------ |
|
||||
| `qqbot:c2c:OPENID` | Private chat (C2C) |
|
||||
| `qqbot:group:GROUP_OPENID` | Group chat |
|
||||
| `qqbot:channel:CHANNEL_ID` | Guild channel |
|
||||
|
||||
> Each bot has its own set of user OpenIDs. An OpenID received by Bot A **cannot**
|
||||
> be used to send messages via Bot B.
|
||||
|
||||
## Slash commands
|
||||
|
||||
Built-in commands intercepted before the AI queue:
|
||||
|
||||
| Command | Description |
|
||||
| -------------- | ------------------------------------ |
|
||||
| `/bot-ping` | Latency test |
|
||||
| `/bot-version` | Show the OpenClaw framework version |
|
||||
| `/bot-help` | List all commands |
|
||||
| `/bot-upgrade` | Show the QQBot upgrade guide link |
|
||||
| `/bot-logs` | Export recent gateway logs as a file |
|
||||
|
||||
Append `?` to any command for usage help (for example `/bot-upgrade ?`).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Bot replies "gone to Mars":** credentials not configured or Gateway not started.
|
||||
- **No inbound messages:** verify `appId` and `clientSecret` are correct, and the
|
||||
bot is enabled on the QQ Open Platform.
|
||||
- **Setup with `--token-file` still shows unconfigured:** `--token-file` only sets
|
||||
the AppSecret. You still need `appId` in config or `QQBOT_APP_ID`.
|
||||
- **Proactive messages not arriving:** QQ may intercept bot-initiated messages if
|
||||
the user hasn't interacted recently.
|
||||
- **Voice not transcribed:** ensure STT is configured and the provider is reachable.
|
||||
337
openclaw/docs/channels/signal.md
Normal file
337
openclaw/docs/channels/signal.md
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
---
|
||||
summary: "Signal support via signal-cli (JSON-RPC + SSE), setup paths, and number model"
|
||||
read_when:
|
||||
- Setting up Signal support
|
||||
- Debugging Signal send/receive
|
||||
title: "Signal"
|
||||
---
|
||||
|
||||
# Signal (signal-cli)
|
||||
|
||||
Status: external CLI integration. Gateway talks to `signal-cli` over HTTP JSON-RPC + SSE.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- OpenClaw installed on your server (Linux flow below tested on Ubuntu 24).
|
||||
- `signal-cli` available on the host where the gateway runs.
|
||||
- A phone number that can receive one verification SMS (for SMS registration path).
|
||||
- Browser access for Signal captcha (`signalcaptchas.org`) during registration.
|
||||
|
||||
## Quick setup (beginner)
|
||||
|
||||
1. Use a **separate Signal number** for the bot (recommended).
|
||||
2. Install `signal-cli` (Java required if you use the JVM build).
|
||||
3. Choose one setup path:
|
||||
- **Path A (QR link):** `signal-cli link -n "OpenClaw"` and scan with Signal.
|
||||
- **Path B (SMS register):** register a dedicated number with captcha + SMS verification.
|
||||
4. Configure OpenClaw and restart the gateway.
|
||||
5. Send a first DM and approve pairing (`openclaw pairing approve signal <CODE>`).
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
signal: {
|
||||
enabled: true,
|
||||
account: "+15551234567",
|
||||
cliPath: "signal-cli",
|
||||
dmPolicy: "pairing",
|
||||
allowFrom: ["+15557654321"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Field reference:
|
||||
|
||||
| Field | Description |
|
||||
| ----------- | ------------------------------------------------- |
|
||||
| `account` | Bot phone number in E.164 format (`+15551234567`) |
|
||||
| `cliPath` | Path to `signal-cli` (`signal-cli` if on `PATH`) |
|
||||
| `dmPolicy` | DM access policy (`pairing` recommended) |
|
||||
| `allowFrom` | Phone numbers or `uuid:<id>` values allowed to DM |
|
||||
|
||||
## What it is
|
||||
|
||||
- Signal channel via `signal-cli` (not embedded libsignal).
|
||||
- Deterministic routing: replies always go back to Signal.
|
||||
- DMs share the agent's main session; groups are isolated (`agent:<agentId>:signal:group:<groupId>`).
|
||||
|
||||
## Config writes
|
||||
|
||||
By default, Signal is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||
|
||||
Disable with:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: { signal: { configWrites: false } },
|
||||
}
|
||||
```
|
||||
|
||||
## The number model (important)
|
||||
|
||||
- The gateway connects to a **Signal device** (the `signal-cli` account).
|
||||
- If you run the bot on **your personal Signal account**, it will ignore your own messages (loop protection).
|
||||
- For "I text the bot and it replies," use a **separate bot number**.
|
||||
|
||||
## Setup path A: link existing Signal account (QR)
|
||||
|
||||
1. Install `signal-cli` (JVM or native build).
|
||||
2. Link a bot account:
|
||||
- `signal-cli link -n "OpenClaw"` then scan the QR in Signal.
|
||||
3. Configure Signal and start the gateway.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
signal: {
|
||||
enabled: true,
|
||||
account: "+15551234567",
|
||||
cliPath: "signal-cli",
|
||||
dmPolicy: "pairing",
|
||||
allowFrom: ["+15557654321"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Multi-account support: use `channels.signal.accounts` with per-account config and optional `name`. See [`gateway/configuration`](/gateway/configuration-reference#multi-account-all-channels) for the shared pattern.
|
||||
|
||||
## Setup path B: register dedicated bot number (SMS, Linux)
|
||||
|
||||
Use this when you want a dedicated bot number instead of linking an existing Signal app account.
|
||||
|
||||
1. Get a number that can receive SMS (or voice verification for landlines).
|
||||
- Use a dedicated bot number to avoid account/session conflicts.
|
||||
2. Install `signal-cli` on the gateway host:
|
||||
|
||||
```bash
|
||||
VERSION=$(curl -Ls -o /dev/null -w %{url_effective} https://github.com/AsamK/signal-cli/releases/latest | sed -e 's/^.*\/v//')
|
||||
curl -L -O "https://github.com/AsamK/signal-cli/releases/download/v${VERSION}/signal-cli-${VERSION}-Linux-native.tar.gz"
|
||||
sudo tar xf "signal-cli-${VERSION}-Linux-native.tar.gz" -C /opt
|
||||
sudo ln -sf /opt/signal-cli /usr/local/bin/
|
||||
signal-cli --version
|
||||
```
|
||||
|
||||
If you use the JVM build (`signal-cli-${VERSION}.tar.gz`), install JRE 25+ first.
|
||||
Keep `signal-cli` updated; upstream notes that old releases can break as Signal server APIs change.
|
||||
|
||||
3. Register and verify the number:
|
||||
|
||||
```bash
|
||||
signal-cli -a +<BOT_PHONE_NUMBER> register
|
||||
```
|
||||
|
||||
If captcha is required:
|
||||
|
||||
1. Open `https://signalcaptchas.org/registration/generate.html`.
|
||||
2. Complete captcha, copy the `signalcaptcha://...` link target from "Open Signal".
|
||||
3. Run from the same external IP as the browser session when possible.
|
||||
4. Run registration again immediately (captcha tokens expire quickly):
|
||||
|
||||
```bash
|
||||
signal-cli -a +<BOT_PHONE_NUMBER> register --captcha '<SIGNALCAPTCHA_URL>'
|
||||
signal-cli -a +<BOT_PHONE_NUMBER> verify <VERIFICATION_CODE>
|
||||
```
|
||||
|
||||
4. Configure OpenClaw, restart gateway, verify channel:
|
||||
|
||||
```bash
|
||||
# If you run the gateway as a user systemd service:
|
||||
systemctl --user restart openclaw-gateway.service
|
||||
|
||||
# Then verify:
|
||||
openclaw doctor
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
5. Pair your DM sender:
|
||||
- Send any message to the bot number.
|
||||
- Approve code on the server: `openclaw pairing approve signal <PAIRING_CODE>`.
|
||||
- Save the bot number as a contact on your phone to avoid "Unknown contact".
|
||||
|
||||
Important: registering a phone number account with `signal-cli` can de-authenticate the main Signal app session for that number. Prefer a dedicated bot number, or use QR link mode if you need to keep your existing phone app setup.
|
||||
|
||||
Upstream references:
|
||||
|
||||
- `signal-cli` README: `https://github.com/AsamK/signal-cli`
|
||||
- Captcha flow: `https://github.com/AsamK/signal-cli/wiki/Registration-with-captcha`
|
||||
- Linking flow: `https://github.com/AsamK/signal-cli/wiki/Linking-other-devices-(Provisioning)`
|
||||
|
||||
## External daemon mode (httpUrl)
|
||||
|
||||
If you want to manage `signal-cli` yourself (slow JVM cold starts, container init, or shared CPUs), run the daemon separately and point OpenClaw at it:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
signal: {
|
||||
httpUrl: "http://127.0.0.1:8080",
|
||||
autoStart: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
This skips auto-spawn and the startup wait inside OpenClaw. For slow starts when auto-spawning, set `channels.signal.startupTimeoutMs`.
|
||||
|
||||
## Access control (DMs + groups)
|
||||
|
||||
DMs:
|
||||
|
||||
- Default: `channels.signal.dmPolicy = "pairing"`.
|
||||
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
- `openclaw pairing list signal`
|
||||
- `openclaw pairing approve signal <CODE>`
|
||||
- Pairing is the default token exchange for Signal DMs. Details: [Pairing](/channels/pairing)
|
||||
- UUID-only senders (from `sourceUuid`) are stored as `uuid:<id>` in `channels.signal.allowFrom`.
|
||||
|
||||
Groups:
|
||||
|
||||
- `channels.signal.groupPolicy = open | allowlist | disabled`.
|
||||
- `channels.signal.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
||||
- `channels.signal.groups["<group-id>" | "*"]` can override group behavior with `requireMention`, `tools`, and `toolsBySender`.
|
||||
- Use `channels.signal.accounts.<id>.groups` for per-account overrides in multi-account setups.
|
||||
- Runtime note: if `channels.signal` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
## How it works (behavior)
|
||||
|
||||
- `signal-cli` runs as a daemon; the gateway reads events via SSE.
|
||||
- Inbound messages are normalized into the shared channel envelope.
|
||||
- Replies always route back to the same number or group.
|
||||
|
||||
## Media + limits
|
||||
|
||||
- Outbound text is chunked to `channels.signal.textChunkLimit` (default 4000).
|
||||
- Optional newline chunking: set `channels.signal.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- Attachments supported (base64 fetched from `signal-cli`).
|
||||
- Default media cap: `channels.signal.mediaMaxMb` (default 8).
|
||||
- Use `channels.signal.ignoreAttachments` to skip downloading media.
|
||||
- Group history context uses `channels.signal.historyLimit` (or `channels.signal.accounts.*.historyLimit`), falling back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
||||
|
||||
## Typing + read receipts
|
||||
|
||||
- **Typing indicators**: OpenClaw sends typing signals via `signal-cli sendTyping` and refreshes them while a reply is running.
|
||||
- **Read receipts**: when `channels.signal.sendReadReceipts` is true, OpenClaw forwards read receipts for allowed DMs.
|
||||
- Signal-cli does not expose read receipts for groups.
|
||||
|
||||
## Reactions (message tool)
|
||||
|
||||
- Use `message action=react` with `channel=signal`.
|
||||
- Targets: sender E.164 or UUID (use `uuid:<id>` from pairing output; bare UUID works too).
|
||||
- `messageId` is the Signal timestamp for the message you’re reacting to.
|
||||
- Group reactions require `targetAuthor` or `targetAuthorUuid`.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
message action=react channel=signal target=uuid:123e4567-e89b-12d3-a456-426614174000 messageId=1737630212345 emoji=🔥
|
||||
message action=react channel=signal target=+15551234567 messageId=1737630212345 emoji=🔥 remove=true
|
||||
message action=react channel=signal target=signal:group:<groupId> targetAuthor=uuid:<sender-uuid> messageId=1737630212345 emoji=✅
|
||||
```
|
||||
|
||||
Config:
|
||||
|
||||
- `channels.signal.actions.reactions`: enable/disable reaction actions (default true).
|
||||
- `channels.signal.reactionLevel`: `off | ack | minimal | extensive`.
|
||||
- `off`/`ack` disables agent reactions (message tool `react` will error).
|
||||
- `minimal`/`extensive` enables agent reactions and sets the guidance level.
|
||||
- Per-account overrides: `channels.signal.accounts.<id>.actions.reactions`, `channels.signal.accounts.<id>.reactionLevel`.
|
||||
|
||||
## Delivery targets (CLI/cron)
|
||||
|
||||
- DMs: `signal:+15551234567` (or plain E.164).
|
||||
- UUID DMs: `uuid:<id>` (or bare UUID).
|
||||
- Groups: `signal:group:<groupId>`.
|
||||
- Usernames: `username:<name>` (if supported by your Signal account).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Run this ladder first:
|
||||
|
||||
```bash
|
||||
openclaw status
|
||||
openclaw gateway status
|
||||
openclaw logs --follow
|
||||
openclaw doctor
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
Then confirm DM pairing state if needed:
|
||||
|
||||
```bash
|
||||
openclaw pairing list signal
|
||||
```
|
||||
|
||||
Common failures:
|
||||
|
||||
- Daemon reachable but no replies: verify account/daemon settings (`httpUrl`, `account`) and receive mode.
|
||||
- DMs ignored: sender is pending pairing approval.
|
||||
- Group messages ignored: group sender/mention gating blocks delivery.
|
||||
- Config validation errors after edits: run `openclaw doctor --fix`.
|
||||
- Signal missing from diagnostics: confirm `channels.signal.enabled: true`.
|
||||
|
||||
Extra checks:
|
||||
|
||||
```bash
|
||||
openclaw pairing list signal
|
||||
pgrep -af signal-cli
|
||||
grep -i "signal" "/tmp/openclaw/openclaw-$(date +%Y-%m-%d).log" | tail -20
|
||||
```
|
||||
|
||||
For triage flow: [/channels/troubleshooting](/channels/troubleshooting).
|
||||
|
||||
## Security notes
|
||||
|
||||
- `signal-cli` stores account keys locally (typically `~/.local/share/signal-cli/data/`).
|
||||
- Back up Signal account state before server migration or rebuild.
|
||||
- Keep `channels.signal.dmPolicy: "pairing"` unless you explicitly want broader DM access.
|
||||
- SMS verification is only needed for registration or recovery flows, but losing control of the number/account can complicate re-registration.
|
||||
|
||||
## Configuration reference (Signal)
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
|
||||
- `channels.signal.enabled`: enable/disable channel startup.
|
||||
- `channels.signal.account`: E.164 for the bot account.
|
||||
- `channels.signal.cliPath`: path to `signal-cli`.
|
||||
- `channels.signal.httpUrl`: full daemon URL (overrides host/port).
|
||||
- `channels.signal.httpHost`, `channels.signal.httpPort`: daemon bind (default 127.0.0.1:8080).
|
||||
- `channels.signal.autoStart`: auto-spawn daemon (default true if `httpUrl` unset).
|
||||
- `channels.signal.startupTimeoutMs`: startup wait timeout in ms (cap 120000).
|
||||
- `channels.signal.receiveMode`: `on-start | manual`.
|
||||
- `channels.signal.ignoreAttachments`: skip attachment downloads.
|
||||
- `channels.signal.ignoreStories`: ignore stories from the daemon.
|
||||
- `channels.signal.sendReadReceipts`: forward read receipts.
|
||||
- `channels.signal.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing).
|
||||
- `channels.signal.allowFrom`: DM allowlist (E.164 or `uuid:<id>`). `open` requires `"*"`. Signal has no usernames; use phone/UUID ids.
|
||||
- `channels.signal.groupPolicy`: `open | allowlist | disabled` (default: allowlist).
|
||||
- `channels.signal.groupAllowFrom`: group sender allowlist.
|
||||
- `channels.signal.groups`: per-group overrides keyed by Signal group id (or `"*"`). Supported fields: `requireMention`, `tools`, `toolsBySender`.
|
||||
- `channels.signal.accounts.<id>.groups`: per-account version of `channels.signal.groups` for multi-account setups.
|
||||
- `channels.signal.historyLimit`: max group messages to include as context (0 disables).
|
||||
- `channels.signal.dmHistoryLimit`: DM history limit in user turns. Per-user overrides: `channels.signal.dms["<phone_or_uuid>"].historyLimit`.
|
||||
- `channels.signal.textChunkLimit`: outbound chunk size (chars).
|
||||
- `channels.signal.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.signal.mediaMaxMb`: inbound/outbound media cap (MB).
|
||||
|
||||
Related global options:
|
||||
|
||||
- `agents.list[].groupChat.mentionPatterns` (Signal does not support native mentions).
|
||||
- `messages.groupChat.mentionPatterns` (global fallback).
|
||||
- `messages.responsePrefix`.
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
1054
openclaw/docs/channels/slack.md
Normal file
1054
openclaw/docs/channels/slack.md
Normal file
File diff suppressed because it is too large
Load diff
185
openclaw/docs/channels/synology-chat.md
Normal file
185
openclaw/docs/channels/synology-chat.md
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
---
|
||||
summary: "Synology Chat webhook setup and OpenClaw config"
|
||||
read_when:
|
||||
- Setting up Synology Chat with OpenClaw
|
||||
- Debugging Synology Chat webhook routing
|
||||
title: "Synology Chat"
|
||||
---
|
||||
|
||||
# Synology Chat
|
||||
|
||||
Status: bundled plugin direct-message channel using Synology Chat webhooks.
|
||||
The plugin accepts inbound messages from Synology Chat outgoing webhooks and sends replies
|
||||
through a Synology Chat incoming webhook.
|
||||
|
||||
## Bundled plugin
|
||||
|
||||
Synology Chat ships as a bundled plugin in current OpenClaw releases, so normal
|
||||
packaged builds do not need a separate install.
|
||||
|
||||
If you are on an older build or a custom install that excludes Synology Chat,
|
||||
install it manually:
|
||||
|
||||
Install from a local checkout:
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./path/to/local/synology-chat-plugin
|
||||
```
|
||||
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Quick setup
|
||||
|
||||
1. Ensure the Synology Chat plugin is available.
|
||||
- Current packaged OpenClaw releases already bundle it.
|
||||
- Older/custom installs can add it manually from a source checkout with the command above.
|
||||
- `openclaw onboard` now shows Synology Chat in the same channel setup list as `openclaw channels add`.
|
||||
- Non-interactive setup: `openclaw channels add --channel synology-chat --token <token> --url <incoming-webhook-url>`
|
||||
2. In Synology Chat integrations:
|
||||
- Create an incoming webhook and copy its URL.
|
||||
- Create an outgoing webhook with your secret token.
|
||||
3. Point the outgoing webhook URL to your OpenClaw gateway:
|
||||
- `https://gateway-host/webhook/synology` by default.
|
||||
- Or your custom `channels.synology-chat.webhookPath`.
|
||||
4. Finish setup in OpenClaw.
|
||||
- Guided: `openclaw onboard`
|
||||
- Direct: `openclaw channels add --channel synology-chat --token <token> --url <incoming-webhook-url>`
|
||||
5. Restart gateway and send a DM to the Synology Chat bot.
|
||||
|
||||
Webhook auth details:
|
||||
|
||||
- OpenClaw accepts the outgoing webhook token from `body.token`, then
|
||||
`?token=...`, then headers.
|
||||
- Accepted header forms:
|
||||
- `x-synology-token`
|
||||
- `x-webhook-token`
|
||||
- `x-openclaw-token`
|
||||
- `Authorization: Bearer <token>`
|
||||
- Empty or missing tokens fail closed.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
"synology-chat": {
|
||||
enabled: true,
|
||||
token: "synology-outgoing-token",
|
||||
incomingUrl: "https://nas.example.com/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=2&token=...",
|
||||
webhookPath: "/webhook/synology",
|
||||
dmPolicy: "allowlist",
|
||||
allowedUserIds: ["123456"],
|
||||
rateLimitPerMinute: 30,
|
||||
allowInsecureSsl: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
For the default account, you can use env vars:
|
||||
|
||||
- `SYNOLOGY_CHAT_TOKEN`
|
||||
- `SYNOLOGY_CHAT_INCOMING_URL`
|
||||
- `SYNOLOGY_NAS_HOST`
|
||||
- `SYNOLOGY_ALLOWED_USER_IDS` (comma-separated)
|
||||
- `SYNOLOGY_RATE_LIMIT`
|
||||
- `OPENCLAW_BOT_NAME`
|
||||
|
||||
Config values override env vars.
|
||||
|
||||
## DM policy and access control
|
||||
|
||||
- `dmPolicy: "allowlist"` is the recommended default.
|
||||
- `allowedUserIds` accepts a list (or comma-separated string) of Synology user IDs.
|
||||
- In `allowlist` mode, an empty `allowedUserIds` list is treated as misconfiguration and the webhook route will not start (use `dmPolicy: "open"` for allow-all).
|
||||
- `dmPolicy: "open"` allows any sender.
|
||||
- `dmPolicy: "disabled"` blocks DMs.
|
||||
- Reply recipient binding stays on stable numeric `user_id` by default. `channels.synology-chat.dangerouslyAllowNameMatching: true` is break-glass compatibility mode that re-enables mutable username/nickname lookup for reply delivery.
|
||||
- Pairing approvals work with:
|
||||
- `openclaw pairing list synology-chat`
|
||||
- `openclaw pairing approve synology-chat <CODE>`
|
||||
|
||||
## Outbound delivery
|
||||
|
||||
Use numeric Synology Chat user IDs as targets.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
openclaw message send --channel synology-chat --target 123456 --text "Hello from OpenClaw"
|
||||
openclaw message send --channel synology-chat --target synology-chat:123456 --text "Hello again"
|
||||
```
|
||||
|
||||
Media sends are supported by URL-based file delivery.
|
||||
|
||||
## Multi-account
|
||||
|
||||
Multiple Synology Chat accounts are supported under `channels.synology-chat.accounts`.
|
||||
Each account can override token, incoming URL, webhook path, DM policy, and limits.
|
||||
Direct-message sessions are isolated per account and user, so the same numeric `user_id`
|
||||
on two different Synology accounts does not share transcript state.
|
||||
Give each enabled account a distinct `webhookPath`. OpenClaw now rejects duplicate exact paths
|
||||
and refuses to start named accounts that only inherit a shared webhook path in multi-account setups.
|
||||
If you intentionally need legacy inheritance for a named account, set
|
||||
`dangerouslyAllowInheritedWebhookPath: true` on that account or at `channels.synology-chat`,
|
||||
but duplicate exact paths are still rejected fail-closed. Prefer explicit per-account paths.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
"synology-chat": {
|
||||
enabled: true,
|
||||
accounts: {
|
||||
default: {
|
||||
token: "token-a",
|
||||
incomingUrl: "https://nas-a.example.com/...token=...",
|
||||
},
|
||||
alerts: {
|
||||
token: "token-b",
|
||||
incomingUrl: "https://nas-b.example.com/...token=...",
|
||||
webhookPath: "/webhook/synology-alerts",
|
||||
dmPolicy: "allowlist",
|
||||
allowedUserIds: ["987654"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Security notes
|
||||
|
||||
- Keep `token` secret and rotate it if leaked.
|
||||
- Keep `allowInsecureSsl: false` unless you explicitly trust a self-signed local NAS cert.
|
||||
- Inbound webhook requests are token-verified and rate-limited per sender.
|
||||
- Invalid token checks use constant-time secret comparison and fail closed.
|
||||
- Prefer `dmPolicy: "allowlist"` for production.
|
||||
- Keep `dangerouslyAllowNameMatching` off unless you explicitly need legacy username-based reply delivery.
|
||||
- Keep `dangerouslyAllowInheritedWebhookPath` off unless you explicitly accept shared-path routing risk in a multi-account setup.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- `Missing required fields (token, user_id, text)`:
|
||||
- the outgoing webhook payload is missing one of the required fields
|
||||
- if Synology sends the token in headers, make sure the gateway/proxy preserves those headers
|
||||
- `Invalid token`:
|
||||
- the outgoing webhook secret does not match `channels.synology-chat.token`
|
||||
- the request is hitting the wrong account/webhook path
|
||||
- a reverse proxy stripped the token header before the request reached OpenClaw
|
||||
- `Rate limit exceeded`:
|
||||
- too many invalid token attempts from the same source can temporarily lock that source out
|
||||
- authenticated senders also have a separate per-user message rate limit
|
||||
- `Allowlist is empty. Configure allowedUserIds or use dmPolicy=open.`:
|
||||
- `dmPolicy="allowlist"` is enabled but no users are configured
|
||||
- `User not authorized`:
|
||||
- the sender's numeric `user_id` is not in `allowedUserIds`
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
1072
openclaw/docs/channels/telegram.md
Normal file
1072
openclaw/docs/channels/telegram.md
Normal file
File diff suppressed because it is too large
Load diff
290
openclaw/docs/channels/tlon.md
Normal file
290
openclaw/docs/channels/tlon.md
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
---
|
||||
summary: "Tlon/Urbit support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Tlon/Urbit channel features
|
||||
title: "Tlon"
|
||||
---
|
||||
|
||||
# Tlon
|
||||
|
||||
Tlon is a decentralized messenger built on Urbit. OpenClaw connects to your Urbit ship and can
|
||||
respond to DMs and group chat messages. Group replies require an @ mention by default and can
|
||||
be further restricted via allowlists.
|
||||
|
||||
Status: bundled plugin. DMs, group mentions, thread replies, rich text formatting, and
|
||||
image uploads are supported. Reactions and polls are not yet supported.
|
||||
|
||||
## Bundled plugin
|
||||
|
||||
Tlon ships as a bundled plugin in current OpenClaw releases, so normal packaged
|
||||
builds do not need a separate install.
|
||||
|
||||
If you are on an older build or a custom install that excludes Tlon, install it
|
||||
manually:
|
||||
|
||||
Install via CLI (npm registry):
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/tlon
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./path/to/local/tlon-plugin
|
||||
```
|
||||
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Setup
|
||||
|
||||
1. Ensure the Tlon plugin is available.
|
||||
- Current packaged OpenClaw releases already bundle it.
|
||||
- Older/custom installs can add it manually with the commands above.
|
||||
2. Gather your ship URL and login code.
|
||||
3. Configure `channels.tlon`.
|
||||
4. Restart the gateway.
|
||||
5. DM the bot or mention it in a group channel.
|
||||
|
||||
Minimal config (single account):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
enabled: true,
|
||||
ship: "~sampel-palnet",
|
||||
url: "https://your-ship-host",
|
||||
code: "lidlut-tabwed-pillex-ridrup",
|
||||
ownerShip: "~your-main-ship", // recommended: your ship, always allowed
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Private/LAN ships
|
||||
|
||||
By default, OpenClaw blocks private/internal hostnames and IP ranges for SSRF protection.
|
||||
If your ship is running on a private network (localhost, LAN IP, or internal hostname),
|
||||
you must explicitly opt in:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
url: "http://localhost:8080",
|
||||
allowPrivateNetwork: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
This applies to URLs like:
|
||||
|
||||
- `http://localhost:8080`
|
||||
- `http://192.168.x.x:8080`
|
||||
- `http://my-ship.local:8080`
|
||||
|
||||
⚠️ Only enable this if you trust your local network. This setting disables SSRF protections
|
||||
for requests to your ship URL.
|
||||
|
||||
## Group channels
|
||||
|
||||
Auto-discovery is enabled by default. You can also pin channels manually:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
groupChannels: ["chat/~host-ship/general", "chat/~host-ship/support"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Disable auto-discovery:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
autoDiscoverChannels: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Access control
|
||||
|
||||
DM allowlist (empty = no DMs allowed, use `ownerShip` for approval flow):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
dmAllowlist: ["~zod", "~nec"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Group authorization (restricted by default):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
defaultAuthorizedShips: ["~zod"],
|
||||
authorization: {
|
||||
channelRules: {
|
||||
"chat/~host-ship/general": {
|
||||
mode: "restricted",
|
||||
allowedShips: ["~zod", "~nec"],
|
||||
},
|
||||
"chat/~host-ship/announcements": {
|
||||
mode: "open",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Owner and approval system
|
||||
|
||||
Set an owner ship to receive approval requests when unauthorized users try to interact:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
ownerShip: "~your-main-ship",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The owner ship is **automatically authorized everywhere** — DM invites are auto-accepted and
|
||||
channel messages are always allowed. You don't need to add the owner to `dmAllowlist` or
|
||||
`defaultAuthorizedShips`.
|
||||
|
||||
When set, the owner receives DM notifications for:
|
||||
|
||||
- DM requests from ships not in the allowlist
|
||||
- Mentions in channels without authorization
|
||||
- Group invite requests
|
||||
|
||||
## Auto-accept settings
|
||||
|
||||
Auto-accept DM invites (for ships in dmAllowlist):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
autoAcceptDmInvites: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Auto-accept group invites:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
autoAcceptGroupInvites: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Delivery targets (CLI/cron)
|
||||
|
||||
Use these with `openclaw message send` or cron delivery:
|
||||
|
||||
- DM: `~sampel-palnet` or `dm/~sampel-palnet`
|
||||
- Group: `chat/~host-ship/channel` or `group:~host-ship/channel`
|
||||
|
||||
## Bundled skill
|
||||
|
||||
The Tlon plugin includes a bundled skill ([`@tloncorp/tlon-skill`](https://github.com/tloncorp/tlon-skill))
|
||||
that provides CLI access to Tlon operations:
|
||||
|
||||
- **Contacts**: get/update profiles, list contacts
|
||||
- **Channels**: list, create, post messages, fetch history
|
||||
- **Groups**: list, create, manage members
|
||||
- **DMs**: send messages, react to messages
|
||||
- **Reactions**: add/remove emoji reactions to posts and DMs
|
||||
- **Settings**: manage plugin permissions via slash commands
|
||||
|
||||
The skill is automatically available when the plugin is installed.
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Feature | Status |
|
||||
| --------------- | --------------------------------------- |
|
||||
| Direct messages | ✅ Supported |
|
||||
| Groups/channels | ✅ Supported (mention-gated by default) |
|
||||
| Threads | ✅ Supported (auto-replies in thread) |
|
||||
| Rich text | ✅ Markdown converted to Tlon format |
|
||||
| Images | ✅ Uploaded to Tlon storage |
|
||||
| Reactions | ✅ Via [bundled skill](#bundled-skill) |
|
||||
| Polls | ❌ Not yet supported |
|
||||
| Native commands | ✅ Supported (owner-only by default) |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Run this ladder first:
|
||||
|
||||
```bash
|
||||
openclaw status
|
||||
openclaw gateway status
|
||||
openclaw logs --follow
|
||||
openclaw doctor
|
||||
```
|
||||
|
||||
Common failures:
|
||||
|
||||
- **DMs ignored**: sender not in `dmAllowlist` and no `ownerShip` configured for approval flow.
|
||||
- **Group messages ignored**: channel not discovered or sender not authorized.
|
||||
- **Connection errors**: check ship URL is reachable; enable `allowPrivateNetwork` for local ships.
|
||||
- **Auth errors**: verify login code is current (codes rotate).
|
||||
|
||||
## Configuration reference
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
|
||||
- `channels.tlon.enabled`: enable/disable channel startup.
|
||||
- `channels.tlon.ship`: bot's Urbit ship name (e.g. `~sampel-palnet`).
|
||||
- `channels.tlon.url`: ship URL (e.g. `https://sampel-palnet.tlon.network`).
|
||||
- `channels.tlon.code`: ship login code.
|
||||
- `channels.tlon.allowPrivateNetwork`: allow localhost/LAN URLs (SSRF bypass).
|
||||
- `channels.tlon.ownerShip`: owner ship for approval system (always authorized).
|
||||
- `channels.tlon.dmAllowlist`: ships allowed to DM (empty = none).
|
||||
- `channels.tlon.autoAcceptDmInvites`: auto-accept DMs from allowlisted ships.
|
||||
- `channels.tlon.autoAcceptGroupInvites`: auto-accept all group invites.
|
||||
- `channels.tlon.autoDiscoverChannels`: auto-discover group channels (default: true).
|
||||
- `channels.tlon.groupChannels`: manually pinned channel nests.
|
||||
- `channels.tlon.defaultAuthorizedShips`: ships authorized for all channels.
|
||||
- `channels.tlon.authorization.channelRules`: per-channel auth rules.
|
||||
- `channels.tlon.showModelSignature`: append model name to messages.
|
||||
|
||||
## Notes
|
||||
|
||||
- Group replies require a mention (e.g. `~your-bot-ship`) to respond.
|
||||
- Thread replies: if the inbound message is in a thread, OpenClaw replies in-thread.
|
||||
- Rich text: Markdown formatting (bold, italic, code, headers, lists) is converted to Tlon's native format.
|
||||
- Images: URLs are uploaded to Tlon storage and embedded as image blocks.
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
134
openclaw/docs/channels/troubleshooting.md
Normal file
134
openclaw/docs/channels/troubleshooting.md
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
---
|
||||
summary: "Fast channel level troubleshooting with per channel failure signatures and fixes"
|
||||
read_when:
|
||||
- Channel transport says connected but replies fail
|
||||
- You need channel specific checks before deep provider docs
|
||||
title: "Channel Troubleshooting"
|
||||
---
|
||||
|
||||
# Channel troubleshooting
|
||||
|
||||
Use this page when a channel connects but behavior is wrong.
|
||||
|
||||
## Command ladder
|
||||
|
||||
Run these in order first:
|
||||
|
||||
```bash
|
||||
openclaw status
|
||||
openclaw gateway status
|
||||
openclaw logs --follow
|
||||
openclaw doctor
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
Healthy baseline:
|
||||
|
||||
- `Runtime: running`
|
||||
- `Connectivity probe: ok`
|
||||
- `Capability: read-only`, `write-capable`, or `admin-capable`
|
||||
- Channel probe shows transport connected and, where supported, `works` or `audit ok`
|
||||
|
||||
## WhatsApp
|
||||
|
||||
### WhatsApp failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| ------------------------------- | --------------------------------------------------- | ------------------------------------------------------- |
|
||||
| Connected but no DM replies | `openclaw pairing list whatsapp` | Approve sender or switch DM policy/allowlist. |
|
||||
| Group messages ignored | Check `requireMention` + mention patterns in config | Mention the bot or relax mention policy for that group. |
|
||||
| Random disconnect/relogin loops | `openclaw channels status --probe` + logs | Re-login and verify credentials directory is healthy. |
|
||||
|
||||
Full troubleshooting: [/channels/whatsapp#troubleshooting](/channels/whatsapp#troubleshooting)
|
||||
|
||||
## Telegram
|
||||
|
||||
### Telegram failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| ----------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------- |
|
||||
| `/start` but no usable reply flow | `openclaw pairing list telegram` | Approve pairing or change DM policy. |
|
||||
| Bot online but group stays silent | Verify mention requirement and bot privacy mode | Disable privacy mode for group visibility or mention bot. |
|
||||
| Send failures with network errors | Inspect logs for Telegram API call failures | Fix DNS/IPv6/proxy routing to `api.telegram.org`. |
|
||||
| `setMyCommands` rejected at startup | Inspect logs for `BOT_COMMANDS_TOO_MUCH` | Reduce plugin/skill/custom Telegram commands or disable native menus. |
|
||||
| Upgraded and allowlist blocks you | `openclaw security audit` and config allowlists | Run `openclaw doctor --fix` or replace `@username` with numeric sender IDs. |
|
||||
|
||||
Full troubleshooting: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting)
|
||||
|
||||
## Discord
|
||||
|
||||
### Discord failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| ------------------------------- | ----------------------------------- | --------------------------------------------------------- |
|
||||
| Bot online but no guild replies | `openclaw channels status --probe` | Allow guild/channel and verify message content intent. |
|
||||
| Group messages ignored | Check logs for mention gating drops | Mention bot or set guild/channel `requireMention: false`. |
|
||||
| DM replies missing | `openclaw pairing list discord` | Approve DM pairing or adjust DM policy. |
|
||||
|
||||
Full troubleshooting: [/channels/discord#troubleshooting](/channels/discord#troubleshooting)
|
||||
|
||||
## Slack
|
||||
|
||||
### Slack failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| -------------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Socket mode connected but no responses | `openclaw channels status --probe` | Verify app token + bot token and required scopes; watch for `botTokenStatus` / `appTokenStatus = configured_unavailable` on SecretRef-backed setups. |
|
||||
| DMs blocked | `openclaw pairing list slack` | Approve pairing or relax DM policy. |
|
||||
| Channel message ignored | Check `groupPolicy` and channel allowlist | Allow the channel or switch policy to `open`. |
|
||||
|
||||
Full troubleshooting: [/channels/slack#troubleshooting](/channels/slack#troubleshooting)
|
||||
|
||||
## iMessage and BlueBubbles
|
||||
|
||||
### iMessage and BlueBubbles failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| -------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------- |
|
||||
| No inbound events | Verify webhook/server reachability and app permissions | Fix webhook URL or BlueBubbles server state. |
|
||||
| Can send but no receive on macOS | Check macOS privacy permissions for Messages automation | Re-grant TCC permissions and restart channel process. |
|
||||
| DM sender blocked | `openclaw pairing list imessage` or `openclaw pairing list bluebubbles` | Approve pairing or update allowlist. |
|
||||
|
||||
Full troubleshooting:
|
||||
|
||||
- [/channels/imessage#troubleshooting](/channels/imessage#troubleshooting)
|
||||
- [/channels/bluebubbles#troubleshooting](/channels/bluebubbles#troubleshooting)
|
||||
|
||||
## Signal
|
||||
|
||||
### Signal failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| ------------------------------- | ------------------------------------------ | -------------------------------------------------------- |
|
||||
| Daemon reachable but bot silent | `openclaw channels status --probe` | Verify `signal-cli` daemon URL/account and receive mode. |
|
||||
| DM blocked | `openclaw pairing list signal` | Approve sender or adjust DM policy. |
|
||||
| Group replies do not trigger | Check group allowlist and mention patterns | Add sender/group or loosen gating. |
|
||||
|
||||
Full troubleshooting: [/channels/signal#troubleshooting](/channels/signal#troubleshooting)
|
||||
|
||||
## QQ Bot
|
||||
|
||||
### QQ Bot failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| ------------------------------- | ------------------------------------------- | --------------------------------------------------------------- |
|
||||
| Bot replies "gone to Mars" | Verify `appId` and `clientSecret` in config | Set credentials or restart the gateway. |
|
||||
| No inbound messages | `openclaw channels status --probe` | Verify credentials on the QQ Open Platform. |
|
||||
| Voice not transcribed | Check STT provider config | Configure `channels.qqbot.stt` or `tools.media.audio`. |
|
||||
| Proactive messages not arriving | Check QQ platform interaction requirements | QQ may block bot-initiated messages without recent interaction. |
|
||||
|
||||
Full troubleshooting: [/channels/qqbot#troubleshooting](/channels/qqbot#troubleshooting)
|
||||
|
||||
## Matrix
|
||||
|
||||
### Matrix failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| ----------------------------------- | -------------------------------------- | ------------------------------------------------------------------------- |
|
||||
| Logged in but ignores room messages | `openclaw channels status --probe` | Check `groupPolicy`, room allowlist, and mention gating. |
|
||||
| DMs do not process | `openclaw pairing list matrix` | Approve sender or adjust DM policy. |
|
||||
| Encrypted rooms fail | `openclaw matrix verify status` | Re-verify the device, then check `openclaw matrix verify backup status`. |
|
||||
| Backup restore is pending/broken | `openclaw matrix verify backup status` | Run `openclaw matrix verify backup restore` or rerun with a recovery key. |
|
||||
| Cross-signing/bootstrap looks wrong | `openclaw matrix verify bootstrap` | Repair secret storage, cross-signing, and backup state in one pass. |
|
||||
|
||||
Full setup and config: [Matrix](/channels/matrix)
|
||||
394
openclaw/docs/channels/twitch.md
Normal file
394
openclaw/docs/channels/twitch.md
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
---
|
||||
summary: "Twitch chat bot configuration and setup"
|
||||
read_when:
|
||||
- Setting up Twitch chat integration for OpenClaw
|
||||
title: "Twitch"
|
||||
---
|
||||
|
||||
# Twitch
|
||||
|
||||
Twitch chat support via IRC connection. OpenClaw connects as a Twitch user (bot account) to receive and send messages in channels.
|
||||
|
||||
## Bundled plugin
|
||||
|
||||
Twitch ships as a bundled plugin in current OpenClaw releases, so normal
|
||||
packaged builds do not need a separate install.
|
||||
|
||||
If you are on an older build or a custom install that excludes Twitch, install
|
||||
it manually:
|
||||
|
||||
Install via CLI (npm registry):
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/twitch
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./path/to/local/twitch-plugin
|
||||
```
|
||||
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
|
||||
1. Ensure the Twitch plugin is available.
|
||||
- Current packaged OpenClaw releases already bundle it.
|
||||
- Older/custom installs can add it manually with the commands above.
|
||||
2. Create a dedicated Twitch account for the bot (or use an existing account).
|
||||
3. Generate credentials: [Twitch Token Generator](https://twitchtokengenerator.com/)
|
||||
- Select **Bot Token**
|
||||
- Verify scopes `chat:read` and `chat:write` are selected
|
||||
- Copy the **Client ID** and **Access Token**
|
||||
4. Find your Twitch user ID: [https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/)
|
||||
5. Configure the token:
|
||||
- Env: `OPENCLAW_TWITCH_ACCESS_TOKEN=...` (default account only)
|
||||
- Or config: `channels.twitch.accessToken`
|
||||
- If both are set, config takes precedence (env fallback is default-account only).
|
||||
6. Start the gateway.
|
||||
|
||||
**⚠️ Important:** Add access control (`allowFrom` or `allowedRoles`) to prevent unauthorized users from triggering the bot. `requireMention` defaults to `true`.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
enabled: true,
|
||||
username: "openclaw", // Bot's Twitch account
|
||||
accessToken: "oauth:abc123...", // OAuth Access Token (or use OPENCLAW_TWITCH_ACCESS_TOKEN env var)
|
||||
clientId: "xyz789...", // Client ID from Token Generator
|
||||
channel: "vevisk", // Which Twitch channel's chat to join (required)
|
||||
allowFrom: ["123456789"], // (recommended) Your Twitch user ID only - get it from https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## What it is
|
||||
|
||||
- A Twitch channel owned by the Gateway.
|
||||
- Deterministic routing: replies always go back to Twitch.
|
||||
- Each account maps to an isolated session key `agent:<agentId>:twitch:<accountName>`.
|
||||
- `username` is the bot's account (who authenticates), `channel` is which chat room to join.
|
||||
|
||||
## Setup (detailed)
|
||||
|
||||
### Generate credentials
|
||||
|
||||
Use [Twitch Token Generator](https://twitchtokengenerator.com/):
|
||||
|
||||
- Select **Bot Token**
|
||||
- Verify scopes `chat:read` and `chat:write` are selected
|
||||
- Copy the **Client ID** and **Access Token**
|
||||
|
||||
No manual app registration needed. Tokens expire after several hours.
|
||||
|
||||
### Configure the bot
|
||||
|
||||
**Env var (default account only):**
|
||||
|
||||
```bash
|
||||
OPENCLAW_TWITCH_ACCESS_TOKEN=oauth:abc123...
|
||||
```
|
||||
|
||||
**Or config:**
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
enabled: true,
|
||||
username: "openclaw",
|
||||
accessToken: "oauth:abc123...",
|
||||
clientId: "xyz789...",
|
||||
channel: "vevisk",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
If both env and config are set, config takes precedence.
|
||||
|
||||
### Access control (recommended)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
allowFrom: ["123456789"], // (recommended) Your Twitch user ID only
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Prefer `allowFrom` for a hard allowlist. Use `allowedRoles` instead if you want role-based access.
|
||||
|
||||
**Available roles:** `"moderator"`, `"owner"`, `"vip"`, `"subscriber"`, `"all"`.
|
||||
|
||||
**Why user IDs?** Usernames can change, allowing impersonation. User IDs are permanent.
|
||||
|
||||
Find your Twitch user ID: [https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/) (Convert your Twitch username to ID)
|
||||
|
||||
## Token refresh (optional)
|
||||
|
||||
Tokens from [Twitch Token Generator](https://twitchtokengenerator.com/) cannot be automatically refreshed - regenerate when expired.
|
||||
|
||||
For automatic token refresh, create your own Twitch application at [Twitch Developer Console](https://dev.twitch.tv/console) and add to config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
clientSecret: "your_client_secret",
|
||||
refreshToken: "your_refresh_token",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The bot automatically refreshes tokens before expiration and logs refresh events.
|
||||
|
||||
## Multi-account support
|
||||
|
||||
Use `channels.twitch.accounts` with per-account tokens. See [`gateway/configuration`](/gateway/configuration) for the shared pattern.
|
||||
|
||||
Example (one bot account in two channels):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
channel1: {
|
||||
username: "openclaw",
|
||||
accessToken: "oauth:abc123...",
|
||||
clientId: "xyz789...",
|
||||
channel: "vevisk",
|
||||
},
|
||||
channel2: {
|
||||
username: "openclaw",
|
||||
accessToken: "oauth:def456...",
|
||||
clientId: "uvw012...",
|
||||
channel: "secondchannel",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** Each account needs its own token (one token per channel).
|
||||
|
||||
## Access control
|
||||
|
||||
### Role-based restrictions
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
allowedRoles: ["moderator", "vip"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Allowlist by User ID (most secure)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
allowFrom: ["123456789", "987654321"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Role-based access (alternative)
|
||||
|
||||
`allowFrom` is a hard allowlist. When set, only those user IDs are allowed.
|
||||
If you want role-based access, leave `allowFrom` unset and configure `allowedRoles` instead:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
allowedRoles: ["moderator"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Disable @mention requirement
|
||||
|
||||
By default, `requireMention` is `true`. To disable and respond to all messages:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
requireMention: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
First, run diagnostic commands:
|
||||
|
||||
```bash
|
||||
openclaw doctor
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
### Bot does not respond to messages
|
||||
|
||||
**Check access control:** Ensure your user ID is in `allowFrom`, or temporarily remove
|
||||
`allowFrom` and set `allowedRoles: ["all"]` to test.
|
||||
|
||||
**Check the bot is in the channel:** The bot must join the channel specified in `channel`.
|
||||
|
||||
### Token issues
|
||||
|
||||
**"Failed to connect" or authentication errors:**
|
||||
|
||||
- Verify `accessToken` is the OAuth access token value (typically starts with `oauth:` prefix)
|
||||
- Check token has `chat:read` and `chat:write` scopes
|
||||
- If using token refresh, verify `clientSecret` and `refreshToken` are set
|
||||
|
||||
### Token refresh not working
|
||||
|
||||
**Check logs for refresh events:**
|
||||
|
||||
```
|
||||
Using env token source for mybot
|
||||
Access token refreshed for user 123456 (expires in 14400s)
|
||||
```
|
||||
|
||||
If you see "token refresh disabled (no refresh token)":
|
||||
|
||||
- Ensure `clientSecret` is provided
|
||||
- Ensure `refreshToken` is provided
|
||||
|
||||
## Config
|
||||
|
||||
**Account config:**
|
||||
|
||||
- `username` - Bot username
|
||||
- `accessToken` - OAuth access token with `chat:read` and `chat:write`
|
||||
- `clientId` - Twitch Client ID (from Token Generator or your app)
|
||||
- `channel` - Channel to join (required)
|
||||
- `enabled` - Enable this account (default: `true`)
|
||||
- `clientSecret` - Optional: For automatic token refresh
|
||||
- `refreshToken` - Optional: For automatic token refresh
|
||||
- `expiresIn` - Token expiry in seconds
|
||||
- `obtainmentTimestamp` - Token obtained timestamp
|
||||
- `allowFrom` - User ID allowlist
|
||||
- `allowedRoles` - Role-based access control (`"moderator" | "owner" | "vip" | "subscriber" | "all"`)
|
||||
- `requireMention` - Require @mention (default: `true`)
|
||||
|
||||
**Provider options:**
|
||||
|
||||
- `channels.twitch.enabled` - Enable/disable channel startup
|
||||
- `channels.twitch.username` - Bot username (simplified single-account config)
|
||||
- `channels.twitch.accessToken` - OAuth access token (simplified single-account config)
|
||||
- `channels.twitch.clientId` - Twitch Client ID (simplified single-account config)
|
||||
- `channels.twitch.channel` - Channel to join (simplified single-account config)
|
||||
- `channels.twitch.accounts.<accountName>` - Multi-account config (all account fields above)
|
||||
|
||||
Full example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
enabled: true,
|
||||
username: "openclaw",
|
||||
accessToken: "oauth:abc123...",
|
||||
clientId: "xyz789...",
|
||||
channel: "vevisk",
|
||||
clientSecret: "secret123...",
|
||||
refreshToken: "refresh456...",
|
||||
allowFrom: ["123456789"],
|
||||
allowedRoles: ["moderator", "vip"],
|
||||
accounts: {
|
||||
default: {
|
||||
username: "mybot",
|
||||
accessToken: "oauth:abc123...",
|
||||
clientId: "xyz789...",
|
||||
channel: "your_channel",
|
||||
enabled: true,
|
||||
clientSecret: "secret123...",
|
||||
refreshToken: "refresh456...",
|
||||
expiresIn: 14400,
|
||||
obtainmentTimestamp: 1706092800000,
|
||||
allowFrom: ["123456789", "987654321"],
|
||||
allowedRoles: ["moderator"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Tool actions
|
||||
|
||||
The agent can call `twitch` with action:
|
||||
|
||||
- `send` - Send a message to a channel
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
action: "twitch",
|
||||
params: {
|
||||
message: "Hello Twitch!",
|
||||
to: "#mychannel",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Safety & ops
|
||||
|
||||
- **Treat tokens like passwords** - Never commit tokens to git
|
||||
- **Use automatic token refresh** for long-running bots
|
||||
- **Use user ID allowlists** instead of usernames for access control
|
||||
- **Monitor logs** for token refresh events and connection status
|
||||
- **Scope tokens minimally** - Only request `chat:read` and `chat:write`
|
||||
- **If stuck**: Restart the gateway after confirming no other process owns the session
|
||||
|
||||
## Limits
|
||||
|
||||
- **500 characters** per message (auto-chunked at word boundaries)
|
||||
- Markdown is stripped before chunking
|
||||
- No rate limiting (uses Twitch's built-in rate limits)
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
168
openclaw/docs/channels/wechat.md
Normal file
168
openclaw/docs/channels/wechat.md
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
---
|
||||
summary: "WeChat channel setup through the external openclaw-weixin plugin"
|
||||
read_when:
|
||||
- You want to connect OpenClaw to WeChat or Weixin
|
||||
- You are installing or troubleshooting the openclaw-weixin channel plugin
|
||||
- You need to understand how external channel plugins run beside the Gateway
|
||||
title: "WeChat"
|
||||
---
|
||||
|
||||
# WeChat
|
||||
|
||||
OpenClaw connects to WeChat through Tencent's external
|
||||
`@tencent-weixin/openclaw-weixin` channel plugin.
|
||||
|
||||
Status: external plugin. Direct chats and media are supported. Group chats are not
|
||||
advertised by the current plugin capability metadata.
|
||||
|
||||
## Naming
|
||||
|
||||
- **WeChat** is the user-facing name in these docs.
|
||||
- **Weixin** is the name used by Tencent's package and by the plugin id.
|
||||
- `openclaw-weixin` is the OpenClaw channel id.
|
||||
- `@tencent-weixin/openclaw-weixin` is the npm package.
|
||||
|
||||
Use `openclaw-weixin` in CLI commands and config paths.
|
||||
|
||||
## How it works
|
||||
|
||||
The WeChat code does not live in the OpenClaw core repo. OpenClaw provides the
|
||||
generic channel plugin contract, and the external plugin provides the
|
||||
WeChat-specific runtime:
|
||||
|
||||
1. `openclaw plugins install` installs `@tencent-weixin/openclaw-weixin`.
|
||||
2. The Gateway discovers the plugin manifest and loads the plugin entrypoint.
|
||||
3. The plugin registers channel id `openclaw-weixin`.
|
||||
4. `openclaw channels login --channel openclaw-weixin` starts QR login.
|
||||
5. The plugin stores account credentials under the OpenClaw state directory.
|
||||
6. When the Gateway starts, the plugin starts its Weixin monitor for each
|
||||
configured account.
|
||||
7. Inbound WeChat messages are normalized through the channel contract, routed to
|
||||
the selected OpenClaw agent, and sent back through the plugin outbound path.
|
||||
|
||||
That separation matters: OpenClaw core should stay channel-agnostic. WeChat login,
|
||||
Tencent iLink API calls, media upload/download, context tokens, and account
|
||||
monitoring are owned by the external plugin.
|
||||
|
||||
## Install
|
||||
|
||||
Quick install:
|
||||
|
||||
```bash
|
||||
npx -y @tencent-weixin/openclaw-weixin-cli install
|
||||
```
|
||||
|
||||
Manual install:
|
||||
|
||||
```bash
|
||||
openclaw plugins install "@tencent-weixin/openclaw-weixin"
|
||||
openclaw config set plugins.entries.openclaw-weixin.enabled true
|
||||
```
|
||||
|
||||
Restart the Gateway after install:
|
||||
|
||||
```bash
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
## Login
|
||||
|
||||
Run QR login on the same machine that runs the Gateway:
|
||||
|
||||
```bash
|
||||
openclaw channels login --channel openclaw-weixin
|
||||
```
|
||||
|
||||
Scan the QR code with WeChat on your phone and confirm the login. The plugin saves
|
||||
the account token locally after a successful scan.
|
||||
|
||||
To add another WeChat account, run the same login command again. For multiple
|
||||
accounts, isolate direct-message sessions by account, channel, and sender:
|
||||
|
||||
```bash
|
||||
openclaw config set session.dmScope per-account-channel-peer
|
||||
```
|
||||
|
||||
## Access control
|
||||
|
||||
Direct messages use the normal OpenClaw pairing and allowlist model for channel
|
||||
plugins.
|
||||
|
||||
Approve new senders:
|
||||
|
||||
```bash
|
||||
openclaw pairing list openclaw-weixin
|
||||
openclaw pairing approve openclaw-weixin <CODE>
|
||||
```
|
||||
|
||||
For the full access-control model, see [Pairing](/channels/pairing).
|
||||
|
||||
## Compatibility
|
||||
|
||||
The plugin checks the host OpenClaw version at startup.
|
||||
|
||||
| Plugin line | OpenClaw version | npm tag |
|
||||
| ----------- | ----------------------- | -------- |
|
||||
| `2.x` | `>=2026.3.22` | `latest` |
|
||||
| `1.x` | `>=2026.1.0 <2026.3.22` | `legacy` |
|
||||
|
||||
If the plugin reports that your OpenClaw version is too old, either update
|
||||
OpenClaw or install the legacy plugin line:
|
||||
|
||||
```bash
|
||||
openclaw plugins install @tencent-weixin/openclaw-weixin@legacy
|
||||
```
|
||||
|
||||
## Sidecar process
|
||||
|
||||
The WeChat plugin can run helper work beside the Gateway while it monitors the
|
||||
Tencent iLink API. In issue #68451, that helper path exposed a bug in OpenClaw's
|
||||
generic stale-Gateway cleanup: a child process could try to clean up the parent
|
||||
Gateway process, causing restart loops under process managers such as systemd.
|
||||
|
||||
Current OpenClaw startup cleanup excludes the current process and its ancestors,
|
||||
so a channel helper must not kill the Gateway that launched it. This fix is
|
||||
generic; it is not a WeChat-specific path in core.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Check install and status:
|
||||
|
||||
```bash
|
||||
openclaw plugins list
|
||||
openclaw channels status --probe
|
||||
openclaw --version
|
||||
```
|
||||
|
||||
If the channel shows as installed but does not connect, confirm that the plugin is
|
||||
enabled and restart:
|
||||
|
||||
```bash
|
||||
openclaw config set plugins.entries.openclaw-weixin.enabled true
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
If the Gateway restarts repeatedly after enabling WeChat, update both OpenClaw and
|
||||
the plugin:
|
||||
|
||||
```bash
|
||||
npm view @tencent-weixin/openclaw-weixin version
|
||||
openclaw plugins install "@tencent-weixin/openclaw-weixin" --force
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
Temporary disable:
|
||||
|
||||
```bash
|
||||
openclaw config set plugins.entries.openclaw-weixin.enabled false
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
## Related docs
|
||||
|
||||
- Channel overview: [Chat Channels](/channels)
|
||||
- Pairing: [Pairing](/channels/pairing)
|
||||
- Channel routing: [Channel Routing](/channels/channel-routing)
|
||||
- Plugin architecture: [Plugin Architecture](/plugins/architecture)
|
||||
- Channel plugin SDK: [Channel Plugin SDK](/plugins/sdk-channel-plugins)
|
||||
- External package: [@tencent-weixin/openclaw-weixin](https://www.npmjs.com/package/@tencent-weixin/openclaw-weixin)
|
||||
489
openclaw/docs/channels/whatsapp.md
Normal file
489
openclaw/docs/channels/whatsapp.md
Normal file
|
|
@ -0,0 +1,489 @@
|
|||
---
|
||||
summary: "WhatsApp channel support, access controls, delivery behavior, and operations"
|
||||
read_when:
|
||||
- Working on WhatsApp/web channel behavior or inbox routing
|
||||
title: "WhatsApp"
|
||||
---
|
||||
|
||||
# WhatsApp (Web channel)
|
||||
|
||||
Status: production-ready via WhatsApp Web (Baileys). Gateway owns linked session(s).
|
||||
|
||||
## Install (on demand)
|
||||
|
||||
- Onboarding (`openclaw onboard`) and `openclaw channels add --channel whatsapp`
|
||||
prompt to install the WhatsApp plugin the first time you select it.
|
||||
- `openclaw channels login --channel whatsapp` also offers the install flow when
|
||||
the plugin is not present yet.
|
||||
- Dev channel + git checkout: defaults to the local plugin path.
|
||||
- Stable/Beta: defaults to the npm package `@openclaw/whatsapp`.
|
||||
|
||||
Manual install stays available:
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/whatsapp
|
||||
```
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Pairing" icon="link" href="/channels/pairing">
|
||||
Default DM policy is pairing for unknown senders.
|
||||
</Card>
|
||||
<Card title="Channel troubleshooting" icon="wrench" href="/channels/troubleshooting">
|
||||
Cross-channel diagnostics and repair playbooks.
|
||||
</Card>
|
||||
<Card title="Gateway configuration" icon="settings" href="/gateway/configuration">
|
||||
Full channel config patterns and examples.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Quick setup
|
||||
|
||||
<Steps>
|
||||
<Step title="Configure WhatsApp access policy">
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "pairing",
|
||||
allowFrom: ["+15551234567"],
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15551234567"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Link WhatsApp (QR)">
|
||||
|
||||
```bash
|
||||
openclaw channels login --channel whatsapp
|
||||
```
|
||||
|
||||
For a specific account:
|
||||
|
||||
```bash
|
||||
openclaw channels login --channel whatsapp --account work
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Start the gateway">
|
||||
|
||||
```bash
|
||||
openclaw gateway
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Approve first pairing request (if using pairing mode)">
|
||||
|
||||
```bash
|
||||
openclaw pairing list whatsapp
|
||||
openclaw pairing approve whatsapp <CODE>
|
||||
```
|
||||
|
||||
Pairing requests expire after 1 hour. Pending requests are capped at 3 per channel.
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Note>
|
||||
OpenClaw recommends running WhatsApp on a separate number when possible. (The channel metadata and setup flow are optimized for that setup, but personal-number setups are also supported.)
|
||||
</Note>
|
||||
|
||||
## Deployment patterns
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Dedicated number (recommended)">
|
||||
This is the cleanest operational mode:
|
||||
|
||||
- separate WhatsApp identity for OpenClaw
|
||||
- clearer DM allowlists and routing boundaries
|
||||
- lower chance of self-chat confusion
|
||||
|
||||
Minimal policy pattern:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["+15551234567"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Personal-number fallback">
|
||||
Onboarding supports personal-number mode and writes a self-chat-friendly baseline:
|
||||
|
||||
- `dmPolicy: "allowlist"`
|
||||
- `allowFrom` includes your personal number
|
||||
- `selfChatMode: true`
|
||||
|
||||
In runtime, self-chat protections key off the linked self number and `allowFrom`.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="WhatsApp Web-only channel scope">
|
||||
The messaging platform channel is WhatsApp Web-based (`Baileys`) in current OpenClaw channel architecture.
|
||||
|
||||
There is no separate Twilio WhatsApp messaging channel in the built-in chat-channel registry.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Runtime model
|
||||
|
||||
- Gateway owns the WhatsApp socket and reconnect loop.
|
||||
- Outbound sends require an active WhatsApp listener for the target account.
|
||||
- Status and broadcast chats are ignored (`@status`, `@broadcast`).
|
||||
- Direct chats use DM session rules (`session.dmScope`; default `main` collapses DMs to the agent main session).
|
||||
- Group sessions are isolated (`agent:<agentId>:whatsapp:group:<jid>`).
|
||||
- WhatsApp Web transport honors standard proxy environment variables on the gateway host (`HTTPS_PROXY`, `HTTP_PROXY`, `NO_PROXY` / lowercase variants). Prefer host-level proxy config over channel-specific WhatsApp proxy settings.
|
||||
|
||||
## Access control and activation
|
||||
|
||||
<Tabs>
|
||||
<Tab title="DM policy">
|
||||
`channels.whatsapp.dmPolicy` controls direct chat access:
|
||||
|
||||
- `pairing` (default)
|
||||
- `allowlist`
|
||||
- `open` (requires `allowFrom` to include `"*"`)
|
||||
- `disabled`
|
||||
|
||||
`allowFrom` accepts E.164-style numbers (normalized internally).
|
||||
|
||||
Multi-account override: `channels.whatsapp.accounts.<id>.dmPolicy` (and `allowFrom`) take precedence over channel-level defaults for that account.
|
||||
|
||||
Runtime behavior details:
|
||||
|
||||
- pairings are persisted in channel allow-store and merged with configured `allowFrom`
|
||||
- if no allowlist is configured, the linked self number is allowed by default
|
||||
- outbound `fromMe` DMs are never auto-paired
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Group policy + allowlists">
|
||||
Group access has two layers:
|
||||
|
||||
1. **Group membership allowlist** (`channels.whatsapp.groups`)
|
||||
- if `groups` is omitted, all groups are eligible
|
||||
- if `groups` is present, it acts as a group allowlist (`"*"` allowed)
|
||||
|
||||
2. **Group sender policy** (`channels.whatsapp.groupPolicy` + `groupAllowFrom`)
|
||||
- `open`: sender allowlist bypassed
|
||||
- `allowlist`: sender must match `groupAllowFrom` (or `*`)
|
||||
- `disabled`: block all group inbound
|
||||
|
||||
Sender allowlist fallback:
|
||||
|
||||
- if `groupAllowFrom` is unset, runtime falls back to `allowFrom` when available
|
||||
- sender allowlists are evaluated before mention/reply activation
|
||||
|
||||
Note: if no `channels.whatsapp` block exists at all, runtime group-policy fallback is `allowlist` (with a warning log), even if `channels.defaults.groupPolicy` is set.
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Mentions + /activation">
|
||||
Group replies require mention by default.
|
||||
|
||||
Mention detection includes:
|
||||
|
||||
- explicit WhatsApp mentions of the bot identity
|
||||
- configured mention regex patterns (`agents.list[].groupChat.mentionPatterns`, fallback `messages.groupChat.mentionPatterns`)
|
||||
- implicit reply-to-bot detection (reply sender matches bot identity)
|
||||
|
||||
Security note:
|
||||
|
||||
- quote/reply only satisfies mention gating; it does **not** grant sender authorization
|
||||
- with `groupPolicy: "allowlist"`, non-allowlisted senders are still blocked even if they reply to an allowlisted user's message
|
||||
|
||||
Session-level activation command:
|
||||
|
||||
- `/activation mention`
|
||||
- `/activation always`
|
||||
|
||||
`activation` updates session state (not global config). It is owner-gated.
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Personal-number and self-chat behavior
|
||||
|
||||
When the linked self number is also present in `allowFrom`, WhatsApp self-chat safeguards activate:
|
||||
|
||||
- skip read receipts for self-chat turns
|
||||
- ignore mention-JID auto-trigger behavior that would otherwise ping yourself
|
||||
- if `messages.responsePrefix` is unset, self-chat replies default to `[{identity.name}]` or `[openclaw]`
|
||||
|
||||
## Message normalization and context
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Inbound envelope + reply context">
|
||||
Incoming WhatsApp messages are wrapped in the shared inbound envelope.
|
||||
|
||||
If a quoted reply exists, context is appended in this form:
|
||||
|
||||
```text
|
||||
[Replying to <sender> id:<stanzaId>]
|
||||
<quoted body or media placeholder>
|
||||
[/Replying]
|
||||
```
|
||||
|
||||
Reply metadata fields are also populated when available (`ReplyToId`, `ReplyToBody`, `ReplyToSender`, sender JID/E.164).
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Media placeholders and location/contact extraction">
|
||||
Media-only inbound messages are normalized with placeholders such as:
|
||||
|
||||
- `<media:image>`
|
||||
- `<media:video>`
|
||||
- `<media:audio>`
|
||||
- `<media:document>`
|
||||
- `<media:sticker>`
|
||||
|
||||
Location and contact payloads are normalized into textual context before routing.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Pending group history injection">
|
||||
For groups, unprocessed messages can be buffered and injected as context when the bot is finally triggered.
|
||||
|
||||
- default limit: `50`
|
||||
- config: `channels.whatsapp.historyLimit`
|
||||
- fallback: `messages.groupChat.historyLimit`
|
||||
- `0` disables
|
||||
|
||||
Injection markers:
|
||||
|
||||
- `[Chat messages since your last reply - for context]`
|
||||
- `[Current message - respond to this]`
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Read receipts">
|
||||
Read receipts are enabled by default for accepted inbound WhatsApp messages.
|
||||
|
||||
Disable globally:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
sendReadReceipts: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Per-account override:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
accounts: {
|
||||
work: {
|
||||
sendReadReceipts: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Self-chat turns skip read receipts even when globally enabled.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Delivery, chunking, and media
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Text chunking">
|
||||
- default chunk limit: `channels.whatsapp.textChunkLimit = 4000`
|
||||
- `channels.whatsapp.chunkMode = "length" | "newline"`
|
||||
- `newline` mode prefers paragraph boundaries (blank lines), then falls back to length-safe chunking
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Outbound media behavior">
|
||||
- supports image, video, audio (PTT voice-note), and document payloads
|
||||
- `audio/ogg` is rewritten to `audio/ogg; codecs=opus` for voice-note compatibility
|
||||
- animated GIF playback is supported via `gifPlayback: true` on video sends
|
||||
- captions are applied to the first media item when sending multi-media reply payloads
|
||||
- media source can be HTTP(S), `file://`, or local paths
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Media size limits and fallback behavior">
|
||||
- inbound media save cap: `channels.whatsapp.mediaMaxMb` (default `50`)
|
||||
- outbound media send cap: `channels.whatsapp.mediaMaxMb` (default `50`)
|
||||
- per-account overrides use `channels.whatsapp.accounts.<accountId>.mediaMaxMb`
|
||||
- images are auto-optimized (resize/quality sweep) to fit limits
|
||||
- on media send failure, first-item fallback sends text warning instead of dropping the response silently
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Reaction level
|
||||
|
||||
`channels.whatsapp.reactionLevel` controls how broadly the agent uses emoji reactions on WhatsApp:
|
||||
|
||||
| Level | Ack reactions | Agent-initiated reactions | Description |
|
||||
| ------------- | ------------- | ------------------------- | ------------------------------------------------ |
|
||||
| `"off"` | No | No | No reactions at all |
|
||||
| `"ack"` | Yes | No | Ack reactions only (pre-reply receipt) |
|
||||
| `"minimal"` | Yes | Yes (conservative) | Ack + agent reactions with conservative guidance |
|
||||
| `"extensive"` | Yes | Yes (encouraged) | Ack + agent reactions with encouraged guidance |
|
||||
|
||||
Default: `"minimal"`.
|
||||
|
||||
Per-account overrides use `channels.whatsapp.accounts.<id>.reactionLevel`.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
reactionLevel: "ack",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Acknowledgment reactions
|
||||
|
||||
WhatsApp supports immediate ack reactions on inbound receipt via `channels.whatsapp.ackReaction`.
|
||||
Ack reactions are gated by `reactionLevel` — they are suppressed when `reactionLevel` is `"off"`.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
ackReaction: {
|
||||
emoji: "👀",
|
||||
direct: true,
|
||||
group: "mentions", // always | mentions | never
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Behavior notes:
|
||||
|
||||
- sent immediately after inbound is accepted (pre-reply)
|
||||
- failures are logged but do not block normal reply delivery
|
||||
- group mode `mentions` reacts on mention-triggered turns; group activation `always` acts as bypass for this check
|
||||
- WhatsApp uses `channels.whatsapp.ackReaction` (legacy `messages.ackReaction` is not used here)
|
||||
|
||||
## Multi-account and credentials
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Account selection and defaults">
|
||||
- account ids come from `channels.whatsapp.accounts`
|
||||
- default account selection: `default` if present, otherwise first configured account id (sorted)
|
||||
- account ids are normalized internally for lookup
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Credential paths and legacy compatibility">
|
||||
- current auth path: `~/.openclaw/credentials/whatsapp/<accountId>/creds.json`
|
||||
- backup file: `creds.json.bak`
|
||||
- legacy default auth in `~/.openclaw/credentials/` is still recognized/migrated for default-account flows
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Logout behavior">
|
||||
`openclaw channels logout --channel whatsapp [--account <id>]` clears WhatsApp auth state for that account.
|
||||
|
||||
In legacy auth directories, `oauth.json` is preserved while Baileys auth files are removed.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Tools, actions, and config writes
|
||||
|
||||
- Agent tool support includes WhatsApp reaction action (`react`).
|
||||
- Action gates:
|
||||
- `channels.whatsapp.actions.reactions`
|
||||
- `channels.whatsapp.actions.polls`
|
||||
- Channel-initiated config writes are enabled by default (disable via `channels.whatsapp.configWrites=false`).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Not linked (QR required)">
|
||||
Symptom: channel status reports not linked.
|
||||
|
||||
Fix:
|
||||
|
||||
```bash
|
||||
openclaw channels login --channel whatsapp
|
||||
openclaw channels status
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Linked but disconnected / reconnect loop">
|
||||
Symptom: linked account with repeated disconnects or reconnect attempts.
|
||||
|
||||
Fix:
|
||||
|
||||
```bash
|
||||
openclaw doctor
|
||||
openclaw logs --follow
|
||||
```
|
||||
|
||||
If needed, re-link with `channels login`.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="No active listener when sending">
|
||||
Outbound sends fail fast when no active gateway listener exists for the target account.
|
||||
|
||||
Make sure gateway is running and the account is linked.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Group messages unexpectedly ignored">
|
||||
Check in this order:
|
||||
|
||||
- `groupPolicy`
|
||||
- `groupAllowFrom` / `allowFrom`
|
||||
- `groups` allowlist entries
|
||||
- mention gating (`requireMention` + mention patterns)
|
||||
- duplicate keys in `openclaw.json` (JSON5): later entries override earlier ones, so keep a single `groupPolicy` per scope
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Bun runtime warning">
|
||||
WhatsApp gateway runtime should use Node. Bun is flagged as incompatible for stable WhatsApp/Telegram gateway operation.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Configuration reference pointers
|
||||
|
||||
Primary reference:
|
||||
|
||||
- [Configuration reference - WhatsApp](/gateway/configuration-reference#whatsapp)
|
||||
|
||||
High-signal WhatsApp fields:
|
||||
|
||||
- access: `dmPolicy`, `allowFrom`, `groupPolicy`, `groupAllowFrom`, `groups`
|
||||
- delivery: `textChunkLimit`, `chunkMode`, `mediaMaxMb`, `sendReadReceipts`, `ackReaction`, `reactionLevel`
|
||||
- multi-account: `accounts.<id>.enabled`, `accounts.<id>.authDir`, account-level overrides
|
||||
- operations: `configWrites`, `debounceMs`, `web.enabled`, `web.heartbeatSeconds`, `web.reconnect.*`
|
||||
- session behavior: `session.dmScope`, `historyLimit`, `dmHistoryLimit`, `dms.<id>.historyLimit`
|
||||
|
||||
## Related
|
||||
|
||||
- [Pairing](/channels/pairing)
|
||||
- [Groups](/channels/groups)
|
||||
- [Security](/gateway/security)
|
||||
- [Channel routing](/channels/channel-routing)
|
||||
- [Multi-agent routing](/concepts/multi-agent)
|
||||
- [Troubleshooting](/channels/troubleshooting)
|
||||
254
openclaw/docs/channels/zalo.md
Normal file
254
openclaw/docs/channels/zalo.md
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
---
|
||||
summary: "Zalo bot support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Zalo features or webhooks
|
||||
title: "Zalo"
|
||||
---
|
||||
|
||||
# Zalo (Bot API)
|
||||
|
||||
Status: experimental. DMs are supported. The [Capabilities](#capabilities) section below reflects current Marketplace-bot behavior.
|
||||
|
||||
## Bundled plugin
|
||||
|
||||
Zalo ships as a bundled plugin in current OpenClaw releases, so normal packaged
|
||||
builds do not need a separate install.
|
||||
|
||||
If you are on an older build or a custom install that excludes Zalo, install it
|
||||
manually:
|
||||
|
||||
- Install via CLI: `openclaw plugins install @openclaw/zalo`
|
||||
- Or from a source checkout: `openclaw plugins install ./path/to/local/zalo-plugin`
|
||||
- Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
|
||||
1. Ensure the Zalo plugin is available.
|
||||
- Current packaged OpenClaw releases already bundle it.
|
||||
- Older/custom installs can add it manually with the commands above.
|
||||
2. Set the token:
|
||||
- Env: `ZALO_BOT_TOKEN=...`
|
||||
- Or config: `channels.zalo.accounts.default.botToken: "..."`.
|
||||
3. Restart the gateway (or finish setup).
|
||||
4. DM access is pairing by default; approve the pairing code on first contact.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalo: {
|
||||
enabled: true,
|
||||
accounts: {
|
||||
default: {
|
||||
botToken: "12345689:abc-xyz",
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## What it is
|
||||
|
||||
Zalo is a Vietnam-focused messaging app; its Bot API lets the Gateway run a bot for 1:1 conversations.
|
||||
It is a good fit for support or notifications where you want deterministic routing back to Zalo.
|
||||
|
||||
This page reflects current OpenClaw behavior for **Zalo Bot Creator / Marketplace bots**.
|
||||
**Zalo Official Account (OA) bots** are a different Zalo product surface and may behave differently.
|
||||
|
||||
- A Zalo Bot API channel owned by the Gateway.
|
||||
- Deterministic routing: replies go back to Zalo; the model never chooses channels.
|
||||
- DMs share the agent's main session.
|
||||
- The [Capabilities](#capabilities) section below shows current Marketplace-bot support.
|
||||
|
||||
## Setup (fast path)
|
||||
|
||||
### 1) Create a bot token (Zalo Bot Platform)
|
||||
|
||||
1. Go to [https://bot.zaloplatforms.com](https://bot.zaloplatforms.com) and sign in.
|
||||
2. Create a new bot and configure its settings.
|
||||
3. Copy the full bot token (typically `numeric_id:secret`). For Marketplace bots, the usable runtime token may appear in the bot's welcome message after creation.
|
||||
|
||||
### 2) Configure the token (env or config)
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalo: {
|
||||
enabled: true,
|
||||
accounts: {
|
||||
default: {
|
||||
botToken: "12345689:abc-xyz",
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
If you later move to a Zalo bot surface where groups are available, you can add group-specific config such as `groupPolicy` and `groupAllowFrom` explicitly. For current Marketplace-bot behavior, see [Capabilities](#capabilities).
|
||||
|
||||
Env option: `ZALO_BOT_TOKEN=...` (works for the default account only).
|
||||
|
||||
Multi-account support: use `channels.zalo.accounts` with per-account tokens and optional `name`.
|
||||
|
||||
3. Restart the gateway. Zalo starts when a token is resolved (env or config).
|
||||
4. DM access defaults to pairing. Approve the code when the bot is first contacted.
|
||||
|
||||
## How it works (behavior)
|
||||
|
||||
- Inbound messages are normalized into the shared channel envelope with media placeholders.
|
||||
- Replies always route back to the same Zalo chat.
|
||||
- Long-polling by default; webhook mode available with `channels.zalo.webhookUrl`.
|
||||
|
||||
## Limits
|
||||
|
||||
- Outbound text is chunked to 2000 characters (Zalo API limit).
|
||||
- Media downloads/uploads are capped by `channels.zalo.mediaMaxMb` (default 5).
|
||||
- Streaming is blocked by default due to the 2000 char limit making streaming less useful.
|
||||
|
||||
## Access control (DMs)
|
||||
|
||||
### DM access
|
||||
|
||||
- Default: `channels.zalo.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
- `openclaw pairing list zalo`
|
||||
- `openclaw pairing approve zalo <CODE>`
|
||||
- Pairing is the default token exchange. Details: [Pairing](/channels/pairing)
|
||||
- `channels.zalo.allowFrom` accepts numeric user IDs (no username lookup available).
|
||||
|
||||
## Access control (Groups)
|
||||
|
||||
For **Zalo Bot Creator / Marketplace bots**, group support was not available in practice because the bot could not be added to a group at all.
|
||||
|
||||
That means the group-related config keys below exist in the schema, but were not usable for Marketplace bots:
|
||||
|
||||
- `channels.zalo.groupPolicy` controls group inbound handling: `open | allowlist | disabled`.
|
||||
- `channels.zalo.groupAllowFrom` restricts which sender IDs can trigger the bot in groups.
|
||||
- If `groupAllowFrom` is unset, Zalo falls back to `allowFrom` for sender checks.
|
||||
- Runtime note: if `channels.zalo` is missing entirely, runtime still falls back to `groupPolicy="allowlist"` for safety.
|
||||
|
||||
The group policy values (when group access is available on your bot surface) are:
|
||||
|
||||
- `groupPolicy: "disabled"` — blocks all group messages.
|
||||
- `groupPolicy: "open"` — allows any group member (mention-gated).
|
||||
- `groupPolicy: "allowlist"` — fail-closed default; only allowed senders are accepted.
|
||||
|
||||
If you are using a different Zalo bot product surface and have verified working group behavior, document that separately rather than assuming it matches the Marketplace-bot flow.
|
||||
|
||||
## Long-polling vs webhook
|
||||
|
||||
- Default: long-polling (no public URL required).
|
||||
- Webhook mode: set `channels.zalo.webhookUrl` and `channels.zalo.webhookSecret`.
|
||||
- The webhook secret must be 8-256 characters.
|
||||
- Webhook URL must use HTTPS.
|
||||
- Zalo sends events with `X-Bot-Api-Secret-Token` header for verification.
|
||||
- Gateway HTTP handles webhook requests at `channels.zalo.webhookPath` (defaults to the webhook URL path).
|
||||
- Requests must use `Content-Type: application/json` (or `+json` media types).
|
||||
- Duplicate events (`event_name + message_id`) are ignored for a short replay window.
|
||||
- Burst traffic is rate-limited per path/source and may return HTTP 429.
|
||||
|
||||
**Note:** getUpdates (polling) and webhook are mutually exclusive per Zalo API docs.
|
||||
|
||||
## Supported message types
|
||||
|
||||
For a quick support snapshot, see [Capabilities](#capabilities). The notes below add detail where the behavior needs extra context.
|
||||
|
||||
- **Text messages**: Full support with 2000 character chunking.
|
||||
- **Plain URLs in text**: Behave like normal text input.
|
||||
- **Link previews / rich link cards**: See the Marketplace-bot status in [Capabilities](#capabilities); they did not reliably trigger a reply.
|
||||
- **Image messages**: See the Marketplace-bot status in [Capabilities](#capabilities); inbound image handling was unreliable (typing indicator without a final reply).
|
||||
- **Stickers**: See the Marketplace-bot status in [Capabilities](#capabilities).
|
||||
- **Voice notes / audio files / video / generic file attachments**: See the Marketplace-bot status in [Capabilities](#capabilities).
|
||||
- **Unsupported types**: Logged (for example, messages from protected users).
|
||||
|
||||
## Capabilities
|
||||
|
||||
This table summarizes current **Zalo Bot Creator / Marketplace bot** behavior in OpenClaw.
|
||||
|
||||
| Feature | Status |
|
||||
| --------------------------- | --------------------------------------- |
|
||||
| Direct messages | ✅ Supported |
|
||||
| Groups | ❌ Not available for Marketplace bots |
|
||||
| Media (inbound images) | ⚠️ Limited / verify in your environment |
|
||||
| Media (outbound images) | ⚠️ Not re-tested for Marketplace bots |
|
||||
| Plain URLs in text | ✅ Supported |
|
||||
| Link previews | ⚠️ Unreliable for Marketplace bots |
|
||||
| Reactions | ❌ Not supported |
|
||||
| Stickers | ⚠️ No agent reply for Marketplace bots |
|
||||
| Voice notes / audio / video | ⚠️ No agent reply for Marketplace bots |
|
||||
| File attachments | ⚠️ No agent reply for Marketplace bots |
|
||||
| Threads | ❌ Not supported |
|
||||
| Polls | ❌ Not supported |
|
||||
| Native commands | ❌ Not supported |
|
||||
| Streaming | ⚠️ Blocked (2000 char limit) |
|
||||
|
||||
## Delivery targets (CLI/cron)
|
||||
|
||||
- Use a chat id as the target.
|
||||
- Example: `openclaw message send --channel zalo --target 123456789 --message "hi"`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Bot doesn't respond:**
|
||||
|
||||
- Check that the token is valid: `openclaw channels status --probe`
|
||||
- Verify the sender is approved (pairing or allowFrom)
|
||||
- Check gateway logs: `openclaw logs --follow`
|
||||
|
||||
**Webhook not receiving events:**
|
||||
|
||||
- Ensure webhook URL uses HTTPS
|
||||
- Verify secret token is 8-256 characters
|
||||
- Confirm the gateway HTTP endpoint is reachable on the configured path
|
||||
- Check that getUpdates polling is not running (they're mutually exclusive)
|
||||
|
||||
## Configuration reference (Zalo)
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
The flat top-level keys (`channels.zalo.botToken`, `channels.zalo.dmPolicy`, and similar) are a legacy single-account shorthand. Prefer `channels.zalo.accounts.<id>.*` for new configs. Both forms are still documented here because they exist in the schema.
|
||||
|
||||
Provider options:
|
||||
|
||||
- `channels.zalo.enabled`: enable/disable channel startup.
|
||||
- `channels.zalo.botToken`: bot token from Zalo Bot Platform.
|
||||
- `channels.zalo.tokenFile`: read token from a regular file path. Symlinks are rejected.
|
||||
- `channels.zalo.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing).
|
||||
- `channels.zalo.allowFrom`: DM allowlist (user IDs). `open` requires `"*"`. The wizard will ask for numeric IDs.
|
||||
- `channels.zalo.groupPolicy`: `open | allowlist | disabled` (default: allowlist). Present in config; see [Capabilities](#capabilities) and [Access control (Groups)](#access-control-groups) for current Marketplace-bot behavior.
|
||||
- `channels.zalo.groupAllowFrom`: group sender allowlist (user IDs). Falls back to `allowFrom` when unset.
|
||||
- `channels.zalo.mediaMaxMb`: inbound/outbound media cap (MB, default 5).
|
||||
- `channels.zalo.webhookUrl`: enable webhook mode (HTTPS required).
|
||||
- `channels.zalo.webhookSecret`: webhook secret (8-256 chars).
|
||||
- `channels.zalo.webhookPath`: webhook path on the gateway HTTP server.
|
||||
- `channels.zalo.proxy`: proxy URL for API requests.
|
||||
|
||||
Multi-account options:
|
||||
|
||||
- `channels.zalo.accounts.<id>.botToken`: per-account token.
|
||||
- `channels.zalo.accounts.<id>.tokenFile`: per-account regular token file. Symlinks are rejected.
|
||||
- `channels.zalo.accounts.<id>.name`: display name.
|
||||
- `channels.zalo.accounts.<id>.enabled`: enable/disable account.
|
||||
- `channels.zalo.accounts.<id>.dmPolicy`: per-account DM policy.
|
||||
- `channels.zalo.accounts.<id>.allowFrom`: per-account allowlist.
|
||||
- `channels.zalo.accounts.<id>.groupPolicy`: per-account group policy. Present in config; see [Capabilities](#capabilities) and [Access control (Groups)](#access-control-groups) for current Marketplace-bot behavior.
|
||||
- `channels.zalo.accounts.<id>.groupAllowFrom`: per-account group sender allowlist.
|
||||
- `channels.zalo.accounts.<id>.webhookUrl`: per-account webhook URL.
|
||||
- `channels.zalo.accounts.<id>.webhookSecret`: per-account webhook secret.
|
||||
- `channels.zalo.accounts.<id>.webhookPath`: per-account webhook path.
|
||||
- `channels.zalo.accounts.<id>.proxy`: per-account proxy URL.
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
196
openclaw/docs/channels/zalouser.md
Normal file
196
openclaw/docs/channels/zalouser.md
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
---
|
||||
summary: "Zalo personal account support via native zca-js (QR login), capabilities, and configuration"
|
||||
read_when:
|
||||
- Setting up Zalo Personal for OpenClaw
|
||||
- Debugging Zalo Personal login or message flow
|
||||
title: "Zalo Personal"
|
||||
---
|
||||
|
||||
# Zalo Personal (unofficial)
|
||||
|
||||
Status: experimental. This integration automates a **personal Zalo account** via native `zca-js` inside OpenClaw.
|
||||
|
||||
> **Warning:** This is an unofficial integration and may result in account suspension/ban. Use at your own risk.
|
||||
|
||||
## Bundled plugin
|
||||
|
||||
Zalo Personal ships as a bundled plugin in current OpenClaw releases, so normal
|
||||
packaged builds do not need a separate install.
|
||||
|
||||
If you are on an older build or a custom install that excludes Zalo Personal,
|
||||
install it manually:
|
||||
|
||||
- Install via CLI: `openclaw plugins install @openclaw/zalouser`
|
||||
- Or from a source checkout: `openclaw plugins install ./path/to/local/zalouser-plugin`
|
||||
- Details: [Plugins](/tools/plugin)
|
||||
|
||||
No external `zca`/`openzca` CLI binary is required.
|
||||
|
||||
## Quick setup (beginner)
|
||||
|
||||
1. Ensure the Zalo Personal plugin is available.
|
||||
- Current packaged OpenClaw releases already bundle it.
|
||||
- Older/custom installs can add it manually with the commands above.
|
||||
2. Login (QR, on the Gateway machine):
|
||||
- `openclaw channels login --channel zalouser`
|
||||
- Scan the QR code with the Zalo mobile app.
|
||||
3. Enable the channel:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalouser: {
|
||||
enabled: true,
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
4. Restart the Gateway (or finish setup).
|
||||
5. DM access defaults to pairing; approve the pairing code on first contact.
|
||||
|
||||
## What it is
|
||||
|
||||
- Runs entirely in-process via `zca-js`.
|
||||
- Uses native event listeners to receive inbound messages.
|
||||
- Sends replies directly through the JS API (text/media/link).
|
||||
- Designed for “personal account” use cases where Zalo Bot API is not available.
|
||||
|
||||
## Naming
|
||||
|
||||
Channel id is `zalouser` to make it explicit this automates a **personal Zalo user account** (unofficial). We keep `zalo` reserved for a potential future official Zalo API integration.
|
||||
|
||||
## Finding IDs (directory)
|
||||
|
||||
Use the directory CLI to discover peers/groups and their IDs:
|
||||
|
||||
```bash
|
||||
openclaw directory self --channel zalouser
|
||||
openclaw directory peers list --channel zalouser --query "name"
|
||||
openclaw directory groups list --channel zalouser --query "work"
|
||||
```
|
||||
|
||||
## Limits
|
||||
|
||||
- Outbound text is chunked to ~2000 characters (Zalo client limits).
|
||||
- Streaming is blocked by default.
|
||||
|
||||
## Access control (DMs)
|
||||
|
||||
`channels.zalouser.dmPolicy` supports: `pairing | allowlist | open | disabled` (default: `pairing`).
|
||||
|
||||
`channels.zalouser.allowFrom` accepts user IDs or names. During setup, names are resolved to IDs using the plugin's in-process contact lookup.
|
||||
|
||||
Approve via:
|
||||
|
||||
- `openclaw pairing list zalouser`
|
||||
- `openclaw pairing approve zalouser <code>`
|
||||
|
||||
## Group access (optional)
|
||||
|
||||
- Default: `channels.zalouser.groupPolicy = "open"` (groups allowed). Use `channels.defaults.groupPolicy` to override the default when unset.
|
||||
- Restrict to an allowlist with:
|
||||
- `channels.zalouser.groupPolicy = "allowlist"`
|
||||
- `channels.zalouser.groups` (keys should be stable group IDs; names are resolved to IDs on startup when possible)
|
||||
- `channels.zalouser.groupAllowFrom` (controls which senders in allowed groups can trigger the bot)
|
||||
- Block all groups: `channels.zalouser.groupPolicy = "disabled"`.
|
||||
- The configure wizard can prompt for group allowlists.
|
||||
- On startup, OpenClaw resolves group/user names in allowlists to IDs and logs the mapping.
|
||||
- Group allowlist matching is ID-only by default. Unresolved names are ignored for auth unless `channels.zalouser.dangerouslyAllowNameMatching: true` is enabled.
|
||||
- `channels.zalouser.dangerouslyAllowNameMatching: true` is a break-glass compatibility mode that re-enables mutable group-name matching.
|
||||
- If `groupAllowFrom` is unset, runtime falls back to `allowFrom` for group sender checks.
|
||||
- Sender checks apply to both normal group messages and control commands (for example `/new`, `/reset`).
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalouser: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["1471383327500481391"],
|
||||
groups: {
|
||||
"123456789": { allow: true },
|
||||
"Work Chat": { allow: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Group mention gating
|
||||
|
||||
- `channels.zalouser.groups.<group>.requireMention` controls whether group replies require a mention.
|
||||
- Resolution order: exact group id/name -> normalized group slug -> `*` -> default (`true`).
|
||||
- This applies both to allowlisted groups and open group mode.
|
||||
- Quoting a bot message counts as an implicit mention for group activation.
|
||||
- Authorized control commands (for example `/new`) can bypass mention gating.
|
||||
- When a group message is skipped because mention is required, OpenClaw stores it as pending group history and includes it on the next processed group message.
|
||||
- Group history limit defaults to `messages.groupChat.historyLimit` (fallback `50`). You can override per account with `channels.zalouser.historyLimit`.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalouser: {
|
||||
groupPolicy: "allowlist",
|
||||
groups: {
|
||||
"*": { allow: true, requireMention: true },
|
||||
"Work Chat": { allow: true, requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Multi-account
|
||||
|
||||
Accounts map to `zalouser` profiles in OpenClaw state. Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalouser: {
|
||||
enabled: true,
|
||||
defaultAccount: "default",
|
||||
accounts: {
|
||||
work: { enabled: true, profile: "work" },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Typing, reactions, and delivery acknowledgements
|
||||
|
||||
- OpenClaw sends a typing event before dispatching a reply (best-effort).
|
||||
- Message reaction action `react` is supported for `zalouser` in channel actions.
|
||||
- Use `remove: true` to remove a specific reaction emoji from a message.
|
||||
- Reaction semantics: [Reactions](/tools/reactions)
|
||||
- For inbound messages that include event metadata, OpenClaw sends delivered + seen acknowledgements (best-effort).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Login doesn't stick:**
|
||||
|
||||
- `openclaw channels status --probe`
|
||||
- Re-login: `openclaw channels logout --channel zalouser && openclaw channels login --channel zalouser`
|
||||
|
||||
**Allowlist/group name didn't resolve:**
|
||||
|
||||
- Use numeric IDs in `allowFrom`/`groupAllowFrom`/`groups`, or exact friend/group names.
|
||||
|
||||
**Upgraded from old CLI-based setup:**
|
||||
|
||||
- Remove any old external `zca` process assumptions.
|
||||
- The channel now runs fully in OpenClaw without external CLI binaries.
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
Loading…
Add table
Add a link
Reference in a new issue