From 8cab78abbc8dec493ae0fc63f9828636845eb1f8 Mon Sep 17 00:00:00 2001
From: cpojer
` (then the sender is added to a local allowlist store).
- Public inbound DMs require an explicit opt-in: set `dmPolicy="open"` and include `"*"` in the channel allowlist (`allowFrom` / `channels.discord.dm.allowFrom` / `channels.slack.dm.allowFrom`).
@@ -133,6 +135,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
## Everything we built so far
### Core platform
+
- [Gateway WS control plane](https://docs.openclaw.ai/gateway) with sessions, presence, config, cron, webhooks, [Control UI](https://docs.openclaw.ai/web), and [Canvas host](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui).
- [CLI surface](https://docs.openclaw.ai/tools/agent-send): gateway, agent, send, [wizard](https://docs.openclaw.ai/start/wizard), and [doctor](https://docs.openclaw.ai/gateway/doctor).
- [Pi agent runtime](https://docs.openclaw.ai/concepts/agent) in RPC mode with tool streaming and block streaming.
@@ -140,16 +143,19 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
- [Media pipeline](https://docs.openclaw.ai/nodes/images): images/audio/video, transcription hooks, size caps, temp file lifecycle. Audio details: [Audio](https://docs.openclaw.ai/nodes/audio).
### Channels
+
- [Channels](https://docs.openclaw.ai/channels): [WhatsApp](https://docs.openclaw.ai/channels/whatsapp) (Baileys), [Telegram](https://docs.openclaw.ai/channels/telegram) (grammY), [Slack](https://docs.openclaw.ai/channels/slack) (Bolt), [Discord](https://docs.openclaw.ai/channels/discord) (discord.js), [Google Chat](https://docs.openclaw.ai/channels/googlechat) (Chat API), [Signal](https://docs.openclaw.ai/channels/signal) (signal-cli), [iMessage](https://docs.openclaw.ai/channels/imessage) (imsg), [BlueBubbles](https://docs.openclaw.ai/channels/bluebubbles) (extension), [Microsoft Teams](https://docs.openclaw.ai/channels/msteams) (extension), [Matrix](https://docs.openclaw.ai/channels/matrix) (extension), [Zalo](https://docs.openclaw.ai/channels/zalo) (extension), [Zalo Personal](https://docs.openclaw.ai/channels/zalouser) (extension), [WebChat](https://docs.openclaw.ai/web/webchat).
- [Group routing](https://docs.openclaw.ai/concepts/group-messages): mention gating, reply tags, per-channel chunking and routing. Channel rules: [Channels](https://docs.openclaw.ai/channels).
### Apps + nodes
+
- [macOS app](https://docs.openclaw.ai/platforms/macos): menu bar control plane, [Voice Wake](https://docs.openclaw.ai/nodes/voicewake)/PTT, [Talk Mode](https://docs.openclaw.ai/nodes/talk) overlay, [WebChat](https://docs.openclaw.ai/web/webchat), debug tools, [remote gateway](https://docs.openclaw.ai/gateway/remote) control.
- [iOS node](https://docs.openclaw.ai/platforms/ios): [Canvas](https://docs.openclaw.ai/platforms/mac/canvas), [Voice Wake](https://docs.openclaw.ai/nodes/voicewake), [Talk Mode](https://docs.openclaw.ai/nodes/talk), camera, screen recording, Bonjour pairing.
- [Android node](https://docs.openclaw.ai/platforms/android): [Canvas](https://docs.openclaw.ai/platforms/mac/canvas), [Talk Mode](https://docs.openclaw.ai/nodes/talk), camera, screen recording, optional SMS.
- [macOS node mode](https://docs.openclaw.ai/nodes): system.run/notify + canvas/camera exposure.
### Tools + automation
+
- [Browser control](https://docs.openclaw.ai/tools/browser): dedicated openclaw Chrome/Chromium, snapshots, actions, uploads, profiles.
- [Canvas](https://docs.openclaw.ai/platforms/mac/canvas): [A2UI](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui) push/reset, eval, snapshot.
- [Nodes](https://docs.openclaw.ai/nodes): camera snap/clip, screen record, [location.get](https://docs.openclaw.ai/nodes/location-command), notifications.
@@ -157,12 +163,14 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
- [Skills platform](https://docs.openclaw.ai/tools/skills): bundled, managed, and workspace skills with install gating + UI.
### Runtime + safety
+
- [Channel routing](https://docs.openclaw.ai/concepts/channel-routing), [retry policy](https://docs.openclaw.ai/concepts/retry), and [streaming/chunking](https://docs.openclaw.ai/concepts/streaming).
- [Presence](https://docs.openclaw.ai/concepts/presence), [typing indicators](https://docs.openclaw.ai/concepts/typing-indicators), and [usage tracking](https://docs.openclaw.ai/concepts/usage-tracking).
- [Models](https://docs.openclaw.ai/concepts/models), [model failover](https://docs.openclaw.ai/concepts/model-failover), and [session pruning](https://docs.openclaw.ai/concepts/session-pruning).
- [Security](https://docs.openclaw.ai/gateway/security) and [troubleshooting](https://docs.openclaw.ai/channels/troubleshooting).
### Ops + packaging
+
- [Control UI](https://docs.openclaw.ai/web) + [WebChat](https://docs.openclaw.ai/web/webchat) served directly from the Gateway.
- [Tailscale Serve/Funnel](https://docs.openclaw.ai/gateway/tailscale) or [SSH tunnels](https://docs.openclaw.ai/gateway/remote) with token/password auth.
- [Nix mode](https://docs.openclaw.ai/install/nix) for declarative config; [Docker](https://docs.openclaw.ai/install/docker)-based installs.
@@ -205,6 +213,7 @@ OpenClaw can auto-configure Tailscale **Serve** (tailnet-only) or **Funnel** (pu
- `funnel`: public HTTPS via `tailscale funnel` (requires shared password auth).
Notes:
+
- `gateway.bind` must stay `loopback` when Serve/Funnel is enabled (OpenClaw enforces this).
- Serve can be forced to require a password by setting `gateway.auth.mode: "password"` or `gateway.auth.allowTailscale: false`.
- Funnel refuses to start unless `gateway.auth.mode: "password"` is set.
@@ -218,7 +227,7 @@ It’s perfectly fine to run the Gateway on a small Linux instance. Clients (mac
- **Gateway host** runs the exec tool and channel connections by default.
- **Device nodes** run device‑local actions (`system.run`, camera, screen recording, notifications) via `node.invoke`.
-In short: exec runs where the Gateway lives; device actions run where the device lives.
+ In short: exec runs where the Gateway lives; device actions run where the device lives.
Details: [Remote access](https://docs.openclaw.ai/gateway/remote) · [Nodes](https://docs.openclaw.ai/nodes) · [Security](https://docs.openclaw.ai/gateway/security)
@@ -237,7 +246,7 @@ Elevated bash (host permissions) is separate from macOS TCC:
Details: [Nodes](https://docs.openclaw.ai/nodes) · [macOS app](https://docs.openclaw.ai/platforms/macos) · [Gateway protocol](https://docs.openclaw.ai/concepts/architecture)
-## Agent to Agent (sessions_* tools)
+## Agent to Agent (sessions\_\* tools)
- Use these to coordinate work across sessions without jumping between chat surfaces.
- `sessions_list` — discover active sessions (agents) and their metadata.
@@ -307,8 +316,8 @@ Minimal `~/.openclaw/openclaw.json` (model + defaults):
```json5
{
agent: {
- model: "anthropic/claude-opus-4-5"
- }
+ model: "anthropic/claude-opus-4-5",
+ },
}
```
@@ -337,9 +346,9 @@ Details: [Security guide](https://docs.openclaw.ai/gateway/security) · [Docker
{
channels: {
telegram: {
- botToken: "123456:ABCDEF"
- }
- }
+ botToken: "123456:ABCDEF",
+ },
+ },
}
```
@@ -356,9 +365,9 @@ Details: [Security guide](https://docs.openclaw.ai/gateway/security) · [Docker
{
channels: {
discord: {
- token: "1234abcd"
- }
- }
+ token: "1234abcd",
+ },
+ },
}
```
@@ -386,14 +395,15 @@ Browser control (optional):
{
browser: {
enabled: true,
- color: "#FF4500"
- }
+ color: "#FF4500",
+ },
}
```
## Docs
Use these when you’re past the onboarding flow and want the deeper reference.
+
- [Start with the docs index for navigation and “what’s where.”](https://docs.openclaw.ai)
- [Read the architecture overview for the gateway + protocol model.](https://docs.openclaw.ai/concepts/architecture)
- [Use the full configuration reference when you need every key and example.](https://docs.openclaw.ai/gateway/configuration)
diff --git a/docker-compose.yml b/docker-compose.yml
index 8aaed5788..e1be94210 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -12,19 +12,19 @@ services:
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
ports:
- - '${OPENCLAW_GATEWAY_PORT:-18789}:18789'
- - '${OPENCLAW_BRIDGE_PORT:-18790}:18790'
+ - "${OPENCLAW_GATEWAY_PORT:-18789}:18789"
+ - "${OPENCLAW_BRIDGE_PORT:-18790}:18790"
init: true
restart: unless-stopped
command:
[
- 'node',
- 'dist/index.js',
+ "node",
+ "dist/index.js",
"gateway",
- '--bind',
- '${OPENCLAW_GATEWAY_BIND:-lan}',
- '--port',
- '${OPENCLAW_GATEWAY_PORT:-18789}',
+ "--bind",
+ "${OPENCLAW_GATEWAY_BIND:-lan}",
+ "--port",
+ "${OPENCLAW_GATEWAY_PORT:-18789}",
]
openclaw-cli:
@@ -42,4 +42,4 @@ services:
stdin_open: true
tty: true
init: true
- entrypoint: ['node', 'dist/index.js']
+ entrypoint: ["node", "dist/index.js"]
diff --git a/docs.acp.md b/docs.acp.md
index bdb63d29d..00950c9a5 100644
--- a/docs.acp.md
+++ b/docs.acp.md
@@ -84,9 +84,12 @@ To target a specific Gateway or agent:
"command": "openclaw",
"args": [
"acp",
- "--url", "wss://gateway-host:18789",
- "--token", "",
- "--session", "agent:design:main"
+ "--url",
+ "wss://gateway-host:18789",
+ "--token",
+ "",
+ "--session",
+ "agent:design:main"
],
"env": {}
}
@@ -112,7 +115,7 @@ By default each ACP session is mapped to a dedicated Gateway session key:
You can override or reuse sessions in two ways:
-1) CLI defaults
+1. CLI defaults
```bash
openclaw acp --session agent:main:main
@@ -120,7 +123,7 @@ openclaw acp --session-label "support inbox"
openclaw acp --reset-session
```
-2) ACP metadata per session
+2. ACP metadata per session
```json
{
diff --git a/docs/automation/auth-monitoring.md b/docs/automation/auth-monitoring.md
index ed33312b2..b5834923e 100644
--- a/docs/automation/auth-monitoring.md
+++ b/docs/automation/auth-monitoring.md
@@ -4,6 +4,7 @@ read_when:
- Setting up auth expiry monitoring or alerts
- Automating Claude Code / Codex OAuth refresh checks
---
+
# Auth monitoring
OpenClaw exposes OAuth expiry health via `openclaw models status`. Use that for
@@ -16,6 +17,7 @@ openclaw models status --check
```
Exit codes:
+
- `0`: OK
- `1`: expired or missing credentials
- `2`: expiring soon (within 24h)
diff --git a/docs/automation/cron-jobs.md b/docs/automation/cron-jobs.md
index ab2932e01..6f96feeb3 100644
--- a/docs/automation/cron-jobs.md
+++ b/docs/automation/cron-jobs.md
@@ -5,6 +5,7 @@ read_when:
- Wiring automation that should run with or alongside heartbeats
- Deciding between heartbeat and cron for scheduled tasks
---
+
# Cron jobs (Gateway scheduler)
> **Cron vs Heartbeat?** See [Cron vs Heartbeat](/automation/cron-vs-heartbeat) for guidance on when to use each.
@@ -12,10 +13,11 @@ read_when:
Cron is the Gateway’s built-in scheduler. It persists jobs, wakes the agent at
the right time, and can optionally deliver output back to a chat.
-If you want *“run this every morning”* or *“poke the agent in 20 minutes”*,
+If you want _“run this every morning”_ or _“poke the agent in 20 minutes”_,
cron is the mechanism.
## TL;DR
+
- Cron runs **inside the Gateway** (not inside the model).
- Jobs persist under `~/.openclaw/cron/` so restarts don’t lose schedules.
- Two execution styles:
@@ -24,18 +26,19 @@ cron is the mechanism.
- Wakeups are first-class: a job can request “wake now” vs “next heartbeat”.
## Beginner-friendly overview
+
Think of a cron job as: **when** to run + **what** to do.
-1) **Choose a schedule**
+1. **Choose a schedule**
- One-shot reminder → `schedule.kind = "at"` (CLI: `--at`)
- Repeating job → `schedule.kind = "every"` or `schedule.kind = "cron"`
- If your ISO timestamp omits a timezone, it is treated as **UTC**.
-2) **Choose where it runs**
+2. **Choose where it runs**
- `sessionTarget: "main"` → run during the next heartbeat with main context.
- `sessionTarget: "isolated"` → run a dedicated agent turn in `cron:`.
-3) **Choose the payload**
+3. **Choose the payload**
- Main session → `payload.kind = "systemEvent"`
- Isolated session → `payload.kind = "agentTurn"`
@@ -44,7 +47,9 @@ Optional: `deleteAfterRun: true` removes successful one-shot jobs from the store
## Concepts
### Jobs
+
A cron job is a stored record with:
+
- a **schedule** (when it should run),
- a **payload** (what it should do),
- optional **delivery** (where output should be sent).
@@ -56,7 +61,9 @@ In agent tool calls, `jobId` is canonical; legacy `id` is accepted for compatibi
Jobs can optionally auto-delete after a successful one-shot run via `deleteAfterRun: true`.
### Schedules
+
Cron supports three schedule kinds:
+
- `at`: one-shot timestamp (ms since epoch). Gateway accepts ISO 8601 and coerces to UTC.
- `every`: fixed interval (ms).
- `cron`: 5-field cron expression with optional IANA timezone.
@@ -67,6 +74,7 @@ local timezone is used.
### Main vs isolated execution
#### Main session jobs (system events)
+
Main jobs enqueue a system event and optionally wake the heartbeat runner.
They must use `payload.kind = "systemEvent"`.
@@ -77,9 +85,11 @@ This is the best fit when you want the normal heartbeat prompt + main-session co
See [Heartbeat](/gateway/heartbeat).
#### Isolated jobs (dedicated cron sessions)
+
Isolated jobs run a dedicated agent turn in session `cron:`.
Key behaviors:
+
- Prompt is prefixed with `[cron: ]` for traceability.
- Each run starts a **fresh session id** (no prior conversation carry-over).
- A summary is posted to the main session (prefix `Cron`, configurable).
@@ -90,11 +100,14 @@ Use isolated jobs for noisy, frequent, or "background chores" that shouldn't spa
your main chat history.
### Payload shapes (what runs)
+
Two payload kinds are supported:
+
- `systemEvent`: main-session only, routed through the heartbeat prompt.
- `agentTurn`: isolated-session only, runs a dedicated agent turn.
Common `agentTurn` fields:
+
- `message`: required text prompt.
- `model` / `thinking`: optional overrides (see below).
- `timeoutSeconds`: optional timeout override.
@@ -104,12 +117,15 @@ Common `agentTurn` fields:
- `bestEffortDeliver`: avoid failing the job if delivery fails.
Isolation options (only for `session=isolated`):
+
- `postToMainPrefix` (CLI: `--post-prefix`): prefix for the system event in main.
- `postToMainMode`: `summary` (default) or `full`.
- `postToMainMaxChars`: max chars when `postToMainMode=full` (default 8000).
### Model and thinking overrides
+
Isolated jobs (`agentTurn`) can override the model and thinking level:
+
- `model`: Provider/model string (e.g., `anthropic/claude-sonnet-4-20250514`) or alias (e.g., `opus`)
- `thinking`: Thinking level (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`; GPT-5.2 + Codex models only)
@@ -118,12 +134,15 @@ session model. We recommend model overrides only for isolated jobs to avoid
unexpected context shifts.
Resolution priority:
+
1. Job payload override (highest)
2. Hook-specific defaults (e.g., `hooks.gmail.model`)
3. Agent config default
### Delivery (channel + target)
+
Isolated jobs can deliver output to a channel. The job payload can specify:
+
- `channel`: `whatsapp` / `telegram` / `discord` / `slack` / `mattermost` (plugin) / `signal` / `imessage` / `last`
- `to`: channel-specific recipient target
@@ -131,15 +150,18 @@ If `channel` or `to` is omitted, cron can fall back to the main session’s “l
(the last place the agent replied).
Delivery notes:
+
- If `to` is set, cron auto-delivers the agent’s final output even if `deliver` is omitted.
- Use `deliver: true` when you want last-route delivery without an explicit `to`.
- Use `deliver: false` to keep output internal even if a `to` is present.
Target format reminders:
+
- Slack/Discord/Mattermost (plugin) targets should use explicit prefixes (e.g. `channel:`, `user:`) to avoid ambiguity.
- Telegram topics should use the `:topic:` form (see below).
#### Telegram delivery targets (topics / forum threads)
+
Telegram supports forum topics via `message_thread_id`. For cron delivery, you can encode
the topic/thread into the `to` field:
@@ -148,9 +170,11 @@ the topic/thread into the `to` field:
- `-1001234567890:123` (shorthand: numeric suffix)
Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted:
+
- `telegram:group:-1001234567890:topic:123`
## Storage & history
+
- Job store: `~/.openclaw/cron/jobs.json` (Gateway-managed JSON).
- Run history: `~/.openclaw/cron/runs/.jsonl` (JSONL, auto-pruned).
- Override store path: `cron.store` in config.
@@ -162,18 +186,20 @@ Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted:
cron: {
enabled: true, // default true
store: "~/.openclaw/cron/jobs.json",
- maxConcurrentRuns: 1 // default 1
- }
+ maxConcurrentRuns: 1, // default 1
+ },
}
```
Disable cron entirely:
+
- `cron.enabled: false` (config)
- `OPENCLAW_SKIP_CRON=1` (env)
## CLI quickstart
One-shot reminder (UTC ISO, auto-delete after success):
+
```bash
openclaw cron add \
--name "Send reminder" \
@@ -185,6 +211,7 @@ openclaw cron add \
```
One-shot reminder (main session, wake immediately):
+
```bash
openclaw cron add \
--name "Calendar check" \
@@ -195,6 +222,7 @@ openclaw cron add \
```
Recurring isolated job (deliver to WhatsApp):
+
```bash
openclaw cron add \
--name "Morning status" \
@@ -208,6 +236,7 @@ openclaw cron add \
```
Recurring isolated job (deliver to a Telegram topic):
+
```bash
openclaw cron add \
--name "Nightly summary (topic)" \
@@ -221,7 +250,8 @@ openclaw cron add \
```
Isolated job with model and thinking override:
-```bash
+
+````bash
openclaw cron add \
--name "Deep analysis" \
--cron "0 6 * * 1" \
@@ -242,15 +272,17 @@ openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --mes
# Switch or clear the agent on an existing job
openclaw cron edit --agent ops
openclaw cron edit --clear-agent
-```
-```
+````
+
+````
Manual run (debug):
```bash
openclaw cron run --force
-```
+````
Edit an existing job (patch fields):
+
```bash
openclaw cron edit \
--message "Updated prompt" \
@@ -259,28 +291,33 @@ openclaw cron edit \
```
Run history:
+
```bash
openclaw cron runs --id --limit 50
```
Immediate system event without creating a job:
+
```bash
openclaw system event --mode now --text "Next heartbeat: check battery."
```
## Gateway API surface
+
- `cron.list`, `cron.status`, `cron.add`, `cron.update`, `cron.remove`
- `cron.run` (force or due), `cron.runs`
-For immediate system events without a job, use [`openclaw system event`](/cli/system).
+ For immediate system events without a job, use [`openclaw system event`](/cli/system).
## Troubleshooting
### “Nothing runs”
+
- Check cron is enabled: `cron.enabled` and `OPENCLAW_SKIP_CRON`.
- Check the Gateway is running continuously (cron runs inside the Gateway process).
- For `cron` schedules: confirm timezone (`--tz`) vs the host timezone.
### Telegram delivers to the wrong place
+
- For forum topics, use `-100…:topic:` so it’s explicit and unambiguous.
- If you see `telegram:...` prefixes in logs or stored “last route” targets, that’s normal;
cron delivery accepts them and still parses topic IDs correctly.
diff --git a/docs/automation/cron-vs-heartbeat.md b/docs/automation/cron-vs-heartbeat.md
index 01c34e389..f01e03129 100644
--- a/docs/automation/cron-vs-heartbeat.md
+++ b/docs/automation/cron-vs-heartbeat.md
@@ -5,20 +5,21 @@ read_when:
- Setting up background monitoring or notifications
- Optimizing token usage for periodic checks
---
+
# Cron vs Heartbeat: When to Use Each
Both heartbeats and cron jobs let you run tasks on a schedule. This guide helps you choose the right mechanism for your use case.
## Quick Decision Guide
-| Use Case | Recommended | Why |
-|----------|-------------|-----|
-| Check inbox every 30 min | Heartbeat | Batches with other checks, context-aware |
-| Send daily report at 9am sharp | Cron (isolated) | Exact timing needed |
-| Monitor calendar for upcoming events | Heartbeat | Natural fit for periodic awareness |
-| Run weekly deep analysis | Cron (isolated) | Standalone task, can use different model |
-| Remind me in 20 minutes | Cron (main, `--at`) | One-shot with precise timing |
-| Background project health check | Heartbeat | Piggybacks on existing cycle |
+| Use Case | Recommended | Why |
+| ------------------------------------ | ------------------- | ---------------------------------------- |
+| Check inbox every 30 min | Heartbeat | Batches with other checks, context-aware |
+| Send daily report at 9am sharp | Cron (isolated) | Exact timing needed |
+| Monitor calendar for upcoming events | Heartbeat | Natural fit for periodic awareness |
+| Run weekly deep analysis | Cron (isolated) | Standalone task, can use different model |
+| Remind me in 20 minutes | Cron (main, `--at`) | One-shot with precise timing |
+| Background project health check | Heartbeat | Piggybacks on existing cycle |
## Heartbeat: Periodic Awareness
@@ -59,12 +60,12 @@ The agent reads this on each heartbeat and handles all items in one turn.
agents: {
defaults: {
heartbeat: {
- every: "30m", // interval
- target: "last", // where to deliver alerts
- activeHours: { start: "08:00", end: "22:00" } // optional
- }
- }
- }
+ every: "30m", // interval
+ target: "last", // where to deliver alerts
+ activeHours: { start: "08:00", end: "22:00" }, // optional
+ },
+ },
+ },
}
```
@@ -157,8 +158,10 @@ The most efficient setup uses **both**:
### Example: Efficient automation setup
**HEARTBEAT.md** (checked every 30 min):
+
```md
# Heartbeat checklist
+
- Scan inbox for urgent emails
- Check calendar for events in next 2h
- Review any pending tasks
@@ -166,6 +169,7 @@ The most efficient setup uses **both**:
```
**Cron jobs** (precise timing):
+
```bash
# Daily morning briefing at 7am
openclaw cron add --name "Morning brief" --cron "0 7 * * *" --session isolated --message "..." --deliver
@@ -177,7 +181,6 @@ openclaw cron add --name "Weekly review" --cron "0 9 * * 1" --session isolated -
openclaw cron add --name "Call back" --at "2h" --session main --system-event "Call back the client" --wake now
```
-
## Lobster: Deterministic workflows with approvals
Lobster is the workflow runtime for **multi-step tool pipelines** that need deterministic execution and explicit approvals.
@@ -191,8 +194,8 @@ Use it when the task is more than a single agent turn, and you want a resumable
### How it pairs with heartbeat and cron
-- **Heartbeat/cron** decide *when* a run happens.
-- **Lobster** defines *what steps* happen once the run starts.
+- **Heartbeat/cron** decide _when_ a run happens.
+- **Lobster** defines _what steps_ happen once the run starts.
For scheduled workflows, use cron or heartbeat to trigger an agent turn that calls Lobster.
For ad-hoc workflows, call Lobster directly.
@@ -210,17 +213,18 @@ See [Lobster](/tools/lobster) for full usage and examples.
Both heartbeat and cron can interact with the main session, but differently:
-| | Heartbeat | Cron (main) | Cron (isolated) |
-|---|---|---|---|
-| Session | Main | Main (via system event) | `cron:` |
-| History | Shared | Shared | Fresh each run |
-| Context | Full | Full | None (starts clean) |
-| Model | Main session model | Main session model | Can override |
-| Output | Delivered if not `HEARTBEAT_OK` | Heartbeat prompt + event | Summary posted to main |
+| | Heartbeat | Cron (main) | Cron (isolated) |
+| ------- | ------------------------------- | ------------------------ | ---------------------- |
+| Session | Main | Main (via system event) | `cron:` |
+| History | Shared | Shared | Fresh each run |
+| Context | Full | Full | None (starts clean) |
+| Model | Main session model | Main session model | Can override |
+| Output | Delivered if not `HEARTBEAT_OK` | Heartbeat prompt + event | Summary posted to main |
### When to use main session cron
Use `--session main` with `--system-event` when you want:
+
- The reminder/event to appear in main session context
- The agent to handle it during the next heartbeat with full context
- No separate isolated run
@@ -237,6 +241,7 @@ openclaw cron add \
### When to use isolated cron
Use `--session isolated` when you want:
+
- A clean slate without prior context
- Different model or thinking settings
- Output delivered directly to a channel (summary still posts to main by default)
@@ -255,13 +260,14 @@ openclaw cron add \
## Cost Considerations
-| Mechanism | Cost Profile |
-|-----------|--------------|
-| Heartbeat | One turn every N minutes; scales with HEARTBEAT.md size |
-| Cron (main) | Adds event to next heartbeat (no isolated turn) |
-| Cron (isolated) | Full agent turn per job; can use cheaper model |
+| Mechanism | Cost Profile |
+| --------------- | ------------------------------------------------------- |
+| Heartbeat | One turn every N minutes; scales with HEARTBEAT.md size |
+| Cron (main) | Adds event to next heartbeat (no isolated turn) |
+| Cron (isolated) | Full agent turn per job; can use cheaper model |
**Tips**:
+
- Keep `HEARTBEAT.md` small to minimize token overhead.
- Batch similar checks into heartbeat instead of multiple cron jobs.
- Use `target: "none"` on heartbeat if you only want internal processing.
diff --git a/docs/automation/gmail-pubsub.md b/docs/automation/gmail-pubsub.md
index 3998fd4fe..fe4a799be 100644
--- a/docs/automation/gmail-pubsub.md
+++ b/docs/automation/gmail-pubsub.md
@@ -26,8 +26,8 @@ Example hook config (enable Gmail preset mapping):
enabled: true,
token: "OPENCLAW_HOOK_TOKEN",
path: "/hooks",
- presets: ["gmail"]
- }
+ presets: ["gmail"],
+ },
}
```
@@ -47,15 +47,14 @@ that sets `deliver` + optional `channel`/`to`:
wakeMode: "now",
name: "Gmail",
sessionKey: "hook:gmail:{{messages[0].id}}",
- messageTemplate:
- "New email from {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}\n{{messages[0].body}}",
+ messageTemplate: "New email from {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}\n{{messages[0].body}}",
model: "openai/gpt-5.2-mini",
deliver: true,
- channel: "last"
+ channel: "last",
// to: "+15551234567"
- }
- ]
- }
+ },
+ ],
+ },
}
```
@@ -73,13 +72,14 @@ To set a default model and thinking level specifically for Gmail hooks, add
hooks: {
gmail: {
model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
- thinking: "off"
- }
- }
+ thinking: "off",
+ },
+ },
}
```
Notes:
+
- Per-hook `model`/`thinking` in the mapping still overrides these defaults.
- Fallback order: `hooks.gmail.model` → `agents.defaults.model.fallbacks` → primary (auth/rate-limit/timeouts).
- If `agents.defaults.models` is set, the Gmail model must be in the allowlist.
@@ -99,6 +99,7 @@ openclaw webhooks gmail setup \
```
Defaults:
+
- Uses Tailscale Funnel for the public push endpoint.
- Writes `hooks.gmail` config for `openclaw webhooks gmail run`.
- Enables the Gmail hook preset (`hooks.presets: ["gmail"]`).
@@ -117,6 +118,7 @@ Platform note: on macOS the wizard installs `gcloud`, `gogcli`, and `tailscale`
via Homebrew; on Linux install them manually first.
Gateway auto-start (recommended):
+
- When `hooks.enabled=true` and `hooks.gmail.account` is set, the Gateway starts
`gog gmail watch serve` on boot and auto-renews the watch.
- Set `OPENCLAW_SKIP_GMAIL_WATCHER=1` to opt out (useful if you run the daemon yourself).
@@ -131,7 +133,7 @@ openclaw webhooks gmail run
## One-time setup
-1) Select the GCP project **that owns the OAuth client** used by `gog`.
+1. Select the GCP project **that owns the OAuth client** used by `gog`.
```bash
gcloud auth login
@@ -140,19 +142,19 @@ gcloud config set project
Note: Gmail watch requires the Pub/Sub topic to live in the same project as the OAuth client.
-2) Enable APIs:
+2. Enable APIs:
```bash
gcloud services enable gmail.googleapis.com pubsub.googleapis.com
```
-3) Create a topic:
+3. Create a topic:
```bash
gcloud pubsub topics create gog-gmail-watch
```
-4) Allow Gmail push to publish:
+4. Allow Gmail push to publish:
```bash
gcloud pubsub topics add-iam-policy-binding gog-gmail-watch \
@@ -189,6 +191,7 @@ gog gmail watch serve \
```
Notes:
+
- `--token` protects the push endpoint (`x-gog-token` or `?token=`).
- `--hook-url` points to OpenClaw `/hooks/gmail` (mapped; isolated run + summary to main).
- `--include-body` and `--max-bytes` control the body snippet sent to OpenClaw.
diff --git a/docs/automation/poll.md b/docs/automation/poll.md
index f7a37af86..e8f541ced 100644
--- a/docs/automation/poll.md
+++ b/docs/automation/poll.md
@@ -4,10 +4,11 @@ read_when:
- Adding or modifying poll support
- Debugging poll sends from the CLI or gateway
---
+
# Polls
-
## Supported channels
+
- WhatsApp (web channel)
- Discord
- MS Teams (Adaptive Cards)
@@ -33,6 +34,7 @@ openclaw message poll --channel msteams --target conversation:19:abc@thread.tacv
```
Options:
+
- `--channel`: `whatsapp` (default), `discord`, or `msteams`
- `--poll-multi`: allow selecting multiple options
- `--poll-duration-hours`: Discord-only (defaults to 24 when omitted)
@@ -42,6 +44,7 @@ Options:
Method: `poll`
Params:
+
- `to` (string, required)
- `question` (string, required)
- `options` (string[], required)
@@ -51,11 +54,13 @@ Params:
- `idempotencyKey` (string, required)
## Channel differences
+
- WhatsApp: 2-12 options, `maxSelections` must be within option count, ignores `durationHours`.
- Discord: 2-10 options, `durationHours` clamped to 1-768 hours (default 24). `maxSelections > 1` enables multi-select; Discord does not support a strict selection count.
- MS Teams: Adaptive Card polls (OpenClaw-managed). No native poll API; `durationHours` is ignored.
## Agent tool (Message)
+
Use the `message` tool with `poll` action (`to`, `pollQuestion`, `pollOption`, optional `pollMulti`, `pollDurationHours`, `channel`).
Note: Discord has no “pick exactly N” mode; `pollMulti` maps to multi-select.
diff --git a/docs/automation/webhook.md b/docs/automation/webhook.md
index 7926b9d40..6fb823030 100644
--- a/docs/automation/webhook.md
+++ b/docs/automation/webhook.md
@@ -16,18 +16,20 @@ Gateway can expose a small HTTP webhook endpoint for external triggers.
hooks: {
enabled: true,
token: "shared-secret",
- path: "/hooks"
- }
+ path: "/hooks",
+ },
}
```
Notes:
+
- `hooks.token` is required when `hooks.enabled=true`.
- `hooks.path` defaults to `/hooks`.
## Auth
Every request must include the hook token. Prefer headers:
+
- `Authorization: Bearer ` (recommended)
- `x-openclaw-token: `
- `?token=` (deprecated; logs a warning and will be removed in a future major release)
@@ -37,6 +39,7 @@ Every request must include the hook token. Prefer headers:
### `POST /hooks/wake`
Payload:
+
```json
{ "text": "System line", "mode": "now" }
```
@@ -45,12 +48,14 @@ Payload:
- `mode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
Effect:
+
- Enqueues a system event for the **main** session
- If `mode=now`, triggers an immediate heartbeat
### `POST /hooks/agent`
Payload:
+
```json
{
"message": "Run this",
@@ -78,6 +83,7 @@ Payload:
- `timeoutSeconds` optional (number): Maximum duration for the agent run in seconds.
Effect:
+
- Runs an **isolated** agent turn (own session key)
- Always posts a summary into the **main** session
- If `wakeMode=now`, triggers an immediate heartbeat
@@ -89,6 +95,7 @@ turn arbitrary payloads into `wake` or `agent` actions, with optional templates
code transforms.
Mapping options (summary):
+
- `hooks.presets: ["gmail"]` enables the built-in Gmail mapping.
- `hooks.mappings` lets you define `match`, `action`, and templates in config.
- `hooks.transformsDir` + `transform.module` loads a JS/TS module for custom logic.
@@ -99,7 +106,7 @@ Mapping options (summary):
- `allowUnsafeExternalContent: true` disables the external content safety wrapper for that hook
(dangerous; only for trusted internal sources).
- `openclaw webhooks gmail setup` writes `hooks.gmail` config for `openclaw webhooks gmail run`.
-See [Gmail Pub/Sub](/automation/gmail-pubsub) for the full Gmail watch flow.
+ See [Gmail Pub/Sub](/automation/gmail-pubsub) for the full Gmail watch flow.
## Responses
diff --git a/docs/bedrock.md b/docs/bedrock.md
index 251050ca4..78e3225f3 100644
--- a/docs/bedrock.md
+++ b/docs/bedrock.md
@@ -4,6 +4,7 @@ read_when:
- You want to use Amazon Bedrock models with OpenClaw
- You need AWS credential/region setup for model calls
---
+
# Amazon Bedrock
OpenClaw can use **Amazon Bedrock** models via pi‑ai’s **Bedrock Converse**
@@ -34,13 +35,14 @@ Config options live under `models.bedrockDiscovery`:
providerFilter: ["anthropic", "amazon"],
refreshInterval: 3600,
defaultContextWindow: 32000,
- defaultMaxTokens: 4096
- }
- }
+ defaultMaxTokens: 4096,
+ },
+ },
}
```
Notes:
+
- `enabled` defaults to `true` when AWS credentials are present.
- `region` defaults to `AWS_REGION` or `AWS_DEFAULT_REGION`, then `us-east-1`.
- `providerFilter` matches Bedrock provider names (for example `anthropic`).
@@ -50,7 +52,7 @@ Notes:
## Setup (manual)
-1) Ensure AWS credentials are available on the **gateway host**:
+1. Ensure AWS credentials are available on the **gateway host**:
```bash
export AWS_ACCESS_KEY_ID="AKIA..."
@@ -63,7 +65,7 @@ export AWS_PROFILE="your-profile"
export AWS_BEARER_TOKEN_BEDROCK="..."
```
-2) Add a Bedrock provider and model to your config (no `apiKey` required):
+2. Add a Bedrock provider and model to your config (no `apiKey` required):
```json5
{
@@ -81,17 +83,17 @@ export AWS_BEARER_TOKEN_BEDROCK="..."
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
- maxTokens: 8192
- }
- ]
- }
- }
+ maxTokens: 8192,
+ },
+ ],
+ },
+ },
},
agents: {
defaults: {
- model: { primary: "amazon-bedrock/anthropic.claude-opus-4-5-20251101-v1:0" }
- }
- }
+ model: { primary: "amazon-bedrock/anthropic.claude-opus-4-5-20251101-v1:0" },
+ },
+ },
}
```
@@ -112,6 +114,7 @@ export AWS_REGION=us-east-1
```
**Required IAM permissions** for the EC2 instance role:
+
- `bedrock:InvokeModel`
- `bedrock:InvokeModelWithResponseStream`
- `bedrock:ListFoundationModels` (for automatic discovery)
diff --git a/docs/brave-search.md b/docs/brave-search.md
index e60116a84..045366547 100644
--- a/docs/brave-search.md
+++ b/docs/brave-search.md
@@ -11,9 +11,9 @@ OpenClaw uses Brave Search as the default provider for `web_search`.
## Get an API key
-1) Create a Brave Search API account at https://brave.com/search/api/
-2) In the dashboard, choose the **Data for Search** plan and generate an API key.
-3) Store the key in config (recommended) or set `BRAVE_API_KEY` in the Gateway environment.
+1. Create a Brave Search API account at https://brave.com/search/api/
+2. In the dashboard, choose the **Data for Search** plan and generate an API key.
+3. Store the key in config (recommended) or set `BRAVE_API_KEY` in the Gateway environment.
## Config example
@@ -25,10 +25,10 @@ OpenClaw uses Brave Search as the default provider for `web_search`.
provider: "brave",
apiKey: "BRAVE_API_KEY_HERE",
maxResults: 5,
- timeoutSeconds: 30
- }
- }
- }
+ timeoutSeconds: 30,
+ },
+ },
+ },
}
```
diff --git a/docs/broadcast-groups.md b/docs/broadcast-groups.md
index 0b5b6df16..de0e03499 100644
--- a/docs/broadcast-groups.md
+++ b/docs/broadcast-groups.md
@@ -22,7 +22,9 @@ Broadcast groups are evaluated after channel allowlists and group activation rul
## Use Cases
### 1. Specialized Agent Teams
+
Deploy multiple agents with atomic, focused responsibilities:
+
```
Group: "Development Team"
Agents:
@@ -35,6 +37,7 @@ Agents:
Each agent processes the same message and provides its specialized perspective.
### 2. Multi-Language Support
+
```
Group: "International Support"
Agents:
@@ -44,6 +47,7 @@ Agents:
```
### 3. Quality Assurance Workflows
+
```
Group: "Customer Support"
Agents:
@@ -52,6 +56,7 @@ Agents:
```
### 4. Task Automation
+
```
Group: "Project Management"
Agents:
@@ -65,6 +70,7 @@ Agents:
### 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`)
@@ -83,7 +89,9 @@ Add a top-level `broadcast` section (next to `bindings`). Keys are WhatsApp peer
Control how agents process messages:
#### Parallel (Default)
+
All agents process simultaneously:
+
```json
{
"broadcast": {
@@ -94,7 +102,9 @@ All agents process simultaneously:
```
#### Sequential
+
Agents process in order (one waits for previous to finish):
+
```json
{
"broadcast": {
@@ -152,7 +162,7 @@ Agents process in order (one waits for previous to finish):
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.
+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
@@ -166,6 +176,7 @@ Each agent in a broadcast group maintains completely separate:
- **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)
@@ -176,6 +187,7 @@ This allows each agent to have:
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]
@@ -184,8 +196,9 @@ Tools: read, write, exec
```
**Bärbel's context:**
+
```
-Session: agent:baerbel:whatsapp:group:120363403215116621@g.us
+Session: agent:baerbel:whatsapp:group:120363403215116621@g.us
History: [user message, baerbel's previous responses]
Workspace: /Users/pascal/openclaw-baerbel/
Tools: read only
@@ -230,10 +243,10 @@ Give agents only the tools they need:
{
"agents": {
"reviewer": {
- "tools": { "allow": ["read", "exec"] } // Read-only
+ "tools": { "allow": ["read", "exec"] } // Read-only
},
"fixer": {
- "tools": { "allow": ["read", "write", "edit", "exec"] } // Read-write
+ "tools": { "allow": ["read", "write", "edit", "exec"] } // Read-write
}
}
}
@@ -242,6 +255,7 @@ Give agents only the tools they need:
### 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
@@ -260,6 +274,7 @@ Result: Agent A and C respond, Agent B logs error
### Providers
Broadcast groups currently work with:
+
- ✅ WhatsApp (implemented)
- 🚧 Telegram (planned)
- 🚧 Discord (planned)
@@ -272,7 +287,10 @@ Broadcast groups work alongside existing routing:
```json
{
"bindings": [
- { "match": { "channel": "whatsapp", "peer": { "kind": "group", "id": "GROUP_A" } }, "agentId": "alfred" }
+ {
+ "match": { "channel": "whatsapp", "peer": { "kind": "group", "id": "GROUP_A" } },
+ "agentId": "alfred"
+ }
],
"broadcast": {
"GROUP_B": ["agent1", "agent2"]
@@ -290,11 +308,13 @@ Broadcast groups work alongside existing routing:
### 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
```
@@ -308,6 +328,7 @@ tail -f ~/.openclaw/logs/gateway.log | grep broadcast
### Performance Issues
**If slow with many agents:**
+
- Reduce number of agents per group
- Use lighter models (sonnet instead of opus)
- Check sandbox startup time
@@ -329,9 +350,21 @@ tail -f ~/.openclaw/logs/gateway.log | grep broadcast
},
"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": "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"] } }
]
}
@@ -340,6 +373,7 @@ tail -f ~/.openclaw/logs/gateway.log | grep broadcast
**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"
@@ -381,7 +415,6 @@ interface OpenClawConfig {
- `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
@@ -395,6 +428,7 @@ interface OpenClawConfig {
## 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)
diff --git a/docs/channels/bluebubbles.md b/docs/channels/bluebubbles.md
index 31db99d81..2033db10e 100644
--- a/docs/channels/bluebubbles.md
+++ b/docs/channels/bluebubbles.md
@@ -5,11 +5,13 @@ read_when:
- Troubleshooting webhook pairing
- Configuring iMessage on macOS
---
+
# 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.
## 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/*`).
@@ -20,6 +22,7 @@ Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **R
- 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:
@@ -30,21 +33,24 @@ Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **R
enabled: true,
serverUrl: "http://192.168.1.100:1234",
password: "example-password",
- webhookPath: "/bluebubbles-webhook"
- }
- }
+ webhookPath: "/bluebubbles-webhook",
+ },
+ },
}
```
4. Point BlueBubbles webhooks to your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=`).
5. Start the gateway; it will register the webhook handler and start pairing.
## Onboarding
+
BlueBubbles is available in the interactive setup wizard:
+
```
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`
@@ -52,12 +58,15 @@ The wizard prompts for:
- **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
```
## 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:
@@ -66,16 +75,20 @@ DMs:
- Pairing is the default token exchange. Details: [Pairing](/start/pairing)
Groups:
+
- `channels.bluebubbles.groupPolicy = open | allowlist | disabled` (default: `allowlist`).
- `channels.bluebubbles.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
### 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: {
@@ -83,20 +96,22 @@ Per-group configuration:
groupPolicy: "allowlist",
groupAllowFrom: ["+15555550123"],
groups: {
- "*": { requireMention: true }, // default for all groups
- "iMessage;-;chat123": { requireMention: false } // override for specific group
- }
- }
- }
+ "*": { 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.
## 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).
@@ -105,13 +120,14 @@ Per-group configuration:
{
channels: {
bluebubbles: {
- sendReadReceipts: false // disable read receipts
- }
- }
+ sendReadReceipts: false, // disable read receipts
+ },
+ },
}
```
## Advanced actions
+
BlueBubbles supports advanced message actions when enabled in config:
```json5
@@ -119,24 +135,25 @@ BlueBubbles supports advanced message actions when enabled in config:
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
+ 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
- }
- }
- }
+ 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`)
@@ -151,39 +168,47 @@ Available actions:
- 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.
### Message IDs (short vs full)
-OpenClaw may surface *short* message IDs (e.g., `1`, `2`) to save tokens.
+
+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 (default behavior)
- }
- }
+ blockStreaming: true, // enable block streaming (default behavior)
+ },
+ },
}
```
## Media + limits
+
- Inbound attachments are downloaded and stored in the media cache.
- Media cap via `channels.bluebubbles.mediaMaxMb` (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.
@@ -204,11 +229,14 @@ Provider options:
- `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:...`
@@ -216,12 +244,14 @@ Prefer `chat_guid` for stable routing:
- 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`. Requests from `localhost` are also accepted.
- Keep the API password and webhook endpoint secret (treat them like credentials).
- Localhost trust means a same-host reverse proxy can unintentionally bypass the password. If you proxy the gateway, require auth at the proxy and configure `gateway.trustedProxies`. 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 `.
- Reactions require the BlueBubbles private API (`POST /api/v1/message/react`); ensure the server version exposes it.
diff --git a/docs/channels/discord.md b/docs/channels/discord.md
index e3a73d8f6..2092c0c17 100644
--- a/docs/channels/discord.md
+++ b/docs/channels/discord.md
@@ -3,41 +3,45 @@ summary: "Discord bot support status, capabilities, and configuration"
read_when:
- Working on Discord channel features
---
-# Discord (Bot API)
+# Discord (Bot API)
Status: ready for DM and guild text channels via the official Discord bot gateway.
## Quick setup (beginner)
-1) Create a Discord bot and copy the bot token.
-2) In the Discord app settings, enable **Message Content Intent** (and **Server Members Intent** if you plan to use allowlists or name lookups).
-3) Set the token for OpenClaw:
+
+1. Create a Discord bot and copy the bot token.
+2. In the Discord app settings, enable **Message Content Intent** (and **Server Members Intent** if you plan to use allowlists or name lookups).
+3. Set the token for OpenClaw:
- Env: `DISCORD_BOT_TOKEN=...`
- Or config: `channels.discord.token: "..."`.
- If both are set, config takes precedence (env fallback is default-account only).
-4) Invite the bot to your server with message permissions (create a private server if you just want DMs).
-5) Start the gateway.
-6) DM access is pairing by default; approve the pairing code on first contact.
+4. Invite the bot to your server with message permissions (create a private server if you just want DMs).
+5. Start the gateway.
+6. DM access is pairing by default; approve the pairing code on first contact.
Minimal config:
+
```json5
{
channels: {
discord: {
enabled: true,
- token: "YOUR_BOT_TOKEN"
- }
- }
+ token: "YOUR_BOT_TOKEN",
+ },
+ },
}
```
## Goals
+
- Talk to OpenClaw via Discord DMs or guild channels.
- Direct chats collapse into the agent's main session (default `agent:main:main`); guild channels stay isolated as `agent::discord:channel:` (display names use `discord:#`).
- Group DMs are ignored by default; enable via `channels.discord.dm.groupEnabled` and optionally restrict by `channels.discord.dm.groupChannels`.
- Keep routing deterministic: replies always go back to the channel they arrived on.
## How it works
+
1. Create a Discord application → Bot, enable the intents you need (DMs + guild messages + message content), and grab the bot token.
2. Invite the bot to your server with the permissions required to read/send messages where you want to use it.
3. Configure OpenClaw with `channels.discord.token` (or `DISCORD_BOT_TOKEN` as a fallback).
@@ -64,12 +68,14 @@ Note: Slugs are lowercase with spaces replaced by `-`. Channel names are slugged
Note: Guild context `[from:]` lines include `author.tag` + `id` to make ping-ready replies easy.
## Config writes
+
By default, Discord is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
Disable with:
+
```json5
{
- channels: { discord: { configWrites: false } }
+ channels: { discord: { configWrites: false } },
}
```
@@ -78,28 +84,34 @@ Disable with:
This is the “Discord Developer Portal” setup for running OpenClaw in a server (guild) channel like `#help`.
### 1) Create the Discord app + bot user
+
1. Discord Developer Portal → **Applications** → **New Application**
2. In your app:
- **Bot** → **Add Bot**
- Copy the **Bot Token** (this is what you put in `DISCORD_BOT_TOKEN`)
### 2) Enable the gateway intents OpenClaw needs
+
Discord blocks “privileged intents” unless you explicitly enable them.
In **Bot** → **Privileged Gateway Intents**, enable:
+
- **Message Content Intent** (required to read message text in most guilds; without it you’ll see “Used disallowed intents” or the bot will connect but not react to messages)
- **Server Members Intent** (recommended; required for some member/user lookups and allowlist matching in guilds)
You usually do **not** need **Presence Intent**.
### 3) Generate an invite URL (OAuth2 URL Generator)
+
In your app: **OAuth2** → **URL Generator**
**Scopes**
+
- ✅ `bot`
- ✅ `applications.commands` (required for native commands)
**Bot Permissions** (minimal baseline)
+
- ✅ View Channels
- ✅ Send Messages
- ✅ Read Message History
@@ -113,6 +125,7 @@ Avoid **Administrator** unless you’re debugging and fully trust the bot.
Copy the generated URL, open it, pick your server, and install the bot.
### 4) Get the ids (guild/user/channel)
+
Discord uses numeric ids everywhere; OpenClaw config prefers ids.
1. Discord (desktop/web) → **User Settings** → **Advanced** → enable **Developer Mode**
@@ -124,7 +137,9 @@ Discord uses numeric ids everywhere; OpenClaw config prefers ids.
### 5) Configure OpenClaw
#### Token
+
Set the bot token via env var (recommended on servers):
+
- `DISCORD_BOT_TOKEN=...`
Or via config:
@@ -134,15 +149,16 @@ Or via config:
channels: {
discord: {
enabled: true,
- token: "YOUR_BOT_TOKEN"
- }
- }
+ token: "YOUR_BOT_TOKEN",
+ },
+ },
}
```
Multi-account support: use `channels.discord.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
#### Allowlist + channel routing
+
Example “single server, only allow me, only allow #help”:
```json5
@@ -152,26 +168,27 @@ Example “single server, only allow me, only allow #help”:
enabled: true,
dm: { enabled: false },
guilds: {
- "YOUR_GUILD_ID": {
+ YOUR_GUILD_ID: {
users: ["YOUR_USER_ID"],
requireMention: true,
channels: {
- help: { allow: true, requireMention: true }
- }
- }
+ help: { allow: true, requireMention: true },
+ },
+ },
},
retry: {
attempts: 3,
minDelayMs: 500,
maxDelayMs: 30000,
- jitter: 0.1
- }
- }
- }
+ jitter: 0.1,
+ },
+ },
+ },
}
```
Notes:
+
- `requireMention: true` means the bot only replies when mentioned (recommended for shared channels).
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions for guild messages.
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
@@ -182,11 +199,13 @@ Notes:
- Warning: If you allow replies to other bots (`channels.discord.allowBots=true`), prevent bot-to-bot reply loops with `requireMention`, `channels.discord.guilds.*.channels..users` allowlists, and/or clear guardrails in `AGENTS.md` and `SOUL.md`.
### 6) Verify it works
+
1. Start the gateway.
2. In your server channel, send: `@Krill hello` (or whatever your bot name is).
3. If nothing happens: check **Troubleshooting** below.
### Troubleshooting
+
- First: run `openclaw doctor` and `openclaw channels status --probe` (actionable warnings + quick audits).
- **“Used disallowed intents”**: enable **Message Content Intent** (and likely **Server Members Intent**) in the Developer Portal, then restart the gateway.
- **Bot connects but never replies in a guild channel**:
@@ -204,6 +223,7 @@ Notes:
- **DMs don’t work**: `channels.discord.dm.enabled=false`, `channels.discord.dm.policy="disabled"`, or you haven’t been approved yet (`channels.discord.dm.policy="pairing"`).
## Capabilities & limits
+
- DMs and guild text channels (threads are treated as separate channels; voice not supported).
- Typing indicators sent best-effort; message chunking uses `channels.discord.textChunkLimit` (default 2000) and splits tall replies by line count (`channels.discord.maxLinesPerMessage`, default 17).
- Optional newline chunking: set `channels.discord.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
@@ -213,6 +233,7 @@ Notes:
- Native reply threading is **off by default**; enable with `channels.discord.replyToMode` and reply tags.
## Retry policy
+
Outbound Discord API calls retry on rate limits (429) using Discord `retry_after` when available, with exponential backoff and jitter. Configure via `channels.discord.retry`. See [Retry policy](/concepts/retry).
## Config
@@ -227,9 +248,9 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after
guilds: {
"*": {
channels: {
- general: { allow: true }
- }
- }
+ general: { allow: true },
+ },
+ },
},
mediaMaxMb: 8,
actions: {
@@ -250,7 +271,7 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after
channels: true,
voiceStatus: true,
events: true,
- moderation: false
+ moderation: false,
},
replyToMode: "off",
dm: {
@@ -258,7 +279,7 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after
policy: "pairing", // pairing | allowlist | open | disabled
allowFrom: ["123456789012345678", "steipete"],
groupEnabled: false,
- groupChannels: ["openclaw-dm"]
+ groupChannels: ["openclaw-dm"],
},
guilds: {
"*": { requireMention: true },
@@ -274,13 +295,13 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after
requireMention: true,
users: ["987654321098765432"],
skills: ["search", "docs"],
- systemPrompt: "Keep answers short."
- }
- }
- }
- }
- }
- }
+ systemPrompt: "Keep answers short.",
+ },
+ },
+ },
+ },
+ },
+ },
}
```
@@ -327,6 +348,7 @@ ack reaction after the bot replies.
- `moderation` (timeout/kick/ban, default `false`)
Reaction notifications use `guilds..reactionNotifications`:
+
- `off`: no reaction events.
- `own`: reactions on the bot's own messages (default).
- `all`: all reactions on all messages.
@@ -334,40 +356,45 @@ Reaction notifications use `guilds..reactionNotifications`:
### Tool action defaults
-| Action group | Default | Notes |
-| --- | --- | --- |
-| reactions | enabled | React + list reactions + emojiList |
-| stickers | enabled | Send stickers |
-| emojiUploads | enabled | Upload emojis |
-| stickerUploads | enabled | Upload stickers |
-| polls | enabled | Create polls |
-| permissions | enabled | Channel permission snapshot |
-| messages | enabled | Read/send/edit/delete |
-| threads | enabled | Create/list/reply |
-| pins | enabled | Pin/unpin/list |
-| search | enabled | Message search (preview feature) |
-| memberInfo | enabled | Member info |
-| roleInfo | enabled | Role list |
-| channelInfo | enabled | Channel info + list |
-| channels | enabled | Channel/category management |
-| voiceStatus | enabled | Voice state lookup |
-| events | enabled | List/create scheduled events |
-| roles | disabled | Role add/remove |
-| moderation | disabled | Timeout/kick/ban |
+| Action group | Default | Notes |
+| -------------- | -------- | ---------------------------------- |
+| reactions | enabled | React + list reactions + emojiList |
+| stickers | enabled | Send stickers |
+| emojiUploads | enabled | Upload emojis |
+| stickerUploads | enabled | Upload stickers |
+| polls | enabled | Create polls |
+| permissions | enabled | Channel permission snapshot |
+| messages | enabled | Read/send/edit/delete |
+| threads | enabled | Create/list/reply |
+| pins | enabled | Pin/unpin/list |
+| search | enabled | Message search (preview feature) |
+| memberInfo | enabled | Member info |
+| roleInfo | enabled | Role list |
+| channelInfo | enabled | Channel info + list |
+| channels | enabled | Channel/category management |
+| voiceStatus | enabled | Voice state lookup |
+| events | enabled | List/create scheduled events |
+| roles | disabled | Role add/remove |
+| moderation | disabled | Timeout/kick/ban |
+
- `replyToMode`: `off` (default), `first`, or `all`. Applies only when the model includes a reply tag.
## Reply tags
+
To request a threaded reply, the model can include one tag in its output:
+
- `[[reply_to_current]]` — reply to the triggering Discord message.
- `[[reply_to:]]` — reply to a specific message id from context/history.
-Current message ids are appended to prompts as `[message_id: …]`; history entries already include ids.
+ Current message ids are appended to prompts as `[message_id: …]`; history entries already include ids.
Behavior is controlled by `channels.discord.replyToMode`:
+
- `off`: ignore tags.
- `first`: only the first outbound chunk/attachment is a reply.
- `all`: every outbound chunk/attachment is a reply.
Allowlist matching notes:
+
- `allowFrom`/`users`/`groupChannels` accept ids, names, tags, or mentions like `<@id>`.
- Prefixes like `discord:`/`user:` (users) and `channel:` (group DMs) are supported.
- Use `*` to allow any sender/channel.
@@ -379,12 +406,15 @@ Allowlist matching notes:
and logs the mapping; unresolved entries are kept as typed.
Native command notes:
+
- The registered commands mirror OpenClaw’s chat commands.
- Native commands honor the same allowlists as DMs/guild messages (`channels.discord.dm.allowFrom`, `channels.discord.guilds`, per-channel rules).
- Slash commands may still be visible in Discord UI to users who aren’t allowlisted; OpenClaw enforces allowlists on execution and replies “not authorized”.
## Tool actions
+
The agent can call `discord` with actions like:
+
- `react` / `reactions` (add or list reactions)
- `sticker`, `poll`, `permissions`
- `readMessages`, `sendMessage`, `editMessage`, `deleteMessage`
@@ -399,6 +429,7 @@ Discord message ids are surfaced in the injected context (`[discord message id:
Emoji can be unicode (e.g., `✅`) or custom emoji syntax like `<:party_blob:1234567890>`.
## Safety & ops
+
- Treat the bot token like a password; prefer the `DISCORD_BOT_TOKEN` env var on supervised hosts or lock down the config file permissions.
- Only grant the bot permissions it needs (typically Read/Send Messages).
- If the bot is stuck or rate limited, restart the gateway (`openclaw gateway --force`) after confirming no other processes own the Discord session.
diff --git a/docs/channels/googlechat.md b/docs/channels/googlechat.md
index 3e721503a..4d2698878 100644
--- a/docs/channels/googlechat.md
+++ b/docs/channels/googlechat.md
@@ -3,26 +3,28 @@ summary: "Google Chat app support status, capabilities, and configuration"
read_when:
- Working on Google Chat channel features
---
+
# 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**.
+
+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**:
+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**:
+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):
+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`)
@@ -31,44 +33,51 @@ Status: ready for DMs + spaces via Google Chat API webhooks (HTTP only).
- 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.*
+ - _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**:
+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:
+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.
+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!
+
+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
@@ -78,6 +87,7 @@ Use Tailscale Serve for the private dashboard and Funnel for the public webhook
```
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
@@ -106,16 +116,21 @@ 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)
@@ -133,15 +148,18 @@ Configure your tunnel's ingress rules to only route the webhook path:
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/` or `users/` (email addresses are accepted).
- Spaces: `spaces/`.
## Config highlights
+
```json5
{
channels: {
- "googlechat": {
+ googlechat: {
enabled: true,
serviceAccountFile: "/path/to/service-account.json",
audienceType: "app-url",
@@ -150,7 +168,7 @@ Use these identifiers for delivery and allowlists:
botUser: "users/1234567890", // optional; helps mention detection
dm: {
policy: "pairing",
- allowFrom: ["users/1234567890", "name@example.com"]
+ allowFrom: ["users/1234567890", "name@example.com"],
},
groupPolicy: "allowlist",
groups: {
@@ -158,18 +176,19 @@ Use these identifiers for delivery and allowlists:
allow: true,
requireMention: true,
users: ["users/1234567890"],
- systemPrompt: "Short answers only."
- }
+ systemPrompt: "Short answers only.",
+ },
},
actions: { reactions: true },
typingIndicator: "message",
- mediaMaxMb: 20
- }
- }
+ mediaMaxMb: 20,
+ },
+ },
}
```
Notes:
+
- Service account credentials can also be passed inline with `serviceAccount` (JSON string).
- Default webhook path is `/googlechat` if `webhookPath` isn’t set.
- Reactions are available via the `reactions` tool and `channels action` when `actions.reactions` is enabled.
@@ -179,22 +198,29 @@ Notes:
## 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:
@@ -203,18 +229,21 @@ This means the webhook handler isn't registered. Common causes:
```
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)
diff --git a/docs/channels/grammy.md b/docs/channels/grammy.md
index 89e5beed2..f4648c52b 100644
--- a/docs/channels/grammy.md
+++ b/docs/channels/grammy.md
@@ -3,15 +3,17 @@ summary: "Telegram Bot API integration via grammY with setup notes"
read_when:
- Working on Telegram or grammY pathways
---
+
# grammY Integration (Telegram Bot API)
-
# Why grammY
+
- TS-first Bot API client with built-in long-poll + webhook helpers, middleware, error handling, rate limiter.
- Cleaner media helpers than hand-rolling fetch + FormData; supports all Bot API methods.
- Extensible: proxy support via custom fetch, session middleware (optional), type-safe context.
# What we shipped
+
- **Single client path:** fetch-based implementation removed; grammY is now the sole Telegram client (send + gateway) with the grammY throttler enabled by default.
- **Gateway:** `monitorTelegramProvider` builds a grammY `Bot`, wires mention/allowlist gating, media download via `getFile`/`download`, and delivers replies with `sendMessage/sendPhoto/sendVideo/sendAudio/sendDocument`. Supports long-poll or webhook via `webhookCallback`.
- **Proxy:** optional `channels.telegram.proxy` uses `undici.ProxyAgent` through grammY’s `client.baseFetch`.
@@ -22,6 +24,7 @@ read_when:
- **Tests:** grammy mocks cover DM + group mention gating and outbound send; more media/webhook fixtures still welcome.
Open questions
+
- Optional grammY plugins (throttler) if we hit Bot API 429s.
- Add more structured media tests (stickers, voice notes).
- Make webhook listen port configurable (currently fixed to 8787 unless wired through the gateway).
diff --git a/docs/channels/imessage.md b/docs/channels/imessage.md
index 28814c978..0ef602246 100644
--- a/docs/channels/imessage.md
+++ b/docs/channels/imessage.md
@@ -4,73 +4,82 @@ read_when:
- Setting up iMessage support
- Debugging iMessage send/receive
---
-# iMessage (imsg)
+# iMessage (imsg)
Status: external CLI integration. Gateway spawns `imsg rpc` (JSON-RPC over stdio).
## Quick setup (beginner)
-1) Ensure Messages is signed in on this Mac.
-2) Install `imsg`:
+
+1. Ensure Messages is signed in on this Mac.
+2. Install `imsg`:
- `brew install steipete/tap/imsg`
-3) Configure OpenClaw with `channels.imessage.cliPath` and `channels.imessage.dbPath`.
-4) Start the gateway and approve any macOS prompts (Automation + Full Disk Access).
+3. Configure OpenClaw with `channels.imessage.cliPath` and `channels.imessage.dbPath`.
+4. Start the gateway and approve any macOS prompts (Automation + Full Disk Access).
Minimal config:
+
```json5
{
channels: {
imessage: {
enabled: true,
cliPath: "/usr/local/bin/imsg",
- dbPath: "/Users//Library/Messages/chat.db"
- }
- }
+ dbPath: "/Users//Library/Messages/chat.db",
+ },
+ },
}
```
## What it is
+
- iMessage channel backed by `imsg` on macOS.
- Deterministic routing: replies always go back to iMessage.
- DMs share the agent's main session; groups are isolated (`agent::imessage:group:`).
- If a multi-participant thread arrives with `is_group=false`, you can still isolate it by `chat_id` using `channels.imessage.groups` (see “Group-ish threads” below).
## Config writes
+
By default, iMessage is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
Disable with:
+
```json5
{
- channels: { imessage: { configWrites: false } }
+ channels: { imessage: { configWrites: false } },
}
```
## Requirements
+
- macOS with Messages signed in.
- Full Disk Access for OpenClaw + `imsg` (Messages DB access).
- Automation permission when sending.
- `channels.imessage.cliPath` can point to any command that proxies stdin/stdout (for example, a wrapper script that SSHes to another Mac and runs `imsg rpc`).
## Setup (fast path)
-1) Ensure Messages is signed in on this Mac.
-2) Configure iMessage and start the gateway.
+
+1. Ensure Messages is signed in on this Mac.
+2. Configure iMessage and start the gateway.
### Dedicated bot macOS user (for isolated identity)
+
If you want the bot to send from a **separate iMessage identity** (and keep your personal Messages clean), use a dedicated Apple ID + a dedicated macOS user.
-1) Create a dedicated Apple ID (example: `my-cool-bot@icloud.com`).
+1. Create a dedicated Apple ID (example: `my-cool-bot@icloud.com`).
- Apple may require a phone number for verification / 2FA.
-2) Create a macOS user (example: `openclawhome`) and sign into it.
-3) Open Messages in that macOS user and sign into iMessage using the bot Apple ID.
-4) Enable Remote Login (System Settings → General → Sharing → Remote Login).
-5) Install `imsg`:
+2. Create a macOS user (example: `openclawhome`) and sign into it.
+3. Open Messages in that macOS user and sign into iMessage using the bot Apple ID.
+4. Enable Remote Login (System Settings → General → Sharing → Remote Login).
+5. Install `imsg`:
- `brew install steipete/tap/imsg`
-6) Set up SSH so `ssh @localhost true` works without a password.
-7) Point `channels.imessage.accounts.bot.cliPath` at an SSH wrapper that runs `imsg` as the bot user.
+6. Set up SSH so `ssh @localhost true` works without a password.
+7. Point `channels.imessage.accounts.bot.cliPath` at an SSH wrapper that runs `imsg` as the bot user.
-First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the *bot macOS user*. If `imsg rpc` looks stuck or exits, log into that user (Screen Sharing helps), run a one-time `imsg chats --limit 1` / `imsg send ...`, approve prompts, then retry.
+First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the _bot macOS user_. If `imsg rpc` looks stuck or exits, log into that user (Screen Sharing helps), run a one-time `imsg chats --limit 1` / `imsg send ...`, approve prompts, then retry.
Example wrapper (`chmod +x`). Replace `` with your actual macOS username:
+
```bash
#!/usr/bin/env bash
set -euo pipefail
@@ -82,6 +91,7 @@ exec /usr/bin/ssh -o BatchMode=yes -o ConnectTimeout=5 -T @local
```
Example config:
+
```json5
{
channels: {
@@ -92,20 +102,22 @@ Example config:
name: "Bot",
enabled: true,
cliPath: "/path/to/imsg-bot",
- dbPath: "/Users//Library/Messages/chat.db"
- }
- }
- }
- }
+ dbPath: "/Users//Library/Messages/chat.db",
+ },
+ },
+ },
+ },
}
```
For single-account setups, use flat options (`channels.imessage.cliPath`, `channels.imessage.dbPath`) instead of the `accounts` map.
### Remote/SSH variant (optional)
+
If you want iMessage on another Mac, set `channels.imessage.cliPath` to a wrapper that runs `imsg` on the remote macOS host over SSH. OpenClaw only needs stdio.
Example wrapper:
+
```bash
#!/usr/bin/env bash
exec ssh -T gateway-host imsg "$@"
@@ -117,20 +129,22 @@ exec ssh -T gateway-host imsg "$@"
{
channels: {
imessage: {
- cliPath: "~/imsg-ssh", // SSH wrapper to remote Mac
- remoteHost: "user@gateway-host", // for SCP file transfer
- includeAttachments: true
- }
- }
+ cliPath: "~/imsg-ssh", // SSH wrapper to remote Mac
+ remoteHost: "user@gateway-host", // for SCP file transfer
+ includeAttachments: true,
+ },
+ },
}
```
If `remoteHost` is not set, OpenClaw attempts to auto-detect it by parsing the SSH command in your wrapper script. Explicit configuration is recommended for reliability.
#### Remote Mac via Tailscale (example)
+
If the Gateway runs on a Linux host/VM but iMessage must run on a Mac, Tailscale is the simplest bridge: the Gateway talks to the Mac over the tailnet, runs `imsg` via SSH, and SCPs attachments back.
Architecture:
+
```
┌──────────────────────────────┐ SSH (imsg rpc) ┌──────────────────────────┐
│ Gateway host (Linux/VM) │──────────────────────────────────▶│ Mac with Messages + imsg │
@@ -144,6 +158,7 @@ Architecture:
```
Concrete config example (Tailscale hostname):
+
```json5
{
channels: {
@@ -152,19 +167,21 @@ Concrete config example (Tailscale hostname):
cliPath: "~/.openclaw/scripts/imsg-ssh",
remoteHost: "bot@mac-mini.tailnet-1234.ts.net",
includeAttachments: true,
- dbPath: "/Users/bot/Library/Messages/chat.db"
- }
- }
+ dbPath: "/Users/bot/Library/Messages/chat.db",
+ },
+ },
}
```
Example wrapper (`~/.openclaw/scripts/imsg-ssh`):
+
```bash
#!/usr/bin/env bash
exec ssh -T bot@mac-mini.tailnet-1234.ts.net imsg "$@"
```
Notes:
+
- Ensure the Mac is signed in to Messages, and Remote Login is enabled.
- Use SSH keys so `ssh bot@mac-mini.tailnet-1234.ts.net` works without prompts.
- `remoteHost` should match the SSH target so SCP can fetch attachments.
@@ -172,7 +189,9 @@ Notes:
Multi-account support: use `channels.imessage.accounts` with per-account config and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern. Don't commit `~/.openclaw/openclaw.json` (it often contains tokens).
## Access control (DMs + groups)
+
DMs:
+
- Default: `channels.imessage.dmPolicy = "pairing"`.
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
- Approve via:
@@ -181,23 +200,28 @@ DMs:
- Pairing is the default token exchange for iMessage DMs. Details: [Pairing](/start/pairing)
Groups:
+
- `channels.imessage.groupPolicy = open | allowlist | disabled`.
- `channels.imessage.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
- Mention gating uses `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) because iMessage has no native mention metadata.
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
## How it works (behavior)
+
- `imsg` streams message events; the gateway normalizes them into the shared channel envelope.
- Replies always route back to the same chat id or handle.
## Group-ish threads (`is_group=false`)
+
Some iMessage threads can have multiple participants but still arrive with `is_group=false` depending on how Messages stores the chat identifier.
If you explicitly configure a `chat_id` under `channels.imessage.groups`, OpenClaw treats that thread as a “group” for:
+
- session isolation (separate `agent::imessage:group:` session key)
- group allowlisting / mention gating behavior
Example:
+
```json5
{
channels: {
@@ -205,39 +229,47 @@ Example:
groupPolicy: "allowlist",
groupAllowFrom: ["+15555550123"],
groups: {
- "42": { "requireMention": false }
- }
- }
- }
+ "42": { requireMention: false },
+ },
+ },
+ },
}
```
+
This is useful when you want an isolated personality/model for a specific thread (see [Multi-agent routing](/concepts/multi-agent)). For filesystem isolation, see [Sandboxing](/gateway/sandboxing).
## Media + limits
+
- Optional attachment ingestion via `channels.imessage.includeAttachments`.
- Media cap via `channels.imessage.mediaMaxMb`.
## Limits
+
- Outbound text is chunked to `channels.imessage.textChunkLimit` (default 4000).
- Optional newline chunking: set `channels.imessage.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
- Media uploads are capped by `channels.imessage.mediaMaxMb` (default 16).
## Addressing / delivery targets
+
Prefer `chat_id` for stable routing:
+
- `chat_id:123` (preferred)
- `chat_guid:...`
- `chat_identifier:...`
- direct handles: `imessage:+1555` / `sms:+1555` / `user@example.com`
List chats:
+
```
imsg chats --limit 20
```
## Configuration reference (iMessage)
+
Full configuration: [Configuration](/gateway/configuration)
Provider options:
+
- `channels.imessage.enabled`: enable/disable channel startup.
- `channels.imessage.cliPath`: path to `imsg`.
- `channels.imessage.dbPath`: Messages DB path.
@@ -257,5 +289,6 @@ Provider options:
- `channels.imessage.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
Related global options:
+
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`).
- `messages.responsePrefix`.
diff --git a/docs/channels/index.md b/docs/channels/index.md
index ea1b1bc8a..75f2ace67 100644
--- a/docs/channels/index.md
+++ b/docs/channels/index.md
@@ -4,6 +4,7 @@ read_when:
- You want to choose a chat channel for OpenClaw
- You need a quick overview of supported messaging platforms
---
+
# Chat Channels
OpenClaw can talk to you on any chat app you already use. Each channel connects via the Gateway.
diff --git a/docs/channels/line.md b/docs/channels/line.md
index 7ce70c183..73bff62c1 100644
--- a/docs/channels/line.md
+++ b/docs/channels/line.md
@@ -32,12 +32,12 @@ openclaw plugins install ./extensions/line
## Setup
-1) Create a LINE Developers account and open the Console:
+1. Create a LINE Developers account and open the 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):
+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
@@ -58,9 +58,9 @@ Minimal config:
enabled: true,
channelAccessToken: "LINE_CHANNEL_ACCESS_TOKEN",
channelSecret: "LINE_CHANNEL_SECRET",
- dmPolicy: "pairing"
- }
- }
+ dmPolicy: "pairing",
+ },
+ },
}
```
@@ -76,9 +76,9 @@ Token/secret files:
channels: {
line: {
tokenFile: "/path/to/line-token.txt",
- secretFile: "/path/to/line-secret.txt"
- }
- }
+ secretFile: "/path/to/line-secret.txt",
+ },
+ },
}
```
@@ -92,11 +92,11 @@ Multiple accounts:
marketing: {
channelAccessToken: "...",
channelSecret: "...",
- webhookPath: "/line/marketing"
- }
- }
- }
- }
+ webhookPath: "/line/marketing",
+ },
+ },
+ },
+ },
}
```
@@ -148,11 +148,13 @@ messages.
title: "Office",
address: "123 Main St",
latitude: 35.681236,
- longitude: 139.767125
+ longitude: 139.767125,
},
flexMessage: {
altText: "Status card",
- contents: { /* Flex payload */ }
+ contents: {
+ /* Flex payload */
+ },
},
templateMessage: {
type: "confirm",
@@ -160,10 +162,10 @@ messages.
confirmLabel: "Yes",
confirmData: "yes",
cancelLabel: "No",
- cancelData: "no"
- }
- }
- }
+ cancelData: "no",
+ },
+ },
+ },
}
```
diff --git a/docs/channels/location.md b/docs/channels/location.md
index 14f1c4af9..511e4aa32 100644
--- a/docs/channels/location.md
+++ b/docs/channels/location.md
@@ -8,15 +8,18 @@ read_when:
# 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:
@@ -27,13 +30,16 @@ Locations are rendered as friendly lines without brackets:
- `🛰 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)
@@ -43,6 +49,7 @@ When a location is present, these fields are added to `ctx`:
- `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.
diff --git a/docs/channels/matrix.md b/docs/channels/matrix.md
index 4ad6b9e00..9ceeaa5ad 100644
--- a/docs/channels/matrix.md
+++ b/docs/channels/matrix.md
@@ -3,6 +3,7 @@ summary: "Matrix support status, capabilities, and configuration"
read_when:
- Working on Matrix channel features
---
+
# Matrix (plugin)
Matrix is an open, decentralized messaging protocol. OpenClaw connects as a Matrix **user**
@@ -36,13 +37,13 @@ Details: [Plugins](/plugin)
## Setup
-1) Install the Matrix plugin:
+1. Install the Matrix plugin:
- From npm: `openclaw plugins install @openclaw/matrix`
- From a local checkout: `openclaw plugins install ./extensions/matrix`
-2) Create a Matrix account on a homeserver:
+2. Create a Matrix account on a homeserver:
- Browse hosting options at [https://matrix.org/ecosystem/hosting/](https://matrix.org/ecosystem/hosting/)
- Or host it yourself.
-3) Get an access token for the bot account:
+3. Get an access token for the bot account:
- Use the Matrix login API with `curl` at your home server:
```bash
@@ -63,14 +64,15 @@ Details: [Plugins](/plugin)
- Or set `channels.matrix.userId` + `channels.matrix.password`: OpenClaw calls the same
login endpoint, stores the access token in `~/.openclaw/credentials/matrix/credentials.json`,
and reuses it on next start.
-4) Configure credentials:
+
+4. Configure credentials:
- Env: `MATRIX_HOMESERVER`, `MATRIX_ACCESS_TOKEN` (or `MATRIX_USER_ID` + `MATRIX_PASSWORD`)
- Or config: `channels.matrix.*`
- If both are set, config takes precedence.
- With access token: user ID is fetched automatically via `/whoami`.
- When set, `channels.matrix.userId` should be the full Matrix ID (example: `@bot:example.org`).
-5) Restart the gateway (or finish onboarding).
-6) Start a DM with the bot or invite it to a room from any Matrix client
+5. Restart the gateway (or finish onboarding).
+6. Start a DM with the bot or invite it to a room from any Matrix client
(Element, Beeper, etc.; see https://matrix.org/ecosystem/clients/). Beeper requires E2EE,
so set `channels.matrix.encryption: true` and verify the device.
@@ -83,9 +85,9 @@ Minimal config (access token, user ID auto-fetched):
enabled: true,
homeserver: "https://matrix.example.org",
accessToken: "syt_***",
- dm: { policy: "pairing" }
- }
- }
+ dm: { policy: "pairing" },
+ },
+ },
}
```
@@ -99,9 +101,9 @@ E2EE config (end to end encryption enabled):
homeserver: "https://matrix.example.org",
accessToken: "syt_***",
encryption: true,
- dm: { policy: "pairing" }
- }
- }
+ dm: { policy: "pairing" },
+ },
+ },
}
```
@@ -159,11 +161,11 @@ Once verified, the bot can decrypt messages in encrypted rooms.
groupPolicy: "allowlist",
groups: {
"!roomId:example.org": { allow: true },
- "#alias:example.org": { allow: true }
+ "#alias:example.org": { allow: true },
},
- groupAllowFrom: ["@owner:example.org"]
- }
- }
+ groupAllowFrom: ["@owner:example.org"],
+ },
+ },
}
```
@@ -187,17 +189,17 @@ Once verified, the bot can decrypt messages in encrypted rooms.
## Capabilities
-| Feature | Status |
-|---------|--------|
-| Direct messages | ✅ Supported |
-| Rooms | ✅ Supported |
-| Threads | ✅ Supported |
-| Media | ✅ Supported |
-| E2EE | ✅ Supported (crypto module required) |
-| Reactions | ✅ Supported (send/read via tools) |
-| Polls | ✅ Send supported; inbound poll starts are converted to text (responses/ends ignored) |
-| Location | ✅ Supported (geo URI; altitude ignored) |
-| Native commands | ✅ Supported |
+| Feature | Status |
+| --------------- | ------------------------------------------------------------------------------------- |
+| Direct messages | ✅ Supported |
+| Rooms | ✅ Supported |
+| Threads | ✅ Supported |
+| Media | ✅ Supported |
+| E2EE | ✅ Supported (crypto module required) |
+| Reactions | ✅ Supported (send/read via tools) |
+| Polls | ✅ Send supported; inbound poll starts are converted to text (responses/ends ignored) |
+| Location | ✅ Supported (geo URI; altitude ignored) |
+| Native commands | ✅ Supported |
## Configuration reference (Matrix)
diff --git a/docs/channels/mattermost.md b/docs/channels/mattermost.md
index 6ed4ba5a0..168e2a1e4 100644
--- a/docs/channels/mattermost.md
+++ b/docs/channels/mattermost.md
@@ -12,14 +12,17 @@ Mattermost is a self-hostable team messaging platform; see the official site at
[mattermost.com](https://mattermost.com) for product details and downloads.
## Plugin required
+
Mattermost ships as a plugin and is not bundled with the core install.
Install via CLI (npm registry):
+
```bash
openclaw plugins install @openclaw/mattermost
```
Local checkout (when running from a git repo):
+
```bash
openclaw plugins install ./extensions/mattermost
```
@@ -30,12 +33,14 @@ OpenClaw will offer the local install path automatically.
Details: [Plugins](/plugin)
## Quick setup
-1) Install the Mattermost plugin.
-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.
+
+1. Install the Mattermost plugin.
+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: {
@@ -43,13 +48,14 @@ Minimal config:
enabled: true,
botToken: "mm-token",
baseUrl: "https://chat.example.com",
- dmPolicy: "pairing"
- }
- }
+ dmPolicy: "pairing",
+ },
+ },
}
```
## Environment variables (default account)
+
Set these on the gateway host if you prefer env vars:
- `MATTERMOST_BOT_TOKEN=...`
@@ -58,6 +64,7 @@ Set these on the gateway host if you prefer env vars:
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.
@@ -65,22 +72,25 @@ Mattermost responds to DMs automatically. Channel behavior is controlled by `cha
- `onchar`: respond when a message starts with a trigger prefix.
Config example:
+
```json5
{
channels: {
mattermost: {
chatmode: "onchar",
- oncharPrefixes: [">", "!"]
- }
- }
+ oncharPrefixes: [">", "!"],
+ },
+ },
}
```
Notes:
+
- `onchar` still responds to explicit @mentions.
- `channels.mattermost.requireMention` is honored for legacy configs but `chatmode` is preferred.
## Access control (DMs)
+
- Default: `channels.mattermost.dmPolicy = "pairing"` (unknown senders get a pairing code).
- Approve via:
- `openclaw pairing list mattermost`
@@ -88,11 +98,13 @@ Notes:
- 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 or `@username`).
- Open channels: `channels.mattermost.groupPolicy="open"` (mention-gated).
## Targets for outbound delivery
+
Use these target formats with `openclaw message send` or cron/webhooks:
- `channel:` for a channel
@@ -102,6 +114,7 @@ Use these target formats with `openclaw message send` or cron/webhooks:
Bare IDs are treated as channels.
## Multi-account
+
Mattermost supports multiple accounts under `channels.mattermost.accounts`:
```json5
@@ -110,14 +123,15 @@ Mattermost supports multiple accounts under `channels.mattermost.accounts`:
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" }
- }
- }
- }
+ 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.
diff --git a/docs/channels/msteams.md b/docs/channels/msteams.md
index af8638746..cd0932808 100644
--- a/docs/channels/msteams.md
+++ b/docs/channels/msteams.md
@@ -3,16 +3,17 @@ summary: "Microsoft Teams bot support status, capabilities, and configuration"
read_when:
- Working on MS Teams channel features
---
+
# Microsoft Teams (plugin)
> "Abandon all hope, ye who enter here."
-
Updated: 2026-01-21
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.
## Plugin required
+
Microsoft Teams ships as a plugin and is not bundled with the core install.
**Breaking change (2026.1.15):** MS Teams moved out of core. If you use it, you must install the plugin.
@@ -20,11 +21,13 @@ Microsoft Teams ships as a plugin and is not bundled with the core install.
Explainable: keeps core installs lighter and lets MS Teams dependencies update independently.
Install via CLI (npm registry):
+
```bash
openclaw plugins install @openclaw/msteams
```
Local checkout (when running from a git repo):
+
```bash
openclaw plugins install ./extensions/msteams
```
@@ -35,13 +38,15 @@ OpenClaw will offer the local install path automatically.
Details: [Plugins](/plugin)
## Quick setup (beginner)
-1) Install the Microsoft Teams plugin.
-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.
+
+1. Install the Microsoft Teams plugin.
+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:
+
```json5
{
channels: {
@@ -50,53 +55,61 @@ Minimal config:
appId: "",
appPassword: "",
tenantId: "",
- webhook: { port: 3978, path: "/api/messages" }
- }
- }
+ webhook: { port: 3978, path: "/api/messages" },
+ },
+ },
}
```
+
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 } }
+ channels: { msteams: { configWrites: false } },
}
```
## Access control (DMs + groups)
**DM access**
+
- Default: `channels.msteams.dmPolicy = "pairing"`. Unknown senders are ignored until approved.
- `channels.msteams.allowFrom` accepts AAD object IDs, UPNs, or display names. The wizard resolves 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"]
- }
- }
+ groupAllowFrom: ["user@org.com"],
+ },
+ },
}
```
**Teams + channel allowlist**
+
- Scope group/channel replies by listing teams and channels under `channels.msteams.teams`.
- Keys can be team IDs or names; channel keys can be conversation IDs or names.
- When `groupPolicy="allowlist"` and a teams allowlist is present, only listed teams/channels are accepted (mention‑gated).
@@ -105,6 +118,7 @@ Example:
and logs the mapping; unresolved entries are kept as typed.
Example:
+
```json5
{
channels: {
@@ -113,16 +127,17 @@ Example:
teams: {
"My Team": {
channels: {
- "General": { requireMention: true }
- }
- }
- }
- }
- }
+ General: { requireMention: true },
+ },
+ },
+ },
+ },
+ },
}
```
## How it works
+
1. Install the Microsoft Teams plugin.
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.
@@ -139,14 +154,14 @@ Before configuring OpenClaw, you need to create an Azure Bot resource.
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** |
+ | 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.
@@ -178,6 +193,7 @@ Before configuring OpenClaw, you need to create an Azure Bot resource.
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
@@ -185,6 +201,7 @@ ngrok http 3978
```
**Option B: Tailscale Funnel**
+
```bash
tailscale funnel 3978
# Use your Tailscale funnel URL as the messaging endpoint
@@ -207,16 +224,19 @@ 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. **Install the Microsoft Teams plugin**
- From npm: `openclaw plugins install @openclaw/msteams`
- From a local checkout: `openclaw plugins install ./extensions/msteams`
@@ -236,6 +256,7 @@ This is often easier than hand-editing JSON manifests.
- Zip all three files together: `manifest.json`, `outline.png`, `color.png`.
4. **Configure OpenClaw**
+
```json
{
"msteams": {
@@ -261,14 +282,17 @@ This is often easier than hand-editing JSON manifests.
- The Teams channel starts automatically when the plugin is installed and `msteams` config exists with credentials.
## 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).
- DM history can be limited with `channels.msteams.dmHistoryLimit` (user turns). Per-user overrides: `channels.msteams.dms[""].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)
@@ -278,9 +302,11 @@ These are the **existing resourceSpecific permissions** in our Teams app manifes
- `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.
```json
@@ -330,6 +356,7 @@ Minimal, valid example with the required fields. Replace IDs and URLs.
```
### 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`).
@@ -352,34 +379,40 @@ To update an already-installed Teams app (e.g., to add RSC permissions):
## 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) |
+| 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**:
@@ -392,7 +425,9 @@ If you need images/files in **channels** or want to fetch **message history**, y
## 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
@@ -400,12 +435,15 @@ Teams delivers messages via HTTP webhook. If processing takes too long (e.g., sl
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
+
+- 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.
@@ -430,6 +468,7 @@ Key settings (see `/gateway/configuration` for shared channel patterns):
- `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::`).
- Channel/group messages use conversation id:
@@ -440,12 +479,13 @@ Key settings (see `/gateway/configuration` for shared channel patterns):
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` |
+| 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
@@ -471,6 +511,7 @@ Teams recently introduced two channel UI styles over the same underlying data mo
## 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.
@@ -481,11 +522,11 @@ By default, OpenClaw only downloads media from Microsoft/Teams hostnames. Overri
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 |
+| 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
@@ -500,6 +541,7 @@ Bots don't have a personal OneDrive drive (the `/me/drive` Graph API endpoint do
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" \
@@ -518,35 +560,36 @@ Bots don't have a personal OneDrive drive (the `/me/drive` Graph API endpoint do
channels: {
msteams: {
// ... other config ...
- sharePointSiteId: "contoso.sharepoint.com,guid1,guid2"
- }
- }
+ 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) |
+| 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) |
+| 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: ...`
@@ -555,11 +598,13 @@ OpenClaw sends Teams polls as Adaptive Cards (there is no native Teams poll API)
- 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:**
+
```json
{
"action": "send",
@@ -568,12 +613,13 @@ The `card` parameter accepts an Adaptive Card JSON object. When `card` is provid
"card": {
"type": "AdaptiveCard",
"version": "1.5",
- "body": [{"type": "TextBlock", "text": "Hello!"}]
+ "body": [{ "type": "TextBlock", "text": "Hello!" }]
}
}
```
**CLI:**
+
```bash
openclaw message send --channel msteams \
--target "conversation:19:abc...@thread.tacv2" \
@@ -586,14 +632,15 @@ See [Adaptive Cards documentation](https://adaptivecards.io/) for card schema an
MSTeams targets use prefixes to distinguish between users and conversations:
-| Target type | Format | Example |
-|-------------|--------|---------|
-| User (by ID) | `user:` | `user:40a1a0ed-4ff2-4164-a219-55518990c197` |
-| User (by name) | `user:` | `user:John Smith` (requires Graph API) |
-| Group/channel | `conversation:` | `conversation:19:abc123...@thread.tacv2` |
-| Group/channel (raw) | `` | `19:abc123...@thread.tacv2` (if contains `@thread`) |
+| Target type | Format | Example |
+| ------------------- | -------------------------------- | --------------------------------------------------- |
+| User (by ID) | `user:` | `user:40a1a0ed-4ff2-4164-a219-55518990c197` |
+| User (by name) | `user:` | `user:John Smith` (requires Graph API) |
+| Group/channel | `conversation:` | `conversation:19:abc123...@thread.tacv2` |
+| Group/channel (raw) | `` | `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"
@@ -610,6 +657,7 @@ openclaw message send --channel msteams --target "conversation:19:abc...@thread.
```
**Agent tool examples:**
+
```json
{
"action": "send",
@@ -624,13 +672,18 @@ openclaw message send --channel msteams --target "conversation:19:abc...@thread.
"action": "send",
"channel": "msteams",
"target": "conversation:19:abc...@thread.tacv2",
- "card": {"type": "AdaptiveCard", "version": "1.5", "body": [{"type": "TextBlock", "text": "Hello"}]}
+ "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.
@@ -639,6 +692,7 @@ Note: Without the `user:` prefix, names default to group/team resolution. Always
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=...
└────────────────────────────┘
@@ -646,6 +700,7 @@ https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?gro
```
**Channel URL:**
+
```
https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=...
└─────────────────────────┘
@@ -653,6 +708,7 @@ https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?gr
```
**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
@@ -661,15 +717,16 @@ https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?gr
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) |
+| 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`)
@@ -698,6 +755,7 @@ Bots have limited support in private channels:
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)
diff --git a/docs/channels/nextcloud-talk.md b/docs/channels/nextcloud-talk.md
index 0da464c37..337058613 100644
--- a/docs/channels/nextcloud-talk.md
+++ b/docs/channels/nextcloud-talk.md
@@ -3,19 +3,23 @@ summary: "Nextcloud Talk support status, capabilities, and configuration"
read_when:
- Working on Nextcloud Talk channel features
---
+
# Nextcloud Talk (plugin)
Status: supported via plugin (webhook bot). Direct messages, rooms, reactions, and markdown messages are supported.
## Plugin required
+
Nextcloud Talk ships as a plugin and is not bundled with the core install.
Install via CLI (npm registry):
+
```bash
openclaw plugins install @openclaw/nextcloud-talk
```
Local checkout (when running from a git repo):
+
```bash
openclaw plugins install ./extensions/nextcloud-talk
```
@@ -26,18 +30,20 @@ OpenClaw will offer the local install path automatically.
Details: [Plugins](/plugin)
## Quick setup (beginner)
-1) Install the Nextcloud Talk plugin.
-2) On your Nextcloud server, create a bot:
+
+1. Install the Nextcloud Talk plugin.
+2. On your Nextcloud server, create a bot:
```bash
./occ talk:bot:install "OpenClaw" "" "" --feature reaction
```
-3) Enable the bot in the target room settings.
-4) Configure OpenClaw:
+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 onboarding).
+5. Restart the gateway (or finish onboarding).
Minimal config:
+
```json5
{
channels: {
@@ -45,19 +51,21 @@ Minimal config:
enabled: true,
baseUrl: "https://cloud.example.com",
botSecret: "shared-secret",
- dmPolicy: "pairing"
- }
- }
+ 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`
@@ -65,35 +73,41 @@ Minimal config:
- Public DMs: `channels.nextcloud-talk.dmPolicy="open"` plus `channels.nextcloud-talk.allowFrom=["*"]`.
## 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 }
- }
- }
- }
+ "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 |
+
+| 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.
diff --git a/docs/channels/nostr.md b/docs/channels/nostr.md
index 639c51244..0eafb5df6 100644
--- a/docs/channels/nostr.md
+++ b/docs/channels/nostr.md
@@ -4,6 +4,7 @@ read_when:
- You want OpenClaw to receive DMs via Nostr
- You're setting up decentralized messaging
---
+
# Nostr
**Status:** Optional plugin (disabled by default).
@@ -40,14 +41,14 @@ Restart the Gateway after installing or enabling plugins.
## Quick setup
-1) Generate a Nostr keypair (if needed):
+1. Generate a Nostr keypair (if needed):
```bash
# Using nak
nak key generate
```
-2) Add to config:
+2. Add to config:
```json
{
@@ -59,25 +60,25 @@ nak key generate
}
```
-3) Export the key:
+3. Export the key:
```bash
export NOSTR_PRIVATE_KEY="nsec1..."
```
-4) Restart the Gateway.
+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 |
+| 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
@@ -149,11 +150,7 @@ Defaults: `relay.damus.io` and `nos.lol`.
"channels": {
"nostr": {
"privateKey": "${NOSTR_PRIVATE_KEY}",
- "relays": [
- "wss://relay.damus.io",
- "wss://relay.primal.net",
- "wss://nostr.wine"
- ]
+ "relays": ["wss://relay.damus.io", "wss://relay.primal.net", "wss://nostr.wine"]
}
}
}
@@ -168,12 +165,12 @@ Tips:
## Protocol support
-| NIP | Status | Description |
-| --- | --- | --- |
+| 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 |
+| NIP-04 | Supported | Encrypted DMs (`kind:4`) |
+| NIP-17 | Planned | Gift-wrapped DMs |
+| NIP-44 | Planned | Versioned encryption |
## Testing
@@ -197,10 +194,10 @@ docker run -p 7777:7777 ghcr.io/hoytech/strfry
### 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.
+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
diff --git a/docs/channels/signal.md b/docs/channels/signal.md
index 5f6c33de5..10a693332 100644
--- a/docs/channels/signal.md
+++ b/docs/channels/signal.md
@@ -4,19 +4,21 @@ read_when:
- Setting up Signal support
- Debugging Signal send/receive
---
-# Signal (signal-cli)
+# Signal (signal-cli)
Status: external CLI integration. Gateway talks to `signal-cli` over HTTP JSON-RPC + SSE.
## Quick setup (beginner)
-1) Use a **separate Signal number** for the bot (recommended).
-2) Install `signal-cli` (Java required).
-3) Link the bot device and start the daemon:
+
+1. Use a **separate Signal number** for the bot (recommended).
+2. Install `signal-cli` (Java required).
+3. Link the bot device and start the daemon:
- `signal-cli link -n "OpenClaw"`
-4) Configure OpenClaw and start the gateway.
+4. Configure OpenClaw and start the gateway.
Minimal config:
+
```json5
{
channels: {
@@ -25,39 +27,45 @@ Minimal config:
account: "+15551234567",
cliPath: "signal-cli",
dmPolicy: "pairing",
- allowFrom: ["+15557654321"]
- }
- }
+ allowFrom: ["+15557654321"],
+ },
+ },
}
```
## 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::signal:group:`).
## 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 } }
+ 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 (fast path)
-1) Install `signal-cli` (Java required).
-2) Link a bot account:
+
+1. Install `signal-cli` (Java required).
+2. Link a bot account:
- `signal-cli link -n "OpenClaw"` then scan the QR in Signal.
-3) Configure Signal and start the gateway.
+3. Configure Signal and start the gateway.
Example:
+
```json5
{
channels: {
@@ -66,15 +74,16 @@ Example:
account: "+15551234567",
cliPath: "signal-cli",
dmPolicy: "pairing",
- allowFrom: ["+15557654321"]
- }
- }
+ allowFrom: ["+15557654321"],
+ },
+ },
}
```
Multi-account support: use `channels.signal.accounts` with per-account config and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
## 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
@@ -82,16 +91,18 @@ If you want to manage `signal-cli` yourself (slow JVM cold starts, container ini
channels: {
signal: {
httpUrl: "http://127.0.0.1:8080",
- autoStart: false
- }
- }
+ 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:
@@ -101,15 +112,18 @@ DMs:
- UUID-only senders (from `sourceUuid`) are stored as `uuid:` in `channels.signal.allowFrom`.
Groups:
+
- `channels.signal.groupPolicy = open | allowlist | disabled`.
- `channels.signal.groupAllowFrom` controls who can trigger in groups when `allowlist` 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`).
@@ -118,17 +132,20 @@ Groups:
- 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:` 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
@@ -136,6 +153,7 @@ message action=react channel=signal target=signal:group: targetAuthor=u
```
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).
@@ -143,15 +161,18 @@ Config:
- Per-account overrides: `channels.signal.accounts..actions.reactions`, `channels.signal.accounts..reactionLevel`.
## Delivery targets (CLI/cron)
+
- DMs: `signal:+15551234567` (or plain E.164).
- UUID DMs: `uuid:` (or bare UUID).
- Groups: `signal:group:`.
- Usernames: `username:` (if supported by your Signal account).
## 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`.
@@ -174,6 +195,7 @@ Provider options:
- `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`.
diff --git a/docs/channels/slack.md b/docs/channels/slack.md
index cc11d9395..bffc476bb 100644
--- a/docs/channels/slack.md
+++ b/docs/channels/slack.md
@@ -8,38 +8,41 @@ read_when: "Setting up Slack or debugging Slack socket/HTTP mode"
## Socket mode (default)
### Quick setup (beginner)
-1) Create a Slack app and enable **Socket Mode**.
-2) Create an **App Token** (`xapp-...`) and **Bot Token** (`xoxb-...`).
-3) Set tokens for OpenClaw and start the gateway.
+
+1. Create a Slack app and enable **Socket Mode**.
+2. Create an **App Token** (`xapp-...`) and **Bot Token** (`xoxb-...`).
+3. Set tokens for OpenClaw and start the gateway.
Minimal config:
+
```json5
{
channels: {
slack: {
enabled: true,
appToken: "xapp-...",
- botToken: "xoxb-..."
- }
- }
+ botToken: "xoxb-...",
+ },
+ },
}
```
### Setup
-1) Create a Slack app (From scratch) in https://api.slack.com/apps.
-2) **Socket Mode** → toggle on. Then go to **Basic Information** → **App-Level Tokens** → **Generate Token and Scopes** with scope `connections:write`. Copy the **App Token** (`xapp-...`).
-3) **OAuth & Permissions** → add bot token scopes (use the manifest below). Click **Install to Workspace**. Copy the **Bot User OAuth Token** (`xoxb-...`).
-4) Optional: **OAuth & Permissions** → add **User Token Scopes** (see the read-only list below). Reinstall the app and copy the **User OAuth Token** (`xoxp-...`).
-5) **Event Subscriptions** → enable events and subscribe to:
+
+1. Create a Slack app (From scratch) in https://api.slack.com/apps.
+2. **Socket Mode** → toggle on. Then go to **Basic Information** → **App-Level Tokens** → **Generate Token and Scopes** with scope `connections:write`. Copy the **App Token** (`xapp-...`).
+3. **OAuth & Permissions** → add bot token scopes (use the manifest below). Click **Install to Workspace**. Copy the **Bot User OAuth Token** (`xoxb-...`).
+4. Optional: **OAuth & Permissions** → add **User Token Scopes** (see the read-only list below). Reinstall the app and copy the **User OAuth Token** (`xoxp-...`).
+5. **Event Subscriptions** → enable events and subscribe to:
- `message.*` (includes edits/deletes/thread broadcasts)
- `app_mention`
- `reaction_added`, `reaction_removed`
- `member_joined_channel`, `member_left_channel`
- `channel_rename`
- `pin_added`, `pin_removed`
-6) Invite the bot to channels you want it to read.
-7) Slash Commands → create `/openclaw` if you use `channels.slack.slashCommand`. If you enable native commands, add one slash command per built-in command (same names as `/help`). Native defaults to off for Slack unless you set `channels.slack.commands.native: true` (global `commands.native` is `"auto"` which leaves Slack off).
-8) App Home → enable the **Messages Tab** so users can DM the bot.
+6. Invite the bot to channels you want it to read.
+7. Slash Commands → create `/openclaw` if you use `channels.slack.slashCommand`. If you enable native commands, add one slash command per built-in command (same names as `/help`). Native defaults to off for Slack unless you set `channels.slack.commands.native: true` (global `commands.native` is `"auto"` which leaves Slack off).
+8. App Home → enable the **Messages Tab** so users can DM the bot.
Use the manifest below so scopes and events stay in sync.
@@ -48,6 +51,7 @@ Multi-account support: use `channels.slack.accounts` with per-account tokens and
### OpenClaw config (minimal)
Set tokens via env vars (recommended):
+
- `SLACK_APP_TOKEN=xapp-...`
- `SLACK_BOT_TOKEN=xoxb-...`
@@ -59,13 +63,14 @@ Or via config:
slack: {
enabled: true,
appToken: "xapp-...",
- botToken: "xoxb-..."
- }
- }
+ botToken: "xoxb-...",
+ },
+ },
}
```
### User token (optional)
+
OpenClaw can use a Slack user token (`xoxp-...`) for read operations (history,
pins, reactions, emoji, member info). By default this stays read-only: reads
prefer the user token when present, and writes still use the bot token unless
@@ -76,20 +81,7 @@ User tokens are configured in the config file (no env var support). For
multi-account, set `channels.slack.accounts..userToken`.
Example with bot + app + user tokens:
-```json5
-{
- channels: {
- slack: {
- enabled: true,
- appToken: "xapp-...",
- botToken: "xoxb-...",
- userToken: "xoxp-..."
- }
- }
-}
-```
-Example with userTokenReadOnly explicitly set (allow user token writes):
```json5
{
channels: {
@@ -98,13 +90,29 @@ Example with userTokenReadOnly explicitly set (allow user token writes):
appToken: "xapp-...",
botToken: "xoxb-...",
userToken: "xoxp-...",
- userTokenReadOnly: false
- }
- }
+ },
+ },
+}
+```
+
+Example with userTokenReadOnly explicitly set (allow user token writes):
+
+```json5
+{
+ channels: {
+ slack: {
+ enabled: true,
+ appToken: "xapp-...",
+ botToken: "xoxb-...",
+ userToken: "xoxp-...",
+ userTokenReadOnly: false,
+ },
+ },
}
```
#### Token usage
+
- Read operations (history, reactions list, pins list, emoji list, member info,
search) prefer the user token when configured, otherwise the bot token.
- Write operations (send/edit/delete messages, add/remove reactions, pin/unpin,
@@ -112,25 +120,29 @@ Example with userTokenReadOnly explicitly set (allow user token writes):
no bot token is available, OpenClaw falls back to the user token.
### History context
+
- `channels.slack.historyLimit` (or `channels.slack.accounts.*.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).
## HTTP mode (Events API)
+
Use HTTP webhook mode when your Gateway is reachable by Slack over HTTPS (typical for server deployments).
HTTP mode uses the Events API + Interactivity + Slash Commands with a shared request URL.
### Setup
-1) Create a Slack app and **disable Socket Mode** (optional if you only use HTTP).
-2) **Basic Information** → copy the **Signing Secret**.
-3) **OAuth & Permissions** → install the app and copy the **Bot User OAuth Token** (`xoxb-...`).
-4) **Event Subscriptions** → enable events and set the **Request URL** to your gateway webhook path (default `/slack/events`).
-5) **Interactivity & Shortcuts** → enable and set the same **Request URL**.
-6) **Slash Commands** → set the same **Request URL** for your command(s).
+
+1. Create a Slack app and **disable Socket Mode** (optional if you only use HTTP).
+2. **Basic Information** → copy the **Signing Secret**.
+3. **OAuth & Permissions** → install the app and copy the **Bot User OAuth Token** (`xoxb-...`).
+4. **Event Subscriptions** → enable events and set the **Request URL** to your gateway webhook path (default `/slack/events`).
+5. **Interactivity & Shortcuts** → enable and set the same **Request URL**.
+6. **Slash Commands** → set the same **Request URL** for your command(s).
Example request URL:
`https://gateway-host/slack/events`
### OpenClaw config (minimal)
+
```json5
{
channels: {
@@ -139,9 +151,9 @@ Example request URL:
mode: "http",
botToken: "xoxb-...",
signingSecret: "your-signing-secret",
- webhookPath: "/slack/events"
- }
- }
+ webhookPath: "/slack/events",
+ },
+ },
}
```
@@ -149,6 +161,7 @@ Multi-account HTTP mode: set `channels.slack.accounts..mode = "http"` and pr
`webhookPath` per account so each Slack app can point to its own URL.
### Manifest (optional)
+
Use this Slack app manifest to create the app quickly (adjust the name/command if you want). Include the
user scopes if you plan to configure a user token.
@@ -243,11 +256,13 @@ user scopes if you plan to configure a user token.
If you enable native commands, add one `slash_commands` entry per command you want to expose (matching the `/help` list). Override with `channels.slack.commands.native`.
## Scopes (current vs optional)
+
Slack's Conversations API is type-scoped: you only need the scopes for the
conversation types you actually touch (channels, groups, im, mpim). See
https://docs.slack.dev/apis/web-api/using-the-conversations-api/ for the overview.
### Bot token scopes (required)
+
- `chat:write` (send/update/delete messages via `chat.postMessage`)
https://docs.slack.dev/reference/methods/chat.postMessage
- `im:write` (open DMs via `conversations.open` for user DMs)
@@ -270,6 +285,7 @@ https://docs.slack.dev/apis/web-api/using-the-conversations-api/ for the overvie
https://docs.slack.dev/messaging/working-with-files/#upload
### User token scopes (optional, read-only by default)
+
Add these under **User Token Scopes** if you configure `channels.slack.userToken`.
- `channels:history`, `groups:history`, `im:history`, `mpim:history`
@@ -281,6 +297,7 @@ Add these under **User Token Scopes** if you configure `channels.slack.userToken
- `search:read`
### Not needed today (but likely future)
+
- `mpim:write` (only if we add group-DM open/DM start via `conversations.open`)
- `groups:write` (only if we add private-channel management: create/rename/invite/archive)
- `chat:write.public` (only if we want to post to channels the bot isn't in)
@@ -290,6 +307,7 @@ Add these under **User Token Scopes** if you configure `channels.slack.userToken
- `files:read` (only if we start listing/reading file metadata)
## Config
+
Slack uses Socket Mode only (no HTTP webhook server). Provide both tokens:
```json
@@ -340,6 +358,7 @@ Slack uses Socket Mode only (no HTTP webhook server). Provide both tokens:
```
Tokens can also be supplied via env vars:
+
- `SLACK_BOT_TOKEN`
- `SLACK_APP_TOKEN`
@@ -348,94 +367,105 @@ Ack reactions are controlled globally via `messages.ackReaction` +
ack reaction after the bot replies.
## Limits
+
- Outbound text is chunked to `channels.slack.textChunkLimit` (default 4000).
- Optional newline chunking: set `channels.slack.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
- Media uploads are capped by `channels.slack.mediaMaxMb` (default 20).
## Reply threading
+
By default, OpenClaw replies in the main channel. Use `channels.slack.replyToMode` to control automatic threading:
-| Mode | Behavior |
-| --- | --- |
-| `off` | **Default.** Reply in main channel. Only thread if the triggering message was already in a thread. |
+| Mode | Behavior |
+| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `off` | **Default.** Reply in main channel. Only thread if the triggering message was already in a thread. |
| `first` | First reply goes to thread (under the triggering message), subsequent replies go to main channel. Useful for keeping context visible while avoiding thread clutter. |
-| `all` | All replies go to thread. Keeps conversations contained but may reduce visibility. |
+| `all` | All replies go to thread. Keeps conversations contained but may reduce visibility. |
The mode applies to both auto-replies and agent tool calls (`slack sendMessage`).
### Per-chat-type threading
+
You can configure different threading behavior per chat type by setting `channels.slack.replyToModeByChatType`:
```json5
{
channels: {
slack: {
- replyToMode: "off", // default for channels
+ replyToMode: "off", // default for channels
replyToModeByChatType: {
- direct: "all", // DMs always thread
- group: "first" // group DMs/MPIM thread first reply
+ direct: "all", // DMs always thread
+ group: "first", // group DMs/MPIM thread first reply
},
- }
- }
+ },
+ },
}
```
Supported chat types:
+
- `direct`: 1:1 DMs (Slack `im`)
- `group`: group DMs / MPIMs (Slack `mpim`)
- `channel`: standard channels (public/private)
Precedence:
-1) `replyToModeByChatType.`
-2) `replyToMode`
-3) Provider default (`off`)
+
+1. `replyToModeByChatType.`
+2. `replyToMode`
+3. Provider default (`off`)
Legacy `channels.slack.dm.replyToMode` is still accepted as a fallback for `direct` when no chat-type override is set.
Examples:
Thread DMs only:
+
```json5
{
channels: {
slack: {
replyToMode: "off",
- replyToModeByChatType: { direct: "all" }
- }
- }
+ replyToModeByChatType: { direct: "all" },
+ },
+ },
}
```
Thread group DMs but keep channels in the root:
+
```json5
{
channels: {
slack: {
replyToMode: "off",
- replyToModeByChatType: { group: "first" }
- }
- }
+ replyToModeByChatType: { group: "first" },
+ },
+ },
}
```
Make channels thread, keep DMs in the root:
+
```json5
{
channels: {
slack: {
replyToMode: "first",
- replyToModeByChatType: { direct: "off", group: "off" }
- }
- }
+ replyToModeByChatType: { direct: "off", group: "off" },
+ },
+ },
}
```
### Manual threading tags
+
For fine-grained control, use these tags in agent responses:
+
- `[[reply_to_current]]` — reply to the triggering message (start/continue thread).
- `[[reply_to:]]` — reply to a specific message id.
## Sessions + routing
+
- DMs share the `main` session (like WhatsApp/Telegram).
- Channels map to `agent::slack:channel:` sessions.
- Slash commands use `agent::slack:slash:` sessions (prefix configurable via `channels.slack.slashCommand.sessionPrefix`).
@@ -444,24 +474,27 @@ For fine-grained control, use these tags in agent responses:
- Full command list + config: [Slash commands](/tools/slash-commands)
## DM security (pairing)
+
- Default: `channels.slack.dm.policy="pairing"` — unknown DM senders get a pairing code (expires after 1 hour).
- Approve via: `openclaw pairing approve slack `.
- To allow anyone: set `channels.slack.dm.policy="open"` and `channels.slack.dm.allowFrom=["*"]`.
- `channels.slack.dm.allowFrom` accepts user IDs, @handles, or emails (resolved at startup when tokens allow). The wizard accepts usernames and resolves them to ids during setup when tokens allow.
## Group policy
+
- `channels.slack.groupPolicy` controls channel handling (`open|disabled|allowlist`).
- `allowlist` requires channels to be listed in `channels.slack.channels`.
- - If you only set `SLACK_BOT_TOKEN`/`SLACK_APP_TOKEN` and never create a `channels.slack` section,
- the runtime defaults `groupPolicy` to `open`. Add `channels.slack.groupPolicy`,
- `channels.defaults.groupPolicy`, or a channel allowlist to lock it down.
- - The configure wizard accepts `#channel` names and resolves them to IDs when possible
- (public + private); if multiple matches exist, it prefers the active channel.
- - On startup, OpenClaw resolves channel/user names in allowlists to IDs (when tokens allow)
- and logs the mapping; unresolved entries are kept as typed.
- - To allow **no channels**, set `channels.slack.groupPolicy: "disabled"` (or keep an empty allowlist).
+- If you only set `SLACK_BOT_TOKEN`/`SLACK_APP_TOKEN` and never create a `channels.slack` section,
+ the runtime defaults `groupPolicy` to `open`. Add `channels.slack.groupPolicy`,
+ `channels.defaults.groupPolicy`, or a channel allowlist to lock it down.
+- The configure wizard accepts `#channel` names and resolves them to IDs when possible
+ (public + private); if multiple matches exist, it prefers the active channel.
+- On startup, OpenClaw resolves channel/user names in allowlists to IDs (when tokens allow)
+ and logs the mapping; unresolved entries are kept as typed.
+- To allow **no channels**, set `channels.slack.groupPolicy: "disabled"` (or keep an empty allowlist).
Channel options (`channels.slack.channels.` or `channels.slack.channels.`):
+
- `allow`: allow/deny the channel when `groupPolicy="allowlist"`.
- `requireMention`: mention gating for the channel.
- `tools`: optional per-channel tool policy overrides (`allow`/`deny`/`alsoAllow`).
@@ -473,22 +506,26 @@ Channel options (`channels.slack.channels.` or `channels.slack.channels.` for DMs
- `channel:` for channels
## Tool actions
+
Slack tool actions can be gated with `channels.slack.actions.*`:
-| Action group | Default | Notes |
-| --- | --- | --- |
-| reactions | enabled | React + list reactions |
-| messages | enabled | Read/send/edit/delete |
-| pins | enabled | Pin/unpin/list |
-| memberInfo | enabled | Member info |
-| emojiList | enabled | Custom emoji list |
+| Action group | Default | Notes |
+| ------------ | ------- | ---------------------- |
+| reactions | enabled | React + list reactions |
+| messages | enabled | Read/send/edit/delete |
+| pins | enabled | Pin/unpin/list |
+| memberInfo | enabled | Member info |
+| emojiList | enabled | Custom emoji list |
## Security notes
+
- Writes default to the bot token so state-changing actions stay scoped to the
app's bot permissions and identity.
- Setting `userTokenReadOnly: false` allows the user token to be used for write
@@ -500,6 +537,7 @@ Slack tool actions can be gated with `channels.slack.actions.*`:
`files:write`) or those operations will fail.
## Notes
+
- Mention gating is controlled via `channels.slack.channels` (set `requireMention` to `true`); `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions.
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
- Reaction notifications follow `channels.slack.reactionNotifications` (use `reactionAllowlist` with mode `allowlist`).
diff --git a/docs/channels/telegram.md b/docs/channels/telegram.md
index 351988f1c..38e86d6b8 100644
--- a/docs/channels/telegram.md
+++ b/docs/channels/telegram.md
@@ -3,49 +3,56 @@ summary: "Telegram bot support status, capabilities, and configuration"
read_when:
- Working on Telegram features or webhooks
---
-# Telegram (Bot API)
+# Telegram (Bot API)
Status: production-ready for bot DMs + groups via grammY. Long-polling by default; webhook optional.
## Quick setup (beginner)
-1) Create a bot with **@BotFather** and copy the token.
-2) Set the token:
+
+1. Create a bot with **@BotFather** and copy the token.
+2. Set the token:
- Env: `TELEGRAM_BOT_TOKEN=...`
- Or config: `channels.telegram.botToken: "..."`.
- If both are set, config takes precedence (env fallback is default-account only).
-3) Start the gateway.
-4) DM access is pairing by default; approve the pairing code on first contact.
+3. Start the gateway.
+4. DM access is pairing by default; approve the pairing code on first contact.
Minimal config:
+
```json5
{
channels: {
telegram: {
enabled: true,
botToken: "123:abc",
- dmPolicy: "pairing"
- }
- }
+ dmPolicy: "pairing",
+ },
+ },
}
```
## What it is
+
- A Telegram Bot API channel owned by the Gateway.
- Deterministic routing: replies go back to Telegram; the model never chooses channels.
- DMs share the agent's main session; groups stay isolated (`agent::telegram:group:`).
## Setup (fast path)
+
### 1) Create a bot token (BotFather)
-1) Open Telegram and chat with **@BotFather**.
-2) Run `/newbot`, then follow the prompts (name + username ending in `bot`).
-3) Copy the token and store it safely.
+
+1. Open Telegram and chat with **@BotFather**.
+2. Run `/newbot`, then follow the prompts (name + username ending in `bot`).
+3. Copy the token and store it safely.
Optional BotFather settings:
+
- `/setjoingroups` — allow/deny adding the bot to groups.
- `/setprivacy` — control whether the bot sees all group messages.
### 2) Configure the token (env or config)
+
Example:
```json5
@@ -55,9 +62,9 @@ Example:
enabled: true,
botToken: "123:abc",
dmPolicy: "pairing",
- groups: { "*": { requireMention: true } }
- }
- }
+ groups: { "*": { requireMention: true } },
+ },
+ },
}
```
@@ -66,19 +73,22 @@ If both env and config are set, config takes precedence.
Multi-account support: use `channels.telegram.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
-3) Start the gateway. Telegram starts when a token is resolved (config first, env fallback).
-4) DM access defaults to pairing. Approve the code when the bot is first contacted.
-5) For groups: add the bot, decide privacy/admin behavior (below), then set `channels.telegram.groups` to control mention gating + allowlists.
+3. Start the gateway. Telegram starts when a token is resolved (config first, env fallback).
+4. DM access defaults to pairing. Approve the code when the bot is first contacted.
+5. For groups: add the bot, decide privacy/admin behavior (below), then set `channels.telegram.groups` to control mention gating + allowlists.
## Token + privacy + permissions (Telegram side)
### Token creation (BotFather)
+
- `/newbot` creates the bot and returns the token (keep it secret).
- If a token leaks, revoke/regenerate it via @BotFather and update your config.
### Group message visibility (Privacy Mode)
+
Telegram bots default to **Privacy Mode**, which limits which group messages they receive.
-If your bot must see *all* group messages, you have two options:
+If your bot must see _all_ group messages, you have two options:
+
- Disable privacy mode with `/setprivacy` **or**
- Add the bot as a group **admin** (admin bots receive all messages).
@@ -86,10 +96,12 @@ If your bot must see *all* group messages, you have two options:
to each group for the change to take effect.
### Group permissions (admin rights)
+
Admin status is set inside the group (Telegram UI). Admin bots always receive all
group messages, so use admin if you need full visibility.
## How it works (behavior)
+
- Inbound messages are normalized into the shared channel envelope with reply context and media placeholders.
- Group replies require a mention by default (native @mention or `agents.list[].groupChat.mentionPatterns` / `messages.groupChat.mentionPatterns`).
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
@@ -98,12 +110,14 @@ group messages, so use admin if you need full visibility.
- Telegram Bot API does not support read receipts; there is no `sendReadReceipts` option.
## Formatting (Telegram HTML)
+
- Outbound Telegram text uses `parse_mode: "HTML"` (Telegram’s supported tag subset).
- Markdown-ish input is rendered into **Telegram-safe HTML** (bold/italic/strike/code/links); block elements are flattened to text with newlines/bullets.
- Raw HTML from models is escaped to avoid Telegram parse errors.
- If Telegram rejects the HTML payload, OpenClaw retries the same message as plain text.
## Commands (native + custom)
+
OpenClaw registers native commands (like `/status`, `/reset`, `/model`) with Telegram’s bot menu on startup.
You can add custom commands to the menu via config:
@@ -113,10 +127,10 @@ You can add custom commands to the menu via config:
telegram: {
customCommands: [
{ command: "backup", description: "Git backup" },
- { command: "generate", description: "Create an image" }
- ]
- }
- }
+ { command: "generate", description: "Create an image" },
+ ],
+ },
+ },
}
```
@@ -128,12 +142,14 @@ You can add custom commands to the menu via config:
More help: [Channel troubleshooting](/channels/troubleshooting).
Notes:
+
- Custom commands are **menu entries only**; OpenClaw does not implement them unless you handle them elsewhere.
- Command names are normalized (leading `/` stripped, lowercased) and must match `a-z`, `0-9`, `_` (1–32 chars).
- Custom commands **cannot override native commands**. Conflicts are ignored and logged.
- If `commands.native` is disabled, only custom commands are registered (or cleared if none).
## Limits
+
- Outbound text is chunked to `channels.telegram.textChunkLimit` (default 4000).
- Optional newline chunking: set `channels.telegram.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
- Media downloads/uploads are capped by `channels.telegram.mediaMaxMb` (default 5).
@@ -152,10 +168,10 @@ By default, the bot only responds to mentions in groups (`@botname` or patterns
channels: {
telegram: {
groups: {
- "-1001234567890": { requireMention: false } // always respond in this group
- }
- }
- }
+ "-1001234567890": { requireMention: false }, // always respond in this group
+ },
+ },
+ },
}
```
@@ -163,34 +179,37 @@ By default, the bot only responds to mentions in groups (`@botname` or patterns
Forum topics inherit their parent group config (allowFrom, requireMention, skills, prompts) unless you add per-topic overrides under `channels.telegram.groups..topics.`.
To allow all groups with always-respond:
+
```json5
{
channels: {
telegram: {
groups: {
- "*": { requireMention: false } // all groups, always respond
- }
- }
- }
+ "*": { requireMention: false }, // all groups, always respond
+ },
+ },
+ },
}
```
To keep mention-only for all groups (default behavior):
+
```json5
{
channels: {
telegram: {
groups: {
- "*": { requireMention: true } // or omit groups entirely
- }
- }
- }
+ "*": { requireMention: true }, // or omit groups entirely
+ },
+ },
+ },
}
```
### Via command (session-level)
Send in the group:
+
- `/activation always` - respond to all messages
- `/activation mention` - require mentions (default)
@@ -205,21 +224,26 @@ Forward any message from the group to `@userinfobot` or `@getidsbot` on Telegram
**Privacy note:** `@userinfobot` is a third-party bot. If you prefer, add the bot to the group, send a message, and use `openclaw logs --follow` to read `chat.id`, or use the Bot API `getUpdates`.
## Config writes
+
By default, Telegram is allowed to write config updates triggered by channel events or `/config set|unset`.
This happens when:
+
- A group is upgraded to a supergroup and Telegram emits `migrate_to_chat_id` (chat ID changes). OpenClaw can migrate `channels.telegram.groups` automatically.
- You run `/config set` or `/config unset` in a Telegram chat (requires `commands.config: true`).
Disable with:
+
```json5
{
- channels: { telegram: { configWrites: false } }
+ channels: { telegram: { configWrites: false } },
}
```
## Topics (forum supergroups)
+
Telegram forum topics include a `message_thread_id` per message. OpenClaw:
+
- Appends `:topic:` to the Telegram group session key so each topic is isolated.
- Sends typing indicators and replies with `message_thread_id` so responses stay in the topic.
- General topic (thread id `1`) is special: message sends omit `message_thread_id` (Telegram rejects it), but typing indicators still include it.
@@ -235,34 +259,36 @@ Telegram supports inline keyboards with callback buttons.
```json5
{
- "channels": {
- "telegram": {
- "capabilities": {
- "inlineButtons": "allowlist"
- }
- }
- }
+ channels: {
+ telegram: {
+ capabilities: {
+ inlineButtons: "allowlist",
+ },
+ },
+ },
}
```
For per-account configuration:
+
```json5
{
- "channels": {
- "telegram": {
- "accounts": {
- "main": {
- "capabilities": {
- "inlineButtons": "allowlist"
- }
- }
- }
- }
- }
+ channels: {
+ telegram: {
+ accounts: {
+ main: {
+ capabilities: {
+ inlineButtons: "allowlist",
+ },
+ },
+ },
+ },
+ },
}
```
Scopes:
+
- `off` — inline buttons disabled
- `dm` — only DMs (group targets blocked)
- `group` — only groups (DM targets blocked)
@@ -278,19 +304,17 @@ Use the message tool with the `buttons` parameter:
```json5
{
- "action": "send",
- "channel": "telegram",
- "to": "123456789",
- "message": "Choose an option:",
- "buttons": [
+ action: "send",
+ channel: "telegram",
+ to: "123456789",
+ message: "Choose an option:",
+ buttons: [
[
- {"text": "Yes", "callback_data": "yes"},
- {"text": "No", "callback_data": "no"}
+ { text: "Yes", callback_data: "yes" },
+ { text: "No", callback_data: "no" },
],
- [
- {"text": "Cancel", "callback_data": "cancel"}
- ]
- ]
+ [{ text: "Cancel", callback_data: "cancel" }],
+ ],
}
```
@@ -305,9 +329,11 @@ Telegram capabilities can be configured at two levels (object form shown above;
- `channels.telegram.accounts..capabilities`: Per-account capabilities that override the global defaults for that specific account.
Use the global setting when all Telegram bots/accounts should behave the same. Use per-account configuration when different bots need different behaviors (for example, one account only handles DMs while another is allowed in groups).
+
## Access control (DMs + groups)
### DM access
+
- Default: `channels.telegram.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
- Approve via:
- `openclaw pairing list telegram`
@@ -316,18 +342,22 @@ Use the global setting when all Telegram bots/accounts should behave the same. U
- `channels.telegram.allowFrom` accepts numeric user IDs (recommended) or `@username` entries. It is **not** the bot username; use the human sender’s ID. The wizard accepts `@username` and resolves it to the numeric ID when possible.
#### Finding your Telegram user ID
+
Safer (no third-party bot):
-1) Start the gateway and DM your bot.
-2) Run `openclaw logs --follow` and look for `from.id`.
+
+1. Start the gateway and DM your bot.
+2. Run `openclaw logs --follow` and look for `from.id`.
Alternate (official Bot API):
-1) DM your bot.
-2) Fetch updates with your bot token and read `message.from.id`:
+
+1. DM your bot.
+2. Fetch updates with your bot token and read `message.from.id`:
```bash
curl "https://api.telegram.org/bot/getUpdates"
```
Third-party (less private):
+
- DM `@userinfobot` or `@getidsbot` and use the returned user id.
### Group access
@@ -335,37 +365,45 @@ Third-party (less private):
Two independent controls:
**1. Which groups are allowed** (group allowlist via `channels.telegram.groups`):
+
- No `groups` config = all groups allowed
- With `groups` config = only listed groups or `"*"` are allowed
- Example: `"groups": { "-1001234567890": {}, "*": {} }` allows all groups
**2. Which senders are allowed** (sender filtering via `channels.telegram.groupPolicy`):
+
- `"open"` = all senders in allowed groups can message
- `"allowlist"` = only senders in `channels.telegram.groupAllowFrom` can message
- `"disabled"` = no group messages accepted at all
-Default is `groupPolicy: "allowlist"` (blocked unless you add `groupAllowFrom`).
+ Default is `groupPolicy: "allowlist"` (blocked unless you add `groupAllowFrom`).
Most users want: `groupPolicy: "allowlist"` + `groupAllowFrom` + specific groups listed in `channels.telegram.groups`
## Long-polling vs webhook
+
- Default: long-polling (no public URL required).
- Webhook mode: set `channels.telegram.webhookUrl` (optionally `channels.telegram.webhookSecret` + `channels.telegram.webhookPath`).
- The local listener binds to `0.0.0.0:8787` and serves `POST /telegram-webhook` by default.
- If your public URL is different, use a reverse proxy and point `channels.telegram.webhookUrl` at the public endpoint.
## Reply threading
+
Telegram supports optional threaded replies via tags:
+
- `[[reply_to_current]]` -- reply to the triggering message.
- `[[reply_to:]]` -- reply to a specific message id.
Controlled by `channels.telegram.replyToMode`:
+
- `first` (default), `all`, `off`.
## Audio messages (voice vs file)
+
Telegram distinguishes **voice notes** (round bubble) from **audio files** (metadata card).
OpenClaw defaults to audio files for backward compatibility.
To force a voice note bubble in agent replies, include this tag anywhere in the reply:
+
- `[[audio_as_voice]]` — send audio as a voice note instead of a file.
The tag is stripped from the delivered text. Other channels ignore this tag.
@@ -375,11 +413,11 @@ For message tool sends, set `asVoice: true` with a voice-compatible audio `media
```json5
{
- "action": "send",
- "channel": "telegram",
- "to": "123456789",
- "media": "https://example.com/voice.ogg",
- "asVoice": true
+ action: "send",
+ channel: "telegram",
+ to: "123456789",
+ media: "https://example.com/voice.ogg",
+ asVoice: true,
}
```
@@ -396,6 +434,7 @@ When a user sends a sticker, OpenClaw handles it based on the sticker type:
- **Video stickers (WEBM):** Skipped (video format not supported for processing).
Template context field available when receiving stickers:
+
- `Sticker` — object with:
- `emoji` — emoji associated with the sticker
- `setName` — name of the sticker set
@@ -416,6 +455,7 @@ Stickers are processed through the AI's vision capabilities to generate descript
**Cache location:** `~/.openclaw/telegram/sticker-cache.json`
**Cache entry format:**
+
```json
{
"fileId": "CAACAgIAAxkBAAI...",
@@ -428,6 +468,7 @@ Stickers are processed through the AI's vision capabilities to generate descript
```
**Benefits:**
+
- Reduces API costs by avoiding repeated vision calls for the same sticker
- Faster response times for cached stickers (no vision processing delay)
- Enables sticker search functionality based on cached descriptions
@@ -443,10 +484,10 @@ The agent can send and search stickers using the `sticker` and `sticker-search`
channels: {
telegram: {
actions: {
- sticker: true
- }
- }
- }
+ sticker: true,
+ },
+ },
+ },
}
```
@@ -454,14 +495,15 @@ The agent can send and search stickers using the `sticker` and `sticker-search`
```json5
{
- "action": "sticker",
- "channel": "telegram",
- "to": "123456789",
- "fileId": "CAACAgIAAxkBAAI..."
+ action: "sticker",
+ channel: "telegram",
+ to: "123456789",
+ fileId: "CAACAgIAAxkBAAI...",
}
```
Parameters:
+
- `fileId` (required) — the Telegram file ID of the sticker. Obtain this from `Sticker.fileId` when receiving a sticker, or from a `sticker-search` result.
- `replyTo` (optional) — message ID to reply to.
- `threadId` (optional) — message thread ID for forum topics.
@@ -472,26 +514,27 @@ The agent can search cached stickers by description, emoji, or set name:
```json5
{
- "action": "sticker-search",
- "channel": "telegram",
- "query": "cat waving",
- "limit": 5
+ action: "sticker-search",
+ channel: "telegram",
+ query: "cat waving",
+ limit: 5,
}
```
Returns matching stickers from the cache:
+
```json5
{
- "ok": true,
- "count": 2,
- "stickers": [
+ ok: true,
+ count: 2,
+ stickers: [
{
- "fileId": "CAACAgIAAxkBAAI...",
- "emoji": "👋",
- "description": "A cartoon cat waving enthusiastically",
- "setName": "CoolCats"
- }
- ]
+ fileId: "CAACAgIAAxkBAAI...",
+ emoji: "👋",
+ description: "A cartoon cat waving enthusiastically",
+ setName: "CoolCats",
+ },
+ ],
}
```
@@ -501,26 +544,29 @@ The search uses fuzzy matching across description text, emoji characters, and se
```json5
{
- "action": "sticker",
- "channel": "telegram",
- "to": "-1001234567890",
- "fileId": "CAACAgIAAxkBAAI...",
- "replyTo": 42,
- "threadId": 123
+ action: "sticker",
+ channel: "telegram",
+ to: "-1001234567890",
+ fileId: "CAACAgIAAxkBAAI...",
+ replyTo: 42,
+ threadId: 123,
}
```
## Streaming (drafts)
+
Telegram can stream **draft bubbles** while the agent is generating a response.
OpenClaw uses Bot API `sendMessageDraft` (not real messages) and then sends the
final reply as a normal message.
Requirements (Telegram Bot API 9.3+):
+
- **Private chats with topics enabled** (forum topic mode for the bot).
- Incoming messages must include `message_thread_id` (private topic thread).
- Streaming is ignored for groups/supergroups/channels.
Config:
+
- `channels.telegram.streamMode: "off" | "partial" | "block"` (default: `partial`)
- `partial`: update the draft bubble with the latest streaming text.
- `block`: update the draft bubble in larger blocks (chunked).
@@ -534,15 +580,18 @@ Block streaming is off by default and requires `channels.telegram.blockStreaming
if you want early Telegram messages instead of draft updates.
Reasoning stream (Telegram only):
+
- `/reasoning stream` streams reasoning into the draft bubble while the reply is
generating, then sends the final answer without reasoning.
- If `channels.telegram.streamMode` is `off`, reasoning stream is disabled.
-More context: [Streaming + chunking](/concepts/streaming).
+ More context: [Streaming + chunking](/concepts/streaming).
## Retry policy
+
Outbound Telegram API calls retry on transient network/429 errors with exponential backoff and jitter. Configure via `channels.telegram.retry`. See [Retry policy](/concepts/retry).
## Agent tool (messages + reactions)
+
- Tool: `telegram` with `sendMessage` action (`to`, `content`, optional `mediaUrl`, `replyToMessageId`, `messageThreadId`).
- Tool: `telegram` with `react` action (`chatId`, `messageId`, `emoji`).
- Tool: `telegram` with `deleteMessage` action (`chatId`, `messageId`).
@@ -562,6 +611,7 @@ Telegram reactions arrive as **separate `message_reaction` events**, not as prop
The agent sees reactions as **system notifications** in the conversation history, not as message metadata.
**Configuration:**
+
- `channels.telegram.reactionNotifications`: Controls which reactions trigger notifications
- `"off"` — ignore all reactions
- `"own"` — notify when users react to bot messages (best-effort; in-memory) (default)
@@ -576,29 +626,33 @@ The agent sees reactions as **system notifications** in the conversation history
**Forum groups:** Reactions in forum groups include `message_thread_id` and use session keys like `agent:main:telegram:group:{chatId}:topic:{threadId}`. This ensures reactions and messages in the same topic stay together.
**Example config:**
+
```json5
{
channels: {
telegram: {
- reactionNotifications: "all", // See all reactions
- reactionLevel: "minimal" // Agent can react sparingly
- }
- }
+ reactionNotifications: "all", // See all reactions
+ reactionLevel: "minimal", // Agent can react sparingly
+ },
+ },
}
```
**Requirements:**
+
- Telegram bots must explicitly request `message_reaction` in `allowed_updates` (configured automatically by OpenClaw)
- For webhook mode, reactions are included in the webhook `allowed_updates`
- For polling mode, reactions are included in the `getUpdates` `allowed_updates`
## Delivery targets (CLI/cron)
+
- Use a chat id (`123456789`) or a username (`@name`) as the target.
- Example: `openclaw message send --channel telegram --target 123456789 --message "hi"`.
## Troubleshooting
**Bot doesn’t respond to non-mention messages in a group:**
+
- If you set `channels.telegram.groups.*.requireMention=false`, Telegram’s Bot API **privacy mode** must be disabled.
- BotFather: `/setprivacy` → **Disable** (then remove + re-add the bot to the group)
- `openclaw channels status` shows a warning when config expects unmentioned group messages.
@@ -606,32 +660,39 @@ The agent sees reactions as **system notifications** in the conversation history
- Quick test: `/activation always` (session-only; use config for persistence)
**Bot not seeing group messages at all:**
+
- If `channels.telegram.groups` is set, the group must be listed or use `"*"`
- Check Privacy Settings in @BotFather → "Group Privacy" should be **OFF**
- Verify bot is actually a member (not just an admin with no read access)
- Check gateway logs: `openclaw logs --follow` (look for "skipping group message")
**Bot responds to mentions but not `/activation always`:**
+
- The `/activation` command updates session state but doesn't persist to config
- For persistent behavior, add group to `channels.telegram.groups` with `requireMention: false`
**Commands like `/status` don't work:**
+
- Make sure your Telegram user ID is authorized (via pairing or `channels.telegram.allowFrom`)
- Commands require authorization even in groups with `groupPolicy: "open"`
**Long-polling aborts immediately on Node 22+ (often with proxies/custom fetch):**
+
- Node 22+ is stricter about `AbortSignal` instances; foreign signals can abort `fetch` calls right away.
- Upgrade to a OpenClaw build that normalizes abort signals, or run the gateway on Node 20 until you can upgrade.
**Bot starts, then silently stops responding (or logs `HttpError: Network request ... failed`):**
+
- Some hosts resolve `api.telegram.org` to IPv6 first. If your server does not have working IPv6 egress, grammY can get stuck on IPv6-only requests.
- Fix by enabling IPv6 egress **or** forcing IPv4 resolution for `api.telegram.org` (for example, add an `/etc/hosts` entry using the IPv4 A record, or prefer IPv4 in your OS DNS stack), then restart the gateway.
- Quick check: `dig +short api.telegram.org A` and `dig +short api.telegram.org AAAA` to confirm what DNS returns.
## Configuration reference (Telegram)
+
Full configuration: [Configuration](/gateway/configuration)
Provider options:
+
- `channels.telegram.enabled`: enable/disable channel startup.
- `channels.telegram.botToken`: bot token (BotFather).
- `channels.telegram.tokenFile`: read token from file path.
@@ -669,6 +730,7 @@ Provider options:
- `channels.telegram.reactionLevel`: `off | ack | minimal | extensive` — control agent's reaction capability (default: `minimal` when not set).
Related global options:
+
- `agents.list[].groupChat.mentionPatterns` (mention gating patterns).
- `messages.groupChat.mentionPatterns` (global fallback).
- `commands.native` (defaults to `"auto"` → on for Telegram/Discord, off for Slack), `commands.text`, `commands.useAccessGroups` (command behavior). Override with `channels.telegram.commands.native`.
diff --git a/docs/channels/tlon.md b/docs/channels/tlon.md
index 54974cbdc..d082f66b3 100644
--- a/docs/channels/tlon.md
+++ b/docs/channels/tlon.md
@@ -3,6 +3,7 @@ summary: "Tlon/Urbit support status, capabilities, and configuration"
read_when:
- Working on Tlon/Urbit channel features
---
+
# Tlon (plugin)
Tlon is a decentralized messenger built on Urbit. OpenClaw connects to your Urbit ship and can
@@ -32,11 +33,11 @@ Details: [Plugins](/plugin)
## Setup
-1) Install the Tlon plugin.
-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.
+1. Install the Tlon plugin.
+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):
@@ -47,9 +48,9 @@ Minimal config (single account):
enabled: true,
ship: "~sampel-palnet",
url: "https://your-ship-host",
- code: "lidlut-tabwed-pillex-ridrup"
- }
- }
+ code: "lidlut-tabwed-pillex-ridrup",
+ },
+ },
}
```
@@ -61,12 +62,9 @@ Auto-discovery is enabled by default. You can also pin channels manually:
{
channels: {
tlon: {
- groupChannels: [
- "chat/~host-ship/general",
- "chat/~host-ship/support"
- ]
- }
- }
+ groupChannels: ["chat/~host-ship/general", "chat/~host-ship/support"],
+ },
+ },
}
```
@@ -76,9 +74,9 @@ Disable auto-discovery:
{
channels: {
tlon: {
- autoDiscoverChannels: false
- }
- }
+ autoDiscoverChannels: false,
+ },
+ },
}
```
@@ -90,9 +88,9 @@ DM allowlist (empty = allow all):
{
channels: {
tlon: {
- dmAllowlist: ["~zod", "~nec"]
- }
- }
+ dmAllowlist: ["~zod", "~nec"],
+ },
+ },
}
```
@@ -107,15 +105,15 @@ Group authorization (restricted by default):
channelRules: {
"chat/~host-ship/general": {
mode: "restricted",
- allowedShips: ["~zod", "~nec"]
+ allowedShips: ["~zod", "~nec"],
},
"chat/~host-ship/announcements": {
- mode: "open"
- }
- }
- }
- }
- }
+ mode: "open",
+ },
+ },
+ },
+ },
+ },
}
```
diff --git a/docs/channels/troubleshooting.md b/docs/channels/troubleshooting.md
index 280557ec8..dc0b3a72e 100644
--- a/docs/channels/troubleshooting.md
+++ b/docs/channels/troubleshooting.md
@@ -4,6 +4,7 @@ read_when:
- A channel connects but messages don’t flow
- Investigating channel misconfiguration (intents, permissions, privacy mode)
---
+
# Channel troubleshooting
Start with:
@@ -16,10 +17,12 @@ openclaw channels status --probe
`channels status --probe` prints warnings when it can detect common channel misconfigurations, and includes small live checks (credentials, some permissions/membership).
## Channels
+
- Discord: [/channels/discord#troubleshooting](/channels/discord#troubleshooting)
- Telegram: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting)
- WhatsApp: [/channels/whatsapp#troubleshooting-quick](/channels/whatsapp#troubleshooting-quick)
## Telegram quick fixes
+
- Logs show `HttpError: Network request for 'sendMessage' failed` or `sendChatAction` → check IPv6 DNS. If `api.telegram.org` resolves to IPv6 first and the host lacks IPv6 egress, force IPv4 or enable IPv6. See [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting).
- Logs show `setMyCommands failed` → check outbound HTTPS and DNS reachability to `api.telegram.org` (common on locked-down VPS or proxies).
diff --git a/docs/channels/twitch.md b/docs/channels/twitch.md
index 571edf814..3bdc06c53 100644
--- a/docs/channels/twitch.md
+++ b/docs/channels/twitch.md
@@ -3,6 +3,7 @@ summary: "Twitch chat bot configuration and setup"
read_when:
- Setting up Twitch chat integration for OpenClaw
---
+
# Twitch (plugin)
Twitch chat support via IRC connection. OpenClaw connects as a Twitch user (bot account) to receive and send messages in channels.
@@ -27,17 +28,17 @@ Details: [Plugins](/plugin)
## Quick setup (beginner)
-1) Create a dedicated Twitch account for the bot (or use an existing account).
-2) Generate credentials: [Twitch Token Generator](https://twitchtokengenerator.com/)
+1. Create a dedicated Twitch account for the bot (or use an existing account).
+2. 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**
-3) Find your Twitch user ID: https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
-4) Configure the token:
+3. Find your Twitch user ID: https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
+4. 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).
-5) Start the gateway.
+5. Start the gateway.
**⚠️ Important:** Add access control (`allowFrom` or `allowedRoles`) to prevent unauthorized users from triggering the bot. `requireMention` defaults to `true`.
@@ -48,13 +49,13 @@ Minimal config:
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/
- }
- }
+ 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/
+ },
+ },
}
```
@@ -70,6 +71,7 @@ Minimal config:
### 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**
@@ -79,11 +81,13 @@ 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: {
@@ -92,9 +96,9 @@ OPENCLAW_TWITCH_ACCESS_TOKEN=oauth:abc123...
username: "openclaw",
accessToken: "oauth:abc123...",
clientId: "xyz789...",
- channel: "vevisk"
- }
- }
+ channel: "vevisk",
+ },
+ },
}
```
@@ -106,10 +110,10 @@ If both env and config are set, config takes precedence.
{
channels: {
twitch: {
- allowFrom: ["123456789"], // (recommended) Your Twitch user ID only
- allowedRoles: ["moderator"] // Or restrict to roles
- }
- }
+ allowFrom: ["123456789"], // (recommended) Your Twitch user ID only
+ allowedRoles: ["moderator"], // Or restrict to roles
+ },
+ },
}
```
@@ -130,9 +134,9 @@ For automatic token refresh, create your own Twitch application at [Twitch Devel
channels: {
twitch: {
clientSecret: "your_client_secret",
- refreshToken: "your_refresh_token"
- }
- }
+ refreshToken: "your_refresh_token",
+ },
+ },
}
```
@@ -153,17 +157,17 @@ Example (one bot account in two channels):
username: "openclaw",
accessToken: "oauth:abc123...",
clientId: "xyz789...",
- channel: "vevisk"
+ channel: "vevisk",
},
channel2: {
username: "openclaw",
accessToken: "oauth:def456...",
clientId: "uvw012...",
- channel: "secondchannel"
- }
- }
- }
- }
+ channel: "secondchannel",
+ },
+ },
+ },
+ },
}
```
@@ -179,11 +183,11 @@ Example (one bot account in two channels):
twitch: {
accounts: {
default: {
- allowedRoles: ["moderator", "vip"]
- }
- }
- }
- }
+ allowedRoles: ["moderator", "vip"],
+ },
+ },
+ },
+ },
}
```
@@ -195,11 +199,11 @@ Example (one bot account in two channels):
twitch: {
accounts: {
default: {
- allowFrom: ["123456789", "987654321"]
- }
- }
- }
- }
+ allowFrom: ["123456789", "987654321"],
+ },
+ },
+ },
+ },
}
```
@@ -214,11 +218,11 @@ Users in `allowFrom` bypass role checks:
accounts: {
default: {
allowFrom: ["123456789"],
- allowedRoles: ["moderator"]
- }
- }
- }
- }
+ allowedRoles: ["moderator"],
+ },
+ },
+ },
+ },
}
```
@@ -232,11 +236,11 @@ By default, `requireMention` is `true`. To disable and respond to all messages:
twitch: {
accounts: {
default: {
- requireMention: false
- }
- }
- }
- }
+ requireMention: false,
+ },
+ },
+ },
+ },
}
```
@@ -258,6 +262,7 @@ openclaw channels status --probe
### 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
@@ -265,18 +270,21 @@ openclaw channels status --probe
### 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)
@@ -291,6 +299,7 @@ If you see "token refresh disabled (no refresh token)":
- `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)
@@ -325,28 +334,29 @@ Full example:
expiresIn: 14400,
obtainmentTimestamp: 1706092800000,
allowFrom: ["123456789", "987654321"],
- allowedRoles: ["moderator"]
- }
- }
- }
- }
+ 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"
- }
+ action: "twitch",
+ params: {
+ message: "Hello Twitch!",
+ to: "#mychannel",
+ },
}
```
diff --git a/docs/channels/whatsapp.md b/docs/channels/whatsapp.md
index cb104fda2..deff5a9e2 100644
--- a/docs/channels/whatsapp.md
+++ b/docs/channels/whatsapp.md
@@ -3,45 +3,51 @@ summary: "WhatsApp (web channel) integration: login, inbox, replies, media, and
read_when:
- Working on WhatsApp/web channel behavior or inbox routing
---
-# WhatsApp (web channel)
+# WhatsApp (web channel)
Status: WhatsApp Web via Baileys only. Gateway owns the session(s).
## Quick setup (beginner)
-1) Use a **separate phone number** if possible (recommended).
-2) Configure WhatsApp in `~/.openclaw/openclaw.json`.
-3) Run `openclaw channels login` to scan the QR code (Linked Devices).
-4) Start the gateway.
+
+1. Use a **separate phone number** if possible (recommended).
+2. Configure WhatsApp in `~/.openclaw/openclaw.json`.
+3. Run `openclaw channels login` to scan the QR code (Linked Devices).
+4. Start the gateway.
Minimal config:
+
```json5
{
channels: {
whatsapp: {
dmPolicy: "allowlist",
- allowFrom: ["+15551234567"]
- }
- }
+ allowFrom: ["+15551234567"],
+ },
+ },
}
```
## Goals
+
- Multiple WhatsApp accounts (multi-account) in one Gateway process.
- Deterministic routing: replies return to WhatsApp, no model routing.
- Model sees enough context to understand quoted replies.
## Config writes
+
By default, WhatsApp is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
Disable with:
+
```json5
{
- channels: { whatsapp: { configWrites: false } }
+ channels: { whatsapp: { configWrites: false } },
}
```
## Architecture (who owns what)
+
- **Gateway** owns the Baileys socket and inbox loop.
- **CLI / macOS app** talk to the gateway; no direct Baileys use.
- **Active listener** is required for outbound sends; otherwise send fails fast.
@@ -51,19 +57,21 @@ Disable with:
WhatsApp requires a real mobile number for verification. VoIP and virtual numbers are usually blocked. There are two supported ways to run OpenClaw on WhatsApp:
### Dedicated number (recommended)
+
Use a **separate phone number** for OpenClaw. Best UX, clean routing, no self-chat quirks. Ideal setup: **spare/old Android phone + eSIM**. Leave it on Wi‑Fi and power, and link it via QR.
**WhatsApp Business:** You can use WhatsApp Business on the same device with a different number. Great for keeping your personal WhatsApp separate — install WhatsApp Business and register the OpenClaw number there.
**Sample config (dedicated number, single-user allowlist):**
+
```json5
{
channels: {
whatsapp: {
dmPolicy: "allowlist",
- allowFrom: ["+15551234567"]
- }
- }
+ allowFrom: ["+15551234567"],
+ },
+ },
}
```
@@ -72,10 +80,12 @@ If you want pairing instead of allowlist, set `channels.whatsapp.dmPolicy` to `p
`openclaw pairing approve whatsapp `
### Personal number (fallback)
+
Quick fallback: run OpenClaw on **your own number**. Message yourself (WhatsApp “Message yourself”) for testing so you don’t spam contacts. Expect to read verification codes on your main phone during setup and experiments. **Must enable self-chat mode.**
When the wizard asks for your personal WhatsApp number, enter the phone you will message from (the owner/sender), not the assistant number.
**Sample config (personal number, self-chat):**
+
```json
{
"whatsapp": {
@@ -91,6 +101,7 @@ if `messages.responsePrefix` is unset. Set it explicitly to customize or disable
the prefix (use `""` to remove it).
### Number sourcing tips
+
- **Local eSIM** from your country's mobile carrier (most reliable)
- Austria: [hot.at](https://www.hot.at)
- UK: [giffgaff](https://www.giffgaff.com) — free SIM, no contract
@@ -101,6 +112,7 @@ the prefix (use `""` to remove it).
**Tip:** The number only needs to receive one verification SMS. After that, WhatsApp Web sessions persist via `creds.json`.
## Why Not Twilio?
+
- Early OpenClaw builds supported Twilio’s WhatsApp Business integration.
- WhatsApp Business numbers are a poor fit for a personal assistant.
- Meta enforces a 24‑hour reply window; if you haven’t responded in the last 24 hours, the business number can’t initiate new messages.
@@ -108,6 +120,7 @@ the prefix (use `""` to remove it).
- Result: unreliable delivery and frequent blocks, so support was removed.
## Login + credentials
+
- Login command: `openclaw channels login` (QR via Linked Devices).
- Multi-account login: `openclaw channels login --account ` (`` = `accountId`).
- Default account (when `--account` is omitted): `default` if present, otherwise the first configured account id (sorted).
@@ -118,6 +131,7 @@ the prefix (use `""` to remove it).
- Logged-out socket => error instructs re-link.
## Inbound flow (DM + group)
+
- WhatsApp events come from `messages.upsert` (Baileys).
- Inbox listeners are detached on shutdown to avoid accumulating event handlers in tests/restarts.
- Status/broadcast chats are ignored.
@@ -128,38 +142,44 @@ the prefix (use `""` to remove it).
- Your linked WhatsApp number is implicitly trusted, so self messages skip `channels.whatsapp.dmPolicy` and `channels.whatsapp.allowFrom` checks.
### Personal-number mode (fallback)
+
If you run OpenClaw on your **personal WhatsApp number**, enable `channels.whatsapp.selfChatMode` (see sample above).
Behavior:
+
- Outbound DMs never trigger pairing replies (prevents spamming contacts).
- Inbound unknown senders still follow `channels.whatsapp.dmPolicy`.
- Self-chat mode (allowFrom includes your number) avoids auto read receipts and ignores mention JIDs.
- Read receipts sent for non-self-chat DMs.
## Read receipts
+
By default, the gateway marks inbound WhatsApp messages as read (blue ticks) once they are accepted.
Disable globally:
+
```json5
{
- channels: { whatsapp: { sendReadReceipts: false } }
+ channels: { whatsapp: { sendReadReceipts: false } },
}
```
Disable per account:
+
```json5
{
channels: {
whatsapp: {
accounts: {
- personal: { sendReadReceipts: false }
- }
- }
- }
+ personal: { sendReadReceipts: false },
+ },
+ },
+ },
}
```
Notes:
+
- Self-chat mode always skips read receipts.
## WhatsApp FAQ: sending messages + pairing
@@ -169,6 +189,7 @@ No. Default DM policy is **pairing**, so unknown senders only get a pairing code
**How does pairing work on WhatsApp?**
Pairing is a DM gate for unknown senders:
+
- First DM from a new sender returns a short code (message is not processed).
- Approve with: `openclaw pairing approve whatsapp ` (list with `openclaw pairing list whatsapp`).
- Codes expire after 1 hour; pending requests are capped at 3 per channel.
@@ -180,6 +201,7 @@ Yes, by routing each sender to a different agent via `bindings` (peer `kind: "dm
The wizard uses it to set your **allowlist/owner** so your own DMs are permitted. It’s not used for auto-sending. If you run on your personal WhatsApp number, use that same number and enable `channels.whatsapp.selfChatMode`.
## Message normalization (what the model sees)
+
- `Body` is the current message body with envelope.
- Quoted reply context is **always appended**:
```
@@ -195,6 +217,7 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
- ``
## Groups
+
- Groups map to `agent::whatsapp:group:` sessions.
- Group policy: `channels.whatsapp.groupPolicy = open|disabled|allowlist` (default `allowlist`).
- Activation modes:
@@ -203,7 +226,7 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
- `/activation mention|always` is owner-only and must be sent as a standalone message.
- Owner = `channels.whatsapp.allowFrom` (or self E.164 if unset).
- **History injection** (pending-only):
- - Recent *unprocessed* messages (default 50) inserted under:
+ - Recent _unprocessed_ messages (default 50) inserted under:
`[Chat messages since your last reply - for context]` (messages already in the session are not re-injected)
- Current message under:
`[Current message - respond to this]`
@@ -211,6 +234,7 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
- Group metadata cached 5 min (subject + participants).
## Reply delivery (threading)
+
- WhatsApp Web sends standard messages (no quoted reply threading in the current gateway).
- Reply tags are ignored on this channel.
@@ -219,6 +243,7 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
WhatsApp can automatically send emoji reactions to incoming messages immediately upon receipt, before the bot generates a reply. This provides instant feedback to users that their message was received.
**Configuration:**
+
```json
{
"whatsapp": {
@@ -232,6 +257,7 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately
```
**Options:**
+
- `emoji` (string): Emoji to use for acknowledgment (e.g., "👀", "✅", "📨"). Empty or omitted = feature disabled.
- `direct` (boolean, default: `true`): Send reactions in direct/DM chats.
- `group` (string, default: `"mentions"`): Group chat behavior:
@@ -240,6 +266,7 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately
- `"never"`: Never react in groups
**Per-account override:**
+
```json
{
"whatsapp": {
@@ -257,6 +284,7 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately
```
**Behavior notes:**
+
- Reactions are sent **immediately** upon message receipt, before typing indicators or bot replies.
- In groups with `requireMention: false` (activation: always), `group: "mentions"` will react to all messages (not just @mentions).
- Fire-and-forget: reaction failures are logged but don't prevent the bot from replying.
@@ -264,18 +292,21 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately
- WhatsApp ignores `messages.ackReaction`; use `channels.whatsapp.ackReaction` instead.
## Agent tool (reactions)
+
- Tool: `whatsapp` with `react` action (`chatJid`, `messageId`, `emoji`, optional `remove`).
- Optional: `participant` (group sender), `fromMe` (reacting to your own message), `accountId` (multi-account).
- Reaction removal semantics: see [/tools/reactions](/tools/reactions).
- Tool gating: `channels.whatsapp.actions.reactions` (default: enabled).
## Limits
+
- Outbound text is chunked to `channels.whatsapp.textChunkLimit` (default 4000).
- Optional newline chunking: set `channels.whatsapp.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
- Inbound media saves are capped by `channels.whatsapp.mediaMaxMb` (default 50 MB).
- Outbound media items are capped by `agents.defaults.mediaMaxMb` (default 5 MB).
## Outbound send (text + media)
+
- Uses active web listener; error if gateway not running.
- Text chunking: 4k max per message (configurable via `channels.whatsapp.textChunkLimit`, optional `channels.whatsapp.chunkMode`).
- Media:
@@ -288,17 +319,21 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately
- Gateway: `send` params include `gifPlayback: true`
## Voice notes (PTT audio)
+
WhatsApp sends audio as **voice notes** (PTT bubble).
+
- Best results: OGG/Opus. OpenClaw rewrites `audio/ogg` to `audio/ogg; codecs=opus`.
- `[[audio_as_voice]]` is ignored for WhatsApp (audio already ships as voice note).
## Media limits + optimization
+
- Default outbound cap: 5 MB (per media item).
- Override: `agents.defaults.mediaMaxMb`.
- Images are auto-optimized to JPEG under cap (resize + quality sweep).
- Oversize media => error; media reply falls back to text warning.
## Heartbeats
+
- **Gateway heartbeat** logs connection health (`web.heartbeatSeconds`, default 60s).
- **Agent heartbeat** can be configured per agent (`agents.list[].heartbeat`) or globally
via `agents.defaults.heartbeat` (fallback when no per-agent entries are set).
@@ -306,12 +341,14 @@ WhatsApp sends audio as **voice notes** (PTT bubble).
- Delivery defaults to the last used channel (or configured target).
## Reconnect behavior
+
- Backoff policy: `web.reconnect`:
- `initialMs`, `maxMs`, `factor`, `jitter`, `maxAttempts`.
- If maxAttempts reached, web monitoring stops (degraded).
- Logged-out => stop and require re-link.
## Config quick map
+
- `channels.whatsapp.dmPolicy` (DM policy: pairing/allowlist/open/disabled).
- `channels.whatsapp.selfChatMode` (same-phone setup; bot uses your personal WhatsApp number).
- `channels.whatsapp.allowFrom` (DM allowlist). WhatsApp uses E.164 phone numbers (no usernames).
@@ -343,6 +380,7 @@ WhatsApp sends audio as **voice notes** (PTT bubble).
- `web.reconnect.*`
## Logs + troubleshooting
+
- Subsystems: `whatsapp/inbound`, `whatsapp/outbound`, `web-heartbeat`, `web-reconnect`.
- Log file: `/tmp/openclaw/openclaw-YYYY-MM-DD.log` (configurable).
- Troubleshooting guide: [Gateway troubleshooting](/gateway/troubleshooting).
@@ -350,13 +388,16 @@ WhatsApp sends audio as **voice notes** (PTT bubble).
## Troubleshooting (quick)
**Not linked / QR login required**
+
- Symptom: `channels status` shows `linked: false` or warns “Not linked”.
- Fix: run `openclaw channels login` on the gateway host and scan the QR (WhatsApp → Settings → Linked Devices).
**Linked but disconnected / reconnect loop**
+
- Symptom: `channels status` shows `running, disconnected` or warns “Linked but disconnected”.
- Fix: `openclaw doctor` (or restart the gateway). If it persists, relink via `channels login` and inspect `openclaw logs --follow`.
**Bun runtime**
+
- Bun is **not recommended**. WhatsApp (Baileys) and Telegram are unreliable on Bun.
Run the gateway with **Node**. (See Getting Started runtime note.)
diff --git a/docs/channels/zalo.md b/docs/channels/zalo.md
index 407af50f0..e4c2f8c4b 100644
--- a/docs/channels/zalo.md
+++ b/docs/channels/zalo.md
@@ -3,43 +3,50 @@ summary: "Zalo bot support status, capabilities, and configuration"
read_when:
- Working on Zalo features or webhooks
---
+
# Zalo (Bot API)
Status: experimental. Direct messages only; groups coming soon per Zalo docs.
## Plugin required
+
Zalo ships as a plugin and is not bundled with the core install.
+
- Install via CLI: `openclaw plugins install @openclaw/zalo`
- Or select **Zalo** during onboarding and confirm the install prompt
- Details: [Plugins](/plugin)
## Quick setup (beginner)
-1) Install the Zalo plugin:
+
+1. Install the Zalo plugin:
- From a source checkout: `openclaw plugins install ./extensions/zalo`
- From npm (if published): `openclaw plugins install @openclaw/zalo`
- Or pick **Zalo** in onboarding and confirm the install prompt
-2) Set the token:
+2. Set the token:
- Env: `ZALO_BOT_TOKEN=...`
- Or config: `channels.zalo.botToken: "..."`.
-3) Restart the gateway (or finish onboarding).
-4) DM access is pairing by default; approve the pairing code on first contact.
+3. Restart the gateway (or finish onboarding).
+4. DM access is pairing by default; approve the pairing code on first contact.
Minimal config:
+
```json5
{
channels: {
zalo: {
enabled: true,
botToken: "12345689:abc-xyz",
- dmPolicy: "pairing"
- }
- }
+ 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.
+
- 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.
@@ -48,11 +55,13 @@ It is a good fit for support or notifications where you want deterministic routi
## Setup (fast path)
### 1) Create a bot token (Zalo Bot Platform)
-1) Go to **https://bot.zaloplatforms.com** and sign in.
-2) Create a new bot and configure its settings.
-3) Copy the bot token (format: `12345689:abc-xyz`).
+
+1. Go to **https://bot.zaloplatforms.com** and sign in.
+2. Create a new bot and configure its settings.
+3. Copy the bot token (format: `12345689:abc-xyz`).
### 2) Configure the token (env or config)
+
Example:
```json5
@@ -61,9 +70,9 @@ Example:
zalo: {
enabled: true,
botToken: "12345689:abc-xyz",
- dmPolicy: "pairing"
- }
- }
+ dmPolicy: "pairing",
+ },
+ },
}
```
@@ -71,15 +80,17 @@ 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.
+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.
@@ -87,6 +98,7 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and
## 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`
@@ -95,6 +107,7 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and
- `channels.zalo.allowFrom` accepts numeric user IDs (no username lookup available).
## 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.
@@ -105,44 +118,51 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and
**Note:** getUpdates (polling) and webhook are mutually exclusive per Zalo API docs.
## Supported message types
+
- **Text messages**: Full support with 2000 character chunking.
- **Image messages**: Download and process inbound images; send images via `sendPhoto`.
- **Stickers**: Logged but not fully processed (no agent response).
- **Unsupported types**: Logged (e.g., messages from protected users).
## Capabilities
-| Feature | Status |
-|---------|--------|
-| Direct messages | ✅ Supported |
-| Groups | ❌ Coming soon (per Zalo docs) |
-| Media (images) | ✅ Supported |
-| Reactions | ❌ Not supported |
-| Threads | ❌ Not supported |
-| Polls | ❌ Not supported |
-| Native commands | ❌ Not supported |
-| Streaming | ⚠️ Blocked (2000 char limit) |
+
+| Feature | Status |
+| --------------- | ------------------------------ |
+| Direct messages | ✅ Supported |
+| Groups | ❌ Coming soon (per Zalo docs) |
+| Media (images) | ✅ Supported |
+| Reactions | ❌ Not supported |
+| 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)
Provider options:
+
- `channels.zalo.enabled`: enable/disable channel startup.
- `channels.zalo.botToken`: bot token from Zalo Bot Platform.
- `channels.zalo.tokenFile`: read token from file path.
@@ -155,6 +175,7 @@ Provider options:
- `channels.zalo.proxy`: proxy URL for API requests.
Multi-account options:
+
- `channels.zalo.accounts..botToken`: per-account token.
- `channels.zalo.accounts..tokenFile`: per-account token file.
- `channels.zalo.accounts..name`: display name.
diff --git a/docs/channels/zalouser.md b/docs/channels/zalouser.md
index c6e0e28b6..b5e91b39e 100644
--- a/docs/channels/zalouser.md
+++ b/docs/channels/zalouser.md
@@ -4,6 +4,7 @@ read_when:
- Setting up Zalo Personal for OpenClaw
- Debugging Zalo Personal login or message flow
---
+
# Zalo Personal (unofficial)
Status: experimental. This integration automates a **personal Zalo account** via `zca-cli`.
@@ -11,47 +12,54 @@ Status: experimental. This integration automates a **personal Zalo account** via
> **Warning:** This is an unofficial integration and may result in account suspension/ban. Use at your own risk.
## Plugin required
+
Zalo Personal ships as a plugin and is not bundled with the core install.
+
- Install via CLI: `openclaw plugins install @openclaw/zalouser`
- Or from a source checkout: `openclaw plugins install ./extensions/zalouser`
- Details: [Plugins](/plugin)
## Prerequisite: zca-cli
+
The Gateway machine must have the `zca` binary available in `PATH`.
- Verify: `zca --version`
- If missing, install zca-cli (see `extensions/zalouser/README.md` or the upstream zca-cli docs).
## Quick setup (beginner)
-1) Install the plugin (see above).
-2) Login (QR, on the Gateway machine):
+
+1. Install the plugin (see above).
+2. Login (QR, on the Gateway machine):
- `openclaw channels login --channel zalouser`
- Scan the QR code in the terminal with the Zalo mobile app.
-3) Enable the channel:
+3. Enable the channel:
```json5
{
channels: {
zalouser: {
enabled: true,
- dmPolicy: "pairing"
- }
- }
+ dmPolicy: "pairing",
+ },
+ },
}
```
-4) Restart the Gateway (or finish onboarding).
-5) DM access defaults to pairing; approve the pairing code on first contact.
+4. Restart the Gateway (or finish onboarding).
+5. DM access defaults to pairing; approve the pairing code on first contact.
## What it is
+
- Uses `zca listen` to receive inbound messages.
- Uses `zca msg ...` to send replies (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
@@ -61,18 +69,22 @@ 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. The wizard resolves names to IDs via `zca friend find` when available.
Approve via:
+
- `openclaw pairing list zalouser`
- `openclaw pairing approve zalouser `
## 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"`
@@ -82,6 +94,7 @@ Approve via:
- On startup, OpenClaw resolves group/user names in allowlists to IDs and logs the mapping; unresolved entries are kept as typed.
Example:
+
```json5
{
channels: {
@@ -89,14 +102,15 @@ Example:
groupPolicy: "allowlist",
groups: {
"123456789": { allow: true },
- "Work Chat": { allow: true }
- }
- }
- }
+ "Work Chat": { allow: true },
+ },
+ },
+ },
}
```
## Multi-account
+
Accounts map to zca profiles. Example:
```json5
@@ -106,18 +120,20 @@ Accounts map to zca profiles. Example:
enabled: true,
defaultAccount: "default",
accounts: {
- work: { enabled: true, profile: "work" }
- }
- }
- }
+ work: { enabled: true, profile: "work" },
+ },
+ },
+ },
}
```
## Troubleshooting
**`zca` not found:**
+
- Install zca-cli and ensure it’s on `PATH` for the Gateway process.
**Login doesn’t stick:**
+
- `openclaw channels status --probe`
- Re-login: `openclaw channels logout --channel zalouser && openclaw channels login --channel zalouser`
diff --git a/docs/cli/acp.md b/docs/cli/acp.md
index 7ae571fc8..5e335d365 100644
--- a/docs/cli/acp.md
+++ b/docs/cli/acp.md
@@ -110,9 +110,12 @@ To target a specific Gateway or agent:
"command": "openclaw",
"args": [
"acp",
- "--url", "wss://gateway-host:18789",
- "--token", "",
- "--session", "agent:design:main"
+ "--url",
+ "wss://gateway-host:18789",
+ "--token",
+ "",
+ "--session",
+ "agent:design:main"
],
"env": {}
}
diff --git a/docs/cli/agent.md b/docs/cli/agent.md
index 682deb204..778516eac 100644
--- a/docs/cli/agent.md
+++ b/docs/cli/agent.md
@@ -10,6 +10,7 @@ Run an agent turn via the Gateway (use `--local` for embedded).
Use `--agent ` to target a configured agent directly.
Related:
+
- Agent send tool: [Agent send](/tools/agent-send)
## Examples
diff --git a/docs/cli/agents.md b/docs/cli/agents.md
index 694871aaf..88a0a8272 100644
--- a/docs/cli/agents.md
+++ b/docs/cli/agents.md
@@ -9,6 +9,7 @@ read_when:
Manage isolated agents (workspaces + auth + routing).
Related:
+
- Multi-agent routing: [Multi-Agent Routing](/concepts/multi-agent)
- Agent workspace: [Agent workspace](/concepts/agent-workspace)
@@ -25,6 +26,7 @@ openclaw agents delete work
## Identity files
Each agent workspace can include an `IDENTITY.md` at the workspace root:
+
- Example path: `~/.openclaw/workspace/IDENTITY.md`
- `set-identity --from-identity` reads from the workspace root (or an explicit `--identity-file`)
@@ -33,6 +35,7 @@ Avatar paths resolve relative to the workspace root.
## Set identity
`set-identity` writes fields into `agents.list[].identity`:
+
- `name`
- `theme`
- `emoji`
@@ -62,10 +65,10 @@ Config sample:
name: "OpenClaw",
theme: "space lobster",
emoji: "🦞",
- avatar: "avatars/openclaw.png"
- }
- }
- ]
- }
+ avatar: "avatars/openclaw.png",
+ },
+ },
+ ],
+ },
}
```
diff --git a/docs/cli/approvals.md b/docs/cli/approvals.md
index 2e3b60015..136072a31 100644
--- a/docs/cli/approvals.md
+++ b/docs/cli/approvals.md
@@ -11,6 +11,7 @@ Manage exec approvals for the **local host**, **gateway host**, or a **node host
By default, commands target the local approvals file on disk. Use `--gateway` to target the gateway, or `--node` to target a specific node.
Related:
+
- Exec approvals: [Exec approvals](/tools/exec-approvals)
- Nodes: [Nodes](/nodes)
diff --git a/docs/cli/browser.md b/docs/cli/browser.md
index a0f35f511..123db7eff 100644
--- a/docs/cli/browser.md
+++ b/docs/cli/browser.md
@@ -11,6 +11,7 @@ read_when:
Manage OpenClaw’s browser control server and run browser actions (tabs, snapshots, screenshots, navigation, clicks, typing).
Related:
+
- Browser tool + API: [Browser tool](/tools/browser)
- Chrome extension relay: [Chrome extension](/tools/chrome-extension)
@@ -34,6 +35,7 @@ openclaw browser --browser-profile openclaw snapshot
## Profiles
Profiles are named browser routing configs. In practice:
+
- `openclaw`: launches/attaches to a dedicated OpenClaw-managed Chrome instance (isolated user data dir).
- `chrome`: controls your existing Chrome tab(s) via the Chrome extension relay.
diff --git a/docs/cli/channels.md b/docs/cli/channels.md
index 26657e0c5..0bb151fce 100644
--- a/docs/cli/channels.md
+++ b/docs/cli/channels.md
@@ -10,6 +10,7 @@ read_when:
Manage chat channel accounts and their runtime status on the Gateway.
Related docs:
+
- Channel guides: [Channels](/channels/index)
- Gateway configuration: [Configuration](/gateway/configuration)
@@ -56,6 +57,7 @@ openclaw channels capabilities --channel discord --target channel:123
```
Notes:
+
- `--channel` is optional; omit it to list every channel (including extensions).
- `--target` accepts `channel:` or a raw numeric channel id and only applies to Discord.
- Probes are provider-specific: Discord intents + optional channel permissions; Slack bot + user scopes; Telegram bot flags + webhook; Signal daemon version; MS Teams app token + Graph roles/scopes (annotated where known). Channels without probes report `Probe: unavailable`.
@@ -71,5 +73,6 @@ openclaw channels resolve --channel matrix "Project Room"
```
Notes:
+
- Use `--kind user|group|auto` to force the target type.
- Resolution prefers active matches when multiple entries share the same name.
diff --git a/docs/cli/configure.md b/docs/cli/configure.md
index 91a2bfb30..d820daf9f 100644
--- a/docs/cli/configure.md
+++ b/docs/cli/configure.md
@@ -15,10 +15,12 @@ Tip: `openclaw config` without a subcommand opens the same wizard. Use
`openclaw config get|set|unset` for non-interactive edits.
Related:
+
- Gateway configuration reference: [Configuration](/gateway/configuration)
- Config CLI: [Config](/cli/config)
Notes:
+
- Choosing where the Gateway runs always updates `gateway.mode`. You can select "Continue" without other sections if that is all you need.
- Channel-oriented services (Slack/Discord/Matrix/Microsoft Teams) prompt for channel/room allowlists during setup. You can enter names or IDs; the wizard resolves names to IDs when possible.
diff --git a/docs/cli/cron.md b/docs/cli/cron.md
index 977339ee9..4cabcebb8 100644
--- a/docs/cli/cron.md
+++ b/docs/cli/cron.md
@@ -10,6 +10,7 @@ read_when:
Manage cron jobs for the Gateway scheduler.
Related:
+
- Cron jobs: [Cron jobs](/automation/cron-jobs)
Tip: run `openclaw cron --help` for the full command surface.
diff --git a/docs/cli/dashboard.md b/docs/cli/dashboard.md
index 03a6d7c74..b63aa8c6f 100644
--- a/docs/cli/dashboard.md
+++ b/docs/cli/dashboard.md
@@ -13,4 +13,3 @@ Open the Control UI using your current auth.
openclaw dashboard
openclaw dashboard --no-open
```
-
diff --git a/docs/cli/directory.md b/docs/cli/directory.md
index 82c52a3a8..8caec8147 100644
--- a/docs/cli/directory.md
+++ b/docs/cli/directory.md
@@ -10,11 +10,13 @@ read_when:
Directory lookups for channels that support it (contacts/peers, groups, and “me”).
## Common flags
+
- `--channel `: channel id/alias (required when multiple channels are configured; auto when only one is configured)
- `--account `: account id (default: channel default)
- `--json`: output JSON
## Notes
+
- `directory` is meant to help you find IDs you can paste into other commands (especially `openclaw message send --target ...`).
- For many channels, results are config-backed (allowlists / configured groups) rather than a live provider directory.
- Default output is `id` (and sometimes `name`) separated by a tab; use `--json` for scripting.
diff --git a/docs/cli/dns.md b/docs/cli/dns.md
index b92aed918..cfe03dad8 100644
--- a/docs/cli/dns.md
+++ b/docs/cli/dns.md
@@ -10,6 +10,7 @@ read_when:
DNS helpers for wide-area discovery (Tailscale + CoreDNS). Currently focused on macOS + Homebrew CoreDNS.
Related:
+
- Gateway discovery: [Discovery](/gateway/discovery)
- Wide-area discovery config: [Configuration](/gateway/configuration)
diff --git a/docs/cli/docs.md b/docs/cli/docs.md
index a7febc00b..db2f4c397 100644
--- a/docs/cli/docs.md
+++ b/docs/cli/docs.md
@@ -12,4 +12,3 @@ Search the live docs index.
openclaw docs browser extension
openclaw docs sandbox allowHostControl
```
-
diff --git a/docs/cli/doctor.md b/docs/cli/doctor.md
index 24e1e8aa0..df75d932c 100644
--- a/docs/cli/doctor.md
+++ b/docs/cli/doctor.md
@@ -10,6 +10,7 @@ read_when:
Health checks + quick fixes for the gateway and channels.
Related:
+
- Troubleshooting: [Troubleshooting](/gateway/troubleshooting)
- Security audit: [Security](/gateway/security)
@@ -22,6 +23,7 @@ openclaw doctor --deep
```
Notes:
+
- Interactive prompts (like keychain/OAuth fixes) only run when stdin is a TTY and `--non-interactive` is **not** set. Headless runs (cron, Telegram, no terminal) will skip prompts.
- `--fix` (alias for `--repair`) writes a backup to `~/.openclaw/openclaw.json.bak` and drops unknown config keys, listing each removal.
diff --git a/docs/cli/gateway.md b/docs/cli/gateway.md
index 84d16113c..8630ef69d 100644
--- a/docs/cli/gateway.md
+++ b/docs/cli/gateway.md
@@ -13,6 +13,7 @@ The Gateway is OpenClaw’s WebSocket server (channels, nodes, sessions, hooks).
Subcommands in this page live under `openclaw gateway …`.
Related docs:
+
- [/gateway/bonjour](/gateway/bonjour)
- [/gateway/discovery](/gateway/discovery)
- [/gateway/configuration](/gateway/configuration)
@@ -32,6 +33,7 @@ openclaw gateway run
```
Notes:
+
- By default, the Gateway refuses to start unless `gateway.mode=local` is set in `~/.openclaw/openclaw.json`. Use `--allow-unconfigured` for ad-hoc/dev runs.
- Binding beyond loopback without auth is blocked (safety guardrail).
- `SIGUSR1` triggers an in-process restart when authorized (enable `commands.restart` or use the gateway tool/config apply/update).
@@ -62,11 +64,13 @@ Notes:
All query commands use WebSocket RPC.
Output modes:
+
- Default: human-readable (colored in TTY).
- `--json`: machine-readable JSON (no styling/spinner).
- `--no-color` (or `NO_COLOR=1`): disable ANSI while keeping human layout.
Shared options (where supported):
+
- `--url `: Gateway WebSocket URL.
- `--token `: Gateway token.
- `--password `: Gateway password.
@@ -89,6 +93,7 @@ openclaw gateway status --json
```
Options:
+
- `--url `: override the probe URL.
- `--token `: token auth for the probe.
- `--password `: password auth for the probe.
@@ -99,6 +104,7 @@ Options:
### `gateway probe`
`gateway probe` is the “debug everything” command. It always probes:
+
- your configured remote gateway (if set), and
- localhost (loopback) **even if remote is configured**.
@@ -120,11 +126,13 @@ openclaw gateway probe --ssh user@gateway-host
```
Options:
+
- `--ssh `: `user@host` or `user@host:port` (port defaults to `22`).
- `--ssh-identity `: identity file.
- `--ssh-auto`: pick the first discovered gateway host as SSH target (LAN/WAB only).
Config (optional, used as defaults):
+
- `gateway.remote.sshTarget`
- `gateway.remote.sshIdentity`
@@ -148,6 +156,7 @@ openclaw gateway uninstall
```
Notes:
+
- `gateway install` supports `--port`, `--runtime`, `--token`, `--force`, `--json`.
- Lifecycle commands accept `--json` for scripting.
@@ -161,6 +170,7 @@ Notes:
Only gateways with Bonjour discovery enabled (default) advertise the beacon.
Wide-Area discovery records include (TXT):
+
- `role` (gateway role hint)
- `transport` (transport hint, e.g. `gateway`)
- `gatewayPort` (WebSocket port, usually `18789`)
@@ -176,6 +186,7 @@ openclaw gateway discover
```
Options:
+
- `--timeout `: per-command timeout (browse/resolve); default `2000`.
- `--json`: machine-readable output (also disables styling/spinner).
diff --git a/docs/cli/health.md b/docs/cli/health.md
index a8f219eee..2e04f2294 100644
--- a/docs/cli/health.md
+++ b/docs/cli/health.md
@@ -15,5 +15,6 @@ openclaw health --verbose
```
Notes:
+
- `--verbose` runs live probes and prints per-account timings when multiple accounts are configured.
- Output includes per-agent session stores when multiple agents are configured.
diff --git a/docs/cli/hooks.md b/docs/cli/hooks.md
index cc0e00f3b..80025c383 100644
--- a/docs/cli/hooks.md
+++ b/docs/cli/hooks.md
@@ -10,6 +10,7 @@ read_when:
Manage agent hooks (event-driven automations for commands like `/new`, `/reset`, and gateway startup).
Related:
+
- Hooks: [Hooks](/hooks)
- Plugin hooks: [Plugins](/plugin#plugin-hooks)
@@ -22,6 +23,7 @@ openclaw hooks list
List all discovered hooks from workspace, managed, and bundled directories.
**Options:**
+
- `--eligible`: Show only eligible hooks (requirements met)
- `--json`: Output as JSON
- `-v, --verbose`: Show detailed information including missing requirements
@@ -63,9 +65,11 @@ openclaw hooks info
Show detailed information about a specific hook.
**Arguments:**
+
- ``: Hook name (e.g., `session-memory`)
**Options:**
+
- `--json`: Output as JSON
**Example:**
@@ -101,6 +105,7 @@ openclaw hooks check
Show summary of hook eligibility status (how many are ready vs. not ready).
**Options:**
+
- `--json`: Output as JSON
**Example output:**
@@ -125,6 +130,7 @@ Enable a specific hook by adding it to your config (`~/.openclaw/config.json`).
can’t be enabled/disabled here. Enable/disable the plugin instead.
**Arguments:**
+
- ``: Hook name (e.g., `session-memory`)
**Example:**
@@ -140,11 +146,13 @@ openclaw hooks enable session-memory
```
**What it does:**
+
- Checks if hook exists and is eligible
- Updates `hooks.internal.entries..enabled = true` in your config
- Saves config to disk
**After enabling:**
+
- Restart the gateway so hooks reload (menu bar app restart on macOS, or restart your gateway process in dev).
## Disable a Hook
@@ -156,6 +164,7 @@ openclaw hooks disable
Disable a specific hook by updating your config.
**Arguments:**
+
- ``: Hook name (e.g., `command-logger`)
**Example:**
@@ -171,6 +180,7 @@ openclaw hooks disable command-logger
```
**After disabling:**
+
- Restart the gateway so hooks reload
## Install Hooks
@@ -182,11 +192,13 @@ openclaw hooks install
Install a hook pack from a local folder/archive or npm.
**What it does:**
+
- Copies the hook pack into `~/.openclaw/hooks/`
- Enables the installed hooks in `hooks.internal.entries.*`
- Records the install under `hooks.internal.installs`
**Options:**
+
- `-l, --link`: Link a local directory instead of copying (adds it to `hooks.internal.load.extraDirs`)
**Supported archives:** `.zip`, `.tgz`, `.tar.gz`, `.tar`
@@ -217,6 +229,7 @@ openclaw hooks update --all
Update installed hook packs (npm installs only).
**Options:**
+
- `--all`: Update all tracked hook packs
- `--dry-run`: Show what would change without writing
diff --git a/docs/cli/index.md b/docs/cli/index.md
index 03770965f..61b10311e 100644
--- a/docs/cli/index.md
+++ b/docs/cli/index.md
@@ -269,6 +269,7 @@ Vector search over `MEMORY.md` + `memory/*.md`:
Chat messages support `/...` commands (text and native). See [/tools/slash-commands](/tools/slash-commands).
Highlights:
+
- `/status` for quick diagnostics.
- `/config` for persisted config changes.
- `/debug` for runtime-only config overrides (memory, not disk; requires `commands.debug: true`).
@@ -276,9 +277,11 @@ Highlights:
## Setup + onboarding
### `setup`
+
Initialize config + workspace.
Options:
+
- `--workspace `: agent workspace path (default `~/.openclaw/workspace`).
- `--wizard`: run the onboarding wizard.
- `--non-interactive`: run wizard without prompts.
@@ -289,9 +292,11 @@ Options:
Wizard auto-runs when any wizard flags are present (`--non-interactive`, `--mode`, `--remote-url`, `--remote-token`).
### `onboard`
+
Interactive wizard to set up gateway, workspace, and skills.
Options:
+
- `--workspace `
- `--reset` (reset config + credentials + sessions + workspace before wizard)
- `--non-interactive`
@@ -332,21 +337,26 @@ Options:
- `--json`
### `configure`
+
Interactive configuration wizard (models, channels, skills, gateway).
### `config`
+
Non-interactive config helpers (get/set/unset). Running `openclaw config` with no
subcommand launches the wizard.
Subcommands:
+
- `config get `: print a config value (dot/bracket path).
- `config set `: set a value (JSON5 or raw string).
- `config unset `: remove a value.
### `doctor`
+
Health checks + quick fixes (config + gateway + legacy services).
Options:
+
- `--no-workspace-suggestions`: disable workspace memory hints.
- `--yes`: accept defaults without prompting (headless).
- `--non-interactive`: skip prompts; apply safe migrations only.
@@ -355,9 +365,11 @@ Options:
## Channel helpers
### `channels`
+
Manage chat channel accounts (WhatsApp/Telegram/Discord/Google Chat/Slack/Mattermost (plugin)/Signal/iMessage/MS Teams).
Subcommands:
+
- `channels list`: show configured channels and auth profiles.
- `channels status`: check gateway reachability and channel health (`--probe` runs extra checks; use `openclaw health` or `openclaw status --deep` for gateway health probes).
- Tip: `channels status` prints warnings with suggested fixes when it can detect common misconfigurations (then points you to `openclaw doctor`).
@@ -368,24 +380,29 @@ Subcommands:
- `channels logout`: log out of a channel session (if supported).
Common options:
+
- `--channel `: `whatsapp|telegram|discord|googlechat|slack|mattermost|signal|imessage|msteams`
- `--account `: channel account id (default `default`)
- `--name