# Interactive, Print, RPC, and TUI Surfaces

> The runtime adapters above AgentSession: terminal interaction, print and JSON modes, JSONL RPC protocol, keybindings, editor components, overlays, and differential rendering.

- Repository: earendil-works/pi
- GitHub: https://github.com/earendil-works/pi
- Human wiki: https://grok-wiki.com/public/wiki/earendil-works-pi-121d322b171c
- Complete Markdown: https://grok-wiki.com/public/wiki/earendil-works-pi-121d322b171c/llms-full.txt

## Source Files

- `packages/coding-agent/src/modes/interactive/interactive-mode.ts`
- `packages/coding-agent/src/modes/print-mode.ts`
- `packages/coding-agent/src/modes/rpc/rpc-mode.ts`
- `packages/coding-agent/src/modes/rpc/rpc-types.ts`
- `packages/coding-agent/src/core/keybindings.ts`
- `packages/tui/src/tui.ts`
- `packages/tui/src/components/editor.ts`
- `packages/tui/src/keybindings.ts`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [packages/coding-agent/src/main.ts](packages/coding-agent/src/main.ts)
- [packages/coding-agent/src/cli/args.ts](packages/coding-agent/src/cli/args.ts)
- [packages/coding-agent/src/modes/interactive/interactive-mode.ts](packages/coding-agent/src/modes/interactive/interactive-mode.ts)
- [packages/coding-agent/src/modes/print-mode.ts](packages/coding-agent/src/modes/print-mode.ts)
- [packages/coding-agent/src/modes/rpc/rpc-mode.ts](packages/coding-agent/src/modes/rpc/rpc-mode.ts)
- [packages/coding-agent/src/modes/rpc/rpc-types.ts](packages/coding-agent/src/modes/rpc/rpc-types.ts)
- [packages/coding-agent/src/modes/rpc/jsonl.ts](packages/coding-agent/src/modes/rpc/jsonl.ts)
- [packages/coding-agent/src/core/keybindings.ts](packages/coding-agent/src/core/keybindings.ts)
- [packages/tui/src/tui.ts](packages/tui/src/tui.ts)
- [packages/tui/src/components/editor.ts](packages/tui/src/components/editor.ts)
- [packages/tui/src/keybindings.ts](packages/tui/src/keybindings.ts)
- [packages/tui/test/tui-render.test.ts](packages/tui/test/tui-render.test.ts)
- [packages/tui/test/overlay-options.test.ts](packages/tui/test/overlay-options.test.ts)
- [packages/coding-agent/test/rpc-jsonl.test.ts](packages/coding-agent/test/rpc-jsonl.test.ts)
- [packages/coding-agent/test/print-mode.test.ts](packages/coding-agent/test/print-mode.test.ts)
</details>

# Interactive, Print, RPC, and TUI Surfaces

This page explains the runtime adapters that sit above `AgentSession`: interactive terminal mode, single-shot print/JSON mode, JSONL RPC mode, keybinding resolution, TUI components, overlays, editor behavior, and differential rendering. These surfaces decide how user input, extension UI, and session events are transported; `AgentSession` remains the common execution layer.

This page follows the provided bundled Compound Engineering wiki guidance for page shape. No repository `STRATEGY.md`, generated wiki page, or `docs/solutions` source was found during this focused pass, so implementation code is the source of truth.

## Surface Selection and Adapter Boundary

The CLI resolves four app modes: `interactive`, `print`, `json`, and `rpc`. `--mode rpc` selects RPC, `--mode json` selects JSON print mode, `--print` or non-TTY stdin selects print mode, and otherwise the CLI starts interactive mode. The main entrypoint then dispatches to `runRpcMode`, constructs `InteractiveMode`, or calls `runPrintMode` with `"text"`/`"json"` output mode.

Sources: [packages/coding-agent/src/main.ts:97-112](), [packages/coding-agent/src/main.ts:678-714](), [packages/coding-agent/src/cli/args.ts:74-78]()

| Surface | Module | Input | Output | Primary use |
|---|---|---|---|---|
| Interactive | `interactive-mode.ts` | terminal keys, editor text, dialogs | differential TUI rendering | normal terminal chat |
| Print text | `print-mode.ts` | initial prompt and extra messages | final assistant text | scripts and one-shot CLI calls |
| Print JSON | `print-mode.ts` | initial prompt and extra messages | session header and events as JSON lines | event capture without RPC command loop |
| RPC | `rpc-mode.ts` | JSON commands on stdin | JSON responses, events, extension UI requests | embedding in other processes |

```mermaid
flowchart TB
  subgraph CLI["packages/coding-agent/src/main.ts"]
    Resolve["resolveAppMode()"]
  end

  subgraph Surfaces["Runtime adapters above AgentSession"]
    Interactive["InteractiveMode\ninteractive-mode.ts"]
    Print["runPrintMode\nprint-mode.ts"]
    RPC["runRpcMode\nrpc-mode.ts"]
  end

  subgraph UI["Terminal/TUI package"]
    TUI["TUI\npackages/tui/src/tui.ts"]
    Editor["Editor\npackages/tui/src/components/editor.ts"]
    Keybindings["KeybindingsManager\npackages/tui/src/keybindings.ts"]
  end

  Session["AgentSession / AgentSessionRuntime"]

  Resolve --> Interactive
  Resolve --> Print
  Resolve --> RPC
  Interactive --> TUI
  TUI --> Editor
  Editor --> Keybindings
  Interactive --> Session
  Print --> Session
  RPC --> Session
```

## Interactive Mode

`InteractiveMode` owns terminal interaction. Its constructor creates a `TUI` backed by `ProcessTerminal`, builds containers for header, chat, pending messages, status, widgets, editor, and footer, creates the app-level `KeybindingsManager`, and installs those keybindings globally for TUI components.

Sources: [packages/coding-agent/src/modes/interactive/interactive-mode.ts:359-384](), [packages/coding-agent/src/modes/interactive/interactive-mode.ts:568-699]()

During `init()`, interactive mode registers signal handlers, ensures tools used by autocomplete/search exist, builds startup hints from keybindings, adds the main containers to the TUI, focuses the editor, installs key handlers and submit handling, starts the TUI, then rebinds the current session so extensions can use interactive dialogs.

The main `run()` loop processes optional startup messages first, then repeatedly waits for editor input and sends it to `session.prompt()`. Session events are subscribed through `session.subscribe()`, and event handlers update chat components, pending-message displays, footer state, streaming assistant content, tool components, loaders, and terminal progress.

Sources: [packages/coding-agent/src/modes/interactive/interactive-mode.ts:716-784](), [packages/coding-agent/src/modes/interactive/interactive-mode.ts:2645-2729]()

### Interactive Extension UI

Interactive extensions receive a full `ExtensionUIContext`: selectors, confirms, inputs, notifications, terminal input listeners, status, working indicators, widgets, footer/header replacement, title changes, custom UI, editor text operations, autocomplete providers, custom editor components, theme access, and tool expansion state.

Sources: [packages/coding-agent/src/modes/interactive/interactive-mode.ts:1483-1564](), [packages/coding-agent/src/modes/interactive/interactive-mode.ts:1950-2028]()

Interactive submit handling is command-aware. Built-in slash commands such as `/settings`, `/model`, `/export`, `/import`, `/share`, `/fork`, `/tree`, `/login`, `/logout`, `/new`, `/compact`, `/reload`, `/resume`, and `/quit` are intercepted before ordinary prompt submission. Bash commands are recognized with `!` and `!!`. While compaction or streaming is active, text is queued or sent with `streamingBehavior` so steering and follow-up behavior remains explicit.

Sources: [packages/coding-agent/src/modes/interactive/interactive-mode.ts:2464-2643](), [packages/coding-agent/src/modes/interactive/interactive-mode.ts:3390-3425]()

## Print and JSON Modes

`runPrintMode()` is single-shot. It binds extensions with command-context actions, subscribes to session events, sends the initial prompt and extra messages, and exits. In `"json"` mode it writes the session header and every session event as JSON lines. In `"text"` mode it prints only text content from the final assistant message and returns non-zero if the final assistant stop reason is `error` or `aborted`.

Sources: [packages/coding-agent/src/modes/print-mode.ts:1-145](), [packages/coding-agent/test/print-mode.test.ts:93-151]()

Print mode also installs signal handlers for `SIGTERM` and non-Windows `SIGHUP`, kills tracked detached children on those signals, disposes the runtime once, removes signal handlers in `finally`, and flushes guarded raw stdout before returning.

Sources: [packages/coding-agent/src/modes/print-mode.ts:32-63](), [packages/coding-agent/src/modes/print-mode.ts:136-145]()

## RPC JSONL Protocol

RPC mode is headless and starts by taking over stdout. It serializes every response, session event, and extension UI request as one JSON line. The protocol types define commands for prompting, aborting, session management, state, model selection, thinking levels, queue modes, compaction, retry, bash, message retrieval, and command listing.

Sources: [packages/coding-agent/src/modes/rpc/rpc-mode.ts:48-80](), [packages/coding-agent/src/modes/rpc/rpc-types.ts:17-70](), [packages/coding-agent/src/modes/rpc/rpc-types.ts:111-208]()

RPC extension UI is represented as data. Requests such as `select`, `confirm`, `input`, `editor`, `notify`, `setStatus`, `setWidget`, `setTitle`, and `set_editor_text` are emitted on stdout. The client answers with `extension_ui_response` records correlated by `id`.

Sources: [packages/coding-agent/src/modes/rpc/rpc-mode.ts:82-263](), [packages/coding-agent/src/modes/rpc/rpc-types.ts:213-258]()

The command loop parses each JSON line, handles extension UI responses before treating input as an `RpcCommand`, writes a success or error response, and shuts down when stdin ends or an extension requests shutdown. Prompt commands are asynchronous: RPC sends the authoritative prompt response after prompt preflight succeeds, while session events continue streaming separately.

Sources: [packages/coding-agent/src/modes/rpc/rpc-mode.ts:306-410](), [packages/coding-agent/src/modes/rpc/rpc-mode.ts:668-755]()

JSONL framing is strict LF framing rather than Node `readline`; payload strings may contain Unicode separators such as `U+2028` and `U+2029`, and tests cover LF-only splitting, CRLF input, and final lines without trailing LF.

Sources: [packages/coding-agent/src/modes/rpc/jsonl.ts:1-50](), [packages/coding-agent/test/rpc-jsonl.test.ts:1-55]()

## Keybindings

The TUI package defines generic editor, input, and selection keybindings, plus a `KeybindingsManager` that resolves defaults and user overrides. It does not evict defaults when another action reuses a key; it records direct user-binding conflicts separately.

Sources: [packages/tui/src/keybindings.ts:1-230](), [packages/tui/test/keybindings.test.ts:1-34]()

The coding-agent package extends TUI keybindings through declaration merging with app actions such as interrupt, clear, exit, model cycling, model selection, tool expansion, thinking toggle, external editor, follow-up queueing, image paste, session tree/fork/resume, tree filters, and model selector actions. User config is read from `keybindings.json`, legacy names are migrated, and effective bindings can be retrieved from the app manager.

Sources: [packages/coding-agent/src/core/keybindings.ts:1-202](), [packages/coding-agent/src/core/keybindings.ts:270-365]()

## TUI Component Model, Focus, and Overlays

`TUI` is a `Container` of `Component`s. Components render to terminal-width-aware string arrays, may handle input when focused, may opt into key-release events, and can invalidate cached rendering. Focusable components expose a `focused` boolean and can emit `CURSOR_MARKER` so TUI can position the hardware cursor for IME candidate windows.

Sources: [packages/tui/src/tui.ts:35-90](), [packages/tui/src/tui.ts:196-237]()

Overlays are stacked components with sizing, positioning, visibility predicates, optional non-capturing behavior, focus ordering, and handles for hide/show/focus/unfocus. Interactive mode uses both overlay-style custom UI and editor-container replacement for extension selectors, inputs, editors, OAuth dialogs, reload boxes, and other modal flows.

Sources: [packages/tui/src/tui.ts:150-407](), [packages/tui/test/overlay-options.test.ts:1-180](), [packages/coding-agent/src/modes/interactive/interactive-mode.ts:2056-2185]()

## Editor Component

The editor stores lines plus cursor position, supports padding, dynamic border color, vertical scrolling, autocomplete, bracketed paste buffering, paste markers for large pastes, prompt history, Emacs-style kill/yank, character jump mode, sticky visual columns, and undo.

Sources: [packages/tui/src/components/editor.ts:220-335]()

Rendering word-wraps text to the current layout width, limits visible editor height to a fraction of terminal rows, keeps the cursor visible, emits `CURSOR_MARKER` when focused, draws top/bottom borders, and appends an autocomplete list when active.

Sources: [packages/tui/src/components/editor.ts:409-532]()

Input handling is keybinding-driven. It handles bracketed paste first, then undo, autocomplete navigation/application, tab completion, deletion, kill ring, cursor movement, newline insertion, submit, history navigation, paging, character jump triggers, shifted space, and printable input. `getExpandedText()` expands paste markers back to their original content for consumers that need full text.

Sources: [packages/tui/src/components/editor.ts:534-814](), [packages/tui/src/components/editor.ts:911-955]()

## Differential Rendering

The TUI renderer keeps previous rendered lines, previous dimensions, previous viewport position, previous Kitty image IDs, and cursor rows. A render request is coalesced and rate-limited to a minimum interval. Forced render clears previous state and schedules a full redraw.

Sources: [packages/tui/src/tui.ts:239-296](), [packages/tui/src/tui.ts:430-620]()

`doRender()` renders all components, composites overlays, extracts the cursor marker, applies line resets, then chooses between full redraw and differential update. Full redraw is used for first render, width changes, most height changes, optional clear-on-shrink, or changes above the visible viewport. Otherwise it computes first and last changed lines, deletes changed Kitty images when needed, writes only changed lines, clears deleted rows, and updates previous render state. Tests cover Kitty image cleanup and resize behavior.

Sources: [packages/tui/src/tui.ts:920-1279](), [packages/tui/test/tui-render.test.ts:1-200]()

## Implementation Responsibilities

| Area | Owner | Responsibility |
|---|---|---|
| Mode dispatch | `main.ts` | Convert CLI flags and stdin TTY state into runtime surface selection |
| Interactive adapter | `interactive-mode.ts` | Terminal UI, extension dialogs, editor lifecycle, session event rendering |
| Print adapter | `print-mode.ts` | One-shot prompts, final text output, JSON event stream mode |
| RPC adapter | `rpc-mode.ts` / `rpc-types.ts` | JSONL command loop, typed responses, event streaming, extension UI request/response bridge |
| TUI runtime | `tui.ts` | Component tree, focus, overlays, input forwarding, differential renderer |
| Editor | `components/editor.ts` | Multiline editing, autocomplete, paste handling, keybinding-driven text operations |
| Keybindings | `tui/src/keybindings.ts` and `coding-agent/src/core/keybindings.ts` | Shared TUI defaults plus app-specific configurable bindings |

Sources: [packages/coding-agent/src/main.ts:678-714](), [packages/coding-agent/src/modes/rpc/rpc-mode.ts:379-641](), [packages/tui/src/tui.ts:544-595]()

## Summary

The repository uses one common session runtime with multiple transport adapters: interactive mode renders a rich terminal UI, print mode turns a prompt sequence into text or JSON event output, and RPC mode exposes a JSONL command protocol for embedding. The TUI layer provides reusable components, focus, overlays, keybindings, editor behavior, and differential rendering without depending on a specific model provider or hosted service; provider/model choices flow through session state and commands rather than the UI transport itself. Sources: [packages/coding-agent/src/modes/rpc/rpc-types.ts:17-70](), [packages/coding-agent/src/modes/interactive/interactive-mode.ts:568-699](), [packages/tui/src/tui.ts:920-1279]().
