Skip to content

Latest commit

 

History

History
138 lines (96 loc) · 4.99 KB

File metadata and controls

138 lines (96 loc) · 4.99 KB

Terminal I/O

Overview

Terminal I/O uses gRPC bidirectional streaming for low-latency interactive sessions. The server maintains full VT state for each session, enabling instant reconnect with screen snapshot.

Protocol

Defined in proto/terminal.proto, package relay.terminal.v1.

AttachSession RPC

rpc AttachSession(stream ClientMessage) returns (stream ServerMessage);

Bidirectional stream for interactive terminal I/O. The client sends an AttachRequest as the first message, then sends stdin data and resize events. The server streams back stdout data and session events.

Client → Server (ClientMessage)

Field Description
attach_request First message — handshake with session_id
stdin_data Raw bytes forwarded to the PTY
resize_request Terminal resize (cols, rows)
detach_request Graceful detach without killing the session

Server → Client (ServerMessage)

Field Description
attach_response First response — screen snapshot, size, title, cwd, input mode
stdout_data Raw bytes from the PTY
session_event State changes: title, cwd, size, terminated
error_response Protocol-level error with ErrorCode

AttachResponse

On successful attach, the server sends:

  • screen_snapshot — full VT screen state as bytes
  • size — current terminal dimensions
  • title — terminal window title
  • cwd — current working directory
  • input_mode — READ_WRITE (session creator) or READ_ONLY (other clients)
  • version_info — runner and protocol versions

InputMode

  • READ_WRITE — session creator, can send stdin
  • READ_ONLY — attached observer, stdin is rejected

Determined by session_auth.rs: the session creator gets read-write access, all other clients get read-only.

VT Parser

Server-side VT state tracking via the vte crate (runner/src/vt_parser.rs).

What it tracks

  • Full screen buffer (rows x cols)
  • Cursor position and attributes
  • Scrollback buffer (configurable, default 10,000 lines)
  • Terminal title (from OSC sequences)
  • Current working directory (from OSC 7)
  • Terminal size

Snapshot

ArcSwap provides lock-free snapshots of the VT state. On each PTY read:

  1. Bytes are fed through the VT parser
  2. Screen state is updated
  3. A new snapshot is atomically published

Clients attaching mid-session receive the latest snapshot, rendering a complete screen immediately.

Security filtering

  • OSC 52 (clipboard write) — stripped from output to prevent clipboard hijacking
  • DCS/APC sequences — stripped from input to prevent terminal escape attacks

Session Backend

The SessionBackend trait (runner/src/backend.rs) abstracts the I/O source:

pub trait SessionBackend: Send + Sync {
    fn read(&self, buf: &mut [u8]) -> Pin<Box<dyn Future<Output = Result<usize, io::Error>> + Send>>;
    fn write_stdin(&self, data: &[u8]) -> Result<usize, io::Error>;
    fn resize(&self, cols: u16, rows: u16) -> Result<(), io::Error>;
    fn is_alive(&self) -> bool;
    fn kill(&self) -> Result<(), io::Error>;
}

Implementations:

  • LocalPtyBackend (runner/src/backends/local_pty.rs) — direct PTY via nix (openpty/fork/execvp)
  • DockerExecBackend (runner/src/backends/docker_exec.rs) — Docker exec attach via bollard

The read method returns Pin<Box<dyn Future>> because TerminalSession stores backends as Box<dyn SessionBackend>. See ADR-005.

Client Registry

ClientRegistry (runner/src/client_registry.rs) manages per-session client connections:

  • Each client gets a bounded mpsc channel for output delivery
  • Backpressure: when a client's channel is full, messages are dropped (slow client doesn't block others)
  • Client count tracked per session (exposed in SessionInfo.client_count)
  • Broadcast: PTY output is fanned out to all attached clients

PTY Details

Direct PTY management (runner/src/pty.rs):

  • Uses nix crate for POSIX PTY operations: openpty, fork, execvp
  • Shell resolved from configured allowlist (default: /bin/bash, /bin/zsh, /bin/sh)
  • Environment filtering: blocks LD_PRELOAD, DYLD_*, and other injection vectors
  • Async I/O via tokio's AsyncFd
  • SIGHUP for graceful shutdown, SIGKILL as fallback

Scrollback

rpc GetScrollback(GetScrollbackRequest) returns (GetScrollbackResponse);

Returns buffered scrollback lines. max_lines = 0 returns all buffered lines. Configurable buffer size via scrollback_lines in config (default: 10,000).

Other gRPC RPCs

RPC Description
CreateSession Spawn a new PTY session (local mode only; cloud mode returns FAILED_PRECONDITION)
ListSessions List all active sessions
TerminateSession Kill a session by ID
GetScrollback Retrieve scrollback buffer
RefreshToken Issue a new auth token before expiry

Source: runner/src/grpc_service.rs