Terminal Sessions
Persistent PTY sessions, agent attention detection, and recovery behaviour in the Implementation Plans terminal.
Terminal sessions are managed by terminal_manager.rs (41KB), which provides PTY-based terminal emulation using the portable-pty crate. Each session maintains dual output channels for local consumption and remote WebSocket clients, with a 32MB in-memory buffer per session and automatic SQLite persistence every 10 seconds.
Terminal session architecture
PTY process lifecycle, output routing, and persistence layers.

PTY Management with portable-pty
The terminal manager uses the portable-pty crate to spawn pseudo-terminal processes. Each PTY is configured with the user's default shell, working directory, and environment variables. The manager maintains a HashMap of active sessions keyed by session_id.
PTY Operations
- • PTY spawning: CommandBuilder with shell path, args, cwd, and env vars passed to PtySystem::default().openpty()
- • Terminal sizing: PtySize struct with rows, cols, pixel_width, pixel_height sent via master.resize()
- • Output reading: Blocking read loop on master.try_clone_reader() with configurable buffer size
- • Input writing: master.take_writer() for stdin, supporting raw bytes and escape sequences
Dual Output Routing
Each session maintains two output destinations: a local Channel for UI consumption and WebSocket connections for mobile clients. Output is broadcast to all connected receivers without blocking the PTY read loop.
Local Channel
tokio::sync::mpsc::channel with 1024-message buffer for UI updates. The React TerminalView component consumes this channel and renders output in real-time.
Remote WebSocket
Binary frames sent to connected mobile clients via device link. Uses PTC1 framing protocol for efficient multiplexed streaming.
Broadcast pattern: Output is cloned to each receiver; slow consumers are dropped to prevent backpressure from affecting the PTY read loop.
32MB In-Memory Buffer
Each session maintains a 32MB circular buffer for output history. This allows clients to reconnect and receive recent output without querying the database. The buffer automatically evicts oldest content when full.
// Buffer configuration
const BUFFER_SIZE: usize = 32 * 1024 * 1024; // 32MB per session
struct SessionBuffer {
data: VecDeque<u8>,
write_offset: usize,
}
impl SessionBuffer {
fn push(&mut self, bytes: &[u8]) {
// Evict from front if buffer full
while self.data.len() + bytes.len() > BUFFER_SIZE {
self.data.pop_front();
}
self.data.extend(bytes);
self.write_offset += bytes.len();
}
fn get_since(&self, offset: usize) -> &[u8] {
// Return bytes from offset to current end
}
}PTC1 Binary Framing for Mobile
Mobile clients receive terminal output using the PTC1 binary framing protocol. This compact format includes message type, session ID, and payload length to support multiplexed session streams over a single WebSocket connection.
// PTC1 Frame Structure
┌─────────────────────────────────────────────────────┐
│ Type │ Session UUID │ Payload Length │
│ (1 byte)│ (16 bytes) │ (4 bytes LE) │
├─────────────────────────────────────────────────────┤
│ Payload Data │
│ (variable length) │
└─────────────────────────────────────────────────────┘
Message Types:
0x01 = Terminal output
0x02 = Resize notification
0x03 = Input acknowledgment
0x04 = State change eventThe compact header (21 bytes) minimizes overhead for real-time streaming to bandwidth-constrained mobile clients.
Session State Machine
Sessions transition through defined states that reflect the PTY process lifecycle and recovery status.
SQLite Persistence with 10-Second Flush
Session state and output logs are flushed to SQLite every 10 seconds. This provides durability without impacting PTY performance. On app restart, sessions in Running or Suspended state are restored from the database.
-- terminal_sessions table schema
CREATE TABLE terminal_sessions (
session_id TEXT PRIMARY KEY,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
status TEXT NOT NULL, -- Initializing|Running|Suspended|Exited|...
exit_code INTEGER,
working_dir TEXT NOT NULL,
env_vars TEXT, -- JSON object
output_log BLOB -- Full session output for restoration
);
-- Restoration query on startup
SELECT * FROM terminal_sessions
WHERE status IN ('Running', 'Suspended');Persistence Details
- • Flush interval: 10 seconds via tokio::time::interval
- • Output log: BLOB column containing full session output history
- • Restoration: Replay output_log to buffer, resume PTY monitoring
Health Checks: PTY + Database
Health checks combine PTY process status with database records to determine session liveness. This dual-source approach handles edge cases where the PTY dies without updating the database.
Health Check Process
- • PTY check: try_wait() on child process to detect exit without blocking
- • Database check: Query terminal_sessions for status and updated_at timestamp
- • Reconciliation: If PTY dead but DB shows Running, update status to Exited with detected exit code
Session Lifecycle
Sessions follow a predictable lifecycle from creation through termination or restoration. The UI component initiates sessions, the terminal manager handles PTY operations, and the persistence layer ensures durability.
Create
UI calls create_terminal_session command with working_dir and env overrides
Stream
Output flows through mpsc channel to TerminalView component for rendering
Input
Keystrokes sent via write_to_terminal command, supporting raw mode and escape sequences
Terminate
close_terminal_session sends SIGTERM, waits 5s, then SIGKILL if needed
Agent Attention Detection
The terminal monitors agent activity through a two-level inactivity detection system. When an agent stops producing output, the system progressively alerts you to check what has happened:
Level 1 (30 seconds)
Agent idle - may have completed task. Yellow indicator displayed.
Level 2 (2 minutes)
Agent requires attention - check terminal. Red indicator and desktop notification.
Attention indicators clear automatically when new output is received. This helps track when agents have finished tasks or need guidance without guessing why they stopped.
Dependency and Shell Detection
Before launching commands, the terminal checks for configured CLI tools and reports the default shell. This ensures users know which environment will run and whether required tools are available.
- • Shell detection: SHELL env var on Unix, ComSpec on Windows, fallback to /bin/sh or cmd.exe
- • Tool checks: which/where command for configured CLI binaries (claude, aider, cursor, etc.)
- • Reporting: Missing tools surfaced in UI before session creation to prevent failed executions
Voice Transcription and Recovery
Voice Input
Voice transcription can capture speech and paste it into the terminal input area. The recording hook looks up project-level transcription settings, tracks recording state, and streams recognized text into the active plan session.
Session Recovery
If a PTY session disconnects, the terminal surface displays recovery controls and retries with exponential backoff. Health checks continue monitoring session state and provide automatic recovery actions when connection issues are detected.
Connect terminals to execution
See how copy buttons hand off plan content to terminal sessions for agent execution.