Skip to content

Integrated Terminal

The console's Terminal rail is a real PTY-backed shell (xterm.js) for running tools directly when the agent's loop isn't the right fit. It is the only WebSocket on the server; everything else is REST/SSE.

Endpoint

GET ws(s)://<host>:7870/ws/terminal — upgraded to a WebSocket. Not part of the OpenAPI schema (so it doesn't appear in API Endpoints).

Authentication

Gated by the same operator key as the REST API (PROTOPEN_API_KEY / RESEARCHER_API_KEY). Browser WebSockets can't set headers, so the key rides the key query param: …/ws/terminal?key=<operator-key>. A wrong/missing key when a key is configured closes the socket with code 1008 before accept. When no key is configured (local dev), the socket is open like the rest of the console.

Wire protocol

JSON text frames:

DirectionFrame
client → server{"type":"input","data": "<keystrokes>"}
client → server{"type":"resize","cols": <int>,"rows": <int>}
client → server{"type":"clear"} (wipes the server scrollback; ⌘/Ctrl+K)
client → server{"type":"ping"}
server → client{"type":"data","data": "<output>"}
server → client{"type":"exit","code": <int>}
server → client{"type":"pong"}

Behavior

  • Shell$SHELL, else bash / zsh / sh, started as a session leader in a pseudo-terminal. cwd is SANDBOX_DIR if set, else $HOME.
  • Tabs — multiple shells as tabs; each stays mounted (hidden) across tab switches, and the surface stays mounted across rail navigation, so a running command keeps going while you glance elsewhere.
  • Sizing — the PTY size tracks the browser viewport (TIOCSWINSZ), so full-screen TUIs and line wrapping render correctly.
  • Theme — matches the Pilot Protocol terminal skin.

Hotkeys

Local convenience keys; everything else passes through to the shell. Mac uses ⌘; other platforms use Ctrl (with Shift for copy/paste so Ctrl+C stays SIGINT).

Key (Mac / other)Action
⌘K / Ctrl+KClear the terminal (also wipes the server scrollback)
⌘C / Ctrl+Shift+CCopy selection
⌘V / Ctrl+Shift+VPaste
⌘A (Mac)Select all
Ctrl+CSIGINT (unchanged — passes through to the shell)

Security

A live interactive shell is a real capability — it runs commands in the server's environment with the server's privileges. In the hardened container the rootfs is read-only and capabilities are dropped (NET_RAW only); on the Steam Deck it runs as the native deck user. It is operator-key gated and on by default; gate the console behind the operator key on any reachable deployment.

Implementation

  • Backend: server/terminal.py (register_terminal_ws), wired in server/app.py:build_app. Python stdlib pty + an async pipe; uvicorn serves the WebSocket via the websockets dependency.
  • Frontend: apps/web/src/terminal/TerminalSurface.tsx (xterm.js + fit/web-links addons).
  • See the Operator Console guide for the rail in context.

Part of the protoLabs autonomous development studio.