You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit a9a669e
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: docs/ARCHITECTURE.md
+4-4Lines changed: 4 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -76,11 +76,11 @@ In TUI chat mode there is no completion gate — the session stays open across t
76
76
- Both settings files and the project/global grant store (`.corbits/permissions.json`) are on the secret-guard denylist for path-keyed tools, so the agent cannot `read_file` its own credentials or persist standing auto-approvals. Shell commands that reference them still require explicit operator approval.
77
77
- Credential-surface ownership: each auth store module enumerates its own files (`*_AUTH_FILENAME` / `MCP_AUTH_DIRNAME`), the data-only registry in `src/auth/credential-surface.ts` turns them into denylist patterns, `secret-guard-plugin.ts` owns matching (lexical plus realpath), and `@mention` resolution consumes the resolved check — never the registry directly. A new `*-auth.json` token store is denied only once its store module exports its filename and the registry lists it; the coverage test scans store sources for `*-auth.json` literals (registered dirnames get an includes-check instead) and fails the build until both exist.
78
78
79
-
### TUI Runner (`src/tui/runner.ts`)
79
+
### TUI Runner (`src/tui/runner/`)
80
80
81
81
- Builds a chat-mode agent using the `ChatDirector`
82
82
- Wires `ask_operator` to an operator-gate event resolved by a modal
83
-
- Mounts the OpenTUI host via `mountRunnerHost` (`src/tui/runner-host.ts`), which mounts `mountProductHost` (`src/tui/product-host.ts`) over the shell (`src/tui/shell.ts`)
83
+
- Mounts the OpenTUI host via `mountRunnerHost` (`src/tui/runner/host.ts`), which mounts `mountProductHost` (`src/tui/product-host.ts`) over the shell (`src/tui/shell/`)
84
84
- Bridges reactor events to the OpenTUI host via a plain `EventEmitter`
85
85
-**Mid-run injection** — Shell `session-queue` items drain at the parent
86
86
`tool.boundary` through `SessionPort.deliver`. Production `routeQueuedDelivery`
@@ -314,7 +314,7 @@ Every shipped specialist is a **director package** — a prompt-first `DirectorP
314
314
315
315
**Wait mailbox** (`src/subagent/agent-fleet.ts``FleetMailbox`): per-install overlay over that session store. Wait JSON is a projection of stored lifecycle plus mailbox membership, pin, collected, and optional interrupt override — not a second terminal store. Mailbox `register` pins an uncollected result (honored by prune); past `MAX_FLEET_RECORDS` the oldest never-collected pin is compacted to a tombstone. Operator cancel projects wait status `interrupted`.
316
316
317
-
**Observe (OpenTUI)**: `shell.ts:enterSubagentObserve` swaps the transcript for a child's stream (live while running, historical when done) without stealing the parent reactor; child events are mapped to stream rows by `src/tui/observe-map.ts`. Esc leaves observe and restores the parent transcript. Parent Esc/stop and `/clear` still call `cancelAll` so live children close (`agent.close`) instead of continuing after the parent stops. The host-injection point that resolves a live session (`onObserveRequest` → `observeSessionFromSubAgents`, `src/tui/runner-host.ts`, picking the newest running child else the most recent session of any status) is triggered by Alt+O (`shell.ts:observeActiveSubagent`) — the command palette action that used to call it is gone along with `src/tui/palette.ts` itself, but the chord replaces it rather than dropping the feature.
317
+
**Observe (OpenTUI)**: `shell/observe.ts:enterSubagentObserve` swaps the transcript for a child's stream (live while running, historical when done) without stealing the parent reactor; child events are mapped to stream rows by `src/tui/observe-map.ts`. Esc leaves observe and restores the parent transcript. Parent Esc/stop and `/clear` still call `cancelAll` so live children close (`agent.close`) instead of continuing after the parent stops. The host-injection point that resolves a live session (`onObserveRequest` → `observeSessionFromSubAgents`, `src/tui/runner-host.ts`, picking the newest running child else the most recent session of any status) is triggered by Alt+O (`shell.ts:observeActiveSubagent`) — the command palette action that used to call it is gone along with `src/tui/shell/palette.ts` itself, but the chord replaces it rather than dropping the feature.
318
318
319
319
Data-only agent plugins (`src/plugins/data-only-agent.ts`) synthesize `agentPlugin.agents[]` from `agents/*.md` or flat `*.md` in the plugin directory, with optional co-located `skills/`. `loadPluginEntry` tries JS entrypoints first, then falls back to this layout (`/plugins` add-by-path supports filesystem completion via `listPathSuggestions`).
320
320
@@ -413,7 +413,7 @@ A queued gate's display-dependent timers (auto-deny timeout, tool-budget pause c
413
413
414
414
### TUI (`src/tui/`)
415
415
416
-
OpenTUI (`@opentui/core`) is the shipping shell; the Ink/React tree has been deleted from the repo. The runner (`src/tui/runner.ts`) mounts the host via `mountRunnerHost` (`src/tui/runner-host.ts`), which mounts `mountProductHost` (`src/tui/product-host.ts`) over the shell (`src/tui/shell.ts`).
416
+
OpenTUI (`@opentui/core`) is the shipping shell; the Ink/React tree has been deleted from the repo. The runner (`src/tui/runner/`) mounts the host via `mountRunnerHost` (`src/tui/runner/host.ts`), which mounts `mountProductHost` (`src/tui/product-host.ts`) over the shell (`src/tui/shell/`).
417
417
418
418
-**Shell** (`shell.ts`) — Owns the transcript window, header, status line, prompt, overlay/palette stack, and layout/relayout (`applyLayout`, `relayout`). Transcript rows are appended via `appendStreamRow`/`appendObserveStreamRow`; focus moves between prompt and transcript via `applyFocus`/`toggleShellFocus`.
419
419
-**Product host** (`product-host.ts`) — Creates the `CliRenderer`, wires the event emitter bridge, model/command catalogs, and chrome pushes.
Copy file name to clipboardExpand all lines: docs/TUI.md
+21-11Lines changed: 21 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,9 +2,7 @@
2
2
3
3
This is the normative behavior spec for the terminal UI: Corbits Code, built on
4
4
OpenTUI (`@opentui/core`). It describes what the shell must do, not how the
5
-
OpenTUI cutover got here. The implementation lives in `src/tui/`; the
6
-
runner that mounts it is `src/tui/runner.ts` (see `docs/ARCHITECTURE.md` for
7
-
how the TUI fits the rest of the system). A reviewer should be able to hold a
5
+
OpenTUI cutover got here. The implementation lives in `src/tui/`; the runner that mounts it is `mountRunnerHost` in `src/tui/runner/host.ts` (see `docs/ARCHITECTURE.md` for how the TUI fits the rest of the system). A reviewer should be able to hold a
8
6
PR against this document; someone building a new overlay or picker should be
9
7
able to build it correctly from this document alone.
10
8
@@ -28,8 +26,7 @@ A single **geometry resolver** turns terminal size, zone visibility, and
28
26
overlay mode into region rects; every zone reads its rect from that resolver
29
27
instead of computing its own height from `process.stdout.rows`. On an 80×24
30
28
terminal with nothing optional showing, the transcript floor is 12 rows
31
-
(`IDLE_TRANSCRIPT_FLOOR`); with an inset overlay open the floor drops to a
32
-
proposed 8 rows (`OVERLAY_TRANSCRIPT_FLOOR`) so the log stays glanceable
29
+
(`IDLE_TRANSCRIPT_FLOOR`); with an inset overlay open the floor drops to 8 rows (`OVERLAY_TRANSCRIPT_FLOOR`) so the log stays glanceable
33
30
underneath a permission prompt. When space is scarce, collapse follows a
34
31
fixed order — transient banners first, then settings/plugin notices, then
35
32
task/agents strips, then progress, then the prompt itself shrinks one
@@ -337,6 +334,13 @@ a single tally. Errors clip to `OUTCOME_CHARS`/`MAX_UPDATE_CHARS` on the
337
334
"one update is one row" rule. `fleetDigest()` is the on-demand counterpart
338
335
for `/status` or an operator question mid-run.
339
336
337
+
### Subagent observe
338
+
339
+
Alt+O swaps the transcript for a running child's stream without stealing the
leaves observe and restores the parent transcript. With no live subagent
342
+
session, the chord paints a system row saying so instead of opening anything.
343
+
340
344
## How pop-ups should feel
341
345
342
346
A blocking surface (permissions, an operator question, the model/provider
@@ -370,7 +374,7 @@ below the prompt floor (`PROMPT_BASE_ROWS`). An unanswerable approval
370
374
deadlocks the session; a cramped prompt does not. An overlay must never paint
371
375
past the box it was actually assigned, and its border must always close.
372
376
373
-
Escape dismisses the open overlay and, for a permission or operator prompt,
377
+
Escape dismisses the open overlay, or leaves subagent observe when observe holds the transcript, and, for a permission or operator prompt,
374
378
that dismissal **denies** the request rather than leaving it unresolved
375
379
(`src/tui/gate-wire.ts`: both `onPermission`'s and `onOperator`'s
376
380
`onCancel` handlers resolve the pending promise — as a deny for permissions,
@@ -445,7 +449,7 @@ leading marker column and no per-row kind column; the selected row is marked
445
449
by text color only (`paintPaletteList` in `overlay-view.ts`: "the highlighted row
446
450
already stands out by sitting under the cursor, so a leading `>` and a grey
447
451
block would both be saying the same thing twice"). Rows stay name-only
448
-
(`/help`, `/model`); the focused command's registry description paints in the
452
+
(`/help`, `/model`) except plugin-supplied commands, which append their origin marker (`/name [bundled]` for bundled repo commands, `/name [<origin>]` otherwise); the focused command's registry description paints in the
449
453
shared two-line description zone under the list (`openListOverlay({ describe })`,
450
454
`paintDescriptionZone` in `overlay-view.ts`). A missing or blank `description` still
451
455
reserves the zone (rule plus two blank lines); it does not collapse. Built-ins
@@ -469,6 +473,7 @@ lives in the prompt — list chrome is in How selectors should work above.
469
473
When the prefix matches nothing, the list stays open and paints a
470
474
`(no matches)` row (CL-6699: a close-and-reopen refresh would drain a
471
475
queued gate); Enter then dismisses and leaves the prompt as typed (`/z`).
476
+
Elsewhere, an empty list paints `(no choices)` inside the body chrome with zero list rows (`OVERLAY_EMPTY_STATE`).
472
477
Every entry is backed by the live command registry
473
478
(`src/tui/command-catalog.ts:commandItemsFromRegistry`) — there is no
474
479
separate palette overlay and no shell-owned action outside the registry. The
@@ -486,14 +491,14 @@ in `SHELL_SHORTCUTS` documents that in place of a dedicated `?` row.
486
491
`/plugins` lists every discovered plugin in a flat list. The title line carries
falling back to shorter forms as the terminal narrows). Enter toggles
489
-
enablement. Alt+A adds a plugin by path. Alt+X removes a user, project, or
494
+
enablement (blocked pre-trust). Alt+C opens credentials, Alt+V verifies, Alt+T trusts, Alt+A adds a plugin by path, Alt+W picks the web provider. Alt+X removes a user, project, or
490
495
path plugin (owned user/project installs, including a path-origin plugin
491
496
whose directory sits under those roots, confirm before deleting from disk).
492
497
Every remove writes `enabled: false` rather than dropping `settings.plugins[id]`
493
498
so in-session command gating holds; disk and unique `pluginPaths` entries are
494
499
still removed so the plugin is gone after restart. Bundled Corbits plugins
495
500
cannot be uninstalled — Alt+X disables them instead and they stay listed.
496
-
Claude marketplace installs write `enabled: false` and never delete `~/.claude`.
501
+
Plugin rows carry their origin marker (`pluginOriginMarker`): `[bundled]` for repo-origin bundled plugins, `[<origin>]` otherwise. Claude marketplace installs write `enabled: false` and never delete `~/.claude`.
497
502
498
503
The idle landing paints two doors beside the mark, keys aligned so the
499
504
descriptions share a column (`LANDING_HINTS` in `src/tui/landing.ts`): `/`
@@ -558,6 +563,12 @@ pair as the default (global `defaultProvider` + that provider's `defaultModel`
558
563
disable, **Alt+R** remove — never bare letters. A remove confirm drops those
559
564
manage hints so the footer is the default Esc/Enter pair.
560
565
566
+
Each row carries its live state (`mcpRowLabel`): `name — connecting`,
The list itself never nests by provider, but connecting a new provider is not
562
573
a flat-list row either: the picker used to grow a "connect →" row per
563
574
not-yet-configured provider kind, filtered out once that kind had any
@@ -613,8 +624,7 @@ The prompt is a genuine multi-line composing area built on OpenTUI's
613
624
`TextareaRenderable` rather than its single-line `InputRenderable`, because
614
625
the single-line widget is hard-wired to one row, no wrapping, and strips
615
626
newlines (`src/tui/prompt-input.ts`). Enter sends; a literal newline
616
-
needs an explicit chord: Ctrl+Enter or Ctrl+J work on every terminal, and
617
-
Shift+Enter works too on a terminal that negotiates the kitty keyboard
627
+
needs an explicit chord: Ctrl+Enter and Ctrl+J (`linefeed`) are the portable pair — they work on every terminal — while Shift+Enter needs a terminal that negotiates the kitty keyboard
618
628
protocol (this app requests it — `useKittyKeyboard` in `product-host.ts`) and
619
629
reports the modifier back. A plain terminal sends the same bare `\r` for
620
630
Enter and Shift+Enter, so on those Shift+Enter silently does nothing — driven
0 commit comments