Merge branch 'main' into tobias-sync
3
.gitignore
vendored
@@ -50,3 +50,6 @@ apps/ios/*.dSYM.zip
|
||||
# provisioning profiles (local)
|
||||
apps/ios/*.mobileprovision
|
||||
.env
|
||||
|
||||
# Local untracked files
|
||||
.local/
|
||||
|
||||
14
AGENTS.md
@@ -28,6 +28,7 @@
|
||||
- Add brief code comments for tricky or non-obvious logic.
|
||||
- Keep files concise; extract helpers instead of “V2” copies. Use existing patterns for CLI options and dependency injection via `createDefaultDeps`.
|
||||
- Aim to keep files under ~700 LOC; guideline only (not a hard guardrail). Split/refactor when it improves clarity or testability.
|
||||
- Naming: use **Clawdbot** for product/app/docs headings; use `clawdbot` for CLI command, package/binary, paths, and config keys.
|
||||
|
||||
## Testing Guidelines
|
||||
- Framework: Vitest with V8 coverage thresholds (70% lines/branches/functions/statements).
|
||||
@@ -63,6 +64,7 @@
|
||||
|
||||
## Agent-Specific Notes
|
||||
- Vocabulary: "makeup" = "mac app".
|
||||
- When answering questions, respond with high-confidence answers only: verify in code; do not guess.
|
||||
- Gateway currently runs only as the menubar app; there is no separate LaunchAgent/helper label installed. Restart via the Clawdbot Mac app or `scripts/restart-mac.sh`; to verify/kill use `launchctl print gui/$UID | grep clawdbot` rather than assuming a fixed label. **When debugging on macOS, start/stop the gateway via the app, not ad-hoc tmux sessions; kill any temporary tunnels before handoff.**
|
||||
- macOS logs: use `./scripts/clawlog.sh` (aka `vtlog`) to query unified logs for the Clawdbot subsystem; it supports follow/tail/category filters and expects passwordless sudo for `/usr/bin/log`.
|
||||
- If shared guardrails are available locally, review them; otherwise follow this repo's guidance.
|
||||
@@ -81,6 +83,10 @@
|
||||
- **Multi-agent safety:** do **not** switch branches / check out a different branch unless explicitly requested.
|
||||
- **Multi-agent safety:** running multiple agents is OK as long as each agent has its own session.
|
||||
- **Multi-agent safety:** when you see unrecognized files, keep going; focus on your changes and commit only those.
|
||||
- Lobster seam: use the shared CLI palette in `src/terminal/palette.ts` (no hardcoded colors); apply palette to onboarding/config prompts and other TTY UI output as needed.
|
||||
- **Multi-agent safety:** focus reports on your edits; avoid guard-rail disclaimers unless truly blocked; when multiple agents touch the same file, continue if safe; end with a brief “other files present” note only if relevant.
|
||||
- Bug investigations: read source code of relevant npm dependencies and all related local code before concluding; aim for high-confidence root cause.
|
||||
- Code style: add brief comments for tricky logic; keep files under ~500 LOC when feasible (split/refactor as needed).
|
||||
- When asked to open a “session” file, open the Pi session logs under `~/.clawdbot/sessions/*.jsonl` (newest unless a specific ID is given), not the default `sessions.json`. If logs are needed from another machine, SSH via Tailscale and read the same path there.
|
||||
- Menubar dimming + restart flow mirrors Trimmy: use `scripts/restart-mac.sh` (kills all Clawdbot variants, runs `swift build`, packages, relaunches). Icon dimming depends on MenuBarExtraAccess wiring in AppMain; keep `appearsDisabled` updates intact when touching the status item.
|
||||
- Do not rebuild the macOS app over SSH; rebuilds must be run directly on the Mac.
|
||||
@@ -88,17 +94,17 @@
|
||||
- Voice wake forwarding tips:
|
||||
- Command template should stay `clawdbot-mac agent --message "${text}" --thinking low`; `VoiceWakeForwarder` already shell-escapes `${text}`. Don’t add extra quotes.
|
||||
- launchd PATH is minimal; ensure the app’s launch agent PATH includes standard system paths plus your pnpm bin (typically `$HOME/Library/pnpm`) so `pnpm`/`clawdbot` binaries resolve when invoked via `clawdbot-mac`.
|
||||
- For manual `clawdbot send` messages that include `!`, use the heredoc pattern noted below to avoid the Bash tool’s escaping.
|
||||
- For manual `clawdbot message send` messages that include `!`, use the heredoc pattern noted below to avoid the Bash tool’s escaping.
|
||||
|
||||
## Exclamation Mark Escaping Workaround
|
||||
The Claude Code Bash tool escapes `!` to `\\!` in command arguments. When using `clawdbot send` with messages containing exclamation marks, use heredoc syntax:
|
||||
The Claude Code Bash tool escapes `!` to `\\!` in command arguments. When using `clawdbot message send` with messages containing exclamation marks, use heredoc syntax:
|
||||
|
||||
```bash
|
||||
# WRONG - will send "Hello\\!" with backslash
|
||||
clawdbot send --to "+1234" --message 'Hello!'
|
||||
clawdbot message send --to "+1234" --message 'Hello!'
|
||||
|
||||
# CORRECT - use heredoc to avoid escaping
|
||||
clawdbot send --to "+1234" --message "$(cat <<'EOF'
|
||||
clawdbot message send --to "+1234" --message "$(cat <<'EOF'
|
||||
Hello!
|
||||
EOF
|
||||
)"
|
||||
|
||||
67
CHANGELOG.md
@@ -2,28 +2,95 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- CLI: add `sandbox list` and `sandbox recreate` commands for managing Docker sandbox containers after image/config updates. (#563) — thanks @pasogott
|
||||
- Providers: add Microsoft Teams provider with polling, attachments, and CLI send support. (#404) — thanks @onutc
|
||||
- Commands: accept /models as an alias for /model.
|
||||
- Debugging: add raw model stream logging flags and document gateway watch mode.
|
||||
- Agent: add claude-cli/opus-4.5 runner via Claude CLI with resume support (tools disabled).
|
||||
- CLI: move `clawdbot message` to subcommands (`message send|poll|…`), fold Discord/Slack/Telegram/WhatsApp tools into `message`, and require `--provider` unless only one provider is configured.
|
||||
- CLI: improve `logs` output (pretty/plain/JSONL), add gateway unreachable hint, and document logging.
|
||||
- WhatsApp: route queued replies to the original sender instead of the bot's own number. (#534) — thanks @mcinteerj
|
||||
- Models: add OAuth expiry checks in doctor, expanded `models status` auth output (missing auth + `--check` exit codes). (#538) — thanks @latitudeki5223
|
||||
- Deps: bump Pi to 0.40.0 and drop pi-ai patch (upstream 429 fix). (#543) — thanks @mcinteerj
|
||||
- Security: per-agent mention patterns and group elevated directives now require explicit mention to avoid cross-agent toggles.
|
||||
- Config: support inline env vars in config (`env.*` / `env.vars`) and document env precedence.
|
||||
- Agent: enable adaptive context pruning by default for tool-result trimming.
|
||||
- Doctor: check config/state permissions and offer to tighten them. — thanks @steipete
|
||||
- Doctor/Daemon: audit supervisor configs, add --repair/--force flows, surface service config audits in daemon status, and document user vs system services. — thanks @steipete
|
||||
- Daemon: align generated systemd unit with docs for network-online + restart delay. (#479) — thanks @azade-c
|
||||
- Daemon: add KillMode=process to systemd units to avoid podman restart hangs. (#541) — thanks @ogulcancelik
|
||||
- Doctor: run legacy state migrations in non-interactive mode without prompts.
|
||||
- Cron: parse Telegram topic targets for isolated delivery. (#478) — thanks @nachoiacovino
|
||||
- Outbound: default Telegram account selection for config-only tokens; remove heartbeat-specific accountId handling. (follow-up #516) — thanks @YuriNachos
|
||||
- Cron: allow Telegram delivery targets with topic/thread IDs (e.g. `-100…:topic:123`). (#474) — thanks @mitschabaude-bot
|
||||
- Heartbeat: resolve Telegram account IDs from config-only tokens; cron tool accepts canonical `jobId` and legacy `id` for job actions. (#516) — thanks @YuriNachos
|
||||
- Discord: stop provider when gateway reconnects are exhausted and surface errors. (#514) — thanks @joshp123
|
||||
- Agents: strip empty assistant text blocks from session history to avoid Claude API 400s. (#210)
|
||||
- Auto-reply: preserve block reply ordering with timeout fallback for streaming. (#503) — thanks @joshp123
|
||||
- Auto-reply: block reply ordering fix (duplicate PR superseded by #503). (#483) — thanks @AbhisekBasu1
|
||||
- Auto-reply: avoid splitting outbound chunks inside parentheses. (#499) — thanks @philipp-spiess
|
||||
- Auto-reply: preserve spacing when stripping inline directives. (#539) — thanks @joshp123
|
||||
- Auto-reply: fix /status usage summary filtering for the active provider.
|
||||
- Status: show provider prefix in /status model display. (#506) — thanks @mcinteerj
|
||||
- Status: compact /status with session token usage + estimated cost, add `/cost` per-response usage lines (tokens-only for OAuth).
|
||||
- Status: show active auth profile and key snippet in /status.
|
||||
- Agent: promote `<think>`/`<thinking>` tag reasoning into structured thinking blocks so `/reasoning` works consistently for OpenAI-compat providers.
|
||||
- macOS: package ClawdbotKit resources and Swift 6.2 compatibility dylib to avoid launch/tool crashes. (#473) — thanks @gupsammy
|
||||
- WhatsApp: group `/model list` output by provider for scannability. (#456) - thanks @mcinteerj
|
||||
- Hooks: allow per-hook model overrides for webhook/Gmail runs (e.g. GPT 5 Mini).
|
||||
- Control UI: logs tab opens at the newest entries (bottom).
|
||||
- Control UI: add Docs link, remove chat composer divider, and add New session button.
|
||||
- Control UI: link sessions list to chat view. (#471) — thanks @HazAT
|
||||
- Control UI: show/patch per-session reasoning level and render extracted reasoning in chat.
|
||||
- Control UI: queue outgoing chat messages, add Enter-to-send, and show queued items. (#527) — thanks @YuriNachos
|
||||
- Control UI: drop explicit `ui:install` step; `ui:build` now auto-installs UI deps (docs + update flow).
|
||||
- Telegram: retry long-polling conflicts with backoff to avoid fatal exits.
|
||||
- Telegram: fix grammY fetch type mismatch when injecting `fetch`. (#512) — thanks @YuriNachos
|
||||
- WhatsApp: resolve @lid JIDs via Baileys mapping to unblock inbound messages. (#415)
|
||||
- Pairing: replies now include sender ids for Discord/Slack/Signal/iMessage/WhatsApp; pairing list labels them explicitly.
|
||||
- Signal: accept UUID-only senders for pairing/allowlists/routing when sourceNumber is missing. (#523) — thanks @neist
|
||||
- Agent system prompt: avoid automatic self-updates unless explicitly requested.
|
||||
- Onboarding: tighten QuickStart hint copy for configuring later.
|
||||
- Onboarding: set Gemini 3 Pro as the default model for Gemini API key auth. (#489) — thanks @jonasjancarik
|
||||
- Onboarding: avoid “token expired” for Codex CLI when expiry is heuristic.
|
||||
- Onboarding: QuickStart jumps straight into provider selection with Telegram preselected when unset.
|
||||
- Onboarding: QuickStart auto-installs the Gateway daemon with Node (no runtime picker).
|
||||
- Onboarding: clarify WhatsApp owner number prompt and label pairing phone number.
|
||||
- Onboarding: add hosted MiniMax M2.1 API key flow + config. (#495) — thanks @tobiasbischoff
|
||||
- Daemon runtime: remove Bun from selection options.
|
||||
- CLI: restore hidden `gateway-daemon` alias for legacy launchd configs.
|
||||
- Onboarding/Configure: add OpenAI API key flow that stores in shared `~/.clawdbot/.env` for launchd; simplify Anthropic token prompt order.
|
||||
- Configure/Onboarding: show Control UI docs with gateway reachability status and only offer to open when a gateway is detected; default model prompt now prefers Opus 4.5 for Anthropic auth.
|
||||
- Control UI: show skill install progress + per-skill results, hide install once binaries present. (#445) — thanks @pkrmf
|
||||
- Providers/Doctor: surface Discord privileged intent (Message Content) misconfiguration with actionable warnings.
|
||||
- Providers/Doctor: warn when Telegram config expects unmentioned group messages but Bot API privacy mode is likely enabled; surface WhatsApp login/disconnect hints.
|
||||
- Providers/Doctor: add last inbound/outbound activity timestamps in `providers status` and extend `--probe` with Discord channel permission + Telegram group membership audits.
|
||||
- Docs: add provider troubleshooting index (`/providers/troubleshooting`) and link it from the main troubleshooting guide.
|
||||
- Docs: clarify model allowlist errors and add safety notes for verbose/reasoning in groups.
|
||||
- Docs: add Ansible installation guide. (#545) — thanks @pasogott
|
||||
- Telegram: include the user id in DM pairing messages and label it clearly in `clawdbot pairing list --provider telegram`.
|
||||
- Apps: refresh iOS/Android/macOS app icons for Clawdbot branding. (#521) — thanks @fishfisher
|
||||
- Docs: expand parameter descriptions for agent/wake hooks. (#532) — thanks @mcinteerj
|
||||
- Docs: add community showcase entries from Discord. (#476) — thanks @gupsammy
|
||||
- TUI: refresh status bar after think/verbose/reasoning changes. (#519) — thanks @jdrhyne
|
||||
- Status: show Verbose/Elevated only when enabled.
|
||||
- Status: filter usage summary to the active model provider.
|
||||
- Status: map model providers to usage sources so unrelated usage doesn’t appear.
|
||||
- Commands: allow /elevated off in groups without a mention; keep /elevated on mention-gated.
|
||||
- Commands: keep multi-directive messages from clearing directive handling.
|
||||
- Commands: warn when /elevated runs in direct (unsandboxed) runtime.
|
||||
- Commands: treat mention-bypassed group command messages as mentioned so elevated directives respond.
|
||||
- Commands: return /status in directive-only multi-line messages.
|
||||
- Models: fall back to configured models when the provider catalog is unavailable.
|
||||
- Agent system prompt: add messaging guidance for reply routing and cross-session sends. (#526) — thanks @neist
|
||||
- Agent: bypass Anthropic OAuth tool-name blocks by capitalizing built-ins and keeping pruning tool matching case-insensitive. (#553) — thanks @andrewting19
|
||||
- Commands/Tools: disable /restart and gateway restart tool by default (enable with commands.restart=true).
|
||||
- Gateway/CLI: add `clawdbot gateway discover` (Bonjour scan on `local.` + `clawdbot.internal.`) with `--timeout` and `--json`. — thanks @steipete
|
||||
- Gateway/CLI: make `clawdbot gateway status` human-readable by default, add `--json`, and probe localhost + configured remote (warn on multiple gateways). — thanks @steipete
|
||||
- Gateway/CLI: support remote loopback gateways via SSH tunnel in `clawdbot gateway status` (`--ssh` / `--ssh-auto`). — thanks @steipete
|
||||
- CLI: add global `--no-color` (and respect `NO_COLOR=1`) to disable ANSI output. — thanks @steipete
|
||||
- CLI: centralize lobster palette + apply it to onboarding/config prompts. — thanks @steipete
|
||||
- Gateway/CLI: add `clawdbot gateway --dev/--reset` to auto-create a dev config/workspace with a robot identity (no BOOTSTRAP.md). — thanks @steipete
|
||||
|
||||
## 2026.1.8
|
||||
|
||||
|
||||
32
README.md
@@ -62,7 +62,7 @@ clawdbot onboard --install-daemon
|
||||
clawdbot gateway --port 18789 --verbose
|
||||
|
||||
# Send a message
|
||||
clawdbot send --to +1234567890 --message "Hello from Clawdbot"
|
||||
clawdbot message send --to +1234567890 --message "Hello from Clawdbot"
|
||||
|
||||
# Talk to the assistant (optionally deliver back to WhatsApp/Telegram/Slack/Discord)
|
||||
clawdbot agent --message "Ship checklist" --thinking high
|
||||
@@ -79,8 +79,7 @@ git clone https://github.com/clawdbot/clawdbot.git
|
||||
cd clawdbot
|
||||
|
||||
pnpm install
|
||||
pnpm ui:install
|
||||
pnpm ui:build
|
||||
pnpm ui:build # auto-installs UI deps on first run
|
||||
pnpm build
|
||||
|
||||
pnpm clawdbot onboard --install-daemon
|
||||
@@ -240,11 +239,12 @@ ClawdHub is a minimal skill registry. With ClawdHub enabled, the agent can searc
|
||||
|
||||
Send these in WhatsApp/Telegram/Slack/WebChat (group commands are owner-only):
|
||||
|
||||
- `/status` — health + session info (group shows activation mode)
|
||||
- `/status` — compact session status (model + tokens, cost when available)
|
||||
- `/new` or `/reset` — reset the session
|
||||
- `/compact` — compact session context (summary)
|
||||
- `/think <level>` — off|minimal|low|medium|high
|
||||
- `/verbose on|off`
|
||||
- `/cost on|off` — append per-response token/cost usage lines
|
||||
- `/restart` — restart the gateway (owner-only in groups)
|
||||
- `/activation mention|always` — group activation toggle (groups only)
|
||||
|
||||
@@ -452,17 +452,21 @@ by Peter Steinberger and the community.
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines, maintainers, and how to submit PRs.
|
||||
AI/vibe-coded PRs welcome! 🤖
|
||||
|
||||
Special thanks to @andrewting19 for the Anthropic OAuth tool-name fix.
|
||||
|
||||
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/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/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/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/maxsumrall"><img src="https://avatars.githubusercontent.com/u/628843?v=4&s=48" width="48" height="48" alt="maxsumrall" title="maxsumrall"/></a> <a href="https://github.com/xadenryan"><img src="https://avatars.githubusercontent.com/u/165437834?v=4&s=48" width="48" height="48" alt="xadenryan" title="xadenryan"/></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/hsrvc"><img src="https://avatars.githubusercontent.com/u/129702169?v=4&s=48" width="48" height="48" alt="hsrvc" title="hsrvc"/></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/dantelex"><img src="https://avatars.githubusercontent.com/u/631543?v=4&s=48" width="48" height="48" alt="dantelex" title="dantelex"/></a>
|
||||
<a href="https://github.com/daveonkels"><img src="https://avatars.githubusercontent.com/u/533642?v=4&s=48" width="48" height="48" alt="daveonkels" title="daveonkels"/></a> <a href="https://github.com/omniwired"><img src="https://avatars.githubusercontent.com/u/322761?v=4&s=48" width="48" height="48" alt="Eng. Juan Combetto" title="Eng. Juan Combetto"/></a> <a href="https://github.com/mbelinky"><img src="https://avatars.githubusercontent.com/u/132747814?v=4&s=48" width="48" height="48" alt="Mariano Belinky" title="Mariano Belinky"/></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/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/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/vsabavat"><img src="https://avatars.githubusercontent.com/u/50385532?v=4&s=48" width="48" height="48" alt="Vasanth Rao Naik Sabavat" title="Vasanth Rao Naik Sabavat"/></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/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/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/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/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/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/sircrumpet"><img src="https://avatars.githubusercontent.com/u/4436535?v=4&s=48" width="48" height="48" alt="sircrumpet" title="sircrumpet"/></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/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/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/lutr0"><img src="https://avatars.githubusercontent.com/u/76906369?v=4&s=48" width="48" height="48" alt="lutr0" title="lutr0"/></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>
|
||||
<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/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/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/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/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/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/buddyh"><img src="https://avatars.githubusercontent.com/u/31752869?v=4&s=48" width="48" height="48" alt="buddyh" title="buddyh"/></a> <a href="https://github.com/search?q=sheeek"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="sheeek" title="sheeek"/></a> <a href="https://github.com/timkrase"><img src="https://avatars.githubusercontent.com/u/38947626?v=4&s=48" width="48" height="48" alt="timkrase" title="timkrase"/></a> <a href="https://github.com/gupsammy"><img src="https://avatars.githubusercontent.com/u/20296019?v=4&s=48" width="48" height="48" alt="gupsammy" title="gupsammy"/></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/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/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/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/RandyVentures"><img src="https://avatars.githubusercontent.com/u/149904821?v=4&s=48" width="48" height="48" alt="RandyVentures" title="RandyVentures"/></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/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/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/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/manmal"><img src="https://avatars.githubusercontent.com/u/142797?v=4&s=48" width="48" height="48" alt="manmal" title="manmal"/></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/zats"><img src="https://avatars.githubusercontent.com/u/2688806?v=4&s=48" width="48" height="48" alt="zats" title="zats"/></a> <a href="https://github.com/djangonavarro220"><img src="https://avatars.githubusercontent.com/u/251162586?v=4&s=48" width="48" height="48" alt="Django Navarro" title="Django Navarro"/></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/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/fcatuhe"><img src="https://avatars.githubusercontent.com/u/17382215?v=4&s=48" width="48" height="48" alt="fcatuhe" title="fcatuhe"/></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/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/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/search?q=Sash%20Catanzarite"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Sash Catanzarite" title="Sash Catanzarite"/></a>
|
||||
<a href="https://github.com/search?q=VAC"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="VAC" title="VAC"/></a> <a href="https://github.com/search?q=alejandro%20maza"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="alejandro maza" title="alejandro maza"/></a> <a href="https://github.com/antons"><img src="https://avatars.githubusercontent.com/u/129705?v=4&s=48" width="48" height="48" alt="antons" title="antons"/></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/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/search?q=Clawd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Clawd" title="Clawd"/></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/erikpr1994"><img src="https://avatars.githubusercontent.com/u/6299331?v=4&s=48" width="48" height="48" alt="erikpr1994" title="erikpr1994"/></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/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/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/search?q=Jarvis"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jarvis" title="Jarvis"/></a> <a href="https://github.com/jonasjancarik"><img src="https://avatars.githubusercontent.com/u/2459191?v=4&s=48" width="48" height="48" alt="jonasjancarik" title="jonasjancarik"/></a> <a href="https://github.com/jdrhyne"><img src="https://avatars.githubusercontent.com/u/7828464?v=4&s=48" width="48" height="48" alt="Jonathan D. Rhyne (DJ-D)" title="Jonathan D. Rhyne (DJ-D)"/></a> <a href="https://github.com/search?q=Kit"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Kit" title="Kit"/></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/kkarimi"><img src="https://avatars.githubusercontent.com/u/875218?v=4&s=48" width="48" height="48" alt="kkarimi" title="kkarimi"/></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/mrdbstn"><img src="https://avatars.githubusercontent.com/u/58957632?v=4&s=48" width="48" height="48" alt="mrdbstn" title="mrdbstn"/></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/nexty5870"><img src="https://avatars.githubusercontent.com/u/3869659?v=4&s=48" width="48" height="48" alt="nexty5870" title="nexty5870"/></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/reeltimeapps"><img src="https://avatars.githubusercontent.com/u/637338?v=4&s=48" width="48" height="48" alt="reeltimeapps" title="reeltimeapps"/></a> <a href="https://github.com/RLTCmpe"><img src="https://avatars.githubusercontent.com/u/10762242?v=4&s=48" width="48" height="48" alt="RLTCmpe" title="RLTCmpe"/></a> <a href="https://github.com/search?q=Rolf%20Fredheim"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Rolf Fredheim" title="Rolf Fredheim"/></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/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/search?q=Azade"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Azade" title="Azade"/></a> <a href="https://github.com/search?q=ddyo"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ddyo" title="ddyo"/></a>
|
||||
<a href="https://github.com/search?q=Erik"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Erik" title="Erik"/></a> <a href="https://github.com/search?q=Manuel%20Maly"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Manuel Maly" title="Manuel Maly"/></a> <a href="https://github.com/search?q=Mourad%20Boustani"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Mourad Boustani" title="Mourad Boustani"/></a> <a href="https://github.com/pcty-nextgen-ios-builder"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="pcty-nextgen-ios-builder" title="pcty-nextgen-ios-builder"/></a> <a href="https://github.com/search?q=Quentin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Quentin" title="Quentin"/></a> <a href="https://github.com/search?q=Randy%20Torres"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Randy Torres" title="Randy Torres"/></a> <a href="https://github.com/search?q=Tobias%20Bischoff"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Tobias Bischoff" title="Tobias Bischoff"/></a> <a href="https://github.com/search?q=William%20Stock"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="William Stock" title="William Stock"/></a>
|
||||
<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/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/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/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/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/maxsumrall"><img src="https://avatars.githubusercontent.com/u/628843?v=4&s=48" width="48" height="48" alt="maxsumrall" title="maxsumrall"/></a> <a href="https://github.com/xadenryan"><img src="https://avatars.githubusercontent.com/u/165437834?v=4&s=48" width="48" height="48" alt="xadenryan" title="xadenryan"/></a> <a href="https://github.com/hsrvc"><img src="https://avatars.githubusercontent.com/u/129702169?v=4&s=48" width="48" height="48" alt="hsrvc" title="hsrvc"/></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/dantelex"><img src="https://avatars.githubusercontent.com/u/631543?v=4&s=48" width="48" height="48" alt="dantelex" title="dantelex"/></a>
|
||||
<a href="https://github.com/daveonkels"><img src="https://avatars.githubusercontent.com/u/533642?v=4&s=48" width="48" height="48" alt="daveonkels" title="daveonkels"/></a> <a href="https://github.com/omniwired"><img src="https://avatars.githubusercontent.com/u/322761?v=4&s=48" width="48" height="48" alt="Eng. Juan Combetto" title="Eng. Juan Combetto"/></a> <a href="https://github.com/mbelinky"><img src="https://avatars.githubusercontent.com/u/132747814?v=4&s=48" width="48" height="48" alt="Mariano Belinky" title="Mariano Belinky"/></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/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/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/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/gupsammy"><img src="https://avatars.githubusercontent.com/u/20296019?v=4&s=48" width="48" height="48" alt="gupsammy" title="gupsammy"/></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/vsabavat"><img src="https://avatars.githubusercontent.com/u/50385532?v=4&s=48" width="48" height="48" alt="Vasanth Rao Naik Sabavat" title="Vasanth Rao Naik Sabavat"/></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/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/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/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/sircrumpet"><img src="https://avatars.githubusercontent.com/u/4436535?v=4&s=48" width="48" height="48" alt="sircrumpet" title="sircrumpet"/></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/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/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/lutr0"><img src="https://avatars.githubusercontent.com/u/76906369?v=4&s=48" width="48" height="48" alt="lutr0" title="lutr0"/></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> <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/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/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/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/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/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/buddyh"><img src="https://avatars.githubusercontent.com/u/31752869?v=4&s=48" width="48" height="48" alt="buddyh" title="buddyh"/></a> <a href="https://github.com/search?q=sheeek"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="sheeek" title="sheeek"/></a> <a href="https://github.com/timkrase"><img src="https://avatars.githubusercontent.com/u/38947626?v=4&s=48" width="48" height="48" alt="timkrase" title="timkrase"/></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/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/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/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/RandyVentures"><img src="https://avatars.githubusercontent.com/u/149904821?v=4&s=48" width="48" height="48" alt="RandyVentures" title="RandyVentures"/></a> <a href="https://github.com/search?q=Yurii%20Chukhlib"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Yurii Chukhlib" title="Yurii Chukhlib"/></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/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/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/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/manmal"><img src="https://avatars.githubusercontent.com/u/142797?v=4&s=48" width="48" height="48" alt="manmal" title="manmal"/></a> <a href="https://github.com/ogulcancelik"><img src="https://avatars.githubusercontent.com/u/7064011?v=4&s=48" width="48" height="48" alt="ogulcancelik" title="ogulcancelik"/></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/zats"><img src="https://avatars.githubusercontent.com/u/2688806?v=4&s=48" width="48" height="48" alt="zats" title="zats"/></a> <a href="https://github.com/djangonavarro220"><img src="https://avatars.githubusercontent.com/u/251162586?v=4&s=48" width="48" height="48" alt="Django Navarro" title="Django Navarro"/></a> <a href="https://github.com/search?q=L36%20Server"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="L36 Server" title="L36 Server"/></a> <a href="https://github.com/neist"><img src="https://avatars.githubusercontent.com/u/1029724?v=4&s=48" width="48" height="48" alt="neist" title="neist"/></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/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/erik-agens"><img src="https://avatars.githubusercontent.com/u/80908960?v=4&s=48" width="48" height="48" alt="erik-agens" title="erik-agens"/></a>
|
||||
<a href="https://github.com/fcatuhe"><img src="https://avatars.githubusercontent.com/u/17382215?v=4&s=48" width="48" height="48" alt="fcatuhe" title="fcatuhe"/></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/jonasjancarik"><img src="https://avatars.githubusercontent.com/u/2459191?v=4&s=48" width="48" height="48" alt="jonasjancarik" title="jonasjancarik"/></a> <a href="https://github.com/jdrhyne"><img src="https://avatars.githubusercontent.com/u/7828464?v=4&s=48" width="48" height="48" alt="Jonathan D. Rhyne (DJ-D)" title="Jonathan D. Rhyne (DJ-D)"/></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/mitschabaude-bot"><img src="https://avatars.githubusercontent.com/u/247582884?v=4&s=48" width="48" height="48" alt="mitschabaude-bot" title="mitschabaude-bot"/></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/philipp-spiess"><img src="https://avatars.githubusercontent.com/u/458591?v=4&s=48" width="48" height="48" alt="philipp-spiess" title="philipp-spiess"/></a> <a href="https://github.com/pkrmf"><img src="https://avatars.githubusercontent.com/u/1714267?v=4&s=48" width="48" height="48" alt="pkrmf" title="pkrmf"/></a> <a href="https://github.com/search?q=Sash%20Catanzarite"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Sash Catanzarite" title="Sash Catanzarite"/></a>
|
||||
<a href="https://github.com/search?q=VAC"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="VAC" title="VAC"/></a> <a href="https://github.com/search?q=alejandro%20maza"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="alejandro maza" title="alejandro maza"/></a> <a href="https://github.com/antons"><img src="https://avatars.githubusercontent.com/u/129705?v=4&s=48" width="48" height="48" alt="antons" title="antons"/></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/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/search?q=Clawd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Clawd" title="Clawd"/></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/erikpr1994"><img src="https://avatars.githubusercontent.com/u/6299331?v=4&s=48" width="48" height="48" alt="erikpr1994" title="erikpr1994"/></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/HazAT"><img src="https://avatars.githubusercontent.com/u/363802?v=4&s=48" width="48" height="48" alt="HazAT" title="HazAT"/></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/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/search?q=Jarvis"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jarvis" title="Jarvis"/></a> <a href="https://github.com/search?q=Keith%20the%20Silly%20Goose"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Keith the Silly Goose" title="Keith the Silly Goose"/></a> <a href="https://github.com/search?q=Kit"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Kit" title="Kit"/></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/kkarimi"><img src="https://avatars.githubusercontent.com/u/875218?v=4&s=48" width="48" height="48" alt="kkarimi" title="kkarimi"/></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/mrdbstn"><img src="https://avatars.githubusercontent.com/u/58957632?v=4&s=48" width="48" height="48" alt="mrdbstn" title="mrdbstn"/></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/nexty5870"><img src="https://avatars.githubusercontent.com/u/3869659?v=4&s=48" width="48" height="48" alt="nexty5870" title="nexty5870"/></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/prathamdby"><img src="https://avatars.githubusercontent.com/u/134331217?v=4&s=48" width="48" height="48" alt="prathamdby" title="prathamdby"/></a> <a href="https://github.com/reeltimeapps"><img src="https://avatars.githubusercontent.com/u/637338?v=4&s=48" width="48" height="48" alt="reeltimeapps" title="reeltimeapps"/></a> <a href="https://github.com/RLTCmpe"><img src="https://avatars.githubusercontent.com/u/10762242?v=4&s=48" width="48" height="48" alt="RLTCmpe" title="RLTCmpe"/></a> <a href="https://github.com/search?q=Rolf%20Fredheim"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Rolf Fredheim" title="Rolf Fredheim"/></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/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/YuriNachos"><img src="https://avatars.githubusercontent.com/u/19365375?v=4&s=48" width="48" height="48" alt="YuriNachos" title="YuriNachos"/></a>
|
||||
<a href="https://github.com/search?q=Azade"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Azade" title="Azade"/></a> <a href="https://github.com/search?q=ddyo"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ddyo" title="ddyo"/></a> <a href="https://github.com/search?q=Erik"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Erik" title="Erik"/></a> <a href="https://github.com/latitudeki5223"><img src="https://avatars.githubusercontent.com/u/119656367?v=4&s=48" width="48" height="48" alt="latitudeki5223" title="latitudeki5223"/></a> <a href="https://github.com/search?q=Manuel%20Maly"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Manuel Maly" title="Manuel Maly"/></a> <a href="https://github.com/search?q=Mourad%20Boustani"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Mourad Boustani" title="Mourad Boustani"/></a> <a href="https://github.com/pcty-nextgen-ios-builder"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="pcty-nextgen-ios-builder" title="pcty-nextgen-ios-builder"/></a> <a href="https://github.com/search?q=Quentin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Quentin" title="Quentin"/></a> <a href="https://github.com/search?q=Randy%20Torres"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Randy Torres" title="Randy Torres"/></a> <a href="https://github.com/search?q=Tobias%20Bischoff"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Tobias Bischoff" title="Tobias Bischoff"/></a>
|
||||
<a href="https://github.com/search?q=William%20Stock"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="William Stock" title="William Stock"/></a> <a href="https://github.com/andrewting19"><img src="https://avatars.githubusercontent.com/u/10536704?v=4&s=48" width="48" height="48" alt="andrewting19" title="andrewting19"/></a>
|
||||
</p>
|
||||
|
||||
@@ -21,8 +21,8 @@ android {
|
||||
applicationId = "com.clawdbot.android"
|
||||
minSdk = 31
|
||||
targetSdk = 36
|
||||
versionCode = 20260108
|
||||
versionName = "2026.1.8"
|
||||
versionCode = 20260109
|
||||
versionName = "2026.1.9"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 267 KiB After Width: | Height: | Size: 270 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 46 KiB |
@@ -19,9 +19,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.1.8</string>
|
||||
<string>2026.1.9</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>20260108</string>
|
||||
<string>20260109</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoadsInWebContent</key>
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.1.8</string>
|
||||
<string>2026.1.9</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>20260108</string>
|
||||
<string>20260109</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
@@ -15,9 +15,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.1.8</string>
|
||||
<string>2026.1.9</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>20260108</string>
|
||||
<string>20260109</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>Clawdbot</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -664,19 +664,22 @@ public struct SessionsListParams: Codable, Sendable {
|
||||
public let includeglobal: Bool?
|
||||
public let includeunknown: Bool?
|
||||
public let spawnedby: String?
|
||||
public let agentid: String?
|
||||
|
||||
public init(
|
||||
limit: Int?,
|
||||
activeminutes: Int?,
|
||||
includeglobal: Bool?,
|
||||
includeunknown: Bool?,
|
||||
spawnedby: String?
|
||||
spawnedby: String?,
|
||||
agentid: String?
|
||||
) {
|
||||
self.limit = limit
|
||||
self.activeminutes = activeminutes
|
||||
self.includeglobal = includeglobal
|
||||
self.includeunknown = includeunknown
|
||||
self.spawnedby = spawnedby
|
||||
self.agentid = agentid
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case limit
|
||||
@@ -684,6 +687,7 @@ public struct SessionsListParams: Codable, Sendable {
|
||||
case includeglobal = "includeGlobal"
|
||||
case includeunknown = "includeUnknown"
|
||||
case spawnedby = "spawnedBy"
|
||||
case agentid = "agentId"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,6 +696,7 @@ public struct SessionsPatchParams: Codable, Sendable {
|
||||
public let thinkinglevel: AnyCodable?
|
||||
public let verboselevel: AnyCodable?
|
||||
public let reasoninglevel: AnyCodable?
|
||||
public let responseusage: AnyCodable?
|
||||
public let elevatedlevel: AnyCodable?
|
||||
public let model: AnyCodable?
|
||||
public let spawnedby: AnyCodable?
|
||||
@@ -703,6 +708,7 @@ public struct SessionsPatchParams: Codable, Sendable {
|
||||
thinkinglevel: AnyCodable?,
|
||||
verboselevel: AnyCodable?,
|
||||
reasoninglevel: AnyCodable?,
|
||||
responseusage: AnyCodable?,
|
||||
elevatedlevel: AnyCodable?,
|
||||
model: AnyCodable?,
|
||||
spawnedby: AnyCodable?,
|
||||
@@ -713,6 +719,7 @@ public struct SessionsPatchParams: Codable, Sendable {
|
||||
self.thinkinglevel = thinkinglevel
|
||||
self.verboselevel = verboselevel
|
||||
self.reasoninglevel = reasoninglevel
|
||||
self.responseusage = responseusage
|
||||
self.elevatedlevel = elevatedlevel
|
||||
self.model = model
|
||||
self.spawnedby = spawnedby
|
||||
@@ -724,6 +731,7 @@ public struct SessionsPatchParams: Codable, Sendable {
|
||||
case thinkinglevel = "thinkingLevel"
|
||||
case verboselevel = "verboseLevel"
|
||||
case reasoninglevel = "reasoningLevel"
|
||||
case responseusage = "responseUsage"
|
||||
case elevatedlevel = "elevatedLevel"
|
||||
case model
|
||||
case spawnedby = "spawnedBy"
|
||||
@@ -1100,6 +1108,51 @@ public struct WebLoginWaitParams: Codable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentSummary: Codable, Sendable {
|
||||
public let id: String
|
||||
public let name: String?
|
||||
|
||||
public init(
|
||||
id: String,
|
||||
name: String?
|
||||
) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case name
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsListParams: Codable, Sendable {
|
||||
}
|
||||
|
||||
public struct AgentsListResult: Codable, Sendable {
|
||||
public let defaultid: String
|
||||
public let mainkey: String
|
||||
public let scope: AnyCodable
|
||||
public let agents: [AgentSummary]
|
||||
|
||||
public init(
|
||||
defaultid: String,
|
||||
mainkey: String,
|
||||
scope: AnyCodable,
|
||||
agents: [AgentSummary]
|
||||
) {
|
||||
self.defaultid = defaultid
|
||||
self.mainkey = mainkey
|
||||
self.scope = scope
|
||||
self.agents = agents
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case defaultid = "defaultId"
|
||||
case mainkey = "mainKey"
|
||||
case scope
|
||||
case agents
|
||||
}
|
||||
}
|
||||
|
||||
public struct ModelChoice: Codable, Sendable {
|
||||
public let id: String
|
||||
public let name: String
|
||||
|
||||
@@ -22,7 +22,7 @@ defaults:
|
||||
nav:
|
||||
- title: "Home"
|
||||
url: "/"
|
||||
- title: "Clawd Setup"
|
||||
- title: "Clawdbot Assistant"
|
||||
url: "/start/clawd/"
|
||||
- title: "Gateway"
|
||||
url: "/gateway/"
|
||||
|
||||
41
docs/automation/auth-monitoring.md
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
summary: "Monitor OAuth expiry for model providers"
|
||||
read_when:
|
||||
- Setting up auth expiry monitoring or alerts
|
||||
- Automating Claude Code / Codex OAuth refresh checks
|
||||
---
|
||||
# Auth monitoring
|
||||
|
||||
Clawdbot exposes OAuth expiry health via `clawdbot models status`. Use that for
|
||||
automation and alerting; scripts are optional extras for phone workflows.
|
||||
|
||||
## Preferred: CLI check (portable)
|
||||
|
||||
```bash
|
||||
clawdbot models status --check
|
||||
```
|
||||
|
||||
Exit codes:
|
||||
- `0`: OK
|
||||
- `1`: expired or missing credentials
|
||||
- `2`: expiring soon (within 24h)
|
||||
|
||||
This works in cron/systemd and requires no extra scripts.
|
||||
|
||||
## Optional scripts (ops / phone workflows)
|
||||
|
||||
These live under `scripts/` and are **optional**. They assume SSH access to the
|
||||
gateway host and are tuned for systemd + Termux.
|
||||
|
||||
- `scripts/claude-auth-status.sh` now uses `clawdbot models status --json` as the
|
||||
source of truth (falling back to direct file reads if the CLI is unavailable),
|
||||
so keep `clawdbot` on `PATH` for timers.
|
||||
- `scripts/auth-monitor.sh`: cron/systemd timer target; sends alerts (ntfy or phone).
|
||||
- `scripts/systemd/clawdbot-auth-monitor.{service,timer}`: systemd user timer.
|
||||
- `scripts/claude-auth-status.sh`: Claude Code + Clawdbot auth checker (full/json/simple).
|
||||
- `scripts/mobile-reauth.sh`: guided re‑auth flow over SSH.
|
||||
- `scripts/termux-quick-auth.sh`: one‑tap widget status + open auth URL.
|
||||
- `scripts/termux-auth-widget.sh`: full guided widget flow.
|
||||
- `scripts/termux-sync-widget.sh`: sync Claude Code creds → Clawdbot.
|
||||
|
||||
If you don’t need phone automation or systemd timers, skip these scripts.
|
||||
@@ -6,62 +6,85 @@ read_when:
|
||||
---
|
||||
# Cron jobs (Gateway scheduler)
|
||||
|
||||
Cron runs inside the Gateway and schedules background work so Clawdbot can
|
||||
wake itself up, run isolated agent jobs, and deliver reminders on time.
|
||||
Cron is the Gateway’s built-in scheduler. It persists jobs, wakes the agent at
|
||||
the right time, and can optionally deliver output back to a chat.
|
||||
|
||||
## Update checklist (internal)
|
||||
- [x] Audit cron + heartbeat behavior in code
|
||||
- [x] Rewrite cron doc as user-facing feature
|
||||
- [x] Update heartbeat docs + templates
|
||||
- [x] Update cron links in docs
|
||||
- [x] Update changelog
|
||||
- [x] Run full gate (lint/build/test/docs)
|
||||
If you want *“run this every morning”* or *“poke the agent in 20 minutes”*,
|
||||
cron is the mechanism.
|
||||
|
||||
## What cron is
|
||||
- **Gateway-owned scheduler** that persists jobs under `~/.clawdbot/cron/`.
|
||||
- **Two execution modes**:
|
||||
- **Main session jobs** enqueue `System:` events and rely on the heartbeat runner.
|
||||
- **Isolated jobs** run a dedicated agent turn in `cron:<jobId>` sessions.
|
||||
- **Wakeups** are first-class: a job can trigger the next heartbeat or run it now.
|
||||
## TL;DR
|
||||
- Cron runs **inside the Gateway** (not inside the model).
|
||||
- Jobs persist under `~/.clawdbot/cron/` so restarts don’t lose schedules.
|
||||
- Two execution styles:
|
||||
- **Main session**: enqueue a system event, then run on the next heartbeat.
|
||||
- **Isolated**: run a dedicated agent turn in `cron:<jobId>`, optionally deliver output.
|
||||
- Wakeups are first-class: a job can request “wake now” vs “next heartbeat”.
|
||||
|
||||
## When to use it
|
||||
- Recurring reminders: “every weekday at 7:30” or “every 2h.”
|
||||
- Background chores: summarize inboxes, check dashboards, watch logs.
|
||||
- Automation that should not pollute the main chat history.
|
||||
- Scheduled wakeups that drive the heartbeat pipeline.
|
||||
## Concepts
|
||||
|
||||
## Schedules
|
||||
### Jobs
|
||||
A cron job is a stored record with:
|
||||
- a **schedule** (when it should run),
|
||||
- a **payload** (what it should do),
|
||||
- optional **delivery** (where output should be sent).
|
||||
|
||||
Jobs are identified by a stable `jobId` (used by CLI/Gateway APIs).
|
||||
In agent tool calls, `jobId` is canonical; legacy `id` is accepted for compatibility.
|
||||
|
||||
### Schedules
|
||||
Cron supports three schedule kinds:
|
||||
- `at`: one-shot timestamp in ms.
|
||||
- `at`: one-shot timestamp (ms since epoch).
|
||||
- `every`: fixed interval (ms).
|
||||
- `cron`: 5-field cron expression, optional IANA timezone.
|
||||
- `cron`: 5-field cron expression with optional IANA timezone.
|
||||
|
||||
Cron expressions use `croner` under the hood. If a timezone is omitted, the
|
||||
server’s local timezone is used.
|
||||
Cron expressions use `croner`. If a timezone is omitted, the Gateway host’s
|
||||
local timezone is used.
|
||||
|
||||
## Job types
|
||||
### Main vs isolated execution
|
||||
|
||||
### Main session jobs
|
||||
#### Main session jobs (system events)
|
||||
Main jobs enqueue a system event and optionally wake the heartbeat runner.
|
||||
They **must** use `payload.kind = "systemEvent"`.
|
||||
They must use `payload.kind = "systemEvent"`.
|
||||
|
||||
- **`wakeMode: "next-heartbeat"`** (default): the event waits for the next
|
||||
scheduled heartbeat.
|
||||
- **`wakeMode: "now"`**: the event triggers an immediate heartbeat run.
|
||||
- `wakeMode: "next-heartbeat"` (default): event waits for the next scheduled heartbeat.
|
||||
- `wakeMode: "now"`: event triggers an immediate heartbeat run.
|
||||
|
||||
### Isolated jobs
|
||||
Isolated jobs run a dedicated agent turn in session `cron:<jobId>` and can
|
||||
optionally deliver a message.
|
||||
This is the best fit when you want the normal heartbeat prompt + main-session context.
|
||||
See [Heartbeat](/gateway/heartbeat).
|
||||
|
||||
#### Isolated jobs (dedicated cron sessions)
|
||||
Isolated jobs run a dedicated agent turn in session `cron:<jobId>`.
|
||||
|
||||
Key behaviors:
|
||||
- Prompt is prefixed with `[cron:<jobId> <job name>]` for traceability.
|
||||
- A summary is posted to the main session with prefix `Cron` (or
|
||||
`isolation.postToMainPrefix`).
|
||||
- A summary is posted to the main session (prefix `Cron`, configurable).
|
||||
- `wakeMode: "now"` triggers an immediate heartbeat after posting the summary.
|
||||
- `payload.deliver: true` sends output to a provider; otherwise it stays internal.
|
||||
- If `payload.deliver: true`, output is delivered to a provider; otherwise it stays internal.
|
||||
|
||||
Use isolated jobs for noisy, frequent, or “background chores” that shouldn’t spam
|
||||
your main chat history.
|
||||
|
||||
### Delivery (provider + target)
|
||||
Isolated jobs can deliver output to a provider. The job payload can specify:
|
||||
- `provider`: `whatsapp` / `telegram` / `discord` / `slack` / `signal` / `imessage` / `last`
|
||||
- `to`: provider-specific recipient target
|
||||
|
||||
If `provider` or `to` is omitted, cron can fall back to the main session’s “last route”
|
||||
(the last place the agent replied).
|
||||
|
||||
#### Telegram delivery targets (topics / forum threads)
|
||||
Telegram supports forum topics via `message_thread_id`. For cron delivery, you can encode
|
||||
the topic/thread into the `to` field:
|
||||
|
||||
- `-1001234567890` (chat id only)
|
||||
- `-1001234567890:topic:123` (preferred: explicit topic marker)
|
||||
- `-1001234567890:123` (shorthand: numeric suffix)
|
||||
|
||||
Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted:
|
||||
- `telegram:group:-1001234567890:topic:123`
|
||||
|
||||
## Storage & history
|
||||
- Job store: `~/.clawdbot/cron/jobs.json` (JSON, Gateway-managed).
|
||||
- Job store: `~/.clawdbot/cron/jobs.json` (Gateway-managed JSON).
|
||||
- Run history: `~/.clawdbot/cron/runs/<jobId>.jsonl` (JSONL, auto-pruned).
|
||||
- Override store path: `cron.store` in config.
|
||||
|
||||
@@ -70,16 +93,16 @@ Key behaviors:
|
||||
```json5
|
||||
{
|
||||
cron: {
|
||||
enabled: true, // default true
|
||||
enabled: true, // default true
|
||||
store: "~/.clawdbot/cron/jobs.json",
|
||||
maxConcurrentRuns: 1 // default 1
|
||||
maxConcurrentRuns: 1 // default 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Disable cron entirely:
|
||||
- `cron.enabled: false` (config)
|
||||
- or `CLAWDBOT_SKIP_CRON=1` (env)
|
||||
- `CLAWDBOT_SKIP_CRON=1` (env)
|
||||
|
||||
## CLI quickstart
|
||||
|
||||
@@ -106,6 +129,19 @@ clawdbot cron add \
|
||||
--to "+15551234567"
|
||||
```
|
||||
|
||||
Recurring isolated job (deliver to a Telegram topic):
|
||||
```bash
|
||||
clawdbot cron add \
|
||||
--name "Nightly summary (topic)" \
|
||||
--cron "0 22 * * *" \
|
||||
--tz "America/Los_Angeles" \
|
||||
--session isolated \
|
||||
--message "Summarize today; send to the nightly topic." \
|
||||
--deliver \
|
||||
--provider telegram \
|
||||
--to "-1001234567890:topic:123"
|
||||
```
|
||||
|
||||
Manual run (debug):
|
||||
```bash
|
||||
clawdbot cron run <jobId> --force
|
||||
@@ -121,12 +157,19 @@ Immediate wake without creating a job:
|
||||
clawdbot wake --mode now --text "Next heartbeat: check battery."
|
||||
```
|
||||
|
||||
## API surface (Gateway)
|
||||
## Gateway API surface
|
||||
- `cron.list`, `cron.status`, `cron.add`, `cron.update`, `cron.remove`
|
||||
- `cron.run` (force or due), `cron.runs`
|
||||
- `wake` (enqueue system event + optional heartbeat)
|
||||
|
||||
## Tips
|
||||
- Use **main session jobs** when you want the heartbeat prompt + existing context.
|
||||
- Use **isolated jobs** for noisy, frequent, or long-running work.
|
||||
- Keep messages short; cron turns are full agent runs and can burn tokens.
|
||||
## Troubleshooting
|
||||
|
||||
### “Nothing runs”
|
||||
- Check cron is enabled: `cron.enabled` and `CLAWDBOT_SKIP_CRON`.
|
||||
- Check the Gateway is running continuously (cron runs inside the Gateway process).
|
||||
- For `cron` schedules: confirm timezone (`--tz`) vs the host timezone.
|
||||
|
||||
### Telegram delivers to the wrong place
|
||||
- For forum topics, use `-100…:topic:<id>` so it’s explicit and unambiguous.
|
||||
- If you see `telegram:...` prefixes in logs or stored “last route” targets, that’s normal;
|
||||
cron delivery accepts them and still parses topic IDs correctly.
|
||||
|
||||
@@ -171,9 +171,9 @@ Notes:
|
||||
|
||||
Recommended: `clawdbot hooks gmail run` wraps the same flow and auto-renews the watch.
|
||||
|
||||
## Expose the handler (dev, unsupported hack)
|
||||
## Expose the handler (advanced, unsupported)
|
||||
|
||||
If you insist on a non-Tailscale tunnel, wire it manually and use the public URL in the push
|
||||
If you need a non-Tailscale tunnel, wire it manually and use the public URL in the push
|
||||
subscription (unsupported, no guardrails):
|
||||
|
||||
```bash
|
||||
|
||||
@@ -6,28 +6,36 @@ read_when:
|
||||
---
|
||||
# Polls
|
||||
|
||||
Updated: 2026-01-06
|
||||
|
||||
## Supported providers
|
||||
- WhatsApp (web provider)
|
||||
- Discord
|
||||
- MS Teams (Adaptive Cards)
|
||||
|
||||
## 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
|
||||
clawdbot message poll --to +15555550123 \
|
||||
--poll-question "Lunch today?" --poll-option "Yes" --poll-option "No" --poll-option "Maybe"
|
||||
clawdbot message poll --to 123456789@g.us \
|
||||
--poll-question "Meeting time?" --poll-option "10am" --poll-option "2pm" --poll-option "4pm" --poll-multi
|
||||
|
||||
# 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
|
||||
clawdbot message poll --provider discord --to channel:123456789 \
|
||||
--poll-question "Snack?" --poll-option "Pizza" --poll-option "Sushi"
|
||||
clawdbot message poll --provider discord --to channel:123456789 \
|
||||
--poll-question "Plan?" --poll-option "A" --poll-option "B" --poll-duration-hours 48
|
||||
|
||||
# MS Teams
|
||||
clawdbot message poll --provider msteams --to conversation:19:abc@thread.tacv2 \
|
||||
--poll-question "Lunch?" --poll-option "Pizza" --poll-option "Sushi"
|
||||
```
|
||||
|
||||
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)
|
||||
- `--provider`: `whatsapp` (default), `discord`, or `msteams`
|
||||
- `--poll-multi`: allow selecting multiple options
|
||||
- `--poll-duration-hours`: Discord-only (defaults to 24 when omitted)
|
||||
|
||||
## Gateway RPC
|
||||
|
||||
@@ -45,8 +53,11 @@ Params:
|
||||
## 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.
|
||||
- MS Teams: Adaptive Card polls (Clawdbot-managed). No native poll API; `durationHours` is ignored.
|
||||
|
||||
## 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`.
|
||||
## Agent tool (Message)
|
||||
Use the `message` tool with `poll` action (`to`, `pollQuestion`, `pollOption`, optional `pollMulti`, `pollDurationHours`, `provider`).
|
||||
|
||||
Note: Discord has no “pick exactly N” mode; `maxSelections` is treated as a boolean (`> 1` = multiselect).
|
||||
Note: Discord has no “pick exactly N” mode; `pollMulti` maps to multi-select.
|
||||
Teams polls are rendered as Adaptive Cards and require the gateway to stay online
|
||||
to record votes in `~/.clawdbot/msteams-polls.json`.
|
||||
|
||||
@@ -41,8 +41,8 @@ Payload:
|
||||
{ "text": "System line", "mode": "now" }
|
||||
```
|
||||
|
||||
- `text` required (string)
|
||||
- `mode` optional: `now` | `next-heartbeat` (default `now`)
|
||||
- `text` **required** (string): The description of the event (e.g., "New email received").
|
||||
- `mode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
|
||||
|
||||
Effect:
|
||||
- Enqueues a system event for the **main** session
|
||||
@@ -66,16 +66,16 @@ Payload:
|
||||
}
|
||||
```
|
||||
|
||||
- `message` required (string)
|
||||
- `name` optional (used in the summary prefix)
|
||||
- `sessionKey` optional (default random `hook:<uuid>`)
|
||||
- `wakeMode` optional: `now` | `next-heartbeat` (default `now`)
|
||||
- `deliver` optional (default `false`)
|
||||
- `provider` optional: `last` | `whatsapp` | `telegram`
|
||||
- `to` optional (provider-specific target)
|
||||
- `model` optional (model override, `provider/model` or alias; must be allowed if `agent.models` is set)
|
||||
- `thinking` optional (override)
|
||||
- `timeoutSeconds` optional
|
||||
- `message` **required** (string): The prompt or message for the agent to process.
|
||||
- `name` optional (string): Human-readable name for the hook (e.g., "GitHub"), used as a prefix in session summaries.
|
||||
- `sessionKey` optional (string): The key used to identify the agent's session. Defaults to a random `hook:<uuid>`. Using a consistent key allows for a multi-turn conversation within the hook context.
|
||||
- `wakeMode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
|
||||
- `deliver` optional (boolean): If `true`, the agent's response will be sent to the messaging provider. Defaults to `false`. Responses that are only heartbeat acknowledgments are automatically skipped.
|
||||
- `provider` optional (string): The messaging service for delivery. One of: `last`, `whatsapp`, `telegram`, `discord`, `slack`, `signal`, `imessage`. Defaults to `last`.
|
||||
- `to` optional (string): The recipient identifier for the provider (e.g., phone number for WhatsApp/Signal, chat ID for Telegram, channel ID for Discord/Slack). Defaults to the last recipient in the main session.
|
||||
- `model` optional (string): Model override (e.g., `anthropic/claude-3-5-sonnet` or an alias). Must be in the allowed model list if restricted.
|
||||
- `thinking` optional (string): Thinking level override (e.g., `low`, `medium`, `high`).
|
||||
- `timeoutSeconds` optional (number): Maximum duration for the agent run in seconds.
|
||||
|
||||
Effect:
|
||||
- Runs an **isolated** agent turn (own session key)
|
||||
|
||||
139
docs/cli/gateway.md
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
summary: "Clawdbot Gateway CLI (`clawdbot gateway`) — run, query, and discover gateways"
|
||||
read_when:
|
||||
- Running the Gateway from the CLI (dev or servers)
|
||||
- Debugging Gateway auth, bind modes, and connectivity
|
||||
- Discovering gateways via Bonjour (LAN + tailnet)
|
||||
---
|
||||
|
||||
# Gateway CLI
|
||||
|
||||
The Gateway is Clawdbot’s WebSocket server (providers, nodes, sessions, hooks).
|
||||
|
||||
Subcommands in this page live under `clawdbot gateway …`.
|
||||
|
||||
Related docs:
|
||||
- [/gateway/bonjour](/gateway/bonjour)
|
||||
- [/gateway/discovery](/gateway/discovery)
|
||||
- [/gateway/configuration](/gateway/configuration)
|
||||
|
||||
## Run the Gateway
|
||||
|
||||
Run a local Gateway process:
|
||||
|
||||
```bash
|
||||
clawdbot gateway
|
||||
```
|
||||
|
||||
Notes:
|
||||
- By default, the Gateway refuses to start unless `gateway.mode=local` is set in `~/.clawdbot/clawdbot.json`. Use `--allow-unconfigured` for ad-hoc/dev runs.
|
||||
- Binding beyond loopback without auth is blocked (safety guardrail).
|
||||
- `SIGUSR1` triggers an in-process restart (useful without a supervisor).
|
||||
|
||||
### Options
|
||||
|
||||
- `--port <port>`: WebSocket port (default comes from config/env; usually `18789`).
|
||||
- `--bind <loopback|lan|tailnet|auto>`: listener bind mode.
|
||||
- `--auth <token|password>`: auth mode override.
|
||||
- `--token <token>`: token override (also sets `CLAWDBOT_GATEWAY_TOKEN` for the process).
|
||||
- `--password <password>`: password override (also sets `CLAWDBOT_GATEWAY_PASSWORD` for the process).
|
||||
- `--tailscale <off|serve|funnel>`: expose the Gateway via Tailscale.
|
||||
- `--tailscale-reset-on-exit`: reset Tailscale serve/funnel config on shutdown.
|
||||
- `--dev`: create a dev config + workspace if missing (skips BOOTSTRAP.md).
|
||||
- `--reset`: recreate the dev config (requires `--dev`).
|
||||
- `--force`: kill any existing listener on the selected port before starting.
|
||||
- `--verbose`: verbose logs.
|
||||
- `--claude-cli-logs`: only show claude-cli logs in the console (and enable its stdout/stderr).
|
||||
- `--ws-log <auto|full|compact>`: websocket log style (default `auto`).
|
||||
- `--compact`: alias for `--ws-log compact`.
|
||||
- `--raw-stream`: log raw model stream events to jsonl.
|
||||
- `--raw-stream-path <path>`: raw stream jsonl path.
|
||||
|
||||
## Query a running Gateway
|
||||
|
||||
All query commands use WebSocket RPC.
|
||||
|
||||
Output modes:
|
||||
- Default: human-readable (colored in TTY).
|
||||
- `--json`: machine-readable JSON (no styling/spinner).
|
||||
- `--no-color` (or `NO_COLOR=1`): disable ANSI while keeping human layout.
|
||||
|
||||
Shared options (where supported):
|
||||
- `--url <url>`: Gateway WebSocket URL.
|
||||
- `--token <token>`: Gateway token.
|
||||
- `--password <password>`: Gateway password.
|
||||
- `--timeout <ms>`: timeout/budget (varies per command).
|
||||
- `--expect-final`: wait for a “final” response (agent calls).
|
||||
|
||||
### `gateway health`
|
||||
|
||||
```bash
|
||||
clawdbot gateway health --url ws://127.0.0.1:18789
|
||||
```
|
||||
|
||||
### `gateway status`
|
||||
|
||||
`gateway status` is the “debug everything” command. It always probes:
|
||||
- your configured remote gateway (if set), and
|
||||
- localhost (loopback) **even if remote is configured**.
|
||||
|
||||
If multiple gateways are reachable, it prints all of them and warns this is an unconventional setup (usually you want only one gateway).
|
||||
|
||||
```bash
|
||||
clawdbot gateway status
|
||||
clawdbot gateway status --json
|
||||
```
|
||||
|
||||
#### Remote over SSH (Mac app parity)
|
||||
|
||||
The macOS app “Remote over SSH” mode uses a local port-forward so the remote gateway (which may be bound to loopback only) becomes reachable at `ws://127.0.0.1:<port>`.
|
||||
|
||||
CLI equivalent:
|
||||
|
||||
```bash
|
||||
clawdbot gateway status --ssh steipete@peters-mac-studio-1
|
||||
```
|
||||
|
||||
Options:
|
||||
- `--ssh <target>`: `user@host` or `user@host:port` (port defaults to `22`).
|
||||
- `--ssh-identity <path>`: identity file.
|
||||
- `--ssh-auto`: pick the first discovered bridge host as SSH target (LAN/WAB only).
|
||||
|
||||
Config (optional, used as defaults):
|
||||
- `gateway.remote.sshTarget`
|
||||
- `gateway.remote.sshIdentity`
|
||||
|
||||
### `gateway call <method>`
|
||||
|
||||
Low-level RPC helper.
|
||||
|
||||
```bash
|
||||
clawdbot gateway call status
|
||||
clawdbot gateway call logs.tail --params '{"sinceMs": 60000}'
|
||||
```
|
||||
|
||||
## Discover gateways (Bonjour)
|
||||
|
||||
`gateway discover` scans for Gateway bridge beacons (`_clawdbot-bridge._tcp`).
|
||||
|
||||
- Multicast DNS-SD: `local.`
|
||||
- Unicast DNS-SD (Wide-Area Bonjour): `clawdbot.internal.` (requires split DNS + DNS server; see [/gateway/bonjour](/gateway/bonjour))
|
||||
|
||||
Only gateways with the **bridge enabled** will advertise the discovery beacon.
|
||||
|
||||
### `gateway discover`
|
||||
|
||||
```bash
|
||||
clawdbot gateway discover
|
||||
```
|
||||
|
||||
Options:
|
||||
- `--timeout <ms>`: per-command timeout (browse/resolve); default `2000`.
|
||||
- `--json`: machine-readable output (also disables styling/spinner).
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
clawdbot gateway discover --timeout 4000
|
||||
clawdbot gateway discover --json | jq '.beacons[].wsUrl'
|
||||
```
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
summary: "CLI reference for clawdbot commands, subcommands, and options"
|
||||
summary: "Clawdbot CLI reference for `clawdbot` commands, subcommands, and options"
|
||||
read_when:
|
||||
- Adding or modifying CLI commands or options
|
||||
- Documenting new command surfaces
|
||||
@@ -7,13 +7,13 @@ read_when:
|
||||
|
||||
# CLI reference
|
||||
|
||||
This page mirrors `src/cli/*` and is the source of truth for CLI behavior.
|
||||
If you change the CLI code, update this doc.
|
||||
This page describes the current CLI behavior. If commands change, update this doc.
|
||||
|
||||
## Global flags
|
||||
|
||||
- `--dev`: isolate state under `~/.clawdbot-dev` and shift default ports.
|
||||
- `--profile <name>`: isolate state under `~/.clawdbot-<name>`.
|
||||
- `--no-color`: disable ANSI colors.
|
||||
- `-V`, `--version`, `-v`: print version and exit.
|
||||
|
||||
## Output styling
|
||||
@@ -21,11 +21,12 @@ If you change the CLI code, update this doc.
|
||||
- ANSI colors and progress indicators only render in TTY sessions.
|
||||
- OSC-8 hyperlinks render as clickable links in supported terminals; otherwise we fall back to plain URLs.
|
||||
- `--json` (and `--plain` where supported) disables styling for clean output.
|
||||
- `--no-color` disables ANSI styling; `NO_COLOR=1` is also respected.
|
||||
- Long-running commands show a progress indicator (OSC 9;4 when supported).
|
||||
|
||||
## Color palette
|
||||
|
||||
Clawdbot uses a lobster palette for CLI output. Source of truth: `src/terminal/theme.ts`.
|
||||
Clawdbot uses a lobster palette for CLI output.
|
||||
|
||||
- `accent` (#FF5A2D): headings, provider labels, primary highlights.
|
||||
- `accentBright` (#FF7A3D): command names, emphasis.
|
||||
@@ -36,6 +37,8 @@ Clawdbot uses a lobster palette for CLI output. Source of truth: `src/terminal/t
|
||||
- `error` (#E23D2D): errors, failures.
|
||||
- `muted` (#8B7F77): de-emphasis, metadata.
|
||||
|
||||
Palette source of truth: `src/terminal/palette.ts` (aka “lobster seam”).
|
||||
|
||||
## Command tree
|
||||
|
||||
```
|
||||
@@ -55,8 +58,7 @@ clawdbot [--dev] [--profile <name>] <command>
|
||||
list
|
||||
info
|
||||
check
|
||||
send
|
||||
poll
|
||||
message
|
||||
agent
|
||||
agents
|
||||
list
|
||||
@@ -69,6 +71,7 @@ clawdbot [--dev] [--profile <name>] <command>
|
||||
call
|
||||
health
|
||||
status
|
||||
discover
|
||||
models
|
||||
list
|
||||
status
|
||||
@@ -166,8 +169,10 @@ Options:
|
||||
- `--workspace <dir>`
|
||||
- `--non-interactive`
|
||||
- `--mode <local|remote>`
|
||||
- `--auth-choice <oauth|claude-cli|openai-codex|codex-cli|antigravity|apiKey|minimax-cloud|minimax|skip>`
|
||||
- `--auth-choice <oauth|claude-cli|token|openai-codex|openai-api-key|codex-cli|antigravity|gemini-api-key|apiKey|minimax-cloud|minimax|skip>`
|
||||
- `--anthropic-api-key <key>`
|
||||
- `--openai-api-key <key>`
|
||||
- `--gemini-api-key <key>`
|
||||
- `--minimax-api-key <key>`
|
||||
- `--gateway-port <port>`
|
||||
- `--gateway-bind <loopback|lan|tailnet|auto>`
|
||||
@@ -204,7 +209,8 @@ Manage chat provider accounts (WhatsApp/Telegram/Discord/Slack/Signal/iMessage).
|
||||
|
||||
Subcommands:
|
||||
- `providers list`: show configured chat providers and auth profiles (Claude Code + Codex CLI OAuth sync included).
|
||||
- `providers status`: check gateway reachability and provider health (`--probe` to verify credentials; use `status --deep` for local-only probes).
|
||||
- `providers status`: check gateway reachability and provider health (`--probe` to verify credentials and run small provider audits; use `status --deep` for local-only probes).
|
||||
- Tip: `providers status` prints warnings with suggested fixes when it can detect common misconfigurations (then points you to `clawdbot doctor`).
|
||||
- `providers add`: wizard-style setup when no flags are passed; flags switch to non-interactive mode.
|
||||
- `providers remove`: disable by default; pass `--delete` to remove config entries without prompts.
|
||||
- `providers login`: interactive provider login (WhatsApp Web only).
|
||||
@@ -229,7 +235,9 @@ Common options:
|
||||
- `--json`: output JSON (includes usage unless `--no-usage` is set).
|
||||
|
||||
OAuth sync sources:
|
||||
- `~/.claude/.credentials.json` → `anthropic:claude-cli`
|
||||
- Claude Code → `anthropic:claude-cli`
|
||||
- macOS: Keychain item "Claude Code-credentials" (choose "Always Allow" to avoid launchd prompts)
|
||||
- Linux/Windows: `~/.claude/.credentials.json`
|
||||
- `~/.codex/auth.json` → `openai-codex:codex-cli`
|
||||
|
||||
More detail: [/concepts/oauth](/concepts/oauth)
|
||||
@@ -280,37 +288,25 @@ Options:
|
||||
|
||||
## Messaging + agent
|
||||
|
||||
### `send`
|
||||
Send a message through a provider.
|
||||
### `message`
|
||||
Unified outbound messaging + provider actions.
|
||||
|
||||
Required:
|
||||
- `--to <dest>`
|
||||
- `--message <text>`
|
||||
See: [/cli/message](/cli/message)
|
||||
|
||||
Options:
|
||||
- `--media <path-or-url>`
|
||||
- `--gif-playback`
|
||||
- `--provider <whatsapp|telegram|discord|slack|signal|imessage>`
|
||||
- `--account <id>` (WhatsApp)
|
||||
- `--dry-run`
|
||||
- `--json`
|
||||
- `--verbose`
|
||||
Subcommands:
|
||||
- `message send|poll|react|reactions|read|edit|delete|pin|unpin|pins|permissions|search|timeout|kick|ban`
|
||||
- `message thread <create|list|reply>`
|
||||
- `message emoji <list|upload>`
|
||||
- `message sticker <send|upload>`
|
||||
- `message role <info|add|remove>`
|
||||
- `message channel <info|list>`
|
||||
- `message member info`
|
||||
- `message voice status`
|
||||
- `message event <list|create>`
|
||||
|
||||
### `poll`
|
||||
Create a poll (WhatsApp or Discord).
|
||||
|
||||
Required:
|
||||
- `--to <id>`
|
||||
- `--question <text>`
|
||||
- `--option <choice>` (repeat 2-12 times)
|
||||
|
||||
Options:
|
||||
- `--max-selections <n>`
|
||||
- `--duration-hours <n>` (Discord)
|
||||
- `--provider <whatsapp|discord>`
|
||||
- `--dry-run`
|
||||
- `--json`
|
||||
- `--verbose`
|
||||
Examples:
|
||||
- `clawdbot message send --to +15555550123 --message "Hi"`
|
||||
- `clawdbot message poll --provider discord --to channel:123 --poll-question "Snack?" --poll-option Pizza --poll-option Sushi`
|
||||
|
||||
### `agent`
|
||||
Run one agent turn via the Gateway (or `--local` embedded).
|
||||
@@ -414,6 +410,8 @@ Options:
|
||||
- `--tailscale <off|serve|funnel>`
|
||||
- `--tailscale-reset-on-exit`
|
||||
- `--allow-unconfigured`
|
||||
- `--dev`
|
||||
- `--reset`
|
||||
- `--force` (kill existing listener on port)
|
||||
- `--verbose`
|
||||
- `--ws-log <auto|full|compact>`
|
||||
@@ -441,10 +439,17 @@ Notes:
|
||||
### `logs`
|
||||
Tail Gateway file logs via RPC.
|
||||
|
||||
Notes:
|
||||
- TTY sessions render a colorized, structured view; non-TTY falls back to plain text.
|
||||
- `--json` emits line-delimited JSON (one log event per line).
|
||||
|
||||
Examples:
|
||||
```bash
|
||||
clawdbot logs --follow
|
||||
clawdbot logs --limit 200
|
||||
clawdbot logs --plain
|
||||
clawdbot logs --json
|
||||
clawdbot logs --no-color
|
||||
```
|
||||
|
||||
### `gateway <subcommand>`
|
||||
@@ -478,6 +483,9 @@ Options:
|
||||
Options:
|
||||
- `--json`
|
||||
- `--plain`
|
||||
- `--check` (exit 1=expired/missing, 2=expiring)
|
||||
|
||||
Always includes the auth overview and OAuth expiry status for profiles in the auth store.
|
||||
|
||||
### `models set <model>`
|
||||
Set `agent.model.primary`.
|
||||
|
||||
176
docs/cli/message.md
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
summary: "CLI reference for `clawdbot message` (send + provider actions)"
|
||||
read_when:
|
||||
- Adding or modifying message CLI actions
|
||||
- Changing outbound provider behavior
|
||||
---
|
||||
|
||||
# `clawdbot message`
|
||||
|
||||
Single outbound command for sending messages and provider actions
|
||||
(Discord/Slack/Telegram/WhatsApp/Signal/iMessage/MS Teams).
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
clawdbot message <subcommand> [flags]
|
||||
```
|
||||
|
||||
Provider selection:
|
||||
- `--provider` required if more than one provider is configured.
|
||||
- If exactly one provider is configured, it becomes the default.
|
||||
- Values: `whatsapp|telegram|discord|slack|signal|imessage|msteams`
|
||||
|
||||
Target formats (`--to`):
|
||||
- WhatsApp: E.164 or group JID
|
||||
- Telegram: chat id or `@username`
|
||||
- Discord/Slack: `channel:<id>` or `user:<id>` (raw id ok)
|
||||
- Signal: E.164, `group:<id>`, or `signal:+E.164`
|
||||
- iMessage: handle or `chat_id:<id>`
|
||||
- MS Teams: conversation id (`19:...@thread.tacv2`) or `conversation:<id>` or `user:<aad-object-id>`
|
||||
|
||||
## Common flags
|
||||
|
||||
- `--provider <name>`
|
||||
- `--account <id>`
|
||||
- `--json`
|
||||
- `--dry-run`
|
||||
- `--verbose`
|
||||
|
||||
## Actions
|
||||
|
||||
### Core
|
||||
|
||||
- `send`
|
||||
- Required: `--to`, `--message`
|
||||
- Optional: `--media`, `--reply-to`, `--thread-id`, `--gif-playback`
|
||||
|
||||
- `poll`
|
||||
- Required: `--to`, `--poll-question`, `--poll-option` (repeat)
|
||||
- Optional: `--poll-multi`, `--poll-duration-hours`, `--message`
|
||||
|
||||
- `react`
|
||||
- Required: `--to`, `--message-id`
|
||||
- Optional: `--emoji`, `--remove`, `--participant`, `--from-me`, `--channel-id`
|
||||
|
||||
- `reactions`
|
||||
- Required: `--to`, `--message-id`
|
||||
- Optional: `--limit`, `--channel-id`
|
||||
|
||||
- `read`
|
||||
- Required: `--to`
|
||||
- Optional: `--limit`, `--before`, `--after`, `--around`, `--channel-id`
|
||||
|
||||
- `edit`
|
||||
- Required: `--to`, `--message-id`, `--message`
|
||||
- Optional: `--channel-id`
|
||||
|
||||
- `delete`
|
||||
- Required: `--to`, `--message-id`
|
||||
- Optional: `--channel-id`
|
||||
|
||||
- `pin` / `unpin`
|
||||
- Required: `--to`, `--message-id`
|
||||
- Optional: `--channel-id`
|
||||
|
||||
- `pins` (list)
|
||||
- Required: `--to`
|
||||
- Optional: `--channel-id`
|
||||
|
||||
- `permissions`
|
||||
- Required: `--to`
|
||||
- Optional: `--channel-id`
|
||||
|
||||
- `search`
|
||||
- Required: `--guild-id`, `--query`
|
||||
- Optional: `--channel-id`, `--channel-ids` (repeat), `--author-id`, `--author-ids` (repeat), `--limit`
|
||||
|
||||
### Threads
|
||||
|
||||
- `thread create`
|
||||
- Required: `--thread-name`, `--to` (channel id) or `--channel-id`
|
||||
- Optional: `--message-id`, `--auto-archive-min`
|
||||
|
||||
- `thread list`
|
||||
- Required: `--guild-id`
|
||||
- Optional: `--channel-id`, `--include-archived`, `--before`, `--limit`
|
||||
|
||||
- `thread reply`
|
||||
- Required: `--to` (thread id), `--message`
|
||||
- Optional: `--media`, `--reply-to`
|
||||
|
||||
### Emojis
|
||||
|
||||
- `emoji list`
|
||||
- Discord: `--guild-id`
|
||||
|
||||
- `emoji upload`
|
||||
- Required: `--guild-id`, `--emoji-name`, `--media`
|
||||
- Optional: `--role-ids` (repeat)
|
||||
|
||||
### Stickers
|
||||
|
||||
- `sticker send`
|
||||
- Required: `--to`, `--sticker-id` (repeat)
|
||||
- Optional: `--message`
|
||||
|
||||
- `sticker upload`
|
||||
- Required: `--guild-id`, `--sticker-name`, `--sticker-desc`, `--sticker-tags`, `--media`
|
||||
|
||||
### Roles / Channels / Members / Voice
|
||||
|
||||
- `role info` (Discord): `--guild-id`
|
||||
- `role add` / `role remove` (Discord): `--guild-id`, `--user-id`, `--role-id`
|
||||
- `channel info` (Discord): `--channel-id`
|
||||
- `channel list` (Discord): `--guild-id`
|
||||
- `member info` (Discord/Slack): `--user-id` (+ `--guild-id` for Discord)
|
||||
- `voice status` (Discord): `--guild-id`, `--user-id`
|
||||
|
||||
### Events
|
||||
|
||||
- `event list` (Discord): `--guild-id`
|
||||
- `event create` (Discord): `--guild-id`, `--event-name`, `--start-time`
|
||||
- Optional: `--end-time`, `--desc`, `--channel-id`, `--location`, `--event-type`
|
||||
|
||||
### Moderation (Discord)
|
||||
|
||||
- `timeout`: `--guild-id`, `--user-id` (+ `--duration-min` or `--until`)
|
||||
- `kick`: `--guild-id`, `--user-id`
|
||||
- `ban`: `--guild-id`, `--user-id` (+ `--delete-days`)
|
||||
|
||||
## Examples
|
||||
|
||||
Send a Discord reply:
|
||||
```
|
||||
clawdbot message send --provider discord \
|
||||
--to channel:123 --message "hi" --reply-to 456
|
||||
```
|
||||
|
||||
Create a Discord poll:
|
||||
```
|
||||
clawdbot message poll --provider discord \
|
||||
--to channel:123 \
|
||||
--poll-question "Snack?" \
|
||||
--poll-option Pizza --poll-option Sushi \
|
||||
--poll-multi --poll-duration-hours 48
|
||||
```
|
||||
|
||||
Send a Teams proactive message:
|
||||
```
|
||||
clawdbot message send --provider msteams \
|
||||
--to conversation:19:abc@thread.tacv2 --message "hi"
|
||||
```
|
||||
|
||||
Create a Teams poll:
|
||||
```
|
||||
clawdbot message poll --provider msteams \
|
||||
--to conversation:19:abc@thread.tacv2 \
|
||||
--poll-question "Lunch?" \
|
||||
--poll-option Pizza --poll-option Sushi
|
||||
```
|
||||
|
||||
React in Slack:
|
||||
```
|
||||
clawdbot message react --provider slack \
|
||||
--to C123 --message-id 456 --emoji "✅"
|
||||
```
|
||||
118
docs/cli/sandbox.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Sandbox CLI
|
||||
|
||||
Manage Docker-based sandbox containers for isolated agent execution.
|
||||
|
||||
## Overview
|
||||
|
||||
ClawdBot can run agents in isolated Docker containers for security. The `sandbox` commands help you manage these containers, especially after updates or configuration changes.
|
||||
|
||||
## Commands
|
||||
|
||||
### `clawdbot sandbox list`
|
||||
|
||||
List all sandbox containers with their status and configuration.
|
||||
|
||||
```bash
|
||||
clawdbot sandbox list
|
||||
clawdbot sandbox list --browser # List only browser containers
|
||||
clawdbot sandbox list --json # JSON output
|
||||
```
|
||||
|
||||
**Output includes:**
|
||||
- Container name and status (running/stopped)
|
||||
- Docker image and whether it matches config
|
||||
- Age (time since creation)
|
||||
- Idle time (time since last use)
|
||||
- Associated session/agent
|
||||
|
||||
### `clawdbot sandbox recreate`
|
||||
|
||||
Remove sandbox containers to force recreation with updated images/config.
|
||||
|
||||
```bash
|
||||
clawdbot sandbox recreate --all # Recreate all containers
|
||||
clawdbot sandbox recreate --session main # Specific session
|
||||
clawdbot sandbox recreate --agent mybot # Specific agent
|
||||
clawdbot sandbox recreate --browser # Only browser containers
|
||||
clawdbot sandbox recreate --all --force # Skip confirmation
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--all`: Recreate all sandbox containers
|
||||
- `--session <key>`: Recreate container for specific session
|
||||
- `--agent <id>`: Recreate containers for specific agent
|
||||
- `--browser`: Only recreate browser containers
|
||||
- `--force`: Skip confirmation prompt
|
||||
|
||||
**Important:** Containers are automatically recreated when the agent is next used.
|
||||
|
||||
## Use Cases
|
||||
|
||||
### After updating Docker images
|
||||
|
||||
```bash
|
||||
# Pull new image
|
||||
docker pull clawdbot-sandbox:latest
|
||||
docker tag clawdbot-sandbox:latest clawdbot-sandbox:bookworm-slim
|
||||
|
||||
# Update config to use new image
|
||||
# Edit clawdbot.config.json: agent.sandbox.docker.image
|
||||
|
||||
# Recreate containers
|
||||
clawdbot sandbox recreate --all
|
||||
```
|
||||
|
||||
### After changing sandbox configuration
|
||||
|
||||
```bash
|
||||
# Edit clawdbot.config.json: agent.sandbox.*
|
||||
|
||||
# Recreate to apply new config
|
||||
clawdbot sandbox recreate --all
|
||||
```
|
||||
|
||||
### For a specific agent only
|
||||
|
||||
```bash
|
||||
# Update only one agent's containers
|
||||
clawdbot sandbox recreate --agent alfred
|
||||
```
|
||||
|
||||
## Why is this needed?
|
||||
|
||||
**Problem:** When you update sandbox Docker images or configuration:
|
||||
- Existing containers continue running with old settings
|
||||
- Containers are only pruned after 24h of inactivity
|
||||
- Regularly-used agents keep old containers running indefinitely
|
||||
|
||||
**Solution:** Use `clawdbot sandbox recreate` to force removal of old containers. They'll be recreated automatically with current settings when next needed.
|
||||
|
||||
## Configuration
|
||||
|
||||
Sandbox settings are in `clawdbot.config.json`:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"agent": {
|
||||
"sandbox": {
|
||||
"mode": "all", // off, non-main, all
|
||||
"scope": "agent", // session, agent, shared
|
||||
"docker": {
|
||||
"image": "clawdbot-sandbox:bookworm-slim",
|
||||
"containerPrefix": "clawdbot-sbx-"
|
||||
// ... more Docker options
|
||||
},
|
||||
"prune": {
|
||||
"idleHours": 24, // Auto-prune after 24h idle
|
||||
"maxAgeDays": 7 // Auto-prune after 7 days
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [Sandbox Documentation](../gateway/sandboxing.md)
|
||||
- [Agent Configuration](../concepts/agent-workspace.md)
|
||||
- [Doctor Command](./doctor.md) - Check sandbox setup
|
||||
@@ -3,13 +3,13 @@ summary: "Agent loop lifecycle, streams, and wait semantics"
|
||||
read_when:
|
||||
- You need an exact walkthrough of the agent loop or lifecycle events
|
||||
---
|
||||
# Agent Loop (Clawdis)
|
||||
# Agent Loop (Clawdbot)
|
||||
|
||||
Short, exact flow of one agent run. Source of truth: current code in `src/`.
|
||||
Short, exact flow of one agent run.
|
||||
|
||||
## 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`.
|
||||
- CLI: `agent` command.
|
||||
|
||||
## High-level flow
|
||||
1) `agent` RPC validates params, resolves session (sessionKey/sessionId), persists session metadata, returns `{ runId, acceptedAt }` immediately.
|
||||
@@ -23,7 +23,7 @@ Short, exact flow of one agent run. Source of truth: current code in `src/`.
|
||||
- streams assistant deltas + tool events
|
||||
- enforces timeout -> aborts run if exceeded
|
||||
- returns payloads + usage metadata
|
||||
4) `subscribeEmbeddedPiSession` bridges pi-agent-core events to Clawdis `agent` stream:
|
||||
4) `subscribeEmbeddedPiSession` bridges pi-agent-core events to Clawdbot `agent` stream:
|
||||
- tool events => `stream: "tool"`
|
||||
- assistant deltas => `stream: "assistant"`
|
||||
- lifecycle events => `stream: "lifecycle"` (`phase: "start" | "end" | "error"`)
|
||||
@@ -37,10 +37,8 @@ Short, exact flow of one agent run. Source of truth: current code in `src/`.
|
||||
- `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):
|
||||
- buffers assistant deltas
|
||||
- emits chat `delta` messages
|
||||
- emits chat `final` when **lifecycle end/error** arrives
|
||||
- Assistant deltas are buffered into chat `delta` messages.
|
||||
- A chat `final` is emitted on **lifecycle end/error**.
|
||||
|
||||
## Timeouts
|
||||
- `agent.wait` default: 30s (just the wait). `timeoutMs` param overrides.
|
||||
@@ -51,11 +49,3 @@ Short, exact flow of one agent run. Source of truth: current code in `src/`.
|
||||
- AbortSignal (cancel)
|
||||
- Gateway disconnect or RPC timeout
|
||||
- `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)
|
||||
|
||||
@@ -12,6 +12,13 @@ file tools and for workspace context. Keep it private and treat it as memory.
|
||||
This is separate from `~/.clawdbot/`, which stores config, credentials, and
|
||||
sessions.
|
||||
|
||||
**Important:** the workspace is the **default cwd**, not a hard sandbox. Tools
|
||||
resolve relative paths against the workspace, but absolute paths can still reach
|
||||
elsewhere on the host unless sandboxing is enabled. If you need isolation, use
|
||||
[`agent.sandbox`](/gateway/sandboxing) (and/or per‑agent sandbox config).
|
||||
When sandboxing is enabled and `workspaceAccess` is not `"rw"`, tools operate
|
||||
inside a sandbox workspace under `~/.clawdbot/sandboxes`, not your host workspace.
|
||||
|
||||
## Default location
|
||||
|
||||
- Default: `~/clawd`
|
||||
|
||||
@@ -5,7 +5,7 @@ read_when:
|
||||
---
|
||||
# Agent Runtime 🤖
|
||||
|
||||
CLAWDBOT runs a single embedded agent runtime derived from **p-mono** (internal name: **p**).
|
||||
CLAWDBOT runs a single embedded agent runtime derived from **p-mono**.
|
||||
|
||||
## Workspace (required)
|
||||
|
||||
@@ -43,9 +43,9 @@ To disable bootstrap file creation entirely (for pre-seeded workspaces), set:
|
||||
{ agent: { skipBootstrap: true } }
|
||||
```
|
||||
|
||||
## Built-in tools (internal)
|
||||
## Built-in tools
|
||||
|
||||
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.
|
||||
Core tools (read/bash/edit/write and related system tools) are always available. `TOOLS.md` does **not** control which tools exist; it’s guidance for how *you* want them used.
|
||||
|
||||
## Skills
|
||||
|
||||
@@ -63,18 +63,6 @@ Clawdbot reuses pieces of the p-mono codebase (models/tools), but **session mana
|
||||
- No p-coding agent runtime.
|
||||
- No `~/.pi/agent` or `<workspace>/.pi` settings are consulted.
|
||||
|
||||
## Peter @ steipete (only)
|
||||
|
||||
Apply these notes **only** when the user is Peter Steinberger at steipete.
|
||||
|
||||
- Gateway runs on the **Mac Studio in London**.
|
||||
- Primary work computer: **MacBook Pro**.
|
||||
- Peter travels between **Vienna** and **London**; there are two networks bridged via **Tailscale**.
|
||||
- For debugging, connect to the Mac Studio (London) or MacBook Pro (primary).
|
||||
- There is also an **M1 MacBook Pro** on the Vienna tailnet you can use to access the Vienna network.
|
||||
- Nodes can be accessed via the `clawdbot` binary (`pnpm clawdbot` in `~/Projects/clawdbot`).
|
||||
- See also `skills/clawdbot*` for node/browser/canvas/cron usage.
|
||||
|
||||
## Sessions
|
||||
|
||||
Session transcripts are stored as JSONL at:
|
||||
|
||||
@@ -3,67 +3,55 @@ summary: "WebSocket gateway architecture, components, and client flows"
|
||||
read_when:
|
||||
- Working on gateway protocol, clients, or transports
|
||||
---
|
||||
# Gateway Architecture
|
||||
# Gateway architecture
|
||||
|
||||
Last updated: 2026-01-05
|
||||
|
||||
## 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).
|
||||
- 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.
|
||||
- A single long‑lived **Gateway** owns all messaging surfaces (WhatsApp via
|
||||
Baileys, Telegram via grammY, Slack, Discord, Signal, iMessage, WebChat).
|
||||
- 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`).
|
||||
- One Gateway per host; it is the only place that opens a WhatsApp session.
|
||||
- A **bridge** (default `18790`) is used for nodes (macOS/iOS/Android).
|
||||
- A **canvas host** (default `18793`) serves agent‑editable HTML and A2UI.
|
||||
|
||||
## Components and flows
|
||||
- **Gateway (daemon)**
|
||||
- Maintains WhatsApp (Baileys), Telegram (grammY), Slack (Bolt), Discord (discord.js), Signal (signal-cli), and iMessage (imsg) 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`](/platforms/macos)).
|
||||
- **Agent process (Pi)**
|
||||
- Spawned by the Gateway on demand for `agent` calls; streams events back over the same WS connection.
|
||||
- **WebChat**
|
||||
- Serves static assets locally.
|
||||
- Holds a single WS connection to the Gateway for control/data; all sends/agent runs go through the Gateway WS.
|
||||
- Remote use goes through the same SSH/Tailscale tunnel as other clients.
|
||||
|
||||
### Gateway (daemon)
|
||||
- Maintains provider connections.
|
||||
- Exposes a typed WS API (requests, responses, server‑push events).
|
||||
- Validates inbound frames against JSON Schema.
|
||||
- Emits events like `agent`, `chat`, `presence`, `health`, `heartbeat`, `cron`.
|
||||
|
||||
### Clients (mac app / CLI / web admin)
|
||||
- One WS connection per client.
|
||||
- Send requests (`health`, `status`, `send`, `agent`, `system-presence`).
|
||||
- Subscribe to events (`tick`, `agent`, `presence`, `shutdown`).
|
||||
|
||||
### Nodes (macOS / iOS / Android)
|
||||
- Connect to the **bridge** (TCP JSONL) rather than the WS server.
|
||||
- Pair with the Gateway to receive a token.
|
||||
- Expose commands like `canvas.*`, `camera.*`, `screen.record`, `location.get`.
|
||||
|
||||
### WebChat
|
||||
- Static UI that uses the Gateway WS API for chat history and sends.
|
||||
- In remote setups, connects through the same SSH/Tailscale tunnel as other
|
||||
clients.
|
||||
|
||||
## Connection lifecycle (single client)
|
||||
|
||||
```
|
||||
Client Gateway
|
||||
| |
|
||||
|---- req:connect -------->|
|
||||
|<------ res (ok) ---------| (or res error + close)
|
||||
| (payload=hello-ok carries snapshot: presence + health)
|
||||
| (payload=hello-ok carries snapshot: presence + health)
|
||||
| |
|
||||
|<------ event:presence ---| (deltas)
|
||||
|<------ event:tick -------| (keepalive/no-op)
|
||||
|<------ event:presence ---|
|
||||
|<------ event:tick -------|
|
||||
| |
|
||||
|------- req:agent ------->|
|
||||
|<------ res:agent --------| (ack: {runId,status:"accepted"})
|
||||
@@ -71,44 +59,42 @@ Client Gateway
|
||||
|<------ res:agent --------| (final: {runId,status,summary})
|
||||
| |
|
||||
```
|
||||
|
||||
## Wire protocol (summary)
|
||||
|
||||
- Transport: WebSocket, text frames with JSON payloads.
|
||||
- First frame must be `req {type:"req", id, method:"connect", params:{minProtocol, maxProtocol, client:{name,version,platform,mode,instanceId}, caps, auth?, locale?, userAgent? } }`.
|
||||
- Server replies `res {type:"res", id, ok:true, payload: hello-ok }` or `ok:false` then closes.
|
||||
- After handshake:
|
||||
- Requests: `{type:"req", id, method, params}` → `{type:"res", id, ok, payload|error}`
|
||||
- Events: `{type:"event", event:"agent"|"presence"|"tick"|"shutdown", payload, seq?, stateVersion?}`
|
||||
- If `CLAWDBOT_GATEWAY_TOKEN` (or `--token`) is set, `connect.params.auth.token` must match; otherwise the socket closes with policy violation.
|
||||
- Presence payload is structured, not free text: `{host, ip, version, mode, lastInputSeconds?, ts, reason?, tags?[], instanceId? }`.
|
||||
- Agent runs are acked `{runId,status:"accepted"}` then complete with a final res `{runId,status,summary}`; streamed output arrives as `event:"agent"`.
|
||||
- Protocol versions are bumped on breaking changes; clients must match `minClient`; Gateway chooses within client’s min/max.
|
||||
- Idempotency keys are required for side-effecting methods (`send`, `agent`) to safely retry; server keeps a short-lived dedupe cache.
|
||||
- Policy in `hello-ok` communicates payload/queue limits and tick interval.
|
||||
- First frame **must** be `connect`.
|
||||
- After handshake:
|
||||
- Requests: `{type:"req", id, method, params}` → `{type:"res", id, ok, payload|error}`
|
||||
- Events: `{type:"event", event, payload, seq?, stateVersion?}`
|
||||
- If `CLAWDBOT_GATEWAY_TOKEN` (or `--token`) is set, `connect.params.auth.token`
|
||||
must match or the socket closes.
|
||||
- Idempotency keys are required for side‑effecting methods (`send`, `agent`) to
|
||||
safely retry; the server keeps a short‑lived dedupe cache.
|
||||
|
||||
## Type system and codegen
|
||||
- Source of truth: TypeBox (or ArkType) definitions in `protocol/` on the server.
|
||||
- Build step emits JSON Schema.
|
||||
- Clients:
|
||||
- TypeScript: uses the same TypeBox types directly.
|
||||
- Swift: generated `Codable` models via quicktype from the JSON Schema.
|
||||
- Validation: AJV on the server for every inbound frame; optional client-side validation for defensive programming.
|
||||
## Protocol typing and codegen
|
||||
|
||||
## Invariants
|
||||
- Exactly one Gateway controls a single Baileys session per host. No fallbacks to ad-hoc direct Baileys sends.
|
||||
- Handshake is mandatory; any non-JSON or non-connect first frame is a hard close.
|
||||
- All methods and events are versioned; new fields are additive; breaking changes increment `protocol`.
|
||||
- No event replay: on seq gaps, clients must refresh (`health` + `system-presence`) and continue; presence is bounded via TTL/max entries.
|
||||
- TypeBox schemas define the protocol.
|
||||
- JSON Schema is generated from those schemas.
|
||||
- Swift models are generated from the JSON Schema.
|
||||
|
||||
## Remote access
|
||||
- Preferred: Tailscale or VPN; alternate: SSH tunnel `ssh -N -L 18789:127.0.0.1:18789 user@host`.
|
||||
- Same protocol over the tunnel; same handshake. If a shared token is configured, clients must send it in `connect.params.auth.token` even over the tunnel.
|
||||
- Same protocol over the tunnel; same handshake. If a shared token is configured, clients must send it in `connect.params.auth.token` even over the tunnel.
|
||||
|
||||
- Preferred: Tailscale or VPN.
|
||||
- Alternative: SSH tunnel
|
||||
```bash
|
||||
ssh -N -L 18789:127.0.0.1:18789 user@host
|
||||
```
|
||||
- The same handshake + auth token apply over the tunnel.
|
||||
|
||||
## Operations snapshot
|
||||
- Start: `clawdbot gateway` (foreground, logs to stdout).
|
||||
Supervise with launchd/systemd for restarts.
|
||||
- Health: request `health` over WS; also surfaced in `hello-ok.health`.
|
||||
- Metrics/logging: keep outside this spec; gateway should expose Prometheus text or structured logs separately.
|
||||
|
||||
## Migration notes
|
||||
- This architecture supersedes the legacy stdin RPC and the ad-hoc TCP control port. New clients should speak only the WS protocol. Legacy compatibility is intentionally dropped.
|
||||
- Start: `clawdbot gateway` (foreground, logs to stdout).
|
||||
- Health: `health` over WS (also included in `hello-ok`).
|
||||
- Supervision: launchd/systemd for auto‑restart.
|
||||
|
||||
## Invariants
|
||||
|
||||
- Exactly one Gateway controls a single Baileys session per host.
|
||||
- Handshake is mandatory; any non‑JSON or non‑connect first frame is a hard close.
|
||||
- Events are not replayed; clients must refresh on gaps.
|
||||
|
||||
@@ -7,7 +7,7 @@ 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.
|
||||
Note: `routing.groupChat.mentionPatterns` is now used by Telegram/Discord/Slack/iMessage as well; this doc focuses on WhatsApp-specific behavior. For multi-agent setups, you can override per agent with `routing.agents.<agentId>.mentionPatterns`.
|
||||
|
||||
## 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).
|
||||
@@ -61,7 +61,6 @@ Only the owner number (from `whatsapp.allowFrom`, or the bot’s own E.164 when
|
||||
4) Session-level directives (`/verbose on`, `/think high`, `/new` or `/reset`, `/compact`) apply only to that group’s session; send them as standalone messages so they register. Your personal DM session remains independent.
|
||||
|
||||
## Testing / verification
|
||||
- Automated: `pnpm test -- src/web/auto-reply.test.ts --runInBand` (covers mention gating, history injection, sender suffix).
|
||||
- Manual smoke:
|
||||
- Send an `@clawd` ping in the group and confirm a reply that references the sender name.
|
||||
- Send a second ping and verify the history block is included then cleared on the next turn.
|
||||
|
||||
@@ -100,6 +100,7 @@ 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.
|
||||
- Per-agent override: `routing.agents.<agentId>.mentionPatterns` (useful when multiple agents share a group).
|
||||
- Mention gating is only enforced when mention detection is possible (native mentions or `mentionPatterns` are configured).
|
||||
- Discord defaults live in `discord.guilds."*"` (overridable per guild/channel).
|
||||
|
||||
|
||||
@@ -1,157 +1,151 @@
|
||||
---
|
||||
summary: "Plan for models CLI: scan, list, aliases, fallbacks, status"
|
||||
summary: "Models CLI: list, set, aliases, fallbacks, scan, status"
|
||||
read_when:
|
||||
- Adding or modifying models CLI (models list/set/scan/aliases/fallbacks)
|
||||
- Changing model fallback behavior or selection UX
|
||||
- Updating model scan probes (tools/images)
|
||||
---
|
||||
# Models CLI plan
|
||||
# Models CLI
|
||||
|
||||
See [`docs/model-failover.md`](/concepts/model-failover) for how auth profiles rotate (OAuth vs API keys), cooldowns, and how that interacts with model fallbacks.
|
||||
See [/concepts/model-failover](/concepts/model-failover) for auth profile
|
||||
rotation, cooldowns, and how that interacts with fallbacks.
|
||||
|
||||
Goal: give clear model visibility + control (configured vs available), plus scan tooling
|
||||
that prefers tool-call + image-capable models and maintains ordered fallbacks.
|
||||
|
||||
## How Clawdbot models work (quick explainer)
|
||||
## How model selection works
|
||||
|
||||
Clawdbot selects models in this order:
|
||||
1) The configured **primary** model (`agent.model.primary`).
|
||||
2) If it fails, fallbacks in `agent.model.fallbacks` (in order).
|
||||
3) Auth failover happens **inside** the provider first (see [/concepts/model-failover](/concepts/model-failover)).
|
||||
|
||||
Key pieces:
|
||||
- `provider/model` is the canonical model id (e.g. `anthropic/claude-opus-4-5`).
|
||||
- `agent.models` is the **allowlist/catalog** of models Clawdbot can use, with optional aliases and provider params.
|
||||
- `agent.imageModel` is only used when the primary model **can’t** accept images.
|
||||
- `models.providers` lets you add custom providers + models (written to `models.json`).
|
||||
- `/model <id>` switches the active model for the current session; `/model list` shows what’s allowed.
|
||||
1) **Primary** model (`agent.model.primary` or `agent.model`).
|
||||
2) **Fallbacks** in `agent.model.fallbacks` (in order).
|
||||
3) **Provider auth failover** happens inside a provider before moving to the
|
||||
next model.
|
||||
|
||||
Related:
|
||||
- Context limits are model-specific; long sessions may trigger compaction. See [/concepts/compaction](/concepts/compaction).
|
||||
- `agent.models` is the allowlist/catalog of models Clawdbot can use (plus aliases).
|
||||
- `agent.imageModel` is used **only when** the primary model can’t accept images.
|
||||
|
||||
## Model recommendations
|
||||
## Config keys (overview)
|
||||
|
||||
- [Claude Opus 4.5](https://www.anthropic.com/claude/opus): default primary for assistant + general work. It’s pricey and cap-prone, so consider the [Claude Max $200 subscription](https://www.anthropic.com/pricing/) if you live here.
|
||||
- [Claude Sonnet 4.5](https://www.anthropic.com/claude/sonnet): default fallback when Opus caps out. Similar behavior with fewer limit headaches.
|
||||
- [GPT-5.2-Codex](https://developers.openai.com/codex/models): recommended for coding and sub-agents. Prefer the [Codex CLI](https://developers.openai.com/codex/cli) if you want the strongest feel.
|
||||
- `agent.model.primary` and `agent.model.fallbacks`
|
||||
- `agent.imageModel.primary` and `agent.imageModel.fallbacks`
|
||||
- `agent.models` (allowlist + aliases + provider params)
|
||||
- `models.providers` (custom providers written into `models.json`)
|
||||
|
||||
Suggested stacks:
|
||||
- Assistant-first: Opus 4.5 primary → Sonnet 4.5 fallback.
|
||||
- Agentic coding: Opus 4.5 primary → GPT-5.2-Codex for sub-agents → Sonnet 4.5 fallback.
|
||||
Model refs are normalized to lowercase. Provider aliases like `z.ai/*` normalize
|
||||
to `zai/*`.
|
||||
|
||||
## Model discussions (community notes)
|
||||
## “Model is not allowed” (and why replies stop)
|
||||
|
||||
Anecdotal notes from the Discord thread on January 4–5, 2026. Treat as “reported by users,” not a benchmark.
|
||||
If `agent.models` is set, it becomes the **allowlist** for `/model` and for
|
||||
session overrides. When a user selects a model that isn’t in that allowlist,
|
||||
Clawdbot returns:
|
||||
|
||||
**Reported working well**
|
||||
- [Claude Opus 4.5](https://www.anthropic.com/claude/opus): best overall quality in Clawdbot, especially for “assistant” work. Tradeoff is cost and hitting usage limits quickly.
|
||||
- [Claude Sonnet 4.5](https://www.anthropic.com/claude/sonnet): common fallback when Opus caps out. Similar behavior with fewer limit headaches.
|
||||
- [Gemini 3 Pro](https://deepmind.google/en/models/gemini/pro/): some users felt it maps well to Clawdbot’s structure. Vibe was “fits the framework” more than “best at everything.”
|
||||
- [GLM](https://www.zhipuai.cn/en/): used successfully as a worker model under orchestration. Seen as strong for delegated/secondary tasks, not the primary brain.
|
||||
- [MiniMax M2.1](https://platform.minimax.io/docs/guides/models-intro): “good enough” for grunt work or a cheap fallback. Community nickname was “Temu-Sonnet,” i.e. usable but not Sonnet-level polish.
|
||||
```
|
||||
Model "provider/model" is not allowed. Use /model to list available models.
|
||||
```
|
||||
|
||||
**Mixed / unclear**
|
||||
- [Antigravity](https://blog.google/technology/ai/google-ai-updates-november-2025/) (Claude Opus access): some reported extra Opus quota. Pricing/limits were unclear, so the value is hard to predict.
|
||||
This happens **before** a normal reply is generated, so the message can feel
|
||||
like it “didn’t respond.” The fix is to either:
|
||||
|
||||
**Reported weak in Clawdbot**
|
||||
- [GPT-5.2-Codex](https://developers.openai.com/codex/models) inside Clawdbot: reported as rough for conversation/assistant tasks when embedded. Same notes said Codex felt stronger via the [Codex CLI](https://developers.openai.com/codex/cli) than embedded use.
|
||||
- [Grok](https://docs.x.ai/docs/models/grok-4): people tried it and then abandoned it. No strong upside showed up in the notes.
|
||||
- Add the model to `agent.models`, or
|
||||
- Clear the allowlist (remove `agent.models`), or
|
||||
- Pick a model from `/model list`.
|
||||
|
||||
**Theme**
|
||||
- Token burn feels higher than expected in long sessions; people suspect context buildup + tool outputs. Pruning/compaction helps. Check session logs before blaming providers. See [/concepts/session](/concepts/session) and [/concepts/model-failover](/concepts/model-failover).
|
||||
Example allowlist config:
|
||||
|
||||
Want a tailored stack? Share whether you’re using Clawdbot or Clawdis and your main workload (agentic coding vs “assistant” work), and we can suggest a primary + fallback set based on these reports.
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
model: { primary: "anthropic/claude-sonnet-4-5" },
|
||||
models: {
|
||||
"anthropic/claude-sonnet-4-5": { alias: "Sonnet" },
|
||||
"anthropic/claude-opus-4-5": { alias: "Opus" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Models CLI
|
||||
## CLI commands
|
||||
|
||||
See [/cli](/cli) for the full command tree and CLI flags.
|
||||
```bash
|
||||
clawdbot models list
|
||||
clawdbot models status
|
||||
clawdbot models set <provider/model>
|
||||
clawdbot models set-image <provider/model>
|
||||
|
||||
### CLI output (list + status)
|
||||
clawdbot models aliases list
|
||||
clawdbot models aliases add <alias> <provider/model>
|
||||
clawdbot models aliases remove <alias>
|
||||
|
||||
`clawdbot models list` (default) prints a table with these columns:
|
||||
- `Model`: `provider/model` key (truncated in TTY).
|
||||
- `Input`: `text` or `text+image`.
|
||||
- `Ctx`: context window in K tokens (from the model registry).
|
||||
- `Local`: `yes/no` when the provider base URL is local.
|
||||
- `Auth`: `yes/no` when the provider has usable auth.
|
||||
- `Tags`: origin + role hints.
|
||||
clawdbot models fallbacks list
|
||||
clawdbot models fallbacks add <provider/model>
|
||||
clawdbot models fallbacks remove <provider/model>
|
||||
clawdbot models fallbacks clear
|
||||
|
||||
Common tags:
|
||||
- `default` — resolved default model.
|
||||
- `fallback#N` — `agent.model.fallbacks` order.
|
||||
- `image` — `agent.imageModel.primary`.
|
||||
- `img-fallback#N` — `agent.imageModel.fallbacks` order.
|
||||
- `configured` — present in `agent.models`.
|
||||
- `alias:<name>` — alias from `agent.models.*.alias`.
|
||||
- `missing` — referenced in config but not found in the registry.
|
||||
clawdbot models image-fallbacks list
|
||||
clawdbot models image-fallbacks add <provider/model>
|
||||
clawdbot models image-fallbacks remove <provider/model>
|
||||
clawdbot models image-fallbacks clear
|
||||
```
|
||||
|
||||
Output formats:
|
||||
- `--plain`: prints only `provider/model` keys (one per line).
|
||||
- `--json`: `{ count, models: [{ key, name, input, contextWindow, local, available, tags, missing }] }`.
|
||||
`clawdbot models` (no subcommand) is a shortcut for `models status`.
|
||||
|
||||
`clawdbot models status` prints the resolved defaults, fallbacks, image model, aliases,
|
||||
and an **Auth overview** section showing which providers have profiles/env/models.json keys.
|
||||
`--plain` prints the resolved default model only; `--json` returns a structured object for tooling.
|
||||
### `models list`
|
||||
|
||||
## Config changes
|
||||
Shows configured models by default. Useful flags:
|
||||
|
||||
- `agent.models` (configured model catalog + aliases).
|
||||
- `agent.models.*.params` (provider-specific API params passed through to requests).
|
||||
- `agent.model.primary` + `agent.model.fallbacks`.
|
||||
- `agent.imageModel.primary` + `agent.imageModel.fallbacks` (optional).
|
||||
- `auth.profiles` + `auth.order` for per-provider auth failover.
|
||||
- `--all`: full catalog
|
||||
- `--local`: local providers only
|
||||
- `--provider <name>`: filter by provider
|
||||
- `--plain`: one model per line
|
||||
- `--json`: machine‑readable output
|
||||
|
||||
## Scan behavior (models scan)
|
||||
### `models status`
|
||||
|
||||
Shows the resolved primary model, fallbacks, image model, and an auth overview
|
||||
of configured providers. It also surfaces OAuth expiry status for profiles found
|
||||
in the auth store (warns within 24h by default). `--plain` prints only the
|
||||
resolved primary model.
|
||||
OAuth status is always shown (and included in `--json` output). If a configured
|
||||
provider has no credentials, `models status` prints a **Missing auth** section.
|
||||
JSON includes `auth.oauth` (warn window + profiles) and `auth.providers`
|
||||
(effective auth per provider).
|
||||
Use `--check` for automation (exit `1` when missing/expired, `2` when expiring).
|
||||
|
||||
## Scanning (OpenRouter free models)
|
||||
|
||||
`clawdbot models scan` inspects OpenRouter’s **free model catalog** and can
|
||||
optionally probe models for tool and image support.
|
||||
|
||||
Key flags:
|
||||
|
||||
- `--no-probe`: skip live probes (metadata only)
|
||||
- `--min-params <b>`: minimum parameter size (billions)
|
||||
- `--max-age-days <days>`: skip older models
|
||||
- `--provider <name>`: provider prefix filter
|
||||
- `--max-candidates <n>`: fallback list size
|
||||
- `--set-default`: set `agent.model.primary` to the first selection
|
||||
- `--set-image`: set `agent.imageModel.primary` to the first image selection
|
||||
|
||||
Probing requires an OpenRouter API key (from auth profiles or
|
||||
`OPENROUTER_API_KEY`). Without a key, use `--no-probe` to list candidates only.
|
||||
|
||||
Scan results are ranked by:
|
||||
1) Image support
|
||||
2) Tool latency
|
||||
3) Context size
|
||||
4) Parameter count
|
||||
|
||||
Input
|
||||
- OpenRouter `/models` list (filter `:free`)
|
||||
- Requires OpenRouter API key from auth profiles or `OPENROUTER_API_KEY`
|
||||
- Requires OpenRouter API key from auth profiles or `OPENROUTER_API_KEY` (see [/environment](/environment))
|
||||
- Optional filters: `--max-age-days`, `--min-params`, `--provider`, `--max-candidates`
|
||||
- Probe controls: `--timeout`, `--concurrency`
|
||||
|
||||
Probes (direct pi-ai complete)
|
||||
- Tool-call probe (required):
|
||||
- Provide a dummy tool, verify tool call emitted.
|
||||
- Image probe (preferred):
|
||||
- Prompt includes 1x1 PNG; success if no "unsupported image" error.
|
||||
When run in a TTY, you can select fallbacks interactively. In non‑interactive
|
||||
mode, pass `--yes` to accept defaults.
|
||||
|
||||
Scoring/selection
|
||||
- Prefer models passing tool + image for text/tool fallbacks.
|
||||
- Prefer image-only models for image tool fallback (even if tool probe fails).
|
||||
- Rank by: image ok, then lower tool latency, then larger context, then params.
|
||||
## Models registry (`models.json`)
|
||||
|
||||
Interactive selection (TTY)
|
||||
- Multiselect list with per-model stats:
|
||||
- model id, tool ok, image ok, median latency, context, inferred params.
|
||||
- Pre-select top N (default 6).
|
||||
- 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`.
|
||||
|
||||
## 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`](/concepts/model-failover) for auth profile rotation, cooldowns, and timeout handling.
|
||||
|
||||
## Tests
|
||||
|
||||
- Unit: scan selection ordering + probe classification.
|
||||
- CLI: list/aliases/fallbacks add/remove + scan writes config.
|
||||
- Status: shows last used model + fallbacks.
|
||||
|
||||
## Docs
|
||||
|
||||
- Update [`docs/configuration.md`](/gateway/configuration) with `agent.models` + `agent.model` + `agent.imageModel`.
|
||||
- Keep this doc current when CLI surface or scan logic changes.
|
||||
- Note provider aliases like `z.ai/*` -> `zai/*` when relevant.
|
||||
- Provider ids in model refs are normalized to lowercase.
|
||||
Custom providers in `models.providers` are written into `models.json` under the
|
||||
agent directory (default `~/.clawdbot/agents/<agentId>/models.json`). This file
|
||||
is merged by default unless `models.mode` is set to `replace`.
|
||||
|
||||
@@ -17,8 +17,16 @@ An **agent** is a fully scoped brain with its own:
|
||||
- **State directory** (`agentDir`) for auth profiles, model registry, and per-agent config.
|
||||
- **Session store** (chat history + routing state) under `~/.clawdbot/agents/<agentId>/sessions`.
|
||||
|
||||
Skills are per-agent via each workspace’s `skills/` folder, with shared skills
|
||||
available from `~/.clawdbot/skills`. See [Skills: per-agent vs shared](/tools/skills#per-agent-vs-shared-skills).
|
||||
|
||||
The Gateway can host **one agent** (default) or **many agents** side-by-side.
|
||||
|
||||
**Workspace note:** each agent’s workspace is the **default cwd**, not a hard
|
||||
sandbox. Relative paths resolve inside the workspace, but absolute paths can
|
||||
reach other host locations unless sandboxing is enabled. See
|
||||
[Sandboxing](/gateway/sandboxing).
|
||||
|
||||
## Paths (quick map)
|
||||
|
||||
- Config: `~/.clawdbot/clawdbot.json` (or `CLAWDBOT_CONFIG_PATH`)
|
||||
@@ -186,4 +194,8 @@ Starting with v2026.1.6, each agent can have its own sandbox and tool restrictio
|
||||
- **Resource control**: Sandbox specific agents while keeping others on host
|
||||
- **Flexible policies**: Different permissions per agent
|
||||
|
||||
Note: `agent.elevated` is **global** and sender-based; it is not configurable per agent.
|
||||
If you need per-agent boundaries, use `routing.agents[id].tools` to deny `bash`.
|
||||
For group targeting, you can set `routing.agents[id].mentionPatterns` so @mentions map cleanly to the intended agent.
|
||||
|
||||
See [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for detailed examples.
|
||||
|
||||
@@ -43,14 +43,19 @@ All of the above also respect `$CLAWDBOT_STATE_DIR` (state dir override). Full r
|
||||
|
||||
If you already signed in with the external CLIs *on the gateway host*, Clawdbot can reuse those tokens without starting a separate OAuth flow:
|
||||
|
||||
- Claude Code: reads `~/.claude/.credentials.json` → profile `anthropic:claude-cli`
|
||||
- Claude Code: `anthropic:claude-cli`
|
||||
- macOS: Keychain item "Claude Code-credentials" (choose "Always Allow" to avoid launchd prompts)
|
||||
- Linux/Windows: `~/.claude/.credentials.json`
|
||||
- Codex CLI: reads `~/.codex/auth.json` → profile `openai-codex:codex-cli`
|
||||
|
||||
Sync happens when Clawdbot loads the auth store (so it stays up-to-date when the CLIs refresh tokens).
|
||||
On macOS, the first read may trigger a Keychain prompt; run `clawdbot models status`
|
||||
in a terminal once if the Gateway runs headless and can’t access the entry.
|
||||
|
||||
How to verify:
|
||||
|
||||
```bash
|
||||
clawdbot models status
|
||||
clawdbot providers list
|
||||
```
|
||||
|
||||
@@ -97,7 +102,7 @@ At runtime:
|
||||
- if `expires` is in the future → use the stored access token
|
||||
- if expired → refresh (under a file lock) and overwrite the stored credentials
|
||||
|
||||
See implementation: `src/agents/auth-profiles.ts`.
|
||||
The refresh flow is automatic; you generally don’t need to manage tokens manually.
|
||||
|
||||
## Multiple accounts (profiles) + routing
|
||||
|
||||
|
||||
@@ -7,127 +7,93 @@ read_when:
|
||||
---
|
||||
# Presence
|
||||
|
||||
Clawdbot “presence” is a lightweight, best-effort view of:
|
||||
- The **Gateway** itself (one per host), and
|
||||
- The **clients connected to the Gateway** (mac app, WebChat, CLI, etc.).
|
||||
Clawdbot “presence” is a lightweight, best‑effort view of:
|
||||
- the **Gateway** itself, and
|
||||
- **clients connected to the Gateway** (mac app, WebChat, CLI, etc.)
|
||||
|
||||
Presence is used primarily to render the mac app’s **Instances** tab and to provide quick operator visibility.
|
||||
Presence is used primarily to render the macOS app’s **Instances** tab and to
|
||||
provide quick operator visibility.
|
||||
|
||||
## The data model
|
||||
## Presence fields (what shows up)
|
||||
|
||||
Presence entries are structured objects with (some) fields:
|
||||
- `instanceId` (optional but strongly recommended): stable client identity used for dedupe
|
||||
- `host`: a human-readable name (often the machine name)
|
||||
- `ip`: best-effort IP address (may be missing or stale)
|
||||
Presence entries are structured objects with fields like:
|
||||
|
||||
- `instanceId` (optional but strongly recommended): stable client identity
|
||||
- `host`: human‑friendly host name
|
||||
- `ip`: best‑effort IP address
|
||||
- `version`: client version string
|
||||
- `deviceFamily` (optional): hardware family like `iPad`, `iPhone`, `Mac`
|
||||
- `modelIdentifier` (optional): hardware model identifier like `iPad16,6` or `Mac16,6`
|
||||
- `mode`: e.g. `gateway`, `app`, `webchat`, `cli`
|
||||
- `lastInputSeconds` (optional): “seconds since last user input” for that client machine
|
||||
- `reason`: a short marker like `self`, `connect`, `node-connected`, `node-disconnected`, `periodic`, `instances-refresh`
|
||||
- `text`: legacy/debug summary string (kept for backwards compatibility and UI display)
|
||||
- `deviceFamily` / `modelIdentifier`: hardware hints
|
||||
- `mode`: `gateway`, `app`, `webchat`, `cli`, `node`, ...
|
||||
- `lastInputSeconds`: “seconds since last user input” (if known)
|
||||
- `reason`: `self`, `connect`, `node-connected`, `periodic`, ...
|
||||
- `ts`: last update timestamp (ms since epoch)
|
||||
|
||||
## Producers (where presence comes from)
|
||||
|
||||
Presence entries are produced by multiple sources and then **merged**.
|
||||
Presence entries are produced by multiple sources and **merged**.
|
||||
|
||||
### 1) Gateway self entry
|
||||
|
||||
The Gateway seeds a “self” entry at startup so UIs always show at least the current gateway host.
|
||||
The Gateway always seeds a “self” entry at startup so UIs show the gateway host
|
||||
even before any clients connect.
|
||||
|
||||
Implementation: [`src/infra/system-presence.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/system-presence.ts) (`initSelfPresence()`).
|
||||
### 2) WebSocket connect
|
||||
|
||||
### 2) WebSocket connect (connection-derived presence)
|
||||
Every WS client begins with a `connect` request. On successful handshake the
|
||||
Gateway upserts a presence entry for that connection.
|
||||
|
||||
Every WS client must begin with a `connect` request. On successful handshake, the Gateway upserts a presence entry for that connection.
|
||||
#### Why one‑off CLI commands don’t show up
|
||||
|
||||
This is meant to answer: “Which clients are currently connected?”
|
||||
The CLI often connects for short, one‑off commands. To avoid spamming the
|
||||
Instances list, `client.mode === "cli"` is **not** turned into a presence entry.
|
||||
|
||||
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`).
|
||||
### 3) `system-event` beacons
|
||||
|
||||
#### Why one-off CLI commands do not show up
|
||||
Clients can send richer periodic beacons via the `system-event` method. The mac
|
||||
app uses this to report host name, IP, and `lastInputSeconds`.
|
||||
|
||||
The CLI connects to the Gateway to execute one-off commands (health/status/send/agent/etc.). These are not “nodes” and would spam the Instances list, so the Gateway does not create presence entries for clients with `client.mode === "cli"`.
|
||||
|
||||
### 3) `system-event` beacons (client-reported presence)
|
||||
|
||||
Clients can publish richer periodic beacons via the `system-event` method. The mac app uses this to report:
|
||||
- a human-friendly host name
|
||||
- its best-known IP address
|
||||
- `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).
|
||||
|
||||
### 4) Node bridge beacons (gateway-owned presence)
|
||||
### 4) Node bridge beacons
|
||||
|
||||
When a node bridge connection authenticates, the Gateway emits a presence entry
|
||||
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).
|
||||
for that node and refreshes it periodically so it doesn’t expire.
|
||||
|
||||
## Merge + dedupe rules (why `instanceId` matters)
|
||||
|
||||
All producers write into a single in-memory presence map.
|
||||
Presence entries are stored in a single in‑memory map:
|
||||
|
||||
Key points:
|
||||
- Entries are **keyed** by a “presence key”. If two producers use the same key, they update the same entry.
|
||||
- The best key is a stable, opaque `instanceId` that does not change across restarts.
|
||||
- Keys are treated case-insensitively.
|
||||
- Entries are keyed by a **presence key**.
|
||||
- The best key is a stable `instanceId` that survives restarts.
|
||||
- Keys are case‑insensitive.
|
||||
|
||||
Implementation: [`src/infra/system-presence.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/system-presence.ts) (`normalizePresenceKey()`).
|
||||
If a client reconnects without a stable `instanceId`, it may show up as a
|
||||
**duplicate** row.
|
||||
|
||||
### mac app identity (stable UUID)
|
||||
## TTL and bounded size
|
||||
|
||||
The mac app uses a persisted UUID as `instanceId` so:
|
||||
- restarts/reconnects do not create duplicates
|
||||
- renaming the Mac does not create a new “instance”
|
||||
- debug/release builds can share the same identity
|
||||
Presence is intentionally ephemeral:
|
||||
|
||||
Implementation: [`apps/macos/Sources/Clawdbot/InstanceIdentity.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/Clawdbot/InstanceIdentity.swift).
|
||||
- **TTL:** entries older than 5 minutes are pruned
|
||||
- **Max entries:** 200 (oldest dropped first)
|
||||
|
||||
`displayName` (machine name) is used for UI, while `instanceId` is used for dedupe.
|
||||
|
||||
## TTL and bounded size (why stale rows disappear)
|
||||
|
||||
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()`).
|
||||
This keeps the list fresh and avoids unbounded memory growth.
|
||||
|
||||
## Remote/tunnel caveat (loopback IPs)
|
||||
|
||||
When a client connects over an SSH tunnel / local port forward, the Gateway may see the remote address as loopback (`127.0.0.1`).
|
||||
When a client connects over an SSH tunnel / local port forward, the Gateway may
|
||||
see the remote address as `127.0.0.1`. To avoid overwriting a good client‑reported
|
||||
IP, loopback remote addresses are ignored.
|
||||
|
||||
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()`).
|
||||
|
||||
## Consumers (who reads presence)
|
||||
## Consumers
|
||||
|
||||
### macOS Instances tab
|
||||
|
||||
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)
|
||||
|
||||
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`).
|
||||
|
||||
The store refreshes periodically and also applies `presence` WS events.
|
||||
The macOS app renders the output of `system-presence` and applies a small status
|
||||
indicator (Active/Idle/Stale) based on the age of the last update.
|
||||
|
||||
## Debugging tips
|
||||
|
||||
- To see the raw list, call `system-presence` against the gateway.
|
||||
- To see the raw list, call `system-presence` against the Gateway.
|
||||
- If you see duplicates:
|
||||
- confirm clients send a stable `instanceId` in the handshake (`connect.params.client.instanceId`)
|
||||
- confirm beaconing uses the same `instanceId`
|
||||
- check whether the connection-derived entry is missing `instanceId` (then it will be keyed by `connId` and duplicates are expected on reconnect)
|
||||
- confirm clients send a stable `instanceId` in the handshake
|
||||
- confirm periodic beacons use the same `instanceId`
|
||||
- check whether the connection‑derived entry is missing `instanceId` (duplicates are expected)
|
||||
|
||||
@@ -3,24 +3,96 @@ summary: "Routing rules per provider (WhatsApp, Telegram, Discord, web) and shar
|
||||
read_when:
|
||||
- Changing provider routing or inbox behavior
|
||||
---
|
||||
# Providers & Routing
|
||||
# Providers & routing
|
||||
|
||||
Updated: 2026-01-06
|
||||
|
||||
Goal: deterministic replies per provider, while supporting multi-agent + multi-account routing.
|
||||
Clawdbot routes replies **back to the provider where a message came from**. The
|
||||
model does not choose a provider; routing is deterministic and controlled by the
|
||||
host configuration.
|
||||
|
||||
- **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>`
|
||||
- Telegram forum topics: `agent:<agentId>:telegram:group:<chatId>:topic:<threadId>`
|
||||
- **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.
|
||||
## Key terms
|
||||
|
||||
- **Provider**: `whatsapp`, `telegram`, `discord`, `slack`, `signal`, `imessage`, `webchat`.
|
||||
- **AccountId**: per‑provider account instance (when supported).
|
||||
- **AgentId**: an isolated workspace + session store (“brain”).
|
||||
- **SessionKey**: the bucket key used to store context and control concurrency.
|
||||
|
||||
## Session key shapes (examples)
|
||||
|
||||
Direct messages collapse to the agent’s **main** session:
|
||||
|
||||
- `agent:<agentId>:<mainKey>` (default: `agent:main:main`)
|
||||
|
||||
Groups and channels remain isolated per provider:
|
||||
|
||||
- Groups: `agent:<agentId>:<provider>:group:<id>`
|
||||
- Channels/rooms: `agent:<agentId>:<provider>:channel:<id>`
|
||||
|
||||
Threads:
|
||||
|
||||
- Slack/Discord threads append `:thread:<threadId>` to the base key.
|
||||
- Telegram forum topics embed `:topic:<topicId>` in the group key.
|
||||
|
||||
Examples:
|
||||
|
||||
- `agent:main:telegram:group:-1001234567890:topic:42`
|
||||
- `agent:main:discord:channel:123456:thread:987654`
|
||||
|
||||
## Routing rules (how an agent is chosen)
|
||||
|
||||
Routing picks **one agent** for each inbound message:
|
||||
|
||||
1. **Exact peer match** (`routing.bindings` with `peer.kind` + `peer.id`).
|
||||
2. **Guild match** (Discord) via `guildId`.
|
||||
3. **Team match** (Slack) via `teamId`.
|
||||
4. **Account match** (`accountId` on the provider).
|
||||
5. **Provider match** (any account on that provider).
|
||||
6. **Default agent** (`routing.defaultAgentId`, fallback to `main`).
|
||||
|
||||
The matched agent determines which workspace and session store are used.
|
||||
|
||||
## Config overview
|
||||
|
||||
- `routing.defaultAgentId`: default agent when no binding matches.
|
||||
- `routing.agents`: named agent definitions (workspace, model, etc.).
|
||||
- `routing.bindings`: map inbound providers/accounts/peers to agents.
|
||||
|
||||
Example:
|
||||
|
||||
```json5
|
||||
{
|
||||
routing: {
|
||||
defaultAgentId: "main",
|
||||
agents: {
|
||||
support: { name: "Support", workspace: "~/clawd-support" }
|
||||
},
|
||||
bindings: [
|
||||
{ match: { provider: "slack", teamId: "T123" }, agentId: "support" },
|
||||
{ match: { provider: "telegram", peer: { kind: "group", id: "-100123" } }, agentId: "support" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Session storage
|
||||
|
||||
Session stores live under the state directory (default `~/.clawdbot`):
|
||||
|
||||
- `~/.clawdbot/agents/<agentId>/sessions/sessions.json`
|
||||
- JSONL transcripts live alongside the store
|
||||
|
||||
You can override the store path via `session.store` and `{agentId}` templating.
|
||||
|
||||
## WebChat behavior
|
||||
|
||||
WebChat attaches to the **selected agent** and defaults to the agent’s main
|
||||
session. Because of this, WebChat lets you see cross‑provider context for that
|
||||
agent in one place.
|
||||
|
||||
## Reply context
|
||||
|
||||
Inbound replies include:
|
||||
- `ReplyToId`, `ReplyToBody`, and `ReplyToSender` when available.
|
||||
- Quoted context is appended to `Body` as a `[Replying to ...]` block.
|
||||
|
||||
This is consistent across providers.
|
||||
|
||||
@@ -12,7 +12,7 @@ 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.
|
||||
- A lane-aware FIFO queue 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.
|
||||
@@ -74,4 +74,4 @@ Defaults: `debounceMs: 1000`, `cap: 20`, `drop: summarize`.
|
||||
|
||||
## Troubleshooting
|
||||
- If commands seem stuck, enable verbose logs and look for “queued for …ms” lines to confirm the queue is draining.
|
||||
- `enqueueCommand` exposes a lightweight `getQueueSize()` helper if you need to surface queue depth in future diagnostics.
|
||||
- If you need queue depth, enable verbose logs and watch for queue timing lines.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
summary: "Session pruning: opt-in tool-result trimming to reduce context bloat"
|
||||
summary: "Session pruning: tool-result trimming to reduce context bloat"
|
||||
read_when:
|
||||
- You want to reduce LLM context growth from tool outputs
|
||||
- You are tuning agent.contextPruning
|
||||
---
|
||||
# Session Pruning
|
||||
|
||||
Session pruning trims **old tool results** from the in-memory context right before each LLM call. It is **opt-in** and does **not** rewrite the on-disk session history (`*.jsonl`).
|
||||
Session pruning trims **old tool results** from the in-memory context right before each LLM call. It does **not** rewrite the on-disk session history (`*.jsonl`).
|
||||
|
||||
## When it runs
|
||||
- Before each LLM request (context hook).
|
||||
@@ -44,6 +44,7 @@ Pruning uses an estimated context window (chars ≈ tokens × 4). The window siz
|
||||
## Tool selection
|
||||
- `tools.allow` / `tools.deny` support `*` wildcards.
|
||||
- Deny wins.
|
||||
- Matching is case-insensitive.
|
||||
- Empty allow list => all tools allowed.
|
||||
|
||||
## Interaction with other limits
|
||||
@@ -59,7 +60,7 @@ Pruning uses an estimated context window (chars ≈ tokens × 4). The window siz
|
||||
- `hardClear`: `{ enabled: true, placeholder: "[Old tool result content cleared]" }`
|
||||
|
||||
## Examples
|
||||
Minimal (adaptive):
|
||||
Default (adaptive):
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
@@ -68,6 +69,15 @@ Minimal (adaptive):
|
||||
}
|
||||
```
|
||||
|
||||
To disable:
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
contextPruning: { mode: "off" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Aggressive:
|
||||
```json5
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ Goal: small, hard-to-misuse tool set so agents can list sessions, fetch history,
|
||||
- Hooks use `hook:<uuid>` unless explicitly set.
|
||||
- Node bridge uses `node-<nodeId>` unless explicitly set.
|
||||
|
||||
`global` and `unknown` are internal-only and never listed. If `session.scope = "global"`, we alias it to `main` for all tools so callers never see `global`.
|
||||
`global` and `unknown` are reserved values and are never listed. If `session.scope = "global"`, we alias it to `main` for all tools so callers never see `global`.
|
||||
|
||||
## sessions_list
|
||||
List sessions as an array of rows.
|
||||
|
||||
@@ -21,8 +21,8 @@ All session state is **owned by the gateway** (the “master” Clawdbot). UI cl
|
||||
- Group entries may include `displayName`, `provider`, `subject`, `room`, and `space` to label sessions in UIs.
|
||||
- Clawdbot does **not** read legacy Pi/Tau session folders.
|
||||
|
||||
## Session pruning (optional)
|
||||
Clawdbot can trim **old tool results** from the in-memory context right before LLM calls (opt-in).
|
||||
## Session pruning
|
||||
Clawdbot trims **old tool results** from the in-memory context right before LLM calls by default.
|
||||
This does **not** rewrite JSONL history. See [/concepts/session-pruning](/concepts/session-pruning).
|
||||
|
||||
## Mapping transports → session keys
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
summary: "What the ClaudeBot system prompt contains and how it is assembled"
|
||||
summary: "What the Clawdbot system prompt contains and how it is assembled"
|
||||
read_when:
|
||||
- Editing system prompt text, tools list, or time/heartbeat sections
|
||||
- Changing workspace bootstrap or skills injection behavior
|
||||
---
|
||||
# System Prompt
|
||||
|
||||
ClaudeBot builds a custom system prompt for every agent run. The prompt is **Clawdbot-owned** and does not use the p-coding-agent default prompt.
|
||||
Clawdbot builds a custom system prompt for every agent run. The prompt is **Clawdbot-owned** and does not use the p-coding-agent default prompt.
|
||||
|
||||
The prompt is assembled in `src/agents/system-prompt.ts` and injected by `src/agents/pi-embedded-runner.ts`.
|
||||
The prompt is assembled by Clawdbot and injected into each agent run.
|
||||
|
||||
## Structure
|
||||
|
||||
@@ -16,7 +16,7 @@ The prompt is intentionally compact and uses fixed sections:
|
||||
|
||||
- **Tooling**: current tool list + short descriptions.
|
||||
- **Skills**: tells the model how to load skill instructions on demand.
|
||||
- **ClaudeBot Self-Update**: how to run `config.apply` and `update.run`.
|
||||
- **Clawdbot Self-Update**: how to run `config.apply` and `update.run`.
|
||||
- **Workspace**: working directory (`agent.workspace`).
|
||||
- **Workspace Files (injected)**: indicates bootstrap files are included below.
|
||||
- **Time**: UTC default + the user’s local time (already converted).
|
||||
@@ -56,9 +56,3 @@ Skills are **not** auto-injected. Instead, the prompt instructs the model to use
|
||||
```
|
||||
|
||||
This keeps the base prompt small while still enabling targeted skill usage.
|
||||
|
||||
## Code references
|
||||
|
||||
- Prompt text: `src/agents/system-prompt.ts`
|
||||
- Prompt assembly + injection: `src/agents/pi-embedded-runner.ts`
|
||||
- Bootstrap trimming: `src/agents/pi-embedded-helpers.ts`
|
||||
|
||||
@@ -3,40 +3,34 @@ summary: "TypeBox schemas as the single source of truth for the gateway protocol
|
||||
read_when:
|
||||
- Updating protocol schemas or codegen
|
||||
---
|
||||
# TypeBox as Protocol Source of Truth
|
||||
# TypeBox as protocol source of truth
|
||||
|
||||
Last updated: 2025-12-09
|
||||
Last updated: 2026-01-08
|
||||
|
||||
We use TypeBox schemas in [`src/gateway/protocol/schema.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/protocol/schema.ts) as the single source of truth for the Gateway control plane (connect/req/res/event frames and payloads). All derived artifacts should be generated from these schemas, not edited by hand.
|
||||
TypeBox schemas define the Gateway control plane (connect/req/res/event frames and
|
||||
payloads). All generated artifacts must come from these schemas.
|
||||
|
||||
## Current pipeline
|
||||
|
||||
- **TypeBox → JSON Schema**: `pnpm protocol:gen` writes [`dist/protocol.schema.json`](https://github.com/clawdbot/clawdbot/blob/main/dist/protocol.schema.json) (draft-07) and runs AJV in the server tests.
|
||||
- **TypeBox → Swift**: `pnpm protocol:gen:swift` generates [`apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift).
|
||||
- `pnpm protocol:gen`
|
||||
- writes the JSON Schema output (draft‑07)
|
||||
- `pnpm protocol:gen:swift`
|
||||
- generates Swift gateway models
|
||||
- `pnpm protocol:check`
|
||||
- runs both generators and verifies the output is committed
|
||||
|
||||
## Problem
|
||||
## Swift codegen behavior
|
||||
|
||||
- We want strong typing in Swift, including a sealed `GatewayFrame` enum with a discriminator and a forward-compatible `unknown` case.
|
||||
The Swift generator emits:
|
||||
|
||||
## Preferred plan (next step)
|
||||
- `GatewayFrame` enum with `req`, `res`, `event`, and `unknown` cases
|
||||
- Strongly typed payload structs/enums
|
||||
- `ErrorCode` values and `GATEWAY_PROTOCOL_VERSION`
|
||||
|
||||
- Add a small, custom Swift generator driven directly by the TypeBox schemas:
|
||||
- Emit a sealed `enum GatewayFrame: Codable { case req(RequestFrame), res(ResponseFrame), event(EventFrame) }`.
|
||||
- Emit strongly typed payload structs/enums (`ConnectParams`, `HelloOk`, `RequestFrame`, `ResponseFrame`, `EventFrame`, `PresenceEntry`, `Snapshot`, `StateVersion`, `ErrorShape`, `AgentEvent`, `TickEvent`, `ShutdownEvent`, `SendParams`, `AgentParams`, `ErrorCode`, `PROTOCOL_VERSION`).
|
||||
- Custom `init(from:)` / `encode(to:)` enforces the `type` discriminator and can include an `unknown` case for forward compatibility.
|
||||
- Wire a new script (e.g., `pnpm protocol:gen:swift`) into `protocol:check` so CI fails if the generated Swift is stale.
|
||||
Unknown frame types are preserved as raw payloads for forward compatibility.
|
||||
|
||||
Why this path:
|
||||
- Single source of truth stays TypeBox; no new IDL to maintain.
|
||||
- Predictable, strongly typed Swift (no optional soup).
|
||||
- Small deterministic codegen (~150–200 LOC script) we control.
|
||||
## When you change schemas
|
||||
|
||||
## Alternative (if we want off-the-shelf codegen)
|
||||
|
||||
- Wrap the existing JSON Schema into an OpenAPI 3.1 doc (auto-generated) and use **swift-openapi-generator** or **openapi-generator swift5**. More moving parts, but also yields enums with discriminator support. Keep this as a fallback if we don’t want a custom emitter.
|
||||
|
||||
## Action items
|
||||
|
||||
- Implement `protocol:gen:swift` that reads the TypeBox schemas and emits the sealed Swift enum + payload structs.
|
||||
- Update `protocol:check` to include the Swift generator output in the diff check.
|
||||
- Remove quicktype output once the custom generator is in place (or keep it for docs only).
|
||||
1) Update the TypeBox schemas.
|
||||
2) Run `pnpm protocol:check`.
|
||||
3) Commit the regenerated schema + Swift models.
|
||||
|
||||
@@ -11,7 +11,8 @@ read_when:
|
||||
- No estimated costs; only the provider-reported windows.
|
||||
|
||||
## Where it shows up
|
||||
- `/status` in chats: adds a short “Usage” line (only if available).
|
||||
- `/status` in chats: emoji‑rich status card with session tokens + estimated cost (API key only) and provider quota windows when available.
|
||||
- `/cost on|off` in chats: toggles per‑response usage lines (OAuth shows tokens only).
|
||||
- CLI: `clawdbot status --usage` prints a full per-provider breakdown.
|
||||
- CLI: `clawdbot providers list` prints the same usage snapshot alongside provider config (use `--no-usage` to skip).
|
||||
- macOS menu bar: “Usage” section under Context (only if available).
|
||||
|
||||
86
docs/debugging.md
Normal file
@@ -0,0 +1,86 @@
|
||||
---
|
||||
summary: "Debugging tools: watch mode, raw model streams, and tracing reasoning leakage"
|
||||
read_when:
|
||||
- You need to inspect raw model output for reasoning leakage
|
||||
- You want to run the Gateway in watch mode while iterating
|
||||
- You need a repeatable debugging workflow
|
||||
---
|
||||
|
||||
# Debugging
|
||||
|
||||
This page covers debugging helpers for streaming output, especially when a
|
||||
provider mixes reasoning into normal text.
|
||||
|
||||
## Gateway watch mode
|
||||
|
||||
For fast iteration, run the gateway under the file watcher:
|
||||
|
||||
```bash
|
||||
pnpm gateway:watch --force
|
||||
```
|
||||
|
||||
This maps to:
|
||||
|
||||
```bash
|
||||
tsx watch src/entry.ts gateway --force
|
||||
```
|
||||
|
||||
Add any gateway CLI flags after `gateway:watch` and they will be passed through
|
||||
on each restart.
|
||||
|
||||
## Raw stream logging (Clawdbot)
|
||||
|
||||
Clawdbot can log the **raw assistant stream** before any filtering/formatting.
|
||||
This is the best way to see whether reasoning is arriving as plain text deltas
|
||||
(or as separate thinking blocks).
|
||||
|
||||
Enable it via CLI:
|
||||
|
||||
```bash
|
||||
pnpm gateway:watch --force --raw-stream
|
||||
```
|
||||
|
||||
Optional path override:
|
||||
|
||||
```bash
|
||||
pnpm gateway:watch --force --raw-stream --raw-stream-path ~/.clawdbot/logs/raw-stream.jsonl
|
||||
```
|
||||
|
||||
Equivalent env vars:
|
||||
|
||||
```bash
|
||||
CLAWDBOT_RAW_STREAM=1
|
||||
CLAWDBOT_RAW_STREAM_PATH=~/.clawdbot/logs/raw-stream.jsonl
|
||||
```
|
||||
|
||||
Default file:
|
||||
|
||||
`~/.clawdbot/logs/raw-stream.jsonl`
|
||||
|
||||
## Raw chunk logging (pi-mono)
|
||||
|
||||
To capture **raw OpenAI-compat chunks** before they are parsed into blocks,
|
||||
pi-mono exposes a separate logger:
|
||||
|
||||
```bash
|
||||
PI_RAW_STREAM=1
|
||||
```
|
||||
|
||||
Optional path:
|
||||
|
||||
```bash
|
||||
PI_RAW_STREAM_PATH=~/.pi-mono/logs/raw-openai-completions.jsonl
|
||||
```
|
||||
|
||||
Default file:
|
||||
|
||||
`~/.pi-mono/logs/raw-openai-completions.jsonl`
|
||||
|
||||
> Note: this is only emitted by processes using pi-mono’s
|
||||
> `openai-completions` provider.
|
||||
|
||||
## Safety notes
|
||||
|
||||
- Raw stream logs can include full prompts, tool output, and user data.
|
||||
- Keep logs local and delete them after debugging.
|
||||
- If you share logs, scrub secrets and PII first.
|
||||
@@ -97,6 +97,14 @@
|
||||
"source": "/bun",
|
||||
"destination": "/install/bun"
|
||||
},
|
||||
{
|
||||
"source": "/auth-monitoring",
|
||||
"destination": "/automation/auth-monitoring"
|
||||
},
|
||||
{
|
||||
"source": "/scripts",
|
||||
"destination": "/scripts"
|
||||
},
|
||||
{
|
||||
"source": "/camera",
|
||||
"destination": "/nodes/camera"
|
||||
@@ -474,8 +482,8 @@
|
||||
"destination": "/gateway/troubleshooting"
|
||||
},
|
||||
{
|
||||
"source": "/tui",
|
||||
"destination": "/web/tui"
|
||||
"source": "/web/tui",
|
||||
"destination": "/tui"
|
||||
},
|
||||
{
|
||||
"source": "/typebox",
|
||||
@@ -541,6 +549,14 @@
|
||||
"install/bun"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "CLI",
|
||||
"pages": [
|
||||
"cli/index",
|
||||
"cli/gateway",
|
||||
"cli/sandbox"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Core Concepts",
|
||||
"pages": [
|
||||
@@ -548,6 +564,7 @@
|
||||
"concepts/agent",
|
||||
"concepts/agent-loop",
|
||||
"concepts/system-prompt",
|
||||
"token-use",
|
||||
"concepts/oauth",
|
||||
"concepts/agent-workspace",
|
||||
"concepts/multi-agent",
|
||||
@@ -576,6 +593,7 @@
|
||||
"gateway/gateway-lock",
|
||||
"gateway/configuration",
|
||||
"gateway/configuration-examples",
|
||||
"gateway/authentication",
|
||||
"gateway/background-process",
|
||||
"gateway/health",
|
||||
"gateway/heartbeat",
|
||||
@@ -597,7 +615,7 @@
|
||||
"web/control-ui",
|
||||
"web/dashboard",
|
||||
"web/webchat",
|
||||
"web/tui"
|
||||
"tui"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -616,6 +634,7 @@
|
||||
{
|
||||
"group": "Automation & Hooks",
|
||||
"pages": [
|
||||
"automation/auth-monitoring",
|
||||
"automation/webhook",
|
||||
"automation/gmail-pubsub",
|
||||
"automation/cron-jobs",
|
||||
@@ -689,6 +708,7 @@
|
||||
{
|
||||
"group": "Reference & Templates",
|
||||
"pages": [
|
||||
"scripts",
|
||||
"reference/rpc",
|
||||
"reference/device-models",
|
||||
"reference/test",
|
||||
|
||||
60
docs/environment.md
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
summary: "Where Clawdbot loads environment variables and the precedence order"
|
||||
read_when:
|
||||
- You need to know which env vars are loaded, and in what order
|
||||
- You are debugging missing API keys in the Gateway
|
||||
- You are documenting provider auth or deployment environments
|
||||
---
|
||||
# Environment variables
|
||||
|
||||
Clawdbot pulls environment variables from multiple sources. The rule is **never override existing values**.
|
||||
|
||||
## Precedence (highest → lowest)
|
||||
|
||||
1) **Process environment** (what the Gateway process already has from the parent shell/daemon).
|
||||
2) **`.env` in the current working directory** (dotenv default; does not override).
|
||||
3) **Global `.env`** at `~/.clawdbot/.env` (aka `$CLAWDBOT_STATE_DIR/.env`; does not override).
|
||||
4) **Config `env` block** in `~/.clawdbot/clawdbot.json` (applied only if missing).
|
||||
5) **Optional login-shell import** (`env.shellEnv.enabled` or `CLAWDBOT_LOAD_SHELL_ENV=1`), applied only for missing expected keys.
|
||||
|
||||
If the config file is missing entirely, step 4 is skipped; shell import still runs if enabled.
|
||||
|
||||
## Config `env` block
|
||||
|
||||
Two equivalent ways to set inline env vars (both are non-overriding):
|
||||
|
||||
```json5
|
||||
{
|
||||
env: {
|
||||
OPENROUTER_API_KEY: "sk-or-...",
|
||||
vars: {
|
||||
GROQ_API_KEY: "gsk-..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Shell env import
|
||||
|
||||
`env.shellEnv` runs your login shell and imports only **missing** expected keys:
|
||||
|
||||
```json5
|
||||
{
|
||||
env: {
|
||||
shellEnv: {
|
||||
enabled: true,
|
||||
timeoutMs: 15000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Env var equivalents:
|
||||
- `CLAWDBOT_LOAD_SHELL_ENV=1`
|
||||
- `CLAWDBOT_SHELL_ENV_TIMEOUT_MS=15000`
|
||||
|
||||
## Related
|
||||
|
||||
- [Gateway configuration](/gateway/configuration)
|
||||
- [FAQ: env vars and .env loading](/start/faq#env-vars-and-env-loading)
|
||||
- [Models overview](/concepts/models)
|
||||
@@ -8,11 +8,11 @@ read_when: "Changing onboarding wizard steps or config schema endpoints"
|
||||
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.
|
||||
- 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.
|
||||
- Wizard engine (shared session + prompts + onboarding state).
|
||||
- CLI onboarding uses the same wizard flow as the UI clients.
|
||||
- Gateway RPC exposes wizard + config schema endpoints.
|
||||
- macOS onboarding uses the wizard step model.
|
||||
- Web UI renders config forms from JSON Schema + UI hints.
|
||||
|
||||
## Gateway RPC
|
||||
- `wizard.start` params: `{ mode?: "local"|"remote", workspace?: string }`
|
||||
|
||||
@@ -28,44 +28,29 @@ Recent gateway logs show repeated `cron.add` failures with invalid parameters (m
|
||||
- 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.
|
||||
## What changed
|
||||
|
||||
## Multi-phase Execution Plan
|
||||
- `cron.add` and `cron.update` now normalize common wrapper shapes and infer missing `kind` fields.
|
||||
- Agent cron tool schema matches the gateway schema, which reduces invalid payloads.
|
||||
- Provider enums are aligned across gateway, CLI, UI, and macOS picker.
|
||||
- Control UI uses the gateway’s `jobs` count field for status.
|
||||
|
||||
### 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-jobs.md`](/automation/cron-jobs).
|
||||
## Current behavior
|
||||
|
||||
### 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.
|
||||
- **Normalization:** wrapped `data`/`job` payloads are unwrapped; `schedule.kind` and `payload.kind` are inferred when safe.
|
||||
- **Defaults:** safe defaults are applied for `wakeMode` and `sessionTarget` when missing.
|
||||
- **Providers:** Discord/Slack/Signal/iMessage are now consistently surfaced across CLI/UI.
|
||||
|
||||
### 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.
|
||||
See [`docs/cron-jobs.md`](/automation/cron-jobs) for the normalized shape and examples.
|
||||
|
||||
### Phase 4 — Verification
|
||||
- [x] Run tests (full suite executed via `pnpm test -- cron-tool`).
|
||||
## Verification
|
||||
|
||||
## 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.
|
||||
|
||||
- Manual Control UI smoke: add a cron job per provider + verify status job count.
|
||||
|
||||
## Open Questions
|
||||
- Should `cron.add` accept explicit `state` from clients (currently disallowed by schema)?
|
||||
|
||||
@@ -1,126 +1,38 @@
|
||||
---
|
||||
summary: "Spec: groupPolicy hardening for Telegram allowlist parity"
|
||||
summary: "Telegram allowlist hardening: prefix + whitespace normalization"
|
||||
read_when:
|
||||
- Reviewing historical Telegram allowlist normalization changes
|
||||
- Reviewing historical Telegram allowlist changes
|
||||
---
|
||||
# Engineering Execution Spec: groupPolicy Hardening (Telegram Allowlist Parity)
|
||||
# Telegram Allowlist Hardening
|
||||
|
||||
**Date**: 2026-01-05
|
||||
**Status**: Complete
|
||||
**PR**: #216 (feat/whatsapp-group-policy)
|
||||
**PR**: #216
|
||||
|
||||
---
|
||||
## Summary
|
||||
|
||||
## Executive Summary
|
||||
Telegram allowlists now accept `telegram:` and `tg:` prefixes case-insensitively, and tolerate
|
||||
accidental whitespace. This aligns inbound allowlist checks with outbound send normalization.
|
||||
|
||||
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.
|
||||
## What changed
|
||||
|
||||
---
|
||||
- Prefixes `telegram:` and `tg:` are treated the same (case-insensitive).
|
||||
- Allowlist entries are trimmed; empty entries are ignored.
|
||||
|
||||
## Findings Analysis
|
||||
## Examples
|
||||
|
||||
### [MED] F1: Telegram Allowlist Prefix Handling Is Case-Sensitive and Excludes `tg:`
|
||||
All of these are accepted for the same ID:
|
||||
|
||||
**Location**: [`src/telegram/bot.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/telegram/bot.ts)
|
||||
- `telegram:123456`
|
||||
- `TG:123456`
|
||||
- ` tg:123456 `
|
||||
|
||||
**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.
|
||||
## Why it matters
|
||||
|
||||
**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.
|
||||
Copy/paste from logs or chat IDs often includes prefixes and whitespace. Normalizing avoids
|
||||
false negatives when deciding whether to respond in DMs or groups.
|
||||
|
||||
**Fix**: Normalize allowlist entries by trimming whitespace and stripping `telegram:` / `tg:` prefixes case-insensitively at pre-compute time.
|
||||
## Related docs
|
||||
|
||||
---
|
||||
|
||||
### [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`](/concepts/groups)
|
||||
- [`docs/telegram.md`](/providers/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`](/concepts/groups) | Docs | Mention `tg:` alias + case-insensitive prefixes |
|
||||
| [`docs/telegram.md`](/providers/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
|
||||
- [Group Chats](/concepts/groups)
|
||||
- [Telegram Provider](/providers/telegram)
|
||||
|
||||
@@ -1,147 +1,32 @@
|
||||
---
|
||||
summary: "Proposal: model config, auth profiles, and fallback behavior"
|
||||
summary: "Exploration: model config, auth profiles, and fallback behavior"
|
||||
read_when:
|
||||
- Designing model selection, auth profiles, or fallback behavior
|
||||
- Migrating model config schema
|
||||
- Exploring future model selection + auth profile ideas
|
||||
---
|
||||
# Model Config (Exploration)
|
||||
|
||||
# Model config proposal
|
||||
This document captures **ideas** for future model configuration. It is not a
|
||||
shipping spec. For current behavior, see:
|
||||
- [Models](/concepts/models)
|
||||
- [Model failover](/concepts/model-failover)
|
||||
- [OAuth + profiles](/concepts/oauth)
|
||||
|
||||
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
|
||||
## Motivation
|
||||
|
||||
Non-goals (v1)
|
||||
- Auto-discovery of provider capabilities beyond catalog input tags
|
||||
- Per-model auth profile order (see open questions)
|
||||
Operators want:
|
||||
- Multiple auth profiles per provider (personal vs work).
|
||||
- Simple `/model` selection with predictable fallbacks.
|
||||
- Clear separation between text models and image-capable models.
|
||||
|
||||
## Proposed config shape
|
||||
## Possible direction (high level)
|
||||
|
||||
```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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
- Keep model selection simple: `provider/model` with optional aliases.
|
||||
- Let providers have multiple auth profiles, with an explicit order.
|
||||
- Use a global fallback list so all sessions fail over consistently.
|
||||
- Only override image routing when explicitly configured.
|
||||
|
||||
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.
|
||||
## Open questions
|
||||
|
||||
## 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.
|
||||
- Should profile rotation be per-provider or per-model?
|
||||
- How should the UI surface profile selection for a session?
|
||||
- What is the safest migration path from legacy config keys?
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
summary: "Proposal + research notes: offline memory system for Clawd workspaces (Markdown source-of-truth + derived index)"
|
||||
summary: "Research notes: offline memory system for Clawd workspaces (Markdown source-of-truth + derived index)"
|
||||
read_when:
|
||||
- Designing workspace memory (~/clawd) beyond daily Markdown logs
|
||||
- Deciding: standalone CLI vs deep Clawdbot integration
|
||||
- Adding offline recall + reflection (retain/recall/reflect)
|
||||
---
|
||||
|
||||
# Workspace Memory v2 (offline): proposal + research
|
||||
# Workspace Memory v2 (offline): research notes
|
||||
|
||||
Target: Clawd-style workspace (`agent.workspace`, default `~/clawd`) where “memory” is stored as one Markdown file per day (`memory/YYYY-MM-DD.md`) plus a small set of stable files (e.g. `memory.md`, `SOUL.md`).
|
||||
|
||||
@@ -171,8 +171,7 @@ Recommendation: **deep integration in Clawdbot**, but keep a separable core libr
|
||||
- reuse from other contexts (local scripts, future desktop app, etc.)
|
||||
|
||||
Shape:
|
||||
- `src/memory/*` (library-ish core; pure functions + sqlite adapter)
|
||||
- [`src/commands/memory/*.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/commands/memory/*.ts) (CLI glue)
|
||||
The memory tooling is intended to be a small CLI + library layer, but this is exploratory only.
|
||||
|
||||
## “S-Collide” / SuCo: when to use it (research)
|
||||
|
||||
@@ -196,29 +195,13 @@ Open question:
|
||||
- what’s the **best** offline embedding model for “personal assistant memory” on your machines (MacBook + Castle)?
|
||||
- if you already have Ollama: embed with a local model; otherwise ship a small embedding model in the toolchain.
|
||||
|
||||
## Implementation plan (phased, shippable)
|
||||
## Smallest useful pilot
|
||||
|
||||
### Phase 0: workspace conventions (no code)
|
||||
- add `bank/` files + entity pages
|
||||
- add `## Retain` convention to daily logs
|
||||
If you want a minimal, still-useful version:
|
||||
|
||||
### Phase 1: `clawdbot memory index|recall` (FTS-only)
|
||||
- parse Markdown (`memory/*.md`, `bank/*.md`) into chunks
|
||||
- write to SQLite: `facts`, `entities`, `fact_entities`, `opinions`
|
||||
- FTS5 table over `facts.content`
|
||||
- `recall` returns citations (path + line) + trimmed content budget
|
||||
|
||||
### Phase 2: entity summaries + opinion tracking
|
||||
- `reflect` updates `bank/entities/*.md`
|
||||
- opinion confidence updates with evidence pointers (no embeddings required yet)
|
||||
|
||||
### Phase 3: semantic recall (offline embeddings)
|
||||
- compute embeddings during indexing (incremental)
|
||||
- retrieval = `hybrid(FTS, vector)` with simple fusion
|
||||
|
||||
### Phase 4: “graph-ish” traversal (still simple)
|
||||
- entity links enable multi-hop: “related to Peter via warelay”
|
||||
- optional: “topic” nodes, lightweight edges (not a full KG)
|
||||
- Add `bank/` entity pages and a `## Retain` section in daily logs.
|
||||
- Use SQLite FTS for recall with citations (path + line numbers).
|
||||
- Add embeddings only if recall quality or scale demands it.
|
||||
|
||||
## References
|
||||
|
||||
|
||||
82
docs/gateway/authentication.md
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
summary: "Model authentication: OAuth, API keys, and Claude Code token reuse"
|
||||
read_when:
|
||||
- Debugging model auth or OAuth expiry
|
||||
- Documenting authentication or credential storage
|
||||
---
|
||||
# Authentication
|
||||
|
||||
Clawdbot supports OAuth and API keys for model providers. For Anthropic
|
||||
subscription accounts, the most stable path is to **reuse Claude Code OAuth
|
||||
credentials**, including the 1‑year token created by `claude setup-token`.
|
||||
|
||||
See [/concepts/oauth](/concepts/oauth) for the full OAuth flow and storage
|
||||
layout.
|
||||
|
||||
## Recommended: long‑lived Claude Code token
|
||||
|
||||
Run this on the **gateway host** (the machine running the Gateway):
|
||||
|
||||
```bash
|
||||
claude setup-token
|
||||
```
|
||||
|
||||
This issues a long‑lived **OAuth token** (not an API key) and stores it for
|
||||
Claude Code. Then sync and verify:
|
||||
|
||||
```bash
|
||||
clawdbot models status
|
||||
clawdbot doctor
|
||||
```
|
||||
|
||||
Automation-friendly check (exit `1` when expired/missing, `2` when expiring):
|
||||
|
||||
```bash
|
||||
clawdbot models status --check
|
||||
```
|
||||
|
||||
Optional ops scripts (systemd/Termux) are documented here:
|
||||
[/automation/auth-monitoring](/automation/auth-monitoring)
|
||||
|
||||
`clawdbot models status` loads Claude Code credentials into Clawdbot’s
|
||||
`auth-profiles.json` and shows expiry (warns within 24h by default).
|
||||
`clawdbot doctor` also performs the sync when it runs.
|
||||
|
||||
> `claude setup-token` requires an interactive TTY.
|
||||
|
||||
## Checking model auth status
|
||||
|
||||
```bash
|
||||
clawdbot models status
|
||||
clawdbot doctor
|
||||
```
|
||||
|
||||
## How sync works
|
||||
|
||||
1. **Claude Code** stores credentials in `~/.claude/.credentials.json` (or
|
||||
Keychain on macOS).
|
||||
2. **Clawdbot** syncs those into
|
||||
`~/.clawdbot/agents/<agentId>/agent/auth-profiles.json` when the auth store is
|
||||
loaded.
|
||||
3. OAuth refresh happens automatically on use if a token is expired.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### “No credentials found”
|
||||
|
||||
If the Anthropic OAuth profile is missing, run `claude setup-token` on the
|
||||
**gateway host**, then re-check:
|
||||
|
||||
```bash
|
||||
clawdbot models status
|
||||
```
|
||||
|
||||
### Token expiring/expired
|
||||
|
||||
Run `clawdbot models status` to confirm which profile is expiring. If the profile
|
||||
is `anthropic:claude-cli`, rerun `claude setup-token`.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Claude Max or Pro subscription (for `claude setup-token`)
|
||||
- Claude Code CLI installed (`claude` command available)
|
||||
@@ -6,24 +6,29 @@ read_when:
|
||||
---
|
||||
# Bonjour / mDNS discovery
|
||||
|
||||
Clawdbot uses Bonjour (mDNS / DNS-SD) as a **LAN-only convenience** to discover a running Gateway bridge transport. It is best-effort and does **not** replace SSH or Tailnet-based connectivity.
|
||||
Clawdbot uses Bonjour (mDNS / DNS‑SD) as a **LAN‑only convenience** to discover
|
||||
an active Gateway bridge. It is best‑effort and does **not** replace SSH or
|
||||
Tailnet-based connectivity.
|
||||
|
||||
## Wide-Area Bonjour (Unicast DNS-SD) over Tailscale
|
||||
## Wide‑area Bonjour (Unicast DNS‑SD) over Tailscale
|
||||
|
||||
If you want iOS node auto-discovery while the Gateway is on another network (e.g. Vienna ⇄ London), you can keep the `NWBrowser` UX but switch discovery from multicast mDNS (`local.`) to **unicast DNS-SD** (“Wide-Area Bonjour”) over Tailscale.
|
||||
If the node and gateway are on different networks, multicast mDNS won’t cross the
|
||||
boundary. You can keep the same discovery UX by switching to **unicast DNS‑SD**
|
||||
("Wide‑Area Bonjour") over Tailscale.
|
||||
|
||||
High level:
|
||||
High‑level steps:
|
||||
|
||||
1) Run a DNS server on the gateway host (reachable via tailnet IP).
|
||||
2) Publish DNS-SD records for `_clawdbot-bridge._tcp` in a dedicated zone (example: `clawdbot.internal.`).
|
||||
3) Configure Tailscale **split DNS** so `clawdbot.internal` resolves via that DNS server for clients (including iOS).
|
||||
1) Run a DNS server on the gateway host (reachable over Tailnet).
|
||||
2) Publish DNS‑SD records for `_clawdbot-bridge._tcp` under a dedicated zone
|
||||
(example: `clawdbot.internal.`).
|
||||
3) Configure Tailscale **split DNS** so `clawdbot.internal` resolves via that
|
||||
DNS server for clients (including iOS).
|
||||
|
||||
Clawdbot standardizes on the discovery domain `clawdbot.internal.` for this mode. iOS/Android nodes browse both `local.` and `clawdbot.internal.` automatically (no per-device knob).
|
||||
Clawdbot standardizes on `clawdbot.internal.` for this mode. iOS/Android nodes
|
||||
browse both `local.` and `clawdbot.internal.` automatically.
|
||||
|
||||
### Gateway config (recommended)
|
||||
|
||||
On the gateway host (the machine running the Gateway bridge), add to `~/.clawdbot/clawdbot.json` (JSON5):
|
||||
|
||||
```json5
|
||||
{
|
||||
bridge: { bind: "tailnet" }, // tailnet-only (recommended)
|
||||
@@ -31,21 +36,17 @@ On the gateway host (the machine running the Gateway bridge), add to `~/.clawdbo
|
||||
}
|
||||
```
|
||||
|
||||
### One-time DNS server setup (gateway host)
|
||||
|
||||
On the gateway host (macOS), run:
|
||||
### One‑time DNS server setup (gateway host)
|
||||
|
||||
```bash
|
||||
clawdbot dns setup --apply
|
||||
```
|
||||
|
||||
This installs CoreDNS and configures it to:
|
||||
- listen on port 53 **only** on the gateway’s Tailscale interface IPs
|
||||
- serve the zone `clawdbot.internal.` from the gateway-owned zone file `~/.clawdbot/dns/clawdbot.internal.db`
|
||||
- listen on port 53 only on the gateway’s Tailscale interfaces
|
||||
- serve `clawdbot.internal.` from `~/.clawdbot/dns/clawdbot.internal.db`
|
||||
|
||||
The Gateway writes/updates that zone file when `discovery.wideArea.enabled` is true.
|
||||
|
||||
Validate from any tailnet-connected machine:
|
||||
Validate from a tailnet‑connected machine:
|
||||
|
||||
```bash
|
||||
dns-sd -B _clawdbot-bridge._tcp clawdbot.internal.
|
||||
@@ -59,99 +60,102 @@ In the Tailscale admin console:
|
||||
- Add a nameserver pointing at the gateway’s tailnet IP (UDP/TCP 53).
|
||||
- Add split DNS so the domain `clawdbot.internal` uses that nameserver.
|
||||
|
||||
Once clients accept tailnet DNS, iOS nodes can browse `_clawdbot-bridge._tcp` in `clawdbot.internal.` without multicast.
|
||||
Wide-area beacons also include `tailnetDns` (when available) so the macOS app can auto-fill SSH targets off-LAN.
|
||||
Once clients accept tailnet DNS, iOS nodes can browse
|
||||
`_clawdbot-bridge._tcp` in `clawdbot.internal.` without multicast.
|
||||
|
||||
### Bridge listener security (recommended)
|
||||
|
||||
The bridge port (default `18790`) is a plain TCP service. By default it binds to `0.0.0.0`, which makes it reachable from *any* interface on the gateway machine (LAN/Wi‑Fi/Tailscale).
|
||||
|
||||
For a tailnet-only setup, bind it to the Tailscale IP instead:
|
||||
The bridge port (default `18790`) is a plain TCP service. By default it binds to
|
||||
`0.0.0.0`, which makes it reachable from any interface on the gateway host.
|
||||
|
||||
For tailnet‑only setups:
|
||||
- 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).
|
||||
|
||||
This keeps the bridge reachable only from devices on your tailnet (while still listening on loopback for local/SSH port-forwards).
|
||||
- Restart the Gateway (or restart the macOS menubar app).
|
||||
|
||||
## What advertises
|
||||
|
||||
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)
|
||||
Only the Gateway (when the **bridge is enabled**) advertises `_clawdbot-bridge._tcp`.
|
||||
|
||||
## Service types
|
||||
|
||||
- `_clawdbot-bridge._tcp` — bridge transport beacon (used by macOS/iOS/Android nodes).
|
||||
|
||||
## TXT keys (non-secret hints)
|
||||
## TXT keys (non‑secret hints)
|
||||
|
||||
The Gateway advertises small non-secret hints to make UI flows convenient:
|
||||
The Gateway advertises small non‑secret hints to make UI flows convenient:
|
||||
|
||||
- `role=gateway`
|
||||
- `displayName=<friendly name>`
|
||||
- `lanHost=<hostname>.local`
|
||||
- `sshPort=<port>` (defaults to 22 when not overridden)
|
||||
- `gatewayPort=<port>` (informational; the Gateway WS is typically loopback-only)
|
||||
- `gatewayPort=<port>` (informational; Gateway WS is usually loopback‑only)
|
||||
- `bridgePort=<port>` (only when bridge is enabled)
|
||||
- `canvasPort=<port>` (only when the canvas host is enabled + reachable; default `18793`; serves `/__clawdbot__/canvas/`)
|
||||
- `cliPath=<path>` (optional; absolute path to a runnable `clawdbot` entrypoint or binary)
|
||||
- `tailnetDns=<magicdns>` (optional hint; auto-detected from Tailscale when available; may be absent)
|
||||
- `canvasPort=<port>` (only when the canvas host is enabled; default `18793`)
|
||||
- `sshPort=<port>` (defaults to 22 when not overridden)
|
||||
- `transport=bridge`
|
||||
- `cliPath=<path>` (optional; absolute path to a runnable `clawdbot` entrypoint)
|
||||
- `tailnetDns=<magicdns>` (optional hint when Tailnet is available)
|
||||
|
||||
## Debugging on macOS
|
||||
|
||||
Useful built-in tools:
|
||||
Useful built‑in tools:
|
||||
|
||||
- Browse instances:
|
||||
- `dns-sd -B _clawdbot-bridge._tcp local.`
|
||||
```bash
|
||||
dns-sd -B _clawdbot-bridge._tcp local.
|
||||
```
|
||||
- Resolve one instance (replace `<instance>`):
|
||||
- `dns-sd -L "<instance>" _clawdbot-bridge._tcp local.`
|
||||
```bash
|
||||
dns-sd -L "<instance>" _clawdbot-bridge._tcp local.
|
||||
```
|
||||
|
||||
If browsing shows instances but resolving fails, you’re usually hitting a LAN policy / multicast issue.
|
||||
If browsing works but resolving fails, you’re usually hitting a LAN policy or
|
||||
mDNS resolver issue.
|
||||
|
||||
## Debugging in Gateway logs
|
||||
|
||||
The Gateway writes a rolling log file (printed on startup as `gateway log file: ...`).
|
||||
The Gateway writes a rolling log file (printed on startup as
|
||||
`gateway log file: ...`). Look for `bonjour:` lines, especially:
|
||||
|
||||
Look for `bonjour:` lines, especially:
|
||||
|
||||
- `bonjour: advertise failed ...` (probing/announce failure)
|
||||
- `bonjour: advertise failed ...`
|
||||
- `bonjour: ... name conflict resolved` / `hostname conflict resolved`
|
||||
- `bonjour: watchdog detected non-announced service; attempting re-advertise ...` (self-heal attempt after sleep/interface churn)
|
||||
- `bonjour: watchdog detected non-announced service ...`
|
||||
|
||||
## Debugging on iOS node
|
||||
|
||||
The iOS node app discovers bridges via `NWBrowser` browsing `_clawdbot-bridge._tcp`.
|
||||
The iOS node uses `NWBrowser` to discover `_clawdbot-bridge._tcp`.
|
||||
|
||||
To capture what the browser is doing:
|
||||
To capture logs:
|
||||
- Settings → Bridge → Advanced → **Discovery Debug Logs**
|
||||
- Settings → Bridge → Advanced → **Discovery Logs** → reproduce → **Copy**
|
||||
|
||||
- Settings → Bridge → Advanced → enable **Discovery Debug Logs**
|
||||
- Settings → Bridge → Advanced → open **Discovery Logs** → reproduce the “Searching…” / “No bridges found” case → **Copy**
|
||||
|
||||
The log includes browser state transitions (`ready`, `waiting`, `failed`, `cancelled`) and result-set changes (added/removed counts).
|
||||
The log includes browser state transitions and result‑set changes.
|
||||
|
||||
## Common failure modes
|
||||
|
||||
- **Bonjour doesn’t cross networks**: London/Vienna style setups require Tailnet (MagicDNS/IP) or SSH.
|
||||
- **Multicast blocked**: some Wi‑Fi networks (enterprise/hotels) disable mDNS; expect “no results”.
|
||||
- **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).
|
||||
- **Bonjour doesn’t cross networks**: use Tailnet or SSH.
|
||||
- **Multicast blocked**: some Wi‑Fi networks disable mDNS.
|
||||
- **Sleep / interface churn**: macOS may temporarily drop mDNS results; retry.
|
||||
- **Browse works but resolve fails**: keep machine names simple (avoid emojis or
|
||||
punctuation), then restart the Gateway. The bridge instance name derives from
|
||||
the host name, so overly complex names can confuse some resolvers.
|
||||
|
||||
## Escaped instance names (`\\032`)
|
||||
Bonjour/DNS-SD often escapes bytes in service instance names as decimal `\\DDD` sequences (e.g. spaces become `\\032`).
|
||||
## Escaped instance names (`\032`)
|
||||
|
||||
Bonjour/DNS‑SD often escapes bytes in service instance names as decimal `\DDD`
|
||||
sequences (e.g. spaces become `\032`).
|
||||
|
||||
- This is normal at the protocol level.
|
||||
- UIs should decode for display (iOS uses `BonjourEscapes.decode` in `apps/shared/ClawdbotKit`).
|
||||
- UIs should decode for display (iOS uses `BonjourEscapes.decode`).
|
||||
|
||||
## Disabling / configuration
|
||||
|
||||
- `CLAWDBOT_DISABLE_BONJOUR=1` disables advertising.
|
||||
- `CLAWDBOT_BRIDGE_ENABLED=0` disables the bridge listener (and therefore the bridge beacon).
|
||||
- `bridge.bind` / `bridge.port` in `~/.clawdbot/clawdbot.json` control bridge bind/port (preferred).
|
||||
- `CLAWDBOT_BRIDGE_HOST` / `CLAWDBOT_BRIDGE_PORT` still work as a back-compat override when `bridge.bind` / `bridge.port` are not set.
|
||||
- `CLAWDBOT_SSH_PORT` overrides the SSH port advertised in `_clawdbot-bridge._tcp`.
|
||||
- `CLAWDBOT_TAILNET_DNS` publishes a `tailnetDns` hint (MagicDNS) in `_clawdbot-bridge._tcp`. If unset, the gateway auto-detects Tailscale and publishes the MagicDNS name when possible.
|
||||
- `CLAWDBOT_BRIDGE_ENABLED=0` disables the bridge listener (and the bridge beacon).
|
||||
- `bridge.bind` / `bridge.port` in `~/.clawdbot/clawdbot.json` control bridge bind/port.
|
||||
- `CLAWDBOT_BRIDGE_HOST` / `CLAWDBOT_BRIDGE_PORT` still work as back‑compat overrides.
|
||||
- `CLAWDBOT_SSH_PORT` overrides the SSH port advertised in TXT.
|
||||
- `CLAWDBOT_TAILNET_DNS` publishes a MagicDNS hint in TXT.
|
||||
- `CLAWDBOT_CLI_PATH` overrides the advertised CLI path.
|
||||
|
||||
## Related docs
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
summary: "Schema-accurate configuration examples for common Clawdbot setups"
|
||||
read_when:
|
||||
- Learning how to configure clawdbot
|
||||
- Learning how to configure Clawdbot
|
||||
- Looking for configuration examples
|
||||
- Setting up clawdbot for the first time
|
||||
- Setting up Clawdbot for the first time
|
||||
---
|
||||
# Configuration Examples
|
||||
|
||||
@@ -48,6 +48,10 @@ Save to `~/.clawdbot/clawdbot.json` and you can DM the bot from that number.
|
||||
{
|
||||
// Environment + shell
|
||||
env: {
|
||||
OPENROUTER_API_KEY: "sk-or-...",
|
||||
vars: {
|
||||
GROQ_API_KEY: "gsk-..."
|
||||
},
|
||||
shellEnv: {
|
||||
enabled: true,
|
||||
timeoutMs: 15000
|
||||
|
||||
@@ -91,6 +91,22 @@ Additionally, it loads:
|
||||
|
||||
Neither `.env` file overrides existing env vars.
|
||||
|
||||
You can also provide inline env vars in config. These are only applied if the
|
||||
process env is missing the key (same non-overriding rule):
|
||||
|
||||
```json5
|
||||
{
|
||||
env: {
|
||||
OPENROUTER_API_KEY: "sk-or-...",
|
||||
vars: {
|
||||
GROQ_API_KEY: "gsk-..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See [/environment](/environment) for full precedence and sources.
|
||||
|
||||
### `env.shellEnv` (optional)
|
||||
|
||||
Opt-in convenience: if enabled and none of the expected keys are set yet, CLAWDBOT runs your login shell and imports only the missing expected keys (never overrides).
|
||||
@@ -134,7 +150,9 @@ Overrides:
|
||||
On first use, Clawdbot imports `oauth.json` entries into `auth-profiles.json`.
|
||||
|
||||
Clawdbot also auto-syncs OAuth tokens from external CLIs into `auth-profiles.json` (when present on the gateway host):
|
||||
- `~/.claude/.credentials.json` (Claude Code) → `anthropic:claude-cli`
|
||||
- Claude Code → `anthropic:claude-cli`
|
||||
- macOS: Keychain item "Claude Code-credentials" (choose "Always Allow" to avoid launchd prompts)
|
||||
- Linux/Windows: `~/.claude/.credentials.json`
|
||||
- `~/.codex/auth.json` (Codex CLI) → `openai-codex:codex-cli`
|
||||
|
||||
### `auth`
|
||||
@@ -303,6 +321,7 @@ Group messages default to **require mention** (either metadata mention or regex
|
||||
- **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`).
|
||||
- Per-agent override: `routing.agents.<agentId>.mentionPatterns` (useful when multiple agents share a group).
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -315,6 +334,18 @@ Group messages default to **require mention** (either metadata mention or regex
|
||||
}
|
||||
```
|
||||
|
||||
Per-agent override (takes precedence when set, even `[]`):
|
||||
```json5
|
||||
{
|
||||
routing: {
|
||||
agents: {
|
||||
work: { mentionPatterns: ["@workbot", "\\+15555550123"] },
|
||||
personal: { mentionPatterns: ["@homebot", "\\+15555550999"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
To respond **only** to specific text triggers (ignoring native @-mentions):
|
||||
@@ -560,6 +591,7 @@ Controls how chat commands are enabled across connectors.
|
||||
commands: {
|
||||
native: false, // register native commands when supported
|
||||
text: true, // parse slash commands in chat messages
|
||||
restart: false, // allow /restart + gateway restart tool
|
||||
useAccessGroups: true // enforce access-group allowlists/policies for commands
|
||||
}
|
||||
}
|
||||
@@ -570,6 +602,7 @@ Notes:
|
||||
- `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.restart: true` enables `/restart` and the gateway tool restart action.
|
||||
- `commands.useAccessGroups: false` allows commands to bypass access-group allowlists/policies.
|
||||
|
||||
### `web` (WhatsApp web provider)
|
||||
@@ -994,7 +1027,7 @@ If you configure the same alias name (case-insensitive) yourself, your value win
|
||||
}
|
||||
```
|
||||
|
||||
#### `agent.contextPruning` (opt-in tool-result pruning)
|
||||
#### `agent.contextPruning` (tool-result pruning)
|
||||
|
||||
`agent.contextPruning` prunes **old tool results** from the in-memory context right before a request is sent to the LLM.
|
||||
It does **not** modify the session history on disk (`*.jsonl` remains complete).
|
||||
@@ -1025,7 +1058,7 @@ Notes / current limitations:
|
||||
- If the session doesn’t contain at least `keepLastAssistants` assistant messages yet, pruning is skipped.
|
||||
- In `aggressive` mode, `hardClear.enabled` is ignored (eligible tool results are always replaced with `hardClear.placeholder`).
|
||||
|
||||
Example (minimal):
|
||||
Default (adaptive):
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
@@ -1036,6 +1069,17 @@ Example (minimal):
|
||||
}
|
||||
```
|
||||
|
||||
To disable:
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
contextPruning: {
|
||||
mode: "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Defaults (when `mode` is `"adaptive"` or `"aggressive"`):
|
||||
- `keepLastAssistants`: `3`
|
||||
- `softTrimRatio`: `0.3` (adaptive only)
|
||||
@@ -1167,6 +1211,12 @@ Example:
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `agent.elevated` is **global** (not per-agent). Availability is based on sender allowlists.
|
||||
- `/elevated on|off` stores state per session key; inline directives apply to a single message.
|
||||
- Elevated `bash` runs on the host and bypasses sandboxing.
|
||||
- Tool policy still applies; if `bash` is denied, elevated cannot be used.
|
||||
|
||||
`agent.maxConcurrent` sets the maximum number of embedded agent runs that can
|
||||
execute in parallel across sessions. Each session is still serialized (one run
|
||||
per session key at a time). Default: 1.
|
||||
@@ -1176,6 +1226,8 @@ per session key at a time). Default: 1.
|
||||
Optional **Docker sandboxing** for the embedded agent. Intended for non-main
|
||||
sessions so they cannot access your host system.
|
||||
|
||||
Details: [Sandboxing](/gateway/sandboxing)
|
||||
|
||||
Defaults (if enabled):
|
||||
- scope: `"agent"` (one container + workspace per agent)
|
||||
- Debian bookworm-slim based image
|
||||
@@ -1335,8 +1387,8 @@ Notes:
|
||||
- `z.ai/*` and `z-ai/*` are accepted aliases and normalize to `zai/*`.
|
||||
- If `ZAI_API_KEY` is missing, requests to `zai/*` will fail with an auth error at runtime.
|
||||
- Example error: `No API key found for provider "zai".`
|
||||
- Z.AI’s general API endpoint is `https://api.z.ai/api/paas/v4`. The GLM Coding
|
||||
Plan uses the dedicated Coding endpoint `https://api.z.ai/api/coding/paas/v4`.
|
||||
- Z.AI’s general API endpoint is `https://api.z.ai/api/paas/v4`. GLM coding
|
||||
requests use the dedicated Coding endpoint `https://api.z.ai/api/coding/paas/v4`.
|
||||
The built-in `zai` provider uses the Coding endpoint. If you need the general
|
||||
endpoint, define a custom provider in `models.providers` with the base URL
|
||||
override (see the custom providers section above).
|
||||
@@ -1423,6 +1475,7 @@ 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`.
|
||||
- Sandbox note: `agent.sandbox.mode: "non-main"` uses this key to detect the main session. Any session key that does not match `mainKey` (groups/channels) is sandboxed.
|
||||
- `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.
|
||||
|
||||
@@ -44,7 +44,7 @@ Target direction:
|
||||
|
||||
Troubleshooting and beacon details: [`docs/bonjour.md`](/gateway/bonjour).
|
||||
|
||||
#### Current implementation
|
||||
#### Service beacon details
|
||||
|
||||
- Service types:
|
||||
- `_clawdbot-bridge._tcp` (bridge transport beacon)
|
||||
@@ -98,15 +98,8 @@ The gateway is the source of truth for node/client admission.
|
||||
- scopes/ACLs (bridge is not a raw proxy to every gateway method)
|
||||
- rate limits
|
||||
|
||||
## Where the code lives (target architecture)
|
||||
## Responsibilities by component
|
||||
|
||||
- Node gateway:
|
||||
- advertises discovery beacons (Bonjour)
|
||||
- owns pairing storage + decisions
|
||||
- runs the bridge listener (direct transport)
|
||||
- macOS app:
|
||||
- UI for picking a gateway, showing pairing prompts, and troubleshooting
|
||||
- SSH tunneling only for the fallback path
|
||||
- iOS node:
|
||||
- browses Bonjour (LAN) as a convenience only
|
||||
- uses direct transport + pairing to connect to the gateway
|
||||
- **Gateway**: advertises discovery beacons, owns pairing decisions, runs the bridge listener.
|
||||
- **macOS app**: helps you pick a gateway, shows pairing prompts, and uses SSH only as a fallback.
|
||||
- **iOS/Android nodes**: browse Bonjour as a convenience and connect via the paired bridge.
|
||||
|
||||
@@ -23,11 +23,24 @@ clawdbot doctor --yes
|
||||
|
||||
Accept defaults without prompting (including restart/service/sandbox repair steps when applicable).
|
||||
|
||||
```bash
|
||||
clawdbot doctor --repair
|
||||
```
|
||||
|
||||
Apply recommended repairs without prompting (repairs + restarts where safe).
|
||||
|
||||
```bash
|
||||
clawdbot doctor --repair --force
|
||||
```
|
||||
|
||||
Apply aggressive repairs too (overwrites custom supervisor configs).
|
||||
|
||||
```bash
|
||||
clawdbot doctor --non-interactive
|
||||
```
|
||||
|
||||
Run without prompts and only apply safe migrations (config normalization + on-disk state moves). Skips restart/service/sandbox actions that require human confirmation.
|
||||
Legacy state migrations run automatically when detected.
|
||||
|
||||
```bash
|
||||
clawdbot doctor --deep
|
||||
@@ -47,10 +60,14 @@ cat ~/.clawdbot/clawdbot.json
|
||||
- Legacy config migration and normalization.
|
||||
- Legacy on-disk state migration (sessions/agent dir/WhatsApp auth).
|
||||
- State integrity and permissions checks (sessions, transcripts, state dir).
|
||||
- Config file permission checks (chmod 600) when running locally.
|
||||
- Model auth health: checks OAuth expiry and can refresh expiring tokens.
|
||||
- Legacy workspace dir detection (`~/clawdis`, `~/clawdbot`).
|
||||
- Sandbox image repair when sandboxing is enabled.
|
||||
- Legacy service migration and extra gateway detection.
|
||||
- Gateway runtime checks (service installed but not running; cached launchd label).
|
||||
- Supervisor config audit (launchd/systemd/schtasks) with optional repair.
|
||||
- Gateway runtime best-practice checks (Node vs Bun, version-manager paths).
|
||||
- Gateway port collision diagnostics (default `18789`).
|
||||
- Security warnings for open DM policies.
|
||||
- systemd linger check on Linux.
|
||||
@@ -116,44 +133,73 @@ Doctor checks:
|
||||
split between installs).
|
||||
- **Remote mode reminder**: if `gateway.mode=remote`, doctor reminds you to run
|
||||
it on the remote host (the state lives there).
|
||||
- **Config file permissions**: warns if `~/.clawdbot/clawdbot.json` is
|
||||
group/world readable and offers to tighten to `600`.
|
||||
|
||||
### 5) Sandbox image repair
|
||||
### 5) Model auth health (OAuth expiry)
|
||||
Doctor inspects OAuth profiles in the auth store, warns when tokens are
|
||||
expiring/expired, and can refresh them when safe. If the Anthropic Claude Code
|
||||
profile is stale, it suggests `claude setup-token` on the gateway host.
|
||||
Refresh prompts only appear when running interactively (TTY); `--non-interactive`
|
||||
skips refresh attempts.
|
||||
|
||||
### 6) Sandbox image repair
|
||||
When sandboxing is enabled, doctor checks Docker images and offers to build or
|
||||
switch to legacy names if the current image is missing.
|
||||
|
||||
### 6) Gateway service migrations and cleanup hints
|
||||
### 7) Gateway service migrations and cleanup hints
|
||||
Doctor detects legacy Clawdis gateway services (launchd/systemd/schtasks) and
|
||||
offers to remove them and install the Clawdbot service using the current gateway
|
||||
port. It can also scan for extra gateway-like services and print cleanup hints
|
||||
to ensure only one gateway runs per machine.
|
||||
|
||||
### 7) Security warnings
|
||||
### 8) Security warnings
|
||||
Doctor emits warnings when a provider is open to DMs without an allowlist, or
|
||||
when a policy is configured in a dangerous way.
|
||||
|
||||
### 8) systemd linger (Linux)
|
||||
### 9) systemd linger (Linux)
|
||||
If running as a systemd user service, doctor ensures lingering is enabled so the
|
||||
gateway stays alive after logout.
|
||||
|
||||
### 9) Skills status
|
||||
### 10) Skills status
|
||||
Doctor prints a quick summary of eligible/missing/blocked skills for the current
|
||||
workspace.
|
||||
|
||||
### 10) Gateway health check + restart
|
||||
### 11) Gateway health check + restart
|
||||
Doctor runs a health check and offers to restart the gateway when it looks
|
||||
unhealthy.
|
||||
|
||||
### 11) Gateway runtime + port diagnostics
|
||||
### 12) Supervisor config audit + repair
|
||||
Doctor checks the installed supervisor config (launchd/systemd/schtasks) for
|
||||
missing or outdated defaults (e.g., systemd network-online dependencies and
|
||||
restart delay). When it finds a mismatch, it recommends an update and can
|
||||
rewrite the service file/task to the current defaults.
|
||||
|
||||
Notes:
|
||||
- `clawdbot doctor` prompts before rewriting supervisor config.
|
||||
- `clawdbot doctor --yes` accepts the default repair prompts.
|
||||
- `clawdbot doctor --repair` applies recommended fixes without prompts.
|
||||
- `clawdbot doctor --repair --force` overwrites custom supervisor configs.
|
||||
- You can always force a full rewrite via `clawdbot daemon install --force`.
|
||||
|
||||
### 13) Gateway runtime + port diagnostics
|
||||
Doctor inspects the daemon runtime (PID, last exit status) and warns when the
|
||||
service is installed but not actually running. It also checks for port collisions
|
||||
on the gateway port (default `18789`) and reports likely causes (gateway already
|
||||
running, SSH tunnel).
|
||||
|
||||
### 12) Config write + wizard metadata
|
||||
### 14) Gateway runtime best practices
|
||||
Doctor warns when the gateway service runs on Bun or a version-managed Node path
|
||||
(`nvm`, `fnm`, `volta`, `asdf`, etc.). WhatsApp + Telegram providers require Node,
|
||||
and version-manager paths can break after upgrades because the daemon does not
|
||||
load your shell init. Doctor offers to migrate to a system Node install when
|
||||
available (Homebrew/apt/choco).
|
||||
|
||||
### 15) Config write + wizard metadata
|
||||
Doctor persists any config changes and stamps wizard metadata to record the
|
||||
doctor run.
|
||||
|
||||
### 13) Workspace tips (backup + memory system)
|
||||
### 16) Workspace tips (backup + memory system)
|
||||
Doctor suggests a workspace memory system when missing and prints a backup tip
|
||||
if the workspace is not already under git.
|
||||
|
||||
|
||||
@@ -1,47 +1,33 @@
|
||||
---
|
||||
summary: "Plan for heartbeat polling messages and notification rules"
|
||||
summary: "Heartbeat polling messages and notification rules"
|
||||
read_when:
|
||||
- Adjusting heartbeat cadence or messaging
|
||||
---
|
||||
# Heartbeat (Gateway)
|
||||
|
||||
Heartbeat runs periodic agent turns in the **main session** so the model can
|
||||
surface anything that needs attention without spamming the user.
|
||||
Heartbeat runs **periodic agent turns** in the main session so the model can
|
||||
surface anything that needs attention without spamming you.
|
||||
|
||||
## Defaults
|
||||
- Interval: `30m` (set `agent.heartbeat.every` to change, `0m` disables).
|
||||
|
||||
- Interval: `30m` (set `agent.heartbeat.every`; use `0m` to disable).
|
||||
- Prompt body (configurable via `agent.heartbeat.prompt`):
|
||||
`Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.`
|
||||
- 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.
|
||||
- The heartbeat prompt is sent **verbatim** as the user message. The system
|
||||
prompt includes a “Heartbeat” section and the run is flagged internally.
|
||||
|
||||
## Prompt contract
|
||||
- If nothing needs attention, the model should reply `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).
|
||||
- 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.
|
||||
## Response contract
|
||||
|
||||
## Prompt overrides
|
||||
- Overriding `agent.heartbeat.prompt` **replaces** the default body. Nothing is
|
||||
merged for you.
|
||||
- If you still want `HEARTBEAT.md` instructions, keep a line like
|
||||
`Read HEARTBEAT.md if exists` in your custom prompt.
|
||||
- `HEARTBEAT_OK` handling stays the same; changing the prompt won’t break acks.
|
||||
- If nothing needs attention, reply with **`HEARTBEAT_OK`**.
|
||||
- During heartbeat runs, Clawdbot treats `HEARTBEAT_OK` as an ack when it appears
|
||||
at the **start or end** of the reply. The token is stripped and the reply is
|
||||
dropped if the remaining content is **≤ `ackMaxChars`** (default: 30).
|
||||
- If `HEARTBEAT_OK` appears in the **middle** of a reply, it is not treated
|
||||
specially.
|
||||
- For alerts, **do not** include `HEARTBEAT_OK`; return only the alert text.
|
||||
|
||||
### 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
|
||||
Outside heartbeats, stray `HEARTBEAT_OK` at the start/end of a message is stripped
|
||||
and logged; a message that is only `HEARTBEAT_OK` is dropped.
|
||||
|
||||
## Config
|
||||
|
||||
@@ -51,8 +37,8 @@ and final replies:
|
||||
heartbeat: {
|
||||
every: "30m", // default: 30m (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)
|
||||
target: "last", // last | whatsapp | telegram | discord | slack | signal | imessage | none
|
||||
to: "+15551234567", // optional provider-specific override
|
||||
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
|
||||
}
|
||||
@@ -60,47 +46,45 @@ and final replies:
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
- `every`: heartbeat interval (duration string; default unit minutes). Default:
|
||||
`30m`. 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`).
|
||||
- `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).
|
||||
### Field notes
|
||||
|
||||
## Cost awareness
|
||||
Heartbeats run full agent turns. Shorter intervals burn more tokens. Be
|
||||
intentional about `every`, keep `HEARTBEAT.md` tiny, and consider a cheaper
|
||||
`model` or `target: "none"` if you only want internal state updates.
|
||||
- `every`: heartbeat interval (duration string; default unit = minutes).
|
||||
- `model`: optional model override for heartbeat runs (`provider/model`).
|
||||
- `target`:
|
||||
- `last` (default): deliver to the last used external provider.
|
||||
- explicit provider: `whatsapp` / `telegram` / `discord` / `slack` / `signal` / `imessage`.
|
||||
- `none`: run the heartbeat but **do not deliver** externally.
|
||||
- `to`: optional recipient override (E.164 for WhatsApp, chat id for Telegram, etc.).
|
||||
- `prompt`: overrides the default prompt body (not merged).
|
||||
- `ackMaxChars`: max chars allowed after `HEARTBEAT_OK` before delivery.
|
||||
|
||||
## Delivery behavior
|
||||
|
||||
- Heartbeats run in the **main session** (`main`, or `global` when scope is global).
|
||||
- If the main queue is busy, the heartbeat is skipped and retried later.
|
||||
- If `target` resolves to no external destination, the run still happens but no
|
||||
outbound message is sent.
|
||||
- Heartbeat-only replies do **not** keep the session alive; the last `updatedAt`
|
||||
is restored so idle expiry behaves normally.
|
||||
|
||||
## 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.
|
||||
|
||||
## Behavior
|
||||
- Runs in the main session (`main`, or `global` when scope is global).
|
||||
- Uses the main lane queue; if requests are in flight, the wake is retried.
|
||||
- Empty output or `HEARTBEAT_OK` is treated as “ok” and does **not** keep the
|
||||
session alive (`updatedAt` is restored).
|
||||
- If `target` resolves to no external destination (no last route or `none`), the
|
||||
heartbeat still runs but no outbound message is sent.
|
||||
## Manual wake (on-demand)
|
||||
|
||||
## 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).
|
||||
- Scheduled routines (use [Cron jobs](/automation/cron-jobs) when you
|
||||
need exact schedules or isolated runs).
|
||||
You can enqueue a system event and trigger an immediate heartbeat with:
|
||||
|
||||
## Wake hook
|
||||
- The gateway exposes a heartbeat wake hook so cron/jobs/webhooks can request an
|
||||
immediate run (`requestHeartbeatNow`).
|
||||
- `wake` endpoints should enqueue system events and optionally trigger a wake; the
|
||||
heartbeat runner picks those up on the next tick or immediately.
|
||||
```bash
|
||||
clawdbot wake --text "Check for urgent follow-ups" --mode now
|
||||
```
|
||||
|
||||
Use `--mode next-heartbeat` to wait for the next scheduled tick.
|
||||
|
||||
## Cost awareness
|
||||
|
||||
Heartbeats run full agent turns. Shorter intervals burn more tokens. Keep
|
||||
`HEARTBEAT.md` small and consider a cheaper `model` or `target: "none"` if you
|
||||
only want internal state updates.
|
||||
|
||||
@@ -127,7 +127,9 @@ See also: [`docs/presence.md`](/concepts/presence) for how presence is produced/
|
||||
## 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)).
|
||||
- Protocol definitions are the source of truth; regenerate schema/models with:
|
||||
- `pnpm protocol:gen`
|
||||
- `pnpm protocol:gen: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.
|
||||
@@ -156,6 +158,8 @@ See also: [`docs/presence.md`](/concepts/presence) for how presence is produced/
|
||||
- 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).
|
||||
- `clawdbot daemon install` writes `~/Library/LaunchAgents/com.clawdbot.gateway.plist`.
|
||||
- `clawdbot doctor` audits the LaunchAgent config and can update it to current defaults.
|
||||
|
||||
## Daemon management (CLI)
|
||||
|
||||
@@ -189,6 +193,14 @@ Bundled mac app:
|
||||
- `launchctl` only works if the LaunchAgent is installed; otherwise use `clawdbot daemon install` first.
|
||||
|
||||
## Supervision (systemd user unit)
|
||||
Clawdbot installs a **systemd user service** by default on Linux/WSL2. We
|
||||
recommend user services for single-user machines (simpler env, per-user config).
|
||||
Use a **system service** for multi-user or always-on servers (no lingering
|
||||
required, shared supervision).
|
||||
|
||||
`clawdbot daemon install` writes the user unit. `clawdbot doctor` audits the
|
||||
unit and can update it to match the current recommended defaults.
|
||||
|
||||
Create `~/.config/systemd/user/clawdbot-gateway.service`:
|
||||
```
|
||||
[Unit]
|
||||
@@ -242,7 +254,7 @@ Windows installs should use **WSL2** and follow the Linux systemd section above.
|
||||
|
||||
## CLI helpers
|
||||
- `clawdbot gateway health|status` — request health/status over the Gateway WS.
|
||||
- `clawdbot send --to <num> --message "hi" [--media ...]` — send via Gateway (idempotent for WhatsApp).
|
||||
- `clawdbot message send --to <num> --message "hi" [--media ...]` — send via Gateway (idempotent for WhatsApp).
|
||||
- `clawdbot agent --message "hi" --to <num>` — run an agent turn (waits for final by default).
|
||||
- `clawdbot gateway call <method> --params '{"k":"v"}'` — raw method invoker for debugging.
|
||||
- `clawdbot daemon stop|restart` — stop/restart the supervised gateway service (launchd/systemd).
|
||||
|
||||
@@ -7,15 +7,15 @@ read_when:
|
||||
|
||||
# Logging
|
||||
|
||||
For a user-facing overview (CLI + Control UI + config), see [/logging](/logging).
|
||||
|
||||
Clawdbot has two log “surfaces”:
|
||||
|
||||
- **Console output** (what you see in the terminal / Debug UI).
|
||||
- **File logs** (JSON lines) written by the internal logger.
|
||||
- **File logs** (JSON lines) written by the gateway logger.
|
||||
|
||||
## File-based logger
|
||||
|
||||
Clawdbot uses a file logger backed by `tslog` ([`src/logging.ts`](https://github.com/clawdbot/clawdbot/blob/main/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`:
|
||||
- `logging.file`
|
||||
@@ -40,9 +40,8 @@ clawdbot logs --follow
|
||||
|
||||
## Console capture
|
||||
|
||||
The CLI entrypoint enables console capture ([`src/index.ts`](https://github.com/clawdbot/clawdbot/blob/main/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.
|
||||
The CLI captures `console.log/info/warn/error/debug/trace` and writes them to file logs,
|
||||
while still printing to stdout/stderr.
|
||||
|
||||
You can tune console verbosity independently via:
|
||||
|
||||
@@ -94,13 +93,8 @@ clawdbot gateway --verbose --ws-log full
|
||||
|
||||
## Console formatting (subsystem logging)
|
||||
|
||||
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))
|
||||
|
||||
The console formatter is **TTY-aware** and prints consistent, prefixed lines.
|
||||
Subsystem loggers are created via `createSubsystemLogger("gateway")`.
|
||||
Subsystem loggers keep output grouped and scannable.
|
||||
|
||||
Behavior:
|
||||
|
||||
|
||||
@@ -7,103 +7,83 @@ read_when:
|
||||
---
|
||||
# Gateway-owned pairing (Option B)
|
||||
|
||||
Goal: The Gateway (`clawd`) is the **source of truth** for which nodes are allowed to join the network.
|
||||
|
||||
This enables:
|
||||
- Headless approval via terminal/CLI (no Swift UI required).
|
||||
- Optional macOS UI approval (Swift app is just a frontend).
|
||||
- One consistent membership store for iOS, mac nodes, future hardware nodes.
|
||||
In Gateway-owned pairing, the **Gateway** is the source of truth for which nodes
|
||||
are allowed to join. UIs (macOS app, future clients) are just frontends that
|
||||
approve or reject pending requests.
|
||||
|
||||
## Concepts
|
||||
- **Pending request**: a node asked to join; requires explicit approve/reject.
|
||||
- **Paired node**: node is allowed; gateway returns an auth token for subsequent connects.
|
||||
- **Bridge**: direct transport endpoint owned by the gateway. The bridge does not decide membership.
|
||||
|
||||
- **Pending request**: a node asked to join; requires approval.
|
||||
- **Paired node**: approved node with an issued auth token.
|
||||
- **Bridge**: transport endpoint only; it forwards requests but does not decide
|
||||
membership.
|
||||
|
||||
## How pairing works
|
||||
|
||||
1. A node connects to the bridge and requests pairing.
|
||||
2. The Gateway stores a **pending request** and emits `node.pair.requested`.
|
||||
3. You approve or reject the request (CLI or UI).
|
||||
4. On approval, the Gateway issues a **new token** (tokens are rotated on re‑pair).
|
||||
5. The node reconnects using the token and is now “paired”.
|
||||
|
||||
Pending requests expire automatically after **5 minutes**.
|
||||
|
||||
## CLI workflow (headless friendly)
|
||||
|
||||
```bash
|
||||
clawdbot nodes pending
|
||||
clawdbot nodes approve <requestId>
|
||||
clawdbot nodes reject <requestId>
|
||||
clawdbot nodes status
|
||||
clawdbot nodes rename --node <id|name|ip> --name "Living Room iPad"
|
||||
```
|
||||
|
||||
`nodes status` shows paired/connected nodes and their capabilities.
|
||||
|
||||
## 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.
|
||||
|
||||
### Events
|
||||
- `node.pair.requested`
|
||||
- Emitted whenever a new pending pairing request is created.
|
||||
- Payload:
|
||||
- `requestId` (string)
|
||||
- `nodeId` (string)
|
||||
- `displayName?` (string)
|
||||
- `platform?` (string)
|
||||
- `version?` (string)
|
||||
- `remoteIp?` (string)
|
||||
- `silent?` (boolean) — hint that the UI may attempt auto-approval
|
||||
- `ts` (ms since epoch)
|
||||
- `node.pair.resolved`
|
||||
- Emitted when a pending request is approved/rejected.
|
||||
- Payload:
|
||||
- `requestId` (string)
|
||||
- `nodeId` (string)
|
||||
- `decision` ("approved" | "rejected" | "expired")
|
||||
- `ts` (ms since epoch)
|
||||
Events:
|
||||
- `node.pair.requested` — emitted when a new pending request is created.
|
||||
- `node.pair.resolved` — emitted when a request is approved/rejected/expired.
|
||||
|
||||
### Methods
|
||||
- `node.pair.request`
|
||||
- Creates (or returns) a pending request.
|
||||
- Params: node metadata (same shape as `node.pair.requested` payload, minus `requestId`/`ts`).
|
||||
- Optional `silent` flag hints that the UI can attempt an SSH auto-approve before showing an alert.
|
||||
- Result:
|
||||
- `status` ("pending")
|
||||
- `created` (boolean) — whether this call created the pending request
|
||||
- `request` (pending request object), including `isRepair` when the node was already paired
|
||||
- Security: **never returns an existing token**. If a paired node “lost” its token, it must be approved again (token rotation).
|
||||
- `node.pair.list`
|
||||
- Returns:
|
||||
- `pending[]` (pending requests)
|
||||
- `paired[]` (paired node records)
|
||||
- `node.pair.approve`
|
||||
- Params: `{ requestId }`
|
||||
- Result: `{ requestId, node: { nodeId, token, ... } }`
|
||||
- Must be idempotent (first decision wins).
|
||||
- `node.pair.reject`
|
||||
- Params: `{ requestId }`
|
||||
- Result: `{ requestId, nodeId }`
|
||||
- `node.pair.verify`
|
||||
- Params: `{ nodeId, token }`
|
||||
- Result: `{ ok: boolean, node?: { nodeId, ... } }`
|
||||
|
||||
## CLI flows
|
||||
CLI must be able to fully operate without any GUI:
|
||||
- `clawdbot nodes pending`
|
||||
- `clawdbot nodes approve <requestId>`
|
||||
- `clawdbot nodes reject <requestId>`
|
||||
- `clawdbot nodes status` (paired nodes + connection status/capabilities)
|
||||
|
||||
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)
|
||||
- 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.
|
||||
|
||||
## Storage (private, local)
|
||||
Gateway stores the authoritative state under `~/.clawdbot/`:
|
||||
- `~/.clawdbot/nodes/paired.json`
|
||||
- `~/.clawdbot/nodes/pending.json` (or `~/.clawdbot/nodes/pending/*.json`)
|
||||
Methods:
|
||||
- `node.pair.request` — create or reuse a pending request.
|
||||
- `node.pair.list` — list pending + paired nodes.
|
||||
- `node.pair.approve` — approve a pending request (issues token).
|
||||
- `node.pair.reject` — reject a pending request.
|
||||
- `node.pair.verify` — verify `{ nodeId, token }`.
|
||||
|
||||
Notes:
|
||||
- Tokens are secrets. Treat `paired.json` as sensitive.
|
||||
- Pending entries should have a TTL (e.g. 5 minutes) and expire automatically.
|
||||
- `node.pair.request` is idempotent per node: repeated calls return the same
|
||||
pending request.
|
||||
- Approval **always** generates a fresh token; no token is ever returned from
|
||||
`node.pair.request`.
|
||||
- Requests may include `silent: true` as a hint for auto-approval flows.
|
||||
|
||||
## Bridge integration
|
||||
Target direction:
|
||||
- The gateway runs the bridge listener (LAN/tailnet-facing) and advertises discovery beacons (Bonjour).
|
||||
- The bridge is transport only; it forwards/scopes requests and enforces ACLs, but pairing decisions are made by the gateway.
|
||||
## Auto-approval (macOS app)
|
||||
|
||||
The macOS UI (Swift) can:
|
||||
- Subscribe to `node.pair.requested`, show an alert (including `remoteIp`), and call `node.pair.approve` or `node.pair.reject`.
|
||||
- Or ignore/dismiss (“Later”) and let CLI handle it.
|
||||
- When `silent` is set, it can try a short SSH probe (same user) and auto-approve if reachable; otherwise fall back to the normal alert.
|
||||
The macOS app can optionally attempt a **silent approval** when:
|
||||
- the request is marked `silent`, and
|
||||
- the app can verify an SSH connection to the gateway host using the same user.
|
||||
|
||||
## Implementation note
|
||||
If the bridge is only provided by the macOS app, then “no Swift app running” cannot work end-to-end.
|
||||
The long-term goal is to move bridge hosting + Bonjour advertising into the Node gateway so headless pairing works by default.
|
||||
If silent approval fails, it falls back to the normal “Approve/Reject” prompt.
|
||||
|
||||
## Storage (local, private)
|
||||
|
||||
Pairing state is stored under the Gateway state directory (default `~/.clawdbot`):
|
||||
|
||||
- `~/.clawdbot/nodes/paired.json`
|
||||
- `~/.clawdbot/nodes/pending.json`
|
||||
|
||||
If you override `CLAWDBOT_STATE_DIR`, the `nodes/` folder moves with it.
|
||||
|
||||
Security notes:
|
||||
- Tokens are secrets; treat `paired.json` as sensitive.
|
||||
- Rotating a token requires re-approval (or deleting the node entry).
|
||||
|
||||
## Bridge behavior
|
||||
|
||||
- The bridge is **transport only**; it does not store membership.
|
||||
- If the Gateway is offline or pairing is disabled, nodes cannot pair.
|
||||
- If the bridge is running but the Gateway is in remote mode, pairing still
|
||||
happens against the remote Gateway’s store.
|
||||
|
||||
102
docs/gateway/sandboxing.md
Normal file
@@ -0,0 +1,102 @@
|
||||
---
|
||||
summary: "How Clawdbot sandboxing works: modes, scopes, workspace access, and images"
|
||||
title: Sandboxing
|
||||
read_when: "You want a dedicated explanation of sandboxing or need to tune agent.sandbox."
|
||||
status: active
|
||||
---
|
||||
|
||||
# Sandboxing
|
||||
|
||||
Clawdbot can run **tools inside Docker containers** to reduce blast radius.
|
||||
This is **optional** and controlled by configuration (`agent.sandbox` or
|
||||
`routing.agents[id].sandbox`). If sandboxing is off, tools run on the host.
|
||||
The Gateway stays on the host; tool execution runs in an isolated sandbox
|
||||
when enabled.
|
||||
|
||||
This is not a perfect security boundary, but it materially limits filesystem
|
||||
and process access when the model does something dumb.
|
||||
|
||||
## What gets sandboxed
|
||||
- Tool execution (`bash`, `read`, `write`, `edit`, `process`, etc.).
|
||||
- Optional sandboxed browser (`agent.sandbox.browser`).
|
||||
|
||||
Not sandboxed:
|
||||
- The Gateway process itself.
|
||||
- Any tool explicitly allowed to run on the host (e.g. `agent.elevated`).
|
||||
- **Elevated bash runs on the host and bypasses sandboxing.**
|
||||
- If sandboxing is off, `agent.elevated` does not change execution (already on host). See [Elevated Mode](/tools/elevated).
|
||||
|
||||
## Modes
|
||||
`agent.sandbox.mode` controls **when** sandboxing is used:
|
||||
- `"off"`: no sandboxing.
|
||||
- `"non-main"`: sandbox only **non-main** sessions (default if you want normal chats on host).
|
||||
- `"all"`: every session runs in a sandbox.
|
||||
Note: `"non-main"` is based on `session.mainKey` (default `"main"`), not agent id.
|
||||
Group/channel sessions use their own keys, so they count as non-main and will be sandboxed.
|
||||
|
||||
## Scope
|
||||
`agent.sandbox.scope` controls **how many containers** are created:
|
||||
- `"session"` (default): one container per session.
|
||||
- `"agent"`: one container per agent.
|
||||
- `"shared"`: one container shared by all sandboxed sessions.
|
||||
|
||||
## Workspace access
|
||||
`agent.sandbox.workspaceAccess` controls **what the sandbox can see**:
|
||||
- `"none"` (default): tools see a sandbox workspace under `~/.clawdbot/sandboxes`.
|
||||
- `"ro"`: mounts the agent workspace read-only at `/agent` (disables `write`/`edit`).
|
||||
- `"rw"`: mounts the agent workspace read/write at `/workspace`.
|
||||
|
||||
Inbound media is copied into the active sandbox workspace (`media/inbound/*`).
|
||||
Skills note: the `read` tool is sandbox-rooted. With `workspaceAccess: "none"`,
|
||||
Clawdbot mirrors eligible skills into the sandbox workspace (`.../skills`) so
|
||||
they can be read. With `"rw"`, workspace skills are readable from
|
||||
`/workspace/skills`.
|
||||
|
||||
## Images + setup
|
||||
Default image: `clawdbot-sandbox:bookworm-slim`
|
||||
|
||||
Build it once:
|
||||
```bash
|
||||
scripts/sandbox-setup.sh
|
||||
```
|
||||
|
||||
Sandboxed browser image:
|
||||
```bash
|
||||
scripts/sandbox-browser-setup.sh
|
||||
```
|
||||
|
||||
By default, sandbox containers run with **no network**.
|
||||
Override with `agent.sandbox.docker.network`.
|
||||
|
||||
Docker installs and the containerized gateway live here:
|
||||
[Docker](/install/docker)
|
||||
|
||||
## Tool policy + escape hatches
|
||||
Tool allow/deny policies still apply before sandbox rules. If a tool is denied
|
||||
globally or per-agent, sandboxing doesn’t bring it back.
|
||||
|
||||
`agent.elevated` is an explicit escape hatch that runs `bash` on the host.
|
||||
Keep it locked down.
|
||||
|
||||
## Multi-agent overrides
|
||||
Each agent can override sandbox + tools:
|
||||
`routing.agents[id].sandbox` and `routing.agents[id].tools`.
|
||||
See [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for precedence.
|
||||
|
||||
## Minimal enable example
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
sandbox: {
|
||||
mode: "non-main",
|
||||
scope: "session",
|
||||
workspaceAccess: "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Related docs
|
||||
- [Sandbox Configuration](/gateway/configuration#agent-sandbox)
|
||||
- [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools)
|
||||
- [Security](/gateway/security)
|
||||
@@ -77,6 +77,13 @@ Even with strong system prompts, **prompt injection is not solved**. What helps
|
||||
- Run sensitive tool execution in a sandbox; keep secrets out of the agent’s reachable filesystem.
|
||||
- **Model choice matters:** we recommend Anthropic Opus 4.5 because it’s quite good at recognizing prompt injections (see [“A step forward on safety”](https://www.anthropic.com/news/claude-opus-4-5)). Using weaker models increases risk.
|
||||
|
||||
## Reasoning & verbose output in groups
|
||||
|
||||
`/reasoning` and `/verbose` can expose internal reasoning or tool output that
|
||||
was not meant for a public channel. In group settings, treat them as **debug
|
||||
only** and keep them off unless you explicitly need them. If you enable them,
|
||||
do so only in trusted DMs or tightly controlled rooms.
|
||||
|
||||
## Lessons Learned (The Hard Way)
|
||||
|
||||
### The `find ~` Incident 🦞
|
||||
@@ -95,6 +102,14 @@ This is social engineering 101. Create distrust, encourage snooping.
|
||||
|
||||
## Configuration Hardening (examples)
|
||||
|
||||
### 0) File permissions
|
||||
|
||||
Keep config + state private on the gateway host:
|
||||
- `~/.clawdbot/clawdbot.json`: `600` (user read/write only)
|
||||
- `~/.clawdbot`: `700` (user only)
|
||||
|
||||
`clawdbot doctor` can warn and offer to tighten these permissions.
|
||||
|
||||
### 1) DMs: pairing by default
|
||||
|
||||
```json5
|
||||
@@ -138,10 +153,12 @@ We may add a single `readOnlyMode` flag later to simplify this configuration.
|
||||
|
||||
## Sandboxing (recommended)
|
||||
|
||||
Dedicated doc: [Sandboxing](/gateway/sandboxing)
|
||||
|
||||
Two complementary approaches:
|
||||
|
||||
- **Run the full Gateway in Docker** (container boundary): [Docker](/install/docker)
|
||||
- **Tool sandbox** (`agent.sandbox`, host gateway + Docker-isolated tools): [Configuration](/gateway/configuration)
|
||||
- **Tool sandbox** (`agent.sandbox`, host gateway + Docker-isolated tools): [Sandboxing](/gateway/sandboxing)
|
||||
|
||||
Note: to prevent cross-agent access, keep `sandbox.scope` at `"agent"` (default)
|
||||
or `"session"` for stricter per-session isolation. `scope: "shared"` uses a
|
||||
@@ -152,7 +169,7 @@ Also consider agent workspace access inside the sandbox:
|
||||
- `workspaceAccess: "ro"` mounts the agent workspace read-only at `/agent` (disables `write`/`edit`)
|
||||
- `workspaceAccess: "rw"` mounts the agent workspace read/write at `/workspace`
|
||||
|
||||
Important: `agent.elevated` is an explicit escape hatch that runs bash on the host. Keep `agent.elevated.allowFrom` tight and don’t enable it for strangers.
|
||||
Important: `agent.elevated` is a **global**, sender-based escape hatch that runs bash on the host. Keep `agent.elevated.allowFrom` tight and don’t enable it for strangers. See [Elevated Mode](/tools/elevated).
|
||||
|
||||
## Per-agent access profiles (multi-agent)
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ When Clawdbot misbehaves, here's how to fix it.
|
||||
|
||||
Start with the FAQ’s [First 60 seconds](/start/faq#first-60-seconds-if-somethings-broken) if you just want a quick triage recipe. This page goes deeper on runtime failures and diagnostics.
|
||||
|
||||
Provider-specific shortcuts: [/providers/troubleshooting](/providers/troubleshooting)
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Service Installed but Nothing is Running
|
||||
@@ -31,6 +33,34 @@ Doctor/daemon will show runtime state (PID/last exit) and log hints.
|
||||
- Linux systemd (if installed): `journalctl --user -u clawdbot-gateway.service -n 200 --no-pager`
|
||||
- Windows: `schtasks /Query /TN "Clawdbot Gateway" /V /FO LIST`
|
||||
|
||||
**Enable more logging:**
|
||||
- Bump file log detail (persisted JSONL):
|
||||
```json
|
||||
{ "logging": { "level": "debug" } }
|
||||
```
|
||||
- Bump console verbosity (TTY output only):
|
||||
```json
|
||||
{ "logging": { "consoleLevel": "debug", "consoleStyle": "pretty" } }
|
||||
```
|
||||
- Quick tip: `--verbose` affects **console** output only. File logs remain controlled by `logging.level`.
|
||||
|
||||
See [/logging](/logging) for a full overview of formats, config, and access.
|
||||
|
||||
### Service Environment (PATH + runtime)
|
||||
|
||||
The gateway daemon runs with a **minimal PATH** to avoid shell/manager cruft:
|
||||
- macOS: `/opt/homebrew/bin`, `/usr/local/bin`, `/usr/bin`, `/bin`
|
||||
- Linux: `/usr/local/bin`, `/usr/bin`, `/bin`
|
||||
|
||||
This intentionally excludes version managers (nvm/fnm/volta/asdf) and package
|
||||
managers (pnpm/npm) because the daemon does not load your shell init. Runtime
|
||||
variables like `DISPLAY` should live in `~/.clawdbot/.env` (loaded early by the
|
||||
gateway).
|
||||
|
||||
WhatsApp + Telegram providers require **Node**; Bun is unsupported. If your
|
||||
service was installed with Bun or a version-managed Node path, run `clawdbot doctor`
|
||||
to migrate to a system Node install.
|
||||
|
||||
### Service Running but Port Not Listening
|
||||
|
||||
If the service reports **running** but nothing is listening on the gateway port,
|
||||
@@ -55,6 +85,10 @@ the Gateway likely refused to bind.
|
||||
- If they don’t, you’re almost certainly editing one config while the daemon is running another.
|
||||
- Fix: rerun `clawdbot daemon install --force` from the same `--profile` / `CLAWDBOT_STATE_DIR` you want the daemon to use.
|
||||
|
||||
**If `clawdbot daemon status` reports service config issues**
|
||||
- The supervisor config (launchd/systemd/schtasks) is missing current defaults.
|
||||
- Fix: run `clawdbot doctor` to update it (or `clawdbot daemon install --force` for a full rewrite).
|
||||
|
||||
**If `Last gateway error:` mentions “refusing to bind … without auth”**
|
||||
- You set `gateway.bind` to a non-loopback mode (`lan`/`tailnet`/`auto`) but left auth off.
|
||||
- Fix: set `gateway.auth.mode` + `gateway.auth.token` (or export `CLAWDBOT_GATEWAY_TOKEN`) and restart the daemon.
|
||||
@@ -88,6 +122,19 @@ or state drift because only one workspace is active.
|
||||
**Fix:** keep a single active workspace and archive/remove the rest. See
|
||||
[Agent workspace](/concepts/agent-workspace#legacy-workspace-folders).
|
||||
|
||||
### Main chat running in a sandbox workspace
|
||||
|
||||
Symptoms: `pwd` or file tools show `~/.clawdbot/sandboxes/...` even though you
|
||||
expected the host workspace.
|
||||
|
||||
**Why:** `agent.sandbox.mode: "non-main"` keys off `session.mainKey` (default `"main"`).
|
||||
Group/channel sessions use their own keys, so they are treated as non-main and
|
||||
get sandbox workspaces.
|
||||
|
||||
**Fix options:**
|
||||
- If you want host workspaces for an agent: set `routing.agents.<id>.sandbox.mode: "off"`.
|
||||
- If you want host workspace access inside sandbox: set `workspaceAccess: "rw"` for that agent.
|
||||
|
||||
### "Agent was aborted"
|
||||
|
||||
The agent was interrupted mid-response.
|
||||
@@ -110,6 +157,7 @@ Look for `AllowFrom: ...` in the output.
|
||||
**Check 2:** For group chats, is mention required?
|
||||
```bash
|
||||
# The message must match mentionPatterns or explicit mentions; defaults live in provider groups/guilds.
|
||||
# Multi-agent: `routing.agents.<agentId>.mentionPatterns` overrides global patterns.
|
||||
grep -n "routing\\|groupChat\\|mentionPatterns\\|whatsapp\\.groups\\|telegram\\.groups\\|imessage\\.groups\\|discord\\.guilds" \
|
||||
"${CLAWDBOT_CONFIG_PATH:-$HOME/.clawdbot/clawdbot.json}"
|
||||
```
|
||||
@@ -276,7 +324,7 @@ clawdbot providers login --verbose
|
||||
| Log | Location |
|
||||
|-----|----------|
|
||||
| Gateway file logs (structured) | `/tmp/clawdbot/clawdbot-YYYY-MM-DD.log` (or `logging.file`) |
|
||||
| Gateway service logs (supervisor) | macOS: `$CLAWDBOT_STATE_DIR/logs/gateway.log` + `gateway.err.log` (default: `~/.clawdbot/logs/...`; profiles use `~/.clawdbot-<profile>/logs/...`)<br>Linux: `journalctl --user -u clawdbot-gateway.service -n 200 --no-pager`<br>Windows: `schtasks /Query /TN "Clawdbot Gateway" /V /FO LIST` |
|
||||
| Gateway service logs (supervisor) | macOS: `$CLAWDBOT_STATE_DIR/logs/gateway.log` + `gateway.err.log` (default: `~/.clawdbot/logs/...`; profiles use `~/.clawdbot-<profile>/logs/...`)<br />Linux: `journalctl --user -u clawdbot-gateway.service -n 200 --no-pager`<br />Windows: `schtasks /Query /TN "Clawdbot Gateway" /V /FO LIST` |
|
||||
| Session files | `$CLAWDBOT_STATE_DIR/agents/<agentId>/sessions/` |
|
||||
| Media cache | `$CLAWDBOT_STATE_DIR/media/` |
|
||||
| Credentials | `$CLAWDBOT_STATE_DIR/credentials/` |
|
||||
|
||||
@@ -20,11 +20,11 @@ read_when:
|
||||
<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/start/clawd">Clawd setup</a>
|
||||
<a href="https://docs.clawd.bot/start/clawd">Clawdbot assistant 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.
|
||||
Clawdbot also powers [Clawd](https://clawd.me), the space‑lobster assistant.
|
||||
|
||||
## Start here
|
||||
|
||||
@@ -118,8 +118,7 @@ From source (development):
|
||||
git clone https://github.com/clawdbot/clawdbot.git
|
||||
cd clawdbot
|
||||
pnpm install
|
||||
pnpm ui:install
|
||||
pnpm ui:build
|
||||
pnpm ui:build # auto-installs UI deps on first run
|
||||
pnpm build
|
||||
pnpm clawdbot onboard --install-daemon
|
||||
```
|
||||
@@ -135,7 +134,7 @@ clawdbot gateway --port 19001
|
||||
Send a test message (requires a running Gateway):
|
||||
|
||||
```bash
|
||||
clawdbot send --to +15555550123 --message "Hello from CLAWDBOT"
|
||||
clawdbot message send --to +15555550123 --message "Hello from CLAWDBOT"
|
||||
```
|
||||
|
||||
## Configuration (optional)
|
||||
@@ -169,7 +168,7 @@ Example:
|
||||
- [Updating / rollback](https://docs.clawd.bot/install/updating)
|
||||
- [Pairing (DM + nodes)](https://docs.clawd.bot/start/pairing)
|
||||
- [Nix mode](https://docs.clawd.bot/install/nix)
|
||||
- [Clawd personal assistant setup](https://docs.clawd.bot/start/clawd)
|
||||
- [Clawdbot assistant setup (Clawd)](https://docs.clawd.bot/start/clawd)
|
||||
- [Skills](https://docs.clawd.bot/tools/skills)
|
||||
- [Skills config](https://docs.clawd.bot/tools/skills-config)
|
||||
- [Workspace templates](https://docs.clawd.bot/reference/templates/AGENTS)
|
||||
|
||||
205
docs/install/ansible.md
Normal file
@@ -0,0 +1,205 @@
|
||||
---
|
||||
summary: "Automated, hardened Clawdbot installation with Ansible, Tailscale VPN, and firewall isolation"
|
||||
read_when:
|
||||
- You want automated server deployment with security hardening
|
||||
- You need firewall-isolated setup with VPN access
|
||||
- You're deploying to remote Debian/Ubuntu servers
|
||||
---
|
||||
|
||||
# Ansible Installation
|
||||
|
||||
The recommended way to deploy Clawdbot to production servers is via **[clawdbot-ansible](https://github.com/clawdbot/clawdbot-ansible)** — an automated installer with security-first architecture.
|
||||
|
||||
## Quick Start
|
||||
|
||||
One-command install:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/clawdbot/clawdbot-ansible/main/install.sh | bash
|
||||
```
|
||||
|
||||
> **📦 Full guide: [github.com/clawdbot/clawdbot-ansible](https://github.com/clawdbot/clawdbot-ansible)**
|
||||
>
|
||||
> The clawdbot-ansible repo is the source of truth for Ansible deployment. This page is a quick overview.
|
||||
|
||||
## What You Get
|
||||
|
||||
- 🔒 **Firewall-first security**: UFW + Docker isolation (only SSH + Tailscale accessible)
|
||||
- 🔐 **Tailscale VPN**: Secure remote access without exposing services publicly
|
||||
- 🐳 **Docker**: Isolated sandbox containers, localhost-only bindings
|
||||
- 🛡️ **Defense in depth**: 4-layer security architecture
|
||||
- 🚀 **One-command setup**: Complete deployment in minutes
|
||||
- 🔧 **Systemd integration**: Auto-start on boot with hardening
|
||||
|
||||
## Requirements
|
||||
|
||||
- **OS**: Debian 11+ or Ubuntu 20.04+
|
||||
- **Access**: Root or sudo privileges
|
||||
- **Network**: Internet connection for package installation
|
||||
- **Ansible**: 2.14+ (installed automatically by quick-start script)
|
||||
|
||||
## What Gets Installed
|
||||
|
||||
The Ansible playbook installs and configures:
|
||||
|
||||
1. **Tailscale** (mesh VPN for secure remote access)
|
||||
2. **UFW firewall** (SSH + Tailscale ports only)
|
||||
3. **Docker CE + Compose V2** (for agent sandboxes)
|
||||
4. **Node.js 22.x + pnpm** (runtime dependencies)
|
||||
5. **Clawdbot** (host-based, not containerized)
|
||||
6. **Systemd service** (auto-start with security hardening)
|
||||
|
||||
Note: The gateway runs **directly on the host** (not in Docker), but agent sandboxes use Docker for isolation. See [Sandboxing](/gateway/sandboxing) for details.
|
||||
|
||||
## Post-Install Setup
|
||||
|
||||
After installation completes, switch to the clawdbot user:
|
||||
|
||||
```bash
|
||||
sudo -i -u clawdbot
|
||||
```
|
||||
|
||||
The post-install script will guide you through:
|
||||
|
||||
1. **Onboarding wizard**: Configure Clawdbot settings
|
||||
2. **Provider login**: Connect WhatsApp/Telegram/Discord/Signal
|
||||
3. **Gateway testing**: Verify the installation
|
||||
4. **Tailscale setup**: Connect to your VPN mesh
|
||||
|
||||
### Quick commands
|
||||
|
||||
```bash
|
||||
# Check service status
|
||||
sudo systemctl status clawdbot
|
||||
|
||||
# View live logs
|
||||
sudo journalctl -u clawdbot -f
|
||||
|
||||
# Restart gateway
|
||||
sudo systemctl restart clawdbot
|
||||
|
||||
# Provider login (run as clawdbot user)
|
||||
sudo -i -u clawdbot
|
||||
clawdbot login
|
||||
```
|
||||
|
||||
## Security Architecture
|
||||
|
||||
### 4-Layer Defense
|
||||
|
||||
1. **Firewall (UFW)**: Only SSH (22) + Tailscale (41641/udp) exposed publicly
|
||||
2. **VPN (Tailscale)**: Gateway accessible only via VPN mesh
|
||||
3. **Docker Isolation**: DOCKER-USER iptables chain prevents external port exposure
|
||||
4. **Systemd Hardening**: NoNewPrivileges, PrivateTmp, unprivileged user
|
||||
|
||||
### Verification
|
||||
|
||||
Test external attack surface:
|
||||
|
||||
```bash
|
||||
nmap -p- YOUR_SERVER_IP
|
||||
```
|
||||
|
||||
Should show **only port 22** (SSH) open. All other services (gateway, Docker) are locked down.
|
||||
|
||||
### Docker Availability
|
||||
|
||||
Docker is installed for **agent sandboxes** (isolated tool execution), not for running the gateway itself. The gateway binds to localhost only and is accessible via Tailscale VPN.
|
||||
|
||||
See [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for sandbox configuration.
|
||||
|
||||
## Manual Installation
|
||||
|
||||
If you prefer manual control over the automation:
|
||||
|
||||
```bash
|
||||
# 1. Install prerequisites
|
||||
sudo apt update && sudo apt install -y ansible git
|
||||
|
||||
# 2. Clone repository
|
||||
git clone https://github.com/clawdbot/clawdbot-ansible.git
|
||||
cd clawdbot-ansible
|
||||
|
||||
# 3. Install Ansible collections
|
||||
ansible-galaxy collection install -r requirements.yml
|
||||
|
||||
# 4. Run playbook
|
||||
./run-playbook.sh
|
||||
|
||||
# Or run directly (then manually execute /tmp/clawdbot-setup.sh after)
|
||||
# ansible-playbook playbook.yml --ask-become-pass
|
||||
```
|
||||
|
||||
## Updating Clawdbot
|
||||
|
||||
The Ansible installer sets up Clawdbot for manual updates. See [Updating](/install/updating) for the standard update flow.
|
||||
|
||||
To re-run the Ansible playbook (e.g., for configuration changes):
|
||||
|
||||
```bash
|
||||
cd clawdbot-ansible
|
||||
./run-playbook.sh
|
||||
```
|
||||
|
||||
Note: This is idempotent and safe to run multiple times.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Firewall blocks my connection
|
||||
|
||||
If you're locked out:
|
||||
- Ensure you can access via Tailscale VPN first
|
||||
- SSH access (port 22) is always allowed
|
||||
- The gateway is **only** accessible via Tailscale by design
|
||||
|
||||
### Service won't start
|
||||
|
||||
```bash
|
||||
# Check logs
|
||||
sudo journalctl -u clawdbot -n 100
|
||||
|
||||
# Verify permissions
|
||||
sudo ls -la /opt/clawdbot
|
||||
|
||||
# Test manual start
|
||||
sudo -i -u clawdbot
|
||||
cd ~/clawdbot
|
||||
pnpm start
|
||||
```
|
||||
|
||||
### Docker sandbox issues
|
||||
|
||||
```bash
|
||||
# Verify Docker is running
|
||||
sudo systemctl status docker
|
||||
|
||||
# Check sandbox image
|
||||
sudo docker images | grep clawdbot-sandbox
|
||||
|
||||
# Build sandbox image if missing
|
||||
cd /opt/clawdbot/clawdbot
|
||||
sudo -u clawdbot ./scripts/sandbox-setup.sh
|
||||
```
|
||||
|
||||
### Provider login fails
|
||||
|
||||
Make sure you're running as the `clawdbot` user:
|
||||
|
||||
```bash
|
||||
sudo -i -u clawdbot
|
||||
clawdbot login
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
For detailed security architecture and troubleshooting:
|
||||
- [Security Architecture](https://github.com/clawdbot/clawdbot-ansible/blob/main/docs/security.md)
|
||||
- [Technical Details](https://github.com/clawdbot/clawdbot-ansible/blob/main/docs/architecture.md)
|
||||
- [Troubleshooting Guide](https://github.com/clawdbot/clawdbot-ansible/blob/main/docs/troubleshooting.md)
|
||||
|
||||
## Related
|
||||
|
||||
- [clawdbot-ansible](https://github.com/clawdbot/clawdbot-ansible) — full deployment guide
|
||||
- [Docker](/install/docker) — containerized gateway setup
|
||||
- [Sandboxing](/gateway/sandboxing) — agent sandbox configuration
|
||||
- [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) — per-agent isolation
|
||||
@@ -9,10 +9,18 @@ read_when:
|
||||
|
||||
Docker is **optional**. Use it only if you want a containerized gateway or to validate the Docker flow.
|
||||
|
||||
## Is Docker right for me?
|
||||
|
||||
- **Yes**: you want an isolated, throwaway gateway environment or to run Clawdbot on a host without local installs.
|
||||
- **No**: you’re running on your own machine and just want the fastest dev loop. Use the normal install flow instead.
|
||||
- **Sandboxing note**: agent sandboxing uses Docker too, but it does **not** require the full gateway to run in Docker. See [Sandboxing](/gateway/sandboxing).
|
||||
|
||||
This guide covers:
|
||||
- Containerized Gateway (full Clawdbot in Docker)
|
||||
- Per-session Agent Sandbox (host gateway + Docker-isolated agent tools)
|
||||
|
||||
Sandboxing details: [Sandboxing](/gateway/sandboxing)
|
||||
|
||||
## Requirements
|
||||
|
||||
- Docker Desktop (or Docker Engine) + Docker Compose v2
|
||||
@@ -33,6 +41,11 @@ This script:
|
||||
- runs the onboarding wizard
|
||||
- prints optional provider setup hints
|
||||
- starts the gateway via Docker Compose
|
||||
- generates a gateway token and writes it to `.env`
|
||||
|
||||
After it finishes:
|
||||
- Open `http://127.0.0.1:18789/` in your browser.
|
||||
- Paste the token into the Control UI (Settings → token).
|
||||
|
||||
It writes config/workspace on the host:
|
||||
- `~/.clawdbot/`
|
||||
@@ -92,6 +105,8 @@ pnpm test:docker:qr
|
||||
|
||||
## Agent Sandbox (host gateway + Docker tools)
|
||||
|
||||
Deep dive: [Sandboxing](/gateway/sandboxing)
|
||||
|
||||
### What it does
|
||||
|
||||
When `agent.sandbox` is enabled, **non-main sessions** run tools inside a Docker
|
||||
|
||||
@@ -59,8 +59,7 @@ From the repo checkout:
|
||||
git pull
|
||||
pnpm install
|
||||
pnpm build
|
||||
pnpm ui:install
|
||||
pnpm ui:build
|
||||
pnpm ui:build # auto-installs UI deps on first run
|
||||
pnpm clawdbot doctor
|
||||
pnpm clawdbot health
|
||||
```
|
||||
@@ -109,7 +108,7 @@ Runbook + exact service labels: [Gateway runbook](/gateway)
|
||||
Install a known-good version:
|
||||
|
||||
```bash
|
||||
npm i -g clawdbot@2026.1.8
|
||||
npm i -g clawdbot@2026.1.9
|
||||
```
|
||||
|
||||
Then restart + re-run doctor:
|
||||
|
||||
144
docs/logging.md
Normal file
@@ -0,0 +1,144 @@
|
||||
---
|
||||
summary: "Logging overview: file logs, console output, CLI tailing, and the Control UI"
|
||||
read_when:
|
||||
- You need a beginner-friendly overview of logging
|
||||
- You want to configure log levels or formats
|
||||
- You are troubleshooting and need to find logs quickly
|
||||
---
|
||||
|
||||
# Logging
|
||||
|
||||
Clawdbot logs in two places:
|
||||
|
||||
- **File logs** (JSON lines) written by the Gateway.
|
||||
- **Console output** shown in terminals and the Control UI.
|
||||
|
||||
This page explains where logs live, how to read them, and how to configure log
|
||||
levels and formats.
|
||||
|
||||
## Where logs live
|
||||
|
||||
By default, the Gateway writes a rolling log file under:
|
||||
|
||||
`/tmp/clawdbot/clawdbot-YYYY-MM-DD.log`
|
||||
|
||||
You can override this in `~/.clawdbot/clawdbot.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"logging": {
|
||||
"file": "/path/to/clawdbot.log"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## How to read logs
|
||||
|
||||
### CLI: live tail (recommended)
|
||||
|
||||
Use the CLI to tail the gateway log file via RPC:
|
||||
|
||||
```bash
|
||||
clawdbot logs --follow
|
||||
```
|
||||
|
||||
Output modes:
|
||||
|
||||
- **TTY sessions**: pretty, colorized, structured log lines.
|
||||
- **Non-TTY sessions**: plain text.
|
||||
- `--json`: line-delimited JSON (one log event per line).
|
||||
- `--plain`: force plain text in TTY sessions.
|
||||
- `--no-color`: disable ANSI colors.
|
||||
|
||||
In JSON mode, the CLI emits `type`-tagged objects:
|
||||
|
||||
- `meta`: stream metadata (file, cursor, size)
|
||||
- `log`: parsed log entry
|
||||
- `notice`: truncation / rotation hints
|
||||
- `raw`: unparsed log line
|
||||
|
||||
If the Gateway is unreachable, the CLI prints a short hint to run:
|
||||
|
||||
```bash
|
||||
clawdbot doctor
|
||||
```
|
||||
|
||||
### Control UI (web)
|
||||
|
||||
The Control UI’s **Logs** tab tails the same file using `logs.tail`.
|
||||
See [/web/control-ui](/web/control-ui) for how to open it.
|
||||
|
||||
### Provider-only logs
|
||||
|
||||
To filter provider activity (WhatsApp/Telegram/etc), use:
|
||||
|
||||
```bash
|
||||
clawdbot providers logs --provider whatsapp
|
||||
```
|
||||
|
||||
## Log formats
|
||||
|
||||
### File logs (JSONL)
|
||||
|
||||
Each line in the log file is a JSON object. The CLI and Control UI parse these
|
||||
entries to render structured output (time, level, subsystem, message).
|
||||
|
||||
### Console output
|
||||
|
||||
Console logs are **TTY-aware** and formatted for readability:
|
||||
|
||||
- Subsystem prefixes (e.g. `gateway/providers/whatsapp`)
|
||||
- Level coloring (info/warn/error)
|
||||
- Optional compact or JSON mode
|
||||
|
||||
Console formatting is controlled by `logging.consoleStyle`.
|
||||
|
||||
## Configuring logging
|
||||
|
||||
All logging configuration lives under `logging` in `~/.clawdbot/clawdbot.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"logging": {
|
||||
"level": "info",
|
||||
"file": "/tmp/clawdbot/clawdbot-YYYY-MM-DD.log",
|
||||
"consoleLevel": "info",
|
||||
"consoleStyle": "pretty",
|
||||
"redactSensitive": "tools",
|
||||
"redactPatterns": [
|
||||
"sk-.*"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Log levels
|
||||
|
||||
- `logging.level`: **file logs** (JSONL) level.
|
||||
- `logging.consoleLevel`: **console** verbosity level.
|
||||
|
||||
`--verbose` only affects console output; it does not change file log levels.
|
||||
|
||||
### Console styles
|
||||
|
||||
`logging.consoleStyle`:
|
||||
|
||||
- `pretty`: human-friendly, colored, with timestamps.
|
||||
- `compact`: tighter output (best for long sessions).
|
||||
- `json`: JSON per line (for log processors).
|
||||
|
||||
### Redaction
|
||||
|
||||
Tool summaries can redact sensitive tokens before they hit the console:
|
||||
|
||||
- `logging.redactSensitive`: `off` | `tools` (default: `tools`)
|
||||
- `logging.redactPatterns`: list of regex strings to override the default set
|
||||
|
||||
Redaction affects **console output only** and does not alter file logs.
|
||||
|
||||
## Troubleshooting tips
|
||||
|
||||
- **Gateway not reachable?** Run `clawdbot doctor` first.
|
||||
- **Logs empty?** Check that the Gateway is running and writing to the file path
|
||||
in `logging.file`.
|
||||
- **Need more detail?** Set `logging.level` to `debug` or `trace` and retry.
|
||||
@@ -18,6 +18,8 @@ This allows you to run multiple agents with different security profiles:
|
||||
- Family/work agents with restricted tools
|
||||
- Public-facing agents in sandboxes
|
||||
|
||||
For how sandboxing behaves at runtime, see [Sandboxing](/gateway/sandboxing).
|
||||
|
||||
---
|
||||
|
||||
## Configuration Examples
|
||||
@@ -167,6 +169,14 @@ The filtering order is:
|
||||
Each level can further restrict tools, but cannot grant back denied tools from earlier levels.
|
||||
If `routing.agents[id].sandbox.tools` is set, it replaces `agent.sandbox.tools` for that agent.
|
||||
|
||||
### Elevated Mode (global)
|
||||
`agent.elevated` is **global** and **sender-based** (per-provider allowlist). It is **not** configurable per agent.
|
||||
|
||||
Mitigation patterns:
|
||||
- Deny `bash` for untrusted agents (`routing.agents[id].tools.deny: ["bash"]`)
|
||||
- Avoid allowlisting senders that route to restricted agents
|
||||
- Disable elevated globally (`agent.elevated.enabled: false`) if you only want sandboxed execution
|
||||
|
||||
---
|
||||
|
||||
## Migration from Single Agent
|
||||
@@ -242,6 +252,15 @@ The global `agent.workspace` and `agent.sandbox` are still supported for backwar
|
||||
|
||||
---
|
||||
|
||||
## Common Pitfall: "non-main"
|
||||
|
||||
`sandbox.mode: "non-main"` is based on `session.mainKey` (default `"main"`),
|
||||
not the agent id. Group/channel sessions always get their own keys, so they
|
||||
are treated as non-main and will be sandboxed. If you want an agent to never
|
||||
sandbox, set `routing.agents.<id>.sandbox.mode: "off"`.
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
After configuring multi-agent sandbox and tools:
|
||||
|
||||
@@ -8,12 +8,12 @@ read_when:
|
||||
CLAWDBOT is now **web-only** (Baileys). This document captures the current media handling rules for send, gateway, and agent replies.
|
||||
|
||||
## Goals
|
||||
- Send media with optional captions via `clawdbot send --media`.
|
||||
- Send media with optional captions via `clawdbot message send --media`.
|
||||
- Allow auto-replies from the web inbox to include media alongside text.
|
||||
- Keep per-type limits sane and predictable.
|
||||
|
||||
## CLI Surface
|
||||
- `clawdbot send --media <path-or-url> [--message <caption>]`
|
||||
- `clawdbot message send --media <path-or-url> [--message <caption>]`
|
||||
- `--media` optional; caption can be empty for media-only sends.
|
||||
- `--dry-run` prints the resolved payload; `--json` emits `{ provider, to, messageId, mediaUrl, caption }`.
|
||||
|
||||
@@ -30,7 +30,7 @@ CLAWDBOT is now **web-only** (Baileys). This document captures the current media
|
||||
|
||||
## Auto-Reply Pipeline
|
||||
- `getReplyFromConfig` returns `{ text?, mediaUrl?, mediaUrls? }`.
|
||||
- When media is present, the web sender resolves local paths or URLs using the same pipeline as `clawdbot send`.
|
||||
- When media is present, the web sender resolves local paths or URLs using the same pipeline as `clawdbot message send`.
|
||||
- Multiple media entries are sent sequentially if provided.
|
||||
|
||||
## Inbound Media to Commands (Pi)
|
||||
|
||||
@@ -140,11 +140,3 @@ Nodes may include a `permissions` map in `node.list` / `node.describe`, keyed by
|
||||
|
||||
- The macOS menubar app connects to the Gateway bridge as a node (so `clawdbot nodes …` works against this Mac).
|
||||
- In remote mode, the app opens an SSH tunnel for the bridge port and connects to `localhost`.
|
||||
|
||||
## 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)
|
||||
- Android node commands: `apps/android/app/src/main/java/com/clawdbot/android/node/*`
|
||||
|
||||
@@ -76,7 +76,7 @@ Goal: model can request location even when node is backgrounded, but only when:
|
||||
|
||||
Push-triggered flow (future):
|
||||
1) Gateway sends a push to the node (silent push or FCM data).
|
||||
2) Node wakes briefly and calls `location.get` internally.
|
||||
2) Node wakes briefly and requests location from the device.
|
||||
3) Node forwards payload to Gateway.
|
||||
|
||||
Notes:
|
||||
|
||||
@@ -40,7 +40,7 @@ Supported keys:
|
||||
- `seed`, `normalize`, `lang`, `output_format`, `latency_tier`
|
||||
- `once`
|
||||
|
||||
## Config (clawdbot.json)
|
||||
## Config (`~/.clawdbot/clawdbot.json`)
|
||||
```json5
|
||||
{
|
||||
"talk": {
|
||||
|
||||
@@ -13,6 +13,15 @@ Goal: Clawdbot Gateway running on an exe.dev VM, reachable from your laptop via:
|
||||
|
||||
This page assumes **Ubuntu/Debian**. If you picked a different distro, map packages accordingly.
|
||||
|
||||
If you’re on any other Linux VPS, the same steps apply — you just won’t use the exe.dev proxy commands.
|
||||
|
||||
## Beginner quick path
|
||||
|
||||
1) Create VM → install Node 22 → install Clawdbot
|
||||
2) Run `clawdbot onboard --install-daemon`
|
||||
3) Tunnel from laptop (`ssh -N -L 18789:127.0.0.1:18789 …`)
|
||||
4) Open `http://127.0.0.1:18789/` and paste your token
|
||||
|
||||
## What you need
|
||||
|
||||
- exe.dev account + `ssh exe.dev` working on your laptop
|
||||
|
||||
@@ -9,7 +9,8 @@ read_when:
|
||||
Clawdbot core is written in TypeScript, so the CLI + Gateway run anywhere Node or Bun runs.
|
||||
|
||||
Companion apps exist for macOS (menu bar app) and mobile nodes (iOS/Android). Windows and
|
||||
Linux companion apps are planned, but the core Gateway is fully supported today.
|
||||
Linux companion apps are planned, but the Gateway is fully supported today.
|
||||
Native companion apps for Windows are also planned; the Gateway is recommended via WSL2.
|
||||
|
||||
## Choose your OS
|
||||
|
||||
|
||||
@@ -1,381 +1,105 @@
|
||||
---
|
||||
summary: "iOS app (node): architecture + connection runbook"
|
||||
summary: "iOS node app: connect to the Gateway, pairing, canvas, and troubleshooting"
|
||||
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
|
||||
- Running the iOS app from source
|
||||
- Debugging bridge discovery or canvas commands
|
||||
---
|
||||
# iOS App (Node)
|
||||
|
||||
Status: prototype implemented (internal) · Date: 2025-12-13
|
||||
Availability: internal preview. The iOS app is not publicly distributed yet.
|
||||
|
||||
## Support snapshot
|
||||
- Role: companion node app (iOS does not host the Gateway).
|
||||
- Gateway required: yes (run it on macOS, Linux, or Windows via WSL2).
|
||||
- Install: [Getting Started](/start/getting-started) + [Pairing](/gateway/pairing).
|
||||
- Gateway: [Runbook](/gateway) + [Configuration](/gateway/configuration).
|
||||
## What it does
|
||||
|
||||
## System control
|
||||
System control (launchd/systemd) lives on the Gateway host. See [Gateway](/gateway).
|
||||
- Connects to a Gateway over the bridge (LAN or tailnet).
|
||||
- Exposes node capabilities: Canvas, Screen snapshot, Camera capture, Location, Talk mode, Voice wake.
|
||||
- Receives `node.invoke` commands and reports node status events.
|
||||
|
||||
## Connection Runbook
|
||||
## Requirements
|
||||
|
||||
This is the practical “how do I connect the iOS node” guide:
|
||||
- Gateway running on another device (macOS, Linux, or Windows via WSL2).
|
||||
- Bridge enabled (default).
|
||||
- Network path:
|
||||
- Same LAN via Bonjour, **or**
|
||||
- Tailnet via unicast DNS-SD (`clawdbot.internal.`), **or**
|
||||
- Manual host/port (fallback).
|
||||
|
||||
**iOS app** ⇄ (Bonjour + TCP bridge) ⇄ **Gateway bridge** ⇄ (loopback WS) ⇄ **Gateway**
|
||||
## Quick start (pair + connect)
|
||||
|
||||
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`).
|
||||
1) Start the Gateway (bridge enabled by default):
|
||||
|
||||
```bash
|
||||
clawdbot gateway --port 18789 --verbose
|
||||
clawdbot gateway --port 18789
|
||||
```
|
||||
|
||||
Confirm in logs you see something like:
|
||||
- `bridge listening on tcp://0.0.0.0:18790 (node)`
|
||||
2) In the iOS app, open Settings and pick a discovered gateway (or enable Manual Bridge and enter host/port).
|
||||
|
||||
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`](/gateway/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`](/gateway/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:
|
||||
3) Approve the pairing request on the gateway host:
|
||||
|
||||
```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`](/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):
|
||||
4) Verify connection:
|
||||
|
||||
```bash
|
||||
clawdbot nodes invoke --node "iOS Node" --command canvas.navigate --params '{"url":"http://<gateway-hostname>.local:18793/__clawdbot__/canvas/"}'
|
||||
clawdbot nodes status
|
||||
clawdbot gateway call node.list --params "{}"
|
||||
```
|
||||
|
||||
## Discovery paths
|
||||
|
||||
### Bonjour (LAN)
|
||||
|
||||
The Gateway advertises `_clawdbot-bridge._tcp` on `local.`. The iOS app lists these automatically.
|
||||
|
||||
### Tailnet (cross-network)
|
||||
|
||||
If mDNS is blocked, use a unicast DNS-SD zone (recommended domain: `clawdbot.internal.`) and Tailscale split DNS.
|
||||
See [`docs/bonjour.md`](/gateway/bonjour) for the CoreDNS example.
|
||||
|
||||
### Manual host/port
|
||||
|
||||
In Settings, enable **Manual Bridge** and enter the gateway host + port (default `18790`).
|
||||
|
||||
## Canvas + A2UI
|
||||
|
||||
The iOS node renders a WKWebView canvas. Use `node.invoke` to drive it:
|
||||
|
||||
```bash
|
||||
clawdbot nodes invoke --node "iOS Node" --command canvas.navigate --params '{"url":"http://<gateway-host>: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.
|
||||
- The Gateway canvas host serves `/__clawdbot__/canvas/` and `/__clawdbot__/a2ui/`.
|
||||
- The iOS node auto-navigates to A2UI on connect when a canvas host URL is advertised.
|
||||
- Return to the built-in scaffold with `canvas.navigate` and `{"url":""}`.
|
||||
|
||||
#### Draw with `canvas.eval`
|
||||
### Canvas eval / snapshot
|
||||
|
||||
```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
|
||||
)"
|
||||
clawdbot nodes invoke --node "iOS Node" --command canvas.eval --params '{"javaScript":"(() => { const {ctx} = window.__clawdbot; 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(); return \"ok\"; })()"}'
|
||||
```
|
||||
|
||||
#### Snapshot with `canvas.snapshot`
|
||||
|
||||
```bash
|
||||
clawdbot nodes invoke --node 192.168.0.88 --command canvas.snapshot --params '{"maxWidth":900}'
|
||||
clawdbot nodes invoke --node "iOS Node" --command canvas.snapshot --params '{"maxWidth":900,"format":"jpeg"}'
|
||||
```
|
||||
|
||||
The response includes `{ format, base64 }` image data (default `format="jpeg"`; pass `{"format":"png"}` when you specifically need lossless PNG).
|
||||
## Voice wake + talk mode
|
||||
|
||||
### Common gotchas
|
||||
- Voice wake and talk mode are available in Settings.
|
||||
- iOS may suspend background audio; treat voice features as best-effort when the app is not active.
|
||||
|
||||
- **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`](/gateway/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.
|
||||
## Common errors
|
||||
|
||||
## Design + Architecture
|
||||
|
||||
### 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.
|
||||
- **Dead-simple setup**:
|
||||
- Auto-discover the host on the local network via **Bonjour**.
|
||||
- One-tap pairing with an approval prompt on the Mac.
|
||||
- iOS is **never** a local gateway; it is always a remote node.
|
||||
- Operational clarity:
|
||||
- When iOS is backgrounded, voice may still run; **canvas commands must fail fast** with a structured error.
|
||||
- Provide **settings**: node display name, enable/disable voice wake, pairing status.
|
||||
|
||||
Non-goals (v1):
|
||||
- Exposing the Node Gateway directly on the LAN.
|
||||
- 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`](/gateway/configuration)).
|
||||
- macOS “Canvas” is controlled via the Gateway node protocol (`canvas.*`), matching iOS/Android ([`docs/mac/canvas.md`](/platforms/mac/canvas)).
|
||||
- Voice wake forwards via `GatewayChannel` to Gateway `agent` (mac app: `VoiceWakeForwarder` → `GatewayConnection.sendAgent`).
|
||||
|
||||
### 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`)
|
||||
|
||||
Why:
|
||||
- Preserves current threat model: Gateway remains local-only.
|
||||
- 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
|
||||
- **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
|
||||
- Bonjour discovery shows a candidate “Clawdbot Bridge” on the LAN.
|
||||
- First connection:
|
||||
1) iOS generates a keypair (Secure Enclave if available).
|
||||
2) iOS connects to the bridge and requests pairing.
|
||||
3) The bridge forwards the pairing request to the **Gateway** as a *pending request*.
|
||||
4) Approval can happen via:
|
||||
- **macOS UI** (Clawdbot shows an alert with Approve/Reject/Later, including the node IP), or
|
||||
- **Terminal/CLI** (headless flows).
|
||||
5) Once approved, the bridge returns a token to iOS; iOS stores it in Keychain.
|
||||
- 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)
|
||||
Pairing decisions must be owned by the Gateway (`clawd` / Node) so nodes can be approved without the macOS app running.
|
||||
|
||||
Key idea:
|
||||
- The Swift app may still show an alert, but it is only a **frontend** for pending requests stored in the Gateway.
|
||||
|
||||
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`](/gateway/pairing) 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)
|
||||
The bridge must not be a raw proxy to every gateway method.
|
||||
|
||||
- Allow by default:
|
||||
- `agent` (with guardrails; idempotency required)
|
||||
- minimal `system-event` beacons (presence updates for the node)
|
||||
- node/canvas methods defined below (new protocol surface)
|
||||
- Deny by default:
|
||||
- anything that widens control without explicit intent (future “shell”, “files”, etc.)
|
||||
- Rate limit:
|
||||
- handshake attempts
|
||||
- voice forwards per minute
|
||||
- snapshot frequency / payload size
|
||||
|
||||
### 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):
|
||||
|
||||
**Identity**
|
||||
- Node identity comes from `connect.params.client.instanceId` (stable), and `connect.params.client.mode = "node"` (or `"ios-node"`).
|
||||
|
||||
**Methods**
|
||||
- `node.list` → list paired/connected nodes + capabilities
|
||||
- `node.describe` → describe a node (capabilities + supported `node.invoke` commands)
|
||||
- `node.invoke` → send a command to a specific node
|
||||
- Params: `{ nodeId, command, params?, timeoutMs? }`
|
||||
|
||||
**Events**
|
||||
- `node.event` → async node status/errors
|
||||
- e.g. background/foreground transitions, voice availability, canvas availability
|
||||
|
||||
#### 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)
|
||||
- `canvas.eval` with `{ javaScript }`
|
||||
- `canvas.snapshot` with `{ maxWidth?, quality?, format? }`
|
||||
- A2UI (mobile + macOS canvas):
|
||||
- `canvas.a2ui.push` with `{ messages: [...] }` (A2UI v0.8 server→client messages)
|
||||
- `canvas.a2ui.pushJSONL` with `{ jsonl: "..." }` (legacy alias)
|
||||
- `canvas.a2ui.reset`
|
||||
- A2UI is hosted by the Gateway canvas host (`/__clawdbot__/a2ui/`) on `canvasHost.port`. Commands fail if the host is unreachable.
|
||||
|
||||
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)
|
||||
As of 2025-12-13, the Gateway supports `node.invoke` for bridge-connected nodes.
|
||||
|
||||
Example: draw a diagonal line on the iOS Canvas:
|
||||
```bash
|
||||
clawdbot nodes invoke --node ios-node --command canvas.eval --params '{"javaScript":"(() => { const {ctx} = window.__clawdbot; 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(); return \"ok\"; })()"}'
|
||||
```
|
||||
|
||||
### Background behavior requirement
|
||||
When iOS is backgrounded:
|
||||
- Voice may still be active (subject to iOS suspension).
|
||||
- **All `canvas.*` commands must fail** with a stable error code, e.g.:
|
||||
- `NODE_BACKGROUND_UNAVAILABLE`
|
||||
- Include `retryable: true` and `retryAfterMs` if we want the agent to wait.
|
||||
|
||||
## iOS app architecture (SwiftUI)
|
||||
### App structure
|
||||
- Single fullscreen Canvas surface (WKWebView).
|
||||
- One settings entry point: a **gear button** that opens a settings sheet.
|
||||
- All navigation is **agent-driven** (no local URL bar).
|
||||
|
||||
### Components
|
||||
- `BridgeDiscovery`: Bonjour browse + resolve (Network.framework `NWBrowser`)
|
||||
- `BridgeConnection`: TCP session + pairing handshake + reconnect (TLS planned)
|
||||
- `NodeRuntime`:
|
||||
- Voice pipeline (wake-word + capture + forward)
|
||||
- Canvas pipeline (WKWebView controller + snapshot + eval)
|
||||
- Background state tracking; enforces “canvas unavailable in background”
|
||||
|
||||
### Voice in background (internal)
|
||||
- Enable background audio mode (and required session configuration) so the mic pipeline can keep running when the user switches apps.
|
||||
- If iOS suspends the app anyway, surface a clear node status (`node.event`) so operators can see voice is unavailable.
|
||||
|
||||
## Code sharing (macOS + iOS)
|
||||
Create/expand SwiftPM targets so both apps share:
|
||||
- `ClawdbotProtocol` (generated models; platform-neutral)
|
||||
- `ClawdbotGatewayClient` (shared WS framing + connect/req/res + seq-gap handling)
|
||||
- `ClawdbotKit` (node/canvas command types + deep links + shared utilities)
|
||||
|
||||
macOS continues to own:
|
||||
- local Canvas implementation details (custom scheme handler serving on-disk HTML, window/panel presentation)
|
||||
|
||||
iOS owns:
|
||||
- iOS-specific audio/speech + WKWebView presentation and lifecycle
|
||||
|
||||
## Repo layout
|
||||
- iOS app: `apps/ios/` (XcodeGen `project.yml`)
|
||||
- Shared Swift packages: `apps/shared/`
|
||||
- Lint/format: iOS target runs `swiftformat --lint` + `swiftlint lint` using repo configs (`.swiftformat`, `.swiftlint.yml`).
|
||||
|
||||
Generate the Xcode project:
|
||||
```bash
|
||||
cd apps/ios
|
||||
xcodegen generate
|
||||
open Clawdbot.xcodeproj
|
||||
```
|
||||
|
||||
## Storage plan (private by default)
|
||||
### iOS
|
||||
- Canvas/workspace files (persistent, private):
|
||||
- `Application Support/Clawdbot/canvas/<sessionKey>/...`
|
||||
- Snapshots / temp exports (evictable):
|
||||
- `Library/Caches/Clawdbot/canvas-snapshots/<sessionKey>/...`
|
||||
- Credentials:
|
||||
- Keychain (paired identity + bridge trust anchor)
|
||||
- `NODE_BACKGROUND_UNAVAILABLE`: bring the iOS app to the foreground (canvas/camera/screen commands require it).
|
||||
- `A2UI_HOST_NOT_CONFIGURED`: the Gateway did not advertise a canvas host URL; check `canvasHost` in [`docs/configuration.md`](/gateway/configuration).
|
||||
- Pairing prompt never appears: run `clawdbot nodes pending` and approve manually.
|
||||
- Reconnect fails after reinstall: the Keychain pairing token was cleared; re-pair the node.
|
||||
|
||||
## Related docs
|
||||
|
||||
- [`docs/gateway.md`](/gateway) (gateway runbook)
|
||||
- [`docs/gateway/pairing.md`](/gateway/pairing) (approval + storage)
|
||||
- [`docs/bonjour.md`](/gateway/bonjour) (discovery debugging)
|
||||
- [`docs/discovery.md`](/gateway/discovery) (LAN vs tailnet vs SSH)
|
||||
- [Pairing](/gateway/pairing)
|
||||
- [Discovery](/gateway/discovery)
|
||||
- [Bonjour](/gateway/bonjour)
|
||||
|
||||
@@ -6,9 +6,19 @@ read_when:
|
||||
---
|
||||
# Linux App
|
||||
|
||||
Clawdbot core is fully supported on Linux. The core is written in TypeScript, so it runs anywhere Node or Bun runs.
|
||||
The Gateway is fully supported on Linux. The core is written in TypeScript, so it runs anywhere Node or Bun runs.
|
||||
|
||||
We do not have a Linux companion app yet. It is planned, and we would love contributions to make it happen.
|
||||
Native Linux companion apps are planned. Contributions are welcome if you want to help build one.
|
||||
|
||||
## Beginner quick path (VPS)
|
||||
|
||||
1) Install Node 22+
|
||||
2) `npm i -g clawdbot@latest`
|
||||
3) `clawdbot onboard --install-daemon`
|
||||
4) From your laptop: `ssh -N -L 18789:127.0.0.1:18789 <user>@<host>`
|
||||
5) Open `http://127.0.0.1:18789/` and paste your token
|
||||
|
||||
Step-by-step VPS guide: [exe.dev](/platforms/exe-dev)
|
||||
|
||||
## Install
|
||||
- [Getting Started](/start/getting-started)
|
||||
@@ -35,12 +45,6 @@ clawdbot daemon install
|
||||
|
||||
Or:
|
||||
|
||||
```
|
||||
clawdbot daemon install
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
```
|
||||
clawdbot configure
|
||||
```
|
||||
@@ -54,7 +58,11 @@ clawdbot doctor
|
||||
```
|
||||
|
||||
## System control (systemd user unit)
|
||||
Full unit example lives in the [Gateway runbook](/gateway). Minimal setup:
|
||||
Clawdbot installs a systemd **user** service by default. Use a **system**
|
||||
service for shared or always-on servers. The full unit example and guidance
|
||||
live in the [Gateway runbook](/gateway).
|
||||
|
||||
Minimal setup:
|
||||
|
||||
Create `~/.config/systemd/user/clawdbot-gateway.service`:
|
||||
|
||||
|
||||
@@ -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 …` (LaunchAgent daemon)
|
||||
@@ -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.
|
||||
- The relay honors `__CLAWDBOT_VERSION__` / `CLAWDBOT_BUNDLED_VERSION` so `--version` doesn’t depend on reading `package.json` at runtime.
|
||||
|
||||
## Launchd (Gateway as LaunchAgent)
|
||||
|
||||
@@ -58,11 +58,13 @@ 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)
|
||||
- The macOS app owns LaunchAgent install/update for the bundled gateway.
|
||||
|
||||
Behavior:
|
||||
- “Clawdbot Active” enables/disables the LaunchAgent.
|
||||
- App quit does **not** stop the gateway (launchd keeps it alive).
|
||||
- CLI install (`clawdbot daemon install`) writes the same LaunchAgent; `clawdbot daemon install --force` rewrites it.
|
||||
- `clawdbot doctor` audits the LaunchAgent config and can update it to current defaults.
|
||||
|
||||
Logging:
|
||||
- launchd stdout/err: `/tmp/clawdbot/clawdbot-gateway.log`
|
||||
@@ -77,7 +79,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,18 +89,14 @@ 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)
|
||||
- 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)
|
||||
- Image operations prefer `/usr/bin/sips` on macOS (especially under bun).
|
||||
- When running in Node/dev, `sharp` is used when available.
|
||||
- This affects inbound/outbound media, screenshots, and tool 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).
|
||||
It’s started from the relay daemon process, so the relay binary includes Playwright deps.
|
||||
The Gateway starts the browser control server (loopback only) from the relay daemon process,
|
||||
so the relay binary includes Playwright deps.
|
||||
|
||||
## Tests / smoke checks
|
||||
|
||||
@@ -125,7 +123,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.
|
||||
|
||||
@@ -5,157 +5,117 @@ read_when:
|
||||
- Adding agent controls for visual workspace
|
||||
- Debugging WKWebView canvas loads
|
||||
---
|
||||
|
||||
# Canvas (macOS app)
|
||||
|
||||
Status: draft spec · Date: 2025-12-12
|
||||
The macOS app embeds an agent‑controlled **Canvas panel** using `WKWebView`. It
|
||||
is a lightweight visual workspace for HTML/CSS/JS, A2UI, and small interactive
|
||||
UI surfaces.
|
||||
|
||||
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`](/gateway/configuration).
|
||||
## Where Canvas lives
|
||||
|
||||
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).
|
||||
Canvas state is stored under Application Support:
|
||||
|
||||
This is designed for:
|
||||
- Agent-written HTML/CSS/JS on disk (per-session directory).
|
||||
- A real browser engine for layout, rendering, and basic interactivity.
|
||||
- Agent-driven visibility (show/hide), navigation, DOM/JS queries, and snapshots.
|
||||
- Minimal chrome: borderless panel; bezel/chrome appears only on hover.
|
||||
- `~/Library/Application Support/Clawdbot/canvas/<session>/...`
|
||||
|
||||
## Why a custom scheme (vs. loopback HTTP)
|
||||
The Canvas panel serves those files via a **custom URL scheme**:
|
||||
|
||||
Using `WKURLSchemeHandler` keeps Canvas entirely in-process:
|
||||
- No port conflicts and no extra local server lifecycle.
|
||||
- Easier to sandbox: only serve files we explicitly map.
|
||||
- Works offline and can use an ephemeral data store (no persistent cookies/cache).
|
||||
|
||||
If a Canvas page truly needs “real web” semantics (CORS, fetch to loopback endpoints, service workers), consider the loopback-server variant instead (out of scope for this doc).
|
||||
|
||||
## URL ↔ directory mapping
|
||||
|
||||
The Canvas scheme is:
|
||||
- `clawdbot-canvas://<session>/<path>`
|
||||
|
||||
Routing model:
|
||||
- `clawdbot-canvas://main/` → `<canvasRoot>/main/index.html` (or `index.htm`)
|
||||
- `clawdbot-canvas://main/yolo` → `<canvasRoot>/main/yolo/index.html` (or `index.htm`)
|
||||
Examples:
|
||||
- `clawdbot-canvas://main/` → `<canvasRoot>/main/index.html`
|
||||
- `clawdbot-canvas://main/assets/app.css` → `<canvasRoot>/main/assets/app.css`
|
||||
- `clawdbot-canvas://main/widgets/todo/` → `<canvasRoot>/main/widgets/todo/index.html`
|
||||
|
||||
Directory listings are not served.
|
||||
If no `index.html` exists at the root, the app shows a **built‑in scaffold page**.
|
||||
|
||||
When `/` has no `index.html` yet, the handler serves a **built-in scaffold page** (bundled with the macOS app).
|
||||
This is a visual placeholder only (no A2UI renderer).
|
||||
## Panel behavior
|
||||
|
||||
### Suggested on-disk location
|
||||
- Borderless, resizable panel anchored near the menu bar (or mouse cursor).
|
||||
- Remembers size/position per session.
|
||||
- Auto‑reloads when local canvas files change.
|
||||
- Only one Canvas panel is visible at a time (session is switched as needed).
|
||||
|
||||
Store Canvas state under the app support directory:
|
||||
- `~/Library/Application Support/Clawdbot/canvas/<session>/…`
|
||||
Canvas can be disabled from Settings → **Allow Canvas**. When disabled, canvas
|
||||
node commands return `CANVAS_DISABLED`.
|
||||
|
||||
This keeps it alongside other app-owned state and avoids mixing with `~/.clawdbot/` gateway config.
|
||||
## Agent API surface
|
||||
|
||||
## Panel behavior (agent-controlled)
|
||||
Canvas is exposed via the **node bridge**, so the agent can:
|
||||
|
||||
Canvas is presented as a borderless `NSPanel` (similar to the existing WebChat panel):
|
||||
- Can be shown/hidden at any time by the agent.
|
||||
- Supports an “anchored” presentation (near the menu bar icon or another anchor rect).
|
||||
- Uses a rounded container; shadow stays on, but **chrome/bezel only appears on hover**.
|
||||
- Default position is the **top-right corner** of the current screen’s visible frame (unless the user moved/resized it previously).
|
||||
- The panel is **user-resizable** (edge resize + hover resize handle) and the last frame is persisted per session.
|
||||
- show/hide the panel
|
||||
- navigate to a path or URL
|
||||
- evaluate JavaScript
|
||||
- capture a snapshot image
|
||||
|
||||
### Hover-only chrome
|
||||
CLI examples:
|
||||
|
||||
Implementation notes:
|
||||
- Keep the window borderless at all times (don’t toggle `styleMask`).
|
||||
- Add an overlay view inside the content container for chrome (stroke + subtle gradient/material).
|
||||
- Use an `NSTrackingArea` to fade the chrome in/out on `mouseEntered/mouseExited`.
|
||||
- Optionally show close/drag affordances only while hovered.
|
||||
```bash
|
||||
clawdbot nodes canvas present --node <id>
|
||||
clawdbot nodes canvas navigate --node <id> --url "/"
|
||||
clawdbot nodes canvas eval --node <id> --js "document.title"
|
||||
clawdbot nodes canvas snapshot --node <id>
|
||||
```
|
||||
|
||||
## Agent API surface (current)
|
||||
Notes:
|
||||
- `canvas.navigate` accepts **local canvas paths**, `http(s)` URLs, and `file://` URLs.
|
||||
- If you pass `"/"`, the Canvas shows the local scaffold or `index.html`.
|
||||
|
||||
Canvas is exposed via the Gateway **node bridge**, so the agent can:
|
||||
- Show/hide the panel.
|
||||
- Navigate to a path (relative to the session root).
|
||||
- Evaluate JavaScript and optionally return results.
|
||||
- Query/modify DOM (helpers mirroring “dom query/all/attr/click/type/wait” patterns).
|
||||
- Capture a snapshot image of the current canvas view.
|
||||
- Optionally set panel placement (screen `x/y` + `width/height`) when showing/navigating.
|
||||
## A2UI in Canvas
|
||||
|
||||
This should be modeled after `WebChatManager`/`WebChatSwiftUIWindowController` but targeting `clawdbot-canvas://…` URLs.
|
||||
A2UI is hosted by the Gateway canvas host and rendered inside the Canvas panel.
|
||||
When the Gateway advertises a Canvas host, the macOS app auto‑navigates to the
|
||||
A2UI host page on first open.
|
||||
|
||||
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`](/platforms/macos).
|
||||
|
||||
## Agent commands (current)
|
||||
|
||||
Use the main `clawdbot` CLI; it invokes canvas commands via `node.invoke`.
|
||||
|
||||
- `clawdbot nodes canvas present --node <id> [--target <...>] [--x/--y/--width/--height]`
|
||||
- Local targets map into the session directory via the custom scheme (directory targets resolve `index.html|index.htm`).
|
||||
- If `/` has no index file, Canvas shows the built-in scaffold page and returns `status: "welcome"`.
|
||||
- `clawdbot nodes canvas hide --node <id>`
|
||||
- `clawdbot nodes canvas eval --js <code> --node <id>`
|
||||
- `clawdbot nodes canvas snapshot --node <id>`
|
||||
|
||||
### Canvas A2UI
|
||||
|
||||
Canvas A2UI is hosted by the **Gateway canvas host** at:
|
||||
Default A2UI host URL:
|
||||
|
||||
```
|
||||
http://<gateway-host>:18793/__clawdbot__/a2ui/
|
||||
```
|
||||
|
||||
The macOS app simply renders that page in the Canvas panel. The agent can drive it with JSONL **server→client protocol messages** (one JSON object per line):
|
||||
### A2UI commands (v0.8)
|
||||
|
||||
- `clawdbot nodes canvas a2ui push --jsonl <path> --node <id>`
|
||||
- `clawdbot nodes canvas a2ui reset --node <id>`
|
||||
Canvas currently accepts **A2UI v0.8** server→client messages:
|
||||
|
||||
`push` expects a JSONL file where **each line is a single JSON object** (parsed and forwarded to the in-page A2UI renderer).
|
||||
- `beginRendering`
|
||||
- `surfaceUpdate`
|
||||
- `dataModelUpdate`
|
||||
- `deleteSurface`
|
||||
|
||||
Minimal example (v0.8):
|
||||
`createSurface` (v0.9) is not supported.
|
||||
|
||||
CLI example:
|
||||
|
||||
```bash
|
||||
cat > /tmp/a2ui-v0.8.jsonl <<'EOF'
|
||||
{"surfaceUpdate":{"surfaceId":"main","components":[{"id":"root","component":{"Column":{"children":{"explicitList":["title","content"]}}}},{"id":"title","component":{"Text":{"text":{"literalString":"Canvas (A2UI v0.8)"},"usageHint":"h1"}}},{"id":"content","component":{"Text":{"text":{"literalString":"If you can read this, `nodes canvas a2ui push` works."},"usageHint":"body"}}}]}}
|
||||
cat > /tmp/a2ui-v0.8.jsonl <<'EOFA2'
|
||||
{"surfaceUpdate":{"surfaceId":"main","components":[{"id":"root","component":{"Column":{"children":{"explicitList":["title","content"]}}}},{"id":"title","component":{"Text":{"text":{"literalString":"Canvas (A2UI v0.8)"},"usageHint":"h1"}}},{"id":"content","component":{"Text":{"text":{"literalString":"If you can read this, A2UI push works."},"usageHint":"body"}}}]}}
|
||||
{"beginRendering":{"surfaceId":"main","root":"root"}}
|
||||
EOF
|
||||
EOFA2
|
||||
|
||||
clawdbot nodes canvas a2ui push --jsonl /tmp/a2ui-v0.8.jsonl --node <id>
|
||||
```
|
||||
|
||||
Notes:
|
||||
- This does **not** support the A2UI v0.9 examples using `createSurface`.
|
||||
- A2UI **fails** if the Gateway canvas host is unreachable (no local fallback).
|
||||
- `nodes canvas a2ui push` validates JSONL (line numbers on errors) and rejects v0.9 payloads.
|
||||
- Quick smoke: `clawdbot nodes canvas a2ui push --node <id> --text "Hello from A2UI"` renders a minimal v0.8 view.
|
||||
Quick smoke:
|
||||
|
||||
## Triggering agent runs from Canvas (deep links)
|
||||
```bash
|
||||
clawdbot nodes canvas a2ui push --node <id> --text "Hello from A2UI"
|
||||
```
|
||||
|
||||
## Triggering agent runs from Canvas
|
||||
|
||||
Canvas can trigger new agent runs via deep links:
|
||||
|
||||
Canvas can trigger new agent runs via the macOS app deep-link scheme:
|
||||
- `clawdbot://agent?...`
|
||||
|
||||
This is intentionally separate from `clawdbot-canvas://…` (which is only for serving local Canvas files into the `WKWebView`).
|
||||
Example (in JS):
|
||||
|
||||
Suggested patterns:
|
||||
- HTML: render links/buttons that navigate to `clawdbot://agent?message=...`.
|
||||
- JS: set `window.location.href = 'clawdbot://agent?...'` for “run this now” actions.
|
||||
```js
|
||||
window.location.href = "clawdbot://agent?message=Review%20this%20design";
|
||||
```
|
||||
|
||||
Implementation note (important):
|
||||
- In `WKWebView`, intercept `clawdbot://…` navigations in `WKNavigationDelegate` and forward them to the app, e.g. by calling `DeepLinkHandler.shared.handle(url:)` and returning `.cancel` for the navigation.
|
||||
The app prompts for confirmation unless a valid key is provided.
|
||||
|
||||
Safety:
|
||||
- Deep links (`clawdbot://agent?...`) are always enabled.
|
||||
- Without a `key` query param, the app will prompt for confirmation before invoking the agent.
|
||||
- With a valid `key`, the run is unattended (no prompt). For Canvas-originated actions, the app injects an internal key automatically.
|
||||
## Security notes
|
||||
|
||||
## Security / guardrails
|
||||
|
||||
Recommended defaults:
|
||||
- `WKWebsiteDataStore.nonPersistent()` for Canvas (ephemeral).
|
||||
- Navigation policy: allow only `clawdbot-canvas://…` (and optionally `about:blank`); open `http/https` externally.
|
||||
- Scheme handler must prevent directory traversal: resolved file paths must stay under `<canvasRoot>/<session>/`.
|
||||
- Disable or tightly scope any JS bridge; prefer query-string/bootstrap config over `window.webkit.messageHandlers` for sensitive data.
|
||||
|
||||
## Debugging
|
||||
|
||||
Suggested debugging hooks:
|
||||
- Enable Web Inspector for Canvas builds (same approach as WebChat).
|
||||
- Log scheme requests + resolution decisions to OSLog (subsystem `com.clawdbot`, category `Canvas`).
|
||||
- Provide a “copy canvas dir” action in debug settings to quickly reveal the session directory in Finder.
|
||||
- Canvas scheme blocks directory traversal; files must live under the session root.
|
||||
- Local Canvas content uses a custom scheme (no loopback server required).
|
||||
- External `http(s)` URLs are allowed only when explicitly navigated.
|
||||
|
||||