mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 21:27:26 +00:00
chore: Run pnpm format:fix.
This commit is contained in:
@@ -4,6 +4,7 @@ read_when:
|
||||
- Setting up auth expiry monitoring or alerts
|
||||
- Automating Claude Code / Codex OAuth refresh checks
|
||||
---
|
||||
|
||||
# Auth monitoring
|
||||
|
||||
OpenClaw exposes OAuth expiry health via `openclaw models status`. Use that for
|
||||
@@ -16,6 +17,7 @@ openclaw models status --check
|
||||
```
|
||||
|
||||
Exit codes:
|
||||
|
||||
- `0`: OK
|
||||
- `1`: expired or missing credentials
|
||||
- `2`: expiring soon (within 24h)
|
||||
|
||||
@@ -5,6 +5,7 @@ read_when:
|
||||
- Wiring automation that should run with or alongside heartbeats
|
||||
- Deciding between heartbeat and cron for scheduled tasks
|
||||
---
|
||||
|
||||
# Cron jobs (Gateway scheduler)
|
||||
|
||||
> **Cron vs Heartbeat?** See [Cron vs Heartbeat](/automation/cron-vs-heartbeat) for guidance on when to use each.
|
||||
@@ -12,10 +13,11 @@ read_when:
|
||||
Cron is the Gateway’s built-in scheduler. It persists jobs, wakes the agent at
|
||||
the right time, and can optionally deliver output back to a chat.
|
||||
|
||||
If you want *“run this every morning”* or *“poke the agent in 20 minutes”*,
|
||||
If you want _“run this every morning”_ or _“poke the agent in 20 minutes”_,
|
||||
cron is the mechanism.
|
||||
|
||||
## TL;DR
|
||||
|
||||
- Cron runs **inside the Gateway** (not inside the model).
|
||||
- Jobs persist under `~/.openclaw/cron/` so restarts don’t lose schedules.
|
||||
- Two execution styles:
|
||||
@@ -24,18 +26,19 @@ cron is the mechanism.
|
||||
- Wakeups are first-class: a job can request “wake now” vs “next heartbeat”.
|
||||
|
||||
## Beginner-friendly overview
|
||||
|
||||
Think of a cron job as: **when** to run + **what** to do.
|
||||
|
||||
1) **Choose a schedule**
|
||||
1. **Choose a schedule**
|
||||
- One-shot reminder → `schedule.kind = "at"` (CLI: `--at`)
|
||||
- Repeating job → `schedule.kind = "every"` or `schedule.kind = "cron"`
|
||||
- If your ISO timestamp omits a timezone, it is treated as **UTC**.
|
||||
|
||||
2) **Choose where it runs**
|
||||
2. **Choose where it runs**
|
||||
- `sessionTarget: "main"` → run during the next heartbeat with main context.
|
||||
- `sessionTarget: "isolated"` → run a dedicated agent turn in `cron:<jobId>`.
|
||||
|
||||
3) **Choose the payload**
|
||||
3. **Choose the payload**
|
||||
- Main session → `payload.kind = "systemEvent"`
|
||||
- Isolated session → `payload.kind = "agentTurn"`
|
||||
|
||||
@@ -44,7 +47,9 @@ Optional: `deleteAfterRun: true` removes successful one-shot jobs from the store
|
||||
## Concepts
|
||||
|
||||
### Jobs
|
||||
|
||||
A cron job is a stored record with:
|
||||
|
||||
- a **schedule** (when it should run),
|
||||
- a **payload** (what it should do),
|
||||
- optional **delivery** (where output should be sent).
|
||||
@@ -56,7 +61,9 @@ In agent tool calls, `jobId` is canonical; legacy `id` is accepted for compatibi
|
||||
Jobs can optionally auto-delete after a successful one-shot run via `deleteAfterRun: true`.
|
||||
|
||||
### Schedules
|
||||
|
||||
Cron supports three schedule kinds:
|
||||
|
||||
- `at`: one-shot timestamp (ms since epoch). Gateway accepts ISO 8601 and coerces to UTC.
|
||||
- `every`: fixed interval (ms).
|
||||
- `cron`: 5-field cron expression with optional IANA timezone.
|
||||
@@ -67,6 +74,7 @@ local timezone is used.
|
||||
### Main vs isolated execution
|
||||
|
||||
#### Main session jobs (system events)
|
||||
|
||||
Main jobs enqueue a system event and optionally wake the heartbeat runner.
|
||||
They must use `payload.kind = "systemEvent"`.
|
||||
|
||||
@@ -77,9 +85,11 @@ This is the best fit when you want the normal heartbeat prompt + main-session co
|
||||
See [Heartbeat](/gateway/heartbeat).
|
||||
|
||||
#### Isolated jobs (dedicated cron sessions)
|
||||
|
||||
Isolated jobs run a dedicated agent turn in session `cron:<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).
|
||||
- A summary is posted to the main session (prefix `Cron`, configurable).
|
||||
@@ -90,11 +100,14 @@ Use isolated jobs for noisy, frequent, or "background chores" that shouldn't spa
|
||||
your main chat history.
|
||||
|
||||
### Payload shapes (what runs)
|
||||
|
||||
Two payload kinds are supported:
|
||||
|
||||
- `systemEvent`: main-session only, routed through the heartbeat prompt.
|
||||
- `agentTurn`: isolated-session only, runs a dedicated agent turn.
|
||||
|
||||
Common `agentTurn` fields:
|
||||
|
||||
- `message`: required text prompt.
|
||||
- `model` / `thinking`: optional overrides (see below).
|
||||
- `timeoutSeconds`: optional timeout override.
|
||||
@@ -104,12 +117,15 @@ Common `agentTurn` fields:
|
||||
- `bestEffortDeliver`: avoid failing the job if delivery fails.
|
||||
|
||||
Isolation options (only for `session=isolated`):
|
||||
|
||||
- `postToMainPrefix` (CLI: `--post-prefix`): prefix for the system event in main.
|
||||
- `postToMainMode`: `summary` (default) or `full`.
|
||||
- `postToMainMaxChars`: max chars when `postToMainMode=full` (default 8000).
|
||||
|
||||
### Model and thinking overrides
|
||||
|
||||
Isolated jobs (`agentTurn`) can override the model and thinking level:
|
||||
|
||||
- `model`: Provider/model string (e.g., `anthropic/claude-sonnet-4-20250514`) or alias (e.g., `opus`)
|
||||
- `thinking`: Thinking level (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`; GPT-5.2 + Codex models only)
|
||||
|
||||
@@ -118,12 +134,15 @@ session model. We recommend model overrides only for isolated jobs to avoid
|
||||
unexpected context shifts.
|
||||
|
||||
Resolution priority:
|
||||
|
||||
1. Job payload override (highest)
|
||||
2. Hook-specific defaults (e.g., `hooks.gmail.model`)
|
||||
3. Agent config default
|
||||
|
||||
### Delivery (channel + target)
|
||||
|
||||
Isolated jobs can deliver output to a channel. The job payload can specify:
|
||||
|
||||
- `channel`: `whatsapp` / `telegram` / `discord` / `slack` / `mattermost` (plugin) / `signal` / `imessage` / `last`
|
||||
- `to`: channel-specific recipient target
|
||||
|
||||
@@ -131,15 +150,18 @@ If `channel` or `to` is omitted, cron can fall back to the main session’s “l
|
||||
(the last place the agent replied).
|
||||
|
||||
Delivery notes:
|
||||
|
||||
- If `to` is set, cron auto-delivers the agent’s final output even if `deliver` is omitted.
|
||||
- Use `deliver: true` when you want last-route delivery without an explicit `to`.
|
||||
- Use `deliver: false` to keep output internal even if a `to` is present.
|
||||
|
||||
Target format reminders:
|
||||
|
||||
- Slack/Discord/Mattermost (plugin) targets should use explicit prefixes (e.g. `channel:<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:
|
||||
|
||||
@@ -148,9 +170,11 @@ the topic/thread into the `to` field:
|
||||
- `-1001234567890:123` (shorthand: numeric suffix)
|
||||
|
||||
Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted:
|
||||
|
||||
- `telegram:group:-1001234567890:topic:123`
|
||||
|
||||
## Storage & history
|
||||
|
||||
- Job store: `~/.openclaw/cron/jobs.json` (Gateway-managed JSON).
|
||||
- Run history: `~/.openclaw/cron/runs/<jobId>.jsonl` (JSONL, auto-pruned).
|
||||
- Override store path: `cron.store` in config.
|
||||
@@ -162,18 +186,20 @@ Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted:
|
||||
cron: {
|
||||
enabled: true, // default true
|
||||
store: "~/.openclaw/cron/jobs.json",
|
||||
maxConcurrentRuns: 1 // default 1
|
||||
}
|
||||
maxConcurrentRuns: 1, // default 1
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Disable cron entirely:
|
||||
|
||||
- `cron.enabled: false` (config)
|
||||
- `OPENCLAW_SKIP_CRON=1` (env)
|
||||
|
||||
## CLI quickstart
|
||||
|
||||
One-shot reminder (UTC ISO, auto-delete after success):
|
||||
|
||||
```bash
|
||||
openclaw cron add \
|
||||
--name "Send reminder" \
|
||||
@@ -185,6 +211,7 @@ openclaw cron add \
|
||||
```
|
||||
|
||||
One-shot reminder (main session, wake immediately):
|
||||
|
||||
```bash
|
||||
openclaw cron add \
|
||||
--name "Calendar check" \
|
||||
@@ -195,6 +222,7 @@ openclaw cron add \
|
||||
```
|
||||
|
||||
Recurring isolated job (deliver to WhatsApp):
|
||||
|
||||
```bash
|
||||
openclaw cron add \
|
||||
--name "Morning status" \
|
||||
@@ -208,6 +236,7 @@ openclaw cron add \
|
||||
```
|
||||
|
||||
Recurring isolated job (deliver to a Telegram topic):
|
||||
|
||||
```bash
|
||||
openclaw cron add \
|
||||
--name "Nightly summary (topic)" \
|
||||
@@ -221,7 +250,8 @@ openclaw cron add \
|
||||
```
|
||||
|
||||
Isolated job with model and thinking override:
|
||||
```bash
|
||||
|
||||
````bash
|
||||
openclaw cron add \
|
||||
--name "Deep analysis" \
|
||||
--cron "0 6 * * 1" \
|
||||
@@ -242,15 +272,17 @@ openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --mes
|
||||
# Switch or clear the agent on an existing job
|
||||
openclaw cron edit <jobId> --agent ops
|
||||
openclaw cron edit <jobId> --clear-agent
|
||||
```
|
||||
```
|
||||
````
|
||||
|
||||
````
|
||||
|
||||
Manual run (debug):
|
||||
```bash
|
||||
openclaw cron run <jobId> --force
|
||||
```
|
||||
````
|
||||
|
||||
Edit an existing job (patch fields):
|
||||
|
||||
```bash
|
||||
openclaw cron edit <jobId> \
|
||||
--message "Updated prompt" \
|
||||
@@ -259,28 +291,33 @@ openclaw cron edit <jobId> \
|
||||
```
|
||||
|
||||
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).
|
||||
For immediate system events without a job, use [`openclaw system event`](/cli/system).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### “Nothing runs”
|
||||
|
||||
- Check cron is enabled: `cron.enabled` and `OPENCLAW_SKIP_CRON`.
|
||||
- Check the Gateway is running continuously (cron runs inside the Gateway process).
|
||||
- For `cron` schedules: confirm timezone (`--tz`) vs the host timezone.
|
||||
|
||||
### Telegram delivers to the wrong place
|
||||
|
||||
- For forum topics, use `-100…:topic:<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.
|
||||
|
||||
@@ -5,20 +5,21 @@ read_when:
|
||||
- Setting up background monitoring or notifications
|
||||
- Optimizing token usage for periodic checks
|
||||
---
|
||||
|
||||
# Cron vs Heartbeat: When to Use Each
|
||||
|
||||
Both heartbeats and cron jobs let you run tasks on a schedule. This guide helps you choose the right mechanism for your use case.
|
||||
|
||||
## Quick Decision Guide
|
||||
|
||||
| Use Case | Recommended | Why |
|
||||
|----------|-------------|-----|
|
||||
| Check inbox every 30 min | Heartbeat | Batches with other checks, context-aware |
|
||||
| Send daily report at 9am sharp | Cron (isolated) | Exact timing needed |
|
||||
| Monitor calendar for upcoming events | Heartbeat | Natural fit for periodic awareness |
|
||||
| Run weekly deep analysis | Cron (isolated) | Standalone task, can use different model |
|
||||
| Remind me in 20 minutes | Cron (main, `--at`) | One-shot with precise timing |
|
||||
| Background project health check | Heartbeat | Piggybacks on existing cycle |
|
||||
| Use Case | Recommended | Why |
|
||||
| ------------------------------------ | ------------------- | ---------------------------------------- |
|
||||
| Check inbox every 30 min | Heartbeat | Batches with other checks, context-aware |
|
||||
| Send daily report at 9am sharp | Cron (isolated) | Exact timing needed |
|
||||
| Monitor calendar for upcoming events | Heartbeat | Natural fit for periodic awareness |
|
||||
| Run weekly deep analysis | Cron (isolated) | Standalone task, can use different model |
|
||||
| Remind me in 20 minutes | Cron (main, `--at`) | One-shot with precise timing |
|
||||
| Background project health check | Heartbeat | Piggybacks on existing cycle |
|
||||
|
||||
## Heartbeat: Periodic Awareness
|
||||
|
||||
@@ -59,12 +60,12 @@ The agent reads this on each heartbeat and handles all items in one turn.
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: {
|
||||
every: "30m", // interval
|
||||
target: "last", // where to deliver alerts
|
||||
activeHours: { start: "08:00", end: "22:00" } // optional
|
||||
}
|
||||
}
|
||||
}
|
||||
every: "30m", // interval
|
||||
target: "last", // where to deliver alerts
|
||||
activeHours: { start: "08:00", end: "22:00" }, // optional
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -157,8 +158,10 @@ The most efficient setup uses **both**:
|
||||
### Example: Efficient automation setup
|
||||
|
||||
**HEARTBEAT.md** (checked every 30 min):
|
||||
|
||||
```md
|
||||
# Heartbeat checklist
|
||||
|
||||
- Scan inbox for urgent emails
|
||||
- Check calendar for events in next 2h
|
||||
- Review any pending tasks
|
||||
@@ -166,6 +169,7 @@ The most efficient setup uses **both**:
|
||||
```
|
||||
|
||||
**Cron jobs** (precise timing):
|
||||
|
||||
```bash
|
||||
# Daily morning briefing at 7am
|
||||
openclaw cron add --name "Morning brief" --cron "0 7 * * *" --session isolated --message "..." --deliver
|
||||
@@ -177,7 +181,6 @@ openclaw cron add --name "Weekly review" --cron "0 9 * * 1" --session isolated -
|
||||
openclaw cron add --name "Call back" --at "2h" --session main --system-event "Call back the client" --wake now
|
||||
```
|
||||
|
||||
|
||||
## Lobster: Deterministic workflows with approvals
|
||||
|
||||
Lobster is the workflow runtime for **multi-step tool pipelines** that need deterministic execution and explicit approvals.
|
||||
@@ -191,8 +194,8 @@ Use it when the task is more than a single agent turn, and you want a resumable
|
||||
|
||||
### How it pairs with heartbeat and cron
|
||||
|
||||
- **Heartbeat/cron** decide *when* a run happens.
|
||||
- **Lobster** defines *what steps* happen once the run starts.
|
||||
- **Heartbeat/cron** decide _when_ a run happens.
|
||||
- **Lobster** defines _what steps_ happen once the run starts.
|
||||
|
||||
For scheduled workflows, use cron or heartbeat to trigger an agent turn that calls Lobster.
|
||||
For ad-hoc workflows, call Lobster directly.
|
||||
@@ -210,17 +213,18 @@ See [Lobster](/tools/lobster) for full usage and examples.
|
||||
|
||||
Both heartbeat and cron can interact with the main session, but differently:
|
||||
|
||||
| | Heartbeat | Cron (main) | Cron (isolated) |
|
||||
|---|---|---|---|
|
||||
| Session | Main | Main (via system event) | `cron:<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 | Summary posted to main |
|
||||
| | 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 | Summary posted to main |
|
||||
|
||||
### When to use main session cron
|
||||
|
||||
Use `--session main` with `--system-event` when you want:
|
||||
|
||||
- The reminder/event to appear in main session context
|
||||
- The agent to handle it during the next heartbeat with full context
|
||||
- No separate isolated run
|
||||
@@ -237,6 +241,7 @@ openclaw cron add \
|
||||
### When to use isolated cron
|
||||
|
||||
Use `--session isolated` when you want:
|
||||
|
||||
- A clean slate without prior context
|
||||
- Different model or thinking settings
|
||||
- Output delivered directly to a channel (summary still posts to main by default)
|
||||
@@ -255,13 +260,14 @@ openclaw cron add \
|
||||
|
||||
## Cost Considerations
|
||||
|
||||
| Mechanism | Cost Profile |
|
||||
|-----------|--------------|
|
||||
| Heartbeat | One turn every N minutes; scales with HEARTBEAT.md size |
|
||||
| Cron (main) | Adds event to next heartbeat (no isolated turn) |
|
||||
| Cron (isolated) | Full agent turn per job; can use cheaper model |
|
||||
| Mechanism | Cost Profile |
|
||||
| --------------- | ------------------------------------------------------- |
|
||||
| Heartbeat | One turn every N minutes; scales with HEARTBEAT.md size |
|
||||
| Cron (main) | Adds event to next heartbeat (no isolated turn) |
|
||||
| Cron (isolated) | Full agent turn per job; can use cheaper model |
|
||||
|
||||
**Tips**:
|
||||
|
||||
- Keep `HEARTBEAT.md` small to minimize token overhead.
|
||||
- Batch similar checks into heartbeat instead of multiple cron jobs.
|
||||
- Use `target: "none"` on heartbeat if you only want internal processing.
|
||||
|
||||
@@ -26,8 +26,8 @@ Example hook config (enable Gmail preset mapping):
|
||||
enabled: true,
|
||||
token: "OPENCLAW_HOOK_TOKEN",
|
||||
path: "/hooks",
|
||||
presets: ["gmail"]
|
||||
}
|
||||
presets: ["gmail"],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -47,15 +47,14 @@ that sets `deliver` + optional `channel`/`to`:
|
||||
wakeMode: "now",
|
||||
name: "Gmail",
|
||||
sessionKey: "hook:gmail:{{messages[0].id}}",
|
||||
messageTemplate:
|
||||
"New email from {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}\n{{messages[0].body}}",
|
||||
messageTemplate: "New email from {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}\n{{messages[0].body}}",
|
||||
model: "openai/gpt-5.2-mini",
|
||||
deliver: true,
|
||||
channel: "last"
|
||||
channel: "last",
|
||||
// to: "+15551234567"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -73,13 +72,14 @@ To set a default model and thinking level specifically for Gmail hooks, add
|
||||
hooks: {
|
||||
gmail: {
|
||||
model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
|
||||
thinking: "off"
|
||||
}
|
||||
}
|
||||
thinking: "off",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Per-hook `model`/`thinking` in the mapping still overrides these defaults.
|
||||
- Fallback order: `hooks.gmail.model` → `agents.defaults.model.fallbacks` → primary (auth/rate-limit/timeouts).
|
||||
- If `agents.defaults.models` is set, the Gmail model must be in the allowlist.
|
||||
@@ -99,6 +99,7 @@ openclaw webhooks gmail setup \
|
||||
```
|
||||
|
||||
Defaults:
|
||||
|
||||
- Uses Tailscale Funnel for the public push endpoint.
|
||||
- Writes `hooks.gmail` config for `openclaw webhooks gmail run`.
|
||||
- Enables the Gmail hook preset (`hooks.presets: ["gmail"]`).
|
||||
@@ -117,6 +118,7 @@ Platform note: on macOS the wizard installs `gcloud`, `gogcli`, and `tailscale`
|
||||
via Homebrew; on Linux install them manually first.
|
||||
|
||||
Gateway auto-start (recommended):
|
||||
|
||||
- When `hooks.enabled=true` and `hooks.gmail.account` is set, the Gateway starts
|
||||
`gog gmail watch serve` on boot and auto-renews the watch.
|
||||
- Set `OPENCLAW_SKIP_GMAIL_WATCHER=1` to opt out (useful if you run the daemon yourself).
|
||||
@@ -131,7 +133,7 @@ openclaw webhooks gmail run
|
||||
|
||||
## One-time setup
|
||||
|
||||
1) Select the GCP project **that owns the OAuth client** used by `gog`.
|
||||
1. Select the GCP project **that owns the OAuth client** used by `gog`.
|
||||
|
||||
```bash
|
||||
gcloud auth login
|
||||
@@ -140,19 +142,19 @@ gcloud config set project <project-id>
|
||||
|
||||
Note: Gmail watch requires the Pub/Sub topic to live in the same project as the OAuth client.
|
||||
|
||||
2) Enable APIs:
|
||||
2. Enable APIs:
|
||||
|
||||
```bash
|
||||
gcloud services enable gmail.googleapis.com pubsub.googleapis.com
|
||||
```
|
||||
|
||||
3) Create a topic:
|
||||
3. Create a topic:
|
||||
|
||||
```bash
|
||||
gcloud pubsub topics create gog-gmail-watch
|
||||
```
|
||||
|
||||
4) Allow Gmail push to publish:
|
||||
4. Allow Gmail push to publish:
|
||||
|
||||
```bash
|
||||
gcloud pubsub topics add-iam-policy-binding gog-gmail-watch \
|
||||
@@ -189,6 +191,7 @@ gog gmail watch serve \
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `--token` protects the push endpoint (`x-gog-token` or `?token=`).
|
||||
- `--hook-url` points to OpenClaw `/hooks/gmail` (mapped; isolated run + summary to main).
|
||||
- `--include-body` and `--max-bytes` control the body snippet sent to OpenClaw.
|
||||
|
||||
@@ -4,10 +4,11 @@ read_when:
|
||||
- Adding or modifying poll support
|
||||
- Debugging poll sends from the CLI or gateway
|
||||
---
|
||||
|
||||
# Polls
|
||||
|
||||
|
||||
## Supported channels
|
||||
|
||||
- WhatsApp (web channel)
|
||||
- Discord
|
||||
- MS Teams (Adaptive Cards)
|
||||
@@ -33,6 +34,7 @@ openclaw message poll --channel msteams --target conversation:19:abc@thread.tacv
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `--channel`: `whatsapp` (default), `discord`, or `msteams`
|
||||
- `--poll-multi`: allow selecting multiple options
|
||||
- `--poll-duration-hours`: Discord-only (defaults to 24 when omitted)
|
||||
@@ -42,6 +44,7 @@ Options:
|
||||
Method: `poll`
|
||||
|
||||
Params:
|
||||
|
||||
- `to` (string, required)
|
||||
- `question` (string, required)
|
||||
- `options` (string[], required)
|
||||
@@ -51,11 +54,13 @@ Params:
|
||||
- `idempotencyKey` (string, required)
|
||||
|
||||
## Channel differences
|
||||
|
||||
- WhatsApp: 2-12 options, `maxSelections` must be within option count, ignores `durationHours`.
|
||||
- Discord: 2-10 options, `durationHours` clamped to 1-768 hours (default 24). `maxSelections > 1` enables multi-select; Discord does not support a strict selection count.
|
||||
- MS Teams: Adaptive Card polls (OpenClaw-managed). No native poll API; `durationHours` is ignored.
|
||||
|
||||
## Agent tool (Message)
|
||||
|
||||
Use the `message` tool with `poll` action (`to`, `pollQuestion`, `pollOption`, optional `pollMulti`, `pollDurationHours`, `channel`).
|
||||
|
||||
Note: Discord has no “pick exactly N” mode; `pollMulti` maps to multi-select.
|
||||
|
||||
@@ -16,18 +16,20 @@ Gateway can expose a small HTTP webhook endpoint for external triggers.
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "shared-secret",
|
||||
path: "/hooks"
|
||||
}
|
||||
path: "/hooks",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `hooks.token` is required when `hooks.enabled=true`.
|
||||
- `hooks.path` defaults to `/hooks`.
|
||||
|
||||
## Auth
|
||||
|
||||
Every request must include the hook token. Prefer headers:
|
||||
|
||||
- `Authorization: Bearer <token>` (recommended)
|
||||
- `x-openclaw-token: <token>`
|
||||
- `?token=<token>` (deprecated; logs a warning and will be removed in a future major release)
|
||||
@@ -37,6 +39,7 @@ Every request must include the hook token. Prefer headers:
|
||||
### `POST /hooks/wake`
|
||||
|
||||
Payload:
|
||||
|
||||
```json
|
||||
{ "text": "System line", "mode": "now" }
|
||||
```
|
||||
@@ -45,12 +48,14 @@ Payload:
|
||||
- `mode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
|
||||
|
||||
Effect:
|
||||
|
||||
- Enqueues a system event for the **main** session
|
||||
- If `mode=now`, triggers an immediate heartbeat
|
||||
|
||||
### `POST /hooks/agent`
|
||||
|
||||
Payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Run this",
|
||||
@@ -78,6 +83,7 @@ Payload:
|
||||
- `timeoutSeconds` optional (number): Maximum duration for the agent run in seconds.
|
||||
|
||||
Effect:
|
||||
|
||||
- Runs an **isolated** agent turn (own session key)
|
||||
- Always posts a summary into the **main** session
|
||||
- If `wakeMode=now`, triggers an immediate heartbeat
|
||||
@@ -89,6 +95,7 @@ turn arbitrary payloads into `wake` or `agent` actions, with optional templates
|
||||
code transforms.
|
||||
|
||||
Mapping options (summary):
|
||||
|
||||
- `hooks.presets: ["gmail"]` enables the built-in Gmail mapping.
|
||||
- `hooks.mappings` lets you define `match`, `action`, and templates in config.
|
||||
- `hooks.transformsDir` + `transform.module` loads a JS/TS module for custom logic.
|
||||
@@ -99,7 +106,7 @@ Mapping options (summary):
|
||||
- `allowUnsafeExternalContent: true` disables the external content safety wrapper for that hook
|
||||
(dangerous; only for trusted internal sources).
|
||||
- `openclaw webhooks gmail setup` writes `hooks.gmail` config for `openclaw webhooks gmail run`.
|
||||
See [Gmail Pub/Sub](/automation/gmail-pubsub) for the full Gmail watch flow.
|
||||
See [Gmail Pub/Sub](/automation/gmail-pubsub) for the full Gmail watch flow.
|
||||
|
||||
## Responses
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ read_when:
|
||||
- You want to use Amazon Bedrock models with OpenClaw
|
||||
- You need AWS credential/region setup for model calls
|
||||
---
|
||||
|
||||
# Amazon Bedrock
|
||||
|
||||
OpenClaw can use **Amazon Bedrock** models via pi‑ai’s **Bedrock Converse**
|
||||
@@ -34,13 +35,14 @@ Config options live under `models.bedrockDiscovery`:
|
||||
providerFilter: ["anthropic", "amazon"],
|
||||
refreshInterval: 3600,
|
||||
defaultContextWindow: 32000,
|
||||
defaultMaxTokens: 4096
|
||||
}
|
||||
}
|
||||
defaultMaxTokens: 4096,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `enabled` defaults to `true` when AWS credentials are present.
|
||||
- `region` defaults to `AWS_REGION` or `AWS_DEFAULT_REGION`, then `us-east-1`.
|
||||
- `providerFilter` matches Bedrock provider names (for example `anthropic`).
|
||||
@@ -50,7 +52,7 @@ Notes:
|
||||
|
||||
## Setup (manual)
|
||||
|
||||
1) Ensure AWS credentials are available on the **gateway host**:
|
||||
1. Ensure AWS credentials are available on the **gateway host**:
|
||||
|
||||
```bash
|
||||
export AWS_ACCESS_KEY_ID="AKIA..."
|
||||
@@ -63,7 +65,7 @@ export AWS_PROFILE="your-profile"
|
||||
export AWS_BEARER_TOKEN_BEDROCK="..."
|
||||
```
|
||||
|
||||
2) Add a Bedrock provider and model to your config (no `apiKey` required):
|
||||
2. Add a Bedrock provider and model to your config (no `apiKey` required):
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -81,17 +83,17 @@ export AWS_BEARER_TOKEN_BEDROCK="..."
|
||||
input: ["text", "image"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 8192
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
maxTokens: 8192,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "amazon-bedrock/anthropic.claude-opus-4-5-20251101-v1:0" }
|
||||
}
|
||||
}
|
||||
model: { primary: "amazon-bedrock/anthropic.claude-opus-4-5-20251101-v1:0" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -112,6 +114,7 @@ export AWS_REGION=us-east-1
|
||||
```
|
||||
|
||||
**Required IAM permissions** for the EC2 instance role:
|
||||
|
||||
- `bedrock:InvokeModel`
|
||||
- `bedrock:InvokeModelWithResponseStream`
|
||||
- `bedrock:ListFoundationModels` (for automatic discovery)
|
||||
|
||||
@@ -11,9 +11,9 @@ OpenClaw uses Brave Search as the default provider for `web_search`.
|
||||
|
||||
## Get an API key
|
||||
|
||||
1) Create a Brave Search API account at https://brave.com/search/api/
|
||||
2) In the dashboard, choose the **Data for Search** plan and generate an API key.
|
||||
3) Store the key in config (recommended) or set `BRAVE_API_KEY` in the Gateway environment.
|
||||
1. Create a Brave Search API account at https://brave.com/search/api/
|
||||
2. In the dashboard, choose the **Data for Search** plan and generate an API key.
|
||||
3. Store the key in config (recommended) or set `BRAVE_API_KEY` in the Gateway environment.
|
||||
|
||||
## Config example
|
||||
|
||||
@@ -25,10 +25,10 @@ OpenClaw uses Brave Search as the default provider for `web_search`.
|
||||
provider: "brave",
|
||||
apiKey: "BRAVE_API_KEY_HERE",
|
||||
maxResults: 5,
|
||||
timeoutSeconds: 30
|
||||
}
|
||||
}
|
||||
}
|
||||
timeoutSeconds: 30,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -22,7 +22,9 @@ Broadcast groups are evaluated after channel allowlists and group activation rul
|
||||
## Use Cases
|
||||
|
||||
### 1. Specialized Agent Teams
|
||||
|
||||
Deploy multiple agents with atomic, focused responsibilities:
|
||||
|
||||
```
|
||||
Group: "Development Team"
|
||||
Agents:
|
||||
@@ -35,6 +37,7 @@ Agents:
|
||||
Each agent processes the same message and provides its specialized perspective.
|
||||
|
||||
### 2. Multi-Language Support
|
||||
|
||||
```
|
||||
Group: "International Support"
|
||||
Agents:
|
||||
@@ -44,6 +47,7 @@ Agents:
|
||||
```
|
||||
|
||||
### 3. Quality Assurance Workflows
|
||||
|
||||
```
|
||||
Group: "Customer Support"
|
||||
Agents:
|
||||
@@ -52,6 +56,7 @@ Agents:
|
||||
```
|
||||
|
||||
### 4. Task Automation
|
||||
|
||||
```
|
||||
Group: "Project Management"
|
||||
Agents:
|
||||
@@ -65,6 +70,7 @@ Agents:
|
||||
### Basic Setup
|
||||
|
||||
Add a top-level `broadcast` section (next to `bindings`). Keys are WhatsApp peer ids:
|
||||
|
||||
- group chats: group JID (e.g. `120363403215116621@g.us`)
|
||||
- DMs: E.164 phone number (e.g. `+15551234567`)
|
||||
|
||||
@@ -83,7 +89,9 @@ Add a top-level `broadcast` section (next to `bindings`). Keys are WhatsApp peer
|
||||
Control how agents process messages:
|
||||
|
||||
#### Parallel (Default)
|
||||
|
||||
All agents process simultaneously:
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
@@ -94,7 +102,9 @@ All agents process simultaneously:
|
||||
```
|
||||
|
||||
#### Sequential
|
||||
|
||||
Agents process in order (one waits for previous to finish):
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
@@ -152,7 +162,7 @@ Agents process in order (one waits for previous to finish):
|
||||
4. **If not in broadcast list**:
|
||||
- Normal routing applies (first matching binding)
|
||||
|
||||
Note: broadcast groups do not bypass channel allowlists or group activation rules (mentions/commands/etc). They only change *which agents run* when a message is eligible for processing.
|
||||
Note: broadcast groups do not bypass channel allowlists or group activation rules (mentions/commands/etc). They only change _which agents run_ when a message is eligible for processing.
|
||||
|
||||
### Session Isolation
|
||||
|
||||
@@ -166,6 +176,7 @@ Each agent in a broadcast group maintains completely separate:
|
||||
- **Group context buffer** (recent group messages used for context) is shared per peer, so all broadcast agents see the same context when triggered
|
||||
|
||||
This allows each agent to have:
|
||||
|
||||
- Different personalities
|
||||
- Different tool access (e.g., read-only vs. read-write)
|
||||
- Different models (e.g., opus vs. sonnet)
|
||||
@@ -176,6 +187,7 @@ This allows each agent to have:
|
||||
In group `120363403215116621@g.us` with agents `["alfred", "baerbel"]`:
|
||||
|
||||
**Alfred's context:**
|
||||
|
||||
```
|
||||
Session: agent:alfred:whatsapp:group:120363403215116621@g.us
|
||||
History: [user message, alfred's previous responses]
|
||||
@@ -184,8 +196,9 @@ Tools: read, write, exec
|
||||
```
|
||||
|
||||
**Bärbel's context:**
|
||||
|
||||
```
|
||||
Session: agent:baerbel:whatsapp:group:120363403215116621@g.us
|
||||
Session: agent:baerbel:whatsapp:group:120363403215116621@g.us
|
||||
History: [user message, baerbel's previous responses]
|
||||
Workspace: /Users/pascal/openclaw-baerbel/
|
||||
Tools: read only
|
||||
@@ -230,10 +243,10 @@ Give agents only the tools they need:
|
||||
{
|
||||
"agents": {
|
||||
"reviewer": {
|
||||
"tools": { "allow": ["read", "exec"] } // Read-only
|
||||
"tools": { "allow": ["read", "exec"] } // Read-only
|
||||
},
|
||||
"fixer": {
|
||||
"tools": { "allow": ["read", "write", "edit", "exec"] } // Read-write
|
||||
"tools": { "allow": ["read", "write", "edit", "exec"] } // Read-write
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,6 +255,7 @@ Give agents only the tools they need:
|
||||
### 4. Monitor Performance
|
||||
|
||||
With many agents, consider:
|
||||
|
||||
- Using `"strategy": "parallel"` (default) for speed
|
||||
- Limiting broadcast groups to 5-10 agents
|
||||
- Using faster models for simpler agents
|
||||
@@ -260,6 +274,7 @@ Result: Agent A and C respond, Agent B logs error
|
||||
### Providers
|
||||
|
||||
Broadcast groups currently work with:
|
||||
|
||||
- ✅ WhatsApp (implemented)
|
||||
- 🚧 Telegram (planned)
|
||||
- 🚧 Discord (planned)
|
||||
@@ -272,7 +287,10 @@ Broadcast groups work alongside existing routing:
|
||||
```json
|
||||
{
|
||||
"bindings": [
|
||||
{ "match": { "channel": "whatsapp", "peer": { "kind": "group", "id": "GROUP_A" } }, "agentId": "alfred" }
|
||||
{
|
||||
"match": { "channel": "whatsapp", "peer": { "kind": "group", "id": "GROUP_A" } },
|
||||
"agentId": "alfred"
|
||||
}
|
||||
],
|
||||
"broadcast": {
|
||||
"GROUP_B": ["agent1", "agent2"]
|
||||
@@ -290,11 +308,13 @@ Broadcast groups work alongside existing routing:
|
||||
### Agents Not Responding
|
||||
|
||||
**Check:**
|
||||
|
||||
1. Agent IDs exist in `agents.list`
|
||||
2. Peer ID format is correct (e.g., `120363403215116621@g.us`)
|
||||
3. Agents are not in deny lists
|
||||
|
||||
**Debug:**
|
||||
|
||||
```bash
|
||||
tail -f ~/.openclaw/logs/gateway.log | grep broadcast
|
||||
```
|
||||
@@ -308,6 +328,7 @@ tail -f ~/.openclaw/logs/gateway.log | grep broadcast
|
||||
### Performance Issues
|
||||
|
||||
**If slow with many agents:**
|
||||
|
||||
- Reduce number of agents per group
|
||||
- Use lighter models (sonnet instead of opus)
|
||||
- Check sandbox startup time
|
||||
@@ -329,9 +350,21 @@ tail -f ~/.openclaw/logs/gateway.log | grep broadcast
|
||||
},
|
||||
"agents": {
|
||||
"list": [
|
||||
{ "id": "code-formatter", "workspace": "~/agents/formatter", "tools": { "allow": ["read", "write"] } },
|
||||
{ "id": "security-scanner", "workspace": "~/agents/security", "tools": { "allow": ["read", "exec"] } },
|
||||
{ "id": "test-coverage", "workspace": "~/agents/testing", "tools": { "allow": ["read", "exec"] } },
|
||||
{
|
||||
"id": "code-formatter",
|
||||
"workspace": "~/agents/formatter",
|
||||
"tools": { "allow": ["read", "write"] }
|
||||
},
|
||||
{
|
||||
"id": "security-scanner",
|
||||
"workspace": "~/agents/security",
|
||||
"tools": { "allow": ["read", "exec"] }
|
||||
},
|
||||
{
|
||||
"id": "test-coverage",
|
||||
"workspace": "~/agents/testing",
|
||||
"tools": { "allow": ["read", "exec"] }
|
||||
},
|
||||
{ "id": "docs-checker", "workspace": "~/agents/docs", "tools": { "allow": ["read"] } }
|
||||
]
|
||||
}
|
||||
@@ -340,6 +373,7 @@ tail -f ~/.openclaw/logs/gateway.log | grep broadcast
|
||||
|
||||
**User sends:** Code snippet
|
||||
**Responses:**
|
||||
|
||||
- code-formatter: "Fixed indentation and added type hints"
|
||||
- security-scanner: "⚠️ SQL injection vulnerability in line 12"
|
||||
- test-coverage: "Coverage is 45%, missing tests for error cases"
|
||||
@@ -381,7 +415,6 @@ interface OpenClawConfig {
|
||||
- `strategy` (optional): How to process agents
|
||||
- `"parallel"` (default): All agents process simultaneously
|
||||
- `"sequential"`: Agents process in array order
|
||||
|
||||
- `[peerId]`: WhatsApp group JID, E.164 number, or other peer ID
|
||||
- Value: Array of agent IDs that should process messages
|
||||
|
||||
@@ -395,6 +428,7 @@ interface OpenClawConfig {
|
||||
## Future Enhancements
|
||||
|
||||
Planned features:
|
||||
|
||||
- [ ] Shared context mode (agents see each other's responses)
|
||||
- [ ] Agent coordination (agents can signal each other)
|
||||
- [ ] Dynamic agent selection (choose agents based on message content)
|
||||
|
||||
@@ -5,11 +5,13 @@ read_when:
|
||||
- Troubleshooting webhook pairing
|
||||
- Configuring iMessage on macOS
|
||||
---
|
||||
|
||||
# BlueBubbles (macOS REST)
|
||||
|
||||
Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **Recommended for iMessage integration** due to its richer API and easier setup compared to the legacy imsg channel.
|
||||
|
||||
## Overview
|
||||
|
||||
- Runs on macOS via the BlueBubbles helper app ([bluebubbles.app](https://bluebubbles.app)).
|
||||
- Recommended/tested: macOS Sequoia (15). macOS Tahoe (26) works; edit is currently broken on Tahoe, and group icon updates may report success but not sync.
|
||||
- OpenClaw talks to it through its REST API (`GET /api/v1/ping`, `POST /message/text`, `POST /chat/:id/*`).
|
||||
@@ -20,6 +22,7 @@ Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **R
|
||||
- Advanced features: edit, unsend, reply threading, message effects, group management.
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Install the BlueBubbles server on your Mac (follow the instructions at [bluebubbles.app/install](https://bluebubbles.app/install)).
|
||||
2. In the BlueBubbles config, enable the web API and set a password.
|
||||
3. Run `openclaw onboard` and select BlueBubbles, or configure manually:
|
||||
@@ -30,21 +33,24 @@ Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **R
|
||||
enabled: true,
|
||||
serverUrl: "http://192.168.1.100:1234",
|
||||
password: "example-password",
|
||||
webhookPath: "/bluebubbles-webhook"
|
||||
}
|
||||
}
|
||||
webhookPath: "/bluebubbles-webhook",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
4. Point BlueBubbles webhooks to your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=<password>`).
|
||||
5. Start the gateway; it will register the webhook handler and start pairing.
|
||||
|
||||
## Onboarding
|
||||
|
||||
BlueBubbles is available in the interactive setup wizard:
|
||||
|
||||
```
|
||||
openclaw onboard
|
||||
```
|
||||
|
||||
The wizard prompts for:
|
||||
|
||||
- **Server URL** (required): BlueBubbles server address (e.g., `http://192.168.1.100:1234`)
|
||||
- **Password** (required): API password from BlueBubbles Server settings
|
||||
- **Webhook path** (optional): Defaults to `/bluebubbles-webhook`
|
||||
@@ -52,12 +58,15 @@ The wizard prompts for:
|
||||
- **Allow list**: Phone numbers, emails, or chat targets
|
||||
|
||||
You can also add BlueBubbles via CLI:
|
||||
|
||||
```
|
||||
openclaw channels add bluebubbles --http-url http://192.168.1.100:1234 --password <password>
|
||||
```
|
||||
|
||||
## Access control (DMs + groups)
|
||||
|
||||
DMs:
|
||||
|
||||
- Default: `channels.bluebubbles.dmPolicy = "pairing"`.
|
||||
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
@@ -66,16 +75,20 @@ DMs:
|
||||
- Pairing is the default token exchange. Details: [Pairing](/start/pairing)
|
||||
|
||||
Groups:
|
||||
|
||||
- `channels.bluebubbles.groupPolicy = open | allowlist | disabled` (default: `allowlist`).
|
||||
- `channels.bluebubbles.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
||||
|
||||
### Mention gating (groups)
|
||||
|
||||
BlueBubbles supports mention gating for group chats, matching iMessage/WhatsApp behavior:
|
||||
|
||||
- Uses `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) to detect mentions.
|
||||
- When `requireMention` is enabled for a group, the agent only responds when mentioned.
|
||||
- Control commands from authorized senders bypass mention gating.
|
||||
|
||||
Per-group configuration:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
@@ -83,20 +96,22 @@ Per-group configuration:
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15555550123"],
|
||||
groups: {
|
||||
"*": { requireMention: true }, // default for all groups
|
||||
"iMessage;-;chat123": { requireMention: false } // override for specific group
|
||||
}
|
||||
}
|
||||
}
|
||||
"*": { requireMention: true }, // default for all groups
|
||||
"iMessage;-;chat123": { requireMention: false }, // override for specific group
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Command gating
|
||||
|
||||
- Control commands (e.g., `/config`, `/model`) require authorization.
|
||||
- Uses `allowFrom` and `groupAllowFrom` to determine command authorization.
|
||||
- Authorized senders can run control commands even without mentioning in groups.
|
||||
|
||||
## Typing + read receipts
|
||||
|
||||
- **Typing indicators**: Sent automatically before and during response generation.
|
||||
- **Read receipts**: Controlled by `channels.bluebubbles.sendReadReceipts` (default: `true`).
|
||||
- **Typing indicators**: OpenClaw sends typing start events; BlueBubbles clears typing automatically on send or timeout (manual stop via DELETE is unreliable).
|
||||
@@ -105,13 +120,14 @@ Per-group configuration:
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
sendReadReceipts: false // disable read receipts
|
||||
}
|
||||
}
|
||||
sendReadReceipts: false, // disable read receipts
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced actions
|
||||
|
||||
BlueBubbles supports advanced message actions when enabled in config:
|
||||
|
||||
```json5
|
||||
@@ -119,24 +135,25 @@ BlueBubbles supports advanced message actions when enabled in config:
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
actions: {
|
||||
reactions: true, // tapbacks (default: true)
|
||||
edit: true, // edit sent messages (macOS 13+, broken on macOS 26 Tahoe)
|
||||
unsend: true, // unsend messages (macOS 13+)
|
||||
reply: true, // reply threading by message GUID
|
||||
sendWithEffect: true, // message effects (slam, loud, etc.)
|
||||
renameGroup: true, // rename group chats
|
||||
setGroupIcon: true, // set group chat icon/photo (flaky on macOS 26 Tahoe)
|
||||
addParticipant: true, // add participants to groups
|
||||
reactions: true, // tapbacks (default: true)
|
||||
edit: true, // edit sent messages (macOS 13+, broken on macOS 26 Tahoe)
|
||||
unsend: true, // unsend messages (macOS 13+)
|
||||
reply: true, // reply threading by message GUID
|
||||
sendWithEffect: true, // message effects (slam, loud, etc.)
|
||||
renameGroup: true, // rename group chats
|
||||
setGroupIcon: true, // set group chat icon/photo (flaky on macOS 26 Tahoe)
|
||||
addParticipant: true, // add participants to groups
|
||||
removeParticipant: true, // remove participants from groups
|
||||
leaveGroup: true, // leave group chats
|
||||
sendAttachment: true // send attachments/media
|
||||
}
|
||||
}
|
||||
}
|
||||
leaveGroup: true, // leave group chats
|
||||
sendAttachment: true, // send attachments/media
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Available actions:
|
||||
|
||||
- **react**: Add/remove tapback reactions (`messageId`, `emoji`, `remove`)
|
||||
- **edit**: Edit a sent message (`messageId`, `text`)
|
||||
- **unsend**: Unsend a message (`messageId`)
|
||||
@@ -151,39 +168,47 @@ Available actions:
|
||||
- Voice memos: set `asVoice: true` with **MP3** or **CAF** audio to send as an iMessage voice message. BlueBubbles converts MP3 → CAF when sending voice memos.
|
||||
|
||||
### Message IDs (short vs full)
|
||||
OpenClaw may surface *short* message IDs (e.g., `1`, `2`) to save tokens.
|
||||
|
||||
OpenClaw may surface _short_ message IDs (e.g., `1`, `2`) to save tokens.
|
||||
|
||||
- `MessageSid` / `ReplyToId` can be short IDs.
|
||||
- `MessageSidFull` / `ReplyToIdFull` contain the provider full IDs.
|
||||
- Short IDs are in-memory; they can expire on restart or cache eviction.
|
||||
- Actions accept short or full `messageId`, but short IDs will error if no longer available.
|
||||
|
||||
Use full IDs for durable automations and storage:
|
||||
|
||||
- Templates: `{{MessageSidFull}}`, `{{ReplyToIdFull}}`
|
||||
- Context: `MessageSidFull` / `ReplyToIdFull` in inbound payloads
|
||||
|
||||
See [Configuration](/gateway/configuration) for template variables.
|
||||
|
||||
## Block streaming
|
||||
|
||||
Control whether responses are sent as a single message or streamed in blocks:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
blockStreaming: true // enable block streaming (default behavior)
|
||||
}
|
||||
}
|
||||
blockStreaming: true, // enable block streaming (default behavior)
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Media + limits
|
||||
|
||||
- Inbound attachments are downloaded and stored in the media cache.
|
||||
- Media cap via `channels.bluebubbles.mediaMaxMb` (default: 8 MB).
|
||||
- Outbound text is chunked to `channels.bluebubbles.textChunkLimit` (default: 4000 chars).
|
||||
|
||||
## Configuration reference
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
|
||||
- `channels.bluebubbles.enabled`: Enable/disable the channel.
|
||||
- `channels.bluebubbles.serverUrl`: BlueBubbles REST API base URL.
|
||||
- `channels.bluebubbles.password`: API password.
|
||||
@@ -204,11 +229,14 @@ Provider options:
|
||||
- `channels.bluebubbles.accounts`: Multi-account configuration.
|
||||
|
||||
Related global options:
|
||||
|
||||
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`).
|
||||
- `messages.responsePrefix`.
|
||||
|
||||
## Addressing / delivery targets
|
||||
|
||||
Prefer `chat_guid` for stable routing:
|
||||
|
||||
- `chat_guid:iMessage;-;+15555550123` (preferred for groups)
|
||||
- `chat_id:123`
|
||||
- `chat_identifier:...`
|
||||
@@ -216,12 +244,14 @@ Prefer `chat_guid` for stable routing:
|
||||
- If a direct handle does not have an existing DM chat, OpenClaw will create one via `POST /api/v1/chat/new`. This requires the BlueBubbles Private API to be enabled.
|
||||
|
||||
## Security
|
||||
|
||||
- Webhook requests are authenticated by comparing `guid`/`password` query params or headers against `channels.bluebubbles.password`. Requests from `localhost` are also accepted.
|
||||
- Keep the API password and webhook endpoint secret (treat them like credentials).
|
||||
- Localhost trust means a same-host reverse proxy can unintentionally bypass the password. If you proxy the gateway, require auth at the proxy and configure `gateway.trustedProxies`. See [Gateway security](/gateway/security#reverse-proxy-configuration).
|
||||
- Enable HTTPS + firewall rules on the BlueBubbles server if exposing it outside your LAN.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If typing/read events stop working, check the BlueBubbles webhook logs and verify the gateway path matches `channels.bluebubbles.webhookPath`.
|
||||
- Pairing codes expire after one hour; use `openclaw pairing list bluebubbles` and `openclaw pairing approve bluebubbles <code>`.
|
||||
- Reactions require the BlueBubbles private API (`POST /api/v1/message/react`); ensure the server version exposes it.
|
||||
|
||||
@@ -3,41 +3,45 @@ summary: "Discord bot support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Discord channel features
|
||||
---
|
||||
# Discord (Bot API)
|
||||
|
||||
# Discord (Bot API)
|
||||
|
||||
Status: ready for DM and guild text channels via the official Discord bot gateway.
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Create a Discord bot and copy the bot token.
|
||||
2) In the Discord app settings, enable **Message Content Intent** (and **Server Members Intent** if you plan to use allowlists or name lookups).
|
||||
3) Set the token for OpenClaw:
|
||||
|
||||
1. Create a Discord bot and copy the bot token.
|
||||
2. In the Discord app settings, enable **Message Content Intent** (and **Server Members Intent** if you plan to use allowlists or name lookups).
|
||||
3. Set the token for OpenClaw:
|
||||
- Env: `DISCORD_BOT_TOKEN=...`
|
||||
- Or config: `channels.discord.token: "..."`.
|
||||
- If both are set, config takes precedence (env fallback is default-account only).
|
||||
4) Invite the bot to your server with message permissions (create a private server if you just want DMs).
|
||||
5) Start the gateway.
|
||||
6) DM access is pairing by default; approve the pairing code on first contact.
|
||||
4. Invite the bot to your server with message permissions (create a private server if you just want DMs).
|
||||
5. Start the gateway.
|
||||
6. DM access is pairing by default; approve the pairing code on first contact.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
discord: {
|
||||
enabled: true,
|
||||
token: "YOUR_BOT_TOKEN"
|
||||
}
|
||||
}
|
||||
token: "YOUR_BOT_TOKEN",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Goals
|
||||
|
||||
- Talk to OpenClaw via Discord DMs or guild channels.
|
||||
- Direct chats collapse into the agent's main session (default `agent:main:main`); guild channels stay isolated as `agent:<agentId>:discord:channel:<channelId>` (display names use `discord:<guildSlug>#<channelSlug>`).
|
||||
- Group DMs are ignored by default; enable via `channels.discord.dm.groupEnabled` and optionally restrict by `channels.discord.dm.groupChannels`.
|
||||
- Keep routing deterministic: replies always go back to the channel they arrived on.
|
||||
|
||||
## How it works
|
||||
|
||||
1. Create a Discord application → Bot, enable the intents you need (DMs + guild messages + message content), and grab the bot token.
|
||||
2. Invite the bot to your server with the permissions required to read/send messages where you want to use it.
|
||||
3. Configure OpenClaw with `channels.discord.token` (or `DISCORD_BOT_TOKEN` as a fallback).
|
||||
@@ -64,12 +68,14 @@ Note: Slugs are lowercase with spaces replaced by `-`. Channel names are slugged
|
||||
Note: Guild context `[from:]` lines include `author.tag` + `id` to make ping-ready replies easy.
|
||||
|
||||
## Config writes
|
||||
|
||||
By default, Discord is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||
|
||||
Disable with:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: { discord: { configWrites: false } }
|
||||
channels: { discord: { configWrites: false } },
|
||||
}
|
||||
```
|
||||
|
||||
@@ -78,28 +84,34 @@ Disable with:
|
||||
This is the “Discord Developer Portal” setup for running OpenClaw in a server (guild) channel like `#help`.
|
||||
|
||||
### 1) Create the Discord app + bot user
|
||||
|
||||
1. Discord Developer Portal → **Applications** → **New Application**
|
||||
2. In your app:
|
||||
- **Bot** → **Add Bot**
|
||||
- Copy the **Bot Token** (this is what you put in `DISCORD_BOT_TOKEN`)
|
||||
|
||||
### 2) Enable the gateway intents OpenClaw needs
|
||||
|
||||
Discord blocks “privileged intents” unless you explicitly enable them.
|
||||
|
||||
In **Bot** → **Privileged Gateway Intents**, enable:
|
||||
|
||||
- **Message Content Intent** (required to read message text in most guilds; without it you’ll see “Used disallowed intents” or the bot will connect but not react to messages)
|
||||
- **Server Members Intent** (recommended; required for some member/user lookups and allowlist matching in guilds)
|
||||
|
||||
You usually do **not** need **Presence Intent**.
|
||||
|
||||
### 3) Generate an invite URL (OAuth2 URL Generator)
|
||||
|
||||
In your app: **OAuth2** → **URL Generator**
|
||||
|
||||
**Scopes**
|
||||
|
||||
- ✅ `bot`
|
||||
- ✅ `applications.commands` (required for native commands)
|
||||
|
||||
**Bot Permissions** (minimal baseline)
|
||||
|
||||
- ✅ View Channels
|
||||
- ✅ Send Messages
|
||||
- ✅ Read Message History
|
||||
@@ -113,6 +125,7 @@ Avoid **Administrator** unless you’re debugging and fully trust the bot.
|
||||
Copy the generated URL, open it, pick your server, and install the bot.
|
||||
|
||||
### 4) Get the ids (guild/user/channel)
|
||||
|
||||
Discord uses numeric ids everywhere; OpenClaw config prefers ids.
|
||||
|
||||
1. Discord (desktop/web) → **User Settings** → **Advanced** → enable **Developer Mode**
|
||||
@@ -124,7 +137,9 @@ Discord uses numeric ids everywhere; OpenClaw config prefers ids.
|
||||
### 5) Configure OpenClaw
|
||||
|
||||
#### Token
|
||||
|
||||
Set the bot token via env var (recommended on servers):
|
||||
|
||||
- `DISCORD_BOT_TOKEN=...`
|
||||
|
||||
Or via config:
|
||||
@@ -134,15 +149,16 @@ Or via config:
|
||||
channels: {
|
||||
discord: {
|
||||
enabled: true,
|
||||
token: "YOUR_BOT_TOKEN"
|
||||
}
|
||||
}
|
||||
token: "YOUR_BOT_TOKEN",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Multi-account support: use `channels.discord.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
||||
|
||||
#### Allowlist + channel routing
|
||||
|
||||
Example “single server, only allow me, only allow #help”:
|
||||
|
||||
```json5
|
||||
@@ -152,26 +168,27 @@ Example “single server, only allow me, only allow #help”:
|
||||
enabled: true,
|
||||
dm: { enabled: false },
|
||||
guilds: {
|
||||
"YOUR_GUILD_ID": {
|
||||
YOUR_GUILD_ID: {
|
||||
users: ["YOUR_USER_ID"],
|
||||
requireMention: true,
|
||||
channels: {
|
||||
help: { allow: true, requireMention: true }
|
||||
}
|
||||
}
|
||||
help: { allow: true, requireMention: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
retry: {
|
||||
attempts: 3,
|
||||
minDelayMs: 500,
|
||||
maxDelayMs: 30000,
|
||||
jitter: 0.1
|
||||
}
|
||||
}
|
||||
}
|
||||
jitter: 0.1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `requireMention: true` means the bot only replies when mentioned (recommended for shared channels).
|
||||
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions for guild messages.
|
||||
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
|
||||
@@ -182,11 +199,13 @@ Notes:
|
||||
- Warning: If you allow replies to other bots (`channels.discord.allowBots=true`), prevent bot-to-bot reply loops with `requireMention`, `channels.discord.guilds.*.channels.<id>.users` allowlists, and/or clear guardrails in `AGENTS.md` and `SOUL.md`.
|
||||
|
||||
### 6) Verify it works
|
||||
|
||||
1. Start the gateway.
|
||||
2. In your server channel, send: `@Krill hello` (or whatever your bot name is).
|
||||
3. If nothing happens: check **Troubleshooting** below.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
- First: run `openclaw doctor` and `openclaw channels status --probe` (actionable warnings + quick audits).
|
||||
- **“Used disallowed intents”**: enable **Message Content Intent** (and likely **Server Members Intent**) in the Developer Portal, then restart the gateway.
|
||||
- **Bot connects but never replies in a guild channel**:
|
||||
@@ -204,6 +223,7 @@ Notes:
|
||||
- **DMs don’t work**: `channels.discord.dm.enabled=false`, `channels.discord.dm.policy="disabled"`, or you haven’t been approved yet (`channels.discord.dm.policy="pairing"`).
|
||||
|
||||
## Capabilities & limits
|
||||
|
||||
- DMs and guild text channels (threads are treated as separate channels; voice not supported).
|
||||
- Typing indicators sent best-effort; message chunking uses `channels.discord.textChunkLimit` (default 2000) and splits tall replies by line count (`channels.discord.maxLinesPerMessage`, default 17).
|
||||
- Optional newline chunking: set `channels.discord.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
@@ -213,6 +233,7 @@ Notes:
|
||||
- Native reply threading is **off by default**; enable with `channels.discord.replyToMode` and reply tags.
|
||||
|
||||
## Retry policy
|
||||
|
||||
Outbound Discord API calls retry on rate limits (429) using Discord `retry_after` when available, with exponential backoff and jitter. Configure via `channels.discord.retry`. See [Retry policy](/concepts/retry).
|
||||
|
||||
## Config
|
||||
@@ -227,9 +248,9 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after
|
||||
guilds: {
|
||||
"*": {
|
||||
channels: {
|
||||
general: { allow: true }
|
||||
}
|
||||
}
|
||||
general: { allow: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
mediaMaxMb: 8,
|
||||
actions: {
|
||||
@@ -250,7 +271,7 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after
|
||||
channels: true,
|
||||
voiceStatus: true,
|
||||
events: true,
|
||||
moderation: false
|
||||
moderation: false,
|
||||
},
|
||||
replyToMode: "off",
|
||||
dm: {
|
||||
@@ -258,7 +279,7 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after
|
||||
policy: "pairing", // pairing | allowlist | open | disabled
|
||||
allowFrom: ["123456789012345678", "steipete"],
|
||||
groupEnabled: false,
|
||||
groupChannels: ["openclaw-dm"]
|
||||
groupChannels: ["openclaw-dm"],
|
||||
},
|
||||
guilds: {
|
||||
"*": { requireMention: true },
|
||||
@@ -274,13 +295,13 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after
|
||||
requireMention: true,
|
||||
users: ["987654321098765432"],
|
||||
skills: ["search", "docs"],
|
||||
systemPrompt: "Keep answers short."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
systemPrompt: "Keep answers short.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -327,6 +348,7 @@ ack reaction after the bot replies.
|
||||
- `moderation` (timeout/kick/ban, default `false`)
|
||||
|
||||
Reaction notifications use `guilds.<id>.reactionNotifications`:
|
||||
|
||||
- `off`: no reaction events.
|
||||
- `own`: reactions on the bot's own messages (default).
|
||||
- `all`: all reactions on all messages.
|
||||
@@ -334,40 +356,45 @@ Reaction notifications use `guilds.<id>.reactionNotifications`:
|
||||
|
||||
### Tool action defaults
|
||||
|
||||
| Action group | Default | Notes |
|
||||
| --- | --- | --- |
|
||||
| reactions | enabled | React + list reactions + emojiList |
|
||||
| stickers | enabled | Send stickers |
|
||||
| emojiUploads | enabled | Upload emojis |
|
||||
| stickerUploads | enabled | Upload stickers |
|
||||
| polls | enabled | Create polls |
|
||||
| permissions | enabled | Channel permission snapshot |
|
||||
| messages | enabled | Read/send/edit/delete |
|
||||
| threads | enabled | Create/list/reply |
|
||||
| pins | enabled | Pin/unpin/list |
|
||||
| search | enabled | Message search (preview feature) |
|
||||
| memberInfo | enabled | Member info |
|
||||
| roleInfo | enabled | Role list |
|
||||
| channelInfo | enabled | Channel info + list |
|
||||
| channels | enabled | Channel/category management |
|
||||
| voiceStatus | enabled | Voice state lookup |
|
||||
| events | enabled | List/create scheduled events |
|
||||
| roles | disabled | Role add/remove |
|
||||
| moderation | disabled | Timeout/kick/ban |
|
||||
| Action group | Default | Notes |
|
||||
| -------------- | -------- | ---------------------------------- |
|
||||
| reactions | enabled | React + list reactions + emojiList |
|
||||
| stickers | enabled | Send stickers |
|
||||
| emojiUploads | enabled | Upload emojis |
|
||||
| stickerUploads | enabled | Upload stickers |
|
||||
| polls | enabled | Create polls |
|
||||
| permissions | enabled | Channel permission snapshot |
|
||||
| messages | enabled | Read/send/edit/delete |
|
||||
| threads | enabled | Create/list/reply |
|
||||
| pins | enabled | Pin/unpin/list |
|
||||
| search | enabled | Message search (preview feature) |
|
||||
| memberInfo | enabled | Member info |
|
||||
| roleInfo | enabled | Role list |
|
||||
| channelInfo | enabled | Channel info + list |
|
||||
| channels | enabled | Channel/category management |
|
||||
| voiceStatus | enabled | Voice state lookup |
|
||||
| events | enabled | List/create scheduled events |
|
||||
| roles | disabled | Role add/remove |
|
||||
| moderation | disabled | Timeout/kick/ban |
|
||||
|
||||
- `replyToMode`: `off` (default), `first`, or `all`. Applies only when the model includes a reply tag.
|
||||
|
||||
## Reply tags
|
||||
|
||||
To request a threaded reply, the model can include one tag in its output:
|
||||
|
||||
- `[[reply_to_current]]` — reply to the triggering Discord message.
|
||||
- `[[reply_to:<id>]]` — reply to a specific message id from context/history.
|
||||
Current message ids are appended to prompts as `[message_id: …]`; history entries already include ids.
|
||||
Current message ids are appended to prompts as `[message_id: …]`; history entries already include ids.
|
||||
|
||||
Behavior is controlled by `channels.discord.replyToMode`:
|
||||
|
||||
- `off`: ignore tags.
|
||||
- `first`: only the first outbound chunk/attachment is a reply.
|
||||
- `all`: every outbound chunk/attachment is a reply.
|
||||
|
||||
Allowlist matching notes:
|
||||
|
||||
- `allowFrom`/`users`/`groupChannels` accept ids, names, tags, or mentions like `<@id>`.
|
||||
- Prefixes like `discord:`/`user:` (users) and `channel:` (group DMs) are supported.
|
||||
- Use `*` to allow any sender/channel.
|
||||
@@ -379,12 +406,15 @@ Allowlist matching notes:
|
||||
and logs the mapping; unresolved entries are kept as typed.
|
||||
|
||||
Native command notes:
|
||||
|
||||
- The registered commands mirror OpenClaw’s chat commands.
|
||||
- Native commands honor the same allowlists as DMs/guild messages (`channels.discord.dm.allowFrom`, `channels.discord.guilds`, per-channel rules).
|
||||
- Slash commands may still be visible in Discord UI to users who aren’t allowlisted; OpenClaw enforces allowlists on execution and replies “not authorized”.
|
||||
|
||||
## Tool actions
|
||||
|
||||
The agent can call `discord` with actions like:
|
||||
|
||||
- `react` / `reactions` (add or list reactions)
|
||||
- `sticker`, `poll`, `permissions`
|
||||
- `readMessages`, `sendMessage`, `editMessage`, `deleteMessage`
|
||||
@@ -399,6 +429,7 @@ Discord message ids are surfaced in the injected context (`[discord message id:
|
||||
Emoji can be unicode (e.g., `✅`) or custom emoji syntax like `<:party_blob:1234567890>`.
|
||||
|
||||
## Safety & ops
|
||||
|
||||
- Treat the bot token like a password; prefer the `DISCORD_BOT_TOKEN` env var on supervised hosts or lock down the config file permissions.
|
||||
- Only grant the bot permissions it needs (typically Read/Send Messages).
|
||||
- If the bot is stuck or rate limited, restart the gateway (`openclaw gateway --force`) after confirming no other processes own the Discord session.
|
||||
|
||||
@@ -3,26 +3,28 @@ summary: "Google Chat app support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Google Chat channel features
|
||||
---
|
||||
|
||||
# Google Chat (Chat API)
|
||||
|
||||
Status: ready for DMs + spaces via Google Chat API webhooks (HTTP only).
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Create a Google Cloud project and enable the **Google Chat API**.
|
||||
|
||||
1. Create a Google Cloud project and enable the **Google Chat API**.
|
||||
- Go to: [Google Chat API Credentials](https://console.cloud.google.com/apis/api/chat.googleapis.com/credentials)
|
||||
- Enable the API if it is not already enabled.
|
||||
2) Create a **Service Account**:
|
||||
2. Create a **Service Account**:
|
||||
- Press **Create Credentials** > **Service Account**.
|
||||
- Name it whatever you want (e.g., `openclaw-chat`).
|
||||
- Leave permissions blank (press **Continue**).
|
||||
- Leave principals with access blank (press **Done**).
|
||||
3) Create and download the **JSON Key**:
|
||||
3. Create and download the **JSON Key**:
|
||||
- In the list of service accounts, click on the one you just created.
|
||||
- Go to the **Keys** tab.
|
||||
- Click **Add Key** > **Create new key**.
|
||||
- Select **JSON** and press **Create**.
|
||||
4) Store the downloaded JSON file on your gateway host (e.g., `~/.openclaw/googlechat-service-account.json`).
|
||||
5) Create a Google Chat app in the [Google Cloud Console Chat Configuration](https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat):
|
||||
4. Store the downloaded JSON file on your gateway host (e.g., `~/.openclaw/googlechat-service-account.json`).
|
||||
5. Create a Google Chat app in the [Google Cloud Console Chat Configuration](https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat):
|
||||
- Fill in the **Application info**:
|
||||
- **App name**: (e.g. `OpenClaw`)
|
||||
- **Avatar URL**: (e.g. `https://openclaw.ai/logo.png`)
|
||||
@@ -31,44 +33,51 @@ Status: ready for DMs + spaces via Google Chat API webhooks (HTTP only).
|
||||
- Under **Functionality**, check **Join spaces and group conversations**.
|
||||
- Under **Connection settings**, select **HTTP endpoint URL**.
|
||||
- Under **Triggers**, select **Use a common HTTP endpoint URL for all triggers** and set it to your gateway's public URL followed by `/googlechat`.
|
||||
- *Tip: Run `openclaw status` to find your gateway's public URL.*
|
||||
- _Tip: Run `openclaw status` to find your gateway's public URL._
|
||||
- Under **Visibility**, check **Make this Chat app available to specific people and groups in <Your Domain>**.
|
||||
- Enter your email address (e.g. `user@example.com`) in the text box.
|
||||
- Click **Save** at the bottom.
|
||||
6) **Enable the app status**:
|
||||
6. **Enable the app status**:
|
||||
- After saving, **refresh the page**.
|
||||
- Look for the **App status** section (usually near the top or bottom after saving).
|
||||
- Change the status to **Live - available to users**.
|
||||
- Click **Save** again.
|
||||
7) Configure OpenClaw with the service account path + webhook audience:
|
||||
7. Configure OpenClaw with the service account path + webhook audience:
|
||||
- Env: `GOOGLE_CHAT_SERVICE_ACCOUNT_FILE=/path/to/service-account.json`
|
||||
- Or config: `channels.googlechat.serviceAccountFile: "/path/to/service-account.json"`.
|
||||
8) Set the webhook audience type + value (matches your Chat app config).
|
||||
9) Start the gateway. Google Chat will POST to your webhook path.
|
||||
8. Set the webhook audience type + value (matches your Chat app config).
|
||||
9. Start the gateway. Google Chat will POST to your webhook path.
|
||||
|
||||
## Add to Google Chat
|
||||
|
||||
Once the gateway is running and your email is added to the visibility list:
|
||||
1) Go to [Google Chat](https://chat.google.com/).
|
||||
2) Click the **+** (plus) icon next to **Direct Messages**.
|
||||
3) In the search bar (where you usually add people), type the **App name** you configured in the Google Cloud Console.
|
||||
- **Note**: The bot will *not* appear in the "Marketplace" browse list because it is a private app. You must search for it by name.
|
||||
4) Select your bot from the results.
|
||||
5) Click **Add** or **Chat** to start a 1:1 conversation.
|
||||
6) Send "Hello" to trigger the assistant!
|
||||
|
||||
1. Go to [Google Chat](https://chat.google.com/).
|
||||
2. Click the **+** (plus) icon next to **Direct Messages**.
|
||||
3. In the search bar (where you usually add people), type the **App name** you configured in the Google Cloud Console.
|
||||
- **Note**: The bot will _not_ appear in the "Marketplace" browse list because it is a private app. You must search for it by name.
|
||||
4. Select your bot from the results.
|
||||
5. Click **Add** or **Chat** to start a 1:1 conversation.
|
||||
6. Send "Hello" to trigger the assistant!
|
||||
|
||||
## Public URL (Webhook-only)
|
||||
|
||||
Google Chat webhooks require a public HTTPS endpoint. For security, **only expose the `/googlechat` path** to the internet. Keep the OpenClaw dashboard and other sensitive endpoints on your private network.
|
||||
|
||||
### Option A: Tailscale Funnel (Recommended)
|
||||
|
||||
Use Tailscale Serve for the private dashboard and Funnel for the public webhook path. This keeps `/` private while exposing only `/googlechat`.
|
||||
|
||||
1. **Check what address your gateway is bound to:**
|
||||
|
||||
```bash
|
||||
ss -tlnp | grep 18789
|
||||
```
|
||||
|
||||
Note the IP address (e.g., `127.0.0.1`, `0.0.0.0`, or your Tailscale IP like `100.x.x.x`).
|
||||
|
||||
2. **Expose the dashboard to the tailnet only (port 8443):**
|
||||
|
||||
```bash
|
||||
# If bound to localhost (127.0.0.1 or 0.0.0.0):
|
||||
tailscale serve --bg --https 8443 http://127.0.0.1:18789
|
||||
@@ -78,6 +87,7 @@ Use Tailscale Serve for the private dashboard and Funnel for the public webhook
|
||||
```
|
||||
|
||||
3. **Expose only the webhook path publicly:**
|
||||
|
||||
```bash
|
||||
# If bound to localhost (127.0.0.1 or 0.0.0.0):
|
||||
tailscale funnel --bg --set-path /googlechat http://127.0.0.1:18789/googlechat
|
||||
@@ -106,16 +116,21 @@ Use the public URL (without `:8443`) in the Google Chat app config.
|
||||
> Note: This configuration persists across reboots. To remove it later, run `tailscale funnel reset` and `tailscale serve reset`.
|
||||
|
||||
### Option B: Reverse Proxy (Caddy)
|
||||
|
||||
If you use a reverse proxy like Caddy, only proxy the specific path:
|
||||
|
||||
```caddy
|
||||
your-domain.com {
|
||||
reverse_proxy /googlechat* localhost:18789
|
||||
}
|
||||
```
|
||||
|
||||
With this config, any request to `your-domain.com/` will be ignored or returned as 404, while `your-domain.com/googlechat` is safely routed to OpenClaw.
|
||||
|
||||
### Option C: Cloudflare Tunnel
|
||||
|
||||
Configure your tunnel's ingress rules to only route the webhook path:
|
||||
|
||||
- **Path**: `/googlechat` -> `http://localhost:18789/googlechat`
|
||||
- **Default Rule**: HTTP 404 (Not Found)
|
||||
|
||||
@@ -133,15 +148,18 @@ Configure your tunnel's ingress rules to only route the webhook path:
|
||||
5. Group spaces require @-mention by default. Use `botUser` if mention detection needs the app’s user name.
|
||||
|
||||
## Targets
|
||||
|
||||
Use these identifiers for delivery and allowlists:
|
||||
|
||||
- Direct messages: `users/<userId>` or `users/<email>` (email addresses are accepted).
|
||||
- Spaces: `spaces/<spaceId>`.
|
||||
|
||||
## Config highlights
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
"googlechat": {
|
||||
googlechat: {
|
||||
enabled: true,
|
||||
serviceAccountFile: "/path/to/service-account.json",
|
||||
audienceType: "app-url",
|
||||
@@ -150,7 +168,7 @@ Use these identifiers for delivery and allowlists:
|
||||
botUser: "users/1234567890", // optional; helps mention detection
|
||||
dm: {
|
||||
policy: "pairing",
|
||||
allowFrom: ["users/1234567890", "name@example.com"]
|
||||
allowFrom: ["users/1234567890", "name@example.com"],
|
||||
},
|
||||
groupPolicy: "allowlist",
|
||||
groups: {
|
||||
@@ -158,18 +176,19 @@ Use these identifiers for delivery and allowlists:
|
||||
allow: true,
|
||||
requireMention: true,
|
||||
users: ["users/1234567890"],
|
||||
systemPrompt: "Short answers only."
|
||||
}
|
||||
systemPrompt: "Short answers only.",
|
||||
},
|
||||
},
|
||||
actions: { reactions: true },
|
||||
typingIndicator: "message",
|
||||
mediaMaxMb: 20
|
||||
}
|
||||
}
|
||||
mediaMaxMb: 20,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Service account credentials can also be passed inline with `serviceAccount` (JSON string).
|
||||
- Default webhook path is `/googlechat` if `webhookPath` isn’t set.
|
||||
- Reactions are available via the `reactions` tool and `channels action` when `actions.reactions` is enabled.
|
||||
@@ -179,22 +198,29 @@ Notes:
|
||||
## Troubleshooting
|
||||
|
||||
### 405 Method Not Allowed
|
||||
|
||||
If Google Cloud Logs Explorer shows errors like:
|
||||
|
||||
```
|
||||
status code: 405, reason phrase: HTTP error response: HTTP/1.1 405 Method Not Allowed
|
||||
```
|
||||
|
||||
This means the webhook handler isn't registered. Common causes:
|
||||
|
||||
1. **Channel not configured**: The `channels.googlechat` section is missing from your config. Verify with:
|
||||
|
||||
```bash
|
||||
openclaw config get channels.googlechat
|
||||
```
|
||||
|
||||
If it returns "Config path not found", add the configuration (see [Config highlights](#config-highlights)).
|
||||
|
||||
2. **Plugin not enabled**: Check plugin status:
|
||||
|
||||
```bash
|
||||
openclaw plugins list | grep googlechat
|
||||
```
|
||||
|
||||
If it shows "disabled", add `plugins.entries.googlechat.enabled: true` to your config.
|
||||
|
||||
3. **Gateway not restarted**: After adding config, restart the gateway:
|
||||
@@ -203,18 +229,21 @@ This means the webhook handler isn't registered. Common causes:
|
||||
```
|
||||
|
||||
Verify the channel is running:
|
||||
|
||||
```bash
|
||||
openclaw channels status
|
||||
# Should show: Google Chat default: enabled, configured, ...
|
||||
```
|
||||
|
||||
### Other issues
|
||||
|
||||
- Check `openclaw channels status --probe` for auth errors or missing audience config.
|
||||
- If no messages arrive, confirm the Chat app's webhook URL + event subscriptions.
|
||||
- If mention gating blocks replies, set `botUser` to the app's user resource name and verify `requireMention`.
|
||||
- Use `openclaw logs --follow` while sending a test message to see if requests reach the gateway.
|
||||
|
||||
Related docs:
|
||||
|
||||
- [Gateway configuration](/gateway/configuration)
|
||||
- [Security](/gateway/security)
|
||||
- [Reactions](/tools/reactions)
|
||||
|
||||
@@ -3,15 +3,17 @@ summary: "Telegram Bot API integration via grammY with setup notes"
|
||||
read_when:
|
||||
- Working on Telegram or grammY pathways
|
||||
---
|
||||
|
||||
# grammY Integration (Telegram Bot API)
|
||||
|
||||
|
||||
# Why grammY
|
||||
|
||||
- TS-first Bot API client with built-in long-poll + webhook helpers, middleware, error handling, rate limiter.
|
||||
- Cleaner media helpers than hand-rolling fetch + FormData; supports all Bot API methods.
|
||||
- Extensible: proxy support via custom fetch, session middleware (optional), type-safe context.
|
||||
|
||||
# What we shipped
|
||||
|
||||
- **Single client path:** fetch-based implementation removed; grammY is now the sole Telegram client (send + gateway) with the grammY throttler enabled by default.
|
||||
- **Gateway:** `monitorTelegramProvider` builds a grammY `Bot`, wires mention/allowlist gating, media download via `getFile`/`download`, and delivers replies with `sendMessage/sendPhoto/sendVideo/sendAudio/sendDocument`. Supports long-poll or webhook via `webhookCallback`.
|
||||
- **Proxy:** optional `channels.telegram.proxy` uses `undici.ProxyAgent` through grammY’s `client.baseFetch`.
|
||||
@@ -22,6 +24,7 @@ read_when:
|
||||
- **Tests:** grammy mocks cover DM + group mention gating and outbound send; more media/webhook fixtures still welcome.
|
||||
|
||||
Open questions
|
||||
|
||||
- Optional grammY plugins (throttler) if we hit Bot API 429s.
|
||||
- Add more structured media tests (stickers, voice notes).
|
||||
- Make webhook listen port configurable (currently fixed to 8787 unless wired through the gateway).
|
||||
|
||||
@@ -4,73 +4,82 @@ read_when:
|
||||
- Setting up iMessage support
|
||||
- Debugging iMessage send/receive
|
||||
---
|
||||
# iMessage (imsg)
|
||||
|
||||
# iMessage (imsg)
|
||||
|
||||
Status: external CLI integration. Gateway spawns `imsg rpc` (JSON-RPC over stdio).
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Ensure Messages is signed in on this Mac.
|
||||
2) Install `imsg`:
|
||||
|
||||
1. Ensure Messages is signed in on this Mac.
|
||||
2. Install `imsg`:
|
||||
- `brew install steipete/tap/imsg`
|
||||
3) Configure OpenClaw with `channels.imessage.cliPath` and `channels.imessage.dbPath`.
|
||||
4) Start the gateway and approve any macOS prompts (Automation + Full Disk Access).
|
||||
3. Configure OpenClaw with `channels.imessage.cliPath` and `channels.imessage.dbPath`.
|
||||
4. Start the gateway and approve any macOS prompts (Automation + Full Disk Access).
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
imessage: {
|
||||
enabled: true,
|
||||
cliPath: "/usr/local/bin/imsg",
|
||||
dbPath: "/Users/<you>/Library/Messages/chat.db"
|
||||
}
|
||||
}
|
||||
dbPath: "/Users/<you>/Library/Messages/chat.db",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## What it is
|
||||
|
||||
- iMessage channel backed by `imsg` on macOS.
|
||||
- Deterministic routing: replies always go back to iMessage.
|
||||
- DMs share the agent's main session; groups are isolated (`agent:<agentId>:imessage:group:<chat_id>`).
|
||||
- If a multi-participant thread arrives with `is_group=false`, you can still isolate it by `chat_id` using `channels.imessage.groups` (see “Group-ish threads” below).
|
||||
|
||||
## Config writes
|
||||
|
||||
By default, iMessage is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||
|
||||
Disable with:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: { imessage: { configWrites: false } }
|
||||
channels: { imessage: { configWrites: false } },
|
||||
}
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- macOS with Messages signed in.
|
||||
- Full Disk Access for OpenClaw + `imsg` (Messages DB access).
|
||||
- Automation permission when sending.
|
||||
- `channels.imessage.cliPath` can point to any command that proxies stdin/stdout (for example, a wrapper script that SSHes to another Mac and runs `imsg rpc`).
|
||||
|
||||
## Setup (fast path)
|
||||
1) Ensure Messages is signed in on this Mac.
|
||||
2) Configure iMessage and start the gateway.
|
||||
|
||||
1. Ensure Messages is signed in on this Mac.
|
||||
2. Configure iMessage and start the gateway.
|
||||
|
||||
### Dedicated bot macOS user (for isolated identity)
|
||||
|
||||
If you want the bot to send from a **separate iMessage identity** (and keep your personal Messages clean), use a dedicated Apple ID + a dedicated macOS user.
|
||||
|
||||
1) Create a dedicated Apple ID (example: `my-cool-bot@icloud.com`).
|
||||
1. Create a dedicated Apple ID (example: `my-cool-bot@icloud.com`).
|
||||
- Apple may require a phone number for verification / 2FA.
|
||||
2) Create a macOS user (example: `openclawhome`) and sign into it.
|
||||
3) Open Messages in that macOS user and sign into iMessage using the bot Apple ID.
|
||||
4) Enable Remote Login (System Settings → General → Sharing → Remote Login).
|
||||
5) Install `imsg`:
|
||||
2. Create a macOS user (example: `openclawhome`) and sign into it.
|
||||
3. Open Messages in that macOS user and sign into iMessage using the bot Apple ID.
|
||||
4. Enable Remote Login (System Settings → General → Sharing → Remote Login).
|
||||
5. Install `imsg`:
|
||||
- `brew install steipete/tap/imsg`
|
||||
6) Set up SSH so `ssh <bot-macos-user>@localhost true` works without a password.
|
||||
7) Point `channels.imessage.accounts.bot.cliPath` at an SSH wrapper that runs `imsg` as the bot user.
|
||||
6. Set up SSH so `ssh <bot-macos-user>@localhost true` works without a password.
|
||||
7. Point `channels.imessage.accounts.bot.cliPath` at an SSH wrapper that runs `imsg` as the bot user.
|
||||
|
||||
First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the *bot macOS user*. If `imsg rpc` looks stuck or exits, log into that user (Screen Sharing helps), run a one-time `imsg chats --limit 1` / `imsg send ...`, approve prompts, then retry.
|
||||
First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the _bot macOS user_. If `imsg rpc` looks stuck or exits, log into that user (Screen Sharing helps), run a one-time `imsg chats --limit 1` / `imsg send ...`, approve prompts, then retry.
|
||||
|
||||
Example wrapper (`chmod +x`). Replace `<bot-macos-user>` with your actual macOS username:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
@@ -82,6 +91,7 @@ exec /usr/bin/ssh -o BatchMode=yes -o ConnectTimeout=5 -T <bot-macos-user>@local
|
||||
```
|
||||
|
||||
Example config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
@@ -92,20 +102,22 @@ Example config:
|
||||
name: "Bot",
|
||||
enabled: true,
|
||||
cliPath: "/path/to/imsg-bot",
|
||||
dbPath: "/Users/<bot-macos-user>/Library/Messages/chat.db"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dbPath: "/Users/<bot-macos-user>/Library/Messages/chat.db",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
For single-account setups, use flat options (`channels.imessage.cliPath`, `channels.imessage.dbPath`) instead of the `accounts` map.
|
||||
|
||||
### Remote/SSH variant (optional)
|
||||
|
||||
If you want iMessage on another Mac, set `channels.imessage.cliPath` to a wrapper that runs `imsg` on the remote macOS host over SSH. OpenClaw only needs stdio.
|
||||
|
||||
Example wrapper:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
exec ssh -T gateway-host imsg "$@"
|
||||
@@ -117,20 +129,22 @@ exec ssh -T gateway-host imsg "$@"
|
||||
{
|
||||
channels: {
|
||||
imessage: {
|
||||
cliPath: "~/imsg-ssh", // SSH wrapper to remote Mac
|
||||
remoteHost: "user@gateway-host", // for SCP file transfer
|
||||
includeAttachments: true
|
||||
}
|
||||
}
|
||||
cliPath: "~/imsg-ssh", // SSH wrapper to remote Mac
|
||||
remoteHost: "user@gateway-host", // for SCP file transfer
|
||||
includeAttachments: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
If `remoteHost` is not set, OpenClaw attempts to auto-detect it by parsing the SSH command in your wrapper script. Explicit configuration is recommended for reliability.
|
||||
|
||||
#### Remote Mac via Tailscale (example)
|
||||
|
||||
If the Gateway runs on a Linux host/VM but iMessage must run on a Mac, Tailscale is the simplest bridge: the Gateway talks to the Mac over the tailnet, runs `imsg` via SSH, and SCPs attachments back.
|
||||
|
||||
Architecture:
|
||||
|
||||
```
|
||||
┌──────────────────────────────┐ SSH (imsg rpc) ┌──────────────────────────┐
|
||||
│ Gateway host (Linux/VM) │──────────────────────────────────▶│ Mac with Messages + imsg │
|
||||
@@ -144,6 +158,7 @@ Architecture:
|
||||
```
|
||||
|
||||
Concrete config example (Tailscale hostname):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
@@ -152,19 +167,21 @@ Concrete config example (Tailscale hostname):
|
||||
cliPath: "~/.openclaw/scripts/imsg-ssh",
|
||||
remoteHost: "bot@mac-mini.tailnet-1234.ts.net",
|
||||
includeAttachments: true,
|
||||
dbPath: "/Users/bot/Library/Messages/chat.db"
|
||||
}
|
||||
}
|
||||
dbPath: "/Users/bot/Library/Messages/chat.db",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Example wrapper (`~/.openclaw/scripts/imsg-ssh`):
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
exec ssh -T bot@mac-mini.tailnet-1234.ts.net imsg "$@"
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Ensure the Mac is signed in to Messages, and Remote Login is enabled.
|
||||
- Use SSH keys so `ssh bot@mac-mini.tailnet-1234.ts.net` works without prompts.
|
||||
- `remoteHost` should match the SSH target so SCP can fetch attachments.
|
||||
@@ -172,7 +189,9 @@ Notes:
|
||||
Multi-account support: use `channels.imessage.accounts` with per-account config and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern. Don't commit `~/.openclaw/openclaw.json` (it often contains tokens).
|
||||
|
||||
## Access control (DMs + groups)
|
||||
|
||||
DMs:
|
||||
|
||||
- Default: `channels.imessage.dmPolicy = "pairing"`.
|
||||
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
@@ -181,23 +200,28 @@ DMs:
|
||||
- Pairing is the default token exchange for iMessage DMs. Details: [Pairing](/start/pairing)
|
||||
|
||||
Groups:
|
||||
|
||||
- `channels.imessage.groupPolicy = open | allowlist | disabled`.
|
||||
- `channels.imessage.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
||||
- Mention gating uses `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) because iMessage has no native mention metadata.
|
||||
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
|
||||
|
||||
## How it works (behavior)
|
||||
|
||||
- `imsg` streams message events; the gateway normalizes them into the shared channel envelope.
|
||||
- Replies always route back to the same chat id or handle.
|
||||
|
||||
## Group-ish threads (`is_group=false`)
|
||||
|
||||
Some iMessage threads can have multiple participants but still arrive with `is_group=false` depending on how Messages stores the chat identifier.
|
||||
|
||||
If you explicitly configure a `chat_id` under `channels.imessage.groups`, OpenClaw treats that thread as a “group” for:
|
||||
|
||||
- session isolation (separate `agent:<agentId>:imessage:group:<chat_id>` session key)
|
||||
- group allowlisting / mention gating behavior
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
@@ -205,39 +229,47 @@ Example:
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15555550123"],
|
||||
groups: {
|
||||
"42": { "requireMention": false }
|
||||
}
|
||||
}
|
||||
}
|
||||
"42": { requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
This is useful when you want an isolated personality/model for a specific thread (see [Multi-agent routing](/concepts/multi-agent)). For filesystem isolation, see [Sandboxing](/gateway/sandboxing).
|
||||
|
||||
## Media + limits
|
||||
|
||||
- Optional attachment ingestion via `channels.imessage.includeAttachments`.
|
||||
- Media cap via `channels.imessage.mediaMaxMb`.
|
||||
|
||||
## Limits
|
||||
|
||||
- Outbound text is chunked to `channels.imessage.textChunkLimit` (default 4000).
|
||||
- Optional newline chunking: set `channels.imessage.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- Media uploads are capped by `channels.imessage.mediaMaxMb` (default 16).
|
||||
|
||||
## Addressing / delivery targets
|
||||
|
||||
Prefer `chat_id` for stable routing:
|
||||
|
||||
- `chat_id:123` (preferred)
|
||||
- `chat_guid:...`
|
||||
- `chat_identifier:...`
|
||||
- direct handles: `imessage:+1555` / `sms:+1555` / `user@example.com`
|
||||
|
||||
List chats:
|
||||
|
||||
```
|
||||
imsg chats --limit 20
|
||||
```
|
||||
|
||||
## Configuration reference (iMessage)
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
|
||||
- `channels.imessage.enabled`: enable/disable channel startup.
|
||||
- `channels.imessage.cliPath`: path to `imsg`.
|
||||
- `channels.imessage.dbPath`: Messages DB path.
|
||||
@@ -257,5 +289,6 @@ Provider options:
|
||||
- `channels.imessage.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
|
||||
Related global options:
|
||||
|
||||
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`).
|
||||
- `messages.responsePrefix`.
|
||||
|
||||
@@ -4,6 +4,7 @@ read_when:
|
||||
- You want to choose a chat channel for OpenClaw
|
||||
- You need a quick overview of supported messaging platforms
|
||||
---
|
||||
|
||||
# Chat Channels
|
||||
|
||||
OpenClaw can talk to you on any chat app you already use. Each channel connects via the Gateway.
|
||||
|
||||
@@ -32,12 +32,12 @@ openclaw plugins install ./extensions/line
|
||||
|
||||
## Setup
|
||||
|
||||
1) Create a LINE Developers account and open the Console:
|
||||
1. Create a LINE Developers account and open the Console:
|
||||
https://developers.line.biz/console/
|
||||
2) Create (or pick) a Provider and add a **Messaging API** channel.
|
||||
3) Copy the **Channel access token** and **Channel secret** from the channel settings.
|
||||
4) Enable **Use webhook** in the Messaging API settings.
|
||||
5) Set the webhook URL to your gateway endpoint (HTTPS required):
|
||||
2. Create (or pick) a Provider and add a **Messaging API** channel.
|
||||
3. Copy the **Channel access token** and **Channel secret** from the channel settings.
|
||||
4. Enable **Use webhook** in the Messaging API settings.
|
||||
5. Set the webhook URL to your gateway endpoint (HTTPS required):
|
||||
|
||||
```
|
||||
https://gateway-host/line/webhook
|
||||
@@ -58,9 +58,9 @@ Minimal config:
|
||||
enabled: true,
|
||||
channelAccessToken: "LINE_CHANNEL_ACCESS_TOKEN",
|
||||
channelSecret: "LINE_CHANNEL_SECRET",
|
||||
dmPolicy: "pairing"
|
||||
}
|
||||
}
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -76,9 +76,9 @@ Token/secret files:
|
||||
channels: {
|
||||
line: {
|
||||
tokenFile: "/path/to/line-token.txt",
|
||||
secretFile: "/path/to/line-secret.txt"
|
||||
}
|
||||
}
|
||||
secretFile: "/path/to/line-secret.txt",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -92,11 +92,11 @@ Multiple accounts:
|
||||
marketing: {
|
||||
channelAccessToken: "...",
|
||||
channelSecret: "...",
|
||||
webhookPath: "/line/marketing"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
webhookPath: "/line/marketing",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -148,11 +148,13 @@ messages.
|
||||
title: "Office",
|
||||
address: "123 Main St",
|
||||
latitude: 35.681236,
|
||||
longitude: 139.767125
|
||||
longitude: 139.767125,
|
||||
},
|
||||
flexMessage: {
|
||||
altText: "Status card",
|
||||
contents: { /* Flex payload */ }
|
||||
contents: {
|
||||
/* Flex payload */
|
||||
},
|
||||
},
|
||||
templateMessage: {
|
||||
type: "confirm",
|
||||
@@ -160,10 +162,10 @@ messages.
|
||||
confirmLabel: "Yes",
|
||||
confirmData: "yes",
|
||||
cancelLabel: "No",
|
||||
cancelData: "no"
|
||||
}
|
||||
}
|
||||
}
|
||||
cancelData: "no",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -8,15 +8,18 @@ read_when:
|
||||
# Channel location parsing
|
||||
|
||||
OpenClaw normalizes shared locations from chat channels into:
|
||||
|
||||
- human-readable text appended to the inbound body, and
|
||||
- structured fields in the auto-reply context payload.
|
||||
|
||||
Currently supported:
|
||||
|
||||
- **Telegram** (location pins + venues + live locations)
|
||||
- **WhatsApp** (locationMessage + liveLocationMessage)
|
||||
- **Matrix** (`m.location` with `geo_uri`)
|
||||
|
||||
## Text formatting
|
||||
|
||||
Locations are rendered as friendly lines without brackets:
|
||||
|
||||
- Pin:
|
||||
@@ -27,13 +30,16 @@ Locations are rendered as friendly lines without brackets:
|
||||
- `🛰 Live location: 48.858844, 2.294351 ±12m`
|
||||
|
||||
If the channel includes a caption/comment, it is appended on the next line:
|
||||
|
||||
```
|
||||
📍 48.858844, 2.294351 ±12m
|
||||
Meet here
|
||||
```
|
||||
|
||||
## Context fields
|
||||
|
||||
When a location is present, these fields are added to `ctx`:
|
||||
|
||||
- `LocationLat` (number)
|
||||
- `LocationLon` (number)
|
||||
- `LocationAccuracy` (number, meters; optional)
|
||||
@@ -43,6 +49,7 @@ When a location is present, these fields are added to `ctx`:
|
||||
- `LocationIsLive` (boolean)
|
||||
|
||||
## Channel notes
|
||||
|
||||
- **Telegram**: venues map to `LocationName/LocationAddress`; live locations use `live_period`.
|
||||
- **WhatsApp**: `locationMessage.comment` and `liveLocationMessage.caption` are appended as the caption line.
|
||||
- **Matrix**: `geo_uri` is parsed as a pin location; altitude is ignored and `LocationIsLive` is always false.
|
||||
|
||||
@@ -3,6 +3,7 @@ summary: "Matrix support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Matrix channel features
|
||||
---
|
||||
|
||||
# Matrix (plugin)
|
||||
|
||||
Matrix is an open, decentralized messaging protocol. OpenClaw connects as a Matrix **user**
|
||||
@@ -36,13 +37,13 @@ Details: [Plugins](/plugin)
|
||||
|
||||
## Setup
|
||||
|
||||
1) Install the Matrix plugin:
|
||||
1. Install the Matrix plugin:
|
||||
- From npm: `openclaw plugins install @openclaw/matrix`
|
||||
- From a local checkout: `openclaw plugins install ./extensions/matrix`
|
||||
2) Create a Matrix account on a homeserver:
|
||||
2. Create a Matrix account on a homeserver:
|
||||
- Browse hosting options at [https://matrix.org/ecosystem/hosting/](https://matrix.org/ecosystem/hosting/)
|
||||
- Or host it yourself.
|
||||
3) Get an access token for the bot account:
|
||||
3. Get an access token for the bot account:
|
||||
- Use the Matrix login API with `curl` at your home server:
|
||||
|
||||
```bash
|
||||
@@ -63,14 +64,15 @@ Details: [Plugins](/plugin)
|
||||
- Or set `channels.matrix.userId` + `channels.matrix.password`: OpenClaw calls the same
|
||||
login endpoint, stores the access token in `~/.openclaw/credentials/matrix/credentials.json`,
|
||||
and reuses it on next start.
|
||||
4) Configure credentials:
|
||||
|
||||
4. Configure credentials:
|
||||
- Env: `MATRIX_HOMESERVER`, `MATRIX_ACCESS_TOKEN` (or `MATRIX_USER_ID` + `MATRIX_PASSWORD`)
|
||||
- Or config: `channels.matrix.*`
|
||||
- If both are set, config takes precedence.
|
||||
- With access token: user ID is fetched automatically via `/whoami`.
|
||||
- When set, `channels.matrix.userId` should be the full Matrix ID (example: `@bot:example.org`).
|
||||
5) Restart the gateway (or finish onboarding).
|
||||
6) Start a DM with the bot or invite it to a room from any Matrix client
|
||||
5. Restart the gateway (or finish onboarding).
|
||||
6. Start a DM with the bot or invite it to a room from any Matrix client
|
||||
(Element, Beeper, etc.; see https://matrix.org/ecosystem/clients/). Beeper requires E2EE,
|
||||
so set `channels.matrix.encryption: true` and verify the device.
|
||||
|
||||
@@ -83,9 +85,9 @@ Minimal config (access token, user ID auto-fetched):
|
||||
enabled: true,
|
||||
homeserver: "https://matrix.example.org",
|
||||
accessToken: "syt_***",
|
||||
dm: { policy: "pairing" }
|
||||
}
|
||||
}
|
||||
dm: { policy: "pairing" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -99,9 +101,9 @@ E2EE config (end to end encryption enabled):
|
||||
homeserver: "https://matrix.example.org",
|
||||
accessToken: "syt_***",
|
||||
encryption: true,
|
||||
dm: { policy: "pairing" }
|
||||
}
|
||||
}
|
||||
dm: { policy: "pairing" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -159,11 +161,11 @@ Once verified, the bot can decrypt messages in encrypted rooms.
|
||||
groupPolicy: "allowlist",
|
||||
groups: {
|
||||
"!roomId:example.org": { allow: true },
|
||||
"#alias:example.org": { allow: true }
|
||||
"#alias:example.org": { allow: true },
|
||||
},
|
||||
groupAllowFrom: ["@owner:example.org"]
|
||||
}
|
||||
}
|
||||
groupAllowFrom: ["@owner:example.org"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -187,17 +189,17 @@ Once verified, the bot can decrypt messages in encrypted rooms.
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Feature | Status |
|
||||
|---------|--------|
|
||||
| Direct messages | ✅ Supported |
|
||||
| Rooms | ✅ Supported |
|
||||
| Threads | ✅ Supported |
|
||||
| Media | ✅ Supported |
|
||||
| E2EE | ✅ Supported (crypto module required) |
|
||||
| Reactions | ✅ Supported (send/read via tools) |
|
||||
| Polls | ✅ Send supported; inbound poll starts are converted to text (responses/ends ignored) |
|
||||
| Location | ✅ Supported (geo URI; altitude ignored) |
|
||||
| Native commands | ✅ Supported |
|
||||
| Feature | Status |
|
||||
| --------------- | ------------------------------------------------------------------------------------- |
|
||||
| Direct messages | ✅ Supported |
|
||||
| Rooms | ✅ Supported |
|
||||
| Threads | ✅ Supported |
|
||||
| Media | ✅ Supported |
|
||||
| E2EE | ✅ Supported (crypto module required) |
|
||||
| Reactions | ✅ Supported (send/read via tools) |
|
||||
| Polls | ✅ Send supported; inbound poll starts are converted to text (responses/ends ignored) |
|
||||
| Location | ✅ Supported (geo URI; altitude ignored) |
|
||||
| Native commands | ✅ Supported |
|
||||
|
||||
## Configuration reference (Matrix)
|
||||
|
||||
|
||||
@@ -12,14 +12,17 @@ Mattermost is a self-hostable team messaging platform; see the official site at
|
||||
[mattermost.com](https://mattermost.com) for product details and downloads.
|
||||
|
||||
## Plugin required
|
||||
|
||||
Mattermost ships as a plugin and is not bundled with the core install.
|
||||
|
||||
Install via CLI (npm registry):
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/mattermost
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./extensions/mattermost
|
||||
```
|
||||
@@ -30,12 +33,14 @@ OpenClaw will offer the local install path automatically.
|
||||
Details: [Plugins](/plugin)
|
||||
|
||||
## Quick setup
|
||||
1) Install the Mattermost plugin.
|
||||
2) Create a Mattermost bot account and copy the **bot token**.
|
||||
3) Copy the Mattermost **base URL** (e.g., `https://chat.example.com`).
|
||||
4) Configure OpenClaw and start the gateway.
|
||||
|
||||
1. Install the Mattermost plugin.
|
||||
2. Create a Mattermost bot account and copy the **bot token**.
|
||||
3. Copy the Mattermost **base URL** (e.g., `https://chat.example.com`).
|
||||
4. Configure OpenClaw and start the gateway.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
@@ -43,13 +48,14 @@ Minimal config:
|
||||
enabled: true,
|
||||
botToken: "mm-token",
|
||||
baseUrl: "https://chat.example.com",
|
||||
dmPolicy: "pairing"
|
||||
}
|
||||
}
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Environment variables (default account)
|
||||
|
||||
Set these on the gateway host if you prefer env vars:
|
||||
|
||||
- `MATTERMOST_BOT_TOKEN=...`
|
||||
@@ -58,6 +64,7 @@ Set these on the gateway host if you prefer env vars:
|
||||
Env vars apply only to the **default** account (`default`). Other accounts must use config values.
|
||||
|
||||
## Chat modes
|
||||
|
||||
Mattermost responds to DMs automatically. Channel behavior is controlled by `chatmode`:
|
||||
|
||||
- `oncall` (default): respond only when @mentioned in channels.
|
||||
@@ -65,22 +72,25 @@ Mattermost responds to DMs automatically. Channel behavior is controlled by `cha
|
||||
- `onchar`: respond when a message starts with a trigger prefix.
|
||||
|
||||
Config example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
chatmode: "onchar",
|
||||
oncharPrefixes: [">", "!"]
|
||||
}
|
||||
}
|
||||
oncharPrefixes: [">", "!"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `onchar` still responds to explicit @mentions.
|
||||
- `channels.mattermost.requireMention` is honored for legacy configs but `chatmode` is preferred.
|
||||
|
||||
## Access control (DMs)
|
||||
|
||||
- Default: `channels.mattermost.dmPolicy = "pairing"` (unknown senders get a pairing code).
|
||||
- Approve via:
|
||||
- `openclaw pairing list mattermost`
|
||||
@@ -88,11 +98,13 @@ Notes:
|
||||
- Public DMs: `channels.mattermost.dmPolicy="open"` plus `channels.mattermost.allowFrom=["*"]`.
|
||||
|
||||
## Channels (groups)
|
||||
|
||||
- Default: `channels.mattermost.groupPolicy = "allowlist"` (mention-gated).
|
||||
- Allowlist senders with `channels.mattermost.groupAllowFrom` (user IDs or `@username`).
|
||||
- Open channels: `channels.mattermost.groupPolicy="open"` (mention-gated).
|
||||
|
||||
## Targets for outbound delivery
|
||||
|
||||
Use these target formats with `openclaw message send` or cron/webhooks:
|
||||
|
||||
- `channel:<id>` for a channel
|
||||
@@ -102,6 +114,7 @@ Use these target formats with `openclaw message send` or cron/webhooks:
|
||||
Bare IDs are treated as channels.
|
||||
|
||||
## Multi-account
|
||||
|
||||
Mattermost supports multiple accounts under `channels.mattermost.accounts`:
|
||||
|
||||
```json5
|
||||
@@ -110,14 +123,15 @@ Mattermost supports multiple accounts under `channels.mattermost.accounts`:
|
||||
mattermost: {
|
||||
accounts: {
|
||||
default: { name: "Primary", botToken: "mm-token", baseUrl: "https://chat.example.com" },
|
||||
alerts: { name: "Alerts", botToken: "mm-token-2", baseUrl: "https://alerts.example.com" }
|
||||
}
|
||||
}
|
||||
}
|
||||
alerts: { name: "Alerts", botToken: "mm-token-2", baseUrl: "https://alerts.example.com" },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- No replies in channels: ensure the bot is in the channel and mention it (oncall), use a trigger prefix (onchar), or set `chatmode: "onmessage"`.
|
||||
- Auth errors: check the bot token, base URL, and whether the account is enabled.
|
||||
- Multi-account issues: env vars only apply to the `default` account.
|
||||
|
||||
@@ -3,16 +3,17 @@ summary: "Microsoft Teams bot support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on MS Teams channel features
|
||||
---
|
||||
|
||||
# Microsoft Teams (plugin)
|
||||
|
||||
> "Abandon all hope, ye who enter here."
|
||||
|
||||
|
||||
Updated: 2026-01-21
|
||||
|
||||
Status: text + DM attachments are supported; channel/group file sending requires `sharePointSiteId` + Graph permissions (see [Sending files in group chats](#sending-files-in-group-chats)). Polls are sent via Adaptive Cards.
|
||||
|
||||
## Plugin required
|
||||
|
||||
Microsoft Teams ships as a plugin and is not bundled with the core install.
|
||||
|
||||
**Breaking change (2026.1.15):** MS Teams moved out of core. If you use it, you must install the plugin.
|
||||
@@ -20,11 +21,13 @@ Microsoft Teams ships as a plugin and is not bundled with the core install.
|
||||
Explainable: keeps core installs lighter and lets MS Teams dependencies update independently.
|
||||
|
||||
Install via CLI (npm registry):
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/msteams
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./extensions/msteams
|
||||
```
|
||||
@@ -35,13 +38,15 @@ OpenClaw will offer the local install path automatically.
|
||||
Details: [Plugins](/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Install the Microsoft Teams plugin.
|
||||
2) Create an **Azure Bot** (App ID + client secret + tenant ID).
|
||||
3) Configure OpenClaw with those credentials.
|
||||
4) Expose `/api/messages` (port 3978 by default) via a public URL or tunnel.
|
||||
5) Install the Teams app package and start the gateway.
|
||||
|
||||
1. Install the Microsoft Teams plugin.
|
||||
2. Create an **Azure Bot** (App ID + client secret + tenant ID).
|
||||
3. Configure OpenClaw with those credentials.
|
||||
4. Expose `/api/messages` (port 3978 by default) via a public URL or tunnel.
|
||||
5. Install the Teams app package and start the gateway.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
@@ -50,53 +55,61 @@ Minimal config:
|
||||
appId: "<APP_ID>",
|
||||
appPassword: "<APP_PASSWORD>",
|
||||
tenantId: "<TENANT_ID>",
|
||||
webhook: { port: 3978, path: "/api/messages" }
|
||||
}
|
||||
}
|
||||
webhook: { port: 3978, path: "/api/messages" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Note: group chats are blocked by default (`channels.msteams.groupPolicy: "allowlist"`). To allow group replies, set `channels.msteams.groupAllowFrom` (or use `groupPolicy: "open"` to allow any member, mention-gated).
|
||||
|
||||
## Goals
|
||||
|
||||
- Talk to OpenClaw via Teams DMs, group chats, or channels.
|
||||
- Keep routing deterministic: replies always go back to the channel they arrived on.
|
||||
- Default to safe channel behavior (mentions required unless configured otherwise).
|
||||
|
||||
## Config writes
|
||||
|
||||
By default, Microsoft Teams is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||
|
||||
Disable with:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: { msteams: { configWrites: false } }
|
||||
channels: { msteams: { configWrites: false } },
|
||||
}
|
||||
```
|
||||
|
||||
## Access control (DMs + groups)
|
||||
|
||||
**DM access**
|
||||
|
||||
- Default: `channels.msteams.dmPolicy = "pairing"`. Unknown senders are ignored until approved.
|
||||
- `channels.msteams.allowFrom` accepts AAD object IDs, UPNs, or display names. The wizard resolves names to IDs via Microsoft Graph when credentials allow.
|
||||
|
||||
**Group access**
|
||||
|
||||
- Default: `channels.msteams.groupPolicy = "allowlist"` (blocked unless you add `groupAllowFrom`). Use `channels.defaults.groupPolicy` to override the default when unset.
|
||||
- `channels.msteams.groupAllowFrom` controls which senders can trigger in group chats/channels (falls back to `channels.msteams.allowFrom`).
|
||||
- Set `groupPolicy: "open"` to allow any member (still mention‑gated by default).
|
||||
- To allow **no channels**, set `channels.msteams.groupPolicy: "disabled"`.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
msteams: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["user@org.com"]
|
||||
}
|
||||
}
|
||||
groupAllowFrom: ["user@org.com"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Teams + channel allowlist**
|
||||
|
||||
- Scope group/channel replies by listing teams and channels under `channels.msteams.teams`.
|
||||
- Keys can be team IDs or names; channel keys can be conversation IDs or names.
|
||||
- When `groupPolicy="allowlist"` and a teams allowlist is present, only listed teams/channels are accepted (mention‑gated).
|
||||
@@ -105,6 +118,7 @@ Example:
|
||||
and logs the mapping; unresolved entries are kept as typed.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
@@ -113,16 +127,17 @@ Example:
|
||||
teams: {
|
||||
"My Team": {
|
||||
channels: {
|
||||
"General": { requireMention: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
General: { requireMention: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
1. Install the Microsoft Teams plugin.
|
||||
2. Create an **Azure Bot** (App ID + secret + tenant ID).
|
||||
3. Build a **Teams app package** that references the bot and includes the RSC permissions below.
|
||||
@@ -139,14 +154,14 @@ Before configuring OpenClaw, you need to create an Azure Bot resource.
|
||||
1. Go to [Create Azure Bot](https://portal.azure.com/#create/Microsoft.AzureBot)
|
||||
2. Fill in the **Basics** tab:
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Bot handle** | Your bot name, e.g., `openclaw-msteams` (must be unique) |
|
||||
| **Subscription** | Select your Azure subscription |
|
||||
| **Resource group** | Create new or use existing |
|
||||
| **Pricing tier** | **Free** for dev/testing |
|
||||
| **Type of App** | **Single Tenant** (recommended - see note below) |
|
||||
| **Creation type** | **Create new Microsoft App ID** |
|
||||
| Field | Value |
|
||||
| ------------------ | -------------------------------------------------------- |
|
||||
| **Bot handle** | Your bot name, e.g., `openclaw-msteams` (must be unique) |
|
||||
| **Subscription** | Select your Azure subscription |
|
||||
| **Resource group** | Create new or use existing |
|
||||
| **Pricing tier** | **Free** for dev/testing |
|
||||
| **Type of App** | **Single Tenant** (recommended - see note below) |
|
||||
| **Creation type** | **Create new Microsoft App ID** |
|
||||
|
||||
> **Deprecation notice:** Creation of new multi-tenant bots was deprecated after 2025-07-31. Use **Single Tenant** for new bots.
|
||||
|
||||
@@ -178,6 +193,7 @@ Before configuring OpenClaw, you need to create an Azure Bot resource.
|
||||
Teams can't reach `localhost`. Use a tunnel for local development:
|
||||
|
||||
**Option A: ngrok**
|
||||
|
||||
```bash
|
||||
ngrok http 3978
|
||||
# Copy the https URL, e.g., https://abc123.ngrok.io
|
||||
@@ -185,6 +201,7 @@ ngrok http 3978
|
||||
```
|
||||
|
||||
**Option B: Tailscale Funnel**
|
||||
|
||||
```bash
|
||||
tailscale funnel 3978
|
||||
# Use your Tailscale funnel URL as the messaging endpoint
|
||||
@@ -207,16 +224,19 @@ This is often easier than hand-editing JSON manifests.
|
||||
## Testing the Bot
|
||||
|
||||
**Option A: Azure Web Chat (verify webhook first)**
|
||||
|
||||
1. In Azure Portal → your Azure Bot resource → **Test in Web Chat**
|
||||
2. Send a message - you should see a response
|
||||
3. This confirms your webhook endpoint works before Teams setup
|
||||
|
||||
**Option B: Teams (after app installation)**
|
||||
|
||||
1. Install the Teams app (sideload or org catalog)
|
||||
2. Find the bot in Teams and send a DM
|
||||
3. Check gateway logs for incoming activity
|
||||
|
||||
## Setup (minimal text-only)
|
||||
|
||||
1. **Install the Microsoft Teams plugin**
|
||||
- From npm: `openclaw plugins install @openclaw/msteams`
|
||||
- From a local checkout: `openclaw plugins install ./extensions/msteams`
|
||||
@@ -236,6 +256,7 @@ This is often easier than hand-editing JSON manifests.
|
||||
- Zip all three files together: `manifest.json`, `outline.png`, `color.png`.
|
||||
|
||||
4. **Configure OpenClaw**
|
||||
|
||||
```json
|
||||
{
|
||||
"msteams": {
|
||||
@@ -261,14 +282,17 @@ This is often easier than hand-editing JSON manifests.
|
||||
- The Teams channel starts automatically when the plugin is installed and `msteams` config exists with credentials.
|
||||
|
||||
## History context
|
||||
|
||||
- `channels.msteams.historyLimit` controls how many recent channel/group messages are wrapped into the prompt.
|
||||
- Falls back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
||||
- DM history can be limited with `channels.msteams.dmHistoryLimit` (user turns). Per-user overrides: `channels.msteams.dms["<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)
|
||||
@@ -278,9 +302,11 @@ These are the **existing resourceSpecific permissions** in our Teams app manifes
|
||||
- `TeamSettings.Read.Group` (Application)
|
||||
|
||||
**For group chats:**
|
||||
|
||||
- `ChatMessage.Read.Chat` (Application) - receive all group chat messages without @mention
|
||||
|
||||
## Example Teams Manifest (redacted)
|
||||
|
||||
Minimal, valid example with the required fields. Replace IDs and URLs.
|
||||
|
||||
```json
|
||||
@@ -330,6 +356,7 @@ Minimal, valid example with the required fields. Replace IDs and URLs.
|
||||
```
|
||||
|
||||
### Manifest caveats (must-have fields)
|
||||
|
||||
- `bots[].botId` **must** match the Azure Bot App ID.
|
||||
- `webApplicationInfo.id` **must** match the Azure Bot App ID.
|
||||
- `bots[].scopes` must include the surfaces you plan to use (`personal`, `team`, `groupChat`).
|
||||
@@ -352,34 +379,40 @@ To update an already-installed Teams app (e.g., to add RSC permissions):
|
||||
## Capabilities: RSC only vs Graph
|
||||
|
||||
### With **Teams RSC only** (app installed, no Graph API permissions)
|
||||
|
||||
Works:
|
||||
|
||||
- Read channel message **text** content.
|
||||
- Send channel message **text** content.
|
||||
- Receive **personal (DM)** file attachments.
|
||||
|
||||
Does NOT work:
|
||||
|
||||
- Channel/group **image or file contents** (payload only includes HTML stub).
|
||||
- Downloading attachments stored in SharePoint/OneDrive.
|
||||
- Reading message history (beyond the live webhook event).
|
||||
|
||||
### With **Teams RSC + Microsoft Graph Application permissions**
|
||||
|
||||
Adds:
|
||||
|
||||
- Downloading hosted contents (images pasted into messages).
|
||||
- Downloading file attachments stored in SharePoint/OneDrive.
|
||||
- Reading channel/chat message history via Graph.
|
||||
|
||||
### RSC vs Graph API
|
||||
|
||||
| Capability | RSC Permissions | Graph API |
|
||||
|------------|-----------------|-----------|
|
||||
| **Real-time messages** | Yes (via webhook) | No (polling only) |
|
||||
| **Historical messages** | No | Yes (can query history) |
|
||||
| **Setup complexity** | App manifest only | Requires admin consent + token flow |
|
||||
| **Works offline** | No (must be running) | Yes (query anytime) |
|
||||
| Capability | RSC Permissions | Graph API |
|
||||
| ----------------------- | -------------------- | ----------------------------------- |
|
||||
| **Real-time messages** | Yes (via webhook) | No (polling only) |
|
||||
| **Historical messages** | No | Yes (can query history) |
|
||||
| **Setup complexity** | App manifest only | Requires admin consent + token flow |
|
||||
| **Works offline** | No (must be running) | Yes (query anytime) |
|
||||
|
||||
**Bottom line:** RSC is for real-time listening; Graph API is for historical access. For catching up on missed messages while offline, you need Graph API with `ChannelMessage.Read.All` (requires admin consent).
|
||||
|
||||
## Graph-enabled media + history (required for channels)
|
||||
|
||||
If you need images/files in **channels** or want to fetch **message history**, you must enable Microsoft Graph permissions and grant admin consent.
|
||||
|
||||
1. In Entra ID (Azure AD) **App Registration**, add Microsoft Graph **Application permissions**:
|
||||
@@ -392,7 +425,9 @@ If you need images/files in **channels** or want to fetch **message history**, y
|
||||
## Known Limitations
|
||||
|
||||
### Webhook timeouts
|
||||
|
||||
Teams delivers messages via HTTP webhook. If processing takes too long (e.g., slow LLM responses), you may see:
|
||||
|
||||
- Gateway timeouts
|
||||
- Teams retrying the message (causing duplicates)
|
||||
- Dropped replies
|
||||
@@ -400,12 +435,15 @@ Teams delivers messages via HTTP webhook. If processing takes too long (e.g., sl
|
||||
OpenClaw handles this by returning quickly and sending replies proactively, but very slow responses may still cause issues.
|
||||
|
||||
### Formatting
|
||||
|
||||
Teams markdown is more limited than Slack or Discord:
|
||||
- Basic formatting works: **bold**, *italic*, `code`, links
|
||||
|
||||
- Basic formatting works: **bold**, _italic_, `code`, links
|
||||
- Complex markdown (tables, nested lists) may not render correctly
|
||||
- Adaptive Cards are supported for polls and arbitrary card sends (see below)
|
||||
|
||||
## Configuration
|
||||
|
||||
Key settings (see `/gateway/configuration` for shared channel patterns):
|
||||
|
||||
- `channels.msteams.enabled`: enable/disable the channel.
|
||||
@@ -430,6 +468,7 @@ Key settings (see `/gateway/configuration` for shared channel patterns):
|
||||
- `channels.msteams.sharePointSiteId`: SharePoint site ID for file uploads in group chats/channels (see [Sending files in group chats](#sending-files-in-group-chats)).
|
||||
|
||||
## Routing & Sessions
|
||||
|
||||
- Session keys follow the standard agent format (see [/concepts/session](/concepts/session)):
|
||||
- Direct messages share the main session (`agent:<agentId>:<mainKey>`).
|
||||
- Channel/group messages use conversation id:
|
||||
@@ -440,12 +479,13 @@ Key settings (see `/gateway/configuration` for shared channel patterns):
|
||||
|
||||
Teams recently introduced two channel UI styles over the same underlying data model:
|
||||
|
||||
| Style | Description | Recommended `replyStyle` |
|
||||
|-------|-------------|--------------------------|
|
||||
| **Posts** (classic) | Messages appear as cards with threaded replies underneath | `thread` (default) |
|
||||
| **Threads** (Slack-like) | Messages flow linearly, more like Slack | `top-level` |
|
||||
| Style | Description | Recommended `replyStyle` |
|
||||
| ------------------------ | --------------------------------------------------------- | ------------------------ |
|
||||
| **Posts** (classic) | Messages appear as cards with threaded replies underneath | `thread` (default) |
|
||||
| **Threads** (Slack-like) | Messages flow linearly, more like Slack | `top-level` |
|
||||
|
||||
**The problem:** The Teams API does not expose which UI style a channel uses. If you use the wrong `replyStyle`:
|
||||
|
||||
- `thread` in a Threads-style channel → replies appear nested awkwardly
|
||||
- `top-level` in a Posts-style channel → replies appear as separate top-level posts instead of in-thread
|
||||
|
||||
@@ -471,6 +511,7 @@ Teams recently introduced two channel UI styles over the same underlying data mo
|
||||
## Attachments & Images
|
||||
|
||||
**Current limitations:**
|
||||
|
||||
- **DMs:** Images and file attachments work via Teams bot file APIs.
|
||||
- **Channels/groups:** Attachments live in M365 storage (SharePoint/OneDrive). The webhook payload only includes an HTML stub, not the actual file bytes. **Graph API permissions are required** to download channel attachments.
|
||||
|
||||
@@ -481,11 +522,11 @@ By default, OpenClaw only downloads media from Microsoft/Teams hostnames. Overri
|
||||
|
||||
Bots can send files in DMs using the FileConsentCard flow (built-in). However, **sending files in group chats/channels** requires additional setup:
|
||||
|
||||
| Context | How files are sent | Setup needed |
|
||||
|---------|-------------------|--------------|
|
||||
| **DMs** | FileConsentCard → user accepts → bot uploads | Works out of the box |
|
||||
| **Group chats/channels** | Upload to SharePoint → share link | Requires `sharePointSiteId` + Graph permissions |
|
||||
| **Images (any context)** | Base64-encoded inline | Works out of the box |
|
||||
| Context | How files are sent | Setup needed |
|
||||
| ------------------------ | -------------------------------------------- | ----------------------------------------------- |
|
||||
| **DMs** | FileConsentCard → user accepts → bot uploads | Works out of the box |
|
||||
| **Group chats/channels** | Upload to SharePoint → share link | Requires `sharePointSiteId` + Graph permissions |
|
||||
| **Images (any context)** | Base64-encoded inline | Works out of the box |
|
||||
|
||||
### Why group chats need SharePoint
|
||||
|
||||
@@ -500,6 +541,7 @@ Bots don't have a personal OneDrive drive (the `/me/drive` Graph API endpoint do
|
||||
2. **Grant admin consent** for the tenant.
|
||||
|
||||
3. **Get your SharePoint site ID:**
|
||||
|
||||
```bash
|
||||
# Via Graph Explorer or curl with a valid token:
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
@@ -518,35 +560,36 @@ Bots don't have a personal OneDrive drive (the `/me/drive` Graph API endpoint do
|
||||
channels: {
|
||||
msteams: {
|
||||
// ... other config ...
|
||||
sharePointSiteId: "contoso.sharepoint.com,guid1,guid2"
|
||||
}
|
||||
}
|
||||
sharePointSiteId: "contoso.sharepoint.com,guid1,guid2",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Sharing behavior
|
||||
|
||||
| Permission | Sharing behavior |
|
||||
|------------|------------------|
|
||||
| `Sites.ReadWrite.All` only | Organization-wide sharing link (anyone in org can access) |
|
||||
| `Sites.ReadWrite.All` + `Chat.Read.All` | Per-user sharing link (only chat members can access) |
|
||||
| Permission | Sharing behavior |
|
||||
| --------------------------------------- | --------------------------------------------------------- |
|
||||
| `Sites.ReadWrite.All` only | Organization-wide sharing link (anyone in org can access) |
|
||||
| `Sites.ReadWrite.All` + `Chat.Read.All` | Per-user sharing link (only chat members can access) |
|
||||
|
||||
Per-user sharing is more secure as only the chat participants can access the file. If `Chat.Read.All` permission is missing, the bot falls back to organization-wide sharing.
|
||||
|
||||
### Fallback behavior
|
||||
|
||||
| Scenario | Result |
|
||||
|----------|--------|
|
||||
| Group chat + file + `sharePointSiteId` configured | Upload to SharePoint, send sharing link |
|
||||
| Group chat + file + no `sharePointSiteId` | Attempt OneDrive upload (may fail), send text only |
|
||||
| Personal chat + file | FileConsentCard flow (works without SharePoint) |
|
||||
| Any context + image | Base64-encoded inline (works without SharePoint) |
|
||||
| Scenario | Result |
|
||||
| ------------------------------------------------- | -------------------------------------------------- |
|
||||
| Group chat + file + `sharePointSiteId` configured | Upload to SharePoint, send sharing link |
|
||||
| Group chat + file + no `sharePointSiteId` | Attempt OneDrive upload (may fail), send text only |
|
||||
| Personal chat + file | FileConsentCard flow (works without SharePoint) |
|
||||
| Any context + image | Base64-encoded inline (works without SharePoint) |
|
||||
|
||||
### Files stored location
|
||||
|
||||
Uploaded files are stored in a `/OpenClawShared/` folder in the configured SharePoint site's default document library.
|
||||
|
||||
## Polls (Adaptive Cards)
|
||||
|
||||
OpenClaw sends Teams polls as Adaptive Cards (there is no native Teams poll API).
|
||||
|
||||
- CLI: `openclaw message poll --channel msteams --target conversation:<id> ...`
|
||||
@@ -555,11 +598,13 @@ OpenClaw sends Teams polls as Adaptive Cards (there is no native Teams poll API)
|
||||
- Polls do not auto-post result summaries yet (inspect the store file if needed).
|
||||
|
||||
## Adaptive Cards (arbitrary)
|
||||
|
||||
Send any Adaptive Card JSON to Teams users or conversations using the `message` tool or CLI.
|
||||
|
||||
The `card` parameter accepts an Adaptive Card JSON object. When `card` is provided, the message text is optional.
|
||||
|
||||
**Agent tool:**
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "send",
|
||||
@@ -568,12 +613,13 @@ The `card` parameter accepts an Adaptive Card JSON object. When `card` is provid
|
||||
"card": {
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.5",
|
||||
"body": [{"type": "TextBlock", "text": "Hello!"}]
|
||||
"body": [{ "type": "TextBlock", "text": "Hello!" }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**CLI:**
|
||||
|
||||
```bash
|
||||
openclaw message send --channel msteams \
|
||||
--target "conversation:19:abc...@thread.tacv2" \
|
||||
@@ -586,14 +632,15 @@ See [Adaptive Cards documentation](https://adaptivecards.io/) for card schema an
|
||||
|
||||
MSTeams targets use prefixes to distinguish between users and conversations:
|
||||
|
||||
| Target type | Format | Example |
|
||||
|-------------|--------|---------|
|
||||
| User (by ID) | `user:<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`) |
|
||||
| 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"
|
||||
@@ -610,6 +657,7 @@ openclaw message send --channel msteams --target "conversation:19:abc...@thread.
|
||||
```
|
||||
|
||||
**Agent tool examples:**
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "send",
|
||||
@@ -624,13 +672,18 @@ openclaw message send --channel msteams --target "conversation:19:abc...@thread.
|
||||
"action": "send",
|
||||
"channel": "msteams",
|
||||
"target": "conversation:19:abc...@thread.tacv2",
|
||||
"card": {"type": "AdaptiveCard", "version": "1.5", "body": [{"type": "TextBlock", "text": "Hello"}]}
|
||||
"card": {
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.5",
|
||||
"body": [{ "type": "TextBlock", "text": "Hello" }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: Without the `user:` prefix, names default to group/team resolution. Always use `user:` when targeting people by display name.
|
||||
|
||||
## Proactive messaging
|
||||
|
||||
- Proactive messages are only possible **after** a user has interacted, because we store conversation references at that point.
|
||||
- See `/gateway/configuration` for `dmPolicy` and allowlist gating.
|
||||
|
||||
@@ -639,6 +692,7 @@ Note: Without the `user:` prefix, names default to group/team resolution. Always
|
||||
The `groupId` query parameter in Teams URLs is **NOT** the team ID used for configuration. Extract IDs from the URL path instead:
|
||||
|
||||
**Team URL:**
|
||||
|
||||
```
|
||||
https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?groupId=...
|
||||
└────────────────────────────┘
|
||||
@@ -646,6 +700,7 @@ https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?gro
|
||||
```
|
||||
|
||||
**Channel URL:**
|
||||
|
||||
```
|
||||
https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=...
|
||||
└─────────────────────────┘
|
||||
@@ -653,6 +708,7 @@ https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?gr
|
||||
```
|
||||
|
||||
**For config:**
|
||||
|
||||
- Team ID = path segment after `/team/` (URL-decoded, e.g., `19:Bk4j...@thread.tacv2`)
|
||||
- Channel ID = path segment after `/channel/` (URL-decoded)
|
||||
- **Ignore** the `groupId` query parameter
|
||||
@@ -661,15 +717,16 @@ https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?gr
|
||||
|
||||
Bots have limited support in private channels:
|
||||
|
||||
| Feature | Standard Channels | Private Channels |
|
||||
|---------|-------------------|------------------|
|
||||
| Bot installation | Yes | Limited |
|
||||
| Real-time messages (webhook) | Yes | May not work |
|
||||
| RSC permissions | Yes | May behave differently |
|
||||
| @mentions | Yes | If bot is accessible |
|
||||
| Graph API history | Yes | Yes (with permissions) |
|
||||
| Feature | Standard Channels | Private Channels |
|
||||
| ---------------------------- | ----------------- | ---------------------- |
|
||||
| Bot installation | Yes | Limited |
|
||||
| Real-time messages (webhook) | Yes | May not work |
|
||||
| RSC permissions | Yes | May behave differently |
|
||||
| @mentions | Yes | If bot is accessible |
|
||||
| Graph API history | Yes | Yes (with permissions) |
|
||||
|
||||
**Workarounds if private channels don't work:**
|
||||
|
||||
1. Use standard channels for bot interactions
|
||||
2. Use DMs - users can always message the bot directly
|
||||
3. Use Graph API for historical access (requires `ChannelMessage.Read.All`)
|
||||
@@ -698,6 +755,7 @@ Bots have limited support in private channels:
|
||||
4. Confirm you're using the right scope: `ChannelMessage.Read.Group` for teams, `ChatMessage.Read.Chat` for group chats
|
||||
|
||||
## References
|
||||
|
||||
- [Create Azure Bot](https://learn.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) - Azure Bot setup guide
|
||||
- [Teams Developer Portal](https://dev.teams.microsoft.com/apps) - create/manage Teams apps
|
||||
- [Teams app manifest schema](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema)
|
||||
|
||||
@@ -3,19 +3,23 @@ summary: "Nextcloud Talk support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Nextcloud Talk channel features
|
||||
---
|
||||
|
||||
# Nextcloud Talk (plugin)
|
||||
|
||||
Status: supported via plugin (webhook bot). Direct messages, rooms, reactions, and markdown messages are supported.
|
||||
|
||||
## Plugin required
|
||||
|
||||
Nextcloud Talk ships as a plugin and is not bundled with the core install.
|
||||
|
||||
Install via CLI (npm registry):
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/nextcloud-talk
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./extensions/nextcloud-talk
|
||||
```
|
||||
@@ -26,18 +30,20 @@ OpenClaw will offer the local install path automatically.
|
||||
Details: [Plugins](/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Install the Nextcloud Talk plugin.
|
||||
2) On your Nextcloud server, create a bot:
|
||||
|
||||
1. Install the Nextcloud Talk plugin.
|
||||
2. On your Nextcloud server, create a bot:
|
||||
```bash
|
||||
./occ talk:bot:install "OpenClaw" "<shared-secret>" "<webhook-url>" --feature reaction
|
||||
```
|
||||
3) Enable the bot in the target room settings.
|
||||
4) Configure OpenClaw:
|
||||
3. Enable the bot in the target room settings.
|
||||
4. Configure OpenClaw:
|
||||
- Config: `channels.nextcloud-talk.baseUrl` + `channels.nextcloud-talk.botSecret`
|
||||
- Or env: `NEXTCLOUD_TALK_BOT_SECRET` (default account only)
|
||||
5) Restart the gateway (or finish onboarding).
|
||||
5. Restart the gateway (or finish onboarding).
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
@@ -45,19 +51,21 @@ Minimal config:
|
||||
enabled: true,
|
||||
baseUrl: "https://cloud.example.com",
|
||||
botSecret: "shared-secret",
|
||||
dmPolicy: "pairing"
|
||||
}
|
||||
}
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Bots cannot initiate DMs. The user must message the bot first.
|
||||
- Webhook URL must be reachable by the Gateway; set `webhookPublicUrl` if behind a proxy.
|
||||
- Media uploads are not supported by the bot API; media is sent as URLs.
|
||||
- The webhook payload does not distinguish DMs vs rooms; set `apiUser` + `apiPassword` to enable room-type lookups (otherwise DMs are treated as rooms).
|
||||
|
||||
## Access control (DMs)
|
||||
|
||||
- Default: `channels.nextcloud-talk.dmPolicy = "pairing"`. Unknown senders get a pairing code.
|
||||
- Approve via:
|
||||
- `openclaw pairing list nextcloud-talk`
|
||||
@@ -65,35 +73,41 @@ Minimal config:
|
||||
- Public DMs: `channels.nextcloud-talk.dmPolicy="open"` plus `channels.nextcloud-talk.allowFrom=["*"]`.
|
||||
|
||||
## Rooms (groups)
|
||||
|
||||
- Default: `channels.nextcloud-talk.groupPolicy = "allowlist"` (mention-gated).
|
||||
- Allowlist rooms with `channels.nextcloud-talk.rooms`:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
"nextcloud-talk": {
|
||||
rooms: {
|
||||
"room-token": { requireMention: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
"room-token": { requireMention: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- To allow no rooms, keep the allowlist empty or set `channels.nextcloud-talk.groupPolicy="disabled"`.
|
||||
|
||||
## Capabilities
|
||||
| Feature | Status |
|
||||
|---------|--------|
|
||||
| Direct messages | Supported |
|
||||
| Rooms | Supported |
|
||||
| Threads | Not supported |
|
||||
| Media | URL-only |
|
||||
| Reactions | Supported |
|
||||
|
||||
| Feature | Status |
|
||||
| --------------- | ------------- |
|
||||
| Direct messages | Supported |
|
||||
| Rooms | Supported |
|
||||
| Threads | Not supported |
|
||||
| Media | URL-only |
|
||||
| Reactions | Supported |
|
||||
| Native commands | Not supported |
|
||||
|
||||
## Configuration reference (Nextcloud Talk)
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
|
||||
- `channels.nextcloud-talk.enabled`: enable/disable channel startup.
|
||||
- `channels.nextcloud-talk.baseUrl`: Nextcloud instance URL.
|
||||
- `channels.nextcloud-talk.botSecret`: bot shared secret.
|
||||
|
||||
@@ -4,6 +4,7 @@ read_when:
|
||||
- You want OpenClaw to receive DMs via Nostr
|
||||
- You're setting up decentralized messaging
|
||||
---
|
||||
|
||||
# Nostr
|
||||
|
||||
**Status:** Optional plugin (disabled by default).
|
||||
@@ -40,14 +41,14 @@ Restart the Gateway after installing or enabling plugins.
|
||||
|
||||
## Quick setup
|
||||
|
||||
1) Generate a Nostr keypair (if needed):
|
||||
1. Generate a Nostr keypair (if needed):
|
||||
|
||||
```bash
|
||||
# Using nak
|
||||
nak key generate
|
||||
```
|
||||
|
||||
2) Add to config:
|
||||
2. Add to config:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -59,25 +60,25 @@ nak key generate
|
||||
}
|
||||
```
|
||||
|
||||
3) Export the key:
|
||||
3. Export the key:
|
||||
|
||||
```bash
|
||||
export NOSTR_PRIVATE_KEY="nsec1..."
|
||||
```
|
||||
|
||||
4) Restart the Gateway.
|
||||
4. Restart the Gateway.
|
||||
|
||||
## Configuration reference
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `privateKey` | string | required | Private key in `nsec` or hex format |
|
||||
| `relays` | string[] | `['wss://relay.damus.io', 'wss://nos.lol']` | Relay URLs (WebSocket) |
|
||||
| `dmPolicy` | string | `pairing` | DM access policy |
|
||||
| `allowFrom` | string[] | `[]` | Allowed sender pubkeys |
|
||||
| `enabled` | boolean | `true` | Enable/disable channel |
|
||||
| `name` | string | - | Display name |
|
||||
| `profile` | object | - | NIP-01 profile metadata |
|
||||
| Key | Type | Default | Description |
|
||||
| ------------ | -------- | ------------------------------------------- | ----------------------------------- |
|
||||
| `privateKey` | string | required | Private key in `nsec` or hex format |
|
||||
| `relays` | string[] | `['wss://relay.damus.io', 'wss://nos.lol']` | Relay URLs (WebSocket) |
|
||||
| `dmPolicy` | string | `pairing` | DM access policy |
|
||||
| `allowFrom` | string[] | `[]` | Allowed sender pubkeys |
|
||||
| `enabled` | boolean | `true` | Enable/disable channel |
|
||||
| `name` | string | - | Display name |
|
||||
| `profile` | object | - | NIP-01 profile metadata |
|
||||
|
||||
## Profile metadata
|
||||
|
||||
@@ -149,11 +150,7 @@ Defaults: `relay.damus.io` and `nos.lol`.
|
||||
"channels": {
|
||||
"nostr": {
|
||||
"privateKey": "${NOSTR_PRIVATE_KEY}",
|
||||
"relays": [
|
||||
"wss://relay.damus.io",
|
||||
"wss://relay.primal.net",
|
||||
"wss://nostr.wine"
|
||||
]
|
||||
"relays": ["wss://relay.damus.io", "wss://relay.primal.net", "wss://nostr.wine"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,12 +165,12 @@ Tips:
|
||||
|
||||
## Protocol support
|
||||
|
||||
| NIP | Status | Description |
|
||||
| --- | --- | --- |
|
||||
| NIP | Status | Description |
|
||||
| ------ | --------- | ------------------------------------- |
|
||||
| NIP-01 | Supported | Basic event format + profile metadata |
|
||||
| NIP-04 | Supported | Encrypted DMs (`kind:4`) |
|
||||
| NIP-17 | Planned | Gift-wrapped DMs |
|
||||
| NIP-44 | Planned | Versioned encryption |
|
||||
| NIP-04 | Supported | Encrypted DMs (`kind:4`) |
|
||||
| NIP-17 | Planned | Gift-wrapped DMs |
|
||||
| NIP-44 | Planned | Versioned encryption |
|
||||
|
||||
## Testing
|
||||
|
||||
@@ -197,10 +194,10 @@ docker run -p 7777:7777 ghcr.io/hoytech/strfry
|
||||
|
||||
### Manual test
|
||||
|
||||
1) Note the bot pubkey (npub) from logs.
|
||||
2) Open a Nostr client (Damus, Amethyst, etc.).
|
||||
3) DM the bot pubkey.
|
||||
4) Verify the response.
|
||||
1. Note the bot pubkey (npub) from logs.
|
||||
2. Open a Nostr client (Damus, Amethyst, etc.).
|
||||
3. DM the bot pubkey.
|
||||
4. Verify the response.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -4,19 +4,21 @@ read_when:
|
||||
- Setting up Signal support
|
||||
- Debugging Signal send/receive
|
||||
---
|
||||
# Signal (signal-cli)
|
||||
|
||||
# Signal (signal-cli)
|
||||
|
||||
Status: external CLI integration. Gateway talks to `signal-cli` over HTTP JSON-RPC + SSE.
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Use a **separate Signal number** for the bot (recommended).
|
||||
2) Install `signal-cli` (Java required).
|
||||
3) Link the bot device and start the daemon:
|
||||
|
||||
1. Use a **separate Signal number** for the bot (recommended).
|
||||
2. Install `signal-cli` (Java required).
|
||||
3. Link the bot device and start the daemon:
|
||||
- `signal-cli link -n "OpenClaw"`
|
||||
4) Configure OpenClaw and start the gateway.
|
||||
4. Configure OpenClaw and start the gateway.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
@@ -25,39 +27,45 @@ Minimal config:
|
||||
account: "+15551234567",
|
||||
cliPath: "signal-cli",
|
||||
dmPolicy: "pairing",
|
||||
allowFrom: ["+15557654321"]
|
||||
}
|
||||
}
|
||||
allowFrom: ["+15557654321"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## What it is
|
||||
|
||||
- Signal channel via `signal-cli` (not embedded libsignal).
|
||||
- Deterministic routing: replies always go back to Signal.
|
||||
- DMs share the agent's main session; groups are isolated (`agent:<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 } }
|
||||
channels: { signal: { configWrites: false } },
|
||||
}
|
||||
```
|
||||
|
||||
## The number model (important)
|
||||
|
||||
- The gateway connects to a **Signal device** (the `signal-cli` account).
|
||||
- If you run the bot on **your personal Signal account**, it will ignore your own messages (loop protection).
|
||||
- For "I text the bot and it replies," use a **separate bot number**.
|
||||
|
||||
## Setup (fast path)
|
||||
1) Install `signal-cli` (Java required).
|
||||
2) Link a bot account:
|
||||
|
||||
1. Install `signal-cli` (Java required).
|
||||
2. Link a bot account:
|
||||
- `signal-cli link -n "OpenClaw"` then scan the QR in Signal.
|
||||
3) Configure Signal and start the gateway.
|
||||
3. Configure Signal and start the gateway.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
@@ -66,15 +74,16 @@ Example:
|
||||
account: "+15551234567",
|
||||
cliPath: "signal-cli",
|
||||
dmPolicy: "pairing",
|
||||
allowFrom: ["+15557654321"]
|
||||
}
|
||||
}
|
||||
allowFrom: ["+15557654321"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Multi-account support: use `channels.signal.accounts` with per-account config and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
||||
|
||||
## External daemon mode (httpUrl)
|
||||
|
||||
If you want to manage `signal-cli` yourself (slow JVM cold starts, container init, or shared CPUs), run the daemon separately and point OpenClaw at it:
|
||||
|
||||
```json5
|
||||
@@ -82,16 +91,18 @@ If you want to manage `signal-cli` yourself (slow JVM cold starts, container ini
|
||||
channels: {
|
||||
signal: {
|
||||
httpUrl: "http://127.0.0.1:8080",
|
||||
autoStart: false
|
||||
}
|
||||
}
|
||||
autoStart: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
This skips auto-spawn and the startup wait inside OpenClaw. For slow starts when auto-spawning, set `channels.signal.startupTimeoutMs`.
|
||||
|
||||
## Access control (DMs + groups)
|
||||
|
||||
DMs:
|
||||
|
||||
- Default: `channels.signal.dmPolicy = "pairing"`.
|
||||
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
@@ -101,15 +112,18 @@ DMs:
|
||||
- UUID-only senders (from `sourceUuid`) are stored as `uuid:<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.
|
||||
|
||||
## How it works (behavior)
|
||||
|
||||
- `signal-cli` runs as a daemon; the gateway reads events via SSE.
|
||||
- Inbound messages are normalized into the shared channel envelope.
|
||||
- Replies always route back to the same number or group.
|
||||
|
||||
## Media + limits
|
||||
|
||||
- Outbound text is chunked to `channels.signal.textChunkLimit` (default 4000).
|
||||
- Optional newline chunking: set `channels.signal.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- Attachments supported (base64 fetched from `signal-cli`).
|
||||
@@ -118,17 +132,20 @@ Groups:
|
||||
- Group history context uses `channels.signal.historyLimit` (or `channels.signal.accounts.*.historyLimit`), falling back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
||||
|
||||
## Typing + read receipts
|
||||
|
||||
- **Typing indicators**: OpenClaw sends typing signals via `signal-cli sendTyping` and refreshes them while a reply is running.
|
||||
- **Read receipts**: when `channels.signal.sendReadReceipts` is true, OpenClaw forwards read receipts for allowed DMs.
|
||||
- Signal-cli does not expose read receipts for groups.
|
||||
|
||||
## Reactions (message tool)
|
||||
|
||||
- Use `message action=react` with `channel=signal`.
|
||||
- Targets: sender E.164 or UUID (use `uuid:<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
|
||||
@@ -136,6 +153,7 @@ message action=react channel=signal target=signal:group:<groupId> targetAuthor=u
|
||||
```
|
||||
|
||||
Config:
|
||||
|
||||
- `channels.signal.actions.reactions`: enable/disable reaction actions (default true).
|
||||
- `channels.signal.reactionLevel`: `off | ack | minimal | extensive`.
|
||||
- `off`/`ack` disables agent reactions (message tool `react` will error).
|
||||
@@ -143,15 +161,18 @@ Config:
|
||||
- Per-account overrides: `channels.signal.accounts.<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).
|
||||
|
||||
## Configuration reference (Signal)
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
|
||||
- `channels.signal.enabled`: enable/disable channel startup.
|
||||
- `channels.signal.account`: E.164 for the bot account.
|
||||
- `channels.signal.cliPath`: path to `signal-cli`.
|
||||
@@ -174,6 +195,7 @@ Provider options:
|
||||
- `channels.signal.mediaMaxMb`: inbound/outbound media cap (MB).
|
||||
|
||||
Related global options:
|
||||
|
||||
- `agents.list[].groupChat.mentionPatterns` (Signal does not support native mentions).
|
||||
- `messages.groupChat.mentionPatterns` (global fallback).
|
||||
- `messages.responsePrefix`.
|
||||
|
||||
@@ -8,38 +8,41 @@ read_when: "Setting up Slack or debugging Slack socket/HTTP mode"
|
||||
## Socket mode (default)
|
||||
|
||||
### Quick setup (beginner)
|
||||
1) Create a Slack app and enable **Socket Mode**.
|
||||
2) Create an **App Token** (`xapp-...`) and **Bot Token** (`xoxb-...`).
|
||||
3) Set tokens for OpenClaw and start the gateway.
|
||||
|
||||
1. Create a Slack app and enable **Socket Mode**.
|
||||
2. Create an **App Token** (`xapp-...`) and **Bot Token** (`xoxb-...`).
|
||||
3. Set tokens for OpenClaw and start the gateway.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
enabled: true,
|
||||
appToken: "xapp-...",
|
||||
botToken: "xoxb-..."
|
||||
}
|
||||
}
|
||||
botToken: "xoxb-...",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Setup
|
||||
1) Create a Slack app (From scratch) in https://api.slack.com/apps.
|
||||
2) **Socket Mode** → toggle on. Then go to **Basic Information** → **App-Level Tokens** → **Generate Token and Scopes** with scope `connections:write`. Copy the **App Token** (`xapp-...`).
|
||||
3) **OAuth & Permissions** → add bot token scopes (use the manifest below). Click **Install to Workspace**. Copy the **Bot User OAuth Token** (`xoxb-...`).
|
||||
4) Optional: **OAuth & Permissions** → add **User Token Scopes** (see the read-only list below). Reinstall the app and copy the **User OAuth Token** (`xoxp-...`).
|
||||
5) **Event Subscriptions** → enable events and subscribe to:
|
||||
|
||||
1. Create a Slack app (From scratch) in https://api.slack.com/apps.
|
||||
2. **Socket Mode** → toggle on. Then go to **Basic Information** → **App-Level Tokens** → **Generate Token and Scopes** with scope `connections:write`. Copy the **App Token** (`xapp-...`).
|
||||
3. **OAuth & Permissions** → add bot token scopes (use the manifest below). Click **Install to Workspace**. Copy the **Bot User OAuth Token** (`xoxb-...`).
|
||||
4. Optional: **OAuth & Permissions** → add **User Token Scopes** (see the read-only list below). Reinstall the app and copy the **User OAuth Token** (`xoxp-...`).
|
||||
5. **Event Subscriptions** → enable events and subscribe to:
|
||||
- `message.*` (includes edits/deletes/thread broadcasts)
|
||||
- `app_mention`
|
||||
- `reaction_added`, `reaction_removed`
|
||||
- `member_joined_channel`, `member_left_channel`
|
||||
- `channel_rename`
|
||||
- `pin_added`, `pin_removed`
|
||||
6) Invite the bot to channels you want it to read.
|
||||
7) Slash Commands → create `/openclaw` if you use `channels.slack.slashCommand`. If you enable native commands, add one slash command per built-in command (same names as `/help`). Native defaults to off for Slack unless you set `channels.slack.commands.native: true` (global `commands.native` is `"auto"` which leaves Slack off).
|
||||
8) App Home → enable the **Messages Tab** so users can DM the bot.
|
||||
6. Invite the bot to channels you want it to read.
|
||||
7. Slash Commands → create `/openclaw` if you use `channels.slack.slashCommand`. If you enable native commands, add one slash command per built-in command (same names as `/help`). Native defaults to off for Slack unless you set `channels.slack.commands.native: true` (global `commands.native` is `"auto"` which leaves Slack off).
|
||||
8. App Home → enable the **Messages Tab** so users can DM the bot.
|
||||
|
||||
Use the manifest below so scopes and events stay in sync.
|
||||
|
||||
@@ -48,6 +51,7 @@ Multi-account support: use `channels.slack.accounts` with per-account tokens and
|
||||
### OpenClaw config (minimal)
|
||||
|
||||
Set tokens via env vars (recommended):
|
||||
|
||||
- `SLACK_APP_TOKEN=xapp-...`
|
||||
- `SLACK_BOT_TOKEN=xoxb-...`
|
||||
|
||||
@@ -59,13 +63,14 @@ Or via config:
|
||||
slack: {
|
||||
enabled: true,
|
||||
appToken: "xapp-...",
|
||||
botToken: "xoxb-..."
|
||||
}
|
||||
}
|
||||
botToken: "xoxb-...",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### User token (optional)
|
||||
|
||||
OpenClaw can use a Slack user token (`xoxp-...`) for read operations (history,
|
||||
pins, reactions, emoji, member info). By default this stays read-only: reads
|
||||
prefer the user token when present, and writes still use the bot token unless
|
||||
@@ -76,20 +81,7 @@ User tokens are configured in the config file (no env var support). For
|
||||
multi-account, set `channels.slack.accounts.<id>.userToken`.
|
||||
|
||||
Example with bot + app + user tokens:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
enabled: true,
|
||||
appToken: "xapp-...",
|
||||
botToken: "xoxb-...",
|
||||
userToken: "xoxp-..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example with userTokenReadOnly explicitly set (allow user token writes):
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
@@ -98,13 +90,29 @@ Example with userTokenReadOnly explicitly set (allow user token writes):
|
||||
appToken: "xapp-...",
|
||||
botToken: "xoxb-...",
|
||||
userToken: "xoxp-...",
|
||||
userTokenReadOnly: false
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Example with userTokenReadOnly explicitly set (allow user token writes):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
enabled: true,
|
||||
appToken: "xapp-...",
|
||||
botToken: "xoxb-...",
|
||||
userToken: "xoxp-...",
|
||||
userTokenReadOnly: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### Token usage
|
||||
|
||||
- Read operations (history, reactions list, pins list, emoji list, member info,
|
||||
search) prefer the user token when configured, otherwise the bot token.
|
||||
- Write operations (send/edit/delete messages, add/remove reactions, pin/unpin,
|
||||
@@ -112,25 +120,29 @@ Example with userTokenReadOnly explicitly set (allow user token writes):
|
||||
no bot token is available, OpenClaw falls back to the user token.
|
||||
|
||||
### History context
|
||||
|
||||
- `channels.slack.historyLimit` (or `channels.slack.accounts.*.historyLimit`) controls how many recent channel/group messages are wrapped into the prompt.
|
||||
- Falls back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
||||
|
||||
## HTTP mode (Events API)
|
||||
|
||||
Use HTTP webhook mode when your Gateway is reachable by Slack over HTTPS (typical for server deployments).
|
||||
HTTP mode uses the Events API + Interactivity + Slash Commands with a shared request URL.
|
||||
|
||||
### Setup
|
||||
1) Create a Slack app and **disable Socket Mode** (optional if you only use HTTP).
|
||||
2) **Basic Information** → copy the **Signing Secret**.
|
||||
3) **OAuth & Permissions** → install the app and copy the **Bot User OAuth Token** (`xoxb-...`).
|
||||
4) **Event Subscriptions** → enable events and set the **Request URL** to your gateway webhook path (default `/slack/events`).
|
||||
5) **Interactivity & Shortcuts** → enable and set the same **Request URL**.
|
||||
6) **Slash Commands** → set the same **Request URL** for your command(s).
|
||||
|
||||
1. Create a Slack app and **disable Socket Mode** (optional if you only use HTTP).
|
||||
2. **Basic Information** → copy the **Signing Secret**.
|
||||
3. **OAuth & Permissions** → install the app and copy the **Bot User OAuth Token** (`xoxb-...`).
|
||||
4. **Event Subscriptions** → enable events and set the **Request URL** to your gateway webhook path (default `/slack/events`).
|
||||
5. **Interactivity & Shortcuts** → enable and set the same **Request URL**.
|
||||
6. **Slash Commands** → set the same **Request URL** for your command(s).
|
||||
|
||||
Example request URL:
|
||||
`https://gateway-host/slack/events`
|
||||
|
||||
### OpenClaw config (minimal)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
@@ -139,9 +151,9 @@ Example request URL:
|
||||
mode: "http",
|
||||
botToken: "xoxb-...",
|
||||
signingSecret: "your-signing-secret",
|
||||
webhookPath: "/slack/events"
|
||||
}
|
||||
}
|
||||
webhookPath: "/slack/events",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -149,6 +161,7 @@ Multi-account HTTP mode: set `channels.slack.accounts.<id>.mode = "http"` and pr
|
||||
`webhookPath` per account so each Slack app can point to its own URL.
|
||||
|
||||
### Manifest (optional)
|
||||
|
||||
Use this Slack app manifest to create the app quickly (adjust the name/command if you want). Include the
|
||||
user scopes if you plan to configure a user token.
|
||||
|
||||
@@ -243,11 +256,13 @@ user scopes if you plan to configure a user token.
|
||||
If you enable native commands, add one `slash_commands` entry per command you want to expose (matching the `/help` list). Override with `channels.slack.commands.native`.
|
||||
|
||||
## Scopes (current vs optional)
|
||||
|
||||
Slack's Conversations API is type-scoped: you only need the scopes for the
|
||||
conversation types you actually touch (channels, groups, im, mpim). See
|
||||
https://docs.slack.dev/apis/web-api/using-the-conversations-api/ for the overview.
|
||||
|
||||
### Bot token scopes (required)
|
||||
|
||||
- `chat:write` (send/update/delete messages via `chat.postMessage`)
|
||||
https://docs.slack.dev/reference/methods/chat.postMessage
|
||||
- `im:write` (open DMs via `conversations.open` for user DMs)
|
||||
@@ -270,6 +285,7 @@ https://docs.slack.dev/apis/web-api/using-the-conversations-api/ for the overvie
|
||||
https://docs.slack.dev/messaging/working-with-files/#upload
|
||||
|
||||
### User token scopes (optional, read-only by default)
|
||||
|
||||
Add these under **User Token Scopes** if you configure `channels.slack.userToken`.
|
||||
|
||||
- `channels:history`, `groups:history`, `im:history`, `mpim:history`
|
||||
@@ -281,6 +297,7 @@ Add these under **User Token Scopes** if you configure `channels.slack.userToken
|
||||
- `search:read`
|
||||
|
||||
### Not needed today (but likely future)
|
||||
|
||||
- `mpim:write` (only if we add group-DM open/DM start via `conversations.open`)
|
||||
- `groups:write` (only if we add private-channel management: create/rename/invite/archive)
|
||||
- `chat:write.public` (only if we want to post to channels the bot isn't in)
|
||||
@@ -290,6 +307,7 @@ Add these under **User Token Scopes** if you configure `channels.slack.userToken
|
||||
- `files:read` (only if we start listing/reading file metadata)
|
||||
|
||||
## Config
|
||||
|
||||
Slack uses Socket Mode only (no HTTP webhook server). Provide both tokens:
|
||||
|
||||
```json
|
||||
@@ -340,6 +358,7 @@ Slack uses Socket Mode only (no HTTP webhook server). Provide both tokens:
|
||||
```
|
||||
|
||||
Tokens can also be supplied via env vars:
|
||||
|
||||
- `SLACK_BOT_TOKEN`
|
||||
- `SLACK_APP_TOKEN`
|
||||
|
||||
@@ -348,94 +367,105 @@ Ack reactions are controlled globally via `messages.ackReaction` +
|
||||
ack reaction after the bot replies.
|
||||
|
||||
## Limits
|
||||
|
||||
- Outbound text is chunked to `channels.slack.textChunkLimit` (default 4000).
|
||||
- Optional newline chunking: set `channels.slack.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- Media uploads are capped by `channels.slack.mediaMaxMb` (default 20).
|
||||
|
||||
## Reply threading
|
||||
|
||||
By default, OpenClaw replies in the main channel. Use `channels.slack.replyToMode` to control automatic threading:
|
||||
|
||||
| Mode | Behavior |
|
||||
| --- | --- |
|
||||
| `off` | **Default.** Reply in main channel. Only thread if the triggering message was already in a thread. |
|
||||
| Mode | Behavior |
|
||||
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `off` | **Default.** Reply in main channel. Only thread if the triggering message was already in a thread. |
|
||||
| `first` | First reply goes to thread (under the triggering message), subsequent replies go to main channel. Useful for keeping context visible while avoiding thread clutter. |
|
||||
| `all` | All replies go to thread. Keeps conversations contained but may reduce visibility. |
|
||||
| `all` | All replies go to thread. Keeps conversations contained but may reduce visibility. |
|
||||
|
||||
The mode applies to both auto-replies and agent tool calls (`slack sendMessage`).
|
||||
|
||||
### Per-chat-type threading
|
||||
|
||||
You can configure different threading behavior per chat type by setting `channels.slack.replyToModeByChatType`:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
replyToMode: "off", // default for channels
|
||||
replyToMode: "off", // default for channels
|
||||
replyToModeByChatType: {
|
||||
direct: "all", // DMs always thread
|
||||
group: "first" // group DMs/MPIM thread first reply
|
||||
direct: "all", // DMs always thread
|
||||
group: "first", // group DMs/MPIM thread first reply
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Supported chat types:
|
||||
|
||||
- `direct`: 1:1 DMs (Slack `im`)
|
||||
- `group`: group DMs / MPIMs (Slack `mpim`)
|
||||
- `channel`: standard channels (public/private)
|
||||
|
||||
Precedence:
|
||||
1) `replyToModeByChatType.<chatType>`
|
||||
2) `replyToMode`
|
||||
3) Provider default (`off`)
|
||||
|
||||
1. `replyToModeByChatType.<chatType>`
|
||||
2. `replyToMode`
|
||||
3. Provider default (`off`)
|
||||
|
||||
Legacy `channels.slack.dm.replyToMode` is still accepted as a fallback for `direct` when no chat-type override is set.
|
||||
|
||||
Examples:
|
||||
|
||||
Thread DMs only:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
replyToMode: "off",
|
||||
replyToModeByChatType: { direct: "all" }
|
||||
}
|
||||
}
|
||||
replyToModeByChatType: { direct: "all" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Thread group DMs but keep channels in the root:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
replyToMode: "off",
|
||||
replyToModeByChatType: { group: "first" }
|
||||
}
|
||||
}
|
||||
replyToModeByChatType: { group: "first" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Make channels thread, keep DMs in the root:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
replyToMode: "first",
|
||||
replyToModeByChatType: { direct: "off", group: "off" }
|
||||
}
|
||||
}
|
||||
replyToModeByChatType: { direct: "off", group: "off" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Manual threading tags
|
||||
|
||||
For fine-grained control, use these tags in agent responses:
|
||||
|
||||
- `[[reply_to_current]]` — reply to the triggering message (start/continue thread).
|
||||
- `[[reply_to:<id>]]` — reply to a specific message id.
|
||||
|
||||
## Sessions + routing
|
||||
|
||||
- DMs share the `main` session (like WhatsApp/Telegram).
|
||||
- Channels map to `agent:<agentId>:slack:channel:<channelId>` sessions.
|
||||
- Slash commands use `agent:<agentId>:slack:slash:<userId>` sessions (prefix configurable via `channels.slack.slashCommand.sessionPrefix`).
|
||||
@@ -444,24 +474,27 @@ For fine-grained control, use these tags in agent responses:
|
||||
- Full command list + config: [Slash commands](/tools/slash-commands)
|
||||
|
||||
## DM security (pairing)
|
||||
|
||||
- Default: `channels.slack.dm.policy="pairing"` — unknown DM senders get a pairing code (expires after 1 hour).
|
||||
- Approve via: `openclaw pairing approve slack <code>`.
|
||||
- To allow anyone: set `channels.slack.dm.policy="open"` and `channels.slack.dm.allowFrom=["*"]`.
|
||||
- `channels.slack.dm.allowFrom` accepts user IDs, @handles, or emails (resolved at startup when tokens allow). The wizard accepts usernames and resolves them to ids during setup when tokens allow.
|
||||
|
||||
## Group policy
|
||||
|
||||
- `channels.slack.groupPolicy` controls channel handling (`open|disabled|allowlist`).
|
||||
- `allowlist` requires channels to be listed in `channels.slack.channels`.
|
||||
- If you only set `SLACK_BOT_TOKEN`/`SLACK_APP_TOKEN` and never create a `channels.slack` section,
|
||||
the runtime defaults `groupPolicy` to `open`. Add `channels.slack.groupPolicy`,
|
||||
`channels.defaults.groupPolicy`, or a channel allowlist to lock it down.
|
||||
- The configure wizard accepts `#channel` names and resolves them to IDs when possible
|
||||
(public + private); if multiple matches exist, it prefers the active channel.
|
||||
- On startup, OpenClaw resolves channel/user names in allowlists to IDs (when tokens allow)
|
||||
and logs the mapping; unresolved entries are kept as typed.
|
||||
- To allow **no channels**, set `channels.slack.groupPolicy: "disabled"` (or keep an empty allowlist).
|
||||
- If you only set `SLACK_BOT_TOKEN`/`SLACK_APP_TOKEN` and never create a `channels.slack` section,
|
||||
the runtime defaults `groupPolicy` to `open`. Add `channels.slack.groupPolicy`,
|
||||
`channels.defaults.groupPolicy`, or a channel allowlist to lock it down.
|
||||
- The configure wizard accepts `#channel` names and resolves them to IDs when possible
|
||||
(public + private); if multiple matches exist, it prefers the active channel.
|
||||
- On startup, OpenClaw resolves channel/user names in allowlists to IDs (when tokens allow)
|
||||
and logs the mapping; unresolved entries are kept as typed.
|
||||
- To allow **no channels**, set `channels.slack.groupPolicy: "disabled"` (or keep an empty allowlist).
|
||||
|
||||
Channel options (`channels.slack.channels.<id>` or `channels.slack.channels.<name>`):
|
||||
|
||||
- `allow`: allow/deny the channel when `groupPolicy="allowlist"`.
|
||||
- `requireMention`: mention gating for the channel.
|
||||
- `tools`: optional per-channel tool policy overrides (`allow`/`deny`/`alsoAllow`).
|
||||
@@ -473,22 +506,26 @@ Channel options (`channels.slack.channels.<id>` or `channels.slack.channels.<nam
|
||||
- `enabled`: set `false` to disable the channel.
|
||||
|
||||
## Delivery targets
|
||||
|
||||
Use these with cron/CLI sends:
|
||||
|
||||
- `user:<id>` for DMs
|
||||
- `channel:<id>` for channels
|
||||
|
||||
## Tool actions
|
||||
|
||||
Slack tool actions can be gated with `channels.slack.actions.*`:
|
||||
|
||||
| Action group | Default | Notes |
|
||||
| --- | --- | --- |
|
||||
| reactions | enabled | React + list reactions |
|
||||
| messages | enabled | Read/send/edit/delete |
|
||||
| pins | enabled | Pin/unpin/list |
|
||||
| memberInfo | enabled | Member info |
|
||||
| emojiList | enabled | Custom emoji list |
|
||||
| Action group | Default | Notes |
|
||||
| ------------ | ------- | ---------------------- |
|
||||
| reactions | enabled | React + list reactions |
|
||||
| messages | enabled | Read/send/edit/delete |
|
||||
| pins | enabled | Pin/unpin/list |
|
||||
| memberInfo | enabled | Member info |
|
||||
| emojiList | enabled | Custom emoji list |
|
||||
|
||||
## Security notes
|
||||
|
||||
- Writes default to the bot token so state-changing actions stay scoped to the
|
||||
app's bot permissions and identity.
|
||||
- Setting `userTokenReadOnly: false` allows the user token to be used for write
|
||||
@@ -500,6 +537,7 @@ Slack tool actions can be gated with `channels.slack.actions.*`:
|
||||
`files:write`) or those operations will fail.
|
||||
|
||||
## Notes
|
||||
|
||||
- Mention gating is controlled via `channels.slack.channels` (set `requireMention` to `true`); `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions.
|
||||
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
|
||||
- Reaction notifications follow `channels.slack.reactionNotifications` (use `reactionAllowlist` with mode `allowlist`).
|
||||
|
||||
@@ -3,49 +3,56 @@ summary: "Telegram bot support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Telegram features or webhooks
|
||||
---
|
||||
# Telegram (Bot API)
|
||||
|
||||
# Telegram (Bot API)
|
||||
|
||||
Status: production-ready for bot DMs + groups via grammY. Long-polling by default; webhook optional.
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Create a bot with **@BotFather** and copy the token.
|
||||
2) Set the token:
|
||||
|
||||
1. Create a bot with **@BotFather** and copy the token.
|
||||
2. Set the token:
|
||||
- Env: `TELEGRAM_BOT_TOKEN=...`
|
||||
- Or config: `channels.telegram.botToken: "..."`.
|
||||
- If both are set, config takes precedence (env fallback is default-account only).
|
||||
3) Start the gateway.
|
||||
4) DM access is pairing by default; approve the pairing code on first contact.
|
||||
3. Start the gateway.
|
||||
4. DM access is pairing by default; approve the pairing code on first contact.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
botToken: "123:abc",
|
||||
dmPolicy: "pairing"
|
||||
}
|
||||
}
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## What it is
|
||||
|
||||
- A Telegram Bot API channel owned by the Gateway.
|
||||
- Deterministic routing: replies go back to Telegram; the model never chooses channels.
|
||||
- DMs share the agent's main session; groups stay isolated (`agent:<agentId>:telegram:group:<chatId>`).
|
||||
|
||||
## Setup (fast path)
|
||||
|
||||
### 1) Create a bot token (BotFather)
|
||||
1) Open Telegram and chat with **@BotFather**.
|
||||
2) Run `/newbot`, then follow the prompts (name + username ending in `bot`).
|
||||
3) Copy the token and store it safely.
|
||||
|
||||
1. Open Telegram and chat with **@BotFather**.
|
||||
2. Run `/newbot`, then follow the prompts (name + username ending in `bot`).
|
||||
3. Copy the token and store it safely.
|
||||
|
||||
Optional BotFather settings:
|
||||
|
||||
- `/setjoingroups` — allow/deny adding the bot to groups.
|
||||
- `/setprivacy` — control whether the bot sees all group messages.
|
||||
|
||||
### 2) Configure the token (env or config)
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
@@ -55,9 +62,9 @@ Example:
|
||||
enabled: true,
|
||||
botToken: "123:abc",
|
||||
dmPolicy: "pairing",
|
||||
groups: { "*": { requireMention: true } }
|
||||
}
|
||||
}
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -66,19 +73,22 @@ If both env and config are set, config takes precedence.
|
||||
|
||||
Multi-account support: use `channels.telegram.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
||||
|
||||
3) Start the gateway. Telegram starts when a token is resolved (config first, env fallback).
|
||||
4) DM access defaults to pairing. Approve the code when the bot is first contacted.
|
||||
5) For groups: add the bot, decide privacy/admin behavior (below), then set `channels.telegram.groups` to control mention gating + allowlists.
|
||||
3. Start the gateway. Telegram starts when a token is resolved (config first, env fallback).
|
||||
4. DM access defaults to pairing. Approve the code when the bot is first contacted.
|
||||
5. For groups: add the bot, decide privacy/admin behavior (below), then set `channels.telegram.groups` to control mention gating + allowlists.
|
||||
|
||||
## Token + privacy + permissions (Telegram side)
|
||||
|
||||
### Token creation (BotFather)
|
||||
|
||||
- `/newbot` creates the bot and returns the token (keep it secret).
|
||||
- If a token leaks, revoke/regenerate it via @BotFather and update your config.
|
||||
|
||||
### Group message visibility (Privacy Mode)
|
||||
|
||||
Telegram bots default to **Privacy Mode**, which limits which group messages they receive.
|
||||
If your bot must see *all* group messages, you have two options:
|
||||
If your bot must see _all_ group messages, you have two options:
|
||||
|
||||
- Disable privacy mode with `/setprivacy` **or**
|
||||
- Add the bot as a group **admin** (admin bots receive all messages).
|
||||
|
||||
@@ -86,10 +96,12 @@ If your bot must see *all* group messages, you have two options:
|
||||
to each group for the change to take effect.
|
||||
|
||||
### Group permissions (admin rights)
|
||||
|
||||
Admin status is set inside the group (Telegram UI). Admin bots always receive all
|
||||
group messages, so use admin if you need full visibility.
|
||||
|
||||
## How it works (behavior)
|
||||
|
||||
- Inbound messages are normalized into the shared channel envelope with reply context and media placeholders.
|
||||
- Group replies require a mention by default (native @mention or `agents.list[].groupChat.mentionPatterns` / `messages.groupChat.mentionPatterns`).
|
||||
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
|
||||
@@ -98,12 +110,14 @@ group messages, so use admin if you need full visibility.
|
||||
- Telegram Bot API does not support read receipts; there is no `sendReadReceipts` option.
|
||||
|
||||
## Formatting (Telegram HTML)
|
||||
|
||||
- Outbound Telegram text uses `parse_mode: "HTML"` (Telegram’s supported tag subset).
|
||||
- Markdown-ish input is rendered into **Telegram-safe HTML** (bold/italic/strike/code/links); block elements are flattened to text with newlines/bullets.
|
||||
- Raw HTML from models is escaped to avoid Telegram parse errors.
|
||||
- If Telegram rejects the HTML payload, OpenClaw retries the same message as plain text.
|
||||
|
||||
## Commands (native + custom)
|
||||
|
||||
OpenClaw registers native commands (like `/status`, `/reset`, `/model`) with Telegram’s bot menu on startup.
|
||||
You can add custom commands to the menu via config:
|
||||
|
||||
@@ -113,10 +127,10 @@ You can add custom commands to the menu via config:
|
||||
telegram: {
|
||||
customCommands: [
|
||||
{ command: "backup", description: "Git backup" },
|
||||
{ command: "generate", description: "Create an image" }
|
||||
]
|
||||
}
|
||||
}
|
||||
{ command: "generate", description: "Create an image" },
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -128,12 +142,14 @@ You can add custom commands to the menu via config:
|
||||
More help: [Channel troubleshooting](/channels/troubleshooting).
|
||||
|
||||
Notes:
|
||||
|
||||
- Custom commands are **menu entries only**; OpenClaw does not implement them unless you handle them elsewhere.
|
||||
- Command names are normalized (leading `/` stripped, lowercased) and must match `a-z`, `0-9`, `_` (1–32 chars).
|
||||
- Custom commands **cannot override native commands**. Conflicts are ignored and logged.
|
||||
- If `commands.native` is disabled, only custom commands are registered (or cleared if none).
|
||||
|
||||
## Limits
|
||||
|
||||
- Outbound text is chunked to `channels.telegram.textChunkLimit` (default 4000).
|
||||
- Optional newline chunking: set `channels.telegram.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- Media downloads/uploads are capped by `channels.telegram.mediaMaxMb` (default 5).
|
||||
@@ -152,10 +168,10 @@ By default, the bot only responds to mentions in groups (`@botname` or patterns
|
||||
channels: {
|
||||
telegram: {
|
||||
groups: {
|
||||
"-1001234567890": { requireMention: false } // always respond in this group
|
||||
}
|
||||
}
|
||||
}
|
||||
"-1001234567890": { requireMention: false }, // always respond in this group
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -163,34 +179,37 @@ By default, the bot only responds to mentions in groups (`@botname` or patterns
|
||||
Forum topics inherit their parent group config (allowFrom, requireMention, skills, prompts) unless you add per-topic overrides under `channels.telegram.groups.<groupId>.topics.<topicId>`.
|
||||
|
||||
To allow all groups with always-respond:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
groups: {
|
||||
"*": { requireMention: false } // all groups, always respond
|
||||
}
|
||||
}
|
||||
}
|
||||
"*": { requireMention: false }, // all groups, always respond
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
To keep mention-only for all groups (default behavior):
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
groups: {
|
||||
"*": { requireMention: true } // or omit groups entirely
|
||||
}
|
||||
}
|
||||
}
|
||||
"*": { requireMention: true }, // or omit groups entirely
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Via command (session-level)
|
||||
|
||||
Send in the group:
|
||||
|
||||
- `/activation always` - respond to all messages
|
||||
- `/activation mention` - require mentions (default)
|
||||
|
||||
@@ -205,21 +224,26 @@ Forward any message from the group to `@userinfobot` or `@getidsbot` on Telegram
|
||||
**Privacy note:** `@userinfobot` is a third-party bot. If you prefer, add the bot to the group, send a message, and use `openclaw logs --follow` to read `chat.id`, or use the Bot API `getUpdates`.
|
||||
|
||||
## Config writes
|
||||
|
||||
By default, Telegram is allowed to write config updates triggered by channel events or `/config set|unset`.
|
||||
|
||||
This happens when:
|
||||
|
||||
- A group is upgraded to a supergroup and Telegram emits `migrate_to_chat_id` (chat ID changes). OpenClaw can migrate `channels.telegram.groups` automatically.
|
||||
- You run `/config set` or `/config unset` in a Telegram chat (requires `commands.config: true`).
|
||||
|
||||
Disable with:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: { telegram: { configWrites: false } }
|
||||
channels: { telegram: { configWrites: false } },
|
||||
}
|
||||
```
|
||||
|
||||
## Topics (forum supergroups)
|
||||
|
||||
Telegram forum topics include a `message_thread_id` per message. OpenClaw:
|
||||
|
||||
- Appends `:topic:<threadId>` to the Telegram group session key so each topic is isolated.
|
||||
- Sends typing indicators and replies with `message_thread_id` so responses stay in the topic.
|
||||
- General topic (thread id `1`) is special: message sends omit `message_thread_id` (Telegram rejects it), but typing indicators still include it.
|
||||
@@ -235,34 +259,36 @@ Telegram supports inline keyboards with callback buttons.
|
||||
|
||||
```json5
|
||||
{
|
||||
"channels": {
|
||||
"telegram": {
|
||||
"capabilities": {
|
||||
"inlineButtons": "allowlist"
|
||||
}
|
||||
}
|
||||
}
|
||||
channels: {
|
||||
telegram: {
|
||||
capabilities: {
|
||||
inlineButtons: "allowlist",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
For per-account configuration:
|
||||
|
||||
```json5
|
||||
{
|
||||
"channels": {
|
||||
"telegram": {
|
||||
"accounts": {
|
||||
"main": {
|
||||
"capabilities": {
|
||||
"inlineButtons": "allowlist"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
channels: {
|
||||
telegram: {
|
||||
accounts: {
|
||||
main: {
|
||||
capabilities: {
|
||||
inlineButtons: "allowlist",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Scopes:
|
||||
|
||||
- `off` — inline buttons disabled
|
||||
- `dm` — only DMs (group targets blocked)
|
||||
- `group` — only groups (DM targets blocked)
|
||||
@@ -278,19 +304,17 @@ Use the message tool with the `buttons` parameter:
|
||||
|
||||
```json5
|
||||
{
|
||||
"action": "send",
|
||||
"channel": "telegram",
|
||||
"to": "123456789",
|
||||
"message": "Choose an option:",
|
||||
"buttons": [
|
||||
action: "send",
|
||||
channel: "telegram",
|
||||
to: "123456789",
|
||||
message: "Choose an option:",
|
||||
buttons: [
|
||||
[
|
||||
{"text": "Yes", "callback_data": "yes"},
|
||||
{"text": "No", "callback_data": "no"}
|
||||
{ text: "Yes", callback_data: "yes" },
|
||||
{ text: "No", callback_data: "no" },
|
||||
],
|
||||
[
|
||||
{"text": "Cancel", "callback_data": "cancel"}
|
||||
]
|
||||
]
|
||||
[{ text: "Cancel", callback_data: "cancel" }],
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
@@ -305,9 +329,11 @@ Telegram capabilities can be configured at two levels (object form shown above;
|
||||
- `channels.telegram.accounts.<account>.capabilities`: Per-account capabilities that override the global defaults for that specific account.
|
||||
|
||||
Use the global setting when all Telegram bots/accounts should behave the same. Use per-account configuration when different bots need different behaviors (for example, one account only handles DMs while another is allowed in groups).
|
||||
|
||||
## Access control (DMs + groups)
|
||||
|
||||
### DM access
|
||||
|
||||
- Default: `channels.telegram.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
- `openclaw pairing list telegram`
|
||||
@@ -316,18 +342,22 @@ Use the global setting when all Telegram bots/accounts should behave the same. U
|
||||
- `channels.telegram.allowFrom` accepts numeric user IDs (recommended) or `@username` entries. It is **not** the bot username; use the human sender’s ID. The wizard accepts `@username` and resolves it to the numeric ID when possible.
|
||||
|
||||
#### Finding your Telegram user ID
|
||||
|
||||
Safer (no third-party bot):
|
||||
1) Start the gateway and DM your bot.
|
||||
2) Run `openclaw logs --follow` and look for `from.id`.
|
||||
|
||||
1. Start the gateway and DM your bot.
|
||||
2. Run `openclaw logs --follow` and look for `from.id`.
|
||||
|
||||
Alternate (official Bot API):
|
||||
1) DM your bot.
|
||||
2) Fetch updates with your bot token and read `message.from.id`:
|
||||
|
||||
1. DM your bot.
|
||||
2. Fetch updates with your bot token and read `message.from.id`:
|
||||
```bash
|
||||
curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
```
|
||||
|
||||
Third-party (less private):
|
||||
|
||||
- DM `@userinfobot` or `@getidsbot` and use the returned user id.
|
||||
|
||||
### Group access
|
||||
@@ -335,37 +365,45 @@ Third-party (less private):
|
||||
Two independent controls:
|
||||
|
||||
**1. Which groups are allowed** (group allowlist via `channels.telegram.groups`):
|
||||
|
||||
- No `groups` config = all groups allowed
|
||||
- With `groups` config = only listed groups or `"*"` are allowed
|
||||
- Example: `"groups": { "-1001234567890": {}, "*": {} }` allows all groups
|
||||
|
||||
**2. Which senders are allowed** (sender filtering via `channels.telegram.groupPolicy`):
|
||||
|
||||
- `"open"` = all senders in allowed groups can message
|
||||
- `"allowlist"` = only senders in `channels.telegram.groupAllowFrom` can message
|
||||
- `"disabled"` = no group messages accepted at all
|
||||
Default is `groupPolicy: "allowlist"` (blocked unless you add `groupAllowFrom`).
|
||||
Default is `groupPolicy: "allowlist"` (blocked unless you add `groupAllowFrom`).
|
||||
|
||||
Most users want: `groupPolicy: "allowlist"` + `groupAllowFrom` + specific groups listed in `channels.telegram.groups`
|
||||
|
||||
## Long-polling vs webhook
|
||||
|
||||
- Default: long-polling (no public URL required).
|
||||
- Webhook mode: set `channels.telegram.webhookUrl` (optionally `channels.telegram.webhookSecret` + `channels.telegram.webhookPath`).
|
||||
- The local listener binds to `0.0.0.0:8787` and serves `POST /telegram-webhook` by default.
|
||||
- If your public URL is different, use a reverse proxy and point `channels.telegram.webhookUrl` at the public endpoint.
|
||||
|
||||
## Reply threading
|
||||
|
||||
Telegram supports optional threaded replies via tags:
|
||||
|
||||
- `[[reply_to_current]]` -- reply to the triggering message.
|
||||
- `[[reply_to:<id>]]` -- reply to a specific message id.
|
||||
|
||||
Controlled by `channels.telegram.replyToMode`:
|
||||
|
||||
- `first` (default), `all`, `off`.
|
||||
|
||||
## Audio messages (voice vs file)
|
||||
|
||||
Telegram distinguishes **voice notes** (round bubble) from **audio files** (metadata card).
|
||||
OpenClaw defaults to audio files for backward compatibility.
|
||||
|
||||
To force a voice note bubble in agent replies, include this tag anywhere in the reply:
|
||||
|
||||
- `[[audio_as_voice]]` — send audio as a voice note instead of a file.
|
||||
|
||||
The tag is stripped from the delivered text. Other channels ignore this tag.
|
||||
@@ -375,11 +413,11 @@ For message tool sends, set `asVoice: true` with a voice-compatible audio `media
|
||||
|
||||
```json5
|
||||
{
|
||||
"action": "send",
|
||||
"channel": "telegram",
|
||||
"to": "123456789",
|
||||
"media": "https://example.com/voice.ogg",
|
||||
"asVoice": true
|
||||
action: "send",
|
||||
channel: "telegram",
|
||||
to: "123456789",
|
||||
media: "https://example.com/voice.ogg",
|
||||
asVoice: true,
|
||||
}
|
||||
```
|
||||
|
||||
@@ -396,6 +434,7 @@ When a user sends a sticker, OpenClaw handles it based on the sticker type:
|
||||
- **Video stickers (WEBM):** Skipped (video format not supported for processing).
|
||||
|
||||
Template context field available when receiving stickers:
|
||||
|
||||
- `Sticker` — object with:
|
||||
- `emoji` — emoji associated with the sticker
|
||||
- `setName` — name of the sticker set
|
||||
@@ -416,6 +455,7 @@ Stickers are processed through the AI's vision capabilities to generate descript
|
||||
**Cache location:** `~/.openclaw/telegram/sticker-cache.json`
|
||||
|
||||
**Cache entry format:**
|
||||
|
||||
```json
|
||||
{
|
||||
"fileId": "CAACAgIAAxkBAAI...",
|
||||
@@ -428,6 +468,7 @@ Stickers are processed through the AI's vision capabilities to generate descript
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- Reduces API costs by avoiding repeated vision calls for the same sticker
|
||||
- Faster response times for cached stickers (no vision processing delay)
|
||||
- Enables sticker search functionality based on cached descriptions
|
||||
@@ -443,10 +484,10 @@ The agent can send and search stickers using the `sticker` and `sticker-search`
|
||||
channels: {
|
||||
telegram: {
|
||||
actions: {
|
||||
sticker: true
|
||||
}
|
||||
}
|
||||
}
|
||||
sticker: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -454,14 +495,15 @@ The agent can send and search stickers using the `sticker` and `sticker-search`
|
||||
|
||||
```json5
|
||||
{
|
||||
"action": "sticker",
|
||||
"channel": "telegram",
|
||||
"to": "123456789",
|
||||
"fileId": "CAACAgIAAxkBAAI..."
|
||||
action: "sticker",
|
||||
channel: "telegram",
|
||||
to: "123456789",
|
||||
fileId: "CAACAgIAAxkBAAI...",
|
||||
}
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
- `fileId` (required) — the Telegram file ID of the sticker. Obtain this from `Sticker.fileId` when receiving a sticker, or from a `sticker-search` result.
|
||||
- `replyTo` (optional) — message ID to reply to.
|
||||
- `threadId` (optional) — message thread ID for forum topics.
|
||||
@@ -472,26 +514,27 @@ The agent can search cached stickers by description, emoji, or set name:
|
||||
|
||||
```json5
|
||||
{
|
||||
"action": "sticker-search",
|
||||
"channel": "telegram",
|
||||
"query": "cat waving",
|
||||
"limit": 5
|
||||
action: "sticker-search",
|
||||
channel: "telegram",
|
||||
query: "cat waving",
|
||||
limit: 5,
|
||||
}
|
||||
```
|
||||
|
||||
Returns matching stickers from the cache:
|
||||
|
||||
```json5
|
||||
{
|
||||
"ok": true,
|
||||
"count": 2,
|
||||
"stickers": [
|
||||
ok: true,
|
||||
count: 2,
|
||||
stickers: [
|
||||
{
|
||||
"fileId": "CAACAgIAAxkBAAI...",
|
||||
"emoji": "👋",
|
||||
"description": "A cartoon cat waving enthusiastically",
|
||||
"setName": "CoolCats"
|
||||
}
|
||||
]
|
||||
fileId: "CAACAgIAAxkBAAI...",
|
||||
emoji: "👋",
|
||||
description: "A cartoon cat waving enthusiastically",
|
||||
setName: "CoolCats",
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
@@ -501,26 +544,29 @@ The search uses fuzzy matching across description text, emoji characters, and se
|
||||
|
||||
```json5
|
||||
{
|
||||
"action": "sticker",
|
||||
"channel": "telegram",
|
||||
"to": "-1001234567890",
|
||||
"fileId": "CAACAgIAAxkBAAI...",
|
||||
"replyTo": 42,
|
||||
"threadId": 123
|
||||
action: "sticker",
|
||||
channel: "telegram",
|
||||
to: "-1001234567890",
|
||||
fileId: "CAACAgIAAxkBAAI...",
|
||||
replyTo: 42,
|
||||
threadId: 123,
|
||||
}
|
||||
```
|
||||
|
||||
## Streaming (drafts)
|
||||
|
||||
Telegram can stream **draft bubbles** while the agent is generating a response.
|
||||
OpenClaw uses Bot API `sendMessageDraft` (not real messages) and then sends the
|
||||
final reply as a normal message.
|
||||
|
||||
Requirements (Telegram Bot API 9.3+):
|
||||
|
||||
- **Private chats with topics enabled** (forum topic mode for the bot).
|
||||
- Incoming messages must include `message_thread_id` (private topic thread).
|
||||
- Streaming is ignored for groups/supergroups/channels.
|
||||
|
||||
Config:
|
||||
|
||||
- `channels.telegram.streamMode: "off" | "partial" | "block"` (default: `partial`)
|
||||
- `partial`: update the draft bubble with the latest streaming text.
|
||||
- `block`: update the draft bubble in larger blocks (chunked).
|
||||
@@ -534,15 +580,18 @@ Block streaming is off by default and requires `channels.telegram.blockStreaming
|
||||
if you want early Telegram messages instead of draft updates.
|
||||
|
||||
Reasoning stream (Telegram only):
|
||||
|
||||
- `/reasoning stream` streams reasoning into the draft bubble while the reply is
|
||||
generating, then sends the final answer without reasoning.
|
||||
- If `channels.telegram.streamMode` is `off`, reasoning stream is disabled.
|
||||
More context: [Streaming + chunking](/concepts/streaming).
|
||||
More context: [Streaming + chunking](/concepts/streaming).
|
||||
|
||||
## Retry policy
|
||||
|
||||
Outbound Telegram API calls retry on transient network/429 errors with exponential backoff and jitter. Configure via `channels.telegram.retry`. See [Retry policy](/concepts/retry).
|
||||
|
||||
## Agent tool (messages + reactions)
|
||||
|
||||
- Tool: `telegram` with `sendMessage` action (`to`, `content`, optional `mediaUrl`, `replyToMessageId`, `messageThreadId`).
|
||||
- Tool: `telegram` with `react` action (`chatId`, `messageId`, `emoji`).
|
||||
- Tool: `telegram` with `deleteMessage` action (`chatId`, `messageId`).
|
||||
@@ -562,6 +611,7 @@ Telegram reactions arrive as **separate `message_reaction` events**, not as prop
|
||||
The agent sees reactions as **system notifications** in the conversation history, not as message metadata.
|
||||
|
||||
**Configuration:**
|
||||
|
||||
- `channels.telegram.reactionNotifications`: Controls which reactions trigger notifications
|
||||
- `"off"` — ignore all reactions
|
||||
- `"own"` — notify when users react to bot messages (best-effort; in-memory) (default)
|
||||
@@ -576,29 +626,33 @@ The agent sees reactions as **system notifications** in the conversation history
|
||||
**Forum groups:** Reactions in forum groups include `message_thread_id` and use session keys like `agent:main:telegram:group:{chatId}:topic:{threadId}`. This ensures reactions and messages in the same topic stay together.
|
||||
|
||||
**Example config:**
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
telegram: {
|
||||
reactionNotifications: "all", // See all reactions
|
||||
reactionLevel: "minimal" // Agent can react sparingly
|
||||
}
|
||||
}
|
||||
reactionNotifications: "all", // See all reactions
|
||||
reactionLevel: "minimal", // Agent can react sparingly
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- Telegram bots must explicitly request `message_reaction` in `allowed_updates` (configured automatically by OpenClaw)
|
||||
- For webhook mode, reactions are included in the webhook `allowed_updates`
|
||||
- For polling mode, reactions are included in the `getUpdates` `allowed_updates`
|
||||
|
||||
## Delivery targets (CLI/cron)
|
||||
|
||||
- Use a chat id (`123456789`) or a username (`@name`) as the target.
|
||||
- Example: `openclaw message send --channel telegram --target 123456789 --message "hi"`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Bot doesn’t respond to non-mention messages in a group:**
|
||||
|
||||
- If you set `channels.telegram.groups.*.requireMention=false`, Telegram’s Bot API **privacy mode** must be disabled.
|
||||
- BotFather: `/setprivacy` → **Disable** (then remove + re-add the bot to the group)
|
||||
- `openclaw channels status` shows a warning when config expects unmentioned group messages.
|
||||
@@ -606,32 +660,39 @@ The agent sees reactions as **system notifications** in the conversation history
|
||||
- Quick test: `/activation always` (session-only; use config for persistence)
|
||||
|
||||
**Bot not seeing group messages at all:**
|
||||
|
||||
- If `channels.telegram.groups` is set, the group must be listed or use `"*"`
|
||||
- Check Privacy Settings in @BotFather → "Group Privacy" should be **OFF**
|
||||
- Verify bot is actually a member (not just an admin with no read access)
|
||||
- Check gateway logs: `openclaw logs --follow` (look for "skipping group message")
|
||||
|
||||
**Bot responds to mentions but not `/activation always`:**
|
||||
|
||||
- The `/activation` command updates session state but doesn't persist to config
|
||||
- For persistent behavior, add group to `channels.telegram.groups` with `requireMention: false`
|
||||
|
||||
**Commands like `/status` don't work:**
|
||||
|
||||
- Make sure your Telegram user ID is authorized (via pairing or `channels.telegram.allowFrom`)
|
||||
- Commands require authorization even in groups with `groupPolicy: "open"`
|
||||
|
||||
**Long-polling aborts immediately on Node 22+ (often with proxies/custom fetch):**
|
||||
|
||||
- Node 22+ is stricter about `AbortSignal` instances; foreign signals can abort `fetch` calls right away.
|
||||
- Upgrade to a OpenClaw build that normalizes abort signals, or run the gateway on Node 20 until you can upgrade.
|
||||
|
||||
**Bot starts, then silently stops responding (or logs `HttpError: Network request ... failed`):**
|
||||
|
||||
- Some hosts resolve `api.telegram.org` to IPv6 first. If your server does not have working IPv6 egress, grammY can get stuck on IPv6-only requests.
|
||||
- Fix by enabling IPv6 egress **or** forcing IPv4 resolution for `api.telegram.org` (for example, add an `/etc/hosts` entry using the IPv4 A record, or prefer IPv4 in your OS DNS stack), then restart the gateway.
|
||||
- Quick check: `dig +short api.telegram.org A` and `dig +short api.telegram.org AAAA` to confirm what DNS returns.
|
||||
|
||||
## Configuration reference (Telegram)
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
|
||||
- `channels.telegram.enabled`: enable/disable channel startup.
|
||||
- `channels.telegram.botToken`: bot token (BotFather).
|
||||
- `channels.telegram.tokenFile`: read token from file path.
|
||||
@@ -669,6 +730,7 @@ Provider options:
|
||||
- `channels.telegram.reactionLevel`: `off | ack | minimal | extensive` — control agent's reaction capability (default: `minimal` when not set).
|
||||
|
||||
Related global options:
|
||||
|
||||
- `agents.list[].groupChat.mentionPatterns` (mention gating patterns).
|
||||
- `messages.groupChat.mentionPatterns` (global fallback).
|
||||
- `commands.native` (defaults to `"auto"` → on for Telegram/Discord, off for Slack), `commands.text`, `commands.useAccessGroups` (command behavior). Override with `channels.telegram.commands.native`.
|
||||
|
||||
@@ -3,6 +3,7 @@ summary: "Tlon/Urbit support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Tlon/Urbit channel features
|
||||
---
|
||||
|
||||
# Tlon (plugin)
|
||||
|
||||
Tlon is a decentralized messenger built on Urbit. OpenClaw connects to your Urbit ship and can
|
||||
@@ -32,11 +33,11 @@ Details: [Plugins](/plugin)
|
||||
|
||||
## Setup
|
||||
|
||||
1) Install the Tlon plugin.
|
||||
2) Gather your ship URL and login code.
|
||||
3) Configure `channels.tlon`.
|
||||
4) Restart the gateway.
|
||||
5) DM the bot or mention it in a group channel.
|
||||
1. Install the Tlon plugin.
|
||||
2. Gather your ship URL and login code.
|
||||
3. Configure `channels.tlon`.
|
||||
4. Restart the gateway.
|
||||
5. DM the bot or mention it in a group channel.
|
||||
|
||||
Minimal config (single account):
|
||||
|
||||
@@ -47,9 +48,9 @@ Minimal config (single account):
|
||||
enabled: true,
|
||||
ship: "~sampel-palnet",
|
||||
url: "https://your-ship-host",
|
||||
code: "lidlut-tabwed-pillex-ridrup"
|
||||
}
|
||||
}
|
||||
code: "lidlut-tabwed-pillex-ridrup",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -61,12 +62,9 @@ Auto-discovery is enabled by default. You can also pin channels manually:
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
groupChannels: [
|
||||
"chat/~host-ship/general",
|
||||
"chat/~host-ship/support"
|
||||
]
|
||||
}
|
||||
}
|
||||
groupChannels: ["chat/~host-ship/general", "chat/~host-ship/support"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -76,9 +74,9 @@ Disable auto-discovery:
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
autoDiscoverChannels: false
|
||||
}
|
||||
}
|
||||
autoDiscoverChannels: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -90,9 +88,9 @@ DM allowlist (empty = allow all):
|
||||
{
|
||||
channels: {
|
||||
tlon: {
|
||||
dmAllowlist: ["~zod", "~nec"]
|
||||
}
|
||||
}
|
||||
dmAllowlist: ["~zod", "~nec"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -107,15 +105,15 @@ Group authorization (restricted by default):
|
||||
channelRules: {
|
||||
"chat/~host-ship/general": {
|
||||
mode: "restricted",
|
||||
allowedShips: ["~zod", "~nec"]
|
||||
allowedShips: ["~zod", "~nec"],
|
||||
},
|
||||
"chat/~host-ship/announcements": {
|
||||
mode: "open"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mode: "open",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ read_when:
|
||||
- A channel connects but messages don’t flow
|
||||
- Investigating channel misconfiguration (intents, permissions, privacy mode)
|
||||
---
|
||||
|
||||
# Channel troubleshooting
|
||||
|
||||
Start with:
|
||||
@@ -16,10 +17,12 @@ openclaw channels status --probe
|
||||
`channels status --probe` prints warnings when it can detect common channel misconfigurations, and includes small live checks (credentials, some permissions/membership).
|
||||
|
||||
## Channels
|
||||
|
||||
- Discord: [/channels/discord#troubleshooting](/channels/discord#troubleshooting)
|
||||
- Telegram: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting)
|
||||
- WhatsApp: [/channels/whatsapp#troubleshooting-quick](/channels/whatsapp#troubleshooting-quick)
|
||||
|
||||
## Telegram quick fixes
|
||||
|
||||
- Logs show `HttpError: Network request for 'sendMessage' failed` or `sendChatAction` → check IPv6 DNS. If `api.telegram.org` resolves to IPv6 first and the host lacks IPv6 egress, force IPv4 or enable IPv6. See [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting).
|
||||
- Logs show `setMyCommands failed` → check outbound HTTPS and DNS reachability to `api.telegram.org` (common on locked-down VPS or proxies).
|
||||
|
||||
@@ -3,6 +3,7 @@ summary: "Twitch chat bot configuration and setup"
|
||||
read_when:
|
||||
- Setting up Twitch chat integration for OpenClaw
|
||||
---
|
||||
|
||||
# Twitch (plugin)
|
||||
|
||||
Twitch chat support via IRC connection. OpenClaw connects as a Twitch user (bot account) to receive and send messages in channels.
|
||||
@@ -27,17 +28,17 @@ Details: [Plugins](/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
|
||||
1) Create a dedicated Twitch account for the bot (or use an existing account).
|
||||
2) Generate credentials: [Twitch Token Generator](https://twitchtokengenerator.com/)
|
||||
1. Create a dedicated Twitch account for the bot (or use an existing account).
|
||||
2. Generate credentials: [Twitch Token Generator](https://twitchtokengenerator.com/)
|
||||
- Select **Bot Token**
|
||||
- Verify scopes `chat:read` and `chat:write` are selected
|
||||
- Copy the **Client ID** and **Access Token**
|
||||
3) Find your Twitch user ID: https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
|
||||
4) Configure the token:
|
||||
3. Find your Twitch user ID: https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
|
||||
4. Configure the token:
|
||||
- Env: `OPENCLAW_TWITCH_ACCESS_TOKEN=...` (default account only)
|
||||
- Or config: `channels.twitch.accessToken`
|
||||
- If both are set, config takes precedence (env fallback is default-account only).
|
||||
5) Start the gateway.
|
||||
5. Start the gateway.
|
||||
|
||||
**⚠️ Important:** Add access control (`allowFrom` or `allowedRoles`) to prevent unauthorized users from triggering the bot. `requireMention` defaults to `true`.
|
||||
|
||||
@@ -48,13 +49,13 @@ Minimal config:
|
||||
channels: {
|
||||
twitch: {
|
||||
enabled: true,
|
||||
username: "openclaw", // Bot's Twitch account
|
||||
accessToken: "oauth:abc123...", // OAuth Access Token (or use OPENCLAW_TWITCH_ACCESS_TOKEN env var)
|
||||
clientId: "xyz789...", // Client ID from Token Generator
|
||||
channel: "vevisk", // Which Twitch channel's chat to join (required)
|
||||
allowFrom: ["123456789"] // (recommended) Your Twitch user ID only - get it from https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
|
||||
}
|
||||
}
|
||||
username: "openclaw", // Bot's Twitch account
|
||||
accessToken: "oauth:abc123...", // OAuth Access Token (or use OPENCLAW_TWITCH_ACCESS_TOKEN env var)
|
||||
clientId: "xyz789...", // Client ID from Token Generator
|
||||
channel: "vevisk", // Which Twitch channel's chat to join (required)
|
||||
allowFrom: ["123456789"], // (recommended) Your Twitch user ID only - get it from https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -70,6 +71,7 @@ Minimal config:
|
||||
### Generate credentials
|
||||
|
||||
Use [Twitch Token Generator](https://twitchtokengenerator.com/):
|
||||
|
||||
- Select **Bot Token**
|
||||
- Verify scopes `chat:read` and `chat:write` are selected
|
||||
- Copy the **Client ID** and **Access Token**
|
||||
@@ -79,11 +81,13 @@ No manual app registration needed. Tokens expire after several hours.
|
||||
### Configure the bot
|
||||
|
||||
**Env var (default account only):**
|
||||
|
||||
```bash
|
||||
OPENCLAW_TWITCH_ACCESS_TOKEN=oauth:abc123...
|
||||
```
|
||||
|
||||
**Or config:**
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
@@ -92,9 +96,9 @@ OPENCLAW_TWITCH_ACCESS_TOKEN=oauth:abc123...
|
||||
username: "openclaw",
|
||||
accessToken: "oauth:abc123...",
|
||||
clientId: "xyz789...",
|
||||
channel: "vevisk"
|
||||
}
|
||||
}
|
||||
channel: "vevisk",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -106,10 +110,10 @@ If both env and config are set, config takes precedence.
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
allowFrom: ["123456789"], // (recommended) Your Twitch user ID only
|
||||
allowedRoles: ["moderator"] // Or restrict to roles
|
||||
}
|
||||
}
|
||||
allowFrom: ["123456789"], // (recommended) Your Twitch user ID only
|
||||
allowedRoles: ["moderator"], // Or restrict to roles
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -130,9 +134,9 @@ For automatic token refresh, create your own Twitch application at [Twitch Devel
|
||||
channels: {
|
||||
twitch: {
|
||||
clientSecret: "your_client_secret",
|
||||
refreshToken: "your_refresh_token"
|
||||
}
|
||||
}
|
||||
refreshToken: "your_refresh_token",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -153,17 +157,17 @@ Example (one bot account in two channels):
|
||||
username: "openclaw",
|
||||
accessToken: "oauth:abc123...",
|
||||
clientId: "xyz789...",
|
||||
channel: "vevisk"
|
||||
channel: "vevisk",
|
||||
},
|
||||
channel2: {
|
||||
username: "openclaw",
|
||||
accessToken: "oauth:def456...",
|
||||
clientId: "uvw012...",
|
||||
channel: "secondchannel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
channel: "secondchannel",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -179,11 +183,11 @@ Example (one bot account in two channels):
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
allowedRoles: ["moderator", "vip"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
allowedRoles: ["moderator", "vip"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -195,11 +199,11 @@ Example (one bot account in two channels):
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
allowFrom: ["123456789", "987654321"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
allowFrom: ["123456789", "987654321"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -214,11 +218,11 @@ Users in `allowFrom` bypass role checks:
|
||||
accounts: {
|
||||
default: {
|
||||
allowFrom: ["123456789"],
|
||||
allowedRoles: ["moderator"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
allowedRoles: ["moderator"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -232,11 +236,11 @@ By default, `requireMention` is `true`. To disable and respond to all messages:
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
requireMention: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
requireMention: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -258,6 +262,7 @@ openclaw channels status --probe
|
||||
### Token issues
|
||||
|
||||
**"Failed to connect" or authentication errors:**
|
||||
|
||||
- Verify `accessToken` is the OAuth access token value (typically starts with `oauth:` prefix)
|
||||
- Check token has `chat:read` and `chat:write` scopes
|
||||
- If using token refresh, verify `clientSecret` and `refreshToken` are set
|
||||
@@ -265,18 +270,21 @@ openclaw channels status --probe
|
||||
### Token refresh not working
|
||||
|
||||
**Check logs for refresh events:**
|
||||
|
||||
```
|
||||
Using env token source for mybot
|
||||
Access token refreshed for user 123456 (expires in 14400s)
|
||||
```
|
||||
|
||||
If you see "token refresh disabled (no refresh token)":
|
||||
|
||||
- Ensure `clientSecret` is provided
|
||||
- Ensure `refreshToken` is provided
|
||||
|
||||
## Config
|
||||
|
||||
**Account config:**
|
||||
|
||||
- `username` - Bot username
|
||||
- `accessToken` - OAuth access token with `chat:read` and `chat:write`
|
||||
- `clientId` - Twitch Client ID (from Token Generator or your app)
|
||||
@@ -291,6 +299,7 @@ If you see "token refresh disabled (no refresh token)":
|
||||
- `requireMention` - Require @mention (default: `true`)
|
||||
|
||||
**Provider options:**
|
||||
|
||||
- `channels.twitch.enabled` - Enable/disable channel startup
|
||||
- `channels.twitch.username` - Bot username (simplified single-account config)
|
||||
- `channels.twitch.accessToken` - OAuth access token (simplified single-account config)
|
||||
@@ -325,28 +334,29 @@ Full example:
|
||||
expiresIn: 14400,
|
||||
obtainmentTimestamp: 1706092800000,
|
||||
allowFrom: ["123456789", "987654321"],
|
||||
allowedRoles: ["moderator"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
allowedRoles: ["moderator"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Tool actions
|
||||
|
||||
The agent can call `twitch` with action:
|
||||
|
||||
- `send` - Send a message to a channel
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
"action": "twitch",
|
||||
"params": {
|
||||
"message": "Hello Twitch!",
|
||||
"to": "#mychannel"
|
||||
}
|
||||
action: "twitch",
|
||||
params: {
|
||||
message: "Hello Twitch!",
|
||||
to: "#mychannel",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -3,45 +3,51 @@ summary: "WhatsApp (web channel) integration: login, inbox, replies, media, and
|
||||
read_when:
|
||||
- Working on WhatsApp/web channel behavior or inbox routing
|
||||
---
|
||||
# WhatsApp (web channel)
|
||||
|
||||
# WhatsApp (web channel)
|
||||
|
||||
Status: WhatsApp Web via Baileys only. Gateway owns the session(s).
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Use a **separate phone number** if possible (recommended).
|
||||
2) Configure WhatsApp in `~/.openclaw/openclaw.json`.
|
||||
3) Run `openclaw channels login` to scan the QR code (Linked Devices).
|
||||
4) Start the gateway.
|
||||
|
||||
1. Use a **separate phone number** if possible (recommended).
|
||||
2. Configure WhatsApp in `~/.openclaw/openclaw.json`.
|
||||
3. Run `openclaw channels login` to scan the QR code (Linked Devices).
|
||||
4. Start the gateway.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["+15551234567"]
|
||||
}
|
||||
}
|
||||
allowFrom: ["+15551234567"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Goals
|
||||
|
||||
- Multiple WhatsApp accounts (multi-account) in one Gateway process.
|
||||
- Deterministic routing: replies return to WhatsApp, no model routing.
|
||||
- Model sees enough context to understand quoted replies.
|
||||
|
||||
## Config writes
|
||||
|
||||
By default, WhatsApp is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||
|
||||
Disable with:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: { whatsapp: { configWrites: false } }
|
||||
channels: { whatsapp: { configWrites: false } },
|
||||
}
|
||||
```
|
||||
|
||||
## Architecture (who owns what)
|
||||
|
||||
- **Gateway** owns the Baileys socket and inbox loop.
|
||||
- **CLI / macOS app** talk to the gateway; no direct Baileys use.
|
||||
- **Active listener** is required for outbound sends; otherwise send fails fast.
|
||||
@@ -51,19 +57,21 @@ Disable with:
|
||||
WhatsApp requires a real mobile number for verification. VoIP and virtual numbers are usually blocked. There are two supported ways to run OpenClaw on WhatsApp:
|
||||
|
||||
### Dedicated number (recommended)
|
||||
|
||||
Use a **separate phone number** for OpenClaw. Best UX, clean routing, no self-chat quirks. Ideal setup: **spare/old Android phone + eSIM**. Leave it on Wi‑Fi and power, and link it via QR.
|
||||
|
||||
**WhatsApp Business:** You can use WhatsApp Business on the same device with a different number. Great for keeping your personal WhatsApp separate — install WhatsApp Business and register the OpenClaw number there.
|
||||
|
||||
**Sample config (dedicated number, single-user allowlist):**
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["+15551234567"]
|
||||
}
|
||||
}
|
||||
allowFrom: ["+15551234567"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -72,10 +80,12 @@ If you want pairing instead of allowlist, set `channels.whatsapp.dmPolicy` to `p
|
||||
`openclaw pairing approve whatsapp <code>`
|
||||
|
||||
### Personal number (fallback)
|
||||
|
||||
Quick fallback: run OpenClaw on **your own number**. Message yourself (WhatsApp “Message yourself”) for testing so you don’t spam contacts. Expect to read verification codes on your main phone during setup and experiments. **Must enable self-chat mode.**
|
||||
When the wizard asks for your personal WhatsApp number, enter the phone you will message from (the owner/sender), not the assistant number.
|
||||
|
||||
**Sample config (personal number, self-chat):**
|
||||
|
||||
```json
|
||||
{
|
||||
"whatsapp": {
|
||||
@@ -91,6 +101,7 @@ if `messages.responsePrefix` is unset. Set it explicitly to customize or disable
|
||||
the prefix (use `""` to remove it).
|
||||
|
||||
### Number sourcing tips
|
||||
|
||||
- **Local eSIM** from your country's mobile carrier (most reliable)
|
||||
- Austria: [hot.at](https://www.hot.at)
|
||||
- UK: [giffgaff](https://www.giffgaff.com) — free SIM, no contract
|
||||
@@ -101,6 +112,7 @@ the prefix (use `""` to remove it).
|
||||
**Tip:** The number only needs to receive one verification SMS. After that, WhatsApp Web sessions persist via `creds.json`.
|
||||
|
||||
## Why Not Twilio?
|
||||
|
||||
- Early OpenClaw builds supported Twilio’s WhatsApp Business integration.
|
||||
- WhatsApp Business numbers are a poor fit for a personal assistant.
|
||||
- Meta enforces a 24‑hour reply window; if you haven’t responded in the last 24 hours, the business number can’t initiate new messages.
|
||||
@@ -108,6 +120,7 @@ the prefix (use `""` to remove it).
|
||||
- Result: unreliable delivery and frequent blocks, so support was removed.
|
||||
|
||||
## Login + credentials
|
||||
|
||||
- Login command: `openclaw channels login` (QR via Linked Devices).
|
||||
- Multi-account login: `openclaw channels login --account <id>` (`<id>` = `accountId`).
|
||||
- Default account (when `--account` is omitted): `default` if present, otherwise the first configured account id (sorted).
|
||||
@@ -118,6 +131,7 @@ the prefix (use `""` to remove it).
|
||||
- Logged-out socket => error instructs re-link.
|
||||
|
||||
## Inbound flow (DM + group)
|
||||
|
||||
- WhatsApp events come from `messages.upsert` (Baileys).
|
||||
- Inbox listeners are detached on shutdown to avoid accumulating event handlers in tests/restarts.
|
||||
- Status/broadcast chats are ignored.
|
||||
@@ -128,38 +142,44 @@ the prefix (use `""` to remove it).
|
||||
- Your linked WhatsApp number is implicitly trusted, so self messages skip `channels.whatsapp.dmPolicy` and `channels.whatsapp.allowFrom` checks.
|
||||
|
||||
### Personal-number mode (fallback)
|
||||
|
||||
If you run OpenClaw on your **personal WhatsApp number**, enable `channels.whatsapp.selfChatMode` (see sample above).
|
||||
|
||||
Behavior:
|
||||
|
||||
- Outbound DMs never trigger pairing replies (prevents spamming contacts).
|
||||
- Inbound unknown senders still follow `channels.whatsapp.dmPolicy`.
|
||||
- Self-chat mode (allowFrom includes your number) avoids auto read receipts and ignores mention JIDs.
|
||||
- Read receipts sent for non-self-chat DMs.
|
||||
|
||||
## Read receipts
|
||||
|
||||
By default, the gateway marks inbound WhatsApp messages as read (blue ticks) once they are accepted.
|
||||
|
||||
Disable globally:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: { whatsapp: { sendReadReceipts: false } }
|
||||
channels: { whatsapp: { sendReadReceipts: false } },
|
||||
}
|
||||
```
|
||||
|
||||
Disable per account:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
accounts: {
|
||||
personal: { sendReadReceipts: false }
|
||||
}
|
||||
}
|
||||
}
|
||||
personal: { sendReadReceipts: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Self-chat mode always skips read receipts.
|
||||
|
||||
## WhatsApp FAQ: sending messages + pairing
|
||||
@@ -169,6 +189,7 @@ No. Default DM policy is **pairing**, so unknown senders only get a pairing code
|
||||
|
||||
**How does pairing work on WhatsApp?**
|
||||
Pairing is a DM gate for unknown senders:
|
||||
|
||||
- First DM from a new sender returns a short code (message is not processed).
|
||||
- Approve with: `openclaw pairing approve whatsapp <code>` (list with `openclaw pairing list whatsapp`).
|
||||
- Codes expire after 1 hour; pending requests are capped at 3 per channel.
|
||||
@@ -180,6 +201,7 @@ Yes, by routing each sender to a different agent via `bindings` (peer `kind: "dm
|
||||
The wizard uses it to set your **allowlist/owner** so your own DMs are permitted. It’s not used for auto-sending. If you run on your personal WhatsApp number, use that same number and enable `channels.whatsapp.selfChatMode`.
|
||||
|
||||
## Message normalization (what the model sees)
|
||||
|
||||
- `Body` is the current message body with envelope.
|
||||
- Quoted reply context is **always appended**:
|
||||
```
|
||||
@@ -195,6 +217,7 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
|
||||
- `<media:image|video|audio|document|sticker>`
|
||||
|
||||
## Groups
|
||||
|
||||
- Groups map to `agent:<agentId>:whatsapp:group:<jid>` sessions.
|
||||
- Group policy: `channels.whatsapp.groupPolicy = open|disabled|allowlist` (default `allowlist`).
|
||||
- Activation modes:
|
||||
@@ -203,7 +226,7 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
|
||||
- `/activation mention|always` is owner-only and must be sent as a standalone message.
|
||||
- Owner = `channels.whatsapp.allowFrom` (or self E.164 if unset).
|
||||
- **History injection** (pending-only):
|
||||
- Recent *unprocessed* messages (default 50) inserted under:
|
||||
- Recent _unprocessed_ messages (default 50) inserted under:
|
||||
`[Chat messages since your last reply - for context]` (messages already in the session are not re-injected)
|
||||
- Current message under:
|
||||
`[Current message - respond to this]`
|
||||
@@ -211,6 +234,7 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
|
||||
- Group metadata cached 5 min (subject + participants).
|
||||
|
||||
## Reply delivery (threading)
|
||||
|
||||
- WhatsApp Web sends standard messages (no quoted reply threading in the current gateway).
|
||||
- Reply tags are ignored on this channel.
|
||||
|
||||
@@ -219,6 +243,7 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
|
||||
WhatsApp can automatically send emoji reactions to incoming messages immediately upon receipt, before the bot generates a reply. This provides instant feedback to users that their message was received.
|
||||
|
||||
**Configuration:**
|
||||
|
||||
```json
|
||||
{
|
||||
"whatsapp": {
|
||||
@@ -232,6 +257,7 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
- `emoji` (string): Emoji to use for acknowledgment (e.g., "👀", "✅", "📨"). Empty or omitted = feature disabled.
|
||||
- `direct` (boolean, default: `true`): Send reactions in direct/DM chats.
|
||||
- `group` (string, default: `"mentions"`): Group chat behavior:
|
||||
@@ -240,6 +266,7 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately
|
||||
- `"never"`: Never react in groups
|
||||
|
||||
**Per-account override:**
|
||||
|
||||
```json
|
||||
{
|
||||
"whatsapp": {
|
||||
@@ -257,6 +284,7 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately
|
||||
```
|
||||
|
||||
**Behavior notes:**
|
||||
|
||||
- Reactions are sent **immediately** upon message receipt, before typing indicators or bot replies.
|
||||
- In groups with `requireMention: false` (activation: always), `group: "mentions"` will react to all messages (not just @mentions).
|
||||
- Fire-and-forget: reaction failures are logged but don't prevent the bot from replying.
|
||||
@@ -264,18 +292,21 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately
|
||||
- WhatsApp ignores `messages.ackReaction`; use `channels.whatsapp.ackReaction` instead.
|
||||
|
||||
## Agent tool (reactions)
|
||||
|
||||
- Tool: `whatsapp` with `react` action (`chatJid`, `messageId`, `emoji`, optional `remove`).
|
||||
- Optional: `participant` (group sender), `fromMe` (reacting to your own message), `accountId` (multi-account).
|
||||
- Reaction removal semantics: see [/tools/reactions](/tools/reactions).
|
||||
- Tool gating: `channels.whatsapp.actions.reactions` (default: enabled).
|
||||
|
||||
## Limits
|
||||
|
||||
- Outbound text is chunked to `channels.whatsapp.textChunkLimit` (default 4000).
|
||||
- Optional newline chunking: set `channels.whatsapp.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- Inbound media saves are capped by `channels.whatsapp.mediaMaxMb` (default 50 MB).
|
||||
- Outbound media items are capped by `agents.defaults.mediaMaxMb` (default 5 MB).
|
||||
|
||||
## Outbound send (text + media)
|
||||
|
||||
- Uses active web listener; error if gateway not running.
|
||||
- Text chunking: 4k max per message (configurable via `channels.whatsapp.textChunkLimit`, optional `channels.whatsapp.chunkMode`).
|
||||
- Media:
|
||||
@@ -288,17 +319,21 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately
|
||||
- Gateway: `send` params include `gifPlayback: true`
|
||||
|
||||
## Voice notes (PTT audio)
|
||||
|
||||
WhatsApp sends audio as **voice notes** (PTT bubble).
|
||||
|
||||
- Best results: OGG/Opus. OpenClaw rewrites `audio/ogg` to `audio/ogg; codecs=opus`.
|
||||
- `[[audio_as_voice]]` is ignored for WhatsApp (audio already ships as voice note).
|
||||
|
||||
## Media limits + optimization
|
||||
|
||||
- Default outbound cap: 5 MB (per media item).
|
||||
- Override: `agents.defaults.mediaMaxMb`.
|
||||
- Images are auto-optimized to JPEG under cap (resize + quality sweep).
|
||||
- Oversize media => error; media reply falls back to text warning.
|
||||
|
||||
## Heartbeats
|
||||
|
||||
- **Gateway heartbeat** logs connection health (`web.heartbeatSeconds`, default 60s).
|
||||
- **Agent heartbeat** can be configured per agent (`agents.list[].heartbeat`) or globally
|
||||
via `agents.defaults.heartbeat` (fallback when no per-agent entries are set).
|
||||
@@ -306,12 +341,14 @@ WhatsApp sends audio as **voice notes** (PTT bubble).
|
||||
- Delivery defaults to the last used channel (or configured target).
|
||||
|
||||
## Reconnect behavior
|
||||
|
||||
- Backoff policy: `web.reconnect`:
|
||||
- `initialMs`, `maxMs`, `factor`, `jitter`, `maxAttempts`.
|
||||
- If maxAttempts reached, web monitoring stops (degraded).
|
||||
- Logged-out => stop and require re-link.
|
||||
|
||||
## Config quick map
|
||||
|
||||
- `channels.whatsapp.dmPolicy` (DM policy: pairing/allowlist/open/disabled).
|
||||
- `channels.whatsapp.selfChatMode` (same-phone setup; bot uses your personal WhatsApp number).
|
||||
- `channels.whatsapp.allowFrom` (DM allowlist). WhatsApp uses E.164 phone numbers (no usernames).
|
||||
@@ -343,6 +380,7 @@ WhatsApp sends audio as **voice notes** (PTT bubble).
|
||||
- `web.reconnect.*`
|
||||
|
||||
## Logs + troubleshooting
|
||||
|
||||
- Subsystems: `whatsapp/inbound`, `whatsapp/outbound`, `web-heartbeat`, `web-reconnect`.
|
||||
- Log file: `/tmp/openclaw/openclaw-YYYY-MM-DD.log` (configurable).
|
||||
- Troubleshooting guide: [Gateway troubleshooting](/gateway/troubleshooting).
|
||||
@@ -350,13 +388,16 @@ WhatsApp sends audio as **voice notes** (PTT bubble).
|
||||
## Troubleshooting (quick)
|
||||
|
||||
**Not linked / QR login required**
|
||||
|
||||
- Symptom: `channels status` shows `linked: false` or warns “Not linked”.
|
||||
- Fix: run `openclaw channels login` on the gateway host and scan the QR (WhatsApp → Settings → Linked Devices).
|
||||
|
||||
**Linked but disconnected / reconnect loop**
|
||||
|
||||
- Symptom: `channels status` shows `running, disconnected` or warns “Linked but disconnected”.
|
||||
- Fix: `openclaw doctor` (or restart the gateway). If it persists, relink via `channels login` and inspect `openclaw logs --follow`.
|
||||
|
||||
**Bun runtime**
|
||||
|
||||
- Bun is **not recommended**. WhatsApp (Baileys) and Telegram are unreliable on Bun.
|
||||
Run the gateway with **Node**. (See Getting Started runtime note.)
|
||||
|
||||
@@ -3,43 +3,50 @@ summary: "Zalo bot support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Zalo features or webhooks
|
||||
---
|
||||
|
||||
# Zalo (Bot API)
|
||||
|
||||
Status: experimental. Direct messages only; groups coming soon per Zalo docs.
|
||||
|
||||
## Plugin required
|
||||
|
||||
Zalo ships as a plugin and is not bundled with the core install.
|
||||
|
||||
- Install via CLI: `openclaw plugins install @openclaw/zalo`
|
||||
- Or select **Zalo** during onboarding and confirm the install prompt
|
||||
- Details: [Plugins](/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Install the Zalo plugin:
|
||||
|
||||
1. Install the Zalo plugin:
|
||||
- From a source checkout: `openclaw plugins install ./extensions/zalo`
|
||||
- From npm (if published): `openclaw plugins install @openclaw/zalo`
|
||||
- Or pick **Zalo** in onboarding and confirm the install prompt
|
||||
2) Set the token:
|
||||
2. Set the token:
|
||||
- Env: `ZALO_BOT_TOKEN=...`
|
||||
- Or config: `channels.zalo.botToken: "..."`.
|
||||
3) Restart the gateway (or finish onboarding).
|
||||
4) DM access is pairing by default; approve the pairing code on first contact.
|
||||
3. Restart the gateway (or finish onboarding).
|
||||
4. DM access is pairing by default; approve the pairing code on first contact.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalo: {
|
||||
enabled: true,
|
||||
botToken: "12345689:abc-xyz",
|
||||
dmPolicy: "pairing"
|
||||
}
|
||||
}
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## What it is
|
||||
|
||||
Zalo is a Vietnam-focused messaging app; its Bot API lets the Gateway run a bot for 1:1 conversations.
|
||||
It is a good fit for support or notifications where you want deterministic routing back to Zalo.
|
||||
|
||||
- A Zalo Bot API channel owned by the Gateway.
|
||||
- Deterministic routing: replies go back to Zalo; the model never chooses channels.
|
||||
- DMs share the agent's main session.
|
||||
@@ -48,11 +55,13 @@ It is a good fit for support or notifications where you want deterministic routi
|
||||
## Setup (fast path)
|
||||
|
||||
### 1) Create a bot token (Zalo Bot Platform)
|
||||
1) Go to **https://bot.zaloplatforms.com** and sign in.
|
||||
2) Create a new bot and configure its settings.
|
||||
3) Copy the bot token (format: `12345689:abc-xyz`).
|
||||
|
||||
1. Go to **https://bot.zaloplatforms.com** and sign in.
|
||||
2. Create a new bot and configure its settings.
|
||||
3. Copy the bot token (format: `12345689:abc-xyz`).
|
||||
|
||||
### 2) Configure the token (env or config)
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
@@ -61,9 +70,9 @@ Example:
|
||||
zalo: {
|
||||
enabled: true,
|
||||
botToken: "12345689:abc-xyz",
|
||||
dmPolicy: "pairing"
|
||||
}
|
||||
}
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -71,15 +80,17 @@ Env option: `ZALO_BOT_TOKEN=...` (works for the default account only).
|
||||
|
||||
Multi-account support: use `channels.zalo.accounts` with per-account tokens and optional `name`.
|
||||
|
||||
3) Restart the gateway. Zalo starts when a token is resolved (env or config).
|
||||
4) DM access defaults to pairing. Approve the code when the bot is first contacted.
|
||||
3. Restart the gateway. Zalo starts when a token is resolved (env or config).
|
||||
4. DM access defaults to pairing. Approve the code when the bot is first contacted.
|
||||
|
||||
## How it works (behavior)
|
||||
|
||||
- Inbound messages are normalized into the shared channel envelope with media placeholders.
|
||||
- Replies always route back to the same Zalo chat.
|
||||
- Long-polling by default; webhook mode available with `channels.zalo.webhookUrl`.
|
||||
|
||||
## Limits
|
||||
|
||||
- Outbound text is chunked to 2000 characters (Zalo API limit).
|
||||
- Media downloads/uploads are capped by `channels.zalo.mediaMaxMb` (default 5).
|
||||
- Streaming is blocked by default due to the 2000 char limit making streaming less useful.
|
||||
@@ -87,6 +98,7 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and
|
||||
## Access control (DMs)
|
||||
|
||||
### DM access
|
||||
|
||||
- Default: `channels.zalo.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||
- Approve via:
|
||||
- `openclaw pairing list zalo`
|
||||
@@ -95,6 +107,7 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and
|
||||
- `channels.zalo.allowFrom` accepts numeric user IDs (no username lookup available).
|
||||
|
||||
## Long-polling vs webhook
|
||||
|
||||
- Default: long-polling (no public URL required).
|
||||
- Webhook mode: set `channels.zalo.webhookUrl` and `channels.zalo.webhookSecret`.
|
||||
- The webhook secret must be 8-256 characters.
|
||||
@@ -105,44 +118,51 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and
|
||||
**Note:** getUpdates (polling) and webhook are mutually exclusive per Zalo API docs.
|
||||
|
||||
## Supported message types
|
||||
|
||||
- **Text messages**: Full support with 2000 character chunking.
|
||||
- **Image messages**: Download and process inbound images; send images via `sendPhoto`.
|
||||
- **Stickers**: Logged but not fully processed (no agent response).
|
||||
- **Unsupported types**: Logged (e.g., messages from protected users).
|
||||
|
||||
## Capabilities
|
||||
| Feature | Status |
|
||||
|---------|--------|
|
||||
| Direct messages | ✅ Supported |
|
||||
| Groups | ❌ Coming soon (per Zalo docs) |
|
||||
| Media (images) | ✅ Supported |
|
||||
| Reactions | ❌ Not supported |
|
||||
| Threads | ❌ Not supported |
|
||||
| Polls | ❌ Not supported |
|
||||
| Native commands | ❌ Not supported |
|
||||
| Streaming | ⚠️ Blocked (2000 char limit) |
|
||||
|
||||
| Feature | Status |
|
||||
| --------------- | ------------------------------ |
|
||||
| Direct messages | ✅ Supported |
|
||||
| Groups | ❌ Coming soon (per Zalo docs) |
|
||||
| Media (images) | ✅ Supported |
|
||||
| Reactions | ❌ Not supported |
|
||||
| Threads | ❌ Not supported |
|
||||
| Polls | ❌ Not supported |
|
||||
| Native commands | ❌ Not supported |
|
||||
| Streaming | ⚠️ Blocked (2000 char limit) |
|
||||
|
||||
## Delivery targets (CLI/cron)
|
||||
|
||||
- Use a chat id as the target.
|
||||
- Example: `openclaw message send --channel zalo --target 123456789 --message "hi"`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Bot doesn't respond:**
|
||||
|
||||
- Check that the token is valid: `openclaw channels status --probe`
|
||||
- Verify the sender is approved (pairing or allowFrom)
|
||||
- Check gateway logs: `openclaw logs --follow`
|
||||
|
||||
**Webhook not receiving events:**
|
||||
|
||||
- Ensure webhook URL uses HTTPS
|
||||
- Verify secret token is 8-256 characters
|
||||
- Confirm the gateway HTTP endpoint is reachable on the configured path
|
||||
- Check that getUpdates polling is not running (they're mutually exclusive)
|
||||
|
||||
## Configuration reference (Zalo)
|
||||
|
||||
Full configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
Provider options:
|
||||
|
||||
- `channels.zalo.enabled`: enable/disable channel startup.
|
||||
- `channels.zalo.botToken`: bot token from Zalo Bot Platform.
|
||||
- `channels.zalo.tokenFile`: read token from file path.
|
||||
@@ -155,6 +175,7 @@ Provider options:
|
||||
- `channels.zalo.proxy`: proxy URL for API requests.
|
||||
|
||||
Multi-account options:
|
||||
|
||||
- `channels.zalo.accounts.<id>.botToken`: per-account token.
|
||||
- `channels.zalo.accounts.<id>.tokenFile`: per-account token file.
|
||||
- `channels.zalo.accounts.<id>.name`: display name.
|
||||
|
||||
@@ -4,6 +4,7 @@ read_when:
|
||||
- Setting up Zalo Personal for OpenClaw
|
||||
- Debugging Zalo Personal login or message flow
|
||||
---
|
||||
|
||||
# Zalo Personal (unofficial)
|
||||
|
||||
Status: experimental. This integration automates a **personal Zalo account** via `zca-cli`.
|
||||
@@ -11,47 +12,54 @@ Status: experimental. This integration automates a **personal Zalo account** via
|
||||
> **Warning:** This is an unofficial integration and may result in account suspension/ban. Use at your own risk.
|
||||
|
||||
## Plugin required
|
||||
|
||||
Zalo Personal ships as a plugin and is not bundled with the core install.
|
||||
|
||||
- Install via CLI: `openclaw plugins install @openclaw/zalouser`
|
||||
- Or from a source checkout: `openclaw plugins install ./extensions/zalouser`
|
||||
- Details: [Plugins](/plugin)
|
||||
|
||||
## Prerequisite: zca-cli
|
||||
|
||||
The Gateway machine must have the `zca` binary available in `PATH`.
|
||||
|
||||
- Verify: `zca --version`
|
||||
- If missing, install zca-cli (see `extensions/zalouser/README.md` or the upstream zca-cli docs).
|
||||
|
||||
## Quick setup (beginner)
|
||||
1) Install the plugin (see above).
|
||||
2) Login (QR, on the Gateway machine):
|
||||
|
||||
1. Install the plugin (see above).
|
||||
2. Login (QR, on the Gateway machine):
|
||||
- `openclaw channels login --channel zalouser`
|
||||
- Scan the QR code in the terminal with the Zalo mobile app.
|
||||
3) Enable the channel:
|
||||
3. Enable the channel:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
zalouser: {
|
||||
enabled: true,
|
||||
dmPolicy: "pairing"
|
||||
}
|
||||
}
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
4) Restart the Gateway (or finish onboarding).
|
||||
5) DM access defaults to pairing; approve the pairing code on first contact.
|
||||
4. Restart the Gateway (or finish onboarding).
|
||||
5. DM access defaults to pairing; approve the pairing code on first contact.
|
||||
|
||||
## What it is
|
||||
|
||||
- Uses `zca listen` to receive inbound messages.
|
||||
- Uses `zca msg ...` to send replies (text/media/link).
|
||||
- Designed for “personal account” use cases where Zalo Bot API is not available.
|
||||
|
||||
## Naming
|
||||
|
||||
Channel id is `zalouser` to make it explicit this automates a **personal Zalo user account** (unofficial). We keep `zalo` reserved for a potential future official Zalo API integration.
|
||||
|
||||
## Finding IDs (directory)
|
||||
|
||||
Use the directory CLI to discover peers/groups and their IDs:
|
||||
|
||||
```bash
|
||||
@@ -61,18 +69,22 @@ openclaw directory groups list --channel zalouser --query "work"
|
||||
```
|
||||
|
||||
## Limits
|
||||
|
||||
- Outbound text is chunked to ~2000 characters (Zalo client limits).
|
||||
- Streaming is blocked by default.
|
||||
|
||||
## Access control (DMs)
|
||||
|
||||
`channels.zalouser.dmPolicy` supports: `pairing | allowlist | open | disabled` (default: `pairing`).
|
||||
`channels.zalouser.allowFrom` accepts user IDs or names. The wizard resolves names to IDs via `zca friend find` when available.
|
||||
|
||||
Approve via:
|
||||
|
||||
- `openclaw pairing list zalouser`
|
||||
- `openclaw pairing approve zalouser <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"`
|
||||
@@ -82,6 +94,7 @@ Approve via:
|
||||
- On startup, OpenClaw resolves group/user names in allowlists to IDs and logs the mapping; unresolved entries are kept as typed.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
@@ -89,14 +102,15 @@ Example:
|
||||
groupPolicy: "allowlist",
|
||||
groups: {
|
||||
"123456789": { allow: true },
|
||||
"Work Chat": { allow: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
"Work Chat": { allow: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Multi-account
|
||||
|
||||
Accounts map to zca profiles. Example:
|
||||
|
||||
```json5
|
||||
@@ -106,18 +120,20 @@ Accounts map to zca profiles. Example:
|
||||
enabled: true,
|
||||
defaultAccount: "default",
|
||||
accounts: {
|
||||
work: { enabled: true, profile: "work" }
|
||||
}
|
||||
}
|
||||
}
|
||||
work: { enabled: true, profile: "work" },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**`zca` not found:**
|
||||
|
||||
- Install zca-cli and ensure it’s on `PATH` for the Gateway process.
|
||||
|
||||
**Login doesn’t stick:**
|
||||
|
||||
- `openclaw channels status --probe`
|
||||
- Re-login: `openclaw channels logout --channel zalouser && openclaw channels login --channel zalouser`
|
||||
|
||||
@@ -110,9 +110,12 @@ To target a specific Gateway or agent:
|
||||
"command": "openclaw",
|
||||
"args": [
|
||||
"acp",
|
||||
"--url", "wss://gateway-host:18789",
|
||||
"--token", "<token>",
|
||||
"--session", "agent:design:main"
|
||||
"--url",
|
||||
"wss://gateway-host:18789",
|
||||
"--token",
|
||||
"<token>",
|
||||
"--session",
|
||||
"agent:design:main"
|
||||
],
|
||||
"env": {}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
|
||||
@@ -9,6 +9,7 @@ read_when:
|
||||
Manage isolated agents (workspaces + auth + routing).
|
||||
|
||||
Related:
|
||||
|
||||
- Multi-agent routing: [Multi-Agent Routing](/concepts/multi-agent)
|
||||
- Agent workspace: [Agent workspace](/concepts/agent-workspace)
|
||||
|
||||
@@ -25,6 +26,7 @@ openclaw agents delete work
|
||||
## Identity files
|
||||
|
||||
Each agent workspace can include an `IDENTITY.md` at the workspace root:
|
||||
|
||||
- Example path: `~/.openclaw/workspace/IDENTITY.md`
|
||||
- `set-identity --from-identity` reads from the workspace root (or an explicit `--identity-file`)
|
||||
|
||||
@@ -33,6 +35,7 @@ Avatar paths resolve relative to the workspace root.
|
||||
## Set identity
|
||||
|
||||
`set-identity` writes fields into `agents.list[].identity`:
|
||||
|
||||
- `name`
|
||||
- `theme`
|
||||
- `emoji`
|
||||
@@ -62,10 +65,10 @@ Config sample:
|
||||
name: "OpenClaw",
|
||||
theme: "space lobster",
|
||||
emoji: "🦞",
|
||||
avatar: "avatars/openclaw.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
avatar: "avatars/openclaw.png",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -11,6 +11,7 @@ Manage exec approvals for the **local host**, **gateway host**, or a **node host
|
||||
By default, commands target the local approvals file on disk. Use `--gateway` to target the gateway, or `--node` to target a specific node.
|
||||
|
||||
Related:
|
||||
|
||||
- Exec approvals: [Exec approvals](/tools/exec-approvals)
|
||||
- Nodes: [Nodes](/nodes)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ read_when:
|
||||
Manage OpenClaw’s browser control server and run browser actions (tabs, snapshots, screenshots, navigation, clicks, typing).
|
||||
|
||||
Related:
|
||||
|
||||
- Browser tool + API: [Browser tool](/tools/browser)
|
||||
- Chrome extension relay: [Chrome extension](/tools/chrome-extension)
|
||||
|
||||
@@ -34,6 +35,7 @@ openclaw browser --browser-profile openclaw snapshot
|
||||
## Profiles
|
||||
|
||||
Profiles are named browser routing configs. In practice:
|
||||
|
||||
- `openclaw`: launches/attaches to a dedicated OpenClaw-managed Chrome instance (isolated user data dir).
|
||||
- `chrome`: controls your existing Chrome tab(s) via the Chrome extension relay.
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ read_when:
|
||||
Manage chat channel accounts and their runtime status on the Gateway.
|
||||
|
||||
Related docs:
|
||||
|
||||
- Channel guides: [Channels](/channels/index)
|
||||
- Gateway configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
@@ -56,6 +57,7 @@ openclaw channels capabilities --channel discord --target channel:123
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `--channel` is optional; omit it to list every channel (including extensions).
|
||||
- `--target` accepts `channel:<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`.
|
||||
@@ -71,5 +73,6 @@ openclaw channels resolve --channel matrix "Project Room"
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Use `--kind user|group|auto` to force the target type.
|
||||
- Resolution prefers active matches when multiple entries share the same name.
|
||||
|
||||
@@ -15,10 +15,12 @@ Tip: `openclaw config` without a subcommand opens the same wizard. Use
|
||||
`openclaw config get|set|unset` for non-interactive edits.
|
||||
|
||||
Related:
|
||||
|
||||
- Gateway configuration reference: [Configuration](/gateway/configuration)
|
||||
- Config CLI: [Config](/cli/config)
|
||||
|
||||
Notes:
|
||||
|
||||
- Choosing where the Gateway runs always updates `gateway.mode`. You can select "Continue" without other sections if that is all you need.
|
||||
- Channel-oriented services (Slack/Discord/Matrix/Microsoft Teams) prompt for channel/room allowlists during setup. You can enter names or IDs; the wizard resolves names to IDs when possible.
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ read_when:
|
||||
Manage cron jobs for the Gateway scheduler.
|
||||
|
||||
Related:
|
||||
|
||||
- Cron jobs: [Cron jobs](/automation/cron-jobs)
|
||||
|
||||
Tip: run `openclaw cron --help` for the full command surface.
|
||||
|
||||
@@ -13,4 +13,3 @@ Open the Control UI using your current auth.
|
||||
openclaw dashboard
|
||||
openclaw dashboard --no-open
|
||||
```
|
||||
|
||||
|
||||
@@ -10,11 +10,13 @@ read_when:
|
||||
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.
|
||||
|
||||
@@ -10,6 +10,7 @@ read_when:
|
||||
DNS helpers for wide-area discovery (Tailscale + CoreDNS). Currently focused on macOS + Homebrew CoreDNS.
|
||||
|
||||
Related:
|
||||
|
||||
- Gateway discovery: [Discovery](/gateway/discovery)
|
||||
- Wide-area discovery config: [Configuration](/gateway/configuration)
|
||||
|
||||
|
||||
@@ -12,4 +12,3 @@ Search the live docs index.
|
||||
openclaw docs browser extension
|
||||
openclaw docs sandbox allowHostControl
|
||||
```
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ read_when:
|
||||
Health checks + quick fixes for the gateway and channels.
|
||||
|
||||
Related:
|
||||
|
||||
- Troubleshooting: [Troubleshooting](/gateway/troubleshooting)
|
||||
- Security audit: [Security](/gateway/security)
|
||||
|
||||
@@ -22,6 +23,7 @@ openclaw doctor --deep
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Interactive prompts (like keychain/OAuth fixes) only run when stdin is a TTY and `--non-interactive` is **not** set. Headless runs (cron, Telegram, no terminal) will skip prompts.
|
||||
- `--fix` (alias for `--repair`) writes a backup to `~/.openclaw/openclaw.json.bak` and drops unknown config keys, listing each removal.
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ The Gateway is OpenClaw’s WebSocket server (channels, nodes, sessions, hooks).
|
||||
Subcommands in this page live under `openclaw gateway …`.
|
||||
|
||||
Related docs:
|
||||
|
||||
- [/gateway/bonjour](/gateway/bonjour)
|
||||
- [/gateway/discovery](/gateway/discovery)
|
||||
- [/gateway/configuration](/gateway/configuration)
|
||||
@@ -32,6 +33,7 @@ openclaw gateway run
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- By default, the Gateway refuses to start unless `gateway.mode=local` is set in `~/.openclaw/openclaw.json`. Use `--allow-unconfigured` for ad-hoc/dev runs.
|
||||
- Binding beyond loopback without auth is blocked (safety guardrail).
|
||||
- `SIGUSR1` triggers an in-process restart when authorized (enable `commands.restart` or use the gateway tool/config apply/update).
|
||||
@@ -62,11 +64,13 @@ Notes:
|
||||
All query commands use WebSocket RPC.
|
||||
|
||||
Output modes:
|
||||
|
||||
- Default: human-readable (colored in TTY).
|
||||
- `--json`: machine-readable JSON (no styling/spinner).
|
||||
- `--no-color` (or `NO_COLOR=1`): disable ANSI while keeping human layout.
|
||||
|
||||
Shared options (where supported):
|
||||
|
||||
- `--url <url>`: Gateway WebSocket URL.
|
||||
- `--token <token>`: Gateway token.
|
||||
- `--password <password>`: Gateway password.
|
||||
@@ -89,6 +93,7 @@ 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.
|
||||
@@ -99,6 +104,7 @@ Options:
|
||||
### `gateway probe`
|
||||
|
||||
`gateway probe` is the “debug everything” command. It always probes:
|
||||
|
||||
- your configured remote gateway (if set), and
|
||||
- localhost (loopback) **even if remote is configured**.
|
||||
|
||||
@@ -120,11 +126,13 @@ openclaw gateway probe --ssh user@gateway-host
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `--ssh <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`
|
||||
|
||||
@@ -148,6 +156,7 @@ openclaw gateway uninstall
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `gateway install` supports `--port`, `--runtime`, `--token`, `--force`, `--json`.
|
||||
- Lifecycle commands accept `--json` for scripting.
|
||||
|
||||
@@ -161,6 +170,7 @@ Notes:
|
||||
Only gateways with Bonjour discovery enabled (default) advertise the beacon.
|
||||
|
||||
Wide-Area discovery records include (TXT):
|
||||
|
||||
- `role` (gateway role hint)
|
||||
- `transport` (transport hint, e.g. `gateway`)
|
||||
- `gatewayPort` (WebSocket port, usually `18789`)
|
||||
@@ -176,6 +186,7 @@ openclaw gateway discover
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `--timeout <ms>`: per-command timeout (browse/resolve); default `2000`.
|
||||
- `--json`: machine-readable output (also disables styling/spinner).
|
||||
|
||||
|
||||
@@ -15,5 +15,6 @@ openclaw health --verbose
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `--verbose` runs live probes and prints per-account timings when multiple accounts are configured.
|
||||
- Output includes per-agent session stores when multiple agents are configured.
|
||||
|
||||
@@ -10,6 +10,7 @@ read_when:
|
||||
Manage agent hooks (event-driven automations for commands like `/new`, `/reset`, and gateway startup).
|
||||
|
||||
Related:
|
||||
|
||||
- Hooks: [Hooks](/hooks)
|
||||
- Plugin hooks: [Plugins](/plugin#plugin-hooks)
|
||||
|
||||
@@ -22,6 +23,7 @@ openclaw hooks list
|
||||
List all discovered hooks from workspace, managed, and bundled directories.
|
||||
|
||||
**Options:**
|
||||
|
||||
- `--eligible`: Show only eligible hooks (requirements met)
|
||||
- `--json`: Output as JSON
|
||||
- `-v, --verbose`: Show detailed information including missing requirements
|
||||
@@ -63,9 +65,11 @@ openclaw hooks info <name>
|
||||
Show detailed information about a specific hook.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `<name>`: Hook name (e.g., `session-memory`)
|
||||
|
||||
**Options:**
|
||||
|
||||
- `--json`: Output as JSON
|
||||
|
||||
**Example:**
|
||||
@@ -101,6 +105,7 @@ openclaw hooks check
|
||||
Show summary of hook eligibility status (how many are ready vs. not ready).
|
||||
|
||||
**Options:**
|
||||
|
||||
- `--json`: Output as JSON
|
||||
|
||||
**Example output:**
|
||||
@@ -125,6 +130,7 @@ Enable a specific hook by adding it to your config (`~/.openclaw/config.json`).
|
||||
can’t be enabled/disabled here. Enable/disable the plugin instead.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `<name>`: Hook name (e.g., `session-memory`)
|
||||
|
||||
**Example:**
|
||||
@@ -140,11 +146,13 @@ openclaw hooks enable session-memory
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
||||
- Checks if hook exists and is eligible
|
||||
- Updates `hooks.internal.entries.<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
|
||||
@@ -156,6 +164,7 @@ openclaw hooks disable <name>
|
||||
Disable a specific hook by updating your config.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `<name>`: Hook name (e.g., `command-logger`)
|
||||
|
||||
**Example:**
|
||||
@@ -171,6 +180,7 @@ openclaw hooks disable command-logger
|
||||
```
|
||||
|
||||
**After disabling:**
|
||||
|
||||
- Restart the gateway so hooks reload
|
||||
|
||||
## Install Hooks
|
||||
@@ -182,11 +192,13 @@ openclaw hooks install <path-or-spec>
|
||||
Install a hook pack from a local folder/archive or npm.
|
||||
|
||||
**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`)
|
||||
|
||||
**Supported archives:** `.zip`, `.tgz`, `.tar.gz`, `.tar`
|
||||
@@ -217,6 +229,7 @@ openclaw hooks update --all
|
||||
Update installed hook packs (npm installs only).
|
||||
|
||||
**Options:**
|
||||
|
||||
- `--all`: Update all tracked hook packs
|
||||
- `--dry-run`: Show what would change without writing
|
||||
|
||||
|
||||
@@ -269,6 +269,7 @@ Vector search over `MEMORY.md` + `memory/*.md`:
|
||||
Chat messages support `/...` commands (text and native). See [/tools/slash-commands](/tools/slash-commands).
|
||||
|
||||
Highlights:
|
||||
|
||||
- `/status` for quick diagnostics.
|
||||
- `/config` for persisted config changes.
|
||||
- `/debug` for runtime-only config overrides (memory, not disk; requires `commands.debug: true`).
|
||||
@@ -276,9 +277,11 @@ Highlights:
|
||||
## Setup + onboarding
|
||||
|
||||
### `setup`
|
||||
|
||||
Initialize config + workspace.
|
||||
|
||||
Options:
|
||||
|
||||
- `--workspace <dir>`: agent workspace path (default `~/.openclaw/workspace`).
|
||||
- `--wizard`: run the onboarding wizard.
|
||||
- `--non-interactive`: run wizard without prompts.
|
||||
@@ -289,9 +292,11 @@ Options:
|
||||
Wizard auto-runs when any wizard flags are present (`--non-interactive`, `--mode`, `--remote-url`, `--remote-token`).
|
||||
|
||||
### `onboard`
|
||||
|
||||
Interactive wizard to set up gateway, workspace, and skills.
|
||||
|
||||
Options:
|
||||
|
||||
- `--workspace <dir>`
|
||||
- `--reset` (reset config + credentials + sessions + workspace before wizard)
|
||||
- `--non-interactive`
|
||||
@@ -332,21 +337,26 @@ Options:
|
||||
- `--json`
|
||||
|
||||
### `configure`
|
||||
|
||||
Interactive configuration wizard (models, channels, skills, gateway).
|
||||
|
||||
### `config`
|
||||
|
||||
Non-interactive config helpers (get/set/unset). Running `openclaw config` with no
|
||||
subcommand launches the wizard.
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `config get <path>`: print a config value (dot/bracket path).
|
||||
- `config set <path> <value>`: set a value (JSON5 or raw string).
|
||||
- `config unset <path>`: remove a value.
|
||||
|
||||
### `doctor`
|
||||
|
||||
Health checks + quick fixes (config + gateway + legacy services).
|
||||
|
||||
Options:
|
||||
|
||||
- `--no-workspace-suggestions`: disable workspace memory hints.
|
||||
- `--yes`: accept defaults without prompting (headless).
|
||||
- `--non-interactive`: skip prompts; apply safe migrations only.
|
||||
@@ -355,9 +365,11 @@ Options:
|
||||
## Channel helpers
|
||||
|
||||
### `channels`
|
||||
|
||||
Manage chat channel accounts (WhatsApp/Telegram/Discord/Google Chat/Slack/Mattermost (plugin)/Signal/iMessage/MS Teams).
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `channels list`: show configured channels and auth profiles.
|
||||
- `channels status`: check gateway reachability and channel health (`--probe` runs extra checks; use `openclaw health` or `openclaw status --deep` for gateway health probes).
|
||||
- Tip: `channels status` prints warnings with suggested fixes when it can detect common misconfigurations (then points you to `openclaw doctor`).
|
||||
@@ -368,24 +380,29 @@ Subcommands:
|
||||
- `channels logout`: log out of a channel session (if supported).
|
||||
|
||||
Common options:
|
||||
|
||||
- `--channel <name>`: `whatsapp|telegram|discord|googlechat|slack|mattermost|signal|imessage|msteams`
|
||||
- `--account <id>`: channel account id (default `default`)
|
||||
- `--name <label>`: display name for the account
|
||||
|
||||
`channels login` options:
|
||||
|
||||
- `--channel <channel>` (default `whatsapp`; supports `whatsapp`/`web`)
|
||||
- `--account <id>`
|
||||
- `--verbose`
|
||||
|
||||
`channels logout` options:
|
||||
|
||||
- `--channel <channel>` (default `whatsapp`)
|
||||
- `--account <id>`
|
||||
|
||||
`channels list` options:
|
||||
|
||||
- `--no-usage`: skip model provider usage/quota snapshots (OAuth/API-backed only).
|
||||
- `--json`: output JSON (includes usage unless `--no-usage` is set).
|
||||
|
||||
`channels logs` options:
|
||||
|
||||
- `--channel <name|all>` (default `all`)
|
||||
- `--lines <n>` (default `200`)
|
||||
- `--json`
|
||||
@@ -393,6 +410,7 @@ Common options:
|
||||
More detail: [/concepts/oauth](/concepts/oauth)
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
openclaw channels add --channel telegram --account alerts --name "Alerts Bot" --token $TELEGRAM_BOT_TOKEN
|
||||
openclaw channels add --channel discord --account work --name "Work Bot" --token $DISCORD_BOT_TOKEN
|
||||
@@ -402,14 +420,17 @@ openclaw status --deep
|
||||
```
|
||||
|
||||
### `skills`
|
||||
|
||||
List and inspect available skills plus readiness info.
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `skills list`: list skills (default when no subcommand).
|
||||
- `skills info <name>`: show details for one skill.
|
||||
- `skills check`: summary of ready vs missing requirements.
|
||||
|
||||
Options:
|
||||
|
||||
- `--eligible`: show only ready skills.
|
||||
- `--json`: output JSON (no styling).
|
||||
- `-v`, `--verbose`: include missing requirements detail.
|
||||
@@ -417,33 +438,41 @@ Options:
|
||||
Tip: use `npx clawhub` to search, install, and sync skills.
|
||||
|
||||
### `pairing`
|
||||
|
||||
Approve DM pairing requests across channels.
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `pairing list <channel> [--json]`
|
||||
- `pairing approve <channel> <code> [--notify]`
|
||||
|
||||
### `webhooks gmail`
|
||||
|
||||
Gmail Pub/Sub hook setup + runner. See [/automation/gmail-pubsub](/automation/gmail-pubsub).
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `webhooks gmail setup` (requires `--account <email>`; supports `--project`, `--topic`, `--subscription`, `--label`, `--hook-url`, `--hook-token`, `--push-token`, `--bind`, `--port`, `--path`, `--include-body`, `--max-bytes`, `--renew-minutes`, `--tailscale`, `--tailscale-path`, `--tailscale-target`, `--push-endpoint`, `--json`)
|
||||
- `webhooks gmail run` (runtime overrides for the same flags)
|
||||
|
||||
### `dns setup`
|
||||
|
||||
Wide-area discovery DNS helper (CoreDNS + Tailscale). See [/gateway/discovery](/gateway/discovery).
|
||||
|
||||
Options:
|
||||
|
||||
- `--apply`: install/update CoreDNS config (requires sudo; macOS only).
|
||||
|
||||
## Messaging + agent
|
||||
|
||||
### `message`
|
||||
|
||||
Unified outbound messaging + channel actions.
|
||||
|
||||
See: [/cli/message](/cli/message)
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `message send|poll|react|reactions|read|edit|delete|pin|unpin|pins|permissions|search|timeout|kick|ban`
|
||||
- `message thread <create|list|reply>`
|
||||
- `message emoji <list|upload>`
|
||||
@@ -455,16 +484,20 @@ Subcommands:
|
||||
- `message event <list|create>`
|
||||
|
||||
Examples:
|
||||
|
||||
- `openclaw message send --target +15555550123 --message "Hi"`
|
||||
- `openclaw message poll --channel discord --target channel:123 --poll-question "Snack?" --poll-option Pizza --poll-option Sushi`
|
||||
|
||||
### `agent`
|
||||
|
||||
Run one agent turn via the Gateway (or `--local` embedded).
|
||||
|
||||
Required:
|
||||
|
||||
- `--message <text>`
|
||||
|
||||
Options:
|
||||
|
||||
- `--to <dest>` (for session key and optional delivery)
|
||||
- `--session-id <id>`
|
||||
- `--thinking <off|minimal|low|medium|high|xhigh>` (GPT-5.2 + Codex models only)
|
||||
@@ -476,19 +509,24 @@ Options:
|
||||
- `--timeout <seconds>`
|
||||
|
||||
### `agents`
|
||||
|
||||
Manage isolated agents (workspaces + auth + routing).
|
||||
|
||||
#### `agents list`
|
||||
|
||||
List configured agents.
|
||||
|
||||
Options:
|
||||
|
||||
- `--json`
|
||||
- `--bindings`
|
||||
|
||||
#### `agents add [name]`
|
||||
|
||||
Add a new isolated agent. Runs the guided wizard unless flags (or `--non-interactive`) are passed; `--workspace` is required in non-interactive mode.
|
||||
|
||||
Options:
|
||||
|
||||
- `--workspace <dir>`
|
||||
- `--model <id>`
|
||||
- `--agent-dir <dir>`
|
||||
@@ -499,21 +537,26 @@ Options:
|
||||
Binding specs use `channel[:accountId]`. When `accountId` is omitted for WhatsApp, the default account id is used.
|
||||
|
||||
#### `agents delete <id>`
|
||||
|
||||
Delete an agent and prune its workspace + state.
|
||||
|
||||
Options:
|
||||
|
||||
- `--force`
|
||||
- `--json`
|
||||
|
||||
### `acp`
|
||||
|
||||
Run the ACP bridge that connects IDEs to the Gateway.
|
||||
|
||||
See [`acp`](/cli/acp) for full options and examples.
|
||||
|
||||
### `status`
|
||||
|
||||
Show linked session health and recent recipients.
|
||||
|
||||
Options:
|
||||
|
||||
- `--json`
|
||||
- `--all` (full diagnosis; read-only, pasteable)
|
||||
- `--deep` (probe channels)
|
||||
@@ -523,34 +566,42 @@ Options:
|
||||
- `--debug` (alias for `--verbose`)
|
||||
|
||||
Notes:
|
||||
|
||||
- Overview includes Gateway + node host service status when available.
|
||||
|
||||
### Usage tracking
|
||||
|
||||
OpenClaw can surface provider usage/quota when OAuth/API creds are available.
|
||||
|
||||
Surfaces:
|
||||
|
||||
- `/status` (adds a short provider usage line when available)
|
||||
- `openclaw status --usage` (prints full provider breakdown)
|
||||
- macOS menu bar (Usage section under Context)
|
||||
|
||||
Notes:
|
||||
|
||||
- Data comes directly from provider usage endpoints (no estimates).
|
||||
- Providers: Anthropic, GitHub Copilot, OpenAI Codex OAuth, plus Gemini CLI/Antigravity when those provider plugins are enabled.
|
||||
- If no matching credentials exist, usage is hidden.
|
||||
- Details: see [Usage tracking](/concepts/usage-tracking).
|
||||
|
||||
### `health`
|
||||
|
||||
Fetch health from the running Gateway.
|
||||
|
||||
Options:
|
||||
|
||||
- `--json`
|
||||
- `--timeout <ms>`
|
||||
- `--verbose`
|
||||
|
||||
### `sessions`
|
||||
|
||||
List stored conversation sessions.
|
||||
|
||||
Options:
|
||||
|
||||
- `--json`
|
||||
- `--verbose`
|
||||
- `--store <path>`
|
||||
@@ -559,21 +610,26 @@ Options:
|
||||
## Reset / Uninstall
|
||||
|
||||
### `reset`
|
||||
|
||||
Reset local config/state (keeps the CLI installed).
|
||||
|
||||
Options:
|
||||
|
||||
- `--scope <config|config+creds+sessions|full>`
|
||||
- `--yes`
|
||||
- `--non-interactive`
|
||||
- `--dry-run`
|
||||
|
||||
Notes:
|
||||
|
||||
- `--non-interactive` requires `--scope` and `--yes`.
|
||||
|
||||
### `uninstall`
|
||||
|
||||
Uninstall the gateway service + local data (CLI remains).
|
||||
|
||||
Options:
|
||||
|
||||
- `--service`
|
||||
- `--state`
|
||||
- `--workspace`
|
||||
@@ -584,14 +640,17 @@ Options:
|
||||
- `--dry-run`
|
||||
|
||||
Notes:
|
||||
|
||||
- `--non-interactive` requires `--yes` and explicit scopes (or `--all`).
|
||||
|
||||
## Gateway
|
||||
|
||||
### `gateway`
|
||||
|
||||
Run the WebSocket Gateway.
|
||||
|
||||
Options:
|
||||
|
||||
- `--port <port>`
|
||||
- `--bind <loopback|tailnet|lan|auto|custom>`
|
||||
- `--token <token>`
|
||||
@@ -611,9 +670,11 @@ Options:
|
||||
- `--raw-stream-path <path>`
|
||||
|
||||
### `gateway service`
|
||||
|
||||
Manage the Gateway service (launchd/systemd/schtasks).
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `gateway status` (probes the Gateway RPC by default)
|
||||
- `gateway install` (service install)
|
||||
- `gateway uninstall`
|
||||
@@ -622,6 +683,7 @@ Subcommands:
|
||||
- `gateway restart`
|
||||
|
||||
Notes:
|
||||
|
||||
- `gateway status` probes the Gateway RPC by default using the service’s resolved port/config (override with `--url/--token/--password`).
|
||||
- `gateway status` supports `--no-probe`, `--deep`, and `--json` for scripting.
|
||||
- `gateway status` also surfaces legacy or extra gateway services when it can detect them (`--deep` adds system-level scans). Profile-named OpenClaw services are treated as first-class and aren't flagged as "extra".
|
||||
@@ -631,13 +693,16 @@ Notes:
|
||||
- `gateway install` options: `--port`, `--runtime`, `--token`, `--force`, `--json`.
|
||||
|
||||
### `logs`
|
||||
|
||||
Tail Gateway file logs via RPC.
|
||||
|
||||
Notes:
|
||||
|
||||
- TTY sessions render a colorized, structured view; non-TTY falls back to plain text.
|
||||
- `--json` emits line-delimited JSON (one log event per line).
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
openclaw logs --follow
|
||||
openclaw logs --limit 200
|
||||
@@ -647,9 +712,11 @@ openclaw logs --no-color
|
||||
```
|
||||
|
||||
### `gateway <subcommand>`
|
||||
|
||||
Gateway CLI helpers (use `--url`, `--token`, `--password`, `--timeout`, `--expect-final` for RPC subcommands).
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `gateway call <method> [--params <json>]`
|
||||
- `gateway health`
|
||||
- `gateway status`
|
||||
@@ -659,6 +726,7 @@ Subcommands:
|
||||
- `gateway run`
|
||||
|
||||
Common RPCs:
|
||||
|
||||
- `config.apply` (validate + write config + restart + wake)
|
||||
- `config.patch` (merge a partial update + restart + wake)
|
||||
- `update.run` (run update + restart + wake)
|
||||
@@ -679,14 +747,18 @@ openclaw models status
|
||||
```
|
||||
|
||||
### `models` (root)
|
||||
|
||||
`openclaw models` is an alias for `models status`.
|
||||
|
||||
Root options:
|
||||
|
||||
- `--status-json` (alias for `models status --json`)
|
||||
- `--status-plain` (alias for `models status --plain`)
|
||||
|
||||
### `models list`
|
||||
|
||||
Options:
|
||||
|
||||
- `--all`
|
||||
- `--local`
|
||||
- `--provider <name>`
|
||||
@@ -694,7 +766,9 @@ Options:
|
||||
- `--plain`
|
||||
|
||||
### `models status`
|
||||
|
||||
Options:
|
||||
|
||||
- `--json`
|
||||
- `--plain`
|
||||
- `--check` (exit 1=expired/missing, 2=expiring)
|
||||
@@ -709,33 +783,43 @@ Always includes the auth overview and OAuth expiry status for profiles in the au
|
||||
`--probe` runs live requests (may consume tokens and trigger rate limits).
|
||||
|
||||
### `models set <model>`
|
||||
|
||||
Set `agents.defaults.model.primary`.
|
||||
|
||||
### `models set-image <model>`
|
||||
|
||||
Set `agents.defaults.imageModel.primary`.
|
||||
|
||||
### `models aliases list|add|remove`
|
||||
|
||||
Options:
|
||||
|
||||
- `list`: `--json`, `--plain`
|
||||
- `add <alias> <model>`
|
||||
- `remove <alias>`
|
||||
|
||||
### `models fallbacks list|add|remove|clear`
|
||||
|
||||
Options:
|
||||
|
||||
- `list`: `--json`, `--plain`
|
||||
- `add <model>`
|
||||
- `remove <model>`
|
||||
- `clear`
|
||||
|
||||
### `models image-fallbacks list|add|remove|clear`
|
||||
|
||||
Options:
|
||||
|
||||
- `list`: `--json`, `--plain`
|
||||
- `add <model>`
|
||||
- `remove <model>`
|
||||
- `clear`
|
||||
|
||||
### `models scan`
|
||||
|
||||
Options:
|
||||
|
||||
- `--min-params <b>`
|
||||
- `--max-age-days <days>`
|
||||
- `--provider <name>`
|
||||
@@ -750,13 +834,17 @@ Options:
|
||||
- `--json`
|
||||
|
||||
### `models auth add|setup-token|paste-token`
|
||||
|
||||
Options:
|
||||
|
||||
- `add`: interactive auth helper
|
||||
- `setup-token`: `--provider <name>` (default `anthropic`), `--yes`
|
||||
- `paste-token`: `--provider <name>`, `--profile-id <id>`, `--expires-in <duration>`
|
||||
|
||||
### `models auth order get|set|clear`
|
||||
|
||||
Options:
|
||||
|
||||
- `get`: `--provider <name>`, `--agent <id>`, `--json`
|
||||
- `set`: `--provider <name>`, `--agent <id>`, `<profileIds...>`
|
||||
- `clear`: `--provider <name>`, `--agent <id>`
|
||||
@@ -764,34 +852,43 @@ Options:
|
||||
## System
|
||||
|
||||
### `system event`
|
||||
|
||||
Enqueue a system event and optionally trigger a heartbeat (Gateway RPC).
|
||||
|
||||
Required:
|
||||
|
||||
- `--text <text>`
|
||||
|
||||
Options:
|
||||
|
||||
- `--mode <now|next-heartbeat>`
|
||||
- `--json`
|
||||
- `--url`, `--token`, `--timeout`, `--expect-final`
|
||||
|
||||
### `system heartbeat last|enable|disable`
|
||||
|
||||
Heartbeat controls (Gateway RPC).
|
||||
|
||||
Options:
|
||||
|
||||
- `--json`
|
||||
- `--url`, `--token`, `--timeout`, `--expect-final`
|
||||
|
||||
### `system presence`
|
||||
|
||||
List system presence entries (Gateway RPC).
|
||||
|
||||
Options:
|
||||
|
||||
- `--json`
|
||||
- `--url`, `--token`, `--timeout`, `--expect-final`
|
||||
|
||||
## Cron
|
||||
|
||||
Manage scheduled jobs (Gateway RPC). See [/automation/cron-jobs](/automation/cron-jobs).
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `cron status [--json]`
|
||||
- `cron list [--all] [--json]` (table output by default; use `--json` for raw)
|
||||
- `cron add` (alias: `create`; requires `--name` and exactly one of `--at` | `--every` | `--cron`, and exactly one payload of `--system-event` | `--message`)
|
||||
@@ -810,6 +907,7 @@ All `cron` commands accept `--url`, `--token`, `--timeout`, `--expect-final`.
|
||||
[`openclaw node`](/cli/node).
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `node run --host <gateway-host> --port 18789`
|
||||
- `node status`
|
||||
- `node install [--host <gateway-host>] [--port <port>] [--tls] [--tls-fingerprint <sha256>] [--node-id <id>] [--display-name <name>] [--runtime <node|bun>] [--force]`
|
||||
@@ -822,9 +920,11 @@ Subcommands:
|
||||
`nodes` talks to the Gateway and targets paired nodes. See [/nodes](/nodes).
|
||||
|
||||
Common options:
|
||||
|
||||
- `--url`, `--token`, `--timeout`, `--json`
|
||||
|
||||
Subcommands:
|
||||
|
||||
- `nodes status [--connected] [--last-connected <duration>]`
|
||||
- `nodes describe --node <id|name|ip>`
|
||||
- `nodes list [--connected] [--last-connected <duration>]`
|
||||
@@ -837,11 +937,13 @@ Subcommands:
|
||||
- `nodes notify --node <id|name|ip> [--title <text>] [--body <text>] [--sound <name>] [--priority <passive|active|timeSensitive>] [--delivery <system|overlay|auto>] [--invoke-timeout <ms>]` (mac only)
|
||||
|
||||
Camera:
|
||||
|
||||
- `nodes camera list --node <id|name|ip>`
|
||||
- `nodes camera snap --node <id|name|ip> [--facing front|back|both] [--device-id <id>] [--max-width <px>] [--quality <0-1>] [--delay-ms <ms>] [--invoke-timeout <ms>]`
|
||||
- `nodes camera clip --node <id|name|ip> [--facing front|back] [--device-id <id>] [--duration <ms|10s|1m>] [--no-audio] [--invoke-timeout <ms>]`
|
||||
|
||||
Canvas + screen:
|
||||
|
||||
- `nodes canvas snapshot --node <id|name|ip> [--format png|jpg|jpeg] [--max-width <px>] [--quality <0-1>] [--invoke-timeout <ms>]`
|
||||
- `nodes canvas present --node <id|name|ip> [--target <urlOrPath>] [--x <px>] [--y <px>] [--width <px>] [--height <px>] [--invoke-timeout <ms>]`
|
||||
- `nodes canvas hide --node <id|name|ip> [--invoke-timeout <ms>]`
|
||||
@@ -852,6 +954,7 @@ Canvas + screen:
|
||||
- `nodes screen record --node <id|name|ip> [--screen <index>] [--duration <ms|10s>] [--fps <n>] [--no-audio] [--out <path>] [--invoke-timeout <ms>]`
|
||||
|
||||
Location:
|
||||
|
||||
- `nodes location get --node <id|name|ip> [--max-age <ms>] [--accuracy <coarse|balanced|precise>] [--location-timeout <ms>] [--invoke-timeout <ms>]`
|
||||
|
||||
## Browser
|
||||
@@ -859,10 +962,12 @@ Location:
|
||||
Browser control CLI (dedicated Chrome/Brave/Edge/Chromium). See [`openclaw browser`](/cli/browser) and the [Browser tool](/tools/browser).
|
||||
|
||||
Common options:
|
||||
|
||||
- `--url`, `--token`, `--timeout`, `--json`
|
||||
- `--browser-profile <name>`
|
||||
|
||||
Manage:
|
||||
|
||||
- `browser status`
|
||||
- `browser start`
|
||||
- `browser stop`
|
||||
@@ -876,10 +981,12 @@ Manage:
|
||||
- `browser delete-profile --name <name>`
|
||||
|
||||
Inspect:
|
||||
|
||||
- `browser screenshot [targetId] [--full-page] [--ref <ref>] [--element <selector>] [--type png|jpeg]`
|
||||
- `browser snapshot [--format aria|ai] [--target-id <id>] [--limit <n>] [--interactive] [--compact] [--depth <n>] [--selector <sel>] [--out <path>]`
|
||||
|
||||
Actions:
|
||||
|
||||
- `browser navigate <url> [--target-id <id>]`
|
||||
- `browser resize <width> <height> [--target-id <id>]`
|
||||
- `browser click <ref> [--double] [--button <left|right|middle>] [--modifiers <csv>] [--target-id <id>]`
|
||||
@@ -899,14 +1006,17 @@ Actions:
|
||||
## Docs search
|
||||
|
||||
### `docs [query...]`
|
||||
|
||||
Search the live docs index.
|
||||
|
||||
## TUI
|
||||
|
||||
### `tui`
|
||||
|
||||
Open the terminal UI connected to the Gateway.
|
||||
|
||||
Options:
|
||||
|
||||
- `--url <url>`
|
||||
- `--token <token>`
|
||||
- `--password <password>`
|
||||
|
||||
@@ -10,6 +10,7 @@ read_when:
|
||||
Tail Gateway file logs over RPC (works in remote mode).
|
||||
|
||||
Related:
|
||||
|
||||
- Logging overview: [Logging](/logging)
|
||||
|
||||
## Examples
|
||||
@@ -20,4 +21,3 @@ openclaw logs --follow
|
||||
openclaw logs --json
|
||||
openclaw logs --limit 500
|
||||
```
|
||||
|
||||
|
||||
@@ -11,8 +11,9 @@ 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](/plugins)
|
||||
- Plugins: [Plugins](/plugins)
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -36,6 +37,7 @@ Common:
|
||||
- `--verbose`: emit detailed logs during probes and indexing.
|
||||
|
||||
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).
|
||||
|
||||
@@ -17,11 +17,13 @@ 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)
|
||||
@@ -33,6 +35,7 @@ Target formats (`--target`):
|
||||
- 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.
|
||||
|
||||
@@ -180,12 +183,14 @@ Name lookup:
|
||||
## Examples
|
||||
|
||||
Send a Discord reply:
|
||||
|
||||
```
|
||||
openclaw message send --channel discord \
|
||||
--target channel:123 --message "hi" --reply-to 456
|
||||
```
|
||||
|
||||
Create a Discord poll:
|
||||
|
||||
```
|
||||
openclaw message poll --channel discord \
|
||||
--target channel:123 \
|
||||
@@ -195,12 +200,14 @@ openclaw message poll --channel discord \
|
||||
```
|
||||
|
||||
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 \
|
||||
@@ -209,12 +216,14 @@ openclaw message poll --channel msteams \
|
||||
```
|
||||
|
||||
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 \
|
||||
@@ -222,6 +231,7 @@ openclaw message react --channel signal \
|
||||
```
|
||||
|
||||
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"}] ]'
|
||||
|
||||
@@ -10,6 +10,7 @@ read_when:
|
||||
Model discovery, scanning, and configuration (default model, fallbacks, auth profiles).
|
||||
|
||||
Related:
|
||||
|
||||
- Providers + models: [Models](/providers/models)
|
||||
- Provider auth setup: [Getting started](/start/getting-started)
|
||||
|
||||
@@ -32,12 +33,15 @@ the command uses `OPENCLAW_AGENT_DIR`/`PI_CODING_AGENT_DIR` if set, otherwise th
|
||||
configured default agent.
|
||||
|
||||
Notes:
|
||||
|
||||
- `models set <model-or-alias>` accepts `provider/model` or an alias.
|
||||
- Model refs are parsed by splitting on the **first** `/`. If the model ID includes `/` (OpenRouter-style), include the provider prefix (example: `openrouter/moonshotai/kimi-k2`).
|
||||
- If you omit the provider, OpenClaw treats the input as an alias or a model for the **default provider** (only works when there is no `/` in the model ID).
|
||||
|
||||
### `models status`
|
||||
|
||||
Options:
|
||||
|
||||
- `--json`
|
||||
- `--plain`
|
||||
- `--check` (exit 1=expired/missing, 2=expiring)
|
||||
@@ -64,9 +68,11 @@ openclaw models auth login --provider <id>
|
||||
openclaw models auth setup-token
|
||||
openclaw models auth paste-token
|
||||
```
|
||||
|
||||
`models auth login` runs a provider plugin’s auth flow (OAuth/API key). Use
|
||||
`openclaw plugins list` to see which providers are installed.
|
||||
|
||||
Notes:
|
||||
|
||||
- `setup-token` prompts for a setup-token value (generate it with `claude setup-token` on any machine).
|
||||
- `paste-token` accepts a token string generated elsewhere or from automation.
|
||||
|
||||
@@ -16,6 +16,7 @@ Use a node host when you want agents to **run commands on other machines** in yo
|
||||
network without installing a full macOS companion app there.
|
||||
|
||||
Common use cases:
|
||||
|
||||
- Run commands on remote Linux/Windows boxes (build servers, lab machines, NAS).
|
||||
- Keep exec **sandboxed** on the gateway, but delegate approved runs to other hosts.
|
||||
- Provide a lightweight, headless execution target for automation or CI nodes.
|
||||
@@ -35,9 +36,9 @@ Disable it on the node if needed:
|
||||
{
|
||||
nodeHost: {
|
||||
browserProxy: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -48,6 +49,7 @@ openclaw node run --host <gateway-host> --port 18789
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `--host <host>`: Gateway WebSocket host (default: `127.0.0.1`)
|
||||
- `--port <port>`: Gateway WebSocket port (default: `18789`)
|
||||
- `--tls`: Use TLS for the gateway connection
|
||||
@@ -64,6 +66,7 @@ openclaw node install --host <gateway-host> --port 18789
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `--host <host>`: Gateway WebSocket host (default: `127.0.0.1`)
|
||||
- `--port <port>`: Gateway WebSocket port (default: `18789`)
|
||||
- `--tls`: Use TLS for the gateway connection
|
||||
|
||||
@@ -10,11 +10,13 @@ read_when:
|
||||
Manage paired nodes (devices) and invoke node capabilities.
|
||||
|
||||
Related:
|
||||
|
||||
- Nodes overview: [Nodes](/nodes)
|
||||
- Camera: [Camera nodes](/nodes/camera)
|
||||
- Images: [Image nodes](/nodes/images)
|
||||
|
||||
Common options:
|
||||
|
||||
- `--url`, `--token`, `--timeout`, `--json`
|
||||
|
||||
## Common commands
|
||||
@@ -44,6 +46,7 @@ openclaw nodes run --agent main --node <id|name|ip> --raw "git status"
|
||||
```
|
||||
|
||||
Invoke flags:
|
||||
|
||||
- `--params <json>`: JSON object string (default `{}`).
|
||||
- `--invoke-timeout <ms>`: node invoke timeout (default `15000`).
|
||||
- `--idempotency-key <key>`: optional idempotency key.
|
||||
@@ -58,6 +61,7 @@ Invoke flags:
|
||||
- Requires a node that advertises `system.run` (macOS companion app or headless node host).
|
||||
|
||||
Flags:
|
||||
|
||||
- `--cwd <path>`: working directory.
|
||||
- `--env <key=val>`: env override (repeatable).
|
||||
- `--command-timeout <ms>`: command timeout.
|
||||
|
||||
@@ -9,6 +9,7 @@ read_when:
|
||||
Interactive onboarding wizard (local or remote Gateway setup).
|
||||
|
||||
Related:
|
||||
|
||||
- Wizard guide: [Onboarding](/start/onboarding)
|
||||
|
||||
## Examples
|
||||
@@ -21,6 +22,7 @@ openclaw onboard --mode remote --remote-url ws://gateway-host:18789
|
||||
```
|
||||
|
||||
Flow notes:
|
||||
|
||||
- `quickstart`: minimal prompts, auto-generates a gateway token.
|
||||
- `manual`: full prompts for port/bind/auth (alias of `advanced`).
|
||||
- Fastest first chat: `openclaw dashboard` (Control UI, no channel setup).
|
||||
|
||||
@@ -9,6 +9,7 @@ read_when:
|
||||
Approve or inspect DM pairing requests (for channels that support pairing).
|
||||
|
||||
Related:
|
||||
|
||||
- Pairing flow: [Pairing](/start/pairing)
|
||||
|
||||
## Commands
|
||||
@@ -17,4 +18,3 @@ Related:
|
||||
openclaw pairing list whatsapp
|
||||
openclaw pairing approve whatsapp <code> --notify
|
||||
```
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ read_when:
|
||||
Manage Gateway plugins/extensions (loaded in-process).
|
||||
|
||||
Related:
|
||||
|
||||
- Plugin system: [Plugins](/plugin)
|
||||
- Plugin manifest + schema: [Plugin manifest](/plugins/manifest)
|
||||
- Security hardening: [Security](/gateway/security)
|
||||
|
||||
@@ -14,4 +14,3 @@ openclaw reset
|
||||
openclaw reset --dry-run
|
||||
openclaw reset --scope config+creds+sessions --yes --non-interactive
|
||||
```
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ openclaw sandbox list --json # JSON output
|
||||
```
|
||||
|
||||
**Output includes:**
|
||||
|
||||
- Container name and status (running/stopped)
|
||||
- Docker image and whether it matches config
|
||||
- Age (time since creation)
|
||||
@@ -56,6 +57,7 @@ openclaw sandbox recreate --all --force # Skip confirmation
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
- `--all`: Recreate all sandbox containers
|
||||
- `--session <key>`: Recreate container for specific session
|
||||
- `--agent <id>`: Recreate containers for specific agent
|
||||
@@ -97,7 +99,6 @@ openclaw sandbox recreate --all
|
||||
openclaw sandbox recreate --agent family
|
||||
```
|
||||
|
||||
|
||||
### For a specific agent only
|
||||
|
||||
```bash
|
||||
@@ -108,6 +109,7 @@ openclaw sandbox recreate --agent alfred
|
||||
## Why is this needed?
|
||||
|
||||
**Problem:** When you update sandbox Docker images or configuration:
|
||||
|
||||
- Existing containers continue running with old settings
|
||||
- Containers are only pruned after 24h of inactivity
|
||||
- Regularly-used agents keep old containers running indefinitely
|
||||
@@ -126,20 +128,20 @@ Sandbox settings live in `~/.openclaw/openclaw.json` under `agents.defaults.sand
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"sandbox": {
|
||||
"mode": "all", // off, non-main, all
|
||||
"scope": "agent", // session, agent, shared
|
||||
"mode": "all", // off, non-main, all
|
||||
"scope": "agent", // session, agent, shared
|
||||
"docker": {
|
||||
"image": "openclaw-sandbox:bookworm-slim",
|
||||
"containerPrefix": "openclaw-sbx-"
|
||||
"containerPrefix": "openclaw-sbx-",
|
||||
// ... more Docker options
|
||||
},
|
||||
"prune": {
|
||||
"idleHours": 24, // Auto-prune after 24h idle
|
||||
"maxAgeDays": 7 // Auto-prune after 7 days
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"idleHours": 24, // Auto-prune after 24h idle
|
||||
"maxAgeDays": 7, // Auto-prune after 7 days
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ read_when:
|
||||
Security tools (audit + optional fixes).
|
||||
|
||||
Related:
|
||||
|
||||
- Security guide: [Security](/gateway/security)
|
||||
|
||||
## Audit
|
||||
|
||||
@@ -13,4 +13,3 @@ openclaw sessions
|
||||
openclaw sessions --active 120
|
||||
openclaw sessions --json
|
||||
```
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ read_when:
|
||||
Initialize `~/.openclaw/openclaw.json` and the agent workspace.
|
||||
|
||||
Related:
|
||||
|
||||
- Getting started: [Getting started](/start/getting-started)
|
||||
- Wizard: [Onboarding](/start/onboarding)
|
||||
|
||||
@@ -25,4 +26,3 @@ To run the wizard via setup:
|
||||
```bash
|
||||
openclaw setup --wizard
|
||||
```
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ read_when:
|
||||
Inspect skills (bundled + workspace + managed overrides) and see what’s eligible vs missing requirements.
|
||||
|
||||
Related:
|
||||
|
||||
- Skills system: [Skills](/tools/skills)
|
||||
- Skills config: [Skills config](/tools/skills-config)
|
||||
- ClawHub installs: [ClawHub](/tools/clawhub)
|
||||
|
||||
@@ -17,6 +17,7 @@ openclaw status --usage
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `--deep` runs live probes (WhatsApp Web + Telegram + Discord + Google Chat + Slack + Signal).
|
||||
- Output includes per-agent session stores when multiple agents are configured.
|
||||
- Overview includes Gateway + node host service install/runtime status when available.
|
||||
|
||||
@@ -27,6 +27,7 @@ it as a `System:` line in the prompt. Use `--mode now` to trigger the heartbeat
|
||||
immediately; `next-heartbeat` waits for the next scheduled tick.
|
||||
|
||||
Flags:
|
||||
|
||||
- `--text <text>`: required system event text.
|
||||
- `--mode <mode>`: `now` or `next-heartbeat` (default).
|
||||
- `--json`: machine-readable output.
|
||||
@@ -34,11 +35,13 @@ Flags:
|
||||
## `system heartbeat last|enable|disable`
|
||||
|
||||
Heartbeat controls:
|
||||
|
||||
- `last`: show the last heartbeat event.
|
||||
- `enable`: turn heartbeats back on (use this if they were disabled).
|
||||
- `disable`: pause heartbeats.
|
||||
|
||||
Flags:
|
||||
|
||||
- `--json`: machine-readable output.
|
||||
|
||||
## `system presence`
|
||||
@@ -47,6 +50,7 @@ List the current system presence entries the Gateway knows about (nodes,
|
||||
instances, and similar status lines).
|
||||
|
||||
Flags:
|
||||
|
||||
- `--json`: machine-readable output.
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -10,6 +10,7 @@ read_when:
|
||||
Open the terminal UI connected to the Gateway.
|
||||
|
||||
Related:
|
||||
|
||||
- TUI guide: [TUI](/tui)
|
||||
|
||||
## Examples
|
||||
@@ -19,4 +20,3 @@ openclaw tui
|
||||
openclaw tui --url ws://127.0.0.1:18789 --token <token>
|
||||
openclaw tui --session main --deliver
|
||||
```
|
||||
|
||||
|
||||
@@ -14,4 +14,3 @@ openclaw uninstall
|
||||
openclaw uninstall --all --yes
|
||||
openclaw uninstall --dry-run
|
||||
```
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ openclaw update status --timeout 10
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `--json`: print machine-readable status JSON.
|
||||
- `--timeout <seconds>`: timeout for checks (default is 3s).
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ read_when:
|
||||
`voicecall` is a plugin-provided command. It only appears if the voice-call plugin is installed and enabled.
|
||||
|
||||
Primary doc:
|
||||
|
||||
- Voice-call plugin: [Voice Call](/plugins/voice-call)
|
||||
|
||||
## Common commands
|
||||
@@ -30,4 +31,3 @@ openclaw voicecall unexpose
|
||||
```
|
||||
|
||||
Security note: only expose the webhook endpoint to networks you trust. Prefer Tailscale Serve over Funnel when possible.
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ read_when:
|
||||
Webhook helpers and integrations (Gmail Pub/Sub, webhook helpers).
|
||||
|
||||
Related:
|
||||
|
||||
- Webhooks: [Webhook](/automation/webhook)
|
||||
- Gmail Pub/Sub: [Gmail Pub/Sub](/automation/gmail-pubsub)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ summary: "Agent loop lifecycle, streams, and wait semantics"
|
||||
read_when:
|
||||
- You need an exact walkthrough of the agent loop or lifecycle events
|
||||
---
|
||||
|
||||
# Agent Loop (OpenClaw)
|
||||
|
||||
An agentic loop is the full “real” run of an agent: intake → context assembly → model inference →
|
||||
@@ -14,53 +15,61 @@ as the model thinks, calls tools, and streams output. This doc explains how that
|
||||
wired end-to-end.
|
||||
|
||||
## Entry points
|
||||
|
||||
- Gateway RPC: `agent` and `agent.wait`.
|
||||
- CLI: `agent` command.
|
||||
|
||||
## How it works (high-level)
|
||||
1) `agent` RPC validates params, resolves session (sessionKey/sessionId), persists session metadata, returns `{ runId, acceptedAt }` immediately.
|
||||
2) `agentCommand` runs the agent:
|
||||
|
||||
1. `agent` RPC validates params, resolves session (sessionKey/sessionId), persists session metadata, returns `{ runId, acceptedAt }` immediately.
|
||||
2. `agentCommand` runs the agent:
|
||||
- resolves model + thinking/verbose defaults
|
||||
- loads skills snapshot
|
||||
- calls `runEmbeddedPiAgent` (pi-agent-core runtime)
|
||||
- emits **lifecycle end/error** if the embedded loop does not emit one
|
||||
3) `runEmbeddedPiAgent`:
|
||||
3. `runEmbeddedPiAgent`:
|
||||
- serializes runs via per-session + global queues
|
||||
- resolves model + auth profile and builds the pi session
|
||||
- subscribes to pi events and streams assistant/tool deltas
|
||||
- enforces timeout -> aborts run if exceeded
|
||||
- returns payloads + usage metadata
|
||||
4) `subscribeEmbeddedPiSession` bridges pi-agent-core events to OpenClaw `agent` stream:
|
||||
4. `subscribeEmbeddedPiSession` bridges pi-agent-core events to OpenClaw `agent` stream:
|
||||
- tool events => `stream: "tool"`
|
||||
- assistant deltas => `stream: "assistant"`
|
||||
- lifecycle events => `stream: "lifecycle"` (`phase: "start" | "end" | "error"`)
|
||||
5) `agent.wait` uses `waitForAgentJob`:
|
||||
5. `agent.wait` uses `waitForAgentJob`:
|
||||
- waits for **lifecycle end/error** for `runId`
|
||||
- returns `{ status: ok|error|timeout, startedAt, endedAt, error? }`
|
||||
|
||||
## Queueing + concurrency
|
||||
|
||||
- Runs are serialized per session key (session lane) and optionally through a global lane.
|
||||
- This prevents tool/session races and keeps session history consistent.
|
||||
- Messaging channels can choose queue modes (collect/steer/followup) that feed this lane system.
|
||||
See [Command Queue](/concepts/queue).
|
||||
|
||||
## Session + workspace preparation
|
||||
|
||||
- Workspace is resolved and created; sandboxed runs may redirect to a sandbox workspace root.
|
||||
- Skills are loaded (or reused from a snapshot) and injected into env and prompt.
|
||||
- Bootstrap/context files are resolved and injected into the system prompt report.
|
||||
- A session write lock is acquired; `SessionManager` is opened and prepared before streaming.
|
||||
|
||||
## Prompt assembly + system prompt
|
||||
|
||||
- System prompt is built from OpenClaw’s base prompt, skills prompt, bootstrap context, and per-run overrides.
|
||||
- Model-specific limits and compaction reserve tokens are enforced.
|
||||
- See [System prompt](/concepts/system-prompt) for what the model sees.
|
||||
|
||||
## Hook points (where you can intercept)
|
||||
|
||||
OpenClaw has two hook systems:
|
||||
|
||||
- **Internal hooks** (Gateway hooks): event-driven scripts for commands and lifecycle events.
|
||||
- **Plugin hooks**: extension points inside the agent/tool lifecycle and gateway pipeline.
|
||||
|
||||
### Internal hooks (Gateway hooks)
|
||||
|
||||
- **`agent:bootstrap`**: runs while building bootstrap files before the system prompt is finalized.
|
||||
Use this to add/remove bootstrap context files.
|
||||
- **Command hooks**: `/new`, `/reset`, `/stop`, and other command events (see Hooks doc).
|
||||
@@ -68,7 +77,9 @@ OpenClaw has two hook systems:
|
||||
See [Hooks](/hooks) for setup and examples.
|
||||
|
||||
### Plugin hooks (agent + gateway lifecycle)
|
||||
|
||||
These run inside the agent loop or gateway pipeline:
|
||||
|
||||
- **`before_agent_start`**: inject context or override system prompt before the run starts.
|
||||
- **`agent_end`**: inspect the final message list and run metadata after completion.
|
||||
- **`before_compaction` / `after_compaction`**: observe or annotate compaction cycles.
|
||||
@@ -81,17 +92,20 @@ These run inside the agent loop or gateway pipeline:
|
||||
See [Plugins](/plugin#plugin-hooks) for the hook API and registration details.
|
||||
|
||||
## Streaming + partial replies
|
||||
|
||||
- Assistant deltas are streamed from pi-agent-core and emitted as `assistant` events.
|
||||
- Block streaming can emit partial replies either on `text_end` or `message_end`.
|
||||
- Reasoning streaming can be emitted as a separate stream or as block replies.
|
||||
- See [Streaming](/concepts/streaming) for chunking and block reply behavior.
|
||||
|
||||
## Tool execution + messaging tools
|
||||
|
||||
- Tool start/update/end events are emitted on the `tool` stream.
|
||||
- Tool results are sanitized for size and image payloads before logging/emitting.
|
||||
- Messaging tool sends are tracked to suppress duplicate assistant confirmations.
|
||||
|
||||
## Reply shaping + suppression
|
||||
|
||||
- Final payloads are assembled from:
|
||||
- assistant text (and optional reasoning)
|
||||
- inline tool summaries (when verbose + allowed)
|
||||
@@ -102,24 +116,29 @@ See [Plugins](/plugin#plugin-hooks) for the hook API and registration details.
|
||||
(unless a messaging tool already sent a user-visible reply).
|
||||
|
||||
## Compaction + retries
|
||||
|
||||
- Auto-compaction emits `compaction` stream events and can trigger a retry.
|
||||
- On retry, in-memory buffers and tool summaries are reset to avoid duplicate output.
|
||||
- See [Compaction](/concepts/compaction) for the compaction pipeline.
|
||||
|
||||
## Event streams (today)
|
||||
|
||||
- `lifecycle`: emitted by `subscribeEmbeddedPiSession` (and as a fallback by `agentCommand`)
|
||||
- `assistant`: streamed deltas from pi-agent-core
|
||||
- `tool`: streamed tool events from pi-agent-core
|
||||
|
||||
## Chat channel handling
|
||||
|
||||
- Assistant deltas are buffered into chat `delta` messages.
|
||||
- A chat `final` is emitted on **lifecycle end/error**.
|
||||
|
||||
## Timeouts
|
||||
|
||||
- `agent.wait` default: 30s (just the wait). `timeoutMs` param overrides.
|
||||
- Agent runtime: `agents.defaults.timeoutSeconds` default 600s; enforced in `runEmbeddedPiAgent` abort timer.
|
||||
|
||||
## Where things can end early
|
||||
|
||||
- Agent timeout (abort)
|
||||
- AbortSignal (cancel)
|
||||
- Gateway disconnect or RPC timeout
|
||||
|
||||
@@ -4,6 +4,7 @@ read_when:
|
||||
- You need to explain the agent workspace or its file layout
|
||||
- You want to back up or migrate an agent workspace
|
||||
---
|
||||
|
||||
# Agent workspace
|
||||
|
||||
The workspace is the agent's home. It is the only working directory used for
|
||||
@@ -29,8 +30,8 @@ inside a sandbox workspace under `~/.openclaw/sandboxes`, not your host workspac
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
workspace: "~/.openclaw/workspace"
|
||||
}
|
||||
workspace: "~/.openclaw/workspace",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ summary: "Agent runtime (embedded pi-mono), workspace contract, and session boot
|
||||
read_when:
|
||||
- Changing agent runtime, workspace bootstrap, or session behavior
|
||||
---
|
||||
|
||||
# Agent Runtime 🤖
|
||||
|
||||
OpenClaw runs a single embedded agent runtime derived from **pi-mono**.
|
||||
@@ -22,6 +23,7 @@ per-session workspaces under `agents.defaults.sandbox.workspaceRoot` (see
|
||||
## Bootstrap files (injected)
|
||||
|
||||
Inside `agents.defaults.workspace`, OpenClaw expects these user-editable files:
|
||||
|
||||
- `AGENTS.md` — operating instructions + “memory”
|
||||
- `SOUL.md` — persona, boundaries, tone
|
||||
- `TOOLS.md` — user-maintained tool notes (e.g. `imsg`, `sag`, conventions)
|
||||
@@ -48,11 +50,12 @@ To disable bootstrap file creation entirely (for pre-seeded workspaces), set:
|
||||
Core tools (read/exec/edit/write and related system tools) are always available,
|
||||
subject to tool policy. `apply_patch` is optional and gated by
|
||||
`tools.exec.applyPatch`. `TOOLS.md` does **not** control which tools exist; it’s
|
||||
guidance for how *you* want them used.
|
||||
guidance for how _you_ want them used.
|
||||
|
||||
## Skills
|
||||
|
||||
OpenClaw loads skills from three locations (workspace wins on name conflict):
|
||||
|
||||
- Bundled (shipped with the install)
|
||||
- Managed/local: `~/.openclaw/skills`
|
||||
- Workspace: `<workspace>/skills`
|
||||
@@ -69,6 +72,7 @@ OpenClaw reuses pieces of the pi-mono codebase (models/tools), but **session man
|
||||
## Sessions
|
||||
|
||||
Session transcripts are stored as JSONL at:
|
||||
|
||||
- `~/.openclaw/agents/<agentId>/sessions/<SessionId>.jsonl`
|
||||
|
||||
The session ID is stable and chosen by OpenClaw.
|
||||
@@ -109,9 +113,10 @@ Model refs in config (for example `agents.defaults.model` and `agents.defaults.m
|
||||
## Configuration (minimal)
|
||||
|
||||
At minimum, set:
|
||||
|
||||
- `agents.defaults.workspace`
|
||||
- `channels.whatsapp.allowFrom` (strongly recommended)
|
||||
|
||||
---
|
||||
|
||||
*Next: [Group Chats](/concepts/group-messages)* 🦞
|
||||
_Next: [Group Chats](/concepts/group-messages)_ 🦞
|
||||
|
||||
@@ -3,6 +3,7 @@ summary: "WebSocket gateway architecture, components, and client flows"
|
||||
read_when:
|
||||
- Working on gateway protocol, clients, or transports
|
||||
---
|
||||
|
||||
# Gateway architecture
|
||||
|
||||
Last updated: 2026-01-22
|
||||
@@ -22,26 +23,31 @@ Last updated: 2026-01-22
|
||||
## Components and flows
|
||||
|
||||
### Gateway (daemon)
|
||||
|
||||
- Maintains provider connections.
|
||||
- Exposes a typed WS API (requests, responses, server‑push events).
|
||||
- Validates inbound frames against JSON Schema.
|
||||
- Emits events like `agent`, `chat`, `presence`, `health`, `heartbeat`, `cron`.
|
||||
|
||||
### Clients (mac app / CLI / web admin)
|
||||
|
||||
- One WS connection per client.
|
||||
- Send requests (`health`, `status`, `send`, `agent`, `system-presence`).
|
||||
- Subscribe to events (`tick`, `agent`, `presence`, `shutdown`).
|
||||
|
||||
### Nodes (macOS / iOS / Android / headless)
|
||||
|
||||
- Connect to the **same WS server** with `role: node`.
|
||||
- Provide a device identity in `connect`; pairing is **device‑based** (role `node`) and
|
||||
approval lives in the device pairing store.
|
||||
- Expose commands like `canvas.*`, `camera.*`, `screen.record`, `location.get`.
|
||||
|
||||
Protocol details:
|
||||
|
||||
- [Gateway protocol](/gateway/protocol)
|
||||
|
||||
### WebChat
|
||||
|
||||
- Static UI that uses the Gateway WS API for chat history and sends.
|
||||
- In remote setups, connects through the same SSH/Tailscale tunnel as other
|
||||
clients.
|
||||
|
||||
@@ -3,8 +3,8 @@ summary: "Routing rules per channel (WhatsApp, Telegram, Discord, Slack) and sha
|
||||
read_when:
|
||||
- Changing channel routing or inbox behavior
|
||||
---
|
||||
# Channels & 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
|
||||
@@ -62,8 +62,8 @@ Config:
|
||||
broadcast: {
|
||||
strategy: "parallel",
|
||||
"120363403215116621@g.us": ["alfred", "baerbel"],
|
||||
"+15555550123": ["support", "logger"]
|
||||
}
|
||||
"+15555550123": ["support", "logger"],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -79,14 +79,12 @@ Example:
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "support", name: "Support", workspace: "~/.openclaw/workspace-support" }
|
||||
]
|
||||
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" }
|
||||
]
|
||||
{ match: { channel: "telegram", peer: { kind: "group", id: "-100123" } }, agentId: "support" },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
@@ -108,6 +106,7 @@ 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.
|
||||
|
||||
|
||||
@@ -4,24 +4,30 @@ read_when:
|
||||
- You want to understand auto-compaction and /compact
|
||||
- You are debugging long sessions hitting context limits
|
||||
---
|
||||
|
||||
# Context Window & Compaction
|
||||
|
||||
Every model has a **context window** (max tokens it can see). Long-running chats accumulate messages and tool results; once the window is tight, OpenClaw **compacts** older history to stay within limits.
|
||||
|
||||
## What compaction is
|
||||
|
||||
Compaction **summarizes older conversation** into a compact summary entry and keeps recent messages intact. The summary is stored in the session history, so future requests use:
|
||||
|
||||
- The compaction summary
|
||||
- Recent messages after the compaction point
|
||||
|
||||
Compaction **persists** in the session’s JSONL history.
|
||||
|
||||
## Configuration
|
||||
|
||||
See [Compaction config & modes](/concepts/compaction) for the `agents.defaults.compaction` settings.
|
||||
|
||||
## Auto-compaction (default on)
|
||||
|
||||
When a session nears or exceeds the model’s context window, OpenClaw triggers auto-compaction and may retry the original request using the compacted context.
|
||||
|
||||
You’ll see:
|
||||
|
||||
- `🧹 Auto-compaction complete` in verbose mode
|
||||
- `/status` showing `🧹 Compactions: <count>`
|
||||
|
||||
@@ -29,21 +35,26 @@ Before compaction, OpenClaw can run a **silent memory flush** turn to store
|
||||
durable notes to disk. See [Memory](/concepts/memory) for details and config.
|
||||
|
||||
## Manual compaction
|
||||
|
||||
Use `/compact` (optionally with instructions) to force a compaction pass:
|
||||
|
||||
```
|
||||
/compact Focus on decisions and open questions
|
||||
```
|
||||
|
||||
## Context window source
|
||||
|
||||
Context window is model-specific. OpenClaw uses the model definition from the configured provider catalog to determine limits.
|
||||
|
||||
## Compaction vs pruning
|
||||
|
||||
- **Compaction**: summarises and **persists** in JSONL.
|
||||
- **Session pruning**: trims old **tool results** only, **in-memory**, per request.
|
||||
|
||||
See [/concepts/session-pruning](/concepts/session-pruning) for pruning details.
|
||||
|
||||
## Tips
|
||||
|
||||
- Use `/compact` when sessions feel stale or context is bloated.
|
||||
- Large tool outputs are already truncated; pruning can further reduce tool-result buildup.
|
||||
- If you need a fresh slate, `/new` or `/reset` starts a new session id.
|
||||
|
||||
@@ -5,16 +5,18 @@ read_when:
|
||||
- You are debugging why the model “knows” something (or forgot it)
|
||||
- You want to reduce context overhead (/context, /status, /compact)
|
||||
---
|
||||
|
||||
# Context
|
||||
|
||||
“Context” is **everything OpenClaw sends to the model for a run**. It is bounded by the model’s **context window** (token limit).
|
||||
|
||||
Beginner mental model:
|
||||
|
||||
- **System prompt** (OpenClaw-built): rules, tools, skills list, time/runtime, and injected workspace files.
|
||||
- **Conversation history**: your messages + the assistant’s messages for this session.
|
||||
- **Tool calls/results + attachments**: command output, file reads, images/audio, etc.
|
||||
|
||||
Context is *not the same thing* as “memory”: memory can be stored on disk and reloaded later; context is what’s inside the model’s current window.
|
||||
Context is _not the same thing_ as “memory”: memory can be stored on disk and reloaded later; context is what’s inside the model’s current window.
|
||||
|
||||
## Quick start (inspect context)
|
||||
|
||||
@@ -76,6 +78,7 @@ Top tools (schema size):
|
||||
## What counts toward the context window
|
||||
|
||||
Everything the model receives counts, including:
|
||||
|
||||
- System prompt (all sections).
|
||||
- Conversation history.
|
||||
- Tool calls + tool results.
|
||||
@@ -86,6 +89,7 @@ Everything the model receives counts, including:
|
||||
## How OpenClaw builds the system prompt
|
||||
|
||||
The system prompt is **OpenClaw-owned** and rebuilt each run. It includes:
|
||||
|
||||
- Tool list + short descriptions.
|
||||
- Skills list (metadata only; see below).
|
||||
- Workspace location.
|
||||
@@ -98,6 +102,7 @@ Full breakdown: [System Prompt](/concepts/system-prompt).
|
||||
## Injected workspace files (Project Context)
|
||||
|
||||
By default, OpenClaw injects a fixed set of workspace files (if present):
|
||||
|
||||
- `AGENTS.md`
|
||||
- `SOUL.md`
|
||||
- `TOOLS.md`
|
||||
@@ -112,19 +117,21 @@ Large files are truncated per-file using `agents.defaults.bootstrapMaxChars` (de
|
||||
|
||||
The system prompt includes a compact **skills list** (name + description + location). This list has real overhead.
|
||||
|
||||
Skill instructions are *not* included by default. The model is expected to `read` the skill’s `SKILL.md` **only when needed**.
|
||||
Skill instructions are _not_ included by default. The model is expected to `read` the skill’s `SKILL.md` **only when needed**.
|
||||
|
||||
## Tools: there are two costs
|
||||
|
||||
Tools affect context in two ways:
|
||||
1) **Tool list text** in the system prompt (what you see as “Tooling”).
|
||||
2) **Tool schemas** (JSON). These are sent to the model so it can call tools. They count toward context even though you don’t see them as plain text.
|
||||
|
||||
1. **Tool list text** in the system prompt (what you see as “Tooling”).
|
||||
2. **Tool schemas** (JSON). These are sent to the model so it can call tools. They count toward context even though you don’t see them as plain text.
|
||||
|
||||
`/context detail` breaks down the biggest tool schemas so you can see what dominates.
|
||||
|
||||
## Commands, directives, and “inline shortcuts”
|
||||
|
||||
Slash commands are handled by the Gateway. There are a few different behaviors:
|
||||
|
||||
- **Standalone commands**: a message that is only `/...` runs as a command.
|
||||
- **Directives**: `/think`, `/verbose`, `/reasoning`, `/elevated`, `/model`, `/queue` are stripped before the model sees the message.
|
||||
- Directive-only messages persist session settings.
|
||||
@@ -136,15 +143,17 @@ Details: [Slash commands](/tools/slash-commands).
|
||||
## Sessions, compaction, and pruning (what persists)
|
||||
|
||||
What persists across messages depends on the mechanism:
|
||||
|
||||
- **Normal history** persists in the session transcript until compacted/pruned by policy.
|
||||
- **Compaction** persists a summary into the transcript and keeps recent messages intact.
|
||||
- **Pruning** removes old tool results from the *in-memory* prompt for a run, but does not rewrite the transcript.
|
||||
- **Pruning** removes old tool results from the _in-memory_ prompt for a run, but does not rewrite the transcript.
|
||||
|
||||
Docs: [Session](/concepts/session), [Compaction](/concepts/compaction), [Session pruning](/concepts/session-pruning).
|
||||
|
||||
## What `/context` actually reports
|
||||
|
||||
`/context` prefers the latest **run-built** system prompt report when available:
|
||||
|
||||
- `System prompt (run)` = captured from the last embedded (tool-capable) run and persisted in the session store.
|
||||
- `System prompt (estimate)` = computed on the fly when no run report exists (or when running via a CLI backend that doesn’t generate the report).
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ summary: "Behavior and config for WhatsApp group message handling (mentionPatter
|
||||
read_when:
|
||||
- Changing group message rules or mentions
|
||||
---
|
||||
|
||||
# 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.
|
||||
@@ -10,15 +11,17 @@ Goal: let Clawd sit in WhatsApp groups, wake up only when pinged, and keep that
|
||||
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.
|
||||
- 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
|
||||
@@ -26,9 +29,9 @@ Add a `groupChat` block to `~/.openclaw/openclaw.json` so display-name pings wor
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: {
|
||||
"*": { requireMention: true }
|
||||
}
|
||||
}
|
||||
"*": { requireMention: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
list: [
|
||||
@@ -36,42 +39,44 @@ Add a `groupChat` block to `~/.openclaw/openclaw.json` so display-name pings wor
|
||||
id: "main",
|
||||
groupChat: {
|
||||
historyLimit: 50,
|
||||
mentionPatterns: [
|
||||
"@?openclaw",
|
||||
"\\+?15555550123"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -3,26 +3,31 @@ summary: "Group chat behavior across surfaces (WhatsApp/Telegram/Discord/Slack/S
|
||||
read_when:
|
||||
- Changing group chat behavior or mention gating
|
||||
---
|
||||
|
||||
# Groups
|
||||
|
||||
OpenClaw treats group chats consistently across surfaces: WhatsApp, Telegram, Discord, Slack, Signal, iMessage, Microsoft Teams.
|
||||
|
||||
## 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
|
||||
@@ -41,6 +46,7 @@ If you want...
|
||||
| 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).
|
||||
@@ -53,6 +59,7 @@ Yes — this works well if your “personal” traffic is **DMs** and your “pu
|
||||
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)
|
||||
|
||||
@@ -67,19 +74,19 @@ Example (DMs on host, groups sandboxed + messaging-only tools):
|
||||
sandbox: {
|
||||
mode: "non-main", // groups/channels are non-main -> sandboxed
|
||||
scope: "session", // strongest isolation (one container per group/channel)
|
||||
workspaceAccess: "none"
|
||||
}
|
||||
}
|
||||
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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
deny: ["group:runtime", "group:fs", "group:ui", "nodes", "cron", "gateway"],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -96,25 +103,28 @@ Want “groups can only see folder X” instead of “no host access”? Keep `w
|
||||
docker: {
|
||||
binds: [
|
||||
// hostPath:containerPath:mode
|
||||
"~/FriendsShared:/data:ro"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"~/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
|
||||
@@ -122,53 +132,54 @@ Control how group/room messages are handled per channel:
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groupPolicy: "disabled", // "open" | "disabled" | "allowlist"
|
||||
groupAllowFrom: ["+15551234567"]
|
||||
groupAllowFrom: ["+15551234567"],
|
||||
},
|
||||
telegram: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["123456789", "@username"]
|
||||
groupAllowFrom: ["123456789", "@username"],
|
||||
},
|
||||
signal: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["+15551234567"]
|
||||
groupAllowFrom: ["+15551234567"],
|
||||
},
|
||||
imessage: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["chat_id:123"]
|
||||
groupAllowFrom: ["chat_id:123"],
|
||||
},
|
||||
msteams: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["user@org.com"]
|
||||
groupAllowFrom: ["user@org.com"],
|
||||
},
|
||||
discord: {
|
||||
groupPolicy: "allowlist",
|
||||
guilds: {
|
||||
"GUILD_ID": { channels: { help: { allow: true } } }
|
||||
}
|
||||
GUILD_ID: { channels: { help: { allow: true } } },
|
||||
},
|
||||
},
|
||||
slack: {
|
||||
groupPolicy: "allowlist",
|
||||
channels: { "#general": { allow: true } }
|
||||
channels: { "#general": { allow: true } },
|
||||
},
|
||||
matrix: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["@owner:example.org"],
|
||||
groups: {
|
||||
"!roomId:example.org": { allow: true },
|
||||
"#alias: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. |
|
||||
| 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: use `groupAllowFrom` (fallback: explicit `allowFrom`).
|
||||
- Discord: allowlist uses `channels.discord.guilds.<id>.channels`.
|
||||
@@ -179,11 +190,13 @@ Notes:
|
||||
- Default is `groupPolicy: "allowlist"`; if your group allowlist is empty, group messages are blocked.
|
||||
|
||||
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`)
|
||||
|
||||
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.
|
||||
@@ -194,21 +207,21 @@ Replying to a bot message counts as an implicit mention (when the channel suppor
|
||||
whatsapp: {
|
||||
groups: {
|
||||
"*": { requireMention: true },
|
||||
"123@g.us": { requireMention: false }
|
||||
}
|
||||
"123@g.us": { requireMention: false },
|
||||
},
|
||||
},
|
||||
telegram: {
|
||||
groups: {
|
||||
"*": { requireMention: true },
|
||||
"123456789": { requireMention: false }
|
||||
}
|
||||
"123456789": { requireMention: false },
|
||||
},
|
||||
},
|
||||
imessage: {
|
||||
groups: {
|
||||
"*": { requireMention: true },
|
||||
"123": { requireMention: false }
|
||||
}
|
||||
}
|
||||
"123": { requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
list: [
|
||||
@@ -216,15 +229,16 @@ Replying to a bot message counts as an implicit mention (when the channel suppor
|
||||
id: "main",
|
||||
groupChat: {
|
||||
mentionPatterns: ["@openclaw", "openclaw", "\\+15555550123"],
|
||||
historyLimit: 50
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
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).
|
||||
@@ -233,16 +247,18 @@ Notes:
|
||||
- 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 (keys are sender IDs/usernames/emails/phone numbers depending on the channel). Use `"*"` as a wildcard.
|
||||
|
||||
Resolution order (most specific wins):
|
||||
1) group/channel `toolsBySender` match
|
||||
2) group/channel `tools`
|
||||
3) default (`"*"`) `toolsBySender` match
|
||||
4) default (`"*"`) `tools`
|
||||
|
||||
1. group/channel `toolsBySender` match
|
||||
2. group/channel `tools`
|
||||
3. default (`"*"`) `toolsBySender` match
|
||||
4. default (`"*"`) `tools`
|
||||
|
||||
Example (Telegram):
|
||||
|
||||
@@ -255,78 +271,88 @@ Example (Telegram):
|
||||
"-1001234567890": {
|
||||
tools: { deny: ["exec", "read", "write"] },
|
||||
toolsBySender: {
|
||||
"123456789": { alsoAllow: ["exec"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"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
|
||||
1. Disable all group replies
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: { whatsapp: { groupPolicy: "disabled" } }
|
||||
channels: { whatsapp: { groupPolicy: "disabled" } },
|
||||
}
|
||||
```
|
||||
|
||||
2) Allow only specific groups (WhatsApp)
|
||||
2. Allow only specific groups (WhatsApp)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: {
|
||||
"123@g.us": { requireMention: true },
|
||||
"456@g.us": { requireMention: false }
|
||||
}
|
||||
}
|
||||
}
|
||||
"456@g.us": { requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
3) Allow all groups but require mention (explicit)
|
||||
3. Allow all groups but require mention (explicit)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: { "*": { requireMention: true } }
|
||||
}
|
||||
}
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
4) Only the owner can trigger in groups (WhatsApp)
|
||||
4. Only the owner can trigger in groups (WhatsApp)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15551234567"],
|
||||
groups: { "*": { requireMention: true } }
|
||||
}
|
||||
}
|
||||
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)
|
||||
@@ -336,9 +362,11 @@ Group inbound payloads set:
|
||||
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](/concepts/group-messages) for WhatsApp-only behavior (history injection, mention handling details).
|
||||
|
||||
@@ -5,6 +5,7 @@ read_when:
|
||||
- You are adding a new channel formatter or style mapping
|
||||
- You are debugging formatting regressions across channels
|
||||
---
|
||||
|
||||
# Markdown formatting
|
||||
|
||||
OpenClaw formats outbound Markdown by converting it into a shared intermediate
|
||||
@@ -47,12 +48,8 @@ IR (schematic):
|
||||
```json
|
||||
{
|
||||
"text": "Hello world — see docs.",
|
||||
"styles": [
|
||||
{ "start": 6, "end": 11, "style": "bold" }
|
||||
],
|
||||
"links": [
|
||||
{ "start": 19, "end": 23, "href": "https://docs.openclaw.ai" }
|
||||
]
|
||||
"styles": [{ "start": 6, "end": 11, "style": "bold" }],
|
||||
"links": [{ "start": 19, "end": 23, "href": "https://docs.openclaw.ai" }]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ read_when:
|
||||
- You want the memory file layout and workflow
|
||||
- You want to tune the automatic pre-compaction memory flush
|
||||
---
|
||||
|
||||
# Memory
|
||||
|
||||
OpenClaw memory is **plain Markdown in the agent workspace**. The files are the
|
||||
@@ -38,7 +39,7 @@ These files live under the workspace (`agents.defaults.workspace`, default
|
||||
|
||||
When a session is **close to auto-compaction**, OpenClaw triggers a **silent,
|
||||
agentic turn** that reminds the model to write durable memory **before** the
|
||||
context is compacted. The default prompts explicitly say the model *may reply*,
|
||||
context is compacted. The default prompts explicitly say the model _may reply_,
|
||||
but usually `NO_REPLY` is the correct response so the user never sees this turn.
|
||||
|
||||
This is controlled by `agents.defaults.compaction.memoryFlush`:
|
||||
@@ -53,15 +54,16 @@ This is controlled by `agents.defaults.compaction.memoryFlush`:
|
||||
enabled: true,
|
||||
softThresholdTokens: 4000,
|
||||
systemPrompt: "Session nearing compaction. Store durable memories now.",
|
||||
prompt: "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
prompt: "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Details:
|
||||
|
||||
- **Soft threshold**: flush triggers when the session token estimate crosses
|
||||
`contextWindow - reserveTokensFloor - softThresholdTokens`.
|
||||
- **Silent** by default: prompts include `NO_REPLY` so nothing is delivered.
|
||||
@@ -80,6 +82,7 @@ any extra directories or files you opt in) so semantic queries can find related
|
||||
notes even when wording differs.
|
||||
|
||||
Defaults:
|
||||
|
||||
- Enabled by default.
|
||||
- Watches memory files for changes (debounced).
|
||||
- Uses remote embeddings by default. If `memorySearch.provider` is not set, OpenClaw auto-selects:
|
||||
@@ -113,6 +116,7 @@ agents: {
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Paths can be absolute or workspace-relative.
|
||||
- Directories are scanned recursively for `.md` files.
|
||||
- Only Markdown files are indexed.
|
||||
@@ -137,6 +141,7 @@ agents: {
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `remote.baseUrl` is optional (defaults to the Gemini API base URL).
|
||||
- `remote.headers` lets you add extra headers if needed.
|
||||
- Default model: `gemini-embedding-001`.
|
||||
@@ -164,10 +169,12 @@ If you don't want to set an API key, use `memorySearch.provider = "local"` or se
|
||||
`memorySearch.fallback = "none"`.
|
||||
|
||||
Fallbacks:
|
||||
|
||||
- `memorySearch.fallback` can be `openai`, `gemini`, `local`, or `none`.
|
||||
- The fallback provider is only used when the primary embedding provider fails.
|
||||
|
||||
Batch indexing (OpenAI + Gemini):
|
||||
|
||||
- Enabled by default for OpenAI and Gemini embeddings. Set `agents.defaults.memorySearch.remote.batch.enabled = false` to disable.
|
||||
- Default behavior waits for batch completion; tune `remote.batch.wait`, `remote.batch.pollIntervalMs`, and `remote.batch.timeoutMinutes` if needed.
|
||||
- Set `remote.batch.concurrency` to control how many batch jobs we submit in parallel (default: 2).
|
||||
@@ -175,6 +182,7 @@ Batch indexing (OpenAI + Gemini):
|
||||
- Gemini batch jobs use the async embeddings batch endpoint and require Gemini Batch API availability.
|
||||
|
||||
Why OpenAI batch is fast + cheap:
|
||||
|
||||
- For large backfills, OpenAI is typically the fastest option we support because we can submit many embedding requests in a single batch job and let OpenAI process them asynchronously.
|
||||
- OpenAI offers discounted pricing for Batch API workloads, so large indexing runs are usually cheaper than sending the same requests synchronously.
|
||||
- See the OpenAI Batch API docs and pricing for details:
|
||||
@@ -200,10 +208,12 @@ agents: {
|
||||
```
|
||||
|
||||
Tools:
|
||||
|
||||
- `memory_search` — returns snippets with file + line ranges.
|
||||
- `memory_get` — read memory file content by path.
|
||||
|
||||
Local mode:
|
||||
|
||||
- Set `agents.defaults.memorySearch.provider = "local"`.
|
||||
- Provide `agents.defaults.memorySearch.local.modelPath` (GGUF or `hf:` URI).
|
||||
- Optional: set `agents.defaults.memorySearch.fallback = "none"` to avoid remote fallback.
|
||||
@@ -224,6 +234,7 @@ Local mode:
|
||||
### Hybrid search (BM25 + vector)
|
||||
|
||||
When enabled, OpenClaw combines:
|
||||
|
||||
- **Vector similarity** (semantic match, wording can differ)
|
||||
- **BM25 keyword relevance** (exact tokens like IDs, env vars, code symbols)
|
||||
|
||||
@@ -232,10 +243,12 @@ If full-text search is unavailable on your platform, OpenClaw falls back to vect
|
||||
#### Why hybrid?
|
||||
|
||||
Vector search is great at “this means the same thing”:
|
||||
|
||||
- “Mac Studio gateway host” vs “the machine running the gateway”
|
||||
- “debounce file updates” vs “avoid indexing on every write”
|
||||
|
||||
But it can be weak at exact, high-signal tokens:
|
||||
|
||||
- IDs (`a828e60`, `b3b9895a…`)
|
||||
- code symbols (`memorySearch.query.hybrid`)
|
||||
- error strings (“sqlite-vec unavailable”)
|
||||
@@ -248,17 +261,21 @@ good results for both “natural language” queries and “needle in a haystack
|
||||
|
||||
Implementation sketch:
|
||||
|
||||
1) Retrieve a candidate pool from both sides:
|
||||
1. Retrieve a candidate pool from both sides:
|
||||
|
||||
- **Vector**: top `maxResults * candidateMultiplier` by cosine similarity.
|
||||
- **BM25**: top `maxResults * candidateMultiplier` by FTS5 BM25 rank (lower is better).
|
||||
|
||||
2) Convert BM25 rank into a 0..1-ish score:
|
||||
2. Convert BM25 rank into a 0..1-ish score:
|
||||
|
||||
- `textScore = 1 / (1 + max(0, bm25Rank))`
|
||||
|
||||
3) Union candidates by chunk id and compute a weighted score:
|
||||
3. Union candidates by chunk id and compute a weighted score:
|
||||
|
||||
- `finalScore = vectorWeight * vectorScore + textWeight * textScore`
|
||||
|
||||
Notes:
|
||||
|
||||
- `vectorWeight` + `textWeight` is normalized to 1.0 in config resolution, so weights behave as percentages.
|
||||
- If embeddings are unavailable (or the provider returns a zero-vector), we still run BM25 and return keyword matches.
|
||||
- If FTS5 can’t be created, we keep vector-only search (no hard failure).
|
||||
@@ -322,6 +339,7 @@ agents: {
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Session indexing is **opt-in** (off by default).
|
||||
- Session updates are debounced and **indexed asynchronously** once they cross delta thresholds (best-effort).
|
||||
- `memory_search` never blocks on indexing; results can be slightly stale until background sync finishes.
|
||||
@@ -370,6 +388,7 @@ agents: {
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `enabled` defaults to true; when disabled, search falls back to in-process
|
||||
cosine similarity over stored embeddings.
|
||||
- If the sqlite-vec extension is missing or fails to load, OpenClaw logs the
|
||||
@@ -406,5 +425,6 @@ agents: {
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `remote.*` takes precedence over `models.providers.openai.*`.
|
||||
- `remote.headers` merge with OpenAI headers; remote wins on key conflicts. Omit `remote.headers` to use the OpenAI defaults.
|
||||
|
||||
@@ -5,6 +5,7 @@ read_when:
|
||||
- Clarifying sessions, queueing modes, or streaming behavior
|
||||
- Documenting reasoning visibility and usage implications
|
||||
---
|
||||
|
||||
# Messages
|
||||
|
||||
This page ties together how OpenClaw handles inbound messages, sessions, queueing,
|
||||
@@ -21,6 +22,7 @@ Inbound message
|
||||
```
|
||||
|
||||
Key knobs live in configuration:
|
||||
|
||||
- `messages.*` for prefixes, queueing, and group behavior.
|
||||
- `agents.defaults.*` for block streaming and chunking defaults.
|
||||
- Channel overrides (`channels.whatsapp.*`, `channels.telegram.*`, etc.) for caps and streaming toggles.
|
||||
@@ -40,6 +42,7 @@ agent turn via `messages.inbound`. Debouncing is scoped per channel + conversati
|
||||
and uses the most recent message for reply threading/IDs.
|
||||
|
||||
Config (global default + per-channel overrides):
|
||||
|
||||
```json5
|
||||
{
|
||||
messages: {
|
||||
@@ -48,20 +51,22 @@ Config (global default + per-channel overrides):
|
||||
byChannel: {
|
||||
whatsapp: 5000,
|
||||
slack: 1500,
|
||||
discord: 1500
|
||||
}
|
||||
}
|
||||
}
|
||||
discord: 1500,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Debounce applies to **text-only** messages; media/attachments flush immediately.
|
||||
- Control commands bypass debouncing so they remain standalone.
|
||||
|
||||
## Sessions and devices
|
||||
|
||||
Sessions are owned by the gateway, not by clients.
|
||||
|
||||
- Direct chats collapse into the agent main session key.
|
||||
- Groups/channels get their own session keys.
|
||||
- The session store and transcripts live on the gateway host.
|
||||
@@ -76,12 +81,14 @@ Details: [Session management](/concepts/session).
|
||||
## Inbound bodies and history context
|
||||
|
||||
OpenClaw separates the **prompt body** from the **command body**:
|
||||
|
||||
- `Body`: prompt text sent to the agent. This may include channel envelopes and
|
||||
optional history wrappers.
|
||||
- `CommandBody`: raw user text for directive/command parsing.
|
||||
- `RawBody`: legacy alias for `CommandBody` (kept for compatibility).
|
||||
|
||||
When a channel supplies history, it uses a shared wrapper:
|
||||
|
||||
- `[Chat messages since your last reply - for context]`
|
||||
- `[Current message - respond to this]`
|
||||
|
||||
@@ -89,7 +96,7 @@ For **non-direct chats** (groups/channels/rooms), the **current message body** i
|
||||
sender label (same style used for history entries). This keeps real-time and queued/history
|
||||
messages consistent in the agent prompt.
|
||||
|
||||
History buffers are **pending-only**: they include group messages that did *not*
|
||||
History buffers are **pending-only**: they include group messages that did _not_
|
||||
trigger a run (for example, mention-gated messages) and **exclude** messages
|
||||
already in the session transcript.
|
||||
|
||||
@@ -116,6 +123,7 @@ Block streaming sends partial replies as the model produces text blocks.
|
||||
Chunking respects channel text limits and avoids splitting fenced code.
|
||||
|
||||
Key settings:
|
||||
|
||||
- `agents.defaults.blockStreamingDefault` (`on|off`, default off)
|
||||
- `agents.defaults.blockStreamingBreak` (`text_end|message_end`)
|
||||
- `agents.defaults.blockStreamingChunk` (`minChars|maxChars|breakPreference`)
|
||||
@@ -128,6 +136,7 @@ Details: [Streaming + chunking](/concepts/streaming).
|
||||
## Reasoning visibility and tokens
|
||||
|
||||
OpenClaw can expose or hide model reasoning:
|
||||
|
||||
- `/reasoning on|off|stream` controls visibility.
|
||||
- Reasoning content still counts toward token usage when produced by the model.
|
||||
- Telegram supports reasoning stream into the draft bubble.
|
||||
@@ -137,6 +146,7 @@ Details: [Thinking + reasoning directives](/tools/thinking) and [Token use](/tok
|
||||
## Prefixes, threading, and replies
|
||||
|
||||
Outbound message formatting is centralized in `messages`:
|
||||
|
||||
- `messages.responsePrefix` (outbound prefix) and `channels.whatsapp.messagePrefix` (WhatsApp inbound prefix)
|
||||
- Reply threading via `replyToMode` and per-channel defaults
|
||||
|
||||
|
||||
@@ -8,8 +8,9 @@ read_when:
|
||||
# Model failover
|
||||
|
||||
OpenClaw handles failures in two stages:
|
||||
1) **Auth profile rotation** within the current provider.
|
||||
2) **Model fallback** to the next model in `agents.defaults.model.fallbacks`.
|
||||
|
||||
1. **Auth profile rotation** within the current provider.
|
||||
2. **Model fallback** to the next model in `agents.defaults.model.fallbacks`.
|
||||
|
||||
This doc explains the runtime rules and the data that backs them.
|
||||
|
||||
@@ -24,12 +25,14 @@ OpenClaw uses **auth profiles** for both API keys and OAuth tokens.
|
||||
More detail: [/concepts/oauth](/concepts/oauth)
|
||||
|
||||
Credential types:
|
||||
|
||||
- `type: "api_key"` → `{ provider, key }`
|
||||
- `type: "oauth"` → `{ provider, access, refresh, expires, email? }` (+ `projectId`/`enterpriseUrl` for some providers)
|
||||
|
||||
## Profile IDs
|
||||
|
||||
OAuth logins create distinct profiles so multiple accounts can coexist.
|
||||
|
||||
- Default: `provider:default` when no email is available.
|
||||
- OAuth with email: `provider:<email>` (for example `google-antigravity:user@gmail.com`).
|
||||
|
||||
@@ -39,11 +42,12 @@ Profiles live in `~/.openclaw/agents/<agentId>/agent/auth-profiles.json` under `
|
||||
|
||||
When a provider has multiple profiles, OpenClaw chooses an order like this:
|
||||
|
||||
1) **Explicit config**: `auth.order[provider]` (if set).
|
||||
2) **Configured profiles**: `auth.profiles` filtered by provider.
|
||||
3) **Stored profiles**: entries in `auth-profiles.json` for the provider.
|
||||
1. **Explicit config**: `auth.order[provider]` (if set).
|
||||
2. **Configured profiles**: `auth.profiles` filtered by provider.
|
||||
3. **Stored profiles**: entries in `auth-profiles.json` for the provider.
|
||||
|
||||
If no explicit order is configured, OpenClaw uses a round‑robin order:
|
||||
|
||||
- **Primary key:** profile type (**OAuth before API keys**).
|
||||
- **Secondary key:** `usageStats.lastUsed` (oldest first, within each type).
|
||||
- **Cooldown/disabled profiles** are moved to the end, ordered by soonest expiry.
|
||||
@@ -52,6 +56,7 @@ If no explicit order is configured, OpenClaw uses a round‑robin order:
|
||||
|
||||
OpenClaw **pins the chosen auth profile per session** to keep provider caches warm.
|
||||
It does **not** rotate on every request. The pinned profile is reused until:
|
||||
|
||||
- the session is reset (`/new` / `/reset`)
|
||||
- a compaction completes (compaction count increments)
|
||||
- the profile is in cooldown/disabled
|
||||
@@ -67,6 +72,7 @@ are configured, OpenClaw moves to the next model instead of switching profiles.
|
||||
### Why OAuth can “look lost”
|
||||
|
||||
If you have both an OAuth profile and an API key profile for the same provider, round‑robin can switch between them across messages unless pinned. To force a single profile:
|
||||
|
||||
- Pin with `auth.order[provider] = ["provider:profileId"]`, or
|
||||
- Use a per-session override via `/model …` with a profile override (when supported by your UI/chat surface).
|
||||
|
||||
@@ -78,6 +84,7 @@ Format/invalid‑request errors (for example Cloud Code Assist tool call ID
|
||||
validation failures) are treated as failover‑worthy and use the same cooldowns.
|
||||
|
||||
Cooldowns use exponential backoff:
|
||||
|
||||
- 1 minute
|
||||
- 5 minutes
|
||||
- 25 minutes
|
||||
@@ -115,6 +122,7 @@ State is stored in `auth-profiles.json`:
|
||||
```
|
||||
|
||||
Defaults:
|
||||
|
||||
- Billing backoff starts at **5 hours**, doubles per billing failure, and caps at **24 hours**.
|
||||
- Backoff counters reset if the profile hasn’t failed for **24 hours** (configurable).
|
||||
|
||||
@@ -130,6 +138,7 @@ When a run starts with a model override (hooks or CLI), fallbacks still end at
|
||||
## Related config
|
||||
|
||||
See [Gateway configuration](/gateway/configuration) for:
|
||||
|
||||
- `auth.profiles` / `auth.order`
|
||||
- `auth.cooldowns.billingBackoffHours` / `auth.cooldowns.billingBackoffHoursByProvider`
|
||||
- `auth.cooldowns.billingMaxHours` / `auth.cooldowns.failureWindowHours`
|
||||
|
||||
@@ -4,6 +4,7 @@ read_when:
|
||||
- You need a provider-by-provider model setup reference
|
||||
- You want example configs or CLI onboarding commands for model providers
|
||||
---
|
||||
|
||||
# Model providers
|
||||
|
||||
This page covers **LLM/model providers** (not chat channels like WhatsApp/Telegram).
|
||||
@@ -29,7 +30,7 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no**
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: { defaults: { model: { primary: "openai/gpt-5.2" } } }
|
||||
agents: { defaults: { model: { primary: "openai/gpt-5.2" } } },
|
||||
}
|
||||
```
|
||||
|
||||
@@ -42,7 +43,7 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no**
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: { defaults: { model: { primary: "anthropic/claude-opus-4-5" } } }
|
||||
agents: { defaults: { model: { primary: "anthropic/claude-opus-4-5" } } },
|
||||
}
|
||||
```
|
||||
|
||||
@@ -55,7 +56,7 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no**
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: { defaults: { model: { primary: "openai-codex/gpt-5.2" } } }
|
||||
agents: { defaults: { model: { primary: "openai-codex/gpt-5.2" } } },
|
||||
}
|
||||
```
|
||||
|
||||
@@ -68,7 +69,7 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no**
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: { defaults: { model: { primary: "opencode/claude-opus-4-5" } } }
|
||||
agents: { defaults: { model: { primary: "opencode/claude-opus-4-5" } } },
|
||||
}
|
||||
```
|
||||
|
||||
@@ -132,17 +133,18 @@ Moonshot uses OpenAI-compatible endpoints, so configure it as a custom provider:
|
||||
- Auth: `MOONSHOT_API_KEY`
|
||||
- Example model: `moonshot/kimi-k2.5`
|
||||
- Kimi K2 model IDs:
|
||||
{/* moonshot-kimi-k2-model-refs:start */}
|
||||
{/_ moonshot-kimi-k2-model-refs:start _/}
|
||||
- `moonshot/kimi-k2.5`
|
||||
- `moonshot/kimi-k2-0905-preview`
|
||||
- `moonshot/kimi-k2-turbo-preview`
|
||||
- `moonshot/kimi-k2-thinking`
|
||||
- `moonshot/kimi-k2-thinking-turbo`
|
||||
{/* moonshot-kimi-k2-model-refs:end */}
|
||||
{/_ moonshot-kimi-k2-model-refs:end _/}
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: { model: { primary: "moonshot/kimi-k2.5" } }
|
||||
defaults: { model: { primary: "moonshot/kimi-k2.5" } },
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
@@ -151,10 +153,10 @@ Moonshot uses OpenAI-compatible endpoints, so configure it as a custom provider:
|
||||
baseUrl: "https://api.moonshot.ai/v1",
|
||||
apiKey: "${MOONSHOT_API_KEY}",
|
||||
api: "openai-completions",
|
||||
models: [{ id: "kimi-k2.5", name: "Kimi K2.5" }]
|
||||
}
|
||||
}
|
||||
}
|
||||
models: [{ id: "kimi-k2.5", name: "Kimi K2.5" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -170,8 +172,8 @@ Kimi Coding uses Moonshot AI's Anthropic-compatible endpoint:
|
||||
{
|
||||
env: { KIMI_API_KEY: "sk-..." },
|
||||
agents: {
|
||||
defaults: { model: { primary: "kimi-coding/k2p5" } }
|
||||
}
|
||||
defaults: { model: { primary: "kimi-coding/k2p5" } },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -186,6 +188,7 @@ openclaw models auth login --provider qwen-portal --set-default
|
||||
```
|
||||
|
||||
Model refs:
|
||||
|
||||
- `qwen-portal/coder-model`
|
||||
- `qwen-portal/vision-model`
|
||||
|
||||
@@ -203,7 +206,7 @@ Synthetic provides Anthropic-compatible models behind the `synthetic` provider:
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: { model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.1" } }
|
||||
defaults: { model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.1" } },
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
@@ -212,10 +215,10 @@ Synthetic provides Anthropic-compatible models behind the `synthetic` provider:
|
||||
baseUrl: "https://api.synthetic.new/anthropic",
|
||||
apiKey: "${SYNTHETIC_API_KEY}",
|
||||
api: "anthropic-messages",
|
||||
models: [{ id: "hf:MiniMaxAI/MiniMax-M2.1", name: "MiniMax M2.1" }]
|
||||
}
|
||||
}
|
||||
}
|
||||
models: [{ id: "hf:MiniMaxAI/MiniMax-M2.1", name: "MiniMax M2.1" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -245,8 +248,8 @@ ollama pull llama3.3
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: { model: { primary: "ollama/llama3.3" } }
|
||||
}
|
||||
defaults: { model: { primary: "ollama/llama3.3" } },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -261,8 +264,8 @@ Example (OpenAI‑compatible):
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "lmstudio/minimax-m2.1-gs32" },
|
||||
models: { "lmstudio/minimax-m2.1-gs32": { alias: "Minimax" } }
|
||||
}
|
||||
models: { "lmstudio/minimax-m2.1-gs32": { alias: "Minimax" } },
|
||||
},
|
||||
},
|
||||
models: {
|
||||
providers: {
|
||||
@@ -278,16 +281,17 @@ Example (OpenAI‑compatible):
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 8192
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
maxTokens: 8192,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- For custom providers, `reasoning`, `input`, `cost`, `contextWindow`, and `maxTokens` are optional.
|
||||
When omitted, OpenClaw defaults to:
|
||||
- `reasoning: false`
|
||||
|
||||
@@ -5,6 +5,7 @@ read_when:
|
||||
- Changing model fallback behavior or selection UX
|
||||
- Updating model scan probes (tools/images)
|
||||
---
|
||||
|
||||
# Models CLI
|
||||
|
||||
See [/concepts/model-failover](/concepts/model-failover) for auth profile
|
||||
@@ -15,12 +16,13 @@ Quick provider overview + examples: [/concepts/model-providers](/concepts/model-
|
||||
|
||||
OpenClaw selects models in this order:
|
||||
|
||||
1) **Primary** model (`agents.defaults.model.primary` or `agents.defaults.model`).
|
||||
2) **Fallbacks** in `agents.defaults.model.fallbacks` (in order).
|
||||
3) **Provider auth failover** happens inside a provider before moving to the
|
||||
1. **Primary** model (`agents.defaults.model.primary` or `agents.defaults.model`).
|
||||
2. **Fallbacks** in `agents.defaults.model.fallbacks` (in order).
|
||||
3. **Provider auth failover** happens inside a provider before moving to the
|
||||
next model.
|
||||
|
||||
Related:
|
||||
|
||||
- `agents.defaults.models` is the allowlist/catalog of models OpenClaw can use (plus aliases).
|
||||
- `agents.defaults.imageModel` is used **only when** the primary model can’t accept images.
|
||||
- Per-agent defaults can override `agents.defaults.model` via `agents.list[].model` plus bindings (see [/concepts/multi-agent](/concepts/multi-agent)).
|
||||
@@ -80,9 +82,9 @@ Example allowlist config:
|
||||
model: { primary: "anthropic/claude-sonnet-4-5" },
|
||||
models: {
|
||||
"anthropic/claude-sonnet-4-5": { alias: "Sonnet" },
|
||||
"anthropic/claude-opus-4-5": { alias: "Opus" }
|
||||
}
|
||||
}
|
||||
"anthropic/claude-opus-4-5": { alias: "Opus" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -99,6 +101,7 @@ You can switch models for the current session without restarting:
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `/model` (and `/model list`) is a compact, numbered picker (model family + available providers).
|
||||
- `/model <#>` selects from that picker.
|
||||
- `/model status` is the detailed view (auth candidates and, when configured, provider endpoint `baseUrl` + `api` mode).
|
||||
@@ -181,12 +184,14 @@ Probing requires an OpenRouter API key (from auth profiles or
|
||||
`OPENROUTER_API_KEY`). Without a key, use `--no-probe` to list candidates only.
|
||||
|
||||
Scan results are ranked by:
|
||||
1) Image support
|
||||
2) Tool latency
|
||||
3) Context size
|
||||
4) Parameter count
|
||||
|
||||
1. Image support
|
||||
2. Tool latency
|
||||
3. Context size
|
||||
4. Parameter count
|
||||
|
||||
Input
|
||||
|
||||
- OpenRouter `/models` list (filter `:free`)
|
||||
- Requires OpenRouter API key from auth profiles or `OPENROUTER_API_KEY` (see [/environment](/environment))
|
||||
- Optional filters: `--max-age-days`, `--min-params`, `--provider`, `--max-candidates`
|
||||
|
||||
@@ -7,7 +7,7 @@ status: active
|
||||
|
||||
# Multi-Agent Routing
|
||||
|
||||
Goal: multiple *isolated* agents (separate workspace + `agentDir` + sessions), plus multiple channel accounts (e.g. two WhatsApps) in one running Gateway. Inbound is routed to an agent via bindings.
|
||||
Goal: multiple _isolated_ agents (separate workspace + `agentDir` + sessions), plus multiple channel accounts (e.g. two WhatsApps) in one running Gateway. Inbound is routed to an agent via bindings.
|
||||
|
||||
## What is “one agent”?
|
||||
|
||||
@@ -93,23 +93,24 @@ Example:
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "alex", workspace: "~/.openclaw/workspace-alex" },
|
||||
{ id: "mia", workspace: "~/.openclaw/workspace-mia" }
|
||||
]
|
||||
{ id: "mia", workspace: "~/.openclaw/workspace-mia" },
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
{ agentId: "alex", match: { channel: "whatsapp", peer: { kind: "dm", id: "+15551230001" } } },
|
||||
{ agentId: "mia", match: { channel: "whatsapp", peer: { kind: "dm", id: "+15551230002" } } }
|
||||
{ agentId: "mia", match: { channel: "whatsapp", peer: { kind: "dm", id: "+15551230002" } } },
|
||||
],
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["+15551230001", "+15551230002"]
|
||||
}
|
||||
}
|
||||
allowFrom: ["+15551230001", "+15551230002"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- DM access control is **global per WhatsApp account** (pairing/allowlist), not per agent.
|
||||
- For shared groups, bind the group to one agent or use [Broadcast groups](/broadcast-groups).
|
||||
|
||||
@@ -214,24 +215,25 @@ Split by channel: route WhatsApp to a fast everyday agent and Telegram to an Opu
|
||||
id: "chat",
|
||||
name: "Everyday",
|
||||
workspace: "~/.openclaw/workspace-chat",
|
||||
model: "anthropic/claude-sonnet-4-5"
|
||||
model: "anthropic/claude-sonnet-4-5",
|
||||
},
|
||||
{
|
||||
id: "opus",
|
||||
name: "Deep Work",
|
||||
workspace: "~/.openclaw/workspace-opus",
|
||||
model: "anthropic/claude-opus-4-5"
|
||||
}
|
||||
]
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
},
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
{ agentId: "chat", match: { channel: "whatsapp" } },
|
||||
{ agentId: "opus", match: { channel: "telegram" } }
|
||||
]
|
||||
{ agentId: "opus", match: { channel: "telegram" } },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- If you have multiple accounts for a channel, add `accountId` to the binding (for example `{ channel: "whatsapp", accountId: "personal" }`).
|
||||
- To route a single DM/group to Opus while keeping the rest on chat, add a `match.peer` binding for that peer; peer matches always win over channel-wide rules.
|
||||
|
||||
@@ -243,14 +245,24 @@ Keep WhatsApp on the fast agent, but route one DM to Opus:
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "chat", name: "Everyday", workspace: "~/.openclaw/workspace-chat", model: "anthropic/claude-sonnet-4-5" },
|
||||
{ id: "opus", name: "Deep Work", workspace: "~/.openclaw/workspace-opus", model: "anthropic/claude-opus-4-5" }
|
||||
]
|
||||
{
|
||||
id: "chat",
|
||||
name: "Everyday",
|
||||
workspace: "~/.openclaw/workspace-chat",
|
||||
model: "anthropic/claude-sonnet-4-5",
|
||||
},
|
||||
{
|
||||
id: "opus",
|
||||
name: "Deep Work",
|
||||
workspace: "~/.openclaw/workspace-opus",
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
},
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
{ agentId: "opus", match: { channel: "whatsapp", peer: { kind: "dm", id: "+15551234567" } } },
|
||||
{ agentId: "chat", match: { channel: "whatsapp" } }
|
||||
]
|
||||
{ agentId: "chat", match: { channel: "whatsapp" } },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
@@ -271,32 +283,41 @@ and a tighter tool policy:
|
||||
workspace: "~/.openclaw/workspace-family",
|
||||
identity: { name: "Family Bot" },
|
||||
groupChat: {
|
||||
mentionPatterns: ["@family", "@familybot", "@Family Bot"]
|
||||
mentionPatterns: ["@family", "@familybot", "@Family Bot"],
|
||||
},
|
||||
sandbox: {
|
||||
mode: "all",
|
||||
scope: "agent"
|
||||
scope: "agent",
|
||||
},
|
||||
tools: {
|
||||
allow: ["exec", "read", "sessions_list", "sessions_history", "sessions_send", "sessions_spawn", "session_status"],
|
||||
deny: ["write", "edit", "apply_patch", "browser", "canvas", "nodes", "cron"]
|
||||
}
|
||||
}
|
||||
]
|
||||
allow: [
|
||||
"exec",
|
||||
"read",
|
||||
"sessions_list",
|
||||
"sessions_history",
|
||||
"sessions_send",
|
||||
"sessions_spawn",
|
||||
"session_status",
|
||||
],
|
||||
deny: ["write", "edit", "apply_patch", "browser", "canvas", "nodes", "cron"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
agentId: "family",
|
||||
match: {
|
||||
channel: "whatsapp",
|
||||
peer: { kind: "group", id: "120363999999999999@g.us" }
|
||||
}
|
||||
}
|
||||
]
|
||||
peer: { kind: "group", id: "120363999999999999@g.us" },
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Tool allow/deny lists are **tools**, not skills. If a skill needs to run a
|
||||
binary, ensure `exec` is allowed and the binary exists in the sandbox.
|
||||
- For stricter gating, set `agents.list[].groupChat.mentionPatterns` and keep
|
||||
@@ -343,6 +364,7 @@ Note: `setupCommand` lives under `sandbox.docker` and runs once on container cre
|
||||
Per-agent `sandbox.docker.*` overrides are ignored when the resolved scope is `"shared"`.
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- **Security isolation**: Restrict tools for untrusted agents
|
||||
- **Resource control**: Sandbox specific agents while keeping others on host
|
||||
- **Flexible policies**: Different permissions per agent
|
||||
|
||||
@@ -6,6 +6,7 @@ read_when:
|
||||
- You want setup-token or OAuth auth flows
|
||||
- You want multiple accounts or profile routing
|
||||
---
|
||||
|
||||
# OAuth
|
||||
|
||||
OpenClaw supports “subscription auth” via OAuth for providers that offer it (notably **OpenAI Codex (ChatGPT OAuth)**). For Anthropic subscriptions, use the **setup-token** flow. This page explains:
|
||||
@@ -26,9 +27,11 @@ openclaw models auth login --provider <id>
|
||||
OAuth providers commonly mint a **new refresh token** during login/refresh flows. Some providers (or OAuth clients) can invalidate older refresh tokens when a new one is issued for the same user/app.
|
||||
|
||||
Practical symptom:
|
||||
- you log in via OpenClaw *and* via Claude Code / Codex CLI → one of them randomly gets “logged out” later
|
||||
|
||||
- you log in via OpenClaw _and_ via Claude Code / Codex CLI → one of them randomly gets “logged out” later
|
||||
|
||||
To reduce that, OpenClaw treats `auth-profiles.json` as a **token sink**:
|
||||
|
||||
- the runtime reads credentials from **one place**
|
||||
- we can keep multiple profiles and route them deterministically
|
||||
|
||||
@@ -40,6 +43,7 @@ Secrets are stored **per-agent**:
|
||||
- Runtime cache (managed automatically; don’t edit): `~/.openclaw/agents/<agentId>/agent/auth.json`
|
||||
|
||||
Legacy import-only file (still supported, but not the main store):
|
||||
|
||||
- `~/.openclaw/credentials/oauth.json` (imported into `auth-profiles.json` on first use)
|
||||
|
||||
All of the above also respect `$OPENCLAW_STATE_DIR` (state dir override). Full reference: [/gateway/configuration](/gateway/configuration#auth-storage-oauth--api-keys)
|
||||
@@ -72,9 +76,9 @@ OpenClaw’s interactive login flows are implemented in `@mariozechner/pi-ai` an
|
||||
|
||||
Flow shape:
|
||||
|
||||
1) run `claude setup-token`
|
||||
2) paste the token into OpenClaw
|
||||
3) store as a token auth profile (no refresh)
|
||||
1. run `claude setup-token`
|
||||
2. paste the token into OpenClaw
|
||||
3. store as a token auth profile (no refresh)
|
||||
|
||||
The wizard path is `openclaw onboard` → auth choice `setup-token` (Anthropic).
|
||||
|
||||
@@ -82,12 +86,12 @@ The wizard path is `openclaw onboard` → auth choice `setup-token` (Anthropic).
|
||||
|
||||
Flow shape (PKCE):
|
||||
|
||||
1) generate PKCE verifier/challenge + random `state`
|
||||
2) open `https://auth.openai.com/oauth/authorize?...`
|
||||
3) try to capture callback on `http://127.0.0.1:1455/auth/callback`
|
||||
4) if callback can’t bind (or you’re remote/headless), paste the redirect URL/code
|
||||
5) exchange at `https://auth.openai.com/oauth/token`
|
||||
6) extract `accountId` from the access token and store `{ access, refresh, expires, accountId }`
|
||||
1. generate PKCE verifier/challenge + random `state`
|
||||
2. open `https://auth.openai.com/oauth/authorize?...`
|
||||
3. try to capture callback on `http://127.0.0.1:1455/auth/callback`
|
||||
4. if callback can’t bind (or you’re remote/headless), paste the redirect URL/code
|
||||
5. exchange at `https://auth.openai.com/oauth/token`
|
||||
6. extract `accountId` from the access token and store `{ access, refresh, expires, accountId }`
|
||||
|
||||
Wizard path is `openclaw onboard` → auth choice `openai-codex`.
|
||||
|
||||
@@ -96,6 +100,7 @@ Wizard path is `openclaw onboard` → auth choice `openai-codex`.
|
||||
Profiles store an `expires` timestamp.
|
||||
|
||||
At runtime:
|
||||
|
||||
- if `expires` is in the future → use the stored access token
|
||||
- if expired → refresh (under a file lock) and overwrite the stored credentials
|
||||
|
||||
@@ -121,15 +126,19 @@ Then configure auth per-agent (wizard) and route chats to the right agent.
|
||||
`auth-profiles.json` supports multiple profile IDs for the same provider.
|
||||
|
||||
Pick which profile is used:
|
||||
|
||||
- globally via config ordering (`auth.order`)
|
||||
- per-session via `/model ...@<profileId>`
|
||||
|
||||
Example (session override):
|
||||
|
||||
- `/model Opus@anthropic:work`
|
||||
|
||||
How to see what profile IDs exist:
|
||||
|
||||
- `openclaw channels list --json` (shows `auth[]`)
|
||||
|
||||
Related docs:
|
||||
|
||||
- [/concepts/model-failover](/concepts/model-failover) (rotation + cooldown rules)
|
||||
- [/tools/slash-commands](/tools/slash-commands) (command surface)
|
||||
|
||||
@@ -5,9 +5,11 @@ read_when:
|
||||
- Investigating duplicate or stale instance rows
|
||||
- Changing gateway WS connect or system-event beacons
|
||||
---
|
||||
|
||||
# Presence
|
||||
|
||||
OpenClaw “presence” is a lightweight, best‑effort view of:
|
||||
|
||||
- the **Gateway** itself, and
|
||||
- **clients connected to the Gateway** (mac app, WebChat, CLI, etc.)
|
||||
|
||||
@@ -53,6 +55,7 @@ Clients can send richer periodic beacons via the `system-event` method. The mac
|
||||
app uses this to report host name, IP, and `lastInputSeconds`.
|
||||
|
||||
### 4) Node connects (role: node)
|
||||
|
||||
When a node connects over the Gateway WebSocket with `role: node`, the Gateway
|
||||
upserts a presence entry for that node (same flow as other WS clients).
|
||||
|
||||
|
||||
@@ -3,15 +3,18 @@ summary: "Command queue design that serializes inbound auto-reply runs"
|
||||
read_when:
|
||||
- Changing auto-reply execution or concurrency
|
||||
---
|
||||
|
||||
# Command Queue (2026-01-16)
|
||||
|
||||
We serialize inbound auto-reply runs (all channels) through a tiny in-process queue to prevent multiple agent runs from colliding, while still allowing safe parallelism across sessions.
|
||||
|
||||
## Why
|
||||
|
||||
- Auto-reply runs can be expensive (LLM calls) and can collide when multiple inbound messages arrive close together.
|
||||
- Serializing avoids competing for shared resources (session files, logs, CLI stdin) and reduces the chance of upstream rate limits.
|
||||
|
||||
## How it works
|
||||
|
||||
- A lane-aware FIFO queue drains each lane with a configurable concurrency cap (default 1 for unconfigured lanes; main defaults to 4, subagent to 8).
|
||||
- `runEmbeddedPiAgent` enqueues by **session key** (lane `session:<key>`) to guarantee only one active run per session.
|
||||
- Each session run is then queued into a **global lane** (`main` by default) so overall parallelism is capped by `agents.defaults.maxConcurrent`.
|
||||
@@ -19,7 +22,9 @@ We serialize inbound auto-reply runs (all channels) through a tiny in-process qu
|
||||
- Typing indicators still fire immediately on enqueue (when supported by the channel) so user experience is unchanged while we wait our turn.
|
||||
|
||||
## Queue modes (per channel)
|
||||
|
||||
Inbound messages can steer the current run, wait for a followup turn, or do both:
|
||||
|
||||
- `steer`: inject immediately into the current run (cancels pending tool calls after the next tool boundary). If not streaming, falls back to followup.
|
||||
- `followup`: enqueue for the next agent turn after the current run ends.
|
||||
- `collect`: coalesce all queued messages into a **single** followup turn (default). If messages target different channels/threads, they drain individually to preserve routing.
|
||||
@@ -33,6 +38,7 @@ one response per inbound message.
|
||||
Send `/queue collect` as a standalone command (per-session) or set `messages.queue.byChannel.discord: "collect"`.
|
||||
|
||||
Defaults (when unset in config):
|
||||
|
||||
- All surfaces → `collect`
|
||||
|
||||
Configure globally or per channel via `messages.queue`:
|
||||
@@ -45,14 +51,16 @@ Configure globally or per channel via `messages.queue`:
|
||||
debounceMs: 1000,
|
||||
cap: 20,
|
||||
drop: "summarize",
|
||||
byChannel: { discord: "collect" }
|
||||
}
|
||||
}
|
||||
byChannel: { discord: "collect" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Queue options
|
||||
|
||||
Options apply to `followup`, `collect`, and `steer-backlog` (and to `steer` when it falls back to followup):
|
||||
|
||||
- `debounceMs`: wait for quiet before starting a followup turn (prevents “continue, continue”).
|
||||
- `cap`: max queued messages per session.
|
||||
- `drop`: overflow policy (`old`, `new`, `summarize`).
|
||||
@@ -61,11 +69,13 @@ Summarize keeps a short bullet list of dropped messages and injects it as a synt
|
||||
Defaults: `debounceMs: 1000`, `cap: 20`, `drop: summarize`.
|
||||
|
||||
## Per-session overrides
|
||||
|
||||
- Send `/queue <mode>` as a standalone command to store the mode for the current session.
|
||||
- Options can be combined: `/queue collect debounce:2s cap:25 drop:summarize`
|
||||
- `/queue default` or `/queue reset` clears the session override.
|
||||
|
||||
## Scope and guarantees
|
||||
|
||||
- Applies to auto-reply agent runs across all inbound channels that use the gateway reply pipeline (WhatsApp web, Telegram, Slack, Discord, Signal, iMessage, webchat, etc.).
|
||||
- Default lane (`main`) is process-wide for inbound + main heartbeats; set `agents.defaults.maxConcurrent` to allow multiple sessions in parallel.
|
||||
- Additional lanes may exist (e.g. `cron`, `subagent`) so background jobs can run in parallel without blocking inbound replies.
|
||||
@@ -73,5 +83,6 @@ Defaults: `debounceMs: 1000`, `cap: 20`, `drop: summarize`.
|
||||
- No external dependencies or background worker threads; pure TypeScript + promises.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If commands seem stuck, enable verbose logs and look for “queued for …ms” lines to confirm the queue is draining.
|
||||
- If you need queue depth, enable verbose logs and watch for queue timing lines.
|
||||
|
||||
@@ -4,14 +4,17 @@ read_when:
|
||||
- Updating provider retry behavior or defaults
|
||||
- Debugging provider send errors or rate limits
|
||||
---
|
||||
|
||||
# Retry policy
|
||||
|
||||
## Goals
|
||||
|
||||
- Retry per HTTP request, not per multi-step flow.
|
||||
- Preserve ordering by retrying only the current step.
|
||||
- Avoid duplicating non-idempotent operations.
|
||||
|
||||
## Defaults
|
||||
|
||||
- Attempts: 3
|
||||
- Max delay cap: 30000 ms
|
||||
- Jitter: 0.1 (10 percent)
|
||||
@@ -20,16 +23,20 @@ read_when:
|
||||
- Discord min delay: 500 ms
|
||||
|
||||
## Behavior
|
||||
|
||||
### Discord
|
||||
|
||||
- Retries only on rate-limit errors (HTTP 429).
|
||||
- Uses Discord `retry_after` when available, otherwise exponential backoff.
|
||||
|
||||
### Telegram
|
||||
|
||||
- Retries on transient errors (429, timeout, connect/reset/closed, temporarily unavailable).
|
||||
- Uses `retry_after` when available, otherwise exponential backoff.
|
||||
- Markdown parse errors are not retried; they fall back to plain text.
|
||||
|
||||
## Configuration
|
||||
|
||||
Set retry policy per provider in `~/.openclaw/openclaw.json`:
|
||||
|
||||
```json5
|
||||
@@ -40,21 +47,22 @@ Set retry policy per provider in `~/.openclaw/openclaw.json`:
|
||||
attempts: 3,
|
||||
minDelayMs: 400,
|
||||
maxDelayMs: 30000,
|
||||
jitter: 0.1
|
||||
}
|
||||
jitter: 0.1,
|
||||
},
|
||||
},
|
||||
discord: {
|
||||
retry: {
|
||||
attempts: 3,
|
||||
minDelayMs: 500,
|
||||
maxDelayMs: 30000,
|
||||
jitter: 0.1
|
||||
}
|
||||
}
|
||||
}
|
||||
jitter: 0.1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Retries apply per request (message send, media upload, reaction, poll, sticker).
|
||||
- Composite flows do not retry completed steps.
|
||||
|
||||
@@ -4,29 +4,34 @@ read_when:
|
||||
- You want to reduce LLM context growth from tool outputs
|
||||
- You are tuning agents.defaults.contextPruning
|
||||
---
|
||||
|
||||
# Session Pruning
|
||||
|
||||
Session pruning trims **old tool results** from the in-memory context right before each LLM call. It does **not** rewrite the on-disk session history (`*.jsonl`).
|
||||
|
||||
## When it runs
|
||||
|
||||
- When `mode: "cache-ttl"` is enabled and the last Anthropic call for the session is older than `ttl`.
|
||||
- Only affects the messages sent to the model for that request.
|
||||
- Only active for Anthropic API calls (and OpenRouter Anthropic models).
|
||||
- For best results, match `ttl` to your model `cacheControlTtl`.
|
||||
- After a prune, the TTL window resets so subsequent requests keep cache until `ttl` expires again.
|
||||
- Only active for Anthropic API calls (and OpenRouter Anthropic models).
|
||||
- For best results, match `ttl` to your model `cacheControlTtl`.
|
||||
- After a prune, the TTL window resets so subsequent requests keep cache until `ttl` expires again.
|
||||
|
||||
## Smart defaults (Anthropic)
|
||||
|
||||
- **OAuth or setup-token** profiles: enable `cache-ttl` pruning and set heartbeat to `1h`.
|
||||
- **API key** profiles: enable `cache-ttl` pruning, set heartbeat to `30m`, and default `cacheControlTtl` to `1h` on Anthropic models.
|
||||
- If you set any of these values explicitly, OpenClaw does **not** override them.
|
||||
|
||||
## What this improves (cost + cache behavior)
|
||||
|
||||
- **Why prune:** Anthropic prompt caching only applies within the TTL. If a session goes idle past the TTL, the next request re-caches the full prompt unless you trim it first.
|
||||
- **What gets cheaper:** pruning reduces the **cacheWrite** size for that first request after the TTL expires.
|
||||
- **Why the TTL reset matters:** once pruning runs, the cache window resets, so follow‑up requests can reuse the freshly cached prompt instead of re-caching the full history again.
|
||||
- **What it does not do:** pruning doesn’t add tokens or “double” costs; it only changes what gets cached on that first post‑TTL request.
|
||||
|
||||
## What can be pruned
|
||||
|
||||
- Only `toolResult` messages.
|
||||
- User + assistant messages are **never** modified.
|
||||
- The last `keepLastAssistants` assistant messages are protected; tool results after that cutoff are not pruned.
|
||||
@@ -34,34 +39,42 @@ Session pruning trims **old tool results** from the in-memory context right befo
|
||||
- Tool results containing **image blocks** are skipped (never trimmed/cleared).
|
||||
|
||||
## Context window estimation
|
||||
|
||||
Pruning uses an estimated context window (chars ≈ tokens × 4). The window size is resolved in this order:
|
||||
1) Model definition `contextWindow` (from the model registry).
|
||||
2) `models.providers.*.models[].contextWindow` override.
|
||||
3) `agents.defaults.contextTokens`.
|
||||
4) Default `200000` tokens.
|
||||
|
||||
1. Model definition `contextWindow` (from the model registry).
|
||||
2. `models.providers.*.models[].contextWindow` override.
|
||||
3. `agents.defaults.contextTokens`.
|
||||
4. Default `200000` tokens.
|
||||
|
||||
## Mode
|
||||
|
||||
### cache-ttl
|
||||
|
||||
- Pruning only runs if the last Anthropic call is older than `ttl` (default `5m`).
|
||||
- When it runs: same soft-trim + hard-clear behavior as before.
|
||||
|
||||
## Soft vs hard pruning
|
||||
|
||||
- **Soft-trim**: only for oversized tool results.
|
||||
- Keeps head + tail, inserts `...`, and appends a note with the original size.
|
||||
- Skips results with image blocks.
|
||||
- **Hard-clear**: replaces the entire tool result with `hardClear.placeholder`.
|
||||
|
||||
## Tool selection
|
||||
|
||||
- `tools.allow` / `tools.deny` support `*` wildcards.
|
||||
- Deny wins.
|
||||
- Matching is case-insensitive.
|
||||
- Empty allow list => all tools allowed.
|
||||
|
||||
## Interaction with other limits
|
||||
|
||||
- Built-in tools already truncate their own output; session pruning is an extra layer that prevents long-running chats from accumulating too much tool output in the model context.
|
||||
- Compaction is separate: compaction summarizes and persists, pruning is transient per request. See [/concepts/compaction](/concepts/compaction).
|
||||
|
||||
## Defaults (when enabled)
|
||||
|
||||
- `ttl`: `"5m"`
|
||||
- `keepLastAssistants`: `3`
|
||||
- `softTrimRatio`: `0.3`
|
||||
@@ -71,33 +84,37 @@ Pruning uses an estimated context window (chars ≈ tokens × 4). The window siz
|
||||
- `hardClear`: `{ enabled: true, placeholder: "[Old tool result content cleared]" }`
|
||||
|
||||
## Examples
|
||||
|
||||
Default (off):
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
contextPruning: { mode: "off" }
|
||||
}
|
||||
contextPruning: { mode: "off" },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Enable TTL-aware pruning:
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
contextPruning: { mode: "cache-ttl", ttl: "5m" }
|
||||
}
|
||||
contextPruning: { mode: "cache-ttl", ttl: "5m" },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Restrict pruning to specific tools:
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
contextPruning: {
|
||||
mode: "cache-ttl",
|
||||
tools: { allow: ["exec", "read"], deny: ["*image*"] }
|
||||
}
|
||||
}
|
||||
tools: { allow: ["exec", "read"], deny: ["*image*"] },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -9,12 +9,14 @@ read_when:
|
||||
Goal: small, hard-to-misuse tool set so agents can list sessions, fetch history, and send to another session.
|
||||
|
||||
## Tool Names
|
||||
|
||||
- `sessions_list`
|
||||
- `sessions_history`
|
||||
- `sessions_send`
|
||||
- `sessions_spawn`
|
||||
|
||||
## Key Model
|
||||
|
||||
- Main direct chat bucket is always the literal key `"main"` (resolved to the current agent’s main key).
|
||||
- Group chats use `agent:<agentId>:<channel>:group:<id>` or `agent:<agentId>:<channel>:channel:<id>` (pass the full key).
|
||||
- Cron jobs use `cron:<job.id>`.
|
||||
@@ -24,20 +26,24 @@ Goal: small, hard-to-misuse tool set so agents can list sessions, fetch history,
|
||||
`global` and `unknown` are reserved values and are never listed. If `session.scope = "global"`, we alias it to `main` for all tools so callers never see `global`.
|
||||
|
||||
## sessions_list
|
||||
|
||||
List sessions as an array of rows.
|
||||
|
||||
Parameters:
|
||||
|
||||
- `kinds?: string[]` filter: any of `"main" | "group" | "cron" | "hook" | "node" | "other"`
|
||||
- `limit?: number` max rows (default: server default, clamp e.g. 200)
|
||||
- `activeMinutes?: number` only sessions updated within N minutes
|
||||
- `messageLimit?: number` 0 = no messages (default 0); >0 = include last N messages
|
||||
|
||||
Behavior:
|
||||
|
||||
- `messageLimit > 0` fetches `chat.history` per session and includes the last N messages.
|
||||
- Tool results are filtered out in list output; use `sessions_history` for tool messages.
|
||||
- When running in a **sandboxed** agent session, session tools default to **spawned-only visibility** (see below).
|
||||
|
||||
Row shape (JSON):
|
||||
|
||||
- `key`: session key (string)
|
||||
- `kind`: `main | group | cron | hook | node | other`
|
||||
- `channel`: `whatsapp | telegram | discord | signal | imessage | webchat | internal | unknown`
|
||||
@@ -53,27 +59,33 @@ Row shape (JSON):
|
||||
- `messages?` (only when `messageLimit > 0`)
|
||||
|
||||
## sessions_history
|
||||
|
||||
Fetch transcript for one session.
|
||||
|
||||
Parameters:
|
||||
|
||||
- `sessionKey` (required; accepts session key or `sessionId` from `sessions_list`)
|
||||
- `limit?: number` max messages (server clamps)
|
||||
- `includeTools?: boolean` (default false)
|
||||
|
||||
Behavior:
|
||||
|
||||
- `includeTools=false` filters `role: "toolResult"` messages.
|
||||
- Returns messages array in the raw transcript format.
|
||||
- When given a `sessionId`, OpenClaw resolves it to the corresponding session key (missing ids error).
|
||||
|
||||
## sessions_send
|
||||
|
||||
Send a message into another session.
|
||||
|
||||
Parameters:
|
||||
|
||||
- `sessionKey` (required; accepts session key or `sessionId` from `sessions_list`)
|
||||
- `message` (required)
|
||||
- `timeoutSeconds?: number` (default >0; 0 = fire-and-forget)
|
||||
|
||||
Behavior:
|
||||
|
||||
- `timeoutSeconds = 0`: enqueue and return `{ runId, status: "accepted" }`.
|
||||
- `timeoutSeconds > 0`: wait up to N seconds for completion, then return `{ runId, status: "ok", reply }`.
|
||||
- If wait times out: `{ runId, status: "timeout", error }`. Run continues; call `sessions_history` later.
|
||||
@@ -91,12 +103,14 @@ Behavior:
|
||||
- Announce step includes the original request + round‑1 reply + latest ping‑pong reply.
|
||||
|
||||
## Channel Field
|
||||
|
||||
- For groups, `channel` is the channel recorded on the session entry.
|
||||
- For direct chats, `channel` maps from `lastChannel`.
|
||||
- For cron/hook/node, `channel` is `internal`.
|
||||
- If missing, `channel` is `unknown`.
|
||||
|
||||
## Security / Send Policy
|
||||
|
||||
Policy-based blocking by channel/chat type (not per session id).
|
||||
|
||||
```json
|
||||
@@ -116,17 +130,21 @@ Policy-based blocking by channel/chat type (not per session id).
|
||||
```
|
||||
|
||||
Runtime override (per session entry):
|
||||
|
||||
- `sendPolicy: "allow" | "deny"` (unset = inherit config)
|
||||
- Settable via `sessions.patch` or owner-only `/send on|off|inherit` (standalone message).
|
||||
|
||||
Enforcement points:
|
||||
|
||||
- `chat.send` / `agent` (gateway)
|
||||
- auto-reply delivery logic
|
||||
|
||||
## sessions_spawn
|
||||
|
||||
Spawn a sub-agent run in an isolated session and announce the result back to the requester chat channel.
|
||||
|
||||
Parameters:
|
||||
|
||||
- `task` (required)
|
||||
- `label?` (optional; used for logs/UI)
|
||||
- `agentId?` (optional; spawn under another agent id if allowed)
|
||||
@@ -135,12 +153,15 @@ Parameters:
|
||||
- `cleanup?` (`delete|keep`, default `keep`)
|
||||
|
||||
Allowlist:
|
||||
|
||||
- `agents.list[].subagents.allowAgents`: list of agent ids allowed via `agentId` (`["*"]` to allow any). Default: only the requester agent.
|
||||
|
||||
Discovery:
|
||||
|
||||
- Use `agents_list` to discover which agent ids are allowed for `sessions_spawn`.
|
||||
|
||||
Behavior:
|
||||
|
||||
- Starts a new `agent:<agentId>:subagent:<uuid>` session with `deliver: false`.
|
||||
- Sub-agents default to the full tool set **minus session tools** (configurable via `tools.subagents.tools`).
|
||||
- Sub-agents are not allowed to call `sessions_spawn` (no sub-agent → sub-agent spawning).
|
||||
@@ -163,9 +184,9 @@ Config:
|
||||
defaults: {
|
||||
sandbox: {
|
||||
// default: "spawned"
|
||||
sessionToolsVisibility: "spawned" // or "all"
|
||||
}
|
||||
}
|
||||
}
|
||||
sessionToolsVisibility: "spawned", // or "all"
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -3,24 +3,28 @@ summary: "Session management rules, keys, and persistence for chats"
|
||||
read_when:
|
||||
- Modifying session handling or storage
|
||||
---
|
||||
|
||||
# Session Management
|
||||
|
||||
OpenClaw treats **one direct-chat session per agent** as primary. Direct chats collapse to `agent:<agentId>:<mainKey>` (default `main`), while group/channel chats get their own keys. `session.mainKey` is honored.
|
||||
|
||||
Use `session.dmScope` to control how **direct messages** are grouped:
|
||||
|
||||
- `main` (default): all DMs share the main session for continuity.
|
||||
- `per-peer`: isolate by sender id across channels.
|
||||
- `per-channel-peer`: isolate by channel + sender (recommended for multi-user inboxes).
|
||||
- `per-account-channel-peer`: isolate by account + channel + sender (recommended for multi-account inboxes).
|
||||
Use `session.identityLinks` to map provider-prefixed peer ids to a canonical identity so the same person shares a DM session across channels when using `per-peer`, `per-channel-peer`, or `per-account-channel-peer`.
|
||||
Use `session.identityLinks` to map provider-prefixed peer ids to a canonical identity so the same person shares a DM session across channels when using `per-peer`, `per-channel-peer`, or `per-account-channel-peer`.
|
||||
|
||||
## Gateway is the source of truth
|
||||
|
||||
All session state is **owned by the gateway** (the “master” OpenClaw). UI clients (macOS app, WebChat, etc.) must query the gateway for session lists and token counts instead of reading local files.
|
||||
|
||||
- In **remote mode**, the session store you care about lives on the remote gateway host, not your Mac.
|
||||
- Token counts shown in UIs come from the gateway’s store fields (`inputTokens`, `outputTokens`, `totalTokens`, `contextTokens`). Clients do not parse JSONL transcripts to “fix up” totals.
|
||||
|
||||
## Where state lives
|
||||
|
||||
- On the **gateway host**:
|
||||
- Store file: `~/.openclaw/agents/<agentId>/sessions/sessions.json` (per agent).
|
||||
- Transcripts: `~/.openclaw/agents/<agentId>/sessions/<SessionId>.jsonl` (Telegram topic sessions use `.../<SessionId>-topic-<threadId>.jsonl`).
|
||||
@@ -30,16 +34,19 @@ All session state is **owned by the gateway** (the “master” OpenClaw). UI cl
|
||||
- OpenClaw does **not** read legacy Pi/Tau session folders.
|
||||
|
||||
## Session pruning
|
||||
|
||||
OpenClaw trims **old tool results** from the in-memory context right before LLM calls by default.
|
||||
This does **not** rewrite JSONL history. See [/concepts/session-pruning](/concepts/session-pruning).
|
||||
|
||||
## Pre-compaction memory flush
|
||||
|
||||
When a session nears auto-compaction, OpenClaw can run a **silent memory flush**
|
||||
turn that reminds the model to write durable notes to disk. This only runs when
|
||||
the workspace is writable. See [Memory](/concepts/memory) and
|
||||
[Compaction](/concepts/compaction).
|
||||
|
||||
## Mapping transports → session keys
|
||||
|
||||
- Direct chats follow `session.dmScope` (default `main`).
|
||||
- `main`: `agent:<agentId>:<mainKey>` (continuity across devices/channels).
|
||||
- Multiple phone numbers and channels can map to the same agent main key; they act as transports into one conversation.
|
||||
@@ -57,6 +64,7 @@ the workspace is writable. See [Memory](/concepts/memory) and
|
||||
- Node runs: `node-<nodeId>`
|
||||
|
||||
## Lifecycle
|
||||
|
||||
- Reset policy: sessions are reused until they expire, and expiry is evaluated on the next inbound message.
|
||||
- Daily reset: defaults to **4:00 AM local time on the gateway host**. A session is stale once its last update is earlier than the most recent daily reset time.
|
||||
- Idle reset (optional): `idleMinutes` adds a sliding idle window. When both daily and idle resets are configured, **whichever expires first** forces a new session.
|
||||
@@ -68,6 +76,7 @@ the workspace is writable. See [Memory](/concepts/memory) and
|
||||
- Isolated cron jobs always mint a fresh `sessionId` per run (no idle reuse).
|
||||
|
||||
## Send policy (optional)
|
||||
|
||||
Block delivery for specific session types without listing individual ids.
|
||||
|
||||
```json5
|
||||
@@ -76,53 +85,56 @@ Block delivery for specific session types without listing individual ids.
|
||||
sendPolicy: {
|
||||
rules: [
|
||||
{ action: "deny", match: { channel: "discord", chatType: "group" } },
|
||||
{ action: "deny", match: { keyPrefix: "cron:" } }
|
||||
{ action: "deny", match: { keyPrefix: "cron:" } },
|
||||
],
|
||||
default: "allow"
|
||||
}
|
||||
}
|
||||
default: "allow",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Runtime override (owner only):
|
||||
|
||||
- `/send on` → allow for this session
|
||||
- `/send off` → deny for this session
|
||||
- `/send inherit` → clear override and use config rules
|
||||
Send these as standalone messages so they register.
|
||||
Send these as standalone messages so they register.
|
||||
|
||||
## Configuration (optional rename example)
|
||||
|
||||
```json5
|
||||
// ~/.openclaw/openclaw.json
|
||||
{
|
||||
session: {
|
||||
scope: "per-sender", // keep group keys separate
|
||||
dmScope: "main", // DM continuity (set per-channel-peer/per-account-channel-peer for shared inboxes)
|
||||
scope: "per-sender", // keep group keys separate
|
||||
dmScope: "main", // DM continuity (set per-channel-peer/per-account-channel-peer for shared inboxes)
|
||||
identityLinks: {
|
||||
alice: ["telegram:123456789", "discord:987654321012345678"]
|
||||
alice: ["telegram:123456789", "discord:987654321012345678"],
|
||||
},
|
||||
reset: {
|
||||
// Defaults: mode=daily, atHour=4 (gateway host local time).
|
||||
// If you also set idleMinutes, whichever expires first wins.
|
||||
mode: "daily",
|
||||
atHour: 4,
|
||||
idleMinutes: 120
|
||||
idleMinutes: 120,
|
||||
},
|
||||
resetByType: {
|
||||
thread: { mode: "daily", atHour: 4 },
|
||||
dm: { mode: "idle", idleMinutes: 240 },
|
||||
group: { mode: "idle", idleMinutes: 120 }
|
||||
group: { mode: "idle", idleMinutes: 120 },
|
||||
},
|
||||
resetByChannel: {
|
||||
discord: { mode: "idle", idleMinutes: 10080 }
|
||||
discord: { mode: "idle", idleMinutes: 10080 },
|
||||
},
|
||||
resetTriggers: ["/new", "/reset"],
|
||||
store: "~/.openclaw/agents/{agentId}/sessions/sessions.json",
|
||||
mainKey: "main",
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Inspecting
|
||||
|
||||
- `openclaw status` — shows store path and recent sessions.
|
||||
- `openclaw sessions --json` — dumps every entry (filter with `--active <minutes>`).
|
||||
- `openclaw gateway call sessions.list --params '{}'` — fetch sessions from the running gateway (use `--url`/`--token` for remote gateway access).
|
||||
@@ -133,20 +145,23 @@ Send these as standalone messages so they register.
|
||||
- JSONL transcripts can be opened directly to review full turns.
|
||||
|
||||
## Tips
|
||||
|
||||
- Keep the primary key dedicated to 1:1 traffic; let groups keep their own keys.
|
||||
- When automating cleanup, delete individual keys instead of the whole store to preserve context elsewhere.
|
||||
|
||||
## Session origin metadata
|
||||
|
||||
Each session entry records where it came from (best-effort) in `origin`:
|
||||
|
||||
- `label`: human label (resolved from conversation label + group subject/channel)
|
||||
- `provider`: normalized channel id (including extensions)
|
||||
- `from`/`to`: raw routing ids from the inbound envelope
|
||||
- `accountId`: provider account id (when multi-account)
|
||||
- `threadId`: thread/topic id when the channel supports it
|
||||
The origin fields are populated for direct messages, channels, and groups. If a
|
||||
connector only updates delivery routing (for example, to keep a DM main session
|
||||
fresh), it should still provide inbound context so the session keeps its
|
||||
explainer metadata. Extensions can do this by sending `ConversationLabel`,
|
||||
`GroupSubject`, `GroupChannel`, `GroupSpace`, and `SenderName` in the inbound
|
||||
context and calling `recordSessionMetaFromInbound` (or passing the same context
|
||||
to `updateLastRoute`).
|
||||
The origin fields are populated for direct messages, channels, and groups. If a
|
||||
connector only updates delivery routing (for example, to keep a DM main session
|
||||
fresh), it should still provide inbound context so the session keeps its
|
||||
explainer metadata. Extensions can do this by sending `ConversationLabel`,
|
||||
`GroupSubject`, `GroupChannel`, `GroupSpace`, and `SenderName` in the inbound
|
||||
context and calling `recordSessionMetaFromInbound` (or passing the same context
|
||||
to `updateLastRoute`).
|
||||
|
||||
@@ -3,6 +3,7 @@ summary: "Alias for session management docs"
|
||||
read_when:
|
||||
- You looked for docs/sessions.md; canonical doc lives in docs/session.md
|
||||
---
|
||||
|
||||
# Sessions
|
||||
|
||||
Canonical session management docs live in [Session management](/concepts/session).
|
||||
|
||||
@@ -5,9 +5,11 @@ read_when:
|
||||
- Changing block streaming or channel chunking behavior
|
||||
- Debugging duplicate/early block replies or draft streaming
|
||||
---
|
||||
|
||||
# Streaming + chunking
|
||||
|
||||
OpenClaw has two separate “streaming” layers:
|
||||
|
||||
- **Block streaming (channels):** emit completed **blocks** as the assistant writes. These are normal channel messages (not token deltas).
|
||||
- **Token-ish streaming (Telegram only):** update a **draft bubble** with partial text while generating; final message is sent at the end.
|
||||
|
||||
@@ -26,12 +28,15 @@ Model output
|
||||
└─ chunker flushes at message_end
|
||||
└─ channel send (block replies)
|
||||
```
|
||||
|
||||
Legend:
|
||||
|
||||
- `text_delta/events`: model stream events (may be sparse for non-streaming models).
|
||||
- `chunker`: `EmbeddedBlockChunker` applying min/max bounds + break preference.
|
||||
- `channel send`: actual outbound messages (block replies).
|
||||
|
||||
**Controls:**
|
||||
|
||||
- `agents.defaults.blockStreamingDefault`: `"on"`/`"off"` (default off).
|
||||
- Channel overrides: `*.blockStreaming` (and per-account variants) to force `"on"`/`"off"` per channel.
|
||||
- `agents.defaults.blockStreamingBreak`: `"text_end"` or `"message_end"`.
|
||||
@@ -42,6 +47,7 @@ Legend:
|
||||
- Discord soft cap: `channels.discord.maxLinesPerMessage` (default 17) splits tall replies to avoid UI clipping.
|
||||
|
||||
**Boundary semantics:**
|
||||
|
||||
- `text_end`: stream blocks as soon as chunker emits; flush on each `text_end`.
|
||||
- `message_end`: wait until assistant message finishes, then flush buffered output.
|
||||
|
||||
@@ -50,6 +56,7 @@ Legend:
|
||||
## Chunking algorithm (low/high bounds)
|
||||
|
||||
Block chunking is implemented by `EmbeddedBlockChunker`:
|
||||
|
||||
- **Low bound:** don’t emit until buffer >= `minChars` (unless forced).
|
||||
- **High bound:** prefer splits before `maxChars`; if forced, split at `maxChars`.
|
||||
- **Break preference:** `paragraph` → `newline` → `sentence` → `whitespace` → hard break.
|
||||
@@ -85,6 +92,7 @@ more natural.
|
||||
## “Stream chunks or everything”
|
||||
|
||||
This maps to:
|
||||
|
||||
- **Stream chunks:** `blockStreamingDefault: "on"` + `blockStreamingBreak: "text_end"` (emit as you go). Non-Telegram channels also need `*.blockStreaming: true`.
|
||||
- **Stream everything at end:** `blockStreamingBreak: "message_end"` (flush once, possibly multiple chunks if very long).
|
||||
- **No block streaming:** `blockStreamingDefault: "off"` (only final reply).
|
||||
@@ -99,6 +107,7 @@ Config location reminder: the `blockStreaming*` defaults live under
|
||||
## Telegram draft streaming (token-ish)
|
||||
|
||||
Telegram is the only channel with draft streaming:
|
||||
|
||||
- Uses Bot API `sendMessageDraft` in **private chats with topics**.
|
||||
- `channels.telegram.streamMode: "partial" | "block" | "off"`.
|
||||
- `partial`: draft updates with the latest stream text.
|
||||
@@ -118,6 +127,8 @@ Telegram (private + topics)
|
||||
└─ streamMode=block → chunker updates draft
|
||||
└─ final reply → normal message
|
||||
```
|
||||
|
||||
Legend:
|
||||
|
||||
- `sendMessageDraft`: Telegram draft bubble (not a real message).
|
||||
- `final reply`: normal Telegram message send.
|
||||
|
||||
@@ -4,6 +4,7 @@ read_when:
|
||||
- Editing system prompt text, tools list, or time/heartbeat sections
|
||||
- Changing workspace bootstrap or skills injection behavior
|
||||
---
|
||||
|
||||
# System Prompt
|
||||
|
||||
OpenClaw builds a custom system prompt for every agent run. The prompt is **OpenClaw-owned** and does not use the p-coding-agent default prompt.
|
||||
|
||||
@@ -27,9 +27,9 @@ You can override this with:
|
||||
defaults: {
|
||||
envelopeTimezone: "local", // "utc" | "local" | "user" | IANA timezone
|
||||
envelopeTimestamp: "on", // "on" | "off"
|
||||
envelopeElapsed: "on" // "on" | "off"
|
||||
}
|
||||
}
|
||||
envelopeElapsed: "on", // "on" | "off"
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -76,11 +76,12 @@ unset, OpenClaw resolves the **host timezone at runtime** (no config write).
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: { defaults: { userTimezone: "America/Chicago" } }
|
||||
agents: { defaults: { userTimezone: "America/Chicago" } },
|
||||
}
|
||||
```
|
||||
|
||||
The system prompt includes:
|
||||
|
||||
- `Current Date & Time` section with local time and timezone
|
||||
- `Time format: 12-hour` or `24-hour`
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ summary: "TypeBox schemas as the single source of truth for the gateway protocol
|
||||
read_when:
|
||||
- Updating protocol schemas or codegen
|
||||
---
|
||||
|
||||
# TypeBox as protocol source of truth
|
||||
|
||||
Last updated: 2026-01-10
|
||||
@@ -40,14 +41,14 @@ Client Gateway
|
||||
|
||||
Common methods + events:
|
||||
|
||||
| Category | Examples | Notes |
|
||||
| --- | --- | --- |
|
||||
| Core | `connect`, `health`, `status` | `connect` must be first |
|
||||
| Messaging | `send`, `poll`, `agent`, `agent.wait` | side-effects need `idempotencyKey` |
|
||||
| Chat | `chat.history`, `chat.send`, `chat.abort`, `chat.inject` | WebChat uses these |
|
||||
| Sessions | `sessions.list`, `sessions.patch`, `sessions.delete` | session admin |
|
||||
| Nodes | `node.list`, `node.invoke`, `node.pair.*` | Gateway WS + node actions |
|
||||
| Events | `tick`, `presence`, `agent`, `chat`, `health`, `shutdown` | server push |
|
||||
| Category | Examples | Notes |
|
||||
| --------- | --------------------------------------------------------- | ---------------------------------- |
|
||||
| Core | `connect`, `health`, `status` | `connect` must be first |
|
||||
| Messaging | `send`, `poll`, `agent`, `agent.wait` | side-effects need `idempotencyKey` |
|
||||
| Chat | `chat.history`, `chat.send`, `chat.abort`, `chat.inject` | WebChat uses these |
|
||||
| Sessions | `sessions.list`, `sessions.patch`, `sessions.delete` | session admin |
|
||||
| Nodes | `node.list`, `node.invoke`, `node.pair.*` | Gateway WS + node actions |
|
||||
| Events | `tick`, `presence`, `agent`, `chat`, `health`, `shutdown` | server push |
|
||||
|
||||
Authoritative list lives in `src/gateway/server.ts` (`METHODS`, `EVENTS`).
|
||||
|
||||
@@ -114,7 +115,12 @@ Hello-ok response:
|
||||
"protocol": 2,
|
||||
"server": { "version": "dev", "connId": "ws-1" },
|
||||
"features": { "methods": ["health"], "events": ["tick"] },
|
||||
"snapshot": { "presence": [], "health": {}, "stateVersion": { "presence": 0, "health": 0 }, "uptimeMs": 0 },
|
||||
"snapshot": {
|
||||
"presence": [],
|
||||
"health": {},
|
||||
"stateVersion": { "presence": 0, "health": 0 },
|
||||
"uptimeMs": 0
|
||||
},
|
||||
"policy": { "maxPayload": 1048576, "maxBufferedBytes": 1048576, "tickIntervalMs": 30000 }
|
||||
}
|
||||
}
|
||||
@@ -146,22 +152,24 @@ import { WebSocket } from "ws";
|
||||
const ws = new WebSocket("ws://127.0.0.1:18789");
|
||||
|
||||
ws.on("open", () => {
|
||||
ws.send(JSON.stringify({
|
||||
type: "req",
|
||||
id: "c1",
|
||||
method: "connect",
|
||||
params: {
|
||||
minProtocol: 3,
|
||||
maxProtocol: 3,
|
||||
client: {
|
||||
id: "cli",
|
||||
displayName: "example",
|
||||
version: "dev",
|
||||
platform: "node",
|
||||
mode: "cli"
|
||||
}
|
||||
}
|
||||
}));
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "req",
|
||||
id: "c1",
|
||||
method: "connect",
|
||||
params: {
|
||||
minProtocol: 3,
|
||||
maxProtocol: 3,
|
||||
client: {
|
||||
id: "cli",
|
||||
displayName: "example",
|
||||
version: "dev",
|
||||
platform: "node",
|
||||
mode: "cli",
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
ws.on("message", (data) => {
|
||||
@@ -180,7 +188,7 @@ ws.on("message", (data) => {
|
||||
|
||||
Example: add a new `system.echo` request that returns `{ ok: true, text }`.
|
||||
|
||||
1) **Schema (source of truth)**
|
||||
1. **Schema (source of truth)**
|
||||
|
||||
Add to `src/gateway/protocol/schema.ts`:
|
||||
|
||||
@@ -208,16 +216,15 @@ export type SystemEchoParams = Static<typeof SystemEchoParamsSchema>;
|
||||
export type SystemEchoResult = Static<typeof SystemEchoResultSchema>;
|
||||
```
|
||||
|
||||
2) **Validation**
|
||||
2. **Validation**
|
||||
|
||||
In `src/gateway/protocol/index.ts`, export an AJV validator:
|
||||
|
||||
```ts
|
||||
export const validateSystemEchoParams =
|
||||
ajv.compile<SystemEchoParams>(SystemEchoParamsSchema);
|
||||
export const validateSystemEchoParams = ajv.compile<SystemEchoParams>(SystemEchoParamsSchema);
|
||||
```
|
||||
|
||||
3) **Server behavior**
|
||||
3. **Server behavior**
|
||||
|
||||
Add a handler in `src/gateway/server-methods/system.ts`:
|
||||
|
||||
@@ -233,13 +240,13 @@ export const systemHandlers: GatewayRequestHandlers = {
|
||||
Register it in `src/gateway/server-methods.ts` (already merges `systemHandlers`),
|
||||
then add `"system.echo"` to `METHODS` in `src/gateway/server.ts`.
|
||||
|
||||
4) **Regenerate**
|
||||
4. **Regenerate**
|
||||
|
||||
```bash
|
||||
pnpm protocol:check
|
||||
```
|
||||
|
||||
5) **Tests + docs**
|
||||
5. **Tests + docs**
|
||||
|
||||
Add a server test in `src/gateway/server.*.test.ts` and note the method in docs.
|
||||
|
||||
@@ -276,6 +283,6 @@ published raw file is typically available at:
|
||||
|
||||
## When you change schemas
|
||||
|
||||
1) Update the TypeBox schemas.
|
||||
2) Run `pnpm protocol:check`.
|
||||
3) Commit the regenerated schema + Swift models.
|
||||
1. Update the TypeBox schemas.
|
||||
2. Run `pnpm protocol:check`.
|
||||
3. Commit the regenerated schema + Swift models.
|
||||
|
||||
@@ -3,6 +3,7 @@ summary: "When OpenClaw shows typing indicators and how to tune them"
|
||||
read_when:
|
||||
- Changing typing indicator behavior or defaults
|
||||
---
|
||||
|
||||
# Typing indicators
|
||||
|
||||
Typing indicators are sent to the chat channel while a run is active. Use
|
||||
@@ -10,14 +11,18 @@ Typing indicators are sent to the chat channel while a run is active. Use
|
||||
to control **how often** it refreshes.
|
||||
|
||||
## Defaults
|
||||
|
||||
When `agents.defaults.typingMode` is **unset**, OpenClaw keeps the legacy behavior:
|
||||
|
||||
- **Direct chats**: typing starts immediately once the model loop begins.
|
||||
- **Group chats with a mention**: typing starts immediately.
|
||||
- **Group chats without a mention**: typing starts only when message text begins streaming.
|
||||
- **Heartbeat runs**: typing is disabled.
|
||||
|
||||
## Modes
|
||||
|
||||
Set `agents.defaults.typingMode` to one of:
|
||||
|
||||
- `never` — no typing indicator, ever.
|
||||
- `instant` — start typing **as soon as the model loop begins**, even if the run
|
||||
later returns only the silent reply token.
|
||||
@@ -30,26 +35,29 @@ Order of “how early it fires”:
|
||||
`never` → `message` → `thinking` → `instant`
|
||||
|
||||
## Configuration
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
typingMode: "thinking",
|
||||
typingIntervalSeconds: 6
|
||||
}
|
||||
typingIntervalSeconds: 6,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
You can override mode or cadence per session:
|
||||
|
||||
```json5
|
||||
{
|
||||
session: {
|
||||
typingMode: "message",
|
||||
typingIntervalSeconds: 4
|
||||
}
|
||||
typingIntervalSeconds: 4,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `message` mode won’t show typing for silent-only replies (e.g. the `NO_REPLY`
|
||||
token used to suppress output).
|
||||
- `thinking` only fires if the run streams reasoning (`reasoningLevel: "stream"`).
|
||||
|
||||
@@ -4,13 +4,16 @@ read_when:
|
||||
- You are wiring provider usage/quota surfaces
|
||||
- You need to explain usage tracking behavior or auth requirements
|
||||
---
|
||||
|
||||
# Usage tracking
|
||||
|
||||
## What it is
|
||||
|
||||
- Pulls provider usage/quota directly from their usage endpoints.
|
||||
- No estimated costs; only the provider-reported windows.
|
||||
|
||||
## Where it shows up
|
||||
|
||||
- `/status` in chats: emoji‑rich status card with session tokens + estimated cost (API key only). Provider usage shows for the **current model provider** when available.
|
||||
- `/usage off|tokens|full` in chats: per-response usage footer (OAuth shows tokens only).
|
||||
- `/usage cost` in chats: local cost summary aggregated from OpenClaw session logs.
|
||||
@@ -19,6 +22,7 @@ read_when:
|
||||
- macOS menu bar: “Usage” section under Context (only if available).
|
||||
|
||||
## Providers + credentials
|
||||
|
||||
- **Anthropic (Claude)**: OAuth tokens in auth profiles.
|
||||
- **GitHub Copilot**: OAuth tokens in auth profiles.
|
||||
- **Gemini CLI**: OAuth tokens in auth profiles.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user