-
Notifications
You must be signed in to change notification settings - Fork 5
Open
Description
Part of #271 — Gastown Cloud Proposal A (Sandbox-per-Town)
Goal
Build the cloud-side WebSocket proxy and React terminal component so users can observe and interact with gastown agent sessions from the dashboard.
Context
The terminal proxy (PR 7) runs inside the sandbox. This PR adds the cloud app layer that authenticates users, proxies WebSocket connections to the sandbox, and renders terminal output using xterm.js.
Requirements
WebSocket Proxy (Cloud App)
Auth flow:
- Browser requests stream ticket via tRPC:
gastown.getStreamTicket({ townId, sessionName }) - Cloud app mints short-lived JWT (60s expiry):
Same pattern as
{ "userId": "...", "townId": "...", "sessionName": "...", "exp": ... }signStreamTicketincloud/src/lib/cloud-agent/. - Browser connects to WebSocket endpoint:
wss://app.kilo.ai/api/gastown/terminal?ticket=<jwt> - Cloud app validates ticket, extracts
townIdandsessionName - Cloud app opens upstream WebSocket to sandbox terminal proxy:
ws://<fly-proxy>:8081/sessions/<sessionName> - Bidirectional proxy: browser ↔ cloud app ↔ sandbox terminal proxy
Route: cloud/src/app/api/gastown/terminal/route.ts — WebSocket upgrade handler
tRPC Additions
Add to the gastown router:
| Route | Type | Description |
|---|---|---|
gastown.getStreamTicket |
mutation | Mint short-lived JWT for WebSocket auth |
gastown.listSessions |
query | Proxy to sandbox internal API GET /sessions |
React Component: GastownTerminal
Props:
townId: stringsessionName: string
Behavior:
- Renders xterm.js terminal with fit addon (auto-resize to container)
- Connects via WebSocket using stream ticket
- Displays terminal output from the agent session
- Read-only mode for polecats, witnesses, refineries (observation only)
- Read-write mode for Mayor session (user can type instructions)
- Reconnects automatically on disconnect (exponential backoff)
- Shows connection status indicator (connected/reconnecting/disconnected)
Session Picker Sidebar: GastownSessionPicker
Props:
townId: stringonSelectSession: (sessionName: string) => void
Behavior:
- Fetches session list from
gastown.listSessions - Color-coded by role:
- Mayor: gold
- Witness: blue
- Refinery: green
- Polecat: gray
- Shows session status: active/idle/dead
- Click to switch terminal view
- Auto-refreshes every 5 seconds
Files
cloud/src/app/api/gastown/terminal/route.ts(new) — WebSocket upgrade handlercloud/src/components/gastown/GastownTerminal.tsx(new)cloud/src/components/gastown/GastownSessionPicker.tsx(new)- Updates to
cloud/src/server/api/routers/gastown.ts—getStreamTicket,listSessions
Dependencies (npm)
xterm— terminal emulator@xterm/addon-fit— auto-resize addon
Acceptance Criteria
-
getStreamTicketmints a valid 60s JWT with correct payload - WebSocket upgrade handler validates ticket and rejects expired/invalid tokens
- WebSocket proxy correctly forwards output from sandbox to browser
- WebSocket proxy correctly forwards input from browser to sandbox (Mayor only)
-
GastownTerminalrenders xterm.js terminal with proper sizing - Terminal auto-resizes when container dimensions change
- Read-only mode prevents input for non-Mayor sessions
- Read-write mode allows input for Mayor session
- Auto-reconnect on disconnect with exponential backoff
- Connection status indicator shows current state
-
GastownSessionPickerlists sessions with correct role colors - Clicking a session in the picker switches the terminal view
- Session list auto-refreshes every 5 seconds
Dependencies
- PR 2 (Provisioning API) — town ownership verification
- PR 3 (Sandbox Internal API) —
GET /sessionsendpoint - PR 7 (Terminal Proxy) — sandbox-side WebSocket server
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels