Files
goclaw/websocket-protocol.md
yatulandClaude Opus 5 ea4890c257 fix(pairing): address review — docs, error mapping, approve feedback, unreadable expiry
- docs: device.pair.update and the `permanent` option on approve in
  docs/04-gateway-protocol.md, docs/19-websocket-rpc.md and
  websocket-protocol.md; the paired-device TTL row in docs/09-security.md
  now mentions the admin opt-out.
- store.ErrPairedDeviceNotFound: SetPairingPermanent wraps it in both stores.
  device.pair.update maps it to NOT_FOUND and any other store error to
  INTERNAL, so a DB failure no longer reads as "not found".
- web UI: approve and make-permanent/set-expiry now toast the server error
  and reload the list in `finally`. A partially applied approve (paired, but
  the permanent write failed) shows up in the table instead of leaving the
  dialog dead-ended.
- SQLite ListPaired: a stored expiry that fails to parse stays 0 (expires,
  date unknown) rather than being mistaken for permanent; the UI renders it
  as "--" instead of a 1970 date.

Tests: gateway handler error mapping (NOT_FOUND / INTERNAL / OK), sentinel
checks in the PG and SQLite store tests, SQLite unreadable-expiry case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 19:26:43 +04:00

101 lines
2.7 KiB
Markdown

# WebSocket Protocol (v3)
Frame types: `req` (client request), `res` (server response), `event` (server push).
## Authentication
The first request must be a `connect` handshake. Authentication supports three paths:
```json
// Path 1: Token-based (admin role)
{"type": "req", "id": 1, "method": "connect", "params": {"token": "your-gateway-token", "user_id": "alice"}}
// Path 2: Browser pairing reconnect (operator role)
{"type": "req", "id": 1, "method": "connect", "params": {"sender_id": "previously-paired-id", "user_id": "alice"}}
// Path 3: No token — initiates browser pairing flow (returns pairing code)
{"type": "req", "id": 1, "method": "connect", "params": {"user_id": "alice"}}
```
## Methods
| Method | Description |
|--------|-------------|
| `connect` | Authentication handshake (must be first request) |
| `health` | Server health check |
| `status` | Server status and metadata |
| `chat.send` | Send a message to an agent |
| `chat.history` | Retrieve session history |
| `chat.abort` | Abort a running agent request |
| `agent` | Get agent info |
| `sessions.list` | List active sessions |
| `sessions.delete` | Delete a session |
| `sessions.label` | Label a session |
| `skills.list` | List available skills |
| `cron.list` | List scheduled jobs |
| `cron.create` | Create a cron job |
| `cron.delete` | Delete a cron job |
| `cron.toggle` | Enable/disable a cron job |
| `models.list` | List available AI models |
| `browser.pairing.status` | Poll pairing approval status |
| `device.pair.request` | Request device pairing |
| `device.pair.approve` | Approve a pairing code |
| `device.pair.list` | List pending and approved pairings |
| `device.pair.revoke` | Revoke a pairing |
| `device.pair.update` | Make a pairing permanent or restore the default TTL |
## Events (server push)
| Event | Description |
|-------|-------------|
| `chunk` | Streaming token from LLM (payload: `{content}`) |
| `tool.call` | Agent invoking a tool (payload: `{name, id}`) |
| `tool.result` | Tool execution result |
| `run.started` | Agent started processing |
| `run.completed` | Agent finished processing |
| `shutdown` | Server shutting down |
## Frame Format
### Request (client to server)
```json
{
"type": "req",
"id": "unique-request-id",
"method": "chat.send",
"params": { ... }
}
```
### Response (server to client)
```json
{
"type": "res",
"id": "matching-request-id",
"ok": true,
"payload": { ... }
}
```
### Error Response
```json
{
"type": "res",
"id": "matching-request-id",
"ok": false,
"error": {
"code": "error_code",
"message": "Human-readable error message"
}
}
```
### Event (server push)
```json
{
"type": "event",
"event": "chunk",
"payload": { "content": "streaming text..." }
}
```