Skip to content

feat: WebSocket terminal I/O with server-side DEC 2026 sync#40

Open
Spirotot wants to merge 1 commit intoArk0N:masterfrom
Spirotot:feat/ws-terminal-io-upstream
Open

feat: WebSocket terminal I/O with server-side DEC 2026 sync#40
Spirotot wants to merge 1 commit intoArk0N:masterfrom
Spirotot:feat/ws-terminal-io-upstream

Conversation

@Spirotot
Copy link

Summary

  • Adds a WebSocket endpoint (/ws/sessions/:id/terminal) as a low-latency bidirectional channel for terminal I/O, replacing per-keystroke HTTP POST + SSE output with a single persistent connection
  • Strictly additive — the existing HTTP POST input and SSE terminal output paths remain fully functional; the frontend opts into WS when available and falls back transparently
  • Server-side 8ms micro-batching with 16KB flush threshold groups rapid PTY events into single WS frames
  • Each WS batch is wrapped in DEC 2026 synchronized update markers (\x1b[?2026h / \x1b[?2026l) so xterm.js renders atomically — eliminates flicker from Ink's cursor-up redraws (Ink's DA capability negotiation for DEC 2026 fails through the PTY→server→WS proxy chain, so server-injected markers are required)

Protocol

All JSON text frames:

Direction Type Payload
Server → Client {"t":"o","d":"..."} Terminal output (DEC 2026 wrapped)
Server → Client {"t":"c"} Clear terminal
Server → Client {"t":"r"} Needs refresh (reload buffer)
Client → Server {"t":"i","d":"..."} Input (keystroke or paste)
Client → Server {"t":"z","c":N,"r":N} Resize terminal

Files changed

  • src/web/routes/ws-routes.ts (new) — WebSocket route handler with micro-batching and DEC 2026 wrapping
  • src/web/routes/index.ts — Barrel export
  • src/web/server.ts@fastify/websocket registration
  • src/web/public/app.js — WS client lifecycle, SSE suppression guards, input/resize fast paths
  • package.json@fastify/websocket dependency

Test plan

  • Verify WS connects on session select (Safari, Chrome)
  • Verify input latency is dramatically lower vs HTTP POST
  • Verify SSE fallback works when WS is unavailable
  • Verify DEC 2026 markers present in WS output (console diagnostic)
  • Verify no flicker during Ink cursor-up redraws
  • Verify WS disconnects on tab switch and session delete
  • Verify resize messages sent via WS fast path

🤖 Generated with Claude Code

Replace per-keystroke HTTP POST + SSE terminal output with a single
bidirectional WebSocket connection for dramatically lower input latency.
The existing SSE+POST paths remain fully functional as fallback.

Server-side: ws-routes.ts provides /ws/sessions/:id/terminal with 8ms
micro-batching and 16KB flush threshold. Each batch is wrapped in
DEC 2026 synchronized update markers so xterm.js renders atomically —
Ink's DA capability negotiation fails through the PTY→server→WS proxy
chain, so without server-injected markers, cursor-up redraws flicker.

Frontend: _connectWs/_disconnectWs manage per-session WS lifecycle.
Input and resize use WS fast path with HTTP POST fallback. SSE terminal
events are suppressed when WS is active to prevent double rendering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant