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>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
d68bec491c
commit
2b70ec6f3a
@@ -0,0 +1,48 @@
|
||||
# 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`.
|
||||
Reference in New Issue
Block a user