{"asyncapi":"3.0.0","info":{"title":"clawborrator hub_v1 WebSocket protocol","version":"0.3.0","description":"Three WebSocket surfaces:\n\n  * **/channel** — clawborrator-mcp ↔ hub. The MCP child of a Claude\n    Code instance opens this on register, ships chat/tail events and\n    permission requests, services peer-routing, etc. Long-lived; one\n    per CC session.\n\n  * **/cli** — `claw session attach` (or any browser TUI) ↔ hub. The\n    operator's eyes-and-write surface. Subscribes to a session,\n    receives the broadcast firehose, sends prompts / op-messages /\n    approvals.\n\n  * **/supervisor** — clawborrator-supervisor (desktop daemon) ↔ hub.\n    Long-lived RPC channel: daemon registers with `hello`, hub then\n    sends `cmd` frames to operate managed sessions (create / kill /\n    restart / screenshot) and the daemon replies with `ok`/`err`\n    keyed by command id.\n\nAuth differs per surface:\n  * /channel takes a channel token (`ck_live_…`) as a `register`\n    message field, NOT in HTTP headers.\n  * /cli takes a session token (`cw_sess_…`) as either an HttpOnly\n    cookie (browser, set by /api/v1/auth/oauth/callback) OR a Bearer\n    Authorization header (CLI, returned by /api/v1/auth/oauth/token)\n    OR a `Sec-WebSocket-Protocol: bearer, <token>` subprotocol\n    (browser fallback when cookies aren't viable).\n  * /supervisor takes an `Authorization: Bearer cw_app_…` header\n    (preferred — minted via the SPA OAuth + PKCE flow on first run)\n    or `cw_sess_…` (also accepted).\n\nCompanion docs:\n  * Wire-level walkthrough with mermaid diagrams: `docs/CLI-WS-PROTOCOL.md`\n  * Cross-session routing context impact:        `docs/PEER-ROUTING.md`\n  * REST surface (/api/v1/*):                    `/docs` (OpenAPI)\n","contact":{"name":"clawborrator","url":"https://github.com/clawborrator/hub_v1"},"license":{"name":"MIT"}},"servers":{"hosted":{"host":"next.clawborrator.com","protocol":"wss","description":"hosted hub"},"local":{"host":"localhost:8787","protocol":"ws","description":"local dev"}},"channels":{"cli":{"address":"/cli","title":"Operator (CLI / browser) WS","description":"Operator-facing read/write surface. Operators subscribe to a\nsessionId and receive every chat/tail event broadcast by /channel\nWS or /api/channel/event ingests for that session, plus permission\nrelays, presence deltas, channel-online/offline transitions, and\nfile events. Operators can write prompts, op-messages, approvals.\n\n**Agent interception** — when an outbound `prompt` frame's `text`\nstarts with `@<owner-login>/<slug> ` (anchored at position 0), the\nhub treats it as an invocation of a published expert agent and\nroutes it to the agent's session instead of the source session's\nClaude. The agent's `mcp__clawborrator__reply` lands back in the\nsource session as a `chat/reply` event with `payload.source =\n'peer-reply'` and `payload.agentHandle = '<owner>/<slug>'`. No new\nWS message types are involved — this is purely server-side\nrewiring inside `services/prompts.ts → maybeInterceptAgentPrompt`.\nSee REST `/api/v1/agents` for the agent management surface.\n","messages":{"cli_out_subscribe":{"$ref":"#/components/messages/cli_out_subscribe"},"cli_out_unsubscribe":{"$ref":"#/components/messages/cli_out_unsubscribe"},"cli_out_prompt":{"$ref":"#/components/messages/cli_out_prompt"},"cli_out_op_message":{"$ref":"#/components/messages/cli_out_op_message"},"cli_out_approval":{"$ref":"#/components/messages/cli_out_approval"},"cli_out_route":{"$ref":"#/components/messages/cli_out_route"},"cli_in_subscribed":{"$ref":"#/components/messages/cli_in_subscribed"},"cli_in_event":{"$ref":"#/components/messages/cli_in_event"},"cli_in_op_message":{"$ref":"#/components/messages/cli_in_op_message"},"cli_in_permission_request":{"$ref":"#/components/messages/cli_in_permission_request"},"cli_in_permission_resolved":{"$ref":"#/components/messages/cli_in_permission_resolved"},"cli_in_presence":{"$ref":"#/components/messages/cli_in_presence"},"cli_in_channel_status":{"$ref":"#/components/messages/cli_in_channel_status"},"cli_in_file_event":{"$ref":"#/components/messages/cli_in_file_event"},"cli_in_ack":{"$ref":"#/components/messages/cli_in_ack"},"cli_in_error":{"$ref":"#/components/messages/cli_in_error"}}},"channel":{"address":"/channel","title":"Channel (clawborrator-mcp) WS","description":"Channel-side bidirectional. `register` is the first message after\nopen and carries the channel token; the hub responds with\n`welcome` once auth + session resolution succeed. After that, the\nMCP ships chat_event / tail_event frames for everything CC does,\nforwards permission_request frames CC raises, and handles\nroute_request / probe_request / list_peers_request from Claude's\ntool calls. Hub pushes back `prompt` (operator-driven or\npeer-routed), `permission_response`, `route_reply`, etc.\n","messages":{"ch_out_register":{"$ref":"#/components/messages/ch_out_register"},"ch_out_chat_event":{"$ref":"#/components/messages/ch_out_chat_event"},"ch_out_tail_event":{"$ref":"#/components/messages/ch_out_tail_event"},"ch_out_permission_request":{"$ref":"#/components/messages/ch_out_permission_request"},"ch_out_route_request":{"$ref":"#/components/messages/ch_out_route_request"},"ch_out_probe_request":{"$ref":"#/components/messages/ch_out_probe_request"},"ch_out_list_peers_request":{"$ref":"#/components/messages/ch_out_list_peers_request"},"ch_out_pong":{"$ref":"#/components/messages/ch_out_pong"},"ch_in_welcome":{"$ref":"#/components/messages/ch_in_welcome"},"ch_in_prompt":{"$ref":"#/components/messages/ch_in_prompt"},"ch_in_permission_response":{"$ref":"#/components/messages/ch_in_permission_response"},"ch_in_route_response":{"$ref":"#/components/messages/ch_in_route_response"},"ch_in_route_reply":{"$ref":"#/components/messages/ch_in_route_reply"},"ch_in_probe_response":{"$ref":"#/components/messages/ch_in_probe_response"},"ch_in_peers_update":{"$ref":"#/components/messages/ch_in_peers_update"},"ch_in_list_peers_response":{"$ref":"#/components/messages/ch_in_list_peers_response"},"ch_in_bye":{"$ref":"#/components/messages/ch_in_bye"},"ch_in_ping":{"$ref":"#/components/messages/ch_in_ping"},"ch_in_error":{"$ref":"#/components/messages/ch_in_error"}}},"supervisor":{"address":"/supervisor","title":"Supervisor (desktop daemon) WS","description":"Long-lived control channel between the hub and a desktop daemon\n(clawborrator-supervisor). The daemon connects with a Bearer\ntoken and identifies its `machine_id` in a `hello` frame; the\nhub responds with `hello_ack`. After that, the daemon is a\npassive RPC endpoint — the hub sends `cmd` frames to operate\nmanaged sessions (create / kill / restart / screenshot) and\nthe daemon replies with `ok` or `err` keyed by command id.\n\nAuth: `Authorization: Bearer cw_app_…` (preferred — minted via\nthe SPA OAuth + PKCE flow on first run) or `cw_sess_…` (also\naccepted).\n","messages":{"sv_out_hello":{"$ref":"#/components/messages/sv_out_hello"},"sv_out_ping":{"$ref":"#/components/messages/sv_out_ping"},"sv_out_pong":{"$ref":"#/components/messages/sv_out_pong"},"sv_out_ok":{"$ref":"#/components/messages/sv_out_ok"},"sv_out_err":{"$ref":"#/components/messages/sv_out_err"},"sv_out_evt":{"$ref":"#/components/messages/sv_out_evt"},"sv_in_hello_ack":{"$ref":"#/components/messages/sv_in_hello_ack"},"sv_in_ping":{"$ref":"#/components/messages/sv_in_ping"},"sv_in_pong":{"$ref":"#/components/messages/sv_in_pong"},"sv_in_cmd":{"$ref":"#/components/messages/sv_in_cmd"},"sv_in_error":{"$ref":"#/components/messages/sv_in_error"}}}},"operations":{"cli_send":{"action":"send","channel":{"$ref":"#/channels/cli"},"title":"Operator → hub","summary":"Frames the operator (claw or browser demo) sends after a successful WS upgrade.","messages":[{"$ref":"#/channels/cli/messages/cli_out_subscribe"},{"$ref":"#/channels/cli/messages/cli_out_unsubscribe"},{"$ref":"#/channels/cli/messages/cli_out_prompt"},{"$ref":"#/channels/cli/messages/cli_out_op_message"},{"$ref":"#/channels/cli/messages/cli_out_approval"},{"$ref":"#/channels/cli/messages/cli_out_route"}]},"cli_receive":{"action":"receive","channel":{"$ref":"#/channels/cli"},"title":"Hub → operator","summary":"Frames the hub pushes to /cli WS subscribers (events, presence, permission relays, file events, etc.).","messages":[{"$ref":"#/channels/cli/messages/cli_in_subscribed"},{"$ref":"#/channels/cli/messages/cli_in_event"},{"$ref":"#/channels/cli/messages/cli_in_op_message"},{"$ref":"#/channels/cli/messages/cli_in_permission_request"},{"$ref":"#/channels/cli/messages/cli_in_permission_resolved"},{"$ref":"#/channels/cli/messages/cli_in_presence"},{"$ref":"#/channels/cli/messages/cli_in_channel_status"},{"$ref":"#/channels/cli/messages/cli_in_file_event"},{"$ref":"#/channels/cli/messages/cli_in_ack"},{"$ref":"#/channels/cli/messages/cli_in_error"}]},"channel_send":{"action":"send","channel":{"$ref":"#/channels/channel"},"title":"Channel (clawborrator-mcp) → hub","summary":"Frames the MCP ships up — register/heartbeat, captured CC events, permission gates, peer-routing tool calls.","messages":[{"$ref":"#/channels/channel/messages/ch_out_register"},{"$ref":"#/channels/channel/messages/ch_out_chat_event"},{"$ref":"#/channels/channel/messages/ch_out_tail_event"},{"$ref":"#/channels/channel/messages/ch_out_permission_request"},{"$ref":"#/channels/channel/messages/ch_out_route_request"},{"$ref":"#/channels/channel/messages/ch_out_probe_request"},{"$ref":"#/channels/channel/messages/ch_out_list_peers_request"},{"$ref":"#/channels/channel/messages/ch_out_pong"}]},"channel_receive":{"action":"receive","channel":{"$ref":"#/channels/channel"},"title":"Hub → channel","summary":"Frames the hub pushes to a registered channel — operator-driven prompts, permission resolutions, peer-route replies, peer-list updates, lifecycle.","messages":[{"$ref":"#/channels/channel/messages/ch_in_welcome"},{"$ref":"#/channels/channel/messages/ch_in_prompt"},{"$ref":"#/channels/channel/messages/ch_in_permission_response"},{"$ref":"#/channels/channel/messages/ch_in_route_response"},{"$ref":"#/channels/channel/messages/ch_in_route_reply"},{"$ref":"#/channels/channel/messages/ch_in_probe_response"},{"$ref":"#/channels/channel/messages/ch_in_peers_update"},{"$ref":"#/channels/channel/messages/ch_in_list_peers_response"},{"$ref":"#/channels/channel/messages/ch_in_bye"},{"$ref":"#/channels/channel/messages/ch_in_ping"},{"$ref":"#/channels/channel/messages/ch_in_error"}]},"supervisor_send":{"action":"send","channel":{"$ref":"#/channels/supervisor"},"title":"Daemon → hub","summary":"Frames the desktop daemon emits — hello on connect, ping/pong, RPC responses, evt for daemon-initiated state pushes.","messages":[{"$ref":"#/channels/supervisor/messages/sv_out_hello"},{"$ref":"#/channels/supervisor/messages/sv_out_ping"},{"$ref":"#/channels/supervisor/messages/sv_out_pong"},{"$ref":"#/channels/supervisor/messages/sv_out_ok"},{"$ref":"#/channels/supervisor/messages/sv_out_err"},{"$ref":"#/channels/supervisor/messages/sv_out_evt"}]},"supervisor_receive":{"action":"receive","channel":{"$ref":"#/channels/supervisor"},"title":"Hub → daemon","summary":"Frames the hub sends — hello_ack after registration, ping/pong, cmd frames the daemon executes and responds to.","messages":[{"$ref":"#/channels/supervisor/messages/sv_in_hello_ack"},{"$ref":"#/channels/supervisor/messages/sv_in_ping"},{"$ref":"#/channels/supervisor/messages/sv_in_pong"},{"$ref":"#/channels/supervisor/messages/sv_in_cmd"},{"$ref":"#/channels/supervisor/messages/sv_in_error"}]}},"components":{"messages":{"cli_out_subscribe":{"summary":"Subscribe to a session's broadcast firehose.","payload":{"type":"object","required":["type","sessionId"],"properties":{"type":{"type":"string","const":"subscribe"},"sessionId":{"type":"string","format":"uuid"}}}},"cli_out_unsubscribe":{"summary":"Stop receiving broadcasts for a session.","payload":{"type":"object","required":["type","sessionId"],"properties":{"type":{"type":"string","const":"unsubscribe"},"sessionId":{"type":"string","format":"uuid"}}}},"cli_out_prompt":{"summary":"Send a prompt to a session's live Claude.","description":"`sourceSessionId` is set when the operator is attached to one\nsession and redirecting `@peer text` to a different session\n(Flow A in PEER-ROUTING.md). The hub then sets up a permission\nrelay so the source operator sees gates the target hits.\n\n**Agent interception**: if `text` starts with `@<owner>/<slug> `\nand a published agent matches that handle, the hub routes the\nprompt to the agent's session instead of `sessionId`. The\nquerier's CC never sees the prompt; the reply lands in\n`sessionId` as a `chat/reply` event tagged `agentHandle`.\nSubject to per-agent rate limits + daily budgets — denials\nreturn a `cli_in_error` frame with code starting `agent_`.\n","payload":{"type":"object","required":["type","sessionId","text"],"properties":{"type":{"type":"string","const":"prompt"},"sessionId":{"type":"string","format":"uuid","description":"target session — usually the attached one, or a peer for cross-session redirects"},"text":{"type":"string"},"sourceSessionId":{"type":"string","format":"uuid","description":"set when redirecting; the hub uses it for permission-relay setup and to persist the dispatch in the source's events"}}}},"cli_out_op_message":{"summary":"Operator-to-operator chat lane (NOT a chat event sent to Claude).","payload":{"type":"object","required":["type","sessionId","text"],"properties":{"type":{"type":"string","const":"op_message"},"sessionId":{"type":"string","format":"uuid"},"text":{"type":"string"},"mentions":{"type":"array","items":{"type":"string"}}}}},"cli_out_approval":{"summary":"Resolve a permission request (allow/deny).","payload":{"type":"object","required":["type","sessionId","requestId","decision"],"properties":{"type":{"type":"string","const":"approval"},"sessionId":{"type":"string","format":"uuid","description":"the GATE's session — for cross-session relays this differs from the operator's attached session"},"requestId":{"type":"string"},"decision":{"type":"string","enum":["allow","deny"]},"message":{"type":"string"}}}},"cli_out_route":{"summary":"Reserved — not yet wired.","description":"Placeholder for a future direct-route tool that bypasses session attach.","payload":{"type":"object","required":["type","peer","prompt","mode"],"properties":{"type":{"type":"string","const":"route"},"peer":{"type":"string"},"prompt":{"type":"string"},"mode":{"type":"string","enum":["ask","tell"]}}}},"cli_in_subscribed":{"summary":"Hub confirms our subscribe; carries our role on the session.","payload":{"type":"object","required":["type","sessionId","role"],"properties":{"type":{"type":"string","const":"subscribed"},"sessionId":{"type":"string","format":"uuid"},"role":{"type":"string","enum":["owner","viewer","prompter","approver"]}}}},"cli_in_event":{"summary":"A chat or tail event broadcast on this session.","description":"The inner `event` carries kind=chat|tail and a free-form\ntype+payload. Common chat types: prompt, assistant_text, reply,\npeer-timeout. Common tail types: PreToolUse, PostToolUse, Stop,\nNotification, UserPromptSubmit, SubagentStop. Payload shape\nvaries by type — see PEER-ROUTING.md for the chat-flow shapes\nand CLI-WS-PROTOCOL.md for the broader event surface.\n","payload":{"type":"object","required":["type","sessionId","event"],"properties":{"type":{"type":"string","const":"event"},"sessionId":{"type":"string","format":"uuid"},"event":{"type":"object","required":["kind","type","payload","ts"],"properties":{"kind":{"type":"string","enum":["chat","tail"]},"type":{"type":"string","description":"free-form discriminator; e.g. prompt, assistant_text, reply, peer-timeout, PreToolUse, PostToolUse, Stop"},"payload":{"type":"object","additionalProperties":true},"ts":{"type":"string","format":"date-time"}}}}}},"cli_in_op_message":{"summary":"An op-message landed on this session.","payload":{"type":"object","required":["type","sessionId","authorLogin","text","mentions","ts"],"properties":{"type":{"type":"string","const":"op_message"},"sessionId":{"type":"string","format":"uuid"},"authorLogin":{"type":"string"},"text":{"type":"string"},"mentions":{"type":"array","items":{"type":"string"}},"ts":{"type":"string","format":"date-time"}}}},"cli_in_permission_request":{"summary":"A tool-permission gate is pending on this session.","description":"Fans out to (a) every operator subscribed to the session and\n(b) every operator with a permission-relay registered (cross-\nsession redirects via Flow A or Flow B). When `sessionId`\ndiffers from the operator's currently-attached session, the\nrequest is a relayed cross-session gate — the approval frame\nsent in response MUST address the gate's sessionId, not the\nattached one.\n","payload":{"type":"object","required":["type","sessionId","requestId","tool","ts"],"properties":{"type":{"type":"string","const":"permission_request"},"sessionId":{"type":"string","format":"uuid","description":"session where the tool is gated"},"requestId":{"type":"string"},"tool":{"type":"string"},"inputPreview":{"type":"string"},"ts":{"type":"string","format":"date-time"}}}},"cli_in_permission_resolved":{"summary":"A pending permission_request got an allow/deny/expired decision.","payload":{"type":"object","required":["type","sessionId","requestId","decision"],"properties":{"type":{"type":"string","const":"permission_resolved"},"sessionId":{"type":"string","format":"uuid"},"requestId":{"type":"string"},"decision":{"type":"string","enum":["allow","deny","expired"]},"resolverLogin":{"type":"string","nullable":true,"description":"github login of the operator who resolved; null when expired without resolution"}}}},"cli_in_presence":{"summary":"Set of operators currently attached, plus joined/left delta.","payload":{"type":"object","required":["type","sessionId","attached"],"properties":{"type":{"type":"string","const":"presence"},"sessionId":{"type":"string","format":"uuid"},"attached":{"type":"array","items":{"type":"string"},"description":"github logins of every /cli WS currently subscribed to this session"},"joined":{"type":"string","description":"set on the broadcast that observed a new operator subscribe"},"left":{"type":"string","description":"set on the broadcast that observed an operator unsubscribe / disconnect"}}}},"cli_in_channel_status":{"summary":"The session's /channel WS connected or disconnected (CC online/offline).","payload":{"type":"object","required":["type","sessionId","connected","ts"],"properties":{"type":{"type":"string","const":"channel_status"},"sessionId":{"type":"string","format":"uuid"},"connected":{"type":"boolean"},"ts":{"type":"string","format":"date-time"}}}},"cli_in_file_event":{"summary":"A file was uploaded or deleted on this session.","payload":{"type":"object","required":["type","sessionId","action","file"],"properties":{"type":{"type":"string","const":"file_event"},"sessionId":{"type":"string","format":"uuid"},"action":{"type":"string","enum":["uploaded","deleted"]},"file":{"$ref":"#/components/schemas/ApiFile"}}}},"cli_in_ack":{"summary":"Hub acknowledged a write (typically a prompt send). May carry a chatId.","payload":{"type":"object","required":["type","ok"],"properties":{"type":{"type":"string","const":"ack"},"ok":{"type":"boolean","const":true},"chatId":{"type":"string","description":"set after a prompt send; can be used for reply correlation"}}}},"cli_in_error":{"summary":"Hub-side error on a frame the operator just sent.","payload":{"type":"object","required":["type","ok","code","message"],"properties":{"type":{"type":"string","const":"error"},"ok":{"type":"boolean","const":false},"code":{"type":"string","description":"stable code: auth_failed, forbidden, internal, …"},"message":{"type":"string"}}}},"ch_out_register":{"summary":"First message on every /channel WS — auth + session intent.","description":"sessionId may be null (hub mints a fresh UUID) or a previously-\nissued UUID (hub rebinds the existing row). Channel token rides\nin the `Authorization: Bearer ck_live_…` header (NOT in this\nmessage's body — convention is HTTP-side).\n","payload":{"type":"object","required":["type","host","cwd","pid","channelVersion"],"properties":{"type":{"type":"string","const":"register"},"host":{"type":"string"},"cwd":{"type":"string"},"osUser":{"type":"string","nullable":true},"pid":{"type":"integer"},"channelVersion":{"type":"string"},"sessionId":{"type":"string","format":"uuid","nullable":true}}}},"ch_out_chat_event":{"summary":"A chat-lane event from CC (prompt, reply, assistant_text, …).","payload":{"type":"object","required":["type","eventType","payload","ts"],"properties":{"type":{"type":"string","const":"chat_event"},"eventType":{"type":"string","enum":["prompt","reply"]},"payload":{"type":"object","additionalProperties":true},"ts":{"type":"string","format":"date-time"}}}},"ch_out_tail_event":{"summary":"A hook-fired tail event (PreToolUse, PostToolUse, Stop, …) or a clawborrator-emitted card (AskUserQuestion).","payload":{"type":"object","required":["type","eventType","payload","ts"],"properties":{"type":{"type":"string","const":"tail_event"},"eventType":{"type":"string","enum":["PreToolUse","PostToolUse","Stop","Notification","UserPromptSubmit","AskUserQuestion"]},"payload":{"type":"object","additionalProperties":true},"ts":{"type":"string","format":"date-time"}}}},"ch_out_permission_request":{"summary":"CC needs permission to call a tool — relay to attached operators.","payload":{"type":"object","required":["type","requestId","tool","inputPreview","ts"],"properties":{"type":{"type":"string","const":"permission_request"},"requestId":{"type":"string"},"tool":{"type":"string"},"inputPreview":{"type":"string"},"ts":{"type":"string","format":"date-time"}}}},"ch_out_route_request":{"summary":"Send a prompt to a peer (route_to_peer MCP tool).","payload":{"type":"object","required":["type","correlationId","peer","prompt","mode"],"properties":{"type":{"type":"string","const":"route_request"},"correlationId":{"type":"string"},"peer":{"type":"string","description":"routing name like @backend or @owner/backend"},"prompt":{"type":"string"},"mode":{"type":"string","enum":["ask","tell"]}}}},"ch_out_probe_request":{"summary":"Fan out a question to many peers (probe_peers MCP tool).","payload":{"type":"object","required":["type","correlationId","prompt"],"properties":{"type":{"type":"string","const":"probe_request"},"correlationId":{"type":"string"},"peers":{"type":"array","items":{"type":"string"},"nullable":true,"description":"null = all online peers"},"prompt":{"type":"string"}}}},"ch_out_list_peers_request":{"summary":"Discover routable peers (list_peers MCP tool).","payload":{"type":"object","required":["type","correlationId"],"properties":{"type":{"type":"string","const":"list_peers_request"},"correlationId":{"type":"string"}}}},"ch_out_pong":{"summary":"Reply to a hub-side ping.","payload":{"type":"object","required":["type","ts"],"properties":{"type":{"type":"string","const":"pong"},"ts":{"type":"string","format":"date-time"}}}},"ch_in_welcome":{"summary":"Auth ok, session resolved — channel is registered and ready.","payload":{"type":"object","required":["type","sessionId","routingName","channelTokenName"],"properties":{"type":{"type":"string","const":"welcome"},"sessionId":{"type":"string","format":"uuid"},"routingName":{"type":"string","description":"@-prefixed, e.g. @backend"},"channelTokenName":{"type":"string","description":"human label of the channel token used (audit only)"}}}},"ch_in_prompt":{"summary":"Operator-driven or peer-routed prompt — surface to Claude.","payload":{"type":"object","required":["type","chatId","text"],"properties":{"type":{"type":"string","const":"prompt"},"chatId":{"type":"string"},"text":{"type":"string"}}}},"ch_in_permission_response":{"summary":"Operator resolved a pending permission_request.","payload":{"type":"object","required":["type","requestId","decision"],"properties":{"type":{"type":"string","const":"permission_response"},"requestId":{"type":"string"},"decision":{"type":"string","enum":["allow","deny","expired"]},"message":{"type":"string","nullable":true}}}},"ch_in_route_response":{"summary":"route_to_peer ask-mode reply (or tell-mode confirmation).","payload":{"type":"object","required":["type","correlationId","peerLogin","reply"],"properties":{"type":{"type":"string","const":"route_response"},"correlationId":{"type":"string"},"peerLogin":{"type":"string"},"reply":{"type":"string"}}}},"ch_in_route_reply":{"summary":"A peer's reply landed for a previously-dispatched prompt (operator @-redirect or MCP tell-mode).","payload":{"type":"object","required":["type","routeId","fromName","text","ts"],"properties":{"type":{"type":"string","const":"route_reply"},"routeId":{"type":"string"},"fromName":{"type":"string"},"text":{"type":"string"},"ts":{"type":"string","format":"date-time"},"origin":{"type":"string","enum":["operator","mcp"],"description":"operator: dispatched via Flow A @-redirect; mcp: dispatched via route_to_peer tool"}}}},"ch_in_probe_response":{"summary":"One peer's answer to a probe (others stream individually).","payload":{"type":"object","required":["type","correlationId","peerLogin","answer"],"properties":{"type":{"type":"string","const":"probe_response"},"correlationId":{"type":"string"},"peerLogin":{"type":"string","nullable":true,"description":"null on the terminal `done` marker"},"answer":{"type":"string","nullable":true},"done":{"type":"boolean"}}}},"ch_in_peers_update":{"summary":"Live broadcast of online/offline peers (own + shared).","payload":{"type":"object","required":["type","peers"],"properties":{"type":{"type":"string","const":"peers_update"},"peers":{"type":"array","items":{"type":"object","required":["login","name","online"],"properties":{"login":{"type":"string"},"name":{"type":"string"},"online":{"type":"boolean"}}}}}}},"ch_in_list_peers_response":{"summary":"One-shot reply to list_peers_request.","payload":{"type":"object","required":["type","correlationId","peers"],"properties":{"type":{"type":"string","const":"list_peers_response"},"correlationId":{"type":"string"},"peers":{"type":"array","items":{"type":"object","required":["login","name","online"],"properties":{"login":{"type":"string"},"name":{"type":"string"},"online":{"type":"boolean"}}}}}}},"ch_in_bye":{"summary":"Hub is closing the channel — server restart, kick, etc.","payload":{"type":"object","required":["type","reason","retry"],"properties":{"type":{"type":"string","const":"bye"},"reason":{"type":"string"},"retry":{"type":"boolean","description":"true if reconnect is expected to succeed (e.g. transient restart); false on permanent reasons (revoked token)"}}}},"ch_in_ping":{"summary":"Liveness probe — channel responds with `pong`.","payload":{"type":"object","required":["type","ts"],"properties":{"type":{"type":"string","const":"ping"},"ts":{"type":"string","format":"date-time"}}}},"ch_in_error":{"summary":"Hub-side error on a frame the channel just sent.","payload":{"type":"object","required":["type","code","message"],"properties":{"type":{"type":"string","const":"error"},"code":{"type":"string"},"message":{"type":"string"}}}},"sv_out_hello":{"summary":"First frame after WS upgrade. Identifies the daemon to the hub.","description":"Sent immediately after the WebSocket upgrade succeeds. The hub\nupserts a `desktops` row keyed by (userId-from-token, machine_id)\nand replies with `hello_ack`. If `hello` doesn't arrive within\n10s the hub closes with `code: no_hello`.\n","payload":{"type":"object","required":["t","machine_id"],"properties":{"t":{"type":"string","const":"hello"},"machine_id":{"type":"string","description":"stable per-install uuid generated by the daemon"},"daemon_version":{"type":"string"},"hostname":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"},"description":"advisory; lets the hub gracefully degrade for older daemons"}}}},"sv_out_ping":{"summary":"Daemon-initiated liveness probe. Hub responds with `pong`.","payload":{"type":"object","required":["t"],"properties":{"t":{"type":"string","const":"ping"}}}},"sv_out_pong":{"summary":"Daemon's reply to a hub-initiated `ping`.","payload":{"type":"object","required":["t"],"properties":{"t":{"type":"string","const":"pong"}}}},"sv_out_ok":{"summary":"RPC success. Replies to a hub `cmd` frame keyed by `id`.","payload":{"type":"object","required":["t","id"],"properties":{"t":{"type":"string","const":"ok"},"id":{"type":"string","description":"echoes the cmd frame's id"},"data":{"description":"op-specific success payload (object, array, or scalar)"}}}},"sv_out_err":{"summary":"RPC failure. Replies to a hub `cmd` frame keyed by `id`.","payload":{"type":"object","required":["t","id","code","message"],"properties":{"t":{"type":"string","const":"err"},"id":{"type":"string"},"code":{"type":"string","description":"machine-readable error code, e.g. not_implemented, folder_denied, timeout"},"message":{"type":"string"}}}},"sv_out_evt":{"summary":"Daemon-initiated push event (e.g. session.state, desktop.health).","description":"**Step 2.** Reserved for daemon-side state pushes — a session\nprocess exited, daemon CPU/RAM heartbeat, etc. Not used by\nStep 1 (handshake-only).\n","payload":{"type":"object","required":["t","topic"],"properties":{"t":{"type":"string","const":"evt"},"topic":{"type":"string","description":"e.g. session.state, desktop.health"},"data":{"description":"topic-specific payload"}}}},"sv_in_hello_ack":{"summary":"Hub acknowledges the daemon's `hello` frame.","payload":{"type":"object","required":["t"],"properties":{"t":{"type":"string","const":"hello_ack"},"user_login":{"type":"string","description":"the GitHub login the auth token resolved to"},"message":{"type":"string"}}}},"sv_in_ping":{"summary":"Hub-initiated liveness probe (every 30s). Daemon responds with `pong`.","payload":{"type":"object","required":["t"],"properties":{"t":{"type":"string","const":"ping"}}}},"sv_in_pong":{"summary":"Hub's reply to a daemon-initiated `ping`.","payload":{"type":"object","required":["t"],"properties":{"t":{"type":"string","const":"pong"}}}},"sv_in_cmd":{"summary":"Hub-initiated RPC. Daemon executes `op` and responds with `ok` or `err` keyed by `id`.","description":"Drives every managed-session verb. The current op set:\n\n  * `session.create`     — { folder, routingName?, extraFlags?, autoEnter? } → { sessionId }\n  * `session.kill`       — { sessionId }                              → { ok: true }\n  * `session.destroy`    — { sessionId }                              → { ok: true }\n  * `session.restart`    — { sessionId }                              → { sessionId }   (always AUTO START)\n  * `session.screenshot` — { sessionId }                              → { rows, cols, text, cursor? }\n  * `session.input`      — { sessionId, bytes }                       → { ok: true, wrote }\n\n`autoEnter` defaults to true (\"AUTO START\": daemon types\nEnter ~5x at 1s to dismiss CC's default-yes startup\nprompts). Pass `false` for \"MANUAL START\" — operator\ndrives prompts via the screenshot PIP / `claw session\ninput`. `extraFlags` is `string[]`, one argv slot per\nentry, appended after the daemon's required CC flags.\n\nErrors: a `session_not_found` code on kill/restart/\ndestroy/screenshot/input means the daemon's in-memory\nSessionManager has no entry for the given sessionId.\nHub's REST handlers translate this into recreate\nfallbacks (restart) or no-op success (kill) as\nappropriate.\n\nPer-op timeout enforced hub-side (defaults: 10s, overridden to\n45s for create/restart, 5s for screenshot). On timeout the hub\nrejects the awaiting HTTP request with 504; on `err` reply 502;\non disconnect mid-flight 503.\n","payload":{"type":"object","required":["t","id","op"],"properties":{"t":{"type":"string","const":"cmd"},"id":{"type":"string","description":"uuid; daemon must echo in its ok/err response"},"op":{"type":"string","description":"verb, e.g. session.create"},"args":{"description":"op-specific arguments (object)"}}}},"sv_in_error":{"summary":"Connection-level error pushed by the hub (e.g. auth_failed, no_hello, duplicate_hello, bad_hello, pre_hello, invalid_json, internal). Usually followed by close.","payload":{"type":"object","required":["t","code","message"],"properties":{"t":{"type":"string","const":"error"},"code":{"type":"string"},"message":{"type":"string"}}}}},"schemas":{"ApiFile":{"type":"object","properties":{"id":{"type":"integer"},"sessionId":{"type":"string","format":"uuid"},"uploaderLogin":{"type":"string"},"filename":{"type":"string"},"mime":{"type":"string"},"size":{"type":"integer"},"sha256":{"type":"string"},"scope":{"type":"string","enum":["attachment","reply","corpus"]},"expose":{"type":"boolean"},"uploadedAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"},"deletedAt":{"type":"string","format":"date-time","nullable":true}}}}}}