Include full contents of all nested repositories
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
31
openclaw/docs/.i18n/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# OpenClaw docs i18n assets
|
||||
|
||||
This folder stores **generated** and **config** files for documentation translations.
|
||||
|
||||
## Files
|
||||
|
||||
- `glossary.<lang>.json` — preferred term mappings (used in prompt guidance).
|
||||
- `<lang>.tm.jsonl` — translation memory (cache) keyed by workflow + model + text hash.
|
||||
|
||||
## Glossary format
|
||||
|
||||
`glossary.<lang>.json` is an array of entries:
|
||||
|
||||
```json
|
||||
{
|
||||
"source": "troubleshooting",
|
||||
"target": "故障排除",
|
||||
"ignore_case": true,
|
||||
"whole_word": false
|
||||
}
|
||||
```
|
||||
|
||||
Fields:
|
||||
|
||||
- `source`: English (or source) phrase to prefer.
|
||||
- `target`: preferred translation output.
|
||||
|
||||
## Notes
|
||||
|
||||
- Glossary entries are passed to the model as **prompt guidance** (no deterministic rewrites).
|
||||
- The translation memory is updated by `scripts/docs-i18n`.
|
||||
14
openclaw/docs/.i18n/glossary.ja-JP.json
Normal file
@@ -0,0 +1,14 @@
|
||||
[
|
||||
{ "source": "OpenClaw", "target": "OpenClaw" },
|
||||
{ "source": "Gateway", "target": "Gateway" },
|
||||
{ "source": "Pi", "target": "Pi" },
|
||||
{ "source": "Skills", "target": "Skills" },
|
||||
{ "source": "local loopback", "target": "local loopback" },
|
||||
{ "source": "Tailscale", "target": "Tailscale" },
|
||||
{ "source": "Getting Started", "target": "はじめに" },
|
||||
{ "source": "Getting started", "target": "はじめに" },
|
||||
{ "source": "Quick start", "target": "クイックスタート" },
|
||||
{ "source": "Quick Start", "target": "クイックスタート" },
|
||||
{ "source": "Onboarding", "target": "オンボーディング" },
|
||||
{ "source": "wizard", "target": "ウィザード" }
|
||||
]
|
||||
210
openclaw/docs/.i18n/glossary.zh-CN.json
Normal file
@@ -0,0 +1,210 @@
|
||||
[
|
||||
{
|
||||
"source": "OpenClaw",
|
||||
"target": "OpenClaw"
|
||||
},
|
||||
{
|
||||
"source": "Gateway",
|
||||
"target": "Gateway 网关"
|
||||
},
|
||||
{
|
||||
"source": "Pi",
|
||||
"target": "Pi"
|
||||
},
|
||||
{
|
||||
"source": "Skills",
|
||||
"target": "Skills"
|
||||
},
|
||||
{
|
||||
"source": "Skills config",
|
||||
"target": "Skills 配置"
|
||||
},
|
||||
{
|
||||
"source": "Skills Config",
|
||||
"target": "Skills 配置"
|
||||
},
|
||||
{
|
||||
"source": "local loopback",
|
||||
"target": "local loopback"
|
||||
},
|
||||
{
|
||||
"source": "Tailscale",
|
||||
"target": "Tailscale"
|
||||
},
|
||||
{
|
||||
"source": "Getting Started",
|
||||
"target": "入门指南"
|
||||
},
|
||||
{
|
||||
"source": "Getting started",
|
||||
"target": "入门指南"
|
||||
},
|
||||
{
|
||||
"source": "Quick start",
|
||||
"target": "快速开始"
|
||||
},
|
||||
{
|
||||
"source": "Quick Start",
|
||||
"target": "快速开始"
|
||||
},
|
||||
{
|
||||
"source": "Docs directory",
|
||||
"target": "文档目录"
|
||||
},
|
||||
{
|
||||
"source": "Credits",
|
||||
"target": "致谢"
|
||||
},
|
||||
{
|
||||
"source": "Features",
|
||||
"target": "功能"
|
||||
},
|
||||
{
|
||||
"source": "DMs",
|
||||
"target": "私信"
|
||||
},
|
||||
{
|
||||
"source": "DM",
|
||||
"target": "私信"
|
||||
},
|
||||
{
|
||||
"source": "sandbox",
|
||||
"target": "沙箱"
|
||||
},
|
||||
{
|
||||
"source": "Sandbox",
|
||||
"target": "沙箱"
|
||||
},
|
||||
{
|
||||
"source": "sandboxing",
|
||||
"target": "沙箱隔离"
|
||||
},
|
||||
{
|
||||
"source": "Sandboxing",
|
||||
"target": "沙箱隔离"
|
||||
},
|
||||
{
|
||||
"source": "sandboxed",
|
||||
"target": "沙箱隔离"
|
||||
},
|
||||
{
|
||||
"source": "Sandboxed",
|
||||
"target": "沙箱隔离"
|
||||
},
|
||||
{
|
||||
"source": "Sandboxing note",
|
||||
"target": "沙箱注意事项"
|
||||
},
|
||||
{
|
||||
"source": "Companion apps",
|
||||
"target": "配套应用"
|
||||
},
|
||||
{
|
||||
"source": "expected keys",
|
||||
"target": "预期键名"
|
||||
},
|
||||
{
|
||||
"source": "block streaming",
|
||||
"target": "分块流式传输"
|
||||
},
|
||||
{
|
||||
"source": "Block streaming",
|
||||
"target": "分块流式传输"
|
||||
},
|
||||
{
|
||||
"source": "Discovery + transports",
|
||||
"target": "设备发现 + 传输协议"
|
||||
},
|
||||
{
|
||||
"source": "Discovery",
|
||||
"target": "设备发现"
|
||||
},
|
||||
{
|
||||
"source": "Network model",
|
||||
"target": "网络模型"
|
||||
},
|
||||
{
|
||||
"source": "for full details",
|
||||
"target": "了解详情"
|
||||
},
|
||||
{
|
||||
"source": "First 60 seconds",
|
||||
"target": "最初的六十秒"
|
||||
},
|
||||
{
|
||||
"source": "Auth: where it lives (important)",
|
||||
"target": "凭证:存储位置(重要)"
|
||||
},
|
||||
{
|
||||
"source": "agent",
|
||||
"target": "智能体"
|
||||
},
|
||||
{
|
||||
"source": "channel",
|
||||
"target": "渠道"
|
||||
},
|
||||
{
|
||||
"source": "session",
|
||||
"target": "会话"
|
||||
},
|
||||
{
|
||||
"source": "provider",
|
||||
"target": "提供商"
|
||||
},
|
||||
{
|
||||
"source": "model",
|
||||
"target": "模型"
|
||||
},
|
||||
{
|
||||
"source": "tool",
|
||||
"target": "工具"
|
||||
},
|
||||
{
|
||||
"source": "CLI",
|
||||
"target": "CLI"
|
||||
},
|
||||
{
|
||||
"source": "install sanity",
|
||||
"target": "安装完整性检查"
|
||||
},
|
||||
{
|
||||
"source": "get unstuck",
|
||||
"target": "解决问题"
|
||||
},
|
||||
{
|
||||
"source": "troubleshooting",
|
||||
"target": "故障排除"
|
||||
},
|
||||
{
|
||||
"source": "FAQ",
|
||||
"target": "常见问题"
|
||||
},
|
||||
{
|
||||
"source": "onboarding",
|
||||
"target": "新手引导"
|
||||
},
|
||||
{
|
||||
"source": "Onboarding",
|
||||
"target": "新手引导"
|
||||
},
|
||||
{
|
||||
"source": "wizard",
|
||||
"target": "向导"
|
||||
},
|
||||
{
|
||||
"source": "environment variables",
|
||||
"target": "环境变量"
|
||||
},
|
||||
{
|
||||
"source": "environment variable",
|
||||
"target": "环境变量"
|
||||
},
|
||||
{
|
||||
"source": "env vars",
|
||||
"target": "环境变量"
|
||||
},
|
||||
{
|
||||
"source": "env var",
|
||||
"target": "环境变量"
|
||||
}
|
||||
]
|
||||
0
openclaw/docs/.i18n/ja-JP.tm.jsonl
Normal file
1329
openclaw/docs/.i18n/zh-CN.tm.jsonl
Normal file
1
openclaw/docs/CNAME
Normal file
@@ -0,0 +1 @@
|
||||
docs.openclaw.ai
|
||||
1
openclaw/docs/assets/install-script.svg
Normal file
|
After Width: | Height: | Size: 295 KiB |
BIN
openclaw/docs/assets/macos-onboarding/01-macos-warning.jpeg
Normal file
|
After Width: | Height: | Size: 155 KiB |
BIN
openclaw/docs/assets/macos-onboarding/02-local-networks.jpeg
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
openclaw/docs/assets/macos-onboarding/03-security-notice.png
Normal file
|
After Width: | Height: | Size: 312 KiB |
BIN
openclaw/docs/assets/macos-onboarding/04-choose-gateway.png
Normal file
|
After Width: | Height: | Size: 312 KiB |
BIN
openclaw/docs/assets/macos-onboarding/05-permissions.png
Normal file
|
After Width: | Height: | Size: 362 KiB |
BIN
openclaw/docs/assets/openclaw-logo-text-dark.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
openclaw/docs/assets/openclaw-logo-text.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
60
openclaw/docs/assets/pixel-lobster.svg
Normal file
@@ -0,0 +1,60 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 16 16" role="img" aria-label="Pixel lobster">
|
||||
<rect width="16" height="16" fill="none"/>
|
||||
<!-- outline -->
|
||||
<g fill="#3a0a0d">
|
||||
<rect x="1" y="5" width="1" height="3"/>
|
||||
<rect x="2" y="4" width="1" height="1"/>
|
||||
<rect x="2" y="8" width="1" height="1"/>
|
||||
<rect x="3" y="3" width="1" height="1"/>
|
||||
<rect x="3" y="9" width="1" height="1"/>
|
||||
<rect x="4" y="2" width="1" height="1"/>
|
||||
<rect x="4" y="10" width="1" height="1"/>
|
||||
<rect x="5" y="2" width="6" height="1"/>
|
||||
<rect x="11" y="2" width="1" height="1"/>
|
||||
<rect x="12" y="3" width="1" height="1"/>
|
||||
<rect x="12" y="9" width="1" height="1"/>
|
||||
<rect x="13" y="4" width="1" height="1"/>
|
||||
<rect x="13" y="8" width="1" height="1"/>
|
||||
<rect x="14" y="5" width="1" height="3"/>
|
||||
<rect x="5" y="11" width="6" height="1"/>
|
||||
<rect x="4" y="12" width="1" height="1"/>
|
||||
<rect x="11" y="12" width="1" height="1"/>
|
||||
<rect x="3" y="13" width="1" height="1"/>
|
||||
<rect x="12" y="13" width="1" height="1"/>
|
||||
<rect x="5" y="14" width="6" height="1"/>
|
||||
</g>
|
||||
|
||||
<!-- body -->
|
||||
<g fill="#ff4f40">
|
||||
<rect x="5" y="3" width="6" height="1"/>
|
||||
<rect x="4" y="4" width="8" height="1"/>
|
||||
<rect x="3" y="5" width="10" height="1"/>
|
||||
<rect x="3" y="6" width="10" height="1"/>
|
||||
<rect x="3" y="7" width="10" height="1"/>
|
||||
<rect x="4" y="8" width="8" height="1"/>
|
||||
<rect x="5" y="9" width="6" height="1"/>
|
||||
<rect x="5" y="12" width="6" height="1"/>
|
||||
<rect x="6" y="13" width="4" height="1"/>
|
||||
</g>
|
||||
|
||||
<!-- claws -->
|
||||
<g fill="#ff775f">
|
||||
<rect x="1" y="6" width="2" height="1"/>
|
||||
<rect x="2" y="5" width="1" height="1"/>
|
||||
<rect x="2" y="7" width="1" height="1"/>
|
||||
<rect x="13" y="6" width="2" height="1"/>
|
||||
<rect x="13" y="5" width="1" height="1"/>
|
||||
<rect x="13" y="7" width="1" height="1"/>
|
||||
</g>
|
||||
|
||||
<!-- eyes -->
|
||||
<g fill="#081016">
|
||||
<rect x="6" y="5" width="1" height="1"/>
|
||||
<rect x="9" y="5" width="1" height="1"/>
|
||||
</g>
|
||||
<g fill="#f5fbff">
|
||||
<rect x="6" y="4" width="1" height="1"/>
|
||||
<rect x="9" y="4" width="1" height="1"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
BIN
openclaw/docs/assets/showcase/agents-ui.jpg
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
openclaw/docs/assets/showcase/bambu-cli.png
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
openclaw/docs/assets/showcase/codexmonitor.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
openclaw/docs/assets/showcase/gohome-grafana.png
Normal file
|
After Width: | Height: | Size: 379 KiB |
BIN
openclaw/docs/assets/showcase/ios-testflight.jpg
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
openclaw/docs/assets/showcase/oura-health.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
11
openclaw/docs/assets/showcase/padel-cli.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="420" viewBox="0 0 800 420" role="img" aria-label="padel-cli availability output">
|
||||
<rect width="800" height="420" rx="24" fill="#0b0f14" />
|
||||
<rect x="24" y="24" width="752" height="372" rx="18" fill="#0f172a" stroke="#263246" stroke-width="2" />
|
||||
<text x="48" y="72" fill="#9ca3af" font-size="18" font-family="Fragment Mono, ui-monospace, SFMono-Regular, Menlo, monospace">
|
||||
<tspan x="48" dy="0">$ padel search --location "Barcelona" --date 2026-01-08 --time 18:00-22:00</tspan>
|
||||
<tspan x="48" dy="30" fill="#e5e7eb">Available courts (3):</tspan>
|
||||
<tspan x="48" dy="28" fill="#e5e7eb">- Vall d'Hebron 19:00 Court 2 (90m) EUR 34</tspan>
|
||||
<tspan x="48" dy="28" fill="#e5e7eb">- Badalona 20:30 Court 1 (60m) EUR 28</tspan>
|
||||
<tspan x="48" dy="28" fill="#e5e7eb">- Gracia 21:00 Court 4 (90m) EUR 36</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 897 B |
BIN
openclaw/docs/assets/showcase/padel-screenshot.jpg
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
openclaw/docs/assets/showcase/papla-tts.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
openclaw/docs/assets/showcase/pr-review-telegram.jpg
Normal file
|
After Width: | Height: | Size: 254 KiB |
BIN
openclaw/docs/assets/showcase/roborock-screenshot.jpg
Normal file
|
After Width: | Height: | Size: 76 KiB |
13
openclaw/docs/assets/showcase/roborock-status.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="420" viewBox="0 0 800 420" role="img" aria-label="GoHome Roborock status output">
|
||||
<rect width="800" height="420" rx="24" fill="#0b0f14" />
|
||||
<rect x="24" y="24" width="752" height="372" rx="18" fill="#111827" stroke="#263246" stroke-width="2" />
|
||||
<text x="48" y="72" fill="#9ca3af" font-size="18" font-family="Fragment Mono, ui-monospace, SFMono-Regular, Menlo, monospace">
|
||||
<tspan x="48" dy="0">$ gohome roborock status --device "Living Room"</tspan>
|
||||
<tspan x="48" dy="30" fill="#e5e7eb">Device: Roborock Q Revo</tspan>
|
||||
<tspan x="48" dy="28" fill="#e5e7eb">State: cleaning (zone)</tspan>
|
||||
<tspan x="48" dy="28" fill="#e5e7eb">Battery: 78%</tspan>
|
||||
<tspan x="48" dy="28" fill="#e5e7eb">Dustbin: 42%</tspan>
|
||||
<tspan x="48" dy="28" fill="#e5e7eb">Water tank: 61%</tspan>
|
||||
<tspan x="48" dy="28" fill="#e5e7eb">Last clean: 2026-01-06 19:42</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 947 B |
BIN
openclaw/docs/assets/showcase/roof-camera-sky.jpg
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
openclaw/docs/assets/showcase/snag.png
Normal file
|
After Width: | Height: | Size: 798 KiB |
BIN
openclaw/docs/assets/showcase/tesco-shop.jpg
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
openclaw/docs/assets/showcase/wienerlinien.png
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
openclaw/docs/assets/showcase/wine-cellar-skill.jpg
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
openclaw/docs/assets/showcase/winix-air-purifier.jpg
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
openclaw/docs/assets/showcase/xuezh-pronunciation.jpeg
Normal file
|
After Width: | Height: | Size: 93 KiB |
14
openclaw/docs/assets/sponsors/blacksmith.svg
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
3
openclaw/docs/assets/sponsors/openai.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white">
|
||||
<path d="M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073zM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494zM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646zM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872zm16.597 3.855l-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667zm2.01-3.023l-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66zm-12.64 4.135l-2.02-1.164a.08.08 0 0 1-.038-.057V6.075a4.5 4.5 0 0 1 7.375-3.453l-.142.08L8.704 5.46a.795.795 0 0 0-.393.681zm1.097-2.365l2.602-1.5 2.607 1.5v2.999l-2.597 1.5-2.607-1.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
44
openclaw/docs/automation/auth-monitoring.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
summary: "Monitor OAuth expiry for model providers"
|
||||
read_when:
|
||||
- Setting up auth expiry monitoring or alerts
|
||||
- Automating Claude Code / Codex OAuth refresh checks
|
||||
title: "Auth Monitoring"
|
||||
---
|
||||
|
||||
# Auth monitoring
|
||||
|
||||
OpenClaw exposes OAuth expiry health via `openclaw models status`. Use that for
|
||||
automation and alerting; scripts are optional extras for phone workflows.
|
||||
|
||||
## Preferred: CLI check (portable)
|
||||
|
||||
```bash
|
||||
openclaw models status --check
|
||||
```
|
||||
|
||||
Exit codes:
|
||||
|
||||
- `0`: OK
|
||||
- `1`: expired or missing credentials
|
||||
- `2`: expiring soon (within 24h)
|
||||
|
||||
This works in cron/systemd and requires no extra scripts.
|
||||
|
||||
## Optional scripts (ops / phone workflows)
|
||||
|
||||
These live under `scripts/` and are **optional**. They assume SSH access to the
|
||||
gateway host and are tuned for systemd + Termux.
|
||||
|
||||
- `scripts/claude-auth-status.sh` now uses `openclaw models status --json` as the
|
||||
source of truth (falling back to direct file reads if the CLI is unavailable),
|
||||
so keep `openclaw` on `PATH` for timers.
|
||||
- `scripts/auth-monitor.sh`: cron/systemd timer target; sends alerts (ntfy or phone).
|
||||
- `scripts/systemd/openclaw-auth-monitor.{service,timer}`: systemd user timer.
|
||||
- `scripts/claude-auth-status.sh`: Claude Code + OpenClaw auth checker (full/json/simple).
|
||||
- `scripts/mobile-reauth.sh`: guided re‑auth flow over SSH.
|
||||
- `scripts/termux-quick-auth.sh`: one‑tap widget status + open auth URL.
|
||||
- `scripts/termux-auth-widget.sh`: full guided widget flow.
|
||||
- `scripts/termux-sync-widget.sh`: sync Claude Code creds → OpenClaw.
|
||||
|
||||
If you don’t need phone automation or systemd timers, skip these scripts.
|
||||
633
openclaw/docs/automation/cron-jobs.md
Normal file
@@ -0,0 +1,633 @@
|
||||
---
|
||||
summary: "Cron jobs + wakeups for the Gateway scheduler"
|
||||
read_when:
|
||||
- Scheduling background jobs or wakeups
|
||||
- Wiring automation that should run with or alongside heartbeats
|
||||
- Deciding between heartbeat and cron for scheduled tasks
|
||||
title: "Cron Jobs"
|
||||
---
|
||||
|
||||
# Cron jobs (Gateway scheduler)
|
||||
|
||||
> **Cron vs Heartbeat?** See [Cron vs Heartbeat](/automation/cron-vs-heartbeat) for guidance on when to use each.
|
||||
|
||||
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”_,
|
||||
cron is the mechanism.
|
||||
|
||||
Troubleshooting: [/automation/troubleshooting](/automation/troubleshooting)
|
||||
|
||||
## 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:
|
||||
- **Main session**: enqueue a system event, then run on the next heartbeat.
|
||||
- **Isolated**: run a dedicated agent turn in `cron:<jobId>`, with delivery (announce by default or none).
|
||||
- Wakeups are first-class: a job can request “wake now” vs “next heartbeat”.
|
||||
- Webhook posting is per job via `delivery.mode = "webhook"` + `delivery.to = "<url>"`.
|
||||
- Legacy fallback remains for stored jobs with `notify: true` when `cron.webhook` is set, migrate those jobs to webhook delivery mode.
|
||||
|
||||
## Quick start (actionable)
|
||||
|
||||
Create a one-shot reminder, verify it exists, and run it immediately:
|
||||
|
||||
```bash
|
||||
openclaw cron add \
|
||||
--name "Reminder" \
|
||||
--at "2026-02-01T16:00:00Z" \
|
||||
--session main \
|
||||
--system-event "Reminder: check the cron docs draft" \
|
||||
--wake now \
|
||||
--delete-after-run
|
||||
|
||||
openclaw cron list
|
||||
openclaw cron run <job-id>
|
||||
openclaw cron runs --id <job-id>
|
||||
```
|
||||
|
||||
Schedule a recurring isolated job with delivery:
|
||||
|
||||
```bash
|
||||
openclaw cron add \
|
||||
--name "Morning brief" \
|
||||
--cron "0 7 * * *" \
|
||||
--tz "America/Los_Angeles" \
|
||||
--session isolated \
|
||||
--message "Summarize overnight updates." \
|
||||
--announce \
|
||||
--channel slack \
|
||||
--to "channel:C1234567890"
|
||||
```
|
||||
|
||||
## Tool-call equivalents (Gateway cron tool)
|
||||
|
||||
For the canonical JSON shapes and examples, see [JSON schema for tool calls](/automation/cron-jobs#json-schema-for-tool-calls).
|
||||
|
||||
## Where cron jobs are stored
|
||||
|
||||
Cron jobs are persisted on the Gateway host at `~/.openclaw/cron/jobs.json` by default.
|
||||
The Gateway loads the file into memory and writes it back on changes, so manual edits
|
||||
are only safe when the Gateway is stopped. Prefer `openclaw cron add/edit` or the cron
|
||||
tool call API for changes.
|
||||
|
||||
## Beginner-friendly overview
|
||||
|
||||
Think of a cron job as: **when** to run + **what** to do.
|
||||
|
||||
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**
|
||||
- `sessionTarget: "main"` → run during the next heartbeat with main context.
|
||||
- `sessionTarget: "isolated"` → run a dedicated agent turn in `cron:<jobId>`.
|
||||
|
||||
3. **Choose the payload**
|
||||
- Main session → `payload.kind = "systemEvent"`
|
||||
- Isolated session → `payload.kind = "agentTurn"`
|
||||
|
||||
Optional: one-shot jobs (`schedule.kind = "at"`) delete after success by default. Set
|
||||
`deleteAfterRun: false` to keep them (they will disable after success).
|
||||
|
||||
## Concepts
|
||||
|
||||
### Jobs
|
||||
|
||||
A cron job is a stored record with:
|
||||
|
||||
- a **schedule** (when it should run),
|
||||
- a **payload** (what it should do),
|
||||
- optional **delivery mode** (`announce`, `webhook`, or `none`).
|
||||
- optional **agent binding** (`agentId`): run the job under a specific agent; if
|
||||
missing or unknown, the gateway falls back to the default agent.
|
||||
|
||||
Jobs are identified by a stable `jobId` (used by CLI/Gateway APIs).
|
||||
In agent tool calls, `jobId` is canonical; legacy `id` is accepted for compatibility.
|
||||
One-shot jobs auto-delete after success by default; set `deleteAfterRun: false` to keep them.
|
||||
|
||||
### Schedules
|
||||
|
||||
Cron supports three schedule kinds:
|
||||
|
||||
- `at`: one-shot timestamp via `schedule.at` (ISO 8601).
|
||||
- `every`: fixed interval (ms).
|
||||
- `cron`: 5-field cron expression (or 6-field with seconds) with optional IANA timezone.
|
||||
|
||||
Cron expressions use `croner`. If a timezone is omitted, the Gateway host’s
|
||||
local timezone is used.
|
||||
|
||||
To reduce top-of-hour load spikes across many gateways, OpenClaw applies a
|
||||
deterministic per-job stagger window of up to 5 minutes for recurring
|
||||
top-of-hour expressions (for example `0 * * * *`, `0 */2 * * *`). Fixed-hour
|
||||
expressions such as `0 7 * * *` remain exact.
|
||||
|
||||
For any cron schedule, you can set an explicit stagger window with `schedule.staggerMs`
|
||||
(`0` keeps exact timing). CLI shortcuts:
|
||||
|
||||
- `--stagger 30s` (or `1m`, `5m`) to set an explicit stagger window.
|
||||
- `--exact` to force `staggerMs = 0`.
|
||||
|
||||
### 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"`.
|
||||
|
||||
- `wakeMode: "now"` (default): event triggers an immediate heartbeat run.
|
||||
- `wakeMode: "next-heartbeat"`: event waits for the next scheduled heartbeat.
|
||||
|
||||
This is the best fit when you want the normal heartbeat prompt + main-session context.
|
||||
See [Heartbeat](/gateway/heartbeat).
|
||||
|
||||
#### Isolated jobs (dedicated cron sessions)
|
||||
|
||||
Isolated jobs run a dedicated agent turn in session `cron:<jobId>`.
|
||||
|
||||
Key behaviors:
|
||||
|
||||
- Prompt is prefixed with `[cron:<jobId> <job name>]` for traceability.
|
||||
- Each run starts a **fresh session id** (no prior conversation carry-over).
|
||||
- Default behavior: if `delivery` is omitted, isolated jobs announce a summary (`delivery.mode = "announce"`).
|
||||
- `delivery.mode` chooses what happens:
|
||||
- `announce`: deliver a summary to the target channel and post a brief summary to the main session.
|
||||
- `webhook`: POST the finished event payload to `delivery.to` when the finished event includes a summary.
|
||||
- `none`: internal only (no delivery, no main-session summary).
|
||||
- `wakeMode` controls when the main-session summary posts:
|
||||
- `now`: immediate heartbeat.
|
||||
- `next-heartbeat`: waits for the next scheduled heartbeat.
|
||||
|
||||
Use isolated jobs for noisy, frequent, or "background chores" that shouldn't spam
|
||||
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.
|
||||
|
||||
Delivery config:
|
||||
|
||||
- `delivery.mode`: `none` | `announce` | `webhook`.
|
||||
- `delivery.channel`: `last` or a specific channel.
|
||||
- `delivery.to`: channel-specific target (announce) or webhook URL (webhook mode).
|
||||
- `delivery.bestEffort`: avoid failing the job if announce delivery fails.
|
||||
|
||||
Announce delivery suppresses messaging tool sends for the run; use `delivery.channel`/`delivery.to`
|
||||
to target the chat instead. When `delivery.mode = "none"`, no summary is posted to the main session.
|
||||
|
||||
If `delivery` is omitted for isolated jobs, OpenClaw defaults to `announce`.
|
||||
|
||||
#### Announce delivery flow
|
||||
|
||||
When `delivery.mode = "announce"`, cron delivers directly via the outbound channel adapters.
|
||||
The main agent is not spun up to craft or forward the message.
|
||||
|
||||
Behavior details:
|
||||
|
||||
- Content: delivery uses the isolated run's outbound payloads (text/media) with normal chunking and
|
||||
channel formatting.
|
||||
- Heartbeat-only responses (`HEARTBEAT_OK` with no real content) are not delivered.
|
||||
- If the isolated run already sent a message to the same target via the message tool, delivery is
|
||||
skipped to avoid duplicates.
|
||||
- Missing or invalid delivery targets fail the job unless `delivery.bestEffort = true`.
|
||||
- A short summary is posted to the main session only when `delivery.mode = "announce"`.
|
||||
- The main-session summary respects `wakeMode`: `now` triggers an immediate heartbeat and
|
||||
`next-heartbeat` waits for the next scheduled heartbeat.
|
||||
|
||||
#### Webhook delivery flow
|
||||
|
||||
When `delivery.mode = "webhook"`, cron posts the finished event payload to `delivery.to` when the finished event includes a summary.
|
||||
|
||||
Behavior details:
|
||||
|
||||
- The endpoint must be a valid HTTP(S) URL.
|
||||
- No channel delivery is attempted in webhook mode.
|
||||
- No main-session summary is posted in webhook mode.
|
||||
- If `cron.webhookToken` is set, auth header is `Authorization: Bearer <cron.webhookToken>`.
|
||||
- Deprecated fallback: stored legacy jobs with `notify: true` still post to `cron.webhook` (if configured), with a warning so you can migrate to `delivery.mode = "webhook"`.
|
||||
|
||||
### 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)
|
||||
|
||||
Note: You can set `model` on main-session jobs too, but it changes the shared main
|
||||
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 via the top-level `delivery` config:
|
||||
|
||||
- `delivery.mode`: `announce` (channel delivery), `webhook` (HTTP POST), or `none`.
|
||||
- `delivery.channel`: `whatsapp` / `telegram` / `discord` / `slack` / `mattermost` (plugin) / `signal` / `imessage` / `last`.
|
||||
- `delivery.to`: channel-specific recipient target.
|
||||
|
||||
`announce` delivery is only valid for isolated jobs (`sessionTarget: "isolated"`).
|
||||
`webhook` delivery is valid for both main and isolated jobs.
|
||||
|
||||
If `delivery.channel` or `delivery.to` is omitted, cron can fall back to the main session’s
|
||||
“last route” (the last place the agent replied).
|
||||
|
||||
Target format reminders:
|
||||
|
||||
- Slack/Discord/Mattermost (plugin) targets should use explicit prefixes (e.g. `channel:<id>`, `user:<id>`) 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:
|
||||
|
||||
- `-1001234567890` (chat id only)
|
||||
- `-1001234567890:topic:123` (preferred: explicit topic marker)
|
||||
- `-1001234567890:123` (shorthand: numeric suffix)
|
||||
|
||||
Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted:
|
||||
|
||||
- `telegram:group:-1001234567890:topic:123`
|
||||
|
||||
## JSON schema for tool calls
|
||||
|
||||
Use these shapes when calling Gateway `cron.*` tools directly (agent tool calls or RPC).
|
||||
CLI flags accept human durations like `20m`, but tool calls should use an ISO 8601 string
|
||||
for `schedule.at` and milliseconds for `schedule.everyMs`.
|
||||
|
||||
### cron.add params
|
||||
|
||||
One-shot, main session job (system event):
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Reminder",
|
||||
"schedule": { "kind": "at", "at": "2026-02-01T16:00:00Z" },
|
||||
"sessionTarget": "main",
|
||||
"wakeMode": "now",
|
||||
"payload": { "kind": "systemEvent", "text": "Reminder text" },
|
||||
"deleteAfterRun": true
|
||||
}
|
||||
```
|
||||
|
||||
Recurring, isolated job with delivery:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Morning brief",
|
||||
"schedule": { "kind": "cron", "expr": "0 7 * * *", "tz": "America/Los_Angeles" },
|
||||
"sessionTarget": "isolated",
|
||||
"wakeMode": "next-heartbeat",
|
||||
"payload": {
|
||||
"kind": "agentTurn",
|
||||
"message": "Summarize overnight updates."
|
||||
},
|
||||
"delivery": {
|
||||
"mode": "announce",
|
||||
"channel": "slack",
|
||||
"to": "channel:C1234567890",
|
||||
"bestEffort": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `schedule.kind`: `at` (`at`), `every` (`everyMs`), or `cron` (`expr`, optional `tz`).
|
||||
- `schedule.at` accepts ISO 8601 (timezone optional; treated as UTC when omitted).
|
||||
- `everyMs` is milliseconds.
|
||||
- `sessionTarget` must be `"main"` or `"isolated"` and must match `payload.kind`.
|
||||
- Optional fields: `agentId`, `description`, `enabled`, `deleteAfterRun` (defaults to true for `at`),
|
||||
`delivery`.
|
||||
- `wakeMode` defaults to `"now"` when omitted.
|
||||
|
||||
### cron.update params
|
||||
|
||||
```json
|
||||
{
|
||||
"jobId": "job-123",
|
||||
"patch": {
|
||||
"enabled": false,
|
||||
"schedule": { "kind": "every", "everyMs": 3600000 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `jobId` is canonical; `id` is accepted for compatibility.
|
||||
- Use `agentId: null` in the patch to clear an agent binding.
|
||||
|
||||
### cron.run and cron.remove params
|
||||
|
||||
```json
|
||||
{ "jobId": "job-123", "mode": "force" }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "jobId": "job-123" }
|
||||
```
|
||||
|
||||
## Storage & history
|
||||
|
||||
- Job store: `~/.openclaw/cron/jobs.json` (Gateway-managed JSON).
|
||||
- Run history: `~/.openclaw/cron/runs/<jobId>.jsonl` (JSONL, auto-pruned by size and line count).
|
||||
- Isolated cron run sessions in `sessions.json` are pruned by `cron.sessionRetention` (default `24h`; set `false` to disable).
|
||||
- Override store path: `cron.store` in config.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json5
|
||||
{
|
||||
cron: {
|
||||
enabled: true, // default true
|
||||
store: "~/.openclaw/cron/jobs.json",
|
||||
maxConcurrentRuns: 1, // default 1
|
||||
webhook: "https://example.invalid/legacy", // deprecated fallback for stored notify:true jobs
|
||||
webhookToken: "replace-with-dedicated-webhook-token", // optional bearer token for webhook mode
|
||||
sessionRetention: "24h", // duration string or false
|
||||
runLog: {
|
||||
maxBytes: "2mb", // default 2_000_000 bytes
|
||||
keepLines: 2000, // default 2000
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Run-log pruning behavior:
|
||||
|
||||
- `cron.runLog.maxBytes`: max run-log file size before pruning.
|
||||
- `cron.runLog.keepLines`: when pruning, keep only the newest N lines.
|
||||
- Both apply to `cron/runs/<jobId>.jsonl` files.
|
||||
|
||||
Webhook behavior:
|
||||
|
||||
- Preferred: set `delivery.mode: "webhook"` with `delivery.to: "https://..."` per job.
|
||||
- Webhook URLs must be valid `http://` or `https://` URLs.
|
||||
- When posted, payload is the cron finished event JSON.
|
||||
- If `cron.webhookToken` is set, auth header is `Authorization: Bearer <cron.webhookToken>`.
|
||||
- If `cron.webhookToken` is not set, no `Authorization` header is sent.
|
||||
- Deprecated fallback: stored legacy jobs with `notify: true` still use `cron.webhook` when present.
|
||||
|
||||
Disable cron entirely:
|
||||
|
||||
- `cron.enabled: false` (config)
|
||||
- `OPENCLAW_SKIP_CRON=1` (env)
|
||||
|
||||
## Maintenance
|
||||
|
||||
Cron has two built-in maintenance paths: isolated run-session retention and run-log pruning.
|
||||
|
||||
### Defaults
|
||||
|
||||
- `cron.sessionRetention`: `24h` (set `false` to disable run-session pruning)
|
||||
- `cron.runLog.maxBytes`: `2_000_000` bytes
|
||||
- `cron.runLog.keepLines`: `2000`
|
||||
|
||||
### How it works
|
||||
|
||||
- Isolated runs create session entries (`...:cron:<jobId>:run:<uuid>`) and transcript files.
|
||||
- The reaper removes expired run-session entries older than `cron.sessionRetention`.
|
||||
- For removed run sessions no longer referenced by the session store, OpenClaw archives transcript files and purges old deleted archives on the same retention window.
|
||||
- After each run append, `cron/runs/<jobId>.jsonl` is size-checked:
|
||||
- if file size exceeds `runLog.maxBytes`, it is trimmed to the newest `runLog.keepLines` lines.
|
||||
|
||||
### Performance caveat for high volume schedulers
|
||||
|
||||
High-frequency cron setups can generate large run-session and run-log footprints. Maintenance is built in, but loose limits can still create avoidable IO and cleanup work.
|
||||
|
||||
What to watch:
|
||||
|
||||
- long `cron.sessionRetention` windows with many isolated runs
|
||||
- high `cron.runLog.keepLines` combined with large `runLog.maxBytes`
|
||||
- many noisy recurring jobs writing to the same `cron/runs/<jobId>.jsonl`
|
||||
|
||||
What to do:
|
||||
|
||||
- keep `cron.sessionRetention` as short as your debugging/audit needs allow
|
||||
- keep run logs bounded with moderate `runLog.maxBytes` and `runLog.keepLines`
|
||||
- move noisy background jobs to isolated mode with delivery rules that avoid unnecessary chatter
|
||||
- review growth periodically with `openclaw cron runs` and adjust retention before logs become large
|
||||
|
||||
### Customize examples
|
||||
|
||||
Keep run sessions for a week and allow bigger run logs:
|
||||
|
||||
```json5
|
||||
{
|
||||
cron: {
|
||||
sessionRetention: "7d",
|
||||
runLog: {
|
||||
maxBytes: "10mb",
|
||||
keepLines: 5000,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Disable isolated run-session pruning but keep run-log pruning:
|
||||
|
||||
```json5
|
||||
{
|
||||
cron: {
|
||||
sessionRetention: false,
|
||||
runLog: {
|
||||
maxBytes: "5mb",
|
||||
keepLines: 3000,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Tune for high-volume cron usage (example):
|
||||
|
||||
```json5
|
||||
{
|
||||
cron: {
|
||||
sessionRetention: "12h",
|
||||
runLog: {
|
||||
maxBytes: "3mb",
|
||||
keepLines: 1500,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## CLI quickstart
|
||||
|
||||
One-shot reminder (UTC ISO, auto-delete after success):
|
||||
|
||||
```bash
|
||||
openclaw cron add \
|
||||
--name "Send reminder" \
|
||||
--at "2026-01-12T18:00:00Z" \
|
||||
--session main \
|
||||
--system-event "Reminder: submit expense report." \
|
||||
--wake now \
|
||||
--delete-after-run
|
||||
```
|
||||
|
||||
One-shot reminder (main session, wake immediately):
|
||||
|
||||
```bash
|
||||
openclaw cron add \
|
||||
--name "Calendar check" \
|
||||
--at "20m" \
|
||||
--session main \
|
||||
--system-event "Next heartbeat: check calendar." \
|
||||
--wake now
|
||||
```
|
||||
|
||||
Recurring isolated job (announce to WhatsApp):
|
||||
|
||||
```bash
|
||||
openclaw cron add \
|
||||
--name "Morning status" \
|
||||
--cron "0 7 * * *" \
|
||||
--tz "America/Los_Angeles" \
|
||||
--session isolated \
|
||||
--message "Summarize inbox + calendar for today." \
|
||||
--announce \
|
||||
--channel whatsapp \
|
||||
--to "+15551234567"
|
||||
```
|
||||
|
||||
Recurring cron job with explicit 30-second stagger:
|
||||
|
||||
```bash
|
||||
openclaw cron add \
|
||||
--name "Minute watcher" \
|
||||
--cron "0 * * * * *" \
|
||||
--tz "UTC" \
|
||||
--stagger 30s \
|
||||
--session isolated \
|
||||
--message "Run minute watcher checks." \
|
||||
--announce
|
||||
```
|
||||
|
||||
Recurring isolated job (deliver to a Telegram topic):
|
||||
|
||||
```bash
|
||||
openclaw cron add \
|
||||
--name "Nightly summary (topic)" \
|
||||
--cron "0 22 * * *" \
|
||||
--tz "America/Los_Angeles" \
|
||||
--session isolated \
|
||||
--message "Summarize today; send to the nightly topic." \
|
||||
--announce \
|
||||
--channel telegram \
|
||||
--to "-1001234567890:topic:123"
|
||||
```
|
||||
|
||||
Isolated job with model and thinking override:
|
||||
|
||||
```bash
|
||||
openclaw cron add \
|
||||
--name "Deep analysis" \
|
||||
--cron "0 6 * * 1" \
|
||||
--tz "America/Los_Angeles" \
|
||||
--session isolated \
|
||||
--message "Weekly deep analysis of project progress." \
|
||||
--model "opus" \
|
||||
--thinking high \
|
||||
--announce \
|
||||
--channel whatsapp \
|
||||
--to "+15551234567"
|
||||
```
|
||||
|
||||
Agent selection (multi-agent setups):
|
||||
|
||||
```bash
|
||||
# Pin a job to agent "ops" (falls back to default if that agent is missing)
|
||||
openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --message "Check ops queue" --agent ops
|
||||
|
||||
# Switch or clear the agent on an existing job
|
||||
openclaw cron edit <jobId> --agent ops
|
||||
openclaw cron edit <jobId> --clear-agent
|
||||
```
|
||||
|
||||
Manual run (force is the default, use `--due` to only run when due):
|
||||
|
||||
```bash
|
||||
openclaw cron run <jobId>
|
||||
openclaw cron run <jobId> --due
|
||||
```
|
||||
|
||||
Edit an existing job (patch fields):
|
||||
|
||||
```bash
|
||||
openclaw cron edit <jobId> \
|
||||
--message "Updated prompt" \
|
||||
--model "opus" \
|
||||
--thinking low
|
||||
```
|
||||
|
||||
Force an existing cron job to run exactly on schedule (no stagger):
|
||||
|
||||
```bash
|
||||
openclaw cron edit <jobId> --exact
|
||||
```
|
||||
|
||||
Run history:
|
||||
|
||||
```bash
|
||||
openclaw cron runs --id <jobId> --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).
|
||||
|
||||
## 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.
|
||||
|
||||
### A recurring job keeps delaying after failures
|
||||
|
||||
- OpenClaw applies exponential retry backoff for recurring jobs after consecutive errors:
|
||||
30s, 1m, 5m, 15m, then 60m between retries.
|
||||
- Backoff resets automatically after the next successful run.
|
||||
- One-shot (`at`) jobs disable after a terminal run (`ok`, `error`, or `skipped`) and do not retry.
|
||||
|
||||
### Telegram delivers to the wrong place
|
||||
|
||||
- For forum topics, use `-100…:topic:<id>` 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.
|
||||
|
||||
### Subagent announce delivery retries
|
||||
|
||||
- When a subagent run completes, the gateway announces the result to the requester session.
|
||||
- If the announce flow returns `false` (e.g. requester session is busy), the gateway retries up to 3 times with tracking via `announceRetryCount`.
|
||||
- Announces older than 5 minutes past `endedAt` are force-expired to prevent stale entries from looping indefinitely.
|
||||
- If you see repeated announce deliveries in logs, check the subagent registry for entries with high `announceRetryCount` values.
|
||||
286
openclaw/docs/automation/cron-vs-heartbeat.md
Normal file
@@ -0,0 +1,286 @@
|
||||
---
|
||||
summary: "Guidance for choosing between heartbeat and cron jobs for automation"
|
||||
read_when:
|
||||
- Deciding how to schedule recurring tasks
|
||||
- Setting up background monitoring or notifications
|
||||
- Optimizing token usage for periodic checks
|
||||
title: "Cron vs Heartbeat"
|
||||
---
|
||||
|
||||
# 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 |
|
||||
|
||||
## Heartbeat: Periodic Awareness
|
||||
|
||||
Heartbeats run in the **main session** at a regular interval (default: 30 min). They're designed for the agent to check on things and surface anything important.
|
||||
|
||||
### When to use heartbeat
|
||||
|
||||
- **Multiple periodic checks**: Instead of 5 separate cron jobs checking inbox, calendar, weather, notifications, and project status, a single heartbeat can batch all of these.
|
||||
- **Context-aware decisions**: The agent has full main-session context, so it can make smart decisions about what's urgent vs. what can wait.
|
||||
- **Conversational continuity**: Heartbeat runs share the same session, so the agent remembers recent conversations and can follow up naturally.
|
||||
- **Low-overhead monitoring**: One heartbeat replaces many small polling tasks.
|
||||
|
||||
### Heartbeat advantages
|
||||
|
||||
- **Batches multiple checks**: One agent turn can review inbox, calendar, and notifications together.
|
||||
- **Reduces API calls**: A single heartbeat is cheaper than 5 isolated cron jobs.
|
||||
- **Context-aware**: The agent knows what you've been working on and can prioritize accordingly.
|
||||
- **Smart suppression**: If nothing needs attention, the agent replies `HEARTBEAT_OK` and no message is delivered.
|
||||
- **Natural timing**: Drifts slightly based on queue load, which is fine for most monitoring.
|
||||
|
||||
### Heartbeat example: HEARTBEAT.md checklist
|
||||
|
||||
```md
|
||||
# Heartbeat checklist
|
||||
|
||||
- Check email for urgent messages
|
||||
- Review calendar for events in next 2 hours
|
||||
- If a background task finished, summarize results
|
||||
- If idle for 8+ hours, send a brief check-in
|
||||
```
|
||||
|
||||
The agent reads this on each heartbeat and handles all items in one turn.
|
||||
|
||||
### Configuring heartbeat
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: {
|
||||
every: "30m", // interval
|
||||
target: "last", // explicit alert delivery target (default is "none")
|
||||
activeHours: { start: "08:00", end: "22:00" }, // optional
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
See [Heartbeat](/gateway/heartbeat) for full configuration.
|
||||
|
||||
## Cron: Precise Scheduling
|
||||
|
||||
Cron jobs run at precise times and can run in isolated sessions without affecting main context.
|
||||
Recurring top-of-hour schedules are automatically spread by a deterministic
|
||||
per-job offset in a 0-5 minute window.
|
||||
|
||||
### When to use cron
|
||||
|
||||
- **Exact timing required**: "Send this at 9:00 AM every Monday" (not "sometime around 9").
|
||||
- **Standalone tasks**: Tasks that don't need conversational context.
|
||||
- **Different model/thinking**: Heavy analysis that warrants a more powerful model.
|
||||
- **One-shot reminders**: "Remind me in 20 minutes" with `--at`.
|
||||
- **Noisy/frequent tasks**: Tasks that would clutter main session history.
|
||||
- **External triggers**: Tasks that should run independently of whether the agent is otherwise active.
|
||||
|
||||
### Cron advantages
|
||||
|
||||
- **Precise timing**: 5-field or 6-field (seconds) cron expressions with timezone support.
|
||||
- **Built-in load spreading**: recurring top-of-hour schedules are staggered by up to 5 minutes by default.
|
||||
- **Per-job control**: override stagger with `--stagger <duration>` or force exact timing with `--exact`.
|
||||
- **Session isolation**: Runs in `cron:<jobId>` without polluting main history.
|
||||
- **Model overrides**: Use a cheaper or more powerful model per job.
|
||||
- **Delivery control**: Isolated jobs default to `announce` (summary); choose `none` as needed.
|
||||
- **Immediate delivery**: Announce mode posts directly without waiting for heartbeat.
|
||||
- **No agent context needed**: Runs even if main session is idle or compacted.
|
||||
- **One-shot support**: `--at` for precise future timestamps.
|
||||
|
||||
### Cron example: Daily morning briefing
|
||||
|
||||
```bash
|
||||
openclaw cron add \
|
||||
--name "Morning briefing" \
|
||||
--cron "0 7 * * *" \
|
||||
--tz "America/New_York" \
|
||||
--session isolated \
|
||||
--message "Generate today's briefing: weather, calendar, top emails, news summary." \
|
||||
--model opus \
|
||||
--announce \
|
||||
--channel whatsapp \
|
||||
--to "+15551234567"
|
||||
```
|
||||
|
||||
This runs at exactly 7:00 AM New York time, uses Opus for quality, and announces a summary directly to WhatsApp.
|
||||
|
||||
### Cron example: One-shot reminder
|
||||
|
||||
```bash
|
||||
openclaw cron add \
|
||||
--name "Meeting reminder" \
|
||||
--at "20m" \
|
||||
--session main \
|
||||
--system-event "Reminder: standup meeting starts in 10 minutes." \
|
||||
--wake now \
|
||||
--delete-after-run
|
||||
```
|
||||
|
||||
See [Cron jobs](/automation/cron-jobs) for full CLI reference.
|
||||
|
||||
## Decision Flowchart
|
||||
|
||||
```
|
||||
Does the task need to run at an EXACT time?
|
||||
YES -> Use cron
|
||||
NO -> Continue...
|
||||
|
||||
Does the task need isolation from main session?
|
||||
YES -> Use cron (isolated)
|
||||
NO -> Continue...
|
||||
|
||||
Can this task be batched with other periodic checks?
|
||||
YES -> Use heartbeat (add to HEARTBEAT.md)
|
||||
NO -> Use cron
|
||||
|
||||
Is this a one-shot reminder?
|
||||
YES -> Use cron with --at
|
||||
NO -> Continue...
|
||||
|
||||
Does it need a different model or thinking level?
|
||||
YES -> Use cron (isolated) with --model/--thinking
|
||||
NO -> Use heartbeat
|
||||
```
|
||||
|
||||
## Combining Both
|
||||
|
||||
The most efficient setup uses **both**:
|
||||
|
||||
1. **Heartbeat** handles routine monitoring (inbox, calendar, notifications) in one batched turn every 30 minutes.
|
||||
2. **Cron** handles precise schedules (daily reports, weekly reviews) and one-shot reminders.
|
||||
|
||||
### 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
|
||||
- Light check-in if quiet for 8+ hours
|
||||
```
|
||||
|
||||
**Cron jobs** (precise timing):
|
||||
|
||||
```bash
|
||||
# Daily morning briefing at 7am
|
||||
openclaw cron add --name "Morning brief" --cron "0 7 * * *" --session isolated --message "..." --announce
|
||||
|
||||
# Weekly project review on Mondays at 9am
|
||||
openclaw cron add --name "Weekly review" --cron "0 9 * * 1" --session isolated --message "..." --model opus
|
||||
|
||||
# One-shot reminder
|
||||
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.
|
||||
Use it when the task is more than a single agent turn, and you want a resumable workflow with human checkpoints.
|
||||
|
||||
### When Lobster fits
|
||||
|
||||
- **Multi-step automation**: You need a fixed pipeline of tool calls, not a one-off prompt.
|
||||
- **Approval gates**: Side effects should pause until you approve, then resume.
|
||||
- **Resumable runs**: Continue a paused workflow without re-running earlier steps.
|
||||
|
||||
### How it pairs with heartbeat and cron
|
||||
|
||||
- **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.
|
||||
|
||||
### Operational notes (from the code)
|
||||
|
||||
- Lobster runs as a **local subprocess** (`lobster` CLI) in tool mode and returns a **JSON envelope**.
|
||||
- If the tool returns `needs_approval`, you resume with a `resumeToken` and `approve` flag.
|
||||
- The tool is an **optional plugin**; enable it additively via `tools.alsoAllow: ["lobster"]` (recommended).
|
||||
- Lobster expects the `lobster` CLI to be available on `PATH`.
|
||||
|
||||
See [Lobster](/tools/lobster) for full usage and examples.
|
||||
|
||||
## Main Session vs Isolated Session
|
||||
|
||||
Both heartbeat and cron can interact with the main session, but differently:
|
||||
|
||||
| | Heartbeat | Cron (main) | Cron (isolated) |
|
||||
| ------- | ------------------------------- | ------------------------ | -------------------------- |
|
||||
| Session | Main | Main (via system event) | `cron:<jobId>` |
|
||||
| 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 | Announce summary (default) |
|
||||
|
||||
### 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
|
||||
|
||||
```bash
|
||||
openclaw cron add \
|
||||
--name "Check project" \
|
||||
--every "4h" \
|
||||
--session main \
|
||||
--system-event "Time for a project health check" \
|
||||
--wake now
|
||||
```
|
||||
|
||||
### When to use isolated cron
|
||||
|
||||
Use `--session isolated` when you want:
|
||||
|
||||
- A clean slate without prior context
|
||||
- Different model or thinking settings
|
||||
- Announce summaries directly to a channel
|
||||
- History that doesn't clutter main session
|
||||
|
||||
```bash
|
||||
openclaw cron add \
|
||||
--name "Deep analysis" \
|
||||
--cron "0 6 * * 0" \
|
||||
--session isolated \
|
||||
--message "Weekly codebase analysis..." \
|
||||
--model opus \
|
||||
--thinking high \
|
||||
--announce
|
||||
```
|
||||
|
||||
## 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 |
|
||||
|
||||
**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.
|
||||
- Use isolated cron with a cheaper model for routine tasks.
|
||||
|
||||
## Related
|
||||
|
||||
- [Heartbeat](/gateway/heartbeat) - full heartbeat configuration
|
||||
- [Cron jobs](/automation/cron-jobs) - full cron CLI and API reference
|
||||
- [System](/cli/system) - system events + heartbeat controls
|
||||
256
openclaw/docs/automation/gmail-pubsub.md
Normal file
@@ -0,0 +1,256 @@
|
||||
---
|
||||
summary: "Gmail Pub/Sub push wired into OpenClaw webhooks via gogcli"
|
||||
read_when:
|
||||
- Wiring Gmail inbox triggers to OpenClaw
|
||||
- Setting up Pub/Sub push for agent wake
|
||||
title: "Gmail PubSub"
|
||||
---
|
||||
|
||||
# Gmail Pub/Sub -> OpenClaw
|
||||
|
||||
Goal: Gmail watch -> Pub/Sub push -> `gog gmail watch serve` -> OpenClaw webhook.
|
||||
|
||||
## Prereqs
|
||||
|
||||
- `gcloud` installed and logged in ([install guide](https://docs.cloud.google.com/sdk/docs/install-sdk)).
|
||||
- `gog` (gogcli) installed and authorized for the Gmail account ([gogcli.sh](https://gogcli.sh/)).
|
||||
- OpenClaw hooks enabled (see [Webhooks](/automation/webhook)).
|
||||
- `tailscale` logged in ([tailscale.com](https://tailscale.com/)). Supported setup uses Tailscale Funnel for the public HTTPS endpoint.
|
||||
Other tunnel services can work, but are DIY/unsupported and require manual wiring.
|
||||
Right now, Tailscale is what we support.
|
||||
|
||||
Example hook config (enable Gmail preset mapping):
|
||||
|
||||
```json5
|
||||
{
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "OPENCLAW_HOOK_TOKEN",
|
||||
path: "/hooks",
|
||||
presets: ["gmail"],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
To deliver the Gmail summary to a chat surface, override the preset with a mapping
|
||||
that sets `deliver` + optional `channel`/`to`:
|
||||
|
||||
```json5
|
||||
{
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "OPENCLAW_HOOK_TOKEN",
|
||||
presets: ["gmail"],
|
||||
mappings: [
|
||||
{
|
||||
match: { path: "gmail" },
|
||||
action: "agent",
|
||||
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}}",
|
||||
model: "openai/gpt-5.2-mini",
|
||||
deliver: true,
|
||||
channel: "last",
|
||||
// to: "+15551234567"
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
If you want a fixed channel, set `channel` + `to`. Otherwise `channel: "last"`
|
||||
uses the last delivery route (falls back to WhatsApp).
|
||||
|
||||
To force a cheaper model for Gmail runs, set `model` in the mapping
|
||||
(`provider/model` or alias). If you enforce `agents.defaults.models`, include it there.
|
||||
|
||||
To set a default model and thinking level specifically for Gmail hooks, add
|
||||
`hooks.gmail.model` / `hooks.gmail.thinking` in your config:
|
||||
|
||||
```json5
|
||||
{
|
||||
hooks: {
|
||||
gmail: {
|
||||
model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
|
||||
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.
|
||||
- Gmail hook content is wrapped with external-content safety boundaries by default.
|
||||
To disable (dangerous), set `hooks.gmail.allowUnsafeExternalContent: true`.
|
||||
|
||||
To customize payload handling further, add `hooks.mappings` or a JS/TS transform module
|
||||
under `~/.openclaw/hooks/transforms` (see [Webhooks](/automation/webhook)).
|
||||
|
||||
## Wizard (recommended)
|
||||
|
||||
Use the OpenClaw helper to wire everything together (installs deps on macOS via brew):
|
||||
|
||||
```bash
|
||||
openclaw webhooks gmail setup \
|
||||
--account openclaw@gmail.com
|
||||
```
|
||||
|
||||
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"]`).
|
||||
|
||||
Path note: when `tailscale.mode` is enabled, OpenClaw automatically sets
|
||||
`hooks.gmail.serve.path` to `/` and keeps the public path at
|
||||
`hooks.gmail.tailscale.path` (default `/gmail-pubsub`) because Tailscale
|
||||
strips the set-path prefix before proxying.
|
||||
If you need the backend to receive the prefixed path, set
|
||||
`hooks.gmail.tailscale.target` (or `--tailscale-target`) to a full URL like
|
||||
`http://127.0.0.1:8788/gmail-pubsub` and match `hooks.gmail.serve.path`.
|
||||
|
||||
Want a custom endpoint? Use `--push-endpoint <url>` or `--tailscale off`.
|
||||
|
||||
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).
|
||||
- Do not run the manual daemon at the same time, or you will hit
|
||||
`listen tcp 127.0.0.1:8788: bind: address already in use`.
|
||||
|
||||
Manual daemon (starts `gog gmail watch serve` + auto-renew):
|
||||
|
||||
```bash
|
||||
openclaw webhooks gmail run
|
||||
```
|
||||
|
||||
## One-time setup
|
||||
|
||||
1. Select the GCP project **that owns the OAuth client** used by `gog`.
|
||||
|
||||
```bash
|
||||
gcloud auth login
|
||||
gcloud config set project <project-id>
|
||||
```
|
||||
|
||||
Note: Gmail watch requires the Pub/Sub topic to live in the same project as the OAuth client.
|
||||
|
||||
2. Enable APIs:
|
||||
|
||||
```bash
|
||||
gcloud services enable gmail.googleapis.com pubsub.googleapis.com
|
||||
```
|
||||
|
||||
3. Create a topic:
|
||||
|
||||
```bash
|
||||
gcloud pubsub topics create gog-gmail-watch
|
||||
```
|
||||
|
||||
4. Allow Gmail push to publish:
|
||||
|
||||
```bash
|
||||
gcloud pubsub topics add-iam-policy-binding gog-gmail-watch \
|
||||
--member=serviceAccount:gmail-api-push@system.gserviceaccount.com \
|
||||
--role=roles/pubsub.publisher
|
||||
```
|
||||
|
||||
## Start the watch
|
||||
|
||||
```bash
|
||||
gog gmail watch start \
|
||||
--account openclaw@gmail.com \
|
||||
--label INBOX \
|
||||
--topic projects/<project-id>/topics/gog-gmail-watch
|
||||
```
|
||||
|
||||
Save the `history_id` from the output (for debugging).
|
||||
|
||||
## Run the push handler
|
||||
|
||||
Local example (shared token auth):
|
||||
|
||||
```bash
|
||||
gog gmail watch serve \
|
||||
--account openclaw@gmail.com \
|
||||
--bind 127.0.0.1 \
|
||||
--port 8788 \
|
||||
--path /gmail-pubsub \
|
||||
--token <shared> \
|
||||
--hook-url http://127.0.0.1:18789/hooks/gmail \
|
||||
--hook-token OPENCLAW_HOOK_TOKEN \
|
||||
--include-body \
|
||||
--max-bytes 20000
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
Recommended: `openclaw webhooks gmail run` wraps the same flow and auto-renews the watch.
|
||||
|
||||
## Expose the handler (advanced, unsupported)
|
||||
|
||||
If you need a non-Tailscale tunnel, wire it manually and use the public URL in the push
|
||||
subscription (unsupported, no guardrails):
|
||||
|
||||
```bash
|
||||
cloudflared tunnel --url http://127.0.0.1:8788 --no-autoupdate
|
||||
```
|
||||
|
||||
Use the generated URL as the push endpoint:
|
||||
|
||||
```bash
|
||||
gcloud pubsub subscriptions create gog-gmail-watch-push \
|
||||
--topic gog-gmail-watch \
|
||||
--push-endpoint "https://<public-url>/gmail-pubsub?token=<shared>"
|
||||
```
|
||||
|
||||
Production: use a stable HTTPS endpoint and configure Pub/Sub OIDC JWT, then run:
|
||||
|
||||
```bash
|
||||
gog gmail watch serve --verify-oidc --oidc-email <svc@...>
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
Send a message to the watched inbox:
|
||||
|
||||
```bash
|
||||
gog gmail send \
|
||||
--account openclaw@gmail.com \
|
||||
--to openclaw@gmail.com \
|
||||
--subject "watch test" \
|
||||
--body "ping"
|
||||
```
|
||||
|
||||
Check watch state and history:
|
||||
|
||||
```bash
|
||||
gog gmail watch status --account openclaw@gmail.com
|
||||
gog gmail history --account openclaw@gmail.com --since <historyId>
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- `Invalid topicName`: project mismatch (topic not in the OAuth client project).
|
||||
- `User not authorized`: missing `roles/pubsub.publisher` on the topic.
|
||||
- Empty messages: Gmail push only provides `historyId`; fetch via `gog gmail history`.
|
||||
|
||||
## Cleanup
|
||||
|
||||
```bash
|
||||
gog gmail watch stop --account openclaw@gmail.com
|
||||
gcloud pubsub subscriptions delete gog-gmail-watch-push
|
||||
gcloud pubsub topics delete gog-gmail-watch
|
||||
```
|
||||
1003
openclaw/docs/automation/hooks.md
Normal file
69
openclaw/docs/automation/poll.md
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
summary: "Poll sending via gateway + CLI"
|
||||
read_when:
|
||||
- Adding or modifying poll support
|
||||
- Debugging poll sends from the CLI or gateway
|
||||
title: "Polls"
|
||||
---
|
||||
|
||||
# Polls
|
||||
|
||||
## Supported channels
|
||||
|
||||
- WhatsApp (web channel)
|
||||
- Discord
|
||||
- MS Teams (Adaptive Cards)
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
# WhatsApp
|
||||
openclaw message poll --target +15555550123 \
|
||||
--poll-question "Lunch today?" --poll-option "Yes" --poll-option "No" --poll-option "Maybe"
|
||||
openclaw message poll --target 123456789@g.us \
|
||||
--poll-question "Meeting time?" --poll-option "10am" --poll-option "2pm" --poll-option "4pm" --poll-multi
|
||||
|
||||
# Discord
|
||||
openclaw message poll --channel discord --target channel:123456789 \
|
||||
--poll-question "Snack?" --poll-option "Pizza" --poll-option "Sushi"
|
||||
openclaw message poll --channel discord --target channel:123456789 \
|
||||
--poll-question "Plan?" --poll-option "A" --poll-option "B" --poll-duration-hours 48
|
||||
|
||||
# MS Teams
|
||||
openclaw message poll --channel msteams --target conversation:19:abc@thread.tacv2 \
|
||||
--poll-question "Lunch?" --poll-option "Pizza" --poll-option "Sushi"
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `--channel`: `whatsapp` (default), `discord`, or `msteams`
|
||||
- `--poll-multi`: allow selecting multiple options
|
||||
- `--poll-duration-hours`: Discord-only (defaults to 24 when omitted)
|
||||
|
||||
## Gateway RPC
|
||||
|
||||
Method: `poll`
|
||||
|
||||
Params:
|
||||
|
||||
- `to` (string, required)
|
||||
- `question` (string, required)
|
||||
- `options` (string[], required)
|
||||
- `maxSelections` (number, optional)
|
||||
- `durationHours` (number, optional)
|
||||
- `channel` (string, optional, default: `whatsapp`)
|
||||
- `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.
|
||||
Teams polls are rendered as Adaptive Cards and require the gateway to stay online
|
||||
to record votes in `~/.openclaw/msteams-polls.json`.
|
||||
122
openclaw/docs/automation/troubleshooting.md
Normal file
@@ -0,0 +1,122 @@
|
||||
---
|
||||
summary: "Troubleshoot cron and heartbeat scheduling and delivery"
|
||||
read_when:
|
||||
- Cron did not run
|
||||
- Cron ran but no message was delivered
|
||||
- Heartbeat seems silent or skipped
|
||||
title: "Automation Troubleshooting"
|
||||
---
|
||||
|
||||
# Automation troubleshooting
|
||||
|
||||
Use this page for scheduler and delivery issues (`cron` + `heartbeat`).
|
||||
|
||||
## Command ladder
|
||||
|
||||
```bash
|
||||
openclaw status
|
||||
openclaw gateway status
|
||||
openclaw logs --follow
|
||||
openclaw doctor
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
Then run automation checks:
|
||||
|
||||
```bash
|
||||
openclaw cron status
|
||||
openclaw cron list
|
||||
openclaw system heartbeat last
|
||||
```
|
||||
|
||||
## Cron not firing
|
||||
|
||||
```bash
|
||||
openclaw cron status
|
||||
openclaw cron list
|
||||
openclaw cron runs --id <jobId> --limit 20
|
||||
openclaw logs --follow
|
||||
```
|
||||
|
||||
Good output looks like:
|
||||
|
||||
- `cron status` reports enabled and a future `nextWakeAtMs`.
|
||||
- Job is enabled and has a valid schedule/timezone.
|
||||
- `cron runs` shows `ok` or explicit skip reason.
|
||||
|
||||
Common signatures:
|
||||
|
||||
- `cron: scheduler disabled; jobs will not run automatically` → cron disabled in config/env.
|
||||
- `cron: timer tick failed` → scheduler tick crashed; inspect surrounding stack/log context.
|
||||
- `reason: not-due` in run output → manual run called without `--force` and job not due yet.
|
||||
|
||||
## Cron fired but no delivery
|
||||
|
||||
```bash
|
||||
openclaw cron runs --id <jobId> --limit 20
|
||||
openclaw cron list
|
||||
openclaw channels status --probe
|
||||
openclaw logs --follow
|
||||
```
|
||||
|
||||
Good output looks like:
|
||||
|
||||
- Run status is `ok`.
|
||||
- Delivery mode/target are set for isolated jobs.
|
||||
- Channel probe reports target channel connected.
|
||||
|
||||
Common signatures:
|
||||
|
||||
- Run succeeded but delivery mode is `none` → no external message is expected.
|
||||
- Delivery target missing/invalid (`channel`/`to`) → run may succeed internally but skip outbound.
|
||||
- Channel auth errors (`unauthorized`, `missing_scope`, `Forbidden`) → delivery blocked by channel credentials/permissions.
|
||||
|
||||
## Heartbeat suppressed or skipped
|
||||
|
||||
```bash
|
||||
openclaw system heartbeat last
|
||||
openclaw logs --follow
|
||||
openclaw config get agents.defaults.heartbeat
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
Good output looks like:
|
||||
|
||||
- Heartbeat enabled with non-zero interval.
|
||||
- Last heartbeat result is `ran` (or skip reason is understood).
|
||||
|
||||
Common signatures:
|
||||
|
||||
- `heartbeat skipped` with `reason=quiet-hours` → outside `activeHours`.
|
||||
- `requests-in-flight` → main lane busy; heartbeat deferred.
|
||||
- `empty-heartbeat-file` → interval heartbeat skipped because `HEARTBEAT.md` has no actionable content and no tagged cron event is queued.
|
||||
- `alerts-disabled` → visibility settings suppress outbound heartbeat messages.
|
||||
|
||||
## Timezone and activeHours gotchas
|
||||
|
||||
```bash
|
||||
openclaw config get agents.defaults.heartbeat.activeHours
|
||||
openclaw config get agents.defaults.heartbeat.activeHours.timezone
|
||||
openclaw config get agents.defaults.userTimezone || echo "agents.defaults.userTimezone not set"
|
||||
openclaw cron list
|
||||
openclaw logs --follow
|
||||
```
|
||||
|
||||
Quick rules:
|
||||
|
||||
- `Config path not found: agents.defaults.userTimezone` means the key is unset; heartbeat falls back to host timezone (or `activeHours.timezone` if set).
|
||||
- Cron without `--tz` uses gateway host timezone.
|
||||
- Heartbeat `activeHours` uses configured timezone resolution (`user`, `local`, or explicit IANA tz).
|
||||
- ISO timestamps without timezone are treated as UTC for cron `at` schedules.
|
||||
|
||||
Common signatures:
|
||||
|
||||
- Jobs run at the wrong wall-clock time after host timezone changes.
|
||||
- Heartbeat always skipped during your daytime because `activeHours.timezone` is wrong.
|
||||
|
||||
Related:
|
||||
|
||||
- [/automation/cron-jobs](/automation/cron-jobs)
|
||||
- [/gateway/heartbeat](/gateway/heartbeat)
|
||||
- [/automation/cron-vs-heartbeat](/automation/cron-vs-heartbeat)
|
||||
- [/concepts/timezone](/concepts/timezone)
|
||||
215
openclaw/docs/automation/webhook.md
Normal file
@@ -0,0 +1,215 @@
|
||||
---
|
||||
summary: "Webhook ingress for wake and isolated agent runs"
|
||||
read_when:
|
||||
- Adding or changing webhook endpoints
|
||||
- Wiring external systems into OpenClaw
|
||||
title: "Webhooks"
|
||||
---
|
||||
|
||||
# Webhooks
|
||||
|
||||
Gateway can expose a small HTTP webhook endpoint for external triggers.
|
||||
|
||||
## Enable
|
||||
|
||||
```json5
|
||||
{
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "shared-secret",
|
||||
path: "/hooks",
|
||||
// Optional: restrict explicit `agentId` routing to this allowlist.
|
||||
// Omit or include "*" to allow any agent.
|
||||
// Set [] to deny all explicit `agentId` routing.
|
||||
allowedAgentIds: ["hooks", "main"],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
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 <token>` (recommended)
|
||||
- `x-openclaw-token: <token>`
|
||||
- Query-string tokens are rejected (`?token=...` returns `400`).
|
||||
|
||||
## Endpoints
|
||||
|
||||
### `POST /hooks/wake`
|
||||
|
||||
Payload:
|
||||
|
||||
```json
|
||||
{ "text": "System line", "mode": "now" }
|
||||
```
|
||||
|
||||
- `text` **required** (string): The description of the event (e.g., "New email received").
|
||||
- `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",
|
||||
"name": "Email",
|
||||
"agentId": "hooks",
|
||||
"sessionKey": "hook:email:msg-123",
|
||||
"wakeMode": "now",
|
||||
"deliver": true,
|
||||
"channel": "last",
|
||||
"to": "+15551234567",
|
||||
"model": "openai/gpt-5.2-mini",
|
||||
"thinking": "low",
|
||||
"timeoutSeconds": 120
|
||||
}
|
||||
```
|
||||
|
||||
- `message` **required** (string): The prompt or message for the agent to process.
|
||||
- `name` optional (string): Human-readable name for the hook (e.g., "GitHub"), used as a prefix in session summaries.
|
||||
- `agentId` optional (string): Route this hook to a specific agent. Unknown IDs fall back to the default agent. When set, the hook runs using the resolved agent's workspace and configuration.
|
||||
- `sessionKey` optional (string): The key used to identify the agent's session. By default this field is rejected unless `hooks.allowRequestSessionKey=true`.
|
||||
- `wakeMode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
|
||||
- `deliver` optional (boolean): If `true`, the agent's response will be sent to the messaging channel. Defaults to `true`. Responses that are only heartbeat acknowledgments are automatically skipped.
|
||||
- `channel` optional (string): The messaging channel for delivery. One of: `last`, `whatsapp`, `telegram`, `discord`, `slack`, `mattermost` (plugin), `signal`, `imessage`, `msteams`. Defaults to `last`.
|
||||
- `to` optional (string): The recipient identifier for the channel (e.g., phone number for WhatsApp/Signal, chat ID for Telegram, channel ID for Discord/Slack/Mattermost (plugin), conversation ID for MS Teams). Defaults to the last recipient in the main session.
|
||||
- `model` optional (string): Model override (e.g., `anthropic/claude-3-5-sonnet` or an alias). Must be in the allowed model list if restricted.
|
||||
- `thinking` optional (string): Thinking level override (e.g., `low`, `medium`, `high`).
|
||||
- `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
|
||||
|
||||
## Session key policy (breaking change)
|
||||
|
||||
`/hooks/agent` payload `sessionKey` overrides are disabled by default.
|
||||
|
||||
- Recommended: set a fixed `hooks.defaultSessionKey` and keep request overrides off.
|
||||
- Optional: allow request overrides only when needed, and restrict prefixes.
|
||||
|
||||
Recommended config:
|
||||
|
||||
```json5
|
||||
{
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "${OPENCLAW_HOOKS_TOKEN}",
|
||||
defaultSessionKey: "hook:ingress",
|
||||
allowRequestSessionKey: false,
|
||||
allowedSessionKeyPrefixes: ["hook:"],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Compatibility config (legacy behavior):
|
||||
|
||||
```json5
|
||||
{
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "${OPENCLAW_HOOKS_TOKEN}",
|
||||
allowRequestSessionKey: true,
|
||||
allowedSessionKeyPrefixes: ["hook:"], // strongly recommended
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /hooks/<name>` (mapped)
|
||||
|
||||
Custom hook names are resolved via `hooks.mappings` (see configuration). A mapping can
|
||||
turn arbitrary payloads into `wake` or `agent` actions, with optional templates or
|
||||
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.
|
||||
- `hooks.transformsDir` (if set) must stay within the transforms root under your OpenClaw config directory (typically `~/.openclaw/hooks/transforms`).
|
||||
- `transform.module` must resolve within the effective transforms directory (traversal/escape paths are rejected).
|
||||
- Use `match.source` to keep a generic ingest endpoint (payload-driven routing).
|
||||
- TS transforms require a TS loader (e.g. `bun` or `tsx`) or precompiled `.js` at runtime.
|
||||
- Set `deliver: true` + `channel`/`to` on mappings to route replies to a chat surface
|
||||
(`channel` defaults to `last` and falls back to WhatsApp).
|
||||
- `agentId` routes the hook to a specific agent; unknown IDs fall back to the default agent.
|
||||
- `hooks.allowedAgentIds` restricts explicit `agentId` routing. Omit it (or include `*`) to allow any agent. Set `[]` to deny explicit `agentId` routing.
|
||||
- `hooks.defaultSessionKey` sets the default session for hook agent runs when no explicit key is provided.
|
||||
- `hooks.allowRequestSessionKey` controls whether `/hooks/agent` payloads may set `sessionKey` (default: `false`).
|
||||
- `hooks.allowedSessionKeyPrefixes` optionally restricts explicit `sessionKey` values from request payloads and mappings.
|
||||
- `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.
|
||||
|
||||
## Responses
|
||||
|
||||
- `200` for `/hooks/wake`
|
||||
- `202` for `/hooks/agent` (async run started)
|
||||
- `401` on auth failure
|
||||
- `429` after repeated auth failures from the same client (check `Retry-After`)
|
||||
- `400` on invalid payload
|
||||
- `413` on oversized payloads
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:18789/hooks/wake \
|
||||
-H 'Authorization: Bearer SECRET' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"text":"New email received","mode":"now"}'
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:18789/hooks/agent \
|
||||
-H 'x-openclaw-token: SECRET' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"message":"Summarize inbox","name":"Email","wakeMode":"next-heartbeat"}'
|
||||
```
|
||||
|
||||
### Use a different model
|
||||
|
||||
Add `model` to the agent payload (or mapping) to override the model for that run:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:18789/hooks/agent \
|
||||
-H 'x-openclaw-token: SECRET' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"message":"Summarize inbox","name":"Email","model":"openai/gpt-5.2-mini"}'
|
||||
```
|
||||
|
||||
If you enforce `agents.defaults.models`, make sure the override model is included there.
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:18789/hooks/gmail \
|
||||
-H 'Authorization: Bearer SECRET' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"source":"gmail","messages":[{"from":"Ada","subject":"Hello","snippet":"Hi"}]}'
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
- Keep hook endpoints behind loopback, tailnet, or trusted reverse proxy.
|
||||
- Use a dedicated hook token; do not reuse gateway auth tokens.
|
||||
- Repeated auth failures are rate-limited per client address to slow brute-force attempts.
|
||||
- If you use multi-agent routing, set `hooks.allowedAgentIds` to limit explicit `agentId` selection.
|
||||
- Keep `hooks.allowRequestSessionKey=false` unless you require caller-selected sessions.
|
||||
- If you enable request `sessionKey`, restrict `hooks.allowedSessionKeyPrefixes` (for example, `["hook:"]`).
|
||||
- Avoid including sensitive raw payloads in webhook logs.
|
||||
- Hook payloads are treated as untrusted and wrapped with safety boundaries by default.
|
||||
If you must disable this for a specific hook, set `allowUnsafeExternalContent: true`
|
||||
in that hook's mapping (dangerous).
|
||||
41
openclaw/docs/brave-search.md
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
summary: "Brave Search API setup for web_search"
|
||||
read_when:
|
||||
- You want to use Brave Search for web_search
|
||||
- You need a BRAVE_API_KEY or plan details
|
||||
title: "Brave Search"
|
||||
---
|
||||
|
||||
# Brave Search API
|
||||
|
||||
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/](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
|
||||
|
||||
```json5
|
||||
{
|
||||
tools: {
|
||||
web: {
|
||||
search: {
|
||||
provider: "brave",
|
||||
apiKey: "BRAVE_API_KEY_HERE",
|
||||
maxResults: 5,
|
||||
timeoutSeconds: 30,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The Data for AI plan is **not** compatible with `web_search`.
|
||||
- Brave provides a free tier plus paid plans; check the Brave API portal for current limits.
|
||||
|
||||
See [Web tools](/tools/web) for the full web_search configuration.
|
||||
346
openclaw/docs/channels/bluebubbles.md
Normal file
@@ -0,0 +1,346 @@
|
||||
---
|
||||
summary: "iMessage via BlueBubbles macOS server (REST send/receive, typing, reactions, pairing, advanced actions)."
|
||||
read_when:
|
||||
- Setting up BlueBubbles channel
|
||||
- Troubleshooting webhook pairing
|
||||
- Configuring iMessage on macOS
|
||||
title: "BlueBubbles"
|
||||
---
|
||||
|
||||
# BlueBubbles (macOS REST)
|
||||
|
||||
Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **Recommended for iMessage integration** due to its richer API and easier setup compared to the legacy imsg channel.
|
||||
|
||||
## Overview
|
||||
|
||||
- Runs on macOS via the BlueBubbles helper app ([bluebubbles.app](https://bluebubbles.app)).
|
||||
- Recommended/tested: macOS Sequoia (15). macOS Tahoe (26) works; edit is currently broken on Tahoe, and group icon updates may report success but not sync.
|
||||
- OpenClaw talks to it through its REST API (`GET /api/v1/ping`, `POST /message/text`, `POST /chat/:id/*`).
|
||||
- Incoming messages arrive via webhooks; outgoing replies, typing indicators, read receipts, and tapbacks are REST calls.
|
||||
- Attachments and stickers are ingested as inbound media (and surfaced to the agent when possible).
|
||||
- Pairing/allowlist works the same way as other channels (`/channels/pairing` etc) with `channels.bluebubbles.allowFrom` + pairing codes.
|
||||
- Reactions are surfaced as system events just like Slack/Telegram so agents can "mention" them before replying.
|
||||
- Advanced features: edit, unsend, reply threading, message effects, group management.
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Install the BlueBubbles server on your Mac (follow the instructions at [bluebubbles.app/install](https://bluebubbles.app/install)).
|
||||
2. In the BlueBubbles config, enable the web API and set a password.
|
||||
3. Run `openclaw onboard` and select BlueBubbles, or configure manually:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
enabled: true,
|
||||
serverUrl: "http://192.168.1.100:1234",
|
||||
password: "example-password",
|
||||
webhookPath: "/bluebubbles-webhook",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
4. Point BlueBubbles webhooks to your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=<password>`).
|
||||
5. Start the gateway; it will register the webhook handler and start pairing.
|
||||
|
||||
Security note:
|
||||
|
||||
- Always set a webhook password.
|
||||
- Webhook authentication is always required. OpenClaw rejects BlueBubbles webhook requests unless they include a password/guid that matches `channels.bluebubbles.password` (for example `?password=<password>` or `x-password`), regardless of loopback/proxy topology.
|
||||
|
||||
## Keeping Messages.app alive (VM / headless setups)
|
||||
|
||||
Some macOS VM / always-on setups can end up with Messages.app going “idle” (incoming events stop until the app is opened/foregrounded). A simple workaround is to **poke Messages every 5 minutes** using an AppleScript + LaunchAgent.
|
||||
|
||||
### 1) Save the AppleScript
|
||||
|
||||
Save this as:
|
||||
|
||||
- `~/Scripts/poke-messages.scpt`
|
||||
|
||||
Example script (non-interactive; does not steal focus):
|
||||
|
||||
```applescript
|
||||
try
|
||||
tell application "Messages"
|
||||
if not running then
|
||||
launch
|
||||
end if
|
||||
|
||||
-- Touch the scripting interface to keep the process responsive.
|
||||
set _chatCount to (count of chats)
|
||||
end tell
|
||||
on error
|
||||
-- Ignore transient failures (first-run prompts, locked session, etc).
|
||||
end try
|
||||
```
|
||||
|
||||
### 2) Install a LaunchAgent
|
||||
|
||||
Save this as:
|
||||
|
||||
- `~/Library/LaunchAgents/com.user.poke-messages.plist`
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.user.poke-messages</string>
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/bin/bash</string>
|
||||
<string>-lc</string>
|
||||
<string>/usr/bin/osascript "$HOME/Scripts/poke-messages.scpt"</string>
|
||||
</array>
|
||||
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
|
||||
<key>StartInterval</key>
|
||||
<integer>300</integer>
|
||||
|
||||
<key>StandardOutPath</key>
|
||||
<string>/tmp/poke-messages.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/tmp/poke-messages.err</string>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- This runs **every 300 seconds** and **on login**.
|
||||
- The first run may trigger macOS **Automation** prompts (`osascript` → Messages). Approve them in the same user session that runs the LaunchAgent.
|
||||
|
||||
Load it:
|
||||
|
||||
```bash
|
||||
launchctl unload ~/Library/LaunchAgents/com.user.poke-messages.plist 2>/dev/null || true
|
||||
launchctl load ~/Library/LaunchAgents/com.user.poke-messages.plist
|
||||
```
|
||||
|
||||
## Onboarding
|
||||
|
||||
BlueBubbles is available in 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`
|
||||
- **DM policy**: pairing, allowlist, open, or disabled
|
||||
- **Allow list**: Phone numbers, emails, or chat targets
|
||||
|
||||
You can also add BlueBubbles via CLI:
|
||||
|
||||
```
|
||||
openclaw channels add bluebubbles --http-url http://192.168.1.100:1234 --password <password>
|
||||
```
|
||||
|
||||
## Access control (DMs + groups)
|
||||
|
||||
DMs:
|
||||
|
||||
- Default: `channels.bluebubbles.dmPolicy = "pairing"`.
|
||||
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
- `openclaw pairing list bluebubbles`
|
||||
- `openclaw pairing approve bluebubbles <CODE>`
|
||||
- Pairing is the default token exchange. Details: [Pairing](/channels/pairing)
|
||||
|
||||
Groups:
|
||||
|
||||
- `channels.bluebubbles.groupPolicy = open | allowlist | disabled` (default: `allowlist`).
|
||||
- `channels.bluebubbles.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
||||
|
||||
### Mention gating (groups)
|
||||
|
||||
BlueBubbles supports mention gating for group chats, matching iMessage/WhatsApp behavior:
|
||||
|
||||
- Uses `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) to detect mentions.
|
||||
- When `requireMention` is enabled for a group, the agent only responds when mentioned.
|
||||
- Control commands from authorized senders bypass mention gating.
|
||||
|
||||
Per-group configuration:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15555550123"],
|
||||
groups: {
|
||||
"*": { requireMention: true }, // default for all groups
|
||||
"iMessage;-;chat123": { requireMention: false }, // override for specific group
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Command gating
|
||||
|
||||
- Control commands (e.g., `/config`, `/model`) require authorization.
|
||||
- Uses `allowFrom` and `groupAllowFrom` to determine command authorization.
|
||||
- Authorized senders can run control commands even without mentioning in groups.
|
||||
|
||||
## Typing + read receipts
|
||||
|
||||
- **Typing indicators**: Sent automatically before and during response generation.
|
||||
- **Read receipts**: Controlled by `channels.bluebubbles.sendReadReceipts` (default: `true`).
|
||||
- **Typing indicators**: OpenClaw sends typing start events; BlueBubbles clears typing automatically on send or timeout (manual stop via DELETE is unreliable).
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
sendReadReceipts: false, // disable read receipts
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced actions
|
||||
|
||||
BlueBubbles supports advanced message actions when enabled in config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
actions: {
|
||||
reactions: true, // tapbacks (default: true)
|
||||
edit: true, // edit sent messages (macOS 13+, broken on macOS 26 Tahoe)
|
||||
unsend: true, // unsend messages (macOS 13+)
|
||||
reply: true, // reply threading by message GUID
|
||||
sendWithEffect: true, // message effects (slam, loud, etc.)
|
||||
renameGroup: true, // rename group chats
|
||||
setGroupIcon: true, // set group chat icon/photo (flaky on macOS 26 Tahoe)
|
||||
addParticipant: true, // add participants to groups
|
||||
removeParticipant: true, // remove participants from groups
|
||||
leaveGroup: true, // leave group chats
|
||||
sendAttachment: true, // send attachments/media
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Available actions:
|
||||
|
||||
- **react**: Add/remove tapback reactions (`messageId`, `emoji`, `remove`)
|
||||
- **edit**: Edit a sent message (`messageId`, `text`)
|
||||
- **unsend**: Unsend a message (`messageId`)
|
||||
- **reply**: Reply to a specific message (`messageId`, `text`, `to`)
|
||||
- **sendWithEffect**: Send with iMessage effect (`text`, `to`, `effectId`)
|
||||
- **renameGroup**: Rename a group chat (`chatGuid`, `displayName`)
|
||||
- **setGroupIcon**: Set a group chat's icon/photo (`chatGuid`, `media`) — flaky on macOS 26 Tahoe (API may return success but the icon does not sync).
|
||||
- **addParticipant**: Add someone to a group (`chatGuid`, `address`)
|
||||
- **removeParticipant**: Remove someone from a group (`chatGuid`, `address`)
|
||||
- **leaveGroup**: Leave a group chat (`chatGuid`)
|
||||
- **sendAttachment**: Send media/files (`to`, `buffer`, `filename`, `asVoice`)
|
||||
- Voice memos: set `asVoice: true` with **MP3** or **CAF** audio to send as an iMessage voice message. BlueBubbles converts MP3 → CAF when sending voice memos.
|
||||
|
||||
### Message IDs (short vs full)
|
||||
|
||||
OpenClaw may surface _short_ message IDs (e.g., `1`, `2`) to save tokens.
|
||||
|
||||
- `MessageSid` / `ReplyToId` can be short IDs.
|
||||
- `MessageSidFull` / `ReplyToIdFull` contain the provider full IDs.
|
||||
- Short IDs are in-memory; they can expire on restart or cache eviction.
|
||||
- Actions accept short or full `messageId`, but short IDs will error if no longer available.
|
||||
|
||||
Use full IDs for durable automations and storage:
|
||||
|
||||
- Templates: `{{MessageSidFull}}`, `{{ReplyToIdFull}}`
|
||||
- Context: `MessageSidFull` / `ReplyToIdFull` in inbound payloads
|
||||
|
||||
See [Configuration](/gateway/configuration) for template variables.
|
||||
|
||||
## Block streaming
|
||||
|
||||
Control whether responses are sent as a single message or streamed in blocks:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
blockStreaming: true, // enable block streaming (off by default)
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Media + limits
|
||||
|
||||
- Inbound attachments are downloaded and stored in the media cache.
|
||||
- Media cap via `channels.bluebubbles.mediaMaxMb` (default: 8 MB).
|
||||
- Outbound text is chunked to `channels.bluebubbles.textChunkLimit` (default: 4000 chars).
|
||||
|
||||
## Configuration reference
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
|
||||
- `channels.bluebubbles.enabled`: Enable/disable the channel.
|
||||
- `channels.bluebubbles.serverUrl`: BlueBubbles REST API base URL.
|
||||
- `channels.bluebubbles.password`: API password.
|
||||
- `channels.bluebubbles.webhookPath`: Webhook endpoint path (default: `/bluebubbles-webhook`).
|
||||
- `channels.bluebubbles.dmPolicy`: `pairing | allowlist | open | disabled` (default: `pairing`).
|
||||
- `channels.bluebubbles.allowFrom`: DM allowlist (handles, emails, E.164 numbers, `chat_id:*`, `chat_guid:*`).
|
||||
- `channels.bluebubbles.groupPolicy`: `open | allowlist | disabled` (default: `allowlist`).
|
||||
- `channels.bluebubbles.groupAllowFrom`: Group sender allowlist.
|
||||
- `channels.bluebubbles.groups`: Per-group config (`requireMention`, etc.).
|
||||
- `channels.bluebubbles.sendReadReceipts`: Send read receipts (default: `true`).
|
||||
- `channels.bluebubbles.blockStreaming`: Enable block streaming (default: `false`; required for streaming replies).
|
||||
- `channels.bluebubbles.textChunkLimit`: Outbound chunk size in chars (default: 4000).
|
||||
- `channels.bluebubbles.chunkMode`: `length` (default) splits only when exceeding `textChunkLimit`; `newline` splits on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.bluebubbles.mediaMaxMb`: Inbound media cap in MB (default: 8).
|
||||
- `channels.bluebubbles.mediaLocalRoots`: Explicit allowlist of absolute local directories permitted for outbound local media paths. Local path sends are denied by default unless this is configured. Per-account override: `channels.bluebubbles.accounts.<accountId>.mediaLocalRoots`.
|
||||
- `channels.bluebubbles.historyLimit`: Max group messages for context (0 disables).
|
||||
- `channels.bluebubbles.dmHistoryLimit`: DM history limit.
|
||||
- `channels.bluebubbles.actions`: Enable/disable specific actions.
|
||||
- `channels.bluebubbles.accounts`: Multi-account configuration.
|
||||
|
||||
Related global options:
|
||||
|
||||
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`).
|
||||
- `messages.responsePrefix`.
|
||||
|
||||
## Addressing / delivery targets
|
||||
|
||||
Prefer `chat_guid` for stable routing:
|
||||
|
||||
- `chat_guid:iMessage;-;+15555550123` (preferred for groups)
|
||||
- `chat_id:123`
|
||||
- `chat_identifier:...`
|
||||
- Direct handles: `+15555550123`, `user@example.com`
|
||||
- If a direct handle does not have an existing DM chat, OpenClaw will create one via `POST /api/v1/chat/new`. This requires the BlueBubbles Private API to be enabled.
|
||||
|
||||
## Security
|
||||
|
||||
- Webhook requests are authenticated by comparing `guid`/`password` query params or headers against `channels.bluebubbles.password`. 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 <code>`.
|
||||
- Reactions require the BlueBubbles private API (`POST /api/v1/message/react`); ensure the server version exposes it.
|
||||
- Edit/unsend require macOS 13+ and a compatible BlueBubbles server version. On macOS 26 (Tahoe), edit is currently broken due to private API changes.
|
||||
- Group icon updates can be flaky on macOS 26 (Tahoe): the API may return success but the new icon does not sync.
|
||||
- OpenClaw auto-hides known-broken actions based on the BlueBubbles server's macOS version. If edit still appears on macOS 26 (Tahoe), disable it manually with `channels.bluebubbles.actions.edit=false`.
|
||||
- For status/health info: `openclaw status --all` or `openclaw status --deep`.
|
||||
|
||||
For general channel workflow reference, see [Channels](/channels) and the [Plugins](/tools/plugin) guide.
|
||||
442
openclaw/docs/channels/broadcast-groups.md
Normal file
@@ -0,0 +1,442 @@
|
||||
---
|
||||
summary: "Broadcast a WhatsApp message to multiple agents"
|
||||
read_when:
|
||||
- Configuring broadcast groups
|
||||
- Debugging multi-agent replies in WhatsApp
|
||||
status: experimental
|
||||
title: "Broadcast Groups"
|
||||
---
|
||||
|
||||
# Broadcast Groups
|
||||
|
||||
**Status:** Experimental
|
||||
**Version:** Added in 2026.1.9
|
||||
|
||||
## Overview
|
||||
|
||||
Broadcast Groups enable multiple agents to process and respond to the same message simultaneously. This allows you to create specialized agent teams that work together in a single WhatsApp group or DM — all using one phone number.
|
||||
|
||||
Current scope: **WhatsApp only** (web channel).
|
||||
|
||||
Broadcast groups are evaluated after channel allowlists and group activation rules. In WhatsApp groups, this means broadcasts happen when OpenClaw would normally reply (for example: on mention, depending on your group settings).
|
||||
|
||||
## Use Cases
|
||||
|
||||
### 1. Specialized Agent Teams
|
||||
|
||||
Deploy multiple agents with atomic, focused responsibilities:
|
||||
|
||||
```
|
||||
Group: "Development Team"
|
||||
Agents:
|
||||
- CodeReviewer (reviews code snippets)
|
||||
- DocumentationBot (generates docs)
|
||||
- SecurityAuditor (checks for vulnerabilities)
|
||||
- TestGenerator (suggests test cases)
|
||||
```
|
||||
|
||||
Each agent processes the same message and provides its specialized perspective.
|
||||
|
||||
### 2. Multi-Language Support
|
||||
|
||||
```
|
||||
Group: "International Support"
|
||||
Agents:
|
||||
- Agent_EN (responds in English)
|
||||
- Agent_DE (responds in German)
|
||||
- Agent_ES (responds in Spanish)
|
||||
```
|
||||
|
||||
### 3. Quality Assurance Workflows
|
||||
|
||||
```
|
||||
Group: "Customer Support"
|
||||
Agents:
|
||||
- SupportAgent (provides answer)
|
||||
- QAAgent (reviews quality, only responds if issues found)
|
||||
```
|
||||
|
||||
### 4. Task Automation
|
||||
|
||||
```
|
||||
Group: "Project Management"
|
||||
Agents:
|
||||
- TaskTracker (updates task database)
|
||||
- TimeLogger (logs time spent)
|
||||
- ReportGenerator (creates summaries)
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Basic Setup
|
||||
|
||||
Add a top-level `broadcast` section (next to `bindings`). Keys are WhatsApp peer ids:
|
||||
|
||||
- group chats: group JID (e.g. `120363403215116621@g.us`)
|
||||
- DMs: E.164 phone number (e.g. `+15551234567`)
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"120363403215116621@g.us": ["alfred", "baerbel", "assistant3"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Result:** When OpenClaw would reply in this chat, it will run all three agents.
|
||||
|
||||
### Processing Strategy
|
||||
|
||||
Control how agents process messages:
|
||||
|
||||
#### Parallel (Default)
|
||||
|
||||
All agents process simultaneously:
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"strategy": "parallel",
|
||||
"120363403215116621@g.us": ["alfred", "baerbel"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Sequential
|
||||
|
||||
Agents process in order (one waits for previous to finish):
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"strategy": "sequential",
|
||||
"120363403215116621@g.us": ["alfred", "baerbel"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Complete Example
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"list": [
|
||||
{
|
||||
"id": "code-reviewer",
|
||||
"name": "Code Reviewer",
|
||||
"workspace": "/path/to/code-reviewer",
|
||||
"sandbox": { "mode": "all" }
|
||||
},
|
||||
{
|
||||
"id": "security-auditor",
|
||||
"name": "Security Auditor",
|
||||
"workspace": "/path/to/security-auditor",
|
||||
"sandbox": { "mode": "all" }
|
||||
},
|
||||
{
|
||||
"id": "docs-generator",
|
||||
"name": "Documentation Generator",
|
||||
"workspace": "/path/to/docs-generator",
|
||||
"sandbox": { "mode": "all" }
|
||||
}
|
||||
]
|
||||
},
|
||||
"broadcast": {
|
||||
"strategy": "parallel",
|
||||
"120363403215116621@g.us": ["code-reviewer", "security-auditor", "docs-generator"],
|
||||
"120363424282127706@g.us": ["support-en", "support-de"],
|
||||
"+15555550123": ["assistant", "logger"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### Message Flow
|
||||
|
||||
1. **Incoming message** arrives in a WhatsApp group
|
||||
2. **Broadcast check**: System checks if peer ID is in `broadcast`
|
||||
3. **If in broadcast list**:
|
||||
- All listed agents process the message
|
||||
- Each agent has its own session key and isolated context
|
||||
- Agents process in parallel (default) or sequentially
|
||||
4. **If not in broadcast list**:
|
||||
- Normal routing applies (first matching binding)
|
||||
|
||||
Note: broadcast groups do not bypass channel allowlists or group activation rules (mentions/commands/etc). They only change _which agents run_ when a message is eligible for processing.
|
||||
|
||||
### Session Isolation
|
||||
|
||||
Each agent in a broadcast group maintains completely separate:
|
||||
|
||||
- **Session keys** (`agent:alfred:whatsapp:group:120363...` vs `agent:baerbel:whatsapp:group:120363...`)
|
||||
- **Conversation history** (agent doesn't see other agents' messages)
|
||||
- **Workspace** (separate sandboxes if configured)
|
||||
- **Tool access** (different allow/deny lists)
|
||||
- **Memory/context** (separate IDENTITY.md, SOUL.md, etc.)
|
||||
- **Group context buffer** (recent group messages used for context) is shared per peer, so all broadcast agents see the same context when triggered
|
||||
|
||||
This allows each agent to have:
|
||||
|
||||
- Different personalities
|
||||
- Different tool access (e.g., read-only vs. read-write)
|
||||
- Different models (e.g., opus vs. sonnet)
|
||||
- Different skills installed
|
||||
|
||||
### Example: Isolated Sessions
|
||||
|
||||
In group `120363403215116621@g.us` with agents `["alfred", "baerbel"]`:
|
||||
|
||||
**Alfred's context:**
|
||||
|
||||
```
|
||||
Session: agent:alfred:whatsapp:group:120363403215116621@g.us
|
||||
History: [user message, alfred's previous responses]
|
||||
Workspace: /Users/pascal/openclaw-alfred/
|
||||
Tools: read, write, exec
|
||||
```
|
||||
|
||||
**Bärbel's context:**
|
||||
|
||||
```
|
||||
Session: agent:baerbel:whatsapp:group:120363403215116621@g.us
|
||||
History: [user message, baerbel's previous responses]
|
||||
Workspace: /Users/pascal/openclaw-baerbel/
|
||||
Tools: read only
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Keep Agents Focused
|
||||
|
||||
Design each agent with a single, clear responsibility:
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"DEV_GROUP": ["formatter", "linter", "tester"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
✅ **Good:** Each agent has one job
|
||||
❌ **Bad:** One generic "dev-helper" agent
|
||||
|
||||
### 2. Use Descriptive Names
|
||||
|
||||
Make it clear what each agent does:
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"security-scanner": { "name": "Security Scanner" },
|
||||
"code-formatter": { "name": "Code Formatter" },
|
||||
"test-generator": { "name": "Test Generator" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Configure Different Tool Access
|
||||
|
||||
Give agents only the tools they need:
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"reviewer": {
|
||||
"tools": { "allow": ["read", "exec"] } // Read-only
|
||||
},
|
||||
"fixer": {
|
||||
"tools": { "allow": ["read", "write", "edit", "exec"] } // Read-write
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Monitor Performance
|
||||
|
||||
With many agents, consider:
|
||||
|
||||
- Using `"strategy": "parallel"` (default) for speed
|
||||
- Limiting broadcast groups to 5-10 agents
|
||||
- Using faster models for simpler agents
|
||||
|
||||
### 5. Handle Failures Gracefully
|
||||
|
||||
Agents fail independently. One agent's error doesn't block others:
|
||||
|
||||
```
|
||||
Message → [Agent A ✓, Agent B ✗ error, Agent C ✓]
|
||||
Result: Agent A and C respond, Agent B logs error
|
||||
```
|
||||
|
||||
## Compatibility
|
||||
|
||||
### Providers
|
||||
|
||||
Broadcast groups currently work with:
|
||||
|
||||
- ✅ WhatsApp (implemented)
|
||||
- 🚧 Telegram (planned)
|
||||
- 🚧 Discord (planned)
|
||||
- 🚧 Slack (planned)
|
||||
|
||||
### Routing
|
||||
|
||||
Broadcast groups work alongside existing routing:
|
||||
|
||||
```json
|
||||
{
|
||||
"bindings": [
|
||||
{
|
||||
"match": { "channel": "whatsapp", "peer": { "kind": "group", "id": "GROUP_A" } },
|
||||
"agentId": "alfred"
|
||||
}
|
||||
],
|
||||
"broadcast": {
|
||||
"GROUP_B": ["agent1", "agent2"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `GROUP_A`: Only alfred responds (normal routing)
|
||||
- `GROUP_B`: agent1 AND agent2 respond (broadcast)
|
||||
|
||||
**Precedence:** `broadcast` takes priority over `bindings`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Agents Not Responding
|
||||
|
||||
**Check:**
|
||||
|
||||
1. Agent IDs exist in `agents.list`
|
||||
2. Peer ID format is correct (e.g., `120363403215116621@g.us`)
|
||||
3. Agents are not in deny lists
|
||||
|
||||
**Debug:**
|
||||
|
||||
```bash
|
||||
tail -f ~/.openclaw/logs/gateway.log | grep broadcast
|
||||
```
|
||||
|
||||
### Only One Agent Responding
|
||||
|
||||
**Cause:** Peer ID might be in `bindings` but not `broadcast`.
|
||||
|
||||
**Fix:** Add to broadcast config or remove from bindings.
|
||||
|
||||
### Performance Issues
|
||||
|
||||
**If slow with many agents:**
|
||||
|
||||
- Reduce number of agents per group
|
||||
- Use lighter models (sonnet instead of opus)
|
||||
- Check sandbox startup time
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Code Review Team
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"strategy": "parallel",
|
||||
"120363403215116621@g.us": [
|
||||
"code-formatter",
|
||||
"security-scanner",
|
||||
"test-coverage",
|
||||
"docs-checker"
|
||||
]
|
||||
},
|
||||
"agents": {
|
||||
"list": [
|
||||
{
|
||||
"id": "code-formatter",
|
||||
"workspace": "~/agents/formatter",
|
||||
"tools": { "allow": ["read", "write"] }
|
||||
},
|
||||
{
|
||||
"id": "security-scanner",
|
||||
"workspace": "~/agents/security",
|
||||
"tools": { "allow": ["read", "exec"] }
|
||||
},
|
||||
{
|
||||
"id": "test-coverage",
|
||||
"workspace": "~/agents/testing",
|
||||
"tools": { "allow": ["read", "exec"] }
|
||||
},
|
||||
{ "id": "docs-checker", "workspace": "~/agents/docs", "tools": { "allow": ["read"] } }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**User sends:** Code snippet
|
||||
**Responses:**
|
||||
|
||||
- code-formatter: "Fixed indentation and added type hints"
|
||||
- security-scanner: "⚠️ SQL injection vulnerability in line 12"
|
||||
- test-coverage: "Coverage is 45%, missing tests for error cases"
|
||||
- docs-checker: "Missing docstring for function `process_data`"
|
||||
|
||||
### Example 2: Multi-Language Support
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"strategy": "sequential",
|
||||
"+15555550123": ["detect-language", "translator-en", "translator-de"]
|
||||
},
|
||||
"agents": {
|
||||
"list": [
|
||||
{ "id": "detect-language", "workspace": "~/agents/lang-detect" },
|
||||
{ "id": "translator-en", "workspace": "~/agents/translate-en" },
|
||||
{ "id": "translator-de", "workspace": "~/agents/translate-de" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Config Schema
|
||||
|
||||
```typescript
|
||||
interface OpenClawConfig {
|
||||
broadcast?: {
|
||||
strategy?: "parallel" | "sequential";
|
||||
[peerId: string]: string[];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
- `strategy` (optional): How to process agents
|
||||
- `"parallel"` (default): All agents process simultaneously
|
||||
- `"sequential"`: Agents process in array order
|
||||
- `[peerId]`: WhatsApp group JID, E.164 number, or other peer ID
|
||||
- Value: Array of agent IDs that should process messages
|
||||
|
||||
## Limitations
|
||||
|
||||
1. **Max agents:** No hard limit, but 10+ agents may be slow
|
||||
2. **Shared context:** Agents don't see each other's responses (by design)
|
||||
3. **Message ordering:** Parallel responses may arrive in any order
|
||||
4. **Rate limits:** All agents count toward WhatsApp rate limits
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Planned features:
|
||||
|
||||
- [ ] Shared context mode (agents see each other's responses)
|
||||
- [ ] Agent coordination (agents can signal each other)
|
||||
- [ ] Dynamic agent selection (choose agents based on message content)
|
||||
- [ ] Agent priorities (some agents respond before others)
|
||||
|
||||
## See Also
|
||||
|
||||
- [Multi-Agent Configuration](/tools/multi-agent-sandbox-tools)
|
||||
- [Routing Configuration](/channels/channel-routing)
|
||||
- [Session Management](/concepts/sessions)
|
||||
118
openclaw/docs/channels/channel-routing.md
Normal file
@@ -0,0 +1,118 @@
|
||||
---
|
||||
summary: "Routing rules per channel (WhatsApp, Telegram, Discord, Slack) and shared context"
|
||||
read_when:
|
||||
- Changing channel routing or inbox behavior
|
||||
title: "Channel Routing"
|
||||
---
|
||||
|
||||
# Channels & routing
|
||||
|
||||
OpenClaw routes replies **back to the channel where a message came from**. The
|
||||
model does not choose a channel; routing is deterministic and controlled by the
|
||||
host configuration.
|
||||
|
||||
## Key terms
|
||||
|
||||
- **Channel**: `whatsapp`, `telegram`, `discord`, `slack`, `signal`, `imessage`, `webchat`.
|
||||
- **AccountId**: per‑channel account instance (when supported).
|
||||
- **AgentId**: an isolated workspace + session store (“brain”).
|
||||
- **SessionKey**: the bucket key used to store context and control concurrency.
|
||||
|
||||
## Session key shapes (examples)
|
||||
|
||||
Direct messages collapse to the agent’s **main** session:
|
||||
|
||||
- `agent:<agentId>:<mainKey>` (default: `agent:main:main`)
|
||||
|
||||
Groups and channels remain isolated per channel:
|
||||
|
||||
- Groups: `agent:<agentId>:<channel>:group:<id>`
|
||||
- Channels/rooms: `agent:<agentId>:<channel>:channel:<id>`
|
||||
|
||||
Threads:
|
||||
|
||||
- Slack/Discord threads append `:thread:<threadId>` to the base key.
|
||||
- Telegram forum topics embed `:topic:<topicId>` in the group key.
|
||||
|
||||
Examples:
|
||||
|
||||
- `agent:main:telegram:group:-1001234567890:topic:42`
|
||||
- `agent:main:discord:channel:123456:thread:987654`
|
||||
|
||||
## Routing rules (how an agent is chosen)
|
||||
|
||||
Routing picks **one agent** for each inbound message:
|
||||
|
||||
1. **Exact peer match** (`bindings` with `peer.kind` + `peer.id`).
|
||||
2. **Parent peer match** (thread inheritance).
|
||||
3. **Guild + roles match** (Discord) via `guildId` + `roles`.
|
||||
4. **Guild match** (Discord) via `guildId`.
|
||||
5. **Team match** (Slack) via `teamId`.
|
||||
6. **Account match** (`accountId` on the channel).
|
||||
7. **Channel match** (any account on that channel, `accountId: "*"`).
|
||||
8. **Default agent** (`agents.list[].default`, else first list entry, fallback to `main`).
|
||||
|
||||
When a binding includes multiple match fields (`peer`, `guildId`, `teamId`, `roles`), **all provided fields must match** for that binding to apply.
|
||||
|
||||
The matched agent determines which workspace and session store are used.
|
||||
|
||||
## Broadcast groups (run multiple agents)
|
||||
|
||||
Broadcast groups let you run **multiple agents** for the same peer **when OpenClaw would normally reply** (for example: in WhatsApp groups, after mention/activation gating).
|
||||
|
||||
Config:
|
||||
|
||||
```json5
|
||||
{
|
||||
broadcast: {
|
||||
strategy: "parallel",
|
||||
"120363403215116621@g.us": ["alfred", "baerbel"],
|
||||
"+15555550123": ["support", "logger"],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
See: [Broadcast Groups](/channels/broadcast-groups).
|
||||
|
||||
## Config overview
|
||||
|
||||
- `agents.list`: named agent definitions (workspace, model, etc.).
|
||||
- `bindings`: map inbound channels/accounts/peers to agents.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [{ id: "support", name: "Support", workspace: "~/.openclaw/workspace-support" }],
|
||||
},
|
||||
bindings: [
|
||||
{ match: { channel: "slack", teamId: "T123" }, agentId: "support" },
|
||||
{ match: { channel: "telegram", peer: { kind: "group", id: "-100123" } }, agentId: "support" },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
## Session storage
|
||||
|
||||
Session stores live under the state directory (default `~/.openclaw`):
|
||||
|
||||
- `~/.openclaw/agents/<agentId>/sessions/sessions.json`
|
||||
- JSONL transcripts live alongside the store
|
||||
|
||||
You can override the store path via `session.store` and `{agentId}` templating.
|
||||
|
||||
## WebChat behavior
|
||||
|
||||
WebChat attaches to the **selected agent** and defaults to the agent’s main
|
||||
session. Because of this, WebChat lets you see cross‑channel context for that
|
||||
agent in one place.
|
||||
|
||||
## Reply context
|
||||
|
||||
Inbound replies include:
|
||||
|
||||
- `ReplyToId`, `ReplyToBody`, and `ReplyToSender` when available.
|
||||
- Quoted context is appended to `Body` as a `[Replying to ...]` block.
|
||||
|
||||
This is consistent across channels.
|
||||
1073
openclaw/docs/channels/discord.md
Normal file
586
openclaw/docs/channels/feishu.md
Normal file
@@ -0,0 +1,586 @@
|
||||
---
|
||||
summary: "Feishu bot overview, features, and configuration"
|
||||
read_when:
|
||||
- You want to connect a Feishu/Lark bot
|
||||
- You are configuring the Feishu channel
|
||||
title: Feishu
|
||||
---
|
||||
|
||||
# Feishu bot
|
||||
|
||||
Feishu (Lark) is a team chat platform used by companies for messaging and collaboration. This plugin connects OpenClaw to a Feishu/Lark bot using the platform’s WebSocket event subscription so messages can be received without exposing a public webhook URL.
|
||||
|
||||
---
|
||||
|
||||
## Plugin required
|
||||
|
||||
Install the Feishu plugin:
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/feishu
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./extensions/feishu
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quickstart
|
||||
|
||||
There are two ways to add the Feishu channel:
|
||||
|
||||
### Method 1: onboarding wizard (recommended)
|
||||
|
||||
If you just installed OpenClaw, run the wizard:
|
||||
|
||||
```bash
|
||||
openclaw onboard
|
||||
```
|
||||
|
||||
The wizard guides you through:
|
||||
|
||||
1. Creating a Feishu app and collecting credentials
|
||||
2. Configuring app credentials in OpenClaw
|
||||
3. Starting the gateway
|
||||
|
||||
✅ **After configuration**, check gateway status:
|
||||
|
||||
- `openclaw gateway status`
|
||||
- `openclaw logs --follow`
|
||||
|
||||
### Method 2: CLI setup
|
||||
|
||||
If you already completed initial install, add the channel via CLI:
|
||||
|
||||
```bash
|
||||
openclaw channels add
|
||||
```
|
||||
|
||||
Choose **Feishu**, then enter the App ID and App Secret.
|
||||
|
||||
✅ **After configuration**, manage the gateway:
|
||||
|
||||
- `openclaw gateway status`
|
||||
- `openclaw gateway restart`
|
||||
- `openclaw logs --follow`
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create a Feishu app
|
||||
|
||||
### 1. Open Feishu Open Platform
|
||||
|
||||
Visit [Feishu Open Platform](https://open.feishu.cn/app) and sign in.
|
||||
|
||||
Lark (global) tenants should use [https://open.larksuite.com/app](https://open.larksuite.com/app) and set `domain: "lark"` in the Feishu config.
|
||||
|
||||
### 2. Create an app
|
||||
|
||||
1. Click **Create enterprise app**
|
||||
2. Fill in the app name + description
|
||||
3. Choose an app icon
|
||||
|
||||

|
||||
|
||||
### 3. Copy credentials
|
||||
|
||||
From **Credentials & Basic Info**, copy:
|
||||
|
||||
- **App ID** (format: `cli_xxx`)
|
||||
- **App Secret**
|
||||
|
||||
❗ **Important:** keep the App Secret private.
|
||||
|
||||

|
||||
|
||||
### 4. Configure permissions
|
||||
|
||||
On **Permissions**, click **Batch import** and paste:
|
||||
|
||||
```json
|
||||
{
|
||||
"scopes": {
|
||||
"tenant": [
|
||||
"aily:file:read",
|
||||
"aily:file:write",
|
||||
"application:application.app_message_stats.overview:readonly",
|
||||
"application:application:self_manage",
|
||||
"application:bot.menu:write",
|
||||
"contact:user.employee_id:readonly",
|
||||
"corehr:file:download",
|
||||
"event:ip_list",
|
||||
"im:chat.access_event.bot_p2p_chat:read",
|
||||
"im:chat.members:bot_access",
|
||||
"im:message",
|
||||
"im:message.group_at_msg:readonly",
|
||||
"im:message.p2p_msg:readonly",
|
||||
"im:message:readonly",
|
||||
"im:message:send_as_bot",
|
||||
"im:resource"
|
||||
],
|
||||
"user": ["aily:file:read", "aily:file:write", "im:chat.access_event.bot_p2p_chat:read"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
### 5. Enable bot capability
|
||||
|
||||
In **App Capability** > **Bot**:
|
||||
|
||||
1. Enable bot capability
|
||||
2. Set the bot name
|
||||
|
||||

|
||||
|
||||
### 6. Configure event subscription
|
||||
|
||||
⚠️ **Important:** before setting event subscription, make sure:
|
||||
|
||||
1. You already ran `openclaw channels add` for Feishu
|
||||
2. The gateway is running (`openclaw gateway status`)
|
||||
|
||||
In **Event Subscription**:
|
||||
|
||||
1. Choose **Use long connection to receive events** (WebSocket)
|
||||
2. Add the event: `im.message.receive_v1`
|
||||
|
||||
⚠️ If the gateway is not running, the long-connection setup may fail to save.
|
||||
|
||||

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

|
||||
|
||||
If you want...
|
||||
|
||||
| Goal | What to set |
|
||||
| -------------------------------------------- | ---------------------------------------------------------- |
|
||||
| Allow all groups but only reply on @mentions | `groups: { "*": { requireMention: true } }` |
|
||||
| Disable all group replies | `groupPolicy: "disabled"` |
|
||||
| Only specific groups | `groups: { "<group-id>": { ... } }` (no `"*"` key) |
|
||||
| Only you can trigger in groups | `groupPolicy: "allowlist"`, `groupAllowFrom: ["+1555..."]` |
|
||||
|
||||
## Session keys
|
||||
|
||||
- Group sessions use `agent:<agentId>:<channel>:group:<id>` session keys (rooms/channels use `agent:<agentId>:<channel>:channel:<id>`).
|
||||
- Telegram forum topics add `:topic:<threadId>` to the group id so each topic has its own session.
|
||||
- Direct chats use the main session (or per-sender if configured).
|
||||
- Heartbeats are skipped for group sessions.
|
||||
|
||||
## Pattern: personal DMs + public groups (single agent)
|
||||
|
||||
Yes — this works well if your “personal” traffic is **DMs** and your “public” traffic is **groups**.
|
||||
|
||||
Why: in single-agent mode, DMs typically land in the **main** session key (`agent:main:main`), while groups always use **non-main** session keys (`agent:main:<channel>:group:<id>`). If you enable sandboxing with `mode: "non-main"`, those group sessions run in Docker while your main DM session stays on-host.
|
||||
|
||||
This gives you one agent “brain” (shared workspace + memory), but two execution postures:
|
||||
|
||||
- **DMs**: full tools (host)
|
||||
- **Groups**: sandbox + restricted tools (Docker)
|
||||
|
||||
> If you need truly separate workspaces/personas (“personal” and “public” must never mix), use a second agent + bindings. See [Multi-Agent Routing](/concepts/multi-agent).
|
||||
|
||||
Example (DMs on host, groups sandboxed + messaging-only tools):
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
mode: "non-main", // groups/channels are non-main -> sandboxed
|
||||
scope: "session", // strongest isolation (one container per group/channel)
|
||||
workspaceAccess: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
sandbox: {
|
||||
tools: {
|
||||
// If allow is non-empty, everything else is blocked (deny still wins).
|
||||
allow: ["group:messaging", "group:sessions"],
|
||||
deny: ["group:runtime", "group:fs", "group:ui", "nodes", "cron", "gateway"],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Want “groups can only see folder X” instead of “no host access”? Keep `workspaceAccess: "none"` and mount only allowlisted paths into the sandbox:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
mode: "non-main",
|
||||
scope: "session",
|
||||
workspaceAccess: "none",
|
||||
docker: {
|
||||
binds: [
|
||||
// hostPath:containerPath:mode
|
||||
"/home/user/FriendsShared:/data:ro",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Related:
|
||||
|
||||
- Configuration keys and defaults: [Gateway configuration](/gateway/configuration#agentsdefaultssandbox)
|
||||
- Debugging why a tool is blocked: [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated)
|
||||
- Bind mounts details: [Sandboxing](/gateway/sandboxing#custom-bind-mounts)
|
||||
|
||||
## Display labels
|
||||
|
||||
- UI labels use `displayName` when available, formatted as `<channel>:<token>`.
|
||||
- `#room` is reserved for rooms/channels; group chats use `g-<slug>` (lowercase, spaces -> `-`, keep `#@+._-`).
|
||||
|
||||
## Group policy
|
||||
|
||||
Control how group/room messages are handled per channel:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groupPolicy: "disabled", // "open" | "disabled" | "allowlist"
|
||||
groupAllowFrom: ["+15551234567"],
|
||||
},
|
||||
telegram: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["123456789"], // numeric Telegram user id (wizard can resolve @username)
|
||||
},
|
||||
signal: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["+15551234567"],
|
||||
},
|
||||
imessage: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["chat_id:123"],
|
||||
},
|
||||
msteams: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["user@org.com"],
|
||||
},
|
||||
discord: {
|
||||
groupPolicy: "allowlist",
|
||||
guilds: {
|
||||
GUILD_ID: { channels: { help: { allow: true } } },
|
||||
},
|
||||
},
|
||||
slack: {
|
||||
groupPolicy: "allowlist",
|
||||
channels: { "#general": { allow: true } },
|
||||
},
|
||||
matrix: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["@owner:example.org"],
|
||||
groups: {
|
||||
"!roomId:example.org": { allow: true },
|
||||
"#alias:example.org": { allow: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
| Policy | Behavior |
|
||||
| ------------- | ------------------------------------------------------------ |
|
||||
| `"open"` | Groups bypass allowlists; mention-gating still applies. |
|
||||
| `"disabled"` | Block all group messages entirely. |
|
||||
| `"allowlist"` | Only allow groups/rooms that match the configured allowlist. |
|
||||
|
||||
Notes:
|
||||
|
||||
- `groupPolicy` is separate from mention-gating (which requires @mentions).
|
||||
- WhatsApp/Telegram/Signal/iMessage/Microsoft Teams/Zalo: use `groupAllowFrom` (fallback: explicit `allowFrom`).
|
||||
- DM pairing approvals (`*-allowFrom` store entries) apply to DM access only; group sender authorization stays explicit to group allowlists.
|
||||
- Discord: allowlist uses `channels.discord.guilds.<id>.channels`.
|
||||
- Slack: allowlist uses `channels.slack.channels`.
|
||||
- Matrix: allowlist uses `channels.matrix.groups` (room IDs, aliases, or names). Use `channels.matrix.groupAllowFrom` to restrict senders; per-room `users` allowlists are also supported.
|
||||
- Group DMs are controlled separately (`channels.discord.dm.*`, `channels.slack.dm.*`).
|
||||
- Telegram allowlist can match user IDs (`"123456789"`, `"telegram:123456789"`, `"tg:123456789"`) or usernames (`"@alice"` or `"alice"`); prefixes are case-insensitive.
|
||||
- Default is `groupPolicy: "allowlist"`; if your group allowlist is empty, group messages are blocked.
|
||||
- Runtime safety: when a provider block is completely missing (`channels.<provider>` absent), group policy falls back to a fail-closed mode (typically `allowlist`) instead of inheriting `channels.defaults.groupPolicy`.
|
||||
|
||||
Quick mental model (evaluation order for group messages):
|
||||
|
||||
1. `groupPolicy` (open/disabled/allowlist)
|
||||
2. group allowlists (`*.groups`, `*.groupAllowFrom`, channel-specific allowlist)
|
||||
3. mention gating (`requireMention`, `/activation`)
|
||||
|
||||
## Mention gating (default)
|
||||
|
||||
Group messages require a mention unless overridden per group. Defaults live per subsystem under `*.groups."*"`.
|
||||
|
||||
Replying to a bot message counts as an implicit mention (when the channel supports reply metadata). This applies to Telegram, WhatsApp, Slack, Discord, and Microsoft Teams.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: {
|
||||
"*": { requireMention: true },
|
||||
"123@g.us": { requireMention: false },
|
||||
},
|
||||
},
|
||||
telegram: {
|
||||
groups: {
|
||||
"*": { requireMention: true },
|
||||
"123456789": { requireMention: false },
|
||||
},
|
||||
},
|
||||
imessage: {
|
||||
groups: {
|
||||
"*": { requireMention: true },
|
||||
"123": { requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
groupChat: {
|
||||
mentionPatterns: ["@openclaw", "openclaw", "\\+15555550123"],
|
||||
historyLimit: 50,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `mentionPatterns` are case-insensitive regexes.
|
||||
- Surfaces that provide explicit mentions still pass; patterns are a fallback.
|
||||
- Per-agent override: `agents.list[].groupChat.mentionPatterns` (useful when multiple agents share a group).
|
||||
- Mention gating is only enforced when mention detection is possible (native mentions or `mentionPatterns` are configured).
|
||||
- Discord defaults live in `channels.discord.guilds."*"` (overridable per guild/channel).
|
||||
- Group history context is wrapped uniformly across channels and is **pending-only** (messages skipped due to mention gating); use `messages.groupChat.historyLimit` for the global default and `channels.<channel>.historyLimit` (or `channels.<channel>.accounts.*.historyLimit`) for overrides. Set `0` to disable.
|
||||
|
||||
## Group/channel tool restrictions (optional)
|
||||
|
||||
Some channel configs support restricting which tools are available **inside a specific group/room/channel**.
|
||||
|
||||
- `tools`: allow/deny tools for the whole group.
|
||||
- `toolsBySender`: per-sender overrides within the group.
|
||||
Use explicit key prefixes:
|
||||
`id:<senderId>`, `e164:<phone>`, `username:<handle>`, `name:<displayName>`, and `"*"` wildcard.
|
||||
Legacy unprefixed keys are still accepted and matched as `id:` only.
|
||||
|
||||
Resolution order (most specific wins):
|
||||
|
||||
1. group/channel `toolsBySender` match
|
||||
2. group/channel `tools`
|
||||
3. default (`"*"`) `toolsBySender` match
|
||||
4. default (`"*"`) `tools`
|
||||
|
||||
Example (Telegram):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
groups: {
|
||||
"*": { tools: { deny: ["exec"] } },
|
||||
"-1001234567890": {
|
||||
tools: { deny: ["exec", "read", "write"] },
|
||||
toolsBySender: {
|
||||
"id:123456789": { alsoAllow: ["exec"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Group/channel tool restrictions are applied in addition to global/agent tool policy (deny still wins).
|
||||
- Some channels use different nesting for rooms/channels (e.g., Discord `guilds.*.channels.*`, Slack `channels.*`, MS Teams `teams.*.channels.*`).
|
||||
|
||||
## Group allowlists
|
||||
|
||||
When `channels.whatsapp.groups`, `channels.telegram.groups`, or `channels.imessage.groups` is configured, the keys act as a group allowlist. Use `"*"` to allow all groups while still setting default mention behavior.
|
||||
|
||||
Common intents (copy/paste):
|
||||
|
||||
1. Disable all group replies
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: { whatsapp: { groupPolicy: "disabled" } },
|
||||
}
|
||||
```
|
||||
|
||||
2. Allow only specific groups (WhatsApp)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: {
|
||||
"123@g.us": { requireMention: true },
|
||||
"456@g.us": { requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
3. Allow all groups but require mention (explicit)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
4. Only the owner can trigger in groups (WhatsApp)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15551234567"],
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Activation (owner-only)
|
||||
|
||||
Group owners can toggle per-group activation:
|
||||
|
||||
- `/activation mention`
|
||||
- `/activation always`
|
||||
|
||||
Owner is determined by `channels.whatsapp.allowFrom` (or the bot’s self E.164 when unset). Send the command as a standalone message. Other surfaces currently ignore `/activation`.
|
||||
|
||||
## Context fields
|
||||
|
||||
Group inbound payloads set:
|
||||
|
||||
- `ChatType=group`
|
||||
- `GroupSubject` (if known)
|
||||
- `GroupMembers` (if known)
|
||||
- `WasMentioned` (mention gating result)
|
||||
- Telegram forum topics also include `MessageThreadId` and `IsForum`.
|
||||
|
||||
The agent system prompt includes a group intro on the first turn of a new group session. It reminds the model to respond like a human, avoid Markdown tables, and avoid typing literal `\n` sequences.
|
||||
|
||||
## iMessage specifics
|
||||
|
||||
- Prefer `chat_id:<id>` when routing or allowlisting.
|
||||
- List chats: `imsg chats --limit 20`.
|
||||
- Group replies always go back to the same `chat_id`.
|
||||
|
||||
## WhatsApp specifics
|
||||
|
||||
See [Group messages](/channels/group-messages) for WhatsApp-only behavior (history injection, mention handling details).
|
||||
367
openclaw/docs/channels/imessage.md
Normal file
@@ -0,0 +1,367 @@
|
||||
---
|
||||
summary: "Legacy iMessage support via imsg (JSON-RPC over stdio). New setups should use BlueBubbles."
|
||||
read_when:
|
||||
- Setting up iMessage support
|
||||
- Debugging iMessage send/receive
|
||||
title: "iMessage"
|
||||
---
|
||||
|
||||
# iMessage (legacy: imsg)
|
||||
|
||||
<Warning>
|
||||
For new iMessage deployments, use <a href="/channels/bluebubbles">BlueBubbles</a>.
|
||||
|
||||
The `imsg` integration is legacy and may be removed in a future release.
|
||||
</Warning>
|
||||
|
||||
Status: legacy external CLI integration. Gateway spawns `imsg rpc` and communicates over JSON-RPC on stdio (no separate daemon/port).
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="BlueBubbles (recommended)" icon="message-circle" href="/channels/bluebubbles">
|
||||
Preferred iMessage path for new setups.
|
||||
</Card>
|
||||
<Card title="Pairing" icon="link" href="/channels/pairing">
|
||||
iMessage DMs default to pairing mode.
|
||||
</Card>
|
||||
<Card title="Configuration reference" icon="settings" href="/gateway/configuration-reference#imessage">
|
||||
Full iMessage field reference.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Quick setup
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Local Mac (fast path)">
|
||||
<Steps>
|
||||
<Step title="Install and verify imsg">
|
||||
|
||||
```bash
|
||||
brew install steipete/tap/imsg
|
||||
imsg rpc --help
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Configure OpenClaw">
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
imessage: {
|
||||
enabled: true,
|
||||
cliPath: "/usr/local/bin/imsg",
|
||||
dbPath: "/Users/<you>/Library/Messages/chat.db",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Start gateway">
|
||||
|
||||
```bash
|
||||
openclaw gateway
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Approve first DM pairing (default dmPolicy)">
|
||||
|
||||
```bash
|
||||
openclaw pairing list imessage
|
||||
openclaw pairing approve imessage <CODE>
|
||||
```
|
||||
|
||||
Pairing requests expire after 1 hour.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Remote Mac over SSH">
|
||||
OpenClaw only requires a stdio-compatible `cliPath`, so you can point `cliPath` at a wrapper script that SSHes to a remote Mac and runs `imsg`.
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
exec ssh -T gateway-host imsg "$@"
|
||||
```
|
||||
|
||||
Recommended config when attachments are enabled:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
imessage: {
|
||||
enabled: true,
|
||||
cliPath: "~/.openclaw/scripts/imsg-ssh",
|
||||
remoteHost: "user@gateway-host", // used for SCP attachment fetches
|
||||
includeAttachments: true,
|
||||
// Optional: override allowed attachment roots.
|
||||
// Defaults include /Users/*/Library/Messages/Attachments
|
||||
attachmentRoots: ["/Users/*/Library/Messages/Attachments"],
|
||||
remoteAttachmentRoots: ["/Users/*/Library/Messages/Attachments"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
If `remoteHost` is not set, OpenClaw attempts to auto-detect it by parsing the SSH wrapper script.
|
||||
`remoteHost` must be `host` or `user@host` (no spaces or SSH options).
|
||||
OpenClaw uses strict host-key checking for SCP, so the relay host key must already exist in `~/.ssh/known_hosts`.
|
||||
Attachment paths are validated against allowed roots (`attachmentRoots` / `remoteAttachmentRoots`).
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Requirements and permissions (macOS)
|
||||
|
||||
- Messages must be signed in on the Mac running `imsg`.
|
||||
- Full Disk Access is required for the process context running OpenClaw/`imsg` (Messages DB access).
|
||||
- Automation permission is required to send messages through Messages.app.
|
||||
|
||||
<Tip>
|
||||
Permissions are granted per process context. If gateway runs headless (LaunchAgent/SSH), run a one-time interactive command in that same context to trigger prompts:
|
||||
|
||||
```bash
|
||||
imsg chats --limit 1
|
||||
# or
|
||||
imsg send <handle> "test"
|
||||
```
|
||||
|
||||
</Tip>
|
||||
|
||||
## Access control and routing
|
||||
|
||||
<Tabs>
|
||||
<Tab title="DM policy">
|
||||
`channels.imessage.dmPolicy` controls direct messages:
|
||||
|
||||
- `pairing` (default)
|
||||
- `allowlist`
|
||||
- `open` (requires `allowFrom` to include `"*"`)
|
||||
- `disabled`
|
||||
|
||||
Allowlist field: `channels.imessage.allowFrom`.
|
||||
|
||||
Allowlist entries can be handles or chat targets (`chat_id:*`, `chat_guid:*`, `chat_identifier:*`).
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Group policy + mentions">
|
||||
`channels.imessage.groupPolicy` controls group handling:
|
||||
|
||||
- `allowlist` (default when configured)
|
||||
- `open`
|
||||
- `disabled`
|
||||
|
||||
Group sender allowlist: `channels.imessage.groupAllowFrom`.
|
||||
|
||||
Runtime fallback: if `groupAllowFrom` is unset, iMessage group sender checks fall back to `allowFrom` when available.
|
||||
Runtime note: if `channels.imessage` is completely missing, runtime falls back to `groupPolicy="allowlist"` and logs a warning (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
Mention gating for groups:
|
||||
|
||||
- iMessage has no native mention metadata
|
||||
- mention detection uses regex patterns (`agents.list[].groupChat.mentionPatterns`, fallback `messages.groupChat.mentionPatterns`)
|
||||
- with no configured patterns, mention gating cannot be enforced
|
||||
|
||||
Control commands from authorized senders can bypass mention gating in groups.
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Sessions and deterministic replies">
|
||||
- DMs use direct routing; groups use group routing.
|
||||
- With default `session.dmScope=main`, iMessage DMs collapse into the agent main session.
|
||||
- Group sessions are isolated (`agent:<agentId>:imessage:group:<chat_id>`).
|
||||
- Replies route back to iMessage using originating channel/target metadata.
|
||||
|
||||
Group-ish thread behavior:
|
||||
|
||||
Some multi-participant iMessage threads can arrive with `is_group=false`.
|
||||
If that `chat_id` is explicitly configured under `channels.imessage.groups`, OpenClaw treats it as group traffic (group gating + group session isolation).
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Deployment patterns
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Dedicated bot macOS user (separate iMessage identity)">
|
||||
Use a dedicated Apple ID and macOS user so bot traffic is isolated from your personal Messages profile.
|
||||
|
||||
Typical flow:
|
||||
|
||||
1. Create/sign in a dedicated macOS user.
|
||||
2. Sign into Messages with the bot Apple ID in that user.
|
||||
3. Install `imsg` in that user.
|
||||
4. Create SSH wrapper so OpenClaw can run `imsg` in that user context.
|
||||
5. Point `channels.imessage.accounts.<id>.cliPath` and `.dbPath` to that user profile.
|
||||
|
||||
First run may require GUI approvals (Automation + Full Disk Access) in that bot user session.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Remote Mac over Tailscale (example)">
|
||||
Common topology:
|
||||
|
||||
- gateway runs on Linux/VM
|
||||
- iMessage + `imsg` runs on a Mac in your tailnet
|
||||
- `cliPath` wrapper uses SSH to run `imsg`
|
||||
- `remoteHost` enables SCP attachment fetches
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
imessage: {
|
||||
enabled: true,
|
||||
cliPath: "~/.openclaw/scripts/imsg-ssh",
|
||||
remoteHost: "bot@mac-mini.tailnet-1234.ts.net",
|
||||
includeAttachments: true,
|
||||
dbPath: "/Users/bot/Library/Messages/chat.db",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
exec ssh -T bot@mac-mini.tailnet-1234.ts.net imsg "$@"
|
||||
```
|
||||
|
||||
Use SSH keys so both SSH and SCP are non-interactive.
|
||||
Ensure the host key is trusted first (for example `ssh bot@mac-mini.tailnet-1234.ts.net`) so `known_hosts` is populated.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Multi-account pattern">
|
||||
iMessage supports per-account config under `channels.imessage.accounts`.
|
||||
|
||||
Each account can override fields such as `cliPath`, `dbPath`, `allowFrom`, `groupPolicy`, `mediaMaxMb`, history settings, and attachment root allowlists.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Media, chunking, and delivery targets
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Attachments and media">
|
||||
- inbound attachment ingestion is optional: `channels.imessage.includeAttachments`
|
||||
- remote attachment paths can be fetched via SCP when `remoteHost` is set
|
||||
- attachment paths must match allowed roots:
|
||||
- `channels.imessage.attachmentRoots` (local)
|
||||
- `channels.imessage.remoteAttachmentRoots` (remote SCP mode)
|
||||
- default root pattern: `/Users/*/Library/Messages/Attachments`
|
||||
- SCP uses strict host-key checking (`StrictHostKeyChecking=yes`)
|
||||
- outbound media size uses `channels.imessage.mediaMaxMb` (default 16 MB)
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Outbound chunking">
|
||||
- text chunk limit: `channels.imessage.textChunkLimit` (default 4000)
|
||||
- chunk mode: `channels.imessage.chunkMode`
|
||||
- `length` (default)
|
||||
- `newline` (paragraph-first splitting)
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Addressing formats">
|
||||
Preferred explicit targets:
|
||||
|
||||
- `chat_id:123` (recommended for stable routing)
|
||||
- `chat_guid:...`
|
||||
- `chat_identifier:...`
|
||||
|
||||
Handle targets are also supported:
|
||||
|
||||
- `imessage:+1555...`
|
||||
- `sms:+1555...`
|
||||
- `user@example.com`
|
||||
|
||||
```bash
|
||||
imsg chats --limit 20
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Config writes
|
||||
|
||||
iMessage allows channel-initiated config writes by default (for `/config set|unset` when `commands.config: true`).
|
||||
|
||||
Disable:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
imessage: {
|
||||
configWrites: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="imsg not found or RPC unsupported">
|
||||
Validate the binary and RPC support:
|
||||
|
||||
```bash
|
||||
imsg rpc --help
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
If probe reports RPC unsupported, update `imsg`.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="DMs are ignored">
|
||||
Check:
|
||||
|
||||
- `channels.imessage.dmPolicy`
|
||||
- `channels.imessage.allowFrom`
|
||||
- pairing approvals (`openclaw pairing list imessage`)
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Group messages are ignored">
|
||||
Check:
|
||||
|
||||
- `channels.imessage.groupPolicy`
|
||||
- `channels.imessage.groupAllowFrom`
|
||||
- `channels.imessage.groups` allowlist behavior
|
||||
- mention pattern configuration (`agents.list[].groupChat.mentionPatterns`)
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Remote attachments fail">
|
||||
Check:
|
||||
|
||||
- `channels.imessage.remoteHost`
|
||||
- `channels.imessage.remoteAttachmentRoots`
|
||||
- SSH/SCP key auth from the gateway host
|
||||
- host key exists in `~/.ssh/known_hosts` on the gateway host
|
||||
- remote path readability on the Mac running Messages
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="macOS permission prompts were missed">
|
||||
Re-run in an interactive GUI terminal in the same user/session context and approve prompts:
|
||||
|
||||
```bash
|
||||
imsg chats --limit 1
|
||||
imsg send <handle> "test"
|
||||
```
|
||||
|
||||
Confirm Full Disk Access + Automation are granted for the process context that runs OpenClaw/`imsg`.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Configuration reference pointers
|
||||
|
||||
- [Configuration reference - iMessage](/gateway/configuration-reference#imessage)
|
||||
- [Gateway configuration](/gateway/configuration)
|
||||
- [Pairing](/channels/pairing)
|
||||
- [BlueBubbles](/channels/bluebubbles)
|
||||
47
openclaw/docs/channels/index.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
summary: "Messaging platforms OpenClaw can connect to"
|
||||
read_when:
|
||||
- You want to choose a chat channel for OpenClaw
|
||||
- You need a quick overview of supported messaging platforms
|
||||
title: "Chat Channels"
|
||||
---
|
||||
|
||||
# Chat Channels
|
||||
|
||||
OpenClaw can talk to you on any chat app you already use. Each channel connects via the Gateway.
|
||||
Text is supported everywhere; media and reactions vary by channel.
|
||||
|
||||
## Supported channels
|
||||
|
||||
- [WhatsApp](/channels/whatsapp) — Most popular; uses Baileys and requires QR pairing.
|
||||
- [Telegram](/channels/telegram) — Bot API via grammY; supports groups.
|
||||
- [Discord](/channels/discord) — Discord Bot API + Gateway; supports servers, channels, and DMs.
|
||||
- [IRC](/channels/irc) — Classic IRC servers; channels + DMs with pairing/allowlist controls.
|
||||
- [Slack](/channels/slack) — Bolt SDK; workspace apps.
|
||||
- [Feishu](/channels/feishu) — Feishu/Lark bot via WebSocket (plugin, installed separately).
|
||||
- [Google Chat](/channels/googlechat) — Google Chat API app via HTTP webhook.
|
||||
- [Mattermost](/channels/mattermost) — Bot API + WebSocket; channels, groups, DMs (plugin, installed separately).
|
||||
- [Signal](/channels/signal) — signal-cli; privacy-focused.
|
||||
- [BlueBubbles](/channels/bluebubbles) — **Recommended for iMessage**; uses the BlueBubbles macOS server REST API with full feature support (edit, unsend, effects, reactions, group management — edit currently broken on macOS 26 Tahoe).
|
||||
- [iMessage (legacy)](/channels/imessage) — Legacy macOS integration via imsg CLI (deprecated, use BlueBubbles for new setups).
|
||||
- [Microsoft Teams](/channels/msteams) — Bot Framework; enterprise support (plugin, installed separately).
|
||||
- [Synology Chat](/channels/synology-chat) — Synology NAS Chat via outgoing+incoming webhooks (plugin, installed separately).
|
||||
- [LINE](/channels/line) — LINE Messaging API bot (plugin, installed separately).
|
||||
- [Nextcloud Talk](/channels/nextcloud-talk) — Self-hosted chat via Nextcloud Talk (plugin, installed separately).
|
||||
- [Matrix](/channels/matrix) — Matrix protocol (plugin, installed separately).
|
||||
- [Nostr](/channels/nostr) — Decentralized DMs via NIP-04 (plugin, installed separately).
|
||||
- [Tlon](/channels/tlon) — Urbit-based messenger (plugin, installed separately).
|
||||
- [Twitch](/channels/twitch) — Twitch chat via IRC connection (plugin, installed separately).
|
||||
- [Zalo](/channels/zalo) — Zalo Bot API; Vietnam's popular messenger (plugin, installed separately).
|
||||
- [Zalo Personal](/channels/zalouser) — Zalo personal account via QR login (plugin, installed separately).
|
||||
- [WebChat](/web/webchat) — Gateway WebChat UI over WebSocket.
|
||||
|
||||
## Notes
|
||||
|
||||
- Channels can run simultaneously; configure multiple and OpenClaw will route per chat.
|
||||
- Fastest setup is usually **Telegram** (simple bot token). WhatsApp requires QR pairing and
|
||||
stores more state on disk.
|
||||
- Group behavior varies by channel; see [Groups](/channels/groups).
|
||||
- DM pairing and allowlists are enforced for safety; see [Security](/gateway/security).
|
||||
- Troubleshooting: [Channel troubleshooting](/channels/troubleshooting).
|
||||
- Model providers are documented separately; see [Model Providers](/providers/models).
|
||||
241
openclaw/docs/channels/irc.md
Normal file
@@ -0,0 +1,241 @@
|
||||
---
|
||||
title: IRC
|
||||
description: Connect OpenClaw to IRC channels and direct messages.
|
||||
summary: "IRC plugin setup, access controls, and troubleshooting"
|
||||
read_when:
|
||||
- You want to connect OpenClaw to IRC channels or DMs
|
||||
- You are configuring IRC allowlists, group policy, or mention gating
|
||||
---
|
||||
|
||||
Use IRC when you want OpenClaw in classic channels (`#room`) and direct messages.
|
||||
IRC ships as an extension plugin, but it is configured in the main config under `channels.irc`.
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Enable IRC config in `~/.openclaw/openclaw.json`.
|
||||
2. Set at least:
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"irc": {
|
||||
"enabled": true,
|
||||
"host": "irc.libera.chat",
|
||||
"port": 6697,
|
||||
"tls": true,
|
||||
"nick": "openclaw-bot",
|
||||
"channels": ["#openclaw"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Start/restart gateway:
|
||||
|
||||
```bash
|
||||
openclaw gateway run
|
||||
```
|
||||
|
||||
## Security defaults
|
||||
|
||||
- `channels.irc.dmPolicy` defaults to `"pairing"`.
|
||||
- `channels.irc.groupPolicy` defaults to `"allowlist"`.
|
||||
- With `groupPolicy="allowlist"`, set `channels.irc.groups` to define allowed channels.
|
||||
- Use TLS (`channels.irc.tls=true`) unless you intentionally accept plaintext transport.
|
||||
|
||||
## Access control
|
||||
|
||||
There are two separate “gates” for IRC channels:
|
||||
|
||||
1. **Channel access** (`groupPolicy` + `groups`): whether the bot accepts messages from a channel at all.
|
||||
2. **Sender access** (`groupAllowFrom` / per-channel `groups["#channel"].allowFrom`): who is allowed to trigger the bot inside that channel.
|
||||
|
||||
Config keys:
|
||||
|
||||
- DM allowlist (DM sender access): `channels.irc.allowFrom`
|
||||
- Group sender allowlist (channel sender access): `channels.irc.groupAllowFrom`
|
||||
- Per-channel controls (channel + sender + mention rules): `channels.irc.groups["#channel"]`
|
||||
- `channels.irc.groupPolicy="open"` allows unconfigured channels (**still mention-gated by default**)
|
||||
|
||||
Allowlist entries should use stable sender identities (`nick!user@host`).
|
||||
Bare nick matching is mutable and only enabled when `channels.irc.dangerouslyAllowNameMatching: true`.
|
||||
|
||||
### Common gotcha: `allowFrom` is for DMs, not channels
|
||||
|
||||
If you see logs like:
|
||||
|
||||
- `irc: drop group sender alice!ident@host (policy=allowlist)`
|
||||
|
||||
…it means the sender wasn’t allowed for **group/channel** messages. Fix it by either:
|
||||
|
||||
- setting `channels.irc.groupAllowFrom` (global for all channels), or
|
||||
- setting per-channel sender allowlists: `channels.irc.groups["#channel"].allowFrom`
|
||||
|
||||
Example (allow anyone in `#tuirc-dev` to talk to the bot):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
irc: {
|
||||
groupPolicy: "allowlist",
|
||||
groups: {
|
||||
"#tuirc-dev": { allowFrom: ["*"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Reply triggering (mentions)
|
||||
|
||||
Even if a channel is allowed (via `groupPolicy` + `groups`) and the sender is allowed, OpenClaw defaults to **mention-gating** in group contexts.
|
||||
|
||||
That means you may see logs like `drop channel … (missing-mention)` unless the message includes a mention pattern that matches the bot.
|
||||
|
||||
To make the bot reply in an IRC channel **without needing a mention**, disable mention gating for that channel:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
irc: {
|
||||
groupPolicy: "allowlist",
|
||||
groups: {
|
||||
"#tuirc-dev": {
|
||||
requireMention: false,
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Or to allow **all** IRC channels (no per-channel allowlist) and still reply without mentions:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
irc: {
|
||||
groupPolicy: "open",
|
||||
groups: {
|
||||
"*": { requireMention: false, allowFrom: ["*"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Security note (recommended for public channels)
|
||||
|
||||
If you allow `allowFrom: ["*"]` in a public channel, anyone can prompt the bot.
|
||||
To reduce risk, restrict tools for that channel.
|
||||
|
||||
### Same tools for everyone in the channel
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
irc: {
|
||||
groups: {
|
||||
"#tuirc-dev": {
|
||||
allowFrom: ["*"],
|
||||
tools: {
|
||||
deny: ["group:runtime", "group:fs", "gateway", "nodes", "cron", "browser"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Different tools per sender (owner gets more power)
|
||||
|
||||
Use `toolsBySender` to apply a stricter policy to `"*"` and a looser one to your nick:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
irc: {
|
||||
groups: {
|
||||
"#tuirc-dev": {
|
||||
allowFrom: ["*"],
|
||||
toolsBySender: {
|
||||
"*": {
|
||||
deny: ["group:runtime", "group:fs", "gateway", "nodes", "cron", "browser"],
|
||||
},
|
||||
"id:eigen": {
|
||||
deny: ["gateway", "nodes", "cron"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `toolsBySender` keys should use `id:` for IRC sender identity values:
|
||||
`id:eigen` or `id:eigen!~eigen@174.127.248.171` for stronger matching.
|
||||
- Legacy unprefixed keys are still accepted and matched as `id:` only.
|
||||
- The first matching sender policy wins; `"*"` is the wildcard fallback.
|
||||
|
||||
For more on group access vs mention-gating (and how they interact), see: [/channels/groups](/channels/groups).
|
||||
|
||||
## NickServ
|
||||
|
||||
To identify with NickServ after connect:
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"irc": {
|
||||
"nickserv": {
|
||||
"enabled": true,
|
||||
"service": "NickServ",
|
||||
"password": "your-nickserv-password"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Optional one-time registration on connect:
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"irc": {
|
||||
"nickserv": {
|
||||
"register": true,
|
||||
"registerEmail": "bot@example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Disable `register` after the nick is registered to avoid repeated REGISTER attempts.
|
||||
|
||||
## Environment variables
|
||||
|
||||
Default account supports:
|
||||
|
||||
- `IRC_HOST`
|
||||
- `IRC_PORT`
|
||||
- `IRC_TLS`
|
||||
- `IRC_NICK`
|
||||
- `IRC_USERNAME`
|
||||
- `IRC_REALNAME`
|
||||
- `IRC_PASSWORD`
|
||||
- `IRC_CHANNELS` (comma-separated)
|
||||
- `IRC_NICKSERV_PASSWORD`
|
||||
- `IRC_NICKSERV_REGISTER_EMAIL`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If the bot connects but never replies in channels, verify `channels.irc.groups` **and** whether mention-gating is dropping messages (`missing-mention`). If you want it to reply without pings, set `requireMention:false` for the channel.
|
||||
- If login fails, verify nick availability and server password.
|
||||
- If TLS fails on a custom network, verify host/port and certificate setup.
|
||||
187
openclaw/docs/channels/line.md
Normal file
@@ -0,0 +1,187 @@
|
||||
---
|
||||
summary: "LINE Messaging API plugin setup, config, and usage"
|
||||
read_when:
|
||||
- You want to connect OpenClaw to LINE
|
||||
- You need LINE webhook + credential setup
|
||||
- You want LINE-specific message options
|
||||
title: LINE
|
||||
---
|
||||
|
||||
# LINE (plugin)
|
||||
|
||||
LINE connects to OpenClaw via the LINE Messaging API. The plugin runs as a webhook
|
||||
receiver on the gateway and uses your channel access token + channel secret for
|
||||
authentication.
|
||||
|
||||
Status: supported via plugin. Direct messages, group chats, media, locations, Flex
|
||||
messages, template messages, and quick replies are supported. Reactions and threads
|
||||
are not supported.
|
||||
|
||||
## Plugin required
|
||||
|
||||
Install the LINE plugin:
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/line
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./extensions/line
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
1. Create a LINE Developers account and open the Console:
|
||||
[https://developers.line.biz/console/](https://developers.line.biz/console/)
|
||||
2. Create (or pick) a Provider and add a **Messaging API** channel.
|
||||
3. Copy the **Channel access token** and **Channel secret** from the channel settings.
|
||||
4. Enable **Use webhook** in the Messaging API settings.
|
||||
5. Set the webhook URL to your gateway endpoint (HTTPS required):
|
||||
|
||||
```
|
||||
https://gateway-host/line/webhook
|
||||
```
|
||||
|
||||
The gateway responds to LINE’s webhook verification (GET) and inbound events (POST).
|
||||
If you need a custom path, set `channels.line.webhookPath` or
|
||||
`channels.line.accounts.<id>.webhookPath` and update the URL accordingly.
|
||||
|
||||
## Configure
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
line: {
|
||||
enabled: true,
|
||||
channelAccessToken: "LINE_CHANNEL_ACCESS_TOKEN",
|
||||
channelSecret: "LINE_CHANNEL_SECRET",
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Env vars (default account only):
|
||||
|
||||
- `LINE_CHANNEL_ACCESS_TOKEN`
|
||||
- `LINE_CHANNEL_SECRET`
|
||||
|
||||
Token/secret files:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
line: {
|
||||
tokenFile: "/path/to/line-token.txt",
|
||||
secretFile: "/path/to/line-secret.txt",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Multiple accounts:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
line: {
|
||||
accounts: {
|
||||
marketing: {
|
||||
channelAccessToken: "...",
|
||||
channelSecret: "...",
|
||||
webhookPath: "/line/marketing",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Access control
|
||||
|
||||
Direct messages default to pairing. Unknown senders get a pairing code and their
|
||||
messages are ignored until approved.
|
||||
|
||||
```bash
|
||||
openclaw pairing list line
|
||||
openclaw pairing approve line <CODE>
|
||||
```
|
||||
|
||||
Allowlists and policies:
|
||||
|
||||
- `channels.line.dmPolicy`: `pairing | allowlist | open | disabled`
|
||||
- `channels.line.allowFrom`: allowlisted LINE user IDs for DMs
|
||||
- `channels.line.groupPolicy`: `allowlist | open | disabled`
|
||||
- `channels.line.groupAllowFrom`: allowlisted LINE user IDs for groups
|
||||
- Per-group overrides: `channels.line.groups.<groupId>.allowFrom`
|
||||
- Runtime note: if `channels.line` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
LINE IDs are case-sensitive. Valid IDs look like:
|
||||
|
||||
- User: `U` + 32 hex chars
|
||||
- Group: `C` + 32 hex chars
|
||||
- Room: `R` + 32 hex chars
|
||||
|
||||
## Message behavior
|
||||
|
||||
- Text is chunked at 5000 characters.
|
||||
- Markdown formatting is stripped; code blocks and tables are converted into Flex
|
||||
cards when possible.
|
||||
- Streaming responses are buffered; LINE receives full chunks with a loading
|
||||
animation while the agent works.
|
||||
- Media downloads are capped by `channels.line.mediaMaxMb` (default 10).
|
||||
|
||||
## Channel data (rich messages)
|
||||
|
||||
Use `channelData.line` to send quick replies, locations, Flex cards, or template
|
||||
messages.
|
||||
|
||||
```json5
|
||||
{
|
||||
text: "Here you go",
|
||||
channelData: {
|
||||
line: {
|
||||
quickReplies: ["Status", "Help"],
|
||||
location: {
|
||||
title: "Office",
|
||||
address: "123 Main St",
|
||||
latitude: 35.681236,
|
||||
longitude: 139.767125,
|
||||
},
|
||||
flexMessage: {
|
||||
altText: "Status card",
|
||||
contents: {
|
||||
/* Flex payload */
|
||||
},
|
||||
},
|
||||
templateMessage: {
|
||||
type: "confirm",
|
||||
text: "Proceed?",
|
||||
confirmLabel: "Yes",
|
||||
confirmData: "yes",
|
||||
cancelLabel: "No",
|
||||
cancelData: "no",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The LINE plugin also ships a `/card` command for Flex message presets:
|
||||
|
||||
```
|
||||
/card info "Welcome" "Thanks for joining!"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Webhook verification fails:** ensure the webhook URL is HTTPS and the
|
||||
`channelSecret` matches the LINE console.
|
||||
- **No inbound events:** confirm the webhook path matches `channels.line.webhookPath`
|
||||
and that the gateway is reachable from LINE.
|
||||
- **Media download errors:** raise `channels.line.mediaMaxMb` if media exceeds the
|
||||
default limit.
|
||||
56
openclaw/docs/channels/location.md
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
summary: "Inbound channel location parsing (Telegram + WhatsApp) and context fields"
|
||||
read_when:
|
||||
- Adding or modifying channel location parsing
|
||||
- Using location context fields in agent prompts or tools
|
||||
title: "Channel Location Parsing"
|
||||
---
|
||||
|
||||
# Channel location parsing
|
||||
|
||||
OpenClaw normalizes shared locations from chat channels into:
|
||||
|
||||
- human-readable text appended to the inbound body, and
|
||||
- structured fields in the auto-reply context payload.
|
||||
|
||||
Currently supported:
|
||||
|
||||
- **Telegram** (location pins + venues + live locations)
|
||||
- **WhatsApp** (locationMessage + liveLocationMessage)
|
||||
- **Matrix** (`m.location` with `geo_uri`)
|
||||
|
||||
## Text formatting
|
||||
|
||||
Locations are rendered as friendly lines without brackets:
|
||||
|
||||
- Pin:
|
||||
- `📍 48.858844, 2.294351 ±12m`
|
||||
- Named place:
|
||||
- `📍 Eiffel Tower — Champ de Mars, Paris (48.858844, 2.294351 ±12m)`
|
||||
- Live share:
|
||||
- `🛰 Live location: 48.858844, 2.294351 ±12m`
|
||||
|
||||
If the channel includes a caption/comment, it is appended on the next line:
|
||||
|
||||
```
|
||||
📍 48.858844, 2.294351 ±12m
|
||||
Meet here
|
||||
```
|
||||
|
||||
## Context fields
|
||||
|
||||
When a location is present, these fields are added to `ctx`:
|
||||
|
||||
- `LocationLat` (number)
|
||||
- `LocationLon` (number)
|
||||
- `LocationAccuracy` (number, meters; optional)
|
||||
- `LocationName` (string; optional)
|
||||
- `LocationAddress` (string; optional)
|
||||
- `LocationSource` (`pin | place | live`)
|
||||
- `LocationIsLive` (boolean)
|
||||
|
||||
## Channel notes
|
||||
|
||||
- **Telegram**: venues map to `LocationName/LocationAddress`; live locations use `live_period`.
|
||||
- **WhatsApp**: `locationMessage.comment` and `liveLocationMessage.caption` are appended as the caption line.
|
||||
- **Matrix**: `geo_uri` is parsed as a pin location; altitude is ignored and `LocationIsLive` is always false.
|
||||
303
openclaw/docs/channels/matrix.md
Normal file
@@ -0,0 +1,303 @@
|
||||
---
|
||||
summary: "Matrix support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Matrix channel features
|
||||
title: "Matrix"
|
||||
---
|
||||
|
||||
# Matrix (plugin)
|
||||
|
||||
Matrix is an open, decentralized messaging protocol. OpenClaw connects as a Matrix **user**
|
||||
on any homeserver, so you need a Matrix account for the bot. Once it is logged in, you can DM
|
||||
the bot directly or invite it to rooms (Matrix "groups"). Beeper is a valid client option too,
|
||||
but it requires E2EE to be enabled.
|
||||
|
||||
Status: supported via plugin (@vector-im/matrix-bot-sdk). Direct messages, rooms, threads, media, reactions,
|
||||
polls (send + poll-start as text), location, and E2EE (with crypto support).
|
||||
|
||||
## Plugin required
|
||||
|
||||
Matrix ships as a plugin and is not bundled with the core install.
|
||||
|
||||
Install via CLI (npm registry):
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/matrix
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./extensions/matrix
|
||||
```
|
||||
|
||||
If you choose Matrix during configure/onboarding and a git checkout is detected,
|
||||
OpenClaw will offer the local install path automatically.
|
||||
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Setup
|
||||
|
||||
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:
|
||||
- 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:
|
||||
- Use the Matrix login API with `curl` at your home server:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url https://matrix.example.org/_matrix/client/v3/login \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"type": "m.login.password",
|
||||
"identifier": {
|
||||
"type": "m.id.user",
|
||||
"user": "your-user-name"
|
||||
},
|
||||
"password": "your-password"
|
||||
}'
|
||||
```
|
||||
|
||||
- Replace `matrix.example.org` with your homeserver URL.
|
||||
- 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:
|
||||
- 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
|
||||
(Element, Beeper, etc.; see [https://matrix.org/ecosystem/clients/](https://matrix.org/ecosystem/clients/)). Beeper requires E2EE,
|
||||
so set `channels.matrix.encryption: true` and verify the device.
|
||||
|
||||
Minimal config (access token, user ID auto-fetched):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
matrix: {
|
||||
enabled: true,
|
||||
homeserver: "https://matrix.example.org",
|
||||
accessToken: "syt_***",
|
||||
dm: { policy: "pairing" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
E2EE config (end to end encryption enabled):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
matrix: {
|
||||
enabled: true,
|
||||
homeserver: "https://matrix.example.org",
|
||||
accessToken: "syt_***",
|
||||
encryption: true,
|
||||
dm: { policy: "pairing" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Encryption (E2EE)
|
||||
|
||||
End-to-end encryption is **supported** via the Rust crypto SDK.
|
||||
|
||||
Enable with `channels.matrix.encryption: true`:
|
||||
|
||||
- If the crypto module loads, encrypted rooms are decrypted automatically.
|
||||
- Outbound media is encrypted when sending to encrypted rooms.
|
||||
- On first connection, OpenClaw requests device verification from your other sessions.
|
||||
- Verify the device in another Matrix client (Element, etc.) to enable key sharing.
|
||||
- If the crypto module cannot be loaded, E2EE is disabled and encrypted rooms will not decrypt;
|
||||
OpenClaw logs a warning.
|
||||
- If you see missing crypto module errors (for example, `@matrix-org/matrix-sdk-crypto-nodejs-*`),
|
||||
allow build scripts for `@matrix-org/matrix-sdk-crypto-nodejs` and run
|
||||
`pnpm rebuild @matrix-org/matrix-sdk-crypto-nodejs` or fetch the binary with
|
||||
`node node_modules/@matrix-org/matrix-sdk-crypto-nodejs/download-lib.js`.
|
||||
|
||||
Crypto state is stored per account + access token in
|
||||
`~/.openclaw/matrix/accounts/<account>/<homeserver>__<user>/<token-hash>/crypto/`
|
||||
(SQLite database). Sync state lives alongside it in `bot-storage.json`.
|
||||
If the access token (device) changes, a new store is created and the bot must be
|
||||
re-verified for encrypted rooms.
|
||||
|
||||
**Device verification:**
|
||||
When E2EE is enabled, the bot will request verification from your other sessions on startup.
|
||||
Open Element (or another client) and approve the verification request to establish trust.
|
||||
Once verified, the bot can decrypt messages in encrypted rooms.
|
||||
|
||||
## Multi-account
|
||||
|
||||
Multi-account support: use `channels.matrix.accounts` with per-account credentials and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
||||
|
||||
Each account runs as a separate Matrix user on any homeserver. Per-account config
|
||||
inherits from the top-level `channels.matrix` settings and can override any option
|
||||
(DM policy, groups, encryption, etc.).
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
matrix: {
|
||||
enabled: true,
|
||||
dm: { policy: "pairing" },
|
||||
accounts: {
|
||||
assistant: {
|
||||
name: "Main assistant",
|
||||
homeserver: "https://matrix.example.org",
|
||||
accessToken: "syt_assistant_***",
|
||||
encryption: true,
|
||||
},
|
||||
alerts: {
|
||||
name: "Alerts bot",
|
||||
homeserver: "https://matrix.example.org",
|
||||
accessToken: "syt_alerts_***",
|
||||
dm: { policy: "allowlist", allowFrom: ["@admin:example.org"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Account startup is serialized to avoid race conditions with concurrent module imports.
|
||||
- Env variables (`MATRIX_HOMESERVER`, `MATRIX_ACCESS_TOKEN`, etc.) only apply to the **default** account.
|
||||
- Base channel settings (DM policy, group policy, mention gating, etc.) apply to all accounts unless overridden per account.
|
||||
- Use `bindings[].match.accountId` to route each account to a different agent.
|
||||
- Crypto state is stored per account + access token (separate key stores per account).
|
||||
|
||||
## Routing model
|
||||
|
||||
- Replies always go back to Matrix.
|
||||
- DMs share the agent's main session; rooms map to group sessions.
|
||||
|
||||
## Access control (DMs)
|
||||
|
||||
- Default: `channels.matrix.dm.policy = "pairing"`. Unknown senders get a pairing code.
|
||||
- Approve via:
|
||||
- `openclaw pairing list matrix`
|
||||
- `openclaw pairing approve matrix <CODE>`
|
||||
- Public DMs: `channels.matrix.dm.policy="open"` plus `channels.matrix.dm.allowFrom=["*"]`.
|
||||
- `channels.matrix.dm.allowFrom` accepts full Matrix user IDs (example: `@user:server`). The wizard resolves display names to user IDs when directory search finds a single exact match.
|
||||
- Do not use display names or bare localparts (example: `"Alice"` or `"alice"`). They are ambiguous and are ignored for allowlist matching. Use full `@user:server` IDs.
|
||||
|
||||
## Rooms (groups)
|
||||
|
||||
- Default: `channels.matrix.groupPolicy = "allowlist"` (mention-gated). Use `channels.defaults.groupPolicy` to override the default when unset.
|
||||
- Runtime note: if `channels.matrix` is completely missing, runtime falls back to `groupPolicy="allowlist"` for room checks (even if `channels.defaults.groupPolicy` is set).
|
||||
- Allowlist rooms with `channels.matrix.groups` (room IDs or aliases; names are resolved to IDs when directory search finds a single exact match):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
matrix: {
|
||||
groupPolicy: "allowlist",
|
||||
groups: {
|
||||
"!roomId:example.org": { allow: true },
|
||||
"#alias:example.org": { allow: true },
|
||||
},
|
||||
groupAllowFrom: ["@owner:example.org"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- `requireMention: false` enables auto-reply in that room.
|
||||
- `groups."*"` can set defaults for mention gating across rooms.
|
||||
- `groupAllowFrom` restricts which senders can trigger the bot in rooms (full Matrix user IDs).
|
||||
- Per-room `users` allowlists can further restrict senders inside a specific room (use full Matrix user IDs).
|
||||
- The configure wizard prompts for room allowlists (room IDs, aliases, or names) and resolves names only on an exact, unique match.
|
||||
- On startup, OpenClaw resolves room/user names in allowlists to IDs and logs the mapping; unresolved entries are ignored for allowlist matching.
|
||||
- Invites are auto-joined by default; control with `channels.matrix.autoJoin` and `channels.matrix.autoJoinAllowlist`.
|
||||
- To allow **no rooms**, set `channels.matrix.groupPolicy: "disabled"` (or keep an empty allowlist).
|
||||
- Legacy key: `channels.matrix.rooms` (same shape as `groups`).
|
||||
|
||||
## Threads
|
||||
|
||||
- Reply threading is supported.
|
||||
- `channels.matrix.threadReplies` controls whether replies stay in threads:
|
||||
- `off`, `inbound` (default), `always`
|
||||
- `channels.matrix.replyToMode` controls reply-to metadata when not replying in a thread:
|
||||
- `off` (default), `first`, `all`
|
||||
|
||||
## 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 |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Run this ladder first:
|
||||
|
||||
```bash
|
||||
openclaw status
|
||||
openclaw gateway status
|
||||
openclaw logs --follow
|
||||
openclaw doctor
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
Then confirm DM pairing state if needed:
|
||||
|
||||
```bash
|
||||
openclaw pairing list matrix
|
||||
```
|
||||
|
||||
Common failures:
|
||||
|
||||
- Logged in but room messages ignored: room blocked by `groupPolicy` or room allowlist.
|
||||
- DMs ignored: sender pending approval when `channels.matrix.dm.policy="pairing"`.
|
||||
- Encrypted rooms fail: crypto support or encryption settings mismatch.
|
||||
|
||||
For triage flow: [/channels/troubleshooting](/channels/troubleshooting).
|
||||
|
||||
## Configuration reference (Matrix)
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
|
||||
- `channels.matrix.enabled`: enable/disable channel startup.
|
||||
- `channels.matrix.homeserver`: homeserver URL.
|
||||
- `channels.matrix.userId`: Matrix user ID (optional with access token).
|
||||
- `channels.matrix.accessToken`: access token.
|
||||
- `channels.matrix.password`: password for login (token stored).
|
||||
- `channels.matrix.deviceName`: device display name.
|
||||
- `channels.matrix.encryption`: enable E2EE (default: false).
|
||||
- `channels.matrix.initialSyncLimit`: initial sync limit.
|
||||
- `channels.matrix.threadReplies`: `off | inbound | always` (default: inbound).
|
||||
- `channels.matrix.textChunkLimit`: outbound text chunk size (chars).
|
||||
- `channels.matrix.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.matrix.dm.policy`: `pairing | allowlist | open | disabled` (default: pairing).
|
||||
- `channels.matrix.dm.allowFrom`: DM allowlist (full Matrix user IDs). `open` requires `"*"`. The wizard resolves names to IDs when possible.
|
||||
- `channels.matrix.groupPolicy`: `allowlist | open | disabled` (default: allowlist).
|
||||
- `channels.matrix.groupAllowFrom`: allowlisted senders for group messages (full Matrix user IDs).
|
||||
- `channels.matrix.allowlistOnly`: force allowlist rules for DMs + rooms.
|
||||
- `channels.matrix.groups`: group allowlist + per-room settings map.
|
||||
- `channels.matrix.rooms`: legacy group allowlist/config.
|
||||
- `channels.matrix.replyToMode`: reply-to mode for threads/tags.
|
||||
- `channels.matrix.mediaMaxMb`: inbound/outbound media cap (MB).
|
||||
- `channels.matrix.autoJoin`: invite handling (`always | allowlist | off`, default: always).
|
||||
- `channels.matrix.autoJoinAllowlist`: allowed room IDs/aliases for auto-join.
|
||||
- `channels.matrix.accounts`: multi-account configuration keyed by account ID (each account inherits top-level settings).
|
||||
- `channels.matrix.actions`: per-action tool gating (reactions/messages/pins/memberInfo/channelInfo).
|
||||
160
openclaw/docs/channels/mattermost.md
Normal file
@@ -0,0 +1,160 @@
|
||||
---
|
||||
summary: "Mattermost bot setup and OpenClaw config"
|
||||
read_when:
|
||||
- Setting up Mattermost
|
||||
- Debugging Mattermost routing
|
||||
title: "Mattermost"
|
||||
---
|
||||
|
||||
# Mattermost (plugin)
|
||||
|
||||
Status: supported via plugin (bot token + WebSocket events). Channels, groups, and DMs are supported.
|
||||
Mattermost is a self-hostable team messaging platform; see the official site at
|
||||
[mattermost.com](https://mattermost.com) for product details and downloads.
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
If you choose Mattermost during configure/onboarding and a git checkout is detected,
|
||||
OpenClaw will offer the local install path automatically.
|
||||
|
||||
Details: [Plugins](/tools/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.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
enabled: true,
|
||||
botToken: "mm-token",
|
||||
baseUrl: "https://chat.example.com",
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Environment variables (default account)
|
||||
|
||||
Set these on the gateway host if you prefer env vars:
|
||||
|
||||
- `MATTERMOST_BOT_TOKEN=...`
|
||||
- `MATTERMOST_URL=https://chat.example.com`
|
||||
|
||||
Env vars apply only to the **default** account (`default`). Other accounts must use config values.
|
||||
|
||||
## Chat modes
|
||||
|
||||
Mattermost responds to DMs automatically. Channel behavior is controlled by `chatmode`:
|
||||
|
||||
- `oncall` (default): respond only when @mentioned in channels.
|
||||
- `onmessage`: respond to every channel message.
|
||||
- `onchar`: respond when a message starts with a trigger prefix.
|
||||
|
||||
Config example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
chatmode: "onchar",
|
||||
oncharPrefixes: [">", "!"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `onchar` still responds to explicit @mentions.
|
||||
- `channels.mattermost.requireMention` is honored for legacy configs but `chatmode` is preferred.
|
||||
|
||||
## Access control (DMs)
|
||||
|
||||
- Default: `channels.mattermost.dmPolicy = "pairing"` (unknown senders get a pairing code).
|
||||
- Approve via:
|
||||
- `openclaw pairing list mattermost`
|
||||
- `openclaw pairing approve mattermost <CODE>`
|
||||
- Public DMs: `channels.mattermost.dmPolicy="open"` plus `channels.mattermost.allowFrom=["*"]`.
|
||||
|
||||
## Channels (groups)
|
||||
|
||||
- Default: `channels.mattermost.groupPolicy = "allowlist"` (mention-gated).
|
||||
- Allowlist senders with `channels.mattermost.groupAllowFrom` (user IDs recommended).
|
||||
- `@username` matching is mutable and only enabled when `channels.mattermost.dangerouslyAllowNameMatching: true`.
|
||||
- Open channels: `channels.mattermost.groupPolicy="open"` (mention-gated).
|
||||
- Runtime note: if `channels.mattermost` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
## Targets for outbound delivery
|
||||
|
||||
Use these target formats with `openclaw message send` or cron/webhooks:
|
||||
|
||||
- `channel:<id>` for a channel
|
||||
- `user:<id>` for a DM
|
||||
- `@username` for a DM (resolved via the Mattermost API)
|
||||
|
||||
Bare IDs are treated as channels.
|
||||
|
||||
## Reactions (message tool)
|
||||
|
||||
- Use `message action=react` with `channel=mattermost`.
|
||||
- `messageId` is the Mattermost post id.
|
||||
- `emoji` accepts names like `thumbsup` or `:+1:` (colons are optional).
|
||||
- Set `remove=true` (boolean) to remove a reaction.
|
||||
- Reaction add/remove events are forwarded as system events to the routed agent session.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
message action=react channel=mattermost target=channel:<channelId> messageId=<postId> emoji=thumbsup
|
||||
message action=react channel=mattermost target=channel:<channelId> messageId=<postId> emoji=thumbsup remove=true
|
||||
```
|
||||
|
||||
Config:
|
||||
|
||||
- `channels.mattermost.actions.reactions`: enable/disable reaction actions (default true).
|
||||
- Per-account override: `channels.mattermost.accounts.<id>.actions.reactions`.
|
||||
|
||||
## Multi-account
|
||||
|
||||
Mattermost supports multiple accounts under `channels.mattermost.accounts`:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
accounts: {
|
||||
default: { name: "Primary", botToken: "mm-token", baseUrl: "https://chat.example.com" },
|
||||
alerts: { name: "Alerts", botToken: "mm-token-2", baseUrl: "https://alerts.example.com" },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- No replies in channels: ensure the bot is in the channel and mention it (oncall), use a trigger prefix (onchar), or set `chatmode: "onmessage"`.
|
||||
- Auth errors: check the bot token, base URL, and whether the account is enabled.
|
||||
- Multi-account issues: env vars only apply to the `default` account.
|
||||
776
openclaw/docs/channels/msteams.md
Normal file
@@ -0,0 +1,776 @@
|
||||
---
|
||||
summary: "Microsoft Teams bot support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on MS Teams channel features
|
||||
title: "Microsoft Teams"
|
||||
---
|
||||
|
||||
# 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.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
If you choose Teams during configure/onboarding and a git checkout is detected,
|
||||
OpenClaw will offer the local install path automatically.
|
||||
|
||||
Details: [Plugins](/tools/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.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
enabled: true,
|
||||
appId: "<APP_ID>",
|
||||
appPassword: "<APP_PASSWORD>",
|
||||
tenantId: "<TENANT_ID>",
|
||||
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 } },
|
||||
}
|
||||
```
|
||||
|
||||
## Access control (DMs + groups)
|
||||
|
||||
**DM access**
|
||||
|
||||
- Default: `channels.msteams.dmPolicy = "pairing"`. Unknown senders are ignored until approved.
|
||||
- `channels.msteams.allowFrom` should use stable AAD object IDs.
|
||||
- UPNs/display names are mutable; direct matching is disabled by default and only enabled with `channels.msteams.dangerouslyAllowNameMatching: true`.
|
||||
- The wizard can resolve names to IDs via Microsoft Graph when credentials allow.
|
||||
|
||||
**Group access**
|
||||
|
||||
- Default: `channels.msteams.groupPolicy = "allowlist"` (blocked unless you add `groupAllowFrom`). Use `channels.defaults.groupPolicy` to override the default when unset.
|
||||
- `channels.msteams.groupAllowFrom` controls which senders can trigger in group chats/channels (falls back to `channels.msteams.allowFrom`).
|
||||
- Set `groupPolicy: "open"` to allow any member (still mention‑gated by default).
|
||||
- To allow **no channels**, set `channels.msteams.groupPolicy: "disabled"`.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["user@org.com"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Teams + channel allowlist**
|
||||
|
||||
- Scope group/channel replies by listing teams and channels under `channels.msteams.teams`.
|
||||
- Keys 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).
|
||||
- The configure wizard accepts `Team/Channel` entries and stores them for you.
|
||||
- On startup, OpenClaw resolves team/channel and user allowlist names to IDs (when Graph permissions allow)
|
||||
and logs the mapping; unresolved entries are kept as typed.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
groupPolicy: "allowlist",
|
||||
teams: {
|
||||
"My Team": {
|
||||
channels: {
|
||||
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.
|
||||
4. Upload/install the Teams app into a team (or personal scope for DMs).
|
||||
5. Configure `msteams` in `~/.openclaw/openclaw.json` (or env vars) and start the gateway.
|
||||
6. The gateway listens for Bot Framework webhook traffic on `/api/messages` by default.
|
||||
|
||||
## Azure Bot Setup (Prerequisites)
|
||||
|
||||
Before configuring OpenClaw, you need to create an Azure Bot resource.
|
||||
|
||||
### Step 1: Create Azure Bot
|
||||
|
||||
1. Go to [Create Azure Bot](https://portal.azure.com/#create/Microsoft.AzureBot)
|
||||
2. Fill in the **Basics** tab:
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | -------------------------------------------------------- |
|
||||
| **Bot handle** | Your bot name, e.g., `openclaw-msteams` (must be unique) |
|
||||
| **Subscription** | Select your Azure subscription |
|
||||
| **Resource group** | Create new or use existing |
|
||||
| **Pricing tier** | **Free** for dev/testing |
|
||||
| **Type of App** | **Single Tenant** (recommended - see note below) |
|
||||
| **Creation type** | **Create new Microsoft App ID** |
|
||||
|
||||
> **Deprecation notice:** Creation of new multi-tenant bots was deprecated after 2025-07-31. Use **Single Tenant** for new bots.
|
||||
|
||||
3. Click **Review + create** → **Create** (wait ~1-2 minutes)
|
||||
|
||||
### Step 2: Get Credentials
|
||||
|
||||
1. Go to your Azure Bot resource → **Configuration**
|
||||
2. Copy **Microsoft App ID** → this is your `appId`
|
||||
3. Click **Manage Password** → go to the App Registration
|
||||
4. Under **Certificates & secrets** → **New client secret** → copy the **Value** → this is your `appPassword`
|
||||
5. Go to **Overview** → copy **Directory (tenant) ID** → this is your `tenantId`
|
||||
|
||||
### Step 3: Configure Messaging Endpoint
|
||||
|
||||
1. In Azure Bot → **Configuration**
|
||||
2. Set **Messaging endpoint** to your webhook URL:
|
||||
- Production: `https://your-domain.com/api/messages`
|
||||
- Local dev: Use a tunnel (see [Local Development](#local-development-tunneling) below)
|
||||
|
||||
### Step 4: Enable Teams Channel
|
||||
|
||||
1. In Azure Bot → **Channels**
|
||||
2. Click **Microsoft Teams** → Configure → Save
|
||||
3. Accept the Terms of Service
|
||||
|
||||
## Local Development (Tunneling)
|
||||
|
||||
Teams can't reach `localhost`. Use a tunnel for local development:
|
||||
|
||||
**Option A: ngrok**
|
||||
|
||||
```bash
|
||||
ngrok http 3978
|
||||
# Copy the https URL, e.g., https://abc123.ngrok.io
|
||||
# Set messaging endpoint to: https://abc123.ngrok.io/api/messages
|
||||
```
|
||||
|
||||
**Option B: Tailscale Funnel**
|
||||
|
||||
```bash
|
||||
tailscale funnel 3978
|
||||
# Use your Tailscale funnel URL as the messaging endpoint
|
||||
```
|
||||
|
||||
## Teams Developer Portal (Alternative)
|
||||
|
||||
Instead of manually creating a manifest ZIP, you can use the [Teams Developer Portal](https://dev.teams.microsoft.com/apps):
|
||||
|
||||
1. Click **+ New app**
|
||||
2. Fill in basic info (name, description, developer info)
|
||||
3. Go to **App features** → **Bot**
|
||||
4. Select **Enter a bot ID manually** and paste your Azure Bot App ID
|
||||
5. Check scopes: **Personal**, **Team**, **Group Chat**
|
||||
6. Click **Distribute** → **Download app package**
|
||||
7. In Teams: **Apps** → **Manage your apps** → **Upload a custom app** → select the ZIP
|
||||
|
||||
This is often easier than hand-editing JSON manifests.
|
||||
|
||||
## Testing the Bot
|
||||
|
||||
**Option A: Azure Web Chat (verify webhook first)**
|
||||
|
||||
1. In Azure Portal → your Azure Bot resource → **Test in Web Chat**
|
||||
2. Send a message - you should see a response
|
||||
3. This confirms your webhook endpoint works before Teams setup
|
||||
|
||||
**Option B: Teams (after app installation)**
|
||||
|
||||
1. Install the Teams app (sideload or org catalog)
|
||||
2. Find the bot in Teams and send a DM
|
||||
3. Check gateway logs for incoming activity
|
||||
|
||||
## Setup (minimal text-only)
|
||||
|
||||
1. **Install the Microsoft Teams plugin**
|
||||
- From npm: `openclaw plugins install @openclaw/msteams`
|
||||
- From a local checkout: `openclaw plugins install ./extensions/msteams`
|
||||
|
||||
2. **Bot registration**
|
||||
- Create an Azure Bot (see above) and note:
|
||||
- App ID
|
||||
- Client secret (App password)
|
||||
- Tenant ID (single-tenant)
|
||||
|
||||
3. **Teams app manifest**
|
||||
- Include a `bot` entry with `botId = <App ID>`.
|
||||
- Scopes: `personal`, `team`, `groupChat`.
|
||||
- `supportsFiles: true` (required for personal scope file handling).
|
||||
- Add RSC permissions (below).
|
||||
- Create icons: `outline.png` (32x32) and `color.png` (192x192).
|
||||
- Zip all three files together: `manifest.json`, `outline.png`, `color.png`.
|
||||
|
||||
4. **Configure OpenClaw**
|
||||
|
||||
```json
|
||||
{
|
||||
"msteams": {
|
||||
"enabled": true,
|
||||
"appId": "<APP_ID>",
|
||||
"appPassword": "<APP_PASSWORD>",
|
||||
"tenantId": "<TENANT_ID>",
|
||||
"webhook": { "port": 3978, "path": "/api/messages" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also use environment variables instead of config keys:
|
||||
- `MSTEAMS_APP_ID`
|
||||
- `MSTEAMS_APP_PASSWORD`
|
||||
- `MSTEAMS_TENANT_ID`
|
||||
|
||||
5. **Bot endpoint**
|
||||
- Set the Azure Bot Messaging Endpoint to:
|
||||
- `https://<host>:3978/api/messages` (or your chosen path/port).
|
||||
|
||||
6. **Run the gateway**
|
||||
- The Teams channel starts automatically when the 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["<user_id>"].historyLimit`.
|
||||
|
||||
## Current Teams RSC Permissions (Manifest)
|
||||
|
||||
These are the **existing resourceSpecific permissions** in our Teams app manifest. They only apply inside the team/chat where the app is installed.
|
||||
|
||||
**For channels (team scope):**
|
||||
|
||||
- `ChannelMessage.Read.Group` (Application) - receive all channel messages without @mention
|
||||
- `ChannelMessage.Send.Group` (Application)
|
||||
- `Member.Read.Group` (Application)
|
||||
- `Owner.Read.Group` (Application)
|
||||
- `ChannelSettings.Read.Group` (Application)
|
||||
- `TeamMember.Read.Group` (Application)
|
||||
- `TeamSettings.Read.Group` (Application)
|
||||
|
||||
**For group chats:**
|
||||
|
||||
- `ChatMessage.Read.Chat` (Application) - receive all group chat messages without @mention
|
||||
|
||||
## Example Teams Manifest (redacted)
|
||||
|
||||
Minimal, valid example with the required fields. Replace IDs and URLs.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.23/MicrosoftTeams.schema.json",
|
||||
"manifestVersion": "1.23",
|
||||
"version": "1.0.0",
|
||||
"id": "00000000-0000-0000-0000-000000000000",
|
||||
"name": { "short": "OpenClaw" },
|
||||
"developer": {
|
||||
"name": "Your Org",
|
||||
"websiteUrl": "https://example.com",
|
||||
"privacyUrl": "https://example.com/privacy",
|
||||
"termsOfUseUrl": "https://example.com/terms"
|
||||
},
|
||||
"description": { "short": "OpenClaw in Teams", "full": "OpenClaw in Teams" },
|
||||
"icons": { "outline": "outline.png", "color": "color.png" },
|
||||
"accentColor": "#5B6DEF",
|
||||
"bots": [
|
||||
{
|
||||
"botId": "11111111-1111-1111-1111-111111111111",
|
||||
"scopes": ["personal", "team", "groupChat"],
|
||||
"isNotificationOnly": false,
|
||||
"supportsCalling": false,
|
||||
"supportsVideo": false,
|
||||
"supportsFiles": true
|
||||
}
|
||||
],
|
||||
"webApplicationInfo": {
|
||||
"id": "11111111-1111-1111-1111-111111111111"
|
||||
},
|
||||
"authorization": {
|
||||
"permissions": {
|
||||
"resourceSpecific": [
|
||||
{ "name": "ChannelMessage.Read.Group", "type": "Application" },
|
||||
{ "name": "ChannelMessage.Send.Group", "type": "Application" },
|
||||
{ "name": "Member.Read.Group", "type": "Application" },
|
||||
{ "name": "Owner.Read.Group", "type": "Application" },
|
||||
{ "name": "ChannelSettings.Read.Group", "type": "Application" },
|
||||
{ "name": "TeamMember.Read.Group", "type": "Application" },
|
||||
{ "name": "TeamSettings.Read.Group", "type": "Application" },
|
||||
{ "name": "ChatMessage.Read.Chat", "type": "Application" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Manifest caveats (must-have fields)
|
||||
|
||||
- `bots[].botId` **must** match the Azure Bot App ID.
|
||||
- `webApplicationInfo.id` **must** match the Azure Bot App ID.
|
||||
- `bots[].scopes` must include the surfaces you plan to use (`personal`, `team`, `groupChat`).
|
||||
- `bots[].supportsFiles: true` is required for file handling in personal scope.
|
||||
- `authorization.permissions.resourceSpecific` must include channel read/send if you want channel traffic.
|
||||
|
||||
### Updating an existing app
|
||||
|
||||
To update an already-installed Teams app (e.g., to add RSC permissions):
|
||||
|
||||
1. Update your `manifest.json` with the new settings
|
||||
2. **Increment the `version` field** (e.g., `1.0.0` → `1.1.0`)
|
||||
3. **Re-zip** the manifest with icons (`manifest.json`, `outline.png`, `color.png`)
|
||||
4. Upload the new zip:
|
||||
- **Option A (Teams Admin Center):** Teams Admin Center → Teams apps → Manage apps → find your app → Upload new version
|
||||
- **Option B (Sideload):** In Teams → Apps → Manage your apps → Upload a custom app
|
||||
5. **For team channels:** Reinstall the app in each team for new permissions to take effect
|
||||
6. **Fully quit and relaunch Teams** (not just close the window) to clear cached app metadata
|
||||
|
||||
## Capabilities: RSC only vs Graph
|
||||
|
||||
### With **Teams RSC only** (app installed, no Graph API permissions)
|
||||
|
||||
Works:
|
||||
|
||||
- Read channel message **text** content.
|
||||
- Send channel message **text** content.
|
||||
- Receive **personal (DM)** file attachments.
|
||||
|
||||
Does NOT work:
|
||||
|
||||
- Channel/group **image or file contents** (payload only includes HTML stub).
|
||||
- Downloading attachments stored in SharePoint/OneDrive.
|
||||
- Reading message history (beyond the live webhook event).
|
||||
|
||||
### With **Teams RSC + Microsoft Graph Application permissions**
|
||||
|
||||
Adds:
|
||||
|
||||
- Downloading hosted contents (images pasted into messages).
|
||||
- Downloading file attachments stored in SharePoint/OneDrive.
|
||||
- Reading channel/chat message history via Graph.
|
||||
|
||||
### RSC vs Graph API
|
||||
|
||||
| Capability | RSC Permissions | Graph API |
|
||||
| ----------------------- | -------------------- | ----------------------------------- |
|
||||
| **Real-time messages** | Yes (via webhook) | No (polling only) |
|
||||
| **Historical messages** | No | Yes (can query history) |
|
||||
| **Setup complexity** | App manifest only | Requires admin consent + token flow |
|
||||
| **Works offline** | No (must be running) | Yes (query anytime) |
|
||||
|
||||
**Bottom line:** RSC is for real-time listening; Graph API is for historical access. For catching up on missed messages while offline, you need Graph API with `ChannelMessage.Read.All` (requires admin consent).
|
||||
|
||||
## Graph-enabled media + history (required for channels)
|
||||
|
||||
If you need images/files in **channels** or want to fetch **message history**, you must enable Microsoft Graph permissions and grant admin consent.
|
||||
|
||||
1. In Entra ID (Azure AD) **App Registration**, add Microsoft Graph **Application permissions**:
|
||||
- `ChannelMessage.Read.All` (channel attachments + history)
|
||||
- `Chat.Read.All` or `ChatMessage.Read.All` (group chats)
|
||||
2. **Grant admin consent** for the tenant.
|
||||
3. Bump the Teams app **manifest version**, re-upload, and **reinstall the app in Teams**.
|
||||
4. **Fully quit and relaunch Teams** to clear cached app metadata.
|
||||
|
||||
**Additional permission for user mentions:** User @mentions work out of the box for users in the conversation. However, if you want to dynamically search and mention users who are **not in the current conversation**, add `User.Read.All` (Application) permission and grant admin consent.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### Webhook timeouts
|
||||
|
||||
Teams delivers messages via HTTP webhook. If processing takes too long (e.g., slow LLM responses), you may see:
|
||||
|
||||
- Gateway timeouts
|
||||
- Teams retrying the message (causing duplicates)
|
||||
- Dropped replies
|
||||
|
||||
OpenClaw handles this by returning quickly and sending replies proactively, but very slow responses may still cause issues.
|
||||
|
||||
### Formatting
|
||||
|
||||
Teams markdown is more limited than Slack or Discord:
|
||||
|
||||
- Basic formatting works: **bold**, _italic_, `code`, links
|
||||
- Complex markdown (tables, nested lists) may not render correctly
|
||||
- Adaptive Cards are supported for polls and arbitrary card sends (see below)
|
||||
|
||||
## Configuration
|
||||
|
||||
Key settings (see `/gateway/configuration` for shared channel patterns):
|
||||
|
||||
- `channels.msteams.enabled`: enable/disable the channel.
|
||||
- `channels.msteams.appId`, `channels.msteams.appPassword`, `channels.msteams.tenantId`: bot credentials.
|
||||
- `channels.msteams.webhook.port` (default `3978`)
|
||||
- `channels.msteams.webhook.path` (default `/api/messages`)
|
||||
- `channels.msteams.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing)
|
||||
- `channels.msteams.allowFrom`: DM allowlist (AAD object IDs recommended). The wizard resolves names to IDs during setup when Graph access is available.
|
||||
- `channels.msteams.dangerouslyAllowNameMatching`: break-glass toggle to re-enable mutable UPN/display-name matching.
|
||||
- `channels.msteams.textChunkLimit`: outbound text chunk size.
|
||||
- `channels.msteams.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.msteams.mediaAllowHosts`: allowlist for inbound attachment hosts (defaults to Microsoft/Teams domains).
|
||||
- `channels.msteams.mediaAuthAllowHosts`: allowlist for attaching Authorization headers on media retries (defaults to Graph + Bot Framework hosts).
|
||||
- `channels.msteams.requireMention`: require @mention in channels/groups (default true).
|
||||
- `channels.msteams.replyStyle`: `thread | top-level` (see [Reply Style](#reply-style-threads-vs-posts)).
|
||||
- `channels.msteams.teams.<teamId>.replyStyle`: per-team override.
|
||||
- `channels.msteams.teams.<teamId>.requireMention`: per-team override.
|
||||
- `channels.msteams.teams.<teamId>.tools`: default per-team tool policy overrides (`allow`/`deny`/`alsoAllow`) used when a channel override is missing.
|
||||
- `channels.msteams.teams.<teamId>.toolsBySender`: default per-team per-sender tool policy overrides (`"*"` wildcard supported).
|
||||
- `channels.msteams.teams.<teamId>.channels.<conversationId>.replyStyle`: per-channel override.
|
||||
- `channels.msteams.teams.<teamId>.channels.<conversationId>.requireMention`: per-channel override.
|
||||
- `channels.msteams.teams.<teamId>.channels.<conversationId>.tools`: per-channel tool policy overrides (`allow`/`deny`/`alsoAllow`).
|
||||
- `channels.msteams.teams.<teamId>.channels.<conversationId>.toolsBySender`: per-channel per-sender tool policy overrides (`"*"` wildcard supported).
|
||||
- `toolsBySender` keys should use explicit prefixes:
|
||||
`id:`, `e164:`, `username:`, `name:` (legacy unprefixed keys still map to `id:` only).
|
||||
- `channels.msteams.sharePointSiteId`: SharePoint site ID for file uploads in group chats/channels (see [Sending files in group chats](#sending-files-in-group-chats)).
|
||||
|
||||
## Routing & Sessions
|
||||
|
||||
- Session keys follow the standard agent format (see [/concepts/session](/concepts/session)):
|
||||
- Direct messages share the main session (`agent:<agentId>:<mainKey>`).
|
||||
- Channel/group messages use conversation id:
|
||||
- `agent:<agentId>:msteams:channel:<conversationId>`
|
||||
- `agent:<agentId>:msteams:group:<conversationId>`
|
||||
|
||||
## Reply Style: Threads vs Posts
|
||||
|
||||
Teams recently introduced two channel UI styles over the same underlying data model:
|
||||
|
||||
| Style | Description | Recommended `replyStyle` |
|
||||
| ------------------------ | --------------------------------------------------------- | ------------------------ |
|
||||
| **Posts** (classic) | Messages appear as cards with threaded replies underneath | `thread` (default) |
|
||||
| **Threads** (Slack-like) | Messages flow linearly, more like Slack | `top-level` |
|
||||
|
||||
**The problem:** The Teams API does not expose which UI style a channel uses. If you use the wrong `replyStyle`:
|
||||
|
||||
- `thread` in a Threads-style channel → replies appear nested awkwardly
|
||||
- `top-level` in a Posts-style channel → replies appear as separate top-level posts instead of in-thread
|
||||
|
||||
**Solution:** Configure `replyStyle` per-channel based on how the channel is set up:
|
||||
|
||||
```json
|
||||
{
|
||||
"msteams": {
|
||||
"replyStyle": "thread",
|
||||
"teams": {
|
||||
"19:abc...@thread.tacv2": {
|
||||
"channels": {
|
||||
"19:xyz...@thread.tacv2": {
|
||||
"replyStyle": "top-level"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Attachments & Images
|
||||
|
||||
**Current limitations:**
|
||||
|
||||
- **DMs:** Images and file attachments work via Teams bot file APIs.
|
||||
- **Channels/groups:** Attachments live in M365 storage (SharePoint/OneDrive). The webhook payload only includes an HTML stub, not the actual file bytes. **Graph API permissions are required** to download channel attachments.
|
||||
|
||||
Without Graph permissions, channel messages with images will be received as text-only (the image content is not accessible to the bot).
|
||||
By default, OpenClaw only downloads media from Microsoft/Teams hostnames. Override with `channels.msteams.mediaAllowHosts` (use `["*"]` to allow any host).
|
||||
Authorization headers are only attached for hosts in `channels.msteams.mediaAuthAllowHosts` (defaults to Graph + Bot Framework hosts). Keep this list strict (avoid multi-tenant suffixes).
|
||||
|
||||
## Sending files in group chats
|
||||
|
||||
Bots can send files in DMs using the FileConsentCard flow (built-in). However, **sending files in group chats/channels** requires additional setup:
|
||||
|
||||
| Context | How files are sent | Setup needed |
|
||||
| ------------------------ | -------------------------------------------- | ----------------------------------------------- |
|
||||
| **DMs** | FileConsentCard → user accepts → bot uploads | Works out of the box |
|
||||
| **Group chats/channels** | Upload to SharePoint → share link | Requires `sharePointSiteId` + Graph permissions |
|
||||
| **Images (any context)** | Base64-encoded inline | Works out of the box |
|
||||
|
||||
### Why group chats need SharePoint
|
||||
|
||||
Bots don't have a personal OneDrive drive (the `/me/drive` Graph API endpoint doesn't work for application identities). To send files in group chats/channels, the bot uploads to a **SharePoint site** and creates a sharing link.
|
||||
|
||||
### Setup
|
||||
|
||||
1. **Add Graph API permissions** in Entra ID (Azure AD) → App Registration:
|
||||
- `Sites.ReadWrite.All` (Application) - upload files to SharePoint
|
||||
- `Chat.Read.All` (Application) - optional, enables per-user sharing links
|
||||
|
||||
2. **Grant admin consent** for the tenant.
|
||||
|
||||
3. **Get your SharePoint site ID:**
|
||||
|
||||
```bash
|
||||
# Via Graph Explorer or curl with a valid token:
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
"https://graph.microsoft.com/v1.0/sites/{hostname}:/{site-path}"
|
||||
|
||||
# Example: for a site at "contoso.sharepoint.com/sites/BotFiles"
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
"https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/BotFiles"
|
||||
|
||||
# Response includes: "id": "contoso.sharepoint.com,guid1,guid2"
|
||||
```
|
||||
|
||||
4. **Configure OpenClaw:**
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
// ... other config ...
|
||||
sharePointSiteId: "contoso.sharepoint.com,guid1,guid2",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Sharing behavior
|
||||
|
||||
| Permission | Sharing behavior |
|
||||
| --------------------------------------- | --------------------------------------------------------- |
|
||||
| `Sites.ReadWrite.All` only | Organization-wide sharing link (anyone in org can access) |
|
||||
| `Sites.ReadWrite.All` + `Chat.Read.All` | Per-user sharing link (only chat members can access) |
|
||||
|
||||
Per-user sharing is more secure as only the chat participants can access the file. If `Chat.Read.All` permission is missing, the bot falls back to organization-wide sharing.
|
||||
|
||||
### Fallback behavior
|
||||
|
||||
| Scenario | Result |
|
||||
| ------------------------------------------------- | -------------------------------------------------- |
|
||||
| Group chat + file + `sharePointSiteId` configured | Upload to SharePoint, send sharing link |
|
||||
| Group chat + file + no `sharePointSiteId` | Attempt OneDrive upload (may fail), send text only |
|
||||
| Personal chat + file | FileConsentCard flow (works without SharePoint) |
|
||||
| Any context + image | Base64-encoded inline (works without SharePoint) |
|
||||
|
||||
### Files stored location
|
||||
|
||||
Uploaded files are stored in a `/OpenClawShared/` folder in the configured SharePoint site's default document library.
|
||||
|
||||
## Polls (Adaptive Cards)
|
||||
|
||||
OpenClaw sends Teams polls as Adaptive Cards (there is no native Teams poll API).
|
||||
|
||||
- CLI: `openclaw message poll --channel msteams --target conversation:<id> ...`
|
||||
- Votes are recorded by the gateway in `~/.openclaw/msteams-polls.json`.
|
||||
- The gateway must stay online to record votes.
|
||||
- Polls do not auto-post result summaries yet (inspect the store file if needed).
|
||||
|
||||
## Adaptive Cards (arbitrary)
|
||||
|
||||
Send any Adaptive Card JSON to Teams users or conversations using the `message` tool or CLI.
|
||||
|
||||
The `card` parameter accepts an Adaptive Card JSON object. When `card` is provided, the message text is optional.
|
||||
|
||||
**Agent tool:**
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "send",
|
||||
"channel": "msteams",
|
||||
"target": "user:<id>",
|
||||
"card": {
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.5",
|
||||
"body": [{ "type": "TextBlock", "text": "Hello!" }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**CLI:**
|
||||
|
||||
```bash
|
||||
openclaw message send --channel msteams \
|
||||
--target "conversation:19:abc...@thread.tacv2" \
|
||||
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello!"}]}'
|
||||
```
|
||||
|
||||
See [Adaptive Cards documentation](https://adaptivecards.io/) for card schema and examples. For target format details, see [Target formats](#target-formats) below.
|
||||
|
||||
## Target formats
|
||||
|
||||
MSTeams targets use prefixes to distinguish between users and conversations:
|
||||
|
||||
| Target type | Format | Example |
|
||||
| ------------------- | -------------------------------- | --------------------------------------------------- |
|
||||
| User (by ID) | `user:<aad-object-id>` | `user:40a1a0ed-4ff2-4164-a219-55518990c197` |
|
||||
| User (by name) | `user:<display-name>` | `user:John Smith` (requires Graph API) |
|
||||
| Group/channel | `conversation:<conversation-id>` | `conversation:19:abc123...@thread.tacv2` |
|
||||
| Group/channel (raw) | `<conversation-id>` | `19:abc123...@thread.tacv2` (if contains `@thread`) |
|
||||
|
||||
**CLI examples:**
|
||||
|
||||
```bash
|
||||
# Send to a user by ID
|
||||
openclaw message send --channel msteams --target "user:40a1a0ed-..." --message "Hello"
|
||||
|
||||
# Send to a user by display name (triggers Graph API lookup)
|
||||
openclaw message send --channel msteams --target "user:John Smith" --message "Hello"
|
||||
|
||||
# Send to a group chat or channel
|
||||
openclaw message send --channel msteams --target "conversation:19:abc...@thread.tacv2" --message "Hello"
|
||||
|
||||
# Send an Adaptive Card to a conversation
|
||||
openclaw message send --channel msteams --target "conversation:19:abc...@thread.tacv2" \
|
||||
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello"}]}'
|
||||
```
|
||||
|
||||
**Agent tool examples:**
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "send",
|
||||
"channel": "msteams",
|
||||
"target": "user:John Smith",
|
||||
"message": "Hello!"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "send",
|
||||
"channel": "msteams",
|
||||
"target": "conversation:19:abc...@thread.tacv2",
|
||||
"card": {
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.5",
|
||||
"body": [{ "type": "TextBlock", "text": "Hello" }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: Without the `user:` prefix, names default to group/team resolution. Always use `user:` when targeting people by display name.
|
||||
|
||||
## Proactive messaging
|
||||
|
||||
- Proactive messages are only possible **after** a user has interacted, because we store conversation references at that point.
|
||||
- See `/gateway/configuration` for `dmPolicy` and allowlist gating.
|
||||
|
||||
## Team and Channel IDs (Common Gotcha)
|
||||
|
||||
The `groupId` query parameter in Teams URLs is **NOT** the team ID used for configuration. Extract IDs from the URL path instead:
|
||||
|
||||
**Team URL:**
|
||||
|
||||
```
|
||||
https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?groupId=...
|
||||
└────────────────────────────┘
|
||||
Team ID (URL-decode this)
|
||||
```
|
||||
|
||||
**Channel URL:**
|
||||
|
||||
```
|
||||
https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=...
|
||||
└─────────────────────────┘
|
||||
Channel ID (URL-decode this)
|
||||
```
|
||||
|
||||
**For config:**
|
||||
|
||||
- Team ID = path segment after `/team/` (URL-decoded, e.g., `19:Bk4j...@thread.tacv2`)
|
||||
- Channel ID = path segment after `/channel/` (URL-decoded)
|
||||
- **Ignore** the `groupId` query parameter
|
||||
|
||||
## Private Channels
|
||||
|
||||
Bots have limited support in private channels:
|
||||
|
||||
| Feature | Standard Channels | Private Channels |
|
||||
| ---------------------------- | ----------------- | ---------------------- |
|
||||
| Bot installation | Yes | Limited |
|
||||
| Real-time messages (webhook) | Yes | May not work |
|
||||
| RSC permissions | Yes | May behave differently |
|
||||
| @mentions | Yes | If bot is accessible |
|
||||
| Graph API history | Yes | Yes (with permissions) |
|
||||
|
||||
**Workarounds if private channels don't work:**
|
||||
|
||||
1. Use standard channels for bot interactions
|
||||
2. Use DMs - users can always message the bot directly
|
||||
3. Use Graph API for historical access (requires `ChannelMessage.Read.All`)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common issues
|
||||
|
||||
- **Images not showing in channels:** Graph permissions or admin consent missing. Reinstall the Teams app and fully quit/reopen Teams.
|
||||
- **No responses in channel:** mentions are required by default; set `channels.msteams.requireMention=false` or configure per team/channel.
|
||||
- **Version mismatch (Teams still shows old manifest):** remove + re-add the app and fully quit Teams to refresh.
|
||||
- **401 Unauthorized from webhook:** Expected when testing manually without Azure JWT - means endpoint is reachable but auth failed. Use Azure Web Chat to test properly.
|
||||
|
||||
### Manifest upload errors
|
||||
|
||||
- **"Icon file cannot be empty":** The manifest references icon files that are 0 bytes. Create valid PNG icons (32x32 for `outline.png`, 192x192 for `color.png`).
|
||||
- **"webApplicationInfo.Id already in use":** The app is still installed in another team/chat. Find and uninstall it first, or wait 5-10 minutes for propagation.
|
||||
- **"Something went wrong" on upload:** Upload via [https://admin.teams.microsoft.com](https://admin.teams.microsoft.com) instead, open browser DevTools (F12) → Network tab, and check the response body for the actual error.
|
||||
- **Sideload failing:** Try "Upload an app to your org's app catalog" instead of "Upload a custom app" - this often bypasses sideload restrictions.
|
||||
|
||||
### RSC permissions not working
|
||||
|
||||
1. Verify `webApplicationInfo.id` matches your bot's App ID exactly
|
||||
2. Re-upload the app and reinstall in the team/chat
|
||||
3. Check if your org admin has blocked RSC permissions
|
||||
4. Confirm you're using the right scope: `ChannelMessage.Read.Group` for teams, `ChatMessage.Read.Chat` for group chats
|
||||
|
||||
## References
|
||||
|
||||
- [Create Azure Bot](https://learn.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) - Azure Bot setup guide
|
||||
- [Teams Developer Portal](https://dev.teams.microsoft.com/apps) - create/manage Teams apps
|
||||
- [Teams app manifest schema](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema)
|
||||
- [Receive channel messages with RSC](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/channel-messages-with-rsc)
|
||||
- [RSC permissions reference](https://learn.microsoft.com/en-us/microsoftteams/platform/graph-api/rsc/resource-specific-consent)
|
||||
- [Teams bot file handling](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/bots-filesv4) (channel/group requires Graph)
|
||||
- [Proactive messaging](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/send-proactive-messages)
|
||||
138
openclaw/docs/channels/nextcloud-talk.md
Normal file
@@ -0,0 +1,138 @@
|
||||
---
|
||||
summary: "Nextcloud Talk support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Nextcloud Talk channel features
|
||||
title: "Nextcloud Talk"
|
||||
---
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
If you choose Nextcloud Talk during configure/onboarding and a git checkout is detected,
|
||||
OpenClaw will offer the local install path automatically.
|
||||
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
|
||||
1. Install the Nextcloud Talk plugin.
|
||||
2. On your Nextcloud server, create a bot:
|
||||
|
||||
```bash
|
||||
./occ talk:bot:install "OpenClaw" "<shared-secret>" "<webhook-url>" --feature reaction
|
||||
```
|
||||
|
||||
3. Enable the bot in the target room settings.
|
||||
4. Configure OpenClaw:
|
||||
- Config: `channels.nextcloud-talk.baseUrl` + `channels.nextcloud-talk.botSecret`
|
||||
- Or env: `NEXTCLOUD_TALK_BOT_SECRET` (default account only)
|
||||
5. Restart the gateway (or finish onboarding).
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
"nextcloud-talk": {
|
||||
enabled: true,
|
||||
baseUrl: "https://cloud.example.com",
|
||||
botSecret: "shared-secret",
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Bots cannot initiate DMs. The user must message the bot first.
|
||||
- Webhook URL must be reachable by the Gateway; set `webhookPublicUrl` if behind a proxy.
|
||||
- Media uploads are not supported by the bot API; media is sent as URLs.
|
||||
- The webhook payload does not distinguish DMs vs rooms; set `apiUser` + `apiPassword` to enable room-type lookups (otherwise DMs are treated as rooms).
|
||||
|
||||
## Access control (DMs)
|
||||
|
||||
- Default: `channels.nextcloud-talk.dmPolicy = "pairing"`. Unknown senders get a pairing code.
|
||||
- Approve via:
|
||||
- `openclaw pairing list nextcloud-talk`
|
||||
- `openclaw pairing approve nextcloud-talk <CODE>`
|
||||
- Public DMs: `channels.nextcloud-talk.dmPolicy="open"` plus `channels.nextcloud-talk.allowFrom=["*"]`.
|
||||
- `allowFrom` matches Nextcloud user IDs only; display names are ignored.
|
||||
|
||||
## Rooms (groups)
|
||||
|
||||
- Default: `channels.nextcloud-talk.groupPolicy = "allowlist"` (mention-gated).
|
||||
- Allowlist rooms with `channels.nextcloud-talk.rooms`:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
"nextcloud-talk": {
|
||||
rooms: {
|
||||
"room-token": { requireMention: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- To allow no rooms, keep the allowlist empty or set `channels.nextcloud-talk.groupPolicy="disabled"`.
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Feature | Status |
|
||||
| --------------- | ------------- |
|
||||
| Direct messages | Supported |
|
||||
| Rooms | Supported |
|
||||
| Threads | Not supported |
|
||||
| Media | URL-only |
|
||||
| Reactions | Supported |
|
||||
| Native commands | Not supported |
|
||||
|
||||
## Configuration reference (Nextcloud Talk)
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
|
||||
- `channels.nextcloud-talk.enabled`: enable/disable channel startup.
|
||||
- `channels.nextcloud-talk.baseUrl`: Nextcloud instance URL.
|
||||
- `channels.nextcloud-talk.botSecret`: bot shared secret.
|
||||
- `channels.nextcloud-talk.botSecretFile`: secret file path.
|
||||
- `channels.nextcloud-talk.apiUser`: API user for room lookups (DM detection).
|
||||
- `channels.nextcloud-talk.apiPassword`: API/app password for room lookups.
|
||||
- `channels.nextcloud-talk.apiPasswordFile`: API password file path.
|
||||
- `channels.nextcloud-talk.webhookPort`: webhook listener port (default: 8788).
|
||||
- `channels.nextcloud-talk.webhookHost`: webhook host (default: 0.0.0.0).
|
||||
- `channels.nextcloud-talk.webhookPath`: webhook path (default: /nextcloud-talk-webhook).
|
||||
- `channels.nextcloud-talk.webhookPublicUrl`: externally reachable webhook URL.
|
||||
- `channels.nextcloud-talk.dmPolicy`: `pairing | allowlist | open | disabled`.
|
||||
- `channels.nextcloud-talk.allowFrom`: DM allowlist (user IDs). `open` requires `"*"`.
|
||||
- `channels.nextcloud-talk.groupPolicy`: `allowlist | open | disabled`.
|
||||
- `channels.nextcloud-talk.groupAllowFrom`: group allowlist (user IDs).
|
||||
- `channels.nextcloud-talk.rooms`: per-room settings and allowlist.
|
||||
- `channels.nextcloud-talk.historyLimit`: group history limit (0 disables).
|
||||
- `channels.nextcloud-talk.dmHistoryLimit`: DM history limit (0 disables).
|
||||
- `channels.nextcloud-talk.dms`: per-DM overrides (historyLimit).
|
||||
- `channels.nextcloud-talk.textChunkLimit`: outbound text chunk size (chars).
|
||||
- `channels.nextcloud-talk.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.nextcloud-talk.blockStreaming`: disable block streaming for this channel.
|
||||
- `channels.nextcloud-talk.blockStreamingCoalesce`: block streaming coalesce tuning.
|
||||
- `channels.nextcloud-talk.mediaMaxMb`: inbound media cap (MB).
|
||||
233
openclaw/docs/channels/nostr.md
Normal file
@@ -0,0 +1,233 @@
|
||||
---
|
||||
summary: "Nostr DM channel via NIP-04 encrypted messages"
|
||||
read_when:
|
||||
- You want OpenClaw to receive DMs via Nostr
|
||||
- You're setting up decentralized messaging
|
||||
title: "Nostr"
|
||||
---
|
||||
|
||||
# Nostr
|
||||
|
||||
**Status:** Optional plugin (disabled by default).
|
||||
|
||||
Nostr is a decentralized protocol for social networking. This channel enables OpenClaw to receive and respond to encrypted direct messages (DMs) via NIP-04.
|
||||
|
||||
## Install (on demand)
|
||||
|
||||
### Onboarding (recommended)
|
||||
|
||||
- The onboarding wizard (`openclaw onboard`) and `openclaw channels add` list optional channel plugins.
|
||||
- Selecting Nostr prompts you to install the plugin on demand.
|
||||
|
||||
Install defaults:
|
||||
|
||||
- **Dev channel + git checkout available:** uses the local plugin path.
|
||||
- **Stable/Beta:** downloads from npm.
|
||||
|
||||
You can always override the choice in the prompt.
|
||||
|
||||
### Manual install
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/nostr
|
||||
```
|
||||
|
||||
Use a local checkout (dev workflows):
|
||||
|
||||
```bash
|
||||
openclaw plugins install --link <path-to-openclaw>/extensions/nostr
|
||||
```
|
||||
|
||||
Restart the Gateway after installing or enabling plugins.
|
||||
|
||||
## Quick setup
|
||||
|
||||
1. Generate a Nostr keypair (if needed):
|
||||
|
||||
```bash
|
||||
# Using nak
|
||||
nak key generate
|
||||
```
|
||||
|
||||
2. Add to config:
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"nostr": {
|
||||
"privateKey": "${NOSTR_PRIVATE_KEY}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Export the key:
|
||||
|
||||
```bash
|
||||
export NOSTR_PRIVATE_KEY="nsec1..."
|
||||
```
|
||||
|
||||
4. Restart the Gateway.
|
||||
|
||||
## Configuration reference
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| ------------ | -------- | ------------------------------------------- | ----------------------------------- |
|
||||
| `privateKey` | string | required | Private key in `nsec` or hex format |
|
||||
| `relays` | string[] | `['wss://relay.damus.io', 'wss://nos.lol']` | Relay URLs (WebSocket) |
|
||||
| `dmPolicy` | string | `pairing` | DM access policy |
|
||||
| `allowFrom` | string[] | `[]` | Allowed sender pubkeys |
|
||||
| `enabled` | boolean | `true` | Enable/disable channel |
|
||||
| `name` | string | - | Display name |
|
||||
| `profile` | object | - | NIP-01 profile metadata |
|
||||
|
||||
## Profile metadata
|
||||
|
||||
Profile data is published as a NIP-01 `kind:0` event. You can manage it from the Control UI (Channels -> Nostr -> Profile) or set it directly in config.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"nostr": {
|
||||
"privateKey": "${NOSTR_PRIVATE_KEY}",
|
||||
"profile": {
|
||||
"name": "openclaw",
|
||||
"displayName": "OpenClaw",
|
||||
"about": "Personal assistant DM bot",
|
||||
"picture": "https://example.com/avatar.png",
|
||||
"banner": "https://example.com/banner.png",
|
||||
"website": "https://example.com",
|
||||
"nip05": "openclaw@example.com",
|
||||
"lud16": "openclaw@example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Profile URLs must use `https://`.
|
||||
- Importing from relays merges fields and preserves local overrides.
|
||||
|
||||
## Access control
|
||||
|
||||
### DM policies
|
||||
|
||||
- **pairing** (default): unknown senders get a pairing code.
|
||||
- **allowlist**: only pubkeys in `allowFrom` can DM.
|
||||
- **open**: public inbound DMs (requires `allowFrom: ["*"]`).
|
||||
- **disabled**: ignore inbound DMs.
|
||||
|
||||
### Allowlist example
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"nostr": {
|
||||
"privateKey": "${NOSTR_PRIVATE_KEY}",
|
||||
"dmPolicy": "allowlist",
|
||||
"allowFrom": ["npub1abc...", "npub1xyz..."]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Key formats
|
||||
|
||||
Accepted formats:
|
||||
|
||||
- **Private key:** `nsec...` or 64-char hex
|
||||
- **Pubkeys (`allowFrom`):** `npub...` or hex
|
||||
|
||||
## Relays
|
||||
|
||||
Defaults: `relay.damus.io` and `nos.lol`.
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"nostr": {
|
||||
"privateKey": "${NOSTR_PRIVATE_KEY}",
|
||||
"relays": ["wss://relay.damus.io", "wss://relay.primal.net", "wss://nostr.wine"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Tips:
|
||||
|
||||
- Use 2-3 relays for redundancy.
|
||||
- Avoid too many relays (latency, duplication).
|
||||
- Paid relays can improve reliability.
|
||||
- Local relays are fine for testing (`ws://localhost:7777`).
|
||||
|
||||
## Protocol support
|
||||
|
||||
| NIP | Status | Description |
|
||||
| ------ | --------- | ------------------------------------- |
|
||||
| NIP-01 | Supported | Basic event format + profile metadata |
|
||||
| NIP-04 | Supported | Encrypted DMs (`kind:4`) |
|
||||
| NIP-17 | Planned | Gift-wrapped DMs |
|
||||
| NIP-44 | Planned | Versioned encryption |
|
||||
|
||||
## Testing
|
||||
|
||||
### Local relay
|
||||
|
||||
```bash
|
||||
# Start strfry
|
||||
docker run -p 7777:7777 ghcr.io/hoytech/strfry
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"nostr": {
|
||||
"privateKey": "${NOSTR_PRIVATE_KEY}",
|
||||
"relays": ["ws://localhost:7777"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Manual test
|
||||
|
||||
1. Note the bot pubkey (npub) from logs.
|
||||
2. Open a Nostr client (Damus, Amethyst, etc.).
|
||||
3. DM the bot pubkey.
|
||||
4. Verify the response.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Not receiving messages
|
||||
|
||||
- Verify the private key is valid.
|
||||
- Ensure relay URLs are reachable and use `wss://` (or `ws://` for local).
|
||||
- Confirm `enabled` is not `false`.
|
||||
- Check Gateway logs for relay connection errors.
|
||||
|
||||
### Not sending responses
|
||||
|
||||
- Check relay accepts writes.
|
||||
- Verify outbound connectivity.
|
||||
- Watch for relay rate limits.
|
||||
|
||||
### Duplicate responses
|
||||
|
||||
- Expected when using multiple relays.
|
||||
- Messages are deduplicated by event ID; only the first delivery triggers a response.
|
||||
|
||||
## Security
|
||||
|
||||
- Never commit private keys.
|
||||
- Use environment variables for keys.
|
||||
- Consider `allowlist` for production bots.
|
||||
|
||||
## Limitations (MVP)
|
||||
|
||||
- Direct messages only (no group chats).
|
||||
- No media attachments.
|
||||
- NIP-04 only (NIP-17 gift-wrap planned).
|
||||
110
openclaw/docs/channels/pairing.md
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
summary: "Pairing overview: approve who can DM you + which nodes can join"
|
||||
read_when:
|
||||
- Setting up DM access control
|
||||
- Pairing a new iOS/Android node
|
||||
- Reviewing OpenClaw security posture
|
||||
title: "Pairing"
|
||||
---
|
||||
|
||||
# Pairing
|
||||
|
||||
“Pairing” is OpenClaw’s explicit **owner approval** step.
|
||||
It is used in two places:
|
||||
|
||||
1. **DM pairing** (who is allowed to talk to the bot)
|
||||
2. **Node pairing** (which devices/nodes are allowed to join the gateway network)
|
||||
|
||||
Security context: [Security](/gateway/security)
|
||||
|
||||
## 1) DM pairing (inbound chat access)
|
||||
|
||||
When a channel is configured with DM policy `pairing`, unknown senders get a short code and their message is **not processed** until you approve.
|
||||
|
||||
Default DM policies are documented in: [Security](/gateway/security)
|
||||
|
||||
Pairing codes:
|
||||
|
||||
- 8 characters, uppercase, no ambiguous chars (`0O1I`).
|
||||
- **Expire after 1 hour**. The bot only sends the pairing message when a new request is created (roughly once per hour per sender).
|
||||
- Pending DM pairing requests are capped at **3 per channel** by default; additional requests are ignored until one expires or is approved.
|
||||
|
||||
### Approve a sender
|
||||
|
||||
```bash
|
||||
openclaw pairing list telegram
|
||||
openclaw pairing approve telegram <CODE>
|
||||
```
|
||||
|
||||
Supported channels: `telegram`, `whatsapp`, `signal`, `imessage`, `discord`, `slack`, `feishu`.
|
||||
|
||||
### Where the state lives
|
||||
|
||||
Stored under `~/.openclaw/credentials/`:
|
||||
|
||||
- Pending requests: `<channel>-pairing.json`
|
||||
- Approved allowlist store:
|
||||
- Default account: `<channel>-allowFrom.json`
|
||||
- Non-default account: `<channel>-<accountId>-allowFrom.json`
|
||||
|
||||
Account scoping behavior:
|
||||
|
||||
- Non-default accounts read/write only their scoped allowlist file.
|
||||
- Default account uses the channel-scoped unscoped allowlist file.
|
||||
|
||||
Treat these as sensitive (they gate access to your assistant).
|
||||
|
||||
## 2) Node device pairing (iOS/Android/macOS/headless nodes)
|
||||
|
||||
Nodes connect to the Gateway as **devices** with `role: node`. The Gateway
|
||||
creates a device pairing request that must be approved.
|
||||
|
||||
### Pair via Telegram (recommended for iOS)
|
||||
|
||||
If you use the `device-pair` plugin, you can do first-time device pairing entirely from Telegram:
|
||||
|
||||
1. In Telegram, message your bot: `/pair`
|
||||
2. The bot replies with two messages: an instruction message and a separate **setup code** message (easy to copy/paste in Telegram).
|
||||
3. On your phone, open the OpenClaw iOS app → Settings → Gateway.
|
||||
4. Paste the setup code and connect.
|
||||
5. Back in Telegram: `/pair approve`
|
||||
|
||||
The setup code is a base64-encoded JSON payload that contains:
|
||||
|
||||
- `url`: the Gateway WebSocket URL (`ws://...` or `wss://...`)
|
||||
- `token`: a short-lived pairing token
|
||||
|
||||
Treat the setup code like a password while it is valid.
|
||||
|
||||
### Approve a node device
|
||||
|
||||
```bash
|
||||
openclaw devices list
|
||||
openclaw devices approve <requestId>
|
||||
openclaw devices reject <requestId>
|
||||
```
|
||||
|
||||
### Node pairing state storage
|
||||
|
||||
Stored under `~/.openclaw/devices/`:
|
||||
|
||||
- `pending.json` (short-lived; pending requests expire)
|
||||
- `paired.json` (paired devices + tokens)
|
||||
|
||||
### Notes
|
||||
|
||||
- The legacy `node.pair.*` API (CLI: `openclaw nodes pending/approve`) is a
|
||||
separate gateway-owned pairing store. WS nodes still require device pairing.
|
||||
|
||||
## Related docs
|
||||
|
||||
- Security model + prompt injection: [Security](/gateway/security)
|
||||
- Updating safely (run doctor): [Updating](/install/updating)
|
||||
- Channel configs:
|
||||
- Telegram: [Telegram](/channels/telegram)
|
||||
- WhatsApp: [WhatsApp](/channels/whatsapp)
|
||||
- Signal: [Signal](/channels/signal)
|
||||
- BlueBubbles (iMessage): [BlueBubbles](/channels/bluebubbles)
|
||||
- iMessage (legacy): [iMessage](/channels/imessage)
|
||||
- Discord: [Discord](/channels/discord)
|
||||
- Slack: [Slack](/channels/slack)
|
||||
325
openclaw/docs/channels/signal.md
Normal file
@@ -0,0 +1,325 @@
|
||||
---
|
||||
summary: "Signal support via signal-cli (JSON-RPC + SSE), setup paths, and number model"
|
||||
read_when:
|
||||
- Setting up Signal support
|
||||
- Debugging Signal send/receive
|
||||
title: "Signal"
|
||||
---
|
||||
|
||||
# Signal (signal-cli)
|
||||
|
||||
Status: external CLI integration. Gateway talks to `signal-cli` over HTTP JSON-RPC + SSE.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- OpenClaw installed on your server (Linux flow below tested on Ubuntu 24).
|
||||
- `signal-cli` available on the host where the gateway runs.
|
||||
- A phone number that can receive one verification SMS (for SMS registration path).
|
||||
- Browser access for Signal captcha (`signalcaptchas.org`) during registration.
|
||||
|
||||
## Quick setup (beginner)
|
||||
|
||||
1. Use a **separate Signal number** for the bot (recommended).
|
||||
2. Install `signal-cli` (Java required if you use the JVM build).
|
||||
3. Choose one setup path:
|
||||
- **Path A (QR link):** `signal-cli link -n "OpenClaw"` and scan with Signal.
|
||||
- **Path B (SMS register):** register a dedicated number with captcha + SMS verification.
|
||||
4. Configure OpenClaw and restart the gateway.
|
||||
5. Send a first DM and approve pairing (`openclaw pairing approve signal <CODE>`).
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
signal: {
|
||||
enabled: true,
|
||||
account: "+15551234567",
|
||||
cliPath: "signal-cli",
|
||||
dmPolicy: "pairing",
|
||||
allowFrom: ["+15557654321"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Field reference:
|
||||
|
||||
| Field | Description |
|
||||
| ----------- | ------------------------------------------------- |
|
||||
| `account` | Bot phone number in E.164 format (`+15551234567`) |
|
||||
| `cliPath` | Path to `signal-cli` (`signal-cli` if on `PATH`) |
|
||||
| `dmPolicy` | DM access policy (`pairing` recommended) |
|
||||
| `allowFrom` | Phone numbers or `uuid:<id>` values allowed to DM |
|
||||
|
||||
## What it is
|
||||
|
||||
- Signal channel via `signal-cli` (not embedded libsignal).
|
||||
- Deterministic routing: replies always go back to Signal.
|
||||
- DMs share the agent's main session; groups are isolated (`agent:<agentId>:signal:group:<groupId>`).
|
||||
|
||||
## Config writes
|
||||
|
||||
By default, Signal is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||
|
||||
Disable with:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: { signal: { configWrites: false } },
|
||||
}
|
||||
```
|
||||
|
||||
## The number model (important)
|
||||
|
||||
- The gateway connects to a **Signal device** (the `signal-cli` account).
|
||||
- If you run the bot on **your personal Signal account**, it will ignore your own messages (loop protection).
|
||||
- For "I text the bot and it replies," use a **separate bot number**.
|
||||
|
||||
## Setup path A: link existing Signal account (QR)
|
||||
|
||||
1. Install `signal-cli` (JVM or native build).
|
||||
2. Link a bot account:
|
||||
- `signal-cli link -n "OpenClaw"` then scan the QR in Signal.
|
||||
3. Configure Signal and start the gateway.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
signal: {
|
||||
enabled: true,
|
||||
account: "+15551234567",
|
||||
cliPath: "signal-cli",
|
||||
dmPolicy: "pairing",
|
||||
allowFrom: ["+15557654321"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Multi-account support: use `channels.signal.accounts` with per-account config and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
||||
|
||||
## Setup path B: register dedicated bot number (SMS, Linux)
|
||||
|
||||
Use this when you want a dedicated bot number instead of linking an existing Signal app account.
|
||||
|
||||
1. Get a number that can receive SMS (or voice verification for landlines).
|
||||
- Use a dedicated bot number to avoid account/session conflicts.
|
||||
2. Install `signal-cli` on the gateway host:
|
||||
|
||||
```bash
|
||||
VERSION=$(curl -Ls -o /dev/null -w %{url_effective} https://github.com/AsamK/signal-cli/releases/latest | sed -e 's/^.*\/v//')
|
||||
curl -L -O "https://github.com/AsamK/signal-cli/releases/download/v${VERSION}/signal-cli-${VERSION}-Linux-native.tar.gz"
|
||||
sudo tar xf "signal-cli-${VERSION}-Linux-native.tar.gz" -C /opt
|
||||
sudo ln -sf /opt/signal-cli /usr/local/bin/
|
||||
signal-cli --version
|
||||
```
|
||||
|
||||
If you use the JVM build (`signal-cli-${VERSION}.tar.gz`), install JRE 25+ first.
|
||||
Keep `signal-cli` updated; upstream notes that old releases can break as Signal server APIs change.
|
||||
|
||||
3. Register and verify the number:
|
||||
|
||||
```bash
|
||||
signal-cli -a +<BOT_PHONE_NUMBER> register
|
||||
```
|
||||
|
||||
If captcha is required:
|
||||
|
||||
1. Open `https://signalcaptchas.org/registration/generate.html`.
|
||||
2. Complete captcha, copy the `signalcaptcha://...` link target from "Open Signal".
|
||||
3. Run from the same external IP as the browser session when possible.
|
||||
4. Run registration again immediately (captcha tokens expire quickly):
|
||||
|
||||
```bash
|
||||
signal-cli -a +<BOT_PHONE_NUMBER> register --captcha '<SIGNALCAPTCHA_URL>'
|
||||
signal-cli -a +<BOT_PHONE_NUMBER> verify <VERIFICATION_CODE>
|
||||
```
|
||||
|
||||
4. Configure OpenClaw, restart gateway, verify channel:
|
||||
|
||||
```bash
|
||||
# If you run the gateway as a user systemd service:
|
||||
systemctl --user restart openclaw-gateway
|
||||
|
||||
# Then verify:
|
||||
openclaw doctor
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
5. Pair your DM sender:
|
||||
- Send any message to the bot number.
|
||||
- Approve code on the server: `openclaw pairing approve signal <PAIRING_CODE>`.
|
||||
- Save the bot number as a contact on your phone to avoid "Unknown contact".
|
||||
|
||||
Important: registering a phone number account with `signal-cli` can de-authenticate the main Signal app session for that number. Prefer a dedicated bot number, or use QR link mode if you need to keep your existing phone app setup.
|
||||
|
||||
Upstream references:
|
||||
|
||||
- `signal-cli` README: `https://github.com/AsamK/signal-cli`
|
||||
- Captcha flow: `https://github.com/AsamK/signal-cli/wiki/Registration-with-captcha`
|
||||
- Linking flow: `https://github.com/AsamK/signal-cli/wiki/Linking-other-devices-(Provisioning)`
|
||||
|
||||
## External daemon mode (httpUrl)
|
||||
|
||||
If you want to manage `signal-cli` yourself (slow JVM cold starts, container init, or shared CPUs), run the daemon separately and point OpenClaw at it:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
signal: {
|
||||
httpUrl: "http://127.0.0.1:8080",
|
||||
autoStart: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
This skips auto-spawn and the startup wait inside OpenClaw. For slow starts when auto-spawning, set `channels.signal.startupTimeoutMs`.
|
||||
|
||||
## Access control (DMs + groups)
|
||||
|
||||
DMs:
|
||||
|
||||
- Default: `channels.signal.dmPolicy = "pairing"`.
|
||||
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
- `openclaw pairing list signal`
|
||||
- `openclaw pairing approve signal <CODE>`
|
||||
- Pairing is the default token exchange for Signal DMs. Details: [Pairing](/channels/pairing)
|
||||
- UUID-only senders (from `sourceUuid`) are stored as `uuid:<id>` in `channels.signal.allowFrom`.
|
||||
|
||||
Groups:
|
||||
|
||||
- `channels.signal.groupPolicy = open | allowlist | disabled`.
|
||||
- `channels.signal.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
||||
- Runtime note: if `channels.signal` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
## How it works (behavior)
|
||||
|
||||
- `signal-cli` runs as a daemon; the gateway reads events via SSE.
|
||||
- Inbound messages are normalized into the shared channel envelope.
|
||||
- Replies always route back to the same number or group.
|
||||
|
||||
## Media + limits
|
||||
|
||||
- Outbound text is chunked to `channels.signal.textChunkLimit` (default 4000).
|
||||
- Optional newline chunking: set `channels.signal.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- Attachments supported (base64 fetched from `signal-cli`).
|
||||
- Default media cap: `channels.signal.mediaMaxMb` (default 8).
|
||||
- Use `channels.signal.ignoreAttachments` to skip downloading media.
|
||||
- Group history context uses `channels.signal.historyLimit` (or `channels.signal.accounts.*.historyLimit`), falling back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
||||
|
||||
## Typing + read receipts
|
||||
|
||||
- **Typing indicators**: OpenClaw sends typing signals via `signal-cli sendTyping` and refreshes them while a reply is running.
|
||||
- **Read receipts**: when `channels.signal.sendReadReceipts` is true, OpenClaw forwards read receipts for allowed DMs.
|
||||
- Signal-cli does not expose read receipts for groups.
|
||||
|
||||
## Reactions (message tool)
|
||||
|
||||
- Use `message action=react` with `channel=signal`.
|
||||
- Targets: sender E.164 or UUID (use `uuid:<id>` from pairing output; bare UUID works too).
|
||||
- `messageId` is the Signal timestamp for the message you’re reacting to.
|
||||
- Group reactions require `targetAuthor` or `targetAuthorUuid`.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
message action=react channel=signal target=uuid:123e4567-e89b-12d3-a456-426614174000 messageId=1737630212345 emoji=🔥
|
||||
message action=react channel=signal target=+15551234567 messageId=1737630212345 emoji=🔥 remove=true
|
||||
message action=react channel=signal target=signal:group:<groupId> targetAuthor=uuid:<sender-uuid> messageId=1737630212345 emoji=✅
|
||||
```
|
||||
|
||||
Config:
|
||||
|
||||
- `channels.signal.actions.reactions`: enable/disable reaction actions (default true).
|
||||
- `channels.signal.reactionLevel`: `off | ack | minimal | extensive`.
|
||||
- `off`/`ack` disables agent reactions (message tool `react` will error).
|
||||
- `minimal`/`extensive` enables agent reactions and sets the guidance level.
|
||||
- Per-account overrides: `channels.signal.accounts.<id>.actions.reactions`, `channels.signal.accounts.<id>.reactionLevel`.
|
||||
|
||||
## Delivery targets (CLI/cron)
|
||||
|
||||
- DMs: `signal:+15551234567` (or plain E.164).
|
||||
- UUID DMs: `uuid:<id>` (or bare UUID).
|
||||
- Groups: `signal:group:<groupId>`.
|
||||
- Usernames: `username:<name>` (if supported by your Signal account).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Run this ladder first:
|
||||
|
||||
```bash
|
||||
openclaw status
|
||||
openclaw gateway status
|
||||
openclaw logs --follow
|
||||
openclaw doctor
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
Then confirm DM pairing state if needed:
|
||||
|
||||
```bash
|
||||
openclaw pairing list signal
|
||||
```
|
||||
|
||||
Common failures:
|
||||
|
||||
- Daemon reachable but no replies: verify account/daemon settings (`httpUrl`, `account`) and receive mode.
|
||||
- DMs ignored: sender is pending pairing approval.
|
||||
- Group messages ignored: group sender/mention gating blocks delivery.
|
||||
- Config validation errors after edits: run `openclaw doctor --fix`.
|
||||
- Signal missing from diagnostics: confirm `channels.signal.enabled: true`.
|
||||
|
||||
Extra checks:
|
||||
|
||||
```bash
|
||||
openclaw pairing list signal
|
||||
pgrep -af signal-cli
|
||||
grep -i "signal" "/tmp/openclaw/openclaw-$(date +%Y-%m-%d).log" | tail -20
|
||||
```
|
||||
|
||||
For triage flow: [/channels/troubleshooting](/channels/troubleshooting).
|
||||
|
||||
## Security notes
|
||||
|
||||
- `signal-cli` stores account keys locally (typically `~/.local/share/signal-cli/data/`).
|
||||
- Back up Signal account state before server migration or rebuild.
|
||||
- Keep `channels.signal.dmPolicy: "pairing"` unless you explicitly want broader DM access.
|
||||
- SMS verification is only needed for registration or recovery flows, but losing control of the number/account can complicate re-registration.
|
||||
|
||||
## Configuration reference (Signal)
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
|
||||
- `channels.signal.enabled`: enable/disable channel startup.
|
||||
- `channels.signal.account`: E.164 for the bot account.
|
||||
- `channels.signal.cliPath`: path to `signal-cli`.
|
||||
- `channels.signal.httpUrl`: full daemon URL (overrides host/port).
|
||||
- `channels.signal.httpHost`, `channels.signal.httpPort`: daemon bind (default 127.0.0.1:8080).
|
||||
- `channels.signal.autoStart`: auto-spawn daemon (default true if `httpUrl` unset).
|
||||
- `channels.signal.startupTimeoutMs`: startup wait timeout in ms (cap 120000).
|
||||
- `channels.signal.receiveMode`: `on-start | manual`.
|
||||
- `channels.signal.ignoreAttachments`: skip attachment downloads.
|
||||
- `channels.signal.ignoreStories`: ignore stories from the daemon.
|
||||
- `channels.signal.sendReadReceipts`: forward read receipts.
|
||||
- `channels.signal.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing).
|
||||
- `channels.signal.allowFrom`: DM allowlist (E.164 or `uuid:<id>`). `open` requires `"*"`. Signal has no usernames; use phone/UUID ids.
|
||||
- `channels.signal.groupPolicy`: `open | allowlist | disabled` (default: allowlist).
|
||||
- `channels.signal.groupAllowFrom`: group sender allowlist.
|
||||
- `channels.signal.historyLimit`: max group messages to include as context (0 disables).
|
||||
- `channels.signal.dmHistoryLimit`: DM history limit in user turns. Per-user overrides: `channels.signal.dms["<phone_or_uuid>"].historyLimit`.
|
||||
- `channels.signal.textChunkLimit`: outbound chunk size (chars).
|
||||
- `channels.signal.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.signal.mediaMaxMb`: inbound/outbound media cap (MB).
|
||||
|
||||
Related global options:
|
||||
|
||||
- `agents.list[].groupChat.mentionPatterns` (Signal does not support native mentions).
|
||||
- `messages.groupChat.mentionPatterns` (global fallback).
|
||||
- `messages.responsePrefix`.
|
||||
535
openclaw/docs/channels/slack.md
Normal file
@@ -0,0 +1,535 @@
|
||||
---
|
||||
summary: "Slack setup and runtime behavior (Socket Mode + HTTP Events API)"
|
||||
read_when:
|
||||
- Setting up Slack or debugging Slack socket/HTTP mode
|
||||
title: "Slack"
|
||||
---
|
||||
|
||||
# Slack
|
||||
|
||||
Status: production-ready for DMs + channels via Slack app integrations. Default mode is Socket Mode; HTTP Events API mode is also supported.
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Pairing" icon="link" href="/channels/pairing">
|
||||
Slack DMs default to pairing mode.
|
||||
</Card>
|
||||
<Card title="Slash commands" icon="terminal" href="/tools/slash-commands">
|
||||
Native command behavior and command catalog.
|
||||
</Card>
|
||||
<Card title="Channel troubleshooting" icon="wrench" href="/channels/troubleshooting">
|
||||
Cross-channel diagnostics and repair playbooks.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Quick setup
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Socket Mode (default)">
|
||||
<Steps>
|
||||
<Step title="Create Slack app and tokens">
|
||||
In Slack app settings:
|
||||
|
||||
- enable **Socket Mode**
|
||||
- create **App Token** (`xapp-...`) with `connections:write`
|
||||
- install app and copy **Bot Token** (`xoxb-...`)
|
||||
</Step>
|
||||
|
||||
<Step title="Configure OpenClaw">
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
enabled: true,
|
||||
mode: "socket",
|
||||
appToken: "xapp-...",
|
||||
botToken: "xoxb-...",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Env fallback (default account only):
|
||||
|
||||
```bash
|
||||
SLACK_APP_TOKEN=xapp-...
|
||||
SLACK_BOT_TOKEN=xoxb-...
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Subscribe app events">
|
||||
Subscribe bot events for:
|
||||
|
||||
- `app_mention`
|
||||
- `message.channels`, `message.groups`, `message.im`, `message.mpim`
|
||||
- `reaction_added`, `reaction_removed`
|
||||
- `member_joined_channel`, `member_left_channel`
|
||||
- `channel_rename`
|
||||
- `pin_added`, `pin_removed`
|
||||
|
||||
Also enable App Home **Messages Tab** for DMs.
|
||||
</Step>
|
||||
|
||||
<Step title="Start gateway">
|
||||
|
||||
```bash
|
||||
openclaw gateway
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="HTTP Events API mode">
|
||||
<Steps>
|
||||
<Step title="Configure Slack app for HTTP">
|
||||
|
||||
- set mode to HTTP (`channels.slack.mode="http"`)
|
||||
- copy Slack **Signing Secret**
|
||||
- set Event Subscriptions + Interactivity + Slash command Request URL to the same webhook path (default `/slack/events`)
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Configure OpenClaw HTTP mode">
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
enabled: true,
|
||||
mode: "http",
|
||||
botToken: "xoxb-...",
|
||||
signingSecret: "your-signing-secret",
|
||||
webhookPath: "/slack/events",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Use unique webhook paths for multi-account HTTP">
|
||||
Per-account HTTP mode is supported.
|
||||
|
||||
Give each account a distinct `webhookPath` so registrations do not collide.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Token model
|
||||
|
||||
- `botToken` + `appToken` are required for Socket Mode.
|
||||
- HTTP mode requires `botToken` + `signingSecret`.
|
||||
- Config tokens override env fallback.
|
||||
- `SLACK_BOT_TOKEN` / `SLACK_APP_TOKEN` env fallback applies only to the default account.
|
||||
- `userToken` (`xoxp-...`) is config-only (no env fallback) and defaults to read-only behavior (`userTokenReadOnly: true`).
|
||||
- Optional: add `chat:write.customize` if you want outgoing messages to use the active agent identity (custom `username` and icon). `icon_emoji` uses `:emoji_name:` syntax.
|
||||
|
||||
<Tip>
|
||||
For actions/directory reads, user token can be preferred when configured. For writes, bot token remains preferred; user-token writes are only allowed when `userTokenReadOnly: false` and bot token is unavailable.
|
||||
</Tip>
|
||||
|
||||
## Access control and routing
|
||||
|
||||
<Tabs>
|
||||
<Tab title="DM policy">
|
||||
`channels.slack.dmPolicy` controls DM access (legacy: `channels.slack.dm.policy`):
|
||||
|
||||
- `pairing` (default)
|
||||
- `allowlist`
|
||||
- `open` (requires `channels.slack.allowFrom` to include `"*"`; legacy: `channels.slack.dm.allowFrom`)
|
||||
- `disabled`
|
||||
|
||||
DM flags:
|
||||
|
||||
- `dm.enabled` (default true)
|
||||
- `channels.slack.allowFrom` (preferred)
|
||||
- `dm.allowFrom` (legacy)
|
||||
- `dm.groupEnabled` (group DMs default false)
|
||||
- `dm.groupChannels` (optional MPIM allowlist)
|
||||
|
||||
Multi-account precedence:
|
||||
|
||||
- `channels.slack.accounts.default.allowFrom` applies only to the `default` account.
|
||||
- Named accounts inherit `channels.slack.allowFrom` when their own `allowFrom` is unset.
|
||||
- Named accounts do not inherit `channels.slack.accounts.default.allowFrom`.
|
||||
|
||||
Pairing in DMs uses `openclaw pairing approve slack <code>`.
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Channel policy">
|
||||
`channels.slack.groupPolicy` controls channel handling:
|
||||
|
||||
- `open`
|
||||
- `allowlist`
|
||||
- `disabled`
|
||||
|
||||
Channel allowlist lives under `channels.slack.channels`.
|
||||
|
||||
Runtime note: if `channels.slack` is completely missing (env-only setup), runtime falls back to `groupPolicy="allowlist"` and logs a warning (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
Name/ID resolution:
|
||||
|
||||
- channel allowlist entries and DM allowlist entries are resolved at startup when token access allows
|
||||
- unresolved entries are kept as configured
|
||||
- inbound authorization matching is ID-first by default; direct username/slug matching requires `channels.slack.dangerouslyAllowNameMatching: true`
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Mentions and channel users">
|
||||
Channel messages are mention-gated by default.
|
||||
|
||||
Mention sources:
|
||||
|
||||
- explicit app mention (`<@botId>`)
|
||||
- mention regex patterns (`agents.list[].groupChat.mentionPatterns`, fallback `messages.groupChat.mentionPatterns`)
|
||||
- implicit reply-to-bot thread behavior
|
||||
|
||||
Per-channel controls (`channels.slack.channels.<id|name>`):
|
||||
|
||||
- `requireMention`
|
||||
- `users` (allowlist)
|
||||
- `allowBots`
|
||||
- `skills`
|
||||
- `systemPrompt`
|
||||
- `tools`, `toolsBySender`
|
||||
- `toolsBySender` key format: `id:`, `e164:`, `username:`, `name:`, or `"*"` wildcard
|
||||
(legacy unprefixed keys still map to `id:` only)
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Commands and slash behavior
|
||||
|
||||
- Native command auto-mode is **off** for Slack (`commands.native: "auto"` does not enable Slack native commands).
|
||||
- Enable native Slack command handlers with `channels.slack.commands.native: true` (or global `commands.native: true`).
|
||||
- When native commands are enabled, register matching slash commands in Slack (`/<command>` names).
|
||||
- If native commands are not enabled, you can run a single configured slash command via `channels.slack.slashCommand`.
|
||||
- Native arg menus now adapt their rendering strategy:
|
||||
- up to 5 options: button blocks
|
||||
- 6-100 options: static select menu
|
||||
- more than 100 options: external select with async option filtering when interactivity options handlers are available
|
||||
- if encoded option values exceed Slack limits, the flow falls back to buttons
|
||||
- For long option payloads, Slash command argument menus use a confirm dialog before dispatching a selected value.
|
||||
|
||||
Default slash command settings:
|
||||
|
||||
- `enabled: false`
|
||||
- `name: "openclaw"`
|
||||
- `sessionPrefix: "slack:slash"`
|
||||
- `ephemeral: true`
|
||||
|
||||
Slash sessions use isolated keys:
|
||||
|
||||
- `agent:<agentId>:slack:slash:<userId>`
|
||||
|
||||
and still route command execution against the target conversation session (`CommandTargetSessionKey`).
|
||||
|
||||
## Threading, sessions, and reply tags
|
||||
|
||||
- DMs route as `direct`; channels as `channel`; MPIMs as `group`.
|
||||
- With default `session.dmScope=main`, Slack DMs collapse to agent main session.
|
||||
- Channel sessions: `agent:<agentId>:slack:channel:<channelId>`.
|
||||
- Thread replies can create thread session suffixes (`:thread:<threadTs>`) when applicable.
|
||||
- `channels.slack.thread.historyScope` default is `thread`; `thread.inheritParent` default is `false`.
|
||||
- `channels.slack.thread.initialHistoryLimit` controls how many existing thread messages are fetched when a new thread session starts (default `20`; set `0` to disable).
|
||||
|
||||
Reply threading controls:
|
||||
|
||||
- `channels.slack.replyToMode`: `off|first|all` (default `off`)
|
||||
- `channels.slack.replyToModeByChatType`: per `direct|group|channel`
|
||||
- legacy fallback for direct chats: `channels.slack.dm.replyToMode`
|
||||
|
||||
Manual reply tags are supported:
|
||||
|
||||
- `[[reply_to_current]]`
|
||||
- `[[reply_to:<id>]]`
|
||||
|
||||
Note: `replyToMode="off"` disables **all** reply threading in Slack, including explicit `[[reply_to_*]]` tags. This differs from Telegram, where explicit tags are still honored in `"off"` mode. The difference reflects the platform threading models: Slack threads hide messages from the channel, while Telegram replies remain visible in the main chat flow.
|
||||
|
||||
## Media, chunking, and delivery
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Inbound attachments">
|
||||
Slack file attachments are downloaded from Slack-hosted private URLs (token-authenticated request flow) and written to the media store when fetch succeeds and size limits permit.
|
||||
|
||||
Runtime inbound size cap defaults to `20MB` unless overridden by `channels.slack.mediaMaxMb`.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Outbound text and files">
|
||||
- text chunks use `channels.slack.textChunkLimit` (default 4000)
|
||||
- `channels.slack.chunkMode="newline"` enables paragraph-first splitting
|
||||
- file sends use Slack upload APIs and can include thread replies (`thread_ts`)
|
||||
- outbound media cap follows `channels.slack.mediaMaxMb` when configured; otherwise channel sends use MIME-kind defaults from media pipeline
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Delivery targets">
|
||||
Preferred explicit targets:
|
||||
|
||||
- `user:<id>` for DMs
|
||||
- `channel:<id>` for channels
|
||||
|
||||
Slack DMs are opened via Slack conversation APIs when sending to user targets.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Actions and gates
|
||||
|
||||
Slack actions are controlled by `channels.slack.actions.*`.
|
||||
|
||||
Available action groups in current Slack tooling:
|
||||
|
||||
| Group | Default |
|
||||
| ---------- | ------- |
|
||||
| messages | enabled |
|
||||
| reactions | enabled |
|
||||
| pins | enabled |
|
||||
| memberInfo | enabled |
|
||||
| emojiList | enabled |
|
||||
|
||||
## Events and operational behavior
|
||||
|
||||
- Message edits/deletes/thread broadcasts are mapped into system events.
|
||||
- Reaction add/remove events are mapped into system events.
|
||||
- Member join/leave, channel created/renamed, and pin add/remove events are mapped into system events.
|
||||
- Assistant thread status updates (for "is typing..." indicators in threads) use `assistant.threads.setStatus` and require bot scope `assistant:write`.
|
||||
- `channel_id_changed` can migrate channel config keys when `configWrites` is enabled.
|
||||
- Channel topic/purpose metadata is treated as untrusted context and can be injected into routing context.
|
||||
- Block actions and modal interactions emit structured `Slack interaction: ...` system events with rich payload fields:
|
||||
- block actions: selected values, labels, picker values, and `workflow_*` metadata
|
||||
- modal `view_submission` and `view_closed` events with routed channel metadata and form inputs
|
||||
|
||||
## Ack reactions
|
||||
|
||||
`ackReaction` sends an acknowledgement emoji while OpenClaw is processing an inbound message.
|
||||
|
||||
Resolution order:
|
||||
|
||||
- `channels.slack.accounts.<accountId>.ackReaction`
|
||||
- `channels.slack.ackReaction`
|
||||
- `messages.ackReaction`
|
||||
- agent identity emoji fallback (`agents.list[].identity.emoji`, else "👀")
|
||||
|
||||
Notes:
|
||||
|
||||
- Slack expects shortcodes (for example `"eyes"`).
|
||||
- Use `""` to disable the reaction for a channel or account.
|
||||
|
||||
## Manifest and scope checklist
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Slack app manifest example">
|
||||
|
||||
```json
|
||||
{
|
||||
"display_information": {
|
||||
"name": "OpenClaw",
|
||||
"description": "Slack connector for OpenClaw"
|
||||
},
|
||||
"features": {
|
||||
"bot_user": {
|
||||
"display_name": "OpenClaw",
|
||||
"always_online": false
|
||||
},
|
||||
"app_home": {
|
||||
"messages_tab_enabled": true,
|
||||
"messages_tab_read_only_enabled": false
|
||||
},
|
||||
"slash_commands": [
|
||||
{
|
||||
"command": "/openclaw",
|
||||
"description": "Send a message to OpenClaw",
|
||||
"should_escape": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"oauth_config": {
|
||||
"scopes": {
|
||||
"bot": [
|
||||
"chat:write",
|
||||
"channels:history",
|
||||
"channels:read",
|
||||
"groups:history",
|
||||
"im:history",
|
||||
"mpim:history",
|
||||
"users:read",
|
||||
"app_mentions:read",
|
||||
"assistant:write",
|
||||
"reactions:read",
|
||||
"reactions:write",
|
||||
"pins:read",
|
||||
"pins:write",
|
||||
"emoji:read",
|
||||
"commands",
|
||||
"files:read",
|
||||
"files:write"
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"socket_mode_enabled": true,
|
||||
"event_subscriptions": {
|
||||
"bot_events": [
|
||||
"app_mention",
|
||||
"message.channels",
|
||||
"message.groups",
|
||||
"message.im",
|
||||
"message.mpim",
|
||||
"reaction_added",
|
||||
"reaction_removed",
|
||||
"member_joined_channel",
|
||||
"member_left_channel",
|
||||
"channel_rename",
|
||||
"pin_added",
|
||||
"pin_removed"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Optional user-token scopes (read operations)">
|
||||
If you configure `channels.slack.userToken`, typical read scopes are:
|
||||
|
||||
- `channels:history`, `groups:history`, `im:history`, `mpim:history`
|
||||
- `channels:read`, `groups:read`, `im:read`, `mpim:read`
|
||||
- `users:read`
|
||||
- `reactions:read`
|
||||
- `pins:read`
|
||||
- `emoji:read`
|
||||
- `search:read` (if you depend on Slack search reads)
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="No replies in channels">
|
||||
Check, in order:
|
||||
|
||||
- `groupPolicy`
|
||||
- channel allowlist (`channels.slack.channels`)
|
||||
- `requireMention`
|
||||
- per-channel `users` allowlist
|
||||
|
||||
Useful commands:
|
||||
|
||||
```bash
|
||||
openclaw channels status --probe
|
||||
openclaw logs --follow
|
||||
openclaw doctor
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="DM messages ignored">
|
||||
Check:
|
||||
|
||||
- `channels.slack.dm.enabled`
|
||||
- `channels.slack.dmPolicy` (or legacy `channels.slack.dm.policy`)
|
||||
- pairing approvals / allowlist entries
|
||||
|
||||
```bash
|
||||
openclaw pairing list slack
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Socket mode not connecting">
|
||||
Validate bot + app tokens and Socket Mode enablement in Slack app settings.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="HTTP mode not receiving events">
|
||||
Validate:
|
||||
|
||||
- signing secret
|
||||
- webhook path
|
||||
- Slack Request URLs (Events + Interactivity + Slash Commands)
|
||||
- unique `webhookPath` per HTTP account
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Native/slash commands not firing">
|
||||
Verify whether you intended:
|
||||
|
||||
- native command mode (`channels.slack.commands.native: true`) with matching slash commands registered in Slack
|
||||
- or single slash command mode (`channels.slack.slashCommand.enabled: true`)
|
||||
|
||||
Also check `commands.useAccessGroups` and channel/user allowlists.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Text streaming
|
||||
|
||||
OpenClaw supports Slack native text streaming via the Agents and AI Apps API.
|
||||
|
||||
`channels.slack.streaming` controls live preview behavior:
|
||||
|
||||
- `off`: disable live preview streaming.
|
||||
- `partial` (default): replace preview text with the latest partial output.
|
||||
- `block`: append chunked preview updates.
|
||||
- `progress`: show progress status text while generating, then send final text.
|
||||
|
||||
`channels.slack.nativeStreaming` controls Slack's native streaming API (`chat.startStream` / `chat.appendStream` / `chat.stopStream`) when `streaming` is `partial` (default: `true`).
|
||||
|
||||
Disable native Slack streaming (keep draft preview behavior):
|
||||
|
||||
```yaml
|
||||
channels:
|
||||
slack:
|
||||
streaming: partial
|
||||
nativeStreaming: false
|
||||
```
|
||||
|
||||
Legacy keys:
|
||||
|
||||
- `channels.slack.streamMode` (`replace | status_final | append`) is auto-migrated to `channels.slack.streaming`.
|
||||
- boolean `channels.slack.streaming` is auto-migrated to `channels.slack.nativeStreaming`.
|
||||
|
||||
### Requirements
|
||||
|
||||
1. Enable **Agents and AI Apps** in your Slack app settings.
|
||||
2. Ensure the app has the `assistant:write` scope.
|
||||
3. A reply thread must be available for that message. Thread selection still follows `replyToMode`.
|
||||
|
||||
### Behavior
|
||||
|
||||
- First text chunk starts a stream (`chat.startStream`).
|
||||
- Later text chunks append to the same stream (`chat.appendStream`).
|
||||
- End of reply finalizes stream (`chat.stopStream`).
|
||||
- Media and non-text payloads fall back to normal delivery.
|
||||
- If streaming fails mid-reply, OpenClaw falls back to normal delivery for remaining payloads.
|
||||
|
||||
## Configuration reference pointers
|
||||
|
||||
Primary reference:
|
||||
|
||||
- [Configuration reference - Slack](/gateway/configuration-reference#slack)
|
||||
|
||||
High-signal Slack fields:
|
||||
- mode/auth: `mode`, `botToken`, `appToken`, `signingSecret`, `webhookPath`, `accounts.*`
|
||||
- DM access: `dm.enabled`, `dmPolicy`, `allowFrom` (legacy: `dm.policy`, `dm.allowFrom`), `dm.groupEnabled`, `dm.groupChannels`
|
||||
- compatibility toggle: `dangerouslyAllowNameMatching` (break-glass; keep off unless needed)
|
||||
- channel access: `groupPolicy`, `channels.*`, `channels.*.users`, `channels.*.requireMention`
|
||||
- threading/history: `replyToMode`, `replyToModeByChatType`, `thread.*`, `historyLimit`, `dmHistoryLimit`, `dms.*.historyLimit`
|
||||
- delivery: `textChunkLimit`, `chunkMode`, `mediaMaxMb`, `streaming`, `nativeStreaming`
|
||||
- ops/features: `configWrites`, `commands.native`, `slashCommand.*`, `actions.*`, `userToken`, `userTokenReadOnly`
|
||||
|
||||
## Related
|
||||
|
||||
- [Pairing](/channels/pairing)
|
||||
- [Channel routing](/channels/channel-routing)
|
||||
- [Troubleshooting](/channels/troubleshooting)
|
||||
- [Configuration](/gateway/configuration)
|
||||
- [Slash commands](/tools/slash-commands)
|
||||
128
openclaw/docs/channels/synology-chat.md
Normal file
@@ -0,0 +1,128 @@
|
||||
---
|
||||
summary: "Synology Chat webhook setup and OpenClaw config"
|
||||
read_when:
|
||||
- Setting up Synology Chat with OpenClaw
|
||||
- Debugging Synology Chat webhook routing
|
||||
title: "Synology Chat"
|
||||
---
|
||||
|
||||
# Synology Chat (plugin)
|
||||
|
||||
Status: supported via plugin as a direct-message channel using Synology Chat webhooks.
|
||||
The plugin accepts inbound messages from Synology Chat outgoing webhooks and sends replies
|
||||
through a Synology Chat incoming webhook.
|
||||
|
||||
## Plugin required
|
||||
|
||||
Synology Chat is plugin-based and not part of the default core channel install.
|
||||
|
||||
Install from a local checkout:
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./extensions/synology-chat
|
||||
```
|
||||
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Quick setup
|
||||
|
||||
1. Install and enable the Synology Chat plugin.
|
||||
2. In Synology Chat integrations:
|
||||
- Create an incoming webhook and copy its URL.
|
||||
- Create an outgoing webhook with your secret token.
|
||||
3. Point the outgoing webhook URL to your OpenClaw gateway:
|
||||
- `https://gateway-host/webhook/synology` by default.
|
||||
- Or your custom `channels.synology-chat.webhookPath`.
|
||||
4. Configure `channels.synology-chat` in OpenClaw.
|
||||
5. Restart gateway and send a DM to the Synology Chat bot.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
"synology-chat": {
|
||||
enabled: true,
|
||||
token: "synology-outgoing-token",
|
||||
incomingUrl: "https://nas.example.com/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=2&token=...",
|
||||
webhookPath: "/webhook/synology",
|
||||
dmPolicy: "allowlist",
|
||||
allowedUserIds: ["123456"],
|
||||
rateLimitPerMinute: 30,
|
||||
allowInsecureSsl: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
For the default account, you can use env vars:
|
||||
|
||||
- `SYNOLOGY_CHAT_TOKEN`
|
||||
- `SYNOLOGY_CHAT_INCOMING_URL`
|
||||
- `SYNOLOGY_NAS_HOST`
|
||||
- `SYNOLOGY_ALLOWED_USER_IDS` (comma-separated)
|
||||
- `SYNOLOGY_RATE_LIMIT`
|
||||
- `OPENCLAW_BOT_NAME`
|
||||
|
||||
Config values override env vars.
|
||||
|
||||
## DM policy and access control
|
||||
|
||||
- `dmPolicy: "allowlist"` is the recommended default.
|
||||
- `allowedUserIds` accepts a list (or comma-separated string) of Synology user IDs.
|
||||
- In `allowlist` mode, an empty `allowedUserIds` list is treated as misconfiguration and the webhook route will not start (use `dmPolicy: "open"` for allow-all).
|
||||
- `dmPolicy: "open"` allows any sender.
|
||||
- `dmPolicy: "disabled"` blocks DMs.
|
||||
- Pairing approvals work with:
|
||||
- `openclaw pairing list synology-chat`
|
||||
- `openclaw pairing approve synology-chat <CODE>`
|
||||
|
||||
## Outbound delivery
|
||||
|
||||
Use numeric Synology Chat user IDs as targets.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
openclaw message send --channel synology-chat --target 123456 --text "Hello from OpenClaw"
|
||||
openclaw message send --channel synology-chat --target synology-chat:123456 --text "Hello again"
|
||||
```
|
||||
|
||||
Media sends are supported by URL-based file delivery.
|
||||
|
||||
## Multi-account
|
||||
|
||||
Multiple Synology Chat accounts are supported under `channels.synology-chat.accounts`.
|
||||
Each account can override token, incoming URL, webhook path, DM policy, and limits.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
"synology-chat": {
|
||||
enabled: true,
|
||||
accounts: {
|
||||
default: {
|
||||
token: "token-a",
|
||||
incomingUrl: "https://nas-a.example.com/...token=...",
|
||||
},
|
||||
alerts: {
|
||||
token: "token-b",
|
||||
incomingUrl: "https://nas-b.example.com/...token=...",
|
||||
webhookPath: "/webhook/synology-alerts",
|
||||
dmPolicy: "allowlist",
|
||||
allowedUserIds: ["987654"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Security notes
|
||||
|
||||
- Keep `token` secret and rotate it if leaked.
|
||||
- Keep `allowInsecureSsl: false` unless you explicitly trust a self-signed local NAS cert.
|
||||
- Inbound webhook requests are token-verified and rate-limited per sender.
|
||||
- Prefer `dmPolicy: "allowlist"` for production.
|
||||
793
openclaw/docs/channels/telegram.md
Normal file
@@ -0,0 +1,793 @@
|
||||
---
|
||||
summary: "Telegram bot support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Telegram features or webhooks
|
||||
title: "Telegram"
|
||||
---
|
||||
|
||||
# Telegram (Bot API)
|
||||
|
||||
Status: production-ready for bot DMs + groups via grammY. Long polling is the default mode; webhook mode is optional.
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Pairing" icon="link" href="/channels/pairing">
|
||||
Default DM policy for Telegram is pairing.
|
||||
</Card>
|
||||
<Card title="Channel troubleshooting" icon="wrench" href="/channels/troubleshooting">
|
||||
Cross-channel diagnostics and repair playbooks.
|
||||
</Card>
|
||||
<Card title="Gateway configuration" icon="settings" href="/gateway/configuration">
|
||||
Full channel config patterns and examples.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Quick setup
|
||||
|
||||
<Steps>
|
||||
<Step title="Create the bot token in BotFather">
|
||||
Open Telegram and chat with **@BotFather** (confirm the handle is exactly `@BotFather`).
|
||||
|
||||
Run `/newbot`, follow prompts, and save the token.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Configure token and DM policy">
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
botToken: "123:abc",
|
||||
dmPolicy: "pairing",
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Env fallback: `TELEGRAM_BOT_TOKEN=...` (default account only).
|
||||
Telegram does **not** use `openclaw channels login telegram`; configure token in config/env, then start gateway.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Start gateway and approve first DM">
|
||||
|
||||
```bash
|
||||
openclaw gateway
|
||||
openclaw pairing list telegram
|
||||
openclaw pairing approve telegram <CODE>
|
||||
```
|
||||
|
||||
Pairing codes expire after 1 hour.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Add the bot to a group">
|
||||
Add the bot to your group, then set `channels.telegram.groups` and `groupPolicy` to match your access model.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Note>
|
||||
Token resolution order is account-aware. In practice, config values win over env fallback, and `TELEGRAM_BOT_TOKEN` only applies to the default account.
|
||||
</Note>
|
||||
|
||||
## Telegram side settings
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Privacy mode and group visibility">
|
||||
Telegram bots default to **Privacy Mode**, which limits what group messages they receive.
|
||||
|
||||
If the bot must see all group messages, either:
|
||||
|
||||
- disable privacy mode via `/setprivacy`, or
|
||||
- make the bot a group admin.
|
||||
|
||||
When toggling privacy mode, remove + re-add the bot in each group so Telegram applies the change.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Group permissions">
|
||||
Admin status is controlled in Telegram group settings.
|
||||
|
||||
Admin bots receive all group messages, which is useful for always-on group behavior.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Helpful BotFather toggles">
|
||||
|
||||
- `/setjoingroups` to allow/deny group adds
|
||||
- `/setprivacy` for group visibility behavior
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Access control and activation
|
||||
|
||||
<Tabs>
|
||||
<Tab title="DM policy">
|
||||
`channels.telegram.dmPolicy` controls direct message access:
|
||||
|
||||
- `pairing` (default)
|
||||
- `allowlist` (requires at least one sender ID in `allowFrom`)
|
||||
- `open` (requires `allowFrom` to include `"*"`)
|
||||
- `disabled`
|
||||
|
||||
`channels.telegram.allowFrom` accepts numeric Telegram user IDs. `telegram:` / `tg:` prefixes are accepted and normalized.
|
||||
`dmPolicy: "allowlist"` with empty `allowFrom` blocks all DMs and is rejected by config validation.
|
||||
The onboarding wizard accepts `@username` input and resolves it to numeric IDs.
|
||||
If you upgraded and your config contains `@username` allowlist entries, run `openclaw doctor --fix` to resolve them (best-effort; requires a Telegram bot token).
|
||||
If you previously relied on pairing-store allowlist files, `openclaw doctor --fix` can recover entries into `channels.telegram.allowFrom` in allowlist flows (for example when `dmPolicy: "allowlist"` has no explicit IDs yet).
|
||||
|
||||
### Finding your Telegram user ID
|
||||
|
||||
Safer (no third-party bot):
|
||||
|
||||
1. DM your bot.
|
||||
2. Run `openclaw logs --follow`.
|
||||
3. Read `from.id`.
|
||||
|
||||
Official Bot API method:
|
||||
|
||||
```bash
|
||||
curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
```
|
||||
|
||||
Third-party method (less private): `@userinfobot` or `@getidsbot`.
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Group policy and allowlists">
|
||||
Two controls apply together:
|
||||
|
||||
1. **Which groups are allowed** (`channels.telegram.groups`)
|
||||
- no `groups` config:
|
||||
- with `groupPolicy: "open"`: any group can pass group-ID checks
|
||||
- with `groupPolicy: "allowlist"` (default): groups are blocked until you add `groups` entries (or `"*"`)
|
||||
- `groups` configured: acts as allowlist (explicit IDs or `"*"`)
|
||||
|
||||
2. **Which senders are allowed in groups** (`channels.telegram.groupPolicy`)
|
||||
- `open`
|
||||
- `allowlist` (default)
|
||||
- `disabled`
|
||||
|
||||
`groupAllowFrom` is used for group sender filtering. If not set, Telegram falls back to `allowFrom`.
|
||||
`groupAllowFrom` entries should be numeric Telegram user IDs (`telegram:` / `tg:` prefixes are normalized).
|
||||
Non-numeric entries are ignored for sender authorization.
|
||||
Security boundary (`2026.2.25+`): group sender auth does **not** inherit DM pairing-store approvals.
|
||||
Pairing stays DM-only. For groups, set `groupAllowFrom` or per-group/per-topic `allowFrom`.
|
||||
Runtime note: if `channels.telegram` is completely missing, runtime defaults to fail-closed `groupPolicy="allowlist"` unless `channels.defaults.groupPolicy` is explicitly set.
|
||||
|
||||
Example: allow any member in one specific group:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
groups: {
|
||||
"-1001234567890": {
|
||||
groupPolicy: "open",
|
||||
requireMention: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Mention behavior">
|
||||
Group replies require mention by default.
|
||||
|
||||
Mention can come from:
|
||||
|
||||
- native `@botusername` mention, or
|
||||
- mention patterns in:
|
||||
- `agents.list[].groupChat.mentionPatterns`
|
||||
- `messages.groupChat.mentionPatterns`
|
||||
|
||||
Session-level command toggles:
|
||||
|
||||
- `/activation always`
|
||||
- `/activation mention`
|
||||
|
||||
These update session state only. Use config for persistence.
|
||||
|
||||
Persistent config example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
groups: {
|
||||
"*": { requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Getting the group chat ID:
|
||||
|
||||
- forward a group message to `@userinfobot` / `@getidsbot`
|
||||
- or read `chat.id` from `openclaw logs --follow`
|
||||
- or inspect Bot API `getUpdates`
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Runtime behavior
|
||||
|
||||
- Telegram is owned by the gateway process.
|
||||
- Routing is deterministic: Telegram inbound replies back to Telegram (the model does not pick channels).
|
||||
- Inbound messages normalize into the shared channel envelope with reply metadata and media placeholders.
|
||||
- Group sessions are isolated by group ID. Forum topics append `:topic:<threadId>` to keep topics isolated.
|
||||
- DM messages can carry `message_thread_id`; OpenClaw routes them with thread-aware session keys and preserves thread ID for replies.
|
||||
- Long polling uses grammY runner with per-chat/per-thread sequencing. Overall runner sink concurrency uses `agents.defaults.maxConcurrent`.
|
||||
- Telegram Bot API has no read-receipt support (`sendReadReceipts` does not apply).
|
||||
|
||||
## Feature reference
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Live stream preview (message edits)">
|
||||
OpenClaw can stream partial replies by sending a temporary Telegram message and editing it as text arrives.
|
||||
|
||||
Requirement:
|
||||
|
||||
- `channels.telegram.streaming` is `off | partial | block | progress` (default: `off`)
|
||||
- `progress` maps to `partial` on Telegram (compat with cross-channel naming)
|
||||
- legacy `channels.telegram.streamMode` and boolean `streaming` values are auto-mapped
|
||||
|
||||
This works in direct chats and groups/topics.
|
||||
|
||||
For text-only replies, OpenClaw keeps the same preview message and performs a final edit in place (no second message).
|
||||
|
||||
For complex replies (for example media payloads), OpenClaw falls back to normal final delivery and then cleans up the preview message.
|
||||
|
||||
Preview streaming is separate from block streaming. When block streaming is explicitly enabled for Telegram, OpenClaw skips the preview stream to avoid double-streaming.
|
||||
|
||||
Telegram-only reasoning stream:
|
||||
|
||||
- `/reasoning stream` sends reasoning to the live preview while generating
|
||||
- final answer is sent without reasoning text
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Formatting and HTML fallback">
|
||||
Outbound text uses Telegram `parse_mode: "HTML"`.
|
||||
|
||||
- Markdown-ish text is rendered to Telegram-safe HTML.
|
||||
- Raw model HTML is escaped to reduce Telegram parse failures.
|
||||
- If Telegram rejects parsed HTML, OpenClaw retries as plain text.
|
||||
|
||||
Link previews are enabled by default and can be disabled with `channels.telegram.linkPreview: false`.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Native commands and custom commands">
|
||||
Telegram command menu registration is handled at startup with `setMyCommands`.
|
||||
|
||||
Native command defaults:
|
||||
|
||||
- `commands.native: "auto"` enables native commands for Telegram
|
||||
|
||||
Add custom command menu entries:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
customCommands: [
|
||||
{ command: "backup", description: "Git backup" },
|
||||
{ command: "generate", description: "Create an image" },
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- names are normalized (strip leading `/`, lowercase)
|
||||
- valid pattern: `a-z`, `0-9`, `_`, length `1..32`
|
||||
- custom commands cannot override native commands
|
||||
- conflicts/duplicates are skipped and logged
|
||||
|
||||
Notes:
|
||||
|
||||
- custom commands are menu entries only; they do not auto-implement behavior
|
||||
- plugin/skill commands can still work when typed even if not shown in Telegram menu
|
||||
|
||||
If native commands are disabled, built-ins are removed. Custom/plugin commands may still register if configured.
|
||||
|
||||
Common setup failure:
|
||||
|
||||
- `setMyCommands failed` usually means outbound DNS/HTTPS to `api.telegram.org` is blocked.
|
||||
|
||||
### Device pairing commands (`device-pair` plugin)
|
||||
|
||||
When the `device-pair` plugin is installed:
|
||||
|
||||
1. `/pair` generates setup code
|
||||
2. paste code in iOS app
|
||||
3. `/pair approve` approves latest pending request
|
||||
|
||||
More details: [Pairing](/channels/pairing#pair-via-telegram-recommended-for-ios).
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Inline buttons">
|
||||
Configure inline keyboard scope:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
capabilities: {
|
||||
inlineButtons: "allowlist",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Per-account override:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
accounts: {
|
||||
main: {
|
||||
capabilities: {
|
||||
inlineButtons: "allowlist",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Scopes:
|
||||
|
||||
- `off`
|
||||
- `dm`
|
||||
- `group`
|
||||
- `all`
|
||||
- `allowlist` (default)
|
||||
|
||||
Legacy `capabilities: ["inlineButtons"]` maps to `inlineButtons: "all"`.
|
||||
|
||||
Message action example:
|
||||
|
||||
```json5
|
||||
{
|
||||
action: "send",
|
||||
channel: "telegram",
|
||||
to: "123456789",
|
||||
message: "Choose an option:",
|
||||
buttons: [
|
||||
[
|
||||
{ text: "Yes", callback_data: "yes" },
|
||||
{ text: "No", callback_data: "no" },
|
||||
],
|
||||
[{ text: "Cancel", callback_data: "cancel" }],
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Callback clicks are passed to the agent as text:
|
||||
`callback_data: <value>`
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Telegram message actions for agents and automation">
|
||||
Telegram tool actions include:
|
||||
|
||||
- `sendMessage` (`to`, `content`, optional `mediaUrl`, `replyToMessageId`, `messageThreadId`)
|
||||
- `react` (`chatId`, `messageId`, `emoji`)
|
||||
- `deleteMessage` (`chatId`, `messageId`)
|
||||
- `editMessage` (`chatId`, `messageId`, `content`)
|
||||
- `createForumTopic` (`chatId`, `name`, optional `iconColor`, `iconCustomEmojiId`)
|
||||
|
||||
Channel message actions expose ergonomic aliases (`send`, `react`, `delete`, `edit`, `sticker`, `sticker-search`, `topic-create`).
|
||||
|
||||
Gating controls:
|
||||
|
||||
- `channels.telegram.actions.sendMessage`
|
||||
- `channels.telegram.actions.deleteMessage`
|
||||
- `channels.telegram.actions.reactions`
|
||||
- `channels.telegram.actions.sticker` (default: disabled)
|
||||
|
||||
Note: `edit` and `topic-create` are currently enabled by default and do not have separate `channels.telegram.actions.*` toggles.
|
||||
|
||||
Reaction removal semantics: [/tools/reactions](/tools/reactions)
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Reply threading tags">
|
||||
Telegram supports explicit reply threading tags in generated output:
|
||||
|
||||
- `[[reply_to_current]]` replies to the triggering message
|
||||
- `[[reply_to:<id>]]` replies to a specific Telegram message ID
|
||||
|
||||
`channels.telegram.replyToMode` controls handling:
|
||||
|
||||
- `off` (default)
|
||||
- `first`
|
||||
- `all`
|
||||
|
||||
Note: `off` disables implicit reply threading. Explicit `[[reply_to_*]]` tags are still honored.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Forum topics and thread behavior">
|
||||
Forum supergroups:
|
||||
|
||||
- topic session keys append `:topic:<threadId>`
|
||||
- replies and typing target the topic thread
|
||||
- topic config path:
|
||||
`channels.telegram.groups.<chatId>.topics.<threadId>`
|
||||
|
||||
General topic (`threadId=1`) special-case:
|
||||
|
||||
- message sends omit `message_thread_id` (Telegram rejects `sendMessage(...thread_id=1)`)
|
||||
- typing actions still include `message_thread_id`
|
||||
|
||||
Topic inheritance: topic entries inherit group settings unless overridden (`requireMention`, `allowFrom`, `skills`, `systemPrompt`, `enabled`, `groupPolicy`).
|
||||
|
||||
Template context includes:
|
||||
|
||||
- `MessageThreadId`
|
||||
- `IsForum`
|
||||
|
||||
DM thread behavior:
|
||||
|
||||
- private chats with `message_thread_id` keep DM routing but use thread-aware session keys/reply targets.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Audio, video, and stickers">
|
||||
### Audio messages
|
||||
|
||||
Telegram distinguishes voice notes vs audio files.
|
||||
|
||||
- default: audio file behavior
|
||||
- tag `[[audio_as_voice]]` in agent reply to force voice-note send
|
||||
|
||||
Message action example:
|
||||
|
||||
```json5
|
||||
{
|
||||
action: "send",
|
||||
channel: "telegram",
|
||||
to: "123456789",
|
||||
media: "https://example.com/voice.ogg",
|
||||
asVoice: true,
|
||||
}
|
||||
```
|
||||
|
||||
### Video messages
|
||||
|
||||
Telegram distinguishes video files vs video notes.
|
||||
|
||||
Message action example:
|
||||
|
||||
```json5
|
||||
{
|
||||
action: "send",
|
||||
channel: "telegram",
|
||||
to: "123456789",
|
||||
media: "https://example.com/video.mp4",
|
||||
asVideoNote: true,
|
||||
}
|
||||
```
|
||||
|
||||
Video notes do not support captions; provided message text is sent separately.
|
||||
|
||||
### Stickers
|
||||
|
||||
Inbound sticker handling:
|
||||
|
||||
- static WEBP: downloaded and processed (placeholder `<media:sticker>`)
|
||||
- animated TGS: skipped
|
||||
- video WEBM: skipped
|
||||
|
||||
Sticker context fields:
|
||||
|
||||
- `Sticker.emoji`
|
||||
- `Sticker.setName`
|
||||
- `Sticker.fileId`
|
||||
- `Sticker.fileUniqueId`
|
||||
- `Sticker.cachedDescription`
|
||||
|
||||
Sticker cache file:
|
||||
|
||||
- `~/.openclaw/telegram/sticker-cache.json`
|
||||
|
||||
Stickers are described once (when possible) and cached to reduce repeated vision calls.
|
||||
|
||||
Enable sticker actions:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
actions: {
|
||||
sticker: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Send sticker action:
|
||||
|
||||
```json5
|
||||
{
|
||||
action: "sticker",
|
||||
channel: "telegram",
|
||||
to: "123456789",
|
||||
fileId: "CAACAgIAAxkBAAI...",
|
||||
}
|
||||
```
|
||||
|
||||
Search cached stickers:
|
||||
|
||||
```json5
|
||||
{
|
||||
action: "sticker-search",
|
||||
channel: "telegram",
|
||||
query: "cat waving",
|
||||
limit: 5,
|
||||
}
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Reaction notifications">
|
||||
Telegram reactions arrive as `message_reaction` updates (separate from message payloads).
|
||||
|
||||
When enabled, OpenClaw enqueues system events like:
|
||||
|
||||
- `Telegram reaction added: 👍 by Alice (@alice) on msg 42`
|
||||
|
||||
Config:
|
||||
|
||||
- `channels.telegram.reactionNotifications`: `off | own | all` (default: `own`)
|
||||
- `channels.telegram.reactionLevel`: `off | ack | minimal | extensive` (default: `minimal`)
|
||||
|
||||
Notes:
|
||||
|
||||
- `own` means user reactions to bot-sent messages only (best-effort via sent-message cache).
|
||||
- Reaction events still respect Telegram access controls (`dmPolicy`, `allowFrom`, `groupPolicy`, `groupAllowFrom`); unauthorized senders are dropped.
|
||||
- Telegram does not provide thread IDs in reaction updates.
|
||||
- non-forum groups route to group chat session
|
||||
- forum groups route to the group general-topic session (`:topic:1`), not the exact originating topic
|
||||
|
||||
`allowed_updates` for polling/webhook include `message_reaction` automatically.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Ack reactions">
|
||||
`ackReaction` sends an acknowledgement emoji while OpenClaw is processing an inbound message.
|
||||
|
||||
Resolution order:
|
||||
|
||||
- `channels.telegram.accounts.<accountId>.ackReaction`
|
||||
- `channels.telegram.ackReaction`
|
||||
- `messages.ackReaction`
|
||||
- agent identity emoji fallback (`agents.list[].identity.emoji`, else "👀")
|
||||
|
||||
Notes:
|
||||
|
||||
- Telegram expects unicode emoji (for example "👀").
|
||||
- Use `""` to disable the reaction for a channel or account.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Config writes from Telegram events and commands">
|
||||
Channel config writes are enabled by default (`configWrites !== false`).
|
||||
|
||||
Telegram-triggered writes include:
|
||||
|
||||
- group migration events (`migrate_to_chat_id`) to update `channels.telegram.groups`
|
||||
- `/config set` and `/config unset` (requires command enablement)
|
||||
|
||||
Disable:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
configWrites: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Long polling vs webhook">
|
||||
Default: long polling.
|
||||
|
||||
Webhook mode:
|
||||
|
||||
- set `channels.telegram.webhookUrl`
|
||||
- set `channels.telegram.webhookSecret` (required when webhook URL is set)
|
||||
- optional `channels.telegram.webhookPath` (default `/telegram-webhook`)
|
||||
- optional `channels.telegram.webhookHost` (default `127.0.0.1`)
|
||||
- optional `channels.telegram.webhookPort` (default `8787`)
|
||||
|
||||
Default local listener for webhook mode binds to `127.0.0.1:8787`.
|
||||
|
||||
If your public endpoint differs, place a reverse proxy in front and point `webhookUrl` at the public URL.
|
||||
Set `webhookHost` (for example `0.0.0.0`) when you intentionally need external ingress.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Limits, retry, and CLI targets">
|
||||
- `channels.telegram.textChunkLimit` default is 4000.
|
||||
- `channels.telegram.chunkMode="newline"` prefers paragraph boundaries (blank lines) before length splitting.
|
||||
- `channels.telegram.mediaMaxMb` (default 5) caps inbound Telegram media download/processing size.
|
||||
- `channels.telegram.timeoutSeconds` overrides Telegram API client timeout (if unset, grammY default applies).
|
||||
- group context history uses `channels.telegram.historyLimit` or `messages.groupChat.historyLimit` (default 50); `0` disables.
|
||||
- DM history controls:
|
||||
- `channels.telegram.dmHistoryLimit`
|
||||
- `channels.telegram.dms["<user_id>"].historyLimit`
|
||||
- `channels.telegram.retry` config applies to Telegram send helpers (CLI/tools/actions) for recoverable outbound API errors.
|
||||
|
||||
CLI send target can be numeric chat ID or username:
|
||||
|
||||
```bash
|
||||
openclaw message send --channel telegram --target 123456789 --message "hi"
|
||||
openclaw message send --channel telegram --target @name --message "hi"
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Bot does not respond to non mention group messages">
|
||||
|
||||
- If `requireMention=false`, Telegram privacy mode must allow full visibility.
|
||||
- BotFather: `/setprivacy` -> Disable
|
||||
- then remove + re-add bot to group
|
||||
- `openclaw channels status` warns when config expects unmentioned group messages.
|
||||
- `openclaw channels status --probe` can check explicit numeric group IDs; wildcard `"*"` cannot be membership-probed.
|
||||
- quick session test: `/activation always`.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Bot not seeing group messages at all">
|
||||
|
||||
- when `channels.telegram.groups` exists, group must be listed (or include `"*"`)
|
||||
- verify bot membership in group
|
||||
- review logs: `openclaw logs --follow` for skip reasons
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Commands work partially or not at all">
|
||||
|
||||
- authorize your sender identity (pairing and/or numeric `allowFrom`)
|
||||
- command authorization still applies even when group policy is `open`
|
||||
- `setMyCommands failed` usually indicates DNS/HTTPS reachability issues to `api.telegram.org`
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Polling or network instability">
|
||||
|
||||
- Node 22+ + custom fetch/proxy can trigger immediate abort behavior if AbortSignal types mismatch.
|
||||
- Some hosts resolve `api.telegram.org` to IPv6 first; broken IPv6 egress can cause intermittent Telegram API failures.
|
||||
- If logs include `TypeError: fetch failed` or `Network request for 'getUpdates' failed!`, OpenClaw now retries these as recoverable network errors.
|
||||
- On VPS hosts with unstable direct egress/TLS, route Telegram API calls through `channels.telegram.proxy`:
|
||||
|
||||
```yaml
|
||||
channels:
|
||||
telegram:
|
||||
proxy: socks5://user:pass@proxy-host:1080
|
||||
```
|
||||
|
||||
- Node 22+ defaults to `autoSelectFamily=true` (except WSL2) and `dnsResultOrder=ipv4first`.
|
||||
- If your host is WSL2 or explicitly works better with IPv4-only behavior, force family selection:
|
||||
|
||||
```yaml
|
||||
channels:
|
||||
telegram:
|
||||
network:
|
||||
autoSelectFamily: false
|
||||
```
|
||||
|
||||
- Environment overrides (temporary):
|
||||
- `OPENCLAW_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY=1`
|
||||
- `OPENCLAW_TELEGRAM_ENABLE_AUTO_SELECT_FAMILY=1`
|
||||
- `OPENCLAW_TELEGRAM_DNS_RESULT_ORDER=ipv4first`
|
||||
- Validate DNS answers:
|
||||
|
||||
```bash
|
||||
dig +short api.telegram.org A
|
||||
dig +short api.telegram.org AAAA
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
More help: [Channel troubleshooting](/channels/troubleshooting).
|
||||
|
||||
## Telegram config reference pointers
|
||||
|
||||
Primary reference:
|
||||
|
||||
- `channels.telegram.enabled`: enable/disable channel startup.
|
||||
- `channels.telegram.botToken`: bot token (BotFather).
|
||||
- `channels.telegram.tokenFile`: read token from file path.
|
||||
- `channels.telegram.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing).
|
||||
- `channels.telegram.allowFrom`: DM allowlist (numeric Telegram user IDs). `allowlist` requires at least one sender ID. `open` requires `"*"`. `openclaw doctor --fix` can resolve legacy `@username` entries to IDs and can recover allowlist entries from pairing-store files in allowlist migration flows.
|
||||
- `channels.telegram.defaultTo`: default Telegram target used by CLI `--deliver` when no explicit `--reply-to` is provided.
|
||||
- `channels.telegram.groupPolicy`: `open | allowlist | disabled` (default: allowlist).
|
||||
- `channels.telegram.groupAllowFrom`: group sender allowlist (numeric Telegram user IDs). `openclaw doctor --fix` can resolve legacy `@username` entries to IDs. Non-numeric entries are ignored at auth time. Group auth does not use DM pairing-store fallback (`2026.2.25+`).
|
||||
- Multi-account precedence:
|
||||
- `channels.telegram.accounts.default.allowFrom` and `channels.telegram.accounts.default.groupAllowFrom` apply only to the `default` account.
|
||||
- Named accounts inherit `channels.telegram.allowFrom` and `channels.telegram.groupAllowFrom` when account-level values are unset.
|
||||
- Named accounts do not inherit `channels.telegram.accounts.default.allowFrom` / `groupAllowFrom`.
|
||||
- `channels.telegram.groups`: per-group defaults + allowlist (use `"*"` for global defaults).
|
||||
- `channels.telegram.groups.<id>.groupPolicy`: per-group override for groupPolicy (`open | allowlist | disabled`).
|
||||
- `channels.telegram.groups.<id>.requireMention`: mention gating default.
|
||||
- `channels.telegram.groups.<id>.skills`: skill filter (omit = all skills, empty = none).
|
||||
- `channels.telegram.groups.<id>.allowFrom`: per-group sender allowlist override.
|
||||
- `channels.telegram.groups.<id>.systemPrompt`: extra system prompt for the group.
|
||||
- `channels.telegram.groups.<id>.enabled`: disable the group when `false`.
|
||||
- `channels.telegram.groups.<id>.topics.<threadId>.*`: per-topic overrides (same fields as group).
|
||||
- `channels.telegram.groups.<id>.topics.<threadId>.groupPolicy`: per-topic override for groupPolicy (`open | allowlist | disabled`).
|
||||
- `channels.telegram.groups.<id>.topics.<threadId>.requireMention`: per-topic mention gating override.
|
||||
- `channels.telegram.capabilities.inlineButtons`: `off | dm | group | all | allowlist` (default: allowlist).
|
||||
- `channels.telegram.accounts.<account>.capabilities.inlineButtons`: per-account override.
|
||||
- `channels.telegram.commands.nativeSkills`: enable/disable Telegram native skills commands.
|
||||
- `channels.telegram.replyToMode`: `off | first | all` (default: `off`).
|
||||
- `channels.telegram.textChunkLimit`: outbound chunk size (chars).
|
||||
- `channels.telegram.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.telegram.linkPreview`: toggle link previews for outbound messages (default: true).
|
||||
- `channels.telegram.streaming`: `off | partial | block | progress` (live stream preview; default: `off`; `progress` maps to `partial`; `block` is legacy preview mode compatibility).
|
||||
- `channels.telegram.mediaMaxMb`: inbound Telegram media download/processing cap (MB).
|
||||
- `channels.telegram.retry`: retry policy for Telegram send helpers (CLI/tools/actions) on recoverable outbound API errors (attempts, minDelayMs, maxDelayMs, jitter).
|
||||
- `channels.telegram.network.autoSelectFamily`: override Node autoSelectFamily (true=enable, false=disable). Defaults to enabled on Node 22+, with WSL2 defaulting to disabled.
|
||||
- `channels.telegram.network.dnsResultOrder`: override DNS result order (`ipv4first` or `verbatim`). Defaults to `ipv4first` on Node 22+.
|
||||
- `channels.telegram.proxy`: proxy URL for Bot API calls (SOCKS/HTTP).
|
||||
- `channels.telegram.webhookUrl`: enable webhook mode (requires `channels.telegram.webhookSecret`).
|
||||
- `channels.telegram.webhookSecret`: webhook secret (required when webhookUrl is set).
|
||||
- `channels.telegram.webhookPath`: local webhook path (default `/telegram-webhook`).
|
||||
- `channels.telegram.webhookHost`: local webhook bind host (default `127.0.0.1`).
|
||||
- `channels.telegram.webhookPort`: local webhook bind port (default `8787`).
|
||||
- `channels.telegram.actions.reactions`: gate Telegram tool reactions.
|
||||
- `channels.telegram.actions.sendMessage`: gate Telegram tool message sends.
|
||||
- `channels.telegram.actions.deleteMessage`: gate Telegram tool message deletes.
|
||||
- `channels.telegram.actions.sticker`: gate Telegram sticker actions — send and search (default: false).
|
||||
- `channels.telegram.reactionNotifications`: `off | own | all` — control which reactions trigger system events (default: `own` when not set).
|
||||
- `channels.telegram.reactionLevel`: `off | ack | minimal | extensive` — control agent's reaction capability (default: `minimal` when not set).
|
||||
|
||||
- [Configuration reference - Telegram](/gateway/configuration-reference#telegram)
|
||||
|
||||
Telegram-specific high-signal fields:
|
||||
|
||||
- startup/auth: `enabled`, `botToken`, `tokenFile`, `accounts.*`
|
||||
- access control: `dmPolicy`, `allowFrom`, `groupPolicy`, `groupAllowFrom`, `groups`, `groups.*.topics.*`
|
||||
- command/menu: `commands.native`, `commands.nativeSkills`, `customCommands`
|
||||
- threading/replies: `replyToMode`
|
||||
- streaming: `streaming` (preview), `blockStreaming`
|
||||
- formatting/delivery: `textChunkLimit`, `chunkMode`, `linkPreview`, `responsePrefix`
|
||||
- media/network: `mediaMaxMb`, `timeoutSeconds`, `retry`, `network.autoSelectFamily`, `proxy`
|
||||
- webhook: `webhookUrl`, `webhookSecret`, `webhookPath`, `webhookHost`
|
||||
- actions/capabilities: `capabilities.inlineButtons`, `actions.sendMessage|editMessage|deleteMessage|reactions|sticker`
|
||||
- reactions: `reactionNotifications`, `reactionLevel`
|
||||
- writes/history: `configWrites`, `historyLimit`, `dmHistoryLimit`, `dms.*.historyLimit`
|
||||
|
||||
## Related
|
||||
|
||||
- [Pairing](/channels/pairing)
|
||||
- [Channel routing](/channels/channel-routing)
|
||||
- [Multi-agent routing](/concepts/multi-agent)
|
||||
- [Troubleshooting](/channels/troubleshooting)
|
||||
148
openclaw/docs/channels/tlon.md
Normal file
@@ -0,0 +1,148 @@
|
||||
---
|
||||
summary: "Tlon/Urbit support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Tlon/Urbit channel features
|
||||
title: "Tlon"
|
||||
---
|
||||
|
||||
# Tlon (plugin)
|
||||
|
||||
Tlon is a decentralized messenger built on Urbit. OpenClaw connects to your Urbit ship and can
|
||||
respond to DMs and group chat messages. Group replies require an @ mention by default and can
|
||||
be further restricted via allowlists.
|
||||
|
||||
Status: supported via plugin. DMs, group mentions, thread replies, and text-only media fallback
|
||||
(URL appended to caption). Reactions, polls, and native media uploads are not supported.
|
||||
|
||||
## Plugin required
|
||||
|
||||
Tlon ships as a plugin and is not bundled with the core install.
|
||||
|
||||
Install via CLI (npm registry):
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/tlon
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./extensions/tlon
|
||||
```
|
||||
|
||||
Details: [Plugins](/tools/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.
|
||||
|
||||
Minimal config (single account):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
enabled: true,
|
||||
ship: "~sampel-palnet",
|
||||
url: "https://your-ship-host",
|
||||
code: "lidlut-tabwed-pillex-ridrup",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Private/LAN ship URLs (advanced):
|
||||
|
||||
By default, OpenClaw blocks private/internal hostnames and IP ranges for this plugin (SSRF hardening).
|
||||
If your ship URL is on a private network (for example `http://192.168.1.50:8080` or `http://localhost:8080`),
|
||||
you must explicitly opt in:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
allowPrivateNetwork: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Group channels
|
||||
|
||||
Auto-discovery is enabled by default. You can also pin channels manually:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
groupChannels: ["chat/~host-ship/general", "chat/~host-ship/support"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Disable auto-discovery:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
autoDiscoverChannels: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Access control
|
||||
|
||||
DM allowlist (empty = allow all):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
dmAllowlist: ["~zod", "~nec"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Group authorization (restricted by default):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
defaultAuthorizedShips: ["~zod"],
|
||||
authorization: {
|
||||
channelRules: {
|
||||
"chat/~host-ship/general": {
|
||||
mode: "restricted",
|
||||
allowedShips: ["~zod", "~nec"],
|
||||
},
|
||||
"chat/~host-ship/announcements": {
|
||||
mode: "open",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Delivery targets (CLI/cron)
|
||||
|
||||
Use these with `openclaw message send` or cron delivery:
|
||||
|
||||
- DM: `~sampel-palnet` or `dm/~sampel-palnet`
|
||||
- Group: `chat/~host-ship/channel` or `group:~host-ship/channel`
|
||||
|
||||
## Notes
|
||||
|
||||
- Group replies require a mention (e.g. `~your-bot-ship`) to respond.
|
||||
- Thread replies: if the inbound message is in a thread, OpenClaw replies in-thread.
|
||||
- Media: `sendMedia` falls back to text + URL (no native upload).
|
||||
117
openclaw/docs/channels/troubleshooting.md
Normal file
@@ -0,0 +1,117 @@
|
||||
---
|
||||
summary: "Fast channel level troubleshooting with per channel failure signatures and fixes"
|
||||
read_when:
|
||||
- Channel transport says connected but replies fail
|
||||
- You need channel specific checks before deep provider docs
|
||||
title: "Channel Troubleshooting"
|
||||
---
|
||||
|
||||
# Channel troubleshooting
|
||||
|
||||
Use this page when a channel connects but behavior is wrong.
|
||||
|
||||
## Command ladder
|
||||
|
||||
Run these in order first:
|
||||
|
||||
```bash
|
||||
openclaw status
|
||||
openclaw gateway status
|
||||
openclaw logs --follow
|
||||
openclaw doctor
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
Healthy baseline:
|
||||
|
||||
- `Runtime: running`
|
||||
- `RPC probe: ok`
|
||||
- Channel probe shows connected/ready
|
||||
|
||||
## WhatsApp
|
||||
|
||||
### WhatsApp failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| ------------------------------- | --------------------------------------------------- | ------------------------------------------------------- |
|
||||
| Connected but no DM replies | `openclaw pairing list whatsapp` | Approve sender or switch DM policy/allowlist. |
|
||||
| Group messages ignored | Check `requireMention` + mention patterns in config | Mention the bot or relax mention policy for that group. |
|
||||
| Random disconnect/relogin loops | `openclaw channels status --probe` + logs | Re-login and verify credentials directory is healthy. |
|
||||
|
||||
Full troubleshooting: [/channels/whatsapp#troubleshooting-quick](/channels/whatsapp#troubleshooting-quick)
|
||||
|
||||
## Telegram
|
||||
|
||||
### Telegram failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| --------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------- |
|
||||
| `/start` but no usable reply flow | `openclaw pairing list telegram` | Approve pairing or change DM policy. |
|
||||
| Bot online but group stays silent | Verify mention requirement and bot privacy mode | Disable privacy mode for group visibility or mention bot. |
|
||||
| Send failures with network errors | Inspect logs for Telegram API call failures | Fix DNS/IPv6/proxy routing to `api.telegram.org`. |
|
||||
| Upgraded and allowlist blocks you | `openclaw security audit` and config allowlists | Run `openclaw doctor --fix` or replace `@username` with numeric sender IDs. |
|
||||
|
||||
Full troubleshooting: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting)
|
||||
|
||||
## Discord
|
||||
|
||||
### Discord failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| ------------------------------- | ----------------------------------- | --------------------------------------------------------- |
|
||||
| Bot online but no guild replies | `openclaw channels status --probe` | Allow guild/channel and verify message content intent. |
|
||||
| Group messages ignored | Check logs for mention gating drops | Mention bot or set guild/channel `requireMention: false`. |
|
||||
| DM replies missing | `openclaw pairing list discord` | Approve DM pairing or adjust DM policy. |
|
||||
|
||||
Full troubleshooting: [/channels/discord#troubleshooting](/channels/discord#troubleshooting)
|
||||
|
||||
## Slack
|
||||
|
||||
### Slack failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| -------------------------------------- | ----------------------------------------- | ------------------------------------------------- |
|
||||
| Socket mode connected but no responses | `openclaw channels status --probe` | Verify app token + bot token and required scopes. |
|
||||
| DMs blocked | `openclaw pairing list slack` | Approve pairing or relax DM policy. |
|
||||
| Channel message ignored | Check `groupPolicy` and channel allowlist | Allow the channel or switch policy to `open`. |
|
||||
|
||||
Full troubleshooting: [/channels/slack#troubleshooting](/channels/slack#troubleshooting)
|
||||
|
||||
## iMessage and BlueBubbles
|
||||
|
||||
### iMessage and BlueBubbles failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| -------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------- |
|
||||
| No inbound events | Verify webhook/server reachability and app permissions | Fix webhook URL or BlueBubbles server state. |
|
||||
| Can send but no receive on macOS | Check macOS privacy permissions for Messages automation | Re-grant TCC permissions and restart channel process. |
|
||||
| DM sender blocked | `openclaw pairing list imessage` or `openclaw pairing list bluebubbles` | Approve pairing or update allowlist. |
|
||||
|
||||
Full troubleshooting:
|
||||
|
||||
- [/channels/imessage#troubleshooting-macos-privacy-and-security-tcc](/channels/imessage#troubleshooting-macos-privacy-and-security-tcc)
|
||||
- [/channels/bluebubbles#troubleshooting](/channels/bluebubbles#troubleshooting)
|
||||
|
||||
## Signal
|
||||
|
||||
### Signal failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| ------------------------------- | ------------------------------------------ | -------------------------------------------------------- |
|
||||
| Daemon reachable but bot silent | `openclaw channels status --probe` | Verify `signal-cli` daemon URL/account and receive mode. |
|
||||
| DM blocked | `openclaw pairing list signal` | Approve sender or adjust DM policy. |
|
||||
| Group replies do not trigger | Check group allowlist and mention patterns | Add sender/group or loosen gating. |
|
||||
|
||||
Full troubleshooting: [/channels/signal#troubleshooting](/channels/signal#troubleshooting)
|
||||
|
||||
## Matrix
|
||||
|
||||
### Matrix failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| ----------------------------------- | -------------------------------------------- | ----------------------------------------------- |
|
||||
| Logged in but ignores room messages | `openclaw channels status --probe` | Check `groupPolicy` and room allowlist. |
|
||||
| DMs do not process | `openclaw pairing list matrix` | Approve sender or adjust DM policy. |
|
||||
| Encrypted rooms fail | Verify crypto module and encryption settings | Enable encryption support and rejoin/sync room. |
|
||||
|
||||
Full troubleshooting: [/channels/matrix#troubleshooting](/channels/matrix#troubleshooting)
|
||||
379
openclaw/docs/channels/twitch.md
Normal file
@@ -0,0 +1,379 @@
|
||||
---
|
||||
summary: "Twitch chat bot configuration and setup"
|
||||
read_when:
|
||||
- Setting up Twitch chat integration for OpenClaw
|
||||
title: "Twitch"
|
||||
---
|
||||
|
||||
# Twitch (plugin)
|
||||
|
||||
Twitch chat support via IRC connection. OpenClaw connects as a Twitch user (bot account) to receive and send messages in channels.
|
||||
|
||||
## Plugin required
|
||||
|
||||
Twitch ships as a plugin and is not bundled with the core install.
|
||||
|
||||
Install via CLI (npm registry):
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/twitch
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./extensions/twitch
|
||||
```
|
||||
|
||||
Details: [Plugins](/tools/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/)
|
||||
- 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/](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.
|
||||
|
||||
**⚠️ Important:** Add access control (`allowFrom` or `allowedRoles`) to prevent unauthorized users from triggering the bot. `requireMention` defaults to `true`.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
enabled: true,
|
||||
username: "openclaw", // Bot's Twitch account
|
||||
accessToken: "oauth:abc123...", // OAuth Access Token (or use OPENCLAW_TWITCH_ACCESS_TOKEN env var)
|
||||
clientId: "xyz789...", // Client ID from Token Generator
|
||||
channel: "vevisk", // Which Twitch channel's chat to join (required)
|
||||
allowFrom: ["123456789"], // (recommended) Your Twitch user ID only - get it from https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## What it is
|
||||
|
||||
- A Twitch channel owned by the Gateway.
|
||||
- Deterministic routing: replies always go back to Twitch.
|
||||
- Each account maps to an isolated session key `agent:<agentId>:twitch:<accountName>`.
|
||||
- `username` is the bot's account (who authenticates), `channel` is which chat room to join.
|
||||
|
||||
## Setup (detailed)
|
||||
|
||||
### Generate credentials
|
||||
|
||||
Use [Twitch Token Generator](https://twitchtokengenerator.com/):
|
||||
|
||||
- Select **Bot Token**
|
||||
- Verify scopes `chat:read` and `chat:write` are selected
|
||||
- Copy the **Client ID** and **Access Token**
|
||||
|
||||
No manual app registration needed. Tokens expire after several hours.
|
||||
|
||||
### Configure the bot
|
||||
|
||||
**Env var (default account only):**
|
||||
|
||||
```bash
|
||||
OPENCLAW_TWITCH_ACCESS_TOKEN=oauth:abc123...
|
||||
```
|
||||
|
||||
**Or config:**
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
enabled: true,
|
||||
username: "openclaw",
|
||||
accessToken: "oauth:abc123...",
|
||||
clientId: "xyz789...",
|
||||
channel: "vevisk",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
If both env and config are set, config takes precedence.
|
||||
|
||||
### Access control (recommended)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
allowFrom: ["123456789"], // (recommended) Your Twitch user ID only
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Prefer `allowFrom` for a hard allowlist. Use `allowedRoles` instead if you want role-based access.
|
||||
|
||||
**Available roles:** `"moderator"`, `"owner"`, `"vip"`, `"subscriber"`, `"all"`.
|
||||
|
||||
**Why user IDs?** Usernames can change, allowing impersonation. User IDs are permanent.
|
||||
|
||||
Find your Twitch user ID: [https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/) (Convert your Twitch username to ID)
|
||||
|
||||
## Token refresh (optional)
|
||||
|
||||
Tokens from [Twitch Token Generator](https://twitchtokengenerator.com/) cannot be automatically refreshed - regenerate when expired.
|
||||
|
||||
For automatic token refresh, create your own Twitch application at [Twitch Developer Console](https://dev.twitch.tv/console) and add to config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
clientSecret: "your_client_secret",
|
||||
refreshToken: "your_refresh_token",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The bot automatically refreshes tokens before expiration and logs refresh events.
|
||||
|
||||
## Multi-account support
|
||||
|
||||
Use `channels.twitch.accounts` with per-account tokens. See [`gateway/configuration`](/gateway/configuration) for the shared pattern.
|
||||
|
||||
Example (one bot account in two channels):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
channel1: {
|
||||
username: "openclaw",
|
||||
accessToken: "oauth:abc123...",
|
||||
clientId: "xyz789...",
|
||||
channel: "vevisk",
|
||||
},
|
||||
channel2: {
|
||||
username: "openclaw",
|
||||
accessToken: "oauth:def456...",
|
||||
clientId: "uvw012...",
|
||||
channel: "secondchannel",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** Each account needs its own token (one token per channel).
|
||||
|
||||
## Access control
|
||||
|
||||
### Role-based restrictions
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
allowedRoles: ["moderator", "vip"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Allowlist by User ID (most secure)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
allowFrom: ["123456789", "987654321"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Role-based access (alternative)
|
||||
|
||||
`allowFrom` is a hard allowlist. When set, only those user IDs are allowed.
|
||||
If you want role-based access, leave `allowFrom` unset and configure `allowedRoles` instead:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
allowedRoles: ["moderator"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Disable @mention requirement
|
||||
|
||||
By default, `requireMention` is `true`. To disable and respond to all messages:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
requireMention: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
First, run diagnostic commands:
|
||||
|
||||
```bash
|
||||
openclaw doctor
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
### Bot doesn't respond to messages
|
||||
|
||||
**Check access control:** Ensure your user ID is in `allowFrom`, or temporarily remove
|
||||
`allowFrom` and set `allowedRoles: ["all"]` to test.
|
||||
|
||||
**Check the bot is in the channel:** The bot must join the channel specified in `channel`.
|
||||
|
||||
### Token issues
|
||||
|
||||
**"Failed to connect" or authentication errors:**
|
||||
|
||||
- Verify `accessToken` is the OAuth access token value (typically starts with `oauth:` prefix)
|
||||
- Check token has `chat:read` and `chat:write` scopes
|
||||
- If using token refresh, verify `clientSecret` and `refreshToken` are set
|
||||
|
||||
### Token refresh not working
|
||||
|
||||
**Check logs for refresh events:**
|
||||
|
||||
```
|
||||
Using env token source for mybot
|
||||
Access token refreshed for user 123456 (expires in 14400s)
|
||||
```
|
||||
|
||||
If you see "token refresh disabled (no refresh token)":
|
||||
|
||||
- Ensure `clientSecret` is provided
|
||||
- Ensure `refreshToken` is provided
|
||||
|
||||
## Config
|
||||
|
||||
**Account config:**
|
||||
|
||||
- `username` - Bot username
|
||||
- `accessToken` - OAuth access token with `chat:read` and `chat:write`
|
||||
- `clientId` - Twitch Client ID (from Token Generator or your app)
|
||||
- `channel` - Channel to join (required)
|
||||
- `enabled` - Enable this account (default: `true`)
|
||||
- `clientSecret` - Optional: For automatic token refresh
|
||||
- `refreshToken` - Optional: For automatic token refresh
|
||||
- `expiresIn` - Token expiry in seconds
|
||||
- `obtainmentTimestamp` - Token obtained timestamp
|
||||
- `allowFrom` - User ID allowlist
|
||||
- `allowedRoles` - Role-based access control (`"moderator" | "owner" | "vip" | "subscriber" | "all"`)
|
||||
- `requireMention` - Require @mention (default: `true`)
|
||||
|
||||
**Provider options:**
|
||||
|
||||
- `channels.twitch.enabled` - Enable/disable channel startup
|
||||
- `channels.twitch.username` - Bot username (simplified single-account config)
|
||||
- `channels.twitch.accessToken` - OAuth access token (simplified single-account config)
|
||||
- `channels.twitch.clientId` - Twitch Client ID (simplified single-account config)
|
||||
- `channels.twitch.channel` - Channel to join (simplified single-account config)
|
||||
- `channels.twitch.accounts.<accountName>` - Multi-account config (all account fields above)
|
||||
|
||||
Full example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
enabled: true,
|
||||
username: "openclaw",
|
||||
accessToken: "oauth:abc123...",
|
||||
clientId: "xyz789...",
|
||||
channel: "vevisk",
|
||||
clientSecret: "secret123...",
|
||||
refreshToken: "refresh456...",
|
||||
allowFrom: ["123456789"],
|
||||
allowedRoles: ["moderator", "vip"],
|
||||
accounts: {
|
||||
default: {
|
||||
username: "mybot",
|
||||
accessToken: "oauth:abc123...",
|
||||
clientId: "xyz789...",
|
||||
channel: "your_channel",
|
||||
enabled: true,
|
||||
clientSecret: "secret123...",
|
||||
refreshToken: "refresh456...",
|
||||
expiresIn: 14400,
|
||||
obtainmentTimestamp: 1706092800000,
|
||||
allowFrom: ["123456789", "987654321"],
|
||||
allowedRoles: ["moderator"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Tool actions
|
||||
|
||||
The agent can call `twitch` with action:
|
||||
|
||||
- `send` - Send a message to a channel
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
action: "twitch",
|
||||
params: {
|
||||
message: "Hello Twitch!",
|
||||
to: "#mychannel",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Safety & ops
|
||||
|
||||
- **Treat tokens like passwords** - Never commit tokens to git
|
||||
- **Use automatic token refresh** for long-running bots
|
||||
- **Use user ID allowlists** instead of usernames for access control
|
||||
- **Monitor logs** for token refresh events and connection status
|
||||
- **Scope tokens minimally** - Only request `chat:read` and `chat:write`
|
||||
- **If stuck**: Restart the gateway after confirming no other process owns the session
|
||||
|
||||
## Limits
|
||||
|
||||
- **500 characters** per message (auto-chunked at word boundaries)
|
||||
- Markdown is stripped before chunking
|
||||
- No rate limiting (uses Twitch's built-in rate limits)
|
||||
444
openclaw/docs/channels/whatsapp.md
Normal file
@@ -0,0 +1,444 @@
|
||||
---
|
||||
summary: "WhatsApp channel support, access controls, delivery behavior, and operations"
|
||||
read_when:
|
||||
- Working on WhatsApp/web channel behavior or inbox routing
|
||||
title: "WhatsApp"
|
||||
---
|
||||
|
||||
# WhatsApp (Web channel)
|
||||
|
||||
Status: production-ready via WhatsApp Web (Baileys). Gateway owns linked session(s).
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Pairing" icon="link" href="/channels/pairing">
|
||||
Default DM policy is pairing for unknown senders.
|
||||
</Card>
|
||||
<Card title="Channel troubleshooting" icon="wrench" href="/channels/troubleshooting">
|
||||
Cross-channel diagnostics and repair playbooks.
|
||||
</Card>
|
||||
<Card title="Gateway configuration" icon="settings" href="/gateway/configuration">
|
||||
Full channel config patterns and examples.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Quick setup
|
||||
|
||||
<Steps>
|
||||
<Step title="Configure WhatsApp access policy">
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "pairing",
|
||||
allowFrom: ["+15551234567"],
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15551234567"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Link WhatsApp (QR)">
|
||||
|
||||
```bash
|
||||
openclaw channels login --channel whatsapp
|
||||
```
|
||||
|
||||
For a specific account:
|
||||
|
||||
```bash
|
||||
openclaw channels login --channel whatsapp --account work
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Start the gateway">
|
||||
|
||||
```bash
|
||||
openclaw gateway
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Approve first pairing request (if using pairing mode)">
|
||||
|
||||
```bash
|
||||
openclaw pairing list whatsapp
|
||||
openclaw pairing approve whatsapp <CODE>
|
||||
```
|
||||
|
||||
Pairing requests expire after 1 hour. Pending requests are capped at 3 per channel.
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Note>
|
||||
OpenClaw recommends running WhatsApp on a separate number when possible. (The channel metadata and onboarding flow are optimized for that setup, but personal-number setups are also supported.)
|
||||
</Note>
|
||||
|
||||
## Deployment patterns
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Dedicated number (recommended)">
|
||||
This is the cleanest operational mode:
|
||||
|
||||
- separate WhatsApp identity for OpenClaw
|
||||
- clearer DM allowlists and routing boundaries
|
||||
- lower chance of self-chat confusion
|
||||
|
||||
Minimal policy pattern:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["+15551234567"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Personal-number fallback">
|
||||
Onboarding supports personal-number mode and writes a self-chat-friendly baseline:
|
||||
|
||||
- `dmPolicy: "allowlist"`
|
||||
- `allowFrom` includes your personal number
|
||||
- `selfChatMode: true`
|
||||
|
||||
In runtime, self-chat protections key off the linked self number and `allowFrom`.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="WhatsApp Web-only channel scope">
|
||||
The messaging platform channel is WhatsApp Web-based (`Baileys`) in current OpenClaw channel architecture.
|
||||
|
||||
There is no separate Twilio WhatsApp messaging channel in the built-in chat-channel registry.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Runtime model
|
||||
|
||||
- Gateway owns the WhatsApp socket and reconnect loop.
|
||||
- Outbound sends require an active WhatsApp listener for the target account.
|
||||
- Status and broadcast chats are ignored (`@status`, `@broadcast`).
|
||||
- Direct chats use DM session rules (`session.dmScope`; default `main` collapses DMs to the agent main session).
|
||||
- Group sessions are isolated (`agent:<agentId>:whatsapp:group:<jid>`).
|
||||
|
||||
## Access control and activation
|
||||
|
||||
<Tabs>
|
||||
<Tab title="DM policy">
|
||||
`channels.whatsapp.dmPolicy` controls direct chat access:
|
||||
|
||||
- `pairing` (default)
|
||||
- `allowlist`
|
||||
- `open` (requires `allowFrom` to include `"*"`)
|
||||
- `disabled`
|
||||
|
||||
`allowFrom` accepts E.164-style numbers (normalized internally).
|
||||
|
||||
Multi-account override: `channels.whatsapp.accounts.<id>.dmPolicy` (and `allowFrom`) take precedence over channel-level defaults for that account.
|
||||
|
||||
Runtime behavior details:
|
||||
|
||||
- pairings are persisted in channel allow-store and merged with configured `allowFrom`
|
||||
- if no allowlist is configured, the linked self number is allowed by default
|
||||
- outbound `fromMe` DMs are never auto-paired
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Group policy + allowlists">
|
||||
Group access has two layers:
|
||||
|
||||
1. **Group membership allowlist** (`channels.whatsapp.groups`)
|
||||
- if `groups` is omitted, all groups are eligible
|
||||
- if `groups` is present, it acts as a group allowlist (`"*"` allowed)
|
||||
|
||||
2. **Group sender policy** (`channels.whatsapp.groupPolicy` + `groupAllowFrom`)
|
||||
- `open`: sender allowlist bypassed
|
||||
- `allowlist`: sender must match `groupAllowFrom` (or `*`)
|
||||
- `disabled`: block all group inbound
|
||||
|
||||
Sender allowlist fallback:
|
||||
|
||||
- if `groupAllowFrom` is unset, runtime falls back to `allowFrom` when available
|
||||
- sender allowlists are evaluated before mention/reply activation
|
||||
|
||||
Note: if no `channels.whatsapp` block exists at all, runtime group-policy fallback is `allowlist` (with a warning log), even if `channels.defaults.groupPolicy` is set.
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Mentions + /activation">
|
||||
Group replies require mention by default.
|
||||
|
||||
Mention detection includes:
|
||||
|
||||
- explicit WhatsApp mentions of the bot identity
|
||||
- configured mention regex patterns (`agents.list[].groupChat.mentionPatterns`, fallback `messages.groupChat.mentionPatterns`)
|
||||
- implicit reply-to-bot detection (reply sender matches bot identity)
|
||||
|
||||
Security note:
|
||||
|
||||
- quote/reply only satisfies mention gating; it does **not** grant sender authorization
|
||||
- with `groupPolicy: "allowlist"`, non-allowlisted senders are still blocked even if they reply to an allowlisted user's message
|
||||
|
||||
Session-level activation command:
|
||||
|
||||
- `/activation mention`
|
||||
- `/activation always`
|
||||
|
||||
`activation` updates session state (not global config). It is owner-gated.
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Personal-number and self-chat behavior
|
||||
|
||||
When the linked self number is also present in `allowFrom`, WhatsApp self-chat safeguards activate:
|
||||
|
||||
- skip read receipts for self-chat turns
|
||||
- ignore mention-JID auto-trigger behavior that would otherwise ping yourself
|
||||
- if `messages.responsePrefix` is unset, self-chat replies default to `[{identity.name}]` or `[openclaw]`
|
||||
|
||||
## Message normalization and context
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Inbound envelope + reply context">
|
||||
Incoming WhatsApp messages are wrapped in the shared inbound envelope.
|
||||
|
||||
If a quoted reply exists, context is appended in this form:
|
||||
|
||||
```text
|
||||
[Replying to <sender> id:<stanzaId>]
|
||||
<quoted body or media placeholder>
|
||||
[/Replying]
|
||||
```
|
||||
|
||||
Reply metadata fields are also populated when available (`ReplyToId`, `ReplyToBody`, `ReplyToSender`, sender JID/E.164).
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Media placeholders and location/contact extraction">
|
||||
Media-only inbound messages are normalized with placeholders such as:
|
||||
|
||||
- `<media:image>`
|
||||
- `<media:video>`
|
||||
- `<media:audio>`
|
||||
- `<media:document>`
|
||||
- `<media:sticker>`
|
||||
|
||||
Location and contact payloads are normalized into textual context before routing.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Pending group history injection">
|
||||
For groups, unprocessed messages can be buffered and injected as context when the bot is finally triggered.
|
||||
|
||||
- default limit: `50`
|
||||
- config: `channels.whatsapp.historyLimit`
|
||||
- fallback: `messages.groupChat.historyLimit`
|
||||
- `0` disables
|
||||
|
||||
Injection markers:
|
||||
|
||||
- `[Chat messages since your last reply - for context]`
|
||||
- `[Current message - respond to this]`
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Read receipts">
|
||||
Read receipts are enabled by default for accepted inbound WhatsApp messages.
|
||||
|
||||
Disable globally:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
sendReadReceipts: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Per-account override:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
accounts: {
|
||||
work: {
|
||||
sendReadReceipts: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Self-chat turns skip read receipts even when globally enabled.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Delivery, chunking, and media
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Text chunking">
|
||||
- default chunk limit: `channels.whatsapp.textChunkLimit = 4000`
|
||||
- `channels.whatsapp.chunkMode = "length" | "newline"`
|
||||
- `newline` mode prefers paragraph boundaries (blank lines), then falls back to length-safe chunking
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Outbound media behavior">
|
||||
- supports image, video, audio (PTT voice-note), and document payloads
|
||||
- `audio/ogg` is rewritten to `audio/ogg; codecs=opus` for voice-note compatibility
|
||||
- animated GIF playback is supported via `gifPlayback: true` on video sends
|
||||
- captions are applied to the first media item when sending multi-media reply payloads
|
||||
- media source can be HTTP(S), `file://`, or local paths
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Media size limits and fallback behavior">
|
||||
- inbound media save cap: `channels.whatsapp.mediaMaxMb` (default `50`)
|
||||
- outbound media cap for auto-replies: `agents.defaults.mediaMaxMb` (default `5MB`)
|
||||
- images are auto-optimized (resize/quality sweep) to fit limits
|
||||
- on media send failure, first-item fallback sends text warning instead of dropping the response silently
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Acknowledgment reactions
|
||||
|
||||
WhatsApp supports immediate ack reactions on inbound receipt via `channels.whatsapp.ackReaction`.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
ackReaction: {
|
||||
emoji: "👀",
|
||||
direct: true,
|
||||
group: "mentions", // always | mentions | never
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Behavior notes:
|
||||
|
||||
- sent immediately after inbound is accepted (pre-reply)
|
||||
- failures are logged but do not block normal reply delivery
|
||||
- group mode `mentions` reacts on mention-triggered turns; group activation `always` acts as bypass for this check
|
||||
- WhatsApp uses `channels.whatsapp.ackReaction` (legacy `messages.ackReaction` is not used here)
|
||||
|
||||
## Multi-account and credentials
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Account selection and defaults">
|
||||
- account ids come from `channels.whatsapp.accounts`
|
||||
- default account selection: `default` if present, otherwise first configured account id (sorted)
|
||||
- account ids are normalized internally for lookup
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Credential paths and legacy compatibility">
|
||||
- current auth path: `~/.openclaw/credentials/whatsapp/<accountId>/creds.json`
|
||||
- backup file: `creds.json.bak`
|
||||
- legacy default auth in `~/.openclaw/credentials/` is still recognized/migrated for default-account flows
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Logout behavior">
|
||||
`openclaw channels logout --channel whatsapp [--account <id>]` clears WhatsApp auth state for that account.
|
||||
|
||||
In legacy auth directories, `oauth.json` is preserved while Baileys auth files are removed.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Tools, actions, and config writes
|
||||
|
||||
- Agent tool support includes WhatsApp reaction action (`react`).
|
||||
- Action gates:
|
||||
- `channels.whatsapp.actions.reactions`
|
||||
- `channels.whatsapp.actions.polls`
|
||||
- Channel-initiated config writes are enabled by default (disable via `channels.whatsapp.configWrites=false`).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Not linked (QR required)">
|
||||
Symptom: channel status reports not linked.
|
||||
|
||||
Fix:
|
||||
|
||||
```bash
|
||||
openclaw channels login --channel whatsapp
|
||||
openclaw channels status
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Linked but disconnected / reconnect loop">
|
||||
Symptom: linked account with repeated disconnects or reconnect attempts.
|
||||
|
||||
Fix:
|
||||
|
||||
```bash
|
||||
openclaw doctor
|
||||
openclaw logs --follow
|
||||
```
|
||||
|
||||
If needed, re-link with `channels login`.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="No active listener when sending">
|
||||
Outbound sends fail fast when no active gateway listener exists for the target account.
|
||||
|
||||
Make sure gateway is running and the account is linked.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Group messages unexpectedly ignored">
|
||||
Check in this order:
|
||||
|
||||
- `groupPolicy`
|
||||
- `groupAllowFrom` / `allowFrom`
|
||||
- `groups` allowlist entries
|
||||
- mention gating (`requireMention` + mention patterns)
|
||||
- duplicate keys in `openclaw.json` (JSON5): later entries override earlier ones, so keep a single `groupPolicy` per scope
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Bun runtime warning">
|
||||
WhatsApp gateway runtime should use Node. Bun is flagged as incompatible for stable WhatsApp/Telegram gateway operation.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Configuration reference pointers
|
||||
|
||||
Primary reference:
|
||||
|
||||
- [Configuration reference - WhatsApp](/gateway/configuration-reference#whatsapp)
|
||||
|
||||
High-signal WhatsApp fields:
|
||||
|
||||
- access: `dmPolicy`, `allowFrom`, `groupPolicy`, `groupAllowFrom`, `groups`
|
||||
- delivery: `textChunkLimit`, `chunkMode`, `mediaMaxMb`, `sendReadReceipts`, `ackReaction`
|
||||
- multi-account: `accounts.<id>.enabled`, `accounts.<id>.authDir`, account-level overrides
|
||||
- operations: `configWrites`, `debounceMs`, `web.enabled`, `web.heartbeatSeconds`, `web.reconnect.*`
|
||||
- session behavior: `session.dmScope`, `historyLimit`, `dmHistoryLimit`, `dms.<id>.historyLimit`
|
||||
|
||||
## Related
|
||||
|
||||
- [Pairing](/channels/pairing)
|
||||
- [Channel routing](/channels/channel-routing)
|
||||
- [Multi-agent routing](/concepts/multi-agent)
|
||||
- [Troubleshooting](/channels/troubleshooting)
|
||||
206
openclaw/docs/channels/zalo.md
Normal file
@@ -0,0 +1,206 @@
|
||||
---
|
||||
summary: "Zalo bot support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Zalo features or webhooks
|
||||
title: "Zalo"
|
||||
---
|
||||
|
||||
# Zalo (Bot API)
|
||||
|
||||
Status: experimental. DMs are supported; group handling is available with explicit group policy controls.
|
||||
|
||||
## 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](/tools/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
|
||||
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:
|
||||
- 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.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalo: {
|
||||
enabled: true,
|
||||
botToken: "12345689:abc-xyz",
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## What it is
|
||||
|
||||
Zalo is a Vietnam-focused messaging app; its Bot API lets the Gateway run a bot for 1:1 conversations.
|
||||
It is a good fit for support or notifications where you want deterministic routing back to Zalo.
|
||||
|
||||
- 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.
|
||||
- Groups are supported with policy controls (`groupPolicy` + `groupAllowFrom`) and default to fail-closed allowlist behavior.
|
||||
|
||||
## Setup (fast path)
|
||||
|
||||
### 1) Create a bot token (Zalo Bot Platform)
|
||||
|
||||
1. Go to [https://bot.zaloplatforms.com](https://bot.zaloplatforms.com) and sign in.
|
||||
2. Create a new bot and configure its settings.
|
||||
3. Copy the bot token (format: `12345689:abc-xyz`).
|
||||
|
||||
### 2) Configure the token (env or config)
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalo: {
|
||||
enabled: true,
|
||||
botToken: "12345689:abc-xyz",
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Env option: `ZALO_BOT_TOKEN=...` (works for the default account only).
|
||||
|
||||
Multi-account support: use `channels.zalo.accounts` with per-account tokens and optional `name`.
|
||||
|
||||
3. Restart the gateway. Zalo starts when a token is resolved (env or config).
|
||||
4. DM access defaults to pairing. Approve the code when the bot is first contacted.
|
||||
|
||||
## How it works (behavior)
|
||||
|
||||
- Inbound messages are normalized into the shared channel envelope with media placeholders.
|
||||
- Replies always route back to the same Zalo chat.
|
||||
- Long-polling by default; webhook mode available with `channels.zalo.webhookUrl`.
|
||||
|
||||
## Limits
|
||||
|
||||
- Outbound text is chunked to 2000 characters (Zalo API limit).
|
||||
- Media downloads/uploads are capped by `channels.zalo.mediaMaxMb` (default 5).
|
||||
- Streaming is blocked by default due to the 2000 char limit making streaming less useful.
|
||||
|
||||
## Access control (DMs)
|
||||
|
||||
### DM access
|
||||
|
||||
- Default: `channels.zalo.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
- `openclaw pairing list zalo`
|
||||
- `openclaw pairing approve zalo <CODE>`
|
||||
- Pairing is the default token exchange. Details: [Pairing](/channels/pairing)
|
||||
- `channels.zalo.allowFrom` accepts numeric user IDs (no username lookup available).
|
||||
|
||||
## Access control (Groups)
|
||||
|
||||
- `channels.zalo.groupPolicy` controls group inbound handling: `open | allowlist | disabled`.
|
||||
- Default behavior is fail-closed: `allowlist`.
|
||||
- `channels.zalo.groupAllowFrom` restricts which sender IDs can trigger the bot in groups.
|
||||
- If `groupAllowFrom` is unset, Zalo falls back to `allowFrom` for sender checks.
|
||||
- `groupPolicy: "disabled"` blocks all group messages.
|
||||
- `groupPolicy: "open"` allows any group member (mention-gated).
|
||||
- Runtime note: if `channels.zalo` is missing entirely, runtime still falls back to `groupPolicy="allowlist"` for safety.
|
||||
|
||||
## Long-polling vs webhook
|
||||
|
||||
- Default: long-polling (no public URL required).
|
||||
- Webhook mode: set `channels.zalo.webhookUrl` and `channels.zalo.webhookSecret`.
|
||||
- The webhook secret must be 8-256 characters.
|
||||
- Webhook URL must use HTTPS.
|
||||
- Zalo sends events with `X-Bot-Api-Secret-Token` header for verification.
|
||||
- Gateway HTTP handles webhook requests at `channels.zalo.webhookPath` (defaults to the webhook URL path).
|
||||
- Requests must use `Content-Type: application/json` (or `+json` media types).
|
||||
- Duplicate events (`event_name + message_id`) are ignored for a short replay window.
|
||||
- Burst traffic is rate-limited per path/source and may return HTTP 429.
|
||||
|
||||
**Note:** getUpdates (polling) and webhook are mutually exclusive per Zalo API docs.
|
||||
|
||||
## Supported message types
|
||||
|
||||
- **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 | ⚠️ Supported with policy controls (allowlist by default) |
|
||||
| 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.
|
||||
- `channels.zalo.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing).
|
||||
- `channels.zalo.allowFrom`: DM allowlist (user IDs). `open` requires `"*"`. The wizard will ask for numeric IDs.
|
||||
- `channels.zalo.groupPolicy`: `open | allowlist | disabled` (default: allowlist).
|
||||
- `channels.zalo.groupAllowFrom`: group sender allowlist (user IDs). Falls back to `allowFrom` when unset.
|
||||
- `channels.zalo.mediaMaxMb`: inbound/outbound media cap (MB, default 5).
|
||||
- `channels.zalo.webhookUrl`: enable webhook mode (HTTPS required).
|
||||
- `channels.zalo.webhookSecret`: webhook secret (8-256 chars).
|
||||
- `channels.zalo.webhookPath`: webhook path on the gateway HTTP server.
|
||||
- `channels.zalo.proxy`: proxy URL for API requests.
|
||||
|
||||
Multi-account options:
|
||||
|
||||
- `channels.zalo.accounts.<id>.botToken`: per-account token.
|
||||
- `channels.zalo.accounts.<id>.tokenFile`: per-account token file.
|
||||
- `channels.zalo.accounts.<id>.name`: display name.
|
||||
- `channels.zalo.accounts.<id>.enabled`: enable/disable account.
|
||||
- `channels.zalo.accounts.<id>.dmPolicy`: per-account DM policy.
|
||||
- `channels.zalo.accounts.<id>.allowFrom`: per-account allowlist.
|
||||
- `channels.zalo.accounts.<id>.groupPolicy`: per-account group policy.
|
||||
- `channels.zalo.accounts.<id>.groupAllowFrom`: per-account group sender allowlist.
|
||||
- `channels.zalo.accounts.<id>.webhookUrl`: per-account webhook URL.
|
||||
- `channels.zalo.accounts.<id>.webhookSecret`: per-account webhook secret.
|
||||
- `channels.zalo.accounts.<id>.webhookPath`: per-account webhook path.
|
||||
- `channels.zalo.accounts.<id>.proxy`: per-account proxy URL.
|
||||
140
openclaw/docs/channels/zalouser.md
Normal file
@@ -0,0 +1,140 @@
|
||||
---
|
||||
summary: "Zalo personal account support via zca-cli (QR login), capabilities, and configuration"
|
||||
read_when:
|
||||
- Setting up Zalo Personal for OpenClaw
|
||||
- Debugging Zalo Personal login or message flow
|
||||
title: "Zalo Personal"
|
||||
---
|
||||
|
||||
# Zalo Personal (unofficial)
|
||||
|
||||
Status: experimental. This integration automates a **personal Zalo account** via `zca-cli`.
|
||||
|
||||
> **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](/tools/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):
|
||||
- `openclaw channels login --channel zalouser`
|
||||
- Scan the QR code in the terminal with the Zalo mobile app.
|
||||
3. Enable the channel:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalouser: {
|
||||
enabled: true,
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
openclaw directory self --channel zalouser
|
||||
openclaw directory peers list --channel zalouser --query "name"
|
||||
openclaw directory groups list --channel zalouser --query "work"
|
||||
```
|
||||
|
||||
## Limits
|
||||
|
||||
- Outbound text is chunked to ~2000 characters (Zalo client limits).
|
||||
- Streaming is blocked by default.
|
||||
|
||||
## Access control (DMs)
|
||||
|
||||
`channels.zalouser.dmPolicy` supports: `pairing | allowlist | open | disabled` (default: `pairing`).
|
||||
`channels.zalouser.allowFrom` accepts user IDs or names. The wizard resolves names to IDs via `zca friend find` when available.
|
||||
|
||||
Approve via:
|
||||
|
||||
- `openclaw pairing list zalouser`
|
||||
- `openclaw pairing approve zalouser <code>`
|
||||
|
||||
## Group access (optional)
|
||||
|
||||
- Default: `channels.zalouser.groupPolicy = "open"` (groups allowed). Use `channels.defaults.groupPolicy` to override the default when unset.
|
||||
- Restrict to an allowlist with:
|
||||
- `channels.zalouser.groupPolicy = "allowlist"`
|
||||
- `channels.zalouser.groups` (keys are group IDs or names)
|
||||
- Block all groups: `channels.zalouser.groupPolicy = "disabled"`.
|
||||
- The configure wizard can prompt for group allowlists.
|
||||
- On startup, OpenClaw resolves group/user names in allowlists to IDs and logs the mapping; unresolved entries are kept as typed.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalouser: {
|
||||
groupPolicy: "allowlist",
|
||||
groups: {
|
||||
"123456789": { allow: true },
|
||||
"Work Chat": { allow: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Multi-account
|
||||
|
||||
Accounts map to zca profiles. Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalouser: {
|
||||
enabled: true,
|
||||
defaultAccount: "default",
|
||||
accounts: {
|
||||
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`
|
||||
54
openclaw/docs/ci.md
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
title: CI Pipeline
|
||||
description: How the OpenClaw CI pipeline works
|
||||
summary: "CI job graph, scope gates, and local command equivalents"
|
||||
read_when:
|
||||
- You need to understand why a CI job did or did not run
|
||||
- You are debugging failing GitHub Actions checks
|
||||
---
|
||||
|
||||
# CI Pipeline
|
||||
|
||||
The CI runs on every push to `main` and every pull request. It uses smart scoping to skip expensive jobs when only docs or native code changed.
|
||||
|
||||
## Job Overview
|
||||
|
||||
| Job | Purpose | When it runs |
|
||||
| ----------------- | ----------------------------------------------- | ------------------------- |
|
||||
| `docs-scope` | Detect docs-only changes | Always |
|
||||
| `changed-scope` | Detect which areas changed (node/macos/android) | Non-docs PRs |
|
||||
| `check` | TypeScript types, lint, format | Non-docs changes |
|
||||
| `check-docs` | Markdown lint + broken link check | Docs changed |
|
||||
| `code-analysis` | LOC threshold check (1000 lines) | PRs only |
|
||||
| `secrets` | Detect leaked secrets | Always |
|
||||
| `build-artifacts` | Build dist once, share with other jobs | Non-docs, node changes |
|
||||
| `release-check` | Validate npm pack contents | After build |
|
||||
| `checks` | Node/Bun tests + protocol check | Non-docs, node changes |
|
||||
| `checks-windows` | Windows-specific tests | Non-docs, node changes |
|
||||
| `macos` | Swift lint/build/test + TS tests | PRs with macos changes |
|
||||
| `android` | Gradle build + tests | Non-docs, android changes |
|
||||
|
||||
## Fail-Fast Order
|
||||
|
||||
Jobs are ordered so cheap checks fail before expensive ones run:
|
||||
|
||||
1. `docs-scope` + `code-analysis` + `check` (parallel, ~1-2 min)
|
||||
2. `build-artifacts` (blocked on above)
|
||||
3. `checks`, `checks-windows`, `macos`, `android` (blocked on build)
|
||||
|
||||
## Runners
|
||||
|
||||
| Runner | Jobs |
|
||||
| -------------------------------- | ------------------------------------------ |
|
||||
| `blacksmith-16vcpu-ubuntu-2404` | Most Linux jobs, including scope detection |
|
||||
| `blacksmith-16vcpu-windows-2025` | `checks-windows` |
|
||||
| `macos-latest` | `macos`, `ios` |
|
||||
|
||||
## Local Equivalents
|
||||
|
||||
```bash
|
||||
pnpm check # types + lint + format
|
||||
pnpm test # vitest tests
|
||||
pnpm check:docs # docs format + lint + broken links
|
||||
pnpm release:check # validate npm pack
|
||||
```
|
||||
189
openclaw/docs/cli/acp.md
Normal file
@@ -0,0 +1,189 @@
|
||||
---
|
||||
summary: "Run the ACP bridge for IDE integrations"
|
||||
read_when:
|
||||
- Setting up ACP-based IDE integrations
|
||||
- Debugging ACP session routing to the Gateway
|
||||
title: "acp"
|
||||
---
|
||||
|
||||
# acp
|
||||
|
||||
Run the [Agent Client Protocol (ACP)](https://agentclientprotocol.com/) bridge that talks to a OpenClaw Gateway.
|
||||
|
||||
This command speaks ACP over stdio for IDEs and forwards prompts to the Gateway
|
||||
over WebSocket. It keeps ACP sessions mapped to Gateway session keys.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
openclaw acp
|
||||
|
||||
# Remote Gateway
|
||||
openclaw acp --url wss://gateway-host:18789 --token <token>
|
||||
|
||||
# Remote Gateway (token from file)
|
||||
openclaw acp --url wss://gateway-host:18789 --token-file ~/.openclaw/gateway.token
|
||||
|
||||
# Attach to an existing session key
|
||||
openclaw acp --session agent:main:main
|
||||
|
||||
# Attach by label (must already exist)
|
||||
openclaw acp --session-label "support inbox"
|
||||
|
||||
# Reset the session key before the first prompt
|
||||
openclaw acp --session agent:main:main --reset-session
|
||||
```
|
||||
|
||||
## ACP client (debug)
|
||||
|
||||
Use the built-in ACP client to sanity-check the bridge without an IDE.
|
||||
It spawns the ACP bridge and lets you type prompts interactively.
|
||||
|
||||
```bash
|
||||
openclaw acp client
|
||||
|
||||
# Point the spawned bridge at a remote Gateway
|
||||
openclaw acp client --server-args --url wss://gateway-host:18789 --token-file ~/.openclaw/gateway.token
|
||||
|
||||
# Override the server command (default: openclaw)
|
||||
openclaw acp client --server "node" --server-args openclaw.mjs acp --url ws://127.0.0.1:19001
|
||||
```
|
||||
|
||||
Permission model (client debug mode):
|
||||
|
||||
- Auto-approval is allowlist-based and only applies to trusted core tool IDs.
|
||||
- `read` auto-approval is scoped to the current working directory (`--cwd` when set).
|
||||
- Unknown/non-core tool names, out-of-scope reads, and dangerous tools always require explicit prompt approval.
|
||||
- Server-provided `toolCall.kind` is treated as untrusted metadata (not an authorization source).
|
||||
|
||||
## How to use this
|
||||
|
||||
Use ACP when an IDE (or other client) speaks Agent Client Protocol and you want
|
||||
it to drive a OpenClaw Gateway session.
|
||||
|
||||
1. Ensure the Gateway is running (local or remote).
|
||||
2. Configure the Gateway target (config or flags).
|
||||
3. Point your IDE to run `openclaw acp` over stdio.
|
||||
|
||||
Example config (persisted):
|
||||
|
||||
```bash
|
||||
openclaw config set gateway.remote.url wss://gateway-host:18789
|
||||
openclaw config set gateway.remote.token <token>
|
||||
```
|
||||
|
||||
Example direct run (no config write):
|
||||
|
||||
```bash
|
||||
openclaw acp --url wss://gateway-host:18789 --token <token>
|
||||
# preferred for local process safety
|
||||
openclaw acp --url wss://gateway-host:18789 --token-file ~/.openclaw/gateway.token
|
||||
```
|
||||
|
||||
## Selecting agents
|
||||
|
||||
ACP does not pick agents directly. It routes by the Gateway session key.
|
||||
|
||||
Use agent-scoped session keys to target a specific agent:
|
||||
|
||||
```bash
|
||||
openclaw acp --session agent:main:main
|
||||
openclaw acp --session agent:design:main
|
||||
openclaw acp --session agent:qa:bug-123
|
||||
```
|
||||
|
||||
Each ACP session maps to a single Gateway session key. One agent can have many
|
||||
sessions; ACP defaults to an isolated `acp:<uuid>` session unless you override
|
||||
the key or label.
|
||||
|
||||
## Zed editor setup
|
||||
|
||||
Add a custom ACP agent in `~/.config/zed/settings.json` (or use Zed’s Settings UI):
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"OpenClaw ACP": {
|
||||
"type": "custom",
|
||||
"command": "openclaw",
|
||||
"args": ["acp"],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To target a specific Gateway or agent:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"OpenClaw ACP": {
|
||||
"type": "custom",
|
||||
"command": "openclaw",
|
||||
"args": [
|
||||
"acp",
|
||||
"--url",
|
||||
"wss://gateway-host:18789",
|
||||
"--token",
|
||||
"<token>",
|
||||
"--session",
|
||||
"agent:design:main"
|
||||
],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In Zed, open the Agent panel and select “OpenClaw ACP” to start a thread.
|
||||
|
||||
## Session mapping
|
||||
|
||||
By default, ACP sessions get an isolated Gateway session key with an `acp:` prefix.
|
||||
To reuse a known session, pass a session key or label:
|
||||
|
||||
- `--session <key>`: use a specific Gateway session key.
|
||||
- `--session-label <label>`: resolve an existing session by label.
|
||||
- `--reset-session`: mint a fresh session id for that key (same key, new transcript).
|
||||
|
||||
If your ACP client supports metadata, you can override per session:
|
||||
|
||||
```json
|
||||
{
|
||||
"_meta": {
|
||||
"sessionKey": "agent:main:main",
|
||||
"sessionLabel": "support inbox",
|
||||
"resetSession": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Learn more about session keys at [/concepts/session](/concepts/session).
|
||||
|
||||
## Options
|
||||
|
||||
- `--url <url>`: Gateway WebSocket URL (defaults to gateway.remote.url when configured).
|
||||
- `--token <token>`: Gateway auth token.
|
||||
- `--token-file <path>`: read Gateway auth token from file.
|
||||
- `--password <password>`: Gateway auth password.
|
||||
- `--password-file <path>`: read Gateway auth password from file.
|
||||
- `--session <key>`: default session key.
|
||||
- `--session-label <label>`: default session label to resolve.
|
||||
- `--require-existing`: fail if the session key/label does not exist.
|
||||
- `--reset-session`: reset the session key before first use.
|
||||
- `--no-prefix-cwd`: do not prefix prompts with the working directory.
|
||||
- `--verbose, -v`: verbose logging to stderr.
|
||||
|
||||
Security note:
|
||||
|
||||
- `--token` and `--password` can be visible in local process listings on some systems.
|
||||
- Prefer `--token-file`/`--password-file` or environment variables (`OPENCLAW_GATEWAY_TOKEN`, `OPENCLAW_GATEWAY_PASSWORD`).
|
||||
|
||||
### `acp client` options
|
||||
|
||||
- `--cwd <dir>`: working directory for the ACP session.
|
||||
- `--server <command>`: ACP server command (default: `openclaw`).
|
||||
- `--server-args <args...>`: extra arguments passed to the ACP server.
|
||||
- `--server-verbose`: enable verbose logging on the ACP server.
|
||||
- `--verbose, -v`: verbose client logging.
|
||||
24
openclaw/docs/cli/agent.md
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw agent` (send one agent turn via the Gateway)"
|
||||
read_when:
|
||||
- You want to run one agent turn from scripts (optionally deliver reply)
|
||||
title: "agent"
|
||||
---
|
||||
|
||||
# `openclaw agent`
|
||||
|
||||
Run an agent turn via the Gateway (use `--local` for embedded).
|
||||
Use `--agent <id>` to target a configured agent directly.
|
||||
|
||||
Related:
|
||||
|
||||
- Agent send tool: [Agent send](/tools/agent-send)
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
openclaw agent --to +15555550123 --message "status update" --deliver
|
||||
openclaw agent --agent ops --message "Summarize logs"
|
||||
openclaw agent --session-id 1234 --message "Summarize inbox" --thinking medium
|
||||
openclaw agent --agent ops --message "Generate report" --deliver --reply-channel slack --reply-to "#reports"
|
||||
```
|
||||
123
openclaw/docs/cli/agents.md
Normal file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw agents` (list/add/delete/bindings/bind/unbind/set identity)"
|
||||
read_when:
|
||||
- You want multiple isolated agents (workspaces + routing + auth)
|
||||
title: "agents"
|
||||
---
|
||||
|
||||
# `openclaw agents`
|
||||
|
||||
Manage isolated agents (workspaces + auth + routing).
|
||||
|
||||
Related:
|
||||
|
||||
- Multi-agent routing: [Multi-Agent Routing](/concepts/multi-agent)
|
||||
- Agent workspace: [Agent workspace](/concepts/agent-workspace)
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
openclaw agents list
|
||||
openclaw agents add work --workspace ~/.openclaw/workspace-work
|
||||
openclaw agents bindings
|
||||
openclaw agents bind --agent work --bind telegram:ops
|
||||
openclaw agents unbind --agent work --bind telegram:ops
|
||||
openclaw agents set-identity --workspace ~/.openclaw/workspace --from-identity
|
||||
openclaw agents set-identity --agent main --avatar avatars/openclaw.png
|
||||
openclaw agents delete work
|
||||
```
|
||||
|
||||
## Routing bindings
|
||||
|
||||
Use routing bindings to pin inbound channel traffic to a specific agent.
|
||||
|
||||
List bindings:
|
||||
|
||||
```bash
|
||||
openclaw agents bindings
|
||||
openclaw agents bindings --agent work
|
||||
openclaw agents bindings --json
|
||||
```
|
||||
|
||||
Add bindings:
|
||||
|
||||
```bash
|
||||
openclaw agents bind --agent work --bind telegram:ops --bind discord:guild-a
|
||||
```
|
||||
|
||||
If you omit `accountId` (`--bind <channel>`), OpenClaw resolves it from channel defaults and plugin setup hooks when available.
|
||||
|
||||
### Binding scope behavior
|
||||
|
||||
- A binding without `accountId` matches the channel default account only.
|
||||
- `accountId: "*"` is the channel-wide fallback (all accounts) and is less specific than an explicit account binding.
|
||||
- If the same agent already has a matching channel binding without `accountId`, and you later bind with an explicit or resolved `accountId`, OpenClaw upgrades that existing binding in place instead of adding a duplicate.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
# initial channel-only binding
|
||||
openclaw agents bind --agent work --bind telegram
|
||||
|
||||
# later upgrade to account-scoped binding
|
||||
openclaw agents bind --agent work --bind telegram:ops
|
||||
```
|
||||
|
||||
After the upgrade, routing for that binding is scoped to `telegram:ops`. If you also want default-account routing, add it explicitly (for example `--bind telegram:default`).
|
||||
|
||||
Remove bindings:
|
||||
|
||||
```bash
|
||||
openclaw agents unbind --agent work --bind telegram:ops
|
||||
openclaw agents unbind --agent work --all
|
||||
```
|
||||
|
||||
## 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`)
|
||||
|
||||
Avatar paths resolve relative to the workspace root.
|
||||
|
||||
## Set identity
|
||||
|
||||
`set-identity` writes fields into `agents.list[].identity`:
|
||||
|
||||
- `name`
|
||||
- `theme`
|
||||
- `emoji`
|
||||
- `avatar` (workspace-relative path, http(s) URL, or data URI)
|
||||
|
||||
Load from `IDENTITY.md`:
|
||||
|
||||
```bash
|
||||
openclaw agents set-identity --workspace ~/.openclaw/workspace --from-identity
|
||||
```
|
||||
|
||||
Override fields explicitly:
|
||||
|
||||
```bash
|
||||
openclaw agents set-identity --agent main --name "OpenClaw" --emoji "🦞" --avatar avatars/openclaw.png
|
||||
```
|
||||
|
||||
Config sample:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
identity: {
|
||||
name: "OpenClaw",
|
||||
theme: "space lobster",
|
||||
emoji: "🦞",
|
||||
avatar: "avatars/openclaw.png",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
50
openclaw/docs/cli/approvals.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw approvals` (exec approvals for gateway or node hosts)"
|
||||
read_when:
|
||||
- You want to edit exec approvals from the CLI
|
||||
- You need to manage allowlists on gateway or node hosts
|
||||
title: "approvals"
|
||||
---
|
||||
|
||||
# `openclaw approvals`
|
||||
|
||||
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)
|
||||
|
||||
## Common commands
|
||||
|
||||
```bash
|
||||
openclaw approvals get
|
||||
openclaw approvals get --node <id|name|ip>
|
||||
openclaw approvals get --gateway
|
||||
```
|
||||
|
||||
## Replace approvals from a file
|
||||
|
||||
```bash
|
||||
openclaw approvals set --file ./exec-approvals.json
|
||||
openclaw approvals set --node <id|name|ip> --file ./exec-approvals.json
|
||||
openclaw approvals set --gateway --file ./exec-approvals.json
|
||||
```
|
||||
|
||||
## Allowlist helpers
|
||||
|
||||
```bash
|
||||
openclaw approvals allowlist add "~/Projects/**/bin/rg"
|
||||
openclaw approvals allowlist add --agent main --node <id|name|ip> "/usr/bin/uptime"
|
||||
openclaw approvals allowlist add --agent "*" "/usr/bin/uname"
|
||||
|
||||
openclaw approvals allowlist remove "~/Projects/**/bin/rg"
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `--node` uses the same resolver as `openclaw nodes` (id, name, ip, or id prefix).
|
||||
- `--agent` defaults to `"*"`, which applies to all agents.
|
||||
- The node host must advertise `system.execApprovals.get/set` (macOS app or headless node host).
|
||||
- Approvals files are stored per host at `~/.openclaw/exec-approvals.json`.
|
||||
107
openclaw/docs/cli/browser.md
Normal file
@@ -0,0 +1,107 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw browser` (profiles, tabs, actions, extension relay)"
|
||||
read_when:
|
||||
- You use `openclaw browser` and want examples for common tasks
|
||||
- You want to control a browser running on another machine via a node host
|
||||
- You want to use the Chrome extension relay (attach/detach via toolbar button)
|
||||
title: "browser"
|
||||
---
|
||||
|
||||
# `openclaw browser`
|
||||
|
||||
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)
|
||||
|
||||
## Common flags
|
||||
|
||||
- `--url <gatewayWsUrl>`: Gateway WebSocket URL (defaults to config).
|
||||
- `--token <token>`: Gateway token (if required).
|
||||
- `--timeout <ms>`: request timeout (ms).
|
||||
- `--browser-profile <name>`: choose a browser profile (default from config).
|
||||
- `--json`: machine-readable output (where supported).
|
||||
|
||||
## Quick start (local)
|
||||
|
||||
```bash
|
||||
openclaw browser --browser-profile chrome tabs
|
||||
openclaw browser --browser-profile openclaw start
|
||||
openclaw browser --browser-profile openclaw open https://example.com
|
||||
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.
|
||||
|
||||
```bash
|
||||
openclaw browser profiles
|
||||
openclaw browser create-profile --name work --color "#FF5A36"
|
||||
openclaw browser delete-profile --name work
|
||||
```
|
||||
|
||||
Use a specific profile:
|
||||
|
||||
```bash
|
||||
openclaw browser --browser-profile work tabs
|
||||
```
|
||||
|
||||
## Tabs
|
||||
|
||||
```bash
|
||||
openclaw browser tabs
|
||||
openclaw browser open https://docs.openclaw.ai
|
||||
openclaw browser focus <targetId>
|
||||
openclaw browser close <targetId>
|
||||
```
|
||||
|
||||
## Snapshot / screenshot / actions
|
||||
|
||||
Snapshot:
|
||||
|
||||
```bash
|
||||
openclaw browser snapshot
|
||||
```
|
||||
|
||||
Screenshot:
|
||||
|
||||
```bash
|
||||
openclaw browser screenshot
|
||||
```
|
||||
|
||||
Navigate/click/type (ref-based UI automation):
|
||||
|
||||
```bash
|
||||
openclaw browser navigate https://example.com
|
||||
openclaw browser click <ref>
|
||||
openclaw browser type <ref> "hello"
|
||||
```
|
||||
|
||||
## Chrome extension relay (attach via toolbar button)
|
||||
|
||||
This mode lets the agent control an existing Chrome tab that you attach manually (it does not auto-attach).
|
||||
|
||||
Install the unpacked extension to a stable path:
|
||||
|
||||
```bash
|
||||
openclaw browser extension install
|
||||
openclaw browser extension path
|
||||
```
|
||||
|
||||
Then Chrome → `chrome://extensions` → enable “Developer mode” → “Load unpacked” → select the printed folder.
|
||||
|
||||
Full guide: [Chrome extension](/tools/chrome-extension)
|
||||
|
||||
## Remote browser control (node host proxy)
|
||||
|
||||
If the Gateway runs on a different machine than the browser, run a **node host** on the machine that has Chrome/Brave/Edge/Chromium. The Gateway will proxy browser actions to that node (no separate browser control server required).
|
||||
|
||||
Use `gateway.nodes.browser.mode` to control auto-routing and `gateway.nodes.browser.node` to pin a specific node if multiple are connected.
|
||||
|
||||
Security + remote setup: [Browser tool](/tools/browser), [Remote access](/gateway/remote), [Tailscale](/gateway/tailscale), [Security](/gateway/security)
|
||||
99
openclaw/docs/cli/channels.md
Normal file
@@ -0,0 +1,99 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw channels` (accounts, status, login/logout, logs)"
|
||||
read_when:
|
||||
- You want to add/remove channel accounts (WhatsApp/Telegram/Discord/Google Chat/Slack/Mattermost (plugin)/Signal/iMessage)
|
||||
- You want to check channel status or tail channel logs
|
||||
title: "channels"
|
||||
---
|
||||
|
||||
# `openclaw channels`
|
||||
|
||||
Manage chat channel accounts and their runtime status on the Gateway.
|
||||
|
||||
Related docs:
|
||||
|
||||
- Channel guides: [Channels](/channels/index)
|
||||
- Gateway configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
## Common commands
|
||||
|
||||
```bash
|
||||
openclaw channels list
|
||||
openclaw channels status
|
||||
openclaw channels capabilities
|
||||
openclaw channels capabilities --channel discord --target channel:123
|
||||
openclaw channels resolve --channel slack "#general" "@jane"
|
||||
openclaw channels logs --channel all
|
||||
```
|
||||
|
||||
## Add / remove accounts
|
||||
|
||||
```bash
|
||||
openclaw channels add --channel telegram --token <bot-token>
|
||||
openclaw channels remove --channel telegram --delete
|
||||
```
|
||||
|
||||
Tip: `openclaw channels add --help` shows per-channel flags (token, app token, signal-cli paths, etc).
|
||||
|
||||
When you run `openclaw channels add` without flags, the interactive wizard can prompt:
|
||||
|
||||
- account ids per selected channel
|
||||
- optional display names for those accounts
|
||||
- `Bind configured channel accounts to agents now?`
|
||||
|
||||
If you confirm bind now, the wizard asks which agent should own each configured channel account and writes account-scoped routing bindings.
|
||||
|
||||
You can also manage the same routing rules later with `openclaw agents bindings`, `openclaw agents bind`, and `openclaw agents unbind` (see [agents](/cli/agents)).
|
||||
|
||||
When you add a non-default account to a channel that is still using single-account top-level settings (no `channels.<channel>.accounts` entries yet), OpenClaw moves account-scoped single-account top-level values into `channels.<channel>.accounts.default`, then writes the new account. This preserves the original account behavior while moving to the multi-account shape.
|
||||
|
||||
Routing behavior stays consistent:
|
||||
|
||||
- Existing channel-only bindings (no `accountId`) continue to match the default account.
|
||||
- `channels add` does not auto-create or rewrite bindings in non-interactive mode.
|
||||
- Interactive setup can optionally add account-scoped bindings.
|
||||
|
||||
If your config was already in a mixed state (named accounts present, missing `default`, and top-level single-account values still set), run `openclaw doctor --fix` to move account-scoped values into `accounts.default`.
|
||||
|
||||
## Login / logout (interactive)
|
||||
|
||||
```bash
|
||||
openclaw channels login --channel whatsapp
|
||||
openclaw channels logout --channel whatsapp
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Run `openclaw status --deep` for a broad probe.
|
||||
- Use `openclaw doctor` for guided fixes.
|
||||
- `openclaw channels list` prints `Claude: HTTP 403 ... user:profile` → usage snapshot needs the `user:profile` scope. Use `--no-usage`, or provide a claude.ai session key (`CLAUDE_WEB_SESSION_KEY` / `CLAUDE_WEB_COOKIE`), or re-auth via Claude Code CLI.
|
||||
|
||||
## Capabilities probe
|
||||
|
||||
Fetch provider capability hints (intents/scopes where available) plus static feature support:
|
||||
|
||||
```bash
|
||||
openclaw channels capabilities
|
||||
openclaw channels capabilities --channel discord --target channel:123
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `--channel` is optional; omit it to list every channel (including extensions).
|
||||
- `--target` accepts `channel:<id>` 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`.
|
||||
|
||||
## Resolve names to IDs
|
||||
|
||||
Resolve channel/user names to IDs using the provider directory:
|
||||
|
||||
```bash
|
||||
openclaw channels resolve --channel slack "#general" "@jane"
|
||||
openclaw channels resolve --channel discord "My Server/#support" "@someone"
|
||||
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.
|
||||
21
openclaw/docs/cli/clawbot.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw clawbot` (legacy alias namespace)"
|
||||
read_when:
|
||||
- You maintain older scripts using `openclaw clawbot ...`
|
||||
- You need migration guidance to current commands
|
||||
title: "clawbot"
|
||||
---
|
||||
|
||||
# `openclaw clawbot`
|
||||
|
||||
Legacy alias namespace kept for backwards compatibility.
|
||||
|
||||
Current supported alias:
|
||||
|
||||
- `openclaw clawbot qr` (same behavior as [`openclaw qr`](/cli/qr))
|
||||
|
||||
## Migration
|
||||
|
||||
Prefer modern top-level commands directly:
|
||||
|
||||
- `openclaw clawbot qr` -> `openclaw qr`
|
||||
35
openclaw/docs/cli/completion.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw completion` (generate/install shell completion scripts)"
|
||||
read_when:
|
||||
- You want shell completions for zsh/bash/fish/PowerShell
|
||||
- You need to cache completion scripts under OpenClaw state
|
||||
title: "completion"
|
||||
---
|
||||
|
||||
# `openclaw completion`
|
||||
|
||||
Generate shell completion scripts and optionally install them into your shell profile.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
openclaw completion
|
||||
openclaw completion --shell zsh
|
||||
openclaw completion --install
|
||||
openclaw completion --shell fish --install
|
||||
openclaw completion --write-state
|
||||
openclaw completion --shell bash --write-state
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
- `-s, --shell <shell>`: shell target (`zsh`, `bash`, `powershell`, `fish`; default: `zsh`)
|
||||
- `-i, --install`: install completion by adding a source line to your shell profile
|
||||
- `--write-state`: write completion script(s) to `$OPENCLAW_STATE_DIR/completions` without printing to stdout
|
||||
- `-y, --yes`: skip install confirmation prompts
|
||||
|
||||
## Notes
|
||||
|
||||
- `--install` writes a small "OpenClaw Completion" block into your shell profile and points it at the cached script.
|
||||
- Without `--install` or `--write-state`, the command prints the script to stdout.
|
||||
- Completion generation eagerly loads command trees so nested subcommands are included.
|
||||
50
openclaw/docs/cli/config.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw config` (get/set/unset config values)"
|
||||
read_when:
|
||||
- You want to read or edit config non-interactively
|
||||
title: "config"
|
||||
---
|
||||
|
||||
# `openclaw config`
|
||||
|
||||
Config helpers: get/set/unset values by path. Run without a subcommand to open
|
||||
the configure wizard (same as `openclaw configure`).
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
openclaw config get browser.executablePath
|
||||
openclaw config set browser.executablePath "/usr/bin/google-chrome"
|
||||
openclaw config set agents.defaults.heartbeat.every "2h"
|
||||
openclaw config set agents.list[0].tools.exec.node "node-id-or-name"
|
||||
openclaw config unset tools.web.search.apiKey
|
||||
```
|
||||
|
||||
## Paths
|
||||
|
||||
Paths use dot or bracket notation:
|
||||
|
||||
```bash
|
||||
openclaw config get agents.defaults.workspace
|
||||
openclaw config get agents.list[0].id
|
||||
```
|
||||
|
||||
Use the agent list index to target a specific agent:
|
||||
|
||||
```bash
|
||||
openclaw config get agents.list
|
||||
openclaw config set agents.list[1].tools.exec.node "node-id-or-name"
|
||||
```
|
||||
|
||||
## Values
|
||||
|
||||
Values are parsed as JSON5 when possible; otherwise they are treated as strings.
|
||||
Use `--strict-json` to require JSON5 parsing. `--json` remains supported as a legacy alias.
|
||||
|
||||
```bash
|
||||
openclaw config set agents.defaults.heartbeat.every "0m"
|
||||
openclaw config set gateway.port 19001 --strict-json
|
||||
openclaw config set channels.whatsapp.groups '["*"]' --strict-json
|
||||
```
|
||||
|
||||
Restart the gateway after edits.
|
||||
33
openclaw/docs/cli/configure.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw configure` (interactive configuration prompts)"
|
||||
read_when:
|
||||
- You want to tweak credentials, devices, or agent defaults interactively
|
||||
title: "configure"
|
||||
---
|
||||
|
||||
# `openclaw configure`
|
||||
|
||||
Interactive prompt to set up credentials, devices, and agent defaults.
|
||||
|
||||
Note: The **Model** section now includes a multi-select for the
|
||||
`agents.defaults.models` allowlist (what shows up in `/model` and the model picker).
|
||||
|
||||
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.
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
openclaw configure
|
||||
openclaw configure --section model --section channels
|
||||
```
|
||||
49
openclaw/docs/cli/cron.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw cron` (schedule and run background jobs)"
|
||||
read_when:
|
||||
- You want scheduled jobs and wakeups
|
||||
- You’re debugging cron execution and logs
|
||||
title: "cron"
|
||||
---
|
||||
|
||||
# `openclaw cron`
|
||||
|
||||
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.
|
||||
|
||||
Note: isolated `cron add` jobs default to `--announce` delivery. Use `--no-deliver` to keep
|
||||
output internal. `--deliver` remains as a deprecated alias for `--announce`.
|
||||
|
||||
Note: one-shot (`--at`) jobs delete after success by default. Use `--keep-after-run` to keep them.
|
||||
|
||||
Note: recurring jobs now use exponential retry backoff after consecutive errors (30s → 1m → 5m → 15m → 60m), then return to normal schedule after the next successful run.
|
||||
|
||||
Note: retention/pruning is controlled in config:
|
||||
|
||||
- `cron.sessionRetention` (default `24h`) prunes completed isolated run sessions.
|
||||
- `cron.runLog.maxBytes` + `cron.runLog.keepLines` prune `~/.openclaw/cron/runs/<jobId>.jsonl`.
|
||||
|
||||
## Common edits
|
||||
|
||||
Update delivery settings without changing the message:
|
||||
|
||||
```bash
|
||||
openclaw cron edit <job-id> --announce --channel telegram --to "123456789"
|
||||
```
|
||||
|
||||
Disable delivery for an isolated job:
|
||||
|
||||
```bash
|
||||
openclaw cron edit <job-id> --no-deliver
|
||||
```
|
||||
|
||||
Announce to a specific channel:
|
||||
|
||||
```bash
|
||||
openclaw cron edit <job-id> --announce --channel slack --to "channel:C1234567890"
|
||||
```
|
||||
43
openclaw/docs/cli/daemon.md
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw daemon` (legacy alias for gateway service management)"
|
||||
read_when:
|
||||
- You still use `openclaw daemon ...` in scripts
|
||||
- You need service lifecycle commands (install/start/stop/restart/status)
|
||||
title: "daemon"
|
||||
---
|
||||
|
||||
# `openclaw daemon`
|
||||
|
||||
Legacy alias for Gateway service management commands.
|
||||
|
||||
`openclaw daemon ...` maps to the same service control surface as `openclaw gateway ...` service commands.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
openclaw daemon status
|
||||
openclaw daemon install
|
||||
openclaw daemon start
|
||||
openclaw daemon stop
|
||||
openclaw daemon restart
|
||||
openclaw daemon uninstall
|
||||
```
|
||||
|
||||
## Subcommands
|
||||
|
||||
- `status`: show service install state and probe Gateway health
|
||||
- `install`: install service (`launchd`/`systemd`/`schtasks`)
|
||||
- `uninstall`: remove service
|
||||
- `start`: start service
|
||||
- `stop`: stop service
|
||||
- `restart`: restart service
|
||||
|
||||
## Common options
|
||||
|
||||
- `status`: `--url`, `--token`, `--password`, `--timeout`, `--no-probe`, `--deep`, `--json`
|
||||
- `install`: `--port`, `--runtime <node|bun>`, `--token`, `--force`, `--json`
|
||||
- lifecycle (`uninstall|start|stop|restart`): `--json`
|
||||
|
||||
## Prefer
|
||||
|
||||
Use [`openclaw gateway`](/cli/gateway) for current docs and examples.
|
||||
16
openclaw/docs/cli/dashboard.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw dashboard` (open the Control UI)"
|
||||
read_when:
|
||||
- You want to open the Control UI with your current token
|
||||
- You want to print the URL without launching a browser
|
||||
title: "dashboard"
|
||||
---
|
||||
|
||||
# `openclaw dashboard`
|
||||
|
||||
Open the Control UI using your current auth.
|
||||
|
||||
```bash
|
||||
openclaw dashboard
|
||||
openclaw dashboard --no-open
|
||||
```
|
||||
94
openclaw/docs/cli/devices.md
Normal file
@@ -0,0 +1,94 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw devices` (device pairing + token rotation/revocation)"
|
||||
read_when:
|
||||
- You are approving device pairing requests
|
||||
- You need to rotate or revoke device tokens
|
||||
title: "devices"
|
||||
---
|
||||
|
||||
# `openclaw devices`
|
||||
|
||||
Manage device pairing requests and device-scoped tokens.
|
||||
|
||||
## Commands
|
||||
|
||||
### `openclaw devices list`
|
||||
|
||||
List pending pairing requests and paired devices.
|
||||
|
||||
```
|
||||
openclaw devices list
|
||||
openclaw devices list --json
|
||||
```
|
||||
|
||||
### `openclaw devices remove <deviceId>`
|
||||
|
||||
Remove one paired device entry.
|
||||
|
||||
```
|
||||
openclaw devices remove <deviceId>
|
||||
openclaw devices remove <deviceId> --json
|
||||
```
|
||||
|
||||
### `openclaw devices clear --yes [--pending]`
|
||||
|
||||
Clear paired devices in bulk.
|
||||
|
||||
```
|
||||
openclaw devices clear --yes
|
||||
openclaw devices clear --yes --pending
|
||||
openclaw devices clear --yes --pending --json
|
||||
```
|
||||
|
||||
### `openclaw devices approve [requestId] [--latest]`
|
||||
|
||||
Approve a pending device pairing request. If `requestId` is omitted, OpenClaw
|
||||
automatically approves the most recent pending request.
|
||||
|
||||
```
|
||||
openclaw devices approve
|
||||
openclaw devices approve <requestId>
|
||||
openclaw devices approve --latest
|
||||
```
|
||||
|
||||
### `openclaw devices reject <requestId>`
|
||||
|
||||
Reject a pending device pairing request.
|
||||
|
||||
```
|
||||
openclaw devices reject <requestId>
|
||||
```
|
||||
|
||||
### `openclaw devices rotate --device <id> --role <role> [--scope <scope...>]`
|
||||
|
||||
Rotate a device token for a specific role (optionally updating scopes).
|
||||
|
||||
```
|
||||
openclaw devices rotate --device <deviceId> --role operator --scope operator.read --scope operator.write
|
||||
```
|
||||
|
||||
### `openclaw devices revoke --device <id> --role <role>`
|
||||
|
||||
Revoke a device token for a specific role.
|
||||
|
||||
```
|
||||
openclaw devices revoke --device <deviceId> --role node
|
||||
```
|
||||
|
||||
## Common options
|
||||
|
||||
- `--url <url>`: Gateway WebSocket URL (defaults to `gateway.remote.url` when configured).
|
||||
- `--token <token>`: Gateway token (if required).
|
||||
- `--password <password>`: Gateway password (password auth).
|
||||
- `--timeout <ms>`: RPC timeout.
|
||||
- `--json`: JSON output (recommended for scripting).
|
||||
|
||||
Note: when you set `--url`, the CLI does not fall back to config or environment credentials.
|
||||
Pass `--token` or `--password` explicitly. Missing explicit credentials is an error.
|
||||
|
||||
## Notes
|
||||
|
||||
- Token rotation returns a new token (sensitive). Treat it like a secret.
|
||||
- These commands require `operator.pairing` (or `operator.admin`) scope.
|
||||
- `devices clear` is intentionally gated by `--yes`.
|
||||
- If pairing scope is unavailable on local loopback (and no explicit `--url` is passed), list/approve can use a local pairing fallback.
|
||||
63
openclaw/docs/cli/directory.md
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw directory` (self, peers, groups)"
|
||||
read_when:
|
||||
- You want to look up contacts/groups/self ids for a channel
|
||||
- You are developing a channel directory adapter
|
||||
title: "directory"
|
||||
---
|
||||
|
||||
# `openclaw directory`
|
||||
|
||||
Directory lookups for channels that support it (contacts/peers, groups, and “me”).
|
||||
|
||||
## Common flags
|
||||
|
||||
- `--channel <name>`: channel id/alias (required when multiple channels are configured; auto when only one is configured)
|
||||
- `--account <id>`: 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.
|
||||
|
||||
## Using results with `message send`
|
||||
|
||||
```bash
|
||||
openclaw directory peers list --channel slack --query "U0"
|
||||
openclaw message send --channel slack --target user:U012ABCDEF --message "hello"
|
||||
```
|
||||
|
||||
## ID formats (by channel)
|
||||
|
||||
- WhatsApp: `+15551234567` (DM), `1234567890-1234567890@g.us` (group)
|
||||
- Telegram: `@username` or numeric chat id; groups are numeric ids
|
||||
- Slack: `user:U…` and `channel:C…`
|
||||
- Discord: `user:<id>` and `channel:<id>`
|
||||
- Matrix (plugin): `user:@user:server`, `room:!roomId:server`, or `#alias:server`
|
||||
- Microsoft Teams (plugin): `user:<id>` and `conversation:<id>`
|
||||
- Zalo (plugin): user id (Bot API)
|
||||
- Zalo Personal / `zalouser` (plugin): thread id (DM/group) from `zca` (`me`, `friend list`, `group list`)
|
||||
|
||||
## Self (“me”)
|
||||
|
||||
```bash
|
||||
openclaw directory self --channel zalouser
|
||||
```
|
||||
|
||||
## Peers (contacts/users)
|
||||
|
||||
```bash
|
||||
openclaw directory peers list --channel zalouser
|
||||
openclaw directory peers list --channel zalouser --query "name"
|
||||
openclaw directory peers list --channel zalouser --limit 50
|
||||
```
|
||||
|
||||
## Groups
|
||||
|
||||
```bash
|
||||
openclaw directory groups list --channel zalouser
|
||||
openclaw directory groups list --channel zalouser --query "work"
|
||||
openclaw directory groups members --channel zalouser --group-id <id>
|
||||
```
|
||||
23
openclaw/docs/cli/dns.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw dns` (wide-area discovery helpers)"
|
||||
read_when:
|
||||
- You want wide-area discovery (DNS-SD) via Tailscale + CoreDNS
|
||||
- You’re setting up split DNS for a custom discovery domain (example: openclaw.internal)
|
||||
title: "dns"
|
||||
---
|
||||
|
||||
# `openclaw dns`
|
||||
|
||||
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)
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
openclaw dns setup
|
||||
openclaw dns setup --apply
|
||||
```
|
||||
15
openclaw/docs/cli/docs.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw docs` (search the live docs index)"
|
||||
read_when:
|
||||
- You want to search the live OpenClaw docs from the terminal
|
||||
title: "docs"
|
||||
---
|
||||
|
||||
# `openclaw docs`
|
||||
|
||||
Search the live docs index.
|
||||
|
||||
```bash
|
||||
openclaw docs browser extension
|
||||
openclaw docs sandbox allowHostControl
|
||||
```
|
||||
44
openclaw/docs/cli/doctor.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw doctor` (health checks + guided repairs)"
|
||||
read_when:
|
||||
- You have connectivity/auth issues and want guided fixes
|
||||
- You updated and want a sanity check
|
||||
title: "doctor"
|
||||
---
|
||||
|
||||
# `openclaw doctor`
|
||||
|
||||
Health checks + quick fixes for the gateway and channels.
|
||||
|
||||
Related:
|
||||
|
||||
- Troubleshooting: [Troubleshooting](/gateway/troubleshooting)
|
||||
- Security audit: [Security](/gateway/security)
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
openclaw doctor
|
||||
openclaw doctor --repair
|
||||
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.
|
||||
- State integrity checks now detect orphan transcript files in the sessions directory and can archive them as `.deleted.<timestamp>` to reclaim space safely.
|
||||
- Doctor includes a memory-search readiness check and can recommend `openclaw configure --section model` when embedding credentials are missing.
|
||||
- If sandbox mode is enabled but Docker is unavailable, doctor reports a high-signal warning with remediation (`install Docker` or `openclaw config set agents.defaults.sandbox.mode off`).
|
||||
|
||||
## macOS: `launchctl` env overrides
|
||||
|
||||
If you previously ran `launchctl setenv OPENCLAW_GATEWAY_TOKEN ...` (or `...PASSWORD`), that value overrides your config file and can cause persistent “unauthorized” errors.
|
||||
|
||||
```bash
|
||||
launchctl getenv OPENCLAW_GATEWAY_TOKEN
|
||||
launchctl getenv OPENCLAW_GATEWAY_PASSWORD
|
||||
|
||||
launchctl unsetenv OPENCLAW_GATEWAY_TOKEN
|
||||
launchctl unsetenv OPENCLAW_GATEWAY_PASSWORD
|
||||
```
|
||||
202
openclaw/docs/cli/gateway.md
Normal file
@@ -0,0 +1,202 @@
|
||||
---
|
||||
summary: "OpenClaw Gateway CLI (`openclaw gateway`) — run, query, and discover gateways"
|
||||
read_when:
|
||||
- Running the Gateway from the CLI (dev or servers)
|
||||
- Debugging Gateway auth, bind modes, and connectivity
|
||||
- Discovering gateways via Bonjour (LAN + tailnet)
|
||||
title: "gateway"
|
||||
---
|
||||
|
||||
# Gateway CLI
|
||||
|
||||
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)
|
||||
|
||||
## Run the Gateway
|
||||
|
||||
Run a local Gateway process:
|
||||
|
||||
```bash
|
||||
openclaw gateway
|
||||
```
|
||||
|
||||
Foreground alias:
|
||||
|
||||
```bash
|
||||
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 (`commands.restart` is enabled by default; set `commands.restart: false` to block manual restart, while gateway tool/config apply/update remain allowed).
|
||||
- `SIGINT`/`SIGTERM` handlers stop the gateway process, but they don’t restore any custom terminal state. If you wrap the CLI with a TUI or raw-mode input, restore the terminal before exit.
|
||||
|
||||
### Options
|
||||
|
||||
- `--port <port>`: WebSocket port (default comes from config/env; usually `18789`).
|
||||
- `--bind <loopback|lan|tailnet|auto|custom>`: listener bind mode.
|
||||
- `--auth <token|password>`: auth mode override.
|
||||
- `--token <token>`: token override (also sets `OPENCLAW_GATEWAY_TOKEN` for the process).
|
||||
- `--password <password>`: password override (also sets `OPENCLAW_GATEWAY_PASSWORD` for the process).
|
||||
- `--tailscale <off|serve|funnel>`: expose the Gateway via Tailscale.
|
||||
- `--tailscale-reset-on-exit`: reset Tailscale serve/funnel config on shutdown.
|
||||
- `--allow-unconfigured`: allow gateway start without `gateway.mode=local` in config.
|
||||
- `--dev`: create a dev config + workspace if missing (skips BOOTSTRAP.md).
|
||||
- `--reset`: reset dev config + credentials + sessions + workspace (requires `--dev`).
|
||||
- `--force`: kill any existing listener on the selected port before starting.
|
||||
- `--verbose`: verbose logs.
|
||||
- `--claude-cli-logs`: only show claude-cli logs in the console (and enable its stdout/stderr).
|
||||
- `--ws-log <auto|full|compact>`: websocket log style (default `auto`).
|
||||
- `--compact`: alias for `--ws-log compact`.
|
||||
- `--raw-stream`: log raw model stream events to jsonl.
|
||||
- `--raw-stream-path <path>`: raw stream jsonl path.
|
||||
|
||||
## Query a running Gateway
|
||||
|
||||
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 <url>`: Gateway WebSocket URL.
|
||||
- `--token <token>`: Gateway token.
|
||||
- `--password <password>`: Gateway password.
|
||||
- `--timeout <ms>`: timeout/budget (varies per command).
|
||||
- `--expect-final`: wait for a “final” response (agent calls).
|
||||
|
||||
Note: when you set `--url`, the CLI does not fall back to config or environment credentials.
|
||||
Pass `--token` or `--password` explicitly. Missing explicit credentials is an error.
|
||||
|
||||
### `gateway health`
|
||||
|
||||
```bash
|
||||
openclaw gateway health --url ws://127.0.0.1:18789
|
||||
```
|
||||
|
||||
### `gateway status`
|
||||
|
||||
`gateway status` shows the Gateway service (launchd/systemd/schtasks) plus an optional RPC probe.
|
||||
|
||||
```bash
|
||||
openclaw gateway status
|
||||
openclaw gateway status --json
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `--url <url>`: override the probe URL.
|
||||
- `--token <token>`: token auth for the probe.
|
||||
- `--password <password>`: password auth for the probe.
|
||||
- `--timeout <ms>`: probe timeout (default `10000`).
|
||||
- `--no-probe`: skip the RPC probe (service-only view).
|
||||
- `--deep`: scan system-level services too.
|
||||
|
||||
### `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**.
|
||||
|
||||
If multiple gateways are reachable, it prints all of them. Multiple gateways are supported when you use isolated profiles/ports (e.g., a rescue bot), but most installs still run a single gateway.
|
||||
|
||||
```bash
|
||||
openclaw gateway probe
|
||||
openclaw gateway probe --json
|
||||
```
|
||||
|
||||
#### Remote over SSH (Mac app parity)
|
||||
|
||||
The macOS app “Remote over SSH” mode uses a local port-forward so the remote gateway (which may be bound to loopback only) becomes reachable at `ws://127.0.0.1:<port>`.
|
||||
|
||||
CLI equivalent:
|
||||
|
||||
```bash
|
||||
openclaw gateway probe --ssh user@gateway-host
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `--ssh <target>`: `user@host` or `user@host:port` (port defaults to `22`).
|
||||
- `--ssh-identity <path>`: 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`
|
||||
|
||||
### `gateway call <method>`
|
||||
|
||||
Low-level RPC helper.
|
||||
|
||||
```bash
|
||||
openclaw gateway call status
|
||||
openclaw gateway call logs.tail --params '{"sinceMs": 60000}'
|
||||
```
|
||||
|
||||
## Manage the Gateway service
|
||||
|
||||
```bash
|
||||
openclaw gateway install
|
||||
openclaw gateway start
|
||||
openclaw gateway stop
|
||||
openclaw gateway restart
|
||||
openclaw gateway uninstall
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `gateway install` supports `--port`, `--runtime`, `--token`, `--force`, `--json`.
|
||||
- Lifecycle commands accept `--json` for scripting.
|
||||
|
||||
## Discover gateways (Bonjour)
|
||||
|
||||
`gateway discover` scans for Gateway beacons (`_openclaw-gw._tcp`).
|
||||
|
||||
- Multicast DNS-SD: `local.`
|
||||
- Unicast DNS-SD (Wide-Area Bonjour): choose a domain (example: `openclaw.internal.`) and set up split DNS + a DNS server; see [/gateway/bonjour](/gateway/bonjour)
|
||||
|
||||
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`)
|
||||
- `sshPort` (SSH port; defaults to `22` if not present)
|
||||
- `tailnetDns` (MagicDNS hostname, when available)
|
||||
- `gatewayTls` / `gatewayTlsSha256` (TLS enabled + cert fingerprint)
|
||||
- `cliPath` (optional hint for remote installs)
|
||||
|
||||
### `gateway discover`
|
||||
|
||||
```bash
|
||||
openclaw gateway discover
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `--timeout <ms>`: per-command timeout (browse/resolve); default `2000`.
|
||||
- `--json`: machine-readable output (also disables styling/spinner).
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
openclaw gateway discover --timeout 4000
|
||||
openclaw gateway discover --json | jq '.beacons[].wsUrl'
|
||||
```
|
||||
21
openclaw/docs/cli/health.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw health` (gateway health endpoint via RPC)"
|
||||
read_when:
|
||||
- You want to quickly check the running Gateway’s health
|
||||
title: "health"
|
||||
---
|
||||
|
||||
# `openclaw health`
|
||||
|
||||
Fetch health from the running Gateway.
|
||||
|
||||
```bash
|
||||
openclaw health
|
||||
openclaw health --json
|
||||
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.
|
||||
313
openclaw/docs/cli/hooks.md
Normal file
@@ -0,0 +1,313 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw hooks` (agent hooks)"
|
||||
read_when:
|
||||
- You want to manage agent hooks
|
||||
- You want to install or update hooks
|
||||
title: "hooks"
|
||||
---
|
||||
|
||||
# `openclaw hooks`
|
||||
|
||||
Manage agent hooks (event-driven automations for commands like `/new`, `/reset`, and gateway startup).
|
||||
|
||||
Related:
|
||||
|
||||
- Hooks: [Hooks](/automation/hooks)
|
||||
- Plugin hooks: [Plugins](/tools/plugin#plugin-hooks)
|
||||
|
||||
## List All Hooks
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
**Example output:**
|
||||
|
||||
```
|
||||
Hooks (4/4 ready)
|
||||
|
||||
Ready:
|
||||
🚀 boot-md ✓ - Run BOOT.md on gateway startup
|
||||
📎 bootstrap-extra-files ✓ - Inject extra workspace bootstrap files during agent bootstrap
|
||||
📝 command-logger ✓ - Log all command events to a centralized audit file
|
||||
💾 session-memory ✓ - Save session context to memory when /new command is issued
|
||||
```
|
||||
|
||||
**Example (verbose):**
|
||||
|
||||
```bash
|
||||
openclaw hooks list --verbose
|
||||
```
|
||||
|
||||
Shows missing requirements for ineligible hooks.
|
||||
|
||||
**Example (JSON):**
|
||||
|
||||
```bash
|
||||
openclaw hooks list --json
|
||||
```
|
||||
|
||||
Returns structured JSON for programmatic use.
|
||||
|
||||
## Get Hook Information
|
||||
|
||||
```bash
|
||||
openclaw hooks info <name>
|
||||
```
|
||||
|
||||
Show detailed information about a specific hook.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `<name>`: Hook name (e.g., `session-memory`)
|
||||
|
||||
**Options:**
|
||||
|
||||
- `--json`: Output as JSON
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
openclaw hooks info session-memory
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```
|
||||
💾 session-memory ✓ Ready
|
||||
|
||||
Save session context to memory when /new command is issued
|
||||
|
||||
Details:
|
||||
Source: openclaw-bundled
|
||||
Path: /path/to/openclaw/hooks/bundled/session-memory/HOOK.md
|
||||
Handler: /path/to/openclaw/hooks/bundled/session-memory/handler.ts
|
||||
Homepage: https://docs.openclaw.ai/automation/hooks#session-memory
|
||||
Events: command:new
|
||||
|
||||
Requirements:
|
||||
Config: ✓ workspace.dir
|
||||
```
|
||||
|
||||
## Check Hooks Eligibility
|
||||
|
||||
```bash
|
||||
openclaw hooks check
|
||||
```
|
||||
|
||||
Show summary of hook eligibility status (how many are ready vs. not ready).
|
||||
|
||||
**Options:**
|
||||
|
||||
- `--json`: Output as JSON
|
||||
|
||||
**Example output:**
|
||||
|
||||
```
|
||||
Hooks Status
|
||||
|
||||
Total hooks: 4
|
||||
Ready: 4
|
||||
Not ready: 0
|
||||
```
|
||||
|
||||
## Enable a Hook
|
||||
|
||||
```bash
|
||||
openclaw hooks enable <name>
|
||||
```
|
||||
|
||||
Enable a specific hook by adding it to your config (`~/.openclaw/config.json`).
|
||||
|
||||
**Note:** Hooks managed by plugins show `plugin:<id>` in `openclaw hooks list` and
|
||||
can’t be enabled/disabled here. Enable/disable the plugin instead.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `<name>`: Hook name (e.g., `session-memory`)
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
openclaw hooks enable session-memory
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```
|
||||
✓ Enabled hook: 💾 session-memory
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
||||
- Checks if hook exists and is eligible
|
||||
- Updates `hooks.internal.entries.<name>.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
|
||||
|
||||
```bash
|
||||
openclaw hooks disable <name>
|
||||
```
|
||||
|
||||
Disable a specific hook by updating your config.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `<name>`: Hook name (e.g., `command-logger`)
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
openclaw hooks disable command-logger
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```
|
||||
⏸ Disabled hook: 📝 command-logger
|
||||
```
|
||||
|
||||
**After disabling:**
|
||||
|
||||
- Restart the gateway so hooks reload
|
||||
|
||||
## Install Hooks
|
||||
|
||||
```bash
|
||||
openclaw hooks install <path-or-spec>
|
||||
openclaw hooks install <npm-spec> --pin
|
||||
```
|
||||
|
||||
Install a hook pack from a local folder/archive or npm.
|
||||
|
||||
Npm specs are **registry-only** (package name + optional version/tag). Git/URL/file
|
||||
specs are rejected. Dependency installs run with `--ignore-scripts` for safety.
|
||||
|
||||
**What it does:**
|
||||
|
||||
- Copies the hook pack into `~/.openclaw/hooks/<id>`
|
||||
- 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`)
|
||||
- `--pin`: Record npm installs as exact resolved `name@version` in `hooks.internal.installs`
|
||||
|
||||
**Supported archives:** `.zip`, `.tgz`, `.tar.gz`, `.tar`
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Local directory
|
||||
openclaw hooks install ./my-hook-pack
|
||||
|
||||
# Local archive
|
||||
openclaw hooks install ./my-hook-pack.zip
|
||||
|
||||
# NPM package
|
||||
openclaw hooks install @openclaw/my-hook-pack
|
||||
|
||||
# Link a local directory without copying
|
||||
openclaw hooks install -l ./my-hook-pack
|
||||
```
|
||||
|
||||
## Update Hooks
|
||||
|
||||
```bash
|
||||
openclaw hooks update <id>
|
||||
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
|
||||
|
||||
When a stored integrity hash exists and the fetched artifact hash changes,
|
||||
OpenClaw prints a warning and asks for confirmation before proceeding. Use
|
||||
global `--yes` to bypass prompts in CI/non-interactive runs.
|
||||
|
||||
## Bundled Hooks
|
||||
|
||||
### session-memory
|
||||
|
||||
Saves session context to memory when you issue `/new`.
|
||||
|
||||
**Enable:**
|
||||
|
||||
```bash
|
||||
openclaw hooks enable session-memory
|
||||
```
|
||||
|
||||
**Output:** `~/.openclaw/workspace/memory/YYYY-MM-DD-slug.md`
|
||||
|
||||
**See:** [session-memory documentation](/automation/hooks#session-memory)
|
||||
|
||||
### bootstrap-extra-files
|
||||
|
||||
Injects additional bootstrap files (for example monorepo-local `AGENTS.md` / `TOOLS.md`) during `agent:bootstrap`.
|
||||
|
||||
**Enable:**
|
||||
|
||||
```bash
|
||||
openclaw hooks enable bootstrap-extra-files
|
||||
```
|
||||
|
||||
**See:** [bootstrap-extra-files documentation](/automation/hooks#bootstrap-extra-files)
|
||||
|
||||
### command-logger
|
||||
|
||||
Logs all command events to a centralized audit file.
|
||||
|
||||
**Enable:**
|
||||
|
||||
```bash
|
||||
openclaw hooks enable command-logger
|
||||
```
|
||||
|
||||
**Output:** `~/.openclaw/logs/commands.log`
|
||||
|
||||
**View logs:**
|
||||
|
||||
```bash
|
||||
# Recent commands
|
||||
tail -n 20 ~/.openclaw/logs/commands.log
|
||||
|
||||
# Pretty-print
|
||||
cat ~/.openclaw/logs/commands.log | jq .
|
||||
|
||||
# Filter by action
|
||||
grep '"action":"new"' ~/.openclaw/logs/commands.log | jq .
|
||||
```
|
||||
|
||||
**See:** [command-logger documentation](/automation/hooks#command-logger)
|
||||
|
||||
### boot-md
|
||||
|
||||
Runs `BOOT.md` when the gateway starts (after channels start).
|
||||
|
||||
**Events**: `gateway:startup`
|
||||
|
||||
**Enable**:
|
||||
|
||||
```bash
|
||||
openclaw hooks enable boot-md
|
||||
```
|
||||
|
||||
**See:** [boot-md documentation](/automation/hooks#boot-md)
|
||||
1115
openclaw/docs/cli/index.md
Normal file
28
openclaw/docs/cli/logs.md
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw logs` (tail gateway logs via RPC)"
|
||||
read_when:
|
||||
- You need to tail Gateway logs remotely (without SSH)
|
||||
- You want JSON log lines for tooling
|
||||
title: "logs"
|
||||
---
|
||||
|
||||
# `openclaw logs`
|
||||
|
||||
Tail Gateway file logs over RPC (works in remote mode).
|
||||
|
||||
Related:
|
||||
|
||||
- Logging overview: [Logging](/logging)
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
openclaw logs
|
||||
openclaw logs --follow
|
||||
openclaw logs --json
|
||||
openclaw logs --limit 500
|
||||
openclaw logs --local-time
|
||||
openclaw logs --follow --local-time
|
||||
```
|
||||
|
||||
Use `--local-time` to render timestamps in your local timezone.
|
||||
52
openclaw/docs/cli/memory.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw memory` (status/index/search)"
|
||||
read_when:
|
||||
- You want to index or search semantic memory
|
||||
- You’re debugging memory availability or indexing
|
||||
title: "memory"
|
||||
---
|
||||
|
||||
# `openclaw memory`
|
||||
|
||||
Manage semantic memory indexing and search.
|
||||
Provided by the active memory plugin (default: `memory-core`; set `plugins.slots.memory = "none"` to disable).
|
||||
|
||||
Related:
|
||||
|
||||
- Memory concept: [Memory](/concepts/memory)
|
||||
- Plugins: [Plugins](/tools/plugin)
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
openclaw memory status
|
||||
openclaw memory status --deep
|
||||
openclaw memory status --deep --index
|
||||
openclaw memory status --deep --index --verbose
|
||||
openclaw memory index
|
||||
openclaw memory index --verbose
|
||||
openclaw memory search "release checklist"
|
||||
openclaw memory search --query "release checklist"
|
||||
openclaw memory status --agent main
|
||||
openclaw memory index --agent main --verbose
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
Common:
|
||||
|
||||
- `--agent <id>`: scope to a single agent (default: all configured agents).
|
||||
- `--verbose`: emit detailed logs during probes and indexing.
|
||||
|
||||
`memory search`:
|
||||
|
||||
- Query input: pass either positional `[query]` or `--query <text>`.
|
||||
- If both are provided, `--query` wins.
|
||||
- If neither is provided, the command exits with an error.
|
||||
|
||||
Notes:
|
||||
|
||||
- `memory status --deep` probes vector + embedding availability.
|
||||
- `memory status --deep --index` runs a reindex if the store is dirty.
|
||||
- `memory index --verbose` prints per-phase details (provider, model, sources, batch activity).
|
||||
- `memory status` includes any extra paths configured via `memorySearch.extraPaths`.
|
||||
260
openclaw/docs/cli/message.md
Normal file
@@ -0,0 +1,260 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw message` (send + channel actions)"
|
||||
read_when:
|
||||
- Adding or modifying message CLI actions
|
||||
- Changing outbound channel behavior
|
||||
title: "message"
|
||||
---
|
||||
|
||||
# `openclaw message`
|
||||
|
||||
Single outbound command for sending messages and channel actions
|
||||
(Discord/Google Chat/Slack/Mattermost (plugin)/Telegram/WhatsApp/Signal/iMessage/MS Teams).
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
openclaw message <subcommand> [flags]
|
||||
```
|
||||
|
||||
Channel selection:
|
||||
|
||||
- `--channel` required if more than one channel is configured.
|
||||
- If exactly one channel is configured, it becomes the default.
|
||||
- Values: `whatsapp|telegram|discord|googlechat|slack|mattermost|signal|imessage|msteams` (Mattermost requires plugin)
|
||||
|
||||
Target formats (`--target`):
|
||||
|
||||
- WhatsApp: E.164 or group JID
|
||||
- Telegram: chat id or `@username`
|
||||
- Discord: `channel:<id>` or `user:<id>` (or `<@id>` mention; raw numeric ids are treated as channels)
|
||||
- Google Chat: `spaces/<spaceId>` or `users/<userId>`
|
||||
- Slack: `channel:<id>` or `user:<id>` (raw channel id is accepted)
|
||||
- Mattermost (plugin): `channel:<id>`, `user:<id>`, or `@username` (bare ids are treated as channels)
|
||||
- Signal: `+E.164`, `group:<id>`, `signal:+E.164`, `signal:group:<id>`, or `username:<name>`/`u:<name>`
|
||||
- iMessage: handle, `chat_id:<id>`, `chat_guid:<guid>`, or `chat_identifier:<id>`
|
||||
- MS Teams: conversation id (`19:...@thread.tacv2`) or `conversation:<id>` or `user:<aad-object-id>`
|
||||
|
||||
Name lookup:
|
||||
|
||||
- For supported providers (Discord/Slack/etc), channel names like `Help` or `#help` are resolved via the directory cache.
|
||||
- On cache miss, OpenClaw will attempt a live directory lookup when the provider supports it.
|
||||
|
||||
## Common flags
|
||||
|
||||
- `--channel <name>`
|
||||
- `--account <id>`
|
||||
- `--target <dest>` (target channel or user for send/poll/read/etc)
|
||||
- `--targets <name>` (repeat; broadcast only)
|
||||
- `--json`
|
||||
- `--dry-run`
|
||||
- `--verbose`
|
||||
|
||||
## Actions
|
||||
|
||||
### Core
|
||||
|
||||
- `send`
|
||||
- Channels: WhatsApp/Telegram/Discord/Google Chat/Slack/Mattermost (plugin)/Signal/iMessage/MS Teams
|
||||
- Required: `--target`, plus `--message` or `--media`
|
||||
- Optional: `--media`, `--reply-to`, `--thread-id`, `--gif-playback`
|
||||
- Telegram only: `--buttons` (requires `channels.telegram.capabilities.inlineButtons` to allow it)
|
||||
- Telegram only: `--thread-id` (forum topic id)
|
||||
- Slack only: `--thread-id` (thread timestamp; `--reply-to` uses the same field)
|
||||
- WhatsApp only: `--gif-playback`
|
||||
|
||||
- `poll`
|
||||
- Channels: WhatsApp/Telegram/Discord/Matrix/MS Teams
|
||||
- Required: `--target`, `--poll-question`, `--poll-option` (repeat)
|
||||
- Optional: `--poll-multi`
|
||||
- Discord only: `--poll-duration-hours`, `--silent`, `--message`
|
||||
- Telegram only: `--poll-duration-seconds` (5-600), `--silent`, `--poll-anonymous` / `--poll-public`, `--thread-id`
|
||||
|
||||
- `react`
|
||||
- Channels: Discord/Google Chat/Slack/Telegram/WhatsApp/Signal
|
||||
- Required: `--message-id`, `--target`
|
||||
- Optional: `--emoji`, `--remove`, `--participant`, `--from-me`, `--target-author`, `--target-author-uuid`
|
||||
- Note: `--remove` requires `--emoji` (omit `--emoji` to clear own reactions where supported; see /tools/reactions)
|
||||
- WhatsApp only: `--participant`, `--from-me`
|
||||
- Signal group reactions: `--target-author` or `--target-author-uuid` required
|
||||
|
||||
- `reactions`
|
||||
- Channels: Discord/Google Chat/Slack
|
||||
- Required: `--message-id`, `--target`
|
||||
- Optional: `--limit`
|
||||
|
||||
- `read`
|
||||
- Channels: Discord/Slack
|
||||
- Required: `--target`
|
||||
- Optional: `--limit`, `--before`, `--after`
|
||||
- Discord only: `--around`
|
||||
|
||||
- `edit`
|
||||
- Channels: Discord/Slack
|
||||
- Required: `--message-id`, `--message`, `--target`
|
||||
|
||||
- `delete`
|
||||
- Channels: Discord/Slack/Telegram
|
||||
- Required: `--message-id`, `--target`
|
||||
|
||||
- `pin` / `unpin`
|
||||
- Channels: Discord/Slack
|
||||
- Required: `--message-id`, `--target`
|
||||
|
||||
- `pins` (list)
|
||||
- Channels: Discord/Slack
|
||||
- Required: `--target`
|
||||
|
||||
- `permissions`
|
||||
- Channels: Discord
|
||||
- Required: `--target`
|
||||
|
||||
- `search`
|
||||
- Channels: Discord
|
||||
- Required: `--guild-id`, `--query`
|
||||
- Optional: `--channel-id`, `--channel-ids` (repeat), `--author-id`, `--author-ids` (repeat), `--limit`
|
||||
|
||||
### Threads
|
||||
|
||||
- `thread create`
|
||||
- Channels: Discord
|
||||
- Required: `--thread-name`, `--target` (channel id)
|
||||
- Optional: `--message-id`, `--message`, `--auto-archive-min`
|
||||
|
||||
- `thread list`
|
||||
- Channels: Discord
|
||||
- Required: `--guild-id`
|
||||
- Optional: `--channel-id`, `--include-archived`, `--before`, `--limit`
|
||||
|
||||
- `thread reply`
|
||||
- Channels: Discord
|
||||
- Required: `--target` (thread id), `--message`
|
||||
- Optional: `--media`, `--reply-to`
|
||||
|
||||
### Emojis
|
||||
|
||||
- `emoji list`
|
||||
- Discord: `--guild-id`
|
||||
- Slack: no extra flags
|
||||
|
||||
- `emoji upload`
|
||||
- Channels: Discord
|
||||
- Required: `--guild-id`, `--emoji-name`, `--media`
|
||||
- Optional: `--role-ids` (repeat)
|
||||
|
||||
### Stickers
|
||||
|
||||
- `sticker send`
|
||||
- Channels: Discord
|
||||
- Required: `--target`, `--sticker-id` (repeat)
|
||||
- Optional: `--message`
|
||||
|
||||
- `sticker upload`
|
||||
- Channels: Discord
|
||||
- Required: `--guild-id`, `--sticker-name`, `--sticker-desc`, `--sticker-tags`, `--media`
|
||||
|
||||
### Roles / Channels / Members / Voice
|
||||
|
||||
- `role info` (Discord): `--guild-id`
|
||||
- `role add` / `role remove` (Discord): `--guild-id`, `--user-id`, `--role-id`
|
||||
- `channel info` (Discord): `--target`
|
||||
- `channel list` (Discord): `--guild-id`
|
||||
- `member info` (Discord/Slack): `--user-id` (+ `--guild-id` for Discord)
|
||||
- `voice status` (Discord): `--guild-id`, `--user-id`
|
||||
|
||||
### Events
|
||||
|
||||
- `event list` (Discord): `--guild-id`
|
||||
- `event create` (Discord): `--guild-id`, `--event-name`, `--start-time`
|
||||
- Optional: `--end-time`, `--desc`, `--channel-id`, `--location`, `--event-type`
|
||||
|
||||
### Moderation (Discord)
|
||||
|
||||
- `timeout`: `--guild-id`, `--user-id` (optional `--duration-min` or `--until`; omit both to clear timeout)
|
||||
- `kick`: `--guild-id`, `--user-id` (+ `--reason`)
|
||||
- `ban`: `--guild-id`, `--user-id` (+ `--delete-days`, `--reason`)
|
||||
- `timeout` also supports `--reason`
|
||||
|
||||
### Broadcast
|
||||
|
||||
- `broadcast`
|
||||
- Channels: any configured channel; use `--channel all` to target all providers
|
||||
- Required: `--targets` (repeat)
|
||||
- Optional: `--message`, `--media`, `--dry-run`
|
||||
|
||||
## Examples
|
||||
|
||||
Send a Discord reply:
|
||||
|
||||
```
|
||||
openclaw message send --channel discord \
|
||||
--target channel:123 --message "hi" --reply-to 456
|
||||
```
|
||||
|
||||
Send a Discord message with components:
|
||||
|
||||
```
|
||||
openclaw message send --channel discord \
|
||||
--target channel:123 --message "Choose:" \
|
||||
--components '{"text":"Choose a path","blocks":[{"type":"actions","buttons":[{"label":"Approve","style":"success"},{"label":"Decline","style":"danger"}]}]}'
|
||||
```
|
||||
|
||||
See [Discord components](/channels/discord#interactive-components) for the full schema.
|
||||
|
||||
Create a Discord poll:
|
||||
|
||||
```
|
||||
openclaw message poll --channel discord \
|
||||
--target channel:123 \
|
||||
--poll-question "Snack?" \
|
||||
--poll-option Pizza --poll-option Sushi \
|
||||
--poll-multi --poll-duration-hours 48
|
||||
```
|
||||
|
||||
Create a Telegram poll (auto-close in 2 minutes):
|
||||
|
||||
```
|
||||
openclaw message poll --channel telegram \
|
||||
--target @mychat \
|
||||
--poll-question "Lunch?" \
|
||||
--poll-option Pizza --poll-option Sushi \
|
||||
--poll-duration-seconds 120 --silent
|
||||
```
|
||||
|
||||
Send a Teams proactive message:
|
||||
|
||||
```
|
||||
openclaw message send --channel msteams \
|
||||
--target conversation:19:abc@thread.tacv2 --message "hi"
|
||||
```
|
||||
|
||||
Create a Teams poll:
|
||||
|
||||
```
|
||||
openclaw message poll --channel msteams \
|
||||
--target conversation:19:abc@thread.tacv2 \
|
||||
--poll-question "Lunch?" \
|
||||
--poll-option Pizza --poll-option Sushi
|
||||
```
|
||||
|
||||
React in Slack:
|
||||
|
||||
```
|
||||
openclaw message react --channel slack \
|
||||
--target C123 --message-id 456 --emoji "✅"
|
||||
```
|
||||
|
||||
React in a Signal group:
|
||||
|
||||
```
|
||||
openclaw message react --channel signal \
|
||||
--target signal:group:abc123 --message-id 1737630212345 \
|
||||
--emoji "✅" --target-author-uuid 123e4567-e89b-12d3-a456-426614174000
|
||||
```
|
||||
|
||||
Send Telegram inline buttons:
|
||||
|
||||
```
|
||||
openclaw message send --channel telegram --target @mychat --message "Choose:" \
|
||||
--buttons '[ [{"text":"Yes","callback_data":"cmd:yes"}], [{"text":"No","callback_data":"cmd:no"}] ]'
|
||||
```
|
||||