dsh-composer-ux 0.12.0 → 0.13.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/CHANGELOG.md CHANGED
@@ -2,6 +2,93 @@
2
2
 
3
3
  本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [0.13.1] — 2026-09-30(面板分两半:优化提示词 / 快捷指令二选一;修「展开后看不到 插入/重新优化/复制」)
6
+
7
+ 起因(用户原话):**「现在是点击提示词优化按钮才能展开优化输入框,我想要点开快捷指令分为两个部分:一部分是优化提示词部分,一部分是快捷指令部分。」**
8
+ 并附了一条真机发现的 bug:**「展开优化提示词内容之后我看不到复制和填入之类的按钮」**。
9
+
10
+ ### 改成什么样(开工前问清并确认过的三点)
11
+
12
+ - **默认展开「快捷指令」**,标题行的**三档档位与 ✨ 优化提示词按钮始终露在外面** —— 跑一次优化永远是一步,不必先展开。
13
+ - 中部**二选一**:`优化提示词`(结果框)或 `快捷指令`(分类标签 + 条目列表 + 底部计数),中间一个切换按钮(▸ / ▾)换着看。
14
+ - **点 ✨ 或结果框出现时自动切到「优化提示词」那半**,否则刚跑出来的东西会被自己藏在收起状态里;
15
+ 手动切回快捷指令不会被抢回去;点结果框的「✕」顺手切回快捷指令。
16
+
17
+ ### 修掉的 bug(用户报的那条)
18
+
19
+ 结果框原先和快捷指令列表**挤同一块固定高度**(面板 `max-height: min(60vh, 520px)` + `overflow: hidden`,
20
+ 而结果框自身没有滚动或高度上限):内容一长(条目多 + 成品多行 + 输入框 30vh)就把列表挤到 0,
21
+ 再继续把**自己底部那排按钮(插入输入框 / 重新优化 / 复制)顶出面板边界裁掉**。修法两条:
22
+
23
+ 1. 结果框拆成「**可滚内容区 + 钉在底部的按钮行**」(`dockBody` + `dockButtonsPinned`);
24
+ 2. 两半互斥、各自内部滚(`quickSectionBody` / `quickList` 都补上 `flex: 1 1 auto` + `min-height: 0`)。
25
+
26
+ 顺带一处实现改进:自动切换原本写在 `useEffect` 里,会**先渲染一帧"快捷指令"再切**(点 ✨ 会看到面板闪一下),
27
+ 而且 **SSR 里 effect 不跑**、渲染测试根本测不到 —— 改成 React 文档里"props 变化时同步调整 state"的渲染期写法,
28
+ 两边一起解决(这个决定是被渲染测试逼出来的:第一版这么写时新套件里 7 条全红,红的全是这一组)。
29
+
30
+ ### 内部
31
+
32
+ - 新增 `test/panel-split-render.mjs`(**20 条**,`react-dom/server` **真渲染**):默认展开哪半、两半互斥、
33
+ 三个按钮**在最后一个滚动容器之后**、跑的时候看得到「取消」、锚点为空不渲染。
34
+ - `client-registration.mjs` 新增 **2.0g 节(11 条)** 源码级护栏;两半规则抽成纯函数 `client/panel-sections.ts`。
35
+ - 测试从 **21 套件 2160 条**涨到 **22 套件 2190 条**;变异从 **128 条**涨到 **132 条,全部咬住**
36
+ (EA 默认那半 / EB 有结果也不切 / EC 内容区不再自己滚 / ED 切换按钮不再换另一半)。
37
+
38
+ ## [0.13.0] — 2026-09-30(结果框重启恢复 + 逐轮元数据台账 + 只读查证工具 + 发版门禁三脚本 + 证据登记表)
39
+
40
+ > 起因:用户拿社区插件 [WestFox-AwA/dsh-prompt-optimizer](https://github.com/WestFox-AwA/dsh-prompt-optimizer)
41
+ > (0.7.8 主线)与上一轮对比的结论,逐条确认"建议做的全要做,只读工具查证也做"。
42
+ > 这一版的一句话取舍:**把"看不见的机制"补成"看得见、可回放、可核对"** —— 结果框跨重启还在、
43
+ > 每轮留下一行只含数字的台账、解释层能只读查证、发版前三条门禁自己会红,外加一张诚实的能力登记表。
44
+
45
+ ### 新增
46
+
47
+ - **结果框持久化 + 重启恢复**(①):写进 `$DSH_HOME/composer-ux/optimize-dock.json`,下次启动/刷新恢复。
48
+ 原子写(临时文件 → rename)、**损坏就改名成 `<名>.corrupt-<时间戳>.json` 留证据**(那份文件里可能是你唯一一份成品)、
49
+ 超 64 KB **拒写而不是截半**;信封带版本号,**版本不认识就不解析也不动文件**。读回来一律先净化:
50
+ 认不出就当"没有可恢复的结果",宁可空着也不拿脏数据糊界面;**重启前没跑完的那一轮恢复成「已取消」**,
51
+ 不假装还在跑(秒表也不走)。新增第七条 exact 路由 `GET/POST /composer-ux/optimize-state`(先过官方信任关卡,
52
+ 只读写固定路径那一份)。设置页加「重启后保留结果框」(默认开)与「清空结果框状态」。
53
+ - **逐轮优化台账 + `recap`**(②):每轮往 `$DSH_HOME/composer-ux/optimize-log.jsonl` 追加**只含元数据**的一条
54
+ (档位、草稿字数、上下文规模、条目数、丢弃条数与原因、是否重试/整段照收、工具轮次、耗时、路由、成败)。
55
+ **一个字的原文都不写** —— 模块类型上就没有承载它的字段。超 512 KB 从最旧的行开始丢并留 `rotated` 标记;
56
+ 坏行逐行跳过并计数;写失败**绝不抛错**(旁路失败不能拖垮一次已经跑完的调用)。
57
+ 新增 `scripts/recap.mjs`:`--last N` / `--session <前缀>` / `--json` / `--clear`。
58
+ - **只读查证工具 `read` / `glob` / `grep`**(⑤,**默认关**):放行条件是开关开 ∧ 拿得到本会话工作目录
59
+ (拿不到就一个工具都不派);围栏是 `realpath` 之后比前缀(符号链接也逃不出去),越界拒绝并记账;
60
+ 只有读调用;单文件 200KB / 结果 4000 字 / 遍历 400 文件 / glob 50 命中 / grep 40 命中 / 深度 6 /
61
+ 每轮 ≤3 次 / 总 ≤3 轮 / 总 20 秒。**工具内容不能当引文**(`quote` 仍须出自用户原话)。
62
+ - **发版门禁三脚本**(③):`check-tag-target.mjs`(tag 名里的版本 == 该 tag 所指提交里的 `package.json` 版本)、
63
+ `check-pack.mjs`(`npm pack` 文件清单 == `files` 白名单且不夹带 `src/ test/ scripts/ docs/`)、
64
+ `check-docs.mjs`(README 的套件数/条数/变异数 == **最近一次真实运行**的记录;引用的截图本地都在)。
65
+ `npm test` 与 `npm run test:mutations` 改走包装器,把"多少套件/多少条/多少变异"记成可机读事实。
66
+ - **证据登记表**(④):README 中英两版新增「已验证 / 未验证」两列。
67
+ - **设置页两个新开关**:「记录每轮优化台账」(默认开,只写数字)、「优化时允许只读查证项目文件」(默认关)。
68
+
69
+ ### 修正
70
+
71
+ - **台账的原因模板里嵌着模型给的引文**(`引文不是原话里的逐字片段:「…」`,最多 40 字)—— 而引文很可能
72
+ 就是用户原话的变体,直接落盘等于把用户的文字写进磁盘。现在写台账前**消毒**:刮掉所有成对引号/括号里的内容,
73
+ 只留机器前缀(失败原因同样过一遍)。台账仍能回答"为什么丢",但一个字的用户内容都带不出去。**变异 DH 盯着**。
74
+ - **工具路径的接受判据原本只看装配层的 `ok`**,而装配层对非 JSON 有"整段照收"兜底(`fallback: true`、`itemCount: 0`)
75
+ ⇒ 模型查完文件写的散文会被当成成品,**"查证后回落"永远不会发生**(上游真机翻过同一条路:打开只读工具就必定出散文)。
76
+ 改成"**真的解析出条目**"之后回落才真的走通:换掉系统提示、如实标注 `toolFallback`、保留真实轮次。**变异 DY 盯着**。
77
+ - **macOS 上根目录不归一**(`os.tmpdir()` 给的 `/var/folders/...` 真实路径是 `/private/var/folders/...`):
78
+ 围栏内部 realpath 出来的绝对路径与调用方给的根前缀不同 ⇒ 相对路径被切坏(实测切成 `【e.txt】`)、
79
+ `startsWith` 比对假失败。**CI 的 macOS 那一格红出来的**(ubuntu/windows 全绿),修法是工具入口先把根归一;
80
+ 并补了一条"根目录本身是符号链接"的常驻护栏 + 变异 DZ。
81
+ - **Windows 上路径分隔符不归一**:`path` 给的是反斜杠,而模型写的模式与文档都用 `/` ⇒ `**/*.ts` 永远匹配不到、
82
+ `grep` 的"路径:行号"断言也红。现在对外(模式匹配 + 工具结果)一律归一成 `/`。
83
+
84
+ ### 内部
85
+
86
+ - 测试从 **18 套件 1943 条**涨到 **21 套件 2160 条**;变异从 **109 条**涨到 **128 条,全部咬住**。
87
+ - 新套件:`test/optimize-ledger.mjs`(50)、`test/optimize-state.mjs`(69)、`test/optimize-tools.mjs`(66)。
88
+ - 宿主协议套件新增 5e(台账 17 条)与 5g(只读工具 13 条)两节;"新增 exact 路由必须登记"的关卡同步 6 → 7。
89
+ - 两处如实说明:**原子写的原子性本身没有变异证明**(单线程不可观测);**只读工具的价值(省事/省钱)没有数据**。
90
+ - 计划与设计写死在 `docs/PLAN-0.13.0.md`(含隐私边界与验收判据),便于后续核对。
91
+
5
92
  ## [0.12.0] — 2026-09-29(面板内逐条流式结果框 + 会话上下文/记忆链 + 提示词优化只留一个入口;并入原 0.11.1 的安全项、typecheck 与三平台 CI)
6
93
 
7
94
  > 起因:用户拿社区插件 [Y1X1n/dsh-prompt-optimizer](https://github.com/Y1X1n/dsh-prompt-optimizer)(0.3.17)
package/README.en.md CHANGED
@@ -30,6 +30,35 @@ Install with `dsh plugin --profile <name> add dsh-composer-ux`, then **restart D
30
30
  enough for host-side changes). The built `lib/` ships in the package **and** in this repository, so there is
31
31
  **no build step and no build authorization** at install time.
32
32
 
33
+ ## Screenshots (0.12.0)
34
+
35
+ Real screenshots from a live install (dark theme; the published build):
36
+
37
+ **The tool row carries exactly one plugin button** — 「⚡ 快捷指令」 (Quick commands); the prompt optimizer is
38
+ reached from inside that panel. The standalone 「✨ 优化」 button that 0.11.1 briefly added was removed on request.
39
+
40
+ ![Tool row: a single Quick-commands button](https://raw.githubusercontent.com/fangwen9527/dsh-composer-ux/v0.12.0/docs/optimize-toolrow-0.12.0.png)
41
+
42
+ **Item-by-item streaming**: the result box only ever shows items that have already been closed *and* passed the
43
+ verbatim-quote check (unverified content never appears). The header shows the stopwatch and Cancel, and the
44
+ buttons stay dimmed while it runs.
45
+
46
+ ![Streaming: items arriving one by one](https://raw.githubusercontent.com/fangwen9527/dsh-composer-ux/v0.12.0/docs/optimize-streaming-0.12.0.png)
47
+
48
+ **Done**: every item carries the exact phrase from your own words that it was based on; the finished prompt is
49
+ editable in the box, and 「插入输入框」 (Insert into input) is what writes it back.
50
+
51
+ ![Done: items + verbatim evidence + editable result + Insert button](https://raw.githubusercontent.com/fangwen9527/dsh-composer-ux/v0.12.0/docs/optimize-done-0.12.0.png)
52
+
53
+ **After insert**: the composer holds the optimized prompt (`Ctrl+Z` restores your draft).
54
+
55
+ ![The composer after inserting the result](https://raw.githubusercontent.com/fangwen9527/dsh-composer-ux/v0.12.0/docs/optimize-inserted-0.12.0.png)
56
+
57
+ **Settings**: the new "carry session context" switch (on by default) — it is used for disambiguation only and
58
+ can never be a source of quotes.
59
+
60
+ ![Settings: optimizer tiers + session-context switch](https://raw.githubusercontent.com/fangwen9527/dsh-composer-ux/v0.12.0/docs/settings-session-context-0.12.0.png)
61
+
33
62
  ## Install
34
63
 
35
64
  ```sh
@@ -140,6 +169,43 @@ edited in settings; the JSON output contract is always appended and cannot be ov
140
169
  (`connection.requestRejection` — Host/Origin fence and browser token) **before** the request body is read.
141
170
  - Settings are stored in DSH's own settings document; quick prompts live in `$DSH_HOME/quick-prompts.json`.
142
171
 
172
+ ## Evidence and what is *not* verified
173
+
174
+ > Why this section exists: the upstream project [WestFox-AwA/dsh-prompt-optimizer](https://github.com/WestFox-AwA/dsh-prompt-optimizer)
175
+ > publishes a **verified / not-verified** table and honestly lists *effect* as not verified (their holdout
176
+ > evaluation finished S1 and found "no measurable difference"; even their headline criterion was later
177
+ > declared non-discriminating). That is more honest than dressing design up as capability, so this table
178
+ > follows one rule: **only evidence you can reproduce inside this repository goes in the left table.**
179
+
180
+ ### Verified (reproducible evidence)
181
+
182
+ | Capability | Evidence |
183
+ |---|---|
184
+ | Result-box persistence: atomic write / corrupt quarantine / version envelope / sanitising | `node test/optimize-state.mjs` (69 checks, incl. a real `GET/POST` round-trip and "too big ⇒ refuse"); mutations DL/DM/DN/DO/DP/DQ |
185
+ | Ledger records metadata only — never your text | `node test/optimize-ledger.mjs` (50 checks) plus the 5e section of `test/quick-commands.mjs`: after a real run, **a unique word from the draft cannot be found in the on-disk ledger** |
186
+ | Ledger reason sanitising (a model-supplied quote can no longer reach disk) | same; mutation DH |
187
+ | Ledger rotation / per-line tolerance / clearing | `node scripts/recap.mjs` in a real process: `--last` / `--session` / `--json` / `--clear` and bad-line counting |
188
+ | Read-only tool fence (including symlinks) / every cap / out-of-bounds rejection | `node test/optimize-tools.mjs` (66 checks against a **real temporary directory**) |
189
+ | Tool-loop message shape / round cap / exceptions must degrade | same file, section 6 (a fake `llm.stream` drives the whole loop; `role:'tool'` and `toolCallId` are asserted) |
190
+ | Tool path: off by default, fires only when enabled, **prose after lookups falls back**, no cwd ⇒ no tool at all | `test/quick-commands.mjs` 5g (13 checks) |
191
+ | Mutation guards really bite, and **the *named* test must be the one going red** | `npm run test:mutations`: **132/132 bite** |
192
+ | Panel split + pinned result-box buttons (0.13.1) | `node test/panel-split-render.mjs` (20 checks, real `react-dom/server` render) |
193
+ | Release gates | `npm run gates` (tag target / packed file list / docs drift) |
194
+ | Three-platform CI | GitHub Actions: ubuntu / windows / macos × Node 20 |
195
+
196
+ > 0.13.1 split the panel into「优化提示词 / 快捷指令」halves with one toggle between them, and pinned the result
197
+
198
+ ### Not verified (design intent, or verified on one machine only)
199
+
200
+ | Gap | Note |
201
+ |---|---|
202
+ | **Effect (the important one)** | We have **no controlled evaluation at all** — no holdout set, no A/B, no cost baseline. "The model does better after this" has zero evidence in this repository. |
203
+ | **0.13.0 on a real machine** | Persistence, the ledger and the read-only tools were exercised **only by tests and a fake LLM**. Real-machine acceptance waits for a restart (the user restarts DSH). |
204
+ | Benefit and cost of the read-only tools | No data on how many round trips they save, or how many tokens/seconds they add — exactly why they default to off. |
205
+ | Whether a real model respects "tool content is not a quote" | Structurally enforced (`quote` must be a verbatim substring of your own words); behaviourally untested. |
206
+ | Atomicity itself | Not observable in a single-threaded test; only a "no leftover temp file" hygiene assertion exists, with no mutation proof. |
207
+ | Fence portability | Verified on Windows locally; other platforms only run the same tests in CI. |
208
+
143
209
  ## Development
144
210
 
145
211
  ```sh
package/README.md CHANGED
@@ -143,6 +143,13 @@ dsh plugin --profile web add <你克隆或解压出来的目录>
143
143
  - **0.12.0 起有记忆链**:你把成品插进输入框、改了几处、又点一次优化时,宿主会把**上一版成品**
144
144
  一起给模型(截断 1500 字),它只需围绕变化点调整、不必从零重写(你已确认过的地方不会被改回去)。
145
145
  不带的三种情况:同一段原文重跑(同文重试)、草稿就是上一轮成品(对成品重跑)、上一轮失败/取消;“重新优化”只在你手改过框里成品时才带。
146
+ - **面板分两半(0.13.1)**:中部现在是「**优化提示词**」(结果框)与「**快捷指令**」(分类 + 条目列表)**二选一**,
147
+ 中间一个切换按钮(▸ / ▾)换着看;打开面板**默认展开快捷指令**,标题行的**三档档位与 ✨ 按钮始终露在外面**
148
+ —— 所以「跑一次优化」永远是一步,不必先展开。**点 ✨ 会自己切到优化那半**(否则结果被藏在收起的一半里,
149
+ 看起来像没反应);手动切回快捷指令不会被抢回去;点结果框的「✕」会顺手切回快捷指令。
150
+ - **修掉「展开后看不到按钮」(0.13.1)**:以前结果框和快捷指令列表**挤同一块固定高度**,结果框一长
151
+ (条目多 + 成品多行)就把列表挤没、再把自己底部的**「插入输入框 / 重新优化 / 复制」顶出面板边界被裁掉**。
152
+ 现在两半互斥、各自内部滚,而且结果框拆成「可滚内容区 + **钉在底部的按钮行**」—— 面板再矮也不裁那排按钮。
146
153
  - **优化只从「快捷指令」面板进**(0.12.0 起):工具行只有一枚「快捷指令」按钮,点开面板再点「✨ 优化提示词」。
147
154
  0.11.1 曾试过在工具行加一枚独立的「✨ 优化」按钮(点一下 = 开面板 + 立刻开跑),用户看到实机后明确否掉 ——
148
155
  「我让你集合到快捷指令里的提示词优化按钮里」,于是 0.12.0 删掉它,回到单一入口。
@@ -206,6 +213,70 @@ dsh plugin --profile web add <你克隆或解压出来的目录>
206
213
 
207
214
  优化用的模型**跟随你当前的默认模型**(`agentDefaultModel.currentSelection()`),不额外配置;每次优化会花一次模型调用,但**不占对话轮次、不进会话历史**。
208
215
 
216
+ ### 逐轮台账(只记元数据,不记原文)
217
+
218
+ 优化跑完只留一句话("3 条补全 · 丢弃 1 条"),过一会儿就没了;「这轮为什么长这样」也没处查。0.13.0 起每轮往 `$DSH_HOME/composer-ux/optimize-log.jsonl` 追加**一条元数据**:
219
+
220
+ > 档位 · 草稿字数 · 上下文轮数与字数 · 条目数 · 丢弃条数与**原因** · 是否重试/整段照收 · 耗时 · 路由 · 成败
221
+
222
+ **一条用户原文都不写**(草稿、成品、逐字引文都不进这个文件)—— 这不是靠自觉,而是模块类型上就没有承载它的字段。还有一处必须说明的坑:装配层那几条丢弃原因的模板里**嵌着模型给的引文**(`引文不是原话里的逐字片段:「…」`,最多 40 字),而引文很可能就是你原话的变体,所以写台账前会**消毒**:刮掉所有成对引号/括号里的内容,只留机器写的固定前缀(`引文不是原话里的逐字片段:`)。
223
+
224
+ ```bash
225
+ node scripts/recap.mjs # 最近 20 轮(表格)
226
+ node scripts/recap.mjs --last 5 # 最近 5 轮
227
+ node scripts/recap.mjs --session 6f2a # 只看某会话(sessionId 前缀)
228
+ node scripts/recap.mjs --json # 机器读
229
+ node scripts/recap.mjs --clear # 清空
230
+ ```
231
+
232
+ 台账文件超 512 KB 会从**最旧**的行开始丢(并留一条 `rotated` 标记说明丢了多少行);坏行逐行跳过并计数,不会因为一行坏掉就把整份台账判成"读不出来"。写台账失败**绝不影响优化本身**(旁路失败只少一条记录,不能拖垮一次已经跑完的调用)。开关在**设置 → 插件 → 输入快捷指令 → 记录每轮优化台账**(默认开:只写数字与原因,所以默认开不冒隐私风险)。
233
+
234
+ ### 重启后结果框还在
235
+
236
+ 结果框原先只在内存里:关面板不清空,但**重启一次 DSH 就丢了刚跑出来的成品**,想看只能再花一次模型调用。0.13.0 起它会被存下来 —— 写进 `$DSH_HOME/composer-ux/optimize-dock.json`,下次启动或刷新页面时恢复。
237
+
238
+ 三条硬纪律(都是上游踩出来的教训,照抄做法):
239
+
240
+ | 纪律 | 为什么 |
241
+ |---|---|
242
+ | **原子写**(临时文件 → `rename`) | 半份 JSON 会被读成"状态损坏" |
243
+ | **损坏隔离**(改名成 `<名>.corrupt-<时间戳>.json`) | 那份文件里可能是**你唯一一份成品**,绝不能静默覆盖或删掉 |
244
+ | **超 64 KB 拒写**(不是截半) | 宁可这次没存上,也不要存半份 |
245
+
246
+ 信封里带版本号:**版本不认识就不解析、也不动文件**(可能是你从新版降级回来,那份状态还在)。读回来的状态一律先过净化:认不出(被手工改过/旧版本写的/写坏)就当"没有可恢复的结果"——宁可框是空的,也不拿半份脏数据糊界面。**重启前没跑完的那一轮恢复成"已取消"**,不假装还在跑(秒表也不会走)。
247
+
248
+ > ⚠️ **隐私边界(用户 2026-09-29 明确选 A)**:这份文件里**有内容** —— 成品正文、条目、逐字引文、发起时的草稿。因为"重启后结果框还在"本质上就是把这些存下来;它与 `quick-prompts.json` 同性质。**只记元数据的那一份是上面的逐轮台账**,两者刻意分成两个文件。
249
+
250
+ 开关:**设置 → 插件 → 输入快捷指令 → 重启后保留结果框**(默认开),旁边有「清空结果框状态」
251
+ (磁盘那份 + 当前框里这份一起清)。关掉开关时宿主**既不读也不写**,并把已存的那份删掉。
252
+
253
+ ### 只读查证项目文件(默认关)
254
+
255
+ 草稿里常有"改这个文件里的东西""按现在的写法来" —— 不查就只能猜。打开这个开关后,解释层可以调三个
256
+ **只读**工具:`read`(读一个文件)、`glob`(按通配符列文件)、`grep`(按正则找内容)。
257
+
258
+ **默认关**,因为它真的会多花时间与 token;开了才走工具轮次(0.5 默认开、0.6 改成默认关,理由一样)。
259
+
260
+ | 边界 | 值 |
261
+ |---|---|
262
+ | 放行条件 | 开关开 ∧ 拿得到**本会话工作目录**(拿不到就一个工具都不派 —— 没有围栏的根就不猜路径) |
263
+ | 围栏 | 一切路径必须落在工作目录内;**`realpath` 之后再比前缀**,所以符号链接也逃不出去;越界**拒绝并记账** |
264
+ | 只读 | 只有 `readdir` / `stat` / `readFile`,一个写调用都没有,也不执行任何东西 |
265
+ | 上限 | 单文件 200KB · 单次结果 4000 字 · 遍历 400 文件 · glob 50 命中 · grep 40 命中 · 深度 6 |
266
+ | 轮次 | 每轮 ≤3 次调用、总 ≤3 轮、总时限 20 秒;触顶**如实标注**(`toolCapped`),不假装查全了 |
267
+ | 跳过 | 点文件/点目录(配置与缓存)与 `node_modules` / `dist` / `build` / `coverage` 这类目录 |
268
+
269
+ 两条口径值得单独说:
270
+
271
+ - **查到的内容不能当引文**。`rewrite` / `requirement` / `quality` 的 `quote` 仍必须是你原话里的逐字片段 ——
272
+ 从文件读来的句子写进 quote 会被装配层逐条丢弃。工具内容只用于**理解与消歧义**。
273
+ - **模型查完文件写散文时,这一轮会回落**。真机上模型很爱在查证后写"我看过 xxx,所以建议……",
274
+ 那不是我们的条目 JSON。我们只在**真的解析出条目**时才认工具路径;否则换成**不带工具说明**的系统提示
275
+ 再跑一次单轮优化。回落时台账与结果里保留**真实发生过**的轮次/次数(那几次调用是真花了钱的),
276
+ 并标注 `toolFallback`,不假装没派过工具。
277
+
278
+ 开关:**设置 → 插件 → 输入快捷指令 → 优化时允许只读查证项目文件**(默认关)。
279
+
209
280
  ### 「发送时附加」为什么不自造提交
210
281
 
211
282
  Enter 那一路沿用既有的合成 Enter 回放;**官方发送按钮**那一路在捕获阶段认下点击、先把附加内容写回编辑器、再用同一个按钮重放一次点击。这样官方对「发送 / 排队 / 打断」的判定原样生效,本插件不做第二套提交语义。发送键与停止键共用同一个位置,靠图形区分——停止渲染 `<rect>`(方块),发送渲染 `<path>`(箭头),与界面文案、语言无关。
@@ -374,6 +445,45 @@ Windows 上 DSH 给模型的终端工具是 **PowerShell**(工具名 `pwsh`)
374
445
  - **第一次点的时候,正在跑的宿主还是上一版**(新路由要重启后才加载):界面会如实说明"这次走旧机制,重启之后按钮就是新版了"。
375
446
  - 为什么 DSH 需要插件自己干这件事:官方没有重启宿主的机制(插件市场那边只提示「更改将在下次启动生效」),所以这件事只能由插件做。
376
447
 
448
+ ## 证据与未验证(哪条有实测、哪条只是设计)
449
+
450
+ > 立这一节的起因:上游 [WestFox-AwA/dsh-prompt-optimizer](https://github.com/WestFox-AwA/dsh-prompt-optimizer)
451
+ > 的 README 里有一张**已验证 / 未验证**表,而且把"效果"这条最重要的缺口如实列为**未验证**
452
+ > (他们的留出评估跑完了 S1,结论是"没测出差别",连曾作为主要卖点的"该不该问"判据都被自己判为无效)。
453
+ > 这比"把设计写成能力"诚实得多,照抄。下面**只有本仓里能查到的证据才写进左表**。
454
+
455
+ ### 已验证(有可复现的证据)
456
+
457
+ | 能力 | 证据(都能在仓里重跑) |
458
+ |---|---|
459
+ | 结果框持久化:原子写 / 损坏隔离 / 版本信封 / 净化边界 | `node test/optimize-state.mjs`(69 条,含真路由 `GET/POST` 往返与"太大拒写");变异 DL/DM/DN/DO/DP/DQ |
460
+ | 重启后恢复的接线(只在框空着时恢复、去抖存盘、开关关掉不发请求) | 同上第 6 节(源码级断言 —— 组件行为在浏览器里,本仓测不到) |
461
+ | **面板分两半 + 结果框按钮钉底**(0.13.1) | `node test/panel-split-render.mjs`(20 条,`react-dom/server` **真渲染**:默认展开哪半、两半互斥、三个按钮在滚动区之外)+ `client-registration.mjs` 2.0g 节(11 条源码级) |
462
+ | 台账只记元数据、不落原文 | `node test/optimize-ledger.mjs`(50 条)+ `test/quick-commands.mjs` 5e 节:真路由跑一轮后**在磁盘上的台账文件里搜不到草稿里那个独特的词** |
463
+ | 台账的原因消毒(模型给的引文不再被写进磁盘) | 同上;变异 DH(去掉消毒立刻红) |
464
+ | 台账轮转 / 逐行容错 / 清空 | `node scripts/recap.mjs` 真进程跑:`--last` / `--session` / `--json` / `--clear` 与坏行计数都在 `test/optimize-ledger.mjs` 第 6 节 |
465
+ | 只读工具围栏(含符号链接)/ 各类上限 / 越界拒绝 | `node test/optimize-tools.mjs`(66 条,**真实临时目录**;含"根目录本身是符号链接"那类 macOS 坑) |
466
+ | 工具循环的消息形状 / 轮次封顶 / 异常必须降级 | 同上第 6 节(假 llm 驱动整条循环,逐条检查 `role:'tool'` 与 `toolCallId`) |
467
+ | 工具路径:默认关、开了才派、**查完吐散文必须回落**、拿不到工作目录就不派 | `test/quick-commands.mjs` 5g 节(13 条,真路由 + 假 llm) |
468
+ | 工具读到的内容不进台账 | 同 5g 节:断言台账里只有工具名与次数、没有读到的文件内容 |
469
+ | 变异守卫真的会咬人,且**必须是指定的那条测试变红** | `npm run test:mutations`:**132 条全部咬住**(判据是 `red.some(line => line.includes(item.expect))`,不是"有红就行") |
470
+ | 发布门禁三条判据 | `npm run gates` 全过;`check:tag` 实测 `v0.12.0` 的 tag 名与所指提交里的版本一致 |
471
+ | 三平台 CI | GitHub Actions:ubuntu / windows / macos × Node 20(`npm ci → typecheck → build → test → check:pack → check:docs`) |
472
+
473
+ ### 未验证(只是设计成那样,或只在本机验过)
474
+
475
+ | 缺口 | 说明 |
476
+ |---|---|
477
+ | **效果(最重要的一条)** | 我们**没有任何受控评估** —— 没有留出集、没有 A/B、没有成本基线。"这样优化之后模型做得更好"在本仓里没有一条证据,只是设计意图。上游花钱做过一次,结论是"没测出差别",所以这一格必须留空。 |
478
+ | **0.13.0 的真机(实机)行为** | ①重启恢复、②台账落盘、⑤只读工具这三块**只在测试与假 llm 里跑过**,没有在真实 DSH 里点过。真机验收要等重启后做(重启由用户执行);0.12.0 那 5 张截图是上一版的实机证据,**不覆盖新功能**。 |
479
+ | 只读工具的收益与成本 | 真实项目里派工具能省多少来回、多花多少 token 与时间,**没有数据**。默认关闭正是因为这份不确定。 |
480
+ | "工具内容不会被当成引文"在真实模型上的表现 | 结构上成立(`quote` 必须逐字命中用户原话,否则逐条丢弃,有测试)。但模型会不会因此产出更差的结果,**没测**。 |
481
+ | **原子写的原子性本身** | 单线程测试观测不到"写到一半";只有"不留临时文件"这条卫生断言,**没有变异证明**(已在提交信息里写明,不假装验过)。 |
482
+ | 围栏的跨平台性 | 围栏与路径归一在 Windows 本机验过;Linux/macOS 只有 CI 跑同一批测试(符号链接那条在 POSIX 上是普通 symlink)。**没有在 macOS/Linux 上真机装过。** |
483
+ | 会话上下文「只用于消歧义」 | 0.12.0 起就声明过:这是**结构约束**(上下文内容不能当引文),不是对模型行为的保证。 |
484
+ | `sync-types` 的边界 | 它是**形状核对**,不是类型代码生成:宿主 API 真变了但形状没变的那种情况查不出来。 |
485
+ | 界面语言 | 只有中文界面(上游做了 zh/en 跟随 DSH)。英文用户能读 `README.en.md`,但界面本身不跟语言。 |
486
+
377
487
  ## 结构
378
488
 
379
489
  仓库根目录(克隆或解压出来即是):
@@ -405,6 +515,10 @@ dsh-composer-ux/
405
515
  │ ├── prompt-context.ts # 优化:会话上下文(挑往来 / 收敛预算 / 渲染成块,纯函数;0.12.0)
406
516
  │ ├── optimizer-assemble.ts # 优化:逐字校验 + 装配 + 流式扫描(批处理与流式共用同一份校验)
407
517
  │ ├── optimizer-prompt.ts # 优化:三档系统提示词 + 固定 JSON 契约(自定义提示词不能替换契约)
518
+ │ ├── optimize-ledger.ts # 优化:逐轮台账(只记元数据 + 原因消毒 + 轮转/容错,0.13.0 新增)
519
+ │ ├── optimize-state.ts # 优化:结果框状态落盘(原子写 + 损坏隔离 + 体积上限 + 版本信封,0.13.0 新增)
520
+ │ ├── optimize-tools.ts # 优化:只读查证工具(read/glob/grep:围栏 + 各上限 + 跳过规则,0.13.0 新增)
521
+ │ ├── optimize-tool-loop.ts # 优化:工具循环(工具结果消息形状 + 轮次封顶 + 异常必降级,0.13.0 新增)
408
522
  │ ├── holiday-sync.ts # 金额:法定节假日自动获取(holiday-cn 今年+明年 → 日期表;补班日不收;到期判定/镜像兜底/失败不改动)
409
523
  │ ├── provider-prices.ts # 金额:内置第三方价目**快照**(生成物,11 provider / 346 模型;由 scripts/gen-provider-prices.mjs 生成)
410
524
  │ ├── balance.ts # 金额:官方余额响应消毒 + 查询端点白名单(零 import,两半共用)
@@ -427,6 +541,8 @@ dsh-composer-ux/
427
541
  │ ├── optimize-clock.ts # 优化:面板与结果框共用的秒表读数(0.11.1 新增)
428
542
  │ ├── OptimizeDock.tsx # 优化:面板内结果框(逐条流水 + 可编辑成品 + 插入,0.12.0 新增)
429
543
  │ ├── optimize-dock.ts # 优化:结果框的纯函数状态机与文案(0.12.0 新增,可单测)
544
+ │ ├── optimize-ledger.ts # 优化:逐轮台账(只记元数据 + 原因消毒 + 轮转,0.13.0 新增)
545
+ │ ├── optimize-state.ts # 优化:结果框状态的客户端读写层(GET/POST + 读回来先净化,0.13.0 新增)
430
546
  │ ├── quick-commands.ts # 快捷指令/优化:输入框桥接、插入与写回、斜杠命令与秒表纯函数
431
547
  │ ├── stats-line.ts # 统计行:小数语义(不撒谎)+ 两个字符串变换(纯逻辑)
432
548
  │ ├── stats-dom.ts # 统计行:定位与改写(DOM 助手,不 import React)
@@ -472,13 +588,22 @@ CI(GitHub Actions,`.github/workflows/ci.yml`)是 **ubuntu / windows / maco
472
588
 
473
589
  ```sh
474
590
  npm ci # 按 lockfile 安装(CI 用这条)
475
- npm run typecheck # tsc --noEmit(类型面在 types/dsh-externals.d.ts)
591
+ npm run typecheck # tsc --noEmit(类型面在 types/dsh-externals.d.ts,手写)
592
+ npm run sync-types # 对着 DSH 源码核对本插件依赖的**宿主形状**(只读,需 DSH 检出)
593
+ # 名字沿用社区插件工程面的叫法;我们**不做类型代码生成**:
594
+ # 手写类型面 + 形状核对,比生成一份"跟着版本线漂"的类型更诚实
476
595
  node build.mjs # 产出 lib/index.js + lib/client.js
477
596
  # ⚠️ 宿主半会**内联** schemastery / cosmokit:
478
597
  # 优先用 DSH 检出里的 vendor 副本,检出不在时退到 node_modules
479
598
  # 里同版本的 npm 包(两者逐字节相同)——CI 上走的就是退路
480
- npm test # 18 个套件;当前 1943 passed, 0 failed(2026-09-29 实测;CI 三平台同样全绿)
481
- node test/mutation-guards.mjs # 手动跑:变异测试,证明那套护栏真的在咬人(108 条,须单独跑)
599
+ npm test # 22 个套件;当前 2190 passed, 0 failed(2026-09-29 实测;CI 三平台同样全绿)
600
+ # 走 scripts/run-tests.mjs:顺带把"多少套件/多少条"记进 test/.last-run.json
601
+ npm run test:mutations # 手动跑:变异测试,证明那套护栏真的在咬人(132 条,须单独跑)
602
+ # 同样记录结果,供下面的文档门禁核对
603
+ npm run gates # 发版门禁三条一起跑:tag 指向 / 包内容 / 文档数字
604
+ npm run check:tag # ① tag 名里的版本 == 该 tag 所指提交里的 package.json 版本
605
+ npm run check:pack # ② npm pack 的文件清单 == files 白名单(不许夹带 src/test/scripts/docs)
606
+ npm run check:docs # ③ README 的套件数/条数 == 最近一次真实运行;引用的截图本地都在
482
607
  node test/settings-render.mjs # 已进 npm test:把设置页真渲染成 HTML,断言版式与互斥显示、以及"非默认设置"下的「金额」卡(78 条)
483
608
  node test/cost-panel-render.mjs # 已进 npm test:把「金额浮层」真渲染成 HTML,断言分列 / 峰谷行 / 未定价 / 内置快照价 / 明文没混进 markdown 记号(19 条)
484
609
  node scripts/live-smoke.mjs # 手动跑:**联网复核**(官方页 vs 写死的价目表逐格对比 / models.dev vs 内置快照逐条对比 / 余额端点白名单与状态码);只读,不写任何文件
package/README.simple.md CHANGED
@@ -34,6 +34,26 @@ dsh plugin --profile <你的profile名> add dsh-composer-ux
34
34
 
35
35
  > 装完没看到新东西?多半是没重启,或者这一栏的开关没开。见下面「常见问题」。
36
36
 
37
+ ## 先看看长什么样
38
+
39
+ **① 输入框下面那行**:左边是你的输入,右下角多了两样东西 —— 一枚「⚡ 快捷指令」按钮,和一行小字(缓存命中 / 这次花了多少)。
40
+ (这一行本来就有的那个 `+`、权限、技能、模型、发送照旧。)
41
+
42
+ ![工具行:只剩一枚「快捷指令」按钮](https://raw.githubusercontent.com/fangwen9527/dsh-composer-ux/v0.12.0/docs/optimize-toolrow-0.12.0.png)
43
+
44
+ **② 点「⚡ 快捷指令」→ 点「✨ 优化提示词」**:它会一条一条地给你看改了什么。
45
+ **注意每条下面那行小字**(写了"依据:…")—— 那就是模型引用的你自己的原话;引不出原话的建议它不会给你。
46
+
47
+ ![优化结果框:逐条出现 + 每条标出依据](https://raw.githubusercontent.com/fangwen9527/dsh-composer-ux/v0.12.0/docs/optimize-done-0.12.0.png)
48
+
49
+ **③ 觉得可以了,再点「插入输入框」**:成品才会替换你原来打的那句话(`Ctrl+Z` 能撤回)。
50
+
51
+ ![插入之后,输入框里就是成品](https://raw.githubusercontent.com/fangwen9527/dsh-composer-ux/v0.12.0/docs/optimize-inserted-0.12.0.png)
52
+
53
+ **④ 设置页**:想关掉"带上最近几轮对话"就到这里关(默认开着)。
54
+
55
+ ![设置页:优化强度 + 携带会话上下文](https://raw.githubusercontent.com/fangwen9527/dsh-composer-ux/v0.12.0/docs/settings-session-context-0.12.0.png)
56
+
37
57
  ## 每个功能一句话
38
58
 
39
59
  | 功能 | 说人话 | 什么时候用得上 |