# Changelog 本插件遵循语义化版本。完整设计文档见 [`docs/`](./docs/),工程方法论见 [METHODOLOGY.md](./METHODOLOGY.md)。 ## [0.39.0] — 2026-10-22 > **一句话(人话)**:以前「看门狗」到点只是朝子代理喊一声「停」,然后继续干等——平台不理它,任务就永远不落账(实测有一轮会诊跑到 88.8 分钟,最后靠人手动停)。这版把「到点」变成「到点必落账」:到点先喊停,再等 60 秒;还不回就强制结算,并如实写「它没响应,可能还在后台跑」。 **【计数行】**:`node --test` **678/678**(**基线 660 + 本批 18**:consult 11→15 · silence-watchdog 17→19 · 新增 settle-deadline 9 · 新增 escalate 3)。 **一、为什么做(人话)** 会诊 / `eng_coder` / `escalate` 的超时,一直只做一件事:`ctrl.abort()`。但「发了中止」不等于「对方停了」——平台子代理完全可以既不 resolve 也不 reject。旧代码随后是一句裸 `await run.result`,于是**整轮永远不结算**:会诊的 `pending` 计数不归零、其余模型已经回来的回复也交付不出去;账本里留下 10 条只有 `started`、没有结算的记录,#108 更是跑满 88.8 分钟直到人工 stop。 **二、你会看到什么** · 预算到点后**最多再等 60 秒**,然后就落账——不再出现「看门狗明明 30 分钟,任务却跑了一个多小时」。 · 强制结算的文案会写明「子代理未响应 abort · 插件无法终止· 它可能仍在后台运行」——**不再**声称「已终止」。 · 同轮其它模型的真实回复照常交付(先到者赢,不被强制结算挤掉);会被强制结算的那一路显示「无回复 / 超时」。 **三、改了什么(技术面)** | 位置 | 改动 | |---|---| | `lib/settle-deadline.mjs`(新) | 宽限截止叶子模块:`FORCED_SETTLE_GRACE_MS=60000` · `createForcedSettle`(未武装永不决议 / 武装幂等 / disarm 幂等 / `unref`)· `raceForced`(判据取 race 胜者;非 thenable 走强制分支的 fail-safe)· `FORCED_SETTLE_NOTE` | | `lib/consult.mjs` · `lib/eng.mjs` · `lib/escalate.mjs` | 五个等待点全部 `await raceForced(...)`;六个既有中止回调各恰一次 `armAfterAbort()`(不新增中止写点);`finally` 统一 `disarm()`,强制路径只 fire-and-forget `dispose` | | `lib/consult.mjs` | 账本 `settled` 事件加性字段 `forced:true`(仅强制路径) | | `lib/eng.mjs` · `lib/escalate.mjs` | 同步路径强制分支改用诚实文案,旧的「超限任务被终止」**只留在非强制分支**(AC-2:非强制逐字不动) | | `test/` | 新增 `settle-deadline`(9) 与 `escalate`(3);consult 的批 42 腿拆成四个**独立**用例(AC-1 / AC-3 多模型 / AC-12 / AC-5);silence-watchdog +2;4 条恒真断言改为观测真实事实;两条「退化后挂死」的腿加上界 | **四、过程与诚实清单** · **设计评审 2 轮**(FAIL → PASS):轮 1 抓出 1 个 🔴(escalate 与 eng 同步零行为级验收 + 锚 A-1 在零调用点时空洞成立)与 3 🟡 / 5 🔵,九条全改;轮 2 通过并签发 design token。 · **分歧审计**(只读子代理 + %TEMP% 变异实证)另抓出 5 条:强制分支文案自相矛盾(F1)· 锚 A-4 未达成(三条 AC 挤在同一个 `test()`)· 导出面超 FR-1 · §6.2 判据与实现不同形 · 三条中止面不武装。前两条进代码修复轮,后三条写进设计档。 · **首轮交付红了一条**:会诊夹具 `TIMEOUT=7ms` 比快行回复 10ms 还短,两个看门狗都在真实时钟上先到点 ⇒ 断言到 2。定位为**夹具缺陷**(把 7 改成 60 即全绿、实现一字不改),修复轮把四条腿统一成「先 clearTimeout 再手动点火」。 · **交付代码评审** `VERDICT: PASS`(0 🔴 / 1 🟡 / 2 🔵):两条 🔵(同步路径 spawn 失败漏 `disarm()` · 后台 catch 信封漏 `forcedNote()`)已由主代理按评估的「评审后小修」权限补上;🟡 即本条目(CHANGELOG 收口)。 · **同期 docs 提交**(上一 tag 以来、无代码变更):平台兼容性矩阵(`v0.33.0` 为 DSH peer 断点)· tag 补打交接记录。 · **如实边界**:① 平台没有终止子代理的接口——强制结算只保证「落账」,不保证「真的停了」,文案已如实写明;② 宿主 `job_kill` / `consult_stop` / 父 signal 三条中止面**不武装**宽限(设计档 §九 已登记),它们只负责中止、不负责结算;③ 宽限固定 60s,不做用户可配(D1)。 ## [0.38.0] — 2026-10-19 > **一句话(人话)**:上一版(`0.36.0`)让后端接受了「清单里有重复路径」的配置,但**设置页还在劝退**——你填 9 行只重复 1 行,界面显示「**已超上限——保存会被后端拒绝**」,其实点保存是能存的。这版把**四处**口径统一成「先去掉重复、再看还剩几条」,界面不再骗人,读面也不会再**少读一条文档**。 **【计数行】**:`node --test` **660/660**(**基线 660 + 本批 0**:五条新腿全部并入既有块,另复用既有 C-L4,不新增顶层用例)。 **一、为什么做(人话)** `0.36.0` 把「最多 8 条」的判定从「按行数」改成「按**去重后**条数」,但只改了**后端那一道**。于是同一条规则在四个地方各说各话: | 你在哪看到 | 上一版的行为 | |---|---| | 设置页下方提示 | 「已超上限——保存会被后端拒绝」← **假的** | | 设置页「已填 N / 8」 | 按原始行数数(9 行就显示 9 / 8) | | 点保存 | **后端接受**(去重后 8 ≤ 8) | | 看板读回 | **只读到 7 条**(先截断再去重,第 9 行那条唯一路径被切掉) | ⇒ 用户被**劝退**,而实际上能存;就算硬存了,看板还会**少一条文档且不报错**。 **二、你会看到什么** · 填 9 行、其中 1 行重复 ⇒ 界面显示 **`已填 8 / 8`**,**不再**出现「保存会被后端拒绝」。 · 填 10 行、其中 1 行重复 ⇒ 去重后 9 > 8 ⇒ 仍报「最多 8 条」,且「当前 **9** 条」(**不是 10**)。 · 已存的旧配置(9 项含 1 重复)⇒ 看板这次会读到 **8 条**文档(上一版只读到 7 条)。 **三、改了什么(技术面)** | 位置 | 改动 | |---|---| | `lib/ledger-config-text.mjs`(规则源) | 新增并导出 `dedupePaths`;计数级改用去重后条数;保留「非数组 / 计数级 / 逐项级」三段**短路链** | | `lib/client.js`(浏览器镜像) | 同步镜像(自带一份 `dedupePaths`,**不能** import ESM —— 它是手写 CJS bundle);`pathsOver` 与「已填 N / 8」改用去重后条数 | | `lib/tasks.mjs`(读面) | `resolveLedgerConfig`:**先去重、再截断** | | `test/tasks.test.mjs` | 五条新腿:T-9d(规则源)· T-9e(读面)· T-9h(**数字**也钉,含同屏「文档清单 N 条」与「已填 N / 8」必须一致)· T-9f/T-9g(**静态文本锁**,读源码断言形状) | | `test/config-api.test.mjs` | **C-L4 补两条夹具**(`trim 变体 + 重复` · 稀疏 `Array(9)`)—— 原有 10 条夹具不覆盖去重面,实测「镜像去掉 `trim`」与「`Array.from(paths).every` 换成 `paths.every`」都抓不到 | **四、过程与诚实清单** · **设计评审 3 轮**(FAIL → FAIL → PASS):第 1 轮 12 条,其中 🔴 是我把算术写错(「9 项含 2 重复 ⇒ 8 条唯一」,实际 9−2=**7**)—— **而 D-2 正是请用户拍板的那一项**,等于让用户看着错算术点头;已在档内明写勘误与订正。第 2 轮又抓出我修第 1 轮时**新造的同款错**(T-9h 的夹具要 9−1=8 去触发 `> 8` 的报错 ⇒ 恒红)。 · **实施后亲跑红了 2 条** —— 这是本批最有价值的一段:`C-L3 ③`(「超上限只报一条」)与 `C-L4`(「两侧逐条相同」)双双红。根因是**我照着评审建议**把校验拆成两条独立 `if`,而既有契约要求**短路只报一条**;同时实施把两侧写得不一样(一侧 2 条、一侧 1 条)。已改回 `else if` 链,并把「拆回独立 `if`」做成**变异自证**(实测 `32 / 2` 精确复现)。 · **偏离审计** 2 条 🔵:① 本提交夹带 3 行**行尾符** CRLF→LF(文本逐字相同,功能无影响——**记录在案,不回改**,回改反而增加改动面);② 测试注释把「六条腿」全算进 T-9 块(实际是五条新腿 + 既有 C-L4 在 `config-api`)—— 已订正。 · **变异自证**(父会话亲跑 4 处):规则源计数改回原始长度 · 读面去掉去重 · `pathsOver` 改回原始长度 · `else if` 拆回两条独立 `if` —— **各命中目标腿**,复原后逐字节相同。 · **已知边界**:去重是**精确字符串**相等,不做路径归一化(`./a.md` 与 `a.md` 算两条)—— 会改变既有 `docPaths` 语义,属另一批。 ## [0.37.0] — 2026-10-19 > **一句话(人话)**:注入到每一回合的「任务摘要」原来有个 **512 字符**的预算,**从来没用真实样本标定过**。这版把它量清楚(实测最坏 **501**,离 512 只差 11 个字符)、抬到 **768**,并修掉两个会**静默骗人**的地方:Top-N 之外的任务**以前悄悄消失**(30 条只显示 5 条,不说还有 25 条),以及「注入行」的**两段拼接从来没有合计上限**。 **【计数行】**:`node --test` **660/660**(**基线 660 + 本批 0**:四条新腿 T-4c / T-4d / T-4e / T-4f 全部并入既有块,不新增顶层用例)。 **一、为什么做(人话)** 台账设计档的 **U-4** 从批 36 起就写着「精确字数预算在实施阶段用真实样本标定」—— 一直没兑现。它不影响功能,但影响两件事: 1. **看不见的任务**:摘要只列 Top-5 活任务,而尾串「…还有 N 项」**只在预算溢出时**才出现 ⇒ 30 条活任务时,你看到 5 条、**没有任何提示**说还有 25 条。 2. **没有合计上限**:注入行是「anti-drift 行 + 任务摘要」直接拼的,两段各自有预算,**合起来没人管**。 **二、你会看到什么** · 任务多的时候,摘要末尾会出现 **「…还有 N 项」**(这是本版最主要的变化 —— **以前静默**)。 · 摘要能装下的内容变多(预算 512 → **768**):2 条决策 + 8 条长中文标题以前会被截,现在放得下。 · 极端情况(目标描述很长 + 摘要很长)下,摘要会被**按行裁剪**并以「…(摘要已截断)」收尾 —— **anti-drift 行整行保留**,绝不被切。 **三、改了什么(技术面)** | 项 | 内容 | |---|---| | **预算** | `DIGEST_MAX_CHARS` **512 → 768**;`DIGEST_TOP_N` 保持 **5**(实测:5 条 + 尾串已足够表达「有很多」) | | **Top-N 截断可见** | 尾串分母从「候选数」改成「决策 + 卡住 + **全部活任务**」⇒ Top-N 截断与预算溢出**走同一条尾串** | | **合计上限** | 新增 `export const INJECT_TOTAL_MAX_CHARS = **1150**` + `export function assembleInjection(adLine, taskLine)`(纯函数):未超限**逐字**拼接;超限**按行裁剪摘要**(首行必留、不切半行、不跳行);`adLine` 自身逼近上限时**只返回 adLine**(宁可没有摘要,不留超限串) | | **标定记录** | 四样本实测(95 / 501 / 170 / 484 字符)写进台账设计档 **U-4** | **四、过程与诚实清单** · **设计评审 2 轨**:主轨 PASS(11 条 advisory,含「768 预算与 1024 合计上限**互相蚕食**」);并行只读 subagent **FAIL(7 条)**,其中 1 条是**致命的**:我先定的「派生量 1281 = 768+512+1」在**合法输入上恒不可触发**(两段各有硬顶 ⇒ 合法和 ≤1281 ⇒ 判定 `<=` 永真)⇒ **裁剪分支是生产死代码 = 假保护**;而当时的腿用 **896 字符摘要**(> 摘要硬顶 768,**非法输入**)才触发 ⇒ 那条腿只证明「函数对非法输入可用」。 · **修法**:上限改 **1150 标定值**,并用**两条方向相反的腿**夹住它 —— 改大(1281)⇒ **T-4f 红**(合法最坏组合 512+768 必须被裁);改小(900)⇒ **T-4e② 红**(正常摘要被误裁)。1150 必须落在 `(976, 1281)` 内。 · **偏离审计** 4 条:独立复现了上面那条假保护,并抓到我把设计档改成 1150 后**漏同步 U-4**(同一事实两个权威书写位)。 · **交付代码评审** 6 条(1🔴 + 1🟡 + 4🔵),除已修项外抓到 **`assembleInjection` 分支④ 会返回超上限结果**(`adLine ∈ [上限−8, 上限−1]` 时 budget 为负,实测 1150 下 1142→1151、1149→1158,**违反函数自己写的后置条件**)+ 3 个测试盲区(只留首行也全绿 / 尾串长度不决胜 / 空段无测试)。**全部处置**。 · **评审的随机对拍**(我没要求它做):1 万组随机 `adLine`/`taskLine` 与旧 `filter(非空).join` **逐字相同**;2 万组随机 `view` 下**无超预算、无负尾串**、`列出 + 还有 == 总数` 恒成立。 · **变异自证**(父会话亲跑 6 处):768→512 · `listable` 退回 · 删「未超限早返回」· adLine 超限分支失效 · 上限改 1281 · 上限改 900 —— **各命中目标腿**,复原后逐字节相同。 · **已知边界**:只按**字符**(UTF-16 长度)计量,不按字节;若消费面按字节限(768 字符 ≈ 1800 字节),另开批。 ## [0.36.0] — 2026-10-19 > **一句话(人话)**:这是一批**除尘**——不改产品能力,只把「注释/文档/测试」重新对上代码。**五件完全不改行为**(⑤⑥⑦⑧⑩),**两件改**:④ `docPaths` 清单里**有重复项**时以前**报错**、现在**通过**(**只在后端 PUT 校验器上**,设置页前置检查仍按原始行数,缺口另立 `t-8`);⑨ `/task_add x` 交给 `/task_done` 时以前会把 `x` 当参数,现在不会。 **【计数行】**:`node --test` **660/660**(**基线 660 + 本批 0**:本批**不新增**顶层用例,7 条腿全部并入既有块)。 **一、为什么做(人话)** 批 36-补的交付评审留下 **7 条 🔵**,一直挂在台账里。它们的共同点不是「功能不对」,而是**读了会误判**:注释说的和代码做的不一样、测试脚手架的语义比名字弱、一处命名遮蔽让人读错变量。 **二、你会看到什么** · 日常使用:**察觉不到**(除了下面两条边界)。 · ④ 在 `docPaths` 里写了**重复路径**:以前直接报「最多 8 条」,现在**后端会先去重再判**。 · ⑨ 把 `/task_add xxx` 误发给 `/task_done`:以前会把 `xxx` 当成 id 去操作,现在**按原文拒绝**。 **三、改了什么(技术面)** | # | 内容 | 落点 | |---|---|---| | ④ | `isValidDocPathList` 先 `String(p).trim()` **去重**再判 `MAX_DOC_PATHS` | `lib/tasks.mjs` | | ⑤ | `TasksTab` 的命名遮蔽**早已修**(`propsScope`/`scopeKey`),本批只留复核记录 | `lib/client.js:2684-2701` | | ⑥ | `task_update` 描述与 schema 明写「`createdBy`/`owner`/`claimedFrom` 只读」 | `lib/tool-tasks.mjs` | | ⑦ | 测试脚手架 `assertRejectNoWrite(ws, result, code)` → **`withZeroWrite(ws, fn)` 包装器**:零写入在**操作期间**结构性保证(不再是「跑完再核对」),catch 分支同样先断言再抛出 | `test/tasks.test.mjs` | | ⑧ | T-7③ 真档读取**路径可注入**,`diagnostic` 分支(不可解析 / 缺档)**首次进入套件**并被断言 | `test/tasks.test.mjs` | | ⑨ | `commandArgsOf` 谓词收窄为**逐字相等**(删掉 `startsWith("task_")` 兜底) | `lib/task-commands.mjs` | | ⑩ | 注入点成本注释按事实写:每回合**两次读盘**(`tasks.json` + `config.json`),非一次 | `lib/index.mjs` | **四、过程与诚实清单** · **设计评审 3 轮**:FAIL(11 条)→ FAIL(3 条,其中 1 🔴 是「我只改了被点名的那一处,§0 的反向声明还在」)→ **PASS**。另有**并行只读 subagent 设计评审** 9 条(含「行为声明四处口径不一」「⑩ 原写一次读盘是错的」「④ 的放宽在设置页走不通」),也已全部处置。 · **偏离审计** 6 条(1🔴 ⑧ 的 diagnostic 分支其实从未被执行 → 修复轮落地;⑩ 注释假;⑤ 缺书面凭据;⑨ 只在谓词层;腿名与设计不符;21 的三个含义混淆)。 · **交付代码评审**(只读 subagent,`VERDICT: PASS`)8 条 🔵:稀疏数组绕过上限、`String()` 对无 ToPrimitive 对象会抛、`withZeroWrite(` 的 grep 拆分写错、§十一 列了不存在的 ⑤ 测试面、T-9 腿物理位置与设计不符、⑩ 注释引用了不存在的腿、设计里 ⑩ 口径自相矛盾、`t-8` 漏了客户端第三处。**全部处置**。 · **变异自证**(父会话亲跑):④ 去掉去重 · ⑥ 描述句翻转 · ⑦ 删 catch 断言 · ⑧ 不可解析静默 · ⑧ 路径退回写死 · ⑨ 谓词回退 —— **6 处各命中目标腿**,复原后文件逐字节相同。 · **已知未修(有意)**:④ 的三处**镜像**(`ledger-config-text.mjs` / `client.js:172` / `client.js:1814`)与读面截断(`tasks.mjs:859`)仍按原始行数 ⇒ 设置页里存不进 9 条含重复的清单。**已立 `t-8`**,不在本批扩范围。 · **已知不一致(有意)**:`T-9a/T-9b/T-9c` 三条腿的**物理位置在 T-4a 块内**(改块会牵动看板测试的夹具);腿名按设计档 §八 命名,位置在测试头注释里写明。 ## [0.35.0] — 2026-10-18 > **一句话(人话)**:会诊的「只读」保证,以前只验「预设**在不在、坏没坏**」,不验「里面**装的是什么**」—— 预设被改过(加回写工具 / 把 `tool-fs` 的 `write` 翻成 `true`)或版本覆盖,我们**照样放行**。这版把「内容必须还是我们发的那份」变成机器判据:漂移 ⇒ **拒绝运行并点名差在哪**。 **【计数行】**:`node --test` **660/660**(**基线 656 + 本批 4**:新增 4 条顶层用例 T-39…T-42)。 **一、为什么做(人话)** 会诊的只读保证建立在一个 Agent Preset 上(`thincoder-consult-ro`)。旧闸门只做三件事:预设**注册了**、**没 broken**、否则拒绝 —— **全程没看一眼它里面装了什么**。 而它装的东西是可改的:用户可以在设置里把 `tool-bash` 加回去,或把 `tool-fs` 的 `write: false` 翻成 `true`。这两种情况下闸门**全部判绿**,会诊会以「只读」的名义跑一个**能写文件**的子代理。 **★ 本批先纠正了一个错误前提**:台账原判据写「与 `agentPresets.list()` 里同名预设的**实际内容**比对」,但平台 `list()` **只返回显示元数据**(`id / name / description / order / broken`),**拿不到 plugins**。照原判据做,永远比不出内容。 **二、你会看到什么** · 预设**没被动过** ⇒ 行为与以前完全一样。 · 预设**被改过** ⇒ 会诊**直接拒绝运行**,并说清差在哪:`多出 @deepseek-ai/dsh-tool-pwsh(执行类)` / `缺失 @deepseek-ai/dsh-tool-web` / `关键配置被翻转:write: true`。 · 平台**不暴露**内容观测面(`compositionInventory` / `readDocument`)⇒ **降级放行 + 响亮告警**(`只读预设内容未能核验(…)——本部署的「只读」仍是弱证`),不静默、也不假装验过。 **三、技术面** | 项 | 内容 | |---|---| | **新模块** | `lib/preset-readonly-guard.mjs`:9 个模块名常量 + 4 个纯函数(`explainModule` / `normalizeModules` / `checkModuleSet` / `checkDocFlags`)+ 组合判据 `evaluatePresetDrift`;**零副作用** | | **两层判据** | ① **模块集合精确等值**(顺序无关;停用行不算在用)② **关键配置扫描**(`write: true` / `edit: true`) | | **判据位置** | `lib/consult.mjs` 的 `verifyReadonlyPresetContent`,插在既有 `broken` 检查**之后**、`toolFilter` 早返回**之后** ⇒ 老宿主路线**一字未动** | | **降级语义** | 两个观测面任一缺失 ⇒ `degraded` 非空 ⇒ 同时进 `console.warn` **与派发文本**(warnPrefix 先例);缺预设行(两面矛盾)⇒ **拒绝** | | **测试** | 4 条顶层腿:T-39 集合漂移(顺序/多出/缺失/停用五向)· T-40 配置翻转 · T-41 观测面缺失与异常(含「不许静默」)· T-42 **发货定义同步锁**(文本解析 `cordis.patch.yml`,与常量逐项比对) | **四、过程与诚实清单** · 设计评审**2 轮**:第 1 轮 **FAIL**(2 🔴 + 5 🟡 + 4 🔵 = 11 条)→ 全部处置 → 第 2 轮 **PASS**(+3 条 🔵)。其中一条 🔴 是**假腿**:原设计的「禁用能力词表」在集合精确等值之后**永不可能单独判红**,已删除并降级为文案解释器。 · 独立**偏离审计** 7 条(1 🟡 + 6 🔵):① 设计档的评审修订**漏提交**(已补 `74a411f`)② trim 归一化未授权(改设计写实)③ T-42 自证**空转**(改合成完整集)④ 两个降级分支**没有腿**(已补)⑤ §十二 文案与实现不符(改设计)⑥ §六 伪代码丢模块名(改设计)⑦ 自报两条属**加强**非问题。 · **交付代码评审改道**:`advisor(type=code)` 两次派发均**无产出**(无 partial、无落盘)⇒ 按「advisor 不可用时如实登记」先例,改用**只读 subagent** 做独立代码评审(结论见交付记录)。**这是本版的诚实缺口,不是「评审通过」的等价物。** · **运行时服务面仍未实测**:`compositionInventory` / `readDocument` 是否真在插件拿到的服务对象上,只有代码内自检(首次调用打一次 `keyof` + `typeof`)与首次会诊的派发文本能给出答案。**若真机显示两者皆缺 ⇒ 应停下重新裁定范围**。 · **只读的残余面不变**:平台语义「作用域注册只遮蔽全局」⇒ 预设只能收窄它自己那一层,`CONSULT_READONLY_DISCLAIMER` 的「本部署无法保证只读」**仍然成立**。 ## [0.34.0] — 2026-10-18 > **一句话(人话)**:设置里的「继承代理」在 Windows 上其实**什么都没继承** —— 它只复制环境变量,而 Windows 的系统代理存在**注册表**里。这版把它补成**真的继承**(环境变量优先,没有就读系统代理再注入给 codex),并补上了这块**从来没有测试腿**的缺口。 **【计数行】**:`node --test` **656/656**(**基线 652 + 本批 4**:新增 4 条顶层用例 T-P1…T-P4)。 **一、为什么做(人话)** `codexCli.proxyMode` 默认 `inherit`。旧实现的 inherit 只做一件事:**把环境变量原样复制**给 codex 子进程。 而 Windows 的系统代理**不是环境变量** —— 它住在 `HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings` (`ProxyEnable` / `ProxyServer` / `ProxyOverride`)。⇒ 开了系统代理的机器上,codex 子进程**等于没有代理**。 **本机实测**(2026-10-18):系统代理关闭时,`chatgpt.com` 的 DNS 解析到 `157.240.17.36`(Facebook 的 IP,**污染**)且 TCP 443 不通; 把 `HTTPS_PROXY` 指到本机 Clash 的 `127.0.0.1:7897` 后,`codex exec` **成功返回 `OK`**(`turn.completed`)。 **二、你会看到什么** · 在代理软件里打开「系统代理」后,**四条 codex 链路**(会诊 codex 行 / `advisor` / `eng_coder` / `escalate`)**不用再手配 `codexCli.proxyUrl`**。 · 失败时诊断串**末尾**会写明用了什么代理:`proxy=env` / `proxy=system()` / `proxy=none` —— 这是排障最缺的那句话。 **三、技术面** | 项 | 内容 | |---|---| | **新模块** | `lib/system-proxy.mjs`:`reg.exe query` 读注册表(60s 缓存、1.5s 超时)+ 纯函数解析(`ProxyServer` 单值/分协议、`ProxyOverride` → `NO_PROXY`、PAC 识别、输出去重保序) | | **注入规则** | 环境变量优先(用户环境**一字节不动**);环境里没有代理才读系统代理;`HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` 大小写各一份 | | **fail-open** | `reg.exe` 不存在 / 被策略禁 / 超时 / 输出畸形 ⇒ 视为「没有代理」,**与改动前行为一致**;绝不中断 codex 任务 | | **只告警不注入** | PAC(`AutoConfigURL`)与 socks-only 代理 | | **三态诊断** | 只追加到**失败**信封末尾;`proxy=none` 且是连接类失败(TIMEOUT / PROCESS_ERROR / NO_OUTPUT)时补一句「若你依赖本地代理软件,请配置 `codexCli.proxyUrl`」;pre-spawn 失败(`RUNNER_UNAVAILABLE` / `CONCURRENCY_LIMIT`)**不追加**(那时还没算代理,编一个反而是假信息) | | **回归** | 显式 `none` / `url` / 行级 `runner.proxy` 三分支**逐字未动**(T-P2 的负向断言钉住) | | **测试** | 新增 4 条顶层用例:T-P1 解析与三态 · T-P2 注入/环境优先/三分支回归 · T-P3 非 Windows 零读取/缓存/fail-open · T-P4 诊断可见性(末尾/空诊断/env/none 提示)。**每条都做过变异自证** | **四、过程与诚实清单** · 设计评审 **3 轮**(FAIL → PASS → PASS 复核),**12 条**发现全部处置(8 条 + N1–N4)。 · 实施后跑了**独立偏离审计**:6 条 = 1 🔴 阻塞(T-SG4 执行面计数由 2 提到 3)+ 2 条真发现(`none` 分支越界加三态、`NO_PROXY` 重复)+ 3 条建议(其中 2 条**改设计而非改代码**)。全部处置。 · 交付代码评审 **PASS**(3 条:1 条父侧收口协调项、2 条注释卫生已修)。 · **真机验证**:新读取器对**真实注册表**跑通(临时 `ProxyEnable=1` + `ProxyServer=127.0.0.1:7897` ⇒ 得到 `proxy=system(http://127.0.0.1:7897)` 与正确的 `NO_PROXY`,**随后已还原**并复读证明)。 · **仍未闭环**:整条 codex 任务在**运行中的 DSH** 里走一遍(需要插件重载才能加载新 `lib/`)。本机系统代理当前是关的 ⇒ 这条等用户打开「系统代理」后复验。 · **本批顺手补掉的旧缺口**:代理这块(研究档的 B10)**此前零测试覆盖** —— 全仓 `test/` 里 grep `HTTPS_PROXY` 零命中。 ## [0.33.0] — 2026-10-18 > **一句话(人话)**:DSH 0.2.1 把「限制子代理能用哪些工具」的开关**删掉了** —— 我们的**会诊**因此一度停摆。这版把限制搬进 **DSH 的 Agent Preset**(随插件分发),**会诊的 dsh 通道恢复可用**;顺带把你丢的**工程模式预设**也补回来了。 **【计数行】**:`node --test` **652/652**(**基线 652 + 本批 0**:四条新腿 T-35…T-38 **全部并入既有顶层块** ⇒ 用例总数与档数不变)。 **一、为什么要做(人话)** DSH 0.2.1 移除了子代理请求里的 `persona` / `toolFilter`(源码原话:`is no longer supported`),替代方案是 **Agent Preset**。 `consult`(会诊)原本靠 `toolFilter` 保证「**只看不改**」—— 开关没了,上一版只能让它**拒绝运行**。 **这版把限制搬回来了**,而且**随插件分发**:装上就有,不用手写配置。 **二、你会看到什么** · **会诊的 dsh 通道恢复** —— 你那 4 家(deepseek / zai / kimi / opencode)重新可用,只读由预设的工具面保证。 · **工程模式预设回来了** —— `thincoder-eng` 随插件分发;新建会话选它,**第一句就进工程模式**(不再需要那个已经失效的 `~/.dsh/.agent-presets/` 目录)。 **三、技术面** | 项 | 内容 | |---|---| | **两条预设随包分发** | `cordis.patch.yml` 新增 `preset-thincoder-eng` · `preset-thincoder-consult-ro`(`name: '@deepseek-ai/dsh-agent-preset'`,`config.id` 逐字) | | **只读工具面** | 只留读/检索:`tool-fs`(`write:false`/`edit:false`)· `tool-fs-search` · `skill-filesystem` · `tool-skill` · `tool-web` · `agent-instructions` · compaction 组。**刻意不含** bash / pwsh / str-replace-editor / jobs / ralph / workflow / subagent | | **会诊派发** | 带 `agentPreset: "thincoder-consult-ro"`;★ **预设缺失 / `broken` / 服务不可用 ⇒ 仍 fail-closed**,绝不退化成无约束会诊 | | **版本守卫** | peer `@deepseek-ai/dsh` 由 `^0.2.0-rc.1` 提到 **`^0.2.1-rc.2`** ⇒ 低版本由平台 peer 闸门**拒绝安装**,不会「装上了起不来」 | | **平台基线** | `PLATFORM_VERSION_BASELINE` → `0.2.1-rc.2` | **四、移植时踩到的两个坑(老预设照搬必挂)** 1. `dsh-persona` 的 `config.text:` → 0.2.1 要求 **`config.prefix`(必填)** + `complete: true` 2. `tool-subagent` 的 `backgroundMode` / `enableRunInBackground` → **已不存在**(`persona`/`toolFilter` 更是显式抛错移除) **五、验证读数(父会话亲跑)** ``` node --test test/consult.test.mjs ⇒ 7/7 exit 0 node --test test/advisor-config.test.mjs ⇒ 67/67 exit 0 node --test test/tasks.test.mjs ⇒ 21/21 exit 0 node --test test/config-api.test.mjs ⇒ 34/34 exit 0 node --test ⇒ 652/652 exit 0 node --test doc-hygiene + test-lifecycle ⇒ 31/31 exit 0 ``` 新腿 **T-35**(两条 preset 行在场)· **T-36**(只读预设不含写/执行类)· **T-37**(peer 下限)· **T-38**(派发带 `agentPreset`;预设缺失/`broken` 时 fail-closed),每条都做过**变异自证**。 **六、诚实清单** 1. ★ **「预设真的能挂起来」这一步没有真机验证** —— 要**重启 DSH** 才能确认。本批用「**两条预设块文本级扫描:38 次 / 去重 25 个,扣除预设行自身与伪插件 ⇒ 24 个真实包**,逐个在 asar 头 JSON 里核实(含子路径 `tool-subagent-control/list-agents`)+ 逐插件 `Config` 与老档逐键比对」作为静态证据链。 2. **只读的残余面**:平台的语义是「作用域注册只遮蔽全局」⇒ 预设能收窄的是**预设贡献的那一层**;其它插件在宿主面全局注册的工具**无法被预设隐藏**。所以 `CONSULT_READONLY_DISCLAIMER` 那句「本部署无法保证只读」**依然成立**,本批没有改写它。 3. **只读预设没有 persona 行** —— 子代理拿部署级 persona。会诊在生产从来没传过人格,不构成降级。 4. **`viz/` 仍未跟踪**(我的 UI 原型),一直没定留还是删。 ## [0.32.0] — 2026-10-18 > **一句话(人话)**:侧栏看板**看得懂了**——任务带**优先级徽标**、顶部一张**五态环形图**、默认**展开**、卡片改成**毛玻璃**;设置页从「一堆裸配置键」重构成**4 个 tab + 全中文人话**。 **【计数行】**:`node --test` **652/652**(**基线 652 + 本批 0**:两批新腿**全部并入既有顶层块** ⇒ 顶层用例数不变;`test/tasks.test.mjs` 仍 21 条、`test/config-api.test.mjs` 仍 34 条)。 **一、为什么要做(人话)** 用户原话四条:「**任务清单有优先级标记**」·「**你的前端审美应该进一步提升,包括 thincoder 的设置页和侧边栏看板**」·「**provider、model 肯定是要下拉框,text 输入框填错一个就完全用不了了**」·「**文案也要更新下,要说人话让人能看懂的每个项代表什么**」。 **二、怎么用(人话)** · 看板每条任务左边有 **`P10` / `P5` / `P2`** 徽标(红 / 琥珀 / 灰)——**优先级 0 不显示**,免得满屏噪音。 · 看板顶部一张**五态环**:待办 · 已接手 · 在做 · 卡住 · 完成,中心是总数,旁边有计数与百分比。 · 看板**默认展开**(仍可「收起」)。 · 设置页分 **常用 / 会诊 / 任务台账 / 高级** 四个 tab;每个字段是「**人话主标签** + 灰字技术键 + 一句『它影响什么』」。 · 所有时间字段改成 `[ 数字 ] [分钟 ▾]`,**上限写在字段下方**——想填超范围的值,填之前就知道不行。 **三、技术面(批36-补3 · 看板)** · **P 徽标**:`lib/client.js` 的 `priorityBadgeClass` / `PriorityBadge`;`priority >= 1` 才渲染(`0` / 负数 / 缺省 ⇒ 返回 `null`);三档 `#DC2626` / `#D97706` / `#64748B`。**数据面零改动** —— `boardRowOf` 早就带了 `priority`。 · **毛玻璃**:三个变量,底色 **`color-mix(in srgb, var(--dsw-alias-bg-base, #ffffff) 62%, transparent)`** 派生(不硬编码白黑,深色主题自动适配);`backdrop-filter` **只给卡片级**,行与内部详情不加(长列表性能)。 · **五态环图**:**纯手绘 SVG**,零图表库。★★ 计数**从 `payload.ledger` 的行按 `status` 自己数** —— 因为 `counts.doing` 是「在做 + 已接手」的**合并数**,读它会**默默少一段**(四段也「看起来像那么回事」,极难自查)。 · **默认展开**:`expanded` 初值 `true`;折叠分支与按钮保留。 **四、技术面(批36-补4 · 设置页)** · 4 tab:真 `