* 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>
2.0 KiB
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
- Byte 0: tag (
Control Messages
- Bind active socket to terminal session:
- client -> server:
{"t":"b","s":"<sessionId>","v":2}
- client -> server:
- Keepalive ping:
- client -> server:
{"t":"p","v":2} - server -> client:
{"t":"po","v":2}
- client -> server:
- 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}
- ready:
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.wsand 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-wspath should fail with404 Not Found.