dsh-plugin-tool-management 0.10.0 → 0.12.0

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 (77) hide show
  1. package/CHANGELOG.md +100 -1
  2. package/README.md +67 -50
  3. package/README_EN.md +61 -38
  4. package/cordis.patch.yml +10 -1
  5. package/docs/images/1-EN.png +0 -0
  6. package/docs/images/1.png +0 -0
  7. package/docs/images/2-EN.png +0 -0
  8. package/docs/images/2.png +0 -0
  9. package/docs/images/3-EN.png +0 -0
  10. package/docs/images/3.png +0 -0
  11. package/docs/images/4-EN.png +0 -0
  12. package/docs/images/4.png +0 -0
  13. package/docs/images/5-EN.png +0 -0
  14. package/docs/images/5.png +0 -0
  15. package/docs/images/6-EN.png +0 -0
  16. package/docs/images/6.png +0 -0
  17. package/docs/images/7-EN.png +0 -0
  18. package/docs/images/7.png +0 -0
  19. package/docs/images/8-EN.png +0 -0
  20. package/docs/images/8.png +0 -0
  21. package/docs/update.md +132 -12
  22. package/lib/client.js +2826 -547
  23. package/lib/compat/patch-dialect.js +173 -0
  24. package/lib/compat/preset-reach.js +1 -10
  25. package/lib/compat/probe.js +205 -24
  26. package/lib/compat/runtime-notes.js +25 -0
  27. package/lib/context-inject.js +83 -9
  28. package/lib/host-names.js +12 -0
  29. package/lib/http-fence.js +35 -15
  30. package/lib/hub.js +28 -2
  31. package/lib/imports/parsers.js +15 -9
  32. package/lib/imports/upload.js +43 -4
  33. package/lib/index.js +927 -3897
  34. package/lib/mcp/loader-token.js +238 -0
  35. package/lib/mcp/manager.js +1769 -0
  36. package/lib/mcp/override-blocks.js +10 -3
  37. package/lib/mcp/patch-yaml.js +351 -0
  38. package/lib/mcp/secret-guard.js +145 -0
  39. package/lib/{rules → memories}/archive-engine.js +1 -1
  40. package/lib/{rules → memories}/archive.js +1 -1
  41. package/lib/memories/constants.js +128 -0
  42. package/lib/memories/index-io.js +330 -0
  43. package/lib/memories/projection.js +280 -0
  44. package/lib/memories/service.js +686 -0
  45. package/lib/memories/snapshot.js +672 -0
  46. package/lib/ops/candidates.js +64 -0
  47. package/lib/ops/compat.js +226 -0
  48. package/lib/ops/ctx.js +9 -0
  49. package/lib/ops/memory.js +678 -0
  50. package/lib/ops/prompts.js +107 -0
  51. package/lib/ops/scene-records.js +460 -0
  52. package/lib/ops/scene-sync.js +17 -0
  53. package/lib/ops/sessions.js +603 -0
  54. package/lib/ops/trash.js +140 -0
  55. package/lib/paths.js +103 -0
  56. package/lib/prompts/preset-id.js +49 -0
  57. package/lib/{agents-md → prompts}/service.js +1 -1
  58. package/lib/request-gate.js +320 -0
  59. package/lib/scene-prompt-sync.js +4 -4
  60. package/lib/scenes/candidates.js +344 -0
  61. package/lib/{history → sessions}/bridge.js +124 -36
  62. package/lib/sessions/history.js +323 -0
  63. package/lib/{history → sessions}/tombstone.js +1 -1
  64. package/lib/{history → sessions}/workspace.js +151 -50
  65. package/lib/skills/core.js +74 -39
  66. package/lib/skills/readonly-discovery.js +4 -1
  67. package/lib/skills/service.js +98 -13
  68. package/lib/subagents/service.js +199 -55
  69. package/lib/tools/deps.js +8 -0
  70. package/lib/tools/mcp.js +110 -0
  71. package/lib/tools/memory.js +87 -0
  72. package/lib/tools/prompt.js +70 -0
  73. package/lib/tools/skills.js +139 -0
  74. package/lib/tools/subagent.js +40 -0
  75. package/package.json +13 -10
  76. package/lib/agents-md/preset-id.js +0 -49
  77. package/lib/rules/service.js +0 -3078
package/CHANGELOG.md CHANGED
@@ -10,6 +10,94 @@
10
10
 
11
11
  ---
12
12
 
13
+ ## [0.12.0] - 2026-09-20
14
+
15
+ ### 新增
16
+
17
+ - **兼容页「功能总览」**:一行一个功能点(十几项,第一行是「插件是否完整挂载」),状态由三层合成 —— 装配层(listener / provider / 适配是否真的挂上)+宿主能力层(探测结果)+你自己的配置(开关 / 令牌 / 场景锁定)。取值:正常 / 降级(附原因)/ 未启用 / 受令牌限制 / 部分生效 / 不可用。点一行跳到该功能所在的页签;数据全部来自已有缓存,不新增宿主探测。
18
+ - **停用的 MCP 工具真的从模型可见的工具表里消失了**:`tools.restrict` 之前是在插件级上下文上调的,官方要求 agent scope、必抛 —— 异常被当成"registry race"吞掉,所以「停用」一直只有执行时拦截这一半。现在按 agent 逐个应用(跟随 agent 上线/下线),重新启用后工具会恢复可见。
19
+ - **补丁写入前先做一次解析校验**:复刻官方解析补丁用的 YAML 方言(js-yaml + `!!js` 标签 + 官方那两条断言),写入前判一次产物。判据是**非对称**的 —— 只有"改前能解析、改后不能解析"(=我们的改动把产物弄坏了)才拒绝写入并保留原文件与备份;改前本来就解析不过(说明复刻过期)照写 + 上报;依赖不在也照写 + 上报。校验结论会出现在兼容页,写入回执也带一条 warning。
20
+ - **doctor 新增三项检查**:① 插件运行时按裸模块名要的那几个宿主包(MCP 客户端 / 人设 / AGENTS.md 注入 / 技能目录)是否仍被宿主暴露;② 插件挂载心跳(`tool-management/mount.json`,含 12 个 inject 服务名)—— 官方改名任何一个 inject 服务名时插件根本不会 apply,且官方既不抛错也不打日志,这份心跳是那种情形下唯一的线索;③ 宿主版本与本插件验证过的版本不一致时显式提示。
21
+ - **发行物的 `!!js` 表达式自带兜底**:双挂载判定那段表达式在**每次启动**被宿主求值,而那条调用链没有任何保护;现在表达式自己包了 try/catch,出错返回 false(=不参与判定、保持挂载),绝不把启动流程拖垮。
22
+
23
+ ### 变更
24
+
25
+ - **宿主补上原生删除入口时,先不切换**:单删与批删两条都改为"上报「宿主已提供原生入口」+ 继续走本插件自有完整序列(级联子会话 + spill + 记账 + 缓存行)"。切过去的代码今天不可达,写成即等于它会在官方补上入口的那次升级上首次运行、且无从事前验证。
26
+ - **会话删除的前置检查补齐**:转录清理还要 `readSessionHeader` 与持久化后端的 `locate()`,而它们排在「装写屏障 → flush → detach → 清缓存行」之后 —— 缺任何一个,失败点都会落在中段(转录还在、记账已清)。现在这两项与已有检查一样,在任何破坏性步骤之前先确认。
27
+ - **`@deepseek-ai/cordis` 的 peer 下界提到 `^4.0.2`**(对齐宿主实装);两个子智能体 spawn provider 进 devDependencies(运行时硬 require、此前在本仓库解析不到);移除没有引用的 devDependency `@deepseek-ai/dsh-user-approval`。
28
+ - **依赖投递链写进文档**:`npm run build` 的镜像清单**不含 `node_modules`**,新增的运行时依赖(js-yaml)需要在 profile 侧安装;装不上时校验自动降级为「跳过 + 上报」。
29
+
30
+ ### 修复
31
+
32
+ - **关掉技能 / 提示词域后,官方那条注入真的停了**:此前拦不拦得住取决于启动时的注册先后 —— 没拦住的那次,官方内容照旧进上下文,界面上也看不出异常。现在位置固定,不再看运气。
33
+ - **投影缓存删除屏障的三处缺陷**:① 第三方也包装同一份缓存时,旧屏障退役后仍留在登记表里、被下一次删除复用 → 墓碑拦截静默失效(缓存行可能复活);现在退役即摘出登记表,且复用前先验证包装器确实在岗。② 首次使用抛 `TypeError`(宿主形状变了)时不再把宿主留在"半个包装器"状态下 —— 恢复原方法并上报。③ 安装期(`requireTable()`)抛错时上报,不再只抛一句裸错。
34
+ - **兼容页的删除动作可用性判定与服务端对齐**:此前客户端要求一个可选槽位(`projection.delete-native`),把本机可用的删除显示成不可用;同时服务端运行时真正要求的那条硬前提(投影缓存存储可删行)提升为能力表的正式条目 —— 界面说的和服务端做的从此同一份依据。
35
+ - **兼容页的宿主身份锚点与 doctor 同一套策略**:此前运行时只按"插件自己解析到的包"上溯,dev 布局下会比出假的「同一份模块」,出现"界面说 ok、doctor 说 SEPARATE COPY"。
36
+ - **工作区实体快照写不进去时不再静默**:写入后立刻读回,没写进去就上报(界面上的会话计数可能不刷新)。
37
+ - **几处"只落 console"的装配失败现在出现在兼容页**:技能 provider 装配失败、上下文注入通道装配失败 —— 此前界面看起来一切正常。
38
+ - **关域拦截有了顺序自检**:关域生效期间连续多轮一条都没拦到时会提示"可能没拦到(也可能是官方那几轮本来没内容)"。
39
+
40
+ ---
41
+
42
+ ## [0.11.0] - 2026-09-20
43
+
44
+ ### 新增
45
+
46
+ - **访问令牌可以设置、关闭、修改、删除了**:以前只有「设置」一条路,关掉就等于把配置里的令牌删掉、想再开只能重新输一遍。现在关闭只是"暂时不生效"(令牌留着,随时开回来),新增「修改令牌」与「删除令牌」,状态收成一枚四态胶囊 + 一句实况。
47
+ - **批量启停可以撤销了**:MCP / 技能 / 记忆三处批量开关做完后出现一条不会自动消失的提示,点「撤销」按操作前的状态逐条写回。
48
+ - **兼容页可以清理旧备份了**(页头「刷新」旁边):每份备份都是整份配置的副本、凭据在里面是明文,而自动剪枝只保证每层最多 5 份。逐层级选删除份数,弹窗内二次确认,只删备份、不碰当前配置。
49
+ - **提示词页与子智能体页可以搜了**:按名称 / 描述过滤,无命中说「没有匹配的…」而不是「还没有…」。
50
+ - **兼容体检改为真调一次**:归档 / 解档 / 批量 / 永久删除四个原生入口与会话的 detach / announce,以前只查方法名在不在就判可用;现在各用一个不可能命中的哨兵 id 真调一次,能调通才算数。
51
+ - **令牌开启、本次启动还没解锁时,输入框会锁住**:对话走的是 DSH 自己的会话通路,不经过插件的 HTTP 门禁 —— 此前令牌配了没填照样能聊、五个域的注入照样进上下文。现在这种情况输入框直接锁住并说明去哪填,解锁后立刻恢复。
52
+
53
+ ### 变更
54
+
55
+ - **关闭 / 修改 / 删除令牌都要当场重输一次当前令牌**:解锁过**不算** —— 请求头里那份已验过的凭证对这三个销毁方向一律不作数,否则"能打开界面就能关掉保护 / 换掉凭证"。
56
+ - **令牌面板拆成「访问令牌」与「令牌管理」两块,表单全部搬进弹窗**:前者只管本次启动 —— 一枚状态胶囊 + 解锁框,解锁错误直接顶替状态句显示;后者只管配置文件 —— 设置/修改、保护开关、删除、清除令牌四行静态清单,行名/说明/按钮同形同位。页面上不再有会展开收起的东西;成功 = 弹窗收起 + 状态行更新,失败 = 弹窗不关、错误显示在弹窗内;红色只出现在确认按钮上。
57
+ - **「保护开关」的方向改按"令牌在不在生效"判断**:用环境变量提供令牌的用户以前会把"开着"显示成"开启保护"、找不到关闭入口;配置文件里没有令牌值时「删除令牌」也不再显示(点了是空操作)。「loader 行读不到」的提示也贴到操作区旁 —— 这一态下写配置必被宿主拒绝。
58
+ - **同一类名字的判定口径统一了**:Windows 保留设备名与控制字符一律拒绝;场景名不再接受尾随空格或点(界面与宿主此前各有一套校验、已经分叉)。
59
+ - **目录链接不再被跟随**:链接进来的记忆以前列得出来、开关也能开,却永远注入不进去。
60
+ - **「打开源文件」限定在技能来源与人设目录内**:这个操作把路径交给系统编辑器,以前只校验后缀。
61
+ - **六页搜索改为停手约 200ms 才过滤,列表只在数据或搜索词真变了才重算**:此前每敲一个字就把整份列表过滤排序一遍,而页面上任何一次重渲染(勾一次选、开个弹窗、MCP 每 5 秒轮询)都会再算一遍。
62
+ - **会话批量删除不再逐条重扫全部会话**(原先 O(N × 全部会话),现在这批共用开头取的一次快照);注入通道与记忆列表也不再反复重读同一份索引(按文件 `mtime+size` 指纹缓存,外部改了下一轮照常生效)。
63
+ - **导出加了病态上限**:单文件 32 MB / 合计 256 MB,超了明确报错(不再把"全部正文 + 一份 zip"同时堆在内存里);注定超限的上传在开始读之前就拒掉。
64
+ - **没配访问令牌时,查看明文密钥不再要求令牌**:原先一律拒绝,而那时用户根本没有可填的令牌 —— 点「显示密钥」只有一句"缺少访问令牌",是条死路。有令牌(含"关掉保护")时照旧要带对的。
65
+ - **「注入」设置块不再因为没填令牌而整块消失**:`inject-settings` / `mcpm-settings` 这两个"读改写"op 的**读**不再按写操作门禁(写仍然要令牌)。
66
+
67
+ ### 修复
68
+
69
+ - **打码的密钥不再被「保存」写坏**:编辑弹窗预填的是打码值,以前保存会把它当真值写回补丁文件,真密钥永久丢失。现在保存时用原值顶替、没有原值就跳过并提示。
70
+ - **宿主没配令牌但浏览器里残留旧令牌时,现在有「清除令牌」入口**:以前那一态整行不渲染,残留令牌没有任何出口。
71
+ - **别处触发"去填令牌"时,先收起弹窗再聚焦解锁框**:解锁框在弹窗展开期间是禁用的,直接聚焦会被浏览器吞掉。
72
+ - **「令牌未验证」横幅不再盖住宿主的设置窗口**:层级降到插件弹窗之下,并加了关闭键;关掉后本次锁态期间不再出现,解锁后再锁照常回来。
73
+ - **堵上「清除令牌后仍可对话」的漏洞**:解锁一次后「清除令牌 + 强刷新」,浏览器里已无令牌、对话却畅通 —— pre-step 门禁读的那颗"本次启动已验过"的闩只置位不清零。现在清除令牌会让宿主把闩重新挂上(`token-unaccept`,不进 handlers、模型碰不到;不要求凭证 —— 它只会收紧),必须重新验令牌才能对话;其它标签页内存里的旧令牌同步作废。
74
+ - **编辑 MCP 服务器不再把 URL 里的凭据写成占位串**:URL 查询串是另一种打码形态(`?<redacted>`),现在与 env / headers 同一条规则 —— 没有原值可顶替时拒绝整次保存并提示重填。
75
+ - **「设置宿主令牌」不再可能把 DSH 写坏到起不来**:`config:` 形状识别不了时会插入重复键,而官方解析器对重复键是抛错;现在识别不了就拒绝改写并说明。
76
+ - **记忆索引损坏不再静默清空配置**:以前解析失败被当成"没有索引",下一次写入就把空索引落盘,一次损坏丢掉全部启用集合与排序。现在坏文件留存、拒绝写入并说明怎么办。
77
+ - **开关状态不再莫名回弹或陈旧**:两个开关同时写同一份状态文件会互相抹掉;现在写操作串行执行,并跟随文件的实际改动。
78
+ - **锁定场景正在生效时,不再能通过清空启用集合绕开模型侧的写门禁**。
79
+ - **MCP「重启」中断后不再把服务器留在停用态**(恢复写在 `finally` 里)。
80
+ - **导入文件不再让设置面板变空白**:渲染期抛错把整棵组件树卸载了。
81
+ - **详情弹窗不再被"晚到的响应"搅乱**:连点两条时先发的响应后到会覆盖详情,关窗后晚到的响应还会把它重新打开。
82
+ - **「显示密钥」真的能显示明文了**:以前客户端又打了一次码,解锁后看到的仍是 `sk-1****`。
83
+ - **「令牌没过」的提示统一成一句话**,不再几秒后自己消失,且在任何弹窗里都看得见(此前弹窗里有的什么都不显示、有的没有跳转按钮)。
84
+ - **子代理工具注册失败不再静默**:子智能体页顶部会出黄条说清是哪几个工具、为什么。
85
+ - **读不到的记忆不再"人间蒸发"**:文件被删 / 权限不足 / 被锁时会连名字与原因出现在记忆页的告警条里。
86
+ - **导出会话不再静默覆盖同名文件**(文件名带短哈希区分,撞名如实报 skipped)。
87
+ - **命令行体检(`node scripts/doctor.mjs`)补齐 6 项能力**:删除 / 归档 / 列表路由真正依赖的那些以前不在体检范围,现在 15 项全列。
88
+ - **键盘可达**:导入拖拽区以前是 `div` + `onClick`(Tab 到不了、回车也打不开),现在是真的按钮;工具页签补上页签语义与 ←/→/Home/End 切换。
89
+ - **开了系统的「减少动态效果」后**,开关、拖拽区、注入预算条不再有过渡动画。
90
+ - **批量归档部分失败会在结果里说明**;**人设改名改为原子写入**;**新建 / 导入记忆改为先落索引、再落文件**。
91
+ - **技能 / 子智能体 / 记忆三页现在会显示场景与锁定横幅了**:服务端标了、界面读不到(技能页的字段被响应信封挡掉、子智能体页刷新时漏搬、记忆页压根没渲染)。记忆页的写控件在场景锁定时也因此**从未被禁用**过,一并修好。
92
+ - **场景锁定期间不再显示「改动会同步写进场景档案」那条长提示**:开关都是灰的,那句话没有落点,与锁定横幅并排还互相矛盾。
93
+ - **批量启停的撤销条改成琥珀色**(边框 + 提示句 + 淡底):它是这次批量操作唯一的补救通道,此前与旁边的普通回执长得一样,扫一眼不会注意到。
94
+
95
+ ### 文档
96
+
97
+ - **README 补上此前缺失的边界告知**:场景锁定的具体冻结范围、会话永久删除不进回收站、导出是可读转录不是完整备份、白名单会并回全部 MCP 工具、密钥在磁盘上始终是明文。
98
+
99
+ ---
100
+
13
101
  ## [0.10.0] - 2026-09-18
14
102
 
15
103
  ### 新增
@@ -36,6 +124,17 @@
36
124
 
37
125
  ### 修复
38
126
 
127
+ - **兼容页的「访问令牌」块重排**(用户裁定 2026-09-18):块挪到「注入」**上面**(令牌不过时写操作全被拒,是"先解决才能用别的"的一件事);状态胶囊只放**短状态**(未配置 / 未填写 / 已匹配 / 不匹配)——上一版把整句"与宿主配置的令牌不一致……"塞进胶囊,一行里两种字号交错,是这块"很杂乱很丑"的主因;次要操作改成一行 quiet 文本按钮(与 MCP 页那排「详情 / 编辑 / 重启」同款,三个带边框的按钮挤在一起边框互相打断是另一半原因);说明从一整段散文改成**三条短句**(用户原话:写简单简洁一点,用户大致了解就行),版式与「工具能用 ≠ 模型知道」那份清单一致。
128
+ - **「令牌没过」的提示统一成一套文案,并带跳转按钮**(用户裁定 2026-09-18):此前同一件事有四种说法(宿主写门禁、明文门禁两条分支、界面错误码词典各写一套,截图里能同时看到三种),用户无法判断是不是同一件事。现在宿主侧只有 `http-fence.ts` 里的 `TOKEN_MSG_NO_HOST` / `TOKEN_MSG_BAD` 两句(`TOKEN_PANEL_HINT` 拼进去,保证每个出口都指路),界面词典与它们逐字相同;识别按**文本相等**而不是猜关键词("缺少/未配置"这类词出现在别的提示里会误挂按钮)。按钮挂在共享的 `Notice` 组件里一处实现,于是**任意页面**的令牌提示右侧都会出现「填写令牌」——点了切到兼容页并把光标放进输入框(`ToolsSection` 注册切页签、兼容页消费聚焦请求,两条通路都覆盖:已在兼容页 / 刚切过去)。`apply` **之外**的 CompatPage 用不了 `Notice`,按本页写法手工拼同一套。另加一条契约测试钉住"宿主常量与界面词典逐字相同"——不同则按钮静默消失、同一件事又变两种说法。
129
+ - **MCP 详情弹窗里也能看到「显示密钥」被拒的提示**(用户指出):用户是在弹窗里点的按钮,而提示此前只长在弹窗背后 —— 弹窗盖着它,等于没有提示。现在同一份提示面板在页面与弹窗里各渲染一份(`tokenGatePanel()`),弹窗里就贴在「显示密钥」那一节下面。
130
+ - **作用域护栏补上"暂时性死区"这一类**(`test/client-scope.test.mjs`):`detailNode` 里调了写在它下方的 `const tokenGatePanel` —— 首屏不打开弹窗时那条引用不求值,测试全绿,一点「详情」就白屏(本次差点翻车)。判据扩成两条:跨 `apply` 作用域的**组件引用**、以及同函数内**先用后声明 const**(两种引用形态:组件首参与被调函数)。已用真实代码回退验证过判据抓得到(`L2408 用了 tokenGatePanel,但它的声明在 L2430`)。
131
+ - **访问令牌的控制面挪进面板,且只对本次运行有效**(用户裁定 2026-09-18 的三条):① 宿主没配令牌时,「设为宿主密钥」把 `config.token` 写进 profile 的 `cordis.patch.yml`(新增 `mcp/loader-token.ts` 做行级改写:只动 `config:` / `token:` 两行,注释与 `!!js` 守卫一个字不碰;改前照旧自动备份;空 `config:` 会连块一起删);② 已配令牌时,填入该令牌点「关闭令牌功能」即把配置里的令牌删掉,重启后不再需要令牌 —— 不需要「先保存再关闭」两步(宿主接受输入框里那个值作为凭证),填错一律拒绝,否则"能开 GUI 就能关掉保护";③ 填写的令牌与宿主**本次进程的标识**(`token-status` 回 `bootId` = pid + 进程启动时刻)绑定存储,DSH 退出后再启动必须重填,同一次运行内刷新页面不必重填(旧的无归属裸令牌键按过期处理并清除)。两个新 op(`token-status` / `token-configure`)都**不进 `handlers` 表**:前者只需请求头与进程状态,后者如此才能保证**没有任何模型工具能关掉访问令牌**。配置改动需要重启 DSH 才生效,界面用「本次填写 = 已匹配」与「待重启」两处状态如实说清,而不是让人以为点了没反应。
132
+ - **新增静态护栏:`apply` 外面的组件不许引用 `apply` 里面的组件**(`test/client-scope.test.mjs`):这类引用只在 state 有值时才求值 —— 首屏正常、一交互就白屏,且不留任何日志(客户端错误不进宿主日志)。本次「兼容页访问令牌」的第一版正是这么翻的(`CompatPage` 声明在 `apply` 之外,却用了 `apply` 内的 `Notice`,填完令牌点保存即白屏)。判据只查 `createElement(名)` / `h(名)` 的首参,所以不误报:一般化的跨作用域检查会被同名局部变量与参数遮蔽,实测在本文件上能报出 30+ 条假阳性。护栏附一条自检用例,拿一段必然违规的代码喂给同一个判据,避免"没报错"其实等于"没在跑"。
133
+ - **访问令牌有了与页面无关的固定入口**(兼容页「访问令牌」块,用户裁定):此前输入框只长在 MCP 页的服务详情弹窗里,而「缺少或错误的访问令牌」这条错误会出现在**任意**页面 —— 在场景页被拒的用户找不到任何地方能填(实测死路)。现在兼容页显示两枚状态(宿主配置有没有 / 本机浏览器这串对不对)、能填能清,错误文案也直接指向那里;宿主侧新增只读的 `token-status`(只回答"配没配 / 这次带的对不对",**不回令牌本身**),写操作被拒时回 `code: error.token.required`。
134
+ - **打码密钥不再被写回补丁文件**(真实事故):编辑弹窗的密钥框预填的是**打码值**(列表本来就把它打码了),而写入路径原样接受表单内容 —— 于是「打开编辑 → 改个别的字段 → 保存」就把真密钥覆盖成了 `••••••`,且无法找回(本机 `TAVILY_API_KEY` 即如此丢失)。现在保存前按 `src/mcp/secret-guard.ts` 收敛:有原值就用原值顶替(= 这一项没改),没有原值或原值本身已是打码值时**跳过该键**并回一条提示;新增与 JSON 导入走同一套。出口另加结构性断言(`buildInsertBlock`),任何漏过守卫的调用点直接拒绝写入,而不是写一个假值进去。
135
+ - **补丁文件里已存在的打码密钥会被主动报出来**:MCP 列表把它列进告警(`服务器名.键名`),说明真值已丢失、要重新填写 —— 此前这种状态完全隐形,用户只会在下次保存时再撞一次同一个结果。
136
+ - **「显示密钥」解锁后能看到明文了**:客户端此前在详情面板又打了一次码(保留前 4 个字符 + `****`),导致配好访问令牌、`mcpm-reveal` 返回真值之后,界面上显示的仍是 `sk-1****`;同时打码字形与宿主不一致,详情里会出现 `••••****` 这种双重打码。现在打码的**唯一出处是宿主**,客户端不再加工。
137
+ - **编辑弹窗会说明密钥框里是打码值**:明确「直接保存不会改动它们」「要看原文需要点『显示密钥』并填对访问令牌」,避免用户以为真密钥长这样、或者以为保存会改掉它。
39
138
  - **场景锁定补齐模型侧**:`mcp_manager_add` / `skill_manager_create` / `memory_manager_write` / `prompt_manager_apply` 四个工具此前绕过守卫,锁定期间仍可调用。
40
139
  - **记忆怎么删都进回收站**:形态转换与改名此前直接删旧文件(bundle 连附件一起),现在先入回收站再删。
41
140
  - **`mcpm-tools-refresh` 要求令牌**:它会临时启停服务器并写两次补丁,此前漏在写操作门清单外。
@@ -64,7 +163,7 @@
64
163
 
65
164
  - **兼容页「注入实况」**:显示最近活跃会话里模型**实际看到**的五域文本,并如实分清「在上下文中 / 未投递 / 官方注入 / 已关闭」——勾了开关却没送到时一眼可见。
66
165
  - **「全选」统一成「全选 ↔ 取消全选」二合一**(不再并列两个按钮),并给技能管理页、记忆管理页、记忆的每个场景卡片补上缺的批量开关。
67
- - **场景未锁定时页面开关照常可用**,改动会同步写进当前场景的档案(MCP / 技能 / 子智能体 / 提示词四域,锁定才冻结)。
166
+ - **场景未锁定时页面开关照常可用**,改动会同步写进当前场景的档案(MCP / 技能 / 子智能体 / 提示词**三域**的开关同步进档案 —— 记忆域的开关是单一真相源,不经过档案;锁定才冻结)。
68
167
  - **人设也严格「退出即还原」**:快照记下进场景时全部人设的开与关,退出逐个恢复。
69
168
  - **技能管理页每个来源(目录)卡片也能批量开关**,点它或点来源开关会顺带把卡片展开。
70
169
  - **bundle 记忆在列表里显示附件数量**:多一颗「N 个附件」标签(0 个也显示,灰色),悬停给出总量与文件名,点它打开编辑弹窗并跳到附件区。
package/README.md CHANGED
@@ -8,6 +8,7 @@
8
8
  [![DSH Market](https://raw.githubusercontent.com/2BingLing/dsh-market/master/assets/readme/badge-listed-zh.svg)](https://dsh.market/)
9
9
  [![awesome-dsh-plugin](https://img.shields.io/badge/awesome--dsh--plugin-%E5%B7%B2%E6%94%B6%E5%BD%95-3fb950)](https://awesome-dsh-plugin.com)
10
10
  [![dshfind](https://dshfind.com/api/badge/ouli-1242/dsh-plugin-tool-management?lang=zh)](https://dshfind.com/zh/plugins/ouli-1242/dsh-plugin-tool-management)
11
+ [![0xsline](https://img.shields.io/badge/0xsline-%E5%B7%B2%E6%94%B6%E5%BD%95-3fb950)](https://github.com/0xsline/awesome-deepseek-harness)
11
12
 
12
13
  **简体中文** · [English](README_EN.md) · [Changelog](CHANGELOG.md) · [版本更新概要](docs/update.md)
13
14
 
@@ -39,20 +40,20 @@ dsh plugin --profile web add dsh-plugin-tool-management@latest
39
40
 
40
41
  一句话:**把「工作 / 写作 / 编程」各配成一套场景,点一下整套切换;插件管的东西,模型都看得见。**
41
42
 
42
- | 亮点 | 说明 |
43
- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
44
- | 一键换场景 | 每个场景各配一套:用哪些 MCP 服务器、哪些技能、哪些人设、哪些记忆;点一下整套切换,关掉自动还原 |
45
- | 记忆自动送到模型眼前 | 每个场景下写几段 `.md` 就是它的资料库,正文自动进上下文,不用每次复制粘贴 |
46
- | 给 MCP 服务器写备注 | 像「A 不可用时改用 B 兜底」这种话写进备注,模型看得到,会照做 |
47
- | 单个工具也能关 | 一台服务器里只停掉某个工具,模型看不见也调不到;「重启」只重连,不会偷偷改变开关 |
48
- | 技能状况一眼看穿 | 哪些在生效、哪些被同名技能覆盖、哪一份是首选,都标得清清楚楚 |
49
- | 子智能体 = 一个文件一个角色 | 写一份角色说明就能派活;跑完只回结果、不占你的会话记录;哪些角色能用还能按场景定 |
50
- | 提示词备好几套 | AGENTS.md 可以存多份(简洁模式 / 教学口吻……),一键切换;场景可以各自绑一份 |
51
- | 会话不再丢 | 归档按项目分组、能搜、能批量恢复;Claude Code / Cursor / Codex 的聊天记录都能导进来 |
52
- | 模型一定看得见 | 插件管的内容(记忆 / MCP / 技能 / 子智能体 / 提示词)会主动告诉模型,每个域各发一条、内容没变不重复;极简模式下默认不注入(跟随预设),可在「兼容」页逐项打开 |
53
- | 锁住就不怕手滑 | 场景可以上锁:五个域整体只读,先解锁才能改 |
54
- | 删了能找回,配好能带走 | 删除都进回收站,随时恢复;技能 / 记忆 / 人设 / 提示词都能勾选打包成 zip,也能再导回来 |
55
- | 安全、不添乱 | 密钥默认打码、看明文要令牌;只写自己的文件,技能源文件一个不动,升级重启配置都在 |
43
+ | 亮点 | 说明 |
44
+ | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
45
+ | 一键换场景 | 每个场景各配一套:用哪些 MCP 服务器、哪些技能、哪些人设、哪些记忆;点一下整套切换,关掉自动还原 |
46
+ | 记忆自动送到模型眼前 | 每个场景下写几段 `.md` 就是它的资料库,正文自动进上下文,不用每次复制粘贴 |
47
+ | 给 MCP 服务器写备注 | 像「A 不可用时改用 B 兜底」这种话写进备注,模型看得到,会照做 |
48
+ | 单个工具也能关 | 一台服务器里只停掉某个工具,模型看不见也调不到;「重启」只重连,不会偷偷改变开关 |
49
+ | 技能状况一眼看穿 | 哪些在生效、哪些被同名技能覆盖、哪一份是首选,都标得清清楚楚 |
50
+ | 子智能体 = 一个文件一个角色 | 写一份角色说明就能派活;跑完只回结果、不占你的会话记录;哪些角色能用还能按场景定 |
51
+ | 提示词备好几套 | AGENTS.md 可以存多份(简洁模式 / 教学口吻……),一键切换;场景可以各自绑一份 |
52
+ | 会话不再丢 | 归档按项目分组、能搜、能批量恢复;Claude Code / Cursor / Codex 的聊天记录都能导进来 |
53
+ | 模型一定看得见 | 插件管的内容(记忆 / MCP / 技能 / 子智能体 / 提示词)会主动告诉模型,每个域各发一条、内容没变不重复;极简模式下默认不注入(跟随预设),可在「兼容」页逐项打开 |
54
+ | 锁住就不怕手滑 | 场景可以上锁:MCP / 技能 / 子智能体 / 记忆 / 提示词五个域的**增删改**整体只读,先解锁才能改(场景本身的新建/改名/绑定/启停切换、回收站、重启等不在冻结清单内,见「场景锁定」) |
55
+ | 删了能找回,配好能带走 | 技能 / 记忆 / 人设 / 提示词 / 场景的删除都进回收站,随时恢复;它们都能勾选打包成 zip 再导回来。**会话历史是例外:永久删除不经回收站,不可恢复** |
56
+ | 安全、不添乱 | 密钥默认打码、看明文要令牌;只写自己的文件,技能源文件一个不动,升级重启配置都在。**打码只管界面展示**:密钥在 `cordis.patch.yml` 里始终是明文,每次改配置还会连带产生最多 5 份明文备份副本(见「配置与安全」) |
56
57
 
57
58
  ## 快速开始
58
59
 
@@ -73,7 +74,7 @@ dsh plugin --profile web add dsh-plugin-tool-management@latest
73
74
  装完提醒我硬刷新浏览器。
74
75
  ```
75
76
 
76
- 模型可用 14 个工具管理上述功能(`mcp_manager_*` / `skill_manager_*` / `prompt_manager_*` / `memory_manager_*` / `subagent_manager_*`);脚本走 `POST /dsh-plugin-tool-management/api`(`{op, args}` 协议)。
77
+ 模型可用 14 个工具管理上述功能(`mcp_manager_*` / `skill_manager_*` / `prompt_manager_*` / `memory_manager_*` / `subagent_manager_*`);脚本走 `POST /dsh-plugin-tool-management/api`(`{op, args}` 协议)。其中 `subagent_manager_list` / `subagent_manager_run` 依赖宿主已挂载 `dsh-subagent-*` provider 包,缺席时**不注册**(此时是 12 个,插件只写一条日志,界面不提示)。
77
78
 
78
79
  ---
79
80
 
@@ -90,27 +91,28 @@ dsh plugin --profile web add dsh-plugin-tool-management@latest
90
91
  - **导出**:勾选记忆打包成 zip,保留「场景/名称」层级;bundle 型连目录里的附件一起打进去。只读源文件。
91
92
  - **注入预算**:默认 64 KiB,放不下的跳过并列出清单。删除进回收站。
92
93
  - **子代理会话不注入记忆**:记忆只在**顶层会话**注入 —— 它是"父会话的现场",不是子代理完成任务所需的事实;子代理的上下文只留「角色 + 任务」,要记忆可用 `memory_manager_list/read` 自己取,相关事实应由父代理写进 `task`。其余域不受影响(MCP / 技能 / 提示词照常注入,人设目录按各自的 `catalogDepth`)。
93
- - **场景锁定**:锁定后 MCP / 技能 / 子智能体 / 记忆 / 提示词整体只读,界面禁用 + 服务端守卫双侧拦截;未启动不能上锁,锁定中不能关闭,先解锁再改。
94
+ - **场景锁定**:锁定后 MCP / 技能 / 子智能体 / 记忆 / 提示词五个域的**增删改**整体只读,界面禁用 + 服务端守卫双侧拦截;未启动不能上锁,锁定中不能关闭,先解锁再改。**冻结的是"内容",不是"场景本身"** —— 场景的新建 / 删除 / 改名 / 提示词绑定 / 启用切换 / 回收站的恢复与永久删除、MCP 重启、导出、注入设置与令牌设置都不在冻结清单内(它们不是"场景档案里的内容")。其中「启用切换」在锁定场景正在生效时会被拒绝 —— 清空启用集合会让模型侧的写门禁失去判据(运行时却还是那个场景的档案态)。
94
95
  - **删除场景 = 连记忆一起删**:场景记录、档案与全部记忆进同一条回收站条目,恢复按原路径整条放回;使用中的场景拒绝删除。场景名可改(连带目录与档案,记忆正文不动)。
95
96
 
96
97
  ### 子智能体
97
98
 
98
99
  - **一个文件一个人设**:`agents/<人设>.md`,frontmatter 全可选。
99
100
  - **人设进系统提示词时会带一层角色框**:正文照原样使用,外面加 `# 角色:名` 标题、一行授权(「由调用方指定;与你的默认倾向冲突时以它为准;任务说要什么,怎么做以它为准」)和一句边界(决定**怎么做**,不改变**能做什么**),正文用 `## 角色定义` 围起来;框的语言跟随正文。这是为了让模型把它读成"我被指定为一个角色",而不是身份句后面多跟的两句话——**人设文件一个字都不用改**。框里不列 `description`(那是给**调用方**选人设用的;要给人设子代理看的简介写进正文)。
100
- - **工具限制按 Agent 预设**:每个预设一份白/黑名单(互相排斥),运行期按当前预设生效——堵掉旧「全体并集」名单换预设后子代理起不来的坑。
101
+ - **工具限制按 Agent 预设**:每个预设一份白/黑名单(互相排斥),运行期按当前预设生效——堵掉旧「全体并集」名单换预设后子代理起不来的坑。两条必须知道的行为:**① 白名单会并回当前所有 `mcp__*` 工具**(官方 `allow` 是"清单之外全砍",不并进来会把 MCP 一起砍掉),所以「只勾了 `read`」拦不住一个暴露 shell 的 MCP 服务器;**② 名单里的名字全都对不上当前工具时按"不限制"处理**(fail-open,不是 fail-closed)——极简这类预设下白名单极易整份落空。反向的失败是「写了保留名 `run_code`」:官方 `tools.restrict()` 会直接抛错(子代理起不来),所以插件会把它从名单里剔掉并在结果里说明。
101
102
  - **`output:` 输出要求(硬性契约)**:frontmatter 里可写多行 `output:`(**一行一条**),角色框会把它渲染成 `## 输出要求(硬性)` 单独一节(在角色定义之后)。提示词写「这个角色怎么想」(散文),`output:` 写「产出必须长什么样」(可检验:格式、分级、必标项、禁止项)—— 只有一句抽象要求的角色,产出无法判定"执行了没有";写成可检验的才会被真的遵守。编辑器里有「输出要求(硬性)」多行框,也可以直接写进文件。
102
103
  - **即用即弃**:`subagent_manager_run` 带人设运行、只回传结果、不进 History。子代理的上下文只有**角色 + 任务**:场景记忆不注入(要记忆可用 `memory_manager_*` 自己取,相关事实应由父代理写进 `task`)。场景可绑定可用人设。
103
104
  - **两种上下文模式**:默认新起独立会话(子代理看不到本次对话,任务要写全);把 `inherit` 打开则子代理**继承本次会话已完成的轮次**(与官方 `subagent_fork` 同一套机制),任务只需写新增部分。**只继承已完成的轮次**——在本轮内发起委派时这一轮的内容继承不到(实测:父代理边读边委派,子代理仍从零开始),此时 `task` 要按"写全"对待。与官方两个委派工具(`subagent` / `subagent_fork`)的分界由插件写进上下文:**贴合人设的任务一律走这里**,官方那两个只在没有人设贴合、或需要后台任务(job)时用。
104
105
  - **启停开关**:停用的人设不注入上下文、模型不可见(文件不动);新建 / 导入 / 恢复**默认停用**(v0.8.5 起,与技能 / MCP 同口径),要委派先在这里打开。**进场景按档案勾选集全量对齐**(勾了的开、没勾的关,整段没建 = 全关),退出按进场景前的开关精确还原;场景内这个开关照常可用,改动会同步写进该场景的档案(只有**锁定**才冻结);退出场景时回到进场景前的状态。
105
106
  - **人设目录自动注入**:只列名字 + 描述,模型知道有哪些人设可委派;人设名可改,场景绑定自动跟着改。
106
- - **目录注入(`catalogDepth`)**:控制常驻的人设目录出现在哪些会话里 —— 默认 `1` = 只在顶层注入;写 `2` 让子会话也收到目录;`3` 到两层子会话;界面上的「不限制嵌套」写 `99`,任何深度的会话都注入。**它不限制嵌套**:子代理始终可以继续委派,那由官方决定(`dsh-tool-subagent` 默认 `maxDepth: 3`),本插件不再向官方传 `maxDepth`。子会话看不到常驻目录时,仍可用 `subagent_manager_list` 查询全部人设。三处口径同源(目录过滤、域声明、实况状态),所以"界面说没注入、实际又注入了"这类分叉不会出现。
107
+ - **目录注入(`catalogDepth`)**:控制常驻的人设目录出现在哪些会话里 —— 默认 `1` = 只在顶层注入;写 `2` 让子会话也收到目录;`3` 到两层子会话;界面上的「不限制嵌套」写 `99`,任何深度的会话都注入。**它不限制嵌套**:子代理始终可以继续委派,那由官方决定(宿主侧默认 `maxDepth: 3`;该包不是本插件的依赖,本插件也不再向官方传 `maxDepth`)。「不限制嵌套」是**目录注入深度** `99`,不是递归上限。子会话看不到常驻目录时,仍可用 `subagent_manager_list` 查询全部人设。三处口径同源(目录过滤、域声明、实况状态),所以"界面说没注入、实际又注入了"这类分叉不会出现。
107
108
 
108
109
  ### MCP 服务
109
110
 
110
111
  - **增删改查 + 即改即生效**:写入 `cordis.patch.yml`,HMR 自动生效。
111
112
  - **工具级开关**:单个工具可独立停用(模型看不见也调不到),整台支持批量。
112
113
  - **停着也能看清单**:服务器没在跑时仍显示上次见过的工具名与描述(标「上次运行时」);从没跑过的可以一键「启动服务器读取工具」。
113
- - **密钥打码**:默认保留前 4 个字符、其余打码成 `****`(值不超过 4 位时整串 `****`),「显示密钥」要令牌。
114
+ - **密钥打码**:默认把凭据**整值**打码成 `••••••`(只有名字像密钥的键会打:token / secret / password / auth / api key 这类;`$VAR`、`!!js` 是间接引用,不打码;URL 的 query 整段换成 `?<redacted>`),「显示密钥」要令牌。
115
+ - **打码值永远不会被写进补丁文件**:编辑弹窗的密钥框预填的就是打码值,保存时插件会用补丁里的**原值**顶替(= 这一项没改);原值本身已经是打码值(说明真值曾经被写坏过)或压根没有原值时,该键被跳过并在界面提示 —— 前者要你重新填。补丁里已经存在打码值的密钥,列表页一进来就会告警。**URL 是同一种保护、另一套形态**:查询串被打成 `?<redacted>`,保存时同样按原值顶替;补丁里没有原值可顶替时**整次保存会被拒绝**并提示重新填写完整 URL —— 只删查询串会留下一个"能连上但鉴权失败"的地址,比报错更难发现。
114
116
  - **迁移与备份**:跨项目级/全局迁移失败自动回滚;JSON 导出导入。
115
117
  - **状态与备注自动注入**:列出当前真正可用的 server;**曾经连上过、现在连不上的也会列出并标注**(`(当前未连上;上次连上时 N 个工具)`)—— 这样模型才会说"它没连上,检查一下",而不是把"配了但连不上"读成"本机没配"、建议你装一个。从未连上过的不列。你的备注作为决策提示带给模型;级别分「全局 / 应用级」,新增默认全局。注:极简这类压制型预设默认不注入(模型可用 `mcp_manager_list` 读取服务器名、启停、工具数与备注)——想让它也注入,到「兼容」页的「注入」块打开开关。
116
118
 
@@ -125,7 +127,7 @@ dsh plugin --profile web add dsh-plugin-tool-management@latest
125
127
 
126
128
  ### 提示词预设
127
129
 
128
- - 多套 `~/.dsh/AGENTS.md` 基线,一键应用(宿主每轮重读该文件,下一轮对话生效),保留 5 代备份。
130
+ - 多套 `~/.dsh/AGENTS.md` 基线,一键应用(宿主每步比对该文件版本、变了才重读,下一轮对话生效),保留 5 代备份。
129
131
  - **记得「最近一次应用的是哪份」**:就算你手改过 `AGENTS.md`,模型问「现在用的哪份预设」也答得出来源(会注明「此后文件有变」)。
130
132
  - **描述**:每条预设可写一句「这份是干什么的」,只显示在插件界面里;它存在同目录的 `meta.json`,不进 AGENTS.md,也就不会被注入上下文。
131
133
  - 新建即可写正文,编辑可改 id(= 目录改名,场景绑定自动跟着改)。**被引用的不能删**(场景绑定 / `AGENTS.md` 当前内容 / 退出场景要恢复的那一份),删除进回收站。
@@ -136,13 +138,13 @@ dsh plugin --profile web add dsh-plugin-tool-management@latest
136
138
 
137
139
  - 按项目分组、搜索、批量恢复 / 永久删除、保留期自动清理。
138
140
  - 工作区登记被删后按会话目录重建分组,可一键重新登记。
139
- - 导入 Claude Code / Cursor / Codex / 任意文本;导出 Markdown / JSONL。
141
+ - 导入 Claude Code / Cursor / Codex / 任意文本;导出 Markdown / JSONL。**导出是可读转录,不是完整备份**:只保留 user / assistant 的文本块,工具调用、图片、思考过程与 token 统计都不在里面;Markdown 正文里出现的 `## User` 这类行在再次导入时会被当成轮次分界(导出侧不转义)。要留全量请用「归档」。
140
142
 
141
143
  ### 宿主兼容
142
144
 
143
145
  插件运行期用宿主同一批 `@deepseek-ai/*` 库——必须是同一份物理模块,否则判断退化成猜。
144
146
 
145
- - **「兼容」页**:宿主版本、能力可用数、每个动作走原生/适配/不可用、降级项与原因。只读。
147
+ - **「兼容」页**:宿主版本、能力可用数、每个动作走原生/适配/不可用、降级项与原因。体检本身只读,但这一页有两个明确的写入口:**访问令牌**(写 profile 的 `cordis.patch.yml`,重启生效)与**注入设置**(写 `inject-settings.json`,即时生效)。
146
148
  - **命令行**:`node scripts/doctor.mjs`(体检)、`node scripts/host-deps.mjs --fix`(依赖对齐)、`npm run sync:profile`(把构建产物镜像到 profile 里那份本地安装 —— `file:` 装的是硬链接拷贝,构建新增的文件不会自动过去)。
147
149
  - `minimal` 这类**压制型预设**(persona `complete` / 关闭运行时上下文)下,本插件的注入**默认停用**(跟随预设的设计意图),提示词与技能也因官方那两行没挂而缺席 —— 兼容页逐列标出,同一页的「注入」块可以按域强制打开。
148
150
  - **关掉「技能」「提示词」的勾选 = 真的不再注入**:这两项在标准类预设下由宿主自己送(插件让位),所以取消勾选会**连宿主那份一起停掉**(`skill-catalog` / `agent-instructions` 的消息在这一步不再放行)。其余三项(记忆 / MCP / 子智能体)宿主本来就不送,勾选完全生效。
@@ -152,29 +154,42 @@ dsh plugin --profile web add dsh-plugin-tool-management@latest
152
154
 
153
155
  ## 数据落点
154
156
 
155
- | 内容 | 位置 |
156
- | ------------------------------------------- | ------------------------------------------------------------------------- |
157
- | MCP 定义 | `~/.dsh/cordis.patch.yml`(插件只写它;改前自动备份到 hub 的 `backups/`) |
158
- | 技能策略 / 自定义目录 | `~/.dsh/tool-management/skills-state.json` |
159
- | 技能 / 记忆 / 人设 / 预设 | `~/.dsh/tool-management/{skills,memories,subagents,prompts}/` |
160
- | 子智能体启停 | `~/.dsh/tool-management/subagents-index.json` |
161
- | 回收站 | `~/.dsh/tool-management/trash/{skills,subagents,prompts,scenes}-trash/` |
162
- | 归档账本 / 保留期 | `~/.dsh/tool-management/history-*.json` |
163
- | 记忆索引 / 场景 / 档案 | `~/.dsh/tool-management/memories-index.json` |
164
- | MCP 侧车(停用表 / 已知工具 / 备注 / 设置) | `~/.dsh/tool-management/mcp-*.json` |
165
- | 注入设置(五个域开关 / 压制型预设口径) | `~/.dsh/tool-management/inject-settings.json` |
166
- | 运行日志 / patch 备份 | `~/.dsh/tool-management/tool-management.log` · `backups/` |
157
+ | 内容 | 位置 |
158
+ | ------------------------------------------- | ---------------------------------------------------------------------------------- |
159
+ | MCP 定义 | `~/.dsh/cordis.patch.yml`(插件只写它;改前自动备份到 hub 的 `backups/`) |
160
+ | 技能策略 / 自定义目录 | `~/.dsh/tool-management/skills-state.json` |
161
+ | 技能 / 记忆 / 人设 / 预设 | `~/.dsh/tool-management/{skills,memories,subagents,prompts}/` |
162
+ | 子智能体启停 | `~/.dsh/tool-management/subagents-index.json` |
163
+ | 回收站(技能/人设/预设/场景) | `~/.dsh/tool-management/trash/{skills,subagents,prompts,scenes}-trash/` |
164
+ | 记忆回收站 | `~/.dsh/tool-management/memories-trash/`(**hub 根下独立目录,不在 `trash/` 里**) |
165
+ | 归档账本 / 保留期 | `~/.dsh/tool-management/history-*.json` |
166
+ | 记忆索引 / 场景 / 档案 | `~/.dsh/tool-management/memories-index.json` |
167
+ | MCP 侧车(停用表 / 已知工具 / 备注 / 设置) | `~/.dsh/tool-management/mcp-*.json` |
168
+ | 注入设置(五个域开关 / 压制型预设口径) | `~/.dsh/tool-management/inject-settings.json` |
169
+ | 运行日志 / patch 备份 | `~/.dsh/tool-management/tool-management.log` · `backups/` |
167
170
 
168
171
  **插件安装目录里不存用户数据**(`dsh plugin update` 会整体替换该目录)。
169
172
 
170
- ## 配置与安全
173
+ **`backups/` 的保留份数是「每份 patch 文件各 5 份」**:备份名带层级标签(`.global` / `.profile-<名>`),所以全局与每个 profile 各自留 5 份 —— 不是全局共 5 份。一次批量操作(如 `mcpm-set-all`、进入场景的多次写)就可能把某个层级的 5 个槽位全吃掉、挤掉更早的版本。
171
174
 
172
- | 字段 | 说明 |
173
- | -------------- | ------------------------------------------------------------------------------------------------------------------------- |
174
- | `token` | 访问令牌。设了之后**所有写操作 + 明文密钥**都要求 `x-dsh-token`;**不设时明文接口一律关闭**。也是 curl / 局域网的逃生门。 |
175
- | `maxBodyBytes` | 请求体上限,默认 88 MiB。 |
175
+ ## 配置与安全
176
176
 
177
- - **浏览器**:读写走 cookie,不需要 token;但**明文密钥**(显示密钥 / 导出)要令牌。
177
+ | 字段 | 说明 |
178
+ | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
179
+ | `token` | 访问令牌。设了之后**所有写操作 + 明文密钥**都要求 `x-dsh-token`;**不设时明文接口一律关闭**。也是 curl / 局域网的逃生门。不想把令牌写进配置文件时,可改用环境变量 `DSH_PLUGIN_TOOL_MANAGEMENT_TOKEN`(两者都给了以 `config.token` 为准;`tokenDisabled: true` 会让两者都不生效)。 |
180
+ | `tokenDisabled` | `true` = 令牌**保留在配置里**但当前不生效(在兼容页点「关闭保护」写入的就是这一行)。写操作不再要求令牌;明文查看仍然要凭令牌(明文本身就是凭据)。随时可以在兼容页「开启保护」开回来,不需要重新输一遍。 |
181
+ | `maxBodyBytes` | 请求体上限,默认 88 MiB。 |
182
+
183
+ **关于磁盘上的明文(必读)**:打码**只发生在界面展示**。MCP 的 `env` / `headers` 与插件自己的 `config.token` 在 `~/.dsh/cordis.patch.yml`(及其 profile 副本)里始终是明文,而每次改配置前插件会把**整份文件**备份进 `~/.dsh/tool-management/backups/`(按文件各留 5 份、不加密、不轮转、卸载也不回收)—— 于是一份密钥最多会有 `5 ×(含它的 patch 文件数)+ 1` 份明文副本。令牌门禁管的是"谁能通过 HTTP 拿到明文",**管不到磁盘读取**:本机任何对该目录有读权限的进程都能一次读走全部凭据。唯一的真实防线是操作系统的文件权限。**要清理:设置 → 工具 → 兼容 → 「清理旧备份」**(在「刷新」旁边,按钮上直接写着现有份数),可选**每个层级各一组档位**(N 份 = 把那层最旧的 N 份删掉,档位到该层现有份数为止、默认 2 份;两层各自选,所以合计能凑出任意数 —— 奇数也行)—— 每行下面写着"删完还剩几份",删除要在弹窗里再确认一次(确认那行会给出合计文件数);只删备份文件、不碰当前配置。也可以手工删除 `~/.dsh/tool-management/backups/` 下的旧文件。
184
+
185
+ - **浏览器**:读写走 cookie,不需要 token;但**明文密钥**(显示密钥 / 导出)要令牌。**令牌在「工具 → 兼容」页的「访问令牌」块管理**(与页面无关的固定入口)。那一块是**一枚状态胶囊 + 一句实况 + 三行**:行名说"这一步管到哪儿"、按钮说动词,胶囊只回答"保护开没开、这一程解锁没有"。
186
+ - **本次启动**:填一次,管到 DSH 退出(只写本机浏览器)。填对了,这一行**就地**变成一枚绿胶囊「已解锁」+「清除」(不是塌成一颗按钮 —— 那样看着像刚填的东西没了),宿主才认这串令牌、下面两行才动得了;宿主侧还没有令牌时这一行不出现(没东西可验)。
187
+ - **宿主配置**:写进 profile 的 `cordis.patch.yml`(只动那两行,注释与 `!!js` 守卫原样保留,改前自动备份),重启 DSH 生效。宿主还没配时这一行的按钮就是主操作(「设置令牌」,点开输**两遍**新令牌防手滑);已配之后换令牌点「修改令牌」,表单里多一栏**当前令牌** —— 换令牌要能证明知道当前令牌,而**解锁过不算**(与「关闭保护」同一条口径):必须在表单里当场再输一次;点开时光标就落在这一栏,三栏**竖排**、保存与取消跟在下面(表单打开时只剩这一行,回车 = 保存),三栏齐了「保存新令牌」才可按,输错时那一格会标红。
188
+ - **删除令牌**(与「修改令牌」同一行):把 `token` 与 `tokenDisabled` 两行从配置里删掉,回到**还没设置**那一态 —— 写操作不再要凭证、明文密钥随之不可见。同样就地展开确认框、当场再输一次当前令牌;回执会如实提醒"旧备份里仍有明文副本"(备份是整份配置的副本,要一并清掉用页头的「清理旧备份」)。想保留令牌、只是暂时不要门禁时用「关闭保护」,别用删除。
189
+ - **保护开关**:点「关闭保护」会**就地展开确认框(此时只有这一行,与「修改令牌」同一规矩)**,要再输一次当前令牌(解锁过也要输 —— 请求头里那份已验证的凭证对"关闭"不作数,防的是解锁之后顺手一点就把防护关了);确认后只写一行 `tokenDisabled: true`,**令牌原样留在配置里**,随时点「开启保护」就能开回来(不必重新输一遍)。关闭后写操作不再要求令牌;**明文查看仍然要凭令牌**(明文本身就是凭据)。开启方向沿用原口径:解锁过,或输入框里填着当前令牌即可 —— 否则"能打开 GUI 就能关掉保护",令牌等于白配。
190
+ - **状态胶囊**:还没有令牌 / 保护已关闭 / 保护已开启 · 待解锁 / 保护已开启 · 已解锁。配置改了还没重启时,胶囊下面会多一条橙色横幅说清(胶囊说的是当前进程)。
191
+ - **令牌功能没在生效时,不再挂那句「去填令牌」**:那句话只在实际有门禁(当前进程存在生效令牌)时才留下;关掉或没配时它会被收掉,由面板上的状态胶囊说出实际情况。
192
+ - **提示出现在哪里**:令牌没过时的那句话(含右侧的「填写令牌」按钮)**恰好只有一份** —— 有弹窗打开时在弹窗里(它是当前操作的阻塞原因,放在最显眼处),没有弹窗时在页签下方。它与"当前是哪一页、那一页怎么渲染错误"无关,所以任何一个入口(含各种弹窗里的提交、导入、导出、批量操作)都不会漏提示。
178
193
  - **curl / 脚本**:带 `x-dsh-token`,或带浏览器 cookie。
179
194
  - **HTTP 状态码口径**:除安全栅栏的 401/403 外,令牌缺失、业务失败等一律 HTTP 200 + `{ ok: false, error }` —— 脚本分流请以 `body.ok` 为准,不要按状态码。
180
195
  - **栅栏的降级面**:宿主缺 `connection` 服务时,栅栏退到「Host 回环 + 同源」判定(没有浏览器 cookie 可验),本机任意进程伪造 `Host: localhost` 即可调用写 op —— 把端口转发到局域网/公网的场景**务必配令牌**,它是这条降级面上的最后一道门。
@@ -183,14 +198,14 @@ dsh plugin --profile web add dsh-plugin-tool-management@latest
183
198
 
184
199
  ## 常见问题
185
200
 
186
- | 现象 | 解决 |
187
- | ----------------------------------------- | --------------------------------------------------------------------------------------------------- |
188
- | 装完没有页面 | 硬刷新;不行重启 DSH。 |
189
- | 重复 MCP 页签 | 删 `cordis.patch.yml` 里的旧 loader 行后重启。 |
190
- | 改坏配置 DSH 起不来 | 取最近的 `.bak-<时间戳>` 恢复。 |
191
- | 升级 DSH 后动作不可用 | 设置 → 工具 → **兼容** 看原因;`doctor.mjs` → `host-deps.mjs --fix`。 |
192
- | `approval=never` 还要确认吗 | 不弹卡,直接放行并记日志;想问回来切回「工作区内修改」。 |
193
- | `subagent_manager_run` 报 provider 不可用 | 对应 provider 没注册:`spawn`(默认)/ `fork`(`inherit`)分别挂 `@deepseek-ai/dsh-subagent-spawn-in-process` / `-fork-in-process` 后重启。 |
201
+ | 现象 | 解决 |
202
+ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
203
+ | 装完没有页面 | 硬刷新;不行重启 DSH。 |
204
+ | 重复 MCP 页签 | 删 `cordis.patch.yml` 里的旧 loader 行后重启。 |
205
+ | 改坏配置 DSH 起不来 | 从 `~/.dsh/tool-management/backups/` 取最近的 `cordis.patch.yml.<层级>.bak-<时间戳>` 覆盖回去(每份 patch 各留 5 份)。 |
206
+ | 升级 DSH 后动作不可用 | 设置 → 工具 → **兼容** 看原因;`doctor.mjs` → `host-deps.mjs --fix`。 |
207
+ | `approval=never` 还要确认吗 | 不弹卡,直接放行并记日志;想问回来切回「工作区内修改」。 |
208
+ | `subagent_manager_run` 报 provider 不可用 | 对应 provider 没注册:`spawn`(默认)/ `fork`(`inherit`)分别挂 `@deepseek-ai/dsh-subagent-spawn-in-process` / `-fork-in-process` 后重启。 |
194
209
  | 场景绑了 A 人设,官方 `subagent` 还跑别的 | 官方那两个是宿主的工具,本插件管不到它们的可见性;插件会把分界写进上下文(贴合人设的一律走 `subagent_manager_run`,官方只在没人设贴合或要后台跑时用),`inherit` 也已对齐 fork 的继承能力。 |
195
210
 
196
211
  ---
@@ -200,12 +215,14 @@ dsh plugin --profile web add dsh-plugin-tool-management@latest
200
215
  ```bash
201
216
  npm install
202
217
  npm run build # tsc + 同步客户端
203
- npm test # 构建 + i18n + 冒烟测试(装配与渲染不抛错)
218
+ npm test # 构建 + i18n + 契约测试(纯函数不变量与宿主契约;渲染类测试已于 2026-09-19 移除)
204
219
  npm run check:i18n # 词典自检
205
220
  npm run doctor # 宿主兼容体检
206
221
  ```
207
222
 
208
- `lib/` 不入版本库,克隆后先 `npm run build`。改完重启 `dsh web` 才生效。运行时依赖仅 `fflate`;`@deepseek-ai/*` 一律用宿主那份。
223
+ `lib/` 不入版本库,克隆后先 `npm run build`。改完重启 `dsh web` 才生效。运行时依赖:`fflate`(导出打包)、`js-yaml`(写宿主补丁前的解析校验,惰性加载);`@deepseek-ai/*` 一律用宿主那份。
224
+
225
+ > **部署注意**:`npm run build` 的 profile 镜像清单**不含 `node_modules`**,所以本地联调时新增的运行时依赖(如 `js-yaml`)要么在 profile 侧装一份,要么接受「补丁写入校验跳过并上报」的降级 —— 校验器装不上只影响这一道保险,不阻断写入。`npm install` 装到 profile 的正式安装不受影响(依赖会随包安装)。
209
226
 
210
227
  ## 许可证
211
228