Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8aa2c0229d | ||
|
|
485481895e | ||
|
|
f01784369d | ||
|
|
0a853df6c9 | ||
|
|
25ada49e36 |
No files matched your search
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report a problem or unexpected behavior in Clawdbot.
|
||||
title: "[Bug]: "
|
||||
labels: bug
|
||||
---
|
||||
|
||||
## Summary
|
||||
What went wrong?
|
||||
|
||||
## Steps to reproduce
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
## Expected behavior
|
||||
What did you expect to happen?
|
||||
|
||||
## Actual behavior
|
||||
What actually happened?
|
||||
|
||||
## Environment
|
||||
- Clawdbot version:
|
||||
- OS:
|
||||
- Install method (pnpm/npx/docker/etc):
|
||||
|
||||
## Logs or screenshots
|
||||
Paste relevant logs or add screenshots (redact secrets).
|
||||
@@ -1,8 +0,0 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Onboarding
|
||||
url: https://discord.gg/clawd
|
||||
about: New to Clawdbot? Join Discord for setup guidance from Krill in #help.
|
||||
- name: Support
|
||||
url: https://discord.gg/clawd
|
||||
about: Get help from Krill and the community on Discord in #help.
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea or improvement for Clawdbot.
|
||||
title: "[Feature]: "
|
||||
labels: enhancement
|
||||
---
|
||||
|
||||
## Summary
|
||||
Describe the problem you are trying to solve or the opportunity you see.
|
||||
|
||||
## Proposed solution
|
||||
What would you like Clawdbot to do?
|
||||
|
||||
## Alternatives considered
|
||||
Any other approaches you have considered?
|
||||
|
||||
## Additional context
|
||||
Links, screenshots, or related issues.
|
||||
@@ -52,21 +52,32 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Setup Node.js
|
||||
if: matrix.runtime == 'node'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
check-latest: true
|
||||
|
||||
- name: Setup Bun
|
||||
if: matrix.runtime == 'bun'
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
# bun.sh downloads currently fail with:
|
||||
# "Failed to list releases from GitHub: 401" -> "Unexpected HTTP response: 400"
|
||||
bun-download-url: "https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64.zip"
|
||||
|
||||
- name: Setup Node.js (tooling for bun)
|
||||
if: matrix.runtime == 'bun'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
check-latest: true
|
||||
|
||||
- name: Runtime versions
|
||||
run: |
|
||||
node -v
|
||||
npm -v
|
||||
bun -v
|
||||
if [ "${{ matrix.runtime }}" = "bun" ]; then bun -v; fi
|
||||
|
||||
- name: Capture node path
|
||||
run: echo "NODE_BIN=$(dirname \"$(node -p \"process.execPath\")\")" >> "$GITHUB_ENV"
|
||||
|
||||
@@ -3,8 +3,6 @@ node_modules
|
||||
dist
|
||||
*.bun-build
|
||||
pnpm-lock.yaml
|
||||
bun.lock
|
||||
bun.lockb
|
||||
coverage
|
||||
.pnpm-store
|
||||
.worktrees/
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
- Docs: `docs/` (images, queue, Pi config). Built output lives in `dist/`.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
- Runtime baseline: Node **22+** (keep Node + Bun paths working).
|
||||
- Install deps: `pnpm install`
|
||||
- Also supported: `bun install` (keep `pnpm-lock.yaml` + Bun patching in sync when touching deps/patches).
|
||||
- Prefer Bun for TypeScript execution (scripts, dev, tests): `bun <file.ts>` / `bunx <tool>`.
|
||||
- Run CLI in dev: `pnpm clawdbot ...` (bun) or `pnpm dev`.
|
||||
- Node remains supported for running built output (`dist/*`) and production installs.
|
||||
- Run CLI in dev: `pnpm clawdbot ...` (tsx entry) or `pnpm dev` for `src/index.ts`.
|
||||
- Type-check/build: `pnpm build` (tsc)
|
||||
- Lint/format: `pnpm lint` (biome check), `pnpm format` (biome format)
|
||||
- Tests: `pnpm test` (vitest); coverage: `pnpm test:coverage`
|
||||
@@ -34,16 +30,6 @@
|
||||
- Follow concise, action-oriented commit messages (e.g., `CLI: add verbose flag to send`).
|
||||
- Group related changes; avoid bundling unrelated refactors.
|
||||
- PRs should summarize scope, note testing performed, and mention any user-facing changes or new flags.
|
||||
- PR review flow: when given a PR link, review via `gh pr view`/`gh pr diff` and do **not** change branches.
|
||||
- PR merge flow: create a temp branch from `main`, merge the PR branch into it (prefer squash unless commit history is important; use rebase/merge when it is). Always try to merge the PR unless it’s truly difficult, then use another approach. If we squash, add the PR author as a co-contributor. Apply fixes, add changelog entry (include PR # + thanks), run full gate before the final commit, commit, merge back to `main`, delete the temp branch, and end on `main`.
|
||||
- When working on a PR: add a changelog entry with the PR number and thank the contributor.
|
||||
- When working on an issue: reference the issue in the changelog entry.
|
||||
- When merging a PR: leave a PR comment that explains exactly what we did and include the SHA hashes.
|
||||
- When merging a PR from a new contributor: add their avatar to the README “Thanks to all clawtributors” thumbnail list.
|
||||
|
||||
### PR Workflow (Review vs Land)
|
||||
- **Review mode (PR link only):** read `gh pr view/diff`; **do not** switch branches; **do not** change code.
|
||||
- **Landing mode:** create an integration branch from `main`, bring in PR commits (**prefer rebase** for linear history; **merge allowed** when complexity/conflicts make it safer), apply fixes, add changelog (+ thanks + PR #), run full gate **locally before committing** (`pnpm lint && pnpm build && pnpm test`), commit, merge back to `main`, then `git switch main` (never stay on a topic branch after landing).
|
||||
|
||||
## Security & Configuration Tips
|
||||
- Web provider stores creds at `~/.clawdbot/credentials/`; rerun `clawdbot login` if logged out.
|
||||
|
||||
+2
-109
@@ -4,62 +4,10 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Breaking
|
||||
- **SECURITY (update ASAP):** inbound DMs are now **locked down by default** on Telegram/WhatsApp/Signal/iMessage/Discord/Slack.
|
||||
- Previously, if you didn’t configure an allowlist, your bot could be **open to anyone** (especially discoverable Telegram bots).
|
||||
- New default: DM pairing (`dmPolicy="pairing"` / `discord.dm.policy="pairing"` / `slack.dm.policy="pairing"`).
|
||||
- To keep old “open to everyone” behavior: set `dmPolicy="open"` and include `"*"` in the relevant `allowFrom` (Discord/Slack: `discord.dm.allowFrom` / `slack.dm.allowFrom`).
|
||||
- Approve requests via `clawdbot pairing list --provider <provider>` + `clawdbot pairing approve --provider <provider> <code>` (Telegram also supports `clawdbot telegram pairing ...`).
|
||||
- Timestamps in agent envelopes are now UTC (compact `YYYY-MM-DDTHH:mmZ`); removed `messages.timestampPrefix`. Add `agent.userTimezone` to tell the model the user’s local time (system prompt only).
|
||||
- Model config schema changes (auth profiles + model lists); doctor auto-migrates and the gateway rewrites legacy configs on startup.
|
||||
- Commands: gate all slash commands to authorized senders; add `/compact` to manually compact session context.
|
||||
- Groups: `whatsapp.groups`, `telegram.groups`, and `imessage.groups` now act as allowlists when set. Add `"*"` to keep allow-all behavior.
|
||||
|
||||
### Fixes
|
||||
- Skills: harden skill installs with process-group timeouts, clearer timeout messaging, and logged rerun commands when installs fail.
|
||||
- Heartbeat: default interval now 30m with a new default prompt + HEARTBEAT.md template.
|
||||
- Onboarding: write auth profiles to the multi-agent path (`~/.clawdbot/agents/main/agent/`) so the gateway finds credentials on first startup. Thanks @minghinmatthewlam for PR #327.
|
||||
- Docs: add missing `ui:install` setup step in the README. Thanks @hugobarauna for PR #300.
|
||||
- Build: import tool-display JSON as a module instead of runtime file reads. Thanks @mukhtharcm for PR #312.
|
||||
- Browser: fix `browser snapshot`/`browser act` timeouts under Bun by patching Playwright’s CDP WebSocket selection. Thanks @azade-c for PR #307.
|
||||
- Browser: add `--browser-profile` flag and honor profile in tabs routes + browser tool. Thanks @jamesgroat for PR #324.
|
||||
- Telegram: stop typing after tool results. Thanks @AbhisekBasu1 for PR #322.
|
||||
- Messages: stop defaulting ack reactions to 👀 when identity emoji is missing.
|
||||
- Auto-reply: require slash for control commands to avoid false triggers in normal text.
|
||||
- Auto-reply: flag error payloads and improve Bun socket error messaging. Thanks @emanuelst for PR #331.
|
||||
- Commands: unify native + text chat commands behind `commands.*` config (Discord/Slack/Telegram). Thanks @thewilloftheshadow for PR #275.
|
||||
- Auto-reply: treat steer during compaction as a follow-up, queued until compaction completes.
|
||||
- Auth: lock auth profile refreshes to avoid multi-instance OAuth logouts; keep credentials on refresh failure.
|
||||
- Gateway/CLI: stop forcing localhost URL in remote mode so remote gateway config works. Thanks @oswalpalash for PR #293.
|
||||
- Onboarding: prompt immediately for OpenAI Codex redirect URL on remote/headless logins.
|
||||
- Configure: add OpenAI Codex (ChatGPT OAuth) auth choice (align with onboarding).
|
||||
- Doctor: suggest adding the workspace memory system when missing (opt-out via `--no-workspace-suggestions`).
|
||||
- Doctor: normalize default workspace path to `~/clawd` (avoid `~/clawdbot`).
|
||||
- Workspace: only create `BOOTSTRAP.md` for brand-new workspaces (don’t recreate after deletion).
|
||||
- Build: fix duplicate protocol export, align Codex OAuth options, and add proper-lockfile typings.
|
||||
- Build: install Bun in the Dockerfile so `pnpm build` can run Bun scripts. Thanks @loukotal for PR #284.
|
||||
- Typing indicators: stop typing once the reply dispatcher drains to prevent stuck typing across Discord/Telegram/WhatsApp.
|
||||
- Typing indicators: fix a race that could keep the typing indicator stuck after quick replies. Thanks @thewilloftheshadow for PR #270.
|
||||
- Google: merge consecutive messages to satisfy strict role alternation for Google provider models. Thanks @Asleep123 for PR #266.
|
||||
- Postinstall: handle targetDir symlinks in the install script. Thanks @obviyus for PR #272.
|
||||
- WhatsApp/Telegram: add groupPolicy handling for group messages and normalize allowFrom matching (tg/telegram prefixes). Thanks @mneves75.
|
||||
- Auto-reply: add configurable ack reactions for inbound messages (default 👀 or `identity.emoji`) with scope controls. Thanks @obviyus for PR #178.
|
||||
- Polls: unify WhatsApp + Discord poll sends via the gateway + CLI (`clawdbot poll`). (#123) — thanks @dbhurley
|
||||
- Model: retry fallback on rate-limit/quota errors and unsupported thinking levels. (#223) — thanks @augard
|
||||
- Onboarding: resolve CLI entrypoint when running via `npx` so gateway daemon install works without a build step.
|
||||
- Onboarding: when OpenAI Codex OAuth is used, default to `openai-codex/gpt-5.2` and warn if the selected model lacks auth.
|
||||
- CLI: auto-migrate legacy config entries on command start (same behavior as gateway startup).
|
||||
- Gateway: add `gateway stop|restart` helpers and surface launchd/systemd/schtasks stop hints when the gateway is already running.
|
||||
- Gateway: honor `agent.timeoutSeconds` for `chat.send` and share timeout defaults across chat/cron/auto-reply. Thanks @MSch for PR #229.
|
||||
- Auth: prioritize OAuth profiles but fall back to API keys when refresh fails; stored profiles now load without explicit auth order.
|
||||
- Control UI: harden config Form view with schema normalization, map editing, and guardrails to prevent data loss on save.
|
||||
- Cron: normalize cron.add/update inputs, align channel enums/status fields across gateway/CLI/UI/macOS, and add protocol conformance tests. Thanks @mneves75 for PR #256.
|
||||
- Docs: add group chat participation guidance to the AGENTS template.
|
||||
- Gmail: stop restart loop when `gog gmail watch serve` fails to bind (address already in use).
|
||||
- Linux: auto-attempt lingering during onboarding (try without sudo, fallback to sudo) and prompt on install/restart to keep the gateway alive after logout/idle. Thanks @tobiasbischoff for PR #237.
|
||||
- TUI: migrate key handling to the updated pi-tui Key matcher API.
|
||||
- TUI: add `/elev` alias for `/elevated`.
|
||||
- Logging: redact sensitive tokens in verbose tool summaries by default (configurable patterns).
|
||||
- macOS: keep app connection settings local in remote mode to avoid overwriting gateway config. Thanks @ngutman for PR #310.
|
||||
- macOS: prefer gateway config reads/writes in local mode (fall back to disk if the gateway is unavailable).
|
||||
- macOS: local gateway now connects via tailnet IP when bind mode is `tailnet`/`auto`.
|
||||
- macOS: Connections settings now use a custom sidebar to avoid toolbar toggle issues, with rounded styling and full-width row hit targets.
|
||||
@@ -68,63 +16,14 @@
|
||||
- Model: `/model` list shows auth source (masked key or OAuth email) per provider.
|
||||
- Model: `/model list` is an alias for `/model`.
|
||||
- Model: `/model` output now includes auth source location (env/auth.json/models.json).
|
||||
- Model: avoid duplicate `missing (missing)` auth labels in `/model` list output.
|
||||
- Auth: when `openai` has no API key but Codex OAuth exists, suggest `openai-codex/gpt-5.2` vs `OPENAI_API_KEY`.
|
||||
- Docs: clarify auth storage, migration, and OpenAI Codex OAuth onboarding.
|
||||
- Sandbox: copy inbound media into sandbox workspaces so agent tools can read attachments.
|
||||
- Sandbox: enable session tools in sandboxed sessions with spawned-only visibility by default (opt-in `agent.sandbox.sessionToolsVisibility = "all"`).
|
||||
- Control UI: show a reading indicator bubble while the assistant is responding.
|
||||
- Control UI: animate reading indicator dots (honors reduced-motion).
|
||||
- Control UI: stabilize chat streaming during tool runs (no flicker/vanishing text; correct run scoping).
|
||||
- Control UI: let config-form enums select empty-string values. Thanks @sreekaransrinath for PR #268.
|
||||
- Control UI: scroll chat to bottom on initial load. Thanks @kiranjd for PR #274.
|
||||
- Control UI: add Chat focus mode toggle to collapse header + sidebar.
|
||||
- Control UI: tighten focus mode spacing (reduce top padding, add comfortable compose inset).
|
||||
- Control UI: standardize UI build instructions on `bun run ui:*` (fallback supported).
|
||||
- Status: show runtime (docker/direct) and move shortcuts to `/help`.
|
||||
- Status: show model auth source (api-key/oauth).
|
||||
- Status: fix zero token counters for Anthropic (Opus) sessions by normalizing usage fields and ignoring empty usage updates.
|
||||
- Block streaming: avoid splitting Markdown fenced blocks and reopen fences when forced to split.
|
||||
- Block streaming: preserve leading indentation in block replies (lists, indented fences).
|
||||
- Docs: document systemd lingering and logged-in session requirements on macOS/Windows.
|
||||
- Auto-reply: centralize tool/block/final dispatch across providers for consistent streaming + heartbeat/prefix handling. Thanks @MSch for PR #225.
|
||||
- Heartbeat: make HEARTBEAT_OK ack padding configurable across heartbeat and cron delivery. (#238) — thanks @jalehman
|
||||
- Skills: emit MEDIA token after Nano Banana Pro image generation. Thanks @Iamadig for PR #271.
|
||||
- WhatsApp: set sender E.164 for direct chats so owner commands work in DMs.
|
||||
- Slack: keep auto-replies in the original thread when responding to thread messages. Thanks @scald for PR #251.
|
||||
- Slack: send typing status updates via assistant threads. Thanks @thewilloftheshadow for PR #320.
|
||||
- Slack: fix Slack provider startup under Bun by using a named import for Bolt `App`. Thanks @snopoke for PR #299.
|
||||
- Discord: surface missing-permission hints (muted/role overrides) when replies fail.
|
||||
- Discord: use channel IDs for DMs instead of user IDs. Thanks @VACInc for PR #261.
|
||||
- Docs: clarify Slack manifest scopes (current vs optional) with references. Thanks @jarvis-medmatic for PR #235.
|
||||
- Control UI: avoid Slack config ReferenceError by reading slack config snapshots. Thanks @sreekaransrinath for PR #249.
|
||||
- Auth: rotate across multiple OAuth profiles with cooldown tracking and email-based profile IDs. Thanks @mukhtharcm for PR #269.
|
||||
- Auth: fix multi-account OAuth rotation so round-robin alternates instead of pinning to lastGood. Thanks @mukhtharcm for PR #281.
|
||||
- Configure: stop auto-writing `auth.order` for newly added auth profiles (round-robin default unless explicitly pinned).
|
||||
- Telegram: honor routing.groupChat.mentionPatterns for group mention gating. Thanks Kevin Kern (@regenrek) for PR #242.
|
||||
- Telegram: gate groups via `telegram.groups` allowlist (align with WhatsApp/iMessage). Thanks @kitze for PR #241.
|
||||
- Telegram: support media groups (multi-image messages). Thanks @obviyus for PR #220.
|
||||
- Telegram/WhatsApp: parse shared locations (pins, places, live) and expose structured ctx fields. Thanks @nachoiacovino for PR #194.
|
||||
- Auto-reply: block unauthorized `/reset` and infer WhatsApp senders from E.164 inputs.
|
||||
- Auto-reply: reset corrupted Gemini sessions when function-call ordering breaks. Thanks @VACInc for PR #297.
|
||||
- Auto-reply: track compaction count in session status; verbose mode announces auto-compactions.
|
||||
- Telegram: notify users when inbound media exceeds size limits. Thanks @jarvis-medmatic for PR #283.
|
||||
- Telegram: send GIF media as animations (auto-play) and improve filename sniffing.
|
||||
- Bash tool: inherit gateway PATH so Nix-provided tools resolve during commands. Thanks @joshp123 for PR #202.
|
||||
- Delivery chunking: keep Markdown fenced code blocks valid when splitting long replies (close + reopen fences).
|
||||
- Auth: prefer OAuth profiles over API keys during round-robin selection (prevents OAuth “lost after one message” when both are configured).
|
||||
- Models: extend `clawdbot models` status output with a masked auth overview (profiles, env sources, and OAuth counts).
|
||||
|
||||
### Maintenance
|
||||
- Agent: add `skipBootstrap` config option. Thanks @onutc for PR #292.
|
||||
- UI: add favicon.ico derived from the macOS app icon. Thanks @jeffersonwarrior for PR #305.
|
||||
- Tooling: replace tsx with bun for TypeScript execution. Thanks @obviyus for PR #278.
|
||||
- Deps: bump pi-* stack, Slack SDK, discord-api-types, file-type, zod, and Biome.
|
||||
- Skills: add CodexBar model usage helper with macOS requirement metadata.
|
||||
- Skills: add 1Password CLI skill with op examples.
|
||||
- Lint: organize imports and wrap long lines in reply commands.
|
||||
- Refactor: centralize group allowlist/mention policy across providers.
|
||||
- Deps: update to latest across the repo.
|
||||
|
||||
## 2026.1.5-3
|
||||
|
||||
@@ -150,7 +49,6 @@
|
||||
- Agent tools: new `image` tool routed to the image model (when configured).
|
||||
- Config: default model shorthands (`opus`, `sonnet`, `gpt`, `gpt-mini`, `gemini`, `gemini-flash`).
|
||||
- Docs: document built-in model shorthands + precedence (user config wins).
|
||||
- Bun: optional local install/build workflow without maintaining a Bun lockfile (see `docs/bun.md`).
|
||||
|
||||
### Fixes
|
||||
- Control UI: render Markdown in tool result cards.
|
||||
@@ -169,16 +67,11 @@
|
||||
- Env: load global `$CLAWDBOT_STATE_DIR/.env` (`~/.clawdbot/.env`) as a fallback after CWD `.env`.
|
||||
- Env: optional login-shell env fallback (opt-in; imports expected keys without overriding existing env).
|
||||
- Agent tools: OpenAI-compatible tool JSON Schemas (fix `browser`, normalize union schemas).
|
||||
- Onboarding: when running from source, auto-build missing Control UI assets (`bun run ui:build`).
|
||||
- Onboarding: when running from source, auto-build missing Control UI assets (`pnpm ui:build`).
|
||||
- Discord/Slack: route reaction + system notifications to the correct session (no main-session bleed).
|
||||
- Agent tools: honor `agent.tools` allow/deny policy even when sandbox is off.
|
||||
- Discord: avoid duplicate replies when OpenAI emits repeated `message_end` events.
|
||||
- Commands: unify /status (inline) and command auth across providers; group bypass for authorized control commands; remove Discord /clawd slash handler.
|
||||
- CLI: run `clawdbot agent` via the Gateway by default; use `--local` to force embedded mode.
|
||||
|
||||
## 2026.1.5
|
||||
|
||||
### Fixes
|
||||
- Control UI: render Markdown in chat messages (sanitized).
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
FROM node:22-bookworm
|
||||
|
||||
# Install Bun (required for build scripts)
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
ENV PATH="/root/.bun/bin:${PATH}"
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -16,267 +16,191 @@
|
||||
</p>
|
||||
|
||||
**Clawdbot** is a *personal AI assistant* you run on your own devices.
|
||||
It answers you on the providers you already use (WhatsApp, Telegram, Slack, Discord, Signal, iMessage, WebChat), can speak and listen on macOS/iOS/Android, and can render a live Canvas you control. The Gateway is just the control plane — the product is the assistant.
|
||||
It answers you on the surfaces you already use (WhatsApp, Telegram, Discord, iMessage, WebChat), can speak and listen on macOS/iOS, and can render a live Canvas you control. The Gateway is just the control plane — the product is the assistant.
|
||||
|
||||
If you want a personal, single-user assistant that feels local, fast, and always-on, this is it.
|
||||
|
||||
[Website](https://clawdbot.com) · [Docs](https://docs.clawd.bot) · Getting Started: [https://docs.clawd.bot/getting-started](https://docs.clawd.bot/getting-started) · Updating: [https://docs.clawd.bot/updating](https://docs.clawd.bot/updating) · Showcase: [https://docs.clawd.bot/showcase](https://docs.clawd.bot/showcase) · FAQ: [https://docs.clawd.bot/faq](https://docs.clawd.bot/faq) · Wizard: [https://docs.clawd.bot/wizard](https://docs.clawd.bot/wizard) · Nix: [https://github.com/clawdbot/nix-clawdbot](https://github.com/clawdbot/nix-clawdbot) · Docker: [https://docs.clawd.bot/docker](https://docs.clawd.bot/docker) · Discord: [https://discord.gg/clawd](https://discord.gg/clawd)
|
||||
Website: https://clawd.me · Docs: https://docs.clawdbot.com/ · FAQ: [`docs/faq.md`](docs/faq.md) · Wizard: [`docs/wizard.md`](docs/wizard.md) · Nix: [nix-clawdbot](https://github.com/clawdbot/nix-clawdbot) · Docker: [`docs/docker.md`](docs/docker.md) · Discord: https://discord.gg/clawd
|
||||
|
||||
Preferred setup: run the onboarding wizard (`clawdbot onboard`). It walks through gateway, workspace, providers, and skills. The CLI wizard is the recommended path and works on **macOS, Windows, and Linux**.
|
||||
Works with npm, pnpm, or bun.
|
||||
New install? Start here: https://docs.clawd.bot/getting-started
|
||||
|
||||
**Subscriptions (OAuth):**
|
||||
- **Anthropic** (Claude Pro/Max)
|
||||
- **OpenAI** (ChatGPT/Codex)
|
||||
|
||||
Model note: while any model is supported, I strongly recommend **Anthropic Pro/Max (100/200) + Opus 4.5** for long‑context strength and better prompt‑injection resistance. See [Onboarding](https://docs.clawd.bot/onboarding).
|
||||
|
||||
## Models (selection + auth)
|
||||
|
||||
- Models config + CLI: https://docs.clawd.bot/models
|
||||
- Auth profile rotation (OAuth vs API keys) + fallbacks: https://docs.clawd.bot/model-failover
|
||||
|
||||
## Recommended setup (from source)
|
||||
|
||||
Do **not** download prebuilt binaries. Run from source.
|
||||
|
||||
Prefer **Bun**. `pnpm` is also supported (see https://docs.clawd.bot/getting-started).
|
||||
|
||||
```bash
|
||||
# Clone this repo
|
||||
git clone https://github.com/clawdbot/clawdbot.git
|
||||
cd clawdbot
|
||||
|
||||
bun install
|
||||
bun run ui:install
|
||||
bun run ui:build
|
||||
bun run build
|
||||
bun run clawdbot onboard
|
||||
```
|
||||
|
||||
Note: `bun run clawdbot ...` runs TypeScript directly. `bun run build` produces `dist/` for running via Node / the packaged `clawdbot` binary.
|
||||
|
||||
## Quick start (TL;DR)
|
||||
|
||||
Runtime: **Node ≥22**.
|
||||
|
||||
Full beginner guide (auth, pairing, providers): https://docs.clawd.bot/getting-started
|
||||
|
||||
```bash
|
||||
bun run clawdbot onboard
|
||||
|
||||
bun run clawdbot gateway --port 18789 --verbose
|
||||
|
||||
# Dev loop (auto-reload on TS changes)
|
||||
bun run gateway:watch
|
||||
|
||||
# Send a message
|
||||
bun run clawdbot send --to +1234567890 --message "Hello from Clawdbot"
|
||||
|
||||
# Talk to the assistant (optionally deliver back to WhatsApp/Telegram/Slack/Discord)
|
||||
bun run clawdbot agent --message "Ship checklist" --thinking high
|
||||
```
|
||||
|
||||
Upgrading? https://docs.clawd.bot/updating (and run `clawdbot doctor`).
|
||||
|
||||
If you run from source, prefer `bun run clawdbot …` or `pnpm clawdbot …` (not global `clawdbot`).
|
||||
|
||||
## Security defaults (DM access)
|
||||
|
||||
Clawdbot connects to real messaging surfaces. Treat inbound DMs as **untrusted input**.
|
||||
|
||||
Full security guide: https://docs.clawd.bot/security
|
||||
|
||||
Default behavior on Telegram/WhatsApp/Signal/iMessage/Discord/Slack:
|
||||
- **DM pairing** (`dmPolicy="pairing"` / `discord.dm.policy="pairing"` / `slack.dm.policy="pairing"`): unknown senders receive a short pairing code and the bot does not process their message.
|
||||
- Approve with: `clawdbot pairing approve --provider <provider> <code>` (then the sender is added to a local allowlist store).
|
||||
- Public inbound DMs require an explicit opt-in: set `dmPolicy="open"` and include `"*"` in the provider allowlist (`allowFrom` / `discord.dm.allowFrom` / `slack.dm.allowFrom`).
|
||||
|
||||
Run `clawdbot doctor` to surface risky/misconfigured DM policies.
|
||||
Subscriptions: **Anthropic (Claude Pro/Max)** and **OpenAI (ChatGPT/Codex)** are supported via OAuth. See `docs/onboarding.md`.
|
||||
|
||||
## Highlights
|
||||
|
||||
- **[Local-first Gateway](https://docs.clawd.bot/gateway)** — single control plane for sessions, providers, tools, and events.
|
||||
- **[Multi-provider inbox](https://docs.clawd.bot/surface)** — WhatsApp, Telegram, Slack, Discord, Signal, iMessage, WebChat, macOS, iOS/Android.
|
||||
- **[Multi-agent routing](https://docs.clawd.bot/configuration)** — route inbound providers/accounts/peers to isolated agents (workspaces + per-agent sessions).
|
||||
- **[Voice Wake](https://docs.clawd.bot/voicewake) + [Talk Mode](https://docs.clawd.bot/talk)** — always-on speech for macOS/iOS/Android with ElevenLabs.
|
||||
- **[Live Canvas](https://docs.clawd.bot/mac/canvas)** — agent-driven visual workspace with [A2UI](https://docs.clawd.bot/mac/canvas#canvas-a2ui).
|
||||
- **[First-class tools](https://docs.clawd.bot/tools)** — browser, canvas, nodes, cron, sessions, and Discord/Slack actions.
|
||||
- **[Companion apps](https://docs.clawd.bot/macos)** — macOS menu bar app + iOS/Android [nodes](https://docs.clawd.bot/nodes).
|
||||
- **[Onboarding](https://docs.clawd.bot/wizard) + [skills](https://docs.clawd.bot/skills)** — wizard-driven setup with bundled/managed/workspace skills.
|
||||
- **Local-first Gateway** — single control plane for sessions, providers, tools, and events.
|
||||
- **Multi-surface inbox** — WhatsApp, Telegram, Discord, iMessage, WebChat, macOS, iOS/Android.
|
||||
- **Voice Wake + Talk Mode** — always-on speech for macOS/iOS/Android with ElevenLabs.
|
||||
- **Live Canvas** — agent-driven visual workspace with A2UI.
|
||||
- **First-class tools** — browser, canvas, nodes, cron, sessions, and Discord actions.
|
||||
- **Companion apps** — macOS menu bar app + iOS/Android nodes.
|
||||
- **Onboarding + skills** — wizard-driven setup with bundled/managed/workspace skills.
|
||||
|
||||
## Everything we built so far
|
||||
|
||||
### Core platform
|
||||
- [Gateway WS control plane](https://docs.clawd.bot/gateway) with sessions, presence, config, cron, webhooks, [Control UI](https://docs.clawd.bot/web), and [Canvas host](https://docs.clawd.bot/mac/canvas#canvas-a2ui).
|
||||
- [CLI surface](https://docs.clawd.bot/agent-send): gateway, agent, send, [wizard](https://docs.clawd.bot/wizard), and [doctor](https://docs.clawd.bot/doctor).
|
||||
- [Pi agent runtime](https://docs.clawd.bot/agent) in RPC mode with tool streaming and block streaming.
|
||||
- [Session model](https://docs.clawd.bot/session): `main` for direct chats, group isolation, activation modes, queue modes, reply-back. Group rules: [Groups](https://docs.clawd.bot/groups).
|
||||
- [Media pipeline](https://docs.clawd.bot/images): images/audio/video, transcription hooks, size caps, temp file lifecycle. Audio details: [Audio](https://docs.clawd.bot/audio).
|
||||
- Gateway WS control plane with sessions, presence, config, cron, webhooks, control UI, and Canvas host.
|
||||
- CLI surface: gateway, agent, send, wizard, doctor/update, and TUI.
|
||||
- Pi agent runtime in RPC mode with tool streaming and block streaming.
|
||||
- Session model: `main` for direct chats, group isolation, activation modes, queue modes, reply-back.
|
||||
- Media pipeline: images/audio/video, transcription hooks, size caps, temp file lifecycle.
|
||||
|
||||
### Providers
|
||||
- [Providers](https://docs.clawd.bot/surface): [WhatsApp](https://docs.clawd.bot/whatsapp) (Baileys), [Telegram](https://docs.clawd.bot/telegram) (grammY), [Slack](https://docs.clawd.bot/slack) (Bolt), [Discord](https://docs.clawd.bot/discord) (discord.js), [Signal](https://docs.clawd.bot/signal) (signal-cli), [iMessage](https://docs.clawd.bot/imessage) (imsg), [WebChat](https://docs.clawd.bot/webchat).
|
||||
- [Group routing](https://docs.clawd.bot/group-messages): mention gating, reply tags, per-provider chunking and routing. Provider rules: [Providers](https://docs.clawd.bot/surface).
|
||||
### Surfaces + providers
|
||||
- WhatsApp (Baileys), Telegram (grammY), Discord (discord.js), Signal (signal-cli), iMessage (imsg), WebChat.
|
||||
- Group mention gating, reply tags, per-surface chunking and routing.
|
||||
|
||||
### Apps + nodes
|
||||
- [macOS app](https://docs.clawd.bot/macos): menu bar control plane, [Voice Wake](https://docs.clawd.bot/voicewake)/PTT, [Talk Mode](https://docs.clawd.bot/talk) overlay, [WebChat](https://docs.clawd.bot/webchat), debug tools, [remote gateway](https://docs.clawd.bot/remote) control.
|
||||
- [iOS node](https://docs.clawd.bot/ios): [Canvas](https://docs.clawd.bot/mac/canvas), [Voice Wake](https://docs.clawd.bot/voicewake), [Talk Mode](https://docs.clawd.bot/talk), camera, screen recording, Bonjour pairing.
|
||||
- [Android node](https://docs.clawd.bot/android): [Canvas](https://docs.clawd.bot/mac/canvas), [Talk Mode](https://docs.clawd.bot/talk), camera, screen recording, optional SMS.
|
||||
- [macOS node mode](https://docs.clawd.bot/nodes): system.run/notify + canvas/camera exposure.
|
||||
- macOS app: menu bar control plane, Voice Wake/PTT, Talk Mode overlay, WebChat, Debug tools, SSH remote gateway control.
|
||||
- iOS node: Canvas, Voice Wake, Talk Mode, camera, screen recording, Bonjour pairing.
|
||||
- Android node: Canvas, Talk Mode, camera, screen recording, optional SMS.
|
||||
- macOS node mode: system.run/notify + canvas/camera exposure.
|
||||
|
||||
### Tools + automation
|
||||
- [Browser control](https://docs.clawd.bot/browser): dedicated clawd Chrome/Chromium, snapshots, actions, uploads, profiles.
|
||||
- [Canvas](https://docs.clawd.bot/mac/canvas): [A2UI](https://docs.clawd.bot/mac/canvas#canvas-a2ui) push/reset, eval, snapshot.
|
||||
- [Nodes](https://docs.clawd.bot/nodes): camera snap/clip, screen record, [location.get](https://docs.clawd.bot/location-command), notifications.
|
||||
- [Cron + wakeups](https://docs.clawd.bot/cron); [webhooks](https://docs.clawd.bot/webhook); [Gmail Pub/Sub](https://docs.clawd.bot/gmail-pubsub).
|
||||
- [Skills platform](https://docs.clawd.bot/skills): bundled, managed, and workspace skills with install gating + UI.
|
||||
- Browser control: dedicated clawd Chrome/Chromium, snapshots, actions, uploads, profiles.
|
||||
- Canvas: A2UI push/reset, eval, snapshot.
|
||||
- Nodes: camera snap/clip, screen record, location.get, notifications.
|
||||
- Cron + wakeups; webhooks; Gmail Pub/Sub triggers.
|
||||
- Skills platform: bundled, managed, and workspace skills with install gating + UI.
|
||||
|
||||
### Ops + packaging
|
||||
- [Control UI](https://docs.clawd.bot/web) + [WebChat](https://docs.clawd.bot/webchat) served directly from the Gateway.
|
||||
- [Tailscale Serve/Funnel](https://docs.clawd.bot/tailscale) or [SSH tunnels](https://docs.clawd.bot/remote) with token/password auth.
|
||||
- [Nix mode](https://docs.clawd.bot/nix) for declarative config; [Docker](https://docs.clawd.bot/docker)-based installs.
|
||||
- [Doctor](https://docs.clawd.bot/doctor) migrations, [logging](https://docs.clawd.bot/logging).
|
||||
- Control UI + WebChat served directly from the Gateway.
|
||||
- Tailscale Serve/Funnel or SSH tunnels with token/password auth.
|
||||
- Nix mode for declarative config; Docker-based installs.
|
||||
- Health, doctor migrations, structured logging, release tooling.
|
||||
|
||||
#### Gateway service (optional)
|
||||
## Changes since 2026.1.4 (2026-01-04)
|
||||
|
||||
Install a supervised service (launchd/systemd/Scheduled Task):
|
||||
### Highlights
|
||||
- Project rename completed: CLIs, paths, bundle IDs, env vars, and docs unified on Clawdbot.
|
||||
- Agent-to-agent relay: `sessions_send` ping‑pong with `REPLY_SKIP` plus announce step with `ANNOUNCE_SKIP`.
|
||||
- Gateway config hot reload, configurable port, and Control UI base-path support.
|
||||
- Sandbox options: per-session Docker sandbox with hardened limits + optional sandboxed Chromium.
|
||||
- New node capability: `location.get` across macOS/iOS/Android (CLI + tools).
|
||||
|
||||
```bash
|
||||
clawdbot gateway install
|
||||
clawdbot gateway service-status
|
||||
```
|
||||
### Fixes
|
||||
- Presence beacons keep node lists fresh; Instances view stays accurate.
|
||||
- Block streaming + chunking reliability (Telegram/Discord ordering, fewer duplicates).
|
||||
- WhatsApp GIF playback for MP4-based GIFs.
|
||||
- Onboarding/Control UI basePath handling fixes + UI polish.
|
||||
- Cleaner logging + clearer tool summaries.
|
||||
|
||||
Aliases: `clawdbot service ...` or `clawdbot daemon ...`.
|
||||
### Breaking
|
||||
- Tool names drop the `clawdbot_` prefix (`browser`, `canvas`, `nodes`, `cron`, `gateway`).
|
||||
- Bash tool removed `stdinMode: "pty"` support (use tmux for real TTYs).
|
||||
- Primary session key is fixed to `main` (or `global` for global scope).
|
||||
|
||||
## Project rename + changelog format
|
||||
|
||||
Clawdis → Clawdbot. The rename touched every surface, path, and bundle ID. To make that transition explicit, releases now use **date-based versions** (`YYYY.M.D`), and the changelog is compressed into milestone summaries instead of long semver trains. Full detail still lives in git history and the docs.
|
||||
|
||||
## How it works (short)
|
||||
|
||||
```
|
||||
WhatsApp / Telegram / Slack / Discord / Signal / iMessage / WebChat
|
||||
│
|
||||
▼
|
||||
Your surfaces
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────┐
|
||||
│ Gateway │ ws://127.0.0.1:18789
|
||||
│ (control plane) │ bridge: tcp://0.0.0.0:18790
|
||||
│ (control plane) │ tcp://0.0.0.0:18790 (optional Bridge)
|
||||
└──────────────┬────────────────┘
|
||||
│
|
||||
├─ Pi agent (RPC)
|
||||
├─ CLI (clawdbot …)
|
||||
├─ WebChat UI
|
||||
├─ macOS app
|
||||
└─ iOS/Android nodes
|
||||
├─ WebChat (browser)
|
||||
├─ macOS app (Clawdbot.app)
|
||||
└─ iOS node (Canvas + voice)
|
||||
```
|
||||
|
||||
## Key subsystems
|
||||
|
||||
- **[Gateway WebSocket network](https://docs.clawd.bot/architecture)** — single WS control plane for clients, tools, and events (plus ops: [Gateway runbook](https://docs.clawd.bot/gateway)).
|
||||
- **[Tailscale exposure](https://docs.clawd.bot/tailscale)** — Serve/Funnel for the Gateway dashboard + WS (remote access: [Remote](https://docs.clawd.bot/remote)).
|
||||
- **[Browser control](https://docs.clawd.bot/browser)** — clawd‑managed Chrome/Chromium with CDP control.
|
||||
- **[Canvas + A2UI](https://docs.clawd.bot/mac/canvas)** — agent‑driven visual workspace (A2UI host: [Canvas/A2UI](https://docs.clawd.bot/mac/canvas#canvas-a2ui)).
|
||||
- **[Voice Wake](https://docs.clawd.bot/voicewake) + [Talk Mode](https://docs.clawd.bot/talk)** — always‑on speech and continuous conversation.
|
||||
- **[Nodes](https://docs.clawd.bot/nodes)** — Canvas, camera snap/clip, screen record, `location.get`, notifications, plus macOS‑only `system.run`/`system.notify`.
|
||||
|
||||
## Tailscale access (Gateway dashboard)
|
||||
|
||||
Clawdbot can auto-configure Tailscale **Serve** (tailnet-only) or **Funnel** (public) while the Gateway stays bound to loopback. Configure `gateway.tailscale.mode`:
|
||||
|
||||
- `off`: no Tailscale automation (default).
|
||||
- `serve`: tailnet-only HTTPS via `tailscale serve` (uses Tailscale identity headers by default).
|
||||
- `funnel`: public HTTPS via `tailscale funnel` (requires shared password auth).
|
||||
|
||||
Notes:
|
||||
- `gateway.bind` must stay `loopback` when Serve/Funnel is enabled (Clawdbot enforces this).
|
||||
- Serve can be forced to require a password by setting `gateway.auth.mode: "password"` or `gateway.auth.allowTailscale: false`.
|
||||
- Funnel refuses to start unless `gateway.auth.mode: "password"` is set.
|
||||
- Optional: `gateway.tailscale.resetOnExit` to undo Serve/Funnel on shutdown.
|
||||
|
||||
Details: [Tailscale guide](https://docs.clawd.bot/tailscale) · [Web surfaces](https://docs.clawd.bot/web)
|
||||
|
||||
## Remote Gateway (Linux is great)
|
||||
|
||||
It’s perfectly fine to run the Gateway on a small Linux instance. Clients (macOS app, CLI, WebChat) can connect over **Tailscale Serve/Funnel** or **SSH tunnels**, and you can still pair device nodes (macOS/iOS/Android) to execute device‑local actions when needed.
|
||||
|
||||
- **Gateway host** runs the bash tool and provider connections by default.
|
||||
- **Device nodes** run device‑local actions (`system.run`, camera, screen recording, notifications) via `node.invoke`.
|
||||
In short: bash runs where the Gateway lives; device actions run where the device lives.
|
||||
|
||||
Details: [Remote access](https://docs.clawd.bot/remote) · [Nodes](https://docs.clawd.bot/nodes) · [Security](https://docs.clawd.bot/security)
|
||||
|
||||
## macOS permissions via the Gateway protocol
|
||||
|
||||
The macOS app can run in **node mode** and advertises its capabilities + permission map over the Gateway WebSocket (`node.list` / `node.describe`). Clients can then execute local actions via `node.invoke`:
|
||||
|
||||
- `system.run` runs a local command and returns stdout/stderr/exit code; set `needsScreenRecording: true` to require screen-recording permission (otherwise you’ll get `PERMISSION_MISSING`).
|
||||
- `system.notify` posts a user notification and fails if notifications are denied.
|
||||
- `canvas.*`, `camera.*`, `screen.record`, and `location.get` are also routed via `node.invoke` and follow TCC permission status.
|
||||
|
||||
Elevated bash (host permissions) is separate from macOS TCC:
|
||||
|
||||
- Use `/elevated on|off` to toggle per‑session elevated access when enabled + allowlisted.
|
||||
- Gateway persists the per‑session toggle via `sessions.patch` (WS method) alongside `thinkingLevel`, `verboseLevel`, `model`, `sendPolicy`, and `groupActivation`.
|
||||
|
||||
Details: [Nodes](https://docs.clawd.bot/nodes) · [macOS app](https://docs.clawd.bot/macos) · [Gateway protocol](https://docs.clawd.bot/architecture)
|
||||
|
||||
## Agent to Agent (sessions_* tools)
|
||||
|
||||
- Use these to coordinate work across sessions without jumping between chat surfaces.
|
||||
- `sessions_list` — discover active sessions (agents) and their metadata.
|
||||
- `sessions_history` — fetch transcript logs for a session.
|
||||
- `sessions_send` — message another session; optional reply‑back ping‑pong + announce step (`REPLY_SKIP`, `ANNOUNCE_SKIP`).
|
||||
|
||||
Details: [Session tools](https://docs.clawd.bot/session-tool)
|
||||
|
||||
## Skills registry (ClawdHub)
|
||||
|
||||
ClawdHub is a minimal skill registry. With ClawdHub enabled, the agent can search for skills automatically and pull in new ones as needed.
|
||||
|
||||
https://ClawdHub.com
|
||||
https://clawdhub.com
|
||||
|
||||
## Quick start (from source)
|
||||
|
||||
Runtime: **Node ≥22** + **pnpm**.
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm build
|
||||
pnpm ui:build
|
||||
|
||||
# Recommended: run the onboarding wizard
|
||||
pnpm clawdbot onboard
|
||||
|
||||
# Link WhatsApp (stores creds in ~/.clawdbot/credentials)
|
||||
pnpm clawdbot login
|
||||
|
||||
# Start the gateway
|
||||
pnpm clawdbot gateway --port 18789 --verbose
|
||||
|
||||
# Dev loop (auto-reload on TS changes)
|
||||
pnpm gateway:watch
|
||||
|
||||
# Send a message
|
||||
pnpm clawdbot send --to +1234567890 --message "Hello from Clawdbot"
|
||||
|
||||
# Talk to the assistant (optionally deliver back to WhatsApp/Telegram/Discord)
|
||||
pnpm clawdbot agent --message "Ship checklist" --thinking high
|
||||
```
|
||||
|
||||
If you run from source, prefer `pnpm clawdbot …` (not global `clawdbot`).
|
||||
|
||||
## Chat commands
|
||||
|
||||
Send these in WhatsApp/Telegram/Slack/WebChat (group commands are owner-only):
|
||||
Send these in WhatsApp/Telegram/WebChat (group commands are owner-only):
|
||||
|
||||
- `/status` — health + session info (group shows activation mode)
|
||||
- `/new` or `/reset` — reset the session
|
||||
- `/compact` — compact session context (summary)
|
||||
- `/think <level>` — off|minimal|low|medium|high
|
||||
- `/verbose on|off`
|
||||
- `/restart` — restart the gateway (owner-only in groups)
|
||||
- `/activation mention|always` — group activation toggle (groups only)
|
||||
|
||||
## macOS app (optional)
|
||||
## Architecture
|
||||
|
||||
The Gateway alone delivers a great experience. All apps are optional and add extra features.
|
||||
### TypeScript Gateway (src/gateway/server.ts)
|
||||
- **Single HTTP+WS server** on `ws://127.0.0.1:18789` (bind policy: loopback/lan/tailnet/auto). The first frame must be `connect`; AJV validates frames against TypeBox schemas (`src/gateway/protocol`).
|
||||
- **Single source of truth** for sessions, providers, cron, voice wake, and presence. Methods cover `send`, `agent`, `chat.*`, `sessions.*`, `config.*`, `cron.*`, `voicewake.*`, `node.*`, `system-*`, `wake`.
|
||||
- **Events + snapshot**: handshake returns a snapshot (presence/health) and declares event types; runtime events include `agent`, `chat`, `presence`, `tick`, `health`, `heartbeat`, `cron`, `node.pair.*`, `voicewake.changed`, `shutdown`.
|
||||
- **Idempotency & safety**: `send`/`agent`/`chat.send` require idempotency keys with a TTL cache (5 min, cap 1000) to avoid double‑sends on reconnects; payload sizes are capped per connection.
|
||||
- **Bridge for nodes**: optional TCP bridge (`src/infra/bridge/server.ts`) is newline‑delimited JSON frames (`hello`, pairing, RPC, `invoke`); node connect/disconnect is surfaced into presence.
|
||||
- **Control UI + Canvas Host**: HTTP serves Control UI assets (default `/`, optional base path) and can host a live‑reload Canvas host for nodes (`src/canvas-host/server.ts`), injecting the A2UI postMessage bridge.
|
||||
|
||||
If you plan to build/run companion apps, initialize submodules first:
|
||||
### iOS app (apps/ios)
|
||||
- **Discovery + pairing**: Bonjour discovery via `BridgeDiscoveryModel` (NWBrowser). `BridgeConnectionController` auto‑connects using Keychain token or allows manual host/port.
|
||||
- **Node runtime**: `BridgeSession` (actor) maintains the `NWConnection`, hello handshake, ping/pong, RPC requests, and `invoke` callbacks.
|
||||
- **Capabilities + commands**: advertises `canvas`, `screen`, `camera`, `voiceWake` (settings‑driven) and executes `canvas.*`, `canvas.a2ui.*`, `camera.*`, `screen.record` (`NodeAppModel.handleInvoke`).
|
||||
- **Canvas**: `WKWebView` with bundled Canvas scaffold + A2UI, JS eval, snapshot capture, and `clawdbot://` deep‑link interception (`ScreenController`).
|
||||
- **Voice + deep links**: voice wake sends `voice.transcript` events; `clawdbot://agent` links emit `agent.request`. Voice wake triggers sync via `voicewake.get` + `voicewake.changed`.
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
./scripts/restart-mac.sh
|
||||
```
|
||||
## Companion apps
|
||||
|
||||
### macOS (Clawdbot.app) (optional)
|
||||
The **macOS app is critical**: it runs the menu‑bar control plane, owns local permissions (TCC), hosts Voice Wake, exposes WebChat/debug tools, and coordinates local/remote gateway mode. Most “assistant” UX lives here.
|
||||
|
||||
### macOS (Clawdbot.app)
|
||||
|
||||
- Menu bar control for the Gateway and health.
|
||||
- Voice Wake + push-to-talk overlay.
|
||||
- WebChat + debug tools.
|
||||
- Remote gateway control over SSH.
|
||||
|
||||
Note: signed builds required for macOS permissions to stick across rebuilds (see `docs/mac/permissions.md`).
|
||||
Build/run: `./scripts/restart-mac.sh` (packages + launches).
|
||||
|
||||
### iOS node (optional)
|
||||
### iOS node (internal)
|
||||
|
||||
- Pairs as a node via the Bridge.
|
||||
- Voice trigger forwarding + Canvas surface.
|
||||
- Controlled via `clawdbot nodes …`.
|
||||
|
||||
Runbook: [iOS connect](https://docs.clawd.bot/ios).
|
||||
Runbook: `docs/ios/connect.md`.
|
||||
|
||||
### Android node (optional)
|
||||
### Android node (internal)
|
||||
|
||||
- Pairs via the same Bridge + pairing flow as iOS.
|
||||
- Exposes Canvas, Camera, and Screen capture commands.
|
||||
- Runbook: [Android connect](https://docs.clawd.bot/android).
|
||||
- Runbook: `docs/android/connect.md`.
|
||||
|
||||
## Agent workspace + skills
|
||||
|
||||
@@ -286,36 +210,44 @@ Runbook: [iOS connect](https://docs.clawd.bot/ios).
|
||||
|
||||
## Configuration
|
||||
|
||||
Minimal `~/.clawdbot/clawdbot.json` (model + defaults):
|
||||
Minimal `~/.clawdbot/clawdbot.json`:
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
model: "anthropic/claude-opus-4-5"
|
||||
whatsapp: {
|
||||
allowFrom: ["+1234567890"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[Full configuration reference (all keys + examples).](https://docs.clawd.bot/configuration)
|
||||
Env vars: loaded from `.env` in the current working directory, plus a global fallback at `~/.clawdbot/.env` (aka `$CLAWDBOT_STATE_DIR/.env`) without overriding existing values.
|
||||
|
||||
## Security model (important)
|
||||
Optional: import missing keys from your login shell env (sources your shell profile) via config or env var:
|
||||
|
||||
- **Default:** tools run on the host for the **main** session, so the agent has full access when it’s just you.
|
||||
- **Group/channel safety:** set `agent.sandbox.mode: "non-main"` to run **non‑main sessions** (groups/channels) inside per‑session Docker sandboxes; bash then runs in Docker for those sessions.
|
||||
- **Sandbox defaults:** allowlist `bash`, `process`, `read`, `write`, `edit`, `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`; denylist `browser`, `canvas`, `nodes`, `cron`, `discord`, `gateway`.
|
||||
```json5
|
||||
{
|
||||
env: {
|
||||
shellEnv: {
|
||||
enabled: true,
|
||||
timeoutMs: 15000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Details: [Security guide](https://docs.clawd.bot/security) · [Docker + sandboxing](https://docs.clawd.bot/docker) · [Sandbox config](https://docs.clawd.bot/configuration)
|
||||
- Env var: `CLAWDBOT_LOAD_SHELL_ENV=1`
|
||||
- Timeout override: `CLAWDBOT_SHELL_ENV_TIMEOUT_MS=15000`
|
||||
- Behavior: only imports known/expected keys, never overrides existing `process.env`.
|
||||
|
||||
### [WhatsApp](https://docs.clawd.bot/whatsapp)
|
||||
### WhatsApp
|
||||
|
||||
- Link the device: `pnpm clawdbot login` (stores creds in `~/.clawdbot/credentials`).
|
||||
- Allowlist who can talk to the assistant via `whatsapp.allowFrom`.
|
||||
- If `whatsapp.groups` is set, it becomes a group allowlist; include `"*"` to allow all.
|
||||
|
||||
### [Telegram](https://docs.clawd.bot/telegram)
|
||||
### Telegram
|
||||
|
||||
- Set `TELEGRAM_BOT_TOKEN` or `telegram.botToken` (env wins).
|
||||
- Optional: set `telegram.groups` (with `telegram.groups."*".requireMention`); when set, it is a group allowlist (include `"*"` to allow all). Also `telegram.allowFrom` or `telegram.webhookUrl` as needed.
|
||||
- Optional: set `telegram.groups` (with `telegram.groups."*".requireMention`), `telegram.allowFrom`, or `telegram.webhookUrl` as needed.
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -325,14 +257,10 @@ Details: [Security guide](https://docs.clawd.bot/security) · [Docker + sandboxi
|
||||
}
|
||||
```
|
||||
|
||||
### [Slack](https://docs.clawd.bot/slack)
|
||||
|
||||
- Set `SLACK_BOT_TOKEN` + `SLACK_APP_TOKEN` (or `slack.botToken` + `slack.appToken`).
|
||||
|
||||
### [Discord](https://docs.clawd.bot/discord)
|
||||
### Discord
|
||||
|
||||
- Set `DISCORD_BOT_TOKEN` or `discord.token` (env wins).
|
||||
- Optional: set `commands.native`, `commands.text`, or `commands.useAccessGroups`, plus `discord.dm.allowFrom`, `discord.guilds`, or `discord.mediaMaxMb` as needed.
|
||||
- Optional: set `discord.slashCommand`, `discord.dm.allowFrom`, `discord.guilds`, or `discord.mediaMaxMb` as needed.
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -342,19 +270,6 @@ Details: [Security guide](https://docs.clawd.bot/security) · [Docker + sandboxi
|
||||
}
|
||||
```
|
||||
|
||||
### [Signal](https://docs.clawd.bot/signal)
|
||||
|
||||
- Requires `signal-cli` and a `signal` config section.
|
||||
|
||||
### [iMessage](https://docs.clawd.bot/imessage)
|
||||
|
||||
- macOS only; Messages must be signed in.
|
||||
- If `imessage.groups` is set, it becomes a group allowlist; include `"*"` to allow all.
|
||||
|
||||
### [WebChat](https://docs.clawd.bot/webchat)
|
||||
|
||||
- Uses the Gateway WebSocket; no separate WebChat port/config.
|
||||
|
||||
Browser control (optional):
|
||||
|
||||
```json5
|
||||
@@ -369,100 +284,39 @@ Browser control (optional):
|
||||
|
||||
## Docs
|
||||
|
||||
Use these when you’re past the onboarding flow and want the deeper reference.
|
||||
- [Start with the docs index for navigation and “what’s where.”](https://docs.clawd.bot)
|
||||
- [Read the architecture overview for the gateway + protocol model.](https://docs.clawd.bot/architecture)
|
||||
- [Use the full configuration reference when you need every key and example.](https://docs.clawd.bot/configuration)
|
||||
- [Run the Gateway by the book with the operational runbook.](https://docs.clawd.bot/gateway)
|
||||
- [Learn how the Control UI/Web surfaces work and how to expose them safely.](https://docs.clawd.bot/web)
|
||||
- [Understand remote access over SSH tunnels or tailnets.](https://docs.clawd.bot/remote)
|
||||
- [Follow the onboarding wizard flow for a guided setup.](https://docs.clawd.bot/wizard)
|
||||
- [Wire external triggers via the webhook surface.](https://docs.clawd.bot/webhook)
|
||||
- [Set up Gmail Pub/Sub triggers.](https://docs.clawd.bot/gmail-pubsub)
|
||||
- [Learn the macOS menu bar companion details.](https://docs.clawd.bot/mac/menu-bar)
|
||||
- [Platform guides: Windows](https://docs.clawd.bot/windows), [Linux](https://docs.clawd.bot/linux), [macOS](https://docs.clawd.bot/macos), [iOS](https://docs.clawd.bot/ios), [Android](https://docs.clawd.bot/android)
|
||||
- [Debug common failures with the troubleshooting guide.](https://docs.clawd.bot/troubleshooting)
|
||||
- [Review security guidance before exposing anything.](https://docs.clawd.bot/security)
|
||||
|
||||
## Advanced docs (discovery + control)
|
||||
|
||||
- [Discovery + transports](https://docs.clawd.bot/discovery)
|
||||
- [Bonjour/mDNS](https://docs.clawd.bot/bonjour)
|
||||
- [Gateway pairing](https://docs.clawd.bot/gateway/pairing)
|
||||
- [Remote gateway README](https://docs.clawd.bot/remote-gateway-readme)
|
||||
- [Control UI](https://docs.clawd.bot/control-ui)
|
||||
- [Dashboard](https://docs.clawd.bot/dashboard)
|
||||
|
||||
## Operations & troubleshooting
|
||||
|
||||
- [Health checks](https://docs.clawd.bot/health)
|
||||
- [Gateway lock](https://docs.clawd.bot/gateway-lock)
|
||||
- [Background process](https://docs.clawd.bot/background-process)
|
||||
- [Browser troubleshooting (Linux)](https://docs.clawd.bot/browser-linux-troubleshooting)
|
||||
- [Logging](https://docs.clawd.bot/logging)
|
||||
|
||||
## Deep dives
|
||||
|
||||
- [Agent loop](https://docs.clawd.bot/agent-loop)
|
||||
- [Presence](https://docs.clawd.bot/presence)
|
||||
- [TypeBox schemas](https://docs.clawd.bot/typebox)
|
||||
- [RPC adapters](https://docs.clawd.bot/rpc)
|
||||
- [Queue](https://docs.clawd.bot/queue)
|
||||
|
||||
## Workspace & skills
|
||||
|
||||
- [Skills config](https://docs.clawd.bot/skills-config)
|
||||
- [Default AGENTS](https://docs.clawd.bot/AGENTS.default)
|
||||
- [Templates: AGENTS](https://docs.clawd.bot/templates/AGENTS)
|
||||
- [Templates: BOOTSTRAP](https://docs.clawd.bot/templates/BOOTSTRAP)
|
||||
- [Templates: IDENTITY](https://docs.clawd.bot/templates/IDENTITY)
|
||||
- [Templates: SOUL](https://docs.clawd.bot/templates/SOUL)
|
||||
- [Templates: TOOLS](https://docs.clawd.bot/templates/TOOLS)
|
||||
- [Templates: USER](https://docs.clawd.bot/templates/USER)
|
||||
|
||||
## Platform internals
|
||||
|
||||
- [macOS dev setup](https://docs.clawd.bot/mac/dev-setup)
|
||||
- [macOS menu bar](https://docs.clawd.bot/mac/menu-bar)
|
||||
- [macOS voice wake](https://docs.clawd.bot/mac/voicewake)
|
||||
- [iOS node](https://docs.clawd.bot/ios)
|
||||
- [Android node](https://docs.clawd.bot/android)
|
||||
- [Windows app](https://docs.clawd.bot/windows)
|
||||
- [Linux app](https://docs.clawd.bot/linux)
|
||||
- [`docs/index.md`](docs/index.md) (overview)
|
||||
- [`docs/configuration.md`](docs/configuration.md)
|
||||
- [`docs/group-messages.md`](docs/group-messages.md)
|
||||
- [`docs/gateway.md`](docs/gateway.md)
|
||||
- [`docs/web.md`](docs/web.md)
|
||||
- [`docs/discovery.md`](docs/discovery.md)
|
||||
- [`docs/agent.md`](docs/agent.md)
|
||||
- [`docs/discord.md`](docs/discord.md)
|
||||
- [`docs/wizard.md`](docs/wizard.md)
|
||||
- Webhooks + external triggers: [`docs/webhook.md`](docs/webhook.md)
|
||||
- Gmail hooks (email → wake): [`docs/gmail-pubsub.md`](docs/gmail-pubsub.md)
|
||||
|
||||
## Email hooks (Gmail)
|
||||
|
||||
[Gmail Pub/Sub wiring (gcloud + gogcli), hook tokens, and auto-watch behavior are documented here.](https://docs.clawd.bot/gmail-pubsub)
|
||||
|
||||
Gateway auto-starts the watcher when `hooks.enabled=true` and `hooks.gmail.account` is set; `clawdbot hooks gmail run` is the manual daemon wrapper if you don’t want auto-start.
|
||||
|
||||
```bash
|
||||
clawdbot hooks gmail setup --account you@gmail.com
|
||||
clawdbot hooks gmail run
|
||||
```
|
||||
- [`docs/security.md`](docs/security.md)
|
||||
- [`docs/troubleshooting.md`](docs/troubleshooting.md)
|
||||
- [`docs/ios/connect.md`](docs/ios/connect.md)
|
||||
- [`docs/clawdbot-mac.md`](docs/clawdbot-mac.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines, maintainers, and how to submit PRs.
|
||||
|
||||
AI/vibe-coded PRs welcome! 🤖
|
||||
|
||||
## Clawd
|
||||
|
||||
Clawdbot was built for **Clawd**, a space lobster AI assistant. 🦞
|
||||
by Peter Steinberger and the community.
|
||||
Clawdbot was built for **Clawd**, a space lobster AI assistant.
|
||||
|
||||
- https://clawd.me
|
||||
- https://soul.md
|
||||
- https://steipete.me
|
||||
|
||||
## Community
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines, maintainers, and how to submit PRs.
|
||||
AI/vibe-coded PRs welcome! 🤖
|
||||
|
||||
Thanks to all clawtributors:
|
||||
|
||||
<p align="left">
|
||||
<a href="https://github.com/steipete"><img src="https://avatars.githubusercontent.com/u/58493?v=4&s=48" width="48" height="48" alt="steipete" title="steipete"/></a> <a href="https://github.com/thewilloftheshadow"><img src="https://avatars.githubusercontent.com/u/35580099?v=4&s=48" width="48" height="48" alt="thewilloftheshadow" title="thewilloftheshadow"/></a> <a href="https://github.com/mcinteerj"><img src="https://avatars.githubusercontent.com/u/3613653?v=4&s=48" width="48" height="48" alt="mcinteerj" title="mcinteerj"/></a> <a href="https://github.com/joshp123"><img src="https://avatars.githubusercontent.com/u/1497361?v=4&s=48" width="48" height="48" alt="joshp123" title="joshp123"/></a> <a href="https://github.com/joaohlisboa"><img src="https://avatars.githubusercontent.com/u/8200873?v=4&s=48" width="48" height="48" alt="joaohlisboa" title="joaohlisboa"/></a> <a href="https://github.com/petter-b"><img src="https://avatars.githubusercontent.com/u/62076402?v=4&s=48" width="48" height="48" alt="petter-b" title="petter-b"/></a> <a href="https://github.com/mukhtharcm"><img src="https://avatars.githubusercontent.com/u/56378562?v=4&s=48" width="48" height="48" alt="mukhtharcm" title="mukhtharcm"/></a> <a href="https://github.com/dan-dr"><img src="https://avatars.githubusercontent.com/u/6669808?v=4&s=48" width="48" height="48" alt="dan-dr" title="dan-dr"/></a> <a href="https://github.com/Nachx639"><img src="https://avatars.githubusercontent.com/u/71144023?v=4&s=48" width="48" height="48" alt="Nachx639" title="Nachx639"/></a> <a href="https://github.com/jeffersonwarrior"><img src="https://avatars.githubusercontent.com/u/89030989?v=4&s=48" width="48" height="48" alt="jeffersonwarrior" title="jeffersonwarrior"/></a>
|
||||
<a href="https://github.com/mbelinky"><img src="https://avatars.githubusercontent.com/u/132747814?v=4&s=48" width="48" height="48" alt="mbelinky" title="mbelinky"/></a> <a href="https://github.com/julianengel"><img src="https://avatars.githubusercontent.com/u/10634231?v=4&s=48" width="48" height="48" alt="julianengel" title="julianengel"/></a> <a href="https://github.com/CashWilliams"><img src="https://avatars.githubusercontent.com/u/613573?v=4&s=48" width="48" height="48" alt="CashWilliams" title="CashWilliams"/></a> <a href="https://github.com/omniwired"><img src="https://avatars.githubusercontent.com/u/322761?v=4&s=48" width="48" height="48" alt="omniwired" title="omniwired"/></a> <a href="https://github.com/jverdi"><img src="https://avatars.githubusercontent.com/u/345050?v=4&s=48" width="48" height="48" alt="jverdi" title="jverdi"/></a> <a href="https://github.com/Syhids"><img src="https://avatars.githubusercontent.com/u/671202?v=4&s=48" width="48" height="48" alt="Syhids" title="Syhids"/></a> <a href="https://github.com/meaningfool"><img src="https://avatars.githubusercontent.com/u/2862331?v=4&s=48" width="48" height="48" alt="meaningfool" title="meaningfool"/></a> <a href="https://github.com/rafaelreis-r"><img src="https://avatars.githubusercontent.com/u/57492577?v=4&s=48" width="48" height="48" alt="rafaelreis-r" title="rafaelreis-r"/></a> <a href="https://github.com/wstock"><img src="https://avatars.githubusercontent.com/u/1394687?v=4&s=48" width="48" height="48" alt="wstock" title="wstock"/></a> <a href="https://github.com/vsabavat"><img src="https://avatars.githubusercontent.com/u/50385532?v=4&s=48" width="48" height="48" alt="vsabavat" title="vsabavat"/></a>
|
||||
<a href="https://github.com/scald"><img src="https://avatars.githubusercontent.com/u/1215913?v=4&s=48" width="48" height="48" alt="scald" title="scald"/></a> <a href="https://github.com/sreekaransrinath"><img src="https://avatars.githubusercontent.com/u/50989977?v=4&s=48" width="48" height="48" alt="sreekaransrinath" title="sreekaransrinath"/></a> <a href="https://github.com/ratulsarna"><img src="https://avatars.githubusercontent.com/u/105903728?v=4&s=48" width="48" height="48" alt="ratulsarna" title="ratulsarna"/></a> <a href="https://github.com/osolmaz"><img src="https://avatars.githubusercontent.com/u/2453968?v=4&s=48" width="48" height="48" alt="osolmaz" title="osolmaz"/></a> <a href="https://github.com/conhecendocontato"><img src="https://avatars.githubusercontent.com/u/82890727?v=4&s=48" width="48" height="48" alt="conhecendocontato" title="conhecendocontato"/></a> <a href="https://github.com/hrdwdmrbl"><img src="https://avatars.githubusercontent.com/u/554881?v=4&s=48" width="48" height="48" alt="hrdwdmrbl" title="hrdwdmrbl"/></a> <a href="https://github.com/jayhickey"><img src="https://avatars.githubusercontent.com/u/1676460?v=4&s=48" width="48" height="48" alt="jayhickey" title="jayhickey"/></a> <a href="https://github.com/jamesgroat"><img src="https://avatars.githubusercontent.com/u/2634024?v=4&s=48" width="48" height="48" alt="jamesgroat" title="jamesgroat"/></a> <a href="https://github.com/gtsifrikas"><img src="https://avatars.githubusercontent.com/u/8904378?v=4&s=48" width="48" height="48" alt="gtsifrikas" title="gtsifrikas"/></a> <a href="https://github.com/djangonavarro220"><img src="https://avatars.githubusercontent.com/u/251162586?v=4&s=48" width="48" height="48" alt="djangonavarro220" title="djangonavarro220"/></a>
|
||||
<a href="https://github.com/azade-c"><img src="https://avatars.githubusercontent.com/u/252790079?v=4&s=48" width="48" height="48" alt="azade-c" title="azade-c"/></a> <a href="https://github.com/andranik-sahakyan"><img src="https://avatars.githubusercontent.com/u/8908029?v=4&s=48" width="48" height="48" alt="andranik-sahakyan" title="andranik-sahakyan"/></a>
|
||||
<a href="https://github.com/adamgall"><img src="https://avatars.githubusercontent.com/u/706929?v=4&s=48" width="48" height="48" alt="adamgall" title="adamgall"/></a> <a href="https://github.com/jalehman"><img src="https://avatars.githubusercontent.com/u/550978?v=4&s=48" width="48" height="48" alt="jalehman" title="jalehman"/></a> <a href="https://github.com/jarvis-medmatic"><img src="https://avatars.githubusercontent.com/u/252428873?v=4&s=48" width="48" height="48" alt="jarvis-medmatic" title="jarvis-medmatic"/></a> <a href="https://github.com/mneves75"><img src="https://avatars.githubusercontent.com/u/2423436?v=4&s=48" width="48" height="48" alt="mneves75" title="mneves75"/></a> <a href="https://github.com/regenrek"><img src="https://avatars.githubusercontent.com/u/5182020?v=4&s=48" width="48" height="48" alt="regenrek" title="regenrek"/></a> <a href="https://github.com/tobiasbischoff"><img src="https://avatars.githubusercontent.com/u/711564?v=4&s=48" width="48" height="48" alt="tobiasbischoff" title="tobiasbischoff"/></a> <a href="https://github.com/MSch"><img src="https://avatars.githubusercontent.com/u/7475?v=4&s=48" width="48" height="48" alt="MSch" title="MSch"/></a> <a href="https://github.com/obviyus"><img src="https://avatars.githubusercontent.com/u/22031114?v=4&s=48" width="48" height="48" alt="obviyus" title="obviyus"/></a> <a href="https://github.com/dbhurley"><img src="https://avatars.githubusercontent.com/u/5251425?v=4&s=48" width="48" height="48" alt="dbhurley" title="dbhurley"/></a>
|
||||
<a href="https://github.com/Asleep123"><img src="https://avatars.githubusercontent.com/u/122379135?v=4&s=48" width="48" height="48" alt="Asleep123" title="Asleep123"/></a> <a href="https://github.com/Iamadig"><img src="https://avatars.githubusercontent.com/u/102129234?v=4&s=48" width="48" height="48" alt="Iamadig" title="Iamadig"/></a> <a href="https://github.com/imfing"><img src="https://avatars.githubusercontent.com/u/5097752?v=4&s=48" width="48" height="48" alt="imfing" title="imfing"/></a> <a href="https://github.com/kitze"><img src="https://avatars.githubusercontent.com/u/1160594?v=4&s=48" width="48" height="48" alt="kitze" title="kitze"/></a> <a href="https://github.com/nachoiacovino"><img src="https://avatars.githubusercontent.com/u/50103937?v=4&s=48" width="48" height="48" alt="nachoiacovino" title="nachoiacovino"/></a> <a href="https://github.com/VACInc"><img src="https://avatars.githubusercontent.com/u/3279061?v=4&s=48" width="48" height="48" alt="VACInc" title="VACInc"/></a> <a href="https://github.com/cash-echo-bot"><img src="https://avatars.githubusercontent.com/u/252747386?v=4&s=48" width="48" height="48" alt="cash-echo-bot" title="cash-echo-bot"/></a> <a href="https://github.com/claude"><img src="https://avatars.githubusercontent.com/u/81847?v=4&s=48" width="48" height="48" alt="claude" title="claude"/></a> <a href="https://github.com/kiranjd"><img src="https://avatars.githubusercontent.com/u/25822851?v=4&s=48" width="48" height="48" alt="kiranjd" title="kiranjd"/></a> <a href="https://github.com/pcty-nextgen-service-account"><img src="https://avatars.githubusercontent.com/u/112553441?v=4&s=48" width="48" height="48" alt="pcty-nextgen-service-account" title="pcty-nextgen-service-account"/></a> <a href="https://github.com/minghinmatthewlam"><img src="https://avatars.githubusercontent.com/u/14224566?v=4&s=48" width="48" height="48" alt="minghinmatthewlam" title="minghinmatthewlam"/></a>
|
||||
<a href="https://github.com/ngutman"><img src="https://avatars.githubusercontent.com/u/1540134?v=4&s=48" width="48" height="48" alt="ngutman" title="ngutman"/></a> <a href="https://github.com/onutc"><img src="https://avatars.githubusercontent.com/u/152018508?v=4&s=48" width="48" height="48" alt="onutc" title="onutc"/></a> <a href="https://github.com/oswalpalash"><img src="https://avatars.githubusercontent.com/u/6431196?v=4&s=48" width="48" height="48" alt="oswalpalash" title="oswalpalash"/></a> <a href="https://github.com/snopoke"><img src="https://avatars.githubusercontent.com/u/249606?v=4&s=48" width="48" height="48" alt="snopoke" title="snopoke"/></a> <a href="https://github.com/ManuelHettich"><img src="https://avatars.githubusercontent.com/u/17690367?v=4&s=48" width="48" height="48" alt="ManuelHettich" title="ManuelHettich"/></a> <a href="https://github.com/loukotal"><img src="https://avatars.githubusercontent.com/u/18210858?v=4&s=48" width="48" height="48" alt="loukotal" title="loukotal"/></a> <a href="https://github.com/hugobarauna"><img src="https://avatars.githubusercontent.com/u/2719?v=4&s=48" width="48" height="48" alt="hugobarauna" title="hugobarauna"/></a> <a href="https://github.com/AbhisekBasu1"><img src="https://avatars.githubusercontent.com/u/40645221?v=4&s=48" width="48" height="48" alt="AbhisekBasu1" title="AbhisekBasu1"/></a> <a href="https://github.com/emanuelst"><img src="https://avatars.githubusercontent.com/u/9994339?v=4&s=48" width="48" height="48" alt="emanuelst" title="emanuelst"/></a>
|
||||
</p>
|
||||
@@ -12,7 +12,7 @@
|
||||
"element",
|
||||
"node",
|
||||
"nodeId",
|
||||
"id",
|
||||
"jobId",
|
||||
"requestId",
|
||||
"to",
|
||||
"channelId",
|
||||
@@ -136,10 +136,10 @@
|
||||
"label": "add",
|
||||
"detailKeys": ["job.name", "job.id", "job.schedule", "job.cron"]
|
||||
},
|
||||
"update": { "label": "update", "detailKeys": ["id"] },
|
||||
"remove": { "label": "remove", "detailKeys": ["id"] },
|
||||
"run": { "label": "run", "detailKeys": ["id"] },
|
||||
"runs": { "label": "runs", "detailKeys": ["id"] },
|
||||
"update": { "label": "update", "detailKeys": ["jobId"] },
|
||||
"remove": { "label": "remove", "detailKeys": ["jobId"] },
|
||||
"run": { "label": "run", "detailKeys": ["jobId"] },
|
||||
"runs": { "label": "runs", "detailKeys": ["jobId"] },
|
||||
"wake": { "label": "wake", "detailKeys": ["text", "mode"] }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -416,8 +416,7 @@ final class AppState {
|
||||
: nil
|
||||
|
||||
Task { @MainActor in
|
||||
// Keep app-only connection settings local to avoid overwriting remote gateway config.
|
||||
var root = ClawdbotConfigFile.loadDict()
|
||||
var root = await ConfigStore.load()
|
||||
var gateway = root["gateway"] as? [String: Any] ?? [:]
|
||||
var changed = false
|
||||
|
||||
@@ -447,12 +446,8 @@ final class AppState {
|
||||
}
|
||||
|
||||
guard changed else { return }
|
||||
if gateway.isEmpty {
|
||||
root.removeValue(forKey: "gateway")
|
||||
} else {
|
||||
root["gateway"] = gateway
|
||||
}
|
||||
ClawdbotConfigFile.saveDict(root)
|
||||
root["gateway"] = gateway
|
||||
try? await ConfigStore.save(root)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ actor BridgeServer {
|
||||
thinking: "low",
|
||||
deliver: false,
|
||||
to: nil,
|
||||
provider: .last))
|
||||
channel: .last))
|
||||
|
||||
case "agent.request":
|
||||
guard let json = evt.payloadJSON, let data = json.data(using: .utf8) else {
|
||||
@@ -205,7 +205,7 @@ actor BridgeServer {
|
||||
?? "node-\(nodeId)"
|
||||
let thinking = link.thinking?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty
|
||||
let to = link.to?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty
|
||||
let provider = GatewayAgentProvider(raw: link.channel)
|
||||
let channel = GatewayAgentChannel(raw: link.channel)
|
||||
|
||||
_ = await GatewayConnection.shared.sendAgent(GatewayAgentInvocation(
|
||||
message: message,
|
||||
@@ -213,7 +213,7 @@ actor BridgeServer {
|
||||
thinking: thinking,
|
||||
deliver: link.deliver,
|
||||
to: to,
|
||||
provider: provider))
|
||||
channel: channel))
|
||||
|
||||
default:
|
||||
break
|
||||
|
||||
@@ -79,15 +79,14 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
|
||||
GatewayProcessManager.shared.setActive(true)
|
||||
}
|
||||
|
||||
let result = await GatewayConnection.shared.sendAgent(
|
||||
GatewayAgentInvocation(
|
||||
message: text,
|
||||
sessionKey: self.sessionKey,
|
||||
thinking: "low",
|
||||
deliver: false,
|
||||
to: nil,
|
||||
provider: .last,
|
||||
idempotencyKey: actionId))
|
||||
let result = await GatewayConnection.shared.sendAgent(GatewayAgentInvocation(
|
||||
message: text,
|
||||
sessionKey: self.sessionKey,
|
||||
thinking: "low",
|
||||
deliver: false,
|
||||
to: nil,
|
||||
channel: .last,
|
||||
idempotencyKey: actionId))
|
||||
|
||||
await MainActor.run {
|
||||
guard let webView else { return }
|
||||
|
||||
@@ -33,13 +33,13 @@ extension CronJobEditor {
|
||||
case let .systemEvent(text):
|
||||
self.payloadKind = .systemEvent
|
||||
self.systemEventText = text
|
||||
case let .agentTurn(message, thinking, timeoutSeconds, deliver, provider, to, bestEffortDeliver):
|
||||
case let .agentTurn(message, thinking, timeoutSeconds, deliver, channel, to, bestEffortDeliver):
|
||||
self.payloadKind = .agentTurn
|
||||
self.agentMessage = message
|
||||
self.thinking = thinking ?? ""
|
||||
self.timeoutSeconds = timeoutSeconds.map(String.init) ?? ""
|
||||
self.deliver = deliver ?? false
|
||||
self.provider = GatewayAgentProvider(raw: provider)
|
||||
self.channel = GatewayAgentChannel(raw: channel)
|
||||
self.to = to ?? ""
|
||||
self.bestEffortDeliver = bestEffortDeliver ?? false
|
||||
}
|
||||
@@ -166,7 +166,7 @@ extension CronJobEditor {
|
||||
if let n = Int(self.timeoutSeconds), n > 0 { payload["timeoutSeconds"] = n }
|
||||
payload["deliver"] = self.deliver
|
||||
if self.deliver {
|
||||
payload["provider"] = self.provider.rawValue
|
||||
payload["channel"] = self.channel.rawValue
|
||||
let to = self.to.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if !to.isEmpty { payload["to"] = to }
|
||||
payload["bestEffortDeliver"] = self.bestEffortDeliver
|
||||
|
||||
@@ -13,7 +13,7 @@ extension CronJobEditor {
|
||||
self.payloadKind = .agentTurn
|
||||
self.agentMessage = "Run diagnostic"
|
||||
self.deliver = true
|
||||
self.provider = .last
|
||||
self.channel = .last
|
||||
self.to = "+15551230000"
|
||||
self.thinking = "low"
|
||||
self.timeoutSeconds = "90"
|
||||
|
||||
@@ -17,7 +17,7 @@ struct CronJobEditor: View {
|
||||
static let scheduleKindNote =
|
||||
"“At” runs once, “Every” repeats with a duration, “Cron” uses a 5-field Unix expression."
|
||||
static let isolatedPayloadNote =
|
||||
"Isolated jobs always run an agent turn. The result can be delivered to a provider, "
|
||||
"Isolated jobs always run an agent turn. The result can be delivered to a surface, "
|
||||
+ "and a short summary is posted back to your main chat."
|
||||
static let mainPayloadNote =
|
||||
"System events are injected into the current main session. Agent turns require an isolated session target."
|
||||
@@ -42,7 +42,7 @@ struct CronJobEditor: View {
|
||||
@State var systemEventText: String = ""
|
||||
@State var agentMessage: String = ""
|
||||
@State var deliver: Bool = false
|
||||
@State var provider: GatewayAgentProvider = .last
|
||||
@State var channel: GatewayAgentChannel = .last
|
||||
@State var to: String = ""
|
||||
@State var thinking: String = ""
|
||||
@State var timeoutSeconds: String = ""
|
||||
@@ -309,7 +309,7 @@ struct CronJobEditor: View {
|
||||
}
|
||||
GridRow {
|
||||
self.gridLabel("Deliver")
|
||||
Toggle("Deliver result to a provider", isOn: self.$deliver)
|
||||
Toggle("Deliver result to a surface", isOn: self.$deliver)
|
||||
.toggleStyle(.switch)
|
||||
}
|
||||
}
|
||||
@@ -317,15 +317,12 @@ struct CronJobEditor: View {
|
||||
if self.deliver {
|
||||
Grid(alignment: .leadingFirstTextBaseline, horizontalSpacing: 14, verticalSpacing: 10) {
|
||||
GridRow {
|
||||
self.gridLabel("Provider")
|
||||
Picker("", selection: self.$provider) {
|
||||
Text("last").tag(GatewayAgentProvider.last)
|
||||
Text("whatsapp").tag(GatewayAgentProvider.whatsapp)
|
||||
Text("telegram").tag(GatewayAgentProvider.telegram)
|
||||
Text("discord").tag(GatewayAgentProvider.discord)
|
||||
Text("slack").tag(GatewayAgentProvider.slack)
|
||||
Text("signal").tag(GatewayAgentProvider.signal)
|
||||
Text("imessage").tag(GatewayAgentProvider.imessage)
|
||||
self.gridLabel("Channel")
|
||||
Picker("", selection: self.$channel) {
|
||||
Text("last").tag(GatewayAgentChannel.last)
|
||||
Text("whatsapp").tag(GatewayAgentChannel.whatsapp)
|
||||
Text("telegram").tag(GatewayAgentChannel.telegram)
|
||||
Text("discord").tag(GatewayAgentChannel.discord)
|
||||
}
|
||||
.labelsHidden()
|
||||
.pickerStyle(.segmented)
|
||||
|
||||
@@ -74,12 +74,12 @@ enum CronPayload: Codable, Equatable {
|
||||
thinking: String?,
|
||||
timeoutSeconds: Int?,
|
||||
deliver: Bool?,
|
||||
provider: String?,
|
||||
channel: String?,
|
||||
to: String?,
|
||||
bestEffortDeliver: Bool?)
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case kind, text, message, thinking, timeoutSeconds, deliver, provider, to, bestEffortDeliver
|
||||
case kind, text, message, thinking, timeoutSeconds, deliver, channel, to, bestEffortDeliver
|
||||
}
|
||||
|
||||
var kind: String {
|
||||
@@ -101,7 +101,7 @@ enum CronPayload: Codable, Equatable {
|
||||
thinking: container.decodeIfPresent(String.self, forKey: .thinking),
|
||||
timeoutSeconds: container.decodeIfPresent(Int.self, forKey: .timeoutSeconds),
|
||||
deliver: container.decodeIfPresent(Bool.self, forKey: .deliver),
|
||||
provider: container.decodeIfPresent(String.self, forKey: .provider),
|
||||
channel: container.decodeIfPresent(String.self, forKey: .channel),
|
||||
to: container.decodeIfPresent(String.self, forKey: .to),
|
||||
bestEffortDeliver: container.decodeIfPresent(Bool.self, forKey: .bestEffortDeliver))
|
||||
default:
|
||||
@@ -118,12 +118,12 @@ enum CronPayload: Codable, Equatable {
|
||||
switch self {
|
||||
case let .systemEvent(text):
|
||||
try container.encode(text, forKey: .text)
|
||||
case let .agentTurn(message, thinking, timeoutSeconds, deliver, provider, to, bestEffortDeliver):
|
||||
case let .agentTurn(message, thinking, timeoutSeconds, deliver, channel, to, bestEffortDeliver):
|
||||
try container.encode(message, forKey: .message)
|
||||
try container.encodeIfPresent(thinking, forKey: .thinking)
|
||||
try container.encodeIfPresent(timeoutSeconds, forKey: .timeoutSeconds)
|
||||
try container.encodeIfPresent(deliver, forKey: .deliver)
|
||||
try container.encodeIfPresent(provider, forKey: .provider)
|
||||
try container.encodeIfPresent(channel, forKey: .channel)
|
||||
try container.encodeIfPresent(to, forKey: .to)
|
||||
try container.encodeIfPresent(bestEffortDeliver, forKey: .bestEffortDeliver)
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ extension CronSettings {
|
||||
Text(text)
|
||||
.font(.callout)
|
||||
.textSelection(.enabled)
|
||||
case let .agentTurn(message, thinking, timeoutSeconds, deliver, provider, to, _):
|
||||
case let .agentTurn(message, thinking, timeoutSeconds, deliver, channel, to, _):
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(message)
|
||||
.font(.callout)
|
||||
@@ -216,7 +216,7 @@ extension CronSettings {
|
||||
if let timeoutSeconds { StatusPill(text: "\(timeoutSeconds)s", tint: .secondary) }
|
||||
if deliver ?? false {
|
||||
StatusPill(text: "deliver", tint: .secondary)
|
||||
if let provider, !provider.isEmpty { StatusPill(text: provider, tint: .secondary) }
|
||||
if let channel, !channel.isEmpty { StatusPill(text: channel, tint: .secondary) }
|
||||
if let to, !to.isEmpty { StatusPill(text: to, tint: .secondary) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ struct CronSettings_Previews: PreviewProvider {
|
||||
thinking: "low",
|
||||
timeoutSeconds: 600,
|
||||
deliver: true,
|
||||
provider: "last",
|
||||
channel: "last",
|
||||
to: nil,
|
||||
bestEffortDeliver: true),
|
||||
isolation: CronIsolation(postToMainPrefix: "Cron"),
|
||||
@@ -72,7 +72,7 @@ extension CronSettings {
|
||||
thinking: "low",
|
||||
timeoutSeconds: 120,
|
||||
deliver: true,
|
||||
provider: "whatsapp",
|
||||
channel: "whatsapp",
|
||||
to: "+15551234567",
|
||||
bestEffortDeliver: true),
|
||||
isolation: CronIsolation(postToMainPrefix: "[cron] "),
|
||||
|
||||
@@ -59,7 +59,7 @@ final class DeepLinkHandler {
|
||||
}
|
||||
|
||||
do {
|
||||
let provider = GatewayAgentProvider(raw: link.channel)
|
||||
let channel = GatewayAgentChannel(raw: link.channel)
|
||||
let explicitSessionKey = link.sessionKey?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.nonEmpty
|
||||
@@ -72,9 +72,9 @@ final class DeepLinkHandler {
|
||||
message: messagePreview,
|
||||
sessionKey: resolvedSessionKey,
|
||||
thinking: link.thinking?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty,
|
||||
deliver: provider.shouldDeliver(link.deliver),
|
||||
deliver: channel.shouldDeliver(link.deliver),
|
||||
to: link.to?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty,
|
||||
provider: provider,
|
||||
channel: channel,
|
||||
timeoutSeconds: link.timeoutSeconds,
|
||||
idempotencyKey: UUID().uuidString)
|
||||
|
||||
|
||||
@@ -5,19 +5,16 @@ import OSLog
|
||||
|
||||
private let gatewayConnectionLogger = Logger(subsystem: "com.clawdbot", category: "gateway.connection")
|
||||
|
||||
enum GatewayAgentProvider: String, Codable, CaseIterable, Sendable {
|
||||
enum GatewayAgentChannel: String, Codable, CaseIterable, Sendable {
|
||||
case last
|
||||
case whatsapp
|
||||
case telegram
|
||||
case discord
|
||||
case slack
|
||||
case signal
|
||||
case imessage
|
||||
case webchat
|
||||
|
||||
init(raw: String?) {
|
||||
let normalized = (raw ?? "").trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||
self = GatewayAgentProvider(rawValue: normalized) ?? .last
|
||||
self = GatewayAgentChannel(rawValue: normalized) ?? .last
|
||||
}
|
||||
|
||||
var isDeliverable: Bool { self != .webchat }
|
||||
@@ -31,7 +28,7 @@ struct GatewayAgentInvocation: Sendable {
|
||||
var thinking: String?
|
||||
var deliver: Bool = false
|
||||
var to: String?
|
||||
var provider: GatewayAgentProvider = .last
|
||||
var channel: GatewayAgentChannel = .last
|
||||
var timeoutSeconds: Int?
|
||||
var idempotencyKey: String = UUID().uuidString
|
||||
}
|
||||
@@ -368,7 +365,7 @@ extension GatewayConnection {
|
||||
"thinking": AnyCodable(invocation.thinking ?? "default"),
|
||||
"deliver": AnyCodable(invocation.deliver),
|
||||
"to": AnyCodable(invocation.to ?? ""),
|
||||
"provider": AnyCodable(invocation.provider.rawValue),
|
||||
"channel": AnyCodable(invocation.channel.rawValue),
|
||||
"idempotencyKey": AnyCodable(invocation.idempotencyKey),
|
||||
]
|
||||
if let timeout = invocation.timeoutSeconds {
|
||||
@@ -389,7 +386,7 @@ extension GatewayConnection {
|
||||
sessionKey: String,
|
||||
deliver: Bool,
|
||||
to: String?,
|
||||
provider: GatewayAgentProvider = .last,
|
||||
channel: GatewayAgentChannel = .last,
|
||||
timeoutSeconds: Int? = nil,
|
||||
idempotencyKey: String = UUID().uuidString) async -> (ok: Bool, error: String?)
|
||||
{
|
||||
@@ -399,7 +396,7 @@ extension GatewayConnection {
|
||||
thinking: thinking,
|
||||
deliver: deliver,
|
||||
to: to,
|
||||
provider: provider,
|
||||
channel: channel,
|
||||
timeoutSeconds: timeoutSeconds,
|
||||
idempotencyKey: idempotencyKey))
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ struct GatewaySessionDefaultsRecord: Codable {
|
||||
struct GatewaySessionEntryRecord: Codable {
|
||||
let key: String
|
||||
let displayName: String?
|
||||
let provider: String?
|
||||
let surface: String?
|
||||
let subject: String?
|
||||
let room: String?
|
||||
let space: String?
|
||||
@@ -71,7 +71,7 @@ struct SessionRow: Identifiable {
|
||||
let key: String
|
||||
let kind: SessionKind
|
||||
let displayName: String?
|
||||
let provider: String?
|
||||
let surface: String?
|
||||
let subject: String?
|
||||
let room: String?
|
||||
let space: String?
|
||||
@@ -141,7 +141,7 @@ extension SessionRow {
|
||||
key: "user@example.com",
|
||||
kind: .direct,
|
||||
displayName: nil,
|
||||
provider: nil,
|
||||
surface: nil,
|
||||
subject: nil,
|
||||
room: nil,
|
||||
space: nil,
|
||||
@@ -158,7 +158,7 @@ extension SessionRow {
|
||||
key: "discord:channel:release-squad",
|
||||
kind: .group,
|
||||
displayName: "discord:#release-squad",
|
||||
provider: "discord",
|
||||
surface: "discord",
|
||||
subject: nil,
|
||||
room: "#release-squad",
|
||||
space: nil,
|
||||
@@ -175,7 +175,7 @@ extension SessionRow {
|
||||
key: "global",
|
||||
kind: .global,
|
||||
displayName: nil,
|
||||
provider: nil,
|
||||
surface: nil,
|
||||
subject: nil,
|
||||
room: nil,
|
||||
space: nil,
|
||||
@@ -298,7 +298,7 @@ enum SessionLoader {
|
||||
key: entry.key,
|
||||
kind: SessionKind.from(key: entry.key),
|
||||
displayName: entry.displayName,
|
||||
provider: entry.provider,
|
||||
surface: entry.surface,
|
||||
subject: entry.subject,
|
||||
room: entry.room,
|
||||
space: entry.space,
|
||||
|
||||
@@ -37,7 +37,7 @@ enum VoiceWakeForwarder {
|
||||
var thinking: String = "low"
|
||||
var deliver: Bool = true
|
||||
var to: String?
|
||||
var provider: GatewayAgentProvider = .last
|
||||
var channel: GatewayAgentChannel = .last
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
@@ -46,14 +46,14 @@ enum VoiceWakeForwarder {
|
||||
options: ForwardOptions = ForwardOptions()) async -> Result<Void, VoiceWakeForwardError>
|
||||
{
|
||||
let payload = Self.prefixedTranscript(transcript)
|
||||
let deliver = options.provider.shouldDeliver(options.deliver)
|
||||
let deliver = options.channel.shouldDeliver(options.deliver)
|
||||
let result = await GatewayConnection.shared.sendAgent(GatewayAgentInvocation(
|
||||
message: payload,
|
||||
sessionKey: options.sessionKey,
|
||||
thinking: options.thinking,
|
||||
deliver: deliver,
|
||||
to: options.to,
|
||||
provider: options.provider))
|
||||
channel: options.channel))
|
||||
|
||||
if result.ok {
|
||||
self.logger.info("voice wake forward ok")
|
||||
|
||||
@@ -342,7 +342,6 @@ public struct SendParams: Codable, Sendable {
|
||||
public let mediaurl: String?
|
||||
public let gifplayback: Bool?
|
||||
public let provider: String?
|
||||
public let accountid: String?
|
||||
public let idempotencykey: String
|
||||
|
||||
public init(
|
||||
@@ -351,7 +350,6 @@ public struct SendParams: Codable, Sendable {
|
||||
mediaurl: String?,
|
||||
gifplayback: Bool?,
|
||||
provider: String?,
|
||||
accountid: String?,
|
||||
idempotencykey: String
|
||||
) {
|
||||
self.to = to
|
||||
@@ -359,7 +357,6 @@ public struct SendParams: Codable, Sendable {
|
||||
self.mediaurl = mediaurl
|
||||
self.gifplayback = gifplayback
|
||||
self.provider = provider
|
||||
self.accountid = accountid
|
||||
self.idempotencykey = idempotencykey
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
@@ -368,48 +365,6 @@ public struct SendParams: Codable, Sendable {
|
||||
case mediaurl = "mediaUrl"
|
||||
case gifplayback = "gifPlayback"
|
||||
case provider
|
||||
case accountid = "accountId"
|
||||
case idempotencykey = "idempotencyKey"
|
||||
}
|
||||
}
|
||||
|
||||
public struct PollParams: Codable, Sendable {
|
||||
public let to: String
|
||||
public let question: String
|
||||
public let options: [String]
|
||||
public let maxselections: Int?
|
||||
public let durationhours: Int?
|
||||
public let provider: String?
|
||||
public let accountid: String?
|
||||
public let idempotencykey: String
|
||||
|
||||
public init(
|
||||
to: String,
|
||||
question: String,
|
||||
options: [String],
|
||||
maxselections: Int?,
|
||||
durationhours: Int?,
|
||||
provider: String?,
|
||||
accountid: String?,
|
||||
idempotencykey: String
|
||||
) {
|
||||
self.to = to
|
||||
self.question = question
|
||||
self.options = options
|
||||
self.maxselections = maxselections
|
||||
self.durationhours = durationhours
|
||||
self.provider = provider
|
||||
self.accountid = accountid
|
||||
self.idempotencykey = idempotencykey
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case to
|
||||
case question
|
||||
case options
|
||||
case maxselections = "maxSelections"
|
||||
case durationhours = "durationHours"
|
||||
case provider
|
||||
case accountid = "accountId"
|
||||
case idempotencykey = "idempotencyKey"
|
||||
}
|
||||
}
|
||||
@@ -421,7 +376,7 @@ public struct AgentParams: Codable, Sendable {
|
||||
public let sessionkey: String?
|
||||
public let thinking: String?
|
||||
public let deliver: Bool?
|
||||
public let provider: String?
|
||||
public let channel: String?
|
||||
public let timeout: Int?
|
||||
public let lane: String?
|
||||
public let extrasystemprompt: String?
|
||||
@@ -434,7 +389,7 @@ public struct AgentParams: Codable, Sendable {
|
||||
sessionkey: String?,
|
||||
thinking: String?,
|
||||
deliver: Bool?,
|
||||
provider: String?,
|
||||
channel: String?,
|
||||
timeout: Int?,
|
||||
lane: String?,
|
||||
extrasystemprompt: String?,
|
||||
@@ -446,7 +401,7 @@ public struct AgentParams: Codable, Sendable {
|
||||
self.sessionkey = sessionkey
|
||||
self.thinking = thinking
|
||||
self.deliver = deliver
|
||||
self.provider = provider
|
||||
self.channel = channel
|
||||
self.timeout = timeout
|
||||
self.lane = lane
|
||||
self.extrasystemprompt = extrasystemprompt
|
||||
@@ -459,7 +414,7 @@ public struct AgentParams: Codable, Sendable {
|
||||
case sessionkey = "sessionKey"
|
||||
case thinking
|
||||
case deliver
|
||||
case provider
|
||||
case channel
|
||||
case timeout
|
||||
case lane
|
||||
case extrasystemprompt = "extraSystemPrompt"
|
||||
@@ -663,27 +618,23 @@ public struct SessionsListParams: Codable, Sendable {
|
||||
public let activeminutes: Int?
|
||||
public let includeglobal: Bool?
|
||||
public let includeunknown: Bool?
|
||||
public let spawnedby: String?
|
||||
|
||||
public init(
|
||||
limit: Int?,
|
||||
activeminutes: Int?,
|
||||
includeglobal: Bool?,
|
||||
includeunknown: Bool?,
|
||||
spawnedby: String?
|
||||
includeunknown: Bool?
|
||||
) {
|
||||
self.limit = limit
|
||||
self.activeminutes = activeminutes
|
||||
self.includeglobal = includeglobal
|
||||
self.includeunknown = includeunknown
|
||||
self.spawnedby = spawnedby
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case limit
|
||||
case activeminutes = "activeMinutes"
|
||||
case includeglobal = "includeGlobal"
|
||||
case includeunknown = "includeUnknown"
|
||||
case spawnedby = "spawnedBy"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,7 +644,6 @@ public struct SessionsPatchParams: Codable, Sendable {
|
||||
public let verboselevel: AnyCodable?
|
||||
public let elevatedlevel: AnyCodable?
|
||||
public let model: AnyCodable?
|
||||
public let spawnedby: AnyCodable?
|
||||
public let sendpolicy: AnyCodable?
|
||||
public let groupactivation: AnyCodable?
|
||||
|
||||
@@ -703,7 +653,6 @@ public struct SessionsPatchParams: Codable, Sendable {
|
||||
verboselevel: AnyCodable?,
|
||||
elevatedlevel: AnyCodable?,
|
||||
model: AnyCodable?,
|
||||
spawnedby: AnyCodable?,
|
||||
sendpolicy: AnyCodable?,
|
||||
groupactivation: AnyCodable?
|
||||
) {
|
||||
@@ -712,7 +661,6 @@ public struct SessionsPatchParams: Codable, Sendable {
|
||||
self.verboselevel = verboselevel
|
||||
self.elevatedlevel = elevatedlevel
|
||||
self.model = model
|
||||
self.spawnedby = spawnedby
|
||||
self.sendpolicy = sendpolicy
|
||||
self.groupactivation = groupactivation
|
||||
}
|
||||
@@ -722,7 +670,6 @@ public struct SessionsPatchParams: Codable, Sendable {
|
||||
case verboselevel = "verboseLevel"
|
||||
case elevatedlevel = "elevatedLevel"
|
||||
case model
|
||||
case spawnedby = "spawnedBy"
|
||||
case sendpolicy = "sendPolicy"
|
||||
case groupactivation = "groupActivation"
|
||||
}
|
||||
@@ -1033,41 +980,33 @@ public struct WebLoginStartParams: Codable, Sendable {
|
||||
public let force: Bool?
|
||||
public let timeoutms: Int?
|
||||
public let verbose: Bool?
|
||||
public let accountid: String?
|
||||
|
||||
public init(
|
||||
force: Bool?,
|
||||
timeoutms: Int?,
|
||||
verbose: Bool?,
|
||||
accountid: String?
|
||||
verbose: Bool?
|
||||
) {
|
||||
self.force = force
|
||||
self.timeoutms = timeoutms
|
||||
self.verbose = verbose
|
||||
self.accountid = accountid
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case force
|
||||
case timeoutms = "timeoutMs"
|
||||
case verbose
|
||||
case accountid = "accountId"
|
||||
}
|
||||
}
|
||||
|
||||
public struct WebLoginWaitParams: Codable, Sendable {
|
||||
public let timeoutms: Int?
|
||||
public let accountid: String?
|
||||
|
||||
public init(
|
||||
timeoutms: Int?,
|
||||
accountid: String?
|
||||
timeoutms: Int?
|
||||
) {
|
||||
self.timeoutms = timeoutms
|
||||
self.accountid = accountid
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case timeoutms = "timeoutMs"
|
||||
case accountid = "accountId"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"element",
|
||||
"node",
|
||||
"nodeId",
|
||||
"id",
|
||||
"jobId",
|
||||
"requestId",
|
||||
"to",
|
||||
"channelId",
|
||||
@@ -136,10 +136,10 @@
|
||||
"label": "add",
|
||||
"detailKeys": ["job.name", "job.id", "job.schedule", "job.cron"]
|
||||
},
|
||||
"update": { "label": "update", "detailKeys": ["id"] },
|
||||
"remove": { "label": "remove", "detailKeys": ["id"] },
|
||||
"run": { "label": "run", "detailKeys": ["id"] },
|
||||
"runs": { "label": "runs", "detailKeys": ["id"] },
|
||||
"update": { "label": "update", "detailKeys": ["jobId"] },
|
||||
"remove": { "label": "remove", "detailKeys": ["jobId"] },
|
||||
"run": { "label": "run", "detailKeys": ["jobId"] },
|
||||
"runs": { "label": "runs", "detailKeys": ["jobId"] },
|
||||
"wake": { "label": "wake", "detailKeys": ["text", "mode"] }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,26 +8,26 @@ read_when:
|
||||
|
||||
## First run (recommended)
|
||||
|
||||
Clawdbot uses a dedicated workspace directory for the agent. Default: `~/clawd` (configurable via `agent.workspace`).
|
||||
Clawdbot uses a dedicated workspace directory for the agent. Default: `~/.clawdbot/workspace`.
|
||||
|
||||
1) Create the workspace (if it doesn’t already exist):
|
||||
|
||||
```bash
|
||||
mkdir -p ~/clawd
|
||||
mkdir -p ~/.clawdbot/workspace
|
||||
```
|
||||
|
||||
2) Copy the default workspace templates into the workspace:
|
||||
|
||||
```bash
|
||||
cp docs/templates/AGENTS.md ~/clawd/AGENTS.md
|
||||
cp docs/templates/SOUL.md ~/clawd/SOUL.md
|
||||
cp docs/templates/TOOLS.md ~/clawd/TOOLS.md
|
||||
cp docs/templates/AGENTS.md ~/.clawdbot/workspace/AGENTS.md
|
||||
cp docs/templates/SOUL.md ~/.clawdbot/workspace/SOUL.md
|
||||
cp docs/templates/TOOLS.md ~/.clawdbot/workspace/TOOLS.md
|
||||
```
|
||||
|
||||
3) Optional: if you want the personal assistant skill roster, replace AGENTS.md with this file:
|
||||
|
||||
```bash
|
||||
cp docs/AGENTS.default.md ~/clawd/AGENTS.md
|
||||
cp docs/AGENTS.default.md ~/.clawdbot/workspace/AGENTS.md
|
||||
```
|
||||
|
||||
4) Optional: choose a different workspace by setting `agent.workspace` (supports `~`):
|
||||
@@ -73,7 +73,7 @@ cp docs/AGENTS.default.md ~/clawd/AGENTS.md
|
||||
If you treat this workspace as Clawd’s “memory”, make it a git repo (ideally private) so `AGENTS.md` and your memory files are backed up.
|
||||
|
||||
```bash
|
||||
cd ~/clawd
|
||||
cd ~/.clawdbot/workspace
|
||||
git init
|
||||
git add AGENTS.md
|
||||
git commit -m "Add Clawd workspace"
|
||||
@@ -83,7 +83,7 @@ git commit -m "Add Clawd workspace"
|
||||
## What Clawdbot Does
|
||||
- Runs WhatsApp gateway + Pi coding agent so the assistant can read/write chats, fetch context, and run skills via the host Mac.
|
||||
- macOS app manages permissions (screen recording, notifications, microphone) and exposes the `clawdbot` CLI via its bundled binary.
|
||||
- Direct chats collapse into the agent's `main` session by default; groups stay isolated as `agent:<agentId>:<provider>:group:<id>` (rooms/channels: `agent:<agentId>:<provider>:channel:<id>`); heartbeats keep background tasks alive.
|
||||
- Direct chats collapse into the shared `main` session by default; groups stay isolated as `surface:group:<id>` (rooms: `surface:channel:<id>`); heartbeats keep background tasks alive.
|
||||
|
||||
## Core Skills (enable in Settings → Skills)
|
||||
- **mcporter** — Tool server runtime/CLI for managing external skill backends.
|
||||
|
||||
+6
-6
@@ -12,12 +12,12 @@ Use `pnpm` (Node 22+) from the repo root. Keep the working tree clean before tag
|
||||
|
||||
1) **Version & metadata**
|
||||
- [ ] Bump `package.json` version (e.g., `1.1.0`).
|
||||
- [ ] Update CLI/version strings: [`src/cli/program.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/cli/program.ts) and the Baileys user agent in [`src/provider-web.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/provider-web.ts).
|
||||
- [ ] Confirm package metadata (name, description, repository, keywords, license) and `bin` map points to [`dist/entry.js`](https://github.com/clawdbot/clawdbot/blob/main/dist/entry.js) for `clawdbot`.
|
||||
- [ ] Update CLI/version strings: `src/cli/program.ts` and the Baileys user agent in `src/provider-web.ts`.
|
||||
- [ ] Confirm package metadata (name, description, repository, keywords, license) and `bin` map points to `dist/index.js` for `clawdbot`.
|
||||
- [ ] If dependencies changed, run `pnpm install` so `pnpm-lock.yaml` is current.
|
||||
|
||||
2) **Build & artifacts**
|
||||
- [ ] If A2UI inputs changed, run `pnpm canvas:a2ui:bundle` and commit any updated [`src/canvas-host/a2ui/a2ui.bundle.js`](https://github.com/clawdbot/clawdbot/blob/main/src/canvas-host/a2ui/a2ui.bundle.js).
|
||||
- [ ] If A2UI inputs changed, run `pnpm canvas:a2ui:bundle` and commit any updated `src/canvas-host/a2ui/a2ui.bundle.js`.
|
||||
- [ ] `pnpm run build` (regenerates `dist/`).
|
||||
- [ ] Optional: `npm pack --pack-destination /tmp` after the build; inspect the tarball contents and keep it handy for the GitHub release (do **not** commit it).
|
||||
|
||||
@@ -34,11 +34,11 @@ Use `pnpm` (Node 22+) from the repo root. Keep the working tree clean before tag
|
||||
|
||||
5) **macOS app (Sparkle)**
|
||||
- [ ] Build + sign the macOS app, then zip it for distribution.
|
||||
- [ ] Generate the Sparkle appcast (HTML notes via [`scripts/make_appcast.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/make_appcast.sh)) and update `appcast.xml`.
|
||||
- [ ] Generate the Sparkle appcast (HTML notes via `scripts/make_appcast.sh`) and update `appcast.xml`.
|
||||
- [ ] Keep the app zip (and optional dSYM zip) ready to attach to the GitHub release.
|
||||
- [ ] Follow [`docs/mac/release.md`](https://docs.clawd.bot/mac/release) for the exact commands and required env vars.
|
||||
- [ ] Follow `docs/mac/release.md` for the exact commands and required env vars.
|
||||
- `APP_BUILD` must be numeric + monotonic (no `-beta`) so Sparkle compares versions correctly.
|
||||
- If notarizing, use the `clawdbot-notary` keychain profile created from App Store Connect API env vars (see [`docs/mac/release.md`](https://docs.clawd.bot/mac/release)).
|
||||
- If notarizing, use the `clawdbot-notary` keychain profile created from App Store Connect API env vars (see `docs/mac/release.md`).
|
||||
|
||||
6) **Publish (npm)**
|
||||
- [ ] Confirm git status is clean; commit and push as needed.
|
||||
|
||||
+4
-6
@@ -1,5 +1,5 @@
|
||||
title: "CLAWDBOT Docs"
|
||||
description: "A TypeScript/Node gateway + macOS/iOS/Android companions for WhatsApp (web) and Telegram (bot)."
|
||||
description: "A TypeScript/Node gateway + macOS/iOS companions for WhatsApp (web) and Telegram (bot)."
|
||||
markdown: kramdown
|
||||
highlighter: rouge
|
||||
|
||||
@@ -35,11 +35,9 @@ nav:
|
||||
- title: "WebChat"
|
||||
url: "/webchat.html"
|
||||
- title: "macOS App"
|
||||
url: "/macos.html"
|
||||
- title: "iOS App"
|
||||
url: "/ios.html"
|
||||
- title: "Android App"
|
||||
url: "/android.html"
|
||||
url: "/clawdbot-mac.html"
|
||||
- title: "iOS Node"
|
||||
url: "/ios/connect.html"
|
||||
- title: "Telegram"
|
||||
url: "/telegram.html"
|
||||
- title: "Security"
|
||||
|
||||
+10
-10
@@ -8,8 +8,8 @@ read_when:
|
||||
Short, exact flow of one agent run. Source of truth: current code in `src/`.
|
||||
|
||||
## Entry points
|
||||
- Gateway RPC: `agent` and `agent.wait` in [`src/gateway/server-methods/agent.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server-methods/agent.ts).
|
||||
- CLI: `agentCommand` in [`src/commands/agent.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/commands/agent.ts).
|
||||
- Gateway RPC: `agent` and `agent.wait` in `src/gateway/server-methods/agent.ts`.
|
||||
- CLI: `agentCommand` in `src/commands/agent.ts`.
|
||||
|
||||
## High-level flow
|
||||
1) `agent` RPC validates params, resolves session (sessionKey/sessionId), persists session metadata, returns `{ runId, acceptedAt }` immediately.
|
||||
@@ -36,8 +36,8 @@ Short, exact flow of one agent run. Source of truth: current code in `src/`.
|
||||
- `assistant`: streamed deltas from pi-agent-core
|
||||
- `tool`: streamed tool events from pi-agent-core
|
||||
|
||||
## Chat provider handling
|
||||
- `createAgentEventHandler` in [`src/gateway/server-chat.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server-chat.ts):
|
||||
## Chat surface handling
|
||||
- `createAgentEventHandler` in `src/gateway/server-chat.ts`:
|
||||
- buffers assistant deltas
|
||||
- emits chat `delta` messages
|
||||
- emits chat `final` when **lifecycle end/error** arrives
|
||||
@@ -53,9 +53,9 @@ Short, exact flow of one agent run. Source of truth: current code in `src/`.
|
||||
- `agent.wait` timeout (wait-only, does not stop agent)
|
||||
|
||||
## Files
|
||||
- [`src/gateway/server-methods/agent.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server-methods/agent.ts)
|
||||
- [`src/gateway/server-methods/agent-job.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server-methods/agent-job.ts)
|
||||
- [`src/commands/agent.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/commands/agent.ts)
|
||||
- [`src/agents/pi-embedded-runner.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/agents/pi-embedded-runner.ts)
|
||||
- [`src/agents/pi-embedded-subscribe.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/agents/pi-embedded-subscribe.ts)
|
||||
- [`src/gateway/server-chat.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server-chat.ts)
|
||||
- `src/gateway/server-methods/agent.ts`
|
||||
- `src/gateway/server-methods/agent-job.ts`
|
||||
- `src/commands/agent.ts`
|
||||
- `src/agents/pi-embedded-runner.ts`
|
||||
- `src/agents/pi-embedded-subscribe.ts`
|
||||
- `src/gateway/server-chat.ts`
|
||||
+6
-14
@@ -9,13 +9,13 @@ CLAWDBOT runs a single embedded agent runtime derived from **p-mono** (internal
|
||||
|
||||
## Workspace (required)
|
||||
|
||||
CLAWDBOT uses a single agent workspace directory (`agent.workspace`) as the agent’s **only** working directory (`cwd`) for tools and context.
|
||||
You must set an agent home directory via `agent.workspace`. CLAWDBOT uses this as the agent’s **only** working directory (`cwd`) for tools and context.
|
||||
|
||||
Recommended: use `clawdbot setup` to create `~/.clawdbot/clawdbot.json` if missing and initialize the workspace files.
|
||||
|
||||
If `agent.sandbox` is enabled, non-main sessions can override this with
|
||||
per-session workspaces under `agent.sandbox.workspaceRoot` (see
|
||||
[`docs/configuration.md`](https://docs.clawd.bot/configuration)).
|
||||
`docs/configuration.md`).
|
||||
|
||||
## Bootstrap files (injected)
|
||||
|
||||
@@ -31,14 +31,6 @@ On the first turn of a new session, CLAWDBOT injects the contents of these files
|
||||
|
||||
If a file is missing, CLAWDBOT injects a single “missing file” marker line (and `clawdbot setup` will create a safe default template).
|
||||
|
||||
`BOOTSTRAP.md` is only created for a **brand new workspace** (no other bootstrap files present). If you delete it after completing the ritual, it should not be recreated on later restarts.
|
||||
|
||||
To disable bootstrap file creation entirely (for pre-seeded workspaces), set:
|
||||
|
||||
```json5
|
||||
{ agent: { skipBootstrap: true } }
|
||||
```
|
||||
|
||||
## Built-in tools (internal)
|
||||
|
||||
p’s embedded core tools (read/bash/edit/write and related internals) are defined in code and always available. `TOOLS.md` does **not** control which tools exist; it’s guidance for how *you* want them used.
|
||||
@@ -50,7 +42,7 @@ Clawdbot loads skills from three locations (workspace wins on name conflict):
|
||||
- Managed/local: `~/.clawdbot/skills`
|
||||
- Workspace: `<workspace>/skills`
|
||||
|
||||
Skills can be gated by config/env (see `skills` in [`docs/configuration.md`](https://docs.clawd.bot/configuration)).
|
||||
Skills can be gated by config/env (see `skills` in `docs/configuration.md`).
|
||||
|
||||
## p-mono integration
|
||||
|
||||
@@ -74,7 +66,7 @@ Apply these notes **only** when the user is Peter Steinberger at steipete.
|
||||
## Sessions
|
||||
|
||||
Session transcripts are stored as JSONL at:
|
||||
- `~/.clawdbot/agents/<agentId>/sessions/<SessionId>.jsonl`
|
||||
- `~/.clawdbot/sessions/<SessionId>.jsonl`
|
||||
|
||||
The session ID is stable and chosen by CLAWDBOT.
|
||||
Legacy Pi/Tau session folders are **not** read.
|
||||
@@ -89,7 +81,7 @@ message is injected before the next assistant response.
|
||||
|
||||
When queue mode is `followup` or `collect`, inbound messages are held until the
|
||||
current turn ends, then a new agent turn starts with the queued payloads. See
|
||||
[`docs/queue.md`](https://docs.clawd.bot/queue) for mode + debounce/cap behavior.
|
||||
`docs/queue.md` for mode + debounce/cap behavior.
|
||||
|
||||
Block streaming sends completed assistant blocks as soon as they finish; disable
|
||||
via `agent.blockStreamingDefault: "off"` if you only want the final response.
|
||||
@@ -107,4 +99,4 @@ At minimum, set:
|
||||
|
||||
---
|
||||
|
||||
*Next: [Group Chats](https://docs.clawd.bot/group-messages)* 🦞
|
||||
*Next: [Group Chats](./group-messages.md)* 🦞
|
||||
@@ -1,20 +1,18 @@
|
||||
---
|
||||
summary: "Android app (node): connection runbook + Canvas/Chat/Camera"
|
||||
summary: "Runbook: connect/pair the Android node to a Clawdbot Gateway and use Canvas/Chat/Camera"
|
||||
read_when:
|
||||
- Pairing or reconnecting the Android node
|
||||
- Debugging Android bridge discovery or auth
|
||||
- Verifying chat history parity across clients
|
||||
---
|
||||
|
||||
# Android App (Node)
|
||||
|
||||
## Connection Runbook
|
||||
# Android Node Connection Runbook
|
||||
|
||||
Android node app ⇄ (mDNS/NSD + TCP bridge) ⇄ **Gateway bridge** ⇄ (loopback WS) ⇄ **Gateway**
|
||||
|
||||
The Gateway WebSocket stays loopback-only (`ws://127.0.0.1:18789`). Android talks to the LAN-facing **bridge** (default `tcp://0.0.0.0:18790`) and uses Gateway-owned pairing.
|
||||
|
||||
### Prerequisites
|
||||
## Prerequisites
|
||||
|
||||
- You can run the Gateway on the “master” machine.
|
||||
- Android device/emulator can reach the gateway bridge:
|
||||
@@ -23,7 +21,7 @@ The Gateway WebSocket stays loopback-only (`ws://127.0.0.1:18789`). Android talk
|
||||
- Manual bridge host/port (fallback)
|
||||
- You can run the CLI (`clawdbot`) on the gateway machine (or via SSH).
|
||||
|
||||
### 1) Start the Gateway (with bridge enabled)
|
||||
## 1) Start the Gateway (with bridge enabled)
|
||||
|
||||
Bridge is enabled by default (disable via `CLAWDBOT_BRIDGE_ENABLED=0`).
|
||||
|
||||
@@ -39,7 +37,7 @@ For tailnet-only setups (recommended for Vienna ⇄ London), bind the bridge to
|
||||
- Set `bridge.bind: "tailnet"` in `~/.clawdbot/clawdbot.json` on the gateway host.
|
||||
- Restart the Gateway / macOS menubar app.
|
||||
|
||||
### 2) Verify discovery (optional)
|
||||
## 2) Verify discovery (optional)
|
||||
|
||||
From the gateway machine:
|
||||
|
||||
@@ -47,18 +45,18 @@ From the gateway machine:
|
||||
dns-sd -B _clawdbot-bridge._tcp local.
|
||||
```
|
||||
|
||||
More debugging notes: [`docs/bonjour.md`](https://docs.clawd.bot/bonjour).
|
||||
More debugging notes: `docs/bonjour.md`.
|
||||
|
||||
#### Tailnet (Vienna ⇄ London) discovery via unicast DNS-SD
|
||||
### Tailnet (Vienna ⇄ London) discovery via unicast DNS-SD
|
||||
|
||||
Android NSD/mDNS discovery won’t cross networks. If your Android node and the gateway are on different networks but connected via Tailscale, use Wide-Area Bonjour / unicast DNS-SD instead:
|
||||
|
||||
1) Set up a DNS-SD zone (example `clawdbot.internal.`) on the gateway host and publish `_clawdbot-bridge._tcp` records.
|
||||
2) Configure Tailscale split DNS for `clawdbot.internal` pointing at that DNS server.
|
||||
|
||||
Details and example CoreDNS config: [`docs/bonjour.md`](https://docs.clawd.bot/bonjour).
|
||||
Details and example CoreDNS config: `docs/bonjour.md`.
|
||||
|
||||
### 3) Connect from Android
|
||||
## 3) Connect from Android
|
||||
|
||||
In the Android app:
|
||||
|
||||
@@ -71,7 +69,7 @@ After the first successful pairing, Android auto-reconnects on launch:
|
||||
- Manual endpoint (if enabled), otherwise
|
||||
- The last discovered bridge (best-effort).
|
||||
|
||||
### 4) Approve pairing (CLI)
|
||||
## 4) Approve pairing (CLI)
|
||||
|
||||
On the gateway machine:
|
||||
|
||||
@@ -80,9 +78,9 @@ clawdbot nodes pending
|
||||
clawdbot nodes approve <requestId>
|
||||
```
|
||||
|
||||
Pairing details: [`docs/gateway/pairing.md`](https://docs.clawd.bot/gateway/pairing).
|
||||
Pairing details: `docs/gateway/pairing.md`.
|
||||
|
||||
### 5) Verify the node is connected
|
||||
## 5) Verify the node is connected
|
||||
|
||||
- Via nodes status:
|
||||
```bash
|
||||
@@ -93,7 +91,7 @@ Pairing details: [`docs/gateway/pairing.md`](https://docs.clawd.bot/gateway/pair
|
||||
clawdbot gateway call node.list --params "{}"
|
||||
```
|
||||
|
||||
### 6) Chat + history
|
||||
## 6) Chat + history
|
||||
|
||||
The Android node’s Chat sheet uses the gateway’s **primary session key** (`main`), so history and replies are shared with WebChat and other clients:
|
||||
|
||||
@@ -101,9 +99,9 @@ The Android node’s Chat sheet uses the gateway’s **primary session key** (`m
|
||||
- Send: `chat.send`
|
||||
- Push updates (best-effort): `chat.subscribe` → `event:"chat"`
|
||||
|
||||
### 7) Canvas + camera
|
||||
## 7) Canvas + camera
|
||||
|
||||
#### Gateway Canvas Host (recommended for web content)
|
||||
### Gateway Canvas Host (recommended for web content)
|
||||
|
||||
If you want the node to show real HTML/CSS/JS that the agent can edit on disk, point the node at the Gateway canvas host.
|
||||
|
||||
@@ -130,4 +128,4 @@ Camera commands (foreground only; permission-gated):
|
||||
- `camera.snap` (jpg)
|
||||
- `camera.clip` (mp4)
|
||||
|
||||
See [`docs/camera.md`](https://docs.clawd.bot/camera) for parameters and CLI helpers.
|
||||
See `docs/camera.md` for parameters and CLI helpers.
|
||||
+7
-32
@@ -1,52 +1,27 @@
|
||||
---
|
||||
summary: "WebSocket gateway architecture, components, and client flows"
|
||||
summary: "Target WebSocket gateway architecture, components, and client flows"
|
||||
read_when:
|
||||
- Working on gateway protocol, clients, or transports
|
||||
---
|
||||
# Gateway Architecture
|
||||
# Gateway Architecture (target state)
|
||||
|
||||
Last updated: 2026-01-05
|
||||
Last updated: 2025-12-09
|
||||
|
||||
## Overview
|
||||
- A single long-lived **Gateway** process owns all messaging surfaces (WhatsApp via Baileys, Telegram via grammY, Slack via Bolt, Discord via discord.js, Signal via signal-cli, iMessage via imsg, WebChat) and the control/event plane.
|
||||
- All clients (macOS app, CLI, web UI, automations) connect to the Gateway over one transport: **WebSocket on the configured bind host** (default `127.0.0.1:18789`; tunnel or VPN for remote).
|
||||
- A single long-lived **Gateway** process owns all messaging surfaces (WhatsApp via Baileys, Telegram via grammY, Discord via discord.js) and the control/event plane.
|
||||
- All clients (macOS app, CLI, web UI, automations) connect to the Gateway over one transport: **WebSocket on 127.0.0.1:18789** (tunnel or VPN for remote).
|
||||
- One Gateway per host; it is the only place that is allowed to open a WhatsApp session. All sends/agent runs go through it.
|
||||
- By default: the Gateway exposes a Canvas host on `canvasHost.port` (default `18793`), serving `~/clawd/canvas` at `/__clawdbot__/canvas/` with live-reload; disable via `canvasHost.enabled=false` or `CLAWDBOT_SKIP_CANVAS_HOST=1`.
|
||||
|
||||
## Implementation snapshot (current code)
|
||||
|
||||
### TypeScript Gateway ([`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server.ts))
|
||||
- Single HTTP + WebSocket server (default `18789`); bind policy `loopback|lan|tailnet|auto`. Refuses non-loopback binds without auth; Tailscale serve/funnel requires loopback.
|
||||
- Handshake: first frame must be a `connect` request; AJV validates request + params against TypeBox schemas; protocol negotiated via `minProtocol`/`maxProtocol`.
|
||||
- `hello-ok` includes snapshot (presence/health/stateVersion/uptime/configPath/stateDir), features (methods/events), policy (max payload/buffer/tick), and `canvasHostUrl` when available.
|
||||
- Events emitted: `agent`, `chat`, `presence`, `tick`, `health`, `heartbeat`, `cron`, `talk.mode`, `node.pair.requested`, `node.pair.resolved`, `voicewake.changed`, `shutdown`.
|
||||
- Idempotency keys are required for `send`, `agent`, `chat.send`, and node invokes; the dedupe cache avoids double-sends on reconnects. Payload sizes are capped per connection.
|
||||
- Optional node bridge ([`src/infra/bridge/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/bridge/server.ts)): TCP JSONL frames (`hello`, `pair-request`, `req/res`, `event`, `invoke`, `ping`). Node connect/disconnect updates presence and flows into the session bus.
|
||||
- Control UI + Canvas host: HTTP serves Control UI (base path configurable) and can host the A2UI canvas via [`src/canvas-host/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/canvas-host/server.ts) (live reload). Canvas host URL is advertised to nodes + clients.
|
||||
|
||||
### iOS node (`apps/ios`)
|
||||
- Discovery + pairing: `BridgeDiscoveryModel` uses `NWBrowser` Bonjour discovery and reads TXT fields for LAN/tailnet host hints plus gateway/bridge/canvas ports.
|
||||
- Auto-connect: `BridgeConnectionController` uses stored `node.instanceId` + Keychain token; supports manual host/port; performs `pair-and-hello`.
|
||||
- Bridge runtime: `BridgeSession` actor owns an `NWConnection`, JSONL frames, `hello`/`hello-ok`, ping/pong, `req/res`, server `event`s, and `invoke` callbacks; stores `canvasHostUrl`.
|
||||
- Commands: `NodeAppModel` executes `canvas.*`, `canvas.a2ui.*`, `camera.*`, `screen.record`, `location.get`. Canvas/camera/screen are blocked when backgrounded.
|
||||
- Canvas + actions: `WKWebView` with A2UI action bridge; accepts actions from local-network or trusted file URLs; intercepts `clawdbot://` deep links and forwards `agent.request` to the bridge.
|
||||
- Voice/talk: voice wake sends `voice.transcript` events and syncs triggers via `voicewake.get` + `voicewake.changed`; Talk Mode attaches to the bridge.
|
||||
|
||||
### Android node (`apps/android`)
|
||||
- Discovery + pairing: `BridgeDiscovery` uses mDNS/NSD to find `_clawdbot-bridge._tcp`, with manual host/port fallback.
|
||||
- Auto-connect: `NodeRuntime` restores a stored token, performs `pair-and-hello`, and reconnects to the last discovered or manual bridge.
|
||||
- Bridge runtime: `BridgeSession` owns the TCP JSONL session (`hello`/`hello-ok`, ping/pong, `req/res`, `event`, `invoke`); stores `canvasHostUrl`.
|
||||
- Commands: `NodeRuntime` executes `canvas.*`, `canvas.a2ui.*`, `camera.*`, and chat/session events; foreground-only for canvas/camera.
|
||||
|
||||
## Components and flows
|
||||
- **Gateway (daemon)**
|
||||
- Maintains WhatsApp (Baileys), Telegram (grammY), Slack (Bolt), Discord (discord.js), Signal (signal-cli), and iMessage (imsg) connections.
|
||||
- Maintains Baileys/Telegram/Discord connections.
|
||||
- Exposes a typed WS API (req/resp + server push events).
|
||||
- Validates every inbound frame against JSON Schema; rejects anything before a mandatory `connect`.
|
||||
- **Clients (mac app / CLI / web admin)**
|
||||
- One WS connection per client.
|
||||
- Send requests (`health`, `status`, `send`, `agent`, `system-presence`, toggles) and subscribe to events (`tick`, `agent`, `presence`, `shutdown`).
|
||||
- On macOS, the app can also be invoked via deep links (`clawdbot://agent?...`) which translate into the same Gateway `agent` request path (see [`docs/macos.md`](https://docs.clawd.bot/macos)).
|
||||
- On macOS, the app can also be invoked via deep links (`clawdbot://agent?...`) which translate into the same Gateway `agent` request path (see `docs/clawdbot-mac.md`).
|
||||
- **Agent process (Pi)**
|
||||
- Spawned by the Gateway on demand for `agent` calls; streams events back over the same WS connection.
|
||||
- **WebChat**
|
||||
|
||||
+62
-35
@@ -29,40 +29,40 @@ html[data-theme="auto"] {
|
||||
}
|
||||
|
||||
html[data-theme="dark"] {
|
||||
--bg0: #0b1a22;
|
||||
--bg1: #0a1720;
|
||||
--panel: #0e231f;
|
||||
--panel2: #102a24;
|
||||
--text: #c9eadc;
|
||||
--muted: #8ab8aa;
|
||||
--faint: #699b8d;
|
||||
--link: #6fe8c7;
|
||||
--link2: #ff7b63;
|
||||
--bg0: #06141f;
|
||||
--bg1: #031019;
|
||||
--panel: #061a16;
|
||||
--panel2: #071f19;
|
||||
--text: #d6f6ea;
|
||||
--muted: #95c9b9;
|
||||
--faint: #66a391;
|
||||
--link: #79ffd0;
|
||||
--link2: #ff775f;
|
||||
--accent: #ff4f40;
|
||||
--accent2: #5fdfa2;
|
||||
--frame-border: #6fbfa8;
|
||||
--code-bg: #091814;
|
||||
--code-fg: #d7f5e8;
|
||||
--code-accent: #5fdfa2;
|
||||
--accent2: #67ff9b;
|
||||
--frame-border: #b7ffe6;
|
||||
--code-bg: #04110d;
|
||||
--code-fg: #dcfff1;
|
||||
--code-accent: #67ff9b;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html[data-theme="auto"] {
|
||||
--bg0: #0b1a22;
|
||||
--bg1: #0a1720;
|
||||
--panel: #0e231f;
|
||||
--panel2: #102a24;
|
||||
--text: #c9eadc;
|
||||
--muted: #8ab8aa;
|
||||
--faint: #699b8d;
|
||||
--link: #6fe8c7;
|
||||
--link2: #ff7b63;
|
||||
--bg0: #06141f;
|
||||
--bg1: #031019;
|
||||
--panel: #061a16;
|
||||
--panel2: #071f19;
|
||||
--text: #d6f6ea;
|
||||
--muted: #95c9b9;
|
||||
--faint: #66a391;
|
||||
--link: #79ffd0;
|
||||
--link2: #ff775f;
|
||||
--accent: #ff4f40;
|
||||
--accent2: #5fdfa2;
|
||||
--frame-border: #6fbfa8;
|
||||
--code-bg: #091814;
|
||||
--code-fg: #d7f5e8;
|
||||
--code-accent: #5fdfa2;
|
||||
--accent2: #67ff9b;
|
||||
--frame-border: #b7ffe6;
|
||||
--code-bg: #04110d;
|
||||
--code-fg: #dcfff1;
|
||||
--code-accent: #67ff9b;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,12 +70,9 @@ html[data-theme="dark"] {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -90,9 +87,39 @@ body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body::before,
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
opacity: 0.45;
|
||||
background-image:
|
||||
linear-gradient(to right, color-mix(in oklab, var(--text) 10%, transparent) 1px, transparent 1px),
|
||||
linear-gradient(to bottom, color-mix(in oklab, var(--text) 10%, transparent) 1px, transparent 1px);
|
||||
background-size: 28px 28px;
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
||||
body::after {
|
||||
display: none;
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
rgba(0, 0, 0, var(--scanline-opacity)),
|
||||
rgba(0, 0, 0, var(--scanline-opacity)) 1px,
|
||||
transparent 1px,
|
||||
transparent var(--scanline-size)
|
||||
);
|
||||
opacity: 0.8;
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
body::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
|
||||
+6
-6
@@ -69,7 +69,7 @@ The bridge port (default `18790`) is a plain TCP service. By default it binds to
|
||||
For a tailnet-only setup, bind it to the Tailscale IP instead:
|
||||
|
||||
- Set `bridge.bind: "tailnet"` in `~/.clawdbot/clawdbot.json`.
|
||||
- Restart the Gateway (or restart the macOS menubar app via [`./scripts/restart-mac.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/restart-mac.sh) on that machine).
|
||||
- Restart the Gateway (or restart the macOS menubar app via `./scripts/restart-mac.sh` on that machine).
|
||||
|
||||
This keeps the bridge reachable only from devices on your tailnet (while still listening on loopback for local/SSH port-forwards).
|
||||
|
||||
@@ -77,8 +77,8 @@ This keeps the bridge reachable only from devices on your tailnet (while still l
|
||||
|
||||
Only the **Node Gateway** (`clawd` / `clawdbot gateway`) advertises Bonjour beacons.
|
||||
|
||||
- Implementation: [`src/infra/bonjour.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/bonjour.ts)
|
||||
- Gateway wiring: [`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server.ts)
|
||||
- Implementation: `src/infra/bonjour.ts`
|
||||
- Gateway wiring: `src/gateway/server.ts`
|
||||
|
||||
## Service types
|
||||
|
||||
@@ -136,7 +136,7 @@ The log includes browser state transitions (`ready`, `waiting`, `failed`, `cance
|
||||
- **Sleep / interface churn**: macOS may temporarily drop mDNS results when switching networks; retry.
|
||||
- **Browse works but resolve fails (iOS “NoSuchRecord”)**: make sure the advertiser publishes a valid SRV target hostname.
|
||||
- Implementation detail: `@homebridge/ciao` defaults `hostname` to the *service instance name* when `hostname` is omitted. If your instance name contains spaces/parentheses, some resolvers can fail to resolve the implied A/AAAA record.
|
||||
- Fix: set an explicit DNS-safe `hostname` (single label; no `.local`) in [`src/infra/bonjour.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/bonjour.ts).
|
||||
- Fix: set an explicit DNS-safe `hostname` (single label; no `.local`) in `src/infra/bonjour.ts`.
|
||||
|
||||
## Escaped instance names (`\\032`)
|
||||
Bonjour/DNS-SD often escapes bytes in service instance names as decimal `\\DDD` sequences (e.g. spaces become `\\032`).
|
||||
@@ -155,5 +155,5 @@ Bonjour/DNS-SD often escapes bytes in service instance names as decimal `\\DDD`
|
||||
|
||||
## Related docs
|
||||
|
||||
- Discovery policy and transport selection: [`docs/discovery.md`](https://docs.clawd.bot/discovery)
|
||||
- Node pairing + approvals: [`docs/gateway/pairing.md`](https://docs.clawd.bot/gateway/pairing)
|
||||
- Discovery policy and transport selection: `docs/discovery.md`
|
||||
- Node pairing + approvals: `docs/gateway/pairing.md`
|
||||
@@ -1,114 +0,0 @@
|
||||
---
|
||||
summary: "Fix Chrome/Chromium CDP startup issues for Clawdbot browser control on Linux"
|
||||
read_when: "Browser control fails on Linux, especially with snap Chromium"
|
||||
---
|
||||
|
||||
# Browser Troubleshooting (Linux)
|
||||
|
||||
## Problem: "Failed to start Chrome CDP on port 18800"
|
||||
|
||||
Clawdbot's browser control server fails to launch Chrome/Chromium with the error:
|
||||
```
|
||||
{"error":"Error: Failed to start Chrome CDP on port 18800 for profile \"clawd\"."}
|
||||
```
|
||||
|
||||
### Root Cause
|
||||
|
||||
On Ubuntu (and many Linux distros), the default Chromium installation is a **snap package**. Snap's AppArmor confinement interferes with how Clawdbot spawns and monitors the browser process.
|
||||
|
||||
The `apt install chromium` command installs a stub package that redirects to snap:
|
||||
```
|
||||
Note, selecting 'chromium-browser' instead of 'chromium'
|
||||
chromium-browser is already the newest version (2:1snap1-0ubuntu2).
|
||||
```
|
||||
|
||||
This is NOT a real browser — it's just a wrapper.
|
||||
|
||||
### Solution 1: Install Google Chrome (Recommended)
|
||||
|
||||
Install the official Google Chrome `.deb` package, which is not sandboxed by snap:
|
||||
|
||||
```bash
|
||||
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
||||
sudo dpkg -i google-chrome-stable_current_amd64.deb
|
||||
sudo apt --fix-broken install -y # if there are dependency errors
|
||||
```
|
||||
|
||||
Then update your Clawdbot config (`~/.clawdbot/clawdbot.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"browser": {
|
||||
"enabled": true,
|
||||
"executablePath": "/usr/bin/google-chrome-stable",
|
||||
"headless": true,
|
||||
"noSandbox": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Solution 2: Use Snap Chromium with Attach-Only Mode
|
||||
|
||||
If you must use snap Chromium, configure Clawdbot to attach to a manually-started browser:
|
||||
|
||||
1. Update config:
|
||||
```json
|
||||
{
|
||||
"browser": {
|
||||
"enabled": true,
|
||||
"attachOnly": true,
|
||||
"headless": true,
|
||||
"noSandbox": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Start Chromium manually:
|
||||
```bash
|
||||
chromium-browser --headless --no-sandbox --disable-gpu \
|
||||
--remote-debugging-port=18800 \
|
||||
--user-data-dir=$HOME/.clawdbot/browser/clawd/user-data \
|
||||
about:blank &
|
||||
```
|
||||
|
||||
3. Optionally create a systemd user service to auto-start Chrome:
|
||||
```ini
|
||||
# ~/.config/systemd/user/clawd-browser.service
|
||||
[Unit]
|
||||
Description=Clawd Browser (Chrome CDP)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/snap/bin/chromium --headless --no-sandbox --disable-gpu --remote-debugging-port=18800 --user-data-dir=%h/.clawdbot/browser/clawd/user-data about:blank
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
Enable with: `systemctl --user enable --now clawd-browser.service`
|
||||
|
||||
### Verifying the Browser Works
|
||||
|
||||
Check status:
|
||||
```bash
|
||||
curl -s http://127.0.0.1:18791/ | jq '{running, pid, chosenBrowser}'
|
||||
```
|
||||
|
||||
Test browsing:
|
||||
```bash
|
||||
curl -s -X POST http://127.0.0.1:18791/start
|
||||
curl -s http://127.0.0.1:18791/tabs
|
||||
```
|
||||
|
||||
### Config Reference
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `browser.enabled` | Enable browser control | `true` |
|
||||
| `browser.executablePath` | Path to Chrome/Chromium binary | auto-detected |
|
||||
| `browser.headless` | Run without GUI | `false` |
|
||||
| `browser.noSandbox` | Add `--no-sandbox` flag (needed for some Linux setups) | `false` |
|
||||
| `browser.attachOnly` | Don't launch browser, only attach to existing | `false` |
|
||||
| `browser.cdpPort` | Chrome DevTools Protocol port | `18800` |
|
||||
+1
-21
@@ -189,26 +189,10 @@ All existing endpoints accept optional `?profile=<name>` query parameter:
|
||||
- `GET /?profile=work` — status for work profile
|
||||
- `POST /start?profile=work` — start work profile browser
|
||||
- `GET /tabs?profile=work` — list tabs for work profile
|
||||
- `POST /tabs/open?profile=work` — open tab in work profile
|
||||
- etc.
|
||||
|
||||
When `profile` is omitted, uses `browser.defaultProfile` (defaults to "clawd").
|
||||
|
||||
### Agent browser tool
|
||||
|
||||
The `browser` tool accepts an optional `profile` parameter for all actions:
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "open",
|
||||
"targetUrl": "https://example.com",
|
||||
"profile": "work"
|
||||
}
|
||||
```
|
||||
|
||||
This routes the operation to the specified profile's browser instance. Omitting
|
||||
`profile` uses the default profile.
|
||||
|
||||
### Profile naming rules
|
||||
|
||||
- Lowercase alphanumeric characters and hyphens only
|
||||
@@ -237,7 +221,7 @@ The agent should not assume tabs are ephemeral. It should:
|
||||
|
||||
## CLI quick reference (one example each)
|
||||
|
||||
All commands accept `--browser-profile <name>` to target a specific profile (default: `clawd`).
|
||||
All commands accept `--profile <name>` to target a specific profile (default: `clawd`).
|
||||
|
||||
Profile management:
|
||||
- `clawdbot browser profiles`
|
||||
@@ -303,7 +287,3 @@ Notes:
|
||||
- Sharing the user's logged-in Chrome sessions automatically.
|
||||
- General-purpose web scraping; this is primarily for "close-the-loop" verification
|
||||
and interaction.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
For Linux-specific issues (especially Ubuntu with snap Chromium), see [browser-linux-troubleshooting](https://docs.clawd.bot/browser-linux-troubleshooting).
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
---
|
||||
summary: "Bun workflow (preferred): installs, patches, and gotchas vs pnpm"
|
||||
read_when:
|
||||
- You want the fastest local dev loop (bun + watch)
|
||||
- You hit Bun install/patch/lifecycle script issues
|
||||
---
|
||||
|
||||
# Bun
|
||||
|
||||
Goal: run this repo with **Bun** (preferred) without losing pnpm patch behavior.
|
||||
|
||||
## Status
|
||||
|
||||
- Bun is the preferred local runtime for running TypeScript directly (`bun run …`, `bun --watch …`).
|
||||
- `pnpm` is still fully supported (and used by some docs tooling).
|
||||
- Bun cannot use `pnpm-lock.yaml` and will ignore it.
|
||||
|
||||
## Install
|
||||
|
||||
Default:
|
||||
|
||||
```sh
|
||||
bun install
|
||||
```
|
||||
|
||||
Note: `bun.lock`/`bun.lockb` are gitignored, so there’s no repo churn either way. If you want *no lockfile writes*:
|
||||
|
||||
```sh
|
||||
bun install --no-save
|
||||
```
|
||||
|
||||
## Build / Test (Bun)
|
||||
|
||||
```sh
|
||||
bun run build
|
||||
bun run vitest run
|
||||
```
|
||||
|
||||
## pnpm patchedDependencies under Bun
|
||||
|
||||
pnpm supports `package.json#pnpm.patchedDependencies` and records it in `pnpm-lock.yaml`.
|
||||
Bun does not support pnpm patches, so we apply them in `postinstall` when Bun is detected:
|
||||
|
||||
- [`scripts/postinstall.js`](https://github.com/clawdbot/clawdbot/blob/main/scripts/postinstall.js) runs only for Bun installs and applies every entry from `package.json#pnpm.patchedDependencies` into `node_modules/...` using `git apply` (idempotent).
|
||||
|
||||
To add a new patch that works in both pnpm + Bun:
|
||||
|
||||
1. Add an entry to `package.json#pnpm.patchedDependencies`
|
||||
2. Add the patch file under `patches/`
|
||||
3. Run `pnpm install` (updates `pnpm-lock.yaml` patch hash)
|
||||
|
||||
## Bun lifecycle scripts (blocked by default)
|
||||
|
||||
Bun may block dependency lifecycle scripts unless explicitly trusted (`bun pm untrusted` / `bun pm trust`).
|
||||
For this repo, the commonly blocked scripts are not required:
|
||||
|
||||
- `@whiskeysockets/baileys` `preinstall`: checks Node major >= 20 (we run Node 22+).
|
||||
- `protobufjs` `postinstall`: emits warnings about incompatible version schemes (no build artifacts).
|
||||
|
||||
If you hit a real runtime issue that requires these scripts, trust them explicitly:
|
||||
|
||||
```sh
|
||||
bun pm trust @whiskeysockets/baileys protobufjs
|
||||
```
|
||||
|
||||
## Caveats
|
||||
|
||||
- Some scripts still hardcode pnpm (e.g. `docs:build`, `ui:*`, `protocol:check`). Run those via pnpm for now.
|
||||
+12
-34
@@ -18,7 +18,7 @@ You’re putting an agent in a position to:
|
||||
Start conservative:
|
||||
- Always set `whatsapp.allowFrom` (never run open-to-the-world on your personal Mac).
|
||||
- Use a dedicated WhatsApp number for the assistant.
|
||||
- Heartbeats now default to every 30 minutes. Disable until you trust the setup by setting `agent.heartbeat.every: "0m"`.
|
||||
- Keep heartbeats disabled until you trust the setup (omit `agent.heartbeat` or set `agent.heartbeat.every: "0m"`).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -69,11 +69,6 @@ clawdbot login
|
||||
clawdbot gateway --port 18789
|
||||
```
|
||||
|
||||
Optional (supervised, always-on):
|
||||
```bash
|
||||
clawdbot gateway install
|
||||
```
|
||||
|
||||
3) Put a minimal config in `~/.clawdbot/clawdbot.json`:
|
||||
|
||||
```json5
|
||||
@@ -86,11 +81,11 @@ clawdbot gateway install
|
||||
|
||||
Now message the assistant number from your allowlisted phone.
|
||||
|
||||
## Give the agent a workspace (AGENTS)
|
||||
## Give the agent a workspace (AGENTS.md)
|
||||
|
||||
Clawd reads operating instructions and “memory” from its workspace directory.
|
||||
|
||||
By default, Clawdbot uses `~/clawd` as the agent workspace, and will create it (plus starter `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`) automatically on setup/first agent run. `BOOTSTRAP.md` is only created when the workspace is brand new (it should not come back after you delete it).
|
||||
By default, Clawdbot uses `~/clawd` as the agent workspace, and will create it (plus starter `AGENTS.md`, `SOUL.md`, `TOOLS.md`) automatically on setup/first agent run.
|
||||
|
||||
Tip: treat this folder like Clawd’s “memory” and make it a git repo (ideally private) so your `AGENTS.md` + memory files are backed up.
|
||||
|
||||
@@ -108,16 +103,6 @@ Optional: choose a different workspace with `agent.workspace` (supports `~`).
|
||||
}
|
||||
```
|
||||
|
||||
If you already ship your own workspace files from a repo, you can disable bootstrap file creation entirely:
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
skipBootstrap: true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## The config that turns it into “an assistant”
|
||||
|
||||
CLAWDBOT defaults to a good assistant setup, but you’ll usually want to tune:
|
||||
@@ -159,18 +144,15 @@ Example:
|
||||
|
||||
## Sessions and memory
|
||||
|
||||
- Session files: `~/.clawdbot/agents/<agentId>/sessions/{{SessionId}}.jsonl`
|
||||
- Session metadata (token usage, last route, etc): `~/.clawdbot/agents/<agentId>/sessions/sessions.json` (legacy: `~/.clawdbot/sessions/sessions.json`)
|
||||
- Session files: `~/.clawdbot/sessions/{{SessionId}}.jsonl`
|
||||
- Session metadata (token usage, last route, etc): `~/.clawdbot/sessions/sessions.json` (legacy: `~/.clawdbot/sessions.json`)
|
||||
- `/new` or `/reset` starts a fresh session for that chat (configurable via `resetTriggers`). If sent alone, the agent replies with a short hello to confirm the reset.
|
||||
- `/compact [instructions]` compacts the session context and reports the remaining context budget.
|
||||
|
||||
## Heartbeats (proactive mode)
|
||||
|
||||
By default, CLAWDBOT runs a heartbeat every 30 minutes with the prompt:
|
||||
`Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.`
|
||||
Set `agent.heartbeat.every: "0m"` to disable.
|
||||
When `agent.heartbeat.every` is set to a positive interval, CLAWDBOT periodically runs a heartbeat prompt (default: `HEARTBEAT`).
|
||||
|
||||
- If the agent replies with `HEARTBEAT_OK` (optionally with short padding; see `agent.heartbeat.ackMaxChars`), CLAWDBOT suppresses outbound delivery for that heartbeat.
|
||||
- If the agent replies with `HEARTBEAT_OK` (exact token), CLAWDBOT suppresses outbound delivery for that heartbeat.
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -208,12 +190,8 @@ Logs live under `/tmp/clawdbot/` (default: `clawdbot-YYYY-MM-DD.log`).
|
||||
|
||||
## Next steps
|
||||
|
||||
- WebChat: [WebChat](https://docs.clawd.bot/webchat)
|
||||
- Gateway ops: [Gateway runbook](https://docs.clawd.bot/gateway)
|
||||
- Cron + wakeups: [Cron + wakeups](https://docs.clawd.bot/cron)
|
||||
- macOS menu bar companion: [Clawdbot macOS app](https://docs.clawd.bot/macos)
|
||||
- iOS node app: [iOS app](https://docs.clawd.bot/ios)
|
||||
- Android node app: [Android app](https://docs.clawd.bot/android)
|
||||
- Windows status: [Windows app](https://docs.clawd.bot/windows)
|
||||
- Linux status: [Linux app](https://docs.clawd.bot/linux)
|
||||
- Security: [Security](https://docs.clawd.bot/security)
|
||||
- WebChat: [WebChat](./webchat.md)
|
||||
- Gateway ops: [Gateway runbook](./gateway.md)
|
||||
- Cron + wakeups: [Cron + wakeups](./cron.md)
|
||||
- macOS menu bar companion: [Clawdbot macOS app](./clawdbot-mac.md)
|
||||
- Security: [Security](./security.md)
|
||||
@@ -13,7 +13,7 @@ Author: steipete · Status: draft spec · Date: 2025-12-20
|
||||
- Shows native notifications for Clawdbot/clawdbot events.
|
||||
- Owns TCC prompts (Notifications, Accessibility, Screen Recording, Automation/AppleScript, Microphone, Speech Recognition).
|
||||
- Runs (or connects to) the **Gateway** and exposes itself as a **node** so agents can reach macOS‑only features.
|
||||
- Hosts **PeekabooBridge** for UI automation (consumed by `peekaboo`; see [`docs/mac/peekaboo.md`](https://docs.clawd.bot/mac/peekaboo)).
|
||||
- Hosts **PeekabooBridge** for UI automation (consumed by `peekaboo`; see `docs/mac/peekaboo.md`).
|
||||
- Installs a single CLI (`clawdbot`) by symlinking the bundled binary.
|
||||
|
||||
## High-level design
|
||||
@@ -79,7 +79,7 @@ Query parameters:
|
||||
- `sessionKey` (optional): explicit session key to use.
|
||||
- `thinking` (optional): thinking hint (e.g. `low`; omit for default).
|
||||
- `deliver` (optional): `true|false` (default: false).
|
||||
- `to` / `provider` (optional): forwarded to the Gateway `agent` method (only meaningful with `deliver=true`).
|
||||
- `to` / `channel` (optional): forwarded to the Gateway `agent` method (only meaningful with `deliver=true`).
|
||||
- `timeoutSeconds` (optional): timeout hint forwarded to the Gateway.
|
||||
- `key` (optional): unattended mode key (see below).
|
||||
|
||||
@@ -94,9 +94,9 @@ Notes:
|
||||
- In remote mode, Clawdbot will use the configured remote tunnel/endpoint.
|
||||
|
||||
## Build & dev workflow (native)
|
||||
- `cd apps/macos && swift build` (debug) / `swift build -c release`.
|
||||
- `cd native && swift build` (debug) / `swift build -c release`.
|
||||
- Run app for dev: `swift run Clawdbot` (or Xcode scheme).
|
||||
- Package app + CLI: [`scripts/package-mac-app.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/package-mac-app.sh) (builds bun CLI + gateway).
|
||||
- Package app + CLI: `scripts/package-mac-app.sh` (builds bun CLI + gateway).
|
||||
- Tests: add Swift Testing suites under `apps/macos/Tests`.
|
||||
|
||||
## Open questions / decisions
|
||||
+86
-330
@@ -9,7 +9,7 @@ CLAWDBOT reads an optional **JSON5** config from `~/.clawdbot/clawdbot.json` (co
|
||||
|
||||
If the file is missing, CLAWDBOT uses safe-ish defaults (embedded Pi agent + per-sender sessions + workspace `~/clawd`). You usually only need a config to:
|
||||
- restrict who can trigger the bot (`whatsapp.allowFrom`, `telegram.allowFrom`, etc.)
|
||||
- control group allowlists + mention behavior (`whatsapp.groups`, `telegram.groups`, `discord.guilds`, `routing.groupChat`)
|
||||
- control group mention behavior (`whatsapp.groups`, `telegram.groups`, `discord.guilds`, `routing.groupChat`)
|
||||
- customize message prefixes (`messages`)
|
||||
- set the agent's workspace (`agent.workspace`)
|
||||
- tune the embedded agent (`agent`) and session behavior (`session`)
|
||||
@@ -91,51 +91,26 @@ Env var equivalent:
|
||||
|
||||
### Auth storage (OAuth + API keys)
|
||||
|
||||
Clawdbot stores **per-agent** auth profiles (OAuth + API keys) in:
|
||||
- `<agentDir>/auth-profiles.json` (default: `~/.clawdbot/agents/<agentId>/agent/auth-profiles.json`)
|
||||
Clawdbot keeps subscription OAuth tokens + API keys in the **agent auth store**:
|
||||
- `~/.clawdbot/agent/auth.json`
|
||||
|
||||
Legacy OAuth imports:
|
||||
- `~/.clawdbot/credentials/oauth.json` (or `$CLAWDBOT_STATE_DIR/credentials/oauth.json`)
|
||||
The agent directory can be overridden with:
|
||||
- `CLAWDBOT_AGENT_DIR` (preferred)
|
||||
- `PI_CODING_AGENT_DIR` (legacy)
|
||||
|
||||
The embedded Pi agent maintains a runtime cache at:
|
||||
- `<agentDir>/auth.json` (managed automatically; don’t edit manually)
|
||||
Legacy OAuth storage is still supported for migration:
|
||||
- Default: `~/.clawdbot/credentials/oauth.json` (or `$CLAWDBOT_STATE_DIR/credentials/oauth.json`)
|
||||
- Override: `CLAWDBOT_OAUTH_DIR`
|
||||
|
||||
Legacy agent dir (pre multi-agent):
|
||||
- `~/.clawdbot/agent/*` (migrated by `clawdbot doctor` into `~/.clawdbot/agents/<defaultAgentId>/agent/*`)
|
||||
|
||||
Overrides:
|
||||
- OAuth dir (legacy import only): `CLAWDBOT_OAUTH_DIR`
|
||||
- Agent dir (default agent root override): `CLAWDBOT_AGENT_DIR` (preferred), `PI_CODING_AGENT_DIR` (legacy)
|
||||
|
||||
On first use, Clawdbot imports `oauth.json` entries into `auth-profiles.json`.
|
||||
|
||||
### `auth`
|
||||
|
||||
Optional metadata for auth profiles. This does **not** store secrets; it maps
|
||||
profile IDs to a provider + mode (and optional email) and defines the provider
|
||||
rotation order used for failover.
|
||||
|
||||
```json5
|
||||
{
|
||||
auth: {
|
||||
profiles: {
|
||||
"anthropic:default": { provider: "anthropic", mode: "oauth", email: "me@example.com" },
|
||||
"anthropic:work": { provider: "anthropic", mode: "api_key" }
|
||||
},
|
||||
order: {
|
||||
anthropic: ["anthropic:default", "anthropic:work"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
On first use, Clawdbot auto‑migrates legacy `oauth.json` entries into `auth.json`.
|
||||
|
||||
### `identity`
|
||||
|
||||
Optional agent identity used for defaults and UX. This is written by the macOS onboarding assistant.
|
||||
|
||||
If set, CLAWDBOT derives defaults (only when you haven’t set them explicitly):
|
||||
- `messages.ackReaction` from `identity.emoji` (falls back to 👀)
|
||||
- `routing.groupChat.mentionPatterns` from `identity.name` (so “@Samantha” works in groups across Telegram/Slack/Discord/iMessage/WhatsApp)
|
||||
- `messages.responsePrefix` from `identity.emoji`
|
||||
- `routing.groupChat.mentionPatterns` from `identity.name` (so “@Samantha” works in groups)
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -166,9 +141,6 @@ Metadata written by CLI wizards (`onboard`, `configure`, `doctor`, `update`).
|
||||
- Console output can be tuned separately via:
|
||||
- `logging.consoleLevel` (defaults to `info`, bumps to `debug` when `--verbose`)
|
||||
- `logging.consoleStyle` (`pretty` | `compact` | `json`)
|
||||
- Tool summaries can be redacted to avoid leaking secrets:
|
||||
- `logging.redactSensitive` (`off` | `tools`, default: `tools`)
|
||||
- `logging.redactPatterns` (array of regex strings; overrides defaults)
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -176,68 +148,25 @@ Metadata written by CLI wizards (`onboard`, `configure`, `doctor`, `update`).
|
||||
level: "info",
|
||||
file: "/tmp/clawdbot/clawdbot.log",
|
||||
consoleLevel: "info",
|
||||
consoleStyle: "pretty",
|
||||
redactSensitive: "tools",
|
||||
redactPatterns: [
|
||||
// Example: override defaults with your own rules.
|
||||
"\\bTOKEN\\b\\s*[=:]\\s*([\"']?)([^\\s\"']+)\\1",
|
||||
"/\\bsk-[A-Za-z0-9_-]{8,}\\b/gi"
|
||||
]
|
||||
consoleStyle: "pretty"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `whatsapp.dmPolicy`
|
||||
|
||||
Controls how WhatsApp direct chats (DMs) are handled:
|
||||
- `"pairing"` (default): unknown senders get a pairing code; owner must approve
|
||||
- `"allowlist"`: only allow senders in `whatsapp.allowFrom` (or paired allow store)
|
||||
- `"open"`: allow all inbound DMs (**requires** `whatsapp.allowFrom` to include `"*"`)
|
||||
- `"disabled"`: ignore all inbound DMs
|
||||
|
||||
Pairing approvals:
|
||||
- `clawdbot pairing list --provider whatsapp`
|
||||
- `clawdbot pairing approve --provider whatsapp <code>`
|
||||
|
||||
### `whatsapp.allowFrom`
|
||||
|
||||
Allowlist of E.164 phone numbers that may trigger WhatsApp auto-replies (**DMs only**).
|
||||
If empty and `whatsapp.dmPolicy="pairing"`, unknown senders will receive a pairing code.
|
||||
For groups, use `whatsapp.groupPolicy` + `whatsapp.groupAllowFrom`.
|
||||
Allowlist of E.164 phone numbers that may trigger WhatsApp auto-replies (DMs only).
|
||||
If empty, the default allowlist is your own WhatsApp number (self-chat mode).
|
||||
|
||||
```json5
|
||||
{
|
||||
whatsapp: {
|
||||
dmPolicy: "pairing", // pairing | allowlist | open | disabled
|
||||
allowFrom: ["+15555550123", "+447700900123"],
|
||||
textChunkLimit: 4000 // optional outbound chunk size (chars)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `whatsapp.accounts` (multi-account)
|
||||
|
||||
Run multiple WhatsApp accounts in one gateway:
|
||||
|
||||
```json5
|
||||
{
|
||||
whatsapp: {
|
||||
accounts: {
|
||||
default: {}, // optional; keeps the default id stable
|
||||
personal: {},
|
||||
biz: {
|
||||
// Optional override. Default: ~/.clawdbot/credentials/whatsapp/biz
|
||||
// authDir: "~/.clawdbot/credentials/whatsapp/biz",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Outbound commands default to account `default` if present; otherwise the first configured account id (sorted).
|
||||
- The legacy single-account Baileys auth dir is migrated by `clawdbot doctor` into `whatsapp/default`.
|
||||
|
||||
### `routing.groupChat`
|
||||
|
||||
Group messages default to **require mention** (either metadata mention or regex patterns). Applies to WhatsApp, Telegram, Discord, and iMessage group chats.
|
||||
@@ -245,7 +174,6 @@ Group messages default to **require mention** (either metadata mention or regex
|
||||
**Mention types:**
|
||||
- **Metadata mentions**: Native platform @-mentions (e.g., WhatsApp tap-to-mention). Ignored in WhatsApp self-chat mode (see `whatsapp.allowFrom`).
|
||||
- **Text patterns**: Regex patterns defined in `mentionPatterns`. Always checked regardless of self-chat mode.
|
||||
- Mention gating is enforced only when mention detection is possible (native mentions or at least one `mentionPattern`).
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -258,7 +186,7 @@ Group messages default to **require mention** (either metadata mention or regex
|
||||
}
|
||||
```
|
||||
|
||||
Mention gating defaults live per provider (`whatsapp.groups`, `telegram.groups`, `imessage.groups`, `discord.guilds`). When `*.groups` is set, it also acts as a group allowlist; include `"*"` to allow all groups.
|
||||
Mention gating defaults live per provider (`whatsapp.groups`, `telegram.groups`, `imessage.groups`, `discord.guilds`).
|
||||
|
||||
To respond **only** to specific text triggers (ignoring native @-mentions):
|
||||
```json5
|
||||
@@ -277,114 +205,6 @@ To respond **only** to specific text triggers (ignoring native @-mentions):
|
||||
}
|
||||
```
|
||||
|
||||
### Group policy (per provider)
|
||||
|
||||
Use `*.groupPolicy` to control whether group/room messages are accepted at all:
|
||||
|
||||
```json5
|
||||
{
|
||||
whatsapp: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15551234567"]
|
||||
},
|
||||
telegram: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["tg:123456789", "@alice"]
|
||||
},
|
||||
signal: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15551234567"]
|
||||
},
|
||||
imessage: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["chat_id:123"]
|
||||
},
|
||||
discord: {
|
||||
groupPolicy: "allowlist",
|
||||
guilds: {
|
||||
"GUILD_ID": {
|
||||
channels: { help: { allow: true } }
|
||||
}
|
||||
}
|
||||
},
|
||||
slack: {
|
||||
groupPolicy: "allowlist",
|
||||
channels: { "#general": { allow: true } }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `"open"` (default): groups bypass allowlists; mention-gating still applies.
|
||||
- `"disabled"`: block all group/room messages.
|
||||
- `"allowlist"`: only allow groups/rooms that match the configured allowlist.
|
||||
- WhatsApp/Telegram/Signal/iMessage use `groupAllowFrom` (fallback: explicit `allowFrom`).
|
||||
- Discord/Slack use channel allowlists (`discord.guilds.*.channels`, `slack.channels`).
|
||||
- Group DMs (Discord/Slack) are still controlled by `dm.groupEnabled` + `dm.groupChannels`.
|
||||
|
||||
### Multi-agent routing (`routing.agents` + `routing.bindings`)
|
||||
|
||||
Run multiple isolated agents (separate workspace, `agentDir`, sessions) inside one Gateway. Inbound messages are routed to an agent via bindings.
|
||||
|
||||
- `routing.defaultAgentId`: fallback when no binding matches (default: `main`).
|
||||
- `routing.agents.<agentId>`: per-agent overrides.
|
||||
- `workspace`: default `~/clawd-<agentId>` (for `main`, falls back to legacy `agent.workspace`).
|
||||
- `agentDir`: default `~/.clawdbot/agents/<agentId>/agent`.
|
||||
- `routing.bindings[]`: routes inbound messages to an `agentId`.
|
||||
- `match.provider` (required)
|
||||
- `match.accountId` (optional; `*` = any account; omitted = default account)
|
||||
- `match.peer` (optional; `{ kind: dm|group|channel, id }`)
|
||||
- `match.guildId` / `match.teamId` (optional; provider-specific)
|
||||
|
||||
Deterministic match order:
|
||||
1) `match.peer`
|
||||
2) `match.guildId`
|
||||
3) `match.teamId`
|
||||
4) `match.accountId` (exact, no peer/guild/team)
|
||||
5) `match.accountId: "*"` (provider-wide, no peer/guild/team)
|
||||
6) `routing.defaultAgentId`
|
||||
|
||||
Within each match tier, the first matching entry in `routing.bindings` wins.
|
||||
|
||||
Example: two WhatsApp accounts → two agents:
|
||||
|
||||
```json5
|
||||
{
|
||||
routing: {
|
||||
defaultAgentId: "home",
|
||||
agents: {
|
||||
home: { workspace: "~/clawd-home" },
|
||||
work: { workspace: "~/clawd-work" },
|
||||
},
|
||||
bindings: [
|
||||
{ agentId: "home", match: { provider: "whatsapp", accountId: "personal" } },
|
||||
{ agentId: "work", match: { provider: "whatsapp", accountId: "biz" } },
|
||||
],
|
||||
},
|
||||
whatsapp: {
|
||||
accounts: {
|
||||
personal: {},
|
||||
biz: {},
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `routing.agentToAgent` (optional)
|
||||
|
||||
Agent-to-agent messaging is opt-in:
|
||||
|
||||
```json5
|
||||
{
|
||||
routing: {
|
||||
agentToAgent: {
|
||||
enabled: false,
|
||||
allow: ["home", "work"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `routing.queue`
|
||||
|
||||
Controls how inbound messages behave when an agent run is already active.
|
||||
@@ -397,7 +217,7 @@ Controls how inbound messages behave when an agent run is already active.
|
||||
debounceMs: 1000,
|
||||
cap: 20,
|
||||
drop: "summarize", // old | new | summarize
|
||||
byProvider: {
|
||||
bySurface: {
|
||||
whatsapp: "collect",
|
||||
telegram: "collect",
|
||||
discord: "collect",
|
||||
@@ -409,27 +229,6 @@ Controls how inbound messages behave when an agent run is already active.
|
||||
}
|
||||
```
|
||||
|
||||
### `commands` (chat command handling)
|
||||
|
||||
Controls how chat commands are enabled across connectors.
|
||||
|
||||
```json5
|
||||
{
|
||||
commands: {
|
||||
native: false, // register native commands when supported
|
||||
text: true, // parse slash commands in chat messages
|
||||
useAccessGroups: true // enforce access-group allowlists/policies for commands
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Text commands must be sent as a **standalone** message and use the leading `/` (no plain-text aliases).
|
||||
- `commands.text: false` disables parsing chat messages for commands.
|
||||
- `commands.native: true` registers native commands on supported connectors (Discord/Slack/Telegram). Platforms without native commands still rely on text commands.
|
||||
- `commands.native: false` skips native registration; Discord/Telegram clear previously registered commands on startup. Slack commands are managed in the Slack app.
|
||||
- `commands.useAccessGroups: false` allows commands to bypass access-group allowlists/policies.
|
||||
|
||||
### `web` (WhatsApp web provider)
|
||||
|
||||
WhatsApp runs through the gateway’s web provider. It starts automatically when a linked session exists.
|
||||
@@ -461,9 +260,8 @@ Set `telegram.enabled: false` to disable automatic startup.
|
||||
telegram: {
|
||||
enabled: true,
|
||||
botToken: "your-bot-token",
|
||||
dmPolicy: "pairing", // pairing | allowlist | open | disabled
|
||||
allowFrom: ["tg:123456789"], // optional; "open" requires ["*"]
|
||||
groups: { "*": { requireMention: true } },
|
||||
requireMention: true,
|
||||
allowFrom: ["123456789"],
|
||||
mediaMaxMb: 5,
|
||||
proxy: "socks5://localhost:9050",
|
||||
webhookUrl: "https://example.com/telegram-webhook",
|
||||
@@ -501,10 +299,15 @@ Configure the Discord bot by setting the bot token and optional gating:
|
||||
moderation: false
|
||||
},
|
||||
replyToMode: "off", // off | first | all
|
||||
slashCommand: { // user-installed app slash commands
|
||||
enabled: true,
|
||||
name: "clawd",
|
||||
sessionPrefix: "discord:slash",
|
||||
ephemeral: true
|
||||
},
|
||||
dm: {
|
||||
enabled: true, // disable all DMs when false
|
||||
policy: "pairing", // pairing | allowlist | open | disabled
|
||||
allowFrom: ["1234567890", "steipete"], // optional DM allowlist ("open" requires ["*"])
|
||||
allowFrom: ["1234567890", "steipete"], // optional DM allowlist (ids or names)
|
||||
groupEnabled: false, // enable group DMs
|
||||
groupChannels: ["clawd-dm"] // optional group DM allowlist
|
||||
},
|
||||
@@ -545,8 +348,7 @@ Slack runs in Socket Mode and requires both a bot token and app token:
|
||||
appToken: "xapp-...",
|
||||
dm: {
|
||||
enabled: true,
|
||||
policy: "pairing", // pairing | allowlist | open | disabled
|
||||
allowFrom: ["U123", "U456", "*"], // optional; "open" requires ["*"]
|
||||
allowFrom: ["U123", "U456", "*"],
|
||||
groupEnabled: false,
|
||||
groupChannels: ["G123"]
|
||||
},
|
||||
@@ -601,7 +403,6 @@ Clawdbot spawns `imsg rpc` (JSON-RPC over stdio). No daemon or port required.
|
||||
enabled: true,
|
||||
cliPath: "imsg",
|
||||
dbPath: "~/Library/Messages/chat.db",
|
||||
dmPolicy: "pairing", // pairing | allowlist | open | disabled
|
||||
allowFrom: ["+15555550123", "user@example.com", "chat_id:123"],
|
||||
includeAttachments: false,
|
||||
mediaMaxMb: 16,
|
||||
@@ -631,57 +432,20 @@ Default: `~/clawd`.
|
||||
If `agent.sandbox` is enabled, non-main sessions can override this with their
|
||||
own per-session workspaces under `agent.sandbox.workspaceRoot`.
|
||||
|
||||
### `agent.skipBootstrap`
|
||||
|
||||
Disables automatic creation of the workspace bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, and `BOOTSTRAP.md`).
|
||||
|
||||
Use this for pre-seeded deployments where your workspace files come from a repo.
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: { skipBootstrap: true }
|
||||
}
|
||||
```
|
||||
|
||||
### `agent.userTimezone`
|
||||
|
||||
Sets the user’s timezone for **system prompt context** (not for timestamps in
|
||||
message envelopes). If unset, Clawdbot uses the host timezone at runtime.
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: { userTimezone: "America/Chicago" }
|
||||
}
|
||||
```
|
||||
|
||||
### `messages`
|
||||
|
||||
Controls inbound/outbound prefixes and optional ack reactions.
|
||||
Controls inbound/outbound prefixes and timestamps.
|
||||
|
||||
```json5
|
||||
{
|
||||
messages: {
|
||||
messagePrefix: "[clawdbot]",
|
||||
responsePrefix: "🦞",
|
||||
ackReaction: "👀",
|
||||
ackReactionScope: "group-mentions"
|
||||
timestampPrefix: "Europe/London"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`responsePrefix` is applied to **all outbound replies** (tool summaries, block
|
||||
streaming, final replies) across providers unless already present.
|
||||
|
||||
`ackReaction` sends a best-effort emoji reaction to acknowledge inbound messages
|
||||
on providers that support reactions (Slack/Discord/Telegram). Defaults to the
|
||||
configured `identity.emoji` when set, otherwise `"👀"`. Set it to `""` to disable.
|
||||
|
||||
`ackReactionScope` controls when reactions fire:
|
||||
- `group-mentions` (default): only when a group/room requires mentions **and** the bot was mentioned
|
||||
- `group-all`: all group/room messages
|
||||
- `direct`: direct messages only
|
||||
- `all`: all messages
|
||||
|
||||
### `talk`
|
||||
|
||||
Defaults for Talk mode (macOS/iOS/Android). Voice IDs fall back to `ELEVENLABS_VOICE_ID` or `SAG_VOICE_ID` when unset.
|
||||
@@ -707,12 +471,14 @@ Defaults for Talk mode (macOS/iOS/Android). Voice IDs fall back to `ELEVENLABS_V
|
||||
### `agent`
|
||||
|
||||
Controls the embedded agent runtime (model/thinking/verbose/timeouts).
|
||||
`agent.models` defines the configured model catalog (and acts as the allowlist for `/model`).
|
||||
`agent.model.primary` sets the default model; `agent.model.fallbacks` are global failovers.
|
||||
`agent.imageModel` is optional and is **only used if the primary model lacks image input**.
|
||||
`allowedModels` lets `/model` list/filter and enforce a per-session allowlist
|
||||
(omit to show the full catalog).
|
||||
`modelAliases` adds short names for `/model` (alias -> provider/model).
|
||||
`modelFallbacks` lists ordered fallback models to try when the default fails.
|
||||
`imageModel` selects an image-capable model for the `image` tool.
|
||||
`imageModelFallbacks` lists ordered fallback image models for the `image` tool.
|
||||
|
||||
Clawdbot also ships a few built-in alias shorthands. Defaults only apply when the model
|
||||
is already present in `agent.models`:
|
||||
Clawdbot also ships a few built-in `modelAliases` shorthands (when an `agent` section exists):
|
||||
|
||||
- `opus` -> `anthropic/claude-opus-4-5`
|
||||
- `sonnet` -> `anthropic/claude-sonnet-4-5`
|
||||
@@ -726,24 +492,23 @@ If you configure the same alias name (case-insensitive) yourself, your value win
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
models: {
|
||||
"anthropic/claude-opus-4-5": { alias: "Opus" },
|
||||
"anthropic/claude-sonnet-4-1": { alias: "Sonnet" },
|
||||
"openrouter/deepseek/deepseek-r1:free": {}
|
||||
},
|
||||
model: {
|
||||
primary: "anthropic/claude-opus-4-5",
|
||||
fallbacks: [
|
||||
"openrouter/deepseek/deepseek-r1:free",
|
||||
"openrouter/meta-llama/llama-3.3-70b-instruct:free"
|
||||
]
|
||||
},
|
||||
imageModel: {
|
||||
primary: "openrouter/qwen/qwen-2.5-vl-72b-instruct:free",
|
||||
fallbacks: [
|
||||
"openrouter/google/gemini-2.0-flash-vision:free"
|
||||
]
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
allowedModels: [
|
||||
"anthropic/claude-opus-4-5",
|
||||
"anthropic/claude-sonnet-4-1"
|
||||
],
|
||||
modelAliases: {
|
||||
Opus: "anthropic/claude-opus-4-5",
|
||||
Sonnet: "anthropic/claude-sonnet-4-1"
|
||||
},
|
||||
modelFallbacks: [
|
||||
"openrouter/deepseek/deepseek-r1:free",
|
||||
"openrouter/meta-llama/llama-3.3-70b-instruct:free"
|
||||
],
|
||||
imageModel: "openrouter/qwen/qwen-2.5-vl-72b-instruct:free",
|
||||
imageModelFallbacks: [
|
||||
"openrouter/google/gemini-2.0-flash-vision:free"
|
||||
],
|
||||
thinkingDefault: "low",
|
||||
verboseDefault: "off",
|
||||
elevatedDefault: "on",
|
||||
@@ -778,24 +543,20 @@ Block streaming:
|
||||
}
|
||||
```
|
||||
|
||||
`agent.model.primary` should be set as `provider/model` (e.g. `anthropic/claude-opus-4-5`).
|
||||
Aliases come from `agent.models.*.alias` (e.g. `Opus`).
|
||||
`agent.model` should be set as `provider/model` (e.g. `anthropic/claude-opus-4-5`).
|
||||
If `modelAliases` is configured, you may also use the alias key (e.g. `Opus`).
|
||||
If you omit the provider, CLAWDBOT currently assumes `anthropic` as a temporary
|
||||
deprecation fallback.
|
||||
Z.AI models are available as `zai/<model>` (e.g. `zai/glm-4.7`) and require
|
||||
`ZAI_API_KEY` (or legacy `Z_AI_API_KEY`) in the environment.
|
||||
|
||||
`agent.heartbeat` configures periodic heartbeat runs:
|
||||
- `every`: duration string (`ms`, `s`, `m`, `h`); default unit minutes. Default:
|
||||
`30m`. Set `0m` to disable.
|
||||
- `every`: duration string (`ms`, `s`, `m`, `h`); default unit minutes. Omit or set
|
||||
`0m` to disable.
|
||||
- `model`: optional override model for heartbeat runs (`provider/model`).
|
||||
- `target`: optional delivery provider (`last`, `whatsapp`, `telegram`, `discord`, `slack`, `signal`, `imessage`, `none`). Default: `last`.
|
||||
- `to`: optional recipient override (provider-specific id, e.g. E.164 for WhatsApp, chat id for Telegram).
|
||||
- `prompt`: optional override for the heartbeat body (default: `Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.`).
|
||||
- `ackMaxChars`: max chars allowed after `HEARTBEAT_OK` before delivery (default: 30).
|
||||
|
||||
Heartbeats run full agent turns. Shorter intervals burn more tokens; adjust `every`
|
||||
and/or `model` accordingly.
|
||||
- `target`: optional delivery channel (`last`, `whatsapp`, `telegram`, `discord`, `imessage`, `none`). Default: `last`.
|
||||
- `to`: optional recipient override (E.164 for WhatsApp, chat id for Telegram).
|
||||
- `prompt`: optional override for the heartbeat body (default: `HEARTBEAT`).
|
||||
|
||||
`agent.bash` configures background bash defaults:
|
||||
- `backgroundMs`: time before auto-background (ms, default 10000)
|
||||
@@ -818,7 +579,7 @@ Example (disable browser/canvas everywhere):
|
||||
|
||||
`agent.elevated` controls elevated (host) bash access:
|
||||
- `enabled`: allow elevated mode (default true)
|
||||
- `allowFrom`: per-provider allowlists (empty = disabled)
|
||||
- `allowFrom`: per-surface allowlists (empty = disabled)
|
||||
- `whatsapp`: E.164 numbers
|
||||
- `telegram`: chat ids or usernames
|
||||
- `discord`: user ids or usernames (falls back to `discord.dm.allowFrom` if omitted)
|
||||
@@ -855,7 +616,7 @@ Defaults (if enabled):
|
||||
- Debian bookworm-slim based image
|
||||
- workspace per session under `~/.clawdbot/sandboxes`
|
||||
- auto-prune: idle > 24h OR age > 7d
|
||||
- tools: allow only `bash`, `process`, `read`, `write`, `edit`, `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn` (deny wins)
|
||||
- tools: allow only `bash`, `process`, `read`, `write`, `edit` (deny wins)
|
||||
- optional sandboxed browser (Chromium + CDP, noVNC observer)
|
||||
- hardening knobs: `network`, `user`, `pidsLimit`, `memory`, `cpus`, `ulimits`, `seccompProfile`, `apparmorProfile`
|
||||
|
||||
@@ -901,7 +662,7 @@ Defaults (if enabled):
|
||||
enableNoVnc: true
|
||||
},
|
||||
tools: {
|
||||
allow: ["bash", "process", "read", "write", "edit", "sessions_list", "sessions_history", "sessions_send", "sessions_spawn"],
|
||||
allow: ["bash", "process", "read", "write", "edit"],
|
||||
deny: ["browser", "canvas", "nodes", "cron", "discord", "gateway"]
|
||||
},
|
||||
prune: {
|
||||
@@ -936,24 +697,19 @@ URL is injected per session.
|
||||
|
||||
Clawdbot uses the **pi-coding-agent** model catalog. You can add custom providers
|
||||
(LiteLLM, local OpenAI-compatible servers, Anthropic proxies, etc.) by writing
|
||||
`~/.clawdbot/agents/<agentId>/agent/models.json` or by defining the same schema inside your
|
||||
`~/.clawdbot/agent/models.json` or by defining the same schema inside your
|
||||
Clawdbot config under `models.providers`.
|
||||
|
||||
When `models.providers` is present, Clawdbot writes/merges a `models.json` into
|
||||
`~/.clawdbot/agents/<agentId>/agent/` on startup:
|
||||
`~/.clawdbot/agent/` on startup:
|
||||
- default behavior: **merge** (keeps existing providers, overrides on name)
|
||||
- set `models.mode: "replace"` to overwrite the file contents
|
||||
|
||||
Select the model via `agent.model.primary` (provider/model).
|
||||
Select the model via `agent.model` (provider/model).
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
model: { primary: "custom-proxy/llama-3.1-8b" },
|
||||
models: {
|
||||
"custom-proxy/llama-3.1-8b": {}
|
||||
}
|
||||
},
|
||||
agent: { model: "custom-proxy/llama-3.1-8b" },
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
@@ -986,10 +742,14 @@ via **LM Studio** using the **Responses API**.
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
model: { primary: "lmstudio/minimax-m2.1-gs32" },
|
||||
models: {
|
||||
"anthropic/claude-opus-4-5": { alias: "Opus" },
|
||||
"lmstudio/minimax-m2.1-gs32": { alias: "Minimax" }
|
||||
model: "Minimax",
|
||||
allowedModels: [
|
||||
"anthropic/claude-opus-4-5",
|
||||
"lmstudio/minimax-m2.1-gs32"
|
||||
],
|
||||
modelAliases: {
|
||||
Opus: "anthropic/claude-opus-4-5",
|
||||
Minimax: "lmstudio/minimax-m2.1-gs32"
|
||||
}
|
||||
},
|
||||
models: {
|
||||
@@ -1026,7 +786,7 @@ Notes:
|
||||
`google-generative-ai`
|
||||
- Use `authHeader: true` + `headers` for custom auth needs.
|
||||
- Override the agent config root with `CLAWDBOT_AGENT_DIR` (or `PI_CODING_AGENT_DIR`)
|
||||
if you want `models.json` stored elsewhere (default: `~/.clawdbot/agents/main/agent`).
|
||||
if you want `models.json` stored elsewhere.
|
||||
|
||||
### `session`
|
||||
|
||||
@@ -1038,18 +798,15 @@ Controls session scoping, idle expiry, reset triggers, and where the session sto
|
||||
scope: "per-sender",
|
||||
idleMinutes: 60,
|
||||
resetTriggers: ["/new", "/reset"],
|
||||
// Default is already per-agent under ~/.clawdbot/agents/<agentId>/sessions/sessions.json
|
||||
// You can override with {agentId} templating:
|
||||
store: "~/.clawdbot/agents/{agentId}/sessions/sessions.json",
|
||||
// Direct chats collapse to agent:<agentId>:<mainKey> (default: "main").
|
||||
mainKey: "main",
|
||||
store: "~/.clawdbot/sessions/sessions.json",
|
||||
// mainKey is ignored; primary key is fixed to "main"
|
||||
agentToAgent: {
|
||||
// Max ping-pong reply turns between requester/target (0–5).
|
||||
maxPingPongTurns: 5
|
||||
},
|
||||
sendPolicy: {
|
||||
rules: [
|
||||
{ action: "deny", match: { provider: "discord", chatType: "group" } }
|
||||
{ action: "deny", match: { surface: "discord", chatType: "group" } }
|
||||
],
|
||||
default: "allow"
|
||||
}
|
||||
@@ -1058,10 +815,9 @@ Controls session scoping, idle expiry, reset triggers, and where the session sto
|
||||
```
|
||||
|
||||
Fields:
|
||||
- `mainKey`: direct-chat bucket key (default: `"main"`). Useful when you want to “rename” the primary DM thread without changing `agentId`.
|
||||
- `agentToAgent.maxPingPongTurns`: max reply-back turns between requester/target (0–5, default 5).
|
||||
- `sendPolicy.default`: `allow` or `deny` fallback when no rule matches.
|
||||
- `sendPolicy.rules[]`: match by `provider`, `chatType` (`direct|group|room`), or `keyPrefix` (e.g. `cron:`). First deny wins; otherwise allow.
|
||||
- `sendPolicy.rules[]`: match by `surface` (provider), `chatType` (`direct|group|room`), or `keyPrefix` (e.g. `cron:`). First deny wins; otherwise allow.
|
||||
|
||||
### `skills` (skills config)
|
||||
|
||||
@@ -1283,7 +1039,7 @@ Convenience flags (CLI):
|
||||
- `clawdbot --dev …` → uses `~/.clawdbot-dev` + shifts ports from base `19001`
|
||||
- `clawdbot --profile <name> …` → uses `~/.clawdbot-<name>` (port via config/env/flags)
|
||||
|
||||
See [`docs/gateway.md`](https://docs.clawd.bot/gateway) for the derived port mapping (gateway/bridge/browser/canvas).
|
||||
See `docs/gateway.md` for the derived port mapping (gateway/bridge/browser/canvas).
|
||||
|
||||
Example:
|
||||
```bash
|
||||
@@ -1294,7 +1050,7 @@ clawdbot gateway --port 19001
|
||||
|
||||
### `hooks` (Gateway webhooks)
|
||||
|
||||
Enable a simple HTTP webhook endpoint on the Gateway HTTP server.
|
||||
Enable a simple HTTP webhook surface on the Gateway HTTP server.
|
||||
|
||||
Defaults:
|
||||
- enabled: `false`
|
||||
@@ -1331,7 +1087,7 @@ Requests must include the hook token:
|
||||
|
||||
Endpoints:
|
||||
- `POST /hooks/wake` → `{ text, mode?: "now"|"next-heartbeat" }`
|
||||
- `POST /hooks/agent` → `{ message, name?, sessionKey?, wakeMode?, deliver?, provider?, to?, thinking?, timeoutSeconds? }`
|
||||
- `POST /hooks/agent` → `{ message, name?, sessionKey?, wakeMode?, deliver?, channel?, to?, thinking?, timeoutSeconds? }`
|
||||
- `POST /hooks/<name>` → resolved via `hooks.mappings`
|
||||
|
||||
`/hooks/agent` always posts a summary into the main session (and can optionally trigger an immediate heartbeat via `wakeMode: "now"`).
|
||||
@@ -1461,7 +1217,7 @@ Template placeholders are expanded in `routing.transcribeAudio.command` (and any
|
||||
|----------|-------------|
|
||||
| `{{Body}}` | Full inbound message body |
|
||||
| `{{BodyStripped}}` | Body with group mentions stripped (best default for agents) |
|
||||
| `{{From}}` | Sender identifier (E.164 for WhatsApp; may differ per provider) |
|
||||
| `{{From}}` | Sender identifier (E.164 for WhatsApp; may differ per surface) |
|
||||
| `{{To}}` | Destination identifier |
|
||||
| `{{MessageSid}}` | Provider message id (when available) |
|
||||
| `{{SessionId}}` | Current session UUID |
|
||||
@@ -1475,11 +1231,11 @@ Template placeholders are expanded in `routing.transcribeAudio.command` (and any
|
||||
| `{{GroupMembers}}` | Group members preview (best effort) |
|
||||
| `{{SenderName}}` | Sender display name (best effort) |
|
||||
| `{{SenderE164}}` | Sender phone number (best effort) |
|
||||
| `{{Provider}}` | Provider hint (whatsapp|telegram|discord|imessage|webchat|…) |
|
||||
| `{{Surface}}` | Surface hint (whatsapp|telegram|discord|imessage|webchat|…) |
|
||||
|
||||
## Cron (Gateway scheduler)
|
||||
|
||||
Cron is a Gateway-owned scheduler for wakeups and scheduled jobs. See [Cron + wakeups](https://docs.clawd.bot/cron) for the full RFC and CLI examples.
|
||||
Cron is a Gateway-owned scheduler for wakeups and scheduled jobs. See [Cron + wakeups](./cron.md) for the full RFC and CLI examples.
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -1492,4 +1248,4 @@ Cron is a Gateway-owned scheduler for wakeups and scheduled jobs. See [Cron + wa
|
||||
|
||||
---
|
||||
|
||||
*Next: [Agent Runtime](https://docs.clawd.bot/agent)* 🦞
|
||||
*Next: [Agent Runtime](./agent.md)* 🦞
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
summary: "Deprecated newline-delimited control channel API (pre-gateway)"
|
||||
read_when:
|
||||
- Maintaining legacy control channel support
|
||||
---
|
||||
# Control channel API (newline-delimited JSON)
|
||||
|
||||
**Deprecated (historical):** superseded by the WebSocket Gateway protocol (`clawdbot gateway`, see `docs/architecture.md` and `docs/gateway.md`).
|
||||
Current builds use a WebSocket server on `ws://127.0.0.1:18789` and do **not** expose this TCP control channel.
|
||||
|
||||
Legacy endpoint (if present in an older build): `127.0.0.1:18789` (TCP, localhost only), typically reached via SSH port forward in remote mode.
|
||||
|
||||
## Frame format
|
||||
Each line is a JSON object. Two shapes exist:
|
||||
- **Request**: `{ "type": "request", "id": "<uuid>", "method": "health" | "status" | "last-heartbeat" | "set-heartbeats" | "ping", "params"?: { ... } }`
|
||||
- **Response**: `{ "type": "response", "id": "<same id>", "ok": true, "payload"?: { ... } }` or `{ "type": "response", "id": "<same id>", "ok": false, "error": "message" }`
|
||||
- **Event**: `{ "type": "event", "event": "heartbeat" | "gateway-status" | "log", "payload": { ... } }`
|
||||
|
||||
## Methods
|
||||
- `ping`: sanity check. Payload: `{ pong: true, ts }`.
|
||||
- `health`: returns the gateway health snapshot (same shape as `clawdbot health --json`).
|
||||
- `status`: shorter summary (linked/authAge/heartbeatSeconds, session counts).
|
||||
- `last-heartbeat`: returns the most recent heartbeat event the gateway has seen.
|
||||
- `set-heartbeats { enabled: boolean }`: toggle heartbeat scheduling.
|
||||
|
||||
## Events
|
||||
- `heartbeat` payload:
|
||||
```json
|
||||
{
|
||||
"ts": 1765224052664,
|
||||
"status": "sent" | "ok-empty" | "ok-token" | "skipped" | "failed",
|
||||
"to": "+15551234567",
|
||||
"preview": "Heartbeat OK",
|
||||
"hasMedia": false,
|
||||
"durationMs": 1025,
|
||||
"reason": "<error text>" // only on failed/skipped
|
||||
}
|
||||
```
|
||||
- `gateway-status` payload: `{ "state": "starting" | "running" | "restarting" | "failed" | "stopped", "pid"?: number, "reason"?: string }`
|
||||
- `log` payload: arbitrary log line; optional, can be disabled.
|
||||
|
||||
## Suggested client flow
|
||||
1) Connect (or reconnect) → send `ping`.
|
||||
2) Send `health` and `last-heartbeat` to populate UI.
|
||||
3) Listen for `event` frames; update UI in real time.
|
||||
4) For user toggles, send `set-heartbeats` and await response.
|
||||
|
||||
## Backward compatibility
|
||||
- If the control channel is unavailable: that’s expected on modern builds. Use the Gateway WS protocol instead.
|
||||
+5
-5
@@ -63,21 +63,21 @@ Paste the token into the UI settings (sent as `connect.params.auth.token`).
|
||||
The Gateway serves static files from `dist/control-ui`. Build them with:
|
||||
|
||||
```bash
|
||||
bun run ui:install
|
||||
bun run ui:build
|
||||
pnpm ui:install
|
||||
pnpm ui:build
|
||||
```
|
||||
|
||||
Optional absolute base (when you want fixed asset URLs):
|
||||
|
||||
```bash
|
||||
CLAWDBOT_CONTROL_UI_BASE_PATH=/clawdbot/ bun run ui:build
|
||||
CLAWDBOT_CONTROL_UI_BASE_PATH=/clawdbot/ pnpm ui:build
|
||||
```
|
||||
|
||||
For local development (separate dev server):
|
||||
|
||||
```bash
|
||||
bun run ui:install
|
||||
bun run ui:dev
|
||||
pnpm ui:install
|
||||
pnpm ui:dev
|
||||
```
|
||||
|
||||
Then point the UI at your Gateway WS URL (e.g. `ws://127.0.0.1:18789`).
|
||||
+10
-21
@@ -14,9 +14,9 @@ Last updated: 2025-12-13
|
||||
## Context
|
||||
|
||||
Clawdbot already has:
|
||||
- A **gateway heartbeat runner** that runs the agent with the configured heartbeat prompt (default: `Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.`) and suppresses `HEARTBEAT_OK` ([`src/infra/heartbeat-runner.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/heartbeat-runner.ts)).
|
||||
- A lightweight, in-memory **system event queue** (`enqueueSystemEvent`) that is injected into the next **main session** turn (`drainSystemEvents` in [`src/auto-reply/reply.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/auto-reply/reply.ts)).
|
||||
- A WebSocket **Gateway** daemon that is intended to be always-on ([`docs/gateway.md`](https://docs.clawd.bot/gateway)).
|
||||
- A **gateway heartbeat runner** that runs the agent with `HEARTBEAT` and suppresses `HEARTBEAT_OK` (`src/infra/heartbeat-runner.ts`).
|
||||
- A lightweight, in-memory **system event queue** (`enqueueSystemEvent`) that is injected into the next **main session** turn (`drainSystemEvents` in `src/auto-reply/reply.ts`).
|
||||
- A WebSocket **Gateway** daemon that is intended to be always-on (`docs/gateway.md`).
|
||||
|
||||
This RFC adds a small “cron job system” so Clawd can schedule future work and reliably wake itself up:
|
||||
- **Delayed**: run on the *next* normal heartbeat tick
|
||||
@@ -75,7 +75,7 @@ Each job is a JSON object with stable keys (unknown keys ignored for forward com
|
||||
- For `sessionTarget:"main"`, `wakeMode` controls whether we trigger the heartbeat immediately or just enqueue and wait.
|
||||
- `payload` (one of)
|
||||
- `{"kind":"systemEvent","text":string}` (enqueue as `System:`)
|
||||
- `{"kind":"agentTurn","message":string,"deliver"?:boolean,"provider"?: "last"|"whatsapp"|"telegram"|"discord"|"signal"|"imessage","to"?:string,"timeoutSeconds"?:number}`
|
||||
- `{"kind":"agentTurn","message":string,"deliver"?:boolean,"channel"?: "last"|"whatsapp"|"telegram"|"discord"|"signal"|"imessage","to"?:string,"timeoutSeconds"?:number}`
|
||||
- `isolation` (optional; only meaningful for isolated jobs)
|
||||
- `{"postToMainPrefix"?: string}`
|
||||
- `runtime` (optional)
|
||||
@@ -173,14 +173,14 @@ When due:
|
||||
- Execute via the same agent runner path as other command-mode runs, but pinned to:
|
||||
- `sessionKey = cron:<jobId>`
|
||||
- `sessionId = store[sessionKey].sessionId` (create if missing)
|
||||
- Optionally deliver output (`payload.deliver === true`) to the configured provider/to.
|
||||
- Optionally deliver output (`payload.deliver === true`) to the configured channel/to.
|
||||
- Isolated jobs always enqueue a summary system event to the main session when they finish (derived from the last agent text output).
|
||||
- Prefix defaults to `Cron`, and can be customized via `isolation.postToMainPrefix`.
|
||||
- If `deliver` is omitted/false, nothing is sent to external providers; you still get the main-session summary and can inspect the full isolated transcript in `cron:<jobId>`.
|
||||
|
||||
### “Run in parallel to main”
|
||||
|
||||
Clawdbot currently serializes command execution through a global in-process queue ([`src/process/command-queue.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/process/command-queue.ts)) to avoid collisions.
|
||||
Clawdbot currently serializes command execution through a global in-process queue (`src/process/command-queue.ts`) to avoid collisions.
|
||||
|
||||
To support isolated cron jobs running “in parallel”, we should introduce **lanes** (keyed queues) plus a global concurrency cap:
|
||||
- Lane `"main"`: inbound auto-replies + main heartbeat.
|
||||
@@ -198,7 +198,7 @@ We need a way for the Gateway (or the scheduler) to request an immediate heartbe
|
||||
|
||||
Design:
|
||||
- `startHeartbeatRunner` owns the real heartbeat execution and installs a wake handler.
|
||||
- Wake hook lives in [`src/infra/heartbeat-wake.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/heartbeat-wake.ts):
|
||||
- Wake hook lives in `src/infra/heartbeat-wake.ts`:
|
||||
- `setHeartbeatWakeHandler(fn | null)` installed by the heartbeat runner
|
||||
- `requestHeartbeatNow({ reason, coalesceMs? })`
|
||||
- If the handler is absent, the request is stored as “pending”; the next time the handler is installed, it runs once.
|
||||
@@ -216,17 +216,6 @@ Retention:
|
||||
|
||||
Each log line includes (at minimum) job id, status/error, timing, and a `summary` string (systemEvent text for main jobs, and the last agent text output for isolated jobs).
|
||||
|
||||
## Compatibility policy (cron.add/cron.update)
|
||||
|
||||
To keep older clients working, the Gateway applies **best-effort normalization** for `cron.add` and `cron.update`:
|
||||
- Accepts wrapped payloads under `data` or `job` and unwraps them.
|
||||
- Infers `schedule.kind` from `atMs`, `everyMs`, or `expr` if missing.
|
||||
- Infers `payload.kind` from `text` (systemEvent) or `message` (agentTurn) if missing.
|
||||
- Defaults `wakeMode` to `"next-heartbeat"` when omitted.
|
||||
- Defaults `sessionTarget` based on payload kind (`systemEvent` → `"main"`, `agentTurn` → `"isolated"`).
|
||||
|
||||
Normalization is **compat-only**. New clients should send the full schema (including `kind`, `sessionTarget`, and `wakeMode`) to avoid ambiguity. Unknown fields are still rejected by schema validation.
|
||||
|
||||
## Gateway API
|
||||
|
||||
New methods (names can be bikeshed; `cron.*` is suggested):
|
||||
@@ -275,7 +264,7 @@ Add a `cron` command group (all commands should also support `--json` where sens
|
||||
- `--wake now|next-heartbeat`
|
||||
- payload flags (choose one):
|
||||
- `--system-event "<text>"`
|
||||
- `--message "<agent message>" [--deliver] [--provider last|whatsapp|telegram|discord|slack|signal|imessage] [--to <dest>]`
|
||||
- `--message "<agent message>" [--deliver] [--channel last|whatsapp|telegram|discord|signal|imessage] [--to <dest>]`
|
||||
|
||||
- `clawdbot cron edit <id> ...` (patch-by-flags, non-interactive)
|
||||
- `clawdbot cron rm <id>`
|
||||
@@ -313,7 +302,7 @@ clawdbot cron add \
|
||||
--wake now \
|
||||
--message "Daily check: scan calendar + inbox; deliver only if urgent." \
|
||||
--deliver \
|
||||
--provider last
|
||||
--channel last
|
||||
```
|
||||
|
||||
### Run weekly (every Wednesday)
|
||||
@@ -328,7 +317,7 @@ clawdbot cron add \
|
||||
--wake now \
|
||||
--message "Weekly: summarize status and remind me of goals." \
|
||||
--deliver \
|
||||
--provider last
|
||||
--channel last
|
||||
```
|
||||
|
||||
### “Next heartbeat”
|
||||
|
||||
+4
-4
@@ -9,9 +9,9 @@ The Gateway dashboard is the browser Control UI served at `/` by default
|
||||
(override with `gateway.controlUi.basePath`).
|
||||
|
||||
Key references:
|
||||
- [`docs/control-ui.md`](https://docs.clawd.bot/control-ui) for usage and UI capabilities.
|
||||
- [`docs/tailscale.md`](https://docs.clawd.bot/tailscale) for Serve/Funnel automation.
|
||||
- [`docs/web.md`](https://docs.clawd.bot/web) for bind modes and security notes.
|
||||
- `docs/control-ui.md` for usage and UI capabilities.
|
||||
- `docs/tailscale.md` for Serve/Funnel automation.
|
||||
- `docs/web.md` for bind modes and security notes.
|
||||
|
||||
Authentication is enforced at the WebSocket handshake via `connect.params.auth`
|
||||
(token or password). See `gateway.auth` in [`docs/configuration.md`](https://docs.clawd.bot/configuration).
|
||||
(token or password). See `gateway.auth` in `docs/configuration.md`.
|
||||
+25
-29
@@ -1,7 +1,7 @@
|
||||
---
|
||||
summary: "Discord bot support status, capabilities, and configuration"
|
||||
read_when:
|
||||
- Working on Discord provider features
|
||||
- Working on Discord surface features
|
||||
---
|
||||
# Discord (Bot API)
|
||||
|
||||
@@ -11,9 +11,9 @@ Status: ready for DM and guild text channels via the official Discord bot gatewa
|
||||
|
||||
## 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:<agentId>:discord:channel:<channelId>` (display names use `discord:<guildSlug>#<channelSlug>`).
|
||||
- Share the same `main` session used by WhatsApp/Telegram/WebChat; guild channels stay isolated as `discord:group:<channelId>` (display names use `discord:<guildSlug>#<channelSlug>`).
|
||||
- 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.
|
||||
- Keep routing deterministic: replies always go back to the surface 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.
|
||||
@@ -23,18 +23,13 @@ Status: ready for DM and guild text channels via the official Discord bot gatewa
|
||||
- If you prefer env vars, still add `discord: { enabled: true }` to `~/.clawdbot/clawdbot.json` and set `DISCORD_BOT_TOKEN`.
|
||||
5. Direct chats: use `user:<id>` (or a `<@id>` mention) when delivering; all turns land in the shared `main` session.
|
||||
6. Guild channels: use `channel:<channelId>` 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 <code>`.
|
||||
- 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.
|
||||
7. Optional DM control: set `discord.dm.enabled = false` to ignore all DMs, or `discord.dm.allowFrom` to allow specific users (ids or names). Use `discord.dm.groupEnabled` + `discord.dm.groupChannels` to allow group DMs.
|
||||
8. Optional guild rules: set `discord.guilds` keyed by guild id (preferred) or slug, with per-channel rules.
|
||||
9. Optional slash commands: enable `discord.slashCommand` to accept user-installed app commands (ephemeral replies). Slash invocations respect the same DM/guild allowlists.
|
||||
10. 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.
|
||||
11. Reactions: the agent can trigger reactions via the `discord` tool (gated by `discord.actions.*`).
|
||||
- The `discord` tool is only exposed when the current surface is Discord.
|
||||
12. Slash commands use isolated session keys (`${sessionPrefix}:${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 `#`.
|
||||
@@ -64,7 +59,7 @@ In your app: **OAuth2** → **URL Generator**
|
||||
|
||||
**Scopes**
|
||||
- ✅ `bot`
|
||||
- ✅ `applications.commands` (required for native commands)
|
||||
- ✅ `applications.commands` (only if you want slash commands; otherwise leave unchecked)
|
||||
|
||||
**Bot Permissions** (minimal baseline)
|
||||
- ✅ View Channels
|
||||
@@ -128,7 +123,6 @@ Example “single server, only allow me, only allow #help”:
|
||||
|
||||
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.
|
||||
|
||||
### 6) Verify it works
|
||||
@@ -143,7 +137,7 @@ Notes:
|
||||
- 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"`).
|
||||
- **DMs don’t work**: `discord.dm.enabled` may be `false` or `discord.dm.allowFrom` doesn’t include you.
|
||||
|
||||
## Capabilities & limits
|
||||
- DMs and guild text channels (threads are treated as separate channels; voice not supported).
|
||||
@@ -160,7 +154,6 @@ Notes:
|
||||
discord: {
|
||||
enabled: true,
|
||||
token: "abc.123",
|
||||
groupPolicy: "open",
|
||||
mediaMaxMb: 8,
|
||||
actions: {
|
||||
reactions: true,
|
||||
@@ -180,9 +173,14 @@ Notes:
|
||||
moderation: false
|
||||
},
|
||||
replyToMode: "off",
|
||||
slashCommand: {
|
||||
enabled: true,
|
||||
name: "clawd",
|
||||
sessionPrefix: "discord:slash",
|
||||
ephemeral: true
|
||||
},
|
||||
dm: {
|
||||
enabled: true,
|
||||
policy: "pairing", // pairing | allowlist | open | disabled
|
||||
allowFrom: ["123456789012345678", "steipete"],
|
||||
groupEnabled: false,
|
||||
groupChannels: ["clawd-dm"]
|
||||
@@ -204,15 +202,10 @@ Notes:
|
||||
}
|
||||
```
|
||||
|
||||
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.allowFrom`: DM allowlist (user ids or names). Omit or set to `["*"]` to allow any DM sender.
|
||||
- `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.<id>.slug`: optional friendly slug used for display names.
|
||||
@@ -220,6 +213,7 @@ Ack reactions are controlled globally via `messages.ackReaction` +
|
||||
- `guilds.<id>.channels`: channel rules (keys are channel slugs or ids).
|
||||
- `guilds.<id>.requireMention`: per-guild mention requirement (overridable per channel).
|
||||
- `guilds.<id>.reactionNotifications`: reaction system event mode (`off`, `own`, `all`, `allowlist`).
|
||||
- `slashCommand`: optional config for user-installed slash commands (ephemeral responses).
|
||||
- `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).
|
||||
@@ -273,9 +267,11 @@ Allowlist matching notes:
|
||||
- Use `*` to allow any sender/channel.
|
||||
- When `guilds.<id>.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).
|
||||
Slash command notes:
|
||||
- Register a chat input command in Discord with at least one string option (e.g., `prompt`).
|
||||
- The first non-empty string option is treated as the prompt.
|
||||
- Slash commands honor the same allowlists as DMs/guild messages (`discord.dm.allowFrom`, `discord.guilds`, per-channel rules).
|
||||
- Clawdbot will auto-register `/clawd` (or the configured name) if it doesn't already exist.
|
||||
|
||||
## Tool actions
|
||||
The agent can call `discord` with actions like:
|
||||
|
||||
+3
-3
@@ -42,7 +42,7 @@ Target direction:
|
||||
- The **gateway** advertises its bridge via Bonjour.
|
||||
- Clients browse and show a “pick a gateway” list, then store the chosen endpoint.
|
||||
|
||||
Troubleshooting and beacon details: [`docs/bonjour.md`](https://docs.clawd.bot/bonjour).
|
||||
Troubleshooting and beacon details: `docs/bonjour.md`.
|
||||
|
||||
#### Current implementation
|
||||
|
||||
@@ -77,7 +77,7 @@ If the gateway can detect it is running under Tailscale, it publishes `tailnetDn
|
||||
|
||||
When there is no direct route (or direct is disabled), clients can always connect via SSH by forwarding the loopback gateway port.
|
||||
|
||||
See [`docs/remote.md`](https://docs.clawd.bot/remote).
|
||||
See `docs/remote.md`.
|
||||
|
||||
## Transport selection (client policy)
|
||||
|
||||
@@ -92,7 +92,7 @@ Recommended client behavior:
|
||||
|
||||
The gateway is the source of truth for node/client admission.
|
||||
|
||||
- Pairing requests are created/approved/rejected in the gateway (see [`docs/gateway/pairing.md`](https://docs.clawd.bot/gateway/pairing)).
|
||||
- Pairing requests are created/approved/rejected in the gateway (see `docs/gateway/pairing.md`).
|
||||
- The bridge enforces:
|
||||
- auth (token / keypair)
|
||||
- scopes/ACLs (bridge is not a raw proxy to every gateway method)
|
||||
|
||||
+4
-10
@@ -59,16 +59,10 @@ docker compose exec clawdbot-gateway node dist/index.js health --token "$CLAWDBO
|
||||
scripts/e2e/onboard-docker.sh
|
||||
```
|
||||
|
||||
### QR import smoke test (Docker)
|
||||
|
||||
```bash
|
||||
pnpm test:docker:qr
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
- Gateway bind defaults to `lan` for container use.
|
||||
- The gateway container is the source of truth for sessions (`~/.clawdbot/agents/<agentId>/sessions/`).
|
||||
- The gateway container is the source of truth for sessions (`~/.clawdbot/sessions`).
|
||||
|
||||
## Per-session Agent Sandbox (host gateway + Docker tools)
|
||||
|
||||
@@ -88,7 +82,7 @@ container. The gateway stays on your host, but the tool execution is isolated:
|
||||
- Workspace per session under `~/.clawdbot/sandboxes`
|
||||
- Auto-prune: idle > 24h OR age > 7d
|
||||
- Network: `none` by default (explicitly opt-in if you need egress)
|
||||
- Default allow: `bash`, `process`, `read`, `write`, `edit`, `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`
|
||||
- Default allow: `bash`, `process`, `read`, `write`, `edit`
|
||||
- Default deny: `browser`, `canvas`, `nodes`, `cron`, `discord`, `gateway`
|
||||
|
||||
### Enable sandboxing
|
||||
@@ -124,7 +118,7 @@ container. The gateway stays on your host, but the tool execution is isolated:
|
||||
extraHosts: ["internal.service:10.0.0.5"]
|
||||
},
|
||||
tools: {
|
||||
allow: ["bash", "process", "read", "write", "edit", "sessions_list", "sessions_history", "sessions_send", "sessions_spawn"],
|
||||
allow: ["bash", "process", "read", "write", "edit"],
|
||||
deny: ["browser", "canvas", "nodes", "cron", "discord", "gateway"]
|
||||
},
|
||||
prune: {
|
||||
@@ -252,7 +246,7 @@ Example:
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Image missing: build with [`scripts/sandbox-setup.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/sandbox-setup.sh) or set `agent.sandbox.docker.image`.
|
||||
- Image missing: build with `scripts/sandbox-setup.sh` or set `agent.sandbox.docker.image`.
|
||||
- Container not running: it will auto-create per session on demand.
|
||||
- Permission errors in sandbox: set `docker.user` to a UID:GID that matches your
|
||||
mounted workspace ownership (or chown the workspace folder).
|
||||
+9
-14
@@ -23,25 +23,21 @@
|
||||
"navigation": {
|
||||
"groups": [
|
||||
{
|
||||
"group": "Start Here",
|
||||
"group": "Getting Started",
|
||||
"pages": [
|
||||
"getting-started",
|
||||
"wizard",
|
||||
"index",
|
||||
"setup",
|
||||
"pairing",
|
||||
"faq",
|
||||
"onboarding",
|
||||
"clawd",
|
||||
"showcase",
|
||||
"hubs",
|
||||
"onboarding"
|
||||
"faq"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Install Options",
|
||||
"group": "Installation",
|
||||
"pages": [
|
||||
"wizard",
|
||||
"nix",
|
||||
"docker"
|
||||
"docker",
|
||||
"setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -74,9 +70,8 @@
|
||||
"mac/dev-setup",
|
||||
"mac/menu-bar",
|
||||
"mac/voicewake",
|
||||
"macos",
|
||||
"ios",
|
||||
"android",
|
||||
"ios/connect",
|
||||
"android/connect",
|
||||
"webchat",
|
||||
"web"
|
||||
]
|
||||
|
||||
@@ -15,8 +15,6 @@ read_when:
|
||||
- Migrates legacy `~/.clawdis/clawdis.json` when no Clawdbot config exists.
|
||||
- Checks sandbox Docker images when sandboxing is enabled (offers to build or switch to legacy names).
|
||||
- Detects legacy Clawdis services (launchd/systemd/schtasks) and offers to migrate them.
|
||||
- On Linux, checks if systemd user lingering is enabled and can enable it (required to keep the Gateway alive after logout).
|
||||
- Migrates legacy on-disk state layouts (sessions, agentDir, provider auth dirs) into the current per-agent/per-account structure.
|
||||
|
||||
## Legacy config file migration
|
||||
If `~/.clawdis/clawdis.json` exists and `~/.clawdbot/clawdbot.json` does not, doctor will migrate the file and normalize old paths/image names.
|
||||
@@ -28,26 +26,8 @@ Doctor will:
|
||||
- Show the migration it applied.
|
||||
- Rewrite `~/.clawdbot/clawdbot.json` with the updated schema.
|
||||
|
||||
The Gateway also auto-runs doctor migrations on startup when it detects a legacy
|
||||
config format, so stale configs are repaired without manual intervention.
|
||||
|
||||
Current migrations:
|
||||
- `routing.allowFrom` → `whatsapp.allowFrom`
|
||||
- `agent.model`/`allowedModels`/`modelAliases`/`modelFallbacks`/`imageModelFallbacks`
|
||||
→ `agent.models` + `agent.model.primary/fallbacks` + `agent.imageModel.primary/fallbacks`
|
||||
|
||||
## Legacy state migrations (disk layout)
|
||||
|
||||
Doctor can migrate older on-disk layouts into the current structure:
|
||||
- Sessions store + transcripts:
|
||||
- from `~/.clawdbot/sessions/` to `~/.clawdbot/agents/<agentId>/sessions/`
|
||||
- Agent dir:
|
||||
- from `~/.clawdbot/agent/` to `~/.clawdbot/agents/<agentId>/agent/`
|
||||
- WhatsApp auth state (Baileys):
|
||||
- from legacy `~/.clawdbot/credentials/*.json` (except `oauth.json`)
|
||||
- to `~/.clawdbot/credentials/whatsapp/<accountId>/...` (default account id: `default`)
|
||||
|
||||
These migrations are best-effort and idempotent; doctor will emit warnings when it leaves any legacy folders behind as backups.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ read_when:
|
||||
|
||||
## Availability + allowlists
|
||||
- Feature gate: `agent.elevated.enabled` (default can be off via config even if the code supports it).
|
||||
- Sender allowlist: `agent.elevated.allowFrom` with per-provider allowlists (e.g. `discord`, `whatsapp`).
|
||||
- Sender allowlist: `agent.elevated.allowFrom` with per-surface allowlists (e.g. `discord`, `whatsapp`).
|
||||
- Both must pass; otherwise elevated is treated as unavailable.
|
||||
- Discord fallback: if `agent.elevated.allowFrom.discord` is omitted, the `discord.dm.allowFrom` list is used as a fallback. Set `agent.elevated.allowFrom.discord` (even `[]`) to override.
|
||||
|
||||
|
||||
+67
-109
@@ -3,7 +3,7 @@ summary: "Frequently asked questions about Clawdbot setup, configuration, and us
|
||||
---
|
||||
# FAQ 🦞
|
||||
|
||||
Common questions from the community. For detailed configuration, see [Configuration](https://docs.clawd.bot/configuration).
|
||||
Common questions from the community. For detailed configuration, see [configuration.md](./configuration.md).
|
||||
|
||||
## Installation & Setup
|
||||
|
||||
@@ -14,15 +14,11 @@ Everything lives under `~/.clawdbot/`:
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `~/.clawdbot/clawdbot.json` | Main config (JSON5) |
|
||||
| `~/.clawdbot/credentials/oauth.json` | Legacy OAuth import (copied into auth profiles on first use) |
|
||||
| `~/.clawdbot/agents/<agentId>/agent/auth-profiles.json` | Auth profiles (OAuth + API keys) |
|
||||
| `~/.clawdbot/agents/<agentId>/agent/auth.json` | Runtime auth cache (managed automatically) |
|
||||
| `~/.clawdbot/credentials/` | Provider auth state (e.g. `whatsapp/<accountId>/creds.json`) |
|
||||
| `~/.clawdbot/agents/` | Per-agent state (agentDir + sessions) |
|
||||
| `~/.clawdbot/agents/<agentId>/sessions/` | Conversation history & state (per agent) |
|
||||
| `~/.clawdbot/agents/<agentId>/sessions/sessions.json` | Session metadata (per agent) |
|
||||
|
||||
Legacy single-agent path: `~/.clawdbot/agent/*` (migrated by `clawdbot doctor`).
|
||||
| `~/.clawdbot/agent/auth.json` | OAuth + API key store (Anthropic/OpenAI, etc.) |
|
||||
| `~/.clawdbot/credentials/` | WhatsApp/Telegram auth tokens |
|
||||
| `~/.clawdbot/credentials/oauth.json` | Legacy OAuth store (auto‑migrated) |
|
||||
| `~/.clawdbot/sessions/` | Conversation history & state |
|
||||
| `~/.clawdbot/sessions/sessions.json` | Session metadata |
|
||||
|
||||
Your **workspace** (AGENTS.md, memory files, skills) is separate — configured via `agent.workspace` in your config (default: `~/clawd`).
|
||||
|
||||
@@ -40,16 +36,16 @@ Some features are platform-specific:
|
||||
|
||||
### What are the minimum system requirements?
|
||||
|
||||
**Basically nothing!** The gateway is very lightweight — heavy compute happens on your model provider’s servers (Anthropic/OpenAI/etc.).
|
||||
**Basically nothing!** The gateway is very lightweight — all heavy compute happens on Anthropic's servers.
|
||||
|
||||
- **RAM:** 512MB-1GB is enough (community member runs on 1GB VPS!)
|
||||
- **CPU:** 1 core is fine for personal use
|
||||
- **Disk:** ~500MB for Clawdbot + deps, plus space for logs/media
|
||||
|
||||
The gateway is just shuffling messages around. A Raspberry Pi 4 can run it. For the CLI, prefer the Node runtime (most stable):
|
||||
The gateway is just shuffling messages around. A Raspberry Pi 4 can run it. You can also use **Bun** instead of Node for even lower memory footprint:
|
||||
|
||||
```bash
|
||||
pnpm clawdbot gateway
|
||||
bun clawdbot gateway
|
||||
```
|
||||
|
||||
### How do I install on Linux without Homebrew?
|
||||
@@ -82,7 +78,7 @@ This creates `~/.clawdbot/clawdbot.json` with your API keys, workspace path, and
|
||||
cp -r ~/.clawdbot ~/.clawdbot-backup
|
||||
|
||||
# Remove config and credentials
|
||||
trash ~/.clawdbot
|
||||
rm -rf ~/.clawdbot
|
||||
|
||||
# Re-run onboarding
|
||||
pnpm clawdbot onboard
|
||||
@@ -122,7 +118,7 @@ They're **separate billing**! An API key does NOT use your subscription.
|
||||
pnpm clawdbot login
|
||||
```
|
||||
|
||||
**If OAuth fails** (headless/container): Do OAuth on a normal machine, then copy `~/.clawdbot/agents/<agentId>/agent/auth-profiles.json` (and `auth.json` if present) to your server. Legacy installs can still import `~/.clawdbot/credentials/oauth.json` on first use.
|
||||
**If OAuth fails** (headless/container): Do OAuth on a normal machine, then copy `~/.clawdbot/agent/auth.json` to your server. The auth is just a JSON file.
|
||||
|
||||
### How are env vars loaded?
|
||||
|
||||
@@ -144,15 +140,15 @@ Or set `CLAWDBOT_LOAD_SHELL_ENV=1` (timeout: `CLAWDBOT_SHELL_ENV_TIMEOUT_MS=1500
|
||||
|
||||
### Does enterprise OAuth work?
|
||||
|
||||
**Not currently.** Enterprise accounts use SSO which requires a different auth flow that Clawdbot’s OAuth login doesn’t support yet.
|
||||
**Not currently.** Enterprise accounts use SSO which requires a different auth flow that pi-coding-agent doesn't support yet.
|
||||
|
||||
**Workaround:** Ask your enterprise admin to provision an API key (Anthropic or OpenAI) and use it via `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`.
|
||||
**Workaround:** Ask your enterprise admin to provision an API key via the Anthropic console, then use that with `ANTHROPIC_API_KEY`.
|
||||
|
||||
### OAuth callback not working (containers/headless)?
|
||||
|
||||
OAuth needs the callback to reach the machine running the CLI. Options:
|
||||
|
||||
1. **Copy auth manually** — Run OAuth on your laptop, copy `~/.clawdbot/agents/<agentId>/agent/auth-profiles.json` (and `auth.json` if present) to the container. Legacy flow: copy `~/.clawdbot/credentials/oauth.json` to trigger import.
|
||||
1. **Copy auth manually** — Run OAuth on your laptop, copy `~/.clawdbot/agent/auth.json` to the container.
|
||||
2. **SSH tunnel** — `ssh -L 18789:localhost:18789 user@server`
|
||||
3. **Tailscale** — Put both machines on your tailnet.
|
||||
|
||||
@@ -194,15 +190,7 @@ OAuth needs the callback to reach the machine running the CLI. Options:
|
||||
|
||||
### Can I run Clawdbot in Docker?
|
||||
|
||||
Yes — Docker is optional but supported. Recommended: run the setup script:
|
||||
|
||||
```bash
|
||||
./docker-setup.sh
|
||||
```
|
||||
|
||||
It builds the image, runs onboarding + login, and starts Docker Compose. For manual steps and sandbox notes, see `docs/docker.md`.
|
||||
|
||||
Key considerations:
|
||||
There's no official Docker setup yet, but it works. Key considerations:
|
||||
|
||||
- **WhatsApp login:** QR code works in terminal — no display needed.
|
||||
- **Persistence:** Mount `~/.clawdbot/` and your workspace as volumes.
|
||||
@@ -229,7 +217,7 @@ pnpm clawdbot gateway
|
||||
bash /app/start.sh
|
||||
```
|
||||
|
||||
For more detail, see `docs/docker.md`.
|
||||
Docker support is on the roadmap — PRs welcome!
|
||||
|
||||
### Can I run Clawdbot headless on a VPS?
|
||||
|
||||
@@ -241,7 +229,7 @@ Yes! The terminal QR code login works fine over SSH. For long-running operation:
|
||||
### bun binary vs Node runtime?
|
||||
|
||||
Clawdbot can run as:
|
||||
- **bun binary (macOS app)** — Single executable, easy distribution, auto-restarts via launchd
|
||||
- **bun binary** — Single executable, easy distribution, auto-restarts via launchd
|
||||
- **Node runtime** (`pnpm clawdbot gateway`) — More stable for WhatsApp
|
||||
|
||||
If you see WebSocket errors like `ws.WebSocket 'upgrade' event is not implemented`, use Node instead of the bun binary. Bun's WebSocket implementation has edge cases that can break WhatsApp (Baileys).
|
||||
@@ -301,7 +289,7 @@ Per-group activation can be changed by the owner:
|
||||
- `/activation mention` — respond only when mentioned (default)
|
||||
- `/activation always` — respond to all messages
|
||||
|
||||
See [Groups](https://docs.clawd.bot/groups) for details.
|
||||
See [groups.md](./groups.md) for details.
|
||||
|
||||
---
|
||||
|
||||
@@ -309,11 +297,11 @@ See [Groups](https://docs.clawd.bot/groups) for details.
|
||||
|
||||
### How much context can Clawdbot handle?
|
||||
|
||||
Context window depends on the model. Clawdbot uses **autocompaction** — older conversation gets summarized to stay under the limit.
|
||||
Claude Opus has a 200k token context window, and Clawdbot uses **autocompaction** — older conversation gets summarized to stay under the limit.
|
||||
|
||||
Practical tips:
|
||||
- Keep `AGENTS.md` focused, not bloated.
|
||||
- Use `/compact` to shrink older context or `/new` to reset when it gets stale.
|
||||
- Use `/new` to reset the session when context gets stale.
|
||||
- For large memory/notes collections, use search tools like `qmd` rather than loading everything.
|
||||
|
||||
### Where are my memory files?
|
||||
@@ -338,7 +326,7 @@ cat ~/.clawdbot/clawdbot.json | grep workspace
|
||||
- **Telegram** — Via Bot API (grammY).
|
||||
- **Discord** — Bot integration.
|
||||
- **iMessage** — Via `imsg` CLI (macOS only).
|
||||
- **Signal** — Via `signal-cli` (see [Signal](https://docs.clawd.bot/signal)).
|
||||
- **Signal** — Via `signal-cli` (see [signal.md](./signal.md)).
|
||||
- **WebChat** — Browser-based chat UI.
|
||||
|
||||
### Discord: Bot works in channels but not DMs?
|
||||
@@ -376,7 +364,7 @@ If you send an image but your Clawd doesn't "see" it, check these:
|
||||
|
||||
Not all models support images! Check `agent.model` in your config:
|
||||
|
||||
- ✅ Vision (examples): `anthropic/claude-opus-4-5`, `anthropic/claude-sonnet-4-5`, `anthropic/claude-haiku-4-5`, `openai/gpt-5.2`, `openai/gpt-4o`, `google/gemini-3-pro-preview`, `google/gemini-3-flash-preview`
|
||||
- ✅ Vision: `claude-opus-4-5`, `claude-sonnet-4-5`, `claude-haiku-4-5`, `gpt-5.2`, `gpt-4o`, `gemini-pro`
|
||||
- ❌ No vision: Most local LLMs (Llama, Mistral), older models, text-only configs
|
||||
|
||||
**2. Is media being downloaded?**
|
||||
@@ -478,41 +466,23 @@ cd ~/path/to/clawdbot
|
||||
codex --full-auto "debug why clawdbot gateway won't start"
|
||||
```
|
||||
|
||||
### Gateway stops after I log out (Linux)
|
||||
|
||||
Linux installs use a systemd **user** service. By default, systemd stops user
|
||||
services on logout/idle, which kills the Gateway.
|
||||
|
||||
Onboarding attempts to enable lingering; if it’s still off, run:
|
||||
```bash
|
||||
sudo loginctl enable-linger $USER
|
||||
```
|
||||
|
||||
**macOS/Windows**
|
||||
|
||||
Gateway daemons run in the user session by default. Keep the user logged in.
|
||||
Headless/system services are not configured out of the box.
|
||||
|
||||
### Processes keep restarting after I kill them
|
||||
|
||||
The gateway runs under a supervisor that auto-restarts it. You need to stop the supervisor, not just kill the process.
|
||||
|
||||
**macOS (Clawdbot.app)**
|
||||
|
||||
- Quit the menu bar app to stop the gateway.
|
||||
- For debugging, restart via the app (or `scripts/restart-mac.sh` when working in the repo).
|
||||
- To inspect launchd state: `launchctl print gui/$UID | grep clawdbot`
|
||||
|
||||
**macOS (CLI launchd service, if installed)**
|
||||
If you haven’t installed a supervisor yet, install one first:
|
||||
```bash
|
||||
clawdbot gateway install
|
||||
# Or: clawdbot service install
|
||||
```
|
||||
**macOS (launchd)**
|
||||
|
||||
```bash
|
||||
clawdbot gateway stop
|
||||
clawdbot gateway restart
|
||||
# Check if running
|
||||
launchctl list | grep clawdbot
|
||||
|
||||
# Stop and disable
|
||||
launchctl disable gui/$UID/com.clawdbot.gateway
|
||||
launchctl bootout gui/$UID/com.clawdbot.gateway
|
||||
|
||||
# Re-enable later
|
||||
launchctl enable gui/$UID/com.clawdbot.gateway
|
||||
launchctl bootstrap gui/$UID ~/Library/LaunchAgents/com.clawdbot.gateway.plist
|
||||
```
|
||||
|
||||
**Linux (systemd)**
|
||||
@@ -522,11 +492,7 @@ clawdbot gateway restart
|
||||
systemctl list-units | grep -i clawdbot
|
||||
|
||||
# Stop and disable
|
||||
clawdbot gateway stop
|
||||
systemctl --user disable --now clawdbot-gateway.service
|
||||
|
||||
# Or just restart
|
||||
clawdbot gateway restart
|
||||
sudo systemctl disable --now clawdbot
|
||||
```
|
||||
|
||||
**pm2 (if used)**
|
||||
@@ -543,22 +509,19 @@ pm2 delete clawdbot
|
||||
launchctl disable gui/$UID/com.clawdbot.gateway
|
||||
launchctl bootout gui/$UID/com.clawdbot.gateway 2>/dev/null
|
||||
|
||||
# Linux: stop systemd user service
|
||||
systemctl --user disable --now clawdbot-gateway.service
|
||||
|
||||
# Linux (system-wide unit, if installed)
|
||||
sudo systemctl disable --now clawdbot-gateway.service
|
||||
# Linux: stop systemd service
|
||||
sudo systemctl disable --now clawdbot
|
||||
|
||||
# Kill any remaining processes
|
||||
pkill -f "clawdbot"
|
||||
|
||||
# Remove data
|
||||
trash ~/.clawdbot
|
||||
rm -rf ~/.clawdbot
|
||||
|
||||
# Remove repo and re-clone (adjust path if you cloned elsewhere)
|
||||
trash ~/Projects/clawdbot
|
||||
git clone https://github.com/clawdbot/clawdbot.git ~/Projects/clawdbot
|
||||
cd ~/Projects/clawdbot && pnpm install && pnpm build
|
||||
# Remove repo and re-clone
|
||||
rm -rf ~/clawdbot
|
||||
git clone https://github.com/clawdbot/clawdbot.git
|
||||
cd clawdbot && pnpm install && pnpm build
|
||||
pnpm clawdbot onboard
|
||||
```
|
||||
|
||||
@@ -570,21 +533,14 @@ Quick reference (send these in chat):
|
||||
|
||||
| Command | Action |
|
||||
|---------|--------|
|
||||
| `/help` | Show available commands |
|
||||
| `/status` | Health + session info |
|
||||
| `/new` or `/reset` | Reset the session |
|
||||
| `/compact [notes]` | Compact session context |
|
||||
| `/restart` | Restart Clawdbot |
|
||||
| `/activation mention\|always` | Group activation (owner-only) |
|
||||
| `/think <level>` | Set thinking level (off\|minimal\|low\|medium\|high) |
|
||||
| `/verbose on\|off` | Toggle verbose mode |
|
||||
| `/elevated on\|off` | Toggle elevated bash mode (approved senders only) |
|
||||
| `/activation mention\|always` | Group activation (owner-only) |
|
||||
| `/model <name>` | Switch AI model (see below) |
|
||||
| `/queue <mode>` | Queue mode (see below) |
|
||||
|
||||
Slash commands are owner-only (gated by `whatsapp.allowFrom` and command authorization on other surfaces).
|
||||
Commands are only recognized when the entire message is the command (slash required; no plain-text aliases).
|
||||
Full list + config: https://docs.clawd.bot/slash-commands
|
||||
| `/queue instant\|batch\|serial` | Message queuing mode |
|
||||
|
||||
### How do I switch models on the fly?
|
||||
|
||||
@@ -605,16 +561,21 @@ List available models with `/model`, `/model list`, or `/model status`.
|
||||
Clawdbot ships a few default model shorthands (you can override them in config):
|
||||
`opus`, `sonnet`, `gpt`, `gpt-mini`, `gemini`, `gemini-flash`.
|
||||
|
||||
**Setup:** Configure models and aliases in `clawdbot.json`:
|
||||
**Setup:** Configure allowed models and aliases in `clawdbot.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent": {
|
||||
"model": { "primary": "anthropic/claude-opus-4-5" },
|
||||
"models": {
|
||||
"anthropic/claude-opus-4-5": { "alias": "opus" },
|
||||
"anthropic/claude-sonnet-4-5": { "alias": "sonnet" },
|
||||
"anthropic/claude-haiku-4-5": { "alias": "haiku" }
|
||||
"model": "anthropic/claude-opus-4-5",
|
||||
"allowedModels": [
|
||||
"anthropic/claude-opus-4-5",
|
||||
"anthropic/claude-sonnet-4-5",
|
||||
"anthropic/claude-haiku-4-5"
|
||||
],
|
||||
"modelAliases": {
|
||||
"opus": "anthropic/claude-opus-4-5",
|
||||
"sonnet": "anthropic/claude-sonnet-4-5",
|
||||
"haiku": "anthropic/claude-haiku-4-5"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -630,8 +591,7 @@ If you don't want to use Anthropic directly, you can use alternative providers:
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
model: { primary: "openrouter/anthropic/claude-sonnet-4-5" },
|
||||
models: { "openrouter/anthropic/claude-sonnet-4-5": {} },
|
||||
model: "openrouter/anthropic/claude-sonnet-4",
|
||||
env: { OPENROUTER_API_KEY: "sk-or-..." }
|
||||
}
|
||||
}
|
||||
@@ -641,8 +601,7 @@ If you don't want to use Anthropic directly, you can use alternative providers:
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
model: { primary: "zai/glm-4.7" },
|
||||
models: { "zai/glm-4.7": {} },
|
||||
model: "zai/glm-4.7",
|
||||
env: { ZAI_API_KEY: "..." }
|
||||
}
|
||||
}
|
||||
@@ -662,8 +621,11 @@ If you get weird errors after switching models, try `/think off` and `/new` to r
|
||||
|
||||
### How do I stop/cancel a running task?
|
||||
|
||||
Send one of these **as a standalone message** (no slash): `stop`, `abort`, `esc`, `wait`, `exit`.
|
||||
These are abort triggers, not slash commands.
|
||||
Send `/stop` to immediately abort the current agent run. Other stop words also work:
|
||||
- `/stop`
|
||||
- `/abort`
|
||||
- `/esc`
|
||||
- `/exit`
|
||||
|
||||
For background processes (like Codex), use:
|
||||
```
|
||||
@@ -672,10 +634,8 @@ process action:kill sessionId:XXX
|
||||
|
||||
You can also configure `routing.queue.mode` to control how new messages interact with running tasks:
|
||||
- `steer` — New messages redirect the current task
|
||||
- `followup` — Run messages one at a time
|
||||
- `collect` — Batch messages, reply once after things settle
|
||||
- `steer-backlog` — Steer now, process backlog afterward
|
||||
- `interrupt` — Abort current run, start fresh
|
||||
- `interrupt` — Kills current run, starts fresh
|
||||
- `collect` — Queues messages for after
|
||||
|
||||
### Does Codex CLI use my ChatGPT Pro subscription or API credits?
|
||||
|
||||
@@ -698,13 +658,11 @@ If you have a ChatGPT subscription, use browser auth to avoid API charges!
|
||||
|
||||
Use `/queue` to control how messages sent in quick succession are handled:
|
||||
|
||||
- **`/queue steer`** — New messages steer the current response
|
||||
- **`/queue collect`** — Batch messages, reply once after things settle
|
||||
- **`/queue followup`** — One at a time, in order
|
||||
- **`/queue steer-backlog`** — Steer now, process backlog afterward
|
||||
- **`/queue interrupt`** — Abort current run, start fresh
|
||||
- **`/queue instant`** — New messages interrupt/steer the current response
|
||||
- **`/queue batch`** — Messages queue up, processed after current turn
|
||||
- **`/queue serial`** — One at a time, in order
|
||||
|
||||
If you tend to send multiple short messages, `/queue steer` feels most natural.
|
||||
If you tend to send multiple short messages, `/queue instant` feels most natural.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+8
-38
@@ -111,7 +111,7 @@ CLAWDBOT_CONFIG_PATH=~/.clawdbot/b.json CLAWDBOT_STATE_DIR=~/.clawdbot-b clawdbo
|
||||
- `node.invoke` — invoke a command on a node (e.g. `canvas.*`, `camera.*`).
|
||||
- `node.pair.*` — pairing lifecycle (`request`, `list`, `approve`, `reject`, `verify`).
|
||||
|
||||
See also: [`docs/presence.md`](https://docs.clawd.bot/presence) for how presence is produced/deduped and why `instanceId` matters.
|
||||
See also: `docs/presence.md` for how presence is produced/deduped and why `instanceId` matters.
|
||||
|
||||
## Events
|
||||
- `agent` — streamed tool/output events from the agent run (seq-tagged).
|
||||
@@ -127,7 +127,7 @@ See also: [`docs/presence.md`](https://docs.clawd.bot/presence) for how presence
|
||||
## Typing and validation
|
||||
- Server validates every inbound frame with AJV against JSON Schema emitted from the protocol definitions.
|
||||
- Clients (TS/Swift) consume generated types (TS directly; Swift via the repo’s generator).
|
||||
- Types live in [`src/gateway/protocol/*.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/protocol/*.ts); regenerate schemas/models with `pnpm protocol:gen` (writes [`dist/protocol.schema.json`](https://github.com/clawdbot/clawdbot/blob/main/dist/protocol.schema.json)) and `pnpm protocol:gen:swift` (writes [`apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift)).
|
||||
- Types live in `src/gateway/protocol/*.ts`; regenerate schemas/models with `pnpm protocol:gen` (writes `dist/protocol.schema.json`) and `pnpm protocol:gen:swift` (writes `apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift`).
|
||||
|
||||
## Connection snapshot
|
||||
- `hello-ok` includes a `snapshot` with `presence`, `health`, `stateVersion`, and `uptimeMs` plus `policy {maxPayload,maxBufferedBytes,tickIntervalMs}` so clients can render immediately without extra requests.
|
||||
@@ -155,15 +155,11 @@ See also: [`docs/presence.md`](https://docs.clawd.bot/presence) for how presence
|
||||
- KeepAlive: true
|
||||
- StandardOut/Err: file paths or `syslog`
|
||||
- On failure, launchd restarts; fatal misconfig should keep exiting so the operator notices.
|
||||
- LaunchAgents are per-user and require a logged-in session; for headless setups use a custom LaunchDaemon (not shipped).
|
||||
|
||||
Bundled mac app:
|
||||
- Clawdbot.app can bundle a bun-compiled gateway binary and install a per-user LaunchAgent labeled `com.clawdbot.gateway`.
|
||||
- To stop it cleanly, use `clawdbot gateway stop` (or `launchctl bootout gui/$UID/com.clawdbot.gateway`).
|
||||
- To restart, use `clawdbot gateway restart` (or `launchctl kickstart -k gui/$UID/com.clawdbot.gateway`).
|
||||
|
||||
## Supervision (systemd user unit)
|
||||
Create `~/.config/systemd/user/clawdbot-gateway.service`:
|
||||
## Supervision (systemd example)
|
||||
```
|
||||
[Unit]
|
||||
Description=Clawdbot Gateway
|
||||
@@ -172,36 +168,16 @@ Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/local/bin/clawdbot gateway --port 18789
|
||||
Restart=always
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
User=clawdbot
|
||||
Environment=CLAWDBOT_GATEWAY_TOKEN=
|
||||
WorkingDirectory=/home/youruser
|
||||
WorkingDirectory=/home/clawdbot
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
Enable lingering (required so the user service survives logout/idle):
|
||||
```
|
||||
sudo loginctl enable-linger youruser
|
||||
```
|
||||
Onboarding runs this on Linux (may prompt for sudo; writes `/var/lib/systemd/linger`).
|
||||
Then enable the service:
|
||||
```
|
||||
systemctl --user enable --now clawdbot-gateway.service
|
||||
```
|
||||
|
||||
**Alternative (system service)** - for always-on or multi-user servers, you can
|
||||
install a systemd **system** unit instead of a user unit (no lingering needed).
|
||||
Create `/etc/systemd/system/clawdbot-gateway.service` (copy the unit above,
|
||||
switch `WantedBy=multi-user.target`, set `User=` + `WorkingDirectory=`), then:
|
||||
```
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now clawdbot-gateway.service
|
||||
```
|
||||
|
||||
## Supervision (Windows scheduled task)
|
||||
- Onboarding installs a Scheduled Task named `Clawdbot Gateway` (runs on user logon).
|
||||
- Requires a logged-in user session; for headless setups use a system service or a task configured to run without a logged-in user (not shipped).
|
||||
Enable with `systemctl enable --now clawdbot-gateway.service`.
|
||||
|
||||
## Operational checks
|
||||
- Liveness: open WS and send `req:connect` → expect `res` with `payload.type="hello-ok"` (with snapshot).
|
||||
@@ -219,13 +195,7 @@ sudo systemctl enable --now clawdbot-gateway.service
|
||||
- `clawdbot gateway send --to <num> --message "hi" [--media-url ...]` — send via Gateway (idempotent).
|
||||
- `clawdbot gateway agent --message "hi" [--to ...]` — run an agent turn (waits for final by default).
|
||||
- `clawdbot gateway call <method> --params '{"k":"v"}'` — raw method invoker for debugging.
|
||||
- `clawdbot gateway install|uninstall` — install/remove the supervised gateway service (launchd/systemd/schtasks).
|
||||
- `clawdbot gateway service-status` — report supervised gateway service status.
|
||||
- `clawdbot gateway stop|restart` — stop/restart the supervised gateway service (launchd/systemd/schtasks).
|
||||
- Gateway helper subcommands assume a running gateway on `--url`; they no longer auto-spawn one.
|
||||
- Service aliases:
|
||||
- `clawdbot service ...` — service controls (install/uninstall/status/stop/restart)
|
||||
- `clawdbot daemon ...` — alias for `clawdbot service ...`
|
||||
|
||||
## Migration guidance
|
||||
- Retire uses of `clawdbot gateway` and the legacy TCP control port.
|
||||
|
||||
@@ -20,7 +20,7 @@ This enables:
|
||||
- **Bridge**: direct transport endpoint owned by the gateway. The bridge does not decide membership.
|
||||
|
||||
## API surface (gateway protocol)
|
||||
These are conceptual method names; wire them into [`src/gateway/protocol/schema.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/protocol/schema.ts) and regenerate Swift types.
|
||||
These are conceptual method names; wire them into `src/gateway/protocol/schema.ts` and regenerate Swift types.
|
||||
|
||||
### Events
|
||||
- `node.pair.requested`
|
||||
@@ -78,10 +78,10 @@ Optional interactive helper:
|
||||
- `clawdbot nodes watch` (subscribe to `node.pair.requested` and prompt in-place)
|
||||
|
||||
Implementation pointers:
|
||||
- CLI commands: [`src/cli/nodes-cli.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/cli/nodes-cli.ts)
|
||||
- Gateway handlers + events: [`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server.ts) + [`src/gateway/server-methods/nodes.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server-methods/nodes.ts)
|
||||
- Pairing store: [`src/infra/node-pairing.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/node-pairing.ts) (under `~/.clawdbot/nodes/`)
|
||||
- Optional macOS UI prompt (frontend only): [`apps/macos/Sources/Clawdbot/NodePairingApprovalPrompter.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/Clawdbot/NodePairingApprovalPrompter.swift)
|
||||
- CLI commands: `src/cli/nodes-cli.ts`
|
||||
- Gateway handlers + events: `src/gateway/server.ts`
|
||||
- Pairing store: `src/infra/node-pairing.ts` (under `~/.clawdbot/nodes/`)
|
||||
- Optional macOS UI prompt (frontend only): `apps/macos/Sources/Clawdbot/NodePairingApprovalPrompter.swift`
|
||||
- Push-first: listens to `node.pair.requested`/`node.pair.resolved`, does a `node.pair.list` on startup/reconnect,
|
||||
and only runs a slow safety poll while a request is pending/visible.
|
||||
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
---
|
||||
summary: "Beginner guide: from repo checkout to first message (wizard, auth, providers, pairing)"
|
||||
read_when:
|
||||
- First time setup from zero
|
||||
- You want the fastest path from checkout → onboarding → first message
|
||||
---
|
||||
|
||||
# Getting Started
|
||||
|
||||
Goal: go from **zero** → **first working chat** (with sane defaults) as quickly as possible.
|
||||
|
||||
Recommended path: use the **CLI onboarding wizard** (`clawdbot onboard`). It sets up:
|
||||
- model/auth (OAuth recommended)
|
||||
- gateway settings
|
||||
- providers (WhatsApp/Telegram/Discord/…)
|
||||
- pairing defaults (secure DMs)
|
||||
- workspace bootstrap + skills
|
||||
- optional background daemon
|
||||
|
||||
If you want the deeper reference pages, jump to: [Wizard](https://docs.clawd.bot/wizard), [Setup](https://docs.clawd.bot/setup), [Pairing](https://docs.clawd.bot/pairing), [Security](https://docs.clawd.bot/security).
|
||||
|
||||
## 0) Prereqs
|
||||
|
||||
- Node `>=22`
|
||||
- `bun` (preferred) or `pnpm`
|
||||
- Git
|
||||
|
||||
macOS: if you plan to build the apps, install Xcode / CLT. For the CLI + gateway only, Node is enough.
|
||||
|
||||
## 1) Check out from source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/clawdbot/clawdbot.git
|
||||
cd clawdbot
|
||||
bun install
|
||||
```
|
||||
|
||||
Note: `pnpm` is also supported:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
## 2) Build the Control UI (recommended)
|
||||
|
||||
The Gateway serves the browser dashboard (Control UI) when assets exist.
|
||||
|
||||
```bash
|
||||
bun run ui:install
|
||||
bun run ui:build
|
||||
bun run build
|
||||
```
|
||||
|
||||
If you skip UI build, the gateway still works — you just won’t get the dashboard.
|
||||
|
||||
## 3) Run the onboarding wizard
|
||||
|
||||
```bash
|
||||
bun run clawdbot onboard
|
||||
```
|
||||
|
||||
What you’ll choose:
|
||||
- **Local vs Remote** gateway
|
||||
- **Auth**: Anthropic OAuth or OpenAI OAuth (recommended), API key (optional), or skip for now
|
||||
- **Providers**: WhatsApp QR login, bot tokens, etc.
|
||||
- **Daemon**: optional background install (launchd/systemd/Task Scheduler)
|
||||
|
||||
Wizard doc: https://docs.clawd.bot/wizard
|
||||
|
||||
### Auth: where it lives (important)
|
||||
|
||||
- OAuth credentials (legacy import): `~/.clawdbot/credentials/oauth.json`
|
||||
- Auth profiles (OAuth + API keys): `~/.clawdbot/agents/<agentId>/agent/auth-profiles.json`
|
||||
|
||||
Headless/server tip: do OAuth on a normal machine first, then copy `oauth.json` to the gateway host.
|
||||
|
||||
## 4) Start the Gateway
|
||||
|
||||
If the wizard didn’t start it for you:
|
||||
|
||||
```bash
|
||||
bun run clawdbot gateway --port 18789 --verbose
|
||||
```
|
||||
|
||||
Dashboard (local loopback): `http://127.0.0.1:18789/`
|
||||
|
||||
## 5) Pair + connect your first chat surface
|
||||
|
||||
### WhatsApp (QR login)
|
||||
|
||||
```bash
|
||||
bun run clawdbot login
|
||||
```
|
||||
|
||||
Scan via WhatsApp → Settings → Linked Devices.
|
||||
|
||||
WhatsApp doc: https://docs.clawd.bot/whatsapp
|
||||
|
||||
### Telegram / Discord / others
|
||||
|
||||
The wizard can write tokens/config for you. If you prefer manual config, start with:
|
||||
- Telegram: https://docs.clawd.bot/telegram
|
||||
- Discord: https://docs.clawd.bot/discord
|
||||
|
||||
## 6) DM safety (pairing approvals)
|
||||
|
||||
Default posture: unknown DMs get a short code and messages are not processed until approved.
|
||||
|
||||
Approve:
|
||||
|
||||
```bash
|
||||
bun run clawdbot pairing list --provider telegram
|
||||
bun run clawdbot pairing approve --provider telegram <CODE>
|
||||
```
|
||||
|
||||
Pairing doc: https://docs.clawd.bot/pairing
|
||||
|
||||
## 7) Verify end-to-end
|
||||
|
||||
In a new terminal:
|
||||
|
||||
```bash
|
||||
bun run clawdbot health
|
||||
bun run clawdbot send --to +15555550123 --message "Hello from Clawdbot"
|
||||
```
|
||||
|
||||
If `health` shows “no auth configured”, go back to the wizard and set OAuth/key auth — the agent won’t be able to respond without it.
|
||||
|
||||
## Next steps (optional, but great)
|
||||
|
||||
- macOS menu bar app + voice wake: https://docs.clawd.bot/macos
|
||||
- iOS/Android nodes (Canvas/camera/voice): https://docs.clawd.bot/nodes
|
||||
- Remote access (SSH tunnel / Tailscale Serve): https://docs.clawd.bot/remote and https://docs.clawd.bot/tailscale
|
||||
@@ -13,7 +13,7 @@ Goal: Gmail watch -> Pub/Sub push -> `gog gmail watch serve` -> Clawdbot webhook
|
||||
|
||||
- `gcloud` installed and logged in.
|
||||
- `gog` (gogcli) installed and authorized for the Gmail account.
|
||||
- Clawdbot hooks enabled (see [`docs/webhook.md`](https://docs.clawd.bot/webhook)).
|
||||
- Clawdbot hooks enabled (see `docs/webhook.md`).
|
||||
- `tailscale` logged in if you want a public HTTPS endpoint via Funnel.
|
||||
|
||||
Example hook config (enable Gmail preset mapping):
|
||||
@@ -30,7 +30,7 @@ Example hook config (enable Gmail preset mapping):
|
||||
```
|
||||
|
||||
To customize payload handling, add `hooks.mappings` or a JS/TS transform module
|
||||
under `hooks.transformsDir` (see [`docs/webhook.md`](https://docs.clawd.bot/webhook)).
|
||||
under `hooks.transformsDir` (see `docs/webhook.md`).
|
||||
|
||||
## Wizard (recommended)
|
||||
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@ Updated: 2025-12-07
|
||||
- **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 `telegram.proxy` uses `undici.ProxyAgent` through grammY’s `client.baseFetch`.
|
||||
- **Webhook support:** `webhook-set.ts` wraps `setWebhook/deleteWebhook`; `webhook.ts` hosts the callback with health + graceful shutdown. Gateway enables webhook mode when `telegram.webhookUrl` is set (otherwise it long-polls).
|
||||
- **Sessions:** direct chats collapse into the agent main session (`agent:<agentId>:<mainKey>`); groups use `agent:<agentId>:telegram:group:<chatId>`; replies route back to the same provider.
|
||||
- **Config knobs:** `telegram.botToken`, `telegram.dmPolicy`, `telegram.groups` (allowlist + mention defaults), `telegram.allowFrom`, `telegram.groupAllowFrom`, `telegram.groupPolicy`, `telegram.mediaMaxMb`, `telegram.proxy`, `telegram.webhookSecret`, `telegram.webhookUrl`.
|
||||
- **Sessions:** direct chats map to `main`; groups map to `telegram:group:<chatId>`; replies route back to the same surface.
|
||||
- **Config knobs:** `telegram.botToken`, `telegram.groups`, `telegram.allowFrom`, `telegram.mediaMaxMb`, `telegram.proxy`, `telegram.webhookSecret`, `telegram.webhookUrl`.
|
||||
- **Tests:** grammy mocks cover DM + group mention gating and outbound send; more media/webhook fixtures still welcome.
|
||||
|
||||
Open questions
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
summary: "Behavior and config for WhatsApp group message handling (mentionPatterns are shared across surfaces)"
|
||||
summary: "Behavior and config for WhatsApp group message handling"
|
||||
read_when:
|
||||
- Changing group message rules or mentions
|
||||
---
|
||||
@@ -7,12 +7,10 @@ read_when:
|
||||
|
||||
Goal: let Clawd sit in WhatsApp groups, wake up only when pinged, and keep that thread separate from the personal DM session.
|
||||
|
||||
Note: `routing.groupChat.mentionPatterns` is now used by Telegram/Discord/Slack/iMessage as well; this doc focuses on WhatsApp-specific behavior.
|
||||
|
||||
## 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 (`whatsapp.groups`) and overridden per group via `/activation`. When `whatsapp.groups` is set, it also acts as a group allowlist (include `"*"` to allow all).
|
||||
- Group policy: `whatsapp.groupPolicy` controls whether group messages are accepted (`open|disabled|allowlist`). `allowlist` uses `whatsapp.groupAllowFrom` (fallback: explicit `whatsapp.allowFrom`).
|
||||
- Per-group sessions: session keys look like `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.
|
||||
- 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 (`whatsapp.groups`) and overridden per group via `/activation`.
|
||||
- Group allowlist bypass: we still enforce `whatsapp.allowFrom` on the participant at inbox ingest, but group JIDs themselves no longer block replies.
|
||||
- Per-group sessions: session keys look like `whatsapp:group:<jid>` so commands such as `/verbose on` or `/think:high` are scoped to that group; personal DM state is untouched. Heartbeats are skipped for group threads.
|
||||
- Context injection: last N (default 50) group messages are prefixed under `[Chat messages since your last reply - for context]`, with the triggering line under `[Current message - respond to this]`.
|
||||
- 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.
|
||||
@@ -52,13 +50,13 @@ Use the group chat command:
|
||||
- `/activation mention`
|
||||
- `/activation always`
|
||||
|
||||
Only the owner number (from `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.
|
||||
Only the owner number (from `whatsapp.allowFrom`, defaulting to the bot’s own E.164 when unset) can change this. `/status` in the group shows the current activation mode.
|
||||
|
||||
## How to use
|
||||
1) Add Clawd UK (`+447700900123`) to the group.
|
||||
2) Say `@clawd …` (or `@clawd uk`, `@clawdbot`, or include the number). Anyone in the group can trigger it.
|
||||
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.
|
||||
4) Session-level directives (`/verbose on`, `/think:high`, `/new` or `/reset`) apply only to that group’s session; your personal DM session remains independent.
|
||||
|
||||
## Testing / verification
|
||||
- Automated: `pnpm test -- src/web/auto-reply.test.ts --runInBand` (covers mention gating, history injection, sender suffix).
|
||||
@@ -70,4 +68,4 @@ Only the owner number (from `whatsapp.allowFrom`, or the bot’s own E.164 when
|
||||
## 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 (`~/.clawdbot/agents/<agentId>/sessions/sessions.json` by default); a missing entry just means the group hasn’t triggered a run yet.
|
||||
- Session store entries will appear as `whatsapp:group:<jid>` in the session store (`~/.clawdbot/sessions/sessions.json` by default); a missing entry just means the group hasn’t triggered a run yet.
|
||||
+6
-58
@@ -1,69 +1,21 @@
|
||||
---
|
||||
summary: "Group chat behavior across surfaces (WhatsApp/Telegram/Discord/Slack/Signal/iMessage)"
|
||||
summary: "Group chat behavior across surfaces (WhatsApp/Telegram/Discord/iMessage)"
|
||||
read_when:
|
||||
- Changing group chat behavior or mention gating
|
||||
---
|
||||
# Groups
|
||||
|
||||
Clawdbot treats group chats consistently across surfaces: WhatsApp, Telegram, Discord, Slack, Signal, iMessage.
|
||||
Clawdbot treats group chats consistently across surfaces: WhatsApp, Telegram, Discord, iMessage.
|
||||
|
||||
## Session keys
|
||||
- Group sessions use `agent:<agentId>:<provider>:group:<id>` session keys (rooms/channels use `agent:<agentId>:<provider>:channel:<id>`).
|
||||
- Group sessions use `surface:group:<id>` session keys (rooms/channels use `surface:channel:<id>`).
|
||||
- Direct chats use the main session (or per-sender if configured).
|
||||
- Heartbeats are skipped for group sessions.
|
||||
|
||||
## Display labels
|
||||
- UI labels use `displayName` when available, formatted as `<provider>:<token>`.
|
||||
- UI labels use `displayName` when available, formatted as `surface:<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 provider:
|
||||
|
||||
```json5
|
||||
{
|
||||
whatsapp: {
|
||||
groupPolicy: "disabled", // "open" | "disabled" | "allowlist"
|
||||
groupAllowFrom: ["+15551234567"]
|
||||
},
|
||||
telegram: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["123456789", "@username"]
|
||||
},
|
||||
signal: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["+15551234567"]
|
||||
},
|
||||
imessage: {
|
||||
groupPolicy: "disabled",
|
||||
groupAllowFrom: ["chat_id:123"]
|
||||
},
|
||||
discord: {
|
||||
groupPolicy: "allowlist",
|
||||
guilds: {
|
||||
"GUILD_ID": { channels: { help: { allow: true } } }
|
||||
}
|
||||
},
|
||||
slack: {
|
||||
groupPolicy: "allowlist",
|
||||
channels: { "#general": { allow: true } }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Policy | Behavior |
|
||||
|--------|----------|
|
||||
| `"open"` | Default. 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: use `groupAllowFrom` (fallback: explicit `allowFrom`).
|
||||
- Discord: allowlist uses `discord.guilds.<id>.channels`.
|
||||
- Slack: allowlist uses `slack.channels`.
|
||||
- Group DMs are controlled separately (`discord.dm.*`, `slack.dm.*`).
|
||||
- Telegram allowlist can match user IDs (`"123456789"`, `"telegram:123456789"`, `"tg:123456789"`) or usernames (`"@alice"` or `"alice"`); prefixes are case-insensitive.
|
||||
|
||||
## Mention gating (default)
|
||||
Group messages require a mention unless overridden per group. Defaults live per subsystem under `*.groups."*"`.
|
||||
|
||||
@@ -99,18 +51,14 @@ Group messages require a mention unless overridden per group. Defaults live per
|
||||
Notes:
|
||||
- `mentionPatterns` are case-insensitive regexes.
|
||||
- Surfaces that provide explicit mentions still pass; patterns are a fallback.
|
||||
- Mention gating is only enforced when mention detection is possible (native mentions or `mentionPatterns` are configured).
|
||||
- Discord defaults live in `discord.guilds."*"` (overridable per guild/channel).
|
||||
|
||||
## Group allowlists
|
||||
When `whatsapp.groups`, `telegram.groups`, or `imessage.groups` is configured, the keys act as a group allowlist. Use `"*"` to allow all groups while still setting default mention behavior.
|
||||
|
||||
## Activation (owner-only)
|
||||
Group owners can toggle per-group activation:
|
||||
- `/activation mention`
|
||||
- `/activation always`
|
||||
|
||||
Owner is determined by `whatsapp.allowFrom` (or the bot’s self E.164 when unset). Send the command as a standalone message. Other surfaces currently ignore `/activation`.
|
||||
Owner is determined by `whatsapp.allowFrom` (or the bot’s self E.164 when unset). Other surfaces currently ignore `/activation`.
|
||||
|
||||
## Context fields
|
||||
Group inbound payloads set:
|
||||
@@ -127,4 +75,4 @@ The agent system prompt includes a group intro on the first turn of a new group
|
||||
- Group replies always go back to the same `chat_id`.
|
||||
|
||||
## WhatsApp specifics
|
||||
See [`docs/group-messages.md`](https://docs.clawd.bot/group-messages) for WhatsApp-only behavior (history injection, mention handling details).
|
||||
See `docs/group-messages.md` for WhatsApp-only behavior (history injection, mention handling details).
|
||||
+4
-4
@@ -11,18 +11,18 @@ Short guide to verify the WhatsApp Web / Baileys stack without guessing.
|
||||
- `clawdbot status` — local summary: whether creds exist, auth age, session store path + recent sessions.
|
||||
- `clawdbot status --deep` — also probes the running Gateway (WhatsApp connect + Telegram + Discord APIs).
|
||||
- `clawdbot health --json` — asks the running Gateway for a full health snapshot (WS-only; no direct Baileys socket).
|
||||
- Send `/status` as a standalone message in WhatsApp/WebChat to get a status reply without invoking the agent.
|
||||
- Send `/status` in WhatsApp/WebChat to get a status reply without invoking the agent.
|
||||
- Logs: tail `/tmp/clawdbot/clawdbot-*.log` and filter for `web-heartbeat`, `web-reconnect`, `web-auto-reply`, `web-inbound`.
|
||||
|
||||
## Deep diagnostics
|
||||
- Creds on disk: `ls -l ~/.clawdbot/credentials/whatsapp/<accountId>/creds.json` (mtime should be recent).
|
||||
- Session store: `ls -l ~/.clawdbot/agents/<agentId>/sessions/sessions.json` (path can be overridden in config). Count and recent recipients are surfaced via `status`.
|
||||
- Creds on disk: `ls -l ~/.clawdbot/credentials/creds.json` (mtime should be recent).
|
||||
- Session store: `ls -l ~/.clawdbot/sessions/sessions.json` (legacy: `~/.clawdbot/sessions.json`; path can be overridden in config). Count and recent recipients are surfaced via `status`.
|
||||
- Relink flow: `clawdbot logout && clawdbot login --verbose` when status codes 409–515 or `loggedOut` appear in logs. (Note: the QR login flow auto-restarts once for status 515 after pairing.)
|
||||
|
||||
## When something fails
|
||||
- `logged out` or status 409–515 → relink with `clawdbot logout` then `clawdbot login`.
|
||||
- Gateway unreachable → start it: `clawdbot gateway --port 18789` (use `--force` if the port is busy).
|
||||
- No inbound messages → confirm linked phone is online and the sender is allowed (`whatsapp.allowFrom`); for group chats, ensure allowlist + mention rules match (`whatsapp.groups`, `routing.groupChat.mentionPatterns`).
|
||||
- No inbound messages → confirm linked phone is online and the sender is allowed (`whatsapp.allowFrom`); for group chats, ensure mention rules match (`routing.groupChat.mentionPatterns` and `whatsapp.groups`).
|
||||
|
||||
## Dedicated "health" command
|
||||
`clawdbot health --json` asks the running Gateway for its health snapshot (no direct Baileys socket from the CLI). It reports linked creds, auth age, Baileys connect result/status code, session-store summary, and a probe duration. It exits non-zero if the Gateway is unreachable or the probe fails/timeouts. Use `--timeout <ms>` to override the 10s default.
|
||||
+12
-41
@@ -9,68 +9,45 @@ Heartbeat runs periodic agent turns in the **main session** so the model can
|
||||
surface anything that needs attention without spamming the user.
|
||||
|
||||
## Prompt contract
|
||||
- Heartbeat body defaults to: `Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.` (configurable via `agent.heartbeat.prompt`).
|
||||
- If nothing needs attention, the model should reply `HEARTBEAT_OK`.
|
||||
- Heartbeat body defaults to `HEARTBEAT` (configurable via `agent.heartbeat.prompt`).
|
||||
- If nothing needs attention, the model should reply **exactly** `HEARTBEAT_OK`.
|
||||
- During heartbeat runs, Clawdbot treats `HEARTBEAT_OK` as an ack when it appears at
|
||||
the **start or end** of the reply. Clawdbot strips the token and discards the
|
||||
reply if the remaining content is **≤ `ackMaxChars`** (default: 30).
|
||||
reply if the remaining content is **≤ 30 characters**.
|
||||
- If `HEARTBEAT_OK` is in the **middle** of a reply, it is not treated specially.
|
||||
- For alerts, do **not** include `HEARTBEAT_OK`; return only the alert text.
|
||||
- Heartbeat prompt text is sent **verbatim** as the user message. Clawdbot does
|
||||
not append extra body text. The system prompt includes a Heartbeats section
|
||||
and the run is flagged as a heartbeat internally.
|
||||
|
||||
### Stray `HEARTBEAT_OK` outside heartbeats
|
||||
If the model accidentally includes `HEARTBEAT_OK` at the start or end of a
|
||||
normal (non-heartbeat) reply, Clawdbot strips the token and logs a verbose
|
||||
message. If the reply is only `HEARTBEAT_OK`, it is dropped.
|
||||
|
||||
### Outbound normalization (all providers)
|
||||
For **all providers** (WhatsApp/Web, Telegram, Slack, Discord, Signal, iMessage),
|
||||
Clawdbot applies the same filtering to tool summaries, streaming block replies,
|
||||
and final replies:
|
||||
- drop payloads that are only `HEARTBEAT_OK` with no media
|
||||
- strip `HEARTBEAT_OK` at the edges when mixed with other text
|
||||
|
||||
## Config
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
heartbeat: {
|
||||
every: "30m", // default: 30m (0m disables)
|
||||
every: "30m", // duration string: ms|s|m|h (0m disables)
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
target: "last", // last | whatsapp | telegram | discord | slack | signal | imessage | none
|
||||
to: "+15551234567", // optional provider-specific override (e.g. E.164 or chat id)
|
||||
prompt: "Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.",
|
||||
ackMaxChars: 30 // max chars allowed after HEARTBEAT_OK
|
||||
target: "last", // last | whatsapp | telegram | none
|
||||
to: "+15551234567", // optional override for whatsapp/telegram
|
||||
prompt: "HEARTBEAT" // optional override
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
- `every`: heartbeat interval (duration string; default unit minutes). Default:
|
||||
`30m`. Set to `0m` to disable.
|
||||
- `every`: heartbeat interval (duration string; default unit minutes). Omit or set
|
||||
to `0m` to disable.
|
||||
- `model`: optional model override for heartbeat runs (`provider/model`).
|
||||
- `target`: where heartbeat output is delivered.
|
||||
- `last` (default): send to the last used external provider.
|
||||
- `whatsapp` / `telegram` / `discord` / `slack` / `signal` / `imessage`: force the provider (optionally set `to`).
|
||||
- `last` (default): send to the last used external channel.
|
||||
- `whatsapp` / `telegram`: force the channel (optionally set `to`).
|
||||
- `none`: do not deliver externally; output stays in the session (WebChat-visible).
|
||||
- `to`: optional recipient override (E.164 for WhatsApp, chat id for Telegram).
|
||||
- `prompt`: optional override for the heartbeat body (default shown above). Safe to
|
||||
change; heartbeat acks are still keyed off `HEARTBEAT_OK`.
|
||||
- `ackMaxChars`: max chars allowed after `HEARTBEAT_OK` before delivery (default: 30).
|
||||
|
||||
## Cost awareness
|
||||
Heartbeats run full agent turns. Shorter intervals burn more tokens. If you
|
||||
don’t need frequent checks, increase `every`, pick a cheaper `model`, or set
|
||||
`target: "none"` to keep results internal.
|
||||
|
||||
## HEARTBEAT.md (optional)
|
||||
If a `HEARTBEAT.md` file exists in the workspace, the default prompt tells the
|
||||
agent to read it. Keep it tiny (short checklist or reminders) to avoid prompt
|
||||
bloat.
|
||||
- `prompt`: optional override for the heartbeat body (default: `HEARTBEAT`).
|
||||
|
||||
## Behavior
|
||||
- Runs in the main session (`main`, or `global` when scope is global).
|
||||
@@ -80,12 +57,6 @@ bloat.
|
||||
- If `target` resolves to no external destination (no last route or `none`), the
|
||||
heartbeat still runs but no outbound message is sent.
|
||||
|
||||
## Ideas for use
|
||||
- Check up on the user (light, respectful pings during daytime).
|
||||
- Handle mundane tasks (triage inboxes, summarize queues, refresh notes).
|
||||
- Nudge on open loops or reminders.
|
||||
- Background monitoring (health checks, status polling, low-priority alerts).
|
||||
|
||||
## Wake hook
|
||||
- The gateway exposes a heartbeat wake hook so cron/jobs/webhooks can request an
|
||||
immediate run (`requestHeartbeatNow`).
|
||||
|
||||
-150
@@ -1,150 +0,0 @@
|
||||
---
|
||||
summary: "Hubs that link to every Clawdbot doc"
|
||||
read_when:
|
||||
- You want a complete map of the documentation
|
||||
---
|
||||
# Docs hubs
|
||||
|
||||
Use these hubs to discover every page, including deep dives and reference docs that don’t appear in the left nav.
|
||||
|
||||
## Start here
|
||||
|
||||
- [Index](https://docs.clawd.bot)
|
||||
- [Getting Started](https://docs.clawd.bot/getting-started)
|
||||
- [Onboarding](https://docs.clawd.bot/onboarding)
|
||||
- [Wizard](https://docs.clawd.bot/wizard)
|
||||
- [Setup](https://docs.clawd.bot/setup)
|
||||
- [FAQ](https://docs.clawd.bot/faq)
|
||||
- [Configuration](https://docs.clawd.bot/configuration)
|
||||
- [Clawd (personal assistant)](https://docs.clawd.bot/clawd)
|
||||
- [Lore](https://docs.clawd.bot/lore)
|
||||
|
||||
## Installation + distribution
|
||||
|
||||
- [Docker](https://docs.clawd.bot/docker)
|
||||
- [Nix](https://docs.clawd.bot/nix)
|
||||
|
||||
## Core concepts
|
||||
|
||||
- [Architecture](https://docs.clawd.bot/architecture)
|
||||
- [Agent runtime](https://docs.clawd.bot/agent)
|
||||
- [Agent loop](https://docs.clawd.bot/agent-loop)
|
||||
- [Multi-agent routing](https://docs.clawd.bot/multi-agent)
|
||||
- [Sessions](https://docs.clawd.bot/session)
|
||||
- [Sessions (alias)](https://docs.clawd.bot/sessions)
|
||||
- [Session tools](https://docs.clawd.bot/session-tool)
|
||||
- [Queue](https://docs.clawd.bot/queue)
|
||||
- [Slash commands](https://docs.clawd.bot/slash-commands)
|
||||
- [RPC adapters](https://docs.clawd.bot/rpc)
|
||||
- [TypeBox schemas](https://docs.clawd.bot/typebox)
|
||||
- [Presence](https://docs.clawd.bot/presence)
|
||||
- [Discovery + transports](https://docs.clawd.bot/discovery)
|
||||
- [Bonjour](https://docs.clawd.bot/bonjour)
|
||||
- [Provider routing](https://docs.clawd.bot/provider-routing)
|
||||
- [Groups](https://docs.clawd.bot/groups)
|
||||
- [Group messages](https://docs.clawd.bot/group-messages)
|
||||
|
||||
## Providers + ingress
|
||||
|
||||
- [WhatsApp](https://docs.clawd.bot/whatsapp)
|
||||
- [Telegram](https://docs.clawd.bot/telegram)
|
||||
- [Telegram (grammY notes)](https://docs.clawd.bot/grammy)
|
||||
- [Slack](https://docs.clawd.bot/slack)
|
||||
- [Discord](https://docs.clawd.bot/discord)
|
||||
- [Signal](https://docs.clawd.bot/signal)
|
||||
- [iMessage](https://docs.clawd.bot/imessage)
|
||||
- [WebChat](https://docs.clawd.bot/webchat)
|
||||
- [Webhooks](https://docs.clawd.bot/webhook)
|
||||
- [Gmail Pub/Sub](https://docs.clawd.bot/gmail-pubsub)
|
||||
|
||||
## Gateway + operations
|
||||
|
||||
- [Gateway runbook](https://docs.clawd.bot/gateway)
|
||||
- [Gateway pairing](https://docs.clawd.bot/gateway/pairing)
|
||||
- [Gateway lock](https://docs.clawd.bot/gateway-lock)
|
||||
- [Background process](https://docs.clawd.bot/background-process)
|
||||
- [Health](https://docs.clawd.bot/health)
|
||||
- [Heartbeat](https://docs.clawd.bot/heartbeat)
|
||||
- [Doctor](https://docs.clawd.bot/doctor)
|
||||
- [Logging](https://docs.clawd.bot/logging)
|
||||
- [Dashboard](https://docs.clawd.bot/dashboard)
|
||||
- [Control UI](https://docs.clawd.bot/control-ui)
|
||||
- [Remote access](https://docs.clawd.bot/remote)
|
||||
- [Remote gateway README](https://docs.clawd.bot/remote-gateway-readme)
|
||||
- [Tailscale](https://docs.clawd.bot/tailscale)
|
||||
- [Security](https://docs.clawd.bot/security)
|
||||
- [Troubleshooting](https://docs.clawd.bot/troubleshooting)
|
||||
|
||||
## Tools + automation
|
||||
|
||||
- [Tools surface](https://docs.clawd.bot/tools)
|
||||
- [Bash tool](https://docs.clawd.bot/bash)
|
||||
- [Elevated mode](https://docs.clawd.bot/elevated)
|
||||
- [Cron + wakeups](https://docs.clawd.bot/cron)
|
||||
- [Thinking + verbose](https://docs.clawd.bot/thinking)
|
||||
- [Models](https://docs.clawd.bot/models)
|
||||
- [Agent send CLI](https://docs.clawd.bot/agent-send)
|
||||
- [Terminal UI](https://docs.clawd.bot/tui)
|
||||
- [Browser control](https://docs.clawd.bot/browser)
|
||||
- [Browser (Linux troubleshooting)](https://docs.clawd.bot/browser-linux-troubleshooting)
|
||||
|
||||
## Nodes, media, voice
|
||||
|
||||
- [Nodes overview](https://docs.clawd.bot/nodes)
|
||||
- [Camera](https://docs.clawd.bot/camera)
|
||||
- [Images](https://docs.clawd.bot/images)
|
||||
- [Audio](https://docs.clawd.bot/audio)
|
||||
- [Location command](https://docs.clawd.bot/location-command)
|
||||
- [Voice wake](https://docs.clawd.bot/voicewake)
|
||||
- [Talk mode](https://docs.clawd.bot/talk)
|
||||
|
||||
## Platforms
|
||||
|
||||
- [macOS app overview](https://docs.clawd.bot/macos)
|
||||
- [macOS dev setup](https://docs.clawd.bot/mac/dev-setup)
|
||||
- [macOS menu bar](https://docs.clawd.bot/mac/menu-bar)
|
||||
- [macOS voice wake](https://docs.clawd.bot/mac/voicewake)
|
||||
- [macOS voice overlay](https://docs.clawd.bot/mac/voice-overlay)
|
||||
- [macOS WebChat](https://docs.clawd.bot/mac/webchat)
|
||||
- [macOS Canvas](https://docs.clawd.bot/mac/canvas)
|
||||
- [macOS child process](https://docs.clawd.bot/mac/child-process)
|
||||
- [macOS health](https://docs.clawd.bot/mac/health)
|
||||
- [macOS icon](https://docs.clawd.bot/mac/icon)
|
||||
- [macOS logging](https://docs.clawd.bot/mac/logging)
|
||||
- [macOS permissions](https://docs.clawd.bot/mac/permissions)
|
||||
- [macOS remote](https://docs.clawd.bot/mac/remote)
|
||||
- [macOS signing](https://docs.clawd.bot/mac/signing)
|
||||
- [macOS release](https://docs.clawd.bot/mac/release)
|
||||
- [macOS bun gateway](https://docs.clawd.bot/mac/bun)
|
||||
- [macOS XPC](https://docs.clawd.bot/mac/xpc)
|
||||
- [macOS skills](https://docs.clawd.bot/mac/skills)
|
||||
- [macOS Peekaboo plan](https://docs.clawd.bot/mac/peekaboo)
|
||||
- [iOS node](https://docs.clawd.bot/ios)
|
||||
- [Android node](https://docs.clawd.bot/android)
|
||||
- [Windows app](https://docs.clawd.bot/windows)
|
||||
- [Linux app](https://docs.clawd.bot/linux)
|
||||
- [Web surfaces](https://docs.clawd.bot/web)
|
||||
|
||||
## Workspace + templates
|
||||
|
||||
- [Skills](https://docs.clawd.bot/skills)
|
||||
- [Skills config](https://docs.clawd.bot/skills-config)
|
||||
- [Default AGENTS](https://docs.clawd.bot/AGENTS.default)
|
||||
- [Templates: AGENTS](https://docs.clawd.bot/templates/AGENTS)
|
||||
- [Templates: BOOTSTRAP](https://docs.clawd.bot/templates/BOOTSTRAP)
|
||||
- [Templates: IDENTITY](https://docs.clawd.bot/templates/IDENTITY)
|
||||
- [Templates: SOUL](https://docs.clawd.bot/templates/SOUL)
|
||||
- [Templates: TOOLS](https://docs.clawd.bot/templates/TOOLS)
|
||||
- [Templates: USER](https://docs.clawd.bot/templates/USER)
|
||||
|
||||
## Experiments + proposals
|
||||
|
||||
- [Onboarding config protocol](https://docs.clawd.bot/onboarding-config-protocol)
|
||||
- [Research: memory](https://docs.clawd.bot/research/memory)
|
||||
- [Proposal: model config](https://docs.clawd.bot/proposals/model-config)
|
||||
|
||||
## Testing + release
|
||||
|
||||
- [Testing](https://docs.clawd.bot/test)
|
||||
- [Release checklist](https://docs.clawd.bot/RELEASING)
|
||||
- [Device models](https://docs.clawd.bot/device-models)
|
||||
+1
-32
@@ -13,31 +13,6 @@ Status: external CLI integration. No daemon.
|
||||
- JSON-RPC runs over stdin/stdout (one JSON object per line).
|
||||
- Gateway owns the process; no TCP port needed.
|
||||
|
||||
## 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.
|
||||
|
||||
## Requirements
|
||||
- macOS with Messages signed in.
|
||||
- Full Disk Access for Clawdbot + the `imsg` binary (Messages DB access).
|
||||
@@ -51,10 +26,7 @@ That’s closer to “remote provider instances” (or “multi-gateway aggregat
|
||||
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",
|
||||
@@ -65,9 +37,6 @@ That’s closer to “remote provider instances” (or “multi-gateway aggregat
|
||||
|
||||
Notes:
|
||||
- `allowFrom` accepts handles (phone/email) or `chat_id:<id>` entries.
|
||||
- Default: `imessage.dmPolicy="pairing"` — unknown DM senders get a pairing code (approve via `clawdbot pairing approve --provider imessage <code>`). `"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.
|
||||
|
||||
@@ -86,7 +55,7 @@ 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.
|
||||
- Group activation respects `imessage.groups."*".requireMention` and `routing.groupChat.mentionPatterns`.
|
||||
- Replies go back to the same `chat_id` (group or direct).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
+33
-54
@@ -19,37 +19,30 @@ read_when:
|
||||
<p align="center">
|
||||
<a href="https://github.com/clawdbot/clawdbot">GitHub</a> ·
|
||||
<a href="https://github.com/clawdbot/clawdbot/releases">Releases</a> ·
|
||||
<a href="https://docs.clawd.bot">Docs</a> ·
|
||||
<a href="https://docs.clawd.bot/clawd">Clawd setup</a>
|
||||
<a href="https://docs.clawdbot.com/">Docs</a> ·
|
||||
<a href="./clawd.md">Clawd setup</a>
|
||||
</p>
|
||||
|
||||
CLAWDBOT bridges WhatsApp (via WhatsApp Web / Baileys), Telegram (Bot API / grammY), Discord (Bot API / discord.js), and iMessage (imsg CLI) to coding agents like [Pi](https://github.com/badlogic/pi-mono).
|
||||
It’s built for [Clawd](https://clawd.me), a space lobster who needed a TARDIS.
|
||||
|
||||
## Start here
|
||||
|
||||
- **New install from zero:** https://docs.clawd.bot/getting-started
|
||||
- **Guided setup (recommended):** https://docs.clawd.bot/wizard (`clawdbot onboard`)
|
||||
|
||||
## How it works
|
||||
|
||||
```
|
||||
WhatsApp / Telegram / Discord
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────┐
|
||||
┌──────────────────────────┐
|
||||
│ Gateway │ ws://127.0.0.1:18789 (loopback-only)
|
||||
│ (single source) │ tcp://0.0.0.0:18790 (Bridge)
|
||||
│ │ http://<gateway-host>:18793
|
||||
│ │ /__clawdbot__/canvas/ (Canvas host)
|
||||
│ │ http://<gateway-host>:18793/__clawdbot__/canvas/ (Canvas host)
|
||||
└───────────┬───────────────┘
|
||||
│
|
||||
├─ Pi agent (RPC)
|
||||
├─ CLI (clawdbot …)
|
||||
├─ Chat UI (SwiftUI)
|
||||
├─ macOS app (Clawdbot.app)
|
||||
├─ iOS node via Bridge + pairing
|
||||
└─ Android node via Bridge + pairing
|
||||
└─ iOS node via Bridge + pairing
|
||||
```
|
||||
|
||||
Most operations flow through the **Gateway** (`clawdbot gateway`), a single long-running process that owns provider connections and the WebSocket control plane.
|
||||
@@ -60,8 +53,8 @@ Most operations flow through the **Gateway** (`clawdbot gateway`), a single long
|
||||
- **Loopback-first**: Gateway WS defaults to `ws://127.0.0.1:18789`.
|
||||
- For Tailnet access, run `clawdbot gateway --bind tailnet --token ...` (token is required for non-loopback binds).
|
||||
- **Bridge for nodes**: optional LAN/tailnet-facing bridge on `tcp://0.0.0.0:18790` for paired nodes (Bonjour-discoverable).
|
||||
- **Canvas host**: HTTP file server on `canvasHost.port` (default `18793`), serving `/__clawdbot__/canvas/` for node WebViews; see [`docs/configuration.md`](https://docs.clawd.bot/configuration) (`canvasHost`).
|
||||
- **Remote use**: SSH tunnel or tailnet/VPN; see [`docs/remote.md`](https://docs.clawd.bot/remote) and [`docs/discovery.md`](https://docs.clawd.bot/discovery).
|
||||
- **Canvas host**: HTTP file server on `canvasHost.port` (default `18793`), serving `/__clawdbot__/canvas/` for node WebViews; see `docs/configuration.md` (`canvasHost`).
|
||||
- **Remote use**: SSH tunnel or tailnet/VPN; see `docs/remote.md` and `docs/discovery.md`.
|
||||
|
||||
## Features (high level)
|
||||
|
||||
@@ -70,7 +63,6 @@ Most operations flow through the **Gateway** (`clawdbot gateway`), a single long
|
||||
- 🎮 **Discord Bot** — DMs + guild channels via discord.js
|
||||
- 💬 **iMessage** — Local imsg CLI integration (macOS)
|
||||
- 🤖 **Agent bridge** — Pi (RPC mode) with tool streaming
|
||||
- 🧠 **Multi-agent routing** — Route provider accounts/peers to isolated agents (workspace + per-agent sessions)
|
||||
- 🔐 **Subscription auth** — Anthropic (Claude Pro/Max) + OpenAI (ChatGPT/Codex) via OAuth
|
||||
- 💬 **Sessions** — Direct chats collapse into shared `main` (default); groups are isolated
|
||||
- 👥 **Group Chat Support** — Mention-based by default; owner can toggle `/activation always|mention`
|
||||
@@ -78,7 +70,6 @@ Most operations flow through the **Gateway** (`clawdbot gateway`), a single long
|
||||
- 🎤 **Voice notes** — Optional transcription hook
|
||||
- 🖥️ **WebChat + macOS app** — Local UI + menu bar companion for ops and voice wake
|
||||
- 📱 **iOS node** — Pairs as a node and exposes a Canvas surface
|
||||
- 📱 **Android node** — Pairs as a node and exposes Canvas + Chat + Camera
|
||||
|
||||
Note: legacy Claude/Codex/Gemini/Opencode paths have been removed; Pi is the only coding-agent path.
|
||||
|
||||
@@ -135,45 +126,34 @@ Example:
|
||||
## Docs
|
||||
|
||||
- Start here:
|
||||
- [Docs hubs (all pages linked)](https://docs.clawd.bot/hubs)
|
||||
- [FAQ](https://docs.clawd.bot/faq) ← *common questions answered*
|
||||
- [Configuration](https://docs.clawd.bot/configuration)
|
||||
- [Slash commands](https://docs.clawd.bot/slash-commands)
|
||||
- [Multi-agent routing](https://docs.clawd.bot/multi-agent)
|
||||
- [Updating / rollback](https://docs.clawd.bot/updating)
|
||||
- [Pairing (DM + nodes)](https://docs.clawd.bot/pairing)
|
||||
- [Nix mode](https://docs.clawd.bot/nix)
|
||||
- [Clawd personal assistant setup](https://docs.clawd.bot/clawd)
|
||||
- [Skills](https://docs.clawd.bot/skills)
|
||||
- [Skills config](https://docs.clawd.bot/skills-config)
|
||||
- [Workspace templates](https://docs.clawd.bot/templates/AGENTS)
|
||||
- [RPC adapters](https://docs.clawd.bot/rpc)
|
||||
- [Gateway runbook](https://docs.clawd.bot/gateway)
|
||||
- [Nodes (iOS/Android)](https://docs.clawd.bot/nodes)
|
||||
- [Web surfaces (Control UI)](https://docs.clawd.bot/web)
|
||||
- [Discovery + transports](https://docs.clawd.bot/discovery)
|
||||
- [Remote access](https://docs.clawd.bot/remote)
|
||||
- [FAQ](./faq.md) ← *common questions answered*
|
||||
- [Configuration](./configuration.md)
|
||||
- [Nix mode](./nix.md)
|
||||
- [Clawd personal assistant setup](./clawd.md)
|
||||
- [Skills](./skills.md)
|
||||
- [Skills config](./skills-config.md)
|
||||
- [Workspace templates](./templates/AGENTS.md)
|
||||
- [RPC adapters](./rpc.md)
|
||||
- [Gateway runbook](./gateway.md)
|
||||
- [Nodes (iOS/Android)](./nodes.md)
|
||||
- [Web surfaces (Control UI)](./web.md)
|
||||
- [Discovery + transports](./discovery.md)
|
||||
- [Remote access](./remote.md)
|
||||
- Providers and UX:
|
||||
- [WebChat](https://docs.clawd.bot/webchat)
|
||||
- [Control UI (browser)](https://docs.clawd.bot/control-ui)
|
||||
- [Telegram](https://docs.clawd.bot/telegram)
|
||||
- [Discord](https://docs.clawd.bot/discord)
|
||||
- [iMessage](https://docs.clawd.bot/imessage)
|
||||
- [Groups](https://docs.clawd.bot/groups)
|
||||
- [WhatsApp group messages](https://docs.clawd.bot/group-messages)
|
||||
- [Media: images](https://docs.clawd.bot/images)
|
||||
- [Media: audio](https://docs.clawd.bot/audio)
|
||||
- Companion apps:
|
||||
- [macOS app](https://docs.clawd.bot/macos)
|
||||
- [iOS app](https://docs.clawd.bot/ios)
|
||||
- [Android app](https://docs.clawd.bot/android)
|
||||
- [Windows app](https://docs.clawd.bot/windows)
|
||||
- [Linux app](https://docs.clawd.bot/linux)
|
||||
- [WebChat](./webchat.md)
|
||||
- [Control UI (browser)](./control-ui.md)
|
||||
- [Telegram](./telegram.md)
|
||||
- [Discord](./discord.md)
|
||||
- [iMessage](./imessage.md)
|
||||
- [Groups](./groups.md)
|
||||
- [WhatsApp group messages](./group-messages.md)
|
||||
- [Media: images](./images.md)
|
||||
- [Media: audio](./audio.md)
|
||||
- Ops and safety:
|
||||
- [Sessions](https://docs.clawd.bot/session)
|
||||
- [Cron + wakeups](https://docs.clawd.bot/cron)
|
||||
- [Security](https://docs.clawd.bot/security)
|
||||
- [Troubleshooting](https://docs.clawd.bot/troubleshooting)
|
||||
- [Sessions](./session.md)
|
||||
- [Cron + wakeups](./cron.md)
|
||||
- [Security](./security.md)
|
||||
- [Troubleshooting](./troubleshooting.md)
|
||||
|
||||
## The name
|
||||
|
||||
@@ -192,7 +172,6 @@ Example:
|
||||
## Core Contributors
|
||||
|
||||
- **Maxim Vovshin** (@Hyaxia, 36747317+Hyaxia@users.noreply.github.com) — Blogwatcher skill
|
||||
- **Nacho Iacovino** (@nachoiacovino, nacho.iacovino@gmail.com) — Location parsing (Telegram + WhatsApp)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
---
|
||||
summary: "Runbook: connect/pair the iOS node to a Clawdbot Gateway and drive its Canvas"
|
||||
read_when:
|
||||
- Pairing or reconnecting the iOS node
|
||||
- Debugging iOS bridge discovery or auth
|
||||
- Sending screen/canvas commands to iOS
|
||||
---
|
||||
|
||||
# iOS Node Connection Runbook
|
||||
|
||||
This is the practical “how do I connect the iOS node” guide:
|
||||
|
||||
**iOS app** ⇄ (Bonjour + TCP bridge) ⇄ **Gateway bridge** ⇄ (loopback WS) ⇄ **Gateway**
|
||||
|
||||
The Gateway WebSocket stays loopback-only (`ws://127.0.0.1:18789`). The iOS node talks to the LAN-facing **bridge** (default `tcp://0.0.0.0:18790`) and uses Gateway-owned pairing.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- You can run the Gateway on the “master” machine.
|
||||
- iOS node app can reach the gateway bridge:
|
||||
- Same LAN with Bonjour/mDNS, **or**
|
||||
- Same Tailscale tailnet using Wide-Area Bonjour / unicast DNS-SD (see below), **or**
|
||||
- Manual bridge host/port (fallback)
|
||||
- You can run the CLI (`clawdbot`) on the gateway machine (or via SSH).
|
||||
|
||||
## 1) Start the Gateway (with bridge enabled)
|
||||
|
||||
Bridge is enabled by default (disable via `CLAWDBOT_BRIDGE_ENABLED=0`).
|
||||
|
||||
```bash
|
||||
pnpm clawdbot gateway --port 18789 --verbose
|
||||
```
|
||||
|
||||
Confirm in logs you see something like:
|
||||
- `bridge listening on tcp://0.0.0.0:18790 (node)`
|
||||
|
||||
For tailnet-only setups (recommended for Vienna ⇄ London), bind the bridge to the gateway machine’s Tailscale IP instead:
|
||||
|
||||
- Set `bridge.bind: "tailnet"` in `~/.clawdbot/clawdbot.json` on the gateway host.
|
||||
- Restart the Gateway / macOS menubar app.
|
||||
|
||||
## 2) Verify Bonjour discovery (optional but recommended)
|
||||
|
||||
From the gateway machine:
|
||||
|
||||
```bash
|
||||
dns-sd -B _clawdbot-bridge._tcp local.
|
||||
```
|
||||
|
||||
You should see your gateway advertising `_clawdbot-bridge._tcp`.
|
||||
|
||||
If browse works, but the iOS node can’t connect, try resolving one instance:
|
||||
|
||||
```bash
|
||||
dns-sd -L "<instance name>" _clawdbot-bridge._tcp local.
|
||||
```
|
||||
|
||||
More debugging notes: `docs/bonjour.md`.
|
||||
|
||||
### Tailnet (Vienna ⇄ London) discovery via unicast DNS-SD
|
||||
|
||||
If the iOS node and the gateway are on different networks but connected via Tailscale, multicast mDNS won’t cross the boundary. Use Wide-Area Bonjour / unicast DNS-SD instead:
|
||||
|
||||
1) Set up a DNS-SD zone (example `clawdbot.internal.`) on the gateway host and publish `_clawdbot-bridge._tcp` records.
|
||||
2) Configure Tailscale split DNS for `clawdbot.internal` pointing at that DNS server.
|
||||
|
||||
Details and example CoreDNS config: `docs/bonjour.md`.
|
||||
|
||||
## 3) Connect from the iOS node app
|
||||
|
||||
In the iOS node app:
|
||||
- Pick the discovered bridge (or hit refresh).
|
||||
- If not paired yet, it will initiate pairing automatically.
|
||||
- After the first successful pairing, it will auto-reconnect **strictly to the last discovered gateway** on launch (including after reinstall), as long as the iOS Keychain entry is still present.
|
||||
|
||||
### Connection indicator (always visible)
|
||||
|
||||
The Settings tab icon shows a small status dot:
|
||||
- **Green**: connected to the bridge
|
||||
- **Yellow**: connecting (subtle pulse)
|
||||
- **Red**: not connected / error
|
||||
|
||||
## 4) Approve pairing (CLI)
|
||||
|
||||
On the gateway machine:
|
||||
|
||||
```bash
|
||||
clawdbot nodes pending
|
||||
```
|
||||
|
||||
Approve the request:
|
||||
|
||||
```bash
|
||||
clawdbot nodes approve <requestId>
|
||||
```
|
||||
|
||||
After approval, the iOS node receives/stores the token and reconnects authenticated.
|
||||
|
||||
Pairing details: `docs/gateway/pairing.md`.
|
||||
|
||||
## 5) Verify the node is connected
|
||||
|
||||
- In the macOS app: **Instances** tab should show something like `iOS Node (...)` with a green “Active” presence dot shortly after connect.
|
||||
- Via nodes status (paired + connected):
|
||||
```bash
|
||||
clawdbot nodes status
|
||||
```
|
||||
- Via Gateway (paired + connected):
|
||||
```bash
|
||||
clawdbot gateway call node.list --params "{}"
|
||||
```
|
||||
- Via Gateway presence (legacy-ish, still useful):
|
||||
```bash
|
||||
clawdbot gateway call system-presence --params "{}"
|
||||
```
|
||||
Look for the node `instanceId` (often a UUID).
|
||||
|
||||
## 6) Drive the iOS Canvas (draw / snapshot)
|
||||
|
||||
The iOS node runs a WKWebView “Canvas” scaffold which exposes:
|
||||
- `window.__clawdbot.canvas`
|
||||
- `window.__clawdbot.ctx` (2D context)
|
||||
- `window.__clawdbot.setStatus(title, subtitle)`
|
||||
|
||||
### Gateway Canvas Host (recommended for web content)
|
||||
|
||||
If you want the node to show real HTML/CSS/JS that the agent can edit on disk, point it at the Gateway canvas host.
|
||||
|
||||
Note: nodes always use the standalone canvas host on `canvasHost.port` (default `18793`), bound to the bridge interface.
|
||||
|
||||
1) Create `~/clawd/canvas/index.html` on the gateway host.
|
||||
|
||||
2) Navigate the node to it (LAN):
|
||||
|
||||
```bash
|
||||
clawdbot nodes invoke --node "iOS Node" --command canvas.navigate --params '{"url":"http://<gateway-hostname>.local:18793/__clawdbot__/canvas/"}'
|
||||
```
|
||||
|
||||
Notes:
|
||||
- The server injects a live-reload client into HTML and reloads on file changes.
|
||||
- A2UI is hosted on the same canvas host at `http://<gateway-host>:18793/__clawdbot__/a2ui/`.
|
||||
- Tailnet (optional): if both devices are on Tailscale, use a MagicDNS name or tailnet IP instead of `.local`, e.g. `http://<gateway-magicdns>:18793/__clawdbot__/canvas/`.
|
||||
- iOS may require App Transport Security allowances to load plain `http://` URLs; if it fails to load, prefer HTTPS or adjust the iOS app’s ATS config.
|
||||
|
||||
### Draw with `canvas.eval`
|
||||
|
||||
```bash
|
||||
clawdbot nodes invoke --node "iOS Node" --command canvas.eval --params "$(cat <<'JSON'
|
||||
{"javaScript":"(() => { const {ctx,setStatus} = window.__clawdbot; setStatus('Drawing','…'); ctx.clearRect(0,0,innerWidth,innerHeight); ctx.lineWidth=6; ctx.strokeStyle='#ff2d55'; ctx.beginPath(); ctx.moveTo(40,40); ctx.lineTo(innerWidth-40, innerHeight-40); ctx.stroke(); setStatus(null,null); return 'ok'; })()"}
|
||||
JSON
|
||||
)"
|
||||
```
|
||||
|
||||
### Snapshot with `canvas.snapshot`
|
||||
|
||||
```bash
|
||||
clawdbot nodes invoke --node 192.168.0.88 --command canvas.snapshot --params '{"maxWidth":900}'
|
||||
```
|
||||
|
||||
The response includes `{ format, base64 }` image data (default `format="jpeg"`; pass `{"format":"png"}` when you specifically need lossless PNG).
|
||||
|
||||
## Common gotchas
|
||||
|
||||
- **iOS in background:** all `canvas.*` commands fail fast with `NODE_BACKGROUND_UNAVAILABLE` (bring the iOS node app to foreground).
|
||||
- **Return to default scaffold:** `canvas.navigate` with `{"url":""}` or `{"url":"/"}` returns to the built-in scaffold page.
|
||||
- **mDNS blocked:** some networks block multicast; use a different LAN or plan a tailnet-capable bridge (see `docs/discovery.md`).
|
||||
- **Wrong node selector:** `--node` can be the node id (UUID), display name (e.g. `iOS Node`), IP, or an unambiguous prefix. If it’s ambiguous, the CLI will tell you.
|
||||
- **Stale pairing / Keychain cleared:** if the pairing token is missing (or iOS Keychain was wiped), the node must pair again; approve a new pending request.
|
||||
- **App reinstall but no reconnect:** the node restores `instanceId` + last bridge preference from Keychain; if it still comes up “unpaired”, verify Keychain persistence on your device/simulator and re-pair once.
|
||||
|
||||
## Related docs
|
||||
|
||||
- `docs/ios/spec.md` (design + architecture)
|
||||
- `docs/gateway.md` (gateway runbook)
|
||||
- `docs/gateway/pairing.md` (approval + storage)
|
||||
- `docs/bonjour.md` (discovery debugging)
|
||||
- `docs/discovery.md` (LAN vs tailnet vs SSH)
|
||||
+55
-191
@@ -1,182 +1,17 @@
|
||||
---
|
||||
summary: "iOS app (node): architecture + connection runbook"
|
||||
summary: "Plan for an iOS voice + canvas node that connects via a secure Bonjour-discovered macOS bridge"
|
||||
read_when:
|
||||
- Pairing or reconnecting the iOS node
|
||||
- Debugging iOS bridge discovery or auth
|
||||
- Sending screen/canvas commands to iOS
|
||||
- Designing iOS node + gateway integration
|
||||
- Extending the Gateway protocol for node/canvas commands
|
||||
- Implementing Bonjour pairing or transport security
|
||||
---
|
||||
# iOS App (Node)
|
||||
# iOS Node (internal) — Voice Trigger + Canvas
|
||||
|
||||
Status: prototype implemented (internal) · Date: 2025-12-13
|
||||
|
||||
## Connection Runbook
|
||||
Runbook (how to connect/pair + drive Canvas): `docs/ios/connect.md`
|
||||
|
||||
This is the practical “how do I connect the iOS node” guide:
|
||||
|
||||
**iOS app** ⇄ (Bonjour + TCP bridge) ⇄ **Gateway bridge** ⇄ (loopback WS) ⇄ **Gateway**
|
||||
|
||||
The Gateway WebSocket stays loopback-only (`ws://127.0.0.1:18789`). The iOS node talks to the LAN-facing **bridge** (default `tcp://0.0.0.0:18790`) and uses Gateway-owned pairing.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- You can run the Gateway on the “master” machine.
|
||||
- iOS node app can reach the gateway bridge:
|
||||
- Same LAN with Bonjour/mDNS, **or**
|
||||
- Same Tailscale tailnet using Wide-Area Bonjour / unicast DNS-SD (see below), **or**
|
||||
- Manual bridge host/port (fallback)
|
||||
- You can run the CLI (`clawdbot`) on the gateway machine (or via SSH).
|
||||
|
||||
### 1) Start the Gateway (with bridge enabled)
|
||||
|
||||
Bridge is enabled by default (disable via `CLAWDBOT_BRIDGE_ENABLED=0`).
|
||||
|
||||
```bash
|
||||
pnpm clawdbot gateway --port 18789 --verbose
|
||||
```
|
||||
|
||||
Confirm in logs you see something like:
|
||||
- `bridge listening on tcp://0.0.0.0:18790 (node)`
|
||||
|
||||
For tailnet-only setups (recommended for Vienna ⇄ London), bind the bridge to the gateway machine’s Tailscale IP instead:
|
||||
|
||||
- Set `bridge.bind: "tailnet"` in `~/.clawdbot/clawdbot.json` on the gateway host.
|
||||
- Restart the Gateway / macOS menubar app.
|
||||
|
||||
### 2) Verify Bonjour discovery (optional but recommended)
|
||||
|
||||
From the gateway machine:
|
||||
|
||||
```bash
|
||||
dns-sd -B _clawdbot-bridge._tcp local.
|
||||
```
|
||||
|
||||
You should see your gateway advertising `_clawdbot-bridge._tcp`.
|
||||
|
||||
If browse works, but the iOS node can’t connect, try resolving one instance:
|
||||
|
||||
```bash
|
||||
dns-sd -L "<instance name>" _clawdbot-bridge._tcp local.
|
||||
```
|
||||
|
||||
More debugging notes: [`docs/bonjour.md`](https://docs.clawd.bot/bonjour).
|
||||
|
||||
#### Tailnet (Vienna ⇄ London) discovery via unicast DNS-SD
|
||||
|
||||
If the iOS node and the gateway are on different networks but connected via Tailscale, multicast mDNS won’t cross the boundary. Use Wide-Area Bonjour / unicast DNS-SD instead:
|
||||
|
||||
1) Set up a DNS-SD zone (example `clawdbot.internal.`) on the gateway host and publish `_clawdbot-bridge._tcp` records.
|
||||
2) Configure Tailscale split DNS for `clawdbot.internal` pointing at that DNS server.
|
||||
|
||||
Details and example CoreDNS config: [`docs/bonjour.md`](https://docs.clawd.bot/bonjour).
|
||||
|
||||
### 3) Connect from the iOS node app
|
||||
|
||||
In the iOS node app:
|
||||
- Pick the discovered bridge (or hit refresh).
|
||||
- If not paired yet, it will initiate pairing automatically.
|
||||
- After the first successful pairing, it will auto-reconnect **strictly to the last discovered gateway** on launch (including after reinstall), as long as the iOS Keychain entry is still present.
|
||||
|
||||
#### Connection indicator (always visible)
|
||||
|
||||
The Settings tab icon shows a small status dot:
|
||||
- **Green**: connected to the bridge
|
||||
- **Yellow**: connecting (subtle pulse)
|
||||
- **Red**: not connected / error
|
||||
|
||||
### 4) Approve pairing (CLI)
|
||||
|
||||
On the gateway machine:
|
||||
|
||||
```bash
|
||||
clawdbot nodes pending
|
||||
```
|
||||
|
||||
Approve the request:
|
||||
|
||||
```bash
|
||||
clawdbot nodes approve <requestId>
|
||||
```
|
||||
|
||||
After approval, the iOS node receives/stores the token and reconnects authenticated.
|
||||
|
||||
Pairing details: [`docs/gateway/pairing.md`](https://docs.clawd.bot/gateway/pairing).
|
||||
|
||||
### 5) Verify the node is connected
|
||||
|
||||
- In the macOS app: **Instances** tab should show something like `iOS Node (...)` with a green “Active” presence dot shortly after connect.
|
||||
- Via nodes status (paired + connected):
|
||||
```bash
|
||||
clawdbot nodes status
|
||||
```
|
||||
- Via Gateway (paired + connected):
|
||||
```bash
|
||||
clawdbot gateway call node.list --params "{}"
|
||||
```
|
||||
- Via Gateway presence (legacy-ish, still useful):
|
||||
```bash
|
||||
clawdbot gateway call system-presence --params "{}"
|
||||
```
|
||||
Look for the node `instanceId` (often a UUID).
|
||||
|
||||
### 6) Drive the iOS Canvas (draw / snapshot)
|
||||
|
||||
The iOS node runs a WKWebView “Canvas” scaffold which exposes:
|
||||
- `window.__clawdbot.canvas`
|
||||
- `window.__clawdbot.ctx` (2D context)
|
||||
- `window.__clawdbot.setStatus(title, subtitle)`
|
||||
|
||||
#### Gateway Canvas Host (recommended for web content)
|
||||
|
||||
If you want the node to show real HTML/CSS/JS that the agent can edit on disk, point it at the Gateway canvas host.
|
||||
|
||||
Note: nodes always use the standalone canvas host on `canvasHost.port` (default `18793`), bound to the bridge interface.
|
||||
|
||||
1) Create `~/clawd/canvas/index.html` on the gateway host.
|
||||
|
||||
2) Navigate the node to it (LAN):
|
||||
|
||||
```bash
|
||||
clawdbot nodes invoke --node "iOS Node" --command canvas.navigate --params '{"url":"http://<gateway-hostname>.local:18793/__clawdbot__/canvas/"}'
|
||||
```
|
||||
|
||||
Notes:
|
||||
- The server injects a live-reload client into HTML and reloads on file changes.
|
||||
- A2UI is hosted on the same canvas host at `http://<gateway-host>:18793/__clawdbot__/a2ui/`.
|
||||
- Tailnet (optional): if both devices are on Tailscale, use a MagicDNS name or tailnet IP instead of `.local`, e.g. `http://<gateway-magicdns>:18793/__clawdbot__/canvas/`.
|
||||
- iOS may require App Transport Security allowances to load plain `http://` URLs; if it fails to load, prefer HTTPS or adjust the iOS app’s ATS config.
|
||||
|
||||
#### Draw with `canvas.eval`
|
||||
|
||||
```bash
|
||||
clawdbot nodes invoke --node "iOS Node" --command canvas.eval --params "$(cat <<'JSON'
|
||||
{"javaScript":"(() => { const {ctx,setStatus} = window.__clawdbot; setStatus('Drawing','…'); ctx.clearRect(0,0,innerWidth,innerHeight); ctx.lineWidth=6; ctx.strokeStyle='#ff2d55'; ctx.beginPath(); ctx.moveTo(40,40); ctx.lineTo(innerWidth-40, innerHeight-40); ctx.stroke(); setStatus(null,null); return 'ok'; })()"}
|
||||
JSON
|
||||
)"
|
||||
```
|
||||
|
||||
#### Snapshot with `canvas.snapshot`
|
||||
|
||||
```bash
|
||||
clawdbot nodes invoke --node 192.168.0.88 --command canvas.snapshot --params '{"maxWidth":900}'
|
||||
```
|
||||
|
||||
The response includes `{ format, base64 }` image data (default `format="jpeg"`; pass `{"format":"png"}` when you specifically need lossless PNG).
|
||||
|
||||
### Common gotchas
|
||||
|
||||
- **iOS in background:** all `canvas.*` commands fail fast with `NODE_BACKGROUND_UNAVAILABLE` (bring the iOS node app to foreground).
|
||||
- **Return to default scaffold:** `canvas.navigate` with `{"url":""}` or `{"url":"/"}` returns to the built-in scaffold page.
|
||||
- **mDNS blocked:** some networks block multicast; use a different LAN or plan a tailnet-capable bridge (see [`docs/discovery.md`](https://docs.clawd.bot/discovery)).
|
||||
- **Wrong node selector:** `--node` can be the node id (UUID), display name (e.g. `iOS Node`), IP, or an unambiguous prefix. If it’s ambiguous, the CLI will tell you.
|
||||
- **Stale pairing / Keychain cleared:** if the pairing token is missing (or iOS Keychain was wiped), the node must pair again; approve a new pending request.
|
||||
- **App reinstall but no reconnect:** the node restores `instanceId` + last bridge preference from Keychain; if it still comes up “unpaired”, verify Keychain persistence on your device/simulator and re-pair once.
|
||||
|
||||
## Design + Architecture
|
||||
|
||||
### Goals
|
||||
## Goals
|
||||
- Build an **iOS app** that acts as a **remote node** for Clawdbot:
|
||||
- **Voice trigger** (wake-word / always-listening intent) that forwards transcripts to the Gateway `agent` method.
|
||||
- **Canvas** surface that the agent can control: navigate, draw/render, evaluate JS, snapshot.
|
||||
@@ -193,13 +28,13 @@ Non-goals (v1):
|
||||
- Supporting arbitrary third-party “plugins” on iOS.
|
||||
- Perfect App Store compliance; this is **internal-only** initially.
|
||||
|
||||
### Current repo reality (constraints we respect)
|
||||
- The Gateway WebSocket server binds to `127.0.0.1:18789` ([`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server.ts)) with an optional `CLAWDBOT_GATEWAY_TOKEN`.
|
||||
- The Gateway exposes a Canvas file server (`canvasHost`) on `canvasHost.port` (default `18793`), so nodes can `canvas.navigate` to `http://<lanHost>:18793/__clawdbot__/canvas/` and auto-reload on file changes ([`docs/configuration.md`](https://docs.clawd.bot/configuration)).
|
||||
- macOS “Canvas” is controlled via the Gateway node protocol (`canvas.*`), matching iOS/Android ([`docs/mac/canvas.md`](https://docs.clawd.bot/mac/canvas)).
|
||||
## Current repo reality (constraints we respect)
|
||||
- The Gateway WebSocket server binds to `127.0.0.1:18789` (`src/gateway/server.ts`) with an optional `CLAWDBOT_GATEWAY_TOKEN`.
|
||||
- The Gateway exposes a Canvas file server (`canvasHost`) on `canvasHost.port` (default `18793`), so nodes can `canvas.navigate` to `http://<lanHost>:18793/__clawdbot__/canvas/` and auto-reload on file changes (`docs/configuration.md`).
|
||||
- macOS “Canvas” is controlled via the Gateway node protocol (`canvas.*`), matching iOS/Android (`docs/mac/canvas.md`).
|
||||
- Voice wake forwards via `GatewayChannel` to Gateway `agent` (mac app: `VoiceWakeForwarder` → `GatewayConnection.sendAgent`).
|
||||
|
||||
### Recommended topology (B): Gateway-owned Bridge + loopback Gateway
|
||||
## Recommended topology (B): Gateway-owned Bridge + loopback Gateway
|
||||
Keep the Node gateway loopback-only; expose a dedicated **gateway-owned bridge** to the LAN/tailnet.
|
||||
|
||||
**iOS App** ⇄ (TLS + pairing) ⇄ **Bridge (in gateway)** ⇄ (loopback) ⇄ **Gateway WS** (`ws://127.0.0.1:18789`)
|
||||
@@ -209,12 +44,12 @@ Why:
|
||||
- Centralizes auth, rate limiting, and allowlisting in the bridge.
|
||||
- Lets us unify “canvas node” semantics across mac + iOS without exposing raw gateway methods.
|
||||
|
||||
### Security plan (internal, but still robust)
|
||||
#### Transport
|
||||
## Security plan (internal, but still robust)
|
||||
### Transport
|
||||
- **Current (v0):** bridge is a LAN-facing **TCP** listener with token-based auth after pairing.
|
||||
- **Next:** wrap the bridge in **TLS** and prefer key-pinned or mTLS-like auth after pairing.
|
||||
|
||||
#### Pairing
|
||||
### Pairing
|
||||
- Bonjour discovery shows a candidate “Clawdbot Bridge” on the LAN.
|
||||
- First connection:
|
||||
1) iOS generates a keypair (Secure Enclave if available).
|
||||
@@ -227,7 +62,7 @@ Why:
|
||||
- Subsequent connections:
|
||||
- The bridge requires the paired identity. Unpaired clients get a structured “not paired” error and no access.
|
||||
|
||||
##### Gateway-owned pairing (Option B details)
|
||||
#### Gateway-owned pairing (Option B details)
|
||||
Pairing decisions must be owned by the Gateway (`clawd` / Node) so nodes can be approved without the macOS app running.
|
||||
|
||||
Key idea:
|
||||
@@ -237,14 +72,14 @@ Desired behavior:
|
||||
- If the Swift UI is present: show alert with Approve/Reject/Later.
|
||||
- If the Swift UI is not present: `clawdbot` CLI can list pending requests and approve/reject.
|
||||
|
||||
See [`docs/gateway/pairing.md`](https://docs.clawd.bot/gateway/pairing) for the API/events and storage.
|
||||
See `docs/gateway/pairing.md` for the API/events and storage.
|
||||
|
||||
CLI (headless approvals):
|
||||
- `clawdbot nodes pending`
|
||||
- `clawdbot nodes approve <requestId>`
|
||||
- `clawdbot nodes reject <requestId>`
|
||||
|
||||
#### Authorization / scope control (bridge-side ACL)
|
||||
### Authorization / scope control (bridge-side ACL)
|
||||
The bridge must not be a raw proxy to every gateway method.
|
||||
|
||||
- Allow by default:
|
||||
@@ -258,16 +93,16 @@ The bridge must not be a raw proxy to every gateway method.
|
||||
- voice forwards per minute
|
||||
- snapshot frequency / payload size
|
||||
|
||||
### Protocol unification: add “node/canvas” to Gateway protocol
|
||||
#### Principle
|
||||
## Protocol unification: add “node/canvas” to Gateway protocol
|
||||
### Principle
|
||||
Unify mac Canvas + iOS Canvas under a single conceptual surface:
|
||||
- The agent talks to the Gateway using a stable method set (typed protocol).
|
||||
- The Gateway routes node-targeted requests to:
|
||||
- local mac Canvas implementation, or
|
||||
- remote iOS node via the bridge
|
||||
|
||||
#### Minimal protocol additions (v1)
|
||||
Add to [`src/gateway/protocol/schema.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/protocol/schema.ts) (and regenerate Swift models):
|
||||
### Minimal protocol additions (v1)
|
||||
Add to `src/gateway/protocol/schema.ts` (and regenerate Swift models):
|
||||
|
||||
**Identity**
|
||||
- Node identity comes from `connect.params.client.instanceId` (stable), and `connect.params.client.mode = "node"` (or `"ios-node"`).
|
||||
@@ -282,7 +117,7 @@ Add to [`src/gateway/protocol/schema.ts`](https://github.com/clawdbot/clawdbot/b
|
||||
- `node.event` → async node status/errors
|
||||
- e.g. background/foreground transitions, voice availability, canvas availability
|
||||
|
||||
#### Node command set (canvas)
|
||||
### Node command set (canvas)
|
||||
These are values for `node.invoke.command`:
|
||||
- `canvas.present` / `canvas.hide`
|
||||
- `canvas.navigate` with `{ url }` (loads a URL; use `""` or `"/"` to return to the default scaffold)
|
||||
@@ -298,7 +133,7 @@ Result pattern:
|
||||
- Request is a standard `req/res` with `ok` / `error`.
|
||||
- Long operations (loads, streaming drawing, etc.) may also emit `node.event` progress.
|
||||
|
||||
##### Current (implemented)
|
||||
#### Current (implemented)
|
||||
As of 2025-12-13, the Gateway supports `node.invoke` for bridge-connected nodes.
|
||||
|
||||
Example: draw a diagonal line on the iOS Canvas:
|
||||
@@ -364,9 +199,38 @@ open Clawdbot.xcodeproj
|
||||
- Credentials:
|
||||
- Keychain (paired identity + bridge trust anchor)
|
||||
|
||||
## Related docs
|
||||
### macOS
|
||||
- Keep current Canvas root (already implemented):
|
||||
- `~/Library/Application Support/Clawdbot/canvas/<session>/...`
|
||||
- Bridge state:
|
||||
- No local pairing store (pairing is gateway-owned).
|
||||
- Any local bridge-only state should remain private under Application Support.
|
||||
|
||||
- [`docs/gateway.md`](https://docs.clawd.bot/gateway) (gateway runbook)
|
||||
- [`docs/gateway/pairing.md`](https://docs.clawd.bot/gateway/pairing) (approval + storage)
|
||||
- [`docs/bonjour.md`](https://docs.clawd.bot/bonjour) (discovery debugging)
|
||||
- [`docs/discovery.md`](https://docs.clawd.bot/discovery) (LAN vs tailnet vs SSH)
|
||||
### Gateway (node)
|
||||
- Pairing (source of truth):
|
||||
- `~/.clawdbot/nodes/paired.json`
|
||||
- `~/.clawdbot/nodes/pending.json` (or `pending/*.json` for auditability)
|
||||
|
||||
## Rollout plan (phased)
|
||||
1) **Bridge discovery + pairing (mac + iOS)**
|
||||
- Bonjour browse + resolve
|
||||
- Approve prompt on mac
|
||||
- Persist pairing in Keychain/App Support
|
||||
2) **Voice-only node**
|
||||
- iOS voice wake toggle
|
||||
- Forward transcript to Gateway `agent` via bridge
|
||||
- Presence beacons via `system-event` (or node.event)
|
||||
3) **Protocol additions for nodes**
|
||||
- Add `node.list` / `node.invoke` / `node.event` to Gateway
|
||||
- Implement bridge routing + ACLs
|
||||
4) **iOS canvas**
|
||||
- WKWebView canvas surface
|
||||
- `canvas.navigate/eval/snapshot`
|
||||
- Background fast-fail for `canvas.*`
|
||||
5) **Unify mac Canvas under the same node.invoke**
|
||||
- Keep existing implementation, but expose it through the unified protocol path so the agent uses one API.
|
||||
|
||||
## Open questions
|
||||
- Should `connect.params.client.mode` be `"node"` with `platform="ios ..."` or a distinct mode `"ios-node"`? (Presence filtering currently excludes `"cli"` only.)
|
||||
- Do we want a “permissions” model per node (voice only vs voice+canvas) at pairing time?
|
||||
- Should loading arbitrary websites via `canvas.navigate` allow any https URL, or enforce an allowlist to reduce risk?
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
summary: "Linux app status + contribution call"
|
||||
read_when:
|
||||
- Looking for Linux companion app status
|
||||
- Planning platform coverage or contributions
|
||||
---
|
||||
# Linux App
|
||||
|
||||
Clawdbot core is fully supported on Linux. The core is written in TypeScript, so it runs anywhere Node runs.
|
||||
|
||||
We do not have a Linux companion app yet. It is planned, and we would love contributions to make it happen.
|
||||
@@ -1,46 +0,0 @@
|
||||
---
|
||||
summary: "Inbound provider location parsing (Telegram + WhatsApp) and context fields"
|
||||
read_when:
|
||||
- Adding or modifying provider location parsing
|
||||
- Using location context fields in agent prompts or tools
|
||||
---
|
||||
|
||||
# Provider location parsing
|
||||
|
||||
Clawdbot normalizes shared locations from chat providers 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)
|
||||
|
||||
## Text formatting
|
||||
Locations are rendered as friendly lines without brackets:
|
||||
|
||||
- Pin:
|
||||
- `📍 48.858844, 2.294351 ±12m`
|
||||
- Named place:
|
||||
- `📍 Eiffel Tower — Champ de Mars, Paris (48.858844, 2.294351 ±12m)`
|
||||
- Live share:
|
||||
- `🛰 Live location: 48.858844, 2.294351 ±12m`
|
||||
|
||||
If the provider includes a caption/comment, it is appended on the next line:
|
||||
```
|
||||
📍 48.858844, 2.294351 ±12m
|
||||
Meet here
|
||||
```
|
||||
|
||||
## Context fields
|
||||
When a location is present, these fields are added to `ctx`:
|
||||
- `LocationLat` (number)
|
||||
- `LocationLon` (number)
|
||||
- `LocationAccuracy` (number, meters; optional)
|
||||
- `LocationName` (string; optional)
|
||||
- `LocationAddress` (string; optional)
|
||||
- `LocationSource` (`pin | place | live`)
|
||||
- `LocationIsLive` (boolean)
|
||||
|
||||
## Provider notes
|
||||
- **Telegram**: venues map to `LocationName/LocationAddress`; live locations use `live_period`.
|
||||
- **WhatsApp**: `locationMessage.comment` and `liveLocationMessage.caption` are appended as the caption line.
|
||||
+4
-15
@@ -14,7 +14,7 @@ Clawdbot has two log “surfaces”:
|
||||
|
||||
## File-based logger
|
||||
|
||||
Clawdbot uses a file logger backed by `tslog` ([`src/logging.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/logging.ts)).
|
||||
Clawdbot uses a file logger backed by `tslog` (`src/logging.ts`).
|
||||
|
||||
- Default rolling log file is under `/tmp/clawdbot/` (one file per day): `clawdbot-YYYY-MM-DD.log`
|
||||
- The log file path and level can be configured via `~/.clawdbot/clawdbot.json`:
|
||||
@@ -33,7 +33,7 @@ The file format is one JSON object per line.
|
||||
|
||||
## Console capture
|
||||
|
||||
The CLI entrypoint enables console capture ([`src/index.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/index.ts) calls `enableConsoleCapture()`).
|
||||
The CLI entrypoint enables console capture (`src/index.ts` calls `enableConsoleCapture()`).
|
||||
That means every `console.log/info/warn/error/debug/trace` is also written into the file logs,
|
||||
while still behaving normally on stdout/stderr.
|
||||
|
||||
@@ -42,17 +42,6 @@ You can tune console verbosity independently via:
|
||||
- `logging.consoleLevel` (default `info`)
|
||||
- `logging.consoleStyle` (`pretty` | `compact` | `json`)
|
||||
|
||||
## Tool summary redaction
|
||||
|
||||
Verbose tool summaries (e.g. `🛠️ bash: ...`) can mask sensitive tokens before they hit the
|
||||
console stream. This is **tools-only** and does not alter file logs.
|
||||
|
||||
- `logging.redactSensitive`: `off` | `tools` (default: `tools`)
|
||||
- `logging.redactPatterns`: array of regex strings (overrides defaults)
|
||||
- Use raw regex strings (auto `gi`), or `/pattern/flags` if you need custom flags.
|
||||
- Matches are masked by keeping the first 6 + last 4 chars (length >= 18), otherwise `***`.
|
||||
- Defaults cover common key assignments, CLI flags, JSON fields, bearer headers, PEM blocks, and popular token prefixes.
|
||||
|
||||
## Gateway WebSocket logs
|
||||
|
||||
The gateway prints WebSocket protocol logs in two modes:
|
||||
@@ -89,8 +78,8 @@ clawdbot gateway --verbose --ws-log full
|
||||
|
||||
Clawdbot formats console logs via a small wrapper on top of the existing stack:
|
||||
|
||||
- **tslog** for structured file logs ([`src/logging.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/logging.ts))
|
||||
- **chalk** for colors ([`src/globals.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/globals.ts))
|
||||
- **tslog** for structured file logs (`src/logging.ts`)
|
||||
- **chalk** for colors (`src/globals.ts`)
|
||||
|
||||
The console formatter is **TTY-aware** and prints consistent, prefixed lines.
|
||||
Subsystem loggers are created via `createSubsystemLogger("gateway")`.
|
||||
|
||||
+11
-11
@@ -15,7 +15,7 @@ Goal: ship **Clawdbot.app** with a self-contained relay binary that can run both
|
||||
App bundle layout:
|
||||
|
||||
- `Clawdbot.app/Contents/Resources/Relay/clawdbot`
|
||||
- bun `--compile` relay executable built from [`dist/macos/relay.js`](https://github.com/clawdbot/clawdbot/blob/main/dist/macos/relay.js)
|
||||
- bun `--compile` relay executable built from `dist/macos/relay.js`
|
||||
- Supports:
|
||||
- `clawdbot …` (CLI)
|
||||
- `clawdbot gateway-daemon …` (LaunchAgent daemon)
|
||||
@@ -31,7 +31,7 @@ Why the sidecar files matter:
|
||||
## Build pipeline
|
||||
|
||||
Packaging script:
|
||||
- [`scripts/package-mac-app.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/package-mac-app.sh)
|
||||
- `scripts/package-mac-app.sh`
|
||||
|
||||
It builds:
|
||||
- TS: `pnpm exec tsc`
|
||||
@@ -47,7 +47,7 @@ Important bundler flags:
|
||||
|
||||
Version injection:
|
||||
- `--define "__CLAWDBOT_VERSION__=\"<pkg version>\""`
|
||||
- [`src/version.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/version.ts) also supports `__CLAWDBOT_VERSION__` (and `CLAWDBOT_BUNDLED_VERSION`) so `--version` doesn’t depend on reading `package.json` at runtime.
|
||||
- `src/version.ts` also supports `__CLAWDBOT_VERSION__` (and `CLAWDBOT_BUNDLED_VERSION`) so `--version` doesn’t depend on reading `package.json` at runtime.
|
||||
|
||||
## Launchd (Gateway as LaunchAgent)
|
||||
|
||||
@@ -58,7 +58,7 @@ Plist location (per-user):
|
||||
- `~/Library/LaunchAgents/com.clawdbot.gateway.plist`
|
||||
|
||||
Manager:
|
||||
- [`apps/macos/Sources/Clawdbot/GatewayLaunchAgentManager.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/Clawdbot/GatewayLaunchAgentManager.swift)
|
||||
- `apps/macos/Sources/Clawdbot/GatewayLaunchAgentManager.swift`
|
||||
|
||||
Behavior:
|
||||
- “Clawdbot Active” enables/disables the LaunchAgent.
|
||||
@@ -77,7 +77,7 @@ Symptom (when mis-signed):
|
||||
|
||||
Fix:
|
||||
- The bun executable needs JIT-ish permissions under hardened runtime.
|
||||
- [`scripts/codesign-mac-app.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/codesign-mac-app.sh) signs `Relay/clawdbot` with:
|
||||
- `scripts/codesign-mac-app.sh` signs `Relay/clawdbot` with:
|
||||
- `com.apple.security.cs.allow-jit`
|
||||
- `com.apple.security.cs.allow-unsigned-executable-memory`
|
||||
|
||||
@@ -87,17 +87,17 @@ Problem:
|
||||
- bun can’t load some native Node addons like `sharp` (and we don’t want to ship native addon trees for the gateway).
|
||||
|
||||
Solution:
|
||||
- Central helper [`src/media/image-ops.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/media/image-ops.ts)
|
||||
- Central helper `src/media/image-ops.ts`
|
||||
- Prefers `/usr/bin/sips` on macOS (esp. when running under bun)
|
||||
- Falls back to `sharp` when available (Node/dev)
|
||||
- Used by:
|
||||
- [`src/web/media.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/web/media.ts) (optimize inbound/outbound images)
|
||||
- [`src/browser/screenshot.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/browser/screenshot.ts)
|
||||
- [`src/agents/pi-tools.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/agents/pi-tools.ts) (image sanitization)
|
||||
- `src/web/media.ts` (optimize inbound/outbound images)
|
||||
- `src/browser/screenshot.ts`
|
||||
- `src/agents/pi-tools.ts` (image sanitization)
|
||||
|
||||
## Browser control server
|
||||
|
||||
The Gateway starts the browser control server (loopback only) from [`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server.ts).
|
||||
The Gateway starts the browser control server (loopback only) from `src/gateway/server.ts`.
|
||||
It’s started from the relay daemon process, so the relay binary includes Playwright deps.
|
||||
|
||||
## Tests / smoke checks
|
||||
@@ -125,7 +125,7 @@ Bun may leave dotfiles like `*.bun-build` in the repo root or subfolders.
|
||||
|
||||
## DMG styling (human installer)
|
||||
|
||||
[`scripts/create-dmg.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/create-dmg.sh) styles the DMG via Finder AppleScript.
|
||||
`scripts/create-dmg.sh` styles the DMG via Finder AppleScript.
|
||||
|
||||
Rules of thumb:
|
||||
- Use a **72dpi** background image that matches the Finder window size in points.
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ read_when:
|
||||
|
||||
Status: draft spec · Date: 2025-12-12
|
||||
|
||||
Note: for iOS/Android nodes that should render agent-edited HTML/CSS/JS over the network, prefer the Gateway `canvasHost` (serves `~/clawd/canvas` over LAN/tailnet with live reload). A2UI is also **hosted by the Gateway** over HTTP. This doc focuses on the macOS in-app canvas panel. See [`docs/configuration.md`](https://docs.clawd.bot/configuration).
|
||||
Note: for iOS/Android nodes that should render agent-edited HTML/CSS/JS over the network, prefer the Gateway `canvasHost` (serves `~/clawd/canvas` over LAN/tailnet with live reload). A2UI is also **hosted by the Gateway** over HTTP. This doc focuses on the macOS in-app canvas panel. See `docs/configuration.md`.
|
||||
|
||||
Clawdbot can embed an agent-controlled “visual workspace” panel (“Canvas”) inside the macOS app using `WKWebView`, served via a **custom URL scheme** (no loopback HTTP port required).
|
||||
|
||||
@@ -81,7 +81,7 @@ Canvas is exposed via the Gateway **node bridge**, so the agent can:
|
||||
This should be modeled after `WebChatManager`/`WebChatSwiftUIWindowController` but targeting `clawdbot-canvas://…` URLs.
|
||||
|
||||
Related:
|
||||
- For “invoke the agent again from UI” flows, prefer the macOS deep link scheme (`clawdbot://agent?...`) so *any* UI surface (Canvas, WebChat, native views) can trigger a new agent run. See [`docs/macos.md`](https://docs.clawd.bot/macos).
|
||||
- For “invoke the agent again from UI” flows, prefer the macOS deep link scheme (`clawdbot://agent?...`) so *any* UI surface (Canvas, WebChat, native views) can trigger a new agent run. See `docs/clawdbot-mac.md`.
|
||||
|
||||
## Agent commands (current)
|
||||
|
||||
|
||||
+2
-17
@@ -57,26 +57,11 @@ The macOS app requires a symlink named `clawdbot` in `/usr/local/bin` or `/opt/h
|
||||
|
||||
Alternatively, you can manually link it from your Admin account:
|
||||
```bash
|
||||
sudo ln -sf "/Users/$(whoami)/Projects/clawdbot/dist/Clawdbot.app/Contents/Resources/Relay/clawdbot" /usr/local/bin/clawdbot
|
||||
sudo ln -sf "/Users/$(whoami)/clawdbot/dist/Clawdbot.app/Contents/Resources/Relay/clawdbot" /usr/local/bin/clawdbot
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Fails: Toolchain or SDK Mismatch
|
||||
The macOS app build expects the latest macOS SDK and Swift 6.2 toolchain.
|
||||
|
||||
**System dependencies (required):**
|
||||
- **Latest macOS version available in Software Update** (required by Xcode 26.2 SDKs)
|
||||
- **Xcode 26.2** (Swift 6.2 toolchain)
|
||||
|
||||
**Checks:**
|
||||
```bash
|
||||
xcodebuild -version
|
||||
xcrun swift --version
|
||||
```
|
||||
|
||||
If versions don’t match, update macOS/Xcode and re-run the build.
|
||||
|
||||
### App Crashes on Permission Grant
|
||||
If the app crashes when you try to allow **Speech Recognition** or **Microphone** access, it may be due to a corrupted TCC cache or signature mismatch.
|
||||
|
||||
@@ -85,7 +70,7 @@ If the app crashes when you try to allow **Speech Recognition** or **Microphone*
|
||||
```bash
|
||||
tccutil reset All com.clawdbot.mac.debug
|
||||
```
|
||||
2. If that fails, change the `BUNDLE_ID` temporarily in [`scripts/package-mac-app.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/package-mac-app.sh) to force a "clean slate" from macOS.
|
||||
2. If that fails, change the `BUNDLE_ID` temporarily in `scripts/package-mac-app.sh` to force a "clean slate" from macOS.
|
||||
|
||||
### Gateway "Starting..." indefinitely
|
||||
If the gateway status stays on "Starting...", check if a zombie process is holding the port:
|
||||
|
||||
+1
-1
@@ -25,4 +25,4 @@ How to see whether the WhatsApp Web/Baileys bridge is healthy from the menu bar
|
||||
- Cache the last good snapshot and the last error separately to avoid flicker; show the timestamp of each.
|
||||
|
||||
## When in doubt
|
||||
- You can still use the CLI flow in [`docs/health.md`](https://docs.clawd.bot/health) (`clawdbot status`, `clawdbot status --deep`, `clawdbot health --json`) and tail `/tmp/clawdbot/clawdbot-*.log` for `web-heartbeat` / `web-reconnect`.
|
||||
- You can still use the CLI flow in `docs/health.md` (`clawdbot status`, `clawdbot status --deep`, `clawdbot health --json`) and tail `/tmp/clawdbot/clawdbot-*.log` for `web-heartbeat` / `web-reconnect`.
|
||||
@@ -23,7 +23,7 @@ Peekaboo’s privileged execution moved from “CLI → XPC helper” to “CLI
|
||||
- It lets us piggyback on **either** Peekaboo.app’s permissions **or** Clawdbot.app’s permissions (whichever is running).
|
||||
- It avoids “two apps with two TCC bubbles” unless needed.
|
||||
|
||||
Reference (Peekaboo submodule): `Peekaboo/docs/bridge-host.md`.
|
||||
Reference (Peekaboo submodule): `docs/bridge-host.md`.
|
||||
|
||||
## Architecture
|
||||
### Processes
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ Use the release note generator so Sparkle renders formatted HTML notes:
|
||||
```bash
|
||||
SPARKLE_PRIVATE_KEY_FILE=/Users/steipete/Library/CloudStorage/Dropbox/Backup/Sparkle/ed25519-private-key scripts/make_appcast.sh dist/Clawdbot-0.1.0.zip https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml
|
||||
```
|
||||
Generates HTML release notes from `CHANGELOG.md` (via [`scripts/changelog-to-html.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/changelog-to-html.sh)) and embeds them in the appcast entry.
|
||||
Generates HTML release notes from `CHANGELOG.md` (via `scripts/changelog-to-html.sh`) and embeds them in the appcast entry.
|
||||
Commit the updated `appcast.xml` alongside the release assets (zip + dSYM) when publishing.
|
||||
|
||||
## Publish & verify
|
||||
|
||||
+3
-3
@@ -5,11 +5,11 @@ read_when:
|
||||
---
|
||||
# mac signing (debug builds)
|
||||
|
||||
This app is usually built from [`scripts/package-mac-app.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/package-mac-app.sh), which now:
|
||||
This app is usually built from `scripts/package-mac-app.sh`, which now:
|
||||
|
||||
- sets a stable debug bundle identifier: `com.clawdbot.mac.debug`
|
||||
- writes the Info.plist with that bundle id (override via `BUNDLE_ID=...`)
|
||||
- calls [`scripts/codesign-mac-app.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/codesign-mac-app.sh) to sign the main binary, bundled CLI, and app bundle so macOS treats each rebuild as the same signed bundle and keeps TCC permissions (notifications, accessibility, screen recording, mic, speech). For stable permissions, use a real signing identity; ad-hoc is opt-in and fragile (see [`docs/mac/permissions.md`](https://docs.clawd.bot/mac/permissions)).
|
||||
- calls `scripts/codesign-mac-app.sh` to sign the main binary, bundled CLI, and app bundle so macOS treats each rebuild as the same signed bundle and keeps TCC permissions (notifications, accessibility, screen recording, mic, speech). For stable permissions, use a real signing identity; ad-hoc is opt-in and fragile (see `docs/mac/permissions.md`).
|
||||
- uses `CODESIGN_TIMESTAMP=auto` by default; it enables trusted timestamps for Developer ID signatures. Set `CODESIGN_TIMESTAMP=off` to skip timestamping (offline debug builds).
|
||||
- inject build metadata into Info.plist: `ClawdbotBuildTimestamp` (UTC) and `ClawdbotGitCommit` (short hash) so the About pane can show build, git, and debug/release channel.
|
||||
- **Packaging requires Bun**: The embedded gateway relay is compiled using `bun`. Ensure it is installed (`curl -fsSL https://bun.sh/install | bash`).
|
||||
@@ -26,7 +26,7 @@ SIGN_IDENTITY="-" scripts/package-mac-app.sh # explicit ad-hoc (same cave
|
||||
```
|
||||
|
||||
### Ad-hoc Signing Note
|
||||
When signing with `SIGN_IDENTITY="-"` (ad-hoc), the script automatically disables the **Hardened Runtime** (`--options runtime`). This is necessary to prevent crashes when the app attempts to load embedded frameworks (like Sparkle) that do not share the same Team ID. Ad-hoc signatures also break TCC permission persistence; see [`docs/mac/permissions.md`](https://docs.clawd.bot/mac/permissions) for recovery steps.
|
||||
When signing with `SIGN_IDENTITY="-"` (ad-hoc), the script automatically disables the **Hardened Runtime** (`--options runtime`). This is necessary to prevent crashes when the app attempts to load embedded frameworks (like Sparkle) that do not share the same Team ID. Ad-hoc signatures also break TCC permission persistence; see `docs/mac/permissions.md` for recovery steps.
|
||||
|
||||
## Build metadata for About
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ Hardening:
|
||||
|
||||
## Forwarding behavior
|
||||
- When Voice Wake is enabled, transcripts are forwarded to the active gateway/agent (the same local vs remote mode used by the rest of the mac app).
|
||||
- Replies are delivered to the **last-used main provider** (WhatsApp/Telegram/Discord/WebChat). If delivery fails, the error is logged and the run is still visible via WebChat/session logs.
|
||||
- Replies are delivered to the **last-used main surface** (WhatsApp/Telegram/Discord/WebChat). If delivery fails, the error is logged and the run is still visible via WebChat/session logs.
|
||||
|
||||
## Forwarding payload
|
||||
- `VoiceWakeForwarder.prefixedTranscript(_:)` prepends the machine hint before sending. Shared between wake-word and push-to-talk paths.
|
||||
|
||||
+2
-2
@@ -13,10 +13,10 @@ The macOS menu bar app shows the WebChat UI as a native SwiftUI view and reuses
|
||||
## Launch & debugging
|
||||
- Manual: Lobster menu → “Open Chat”.
|
||||
- Auto-open for testing: run `dist/Clawdbot.app/Contents/MacOS/Clawdbot --webchat` (or pass `--webchat` to the binary launched by launchd). The window opens on startup.
|
||||
- Logs: see [`./scripts/clawlog.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/clawlog.sh) (subsystem `com.clawdbot`, category `WebChatSwiftUI`).
|
||||
- Logs: see `./scripts/clawlog.sh` (subsystem `com.clawdbot`, category `WebChatSwiftUI`).
|
||||
|
||||
## How it’s wired
|
||||
- Implementation: [`apps/macos/Sources/Clawdbot/WebChatSwiftUI.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/Clawdbot/WebChatSwiftUI.swift) hosts `ClawdbotChatUI` and speaks to the Gateway over `GatewayConnection`.
|
||||
- Implementation: `apps/macos/Sources/Clawdbot/WebChatSwiftUI.swift` hosts `ClawdbotChatUI` and speaks to the Gateway over `GatewayConnection`.
|
||||
- Data plane: Gateway WebSocket methods `chat.history`, `chat.send`, `chat.abort`; events `chat`, `agent`, `presence`, `tick`, `health`.
|
||||
- Session: usually primary (`main`); multiple transports (WhatsApp/Telegram/Discord/Desktop) share the same key. The onboarding flow uses a dedicated `onboarding` session to keep first-run setup separate.
|
||||
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@ read_when:
|
||||
- UI automation uses a separate UNIX socket named `bridge.sock` and the PeekabooBridge JSON protocol.
|
||||
- Host preference order (client-side): Peekaboo.app → Claude.app → Clawdbot.app → local execution.
|
||||
- Security: bridge hosts require TeamID `Y5PE65HELJ`; DEBUG-only same-UID escape hatch is guarded by `PEEKABOO_ALLOW_UNSIGNED_SOCKET_CLIENTS=1` (Peekaboo convention).
|
||||
- See: [`docs/mac/peekaboo.md`](https://docs.clawd.bot/mac/peekaboo) for the Clawdbot plan and naming.
|
||||
- See: `docs/mac/peekaboo.md` for the Clawdbot plan and naming.
|
||||
|
||||
### Mach/XPC (future direction)
|
||||
- Still optional for internal app services, but **not required** for automation now that node.invoke is the surface.
|
||||
@@ -37,4 +37,4 @@ read_when:
|
||||
- Prefer requiring a TeamID match for all privileged surfaces.
|
||||
- PeekabooBridge: `PEEKABOO_ALLOW_UNSIGNED_SOCKET_CLIENTS=1` (DEBUG-only) may allow same-UID callers for local development.
|
||||
- All communication remains local-only; no network sockets are exposed.
|
||||
- TCC prompts originate only from the GUI app bundle; run [`scripts/package-mac-app.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/package-mac-app.sh) so the signed bundle ID stays stable.
|
||||
- TCC prompts originate only from the GUI app bundle; run `scripts/package-mac-app.sh` so the signed bundle ID stays stable.
|
||||
@@ -1,93 +0,0 @@
|
||||
---
|
||||
summary: "How Clawdbot rotates auth profiles and falls back across models"
|
||||
read_when:
|
||||
- Diagnosing auth profile rotation, cooldowns, or model fallback behavior
|
||||
- Updating failover rules for auth profiles or models
|
||||
---
|
||||
|
||||
# Model failover
|
||||
|
||||
Clawdbot handles failures in two stages:
|
||||
1) **Auth profile rotation** within the current provider.
|
||||
2) **Model fallback** to the next model in `agent.model.fallbacks`.
|
||||
|
||||
This doc explains the runtime rules and the data that backs them.
|
||||
|
||||
## Auth storage (keys + OAuth)
|
||||
|
||||
Clawdbot uses **auth profiles** for both API keys and OAuth tokens.
|
||||
|
||||
- Secrets live in `~/.clawdbot/agents/<agentId>/agent/auth-profiles.json` (legacy: `~/.clawdbot/agent/auth-profiles.json`).
|
||||
- Config `auth.profiles` / `auth.order` are **metadata + routing only** (no secrets).
|
||||
- Legacy import-only OAuth file: `~/.clawdbot/credentials/oauth.json` (imported into `auth-profiles.json` on first use).
|
||||
|
||||
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`).
|
||||
|
||||
Profiles live in `~/.clawdbot/agents/<agentId>/agent/auth-profiles.json` under `profiles`.
|
||||
|
||||
## Rotation order
|
||||
|
||||
When a provider has multiple profiles, Clawdbot 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.
|
||||
|
||||
If no explicit order is configured, Clawdbot uses a round‑robin order:
|
||||
- **Primary key:** profile type (**OAuth before API keys**).
|
||||
- **Secondary key:** `usageStats.lastUsed` (oldest first, within each type).
|
||||
- **Cooldown profiles** are moved to the end, ordered by soonest cooldown expiry.
|
||||
|
||||
### 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).
|
||||
|
||||
## Cooldowns
|
||||
|
||||
When a profile fails due to auth/rate‑limit errors (or a timeout that looks
|
||||
like rate limiting), Clawdbot marks it in cooldown and moves to the next profile.
|
||||
|
||||
Cooldowns use exponential backoff:
|
||||
- 1 minute
|
||||
- 5 minutes
|
||||
- 25 minutes
|
||||
- 1 hour (cap)
|
||||
|
||||
State is stored in `auth-profiles.json` under `usageStats`:
|
||||
|
||||
```json
|
||||
{
|
||||
"usageStats": {
|
||||
"provider:profile": {
|
||||
"lastUsed": 1736160000000,
|
||||
"cooldownUntil": 1736160600000,
|
||||
"errorCount": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Model fallback
|
||||
|
||||
If all profiles for a provider fail, Clawdbot moves to the next model in
|
||||
`agent.model.fallbacks`. This applies to auth failures, rate limits, and
|
||||
timeouts that exhausted profile rotation.
|
||||
|
||||
## Related config
|
||||
|
||||
See [`docs/configuration.md`](https://docs.clawd.bot/configuration) for:
|
||||
- `auth.profiles` / `auth.order`
|
||||
- `agent.model.primary` / `agent.model.fallbacks`
|
||||
- `agent.imageModel` routing
|
||||
|
||||
See [`docs/models.md`](https://docs.clawd.bot/models) for the broader model selection and fallback overview.
|
||||
+23
-26
@@ -7,8 +7,6 @@ read_when:
|
||||
---
|
||||
# Models CLI plan
|
||||
|
||||
See [`docs/model-failover.md`](https://docs.clawd.bot/model-failover) for how auth profiles rotate (OAuth vs API keys), cooldowns, and how that interacts with model fallbacks.
|
||||
|
||||
Goal: give clear model visibility + control (configured vs available), plus scan tooling
|
||||
that prefers tool-call + image-capable models and maintains ordered fallbacks.
|
||||
|
||||
@@ -18,32 +16,35 @@ that prefers tool-call + image-capable models and maintains ordered fallbacks.
|
||||
- default: configured models only
|
||||
- flags: `--all` (full catalog), `--local`, `--provider <name>`, `--json`, `--plain`
|
||||
- `clawdbot models status`
|
||||
- show default model + aliases + fallbacks + configured models
|
||||
- show default model + aliases + fallbacks + allowlist
|
||||
- `clawdbot models set <modelOrAlias>`
|
||||
- writes `agent.model.primary` and ensures `agent.models` entry
|
||||
- writes `agent.model` in config
|
||||
- `clawdbot models set-image <modelOrAlias>`
|
||||
- writes `agent.imageModel.primary` and ensures `agent.models` entry
|
||||
- writes `agent.imageModel` in config
|
||||
- `clawdbot models aliases list|add|remove`
|
||||
- writes `agent.models.*.alias`
|
||||
- writes `agent.modelAliases`
|
||||
- `clawdbot models fallbacks list|add|remove|clear`
|
||||
- writes `agent.model.fallbacks`
|
||||
- writes `agent.modelFallbacks`
|
||||
- `clawdbot models image-fallbacks list|add|remove|clear`
|
||||
- writes `agent.imageModel.fallbacks`
|
||||
- writes `agent.imageModelFallbacks`
|
||||
- `clawdbot models scan`
|
||||
- OpenRouter :free scan; probe tool-call + image; interactive selection
|
||||
|
||||
## Config changes
|
||||
|
||||
- `agent.models` (configured model catalog + aliases).
|
||||
- `agent.model.primary` + `agent.model.fallbacks`.
|
||||
- `agent.imageModel.primary` + `agent.imageModel.fallbacks` (optional).
|
||||
- `auth.profiles` + `auth.order` for per-provider auth failover.
|
||||
- Add `agent.modelFallbacks: string[]` (ordered list of provider/model IDs).
|
||||
- Add `agent.imageModel?: string` (optional image-capable model for image tool).
|
||||
- Add `agent.imageModelFallbacks?: string[]` (ordered list for image tool).
|
||||
- Keep existing:
|
||||
- `agent.model` (default)
|
||||
- `agent.allowedModels` (list filter)
|
||||
- `agent.modelAliases` (shortcut names)
|
||||
|
||||
## Scan behavior (models scan)
|
||||
|
||||
Input
|
||||
- OpenRouter `/models` list (filter `:free`)
|
||||
- Requires OpenRouter API key from auth profiles or `OPENROUTER_API_KEY`
|
||||
- Requires `OPENROUTER_API_KEY` (or stored OpenRouter key in auth storage)
|
||||
- Optional filters: `--max-age-days`, `--min-params`, `--provider`, `--max-candidates`
|
||||
- Probe controls: `--timeout`, `--concurrency`
|
||||
|
||||
@@ -65,21 +66,17 @@ Interactive selection (TTY)
|
||||
- Non-TTY: auto-select; require `--yes`/`--no-input` to apply.
|
||||
|
||||
Output
|
||||
- Writes `agent.model.fallbacks` ordered.
|
||||
- Writes `agent.imageModel.fallbacks` ordered (image-capable models).
|
||||
- Ensures `agent.models` entries exist for selected models.
|
||||
- Optional `--set-default` to set `agent.model.primary`.
|
||||
- Optional `--set-image` to set `agent.imageModel.primary`.
|
||||
- Writes `agent.modelFallbacks` ordered.
|
||||
- Writes `agent.imageModelFallbacks` ordered (image-capable models).
|
||||
- Optional `--set-default` to set `agent.model`.
|
||||
- Optional `--set-image` to set `agent.imageModel`.
|
||||
|
||||
## Runtime fallback
|
||||
|
||||
- On model failure: try `agent.model.fallbacks` in order.
|
||||
- Per-provider auth failover uses `auth.order` (or stored profile order) **before**
|
||||
moving to the next model.
|
||||
- Image routing uses `agent.imageModel` **only when configured** and the primary
|
||||
model lacks image input.
|
||||
- Persist last successful provider/model to session entry; auth profile success is global.
|
||||
- See [`docs/model-failover.md`](https://docs.clawd.bot/model-failover) for auth profile rotation, cooldowns, and timeout handling.
|
||||
- On model failure: try `agent.modelFallbacks` in order.
|
||||
- Ignore fallback entries not in `agent.allowedModels` (if allowlist set).
|
||||
- Persist last successful provider/model to session entry.
|
||||
- `/status` shows last used model (not just default).
|
||||
|
||||
## Tests
|
||||
|
||||
@@ -89,5 +86,5 @@ Output
|
||||
|
||||
## Docs
|
||||
|
||||
- Update [`docs/configuration.md`](https://docs.clawd.bot/configuration) with `agent.models` + `agent.model` + `agent.imageModel`.
|
||||
- Update `docs/configuration.md` with `agent.modelFallbacks`.
|
||||
- Keep this doc current when CLI surface or scan logic changes.
|
||||
@@ -1,121 +0,0 @@
|
||||
---
|
||||
summary: "Multi-agent routing: isolated agents, provider accounts, and bindings"
|
||||
title: Multi-Agent Routing
|
||||
read_when: "You want multiple isolated agents (workspaces + auth) in one gateway process."
|
||||
status: active
|
||||
---
|
||||
|
||||
# Multi-Agent Routing
|
||||
|
||||
Goal: multiple *isolated* agents (separate workspace + `agentDir` + sessions), plus multiple provider accounts (e.g. two WhatsApps) in one running Gateway. Inbound is routed to an agent via bindings.
|
||||
|
||||
## What is “one agent”?
|
||||
|
||||
An **agent** is a fully scoped brain with its own:
|
||||
|
||||
- **Workspace** (files, AGENTS.md/SOUL.md/USER.md, local notes, persona rules).
|
||||
- **State directory** (`agentDir`) for auth profiles, model registry, and per-agent config.
|
||||
- **Session store** (chat history + routing state) under `~/.clawdbot/agents/<agentId>/sessions`.
|
||||
|
||||
The Gateway can host **one agent** (default) or **many agents** side-by-side.
|
||||
|
||||
### Single-agent mode (default)
|
||||
|
||||
If you do nothing, Clawdbot runs a single agent:
|
||||
|
||||
- `agentId` defaults to **`main`**.
|
||||
- Sessions are keyed as `agent:main:<mainKey>`.
|
||||
- Workspace defaults to `~/clawd` (or `~/clawd-<profile>` when `CLAWDBOT_PROFILE` is set).
|
||||
- State defaults to `~/.clawdbot/agents/main/agent`.
|
||||
|
||||
## Multiple agents = multiple people, multiple personalities
|
||||
|
||||
With **multiple agents**, each `agentId` becomes a **fully isolated persona**:
|
||||
|
||||
- **Different phone numbers/accounts** (per provider `accountId`).
|
||||
- **Different personalities** (per-agent workspace files like `AGENTS.md` and `SOUL.md`).
|
||||
- **Separate auth + sessions** (no cross-talk unless explicitly enabled).
|
||||
|
||||
This lets **multiple people** share one Gateway server while keeping their AI “brains” and data isolated.
|
||||
|
||||
## Routing rules (how messages pick an agent)
|
||||
|
||||
Bindings are **deterministic** and **most-specific wins**:
|
||||
|
||||
1. `peer` match (exact DM/group/channel id)
|
||||
2. `guildId` (Discord)
|
||||
3. `teamId` (Slack)
|
||||
4. `accountId` match for a provider
|
||||
5. provider-level match (`accountId: "*"`)
|
||||
6. fallback to `routing.defaultAgentId` (default: `main`)
|
||||
|
||||
## Multiple accounts / phone numbers
|
||||
|
||||
Providers that support **multiple accounts** (e.g. WhatsApp) use `accountId` to identify
|
||||
each login. Each `accountId` can be routed to a different agent, so one server can host
|
||||
multiple phone numbers without mixing sessions.
|
||||
|
||||
## Concepts
|
||||
|
||||
- `agentId`: one “brain” (workspace, per-agent auth, per-agent session store).
|
||||
- `accountId`: one provider account instance (e.g. WhatsApp account `"personal"` vs `"biz"`).
|
||||
- `binding`: routes inbound messages to an `agentId` by `(provider, accountId, peer)` and optionally guild/team ids.
|
||||
- Direct chats collapse to `agent:<agentId>:<mainKey>` (per-agent “main”; `session.mainKey`).
|
||||
|
||||
## Example: two WhatsApps → two agents
|
||||
|
||||
`~/.clawdbot/clawdbot.json` (JSON5):
|
||||
|
||||
```js
|
||||
{
|
||||
routing: {
|
||||
defaultAgentId: "home",
|
||||
|
||||
agents: {
|
||||
home: {
|
||||
workspace: "~/clawd-home",
|
||||
agentDir: "~/.clawdbot/agents/home/agent",
|
||||
},
|
||||
work: {
|
||||
workspace: "~/clawd-work",
|
||||
agentDir: "~/.clawdbot/agents/work/agent",
|
||||
},
|
||||
},
|
||||
|
||||
// Deterministic routing: first match wins (most-specific first).
|
||||
bindings: [
|
||||
{ agentId: "home", match: { provider: "whatsapp", accountId: "personal" } },
|
||||
{ agentId: "work", match: { provider: "whatsapp", accountId: "biz" } },
|
||||
|
||||
// Optional per-peer override (example: send a specific group to work agent).
|
||||
{
|
||||
agentId: "work",
|
||||
match: {
|
||||
provider: "whatsapp",
|
||||
accountId: "personal",
|
||||
peer: { kind: "group", id: "1203630...@g.us" },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
// Off by default: agent-to-agent messaging must be explicitly enabled + allowlisted.
|
||||
agentToAgent: {
|
||||
enabled: false,
|
||||
allow: ["home", "work"],
|
||||
},
|
||||
},
|
||||
|
||||
whatsapp: {
|
||||
accounts: {
|
||||
personal: {
|
||||
// Optional override. Default: ~/.clawdbot/credentials/whatsapp/personal
|
||||
// authDir: "~/.clawdbot/credentials/whatsapp/personal",
|
||||
},
|
||||
biz: {
|
||||
// Optional override. Default: ~/.clawdbot/credentials/whatsapp/biz
|
||||
// authDir: "~/.clawdbot/credentials/whatsapp/biz",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
+3
-3
@@ -84,12 +84,12 @@ The macOS packaging flow expects a stable Info.plist template at:
|
||||
apps/macos/Sources/Clawdbot/Resources/Info.plist
|
||||
```
|
||||
|
||||
[`scripts/package-mac-app.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/package-mac-app.sh) copies this template into the app bundle and patches dynamic fields
|
||||
`scripts/package-mac-app.sh` copies this template into the app bundle and patches dynamic fields
|
||||
(bundle ID, version/build, Git SHA, Sparkle keys). This keeps the plist deterministic for SwiftPM
|
||||
packaging and Nix builds (which do not rely on a full Xcode toolchain).
|
||||
|
||||
## Related
|
||||
|
||||
- [nix-clawdbot](https://github.com/clawdbot/nix-clawdbot) — full setup guide
|
||||
- [Wizard](https://docs.clawd.bot/wizard) — non-Nix CLI setup
|
||||
- [Docker](https://docs.clawd.bot/docker) — containerized setup
|
||||
- [Wizard](./wizard.md) — non-Nix CLI setup
|
||||
- [Docker](./docker.md) — containerized setup
|
||||
+5
-5
@@ -14,7 +14,7 @@ macOS can also run in **node mode**: the menubar app connects to the Gateway’s
|
||||
|
||||
## Pairing + status
|
||||
|
||||
Pairing is gateway-owned and approval-based. See [`docs/gateway/pairing.md`](https://docs.clawd.bot/gateway/pairing) for the full flow.
|
||||
Pairing is gateway-owned and approval-based. See `docs/gateway/pairing.md` for the full flow.
|
||||
|
||||
Quick CLI:
|
||||
|
||||
@@ -150,8 +150,8 @@ Nodes may include a `permissions` map in `node.list` / `node.describe`, keyed by
|
||||
|
||||
## Where to look in code
|
||||
|
||||
- CLI wiring: [`src/cli/nodes-cli.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/cli/nodes-cli.ts)
|
||||
- Canvas snapshot decoding/temp paths: [`src/cli/nodes-canvas.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/cli/nodes-canvas.ts)
|
||||
- Duration parsing for CLI: [`src/cli/parse-duration.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/cli/parse-duration.ts)
|
||||
- iOS node commands: [`apps/ios/Sources/Model/NodeAppModel.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/ios/Sources/Model/NodeAppModel.swift)
|
||||
- CLI wiring: `src/cli/nodes-cli.ts`
|
||||
- Canvas snapshot decoding/temp paths: `src/cli/nodes-canvas.ts`
|
||||
- Duration parsing for CLI: `src/cli/parse-duration.ts`
|
||||
- iOS node commands: `apps/ios/Sources/Model/NodeAppModel.swift`
|
||||
- Android node commands: `apps/android/app/src/main/java/com/clawdbot/android/node/*`
|
||||
@@ -1,15 +1,10 @@
|
||||
---
|
||||
summary: "RPC protocol notes for onboarding wizard and config schema"
|
||||
read_when: "Changing onboarding wizard steps or config schema endpoints"
|
||||
---
|
||||
|
||||
# Onboarding + Config Protocol
|
||||
|
||||
Purpose: shared onboarding + config surfaces across CLI, macOS app, and Web UI.
|
||||
|
||||
## Components
|
||||
- Wizard engine: `src/wizard` (session + prompts + onboarding state).
|
||||
- CLI: [`src/commands/onboard-*.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/commands/onboard-*.ts) uses the wizard with the CLI prompter.
|
||||
- CLI: `src/commands/onboard-*.ts` uses the wizard with the CLI prompter.
|
||||
- Gateway RPC: wizard + config schema endpoints serve UI clients.
|
||||
- macOS: SwiftUI onboarding uses the wizard step model.
|
||||
- Web UI: config form renders from JSON Schema + hints.
|
||||
|
||||
+12
-13
@@ -19,7 +19,7 @@ This doc describes the intended **first-run onboarding** for Clawdbot. The goal
|
||||
|
||||
First question: where does the **Gateway** run?
|
||||
|
||||
- **Local (this Mac):** onboarding can run OAuth flows and write OAuth credentials locally.
|
||||
- **Local (this Mac):** onboarding can run OAuth flows and write the Clawdbot auth store locally.
|
||||
- **Remote (over SSH/tailnet):** onboarding must not run OAuth locally, because credentials must exist on the **gateway host**.
|
||||
|
||||
Gateway auth tip:
|
||||
@@ -38,10 +38,10 @@ The macOS app should:
|
||||
- Start the Anthropic OAuth (PKCE) flow in the user’s browser.
|
||||
- Ask the user to paste the `code#state` value.
|
||||
- Exchange it for tokens and write credentials to:
|
||||
- `~/.clawdbot/credentials/oauth.json` (file mode `0600`, directory mode `0700`)
|
||||
- `~/.clawdbot/agent/auth.json` (file mode `0600`, directory mode `0700`)
|
||||
|
||||
Why this location matters: it’s the Clawdbot-owned OAuth store.
|
||||
Clawdbot also imports `oauth.json` into the agent auth profile store (`~/.clawdbot/agents/<agentId>/agent/auth-profiles.json`) on first use.
|
||||
Why this location matters: it’s the Clawdbot-owned auth store (OAuth + API keys).
|
||||
Clawdbot auto-migrates legacy OAuth tokens from `~/.clawdbot/credentials/oauth.json` (and older pi/Claude locations) into `auth.json` on first use.
|
||||
|
||||
### Recommended: OAuth (OpenAI Codex)
|
||||
|
||||
@@ -49,8 +49,7 @@ The macOS app should:
|
||||
- Start the OpenAI Codex OAuth (PKCE) flow in the user’s browser.
|
||||
- Auto-capture the callback on `http://127.0.0.1:1455/auth/callback` when possible.
|
||||
- If the callback fails, prompt the user to paste the redirect URL or code.
|
||||
- Store credentials in `~/.clawdbot/credentials/oauth.json` (same OAuth store as Anthropic).
|
||||
- Set `agent.model` to `openai-codex/gpt-5.2` when the model is unset or `openai/*`.
|
||||
- Store credentials in `~/.clawdbot/agent/auth.json` (same auth store as Anthropic).
|
||||
|
||||
### Alternative: API key (instructions only)
|
||||
|
||||
@@ -103,7 +102,7 @@ Once setup is complete, the user can switch to the normal chat (`main`) via the
|
||||
|
||||
We no longer collect identity in the onboarding wizard. Instead, the **first agent run** performs a playful bootstrap ritual using files in the workspace:
|
||||
|
||||
- Workspace is created implicitly (default `~/clawd`, configurable via `agent.workspace`) when local is selected,
|
||||
- Workspace is created implicitly (default `~/.clawdbot/workspace`) when local is selected,
|
||||
but only if the folder is empty or already contains `AGENTS.md`.
|
||||
- Files are seeded: `AGENTS.md`, `BOOTSTRAP.md`, `IDENTITY.md`, `USER.md`.
|
||||
- `BOOTSTRAP.md` tells the agent to keep it conversational:
|
||||
@@ -132,7 +131,7 @@ The workspace is created automatically as part of agent bootstrap (no dedicated
|
||||
Recommendation: treat the workspace as the agent’s “memory” and make it a git repo (ideally private) so identity + memories are backed up:
|
||||
|
||||
```bash
|
||||
cd ~/clawd
|
||||
cd ~/.clawdbot/workspace
|
||||
git init
|
||||
git add AGENTS.md
|
||||
git commit -m "Add agent workspace"
|
||||
@@ -149,12 +148,12 @@ If the Gateway runs on another machine, OAuth credentials must be created/stored
|
||||
|
||||
For now, remote onboarding should:
|
||||
- explain why OAuth isn't shown
|
||||
- point the user at the credential location (`~/.clawdbot/credentials/oauth.json`) and the auth profile store (`~/.clawdbot/agents/<agentId>/agent/auth-profiles.json`) on the gateway host
|
||||
- point the user at the credential location (`~/.clawdbot/agent/auth.json`) and the workspace location on the gateway host
|
||||
- mention that the **bootstrap ritual happens on the gateway host** (same BOOTSTRAP/IDENTITY/USER files)
|
||||
|
||||
### Manual credential setup
|
||||
|
||||
On the gateway host, create `~/.clawdbot/credentials/oauth.json` with this exact format:
|
||||
On the gateway host, create `~/.clawdbot/agent/auth.json` with this exact format:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -163,7 +162,7 @@ On the gateway host, create `~/.clawdbot/credentials/oauth.json` with this exact
|
||||
}
|
||||
```
|
||||
|
||||
Set permissions: `chmod 600 ~/.clawdbot/credentials/oauth.json`
|
||||
Set permissions: `chmod 600 ~/.clawdbot/agent/auth.json`
|
||||
|
||||
**Note:** Clawdbot auto-imports from legacy pi-coding-agent paths (`~/.pi/agent/oauth.json`, etc.) but this does NOT work with Claude Code credentials — different file and format.
|
||||
|
||||
@@ -178,8 +177,8 @@ cat ~/.claude/.credentials.json | jq '{
|
||||
refresh: .claudeAiOauth.refreshToken,
|
||||
expires: .claudeAiOauth.expiresAt
|
||||
}
|
||||
}' > ~/.clawdbot/credentials/oauth.json
|
||||
chmod 600 ~/.clawdbot/credentials/oauth.json
|
||||
}' > ~/.clawdbot/agent/auth.json
|
||||
chmod 600 ~/.clawdbot/agent/auth.json
|
||||
```
|
||||
|
||||
| Claude Code field | Clawdbot field |
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
---
|
||||
summary: "Pairing overview: approve who can DM you + which nodes can join"
|
||||
read_when:
|
||||
- Setting up DM access control
|
||||
- Pairing a new iOS/Android node
|
||||
- Reviewing Clawdbot security posture
|
||||
---
|
||||
|
||||
# Pairing
|
||||
|
||||
“Pairing” is Clawdbot’s explicit **owner approval** step.
|
||||
It is used in two places:
|
||||
|
||||
1) **DM pairing** (who is allowed to talk to the bot)
|
||||
2) **Node pairing** (which devices/nodes are allowed to join the gateway network)
|
||||
|
||||
Security context: https://docs.clawd.bot/security
|
||||
|
||||
## 1) DM pairing (inbound chat access)
|
||||
|
||||
When a provider is configured with DM policy `pairing`, unknown senders get a short code and their message is **not processed** until you approve.
|
||||
|
||||
Default DM policies are documented in: https://docs.clawd.bot/security
|
||||
|
||||
### Approve a sender
|
||||
|
||||
```bash
|
||||
clawdbot pairing list --provider telegram
|
||||
clawdbot pairing approve --provider telegram <CODE>
|
||||
```
|
||||
|
||||
Supported providers: `telegram`, `whatsapp`, `signal`, `imessage`, `discord`, `slack`.
|
||||
|
||||
### Where the state lives
|
||||
|
||||
Stored under `~/.clawdbot/credentials/`:
|
||||
- Pending requests: `<provider>-pairing.json`
|
||||
- Approved allowlist store: `<provider>-allowFrom.json`
|
||||
|
||||
Treat these as sensitive (they gate access to your assistant).
|
||||
|
||||
### Source of truth (code)
|
||||
|
||||
- DM pairing storage + code generation: [`src/pairing/pairing-store.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/pairing/pairing-store.ts)
|
||||
- CLI commands: [`src/cli/pairing-cli.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/cli/pairing-cli.ts)
|
||||
|
||||
## 2) Node pairing (iOS/Android nodes joining the gateway)
|
||||
|
||||
Nodes (iOS/Android, future hardware, etc.) connect to the Gateway and request to join.
|
||||
The Gateway keeps an authoritative allowlist; new nodes require explicit approve/reject.
|
||||
|
||||
### Approve a node
|
||||
|
||||
```bash
|
||||
clawdbot nodes pending
|
||||
clawdbot nodes approve <requestId>
|
||||
```
|
||||
|
||||
### Where the state lives
|
||||
|
||||
Stored under `~/.clawdbot/nodes/`:
|
||||
- `pending.json` (short-lived; pending requests expire)
|
||||
- `paired.json` (paired nodes + tokens)
|
||||
|
||||
### Details
|
||||
|
||||
Full protocol + design notes: https://docs.clawd.bot/gateway/pairing
|
||||
|
||||
### Source of truth (code)
|
||||
|
||||
- Node pairing store (pending/paired + token issuance): [`src/infra/node-pairing.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/node-pairing.ts)
|
||||
- Gateway methods/events (`node.pair.*`): [`src/gateway/server-methods/nodes.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server-methods/nodes.ts)
|
||||
- CLI: [`src/cli/nodes-cli.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/cli/nodes-cli.ts)
|
||||
|
||||
## Related docs
|
||||
|
||||
- Security model + prompt injection: https://docs.clawd.bot/security
|
||||
- Updating safely (run doctor): https://docs.clawd.bot/updating
|
||||
- Provider configs:
|
||||
- Telegram: https://docs.clawd.bot/telegram
|
||||
- WhatsApp: https://docs.clawd.bot/whatsapp
|
||||
- Signal: https://docs.clawd.bot/signal
|
||||
- iMessage: https://docs.clawd.bot/imessage
|
||||
- Discord: https://docs.clawd.bot/discord
|
||||
- Slack: https://docs.clawd.bot/slack
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
summary: "Harden cron.add input handling, align schemas, and improve cron UI/agent tooling"
|
||||
owner: "clawdbot"
|
||||
status: "complete"
|
||||
last_updated: "2026-01-05"
|
||||
---
|
||||
|
||||
# Cron Add Hardening & Schema Alignment
|
||||
|
||||
## Context
|
||||
Recent gateway logs show repeated `cron.add` failures with invalid parameters (missing `sessionTarget`, `wakeMode`, `payload`, and malformed `schedule`). This indicates that at least one client (likely the agent tool call path) is sending wrapped or partially specified job payloads. Separately, there is drift between cron provider enums in TypeScript, gateway schema, CLI flags, and UI form types, plus a UI mismatch for `cron.status` (expects `jobCount` while gateway returns `jobs`).
|
||||
|
||||
## Goals
|
||||
- Stop `cron.add` INVALID_REQUEST spam by normalizing common wrapper payloads and inferring missing `kind` fields.
|
||||
- Align cron provider lists across gateway schema, cron types, CLI docs, and UI forms.
|
||||
- Make agent cron tool schema explicit so the LLM produces correct job payloads.
|
||||
- Fix the Control UI cron status job count display.
|
||||
- Add tests to cover normalization and tool behavior.
|
||||
|
||||
## Non-goals
|
||||
- Change cron scheduling semantics or job execution behavior.
|
||||
- Add new schedule kinds or cron expression parsing.
|
||||
- Overhaul the UI/UX for cron beyond the necessary field fixes.
|
||||
|
||||
## Findings (current gaps)
|
||||
- `CronPayloadSchema` in gateway excludes `signal` + `imessage`, while TS types include them.
|
||||
- Control UI CronStatus expects `jobCount`, but gateway returns `jobs`.
|
||||
- Agent cron tool schema allows arbitrary `job` objects, enabling malformed inputs.
|
||||
- Gateway strictly validates `cron.add` with no normalization, so wrapped payloads fail.
|
||||
|
||||
## Proposed Approach
|
||||
1. **Normalize** incoming `cron.add` payloads (unwrap `data`/`job`, infer `schedule.kind` and `payload.kind`, default `wakeMode` + `sessionTarget` when safe).
|
||||
2. **Harden** the agent cron tool schema using the canonical gateway `CronAddParamsSchema` and normalize before sending to the gateway.
|
||||
3. **Align** provider enums and cron status fields across gateway schema, TS types, CLI descriptions, and UI form controls.
|
||||
4. **Test** normalization in gateway tests and tool behavior in agent tests.
|
||||
|
||||
## Multi-phase Execution Plan
|
||||
|
||||
### Phase 1 — Schema + type alignment
|
||||
- [x] Expand gateway `CronPayloadSchema` provider enum to include `signal` and `imessage`.
|
||||
- [x] Update CLI `--provider` descriptions to include `slack` (already supported by gateway).
|
||||
- [x] Update UI Cron payload/provider union types to include all supported providers.
|
||||
- [x] Fix UI CronStatus type to match gateway (`jobs` instead of `jobCount`).
|
||||
- [x] Update cron UI provider select to include Discord/Slack/Signal/iMessage.
|
||||
- [x] Update macOS CronJobEditor provider picker + enum to include Slack/Signal/iMessage.
|
||||
- [x] Document cron compatibility normalization policy in [`docs/cron.md`](https://docs.clawd.bot/cron).
|
||||
|
||||
### Phase 2 — Input normalization + tooling hardening
|
||||
- [x] Add shared cron input normalization helpers (`normalizeCronJobCreate`/`normalizeCronJobPatch`).
|
||||
- [x] Apply normalization in gateway `cron.add` (and patch normalization in `cron.update`).
|
||||
- [x] Tighten agent cron tool schema to `CronAddParamsSchema` and normalize job/patch before sending.
|
||||
|
||||
### Phase 3 — Tests
|
||||
- [x] Add gateway test covering wrapped `cron.add` payload normalization.
|
||||
- [x] Add cron tool test to assert normalization and defaulting for `cron.add`.
|
||||
- [x] Add gateway test covering `cron.update` normalization.
|
||||
- [x] Add UI + Swift conformance test for cron channels + status fields.
|
||||
|
||||
### Phase 4 — Verification
|
||||
- [x] Run tests (full suite executed via `pnpm test -- cron-tool`).
|
||||
|
||||
## Rollout/Monitoring
|
||||
- Watch gateway logs for reduced `cron.add` INVALID_REQUEST errors.
|
||||
- Confirm Control UI cron status shows job count after refresh.
|
||||
- If errors persist, extend normalization for additional common shapes (e.g., `schedule.at`, `payload.message` without `kind`).
|
||||
|
||||
## Optional Follow-ups
|
||||
- Manual Control UI smoke: add cron job per provider + verify status job count.
|
||||
|
||||
## Open Questions
|
||||
- Should `cron.add` accept explicit `state` from clients (currently disallowed by schema)?
|
||||
- Should we allow `webchat` as an explicit delivery provider (currently filtered in delivery resolution)?
|
||||
@@ -1,126 +0,0 @@
|
||||
---
|
||||
summary: "Spec: groupPolicy hardening for Telegram allowlist parity"
|
||||
read_when:
|
||||
- Reviewing historical Telegram allowlist normalization changes
|
||||
---
|
||||
# Engineering Execution Spec: groupPolicy Hardening (Telegram Allowlist Parity)
|
||||
|
||||
**Date**: 2026-01-05
|
||||
**Status**: Complete
|
||||
**PR**: #216 (feat/whatsapp-group-policy)
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Follow-up hardening work ensures Telegram allowlists behave consistently across inbound group/DM filtering and outbound send normalization. The focus is on prefix parity (`telegram:` / `tg:`), case-insensitive matching for prefixes, and resilience to accidental whitespace in config entries. Documentation and tests were updated to reflect and lock in this behavior.
|
||||
|
||||
---
|
||||
|
||||
## Findings Analysis
|
||||
|
||||
### [MED] F1: Telegram Allowlist Prefix Handling Is Case-Sensitive and Excludes `tg:`
|
||||
|
||||
**Location**: [`src/telegram/bot.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/telegram/bot.ts)
|
||||
|
||||
**Problem**: Inbound allowlist normalization only stripped a lowercase `telegram:` prefix. This rejected `TG:123` / `Telegram:123` and did not accept the `tg:` shorthand even though outbound send normalization already accepts `tg:` and case-insensitive prefixes.
|
||||
|
||||
**Impact**:
|
||||
- DMs and group allowlists fail when users copy/paste prefixed IDs from logs or existing send format.
|
||||
- Behavior is inconsistent between inbound filtering and outbound send normalization.
|
||||
|
||||
**Fix**: Normalize allowlist entries by trimming whitespace and stripping `telegram:` / `tg:` prefixes case-insensitively at pre-compute time.
|
||||
|
||||
---
|
||||
|
||||
### [LOW] F2: Allowlist Entries Are Not Trimmed
|
||||
|
||||
**Location**: [`src/telegram/bot.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/telegram/bot.ts)
|
||||
|
||||
**Problem**: Allowlist entries are not trimmed; accidental whitespace causes mismatches.
|
||||
|
||||
**Fix**: Trim and drop empty entries while normalizing allowlist inputs.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1: Normalize Telegram Allowlist Inputs
|
||||
|
||||
**File**: [`src/telegram/bot.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/telegram/bot.ts)
|
||||
|
||||
**Changes**:
|
||||
1. Trim allowlist entries and drop empty values.
|
||||
2. Strip `telegram:` / `tg:` prefixes case-insensitively.
|
||||
3. Simplify DM allowlist check to rely on normalized values.
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Add Coverage for Prefix + Whitespace
|
||||
|
||||
**File**: [`src/telegram/bot.test.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/telegram/bot.test.ts)
|
||||
|
||||
**Add Tests**:
|
||||
- DM allowlist accepts `TG:` prefix with surrounding whitespace.
|
||||
- Group allowlist accepts `TG:` prefix case-insensitively.
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Documentation Updates
|
||||
|
||||
**Files**:
|
||||
- [`docs/groups.md`](https://docs.clawd.bot/groups)
|
||||
- [`docs/telegram.md`](https://docs.clawd.bot/telegram)
|
||||
|
||||
**Changes**:
|
||||
- Document `tg:` alias and case-insensitive prefixes for Telegram allowlists.
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: Verification
|
||||
|
||||
1. Run targeted Telegram tests (`pnpm test -- src/telegram/bot.test.ts`).
|
||||
2. If time allows, run full suite (`pnpm test`).
|
||||
|
||||
---
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Change Type | Description |
|
||||
|------|-------------|-------------|
|
||||
| [`src/telegram/bot.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/telegram/bot.ts) | Fix | Trim allowlist values; strip `telegram:` / `tg:` prefixes case-insensitively |
|
||||
| [`src/telegram/bot.test.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/telegram/bot.test.ts) | Test | Add DM + group allowlist coverage for `TG:` prefix + whitespace |
|
||||
| [`docs/groups.md`](https://docs.clawd.bot/groups) | Docs | Mention `tg:` alias + case-insensitive prefixes |
|
||||
| [`docs/telegram.md`](https://docs.clawd.bot/telegram) | Docs | Mention `tg:` alias + case-insensitive prefixes |
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [x] Telegram allowlist accepts `telegram:` / `tg:` prefixes case-insensitively.
|
||||
- [x] Telegram allowlist tolerates whitespace in config entries.
|
||||
- [x] DM and group allowlist tests cover prefixed cases.
|
||||
- [x] Docs updated to reflect allowlist formats.
|
||||
- [x] Targeted tests pass.
|
||||
- [x] Full test suite passes.
|
||||
|
||||
---
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
| Risk | Severity | Mitigation |
|
||||
|------|----------|------------|
|
||||
| Behavior change for malformed entries | Low | Normalization is additive and trims only whitespace |
|
||||
| Test fragility | Low | Isolated unit tests; no external dependencies |
|
||||
| Doc drift | Low | Updated docs alongside code |
|
||||
|
||||
---
|
||||
|
||||
## Estimated Complexity
|
||||
|
||||
- **Phase 1**: Low (normalization helpers)
|
||||
- **Phase 2**: Low (2 new tests)
|
||||
- **Phase 3**: Low (doc edits)
|
||||
- **Phase 4**: Low (verification)
|
||||
|
||||
**Total**: ~20 minutes
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
summary: "Poll sending via gateway + CLI"
|
||||
read_when:
|
||||
- Adding or modifying poll support
|
||||
- Debugging poll sends from the CLI or gateway
|
||||
---
|
||||
# Polls
|
||||
|
||||
Updated: 2026-01-06
|
||||
|
||||
## Supported providers
|
||||
- WhatsApp (web provider)
|
||||
- Discord
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
# WhatsApp
|
||||
clawdbot poll --to +15555550123 -q "Lunch today?" -o "Yes" -o "No" -o "Maybe"
|
||||
clawdbot poll --to 123456789@g.us -q "Meeting time?" -o "10am" -o "2pm" -o "4pm" -s 2
|
||||
|
||||
# Discord
|
||||
clawdbot poll --to channel:123456789 -q "Snack?" -o "Pizza" -o "Sushi" --provider discord
|
||||
clawdbot poll --to channel:123456789 -q "Plan?" -o "A" -o "B" --provider discord --duration-hours 48
|
||||
```
|
||||
|
||||
Options:
|
||||
- `--provider`: `whatsapp` (default) or `discord`
|
||||
- `--max-selections`: how many choices a voter can select (default: 1)
|
||||
- `--duration-hours`: Discord-only (defaults to 24 when omitted)
|
||||
|
||||
## Gateway RPC
|
||||
|
||||
Method: `poll`
|
||||
|
||||
Params:
|
||||
- `to` (string, required)
|
||||
- `question` (string, required)
|
||||
- `options` (string[], required)
|
||||
- `maxSelections` (number, optional)
|
||||
- `durationHours` (number, optional)
|
||||
- `provider` (string, optional, default: `whatsapp`)
|
||||
- `idempotencyKey` (string, required)
|
||||
|
||||
## Provider 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.
|
||||
|
||||
## Agent tool (Discord)
|
||||
The Discord tool action `poll` still uses `question`, `answers`, optional `allowMultiselect`, `durationHours`, and `content`. The gateway/CLI poll model maps `allowMultiselect` to `maxSelections > 1`.
|
||||
|
||||
Note: Discord has no “pick exactly N” mode; `maxSelections` is treated as a boolean (`> 1` = multiselect).
|
||||
+11
-11
@@ -36,7 +36,7 @@ Presence entries are produced by multiple sources and then **merged**.
|
||||
|
||||
The Gateway seeds a “self” entry at startup so UIs always show at least the current gateway host.
|
||||
|
||||
Implementation: [`src/infra/system-presence.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/system-presence.ts) (`initSelfPresence()`).
|
||||
Implementation: `src/infra/system-presence.ts` (`initSelfPresence()`).
|
||||
|
||||
### 2) WebSocket connect (connection-derived presence)
|
||||
|
||||
@@ -44,7 +44,7 @@ Every WS client must begin with a `connect` request. On successful handshake, th
|
||||
|
||||
This is meant to answer: “Which clients are currently connected?”
|
||||
|
||||
Implementation: [`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server.ts) (connect handling uses `connect.params.client.instanceId` when provided; otherwise falls back to `connId`).
|
||||
Implementation: `src/gateway/server.ts` (connect handling uses `connect.params.client.instanceId` when provided; otherwise falls back to `connId`).
|
||||
|
||||
#### Why one-off CLI commands do not show up
|
||||
|
||||
@@ -58,8 +58,8 @@ Clients can publish richer periodic beacons via the `system-event` method. The m
|
||||
- `lastInputSeconds`
|
||||
|
||||
Implementation:
|
||||
- Gateway: [`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server.ts) handles method `system-event` by calling `updateSystemPresence(...)`.
|
||||
- mac app beaconing: [`apps/macos/Sources/Clawdbot/PresenceReporter.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/Clawdbot/PresenceReporter.swift).
|
||||
- Gateway: `src/gateway/server.ts` handles method `system-event` by calling `updateSystemPresence(...)`.
|
||||
- mac app beaconing: `apps/macos/Sources/Clawdbot/PresenceReporter.swift`.
|
||||
|
||||
### 4) Node bridge beacons (gateway-owned presence)
|
||||
|
||||
@@ -69,7 +69,7 @@ for that node and starts periodic refresh beacons so it does not expire.
|
||||
- Connect/disconnect markers: `node-connected`, `node-disconnected`
|
||||
- Periodic heartbeat: every 3 minutes (`reason: periodic`)
|
||||
|
||||
Implementation: [`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server.ts) (node bridge handlers + timer beacons).
|
||||
Implementation: `src/gateway/server.ts` (node bridge handlers + timer beacons).
|
||||
|
||||
## Merge + dedupe rules (why `instanceId` matters)
|
||||
|
||||
@@ -80,7 +80,7 @@ Key points:
|
||||
- The best key is a stable, opaque `instanceId` that does not change across restarts.
|
||||
- Keys are treated case-insensitively.
|
||||
|
||||
Implementation: [`src/infra/system-presence.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/system-presence.ts) (`normalizePresenceKey()`).
|
||||
Implementation: `src/infra/system-presence.ts` (`normalizePresenceKey()`).
|
||||
|
||||
### mac app identity (stable UUID)
|
||||
|
||||
@@ -89,7 +89,7 @@ The mac app uses a persisted UUID as `instanceId` so:
|
||||
- renaming the Mac does not create a new “instance”
|
||||
- debug/release builds can share the same identity
|
||||
|
||||
Implementation: [`apps/macos/Sources/Clawdbot/InstanceIdentity.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/Clawdbot/InstanceIdentity.swift).
|
||||
Implementation: `apps/macos/Sources/Clawdbot/InstanceIdentity.swift`.
|
||||
|
||||
`displayName` (machine name) is used for UI, while `instanceId` is used for dedupe.
|
||||
|
||||
@@ -99,7 +99,7 @@ Presence entries are not permanent:
|
||||
- TTL: entries older than 5 minutes are pruned
|
||||
- Max: map is capped at 200 entries (LRU by `ts`)
|
||||
|
||||
Implementation: [`src/infra/system-presence.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/system-presence.ts) (`TTL_MS`, `MAX_ENTRIES`, pruning in `listSystemPresence()`).
|
||||
Implementation: `src/infra/system-presence.ts` (`TTL_MS`, `MAX_ENTRIES`, pruning in `listSystemPresence()`).
|
||||
|
||||
## Remote/tunnel caveat (loopback IPs)
|
||||
|
||||
@@ -107,7 +107,7 @@ When a client connects over an SSH tunnel / local port forward, the Gateway may
|
||||
|
||||
To avoid degrading an otherwise-correct client beacon IP, the Gateway avoids writing loopback remote addresses into presence entries.
|
||||
|
||||
Implementation: [`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server.ts) (`isLoopbackAddress()`).
|
||||
Implementation: `src/gateway/server.ts` (`isLoopbackAddress()`).
|
||||
|
||||
## Consumers (who reads presence)
|
||||
|
||||
@@ -116,8 +116,8 @@ Implementation: [`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/b
|
||||
The mac app’s Instances tab renders the result of `system-presence`.
|
||||
|
||||
Implementation:
|
||||
- View: [`apps/macos/Sources/Clawdbot/InstancesSettings.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/Clawdbot/InstancesSettings.swift)
|
||||
- Store: [`apps/macos/Sources/Clawdbot/InstancesStore.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/Clawdbot/InstancesStore.swift)
|
||||
- View: `apps/macos/Sources/Clawdbot/InstancesSettings.swift`
|
||||
- Store: `apps/macos/Sources/Clawdbot/InstancesStore.swift`
|
||||
|
||||
The Instances rows show a small presence indicator (Active/Idle/Stale) based on
|
||||
the last beacon age. The label is derived from the entry timestamp (`ts`).
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
---
|
||||
summary: "Proposal: model config, auth profiles, and fallback behavior"
|
||||
read_when:
|
||||
- Designing model selection, auth profiles, or fallback behavior
|
||||
- Migrating model config schema
|
||||
---
|
||||
|
||||
# Model config proposal
|
||||
|
||||
Goals
|
||||
- Multi OAuth + multi API key per provider
|
||||
- Model selection via `/model` with sensible fallback
|
||||
- Global (not per-session) fallback logic
|
||||
- Keep last-known-good auth profile when switching models
|
||||
- Profile override only when explicitly requested
|
||||
- Image routing override only when explicitly configured
|
||||
|
||||
Non-goals (v1)
|
||||
- Auto-discovery of provider capabilities beyond catalog input tags
|
||||
- Per-model auth profile order (see open questions)
|
||||
|
||||
## Proposed config shape
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"profiles": {
|
||||
"anthropic:default": {
|
||||
"provider": "anthropic",
|
||||
"mode": "oauth"
|
||||
},
|
||||
"anthropic:work": {
|
||||
"provider": "anthropic",
|
||||
"mode": "api_key"
|
||||
},
|
||||
"openai:default": {
|
||||
"provider": "openai",
|
||||
"mode": "oauth"
|
||||
}
|
||||
},
|
||||
"order": {
|
||||
"anthropic": ["anthropic:default", "anthropic:work"],
|
||||
"openai": ["openai:default"]
|
||||
}
|
||||
},
|
||||
"agent": {
|
||||
"models": {
|
||||
"anthropic/claude-opus-4-5": {
|
||||
"alias": "Opus"
|
||||
},
|
||||
"openai/gpt-5.2": {
|
||||
"alias": "gpt52"
|
||||
}
|
||||
},
|
||||
"model": {
|
||||
"primary": "anthropic/claude-opus-4-5",
|
||||
"fallbacks": ["openai/gpt-5.2"]
|
||||
},
|
||||
"imageModel": {
|
||||
"primary": "openai/gpt-5.2",
|
||||
"fallbacks": ["anthropic/claude-opus-4-5"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes
|
||||
- Canonical model keys are full `provider/model`.
|
||||
- `alias` optional; used by `/model` resolution.
|
||||
- `auth.profiles` is keyed. Default CLI login creates `provider:default`.
|
||||
- `auth.order[provider]` controls rotation order for that provider.
|
||||
|
||||
## CLI / UX
|
||||
|
||||
Login
|
||||
- `clawdbot login anthropic` → create/replace `anthropic:default`.
|
||||
- `clawdbot login anthropic --profile work` → create/replace `anthropic:work`.
|
||||
|
||||
Model selection
|
||||
- `/model Opus` → resolve alias to full id.
|
||||
- `/model anthropic/claude-opus-4-5` → explicit.
|
||||
- Optional: `/model Opus@anthropic:work` (explicit profile override for session only).
|
||||
|
||||
Model listing
|
||||
- `/model` list shows:
|
||||
- model id
|
||||
- alias
|
||||
- provider
|
||||
- auth order (from `auth.order`)
|
||||
- auth source for the current provider (auth-profiles.json/env/shell env/models.json)
|
||||
|
||||
## Fallback behavior (global)
|
||||
|
||||
Fallback list
|
||||
- Use `agent.model.fallbacks` globally.
|
||||
- No per-session fallback list; last-known-good is per-session but uses global ordering.
|
||||
|
||||
Auth profile rotation
|
||||
- If provider auth error (401/403/invalid refresh):
|
||||
- advance to next profile in `auth.order[provider]`.
|
||||
- if all fail, fall back to next model.
|
||||
|
||||
Rate limiting
|
||||
- If rate limit / quota error:
|
||||
- rotate auth profile first (same provider)
|
||||
- if still failing, fall back to next model.
|
||||
|
||||
Model not found / capability mismatch
|
||||
- immediate model fallback.
|
||||
|
||||
## Image routing
|
||||
|
||||
Rule
|
||||
- Only use `agent.imageModel` when explicitly configured.
|
||||
- If `agent.imageModel` is configured and the current text model lacks image input, use it.
|
||||
|
||||
Support detection
|
||||
- From model catalog `input` tags when available (e.g. `image` in models.json).
|
||||
- If unknown: treat as text-only and use `agent.imageModel`.
|
||||
|
||||
## Migration (doctor + gateway auto-run)
|
||||
|
||||
Inputs
|
||||
- Legacy keys (pre-migration):
|
||||
- `agent.model` (string)
|
||||
- `agent.modelFallbacks` (string[])
|
||||
- `agent.imageModel` (string)
|
||||
- `agent.imageModelFallbacks` (string[])
|
||||
- `agent.allowedModels` (string[])
|
||||
- `agent.modelAliases` (record)
|
||||
|
||||
Outputs
|
||||
- `agent.models` map with keys for all referenced models
|
||||
- `agent.model.primary/fallbacks`
|
||||
- `agent.imageModel.primary/fallbacks`
|
||||
- Auth profile store seeded from current auth-profiles.json/auth.json + oauth.json + env (as `provider:default`)
|
||||
- `auth.order` seeded with `["provider:default"]` when config is updated
|
||||
|
||||
Auto-run
|
||||
- Gateway start detects legacy keys and runs doctor migration.
|
||||
|
||||
## Decisions
|
||||
|
||||
- Auth order is per-provider (`auth.order`).
|
||||
- Doctor migration is required; gateway will auto-run on startup when legacy keys detected.
|
||||
- `/model Opus@profile` is explicit session override only.
|
||||
- Image routing override only when `agent.imageModel` is explicitly configured.
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
summary: "Routing rules per provider (WhatsApp, Telegram, Discord, web) and shared context"
|
||||
read_when:
|
||||
- Changing provider routing or inbox behavior
|
||||
---
|
||||
# Providers & Routing
|
||||
|
||||
Updated: 2026-01-06
|
||||
|
||||
Goal: deterministic replies per provider, while supporting multi-agent + multi-account routing.
|
||||
|
||||
- **Provider**: provider label (`whatsapp`, `webchat`, `telegram`, `discord`, `signal`, `imessage`, …). Routing is fixed: replies go back to the origin provider; the model doesn’t choose.
|
||||
- **AccountId**: provider account instance (e.g. WhatsApp account `"default"` vs `"work"`). Not every provider supports multi-account yet.
|
||||
- **AgentId**: one isolated “brain” (workspace + per-agent agentDir + per-agent session store).
|
||||
- **Reply context:** inbound replies include `ReplyToId`, `ReplyToBody`, and `ReplyToSender`, and the quoted context is appended to `Body` as a `[Replying to ...]` block.
|
||||
- **Canonical direct session (per agent):** direct chats collapse to `agent:<agentId>:<mainKey>` (default `main`). Groups/channels stay isolated per agent:
|
||||
- group: `agent:<agentId>:<provider>:group:<id>`
|
||||
- channel/room: `agent:<agentId>:<provider>:channel:<id>`
|
||||
- **Session store:** per-agent store lives under `~/.clawdbot/agents/<agentId>/sessions/sessions.json` (override via `session.store` with `{agentId}` templating). JSONL transcripts live next to it.
|
||||
- **WebChat:** attaches to the selected agent’s main session (so desktop reflects cross-provider history for that agent).
|
||||
- **Implementation hints:**
|
||||
- Set `Provider` + `AccountId` in each ingress.
|
||||
- Route inbound to an agent via `routing.bindings` (match on `provider`, `accountId`, plus optional peer/guild/team).
|
||||
- Keep routing deterministic: originate → same provider. Use the gateway WebSocket for sends; avoid side channels.
|
||||
- Do not let the agent emit “send to X” decisions; keep that policy in the host code.
|
||||
+7
-6
@@ -12,13 +12,13 @@ We now serialize command-based auto-replies (WhatsApp Web listener) through a ti
|
||||
- Serializing avoids competing for terminal/stdin, keeps logs readable, and reduces the chance of rate limits from upstream tools.
|
||||
|
||||
## How it works
|
||||
- [`src/process/command-queue.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/process/command-queue.ts) holds a lane-aware FIFO queue and drains each lane synchronously.
|
||||
- `src/process/command-queue.ts` holds a lane-aware FIFO queue and drains each lane synchronously.
|
||||
- `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 `agent.maxConcurrent`.
|
||||
- When verbose logging is enabled, queued commands emit a short notice if they waited more than ~2s before starting.
|
||||
- Typing indicators (`onReplyStart`) still fire immediately on enqueue so user experience is unchanged while we wait our turn.
|
||||
|
||||
## Queue modes (per provider)
|
||||
## Queue modes (per surface)
|
||||
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.
|
||||
@@ -30,12 +30,12 @@ Inbound messages can steer the current run, wait for a followup turn, or do both
|
||||
Steer-backlog means you can get a followup response after the steered run, so
|
||||
streaming surfaces can look like duplicates. Prefer `collect`/`steer` if you want
|
||||
one response per inbound message.
|
||||
Send `/queue collect` as a standalone command (per-session) or set `routing.queue.byProvider.discord: "collect"`.
|
||||
Inline fix: `/queue collect` (per-session) or set `routing.queue.bySurface.discord: "collect"`.
|
||||
|
||||
Defaults (when unset in config):
|
||||
- All surfaces → `collect`
|
||||
|
||||
Configure globally or per provider via `routing.queue`:
|
||||
Configure globally or per surface via `routing.queue`:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -45,7 +45,7 @@ Configure globally or per provider via `routing.queue`:
|
||||
debounceMs: 1000,
|
||||
cap: 20,
|
||||
drop: "summarize",
|
||||
byProvider: { discord: "collect" }
|
||||
bySurface: { discord: "collect" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,8 @@ 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.
|
||||
- `/queue <mode>` as a standalone command stores the mode for the current session.
|
||||
- `/queue <mode>` embedded in a message applies **once** (no persistence).
|
||||
- Options can be combined: `/queue collect debounce:2s cap:25 drop:summarize`
|
||||
- `/queue default` or `/queue reset` clears the session override.
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
summary: "Refactor plan: unify agent lifecycle events and wait semantics"
|
||||
read_when:
|
||||
- Refactoring agent lifecycle events or wait behavior
|
||||
---
|
||||
# Refactor: Agent Loop
|
||||
|
||||
Goal: align Clawdis run lifecycle with pi/mom semantics, remove ambiguity between "job" and "agent_end".
|
||||
|
||||
## Problem
|
||||
- Two lifecycles today:
|
||||
- `job` (gateway wrapper) => used by `agent.wait` + chat final
|
||||
- pi-agent `agent_end` (inner loop) => only logged
|
||||
- This can finalize early (job done) while late assistant deltas still arrive.
|
||||
- `afterMs` and timeouts can cause false timeouts in `agent.wait`.
|
||||
|
||||
## Reference (mom)
|
||||
- Single lifecycle: `agent_start`/`agent_end` from pi-agent-core event stream.
|
||||
- `waitForIdle()` resolves on `agent_end`.
|
||||
- No separate job state exposed to clients.
|
||||
|
||||
## Proposed refactor (breaking allowed)
|
||||
1) Replace public `job` stream with `lifecycle` stream
|
||||
- `stream: "lifecycle"`
|
||||
- `data: { phase: "start" | "end" | "error", startedAt, endedAt, error? }`
|
||||
2) `agent.wait` waits on lifecycle end/error only
|
||||
- remove `afterMs`
|
||||
- return `{ runId, status, startedAt, endedAt, error? }`
|
||||
3) Chat final emitted on lifecycle end only
|
||||
- deltas still from `assistant` stream
|
||||
4) Centralize run registry
|
||||
- one map keyed by runId: sessionKey, startedAt, lastSeq, bufferedText
|
||||
- clear on lifecycle end
|
||||
|
||||
## Implementation outline
|
||||
- `src/agents/pi-embedded-subscribe.ts`
|
||||
- emit lifecycle start/end events (translate pi `agent_start`/`agent_end`)
|
||||
- `src/infra/agent-events.ts`
|
||||
- add `"lifecycle"` to stream type
|
||||
- `src/gateway/protocol/schema.ts`
|
||||
- update AgentEvent schema; update AgentWait params (remove afterMs, add status)
|
||||
- `src/gateway/server-methods/agent-job.ts`
|
||||
- rename to `agent-wait.ts` or similar; wait on lifecycle end/error
|
||||
- `src/gateway/server-chat.ts`
|
||||
- finalize on lifecycle end (not job)
|
||||
- `src/commands/agent.ts`
|
||||
- stop emitting `job` externally (keep internal log if needed)
|
||||
|
||||
## Migration notes (breaking)
|
||||
- Update all callers of `agent.wait` to new response shape.
|
||||
- Update tests that expect `timeout` based on job events.
|
||||
- If any UI relies on job state, map lifecycle instead.
|
||||
|
||||
## Risks
|
||||
- If lifecycle events are dropped, wait/chat could hang; add timeout in `agent.wait` to fail fast.
|
||||
- Late deltas after lifecycle end should be ignored; keep seq tracking + drop.
|
||||
|
||||
## Acceptance
|
||||
- One lifecycle visible to clients.
|
||||
- `agent.wait` resolves when agent loop ends, not wrapper completion.
|
||||
- Chat final never emits before last assistant delta.
|
||||
|
||||
## Rollout (if we wanted safety)
|
||||
- Gate with config flag `agent.lifecycleMode = "legacy"|"refactor"`.
|
||||
- Remove legacy after one release.
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
summary: "Refactor: simplify browser control API + implementation"
|
||||
read_when:
|
||||
- Refactoring browser control routes, client, or CLI
|
||||
- Auditing agent-facing browser tool surface
|
||||
date: 2025-12-20
|
||||
---
|
||||
|
||||
# Refactor: Browser control simplification
|
||||
|
||||
Goal: make the browser-control surface **small, stable, and agent-oriented**, and remove “implementation-shaped” APIs (Playwright/CDP specifics, one-off endpoints, and debugging helpers).
|
||||
|
||||
## Why
|
||||
|
||||
- The previous API accreted many narrow endpoints (`/click`, `/type`, `/press`, …) plus debug utilities.
|
||||
- Some actions are inherently racy when modeled as “do X *when* the event is already visible” (file chooser, dialogs).
|
||||
- We want a single, coherent contract that keeps “how it’s implemented” private.
|
||||
|
||||
## Target contract (vNext)
|
||||
|
||||
**Basics**
|
||||
- `GET /` status
|
||||
- `POST /start`, `POST /stop`
|
||||
- `GET /tabs`, `POST /tabs/open`, `POST /tabs/focus`, `DELETE /tabs/:targetId`
|
||||
|
||||
**Agent actions**
|
||||
- `POST /navigate` `{ url, targetId? }`
|
||||
- `POST /act` `{ kind, targetId?, ... }` where `kind` is one of:
|
||||
- `click`, `type`, `press`, `hover`, `drag`, `select`, `fill`, `wait`, `resize`, `close`, `evaluate`
|
||||
- `POST /screenshot` `{ targetId?, fullPage?, ref?, element?, type? }`
|
||||
- `GET /snapshot` `?format=ai|aria&targetId?&limit?`
|
||||
- `GET /console` `?level?&targetId?`
|
||||
- `POST /pdf` `{ targetId? }`
|
||||
|
||||
**Hooks (pre-setup / arming)**
|
||||
- `POST /hooks/file-chooser` `{ targetId?, paths, timeoutMs? }`
|
||||
- `POST /hooks/dialog` `{ targetId?, accept, promptText?, timeoutMs? }`
|
||||
|
||||
Semantics:
|
||||
- Hook endpoints **arm** the next matching event within `timeoutMs` (default 2 minutes, clamped to max 2 minutes).
|
||||
- Last arm wins per page (new arm replaces previous).
|
||||
|
||||
## Work checklist
|
||||
|
||||
- [x] Replace action endpoints with `POST /act`
|
||||
- [x] Remove legacy endpoints (`/click`, `/type`, `/wait`, …) and any CLI wrappers that no longer make sense
|
||||
- [x] Remove `/back` and any history-specific routes
|
||||
- [x] Convert `upload` + `dialog` to hook/arming endpoints
|
||||
- [x] Unify screenshots behind `POST /screenshot` (no GET variant)
|
||||
- [x] Trim inspect/debug endpoints (`/query`, `/dom`) unless explicitly needed
|
||||
- [x] Update docs/browser.md to describe contract without implementation details
|
||||
- [x] Update tests (server + client) to cover vNext contract
|
||||
|
||||
## Notes / decisions
|
||||
|
||||
- Keep Playwright as an internal implementation detail for now.
|
||||
- Prefer ref-based interactions (`aria-ref`) over coordinate-based ones.
|
||||
- Keep the code split “routes vs. engine” small and obvious; avoid scattering logic across too many files.
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
summary: "Refactor: host A2UI from the Gateway (HTTP), remove app-bundled shells"
|
||||
read_when:
|
||||
- Refactoring Canvas/A2UI ownership or assets
|
||||
- Moving UI rendering from native bundles into the Gateway
|
||||
- Updating node canvas navigation or A2UI command flows
|
||||
---
|
||||
|
||||
# Canvas / A2UI — HTTP-hosted from Gateway
|
||||
|
||||
Status: Implemented · Date: 2025-12-20
|
||||
|
||||
## Goal
|
||||
- Make the **Gateway (TypeScript)** the single owner of A2UI.
|
||||
- Remove **app-bundled A2UI shells** (macOS, iOS, Android).
|
||||
- A2UI renders only when the **Gateway is reachable** (acceptable failure mode).
|
||||
|
||||
## Decision
|
||||
All A2UI HTML/JS assets are **served by the Gateway canvas host** on
|
||||
`canvasHost.port` (default `18793`), bound to the **bridge interface**. Nodes
|
||||
(mac/iOS/Android) **navigate to the advertised `canvasHostUrl`** before applying
|
||||
A2UI messages. No local custom-scheme or bundled fallback remains.
|
||||
|
||||
## Why
|
||||
- One source of truth (TS) for A2UI rendering.
|
||||
- Faster iteration (no app release required for A2UI updates).
|
||||
- iOS/Android/macOS all behave identically.
|
||||
|
||||
## New behavior (summary)
|
||||
1) `canvas.a2ui.*` on any node:
|
||||
- Ensure Canvas is visible.
|
||||
- Navigate the node WebView to the Gateway A2UI URL.
|
||||
- Apply/reset A2UI messages once the page is ready.
|
||||
2) If Gateway is unreachable:
|
||||
- A2UI fails with an explicit error (no fallback).
|
||||
|
||||
## Gateway changes
|
||||
|
||||
### Serve A2UI assets
|
||||
Add A2UI HTML/JS to the Gateway Canvas host (standalone HTTP server on
|
||||
`canvasHost.port`), e.g.:
|
||||
|
||||
```
|
||||
/__clawdbot__/a2ui/ -> index.html
|
||||
/__clawdbot__/a2ui/a2ui.bundle.js -> bundled A2UI runtime
|
||||
```
|
||||
|
||||
Serve Canvas files at `/__clawdbot__/canvas/` and A2UI at `/__clawdbot__/a2ui/`.
|
||||
Use the shared Canvas host handler (`src/canvas-host/server.ts`) to serve these
|
||||
assets and inject the action bridge + live reload if desired.
|
||||
|
||||
### Canonical host URL
|
||||
The Gateway exposes a **canonical** `canvasHostUrl` in hello/bridge payloads
|
||||
so nodes don’t need to guess.
|
||||
|
||||
## Node changes (mac/iOS/Android)
|
||||
|
||||
### Navigation path
|
||||
Before applying A2UI:
|
||||
- Navigate to `${canvasHostUrl}/__clawdbot__/a2ui/`.
|
||||
|
||||
### Remove bundled shells
|
||||
Remove all fallback logic that serves A2UI from local bundles:
|
||||
- macOS: remove custom-scheme fallback for `/__clawdbot__/a2ui/`
|
||||
- iOS/Android: remove packaged A2UI assets and "default scaffold" assumptions
|
||||
|
||||
### Error behavior
|
||||
If `canvasHostUrl` is missing or unreachable:
|
||||
- `canvas.a2ui.push/reset` returns a clear error:
|
||||
- `A2UI_HOST_UNAVAILABLE` or `A2UI_HOST_NOT_CONFIGURED`
|
||||
|
||||
## Security / transport
|
||||
- For non-TLS Gateway URLs (http), iOS/Android will need ATS exceptions.
|
||||
- For TLS (https), prefer WSS + HTTPS with a valid cert.
|
||||
|
||||
## Implementation plan
|
||||
1) Gateway
|
||||
- Add A2UI assets under `src/canvas-host/`.
|
||||
- Serve them at `/__clawdbot__/a2ui/` (align with existing naming).
|
||||
- Serve Canvas files at `/__clawdbot__/canvas/` on `canvasHost.port`.
|
||||
- Expose `canvasHostUrl` in handshake + bridge hello payloads.
|
||||
2) Node runtimes
|
||||
- Update `canvas.a2ui.*` to navigate to `canvasHostUrl`.
|
||||
- Remove custom-scheme A2UI fallback and bundled assets.
|
||||
3) Tests
|
||||
- TS: verify `/__clawdbot__/a2ui/` responds with HTML + JS.
|
||||
- Node: verify A2UI fails when host is unreachable and succeeds when reachable.
|
||||
4) Docs
|
||||
- Update `docs/mac/canvas.md`, `docs/ios/spec.md`, `docs/android/connect.md`
|
||||
to remove local fallback assumptions and point to gateway-hosted A2UI.
|
||||
|
||||
## Notes
|
||||
- iOS/Android may still require ATS exceptions for `http://` canvas hosts.
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
summary: "Refactor: unify on the clawdbot CLI + gateway-first control; retire clawdbot-mac"
|
||||
read_when:
|
||||
- Removing or replacing the macOS CLI helper
|
||||
- Adding node capabilities or permissions metadata
|
||||
- Updating macOS app packaging/install flows
|
||||
---
|
||||
|
||||
# CLI unification (clawdbot-only)
|
||||
|
||||
Status: active refactor · Date: 2025-12-20
|
||||
|
||||
## Goals
|
||||
- **Single CLI**: use `clawdbot` for all automation (local + remote). Retire `clawdbot-mac`.
|
||||
- **Gateway-first**: all agent actions flow through the Gateway WebSocket + node.invoke.
|
||||
- **Permission awareness**: nodes advertise permission state so the agent can decide what to run.
|
||||
- **No duplicate paths**: remove macOS control socket + Swift CLI surface.
|
||||
|
||||
## Non-goals
|
||||
- Keep legacy `clawdbot-mac` compatibility.
|
||||
- Support agent control when no Gateway is running.
|
||||
|
||||
## Key decisions
|
||||
1) **No Gateway → no control**
|
||||
- If the macOS app is running but the Gateway is not, remote commands (canvas/run/notify) are unavailable.
|
||||
- This is acceptable to keep one network surface.
|
||||
|
||||
2) **Remove ensure-permissions CLI**
|
||||
- Permissions are **advertised by the node** (e.g., screen recording granted/denied).
|
||||
- Commands will still fail with explicit errors when permissions are missing.
|
||||
|
||||
3) **Mac app installs/symlinks `clawdbot`**
|
||||
- Bundle a standalone `clawdbot` binary in the app (bun-compiled).
|
||||
- Install/symlink that binary to `/usr/local/bin/clawdbot` and `/opt/homebrew/bin/clawdbot`.
|
||||
- No `clawdbot-mac` helper remains.
|
||||
|
||||
4) **Canvas parity across node types**
|
||||
- Use `node.invoke` commands consistently (`canvas.present|navigate|eval|snapshot|a2ui.*`).
|
||||
- The TS CLI provides convenient wrappers so agents never have to craft raw `node.invoke` calls.
|
||||
|
||||
## Command surface (new/normalized)
|
||||
- `clawdbot nodes invoke --command canvas.*` remains valid.
|
||||
- New CLI wrappers for convenience:
|
||||
- `clawdbot canvas present|navigate|eval|snapshot|a2ui push|a2ui reset`
|
||||
- New node commands (mac-only initially):
|
||||
- `system.run` (shell execution)
|
||||
- `system.notify` (local notifications)
|
||||
|
||||
## Permission advertising
|
||||
- Node hello/pairing includes a `permissions` map:
|
||||
- Example keys: `screenRecording`, `accessibility`, `microphone`, `notifications`, `speechRecognition`.
|
||||
- Values: boolean (`true` = granted, `false` = not granted).
|
||||
- Gateway `node.list` / `node.describe` surfaces the map.
|
||||
|
||||
## Gateway mode + config
|
||||
- Gateways should only auto-start when explicitly configured for **local** mode.
|
||||
- When config is missing or explicitly remote, `clawdbot gateway` should refuse to auto-start unless forced.
|
||||
|
||||
## Implementation checklist
|
||||
- Add bun-compiled `clawdbot` binary to macOS app bundle; update codesign + install flows.
|
||||
- Remove `ClawdbotCLI` target and control socket server.
|
||||
- Add node command(s) for `system.run` and `system.notify` on macOS.
|
||||
- Add permission map to node hello/pairing + gateway responses.
|
||||
- Update TS CLI + docs to use `clawdbot` only.
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
summary: "Refactor notes for the macOS gateway client typed API migration (Dec 2025)."
|
||||
read_when:
|
||||
- Refactoring macOS gateway client or typed gateway methods
|
||||
- Auditing agent routing or channel semantics
|
||||
---
|
||||
|
||||
# Gateway Client Refactor (Dec 2025)
|
||||
|
||||
Goal: remove stringly-typed gateway calls from the macOS app, centralize routing/channel semantics, and improve error handling.
|
||||
|
||||
## Progress
|
||||
|
||||
- [x] Fold legacy “AgentRPC” into `GatewayConnection` (single layer; no separate client object).
|
||||
- [x] Typed gateway API: `GatewayConnection.Method` + `requestDecoded/requestVoid` + typed helpers (status/agent/chat/cron/etc).
|
||||
- [x] Centralize agent routing/channel semantics via `GatewayAgentChannel` + `GatewayAgentInvocation`.
|
||||
- [x] Improve gateway error model (structured `GatewayResponseError` + decoding errors include method).
|
||||
- [x] Migrate mac call sites to typed helpers (leave only intentionally dynamic forwarding paths).
|
||||
- [x] Convert remaining UI raw channel strings to `GatewayAgentChannel` (Cron editor).
|
||||
- [x] Cleanup naming: rename remaining tests/docs that still reference “RPC/AgentRPC”.
|
||||
|
||||
### Notes
|
||||
|
||||
- Intentionally string-based:
|
||||
- `BridgeServer` dynamic request forwarding (method is data-driven).
|
||||
- `ControlChannel` request wrapper (generic escape hatch).
|
||||
|
||||
## Notes / Non-goals
|
||||
|
||||
- No functional behavior changes intended (beyond better errors and removing “magic strings”).
|
||||
- Keep changes incremental: introduce typed APIs first, then migrate call sites, then remove old helpers.
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
summary: "Refactor notes for the macOS gateway client: single shared websocket + follow-ups"
|
||||
read_when:
|
||||
- Investigating duplicate/stale Gateway WS connections
|
||||
- Refactoring macOS gateway client architecture
|
||||
- Debugging noisy reconnect storms on gateway restart
|
||||
---
|
||||
# Gateway Refactor Notes (macOS client)
|
||||
|
||||
Last updated: 2025-12-12
|
||||
|
||||
This document captures the rationale and outcome of the macOS app’s Gateway client refactor: **one shared websocket connection per app process**, with an in-process event bus for server push frames.
|
||||
|
||||
Related docs:
|
||||
- `docs/refactor/new-arch.md` (overall gateway protocol/server plan)
|
||||
- `docs/gateway.md` (gateway operations/runbook)
|
||||
- `docs/presence.md` (presence semantics and dedupe)
|
||||
- `docs/mac/webchat.md` (WebChat surfaces and debugging)
|
||||
|
||||
---
|
||||
|
||||
## Background: what was wrong
|
||||
|
||||
Symptoms:
|
||||
- Restarting the gateway produced a *storm* of reconnects/log spam (`gateway/ws in connect`, `hello`, `hello-ok`) and elevated `clients=` counts.
|
||||
- Even with “one panel open”, the mac app could hold tens of websocket connections to `ws://127.0.0.1:18789`.
|
||||
|
||||
Root cause (historical bug):
|
||||
- The mac app was repeatedly “reconfiguring” a gateway client on a timer (via health polling), creating a new websocket owner each time.
|
||||
- Old websocket owners were not fully torn down and could keep watchdog/tick tasks alive, leading to **connection accumulation** over time.
|
||||
|
||||
---
|
||||
|
||||
## What changed
|
||||
|
||||
- **One socket owner:** `GatewayConnection.shared` is the only supported entry point for gateway RPC.
|
||||
- **No global notifications:** server push frames are delivered via `GatewayConnection.shared.subscribe(...) -> AsyncStream<GatewayPush>` (no `NotificationCenter` fan-out).
|
||||
- **No tunnel side effects:** `GatewayConnection` does not create/ensure SSH tunnels in remote mode; it consumes the already-established forwarded port.
|
||||
|
||||
---
|
||||
|
||||
## Current architecture (as of 2025-12-12)
|
||||
|
||||
Goal: enforce the invariant **“one gateway websocket per app process (per effective config)”**.
|
||||
|
||||
Key elements:
|
||||
- `GatewayConnection.shared` owns the one websocket and is the *only* supported entry point for app code that needs gateway RPC.
|
||||
- Consumers (e.g. Control UI, agent invocations, SwiftUI WebChat) call `GatewayConnection.shared.request(...)` and do not create their own sockets.
|
||||
- If the effective connection config changes (local ↔ remote tunnel port, token change), `GatewayConnection` replaces the underlying connection.
|
||||
- The transport (`GatewayChannelActor`) is an internal detail and forwards push frames back into `GatewayConnection`.
|
||||
- Server-push frames are delivered via `GatewayConnection.shared.subscribe(...) -> AsyncStream<GatewayPush>` (in-process event bus).
|
||||
|
||||
Notes:
|
||||
- Remote mode requires an SSH control tunnel. `GatewayConnection` **does not** start tunnels; it consumes the already-established forwarded port (owned by `ConnectionModeCoordinator` / `RemoteTunnelManager`).
|
||||
|
||||
---
|
||||
|
||||
## Design constraints / principles
|
||||
|
||||
- **Single ownership:** Exactly one component owns the actual socket and reconnect policy.
|
||||
- **Explicit config changes:** Recreate/reconnect only when config changes, not as a side effect of periodic work.
|
||||
- **No implicit fan-out sockets:** Adding new UI features must not accidentally add new persistent gateway connections.
|
||||
- **Testable seams:** Connection config and websocket session creation should be overridable in tests.
|
||||
|
||||
---
|
||||
|
||||
## Status / remaining work
|
||||
|
||||
- ✅ One shared websocket per app process (per config)
|
||||
- ✅ Event streaming moved into `GatewayConnection` (`AsyncStream<GatewayPush>`) and replays latest snapshot to new subscribers
|
||||
- ✅ `NotificationCenter` removed for in-process gateway events (ControlChannel / Instances / WebChatSwiftUI)
|
||||
- ✅ Remote tunnel lifecycle is not started implicitly by random RPC calls
|
||||
- ✅ Payload decoding helpers extracted so UI adapters stay thin
|
||||
- ✅ Dedicated resolved-endpoint publisher for remote mode (`GatewayEndpointStore`)
|
||||
|
||||
---
|
||||
|
||||
## Testing strategy (what we want to cover)
|
||||
|
||||
Minimum invariants:
|
||||
- Repeated requests under the same config do **not** create additional websocket tasks.
|
||||
- Concurrent requests still create **exactly one** websocket and reuse it.
|
||||
- Shutdown prevents any reconnect loop after failures.
|
||||
- Config changes (token / endpoint) cancel the old socket and reconnect once.
|
||||
|
||||
Nice-to-have integration coverage:
|
||||
- Multiple “consumers” (Control UI + agent invocations + SwiftUI WebChat) all call through the shared connection and still produce only one websocket.
|
||||
|
||||
Additional coverage added (macOS):
|
||||
- Subscribing after connect replays the latest snapshot.
|
||||
- Sequence gaps emit an explicit `GatewayPush.seqGap(...)` before the corresponding event.
|
||||
|
||||
---
|
||||
|
||||
## Debug notes (operational)
|
||||
|
||||
When diagnosing “too many connections”:
|
||||
- Prefer counting actual TCP connections on port 18789 and grouping by PID to see which process is holding sockets.
|
||||
- Gateway `--verbose` prints *every* connect/hello and event broadcast; use it only when needed and filter output if you’re just sanity-checking.
|
||||
@@ -0,0 +1,171 @@
|
||||
---
|
||||
summary: "Implementation plan for the new gateway architecture and protocol"
|
||||
read_when:
|
||||
- Executing the gateway refactor
|
||||
---
|
||||
# New Gateway Architecture – Implementation Plan (detailed)
|
||||
|
||||
Last updated: 2025-12-09
|
||||
|
||||
Goal: replace legacy gateway/stdin/TCP control with a single WebSocket Gateway, typed protocol, and first-frame snapshot. No backward compatibility.
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 — Foundations
|
||||
- **Naming**: CLI subcommand `clawdbot gateway`; internal namespace `Gateway`.
|
||||
- **Protocol folder**: create `protocol/` for schemas and build artifacts. ✅ `src/gateway/protocol`.
|
||||
- **Schema tooling**:
|
||||
- Prefer **TypeBox** (or ArkType) as source-of-truth types. ✅ TypeBox in `schema.ts`.
|
||||
- `pnpm protocol:gen`: emits JSON Schema (`dist/protocol.schema.json`). ✅
|
||||
- `pnpm protocol:gen:swift`: generates Swift `Codable` models (`apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift`). ✅
|
||||
- AJV compile step for server validators. ✅
|
||||
- **CI**: add a job that fails if schema or generated Swift is stale. ✅ `pnpm protocol:check` (runs gen + git diff).
|
||||
|
||||
## Phase 1 — Protocol specification
|
||||
- Frames (WS text JSON, all with explicit `type`):
|
||||
- `req {type:"req", id, method:"connect", params:{minProtocol,maxProtocol,client:{name,version,platform,deviceFamily?,modelIdentifier?,mode,instanceId}, caps, auth:{token?}, locale?, userAgent?}}`
|
||||
- `res {type:"res", id, ok:true, payload: hello-ok }` (or `ok:false` then close)
|
||||
- `hello-ok {type:"hello-ok", protocol:<chosen>, server:{version,commit,host,connId}, features:{methods,events}, snapshot:{presence[], health, stateVersion:{presence,health}, uptimeMs}, policy:{maxPayload, maxBufferedBytes, tickIntervalMs}}`
|
||||
- `req {type:"req", id, method, params?}`
|
||||
- `res {type:"res", id, ok, payload?, error?}` where `error` = `{code,message,details?,retryable?,retryAfterMs?}`
|
||||
- `event {type:"event", event, payload, seq?, stateVersion?}` (presence/tick/shutdown/agent)
|
||||
- `close` (standard WS close codes; policy uses 1008 for slow consumer/unauthorized, 1012/1001 for restart)
|
||||
- Payload types:
|
||||
- `PresenceEntry {host, ip, version, platform?, deviceFamily?, modelIdentifier?, mode, lastInputSeconds?, ts, reason?, tags?[], instanceId?}`
|
||||
- `HealthSnapshot` (match existing `clawdbot health --json` fields)
|
||||
- `AgentEvent` (streamed tool/output; `{runId, seq, stream, data, ts}`)
|
||||
- `TickEvent {ts}`
|
||||
- `ShutdownEvent {reason, restartExpectedMs?}`
|
||||
- Error codes: `NOT_LINKED`, `AGENT_TIMEOUT`, `INVALID_REQUEST`, `UNAVAILABLE`.
|
||||
- Error shape: `{code, message, details?, retryable?, retryAfterMs?}`
|
||||
- Rules:
|
||||
- First frame must be `req` with `method:"connect"`; otherwise close. Add handshake timeout (e.g., 3s) for silent clients.
|
||||
- Negotiate protocol: server picks within `[minProtocol,maxProtocol]`; if none, reply `res ok:false` and close.
|
||||
- Protocol version bump on breaking changes; `hello-ok` must include `minClient` when needed.
|
||||
- `stateVersion` increments for presence/health to drop stale deltas.
|
||||
- Stable IDs: client sends `instanceId`; server issues per-connection `connId` in `hello-ok`; presence entries may include `instanceId` to dedupe reconnects.
|
||||
- Token-based auth: bearer token in `auth.token`; required except for loopback development.
|
||||
- Presence is primarily connection-derived; client may add hints (e.g., lastInputSeconds); entries expire via TTL to keep the map bounded (e.g., 5m TTL, max 200 entries).
|
||||
- Idempotency keys: required for `send` and `agent` to safely retry after disconnects.
|
||||
- Size limits: bound first-frame size by `maxPayload`; reject early if exceeded.
|
||||
- Close on any non-JSON or wrong `type` before connect.
|
||||
- Per-op idempotency keys: client SHOULD supply an explicit key per `send`/`agent`; if omitted, server may derive a scoped key from `instanceId+connId`, but explicit keys are safer across reconnects.
|
||||
- Locale/userAgent are informational; server may log them for analytics but must not rely on them for access control.
|
||||
|
||||
## Phase 2 — Gateway WebSocket server
|
||||
- New module `src/gateway/server.ts`:
|
||||
- Bind 127.0.0.1:18789 (configurable).
|
||||
- On connect: validate `connect` params, send snapshot payload, start event pump.
|
||||
- Per-connection queues with backpressure (bounded; drop oldest non-critical).
|
||||
- WS-level caps: set `maxPayload` to cap frame size before JSON parse.
|
||||
- Emit `tick` every N seconds when idle (or WS ping/pong if adequate).
|
||||
- Emit `shutdown` before exit; then close sockets.
|
||||
- Methods implemented:
|
||||
- `health`, `status`, `system-presence`, `system-event`, `send`, `agent`.
|
||||
- Optional: `set-heartbeats` removed/renamed if heartbeat concept is retired.
|
||||
- Events implemented:
|
||||
- `agent`, `presence` (deltas, with `stateVersion`), `tick`, `shutdown`.
|
||||
- All events include `seq` for loss/out-of-order detection.
|
||||
- Logging: structured logs on connect/close/error; include client fingerprint.
|
||||
- Slow consumer policy:
|
||||
- Per-connection outbound queue limit (bytes/messages). If exceeded, drop non-critical events (presence/tick) or close with a policy violation / retryable code; clients reconnect with backoff.
|
||||
- Handshake edge cases:
|
||||
- Close on handshake timeout.
|
||||
- Close on over-limit first frame (maxPayload).
|
||||
- Close immediately on non-JSON or wrong `type` before connect.
|
||||
- Default guardrails: `maxPayload` ~512 KB, handshake timeout ~3 s, outbound buffered amount cap ~1.5 MB (tune as you implement).
|
||||
- Dedupe cache: bound TTL (~5m) and max size (~1000 entries); evict oldest first (LRU) to prevent memory growth.
|
||||
|
||||
## Phase 3 — Gateway CLI entrypoint
|
||||
- Add `clawdbot gateway` command in CLI program:
|
||||
- Reads config (port, WS options).
|
||||
- Foreground process; exit non-zero on fatal errors.
|
||||
- Flags: `--port`, `--no-tick` (optional), `--log-json` (optional).
|
||||
- System supervision docs for launchd/systemd (see `gateway.md`).
|
||||
|
||||
## Phase 4 — Presence/health snapshot & stateVersion
|
||||
- `hello-ok.snapshot` includes:
|
||||
- `presence[]` (current list)
|
||||
- `health` (full snapshot)
|
||||
- `stateVersion {presence:int, health:int}`
|
||||
- `uptimeMs`
|
||||
- `policy {maxPayload, maxBufferedBytes, tickIntervalMs}`
|
||||
- Emit `presence` deltas with updated `stateVersion.presence`.
|
||||
- Emit `tick` to indicate liveness when no other events occur.
|
||||
- Keep `health` method for manual refresh; not required after connect.
|
||||
- Presence expiry: prune entries older than TTL; enforce a max map size; include `stateVersion` in presence events.
|
||||
|
||||
## Phase 5 — Clients migration
|
||||
- **macOS app**:
|
||||
- Replace stdio/SSH RPC with WS client (tunneled via SSH/Tailscale for remote). ✅ GatewayConnection/ControlChannel now use Gateway WS.
|
||||
- Implement handshake, snapshot hydration, subscriptions to `presence`, `tick`, `agent`, `shutdown`. ✅ snapshot + presence events broadcast to InstancesStore; agent events still to wire to UI if desired.
|
||||
- Remove immediate `health/system-presence` fetch on connect. ✅ presence hydrated from snapshot; periodic refresh kept as fallback.
|
||||
- Handle connect failures (`res ok:false`) and retry with backoff if version/token mismatched. ✅ macOS GatewayChannel reconnects with exponential backoff.
|
||||
- **CLI**:
|
||||
- Add lightweight WS client helper for `status/health/send/agent` when Gateway is up. ✅ `gateway` subcommands use the Gateway over WS.
|
||||
- Consider a “local only” flag to avoid accidental remote connects. (optional; not needed with tunnel-first model.)
|
||||
- **WebChat backend**:
|
||||
- Single WS to Gateway; seed UI from snapshot; forward `presence/tick/agent` to browser. ✅ implemented via the WebChat gateway client in `webchat/server.ts`.
|
||||
- Fail fast if handshake fails; no fallback transports. ✅ (webchat returns gateway unavailable)
|
||||
|
||||
## Phase 6 — Send/agent path hardening
|
||||
- Ensure only the Gateway can open Baileys; no IPC fallback.
|
||||
- `send` executes in-process; respond with explicit result/error, not via heartbeat.
|
||||
- `agent` spawns Pi; respond quickly with `{runId,status:"accepted"}` (ack); stream `event:agent {runId, seq, stream, data, ts}`; final `res:agent {runId, status:"ok"|"error", summary}` completes request (idempotent via key).
|
||||
- Idempotency: side-effecting methods (`send`, `agent`) accept an idempotency key; keep a short-lived dedupe cache to avoid double-send on client retries. Client retry flow: on timeout/close, retry with same key; Gateway returns cached result when available; cache TTL ~5m and bounded.
|
||||
- Agent stream ordering: enforce monotonic `seq` per runId; if gap detected by server, terminate stream with error; if detected by client, issue a retry with same idempotency key.
|
||||
- Send response shape: `{messageId?, toJid?, error?}` and always include `runId` when available for traceability.
|
||||
|
||||
## Phase 7 — Keepalive and shutdown semantics
|
||||
- Keepalive: `tick` events (or WS ping/pong) at fixed interval; clients treat missing ticks as disconnect and reconnect.
|
||||
- Shutdown: send `event:shutdown {reason, restartExpectedMs?}` then close sockets; clients auto-reconnect.
|
||||
- Restart semantics: close sockets with a standard retryable close code; on reconnect, `hello-ok` snapshot must be sufficient to rebuild UI without event replay.
|
||||
- Use a standard close code (e.g., 1012 service restart or 1001 going away) for planned restart; 1008 policy violation for slow consumers.
|
||||
- Include `policy` in `hello-ok` so clients know the tick interval and buffer limits to tune their expectations.
|
||||
|
||||
## Phase 8 — Cleanup and deprecation
|
||||
- Retire `clawdbot rpc` as default path; keep only if explicitly requested (documented as legacy).
|
||||
- Remove reliance on `src/infra/control-channel.ts` for new clients; mark as legacy or delete after migration. ✅ file removed; mac app now uses Gateway WS.
|
||||
- Update README, docs (`architecture.md`, `gateway.md`, `webchat.md`) to final shapes; remove `control-api.md` references if obsolete.
|
||||
- Presence hygiene:
|
||||
- Presence derived primarily from connection (server-fills host/ip/version/connId/instanceId); allow client hints (e.g., lastInputSeconds).
|
||||
- Add TTL/expiry; prune to keep map bounded (e.g., 5m TTL, max 200 entries).
|
||||
|
||||
## Edge cases and ordering
|
||||
- Event ordering: all events carry `seq`; clients detect gaps and should re-fetch snapshot (or targeted refresh) on gap.
|
||||
- Partial handshakes: if client connects and never sends `req:connect`, server closes after handshake timeout.
|
||||
- Garbage/oversize first frame: bounded by `maxPayload`; server closes immediately on parse failure.
|
||||
- Duplicate delivery on reconnect: clients must send idempotency keys; Gateway dedupe cache prevents double-send/agent execution.
|
||||
- Snapshot sufficiency: `hello-ok.snapshot` must contain enough to render UI after reconnect without event replay.
|
||||
- Client reconnect guidance: exponential backoff with jitter; reuse same `instanceId` across reconnects to avoid duplicate presence; resend idempotency keys for in-flight sends/agents; on seq gap, issue `health`/`system-presence` refresh.
|
||||
- Presence TTL/defaults: set a concrete TTL (e.g., 5 minutes) and prune periodically; cap the presence map size with LRU if needed.
|
||||
- Replay policy: if seq gap detected, server does not replay; clients must pull fresh `health` + `system-presence` and continue.
|
||||
|
||||
## Phase 9 — Testing & validation
|
||||
- Unit: frame validation, handshake failure, auth/token, stateVersion on presence events, agent stream fanout, send dedupe. ✅
|
||||
- Integration: connect → snapshot → req/res → streaming agent → shutdown. ✅ Covered in gateway WS tests (connect/health/status/presence, agent ack+final, shutdown broadcast).
|
||||
- Load: multiple concurrent WS clients; backpressure behavior under burst. ✅ Basic fanout test with 3 clients receiving presence broadcast; heavier soak still recommended.
|
||||
- Mac app smoke: presence/health render from snapshot; reconnect on tick loss. (Manual: open Instances tab, verify snapshot after connect, induce seq gap by toggling wifi, ensure UI refreshes.)
|
||||
- WebChat smoke: snapshot seed + event updates; tunnel scenario. ✅ Offline snapshot harness in `src/webchat/server.test.ts` (mock gateway) now passes; live tunnel still recommended for manual.
|
||||
- Idempotency tests: retry send/agent with same key after forced disconnect; expect deduped result. ✅ send + agent dedupe + reconnect retry covered in gateway tests.
|
||||
- Seq-gap handling: ✅ clients now detect seq gaps (WebChat gateway client + mac `GatewayConnection/GatewayChannel`) and refresh health/presence (webchat) or trigger UI refresh (mac). Load-test still optional.
|
||||
|
||||
## Phase 10 — Rollout
|
||||
- Version bump; release notes: breaking change to control plane (WS only).
|
||||
- Ship launchd/systemd templates for `clawdbot gateway`.
|
||||
- Recommend Tailscale/SSH tunnel for remote access; no additional auth layer assumed in this model.
|
||||
|
||||
---
|
||||
|
||||
- Quick checklist
|
||||
- [x] Protocol types & schemas (TS + JSON Schema + Swift via quicktype)
|
||||
- [x] AJV validators wired
|
||||
- [x] WS server with connect → snapshot → events
|
||||
- [x] Tick + shutdown events
|
||||
- [x] stateVersion + presence deltas
|
||||
- [x] Gateway CLI command
|
||||
- [x] macOS app WS client (Gateway WS for control; presence events live; agent stream UI pending)
|
||||
- [x] WebChat WS client
|
||||
- [x] Remove legacy stdin/TCP paths from default flows (file removed; mac app/CLI on Gateway)
|
||||
- [x] Tests (unit/integration/load) — unit + integration + basic fanout/reconnect; heavier load/soak optional
|
||||
- [x] Docs updated and legacy docs flagged
|
||||
Loaded 100 of 456 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user