Files
openchamber/packages/web/server/TERMINAL_WS_PROTOCOL.md
T
YifanandBohdan Triapitsyn 2b70ec6f3a feat(terminal): switch terminal transport to pure websocket with fallback (#762)
* feat(terminal): add resumable websocket transport

Unify terminal input and stream traffic on `/api/terminal/ws` with a v2 control-frame protocol and advertised transport capabilities.

Buffer recent PTY output on the server so rebinding clients can replay missed chunks after reconnects or startup races, while keeping SSE as a fallback stream path.

Update the web terminal client and store to negotiate the new transport, track tab lifecycle, and avoid reopening exited sessions when restoring tabs.

* fix(terminal): retry rehydrated websocket reconnects

* fix(terminal): keep reconnect retries silent

* fix(terminal): wire ws stream output and replay in runtime

* fix(web): enable ws proxy for /api in dev

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-01 19:31:21 +03:00

2.0 KiB

Terminal WebSocket Transport Protocol

Goal

Use a single persistent WebSocket for terminal input and output, while keeping the legacy SSE output route and HTTP input route as compatibility fallbacks.

Scope

  • Primary full-duplex path: WebSocket (/api/terminal/ws)
  • Legacy output fallback: SSE (/api/terminal/:sessionId/stream)
  • HTTP input fallback remains: POST /api/terminal/:sessionId/input

Framing

  • Text frame:
    • client -> server: terminal keystroke payload
    • server -> client: raw PTY output chunk
  • Binary frame: control envelope
    • Byte 0: tag (0x01 = JSON control)
    • Bytes 1..N: UTF-8 JSON payload

Control Messages

  • Bind active socket to terminal session:
    • client -> server: {"t":"b","s":"<sessionId>","v":2}
  • Keepalive ping:
    • client -> server: {"t":"p","v":2}
    • server -> client: {"t":"po","v":2}
  • Server control responses:
    • ready: {"t":"ok","v":2}
    • bind ok: {"t":"bok","s":"<sessionId>","runtime":"node|bun","ptyBackend":"...","v":2}
    • exit: {"t":"x","s":"<sessionId>","exitCode":0,"signal":null}
    • error: {"t":"e","c":"<code>","f":true|false}

Multiplexing Model

  • Single shared socket per client runtime.
  • Socket has one mutable bound session.
  • Client sends a bind control when the active terminal changes.
  • Text frames always apply to the currently bound session.
  • PTY output is pushed back over the same socket as text frames.
  • Client keeps the socket primed so both stream subscription and input reuse the same transport.

Security

  • UI auth session required when UI password is enabled.
  • Origin validation enforced for cookie-authenticated browser upgrades.
  • Invalid or malformed frames are rate-limited and may close the socket.

Fallback Behavior

  • New clients prefer capabilities.stream.ws and reuse the same socket for input.
  • If stream WebSocket capability is unavailable, clients fall back to SSE output.
  • If terminal input cannot be sent over WebSocket, clients fall back to HTTP input.
  • The removed /api/terminal/input-ws path should fail with 404 Not Found.