Skip to content

Terminal Proxy #287

@jrf0110

Description

@jrf0110

Part of #271 — Gastown Cloud Proposal A (Sandbox-per-Town)

Goal

Build a WebSocket server that runs inside the gastown sandbox and streams tmux pane output to web clients, enabling observation and interaction with agent sessions.

Context

Gastown agents run as tmux sessions. To provide a web UI, we need to stream terminal output from these sessions to the browser. This proxy sits inside the sandbox and connects the tmux server to WebSocket clients.

Requirements

Terminal Proxy Binary

A small Go binary (or Node.js server) that:

  1. Listens on a dedicated port (e.g., 8081) for WebSocket connections
  2. Streams output: Periodically runs tmux capture-pane -p -t <session> (200ms interval) and sends the output to connected clients
  3. Accepts input: Receives text from clients and injects via tmux send-keys -t <session> <text> Enter
  4. Handles resize: Accepts resize events and runs tmux resize-pane -t <session> -x <cols> -y <rows>
  5. Supports multiple viewers: Multiple clients can connect to the same session (broadcast)
  6. Session routing: WebSocket path includes session name: ws://localhost:8081/sessions/<name>

Auth

  • x-internal-api-key query parameter or header on WebSocket upgrade
  • Same key as the sandbox internal API (PR 3)
  • Reject connections without valid key

Output Format

Each WebSocket message is JSON:

{
  "type": "output",
  "data": "<captured pane content>",
  "timestamp": 1700000000
}

Input messages:

{
  "type": "input",
  "data": "the text to send"
}

Resize messages:

{
  "type": "resize",
  "cols": 120,
  "rows": 40
}

Session Discovery

GET /sessions endpoint (HTTP, not WebSocket) returns available tmux sessions:

{
  "sessions": [
    { "name": "Mayor", "role": "mayor", "alive": true },
    { "name": "Witness-myrig", "role": "witness", "alive": true },
    { "name": "polecat-Toast", "role": "polecat", "alive": true }
  ]
}

Performance

  • Capture interval: 200ms (configurable)
  • Only send output when content has changed (diff detection via checksum)
  • Buffer: keep last 1000 lines for new client catch-up
  • Max concurrent viewers per session: 10

Files

  • cloud/infra/gastown-sandbox/terminal-proxy/ (new directory)
    • main.go (or server.ts)
    • go.mod / go.sum (or package.json)
  • Updates to cloud/infra/gastown-sandbox/Dockerfile — add binary to image
  • Updates to cloud/infra/gastown-sandbox/startup.sh — start proxy

Acceptance Criteria

  • WebSocket server starts on port 8081
  • Clients can connect and receive tmux pane output stream
  • Output only sent when content changes (no redundant frames)
  • Input messages inject text into the correct tmux session
  • Resize messages resize the tmux pane
  • Multiple clients can view the same session simultaneously
  • Session discovery endpoint returns available sessions with roles
  • Auth rejects connections without valid key
  • New clients receive buffered history (last 1000 lines)
  • Proxy handles non-existent sessions gracefully (error message, not crash)
  • Binary is included in the Docker image and started by startup.sh

Dependencies

  • PR 1 (Sandbox Docker Image) — startup.sh launches this proxy
  • PR 3 (Sandbox Internal API) — shared auth key

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions