Phase 14: Embedded terminals and agent sessions
Implemented
- Added a provider-neutral terminal domain for typed shell, Compose service, database-client, coding-agent, and reviewed interactive-action sessions.
- Added a daemon-owned Unix PTY adapter with explicit working directory, argument-array commands, terminal environment, resize, and process-group termination. Phase 18 subsequently added the native ConPTY adapter.
- Added owner-scoped application lifecycle, bounded 1 MiB reconnect scrollback, non-blocking subscribers, a 30-minute detached idle deadline, explicit termination, daemon-shutdown interruption, and restart recovery.
- Added SQLite schema version 11 for metadata-only session and audit records. Terminal bytes, input, commands, arguments, and environment values are not persisted.
- Added generated REST and TypeScript contracts for terminal and agent session create/list/get/terminate operations.
- Added authenticated same-origin terminal and agent WebSockets. Binary frames carry PTY bytes; bounded JSON frames carry resize control; unknown controls, oversized input, owner mismatch, and slow consumers are rejected.
- Added typed resolution through accepted catalog, action, environment, and
Compose facts. Custom commands require an accepted
interactiveaction; privilege-escalating shell text and path-escaping Compose inputs are denied. - Added xterm.js with Fit and WebLinks addons, screen-reader mode, debounced resize, bounded presentation scrollback, modifier-gated HTTP(S) links, full session-state UI, reconnect controls, and explicit process termination.
- Added project Terminal and Agents surfaces. Agent metadata includes provider,
project, worktree, cwd, status, output byte count, and literal
user_visible_terminal_output_onlycapture semantics. - Preserved trusted external-terminal handoff beside the embedded terminal. MCP deliberately retains no generic terminal, shell, SQL, or Docker tool.
Architecture decisions
No ADR changed. The implementation applies ADR-0004’s REST/WebSocket split, ADR-0005’s domain-owned SQLite boundary, ADR-0007’s process ownership, ADR-0010 and ADR-0011’s provider-neutral least-privilege agent integration, ADR-0013’s local transport security, and ADR-0015’s Unix-first platform order.
The persistence policy is an implementation choice within those accepted decisions: a browser disconnect detaches, the daemon keeps the PTY for a bounded idle window, and restart interrupts rather than fabricating resume.
Safety properties
- Public APIs cannot submit a raw command. The resolver emits an argument array only after accepted project trust and selected-worktree validation.
- Session ownership is checked below the transport for list, get, attach, and terminate. Browser authentication and exact same-origin validation happen before the WebSocket reaches that boundary.
- A slow subscriber is detached without blocking PTY output. Client input, terminal dimensions, subscriber queues, reconnect output, and browser scrollback all have explicit bounds.
- Audits contain lifecycle events, principals, dimensions, byte counts, and process identity only. They contain no terminal input/output or environment.
- Terminal links accept only HTTP(S), require a modifier key, and use
noopener,noreferrer.
Tests added
- Typed launch validation, owner identity validation, start failure, request cancellation, detach persistence, reconnect scrollback, explicit termination, idle expiry, restart interruption, resize/input forwarding, and slow-subscriber fanout load.
- Compose declaration/path resolution, custom-action risk, privilege escalation, working-directory containment, argument arrays, and environment forwarding.
- Real Unix PTY Unicode, ANSI color, alternate-screen sequences, resize, exit, and process-group termination.
- SQLite metadata/audit round trips, missing-session mapping, restart recovery, schema version 11, and absence of sensitive payload columns.
- REST translation and filtering, browser cookie/origin protection on both PTY paths, WebSocket ready/snapshot/input/resize/output/exit protocol, and invalid control rejection.
- Vue typed launcher and agent disclosure unit tests, live browser shell and reconnect E2E coverage, and terminal visual regression.
Acceptance criteria status
- A real interactive shell opens at the trusted project or selected worktree directory.
- Resize, Unicode, ANSI color, and alternate-screen/full-screen control sequences work through a real Unix PTY.
- Browser close behavior is documented and implemented as bounded detach with reconnect, explicit termination, and idle expiry.
- Missing/invalid browser sessions, cross-origin requests, and owner mismatches cannot attach to PTYs.
- Agent records use a literal user-visible-output capture policy and never claim hidden-reasoning access.
Verification evidence
Verified on 2026-07-16 with make quality:
- generated OpenAPI, TypeScript, JSON Schema, and SQL code had no drift;
gofmt,go vet, golangci-lint, architecture checks, ESLint, and TypeScript checks passed;- all Go tests, 20 Vue unit tests, the Go race suite, and a clean schema-11 migration passed;
govulncheck ./...reported no vulnerabilities;- four Chromium E2E scenarios passed against the live daemon, including a
real
shPTY in the mixed-project fixture, UTF-8 output, browser detach and reconnect, explicit termination, native-process lifecycle, and Compose lifecycle; - nine Chromium visual regressions passed, including the typed terminal launcher and persistence disclosure; and
- the production Vue bundle and trimmed Go binary built successfully.
The Unix adapter integration suite separately exercised ANSI color, alternate-screen sequences, resize propagation, Unicode, normal exit, and process-group termination through a real PTY.
Known limitations and deferred work
- Unix PTYs are production-capable on macOS/Linux. Native Windows ConPTY is now supplied by Phase 18 rather than a silent pipe fallback.
- Rich provider SDK orchestration is deferred by the scope guard. Codex and Claude Code run through their reliable user-visible PTY interfaces.
- PTY scrollback is intentionally not durable across daemon restarts.
2026-07-21 terminal glyph evidence
- The PTY continues to launch the user’s selected/default login shell, so zsh, Oh My Zsh, and Powerlevel10k startup behavior remains user-owned.
- The xterm renderer now prefers the Powerlevel10k-recommended
MesloLGS NFfamily and retains the existing portable monospace fallbacks. Systems without that installed family continue through the CSS font stack without assuming Powerlevel10k. - Vue unit coverage fixes the glyph-capable font order, and the real-daemon E2E terminal flow plus all 14 visual regression scenarios passed.