Skip to content

feat: add PTY/terminal session support with ghostty-web UI#44

Open
NathanFlurry wants to merge 4 commits intomainfrom
feat/process-terminal
Open

feat: add PTY/terminal session support with ghostty-web UI#44
NathanFlurry wants to merge 4 commits intomainfrom
feat/process-terminal

Conversation

@NathanFlurry
Copy link
Member

Summary

Adds PTY/terminal session support to the Process Manager API with an interactive terminal UI using ghostty-web.

Backend (Rust)

New API Endpoints:

  • GET /v1/process/:id/terminal — WebSocket for bidirectional terminal I/O
  • POST /v1/process/:id/resize — Resize terminal (cols, rows)
  • POST /v1/process/:id/input — Write data to terminal

Extended StartProcessRequest:

  • tty: bool — Allocate pseudo-TTY (like Docker -t)
  • interactive: bool — Keep stdin open (like Docker -i)
  • terminalSize: {cols, rows} — Initial terminal size

Features:

  • PTY allocation with portable-pty
  • TERM=xterm-256color environment variable
  • Terminal resize support
  • WebSocket protocol for real-time I/O

Frontend (React/TypeScript)

  • Terminal component using ghostty-web (replaced xterm.js)
  • WebSocket integration for live terminal I/O
  • Auto-resize with FitAddon
  • PTY badge and tabbed view in ProcessesTab

Builds on

…ocesses

API Endpoints:
- POST /v1/process - Start a new process
- GET /v1/process - List all processes
- GET /v1/process/{id} - Get process details
- POST /v1/process/{id}/stop - Stop a process (SIGTERM)
- POST /v1/process/{id}/kill - Kill a process (SIGKILL)
- DELETE /v1/process/{id} - Delete a process and clean up logs
- GET /v1/process/{id}/logs - Read process logs (supports tail, follow via SSE)

Features:
- Log files written to ~/.local/share/sandbox-agent/processes/{id}/
  - stdout.log, stderr.log, combined.log
- Process state persisted to state.json for server restart survival
- Status tracking: starting, running, stopped (with exit_code), killed
- Real-time log streaming via SSE with follow=true query param
- Environment variables and working directory support

Cleanup rules:
- Process exits naturally → logs preserved
- DELETE endpoint → logs removed
Backend changes:
- Add timestamps to log lines: [2026-01-30T12:32:45.123Z] <line>
- stdout.log and stderr.log get timestamps per line
- combined.log includes [stdout]/[stderr] prefix after timestamp
- Add strip_timestamps query param to GET /process/{id}/logs
- Use time crate with RFC3339 format for timestamps

Frontend changes:
- Add Processes tab to inspector debug panel
- Show list of processes with status badges (running/stopped/killed)
- Click to expand and view logs
- Log viewer options:
  - Select stream: combined, stdout, stderr
  - Toggle strip_timestamps
  - Refresh logs button
- Action buttons: stop (SIGTERM), kill (SIGKILL), delete
- Auto-refresh process list every 5 seconds
Add Docker-style terminal support with -t (TTY) and -i (interactive) flags:

Backend (Rust):
- Add portable-pty dependency for PTY allocation on Unix
- Extend StartProcessRequest with tty, interactive, and terminalSize options
- Add PTY process spawning with TERM=xterm-256color
- Add WebSocket endpoint for bidirectional terminal I/O
- Add terminal resize endpoint (POST /process/:id/resize)
- Add terminal input endpoint (POST /process/:id/input)
- Support base64-encoded binary input
- Process info now includes tty, interactive, and terminalSize fields
- Terminal output is logged to combined.log for persistence

Frontend (Inspector UI):
- Add @xterm/xterm and addons for terminal rendering
- Create Terminal component with xterm.js integration
- Add tabbed view (Terminal/Logs) for PTY processes
- Terminal auto-connects via WebSocket when process is expanded
- Support terminal resize with ResizeObserver
- Show PTY badge on processes with TTY enabled
- Graceful handling of process exit and disconnection

API:
- GET /v1/process/:id/terminal - WebSocket for terminal I/O
- POST /v1/process/:id/resize - Resize terminal (cols, rows)
- POST /v1/process/:id/input - Write data to terminal

WebSocket protocol:
- type: 'data' - Terminal output (server -> client)
- type: 'input' - Terminal input (client -> server)
- type: 'resize' - Resize request (client -> server)
- type: 'exit' - Process exited (server -> client)
- type: 'error' - Error message (server -> client)
- Replace @xterm/xterm, @xterm/addon-fit, and @xterm/addon-web-links with ghostty-web
- Update Terminal component to use ghostty-web API:
  - Add async WASM initialization via init()
  - Use FitAddon with observeResize() for auto-fitting
  - Use onResize callback for terminal resize events
- ghostty-web is API-compatible with xterm.js but uses Ghostty's WASM-compiled VT100 parser
- Benefits:
  - Better rendering of complex scripts (Devanagari, Arabic)
  - XTPUSHSGR/XTPOPSGR support
  - Same battle-tested code as native Ghostty app

Ref: https://github.com/coder/ghostty-web
@railway-app
Copy link

railway-app bot commented Jan 30, 2026

🚅 Deployed to the sandbox-agent-pr-44 environment in sandbox-agent

Service Status Web Updated (UTC)
inspector 😴 Sleeping (View Logs) Web Jan 30, 2026 at 9:49 pm
website 😴 Sleeping (View Logs) Web Jan 30, 2026 at 9:47 pm

@claude
Copy link

claude bot commented Jan 30, 2026

Code Review

I found 2 CLAUDE.md compliance issues that need to be addressed:

1. Missing CLI ⇄ HTTP endpoint map entries in CLAUDE.md

This PR adds new HTTP API routes for process management that need to be documented in the CLI ⇄ HTTP endpoint map (CLAUDE.md lines 51-65):

  • sandbox-agent api process startPOST /v1/process
  • sandbox-agent api process listGET /v1/process
  • sandbox-agent api process getGET /v1/process/:id
  • sandbox-agent api process deleteDELETE /v1/process/:id
  • sandbox-agent api process stopPOST /v1/process/:id/stop
  • sandbox-agent api process killPOST /v1/process/:id/kill
  • sandbox-agent api process logsGET /v1/process/:id/logs
  • sandbox-agent api process resizePOST /v1/process/:id/resize
  • sandbox-agent api process inputPOST /v1/process/:id/input
  • sandbox-agent api process terminalGET /v1/process/:id/terminal (WebSocket)

These routes are added in

let session = Self::session_mut(&mut sessions, session_id).ok_or_else(|| {
SandboxError::SessionNotFound {
session_id: session_id.to_string(),
}
})?;
Ok(session.record_conversions(conversions))
}
async fn parse_claude_line(&self, line: &str, session_id: &str) -> Vec<EventConversion> {
let trimmed = line.trim();
if trimmed.is_empty() {
return Vec::new();
}
let mut value: Value = match serde_json::from_str(trimmed) {
Ok(value) => value,
Err(err) => {
return vec![agent_unparsed(

Per

sandbox-agent/CLAUDE.md

Lines 30 to 34 in 1d1069d

- Keep CLI subcommands in sync with every HTTP endpoint.
- Update `CLAUDE.md` to keep CLI endpoints in sync with HTTP API changes.
- When adding or modifying CLI commands, update `docs/cli.mdx` to reflect the changes.
- When changing the HTTP API, update the TypeScript SDK and CLI together.
:

Keep CLI subcommands in sync with every HTTP endpoint.
Update CLAUDE.md to keep CLI endpoints in sync with HTTP API changes.


2. Missing HTTP test coverage for new API routes

The PR adds 10 new HTTP API routes for process/terminal management but does not include corresponding test coverage in server/packages/sandbox-agent/tests/http/.

Routes added in

let session = Self::session_mut(&mut sessions, session_id).ok_or_else(|| {
SandboxError::SessionNotFound {
session_id: session_id.to_string(),
}
})?;
Ok(session.record_conversions(conversions))
}
async fn parse_claude_line(&self, line: &str, session_id: &str) -> Vec<EventConversion> {
let trimmed = line.trim();
if trimmed.is_empty() {
return Vec::new();
}
let mut value: Value = match serde_json::from_str(trimmed) {
Ok(value) => value,
Err(err) => {
return vec![agent_unparsed(
:

  • POST /v1/process
  • GET /v1/process
  • GET /v1/process/:id
  • DELETE /v1/process/:id
  • POST /v1/process/:id/stop
  • POST /v1/process/:id/kill
  • GET /v1/process/:id/logs
  • POST /v1/process/:id/resize
  • POST /v1/process/:id/input
  • GET /v1/process/:id/terminal (WebSocket)

Per

sandbox-agent/CLAUDE.md

Lines 35 to 37 in 1d1069d

- Do not make breaking changes to API endpoints.
- When changing API routes, ensure the HTTP/SSE test suite has full coverage of every route.
- When agent schema changes, ensure API tests cover the new schema and event shapes end-to-end.
:

When changing API routes, ensure the HTTP/SSE test suite has full coverage of every route.

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