cleanup: remove TUI package and legacy dsh entrypoints

This commit is contained in:
Turtle
2026-08-04 10:07:17 +08:00
parent 7248b5ec8f
commit 10bb9cbf4a
431 changed files with 1450 additions and 28929 deletions

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/architecture/2026-07-22-tui-interactive-extension-service.md
2026-07-22-tui-interactive-extension-service.md: e523361ceb920d2848369d7d3cf1093765d296d4
2026-07-22-tui-interactive-extension-service.zh.md: 23c46f6bc18d576912aaa648b26877873ea60eda

View File

@@ -0,0 +1,42 @@
# Agent Note: Effect-owned TUI interactive extensions
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-22-tui-interactive-extension-service.zh.md)
## Problem
Cordis plugins can register human commands through `ctx.commands`, but a command that needs terminal interaction has no supported presentation boundary. It must either remain non-interactive or capture the TUI's private pi-tui tree, focus state, renderer, and shutdown lifecycle. That coupling makes the extension depend on one front door's internals, lets independently developed overlays compete for focus, and leaves plugin unload with no reliable way to remove queued or visible UI.
## Decision
A mounted `@deepseek-ai/dsh-tui` provides `ctx.tui` after terminal startup succeeds. The service belongs to that exact terminal and agent, disappears before terminal teardown, and causes plugins that inject it to unload and reload with provider availability. Other front doors do not emulate it.
`ctx.tui.openOverlay()` is the first and only interactive extension primitive. It accepts a component factory, constrained layout options, and an optional abort signal. The factory receives a frozen host with the current viewport, semantic theme functions, display-text escaping, redraw, close, and a lifetime signal. It does not receive the pi-tui `TUI`, overlay handle, editor, transcript tree, focus controller, or terminal object.
One private overlay manager serializes built-in and plugin requests in FIFO order. The model selector and `ctx.userInteraction` question panel use the same manager, so all modal interaction has one focus owner. Closing the active overlay restores pi-tui's previous focus before the next request activates. Overlay state is process-local presentation: it is neither appended to the session log nor rebuilt during resume.
The service method runs through Cordis's traceable service proxy. It installs an effect on the calling plugin fiber before admitting the request; caller disposal therefore removes a queued request or closes an active overlay and awaits the same settled outcome. TUI shutdown first rejects admission, then disposes the service fiber so dependent plugins and their effects quiesce, settles remaining built-in work, and only then drains and stops the terminal.
Component construction, rendering, input, and invalidation run behind an exception boundary. A failure closes that request with an `error` outcome, reports a visible terminal error, and lets the queue continue. Components are trusted package code: their rendered lines may contain ANSI styling, and they must call `host.display()` before including untrusted text.
## Verification
Manager tests pin FIFO admission, cancellation, repeated close, shutdown outcomes, guarded callbacks, host capabilities, and per-file coverage. Cordis lifecycle tests pin caller ownership, provider loss and return, unloading-time rejection, and cleanup quiescence. Fake-terminal integration tests exercise plugin overlays alongside built-in questions, restored editor input, terminal remount, startup rollback, and service disappearance. Existing TUI interaction tests continue to exercise the model selector and question panel through the shared path.
## Alternatives considered
**Expose pi-tui objects directly.** This gives plugins maximum freedom but makes private focus, rendering, and teardown state a public compatibility contract. It also cannot arbitrate independently loaded overlays.
**Put interactive callbacks on command definitions.** Commands remain transport-neutral domain entries even though TUI is their only shipped consumer. Adding terminal state to `ctx.commands` would couple discovery and dispatch to one presentation implementation.
**Create a complete TUI slot and action framework at once.** Actions, editor replacement, transcript renderers, status regions, and completion providers have different composition and conflict rules. Shipping them behind one broad API would freeze those rules before a concrete consumer proves them.
**Persist open overlays in session events.** Modal presentation is not model-visible session state, and arbitrary component state is not replayable. The plugin that owns durable data records that data through its domain service and recreates presentation when appropriate.
## Consequences
Interactive plugins gain a small stable front door with deterministic focus and Cordis-owned cleanup, while the TUI keeps authority over terminal lifecycle and pi-tui internals. Built-in dialogs and extensions cannot overlap or strand focus.
The API deliberately covers modal overlays only. Human command registration remains on `ctx.commands`; actions, slots, editor replacement, event renderers, and completion providers require separate contracts when real consumers establish their ordering and ownership semantics. FIFO serialization also means one stalled overlay blocks later modal work until its owner closes, aborts, or unloads it.

View File

@@ -0,0 +1,42 @@
# Agent Note: 由 effect 持有的 TUI 交互扩展
Status: implemented
Archived: 2026-08-04
[English](2026-07-22-tui-interactive-extension-service.md) | 中文
## 问题
Cordis 插件可以通过 `ctx.commands` 注册用户命令,但需要终端交互的命令没有受支持的呈现边界。它只能保持非交互,或者捕获 TUI 私有的 pi-tui 树、焦点状态、渲染器和关闭生命周期。此类耦合会使扩展依赖某个入口的内部实现,让各自独立开发的浮层争抢焦点,并导致插件卸载时无法可靠移除排队中或已显示的 UI。
## 决策
挂载的 `@deepseek-ai/dsh-tui` 在终端成功启动后提供 `ctx.tui`。该服务只属于挂载时绑定的终端与 agent智能体在终端拆卸前消失并使注入它的插件随着提供方的可用与否卸载和重新加载。其他入口不会模拟该服务。
`ctx.tui.openOverlay()` 是第一个也是唯一一个交互扩展原语。它接受组件工厂、受限的布局选项,以及可选的中止信号。工厂收到一个冻结的 host其中包含当前视口、语义化主题函数、显示文本转义、重绘、关闭和生命周期信号。它不会收到 pi-tui `TUI`、浮层句柄、编辑器、transcript文本记录树、焦点控制器或终端对象。
一个私有浮层管理器按 FIFO 顺序串行处理内置请求和插件请求。模型选择器与 `ctx.userInteraction` 问题面板使用同一个管理器,因此所有模态交互只有一个焦点所有者。关闭活动浮层时,系统会先恢复 pi-tui 之前的焦点,再激活下一项请求。浮层状态是进程本地的呈现状态:它既不会追加到会话日志,也不会在恢复期间重建。
服务方法通过 Cordis 的可追踪服务代理运行。它在接纳请求前,向调用方插件的 fiber 注册一个 effect因此调用方执行 dispose资源释放时会移除排队中的请求或关闭活动浮层并等待同一个结果完成结算。TUI 关闭时会先拒绝新请求,再 dispose 服务 fiber让依赖插件及其 effect 完全静止,然后结算其余内置工作,最后才排空并停止终端。
组件构造、渲染、输入与失效处理均在异常边界内运行。任何失败都会以 `error` 结果关闭对应请求、在终端中报告一条可见错误,并让队列继续处理。组件属于受信任的包代码:其渲染行可以包含 ANSI 样式,但加入不受信任的文本前必须调用 `host.display()`
## 验证
管理器测试固定了 FIFO 准入、取消、重复关闭、关闭结果、受保护回调、host 能力和逐文件覆盖率。Cordis 生命周期测试固定了调用方所有权、提供方消失与恢复、卸载期间的拒绝,以及清理达到完全静止。模拟终端集成测试覆盖插件浮层与内置问题的协作、编辑器输入焦点恢复、终端重新挂载、启动回滚和服务消失。既有 TUI 交互测试继续通过共享路径覆盖模型选择器与问题面板。
## 考虑过的替代方案
**直接暴露 pi-tui 对象。** 这会赋予插件最大的自由度,却会把私有的焦点、渲染与拆卸状态变成公开兼容性契约,也无法在独立加载的浮层之间进行仲裁。
**在命令定义中加入交互回调。** 命令仍是传输无关的领域条目,尽管 TUI 是唯一已交付的消费方。向 `ctx.commands` 添加终端状态,会让发现与分派流程耦合到某一种呈现实现。
**一次性建立完整的 TUI slot 与 action 框架。** action、编辑器替换、transcript 渲染器、状态区域和补全提供方具有不同的组合规则与冲突规则。在具体消费方验证这些规则之前就将其纳入一个宽泛 API会过早固化这些规则。
**将打开的浮层持久化为会话事件。** 模态呈现并非模型可见的会话状态,任意组件状态也无法回放。拥有持久数据的插件应通过自身的领域服务记录这些数据,并在适当时重新创建呈现。
## 后果
交互式插件获得一个小而稳定的入口,具备确定性的焦点管理和由 Cordis 持有的清理机制TUI 则继续掌控终端生命周期和 pi-tui 内部实现。内置对话框与扩展无法重叠,也不会遗留失去归属的焦点。
该 API 有意只覆盖模态浮层。用户命令仍然在 `ctx.commands` 上注册action、slot、编辑器替换、事件渲染器和补全提供方需要另行设计契约等待实际消费方确定其顺序与所有权语义。FIFO 串行处理也意味着,一个停滞的浮层会阻塞后续模态工作,直至其所有者关闭、中止或卸载该浮层。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.md
2026-07-27-tui-chat-channel-module-split.md: 3cf9548d4d9d1b845bee5f7137d79a490827b082
2026-07-27-tui-chat-channel-module-split.zh.md: d5de02bd0f7090c24ccfc5dabbbadff9ff42174e

View File

@@ -0,0 +1,37 @@
# Agent Note: dsh-tui chat channel module split
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-27-tui-chat-channel-module-split.zh.md)
## Problem
`packages/ui/tui/src/index.ts` had grown past 2000 lines. Most of it was one `createTuiChat` factory: a ~1600-line closure holding roughly forty mutable variables and as many nested closures. Model selection, the ask-user-question queue, and session resume were tangled into that single scope, so a reader could not follow any one concern without holding the whole file in their head, and unrelated edits collided. A prior pass had grouped `src/` into `components/`, `session/`, `extension/`, but the entry file itself and the loose top-level input files (`autocomplete.ts`, `file-autocomplete.ts`, `skill-invocation.ts`, `xml-tool-output.ts`) were untouched.
## Decision
The chat channel's cohesive sub-machines are extracted from `createTuiChat` into `src/chat/`, each a factory that takes an explicit dependency bundle instead of closing over the entry scope:
- `chat/model-command.ts``createModelController`: the queued `/model` command, the model+reasoning-effort selector overlay, and the selected model's context-window resolution. Owns the context-window cache that the prompt and status views read.
- `chat/questions.ts``createQuestionQueue`: the user-interaction provider and the one-at-a-time FIFO ask-user-question overlays.
- `chat/resume.ts``createResumeController`: the `/resume` selector, per-candidate summary reads, the pre-handoff preflight, the terminal handoff, and the durable resume-hint command.
- `chat/helpers.ts` — zero-state helpers (`formatCwd`, `gitBranch`, surface/tool-call derivations, session-reference cards), the `HintEditor`, and banner-reveal constants.
- `chat/channel.ts``ChatChannelDeps` (the collaborator surface every sub-controller shares) and `ChannelNotice` (mixed in by the controllers that report outcomes). Each `*Deps` extends these, so the shared surface has one definition.
`src/` is reorganized so `chat/` holds every chat-channel concern: the sub-controllers above plus the former input files and the former `session/` files (`timing.ts`, `tokens.ts`) all move under `chat/`. `xml-tool-output.ts` moves under `components/`. The host/process boundary interfaces (`TuiRuntime`, `TuiResumeHost`) move to `src/runtime.ts`. After the split `src/` is `chat/`, `components/`, `extension/`, and the top-level `index.ts` / `config.ts` / `prompt.ts` / `runtime.ts` / `invariant.ts`; `index.ts` drops from 2067 to ~1530 lines and now constructs and wires the three controllers.
The convention for a controller's dependency bundle: stable value collaborators (`ctx`, `resolved`, `palette`, `overlayManager`, and each controller's own services) are destructured once; the channel callbacks (`appendNotice`, `requestRender`, `isDisposed`, `agentStatus`) stay on `deps` so a controller always calls the channel's current implementation. `channel.ts`'s JSDoc states this rule.
## Alternatives considered
- **Free functions taking a shared mutable context object.** Rejected: it would re-expose the same forty-field grab-bag the split set out to remove, just under a parameter name.
- **Extracting the status/timing animation controller too.** Deferred: `runningStatus` is read directly by the prompt caret animation in `updatePromptValues`, so a controller boundary there would leak its internal state back through getters — a leaky seam for little gain. It stays inline in `index.ts`.
## Consequences
Each concern is now readable and testable in isolation, and the shared dependency surface is defined once instead of copied into three interfaces. The cost: `index.ts` constructs the controllers and threads the callback bundle, and the model controller is a `let` forward-reference (`updatePromptValues` closes over it, but it is built later once `appendNotice`/`overlayManager` exist), carrying one justified `prefer-const` disable and a deferred first paint.
## Testing
Behavior is unchanged: all existing package tests and TUI snapshots pass without re-recording, which is the contract for this refactor.

View File

@@ -0,0 +1,37 @@
# Agent Note: dsh-tui 聊天通道模块拆分
Status: implemented
Archived: 2026-08-04
[English](2026-07-27-tui-chat-channel-module-split.md) | 中文
## Problem
`packages/ui/tui/src/index.ts` 已超过 2000 行,其中绝大部分是单个 `createTuiChat` 工厂:一个约 1600 行的闭包持有约四十个可变变量以及同等数量的嵌套闭包。模型选择、ask-user-question 队列、会话恢复都缠绕在这一个作用域里,读者无法在不把整份文件装进脑子的前提下理清任何单一关注点,互不相关的改动也会彼此冲突。此前一轮已把 `src/` 归组为 `components/``session/``extension/`,但入口文件本身以及散落在顶层的输入相关文件(`autocomplete.ts``file-autocomplete.ts``skill-invocation.ts``xml-tool-output.ts`)未动。
## Decision
聊天通道内聚的子机制从 `createTuiChat` 中抽出,迁入 `src/chat/`,每个都是接收显式依赖包的工厂,而非闭包捕获入口作用域:
- `chat/model-command.ts``createModelController`:排队执行的 `/model` 命令、模型加推理力度reasoning-effort的选择浮层以及所选模型上下文窗口的解析。持有供提示行与状态视图读取的上下文窗口缓存。
- `chat/questions.ts``createQuestionQueue`user-interaction provider 以及一次仅一个的 FIFO ask-user-question 浮层。
- `chat/resume.ts``createResumeController``/resume` 选择器、逐候选摘要读取、交接前预检、终端交接,以及持久化的恢复提示命令。
- `chat/helpers.ts` — 无状态辅助函数(`formatCwd``gitBranch`、surface/工具调用派生、会话引用卡片)、`HintEditor`,以及横幅揭示常量。
- `chat/channel.ts``ChatChannelDeps`(每个子控制器共享的协作者面)与 `ChannelNotice`(由需要上报结果的控制器混入)。各 `*Deps` 继承它们,使共享面只有一处定义。
`src/` 随之重组,使 `chat/` 汇集所有聊天通道关注点:上述子控制器,加上原来的输入文件与原 `session/` 文件(`timing.ts``tokens.ts`)都迁到 `chat/` 之下。`xml-tool-output.ts` 迁到 `components/` 之下。宿主/进程边界接口(`TuiRuntime``TuiResumeHost`)迁到 `src/runtime.ts`。拆分后 `src/``chat/``components/``extension/`,以及顶层的 `index.ts` / `config.ts` / `prompt.ts` / `runtime.ts` / `invariant.ts``index.ts` 从 2067 行降至约 1530 行,现负责构造并接线这三个控制器。
控制器依赖包的约定:稳定的取值型协作者(`ctx``resolved``palette``overlayManager`,以及各控制器自有的服务)一次性解构;通道回调(`appendNotice``requestRender``isDisposed``agentStatus`)保留在 `deps` 上,使控制器始终调用通道当前的实现。`channel.ts` 的 JSDoc 陈述了此规则。
## Alternatives considered
- **接收共享可变上下文对象的自由函数。** 否决:那会把拆分本要消除的四十字段大杂烩,仅换个参数名重新暴露出来。
- **同时抽出状态/计时动画控制器。** 推迟:`runningStatus``updatePromptValues` 中的提示光标动画直接读取,在此设控制器边界会让其内部状态经 getter 反向泄漏——收益甚微的漏隙缝。它继续内联在 `index.ts` 中。
## Consequences
每个关注点现可独立阅读与测试,共享依赖面只定义一次,而非复制进三个接口。代价:`index.ts` 负责构造这些控制器并穿针引线地传入回调包;模型控制器是 `let` 前向引用(`updatePromptValues` 闭包捕获它,但它要待 `appendNotice`/`overlayManager` 就绪后才构造),因而带一处有正当理由的 `prefer-const` 禁用与一次延后的首帧绘制。
## Testing
行为不变:现有的包测试与 TUI 快照全部无需重录即通过,这正是本次重构的契约。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/architecture/2026-07-28-consolidated-tui-presentation.md
2026-07-28-consolidated-tui-presentation.md: c772985a11bc61c2bfcbb4422cae219966246163
2026-07-28-consolidated-tui-presentation.zh.md: c96fd105bc4ff6632017b16a8b7a0e8f448449bd

View File

@@ -0,0 +1,64 @@
# Agent Note: Consolidated TUI presentation and navigation
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-28-consolidated-tui-presentation.zh.md)
## Problem
The terminal UI accumulated independent presentation rules that interacted poorly: palette roles aliased one another or inverted emphasis on light terminals; tool-card framing, output, and exit markers repeated or competed; injected context was parsed as XML and could not fold reliably; and `/resume` excluded sessions outside the current workspace even when the launcher could reach them. Each symptom appeared local, but the durable decision is one terminal-reading model: a small inspectable palette, status-led cards with recessed bodies, content-independent transcript folding, and workspace-aware navigation.
## Decision
### Palette
`paletteSpec(scheme)` is the single table of SGR codes, close codes, and purposes. `createPalette` derives every wrapper from it and `/palette` prints the same table in the running terminal. Components do not emit their own SGR sequences except for the fixed startup brand gradient. Every close resets every SGR group its open sets.
Duplicate roles are merged: `muted` into `dim`, `added` into `success`, `removed` into `error`, and the unused second accent is removed. `dim` uses `2;39` and closes with `22;39` on both schemes so recessed text stays relative to the terminal foreground rather than becoming a fixed heavy gray on light backgrounds. Colors and attributes are branded separately in TypeScript, allowing attribute/color composition while rejecting nested colors whose reset would discard the outer color.
### Tool cards
A tool card has one colored `Tool / <name>` status header over one dim body. Presenter titles, terminal commands and cwd rows, output, XML text, and fold markers use that body tone. Diff colors remain because red and green carry meaning, and signal markers remain errors.
`renderUnknownXml` receives an explicit body styler for unknown tool results. Terminal presenters parse and remove the model-facing final exit or signal marker before returning `TerminalResultView.output`; the TUI renders the structured status once as its own pill. Truncation, timeout, and sandbox lines remain in the body because the pill does not represent them.
### Injected context and folding
Injected context renders as prose in `ContextCardComponent`, not through the XML tree renderer. Exact matched outer `<system-reminder>` lines are stripped, but mismatched, unpaired, or inline tag-like text remains verbatim. Model-facing content is unchanged. Folding uses the shared `preview` helper after body assembly, so it depends only on row count, never parser success or payload characters.
`Ctrl+O` cycles collapsed, expanded, and hidden. Tool cards disappear in the hidden state together with their card-owned leading gap. Context cards participate in collapsed and expanded states but fall back to collapsed while tools are hidden, because injected instructions are not disposable tool traffic. The hidden phase additionally folds each turn's assistant steps into one message; the [hidden-mode assistant fold Agent Note](../feature/2026-07-29-tui-hidden-mode-assistant-fold.md) owns that rule.
### Cross-workspace resume
The resume picker summarizes all records and owns a current-workspace/all-workspaces scope toggled with Tab. It defaults to the current workspace, adds workspace labels only in the broader scope, and refuses records without a cwd because there is no directory to enter.
`TuiResumeHost.handoff` receives the selected `SessionId` and the cwd re-read during preflight. The CLI changes directory before disposing the current app, so an unreachable directory fails while the terminal can still recover; `execve` then inherits the selected workspace. The launcher also supplies the exit message rather than asking the TUI to reconstruct launcher syntax.
## Alternatives considered
**Keep separate notes and local fixes for each visual symptom.** Rejected: the decisions share one reading hierarchy and repeatedly superseded each other. One owner makes the final palette, card, context, and navigation rules clear without requiring readers to reconstruct chronology.
**Keep aliases and enforce presentation by convention.** Rejected: aliases imply distinctions that do not exist, and nested color resets or incomplete SGR closes fail silently. A single table plus types makes the contract inspectable and mechanically checked.
**Retain framing/output color splits inside tool cards.** Rejected: real cards mixed default foreground, cyan commands, dim cwd, unstyled XML, and dim output. The status header already provides the scan anchor; one recessed body removes noise. Diff colors are the narrow semantic exception.
**Parse or repair injected context as XML.** Rejected: reminder frames are prompting conventions around arbitrary prose containing raw ampersands, comparisons, and placeholder angle brackets. Repairing or escaping it would either guess structure or alter model-visible text.
**Hide context cards with tool cards.** Rejected: context carries injected instructions, not recoverable execution detail. The hidden phase therefore removes only tool traffic.
**Keep resume restricted to one workspace or infer cwd after boot.** Rejected: the restriction forces manual relaunch, while restored header cwd does not control filesystem and shell resolution. The target directory must cross the host seam before process replacement.
**Drop the TUI exit pill or remove model-facing exit markers.** Rejected: the pill is the scannable UI status, while the text marker is the model's status signal. The presenter consumes the marker when constructing the structured view so both audiences receive one representation.
## Consequences
The transcript reads as colored status headers over recessed detail, context presentation is stable for arbitrary prose, and one shortcut controls transcript density. The public `TuiTheme.muted` role is removed; extensions use `dim`. The palette and `renderUnknownXml` contracts are stricter, adding small compile-time friction in exchange for preventing silent style loss.
Cross-workspace resume can move every path-resolving tool to another directory. A missing or inaccessible cwd prevents handoff. The broader picker also makes concurrent access to a shared session store easier to reach; cross-process session locking remains separate work.
The terminal presenter still treats a final output line exactly matching its exit-marker grammar as structured status, so a command that intentionally prints such a line can lose it from the card body. This residual is documented by `dsh-tool-bash`.
## Testing
TUI unit and keyless terminal snapshots cover palette enumeration, light/dark roles, legal and illegal style composition, uniformly dim card bodies, semantic diff colors, marker-free terminal output with one exit pill, prose-preserving context frames, content-independent folding, the three-state Ctrl+O cycle, model filtering, and both resume scopes. CLI handoff tests cover passing the re-read cwd and rejecting directory-entry failure before teardown. Tool-bash tests pin result-marker emission, parse, and stripping as one round trip.

View File

@@ -0,0 +1,64 @@
# Agent Note: 统一的 TUI 呈现与导航
Status: implemented
Archived: 2026-08-04
[English](2026-07-28-consolidated-tui-presentation.md) | 中文
## Problem
终端 UI 逐步积累了多套彼此干扰的呈现规则:调色板角色互为别名,或在浅色终端中颠倒强调层级;工具卡片的框架、输出和退出标记重复或争夺注意力;注入上下文被当作 XML 解析,无法可靠折叠;`/resume` 即使能通过启动器访问其他工作区,也会排除不属于当前工作区的会话。每个症状看似局部,但持久决策只有一个终端阅读模型:精简且可检查的调色板、以状态为首且正文内收的卡片、与内容无关的记录折叠,以及感知工作区的导航。
## Decision
### 调色板
`paletteSpec(scheme)` 是 SGR 开始码、结束码和用途的唯一表。`createPalette` 从该表派生所有包装器,`/palette` 在运行中的终端打印同一张表。除固定的启动品牌渐变外,组件不自行发出 SGR 序列。每个结束码都重置对应开始码设置的所有 SGR 组。
重复角色被合并:`muted` 并入 `dim``added` 并入 `success``removed` 并入 `error`,未使用的第二强调色被移除。`dim` 在两种配色方案中都使用 `2;39`,并以 `22;39` 结束使内收文本相对于终端前景色变暗而不会在浅色背景上变成固定的深灰色。TypeScript 分别标记颜色和属性,允许属性与颜色组合,同时拒绝会因重置而丢失外层颜色的嵌套颜色。
### 工具卡片
工具卡片由一行带颜色的 `Tool / <name>` 状态标题和一块统一的 dim 正文组成。呈现器标题、终端命令及 cwd 行、输出、XML 文本和折叠标记都使用正文色调。差异颜色继续保留,因为红绿承载语义;信号标记也继续作为错误显示。
`renderUnknownXml` 对未知工具结果显式接收正文样式器。终端呈现器在返回 `TerminalResultView.output` 前解析并移除面向模型的末尾退出或信号标记TUI 只把结构化状态呈现一次。截断、超时和沙箱信息继续留在正文中,因为状态标记不表达这些事实。
### 注入上下文与折叠
注入上下文由 `ContextCardComponent` 按普通文本呈现,不经过 XML 树渲染器。仅移除精确配对的外层 `<system-reminder>` 行;不匹配、单边或正文内类似标签的文本都原样保留。面向模型的内容不变。折叠在正文组装完成后使用共享 `preview` 辅助函数,因此只取决于行数,不依赖解析是否成功或载荷包含哪些字符。
`Ctrl+O` 在折叠、展开和隐藏之间循环。隐藏状态会连同卡片自有的前导间距一起移除工具卡片。上下文卡片参与折叠和展开状态,但工具隐藏时回到折叠状态,因为注入指令不是可丢弃的工具流量。隐藏阶段还会把每个轮次的 assistant 步骤折叠为一条消息;该规则由[隐藏模式 assistant 折叠 Agent Note](../feature/2026-07-29-tui-hidden-mode-assistant-fold.md)负责。
### 跨工作区恢复
恢复选择器汇总所有记录,并维护可用 Tab 切换的当前工作区/所有工作区范围。默认范围是当前工作区;只有更宽范围才显示工作区标签。没有 cwd 的记录会被拒绝,因为没有可进入的目录。
`TuiResumeHost.handoff` 接收选中的 `SessionId` 和预检时重新读取的 cwd。CLI 在释放当前应用前切换目录,因此无法访问的目录会在终端仍可恢复时失败;随后 `execve` 继承所选工作区。退出提示也由启动器提供,而不是让 TUI 反推启动器命令语法。
## Alternatives considered
**为每个视觉症状保留独立 Agent Note 和局部修复。** 否决:这些决策共享同一阅读层级,而且彼此多次取代。由一份记录统一拥有最终的调色板、卡片、上下文和导航规则,读者无需重建变更顺序。
**保留别名,并依靠约定执行呈现规则。** 否决:别名暗示并不存在的差异;嵌套颜色重置或不完整的 SGR 结束会静默失败。单一表格加类型约束使契约可检查且可机械验证。
**保留工具卡片内部的框架/输出颜色分层。** 否决真实卡片会混用默认前景、青色命令、dim cwd、无样式 XML 和 dim 输出。状态标题已经提供扫描锚点;统一内收正文能消除噪声。差异颜色是狭窄的语义例外。
**把注入上下文继续解析或修复成 XML。** 否决:提醒框架只是包裹任意普通文本的提示约定,其中会包含原始 `&`、比较表达式和尖括号占位符。修复或转义要么猜测结构,要么改变模型可见文本。
**随工具卡片一起隐藏上下文卡片。** 否决:上下文承载注入指令,不是可恢复的执行细节。因此隐藏阶段只移除工具流量。
**把恢复限制在一个工作区,或在启动后推断 cwd。** 否决:前者迫使用户手动重启;后者恢复的会话头 cwd 并不控制文件系统和 shell 的路径解析。目标目录必须在进程替换前跨过主机接口。
**移除 TUI 退出状态标记,或移除面向模型的退出标记。** 否决:前者是便于扫描的 UI 状态,后者是模型的状态信号。呈现器在构造结构化视图时消费文本标记,使两类受众各看到一种表示。
## Consequences
记录现在表现为带颜色的状态标题和内收细节;上下文对任意普通文本都稳定呈现;一个快捷键控制记录密度。公共 `TuiTheme.muted` 角色被移除,扩展改用 `dim`。调色板和 `renderUnknownXml` 契约更严格,以少量编译期摩擦换取对静默样式丢失的防护。
跨工作区恢复会把所有依赖路径解析的工具移动到另一个目录。cwd 缺失或不可访问时不能交接。更宽的选择范围也使共享会话存储的并发访问更容易触达;跨进程会话锁仍是独立后续工作。
终端呈现器仍会把与退出标记语法完全一致的最后一行输出视为结构化状态,因此命令有意打印这种行时,卡片正文可能丢失该行。`dsh-tool-bash` 已记录这一残余限制。
## Testing
TUI 单元测试和无密钥终端快照覆盖调色板枚举、浅色/深色角色、合法与非法样式组合、统一 dim 卡片正文、保留语义的差异颜色、仅有一个退出状态且正文无标记、普通文本上下文框架、与内容无关的折叠、Ctrl+O 三态循环、模型过滤和两种恢复范围。CLI 交接测试覆盖传递重新读取的 cwd并在释放前拒绝目录切换失败。tool-bash 测试把结果标记的生成、解析和移除固定为同一轮往返契约。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/bug-fix/2026-07-23-tui-generic-card-markdown.md
2026-07-23-tui-generic-card-markdown.md: 5c092c4d5ffbc566074f9b280d3fbc207d645ec7
2026-07-23-tui-generic-card-markdown.zh.md: 93e6388c64e69d9028822ce65f9eda368ed26e7d

View File

@@ -0,0 +1,30 @@
# Agent Note: TUI generic-card Markdown rendering
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-23-tui-generic-card-markdown.zh.md)
## Problem
Tool presenters can put Markdown in generic-card content, including fenced `console` output used for background-task acknowledgements and execution errors. Rendering that content as plain text exposes the fence markers and diverges from assistant and user content in the same transcript.
## Decision
The TUI renders generic-card result content with its shared Markdown theme before applying the card's head-and-tail line limit. Terminal and diff cards retain their specialized plain-text renderers, and generic-card raw input remains literal because it represents tool arguments rather than presenter-authored prose.
The shared theme hides fence syntax, retains the optional language label, and colors the fenced body as code. Rendering precedes truncation so collapsed-card line counts and boundaries describe the visible terminal rows rather than Markdown source rows.
## Alternatives considered
**Strip fences in the Bash presenter.** This would fix one producer while leaving generic-card Markdown from other tools unrendered and would make the presenter depend on TUI behavior.
**Render every tool card as Markdown.** Terminal output and diffs have dedicated formatting and may contain Markdown punctuation that must remain literal.
**Apply the collapsed-card limit before Markdown rendering.** Source-line truncation can split a fenced block and makes the visible line count differ from the count used by the card.
## Consequences
Generic tool cards use the same Markdown vocabulary and sanitization path as conversation content. Markdown punctuation in a generic card is interpreted rather than always displayed literally; tools that require literal terminal output use the terminal card intent.
The focused TUI test pins hidden fences, retained language labels, and body text. The keyless terminal-state snapshot covers the behavior through an assembled TUI transcript.

View File

@@ -0,0 +1,30 @@
# Agent Note: TUI 通用卡片的 Markdown 渲染
Status: implemented
Archived: 2026-08-04
[English](2026-07-23-tui-generic-card-markdown.md) | 中文
## Problem
工具展示器可以在通用卡片generic card内容中写入 Markdown其中包括用于后台任务确认和执行错误的围栏 `console` 输出。把这些内容按纯文本渲染会暴露围栏标记,并与同一 transcript文本记录中的助手内容和用户内容显示不一致。
## Decision
TUI 先用共享的 Markdown 主题渲染通用卡片的结果内容,再应用卡片的头尾行数限制。终端卡片和 diff 卡片保留各自专门的纯文本渲染器;通用卡片的原始输入仍按字面显示,因为它代表的是工具参数,而非展示器撰写的行文。
共享主题隐藏围栏语法,保留可选的语言标签,并将围栏正文按代码配色。渲染先于截断执行,因此收起状态卡片的行数和边界描述的是可见的终端行,而非 Markdown 源文本行。
## Alternatives considered
**在 Bash 展示器中剥除围栏。**这只修复一个生产方,其他工具产生的通用卡片 Markdown 仍不会被渲染,还会让展示器依赖 TUI 的行为。
**把每种工具卡片都按 Markdown 渲染。**终端输出和 diff 有专门的格式,且可能包含必须保持字面显示的 Markdown 标点。
**在 Markdown 渲染之前应用收起状态卡片的行数限制。**按源文本行截断可能从中间截断围栏块,还会让可见行数与卡片使用的行数不一致。
## Consequences
通用工具卡片与对话内容使用同一套 Markdown 词汇和净化路径。通用卡片中的 Markdown 标点会被解释,而不再总是按字面显示;需要字面终端输出的工具使用终端卡片这一渲染意图。
聚焦的 TUI 测试固定了隐藏的围栏、保留的语言标签和正文文本。无密钥的终端状态快照通过组装后的 TUI transcript 覆盖该行为。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.md
2026-07-24-tui-turn-end-stop-reason-notices.md: 62784ed7cd085ada49b2e0e3d9d809c33a2af917
2026-07-24-tui-turn-end-stop-reason-notices.zh.md: 162ba4ed44c98bfe00153b7acf69424784f92f5e

View File

@@ -0,0 +1,28 @@
# Agent Note: TUI presents a reason for every turn-end kind
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-24-tui-turn-end-stop-reason-notices.zh.md)
## Problem
The TUI rendered transcript notices for `error`, `aborted`, `max-tokens`, `rejected`, and `interrupted` turn ends, but a `disposed` turn end and any plugin-added `TurnEndReasonMap` kind rendered nothing. When such a turn ended — live or replayed from a persisted log — the agent stopped working with no visible reason, breaking the product expectation that every stop is explained to the user.
## Decision
The `turn/end` case in `packages/ui/tui/src/index.ts` switches on the reason's discriminant and covers every kind: `completed` stays silent because the settled assistant message and its `Completed` timing header already present that outcome; `disposed` appends `Turn stopped: the agent was disposed.`; and the merge-extensible default appends `Turn ended: <kind>.` so an unknown plugin-added outcome still names why the agent stopped. All other kinds keep their existing notices.
## Alternatives considered
**A notice for `completed` turns too.** Rejected as noise: every ordinary response would gain a redundant line, and the assistant message plus its frozen timing header already mark the completion.
**Suppressing the `disposed` turn-end notice live because `agent/disposed` also appends `Agent "<id>" was disposed.`** Rejected: the two notices state different facts (this turn was cut short vs. the agent is gone), and the turn-end notice is the only one that survives replay of a persisted log, where the live `agent/disposed` emission does not recur.
**Keeping the default branch silent (the prior behavior).** Rejected: a merge-extensible kind unknown to the TUI is exactly the case where the user has no other way to learn why the agent stopped.
## Consequences
- A turn never ends without a user-visible reason in the TUI: every non-`completed` `turn/end` kind appends a transcript notice, including unknown plugin-added kinds by name.
- Live disposal during a running turn shows two notices (the turn-end notice plus `agent/disposed`); a replayed log shows the turn-end notice alone.
- The `errors-and-help` snapshot pins the `disposed` and unknown-kind notices alongside the existing failure and interruption notices.

View File

@@ -0,0 +1,28 @@
# Agent Note: TUI 为每种轮次结束 kind 呈现原因
Status: implemented
Archived: 2026-08-04
[English](2026-07-24-tui-turn-end-stop-reason-notices.md) | 中文
## 问题
TUI 会为 `error``aborted``max-tokens``rejected``interrupted` 这几种轮次结束渲染 transcript文本记录通知`disposed` 轮次结束和任何插件新增的 `TurnEndReasonMap` kind 不渲染任何内容。此类轮次结束时无论实时发生还是从持久化日志回放agent智能体都会在没有任何可见原因的情况下停止工作违背了「每次停止都要向用户解释」的产品预期。
## 决策
`packages/ui/tui/src/index.ts` 中的 `turn/end` 分支按 reason 的判别字段做 switch覆盖每一种 kind`completed` 保持沉默,因为已定稿的助手消息及其 `Completed` 计时头部已经呈现了这一结果;`disposed` 追加 `Turn stopped: the agent was disposed.`merge 扩展的 default 分支追加 `Turn ended: <kind>.`,让未知的插件新增结果仍能点明 agent 停止的原因。其余各 kind 保留现有通知。
## 备选方案
**为 `completed` 轮次也加一条通知。** 否决,属于噪音:每次普通响应都会平添一行冗余内容,而助手消息加上已冻结的计时头部本就标示了完成。
**因为 `agent/disposed` 也会追加 `Agent "<id>" was disposed.`,就在实时场景下抑制 `disposed` 轮次结束通知。** 否决:两条通知陈述的是不同事实(前者说明这一轮被中途截断,后者说明 agent 已不复存在),而且只有轮次结束通知在回放持久化日志时得以保留,实时发出的 `agent/disposed` 不会在回放中重现。
**让 default 分支保持沉默(沿用先前行为)。** 否决TUI 不认识的 merge 扩展 kind恰恰是用户没有其他途径得知 agent 为何停止的情形。
## 后果
- 在 TUI 中,轮次结束永远不会缺少用户可见的原因:每种非 `completed``turn/end` kind 都会追加一条 transcript 通知,未知的插件新增 kind 也会按名称列明。
- 轮次运行期间实时 dispose资源释放会显示两条通知轮次结束通知加上 `agent/disposed`);回放日志则只显示轮次结束通知。
- `errors-and-help` 快照把 `disposed` 通知和未知 kind 通知连同现有的失败与中断通知一并固定下来。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/bug-fix/2026-07-27-tool-card-single-row-fields-inline.md
2026-07-27-tool-card-single-row-fields-inline.md: 23989e08ac7e32097212bf951461ca1d4213f729
2026-07-27-tool-card-single-row-fields-inline.zh.md: 8358f7b8dff67c0d3f459d08bf37e3a47f276107

View File

@@ -0,0 +1,24 @@
# Agent Note: Tool-card single-row fields render inline
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-27-tool-card-single-row-fields-inline.zh.md)
## Problem
A tool card's title, description, cwd, and pending `$ <command>` echo are each one logical row. The bash tool sets the card title (and description) directly from the model's command and description, which for a multi-line bash script contain real newlines. These fields were escaped with `displayText`, which deliberately preserves `\n` as structural layout. A multi-line title therefore broke onto extra terminal rows that the card's line accounting did not reserve, so the title's later lines overwrote the description, the output, or the editor's steering hint — the card rendered as garbled, overlapping text. Removing the gutter bar (see the [copyable-transcript note](../simplification/2026-07-27-copyable-transcript-no-gutter-bar.md)) made the collision visible because those rows no longer sat behind a per-line prefix.
## Decision
Single-row card fields use `displayInlineText` (which escapes `\n` to the literal `\x0a`) instead of `displayText`: the card title, the terminal-card `description` and `cwd` meta rows, and the pending `$ <command>` echo. Each stays on exactly one row, so a multi-line command can no longer break rows and collide with adjacent lines. Genuinely multi-line fields — captured command output and the `contentText` result body — keep `displayText` plus `split('\n')`, because those legitimately occupy multiple rows.
## Alternatives considered
- **Strip newlines from the presenter output** (in the bash tool) — hides the model's real command shape from any consumer of the view, and pushes a UI concern into the tool. The escape belongs at the single-row render site.
- **Let the title wrap to multiple rows deliberately** — a card title is a one-line identity; a wrapped multi-line title still collides with the following meta rows unless the whole card is re-laid-out, and it bloats the transcript.
## Consequences
- Multi-line bash commands render as a single inline title (`S=/tmp\x0aecho …`); the description, output, and exit rows below stay intact. Verified live in tmux for both the pending (`◌`) and completed (`✓`) states.
- A `multilineTerminal` tool-card case in `tui.spec.ts` asserts the inline-escaped form appears for a newline-bearing title and description.

View File

@@ -0,0 +1,24 @@
# Agent Note: 工具卡片的单行字段以内联方式渲染
Status: implemented
Archived: 2026-08-04
[English](2026-07-27-tool-card-single-row-fields-inline.md) | 中文
## Problem
工具卡片的标题、描述、cwd 以及待执行的 `$ <command>` 回显各自都是一个逻辑行。bash 工具直接用模型给出的命令与描述来设置卡片标题(和描述),而对于多行 bash 脚本,这些内容包含真实换行。这些字段此前用 `displayText` 转义,而 `displayText` 会刻意保留 `\n` 作为结构性布局。于是多行标题会换到卡片行数核算未预留的额外终端行上,标题后续的行便覆盖了描述、输出,或编辑器的 steering 提示——卡片渲染成互相重叠的乱码文本。移除 gutter bar见[可复制 transcript 的 note](../simplification/2026-07-27-copyable-transcript-no-gutter-bar.md))后,这些行不再位于逐行前缀之后,因而暴露了这一冲突。
## Decision
单行卡片字段改用 `displayInlineText`(将 `\n` 转义为字面量 `\x0a`)而非 `displayText`包括卡片标题、terminal 卡片的 `description``cwd` 元数据行,以及待执行的 `$ <command>` 回显。每个字段都严格保持在一行内,因此多行命令不再会换行并与相邻行冲突。真正多行的字段——捕获的命令输出与 `contentText` 结果正文——仍保留 `displayText``split('\n')`,因为它们本就应占据多行。
## Alternatives considered
- **在 presenter 输出中剥除换行**(在 bash 工具里)—— 会对该视图的所有消费方隐藏模型真实的命令形态,并把 UI 关注点塞进工具。转义应发生在单行渲染处。
- **让标题刻意换到多行** —— 卡片标题是一行式身份标识;除非重排整个卡片,多行标题仍会与其后的元数据行冲突,还会让 transcript 膨胀。
## Consequences
- 多行 bash 命令渲染为单行内联标题(`S=/tmp\x0aecho …`);其下的描述、输出与退出码行保持完整。已在 tmux 中对待执行(`◌`)与已完成(`✓`)两种状态实测验证。
- `tui.spec.ts` 中新增了一个 `multilineTerminal` 工具卡片用例,断言对含换行的标题与描述会出现内联转义后的形式。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/bug-fix/2026-07-27-tui-step-timing-trails-tool-cards.md
2026-07-27-tui-step-timing-trails-tool-cards.md: 00256fc0f1c0e9469d0dd4e8567fdbc4a48dcaff
2026-07-27-tui-step-timing-trails-tool-cards.zh.md: a566b521923fecb47c721617ac1b4a5fffce7a0f

View File

@@ -0,0 +1,29 @@
# Agent Note: TUI step timing trails the step's last message
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-27-tui-step-timing-trails-tool-cards.zh.md)
## Problem
The per-step timing summary (`Model wait … · Completed …`) was a child of the assistant message component, so it rendered directly under the assistant text. When a step drove tool calls, the tool cards were appended to the chat *after* the assistant message, leaving the timing line stranded above them — one message before the step's actual last output. The summary is meant to close a step, so on any tool-calling step it appeared in the wrong place.
## Decision
The timing summary is its own `StepTimingComponent`, no longer a child of `AssistantMessageComponent`. `StreamingAssistantComponent` owns one and exposes it as `timing`, but the renderer attaches it to the chat as a sibling that follows the assistant message. Whenever a `tool/call` or `tool/result` of the open step appends a card, `trailStreamingTiming()` moves the footer back to the tail of the chat, so it always trails the step's last message. On `step/end` the footer is completed in place — already at the tail — and stays pinned while the next step's output follows. `removeStreaming` and the reasoning-toggle rebuild detach and reattach the footer together with its streaming component.
Event ordering makes this exact: within a step the loop appends `tool/call` and `tool/result` before `step/end`, so the footer is repositioned while `streaming` is still set, then frozen when the step ends.
## Alternatives considered
**Keep the timing inside the assistant message and reorder tool cards above it.** Rejected: tool cards belong after the assistant text that requested them; moving them above the assistant message to sit under the timing would misrepresent the transcript order.
**Recompute a single trailing footer for the whole turn instead of one per step.** Rejected: a multi-step turn shows each step's own completed timing, and collapsing them would drop the per-step buckets the existing timing tests pin.
**Reposition the footer from a `step/end`-only handler.** Rejected: tool cards render before `step/end`, so a footer moved only at step end would already be trailing but would not track a mid-step re-render, and the running (pre-completion) footer would still sit above the tool cards during streaming.
## Consequences
- On a tool-calling step the timing summary renders below the tool cards, both while the turn runs and after it completes; the package snapshots (`untrusted-controls`, `cordis-tools-pending`, `advanced-cards-*`, `code-mode-pending`, `dynamic-workflow-pending`, `surface-before-compaction`) and the example transcripts (`todo-plan`, `bash-terminal-card`, `code-mode`, `parallel-file-reads`, `dynamic-workflow`, `cordis-dynamic-toolchain`, `code-mode-dispatch-spill`) pin the new order.
- A unit test asserts the completed timing appears after a step's tool output; it fails on the pre-fix ordering.

View File

@@ -0,0 +1,29 @@
# Agent Note: TUI 步骤计时跟在该步骤最后一条消息之后
Status: implemented
Archived: 2026-08-04
[English](2026-07-27-tui-step-timing-trails-tool-cards.md) | 中文
## 问题
每步的计时摘要(`Model wait … · Completed …`)原本是助手消息组件的子节点,因此直接渲染在助手文本下方。当某一步触发 tool call 时tool card工具卡片会在助手消息*之后*追加到聊天区,使计时行被搁在它们上方——落在该步骤真正的最后一条输出之前一条消息处。该摘要本意是收束一个步骤,因此在任何含 tool call 的步骤上都出现在了错误的位置。
## 决策
计时摘要现在是独立的 `StepTimingComponent`,不再是 `AssistantMessageComponent` 的子节点。`StreamingAssistantComponent` 持有一个并以 `timing` 暴露它,但渲染器把它作为紧随助手消息之后的同级节点挂到聊天区。每当当前打开步骤的 `tool/call``tool/result` 追加一张卡片,`trailStreamingTiming()` 就把该页脚移回聊天区末尾,使它始终跟在该步骤的最后一条消息之后。在 `step/end` 时该页脚就地定稿——此时已在末尾——并在后续步骤的输出接续时保持钉住。`removeStreaming` 与推理开关重建会把该页脚连同其流式组件一起摘除并重新挂上。
事件顺序让这一点精确成立:在一个步骤内,循环会先追加 `tool/call``tool/result`,再追加 `step/end`,因此页脚是在 `streaming` 仍被设置时重新定位的,随后在步骤结束时冻结。
## 备选方案
**把计时保留在助手消息内部,改为把 tool card 排到它上方。** 否决tool card 应位于请求它们的助手文本之后;把它们移到助手消息上方以贴在计时下方,会歪曲 transcript文本记录的顺序。
**为整个轮次重算一个末尾页脚,而非每步一个。** 否决:多步轮次会显示各步自己的完成计时,合并它们会丢掉现有计时测试所固定的每步分桶。
**只在 `step/end` 处理器里重新定位页脚。** 否决tool card 在 `step/end` 之前渲染,因此仅在步骤结束时移动的页脚虽已处于末尾,却无法跟踪步骤中途的重新渲染,而且流式过程中运行态(完成前)的页脚仍会落在 tool card 上方。
## 后果
- 在含 tool call 的步骤上,计时摘要渲染在 tool card 下方,轮次运行期间与完成之后皆如此;相关包快照(`untrusted-controls``cordis-tools-pending``advanced-cards-*``code-mode-pending``dynamic-workflow-pending``surface-before-compaction`)与示例 transcript`todo-plan``bash-terminal-card``code-mode``parallel-file-reads``dynamic-workflow``cordis-dynamic-toolchain``code-mode-dispatch-spill`)固定了新顺序。
- 一个单元测试断言完成计时出现在某步骤的工具输出之后;在修复前的顺序下它会失败。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/bug-fix/2026-07-30-tui-adapter-registration-race.md
2026-07-30-tui-adapter-registration-race.md: 051f5e6af5d7fd6c0096c84af064d065dd6f8983
2026-07-30-tui-adapter-registration-race.zh.md: 1706acb1d23bab987333a7ac0b5e2695226c1af5

View File

@@ -0,0 +1,30 @@
# Agent Note: TUI model-context resolution defers on the adapter-registration race
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-30-tui-adapter-registration-race.zh.md)
## Problem
Cordis activates plugins by service availability, not configuration order, so the TUI (whose `inject` requires only the `llm` service) can mount before a configured adapter plugin such as `dsh-llm-pi-ai` finishes registering its provider routes. The TUI's model controller resolves the selected model's context window immediately on mount; when the agent's route pointed at a not-yet-registered provider, `resolveModelInfo` rejected with `NO_ADAPTER` and every fresh session printed `Could not resolve model context: no adapter registered for provider "…"` — a spurious error for a fully working configuration (the adapter registered milliseconds later, and chatting worked).
## Decision
The TUI model controller treats a `NO_ADAPTER` rejection of its context-window resolution as a transient state rather than an error: it parks the resolution silently and re-resolves on the next `llm/adapters-updated` commit — the payload-free registry notification `LlmService` already fires at every route commit point. A commit that still lacks the route parks the wait again, so unrelated topology changes stay silent. Any target change re-enters the resolution and clears the pending wait, so the deferred state can never go stale against the current selection; every other resolution error still prints the notice.
## Alternatives considered
**Have the TUI wait for boot to settle before resolving.** The TUI has no Loader dependency (tests and embedders run without one) and "settled" is not observable from inside a plugin; adding a Loader coupling for one cosmetic resolution inverts the dependency direction.
**Poll or retry with a timer.** A timer guesses at activation latency, still mis-prints on a slow adapter, and adds a tunable with no owner. The registry already announces every commit through `llm/adapters-updated`; subscribing is precise and free.
**Order the config so adapters load first.** Row order carries no load semantics in the Loader (activation is service-driven by design), so this cannot be expressed in configuration.
**Suppress NO_ADAPTER errors entirely.** A permanently missing adapter (typo in the provider name) would then never surface in the context-window path. Deferring keeps the signal: a wrong provider name still shows `model unset`-like behavior in the selector and fails loudly at dispatch, while the startup race resolves itself.
**Resolve the context window per submitted message instead of at mount.** The send path already resolves per step (`prepareCall()`), and the indicator is displayed continuously, not only when sending; per-submit display resolution would leave the indicator blank until the first message and re-run adapter I/O for a value that only changes on route changes.
## Consequences
A genuinely misconfigured provider no longer prints the context-resolution error at startup — it surfaces at first dispatch instead, which is where the failure is actionable. The controller subscribes to every `llm/adapters-updated` commit but acts only while a wait is parked; the listener's disposer is released by the channel's `detachListeners()` through the controller's `detach()`, symmetric with the sibling channel listeners. Covered by three TUI tests: the deferred resolution stays silent through an unrelated commit and completes when the route's commit arrives, a target change drops the stale wait, and after channel detach a registry commit no longer re-enters resolution.

View File

@@ -0,0 +1,30 @@
# Agent Note: TUI 模型上下文解析在适配器注册竞争时延后重试
Status: implemented
Archived: 2026-08-04
[English](2026-07-30-tui-adapter-registration-race.md) | 中文
## Problem
Cordis 按服务可用性而非配置顺序激活插件,因此 TUI`inject` 只要求 `llm` 服务)可能在 `dsh-llm-pi-ai` 这类已配置的适配器插件完成提供方路由注册之前就挂载。TUI 的模型控制器在挂载时立即解析所选模型的上下文窗口;当 agent 的路由指向尚未注册的提供方时,`resolveModelInfo``NO_ADAPTER` 拒绝,于是每个新会话都会打印 `Could not resolve model context: no adapter registered for provider "…"` —— 对一份完全正常的配置报出的虚假错误(适配器几毫秒后就完成注册,对话也一切正常)。
## Decision
TUI 模型控制器把上下文窗口解析中的 `NO_ADAPTER` 拒绝视为瞬态状态而非错误:静默搁置这次解析,并在下一次 `llm/adapters-updated` 提交时重新解析——这是 `LlmService` 本就在每个路由提交点发出的无载荷注册表通知。若某次提交仍缺少该路由,等待会被再次搁置,因此无关的拓扑变化保持沉默。任何目标变更都会重新进入解析并清除挂起的等待,因此延后状态绝不会相对当前选择变陈旧;其他所有解析错误仍照常打印通知。
## Alternatives considered
**让 TUI 等启动结算后再解析。** TUI 不依赖 Loader测试和嵌入方在没有 Loader 的环境下运行),而且"已结算"在插件内部不可观测;为一次外观性的解析引入 Loader 耦合会颠倒依赖方向。
**用定时器轮询或重试。** 定时器只能猜测激活延迟,遇到慢适配器仍会误报,还会引入一个没有归属者的可调参数。注册表本就通过 `llm/adapters-updated` 公告每次提交;订阅它既精确又零成本。
**调整配置顺序让适配器先加载。** Loader 中行顺序不承载加载语义(激活按设计由服务驱动),因此这无法用配置表达。
**彻底压制 NO_ADAPTER 错误。** 那样的话,永久缺失的适配器(提供方名字拼错)在上下文窗口路径上就永远不会暴露。延后重试保留了信号:错误的提供方名字仍会在选择器中表现出类似 `model unset` 的行为,并在分派时大声失败,而启动竞争则自行化解。
**改为在每次提交消息时解析上下文窗口,而不是在挂载时。** 发送路径本就按步解析(`prepareCall()`),且指示器是持续显示的,不只在发送时;按提交解析显示值会让指示器在首条消息之前一直空白,并为一个仅在路由变化时才变的值反复执行适配器 I/O。
## Consequences
真正配置错误的提供方不再在启动时打印上下文解析错误——它改在首次分派时暴露,那才是该失败可以被处理的地方。控制器订阅每次 `llm/adapters-updated` 提交,但只在有等待被搁置时才动作;监听器的 disposer 经由控制器的 `detach()` 在频道的 `detachListeners()` 中释放,与同级频道监听器保持对称。由三个 TUI 测试覆盖:延后的解析在无关提交中保持沉默、在该路由的提交到来时完成;目标变更丢弃陈旧等待;频道 detach 之后注册表提交不再重新进入解析。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/bug-fix/2026-07-31-tui-diff-context-line-accounting.md
2026-07-31-tui-diff-context-line-accounting.md: 5172069c7def39808b018030b630ea4313bd7038
2026-07-31-tui-diff-context-line-accounting.zh.md: b9a9863f05d1c5b829facdc850eccc7a321a76ab

View File

@@ -0,0 +1,34 @@
# Agent Note: TUI diff context lines stay neutral
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-31-tui-diff-context-line-accounting.zh.md)
## Problem
Result-time filesystem diffs carry the applied change with three surrounding context lines in each `FileDiff.oldText` and `FileDiff.newText`. The TUI rendered every old-side row as removed and every new-side row as added, including the identical context present on both sides. A one-line edit therefore appeared as seven removals plus seven additions, and the footer repeated those inflated totals.
## Decision
The TUI compares each `FileDiff` whose old and new text are both available. Added and removed rows retain their green `+` and red `-` markers; equal context rows use the recessed body tone with a neutral two-space prefix. The footer sums only the rows classified as added or removed. `maxDiffEditLength` bounds the exact comparison by its combined added and removed line count; the default is 1000. Exceeding the bound renders the complete old side as removed and the complete new side as added, marks the footer approximate, and caches that result so redraws do not repeat the comparison. A tool result clears the pending-view cache before deriving the settled view, including when a presenter mutates and reuses the same view object.
When `oldText` is `null`, the renderer cannot distinguish a create from a pending overwrite or an argument fallback whose prior text is unavailable. It therefore shows every non-empty new-side row as added, without claiming those rows were absent from an existing file. Empty new content renders no synthetic added row.
This remains a consumer-side interpretation of the existing `FileDiff` contract. Filesystem tools continue to persist contextual before/after snippets, so other consumers keep their placement context and existing session logs replay with corrected TUI presentation. The TUI uses the same maintained `diff` package as `dsh-tool-fs` instead of introducing a second line-diff implementation.
## Alternatives considered
**Remove context from filesystem result metadata.** Rejected: contextual applied hunks are intentional producer output used by capable editors, and changing them would weaken every consumer while leaving old session logs misleading in the TUI.
**Extend `FileDiff` with persisted per-line tags.** Rejected: the tags can be derived deterministically from the existing before/after pair; persisting them would widen the cross-package and session-log contract solely for one renderer.
**Match equal lines by position without a diff algorithm.** Rejected: insertions and deletions shift subsequent context, so positional pairing would misclassify valid hunks.
**Run every comparison to completion.** Rejected: pending tool views can contain unrestricted model-authored old and new strings, and an unbounded Myers comparison can block the synchronous terminal renderer.
## Consequences
TUI diff cards distinguish evidence-bearing context from the mutation itself, and an exact `+A -R` footer reports the actual line delta. Replaying an existing contextual diff gains the corrected rendering without a migration. Result-time filesystem hunks are context-bounded; unrestricted pending views either complete within the configured edit-length budget or degrade to an explicitly approximate linear rendering.
The focused TUI tests cover neutral context, exact totals, an empty create, bounded fallback, result-time cache invalidation, and redraw cache reuse. The assembled `advanced-cards` terminal snapshots pin the neutral context style, semantic change colors, exact footer, and approximate fallback through collapsed and expanded card states.

View File

@@ -0,0 +1,34 @@
# Agent Note: TUI diff 上下文行保持中性
Status: implemented
Archived: 2026-08-04
[English](2026-07-31-tui-diff-context-line-accounting.md) | 中文
## 问题
文件系统 diff 返回结果时,每个 `FileDiff.oldText``FileDiff.newText` 都会包含已应用的变更及其前后各 3 行上下文。TUI 将旧侧的每一行都渲染为删除行,将新侧的每一行都渲染为新增行,其中包括两侧相同的上下文。因此,一行编辑会显示为删除 7 行并新增 7 行,页脚还会重复这些虚高的合计值。
## 决策
TUI 会比较每个变更前后文本均可用的 `FileDiff`。新增行和删除行仍分别使用绿色 `+` 和红色 `-` 标记;相同的上下文行则使用弱化的正文色调,并带有由两个空格构成的中性前缀。页脚只汇总归类为新增或删除的行。`maxDiffEditLength` 以新增行与删除行的合计数为精确比较设置上限,默认值为 1000。超过上限时TUI 会把完整旧侧渲染为删除内容、把完整新侧渲染为新增内容,将页脚标记为近似结果,并缓存该结果,避免后续重绘重复比较。工具结果会在派生已结算视图前清除待处理视图缓存,即使 presenter 修改并复用同一个视图对象也一样。
`oldText``null` 时,渲染器无法区分文件创建、待处理覆写,以及旧文本不可用的参数回退。因此,它会把新侧的每个非空行显示并计作新增行,但不会声称这些行原先不存在于已有文件中。新内容为空时,不会渲染虚构的新增行。
该行为仍然只是消费方对现有 `FileDiff` 契约的解释。文件系统工具仍会持久化带上下文的变更前后片段,因此其他消费方仍能获得定位上下文,已有会话日志在回放时也会采用修正后的 TUI 呈现。TUI 与 `dsh-tool-fs` 共用同一个受维护的 `diff`package无需引入第二套逐行 diff 实现。
## 考虑过的替代方案
**从文件系统结果元数据中移除上下文。** 不予采纳:带上下文的已应用 hunk 是有意保留的生产方输出,供具备相应能力的编辑器使用;更改这些内容会让所有消费方丢失信息,同时旧会话日志在 TUI 中仍会产生误导。
**为 `FileDiff` 扩展持久化的逐行标签。** 不予采纳:这些标签可以根据现有的变更前后文本对确定性派生;仅为一个渲染器持久化标签,会扩大跨包契约和会话日志契约。
**不使用 diff 算法,按位置匹配相同行。** 不予采纳:插入和删除会使后续上下文发生位移,因此按位置配对会把有效 hunk 错误分类。
**让所有比较都运行至完成。** 不予采纳:待处理工具视图可能包含由模型生成且长度不受限制的新旧字符串,无界的 Myers 比较可能阻塞同步终端渲染器。
## 后果
TUI diff 卡片会区分用于佐证的上下文与变更本身,精确的 `+A -R` 页脚会报告实际的行变更量。回放已有的上下文 diff 无需迁移即可获得修正后的渲染。结果时刻的文件系统 hunk 受上下文范围限制;不受限制的待处理视图要么在配置的编辑长度预算内完成比较,要么降级为明确标注为近似结果的线性渲染。
聚焦的 TUI 测试覆盖中性上下文、精确合计值、空文件创建、有界回退、结果到达时的缓存失效和重绘缓存复用。组装后的 `advanced-cards` 终端快照在卡片折叠和展开状态下固定了中性上下文样式、变更行的语义色彩、精确结果页脚和近似回退。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/bug-fix/2026-08-03-tui-long-session-render-costs.md
2026-08-03-tui-long-session-render-costs.md: 2f6e909aa280cf72cd15a635a64c80aee6cd0cf1
2026-08-03-tui-long-session-render-costs.zh.md: eeff7bc33df1f6e69d749c590953a42675a4d688

View File

@@ -0,0 +1,34 @@
# Agent Note: TUI long-session render costs — shared step-timing scan and card line caches
Status: implemented
Archived: 2026-08-04
English | [中文](2026-08-03-tui-long-session-render-costs.zh.md)
## Problem
On a long resumed session (196k events, 2.2k steps, 1.8k tool cards) the TUI took ~12 s to render the transcript and ~800 ms to echo one keystroke. Profiling attributed both to the render path, not to session load (zstd + parse + surface seed is ~1.7 s):
- Every step's timing footer called `stepTimingAt`, which replayed the whole event log from index 0 per footer — O(steps × events) on the initial render, ~6 s of CPU.
- pi-tui re-renders every component each frame and relies on per-component line caches (its own `Text`/`Markdown` cache by `(text, width)`). `ToolCardComponent.render()` and `ContextCardComponent.render()` built throwaway `new Text(...)`/`new Markdown(...)` instances inside `render(width)`, so every frame — every keystroke — re-wrapped every settled card's output.
## Decision
`packages/ui/tui/src/chat/timing.ts` replaces `stepTimingAt` with `StepTimingTracker`: one accumulator per chat mount, created in `createTuiChat` and threaded through `StreamingAssistantComponent` into each `StepTimingComponent`. A query advances a cursor over events appended since the previous query and keeps per-step bucket state in a map, so all footers together cost O(events). The open bucket is accumulated to the query clock at lookup, and a step is pinned at its `step/end`. The tracker requires the append-only session log (the `seq = log length` contract).
`ToolCardComponent` and `ContextCardComponent` cache their rendered rows keyed by width. The cache drops on every state mutator (`updateResult`, `setVisibility`, `setExpanded`) and on `invalidate()` (pi-tui's tree-wide cascade), so a state change always re-renders; everything else — including every keystroke frame — returns the cached rows. This restores upstream pi's own component convention (persistent child components plus explicit `cachedWidth`/`cachedLines` where rendering is custom, e.g. pi `coding-agent` `bash.ts`), which the imperative `render(width)` bodies here had silently defeated.
Measured on the 196k-event session (tmux, 200×50): resume prompt-ready 12.2 s → 7.2 s; per-keystroke echo 796 ms median → 17 ms (fresh-session parity).
## Alternatives considered
- **Index `step/start` offsets, keep per-footer replay** — removes the `findIndex` but each footer still scans its step's span from a shared array; the tracker's single shared pass is the same complexity win with less bookkeeping.
- **Restructure the cards into persistent pi-tui child components** (upstream pi's primary style) — equivalent steady-state cost, but a larger diff across card state handling for no additional win over the width-keyed cache.
- **Cache inside pi-tui's `Container.render`** — wrong layer: the vendored patch surface would grow, and the contract (components own their caches) already exists upstream.
## Consequences
- Typing latency no longer scales with total tool output; the residual per-frame cost is pi-tui's tree traversal and row concatenation, linear in rendered rows. Resume render cost is now dominated by pi-tui's one-time initial layout (~4 s at 196k events) plus load (~1.7 s), both linear.
- The tracker consumes event times as logged and drops the removed implementation's mid-scan `time > at` cutoff, which per-footer `at` values make impossible in a shared scan; under a backward wall-clock step each bucket clamps at zero, which can differ from the old cutoff's totals.
- Card `render()` is no longer a pure function of `(state, width)` per call — mutators must drop `linesCache`. A new mutator that forgets to do so shows stale rows; the cache tests in `packages/ui/tui/tests/transcript-card-cache.spec.ts` pin the contract for the existing mutators.
- `StepTimingTracker` assumes step coordinates are not reused after `step/end`; a duplicate `step/start` for a closed step is ignored rather than restarting the step.

View File

@@ -0,0 +1,34 @@
# Agent Note: TUI 长会话渲染开销:共享步骤耗时扫描与卡片行缓存
Status: implemented
Archived: 2026-08-04
[English](2026-08-03-tui-long-session-render-costs.md) | 中文
## 问题
在一个恢复后的长会话196k 条事件、2.2k 个步骤、1.8k 张工具卡片TUI 渲染 transcript文本记录耗时约 12 秒,回显一次按键耗时约 800 毫秒。性能剖析表明两项耗时都来自渲染路径而非会话加载zstd + 解析 + 表层播种约为 1.7 秒):
- 每个步骤的耗时页脚都会调用 `stepTimingAt`,而它会针对每个页脚从索引 0 起回放整个事件日志,因此初次渲染的复杂度为 O(步骤数 × 事件数),占用约 6 秒 CPU 时间。
- pi-tui 每一帧都会重新渲染所有组件,并依赖各组件自己的行缓存(它的 `Text`/`Markdown` 会按 `(text, width)` 缓存)。`ToolCardComponent.render()``ContextCardComponent.render()` 构造用后即弃的 `new Text(...)`/`new Markdown(...)` 实例,且构造发生在 `render(width)` 内,因此每一帧,也就是每次按键,都会重新对每张已结算卡片的输出进行折行。
## 决策
`packages/ui/tui/src/chat/timing.ts` 不再使用 `stepTimingAt`,改用 `StepTimingTracker`:每次挂载聊天界面时在 `createTuiChat` 中创建一个累加器,再经 `StreamingAssistantComponent` 传入每个 `StepTimingComponent`。每次查询都会推进游标,扫描上次查询后追加的事件,并在一个映射表中保存各步骤的 bucket 状态,因此所有页脚合计只需 O(事件数)。查询时,系统把未闭合 bucket 累加到查询时刻;步骤在其 `step/end` 处固定。该跟踪器要求会话日志仅追加,即遵守 `seq = log length` 契约。
`ToolCardComponent``ContextCardComponent` 按宽度键控缓存渲染行。调用任一状态修改方法(`updateResult``setVisibility``setExpanded`)或 `invalidate()`pi-tui 的全树级联)时会清空缓存,因此状态变化一定会重新渲染;其他情况,包括每一次按键帧,都会返回缓存行。这恢复了上游 pi 自身的组件惯例:使用常驻子组件;自定义渲染时显式使用 `cachedWidth`/`cachedLines`,例如 pi `coding-agent``bash.ts`。而这里命令式的 `render(width)` 函数体此前让这套惯例失效。
在该 196k 条事件的会话上测得tmux200×50恢复后提示符就绪耗时从 12.2 秒降至 7.2 秒;每次按键的回显耗时中位数从 796 毫秒降至 17 毫秒(与新会话持平)。
## 曾考虑的替代方案
- **索引 `step/start` 偏移量,保留逐页脚回放**:这会消除 `findIndex`,但每个页脚仍要从共享数组扫描所属步骤的区间;跟踪器的一次共享遍历以更少的额外状态记录取得相同的复杂度改进。
- **把卡片重构为常驻 pi-tui 子组件**(上游 pi 的主要风格):稳定状态下成本相同,但卡片状态处理所需改动更大,相较按宽度键控的缓存并无额外收益。
- **在 pi-tui 的 `Container.render` 内缓存**:层级不对:对第三方内嵌代码的补丁范围会扩大,而上游已经约定由组件拥有各自的缓存。
## 后果
- 输入延迟不再随工具输出总量增长;剩余的每帧成本是 pi-tui 的树遍历与行拼接,与渲染行数呈线性关系。恢复时的渲染成本现由 pi-tui 的一次性初始布局196k 条事件时约 4 秒)与加载(约 1.7 秒)主导,两者均为线性。
- 该跟踪器直接采用日志记录的事件时间,不再像已移除的实现那样,在扫描中途遇到 `time > at` 时截断;由于每个页脚的 `at` 值不同,共享扫描无法采用这种截断;挂钟时间倒退时,每个 bucket 都以零为下限,所得总计值可能与旧截断下的总计值不同。
- 卡片的 `render()` 不再是每次调用时 `(state, width)` 的纯函数,状态修改方法必须清空 `linesCache`。若新增状态修改方法时忘记清空,界面会显示陈旧行;`packages/ui/tui/tests/transcript-card-cache.spec.ts` 中的缓存测试固定了现有状态修改方法的契约。
- `StepTimingTracker` 假定步骤坐标在 `step/end` 后不会复用;对已关闭步骤重复出现的 `step/start` 会被忽略,不会重新启动该步骤。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.md
2026-07-17-dedicated-full-screen-tui-front-door.md: 1a04e738a2810abdb4bac81aebe9b23db404879e
2026-07-17-dedicated-full-screen-tui-front-door.zh.md: 75fc3d47aa258fa6db767b1049194663148d1afa

View File

@@ -0,0 +1,55 @@
# Agent Note: Dedicated full-screen TUI front door
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-17-dedicated-full-screen-tui-front-door.zh.md)
## Problem
The reusable TUI package remains implemented, but [`dsh` no longer ships it as an application entrypoint](../simplification/2026-08-03-explicit-config-dsh-entrypoint.md). This note continues to own the package boundary and terminal behavior; the later note owns product composition.
At the time this front door was introduced, the line-oriented agent handled pipes and ordinary terminals, but a full-screen coding interface had to own raw input, differential screen drawing, cursor state, overlays, and terminal restoration. Combining those contracts in one UI plugin would have coupled a stream-oriented path to a TTY-only lifecycle. The later [redundant-agent removal](../simplification/2026-07-20-remove-stdio-and-echo-agents.md) removes that line agent; this Note continues to own the TUI design.
The interactive channel must remain a Cordis plugin over the same agent, session, tool, and user-interaction services as every other front door. It needs to resume durable history, follow compaction replacements, display tool-owned presentation, and restore the terminal on startup failure and disposal. A standalone chat application or a second agent composition would duplicate behavior outside the plugin graph.
## Decision
DeepSeek Harness ships [`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) as a dedicated Cordis plugin. It owns terminal input and presentation only; agent lifecycle, session persistence, tool execution, and the model-facing question tool remain separate composition entries. The plugin requires both stdin and stdout to be TTYs and fails instead of silently changing to line-oriented behavior.
The package is a terminal front door, not a complete application. A host mounts `@deepseek-ai/dsh-tui` before its configured agent and composes the backends, tools, and policies around it. The product CLI currently ships no terminal composition; non-interactive tasks use headless mode, Web owns the installed human surface, and ACP remains a separate automation protocol.
The host supplies the exact generated or resumed `SessionId` used by its pre-created agent. The TUI waits for the matching root agent and enters full-screen mode only after that agent exists. A matching `agent-loop/config-start-failed` event is therefore reported before screen takeover.
### Session projection and interaction
The TUI rebuilds the transcript from the append-origin session events, so resumed history keeps every message the reader already saw; a compacted range stays readable behind one marker instead of matching the model-visible conversation ([append-origin transcript](../bug-fix/2026-07-29-human-transcript-append-origin.md)). It renders Markdown text and reasoning, including fenced code with hidden Markdown markers, a dim optional language label, and a code-colored body, token totals, the latest `todo/write` plan, and tool cards produced through each tool definition's `presentCall` and `presentResult` methods. Long card bodies retain a configurable head/tail preview with the hidden-line count; one terminal control expands or collapses every card. Pending chunks and tool calls update the same components that completed events settle.
Editor input calls `agent.send()` while idle and `agent.steer()` while a turn is running. Cancellation, reasoning visibility, tool-card expansion, redraw, transcript clearing, and exit are terminal-only controls. `/exit` and `/quit` share the same exit path: they cancel an active turn, wait for idle, and then restore and close the terminal. The idle footer derives context occupancy from `tokenMeter` and shows the selected model and explicit reasoning effort; during a run, elapsed activity and the Escape interrupt hint replace that summary. `/status` remains available in either state and appends a detailed terminal-only snapshot: session identity and timestamps, selected model, reasoning effort/default state and reasoning visibility, lifecycle counts folded from the event log, the same deduplicated usage buckets and KV-cache rate as the footer, and context use from `tokenMeter` plus the selected model's advertised capacity. The plugin registers the shared `userInteraction` provider and presents queued questions in a wide bottom-left keyboard panel with batch progress, numbered options, and aligned descriptions; the panel's controls hint lists only actions meaningful for the current option count, omitting navigation when exactly one option is shown; agent behavior and answer logging remain owned by their existing services.
The `/model` command presents the advisory `ctx.llm` catalog as a keyboard selector and changes only this TUI session's target; argument forms remain available for direct selection. The selector carries a filter box above the list: typing narrows the rows to a case-insensitive substring over each row's `provider/model` label, model name, and description, keeping the selection on the previously highlighted row when it survives the filter; Escape clears a non-empty filter before a second Escape cancels the selector. Each model row owns the adapter-advertised reasoning-effort order and default: Shift+Tab cycles that row's efforts, includes provider-default behavior when the adapter advertises no default, and leaves models without selectable metadata unchanged. Agent-scoped prompt-assembly and request waterfalls snapshot one provider/model/reasoning-effort target per step, so `{{provider}}` / `{{model}}` interpolation and request routing cannot split when a command arrives during assembly. The latest logged request header restores a used target; a selection that never reaches a request remains process-local.
### Terminal ownership
Before model output, session data, tool presentation, questions, configuration, or diagnostics reach pi-tui or the terminal title, `displayText()` renders C0 and C1 controls other than line feeds as visible hexadecimal escapes. Only the TUI and pi-tui create ANSI control sequences.
The built-in palette uses standard 16-color ANSI foregrounds and SGR attributes, keeps body text and backgrounds at terminal defaults, and uses reverse video for selection. Host terminals therefore remap the interface for light and dark themes without a TUI-specific theme setting; `color: false` removes styling.
## Verification
The implemented [TUI terminal-state snapshot Agent Note](../testing/2026-07-18-tui-terminal-state-snapshots.md) owns the package verification contract: direct behavior tests and semantic terminal snapshots. A deployment shipping this front door owns its assembled transcript and process/PTY acceptance. The package README owns configuration, commands, model-visible effects, and current limitations.
## Alternatives considered
- **Keep readline and full-screen modes inside `@deepseek-ai/dsh-stdio`** — rejected because line-oriented output and differential TTY rendering have different dependencies, input rules, logging ownership, and teardown obligations. Separate packages keep the pipe-safe contract small and explicit.
- **Let the TUI plugin silently downgrade when either stream is not a TTY** — rejected because a fallback hides deployment mistakes and changes interaction semantics. A host may select a different front door; an explicitly mounted TUI fails loud.
- **Keep TUI wiring and tests under the readline `repl-agent` leaf** — rejected at the time because one leaf would represent two distinct front doors. The later product-entrypoint removal deleted that application wiring while retaining the package boundary.
- **Mutate `agent.options` when `/model` runs** — rejected because creation options do not provide an atomic boundary between asynchronous prompt assembly and request routing. Agent-scoped waterfalls preserve immutable creation input and snapshot the selected pair for each step.
## Consequences
- Deployments that mount the TUI gain a stateful Markdown, card, plan, and question interface with no second terminal protocol to keep aligned.
- The TUI carries a pi-tui dependency and a strict TTY requirement; non-TTY deployments use the Headless app or a structured protocol.
- Session projection makes resume consistent with the durable conversation, but one configured session owns the transcript and editor.
- Tool packages extend terminal cards through their existing presentation methods without adding tool-specific branches to the TUI.
- Model and reasoning-effort selection use adapter-advertised metadata without turning catalog membership into request validation; unused selections are not durable state.

View File

@@ -0,0 +1,55 @@
# Agent Note: 独立的全屏 TUI 入口
Status: implemented
Archived: 2026-08-04
[English](2026-07-17-dedicated-full-screen-tui-front-door.md) | 中文
## 问题
可复用的 TUI 包package仍然保留实现但 [`dsh` 不再将其作为应用入口交付](../simplification/2026-08-03-explicit-config-dsh-entrypoint.md)。本记录继续负责包边界和终端行为;后续记录负责产品组合。
在本入口引入时,面向行的 agent 负责 pipe 与普通终端,但全屏 coding 界面必须负责原始输入、差分绘制、光标状态、浮层和终端恢复。把这两类契约合并到一个 UI 插件中,会迫使面向 stream 的路径依赖仅适用于 TTY 的生命周期。后续的[移除重复 agent 决策](../simplification/2026-07-20-remove-stdio-and-echo-agents.md)移除了这个面向行 agent本 Note 继续负责 TUI 设计。
交互通道必须继续作为 Cordis 插件,使用与其他入口相同的 agent智能体、会话、工具和用户交互服务。它需要恢复持久历史、跟随压缩替换、显示工具自有的呈现内容并在启动失败和资源释放时恢复终端。独立聊天应用或第二套 agent 组合会在插件图之外重复实现这些行为。
## 决策
DeepSeek Harness 将 [`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) 作为独立的 Cordis 插件交付。该插件只负责终端输入与呈现agent 生命周期、会话持久化、工具执行以及模型可见的提问工具仍由不同组合项负责。插件要求 stdin 和 stdout 均为 TTY条件不满足时会失败不会静默切换为逐行输出。
该包是终端入口,而不是完整应用。宿主在已配置 agent 之前挂载 `@deepseek-ai/dsh-tui`,并围绕它组合后端、工具和策略。产品 CLI 目前不交付终端组合;非交互任务使用 headless 模式Web 是已安装产品中面向人的界面,而 ACP 仍是独立的自动化协议。
宿主提供其预创建 agent 使用的同一个新建或恢复 `SessionId`。TUI 等待相符的根 agent 出现,然后才进入全屏模式。因此,相符的 `agent-loop/config-start-failed` 事件会在接管屏幕前报告。
### 会话投影与交互
TUI 从追加来源的会话事件重建 transcript文本记录因此恢复后的历史会保留读者已经看到的每条消息被压缩的范围不再与模型可见会话保持一致而是留在一行标记之后仍可阅读[追加来源的 transcript](../bug-fix/2026-07-29-human-transcript-append-origin.md)。TUI 渲染 Markdown 文本与推理(其中围栏代码块隐藏 Markdown 标记、保留一个暗色的可选语言标签并使用代码配色的正文、token 用量、最新 `todo/write` 计划,以及各工具定义通过 `presentCall``presentResult` 方法生成的工具卡片。较长的工具卡片正文会保留可配置的头尾预览,并显示隐藏行数;一个终端控制可以展开或收起全部卡片。进行中的分片与工具调用会更新同一组组件,随后由完成事件收束状态。
agent 空闲时,编辑器输入调用 `agent.send()`;轮次运行中则调用 `agent.steer()`。取消、推理显隐、工具卡片展开、重绘、清空 transcript 和退出都只是终端控制。`/exit``/quit` 共用同一条退出路径:先取消进行中的轮次,等待 agent 空闲,然后恢复并关闭终端。空闲态页脚根据 `tokenMeter` 得出上下文占用率并显示所选模型和显式选定的推理强度agent 运行期间,该摘要会替换为带已用时长的活动指示和 Escape 中断提示。`/status` 在这两种状态下均可用,并会追加一份仅在终端显示的详细快照,其中包括会话标识与时间戳、所选模型、推理强度(或默认状态)及推理显隐状态、从事件日志归并得出的生命周期计数、与页脚一致的去重用量分项和 KV 缓存命中率,以及 `tokenMeter` 给出的上下文用量和所选模型公布的容量。插件注册共享的 `userInteraction` 提供方在左下角宽幅键盘操作面板中呈现排队的问题面板显示批次进度、带编号的选项和对齐的描述面板的操作提示只列出在当前选项数量下有意义的操作仅有一个选项时不显示导航项agent 行为和答案日志仍由既有服务负责。
`/model` 命令将建议性的 `ctx.llm` 目录呈现为键盘选择器,并且只更改当前 TUI 会话的目标;带参数的形式仍可直接选择目标。选择器在列表上方设有一个过滤框:输入内容会按对每行 `provider/model` 标签、模型名称和描述的大小写不敏感子串匹配来缩小行集并在原先高亮行仍通过过滤时保持其选中状态Escape 会先清空非空的过滤内容,再次按 Escape 才取消选择器。每个模型行都持有适配器公布的推理强度顺序和默认值:按 Shift+Tab 可循环切换该行的推理强度如果适配器没有公布默认值循环中还会包含提供方默认行为没有可选元数据的模型则保持不变。agent 作用域内的 prompt 组装和请求两条 waterfall瀑布式事件会为每个步骤快照一次同一个提供方/模型/推理强度目标,因此即使命令在组装期间到达,`{{provider}}` / `{{model}}` 插值与请求路由也不会分裂。系统通过日志中最新的请求头恢复已经使用过的目标;未被请求使用的选择只保留在当前进程中。
### 终端所有权
在模型输出、会话数据、工具呈现、问题、配置或诊断信息进入 pi-tui 或终端标题前,`displayText()` 会把换行之外的 C0 和 C1 控制字符显示为十六进制转义文本。只有 TUI 和 pi-tui 可以生成 ANSI 控制序列。
内置配色仅使用标准 16 色 ANSI 前景色和 SGR 属性,正文文字和背景沿用终端默认值,选中项使用反显。因此,宿主终端可以直接按浅色或深色主题重映射界面,无需 TUI 专用主题设置;`color: false` 会移除样式。
## 验证
已实现的 [TUI 终端状态快照 Agent Note](../testing/2026-07-18-tui-terminal-state-snapshots.md) 规定包验证契约:直接行为测试和语义终端快照。交付该入口的部署负责其组装后 transcript 和进程PTY 验收。包 README 负责记录配置、命令、模型可见效果和当前限制。
## 曾考虑的替代方案
- **把 readline 与全屏模式都保留在 `@deepseek-ai/dsh-stdio` 中**:不予采纳,因为逐行输出和差分 TTY 渲染具有不同的依赖、输入规则、日志所有权和资源清理义务。拆分为独立包可以让管道安全契约保持精简、明确。
- **当任一进程流不是 TTY 时,让 TUI 插件静默降级**:不予采纳,因为回退会掩盖部署错误并改变交互语义。宿主可以选择其他入口;明确挂载的 TUI 会快速失败。
- **把 TUI 接线与测试保留在 readline `repl-agent` 叶节点下**:当时不予采纳,因为一个叶节点会代表两个不同入口。后续移除产品入口时删除了该应用接线,但保留了包边界。
- **在 `/model` 运行时修改 `agent.options`**:不予采纳,因为创建选项无法在异步 prompt 组装与请求路由之间提供原子边界。agent 作用域内的 waterfall 会在保持创建输入不可变的同时,为每个 step 快照一次选中的字段组合。
## 后果
- 挂载 TUI 的部署会获得带状态的 Markdown、卡片、计划和提问界面无需再对齐第二套终端协议。
- TUI 会引入 pi-tui 依赖并严格要求 TTY非 TTY 部署使用 Headless app 或结构化协议。
- 会话投影使恢复与持久会话保持一致,但只有一个已配置会话拥有 transcript 和编辑器。
- 工具包通过既有呈现方法扩展终端卡片,无需在 TUI 中增加工具专用分支。
- 模型和推理强度选择使用适配器公布的元数据,但不会把目录成员关系变成请求校验;未使用的选择不属于持久化状态。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-07-20-windows-tui-support.md
2026-07-20-windows-tui-support.md: 9308df7fb43378610cb2d480324c55bff07ea108
2026-07-20-windows-tui-support.zh.md: 8b4a1eb4cef3056b47b911a07ed62202b52d1f73

View File

@@ -0,0 +1,34 @@
# Agent Note: Support the TUI on Windows
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-20-windows-tui-support.zh.md)
## Problem
The full-screen TUI delegates raw input, ANSI rendering, resize events, and terminal restoration to pi-tui's `ProcessTerminal`. That dependency contains a native Windows console path, but the repository's real-process smoke used Python's POSIX-only `pty` and `termios` modules. Skipping that smoke on Windows would leave the supported product path without coverage for startup, input, interaction, failure reporting, or restoration.
The TUI platform contract must follow the runtime shipped to users rather than the portability of one test driver. A platform exclusion is justified only when the product has an unsupported runtime dependency or a demonstrated semantic gap.
## Decision
[`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) supports interactive terminals on Windows as well as macOS and Linux. The product continues to use pi-tui's `ProcessTerminal`; on Windows it enables virtual-terminal input after raw mode and avoids the Unix-only `SIGWINCH` refresh. DeepSeek Harness adds no platform rejection or reduced Windows mode.
The real Loader smoke selects a native pseudo-terminal boundary by host. macOS and Linux retain the Python POSIX PTY driver. Windows uses `node-pty` and ConPTY. Both drivers receive the same launch command, environment, terminal dimensions, marker-gated input actions, timeout, expected exit code, and output assertions, and all three smoke scenarios run on every supported platform.
`node-pty` is a test-only dependency of the examples workspace. Its reviewed native install script is explicitly enabled in `pnpm-workspace.yaml`; production TUI packages do not acquire a new dependency or subprocess layer.
## Alternatives considered
- **Declare the TUI unsupported on Windows** — rejected because the pinned terminal runtime implements Windows console input explicitly and the harness has no POSIX-only production dependency. A documentation-only exclusion would discard an existing product path to accommodate a test harness gap.
- **Run the POSIX driver through MSYS, Cygwin, or WSL** — rejected because that would test a compatibility environment rather than the native Windows console path users run.
- **Use `node-pty` on every host** — rejected because the established POSIX driver already provides the macOS and Linux boundary; replacing it would widen the runtime change without improving those hosts. Platform-specific drivers reserve the `node-pty` runtime path for Windows while sharing one scenario contract.
- **Rely on renderer unit tests and semantic terminal snapshots** — rejected because fake terminals do not prove Loader boot, real raw input, process exit, or terminal restoration at the operating-system boundary.
## Consequences
- The Windows artifact lane executes the startup, scripted interaction, resume-failure, and restoration scenarios, and the suite has no supported-platform skip.
- The Windows process proof depends on ConPTY and a pinned `node-pty` release; changing that dependency or its allowed install script requires native-boundary review.
- The two PTY drivers can differ internally, but shared inputs and assertions keep their observable TUI contract aligned.
- Windows support remains bounded by the Node and pi-tui versions shipped by the repository; unsupported historical Windows console environments do not receive a compatibility layer.

View File

@@ -0,0 +1,34 @@
# Agent Note: 在 Windows 上支持 TUI
Status: implemented
Archived: 2026-08-04
[English](2026-07-20-windows-tui-support.md) | 中文
## 问题
全屏 TUI 将原始输入、ANSI 渲染、终端尺寸变更事件和终端恢复委托给 pi-tui 的 `ProcessTerminal`。该依赖已实现原生 Windows 控制台路径,但仓库的真实进程冒烟测试此前使用 Python 中仅适用于 POSIX 的 `pty``termios` 模块。若在 Windows 上跳过该测试,这条受支持的产品路径便会缺少针对启动、输入、交互、失败报告和终端恢复的测试覆盖率。
TUI 平台契约必须以交付给用户的运行时为准,而不是取决于某个测试驱动程序的可移植性。只有产品存在不受支持的运行时依赖,或已证实存在语义缺口时,排除某个平台才有依据。
## 决策
[`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) 在 Windows、macOS 和 Linux 上均支持交互式终端。产品继续使用 pi-tui 的 `ProcessTerminal`;在 Windows 上,它会在进入原始模式后启用虚拟终端输入,并避开仅适用于 Unix 的 `SIGWINCH` 刷新。DeepSeek Harness 不增加平台拒绝逻辑,也不采用功能受限的 Windows 模式。
真实 Loader 冒烟测试根据宿主选择原生伪终端边界。macOS 和 Linux 继续使用 Python POSIX PTY 驱动Windows 则使用 `node-pty` 和 ConPTY。两种驱动接收相同的启动命令、环境、终端尺寸、以标记为触发条件的输入动作、超时、预期退出码和输出断言3 个冒烟场景都会在每个受支持平台上运行。
`node-pty` 是 examples 工作区仅供测试使用的依赖。该依赖经评审的原生安装脚本在 `pnpm-workspace.yaml` 中显式启用;生产 TUI 包package不会新增依赖或子进程层。
## 曾考虑的替代方案
- **声明 TUI 不支持 Windows**:不予采纳,因为固定版本的终端运行时已显式实现 Windows 控制台输入,且 harness 没有仅适用于 POSIX 的生产依赖。仅通过文档排除 Windows等于为迁就测试 harness 的缺口而舍弃现有产品路径。
- **通过 MSYS、Cygwin 或 WSL 运行 POSIX 驱动**:不予采纳,因为这会测试兼容环境,而不是用户实际运行的原生 Windows 控制台路径。
- **在所有宿主上使用 `node-pty`**:不予采纳,因为现有 POSIX 驱动已经为 macOS 和 Linux 提供所需边界;替换该驱动会扩大运行时变更范围,却不会给这两个宿主带来改进。按平台选择驱动,仅在 Windows 上启用 `node-pty` 运行时路径,同时共享同一份场景契约。
- **依赖渲染器单元测试和语义终端快照**:不予采纳,因为模拟终端无法证明 Loader 启动、真实原始输入、进程退出或操作系统边界上的终端恢复。
## 后果
- Windows 产物 lane 执行启动、脚本化交互、配置恢复失败和终端恢复场景,这套测试不会在任何受支持平台上跳过。
- Windows 进程级验证依赖 ConPTY 和固定版本的 `node-pty`;变更该依赖或允许执行的安装脚本时,必须进行原生边界评审。
- 两种 PTY 驱动的内部实现可以不同,但共享的输入和断言会使其可观测 TUI 契约保持一致。
- Windows 支持范围以仓库交付的 Node 和 pi-tui 版本为界;不受支持的旧版 Windows 控制台环境不会获得兼容层。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-07-21-tui-resume-command.md
2026-07-21-tui-resume-command.md: de6c488b51b300705b33db13b8959b8114fdd025
2026-07-21-tui-resume-command.zh.md: d7639c7a51fc5c35f10c514356ceb79d9c1ca76a

View File

@@ -0,0 +1,40 @@
# Agent Note: Product-level TUI session resume
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-21-tui-resume-command.zh.md)
## Problem
The original `/resume` printed shell commands. It did not let a keyboard user inspect titles or outcomes, distinguish corruption from a missing adapter, or safely transfer the terminal. Leaving the TUI and manually launching a command also hid the required ordering: finish current work, flush it, release the UI and app, then restore the exact persisted identity without silently creating a replacement.
## Decision
`/resume` uses the TUI's existing interactive overlay seam as a full-viewport picker rather than a centered dialog. The flat page keeps the search field, workspace scope line, candidates, and shortcut footer in stable screen regions; only the active row uses the accent role. Its search editor starts immediately after the search glyph and emits pi-tui's cursor marker, so terminal IME composition remains anchored in the field. Escape clears a non-empty query before a second Escape closes the picker. It orders candidates by last logged activity and searches log-backed title or id. Each candidate displays current/live/persisted state, last turn outcome, recent provider/model, durable goal phase when present, and the id as secondary text. The current session and sessions already live in this runtime remain visible but disabled. The picker opens on the current workspace and reaches every other one through the scope toggle the [cross-workspace resume](2026-07-28-cross-workspace-resume.md) note owns.
`session-query.readSession()` supplies a detached complete log validated by the same core replay boundary used by resume. The TUI folds title and goal state from that log. A candidate load failure is local to that row; selecting a candidate revalidates the log, workspace, route, current agent's idle status, and the exclusions for the current session and sessions already live in this runtime, so a stale listing cannot bypass preflight. A missing adapter reports an intact session with an unavailable route. This preflight does not lock the target or exclude another process.
After preflight, the TUI flushes the current session, confirms that its agent remains idle, then stops the terminal before calling `TuiRuntime.handoffResume` with the validated id and the target workspace. The shipped `dsh` host disposes the root app and uses `process.execve` with a normalized `--resume` argument, atomically replacing the process rather than starting a child. The resumed app publishes the same `SessionId`; ordinary replay restores transcript, title, todos, and durable goal state. Goal activation is intentionally disarmed, and the TUI asks for human confirmation or `/goal resume`.
The exit line is a launcher-owned context slot rather than a config template, and a host without in-place handoff reports that the session stays resumable instead of naming a command it cannot construct; the [launcher-owned resume identity](../architecture/2026-07-28-launcher-owned-resume-identity.md) note owns that ownership move and supersedes the `resumeCommand` config key this note originally shipped. The TUI still never executes shell text.
## Alternatives considered
**Have the TUI spawn the resume command.** Rejected: the text is display copy, not trusted argv, and the TUI does not own app teardown or process lifetime. The constrained host seam receives only a validated `SessionId`.
**Construct the resumed agent inside the existing TUI.** Rejected: replacing one config-created agent would cross Loader ownership, scoped plugin setup, persistence retirement, and terminal lifecycle in the presentation layer. Root disposal plus process replacement reuses the supported startup path.
**Treat a missing adapter as a missing session.** Rejected: storage validity and current route availability are independent facts. The selector keeps the row and names the unavailable provider/model.
**Persist goal activation across resume.** Rejected: durable intent is not authorization to continue after a human or process boundary. Goal phase survives; automatic continuation does not.
## Consequences
- Concurrent processes can select or resume the same persisted session because preflight does not serialize them.
- `/resume` depends on `session-query` for discovery and complete-log reads, but persistence and host handoff remain optional; without a host, the command fallback stays usable.
- Process replacement intentionally restarts Loader composition. Runtime-only state is rebuilt, while only logged or header-backed session state survives.
## Testing
TUI package tests cover keyboard navigation, title/id search, search-clear/cancel behavior, running-agent refusal, refusal of the current session and sessions already live in this runtime, route absence, corrupt rows, preflight revalidation, the no-host warning, and stop-before-handoff ordering. Session-query tests pin detached full-log validation. Agent-loop resume tests pin exact identity and history; title, todo, and goal replay suites pin restored projections and disarmed goal activation. The package semantic snapshot owns the full-viewport selector and its IME cursor anchor; a deployment shipping the TUI owns its process and PTY handoff acceptance.

View File

@@ -0,0 +1,40 @@
# Agent Note: 产品级 TUI 会话恢复
Status: implemented
Archived: 2026-08-04
[English](2026-07-21-tui-resume-command.md) | 中文
## Problem
原有 `/resume` 只会打印 shell 命令。使用键盘操作的用户无法查看标题或结果、区分日志损坏与适配器缺失,也无法安全移交终端。退出 TUI 后手动启动命令还掩盖了必要的操作顺序:等待当前工作结束并将其刷写,释放 UI 和应用,再恢复持久化的原有身份,绝不能静默创建替代会话。
## Decision
`/resume` 使用 TUI 现有的交互式浮层接口,但以占满 viewport 的选择页呈现而不是居中弹窗。这个扁平页面把搜索框、workspace 作用域行、候选项和快捷键页脚放在稳定的屏幕区域,只有当前行使用强调色。搜索编辑器紧跟搜索图标起始,并输出 pi-tui 的光标标记,因此终端输入法的组合文本会锚定在输入框中。查询非空时,第一次按 Escape 会清空查询,第二次才关闭选择页。页面按日志记录的最后活动时间排列候选项,并支持按日志内标题或 id 搜索。每个候选项都会显示是否为当前会话、是否活跃、是否已持久化最近一个轮次的结果最近使用的提供方模型以及可用时的持久化目标阶段id 作为次要信息显示。当前会话和已在本运行时中处于活跃状态的会话仍会显示,但不可选择。选择页打开时位于当前 workspace并通过[跨 workspace 恢复](2026-07-28-cross-workspace-resume.md)记录所拥有的作用域切换到达其他每一个 workspace。
`session-query.readSession()` 提供一份脱离运行时的完整日志并通过恢复流程所用的同一核心回放边界完成验证。TUI 从该日志中折叠出标题和目标状态。候选项加载失败时只影响该行选择候选项后会复查日志、workspace、路由、当前 agent 的空闲状态,以及针对当前会话和已在本运行时中处于活跃状态的会话的排除规则,避免陈旧列表绕过预检。适配器缺失时会报告会话完整但路由不可用。该预检不会锁定目标,也不会排除其他进程。
预检通过后TUI 会刷写当前会话,再次确认其 agent 仍处于空闲状态,然后停止终端并以经过验证的 id 和目标 workspace 调用 `TuiRuntime.handoffResume`。已交付的 `dsh` 宿主会释放根应用,并使用带有规范化 `--resume` 参数的 `process.execve` 原子替换当前进程,而不是启动子进程。恢复后的应用发布相同的 `SessionId`;常规回放会还原 transcript文本记录、标题、待办事项和持久化目标状态。系统会有意解除目标的激活状态TUI 则要求用户确认继续或执行 `/goal resume`
退出时打印的行是启动器拥有的上下文插槽,而非配置模板;不支持原地交接的宿主会说明会话仍可恢复,而不再给出它无法构造的命令。[由启动器持有的会话身份与退出行](../architecture/2026-07-28-launcher-owned-resume-identity.md)记录了这次所有权迁移,并取代本记录最初交付的 `resumeCommand` 配置键。TUI 仍然绝不执行 shell 文本。
## Alternatives considered
**让 TUI 创建恢复命令进程。** 否决:该文本是展示用文案,不是可信的参数列表,且 TUI 不拥有应用拆卸或进程生命周期。受约束的宿主接口只接收经过验证的 `SessionId`
**在现有 TUI 内构造恢复后的 agent。** 否决:在表现层替换由配置创建的 agent会跨越 Loader 所有权、作用域插件初始化、持久化资源释放和终端生命周期。释放根应用并替换进程可以复用受支持的启动路径。
**把适配器缺失视为会话缺失。** 否决:存储有效性和当前路由可用性是相互独立的事实。选择器会保留该行,并指出不可用的提供方/模型。
**恢复会话时延续目标激活状态。** 否决:持久意图并不代表跨越用户或进程边界后仍获授权继续执行。目标阶段会保留,但不会自动续跑。
## Consequences
- 预检不会串行化不同进程;多个进程可以并发选择或恢复同一个持久化会话。
- `/resume` 依赖 `session-query` 发现会话并读取完整日志,但持久化和宿主交接仍是可选功能;没有宿主时,命令回退仍可使用。
- 进程替换会有意重启 Loader 组合。系统会重建仅存在于运行时的状态,而只有日志或会话头部记录的会话状态能够保留。
## Testing
TUI 包package测试覆盖键盘导航、标题id 搜索、清空搜索后再取消、agent 运行期间拒绝恢复、拒绝恢复当前会话和已在本运行时中处于活跃状态的会话、路由缺失、损坏的候选行、预检复查、无宿主时的告警以及停止终端先于宿主交接的顺序。session-query 测试固定脱离运行时的完整日志验证。agent-loop 恢复测试固定会话身份和历史完全一致;标题、待办事项和目标回放测试套件固定这些投影均可恢复,且目标激活状态已经解除。包级语义快照固定全屏选择页和输入法光标锚点;交付 TUI 的部署负责其进程与 PTY 交接验收。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.md
2026-07-21-tui-skill-slash-command.md: e151f92d0d9eb1486bcf21498fb73ba156edd861
2026-07-21-tui-skill-slash-command.zh.md: ac1c4da948dcbd6b01bdb6e62d66d5f704c0b132

View File

@@ -0,0 +1,34 @@
# Agent Note: TUI skill slash command
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-21-tui-skill-slash-command.zh.md)
## Problem
The [skill system](2026-07-05-skill-system.md) shipped with model-initiated loading as its only path: the `skill({ name })` tool lets the model pull a skill body into a turn, but a person driving the TUI could not load a skill on demand. Other coding agents expose a `/skill:<name>` slash command for exactly this — the user, not the model, decides a task matches a skill and injects its instructions. The skill-system note listed direct user invocation as deferred work, and the interactive front door is where it belongs.
## Decision
The [`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) front door owns a `/skill:<name> [instructions]` command. On submit it loads the named skill and delivers one text block as a user turn — sent with `agent.send()` while idle and `agent.steer()` while running, the same rule as ordinary editor input. The block is `renderSkillInvocation(skill, instructions)`: a `<skill name="…">` element wrapping the skill body, preceded by one resource-base line when the provider exposes one, with the user's trailing text appended after a blank line. The command is a TUI-only affordance; it adds no model-facing tool. Its visibility and loading policy comes from the shared [independent model and user skill invocation policy](2026-07-28-skill-invocation-policy.md).
The TUI reads the skill service through `ctx.get('skills')`, not a declared injection, because skills mount conditionally: a deployment without the registry keeps a working front door, and `/skill:` there reports that skills are unavailable rather than failing to mount. `createTuiChat` is synchronous while `ctx.skills.list()` is async, so autocomplete seeds the static slash commands immediately and rebuilds the provider with `skill:<name>` entries once the catalog resolves; a resolution that arrives after disposal is dropped, and a rejected lookup keeps the base commands.
Autocomplete filters the invocation-neutral `list()` result with `isUserInvocable`, and manual submission applies the same predicate after trusted `get()` resolves the definition. A user-only skill can therefore appear and load even when model invocation is disabled, while a user-disabled skill is neither advertised nor loadable by exact name. Each completion entry is labeled with its winning source's scope — `(project)` for the `project-` sources, `(user)` for every other source — in the slash-command argument-hint slot, which the menu shows but selection never inserts, so trailing instructions still follow the completed name. An unknown name, an empty name after the prefix, a user-disabled name, and a lookup failure each surface as a transcript notice without sending anything.
`renderSkillInvocation` and the resource-base line are the TUI's own, deliberately not reused from `dsh-tool-skill`'s `skill` tool result. The tool wraps a body in `<skill_content>`/`<skill_resources>`/`<skill_instructions>` for a *tool result*; a manual invocation is a *user turn*, and coupling the two renderers would force one model-facing shape to serve both surfaces. The cost is two renderers that both format a skill body; the benefit is that each surface's model-facing text evolves independently, and each is pinned where it is produced.
## Alternatives considered
**Add a `user-invocable` frontmatter field only inside the original TUI change.** Rejected there because a TUI-only field would have changed the registry, provider, and tool contract without a shared invocation model. The later [independent invocation-policy decision](2026-07-28-skill-invocation-policy.md) adds it across every relevant consumer and preserves `get()` as a trusted primitive.
**Declare `skills` as a TUI injection.** Rejected because skills mount conditionally; a declared injection would make the front door require the registry and refuse to mount without it, contradicting the package's optional-service stance. `ctx.get('skills')` reads the global store and tolerates absence.
**Reuse `dsh-tool-skill`'s renderer.** Rejected because its output is a tool-result shape (`<skill_content>` and siblings) written for the model's tool channel, while a slash invocation is a user message. Sharing it would either leak tool-result vocabulary into a user turn or fork the shared renderer on a `surface` flag — more coupling than two small formatters.
**Route submissions through the model's `skill` tool.** Rejected because the user has already decided; a tool call would spend a model round-trip to fetch a body the front door can load directly, and would not work while the agent is mid-turn.
## Consequences
Manual invocation always reloads the full skill body: the TUI does not detect a skill already present in the conversation, so a repeated `/skill:` appends its instructions again — acceptable because re-injection is sometimes the intent, and documented under the package README's Known Limitations. The two-renderer duplication is a standing maintenance cost accepted above. The `<skill name="…">` wrapper is stable model-visible text and is pinned verbatim in package tests against a real `SkillService`; the package semantic matrix pins the help-panel line. Autocomplete population, user-only discovery, delivery to idle and running agents, and the disposed-lookup and failed-lookup branches are covered by package tests that mount the real registry or a controllable service. The removed product TUI's keyless PTY smoke formerly covered the assembled Loader path; a future terminal deployment owns that application-level scenario.

View File

@@ -0,0 +1,34 @@
# Agent Note: TUI skill slash command
Status: implemented
Archived: 2026-08-04
[English](2026-07-21-tui-skill-slash-command.md) | 中文
## Problem
[skill 系统](2026-07-05-skill-system.md)交付时只有模型发起加载这一条路径:`skill({ name })` 工具让模型把某个 skill 正文拉进一个轮次,但操作 TUI 的人无法按需加载 skill。其他编码 agent智能体正是为此提供了 `/skill:<name>` 斜杠命令——由用户而非模型判断某个任务与某个 skill 匹配并注入其指令。skill 系统 note 把直接的用户发起调用列为待办工作,而交互式前门正是它该落地的地方。
## Decision
[`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) 前门拥有一条 `/skill:<name> [instructions]` 命令。提交时它加载指定的 skill并投递一个文本块作为用户轮次——空闲时用 `agent.send()` 发送、运行中用 `agent.steer()` 中途引导,与普通编辑器输入遵循同一规则。该文本块由 `renderSkillInvocation(skill, instructions)` 生成:一个包裹 skill 正文的 `<skill name="…">` 元素,当提供方暴露资源基址时在其前加一行资源基址行,用户尾随的文本在空行之后追加。该命令是 TUI 独有的功能;它不新增任何面向模型的工具。其可见性和加载策略来自共享的[模型与用户独立 skill 调用策略](2026-07-28-skill-invocation-policy.md)。
TUI 通过 `ctx.get('skills')` 读取 skill 服务,而非声明式注入,因为 skill 是条件挂载的:没有注册表的部署仍保有可用的前门,此时 `/skill:` 会报告 skill 不可用,而不是挂载失败。`createTuiChat` 是同步的,而 `ctx.skills.list()` 是异步的,所以自动补全先立即种入静态斜杠命令,待目录解析完成后再用 `skill:<name>` 条目重建 provider提供方在 dispose资源释放之后才到达的解析结果会被丢弃而被拒绝的查找会保留基础命令。
自动补全使用 `isUserInvocable` 过滤与调用策略无关的 `list()` 结果;手动提交则在受信的 `get()` 解析定义后应用相同判定。因此,即使模型调用已禁用,仅供用户调用的 skill 仍会显示并可加载;用户禁用的 skill 既不会展示,也无法按精确名称加载。每个补全条目都以其胜出来源的作用域为标签——`project-` 来源标为 `(project)`,其他一切来源标为 `(user)`——标签置于斜杠命令的参数提示位,菜单会显示它,但选中时绝不会插入,因此尾随指令仍然跟在补全后的名称之后。未知名称、前缀之后为空的名称、用户禁用的名称以及查找失败,都会各自呈现为 transcript文本记录中的一条通知且不发送任何内容。
`renderSkillInvocation` 及资源基址行是 TUI 自有的,刻意不复用 `dsh-tool-skill``skill` 工具结果。该工具把正文包进 `<skill_content>`/`<skill_resources>`/`<skill_instructions>` 是为了一个*工具结果*;而手动调用是一个*用户轮次*,把两个渲染器耦合起来会迫使一种面向模型的形态同时服务两个界面。代价是两个都在格式化 skill 正文的渲染器;收益是各界面面向模型的文本可以独立演进,且各自在其产出处被固定。
## Alternatives considered
**仅在最初的 TUI 变更内新增 `user-invocable` frontmatter 字段。** 当时未采纳,因为 TUI 独有的字段会在没有共享调用模型的情况下改变注册表、提供方和工具契约。后续的[独立调用策略决策](2026-07-28-skill-invocation-policy.md)将其扩展到每个相关消费方,并保留 `get()` 作为受信原语。
**把 `skills` 声明为 TUI 注入。** 否决,因为 skill 是条件挂载的;声明式注入会使前门必须依赖注册表,缺少它就拒绝挂载,与本包可选服务的立场相悖。`ctx.get('skills')` 读取全局存储并容忍其缺失。
**复用 `dsh-tool-skill` 的渲染器。** 否决,因为它的输出是为模型的工具通道所写的工具结果形态(`<skill_content>` 及其同类),而斜杠调用是一条用户消息。共用它要么把工具结果词汇泄漏进用户轮次,要么按 `surface` 标志分叉共享渲染器——比两个小格式化器耦合更重。
**让提交经由模型的 `skill` 工具。** 否决,因为用户已经作出了判断;一次工具调用会花掉一个模型往返去取一份前门可以直接加载的正文,而且在 agent 处于轮次中途时也无法工作。
## Consequences
手动调用总是重新加载完整的 skill 正文TUI 不会检测某个 skill 是否已在对话中出现,因此重复的 `/skill:` 会再次追加其指令——这可以接受,因为重新注入有时正是意图所在,且已在本包 README 的已知限制中说明。上文接受的双渲染器重复是一项长期维护成本。`<skill name="…">` 包装层是稳定的、模型可见的文本,并在包测试中针对一个真实的 `SkillService` 逐字固定;包语义矩阵固定帮助面板中的这一行。自动补全填充、仅限用户的发现、向空闲及运行中 agent 投递,以及 dispose 后查找和查找失败分支,都由挂载真实注册表或可控服务的包测试覆盖。已移除的产品 TUI 的无密钥 PTY 冒烟测试过去覆盖组装后的 Loader 路径;未来的终端部署负责该应用级场景。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-07-23-tui-file-reference-autocomplete.md
2026-07-23-tui-file-reference-autocomplete.md: 3ceda81858bc3364a2c4ac5b91d599b23479412a
2026-07-23-tui-file-reference-autocomplete.zh.md: c0b947edf6df42d27f5fee40d95eb073029e32b1

View File

@@ -0,0 +1,34 @@
# Agent Note: TUI file-reference autocomplete
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-23-tui-file-reference-autocomplete.zh.md)
## Problem
The TUI offered structured `@session` references but no dependable way to discover workspace paths while composing a prompt. Requiring users to remember exact paths made file-oriented requests unnecessarily awkward, while eagerly attaching every selected file would spend context before the model knew whether its contents were relevant and would hide the normal `read` observation from the tool transcript.
## Decision
The TUI owns a bounded, cancellable host-workspace path index rooted at the active session's working directory. Typing `@` at a token boundary fuzzy-matches files and directories; queries containing `/` list the named directory directly, accepting a directory continues completion, and paths containing whitespace use the `@"path with spaces"` form. Configuration controls result count, index size, and excluded directory basenames. The default exclusions are `.git` and `node_modules`; traversal does not follow directory symlinks or interpret ignore files.
Selecting a file changes only the editor text. The submitted user message retains the natural `@path` spelling and carries no injected contents, hidden context, or reference object. When the model-facing `read` tool is registered, the TUI contributes a stable system-prompt section that identifies `@` paths as explicit user references, directs the model to call `read` when contents are needed, and forbids claiming inspection before that call. Tool results invalidate the reusable fuzzy index so subsequent interactions observe likely workspace mutations.
Structured session mentions keep their existing snapshot preparation. Unlike files, a referenced session has no general model-facing retrieval tool, so reducing `@session` to a path-like label would make its content unreachable.
## Alternatives considered
**Eagerly inject selected file contents.** This spends tokens before relevance is known, can capture stale content before execution reaches the reference, and bypasses the auditable `read` call/result sequence.
**Require an external file finder.** Depending on `fd`, `rg --files`, or another executable would make baseline completion vary by host installation and complicate cancellation and cross-platform behavior.
**Use the filesystem service's ordinary directory-list operation for discovery.** That seam is optimized for exact model-facing filesystem operations and may represent a remote namespace; recursive fuzzy indexing would multiply provider round trips and couple editor latency to tool policy. Host-side discovery keeps the terminal interaction local, while the documented namespace-alignment limitation remains explicit for non-local deployments.
**Add a new cross-package file-search capability.** The TUI is the only current consumer and the behavior is editor presentation rather than a model capability, so a new interface, implementation, and consumer package set would split the seam prematurely.
## Consequences
Users can discover and insert paths without making selection itself expensive or model-visible beyond the path. The model preserves agency over whether to inspect a file, and any inspection remains reconstructable through the logged tool transcript. The fixed instruction slightly enlarges TUI system prompts when `read` is present, and content-requiring requests take an additional tool round trip.
Completion is deliberately bounded and advisory: very large workspaces may omit paths beyond the configured index cap, ignored files may still appear, and remote or virtual filesystem deployments must align the TUI host working directory with the `read` namespace or supply a different completion surface. Package tests pin token grammar, ranking, bounds, cancellation, invalidation, path-only submission, the visible menu, and keyboard completion; a deployment shipping the TUI owns its Loader and PTY acceptance.

View File

@@ -0,0 +1,34 @@
# Agent Note: TUI 文件引用自动补全
Status: implemented
Archived: 2026-08-04
[English](2026-07-23-tui-file-reference-autocomplete.md) | 中文
## 问题
TUI 提供结构化的 `@session` 引用,但用户在编辑提示词时无法可靠地发现工作区路径。要求用户记住准确路径会给面向文件的请求带来不必要的麻烦;如果直接附加每个选中文件,则会在模型判断其内容是否相关之前占用上下文,并在工具 transcript文本记录中隐藏常规的 `read` 观察结果。
## 决策
TUI 维护一个有容量上限且可取消的主机工作区路径索引,以活跃会话的工作目录为根。在 token 边界输入 `@` 会对文件和目录进行模糊匹配;查询包含 `/` 时会直接列出指定目录,接受目录后会继续补全,包含空白的路径采用 `@"path with spaces"` 形式。配置项控制结果数量、索引大小以及排除的目录基名。默认排除 `.git``node_modules`;遍历既不跟随目录符号链接,也不解析忽略文件。
选择文件只会改变编辑器文本。提交的用户消息保留自然的 `@path` 写法,不携带注入的内容、隐藏上下文或引用对象。注册面向模型的 `read` 工具时TUI 会加入一个稳定的系统提示词段,说明 `@` 路径是用户的显式引用,指示模型在需要内容时调用 `read`,并禁止模型在调用前声称已检查文件。工具结果会使可复用的模糊索引失效,后续交互因而能看到工作区中可能发生的变更。
结构化会话提及保留现有的快照准备方式。与文件不同,被引用的会话没有通用的模型侧检索工具;如果把 `@session` 简化为类似路径的标签,模型将无法获取其内容。
## 备选方案
**直接注入选中文件的内容。** 这种方式会在确定相关性前消耗 token可能在执行到该引用前捕获到陈旧内容并绕过可审计的 `read` 调用与结果序列。
**要求使用外部文件查找器。** 依赖 `fd``rg --files` 或其他可执行文件,会使基础补全行为随主机安装情况而变化,也会增加取消处理和跨平台支持的复杂度。
**使用文件系统服务的常规目录列表操作进行发现。** 该 seam 针对面向模型的准确文件系统操作进行了优化,并且可能表示远程命名空间;递归模糊索引会增加提供方往返次数,并使编辑器延迟与工具策略耦合。主机侧发现让终端交互保留在本地,同时文档仍明确说明非本地部署中的命名空间对齐限制。
**新增跨包的文件搜索功能。** TUI 是目前唯一的消费方,而且该行为属于编辑器呈现而非模型功能;新增一组接口、实现和消费方包会过早拆分这条 seam。
## 影响
用户可以发现并插入路径,而选择操作本身不会带来高开销,对模型可见的内容也仅限路径。模型仍可自行决定是否检查文件,任何检查都能通过已记录的工具 transcript 重建。存在 `read` 时,固定指令会略微增大 TUI 系统提示词;需要文件内容的请求还会增加一次工具往返。
补全有意采用有界的提示性设计:超大型工作区可能省略超过配置索引上限的路径,被忽略的文件仍可能出现,远程或虚拟文件系统部署必须让 TUI 的主机工作目录与 `read` 命名空间对齐否则需要提供不同的补全接口。包package测试固定 token 语法、排序、边界、取消、失效、仅提交路径的行为、可见菜单和键盘补全;交付 TUI 的部署负责其 Loader 与 PTY 验收。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-07-23-tui-status-prompt-tools.md
2026-07-23-tui-status-prompt-tools.md: b8e5e9fc4fd8f9d6dafa689512ef991ee1013b39
2026-07-23-tui-status-prompt-tools.zh.md: 3d10073b4332609371fdd1ebed7486970b439251

View File

@@ -0,0 +1,30 @@
# Agent Note: TUI status inspects model request inputs
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-23-tui-status-prompt-tools.zh.md)
## Problem
Session counters describe activity but do not reveal the instructions and capabilities that the next model request receives. Diagnosing scoped prompt contributions and tool restrictions otherwise requires leaving the TUI or inferring configuration from files.
## Decision
`/status` assembles the current agent's system prompt through `ctx.systemPrompt` and renders it with the same renderer used by the agent loop. After the bordered diagnostics card, separate unbordered `System prompt` and `Registered tools` sections show the rendered prompt and the assembly's ordered tool names, which are the schemas exposed to the model for that agent and presentation mode.
Assembly uses the command's cancellation signal and current agent scope, so scoped sections, variables, tool restrictions, and assembly listeners match a request made at that point. Prompt and tool values are escaped through the TUI's terminal-control sanitizer before rendering. Empty prompt text and an empty tool list render as `(empty)` and `(none)`.
## Alternatives considered
**Read prompt sections and the tool registry independently.** Rejected: that bypasses prompt assembly waterfalls, tool ordering, presentation modes, and per-agent restrictions, so the diagnostics could disagree with the next request.
**Show complete tool schemas.** Rejected: names answer which capabilities are registered without making the status card dominated by parameter JSON; schema details remain available in the generated tool catalog and source definitions.
## Consequences
The command can run prompt providers and assembly listeners, just like request preparation, and reports their failures through the existing command-error notice. The snapshot is point-in-time: a later registration, restriction, mode change, or dynamic provider can alter the next request.
## Testing
Package behavior tests pin scoped assembly output, ordered tool names, empty labels, and terminal-control escaping. The package semantic snapshots exercise `/status` at normal and narrow widths; a deployment shipping the TUI owns its assembled process acceptance.

View File

@@ -0,0 +1,30 @@
# Agent Note: TUI 状态检查模型请求输入
Status: implemented
Archived: 2026-08-04
[English](2026-07-23-tui-status-prompt-tools.md) | 中文
## 问题
会话计数器可以描述活动情况,却无法显示下一次模型请求将收到的指令和能力。若要诊断按作用域贡献的提示词与工具限制,用户只能离开 TUI或根据配置文件进行推断。
## 决策
`/status` 通过 `ctx.systemPrompt` 为当前 agent智能体组装系统提示词并使用与 agent loop智能体循环相同的渲染器完成渲染。在带边框的诊断卡片之后独立且无边框的 `System prompt``Registered tools` 区域分别显示渲染后的提示词与 assembly 中按顺序排列的工具名称;这些名称对应当前 agent 与呈现模式向模型公开的 schema。
组装使用命令的取消信号和当前 agent 作用域,因此按作用域注册的 section、变量、工具限制及 assembly listener 与此时发起的请求保持一致。提示词和工具值在呈现前经过 TUI 的终端控制字符净化。空提示词与空工具列表分别显示为 `(empty)``(none)`
## 曾考虑的替代方案
**分别读取提示词 section 和工具注册表。** 已否决:该做法会绕过提示词组装 waterfall瀑布式事件、工具排序、呈现模式和按 agent 限制,因此诊断结果可能与下一次请求不一致。
**显示完整工具 schema。** 已否决:工具名称足以回答注册了哪些能力,同时避免参数 JSON 占据大部分状态卡片schema 详情仍可在生成的工具目录和源代码定义中查看。
## 后果
该命令可能像请求准备一样运行提示词提供方与 assembly listener并通过现有命令错误提示报告失败。结果是一个时点快照后续注册、限制、模式变更或动态提供方都可能改变下一次请求。
## 测试
package行为测试固定按作用域组装的输出、工具名称顺序、空值标签和终端控制字符转义。包级语义快照在正常宽度和窄宽度下执行 `/status`;交付 TUI 的部署负责其组装后的进程验收。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-07-24-configurable-tui-prompt-theme.md
2026-07-24-configurable-tui-prompt-theme.md: 918eaa617811049a5caa4b9f43a8cc12854471c3
2026-07-24-configurable-tui-prompt-theme.zh.md: eccfed00c3fe866291d2f18bd7cecf117296bd58

View File

@@ -0,0 +1,40 @@
# Agent Note: TUI prompt themes compose mutable plugin values
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-24-configurable-tui-prompt-theme.zh.md)
## Problem
The terminal prompt row and editor prefix were assembled inside the TUI from a fixed set of workspace, model, usage, cache, context, and timing fields. Deployments could change colors globally but could not choose field order, replace the input prefix, add plugin state, or build a Powerline prompt.
## Decision
The TUI theme groups `color`, `truecolor`, `leftPrompt`, `rightPrompt`, `inputPrompt`, and the static running-state `inputPlaceholder`. The three prompt strings interpolate `${name}` references; unknown or unavailable values disappear with adjacent horizontal separator whitespace. The left and right templates share one row, retain the right side on overlap, and use ANSI-aware visible widths. The input template controls the first-line editor prefix and continuation indentation.
`ctx.tuiPrompt` is a context-global registry supplied by `@deepseek-ai/dsh-tui/prompt`. `register(name, initialValue)` returns a handle with `set(value)` and `dispose()`. Values are stored strings rather than callbacks: updates are explicit, unchanged strings are ignored, and a registration, mutation, or disposal schedules one coalesced notification. The renderer reads current values with `get(name)` and subscribes with `subscribe(listener)` to learn when to redraw. That subscription is a direct in-service callback, not a Cordis event, so a value changing on its own schedule still repaints without a bus entry other consumers would never use. Both `subscribe` and each registration are owned by the caller's Cordis effect, so they are removed when the subscriber's or contributor's fiber disposes. Each `subscribe` call is a distinct subscription keyed by record identity, so two fibers may pass the same callback and disposing one leaves the other live. The coalesced notification contains every observer — a synchronous throw, a rejected returned promise, and even an error hostile to string rendering (logs go through the non-throwing `errorChain`) — so one broken observer cannot starve the rest, and it re-checks each subscription's liveness during delivery so a listener that synchronously unsubscribes another in the same burst silences it immediately. Registration follows Cordis effect ownership, rejects duplicate names, and removes the value on plugin disposal.
Registered fragments are trusted ANSI-capable presentation output. Template literals and ordinary external content remain sanitized, but a prompt-value plugin may emit terminal controls. Composite values own coordinated background transitions and separators, so one `${powerline}` value can render a complete Powerline segment without coupling adjacent atomic providers.
The built-in `cwd`, `git/worktree`, `token_meter/cache_hit_rate`, `model`, `context`, `queued`, styled `symbol` label, and `indicator` caret values use the same registry. Session and agent events update their handles, while the running timer updates `queued` — the steering-queue badge, unavailable unless a running turn has queued messages — and the animated `indicator` each tick. The shipped input template is `${symbol} ${indicator}`, preserving the existing `dsh > ` prefix.
## Alternatives considered
**Evaluate synchronous provider callbacks on every render.** Rejected: render-time plugin code adds an avoidable failure boundary; stored strings keep the render pass free of plugin evaluation.
**Publish the change notification as a Cordis event.** Rejected: the notification has exactly one consumer (the TUI renderer for the current session), so a global typed event adds a bus entry, scoped-dispatch surface, and cross-plugin fan-out no one else observes. A direct `subscribe` callback contained inside the service carries the same coalesced redraw with less surface.
**Expose semantic style roles instead of ANSI.** Rejected: semantic roles cannot express arbitrary Powerline background transitions without expanding the shared style protocol for each presentation technique.
**Put prompt fields at the top level of TUI config.** Rejected: templates and color selection jointly define terminal presentation and belong under one `theme` object.
## Consequences
Prompt contributors depend on the TUI-specific registry and are loaded after the service but before the TUI consumer. The namespace is global to the Cordis context, matching the TUI's current single-session transcript ownership. Arbitrary ANSI is intentionally trusted: unsupported cursor-affecting sequences can disrupt layout, and alignment is reliable only for sequences understood by pi-tui's visible-width utilities.
Changing `inputPrompt` through a registered value preserves editor text, cursor, history, completion, and focus because pi-tui supports replacing equal-width first and continuation prefixes in place. The static `inputPlaceholder` is sanitized and appears only while the agent runs and the editor is empty.
## Testing
Registry tests pin validation, duplicate rejection, updates, unavailable values, coalesced-notification containment, unsubscribe, disposal, interpolation, trailing-literal retention, whitespace cleanup, and ANSI preservation. TUI package tests pin service availability, nested theme defaults, config forwarding, custom templates, out-of-band value redraw, mutable redraw, Powerline-capable fragments, dynamic input-prefix width, and the static running placeholder. A deployment shipping the TUI owns assembled load-order acceptance.

View File

@@ -0,0 +1,40 @@
# Agent Note: TUI 提示符主题组合可变的插件值
Status: implemented
Archived: 2026-08-04
[English](2026-07-24-configurable-tui-prompt-theme.md) | 中文
## 问题
终端提示符行与编辑器前缀原先在 TUI 内部由一组固定字段拼装而成,涵盖工作区、模型、用量、缓存、上下文与计时。部署方可以全局更改颜色,却无法调整字段顺序、替换输入前缀、加入插件状态,也无法构建 Powerline 风格的提示符。
## 决策
TUI 主题把 `color``truecolor``leftPrompt``rightPrompt``inputPrompt` 以及运行状态下的静态 `inputPlaceholder` 归为一组。三个提示符字符串通过插值引用 `${name}`;未知或不可用的值连同相邻的横向分隔空白一起消失。左右模板共用一行,重叠时保留右侧,宽度计算使用可识别 ANSI 的可见宽度。输入模板控制编辑器首行前缀与续行缩进。
`ctx.tuiPrompt` 是由 `@deepseek-ai/dsh-tui/prompt` 提供的上下文全局注册表。`register(name, initialValue)` 返回带 `set(value)``dispose()` 的句柄。存储的值是字符串而非回调:更新必须显式发起,未变化的字符串会被忽略,而一次注册、变更或 dispose 会安排一次合并后的通知。渲染器用 `get(name)` 读取当前值,并用 `subscribe(listener)` 订阅何时重绘。该订阅是服务内部的直接回调,而非 Cordis 事件,因此一个自行变化的值仍能重绘,而不需要一个其他消费方永远不会观察的总线条目。`subscribe` 与每个注册都由调用方的 Cordis effect 拥有,因此在订阅方或贡献方的 fiber dispose 时一并移除。每次 `subscribe` 都是一个按记录身份区分的独立订阅,因此两个 fiber 可以传入同一个回调,而 dispose 其中一个不会影响另一个。合并通知会容错每个观察者——同步抛出、返回被拒 promise甚至一个对字符串渲染也会抛异常的错误日志走不抛异常的 `errorChain`)——因此一个损坏的观察者不会饿死其余观察者;并且在派发过程中会重新校验每个订阅的存活性,因此同一批次中同步取消了另一个订阅的监听器会立即使其静默。注册遵循 Cordis 的 effect 所有权模型,拒绝重复名称,并在插件 dispose资源释放时移除对应的值。
注册的片段被视为可信的、允许携带 ANSI 的呈现输出。模板中的字面文本与普通外部内容仍会被清洗,但提供提示符值的插件可以输出终端控制序列。复合值自行负责协调背景色过渡与分隔符,因此一个 `${powerline}` 值就能渲染完整的 Powerline 段,而无需与相邻的原子提供方耦合。
内置的 `cwd``git/worktree``token_meter/cache_hit_rate``model``context``queued`、带样式的 `symbol` 标签与 `indicator` 光标符值使用同一个注册表。会话与 agent智能体事件更新各自的句柄运行计时器每一拍更新 `queued`——转向队列徽标,仅在运行中的一轮有排队消息时才可用——与带动画的 `indicator`。随附的输入模板为 `${symbol} ${indicator}`,保留了原有的 `dsh > ` 前缀。
## 曾考虑的替代方案
**每次渲染时求值同步的提供方回调。** 不予采纳:在渲染期执行插件代码会引入一个本可避免的故障边界;存储字符串能让渲染过程不涉及插件求值。
**把变更通知发布为 Cordis 事件。** 已否决:该通知只有一个消费方(当前会话的 TUI 渲染器因此全局类型事件会增加一个总线条目、scope 分发面以及无人观察的跨插件扇出。服务内部包裹的直接 `subscribe` 回调以更小的面积承载同样的合并重绘。
**暴露语义化的样式角色而非 ANSI。** 不予采纳:语义角色无法表达任意的 Powerline 背景色过渡,除非为每种呈现技巧扩展共享的样式协议。
**把提示符字段放在 TUI 配置顶层。** 不予采纳:模板与颜色选择共同定义终端呈现,应归属于同一个 `theme` 对象之下。
## 后果
提示符值的贡献插件依赖 TUI 专属的注册表加载顺序位于该服务之后、TUI 消费方之前。命名空间对整个 Cordis 上下文全局生效,与 TUI 当前的单会话 transcript文本记录所有权一致。允许任意 ANSI 是有意的信任决策:不受支持的、影响光标的序列可能破坏布局,只有 pi-tui 可见宽度工具能理解的序列才能保证对齐可靠。
通过注册值更改 `inputPrompt` 时,编辑器文本、光标、历史、自动补全与焦点均得以保留,因为 pi-tui 支持原地替换等宽的首行与续行前缀。静态的 `inputPlaceholder` 会被清洗,且仅在 agent 运行且编辑器为空时显示。
## 测试
注册表测试固定校验、重名拒绝、更新、不可用值、合并通知的容错、取消订阅、dispose、插值、尾随字面保留、空白清理与 ANSI 保留等行为。TUI 包package测试固定服务可用性、嵌套主题默认值、配置转发、自定义模板、带外值重绘、可变重绘、支持 Powerline 的片段、动态输入前缀宽度以及运行状态下的静态占位文本。交付 TUI 的部署负责组装后的加载顺序验收。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-07-24-tui-question-dialog-multiline.md
2026-07-24-tui-question-dialog-multiline.md: 9f1e75325942f28d69d9cadadf938c0f0b5e3ae5
2026-07-24-tui-question-dialog-multiline.zh.md: bd112898367e7a7dd940f2b5fa76e531029ab803

View File

@@ -0,0 +1,42 @@
# Agent Note: TUI QuestionDialog renders options across multiple lines
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-24-tui-question-dialog-multiline.zh.md)
## Problem
`ctx.userInteraction.ask()` must keep question text, supporting `detail`, option labels, descriptions, validation, and controls readable inside configured width and height bounds. The question panel also belongs directly above the editor: placing it at the terminal edge separates the pending decision from both the transcript that prompted it and the input that follows it.
## Decision
The TUI renders a pending question as an inline modal between the transcript/status area and the editor while retaining the shared FIFO with model and plugin overlays:
- `InlineModalComponent` applies `questionDialogWidth` and `questionDialogMaxHeight` inside the normal component flow. The effective question height is additionally clamped to the current viewport after reserving the editor, so the editor remains below the question during resize.
- `renderOptionBlock` wraps each label beneath its cursor/number prefix and renders the muted description on separately wrapped, equally indented lines. The progress header, question, custom-answer hint, validation text, and final rows are width-bounded as well; the final ellipsis clamp is only a safety boundary for prefixes or other indivisible content. The explicit `↑ N lines hidden` fallback is reserved for a viewport below the configured minimum, where the whole semantic layout cannot fit.
- When question text or `detail` exceeds the header allocation, the header becomes a paged line viewport with its own `… lines A-B/N • PgUp/PgDn` status row. Page Up and Page Down traverse both line viewports: forward navigation exhausts the header/detail pages before entering oversized selected-option pages, and backward navigation reverses that order. This keeps plan-review detail reachable rather than leaving it behind the height clamp.
- The option-line budget subtracts padding, header, position, and footer rows before `windowBlocks` runs. The window obeys both `maxQuestionOptions` and the remaining row budget, keeps the selected option visible, and renders omitted options as `↑ N more` / `↓ N more` markers. If fixed chrome would leave fewer than four option rows, the compact header becomes the line pager so selected content, paging status, and both option markers still fit.
- When one selected block exceeds its allocation, it becomes a line viewport with a `lines A-B/N • PgUp/PgDn` status row. Page Up and Page Down expose every wrapped line without allowing the block to hide the option markers, validation, or controls.
Package tests pin count and height bounds, header and selected-block paging order, narrow-width wrapping, selection behavior, and placement relative to retained editor input. Semantic TUI snapshots pin the assembled terminal layout, header/detail and selected-option page transitions, and validation state.
## Alternatives considered
**Ellipsis-only horizontal truncation.** Keeping one option per row would signal lost text without making the description readable and would not address vertical bounds. The implementation wraps readable content and retains an ellipsis only as a final safety boundary.
**Wrap the combined label and description.** A composite row couples their widths, so either side can starve the other. Separate lines keep both widths predictable.
**Keep the question as a bottom-edge overlay.** A terminal-edge anchor can place the panel after the editor or cover lower chrome, depending on transcript and viewport height. The inline modal preserves ordering while the modal manager retains focus and FIFO ownership.
**Push the bounds into pi-tui.** Generic overlay slicing cannot identify option boundaries, selected content, controls, or the inline editor relationship. The owning dialog therefore applies semantic count, row, and paging rules.
**Use only the option-count cap.** `maxQuestionOptions` remains a public count bound, but it cannot contain wrapped blocks by itself. The dialog enforces the count and row bounds together.
## Consequences
- Descriptions consume additional rows, so fewer options can be visible than `maxQuestionOptions`; markers state the omitted option counts.
- Long question text and plan-review detail remain reachable inside a height-bounded panel, at the cost of sharing Page Up and Page Down with selected-option paging.
- An oversized selected block reserves one status row and requires Page Up or Page Down to read beyond the current line page.
- The inline question can displace older transcript rows from a short viewport. Below the configured minimum height, the final fallback can collapse upper rows behind an explicit hidden-line marker so the input controls and editor remain available.
- The model-facing schema, selected labels, abort/cancel behavior, and ACP elicitation path are unchanged.

View File

@@ -0,0 +1,42 @@
# Agent Note: TUI QuestionDialog 以多行方式渲染选项
Status: implemented
Archived: 2026-08-04
[English](2026-07-24-tui-question-dialog-multiline.md) | 中文
## 问题
`ctx.userInteraction.ask()` 必须确保问题正文、`detail` 补充内容、选项标签、描述、校验信息和控件在已配置的宽度与高度边界内均可读。问题面板也直接位于编辑器上方:若将其置于终端边缘,待处理决策就会同时脱离触发该决策的 transcript文本记录和后续输入。
## 决策
TUI 将待处理问题渲染为位于 transcript状态区域与编辑器之间的内联模态框同时仍与模型浮层和插件浮层共享 FIFO
- `InlineModalComponent` 在正常组件流内应用 `questionDialogWidth``questionDialogMaxHeight`。系统在为编辑器预留空间后,还会根据当前视口限制问题的实际高度,因此调整窗口大小时,编辑器仍位于问题下方。
- `renderOptionBlock` 将每个标签换行到光标/编号前缀下方,并在另行换行且缩进相同的行上渲染弱化的描述。进度标题、问题、自定义答案提示、校验文本和末尾行也受宽度边界约束;最终的省略号截断仅作为前缀或其他不可拆分内容的安全边界。明确的 `↑ N lines hidden` 回退仅用于低于已配置最小值、无法容纳完整语义布局的视口。
- 当问题正文或 `detail` 超出头部分配的空间时,头部会成为带有独立 `… lines A-B/N • PgUp/PgDn` 状态行的分页行视口。Page Up 和 Page Down 会遍历这两个行视口:向前导航先翻完问题正文/`detail` 页面,再进入超大选中选项页面;向后导航则采用相反顺序。这样可确保计划评审的 `detail` 内容始终可达,而不会被高度边界挡住。
-`windowBlocks` 运行前,选项行预算会扣除内边距、标题行、位置行和页脚行。窗口同时遵守 `maxQuestionOptions` 和剩余行预算,保持选中项可见,并将省略的选项渲染为 `↑ N more``↓ N more` 标记。若固定界面元素会使选项行少于四行,紧凑头部会转为行分页器,从而容纳选中内容、分页状态和上下两个选项标记。
- 当一个选中块超出分配空间时,它会成为带有 `lines A-B/N • PgUp/PgDn` 状态行的行视口。Page Up 和 Page Down 可展示每一行已换行内容,同时防止该块遮住选项标记、校验信息或控件。
package测试固定数量和高度边界、头部与选中块的分页顺序、窄宽度换行、选择行为以及问题相对于保留的编辑器输入的位置。语义 TUI 快照固定组装后的终端布局、头部/详情与选中选项的分页转换,以及校验状态。
## 备选方案
**仅用省略号进行横向截断。** 保持每个选项占一行,只能提示文本有所丢失,无法使描述变得可读,也无法处理纵向边界。该实现会对可读内容换行,仅将省略号保留为最终安全边界。
**将标签与描述合并后换行。** 组合行会将两者的宽度耦合在一起,任一方都可能挤占另一方的空间。分行渲染可使二者的宽度保持可预测。
**将问题保留为终端底边浮层。** 根据 transcript 和视口高度,锚定在终端边缘的面板可能出现在编辑器之后,也可能遮盖下方界面元素。内联模态框可保留顺序,同时由模态管理器继续负责焦点和 FIFO 所有权。
**将边界处理下推至 pi-tui。** 通用浮层切片无法识别选项边界、选中内容、控件或内联编辑器关系。因此,负责该语义的对话框会应用数量、行数和分页规则。
**仅使用选项数量上限。** `maxQuestionOptions` 仍是公开的数量边界,但仅靠它无法容纳已换行的块。对话框会同时执行数量边界和行数边界。
## 后果
- 描述会占用额外行,因此可见选项数可能少于 `maxQuestionOptions`;标记会说明省略的选项数量。
- 较长的问题正文和计划评审 `detail` 在受高度约束的面板内仍然可达,代价是 Page Up 和 Page Down 需要与选中选项分页共用。
- 超出空间的选中块会预留一行状态信息;若要阅读当前页面之外的行,必须使用 Page Up 或 Page Down。
- 在较矮的视口内,内联问题可能将较早的 transcript 行挤出可见区域。低于已配置最小高度时,最终回退可能将上部行折叠到明确的隐藏行标记之后,从而让输入控件和编辑器仍然可用。
- 面向模型的 schema、选中的标签、中止取消行为以及 ACPAgent Client Protocol的 elicitation 路径均保持不变。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-07-24-tui-shell-prompt-editor.md
2026-07-24-tui-shell-prompt-editor.md: a3707d3fcd0ae30517083ed388eb38cb3b92cad7
2026-07-24-tui-shell-prompt-editor.zh.md: ca050e2000ff772389086b5023935c816ec1154e

View File

@@ -0,0 +1,34 @@
# Agent Note: TUI shell-prompt editor
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-24-tui-shell-prompt-editor.zh.md)
## Problem
The upstream pi-tui editor always renders horizontal frame rows. That presentation separates input from the transcript but occupies two terminal rows and does not resemble the command-oriented input used by shells.
## Decision
The TUI presents a two-line prompt. A DSH-owned context line shows the working directory, running-turn timing, optional Git branch, current model, token totals, cache hit rate, and context pressure as independently prioritized segments. Narrow terminals omit lower-priority segments while retaining the directory, followed by running timing when it is present. The second line uses a fixed-width `dsh> ` prefix and equal-width continuation indent; its running steer/cancel guidance is placeholder text that disappears when input begins.
The pinned `@earendil-works/pi-tui` package carries a pnpm patch that adds `frame: "none"` and fixed-width prompt prefixes to `EditorOptions`. The default remains the upstream horizontal frame, so only the DSH editor opts into the behavior. Prefixes must have equal visible widths; construction fails when they differ. Input, explicit newlines, autocomplete, cursor placement, and scroll indicators share the reduced first-row width; automatically wrapped rows render no prefix, so their text starts at the editor's left padding, occupies the prefix columns, and wraps at the full content width.
The patch stays limited to the published editor JavaScript and declarations. Keeping the exact dependency pin makes installation either apply the known patch or fail rather than silently dropping the presentation.
## Alternatives considered
**Filter the rendered editor output in a wrapper.** This would depend on recognizing ANSI-styled border and scroll-indicator rows and distinguishing autocomplete output from input output, all of which are undocumented render details.
**Vendor the complete pi-tui package.** The project updates frequently, while this change needs only a localized editor rendering option. Owning the full source and synchronization process would add disproportionate maintenance.
**Keep the horizontal frame.** This avoids dependency customization but retains the presentation the change is intended to replace.
## Consequences
The editor and context use two rows instead of the framed editor plus footer, with one blank row separating the prompt area from conversation cards. The persistent presentation omits session identity and tool-card mode; `/status` and commands retain those details. Input layout and autocomplete lose six columns to the prompt prefix, but wrapped text uses the otherwise blank prefix columns. Borderless scrolling uses standalone `↑ N more` and `↓ N more` rows.
The internal segment representation establishes width priorities without exposing a public customization language. Future Starship-like configuration can build on it after the default modules and overflow behavior have production evidence.
A pi-tui upgrade requires reviewing and reapplying or retiring the patch. TUI terminal snapshots pin the assembled presentation, including context modules, prompt color, alignment, cursor placement, and autocomplete width.

View File

@@ -0,0 +1,34 @@
# Agent Note: TUI shell 提示符编辑器
Status: implemented
Archived: 2026-08-04
[English](2026-07-24-tui-shell-prompt-editor.md) | 中文
## 问题
上游 pi-tui 编辑器始终渲染横向边框行。这种呈现方式虽然把输入区与 transcript文本记录分隔开却占用两行终端高度也不像 shell 中面向命令的输入形态。
## 决策
TUI 呈现两行提示符。DSH 自有的上下文行把工作目录、运行中轮次的计时、可选的 Git 分支、当前模型、token 总量、缓存命中率与上下文压力显示为各自独立分配优先级的段segment。窄终端会省略低优先级的段但保留目录运行中计时存在时其保留优先级仅次于目录。第二行使用固定宽度的 `dsh> ` 前缀与等宽的续行缩进agent 运行期间提示 steering中途引导与取消的引导文字是占位文本开始输入后即消失。
固定版本的 `@earendil-works/pi-tui`package携带一个 pnpm 补丁,为 `EditorOptions` 增加 `frame: "none"` 与固定宽度的提示符前缀。默认值仍是上游的横向边框,因此只有 DSH 编辑器选择启用该行为。两个前缀的可见宽度必须相等;宽度不同时构造会失败。输入、显式换行、自动补全、光标定位和滚动指示共用缩减后的首行宽度;自动折行产生的行不渲染前缀,其文本从编辑器左侧留白处开始,占用前缀列,并按完整内容宽度折行。
补丁范围仅限已发布的编辑器 JavaScript 与类型声明。依赖保持精确的版本固定,使安装要么应用已知补丁,要么直接失败,而不会静默丢掉这种呈现方式。
## 曾考虑的替代方案
**在包装层过滤编辑器的渲染输出。** 这需要识别带 ANSI 样式的边框行与滚动指示行,并区分自动补全输出与输入输出,而这些都是未见于文档的渲染细节。
**vendor 完整的 pi-tui 包。** 该项目更新频繁,而本次改动只需要一个局部的编辑器渲染选项。接手全部源码及其同步流程会带来不成比例的维护成本。
**保留横向边框。** 这可以避免定制依赖,但保留的正是本次改动想要替换的呈现方式。
## 后果
编辑器与上下文共占两行,取代原先带边框的编辑器加页脚,提示符区域与对话卡片之间以一行空行分隔。常驻呈现不含会话标识与工具卡片模式;`/status` 与各命令仍保留这些细节。输入布局与自动补全因提示符前缀占位而损失六列宽度,但折行后的文本会占用原本留空的前缀列。无边框滚动使用独立的 `↑ N more``↓ N more` 行。
段的内部表示确立了宽度优先级,而未暴露公开的定制语言。待默认模块与溢出行为积累生产环境证据后,未来可在其上构建类似 Starship 的配置。
升级 pi-tui 时需要评审该补丁并重新应用或将其退役。TUI 终端快照固定组装后的呈现效果,包括上下文模块、提示符颜色、对齐、光标定位和自动补全宽度。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.md
2026-07-27-assistant-timing-header-trailing.md: 2ef926cff989499aa05ebfa781eea10399c69d18
2026-07-27-assistant-timing-header-trailing.zh.md: 282ac1e65b3c4e185839d3c6b70a085f685e8013

View File

@@ -0,0 +1,26 @@
# Agent Note: Assistant timing line renders after the message body
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-27-assistant-timing-header-trailing.zh.md)
## Problem
The TUI assistant message opened with a single header line joining the `Assistant` label and the step-timing string (`Assistant · Model wait 0.0s · Completed …`). Placing the timing before the body pushed the durations away from the answer they describe and, once completed, buried the reply's first line under a metadata line the reader scans past.
## Decision
**Split the label from the timing; render the timing as the message's trailing line.**
`AssistantMessageComponent` (packages/ui/tui/src/index.ts) now emits the bold `Assistant` label as the first line and appends the dim timing string (already assembled by `StreamingAssistantComponent.rebuild()` as `header`, including the `· Completed …` suffix when settled) as the last child, after reasoning and text. The timing content, bucket-hiding, and completion-time behavior are unchanged — only its position moved from the top to the bottom of the message.
## Alternatives considered
**Move the whole header line (label included) to the end.** Rejected: the `Assistant` label orients the reader to who is speaking and belongs at the top like the `You` label; only the timing metadata benefits from trailing placement.
**Keep the timing inline but below the label as a second top line.** Rejected: that still separates the durations from the completed answer and keeps two metadata lines between the prompt and the reply.
## Consequences
Each assistant message reads label → reasoning → answer → timing, so completed timing sits next to the reply it measures. The keyless TUI snapshot suite was refreshed to pin the new layout across every fixture; four `tui.spec.ts` assertions that matched the old inline `Assistant · Model wait …` string now assert the label and timing separately, since the two no longer render contiguously.

View File

@@ -0,0 +1,26 @@
# Agent Note: Assistant timing line renders after the message body
Status: implemented
Archived: 2026-08-04
[English](2026-07-27-assistant-timing-header-trailing.md) | 中文
## Problem
TUI 的助手消息此前以一行开头,把 `Assistant` 标签和步骤计时串拼在一起(`Assistant · Model wait 0.0s · Completed …`)。计时放在正文之前,使耗时数据远离它所描述的回答;一旦完成,回复的首行还被读者会略过的元数据行压在下面。
## Decision
**把标签与计时拆开;计时作为消息的末行渲染。**
`AssistantMessageComponent`packages/ui/tui/src/index.ts现在把加粗的 `Assistant` 标签作为首行,并把暗色的计时串(仍由 `StreamingAssistantComponent.rebuild()` 组装为 `header`settled 时含 `· Completed …` 后缀)作为最后一个子节点,追加在 reasoning 与正文之后。计时内容、隐藏零值桶以及完成时间的行为均不变——仅位置从消息顶部移到底部。
## Alternatives considered
**把整行表头(含标签)都移到末尾。** 否决:`Assistant` 标签让读者知道是谁在说话,应与 `You` 标签一样置顶;只有计时这类元数据才受益于置底。
**计时仍内联,但作为标签下方的第二行置顶。** 否决:这仍把耗时数据与完成的回答分离,并在提示与回复之间保留两行元数据。
## Consequences
每条助手消息按 标签 → reasoning → 回答 → 计时 阅读,完成计时紧挨它所度量的回复。无密钥的 TUI 快照套件已刷新,在每个 fixture 中固定新布局;`tui.spec.ts` 中四处原先匹配旧内联串 `Assistant · Model wait …` 的断言,现改为分别断言标签与计时,因为两者不再连续渲染。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-07-27-tui-running-glyph-smooth-fade.md
2026-07-27-tui-running-glyph-smooth-fade.md: 09a944a5e39d407713da5665a3dd32a171a04bc1
2026-07-27-tui-running-glyph-smooth-fade.zh.md: 5fc6a7e490670ce02d011651741a952cfb97dcce

View File

@@ -0,0 +1,38 @@
# Agent Note: Dim-gray pulse for the running prompt glyph
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-27-tui-running-glyph-smooth-fade.zh.md)
## Problem
While a turn runs, the TUI replaces the `>` prompt caret with a phase glyph (`◍`/`✻`/`●`/`⚙`). Earlier iterations animated its brightness in the accent blue (a discrete SGR wave, then a truecolor throb) — a colored, always-pulsing indicator. The desired effect keeps the continuous pulse to signal ongoing work, but as a quiet dim gray rather than a color, and with smooth fade-in and fade-out at its edges.
## Decision
The running glyph is a dim gray that fades in on turn start, throbs continuously while the turn runs, and fades out after it ends before the plain `>` caret returns. It is never the accent color.
Brightness is a fade envelope times a running throb. The envelope gates appear/disappear, linear in the render clock over `STATUS_FADE_MS = 300`: `(now startedAt)/FADE` clamped for fade-in, `1 (now endedAt)/FADE` for fade-out. `pulseLevel` is a cosine between `STATUS_PULSE_FLOOR` (0) and 1 over `STATUS_PULSE_PERIOD_MS = 1400`, so each breath swells from fully invisible to full and back. The truecolor opacity handed to `fadeGlyph` is `envelope × pulse`.
`fadeGlyph` renders at that opacity. With truecolor, below `STATUS_FADE_MIN_OPACITY` (0.12) the glyph is hidden entirely — a blank column — so the pulse trough disappears rather than lingering as a near-background gray; above it the glyph interpolates a 24-bit gray between `STATUS_FADE_GRAY.trough` and `.settled` (the same dim gray as the idle caret), emitting `\x1b[38;2;r;g;bm`, so both the fade and the throb are brightness. Without truecolor there is no per-frame gray, so a separate `visible` flag — driven by the envelope alone, not the pulsing opacity — shows the glyph in the palette's muted role or leaves a blank column; the throb never blinks the fallback. With color off entirely a visible glyph is bare, preserving the caret column on a monochrome terminal.
The running prompt refreshes at `STATUS_ANIMATION_INTERVAL_MS = 50` (~20 fps) so the throb moves every frame; the same tick keeps the 0.1 s-resolution elapsed text current, so no separate timing timer exists.
Fade-out outlives the turn: on the running → non-running edge `beginFadeOut` hands the last rendered glyph to a `FadingStatus` whose own timer re-renders until the fade window elapses, then calls `clearStatus` and restores `>`. Teardown paths (dispose, agent-disposed, startup-failure) call `clearStatus` directly, stopping both the running and fading timers at once — no lingering fade. The glyph handed to the fade-out is the last live phase glyph (`runningStatus.lastGlyph`), not the ttft fallback the phase derivation returns once the closing turn's step has ended.
The glyph character and its cell never change — only the gray brightness — so the caret column stays fixed across frames and across the caret↔glyph transitions.
## Alternatives considered
**Keep the accent color.** The pulse is wanted, but as a quiet gray matching the idle caret's tone, not a colored indicator; the accent is removed while the throb stays.
**Hold steady while running (no throb).** A steady dim glyph was tried and rejected: a continuous pulse better conveys that the agent is actively working. The throb returns, in gray.
**A non-zero floor that keeps the trough faintly visible.** Successive floors (0.45 → 0.15 → 0.02) each kept the dimmest point too visible to read as truly quiet; even 0.02 sat at gray ≈ 45, one step off the background. A floor of 0 with an explicit visibility threshold (`STATUS_FADE_MIN_OPACITY`) instead hides the glyph entirely at the bottom of each breath, so the trough is genuinely absent. Because the swell is a smooth cosine, the disappearance reads as a soft fade-out, not the hard on/off blink a low-but-nonzero gray toggle would give.
**Pulse the non-truecolor fallback too.** SGR exposes only three intensity levels, too coarse for a smooth throb, and toggling the glyph on/off across the pulse would blink it. The fallback instead shows a steady muted glyph gated by the envelope; only truecolor terminals get the throb.
## Consequences
The running glyph reads as a quiet gray breath that swells from nothing to a dim mark and back the whole turn, matching the idle caret's tone, at the cost of a faster render tick (50 ms) while a turn is active or fading out; the diffing terminal only re-emits changed cells, so the extra frames are cheap. The fade-out means the indicator lingers ~300 ms after a turn completes. Snapshots run non-truecolor with a frozen clock, so they pin only the steady muted glyph (envelope-gated), not the throb; the truecolor invisible trough, the settled peak, a rising mid-frame, the fade-out, and the non-truecolor appear/disappear are pinned by unit tests in `tui.spec.ts`.

View File

@@ -0,0 +1,38 @@
# Agent Note: Dim-gray pulse for the running prompt glyph
Status: implemented
Archived: 2026-08-04
[English](2026-07-27-tui-running-glyph-smooth-fade.md) | 中文
## Problem
回合运行时TUI 会把 `>` 提示符替换为阶段字形(`◍`/`✻`/`●`/`⚙`)。此前的迭代用强调蓝为其亮度做动画(先是离散 SGR 波,后是 truecolor 呼吸)——一个持续脉动的彩色指示器。期望的效果保留持续脉动以示正在工作,但改为安静的暗灰而非颜色,并在两端做平滑的淡入淡出。
## Decision
运行字形是一种暗灰色,在回合开始时淡入,运行期间持续脉动,回合结束后淡出,随后恢复为普通的 `>` 光标。它从不使用强调色。
亮度是淡入淡出包络乘以运行脉冲。包络控制出现/消失,随渲染时钟在 `STATUS_FADE_MS = 300` 内线性变化:淡入为 `(now startedAt)/FADE` 并做钳制,淡出为 `1 (now endedAt)/FADE``pulseLevel` 是在 `STATUS_PULSE_FLOOR`0与 1 之间、周期为 `STATUS_PULSE_PERIOD_MS = 1400` 的余弦,因此每次呼吸都从完全不可见涨到满亮再回落。交给 `fadeGlyph` 的 truecolor 不透明度为 `envelope × pulse`
`fadeGlyph` 以该不透明度渲染。在 truecolor 下,低于 `STATUS_FADE_MIN_OPACITY`0.12)时字形被完全隐藏——留出空白列——因此脉冲谷值消失,而非停留为接近背景的灰;在其之上,字形在 `STATUS_FADE_GRAY.trough``.settled`(与空闲光标相同的暗灰)之间插值出 24 位灰色,发出 `\x1b[38;2;r;g;bm`,因此淡入与脉冲都表现为亮度。没有 truecolor 时不存在逐帧灰度,因此用一个单独的 `visible` 标志——只由包络驱动,而非脉动的不透明度——以调色板 muted 角色显示字形或留出空白列;脉冲从不使回退闪烁。完全关闭颜色时,可见字形以裸字符呈现,在单色终端上保住光标列。
运行提示符以 `STATUS_ANIMATION_INTERVAL_MS = 50`(约 20 fps刷新使脉动逐帧移动同一次 tick 也让 0.1 s 精度的耗时文本保持最新,因此不需要单独的计时器。
淡出会延续到回合之后:在运行 → 非运行的边沿,`beginFadeOut` 把最后渲染的字形交给一个 `FadingStatus`,其自有计时器持续重绘,直到渐变窗口结束,然后调用 `clearStatus` 并恢复 `>`。拆解路径dispose、agent-disposed、启动失败直接调用 `clearStatus`,一次性停止运行与淡出两个计时器——不会有残留的渐变。交给淡出的字形是最后一次的实时阶段字形(`runningStatus.lastGlyph`),而非收尾回合的步骤结束后阶段推导返回的 ttft 兜底字形。
字形字符及其单元格从不改变——只有灰色亮度变化——所以光标列在各帧之间以及光标↔字形的切换之间都保持固定。
## Alternatives considered
**保留强调色。** 需要脉冲,但要用与空闲光标一致的安静灰色,而非彩色指示器;移除强调色,保留脉动。
**运行时保持稳定(不脉动)。** 曾试过稳定的暗色字形并被否决:持续脉动更能表明代理正在积极工作。脉动以灰色回归。
**用非零下限让谷值保持微弱可见。** 逐次下限0.45 → 0.15 → 0.02)都让最暗点太可见,读不出真正的安静;即便 0.02 也停在灰度约 45仅比背景高一档。改用下限 0 加显式可见阈值(`STATUS_FADE_MIN_OPACITY`),在每次呼吸的底部完全隐藏字形,使谷值真正缺席。由于涨落是平滑余弦,消失读作柔和的淡出,而非低而非零的灰度开关会带来的硬性开/关闪烁。
**让非 truecolor 回退也脉动。** SGR 只暴露三个强度档位,做平滑脉动太粗糙,而按脉冲开关字形会使其闪烁。回退改为由包络控制的稳定 muted 字形;只有 truecolor 终端获得脉动。
## Consequences
代价是运行或淡出期间渲染 tick 更快50 ms换来的是运行字形整段回合读作一种从无涨到暗记号再回落的安静灰色呼吸与空闲光标的色调一致差分终端只重发变化的单元格因此额外帧开销很低。淡出意味着指示器在回合结束后残留约 300 ms。快照以非 truecolor、冻结时钟运行因此只钉住由包络控制的稳定 muted 字形而非脉动truecolor 的不可见谷值、稳定峰值、上升中间帧、淡出、以及非 truecolor 的出现/消失均由 `tui.spec.ts` 的单元测试钉住。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-07-27-tui-tool-card-header.md
2026-07-27-tui-tool-card-header.md: f054ca378481be477ac2d261423c3e6af2035b2a
2026-07-27-tui-tool-card-header.zh.md: bf3bf39662f8cc04a28d8f5ea411077bb190c59a

View File

@@ -0,0 +1,36 @@
# Agent Note: Fixed `Tool / <name>` header for tool-call cards
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-27-tui-tool-card-header.zh.md)
## Problem
The TUI rendered each tool call as `{glyph} {title}`, where `title` was the presenter's fused verb-plus-detail string (`Read src/index.ts (1200-1360)`, `Edit files`, or a bash card's model description), bold and underlined in the status color. One flat slot carried the tool identity, the target, and the status at once, and the styling mixed bold, underline, and color inconsistently — the header read as noise, and which tool ran was not visually separable from what it operated on.
## Decision
The header is a fixed `{ring} Tool / <name>` frame in a single flat status color — no bold, no underline, no dim — so one color reads consistently across the whole row. `Tool` is a literal constant; `<name>` is the raw tool name. The separator is ASCII `/`. The ring marker is `○` while the call is pending and `●` once it settles; the header color (warning pending / success ok / error) distinguishes pending from ok from error, so the same filled ring serves both settled states.
The header carries exactly one optional extra: a bash (terminal) card's model-authored description, appended as a ` / <desc>` segment (`● Tool / bash / Run the coverage gate`). No other tool contributes a header detail.
Every tool-specific detail moves into the body block below the header. A non-terminal card's presenter title (`Read src/index.ts`, `Grep pattern`) becomes the first body line, unless it only repeats the tool name (the fallback presenter for a tool with no `presentCall`, or an unknown tool), which the header already shows. A terminal card keeps its command as the `$`-line. A diff card drops its title entirely — the per-file path headers and a change footer carry the meaning — and appends a dim `└ +A -R · N file(s)` footer summarizing added/removed line counts across the files.
The redesign is TUI-only. It touches `ToolCardComponent` in `packages/ui/tui/src/components/transcript.ts` and no presenter: the `Tool / <name>` frame derives the name TUI-side from the call's tool name, and the body-title relocation reuses the presenter title already returned. `presentation.ts` and every `presentCall`/`presentResult` are unchanged.
## Alternatives considered
**Bold the name to make it stand out.** Rejected: on terminals that render SGR-1 as the bright color variant, a bold green name reads as a different color from the rest of the green header — reintroducing the inconsistency the redesign removes. The name stands out by position in the fixed frame, not by weight.
**Keep the presenter title in the header** (e.g. `Tool / read / Read src/index.ts`). Rejected: the verb duplicates the tool name, and non-bash tools have no genuinely distinct one-line description — the target belongs in the body, so only bash contributes a header desc.
**A summary footer for every card type** (line counts, exit pills, diff counts as a uniform `└ …` line). Deferred: only the diff footer shipped. Terminal exit keeps its existing dim `[exit N]` line, long output keeps its existing head+tail middle-elision, an empty result stays header-only, and an error body stays plain (only the header color carries the error) — the current treatments were kept deliberately, not by omission. The body's flat default-foreground styling was later revisited: the [consolidated TUI presentation](../architecture/2026-07-28-consolidated-tui-presentation.md) recesses the whole body into one dim tone under this note's colored status header.
## Consequences
A tool call now shows its identity in one stable place, and status reads as one flat color per row, so a transcript of many calls scans as a column of `Tool / <name>` rather than a wall of mixed-styled verb strings. The cost is one extra body line for non-terminal tools (the relocated title) and the loss of the earlier redundancy-suppression that omitted a diff's per-file path when the header already named it — the header no longer names any path, so every diff prints its path once. Because the change is confined to `ToolCardComponent`, other UI bridges (ACP, JSON-RPC) keep their own tool-call presentation; the `Tool / <name>` shape is TUI-local and not part of any cross-package contract.
## Testing
`packages/ui/tui/tests/tui.spec.ts` pins the new header (`Tool / <name>`), the dropped diff title, the relocated generic title, and the `· N file(s)` footer. Package semantic snapshots cover the card families in a headless terminal. The deleted application journeys formerly supplied assembled tool executions; a future terminal deployment owns equivalent transcript coverage.

View File

@@ -0,0 +1,36 @@
# Agent Note: Fixed `Tool / <name>` header for tool-call cards
Status: implemented
Archived: 2026-08-04
[English](2026-07-27-tui-tool-card-header.md) | 中文
## Problem
TUI 曾把每次工具调用渲染为 `{glyph} {title}`,其中 `title` 是 presenter 拼接的「动词加细节」字符串(`Read src/index.ts (1200-1360)``Edit files`,或 bash 卡片的模型描述),以状态色加粗并加下划线显示。单一扁平的槽位同时承载了工具身份、操作对象和状态,而样式又混用了加粗、下划线和颜色,前后不一致——表头读起来像噪声,「运行了哪个工具」在视觉上与「它操作了什么」无法区分。
## Decision
表头是固定的 `{ring} Tool / <name>` 框架,采用单一扁平的状态色——不加粗、不加下划线、不变暗——因此整行的颜色保持一致。`Tool` 是字面常量;`<name>` 是原始工具名。分隔符是 ASCII 的 `/`。环形标记在调用挂起时为 `○`,落定后为 `●`;表头颜色(挂起用 warning、成功用 success、错误用 error区分挂起、成功与错误因此同一个实心环可同时服务于两种落定状态。
表头只携带一个可选的额外内容bash终端卡片由模型撰写的描述作为 ` / <desc>` 段追加(`● Tool / bash / Run the coverage gate`)。其他工具都不向表头贡献细节。
每一项工具专属的细节都移入表头下方的正文块。非终端卡片的 presenter 标题(`Read src/index.ts``Grep pattern`)成为正文第一行,除非它只是重复工具名(无 `presentCall` 的工具的兜底 presenter或未知工具此时表头已经显示过。终端卡片保留其命令作为 `$` 行。diff 卡片完全弃用其标题——由各文件的路径表头与一条变更页脚承载含义——并追加一条变暗的 `└ +A -R · N file(s)` 页脚,汇总各文件增删的行数。
本次改版仅限 TUI。它改动 `packages/ui/tui/src/components/transcript.ts` 中的 `ToolCardComponent`,不触碰任何 presenter`Tool / <name>` 框架在 TUI 侧从调用的工具名推导出名称,正文标题的迁移则复用 presenter 已返回的标题。`presentation.ts` 以及每一个 `presentCall`/`presentResult` 均保持不变。
## Alternatives considered
**把工具名加粗使其突出。** 已否决:在把 SGR-1 渲染为亮色变体的终端上,加粗的绿色工具名读起来与其余绿色表头是不同的颜色——重新引入了改版本要消除的不一致。工具名靠它在固定框架中的位置突出,而非靠字重。
**把 presenter 标题保留在表头**(例如 `Tool / read / Read src/index.ts`)。已否决:动词与工具名重复,而非 bash 工具并没有真正独立的单行描述——操作对象属于正文,因此只有 bash 向表头贡献描述段。
**为每一种卡片都加一条汇总页脚**行数、退出码徽章、diff 计数统一为一条 `└ …` 行)。已推迟:仅 diff 页脚落地。终端退出保留其既有的变暗 `[exit N]` 行,长输出保留其既有的首尾中段省略,空结果保持仅表头,错误正文保持朴素(仅表头颜色承载错误)——这些既有处理是有意保留的,而非遗漏。正文原本以默认前景色平铺,这种样式后来也经过调整:[整合后的 TUI 呈现](../architecture/2026-07-28-consolidated-tui-presentation.md)把整个正文收进本文所述彩色状态标题之下的同一种暗色调。
## Consequences
工具调用现在把身份显示在一个稳定的位置,状态每行读作一种扁平色,于是许多调用的记录扫读起来是一列 `Tool / <name>`,而非一堵混合样式的动词字符串之墙。代价是非终端工具多出一行正文(迁移过来的标题),以及丢失了先前的冗余抑制——当表头已命名路径时省略 diff 的各文件路径;如今表头不再命名任何路径,因此每个 diff 都会把路径打印一次。由于改动局限于 `ToolCardComponent`,其他 UI 桥ACP、JSON-RPC保留各自的工具调用呈现`Tool / <name>` 的形态是 TUI 局部的,不属于任何跨包契约。
## Testing
`packages/ui/tui/tests/tui.spec.ts` 固定了新表头(`Tool / <name>`)、弃用的 diff 标题、迁移后的 generic 标题以及 `· N file(s)` 页脚。包级语义快照在无界面终端中覆盖各类卡片。已删除的应用流程此前提供组装后的工具执行;未来的终端部署负责提供等价的 transcript 覆盖。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-28-dsh-guided-skill-session-commands.md
2026-07-28-dsh-guided-skill-session-commands.md: 454d090a55db8987f8e4987aba67deff1b21b1a0
2026-07-28-dsh-guided-skill-session-commands.zh.md: 64dad4e39c6fd3e3341313c3f8dd2b04947695cf

View File

@@ -0,0 +1,44 @@
# Agent Note: `dsh migrate`/`dsh upgrade` seed the first turn with a skill
Status: implemented
Archived: 2026-08-03
English | [中文](2026-07-28-dsh-guided-skill-session-commands.zh.md)
## Problem
Two recurring flows begin with the user manually invoking one skill and answering its questions: migrating from another coding agent, and upgrading this checkout. Both require the user to know the skill exists and to type `/skill:dsh-migrate` or `/skill:dsh-upgrade` as the session's first turn. A dedicated entry command that drops the user straight into that guided session removes the discovery step.
## Decision
`dsh migrate` and `dsh upgrade` boot the ordinary TUI as a fresh session whose first turn auto-invokes a bundled skill (`dsh-migrate`, `dsh-upgrade`), exactly as if the user typed `/skill:<name>` and pressed Enter.
The seed reuses the existing TUI skill path, not a new one. `createTuiChat` already has `invokeSkill(name, instructions)` — the code a typed `/skill:<name>` runs, including the "Unknown skill" notice. The launcher passes the skill name to the TUI through a new boot-context slot `INITIAL_SKILL_KEY` (`tuiInitialSkill`), mirroring `CONFIGURED_AGENT_IDENTITIES_KEY`/`TUI_GOODBYE_MESSAGE_KEY`: `ctx.provide` is the only channel from launcher argv into a Loader-mounted plugin. The TUI's `apply()` reads the slot and folds it into `config.initialSkill`; after `ui.start()` succeeds, `createTuiChat` fires `invokeSkill(config.initialSkill, '')` once when set.
**Freshness is gated in the launcher, not the TUI.** `runSkillSession` always mints a fresh session and provides the slot only when `resumeSessionId === undefined`, so a later `dsh --resume <id>` of that session is an ordinary TUI session with no re-injection. The TUI stays generic: it invokes whatever skill it is handed, once, at startup.
**`migrate`/`upgrade` take no default-surface options** (`upgrade` additionally carries the [experimental gate](2026-07-31-experimental-subcommand-gate.md)'s `--experimental`). They carry no `--resume`, `--config`, or `-p`; a guided fresh-session entry has nothing to resume or reconfigure. Any leaked default-surface option fails loud, matching the `web`/`meta` rejection pattern in the Commander adapter. The two modes share one `SkillSessionInvocation` discriminant (`mode: 'migrate' | 'upgrade'`); `bin.ts` maps the mode to `dsh-${mode}`.
The `dsh-migrate` skill is bundled under `skills/` (shipped through `DSH_BUNDLED_SKILL_DIR`, like `dsh-upgrade`). It asks which source agent (opencode/pi/Claude Code/Codex) if unstated, then maps each capability — workspace instructions, personal overlay, skills, hooks, MCP, API/env — to its DSH equivalent, grounded in the actual repo surfaces (the `hooks-claude`/`hooks-codex` bridges, `~/.dsh/{config.yaml,.env,AGENTS.md,skills/}`, `AGENTS.md`/`CLAUDE.md`, `mcporter`), and states plainly when a capability has no equivalent.
## Testing
`apps/cli/tests/args.spec.ts` gains routing for `migrate`/`upgrade` (bare discriminant) and exit-1 for every leaked option on either side of each subcommand.
`packages/ui/tui/tests/tui.spec.ts` gains two fake-terminal cases in the existing skill describe block: `config.initialSkill` set delivers the rendered skill body as the first turn with no user input, and an unknown initial skill reports a notice without sending. `runSkillSession` itself is composition inside the module's `v8 ignore` block, like `runTui`/`runMeta`.
No keyless PTY snapshot: per the maintainer's scope call for this change, unit coverage plus interactive verification suffices, and the seed rides the already-snapshotted `/skill:` render path. Both commands were verified interactively in tmux from a scratch cwd: `dsh migrate` loaded `dsh-migrate` and asked which source agent; `dsh upgrade` loaded `dsh-upgrade`, which pulled in `dsh-customize` and began checkout discovery.
## Alternatives considered
**Prefill the input and let the user press Enter.** Rejected: needs a new editor-prefill seam and still requires a keystroke. Auto-submit reuses `invokeSkill` and delivers the intended one-command entry.
**Seed a natural-language instruction ("use the dsh-migrate skill…") instead of `/skill:<name>`.** Rejected here: the literal skill-invocation path renders the skill body into the first turn deterministically, identical to the manual command, rather than depending on the model choosing to load the skill.
**Support `--resume` on `migrate`/`upgrade`.** Rejected: these are one-shot guided entries. A resumed session is an ordinary TUI session reachable through the default surface's `dsh --resume <id>`; re-injecting the skill on resume would duplicate the first turn.
**Read `INITIAL_SKILL_KEY` outside the TUI (as `CONFIGURED_AGENT_IDENTITIES_KEY` is read by `agent-loop`) rather than in the TUI's `apply()`.** Not needed: `initialSkill` is a TUI `Config` field consumed in `createTuiChat`, so folding the slot into config at the TUI entry keeps it beside the other launcher-owned runtime reads (`tuiResumeHost`, `tuiGoodbyeMessage`) and touches no other plugin.
## Consequences
Migrating or upgrading is one command from anywhere, with the guiding skill already invoked. The launcher→TUI initial-skill slot is reusable by any future guided-session command; the TUI's contract is "invoke this named skill once at startup," and freshness/resume policy stays with the launcher that owns session identity. The [TUI skill slash command](2026-07-21-tui-skill-slash-command.md) remains the mechanism; this note adds a launcher-driven auto-invocation of it and does not supersede it.

View File

@@ -0,0 +1,44 @@
# Agent Note: `dsh migrate`/`dsh upgrade` 以 skill 播种首轮
Status: implemented
Archived: 2026-08-03
[English](2026-07-28-dsh-guided-skill-session-commands.md) | 中文
## 问题
有两个反复出现的流程都以用户手动调用某个 skill 并回答其问题开始:从其他编码 agent 迁移,以及升级本 checkout。二者都要求用户知道该 skill 存在,并把 `/skill:dsh-migrate``/skill:dsh-upgrade` 作为会话首轮键入。一个专用入口命令若能让用户直接进入该引导式会话,便可省去这一发现步骤。
## 决策
`dsh migrate``dsh upgrade` 以全新会话启动普通 TUI其首轮自动调用一个内置 skill`dsh-migrate``dsh-upgrade`),效果等同于用户键入 `/skill:<name>` 并回车。
播种复用现有的 TUI skill 路径,而非新增一条。`createTuiChat` 已有 `invokeSkill(name, instructions)`——即键入 `/skill:<name>` 所走的代码,包含“未知 skill”通知。启动器通过一个新的启动上下文槽 `INITIAL_SKILL_KEY``tuiInitialSkill`)把 skill 名称传给 TUI`CONFIGURED_AGENT_IDENTITIES_KEY`/`TUI_GOODBYE_MESSAGE_KEY` 一致:`ctx.provide` 是从启动器 argv 进入 Loader 挂载插件的唯一通道。TUI 的 `apply()` 读取该槽并折叠进 `config.initialSkill``ui.start()` 成功后,`createTuiChat` 在其被设置时调用一次 `invokeSkill(config.initialSkill, '')`
**新鲜性在启动器而非 TUI 中把关。** `runSkillSession` 总是创建全新会话,且仅在 `resumeSessionId === undefined` 时提供该槽,因此之后 `dsh --resume <id>` 恢复该会话时是普通 TUI 会话不会重复注入。TUI 保持通用:它只是把接到的 skill 在启动时调用一次。
**`migrate`/`upgrade` 不接受任何默认界面选项**`upgrade` 另带[实验性门槛](2026-07-31-experimental-subcommand-gate.md)的 `--experimental`)。它们不带 `--resume``--config``-p`;引导式全新会话入口没有可恢复或可重配置的内容。任何泄漏的默认界面选项都会明确报错,与 Commander 适配器中 `web`/`meta` 的拒绝模式一致。两个 mode 共用一个 `SkillSessionInvocation` 判别式(`mode: 'migrate' | 'upgrade'``bin.ts` 将 mode 映射为 `dsh-${mode}`
`dsh-migrate` skill 内置于 `skills/`(经 `DSH_BUNDLED_SKILL_DIR` 交付,与 `dsh-upgrade` 相同)。若未说明源 agent它会先询问是哪个opencode/pi/Claude Code/Codex再把每项能力——workspace 指令、个人覆盖、skills、hooks、MCP、API/env——映射到对应的 DSH 等价物,并基于仓库实际的表面(`hooks-claude`/`hooks-codex` 桥、`~/.dsh/{config.yaml,.env,AGENTS.md,skills/}``AGENTS.md`/`CLAUDE.md``mcporter`)落地;当某能力无等价物时明确说明。
## 测试
`apps/cli/tests/args.spec.ts` 新增 `migrate`/`upgrade` 的路由(裸判别式),以及每个子命令两侧任一泄漏选项的退出码 1。
`packages/ui/tui/tests/tui.spec.ts` 在既有 skill describe 块中新增两个伪终端用例:设置 `config.initialSkill` 时无需用户输入即把渲染后的 skill 正文作为首轮投递;未知的初始 skill 以通知形式报告且不发送。`runSkillSession` 本身是模块 `v8 ignore` 块内的组装,与 `runTui`/`runMeta` 相同。
无 keyless PTY 快照:依据维护者对本次改动的范围裁定,单元覆盖加交互式验证已足够,且播种走的是已有快照的 `/skill:` 渲染路径。两个命令均已在 tmux 中从临时 cwd 交互式验证:`dsh migrate` 加载 `dsh-migrate` 并询问源 agent`dsh upgrade` 加载 `dsh-upgrade`,后者引入 `dsh-customize` 并开始 checkout 发现。
## 考虑过的替代方案
**预填输入框并让用户按回车。** 已否决:需要新增编辑器预填 seam且仍需一次按键。自动提交复用 `invokeSkill`,实现预期的一命令入口。
**播种自然语言指令(“使用 dsh-migrate skill……”而非 `/skill:<name>`。** 在此否决:字面 skill 调用路径会确定性地把 skill 正文渲染进首轮,与手动命令完全一致,而不依赖模型自行选择加载该 skill。
**在 `migrate`/`upgrade` 上支持 `--resume`。** 已否决:它们是一次性引导入口。恢复的会话是可经默认界面 `dsh --resume <id>` 到达的普通 TUI 会话;恢复时重新注入 skill 会重复首轮。
**在 TUI 之外读取 `INITIAL_SKILL_KEY`(如同 `agent-loop` 读取 `CONFIGURED_AGENT_IDENTITIES_KEY` 那样),而非在 TUI 的 `apply()` 中。** 无此必要:`initialSkill` 是在 `createTuiChat` 中消费的 TUI `Config` 字段,因此在 TUI 入口处把该槽位折叠进配置,可以让它与其他由启动器持有的运行时读取(`tuiResumeHost``tuiGoodbyeMessage`)并列,且不触及任何其他插件。
## 后果
迁移或升级从任何位置都只需一条命令,且引导 skill 已被调用。启动器→TUI 的初始 skill 槽可被未来任何引导式会话命令复用TUI 的契约是“在启动时调用一次这个具名 skill”而新鲜性/恢复策略留在拥有会话身份的启动器一侧。[TUI skill 斜杠命令](2026-07-21-tui-skill-slash-command.md)仍是该机制;本 note 在其之上新增了一个由启动器驱动的自动调用,并未取代它。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-28-dsh-meta-source-workspace.md
2026-07-28-dsh-meta-source-workspace.md: 1e433bb50ae5cd5cc893e867d0f4b6edce2bf168
2026-07-28-dsh-meta-source-workspace.zh.md: 95b1edabbbac2e78d09097db614ee4e5d1b33bf7

View File

@@ -0,0 +1,42 @@
# Agent Note: `dsh meta` boots the TUI over the harness checkout
Status: implemented
Archived: 2026-08-03
English | [中文](2026-07-28-dsh-meta-source-workspace.zh.md)
## Problem
`dsh` treats the invoking directory as the workspace, which is what makes it useful on arbitrary projects. Working on dsh itself therefore means `cd`-ing to the checkout first — and the checkout is not a memorable path: the source install keeps it under a container directory as a timestamped staging worktree (`~/.dsh/source/staging-<timestamp>`) behind a `current` symlink, so the target moves on every upgrade. The agent is already *told* where its source lives by the `harness:source` prompt section, and the `cordis` toolset can modify that runtime, but the human still had to locate the directory by hand to start a session there.
## Decision
`dsh meta` boots the ordinary TUI with the harness checkout as the workspace, from any directory.
The target is `SOURCE_ROOT` in `apps/cli/src/tui.ts``fileURLToPath(new URL('../../..', import.meta.url))`, three hops up from `apps/cli/{src,lib}` — the same constant the `harness:source` prompt section already names, so the workspace and the path advertised to the model cannot drift. It follows the launcher's real path, so a PATH symlink through `current` resolves to whichever staging worktree is active.
The mechanism is one `process.chdir(workspace)` inside `runTui`, guarded by an optional third parameter that only the `meta` dispatch passes. The cwd *is* the workspace seam in the shipped tree: `examples/tui-agent/cordis.yml` derives the session cwd (`!!js process.cwd()`), the `./.sessions` persistence root, and the HMR watch root (`root: ['.']`) from it, so one chdir moves all three together and meta sessions land in the checkout's gitignored `.sessions/`. It runs after both `.env` layers are loaded — the bin's invoking-directory load and the personal one — so the ambient > project > personal precedence is untouched. `DEFAULT_CONFIG` and `SOURCE_ROOT` are absolute and TUI mode passes no snapshot mode, so config resolution is chdir-independent.
`meta` always starts a fresh session and accepts no default-surface options; its only option is the [experimental gate](2026-07-31-experimental-subcommand-gate.md)'s `--experimental`. `--config` would boot a foreign tree against the harness workspace, which is the default surface's `--config` case rather than this command; `-p` is not interactive, and resume re-enters the persisted session's own workspace through `dsh --resume <id>`. Any leaked default-surface option fails loud.
## Testing
`apps/cli/tests/args.spec.ts` pins routing for `meta`, rejection of every leaked default-surface option, and rejection of the former `experimental-meta` name. The dispatch itself is composition inside `bin.ts`'s existing `v8 ignore` block.
There is no keyless PTY smoke for this mode. The smoke harness gives each run a temp cwd, but `dsh meta` deliberately chdirs to the real checkout, so a smoke would write `.sessions/` into the live tree mid-test. Covering it properly needs an injectable target directory — a test-only seam this note declines to add for a one-line chdir.
The mode was verified interactively instead. Launched from `$HOME`, a `pwd` tool call reports the checkout, git resolves to its branch, the session log lands under the checkout's `.sessions/` (leaving `~/.sessions` untouched and the tree free of unignored residue), and plain `dsh` from another directory still uses the invoking one.
## Alternatives considered
**Thread an explicit workspace through `boot` and the config tree.** Avoids mutating process-wide state, but the shipped config reads the cwd in three places (`!!js process.cwd()`, `persistenceRoot`, HMR `root`), so each would need its own new plumbing and config key to stay consistent. `chdir` before boot expresses "this is the workspace" once, at the seam that already means it.
**An `--experimental-meta` flag on the default surface.** Rejected: the default surface is option-only so that subcommands do not collide with a positional, and a flag that silently relocates the workspace reads as a modifier of the current directory rather than a different target. `meta` alongside `web` matches the existing shape.
**Resolve `~/.dsh/source/current` instead of the launcher's own path.** Rejected: it would diverge from the `harness:source` prompt path whenever a non-installed checkout's `bin/dsh` is invoked directly, telling the model one source root while working in another.
## Consequences
Starting a session on dsh's own source is `dsh meta --experimental` from anywhere (or bare `dsh meta` under `DSH_EXPERIMENTAL=1`), and the workspace is guaranteed to be the same checkout the model is told about. The command always starts fresh; an ordinary `dsh --resume <id>` later restores the session and enters its persisted workspace.
`runTui` gains an optional third parameter, so the workspace override is visible at the one function that owns TUI composition rather than hidden in a second copy of it.

View File

@@ -0,0 +1,42 @@
# Agent Note: `dsh meta` 以 harness 检出为 workspace 启动 TUI
Status: implemented
Archived: 2026-08-03
[English](2026-07-28-dsh-meta-source-workspace.md) | 中文
## Problem
`dsh` 把调用目录视为 workspace这正是它能作用于任意项目的原因。但因此开发 dsh 自身就得先 `cd` 到检出目录——而该目录并不是一个好记的路径:源码安装会把它放在一个容器目录下、作为带时间戳的 staging 工作树(`~/.dsh/source/staging-<timestamp>`),并由 `current` 符号链接指向,因此每次升级后目标都会变化。`harness:source` 提示词段已经*告知* agent 其源码位置,`cordis` 工具集也能修改该运行时,但人类仍需手工定位该目录才能在其中开始会话。
## Decision
`dsh meta` 在任意目录下都以 harness 检出为 workspace 启动普通 TUI。
目标是 `apps/cli/src/tui.ts` 中的 `SOURCE_ROOT`——`fileURLToPath(new URL('../../..', import.meta.url))`,从 `apps/cli/{src,lib}` 向上三级——与 `harness:source` 提示词段所用的常量完全相同,因此 workspace 与告知模型的路径不可能发生偏离。它跟随启动器的真实路径,所以经由 `current` 的 PATH 符号链接会解析到当前生效的那个 staging 工作树。
机制是 `runTui` 内的一次 `process.chdir(workspace)`,由一个可选第三参数把守,只有 `meta` 分派会传入。在已交付的配置树中cwd *就是* workspace 的接缝:`examples/tui-agent/cordis.yml` 由它派生出会话 cwd`!!js process.cwd()`)、`./.sessions` 持久化根目录以及 HMR 监视根目录(`root: ['.']`),因此一次 chdir 会让三者一并移动meta 会话则落在检出目录中被 gitignore 的 `.sessions/` 内。它在两层 `.env` 都加载之后执行——bin 对调用目录的加载与个人层加载——因此“环境中已有的值 > 项目 > 个人”的优先级不受影响。`DEFAULT_CONFIG``SOURCE_ROOT` 都是绝对路径,且 TUI 模式不传 snapshot mode所以配置解析与 chdir 无关。
`meta` 始终启动新会话,且不接受任何默认界面选项;它唯一的选项是[实验性门槛](2026-07-31-experimental-subcommand-gate.md)的 `--experimental``--config` 会针对 harness workspace 启动其他配置树,那是默认界面的 `--config` 场景,而不是该命令的场景;`-p` 并非交互式,恢复则通过 `dsh --resume <id>` 重新进入已持久化会话自身的 workspace。任何泄漏的默认界面选项都会明确报错。
## Testing
`apps/cli/tests/args.spec.ts` 钉住 `meta` 的路由、对每个泄漏的默认界面选项的拒绝,以及对旧名称 `experimental-meta` 的拒绝。该分派本身是 `bin.ts` 既有 `v8 ignore` 块内的组合代码。
该 mode 没有 keyless PTY 冒烟测试。冒烟框架会为每次运行提供临时 cwd`dsh meta` 刻意 chdir 到真实检出目录,因此冒烟测试会在测试中途把 `.sessions/` 写入实际工作树。要正确覆盖它需要一个可注入的目标目录——为了一行 chdir 而引入的测试专用 seam本 note 不予采纳。
取而代之的是交互式验证。从 `$HOME` 启动后,`pwd` 工具调用报告的是该检出目录git 解析到其分支,会话日志落在该检出的 `.sessions/` 下(`~/.sessions` 未被触及,工作树也没有未被忽略的残留),并且从其他目录运行的普通 `dsh` 仍使用调用目录。
## Alternatives considered
**通过 `boot` 与配置树显式传递 workspace。** 这可避免修改进程级状态,但已交付的配置在三处读取 cwd`!!js process.cwd()``persistenceRoot`、HMR `root`),每一处都需要各自新增管线与配置键才能保持一致。启动前 chdir 只在本就表达该含义的接缝上表达一次“这就是 workspace”。
**在默认界面上加一个 `--experimental-meta` 标志。** 拒绝:默认界面是纯选项形式,以免子命令与位置参数冲突;而一个会静默改变 workspace 的标志读起来像是对当前目录的修饰,而非另一个目标。`meta``web` 并列符合既有形态。
**解析 `~/.dsh/source/current` 而非启动器自身路径。** 拒绝:当直接调用某个非安装检出的 `bin/dsh` 时,它会与 `harness:source` 提示词路径产生偏离——告知模型一个源码根目录,却在另一个目录中工作。
## Consequences
在 dsh 自身源码上开启会话变成了在任意位置执行 `dsh meta --experimental`(在 `DSH_EXPERIMENTAL=1` 下可直接执行 `dsh meta`),且该 workspace 必然就是告知模型的那个检出目录。该命令始终启动新会话;之后,普通的 `dsh --resume <id>` 会恢复该会话并进入其已持久化的 workspace。
`runTui` 新增一个可选第三参数,因此 workspace 覆盖是在拥有 TUI 组合逻辑的那唯一一个函数上可见的,而不是隐藏在它的第二份副本中。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-07-29-tui-hidden-mode-assistant-fold.md
2026-07-29-tui-hidden-mode-assistant-fold.md: 53cd3d812212f8a72c56165c37b806cae58f37f6
2026-07-29-tui-hidden-mode-assistant-fold.zh.md: cf3644478588bbba955fb6539dd99744ef6eb84c

View File

@@ -0,0 +1,26 @@
# Agent Note: TUI hidden mode folds a turn's assistant steps into one message
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-29-tui-hidden-mode-assistant-fold.zh.md)
## Problem
Ctrl+O's hidden phase ([consolidated TUI presentation](../architecture/2026-07-28-consolidated-tui-presentation.md)) drops tool cards so the transcript reads as a conversation, but each model step still rendered its own `Assistant` header. A multi-step turn (text → tools → text) therefore showed several consecutive `Assistant` blocks with nothing between them — the removed tool cards were the only thing that had justified the repeated headers. Codex-style conversation-only reading wants one assistant message per turn.
## Decision
Hidden mode is also a fold rule, applied purely as TUI presentation: per turn, the first step whose rendered content is visible (text, or reasoning while reasoning display is on) owns the turn's single `Assistant` header; every other step renders as a headerless continuation, and a step with no visible body renders nothing at all — a tool-only step neither consumes the header nor leaves a blank segment. Collapsed and expanded phases keep per-step headers; leaving hidden restores them.
Mechanics: `StreamingAssistantComponent` carries its `StepPosition` and a `setFoldedContinuation` presentation flag; `createTuiChat` keeps a per-turn list of step components and re-derives the fold on Ctrl+O, on each streamed text/reasoning chunk, on message settle, and on retraction of a failed stream (which may hand the header to the next step). Transcript rebuild clears the map and replays the log, so resume, compaction replacement, resize, and theme swaps converge on the same fold. Step timing footers keep their per-step ownership and are unaffected.
## Alternatives considered
- **Merge steps into one component** — collides with per-step streaming lifecycle, retry retraction, and timing footers; the flag on existing components changes only the header/spacer.
- **Fold in the session log or `deriveMessages`** — mutates durable/model-visible history for a UI reading mode; the log stays step-shaped.
- **Always fold (all visibility phases)** — collapsed/expanded interleave tool cards between steps, where per-step headers delimit which output belongs to which step.
## Consequences
Hidden mode now reads as one assistant message per turn; turns stay separated by their headers. The fold is recomputed state, never stored, so no session or persistence format changes. Coverage: TUI unit specs for the Ctrl+O cycle header counts, tool-only first step header handoff, per-turn separation, and live streaming + rebuild convergence; keyless snapshot `tool-cards-hidden-folded` pins the folded frame.

View File

@@ -0,0 +1,26 @@
# Agent Note: TUI 隐藏模式把一个轮次的 assistant 步骤折叠为一条消息
Status: implemented
Archived: 2026-08-04
[English](2026-07-29-tui-hidden-mode-assistant-fold.md) | 中文
## 问题
Ctrl+O 的隐藏阶段([整合的 TUI 展示](../architecture/2026-07-28-consolidated-tui-presentation.md))去掉工具卡片,让 transcript文本记录读作一段对话但每个模型步骤仍渲染自己的 `Assistant` 标题。因此一个多步骤轮次(文本 → 工具 → 文本)会显示多个连续、之间空无一物的 `Assistant` 区块——被移除的工具卡片正是重复标题曾经的唯一理由。Codex 风格的纯对话阅读需要每轮次一条 assistant 消息。
## 决定
隐藏模式同时也是一条折叠规则,且纯粹作为 TUI 展示实现:在每个轮次内,第一个渲染内容可见(有文本,或在 reasoning 显示开启时有 reasoning的步骤拥有该轮次唯一的 `Assistant` 标题;其余步骤渲染为无标题的续段,没有可见正文的步骤则完全不渲染——仅有工具调用的步骤既不占用标题,也不留下空白段。折叠与展开阶段保留每步各自的标题;离开隐藏阶段会恢复它们。
机制:`StreamingAssistantComponent` 携带自己的 `StepPosition` 和一个 `setFoldedContinuation` 展示标志;`createTuiChat` 维护每轮次的步骤组件列表,并在 Ctrl+O、每个流式 text/reasoning chunk、消息结算以及失败流被撤回可能把标题移交给下一个步骤时重新推导折叠。transcript 重建会清空该映射并重放日志,因此恢复、压缩替换、调整尺寸和主题切换收敛到同一折叠结果。步骤计时页脚保持按步骤归属,不受影响。
## 考虑过的替代方案
- **把多个步骤合并为一个组件**——与按步骤的流式生命周期、重试撤回和计时页脚冲突;在现有组件上加标志只改变标题与前导间距。
- **在会话日志或 `deriveMessages` 中折叠**——为一种 UI 阅读模式改变持久 / 模型可见的历史;日志保持按步骤的形状。
- **所有可见性阶段都折叠**——折叠 / 展开阶段在步骤之间穿插工具卡片,此时每步的标题用来划分哪段输出属于哪个步骤。
## 后果
隐藏模式现在每轮次读作一条 assistant 消息轮次之间仍由各自的标题分隔。折叠是重新计算的状态从不存储因此会话与持久化格式没有变化。覆盖TUI 单元测试覆盖 Ctrl+O 循环的标题计数、仅工具的首步骤标题移交、按轮次分隔,以及实时流式 + 重建收敛;无密钥快照 `tool-cards-hidden-folded` 固定折叠后的帧。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-30-compaction-progress-visibility.md
2026-07-30-compaction-progress-visibility.md: e0d44c8616661161f6a99b54e3fe75a75dd5b9ad
2026-07-30-compaction-progress-visibility.zh.md: 5b181da16280393db9da5bc00127e71de1d9341a

View File

@@ -0,0 +1,50 @@
# Agent Note: Live standalone compaction progress in the terminal
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-30-compaction-progress-visibility.zh.md)
## Problem
A standalone manual compaction runs between turns while the agent remains idle. The TUI's turn-phase indicator therefore kept its plain `>` caret throughout the slow summary operation, and a failed attempt produced no transcript row because no replacement checkpoint landed. The liveness presentation needs to reuse the existing status indicator without introducing a second animated status location.
The durable log can retain an unmatched `compact/start` after a process dies. That orphan is useful recovery evidence, but it is not proof that work is running in the current process; replaying it as progress would leave resumed sessions with a permanent phantom indicator.
## Decision
The TUI treats the live standalone `compact/start { turn: null }` to matching `compact/end` bracket as the source of in-flight compaction presentation. A module-local `compacting` cell records the render-clock start and owns one animation timer. A fixed row above the prompt renders `Context being compacted <elapsed>` from that clock, the existing one-cell running status indicator renders `⊙` through the same fade and throb path as turn-phase glyphs, and the terminal progress bit remains active until the bracket closes.
`runningPhaseGlyph` owns the choice among turn-phase glyphs, `⊙`, and the idle caret. Turn-phase glyphs take precedence because numbered compaction brackets are enclosed by a running turn whose phase already lights the indicator. The fixed row is outside the transcript and owns neither a spinner nor another timer; it collapses when empty. The compaction cell does not change the idle editor border, hint, or steering badge, so prompts remain visibly accepted while standalone compaction reserves turn admission.
The cell is live-only. Mount and transcript replay never scan history for an unmatched start; only a `session/event` notification observed by the mounted TUI can open it. Turn-status transitions preserve the cell, while terminal teardown clears its timer and progress bit.
On `compact/end`, the TUI clears the cell before starting the ordinary glyph fade-out. An end carrying `error` adds `Compaction failed: <error>` as a warning. Successful completion remains represented by the landed replacement's transcript marker, and duration remains derivable from the matching durable start and end timestamps without another settled row.
This decision partially supersedes only the progress-related deferred clauses in the [terminal transcript decision](../bug-fix/2026-07-29-human-transcript-append-origin.md) and [browser transcript decision](../bug-fix/2026-07-30-web-transcript-log-ordered-projection.md): progress does not require marker scale or a replacement-rendering refactor. Both notes remain active and continue to own append-origin transcript projection and landed checkpoint markers. The [queued manual compaction decision](2026-07-30-queued-manual-compaction.md) remains the owner of bracket ordering, locking, and stale-orphan classification.
## Alternatives considered
**Add `progressLabel` to `CommandDefinition` and a second TUI status controller, as explored in PR #669.** Rejected because command metadata is not the compaction lifecycle authority, automatic compaction does not originate from a human command, and two status controllers can disagree about the same indicator.
**Add `compacting` to `TurnPhase`, as explored in PR #669.** Rejected because standalone compaction deliberately has no turn, while numbered compaction already has a visible running-turn phase.
**Add a fifth `TimingBucket`.** Rejected because timing buckets partition an open model step and feed its transcript footer. Standalone compaction has no step transition, and a new bucket would add a meaningless compaction column to every step total.
**Share one timer among running, fading, and compaction states.** Rejected because fade-out owns a self-terminating timer, while live compaction has an independent open/close lifetime. Sharing would restructure the reviewed animation state machine without removing an actual concurrent timer.
**Scan the log for an unmatched `compact/start`.** Rejected because a stale orphan from an earlier process lifecycle is expected durable history. Only the live notification proves current work.
**Use a generic command-running indicator.** Rejected for this behavior because the compaction bracket is the more precise source and also covers non-command paths. A future generic command indicator belongs to the `command/run` / `command/done` lifecycle.
**Add an animated compaction row to the transcript.** Rejected because it creates a second status animation point for the same lifecycle. The existing one-cell indicator owns liveness, while the landed marker and failure warning own settled transcript presentation.
**Print a success notice with duration.** Rejected because the landed replacement already supplies the completion marker. The bracket timestamps preserve duration for a future presentation that justifies another transcript row.
## Consequences
Manual compaction has a named elapsed-time display above the prompt while the agent is idle, failure has a direct warning, and a resumed orphan never looks active. The prompt indicator remains one terminal cell wide, while the row and indicator reuse the existing status animation, semantic palette, and terminal-progress lifecycle.
The live cell and timer are additional process-local state, cleared on both bracket close and TUI teardown. This is intentionally not reconstructible presentation state: durable history supplies the successful marker and timing facts, while current-process observation alone supplies liveness.
The package-level TUI tests pin standalone start, elapsed-time refresh, single-indicator presentation, numbered-start exclusion, fade-out, failure warning, idle-status preservation, running-turn precedence, orphaned resume, and timer disposal. The removed product TUI scenario formerly observed `Context being compacted 1.0s` and `dsh ⊙` across a held real summary boundary; a future terminal deployment owns that assembled journey.

View File

@@ -0,0 +1,50 @@
# Agent Note: 终端中的实时独立压缩进度
Status: implemented
Archived: 2026-08-04
[English](2026-07-30-compaction-progress-visibility.md) | 中文
## 问题
独立手动压缩compaction在轮次之间运行此时 agent智能体保持空闲。因此在缓慢的摘要操作期间TUI 的轮次阶段指示器始终显示普通的 `>` 光标;尝试失败时,由于没有替换检查点落地,也不会产生 transcript文本记录行。运行状态显示需要复用现有的状态指示器不能再设置第二处带动画的状态显示。
进程终止后,持久日志中可能保留未匹配的 `compact/start`。该未匹配标记是有用的恢复证据,但无法证明当前进程中有工作正在运行;若将其回放为进度,恢复后的会话便会永久显示虚假的进度指示。
## 决策
TUI 将实时独立的 `compact/start { turn: null }` 与匹配的 `compact/end` 组成的标记对,作为显示进行中压缩状态的真源。模块局部的 `compacting` 状态记录渲染时钟的起始时间,并独占一个动画定时器。提示词上方的固定行根据该时钟渲染 `Context being compacted <elapsed>`,现有的单格运行状态指示器通过与轮次阶段字形相同的明暗渐变和呼吸律动路径渲染 `⊙`,终端进度标志位会保持活跃,直至标记对闭合。
`runningPhaseGlyph` 负责在轮次阶段字形、`⊙` 和空闲光标之间作出选择。轮次阶段字形的优先级更高,因为带编号的压缩标记对处于运行中的轮次内,该轮次的阶段已经激活指示器。该固定行位于 transcript 之外,不带 spinner也不另设定时器内容为空时会折叠。压缩状态不会改变空闲编辑器边框、提示或 steering中途引导徽标因此在独立压缩预留轮次准入期间界面仍会明确显示提示词已获接纳。
该状态只反映实时事件。挂载和 transcript 回放绝不会扫描历史以查找未匹配的 start只有已挂载的 TUI 观察到 `session/event` 通知,才能开启它。轮次状态转换会保留该状态,而终端清理会清除其定时器和进度标志位。
收到 `compact/end`TUI 会先清除状态,再启动普通字形的淡出。携带 `error` 的结束事件会以警告形式添加 `Compaction failed: <error>`。成功完成仍由已落地替换项的 transcript 标记呈现;无需再添加已结算的行,也可从匹配且已持久记录的开始与结束时间戳推导持续时间。
本决策仅部分取代[终端 transcript 决策](../bug-fix/2026-07-29-human-transcript-append-origin.md)和[浏览器 transcript 决策](../bug-fix/2026-07-30-web-transcript-log-ordered-projection.md)中与进度相关的延期条款:进度显示不要求标记携带规模信息,也不要求重构替换项渲染。两份记录均保持活动状态,并继续负责基于追加来源的 transcript 投影和已落地检查点标记。[排队式手动压缩决策](2026-07-30-queued-manual-compaction.md)继续负责标记对顺序、锁定机制和陈旧未匹配标记分类。
## 曾考虑的替代方案
**按照 PRPull Request#669 中探索的方案,为 `CommandDefinition` 添加 `progressLabel` 和第二个 TUI 状态控制器。** 不予采用:命令元数据并非压缩生命周期的权威依据,自动压缩并非由人工命令发起,两个状态控制器也可能对同一个指示器给出不一致状态。
**按照 PR #669 中探索的方案,将 `compacting` 添加到 `TurnPhase`。** 不予采用:独立压缩按设计没有轮次,而带编号的压缩已经具有可见的运行轮次阶段。
**添加第五个 `TimingBucket`。** 不予采用:计时分桶用于划分一个开放模型步骤内的时间,并为其 transcript 页脚提供数据。独立压缩没有步骤转换,新分桶会在每个步骤总计中加入一个没有意义的压缩列。
**让运行、淡出和压缩状态共享一个定时器。** 不予采用:淡出过程独占一个会自行终止的定时器,而实时压缩有独立的开启与闭合生命周期。共享定时器会重构已经评审的动画状态机,却不能消除实际存在的并发定时器。
**扫描日志,查找未匹配的 `compact/start`。** 不予采用:来自先前进程生命周期的陈旧未匹配标记是预期的持久历史。只有实时通知才能证明当前进程正在执行工作。
**使用通用的命令运行指示器。** 本行为不采用该方案,因为压缩标记对是更精确的真源,并且还覆盖非命令路径。未来若实现通用命令指示器,应归属于 `command/run``command/done` 生命周期。
**在 transcript 中添加动画压缩行。** 不予采用:这会为同一个生命周期设置第二处状态动画显示。现有的单格指示器负责显示运行状态,而已落地标记和失败警告负责已结算的 transcript 呈现。
**打印包含持续时间的成功通知。** 不予采用:已落地的替换项已经提供完成标记。标记对的时间戳保留了持续时间,可供未来能够证明新增 transcript 行合理的展示方式使用。
## 后果
手动压缩在 agent 空闲时会在提示词上方显示带名称的已用时间,失败会直接产生警告,恢复会话时的陈旧未匹配标记绝不会显示为活动状态。提示符指示器保持一个终端字符单元宽,状态行和指示器则复用现有的状态动画、语义调色板和终端进度生命周期。
实时状态及其定时器是额外的进程局部状态,在标记对闭合和 TUI 清理这两种情况下都会清除。按设计,这种显示状态不可重建:持久历史提供成功标记与计时事实,只有当前进程的观察才能提供运行中状态。
package级 TUI 测试固定了以下行为:独立开始事件、已用时间刷新、单指示器呈现、排除带编号的开始事件、淡出、失败警告、保留空闲状态、运行轮次优先级、存在未匹配标记时的恢复,以及定时器释放。已移除的产品 TUI 场景此前会在真实摘要边界保持开放期间观察到 `Context being compacted 1.0s``dsh ⊙`;未来的终端部署负责该组装流程。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-07-30-tui-details-command.md
2026-07-30-tui-details-command.md: b17cd493fc0c47d7484714f72f418266cd006353
2026-07-30-tui-details-command.zh.md: ef2a682db1248dfc97fd6ccaa4260b4b9d1a0861

View File

@@ -0,0 +1,35 @@
# Agent Note: /details command for transcript detail state
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-30-tui-details-command.zh.md)
## Problem
The TUI's transcript detail state — tool-card visibility (`collapsed`/`expanded`/`hidden`, per the [consolidated TUI presentation](../architecture/2026-07-28-consolidated-tui-presentation.md)) and reasoning-block display — was reachable only through the Ctrl+O cycle and the Ctrl+R toggle. A user who wants a specific mode must cycle through the others, cannot set both dimensions in one action, and has no way to query the current state; a terminal that swallows those control keys has no fallback at all.
## Decision
`dsh-tui` registers `/details` beside its other agent-scoped commands. Bare `/details` opens `DetailsDialog`, a centered keyboard toggle with one entry per dimension — `Tool cards` and `Reasoning` — showing the live values: Tab cycles the highlighted entry and applies the change immediately, so the transcript behind the dialog is the preview, and Enter, Esc, or Ctrl+C closes; its width is the `detailsDialogWidth` config key and a second `/details` replaces an open selector, mirroring the `/model` overlay. Arguments name target states directly: `collapsed|expanded|hidden` jumps tool cards to that phase, `reasoning on|off` sets reasoning display, bare `reasoning` toggles it, and directives combine in one invocation. An unknown token returns a command error carrying the usage line. Every entry mutates the same closure state as the shortcuts, refactored so the cycle and toggle are thin wrappers over `setToolsVisibility`/`setReasoning`; the shortcuts and their notices are unchanged.
A combined invocation applies reasoning before visibility because `setReasoning` rebuilds the transcript from session events, which drops non-durable notice components; applying it last would erase the just-appended visibility notice.
The reasoning rebuild exposed a replay defect that this change fixes in `renderEvent`: the live path cleared a settled `StreamingAssistantComponent` before a later `assistant/message` of the same step (so the second message got a fresh component), but `rebuildTranscript` replay reused the settled component and `settle()` overwrote its content, silently dropping the earlier message's text. The settled check now lives in `renderEvent`'s `assistant/message` case — one home for both paths — and the previously wrong `untrusted-controls` snapshot (an empty `Assistant` header where reasoning and text had been dropped) was re-recorded with the content present.
## Alternatives considered
**Cycle on bare `/details`, mirroring Ctrl+O.** Rejected: the command's value over the shortcut is naming an absolute state; a cycling command is the shortcut with more keystrokes, and bare invocation is more useful as the selector, which shows the current state while offering every target.
**Bare `/details` as a text-only state report.** Shipped first, replaced by the selector: the report answered "where am I" but still required a second, argument-spelling invocation to change anything, while the selector shows the same state and applies a change in one interaction. The textual grammar remains for scripts, muscle memory, and combined two-dimension changes.
**Separate `/tools` and `/reasoning` commands.** Rejected: both dimensions are one presentation concern ("how much detail does the transcript show"), and a single command keeps the registry and `/help` list small while allowing one combined invocation.
**Config-key defaults per mode.** Out of scope: `showReasoning` already exists as config; the command is runtime state on top of it, matching the shortcuts.
## Consequences
- A user can jump to any detail mode, set both dimensions at once, and see the current state in the selector — including on terminals that intercept Ctrl+O/Ctrl+R.
- The parser accepts order-free tokens, so `/details reasoning expanded` toggles reasoning and expands cards; last directive wins per dimension. This leniency is deliberate and documented in the README.
- The selector has no pending state or cancel: every Tab is a real, already-notified change, and closing never reverts. A user who over-cycles simply Tabs on to the wanted value.
- Transcript rebuilds no longer lose assistant messages when a step carries more than one `assistant/message` event; the `details-command` snapshot pins the argument surface and the fixed replay, and `details-selector` pins the open toggle right after a Tab applied `hidden` -> `collapsed`, including the restored tool card behind it.

View File

@@ -0,0 +1,35 @@
# Agent Note: 用于 transcript 细节状态的 /details 命令
Status: implemented
Archived: 2026-08-04
[English](2026-07-30-tui-details-command.md) | 中文
## Problem
TUI 的 transcript文本记录细节状态——工具卡片可见性`collapsed`/`expanded`/`hidden`,见[整合的 TUI 展示](../architecture/2026-07-28-consolidated-tui-presentation.md))与 reasoning 块显示——过去只能通过 Ctrl+O 循环和 Ctrl+R 切换来触达。想要某个特定模式的用户必须循环经过其他模式,无法一次操作同时设置两个维度,也无法查询当前状态;吞掉这些控制键的终端更是完全没有替代途径。
## Decision
`dsh-tui` 在其他 agent 作用域命令旁注册 `/details`。裸 `/details` 打开 `DetailsDialog`:一个居中的键盘开关,每个维度一个条目——`Tool cards``Reasoning`——显示实时值Tab 循环高亮条目并立即应用变更,对话框背后的 transcript 即是预览Enter、Esc 或 Ctrl+C 关闭;其宽度由配置键 `detailsDialogWidth` 决定,选择器打开时再次执行 `/details` 会替换它,与 `/model` 浮层一致。参数直接命名目标状态:`collapsed|expanded|hidden` 让工具卡片跳到该阶段,`reasoning on|off` 设置 reasoning 显示,裸 `reasoning` 切换它,且指令可在一次调用中组合。未知 token 返回携带用法行的命令错误。每个入口改动的都是与快捷键相同的闭包状态,重构后循环与切换成为 `setToolsVisibility`/`setReasoning` 之上的薄封装;快捷键及其通知保持不变。
组合调用先应用 reasoning 再应用可见性,因为 `setReasoning` 会从会话事件重建 transcript而重建会丢弃非持久的通知组件若最后才应用它会抹掉刚追加的可见性通知。
reasoning 重建暴露了一个重放缺陷,本变更在 `renderEvent` 中修复:实时路径会在同一步骤的后续 `assistant/message` 之前清除已结算的 `StreamingAssistantComponent`(因此第二条消息获得新组件),但 `rebuildTranscript` 重放复用了已结算组件,`settle()` 覆盖其内容,静默丢掉了前一条消息的文本。已结算检查现在位于 `renderEvent``assistant/message` 分支——两条路径共用一个归属地——此前错误的 `untrusted-controls` 快照reasoning 与文本被丢弃后只剩空 `Assistant` 标题)已重录为包含内容的版本。
## Alternatives considered
**裸 `/details` 像 Ctrl+O 一样循环。** 否决:命令相对快捷键的价值在于命名绝对状态;循环命令只是按键更多的快捷键,裸调用作为选择器更有用——它在展示当前状态的同时提供所有目标。
**裸 `/details` 仅输出文本状态报告。** 首版如此实现,后被选择器取代:报告回答了“我在哪”,但改变任何东西仍需第二次、拼写参数的调用;选择器展示同样的状态并在一次交互中应用变更。文本语法保留给脚本、肌肉记忆和两维组合变更。
**拆分 `/tools` 与 `/reasoning` 两个命令。** 否决两个维度同属一个展示关注点“transcript 显示多少细节”),单一命令让注册表与 `/help` 列表更小,同时允许一次组合调用。
**按模式提供配置键默认值。** 超出范围:`showReasoning` 已作为配置存在;命令是其上的运行时状态,与快捷键一致。
## Consequences
- 用户可以跳到任意细节模式、一次设置两个维度,并在选择器中看到当前状态——包括在拦截 Ctrl+O/Ctrl+R 的终端上。
- 解析器接受无序 token因此 `/details reasoning expanded` 会切换 reasoning 并展开卡片;每个维度以最后一个指令为准。这一宽松是刻意的,并记录在 README 中。
- 选择器没有待定状态与取消:每次 Tab 都是已生效、已通知的真实变更,关闭从不回退。循环过头的用户继续 Tab 到想要的值即可。
- 当一个步骤携带多条 `assistant/message` 事件时transcript 重建不再丢失 assistant 消息;`details-command` 快照固定参数表面与修复后的重放,`details-selector` 固定 Tab 将 `hidden` 应用为 `collapsed` 后仍打开的开关,包括其背后恢复显示的工具卡片。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-30-versioned-tui-first-run-welcome.md
2026-07-30-versioned-tui-first-run-welcome.md: c032785a34455e868c2643bc58aafcb0e4916592
2026-07-30-versioned-tui-first-run-welcome.zh.md: 0f3e684baeaed178ce3d7590590d0086f345e461

View File

@@ -0,0 +1,44 @@
# Agent Note: Versioned TUI first-run welcome
Status: implemented
Archived: 2026-08-03
English | [中文](2026-07-30-versioned-tui-first-run-welcome.zh.md)
## Problem
The shipped `dsh` terminal starts directly in the editor and gives first-time internal testers no durable orientation about the product's maturity or feedback channel. The existing one-line `welcome` banner subtitle cannot carry the supplied notice without crowding the normal session header, and putting onboarding in the session log would create a user turn or model-visible context that is unrelated to the user's work.
The notice also needs a recognizable DeepSeek composition without copying another product's startup art or maintaining a hand-drawn approximation that drifts from the official mark.
## Decision
The official `dsh` launcher owns one versioned acknowledgement marker under the resolved `DSH_HOME`. It checks the immutable marker before boot, then mounts an effect-owned consumer of `ctx.tui.openOverlay()` only after the real TUI service is available. Enter is the sole acknowledgement action: the plugin creates and synchronizes the fixed per-version marker before closing. Escape and unrecognized input leave the overlay open; Ctrl+C and Ctrl+D use the normal exit path without acknowledging. Disposal waits for an acknowledgement already started by Enter, while disposal or process exit before Enter writes nothing. The version is part of the marker filename, so incrementing the centrally owned notice version presents materially revised copy once without migrating or rewriting an aggregate settings document.
The marker is launcher state rather than session persistence because eligibility spans sessions and workspaces but is scoped to one Harness home. Each Enter syncs a random same-directory file before atomically replacing the fixed marker; concurrent launches publish the same immutable fact, so same-value last-writer-wins replacement has no lost-update shape and needs no lock or dependency on the settings stack. The notice never appends a session event, injects model context, or creates a user turn; resume therefore presents it only when the same Harness home has not acknowledged that version and never replays it from the session log.
The supplied official `24x24` DeepSeek SVG is committed as the visual source. Static full, compact, and minimal terminal rasters sample that exact path at decreasing square resolutions; they do not redraw the contour. Unicode `▀`/`▄`/`█` cells preserve two vertical source pixels per terminal cell, while an explicitly ASCII-only locale uses the bit-equivalent `'`/`_`/`#` fallback. ANSI styling stays outside both the SVG and editable copy: `ctx.tui` supplies a semantic `brand` role, using the official `#4D6BFE` ink when truecolor is available, standard ANSI blue otherwise, and plain text when color is disabled. The normal startup banner retains its existing gradient.
The overlay is centered and consumes the available terminal width, while its height follows actual content and treats 90% of the viewport only as an upper bound. Wide terminals place the full icon beside the title and prose; medium and narrow terminals stack the compact or minimal icon above them; low height removes the icon before reducing prose space. The prose scrolls while the title and only action remain fixed. Every locale uses the same centrally owned Chinese copy, and the quotation is promoted to its own visual paragraph without changing that string. Closing through Enter returns modal ownership to the existing FIFO manager, which restores the editor and leaves the normal startup banner, transcript, and focus behavior intact.
## Verification
Focused unit coverage pins the supplied SVG and Chinese copy hashes, version bumps, exclusive concurrent acknowledgement, malformed markers, persistence retry, Escape behavior, ASCII fallback, width-tier selection, bounded rendering, and low-height scrolling. Real Loader/PTY cases cover 60, 80, 120, and 160 columns plus a low-height viewport, emit semantic terminal snapshots, prove first launch then second-launch suppression under one `DSH_HOME`, and prove a resumed session appends no notice-derived user message or turn; ordinary terminal-exit lifecycle events remain unchanged.
## Alternatives considered
**Reuse the TUI `welcome` subtitle.** It is one transient header line whose normal job is to identify an untitled session. The required prose and action would either be clipped or permanently crowd ordinary launches.
**Copy Claude Code's startup art or composition.** Its strong hierarchy is useful product evidence, but its graphic, layout, and brand treatment belong to another product. The official DeepSeek SVG provides a direct brand source, and the terminal composition is derived independently around this notice's copy and responsive constraints.
**Hand-draw an original whale.** A freehand silhouette can be recognizable yet still disagree with the official mark's body, internal negative space, fin, and tail. Exact-path raster sampling keeps the terminal limitation explicit and makes every tier traceable to one source asset.
**Store a boolean in session events or a shared settings document.** Session state has the wrong lifetime and would pollute replay or model-visible history. An aggregate document would require cross-process read-modify-write locking for one immutable fact; an atomically replaced version marker has no lost-update shape.
**Allow Escape or a later-reminder action.** Either would make dismissal indistinguishable from acknowledgement or introduce reminder policy that the notice does not need. Normal process exit remains the abort path and leaves the version unacknowledged.
## Consequences
Each Harness home receives the notice once per copy version, only after a successful Enter acknowledgement. Maintainers can edit the all-locale Chinese wording and version in one small owner file, and can update the official SVG and derived static rasters in their separate visual owner without chasing snapshots for full prose copies.
The terminal cannot display SVG vectors directly, so its faithful representation is resolution-bounded. Smaller tiers preserve the sampled silhouette but necessarily lose fine detail; low-height terminals prefer readable prose and an always-reachable action over brand art. The marker format is intentionally one-file-per-version during the pre-release period; old markers are harmless and no compatibility reader is required.

View File

@@ -0,0 +1,44 @@
# Agent Note: 版本化 TUI 首次运行欢迎页
Status: implemented
Archived: 2026-08-03
[English](2026-07-30-versioned-tui-first-run-welcome.md) | 中文
## 问题
已交付的 `dsh` 终端会直接进入编辑器,没有为首次使用的内部测试者提供持久的产品成熟度说明或反馈渠道指引。现有单行 `welcome` banner 副标题无法容纳指定通知,否则会挤占正常的会话 header如果把 onboarding 写入会话日志,还会创建一个与用户工作无关的用户轮次或模型可见上下文。
该通知还需要形成具有辨识度的 DeepSeek 视觉构图,同时不能复制其他产品的启动图形,也不能维护一份会偏离官方标志的手绘近似图。
## 决策
官方 `dsh` 启动器在解析后的 `DSH_HOME` 下持有一个版本化确认标记。它会在启动前检查该不可变标记,并仅在真实 TUI 服务可用后,挂载一个由 effect 持有的 `ctx.tui.openOverlay()` 消费方。Enter 是唯一确认操作插件先创建并同步固定的逐版本标记再关闭浮层。Escape 和无法识别的输入会让浮层保持打开Ctrl+C 与 Ctrl+D 通过普通退出路径离开且不确认。资源释放会等待已经由 Enter 启动的确认任务;在按 Enter 前进行资源释放或退出进程不会写入任何内容。版本号属于标记文件名的一部分,因此只需递增集中持有的通知版本,即可让有实质修改的文案重新展示一次,无需迁移或改写聚合设置文档。
该标记属于启动器状态,而非会话持久化,因为展示资格跨越会话与 workspace但作用域仅限一个 Harness 主目录。每次 Enter 都会先同步一个同目录随机文件,再以原子方式替换固定标记;并发启动发布的是同一个不可变事实,因此同值的最后写入者胜出不会丢失更新,也无需加锁或依赖设置栈。该通知绝不追加会话事件、注入模型上下文或创建用户轮次;因此,恢复会话只会在同一个 Harness 主目录尚未确认该版本时展示通知,也绝不会从会话日志中回放通知。
指定的官方 `24x24` DeepSeek SVG 作为视觉真源提交。静态的完整、紧凑和最小终端栅格图以逐级降低的方形分辨率对该精确路径取样不会重新绘制轮廓。Unicode `▀`/`▄`/`█` 单元格让每个终端单元格保留两个垂直方向的源像素;明确仅支持 ASCII 的 locale 则使用位级等价的 `'`/`_`/`#` 回退。ANSI 样式与 SVG 和可编辑文案完全分离:`ctx.tui` 提供语义化 `brand` 角色,在真彩色可用时使用官方 `#4D6BFE` 色值,否则使用标准 ANSI 蓝色;禁用颜色时则使用纯文本。普通启动 banner 保留现有渐变。
浮层居中显示,并使用可用的终端宽度;高度则跟随实际内容,仅将 viewport 的 90% 作为上限。宽终端将完整图标置于标题与正文旁边;中等和窄终端把紧凑或最小图标堆叠在正文上方;高度不足时,先移除图标,再减少正文空间。正文可以滚动,而标题和唯一操作保持固定。所有 locale 共用同一份集中持有的中文文案,引用内容会提升为独立的视觉段落,但不会改变该字符串。通过 Enter 关闭浮层后,模态所有权会交还给现有 FIFO 管理器;该管理器恢复编辑器,同时保持正常的启动 banner、transcript文本记录和焦点行为不变。
## 验证
聚焦单元测试固定指定 SVG 与中文文案的 hash、版本递增、并发独占确认、格式错误的标记、持久化重试、Escape 行为、ASCII 回退、宽度分级选择、有界渲染和低高度滚动。真实 Loader/PTY 用例覆盖 60、80、120、160 列以及一个低高度 viewport产出语义终端快照证明同一个 `DSH_HOME` 下首次启动后再次启动会抑制展示,并证明恢复会话不会追加任何由通知衍生的用户消息或轮次;普通终端退出的生命周期事件保持不变。
## 曾考虑的替代方案
**复用 TUI 的 `welcome` 副标题。** 它是一行瞬态 header正常职责是标识无标题会话。所需正文和操作要么会被裁剪要么会永久挤占普通启动界面。
**复制 Claude Code 的启动图形或构图。** 其清晰的视觉层级是有用的产品证据,但图形、布局和品牌处理属于另一个产品。官方 DeepSeek SVG 提供直接的品牌来源,而终端构图围绕本通知的文案与响应式约束独立推导。
**手绘原创鲸鱼。** 自由绘制的轮廓可以具有辨识度,却仍可能与官方标志的身体、内部负空间、鳍和尾部不一致。对精确路径进行栅格取样,可以明确呈现终端限制,并让每个分级都能追溯到同一个源资产。
**在会话事件或共享设置文档中存储布尔值。** 会话状态的生命周期不正确,还会污染回放或模型可见历史。聚合文档为记录一个不可变事实,需要承担跨进程读取、修改、写入锁;以原子方式替换的版本标记不存在更新丢失问题。
**允许 Escape 或稍后提醒操作。** 两者都会让取消与确认无法区分,或引入本通知并不需要的提醒策略。正常退出进程仍是中止路径,并会让该版本保持未确认状态。
## 后果
每个 Harness 主目录在每个文案版本中都会收到一次通知,且仅在用户成功按 Enter 确认后停止展示。维护者可以在一个小型 owner 文件中编辑所有 locale 共用的中文文案和版本,也可以在独立的视觉 owner 中更新官方 SVG 与衍生静态栅格图,无需到各个快照中查找完整正文副本。
终端无法直接显示 SVG 矢量图,因此其忠实呈现受分辨率限制。较小分级会保留取样后的轮廓,但必然损失精细细节;低高度终端优先保障正文可读和操作始终可达,而非展示品牌图形。在预发布阶段,标记格式有意采用每个版本一个文件;旧标记无害,也无需兼容性读取器。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-31-experimental-subcommand-gate.md
2026-07-31-experimental-subcommand-gate.md: a12c93935805a9ab19d4a15905f90783851eabba
2026-07-31-experimental-subcommand-gate.zh.md: ee05ec2c1c488e9d815bc150135202963f030ae7

View File

@@ -0,0 +1,36 @@
# Agent Note: experimental subcommands gate behind `--experimental` or `DSH_EXPERIMENTAL=1`
Status: implemented
Archived: 2026-08-03
English | [中文](2026-07-31-experimental-subcommand-gate.zh.md)
## Problem
The `meta` and `upgrade` entry points carried their experimental status in their names: `dsh experimental-meta` and `dsh experimental-upgrade`. The prefix made every invocation verbose, and renaming a command at stabilization would break every reference to it — muscle memory, scripts, and docs alike. The status belongs in an opt-in gate, not in the name.
## Decision
`dsh experimental-meta` is `dsh meta` and `dsh experimental-upgrade` is `dsh upgrade`. Each runs only when the invocation passes its `--experimental` flag or the environment carries `DSH_EXPERIMENTAL=1`; otherwise the command fails loud on stderr with exit 1, naming both opt-ins. Per the pre-release stance, the old names are gone with no aliases, and `args.spec.ts` pins their rejection.
The gate has two halves with one owner each. The per-invocation half is a Commander `--experimental` option on each experimental subcommand, checked inside its action after the leaked-parent-option rejection. The environment half is a boolean `parseDshArgs` parameter: `bin.ts` reads `process.env.DSH_EXPERIMENTAL === '1'` at the process boundary (after `loadEnv`, so a project `.env` can set it) and passes the result down, so the parser's environment dependency is explicit in its signature and the tests need no env mutation. `1` is the only enabling value — the variable is an explicit opt-in, not a truthiness check.
Stabilizing a command later means deleting its `--experimental` option and `requireExperimental` call; the name does not move.
## Testing
`args.spec.ts` pins both admit paths, bare-name rejection, old-name rejection, and leaked-option rejection under the env opt-in. `built-bin.e2e.ts` proves the assembled entry end to end: the gate diagnostic on stderr with exit 1, and that `--experimental`, `DSH_EXPERIMENTAL=1`, but not `DSH_EXPERIMENTAL=0`, reach the TUI's piped-stdio refusal — the next gate past this one. Both gated commands were also verified interactively in tmux: `dsh meta --experimental` and `DSH_EXPERIMENTAL=1 dsh meta` boot the TUI over the checkout, and `DSH_EXPERIMENTAL=1 dsh upgrade` seeds the `dsh-upgrade` skill.
## Alternatives considered
**Keep the `experimental-` name prefix.** Rejected by the user's direction: the prefix taxes every invocation, and stabilization would be a breaking rename instead of deleting a gate.
**A parent-level `--experimental` flag (`dsh --experimental meta`).** Rejected: the default surface is deliberately option-only with `enablePositionalOptions`, so parent options that leak across the subcommand boundary are treated as mistyped invocations. A parent flag consumed only by two subcommands would be exactly the leaked-option shape the adapter rejects everywhere else.
**Read `process.env` inside `parseDshArgs`.** Rejected: the repo validates at the process boundary and keeps typed seams pure; tests would have to mutate and restore `process.env` around each case.
**Accept any non-empty `DSH_EXPERIMENTAL`.** Rejected: the telemetry switch prefers off-by-mistake for a privacy control, but an experimental gate is an acknowledgement — `DSH_EXPERIMENTAL=0` must not enable the commands it names.
## Consequences
Daily invocations shorten to `dsh meta --experimental` and `dsh upgrade --experimental`, and a developer who sets `DSH_EXPERIMENTAL=1` in their environment gets the bare `dsh meta`/`dsh upgrade`. `dsh --help` marks both commands `(experimental)`. The gate costs one extra flag or env var until a command stabilizes, at which point the gate is deleted and the name is already final.

View File

@@ -0,0 +1,36 @@
# Agent Note: 实验性子命令由 `--experimental` 或 `DSH_EXPERIMENTAL=1` 把守
Status: implemented
Archived: 2026-08-03
[English](2026-07-31-experimental-subcommand-gate.md) | 中文
## Problem
`meta``upgrade` 两个入口把实验性状态写在名字里:`dsh experimental-meta``dsh experimental-upgrade`。前缀让每次调用都变得冗长,而在稳定时重命名命令会破坏对它的所有引用——肌肉记忆、脚本与文档皆然。这种状态应当由一个显式选择加入的门槛承载,而不是由名字承载。
## Decision
`dsh experimental-meta` 改为 `dsh meta``dsh experimental-upgrade` 改为 `dsh upgrade`。二者只有在调用时传入各自的 `--experimental` 标志、或环境中带有 `DSH_EXPERIMENTAL=1` 时才会运行;否则命令在 stderr 上明确报错并以退出码 1 结束,同时指明两种选择加入方式。依据发布前立场,旧名称已移除且没有别名,`args.spec.ts` 钉住了对它们的拒绝。
该门槛分为两半,各有其归属。按调用的一半是每个实验性子命令上的 Commander `--experimental` 选项,在其 action 内、泄漏父级选项的拒绝之后检查。环境的一半是 `parseDshArgs` 的一个布尔参数:`bin.ts` 在进程边界读取 `process.env.DSH_EXPERIMENTAL === '1'`(在 `loadEnv` 之后,因此项目 `.env` 也可以设置它)并向下传递结果,因此解析器对环境的依赖显式体现在签名中,测试也无需改动环境变量。`1` 是唯一的启用值——该变量是显式的选择加入,而不是真值判断。
之后要稳定某个命令,只需删除它的 `--experimental` 选项和 `requireExperimental` 调用;名字不再变动。
## Testing
`args.spec.ts` 钉住两条准入路径、裸名称拒绝、旧名称拒绝,以及在环境选择加入下对泄漏选项的拒绝。`built-bin.e2e.ts` 端到端地证明组装后的入口stderr 上的门槛诊断与退出码 1以及 `--experimental``DSH_EXPERIMENTAL=1`(而非 `DSH_EXPERIMENTAL=0`)会到达 TUI 的管道 stdio 拒绝——即此门之后的下一道关卡。两个被把守的命令还在 tmux 中做了交互式验证:`dsh meta --experimental``DSH_EXPERIMENTAL=1 dsh meta` 以检出目录为 workspace 启动 TUI`DSH_EXPERIMENTAL=1 dsh upgrade` 播种 `dsh-upgrade` skill。
## Alternatives considered
**保留 `experimental-` 名称前缀。** 按用户的指示拒绝:前缀让每次调用都付出代价,稳定时也会变成破坏性的重命名,而不是删除一个门槛。
**父级 `--experimental` 标志(`dsh --experimental meta`)。** 拒绝:默认界面刻意保持纯选项形式并启用 `enablePositionalOptions`,跨子命令边界泄漏的父级选项都被视为拼错的调用。一个只被两个子命令消费的父级标志,恰恰就是适配器在其他所有地方都拒绝的泄漏选项形态。
**在 `parseDshArgs` 内部读取 `process.env`。** 拒绝:本仓库在进程边界做验证,并保持类型化接缝的纯粹性;否则测试必须在每个用例前后修改并恢复 `process.env`
**接受任何非空的 `DSH_EXPERIMENTAL`。** 拒绝:遥测开关作为隐私控制倾向于误关而非误开,但实验性门槛是一种确认——`DSH_EXPERIMENTAL=0` 绝不能启用它所指名的命令。
## Consequences
日常调用缩短为 `dsh meta --experimental``dsh upgrade --experimental`;在环境中设置了 `DSH_EXPERIMENTAL=1` 的开发者可以直接使用 `dsh meta`/`dsh upgrade``dsh --help` 将这两个命令标注为 `(experimental)`。在命令稳定之前,门槛的代价是一个额外的标志或环境变量;稳定时删除门槛即可,名字已是最终形态。

View File

@@ -25,12 +25,21 @@
"architecture/2026-07-05-windows-fs-permissions.i18n.yaml": "sha256:7e61ee9bbd9de4bf3285a6f250d9625bd062e5fb90279dbffd64c820f1f7fe6b",
"architecture/2026-07-05-windows-fs-permissions.md": "sha256:03734da511eae3b0736f7cad73d9da76ae2f69f9d5ed09089b0121ccb135a861",
"architecture/2026-07-05-windows-fs-permissions.zh.md": "sha256:454848057ea905fe76c88d17264e71e71fb685f08f82088de6976878372865c3",
"architecture/2026-07-22-tui-interactive-extension-service.i18n.yaml": "sha256:1b4822af5c8d642b73e3a0b04fb0a1dea9f50d0147046fbef53f5e49c030fb91",
"architecture/2026-07-22-tui-interactive-extension-service.md": "sha256:ca6b2774f4821e66f7c8397f20fcd34926728ded853fa48cbe451db7a8d2f883",
"architecture/2026-07-22-tui-interactive-extension-service.zh.md": "sha256:5b060c7626ee796c27108be7467a5e4be0677d7525d383336e7ec31ddce5c303",
"architecture/2026-07-23-unified-session-query-service.i18n.yaml": "sha256:e8733b6543d9602ec206a087d9e89815f041f60fb57e93bee80e1309b9f03067",
"architecture/2026-07-23-unified-session-query-service.md": "sha256:28d003686f29ec5e072e51e73da353575bcdcba5af20fefdfad88340e1ddd32c",
"architecture/2026-07-23-unified-session-query-service.zh.md": "sha256:cfbe6525bc3b072fbc6db6bdca7a4d8cb4fc5507b1655bebc6af0589ed29ed31",
"architecture/2026-07-24-dsh-commander-argument-adapter.i18n.yaml": "sha256:cf99eda0e58b49630d5f95792459d7095666fafbef61f614165d5cdd031b7118",
"architecture/2026-07-24-dsh-commander-argument-adapter.md": "sha256:705654c8a43bcd199f72c21a77d24ca8bfa02447aff1c7f3e4e820be61dcd562",
"architecture/2026-07-24-dsh-commander-argument-adapter.zh.md": "sha256:3844f02d7659d18caf5d39e1131ed775c789cbf92dc44b4a446c7d6468aa5d00",
"architecture/2026-07-27-tui-chat-channel-module-split.i18n.yaml": "sha256:7b9dbe8b4a340640610abe7e54fb29492d77a187c176996a53d0e1fc7c8e1945",
"architecture/2026-07-27-tui-chat-channel-module-split.md": "sha256:3e2cd43f306a18b3eaf9bac23e6bdc3a5dbdc7388b7c399ce71e4f71b8f71d2a",
"architecture/2026-07-27-tui-chat-channel-module-split.zh.md": "sha256:d6b84fdcd91a2693b72cf6884b3a0c39e56e571b2b694f630805d894a6ba292f",
"architecture/2026-07-28-consolidated-tui-presentation.i18n.yaml": "sha256:1eb43c420a21b7a3adf0aa5274d9aa597187630a29d7e535c5e266f82e803665",
"architecture/2026-07-28-consolidated-tui-presentation.md": "sha256:e6fa4ea0c9d1d94942ab98de47c554f4e8aa3b639a1cce52113107b1dbb0f4b0",
"architecture/2026-07-28-consolidated-tui-presentation.zh.md": "sha256:01814434482a84ebd7f672eb5c26fc468b568e773452563bf39ba52ad25d054a",
"bug-fix/2026-07-20-code-mode-result-card-completeness.i18n.yaml": "sha256:1035dae11d049d32ab09fd7d4f950eceae44bf46ba498b3cfaf3c75102b9fb64",
"bug-fix/2026-07-20-code-mode-result-card-completeness.md": "sha256:6ca2c9d4df98be18813ef38b7462db880900b5bcd6944fbcd1b8f2258006b93e",
"bug-fix/2026-07-20-code-mode-result-card-completeness.zh.md": "sha256:ed85fa7f935e5f525d566bc37a92014614983e649c75de9a9f244939097a7991",
@@ -43,12 +52,33 @@
"bug-fix/2026-07-23-thinking-row-disclosure-target.i18n.yaml": "sha256:fd926967311f30ea4a222e88b845f95d74af75d1e94b24ebef59186593b9ca78",
"bug-fix/2026-07-23-thinking-row-disclosure-target.md": "sha256:92815c170972b1b91c3d75dd0c846c070805ec1e99ce368b6aae37b048e19869",
"bug-fix/2026-07-23-thinking-row-disclosure-target.zh.md": "sha256:0e09f5f5e14d74214e5157ceb5859c866bab6de701c47e2ce5c450866d75aecf",
"bug-fix/2026-07-23-tui-generic-card-markdown.i18n.yaml": "sha256:51fec8e6d7a998fd6189e70d32a6be293500abc8435b23257449e6f6d15a0053",
"bug-fix/2026-07-23-tui-generic-card-markdown.md": "sha256:7dda1114a392737465837e014059c1049da37330e3069b74f6e2ea34eccbce6e",
"bug-fix/2026-07-23-tui-generic-card-markdown.zh.md": "sha256:5040a1aaa8ff9dddeeb572a628410a79d870a90f84be00bf421e4b63e1974a4b",
"bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.i18n.yaml": "sha256:8ac9e40a4a7ac1f526b5a23656579414322bd947d991cfc02c0be0e1aacf2d68",
"bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.md": "sha256:5a1f6ba4baf25ad412eda3601ee074aeb607388da0375979797797a81eeba9da",
"bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.zh.md": "sha256:a7bbc530b9fc5e31ceb0f72d87edc720a0bd04ce7613b2ca9f9704cd5e7f682e",
"bug-fix/2026-07-26-intent-draft-same-tick-echo.i18n.yaml": "sha256:c623947c4fa00e6d4b51792c7972ba09582bbcb7605beb373725c0dd666f2c81",
"bug-fix/2026-07-26-intent-draft-same-tick-echo.md": "sha256:fa8b1417b2cdd3deecbf8e55bdddd73dd3a8c6e3486fd399b0b8bdf317e56373",
"bug-fix/2026-07-26-intent-draft-same-tick-echo.zh.md": "sha256:00ce72552dbaa11562fbc541343a5d33f9449edabbe6dd354eb879a7d4d530f8",
"bug-fix/2026-07-27-tool-card-single-row-fields-inline.i18n.yaml": "sha256:4b94aded16c60628d22414dce524e8a98a8af4fff298805ee7efc63cae02c90d",
"bug-fix/2026-07-27-tool-card-single-row-fields-inline.md": "sha256:40adcd522a9a2eeacc6f2b0196d1f24888a4f57830b7490a3be3d78c86c4e968",
"bug-fix/2026-07-27-tool-card-single-row-fields-inline.zh.md": "sha256:a79d56c9b781442ee596b47707d1a8c80abcd6466094b01802189c8e55f16da7",
"bug-fix/2026-07-27-tui-diff-card-redundant-path-header.i18n.yaml": "sha256:8613a1cfcf4b9c7fafa78a8d8565e2a65ef0335b7b826af9b2bb32097836af55",
"bug-fix/2026-07-27-tui-diff-card-redundant-path-header.md": "sha256:1bd344aec5454d2a2d6e1e6a32eff035c4a99c3df409f2624b39fd32e23ee402",
"bug-fix/2026-07-27-tui-diff-card-redundant-path-header.zh.md": "sha256:0a1747006efb1a4b67feceb9b627a437a0f023158e90ae86e1fe8aef76485384",
"bug-fix/2026-07-27-tui-step-timing-trails-tool-cards.i18n.yaml": "sha256:280b93ece72662501f65edd58a00cdafb5b5941e4ef1314d7198fab18950cb03",
"bug-fix/2026-07-27-tui-step-timing-trails-tool-cards.md": "sha256:112bdbde16b6023eeb5b8a79cd2a711385e7198d51bbfc0520e9612acaa95c8a",
"bug-fix/2026-07-27-tui-step-timing-trails-tool-cards.zh.md": "sha256:fc4e7f778ea63c4583cf81132c264cf6c4b9cc3e1818778061b0497ff16b8ef6",
"bug-fix/2026-07-30-tui-adapter-registration-race.i18n.yaml": "sha256:50b7a32e11591719c249258ecc2ec0f45e58f1a04050d2e53f6e2650f58ba137",
"bug-fix/2026-07-30-tui-adapter-registration-race.md": "sha256:7e17eb1dd8f92e1efb7a18477df277b13580840b473ffe8a5309fc70ec3cfa3e",
"bug-fix/2026-07-30-tui-adapter-registration-race.zh.md": "sha256:efcbd3d82af6a58677efe1a0580edd715945b6a93418fde47badac9c01a29866",
"bug-fix/2026-07-31-tui-diff-context-line-accounting.i18n.yaml": "sha256:cd39ae2646fdc6827bf29a63953b5463faa37d5b404ae8cc3c0913c47bc92d0c",
"bug-fix/2026-07-31-tui-diff-context-line-accounting.md": "sha256:57066bccd22c2dc2c3546b363de73d13b55ff8683ee12b17a81ed2bcf536645b",
"bug-fix/2026-07-31-tui-diff-context-line-accounting.zh.md": "sha256:a658d886c5eb203f5f30a6fac70ad18e4a24cf756746254723d8f1d144653c04",
"bug-fix/2026-08-03-tui-long-session-render-costs.i18n.yaml": "sha256:f65f7bf8fc84c7a1f022ee393c8d969c06d9bde8bed3a0206de86fb35b246ac6",
"bug-fix/2026-08-03-tui-long-session-render-costs.md": "sha256:6ecf2ef831f527f361ade18a882d79bc6eccf15cc676d05728e7753f41cde051",
"bug-fix/2026-08-03-tui-long-session-render-costs.zh.md": "sha256:5f44e707b332e13fa06d625212173ea055c1c3c0aee60888435a0ff099ec6037",
"feature/2026-06-14-acp-agent-client-protocol.i18n.yaml": "sha256:006795baa43ae962a8d125cc0f1e9f134bc2ee9fb758b6e7669e3fa0126e1918",
"feature/2026-06-14-acp-agent-client-protocol.md": "sha256:6828c0af74bb3fb96206ca6b21c0e56a000b50e4744aad4bc2c05092f3a5a31b",
"feature/2026-06-14-acp-agent-client-protocol.zh.md": "sha256:ba104e841a1fb84edbd3b6c8119d50445b7785255a7a8d13bb9ac8a2cb4d2e69",
@@ -79,9 +109,15 @@
"feature/2026-07-14-time-context-plugin.i18n.yaml": "sha256:670c093817c77e093562e02f43984d42ed44ebcced7c91d09366839e412d05e1",
"feature/2026-07-14-time-context-plugin.md": "sha256:618b121da38a8b610bcadaecf121ca823b2c8c13598c012b350c214b82fd238f",
"feature/2026-07-14-time-context-plugin.zh.md": "sha256:1e9eee8ba427a6f2ee08c79e2fcb33c0948e67a80758fdf9f8c9f7dff9aea361",
"feature/2026-07-17-dedicated-full-screen-tui-front-door.i18n.yaml": "sha256:c04ec462f846887f349ffeb48aef8febf50f06903e7177ffd7679a694f6c1a57",
"feature/2026-07-17-dedicated-full-screen-tui-front-door.md": "sha256:feb70faaad016965e8cc1bd7339947e48937fb3e8ea1425dada581073396f50c",
"feature/2026-07-17-dedicated-full-screen-tui-front-door.zh.md": "sha256:71e4a4abf01e5fc46f7da60e0a66bca659c1ee3c4ea2c4bc20587844f33920fe",
"feature/2026-07-20-tui-startup-slogans.i18n.yaml": "sha256:265d1fd79dae6c785201c81ffe2de3baa9fe9e3b6c0f84aac79c90f4040ced15",
"feature/2026-07-20-tui-startup-slogans.md": "sha256:aaaab4b419d35ce24317b7730f15af0029878bf3d17c6f184b05138c2cd44930",
"feature/2026-07-20-tui-startup-slogans.zh.md": "sha256:01fba568cd92e9c54857f6dba1a3a5a6a4d0e906f36915d7e64682e67d456708",
"feature/2026-07-20-windows-tui-support.i18n.yaml": "sha256:fa7253a8e20308b216c21d201720a4b7d6b74ccc9a650c0d30081c9e5b6bf04c",
"feature/2026-07-20-windows-tui-support.md": "sha256:abf80ade38ec4d9e558c0fa9b0ecc8877acc18442f4ed945ebc2f4b153d85087",
"feature/2026-07-20-windows-tui-support.zh.md": "sha256:6690a7693b8a4c8ce495c13ae84d9dc81f41c691d217db3152ea0d926dffa0f8",
"feature/2026-07-21-dsh-system-prompt-source-path.i18n.yaml": "sha256:22efaf3237425fecbac1b40a444454e0fc244a3c85c2f6a14535de22ea777719",
"feature/2026-07-21-dsh-system-prompt-source-path.md": "sha256:5fa554932c62a8bbd5a619581710d7f8b6b65d79ec1e340129cda96d279c5ae3",
"feature/2026-07-21-dsh-system-prompt-source-path.zh.md": "sha256:995cd593074881c72510a6af3ba80108bbf986d49508cce9f698c2fcb493fd23",
@@ -109,6 +145,12 @@
"feature/2026-07-21-tui-reload-command.i18n.yaml": "sha256:9be416ccd681aed0781fdfd2c44c4821c1e45f2a0deccb1f2b47d46163bde488",
"feature/2026-07-21-tui-reload-command.md": "sha256:b8616457822ae87c90062308bc8c0d2badd5f368092ec65847d0d9520b1ac372",
"feature/2026-07-21-tui-reload-command.zh.md": "sha256:c24bfcb0df13977a9c11c4d0fe433169e535b5f764995b668430dbb14a8e6b33",
"feature/2026-07-21-tui-resume-command.i18n.yaml": "sha256:50526c2ec1bf5fe4f2f912e70fc8145f6978907f80f9d716ca3bcb17cd028933",
"feature/2026-07-21-tui-resume-command.md": "sha256:821cfee22ff6ed491807ecca492538e2dd18ce4b00e7c09b6f4e8604cb02b97b",
"feature/2026-07-21-tui-resume-command.zh.md": "sha256:7fe8e638df0f1977bb93a9efd973d4f69a19ad48e43dae6b1bc8eb70758a561b",
"feature/2026-07-21-tui-skill-slash-command.i18n.yaml": "sha256:c711fd237649c81705a6bc5b20d7b4585ced3166acbc8cc3c2877d97bb386edf",
"feature/2026-07-21-tui-skill-slash-command.md": "sha256:b2904829540a1801852e072937dc259830e93f0c719590da0a189879aca1d7a6",
"feature/2026-07-21-tui-skill-slash-command.zh.md": "sha256:350ed930218b5c4dfb721ebaceac419f4cb2c73f047c004747ba01535a77cab5",
"feature/2026-07-21-tui-steering-queue-badge.i18n.yaml": "sha256:a029da558a6e14e1f13269960b98273ca9af0141579967acfbc19b656775f4a5",
"feature/2026-07-21-tui-steering-queue-badge.md": "sha256:9aabd68c8910fdc7e7b05674492ddb8dc9285dd691fe554adcb84026fb846cc8",
"feature/2026-07-21-tui-steering-queue-badge.zh.md": "sha256:919fd737866c3700f945751628071dab89eabdbf8f809deab93b9e6fbe2c8c59",
@@ -118,15 +160,60 @@
"feature/2026-07-23-trajectory-step-cell.i18n.yaml": "sha256:fe2e935a0affdef877902a40d9861ef5f55b30f40650469f6a52a4d45a92793f",
"feature/2026-07-23-trajectory-step-cell.md": "sha256:185e3b87174cb6d2f2d2271fd2a74b1517d03e8570be602570d027bf6002d106",
"feature/2026-07-23-trajectory-step-cell.zh.md": "sha256:51f46be43d2f5c4f78a05ed9aeec92d1f33ac988f45cf24d35528e9c43828ef3",
"feature/2026-07-23-tui-file-reference-autocomplete.i18n.yaml": "sha256:f95d9b37369ca8a0eecb07784759e94b5e0e85b7565c7256008c2f9db0d0c157",
"feature/2026-07-23-tui-file-reference-autocomplete.md": "sha256:5a6fd182d9080d757b4ad565e98233b0f128589cbfa6d741dc76a7cc0f41b5c6",
"feature/2026-07-23-tui-file-reference-autocomplete.zh.md": "sha256:8676fd1f57fd705c25f8c37fbfd91126a85387d5b6261d22470b035723a6ad73",
"feature/2026-07-23-tui-status-prompt-tools.i18n.yaml": "sha256:e8072d6661c91b43d13ef88b69fc49b9a312a7b5cb0b6f0171c0fdafc36b0435",
"feature/2026-07-23-tui-status-prompt-tools.md": "sha256:0dd669c70ac34024b389d76c81dcf4f1d5b747f56b7d6d82587c8c15a421c0a2",
"feature/2026-07-23-tui-status-prompt-tools.zh.md": "sha256:6a488b18e1516887d6073375651fba59c3a266b888f9ca2d4804f6681838059b",
"feature/2026-07-24-configurable-tui-prompt-theme.i18n.yaml": "sha256:694de3851ca1e3de76c4f4fb7f3581ad0df568a99046c905f56d24a54407a5ba",
"feature/2026-07-24-configurable-tui-prompt-theme.md": "sha256:694136b6d9bf7de25c240c373cb268cf798f8617c206d53c1d0ef7443e75adeb",
"feature/2026-07-24-configurable-tui-prompt-theme.zh.md": "sha256:ee083f864bf6915a887b7883d14a6662ff6ecb4e702f4ac0f583d0c8f823bf50",
"feature/2026-07-24-new-session-clears-to-empty-state.i18n.yaml": "sha256:978638cbf18bc6dce9fea0817654f41cc307f99004a637b85a63ae2208fe9095",
"feature/2026-07-24-new-session-clears-to-empty-state.md": "sha256:b6b71d3883a167056070713e3dffb5046de953bdd218074d17c88e7690e03d83",
"feature/2026-07-24-new-session-clears-to-empty-state.zh.md": "sha256:82a80b48337487029acd05a0137d268f0850f46801fa44a0e62733cacd00d5e9",
"feature/2026-07-24-tui-question-dialog-multiline.i18n.yaml": "sha256:b8a90f13f579c17476e94740079ac2e058f2a97ec4d723fe2841398426129858",
"feature/2026-07-24-tui-question-dialog-multiline.md": "sha256:0bf07eb8731cf6928db275f160ba36b07ce93d53873eec2fb6dcaabaa0c57f76",
"feature/2026-07-24-tui-question-dialog-multiline.zh.md": "sha256:85f5dedc794014152792557158b826650b147fe6d439e71ea23c115d2c2c84de",
"feature/2026-07-24-tui-shell-prompt-editor.i18n.yaml": "sha256:f40b5ba2d22e3a52fea2d3d0f3f481ef710764c3ec4379e7c859f52c61434b1b",
"feature/2026-07-24-tui-shell-prompt-editor.md": "sha256:7bbb99108a7dfe219c031f77cafa5bbcfacd1dd05080822fe11e0ca58da4b3f5",
"feature/2026-07-24-tui-shell-prompt-editor.zh.md": "sha256:6e20225724941290bbdc9af86d4f91e1beb93bde5c2d67144f0a32c4abb30e7b",
"feature/2026-07-26-code-mode-trajectory-waterfall-spans.i18n.yaml": "sha256:916525fdc3a12061928380fd8f7b81cd9763a7873663aad564843fccab0ccedc",
"feature/2026-07-26-code-mode-trajectory-waterfall-spans.md": "sha256:a822963e4c34c9737681d6d70d8167731d4350e66ef7f356684ad096c04fa7ab",
"feature/2026-07-26-code-mode-trajectory-waterfall-spans.zh.md": "sha256:ef01163adf1245f75cc8291db70d389314dee43b6bff958a9da49b097531218d",
"feature/2026-07-27-assistant-timing-header-trailing.i18n.yaml": "sha256:e5a8aa9e5194dae6f4369299a64acae2c751d7f85e457e8dec1c14e1dbcee3e9",
"feature/2026-07-27-assistant-timing-header-trailing.md": "sha256:85fac4a712ac5b4d61c224eddede08edb27db2e3718321c094a55034c75ce984",
"feature/2026-07-27-assistant-timing-header-trailing.zh.md": "sha256:260d115d2da429eeac73b7256944e60cb03a93714f5d13b5e219dff316f5a958",
"feature/2026-07-27-tui-running-glyph-smooth-fade.i18n.yaml": "sha256:e440b7f4800f722f1a0f9d089081ac19a7fbf809cb75136566ae363b4aa0aab9",
"feature/2026-07-27-tui-running-glyph-smooth-fade.md": "sha256:cc58dce4788c8478c97ce589e4519cfb1b6a6cc3ec46d263f3038823231e5ae5",
"feature/2026-07-27-tui-running-glyph-smooth-fade.zh.md": "sha256:c1735d5bd4b5e75989a9aead8af29136e7fadc01e79fb8c8cc68601a31fbf94a",
"feature/2026-07-27-tui-tool-card-header.i18n.yaml": "sha256:1ed2c377f6d2c589195a00e84f92e6511b9a0793a2684a75ec3a0ca1f2b98670",
"feature/2026-07-27-tui-tool-card-header.md": "sha256:ebb3d913960cdb0949a753c5811bd7d68a96f64f3b0549cd2ab7e4997b6b2d28",
"feature/2026-07-27-tui-tool-card-header.zh.md": "sha256:402a619dce15534fbf88a59361b693eeb2121584fbb2e7af4e93eeaccaf2d0ea",
"feature/2026-07-27-user-message-icon-actions.i18n.yaml": "sha256:b33e480f19ec58c8c60417a6c03999953d463ca54606a5ac80ec528edf57c49b",
"feature/2026-07-27-user-message-icon-actions.md": "sha256:b6332e67c6dad0a3fcdb597cec9e4dc32b44ad33665f39c1a50501cf38d3f5ad",
"feature/2026-07-27-user-message-icon-actions.zh.md": "sha256:0fc824eac66a18063f7098e1c395c09b580d5a30b96f2b856608c084212c2ac2",
"feature/2026-07-28-dsh-guided-skill-session-commands.i18n.yaml": "sha256:cf56e0ff7f2af7a5b50b27818fddb66a0c5300de98fc5733673ce094cd14bb22",
"feature/2026-07-28-dsh-guided-skill-session-commands.md": "sha256:9234465259f89fbedd5dfc07f26acbc9e48e16ce829cc6ada672c4d597b0b255",
"feature/2026-07-28-dsh-guided-skill-session-commands.zh.md": "sha256:57ee966476f755c1a6832d35d5f045f9d9e63a97a824e8a3723ba914502ade7f",
"feature/2026-07-28-dsh-meta-source-workspace.i18n.yaml": "sha256:f7c6b5db53c32c7475f4f6bb3ae189ed8d67f2163196ab315d6f81fde3aa37d9",
"feature/2026-07-28-dsh-meta-source-workspace.md": "sha256:ee8b2f6055b27957fa27258f26a07183b7102933af68c64d0df418e32d3d8754",
"feature/2026-07-28-dsh-meta-source-workspace.zh.md": "sha256:0b10db368e04c24be03569a56ec4b69fed66b70c39a3c4f168b5f1c912efcd96",
"feature/2026-07-29-tui-hidden-mode-assistant-fold.i18n.yaml": "sha256:0865835802348b730542adbe6b7db613750f3786993c6a14dbb2f47686c13c70",
"feature/2026-07-29-tui-hidden-mode-assistant-fold.md": "sha256:a5fefebd802e2d9c3c79c7852c1c34c7bbef3f2ac2150d224608b9ec44e966ad",
"feature/2026-07-29-tui-hidden-mode-assistant-fold.zh.md": "sha256:21bccd1e07ec8dc73b618f428461848bb90b6235afe0b842afb0afab2d5cc575",
"feature/2026-07-30-compaction-progress-visibility.i18n.yaml": "sha256:4c2267054ad5d73aecc8d39d138a0cb532981b33175252e67252d07c3314b0f5",
"feature/2026-07-30-compaction-progress-visibility.md": "sha256:2dfe07244cd784f27a9e5850801d40e96eae21a20ba10aaf56f9a793cdf5b505",
"feature/2026-07-30-compaction-progress-visibility.zh.md": "sha256:6180b8aff0536147ab6ed6a78ecdbe1448fd12d89407746ecb1c7c05c73d4d60",
"feature/2026-07-30-tui-details-command.i18n.yaml": "sha256:033cea6df0a16fc68cbdb435babdc6e75c1199a8e70e1a71d87c800c40f5a044",
"feature/2026-07-30-tui-details-command.md": "sha256:a13478d4e55ec6d358209b51b541413ec75d0e20dfc22196ace28020f03f0c2d",
"feature/2026-07-30-tui-details-command.zh.md": "sha256:de9c449b98468cef34ce4f9a9d2a854a5d8905eecd61f80e27a9a0e4495e9901",
"feature/2026-07-30-versioned-tui-first-run-welcome.i18n.yaml": "sha256:4c3fc380b0512ad7c00baacd0ac610e1a78ae45374311d9bd43bab6b5e29e630",
"feature/2026-07-30-versioned-tui-first-run-welcome.md": "sha256:296f153e6c839f3743078e4f5aab3b2befc211c934835238668c57bdeae52231",
"feature/2026-07-30-versioned-tui-first-run-welcome.zh.md": "sha256:82871a9cca1fec46bb08a5b39daad28a44bb2419dea367b4ae41af3cf07bfa65",
"feature/2026-07-31-experimental-subcommand-gate.i18n.yaml": "sha256:d223669bebbf6ea65b4ec636e8e7ed618eff389117335946be713897151c6968",
"feature/2026-07-31-experimental-subcommand-gate.md": "sha256:8fdee37340f7e72397cf2f440a2ca70639a987d07f0c2102e02e79c0fec4bfeb",
"feature/2026-07-31-experimental-subcommand-gate.zh.md": "sha256:bcdec0f82319670a1d1de54a27b103b5e2d86306b884a5f9f415b89cd5a373f4",
"process/2026-06-11-doc-sync-enforcement.i18n.yaml": "sha256:33b6d5874427bd7a2bd82e7e2f4f482b12448b2464aef15a9c57975edb48554d",
"process/2026-06-11-doc-sync-enforcement.md": "sha256:aa2fe83d519fc30d48dff19e596e83c8922aacc9e063e14fe2cc35b769b9100e",
"process/2026-06-11-doc-sync-enforcement.zh.md": "sha256:698017bd35f030fdea3eac51df9e43138c48140f504739d687b7251d13fced2b",
@@ -232,6 +319,9 @@
"simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.i18n.yaml": "sha256:7acf002ea8c1533f052c7bfc0c4e3da013ecf43c5872866a3ee4a8c2691c5e33",
"simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.md": "sha256:f18a913096b7defd2192c4bac888a33f68075c3662703a0e28a6146897d17777",
"simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.zh.md": "sha256:ff48a37673c97059536fe5b61aff746133eac682145550badb049eb5c83b097c",
"simplification/2026-07-27-copyable-transcript-no-gutter-bar.i18n.yaml": "sha256:821f96f3e203e03b80553c07b10a511926bb5014be95c7df6bffb30c8e226d31",
"simplification/2026-07-27-copyable-transcript-no-gutter-bar.md": "sha256:4b6aa150bbc8a4da0acac4d20f5fb8c2b77fef7e9c4c4dba8fd8e84dec36d619",
"simplification/2026-07-27-copyable-transcript-no-gutter-bar.zh.md": "sha256:5225e627ff301be171434a5b9f18905fe1578f50eb2d4bf9998e126aba6cc3e3",
"testing/2026-06-20-remove-redundant-snapshot-log-expected-output.i18n.yaml": "sha256:4177012c0821a8c22499852ecdf096af56d7263cb91c5d9d1bcd552cc26a3e00",
"testing/2026-06-20-remove-redundant-snapshot-log-expected-output.md": "sha256:45234e7cc04b6010c6141f8d5924c04547300098f96262d423c50108e7c7011a",
"testing/2026-06-20-remove-redundant-snapshot-log-expected-output.zh.md": "sha256:15e5a4ad3dee0bb711480cabe45cd97ec37bbdba19c2c2b47d1e9c203b07a48b",
@@ -249,6 +339,9 @@
"testing/2026-07-06-pin-request-header-content-in-one-scenario.zh.md": "sha256:cac75d4475666239bbe0030b90c0fa7cc66024af5b9f8ef217e53018be64890e",
"testing/2026-07-08-shared-acp-snapshot-package.i18n.yaml": "sha256:fc37fcdfe8744f8c8f39eda3f494eed25ee8c4d5322d1f3f671eabd2c5d7026e",
"testing/2026-07-08-shared-acp-snapshot-package.md": "sha256:285b4a3c0b1ef7a837e6713cf0192ddc8a26101f6737fa3923682a9e91350c50",
"testing/2026-07-08-shared-acp-snapshot-package.zh.md": "sha256:02da3f910c2060f70038a0d86a7ddae4a8890905600440e1373412f54fbdcea8"
"testing/2026-07-08-shared-acp-snapshot-package.zh.md": "sha256:02da3f910c2060f70038a0d86a7ddae4a8890905600440e1373412f54fbdcea8",
"testing/2026-07-18-tui-terminal-state-snapshots.i18n.yaml": "sha256:c1a22174274b9f34ef4368039b3547f221507b040bd51b87af73a2722ee6b4d2",
"testing/2026-07-18-tui-terminal-state-snapshots.md": "sha256:9a7fdcbeafc34376cb049b9668e0f4e9e541f523116fb11c3af9d35c2963e908",
"testing/2026-07-18-tui-terminal-state-snapshots.zh.md": "sha256:26750f240f6c8a7b28746f62fe161b357e9c5dd52867cc7037399f1ed6ff37fa"
}
}

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/simplification/2026-07-27-copyable-transcript-no-gutter-bar.md
2026-07-27-copyable-transcript-no-gutter-bar.md: a4eca191d28a834b05a6839997aab602852912c0
2026-07-27-copyable-transcript-no-gutter-bar.zh.md: b1f661cb76bb879e414a0dcfd08fc9349ae070c7

View File

@@ -0,0 +1,35 @@
# Agent Note: Copyable TUI transcript without gutter bars
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-27-copyable-transcript-no-gutter-bar.zh.md)
## Problem
The TUI grouped user prompts and tool cards behind a colored left-gutter bar (`▌ `) prepended to every body line, and indented assistant and system blocks by one column. Both are per-line prefixes: a terminal mouse drag-select over the scrollback captures the leading `▌ ` or the leading space on each line, so copy-paste of a message, a tool's output, or a code block pulls in decoration the user must strip by hand. The bar was the transcript's only per-message separator, so it could not simply be dropped without another way to tell messages apart.
## Decision
The scrollback carries no per-line prefix. Messages are separated only by a bold, underlined role header in the role color and blank-line spacing, both of which the terminal already inserts around each block. The underline gives each role a distinct visual band without a background fill, so it reads on any terminal theme and never enters the clipboard:
- User and steering prompts (`UserMessageComponent`) are a plain `Container`: a bold, underlined accent `You` / `Steering` header line (via the shared `messageHeader` helper), then the prompt body at column 0.
- Assistant blocks render a bold, underlined `Assistant` header, then reasoning and text at column 0, with the timing line at the end of the block (the former `paddingX = 1` indent is gone).
- Tool cards drop the `GutterBox` wrapper. The card status (pending / error / success) colors the whole title line — the status glyph (`◌` / `✕` / `✓`) plus the title text share one color, bold and underlined to match the role headers — instead of a colored bar beside an uncolored title. The body renders unprefixed; body lines still pass through `Text` at the terminal width so overlong raw tool output wraps rather than overflowing.
- The `GutterBox` class is deleted; nothing else used it.
A drag-select over any of these regions now copies exactly the message text.
## Alternatives considered
- **Keep the bar only on user messages, drop it on tool cards** — leaves tool output, the most-copied region, still polluted. Rejected: the goal is a wholly copyable transcript.
- **A single top rule or bar on the header line only** — the body copies clean, but selecting the header still captures a glyph, and it reintroduces a decoration character for no distinguishing gain over the underlined role header.
- **Indent grouped bodies instead of a bar** — leading spaces still enter the clipboard, so it does not solve the copy problem; explicitly ruled out.
- **A filled background band on the header** (reverse video, or a 256-color muted background) — gives each role a strong color block, but the saturated ANSI fill reads as too heavy and the 256-color shades are fixed rather than theme-remapped. The underline gives per-role distinction with a far lighter footprint.
## Consequences
- Copy-paste from the scrollback is clean with no user post-processing. This was the motivating win.
- The transcript is flatter than the gutter-bar layout, but each role's bold, underlined header in the role color plus blank-line spacing keeps message boundaries clear without any left-edge fill. Tool-card status stays legible through the colored, underlined glyph and title.
- Box-drawing borders (`│`) on transient overlays — status panel, model selector, resume list — are untouched. They are not scrollback message content and are rarely copied.
- The affected keyless TUI `*.expected.txt` snapshots were re-recorded by fixture replay (no API key needed; the recorded LLM sessions are unchanged, only the render differs). Interactive boot and a round-trip prompt were verified in tmux.

View File

@@ -0,0 +1,35 @@
# Agent Note: 无 gutter bar 的可复制 TUI transcript
Status: implemented
Archived: 2026-08-04
[English](2026-07-27-copyable-transcript-no-gutter-bar.md) | 中文
## Problem
TUI 此前把用户提示词和工具卡片分组在一条彩色左侧 gutter bar`▌ `)之后,该竖条被逐行加在每一行正文前面,并把 assistant 与系统块整体缩进一列。两者都是逐行前缀:在 transcript 上用鼠标框选时,每一行开头的 `▌ ` 或前导空格都会被一并选中,因此复制一条消息、一段工具输出或一个代码块时都会带上装饰字符,用户必须手动清理。该竖条又是 transcript 中唯一的逐条消息分隔标记,所以不能在没有其他区分方式的情况下直接删掉。
## Decision
transcript 不再带任何逐行前缀。消息仅通过以角色色渲染的粗体带下划线角色标题和空行分隔,而这两者本就由终端在每个块前后自动插入。下划线让每个角色获得清晰的视觉分带,且无需背景填充,因此在任何终端配色下都可读,也绝不会进入剪贴板:
- 用户提示词与 steering 提示词(`UserMessageComponent`)改为普通 `Container`:一行粗体带下划线的强调色 `You` / `Steering` 标题(经共享的 `messageHeader` 辅助函数生成),随后是位于第 0 列的提示词正文。
- Assistant 块渲染一行粗体带下划线的 `Assistant` 标题,随后 reasoning 与文本均在第 0 列渲染timing 行位于块末尾(原先的 `paddingX = 1` 缩进已移除)。
- 工具卡片去掉 `GutterBox` 包装层。卡片状态(进行中 / 错误 / 成功)对整行标题着色——状态字形(`◌` / `✕` / `✓`)与标题文本共用一种颜色,并同角色标题一样加粗且带下划线——而不再是未着色标题旁的一条彩色竖条。正文无前缀渲染;正文行仍按终端宽度经 `Text` 处理,使过长的原始工具输出换行而非溢出。
- `GutterBox` 类被删除;没有其他地方使用它。
现在对上述任一区域框选,复制得到的正是消息文本本身。
## Alternatives considered
- **仅在用户消息上保留竖条、在工具卡片上去掉** —— 会让最常被复制的工具输出仍然带有污染。已否决:目标是让整个 transcript 都可复制。
- **仅在标题行上加一条顶部横线或竖条** —— 正文复制干净,但选中标题时仍会带上一个字形,且相比带下划线的角色标题并未带来额外的区分收益,却重新引入了装饰字符。
- **用缩进代替竖条对分组正文缩进** —— 前导空格仍会进入剪贴板,无法解决复制问题;已明确排除。
- **在标题上使用填充背景带**(反色,或 256 色柔和背景)—— 能给每个角色一块强烈的色块,但饱和的 ANSI 填充观感过重,且 256 色是固定色而非随主题重映射。下划线以远更轻的方式提供了同样的逐角色区分。
## Consequences
- 从 transcript 复制粘贴无需用户做任何后处理。这正是本次改动的核心收益。
- transcript 比 gutter bar 布局更扁平,但每个角色以角色色渲染的粗体带下划线标题加空行分隔,无需任何左缘填充即可让消息边界保持清晰。工具卡片状态仍通过彩色带下划线的字形与标题保持可读。
- 临时浮层(状态面板、模型选择器、恢复列表)上的制表符边框(`│`)保持不变。它们不属于 transcript 消息内容,且很少被复制。
- 受影响的 keyless TUI `*.expected.txt` 快照均通过 fixture 回放重新记录(无需 API 密钥;所记录的 LLM 会话未变,仅渲染不同)。交互式启动与一次往返提示已在 tmux 中验证。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/testing/2026-07-18-tui-terminal-state-snapshots.md
2026-07-18-tui-terminal-state-snapshots.md: eef9777c1f3d19c03985c0d105223960dd7d4878
2026-07-18-tui-terminal-state-snapshots.zh.md: 50d2e1b81762538a08abda5608a7d26f2dd0b841

View File

@@ -0,0 +1,62 @@
# Agent Note: Snapshot semantic terminal state for the TUI
Status: implemented
Archived: 2026-08-04
English | [中文](2026-07-18-tui-terminal-state-snapshots.zh.md)
## Problem
The TUI is a stateful renderer. Its user-visible result depends on ANSI parsing, differential frames, wrapping, scrollback, viewport position, terminal width, focus, cursor state, and each tool's presentation intent. Unit tests that collect `Terminal.write()` fragments can prove event handling, but they cannot prove the final screen a terminal displays. The same screen may also be emitted through different write fragments, so pinning those fragments creates false regressions.
Component-line snapshots stop before ANSI reaches a terminal and miss cursor movement, clearing, styling, overlay composition, and reflow. Raster screenshots include font and platform rendering noise that is unrelated to the TUI contract. A completed flow built by directly appending plausible session events has another blind spot: it proves the renderer accepts those shapes, not that the production agent loop and tool implementations produce them.
The reusable TUI therefore needs a deterministic, reviewable representation of terminal state. A product deployment that ships it additionally needs recorded model journeys through the assembled stack and a smaller test at the real process and PTY boundary.
## Decision
Reusable TUI coverage has two complementary package layers:
1. `packages/ui/tui/tests/tui.spec.ts` tests event mapping, input routing, disposal, and error behavior directly.
2. `packages/ui/tui/tests/tui.snapshot.ts` mounts the production TUI against a headless terminal emulator for transient states that a completed session log cannot retain: in-flight streaming, pending tool calls, overlays, expansion, compaction reflow, errors, and shutdown.
The [explicit-config entrypoint decision](../simplification/2026-08-03-explicit-config-dsh-entrypoint.md) removed the product TUI composition, recorded application journeys, and PTY suite. A deployment shipping a terminal front door owns those assembled-application layers; package tests do not claim that product coverage.
### Removed application replay
The deleted application suite gave each scenario `session.jsonl`, optional child logs `session.<n>.jsonl`, and `terminal.expected.txt`. The primary log supplied user-authored `user/message` prompts and the recorded `assistant/chunk` sequence. `dsh-llm-replay` derived one model-call script per session and was the only mocked boundary; the agent loop, tools, workers, presenters, and TUI were production implementations.
That suite rejected a journey when its tool-call sequence differed, an expected event count was missing, a tool result was an error, a turn ended in error, a workflow lifecycle was incomplete, or the live child-session count differed from the fixture set. These checks remain the acceptance pattern for any future terminal deployment; they are no longer shipped fixtures.
The removed recording workflow used `DSH_SNAPSHOT=record` for model journeys and `DSH_SNAPSHOT=refresh` for derived terminal output. Removing the product entrypoint also removed those modes from the repository snapshot lane; reusable TUI snapshots are authored directly from package scenarios.
### Semantic terminal projection
The package-local `HeadlessTerminal` implements the same pi-tui `Terminal` interface as the process terminal and feeds every ANSI write into the pinned `@xterm/headless` parser. Snapshot code waits for synchronized frames to quiesce before reading state. The streaming checkpoint freezes the loader interval while allowing real wall-clock delay across one animation tick, so it pins semantic status rather than whichever spinner glyph the scheduler happened to render.
Each expected output projects dimensions, active-buffer and viewport coordinates, lifecycle and cursor state, rows, wrap markers, and non-default style ranges into text. Scroll-heavy cards capture the used buffer; overlays capture the visible viewport. Text and style remain separate so a reviewer can distinguish content changes from presentation changes without decoding ANSI bytes.
Every checkpoint enforces theme independence across the complete terminal state: no RGB colors, no palette entries beyond ANSI 015, and no explicit background colors. Reverse video remains valid for selection because it uses terminal defaults. Both suites own closed inventories that reject missing scenarios, missing checkpoints, and orphaned expected output files.
### Required scenario matrix
| Layer | Scenario | Contract pinned |
|---|---|---|
| Transient state | Streaming and pending advanced calls | In-flight reasoning/text plus pending Code Mode, workflow, and Cordis cards that disappear from completed logs |
| Transient state | Cards, interaction, layout, failure, and shutdown | Collapsed/expanded card families, question validation, compaction replacement, resize reflow, help/errors, cursor restoration, and terminal stop |
## Alternatives considered
- **Snapshot raw terminal writes** — rejected because differential rendering may change write boundaries without changing the screen, while cursor and clear sequences are unreadable in review.
- **Snapshot component render lines before terminal output** — rejected because it does not test ANSI parsing, cursor movement, overlays, viewport behavior, or independent components in one frame.
- **Build every completed flow by appending session events** — rejected because a hand-authored event sequence can drift from the agent loop, tool execution, child-session binding, or worker behavior while its presentation test stays green. Direct event construction remains limited to transient renderer states.
- **Reuse ACP stdout expected outputs as the TUI oracle** — rejected because a recorded model journey is transport-neutral but its presentation is not. A terminal deployment owns its expected output while it may reuse the same JSONL replay vocabulary.
- **Commit raster screenshots** — rejected because fonts, glyph metrics, antialiasing, and host terminal themes make them platform-sensitive and make semantic style changes difficult to review.
- **Use only PTY end-to-end tests** — rejected because raw PTY output is a stream of historical drawing operations, not queryable final state. PTY tests retain the real Loader/input/teardown boundary, while the emulator owns broad state coverage.
## Consequences
- Package snapshots fail when TUI event mapping or presentation breaks; they do not substitute for an assembled application's tool-path transcript.
- TUI visual regressions produce readable cell-and-style diffs, while JSONL fixtures retain the exact model chunks that made the production path execute.
- The emulator uses xterm's proposed buffer API. An xterm upgrade requires rerunning and reviewing the semantic projection; terminal-specific behavior still needs a PTY smoke owned by the deployment that ships it.
- Expected outputs deliberately encode wrapping and viewport behavior at fixed sizes. Intentional layout changes update and review the package semantic snapshots.

View File

@@ -0,0 +1,62 @@
# Agent Note: TUI 语义终端状态快照
Status: implemented
Archived: 2026-08-04
[English](2026-07-18-tui-terminal-state-snapshots.md) | 中文
## 问题
TUI 是有状态的渲染器。用户最终看到的结果取决于 ANSI 解析、差分帧、换行、回滚缓冲、视口位置、终端宽度、焦点、光标状态,以及各工具的呈现意图。收集 `Terminal.write()` 片段的单元测试可以验证事件处理,却无法验证终端最终显示的画面。同一画面也可能由不同的写入片段产生,因此固定这些片段会制造误报。
组件行快照止于 ANSI 进入终端之前,无法覆盖光标移动、清屏、样式、浮层组合和重排。栅格截图会带入与 TUI 契约无关的字体和平台渲染噪声。直接追加看似合理的会话事件来构造完整流程还存在另一处盲区:这种测试只能证明渲染器接受这些数据形态,无法证明生产环境的 agent loop智能体循环和工具实现会生成这些事件。
因此,可复用 TUI 需要确定、便于评审的终端状态表示。交付它的产品部署还需要通过组装后的技术栈运行已录制模型流程,并保留一项范围更小、覆盖真实进程与 PTY 边界的测试。
## 决策
可复用 TUI 的覆盖分为两个互补的包级层次:
1. `packages/ui/tui/tests/tui.spec.ts` 直接测试事件映射、输入路由、资源释放和错误行为。
2. `packages/ui/tui/tests/tui.snapshot.ts` 将生产 TUI 挂载到无界面终端模拟器,覆盖完整会话日志无法保留的瞬态:进行中的流式输出、待完成工具调用、浮层、展开状态、压缩重排、错误和关闭过程。
[显式配置入口决策](../simplification/2026-08-03-explicit-config-dsh-entrypoint.md)移除了产品 TUI 组合、已录制应用流程和 PTY 测试套件。交付终端入口的部署负责这些组装应用层;包测试不声称提供产品覆盖。
### 已移除的应用回放
已删除的应用测试套件为每个场景提供 `session.jsonl`、可选的子会话日志 `session.<n>.jsonl`,以及 `terminal.expected.txt`。主日志提供用户来源的 `user/message` 提示词和已录制的 `assistant/chunk` 序列。`dsh-llm-replay` 为每个会话派生一份模型调用脚本,并且是测试中唯一的 mock 边界agent loop、工具、worker、呈现器和 TUI 都使用生产实现。
如果工具调用顺序不符、预期事件数量不足、工具结果报错、轮次以错误结束、工作流生命周期不完整,或者实时子会话数量与 fixture测试前置数据集合不一致该测试套件都会拒绝流程。这些检查仍是未来任何终端部署的验收模式它们已不再作为 fixture 交付。
已移除的录制工作流使用 `DSH_SNAPSHOT=record` 录制模型流程,使用 `DSH_SNAPSHOT=refresh` 更新派生的终端输出。移除产品入口时也从仓库快照通道中移除了这些模式;可复用 TUI 快照直接由包级场景编写。
### 语义终端投影
包内的 `HeadlessTerminal` 实现与进程终端相同的 pi-tui `Terminal` 接口,并把每次 ANSI 写入交给固定版本的 `@xterm/headless` 解析器。读取状态前,快照代码会等待同步帧稳定。流式输出检查点会冻结 loader 的 interval同时保留跨过一次动画 tick 的真实墙钟等待,从而固定语义状态,而非调度器碰巧渲染出的某个加载动画字形。
每份预期输出把终端尺寸、活动缓冲区和视口坐标、生命周期与光标状态、各行、换行标记以及非默认样式区间投影为文本。滚动内容较多的卡片捕获已使用缓冲区;浮层捕获可见视口。文本和样式相互分离,评审人无需解码 ANSI 字节即可区分内容变化与呈现变化。
每个检查点还会对完整终端状态强制执行主题无关性:禁止 RGB 颜色、禁止 ANSI 015 以外的调色板项,也禁止显式背景色。选择行使用终端默认色进行反显,因此仍然有效。两套测试都拥有封闭清单,会拒绝缺失的场景、缺失的检查点和遗留预期输出文件。
### 必需场景矩阵
| 层次 | 场景 | 固定的契约 |
|---|---|---|
| 瞬态 | 流式输出与待完成高级调用 | 进行中的推理和文本,以及完整日志中不会保留的待完成 Code Mode、工作流和 Cordis 卡片 |
| 瞬态 | 卡片、交互、布局、失败和关闭 | 折叠与展开的卡片族、问题校验、压缩替换、尺寸重排、帮助与错误、光标恢复和终端停止 |
## 曾考虑的替代方案
- **快照原始终端写入**:不予采纳,因为差分渲染可能在画面不变时改变写入边界,而且光标与清屏序列难以评审。
- **快照进入终端输出之前的组件渲染行**:不予采纳,因为它无法测试 ANSI 解析、光标移动、浮层、视口行为,也无法测试独立组件在同一帧中的相互作用。
- **通过追加会话事件构造所有完整流程**:不予采纳,因为人工编写的事件序列可能与 agent loop、工具执行、子会话绑定或 worker 行为发生偏差,但呈现测试仍然保持绿色。直接构造事件只用于渲染器瞬态。
- **复用 ACP stdout 预期输出作为 TUI 判定依据**:不予采纳,因为已录制模型流程与传输方式无关,其呈现方式却并非如此。终端部署拥有自己的预期输出,同时可以复用同一套 JSONL 回放词汇。
- **提交栅格截图**:不予采纳,因为字体、字形度量、抗锯齿和宿主终端主题会使结果依赖平台,也会增加语义样式变更的评审难度。
- **只使用 PTY 端到端测试**:不予采纳,因为原始 PTY 输出是一系列历史绘制操作而不是可查询的最终状态。PTY 测试保留真实 Loader、输入与清理边界模拟器负责广泛的状态覆盖。
## 后果
- 当 TUI 事件映射或呈现损坏时,包快照会失败;它们不能代替组装应用的工具路径 transcript。
- TUI 视觉回归会产生便于阅读的单元格和样式 diff而 JSONL fixture 会保留触发生产路径的确切模型分片。
- 模拟器使用 xterm 的拟议缓冲区 API。升级 xterm 时必须重新运行并评审语义投影;终端特有行为仍需由交付该终端的部署所拥有的 PTY 冒烟测试覆盖。
- 预期输出有意固定指定尺寸下的换行与视口行为。预期布局变更会更新并评审包级语义快照。