Files
stablyai--orca/docs/reference/terminal-model-view-contract.md
2026-07-13 13:05:33 +08:00

12 KiB

Terminal Model/View Contract

Goal

Terminal output should have one authoritative model path and many disposable views. A renderer xterm is the fast interactive view, but it must not be the only place hidden, remote, mobile, SSH, or CLI-visible terminal state exists.

This contract defines the boundary the shipped terminal stack implements — and that future terminal work must preserve — without changing the query-response behavior that real shells and TUIs depend on. See Architecture Status for the shipped phases.

Terms

  • PTY stream: Ordered bytes read from a local PTY, daemon PTY, SSH relay PTY, or remote runtime PTY.
  • Terminal model: Main/runtime-owned state derived from PTY bytes. Today this is mostly the headless emulator plus retained read transcript state.
  • Terminal view: A renderer xterm, mobile subscriber, remote desktop subscriber, or CLI read page consuming model state and live output.
  • Snapshot: A bounded model serialization that can restore a view without replaying an unbounded byte log.
  • Transcript: The retained output contract for orca terminal read; it is line/cursor oriented and distinct from a screen snapshot.

Non-Negotiable Invariants

  1. PTY reads do not stop to protect renderer performance. Backpressure may bound delivery to views, but terminal state, notifications, titles, and agent status keep advancing from the PTY stream.
  2. Active visible terminal input/output stays on the lowest-latency path. Bulk hidden or background output must not delay keystroke-sized foreground redraws.
  3. Hidden views do not own unbounded output memory. Main's hidden-delivery gate drops renderer-bound bytes for hidden-marked PTYs after model ingestion and emits an out-of-band restore marker (pty:modelRestoreNeeded) so the view restores from the model on reveal. With the gate's kill switches off, hidden bytes ride a bounded renderer queue whose overflow latches the same model restore.
  4. Returning to a hidden or slept terminal must show model-correct output. A stale or replaced view may be cleared and replayed from a snapshot, but it must not show a warning fallback when model recovery is available.
  5. Snapshots and live bytes have ordering metadata. A view restore must not duplicate bytes already included in the snapshot or drop bytes that arrived after it. Main buffer snapshots report the pending-delivery start sequence (pendingDeliveryStartSeq) so the renderer reconciles live chunks racing a restore without misreading foreign sequence domains as duplicates.
  6. Terminal query authority is singular and structural: the party that writes a chunk into a live terminal answers its queries. Visible renderer and remote views keep xterm authority. Chunks dropped by the hidden-delivery gate are answered exactly once by the main model responder, from runtime-emulator state plus renderer-pushed view attributes. Replayed, seeded, or snapshot bytes are answered by no one. The daemon emulator never answers. (Amended by Phase 5 — see terminal-query-authority.md.)
  7. The transcript contract stays separate from screen restore. orca terminal read must preserve bounded previews, cursor pagination, partial-line rules, truncation flags, and total counts even if view snapshots change shape.
  8. Local, daemon, SSH, remote runtime, mobile, and CLI paths must either satisfy the same model/view contract or explicitly report that model recovery is unavailable.

Current Owners

Responsibility Current owner
PTY byte source and local/SSH delivery src/main/ipc/pty.ts
Hidden-delivery gate (hidden marks, delivery interest, drop accounting, restore markers) src/main/ipc/pty-hidden-delivery-gate.ts, drop sites in src/main/ipc/pty.ts and src/main/ssh/ssh-relay-session.ts
Side-effect parsing and the pty:sideEffect facts channel src/shared/terminal-output-side-effects.ts driven from OrcaRuntimeService.onPtyData; renderer policy in src/renderer/src/components/terminal-pane/terminal-side-effect-facts-handler.ts
Model query responder and view-attribute bridge src/main/runtime/terminal-model-query-authority.ts, src/main/daemon/terminal-view-attribute-responder.ts, src/main/runtime/terminal-view-attribute-store.ts
Hidden view parking policy and parked watcher src/renderer/src/components/terminal-pane/terminal-hidden-view-parking.ts, parked-terminal-byte-watcher.ts
Daemon PTY state and headless snapshots src/main/daemon/headless-emulator.ts
Runtime headless state, retained reads, mobile/session tabs src/main/runtime/orca-runtime.ts
Remote terminal subscribe/multiplex/ACK semantics src/main/runtime/rpc/methods/terminal.ts
Renderer xterm view and hidden restore behavior src/renderer/src/components/terminal-pane/pty-connection.ts
Remote desktop runtime xterm transport src/renderer/src/runtime/remote-runtime-terminal-multiplexer.ts

Snapshot Contract

A model snapshot must include:

  • terminal dimensions used to produce the snapshot;
  • enough ANSI state to rehydrate xterm before snapshot content;
  • bounded screen and scrollback content;
  • title and cwd metadata when known;
  • source metadata that distinguishes headless/model snapshots from renderer fallback snapshots;
  • monotonic ordering metadata for live-output reconciliation when available.

A snapshot must not:

  • include unbounded transcript history;
  • answer terminal queries while replaying into the model;
  • overwrite newer live view output with older model output;
  • hide that recovery was unavailable for a PTY surface.

View Contract

A renderer or remote view may:

  • write active visible output immediately;
  • budget visible inactive output;
  • stop receiving hidden output entirely while main's hidden-delivery gate owns the bytes (model restore on reveal);
  • request fresh snapshots for restore, mobile subscription, or explicit remote snapshot recovery.

A view must:

  • keep live-output buffers bounded while a snapshot is in flight;
  • apply generation or sequence checks before replaying a snapshot;
  • refresh/repaint after replay when xterm/WebGL needs an explicit paint;
  • keep side effects such as title, bell, cwd, and agent status flowing from the PTY/model path (the pty:sideEffect facts channel) even while renderer byte delivery is budgeted, gated, or parked.

Transcript Contract

The retained read transcript is not a screen dump. It must preserve:

  • uncursored bounded latest preview behavior;
  • cursor reads over completed retained lines;
  • oldestCursor, nextCursor, latestCursor, and returnedLineCount;
  • partial-line duplication rules;
  • truncated, limited, and total count metadata;
  • bounded memory for long partial lines and large output bursts.

Snapshot optimizations must be tested against this transcript contract instead of assuming xterm scrollback serialization can replace it.

Required Contract Tests

Before moving more runtime behavior behind the model/view boundary, add or extend tests that prove:

  • headless snapshots rehydrate rich alternate-screen TUI state;
  • the daemon emulator never answers DA, DSR, OSC 11, or theme-sensitive queries (the session.test.ts pins are permanent);
  • the main runtime responder answers queries only from live chunks the hidden-delivery gate dropped — never delivered, replayed, seeded, or remote-subscribed chunks;
  • hidden renderer overflow restores from model state without duplicate live output;
  • sleep/wake and worktree revisit restore from model-correct state;
  • SSH-backed PTYs follow the same snapshot and ordering semantics as local PTYs;
  • remote runtime multiplex output remains ACK bounded and can request recovery snapshots;
  • mobile subscribers receive bounded snapshots without unbounded pending live output;
  • retained terminal reads remain pageable and bounded after large output.

Current coverage is spread across:

  • src/main/daemon/headless-emulator.test.ts
  • src/main/daemon/session.test.ts
  • src/main/ipc/pty.test.ts (hidden-gate drops, restore markers, pendingDeliveryStartSeq)
  • src/main/ipc/pty-hidden-delivery-gate.test.ts
  • src/main/runtime/mobile-subscribe-integration.test.ts
  • src/main/runtime/rpc/terminal-subscribe-buffer.test.ts
  • src/main/runtime/rpc/terminal-multiplex.test.ts
  • src/main/runtime/orca-runtime.test.ts
  • src/main/runtime/terminal-query-responder.test.ts
  • src/shared/terminal-output-side-effects.test.ts
  • src/renderer/src/components/terminal-pane/terminal-title-tracker-parity.test.ts
  • src/renderer/src/components/terminal-pane/terminal-side-effect-facts-handler.test.ts
  • src/renderer/src/components/terminal-pane/terminal-hidden-view-parking.test.ts
  • src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.test.ts
  • src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.test.ts
  • tests/e2e/terminal-hidden-tui-visual-restore.spec.ts
  • tests/e2e/terminal-hidden-view-parking.spec.ts
  • tests/e2e/terminal-parked-memory.spec.ts
  • tests/e2e/terminal-sleep-wake-restore.spec.ts
  • tests/e2e/terminal-output-scheduler.spec.ts
  • tests/e2e/artificial-opencode-terminal-load.spec.ts

Architecture Status

All six phases of the terminal model/view architecture are shipped; the kill switches noted in parentheses default on:

  1. Hidden view parking — "Park hidden terminal views behind a byte watcher": hidden terminal tabs unmount their xterm after a cold-park hysteresis; a pane-less watcher keeps bell/title/agent/PR side effects alive while parked (terminalHiddenViewParking). See terminal-hidden-view-parking.md.
  2. Parked memory benchmarks — "Benchmark parked hidden terminal memory": renderer heap and live-terminal counts gate parking in the perf suite (tests/e2e/terminal-parked-memory.spec.ts).
  3. Side-effect authority in main — "Track terminal titles in main with all-titles ordering", "Move terminal side-effect authority to a main facts channel", "Complete terminal side-effect facts coverage", "Finish terminal side-effect authority migration": every local/daemon/SSH PTY byte is side-effect-parsed once in main and delivered as pty:sideEffect facts (terminalMainSideEffectAuthority). See terminal-side-effect-authority.md.
  4. Hidden delivery gate — "Gate PTY delivery to hidden terminal views": main drops renderer-bound bytes for hidden-marked PTYs after model ingestion; delivery-interest registrations exempt sidecar byte consumers, out-of-band restore markers latch model restore, and pendingDeliveryStartSeq reconciles live output racing a restore (terminalHiddenDeliveryGate).
  5. Model query authority — "Answer hidden terminal queries from the model", "Bridge renderer view attributes to the model responder", "Align query authority contract and spawn-time ownership": hidden-dropped queries are answered by the runtime emulator plus renderer-pushed view attributes, and hidden-at-spawn PTYs are marked before byte one (terminalModelQueryAuthority). See terminal-query-authority.md.
  6. Skip grammar deletion — "Delete the hidden renderer skip grammar": the renderer's per-chunk hidden-skip eligibility grammar and the 10s codex startup query window are deleted; the kill-switch-off fallback is the bounded background queue with overflow-latched model restore.

Treat every hidden/slept/revisited TUI glitch as a contract failure, not as a local repaint quirk. Renderer fallback paths retire only when their kill switches do, and only after the equivalent model path has platform and TUI golden coverage.