diff --git a/docs/concepts/architecture.md b/docs/concepts/architecture.md index f473b4c6a..1fa4cef3a 100644 --- a/docs/concepts/architecture.md +++ b/docs/concepts/architecture.md @@ -11,9 +11,11 @@ Last updated: 2026-01-05 - 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 +- Control-plane clients (macOS app, CLI, web UI, automations) connect to the + Gateway over **WebSocket** on the configured bind host (default `127.0.0.1:18789`). +- **Nodes** (macOS/iOS/Android) use the **Bridge** protocol (TCP JSONL) instead + of the WebSocket control plane. - 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. @@ -36,6 +38,10 @@ Last updated: 2026-01-05 - Pair with the Gateway to receive a token. - Expose commands like `canvas.*`, `camera.*`, `screen.record`, `location.get`. +Protocol details: +- [Gateway protocol](/gateway/protocol) +- [Bridge protocol](/gateway/bridge-protocol) + ### WebChat - Static UI that uses the Gateway WS API for chat history and sends. - In remote setups, connects through the same SSH/Tailscale tunnel as other diff --git a/docs/docs.json b/docs/docs.json index fba1f321e..a216b7823 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -635,6 +635,8 @@ "group": "Gateway & Ops", "pages": [ "gateway", + "gateway/protocol", + "gateway/bridge-protocol", "gateway/pairing", "gateway/gateway-lock", "environment", diff --git a/docs/gateway/bridge-protocol.md b/docs/gateway/bridge-protocol.md new file mode 100644 index 000000000..995e2fe18 --- /dev/null +++ b/docs/gateway/bridge-protocol.md @@ -0,0 +1,67 @@ +--- +summary: "Bridge protocol (nodes): TCP JSONL, pairing, scoped RPC" +read_when: + - Building or debugging node clients (iOS/Android/macOS node mode) + - Investigating pairing or bridge auth failures + - Auditing the node surface exposed by the gateway +--- + +# Bridge protocol (Node transport) + +The Bridge protocol is a **narrow, authenticated** transport for nodes +(iOS/Android/macOS node mode). It keeps the Gateway WS control plane loopback‑only +and exposes only a scoped set of methods for nodes. + +If you are building an operator client (CLI, web UI, automations), use the +[Gateway protocol](/gateway/protocol). + +## Why we have both + +- **Security boundary**: the bridge exposes a small allowlist instead of the + full gateway API surface. +- **Pairing + node identity**: node admission is owned by the gateway and tied + to a per-node token. +- **Discovery UX**: nodes can discover gateways via Bonjour on LAN, or connect + directly over a tailnet. +- **Loopback WS**: the full WS control plane stays local unless tunneled via SSH. + +## Transport + +- TCP, one JSON object per line (JSONL). +- Gateway owns the listener (default `18790`). + +## Handshake + pairing + +1) Client sends `hello` with node metadata + token (if already paired). +2) If not paired, gateway replies `error` (`NOT_PAIRED`/`UNAUTHORIZED`). +3) Client sends `pair-request`. +4) Gateway waits for approval, then sends `pair-ok` and `hello-ok`. + +`hello-ok` returns `serverName` and may include `canvasHostUrl`. + +## Frames + +Client → Gateway: +- `req` / `res`: scoped gateway RPC (chat, sessions, config, health, voicewake) +- `event`: node signals (voice transcript, agent request, chat subscribe) + +Gateway → Client: +- `invoke` / `invoke-res`: node commands (`canvas.*`, `camera.*`, `screen.record`, + `location.get`, `sms.send`) +- `event`: chat updates for subscribed sessions +- `ping` / `pong`: keepalive + +Exact allowlist is enforced in `src/gateway/server-bridge.ts`. + +## Tailnet usage + +- Bind the bridge to a tailnet IP: `bridge.bind: "tailnet"` in + `~/.clawdbot/clawdbot.json`. +- Clients connect via MagicDNS name or tailnet IP. +- Bonjour does **not** cross networks; use manual host/port or wide-area DNS‑SD + when needed. + +## Versioning + +Bridge is currently **implicit v1** (no min/max negotiation). Backward‑compat +is expected; add a bridge protocol version field before any breaking changes. diff --git a/docs/gateway/discovery.md b/docs/gateway/discovery.md index 09a2dd6b3..af3429ba6 100644 --- a/docs/gateway/discovery.md +++ b/docs/gateway/discovery.md @@ -21,6 +21,10 @@ The design goal is to keep all network discovery/advertising in the **Node Gatew - **Bridge (direct transport)**: a LAN/tailnet-facing endpoint owned by the gateway that allows authenticated clients/nodes to call a scoped subset of gateway methods. The bridge exists so the gateway can remain loopback-only. - **SSH transport (fallback)**: remote control by forwarding `127.0.0.1:18789` over SSH. +Protocol details: +- [Gateway protocol](/gateway/protocol) +- [Bridge protocol](/gateway/bridge-protocol) + ## Why we keep both “direct” and SSH - **Direct bridge** is the best UX on the same network and within a tailnet: diff --git a/docs/gateway/index.md b/docs/gateway/index.md index 9322fc8c6..e5d2f58f4 100644 --- a/docs/gateway/index.md +++ b/docs/gateway/index.md @@ -103,6 +103,7 @@ CLAWDBOT_CONFIG_PATH=~/.clawdbot/b.json CLAWDBOT_STATE_DIR=~/.clawdbot-b clawdbo ``` ## Protocol (operator view) +- Full docs: [Gateway protocol](/gateway/protocol) and [Bridge protocol](/gateway/bridge-protocol). - Mandatory first frame from client: `req {type:"req", id, method:"connect", params:{minProtocol,maxProtocol,client:{id,displayName?,version,platform,deviceFamily?,modelIdentifier?,mode,instanceId?}, caps, auth?, locale?, userAgent? } }`. - Gateway replies `res {type:"res", id, ok:true, payload:hello-ok }` (or `ok:false` with an error, then closes). - After handshake: diff --git a/docs/gateway/protocol.md b/docs/gateway/protocol.md new file mode 100644 index 000000000..1138e7f63 --- /dev/null +++ b/docs/gateway/protocol.md @@ -0,0 +1,86 @@ +--- +summary: "Gateway WebSocket protocol: handshake, frames, versioning" +read_when: + - Implementing or updating gateway WS clients + - Debugging protocol mismatches or connect failures + - Regenerating protocol schema/models +--- + +# Gateway protocol (WebSocket) + +The Gateway WS protocol is the **full control plane** for Clawdbot. It is +loopback-only by default and is intended for local clients (CLI, web UI, +automations). + +If you are building a **node client** (iOS/Android/macOS node mode), use the +[Bridge protocol](/gateway/bridge-protocol) instead. + +## Transport + +- WebSocket, text frames with JSON payloads. +- First frame **must** be a `connect` request. + +## Handshake (connect) + +Client → Gateway: + +```json +{ + "type": "req", + "id": "…", + "method": "connect", + "params": { + "minProtocol": 3, + "maxProtocol": 3, + "client": { + "id": "cli", + "version": "1.2.3", + "platform": "macos", + "mode": "operator" + }, + "caps": [], + "auth": { "token": "…" }, + "locale": "en-US", + "userAgent": "clawdbot-cli/1.2.3" + } +} +``` + +Gateway → Client: + +```json +{ + "type": "res", + "id": "…", + "ok": true, + "payload": { "type": "hello-ok", "protocol": 3, "policy": { "tickIntervalMs": 15000 } } +} +``` + +## Framing + +- **Request**: `{type:"req", id, method, params}` +- **Response**: `{type:"res", id, ok, payload|error}` +- **Event**: `{type:"event", event, payload, seq?, stateVersion?}` + +Side-effecting methods require **idempotency keys** (see schema). + +## Versioning + +- `PROTOCOL_VERSION` lives in `src/gateway/protocol/schema.ts`. +- Clients send `minProtocol` + `maxProtocol`; the server rejects mismatches. +- Schemas + models are generated from TypeBox definitions: + - `pnpm protocol:gen` + - `pnpm protocol:gen:swift` + - `pnpm protocol:check` + +## Auth + +- If `CLAWDBOT_GATEWAY_TOKEN` (or `--token`) is set, `connect.params.auth.token` + must match or the socket is closed. + +## Scope + +This protocol exposes the **full gateway API** (status, providers, models, +chat, agent, sessions, nodes, etc.). The exact surface is defined by the +TypeBox schemas in `src/gateway/protocol/schema.ts`. diff --git a/docs/nodes/index.md b/docs/nodes/index.md index 285eb639d..dcbb6f76c 100644 --- a/docs/nodes/index.md +++ b/docs/nodes/index.md @@ -8,7 +8,7 @@ read_when: # Nodes -A **node** is a companion device (iOS/Android today) that connects to the Gateway over the **Bridge** and exposes a command surface (e.g. `canvas.*`, `camera.*`, `system.*`) via `node.invoke`. +A **node** is a companion device (iOS/Android today) that connects to the Gateway over the **Bridge** and exposes a command surface (e.g. `canvas.*`, `camera.*`, `system.*`) via `node.invoke`. Bridge protocol details: [Bridge protocol](/gateway/bridge-protocol). macOS can also run in **node mode**: the menubar app connects to the Gateway’s bridge and exposes its local canvas/camera commands as a node (so `clawdbot nodes …` works against this Mac). diff --git a/docs/platforms/android.md b/docs/platforms/android.md index 83c452518..d096b15aa 100644 --- a/docs/platforms/android.md +++ b/docs/platforms/android.md @@ -13,6 +13,7 @@ read_when: - 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). + - Protocols: [Bridge protocol](/gateway/bridge-protocol) (nodes) and [Gateway protocol](/gateway/protocol) (control plane). ## System control System control (launchd/systemd) lives on the Gateway host. See [Gateway](/gateway).