@xmoon76/dsh-pi-tui 0.3.6 → 0.4.0-alpha.1

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.en.md CHANGED
@@ -10,12 +10,24 @@ A Pi-style terminal frontend for [DeepSeek Harness](https://github.com/deepseek-
10
10
  `dsh-pi-tui` is installed as an independent dsh bundle inside a profile. It provides terminal interaction for streaming conversations, tool calls, session management, subagents, history search, shell commands, approvals, and settings. Models, tools, Sessions, permissions, Skills, Plan, Goal, and Subagent runtime behavior are still provided by DeepSeek Harness.
11
11
 
12
12
  ```sh
13
- dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui
13
+ dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@next
14
14
  dsh --profile pi-tui
15
15
  ```
16
16
 
17
17
  ![dsh-pi-tui](docs/dsh-pi-tui.png)
18
18
 
19
+ ## DSH compatibility and source validation
20
+
21
+ The published package declares the lower-bound-only DSH peer contract `>=0.1.2-alpha.2`. Source validation does not change that contract or vendor DSH into this repository.
22
+
23
+ When the target DSH version is not yet available from npm, validate against the official checkout pinned by commit SHA:
24
+
25
+ ```sh
26
+ pnpm compat:dsh:source -- --dsh-dir "$HOME/project/deepseek-harness"
27
+ ```
28
+
29
+ CI uses Source Mode for `next` and npm Mode for `main` and every tag. The source lane validates the complete official DSH tarball family, TUI presets, and the old-runtime boundary; the published `pi2dsh` ecosystem check is explicitly marked skipped until a compatible published combination exists. See [`docs/dsh-compatibility.md`](docs/dsh-compatibility.md) for the full workflow.
30
+
19
31
  ## Features
20
32
 
21
33
  ### Conversation and tools
@@ -171,134 +183,25 @@ Slash Commands registered by other plugins through `ctx.commands` are discovered
171
183
 
172
184
  ### Footer customization
173
185
 
174
- The status line is a **composable surface** — no plugin or shell needed
175
- for the common cases.
176
-
177
- `/settings` → Status line (or the `footer` key in the `dsh-pi-tui`
178
- settings document) selects the preset:
179
-
180
- | Value | Meaning |
181
- |---|---|
182
- | `default` (legacy `full`) | the classic two-row footer (status + stats) |
183
- | `compact` | the status row only (stats line hidden) |
184
- | `custom` | a versioned `footerLayout` (see below) |
185
- | `command` | a user-configured command renders the status surface (see below) |
186
-
187
- The first three values are selectable in the `/settings` panel; `command`
188
- is NOT in the panel — it can only be enabled through the USER-layer
189
- settings document (`footer: "command"` + `footerCommand`). The `/settings`
190
- Status line row offers exactly `default / compact / custom`.
191
-
192
- `/footer` is a hierarchical interactive configurator: pick a row first
193
- (Row Selector), then edit that row's items — `↑/↓` walks every item of
194
- the row in order (Left/Right are visual grouping only), `←/→` moves an
195
- item across sides, `Space` removes it, `A` opens a searchable Add Picker
196
- (filtered by label / id / description, with the highlighted item's
197
- description below), `M` enters Move Mode to reorder, and `Enter` opens
198
- the Item Editor (Style candidates previewed with the item's real render;
199
- semantic tones; Advanced edits prefix / suffix / importance with a
200
- one-keystroke Reset). The preview is composed by the real footer engine
201
- and — with the contextual help — pinned to the top of the panel; it never
202
- scrolls away at any terminal size. `S` on the Row Selector saves
203
- (persisted); `Esc` walks back page by page and closes on the first page
204
- without touching the active layout. With dirty changes, `Esc` first offers
205
- Save & Exit, Discard & Exit, or Keep Editing; saving waits for the settings
206
- write to succeed. Usable before any session exists.
207
-
208
- The Add Picker also offers `+ Create Custom Text` for user-defined static text items. Their text, default semantic tone, display name, and deletion are editable; definitions are read and persisted only from the USER layer. The definition tone is separate from the placement Tone, and the item can otherwise be shown/hidden, moved, and reordered like any other footer item.
209
-
210
- `footerLayout` is a nested settings object (schemaVersion 1, 1–2 rows,
211
- left/right zones, a separator, finite formatters, semantic tones,
212
- prefix/suffix, importance). The `/footer` configurator builds it
213
- interactively; the YAML shape is:
186
+ `/footer` is the interactive Footer editor. You can combine builtin status items, change their side and order, choose a Style and Tone, edit Prefix/Suffix and Importance, and create your own Footer items.
214
187
 
215
- ```yaml
216
- footer: custom
217
- footerLayout:
218
- schemaVersion: 1
219
- rows:
220
- - left:
221
- - id: agent-preset
222
- format: compact
223
- - id: model
224
- - id: project
225
- - id: context
226
- format: full
227
- - id: cache-hit
228
- - id: token-usage
229
- format: io
230
- - id: performance
231
- format: speed
232
- - id: version
233
- format: tui
234
- right:
235
- - id: focus-mode
236
- separator:
237
- text: " │ "
238
- tone: textDim
239
- ```
188
+ Four item sources are supported:
240
189
 
241
- Builtin format choices are finite and use the existing `format` field (no
242
- second style schema): Model `badge` / `plain` / `compact`; Permission preset
243
- `badge` / `plain` / `compact`; Plan state `badge` / `plain`; Working directory
244
- `short` / `basename` / `full`; Git branch `plain` / `label`; Context `bar` /
245
- `percent` / `full`; Token usage `io` / `total` / `compact`; Cache hit `full` /
246
- `compact`; Performance `full` / `speed` / `latency` measures average time
247
- to first token; Turns/steps `both` / `turns` / `steps`; Version keeps `tui` / `dsh` /
248
- `both`. Omitting `format` continues to use each item's legacy default.
249
-
250
- Builtin item ids: `agent-preset`, `model`, `reasoning`,
251
- `permission-preset`, `sandbox-mode`, `approval-policy`, `plan-state`,
252
- `focus-mode`, `focused-seat`, `view-scope`, `cwd`, `project`,
253
- `git-branch`, `run-state`, `queue`, `tasks`, `agents`, `todo`,
254
- `context`, `cache-hit`, `token-usage`, `performance`, `turns-steps`,
255
- `stats-line`, `version`, `ext:*` (the legacy extension segments).
256
- An invalid `footerLayout` warns once and falls back to the default — the
257
- TUI always starts.
258
-
259
- `footer: command` hands the Status Surface to a user-configured command
260
- (Claude/Kimi style): the current status snapshot is serialized to JSON on
261
- the command's stdin (schemaVersion 1 — no secrets, no credentials, no
262
- prompts), and the command's stdout (sanitized: SGR colors and OSC 8
263
- hyperlinks only) renders the status surface. The Host's instruction
264
- surface (e.g. the Ctrl+C exit hint) always survives on top.
190
+ - **Builtin Item** — Model, Context, Token, Tasks, Git branch, and other built-in status facts;
191
+ - **Custom Text** — a user-created static text item;
192
+ - **Custom Command Item** — a user-created dynamic command output that can be composed with other items;
193
+ - **Extension Item** — a Footer item contributed by a plugin through the Stable Extension API.
265
194
 
266
- ```yaml
267
- footer: command
268
- footerCommand:
269
- schemaVersion: 1
270
- command: "~/.config/dsh/statusline.sh"
271
- timeoutMs: 300 # default 300, max 1000
272
- refreshIntervalMs: 1000 # min 1000
273
- maxRows: 1 # 1..2
274
- ```
195
+ On narrow terminals, builtins with compact forms shorten before lower-importance content is dropped. Runtime compaction never rewrites the Style you saved.
196
+
197
+ A `/footer` Custom Command Item is different from `footer: command`: the former is one dynamic item that can sit next to Model, Context, and other items; the latter hands the whole Footer status surface to one user command.
275
198
 
276
- **Security:** the command is executed ONLY when it lives in the USER
277
- layer of your settings document. A repository/project-supplied
278
- `footerCommand` is never executed — command mode is disabled and the
279
- native layout applies. The command refreshes periodically according to
280
- `refreshIntervalMs` and renders at most two rows; failures (empty output,
281
- non-zero exit, timeout) fall back to the native layout automatically.
282
-
283
- ### Extension footer items
284
-
285
- Plugins can contribute **configurable footer items** through the Stable
286
- extension API (`@xmoon76/dsh-pi-tui/extensions`): register a
287
- `FooterItemContribution` on the `chrome.footer.item` slot — a label and a
288
- plain-data `segment` (styled spans; the host strips any terminal control
289
- sequence, plugins never style the terminal). Users show/hide, reorder and
290
- zone-place the item in `/footer` exactly like a builtin item. Feature-detect
291
- the `slot.chrome.footer.item` capability before registering (it is
292
- advertised before any surface exists). The item's config identity is the
293
- canonical key `ext:<owner>/<id>` where the owner is the plugin's stable
294
- name — **stable across HMR**: a layout referencing an unloaded plugin's
295
- item keeps the reference and recovers automatically when the plugin
296
- reloads. An npm-scoped plugin name (`@scope/name`) is legal: its `/` is
297
- percent-encoded via `encodeURIComponent` in the key
298
- (`ext:%40scope%2Fname/<id>`); the id itself must
299
- not contain `/`. The legacy `chrome.footer.status` slot is unchanged: its
300
- segments aggregate into the single `ext:*` item. Full author guide:
301
- [docs/extension-api.md](docs/extension-api.md).
199
+ For the full `/footer` workflow, Custom Text / Command items, YAML reference, security model, and troubleshooting, see:
200
+
201
+ - [Footer customization guide](docs/footer-customization.md)
202
+ - [Extension API for plugin authors](docs/extension-api.md)
203
+
204
+ `/statusline` is an alias of `/footer`.
302
205
 
303
206
  ## Common keys
304
207
 
@@ -405,14 +308,52 @@ dsh-pi-tui:
405
308
  * DeepSeek Harness
406
309
  * Node.js `^22.19.0 || >=24`
407
310
 
408
- The project currently follows the DeepSeek Harness `0.1.1-rc.x` release line.
311
+ ### DSH/TUI version pairing (important)
312
+
313
+ | TUI package line | Matching DSH line | Notes |
314
+ |---|---|---|
315
+ | `0.4.0-alpha.1` (`@next`) | `>=0.1.2-alpha.2` | Current prerelease; each release validates its concrete DSH family |
316
+ | `0.3.x` (`@0.3`) | `0.1.1-rc.2` | Legacy runtime line |
317
+
318
+ Do not mix the two lines: DSH 0.1.1 is outside the 0.4 peer window and the
319
+ normal incompatible-runtime boundary will fail. The startup row prints upgrade
320
+ and rollback guidance when concurrent Loader ordering allows it, but that
321
+ friendly notice is best-effort rather than a startup-order guarantee. If you
322
+ keep DSH 0.1.1, use the 0.3 TUI line. For the current `0.4.0-alpha.1`, install
323
+ DSH first and then add the TUI to a profile:
324
+
325
+ ```sh
326
+ npm install -g @deepseek-ai/dsh@0.1.2-alpha.3
327
+ dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@next
328
+ dsh --profile pi-tui
329
+ ```
330
+
331
+ If you need to keep the legacy DSH runtime:
332
+
333
+ ```sh
334
+ npm install -g @deepseek-ai/dsh@0.1.1-rc.2
335
+ dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@0.3
336
+ dsh --profile pi-tui
337
+ ```
338
+
339
+ The declared 0.4 support range is `>=0.1.2-alpha.2`; each release validates
340
+ its concrete DSH family. Running only `npm install -g @xmoon76/dsh-pi-tui` does
341
+ not install the plugin into a DSH profile; the `dsh plugin` command is still
342
+ required.
343
+
344
+ New agent sessions use the official roster's selected preset id. A custom DSH
345
+ preset literally named `code` is valid and remains `code` when it exists in the
346
+ current roster. Old persisted `code` defaults/session values fall back to `ptc`
347
+ only after the roster proves that no custom `code` preset exists.
409
348
 
410
349
  ### npm
411
350
 
412
- Using a dedicated `pi-tui` profile is recommended:
351
+ Using a dedicated `pi-tui` profile is recommended. Once a stable 0.4 release
352
+ exists, use the matching stable TUI channel (`@latest`); use `@next` for the
353
+ prerelease channel:
413
354
 
414
355
  ```sh
415
- dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui
356
+ dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@next
416
357
  dsh --profile pi-tui
417
358
  ```
418
359
 
@@ -422,6 +363,15 @@ Resume an existing Session:
422
363
  dsh --profile pi-tui --session <session-id>
423
364
  ```
424
365
 
366
+ ### Source Mode (validation only)
367
+
368
+ Source Mode is only for `next` CI and local compatibility validation. It reads the full commit pin in `test/compat/dsh-source.json`, builds the official DSH tarball family, installs it through temporary pnpm overrides, and removes the temporary state afterward. Do not write DSH source paths, `file:` dependencies, or workspace symlinks into a published package.
369
+
370
+ ```sh
371
+ pnpm compat:dsh:source -- --dsh-dir "$HOME/project/deepseek-harness"
372
+ pnpm compat:dsh:npm
373
+ ```
374
+
425
375
  The published package already contains the Pi TUI fork required at runtime. No separate internal TUI package needs to be installed.
426
376
 
427
377
  ### Update
package/README.md CHANGED
@@ -10,12 +10,24 @@
10
10
  `dsh-pi-tui` 作为独立的 dsh bundle 安装到 profile 中,提供流式对话、工具调用、会话管理、Subagent、历史搜索、Shell、审批与设置等终端交互。模型、工具、Session、权限、Skills、Plan、Goal、Subagent 等运行时能力仍由 DeepSeek Harness 提供。
11
11
 
12
12
  ```sh
13
- dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui
13
+ dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@next
14
14
  dsh --profile pi-tui
15
15
  ```
16
16
 
17
17
  ![dsh-pi-tui](docs/dsh-pi-tui.png)
18
18
 
19
+ ## DSH 兼容性与源码验证
20
+
21
+ 发布包通过 `package.json` peer contract 使用 DSH `>=0.1.2-alpha.2`;源码验证不会修改这个发布契约,也不会把 DSH vendor 进本仓库。
22
+
23
+ 当目标 DSH 版本尚未发布到 npm 时,可以用固定 commit 的官方源码包做本地验证:
24
+
25
+ ```sh
26
+ pnpm compat:dsh:source -- --dsh-dir "$HOME/project/deepseek-harness"
27
+ ```
28
+
29
+ CI 中 `next` 使用 Source Mode,`main` 和所有 tag 使用 npm Mode。源码 lane 会验证完整的官方 DSH tarball family、TUI 预设和旧 runtime 边界;依赖已发布 `pi2dsh` 的生态检查会明确标记为 skipped。详细流程见 [`docs/dsh-compatibility.md`](docs/dsh-compatibility.md)。
30
+
19
31
  ## 功能
20
32
 
21
33
  ### 对话与工具
@@ -170,118 +182,25 @@ TUI 使用 DSH 提供的模型和设置服务。
170
182
 
171
183
  ### Footer 自定义
172
184
 
173
- 状态行是一个**可组合表面**——常见场景无需插件或 shell。
174
-
175
- `/settings` → Status line(或 `dsh-pi-tui` 设置文档中的 `footer` 键)
176
- 选择预设:
185
+ `/footer` 提供交互式 Footer 编辑器。你可以组合内置状态条目,调整左右位置、顺序、Style、Tone、Prefix/Suffix 和 Importance,也可以创建自己的 Footer 条目。
177
186
 
178
- | 值 | 含义 |
179
- |---|---|
180
- | `default`(旧名 `full`) | 经典两行 Footer(状态 + 统计) |
181
- | `compact` | 仅状态行(隐藏统计行) |
182
- | `custom` | 版本化 `footerLayout`(见下) |
183
- | `command` | 用户配置的命令渲染状态表面(见下) |
187
+ 支持四类条目:
184
188
 
185
- 前三个值可在 `/settings` 面板选择;`command` **不在面板中**——它只能
186
- 通过 USER 层设置文档(`footer: "command"` + `footerCommand`)启用,
187
- `/settings` 的 Status line 行只有 `default / compact / custom` 三个选项。
189
+ - **Builtin Item**:Model、Context、Token、Tasks、Git branch 等内置状态;
190
+ - **Custom Text**:用户创建的固定文本;
191
+ - **Custom Command Item**:用户创建的动态命令输出,可和其他条目一起排列;
192
+ - **Extension Item**:插件通过 Stable Extension API 提供的 Footer 条目。
188
193
 
189
- `/footer` 是层级式交互配置器:先选行(Row Selector),再编辑该行的
190
- 条目——`↑/↓` 在整行条目间顺序移动(Left/Right 只是视觉分组),
191
- `←/→` 左右换侧,`Space` 移除,`A` 打开可搜索的 Add Picker(按
192
- label / id / 描述过滤,选中项下方显示描述),`M` 进入 Move Mode
193
- 排序,`Enter` 打开 Item Editor(Style 候选以条目的真实渲染作示例;
194
- Tone 语义色;Advanced 编辑 prefix / suffix / importance 并可一键
195
- Reset)。预览由真实 Footer 引擎合成,与 contextual help 一起固定在
196
- 面板顶部,任何终端尺寸下都不会随列表滚动消失。Row Selector 页 `S`
197
- 保存(持久化),`Esc` 逐页返回、在首页关闭且不影响当前生效布局。存在未保存改动时,
198
- `Esc` 会先显示 Save & Exit、Discard & Exit 或 Keep Editing;保存会等待设置写入成功。
199
- 无会话时也可使用。
194
+ 在窄终端中,支持 compact 的内置条目会先自动缩短;空间仍不足时再按 Importance 隐藏低优先级内容。运行时 compact 不会修改你保存的 Style。
200
195
 
201
- Add Picker 的末尾还可以选择 `+ Create Custom Text`,创建用户自定义的静态文本条目。创建后可编辑文本、默认语义色、显示名称,也可以删除;条目定义只从 USER 层读取并持久化。定义 Tone 与布局中的放置 Tone 分开,条目仍可在 `/footer` 中显示/隐藏、移动和排序。
196
+ `/footer` 的 Custom Command Item 和 `footer: command` 是两种不同能力:前者只是一个可以与 Model、Context 等混排的动态条目;后者把整个 Footer 状态表面交给一个用户命令。
202
197
 
203
- `footerLayout` 是嵌套设置对象(schemaVersion 1,1–2 行,左/右区域,
204
- 分隔符,有限 formatter,语义 tone,prefix/suffix,importance)。
205
- `/footer` 配置器可交互地构建它;YAML 形状如下:
198
+ 完整的 `/footer` 使用方法、Custom Text / Command、YAML 配置、安全模型和排错说明见:
206
199
 
207
- ```yaml
208
- footer: custom
209
- footerLayout:
210
- schemaVersion: 1
211
- rows:
212
- - left:
213
- - id: agent-preset
214
- format: compact
215
- - id: model
216
- - id: project
217
- - id: context
218
- format: full
219
- - id: cache-hit
220
- - id: token-usage
221
- format: io
222
- - id: performance
223
- format: speed
224
- - id: version
225
- format: tui
226
- right:
227
- - id: focus-mode
228
- separator:
229
- text: " │ "
230
- tone: textDim
231
- ```
232
-
233
- 内置 format 是有限集合,继续使用现有的 `format` 字段(不新增第二套
234
- style schema):Model 为 `badge` / `plain` / `compact`;Permission preset
235
- 为 `badge` / `plain` / `compact`;Plan state 为 `badge` / `plain`;Working
236
- directory 为 `short` / `basename` / `full`;Git branch 为 `plain` / `label`;
237
- Context 为 `bar` / `percent` / `full`;Token usage 为 `io` / `total` /
238
- `compact`;Cache hit 为 `full` / `compact`;Performance 为 `full` / `speed` /
239
- `latency`(平均首 token 时间);Turns/steps 为 `both` / `turns` / `steps`;
240
- Version 保留 `tui` / `dsh` / `both`。省略 `format` 时仍使用各条目的旧默认值。
241
-
242
- 内置条目 id:`agent-preset`、`model`、`reasoning`、
243
- `permission-preset`、`sandbox-mode`、`approval-policy`、`plan-state`、
244
- `focus-mode`、`focused-seat`、`view-scope`、`cwd`、`project`、
245
- `git-branch`、`run-state`、`queue`、`tasks`、`agents`、`todo`、
246
- `context`、`cache-hit`、`token-usage`、`performance`、`turns-steps`、
247
- `stats-line`、`version`、`ext:*`(旧扩展段)。非法的 `footerLayout`
248
- 会警告一次并回退到默认布局——TUI 始终能启动。
249
-
250
- `footer: command` 把状态表面交给用户配置的命令(Claude/Kimi 风格):
251
- 当前状态快照以 JSON 序列化到命令的 stdin(schemaVersion 1——不含
252
- secret、凭据、提示词),命令的 stdout(经过净化:仅保留 SGR 颜色与
253
- OSC 8 超链接)渲染状态表面。Host 的指令表面(如 Ctrl+C 退出提示)
254
- 始终叠加在最上层。
255
-
256
- ```yaml
257
- footer: command
258
- footerCommand:
259
- schemaVersion: 1
260
- command: "~/.config/dsh/statusline.sh"
261
- timeoutMs: 300 # 默认 300,最大 1000
262
- refreshIntervalMs: 1000 # 最小 1000
263
- maxRows: 1 # 1..2
264
- ```
200
+ - [Footer 自定义完整指南](docs/footer-customization.md)
201
+ - [Extension API(插件作者)](docs/extension-api.md)
265
202
 
266
- **安全:** 只有当命令位于你的设置文档的 USER 层时才会被执行。
267
- 仓库/项目提供的 `footerCommand` 永远不会被执行——命令模式被禁用并
268
- 回退到原生布局。命令按 `refreshIntervalMs` 周期刷新,每次最多输出 2 行;失败(空输出、非零退出、超时)自动回退到原生布局。
269
-
270
- ### 扩展 Footer 条目
271
-
272
- 插件可以通过 Stable 扩展 API(`@xmoon76/dsh-pi-tui/extensions`)贡献
273
- **可配置的 Footer 条目**:在 `chrome.footer.item` 槽位注册一个
274
- `FooterItemContribution`——包含 label 与纯数据 `segment`(带样式的
275
- span;Host 会剥离任何终端控制序列,插件永远不能直接给终端上样式)。
276
- 用户可在 `/footer` 中像内置条目一样开关、排序、左右放置。注册前请
277
- 先 feature-detect `slot.chrome.footer.item` 能力(该能力在任何 surface
278
- 存在之前就已声明)。条目的配置身份是规范键 `ext:<owner>/<id>`,其中
279
- owner 是插件的稳定名称——**跨 HMR 稳定**:引用已卸载插件条目的布局
280
- 保留引用,插件重载后自动恢复。npm scoped 插件名(`@scope/name`)合法:
281
- 其 `/` 在键中按 `encodeURIComponent` 百分号编码(`ext:%40scope%2Fname/<id>`);
282
- id 本身不得包含 `/`。旧的 `chrome.footer.status` 槽位不变:
283
- 其 segment 聚合为单一的 `ext:*` 条目。完整作者指南:
284
- [docs/extension-api.md](docs/extension-api.md)。
203
+ `/statusline` 是 `/footer` 的别名。
285
204
 
286
205
  ## 常用按键
287
206
 
@@ -378,14 +297,49 @@ dsh-pi-tui:
378
297
  * DeepSeek Harness
379
298
  * Node.js `^22.19.0 || >=24`
380
299
 
381
- 项目当前跟随 DeepSeek Harness `0.1.1-rc.x` 版本线开发。
300
+ ### DSH 与 TUI 版本对应(重要)
301
+
302
+ | TUI 包版本 | 对应 DSH 版本 | 说明 |
303
+ |---|---|---|
304
+ | `0.4.0-alpha.1`(`@next`) | `>=0.1.2-alpha.2` | 当前预发布线;按每个发布版本的具体 DSH family 验证 |
305
+ | `0.3.x`(`@0.3`) | `0.1.1-rc.2` | 旧运行时兼容线 |
306
+
307
+ 不要把两条线混装:DSH 0.1.1 不在 0.4 的 peer 支持范围内,运行时会在
308
+ 正常的不兼容边界以非零状态失败。启动行会在 Loader 并发挂载顺序允许时打印
309
+ 升级和回退提示,但该友好提示是 best-effort,不是启动顺序保证;保留 DSH
310
+ 0.1.1 时请使用 0.3。`0.4.0-alpha.1` 的推荐安装顺序如下(先装 DSH,再把
311
+ TUI 装入 profile):
312
+
313
+ ```sh
314
+ npm install -g @deepseek-ai/dsh@0.1.2-alpha.3
315
+ dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@next
316
+ dsh --profile pi-tui
317
+ ```
318
+
319
+ 如果需要保留旧 DSH:
320
+
321
+ ```sh
322
+ npm install -g @deepseek-ai/dsh@0.1.1-rc.2
323
+ dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@0.3
324
+ dsh --profile pi-tui
325
+ ```
326
+
327
+ `0.4` 的声明支持范围是 `>=0.1.2-alpha.2`;每个发布版本都会验证具体的
328
+ DSH family。仅执行 `npm install -g @xmoon76/dsh-pi-tui` 不会把插件安装进
329
+ DSH profile,实际使用仍应执行上面的 `dsh plugin` 命令。
330
+
331
+ 新的 Agent preset 使用当前 roster 中选定的 id。DSH 允许合法的自定义
332
+ `code` preset;只要当前 roster 存在它,显式输入和持久化状态都会保留 `code`。
333
+ 旧数据中省略请求的 `code` default/session 值只有在确认 roster 不含 `code` 后
334
+ 才会回退到 `ptc`。
382
335
 
383
336
  ### npm
384
337
 
385
- 推荐使用单独的 `pi-tui` profile:
338
+ 推荐使用单独的 `pi-tui` profile。稳定版发布后,使用与 DSH 版本匹配的
339
+ TUI channel(稳定版用 `@latest`,预发布版用 `@next`):
386
340
 
387
341
  ```sh
388
- dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui
342
+ dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@next
389
343
  dsh --profile pi-tui
390
344
  ```
391
345
 
@@ -395,6 +349,15 @@ dsh --profile pi-tui
395
349
  dsh --profile pi-tui --session <session-id>
396
350
  ```
397
351
 
352
+ ### Source Mode(仅验证)
353
+
354
+ Source Mode 只用于 `next` 的 CI 和本地兼容性验证,不是发布或用户安装方式。它从 `test/compat/dsh-source.json` 的完整 commit SHA 构建官方 DSH tarball family,通过临时 pnpm overrides 安装,并在完成后清理临时状态。不要把 DSH 源码路径、`file:` 依赖或 workspace symlink 写入发布 package。
355
+
356
+ ```sh
357
+ pnpm compat:dsh:source -- --dsh-dir "$HOME/project/deepseek-harness"
358
+ pnpm compat:dsh:npm
359
+ ```
360
+
398
361
  安装包已经包含运行所需的 Pi TUI fork,不需要额外安装内部的 TUI package。
399
362
 
400
363
  ### 更新
package/cordis.patch.yml CHANGED
@@ -42,24 +42,23 @@
42
42
  - id: authorization
43
43
  name: '@deepseek-ai/dsh-authorization'
44
44
 
45
- # The agent-preset roster. The SHIPPED root is an assembly fact of this
46
- # bundle: the four presets (standard/code/minimal/cordis) travel with the
47
- # package under config/agent-presets and carry `system` trust. The
48
- # expression reads the root from the startup service, so it resolves only
49
- # after `tuiStartup` exists — the web bundle's `webserver` row pattern
50
- # (no `!!js` at boot time, no HMR race). The writable root is
51
- # `dsh-agent-presets`' own default (`includeUserRoot`):
52
- # `$DSH_HOME/.agent-presets`, where a person authors their own presets.
45
+ # Consume DSH 0.1.2's official shipped presets directly. The upstream
46
+ # service defaults includeShippedRoot/includeUserRoot to true, so this
47
+ # bundle deliberately owns no duplicate standard/ptc/minimal/cordis root.
48
+ # The legacy persisted id `code` is normalized at the data boundary to
49
+ # `ptc`; it is not another preset row.
53
50
  - id: agent-presets
54
51
  name: '@deepseek-ai/dsh-agent-presets'
55
- inject: [tuiStartup]
56
52
  config:
57
53
  default: standard
58
- roots:
59
- - path: !!js ctx.tuiStartup.shippedPresetRoot
60
- trust: system
61
54
 
62
- # Host-plane TypeScript runtime: the `code` preset's `tool-presentation`
55
+ # The official standard preset enables per-session subagent model
56
+ # selection. Its tool-subagent row requires this Host service when it
57
+ # mounts; without the row the default standard composition throws.
58
+ - id: subagent-model-selection-settings
59
+ name: '@deepseek-ai/dsh-tool-subagent/model-selection-settings'
60
+
61
+ # Host-plane TypeScript runtime: the `ptc` preset's `tool-presentation`
63
62
  # row waits for `codeRuntime` and fails the preset at mount without it
64
63
  # (same host row the web bundle inserts).
65
64
  - id: code-runtime
@@ -85,10 +84,12 @@
85
84
  #
86
85
  # Rows that STAY on the host plane (not listed): `tools` (registry), `shell-env`
87
86
  # (publishes the DSH env the model's shell sees), `subagent` + its spawn/fork
88
- # backends (process singletons), `tool-subagent-report` (host singleton), the
89
- # goal/jobs/compaction services and their commands, `permission`, `approval`,
90
- # `sandbox*`, `fs-observation-policy`, `spill*`, `timeout-policy`,
91
- # `repeat-tool-reminder`, `session-*`, `token-meter`.
87
+ # backends (process singletons), `tool-subagent-report` (host singleton),
88
+ # `subagent-model-selection-settings`, the goal/jobs/compaction services and
89
+ # their host infrastructure, `permission`, `approval`, `sandbox*`,
90
+ # `fs-observation-policy`, `spill*`, `timeout-policy`, `repeat-tool-reminder`,
91
+ # `session-*`, `token-meter`. `command-goal` is disabled below and reintroduced
92
+ # only by presets that own that command in their scoped composition.
92
93
 
93
94
  - id: tool-bash
94
95
  disabled: true
@@ -114,6 +115,9 @@
114
115
  - id: tool-skill
115
116
  disabled: true
116
117
 
118
+ - id: command-goal
119
+ disabled: true
120
+
117
121
  - id: tool-goal
118
122
  disabled: true
119
123
 
package/dist/builtins.mjs CHANGED
@@ -1,5 +1,5 @@
1
+ import { H as PI_TUI_EXTENSIONS_SERVICE, L as dshVersion } from "./src-syk6h8J9.mjs";
1
2
  import { TUI_STARTUP_SERVICE } from "./startup.mjs";
2
- import { I as dshVersion, V as PI_TUI_EXTENSIONS_SERVICE } from "./src-Bk66X6NR.mjs";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  //#region src/builtins.ts
@@ -1,3 +1,3 @@
1
+ import { B as inject, H as PI_TUI_EXTENSIONS_SERVICE, U as API_VERSION, V as name, W as describeKey, z as apply } from "./src-syk6h8J9.mjs";
1
2
  import "./startup.mjs";
2
- import { B as name, H as API_VERSION, R as apply, U as describeKey, V as PI_TUI_EXTENSIONS_SERVICE, z as inject } from "./src-Bk66X6NR.mjs";
3
3
  export { API_VERSION, PI_TUI_EXTENSIONS_SERVICE, apply, describeKey, inject, name };
package/dist/index.d.mts CHANGED
@@ -5,6 +5,7 @@ import { ContentBlock } from "@deepseek-ai/dsh-llm";
5
5
  import { SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
6
6
  import { CommandDescriptor } from "@deepseek-ai/dsh-commands";
7
7
  import { Context } from "@deepseek-ai/cordis";
8
+ import { SessionEvent as SessionEvent$1 } from "@deepseek-ai/dsh-session/types";
8
9
  //#region src/image/admission.d.ts
9
10
  /** Structural subset of `ImageAttachmentRef`. */
10
11
  interface ImageAttachmentRefLike {
@@ -210,6 +211,16 @@ declare const inject: string[];
210
211
  interface Config {
211
212
  /** Resumed session id; a fresh session is created when absent. */
212
213
  sessionId?: string;
214
+ /** Test seam: the pre-mount status output (defaults to process.stdout,
215
+ * TTY-gated). Injectable so the runner tests capture the status writes
216
+ * without patching the global stdout (which would fight the test
217
+ * reporter's own writes). STRUCTURAL on purpose: the public Config type
218
+ * must not reference the internal startup-status module (the public
219
+ * .d.mts leak gate). */
220
+ startupStatusOutput?: {
221
+ readonly isTTY?: boolean;
222
+ write(text: string): unknown;
223
+ };
213
224
  }
214
225
  declare const Config: z<Config>;
215
226
  /**
@@ -467,6 +478,11 @@ interface ResolveInitialCatalogOptions {
467
478
  /** The context surface the collectors read services from. */
468
479
  readonly ctx: SurfaceCatalogContext;
469
480
  readonly diag: Diag;
481
+ /** Suspend the pre-mount startup status before an ordinary log write
482
+ * (the status owns the current terminal line; a TTY shares one cursor
483
+ * between stdout and stderr). Called right before every diag.warn this
484
+ * function may emit. */
485
+ readonly onLog?: () => void;
470
486
  }
471
487
  declare function resolveInitialCatalog(options: ResolveInitialCatalogOptions): Promise<InitialCatalogResolution>;
472
488
  declare function subagentJobTranscriptId(snapshot: unknown): string | undefined;
@@ -635,6 +651,15 @@ declare function settleCompactionSurface(app: CompactionSettleSurface, refreshSt
635
651
  * turn can close (turn/end) before its compaction settles, and the
636
652
  * single-Esc cancel must stay armed until compaction/end. */
637
653
  declare function busyAfterTurnBoundary(eventType: 'turn/start' | 'turn/end', compacting: boolean): boolean;
654
+ /** PR D2 test seam: whether a session event type marks the model-visible
655
+ * context dirty (re-measure through the SessionReader port) or only
656
+ * repaints cheaply (cached measurement). The firehose routes every event
657
+ * through this classification — the single source of truth for the
658
+ * status/measurement split. `compaction/end` is classified 'measure' but
659
+ * the firehose deliberately SKIPS it here: a matched compaction settle
660
+ * re-measures through the fold-outcome path (settleCompactionSurface), so
661
+ * a STALE compaction/end can never trigger a measurement. */
662
+ declare function contextRefreshKind(eventType: string): 'measure' | 'cheap';
638
663
  /**
639
664
  * The in-flight compaction state a resumed session log implies: the newest
640
665
  * compaction bracket decides. A `session/end-seed` boundary makes any
@@ -670,7 +695,10 @@ interface AgentComposition {
670
695
  * A deployment with no roster composes nothing and every session shares the
671
696
  * host composition, which is the behavior before presets existed.
672
697
  * @param ctx - the runner context (services read through `ctx.get`).
673
- * @param selected - the mutable model selection every setup installs.
698
+ * @param installSelection - installs a fresh Agent-local model selection ref
699
+ * during setup. A ModelSelectionRef is still accepted for source
700
+ * compatibility with standalone composition callers; the runner always
701
+ * supplies the Agent-local installer.
674
702
  * @param presetId - the requested preset, or `undefined` for the default.
675
703
  * @param focusState - the shared Focus runtime state (STRUCTURAL on
676
704
  * purpose: the public declaration bundle must not inline src/focus.ts —
@@ -683,13 +711,12 @@ interface AgentComposition {
683
711
  * @returns the id to record on the header (absent without a roster) and the setup callback.
684
712
  * @throws when the roster supplies no such preset.
685
713
  */
686
- declare function composeAgent(ctx: Context, selected: ModelSelectionRef, presetId?: string, focusState?: {
714
+ declare function composeAgent(ctx: Context, installSelection: ((agentCtx: Context) => void) | ModelSelectionRef, presetId?: string, focusState?: {
687
715
  enabled: boolean;
688
716
  }, diag?: Diag): Promise<AgentComposition>;
689
717
  /**
690
- * The preset a persisted session actually runs, from its log (newest
691
- * selection winning), or undefined when persistence is absent, the session is
692
- * unknown, or its log predates the roster.
718
+ * The preset a persisted session actually runs, resolved by the DSH 0.1.2+
719
+ * session projection (header initialization plus the latest selection event).
693
720
  * @param ctx - the runner context.
694
721
  * @param sessionId - the persisted session id.
695
722
  * @returns the recorded preset id, or undefined to compose the default.
@@ -698,7 +725,7 @@ declare function recordedPreset(ctx: Context, sessionId: string): Promise<string
698
725
  /** The session surface {@link recomposeBlank} needs: its log and the append seam. */
699
726
  interface RecomposableSession {
700
727
  readonly id: string;
701
- readonly events: readonly SessionEvent[];
728
+ readonly events: readonly SessionEvent$1[];
702
729
  append(type: 'agent-preset/selected', data: {
703
730
  agentPreset: string;
704
731
  }): unknown;
@@ -735,4 +762,4 @@ declare function recomposeBlank(ctx: Context, agent: {
735
762
  */
736
763
  declare function apply(ctx: Context, config: Config): void;
737
764
  //#endregion
738
- export { AgentComposition, CompactionFold, CompactionSettleSurface, Config, HOST_COMMAND_CATALOG, InitialCatalogResolution, InterruptAgentLike, InterruptWriterLike, LOCAL_COMMANDS, PendingSubagentCall, QueueFoldResult, QueueInboxMessage, QueueNoticeSource, RecomposableSession, RecomposeOutcome, ResolveInitialCatalogOptions, SESSIONLESS_COMMANDS, SessionLockRefusedError, ViewerOpenToken, apply, busyAfterTurnBoundary, commandRejectsImages, compactingFromLog, composeAgent, createViewerOpenToken, dangerCommand, foldCompactionEvent, foldQueueRows, forkSeed, inject, interruptAgent, isPlainExitPrompt, isSubagentSettlementNotice, isUserQueueInput, matchPendingSubagentCall, name, normalizeSkillInvocation, recomposeBlank, recordedPreset, resolveInitialCatalog, resumeCommand, runningProfile, settleCompactionSurface, shouldConsumeAdvertisedMiss, shouldSteerOnEnter, subagentJobTranscriptId, subagentJobViewHint, subagentNoticeIsFailure, teardownViewerForSessionSwap, viewerActionCapability };
765
+ export { AgentComposition, CompactionFold, CompactionSettleSurface, Config, HOST_COMMAND_CATALOG, InitialCatalogResolution, InterruptAgentLike, InterruptWriterLike, LOCAL_COMMANDS, PendingSubagentCall, QueueFoldResult, QueueInboxMessage, QueueNoticeSource, RecomposableSession, RecomposeOutcome, ResolveInitialCatalogOptions, SESSIONLESS_COMMANDS, SessionLockRefusedError, ViewerOpenToken, apply, busyAfterTurnBoundary, commandRejectsImages, compactingFromLog, composeAgent, contextRefreshKind, createViewerOpenToken, dangerCommand, foldCompactionEvent, foldQueueRows, forkSeed, inject, interruptAgent, isPlainExitPrompt, isSubagentSettlementNotice, isUserQueueInput, matchPendingSubagentCall, name, normalizeSkillInvocation, recomposeBlank, recordedPreset, resolveInitialCatalog, resumeCommand, runningProfile, settleCompactionSurface, shouldConsumeAdvertisedMiss, shouldSteerOnEnter, subagentJobTranscriptId, subagentJobViewHint, subagentNoticeIsFailure, teardownViewerForSessionSwap, viewerActionCapability };