|
| 1 | +--- |
| 2 | +summary: "Build a third-party operator or WebChat client for the Gateway WebSocket protocol" |
| 3 | +read_when: |
| 4 | + - Building an operator, dashboard, or WebChat client outside the OpenClaw repository |
| 5 | + - Implementing Gateway reconnect, history, approvals, or device pairing |
| 6 | + - Updating a third-party client for a new Gateway wire version |
| 7 | +title: "Building a Gateway client" |
| 8 | +--- |
| 9 | + |
| 10 | +Use the published Gateway packages to build operator dashboards, WebChat clients, |
| 11 | +and other third-party applications. This guide covers the client lifecycle around |
| 12 | +the wire contract: authentication, capabilities, reconnect recovery, history, |
| 13 | +subscriptions, and version upgrades. |
| 14 | + |
| 15 | +For frame shapes, the handshake, errors, and the complete method surface, read the |
| 16 | +[Gateway protocol specification](https://docs.openclaw.ai/gateway/protocol). |
| 17 | + |
| 18 | +## Install the packages |
| 19 | + |
| 20 | +```bash |
| 21 | +npm install @openclaw/gateway-client @openclaw/gateway-protocol |
| 22 | +``` |
| 23 | + |
| 24 | +<Note> |
| 25 | +These packages ship with OpenClaw release trains. During the initial rollout, npm |
| 26 | +may return `E404` until the first package-bearing OpenClaw release is published; |
| 27 | +install them only after the registry pages below resolve. |
| 28 | +</Note> |
| 29 | + |
| 30 | +- [`@openclaw/gateway-protocol`](https://www.npmjs.com/package/@openclaw/gateway-protocol) |
| 31 | + provides schemas, runtime validators, TypeScript types, client identity and |
| 32 | + capability registries, structured error readers, and protocol version constants. |
| 33 | + Its npm tarball also includes the generated |
| 34 | + [`protocol.schema.json`](https://unpkg.com/@openclaw/gateway-protocol/protocol.schema.json) |
| 35 | + machine-readable contract. |
| 36 | +- [`@openclaw/gateway-client`](https://www.npmjs.com/package/@openclaw/gateway-client) |
| 37 | + is the reference connection implementation. Import the package root for the Node |
| 38 | + client and `@openclaw/gateway-client/browser` for the browser-safe protocol, |
| 39 | + device-auth, and reconnect helpers. |
| 40 | + |
| 41 | +The Node entry owns its WebSocket transport. A browser host supplies a WebSocket |
| 42 | +adapter plus persistent storage and signing callbacks for the device identity and |
| 43 | +device token. |
| 44 | + |
| 45 | +## Choose scopes and pair the device |
| 46 | + |
| 47 | +A full interactive chat client that also renders approval prompts should request |
| 48 | +`role: "operator"` with these scopes: |
| 49 | + |
| 50 | +| Scope | Use it for | |
| 51 | +| -------------------- | ----------------------------------------------------------------------------------------- | |
| 52 | +| `operator.read` | `chat.history`, `sessions.list`, `sessions.subscribe`, model status, and read-only events | |
| 53 | +| `operator.write` | `chat.send` and ordinary session mutations | |
| 54 | +| `operator.approvals` | Listing, displaying, and resolving exec or plugin approvals | |
| 55 | + |
| 56 | +Add `operator.questions` only if the client handles interactive questions, |
| 57 | +`operator.pairing` only if it manages paired devices or nodes, and |
| 58 | +`operator.admin` only for administrative operations such as `config.patch`. |
| 59 | +The [operator scopes reference](https://docs.openclaw.ai/gateway/operator-scopes) |
| 60 | +defines the complete method and approval-time rules. |
| 61 | + |
| 62 | +Do not create a per-client bearer token by hand-editing `openclaw.json`. Configure |
| 63 | +the Gateway's shared bootstrap authentication with `openclaw configure --section |
| 64 | +gateway` or the `openclaw onboard --gateway-auth ...` options, then let device |
| 65 | +pairing mint the client token: |
| 66 | + |
| 67 | +1. Persist an Ed25519 device identity in the client. |
| 68 | +2. Wait for `connect.challenge`, sign the challenge-bound device payload, and send |
| 69 | + `connect` with the requested operator role, scopes, and the shared Gateway token |
| 70 | + or password for bootstrap authentication. |
| 71 | +3. If the Gateway returns structured `PAIRING_REQUIRED` details, show the request |
| 72 | + ID and pause or retry according to `error.details.recommendedNextStep`. |
| 73 | +4. On the Gateway host, review the request with `openclaw devices list`, then |
| 74 | + approve that exact current request with `openclaw devices approve <requestId>`. |
| 75 | +5. Reconnect and persist `hello-ok.auth.deviceToken` with the negotiated role and |
| 76 | + scopes. Use that device token for later connections. |
| 77 | + |
| 78 | +Scope or role upgrades create a new pending pairing request. Token rotation cannot |
| 79 | +expand the approved pairing contract. See the |
| 80 | +[Devices CLI](https://docs.openclaw.ai/cli/devices) for approval, rotation, and |
| 81 | +revocation commands. |
| 82 | + |
| 83 | +## Advertise client capabilities |
| 84 | + |
| 85 | +`connect.params.caps` describes optional behavior the client can consume. It does |
| 86 | +not grant authorization. Import names from `GATEWAY_CLIENT_CAPS` instead of |
| 87 | +duplicating string literals: |
| 88 | + |
| 89 | +```ts |
| 90 | +import { GATEWAY_CLIENT_CAPS } from "@openclaw/gateway-protocol/client-info"; |
| 91 | + |
| 92 | +const caps = [GATEWAY_CLIENT_CAPS.TOOL_EVENTS]; |
| 93 | +``` |
| 94 | + |
| 95 | +The current registry contains `approvals`, `exec-approvals`, `inline-widgets`, |
| 96 | +`run-tool-bindings`, `session-scoped-events`, `plugin-approvals`, |
| 97 | +`task-suggestions`, `terminal-offset-seq`, `tool-events`, and `ui-commands`. |
| 98 | +Advertise only capabilities the client actually implements. |
| 99 | + |
| 100 | +<Warning> |
| 101 | +`tool-events` gates live tool-execution streaming. The Gateway registers only |
| 102 | +connections that advertise this capability as recipients for a run's structured |
| 103 | +tool events. Without it, the connection receives no live tool events and the |
| 104 | +handshake does not report an error. |
| 105 | +</Warning> |
| 106 | + |
| 107 | +Capability-gated agent tools are a separate use of the same declaration. If an |
| 108 | +agent tool requires a client capability, the Gateway omits that tool unless the |
| 109 | +originating client advertised every required capability. |
| 110 | + |
| 111 | +## Recover state after reconnect |
| 112 | + |
| 113 | +Treat every successful reconnect as a new projection over durable history and |
| 114 | +current in-memory run state: |
| 115 | + |
| 116 | +1. Re-establish `sessions.subscribe` and the selected session's |
| 117 | + `sessions.messages.subscribe` subscription. |
| 118 | +2. Call `chat.history` for the selected `sessionKey` and replace local persisted |
| 119 | + rows with the returned `messages` projection. |
| 120 | +3. If `inFlightRun` is present, adopt its `runId`, buffered `text`, and optional |
| 121 | + `plan`. Adopt the run even when `text` is empty. |
| 122 | +4. Read `sessionInfo.hasActiveRun` and `sessionInfo.activeRunIds`. Prefer exact |
| 123 | + membership in `activeRunIds` when deciding whether a retained run still owns |
| 124 | + the streaming UI. A true `hasActiveRun` with no listed ID can represent another |
| 125 | + active runtime projection. |
| 126 | +5. Reconcile subsequent `agent` events by `payload.runId` and `payload.seq`. |
| 127 | + Maintain the highest accepted sequence independently for each run, ignore an |
| 128 | + already-seen or lower sequence, and treat a forward gap as a reason to reload |
| 129 | + authoritative history. |
| 130 | + |
| 131 | +The outer event frame also has an optional `seq`, which orders events on the |
| 132 | +current WebSocket connection. It resets with a new connection. The `seq` inside |
| 133 | +an `agent` event payload is assigned per run and orders that run's lifecycle, |
| 134 | +assistant, plan, tool, and other stream events. |
| 135 | + |
| 136 | +## Use history metadata and stable anchors |
| 137 | + |
| 138 | +Rows returned by `chat.history` can carry an `__openclaw` metadata envelope: |
| 139 | + |
| 140 | +- `id` is the transcript entry identity. Use it for anchored history requests, |
| 141 | + but not as a unique display-row key. |
| 142 | +- `seq` is the positive transcript-record sequence. One stored record can project |
| 143 | + into more than one display row, so keep siblings with the same `id` and sequence |
| 144 | + together. |
| 145 | +- `kind` identifies synthetic rows. A compaction boundary uses |
| 146 | + `kind: "compaction"` and may include `tokensBefore` and `tokensAfter` when a |
| 147 | + matching checkpoint recorded those metrics. |
| 148 | + |
| 149 | +Page backward with the response's `hasMore` and `nextOffset` values. Numeric |
| 150 | +offsets describe the current transcript projection, so do not persist them as |
| 151 | +long-lived bookmarks across reset or compaction. Persist `__openclaw.id` instead. |
| 152 | +To restore around a known row, call `chat.history` with `messageId` and the |
| 153 | +`sessionId` that returned it. The Gateway can resolve that anchor from reset |
| 154 | +archive history; anchored responses intentionally omit numeric paging metadata. |
| 155 | + |
| 156 | +## Subscribe instead of polling usage |
| 157 | + |
| 158 | +Load the initial catalog with `sessions.list`, then call `sessions.subscribe` once |
| 159 | +per connection. Merge `sessions.changed` events by `sessionKey`. Session change |
| 160 | +payloads can carry live `inputTokens`, `outputTokens`, `totalTokens`, |
| 161 | +`totalTokensFresh`, `contextTokens`, `estimatedCostUsd`, response-usage settings, |
| 162 | +and active-run state. |
| 163 | + |
| 164 | +Some change notifications are only invalidation signals. If an event omits the |
| 165 | +row fields your view needs, refresh `sessions.list`. Do not poll `usage.cost` or |
| 166 | +`sessions.usage` to keep a live session list current; reserve those methods for |
| 167 | +on-demand aggregate or detailed reports. |
| 168 | + |
| 169 | +## Backfill exec approvals |
| 170 | + |
| 171 | +A client with `operator.approvals` should install its event listener as soon as |
| 172 | +`hello-ok` completes, then call `exec.approval.list` to backfill requests that |
| 173 | +predate the connection. Reconcile the list and live |
| 174 | +`exec.approval.requested` / `exec.approval.resolved` events by approval ID so a |
| 175 | +transition racing the list request is neither lost nor resurrected. |
| 176 | + |
| 177 | +## Track protocol versions |
| 178 | + |
| 179 | +The current wire version is `4`. General operator and WebChat clients must |
| 180 | +negotiate the exact current version with `minProtocol: 4` and `maxProtocol: 4`. |
| 181 | +Only authenticated node clients and lightweight probes have the N-1 acceptance |
| 182 | +window, currently protocol `3` through `4`. |
| 183 | + |
| 184 | +Protocol changes are additive first. `protocol.schema.json` includes `since` |
| 185 | +release-vintage metadata and required scope metadata for core methods, but a wire |
| 186 | +version bump is still an explicit breaking event for third-party clients. Pin the |
| 187 | +package versions you test, upgrade the client and Gateway together when the wire |
| 188 | +version changes, and review the |
| 189 | +[OpenClaw changelog](https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md) |
| 190 | +before each upgrade. |
| 191 | + |
| 192 | +## Related |
| 193 | + |
| 194 | +- [Gateway protocol](https://docs.openclaw.ai/gateway/protocol) |
| 195 | +- [Embedding OpenClaw](https://docs.openclaw.ai/gateway/embedding) |
| 196 | +- [Gateway RPC reference](https://docs.openclaw.ai/reference/rpc) |
| 197 | +- [Gateway integrations for external apps](https://docs.openclaw.ai/gateway/external-apps) |
0 commit comments