@lingxi-ai-cn/dsh-tui-runtime 0.1.0-rc.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +109 -0
  3. package/README.zh.md +109 -0
  4. package/lib/index.js +10552 -0
  5. package/lib/invariant.js +9 -0
  6. package/lib/types/agent-view.d.ts +40 -0
  7. package/lib/types/app.d.ts +121 -0
  8. package/lib/types/composer.d.ts +188 -0
  9. package/lib/types/detail.d.ts +24 -0
  10. package/lib/types/footer.d.ts +85 -0
  11. package/lib/types/history-search.d.ts +58 -0
  12. package/lib/types/host.d.ts +210 -0
  13. package/lib/types/index.d.ts +60 -0
  14. package/lib/types/invariant.d.ts +6 -0
  15. package/lib/types/keybindings.d.ts +165 -0
  16. package/lib/types/markdown.d.ts +9 -0
  17. package/lib/types/open-url.d.ts +9 -0
  18. package/lib/types/plugin-hub.d.ts +158 -0
  19. package/lib/types/resume.d.ts +66 -0
  20. package/lib/types/rewind.d.ts +37 -0
  21. package/lib/types/sanitize.d.ts +9 -0
  22. package/lib/types/session-export.d.ts +29 -0
  23. package/lib/types/session-lifecycle.d.ts +20 -0
  24. package/lib/types/startup-logo.d.ts +45 -0
  25. package/lib/types/store.d.ts +88 -0
  26. package/lib/types/suggestion.d.ts +99 -0
  27. package/lib/types/terminal-input.d.ts +69 -0
  28. package/lib/types/terminal-session.d.ts +129 -0
  29. package/lib/types/theme.d.ts +76 -0
  30. package/lib/types/todo-panel.d.ts +12 -0
  31. package/lib/types/tool-card.d.ts +24 -0
  32. package/lib/types/tool-group.d.ts +10 -0
  33. package/lib/types/transcript-search.d.ts +67 -0
  34. package/lib/types/transcript-view.d.ts +32 -0
  35. package/lib/types/transcript.d.ts +159 -0
  36. package/lib/types/viewport.d.ts +211 -0
  37. package/lib/types/work-panel.d.ts +28 -0
  38. package/lib/types/work.d.ts +100 -0
  39. package/package.json +104 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # `@lingxi-ai-cn/dsh-tui-runtime`
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ Native full-screen terminal frontend for one owned root DeepSeek Harness Agent and its live continuable child views. The plugin waits for Loader settlement, creates or resumes the root through `ctx.agents`, retains only its root `AgentHandle`, renders committed `session/event` rows with Ink, and disposes the root after flushing its Session. It mounts no HTTP server and imports no browser Client package.
6
+
7
+ An empty root Session opens a centered startup workspace with a nine-row, 60-cell Electric `DSH` foreground-block mark when the complete workspace fits, and a compact `DSH` mark otherwise. Extended-color terminals evaluate the source design's cosine high-voltage-arc palette at each mark cell, ANSI 16-color terminals use the theme accent, and colorless output emits only the block characters. The workspace retains a bounded composer, the resolved model hint, and the workspace footer. After the first admitted input or when a Session already has transcript rows, the header becomes a compact activity row, the transcript follows the live tail above the composer, and short output remains bottom anchored. While an Agent is running, the activity row cycles a fixed-width work indicator and shows `Esc stop`; Escape uses the existing Agent cancellation path. `/quit` and `/exit` accept no arguments, settle their command audit before requesting exit, and converge on the terminal-first idempotent shutdown path.
8
+
9
+ The transcript is a pure fold over the durable Session log. Append-origin messages remain human-visible while model-only surface replacements stay hidden; streaming assistant chunks reconcile into their completed message, tool calls and results pair by `callId` into one lifecycle node, and turn failures remain visible. Scheduler-owned `tool/execution-group` snapshots render model-ordered Parallel pools and Exclusive barriers with queued, running, successful, failed, and cancelled children plus completion progress. Provider-neutral read/search presentation remains an `Explore` semantic label inside a scheduler group rather than evidence of concurrency; older logs without scheduler snapshots retain consecutive Explore grouping. Service-owned `subagent/delegation-*` records attach task label, child identity, provider, elapsed time, terminal reason, and bounded outcome to the originating tool row without creating a second activity. The latest durable `todo/write` snapshot renders as a pinned `Tasks` surface above the composer: an active list shows at most six status-marked items, an all-complete list collapses to one progress row, an empty list renders nothing, and the next `turn/start` clears the standing list. A successful `todo_write` lifecycle with a paired snapshot is absorbed by that surface while failed calls remain in the transcript. Compact cards remain borderless summaries. `Ctrl+O` enters transcript browse mode at the latest inspectable block; Up and Down move among conversation blocks, activities and retained children, and Tasks. Enter opens one bounded detail panel, PageUp and PageDown scroll its complete body, and Escape closes the detail before returning to the composer. Tool details use provider-neutral presentation intents. Missing definitions, obsolete arguments, and throwing presenters fall back to sanitized raw content. Assistant GFM is projected to terminal text so headings, lists, code, and tables retain structure without leaking presentation markers; every untrusted string still passes through terminal-control sanitization. Live `agent/status`, approval requests, and user questions stay outside durable transcript state and are cleared during teardown.
10
+
11
+ `Ctrl+F` enters incremental search over every block in the complete folded transcript, including unmounted pages, user and assistant text, reasoning, command results, and complete tool detail. The index caches normalized lowercase text by stable node key and only replaces a cached document when that block's searchable text changes. Enter and Shift+Enter move cyclically through matching blocks, a second `Ctrl+F` closes search at the selected block, and Escape restores the exact pre-search transcript anchor. The current block and first visible match are highlighted only in process-local render state; query, index, selection, and highlights never modify Session events.
12
+
13
+ The composer sends ordinary text with `Agent.followup()` while idle and `Agent.steer()` while running. It keeps one Unicode grapheme-aware insertion point, supports left/right and word movement, logical-line Home/End, backward/forward and previous-word deletion, submitted-history traversal with unsent-draft restoration, and up to five visible wrapped rows. `Ctrl+J` inserts a newline while Enter submits the original non-blank text without trimming its outer whitespace. `Ctrl+_` or `Ctrl+Shift+-` undoes an edit and `Ctrl+Y` redoes it through at most 100 process-local text-and-cursor snapshots. Rapid single-grapheme typing and same-direction deletion coalesce; paste, multiline insertion, suggestion acceptance, and history acceptance remain separate units, while cursor movement only ends coalescing. A bracketed paste of at least eight lines or 4 KiB becomes one bounded `[Pasted text]` placeholder; its terminal-safe original remains process-local and expands only at submission. Deleting the placeholder removes its payload, while undo, redo, stash, submitted history, and Agent-view switching retain the reference without mounting the pasted lines. `Ctrl+R` searches unique prompts submitted by the current TUI process newest-first; repeated use selects an older match, Enter accepts it, and Escape restores the exact draft and cursor. `Ctrl+S` stashes a non-empty draft, restores it into an empty editor, or swaps it with another non-empty draft so restoration never overwrites text; each draft retains its own edit history. Prompt-history search, stash, paste references, and edit history remain process-local and never enter the Session log. A command-only slash query opens the shared suggestion controller over the effective `ctx.commands.list(agent)` descriptors. An `@token` at the draft or after whitespace opens the same controller over cancellable `ctx.fs.completePaths()` results rooted at the current Session workspace; loading, no-match, and truncated states remain bounded, directory acceptance keeps its trailing slash, and file acceptance adds one separating space. The bounded list marks one selection; Up/Down moves it without traversing prompt history, Shift+Tab moves backward, Tab or Enter accepts the selected command or path, and Escape closes the list while preserving the draft. One immutable action registry owns the effective Global, Composer, Suggestion, HistorySearch, TranscriptSearch, Transcript, Detail, Dialog, Footer, Work, and Approval bindings and their input priority. `/help` keeps returning the effective command descriptors and also opens a bounded, scrollable interaction panel generated from that registry; unsupported capability actions are omitted, `/help extra` remains a usage error, and Escape returns to the prior local mode. `Alt+P` opens the existing model picker without replacing the current draft. A leading slash first uses `ctx.commands.execute()`; an unmatched line remains an ordinary prompt so skill gestures retain their shared Agent path. Approvals are scoped to the exact owned Agent and expose only allow-once/reject. `ctx.userQuestions.registerProvider()` supplies a FIFO structured-question surface.
14
+
15
+ `/models` opens a bounded, scrollable selector over `ctx.llm.listProviders()` and each provider's live `listModels()` result. Providers that advertise interactive authentication appear as sign-in actions until configured. The first native flow is OpenAI Codex: choosing **Sign in with ChatGPT** runs the provider-owned browser OAuth flow, stores the refreshable credential under `$DSH_HOME/oauth/openai-codex.json`, refreshes the account model catalog, and returns to the selector. After a model declares selectable reasoning efforts, a second bounded question chooses one exact effort. The resolved model and effort are persisted through `ctx.agentDefaultModel.saveSelection()`, become the owned Agent's next-step selection, and update the idle header immediately; an already-running step keeps its captured selection.
16
+
17
+ `/resume` or Alt+R opens a bounded searchable Session picker without leaving the alternate screen. It defaults to the current workspace; Tab switches to all workspaces, where each row includes its cwd. Rows combine `ctx.sessionQuery.listSessions()`, live last-event time or persisted artifact mtime, and the latest title. When `ctx.sessionProjectionCache` is mounted, persisted titles use identity-bound checkpoint rows and bounded tail-only cold reads; otherwise one cancellable `readTitleSnapshots()` batch folds them. One unreadable Session becomes one disabled row rather than discarding its peers. Current, already-live, subagent-owned, preset-bearing, format-incompatible, non-persisted, and cwd-less Sessions show explicit disabled reasons. Selection calls `ctx.agents.resume()` with cancellable unpublished setup, where the loaded log is checked again for preset changes and unavailable routes. Only a successfully published handle replaces the active Agent, event/status stores, model selection, and Ink props; the old handle is then flushed and disposed. Failure keeps the original Agent, transcript, draft, terminal transaction, and listeners. Alt+R preserves a non-empty draft until the user stashes or explicitly discards it; an existing process-local stash survives a successful switch.
18
+
19
+ `/clear` and `/new` both open an explicit fresh-Session confirmation; `/clear` never hides durable history while continuing the same model context. Confirmation is available only while the current Agent is idle and no approval or question is waiting. It creates an empty Session through `ctx.agents.create({ source: 'clear' })`, retaining the current Session cwd and exact next-request model selection. The command's `command/run` and `command/done` pair settles on the old Session before confirmation can switch ownership. Fresh preparation uses the same single switch transaction as resume: the old Agent remains published until the new handle and Ink render commit, failure restores the confirmation over the unchanged transcript, and only a committed switch flushes and disposes the old handle. The previous Session remains persisted and selectable through `/resume`; Session-local navigation, history, search, Tasks, and draft reset, while an existing process-local stash survives.
20
+
21
+ `/rewind` settles its command audit on the parent, then enters transcript browse mode over completed append-origin human prompts. Up and Down move only among safe human boundaries and keep the selected prompt visible through its stable event anchor. Confirmation names that prompt and the durable event counts retained in the child or left only in the parent. The child is created through `ctx.agents.create({ source: 'rewind', seed, meta })`; its seed ends after the selected completed turn and any following standalone events before the next turn, while metadata records `parentSession`, `seedLength`, and cwd. Preparation reuses the logged request selection and the same rollback-covered Agent switch as resume and fresh creation. Cancellation or failure leaves the parent Agent, transcript, draft, terminal state, and later history unchanged; a committed child resets Session-local UI state, preserves any process-local stash, and leaves the parent persisted for `/resume`.
22
+
23
+ `/export` records its complete command lifecycle before opening a native directory editor, so the archive can include the command's durable audit pair. Relative paths resolve from the current Session workspace; Tab switches between the current Session and its durable descendant lineage. Export is available only while the Agent is idle and no interaction or Session switch is active. The TUI then calls the host-owned `ctx.sessionLogExporter`, displays the resolved destination and write progress, and reports the exact published path. Escape aborts preparation, lineage and attachment reads, compression, and file writes; shutdown restores the terminal before waiting for that cleanup. The host writer uses a private sibling temporary file and publishes only a complete ZIP under the first unused filename, never replacing an existing archive. This is a diagnostic raw-artifact export: committed prompts, tool arguments/results, and workspace paths remain present, while provider credential stores and transient OAuth progress are outside the archive.
24
+
25
+ Compaction lifecycle events are folded into one durable transcript marker. It shows running, successful, and failed transactions, the shadowed history count, estimated tokens, summary, and command correlation; the replacement checkpoint user message remains model-only and is never rendered as a duplicate human row. Automatic and manual compactions use the same marker, and a successful `/compact` command row is absorbed by its correlated marker. Summary text and failure detail participate in transcript search and survive reload from the Session log.
26
+
27
+ Large option sets never grow the terminal indefinitely. The question surface mounts only a viewport-sized slice, shows its range, and lets Up/Down move the window; a number or exact label remains valid input. The transcript budgets physical display rows using Unicode cell width instead of treating one semantic node as one row. Exploration groups and the pinned Tasks surface mount at most six child rows plus an omission marker and reserve that complete row count. Outside browse mode, `PageUp`, `PageDown`, and the mouse wheel move through complete semantic transcript blocks, while Home and End jump to the oldest block and live tail when the composer is empty. Composer Up and Down remain submitted-input history controls. Historical pages retain a semantic top anchor across streaming updates, Tasks replacement, and terminal resize; new events show a newer-content indicator instead of pulling the reader to the bottom. Returning to the tail resumes automatic following. Browse mode keeps the focused parent block visible across page and resize changes, consumes non-navigation input instead of editing the composer, and restores the nearest surviving target when a streaming replacement removes the focused key. Tasks remains a focus target whose complete durable checklist opens in the shared detail panel. Detail bodies wrap grapheme clusters by Unicode display width and report their visible physical-line range. An oversized text block shows a marked head in a historical page and a marked tail in the live view.
28
+
29
+ Terminal mutation has one owner. It refuses non-TTY streams before writing, enters the alternate buffer once, enables SGR mouse-wheel reports and bracketed paste, uses Ink's raw-mode lifecycle, and restores raw mode, mouse/paste modes, cursor, and screen before shutdown waits for command cancellation, Session flush, or Agent disposal. Complete SGR press, release, motion, and wheel reports are consumed before composer insertion; only wheel reports navigate the transcript. The idempotent path also removes signal listeners and settles outstanding interactions.
30
+
31
+ Raw input passes through one incremental decoder before interaction dispatch. It preserves UTF-8 across transport chunks, separates control bytes from adjacent committed text, and normalizes traditional xterm keys, Kitty CSI-u, xterm modifyOtherKeys, application-keypad sequences, and their available Shift/Ctrl/Alt/Meta/Super modifiers. The same tokenizer consumes SGR mouse reports, focus reports, bracketed paste, terminal replies, unknown function keys, and unknown or incomplete CSI without leaking their bytes into an editor. A standalone Escape settles after 35 ms; a bracketed paste remains one undo unit and is bounded to 16 MiB, with a live truncation notice. Enhanced keyboard protocols are decoded when the terminal emits them; the TUI does not infer support from `$TERM` or enable them without a negotiated terminal capability.
32
+
33
+ Before entering the alternate screen, `TerminalSession` sends bounded device, Kitty keyboard, and OSC probes through the same decoder. A reply-free terminal reaches the UI after the probe deadline with legacy input and no enhanced mouse, focus, paste, or clipboard mode; a confirmed capability enables only its own mode. Replies split across chunks and ordinary input interleaved with them retain byte order, and input buffered during probing is dispatched after Ink mounts. Y in transcript browse or detail copies at most 100,000 UTF-8 bytes through negotiated OSC 52. Under tmux the same sequence enters tmux's configured clipboard buffer path, whose `set-clipboard` policy may forward it; SSH uses OSC only after the remote process observes a reply. Unsupported, oversized, or failed writes produce a live notice and never a Session event. Color depth comes from the output stream and `NO_COLOR`; tmux and SSH are recorded only as this process's outer transport context. The capability snapshot is process-local and teardown disables exactly the modes enabled by that transaction.
34
+
35
+ Text editors use the terminal's real cursor rather than a rendered inverse-space cursor. `TuiApp` computes the insertion cell from the visible multiline layout with Unicode display width and follows Ink/Yoga's leading-cell rounding for a centered startup composer. `TerminalSession.rendererOutput` restores that cell after every full-screen Ink render while suppressing Ink's cursor-hide request. The terminal transaction enables only negotiated bracketed paste and restores paste, mouse, focus, keyboard, cursor, raw, and alternate-screen modes on every shutdown path. This gives macOS and other terminal IMEs the correct anchor for CJK preedit text; read-only panels, including Plugin Hub detail, and approval-only interactions hide the cursor, and teardown still restores it unconditionally.
36
+
37
+ Completed reasoning occupies one `Thinking · duration` transcript heading while its complete text remains available through block detail. Fenced code keeps a language heading, narrow tables become row-oriented field lists, and tool summaries use workspace-relative paths inside the Session workspace while preserving external absolute paths. Terminal, read, search, diff, web, delegation, and generic rows remain bounded at 80 columns; below 60 columns the activity row and footer omit secondary metadata rather than overlap. Before the first request, the centered startup workspace shows the resolved model and provider-default reasoning effort. While running, the activity row shows the durable request selection beside the work indicator and `Esc stop`; while idle, it shows the next-step selection. The footer projects that same captured-or-next selection together with `ctx.permissionPresets`, provider-anchored `contextPressure`, authoritative background-work counters, the Session workspace, and the mounted transcript range; context is absent until both capacity and a provider usage sample exist, and work is absent until at least one row exists. Tab enters an explicit Footer mode after suggestion handling, Left/Right or Tab/Shift+Tab moves among mounted items, Enter opens the existing model picker, the permission preset picker, the bounded Work panel, or bounded read-only details, and Escape returns to the composer. Permission changes execute the registered `/permission` command instead of mutating policy locally. At fewer than 56 columns model, permission, and Work when present remain mounted; wider layouts add context, transcript position, and workspace in priority order. `NO_COLOR` and limited-color terminals retain text and symbols; tmux and SSH inherit their outer terminal capabilities, while Windows Terminal uses the same ANSI and Unicode-width path.
38
+
39
+ Approval dialogs are scoped to the exact owned Agent and render sanitized terminal, diff, filesystem, web, or generic facts supplied by the owning request. Up/Down and PageUp/PageDown scroll bounded detail through the shared interaction registry. Approval content has no copy action because its presentation intent does not declare clipboard safety. The approval service exposes only allow-once and reject; the TUI does not advertise a persistent-policy action that the service cannot perform.
40
+
41
+ The Work footer item and bounded panel project `ctx.jobs` snapshots, durable `ctx.subagents.listDescendants()` catalog rows, live `ctx.agents` status, and owner-scoped `subagent/*` lifecycle events. Each row shows its label, owning Session when exposed, state, elapsed time, and only the stop action supported by its owner. Job-managed one-shot subagents remain job rows, continuable local children remain subagent rows, and remote runs remain summary-only because they expose no local Session transcript. X calls `ctx.jobs.kill()` or `ctx.subagents.interrupt()` and never fabricates lifecycle events. Enter opens the exact Session transcript of a live continuable local child; its header names the Agent and input target, Ctrl+G returns to the root, and a disposed child returns automatically. Child input uses `ctx.subagents.followup()` with the exact live durable direct parent, never direct inbox or Agent methods, so it remains an ordinary FIFO follow-up rather than steering. Root and child views retain independent process-local composer, submitted history, transcript anchor, focus, and search state. The queued count remains zero because no composed service exposes authoritative queue state; workflow events remain transcript facts because workflow exposes no live registry or cancellation handle. `/plugins` opens Discover with the `tui` surface and installable-only filter, anchors its bounded single-line query cursor inside the search field, and keeps the cursor hidden in Detail. Tab switches to Installed, whose managed or unmanaged status, active-bundle membership, resolved version, and health come from active-profile installed truth rather than browser or Registry state. Detail renders its bounded Versions section before README so long third-party text cannot hide version or installability facts. README content uses the terminal Markdown projection: common block boundaries are recovered when a Registry response contains no physical line breaks, while headings, lists, code, image alternative text, and link labels remain readable and presentation markers, raw HTML, link targets, and image payloads are omitted. The complete detail remains capped at 256 physical lines and marks a truncated README explicitly. A provider declares whether profile mutation is available. Catalog-only mode labels the status as external-CLI changes, prints the exact `dsh plugin --profile tui add --save-exact` or `remove` command in the footer, and prevents Enter from requesting a change. When mutation is enabled, install and offline remove first request a detached plan, then require explicit confirmation of the exact package and version, source commit, artifact size and digest prefix, DSH compatibility, signed validation level, lifecycle scripts, advisories, before/after bundle order, Host-process permissions, and required restart. Search, planning, and inactive staging remain cancellable; cancellation after staging races safely discards the staged transaction. Once maintenance handoff begins, the panel cannot cancel or close, the terminal-first shutdown path restores terminal state before the old process exits, and the helper exclusively owns generation activation, relaunch readiness, commit, and failed-boot restoration. Session switching and shutdown abort catalog work and detach observers before asynchronous cleanup. The TUI exposes no Agent installation tool and does not invoke pnpm from presentation components.
42
+
43
+ Plugin Hub Discover rows are structured cards rather than prejoined status strings. At 96 columns and above each card uses three fixed physical rows for name and right-aligned discovery badges, summary, then Registry category, package kind, primary language, package/version, and activity metadata when present; narrower terminals retain a fixed two-row name-and-summary card, and below 48 columns Star count is the only right-side metric. Unicode cell width bounds every physical row, selected and unselected cards share the same height, and no-color output retains the same status labels. Star count, repository activity, category, kind, and language remain discovery metadata and never affect installability, verification, or confirmation authority.
44
+
45
+ Plugin Hub Detail uses bounded semantic sections for Overview, Compatibility and trust, Risks and advisories, Versions, and sanitized README content. It shows Registry OS, archive, license, Star, activity, curation, category, package kind, primary language, and structured advisory facts with warning or error tones while keeping the complete text in no-color mode; category, kind, and language are labeled discovery metadata rather than security or installability state. Detail and confirmation content use semantic palette colors without ANSI dim so muted metadata and README text remain readable. Install confirmation uses Install target, Compatibility and trust, and Profile changes sections; package-manager, artifact, digest, compatibility, signed validation, lifecycle, bundle, restart, and runtime facts come from the detached plan, while catalog detail contributes advisory display context only.
46
+
47
+ Discover defaults to Stars and cycles Relevance, Stars, Updated, and Newest with `Alt+S`. `Alt+C` cycles the Registry-owned category filter and returns to All categories. The TUI sends the selected ordering and category without imposing a TUI-surface or installability filter, appends opaque continuation pages on the list tail, deduplicates plugin ids, and restarts from the first page when the Registry rejects a stale cursor. Existing cards remain visible while a later page loads; catalog status and Showing ranges expose the current ordering and continuation state without entering the Session log. A non-installable card remains visible for discovery and detail inspection, but its detail has no install action.
48
+
49
+ ## Performance diagnostics
50
+
51
+ `pnpm run test:tui:perf` runs an opt-in deterministic 100/1,000/10,000-node long-Session benchmark through the production transcript fold, search index, physical-row viewport, detail wrapping, and Ink transcript renderer. Its fixture includes Markdown and Unicode conversation blocks, tool groups, large output, Tasks, completed delegation, and a streaming append. The emitted JSON records initial and appended fold time, append projection plus render time, PageUp, search navigation and refresh, resize at 40/80/160 columns, detail projection and opening, retained heap, mounted React elements, mounted transcript blocks, and output bytes per frame. Wall-clock and heap observations describe the reported host only; the benchmark remains separate from correctness tests, and its stage targets guide optimization rather than claiming cross-platform latency guarantees. The owning [long-Session benchmark Agent Note](../../../.agents/notes/implemented/testing/2026-08-16-native-tui-long-session-benchmark.md) records the initial machine baseline and known misses.
52
+
53
+ `TuiApp` owns one append-oriented transcript projection per Agent. A verified Session-event suffix updates copy-on-write stream, tool, activity, delegation, Tasks, and compaction state while preserving unchanged node identities; a resolver change, non-prefix snapshot, Session replacement, or compaction start rebuilds from the complete durable log. The full `foldTranscript()` path runs the same event state machine from empty state and remains the differential oracle. Complete-transcript search reuses normalized text while a node reference is unchanged. Focused detail caches logical rows by node or retained-child reference and physical rows by terminal width; resize invalidates only the width-dependent result. The [incremental projection Agent Note](../../../.agents/notes/implemented/architecture/2026-08-16-native-tui-incremental-transcript-projection.md) records the ownership and invalidation rules.
54
+
55
+ `TuiTranscriptViewportIndex` keeps an Agent-local semantic-key index, immutable-node index, conservative unmeasured heights, exact identity-and-width measurements, and a Fenwick physical-row prefix index. A frame measures only enough blocks to fill its physical row budget plus the first excluded boundary; two neighboring blocks on each side are mounted in zero-height overscan. Fixed-height tool, group, Tasks, and compaction blocks reuse measurements across widths, while text measurements are width-specific. `TuiTranscriptScrollController` is the single transition authority for live-tail follow, PageUp/PageDown, mouse wheel, Home/End, focus reveal, transcript search, rewind selection, replacement fallback, and resize-stable semantic anchors. The pre-index helpers remain differential oracles, and oversized text still renders a marked head at an anchored position or a marked tail while following live output. The [physical-row virtualization Agent Note](../../../.agents/notes/implemented/architecture/2026-08-16-native-tui-physical-row-virtualization.md) records the index and correction rules.
56
+
57
+ ## Host compatibility
58
+
59
+ The post-install runtime keeps every official-package import in `src/host.ts`. If a supported Host lacks interactive provider authentication or bounded filesystem completion, `/models` lists its already configured routes and path suggestions remain empty; ordinary model requests and explicit path submission still work. Session rewind uses the same durable-event algorithm locally instead of requiring a newer `@deepseek-ai/dsh-session` named export.
60
+
61
+ ## Configuration
62
+
63
+ | Key | Default | Behavior |
64
+ |---|---:|---|
65
+ | `resume` | unset | Resume this persisted Session at startup instead of creating a new one. |
66
+ | `maxResumeOptions` | `8` | Maximum Session rows mounted at once in the picker. |
67
+ | `resumeScanConcurrency` | `4` | Maximum concurrent projection-cache or artifact-metadata reads during one picker scan. |
68
+
69
+ ### User settings
70
+
71
+ When `ctx.settings` is mounted, the package registers the `tui` namespace. The file provider persists these values in the shared user-settings document; the TUI never writes that file directly.
72
+
73
+ | Key | Default | Behavior |
74
+ |---|---:|---|
75
+ | `theme` | `dark` | Select `dark`, light-terminal-safe `light`, or `no-color` semantic terminal tokens. |
76
+ | `keybindings` | `{}` | Replace key gestures for selected interaction action ids. |
77
+
78
+ Theme changes rerender the existing Ink root and preserve the active Agent, transcript, draft, navigation, and dialogs. Negotiated 16-color output uses ANSI names, extended-color output uses the built-in palette, and either `NO_COLOR` or a colorless output stream forces the no-color result regardless of the stored preference. Selection, progress, failure, approval, and diff rows retain text or marks such as `›`, `✓`, `✕`, `+`, and `-` when color is absent.
79
+
80
+ `keybindings` maps exported `TuiInteractionActionId` values to at most eight canonical lowercase key sequences. A configured list replaces that action's key gestures while command gestures such as `/models` remain available; an empty list leaves the action without a key gesture. Named sequences include `enter`, `escape`, `tab`, `shift+tab`, `shift+enter`, arrows, paging keys, `home`, and `end`; printable ASCII keys may use `ctrl`, `meta`, `super`, `hyper`, and the supported combined modifiers. Unknown actions, unsupported or non-canonical sequences, repeated keys, and same-context conflicts fail settings validation. `app.interrupt` must retain `ctrl+c`, and dialog-local Tab/Y/N/S/D controls cannot be assigned to Dialog actions.
81
+
82
+ ```yaml
83
+ tui:
84
+ keybindings:
85
+ composer.openModels: [ctrl+p]
86
+ composer.historySearch: [ctrl+k]
87
+ composer.transcriptSearch: [ctrl+t]
88
+ ```
89
+
90
+ The resolved immutable registry drives both input dispatch and `/help`. A committed settings change replaces that registry for the next input event and rerenders the same Ink tree without replacing the Agent, transcript, draft, navigation, or dialogs. Reusing a key in different modal contexts is valid because the active context owns ordinary input; Global actions remain higher priority.
91
+
92
+ `/config` opens the native settings menu. Choose a terminal theme, choose any key-capable interaction action, or reset all TUI settings. The keybinding editor accepts comma-separated canonical sequences; enter `default` to remove only that action's override and restore its built-in binding. Confirming the reset replaces the complete `tui` settings section with defaults. Every change goes through `ctx.settings`, so the file provider owns persistence and the active theme and interaction registry update immediately without restarting the Agent.
93
+
94
+ ## Model Experience
95
+
96
+ Indirectly, through selecting which registered LLM route receives the root's next ordinary request and by delivering explicit child-view input through the continuable subagent service; adapters and the `tui-app` bundle still own model-visible content.
97
+
98
+ #### KV Cache effect
99
+
100
+ Changing provider or model selects a different provider cache domain for the next Agent step; this package otherwise registers no system-prompt section or tool schema.
101
+
102
+ ## Known Limitations and Deferred Work
103
+
104
+ - **One visible Agent view and one bounded transcript page** — root and live continuable child views switch in place; there are no simultaneous panes or Session tabs. Inactive and one-shot local children and remote runs remain summary-only. Per-view drafts are process-local rather than durable.
105
+ - **Preset-bearing Session resume is refused** — replaying a Web Session under the process-wide base composition would change its tools and prompts; preset-aware cross-surface resume is deferred.
106
+ - **Transcript inspection has no pointer focus** — the mouse wheel pages history, but pointer selection and focus are unavailable.
107
+ - **Terminal integration varies by host** — automated PTY coverage proves committed Unicode, narrow layout, bracketed-paste restoration, and cursor placement. Native macOS IME preedit and Windows ConPTY behavior remain manual release checks.
108
+ - **Terminal image paste is unavailable** — standard terminal input supplies committed text but no typed image bytes. The TUI does not enable image input merely because `ctx.attachments` exists; a future byte-bearing terminal transport must also preflight the exact model's `inputModalities` before persistence or provider I/O.
109
+ - **Ink 5 follows the repository React 18 line** — adopting a newer renderer major requires an isolated React build-face proof or a coordinated Web React migration.
package/README.zh.md ADDED
@@ -0,0 +1,109 @@
1
+ # `@lingxi-ai-cn/dsh-tui-runtime`
2
+
3
+ [English](README.md) | 中文
4
+
5
+ 面向一个自有 root DeepSeek Harness Agent 及其 live continuable child view 的原生全屏终端前端。该插件等待 Loader 结算,通过 `ctx.agents` 创建或恢复 root,只持有它的 root `AgentHandle`,用 Ink 渲染已提交的 `session/event` 行,并在 flush 其 Session 后处置 root。它不挂载 HTTP server,也不导入浏览器 Client 包。
6
+
7
+ 空的 root Session 会打开居中的启动工作区;完整工作区能容纳时显示九行、60 cell 的 Electric `DSH` 前景色块标志,否则显示紧凑的 `DSH` 标志。扩展颜色终端会在每个标志 cell 上计算源设计的余弦高压电弧调色板,ANSI 16 色终端使用主题 accent,无色输出则只发出块字符。工作区保留有界 composer、已解析的 model 提示和 workspace footer。第一次 admitted input 之后,或 Session 已经存在 transcript 行时,header 会变成紧凑 activity row,transcript 会在 composer 上方跟随 live tail,短输出保持在底部锚定。Agent running 时,activity row 会循环固定宽度的工作指示器并显示 `Esc stop`;Escape 复用已有的 Agent cancellation path。`/quit` 与 `/exit` 不接受参数,会在请求退出前完成 command audit,并汇入 terminal-first 的幂等 shutdown path。
8
+
9
+ Transcript 是对耐久 Session 日志的纯 fold。以 append 方式产生的消息保持人类可见,仅供模型使用的 surface replacement 不显示;流式 assistant chunk 会校准为其完成消息,工具调用与结果按 `callId` 配对成一个生命周期节点,turn 失败保持可见。调度器所有的 `tool/execution-group` 快照会按模型顺序渲染 Parallel pool 与 Exclusive barrier,显示 queued、running、successful、failed、cancelled child 及完成进度。提供方无关的 read/search 呈现只保留为调度组内的 `Explore` 语义标签,而不作为并发证据;没有调度快照的旧日志仍使用连续 Explore grouping。service 所有的 `subagent/delegation-*` 记录会把任务标签、child 身份、提供方、耗时、终止原因和有界结果附到发起调用的工具行,不会创建第二个活动。最近的耐久 `todo/write` 快照会在 composer 上方渲染成固定的 `Tasks` 界面:活动列表最多显示六个带状态标记的条目,全部完成的列表压缩为一行进度,空列表不渲染,下一次 `turn/start` 会清除当前列表。具有配对快照的成功 `todo_write` 生命周期会被该界面吸收,失败调用仍保留在 transcript 中。紧凑卡片继续使用无边框摘要。`Ctrl+O` 会在最近一个可检查 block 上进入 transcript 浏览模式;上下方向键可在 conversation block、活动及其保留 child 和 Tasks 间移动。Enter 打开一个有界详情面板,PageUp 与 PageDown 滚动其完整正文,Escape 先关闭详情再回到 composer。工具详情根据提供方无关的呈现意图显示。缺失定义、过期参数或抛出异常的 presenter 会回退到清理后的原始内容。Assistant GFM 会投影成终端文本,使标题、列表、代码与表格保留结构且不泄露呈现标记;每个不受信任字符串仍会经过终端控制字符清理。实时 `agent/status`、审批请求与用户问题不进入耐久 transcript 状态,并在 teardown 期间清空。
10
+
11
+ `Ctrl+F` 会在完整 folded transcript 的每个 block 上进入增量搜索,包括未挂载页面、user 与 assistant 文本、reasoning、command result 和完整工具详情。索引按稳定 node key 缓存规范化小写文本,只有 block 的可搜索文本改变时才替换对应缓存文档。Enter 与 Shift+Enter 会在匹配 block 间循环移动,再按一次 `Ctrl+F` 会在所选 block 关闭搜索,Escape 则恢复搜索前确切的 transcript anchor。当前 block 与第一个可见匹配只在进程内 render state 中高亮;query、索引、选择与高亮绝不修改 Session event。
12
+
13
+ Agent idle 时,composer 用 `Agent.followup()` 发送普通文本;running 时使用 `Agent.steer()`。它维护一个感知 Unicode grapheme 的插入点,支持左右与按词移动、逻辑行 Home/End、向前/向后及删除前一词、保留未提交草稿的已提交历史遍历,以及最多五行可见换行内容。`Ctrl+J` 插入换行,Enter 提交原始非空白文本而不裁掉首尾空白。`Ctrl+_` 或 `Ctrl+Shift+-` 通过最多 100 份进程内文本与光标快照撤销编辑,`Ctrl+Y` 则重做。快速的单 grapheme 输入和同方向删除会合并;paste、多行插入、suggestion 接受与历史接受各自形成独立 unit,光标移动只会终止合并。至少八行或 4 KiB 的 bracketed paste 会变成一条有界的 `[Pasted text]` placeholder;经过终端安全处理的原文只保留在当前进程中,并仅在提交时展开。删除 placeholder 会移除其 payload;undo、redo、stash、已提交历史与 Agent view 切换都能保留 reference,而不挂载被粘贴的行。`Ctrl+R` 按从新到旧的顺序搜索当前 TUI 进程提交的去重 prompt;重复触发会选择更早的匹配,Enter 接受匹配,Escape 恢复原 draft 和确切光标。`Ctrl+S` 会暂存非空 draft、把它恢复到空 editor,或与另一个非空 draft 交换,因此恢复不会覆盖文本;每份 draft 保留自己的编辑历史。Prompt-history 搜索、暂存、paste reference 与编辑历史仅存在于当前进程,绝不进入 Session 日志。只含命令的斜杠查询会通过共享 suggestion controller 使用有效的 `ctx.commands.list(agent)` descriptor。在 draft 开头或空白之后的 `@token` 会通过同一 controller 使用以当前 Session workspace 为根的、可取消的 `ctx.fs.completePaths()` 结果;loading、无匹配和 truncated 状态保持有界,目录接受时保留尾部斜杠,文件接受时追加一个分隔空格。有界列表标出一个选中项;Up/Down 移动选中项而不遍历 prompt history,Shift+Tab 向前移动,Tab 或 Enter 接受选中的命令或路径,Escape 关闭列表但保留 draft。一个不可变 action registry 统一拥有有效的 Global、Composer、Suggestion、HistorySearch、TranscriptSearch、Transcript、Detail、Dialog、Footer、Work 与 Approval binding 及其输入优先级。`/help` 继续返回有效 command descriptor,同时打开由该 registry 生成的有界可滚动交互面板;不支持的 capability action 会被省略,`/help extra` 仍返回 usage error,Escape 会回到之前的本地模式。`Alt+P` 可打开现有 model picker,且不会替换当前 draft。以斜杠开头的输入会先尝试 `ctx.commands.execute()`;未匹配行仍作为普通提示提交,使 skill 手势继续走共享 Agent 路径。审批限定到确切的自有 Agent,只提供单次允许/拒绝。`ctx.userQuestions.registerProvider()` 提供 FIFO 结构化问题界面。
14
+
15
+ `/models` 会在 `ctx.llm.listProviders()` 及各提供方实时 `listModels()` 结果上打开有界、可滚动的选择器。声明了交互认证的提供方在尚未配置时显示为登录操作。首个原生流程是 OpenAI Codex:选择 **使用 ChatGPT 登录** 后,界面运行提供方自有的浏览器 OAuth 流程,把可刷新的凭据存到 `$DSH_HOME/oauth/openai-codex.json`,刷新账户模型目录,再回到选择器。模型声明可选 reasoning effort 时,第二个有界问题会选择一个确切等级。解析后的模型与 effort 会经 `ctx.agentDefaultModel.saveSelection()` 持久化,成为自有 Agent 的下一步选择,并立即更新 idle header;已经运行中的 step 仍保留它捕获的选择。
16
+
17
+ `/resume` 或 Alt+R 会打开有界、可搜索的 Session picker,且不会离开 alternate screen。默认范围是当前 workspace;Tab 切换到全部 workspace,此时每行还显示自身 cwd。每行组合 `ctx.sessionQuery.listSessions()`、实时 Session 的最后 event 时间或持久化产物 mtime,以及最新标题。挂载 `ctx.sessionProjectionCache` 时,持久化标题使用绑定日志身份的 checkpoint 行和有界的只读 tail 冷读取;否则用一次可取消的 `readTitleSnapshots()` 批量 fold。单个不可读 Session 只会变成一个禁用行,不会丢弃其他结果。当前、已经 live、subagent 所有、带 preset、格式不兼容、未持久化以及没有 cwd 的 Session 都显示明确禁用原因。选择后会调用 `ctx.agents.resume()` 并执行可取消、未发布的 setup,在其中再次检查已加载日志里的 preset 变更和不可用路由。只有成功发布的 handle 才会替换活动 Agent、event/status store、模型选择与 Ink props;随后才 flush 并处置旧 handle。失败会保留原 Agent、transcript、draft、终端事务和 listener。Alt+R 会保留非空 draft,直到用户暂存或明确丢弃;已有的进程内 stash 会在成功切换后保留。
18
+
19
+ `/clear` 与 `/new` 都会打开明确的新 Session 确认;`/clear` 绝不会隐藏耐久历史后继续使用同一份模型上下文。只有当前 Agent idle 且没有 approval 或 question 等待时才能确认。确认操作通过 `ctx.agents.create({ source: 'clear' })` 创建空 Session,并保留当前 Session cwd 与下一请求的确切模型选择。命令的 `command/run` 与 `command/done` 配对会在确认切换 owner 前落到旧 Session。新 Session preparation 与 resume 共用单一切换事务:旧 Agent 会保持已发布状态,直到新 handle 与 Ink render 提交;失败会在未改变的 transcript 上恢复确认界面,只有已提交的切换才会 flush 并处置旧 handle。旧 Session 继续保持持久化状态,可通过 `/resume` 选择;Session 本地的 navigation、history、search、Tasks 与 draft 会重置,已有的进程内 stash 则保留。
20
+
21
+ `/rewind` 会先在父 Session 结算命令 audit,再进入只包含已完成 append-origin 人类提示的 transcript 浏览模式。Up 与 Down 只在安全的人类边界间移动,并通过稳定的事件锚点保持所选提示可见。确认界面会指明该提示,以及子会话保留和仅留在父会话中的耐久事件数量。子会话通过 `ctx.agents.create({ source: 'rewind', seed, meta })` 创建;其 seed 结束于所选完整轮次,以及下一次轮次开始前的尾随独立事件之后,metadata 则记录 `parentSession`、`seedLength` 与 cwd。Preparation 会复用日志记录的请求选择,以及 resume 和新建使用的同一项受回滚保护的 Agent 切换事务。取消或失败会保持父 Agent、transcript、draft、终端状态与后续历史不变;提交成功的子会话会重置 Session 本地 UI 状态,保留进程内 stash,并让父会话继续持久化以供 `/resume` 使用。
22
+
23
+ `/export` 会先记录完整命令生命周期,再打开原生目录编辑器,因此归档可以包含该命令的耐久审计事件对。相对路径从当前 Session workspace 解析;Tab 可在仅当前 Session 与其耐久 descendant lineage 间切换。只有 Agent idle、没有交互等待且未进行 Session 切换时才能导出。随后 TUI 调用 host 所有的 `ctx.sessionLogExporter`,显示已解析的目标目录与写入进度,并报告确切的最终发布路径。Escape 会中止 preparation、lineage 与 attachment 读取、压缩和文件写入;关停流程会先恢复终端,再等待清理完成。Host writer 使用私有同级临时文件,只以第一个未占用文件名发布完整 ZIP,绝不替换已有归档。这是 diagnostic raw-artifact 导出:已提交的 prompt、工具参数/结果与 workspace 路径仍会保留;提供方 credential store 与瞬时 OAuth 进度不在归档内。
24
+
25
+ Compaction 生命周期 event 会被 fold 成一个耐久 transcript marker。Marker 显示运行中、成功或失败状态、被 shadow 的历史数量、估算 token、summary 与 command 关联;replacement checkpoint 的 user message 仍然只服务于模型,不会作为重复的人类行渲染。自动与手动 compaction 使用同一个 marker,成功的 `/compact` command row 会被其关联 marker 吸收。Summary 文本与失败详情都进入 transcript search,并可从 Session log reload 后重建。
26
+
27
+ 大量选项不会无限撑高终端。问题界面只挂载与 viewport 相称的一段,显示当前范围,并用上下方向键移动窗口;数字或精确标签仍可作为输入。Transcript 使用 Unicode cell 宽度预算物理显示行,而不会把一个语义 node 当成一行。Explore group 与固定 Tasks 界面最多挂载六个 child 行及一个省略标记,并为这些完整行预留空间。在浏览模式之外,`PageUp`、`PageDown` 与鼠标滚轮在完整语义 transcript block 间翻页;composer 为空时,Home 与 End 跳到最早 block 和实时尾部。Composer 上下方向键仍控制已提交输入历史。历史页面会在流式更新、Tasks 替换与终端 resize 期间保留语义顶部锚点;新事件只显示有更新内容的提示,不会把读者拉回底部。回到尾部后会恢复自动跟随。浏览模式会在翻页和 resize 期间保持焦点 parent block 可见,消费非导航输入而不修改 composer,并在流式替换移除焦点 key 时恢复到最近的存活 target。Tasks 仍是 focus target,其完整耐久 checklist 会在共享详情面板中打开。详情正文按 Unicode 显示宽度包装 grapheme cluster,并报告当前可见的物理行范围。过大的文本 block 在历史页面显示带标记的头部,在实时视图显示带标记的尾部。
28
+
29
+ 终端改变只有一个 owner。它在写入前拒绝非 TTY stream,只进入一次 alternate buffer,启用 SGR 鼠标滚轮报告与 bracketed paste,复用 Ink 的 raw-mode 生命周期,并在关停流程等待命令取消、Session flush 或 Agent dispose 之前恢复 raw mode、mouse/paste mode、cursor 与 screen。完整的 SGR 按下、释放、移动与滚轮报告会在 composer 插入之前被消费;只有滚轮报告会导航 transcript。这条幂等路径也会移除 signal listener 并结算未完成交互。
30
+
31
+ 原始输入在交互分派前经过一个增量 decoder。它跨传输 chunk 保留 UTF-8,将控制字节与相邻的已提交文本分开,并统一传统 xterm 按键、Kitty CSI-u、xterm modifyOtherKeys、application keypad 序列及协议提供的 Shift/Ctrl/Alt/Meta/Super 修饰键。同一 tokenizer 会消费 SGR 鼠标报告、focus 报告、bracketed paste、终端回复、未知功能键及未知或不完整 CSI,不让其字节泄漏到编辑器。单独的 Escape 在 35 ms 后结算;一段 bracketed paste 保持为一个 undo unit,并以 16 MiB 为上限,截断时显示 live notice。终端发出增强键盘协议时 TUI 可以解码,但不会根据 `$TERM` 推断支持,也不会在缺少协商所得终端能力时启用协议。
32
+
33
+ 进入 alternate screen 前,`TerminalSession` 会通过同一个 decoder 在有界时间内发送设备、Kitty 键盘和 OSC 查询。没有回复的终端会在 deadline 内进入界面,并使用传统输入且不启用增强 mouse、focus、paste 或 clipboard mode;只有确认过的 capability 才会启用对应 mode。跨 chunk 拆分的回复与交错的普通输入保持字节顺序,协商期间缓存的输入会在 Ink 挂载后分派。在 transcript 浏览或详情中按 Y,会通过已协商的 OSC 52 复制最多 100,000 个 UTF-8 字节。tmux 中同一序列会进入 tmux 配置的 clipboard buffer 路径,并可由其 `set-clipboard` 策略转发;SSH 只有在远端进程观察到回复后才会使用 OSC。不支持、超过限制或写入失败只会产生 live notice,绝不写入 Session event。颜色深度来自输出 stream 与 `NO_COLOR`;tmux 与 SSH 只作为当前进程的外层传输上下文记录。Capability snapshot 仅存在于进程内,关停时只关闭该事务实际启用的 mode。
34
+
35
+ 文本编辑器使用终端真实光标,而不是渲染出来的反色空格光标。`TuiApp` 根据可见多行布局和 Unicode 显示宽度计算插入 cell,居中的启动 composer 会沿用 Ink/Yoga 对起始侧 cell 的取整方式。`TerminalSession.rendererOutput` 在每次 Ink 全屏渲染后恢复该 cell,同时拦下 Ink 隐藏光标的请求。终端事务只启用已协商的 bracketed paste,并在每条关停路径恢复 paste、mouse、focus、keyboard、cursor、raw 与 alternate-screen mode。这样 macOS 等终端 IME 的中日韩预编辑文本会锚定在正确位置;包括 Plugin Hub 详情在内的只读 panel 与只接受审批的交互会隐藏光标,teardown 仍会无条件恢复它。
36
+
37
+ 完成的 reasoning 在 transcript 中只占一条 `Thinking · duration` 标题,其完整文本仍可通过 block 详情查看。围栏代码保留语言标题,窄屏表格转成逐行字段列表,工具摘要对 Session workspace 内路径使用相对形式,同时保留外部绝对路径。Terminal、read、search、diff、web、delegation 与 generic 行在 80 列下保持有界;低于 60 列时,activity row 与 footer 会省略次要元数据而不发生重叠。第一次请求前,居中的启动工作区显示解析后的 model 与提供方默认 reasoning effort。running 时,activity row 会在工作指示器和 `Esc stop` 旁显示耐久 request 选择;idle 时显示下一步选择。Footer 会投影同一份已捕获或下一步选择,并组合 `ctx.permissionPresets`、以提供方用量为锚点的 `contextPressure`、权威 background-work counter、Session workspace 与已挂载 transcript 范围;只有容量和提供方用量样本同时存在时才显示 context,只有至少一个 work row 时才显示 Work。Suggestion 处理完毕后,Tab 进入显式 Footer mode;Left/Right 或 Tab/Shift+Tab 在已挂载项目间移动,Enter 打开既有模型选择器、permission preset 选择器、有界 Work 面板或有界只读详情,Escape 返回 composer。Permission 变更执行已注册的 `/permission` 命令,不在本地修改策略。终端少于 56 列时保留 model、permission 与存在时的 Work;更宽布局按优先级加入 context、transcript position 与 workspace。`NO_COLOR` 和有限色终端仍保留文本与符号;tmux 和 SSH 继承外层终端能力,Windows Terminal 使用同一套 ANSI 与 Unicode 宽度路径。
38
+
39
+ 审批对话框限定到 TUI 精确持有的 agent,并渲染 owning request 提供且经过清理的 terminal、diff、filesystem、web 或 generic 事实。Up/Down 与 PageUp/PageDown 通过统一 interaction registry 滚动有界详情。Approval content 没有 copy action,因为其 presentation intent 并未声明 clipboard safety。审批服务只公开 allow-once 与 reject,因此 TUI 不会展示服务无法执行的持久策略操作。
40
+
41
+ Work footer 项与有界面板会投影 `ctx.jobs` 快照、耐久 `ctx.subagents.listDescendants()` 目录行、实时 `ctx.agents` 状态,以及按 owner scope 监听的 `subagent/*` 生命周期事件。每行显示 label、在可用时显示 owning Session、state、elapsed time,并且只公开 owner 支持的 stop action。由 job 管理的 one-shot subagent 只显示为 job 行,可继续的本地 child 只显示为 subagent 行;remote run 因不提供本地 Session transcript 而只显示 summary。X 只调用 `ctx.jobs.kill()` 或 `ctx.subagents.interrupt()`,绝不伪造 lifecycle event。Enter 会打开 live continuable local child 的确切 Session transcript;header 会标明 Agent 与 input target,Ctrl+G 返回 root,child dispose 后则自动返回。Child input 会使用确切的 live durable direct parent 调用 `ctx.subagents.followup()`,绝不直接操作 inbox 或 Agent method,因此它始终是普通 FIFO follow-up,而不是 steering。Root 与 child view 分别保留进程内 composer、submitted history、transcript anchor、focus 与 search state。Queued count 保持为零,因为已组合的 service 没有公开权威 queue state;workflow event 仍是 transcript fact,因为 workflow 没有公开 live registry 或 cancellation handle。`/plugins` 打开 Discover 时会使用 `tui` surface 和 installable-only filter,并将有界的单行查询光标锚定在搜索框内;进入 Detail 后光标隐藏。Tab 切换到 Installed,其中 managed 或 unmanaged 状态、active-bundle membership、resolved version 与 health 均来自 active-profile installed truth,而不是 browser 或 Registry state。Detail 将有界的 Versions 区放在 README 之前,因此较长的第三方文本不会隐藏 version 或 installability 事实。README 使用终端 Markdown 投影:Registry 响应没有物理换行时会恢复常见 block 边界,标题、列表、代码、图片替代文字与链接标签保持可读,presentation marker、原始 HTML、链接目标和图片 payload 则不会显示。完整详情最多保留 256 个物理行,并明确标记被截断的 README。provider 会声明 profile 修改是否可用。仅目录模式会在状态中标记通过外部 CLI 修改,在 footer 中显示确切的 `dsh plugin --profile tui add --save-exact` 或 `remove` 命令,并阻止 Enter 发起修改。启用修改时,安装与离线移除都会先请求 detached plan,再要求用户显式确认确切 package 和 version、source commit、artifact size 与 digest prefix、DSH compatibility、signed validation level、lifecycle scripts、advisories、变更前后 bundle order、Host process permissions 及 required restart。Search、planning 与 inactive staging 可取消;取消与 staging 完成竞争时会安全丢弃 staged transaction。maintenance handoff 开始后,panel 不可取消或关闭;terminal-first shutdown path 会在旧 process 退出前恢复终端,而 helper 独占 generation activation、relaunch readiness、commit 与 failed-boot restoration。Session 切换与关停会中止 catalog work,并在异步清理前解除 observer。TUI 不公开 Agent installation tool,presentation component 也不调用 pnpm。
42
+
43
+ Plugin Hub Discover row 使用结构化 card,而不是预先拼接的 status string。终端达到 96 列时,每张 card 使用固定三行,依次显示 name 与右对齐 discovery badge、summary,以及存在时由 Registry 提供的 category、package kind、primary language、package/version 和 activity metadata;更窄终端保留固定双行 name 与 summary card,低于 48 列时右侧只保留 Star count。每一物理行都按 Unicode cell width 限界,选中与未选中 card 高度一致,no-color 输出仍保留相同状态标签。Star count、repository activity、category、kind 与 language 始终只是 discovery metadata,绝不影响 installability、verification 或 confirmation authority。
44
+
45
+ Plugin Hub Detail 使用有界语义 section,依次呈现 Overview、Compatibility and trust、Risks and advisories、Versions 与清理后的 README content。它会显示 Registry 的 OS、archive、license、Star、activity、curation、category、package kind、primary language 与结构化 advisory 事实,并使用 warning 或 error tone;category、kind 与 language 会明确标为 discovery metadata,而不是 security 或 installability state,no-color 模式仍保留完整文字。Detail 和 confirmation 内容使用 semantic palette color,但不使用 ANSI dim,使 muted metadata 和 README text 保持清晰可读。Install confirmation 使用 Install target、Compatibility and trust 与 Profile changes section;package-manager、artifact、digest、compatibility、signed validation、lifecycle、bundle、restart 与 runtime 事实均来自 detached plan,目录 detail 只提供 advisory 展示上下文。
46
+
47
+ Discover 默认按 Stars 排序,并使用 `Alt+S` 在 Relevance、Stars、Updated 与 Newest 之间循环。`Alt+C` 循环 Registry-owned category,并回到 All categories。TUI 会发送当前 ordering 和 category,但不强制加入 TUI-surface 或 installability filter,在目录尾部追加 opaque continuation page,按 plugin id 去重,并在 Registry 拒绝 stale cursor 时从第一页重新加载。后续 page 加载期间保留已有 card;目录状态和 Showing range 会公开当前 ordering 与 continuation state,但不会进入 Session log。不可安装 card 仍可用于 discovery 和 detail 查看,但 detail 不提供 install action。
48
+
49
+ ## 性能诊断
50
+
51
+ `pnpm run test:tui:perf` 会通过生产 transcript fold、搜索索引、物理行 viewport、详情换行和 Ink transcript renderer,运行一个可选择执行、确定性的 100/1,000/10,000-node 长 Session benchmark。Fixture 包含 Markdown 与 Unicode conversation block、工具组、大型输出、Tasks、已完成 delegation 和一次 streaming append。输出的 JSON 会记录 initial 与 appended fold 时间、append projection 加 render 时间、PageUp、搜索导航与刷新、40/80/160 列 resize、详情 projection 与打开、保留 heap、已挂载 React element、已挂载 transcript block,以及每 frame 输出字节。Wall-clock 与 heap 观察只描述所报告的主机;benchmark 与 correctness tests 分离,其阶段目标用于指导优化,不构成跨平台 latency 保证。归属的[长 Session benchmark Agent Note](../../../.agents/notes/implemented/testing/2026-08-16-native-tui-long-session-benchmark.md)记录初始机器基线和已知未达项目。
52
+
53
+ `TuiApp` 为每个 Agent 持有一个 append-oriented transcript projection。经过验证的 Session-event suffix 会以 copy-on-write 方式更新 stream、tool、activity、delegation、Tasks 和 compaction 状态,同时保留未变化 node 的 identity;resolver 变化、非 prefix snapshot、Session replacement 或 compaction start 会从完整耐久日志重建。完整 `foldTranscript()` 路径会从空状态运行同一个 event state machine,并继续作为 differential oracle。完整 transcript 搜索会在 node 引用未变化时复用规范化文本。Focused detail 按 node 或保留 child 引用缓存逻辑行,并按 terminal width 缓存物理行;resize 只使依赖宽度的结果失效。[增量 projection Agent Note](../../../.agents/notes/implemented/architecture/2026-08-16-native-tui-incremental-transcript-projection.md)记录 ownership 与 invalidation 规则。
54
+
55
+ `TuiTranscriptViewportIndex` 保存 Agent-local 语义 key index、immutable node index、未测量 block 的保守高度、按 identity 与 width 缓存的精确测量,以及一个 Fenwick 物理行 prefix index。每个 frame 只测量足以填满物理行预算的 block 和第一个被排除的边界 block;前后各两个相邻 block 会挂载到零高度 overscan。固定高度的 tool、group、Tasks 和 compaction block 会跨 width 复用测量,text 测量则按 width 隔离。`TuiTranscriptScrollController` 是 live-tail follow、PageUp/PageDown、鼠标滚轮、Home/End、focus reveal、transcript search、rewind selection、replacement fallback 和 resize-stable semantic anchor 的唯一 transition authority。index 引入前的 helper 继续作为 differential oracle;超大 text 在锚定位置仍显示带标记的 head,在跟随实时输出时仍显示带标记的 tail。[物理行 virtualization Agent Note](../../../.agents/notes/implemented/architecture/2026-08-16-native-tui-physical-row-virtualization.md)记录 index 与校正规则。
56
+
57
+ ## Host 兼容性
58
+
59
+ 后装 runtime 把所有官方 package import 集中在 `src/host.ts`。如果受支持的 Host 不提供交互式 provider 认证或有界文件系统补全,`/models` 只列出已经配置的 route,路径建议保持为空;普通模型请求和显式路径提交仍然可用。Session rewind 在本 package 内使用相同的耐久事件算法,不要求较新的 `@deepseek-ai/dsh-session` 命名导出。
60
+
61
+ ## 配置
62
+
63
+ | 键 | 默认值 | 行为 |
64
+ |---|---:|---|
65
+ | `resume` | 未设置 | 启动时恢复该持久化 Session,而不是创建新 Session。 |
66
+ | `maxResumeOptions` | `8` | Picker 一次最多挂载的 Session 行数。 |
67
+ | `resumeScanConcurrency` | `4` | 一次 picker 扫描中 projection-cache 或产物 metadata 读取的最大并发数。 |
68
+
69
+ ### 用户设置
70
+
71
+ 挂载 `ctx.settings` 时,本包会注册 `tui` namespace。文件 provider 把这些值持久化到共享用户设置文档;TUI 从不直接写该文件。
72
+
73
+ | 键 | 默认值 | 行为 |
74
+ |---|---:|---|
75
+ | `theme` | `dark` | 选择 `dark`、适合浅色终端的 `light` 或 `no-color` 语义终端 token。 |
76
+ | `keybindings` | `{}` | 替换指定交互 action id 的按键手势。 |
77
+
78
+ 主题变更会重新渲染现有 Ink root,并保留活动 Agent、transcript、draft、navigation 与 dialog。协商得到的 16 色输出使用 ANSI 颜色名,扩展色输出使用内置 palette;`NO_COLOR` 或无颜色输出 stream 会忽略存储的 preference 并强制使用无色结果。没有颜色时,selection、progress、failure、approval 与 diff 行仍保留文本或 `›`、`✓`、`✕`、`+`、`-` 等标记。
79
+
80
+ `keybindings` 把导出的 `TuiInteractionActionId` 值映射到最多八个规范小写按键序列。配置列表会替换该 action 的按键手势,而 `/models` 等命令手势仍然可用;空列表表示该 action 没有按键手势。具名序列包括 `enter`、`escape`、`tab`、`shift+tab`、`shift+enter`、方向键、翻页键、`home` 与 `end`;可打印 ASCII 按键可以使用 `ctrl`、`meta`、`super`、`hyper` 及受支持的组合修饰键。未知 action、不支持或非规范的序列、重复按键和同 context 冲突都会使 settings validation 失败。`app.interrupt` 必须保留 `ctrl+c`,Dialog action 不能占用 dialog-local 的 Tab/Y/N/S/D 控制键。
81
+
82
+ ```yaml
83
+ tui:
84
+ keybindings:
85
+ composer.openModels: [ctrl+p]
86
+ composer.historySearch: [ctrl+k]
87
+ composer.transcriptSearch: [ctrl+t]
88
+ ```
89
+
90
+ 解析后的不可变 registry 同时驱动 input dispatch 与 `/help`。已提交的 settings 变更会让下一次 input event 使用新 registry,并重新渲染同一 Ink tree,而不替换 Agent、transcript、draft、navigation 或 dialog。按键可以在不同 modal context 中复用,因为普通输入只属于当前 context;Global action 始终具有更高优先级。
91
+
92
+ `/config` 会打开原生设置菜单。用户可以选择 terminal theme、选择任意支持按键的 interaction action,或重置全部 TUI settings。快捷键编辑器接受逗号分隔的规范序列;输入 `default` 只会删除该 action 的 override 并恢复内建 binding。确认重置后,完整的 `tui` settings section 会被替换为空默认值。每次变更都通过 `ctx.settings` 提交,因此由 file provider 负责持久化,当前 theme 与 interaction registry 会立即更新而无需重启 Agent。
93
+
94
+ ## 模型体验
95
+
96
+ 间接影响:选择哪一条已注册 LLM 路由接收 root 的下一次普通请求,并通过 continuable subagent service 交付明确的 child-view input;模型可见内容仍由适配器与 `tui-app` bundle 拥有。
97
+
98
+ #### KV Cache 影响
99
+
100
+ 切换提供方或模型会让下一 Agent step 进入不同的提供方缓存域;除此以外,本包不注册 system-prompt section 或工具 schema。
101
+
102
+ ## 已知限制与暂缓事项
103
+
104
+ - **只有一个可见 Agent view,且只挂载一页有界 transcript**:root 与 live continuable child view 会原位切换;不提供同时显示的 pane 或 Session tab。Inactive 与 one-shot local child 及 remote run 仍为 summary-only。每个 view 的 draft 只存在于进程内,不会持久化。
105
+ - **拒绝恢复带 preset 的 Session**:在进程级 base 组装下回放 Web Session 会改变其工具与提示词;感知 preset 的跨界面恢复暂缓。
106
+ - **Transcript 检查没有指针焦点**:鼠标滚轮可以翻动历史,但不支持指针选择或焦点。
107
+ - **终端集成随宿主而异**:自动化 PTY 覆盖证明已提交 Unicode、窄屏布局、bracketed-paste 恢复与光标定位。原生 macOS IME preedit 和 Windows ConPTY 行为仍是手工 release check。
108
+ - **终端图片粘贴不可用**:标准终端输入只提供已提交文本,不提供带类型的图片字节。TUI 不会仅因 `ctx.attachments` 存在就启用图片输入;未来携带字节的终端 transport 还必须在持久化或 provider I/O 前预检确切模型的 `inputModalities`。
109
+ - **Ink 5 跟随仓库 React 18 版本线**:采用更新 renderer major 需要先证明隔离的 React build face,或协调 Web React 迁移。