@songtonyli/dsh-cli 0.1.11 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,7 +26,7 @@ workspace and no build step. Native packages that ship one build per platform
26
26
  ripgrep, the PTY backend's addons, sharp, koffi, and the Landlock launcher — are
27
27
  ordinary optional dependencies, so npm resolves the right one for your machine.
28
28
 
29
- Requires Node `^22.19.0 || >=24.0.0`. Version 0.1.11; `dsh --version`
29
+ Requires Node `^22.19.0 || >=24.0.0`. Version 0.1.12; `dsh --version`
30
30
  also reports the harness build the package was assembled from.
31
31
 
32
32
  ## License
package/bin/dsh.mjs CHANGED
@@ -18,7 +18,7 @@ const argv = process.argv.slice(2)
18
18
  // version this package publishes. Reporting only one of the two would make an
19
19
  // ordinary `npm install @songtonyli/dsh-cli@x` look like it installed something else.
20
20
  if (argv.length === 1 && (argv[0] === '--version' || argv[0] === '-V')) {
21
- console.log('@songtonyli/dsh-cli 0.1.11 (deepseek-harness 0.1.6-alpha.1)')
21
+ console.log('@songtonyli/dsh-cli 0.1.12 (deepseek-harness 0.1.6-alpha.1)')
22
22
  process.exit(0)
23
23
  }
24
24
 
@@ -100,7 +100,7 @@ window.__ModuleLoader__.load({
100
100
  const SCROLLBAR_LINGER_MS = 2e3;
101
101
  /** Format complete-build metadata for the local brand badge. */
102
102
  function localBuildVersion() {
103
- return `0.1.6-alpha.1-9ed61dc-dirty`;
103
+ return `0.1.6-alpha.1-2a4344b-dirty`;
104
104
  }
105
105
  /** Each panel row subscribes only to its own selection state. */
106
106
  function PanelRow({ id, label, wide, usePanelInfo, selectPanel, renderSlot }) {
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/bundle/tui-app/README.md
5
- README.md: f99f5ebde7ad398611e91f9e80529503423ee7f9
6
- README.zh.md: 37fdaf5839a39657d560ee5c56c733d13dddd762
5
+ README.md: 6804aca66ed47381fec0aa79bd76517daa04e5aa
6
+ README.zh.md: 4a4475d3cfdc0d0109a1f7b9ba5e2e443a8c39cf
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "Interactive terminal mode for dsh: talk to the agent in your terminal with streamed replies, tool cards, approvals, questions, slash commands, @-references, attachments, and session switching."
2
+ description: "Interactive terminal mode for dsh: talk to the agent in your terminal with streamed replies, tool cards, keyboard navigation over the conversation, approvals, questions, slash commands, @-references, attachments, and session switching."
3
3
  kind: "package-bundle"
4
4
  ---
5
5
 
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- `dsh-tui-app` is the terminal surface of dsh: `dsh tui` starts a multi-turn session in the terminal you are already in, with no browser-hosted application and no server. Replies stream as you watch, tool calls become foldable cards, approvals and `ask_user_question` questions appear above the input, `@` completes paths and sessions, `/attach` adds images and files, and `/`-commands share the Web registry. Sessions persist: `/sessions`, `/new`, and `/fork` switch between them, `/export` writes the browser's ZIP, and `--resume` continues one later. It runs the same model, tools, and safety defaults as `dsh web`, one session at a time.
12
+ `dsh-tui-app` is the terminal surface of dsh: `dsh tui` starts a multi-turn session in the terminal you are in, with no browser-hosted application and no server. Replies stream, tool calls become foldable cards, approvals and `ask_user_question` questions appear above the input, `@` completes paths and sessions, `/attach` adds images and files, and `/`-commands share the Web registry. Arrow keys walk the conversation through a docked inspector. Sessions persist: `/sessions`, `/new`, and `/fork` switch between them, `/export` writes the browser's ZIP, and `--resume` continues one later. It runs `dsh web`'s model, tools, and safety defaults, one session at a time.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -40,7 +40,9 @@ On quit the app prints `dsh: session <id> saved; resume with: dsh --profile tui
40
40
 
41
41
  ### The screen
42
42
 
43
- The header names the session by its title once one is generated or set, with the id beside it. The transcript grows in the terminal's own scrollback: your prompts start with `›` (attachments listed under them), assistant reasoning is dim above the Markdown reply, and each tool call is a card with a status glyph, the tool name, the presenter headline, and a body folded to `toolPreviewLines` rows. Streamed reply text fades in: the newest words arrive near the terminal's background color and brighten to the normal foreground over `streamFadeSteps` levels of `streamFadeStepMs` each, so a soft edge trails the stream head, and text that has settled is never dimmed again. Below the transcript sit a spinner while the agent works, any open prompt, the editor, the subagent panel, and the footer: a status bar of segments over a line of key hints. The editor's caret is the terminal's own blinking bar: the app asks for that shape at start, gives your default back on quit, and draws no caret at all while the status bar or the panel holds the keyboard. Each segment appears only when its fact exists — the model and reasoning effort, the permission preset, the running turn's elapsed time, cumulative token usage, the context window percentage, todo and goal and plan-mode markers from the projection seam, the workspace path (shortened with `~` and `…/` when long), and the count of pending attachments — and `Shift+Up` moves focus into the bar to read a segment's details. Compaction and model-request retries appear as notices, the same facts the browser's markers carry.
43
+ The header names the session by its title once one is generated or set, with the id beside it. The transcript grows in the terminal's own scrollback: your prompts start with `›` (attachments listed under them), assistant reasoning is dim above the Markdown reply, and each tool call is a card with a status glyph, the tool name, the presenter headline, and a body folded to `toolPreviewLines` rows. Streamed text fades in and every word keeps its own clock: a word enters near the terminal's background color and brightens to the color it settles in over `streamFadeSteps` levels of `streamFadeStepMs` each, so a fast stream leaves a longer trail of brightening words rather than a darker one. The same fade covers streamed reasoning, which settles at the dim foreground it is drawn in, and a tool card, whose header and call rows fade in when the call is logged and whose result rows fade in when the tool answers; a card redrawn from persisted history carries no fade, and text that has settled is never dimmed again. Below the transcript sit a spinner while the agent works, any open prompt, the editor, the subagent panel, and the footer: a status bar of segments over a line of key hints. The editor's caret is the terminal's own blinking bar: the app asks for that shape at start, gives your default back on quit, and draws no caret at all while the status bar or the panel holds the keyboard. Each segment appears only when its fact exists — the model and reasoning effort, the permission preset, the running turn's elapsed time, cumulative token usage, the context window percentage, todo and goal and plan-mode markers from the projection seam, the workspace path (shortened with `~` and `…/` when long), and the count of pending attachments — and `Shift+Down` moves focus into the bar to read a segment's details. Compaction and model-request retries appear as notices, the same facts the browser's markers carry.
44
+
45
+ While the keyboard walks the conversation an inspector is docked directly above the editor. It names the focused section — its position among the navigable blocks, the turn it belongs to, and what the section is, such as `3/12 · turn 2 · bash git status · result` — draws the block's parts as a strip like `‹ reasoning · reply ›` when it has more than one, shows the section's own source rows cut to `focusPreviewLines` with `… <n> more rows · Enter opens the page` under them, and ends with a dim line of the keys it answers. The focused block is also marked where it stands, with a two-column gutter that is dim beside the block's other lines and accented beside the focused section's own, and its content wraps two columns narrower while the mark is drawn. That mark reaches only the lines pi-tui still repaints differentially — the last `rows` lines of the last frame it wrote, a boundary a taller frame raises and a shorter one never lowers — because changing anything above them would clear the terminal's scrollback; a block that has scrolled past that point keeps no gutter and its inspector heading reads `off screen` instead, and a fade whose rows reach that edge settles them to their final colors in the frame that writes them, so nothing stays dim in the scrollback.
44
46
 
45
47
  The subagent panel is drawn while a subagent session under the bound one is resident, or the listing carries a candidate it could not read. Its heading counts what it lists, and each row gives the child's depth indent, its label or id, its mode (`one-shot` or `continuable`), `resident`, whether its agent is `running` or `idle`, its elapsed time — the open turn's, else the total its settled turns took — and its token usage, as far as this process's own view of that child and the composed projections carry them. Six rows are drawn at most, with `+<n> more · /subagents lists them all` under them; a candidate the listing could not interpret draws as `unreadable: <reason>` and opens nothing, and a failed listing keeps the rows the last good one produced with `listing failed: <reason>` beneath. The panel disappears with its last row.
46
48
 
@@ -53,16 +55,18 @@ The subagent panel is drawn while a subagent session under the bound one is resi
53
55
  | `Shift+Enter` | Insert a newline |
54
56
  | `Shift+Tab` | While the editor has focus, cycle the current model's reasoning effort for the next request |
55
57
  | `Up` / `Down` | Recall earlier prompts |
56
- | `Shift+Up` | Move focus on through the docked regions: editor status bar subagent panel editor |
57
- | `Shift+Down` | Move focus back through that cycle; in the editor with no panel drawn it stays the editor's own key |
58
+ | `Shift+Up` | Focus the conversation on its newest block; from any region it jumps there |
59
+ | `Shift+Down` | Focus the subagent panel's first row while it is drawn, and the status bar's first segment otherwise; from any region it jumps to the bar |
58
60
  | `Esc` | Stop the running turn; queued messages stay queued |
59
61
  | `Ctrl+O` | Expand or collapse every tool card |
60
62
  | `Ctrl+C` | Clear the editor; a second press within 600 ms quits |
61
63
  | `Ctrl+D` | Quit when the editor is empty |
62
64
 
63
- While the status bar has focus, `Left` / `Right` and `Tab` / `Shift+Tab` move between segments and wrap at both ends, `Enter` opens the selected segment while the bar keeps focus, `Shift+Up` moves on to the subagent panel while it is drawn, and `Esc` or `Shift+Down` returns focus to the editor. No other key reaches the editor while the bar has focus; `Ctrl+C` and `Ctrl+D` keep their usual meaning and return focus to the editor.
65
+ The conversation, the subagent panel, and the status bar stack in that order, and `Up` / `Down` walk the whole stack without wrapping at either end. While the conversation has focus, `Up` / `Down` move between blocks — your prompts, assistant messages, and tool cards, with notices and printed reports skipped — `Left` / `Right` move between a block's parts, which are the reasoning and the reply of a message and the call and the result of a tool, `Enter` opens the focused part as a read-only page carrying its full rows and comes back to the same part, and `Esc` returns focus to the editor. The rows a part carries are the block's own source text, so a reply reads as the Markdown the model wrote rather than the rendering drawn above. Every other key is consumed there, apart from `Ctrl+C` and `Ctrl+D`; a session switch drops the focus back to the editor, and a session with nothing to inspect yet answers `Shift+Up` with `nothing in the transcript to inspect yet` and leaves the keyboard in the editor.
66
+
67
+ While the status bar has focus, `Left` / `Right` and `Tab` / `Shift+Tab` move between segments and wrap at both ends, `Up` leaves the bar for the subagent panel's last row while it is drawn and for the conversation otherwise, `Enter` opens the selected segment while the bar keeps focus, and `Esc` returns focus to the editor. No other key reaches the editor while the bar has focus; `Ctrl+C` and `Ctrl+D` keep their usual meaning and return focus to the editor.
64
68
 
65
- While the subagent panel has focus, `Up` / `Down` move the selection and wrap at both ends, `Enter` opens that child's session details as a read-only page and comes back to the panel on the same row, `Shift+Down` moves on to the status bar, and `Esc` or `Shift+Up` returns focus to the editor. Every other key is consumed there as well, apart from `Ctrl+C` and `Ctrl+D`; the panel also hands the keyboard back to the editor when its last row leaves.
69
+ While the subagent panel has focus, `Up` / `Down` move the selection and continue into the neighboring regions at its ends `Up` on the first row reaches the conversation and `Down` on the last row the status bar — `Enter` opens that child's session details as a read-only page and comes back to the panel on the same row, and `Esc` returns focus to the editor. Every other key is consumed there as well, apart from `Ctrl+C` and `Ctrl+D`; the panel also hands the keyboard back to the editor when its last row leaves.
66
70
 
67
71
  Every segment except `todo` prints its details into the transcript, stating its current facts and naming what changes them: the model segment names `/model`, the effort segment `Shift+Tab`, and the permission segment its preset, the `turn` segment — drawn as `turn <elapsed>` between the permission and usage segments, and only while a turn runs — gives the turn number, its start time, its elapsed time, and the queued-message counts, while the usage, context, goal, and plan segments print the matching sections of the `/status` report, the workspace segment the full path, and the attachments segment the pending attachments. The `todo` segment opens the agent's todo list instead, the same list `/todos` opens.
68
72
 
@@ -117,10 +121,11 @@ An approval request draws `Allow <tool>?` with the asker's reason, the logged ca
117
121
  | `prompt` | none | A first prompt submitted when the terminal is up |
118
122
  | `resume` | none | A persisted session id to continue instead of starting a new one |
119
123
  | `toolPreviewLines` | `8` | Collapsed tool-card body rows before `Ctrl+O` expands them |
124
+ | `focusPreviewLines` | `12` | Rows of the focused section the docked inspector shows before `Enter` opens the whole of it |
120
125
  | `liveRefreshMs` | `1000` | Period of the redraw that advances the `turn` segment and the panel's elapsed values and re-reads a stale subagent listing |
121
- | `streamFadeSteps` | `5` | Brightness levels streamed assistant text climbs before it draws in the normal foreground |
122
- | `streamFadeStepMs` | `40` | How long one brightness level lasts, which is also the repaint period of the fading text |
123
- | `reducedMotion` | `false` | Draw streamed text at the normal foreground, with no fade and no repeating repaint |
126
+ | `streamFadeSteps` | `8` | Brightness levels a streamed word, a reasoning word, or a tool card climbs before it draws in the color it settles in |
127
+ | `streamFadeStepMs` | `33` | How long one brightness level lasts, so a word settles `streamFadeSteps × streamFadeStepMs` after it appeared; it is also the repaint period of the fade |
128
+ | `reducedMotion` | `false` | Draw streamed text, streamed reasoning, and tool cards in their settled colors, with no fade and no repeating repaint |
124
129
  | `openBrowser` | `true` | Hand marked authorization pages to the local default browser |
125
130
 
126
131
  `prompt`, `resume`, and `openBrowser` come from the command line through the startup provider; the generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-tui-app) is the exhaustive source for every accepted field.
@@ -157,8 +162,11 @@ The patch rides over `dsh-base`: it sets the coding persona prefix and cwd suffi
157
162
  | [`src/sessions.ts`](src/sessions.ts) | The `/sessions` list over the query engine and its picker rows |
158
163
  | [`src/attach.ts`](src/attach.ts) | `/attach`: local files into image or file blocks through the attachment store |
159
164
  | [`src/export.ts`](src/export.ts) | `/export`: the session-log ZIP written through the export package's archive helpers |
160
- | [`src/blocks.ts`](src/blocks.ts) | Transcript components: user prompt, assistant reply, tool card, notice |
161
- | [`src/fade.ts`](src/fade.ts) | The streamed-text fade: the tail tracker, the background-to-foreground ramp, and the recolor of rendered lines |
165
+ | [`src/blocks.ts`](src/blocks.ts) | Transcript components: user prompt, assistant reply, tool card, notice; the navigable blocks expose their sections and draw the focus gutter |
166
+ | [`src/navigation.ts`](src/navigation.ts) | The transcript as sections, the cursor that walks them, and the inspector heading |
167
+ | [`src/inspector.ts`](src/inspector.ts) | The docked inspector: the focused section's heading, parts strip, folded rows, and its mounted component |
168
+ | [`src/screen.ts`](src/screen.ts) | The main screen with the settle passes between building a frame and writing it, the repaint window each pass is judged against, and the per-block repaint floor |
169
+ | [`src/fade.ts`](src/fade.ts) | The streamed-text fade: the wall-clock tail tracker, the block-fade clock and registry, the perceptual ramp, and the recolor of rendered lines |
162
170
  | [`src/prompts.ts`](src/prompts.ts) | Approval, question, picker, and read-only detail prompts plus the modal queue |
163
171
  | [`src/transcript.ts`](src/transcript.ts) | Pure text folding of presentation views, usage, and turn-end reasons |
164
172
  | [`src/diff.ts`](src/diff.ts) | Line diff and hunk selection for diff cards |
@@ -175,6 +183,7 @@ The patch rides over `dsh-base`: it sets the coding persona prefix and cwd suffi
175
183
  | [`tests/app.spec.ts`](tests/app.spec.ts) | Rendering, keys, commands, and both seams over a fake terminal |
176
184
  | [`tests/commands.spec.ts`](tests/commands.spec.ts) | Session, attachment, queue, skill, sign-in, `/login`, Shift+Tab effort cycling, export, reference, and effort commands over scripted services |
177
185
  | [`tests/panels.spec.ts`](tests/panels.spec.ts) | Status footer and report, the navigable subagent and todo lists, catalog commands, command hints, and the approval detail |
186
+ | [`tests/transcript-focus.spec.ts`](tests/transcript-focus.spec.ts) | Walking the conversation, the region stack, the inspector, and the in-place gutter inside the repaint window |
178
187
  | [`tests/index.spec.ts`](tests/index.spec.ts) | Creation, resume paging, fork cut, session switching, quit flow, and failure reporting |
179
188
  | [`tests/startup.spec.ts`](tests/startup.spec.ts) | Command-line parsing over a real Loader tree |
180
189
  | [`../../../apps/cli/tests/profiles/tui/tests/keyless-smoke.e2e.ts`](../../../apps/cli/tests/profiles/tui/tests/keyless-smoke.e2e.ts) | The shipped profile through the real launcher with a keyless mock model |
@@ -224,10 +233,12 @@ These limits describe the terminal surface as shipped; they are not a general CL
224
233
  - **Approvals are one-shot** — the prompt offers allow once or reject, matching the approval seam's vocabulary; there is no remembered grant.
225
234
  - **Browser-only pages stay in the browser** — workspace and directory pickers, open-in-app links, the trajectory ledger, and per-message like/dislike have no terminal counterpart; `/settings`, `/plugins`, `/subagents`, `/outline`, and the shared `/feedback` cover their facts as text, and subagent transcripts are read by switching to the child session.
226
235
  - **Deliverables are named, not opened** — `/deliverables` lists the presented paths; the browser previews the files.
227
- - **Terminal scrollback owns history** — the transcript is not searchable or foldable beyond tool cards; the browser surface owns richer navigation.
236
+ - **Terminal scrollback owns history** — the keyboard walks every block and part of the conversation, but there is no search and nothing folds beyond tool cards; the browser surface owns richer navigation.
237
+ - **A focused block that scrolled away is marked only in the inspector** — pi-tui repaints differentially just the last `rows` lines of the last frame it wrote and clears the terminal's scrollback to change anything above them, so a block further back gains no gutter and the inspector heading says `off screen`; a page that filled the terminal raises that boundary for good, so the block it was opened from can read `off screen` once the page closes, and its section still reads in the inspector and on the page `Enter` opens.
238
+ - **The page shows source text** — `Enter` on an assistant reply opens the Markdown the model wrote, not the rendering the transcript draws, so tables and headings read as their source.
228
239
  - **The panel lists residency, not the tree** — a child joins it while its session record is resident in this process, so a subagent run by an out-of-process provider, which owns no session here, never appears; `/subagents` remains the way to every durable descendant.
229
240
  - **Residency is not work** — the listing's `activity: 'running'` says the child's record is resident, which is what the row's `resident` reports; whether the child is working is the separate `running` / `idle` word beside it, read from that child's Agent in this process.
230
- - **Rows behind the overflow row are not selectable** — the panel draws at most six rows and `Up` / `Down` wrap inside them; the children folded into `+<n> more` are reached through `/subagents`, which walks the complete descendant tree.
241
+ - **Rows behind the overflow row are not selectable** — the panel draws at most six rows and `Up` / `Down` leave it at their ends; the children folded into `+<n> more` are reached through `/subagents`, which walks the complete descendant tree.
231
242
  - **The fade needs an answer from the terminal** — its ramp is built from the background color the terminal reports to the query sent at startup, so a terminal that stays silent, or that encodes neither truecolor nor 256 colors, gets the two-level faint mode instead; `NO_COLOR`, a disabled palette, `TERM=dumb`, and `reducedMotion` turn the effect off entirely.
232
243
  - **The terminal's own caret can flicker** — the editor draws no caret of its own and the app turns the terminal cursor on, which pi-tui then moves across the lines it repaints; a terminal that does not honor the synchronized-output sequences pi-tui wraps a frame in can show that movement.
233
244
  - **Runs through the `dsh` launcher** — starting the profile another way fails at startup, because only the launcher can request the process exit.
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "dsh 的交互式终端模式:在你的终端里与 agent(智能体)对话,带流式回复、工具卡片、审批、提问、斜杠命令、@ 引用、附件与会话切换。"
2
+ description: "dsh 的交互式终端模式:在你的终端里与 agent(智能体)对话,带流式回复、工具卡片、用键盘浏览对话记录、审批、提问、斜杠命令、@ 引用、附件与会话切换。"
3
3
  kind: "package-bundle"
4
4
  ---
5
5
 
@@ -9,7 +9,7 @@ kind: "package-bundle"
9
9
 
10
10
  ## 概述
11
11
 
12
- `dsh-tui-app` 是 dsh 的终端表层:`dsh tui` 在你当前所在的终端里启动一个多轮会话,没有浏览器托管的应用、也没有服务器。回复实时流式显示,工具调用变成可折叠的卡片,审批与 `ask_user_question` 的问题出现在输入框上方,`@` 补全路径与会话,`/attach` 加入图片与文件,`/` 命令与 Web 共用注册表。会话持久化:`/sessions`、`/new` 与 `/fork` 在会话间切换,`/export` 写出浏览器的 ZIP,`--resume` 稍后继续。它运行与 `dsh web` 相同的模型、工具与安全默认值,同一时间一个会话。
12
+ `dsh-tui-app` 是 dsh 的终端表层:`dsh tui` 在你所在的终端里启动一个多轮会话,没有浏览器托管的应用、也没有服务器。回复流式显示,工具调用变成可折叠的卡片,审批与 `ask_user_question` 的问题出现在输入框上方,`@` 补全路径与会话,`/attach` 加入图片与文件,`/` 命令与 Web 共用注册表。方向键在停靠的检视面板中走遍对话记录。会话持久化:`/sessions`、`/new` 与 `/fork` 在会话间切换,`/export` 写出浏览器的 ZIP,`--resume` 稍后继续。它运行 `dsh web` 的模型、工具与安全默认值,同一时间一个会话。
13
13
 
14
14
  ## 目录
15
15
 
@@ -40,7 +40,9 @@ dsh tui --no-open # print sign-in URLs without opening a
40
40
 
41
41
  ### 屏幕布局
42
42
 
43
- 页眉在标题生成或设置后以标题命名会话,并在旁边显示 id。对话记录在终端自身的回滚区中增长:你的提示以 `›` 开头(附件列在其下),assistant 的推理以暗色显示在 Markdown 回复上方,每次工具调用是一张卡片,含状态符号、工具名、呈现器标题,以及折叠到 `toolPreviewLines` 行的正文。流式回复的文字会淡入:最新的词以接近终端背景色的亮度出现,并经 `streamFadeSteps` 级亮度、每级 `streamFadeStepMs` 提亮到正常前景色,因此流头之后拖着一条柔和的边缘,而已经稳定下来的文字不会再被调暗。对话记录下方依次是 agent 工作时的旋转指示、任何打开的提示、编辑器、子 agent 面板,以及页脚:一行分段状态栏,其下是一行按键提示。编辑器的光标是终端自身的闪烁竖条:应用在启动时请求这一形状,退出时把你的默认形状还回去,而状态栏或面板持有键盘期间完全不绘制光标。每个分段只在其事实存在时出现——模型与推理强度、权限预设、正在进行轮次的已用时间、累计 token 用量、上下文窗口百分比、来自投影接缝的 todo、目标与计划模式标记、workspace 路径(过长时以 `~` 与 `…/` 缩短)、待发送附件数量——`Shift+Up` 把焦点移入状态栏以查看某个分段的详情。压缩与模型请求重试以通知形式出现,与浏览器标记承载的事实相同。
43
+ 页眉在标题生成或设置后以标题命名会话,并在旁边显示 id。对话记录在终端自身的回滚区中增长:你的提示以 `›` 开头(附件列在其下),assistant 的推理以暗色显示在 Markdown 回复上方,每次工具调用是一张卡片,含状态符号、工具名、呈现器标题,以及折叠到 `toolPreviewLines` 行的正文。流式文字会淡入,而且每个词各走各的时钟:一个词以接近终端背景色的亮度出现,并经 `streamFadeSteps` 级亮度、每级 `streamFadeStepMs` 提亮到它最终稳定的颜色,因此流得更快只会留下更长的一串正在提亮的词,而不会更暗。同一套淡入也覆盖流式推理(它稳定在自身绘制所用的暗色前景)与工具卡片:卡片的表头与调用行在调用被记录时淡入,结果行在工具应答时淡入;从持久化历史重绘的卡片不带淡入,已经稳定下来的文字也不会再被调暗。对话记录下方依次是 agent 工作时的旋转指示、任何打开的提示、编辑器、子 agent 面板,以及页脚:一行分段状态栏,其下是一行按键提示。编辑器的光标是终端自身的闪烁竖条:应用在启动时请求这一形状,退出时把你的默认形状还回去,而状态栏或面板持有键盘期间完全不绘制光标。每个分段只在其事实存在时出现——模型与推理强度、权限预设、正在进行轮次的已用时间、累计 token 用量、上下文窗口百分比、来自投影接缝的 todo、目标与计划模式标记、workspace 路径(过长时以 `~` 与 `…/` 缩短)、待发送附件数量——`Shift+Down` 把焦点移入状态栏以查看某个分段的详情。压缩与模型请求重试以通知形式出现,与浏览器标记承载的事实相同。
44
+
45
+ 键盘在对话记录中移动时,编辑器正上方停靠着一个检视面板。它写出当前聚焦小节的名称——它在可导航块中的位置、所属轮次,以及这是哪一种小节,例如 `3/12 · turn 2 · bash git status · result`——在块含多个部分时把这些部分绘制成形如 `‹ reasoning · reply ›` 的一条选择行,把该小节自身的源文本行截到 `focusPreviewLines` 行并在其下写出 `… <n> more rows · Enter opens the page`,最后以一行暗色文字列出它所应答的按键。被聚焦的块也会在原地被标记:一条两列宽的标记条,在块的其他行旁为暗色、在被聚焦小节自身的行旁为强调色,标记绘制期间该块的内容按窄两列换行。这个标记只能落在 pi-tui 仍以差分方式重绘的行上——也就是它写出的上一帧的最后 `rows` 行,这条边界只会被更高的帧抬高、不会被更矮的帧降低——因为改动其上方的任何一行都会清空终端的回滚区;已经滚过该位置的块不带标记条,其检视面板标题改为写出 `off screen`;淡入中的各行抵达这一边界时,会在写出该帧的同一帧内被结算为最终颜色,因此回滚区里不会留下暗色文字。
44
46
 
45
47
  子 agent 面板在绑定会话之下有常驻的子 agent 会话、或列表中带有无法读取的候选者时绘制。其标题统计所列出的条目,每一行给出该子会话的层级缩进、其标签或 id、其模式(`one-shot` 或 `continuable`)、`resident`、其 agent 处于 `running` 还是 `idle`、其已用时间——进行中轮次的用时,否则是已结束轮次的合计——以及其 token 用量,以本进程对该子会话的可见程度与已组合的投影所能提供的为限。最多绘制六行,其下是 `+<n> more · /subagents lists them all`;列表无法解读的候选者绘制为 `unreadable: <reason>` 且打不开任何页面,读取失败的列表则保留上一次成功读取产生的各行,并在其下写出 `listing failed: <reason>`。面板随其最后一行一同消失。
46
48
 
@@ -53,16 +55,18 @@ dsh tui --no-open # print sign-in URLs without opening a
53
55
  | `Shift+Enter` | 插入换行 |
54
56
  | `Shift+Tab` | 编辑器持有焦点时,循环切换当前模型的推理强度,从下一次请求生效 |
55
57
  | `Up` / `Down` | 调出先前的提示 |
56
- | `Shift+Up` | 在停靠区域之间向前移动焦点:编辑器 → 状态栏 → 子 agent 面板 → 编辑器 |
57
- | `Shift+Down` | 沿该循环反向移动焦点;在编辑器中且未绘制面板时,它仍是编辑器自己的按键 |
58
+ | `Shift+Up` | 把焦点放到对话记录的最新块上;在任何区域都直接跳过去 |
59
+ | `Shift+Down` | 面板已绘制时把焦点放到其第一行,否则放到状态栏的第一个分段;在任何区域都直接跳到状态栏 |
58
60
  | `Esc` | 停止正在进行的轮次;已排队的消息保持排队 |
59
61
  | `Ctrl+O` | 展开或折叠所有工具卡片 |
60
62
  | `Ctrl+C` | 清空编辑器;600 ms 内再按一次则退出 |
61
63
  | `Ctrl+D` | 编辑器为空时退出 |
62
64
 
63
- 状态栏持有焦点时,`Left` / `Right` `Tab` / `Shift+Tab` 在分段之间移动并在两端环绕,`Enter` 打开所选分段且状态栏保持焦点,`Shift+Up` 在面板已绘制时继续移动到子 agent 面板,`Esc` 或 `Shift+Down` 把焦点交还编辑器。状态栏持有焦点期间其他按键不会到达编辑器;`Ctrl+C` 与 `Ctrl+D` 保持其一贯含义,并把焦点交还编辑器。
65
+ 对话记录、子 agent 面板与状态栏按此顺序自上而下排布,`Up` / `Down` 走遍整个序列且在两端都不环绕。对话记录持有焦点时,`Up` / `Down` 在块之间移动——你的提示、assistant 消息与工具卡片,通知与打印出的报告会被跳过——`Left` / `Right` 在块的各部分之间移动,也就是一条消息的推理与回复、一次工具调用的调用与结果,`Enter` 把当前聚焦的部分作为只读页面打开、其中带有该部分的完整各行,离开后回到同一部分,`Esc` 把焦点交还编辑器。各部分承载的是块自身的源文本,因此一条回复读到的是模型写下的 Markdown,而不是其上方绘制出的渲染结果。除 `Ctrl+C` 与 `Ctrl+D` 外,其他按键都在此被消费;切换会话会把焦点交回编辑器,尚无可查看内容的会话对 `Shift+Up` 回以 `nothing in the transcript to inspect yet` 并把键盘留在编辑器。
66
+
67
+ 状态栏持有焦点时,`Left` / `Right` 与 `Tab` / `Shift+Tab` 在分段之间移动并在两端环绕,`Up` 在面板已绘制时离开状态栏前往面板的最后一行、否则前往对话记录,`Enter` 打开所选分段且状态栏保持焦点,`Esc` 把焦点交还编辑器。状态栏持有焦点期间其他按键不会到达编辑器;`Ctrl+C` 与 `Ctrl+D` 保持其一贯含义,并把焦点交还编辑器。
64
68
 
65
- 子 agent 面板持有焦点时,`Up` / `Down` 移动选择并在两端环绕,`Enter` 把该子会话的详情作为只读页面打开、离开后回到面板的同一行,`Shift+Down` 继续移动到状态栏,`Esc` `Shift+Up` 把焦点交还编辑器。除 `Ctrl+C` 与 `Ctrl+D` 外,其他按键同样在此被消费;面板的最后一行离开时,它也会把键盘交还编辑器。
69
+ 子 agent 面板持有焦点时,`Up` / `Down` 移动选择,并在其两端继续进入相邻区域——在第一行按 `Up` 到达对话记录,在最后一行按 `Down` 到达状态栏——`Enter` 把该子会话的详情作为只读页面打开、离开后回到面板的同一行,`Esc` 把焦点交还编辑器。除 `Ctrl+C` 与 `Ctrl+D` 外,其他按键同样在此被消费;面板的最后一行离开时,它也会把键盘交还编辑器。
66
70
 
67
71
  除 `todo` 之外的每个分段都把详情打印到对话记录,陈述其当前事实并指出改变它们的方式:模型分段指出 `/model`,推理强度分段指出 `Shift+Tab`,权限分段指出其预设;`turn` 分段——绘制在权限与用量分段之间、形如 `turn <elapsed>`,且只在轮次进行时出现——给出轮次编号、其开始时间、已用时间与排队消息数量;用量、上下文、目标与计划分段打印 `/status` 报告中对应的小节,workspace 分段显示完整路径,附件分段列出待发送的附件。`todo` 分段则改为打开 agent 的 todo 列表,与 `/todos` 打开的是同一个列表。
68
72
 
@@ -117,10 +121,11 @@ dsh tui --no-open # print sign-in URLs without opening a
117
121
  | `prompt` | 无 | 终端就绪后提交的首个提示 |
118
122
  | `resume` | 无 | 要继续的持久化会话 id,而不是新建会话 |
119
123
  | `toolPreviewLines` | `8` | `Ctrl+O` 展开前折叠的工具卡片正文行数 |
124
+ | `focusPreviewLines` | `12` | `Enter` 完整打开之前,停靠的检视面板为聚焦小节显示的行数 |
120
125
  | `liveRefreshMs` | `1000` | 重绘周期:推进 `turn` 分段与面板中的已用时间,并重新读取已过期的子 agent 列表 |
121
- | `streamFadeSteps` | `5` | 流式 assistant 文本在以正常前景色绘制之前经过的亮度级数 |
122
- | `streamFadeStepMs` | `40` | 每级亮度持续多久,这也是淡入文字的重绘周期 |
123
- | `reducedMotion` | `false` | 以正常前景色绘制流式文本,不做淡入,也不重复重绘 |
126
+ | `streamFadeSteps` | `8` | 一个流式词、一个推理词或一张工具卡片在以其稳定颜色绘制之前经过的亮度级数 |
127
+ | `streamFadeStepMs` | `33` | 每级亮度持续多久,因此一个词在出现后 `streamFadeSteps × streamFadeStepMs` 稳定下来;这也是淡入的重绘周期 |
128
+ | `reducedMotion` | `false` | 以稳定颜色绘制流式文本、流式推理与工具卡片,不做淡入,也不重复重绘 |
124
129
  | `openBrowser` | `true` | 把被标记的授权页面交给本地默认浏览器 |
125
130
 
126
131
  `prompt`、`resume` 与 `openBrowser` 经启动提供方来自命令行;生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-tui-app)是所有可接受字段的完整来源。
@@ -157,8 +162,11 @@ runner 等待完整应用就绪(`ctx.get('loader')?.await()`),并在核心
157
162
  | [`src/sessions.ts`](src/sessions.ts) | 基于查询引擎的 `/sessions` 列表及其选择器行 |
158
163
  | [`src/attach.ts`](src/attach.ts) | `/attach`:本地文件经附件存储成为图片或文件块 |
159
164
  | [`src/export.ts`](src/export.ts) | `/export`:通过导出包的归档辅助函数写出会话日志 ZIP |
160
- | [`src/blocks.ts`](src/blocks.ts) | 对话记录组件:用户提示、assistant 回复、工具卡片、通知 |
161
- | [`src/fade.ts`](src/fade.ts) | 流式文本淡入:尾部追踪器、从背景色到前景色的亮度级别,以及对已渲染行的重新着色 |
165
+ | [`src/blocks.ts`](src/blocks.ts) | 对话记录组件:用户提示、assistant 回复、工具卡片、通知;可导航的块还暴露其各小节并绘制焦点标记条 |
166
+ | [`src/navigation.ts`](src/navigation.ts) | 把对话记录看作各个小节、走遍它们的光标,以及检视面板的标题 |
167
+ | [`src/inspector.ts`](src/inspector.ts) | 停靠的检视面板:聚焦小节的标题、各部分选择行、折叠后的各行,及其挂载的组件 |
168
+ | [`src/screen.ts`](src/screen.ts) | 在构建一帧与写出该帧之间带若干次结算的主屏幕、每次结算所依据的重绘窗口,以及每个块的重绘下界 |
169
+ | [`src/fade.ts`](src/fade.ts) | 流式文本淡入:基于挂钟的尾部追踪器、块淡入时钟与注册表、感知均匀的亮度级别,以及对已渲染行的重新着色 |
162
170
  | [`src/prompts.ts`](src/prompts.ts) | 审批、提问、选择器与只读详情提示以及模态队列 |
163
171
  | [`src/transcript.ts`](src/transcript.ts) | 呈现视图、用量与轮次结束原因的纯文本折叠 |
164
172
  | [`src/diff.ts`](src/diff.ts) | diff 卡片的行 diff 与 hunk 选择 |
@@ -175,6 +183,7 @@ runner 等待完整应用就绪(`ctx.get('loader')?.await()`),并在核心
175
183
  | [`tests/app.spec.ts`](tests/app.spec.ts) | 基于伪终端的渲染、按键、命令与两个接缝 |
176
184
  | [`tests/commands.spec.ts`](tests/commands.spec.ts) | 基于脚本化服务的会话、附件、队列、技能、登录、`/login`、Shift+Tab 推理强度循环、导出、引用与推理强度命令 |
177
185
  | [`tests/panels.spec.ts`](tests/panels.spec.ts) | 状态页脚与报告、可导航的子 agent 与 todo 列表、目录命令、命令提示与审批详情 |
186
+ | [`tests/transcript-focus.spec.ts`](tests/transcript-focus.spec.ts) | 走遍对话记录、区域序列、检视面板,以及重绘窗口内的原地标记条 |
178
187
  | [`tests/index.spec.ts`](tests/index.spec.ts) | 创建、恢复分页、fork 切割、会话切换、退出流程与失败报告 |
179
188
  | [`tests/startup.spec.ts`](tests/startup.spec.ts) | 基于真实 Loader 配置树的命令行解析 |
180
189
  | [`../../../apps/cli/tests/profiles/tui/tests/keyless-smoke.e2e.ts`](../../../apps/cli/tests/profiles/tui/tests/keyless-smoke.e2e.ts) | 通过真实启动器与免密钥模拟模型运行随附 profile |
@@ -224,10 +233,12 @@ runner 不向请求前缀添加任何内容;`/model` 切换像在浏览器中
224
233
  - **审批为一次性**——提示只提供允许一次或拒绝,与审批接缝的词汇一致;没有记忆的授权。
225
234
  - **仅浏览器的页面留在浏览器**——workspace 与目录选择器、在应用中打开的链接、轨迹账本与逐条消息的点赞/点踩没有终端对应物;`/settings`、`/plugins`、`/subagents`、`/outline` 与共享的 `/feedback` 以文本覆盖其事实,子 agent 的对话记录通过切换到子会话来阅读。
226
235
  - **交付物只列名、不打开**——`/deliverables` 列出交付路径;浏览器会预览这些文件。
227
- - **历史由终端回滚区持有**——除工具卡片外,对话记录不可搜索或折叠;更丰富的导航由浏览器表层持有。
236
+ - **历史由终端回滚区持有**——键盘可以走遍对话记录的每个块与每个部分,但没有搜索,除工具卡片外也没有折叠;更丰富的导航由浏览器表层持有。
237
+ - **滚走的聚焦块只在检视面板中被标记**——pi-tui 只对它写出的上一帧的最后 `rows` 行做差分重绘,要改动其上方的内容就得清空终端回滚区,因此更靠前的块得不到标记条,检视面板标题写出 `off screen`;占满终端的页面会把这条边界永久抬高,因此页面关闭后,打开它的那个块也可能写出 `off screen`;该小节仍可在检视面板中、以及 `Enter` 打开的页面上阅读。
238
+ - **页面显示的是源文本**——在 assistant 回复上按 `Enter` 打开的是模型写下的 Markdown,而不是对话记录绘制出的渲染结果,因此表格与标题以源码形式呈现。
228
239
  - **面板列出的是常驻,而不是整棵树**——子会话在其会话记录常驻于本进程期间加入面板,因此由进程外提供方运行、在此没有自己会话的子 agent 永远不会出现;`/subagents` 仍是到达每一个持久后代的途径。
229
240
  - **常驻不等于正在工作**——列表的 `activity: 'running'` 表示该子会话的记录常驻,这正是行内 `resident` 所报告的内容;子会话是否在工作则是它旁边单独的 `running` / `idle`,读自本进程中该子会话的 Agent。
230
- - **溢出行之后的各行不可选中**——面板最多绘制六行,`Up` / `Down` 只在这些行内环绕;被折叠进 `+<n> more` 的子会话通过 `/subagents` 到达,后者遍历完整的后代树。
241
+ - **溢出行之后的各行不可选中**——面板最多绘制六行,`Up` / `Down` 在其两端会离开面板;被折叠进 `+<n> more` 的子会话通过 `/subagents` 到达,后者遍历完整的后代树。
231
242
  - **淡入需要终端的应答**——其亮度级别由终端对启动时发出的查询所报告的背景色构建,因此保持沉默、或既不编码真彩色也不编码 256 色的终端只会得到两级的暗淡模式;`NO_COLOR`、被禁用的调色板、`TERM=dumb` 与 `reducedMotion` 则完全关闭该效果。
232
243
  - **终端自身的光标可能闪烁**——编辑器不绘制自己的光标,应用打开终端光标,而 pi-tui 会在其重绘的各行之间移动它;不支持 pi-tui 为一帧包裹的同步输出序列的终端可能显示出这种移动。
233
244
  - **通过 `dsh` 启动器运行**——以其他方式启动该 profile 会在启动时失败,因为只有启动器能请求进程退出。