dsh-prompt-customizer 0.9.2 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.en.md +49 -234
  2. package/README.md +45 -310
  3. package/build.mjs +27 -10
  4. package/client/client.js +2446 -1364
  5. package/client/client.js.map +4 -4
  6. package/docs/adr/0001-remove-catalog-pool.md +27 -0
  7. package/docs/adr/0002-hide-identity-name-source.md +23 -0
  8. package/docs/adr/0003-destructive-updates-allowed.md +18 -0
  9. package/docs/adr/0004-generalized-filter-model.md +17 -0
  10. package/docs/adr/0005-universal-prompt-takeover.md +109 -0
  11. package/docs/design/prompt-control-consensus.md +103 -0
  12. package/docs/guide.md +280 -0
  13. package/docs/images/prompt.png +0 -0
  14. package/docs/images/tool.png +0 -0
  15. package/docs/testing/feature-matrix.md +142 -0
  16. package/docs/upgrades/1.0.0-sqlite-breaking.md +26 -0
  17. package/docs/upgrades/1.4.0-generalized-model.md +23 -0
  18. package/docs/upgrades/1.4.1-web-preview-fixes.md +15 -0
  19. package/docs/upgrades/2.0.0-remove-workspace-level.md +16 -0
  20. package/docs/upgrades/2.0.1-context-fixes.md +6 -0
  21. package/docs/upgrades/2.0.10-import-and-skill.md +6 -0
  22. package/docs/upgrades/2.0.11-regression-fixes.md +7 -0
  23. package/docs/upgrades/2.0.12-generative-tool-tests.md +5 -0
  24. package/docs/upgrades/2.0.13-runtime-contract.md +8 -0
  25. package/docs/upgrades/2.0.14-tool-execution-guard.md +5 -0
  26. package/docs/upgrades/2.0.15-mask-pool-regression.md +5 -0
  27. package/docs/upgrades/2.0.16-regression-cleanup.md +6 -0
  28. package/docs/upgrades/2.0.17-unhide-source.md +5 -0
  29. package/docs/upgrades/2.0.18-unhide-cleanup.md +4 -0
  30. package/docs/upgrades/2.0.19-mcp-json-edit.md +4 -0
  31. package/docs/upgrades/2.0.2-generic-context-filter.md +6 -0
  32. package/docs/upgrades/2.0.20-preset-isolation.md +4 -0
  33. package/docs/upgrades/2.0.21-isolation-restore-test.md +4 -0
  34. package/docs/upgrades/2.0.22-source-immutability.md +3 -0
  35. package/docs/upgrades/2.0.3-skill-summary.md +6 -0
  36. package/docs/upgrades/2.0.4-tool-draft.md +5 -0
  37. package/docs/upgrades/2.0.5-tool-catalog-robustness.md +13 -0
  38. package/docs/upgrades/2.0.6-tool-restrict.md +6 -0
  39. package/docs/upgrades/2.0.7-tool-stability.md +5 -0
  40. package/docs/upgrades/2.0.8-catalog-row-stability.md +4 -0
  41. package/docs/upgrades/2.0.9-tool-and-write.md +8 -0
  42. package/docs/upgrades/2.1.0-universal-prompt-takeover.md +41 -0
  43. package/docs/upgrades/2.1.1-tool-shadow-detection.md +48 -0
  44. package/docs/upgrades/2.1.2-tool-catalog-scope.md +55 -0
  45. package/docs/upgrades/README.md +298 -0
  46. package/docs/upgrades/dsh-0.2.2-rc1.md +49 -0
  47. package/docs/upgrades/rc2-migration.md +70 -0
  48. package/lib/index.js +4697 -1701
  49. package/lib/index.js.map +4 -4
  50. package/package.json +7 -5
  51. package/scripts/e2e-report.json +221 -0
  52. package/scripts/e2e-terminal.mjs +210 -0
  53. package/scripts/simulate-rescan.mjs +184 -0
  54. package/scripts/smoke-host.mjs +1 -1
  55. package/scripts/verify-apply-all.mjs +64 -0
  56. package/scripts/verify-effect.mjs +153 -0
  57. package/scripts/verify-mcp-edit-ext.mjs +48 -0
  58. package/scripts/verify-mcp-edit.mjs +45 -0
  59. package/scripts/verify-panel-browser.mjs +298 -0
  60. package/scripts/verify-workspace-mcp.mjs +62 -0
  61. package/scripts/test-mcp-layering.mjs +0 -441
  62. package/scripts/test-mcp-tool-disable.mjs +0 -420
  63. package/scripts/test-rc2-runtime.mjs +0 -42
  64. package/scripts/test-skill-toggles.mjs +0 -100
@@ -0,0 +1,5 @@
1
+ # 2.0.17 — 解除屏蔽来源修复
2
+
3
+ - `sameEntry` 对 `source.kind='agent'` 按 name 匹配,不再要求旧 session id;解除屏蔽时能删除上一次会话写入的 exclude 条目。
4
+ - `tools/pre-execute` 与 `restrict` 使用同一份当前配置;解除后不会再被旧 agent 来源条目继续拒绝。
5
+ - 回归测试覆盖跨 session 的 agent source 解除匹配,以及 `restrict` disposer 释放。
@@ -0,0 +1,4 @@
1
+ # 2.0.18 — 解除屏蔽清理旧条目
2
+
3
+ - `ToolsPane.addToPhase` 在解除隐藏时,先按精确身份删除;若没有精确命中,再按工具名清掉该阶段旧 source 条目。
4
+ - 解决旧 session source id / 旧来源写法导致 UI 已取消勾选、`exclude` 仍残留,`tools/pre-execute` 继续拒绝的问题。
@@ -0,0 +1,4 @@
1
+ # 2.0.19 — MCP JSON 编辑恢复
2
+
3
+ - 恢复 MCP 编辑弹窗的 JSON 入口:全局 profile patch 编辑默认显示 YAML,但 `formatOptions` 不再收窄为仅 YAML;JSON/YAML 都可通过 `/api/triad/mcp-format` 互转后应用。
4
+ - 新增 `mcpEditFormatOptions` 纯函数与回归:global 编辑 `['yaml','json']`,preset/workspace 的 `.mcp.json` 保持 `['json']`,新增模式 `['json','yaml']`。
@@ -0,0 +1,6 @@
1
+ # 2.0.2 — 通用运行时上下文过滤
2
+
3
+ - 不按 `tool:<name>:depth` 或任何插件名做特判。
4
+ - 读取宿主 `PromptContext.required`:`required: true` 的空上下文保留(例如 `working-directory:current`);其它空的可选上下文不进入面板。
5
+ - 读不到宿主 `layers`(测试 / 旧宿主)时不过滤,保持原始上下文。
6
+ - 运行时上下文显隐仍为编辑草稿,点「保存」才落盘。
@@ -0,0 +1,4 @@
1
+ # 2.0.20 — 预设隔离修复
2
+
3
+ - `presetIdOfAgent` 优先读取 DSH 0.2.2 的 `agentPresets.composedPreset(agent.ctx)`,再回落到 `presetForSession` / `presetOfSession`。避免投影尚未刷新时多个活动会话被解析到同一默认预设,导致改一个预设串到其它预设。
4
+ - 新增 `test/preset-prompt-isolation.test.mjs`:两个 agent 的 `presetForSession` 故意返回同一个值,仍必须按 `composedPreset` 装配出各自的提示词。
@@ -0,0 +1,4 @@
1
+ # 2.0.21 — 预设隔离与卸载恢复回归
2
+
3
+ - 新增 `test/preset-isolation-restore.test.mjs`:两个 agent 预设各自屏蔽不同提示词段与工具,验证 A 的段/工具屏蔽不会作用于 B。
4
+ - 同一测试验证插件卸载:释放 assemble 包装、所有 `tools.restrict` 和 `tools/pre-execute` 监听后,A/B 都恢复原始提示词,工具不再被屏蔽。
@@ -0,0 +1,3 @@
1
+ # 2.0.22 — 原文不可变回归
2
+
3
+ - `preset-isolation-restore.test.mjs` 增加断言:插件装配 A/B 后,宿主原始 `sections` 数组及条目必须保持不变,证明插件只返回新的包装结果,不原地修改宿主提示词。
@@ -0,0 +1,6 @@
1
+ # 2.0.3 — 禁用技能卡片保留原摘要
2
+
3
+ - 预设层 / 全局层禁用技能时,闸门 provider 的 `disabled by ...` 桩不再作为 UI 摘要。
4
+ - `kernelSkillViews` 优先用文件系统扫描的真实技能元数据回填;扫描不到的 bundled/virtual 技能从全局层真实 summary 回填。
5
+ - 全局 mask 场景下用进程内真实摘要缓存保留启用时的描述;卡片状态仍是「已停用」,摘要与启用时一致。
6
+ - 运行时上下文过滤/草稿显隐沿用 2.0.2。
@@ -0,0 +1,5 @@
1
+ # 2.0.4 — 工具 Tab 草稿实时反映
2
+
3
+ - `buildPreview` 对 `/preview` POST 的草稿 `patch` 应用与 `/config/apply` 相同的 `overlayPatch`,再计算 `previewCfg`。
4
+ - 勾选工具后,不用保存即可看到左侧行状态、已启用/已停用计数、右侧工具预览变化;点「保存」才落盘。
5
+ - 修复工具 Tab 只有顶部“预览含未保存草稿”变化、列表不更新的问题。
@@ -0,0 +1,13 @@
1
+ # 2.0.5 — 工具目录补扫与健壮性修复
2
+
3
+ > 本文记录 2.0.5 当时的行为。「纯预设装配仍不混入会话提示词」已在 2.1.0 修订:提示词清单与预览并入同预设活动 agent 的真实装配([ADR-0005](../adr/0005-universal-prompt-takeover.md));工具目录仍按纯预设装配计算,本条对工具侧的描述不变。
4
+
5
+ - 预设工具目录新增 `catalogTools`:纯预设装配仍不混入会话提示词,但工具完整目录会扫描当前 agent 作用域,`apply_patch` 等客户端/agent 工具不再显示为消失。
6
+ - `/import`、`/restore`、`/config/set` 写入前按 Config schema 校验;非法 customization 返回 400,不再静默写坏数据。
7
+ - Skills 面板的静默轮询/焦点同步始终使用当前选择的工作区;`/presets?workspace=` 与 `/status?workspace=` 按工作区返回开关口径。
8
+ - 预设应用/删除/导入的多个配置写入由客户端串行队列执行,patch 失败不再继续写 `activePreset`。
9
+ - 上下文三态同步先预检全部阶段,再一次性写 draft,避免部分生效。
10
+ - 技能摘要缓存按 `scope + cwd + name` 隔离,并按宿主 rank 让内核 summary 覆盖同名文件扫描结果。
11
+ - `skill-health` 的 bundle 引用检查纳入 bundled / office / plugin 技能。
12
+ - 面板“刷新”同时重读 `/config`,避免旧编辑基线覆盖外部修改。
13
+ - 完整记录见本文件。
@@ -0,0 +1,6 @@
1
+ # 2.0.6 — 预设工具屏蔽在新会话生效
2
+
3
+ - `tool-visibility` 不再只查 root/global 工具名,也不再排除 `source.kind === 'preset'`。
4
+ - 工具名从 `ctx.tools.schemas(agent)` 读取;restrict 使用 `agent.ctx.tools`。
5
+ - 新会话创建后,预设级 `tools.exclude` 会通过 agent 作用域 restrict 生效;已验证 `pwsh` 在会话工具列表中消失。
6
+ - 同时保留 2.0.5 的工具目录补扫、导入校验、工作区轮询、写入串行化、技能健康与上下文预检修复。
@@ -0,0 +1,5 @@
1
+ # 2.0.7 — 工具屏蔽稳定性与预设隔离
2
+
3
+ - 修复工具屏蔽的“第一次生效、下一次会话事件又撤销”问题:每个 agent 缓存首次可见工具名,后续 sync 不再因 restrict 后的 schemas 缩小而清空 deny。
4
+ - 工具完整目录只读取当前编辑预设对应的活动会话,不再把其它预设会话的 agent 工具混入。
5
+ - 工具名来源改用 `ctx.tools.schemas(agent)`;restrict 使用 `agent.ctx.tools`。
@@ -0,0 +1,4 @@
1
+ # 2.0.8 — 完整目录保留预设隐藏行
2
+
3
+ - `catalogTools` 只追加 agent 独有的工具;同名预设工具保留 `baseTools` 的 `hidden` / `controllable` 行。
4
+ - 避免本插件 restrict 后的隐藏工具在完整目录被误标为 `masked-plugin`,从而导致用户无法在工具 Tab 反选恢复。
@@ -0,0 +1,8 @@
1
+ # 2.0.9 — 工具泛例与写入补齐
2
+
3
+ - 工具目录测试改为多个 agent 工具(apply_patch 只是示例),验证任意 agent 作用域工具都会进入完整目录。
4
+ - `tool-visibility` 增加 preset / agent / global 多来源回归用例。
5
+ - `/status`、`/presets` 按指定 workspace 读取技能视图,修复工作区独有技能在开关表中缺项。
6
+ - 保存按钮也走统一写队列,避免与预设应用/删除/导入并发覆盖。
7
+
8
+ > 2.1.0 说明:本版工具目录 / restrict 口径不变;2.1.0 的会话级合入**只作用于系统提示词段**,工具目录仍按纯预设装配计算([ADR-0005](../adr/0005-universal-prompt-takeover.md) D6)。
@@ -0,0 +1,41 @@
1
+ # 2.1.0 — 全域系统提示词接管
2
+
3
+ 决策见 [ADR-0005](../adr/0005-universal-prompt-takeover.md)。版本 2.1.0,宿主契约不变(`engines.dsh: ^0.2.2-rc.1`)。
4
+
5
+ ## 变更
6
+
7
+ 1. **发现面**:`vendor/prompt-customizer/index.js` 新增 `collectLivePromptView` —— `buildPreview` 在「编辑目标 == 同预设活动 agent」时,复用同一份 `hint`(含草稿 patch、名义阶段)只覆盖 `scope` / `agent`,额外跑一次该 agent 自己 scope 的真实装配。一次 `promptCustomizerBase: true` 装配同时给出预过滤清单(`promptCustomizerBaseView`)与过滤后的正文(`sections`)。
8
+ 2. **清单**:新增 `mergePromptEntries`,把「预设 base ∪ agent base」的 `sections` / `contexts` 按 `{name, source}` 去重(agent 视图优先、同名不同源分列);`sectionOrderMap` 改取「预设链 ∪ agent 链」,否则 agent 段的规范 order 取不到会被排到尾部。
9
+ 3. **预览**:有 live 视图时 `sections` / `text` / `contexts` / `emitted` / `takenOverBy` 都取自 agent 真实装配(过滤后),右栏显示模型此刻真正看到的系统提示词;无活动会话 / 装配抛错时 `warnOnce` 并退回纯预设装配。
10
+ 4. **身份稳定化**:新增 `stableSourceOf`,把清单与预览里的 agent 来源统一收敛成 `{kind:'agent'}`(不带 session id)。`model.js:entryMatches` 已对 `agent ↔ agent` 按 kind 匹配(2.0.13),因此 UI 写入的屏蔽名单、草稿 `entryKey`、运行时候选三者身份一致且跨会话稳定。`buildSessionPreview` 也同步稳定化。
11
+ 5. **隔离**:只用 `liveAgentForPreset(ctx, 编辑预设 id)`;`latestRootAgent` 仍只用于工具 catalog 与无会话兜底,切到别的预设不混入别会话的会话级段。
12
+ 6. **工具侧不动**:`finalTools` / `buildToolUnion` / `catalogTools` 仍按纯预设装配计算,避免推翻 2.0.5–2.0.18 的工具目录回归约束。
13
+ 7. 客户端零逻辑改动(`SectionsPane` 的「会话级」徽标、`phase-ops.ts:phaseRows` 已能渲染这些行);只更新 `locales.ts` 的 `agentScopedHint` 文案。
14
+
15
+ ## 动机(可观察的缺陷)
16
+
17
+ `dsh-hashline-edit@0.2.0` 在 `agent/created` 里用 `agent.ctx.systemPrompt.section()` 注册三段动态 `text` provider:`tool:read` / `tool:apply_patch` / `tool:undo_last_edit`。2.0.22 的 `/inventory` / `/preview` 只跑纯预设装配(live 段恒传空数组),导致:
18
+
19
+ - `tool:undo_last_edit` 在可编辑清单里根本不存在;另两段显示的是宿主同名段的原文;
20
+ - 即使勾了屏蔽,UI 写的 `{name, source:{kind:'global'|'preset'}}` 与运行时候选的 `{kind:'agent'}` 命不中(`entryMatches` 要求 `sourceKey` 相等),模型照样收到插件指引。
21
+
22
+ 运行时过滤链(`applySectionPolicy` + `forceSections` 包装)本已支持 agent 来源,缺的只是「UI 发现 / 枚举」这一环。
23
+
24
+ ## 行为变化(用户可见)
25
+
26
+ - 提示词页左栏新增带「会话级」徽标的行(插件在 agent 作用域注册的段),可独立屏蔽 / 替换 / 排序;同名不同源分两行。
27
+ - 右栏预览从「纯预设装配」变为「同预设活动 agent 的真实系统提示词」,与 `/session` 一致;无活动会话时保持原行为。
28
+ - 屏蔽一个会话级段后,最终系统提示词正文与 `/session.sections` 都不再含它;撤销即恢复。
29
+ - 工具页、三阶段名单、预设隔离、`/config/apply` 写盘格式、配置 schema 全部不变。
30
+
31
+ ## 明确不纳入
32
+
33
+ - 工具 schema 的 `description` / 参数 JSON Schema:走 `GenerateOptions.tools`,不是系统提示词 token(宿主 "tool declarations remain provider-owned")。本插件仍只做工具显隐,不做签名编辑。
34
+ - 不并入系统提示词的 user-role 消息、工具结果、LLM 请求体里另加的文本。
35
+ - 无活动会话时枚举 agent 作用域段:宿主 `systemPrompt.layers` 没有「列出所有作用域」的 API,属于契约边界。
36
+
37
+ ## 验证与反例
38
+
39
+ - 反例:把 `buildPreview` 里 `liveBase.sections` 换回空数组,或让 `stableSourceOf` 返回带 session id 的原始 source,新增用例必须失败。
40
+ - 组合用例:会话级段进入 `/inventory` 且 `source.kind==='agent'` / `scope==='agent'`;同名不同源分两行;写入 `{kind:'agent'}` 屏蔽名单后 `/preview.text` 与 `/session.sections` 都不含该段、撤销恢复;换一个不同段名的插件段同样可枚举(非样例特判);切到别的预设不串台;无活 agent 不抛错;工具目录行数 / 状态与变更前一致。
41
+ - `npm run check`(构建 + 全量单测 + 宿主 / 客户端 smoke)为交付门禁;`docs/testing/feature-matrix.md` 的 H 节 13/13 是 1.x 历史结果,不作为 2.1.0 的当前验证结论。
@@ -0,0 +1,48 @@
1
+ # 2.1.1 — 同名遮蔽探测
2
+
3
+ 版本 2.1.1,宿主契约不变(`engines.dsh: ^0.2.2-rc.1`)。本次只改**工具遮蔽池的探测**,不改 2.1.0 的提示词接管链路(ADR-0005 D6 保持)。
4
+
5
+ ## 缺陷(2.1.0 现状)
6
+
7
+ `buildToolUnion` 用单点判定识别同名遮蔽:
8
+
9
+ ```js
10
+ const shadowed = hasLive && inPreset && inLive && presetDefs !== null && liveDefs !== null
11
+ && presetDefs.get(name) !== liveDefs.get(name)
12
+ ```
13
+
14
+ 它会把以下场景漏掉,导致被插件同名工具顶掉的原生工具不进只读遮蔽池:
15
+
16
+ 1. **`ctx.tools.definitions` 缺席 / 抛错 / 返回的数组里没有该 name**:`presetDefs`/`liveDefs` 为 `null` 或 `get(name)` 为 `undefined`,源码注释也承认「没有 definitions 时无法判定,退化为同名即同一工具」。
17
+ 2. **`inPreset = false`**:宿主 `apply_patch` 是条件注册(`applyPatch: true` 才挂);当前 profile 没挂载原生 `apply_patch` 时,插件 agent 作用域注册的那一份被判成 `agentOnly → locked-agent`(启用列表),不是遮蔽。
18
+ 3. **0.2.1 的 `tools.restrict`**:宿主契约 `ToolRestriction` 只作用于继承层,「scope's OWN registrations stay exempt」(`docs/subsystems/tools.md:165`);插件自己的 `apply_patch` 仍在 agent 视图 → `inLive=true` → `restricted` 不成立,只能靠 `shadowed`,而 `shadowed` 有 1/2 的退化。
19
+ 4. **来源标错**:`emit` 的 `masked-plugin` 行 source 走 `sourceForTool(..., role:'agent')`,同名遮蔽时被判成 `agentSourceOf(agent)` → 被顶掉的明明是继承层原生工具,却落到「会话」分组。
20
+
21
+ ## 决策
22
+
23
+ - **多证据判定**(`buildToolUnion`):只要继承层(global ∪ preset)查得到同名定义 / schema,且 agent 视图里的同名工具与它「不是同一注册」,就判同名遮蔽。证据三条取一:① `definitions` 引用不同(精确,保留);② `definitions` 缺席/缺项时 `description` 不同(宿主 `schemas()` 只暴露 name/description/parameters);③ 只有继承层定义、agent 层另有注册(`inPreset=false`)。**继承层查不到同名定义时绝不误判**——纯 agent 新增(如 `undo_last_edit`)仍是 `locked-agent`。
24
+ - **新状态 `masked-shadow`**(区别于 `masked-plugin` = 被别的插件 restrict):`source` 用 `sourceOverride` 标继承层(`global` / `preset:<id>`),`description` 取被顶掉的原定义,`shadowedBy = { name, source: agentSourceOf(liveAgent) }` 指向顶掉它的插件工具,`keySuffix = 'shadowed'` 保证与 `locked-agent` 行的 `entryKey` 不冲突。
25
+ - **客户端**:`ToolState` / `toolStateOf` / `buildMaskedPool` 纳入 `masked-shadow`;徽标「同名遮蔽」与提示说明「只读、不能反开」;分组按继承层 source 落到「预设 / 全局」而不是「会话」。
26
+ - **口径(推荐)**:继承层查不到同名定义时**不凭空造行**,插件工具只显示「会话级」的 `locked-agent` 行。0.2.0 的 `apply_patch` 在没挂载原生 `apply_patch` 的 profile 上仍不会出现在遮蔽池——这是诚实的语义,不是缺陷。
27
+
28
+ ## 行为变化(用户可见)
29
+
30
+ - 同名遮蔽(如 0.2.0 hashline 的 `read`、0.2.1 的 `apply_patch`)在 `definitions` 退化、profile 未挂载原生、restrict 场景下都会出现在「完整目录 / 已遮蔽(只读)」,徽标「同名遮蔽」,来源落在正确的继承层分组,并带 `shadowedBy`。
31
+ - `masked-plugin`(被别的插件 restrict)、`masked-own`(本插件 MCP 遮蔽)、工具目录可开关项与阶段过滤全部不变。
32
+
33
+ ## 明确不纳入
34
+
35
+ - 不改 `finalTools` / 工具目录数据源(ADR-0005 D6)、不改 `tools.restrict` / 阶段过滤 / `tools/pre-execute`、不改 `buildPreview` 的 `editPresetId` 与 live 提示词视图;不接管工具 schema 的 `description`。
36
+ - 不新增 ADR:本次是探测逻辑增强,决策与边界记在本升级记录即可(ADR-0005 仍是提示词接管的唯一决策文档)。
37
+
38
+ ## 影响模块
39
+
40
+ - `vendor/prompt-customizer/index.js`:`buildToolUnion`(判定 / `emit` 第 6 参 `shadowedBy` / `rankOf`)、`catalogTools` 保留条件、`buildInventory` 透传 `maskedByShadow` / `shadowedBy`。
41
+ - `src/client/prompt/`:`types.ts`(`ToolState` / `BaseTool` / `Inventory.tools`)、`tool-pool.ts`、`locales.ts`(中英徽标与提示)、`ToolsPane.tsx`(`stateBadge`)。
42
+ - 测试:`test/tool-shadow.test.mjs` 断言改为 `masked-shadow`;新增 `test/tool-shadow-universal.test.mjs`(6 组 / 8 场景)。
43
+
44
+ ## 验证与反例
45
+
46
+ - 本机 `npm run check`:全量单测 **170/170 通过**、构建产物重建、宿主 / 客户端 smoke 全绿。
47
+ - 反例:把判定换回单点 `presetDefs.get(name) !== liveDefs.get(name)`,`test/tool-shadow-universal.test.mjs` 的 ①②③ 必须失败;为 `read` 写死特判,用例 ⑤(换名字)必须失败。
48
+ - 组合用例:① `definitions` 缺席;② agent `definitions` 缺该 name;③ `schemas` 未挂载原生但 `definitions` 有(`inPreset=false`);④ 0.2.1 restrict 场景;⑤ 任意工具名(`apply_patch` / `edit` / `plugin:custom`);⑥ 不误判 agent 新增 / 同一注册不拆行 / 跨预设不串台 / 无活动 agent 不抛错 / 工具目录状态不变。
@@ -0,0 +1,55 @@
1
+ # 2.1.2 — 工具目录与真实会话同源
2
+
3
+ 版本 2.1.2,宿主契约不变(`engines.dsh: ^0.2.2-rc.1`)。本次修两个由**实读宿主源码**定位的可观测缺陷,不再靠名字猜。
4
+
5
+ ## 缺陷 1:右栏只显示 38 个工具(真实注入 50)
6
+
7
+ 2.1.0/2.1.1 的 `/preview` 把**提示词段**改成了同预设活动 agent 的真实装配(`ADR-0005 D3`),但**工具目录**仍取 `assembled.tools`——`assembled = ctx.systemPrompt.assemble(hint)`,`hint.scope = presetScopeKey`(预设 scope key)。
8
+
9
+ 宿主源码证据:
10
+ - `packages/core/agent/src/dispatch.ts:174-175`:`assembleContextFor(agent, signal)` 返回 `{ agent, scope: agent }`——**真实会话装配的 scope 是 live Agent 对象自身,不是 preset scope key**。
11
+ - `packages/core/agent-loop/src/agent.ts:292-294`:`toolSet = await ctx.tools.resolve({agent, modelSelection, signal})`,`assembly = await ctx.systemPrompt.assemble({ ...assembleContextFor(this, signal), modelSelection: selection, toolSet })`。
12
+ - `assembly.tools` → `buildRequest(...)` → `GenerateOptions.tools`(`agent.ts:479`、`:656`、`:731-736`;`docs/subsystems/llm-streaming.md:678/722`)。
13
+ - `packages/core/system-prompt/src/index.ts:621` `const scope = context.scope`;`:640-644` providers = global + `scopeLayers`(`chainLayers(scope)`);`:674` `orderTools(...)`;`:687` `return { ...transformed, tools }`。**scope 决定 provider 集合**,所以 agent scope 的可见集 ⊇ preset scope。
14
+
15
+ `ctx.tools.schemas(agent)`(`packages/core/tools/src/index.ts:305-317`)返回该 agent 的注册视图全集(global + 祖先/preset + agent own,shadow 与 restrictions 已生效),**不带 toolSet 时含条件注册工具**,是「不区分 step 的可见工具目录」的正确入口(`docs/subsystems/tools.md:695/708`);`schemas(undefined)` 只是 global view,不能用来代表会话。
16
+
17
+ ⚠️ 不能用 `assemble({scope: agent}).tools` 当主源:PTC 模式下 executor 的 `describe()` 忽略传入定义、只返回 `run_code`(`packages/core/tools/src/tool-executor-ptc/src/index.ts:47-49`),会塌缩;`ctx.tools.schemas(agent)` 不经 executor,不受影响。插件也拿不到 `modelSelection`/`signal`,无法调 `resolve()` 复刻 step 准入,故用不带 toolSet 的注册视图(与 `/session` 的 `visibleTools` 同源)。
18
+
19
+ **改动**:`buildPreview` 新增 `visibleFinalTools`,主源 `toolSchemasOf(ctx, livePromptAgent, true)`,回落 `previewSource.tools` / `finalTools`;右栏 `tools`、`registryTools`、`registryTotal` 全部改用这份。`finalTools`(预设装配)仍保留给 `buildToolUnion` 的 `extraTools` 与排序,遮蔽池结构不变。无活动 agent 时回落纯预设装配。
20
+
21
+ ## 缺陷 2:`apply_patch` 不出现在只读池
22
+
23
+ 用户前提「插件遮蔽了原生 `apply_patch`」不成立,根因在宿主配置:
24
+
25
+ - `packages/fs/tool-fs/src/index.ts:53` `applyPatch: z.boolean().default(false)`;`:101` `if (resolved.applyPatch) applyApplyPatchTool(ctx, sandbox)`——**只有 true 才注册原生定义**。
26
+ - 出厂组合行 `packages/bundle/web-app/presets/standard.patch.yml:27-31`(以及 `ptc/cordis`、`bundle/base/cordis.patch.yml:300`)全是 **`applyPatch: false`**;运行时 `~/.dsh/profiles/web/cordis.patch.yml` 三处 tool-fs 行同样 false。`packages/bundle/web-app/cordis.patch.yml:517-518` 把 host 的 tool-fs 行 `disabled: true`,工具由 preset 层重挂。
27
+ - `docs/tool-catalog.md:33/1178` 与 `docs/upgrade-guide/v0.2.2-alpha.2/fs-observation-policy-default-off/guide.md:14` 声称 Web standard `applyPatch: true`,**与实际产物矛盾**(该目录自己也注明「the catalog sets it so the schema is documented」)。以实装 YAML/运行时 profile 为准。
28
+ - `dsh-hashline-edit@0.2.0` 的 `dsh/index.ts` 明确只把 `read` 当遮蔽对象(「shadow the preset's built-in `read`」);`apply_patch` 由 `registerApplyPatchTool` 在 agent 作用域**首次注册**,`cordis.patch.yml` 不覆盖 tool-fs 行。
29
+
30
+ ⇒ 继承层(global/preset)**不存在**名为 `apply_patch` 的原生定义;插件注册的是**纯新增**(`agentOnly → locked-agent`),不是同名遮蔽,没有「被顶掉的原定义」可取(`definitions(presetScopeKey)` 里也没有,teammate 实读 `agent-preset-registry` 确认 `lease.key` 与 tool-fs 注册 scope 是同一个)。2.1.1 的「不凭空造行」口径因此不显示它——语义上正确,但用户无法在只读池看到这个被插件接管的工具。
31
+
32
+ **改动(口径)**:客户端 `buildMaskedPool` 纳入 `locked-agent`,把这类工具在「完整目录 / 已遮蔽(只读)」里按**「会话级」**如实列出(徽标「会话级」而非「同名遮蔽」,不伪造遮蔽)。`completeCatalogHint` 本来就宣称该池包含「agent 作用域注册」,这次让实现与文案一致。同时修正 `toolAgentScopedHint` 的误导文案:这些工具**可以**按阶段隐藏(`isToggleable` 含 `locked-agent`,靠装配后过滤 `tools` 实现),不是「只能查看」。
33
+
34
+ `read` 这类**真·同名遮蔽**仍是 `masked-shadow`(继承层有 `read` 原定义),与纯新增区分;`test/tool-catalog-scope.test.mjs` ⑤ 钉住这条边界。
35
+
36
+ ## 明确不纳入
37
+
38
+ - 不改 `buildToolUnion` 的判定、`tools.restrict` / 阶段过滤 / `tools/pre-execute`、`editPresetId` 与 live 提示词视图;不接管工具 schema 的 `description`;不新增 ADR(探测/数据源修正记在升级记录即可)。
39
+ - 不按 `docs/tool-catalog.md` 的静态描述硬编码工具列表——宿主配置可覆盖,以运行时 `schemas(agent)` 为准。
40
+
41
+ ## 影响模块
42
+
43
+ - `vendor/prompt-customizer/index.js`:`buildPreview` 的 `visibleFinalTools` / `registryTools` / `registryTotal` / `tools` 字段与注释。
44
+ - `src/client/prompt/`:`tool-pool.ts`(`buildMaskedPool` 纳入 `locked-agent`)、`ToolsPane.tsx`(`stateBadge` 四态)、`locales.ts`(`toolAgentScopedHint` 中英、`completeCatalogHint` 不变)。
45
+ - 测试:反转 5 个旧断言(`rc2-preview` / `session-preview` / `tool-mask-pool` / `universal-prompt-takeover`),新增 `test/tool-catalog-scope.test.mjs`(5 用例)。
46
+
47
+ ## 验证与反例
48
+
49
+ - 本机 `npm run check`:全量单测 **175/175 通过**、构建产物重建、宿主 / 客户端 smoke 全绿。
50
+ - **反例实测**:把主源 `toolSchemasOf(ctx, livePromptAgent, true)` 换回 `toolSchemasOf(ctx, scopeKey, true)`(presetScopeKey),`test/tool-catalog-scope.test.mjs` 的 **①③④ 全红、②⑤ 仍绿**;还原后 175/175。
51
+ - 覆盖:① 右栏 = agent scope 注册视图(含 `apply_patch`/`undo_last_edit`);② 无 live agent 回落纯预设;③ 与 `/session` 同源;④ PTC 塌缩防护(`assemble().tools` 只剩 `run_code` 时仍用 `schemas(agent)`);⑤ 纯新增不伪造 `masked-shadow`、`read` 仍是真同名遮蔽。
52
+
53
+ ## 附:文档与产物矛盾(供后续排查)
54
+
55
+ `docs/tool-catalog.md` 与 `v0.2.2-alpha.2` 升级指南称 Web standard 启用 `applyPatch: true`,但 `0.2.2-rc.1` 的 shipped YAML 与运行时 profile 全是 `false`。若用户在 Agent presets 设置页或 `standard.patch.yml` 把它改成 `true`,继承层就会注册原生 `apply_patch`,插件的注册变为真同名遮蔽(`masked-shadow`)——这与本版逻辑兼容,届时遮蔽池会出现「同名遮蔽」行而不是「会话级」行。
@@ -0,0 +1,298 @@
1
+ # 版本升级与适配记录
2
+
3
+ 本目录记录 dsh-prompt-customizer 与 DeepSeek Harness(dsh)的版本对应、逐版变更和适配细节。根目录 README 只做产品介绍,升级相关内容统一放这里。
4
+
5
+ ## 当前兼容版本
6
+
7
+ - 插件 **2.1.2** 对齐 **DSH 0.2.2-rc.1**(`peerDependencies` / `engines.dsh` 为 `^0.2.2-rc.1`);只扩展本插件自己的提示词枚举与工具探测,未改动宿主 API 依赖。
8
+ - 历史起点:**2.0.0** 起对齐该宿主版本。
9
+ - 0.2.2-rc.1 的完整适配记录见 [dsh-0.2.2-rc1.md](dsh-0.2.2-rc1.md)。
10
+ - 更早的 0.2.1-rc.2 适配记录见 [rc2-migration.md](rc2-migration.md)。
11
+
12
+ ## 版本历史
13
+
14
+ ## 2.1.2:工具目录与真实会话同源
15
+
16
+ - 右栏「模型实际可见的工具目录」改用 `ctx.tools.schemas(agent)`(agent scope 注册视图),与 `/session` 同源;修掉只显示预设层工具(38 vs 50)与 `38 / 39 注册表` 错配。
17
+ - 只读「完整目录 / 已遮蔽」池如实列出插件在 agent 作用域**纯新增**的工具(会话级),不伪造「同名遮蔽」;同时修正「会话级工具只能查看」的误导文案(可按阶段隐藏)。
18
+ - 工具提示词段(`tool:*`)按工具组映射(`jobs` / `pty` / `goal` / `schedule` / `subagent`):工具页显示提示词段入口与只读 description,提示词页显示关联工具与空段;`forceSections` 仅在有实际段策略时覆盖,无定制时保留宿主 `complete` 原样。
19
+ - 完整记录见 [2.1.2-tool-catalog-scope.md](2.1.2-tool-catalog-scope.md)。
20
+
21
+ ## 2.1.1:同名遮蔽探测
22
+
23
+ - 工具遮蔽池不再只靠 `definitions` 对象引用相等来识别同名遮蔽;`definitions` 缺席 / 缺项、profile 未把原生工具挂进 `presetTools` 时都能识别,被顶掉的继承层工具进入只读遮蔽池(`masked-shadow`)。
24
+ - 遮蔽行来源标继承层(global / preset)而不是 agent,并携带 `shadowedBy`;工具目录数据源、阶段过滤与 2.1.0 的提示词接管链路全部不变。
25
+ - 完整记录见 [2.1.1-tool-shadow-detection.md](2.1.1-tool-shadow-detection.md)。
26
+
27
+ ## 2.1.0:全域系统提示词接管
28
+
29
+ - 清单与预览并入「同预设活动 agent」的真实装配,插件在 agent 作用域动态注册的段可被枚举、屏蔽 / 替换 / 注入 / 排序;同名不同源分列,agent 来源按 `{kind:'agent'}` 跨会话稳定匹配。
30
+ - 工具目录仍按纯预设装配计算(两条数据源分离),2.0.x 的工具目录 / restrict / 遮蔽池行为不变。
31
+ - 决策与边界见 [ADR-0005](../adr/0005-universal-prompt-takeover.md),完整记录见 [2.1.0-universal-prompt-takeover.md](2.1.0-universal-prompt-takeover.md)。
32
+
33
+ ## 2.0.22:原文不可变回归
34
+
35
+ - 增加插件不得原地修改宿主 `sections` 的回归断言。
36
+ - 完整记录见 [2.0.22-source-immutability.md](2.0.22-source-immutability.md)。
37
+
38
+ ## 2.0.21:预设隔离与卸载恢复回归
39
+
40
+ - 增加 A/B 预设提示词与工具隔离、卸载后提示词和工具恢复的端到端回归。
41
+ - 完整记录见 [2.0.21-isolation-restore-test.md](2.0.21-isolation-restore-test.md)。
42
+
43
+ ## 2.0.20:预设隔离修复
44
+
45
+ - 活跃 Agent 预设优先按 `composedPreset` 解析,修复预设投影未刷新时的跨预设串台。
46
+ - 完整记录见 [2.0.20-preset-isolation.md](2.0.20-preset-isolation.md)。
47
+
48
+ ## 2.0.19:MCP JSON 编辑恢复
49
+
50
+ - 恢复全局 MCP 编辑的 JSON/YAML 双格式入口,宿主扫描和应用仍支持两种格式。
51
+ - 完整记录见 [2.0.19-mcp-json-edit.md](2.0.19-mcp-json-edit.md)。
52
+
53
+ ## 2.0.18:解除屏蔽清理旧条目
54
+
55
+ - 解除隐藏时兼容旧 source id,按工具名兜底清理阶段 exclude。
56
+ - 完整记录见 [2.0.18-unhide-cleanup.md](2.0.18-unhide-cleanup.md)。
57
+
58
+ ## 2.0.17:解除屏蔽来源修复
59
+
60
+ - 修复解除工具屏蔽后旧 agent source 条目仍命中导致的持续隐藏。
61
+ - 完整记录见 [2.0.17-unhide-source.md](2.0.17-unhide-source.md)。
62
+
63
+ ## 2.0.16:回归清理
64
+
65
+ - 屏蔽池、workspace 技能状态、provider 摘要、同名 tool source、配置写队列和静默轮询竞态补齐。
66
+ - 完整记录见 [2.0.16-regression-cleanup.md](2.0.16-regression-cleanup.md)。
67
+
68
+ ## 2.0.15:工具屏蔽池恢复
69
+
70
+ - 修复完整目录过滤把 `masked-plugin`/`masked-own` 行一并吞掉导致屏蔽池消失。
71
+ - 完整记录见 [2.0.15-mask-pool-regression.md](2.0.15-mask-pool-regression.md)。
72
+
73
+ ## 2.0.14:工具执行前门禁
74
+
75
+ - 通过 `tools/pre-execute` 对 restrict 无法触及的 agent 作用域工具执行运行时拒绝。
76
+ - 完整记录见 [2.0.14-tool-execution-guard.md](2.0.14-tool-execution-guard.md)。
77
+
78
+ ## 2.0.13:DSH 运行时契约对齐
79
+
80
+ - 工具目录/来源/restrict 按 DSH 运行时契约泛化,不绑定具体工具名;同名遮蔽保留 keySuffix。
81
+ - 完整记录见 [2.0.13-runtime-contract.md](2.0.13-runtime-contract.md)。
82
+
83
+ ## 2.0.12:工具泛例测试
84
+
85
+ - 工具目录与 restrict 改为生成式 N 工具测试,不依赖具体工具名。
86
+ - 完整记录见 [2.0.12-generative-tool-tests.md](2.0.12-generative-tool-tests.md)。
87
+
88
+ ## 2.0.11:回归修复
89
+
90
+ - 修复预设工作区状态污染、技能摘要跨会话丢失、同名 agent 遮蔽行、health 路由悬挂与刷新错误覆盖。
91
+ - 完整记录见 [2.0.11-regression-fixes.md](2.0.11-regression-fixes.md)。
92
+
93
+ ## 2.0.10:导入原子与技能摘要/健康补齐
94
+
95
+ - 预设导入改为批量全局写入;技能摘要持久化;健康计数纳入 bundled/office/plugin。
96
+ - 完整记录见 [2.0.10-import-and-skill.md](2.0.10-import-and-skill.md)。
97
+
98
+ ## 2.0.9:工具泛例与写入补齐
99
+
100
+ - 工具目录按所有 agent 作用域工具泛化,不再只覆盖 apply_patch 示例;工作区状态与保存队列补齐。
101
+ - 完整记录见 [2.0.9-tool-and-write.md](2.0.9-tool-and-write.md)。
102
+
103
+ ## 2.0.8:完整目录保留预设隐藏行
104
+
105
+ - 完整目录补扫 agent 工具时不覆盖同名预设工具状态,隐藏工具仍可反选。
106
+ - 完整记录见 [2.0.8-catalog-row-stability.md](2.0.8-catalog-row-stability.md)。
107
+
108
+ ## 2.0.7:工具屏蔽稳定性与预设隔离
109
+
110
+ - 修复 restrict 后下一次 sync 撤销 deny 的回归;工具目录限定同预设活动会话。
111
+ - 完整记录见 [2.0.7-tool-stability.md](2.0.7-tool-stability.md)。
112
+
113
+ ## 2.0.6:预设工具屏蔽在新会话生效
114
+
115
+ - 修复预设工具屏蔽只对部分来源生效/新会话仍注入的问题;完整目录仍包含 agent 作用域工具。
116
+ - 完整记录见 [2.0.6-tool-restrict.md](2.0.6-tool-restrict.md)。
117
+
118
+ ## 2.0.5:工具目录补扫与健壮性修复
119
+
120
+ - 预设工具目录补扫 agent 作用域工具(apply_patch 等);导入/恢复校验、工作区轮询、写入串行化、上下文预检等修复。
121
+ - 完整记录见 [2.0.5-tool-catalog-robustness.md](2.0.5-tool-catalog-robustness.md)。
122
+
123
+ ## 2.0.4:工具 Tab 草稿实时反映
124
+
125
+ - 工具草稿预览与保存共用 `overlayPatch` 语义,未保存勾选即时反映到列表/计数/右侧预览。
126
+ - 完整记录见 [2.0.4-tool-draft.md](2.0.4-tool-draft.md)。
127
+
128
+ ## 2.0.3:禁用技能卡片保留原摘要
129
+
130
+ - 禁用技能只改变状态徽标,摘要/元数据保持启用时内容。
131
+ - 完整记录见 [2.0.3-skill-summary.md](2.0.3-skill-summary.md)。
132
+
133
+ ## 2.0.2:通用运行时上下文过滤
134
+
135
+ - 按宿主 `PromptContext.required` 通用过滤空的可选上下文,不做名字特判。
136
+ - 完整记录见 [2.0.2-generic-context-filter.md](2.0.2-generic-context-filter.md)。
137
+
138
+ ## 2.0.1:运行时上下文残留过滤与草稿显隐
139
+
140
+ - 过滤工具拥有的空 `tool:<name>:depth` 上下文;上下文显隐改为保存时落盘。
141
+ - 完整记录见 [2.0.1-context-fixes.md](2.0.1-context-fixes.md)。
142
+
143
+ ## 2.0.0:破坏式 —— 移除工作区级提示词/工具层
144
+
145
+ - 只保留单预设编辑;工作区级提示词/工具的 UI、路由、运行时装配与备份全部移除。
146
+ - `workspaceOverrides` 字段休眠:数据保留但不读、不写、不显示、不进备份。
147
+ - 单预设导入/导出、全部预设备份包与手动清理空 overrides 留在设置页。
148
+ - 完整记录见 [2.0.0-remove-workspace-level.md](2.0.0-remove-workspace-level.md)。
149
+
150
+ ## 1.0.0:破坏式 —— 存储迁移 SQLite + 预设/工作区两级自定义
151
+
152
+ - **存储统一到单文件 SQLite**(`~/.dsh/prompt-customizer/prompt-customizer.db`,WAL,`node:sqlite`,无外部依赖):`config`、`catalog`、工作区 override、MCP `preset-masks` / `tool-disable` / `workspace-close`、技能 `preset-toggles` / `hidden-toggles` 全部入 DB;宿主配置(`cordis.patch.yml` / `.mcp.json`)与技能根 `.bundles.json` 保持文件。
153
+ - **破坏式**:旧 `config.yaml` / `catalog.yaml` 与各 JSON 账本不再读取、不迁移(文件原样保留),升级后需重新配置。详见 [1.0.0-sqlite-breaking.md](1.0.0-sqlite-breaking.md)。
154
+ - **两级自定义**:新增工作区级 override(`workspaceOverrides[cwd]`),运行时按同名覆盖预设级;`/config/apply` 增 `level=workspace`;面板顶部加「预设级 / 工作区级」切换,工作区级可控制会话里全部段/工具。
155
+ - 段与工具按宿主规范顺序排列;同名工具拆分显示(插件提供=启用锁定、原生=遮蔽池);`assemble` 后按阶段过滤 `assembly.tools`,作用域内注册的工具也能真正隐藏。
156
+
157
+ ## 0.12.0:适配 DSH 0.2.2-rc.1
158
+
159
+ - `system-prompt/assemble` 不再携带 `tools`:提示词阶段化工具目录、工作区级 MCP 关闭都迁到 agent 作用域 `tools.restrict`;预览 / 清单工具目录改读 `ctx.tools.schemas()`。
160
+ - 新增 `vendor/prompt-customizer/tool-visibility.js` 与 `test/tool-visibility.test.mjs`;`package.json` 升到 `0.12.0`,peer / engines 对齐 `0.2.2-rc.1`。
161
+ - 完整适配记录见 [dsh-0.2.2-rc1.md](dsh-0.2.2-rc1.md)。
162
+
163
+ ## 0.11.6:测试精简(176 → 117 用例)
164
+
165
+ 两轮清理,`node --test` 始终全绿:
166
+
167
+ 1. **删除 6 个过时测试文件**(−19%):`scripts/test-mcp-layering.mjs`、`scripts/test-mcp-tool-disable.mjs`(0.11.0 已废弃的 mcp-client 行挂载模型)、`scripts/test-skill-toggles.mjs`(旧 frontmatter 语义,与现有覆盖重复)、`scripts/test-rc2-runtime.mjs`(硬编码只认 0.2.0-rc.2,自述在 0.2.1-rc.2 上必然失败)、`test/catalog-replace.test.mjs`(mergeSighting 与 `catalog.test.mjs` 逐条重复)、`test/live-preview.test.mjs`(被 `rc2-preview` / `apply-route` 覆盖)。
168
+ 2. **同组用例参数化合并**(−27%):结构相同的多个 `test()` 合并为「数据表 + 循环」,每个表项执行原用例的**全部**断言,并给断言传场景 label(失败时能看出是哪一项)。涉及 `sectionOps`(27→24)、`chaos-super`(17→11)、`catalog`(11→2)、`effective`(8→2)、`preview-phase`(9→6)、`rc2-preview`(9→7)、`schema`(7→4)、`trust-gate`(3→1)、`windows-titlebar`(3→2)等 16 个文件。
169
+
170
+ **没有删除或弱化任何断言**:合并处逐条 diff 核对过输入表(文件名净化仍覆盖 5 个原始输入、`isTauriEnv` 仍覆盖 6 个探针、trust-gate 三种拒绝各自仍校验「不得写盘」)。生产代码零改动。
171
+
172
+ 保留的「看起来可疑但必要」:`test/mcp-workspace-scope.test.mjs` 测的是 0.11.4 的 `src/mcp-workspace-scope.ts`(不是已删的 `mcp-workspace.ts`),`host.ts` 至今仍在 import 它。
173
+
174
+ ## 0.11.5:工作区 MCP 落盘改为主流 agent 通用的 `.mcp.json`
175
+
176
+ - **格式对齐大众 agent**:工作区「添加 MCP」不再写 DSH 私有的 `patch.yml`,改写项目根的 **`.mcp.json`**(`{ "mcpServers": { <name>: { command|url, … } } }`)—— Claude Desktop / Cursor / Cline 等直接就能读。
177
+ - **第一次是新建**,后续在同一个文件里**追加**(保留已有 server、保留用户手写的其它顶层字段)。
178
+ - 同名已存在 → 跳过,不覆盖用户内容;文件是坏 JSON → 报错并拒绝写入,绝不覆盖。
179
+ - **读取时转换**:内部仍按 `mcp-client` 行挂载,读 `.mcp.json` 时把键名当 serverName 并加工作区哈希后缀(同名跨工作区不冲突),`command/args/env/cwd/url/headers` 原样透传。
180
+ - **全局不转**:全局添加仍写 profile patch(DSH 自己的组合层),保持原行为。
181
+ - **去重按目标文件算**:工作区只和该工作区 `.mcp.json` 里的键比,不再因为「全局已有 anysearch」把工作区的同名项误判为重复。
182
+ - **验证**:新增 `test/workspace-mcp-json.test.mjs`(新建 / 追加 / 同名不覆盖 / 保留其它顶层字段 / 坏 JSON / 读回转换);`node --test` 176/176。接口实测:连续两次添加落在同一 `.mcp.json`,内容为标准格式(无 `serverName`/`transport` 等内部字段);全局添加仍写 profile patch。
183
+
184
+ ## 0.11.4:添加技能 / MCP 可选「全局 / 当前工作区」
185
+
186
+ - **添加入口加落点选择**:默认 **全局**,可选 **当前工作区**。
187
+ - 技能:全局写 `~/.agents/skills`;工作区写 `<ws>/.agents/skills`,**目录不存在自动创建**(全新工作区也能直接装)。
188
+ - MCP:全局写 profile patch;工作区写 `<ws>/.dsh/mcp.patch.yml`(新文件,同样自动建目录),由装配按该工作区会话挂载 —— 同一个 `anysearch` 可在两个工作区各配一份,互不覆盖。(**0.11.5 起工作区落盘已改为项目根的标准 `.mcp.json`,见上节。**)
189
+ - **缺 workspace 时明确报错**:`scope=workspace` 但没给路径 → 400,不会悄悄退回全局。
190
+ - **验证**:新增 `test/add-scope.test.mjs`(默认全局 / 工作区自动建目录 / 工作区不污染全局 / 缺参数报错);`node --test` 174/174。接口实测:工作区装技能落到 `<ws>/.agents/skills/zz-ui-ws/SKILL.md` 且全局无残留;工作区加 MCP 落到 `<ws>/.dsh/mcp.patch.yml` 且 profile patch 无污染。
191
+
192
+ ## 0.11.3:工作区下拉能列出全部工作区,技能按工作区真实切换
193
+
194
+ - **下拉只有「跟随当前会话」一项** → 修好:选项改从内核 `workspaceRegistry.list()`(侧边栏里用户登记的全部工作区)取,再补活动会话 cwd 与启动目录。没开过会话的工作区现在也能选(实测列出 5 个:PatientLifecycle / dsh-pulgn / bot / rea / 默认工作区)。
195
+ - **切工作区后技能不跟着变** → 修好:内核 `skill-filesystem` / `skill-office` 是**挂在预设行里**的 provider(`apply(ctx)` 注册进该行的层),只有「存在会话 + scope 命中预设层」时才读得到;面板要给任意工作区配置,因此改为
196
+ - 内核结果**完整**(有会话且出现 `project-*`/`user-*`/`custom` 来源)时以内核为准;
197
+ - 否则按内核同一套根规则兜底扫描 `<ws>/.dsh/skills` → `<ws>/.agents/skills` → `~/.dsh/skills` → `~/.agents/skills`,再把内核独有的条目(bundled / office / 插件 provider)补在后面。
198
+ - **验证**:往临时工作区放一个探针技能,该工作区 7 个、dsh-pulgn 仍 6 个(不串);`node --test` 172/172。
199
+
200
+ ## 0.11.2:面板内切换工作区 + 工作区/全局标注 + 工作区级 MCP 关闭
201
+
202
+ - **顶栏工作区选择器**:放在 SKILL / MCP / 提示词 那一行右侧(不在操作行)—— 它同时决定技能目录与 MCP 注入,必须一眼可见。选项来自宿主(活动会话 cwd ∪ 启动目录),默认「跟随当前会话」。
203
+ - **技能与 MCP 都标来源**:卡片上新增「工作区 / 全局」标签。技能来源取内核 `source`(`project-*` = 工作区专用,其余 = 全局);MCP 行都是组合行,标「全局」。同名时工作区优先,关全局不影响工作区专用条目。
204
+ - **工作区级 MCP 关闭**:每张 MCP 卡片新增「本工作区关闭 / 本工作区启用」。账本写 `<agentsHome>/skills/.workspace-mcp.json`(`{ 工作区: { serverName: false } }`),由 `system-prompt/assemble` 按该次装配所属会话的 cwd 剔除 `mcp__<server>__<tool>`。
205
+ - 关掉 dsh-pulgn 的 `anysearch` → 该工作区不再注入;换到别的工作区照常注入(**工具集合随工作区变化**),其它工作区不受影响。
206
+ - 与「全局停用」(改 profile patch 的 `disabled`)互不干扰:一个是所有人的默认,一个只针对某个工作区。
207
+ - **修一个写入缺陷**:Windows 上 `rename` 覆盖已存在的账本会 `EPERM`,导致同一工作区第二次调整失败。改为「先删旧文件再改名,失败退化为直接写」,并补了「关→开→再关」的回归测试。
208
+ - **验证**:`node --test` 172/172(新增 `test/mcp-workspace-scope.test.mjs` 覆盖工作区隔离与连续写入);web 实例真浏览器实测:顶栏出现工作区下拉,技能卡片显示「全局」,MCP 卡片显示「全局」+「本工作区已关」,点「本工作区关闭」后账本落盘、`status?cwd=` 回报 `closed:["anysearch"]`、换工作区 `closed:[]`。
209
+
210
+ ## 0.11.1:修技能目录只见 global 层 + 开关改为「全权接管、无只读」
211
+
212
+ - **修「斜杠命令能看到 5 个技能,能力栏只有 1 个」**:内核 registry 的读取是 `[global 层, ...chainLayers(scope)]`(`packages/skill/skill/src/index.ts:556`)—— **不传 `scope` 就只读 global 层**。技能绝大多数注册在预设/agent 层,于是面板只剩随安装的那个 bundled 技能。现在把该 agent 的 scope key(agent 对象本身,`createScope(loopCtx, this)`)传给 `list()`,面板与 `/` 菜单、上下文里的「技能注入」条数一致。
213
+ - **开关改为隐藏账本,不再改用户的 SKILL.md**:全局开关写 `<agentsHome>/skills/.hidden-skills.json`,由闸门 provider 在内核目录里遮掉。好处是**所有来源**(含 `app.asar` 内随安装技能、office 包、插件自带)都能关,且随时可逆、不动别人的文件。
214
+ - **取消「只读」概念**:面板不再区分来源,任何技能都能开关;删除仍然只对真正可写根生效(随安装的技能删不掉文件),此时返回明确提示「该技能随安装/插件提供,请改为关闭」。
215
+ - **双保险**:除闸门 provider 外,面板目录(`kernel-skills`)自己也按隐藏账本过滤一次 —— 内核合并只按层/rank 去重,同名候选未必每次压过真实来源。
216
+
217
+ ## 0.11.0:技能目录直接读内核,MCP 只留组合行
218
+
219
+ 方向调整:**不再自搓扫描**。内核已经有「聚合全部技能来源」的服务与「组合行」这一层配置模型,插件自扫只会在根顺序、去重、只读来源上不断与内核打架。
220
+
221
+ - **技能列表 = 内核真值**:面板改为 `agent.ctx.skills.list({ cwd })`(`packages/skill/skill/src/index.ts`)—— 文件系统各根(project-dsh / project-agents / custom / user-dsh / user-agents)+ `bundled` + `runtime`(office 包)+ 插件自注册 provider,全由内核按 scope 链与 cwd 聚合。**面板显示的就是模型这一会话能用的那一份**(与右侧「上下文 → 技能注入」条数一致)。
222
+ - 选哪个会话:取最新创建的根会话(`agents.roots()` 按 `session.header.createdAt` 降序);客户端目前读不到渲染期的当前会话 id(`uiSession` 是 hook/selector 面),因此面板以「最新会话」为准。无会话时退回宿主全局 `ctx.skills` + 启动 cwd。
223
+ - (只读分级已在 0.11.1 取消:所有来源的技能都能开关,见上节。)
224
+ - **MCP 不再有目录层**:删掉 0.10.x 的 `mcps/` 四目录扫描、挂载、可见性过滤与面板分区。MCP Server 就是内核 Cordis 组合里的 `mcp-client` 行 —— 列表、启停、增删都走组合行(`pluginManager.listPlugins()` / `setPluginEnabled()` 与 `configEditor`),插件不再额外挂行。
225
+ - **验证**:`node --test` 全绿;web 实例真浏览器实测(注入的 bsk 工具):MCP 页只有 3 个组合行、无「mcps 目录」分区、不闪退。
226
+ - **已知限制**:随安装发布的技能在 `app.asar` 内,文件清单读不到(卡片显示「0 文件」);不同 profile/预设的组合不同,所以各处的技能数量天然不同(这不是漏扫)。
227
+
228
+ ## 0.10.1:修复 MCP 页闪退 + 技能/MCP 统一为「四个目录、`.dsh` 优先」
229
+
230
+ - **修复「点 MCP tab 闪退回对话界面」**:0.10.0 的 MCP 页新增分组时,图标随手写成 `IconFolderOutlineRegular`,而宿主 `@deepseek-ai/dsh-client-ui-primitives` 并未导出这个名字 —— 该 JSX 组件是 `undefined`,React 抛 `Element type is invalid`,MCP 页被 ErrorBoundary 摘掉(面板 fallback `null` + 自动关闭 = 闪退回对话界面)。改用宿主确实导出、或插件自绘的图标。
231
+ - **补上静态护栏**,让「宿主没有这个图标」在提交前就失败:`test/client-primitives.test.mjs` 检查客户端从 ui-primitives 导入的每个名字都在 `test/fixtures/client-ui-primitives.json` 白名单里,并逐名核对宿主打包产物确实存在(标识符边界匹配,防止 `IconFolderOpenOutline` 蹭到 `IconFolderOpenOutlineRegular` 而误判)。
232
+ - **补上真浏览器验证**:新增 `scripts/verify-panel-browser.mjs` —— 用真实 Chrome(CDP,无第三方依赖)打开 dsh Web UI,点「能力」→ MCP 页 → SKILL 页,断言没有未捕获异常、MCP 页真的渲染出来、四个目录里的条目都在 DOM 里,并截图留证。闪退这类问题只有真渲染才会暴露,单测全绿也拦不住。
233
+ - **技能与 MCP 统一为四个目录**(`.dsh` 优先于 `.agents`,工作区优先于用户级):
234
+ - 技能:`<工作区>/.dsh/skills` → `<工作区>/.agents/skills` → `~/.dsh/skills` → `~/.agents/skills`
235
+ - MCP:`<工作区>/.dsh/mcps` → `<工作区>/.agents/mcps` → `~/.dsh/mcps` → `~/.agents/mcps`
236
+ - 同名先出现者胜出:技能按名字去重(先出现的那份胜出);MCP 同一作用域内同名只挂一台,被遮蔽的在面板里如实标出「同名遮蔽(来自哪个文件 → 采用了哪个文件)」。
237
+ - 技能根**不再按「目录是否存在」过滤**:四个根始终在列表里,新丢进去的技能下一次请求即可见,不必重开会话。
238
+
239
+ ## 0.10.0:技能扫工作区级根 + mcps 目录接上
240
+
241
+ - **技能面板补上工作区级技能根**:内核 `dsh-skill-filesystem` 扫 5 类根,本插件面板此前只认两个用户级根,于是 `<工作区>/.dsh/skills` 与 `<工作区>/.agents/skills` 里的技能内核看得见、面板看不见(无法归组 / 开关 / 查看文件)。现在面板、技能开关、技能健康三处共用同一份根集合,顺序与内核 rank 一致(工作区级先于用户级,同名先出现者胜出),项目根按内核规则取「最近的含 `.git` 的祖先,没有则会话 cwd」。
242
+ - **补上 mcps 目录的 MCP Server**:内核只认 Cordis 组合里的 `mcp-client` 行,没有「读某个目录下 `mcp.json`」的约定(全仓库 grep `mcps` 零命中),所以 `~/.agents/mcps/**`、`<工作区>/.dsh|.agents/mcps/**` 里放着的配置一直是死文件。现在由本插件读出来挂成真正的 mcp-client 行:全局层所有会话可见,工作区层只在该工作区会话可见(装配时按会话 cwd 过滤),MCP 页单列一组展示。
243
+ - 工作区层的 `serverName` 追加 8 位路径哈希后缀:不同工作区可以各配一台同名但参数不同的 Server,互不挤占(`mcp-client` 的 serverName 在同一作用域只能有一个实例)。配置里的 `${VAR}` 按进程环境展开,`cwd` 的相对值按该配置文件所在目录解析。
244
+ - 与组合里既有的行同名时**以组合为准**:目录里那条不重复挂载,面板如实标出「已让位」—— 否则后挂的 fiber 激活失败并持续报 `serverName ... is already in use`。
245
+ - 相关实现:新增 `src/skill-roots.ts`(根解析)与 `src/mcp-workspace.ts`(mcps 运行时),`vendor/skill-manager/skills-host.js` 增加额外根注入,路由 `GET /api/triad/mcp-workspace/status`;`/api/triad/mcp-status` 增加 `workspaceServers` 段。
246
+
247
+ ## 0.9.3:适配 DSH 0.2.1-rc.2
248
+
249
+ - **修复「存为预设」**:原实现调用宿主 `agentPresets.copy()`,但该 API 在 DSH 任何版本都不存在(宿主只有 `list` / `read` / `select`),所以此前一保存就报 `宿主未提供 agent 预设服务`。现改为读来源预设的声明组成,作为一条 `dsh-agent-preset` 行写进 profile patch(HMR 热加载),并把当前定制写进 `overrides[新名]`。
250
+ - 预设 id 收紧为宿主的真实规则 `[a-z0-9][a-z0-9-]*`(原先允许中文与空格,与宿主不符)。
251
+ - 用户数据根解析对齐内核新的 `resolveDshHome` / `resolveAgentsHome`:`$DSH_HOME` / `$DSH_AGENTS_HOME` 为空串或纯空白时按未设置处理(旧写法会落到进程 cwd),`~` / `~/` 前缀现在展开。技能面板、技能开关账本、技能健康检查三处共用 `src/home-paths.ts`。
252
+ - 三个功能面的服务契约在本区间零破坏:`ctx.skills`、`tools.restrict/schemas`、`configEditor`、`agentPresets`、`system-prompt` 装配 waterfall 与段/变量 API、`connection.requestRejection` 全部保持。详见 [rc2-migration.md](rc2-migration.md)。
253
+
254
+ ## 0.9.2:按模式预设修正 rc.2 适配
255
+
256
+ - 预设目录使用 `agentPresets.acquireScope()`,每次读取选中模式的当前修订;结束释放租约。不匹配已有对话,不创建伪 Agent,不切换对话的预设。
257
+ - 顶部模式 Tab 决定提示词、工具和排序的编辑目标。同名提示词在不同模式下的正文分别来自各自作用域,不能拿另一个模式或旧会话替代。
258
+ - 预设预览中的 `{{model}}`、`{{cwd}}` 等未绑定变量保留为模板引用,并显示非致命说明;目录和草稿编辑不因此失败。真正对话中的严格插值行为不变。
259
+ - 独立展示 `contexts`(user-role 运行时快照、只读)。纯预设作用域无法得到依赖真实会话状态的贡献;这不允许把另一个对话的数据当成当前模式定义。
260
+ - 三阶段模拟本插件的独立规则;已有对话的模式绑定不被编辑目标切换改变。此预览不是完整历史 LLM 请求回放。
261
+
262
+ 本地源码更新后执行 `npm run build`。链接安装无需复制;已启动宿主可能持有旧模块,保存工作后重启原来的 `dsh web` 并硬刷新,确认不再有「预览会话」,来源显示为「当前模式预设」。
263
+
264
+ 目标版本冷启动测试(`scripts/test-rc2-runtime.mjs`)已随测试精简移除:它硬编码只认 **0.2.0-rc.2**,且在 0.2.1-rc.2 上 turn 段断言必然失败。rc.2 适配相关行为改由 `test/rc2-preview.test.mjs` 覆盖,完整校验以 `npm run check` 为准。
265
+
266
+ ## 历史版本要求
267
+
268
+ ### 版本要求
269
+
270
+ 0.9.3 当时的版本要求:对齐 **DSH 0.2.1-rc.2**(`peerDependencies` 为 `^0.2.1-rc.2`)。
271
+ 0.2.0-rc.2 → 0.2.1-rc.2 的适配记录见 [rc2-migration.md](rc2-migration.md):三个功能面的服务契约
272
+ 均无破坏,唯一代码改动是用户数据根解析对齐内核新的 `resolveDshHome`/`resolveAgentsHome`
273
+ (空串按未设置、支持 `~` 展开)。
274
+
275
+ **0.9.0 行为变更:全局层停用。** 提示词必须按具体 agent 预设修改——「全部 Agent」
276
+ 编辑入口移除,`/config/apply` 无 `target` 一律 400,`/config/set|unset` 拒绝六个
277
+ 提示词字段;运行时生效配置 = 该会话所属预设的 `overrides[id]`,未定制的字段 =
278
+ 官方默认。config.yaml 顶层的旧全局层数据**保留不再生效**(可手编回滚;0.8.x 及
279
+ 以前的行为请用 0.8.1)。
280
+ 0.1.7 起的图标代际(`IconXxxOutlineRegular/Medium`,尺寸由 `size` 属性给)在
281
+ 0.2.0 沿用,本插件的宿主接触面(`system-prompt` 装配 waterfall、`agentPresets`、
282
+ `configEditor`、`tools.restrict/schemas`、`webServer.register`、客户端模块加载缝)
283
+ 在 0.1.7-rc.1 → 0.2.1-rc.2 走廊内均无破坏性变更,故客户端 bundle 与 0.7.x 同代。
284
+ 不支持 0.1.7 之前的主机(旧宿主会按 DSH 的插件兼容性策略拒绝加载,而不是半静默
285
+ 地坏掉)。需要旧版的请用 0.6.x。
286
+
287
+ 0.8.1 起 MCP 全局 Server 的管理目标(`/api/triad/mcp-status` / `mcp-config` 读写
288
+ 的 `cordis.patch.yml`)随宿主 `profileContext` 解析——Web 端(profile `web`)与
289
+ **桌面端**(profile `desktop`,默认端口 19387)各管各的 profile,不再写死 `web`。
290
+ 宿主面缺失时按 `DSH_DESKTOP_PROFILE` 环境变量回落(桌面壳 v4 形态),最后回落
291
+ `web` 保持旧行为。注意桌面 profile 没有 `patchReload: live`:切换 / 粘贴 / 删除
292
+ MCP Server 写盘成功后需**重启桌面端**才生效(Web 端热重载语义不变)。
293
+
294
+ 预设专属 MCP Server 的存储位置自 0.1.7 起:预设不再是磁盘上的组合
295
+ 文件,而是声明式插件行(`name: '@deepseek-ai/dsh-agent-preset'`),专属 Server
296
+ 写在它的 `config.plugins` 里;本插件经 DSH 的 `configEditor` 把改动写回 profile
297
+ 的 `cordis.patch.yml`(首次改动会整份覆盖该预设行的 plugins,此后该预设不再跟随
298
+ DSH 升级更新 —— 与 DSH 自带编辑器同一语义,面板上有提示)。