From d09a5100b6d1d35b6f3257c1ec87b435b8ef315c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 7 Jan 2026 00:25:16 +0100 Subject: [PATCH] docs: rewrite provider docs --- docs/discord.md | 347 +++++++++++------------------------------------ docs/imessage.md | 115 ++++++++-------- docs/signal.md | 157 +++++++++------------ docs/slack.md | 278 +++++++++++-------------------------- docs/telegram.md | 143 ++++++++++--------- docs/webchat.md | 42 +++--- docs/whatsapp.md | 201 +++++++++------------------ 7 files changed, 431 insertions(+), 852 deletions(-) diff --git a/docs/discord.md b/docs/discord.md index 6b006c068..8414863dc 100644 --- a/docs/discord.md +++ b/docs/discord.md @@ -5,293 +5,100 @@ read_when: --- # Discord (Bot API) -Updated: 2025-12-07 +Updated: 2026-01-06 -Status: ready for DM and guild text channels via the official Discord bot gateway. +Status: production-ready for DMs + guild channels via the Discord gateway. -## Goals -- Talk to Clawdbot via Discord DMs or guild channels. -- Direct chats collapse into the agent's main session (default `agent:main:main`); guild channels stay isolated as `agent::discord:channel:` (display names use `discord:#`). -- Group DMs are ignored by default; enable via `discord.dm.groupEnabled` and optionally restrict by `discord.dm.groupChannels`. -- Keep routing deterministic: replies always go back to the provider they arrived on. +## What it is +- Discord bot provider owned by the Gateway. +- Deterministic routing: replies always go back to Discord. +- DMs share the agent's main session; guild channels are isolated (`discord:channel:`). -## 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 Clawdbot with `DISCORD_BOT_TOKEN` (or `discord.token` in `~/.clawdbot/clawdbot.json`). -4. Run the gateway; it auto-starts the Discord provider only when a `discord` config section exists **and** the token is set (unless `discord.enabled = false`). - - If you prefer env vars, still add `discord: { enabled: true }` to `~/.clawdbot/clawdbot.json` and set `DISCORD_BOT_TOKEN`. -5. Direct chats: use `user:` (or a `<@id>` mention) when delivering; all turns land in the shared `main` session. -6. Guild channels: use `channel:` for delivery. Mentions are required by default and can be set per guild or per channel. -7. Direct chats: secure by default via `discord.dm.policy` (default: `"pairing"`). Unknown senders get a pairing code; approve via `clawdbot pairing approve --provider discord `. - - To keep old “open to anyone” behavior: set `discord.dm.policy="open"` and `discord.dm.allowFrom=["*"]`. - - To hard-allowlist: set `discord.dm.policy="allowlist"` and list senders in `discord.dm.allowFrom`. - - To ignore all DMs: set `discord.dm.enabled=false` or `discord.dm.policy="disabled"`. -8. Group DMs are ignored by default; enable via `discord.dm.groupEnabled` and optionally restrict by `discord.dm.groupChannels`. -9. Optional guild rules: set `discord.guilds` keyed by guild id (preferred) or slug, with per-channel rules. -10. Optional native commands: set `commands.native: true` to register native commands in Discord; set `commands.native: false` to clear previously registered native commands. Text commands are controlled by `commands.text` and must be sent as standalone `/...` messages. Use `commands.useAccessGroups: false` to bypass access-group checks for commands. - - Full command list + config: https://docs.clawd.bot/slash-commands -11. Optional guild context history: set `discord.historyLimit` (default 20) to include the last N guild messages as context when replying to a mention. Set `0` to disable. -12. Reactions: the agent can trigger reactions via the `discord` tool (gated by `discord.actions.*`). - - The `discord` tool is only exposed when the current provider is Discord. -13. Native commands use isolated session keys (`discord:slash:${userId}`) rather than the shared `main` session. - -Note: Discord does not provide a simple username → id lookup without extra guild context, so prefer ids or `<@id>` mentions for DM delivery targets. -Note: Slugs are lowercase with spaces replaced by `-`. Channel names are slugged without the leading `#`. -Note: Guild context `[from:]` lines include `author.tag` + `id` to make ping-ready replies easy. - -## How to create your own bot - -This is the “Discord Developer Portal” setup for running Clawdbot 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 Clawdbot 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 -- ✅ Embed Links -- ✅ Attach Files -- ✅ Add Reactions (optional but recommended) -- ✅ Use External Emojis / Stickers (optional; only if you want them) - -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; Clawdbot config prefers ids. - -1. Discord (desktop/web) → **User Settings** → **Advanced** → enable **Developer Mode** -2. Right-click: - - Server name → **Copy Server ID** (guild id) - - Channel (e.g. `#help`) → **Copy Channel ID** - - Your user → **Copy User ID** - -### 5) Configure Clawdbot - -#### Token -Set the bot token via env var (recommended on servers): -- `DISCORD_BOT_TOKEN=...` - -Or via config: +## Setup (fast path) +1) Create a Discord application + bot. +2) Enable intents: **Message Content** (required), **Server Members** (recommended). +3) Invite the bot to your server with message permissions. +4) Configure the token (env or config) and start the gateway. +Example: ```json5 { discord: { enabled: true, - token: "YOUR_BOT_TOKEN" + token: "YOUR_BOT_TOKEN", + dm: { policy: "pairing" }, + guilds: { "*": { requireMention: true } } } } ``` -#### Allowlist + channel routing -Example “single server, only allow me, only allow #help”: +## Access control (DMs + guilds) +DMs: +- Default: `discord.dm.policy = "pairing"`. +- Unknown senders receive a pairing code; messages are ignored until approved. +- Approve via: + - `clawdbot pairing list --provider discord` + - `clawdbot pairing approve --provider discord ` +- Pairing is the default token exchange for Discord DMs. Details: https://docs.clawd.bot/pairing -```json5 -{ - discord: { - enabled: true, - dm: { enabled: false }, - guilds: { - "YOUR_GUILD_ID": { - users: ["YOUR_USER_ID"], - requireMention: true, - channels: { - help: { allow: true, requireMention: true } - } - } - } - } -} -``` +Guild channels: +- `discord.groupPolicy = open | allowlist | disabled`. +- `discord.guilds` (per-guild) + `channels` (per-channel) act as allowlists. +- Mentions are required by default; override per guild/channel. -Notes: -- `requireMention: true` means the bot only replies when mentioned (recommended for shared channels). -- `routing.groupChat.mentionPatterns` also count as mentions for guild messages. -- If `channels` is present, any channel not listed is denied by default. +## How it works (behavior) +- Inbound messages are normalized into the shared provider envelope. +- Optional guild context history is injected before the current message. +- Replies always route back to the same channel or DM. -### 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. +## Commands + reply threading +- Native commands: `commands.native = true` (registers `/` commands). +- Text commands: `commands.text = true` (standalone `/...` messages). +- Threaded replies: controlled by `discord.replyToMode` using reply tags. -### Troubleshooting -- **“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**: - - Missing **Message Content Intent**, or - - The bot lacks channel permissions (View/Send/Read History), or - - Your config requires mentions and you didn’t mention it, or - - Your guild/channel allowlist denies the channel/user. -- **DMs don’t work**: `discord.dm.enabled=false`, `discord.dm.policy="disabled"`, or you haven’t been approved yet (`discord.dm.policy="pairing"`). +## Media + limits +- Files supported up to `discord.mediaMaxMb` (default 8 MB). +- Outbound chunking controlled by `discord.textChunkLimit`. -## Capabilities & limits -- DMs and guild text channels (threads are treated as separate channels; voice not supported). -- Typing indicators sent best-effort; message chunking honors Discord’s 2k character limit. -- File uploads supported up to the configured `discord.mediaMaxMb` (default 8 MB). -- Mention-gated guild replies by default to avoid noisy bots. -- Reply context is injected when a message references another message (quoted content + ids). -- Native reply threading is **off by default**; enable with `discord.replyToMode` and reply tags. +## Delivery targets (CLI/cron) +- DMs: `user:` +- Guild channels: `channel:` -## Config +## Configuration reference (Discord) +Full configuration: https://docs.clawd.bot/configuration -```json5 -{ - discord: { - enabled: true, - token: "abc.123", - groupPolicy: "open", - mediaMaxMb: 8, - actions: { - reactions: true, - stickers: true, - polls: true, - permissions: true, - messages: true, - threads: true, - pins: true, - search: true, - memberInfo: true, - roleInfo: true, - roles: false, - channelInfo: true, - voiceStatus: true, - events: true, - moderation: false - }, - replyToMode: "off", - dm: { - enabled: true, - policy: "pairing", // pairing | allowlist | open | disabled - allowFrom: ["123456789012345678", "steipete"], - groupEnabled: false, - groupChannels: ["clawd-dm"] - }, - guilds: { - "*": { requireMention: true }, - "123456789012345678": { - slug: "friends-of-clawd", - requireMention: false, - reactionNotifications: "own", - users: ["987654321098765432", "steipete"], - channels: { - general: { allow: true }, - help: { allow: true, requireMention: true } - } - } - } - } -} -``` +Provider options: +- `discord.enabled`: enable/disable provider startup. +- `discord.token`: bot token (env: `DISCORD_BOT_TOKEN`). +- `discord.groupPolicy`: `open | allowlist | disabled` (default: open). +- `discord.textChunkLimit`: outbound chunk size (chars). +- `discord.mediaMaxMb`: inbound/outbound media cap (MB). +- `discord.historyLimit`: number of recent guild messages injected as context. +- `discord.replyToMode`: `off | first | all`. +- `discord.actions.reactions`: enable reaction tool actions. +- `discord.actions.stickers`: enable sticker actions. +- `discord.actions.polls`: enable poll actions. +- `discord.actions.permissions`: enable permission inspection actions. +- `discord.actions.messages`: enable message read/send/edit/delete actions. +- `discord.actions.threads`: enable thread actions. +- `discord.actions.pins`: enable pin actions. +- `discord.actions.search`: enable search actions. +- `discord.actions.memberInfo`: enable member info actions. +- `discord.actions.roleInfo`: enable role info actions. +- `discord.actions.roles`: enable role management actions. +- `discord.actions.channelInfo`: enable channel info actions. +- `discord.actions.voiceStatus`: enable voice status actions. +- `discord.actions.events`: enable event actions. +- `discord.actions.moderation`: enable moderation actions. +- `discord.dm.enabled`: enable/disable DMs. +- `discord.dm.policy`: `pairing | allowlist | open | disabled` (default: pairing). +- `discord.dm.allowFrom`: DM allowlist (ids/usernames). `open` requires `"*"`. +- `discord.dm.groupEnabled`: enable group DMs. +- `discord.dm.groupChannels`: group DM allowlist. +- `discord.guilds`: per-guild rules: + - `slug`, `requireMention`, `reactionNotifications`, `users`, `channels.*`. -Ack reactions are controlled globally via `messages.ackReaction` + -`messages.ackReactionScope`. - -- `dm.enabled`: set `false` to ignore all DMs (default `true`). -- `dm.policy`: DM access control (`pairing` recommended). `"open"` requires `dm.allowFrom=["*"]`. -- `dm.allowFrom`: DM allowlist (user ids or names). Used by `dm.policy="allowlist"` and for `dm.policy="open"` validation. -- `dm.groupEnabled`: enable group DMs (default `false`). -- `dm.groupChannels`: optional allowlist for group DM channel ids or slugs. -- `groupPolicy`: controls guild channel handling (`open|disabled|allowlist`); `allowlist` requires channel allowlists. -- `guilds`: per-guild rules keyed by guild id (preferred) or slug. -- `guilds."*"`: default per-guild settings applied when no explicit entry exists. -- `guilds..slug`: optional friendly slug used for display names. -- `guilds..users`: optional per-guild user allowlist (ids or names). -- `guilds..channels`: channel rules (keys are channel slugs or ids). -- `guilds..requireMention`: per-guild mention requirement (overridable per channel). -- `guilds..reactionNotifications`: reaction system event mode (`off`, `own`, `all`, `allowlist`). -- `mediaMaxMb`: clamp inbound media saved to disk. -- `historyLimit`: number of recent guild messages to include as context when replying to a mention (default 20, `0` disables). -- `actions`: per-action tool gates; omit to allow all (set `false` to disable). - - `reactions` (covers react + read reactions) - - `stickers`, `polls`, `permissions`, `messages`, `threads`, `pins`, `search` - - `memberInfo`, `roleInfo`, `channelInfo`, `voiceStatus`, `events` - - `roles` (role add/remove, default `false`) - - `moderation` (timeout/kick/ban, default `false`) - -Reaction notifications use `guilds..reactionNotifications`: -- `off`: no reaction events. -- `own`: reactions on the bot's own messages (default). -- `all`: all reactions on all messages. -- `allowlist`: reactions from `guilds..users` on all messages (empty list disables). - -### Tool action defaults - -| Action group | Default | Notes | -| --- | --- | --- | -| reactions | enabled | React + list reactions + emojiList | -| stickers | enabled | Send 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 spec) | -| memberInfo | enabled | Member info | -| roleInfo | enabled | Role list | -| channelInfo | enabled | Channel info + list | -| voiceStatus | enabled | Voice state lookup | -| events | enabled | List/create scheduled events | -| roles | disabled | Role add/remove | -| moderation | disabled | Timeout/kick/ban | -- `replyToMode`: `off` (default), `first`, or `all`. Applies only when the model includes a reply tag. - -## Reply tags -To request a threaded reply, the model can include one tag in its output: -- `[[reply_to_current]]` — reply to the triggering Discord message. -- `[[reply_to:]]` — reply to a specific message id from context/history. -Current message ids are appended to prompts as `[message_id: …]`; history entries already include ids. - -Behavior is controlled by `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. -- When `guilds..channels` is present, channels not listed are denied by default. - -Native command notes: -- The registered commands mirror Clawdbot’s chat commands. -- Native commands honor the same allowlists as DMs/guild messages (`discord.dm.allowFrom`, `discord.guilds`, per-channel rules). - -## Tool actions -The agent can call `discord` with actions like: -- `react` / `reactions` (add or list reactions) -- `sticker`, `poll`, `permissions` -- `readMessages`, `sendMessage`, `editMessage`, `deleteMessage` -- `threadCreate`, `threadList`, `threadReply` -- `pinMessage`, `unpinMessage`, `listPins` -- `searchMessages`, `memberInfo`, `roleInfo`, `roleAdd`, `roleRemove`, `emojiList` -- `channelInfo`, `channelList`, `voiceStatus`, `eventList`, `eventCreate` -- `timeout`, `kick`, `ban` - -Discord message ids are surfaced in the injected context (`[discord message id: …]` and history lines) so the agent can target them. -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 (`clawdbot gateway --force`) after confirming no other processes own the Discord session. +Related global options: +- `routing.groupChat.mentionPatterns`. +- `commands.native`, `commands.text`, `commands.useAccessGroups`. +- `messages.responsePrefix`, `messages.ackReaction`, `messages.ackReactionScope`. diff --git a/docs/imessage.md b/docs/imessage.md index 17ddbc21b..d4c8ae9ac 100644 --- a/docs/imessage.md +++ b/docs/imessage.md @@ -6,77 +6,63 @@ read_when: --- # iMessage (imsg) -Status: external CLI integration. No daemon. +Updated: 2026-01-06 -## Model -- Clawdbot spawns `imsg rpc` as a child process. -- JSON-RPC runs over stdin/stdout (one JSON object per line). -- Gateway owns the process; no TCP port needed. +Status: external CLI integration. Gateway spawns `imsg rpc` (JSON-RPC over stdio). -## Multi-account (Apple IDs) - -iMessage “multi-account” in one Gateway process is not currently supported in a meaningful way: -- Messages accounts are owned by the signed-in macOS user session. -- `imsg` reads the local Messages DB and sends via that user’s configured services. -- There isn’t a robust “pick AppleID X as the sender” switch we can depend on. - -### Practical approach: multiple gateways on multiple Macs/users - -If you need two iMessage identities: -- Run one Gateway on each macOS user/machine that’s signed into the desired Apple ID. -- Connect to the desired Gateway remotely (Tailscale preferred; SSH tunnel is the universal fallback). - -See: -- `docs/remote.md` (SSH tunnel to `127.0.0.1:18789`) -- `docs/discovery.md` (bridge vs SSH transport model) - -### Could we do “iMessage over SSH” from a single Gateway? - -Maybe, but it’s a new design: -- Outbound could theoretically pipe `imsg rpc` over SSH (stdio bridge). -- Inbound still needs a remote watcher (DB polling / event stream) and a transport back to the main Gateway. - -That’s closer to “remote provider instances” (or “multi-gateway aggregation”) than a small config tweak. +## What it is +- iMessage provider backed by `imsg` on macOS. +- Deterministic routing: replies always go back to iMessage. +- DMs share the agent's main session; groups are isolated (`imessage:group:`). ## Requirements - macOS with Messages signed in. -- Full Disk Access for Clawdbot + the `imsg` binary (Messages DB access). -- Automation permission for Messages when sending. +- Full Disk Access for Clawdbot + `imsg` (Messages DB access). +- Automation permission when sending. -## Config +## Setup (fast path) +1) Ensure Messages is signed in on this Mac. +2) Configure iMessage and start the gateway. +Example: ```json5 { imessage: { enabled: true, cliPath: "imsg", - dbPath: "~/Library/Messages/chat.db", - dmPolicy: "pairing", // pairing | allowlist | open | disabled - allowFrom: ["+15555550123", "user@example.com", "chat_id:123"], - groupPolicy: "open", - groupAllowFrom: ["chat_id:123"], - includeAttachments: false, - mediaMaxMb: 16, - service: "auto", - region: "US" + dmPolicy: "pairing", + allowFrom: ["+15555550123"] } } ``` -Notes: -- `allowFrom` accepts handles (phone/email) or `chat_id:` entries. -- Default: `imessage.dmPolicy="pairing"` — unknown DM senders get a pairing code (approve via `clawdbot pairing approve --provider imessage `). `"open"` requires `allowFrom=["*"]`. -- `groupPolicy` controls group handling (`open|disabled|allowlist`). -- `groupAllowFrom` accepts the same entries as `allowFrom`. -- `service` defaults to `auto` (use `imessage` or `sms` to pin). -- `region` is only used for SMS targeting. +## Access control (DMs + groups) +DMs: +- Default: `imessage.dmPolicy = "pairing"`. +- Unknown senders receive a pairing code; messages are ignored until approved. +- Approve via: + - `clawdbot pairing list --provider imessage` + - `clawdbot pairing approve --provider imessage ` +- Pairing is the default token exchange for iMessage DMs. Details: https://docs.clawd.bot/pairing -## Addressing / targets +Groups: +- `imessage.groupPolicy = open | allowlist | disabled`. +- `imessage.groupAllowFrom` controls who can trigger in groups when `allowlist` is set. +- Mention gating uses `routing.groupChat.mentionPatterns` (iMessage has no native mention metadata). +## How it works (behavior) +- `imsg` streams message events; the gateway normalizes them into the shared provider envelope. +- Replies always route back to the same chat id or handle. + +## Media + limits +- Optional attachment ingestion via `imessage.includeAttachments`. +- Media cap via `imessage.mediaMaxMb`. + +## Addressing / delivery targets Prefer `chat_id` for stable routing: - `chat_id:123` (preferred) -- `chat_guid:...` (fallback) -- `chat_identifier:...` (fallback) +- `chat_guid:...` +- `chat_identifier:...` - direct handles: `imessage:+1555` / `sms:+1555` / `user@example.com` List chats: @@ -84,11 +70,24 @@ List chats: imsg chats --limit 20 ``` -## Group chat behavior -- Group messages set `ChatType=group`, `GroupSubject`, and `GroupMembers`. -- Group activation respects `imessage.groups."*".requireMention` and `routing.groupChat.mentionPatterns` (patterns are required to detect mentions on iMessage). When `imessage.groups` is set, it also acts as a group allowlist; include `"*"` to allow all groups. -- Replies go back to the same `chat_id` (group or direct). +## Configuration reference (iMessage) +Full configuration: https://docs.clawd.bot/configuration -## Troubleshooting -- `clawdbot gateway call providers.status --params '{"probe":true}'` -- Verify `imsg` is on PATH and has access to Messages DB. +Provider options: +- `imessage.enabled`: enable/disable provider startup. +- `imessage.cliPath`: path to `imsg`. +- `imessage.dbPath`: Messages DB path. +- `imessage.service`: `imessage | sms | auto`. +- `imessage.region`: SMS region. +- `imessage.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing). +- `imessage.allowFrom`: DM allowlist (handles or `chat_id:*`). `open` requires `"*"`. +- `imessage.groupPolicy`: `open | allowlist | disabled` (default: open). +- `imessage.groupAllowFrom`: group sender allowlist. +- `imessage.groups`: per-group defaults + allowlist (use `"*"` for global defaults). +- `imessage.includeAttachments`: ingest attachments into context. +- `imessage.mediaMaxMb`: inbound/outbound media cap (MB). +- `imessage.textChunkLimit`: outbound chunk size (chars). + +Related global options: +- `routing.groupChat.mentionPatterns`. +- `messages.responsePrefix`. diff --git a/docs/signal.md b/docs/signal.md index b2970747d..d5a7fdbc7 100644 --- a/docs/signal.md +++ b/docs/signal.md @@ -6,117 +6,88 @@ read_when: --- # Signal (signal-cli) -Status: external CLI integration only. No libsignal embedding. +Updated: 2026-01-06 -## Why -- Signal OSS stack is GPL/AGPL; not compatible with Clawdbot MIT if bundled. -- signal-cli is unofficial; must stay up to date (Signal server churn). +Status: external CLI integration. Gateway talks to `signal-cli` over HTTP JSON-RPC + SSE. -## The “number model” (important) -- Clawdbot is a **device** connected via `signal-cli`. -- If you run `signal-cli` on **your personal Signal account**, Clawdbot will **not** respond to messages sent from that same account (loop protection: ignore sender==account). - - Result: you **cannot** “text yourself” to chat with the AI. -- For “I text her, she texts me back” you want a **separate Signal account/number for the bot**: - - Bot number runs `signal-cli` (linked device) - - Your personal number is in `signal.allowFrom` - - You DM the bot number; Clawdbot replies back to you +## What it is +- Signal provider via `signal-cli` (not embedded libsignal). +- Deterministic routing: replies always go back to Signal. +- DMs share the agent's main session; groups are isolated (`signal:group:`). -You can still run Clawdbot on your own Signal account if your goal is “respond to other people as me”, but not for self-chat. +## 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**. -## Model -- Run `signal-cli` as separate process (user-installed). -- Prefer `daemon --http=127.0.0.1:PORT` for JSON-RPC + SSE. -- Alternative: `jsonRpc` mode over stdin/stdout. +## Setup (fast path) +1) Install `signal-cli` (Java required). +2) Link a bot account: + - `signal-cli link -n "Clawdbot"` then scan the QR in Signal. +3) Configure Signal and start the gateway. -## Quickstart (bot number) -1) Install `signal-cli` (keep Java installed). - - If you use the CLI wizard, it can auto-install to `~/.clawdbot/tools/signal-cli/...`. - - If you want a pinned version (example: `v0.13.22`), install manually: - - Download the release asset for your platform from GitHub (tag `v0.13.22`). - - Extract it somewhere stable (example: `~/.clawdbot/tools/signal-cli/0.13.22/`). - - Set `signal.cliPath` to the extracted `signal-cli` binary path. -2) Link the bot account as a device: - - Run: `signal-cli link -n "Clawdbot"` - - Scan QR in Signal: Settings → Linked Devices → Link New Device - - Verify: `signal-cli listAccounts` includes the bot E.164 -3) Configure `~/.clawdbot/clawdbot.json`: +Example: ```json5 { signal: { enabled: true, - account: "+15551234567", // bot number (recommended) + account: "+15551234567", cliPath: "signal-cli", - autoStart: true, - httpHost: "127.0.0.1", - httpPort: 8080, - - // Who is allowed to talk to the bot (DMs) - dmPolicy: "pairing", // pairing | allowlist | open | disabled - allowFrom: ["+15557654321"], // your personal number ("open" requires ["*"]) - - // Group policy + allowlist - groupPolicy: "open", - groupAllowFrom: ["+15557654321"] + dmPolicy: "pairing", + allowFrom: ["+15557654321"] } } ``` -4) Run gateway; sanity probe: - - `clawdbot gateway call providers.status --params '{"probe":true}'` - - Expect `signal.probe.ok=true` and `signal.probe.version`. -5) DM the bot number from your phone; Clawdbot replies. -## DM pairing -- Default: `signal.dmPolicy="pairing"` — unknown DM senders get a pairing code. -- Approve via: `clawdbot pairing approve --provider signal `. +## Access control (DMs + groups) +DMs: +- Default: `signal.dmPolicy = "pairing"`. +- Unknown senders receive a pairing code; messages are ignored until approved. +- Approve via: + - `clawdbot pairing list --provider signal` + - `clawdbot pairing approve --provider signal ` +- Pairing is the default token exchange for Signal DMs. Details: https://docs.clawd.bot/pairing -## “Do I need a separate number?” -- If you want “I text her and she texts me back”, yes: **use a separate Signal account/number for the bot**. -- Your personal account can run `signal-cli`, but you can’t self-chat (Signal loop protection; Clawdbot ignores sender==account). +Groups: +- `signal.groupPolicy = open | allowlist | disabled`. +- `signal.groupAllowFrom` controls who can trigger in groups when `allowlist` is set. -If you have a second phone: -- Create/activate the bot number on that phone. -- Run `signal-cli link -n "Clawdbot"` on your Mac, scan the QR on the bot phone. -- Put your personal number in `signal.allowFrom`, then DM the bot number from your personal phone. +## How it works (behavior) +- `signal-cli` runs as a daemon; the gateway reads events via SSE. +- Inbound messages are normalized into the shared provider envelope. +- Replies always route back to the same number or group. -## Endpoints (daemon --http) -- `POST /api/v1/rpc` JSON-RPC request (single or batch). -- `GET /api/v1/events` SSE stream of `receive` notifications. -- `GET /api/v1/check` health probe (200 = up). +## Media + limits +- Attachments supported (base64 fetched from `signal-cli`). +- Default cap: `signal.mediaMaxMb`. +- Use `signal.ignoreAttachments` to skip downloading media. -## Multi-account -- Start daemon without `-a`. -- Include `params.account` (E164) on JSON-RPC calls. -- SSE `?account=+E164` filters events; no param = all accounts. +## Delivery targets (CLI/cron) +- DMs: `signal:+15551234567` (or plain E.164). +- Groups: `signal:group:`. +- Usernames: `username:` (if supported by your Signal account). -## Troubleshooting -- Gateway log coloring: `signal-cli: ...` lines are classified by severity; red means “treat this as an error”. -- `Failed to initialize HTTP Server` typically means the daemon can’t bind the HTTP port (already in use). Stop the other daemon or change `signal.httpPort`. +## Configuration reference (Signal) +Full configuration: https://docs.clawd.bot/configuration -## Minimal RPC surface -- `send` (recipient/groupId/username, message, attachments). -- `listGroups` (map group IDs). -- `subscribeReceive` / `unsubscribeReceive` (if manual receive). -- `startLink` / `finishLink` (optional device link flow). +Provider options: +- `signal.enabled`: enable/disable provider startup. +- `signal.account`: E.164 for the bot account. +- `signal.cliPath`: path to `signal-cli`. +- `signal.httpUrl`: full daemon URL (overrides host/port). +- `signal.httpHost`, `signal.httpPort`: daemon bind (default 127.0.0.1:8080). +- `signal.autoStart`: auto-spawn daemon (default true if `httpUrl` unset). +- `signal.receiveMode`: `on-start | manual`. +- `signal.ignoreAttachments`: skip attachment downloads. +- `signal.ignoreStories`: ignore stories from the daemon. +- `signal.sendReadReceipts`: forward read receipts. +- `signal.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing). +- `signal.allowFrom`: DM allowlist (E.164). `open` requires `"*"`. +- `signal.groupPolicy`: `open | allowlist | disabled` (default: open). +- `signal.groupAllowFrom`: group sender allowlist. +- `signal.textChunkLimit`: outbound chunk size (chars). +- `signal.mediaMaxMb`: inbound/outbound media cap (MB). -## Addressing (send targets) -- Direct: `signal:+15551234567` (or plain `+15551234567`) -- Groups: `signal:group:` -- Usernames: `username:` / `u:` - -## Process plan (Clawdbot adapter) -1) Detect `signal-cli` binary; refuse if missing. -2) Launch daemon (HTTP preferred), store PID. -3) Poll `/api/v1/check` until ready. -4) Open SSE stream; parse `event: receive`. -5) Translate receive payload into Clawdbot provider model. -6) On SSE disconnect, backoff + reconnect. - -## Storage -- signal-cli data lives in `$XDG_DATA_HOME/signal-cli/data` or - `$HOME/.local/share/signal-cli/data`. - -## References (local) -- `~/Projects/oss/signal-cli/README.md` -- `~/Projects/oss/signal-cli/man/signal-cli-jsonrpc.5.adoc` -- `~/Projects/oss/signal-cli/src/main/java/org/asamk/signal/http/HttpServerHandler.java` -- `~/Projects/oss/signal-cli/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcDispatcherHandler.java` +Related global options: +- `routing.groupChat.mentionPatterns` (Signal does not support native mentions). +- `messages.responsePrefix`. diff --git a/docs/slack.md b/docs/slack.md index a70822116..3dcfc6c45 100644 --- a/docs/slack.md +++ b/docs/slack.md @@ -5,221 +5,95 @@ read_when: "Setting up Slack or debugging Slack socket mode" # Slack (socket mode) -## 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) **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` -5) Invite the bot to channels you want it to read. -6) Slash Commands → create `/clawd` if you use `slack.slashCommand`. If you enable `commands.native`, add slash commands for the built-in chat commands (same names as `/help`). -7) App Home → enable the **Messages Tab** so users can DM the bot. +Updated: 2026-01-06 -Use the manifest below so scopes and events stay in sync. +Status: production-ready for DMs + channels via Slack Socket Mode. -## Manifest (optional) -Use this Slack app manifest to create the app quickly (adjust the name/command if you want). +## What it is +- Slack bot provider owned by the Gateway. +- Socket Mode only (no inbound HTTP server required). +- Deterministic routing: replies always go back to Slack. -```json +## Setup (fast path) +1) Create a Slack app. +2) Enable **Socket Mode** and create an **App Token** (`xapp-...`). +3) Install the app to your workspace and copy the **Bot Token** (`xoxb-...`). +4) Add required scopes + events (see Slack app manifest if needed). +5) Configure tokens and start the gateway. + +Example: +```json5 { - "display_information": { - "name": "Clawdbot", - "description": "Slack connector for Clawdbot" - }, - "features": { - "bot_user": { - "display_name": "Clawdbot", - "always_online": false - }, - "app_home": { - "messages_tab_enabled": true, - "messages_tab_read_only_enabled": false - }, - "slash_commands": [ - { - "command": "/clawd", - "description": "Send a message to Clawdbot", - "should_escape": false - } - ] - }, - "oauth_config": { - "scopes": { - "bot": [ - "chat:write", - "channels:history", - "channels:read", - "groups:history", - "groups:read", - "groups:write", - "im:history", - "im:read", - "im:write", - "mpim:history", - "mpim:read", - "mpim:write", - "users:read", - "app_mentions:read", - "reactions:read", - "reactions:write", - "pins:read", - "pins:write", - "emoji:read", - "commands", - "files:read", - "files:write" - ] - } - }, - "settings": { - "socket_mode_enabled": true, - "event_subscriptions": { - "bot_events": [ - "app_mention", - "message.channels", - "message.groups", - "message.im", - "message.mpim", - "reaction_added", - "reaction_removed", - "member_joined_channel", - "member_left_channel", - "channel_rename", - "pin_added", - "pin_removed" - ] - } + slack: { + enabled: true, + botToken: "xoxb-...", + appToken: "xapp-...", + dm: { policy: "pairing" }, + channels: { "#general": { allow: true, requireMention: true } } } } ``` -If you enable `commands.native`, add one `slash_commands` entry per command you want to expose (matching the `/help` list). +## Access control (DMs + channels) +DMs: +- Default: `slack.dm.policy = "pairing"`. +- Unknown senders receive a pairing code; messages are ignored until approved. +- Approve via: + - `clawdbot pairing list --provider slack` + - `clawdbot pairing approve --provider slack ` +- Pairing is the default token exchange for Slack DMs. Details: https://docs.clawd.bot/pairing -## 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://api.slack.com/docs/conversations-api for the overview. +Channels: +- `slack.groupPolicy = open | allowlist | disabled`. +- `slack.channels` acts as the allowlist when `groupPolicy = allowlist`. +- Mentions are required by default unless overridden per channel. -### Required by current code -- `chat:write` (send/update/delete messages via `chat.postMessage`) - https://api.slack.com/methods/chat.postMessage -- `im:write` (open DMs via `conversations.open` for user DMs) - https://api.slack.com/methods/conversations.open -- `channels:history`, `groups:history`, `im:history`, `mpim:history` - (`conversations.history` in [`src/slack/actions.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/slack/actions.ts)) - https://api.slack.com/methods/conversations.history -- `channels:read`, `groups:read`, `im:read`, `mpim:read` - (`conversations.info` in [`src/slack/monitor.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/slack/monitor.ts)) - https://api.slack.com/methods/conversations.info -- `users:read` (`users.info` in [`src/slack/monitor.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/slack/monitor.ts) + [`src/slack/actions.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/slack/actions.ts)) - https://api.slack.com/methods/users.info -- `reactions:read`, `reactions:write` (`reactions.get` / `reactions.add`) - https://api.slack.com/methods/reactions.get - https://api.slack.com/methods/reactions.add -- `pins:read`, `pins:write` (`pins.list` / `pins.add` / `pins.remove`) - https://api.slack.com/scopes/pins:read - https://api.slack.com/scopes/pins:write -- `emoji:read` (`emoji.list`) - https://api.slack.com/scopes/emoji:read -- `files:write` (uploads via `files.uploadV2`) - https://api.slack.com/messaging/files/uploading +## How it works (behavior) +- Inbound messages are normalized into the shared provider envelope. +- Replies always route back to the same channel or DM. +- Threading: replies to a message stay in that thread if it was a thread message. -### 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) - https://api.slack.com/scopes/chat:write.public -- `users:read.email` (only if we need email fields from `users.info`) - https://api.slack.com/changelog/2017-04-narrowing-email-access -- `files:read` (only if we start listing/reading file metadata) +## Commands +- Text commands: `commands.text = true` (standalone `/...` messages). +- Slack slash command: configure `slack.slashCommand` (separate from `commands.native`). -## Config -Slack uses Socket Mode only (no HTTP webhook server). Provide both tokens: +## Media + limits +- Files supported up to `slack.mediaMaxMb` (default 20 MB). +- Outbound chunking controlled by `slack.textChunkLimit`. -```json -{ - "slack": { - "enabled": true, - "botToken": "xoxb-...", - "appToken": "xapp-...", - "groupPolicy": "open", - "dm": { - "enabled": true, - "policy": "pairing", - "allowFrom": ["U123", "U456", "*"], - "groupEnabled": false, - "groupChannels": ["G123"] - }, - "channels": { - "C123": { "allow": true, "requireMention": true }, - "#general": { "allow": true, "requireMention": false } - }, - "reactionNotifications": "own", - "reactionAllowlist": ["U123"], - "actions": { - "reactions": true, - "messages": true, - "pins": true, - "memberInfo": true, - "emojiList": true - }, - "slashCommand": { - "enabled": true, - "name": "clawd", - "sessionPrefix": "slack:slash", - "ephemeral": true - }, - "textChunkLimit": 4000, - "mediaMaxMb": 20 - } -} -``` +## Delivery targets (CLI/cron) +- DMs: `user:` +- Channels: `channel:` -Tokens can also be supplied via env vars: -- `SLACK_BOT_TOKEN` -- `SLACK_APP_TOKEN` +## Configuration reference (Slack) +Full configuration: https://docs.clawd.bot/configuration -Ack reactions are controlled globally via `messages.ackReaction` + -`messages.ackReactionScope`. +Provider options: +- `slack.enabled`: enable/disable provider startup. +- `slack.botToken`: bot token (env: `SLACK_BOT_TOKEN`). +- `slack.appToken`: app token (env: `SLACK_APP_TOKEN`). +- `slack.groupPolicy`: `open | allowlist | disabled` (default: open). +- `slack.channels`: channel allowlist + per-channel `requireMention`. +- `slack.textChunkLimit`: outbound chunk size (chars). +- `slack.mediaMaxMb`: inbound/outbound media cap (MB). +- `slack.reactionNotifications`: `off | own | all | allowlist`. +- `slack.reactionAllowlist`: user allowlist for reaction notifications. +- `slack.actions.reactions`: enable reaction tool actions. +- `slack.actions.messages`: enable message read/send/edit/delete actions. +- `slack.actions.pins`: enable pin actions. +- `slack.actions.search`: enable search actions. +- `slack.actions.permissions`: enable permission inspection actions. +- `slack.actions.memberInfo`: enable member info actions. +- `slack.actions.channelInfo`: enable channel info actions. +- `slack.actions.emojiList`: enable emoji list actions. +- `slack.slashCommand.*`: configure the Slack slash command endpoint (`name`, `sessionPrefix`, `ephemeral`). +- `slack.dm.enabled`: enable/disable DMs. +- `slack.dm.policy`: `pairing | allowlist | open | disabled` (default: pairing). +- `slack.dm.allowFrom`: DM allowlist (ids/usernames). `open` requires `"*"`. +- `slack.dm.groupEnabled`: enable group DMs. +- `slack.dm.groupChannels`: group DM allowlist. -## Sessions + routing -- DMs share the `main` session (like WhatsApp/Telegram). -- Channels map to `slack:channel:` sessions. -- Slash commands use `slack:slash:` sessions. -- Native command registration is controlled by `commands.native`; text commands require standalone `/...` messages and can be disabled with `commands.text: false`. Slack slash commands are managed in the Slack app and are not removed automatically. Use `commands.useAccessGroups: false` to bypass access-group checks for commands. -- Full command list + config: https://docs.clawd.bot/slash-commands - -## DM security (pairing) -- Default: `slack.dm.policy="pairing"` — unknown DM senders get a pairing code. -- Approve via: `clawdbot pairing approve --provider slack `. -- To allow anyone: set `slack.dm.policy="open"` and `slack.dm.allowFrom=["*"]`. - -## Group policy -- `slack.groupPolicy` controls channel handling (`open|disabled|allowlist`). -- `allowlist` requires channels to be listed in `slack.channels`. - -## Delivery targets -Use these with cron/CLI sends: -- `user:` for DMs -- `channel:` for channels - -## Tool actions -Slack tool actions can be gated with `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 | - -## Notes -- Mention gating is controlled via `slack.channels` (set `requireMention` to `true`); `routing.groupChat.mentionPatterns` also count as mentions. -- Reaction notifications follow `slack.reactionNotifications` (use `reactionAllowlist` with mode `allowlist`). -- Attachments are downloaded to the media store when permitted and under the size limit. +Related global options: +- `routing.groupChat.mentionPatterns`. +- `commands.text`, `commands.useAccessGroups`. +- `messages.responsePrefix`, `messages.ackReaction`, `messages.ackReactionScope`. diff --git a/docs/telegram.md b/docs/telegram.md index 5f682d457..57f6a80df 100644 --- a/docs/telegram.md +++ b/docs/telegram.md @@ -5,94 +5,89 @@ read_when: --- # Telegram (Bot API) -Updated: 2025-12-07 +Updated: 2026-01-06 -Status: ready for bot-mode use with grammY (long-polling by default; webhook supported when configured). Text + media send, mention-gated group replies with per-group overrides, and optional proxy support are implemented. +Status: production-ready for bot DMs + groups via grammY. Long-polling by default; webhook optional. -## Goals -- Let you talk to Clawdbot via a Telegram bot in DMs and groups. -- Share the same `main` session used by WhatsApp/WebChat; groups stay isolated as `telegram:group:`. -- Keep transport routing deterministic: replies always go back to the provider they arrived on. +## What it is +- A Telegram Bot API provider owned by the Gateway. +- Deterministic routing: replies go back to Telegram; the model never chooses providers. +- DMs share the agent's main session; groups stay isolated (`telegram:group:`). -## How it will work (Bot API) -1) Create a bot with @BotFather and grab the token. -2) Configure Clawdbot with `TELEGRAM_BOT_TOKEN` (or `telegram.botToken` in `~/.clawdbot/clawdbot.json`). -3) Run the gateway; it auto-starts Telegram only when a `telegram` config section exists **and** a bot token is set (unless `telegram.enabled = false`). - - If you prefer env vars, still add `telegram: { enabled: true }` to `~/.clawdbot/clawdbot.json` and set `TELEGRAM_BOT_TOKEN`. - - **Long-polling** is the default. - - **Webhook mode** is enabled by setting `telegram.webhookUrl` (optionally `telegram.webhookSecret` / `telegram.webhookPath`). - - The webhook listener currently binds to `0.0.0.0:8787` and serves `POST /telegram-webhook` by default. - - If you need a different public port/host, set `telegram.webhookUrl` to the externally reachable URL and use a reverse proxy to forward to `:8787`. -4) Direct chats: secure by default — unknown senders are gated by `telegram.dmPolicy` (default: `"pairing"`). The bot responds with a pairing code that the owner must approve before messages are processed. If you really want public inbound DMs: set `telegram.dmPolicy="open"` and `telegram.allowFrom=["*"]`. -5) Groups: add the bot, disable privacy mode (or make it admin) so it can read messages; group threads stay on `telegram:group:`. When `telegram.groups` is set, it becomes a group allowlist (use `"*"` to allow all). Mention/command gating defaults come from `telegram.groups`. -6) Allowlist + pairing: - - Direct chats: `telegram.allowFrom` (chat ids) or pairing approvals via `clawdbot pairing approve --provider telegram ` (alias: `clawdbot telegram pairing approve `). - - Groups: set `telegram.groupPolicy = "allowlist"` and list senders in `telegram.groupAllowFrom` (fallback: explicit `telegram.allowFrom`). - - Commands respect group allowlists/policies by default; set `commands.useAccessGroups: false` to bypass. -7) Native commands: set `commands.native: true` to register `/` commands; set `commands.native: false` to clear previously registered commands. +## Setup (fast path) +1) Create a bot with @BotFather and copy the token. +2) Configure the token (env or config). Example: -## Capabilities & limits (Bot API) -- Sees only messages sent after it’s added to a chat; no pre-history access. -- Cannot DM users first; they must initiate. Channels are receive-only unless the bot is an admin poster. -- File size caps follow Telegram Bot API (up to 2 GB for documents; smaller for some media types). -- Typing indicators (`sendChatAction`) supported; native replies are **off by default** and enabled via `telegram.replyToMode` + reply tags. - -## Planned implementation details -- Library: grammY is the only client for send + gateway (fetch fallback removed); grammY throttler is enabled by default to stay under Bot API limits. -- Inbound normalization: maps Bot API updates to `MsgContext` with `Provider: "telegram"`, `ChatType: direct|group`, `SenderName`, `MediaPath`/`MediaType` when attachments arrive, `Timestamp`, and reply-to metadata (`ReplyToId`, `ReplyToBody`, `ReplyToSender`) when the user replies; reply context is appended to `Body` as a `[Replying to ...]` block (includes `id:` when available); groups require @bot mention or a `routing.groupChat.mentionPatterns` match by default (override per chat in config). -- Outbound: text and media (photo/video/audio/document) with optional caption; chunked to limits. Typing cue sent best-effort. -- Config: `TELEGRAM_BOT_TOKEN` env or `telegram.botToken` required; `telegram.dmPolicy`, `telegram.groups` (group allowlist + mention defaults), `telegram.allowFrom`, `telegram.groupAllowFrom`, `telegram.groupPolicy`, `telegram.mediaMaxMb`, `telegram.replyToMode`, `telegram.proxy`, `telegram.webhookSecret`, `telegram.webhookUrl`, `telegram.webhookPath` supported. - - Ack reactions are controlled globally via `messages.ackReaction` + `messages.ackReactionScope`. - - Mention gating precedence (most specific wins): `telegram.groups..requireMention` → `telegram.groups."*".requireMention` → default `true`. - -Example config: ```json5 { telegram: { enabled: true, botToken: "123:abc", - dmPolicy: "pairing", // pairing | allowlist | open | disabled - replyToMode: "off", - groups: { - "*": { requireMention: true }, // allow all groups - "123456789": { requireMention: false } // group chat id - }, - allowFrom: ["123456789"], // direct chat ids allowed ("open" requires ["*"]) - groupPolicy: "allowlist", - groupAllowFrom: ["tg:123456789", "@alice"], - mediaMaxMb: 5, - proxy: "socks5://localhost:9050", - webhookSecret: "mysecret", - webhookPath: "/telegram-webhook", - webhookUrl: "https://yourdomain.com/telegram-webhook" + dmPolicy: "pairing", + groups: { "*": { requireMention: true } } } } ``` -- Tests: grammY-based paths in [`src/telegram/*.test.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/telegram/*.test.ts) cover DM + group gating; add more media and webhook cases as needed. -## Group etiquette -- Keep privacy mode off if you expect the bot to read all messages; with privacy on, it only sees commands/mentions. -- Make the bot an admin if you need it to send in restricted groups or channels. -- Mention the bot (`@yourbot`), use a `routing.groupChat.mentionPatterns` trigger, or send a standalone `/...` command. Per-group overrides live in `telegram.groups` if you want always-on behavior; if `telegram.groups` is set, add `"*"` to keep existing allow-all behavior. +3) Start the gateway. Telegram starts when a `telegram` config section exists and a token is resolved. +4) DM access defaults to pairing. Approve the code when the bot is first contacted. +5) For groups: add the bot, disable privacy mode (or make it admin), then set `telegram.groups` to control mention gating + allowlists. -## Reply tags -To request a threaded reply, the model can include one tag in its output: -- `[[reply_to_current]]` — reply to the triggering Telegram message. -- `[[reply_to:]]` — reply to a specific message id from context. -Current message ids are appended to prompts as `[message_id: …]`; reply context includes `id:` when available. +## How it works (behavior) +- Inbound messages are normalized into the shared provider envelope with reply context and media placeholders. +- Group replies require a mention by default (native @mention or `routing.groupChat.mentionPatterns`). +- Replies always route back to the same Telegram chat. -Behavior is controlled by `telegram.replyToMode`: -- `off`: ignore tags. -- `first`: only the first outbound chunk/attachment is a reply. -- `all`: every outbound chunk/attachment is a reply. +## Access control (DMs + groups) +- Default: `telegram.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved. +- Approve via: + - `clawdbot pairing list --provider telegram` + - `clawdbot pairing approve --provider telegram ` +- Pairing is the default token exchange used for Telegram DMs. Details: https://docs.clawd.bot/pairing -## Roadmap -- ✅ Design and defaults (this doc) -- ✅ grammY long-poll gateway + text/media send -- ✅ Proxy + webhook helpers (setWebhook/deleteWebhook, health endpoint, optional public URL) -- ⏳ Add more grammY coverage (webhook payloads, media edge cases) +Group gating: +- `telegram.groupPolicy = open | allowlist | disabled`. +- `telegram.groups` doubles as a group allowlist when set (include `"*"` to allow all). -## Safety & ops -- Treat the bot token as a secret (equivalent to account control); prefer `TELEGRAM_BOT_TOKEN` or a locked-down config file (`chmod 600 ~/.clawdbot/clawdbot.json`). -- Respect Telegram rate limits (429s); grammY throttling is enabled by default. -- Use a test bot for development to avoid hitting production chats. +## Long-polling vs webhook +- Default: long-polling (no public URL required). +- Webhook mode: set `telegram.webhookUrl` (optionally `telegram.webhookSecret` + `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 `telegram.webhookUrl` at the public endpoint. + +## Reply threading +Telegram supports optional threaded replies via tags: +- `[[reply_to_current]]` -- reply to the triggering message. +- `[[reply_to:]]` -- reply to a specific message id. + +Controlled by `telegram.replyToMode`: +- `off` (default), `first`, `all`. + +## Delivery targets (CLI/cron) +- Use a chat id (`123456789`) or a username (`@name`) as the target. +- Example: `clawdbot send --provider telegram --to 123456789 "hi"`. + +## Configuration reference (Telegram) +Full configuration: https://docs.clawd.bot/configuration + +Provider options: +- `telegram.enabled`: enable/disable provider startup. +- `telegram.botToken`: bot token (BotFather). +- `telegram.tokenFile`: read token from file path. +- `telegram.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing). +- `telegram.allowFrom`: DM allowlist (ids/usernames). `open` requires `"*"`. +- `telegram.groupPolicy`: `open | allowlist | disabled` (default: open). +- `telegram.groupAllowFrom`: group sender allowlist (ids/usernames). +- `telegram.groups`: per-group defaults + allowlist (use `"*"` for global defaults). +- `telegram.replyToMode`: `off | first | all`. +- `telegram.textChunkLimit`: outbound chunk size (chars). +- `telegram.mediaMaxMb`: inbound/outbound media cap (MB). +- `telegram.proxy`: proxy URL for Bot API calls (SOCKS/HTTP). +- `telegram.webhookUrl`: enable webhook mode. +- `telegram.webhookSecret`: webhook secret (optional). +- `telegram.webhookPath`: local webhook path (default `/telegram-webhook`). + +Related global options: +- `routing.groupChat.mentionPatterns` (mention gating patterns). +- `commands.native`, `commands.text`, `commands.useAccessGroups` (command behavior). +- `messages.responsePrefix`, `messages.ackReaction`, `messages.ackReactionScope`. diff --git a/docs/webchat.md b/docs/webchat.md index 46db1b999..5f21f5162 100644 --- a/docs/webchat.md +++ b/docs/webchat.md @@ -3,32 +3,34 @@ summary: "Loopback WebChat static host and Gateway WS usage for chat UI" read_when: - Debugging or configuring WebChat access --- -# WebChat (SwiftUI + Gateway WS) +# WebChat (Gateway WebSocket UI) -Updated: 2025-12-17 +Updated: 2026-01-06 + +Status: the macOS/iOS SwiftUI chat UI talks directly to the Gateway WebSocket. ## What it is -- A native SwiftUI chat UI (macOS app / iOS) that talks directly to the Gateway WebSocket. -- No embedded browser/WKWebView and no bundled static WebChat HTTP server. -- Data plane is entirely Gateway WS: methods `chat.history`, `chat.send`, `chat.abort`; events `chat`, `agent`, `presence`, `tick`, `health`. +- A native chat UI for the gateway (no embedded browser and no local static server). +- Uses the same sessions and routing rules as other providers. +- Deterministic routing: replies always go back to WebChat. -## How it connects -- The UI performs Gateway WS `connect`, then calls `chat.history` for bootstrap and `chat.send` for sends; it listens to `chat/agent/presence/tick/health` events. -- History comes from the Gateway via `chat.history` (no local session file watching). -- If Gateway WS is unavailable, the UI surfaces the error and blocks send. +## How it works (behavior) +- The UI connects to the Gateway WebSocket and uses `chat.history` + `chat.send`. +- History is always fetched from the gateway (no local file watching). +- If the gateway is unreachable, WebChat is read-only. ## Remote use -- In remote mode, the macOS app forwards the Gateway WebSocket control port via SSH and uses that for the SwiftUI chat UI. -- You generally should not need to manage tunnels manually; see `RemoteTunnelManager` in the app. +- Remote mode tunnels the gateway WebSocket over SSH/Tailscale. +- You do not need to run a separate WebChat server. -## Config -- WebChat does not have a separate HTTP port/config anymore. -- Gateway WS is configured via the app’s gateway endpoint settings (`GatewayEndpointStore`) or `clawdbot gateway --port` when running locally. +## Configuration reference (WebChat) +Full configuration: https://docs.clawd.bot/configuration -## Failure handling -- UI errors when the Gateway handshake fails or the WS drops. -- No fallback transport; the Gateway WS is required. +Provider options: +- No dedicated `webchat.*` block. WebChat uses the gateway endpoint + auth settings below. -## Dev notes -- macOS glue: [`apps/macos/Sources/Clawdbot/WebChatSwiftUI.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/Clawdbot/WebChatSwiftUI.swift) + [`apps/macos/Sources/Clawdbot/WebChatManager.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/Clawdbot/WebChatManager.swift). -- Remote tunnel helper: [`apps/macos/Sources/Clawdbot/RemotePortTunnel.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/Clawdbot/RemotePortTunnel.swift). +Related global options: +- `gateway.port`, `gateway.bind`: WebSocket host/port. +- `gateway.auth.mode`, `gateway.auth.token`, `gateway.auth.password`: WebSocket auth. +- `gateway.remote.url`, `gateway.remote.token`, `gateway.remote.password`: remote gateway target. +- `session.*`: session storage and main key defaults. diff --git a/docs/whatsapp.md b/docs/whatsapp.md index e23c597bc..e3fff6262 100644 --- a/docs/whatsapp.md +++ b/docs/whatsapp.md @@ -5,152 +5,83 @@ read_when: --- # WhatsApp (web provider) -Updated: 2025-12-23 +Updated: 2026-01-06 -Status: WhatsApp Web via Baileys only. Gateway owns the session(s). +Status: WhatsApp Web via Baileys. Gateway owns the session(s). -## 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. +## What it is +- WhatsApp Web connection managed by the Gateway. +- Deterministic routing: replies always return to WhatsApp. +- DMs share the agent's main session; groups are isolated (`whatsapp:group:`). -## 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. +## Setup (fast path) +1) Use a real mobile number (WhatsApp blocks most VoIP numbers). +2) Run `clawdbot login` and scan the QR (Linked Devices). +3) Start the gateway; the WhatsApp provider starts when a linked session exists. +4) Lock down DMs and groups (pairing + allowlists are default-safe). -## Getting a phone number +Multi-account: +- `clawdbot login --account ` +- Configure `whatsapp.accounts.` for per-account settings. -WhatsApp requires a real mobile number for verification. VoIP and virtual numbers are usually blocked. +## Access control (DMs + groups) +DMs: +- Default: `whatsapp.dmPolicy = "pairing"`. +- Unknown senders get a pairing code and are ignored until approved. +- Approve via: + - `clawdbot pairing list --provider whatsapp` + - `clawdbot pairing approve --provider whatsapp ` +- Pairing is the default token exchange for WhatsApp DMs. Details: https://docs.clawd.bot/pairing -**Recommended approaches:** -- **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 -- **Prepaid SIM** — cheap, just needs to receive one SMS for verification +Groups: +- `whatsapp.groupPolicy = open | allowlist | disabled`. +- `whatsapp.groups` sets per-group defaults and becomes an allowlist when present (use `"*"` to allow all). +- Mention gating defaults to `requireMention: true` unless overridden. -**Avoid:** TextNow, Google Voice, most "free SMS" services — WhatsApp blocks these aggressively. +## How it works (behavior) +- Inbound messages are normalized into the shared provider envelope with reply context. +- Group replies require a mention by default (native mentions or `routing.groupChat.mentionPatterns`). +- Recent group history can be injected for context (see `routing.groupChat.historyLimit`). -**Tip:** The number only needs to receive one verification SMS. After that, WhatsApp Web sessions persist via `creds.json`. +## Reply delivery +- Standard WhatsApp messages (no threaded replies). +- Text chunking is applied to stay within limits. -**WhatsApp Business:** You can use WhatsApp Business on the same phone with a different number. This is a great option if you want to keep your personal WhatsApp separate — just install WhatsApp Business and register it with Clawdbot's dedicated number. +## Media +- Images/video/audio/documents supported. +- Default cap: 5 MB per item (override via `agent.mediaMaxMb`). +- Oversize media returns a warning instead of sending. -## Login + credentials -- Login command: `clawdbot login` (QR via Linked Devices). -- Multi-account login: `clawdbot login --account ` (`` = `accountId`). -- Default account (when `--account` is omitted): `default` if present, otherwise the first configured account id (sorted). -- Credentials stored in `~/.clawdbot/credentials/whatsapp//creds.json`. -- Backup copy at `creds.json.bak` (restored on corruption). -- Legacy compatibility: older installs stored Baileys files directly in `~/.clawdbot/credentials/`. -- Logout: `clawdbot logout` (or `--account `) deletes WhatsApp auth state (but keeps shared `oauth.json`). -- Logged-out socket => error instructs re-link. +## Delivery targets (CLI/cron) +- DMs: E.164 (`+15551234567`). +- Groups: group JID (`12345-678@g.us`). -## 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. -- Direct chats use E.164; groups use group JID. -- **DM policy**: `whatsapp.dmPolicy` controls direct chat access (default: `pairing`). - - Pairing: unknown senders get a pairing code (approve via `clawdbot pairing approve --provider whatsapp `). - - Open: requires `whatsapp.allowFrom` to include `"*"`. - - Self messages are always allowed; “self-chat mode” still requires `whatsapp.allowFrom` to include your own number. -- **Group policy**: `whatsapp.groupPolicy` controls group handling (`open|disabled|allowlist`). - - `allowlist` uses `whatsapp.groupAllowFrom` (fallback: explicit `whatsapp.allowFrom`). -- **Self-chat mode**: avoids auto read receipts and ignores mention JIDs. -- Read receipts sent for non-self-chat DMs. +## Configuration reference (WhatsApp) +Full configuration: https://docs.clawd.bot/configuration -## Message normalization (what the model sees) -- `Body` is the current message body with envelope. -- Quoted reply context is **always appended**: - ``` - [Replying to +1555 id:ABC123] - > - [/Replying] - ``` -- Reply metadata also set: - - `ReplyToId` = stanzaId - - `ReplyToBody` = quoted body or media placeholder - - `ReplyToSender` = E.164 when known -- Media-only inbound messages use placeholders: - - `` +Provider options: +- `whatsapp.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing). +- `whatsapp.allowFrom`: DM allowlist (E.164). `open` requires `"*"`. +- `whatsapp.groupPolicy`: `open | allowlist | disabled` (default: open). +- `whatsapp.groupAllowFrom`: group sender allowlist (E.164). +- `whatsapp.groups`: per-group defaults + allowlist (use `"*"` for global defaults). +- `whatsapp.textChunkLimit`: outbound chunk size (chars). +- `whatsapp.accounts`: per-account overrides: + - `whatsapp.accounts..enabled` + - `whatsapp.accounts..authDir` + - `whatsapp.accounts..dmPolicy` + - `whatsapp.accounts..allowFrom` + - `whatsapp.accounts..groupPolicy` + - `whatsapp.accounts..groupAllowFrom` + - `whatsapp.accounts..groups` + - `whatsapp.accounts..textChunkLimit` -## Groups -- Groups map to `agent::whatsapp:group:` sessions. -- Group policy: `whatsapp.groupPolicy = open|disabled|allowlist` (default `open`). -- Activation modes: - - `mention` (default): requires @mention or regex match. - - `always`: always triggers. -- `/activation mention|always` is owner-only and must be sent as a standalone message. -- Owner = `whatsapp.allowFrom` (or self E.164 if unset). -- **History injection**: - - Recent messages (default 50) inserted under: - `[Chat messages since your last reply - for context]` - - Current message under: - `[Current message - respond to this]` - - Sender suffix appended: `[from: Name (+E164)]` -- Group metadata cached 5 min (subject + participants). +Runtime options (WhatsApp web provider): +- `web.enabled`: enable/disable provider startup. +- `web.heartbeatSeconds`: gateway heartbeat cadence. +- `web.reconnect.*`: reconnect backoff (`initialMs`, `maxMs`, `factor`, `jitter`, `maxAttempts`). -## Reply delivery (threading) -- WhatsApp Web sends standard messages (no quoted reply threading in the current gateway). -- Reply tags are ignored on this provider. - -## Outbound send (text + media) -- Uses active web listener; error if gateway not running. -- Text chunking: 4k max per message. -- Media: - - Image/video/audio/document supported. - - Audio sent as PTT; `audio/ogg` => `audio/ogg; codecs=opus`. - - Caption only on first media item. - - Media fetch supports HTTP(S) and local paths. - - Animated GIFs: WhatsApp expects MP4 with `gifPlayback: true` for inline looping. - - CLI: `clawdbot send --media --gif-playback` - - Gateway: `send` params include `gifPlayback: true` - -## Media limits + optimization -- Default cap: 5 MB (per media item). -- Override: `agent.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** is global (`agent.heartbeat.*`) and runs in the main session. - - Uses the configured heartbeat prompt (default: `Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.`) + `HEARTBEAT_OK` skip behavior. - - Delivery defaults to the last used provider (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 -- `whatsapp.dmPolicy` (DM policy: pairing/allowlist/open/disabled). -- `whatsapp.allowFrom` (DM allowlist). -- `whatsapp.accounts..*` (per-account settings + optional `authDir`). -- `whatsapp.groupAllowFrom` (group sender allowlist). -- `whatsapp.groupPolicy` (group policy). -- `whatsapp.groups` (group allowlist + mention gating defaults; use `"*"` to allow all) -- `routing.groupChat.mentionPatterns` -- `routing.groupChat.historyLimit` -- `messages.messagePrefix` (inbound prefix) -- `messages.responsePrefix` (outbound prefix) -- `agent.mediaMaxMb` -- `agent.heartbeat.every` -- `agent.heartbeat.model` (optional override) -- `agent.heartbeat.target` -- `agent.heartbeat.to` -- `session.*` (scope, idle, store, mainKey) -- `web.enabled` (disable provider startup when false) -- `web.heartbeatSeconds` -- `web.reconnect.*` - -## Logs + troubleshooting -- Subsystems: `whatsapp/inbound`, `whatsapp/outbound`, `web-heartbeat`, `web-reconnect`. -- Log file: `/tmp/clawdbot/clawdbot-YYYY-MM-DD.log` (configurable). -- Troubleshooting guide: [`docs/troubleshooting.md`](https://docs.clawd.bot/troubleshooting). - -## Tests -- [`src/web/auto-reply.test.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/web/auto-reply.test.ts) (mention gating, history injection, reply flow) -- [`src/web/monitor-inbox.test.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/web/monitor-inbox.test.ts) (inbound parsing + reply context) -- [`src/web/outbound.test.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/web/outbound.test.ts) (send mapping + media) +Related global options: +- `routing.groupChat.mentionPatterns`, `routing.groupChat.historyLimit`. +- `commands.text`, `commands.useAccessGroups`. +- `messages.responsePrefix`, `messages.ackReaction`, `messages.ackReactionScope`.