# Changelog [3.3.3]: https://github.com/Fishsb/dsh-prompt-enhancer/compare/v3.3.2...v3.3.3 [Unreleased]: https://github.com/Fishsb/dsh-prompt-enhancer/compare/v3.4.0...HEAD [3.4.0]: https://github.com/Fishsb/dsh-prompt-enhancer/compare/v3.3.3...v3.4.0 [3.3.2]: https://github.com/Fishsb/dsh-prompt-enhancer/compare/v3.3.1...v3.3.2 [3.3.1]: https://github.com/Fishsb/dsh-prompt-enhancer/compare/v3.3.0...v3.3.1 本项目所有重要变更记录于此文件。 格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循[语义化版本](https://semver.org/lang/zh-CN/)。完整发布说明见 [GitHub Releases](https://github.com/Fishsb/dsh-prompt-enhancer/releases)。 > 实测标注纪律:每条变更须在文末标注「已实测:<方式/等级>」;未实测不得合入(2026-08-24 门禁化)。 > 🗺️ 条目内的 `flow:` 标注为功能链路标签(原 pmg 项目地图 `docs/map/` 已随 pmg 于 2026-09-10 移除,该路径不再存在);agent 开工前先读 [`AGENTS.md`](AGENTS.md)。 ## [Unreleased] ## [3.4.0] - 2026-09-19 > ⚠️ **BREAKING(对外契约变更,单列声明)**:本版**移除插件内重启能力**——RPC `update/portRestart`(端口/服务重启)与 `update/makeShortcut`(桌面快捷方式)连同其客户端重启 UI 一并下线,新增 `update/install`(安装已下载到 staging 的新版本并返回 `restartNeeded:true`)。核对依据:这两个 RPC 在 `v3.3.3` 的 tag 内**确实在册**(`git show v3.3.3:lib/index.cjs` 命中 2 条 `harness.handle`),故属本版引入的破坏性变更,非历史叙述。**升级影响**:一键更新后不再能由插件自己重启 DSH,改为提示**手动重启**;用过「重启按钮 / 桌面快捷方式」的用户会看到入口消失。决策依据 ADR-201。 - **CI 假绿修复:29 条用例静默消失(runner 少报而 CI 报 success)+ 两条孤儿件清理**:A18 真修后 CI 转绿(run 35399509447,10/10 步全过),但随即发现**绿得不完整**——CI 逐文件用例合计 **228** vs 本地 **257**,差额 **29 条全部集中在 `test/lib.test.cjs`**(本地 82 / CI 53),其余 15 个文件逐一对齐;且该文件在两次 CI 之间**自己漂**(52 → 53,多出 `U40c`),而两次 run 的该文件源码**逐字节相同**(`git show` 比对 85,750 B 相等)。**定性(临时探针实证,一次推送后即撤)**:在文件头尾插入 `[LIBREG]` 探针后,CI 日志显示 `load-start … topLevelTestCalls=82` 与 `load-end reached (EOF)` **两条都在**——即**文件读到尾、82 个顶层 `test(` 全部注册**,但 runner 只上报 52/53 条 ⇒ 不是加载被切,而是**子进程提前退出,未跑的用例随进程一起消失**(两次数值不同 ⇒ 竞态而非静态条件)。头号变量是 `--test-force-exit`(该旗标还被 runner 重复继承一次,实测 `execArgv=["--test-force-exit","--test-force-exit"]`)。**修法(三件缺一不可)**:① **去掉 `--test-force-exit`**——本地实测 16 个文件**全部自行退出且 exit 0**(合计 257),不再需要它兜底,保留它反而制造「静默少报」;② **硬顶 40→60s**——`bundle-smoke` 有 ~30s 退出尾巴(实测 30,398ms)需留头寸,真挂死仍会被硬顶杀掉;③ **新增「源码注册数 ≥ runner 上报数」不变量**——CI 每跑一个文件即比对源码里顶层 `test(` 的条数与日志里的 `# tests N`,少报即打印 `FAIL … 静默少报:源码注册 N / runner 上报 M` 并置 `rc=1`,把这一整类假绿变成红色失败。**同批孤儿件清理(用户拍板)**:① 删 `src/client/index.js`——9 行、正文只有 `module.exports = {}`、**不在 `build-client` 的 26 件装配清单**、全仓**零路径引用**(P1b 的死层清理范围只到 `src/host`,这是漏网的同型 M1 脚手架);② `test/a6-drill.cjs` 的 `System32\tar.exe` 硬编码改平台分支(**脚本保留**,它是 A4/A6 拦截演练的手工工具)。**已实测**:本地 16 个文件逐跑「注册 ≥ 上报」不变量 **16/16 通过**(`lib` 82/82、`bundle-smoke` 22/22 …);`npm test` **257/257/0**;`npm run gate` exit 0;双产物 `--check` 零漂移;删 `index.js` 后全仓精确路径引用仅剩 CHANGELOG 与治理档的记述文字(无代码引用)。**未实测**:本条推送后 CI 的新读数(预期 `lib.log` 由 53 回到 **82**、合计回到 **257**)——以该 run 为准。— [PE-F01](flow: 装配与加载链) - **A18 真修:13 条 Windows 耦合用例改为平台自适应(CI 可表态;零对外行为变更)**:accept 判据 A18 规定「排除项限 3 条具名,第 4 条即判该门自我失效」,而实测 CI 失败面是 **13 条 / 4 文件**(DGRD-16/17/19 · MAINT-02/03/04 · SYS-06/07/09/10/11 · UGATE-27/30)——按判据自身规则不通过。用户拍板取「真修」而非「按类申报」,故逐条消除平台耦合,**不改判据、不加排除项、不 skip 任何断言**。四类根因与改法: ① **DGRD-16/17/19|测试夹具硬编码 `System32\tar.exe`**:`makeRealTgz` 在 ubuntu 上 `SystemRoot` 为空 → 落到字面量 `C:\Windows` → ENOENT。改为平台分支,win32 保留绝对路径(**原因留档**:PATH 里的 Git GNU tar 会把 `-C C:\...` 解析成远程主机 `Cannot connect to C: resolve failed`),POSIX 用 PATH 的 `tar`;`lib/stage-install.cjs` 的同类用法**早已有 platform 分支**,无需改动。 ② **UGATE-27/30|产品缺陷,不是夹具问题**:`lib/updater-host.cjs` 的 `dshErrLogTail` 原以 `if (process.platform !== 'win32') return ''` 整条早退,把「Windows 专有的 nssm 注册表定位环」与「平台无关的降级环 `EXECUTOR_ROOT/port-restart.err.log`」一并废掉——Linux 上连降级日志都读不到。改法:平台判定**收窄到注册表那一环**,降级环在 POSIX 恢复可用;净行数 0,win32 执行序/参数/正则/返回值逐字节不变。 ③ **MAINT-02/03/04|POSIX pid 探测恒失效(一处双重必死)**:`dshPrimPortHolder` 的 POSIX 分支跑 `lsof -i : -P -n`,而 ① ubuntu 镜像无 lsof(ENOENT、stdout 空)② 即便装了,其输出 `… TCP 127.0.0.1: (LISTEN)` 把 LISTEN 放在**行尾括号内**,原正则却要求其后还有 `\S+ \d+ \d+` → 永不匹配。两条叠加 ⇒ 恒返回 0,三条用例全是它的下游(02 断言 `=== process.pid` 拿到 0;03 在拿 pid 前就早退 `{ok:true,killed:false}`;04 同理在**身份门之前**早退返回 ok:true——**不是身份门写了 Windows 逻辑**,那个比较是平台无关正则)。改法:POSIX 主路径 `ss -ltnp`(iproute2)+ 退路 `netstat -tlnp`(net-tools),新增两个自包含纯解析原语并登记 `PORT_PRIMS`(**6→8**,仓库纪律:新原语不入 `PORT_PRIMS` 会让生成脚本 ReferenceError);拿不到 pid 一律返回 0,**宁可失败不伪造成功**;win32 分支逐字节不变(`test/maintain.test.cjs` 零改动)。 ④ **SYS-06/07/09/10/11|一半是产品缺陷、一半是测试期错平台**:**(a) 产品缺陷(SYS-09/10/11)**——`lib/sys.cjs` 的 `CMD_ALLOW` 白名单**缺 `'which'`**,而 `probeEnv` 的非 win32 分支两处都在调 `runProbeAsync('which', ['systemctl'|'launchctl'])` → `validateProbe` 在**到达执行器之前**就判 `BAD_PROBE_CMD` → 整段 POSIX 探测成为**死码**,四项一律早退 `tool-unreachable`(这才是 Linux 上 tools 多出 `level` 的真因:**早退形状带 level,直通项不带**——期望集没改,改的是产品)。修法:白名单加入 `'which'`(win32 从不调用它,Windows 行为零变化;命令缺失时 ENOENT → 走既有降级,不抛不崩)。**(b) 测试期错了平台(SYS-06/07)**——`where` 是 Windows 专有解析器,Linux 上真探测的期望语义就是**不可达**且须映射为 `ENOENT`(string code,与退出码区分),原断言写的是 Windows 环境事实;win32 断言原样保留在平台分支内,POSIX 侧另用自建 PATH 工具走**真 spawnSync 通道**补正向断言(覆盖 `ok/code 0/stdout 回传/非 0 退出码为 number`),**覆盖面比原用例更大**。 **已实测**:**CI(ubuntu)转绿**——run `35399509447`(HEAD `3383767`)**10/10 步全过**(本仓近期首次 `success`,此前近 12 次 `success = 0`)。逐文件前后对比(同一测试树):`deploy-guard 9/6/3 → 9/9/0` · `maintain 23/20/3 → 23/23/0` · `sys 13/8/5 → 13/13/0` · `update-gate 24/22/2 → 24/24/0`——**13 条失败精确归零,其余 11 个文件读数一动不动**,`not ok` = **0**。本地(Windows):全量 `npm test` **257/257/0**、`npm run gate` exit 0、`node --check` 通过、双产物 `--check` 零漂移;`deploy-guard` 9/9 · `update-gate` 24/24 · `maintain` 23/23 · `sys` 13/13。**linux-stub 独立复核**(`-r` 覆盖 `process.platform`):`update-gate` 修前 **22/2 复现 CI 两条**(行号与文案逐字一致)→ 修后 **24/24**;`sys` 的 SYS-09/10/11 POSIX 分支真跑通 **3/3**。 **同批发现(不属本条目范围,但必须记名)**:CI 逐文件用例合计 **228** vs 本地 **257**,差额 **29 条全部集中在 `test/lib.test.cjs` 一个文件**(本地 82 / CI 53),其余 15 个文件**逐一相等**;该文件在两次 CI 之间还自己漂(**52 → 53**,多出 `U40c`),而两次 run 的该文件源码**逐字节相同**(`git show` 比对 85,750 B 相等)⇒ **CI 上该文件被动态截断注册,29 条用例静默未上报**(本地双重伪平台 `process.platform` + `os.platform/type/arch` 复现不出,仍 82 ⇒ 非平台条件注册)。故 **「CI 全绿」不等于「CI 覆盖完整」**,该文件的完整覆盖仍以本地 `npm test` 为准;根因待查。 — [PE-F01](flow: 装配与加载链) - **P4 收口:在册缺陷 D-1 修复(`update/pull` 在本仓发布物上不可能成功)+ 三条状态归属判据落机检(零对外契约变更)**:按用户拍板取方案 A 修 D-1,并把「这一类」固化为判据而非一次性补丁。① **D-1**:`UPDATE_MANIFEST` 两处副本(`src/host/pure.js` host 侧、`src/client/updater.js` client 侧)均含已退役的 `plugin-client.js`,而 `validateManifestFiles` 要求入参**恰好覆盖**清单全项 ⇒ `update/pull` 拉取必然因缺件被拒(客户端 contents API 拉取路径同源同一清单)。修复面逐处断言命中 1/1:两副本删该项(5 项→**4 项**)· 注释「全部 6 个文件」→「4 个」(第三处副本漂移)· `test/lib.test.cjs` U30 断言同步 · `U37` 的写死条数改为 **`UPDATE_MANIFEST.length`** 表达(不再有可漂移的魔数,条数一致性交门禁管)· `npm run build` 重建 `plugin-host.js`(205,340 B)与 `lib/client.cjs`(320,840 B)。② **三条新判据(`scripts/arch-claims.mjs`,均按用户拍板「加判据」)**:**S-5** 发布物清单一致(两副本相等 + 逐项 ⊆ `package.json` files 白名单 + 注释条数 == 数组长度)· **S-6** `RESTART_FILES` 自洽(逐项磁盘在位 + ⊆ 白名单 + 含 `plugin-host.js` 与 `lib/index.cjs` 两个装配链入口)· **S-7** i18n 全局平衡(`ZH`/`EN` 顶层键集相等,实测 **273/273**、0 差异)。③ **副产物发现(待拍板,本批不夹带)**:`src/client/index.js` 是**零内容孤儿**(9 行、`module.exports = {}`、不在 `build-client` 的 26 件装配清单、全仓零引用),与 P1b 退役的 M2 骨架同类,但 P1b 范围只到 `src/host`;本轮仅顺带订正其过时头注(不再声称构建产出 `plugin-client.js`),删除与否列决策项。**已实测**:`npm test` **257/257**(修 D-1 时 U37 一度红——写死条数 5 与实测 4 冲突,已改为相对表达);`npm run gate` **exit 0**(断言 33 通过 / 冲突 0 / 在册未达 3 / 在册缺陷 0);**三条新判据各自证伪**——client 副本塞白名单外条目 → S-5 ✗、`RESTART_FILES` 塞幽灵项 → S-6 ✗、EN 表键改名 → S-7 ✗,三处扰动均 exit 1 且还原后逐字节一致、复位 exit 0;双产物 `--check` 零漂移。**未实测(明确边界)**:D-1 修复的**实机**效果(`update/pull` 在真实发布树上的拉取)需发布后按 tag 拉取才可验;`update/pull` 已无仓内调用者,故本条按「修一个真缺陷」而非「修一条用户可见路径」记录。— [PE-F06](flow: 更新 / 装配与加载链) - **P4 状态归属 + 贯穿判据:ADR 承诺↔代码现状机检(新门禁)+ 结构门禁首次进入 CI(零对外行为变更)**:关 **RC-F**(同一状态多处持有/手工同步)+ 贯穿项「**无结构判据**」。① **新增机检门禁 `scripts/arch-claims.mjs`**:把**每条 PE 架构决策**的承诺翻成可执行断言,逐条输出「命令 / 期望 / 实测 / 判定」。**分母不自拟**——取自 `nav_graph mode=adrs` 全量 37 条中锚点匹配 `/pe-f\d|prompt-enhancer/` 的 **7 条**(ADR-146 · 194 · 197 · 201 · 224 · 230 · **234** 本批决策自身),并**自带交叉核对**(`S-2`:读治理事件日志比对锚点多重集,日志不可读时记 **SKIP 而非 PASS**)。判据分四档——**断言 / SKIP / 在册未达 / 在册缺陷**,其中「在册未达」(终局目标未达成,如 ADR-197 的 `lib/index.js ≤120 行`,实测 **839**)**必须写明在册依据**,「在册缺陷」必须写明处置指向 + 证据 + owner,缺项即冲突。实测读数:**断言 30 通过 · 冲突 0 · SKIP 0 · 在册未达 3 · 在册缺陷 1**(CI 环境下为 27 通过 / 3 SKIP,行为一致)。② **门禁存在 ≠ 门禁被执行**:四个结构门禁(死代码/装配 R1–R4 · RPC 契约 · 提示词生成区漂移 · 本批新增的 arch-claims)收敛为 **`npm run gate` 一条命令**,并**首次进入 CI**(原 CI 只跑 RPC 契约一项,`dead-code-gate` 与 `sync-prompts --check` 此前**无人自动执行**——`npm run gate` 只是别名,`test`/`build` 都不调)。③ **RC-F 状态归属清单(10 个状态面,实测持有点)**:仅 3 面为手工同步点(发布物文件集 / `RESTART_FILES` 名单 / i18n 全局键),其余 7 面已由门禁收敛。**机检发现 1 条在册缺陷(D-1,非 ADR 面)**:`plugin-host.js:2249` 的 `UPDATE_MANIFEST` 仍含已退役的 `plugin-client.js`,而 `update/pull` 的 `validateManifestFiles` 要求入参**恰好覆盖**清单全项 ⇒ 该 RPC 在本仓发布物上**不可能成功**;同一清单还有第二处副本漂移(L2372 注释称「全部 6 个文件」,数组实为 **5** 项)——**本批不夹带修改**(触及对外 RPC 行为面,且本仓已无该 RPC 调用者),列决策项待拍板。④ **证伪记录(门禁可失败、失败后自恢复)**:往 `src/host` 塞一件清单外文件 → `A194-1` 与 `A224-2`(R4 门禁)**双失败 exit 1**;把治理日志指到锚点集不符的临时日志 → `S-2` 失败 exit 1;篡改治理档投影 → **文档漂移** exit 1;三处证伪物清理后均复位 exit 0。**已实测**:`npm run gate` **exit 0**(四门禁串行);`npm test` **257/257**(16 文件);`build-host --check` / `build-client --check` **零漂移**;`node --check` 语法全过;治理侧 `nav_node`(PE-F01 落点登记新脚本)+ `nav_decide` **ADR-231** + `nav_render` 投影重生成。**CI 首跑实证(run 35397066749,本批自己的门禁踩了两个坑,均已修)**:① `A230-3` 报**假冲突**——`docs/internal` 整目录在干净 clone 里不存在(gitignore),判据先 `readdirSync` 再判空 → `ENOENT` 被兜成冲突;修法:先判目录存在、缺位记 **SKIP**(本地因目录存在而测不出,属「本地跑绿 ≠ 干净 clone 跑绿」)。② `Structure gates` 失败导致其后 `Run tests` 整步 **skipped**——**一条门禁吃掉了另一条判据的读数**,正是 P0 修掉的静默面换个位置复发;修法:该步与 `Run tests` 步**都加 `if: always()`**(判据之间不得互相遮蔽)。修后按 CI 环境**本地模拟**复核(`docs/internal` 整目录改名 + 日志指向不存在路径):`--check` **exit 0**、SKIP 3 条、冲突 0、无 ENOENT、`--write` 缺档安全跳过、还原后复位。另一次**实弹**:记录 ADR-234 后**未加判据行**时 `S-2` 立即变红(日志 7 条 vs 表 6 条),补行后复位——判据面与决策面是活的耦合,不靠人记。**未实测(明确边界)**:修后 CI 实跑结论(待下一次 run);ADR-197 终局(装配薄化/换装载形态)**仍为在册未达**,本批只给判据不给落地。— [PE-F01](flow: 装配与加载链) - **P2 契约单一事实源:RPC 方法表改为由注册面派生 + 17 条无校验方法具名申报(零 BREAKING)**:关 **RC-A** 主项(症状:RPC 面曾三处并存——线上注册面 34 / 死层 `protocol.js` 声明 21 / schema 17;P1b 已删死层与两组源/副本,本批完成剩余两件)。① **事实源由注册面派生**——新增 `scripts/rpc-manifest.mjs`:静态扫插件双半部的 `harness.handle`(实测 `lib/index.cjs` **16** + `plugin-host.js` **18** = 去重 **34**,交集 **0**),`--check` 锁四不变量:**双面交集=0** · **文档面 100% 覆盖** · **缺口集与具名申报完全相等**(新增线上方法却不申报 → FAIL;申报过期 → FAIL)· **无孤儿 schema**。② **17 条无 schema 方法从「静默缺席」改为具名申报**(**A 桶 12** 无参/只读、**B 桶 5** 有参但保持宽松,各带理由),申报表**由脚本常量生成**并写入 `docs/compatibility-matrix.md`(单一来源,不手抄)。③ **不补运行时校验(零 BREAKING)**——补校验会把既有「静默容忍」的调用变成 **400**,属已发布对外行为变更,须单列 BREAKING 由用户拍板(圆桌决策 A 的边界)。④ **门禁接入默认路径**:CI 新增 `RPC contract check` 步;新增 `test/rpc-contract.test.cjs`(2 项契约锁:34/16/18/17/17 数字锁 + 「已退役 RPC 不得回流」)纳入 `npm test`(15→**16** 文件、255→**257** 项)。**已实测**:`rpc-manifest --check` PASS;**反证**——在 `lib/index.cjs` 临时加一条未申报 RPC → **两条断言同时 FAIL 且退出码 1**,复位后 PASS 且 `build-host --check` 无漂移;`npm test` **257/257**;双 `--check` 零漂移;门禁 PASS(R1–R4);CI YAML 校验 9 步。— [PE-F06](flow: 更新 / 装配与加载链) - **P1b-1 死层退役(第一批·零耦合子集)**:删除 `src/host/{config,index,services}.js` 三件实测「零 require 级引用」的 M2 目标架构骨架(活代码与测试均无引用)→ `src/host` **25 件 / 1028 物理行 → 22 件 / 946 物理行**(−3 件 / −82 行)。**判定依据(架构层)**:这批骨架是 **chunk 形态**的 `src/host/*-service.js`,而 ADR-197 的终局是 `lib/` 下的**原生 Node 领域模块**——两者布局不兼容 ⇒ 骨架**不构成终局的可用起点**,保留只会让「双架构」继续看起来活着(RC-C)。已实测:全仓残留引用**零**、`npm test` **281/281**(不变)、`build-host`/`build-client` `--check` 零漂移、`dead-code-gate` PASS(含 R3)。**同批纠正一处我的度量错误**:此前报「281 个测试块中仅 6 块耦合死层」是按**模块文件名**扫描,而测试实际用**解构符号**(`const { Pipeline } = require('.../pipeline.js')`)→ 漏计;实测真耦合为 **9 个测试文件 / 约 31 个块**。— [PE-F01](flow: 装配与加载链) - **P1b-2 死层退役(第二批·全部 15 件;用户拍板取路径 A「净删」)**:删除 15 件 M2 目标架构骨架(config-schema / config-service / diagnostics-service / enhance / executor-reloader / integrity / logger / model-service / pipeline / plugins-service / protocol / reloader / rpc-schema / update-platform / update-service)+ **6 个纯骨架测试文件**(config-schema / executor-reloader / pipeline / services / enhance-pipeline / update-platform)。**`src/host` 25 件 / 1028 物理行 → 7 件 / 33 物理行**(P1b 目标值精确达成)。**为啥路径 A 而非批准口径的「改挂活层」**:实测 15 件中仅 2 件有成对活副本,其余 13 件在活代码里**没有对应实现**——「改挂」不可行;被退役的测试守的是**从不执行的骨架代码**,其意图已由 ADR-194/197 承载。**守卫不静默丢覆盖**:3 处「双份同步防漂移」守卫(`CFG-P05` / `VOICE-P05` / `UGATE-13`)**改写为「单一事实源 + 死层副本不得复活」**——测试数保留,并把 P1b 成果**锁死**(任何一处把 `src/host/rpc-schema.js` 写回来即红);integrity 守卫改挂活副本 `lib/integrity.cjs`;`OBS-01` / `PROTO-01` / `U-parity` 随骨架退役。**源/副本关系反转修正**:`lib/rpc-schema.cjs` 与 `lib/integrity.cjs` 的「Source of truth: src/host/…」注释改为「**本文件即唯一事实源**」。**新增门禁 R4「src/host 不得再长出死层」**:`src/host` 磁盘文件集必须与 `build-host.mjs` 装配清单**等值**(多一件即 FAIL)——这是 RC-C「双架构无淘汰机制」的结构判据,替代按入边判死(后者识别不出**互相 import 的死簇**,18 件中有 13 件正是这样伪装成"有入边")。**R4 已做反证**:临时塞入清单外文件 → FAIL 且退出码 1;移除后复位 PASS。已实测:`npm test` **281 → 255**(−26 块,与预测 ~24 吻合);双 `--check` 零漂移;门禁 PASS(含 R3/R4);全仓死层路径残留**零**。一处工具教训:`lib/rpc-schema.cjs` 在磁盘上是 **CRLF**,字面量替换须按**文件自身 EOL** 构造(首次替换因此静默未命中,已按 EOL 自适应修好)。— [PE-F01](flow: 装配与加载链) - **P1a client 载体单一化:孤儿产物 `plugin-client.js` 退役(发布物体积 −26.97%,零 BREAKING)**:背景——该文件是「构建生成 + 进发布物 + 进 CI 检查,**从不被装载**」的孤儿(DSH 客户端模块只认 `package.json exports["./client"]` → `lib/client.cjs`);其正文已被 `lib/client.cjs` **逐字内嵌**(318,792 字符在位),删除**零代码丢失**。**同批解耦面**(缺一即红):`.github/workflows/ci.yml`(`node --check` 与 `git diff --exit-code` 两处移除该路径)· `scripts/build-client.mjs`(停止产出该中间物、`--check` 只检 `lib/client.cjs`、头注改写)· `scripts/sync-runtime.mjs`(产物语法门与运行环境同步清单两处数组)· `lib/index.cjs`(`RESTART_FILES`)· `lib/sys.cjs`(救援快照清单)· `lib/stage-install.cjs`(解包语法门清单)· `test/config-persist.test.cjs`(CFG-P07)与 `test/voice-persist.test.cjs`(VOICE-P07/P08/P23/P24 共 4 处字节读取)改挂 `lib/client.cjs` · `package.json` `files` 白名单。**新增门禁 R3「产物清单 ↔ files 白名单一致」**(`scripts/dead-code-gate.mjs`):白名单条目必须磁盘在位(幽灵条目 `npm pack` **静默跳过**、正是本次踩到的静默面)+ `main`/`exports` 入口必须存在且被白名单覆盖 + 运行必需产物(`plugin-host.js` / `lib/index.cjs` / `lib/client.cjs`)必须在位。**按决策明确不动**:`UPDATE_MANIFEST`(host `pure.js` / client `updater.js` / `test/lib.test.cjs:917`)仍列该文件名——属已单列的遗留项,本轮不夹带。**已实测(N1 三阶)**:①解耦后 `npm test` **281/281**(用例数不降)+ `build-host --check`/`build-client --check` 零漂移 + `dead-code-gate` PASS(含 R3);②删除后 `npm pack --dry-run --json` **entryCount 20→19**、**unpackedSize 1,303,630 → 952,442 B**(孤儿 351,607 B = 原发布物 **26.97%**);预测值 952,023 与实测差 **+419 B 已逐项对账**(三个**打包内** lib 文件新增的 P1a 注释 250+110+85,减 `client.cjs` 3、`package.json` 24),**属 accept A29 的 ±3,340 B 容差内**;③删除后再跑 `node scripts/build-client.mjs` **不复活**该孤儿("删掉又被构建重建为 untracked、而 `git diff` 看不见"的静默面已封);四探针(`dsh-enh-result:` / `function EnhanceButton` / `function RecordRTC` / `dsh.enhance.config.v2`)在唯一载体 `lib/client.cjs` 中**静态全中**。**未实测(明确边界)**:浏览器加载路径等价的**实机四探针**未做——需 `sync-runtime` 部署 + 重启 DSH,而本会话正运行于该 DSH 进程内(重启将终止会话),已列为待授权的验收动作。— [PE-F01](flow: 装配与加载链) - **P0 收口实测:CI 从「6h 挂死」到「52 秒给出完整结论」——并推翻「12 件零上报文件有病」的原判**:run `35392998179`(head `2014928`)per-file 隔离跑 **52 s 完成**,21 个文件**全部跑完**:**17 PASS / 4 FAIL**,且 4 个失败文件**全部 ≤1 s 快速失败**(无一触到 40 s 上限)。⇒ 原先那「12 件零上报文件」**不是问题文件,而是被并发池里卡住的文件"饿死"(从未被调度)**——6 h 挂死的真身是**套件级并发争用**,不是某个文件内部挂起(`--test-force-exit`/`--test-timeout` 对它无效的实测结论由此得到解释)。真实失败面 = **4 文件 / 9 用例,全部为平台耦合**:`sys` SYS-06·SYS-07(`'ENOENT' !== 1`)·`maintain` MAINT-02·MAINT-04(端口/PID 探测)·`update-gate` UGATE-27·UGATE-30(日志尾部夹具)·`deploy-guard` DGRD-16/17/19(`spawnSync C:\Windows/System32/tar.exe ENOENT`)。**对门定义的修正**:原「排除项限 3 条具名用例」的阈值被实测打破(实为 9 条),按决策 A「平台性失败只记录不修」改为**具名 9 条清单 + 永不按类别排除**(保留防膨胀锁语义:新增项必须重新具名,不得被类别笼络)。**产物一致性判据在本轮首次被真正评估且通过**。遗留开放项:套件级并发争用的具体争用对象未定位(不影响本门可用性,已记入观察项)。— [PE-F01](flow: 装配与加载链) - **P0 门禁真话化:CI 从「永不完成」改为「能给结论」(圆桌决策 A 落地第一件)**:背景实测——本仓 CI 近 12 次运行 **success = 0**(10 cancelled + 1 action_required + 1 in_progress);逐 step 实证 `Run tests` 每次挂满平台 **6h 硬顶**被 cancelled,其后 `Build client bundle` 与 `Verify generated artifacts are committed` **永远 skipped** → 验证面长期无任何信号(Issue #9 的请求体字段名笔误能一路发到 v3.3.3,即此门失效的直接产物)。根因形态:`node --test` 在用例跑完后**等待事件循环清空**,Linux 上被某测试文件的长驻句柄吊住(Windows 本地 31s 全绿,故此前未被发现)。修复(`.github/workflows/ci.yml`):① 作业级 `timeout-minutes: 15` + 测试步 `timeout-minutes: 12` **双硬顶**(真挂死也快速失败,不再挂 6h)② `npm test -- --test-force-exit --test-timeout=120000`(前者治「进程不退出」且**不掩盖任何用例结果**,后者治单个用例挂起)③ 测试输出 `tee test-report.txt` 并以 `if: always()` 上传 artifact(失败也能取回报告)④ 后置构建与产物一致性两步改 `if: always()`,杜绝「一次挂起吞掉两条独立判据」的静默面。**边界(决策 A 第 4 项)**:CI 平台性失败(`DGRD-16/17/19` 硬编码 Windows `System32\tar.exe`)本轮**只记录不修**,「CI success」不作为验收门。**第一轮实测(run 35391559294 · head 7f9176b):结构目标达成**——run `completed / failure`(**非 cancelled**,命中 12 min 步级硬顶而非平台 6h);`Upload test report` / `Build client bundle` / `Verify generated artifacts are committed` 三步**全部执行**(`if: always()` 生效)→ **产物一致性判据第一次被真正评估且通过**;用例 `ok 146 + not ok 3 + 零上报 132 = 281` 恒等式闭合,`not ok` 即预期平台性 3 条(`DGRD-16/17/19`)。但 **`--test-force-exit` 被实测证伪**:结束仍残留 4 个 node 孤儿进程 ⇒ 挂起发生在测试**进程内**,不是「跑完不退出」;`--test-timeout` 也打断不了阻塞中的用例。→ **第二轮改「按文件隔离跑」**(每文件独立进程 + 40 s 硬顶 + 逐文件 PASS/FAIL 与耗时打印),既产出 12 件零上报文件的逐件裁定,又根治「一个文件吊死连坐全部门」。已实测:本地 `npm test -- --test-force-exit --test-timeout=120000` = **281/281(31 s)**;YAML 经 js-yaml 解析通过(job 15 / step 12 / 后置三步 `if: always()`);run 脚本经 `bash -n` 语法通过。— [PE-F01](flow: 装配与加载链) - **修复:云端 chat 协议 ASR 恒失败——请求体字段名笔误(Issue #9 外部反馈)**:`lib/asr.cjs` 的 `transcribeChat` 组装 `POST /chat/completions` 请求体时,content part 的 `type` 本已写对(`input_audio`),但**内层键名误写为 `audio`**,与 OpenAI 兼容规范 `{"type":"input_audio","input_audio":{"data":…,"format":…}}` 不符 → 服务端一律返回 400「Expected 'input_audio' field in input_audio type content part to be a string or dict.」;该 400 又被 `status >= 400` 分支一律映射为 `ASR_BAD_RESPONSE`,client 侧 `errKeyFor` 落到「音频无效」⇒ **服务端契约错误被伪装成音频质量问题**:引擎=云 + 协议=chat(qwen3-asr-flash)的用户 100% 失败且无从自查(openai multipart 协议路径不受影响)。该行自 `07b4273`(2026-08-20 语音 P1)引入即带病。修复:单行改回 `input_audio: { data, format }`。— [VOICE-003](flow: voice-input) - **补验证缺口:云端 ASR 出网契约断言(同上)**:此前仓库对 `transcribeChat` / `chat/completions` **零断言**——只有 `node --check` 语法门与正弦波假音频的 voice-verify 冒烟门,「语法门 + 冒烟门都在,唯独契约字段无色」,故该笔误一路发到 v3.3.3。新增 `test/asr-cloud-contract.test.cjs`(3 项):**打桩 `node:http.request`,经导出入口 `transcribe()` 走生产全路径**(transcribe → transcribeChat/transcribeOpenai → httpsRequest → JSON 序列化)抓真实请求体,断言 ①chat 协议 `content[0].input_audio` 字段正确且旧键名 `audio` 零残留 ②服务端 400 → `ASR_BAD_RESPONSE` 映射(复现「音频无效」伪装路径)③openai 协议仍走 multipart `/audio/transcriptions` 不受影响;同批把新文件收进 `npm test` 脚本(防「测试存在但未收录 → 长期空跑」,同 2026-09-12 platform-service/watchdog 教训)。**反证已做**:临时副本退回缺陷行后 AC-01 必 FAIL(AC-02/03 不涉键名故仍绿),证明该断言真能抓住此 bug。已实测:node --check 三侧 + lib 全量 + dead-code 门禁 PASS + build-host/build-client --check 零漂移 + 全量 `npm test` **281/281**。**未实测**:真 key 打线上接口的端到端复验(本地无可用 API key;按用户指示本次不发版)——留待发布前补。— [VOICE-003](flow: voice-input) - **订正:验证基线的用例数失真(本次审查顺手校准)**:`AGENTS.md` §6.1 原记「21 测试文件,330 项」、CHANGELOG 2026-09-12 条目亦记 330/330——该数为 `ddc6360` 时点真值,其后 `cfec53c`(PE-F06 移除插件内重启能力)**随功能一并删除** `test/watchdog.test.cjs`(6 项)、`a4-drill.cjs`、`e2e-restart.cjs`,并大幅缩减 `maintain`(51→23)/ `update-gate`(32→24)/ `deploy-guard`(19→9)。实测 `git diff --stat ddc6360 HEAD -- test/` = 净删 1341 行,逐文件计数合计 **278**(本次新增 3 项后 281),**属合法缩减而非覆盖丢失**。已将 AGENTS.md §6.1 订正为 281。— [PEN-002](flow: prompt-enhance) - **审查修复批次(用户指令「全部修复」·2026-09-12)**:以三路只读审查(文档/版本一致性、host 侧代码健康、diagLog 改动复核)+ 实测探针为依据,一次性修掉全部 P0/P1 发现。①**安全·RPC 来源栅栏(实测 P0)**——实测无 token 的 `POST /dsh-prompt-enhancer/rpc`(`text/plain`,不触发 CORS 预检)返回 **200 真实数据**,而 `GET /` 是 401:本插件 RPC 位于 DSH web 鉴权栅栏之外,任意网页可借用户浏览器驱动 `config/set`、`update/portRestart`(重启 DSH)等副作用。修复:带 Origin 的浏览器请求必须与 Host 同源、`Sec-Fetch-Site` 跨站即拒(403 `FORBIDDEN_ORIGIN`);无来源头的本机调用照常放行(不引入 token,避免打断本机工具链);请求体加 **1 MiB 上限**(413 `BODY_TOO_LARGE`,原实现无上限可吃内存)。ADR-146。②**安全·SYSTEM 任务注入面(P0)**——生成的任务 .cmd 由 Task Scheduler **以 SYSTEM 运行**,值中出现 `"` 会闭合 `set "NAME=VALUE"` / `--dsh-bin "…"` 的引号从而注入 `& …`。修复:新增 `cmdSafeValue`(剔除 CR/LF 与双引号、按需转义 `%`),.cmd 全部生成点(env 行/命令行/快捷方式 args)统一走它。③**diagLog 链路补完(D1/D3/D4/D5)**——D1:`update/portRestart` 从不写 diagLog(该特性声称解决的「端口重启 90s 超时」场景恒空)→ 新增只读 RPC `update/diagTail`,client 在 4 处 `updApplyExecutorDown` 终态主动补取 DSH err 日志尾部(仅在本地无根因时写入,不覆盖执行器已给出的根因);D3:尾部 8 行易被 `ExperimentalWarning: SQLite` 等噪声淹没 → 降噪 + 根因优先筛选(筛不到回退原行为,**绝不返回空串**);D4:脱敏漏 JWT/`Basic`/`apiKey=`/`password=`(实测原样外泄,触碰「不暴露密钥」红线)→ 补规则并把 `sk-` 门槛降到 6 字符;D5:host 侧旧 diagLog 永不清除(新失败显示旧根因)→ 新一轮 restart 起始清空。④**静默失效与死代码**——`lib/asr-models.cjs` 的 `modelOpenDir` 原不判 `schtasks` status(失败仍返回 `ok:true`、UI 假成功)→ 判 status 并显式失败;`lib/updater-host.cjs` 的 `tracePush` 空函数(击杀占用者留痕永久丢失)→ 真正落盘;`lib/index.cjs` 删除无用的 `node:os` require。⑤**测试覆盖缺口**——`test/platform-service.test.cjs`(23 项)与 `test/watchdog.test.cjs`(6 项)此前存在却**未被 npm test 收录**(长期空跑)→ 收入脚本;新增 `test/rpc-guard.test.cjs`(5 项:来源栅栏矩阵 / cmd 安全化 / 体积上限 / 源码锚定 / **假 ctx 挂载真实 handler 的端到端冒烟**:跨站 403、同源放行分发 404、超大 413、非 POST 405)。⑥**文档与治理同步**——README 中英(安装命令与 Issue #8 修复的版本错位、EN 补译「网络受限环境下载语音模型」整节、死链 `docs/map/flow/voice-input.md`、executor 路径 `0.1.11`→`0.1.12`、badge/`VAD 60s` 说明);`docs/compatibility-matrix.md` 补 `models/stats` + 10 条 `voice/*`(实测 RPC 全集 **34** 条)与 3.3.x 版本行、新增 §4.1 客户端依赖边界;CHANGELOG 重复 `[3.3.1]` 链接定义去重;AGENTS.md 数字与清单 7 处;M2 not-wired 骨架登记为治理节点 **PE-M2**(`maturity=target-skeleton`,防后续 agent 误当死代码删除或误当线上代码修改)。**明确不删**(防破坏公开 API 面):`models/autochain`、`update/pull` 两条无 in-repo 调用方的 RPC 仅标注现状(矩阵红线②「RPC 方法名不得随意变更」)。测试:`node --check` 全过;dead-code 门禁 PASS;build-host/build-client `--check` 零漂移;sync-prompts `--check` OK;全量 `npm test` **330/330**(测试面扩容:18→21 个文件——补收 platform-service 23 项 + watchdog 6 项,新增 rpc-guard 5 项,diagLog D3/D4/D5 与 D5 清理补 4 项)。— [PEN-002](flow: service-and-env / port-restart / one-click-update) - **移除服务化安装提醒(用户指令·2026-09-12)**:定位——NSSM 服务化对多数用户无意义(官方默认即网页前台模式,插件已有 watchdog 崩溃自愈),而设置页「插件管理」每次挂载都会出现「检测到 DSH 未以系统服务运行(无 nssm)…是否现在安装?」+「确认安装」按钮,对不用服务模式的用户是纯噪音(本机实测:无 dsh-web 服务 + 前台模式 → 每次打开设置页必现)。删除面(**-208 行**):①**client**(updater-card.js -61 行 / i18n.js -12 行 / styles.js -5 行)——引导行 + 按钮 + `svcNeedInstall`/`svcInstalling`/`svcInstallMsg` 三状态 + `doServiceInstall` + **三处触发点**(挂载 effect / 重启轮询 `healthy` 分支 / 端口恢复 `done` 分支各一次 `update/envcheck` 仅用于算引导态)+ i18n `svcInstall*` 5 键(ZH/EN 成对)+ `.dsh-plg-svc-hint*` CSS;挂载 effect 整体移除后设置页少一次无谓 host RPC。②**host**(lib/index.cjs -130 行)——RPC `update/serviceInstall` 及其整条 nssm 链路:`NSSM_VERSION`/`NSSM_URL`、`nssmToolsDir`、`findNssm`、`downloadNssm`(curl 拉 nssm.cc zip + tar/Expand-Archive 解压)、`buildNssmInstallScript`、`runElevated`(写临时 ps1 + `Start-Process -Verb RunAs` 弹 UAC);**`resolveDshBin` 保留**(portRestart 的 Desktop 分支仍在用),`netProxy` require 因唯一消费者被删成为孤儿、一并移除。③**执行器文案**(lib/updater-host.cjs 3 处消息 + 1 处注释)——维护菜单「服务可经设置页『服务化安装』重装」改为「手动重装 nssm 服务(插件已不提供安装入口)」,不再指向已删除的入口。④**文档**——`docs/compatibility-matrix.md` RPC 清单删该行。**明确保留**:port-mode 四态服务检测、服务模式端口重启(schtasks 路径)、执行器恢复阶梯与卸载确认门(`degradeNssmService`)、watchdog 让位——「有服务走服务、无服务走前台」双路径与全部诊断能力不变,已装 nssm 的机器零影响。测试:node --check 三侧 + dead-code 门禁 PASS + build-host/build-client --check 零漂移 + sync-prompts --check + 全量 npm test 292/292;产物 grep 零残留(`serviceInstall|svcInstall|NSSM_URL|downloadNssm|findNssm|buildNssmInstallScript|runElevated`)。— [PEN-002](flow: service-and-env / port-restart) - **改进:端口重启 90s 超时根因直显(diagLog 链路·2026-09-08 用户实测驱动)**:背景——用户实测遭遇「端口重启超时:DSH 服务未在 90 秒内恢复」,根因是另一插件 `dsh-shoucang-memory` 启动期 `JsonSchemaError`(boot 崩溃循环 → 3080 永不监听),而超时文案只引导「刷新/重启 dsh-web」,对此场景是死胡同,用户无从自行定位。改进:①**executor 侧**重启链穷尽失败(`state.phase='failed'`)时经 `dshErrLogTail(svc)` 抓 DSH err 日志尾部(win32 经 nssm 注册表 `AppStderr` 定位 → 降级前台重启 `port-restart.err.log` → 缺失空串静默;尾部 8KB/最多 8 行/单行 300 字符截断/NUL 清洗)挂 `state.diagLog` 随既有 `status` 响应透传——加性字段,零新 RPC、零 rpc-schema 变更;下发前 `redactDiagLine` 脱敏(URL `token=`/`Bearer`/`sk-`,对齐不暴露密钥红线)。②**client 侧**轮询顺带缓存 `diagLog` 入 `lastDiag`(执行器随后不可达仍可显示);`failed` 分支优先取实时值回退缓存;四处 `updApplyExecutorDown` 终态统一接线(无根因清空防旧残留误归因);根因以 `updDiagTitle`(ZH/EN 成对)标题 + 内联样式 `
` 展示在错误行下方(不动 styles chunk)。测试:UGATE-27(脱敏三形态/尾部截取/maxLines/NUL 清洗/降级矩阵)+ UGATE-28(client 五接线点 + i18n 成对)新增;node --check 三侧 + dead-code 门禁 + build-client 重建 --check + build-host --check + sync-prompts --check + 全量 npm test 292/292。已知边界:watchdog 风暴停手(crash loop paused)不改 state.phase、暂不走 diagLog(登记 flow 文档,如需后补)。**2026-09-12 只读审查修复(blocker·D2)**:client 侧缓存原以 `let lastDiag` 声明在 `pollExecutorStatus` 内,而 `runPullApply` 的 `.catch` 与 `pollRestored` 的 >90s 超时分支也写 `setDiagLog(lastDiag || null)`——越作用域引用 ⇒ 运行期 `ReferenceError`,其后的 `setApplyErr(t('updApplyExecutorDown'))`、状态清理、`setAction(null)` 整块不执行(**超时文案消失且按钮卡死,比修复前更坏**;UGATE-28 原为纯字符串计数断言,作用域错照样全绿)。修复:缓存上提为组件级 `const lastDiagRef = React.useRef('')`,6 处接线改走 `.current`;UGATE-28 补「非注释代码零裸 `lastDiag` + 写入/回退读路径」断言。全量 npm test 292/292。审查另记未修项(D1 端口重启链不写 diagLog、D3 尾部 8 行易被 SQLite ExperimentalWarning 淹没、D4 脱敏漏 JWT/Basic/apiKey=/password=、D5 host 侧旧 diagLog 不清除),见交付说明。— [PEN-002](flow: one-click-update / port-restart)
- **修复:✨ 提示词增强按钮在官方槽位渲染器上不渲染(Issue #8)**:EnhanceButton/EnhanceBar/VoiceMicButton 此前从 `props.session.sessionId` / `props.input` 取会话与输入状态——而 `@deepseek-ai/dsh-client-ui-renderer@0.1.2-rc.1` 的会话级槽位条目(`conversation.input.right` / `.dock`)**只注入 `sessionId` prop + `useSession`/`useInput` 选择器 hook + `inputActions` prop,从不合成 `session`/`input` 两个 prop**(ui-session 内置 seat 仅 declare `sessionId`;ui-conversation provide 仅 `inputActions` + `input`/`conversation` hooks)。后果:EnhanceButton 拿不到 `props.session` → `sessionId === undefined` → 恒 `return null`,输入框 ✨ 永不显示(DSH Desktop 与 web 宿主同为 0.1.2-rc.1 渲染器,均受影响;🎤 VoiceMicButton 不 gate sessionId 故不受影响——与 #8 现象一致)。修复:三组件会话/输入取值统一走官方槽位契约——`useSession((s) => s).sessionId` / `sessionId` prop / `useInput((s) => s)`(hook 以 props 上是否存在函数为恒定条件,hook 序稳定),旧宿主 `props.session` / `props.input` 形态保留回退兼容。附带修复 EnhanceBar 会话粒度错误提示与语音按会话状态存储在无 `session` prop 宿主上的 undefined 问题。测试:三 chunk 解码断言(新契约分支/旧回退/无残留旧独行)+ require/语法 + build-client --check + dead-code 门禁 + 全量 npm test;渲染级复验见文末实测注。— [PEN-002](flow: prompt-enhance / voice-input)
- **BREAKING(PE-F06)**:移除插件内重启能力(`update/portRestart` / `update/makeShortcut` RPC、桌面快捷方式、服务/端口重启、自动重启链、维护救援 CLI 全部退役);**更新功能保留**——新增 `update/install` RPC 负责安装已下载的新版本,装完提示**手动重启 DSH** 生效。— [PEN-002](flow: one-click-update)已实测:文档自查(README.md / README.en.md 结构对称 + 全仓「一键重启 / 端口重启 / 快捷方式重启 / `--cli restart`」表述 grep 零残留)。

## [3.3.3] - 2026-09-01

- **修复:一键更新 executor 崩溃(undici 依赖缺失 + 端口链路补强·2026-09-01 用户实测)**:现象——一键更新报「update executor failed to start on port 3081 操作失败,请重试」(代码 `EXECUTOR_START_FAILED`)。根因两层:①**P1 依赖缺失(主因)**:批次A 给 `lib/net-proxy.cjs` 引入 `require('undici')`,但独立 executor 目录(`%LOCALAPPDATA%\dsh-prompt-enhancer\executor\\`)由 `ensureExternalExecutor` 同步 lib/ 时**从不复制 node_modules** → executor 进程启动即 `MODULE_NOT_FOUND: undici` 秒崩(`%TEMP%\dsh-updater-host.log` 实锤)→ 任何 executor 重建后更新/端口重启全链失效(v3.3.3 发布物同样带病);②**P2 端口叠加**:DSH 0.1.1-rc.2 主进程占走 3081(0.0.0.0 web),executor 固定口冲突——产品已有 EADDRINUSE→`listen(0)`→写 `executor.port` 动态口 fallback(updater-host L1126-1145),但 executor 在 bind 前已崩、从未走到;另 `executorEnsure` 以 `pf.port !== port` 为前提读端口文件,陈旧 3081 时跳过验证误报。修复四项:①**ensureExternalExecutor 同步 undici**(`require.resolve('undici')` → cpSync 至 `executorRoot/node_modules/undici`,undici 零传递依赖单目录即可;失败留痕不阻断);②**net-proxy undici 惰性加载 + 降级直连**(缺失不崩进程:新增 `httpsGetDirect` 内置 https + 手动重定向,`httpsGetProxied` 入口分流;留痕 `undici unavailable, proxy disabled (fallback direct)`——进程存活优先、代理能力次之,对齐批次A 降级哲学);③**executorEnsure 无条件验证 executor.port**(`pf` 存在且端口合法即 ping 验证,陈旧端口==请求口陷阱消除,`dynamic` 转展示语义);④**单测锚点 LIBWIRE-03~06**(undici 同步/无条件 pf 验证/惰性加载降级/EADDRINUSE 写端口文件四锚防回退)。测试:node --check 双侧 + dead-code-gate + build-host/client --check + 全量 npm test 289/289(286+3 前缀含 watchdog…实际 286+4 新锚点)+ 地图 check --strict;手动冒烟:占 3081 → updater-host 动态口 + executor.port 写入实测。已实测注:运行环境部署与重启复验按发布节奏执行(本批未部署,线上运行环境为缺 undici 旧副本——应急即复制 undici 至 executor\\node_modules)。— [PEN-002](flow: one-click-update / port-restart)

- **package-lock.json 根版本同步(审查发现·2026-09-01)**:lock(lockfileVersion 3)根 `version` 3.2.4 → 3.3.2,与 package.json 单一事实源对齐——此前存在 1 版漂移(`npm ci` 会告警),依赖树本体一致(`npm ls` 无 error),零依赖变更。— 发布物元数据一致性
- **外部贡献处理(fork/PR 评估·2026-09-01)**:①**Poeagle fork(ymchen c7c2c0c)**「watchdog 超时修复」评估——其修复根因(看门狗/探测硬编码 5s/3s 窗口误杀慢模型)与本仓库 2026-08-27 已合入的「增强易断链修复」(下文条目,WATCHDOG 15s / PROBE 12s / 缓存 5s / 链耗尽整链重试)**同根因、方向一致且已被覆盖**,且其补丁基于旧基线(v3.3.1-era,同步改 package.json bump/emoji 乱码等本仓库已处理项)与当前生成物/测试大面积冲突,**不合并**(以本记录替代,避免引入旧基线回退);②**webbrain-one PR#7**「docs: improve README」——PR 记录已删除(GitHub 仅存僵尸 `refs/pull/7` 引用),其中两处有效改动(Stars badge 链接补 `/stargazers`、VAD 说明补「最长 60 秒自动结束」)经核属实,**手动采纳**入 README.md(忽略其文件头两空行的无效噪音);③其余 fork 提交(tt-a1i lockfile 规范化、wuseyangg/Doraemon `.gitignore` 微调)为琐碎/无价值改动,不采纳。纯文档变更,无代码/构建影响。— [PEN-002](flow: prompt-enhance)
- **增强链路超时/探测窗口放大 + 探测失败短缓存 + 链耗尽整链重试一次(用户实测驱动·增强易断链修复·2026-08-27)**:背景——实测链上模型 TTFT:链1 completions/deepseek-v4-flash 2.8s、链2 responses-proxy/muse-spark-1.2-contributor 9.3s(`models/test` 180 字输入真实流式);而旧增强判定阈值 5s 看门狗 + 3s 探测会把「慢但健康」模型误判不可达:实测 muse 链在 `maxTokens=1200` 下 0 正文(推理预算黑洞,`models/test` 现象)与 TTFT 9.3s 双因叠加,探测/看门狗双重必杀 → 增强「断链」而主会话无感(主会话有宿主 `agent/request-error` retryPolicy(always) 无限重试兜底,增强直调 `llm.stream` 完全绕开该机制)。修复四项:①**WATCHDOG_TIMEOUT_MS 5000→15000**(首条首字响应窗口放大 3 倍,慢但健康模型不再被看门狗误杀);②**PROBE_TIMEOUT_MS 3000→12000**(连通性探测对齐实测 TTFT,muse 9.3s < 12s 探测线可达);③**PROBE_CACHE_TTL_MS 30000→5000**(探测失败缓存 30s→5s,一次抖动不再冻结整链半分钟);④**链耗尽整链重试一次(v3.3.3 新增行为)**——enhanceStageLlm 外层 pass 循环(最多 2 pass):首轮生成级失败耗尽(EMPTY/STREAM_THROW/QUOTA/瞬时错误码)后整体重试一轮,重试轮 `watchdogDone=true` 免看门狗、直接真实生成(不走探测缓存),瞬时限流/慢首字/网络抖动自愈;**探测全败(ALL_MODELS_UNAVAILABLE)保持立即报错**(12s 窗口下全败=实测真不可达,重试无望不空等)。总预算仍由外层 `timeoutMs` 计时器兜底(rec.timedOut 立即中断,不无限重试)。常量集中维护于 src/host/pure.js(附实测依据注释)。测试:SMK-18 断言适配新窗口(elapsed <15s→<25s,REAL_TIMER 真实 15s 看门狗 + 瞬时探测);新增 SMK-20(生成级失败耗尽 → 两轮×两模型调用序列 ['m-a','m-b','m-a','m-b'] → 第二轮命中 m-b,fallbackUsed=true,零探测)锚定新重试行为。已实测:node --check 双侧 chunk 求值 + dead-code-gate PASS + build-host/build-client --check 双侧 + bundle-smoke 22/22 + 全量 npm test 286/286;运行环境 sync-runtime 部署与重启 RPC 复验待部署窗口执行。— [PEN-002](flow: prompt-enhance)
- **日志全链 ISO 时间戳 + 发布物 BOM 收口声明(批次D v4.15·P2-5+P2-7 观测性改进·2026-08-25)**:①**hlog/herr 统一 ISO 时间戳前缀(P2-5)**——src/host/diagnostics.js 在行组装处一次性前置 `[YYYY-MM-DDTHH:mm:ss.sssZ]`(格式先例借 logger.js ISO ts;createLogger 结构化接线维持不采纳——M5 接线是大改非最小解,t3 选项 B 结论维持),bundle 内全部调用点零编辑覆盖(产物实测 62 hlog + 15 herr = 77 处;方案期计数 75),调用点字面量前缀(如 `[enhance]`)原样保留在时间戳之后(grep 兼容:关键 token 不动);LOG_RING/logs-last 消费方为原始行拼接展示,前缀不破坏兼容(D-1 审计+单测断言)——out.log/err.log/logs/last 每行可取证,不再靠行号反推。②**bundle 外直写 console 收编**——lib/index.cjs 新增 `logT` 局部包装并替换模块直写 console.log 全部 15 处真实调用点(executor spawn/delayed sweep/portRestart 自动闸与调度/staged install 等,含批次A 后新增面;生成脚本字符串内嵌的 console.log 属独立进程自有重定向日志不在范围)、lib/stage-install.cjs 同法 4 处(rescue snapshot/deploy ledger/stage tgz 持久化);两文件各自局部包装不抽公共模块(跨文件抽 util 会扩大 executor 内容哈希面,收益不成比例)。③updater-host/asr-worker 各自进程日志本批不加时间戳(输出经重定向入独立文件,格式变更影响面另评,登记 backlog 备注)。④**P2-7 发布物 BOM 收口声明(零代码)**——按 optimization-plan-20260824 §D.2 结论收口:本项目侧已闭环(仓库源与 sync-runtime 链路 v3.3.2 根治 + PKG-BOM 回归测试在位 + 安装链 sha256 强校验);残余风险定性为上游问题(dsh-app-boot `loadProfile` 的 readFileSync+JSON.parse 不容 BOM),维持上游反馈跟踪、本项目不代修(不可动引用的框架红线);现场清除已由批次0 完成(官方 v3.3.2 换装 + 自愈缓存换血),批次B 守卫 '' 拒绝分支使 BOM 包无法静默复活;backlog P2-5/P2-7 条目已标注收口状态。生效说明:本批触 src/host ⇒ sync-runtime 部署后需重启 dsh-web 生效(部署与重启时机归用户,本批未部署)。测试:test/observability.test.cjs 新增 OBS-03(diagnostics chunk eval 手法:hlog/herr ISO 前缀断言 + 原文案/参数原样保留 + LOG_RING 含前缀),OBS 3/3 绿。— [PEN-002](观测性改进)
- **修复:恢复被批次D误删的 require 绑定(t7 终审发现·2026-08-25)**:89577aa(批次D logT 包装插入脚本以「锚点→包装」整行替换实现、漏带原锚点)误删 lib/index.cjs 的 `asrDeploy` 与 lib/stage-install.cjs 的 `sys` 两处 require 绑定——若部署重启,「本地引擎一键部署」及状态轮询将返回 DEPLOY_RUNTIME_FAILED/'asrDeploy is not defined'(catch 兜住不崩溃但功能失效);运行环境未部署本批、线上零影响、属 HEAD 带病。stage-install 侧 sys 同源损害为 auditor 报告之外 t7 自查 git diff 发现。已按 `git show 3a0dad8` 对照原位恢复两绑定;新增 test/lib.test.cjs LIBWIRE-01/02 源文本锚点断言防复发(「引用有声明」类错误不在 node --check/dead-code-gate/既有单测射程内——三道门全漏根因与候选通用化方案登记 backlog 门禁缺环节)。已实测:node --check 全过 + 全量 npm.cmd test 285/285 + build-host/build-client --check 双侧 + 地图 check --strict。— [PEN-002](flow: voice-input / service-and-env)
- **probeEnv 全异步化 + net 探测熔断冷却(批次C v4.14·P1-4 主循环冻结面消除·2026-08-25)**:①**全异步化**——`sys.probeEnv` 转 async,工具预检后四组互相独立探测(tools / net 熔断 / detectService 异步孪生 / 3080 监听者)经 `Promise.all` 并发(最坏耗时≈最长单项),单项独立 try/catch 兜底降级 warn 不拖垮整表;新增 `runProbeAsync`(promisified execFile:无 shell、windowsHide、15s 单项硬超时保持、maxBuffer 10MB;非 0 退出映射 spawnSync status 语义、超时映射 ETIMEDOUT、启动失败映射 ENOENT),白名单校验抽取 `validateProbe` 供 sync/async 双执行器共用单一事实源——**runProbe 保持同步形态**,重启阶梯链同步消费方(svcStateRaw/degradeNssmService/restartPort sc qc)零波及;②**返回契约零变化**(RPC 红线):items 数组序 `[tools,net,port-mode,port-pid]`、各 key 集合、ok/warn/detail/level 字段与同步版逐字段一致,golden 契约单测锚定;消费方接线:plugin-host envcheck handler 与执行器 apply envcheck 段均 await,harness facade 形态零改动(返回值即 Promise);③**platform-service 异步孪生**:`detectServiceAsync` + `defaultProbeAsync/probeAsync/__setProbeAsync`(8s 超时对齐、形状一致),解析复用同一套 parse*,parity 单测锁定孪生一致性,同步 detectService 原样保留(index.cjs 端口重启链不受影响);④**net 熔断冷却**:连续 3 次失败进入 10min 冷却窗,窗内零实测直接回缓存 warn 判定并在 detail 后缀标注 `+cooldown`(degraded 标注走 client 无映射 detail 原文直显兜底,零 client 改动),实测成功即清零,防御旋钮 env `DSH_PE_NET_BREAKER=0` 关闭(默认开),进程内存态重启即清;⑤**B1 端口原语集扩展**:自包含纯解析原语 `dshPrimParsePortHolder` 入 PORT_PRIMS 发射块(sync 原语与 probeEnv 异步链共用同一解析单一事实源);posix lsof 因探针白名单扩面红线保持 sync 原语兜底。预期管理:本条消除的是**插件自身的同步阻塞面**(实测事件循环阻塞从「整段调用期全程阻塞 ≈775-836ms」降至毫秒级心跳抖动),CLOSE_WAIT 设置页冻结归属 DSH 本体出网问题、不因本条根治。测试:test/sys.test.cjs 新增 SYS-06~13 共 8 项(白名单/退出码映射矩阵/真实挂起子进程超时击杀/golden 契约逐字段/早退与降级形状/熔断状态机三阶段+旋钮/Promise 形状防御/孪生 parity),全量门禁见提交说明。已实测:L3 node 直调冒烟(handler 真实传参形态 items 契约断言)+ monitorEventLoopDelay/心跳漂移改造前后对比 + 全量单测 + build --check 双侧 + 地图 check --strict;运行环境 sync-runtime 部署与重启 RPC 复验按派工约束留待部署窗口执行。— [PEN-002](flow: service-and-env)
- **部署账本 + 自愈版本守卫 + 安装一致性自检(批次B·静默降级复活封堵·2026-08-25)**:①**部署账本** `$DSH_HOME/dsh-prompt-enhancer.deploy.json`(per-profile `{version,source,ts}`,tmp+rename 原子写)——四写入点穷举部署入口:sync-runtime md5 全过后 / installStagedTarball 解包覆盖成功后 / 执行器 installLocal pnpm 路径成功后 / heal xcopy 成功后 guard `--mark` 回写;账本只记事实,方向判断在使用点。②**heal 前置版本守卫**:makeShortcut 生成自包含 ASCII 探针脚本 `executor/cli/dsh-heal-guard.cjs`(自算基线 max(账本, 运行环境现读)、内联 gunzip 版本窥视,壳保持 ASCII 短链),壳 :heal 解包前置判定——**缓存 < 基线拒绝恢复**(陈旧账本不构成放行依据,评审修正③:基线被现读抬高)、账本与现读双空盲区放行(首次安装不砖机)、缓存版本不可读(''/BOM/损坏)拒绝并留痕 `cli/heal-refused.log` 后跳 :minfix 裸救援;紧急旋钮 env `DSH_PE_HEAL_OVERRIDE=1` 放行并留痕;xcopy 成功即回写账本。B-5 单测锚定守卫内联比较器与 maintain-lib.compareSemver 向量一致防漂移。③**维护菜单新增 [5] 一致性自检**(只读五项:缓存 tgz 存在/sha256 匹配旁挂/profile file: 依赖指向存在文件/缓存 ≥ max(账本,现读) 基线/staging 易失引用判定)。④**探针实证落地**:B-4 审计确认 sys.profileDir 为全仓唯一 DSH_HOME 口径分叉点(唯一消费方 readInstalledPluginVersion),统一口径;B-7 readInstalledPluginVersion strip BOM(BOM 不再压低守卫基线);**B-6 实锤推翻 v4.8「搬迁根治」注释语义**——官方 `dsh plugin add ` 写入 file:<安装时绝对原路径> 不落自有缓存,rename 搬迁后照样悬空,据此把「指向 executor/staging 的存在依赖」定为自检 WARN 易失项、持久缓存为唯一可靠恢复源。测试:新增 test/deploy-guard.test.cjs 19 项(账本原子性与注入缝/守卫决策矩阵含账本陈旧核心向量/双空盲区/BOM 拒绝/旁路旋钮/--mark 回写/五项自检夹具),全量 274/274。已实测:npm.cmd test 全量 274/274 全绿(node --test);语法门 node --check、build-host/build-client --check 双侧、地图 check --strict 通过(t3 阶段审计双 PASS)。— [PEN-002](flow: service-and-env)
- **更新器自动链熔断与重启调度防抖(批次A·重启风暴止血·2026-08-25)**:①**显式代理下载**——执行器两处下载漏斗(httpDownload/httpsGetText)经 net-proxy.cjs 第 5 参注入配置 `download.proxy`(启动时读一次,容错;显式代理失败降级直连并留痕),SYSTEM 上下文 GitHub 出网不再依赖系统代理注册表(A-1 探针实测 socks5 显式代理 200 JSON);②**失败退避状态文件** `$DSH_HOME/dsh-prompt-enhancer.update-state.json`(指数退避 60s×2^n 封顶 30min + 每日上限 6 次,tmp+rename 原子写;staged 成功清零)——apply 的 stage/verify/异常失败回写;③**kill-switch 总开关**:env `DSH_PE_AUTO_UPDATE=0` ∥ config `update.autoUpdate=false` 停掉全部自动链(手动操作豁免);④**自动链两入口闸门**:client 自愈链 `update/portRestart` 改携 `auto:true`(rpc-schema 宽松可选布尔,host `autoGateDecision` 拒绝 AUTO_DISABLED/BACKOFF_WAITING 并带剩余秒数),执行器 `rollbackToVersion` 重装下载前退避闸命中即弃(评审 P0-1①;apply 保持手动豁免);client 收到闸拦截时状态行显示原因并停止本轮自动重试、不弹错误横幅;⑤**scheduleServiceRestart 双闸防抖**:进程内时间戳 + `{pendingRestartAt, restartScript}` 文件记录双闸(5 分钟窗口参数化),命中返回 `debounced:true`(client 按 updRsDebounced「已排队」展示);先判后写不产生新脚本;禁止「命中即清理」保住 120s 兜底触发,旧脚本仅两条安全路径清理(过期写入点 / janitor 菜单);⑥**exec-* 计划任务残留三连修**:onListen 自删 pipe 留痕+10s 重试、宿主 spawnExecutor 成功后 60s 延迟清扫、维护菜单新增 4「任务清扫」(>24h 孤儿 XML 备份核验非空再删+全表复查);⑦env 注入补 `DSH_HOME`(自定义 DSH_HOME 用户机执行器目录解析隐患)。测试:新增 test/update-gate.test.cjs 26 项(退避矩阵/闸门四态/防抖先判后写/原子写中途失败/rollback 两分支/janitor 备份删除复查闭环),全量 255/255。已实测:npm.cmd test 全量 255/255 全绿(node --test);语法门 node --check、build-host/build-client --check 双侧、地图 check --strict 通过(t3 阶段审计双 PASS)。— [PEN-002](flow: one-click-update / port-restart)
- **package.json 描述乱码修复(第三方市场分发面核查发现·2026-08-24)**:v3.3.2 description 中 ✨/🎤 两处 emoji 为 GBK 化乱码字符(鉁?=U+9241、馃挰=U+9983 U+6330,UTF-8→GBK 解码后回写的编码事故),已逐字恢复至与 v3.3.1 一致的正确文本。该字段随 npm pack 进入发布物、并被外部插件目录作为元数据源读取;不影响运行时行为与构建产物(构建仅注入 version)。map/flow 不涉及(纯发布物元数据)。已实测:JSON.parse 校验通过 + 与 v3.3.1 description 逐字比对一致。

## [3.3.2] - 2026-08-24

### Changed
- **环境检测 unsupported-platform 态接入文案(只读审查补漏·2026-08-24)**:lib/sys.cjs 在非 win32/linux 平台返回 detail='unsupported-platform',client ENV_DETAIL_TEXT 无映射导致界面直显英文原文;i18n 新增 envUnsupportedPlatform(ZH/EN 成对)并纳入映射表。— [PEN-002](flow: service-and-env)
- **i18n 死文案清理 32 键(只读审查驱动·2026-08-24)**:移除四组无任何运行时引用路径的遗留键(ZH/EN 同步,329→298 对)——①语音旧版标签群 21 键(开关自述式改版遗留)②envService/envSvc* 8 键(v3.2.1-v 服务检测三项删除遗留)③svcInstallTitle/svcInstallSkip(服务化引导弹窗改内联行遗留)④cfgFallbackNote。全量键面审计:静态引用零缺键、动态拼接族(stage*/stageDetail*/modeLabel*/modeShort*/cfgModeHint*/tpl*)与 host 下发值逐一闭环;构建漂移检测通过、产物 grep 零残留、单测 221/221。— [PEN-002](flow: voice-input / service-and-env)
- **卸载 nssm 兜底加确认门(用户需求·2026-08-23 实测「端口修复把服务卸了」驱动)**:`degradeNssmService`(恢复阶梯穷尽/配置性死亡的唯一破坏性动作)执行 `sc delete` 前必须过 `confirmNssmUninstall` 确认门——交互控制台 y/N 定时提问(30s 无响应=**默认保留**);stdin 非 TTY(`--run N` 非交互)默认拒绝;自动化显式放行 `DSH_MAINT_ALLOW_UNINSTALL=1`;单测缝 `confirmImpl`。拒绝/超时均保留服务并留痕 web-port-recovery.log(uninstall declined by user),照走前台冷启兜底,服务可随时经设置页「服务化安装」重装。覆盖桌面快捷方式 [1]/[2] 与维护菜单 6 全部降级路径(含 P0 跳级)。测试:MAINT-25d/i/k 补 confirmImpl 放行 + 新增 MAINT-25n/o/p 三用例,全量 215/215。— [PEN-002](flow: service-and-env)
- **设置页端口重启过程提醒阶段化(用户需求·找回 v2.9.x 时代的详细变动反馈)**:v3.2.1 独立化后轮询期恒为单一「正在重启端口(Xs)」,现按阶段切换——①准备(RPC 应答前,含 staging 安装窗口)②host 应答消息直显(如「服务模式·nssm 存在,重启服务 dsh-web」)+ `serviceMode` 兜底文案 ③sawDown 后「旧实例已停止,等待新实例监听…」④完成「✓ 端口重启完成(Xs)」。i18n 新增 updRs* 四键(ZH/EN 成对),RPC 契约零变化(仅消费既有 message/serviceMode 字段)。— [PEN-002](flow: port-restart)
- **桌面快捷方式/维护菜单全程过程提醒 + 计时(用户需求·承接卸载确认门同场实测)**:新增计时层 `createTimedIo`/`runWithTiming`——命令入口(--cli up/repair/update 与维护菜单 1/2/3)统一包装,**每行输出自动带 `[+Xs]` 总耗时戳**,开始打印 `▶ 开始:<功能>`、结束打印 `■ 结束:<功能>(总耗时 Xs)`;长等待原地倒计时(`tick/clearTick`,仅 TTY 刷新、非 TTY 静默):P1 软重启等待/P2 nssm 自动拉起/P3 末次/服务启动 15s 窗口、宽限二次探测 3s、前台冷启 45s、P1 停止轮询与卸载删除确认 ≤5s、G11 版本自检 30s 全部逐秒显示「标签… Xs/Ys」。测试 MAINT-25q(戳/收尾)/25r(waitWebReady 倒计时:立即就绪零打扰 + 死端口超时路径),全量 217/217。顺带修正头部陈旧用法注释(六项菜单→三功能)。— [PEN-002](flow: service-and-env)
- **[1] 启动 Web 运行中询问是否原地重启(用户需求·v4.10)**:健康分支不再「直接返回」——端口已有 DSH 家族监听时经 `confirmRestartRunning` 确认门(TTY y/N 定时提问 30s 无响应=**保持现状**;非 TTY/`--run` 自动保持;单测缝 `restartConfirmImpl`)。确认后复用 CLI 侧现成 `restartCore` 原地重启:**优先 3081 独立执行器**(SYSTEM 权限 sc 重启 + status 轮询进度行),执行器未运行才本进程 sc 兜底——成功 `RESTARTED`、失败 `RESTART_FAILED` 并指向 [2] 端口修复;拒绝则 `ALREADY_UP` + 打开网页(--open)。通用定时提问抽取 `askYesNoTimed`(卸载确认门同步复用)。测试 MAINT-25s/t/u 三用例,全量 220/220。— [PEN-002](flow: service-and-env)
- **语音设置开关按钮化·自述式(用户需求·二次修订)**:「自动增强」由 开/关 MarqueeSelect 下拉改为 **ToggleChip 自述式切换按钮——按钮直显「自动增强开/自动增强关」,无前置标签**;紧挨同排新增同款「**静音监测开/静音监测关**」(原「静音自动停」更名,i18n 新增 `voiceVadShort` ZH/EN)——读写既有 `vad.enabled` 配置(mic-button 录音链路消费点),行1 调整为 `auto auto auto 1fr` 四列。样式对齐既有控件:继承 `.dsh-plg-btn` 圆角矩形(8px),仅保留两态配色。纯 client 改动,RPC/host 零变化。— [PEN-002](flow: voice-input)
- **优化参数页模式默认可见性修复(用户反馈)**:①`clampParamDisplay` 未设值/越界时回退「**当前模式默认档**」(`MODE_PARAMS_DEFAULT[cfg.mode]`),修复旧逻辑回退档位表首元素 `0`=「无限制」的误导显示;②三项参数下方新增提示行「当前模式默认:超时 {t} · Token {k} · 输出上限 {o}」(i18n `cfgParamsModeDefault` ZH/EN)。base/lite/standard 默认同档为实测结论,smart/publish 独立更高默认自此显式可见。— [PEN-002](flow: prompt-enhance)
- **端口重启·快捷方式链路加固(2026-08-24 双事件实测驱动)**:「市场装插件→重启成功但页崩」+「[1] 确认重启 STOP_FAILED 死循环」。`restartService` 三处升级——①**非管理员预检**:win32 未提权不再盲等 20s,立即 `NOT_ADMIN` 早失败并给出三出路(管理员运行快捷方式 / 设置页 SYSTEM 任务链重启 / 维护菜单5救援);安全门置于一切副作用之前——本函数在服务不存在时会读**真实进程索引**做进程级重启,早退必须最前;②**停止失败强制升级**:首停 20s 与每轮 stop 失败各升级一次 `taskkill /T` 服务进程树(P2 同款),仍不停才判 STOP_FAILED;③**双实例取证日志**:start 时同时记录服务 PID 与端口监听者 PID,不一致标 MISMATCH。`repairWebPort` 入口加横幅消除与 [1] 输出歧义。设置页「端口重启」完成回调新增**插件加载健康嗅探**:fetch 首页检测「Failed to load plugins」→ 显式警告 `updRsPluginLoadWarn`(根因案例:dsh-web-search-pro@0.1.9 注册 settings.plugin.item 只传 key 缺 `options.id`,新版核心 list 槽强校验——机械成功≠可用)。测试 MAINT-25w(NOT_ADMIN 早失败 <3s)。— [PEN-002](flow: service-and-env)
- **输入条右侧三控件零间距紧贴(用户需求·方案确认后实施)**:✨增强 / 🎤语音(含动态声波条 idle 外观,保持不变)/ 模型选择器三者零间距相邻——给 `.dsh-enh-btn` 与 `.dsh-vi-btn` 各加 `-12px` 右边距抵消核心 trailing 容器的 flex gap,并移除 `.dsh-vi-btn` 遗留 `margin-left:-8px`(旧间距调参残留,零间距下会压叠 ✨)。无头 Chrome CDP 实测:✨右缘=🎤左缘、🎤右缘=选择器根盒左缘(flush 双 true)。锚定本插件自有类名,核心哈希类更新不受影响。另做**光学对齐**:声波条整体上移并经三轮实测微调最终 `translateY(-1px)`(条底位于文字基线下方 0.5px 的视觉平衡点;Canvas TextMetrics 实测)——初版居中态观感「字高图低」、-2px 版略偏高,按用户逐轮反馈收敛;同时条子不透明度 0.6→0.8(用户反馈颜色偏淡)。— [PEN-002](flow: voice-input / prompt-enhance)
- **增强过程/结果与会话切换解耦(用户需求·「切走再回来就没了」修复)**:①**切走不再取消在途优化**——移除组件卸载副作用里的 cancel+清空,优化后台继续完成,回到原会话即见结果与「可撤回」态;②**完成回调按活动会话守卫注入**:全局登记当前活动会话(`setActiveSession`),用户已切走时只暂存结果不写草稿(杜绝 A 的结果串进 B 的输入框),回归时由恢复效应自动补应用;③**草稿被还原自动重应用**:返回时草稿=优化前原文(服务端回灌场景)→ 自动重新注入增强结果,仅当草稿被真实编辑为其他内容才按原语义消费清理;④**localStorage 结果持久化**(`dsh-enh-result:`):整页刷新后仍恢复「可撤回」结果态。纯 client 改动,RPC/host 零变化。— [PEN-002](flow: prompt-enhance)

### Added
- **治理加固四件套(「优化完成但草稿未替换」事故驱动)**:①`scripts/enhance-verify.mjs` L2 浏览器端到端门禁(零依赖 CDP 直连 headless Chrome:填草稿→点✨→**断言草稿被替换**→撤回还原,真实模型调用);②`scripts/dead-code-gate.mjs` 提交前死代码门禁(R1 diff 新声明必须全仓有调用点 / R2 client chunk 未声明标识符扫描,支持 --range/--allow,updater-card 字符串密集文件降级 WARN);③client 完成回调行为级单测 `test/client-enhance-flow.test.cjs` 8 项(写回/暂存/丢弃/失败/取消五路 + button/bar 接线契约断言);④CHANGELOG「已实测」标注惯例(本条起执行)。已实测:gate 合成红样 FAIL+默认绿;flow 单测 8/8;模板 E2E `.internal/e2e-main.cjs` 35 断言绿;L2 真机 `verify:enhance` 全绿(writeback+undo);全量 229/229。— [PEN-002](flow: prompt-enhance)
- **单快捷方式「DSH Web」·三功能全自动(用户需求·最终版)**:桌面一枚「DSH Web」——`[1] Start Web / [2] Repair port / [3] One-click update`,`choice /t 3 /d 1` 三秒默认启动。**壳纯 ASCII**(UTF-8 中文+chcp 是 cmd 解析错位把行片段当命令执行的乱码根因,实测抓出);**零用户决策**:[1]=ensureWebUp 恢复阶梯自动自愈;[2]=`--cli repair` 自动清理占用者(系统关键进程保护名单 lsass/svchost 等绝不击杀)后重新拉起;[3]=`--cli update` 安装→干跑闸门(失败自动快照回滚)→杀旧实例→重启生效→一致性自检,版本方向全部自动继续并打印决策行;维护菜单同步瘦身为同三项。全局唯一语义=管理 Web 3080(桌面宿主创建的也是它)。— [PEN-002]
- **nssm 异常自愈阶梯(v4.6·卸载仅兜底)**:端口异常时先穷尽恢复手段——P0 配置体检(Application 二进制缺失=配置性死亡取证跳级)→ P1 软重启(stop≤5s+start+等15s)→ P2 强停僵尸树借力 nssm AppExit 自动拉起(等15s)→ P3 末次一发(≤28s,StartLimitBurst 式预算硬顶)→ 全穷尽且管理员才 `sc delete` 降级回归默认前台(留痕 web-port-recovery.log,设置页可重装=可逆)。DISABLED 服务视为机器显式配置永不自动卸载;权限类失败绝不卸载;宽限二次探测防误杀慢启服务(R1)。时间预算:典型成功 <25s,最坏全失败链 ≈2 分钟。并发闸 `up.lock`(pid 探活+同进程重入同样拒绝)杜绝双击双实例抢 3080/索引双写。单测矩阵 MAINT-25a~25m 十三项全覆盖(含 resolve→delete→spawn 顺序断言)。— [PEN-002]
- **端口原语单一事实源(B1)**:四处重复的 netstat/taskkill/pid 探测实现收编 `lib/sys.cjs` 五个自包含原语函数(dshPrimNetstat/PortHolder/TaskKill/PidImage/PidHasListening),并经 `scriptPortPrims()` 以源码块发射嵌入生成式重启脚本(独立 node 进程无法 require,嵌源码是唯一共享形态);port/service 两类重启脚本与维护菜单共用同一探测/击杀语义。— [PEN-002]
- **Web 维护子命令(A4·G4 定案)**:`updater-host.cjs --cli maintain` 五项菜单——①立即重启(staging 提示 + G11 重启后一致性自检)②应用 staged 更新(G5 版本方向门 + G9 干跑双层 + 失败自动快照回滚)③端口占用修复(G6 进程家族自动杀/异族确认 + 冷启动分支)④环境审计(probeEnv + bundles 基座/第三方/patch-disabled 分层标注 + @dsh-external 与 link: 依赖可见性)⑤故障救援五步;支持 `--run N` 非交互单项模式;io 注入可测。CLI 重启脚本(~190 行生成式)随之退役——updater-host.cjs 本身即部署态静态文件,快捷方式降级为逻辑-free `.cmd` 启动器「DSH Web 维护」。— [PEN-002]
- **救援五步全流程(A4·G12-G18)**:只读诊断(nssm 日志注册表发现 → 元凶扫描 → 指认宿主包,指认名≠禁用名)→ 无条件快照 → 三档处置(精准禁用[patch disabled+bundles 移除双保险、依赖声明保留]/快照回退[G16 脏候选预检+逐候选干跑验证自动退更旧]/二分定位[判定代数:通过⟺嫌疑⊂禁用集,≤6 轮秒级])→ 干跑冒烟闸门(G14)→ 验证拉起(sc start→waitWebReady→前台兜底);rescue-report.json 附恢复指引;EXECUTOR_ROOT/rescue.lock 互斥锁(活 pid 拒绝/死 pid 或 >10min 陈旧接管)。红线:pnpm rebuild 绝不代跑(冻结 host 事件循环),仅入报告指引。— [PEN-002]
- **干跑三层 CJS 版 + 装配面工具(maintain-lib.cjs)**:dumpCompose/resolveProbe/dryRunAll(救援运行态只有 lib/ 无 scripts/,故 CJS 单一事实源 + scripts/dry-run-lib.mjs ESM 薄委托);profile bundle/patch 读写(静态 `disabled:true`,防 !!js 恒 truthy);nssm 日志发现与元凶指认;compareSemver/decideUpdateAction 版本方向决策。dryRunAll 支持 extraNames 补录无 scope 裸包名(extractEntryNames 保守口径只收含 '/' 名字,宁漏勿误杀好安装)。— [PEN-002]
- **staging 安装抽取面(stage-install.cjs)**:installStagedTarball/findStagedTarball/peekTarballVersion 从 index.cjs 抽出(裸 require index.cjs 有 20s 进程索引副作用,CLI 不能复用)——index.cjs 经模块委托保持同一实现(单测断言函数引用相等防分叉)。— [PEN-002]
- **救援演练锚点(test/a4-drill.cjs)**:三档真实链路演练——隔离 DSH_HOME + 假 dsh bin(DSH_BIN/DSH_BIN_NODE_SCRIPT 注入,绕开 spawnSync .cmd EINVAL)+ bringUpImpl/logPathsOverride 注入绝不触真实服务;精准禁用→闸门转绿→报告齐备 / 二分收敛元凶不误伤 / 快照回退含 home patch 忠实删除。— [PEN-002]

### Fixed
- **本地引擎 workerPort 缓存主动失效(v4.8 已知限制收口)**:`transcribeLocal` 传输级失败(worker 不可达/端口已变)时立即清空端口缓存并重探——此前依赖 60s TTL 被动过期,动态口 worker 重挂后最长 1 分钟内识别必失败。导出 `clearWorkerPortCache` 供复用。host lib 改动,需重启 DSH 进程生效。已实测:语法门、全量 229/229、部署校验。— [VOICE-003](flow: voice-input)
- **语音设置状态显示即时刷新(用户反馈)**:设置页「本地引擎 ● 就绪」等检查信息此前仅在挂载与部署完成后更新,改动设置后停留旧值。现挂 `subscribeVoiceConfig` 通知——任何 config/set(引擎切换/协议/接口地址/规整配置等)经 notifyVoice 触发 **350ms 防抖自动重查 voice/status**;首次触发跳过避免与挂载检测重复;模型切换因 worker 异步重启,完成后延迟 2.5s 复查。纯 client 改动,强刷生效。已实测:构建漂移零、全量 229/229、sync-runtime 部署校验通过。— [VOICE-003](flow: voice-input)
- **「优化完成但草稿未替换」修复(用户实测·f6fa822 半成品接线补全)**:会话切换增强保持存在四处断线——①`setActiveSession` 定义后从未调用(activeSessionId 恒 null → 完成回调恒判「已切走」→ 结果只暂存不写草稿,即本症状);②恢复效应引用未声明的 `inputActionsRef`(执行即 ReferenceError);③`saveResultStore` 零调用(localStorage 持久化为死代码);④EnhanceBar 卸载副作用残留 v21 cancel 与「后台继续」冲突,且消费效应对「暂存未写回态(draft===backup)」误清。补全:按钮挂载登记活动会话(cleanup 仅自毁)、声明 actions ref、成功/取消/丢弃三路持久化接线、bar 卸载对齐仅回收空闲、消费效应加 backup 豁免。验证:全量 221/221、构建漂移零;新增模板端到端实测 `.internal/e2e-main.cjs` 35 断言全绿(5 模式 T1/T2+纪律层出站报文逐字一致、检索窗口分发 lite1/standard3/smart2+意向1、命中注入参考块+REFERENCE_GUIDE、template/default 目录与 skills/enhance 事实源一致)。— [PEN-002](flow: prompt-enhance)
- **桌面快捷方式·审查二次修正(团队复审 M1/M2/低项清偿)**:① 显式下载代理请求失败即置降级标记——后续候选源(hf-mirror 等)自动回退直连,修正原实现"显式代理失败永不降级"与注释矛盾(asr-models.cjs);② 裸救援 `:minfix` 权限边界如实化——非提权会话预检提示、30s 失败按权限分支给指引(右键管理员重试),并移除 `sc config start= auto` 的持久副作用;③ 菜单壳全部提示行 `[!]`→`[x]`(延迟展开上下文吞 `!` 实测复现),契约测试新增断言(自愈分支在位 / 全 body 禁 ASCII `[!]`);④ stage tgz 搬迁跨卷 EXDEV 兜底改为复制+删源——杜绝删除失败路径复活 file: 悬空地雷;⑤ ECONNREFUSED 分诊独立归类(目标端口拒绝连接 ≠ 网络受限);⑥ `:heal` TGZDIR 支持 DSH_HOME 口径(与运行时一致);⑦ `:upFail/:updfail` 大小写消歧留档(Win11 实测精确匹配优先、路由正确,仅建议改名)。全量测试 212/212。— [PEN-002]
- **语音·模型下载三源架构 + 显式下载代理 + 失败分诊(审查修正)**:实测 CN 网络下 HF 官方遭 DNS 污染、hf-mirror 间歇不可达,双源同时失效即下载全灭。① `files[].mirrors` 数组支持任意额外源(ModelScope 验证后一行接入);② 新增**显式下载代理**:配置文件顶层 `download.proxy`(http(s)://或 socks5(h)://,空=跟随系统代理),`net-proxy.httpsGetProxied` 增第 5 参优先注入——覆盖"代理软件运行但系统代理开关关闭"的主流形态;③ 失败自动分诊附可行动建议(DNS 解析/连接超时/被重置/HTTP 4xx 分类映射),经 modelProgress.error 透传 UI。显式代理链路模块级实测通过。— [VOICE-003]
- **部署链 UTF-8 BOM 根因根治(2026-08-23 端口重启事故闭环)**:全仓扫描发现 5 个文件带 UTF-8 BOM——package.json / scripts/build-client.mjs / src/client/skeleton.js 及两个组件。sync-runtime 原样部署使 DSH 启动解析 bundle 清单时 `JSON.parse` 直接崩溃("端口重启后服务起不来"事故根因;应急会话仅清理了运行时副本,未触及仓库源头,故 BOM 会随每次部署复发)。已全部剥离并三重防复发:① PKG-BOM 回归测试守住 package.json;② `readPluginVersion` 容错 BOM(解析异常不再回退构建常量);③ sync-runtime 对部署文本产物自动去 BOM。重建后全量测试 212/212。— [PEN-002]
- **桌面快捷方式·自愈壳 + 更新器 tgz 持久化(审查修正)**:背景——dsh-market 更新触发 profile pnpm 操作,`file:` 依赖悬空(staging tgz 装完即删)致整包从 node_modules 清除,快捷方式三功能全灭 MODULE_NOT_FOUND。① 壳启动预检运行时目标,缺失自动从持久插件包缓存(profiles/web/plugins/dsh-prompt-enhancer-tgz/ 最新 tgz,内置 tar 解包,离线可用)恢复后正常进菜单;② 更新器安装成功后 tgz 不再删除而是连同 .sha256 搬迁到该持久缓存——file: 依赖永不悬空、自愈源常在;③ **裸救援**:heal 失败(无 tgz/解包失败)自动降级纯系统命令最小端口救援——杀 3080 占用者(lsass/svchost 等系统关键进程保护名单)+ `sc config/start dsh-web` + 30s 轮询确认,插件整包被清时仍可救活网页核心;④ 无 runtimeRoot 的契约调用保持旧体不破坏断言。双态生成(heal/legacy)实解析验证,全量测试 211/211。— [PEN-002]
- **语音·本地引擎 worker 生命周期断链修复(审查修正)**:worker 3082 被第三方占用时 fallback 动态口并写 `worker.port`,但 host 侧三处消费(isWorkerUp/ensureWorker/restartWorker)只认固定 3082——动态口 worker 对存活判定隐身,导致 ensureWorker 每次 host 启动重复 spawn(node 进程堆积 + worker.port 竞态覆盖)、restartWorker 杀不掉动态旧进程。现新增统一发现 `workerListenerPids()`(3082 + 文件口双通道、netstat 匹配由 `:3082` 子串收紧为锚定 `\s` 防 :30820 误杀),三处消费共用;`workerPort()` 文件口缓存加 60s TTL——动态 worker 换口后旧 host 进程 60s 内自愈。全量测试 211/211。— [VOICE-003]
- **桌面快捷方式·三处审查修正**:① 真实桌面路径改经 Shell SpecialFolders 探测(OneDrive 同步/域策略重定向桌面时 `.lnk` 不再写进错误位置;cscript 探测失败回退 `USERPROFILE\Desktop` 旧路径);② 菜单体引号转义改 cmd 惯例双写 `""`(原反斜杠转义 cmd 不解析,纯防御);③ `[3] 一键更新` 成功(exit 0)pause 后自动关窗——服务已在后台重启完毕无需回菜单,失败走新增 GBK 提示行回菜单。生成菜单体经 GBK 解码逐段验证,全量测试 211/211。— [PEN-002]
- **端口重启·服务模式脚本打磨(审查修正)**:① 生成脚本的 `busy` 忙等待(死循环空烧 CPU ≤60s)改 `Atomics.wait` 同步休眠,等价语义零 CPU 占用(不支持环境自动回退旧实现);② 3080 残留监听者清理加镜像名校验——仅 `node.exe` 视作旧 DSH 家族清理,第三方程序占用时跳过并留痕,与桌面端防误杀加固对齐。生成脚本经实解析验证,全量测试 211/211。— [PEN-002]
- **测试环境自适应化(CI 门禁修复)**:MAINT-24 不再硬编码旧开发机的 `dsh-web=DISABLED`——服务存在时改验 START_TYPE 落在 Windows 已知枚举(本机 AUTO_START 亦绿),未装服务的环境维持跳过;VOICE-P17 的 modelApply 断言按模型安装状态走双向分支(未装→验 `MODEL_NOT_INSTALLED` 拒绝路径,已装→验成功路径),干净机器与 CI runner 不再因缺模型必红。修复后本地 211/211 全绿,push 触发的 CI 门禁恢复有效。— [PEN-002]
- **执行器端口发现(A1·端口红线修复)**:`executor-reloader`(M4 apply 路径)旧实现硬编码 3081 且从不读 `executor.port`——执行器 EADDRINUSE 漂移动态端口后恒 `EXECUTOR_UNREACHABLE`。现按「显式注入 → ping 3081 → 读 executor.port 再 ping」顺序发现真实端口;结果带 60s TTL 缓存,RPC 失联即作废重探(防中途换口缓存失真)。portFile 路径可注入(测试隔离)。— [PEN-002]
- **进程索引污染守卫**:裸 require lib/index.cjs 会执行模块级进程索引写入(实测 20.5s 兜底且死 pid 覆盖真值)——现受 `DSH_ENHANCER_NO_INDEX=1` 环境变量守卫,测试/维护 CLI 复用安全。— [PEN-002]

### Added
- **安装即快照(A5·救援底座)**:改写运行环境的安装动作前置快照到 `$DSH_HOME/rescue/<时间戳>/`——配置四文件(profile 三件套 + pnpm-workspace.yaml + home 级 cordis.patch.yml,官方前三层落盘物)+ 当前运行关键产物(plugin-host.js / lib/*.cjs 坏版本回滚锚点);临时目录 rename 原子落位 + meta.json 清单 + 同秒多快照防撞名;保留最近 10 份自动清理。接入点:staging 安装、sync-runtime 部署。— [PEN-002]
- **三层干跑拦截(A6·安装期爆炸前置)**:① `--dump-config` 组合语法层;② resolve-probe 模块层(解析 dump 输出全部 entry name 从 profile 与 dsh-install 双根 require.resolve——实测单层①拦不住引用不存在包的 patch);③ `node --check` 产物语法层(拦包内损坏)。staging 安装与 sync-runtime 部署前强制过门:坏包在覆盖运行环境**之前**被拒(`STAGED_SYNTAX_CHECK_FAILED`),杜绝「装坏插件打死整个 Web 服务」。— [PEN-002]
- **维护工具链复用导出**:`installStagedTarball/findStagedTarball` 显式导出(批次二 CLI 维护菜单与拦截演练复用同一实现,杜绝逻辑分叉)。— [PEN-002]

### Fixed
- **端口重启双实例隔离三连修(web+Desktop 共享 DSH_HOME)**:① 进程索引写入加 `kind`(web/desktop),`update/portRestart` 读取时 kind 与当前运行时不一致即整体不信任、回退当前进程事实值(pid/execPath/argv 第一手真相)——根治两实例互相覆盖索引导致的误杀对方进程/拉错应用;② staging 安装 profile 在 Desktop 下**无条件强制** `'desktop'`——旧三元优先信任 client 恒传的默认 `'web'`(client 无桌面检测),Desktop「一键更新→端口重启」装进 web profile、重启后版本不变;③ 生成的重启脚本 Desktop 分支杀前 tasklist 镜像名校验(非 DSH Desktop.exe 即中止防误杀)+ CLI 快捷方式脚本优先 kind 校验过的索引 pid 并校验存活镜像(防 tasklist 首个匹配命中 GPU/renderer 子进程)。— [PEN-002]
- **portRestart TDZ ReferenceError 修复**:argv 兜底分支在 `const isDesktop` 声明前引用——桌面端索引 argv 缺失时整个 RPC 抛 `Cannot access 'isDesktop' before initialization`;声明提前至索引读取前。索引缺失同时不再硬失败(原 NO_INDEX → 现回退当前进程参数继续执行)。— [PEN-002]
- **一键更新重试链桌面安全化**:apply 轮询中执行器连续失联的重试动作,由 executor `restart`(读共享进程索引 + 健康检查兜底固定 3080、无桌面守卫的后门路径)改为 host `update/portRestart` RPC——复用桌面适配全链路(kind 校验 + 身份校验 + profile 强制),语义更准(staged 安装+重启本就是 portRestart 职责)。— [PEN-002]

### Changed
- **sync-runtime 多 profile 部署**:默认部署到**所有**安装了本插件的 profile(原单 profile:web 优先、否则 readdir 第一个——双 profile 开发机另一侧永远跑旧代码);DSH_RUNTIME_DIR 仍可限定单目标。— [PEN-002]

### Changed
- **一键更新供应链加固(哈希强校验)**:executor 下载 tgz 后强制过哈希门禁——期望 sha256 取自 GitHub Releases API 资产 digest(主通道,api.github.com TLS 不经镜像)或直连 `.sha256` 发布资产(备用);哈希失配拒绝安装(`STAGE_HASH_MISMATCH`),镜像下载且无可信哈希拒绝(`STAGE_HASH_UNVERIFIED`,fail closed),直连无哈希放行(TLS 可信)。校验通过后在 staging 旁挂 `.sha256`,端口重启安装前二次复验(防下载与安装窗口内被替换)。release.mjs 发布时同步上传 `.sha256` 资产。— [PEN-002]

## [3.3.1] - 2026-08-21

> 🔧 **v3.3.1:语音设置体验完善 + 本地引擎一键部署 + llm 注入修复**

### Added
- **本地引擎运行时一键部署(GitHub #4 方案 A)**:新增 `lib/asr-deploy.cjs` + `voice/deployRuntime` / `voice/deployStatus` RPC——缺失 worker/sherpa-onnx 时设置页显示「部署本地引擎」按钮,一键复制 worker + npm install + 拉起 worker,并轮询部署状态;`voice/status` 增加 runtime 缺失诊断,普通用户无需手动运行部署脚本即可修复本地引擎「已下载却未就绪」。— [VOICE-003]
- **语音总开关**:识别引擎新增 `off` 选项;关闭后隐藏输入框 🎤 按钮与语音子配置;默认引擎改为 `local`(仅新环境生效,已保存配置不受影响)。— [VOICE-003]

### Changed
- **语音设置 UI 精细化**:识别引擎/语言等 7 处原生下拉统一为 MarqueeSelect(内容宽/占满两态),原生 select 增加 `appearance:none` + SVG ▾ 箭头;行1 弹性布局窄屏不再重叠;标签两字化并恢复「打开文件夹」按钮;本地模型行内「语言」下拉与「打开文件夹」按钮对调位置。— [VOICE-003]

### Fixed
- **llm 服务声明为 inject 依赖(GitHub #3)**:host 插件 `inject` 列表由 `['timer']` 补为 `['llm','timer']`——cordis 等待 llm 服务就绪后才激活插件,消除启动竞态下 `ctx.get('llm')` 为 undefined、enhance 永远报「LLM 服务不可用」(NO_LLM) 的问题。— [PEN-002]
- **快捷键清除不立即显示**:空组合键显示逻辑返回 `undefined` 导致受控输入退化为非受控,修复为空串并立即渲染清空。— [VOICE-003]

## [3.3.0] - 2026-08-21

> 🎙️ **v3.3.0 里程碑:语音识别正式发布**——本版本整合 v3.2.5~v3.2.36 全部语音识别迭代(云端/本地双引擎、VAD 静音自停、快捷键唤醒、模型管理)+ 界面精简(用户视角)+ 可靠性修复(host 启动自动拉起 worker)。

### Added
- **语音识别模块(v3.2.9·快捷键唤醒 + 点按/长按双触发,2026-08-20 用户需求)**:新增键盘快捷键(默认 `` ` `` 键,可配置)唤醒语音——**点按** = 开始/再按结束(VAD 静音自动停不变);**长按**(按住 ≥500ms)松开后 0.5s 立即识别。统一 `pressStart`/`pressEnd` 抽象(键盘 keydown/keyup + 鼠标 mousedown/mouseup,mouseup 抑制 click 防双重处理);`getUserMedia` 未就绪时 `longReleaseRef` 兜底(startRec 完成后补触发)。设置「语音识别」段落新增**快捷键唤醒开关 + 组合键录制**(readonly 输入框,聚焦后按组合键即保存、Esc 取消、清除按钮);配置 `voice.hotkey`(enabled/combo,e.code 格式)贯通 host sanitize + state.js 默认值 + i18n 中英文案。— [VOICE-003]
- **语音识别模块(新功能·P1 云端闭环)**:输入框旁新增 🎤 录音按钮——语音 → 云端识别 → 可选规整(去口水词)→ 填入草稿 → 可一键优化。**P1 内容**:① host `lib/asr.cjs`——cloud 双协议 ASR(阿里 Qwen3-ASR `chat/completions+input_audio` 默认 / OpenAI `/audio/transcriptions` 兼容),出网走 `node:https` 直连(通道 C,P0.5 探针实测通过);规整层(refine,OpenAI 兼容,失败自动降级原文);② RPC `voice/status`·`voice/transcribe`(rpc-schema 双份 + data URL 校验 + 30s/15s 超时 + 错误码);③ client `src/client/voice/`——recorder(RecordRTC 16k mono wav,≤60s,仅内存)/ mic-button(状态机 + 双暂存时序防护:优化中/发送期暂存自动填入)/ voice-insert(能力探测 append/insert)/ voice-section(模型配置 tab 段落块,apiKey 仅存磁盘);④ **config/set 升级「顶层键级 merge」**(enhancer+voice 双写入方互相清空修复,向后兼容);⑤ vendor RecordRTC 5.6.2(官方原码只读 + 转义工具 + GENERATED chunk);⑥ 契约测试 voice-persist(12 项)+ config merge(2 项)。— [VOICE-003]
- **语音识别模块(P2·本地离线引擎)**:新增 sherpa-onnx + SenseVoice 本地识别——完全离线、零 key、隐私(音频不出本机)。**P2 内容**:① `lib/asr-worker.cjs` 独立 worker 进程(端口 3082,对齐 updater executor 模式)+ 部署脚本 `scripts/asr-deploy.mjs`(装 sherpa-onnx + 模型校验 + 启动 + 健康检查,清单化防漏复制);② SenseVoice int8 模型(228MB)下载管理(HF 源);③ `lib/asr.cjs` local 引擎分派(worker HTTP 调用)+ `voice/status` local 字段实时探测(installed/modelReady/workerUp);④ 本地识别链路实测通过(正弦波 → "Okay.")。— [VOICE-003]
- **语音识别模块(P3·VAD 静音自动停)**:说完话停顿 1.2s 自动停止录音并进入识别——不再需要手动点停止。**内容**:① recorder 新增 VAD(AudioContext + AnalyserNode 实时 RMS,**EMA 平滑 + 连续静音帧计数**抗底噪抖动——曾因 mp3 底噪偶发帧超阈值致静音计时不断重置);② mic-button 经 `stopRef`(ref 取最新闭包,修复 VAD 异步回调持旧闭包致守卫误判);③ 设置「语音识别」段落新增**静音自动停**开关(默认开启,`voiceCfgState.vad.enabled` + host sanitize 白名单);④ 端到端实测通过(fake-audio-capture 循环播放「语音+2s静音」→ 录音→自动停→识别→回待机)。— [VOICE-003]
- **语音识别模块(规整复用模型配置,2026-08-20 用户需求)**:规整(去口水词)不再强制独立填 baseUrl/apiKey——新增「模型来源」选择:**跟随模型配置**(默认,用提示词增强的模型链 + 基座 llm 服务,免填 key,模型下拉选择链中任意一条,样式对齐模型配置)/ **自定义**(保留独立 OpenAI 兼容配置,补上此前缺失的 API Key 输入)。host:asr.cjs 注入基座 llm(setLlm),chain 模式 llm.stream 收集,空链跳过、无 llm 报 REFINE_NO_LLM、失败降级 raw;status refine.configured 按模式判定。— [VOICE-003]
- **语音识别模块(P3·多语言 + 排队 + 索引时机)**:① **识别语言选择**——SenseVoice 支持 auto/中文/English/日本語/한국어/粤语,设置「语音识别」段落新增语言下拉(`local.language` 配置贯通:state.js 白名单 + host sanitize + worker 语言懒重建 recognizer,切换约 1-2s 单实例);② **暂存排队**——pending(优化中暂存)时 🎤 可再点开始新录音,识别结果入 FIFO 队列,enhance 恢复后**合并一次填入**(`voice-insert.js` 模块级队列 + flush,`draftRef` 实时草稿防逐段覆盖);③ **host 索引写入时机优化**——从「模块加载时立即写」改为「确认 3080 由本进程监听后写 + 20s 兜底」,防 host 初始化崩溃前污染索引为死 pid。— [VOICE-003]
- **语音识别模块(桌面端随机端口适配)**:核查发现语音模块对「DSH Desktop 动态端口」无影响的部分(host RPC 走 DSH 分发、配置走磁盘 config 跨 Origin 共享——Issue #1 方案已覆盖),优化两处真实风险:① **worker 端口动态 fallback**——3082 被占(多 DSH 实例并发)时自动 listen 0 动态换端口并写 `worker.port` 文件(对齐 executor.port 先例),host `workerPort()` 读文件发现实际端口;部署脚本 `--start` 检测 3082 已健康则**复用跳过**(同 DSH_HOME 多实例共享 worker,避免端口冲突);② **host 索引就绪探测泛化**——从「确认 3080 监听」改为「本进程任意 LISTENING」(桌面版随机端口 = 就绪信号),桌面端不再等满 20s 兜底。动态 fallback 已实测(3082 被占 → 56787 + 文件写入 + host 读取 → 恢复 3082 识别正常)。— [VOICE-003]
- **语音识别模块(模型管理 v2·多模型切换 + 自定义模型,2026-08-20)**:① 模型市场扩展——内置 **2 个下载配置**(SenseVoice 多语言 228MB + **Paraformer 中文 137MB**,各带 `type` 供 worker 按类型加载,新增模型只改 `VOICE_MODELS`);② **自定义模型自动识别**——`modelList` 扫描 `models/` 子目录(含 `.onnx` + `tokens.txt` 即列为可用模型,可选 `model.json` 声明名称/类型),用户经「打开模型文件夹」放入第三方社区模型即被检测;③ worker **类型化加载 + 自动探测**(`DSH_ASR_MODEL_TYPE`/`model.json` 指定,否则按 sense-voice→paraformer→whisper 依次尝试创建 recognizer,首次成功即用);④ 新 RPC `voice/modelApply`(切换当前模型→重启 worker 加载)·`voice/modelOpenDir`(explorer 打开模型文件夹);⑤ 设置模型区:**「设为当前」切换**(高亮当前模型)+ 自定义标记 + 「打开模型文件夹」按钮。— [VOICE-003]
- **语音识别模块(模型管理框架·插件精简·用户需求 2026-08-20)**:**插件只提供框架接口,不携带/默认下载模型**(发布物 `files` 不含模型,安装即用零下载负担)——本地引擎模型由**用户在设置页选择下载**:① 新增 `lib/asr-models.cjs`(模型清单 `VOICE_MODELS` 单一事实源:SenseVoice-zh-en-ja-ko-yue 228MB,新增模型在此追加 UI/下载/worker 自动跟随)+ **node:https 流式下载**(通道 C)+ 下载进度内存 Map;② 新 RPC `voice/modelList`·`voice/modelDownload`·`voice/modelProgress`(rpc-schema 双份 + 契约测试,已安装/下载中幂等);③ worker 模型目录**参数化**(`DSH_ASR_MODEL` 环境变量 → `models//`),下载完成**自动重启 worker** 热就绪;④ 设置「语音识别」local 分支改**模型管理区**(模型列表 + 下载按钮 + 进度 % 轮询 2s + 已安装 ✓/失败提示);⑤ `local.model` 配置语义改模型 id(`sense-voice`,旧值 `sensevoice-q8` 兼容映射)。— [VOICE-003]

### Fixed
- **语音快捷键功能三处缺陷(v3.2.9 自测修复)**:① `state.js`(字符串模板文件)把换行误写 `\\n` 双反斜杠 → 构建产物经 `new Function(JSON.stringify(body))` 包装后还原出字面 `\n` → 页面报 `Invalid or unexpected token` 插件加载失败——7 处改回单反斜杠;② `mic-button.js` `useState` 写 `const [, setCfgVer]` 忽略首值却引用 `[cfgVer]` 依赖数组 → `ReferenceError: cfgVer is not defined`——取出 cfgVer;③ `pressStart`/`pressEnd` 状态限制仅允许 idle/recording → 识别失败(error 态)后按键被忽略无法重试——放开 error 态。浏览器 e2e 13/13 通过(fake 麦克风 + 设置页录制链路)。— [VOICE-003]
- **优化失败报错信息不明确(用户反馈)**:「优化失败:优化失败」泛化提示——根因:模型全不可达时 host 返回 `ALL_MODELS_UNAVAILABLE`/`STREAM_THROW`,而 client `errorKey` 无此映射 → 落 `errUNKNOWN`(无信息量)。修复:errorKey 补 2 映射 + i18n 新键——`errAllModels`(所有模型连接失败,请检查模型配置)/ `errModelUnreachable`(模型连接失败,网络或配置错误),ZH/EN 成对。— [PEN-002]
- **「恢复默认配置」描述与行为对齐(用户反馈)**:cfgRestoreNote 描述说「不清除自定义模板/参数/记忆」被误读为「不删自定义模型」——行为(模型链重置为官方默认)正确,**描述补充「自定义模型会被替换」**(恢复默认语义即重置模型链),中英文案同步。— [PEN-002]
- **添加模型默认位置(用户需求)**:设置「+ 添加模型」从追加末尾改为**默认插入第一个位置**(`unshift`)——新模型即为主用模型(模型链按序尝试)。— [PEN-002]
- **优化按钮模型序号改为「设置链序号」(用户需求)**:按钮显示的「{n}正在优化」数字改为**用户模型链(设置里)的对应序号**——host 侧 `buildTryChain` 会去重/过滤导致 chain 索引与配置偏移,现在 llm 阶段按 fallback 原始数组匹配当前模型位置(第 N 条即显示 N,自动随设置数量 1..N);进度 total 同步为设置模型总数。— [PEN-002]

### Changed
- **设置页语音识别段落界面精简(v3.3.0,2026-08-21 用户指令「界面精简·用户视角·只展示对用户有效信息」)**:删除两个开发者向控件——① **「打开模型文件夹」按钮**(放第三方模型的开发者入口,普通用户不需要);② **「检测状态」按钮**(挂载时已自动检测 + v3.2.36 worker 自动拉起,手动入口冗余)——状态行保留实时状态显示(本地引擎 ● 就绪 / 云端就绪 / 未配置提示)。i18n 键保留(ZH/EN 平衡不受影响)。**验证**:voice-section 内层 SYNTAX_OK + 构建产物(lib/client.cjs / plugin-client.js)含精简后代码 + git diff 确认按钮移除。— [VOICE-003]
- **host 启动自动拉起本地 ASR worker(v3.2.36,2026-08-21 用户重启电脑后报「本地引擎未就绪」)**:根因 = worker 只在「模型下载完成 / `voice/modelApply`」时拉起,**host 启动无自动拉起逻辑** → 重启电脑后 3082 无监听 → 本地引擎未就绪(需手动切一次模型才恢复)。修复(asr-models.cjs + index.cjs):新增 `ensureWorker()` —— host apply 延迟 **5s** 调用,读磁盘配置(`voice.asr.engine` 非 local 跳过 / `local.model` 或默认 `sense-voice` / 模型未安装跳过 / 3082 已监听跳过),否则 `restartWorker(modelId, type)`(detached 异步加载,不阻塞 host);幂等。**验证**:node 直调两分支(在跑→`skipped:worker-already-up`;杀进程→`launched:true` 且 3s 后 3082 LISTENING)+ 语法 OK + 单测 192(191/0)+ 端到端:杀 worker 后重启 web → 7s 自动拉起 + `voice/status` 全绿(workerUp=true/modelReady=true)+ 地图无漂移。**生效**:web 重启;Harness/Desktop 重启。— [VOICE-003]
- **VAD 自适应底噪校准(v3.2.35,2026-08-21 用户质疑「会不会把环境噪音当成说话导致静音自停失效」→ 确认风险后实施)**:原固定阈值 `VAD_THRESHOLD=0.025` 不感知环境——空调/风扇(RMS 0.02~0.04)临界、马路/嘈杂(>0.04)大概率把持续噪音当说话 → `heardSpeech` 恒真 → 2s 静音计数永不达标 → 静音自停失效(仅剩 60s 超时兜底)。**自适应校准**(recorder.js):录音开始前 **500ms(5 帧 @100ms)采样环境底噪**(只统计 RMS 不判定,防说话前静音误停),取**中位数**作底噪基线(抗瞬时尖峰/采样混入语音),阈值 = `min(0.2, max(0.025, 底噪中位数 × 2.5))`——安静环境阈值不变(0.025 保灵敏度)、空调环境抬到 0.075、嘈杂环境 0.125、采样混入大噪声封顶 0.2;每轮录音独立校准,校准完成后才进入 heardSpeech/静音计数判定。**新增常量**:`VAD_CALIBRATE_FRAMES=5` / `VAD_NOISE_FACTOR=2.5` / `VAD_THRESHOLD_MAX=0.2`;判定从 `VAD_THRESHOLD` 改用运行时 `vadThreshold`。**验证**:recorder 内层 SYNTAX_OK + 常量断言 + 4 场景阈值计算模拟 PASS(安静 0.025/空调 0.075/嘈杂 0.125/封顶 0.2)+ 单测 192(191/0)+ 浏览器 e2e 页面 200 / rev ec3d8f19 / JS 错误 0 + 地图无漂移。— [VOICE-003]
- **开始/停止逻辑核查 + VAD 静音 1.2s→2s(v3.2.34,2026-08-21 用户设定逻辑核对 + 调整)**:① **逻辑一致性核查(对照用户设定,全部一致)**——快捷键点按/按钮点按统一走 `pressStart`/`pressEnd` 抽象(按钮 mouseup 以 `suppressClickRef` 抑制 click 防双重):点按(<500ms)只开始录音、不主动停,靠 **VAD 静音自停**(首次语音后静音持续 → `onAutoStop` → 立即识别);快捷键长按(≥500ms)按住持续录音、松开 → `stopAndTranscribe(VOICE_RELEASE_BUFFER_MS=500)` → 0.5s 缓冲后识别;按钮与快捷键点按路径一致 ✓。② **VAD 静音时长**——用户以为 2s,**实际是 `VAD_SILENCE_MS = 1200`(1.2s)**,如实汇报后按用户决定调整到 **2000ms(2s)**(`recorder.js` 常量 + 注释;`VAD_REQUIRED_QUIET = round(2000/100) = 20` 帧自动推算)。**验证**:recorder 内层 SYNTAX_OK + 常量/推算断言 + 单测 192(191/0)+ 浏览器 e2e 页面 200 / rev b081f5f4 / JS 错误 0 + 地图无漂移。— [VOICE-003]
- **语音默认值调整:快捷键默认空(v3.2.33,2026-08-21 用户需求「自动增强默认关闭,快捷键的设置默认是空的,清理按钮是清理空白的意思」)**:① **自动增强**——`autoEnhance` 默认 `false` 已是 v3.2.17 起的既定默认,无需改(识别完成不自动触发增强);② **快捷键默认空**——client `VOICE_CFG_DEFAULTS.hotkey` 从 `{enabled: true, combo: 'Backquote'}` 改为 `{enabled: false, combo: ''}`;`mergeVoice`/host `sanitizeVoiceCfg` 的 hotkey 启用判定从「非 false 即开」改为「**显式 true 才开**」(`hk.enabled === true`)+ combo 非法/缺省回退从 `'Backquote'` 改为 `''`——全新安装/重置后快捷键唤醒关闭、输入框显示「未设置」placeholder;**已保存显式配置(如 `{enabled:true, combo:'Ctrl+Shift+Backquote'}`)原样保留**;③ **清理按钮**(快捷键框旁「清除」`voiceHotkeyClear`)行为 = 清空成空白(`saveVoiceCfg({hotkey:{enabled:false, combo:''}})`),现状已如此,无需改。**联动确认**:mic-button 快捷键监听 `!enabled || !combo` 即不挂监听,默认空后不误触发;idle hover 提示只在 enabled+combo 存在时追加。**验证**:client state.js 内层(vm.compileFunction)+ host 双端语义断言(默认 false/''、显式启用保留、显式禁用、非法回退空)+ 单测 192(191/0,VOICE-P19 断言同步默认关/空)+ 浏览器 e2e 页面 200 / rev d039724f / JS 错误 0 + 地图无漂移。**注意**:用户机器 localStorage 若已存旧配置(Backquote),显式值保留不强行清除——需要时点「清除」按钮置空。— [VOICE-003]
- **录音计时显示隐藏(v3.2.32,2026-08-21 用户需求「不要录音计时 / 隐藏了不要显示就行,后面可能会需要」)**:recording 状态不再显示录音秒数(`12s`)。**隐藏而非删除**:保留 `seconds` 状态(60s 自动停止依赖)+ `labelEl` 计算行 + 恢复注释指引——后续需要时把 return 行改回 `React.createElement('span', { className: 'dsh-vi-wave' }, bars, labelEl)` 即可。**实现**:mic-button.js `renderVoiceVisual` recording 分支渲染改传 `bars`(不传 `labelEl`)。**验证**:外层 + 内层(vm.compileFunction)SYNTAX_OK + 单测 192(191/0)+ 浏览器 e2e 页面 200 / rev 847fe50d / JS 错误 0 + 地图无漂移。— [VOICE-003]
- **语音按钮三态动图样式插入坏块修复(v3.2.31-fix,2026-08-21 用户反馈 DeepSeek Harness/DSH Desktop 启动报 `Failed to load plugins / Invalid or unexpected token`)**:根因 = v3.2.30 向 `src/client/styles.js`(`module.exports = "..."` 字符串模板,内层代码用字面 `\n` 表示换行)追加 19 条 CSS 时**项格式错误**——每项被包成 `'\n  ,'ITEM,`(多前导引号 + 尾逗号),还原后内层代码出现 `'<换行>  ,'.dsh-vi-wave{...}`(裸 token)+ **cloud 项闭引号被吞** + **media 项双引号**三处引号错乱 → bundle 内层 `Invalid or unexpected token` → 页面加载插件失败。**修复(3 处精准替换)**:① 坏块 `'\n  ,'ITEM,` → 正确 `\n  ,'ITEM'`(每项无前导引号/尾逗号,逗号在下一项前缀 `\n  ,'` 里);② 补回 `.dsh-vi-cloud` 缺失闭引号(坏块首字符 `'` 原是 cloud 闭引号,替换时被误删);③ 删除 `@media (...)}` 后双引号(好块自带闭引号 + anchor 残留 `'` 重复)。**验证(关键教训)**:外层 `new Function(src)` SYNTAX_OK ≠ 内层合法——必须提取 `new Function` 字符串参数还原内层 code 再 `vm.compileFunction`(bundle 顶层含 `return`,`vm.Script` 会误报 `Illegal return statement`)逐层验证;修复后:src styles 内层 SYNTAX_OK + 仓库 lib/client.cjs 内层 SYNTAX_OK + 部署产物内层 SYNTAX_OK + 全量单测 192(191/0)+ **浏览器 e2e 实测页面 200 / 插件 rev 非空(b73da0a5)/ JS 错误 0 / 无 Failed to load plugins** + 地图无漂移。**生效**:web Ctrl+F5;Harness/Desktop 重启。— [VOICE-003]
- **语音按钮三态动图(v3.2.30,2026-08-21 用户需求「🎤 按钮调整成声波条小动图」)**:① **idle 默认** = 6 条不同高度竖条(5/9/12/7/11/6px)圆形圆头静态声波条;② **recording 录音中** = 同 6 条声波条**跳动动画**(1s ease-in-out infinite,6 条错位 0/.1/.2/.3/.4/.5s 延迟 → 视觉上像音频波形随声音波动);③ **recognizing/pending 识别中** = **圆旋转动画**(12px 圆环 + border-top-color:transparent,0.8s linear infinite);④ error 状态回退 🎤 emoji。**实现**:mic-button.js 提取 `renderVoiceVisual(status, seconds)` 组件外函数(替换原 `const inner = ...` 文本拼接)——按 status 分支返回不同 React 元素;styles.js 追加 19 条 CSS(`.dsh-vi-wave`/`.dsh-vi-wave-bar`/`.dsh-vi-btn.dsh-vi-rec` 触发 `@keyframes dsh-vi-wave`/`.dsh-vi-spin` 触发 `@keyframes dsh-vi-spin` + `@media (prefers-reduced-motion: reduce)` 无障碍支持)。**验证**:SYNTAX_OK(mic-button + styles 双文件)+ 全量单测 192(191 pass/1 skip/0 fail)+ 部署产物含新 CSS 24 处 + 地图无漂移。**无障碍**:title/aria-label 完整(`t('voiceRecord')`)保留键盘快捷键提示;reduced-motion 媒体查询自动停动画。**注意**:mic-button.js 与 styles.js 行分隔格式不同(前者字面 `\n` 2 字符,后者真换行 LF)——chunk 文件批量改时需按文件格式分别处理锚点。— [VOICE-003]
- **参考分层语义校准(v3.2.28,2026-08-21 用户澄清设计意图「记忆链=多轮优化历史 / 会话检索=任务背景参考,所有引入历史会话的模式都要调整」)**:lite 模板【上轮参考处理】措辞错位——把会话窗口检索注入的【相关会话参考】(任务背景)表述成「上一轮已确认的优化结果」(实为记忆链的定位),误导模型把任务背景当优化结果延续。**统一参考分层语义**(reference-guide.md L2 新增「参考来源分级」第 4 条:① 优化历史(记忆链)= 上一轮已确认的优化结果→延续优化依据;② 任务背景(历史会话)= 优化本次草稿是为继续执行任务→理解草稿意图;③ 项目事实(文档/代码)= 以文档代码为准)。**覆盖所有引入历史会话的模式**:① **lite T1/T2(核心,43 处措辞收敛)**——开场「延续上一轮优化结果」→「延续任务背景与已确认优化结果」;【上轮参考处理】→【参考处理】分两类(任务背景参考(上一轮相关会话)+ 优化历史参考(记忆链)+ 统一规则);五步法「上轮结果/上轮已确认决策」→「参考(会话背景/优化历史)/参考中已确认决策」;② **standard T1/T2**——演进记录段加定性「**任务背景参考**:理解本轮任务在多轮会话中的上下文」+ 记忆链延续条款;③ **smart T1/T2**——补「若注入【相关会话参考】,作为**任务背景**理解草稿意图」;④ publish 不动(网络/会话/文档通用条款已准确);base 不动(无检索)。**验证**:U39b/U39c 断言同步更新(上轮参考处理→参考处理分层);全量单测 192(191/1 skip 环境相关/0 fail);部署产物含新文本(2 处);地图无漂移。**运行时行为零变化**(检索逻辑/窗口/判定/注入机制未动,纯措辞与语义呈现校准)。— [PEN-002]
- **publish 长文空流修复 + 输出形态实测确认(v3.2.27,2026-08-21 真实实测发现)**:真实 RPC 实测 publish 模式时发现 `maxTokens=2000` 配置下主调用 `EMPTY_RESPONSE`(九章规格长文超出 token 预算→空流)。**修复**:`enhanceStageLlm` 的 maxTokens 展开给 publish 模式加放宽分支——`isPublish ? { maxTokens: Math.max(maxTokens, 8000) }`(与 reasoning 同策略;此前只有 reasoning 放宽,publish 仅 maxTokens<=0 才省略)。**输出形态误判修正**:初测 publish 输出"你是一个顶级的软件架构师…一句话目标…核心亮点"被误判为「提示词形态非九章规格」——实为九章**第一章「目标概述与角色设定」**内容(角色设定+一句话目标+核心亮点正是模板第一章要求);改用 markdown 标题正则(`^#{0,4}\s*[一二三四五六七八九]、`)检测:**完整九章 PASS**(章节一二三四五六七八九+判定小节,markdown 标题形态,模板【输出结构】正常生效),publish 输出形态无问题。**验证**:publish+maxTokens=2000 → ok=true/6702 字符/完整九章 ✅;全量单测 192(191/1 skip/0 fail);地图无漂移。**方法论**:判断模型输出格式必须看**完整输出**(本次仅看前 200 字符误判),用结构性正则(章节标题)而非关键词抽样。— [PEN-002]
- **语用规则实测校准:转化允许·原意锚定(v3.2.26,2026-08-21 真实实测 + 用户方向纠正)**:上轮 v3.2.25「疑问句必须保持疑问句」经**真实环境 RPC 实测**发现约束过死——「为什么服务重启后端口没恢复?」被优化成「服务重启后端口未恢复,可能的原因有哪些?请逐一排查」这类**疑问→分析/请求的语用转化**,用户确认**转化没问题**(「分析草稿的语境就是为了去重构调整」)。**校准边界**:保的是**原意(提问焦点/分析对象/隐含需求)**不是句式——疑问句可转化为分析判断/明确请求,但转化必须锚定原意,不得丢焦点、不得偏离原文无关内容。**落地**:L0 纪律第 6 条从「语用类型必须保持不变/禁止转祈使/禁止混合」改为「**语用转化允许、但必须锚定原意**」(含正例「为什么 X?→X 可能的原因有哪些?请排查」);T1 ×4 语用判定行同步改为「识别后允许转化 + 转化锚定原意 + 边界示例」(「为什么 X?」→「X 可能的原因有哪些?请排查」合规 / 「为什么 X?」→ 与 X 无关的通用指令 违规)。**验证**:真实 RPC 实测 3 场景(为什么型/征询型/反问型)全部「原意保留 PASS」——焦点与隐含需求均在;全量单测 192(191/1 skip/0 fail);地图无漂移。**方法论沉淀**:mock 验证只能证明规则进了 system,**验证不了模型行为**——提示词层约束必须走真实环境实测(本次即 mock PASS 但真实 FAIL 抓出过度约束)。— [PEN-002]
- **草稿原意不可丢 + 语用类型保真(v3.2.25,2026-08-21 用户需求「所有优化增强一定基于原草稿,草稿原本意思一定不能丢失」)**:用户指出疑问句草稿会被"优化掉"(如「为什么下载失败?」被改写成陈述指令)——查证根因是规则内部冲突:L0 第 6 条笼统要求"语气一致",但 T1「把软性语气升级为明确规则(如『尽量不要有』→『不该有』)」与 T2「不得以提问、征询收尾——执行模型需要明确指令」假设草稿是命令型,诱导模型把疑问改陈述。**修复(落点分层)**:① **L0 纪律第 6 条重写**为「语义保真(原意不可丢·最高原则)」:优化与增量增强一律基于原草稿、草稿原本意思绝不可丢失;**语用类型(疑问/陈述/祈使/感叹)是原意的组成部分必须保持不变**——疑问句草稿优化后必须仍是疑问句(可更清晰具体,不得改陈述或命令);② **T1 ×4**(base/lite/standard/smart):原子拆解/通读原文加「语用类型判定(第一锚点·原意不可丢)」;「软性语气升级/绝对必须强化」加限定——**仅适用于祈使/指令型草稿**,疑问语气是语义要素禁止升级;③ **T2 ×4**(base/lite/standard/smart):「不得以提问收尾」加限定——仅当原文是命令/指令型;**原文疑问型保持疑问收尾**(可优化为带上下文约束的聚焦提问);「去除冗余与口语」加限定——疑问词(吗/呢/为什么/是否)是语义要素不得删;④ publish 两模板无冲突规则不改(九章规格场景,L0 兜底)。**验证**:产物 5/5 含新规则 + 部署同步;全量单测 192(191/1 skip 环境相关/0 fail);地图无漂移。**踩坑(重复教训)**:改 prompts 后必须先跑 `sync-prompts.mjs` 再 `sync-runtime.mjs`——sync-runtime 只 build 不 sync,产物仍旧导致 U40 从旧产物切片假失败(v3.2.24 已记,本次又犯,已列入流程)。— [PEN-002]
- **规则落点四层化 + 模型按需引入参考(v3.2.24,2026-08-21 用户需求「每个规则落点在哪里 + 大模型根据情况自选引入参考规则 + 执行效率」)**:在 skill 集合基础上深化规则组织——**L0 全局纪律(无条件·精简)**:discipline.md 移除参考使用规则段(9-11 条 → 独立技能文件),只留跨模式共性(防回显 1-5 / 质量 6-8 / 稳定性 9);**L1 模板规则(随 T1/T2)**:8 个模板【硬性约束】去重(删与 L0 重复的「只输出本体/语言匹配/严禁复述」3 行/模板,保留「保持原始目标与语义不变」——U39c 契约禁臆造红线 + publish 自评豁免依赖);**L2 场景规则(声明式·命中才注入)**:新增 `retrieval/reference-guide.md`(REFERENCE_GUIDE,参考使用规则 3 条)——retrieve 命中参考块/记忆才追加(恢复 v3.2.20 前的条件注入点,但用独立 L2 技能文件),SMART_TAIL/CONTINUE/场景判定维持条件注入;**L3 检索判定(模型自选参考·不进 system)**:SKILL.md 新增 `sources`(base:[] / lite+standard:[session] / smart:[session,workspace] / publish:[web])+ `rules`(场景触发规则清单)声明 → SKILL_MANIFEST 生成 → smart 工作区判定改由 `sources.includes('workspace')` 驱动(声明路由)。**效率收益**:无参考场景 system 少 ~185 字符参考段 + 每模板少 3 行重复 ≈ 省 20%;模型按声明自选参考(judge 判定命中才检索引入)。**验证**:L3 实测——base 无参考 system 不含参考规则 ✅、lite 窗口命中 system 含参考规则 ✅;全量单测 192(191/1 skip/0 fail);地图无漂移。**顺带修复**:v3.2.22 批量插入稳定性段导致的 9 个模板【硬性约束】标题重复(v3.2.24 审查发现)。**踩坑**:chunk 转义串内**字符串字面量转义需双反斜杠**(`'\\n\\n'` 在文件字节层),行分隔用单反斜杠——两者混用会致 require 后字符串内真实换行(Invalid token);mock sessionQuery 需完整三方法(readSurface+listEvents+filterEvents)才能触发 rounds 检索。— [PEN-002]
- **技能集合化重构(v3.2.23,2026-08-21 用户需求「参照 skill/插件 skill 集合架构,不过分依赖代码架构」)**:提示词增强模块从「代码为中心」重构为「**技能集合为中心**」——① **目录重组**:`prompts/`(平铺 18 md)→ `skills/enhance/`(git mv 保留历史):5 模式各建目录(base/lite/standard/smart/publish,含 system.md=T1 + increment.md=T2)+ `retrieval/`(relevance/intent/doc-analysis/websearch 判定子技能)+ `assemble/`(task-analysis/continue/smart)+ 包级 `discipline.md`(全局纪律层);② **SKILL.md 元数据声明**:包级 + 5 模式 SKILL.md 用 YAML frontmatter 声明 name/description/mode/templates(t1,t2)/retrieve(含 windows 检索窗口 + budgets 预算表)——**业务知识事实源外置**,新增模式 = 加目录零代码;③ **生成器自动发现**:`sync-prompts.mjs` 从 SOURCES 硬编码改为扫描 `skills/enhance/*/SKILL.md` 自动发现(NAME_MAP 常量名兼容:SYSTEM_PROMPT 等不变),新增生成 `SKILL_MANIFEST`(templates 直接引用同作用域常量 + retrieve 声明)与 `SKILL_RETRIEVE_BUDGETS`;④ **代码读声明**:enhance-handlers 的 `BUILTIN_TEMPLATES` 硬编码块与 `RETRIEVE_TABLE[cfg.mode]` 引用改读 SKILL_MANIFEST(分发逻辑不变,只换数据源);pure.js 的 PURE 表保留(单测切片依赖)并新增 U40c 一致性断言(SKILL_MANIFEST retrieve/budgets 与 RETRIEVE_TABLE/BUDGET_RETRIEVE_TABLE 等价,防漂移)。**验证**:U40/U40b/U39c 断言路径迁移至 skills/enhance/;新增 U40c(5 模式 + 模板引用 + retrieve/budgets 等价)全过;全量单测 192(191/1 skip 环境相关/0 fail);L3 冒烟(web 重启新 host + template/default RPC 正常);地图无漂移(8 目录)。**踩坑**:chunk 转义串(module.exports="...")内行分隔是字面 `\n`(2 字符)——python 注入 replacement 时误用 `\\n`(3 字符)导致 `//` 注释吞掉 `const BUILTIN_TEMPLATES` 声明(ReferenceError),教训:**chunk 文件注入必须精确匹配文件字节的行分隔形态**('\\n' 单反斜杠),注入后用 require 验证注释不吞代码。— [PEN-002]
- **模板层温度/稳定性分工(v3.2.22,2026-08-21 用户需求「区分一下直接写入每一个提示词模板中」)**:不采用代码按模板区分 temperature,而是**把稳定性/生成性要求直接写进全部 10 个模板文件**——① T1 系列(system/system-lite/system-standard/system-smart/publish)新增「【稳定性(最高优先级·确定性任务)】」:本模板是「重述+结构梳理」的确定性任务,输出高度稳定可复现,保持原文已明确的措辞/句式/结构,不无依据换词变式,「梳理」≠「改写」,宁可少动;② T2 系列(increment 五模式)新增「【稳定性与增量平衡】」:重述与保留部分高度稳定(同 T1),仅「保守增量补充」部分是生成性的,补充内容可适度变化但严守「保守增量规则」红线(补得少比补得多好);③ 纪律模板第 12 条同步区分「重述与保留部分必须稳定 / 仅增量补充可按模板要求适度变化」。**设计意图落地**:T1 越稳越好(同输入重复优化结果一致),T2 补充适度变化(避免每次补出雷同措辞),与用户「T1 只梳理语言逻辑/结构、T2 才补充增强」的原始设计对齐。**验证**:10/10 模板含各自条款(5 T1 + 5 T2);产物 plugin-host.js 含 T1×5/T2×5/纪律区分;部署产物 10 处;全量单测 191(190/1 skip/0 fail,U40 生成区一致性过);地图无漂移。**踩坑**:python 脚本 `open(p,'w').write(expr)` 在 expr 抛错时 `open('w')` 已先截断文件 → publish.md 被清空(git checkout 恢复后安全重写,教训:先算好内容再 open('w'));publish.md 的【需求表述纪律】标题后跟括号非换行(锚点需含完整标题行)。— [PEN-002]
- **优化结果稳定性(v3.2.21,2026-08-21 用户反馈「同一个提示词每次优化结果都不一样」)**:根因 = enhance 主链路 `llm.stream` **从未传 temperature**(DSH llm 接口原生支持 `temperature?: number`,call-config.d.ts 确认)→ 平台默认采样(随机性高)→ 同一输入每次结果不同是必然的;模板分工本身已实现(T1=语言逻辑/结构梳理重述、T2=保守增量补充)。修复双管齐下:① **代码层**:`enhanceStageLlm` 调用加 `temperature: 0.3`(确定性重述任务压低采样随机性;reasoning 链节不传——由 effort 控制,思考模型 temperature 无意义且可能不支持);② **提示词层**:纪律模板(discipline.md)新增「稳定性纪律」第 12 条——明确这是「重述/结构梳理+保守增量」的确定性任务而非创意改写,对同一输入重复优化应保持语义、结构与措辞稳定一致,不得为「看起来不同」换同义词/句式/顺序。**验证**:L3 mock llm 实测——非 reasoning 链节 stream 参数 temperature=0.3 ✅、reasoning 链节 temperature=undefined ✅、system 含稳定性纪律 ✅;全量单测 191(190/1 skip/0 fail);生成区/产物/部署同步;地图无漂移。— [PEN-002]
- **防回显护栏并入纪律模板(v3.2.20,2026-08-21 用户需求「CONTEXT_GUARD 与纪律模板语义重叠,合并减 tokens」)**:`prompts/context-guard.md`(CONTEXT_GUARD,参考上下文使用规则 5 条)**删除**,内容精简为 3 条并入 `prompts/discipline.md` 新增「参考上下文使用规则」小节(第 9-11 条:吸收明确需求不复述 / 冲突以原文为准不臆造 / 不扩大缩小颠倒范围)——纪律模板成为唯一事实源,**单注入点**(analyze 无条件追加),删除 retrieve 阶段 `state.system += CONTEXT_GUARD` 条件追加点。**收益**:消除两份防回显提示词重复(原来有参考时 system 会带两份「禁复述参考」),有参考场景 tokens 净减,注入逻辑从「条件 + 无条件」双点收敛为单点,行为一致。**验证**:生成区 DISCIPLINE_PROMPT 含参考规则 5 项断言全过;产物 plugin-host.js 无 CONTEXT_GUARD 常量/追加点、纪律含参考规则、无条件追加保留;已部署 web profile 产物同步(参考规则 2 处、无 CONTEXT_GUARD);全量单测 191(190/1 skip/0 fail,U40/U40b 断言已迁移至 discipline.md);地图无漂移。— [PEN-002]
- **cleanOutput 原文回显剥离修复(v3.2.19-fix,2026-08-21 用户实测「还有返回给优化侧模型的提示词」)**:复现用户场景定位根因——模型只回显「请优化以下提示词:」前缀(无闭合 """ 包装)时,v3.2.18 的循环剥离不处理 → 原文残留。修复两层:① `cleanOutput(raw, originalText)` 新增第二参,孤立前缀剥离(`^(?:请优化以下提示词[::]|优化以下提示词[::]|【用户输入】)` 正则剥前缀行,含无闭合包装形态)+ 传原文时精确剥离「原文回显段」(`s.startsWith(ot) && s.length > ot.length` 才剥,结果=原文不误伤);② 调用点 `cleanOutput(result.text, state.text)` 传入用户原文。**顺带修复引入 bug**:上一版调用点误用未定义变量 `text`(llm stage 作用域无 `text`,只在 assemble stage 定义)→ ReferenceError → 空 Error 被 herr 序列化为 `{}` → enhance 恒 LLM_FAILED → bundle-smoke SMK-06 起 15 项全挂(发布前全量单测拦截)。**验证**:用户场景 5 形态全 PASS(纯前缀+正文/纯前缀仅原文/完整回显/正常输出/结果=原文不误伤);全量单测 191(190/1 skip/0 fail);web 重启新 host RPC 正常。**教训**:改 host stage 代码注意作用域内变量可用性(llm stage 无 text);改动后必须全量单测(bundle-smoke 从构建产物切片,能拦截此类 ReferenceError)。— [PEN-002]
- **纪律约束模板(v3.2.19,2026-08-21 用户需求「优化方向调整:应该优化提示词,模板之外加纪律约束」)**:新增独立纪律文件 `prompts/discipline.md`(DISCIPLINE_PROMPT,类似给优化模型的一个 skill/纪律文件)——analyze 阶段 `resolveTemplateSystem` 后**无条件追加**(所有模式含自定义模板每次必带,与 cleanOutput 事后清理互补,从源头约束模型行为)。纪律内容(最高优先级,优先于任何模板指令):① 只输出优化后的提示词本体;② 禁止任何前缀/后缀说明(以下是优化后的提示词等);③ 禁止复述/引用/回显指令、原文、参考上下文、示例;④ 禁止代码块/引号包裹(除非内容本身需要);⑤ 禁止输出思考过程/改写理由;⑥ 语义保真(不臆造/删减/替换原文已明确信息);⑦ 明确化须有原文依据;⑧ 语言匹配跟随输入。**意义**:用户自定义模板(custom)原本完全不含内置纪律,现在纪律层独立强制附加,解决「优化结果带出指令 + 质量参差(有时补全多有时基本没变化)」的源头治理。**验证**:sync-prompts 生成区含 DISCIPLINE_PROMPT;产物 plugin-host.js 含常量+无条件追加;已部署 web profile 产物含 2 处;全量单测 191(190 pass/1 skip/0 fail)。— [PEN-002]
- **优化结果清理增强(v3.2.18,2026-08-21 用户反馈「优化后的提示词偶尔带出发给模型的提示词」)**:根因 = `src/host/pure.js` `cleanOutput` 只剥离「请优化以下提示词:」开头、未找闭合——模型把完整指令(`请优化以下提示词:
"""
<原文>
"""

<正文>`)复述进输出时,清理后**原文残留在结果里**;另无前缀变体/前缀说明行/尾部附注均未处理。修复:`cleanOutput` 重写为**循环剥离(≤3 轮直至稳定)**——① 完整回显按语义处理(有后续正文取正文 / 仅回显无正文返回包装内原文,剥掉指令而非把指令原样带出);② 兼容 publish「【用户输入】」包装回显;③ 剥离常见前缀说明行(以下是优化后的提示词:/优化后的提示词如下:/结果如下:等 20 条);④ 剥离尾部附注(以上是优化后的提示词等 6 条);⑤ 保留原成对引号剥离。确定性后处理,不依赖模型遵从。**验证**:14 场景全 PASS(完整回显剥离原文/publish 回显/前缀行/尾部附注/正常输出不变/引号对/仅回显返原文/前缀+回显叠加);全量单测 191(190 pass/1 skip 环境相关/0 fail,含新增 4 条 cleanOutput 断言);构建+部署完成。— [PEN-002]
- **语音识别完自动触发提示词增强开关(v3.2.17,2026-08-21 用户需求)**:语音设置行1「识别引擎」与「快捷键」之间新增**两态开关按钮**「自动增强:开/关」(复用下载/切换/删除同款 dsh-plg-btn 样式,hover 提示用途)——开启后语音识别完成、文本填入草稿时自动触发提示词增强(等效自动点输入框旁优化按钮);关闭不触发。
**实现**:① src/client/voice/state.js VOICE_CFG_DEFAULTS 加 autoEnhance:false(默认关,不改变现有行为)+ mergeVoice 校验(非法回退 false);② voice-section.js 行1 grid 改 1fr auto 1fr 三列插开关;③ mic-button.js runTranscribe 内 insertVoiceText 返回后判定 fill.filled===true && !fill.pending && autoEnhance===true → 调同作用域 enhance(sessionId, filledText, inputActions, draftRef)(draft 传填完后的文本,保证 enhance 内部 s.backup 与结果回来时 draftRef.current 一致才应用;pending=enhance 进行中已暂存不重复触发);④ lib/asr.cjs sanitizeVoiceCfg 加 autoEnhance 白名单(与 hotkey 对称,防 host 读路径丢字段);⑤ i18n 3 键 ZH/EN(voiceAutoEnhanceOn/Off/Tip,i18n 键 84→87)。
**验证**:单测 190 pass/1 skip/0 fail(含 VOICE-P21~24 持久化+mergeVoice+产物断言);构建+漂移+部署;L2 浏览器实测(CDP+headless Edge)——开关位于引擎(x=248)→开关(x=401)→快捷键(x=521) 之间、toggle label 变化、localStorage dsh.prompt-enhancer.voice + 磁盘 config voice.autoEnhance 双持久化 + reload 保留;触发链路 enhance 同作用域引用三重确认(helpers chunk 先于 voice 注入同 body、单测产物断言、代码审查)——headless 无麦克风未直接触发真实 enhance,条件分支由单测覆盖。— [VOICE-003]/[PEN-002]
- **语音模型切换修复 + 设置 UI 全量核对(v3.2.16,2026-08-21 用户报告「下载完不能切换/按钮只做了样式」)**:根因 3 层叠加——① **R1 client 状态层**:`src/client/voice/state.js` `mergeVoice` 模型白名单硬编码只认 `sense-voice`(v3.2.7 多模型后未扩展)→ 点「切换」保存 paraformer-zh 被静默重置回 sense-voice,UI 看似无反应;② **R2 部署层**:`scripts/asr-deploy.mjs` 复制条件「目标不存在或目录为空才复制」→ 已装环境的 `asr-worker.cjs` 恒为旧版(硬编码 sense-voice),v3.2.7+ 新 worker 从未部署;③ **R3 host 读路径**:`lib/asr.cjs` `sanitizeVoiceCfg` 同样硬编码洗回 sense-voice。**修复**:mergeVoice/sanitizeVoiceCfg 白名单动态化(`sensevoice-q8`→`sense-voice` 兼容映射、`''` 保留、`^[A-Za-z0-9._-]{1,64}$` 校验放行内置+自定义、非法回退默认);`asr-deploy.mjs` 改为**始终覆盖** worker + 首次启动按配置注入 `DSH_ASR_MODEL`;`lib/asr-worker.cjs` 修复 `status()` 未定义 `MODEL_ONNX/TOKENS`(动态扫描 onnx)+ `/health` 上报 `model`;host `voice/status` 上报 `asr.local.model`。**全量核对 voice-section.js 18 个交互元素**:全部绑定真实 handler(识别引擎/快捷键录制/cloud 配置/模型下拉/三态按钮/语言/打开文件夹/文本规整/chain/检测状态等),无纯样式控件——「只做了样式」观感即 R1 白名单吞状态。**验证**:单测 186 pass/0 fail(含新 VOICE-P20);RPC 直调 config/set→modelApply→worker /health `model:paraformer-zh`+modelReady ✅;浏览器 L2 7/7(切 Paraformer→按钮变「切换」→点击→config 持久化+worker 真加载+按钮变「删除」);worker /rpc status 修复后返回 modelFile/tokensFile/model ✅。**注意**:运行中 DSH host 为旧内存代码,`voice/status` 新字段与 host sanitize 修复需**重启 DSH** 后生效;核心切换链路不依赖重启(已实测走磁盘+新 worker);以后升级 worker 需跑 `node scripts/asr-deploy.mjs --start`。— [VOICE-003]
- **下载连接层重构为 undici(v3.2.15,2026-08-20 用户需求「找成熟项目看看怎么下载」)**:调研 Node 生态成熟方案(undici/got/aria2/Node 内置 fetch,联网查证官方文档)后,采用 **undici@^7.29.0** 重构 `lib/net-proxy.cjs` 连接层——① `httpsGetProxied` 改为 `fetch + dispatcher`(`Socks5ProxyAgent` 走系统代理 SOCKS5 / `ProxyAgent` 走 HTTP 代理 / 无代理直连,Agent 按 key 缓存复用连接池);② 删除手写 CONNECT 隧道 / SOCKS5 协议 / HTTP/1.1 流解析(根治「双重 TLS 400」「connectReq 挂死」等手写边界 bug);③ `redirect:manual` 手动跟随重定向(≤5 跳,每跳 `body.cancel()` 释放连接,保留 Range——规避 fetch 自动跟随剥离跨域 Range);④ AbortController 支撑 destroy/超时(空闲超时语义,destroy(err) 冒泡 error);⑤ 接口契约不变(resolveProxy/systemProxyWin/curlProxyArgs/tunnelRequest/httpsGetProxied/socks5Connect),asr-models/updater-host 调用点零改动;⑥ 保留多源 fallback / .part 断点续传 / 重试 / 系统代理注册表读取;⑦ `scripts/sync-runtime.mjs` 新增 undici 运行时部署(拷贝到运行环境 node_modules,发布链路 pnpm 自动装依赖)。**验证**:本地确定性 12/12(Range 206 续传内容一致/416/302 保 Range/destroy 冒泡);系统代理 SOCKS5 真连 GitHub 200(本机 127.0.0.1:10808=v2rayN SOCKS,925ms/424B);假代理降级直连;全量单测 186(185 pass/1 skip 环境相关);部署后 web profile `require(undici)` 可解析。**注意**:`Socks5ProxyAgent` 构造打印一次 ExperimentalWarning(undici v7.23+ 官方标记 Stability 1,行为正常)。— [VOICE-003]/[PEN-002]
- **模型下载断点续传 + 代理支持(v3.2.13,2026-08-20 用户需求「下载不可靠」)**:`downloadFile` 升级——① **断点续传**:目标写 `.part` 临时文件 + `Range: bytes=N-` 请求(206 追加 / 200 覆盖 / 416 视为完成 rename),中断保留 `.part`,重试/换源/下次点击从断点续传(大文件 232MB 中断不再从头下);② **代理支持**:`HTTPS_PROXY` 环境变量 → `CONNECT` 隧道(http.request CONNECT → `tls.connect(socket)` → 等 `secureConnect` → `https.request({createConnection})` 复用隧道 socket,标准 IncomingMessage 处理);③ **Range 兼容降级**:实测 `/api/resolve-cache` 端点对 Range 请求挂死(无响应)→ 60s 空闲超时(`req.setTimeout` + destroy)自动标记 `rangeBroken`,后续尝试放弃 Range 全量重下(防永久挂死);④ 每源 2 次尝试(首带 Range 续传、降级后全量);⑤ http 源支持(按协议选 http/https 模块)。**验证**:本地 HTTP 服务器(支持 Range/206/416)确定性测试 **8/8 PASS**——全量下载 / .part 前 20KB 续传补齐且内容一致 / 416 完整处理 / 进度 total 正确。**注意**:hf-mirror 大文件(232MB)网络波动仍可能多次中断,但续传保证不从头下;代理可进一步稳定(用户配 `HTTPS_PROXY` 后自动走隧道)。— [VOICE-003]
- **桌面快捷方式双实例选择路由(v3.2.11,2026-08-20 用户需求)**:`buildCliRestartScript`(「重启DSH」快捷方式 CLI)从「生成时固化模式」重构为**运行时探测 + 三态路由**——① 脚本运行时探测 Desktop 客户端(tasklist DSH Desktop.exe)+ Web 服务(netstat 3080 LISTENING)谁在跑;② **双实例并存 → readline 交互让用户选择重启哪个**(1=Desktop 客户端 / 2=Web 服务);③ 单实例 → 直接重启在跑的(不再误杀/误拉);④ 都不跑 → 明确报错;⑤ Desktop 分支杀进程改为实时 `desktopPid()`(tasklist 解析,不再依赖可能被 web 覆盖的共享索引);web 分支保留管理员检查 + 服务模式/默认模式原逻辑。**验证**:生成脚本语法 OK + 探测函数在当前双实例环境实测(desktopRunning=true/pid 9128、webRunning=true → 触发用户选择路径)✅。— [PEN-002]
- **DSH Desktop 客户端适配(v3.2.10,2026-08-20 用户需求·web 端零丢失)**:服务端口/更新模块全面适配 `dsh-plugin-desktop`(Electron shell,web 端口随机 listen 0、无 nssm 服务概念)——① `sys.isDesktop()` 识别(`process.versions.electron` 或 execPath 含 'DSH Desktop'),所有改动均加 isDesktop 条件分支,web 场景(纯 node)原逻辑 100% 保留;② `probeEnv` Desktop 下 port-mode 返回专有 `desktop` 态、port-pid=本进程,不再硬编码探测 3080(实测修复:Desktop 上 envcheck 曾误报 web 实例的 3080 状态);③ `update/portRestart` Desktop 下跳过 nssm 检测、杀索引 pid(Desktop 主进程)、`launchArgv=[execPath]` 直接拉起 DSH Desktop.exe(修复:原 argv=[] 兜底成 bin.js web,杀 Desktop 后 GUI 不回来);④ `buildPortRestartScript`/`buildCliRestartScript`(快捷方式)Desktop 分支:杀索引 pid、spawn(execPath) 无 argv、成功判定=新主进程端口就绪(netstat 查本进程 LISTENING,不 poll 3080);⑤ `makeShortcut` Desktop 下跳过 RunAs 打标(GUI 无需管理员);⑥ `update/serviceInstall` Desktop 守卫(返回 DESKTOP_UNSUPPORTED);⑦ `update/restartNeeded` Desktop 下 command 改提示重启客户端;⑧ `update/portRestart` staged 安装 profile Desktop 下强制 `desktop`(防装错 web profile);⑨ client `ENV_DETAIL_TEXT` 加 desktop 映射 + i18n `envPortModeDesktop` zh/en(桌面版客户端(端口由客户端管理))。**验证**:模拟 Desktop(注入 electron)probeEnv 返回 desktop 态 ✅;真实 Desktop 重启后 envcheck 实测 port-mode=desktop/port-pid=9128 ✅;web 场景 RPC 回归(envcheck=default / serviceInstall ok / portRestart 服务模式链路真实触发)✅;全量单测 157/157 ✅。— [PEN-002]
- **优化按钮文字迭代(用户需求·上轮优化调整)**:① 模型 1(默认)直接显示「正在优化」——去掉序号与三点动态;失败切换模型后显示「{n}正在优化」(2正在优化、3正在优化…),统一不再区分「重试」;② 移除三点 1→2→3 循环动态效果;③ hover 提示(title)全面精简:一键优化 / 继续优化 / 点击取消 / 点击恢复原文 / 命令无可优化 / 当前不可优化 / 点击切换记忆(≤6 字);④ 阶段文案「准备中」→「正在准备」。— [PEN-002]

### Changed
- **优化按钮文字精简 + 动态「优化中」效果(用户需求)**:① 按钮可见文字全部 ≤6 字(标准 4 字)——模型序号文案 `{n} 优化中`→`{n}优化中`、`{n} 正在重试`→`{n}重试中`、结果态「可撤回」→「撤销优化」;② 优化中状态动态点:基础文案 + 三点按 1→2→3 循环(400ms/步,经 timerSvc 定时器,与进度轮询一致);③ 移除优化中按钮的秒数追加(动态点提供持续动态反馈,保证文字 6 字内)。— [PEN-002]

### Changed
- **Issue #1 修复审查补充(v3.2.4 发布后代码审查)**:① 新增 `test/config-persist.test.cjs`(7 项契约测试:config/get·config/set 参数校验行为、lib 与 src/host 双份 rpc-schema 同步防漂移、lib/index.cjs 注册存在、plugin-client.js 产物注入防漂移);② 首次安装继承条件加固——`hostSync !== null` 收紧为 `hostSync === true`:host 磁盘同步超时放弃(20s)时不再执行自动继承,杜绝「磁盘有配置但同步失败」场景下默认链覆盖用户配置的残余路径。— [PEN-002]

### Changed
- **语音识别设置面板(云端 + 本地严格按最终确认 SVG 重落,2026-08-20 用户需求·功能零增删)**:设置「语音识别」段落按用户最后提供的 SVG 规格完整重排。**云端**:① 行1 识别引擎 + 快捷键(92×26 同尺寸)两列;② 行2 协议 + 接口地址;③ 行3 模型 + API Key;④ 文本规整合并为单一下拉(关闭/跟随模型配置/自定义模型),标题置顶,选「自定义」才展开子字段;⑤ 状态行 云端 ✓就绪/✗缺 API Key + 检测状态。**本地(严格按用户最后 SVG)**:① 行1 识别引擎(本地) + 快捷键(92×26 未设置 + 清除);② 行2 两列——**当前模型** label + 模型▾(已装模型切换) + 语言▾ | **模型管理** label + 打开文件夹 + 管理模型 两按钮;③ 模型状态行 "SenseVoice 已装 · Paraformer 未装 下载"(下载触发下载,未装才有);④ 管理模型按钮展开/折叠模型列表(下载进度/设为当前,功能保留,默认折叠保持面板与 SVG 一致);⑤ 文本规整下拉 + helper「开启后用模型对识别结果规整」(关闭态显示);⑥ 状态行 **本地引擎 ● 就绪**(绿色圆点 + 就绪文字)+ 检测状态。**删除项**:静音自动停/快捷键唤醒/按住说话/音频上传隐私/云端引擎 helper/规整 helper(链模式)/CollapsibleSection summary/本地 hint/本地模型列表(默认)。**i18n 对齐**:打开文件夹/已装/下载/中文(普通话)4 个 zh 值;新增 6 键 ZH+EN(当前模型/管理模型/未装/本地引擎/就绪/规整 helper)。**守卫**:状态行 `status.refine &&` 防崩溃;文本规整下拉 value 映射与渲染条件边界对齐。— [VOICE-003]
- **语音设置控件样式统一 + 模型下载/删除(2026-08-20 用户需求)**:① **所有按钮/下拉/输入框统一框高**——移除快捷键框与清除按钮的内联 `26px` 高度(统一走默认 ~30px,与「模型配置」区一致);② **label 贴控件**——去掉 `dsh-plg-label` 的 `min-width:96px` 预留,实测 label 宽度 识别引擎 96→52px、快捷键 96→39px(文字与控件间隙 8px);③ 本地模型区状态行改为 **模型下拉(全部模型)+ 下载/删除按钮**——下拉含所有模型(名称+体积),选中未装模型→按钮「下载」触发下载,选中已装→按钮「删除」删除模型;语言下拉保留;④ **新增 host RPC `voice/modelDelete`**:`lib/asr-models.cjs` 新增 `modelDelete`(路径穿越校验→`BAD_MODEL_ID`、目录不存在→`MODEL_NOT_FOUND`、`fs.rmSync` 删除)+ `lib/index.cjs` 注册 handler + rpc-schema **双份**(lib + src/host)同步;客户端删除当前模型时**先切走(worker 重启释放 Windows 文件句柄)再删**;⑤ 删除「已装·未装」文本状态行。**实测**:L3 RPC 冒烟(穿越守卫/不存在/真实删除空目录 ok:true)+ L2 浏览器交互(切未装→按钮变下载、切回已装→变删除)+ 单测 157/157 + voice-verify 5/5 全过。— [VOICE-003]
- **语音设置面板微调(2026-08-20 用户需求)**:① 删除「管理模型」按钮与展开的模型列表——本地模型全部并入「当前模型」下拉;② 「当前模型」行改为**单行四控件**:模型▾(全部模型,宽度随内容自适应)+ **三态按钮**(未装→**下载** / 已装非当前→**切换** / 已装且当前→**删除**)+ 语言▾ + 打开文件夹;③ 打开文件夹放入符合的模型文件后,下拉自动检测到(scanCustomModels),选中后按钮变「切换」;④ 模型配置区提示文案精简——只保留「恢复默认」将模型链重置为官方默认模型(自定义模型会被替换),删除「按顺序逐一尝试,失败则用下一条,可增删改序」;⑤ 修复渲染期 `btnState` 引用 `const v` 的 TDZ 顺序(v 提到 handlers 前,否则插件设置区渲染空白)。**实测**:L2 浏览器(单行四控件 11/11 + 交互 当前→删除✅/未装→下载✅ + 配置区文案三项检查)。— [VOICE-003]

## [3.2.4] - 2026-08-19

### Fixed
- **桌面端 (DSH Desktop) 每次重启插件设置被重置(GitHub Issue #1 用户反馈)**:DSH Desktop 主进程每次启动动态分配端口(`listen(port 0)`),Chromium localStorage 按 Origin(协议://域名:端口)隔离 → 每次重启新 Origin 下配置「消失」→ client 误判 `fresh=true` 用默认模型链覆盖用户配置(模型链/模板/参数全丢)。**修复**:① host 新增 `config/get`·`config/set` RPC(`$DSH_HOME/dsh-prompt-enhancer.config.json` 磁盘持久化,原子写 tmp+rename,≤1MB);② client `saveConfig` 双写(localStorage + 磁盘 fire-and-forget);③ 启动时 `syncConfigFromHost()` 从磁盘恢复配置并回填 localStorage(host 未就绪重试 10×2s,覆盖冷启动);④ 首次安装模型链继承加 `hostSync` 门控——磁盘同步完成前不判定 fresh,杜绝「配置待恢复」被误判为「首次安装」覆盖;⑤ web 固定端口场景不受影响,磁盘为空时自动迁移 localStorage 存量配置写盘。— [PEN-002]

## [3.2.3] - 2026-08-19

### Fixed
- **环境检测误报「安装 nssm」(用户实测·v3.2.1-w)**:nssm 已装且服务模式生效(port-mode=service)时,环境检测仍显示「检测到 DSH 未以系统服务运行(无 nssm)。是否现在安装?」——根因:v3.2.1-v 删除 host `service` 探测项后,client `updater-card.js` **三处 `svcNeedInstall` 判定仍写死 `its.find((i) => i.key === 'service')`** → 永远 undefined → 永远判「需要安装」。修复:判定改用 `port-mode` 四态——仅 `default`(前台模式·用户会话)/ `no-listener`(未运行)提示安装;`service`(nssm 接管)/ `service-stopped`(已装未运行)/ 未知态(pid-only / tool-unreachable)一律不再误报。— [PEN-002]
- **端口重启服务模式根治(v3.2.1-u)**:实测发现 `schtasks /create` 的 **`/tr` 参数有 261 字符上限**——A+B 方案把完整 PowerShell 任务链塞进 `/tr` 超长 → create 失败(status=2147500037)→ 旧降级分支 `sc stop`(只停不启)→ 服务挂死、3080 空、页面全报错(08-19 18:32 实测)。**修复**:① 任务链改为**独立 node 脚本**(host 生成 `restart-service-.cjs` 到 executor/cli,`/tr` 只指 `node.exe