forked from bots-garden/ori
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 1 | # Reference: WebSocket protocol |
| 2 | ||
| 3 | > Neutral, exhaustive description of the messages exchanged between the browser and the ori backend on `GET /ws`. One JSON object per text frame. ACP payloads are relayed verbatim; their shapes are specified by the [Agent Client Protocol](https://agentclientprotocol.com). | |
| 4 | ||
| 5 | ## Connection sequence | |
| 6 | ||
| 7 | On every connection the server sends `hello`, then replays the recorded session history (up to 4096 events), then any permission request still awaiting an answer, then live events as they happen. | |
| 8 | ||
| 9 | ## Browser → server messages | |
| 10 | ||
| 11 | ### `prompt` | |
| 12 | ||
| 13 | Starts a turn. | |
| 14 | ||
| 15 | | Field | Type | Required | Description | | |
| 16 | | --- | --- | --- | --- | | |
| 17 | | `type` | string | yes | `"prompt"` | | |
| 18 | | `text` | string | yes | The user's message. | | |
| 19 | | `attachments` | array | no | Files mentioned with `@` in the text: `{ "path": "<absolute or root-relative path>", "name": "<label>" }`. | | |
| 20 | ||
| 21 | ```json | |
| 22 | { "type": "prompt", "text": "Explain @src/main.go", "attachments": [ { "path": "/work/src/main.go", "name": "src/main.go" } ] } | |
| 23 | ``` | |
| 24 | ||
| 25 | The server sends the ACP prompt as a `text` content block followed by one `resource_link` block per attachment (`uri: "file://<absolute path>"`, `name` as given, defaulting to the path). Relative paths are joined to the server's `--cwd`; attachments with an empty `path` are dropped. The `@name` mention stays in the text. | |
| 26 | ||
| 27 | ### `cancel` | |
| 28 | ||
| 29 | Interrupts the running turn. | |
| 30 | ||
| 31 | | Field | Type | Required | Description | | |
| 32 | | --- | --- | --- | --- | | |
| 33 | | `type` | string | yes | `"cancel"` | | |
| 34 | ||
| 35 | ### `permission_response` | |
| 36 | ||
| 37 | Answers a `permission_request`. | |
| 38 | ||
| 39 | | Field | Type | Required | Description | | |
| 40 | | --- | --- | --- | --- | | |
| 41 | | `type` | string | yes | `"permission_response"` | | |
| 42 | | `requestId` | string | yes | The id from the `permission_request` event. | | |
| 43 | | `optionId` | string | one of the two | The chosen ACP permission option. | | |
| 44 | | `cancelled` | boolean | one of the two | `true` dismisses the request without choosing. | | |
| 45 | ||
| 46 | ## Server → browser messages | |
| 47 | ||
| 48 | ### `hello` | |
| 49 | ||
| 50 | | Field | Type | Description | | |
| 51 | | --- | --- | --- | | |
| 52 | | `sessionId` | string | The ACP session id, empty if no agent is attached. | | |
| 53 | | `turnActive` | boolean | `true` if a turn is currently running. | | |
| 54 | ||
| 55 | ### `user_message` | |
| 56 | ||
| 57 | Echo of the prompt that started a turn; also present in replays. | |
| 58 | ||
| 59 | | Field | Type | Description | | |
| 60 | | --- | --- | --- | | |
| 61 | | `text` | string | The user's message. | | |
| 62 | | `attachments` | array | The `attachments` of the prompt, verbatim (absent when there were none). | | |
| 63 | ||
| 64 | ### `turn_started` / `turn_ended` | |
| 65 | ||
| 66 | | Field | Type | Description | | |
| 67 | | --- | --- | --- | | |
| 68 | | `stopReason` | string | `turn_ended` only: the ACP stop reason (`end_turn`, `cancelled`, `refusal`, `max_tokens`, `max_turn_requests`). | | |
| 69 | ||
| 70 | ### `session_update` | |
| 71 | ||
| 72 | | Field | Type | Description | | |
| 73 | | --- | --- | --- | | |
| 74 | | `update` | object | One raw ACP `SessionUpdate`, discriminated by its `sessionUpdate` field (`agent_message_chunk`, `agent_thought_chunk`, `tool_call`, `tool_call_update`, `plan`, `available_commands_update`, …). | | |
| 75 | ||
| 76 | The SPA renders message and thought chunks, tool calls and the plan, and keeps the `availableCommands` of the last `available_commands_update` for the composer's `/` selector (the mock agent sends one at session start; the Claude Code adapter sends Claude Code's slash commands). Other kinds are ignored. | |
| 77 | ||
| 78 | ```json | |
| 79 | { "type": "session_update", "update": { "sessionUpdate": "agent_message_chunk", "content": { "type": "text", "text": "Hello" } } } | |
| 80 | ``` | |
| 81 | ||
| 82 | ### `permission_request` | |
| 83 | ||
| 84 | | Field | Type | Description | | |
| 85 | | --- | --- | --- | | |
| 86 | | `requestId` | string | Correlates with `permission_response` and `permission_resolved`. | | |
| 87 | | `request` | object | The raw ACP `RequestPermissionRequest` (`toolCall`, `options`). | | |
| 88 | ||
| 89 | ### `permission_resolved` | |
| 90 | ||
| 91 | | Field | Type | Description | | |
| 92 | | --- | --- | --- | | |
| 93 | | `requestId` | string | The request that was answered (by any connected client, or cancelled by the agent). | | |
| 94 | ||
| 95 | ### `error` | |
| 96 | ||
| 97 | | Field | Type | Description | | |
| 98 | | --- | --- | --- | | |
| 99 | | `message` | string | Human-readable error (`"a turn is already running"`, `"no agent session"`, `"prompt failed: …"`, `"cancel failed: …"`, `"malformed message: …"`, `"unknown message type …"`). | | |
| 100 | ||
| 101 | ## Recorded vs transient events | |
| 102 | ||
| 103 | Events replayed to late-joining clients: `user_message`, `turn_started`, `turn_ended`, `session_update`, `permission_resolved`, plus pending `permission_request`s. Not recorded: `hello` (regenerated per connection) and `error` events emitted outside a turn failure. |