dsh-plugin-tool-management 0.20.2 → 0.21.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 (42) hide show
  1. package/CHANGELOG.md +60 -1
  2. package/README.md +4 -1
  3. package/README_EN.md +4 -1
  4. package/docs/update.md +3 -1
  5. package/lib/client.js +225 -54
  6. package/lib/compat/capability-specs.js +60 -1
  7. package/lib/compat/host-contracts.js +72 -0
  8. package/lib/compat/host-probe.js +59 -0
  9. package/lib/compat/host-roots.js +1 -1
  10. package/lib/compat/probe.js +84 -13
  11. package/lib/context-inject-frame.js +134 -26
  12. package/lib/context-inject.js +12 -9
  13. package/lib/http-api.js +3 -2
  14. package/lib/http-fence.js +26 -1
  15. package/lib/index.js +5 -2
  16. package/lib/mcp/manager.js +6 -1
  17. package/lib/mcp/state-section.js +75 -34
  18. package/lib/memories/constants.js +39 -7
  19. package/lib/memories/projection.js +18 -18
  20. package/lib/memories/service.js +8 -7
  21. package/lib/memories/snapshot.js +27 -17
  22. package/lib/ops/compat.js +64 -2
  23. package/lib/ops/memory.js +2 -2
  24. package/lib/ops/prompts.js +30 -2
  25. package/lib/request-gate.js +5 -4
  26. package/lib/skills/catalog.js +2 -2
  27. package/lib/subagents/catalog.js +4 -4
  28. package/lib/subagents/child-preset.js +120 -0
  29. package/lib/subagents/persona-parse.js +9 -9
  30. package/lib/subagents/run-provider.js +218 -62
  31. package/lib/subagents/tool-filter.js +12 -12
  32. package/lib/subagents/tools.js +29 -22
  33. package/lib/tools/deps.js +4 -4
  34. package/lib/tools/mcp.js +34 -34
  35. package/lib/tools/memory.js +34 -34
  36. package/lib/tools/prompt.js +5 -5
  37. package/lib/tools/remove.js +17 -17
  38. package/lib/tools/scene.js +66 -66
  39. package/lib/tools/skills.js +21 -21
  40. package/lib/tools/subagent.js +13 -13
  41. package/lib/tools/table.js +2 -2
  42. package/package.json +25 -24
package/lib/client.js CHANGED
@@ -46,13 +46,13 @@ window.__ModuleLoader__.load({
46
46
  }))
47
47
  }
48
48
  const CSS =
49
- ':root{--dsm-warn:#d49245;--dsm-warn-bg:rgba(212,146,69,.12);--dsm-radius-s:4px;--dsm-radius-m:8px;--dsm-radius-l:12px}' +
49
+ ':root{--dsm-warn:#d49245;--dsm-warn-bg:rgba(212,146,69,.12);--dsm-radius-s:var(--dsw-radius-xs,4px);--dsm-radius-m:var(--dsw-radius-sm,8px);--dsm-radius-l:var(--dsw-radius-md,12px)}' +
50
50
  '.dsm-stat-row{display:flex;min-width:0;align-items:center;gap:8px}' +
51
51
  '.dsm-version{color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:400;letter-spacing:.3px;line-height:1}' +
52
52
  '.dsm-row-hint{grid-column:1/-1;margin:-4px 0 8px;color:var(--dsm-warn);font-size:12px;line-height:16px}' +
53
53
  '.dsm-tools{display:flex;flex-direction:column;gap:8px}' +
54
54
  '.dsm-tool{padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-1)}' +
55
- '.dsm-tool-name{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;font-weight:400;word-break:break-all}' +
55
+ '.dsm-tool-name{font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);font-size:12px;font-weight:400;word-break:break-all}' +
56
56
  '.dsm-tool-name-row{display:flex;align-items:center;gap:8px}' +
57
57
  // 标签紧跟工具名(原来是 space-between,标签被推到中间、与名字隔着一大片空白);
58
58
  // 开关单独用 margin-left:auto 顶到最右,标签不再受它的位置影响。
@@ -61,7 +61,7 @@ window.__ModuleLoader__.load({
61
61
  '.dsm-tool-desc{min-width:0;flex:1;margin-top:4px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:17px}' +
62
62
  '.dsm-tool-params{display:flex;flex-direction:column;gap:4px;margin-top:8px;padding-top:8px;border-top:1px dashed var(--dsw-alias-border-l2)}' +
63
63
  '.dsm-tool-param{display:flex;gap:8px;font-size:12px;align-items:baseline}' +
64
- '.dsm-tool-param-key{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-weight:400;min-width:80px;word-break:break-all}' +
64
+ '.dsm-tool-param-key{font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);font-weight:400;min-width:80px;word-break:break-all}' +
65
65
  '.dsm-tool-param-type{color:var(--dsw-alias-label-tertiary);min-width:56px}' +
66
66
  '.dsm-tool-param-desc{flex:1;color:var(--dsw-alias-label-secondary);word-break:break-all}' +
67
67
  '.dsm-modal textarea.dsm-control.dsm-textarea-sm{min-height:88px}' +
@@ -77,7 +77,7 @@ window.__ModuleLoader__.load({
77
77
  在右,组内间距 8px 与行间隙一致。 */
78
78
  '.dsm-detail-title-acts{display:flex;align-items:center;gap:8px}' +
79
79
  `
80
- .dsm-pick-list{display:flex;max-height:240px;flex-direction:column;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);padding:4px}.dsm-tab{padding:8px 14px;border:0;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px}.dsm-tab-active{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-success-primary)}.dsm-section{box-sizing:border-box;display:flex;width:100%;max-width:820px;min-width:0;margin:0 auto;padding:4px 0 24px;container-type:inline-size;flex-direction:column;gap:12px;color:var(--dsw-alias-label-primary);font-family:inherit}.dsm-head{display:flex;flex-direction:column;align-items:stretch;gap:16px}.dsm-feedback-links{display:flex;flex-direction:column;align-items:flex-end;gap:4px}.dsm-title{margin:0;font-size:18px;line-height:26px;font-weight:600;letter-spacing:-.2px;white-space:nowrap}.dsm-feedback-link{display:inline-flex;min-height:30px;align-items:center;gap:4px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;font-weight:400;line-height:18px;text-decoration:none;white-space:nowrap}.dsm-feedback-link:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.dsm-feedback-link:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsm-feedback-link svg{flex:none}.dsm-desc{margin:12px 0 0;display:-webkit-box;overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:22px;-webkit-box-orient:vertical;-webkit-line-clamp:2}.dsm-actions{display:flex;flex-wrap:wrap;gap:8px;margin-left:0;flex:none}
80
+ .dsm-pick-list{display:flex;max-height:240px;flex-direction:column;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);padding:4px}.dsm-tab{padding:8px 14px;border:0;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px}.dsm-tab-active{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-success-primary)}.dsm-section{box-sizing:border-box;display:flex;width:100%;max-width:820px;min-width:0;margin:0 auto;padding:4px 0 24px;container-type:inline-size;flex-direction:column;gap:12px;color:var(--dsw-alias-label-primary);font-family:inherit}.dsm-head{display:flex;flex-direction:column;align-items:stretch;gap:16px}.dsm-feedback-links{display:flex;flex-direction:column;align-items:flex-end;gap:4px}.dsm-title{margin:0;font-size:18px;line-height:26px;font-weight:600;letter-spacing:-.2px;white-space:nowrap}.dsm-feedback-link{display:inline-flex;min-height:30px;align-items:center;gap:4px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;font-weight:400;line-height:18px;text-decoration:none;white-space:nowrap}.dsm-feedback-link:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.dsm-feedback-link:focus-visible{outline-style:solid;outline-color:var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-width:var(--dsw-focus-ring-width,2px);outline-offset:2px}.dsm-feedback-link svg{flex:none}.dsm-desc{margin:12px 0 0;display:-webkit-box;overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:22px;-webkit-box-orient:vertical;-webkit-line-clamp:2}.dsm-actions{display:flex;flex-wrap:wrap;gap:8px;margin-left:0;flex:none}
81
81
  /* 页头动作行:内边距收 2px。批量按钮要按「取消全选」定宽(见 .dsm-btn-bulk),
82
82
  收这一点正好把定宽多出来的宽度抵掉,整行仍在一行内。 */
83
83
  .dsm-actions .dsm-btn{padding:0 12px}
@@ -162,7 +162,12 @@ button.dsm-qty:disabled{cursor:default}
162
162
  铁律:**同一行 / 同一组按钮必须同高** —— 行里有实心或常规动作就整行 30px(弹窗脚、
163
163
  页头动作条),整行都是行内小动作才整行 24px(列表行、回收站行、分段组)。
164
164
  (CSS 注释里别写反引号:这段整体在 JS 模板字符串里,反引号会把字符串截断。) */
165
- .dsm-btn{box-sizing:border-box;display:inline-flex;min-height:30px;align-items:center;justify-content:center;padding:0 12px;border:1px solid transparent;border-radius:var(--dsm-radius-m);background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);font:inherit;font-size:13px;font-weight:600;white-space:nowrap;cursor:pointer}.dsm-btn:hover:not(:disabled){opacity:.9}.dsm-btn:disabled{opacity:.48;cursor:default}.dsm-btn-secondary{border-color:var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary)}.dsm-btn-quiet{border-color:var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);min-height:24px;padding:0 8px;font-size:12px;font-weight:400}.dsm-btn-danger{border-color:transparent;background:var(--dsw-alias-state-error-primary);color:#fff}.dsm-btn-lock{background:transparent;color:var(--dsm-warn)}.dsm-btn:focus-visible,.dsm-control:focus-visible,.dsm-select-trigger:focus-visible,.dsm-source-head:focus-visible,.dsm-switch:focus-visible,.dsm-file-remove:focus-visible,.dsm-dropzone:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}
165
+ .dsm-btn{box-sizing:border-box;display:inline-flex;min-height:30px;align-items:center;justify-content:center;padding:0 12px;border:1px solid transparent;border-radius:var(--dsm-radius-m);background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);font:inherit;font-size:13px;font-weight:600;white-space:nowrap;cursor:pointer}.dsm-btn:hover:not(:disabled){opacity:.9}.dsm-btn:disabled{opacity:.48;cursor:default}.dsm-btn-secondary{border-color:var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary)}.dsm-btn-quiet{border-color:var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);min-height:24px;padding:0 8px;font-size:12px;font-weight:400}.dsm-btn-danger{border-color:transparent;background:var(--dsw-alias-state-error-primary);color:#fff}.dsm-btn-lock{background:transparent;color:var(--dsm-warn)}/* 焦点环**分三项写、不写 outline 简写**(PR #18 指出的问题,2026-10-17 实测确认):
166
+ 宿主 focus.css 只声明 outline-color / outline-width、刻意不声明 outline-style,
167
+ 好让「控件自己设了 outline:none 就真的不画」。outline:2px solid <色> 这种简写会把
168
+ outline-style 一起重置成 solid,等于替宿主"重新打开"那些本该没有环的控件。
169
+ 三项分开写就只覆盖颜色与宽度,style 仍由宿主与控件自己定。 */
170
+ .dsm-btn:focus-visible,.dsm-source-head:focus-visible,.dsm-switch:focus-visible,.dsm-file-remove:focus-visible,.dsm-dropzone:focus-visible{outline-style:solid;outline-color:var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-width:var(--dsw-focus-ring-width,2px);outline-offset:2px}
166
171
  /* 列表工具条的一行:搜索占满剩余、筛选下拉定宽靠右。
167
172
  下拉宽度 210 → 150(用户 2026-10-12:「全部级别缩短,当前太长了,多的内容交给输入框」;
168
173
  技能「全部来源」、记忆「全部场景」、会话保留期同一条口径)。触发器内的文本本来就带
@@ -193,7 +198,7 @@ button.dsm-qty:disabled{cursor:default}
193
198
  .dsm-source-body .dsm-source{border:0;border-radius:0;border-bottom:1px solid var(--dsw-alias-border-l1)}
194
199
  .dsm-source-body .dsm-source:last-child{border-bottom:0}
195
200
  .dsm-note{overflow:hidden;margin-top:4px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:17px;text-overflow:ellipsis;white-space:nowrap}.dsm-tags{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.dsm-tag{display:inline-flex;min-height:20px;align-items:center;padding:0 8px;border:1px solid var(--dsw-alias-border-l3);border-radius:var(--dsm-radius-s);color:var(--dsw-alias-label-secondary);font-size:12px;white-space:nowrap}.dsm-tag-on{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.dsm-tag-off{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.dsm-tag-warn{border-color:var(--dsm-warn);color:var(--dsm-warn)}
196
- .dsm-switch{position:relative;width:32px;height:18px;flex:none;padding:0;border:0;border-radius:99px;background:var(--dsw-alias-border-l3);cursor:pointer}.dsm-switch:after{position:absolute;top:3px;left:3px;width:12px;height:12px;border-radius:50%;background:#fff;content:"";transition:transform 160ms ease}.dsm-switch-on{background:var(--dsw-alias-state-success-primary)}.dsm-switch-on:after{transform:translateX(14px)}.dsm-switch:disabled{opacity:.45;cursor:default}.dsm-trash-count{margin-left:auto;padding:4px 8px;border-radius:99px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font-size:11px;font-variant-numeric:tabular-nums}.dsm-empty{padding:24px 12px;color:var(--dsw-alias-label-tertiary);font-size:12px;text-align:center}.dsm-feedback{padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);color:var(--dsw-alias-label-secondary);font-size:12px}.dsm-undo{display:flex;align-items:center;gap:8px}.dsm-undo-text{min-width:0;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dsm-undo-close{padding:0;border:0;background:transparent;font:inherit;font-size:13px;cursor:pointer;width:24px;height:24px;color:var(--dsw-alias-label-secondary)}.dsm-undo-close:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsm-undo.dsm-warning{background:var(--dsm-warn-bg)}.dsm-undo.dsm-warning .dsm-undo-text{font-weight:400}.dsm-undo.dsm-warning .dsm-undo-close{color:var(--dsm-warn)}.dsm-warning{border-color:var(--dsm-warn);color:var(--dsm-warn)}.dsm-error{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
201
+ .dsm-switch{position:relative;width:32px;height:18px;flex:none;padding:0;border:0;border-radius:99px;background:var(--dsw-alias-border-l3);cursor:pointer}.dsm-switch:after{position:absolute;top:3px;left:3px;width:12px;height:12px;border-radius:50%;background:#fff;content:"";transition:transform 160ms ease}.dsm-switch-on{background:var(--dsw-alias-state-success-primary)}.dsm-switch-on:after{transform:translateX(14px)}.dsm-switch:disabled{opacity:.45;cursor:default}.dsm-trash-count{margin-left:auto;padding:4px 8px;border-radius:99px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font-size:11px;font-variant-numeric:tabular-nums}.dsm-empty{padding:24px 12px;color:var(--dsw-alias-label-tertiary);font-size:12px;text-align:center}.dsm-feedback{padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);color:var(--dsw-alias-label-secondary);font-size:12px}.dsm-undo{display:flex;align-items:center;gap:8px}.dsm-undo-text{min-width:0;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dsm-undo-close{padding:0;border:0;background:transparent;font:inherit;font-size:13px;cursor:pointer;width:24px;height:24px;color:var(--dsw-alias-label-secondary)}.dsm-undo-close:focus-visible{outline-style:solid;outline-color:var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-width:var(--dsw-focus-ring-width,2px);outline-offset:2px}.dsm-undo.dsm-warning{background:var(--dsm-warn-bg)}.dsm-undo.dsm-warning .dsm-undo-text{font-weight:400}.dsm-undo.dsm-warning .dsm-undo-close{color:var(--dsm-warn)}.dsm-warning{border-color:var(--dsm-warn);color:var(--dsm-warn)}.dsm-error{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
197
202
  .dsm-mask{position:fixed;z-index:1100;inset:0;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(0,0,0,.62)}.dsm-modal{box-sizing:border-box;display:flex;width:min(560px,100%)!important;max-height:min(760px,calc(100vh - 48px));min-width:0;flex-direction:column;gap:16px;padding:20px;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-l);background:var(--dsw-alias-bg-layer-2);box-shadow:var(--dsw-shadow-lv3)}.dsm-modal-sm{width:min(480px,100%)!important}.dsm-modal-lg,.dsm-modal-wide{width:min(720px,100%)!important}.dsm-modal-list{width:min(560px,100%)!important;height:min(640px,calc(100vh - 48px))!important;max-height:none!important;overflow:hidden}.dsm-modal-list.dsm-modal-editor{width:min(720px,100%)!important}.dsm-modal-body{display:flex;min-height:0;flex-direction:column;gap:8px}.dsm-modal-list .dsm-modal-body{flex:1;overflow:auto;overscroll-behavior:contain}.dsm-modal-head{display:flex;align-items:flex-start;gap:12px}
198
203
  /* 头部这一层包住「标题行 + 令牌提示」:提示挂在头部里面(而不是 head 与 body 之间)是为了
199
204
  让 body 的子节点位置稳定 —— 位置一变 React 会把 body 整棵重挂(输入框失焦、下拉归零)。 */
@@ -204,7 +209,7 @@ button.dsm-qty:disabled{cursor:default}
204
209
  /* 条目清单必须自己声明字号:裸 ul/li 会从宿主那里继承 16px 近白正文(兼容页「工具能用 ≠
205
210
  模型知道」三条),比同页的说明文字大两号。口径照 .dsm-help-list。 */
206
211
  .dsm-upload-requirements ul{display:flex;margin:4px 0 0;padding-left:16px;flex-direction:column;gap:4px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:17px}
207
- .dsm-upload-req{margin-top:8px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:17px}.dsm-detail-section{gap:8px}.dsm-detail-path,.dsm-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px}.dsm-detail-path{padding:8px 8px;border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);word-break:break-all}
212
+ .dsm-upload-req{margin-top:8px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:17px}.dsm-detail-section{gap:8px}.dsm-detail-path,.dsm-code{font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);font-size:11px}.dsm-detail-path{padding:8px 8px;border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);word-break:break-all}
208
213
  /* 技能详情的「路径 + 编辑器查看」同一行(用户 2026-10-12):路径条 flex:1 吃掉剩余宽度,
209
214
  按钮 flex:none 固定不动。按钮那颗 .dsm-btn 自带 white-space:nowrap,长路径自己折行。 */
210
215
  .dsm-detail-path-row{display:flex;min-width:0;align-items:center;gap:8px}
@@ -240,11 +245,11 @@ button.dsm-qty:disabled{cursor:default}
240
245
  (.dsm-trash-group 正是),超出的条目会被**直接裁掉** —— 看不见、也滚不到,表现就是
241
246
  「没有滚动条 + 内容缺一截」。列表项的尺寸一律由内容决定,滚动只交给 body 这一层。 */
242
247
  .dsm-modal-list .dsm-modal-body>*{flex:none}
243
- .dsm-fm{display:flex;flex-direction:column;overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-1)}.dsm-fm-row{display:grid;grid-template-columns:minmax(96px,150px) minmax(0,1fr);gap:8px;padding:8px 12px;border-bottom:1px solid var(--dsw-alias-border-l1);font-size:12px;line-height:18px}.dsm-fm-row:last-child{border-bottom:0}.dsm-fm-key{color:var(--dsw-alias-label-secondary);word-break:break-word}.dsm-fm-raw{margin-left:4px;color:var(--dsw-alias-label-tertiary);font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px}.dsm-fm-val{min-width:0;color:var(--dsw-alias-label-primary);word-break:break-word;white-space:pre-wrap}
248
+ .dsm-fm{display:flex;flex-direction:column;overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-1)}.dsm-fm-row{display:grid;grid-template-columns:minmax(96px,150px) minmax(0,1fr);gap:8px;padding:8px 12px;border-bottom:1px solid var(--dsw-alias-border-l1);font-size:12px;line-height:18px}.dsm-fm-row:last-child{border-bottom:0}.dsm-fm-key{color:var(--dsw-alias-label-secondary);word-break:break-word}.dsm-fm-raw{margin-left:4px;color:var(--dsw-alias-label-tertiary);font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);font-size:11px}.dsm-fm-val{min-width:0;color:var(--dsw-alias-label-primary);word-break:break-word;white-space:pre-wrap}
244
249
  @container(max-width:780px){
245
250
  .dsm-path{display:none}
246
251
  }@container(max-width:520px){.dsm-head{flex-direction:column}.dsm-actions{width:100%;margin-left:0}.dsm-actions .dsm-btn{flex:1}.dsm-filters{flex-direction:column}.dsm-source-filter{width:100%}}
247
- .dsm-hist-group-head{gap:8px}.dsm-hist-group-extra{display:flex;align-items:center;gap:8px;flex:none;margin-left:4px}.dsm-hist-group-extra .dsm-tag{flex:none}.dsm-hist-dir-missing{flex:none;padding:1px 6px;border:1px solid var(--dsw-alias-state-error-primary);border-radius:var(--dsm-radius-s);color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:16px;white-space:nowrap;cursor:default;user-select:none}.dsm-hist-register-path{margin-top:-4px;color:var(--dsw-alias-label-tertiary);font-family:var(--dsw-font-mono,monospace);font-size:11px;word-break:break-all}/* (.dsm-hist-batch / .dsm-hist-batch-actions 已随批量动作移进列表工具条删除;
252
+ .dsm-hist-group-head{gap:8px}.dsm-hist-group-extra{display:flex;align-items:center;gap:8px;flex:none;margin-left:4px}.dsm-hist-group-extra .dsm-tag{flex:none}.dsm-hist-dir-missing{flex:none;padding:1px 6px;border:1px solid var(--dsw-alias-state-error-primary);border-radius:var(--dsm-radius-s);color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:16px;white-space:nowrap;cursor:default;user-select:none}.dsm-hist-register-path{margin-top:-4px;color:var(--dsw-alias-label-tertiary);font-family:var(--ds-font-family-code,monospace);font-size:11px;word-break:break-all}/* (.dsm-hist-batch / .dsm-hist-batch-actions 已随批量动作移进列表工具条删除;
248
253
  .dsm-hist-batch-count 仍用于导出弹窗里的「已选 N 项」计数。) */
249
254
  .dsm-hist-batch-count{color:var(--dsw-alias-label-secondary);font-size:12px;font-variant-numeric:tabular-nums;white-space:nowrap}.dsm-dir-row{display:flex;gap:8px}.dsm-dir-row .dsm-control{flex:1}.dsm-dir-list{display:flex;max-height:220px;flex-direction:column;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:var(--dsw-alias-bg-layer-1)}.dsm-dir-item{display:flex;width:100%;align-items:center;padding:8px 12px;border:0;border-bottom:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;text-align:left;cursor:pointer}.dsm-dir-item:last-child{border-bottom:0}.dsm-dir-item:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsm-dir-item::before{content:"📁";margin-right:8px;font-size:12px}
250
255
  .dsm-rule-shadowed .dsm-note{color:var(--dsw-alias-state-error-primary)}.dsm-rule-off .dsm-note{color:var(--dsw-alias-label-tertiary)}.dsm-rule-shadow-hint{margin-top:4px;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:17px}.dsm-rule-invalid{border-color:var(--dsw-alias-state-error-primary)!important}.dsm-rule-hint{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:17px}/* (.dsm-rule-budget 与 .dsm-budget-bar / -fill / -over-text 都已随记忆页预算条的两次搬家
@@ -306,7 +311,7 @@ button.dsm-qty:disabled{cursor:default}
306
311
  .dsm-scene-tile-mode{border-color:var(--dsw-alias-state-success-primary)}
307
312
  .dsm-scene-tile-head{display:flex;min-width:0;align-items:center;gap:8px;padding:12px 12px 0}
308
313
  .dsm-scene-tile-name{overflow:hidden;font-size:13px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}
309
- .dsm-scene-tile-key{flex:none;color:var(--dsw-alias-label-tertiary);font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px}
314
+ .dsm-scene-tile-key{flex:none;color:var(--dsw-alias-label-tertiary);font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);font-size:11px}
310
315
  .dsm-scene-tile-head .dsm-tag{flex:none}
311
316
  .dsm-scene-tile-switch{margin-left:auto;flex:none}
312
317
  /* 描述行是卡片的最后一行(2026-10-16 删掉底部那条 编辑/删除 band,动作进卡头 ⋯),
@@ -425,7 +430,7 @@ button.dsm-qty:disabled{cursor:default}
425
430
  .dsm-compat-mod-row:last-child{border-bottom:0}
426
431
  .dsm-compat-mod-list.dsm-compat-cards .dsm-compat-mod-row{display:grid;min-height:0;grid-template-columns:minmax(0,1fr) max-content;gap:4px 10px;padding:8px 12px}
427
432
  .dsm-compat-mod-self{grid-column:1/-1}
428
- .dsm-compat-mod{min-width:0;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px;line-height:17px;word-break:break-word}
433
+ .dsm-compat-mod{min-width:0;font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);font-size:11px;line-height:17px;word-break:break-word}
429
434
  .dsm-compat-pill{display:inline-flex;min-height:20px;flex:none;align-items:center;padding:0 8px;border:1px solid var(--dsw-alias-border-l3);border-radius:99px;color:var(--dsw-alias-label-secondary);font-size:12px;white-space:nowrap}
430
435
  .dsm-compat-pill-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}
431
436
  .dsm-compat-pill-warn{border-color:var(--dsm-warn);color:var(--dsm-warn)}
@@ -444,7 +449,7 @@ button.dsm-qty:disabled{cursor:default}
444
449
  .dsm-inject-domains{display:flex;flex-wrap:wrap;gap:6px}
445
450
  .dsm-inject-chip{box-sizing:border-box;display:inline-flex;flex:1 1 0;min-width:max-content;min-height:32px;align-items:center;justify-content:center;gap:6px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-m);background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:12px;line-height:18px;white-space:nowrap;cursor:pointer}
446
451
  .dsm-inject-chip:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
447
- .dsm-inject-chip:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}
452
+ .dsm-inject-chip:focus-visible{outline-style:solid;outline-color:var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-width:var(--dsw-focus-ring-width,2px);outline-offset:2px}
448
453
  .dsm-inject-chip-dot{flex:none;width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.4}
449
454
  .dsm-inject-chip-name{flex:none}
450
455
  /* 开:成功色描边 + 主字色 + 实心点。底色不动 —— 六块都涂色会把「注入内容」这一块压得
@@ -481,14 +486,14 @@ button.dsm-qty:disabled{cursor:default}
481
486
  .dsm-tooltable-tool{display:grid;grid-template-columns:15px 186px 48px minmax(0,1fr);align-items:center;gap:0 8px;padding:4px 4px;border-radius:var(--dsm-radius-s);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;cursor:pointer}
482
487
  .dsm-tooltable-tool:hover{background:var(--dsw-alias-interactive-bg-hover)}
483
488
  .dsm-tooltable-tool input[type=checkbox]{width:14px;height:14px;margin:0;cursor:pointer;accent-color:var(--dsw-alias-state-success-primary)}
484
- .dsm-tooltable-name{overflow:hidden;color:var(--dsw-alias-label-primary);font-family:ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}
489
+ .dsm-tooltable-name{overflow:hidden;color:var(--dsw-alias-label-primary);font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);text-overflow:ellipsis;white-space:nowrap}
485
490
  .dsm-tooltable-tok{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;white-space:nowrap}
486
491
  .dsm-tooltable-about{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
487
492
  /* 注入实况(只读):与「注入」设置同屏。行 = 域 + 体积 + 状态胶囊,点域名展开正文。 */
488
493
  .dsm-inject-live-actions{display:flex;flex:none;align-items:center;gap:4px;margin-left:auto}
489
494
  .dsm-inject-live-toggle{padding:0;border:0;background:transparent;color:inherit;font:inherit;cursor:pointer}
490
495
  .dsm-inject-live-toggle:hover{text-decoration:underline}
491
- .dsm-inject-live-toggle:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}
496
+ .dsm-inject-live-toggle:focus-visible{outline-style:solid;outline-color:var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-width:var(--dsw-focus-ring-width,2px);outline-offset:2px}
492
497
  /* 没有正文可展开的行:用一个看不见的同字符占位,把展开箭头那一格宽度留出来,
493
498
  这样所有行的域名左缘才会在同一条竖线上(用户 2026-09-17 指出参差不齐)。
494
499
  占位与文字放在**同一个** flex 子项里(.dsm-inject-live-static),否则 name 格的
@@ -511,7 +516,7 @@ button.dsm-qty:disabled{cursor:default}
511
516
  .dsm-compat-pill-bad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
512
517
  .dsm-compat-name{min-width:0;flex:1;font-size:12px;font-weight:400;word-break:break-word}
513
518
  .dsm-compat-label{margin-right:4px;color:var(--dsw-alias-label-tertiary);font-size:12px;white-space:nowrap}
514
- .dsm-compat-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px}
519
+ .dsm-compat-code{font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);font-size:11px}
515
520
  .dsm-compat-list{margin:0;padding-left:16px}.dsm-compat-list li+li{margin-top:4px}
516
521
  @container(max-width:640px){.dsm-compat-grid{grid-template-columns:1fr}}
517
522
  html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!important;scrollbar-gutter:stable}.dsm-tabs-shell{position:sticky;top:0;z-index:6;display:flex;min-width:0;align-items:center;gap:4px;margin-bottom:0;padding-top:2px;background:var(--dsw-alias-bg-layer-2)}.dsm-tabs{display:grid;min-width:0;flex:1;grid-template-columns:repeat(auto-fit,minmax(64px,1fr));gap:0;overflow:hidden;margin-bottom:0;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-tab{box-sizing:border-box;width:100%;min-width:0;padding-right:4px;padding-left:4px;overflow:hidden;font-size:12px;text-overflow:ellipsis;white-space:nowrap}.dsm-feedback-link{width:30px;height:30px;box-sizing:border-box;padding:0;justify-content:center}.dsm-feedback-link svg{width:16px;height:16px}@container(max-width:620px){.dsm-tab{padding-right:4px;padding-left:4px;font-size:12px}}
@@ -581,7 +586,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
581
586
  .dsm-token-banner-go{pointer-events:auto;-webkit-app-region:no-drag;app-region:no-drag;flex:none;align-self:center;padding:4px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-s);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;cursor:pointer;white-space:nowrap}
582
587
  .dsm-token-banner-go:hover{background:rgba(255,255,255,.08)}
583
588
  .dsm-token-banner-go:active{background:rgba(255,255,255,.14)}
584
- .dsm-token-banner-go:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}
589
+ .dsm-token-banner-go:focus-visible{outline-style:solid;outline-color:var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-width:var(--dsw-focus-ring-width,2px);outline-offset:2px}
585
590
 
586
591
  /* 全局轻提示(斜杠命令的执行反馈)。位置在右下角:对话页的输入框居底居中,顶部那条又是
587
592
  令牌横幅,两处都会被挡。z-index 比横幅低一档 —— 令牌没过时那句解释比"改成功了"更要紧。 */
@@ -601,7 +606,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
601
606
  .dsm-imp-row input[type=checkbox]:disabled{cursor:not-allowed}
602
607
  .dsm-imp-main{display:flex;min-width:0;flex-direction:column;gap:4px}
603
608
  .dsm-imp-name{display:flex;align-items:center;flex-wrap:wrap;gap:4px;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}
604
- .dsm-imp-endpoint{color:var(--dsw-alias-label-secondary);font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px;line-height:16px;word-break:break-all}
609
+ .dsm-imp-endpoint{color:var(--dsw-alias-label-secondary);font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);font-size:11px;line-height:16px;word-break:break-all}
605
610
  .dsm-imp-sub,.dsm-imp-note{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:16px}
606
611
  .dsm-imp-err{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:16px}
607
612
  .dsm-imp-meta{display:flex;flex-direction:column;align-items:flex-end;gap:4px}
@@ -617,12 +622,22 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
617
622
  .dsm-dot{display:inline-block;flex:none;width:8px;height:8px;border-radius:50%;background:var(--dsw-alias-border-l3)}
618
623
  .dsm-dot-ok{background:var(--dsw-alias-state-success-primary)}
619
624
  .dsm-dot-warn{background:var(--dsm-warn)}
625
+ /* idle / bad 两条此前**根本没写**(2026-10-17 查出来):StatusDot 的 tone 有
626
+ ok / idle / bad / warn / busy 五档,CSS 只给了 ok / warn / busy 三条,idle 与 bad
627
+ 全靠 .dsm-dot 的默认灰底顶着 —— idle 恰好就该是灰,所以一直没露馅;而 bad
628
+ (MCP 加载失败,41-mcp.js 的 liveStatus)本该是红的,实际也是灰的,
629
+ 那颗灯因此在「连不上」和「没加载过」之间完全分不出来。两条都显式写出来,不再吃默认值。 */
630
+ .dsm-dot-idle{background:var(--dsw-alias-border-l3)}
631
+ .dsm-dot-bad{background:var(--dsw-alias-state-error-primary)}
620
632
  /* 启动进行中的呼吸点(2026-10-15 用户要求加回:服务启动要数秒,原来只有一个静态灰点,
621
633
  "正在启动"只能靠 title 说出来。2026-10-12 曾以"闪动的点只有体检用"为由删过一次,
622
634
  这次是 MCP 启动相位自己要 —— 全页只有 phase 非 active 时用它,不会到处闪)。
623
- 配色改绿(用户 2026-10-15):business 橙在 dsh 的语义里是"占用中/要处理",而启动中既不是
624
- 警告也不是故障,跑起来之后它就变成同一支绿 —— 呼吸与常亮因此是同一个颜色的两种节律。 */
625
- .dsm-dot-busy{background:var(--dsw-alias-state-success-primary);animation:dsm-dot-pulse 1.1s ease-in-out infinite}
635
+ 配色改黄(用户 2026-10-17):加载中既没连上也不是故障,取官方 warn 档
636
+ (--dsw-alias-state-warn-primary = amber-500 #f59e0b,随主题走)—— 与跑起来之后的
637
+ 常亮绿、失败的红三者一眼可分。2026-10-15 曾改绿,那次要的是"别用橙",
638
+ 但绿与常亮态同色、只能靠节律分辨,用户这次明确要黄。
639
+ tone='busy' 全仓只有 MCP 的状态灯在用(41-mcp.js 的两处 liveStatus),别处不受影响。 */
640
+ .dsm-dot-busy{background:var(--dsw-alias-state-warn-primary);animation:dsm-dot-pulse 1.1s ease-in-out infinite}
626
641
  /* 呼吸的最低亮度从 .3 提到 .65,且改成**长一圈**而不是缩一点(用户 2026-10-15:灯在闪,
627
642
  可颜色看着不绿,分不清是不是启动中)。6px 的点压到三成透明度就只剩一层灰;"变亮 + 长大"
628
643
  与跑起来那颗常亮的绿一眼可分。 */
@@ -637,7 +652,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
637
652
  .dsm-audit-head{grid-template-columns:92px minmax(0,1fr)}
638
653
  .dsm-audit-row{display:grid;grid-template-columns:92px minmax(0,1fr);align-items:baseline;gap:8px;padding:4px 0;border-bottom:1px solid var(--dsw-alias-border-l1);font-size:12px}
639
654
  .dsm-audit-row:last-child{border-bottom:0}
640
- .dsm-audit-time{color:var(--dsw-alias-label-tertiary);font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap}
655
+ .dsm-audit-time{color:var(--dsw-alias-label-tertiary);font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace);font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap}
641
656
  .dsm-audit-main{display:flex;align-items:baseline;flex-wrap:wrap;gap:4px 8px;min-width:0}
642
657
  .dsm-audit-item{color:var(--dsw-alias-label-secondary)}
643
658
  .dsm-audit-target{color:var(--dsw-alias-label-primary);font-weight:400}
@@ -693,7 +708,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
693
708
  .dsm-pick input[type=checkbox]{flex:none;width:15px;height:15px;margin:0;cursor:pointer}
694
709
  .dsm-tooltable-ghead input[type=checkbox]{flex:none;width:15px;height:15px;margin:0;cursor:pointer}
695
710
  .dsm-imp-row input[type=checkbox]{width:14px;height:14px;margin:0;cursor:pointer}
696
- .dsm-select-menu button:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary); outline-offset: 1px;}
711
+ .dsm-select-menu button:focus-visible{outline-style:solid;outline-color:var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-width:var(--dsw-focus-ring-width,2px); outline-offset: 1px;}
697
712
  .dsm-btn-lock:hover:not(:disabled){background: var(--dsw-alias-interactive-bg-hover);}
698
713
  .dsm-btn-quiet:hover:not(:disabled){background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary);}
699
714
  /* quiet + danger 必须**自己把描边写回来**:单类的 .dsm-btn-danger(实心红底那颗)在
@@ -711,7 +726,19 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
711
726
  .dsm-select-trigger > span{min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
712
727
  .dsm-select-trigger::after{content:""; flex: none; box-sizing: border-box; width: 7px; height: 7px; margin-top: -3px; border-right: 1.5px solid var(--dsw-alias-label-tertiary); border-bottom: 1.5px solid var(--dsw-alias-label-tertiary); transform: rotate(45deg);}
713
728
  .dsm-select-trigger:empty::before{color: var(--dsw-alias-label-dimmed);}
714
- .dsm-select-trigger:focus-visible{outline: none; border-color: var(--dsw-alias-state-business-primary);}
729
+ /* 输入框的焦点态照官方口径(2026-10-17):**不画环,只把描边染成主色**(官方 settings-models
730
+ 的 .input:focus 就一句 border-color + outline:none,不加深、不发光晕)。
731
+ **描边色不能写 --dsw-focus-ring-color**:宿主在鼠标交互时把那个变量改写成 transparent
732
+ (html[data-input-modality=pointer] body :focus-visible:not(:read-write)),只放过输入框这类
733
+ :read-write 元素 —— 而下拉触发器是 <button>,不在豁免里,照抄就会「点一下边框整圈消失」。
734
+ 所以这里直接取它缺省的那个业务蓝:深浅两套主题下与官方环同色,且不随交互方式变。
735
+ 非法输入框排除在外:描边被 .dsm-rule-invalid 用 !important 钉成红色,染不上色,
736
+ 留着宿主默认的环反而看得见焦点。 */
737
+ .dsm-control:not(.dsm-rule-invalid):focus-visible,.dsm-select-trigger:focus-visible{outline:none;border-color:var(--dsw-alias-state-business-primary)}
738
+ /* 搜索框比普通输入框多一圈 18% 主色的光环(官方 settings-plugin-inventory 的搜索框写法,
739
+ 截图比过色值:1px 描边 + 2px 淡蓝光晕,18% 混白正好落在官方那张图的 (221,230,250))。
740
+ 只给搜索框,别的输入框不加。 */
741
+ .dsm-search:focus-visible{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb, var(--dsw-alias-state-business-primary) 18%, transparent)}
715
742
  .dsm-select-trigger:disabled{color: var(--dsw-alias-label-tertiary); cursor: default;}
716
743
  .dsm-control[aria-invalid= true ]{border-color: var(--dsw-alias-state-error-primary);}
717
744
  /* 这一条**不能再写 background**:它与第 99 行的 .dsm-option:hover 特异性打平
@@ -737,7 +764,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
737
764
  .dsm-btn.dsm-btn-icon > svg{width: 14px; height: 14px; flex: none;}
738
765
  .dsm-btn.dsm-btn-icon.dsm-btn-lock{border: 0; background: transparent; color: var(--dsw-alias-label-tertiary);}
739
766
  .dsm-btn.dsm-btn-icon.dsm-btn-lock:hover:not(:disabled){color: var(--dsw-alias-state-error-primary); background: var(--dsw-alias-interactive-bg-hover-danger);}
740
- .dsm-btn.dsm-btn-icon:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary); outline-offset: 1px;}
767
+ .dsm-btn.dsm-btn-icon:focus-visible{outline-style:solid;outline-color:var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-width:var(--dsw-focus-ring-width,2px); outline-offset: 1px;}
741
768
  .dsm-preset-row > [role= switch ]{margin-right: 4px;}
742
769
  /* ⚠️ 不写 overscroll-behavior:contain(2026-10-12 与场景编辑器组体同批修):代码块是
743
770
  overflow:auto,内容不到 340px 时它不是「没在滚」而是「滚不动」——contain 会把滚轮链
@@ -854,7 +881,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
854
881
  /* 头部右上角 X 图标钮(§4.3):替换「关闭」文字钮,aria-label 仍取「关闭」文案。 */
855
882
  .dsm-modal-close{flex:none;display:inline-flex;width:30px;height:30px;align-items:center;justify-content:center;padding:0;border:0;border-radius:var(--dsm-radius-m);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
856
883
  .dsm-modal-close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
857
- .dsm-modal-close:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
884
+ .dsm-modal-close:focus-visible{outline-style:solid;outline-color:var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-width:var(--dsw-focus-ring-width,2px);outline-offset:1px}
858
885
  /* footer 规范(§4.3):右对齐 [取消][主操作](.dsm-modal-actions 已是 flex-end)。
859
886
  (.dsm-modal-danger-left「危险操作钮靠左」已随场景编辑页那颗「删除场景」一起删除 ——
860
887
  它此前是这颗键唯一的落点,用户 2026-10-12 裁定编辑页不再承担删除。) */
@@ -868,7 +895,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
868
895
  行本体带 role=checkbox + aria-checked,Space/Enter 切换;框是纯视觉(aria-hidden)。 */
869
896
  .dsm-rowselect{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:var(--dsm-radius-s);color:var(--dsw-alias-label-primary);cursor:pointer}
870
897
  .dsm-rowselect:hover,.dsm-rowselect-on{background:var(--dsw-alias-interactive-bg-hover)}
871
- .dsm-rowselect:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
898
+ .dsm-rowselect:focus-visible{outline-style:solid;outline-color:var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-width:var(--dsw-focus-ring-width,2px);outline-offset:1px}
872
899
  .dsm-rowselect-disabled{cursor:default;opacity:.5}
873
900
  .dsm-rowselect-disabled:hover{background:transparent}
874
901
  .dsm-rowselect-box{display:inline-flex;flex:none;width:14px;height:14px;align-items:center;justify-content:center;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-s);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary-foreground)}
@@ -1068,7 +1095,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1068
1095
  .dsm-listrow-meta-desc{min-width:0;overflow:hidden;text-overflow:ellipsis}
1069
1096
  .dsm-listrow-meta-info{flex:none;color:var(--dsw-alias-label-tertiary)}
1070
1097
  /* 技术值(路径 / id)排印(§2.3):与普通文案在字形上区分,不靠颜色。 */
1071
- .dsm-listrow-meta .dsm-mono{font-family:ui-monospace,SFMono-Regular,Consolas,monospace}
1098
+ .dsm-listrow-meta .dsm-mono{font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Consolas,monospace)}
1072
1099
  .dsm-listrow-side{display:flex;flex:none;align-items:center;gap:8px}
1073
1100
  .dsm-listrow-actions{display:flex;flex:none;align-items:center;justify-content:flex-end;gap:4px}
1074
1101
  /* 行2 之下的追加内容(体检结果 / ⚠ 提示 / 被覆盖说明)原本按表格网格留白,
@@ -1631,7 +1658,12 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
1631
1658
  // without a CORS preflight that this route never answers.
1632
1659
  const headers = { 'content-type': 'application/json', 'x-dsh-plugin': 'dsh-plugin-tool-management' }
1633
1660
  if (TOKEN) headers['x-dsh-token'] = TOKEN
1634
- return fetch('/dsh-plugin-tool-management/api', {
1661
+ // **按 document.baseURI 解析**,不写死 '/':宿主可以被挂在反代 / 静态服务器的
1662
+ // 子路径下(base 存在于 index.html 的 <base> 里),写死 '/' 时浏览器会向宿主
1663
+ // 站点的根本身发请求,面板整页 404 —— 而那时宿主与插件都"装对了"。
1664
+ // baseURI 总是绝对 URL(文档没有 base 时等于 location.href 去掉 query/hash),
1665
+ // 所以这里不需要再判断它是不是绝对路径。
1666
+ return fetch(new URL('dsh-plugin-tool-management/api', document.baseURI).toString(), {
1635
1667
  method: 'POST',
1636
1668
  headers,
1637
1669
  body: JSON.stringify({ op, args: args || {} }),
@@ -1887,6 +1919,20 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
1887
1919
  list: 'compat.op.list', archive: 'compat.op.archive', unarchive: 'compat.op.unarchive',
1888
1920
  batch: 'compat.op.batch', delete: 'compat.op.delete',
1889
1921
  }
1922
+ /**
1923
+ * 0.2.1 起**新增**的宿主契约能力 id(capability-specs.ts 的那四条)。
1924
+ *
1925
+ * 与上面那排 action 的区别:action 是"这个操作走哪条路",这些是"某个契约还在不在"。
1926
+ * 它们缺了都不抛错、只是功能少一半,所以单独一块而不是混进 action 表 —— 混进去会让
1927
+ * "删除不可用"和"工具限制失效"看起来一样严重,实际后者不影响你删任何东西。
1928
+ *
1929
+ * 键集必须与服务端 `CONTRACT_CAPABILITY_IDS` 对齐:那里按 feature-overview 的行 key 映射
1930
+ * 到能力 id,这里按能力 id 过滤 findings。两边任一处改名,另一处会在界面上静默失效
1931
+ * (契约行不再出现),所以加了就两处一起加。
1932
+ */
1933
+ var CONTRACT_CAPABILITY_IDS = [
1934
+ 'agentPreset.register', 'tools.restrict-method', 'agent.created-event', 'connection.admit',
1935
+ ]
1890
1936
  /**
1891
1937
  * 「最近改动」流水里 op 的人类名(0.15.0 B2)。
1892
1938
  *
@@ -2617,6 +2663,10 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
2617
2663
  case 'sessions.cold-announce': return t('compat.cap.sessionsColdAnnounce')
2618
2664
  case 'projection.write': return t('compat.cap.projectionWrite')
2619
2665
  case 'projection.table-delete': return t('compat.cap.projectionTableDelete')
2666
+ case 'agentPreset.register': return t('compat.cap.agentPresetRegister')
2667
+ case 'tools.restrict-method': return t('compat.cap.toolsRestrictMethod')
2668
+ case 'agent.created-event': return t('compat.cap.agentCreatedEvent')
2669
+ case 'connection.admit': return t('compat.cap.connectionAdmit')
2620
2670
  case 'archive': return t('compat.cap.opArchive')
2621
2671
  case 'unarchive': return t('compat.cap.opUnarchive')
2622
2672
  case 'batch': return t('compat.cap.opBatch')
@@ -2652,6 +2702,10 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
2652
2702
  case 'prompts': return t('compat.ov.prompts')
2653
2703
  case 'subagents': return t('compat.ov.subagents')
2654
2704
  case 'scenes': return t('compat.ov.scenes')
2705
+ case 'subagent-persona': return t('compat.ov.subagentPersona')
2706
+ case 'subagent-tool-restrict': return t('compat.ov.subagentToolRestrict')
2707
+ case 'subagent-created-event': return t('compat.ov.subagentCreatedEvent')
2708
+ case 'web-admission': return t('compat.ov.webAdmission')
2655
2709
  default: return null
2656
2710
  }
2657
2711
  }
@@ -2736,8 +2790,17 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
2736
2790
  if (row.asarHost) return t('compat.ov.hostIdentity.asar', { version: row.version, name: row.asarName || 'app.asar' })
2737
2791
  return t('compat.ov.hostIdentity.ok', { version: row.version })
2738
2792
  }
2793
+ if (row.key === 'subagent-persona' || row.key === 'subagent-tool-restrict' || row.key === 'subagent-created-event' || row.key === 'web-admission') {
2794
+ // 宿主契约行:可用时说的是"运行时看到的那份实现"(它的 detail 由探针写成"成员齐备"),
2795
+ // 降级时说的是"这条契约没了,对应功能少一半"——两个方向都得按语言说。
2796
+ return row.state === 'unknown' ? t('compat.ov.route.unknown')
2797
+ : row.state === 'ok' ? t('compat.ov.contract.ok')
2798
+ : t('compat.ov.contract.degraded', { detail: row.detail })
2799
+ }
2739
2800
  if (row.state === 'ok' && (row.key === 'memory' || row.key === 'prompts' || row.key === 'subagents' || row.key === 'scenes')) {
2740
- return t('compat.ov.plain.ok')
2801
+ // 有宿主模块可核的域(提示词 / 子智能体)按实况说;确实没有可核对象的才说"无降级上报"。
2802
+ if (row.key === 'memory' || row.key === 'scenes') return t('compat.ov.plain.ok')
2803
+ return t('compat.ov.domain.contract', { detail: row.detail })
2741
2804
  }
2742
2805
  return row.detail
2743
2806
  }
@@ -3412,7 +3475,8 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
3412
3475
  card(t('compat.caps'), String(usableCount) + '/' + String(findings.length), capsNote, degraded.length > 0)))
3413
3476
 
3414
3477
  // 版本栅栏(B3):实际宿主与本插件验证过的版本不同时说出来 —— 否则"照旧版本的印象
3415
- // 判断"会让人把探测结果当成 bug。只提示、不拦(peer 保持无上界是既定决策)。
3478
+ // 判断"会让人把探测结果当成 bug。只提示、不拦(peer 的上界只管**安装期**收不收,
3479
+ // 这里管的是"已经跑起来了,版本对不上要说一声":同代的 rc 差异提示一次即可)。
3416
3480
  const hostVersion = (data.host && data.host.version) || ''
3417
3481
  if (hostVersion && data.verifiedVersion && hostVersion !== data.verifiedVersion) {
3418
3482
  push(React.createElement('p', { className: 'dsm-help' },
@@ -3454,6 +3518,24 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
3454
3518
  blockers.map(function (item, i) { return React.createElement('li', { key: i }, item) })))))
3455
3519
  }
3456
3520
 
3521
+ // 宿主契约(0.2.1 新增的三处适配面 + Web 连接准入):这几条在能力表里是**必填项**,
3522
+ // 不是"宿主没有、插件自己顶上"的可选槽。缺了不抛错,只是功能少一半 —— 所以单独一块
3523
+ // 列出来,让"升级后某处悄悄不工作"有一个显眼的位置。
3524
+ const contractFindings = findings.filter(function (item) {
3525
+ return CONTRACT_CAPABILITY_IDS.indexOf(item.id) !== -1 && item.state !== 'ok'
3526
+ })
3527
+ push(section(t('compat.contracts'), t('compat.contracts.hint'),
3528
+ contractFindings.length === 0
3529
+ ? React.createElement('p', { className: 'dsm-help' }, t('compat.contracts.allOk'))
3530
+ : React.createElement('div', { className: 'dsm-compat-mod-list dsm-compat-cards' },
3531
+ contractFindings.map(function (item) {
3532
+ return React.createElement('div', { className: 'dsm-compat-mod-row', key: item.id },
3533
+ React.createElement('span', { className: 'dsm-compat-name' }, capLabel(item.id, item.label)),
3534
+ React.createElement('span', { className: 'dsm-compat-pill dsm-compat-pill-warn' }, t('compat.contracts.missing')),
3535
+ React.createElement('span', { className: 'dsm-compat-mod dsm-compat-mod-self' },
3536
+ React.createElement('span', { className: 'dsm-compat-label' }, t('compat.reason')), item.detail))
3537
+ }))))
3538
+
3457
3539
  // 动作可用性:一行一个动作 + 路径标记(不可用的标红)。
3458
3540
  push(section(t('compat.ops'), t('compat.ops.hint'),
3459
3541
  React.createElement('div', { className: 'dsm-compat-mod-list' },
@@ -3493,6 +3575,10 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
3493
3575
  // 其它非 ok 项:`optional` 表示"宿主没有这个入口,插件自己顶上"——功能完好,
3494
3576
  // 既不算降级,也不能说按钮被禁用(那句只对真降级成立;早先把两者混在一起
3495
3577
  // 会让用户以为删除功能坏了,实际它照常可用)。
3578
+ //
3579
+ // 与上面「宿主契约」那块的区别:这里的条目缺了,插件**自有等价实现**(适配路完整可用);
3580
+ // 那块的条目缺了,没有替代实现,功能就是少一半。两块都是"非 ok",但后果不同,
3581
+ // 所以分开显示、各自的胶囊文案也不同。
3496
3582
  const optionalAbsent = findings.filter(function (item) { return item.state !== 'ok' && item.optional === true })
3497
3583
  if (optionalAbsent.length) {
3498
3584
  push(section(t('compat.others'), t('compat.others.hint'),
@@ -3539,6 +3625,31 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
3539
3625
  React.createElement('span', { className: 'dsm-compat-pill ' + cls }, text))
3540
3626
  }))))
3541
3627
 
3628
+ // 裸模块解析(与上面那一块的分工:上面比的是**同一份物理模块**,这里看的是**包还在不在**)。
3629
+ //
3630
+ // 为什么分开:上面那 6 个包是插件的 peerDependency,「两份不同拷贝」那种故障只可能发生在
3631
+ // 它们身上;而下面这 4 个包是插件按**裸模块名**在运行时 require 的官方包,宿主不要求插件
3632
+ // 依赖它们。官方撤掉其中一个,对应功能静默降级,而身份比对那一栏根本看不见它们 —— 所以
3633
+ // 必须在这里列一行。canary 缺失不是「包坏了」,是「该人工复核这条契约」,显示成提示而非红色。
3634
+ const contractPackages = (data && data.contracts && data.contracts.packages) || []
3635
+ if (contractPackages.length) {
3636
+ push(section(t('compat.bareModules'), t('compat.bareModules.hint'),
3637
+ React.createElement('div', { className: 'dsm-compat-mod-list dsm-compat-cards' },
3638
+ contractPackages.map(function (item) {
3639
+ const missing = item.resolved === null
3640
+ const drifted = !missing && (item.missingMarkers || []).length > 0
3641
+ return React.createElement('div', { className: 'dsm-compat-mod-row', key: item.package },
3642
+ React.createElement('span', { className: 'dsm-compat-name dsm-compat-mod' }, item.package),
3643
+ React.createElement('span', { className: 'dsm-compat-pill ' + (missing ? 'dsm-compat-pill-bad' : drifted ? 'dsm-compat-pill-warn' : 'dsm-compat-pill-ok') },
3644
+ missing ? t('compat.bareModules.absent') : drifted ? t('compat.bareModules.drift') : t('compat.bareModules.present')),
3645
+ React.createElement('span', { className: 'dsm-compat-mod dsm-compat-mod-self' },
3646
+ React.createElement('span', { className: 'dsm-compat-label' }, t('compat.reason')),
3647
+ missing ? t('compat.bareModules.absentWhy', { feature: item.feature })
3648
+ : drifted ? t('compat.bareModules.driftWhy', { feature: item.feature, markers: (item.missingMarkers || []).join('、') })
3649
+ : t('compat.bareModules.presentWhy', { feature: item.feature })))
3650
+ }))))
3651
+ }
3652
+
3542
3653
  // 「无法比对」必须给原因:否则一排未知标记看起来只是噪音,用户会当成正常现象。
3543
3654
  // 归档宿主的说明排最前 —— 它解释的正是这一排「无法比较」的成因,而且必须讲明白
3544
3655
  // **不要去跑 host-deps --fix**:那条命令会把插件的依赖指向 npx 缓存里另一个版本。
@@ -4196,7 +4307,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
4196
4307
  "confirm.custom.remove.desc": "移除后该目录的技能不再列出(文件不受影响):{name}",
4197
4308
  "result.custom.added": "已添加自定义目录:{path}", "result.custom.removed": "已移除自定义目录:{name}",
4198
4309
  "status.enabled": "已启用", "status.disabled": "未启用", "status.invalid": "诊断异常", "status.shadowed": "被覆盖", "status.shadowed.hint": "同名技能「{name}」正在生效;点「启用这个」可改用当前来源的版本", "status.shadowed.inert": "同名技能「{name}」已停用,被它挡住的本来源版本同样不会生效 —— 两份都不生效;启用其中一份即可", "status.preferred": "同名首选", "status.notDeletable": "不可删除", "status.notDeletable.hint": "删除只对插件可管理的来源开放(DSH 技能 / 导入技能 / 项目级 .dsh/skills);外部 Agent 与自定义目录只读,里面的技能不能删", "status.manageable": "可管理", "status.project": "项目级", "status.rank": "优先级 {rank}", "status.source.on": "已启用", "status.source.off": "未启用", "status.bundle": "目录技能", "status.single": "单文件",
4199
- "summary.total.one": "技能", "summary.total.other": "技能", "summary.issues.one": "诊断", "summary.issues.other": "诊断", "summary.sources.one": "来源", "summary.sources.other": "来源", "summary.group.one": "{count} 个技能", "summary.group.other": "{count} 个技能", "table.skill": "技能名称与描述",
4310
+ "summary.total.one": "技能", "summary.total.other": "技能", "summary.issues.one": "诊断", "summary.issues.other": "诊断", "summary.sources.one": "来源", "summary.sources.other": "来源", "table.skill": "技能名称与描述",
4200
4311
  "filter.source": "来源", "filter.all": "全部来源", "filter.option": "{name}({count})", "search": "搜索", "search.placeholder": "搜索技能名称或描述", "search.clear": "清除搜索",
4201
4312
  // 列表四态(REDESIGN §12.2,切片四)共用的一组词:加载 / 加载失败 / 重试 / 无结果 / 清除筛选。
4202
4313
  "list.loading": "加载中…", "list.error": "加载失败", "list.retry": "重试", "list.noMatch": "没有匹配的条目", "list.clearFilter": "清除筛选",
@@ -4222,6 +4333,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
4222
4333
  // 是服务端动态中文尾巴,EN 界面显示英文框架 + 中文细节(比整句中文可读)。
4223
4334
  "error.rules.sceneNotFound": "场景不存在:{group}(请先在「场景」页创建该场景)",
4224
4335
  "error.rules.reservedScene": "「{name}」是保留场景,不可{action}{reason}",
4336
+ "error.rules.sceneNotActive": "场景未启动:先启动再锁定",
4225
4337
  "error.rules.singleSceneOnly": "除「全局」外同时只能启用一个场景{detail}",
4226
4338
  "error.agentsMd.sceneRebindFailed": "无法把场景「{scene}」的提示词绑定改成「{target}」:{reason}",
4227
4339
  "error.agentsMd.referenced": "「{id}」不能删除:{refs}。先解除引用再删 —— 场景绑定的去场景页换绑提示词、或退出场景;正被应用的那份先点左边那颗开关「取消应用」。", "error.agentsMd.refProbeFailed": "无法确认「{id}」是否被引用,已拒绝删除:引用探测失败(场景绑定 / AGENTS.md 当前内容 / 进场景前的基线,这三类里至少有一类没读到)。请稍后重试。", "error.agentsMd.noUnapplyBackup": "没有可恢复的「应用之前的状态」:AGENTS.md 的当前内容不是由「应用」写进去的(例如首次打开时自动生成的那份预设),所以没有起点可回。要改掉文件里的内容:应用别的预设,或直接编辑 AGENTS.md 本身。", "error.agentsMd.unapplyWhileScene": "场景「{scene}」正在驱动全局基线,不能在这里取消应用:先退出场景,或到场景页改掉它的提示词绑定。",
@@ -4437,7 +4549,9 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
4437
4549
  "compat.op.batch": "批量归档", "compat.op.delete": "永久删除",
4438
4550
  "compat.op.native": "宿主原生入口", "compat.op.adapter": "插件适配层", "compat.op.unavailable": "不可用(已禁用)",
4439
4551
  "compat.degraded": "已降级能力", "compat.degraded.hint": "缺什么 + 后果", "compat.reason": "原因:",
4440
- "compat.others": "其它能力项", "compat.others.hint": "不算降级:宿主没有这些入口时,由插件自己顶上",
4552
+ "compat.others": "其它能力项", "compat.others.hint": "不算降级:宿主没有这些入口时,由插件自己顶上",
4553
+ "compat.contracts": "宿主契约", "compat.contracts.hint": "本版适配依赖的宿主成员:缺了不抛错,但对应功能会少一半", "compat.contracts.missing": "不在位", "compat.contracts.allOk": "本版依赖的宿主契约全部在位。",
4554
+ "compat.bareModules": "裸模块解析", "compat.bareModules.hint": "插件在运行时按裸模块名 require 的官方包:它们不是 peer,所以不参与上面的模块比对", "compat.bareModules.present": "在位", "compat.bareModules.absent": "宿主未提供", "compat.bareModules.drift": "契约疑似漂移", "compat.bareModules.presentWhy": "影响:{feature}", "compat.bareModules.absentWhy": "宿主已不提供这个包:{feature}会降级", "compat.bareModules.driftWhy": "{feature}的契约标识有缺失({markers})—— 请人工核对该包的实现是否改过",
4441
4555
  "compat.fallback.substituted": "插件适配层(宿主无此入口)",
4442
4556
  "compat.fallback.native": "改走原生入口", "compat.fallback.refuse": "该操作会被拒绝", "compat.fallback.blocked": "相关按钮已禁用", "compat.fallback.inform": "不影响写入",
4443
4557
  "compat.modules": "模块实体(插件 vs 宿主)", "compat.modules.hint": "同一份模块才谈得上适配", "compat.module.same": "同一份模块", "compat.module.separate": "两份拷贝(需修复)", "compat.module.sameVersion": "同版本(分处归档内外)", "compat.module.unknown": "无法比较", "compat.module.realpath": "无法比较(路径取不到真实形态)", "compat.modules.realpath": "有 {count} 个包**比不了**:它们的路径取不到真实形态({names})—— 这不是「两份拷贝」,而是这项校验没做成。", "compat.modules.realpathSides": "宿主侧:{host};插件侧:{plugin}。修好路径(权限 / 路径长度 / 重新安装)后重新打开本页即重算。", "compat.modules.realpathNone": "无", "compat.modules.unverified": "有 {count} 个包没能比对(宿主的解析锚点里找不到:{names})—— 这不是「两份拷贝」,而是这项校验没做成。可运行 node scripts/doctor.mjs 查看。",
@@ -4469,7 +4583,11 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
4469
4583
  "compat.ov.nativeDelete": "宿主原生删除入口", "compat.ov.nativeDelete.ok": "宿主未提供原生删除入口(本插件自有完整序列)",
4470
4584
  "compat.ov.hostIdentity": "宿主身份与模块", "compat.ov.hostIdentity.ok": "模块与宿主同源(宿主 DSH {version})", "compat.ov.hostIdentity.asar": "官方包与宿主同版本(DSH {version}),但宿主跑在 {name} 里、插件在归档外", "compat.ov.hostIdentity.degraded": "存在阻塞项:{list}",
4471
4585
  "compat.ov.memory": "记忆", "compat.ov.prompts": "提示词", "compat.ov.subagents": "子智能体", "compat.ov.scenes": "场景档案",
4586
+ "compat.ov.subagentPersona": "子智能体人设委派", "compat.ov.subagentToolRestrict": "子代理工具限制", "compat.ov.subagentCreatedEvent": "子代理创建时序", "compat.ov.webAdmission": "Web 连接准入",
4472
4587
  "compat.ov.plain.ok": "无降级上报(装配失败会出现在这里)",
4588
+ "compat.ov.contract.ok": "宿主契约在位(这一项缺了,对应功能会静默少一半)",
4589
+ "compat.ov.contract.degraded": "宿主契约不在位:{detail}",
4590
+ "compat.ov.domain.contract": "依赖的宿主模块在位:{detail}",
4473
4591
  // ── 斜杠命令「工具」段(对话输入框里直接管六个域;实现见 48-slash.js)──────
4474
4592
  // 段标题与六个域名出现在宿主 `/` 菜单里;域名要短 —— 菜单行右侧还要留位置给摘要与 › 箭头。
4475
4593
  "slash.section.tools": "工具",
@@ -4549,6 +4667,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
4549
4667
  "compat.cap.workspaceIndexHeader": "索引会话头部", "compat.cap.workspaceArchiveNative": "宿主原生归档入口", "compat.cap.workspaceUnarchiveNative": "宿主原生恢复入口",
4550
4668
  "compat.cap.workspaceBatchNative": "宿主原生批量入口", "compat.cap.workspaceDeleteNative": "宿主原生删除入口", "compat.cap.sessionsDetachLive": "实时会话落盘与分离",
4551
4669
  "compat.cap.sessionsColdAnnounce": "冷会话移除广播", "compat.cap.projectionWrite": "投影缓存写入路径", "compat.cap.projectionTableDelete": "投影缓存行删除",
4670
+ "compat.cap.agentPresetRegister": "临时子预设注册", "compat.cap.toolsRestrictMethod": "工具限制施加(restrict)", "compat.cap.agentCreatedEvent": "子代理创建事件(agent/created)", "compat.cap.connectionAdmit": "连接准入(admit)",
4552
4671
  "compat.cap.opArchive": "归档", "compat.cap.opUnarchive": "恢复", "compat.cap.opBatch": "批量操作", "compat.cap.opDelete": "删除", "compat.cap.opList": "历史列表",
4553
4672
  "compat.reach": "预设注入边界", "compat.reach.hint": "每个预设下这些内容到不到得了模型",
4554
4673
  "compat.reach.suppressing": "压制型",
@@ -4748,7 +4867,6 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
4748
4867
  "scenes.skills.shadowed.hint": "被同名技能覆盖,在这个场景里不可能生效,因此不能勾选。要改用这一份,请到「技能」页点它的「启用这个」。",
4749
4868
  "scenes.skills.invalid.hint": "技能结构不完整(缺 frontmatter / 名字非法 / 描述为空),无法启停,因此不能勾选。",
4750
4869
  "scenes.filter.skills": "筛选技能/目录名称", "scenes.filter.subagents": "筛选人设(名称或描述)", "scenes.filter.servers": "筛选服务器",
4751
-
4752
4870
  "scenes.mem.title": "记忆", "scenes.mem.search": "在本场景内筛选记忆(名称或描述)",
4753
4871
  "scenes.mem.noMatch": "没有匹配项", "scenes.mem.emptyScene": "该场景还没有记忆",
4754
4872
  "scenes.mem.createNeedScene": "先在左边填好场景名", "scenes.mem.pending": "{count} 条待创建", "scenes.mem.pendingTag": "待创建", "scenes.mem.attach.tip": "带 {count} 个附件 · 共 {size}", "scenes.mem.removePending": "移除",
@@ -4888,7 +5006,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
4888
5006
  "scenes.preview.bind.tool": "模型工具表:切到「{name}」(关 {count} 条)",
4889
5007
  "scenes.preview.bind.toolMissing": "模型工具表:「{name}」已不存在,这次不切换",
4890
5008
  "scenes.preview.bind.toolKeep": "模型工具表:保持现状(本场景未绑定)", "scenes.preview.row.notes": "用场景备注覆盖 {count} 台服务器的备注", "scenes.field.toolTable": "模型工具表方案", "scenes.field.quickPrompts": "快捷提示词", "scenes.field.quickPrompts.current": "当前设置", "scenes.field.quickPrompts.customCount": "自定义({picked}/{total})", "scenes.field.quickPrompts.none": "还没有快捷提示词,先到「提示词」页新增",
4891
- "scenes.field.quickPrompts.noneOption": "无", "scenes.field.quickPrompts.list": "本场景启用", "scenes.field.quickPrompts.pick": "选择", "scenes.quickPick.title": "选择本场景启用的快捷提示词", "scenes.quickPick.hint": "勾选的进入本场景后启用,没勾的停用;退出场景时按进入前那批开关还原。", "scenes.quickPick.confirm": "确定", "scenes.form.section.basic": "基本信息", "scenes.form.section.binding": "场景绑定", "scenes.field.toolTable.none": "当前设置", "scenes.field.toolTable.factory": "出厂默认(关 {count} 条)", "scenes.field.toolTable.count": "关 {count} 条", "scenes.field.toolTable.missing": "方案已不存在", "scenes.preview.more": " 等", "scenes.preview.truncated": "(每一类最多列前 60 项)", "scenes.preview.stale": "档案里有 {count} 项在当前环境里已经不存在,这次不会生效:{items}", "scenes.doctor.title": "有 {count} 处引用指向已经不存在的名字", "scenes.doctor.noneButSkipped": "体检没发现悬空引用", "scenes.doctor.hint": "改名或删除留下的:这一条看着还绑着,运行时按那个名字已经找不到东西了。改回正确的名字,或到档案里取消勾选。", "scenes.doctor.row": "{where}:「{name}」已不存在", "scenes.doctor.more": "另有 {count} 处未列出", "scenes.doctor.skipped": "({domains} 这次没读到,这一类没有算进体检)", "agm.restore.tag": "退出场景后恢复它", "agm.restore.tag.hint": "它是进场景前的全局提示词,退出场景时会按快照里的正文恢复;快照存的是正文,删这份预设不影响恢复", "agm.file.mismatch": "文件里不是它", "agm.file.mismatch.hint": "AGENTS.md 当前内容与这份预设不一致(可能被手动改过);点左边那颗开关把它写回", "hist.restore.note.registered": "已恢复 {count} 个会话;工作区「{title}」已重新登记,会话已挂回该组。", "hist.restore.note.attached": "已恢复 {count} 个会话,并挂回工作区「{title}」。", "hist.restore.note.skip": "{count} 个会话的工作区归属未能恢复:{reason}", "hist.purge.title": "永久删除?", "hist.purge.one": "永久删除会话「{title}」及其全部记录,不可恢复", "hist.purge.batch": "永久删除所选 {count} 个会话及其全部记录,不可恢复", "hist.btn.restore": "恢复", "hist.btn.purge": "永久删除", "hist.btn.confirmPurge": "确认删除", "hist.selectGroup": "全选组 {title}", "hist.select": "选择此会话", "hist.group.count": "{count} 个", "hist.selectAll": "全选", "hist.deselectAll": "取消全选", "hist.btn.import": "导入", "hist.btn.export": "导出", "hist.btn.restoreSelected": "恢复所选 ({count})", "hist.btn.deleteSelected": "删除所选 ({count})", "hist.stat.archived": "归档", "hist.stat.projects": "工作区", "hist.stat.retention": "保留期", "hist.search.placeholder": "搜索标题 / 会话 ID / 项目路径", "hist.loading": "加载中…", "hist.empty.search": "无匹配的归档会话", "hist.empty.none": "暂无归档会话", "hist.import.cwd": "工作区(可留空)", "hist.import.cwd.placeholder": "从已登记工作区选,或直接输入绝对路径;留空 = 归入未分组", "hist.import.workspace": "选工作区", "hist.import.workspace.none": "本机还没有已登记的工作区:可以直接输入绝对路径。", "hist.import.done": "已创建会话 {id}({count} 条消息),可在 DSH 会话列表中继续对话。", "hist.import.lossy": "⚠ 整份文本没有识别出任何说话人标记,已折成一条用户消息:原对话的分轮结构可能已丢失。若源文件是 JSONL 或 Markdown 转录稿,请按原扩展名重新导入。", "hist.export.hint": "导出选中会话为转录文件,可再次导入", "hist.export.dir": "导出目录(绝对路径,自动创建)", "hist.export.browse": "浏览并选择文件夹", "hist.btn.browse": "选择", "hist.export.pickDir": "选择导出目录", "hist.export.format": "格式", "hist.export.detail": "导出内容", "hist.export.detail.simple": "仅正文", "hist.export.detail.tools": "正文 + 工具", "hist.export.detail.full": "正文 + 工具 + 思考", "hist.export.scope": "会话范围", "hist.export.workspace": "工作区", "hist.export.sessions": "会话({count} 个)", "hist.export.loading": "加载会话列表…", "hist.cwd.missing": "⚠ 工作区目录已不存在:", "hist.tag.archived": "已归档", "hist.tag.live": "未归档", "hist.export.empty": "没有符合条件的会话", "hist.result.exported": "已导出 {count} 个会话到 {dir}", "hist.result.skipped": ";跳过 {count} 个({ids})", "hist.export.busy": "处理中…", "hist.btn.exportSelected": "导出 {count} 个会话", "hist.error.title": "操作失败", "hist.error.unknown": "未知错误",
5009
+ "scenes.field.quickPrompts.noneOption": "无", "scenes.field.quickPrompts.list": "本场景启用", "scenes.field.quickPrompts.pick": "选择", "scenes.quickPick.title": "选择本场景启用的快捷提示词", "scenes.quickPick.hint": "勾选的进入本场景后启用,没勾的停用;退出场景时按进入前那批开关还原。", "scenes.quickPick.confirm": "确定", "scenes.form.section.basic": "基本信息", "scenes.form.section.binding": "场景绑定", "scenes.field.toolTable.none": "当前设置", "scenes.field.toolTable.factory": "出厂默认(关 {count} 条)", "scenes.field.toolTable.count": "关 {count} 条", "scenes.field.toolTable.missing": "方案已不存在", "scenes.preview.more": " 等", "scenes.preview.truncated": "(每一类最多列前 60 项)", "scenes.preview.stale": "档案里有 {count} 项在当前环境里已经不存在,这次不会生效:{items}", "scenes.doctor.title": "有 {count} 处引用指向已经不存在的名字", "scenes.doctor.noneButSkipped": "体检没发现悬空引用", "scenes.doctor.hint": "改名或删除留下的:这一条看着还绑着,运行时按那个名字已经找不到东西了。改回正确的名字,或到档案里取消勾选。", "scenes.doctor.row": "{where}:「{name}」已不存在", "scenes.doctor.more": "另有 {count} 处未列出", "scenes.doctor.skipped": "({domains} 这次没读到,这一类没有算进体检)", "agm.restore.tag": "退出场景后恢复它", "agm.restore.tag.hint": "它是进场景前的全局提示词,退出场景时会按快照里的正文恢复;快照存的是正文,删这份预设不影响恢复", "agm.file.mismatch": "文件里不是它", "agm.file.mismatch.hint": "AGENTS.md 当前内容与这份预设不一致(可能被手动改过);点左边那颗开关把它写回", "agm.agentsHome.present": "另有一份全局指令 {path} 也在注入(与别的 agent 工具共用);要改这里的内容去改那个文件。", "hist.restore.note.registered": "已恢复 {count} 个会话;工作区「{title}」已重新登记,会话已挂回该组。", "hist.restore.note.attached": "已恢复 {count} 个会话,并挂回工作区「{title}」。", "hist.restore.note.skip": "{count} 个会话的工作区归属未能恢复:{reason}", "hist.purge.title": "永久删除?", "hist.purge.one": "永久删除会话「{title}」及其全部记录,不可恢复", "hist.purge.batch": "永久删除所选 {count} 个会话及其全部记录,不可恢复", "hist.btn.restore": "恢复", "hist.btn.purge": "永久删除", "hist.btn.confirmPurge": "确认删除", "hist.selectGroup": "全选组 {title}", "hist.select": "选择此会话", "hist.group.count": "{count} 个", "hist.selectAll": "全选", "hist.deselectAll": "取消全选", "hist.btn.import": "导入", "hist.btn.export": "导出", "hist.btn.restoreSelected": "恢复所选 ({count})", "hist.btn.deleteSelected": "删除所选 ({count})", "hist.stat.archived": "归档", "hist.stat.projects": "工作区", "hist.stat.retention": "保留期", "hist.search.placeholder": "搜索标题 / 会话 ID / 项目路径", "hist.loading": "加载中…", "hist.empty.search": "无匹配的归档会话", "hist.empty.none": "暂无归档会话", "hist.import.cwd": "工作区(可留空)", "hist.import.cwd.placeholder": "从已登记工作区选,或直接输入绝对路径;留空 = 归入未分组", "hist.import.workspace": "选工作区", "hist.import.workspace.none": "本机还没有已登记的工作区:可以直接输入绝对路径。", "hist.import.done": "已创建会话 {id}({count} 条消息),可在 DSH 会话列表中继续对话。", "hist.import.lossy": "⚠ 整份文本没有识别出任何说话人标记,已折成一条用户消息:原对话的分轮结构可能已丢失。若源文件是 JSONL 或 Markdown 转录稿,请按原扩展名重新导入。", "hist.export.hint": "导出选中会话为转录文件,可再次导入", "hist.export.dir": "导出目录(绝对路径,自动创建)", "hist.export.browse": "浏览并选择文件夹", "hist.btn.browse": "选择", "hist.export.pickDir": "选择导出目录", "hist.export.format": "格式", "hist.export.detail": "导出内容", "hist.export.detail.simple": "仅正文", "hist.export.detail.tools": "正文 + 工具", "hist.export.detail.full": "正文 + 工具 + 思考", "hist.export.scope": "会话范围", "hist.export.workspace": "工作区", "hist.export.sessions": "会话({count} 个)", "hist.export.loading": "加载会话列表…", "hist.cwd.missing": "⚠ 工作区目录已不存在:", "hist.tag.archived": "已归档", "hist.tag.live": "未归档", "hist.export.empty": "没有符合条件的会话", "hist.result.exported": "已导出 {count} 个会话到 {dir}", "hist.result.skipped": ";跳过 {count} 个({ids})", "hist.export.busy": "处理中…", "hist.btn.exportSelected": "导出 {count} 个会话", "hist.error.title": "操作失败", "hist.error.unknown": "未知错误",
4892
5010
  },
4893
5011
  en: {
4894
5012
  "title": "Skills", "desc": "Manage skills: enable, import, create, trash.", "link.project": "GitHub", "link.feedback": "Issues",
@@ -4920,7 +5038,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
4920
5038
  "result.custom.removed": "Custom folder removed: {name}",
4921
5039
  "result.opened": "Opened with the system default app: {path}",
4922
5040
  "status.enabled": "Enabled", "status.disabled": "Disabled", "status.invalid": "Needs attention", "status.shadowed": "Shadowed", "status.shadowed.hint": "The copy “{name}” is the active one; click “Use this one” to switch to this source instead", "status.shadowed.inert": "The copy “{name}” is disabled, so this source's version is blocked without taking effect either — nothing is active; enable one of the two to fix it", "status.preferred": "Manually chosen", "status.notDeletable": "Cannot delete", "status.notDeletable.hint": "Deleting is available for plugin-managed sources (DSH skills / Imported skills / project .dsh/skills); external agent and custom directories are read-only", "status.manageable": "Manageable", "status.project": "Project scoped", "status.rank": "Rank {rank}", "status.source.on": "Enabled", "status.source.off": "Off", "status.bundle": "Bundle", "status.single": "Single file",
4923
- "summary.total.one": "Skills", "summary.total.other": "Skills", "summary.issues.one": "Issues", "summary.issues.other": "Issues", "summary.sources.one": "Sources", "summary.sources.other": "Sources", "summary.group.one": "{count} skill", "summary.group.other": "{count} skills", "table.skill": "Skill name and description",
5041
+ "summary.total.one": "Skills", "summary.total.other": "Skills", "summary.issues.one": "Issues", "summary.issues.other": "Issues", "summary.sources.one": "Sources", "summary.sources.other": "Sources", "table.skill": "Skill name and description",
4924
5042
  "filter.source": "Source", "filter.all": "All sources", "filter.option": "{name} ({count})", "search": "Search", "search.placeholder": "Search skill names or descriptions", "search.clear": "Clear search",
4925
5043
  // Shared wording for the four list states (REDESIGN §12.2, slice 4): loading / load failed / retry / no match / clear filters.
4926
5044
  "list.loading": "Loading…", "list.error": "Failed to load", "list.retry": "Retry", "list.noMatch": "No matching items", "list.clearFilter": "Clear filters",
@@ -4941,6 +5059,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
4941
5059
  "fm.label.name": "Name", "fm.label.description": "Description", "fm.label.when-to-use": "When to use", "fm.label.user-invocable": "User invocable", "fm.label.disable-model-invocation": "Model invocation disabled", "fm.label.allowed-tools": "Allowed tools", "fm.label.license": "License", "fm.label.version": "Version", "fm.label.author": "Author", "fm.label.category": "Category", "fm.label.tags": "Tags", "fm.label.metadata": "Metadata", "fm.label.homepage": "Homepage", "fm.yes": "Yes", "fm.no": "No",
4942
5060
  "error.rules.sceneNotFound": "Scene not found: {group} (create it on the Scenes page first)",
4943
5061
  "error.rules.reservedScene": "「{name}」 is a reserved scene: {action} is not allowed{reason}",
5062
+ "error.rules.sceneNotActive": "The scene is not started: start it before locking",
4944
5063
  "error.rules.singleSceneOnly": "Only one scene besides 「global」 may be enabled at a time{detail}",
4945
5064
  "error.agentsMd.sceneRebindFailed": "Cannot change the prompt binding of scene 「{scene}」 to 「{target}」: {reason}",
4946
5065
  "error.agentsMd.referenced": "“{id}” cannot be deleted: {refs}. Remove that reference first — for a scene binding, rebind the prompt on the scene page or leave the scene; for the one currently applied, flip the switch on the left to un-apply.", "error.agentsMd.refProbeFailed": "Cannot tell whether “{id}” is still referenced, so the delete was refused: the reference probe failed (at least one of scene bindings / the current AGENTS.md content / the pre-scene baseline could not be read). Please try again later.", "error.agentsMd.noUnapplyBackup": "There is no “state from before the apply” to restore: the current AGENTS.md was not written by “Apply” (the preset created automatically the first time you open this page is one such case), so there is no starting point to go back to. To change what AGENTS.md holds: apply another preset, or edit AGENTS.md itself.", "error.agentsMd.unapplyWhileScene": "Scene “{scene}” is driving the global baseline, so it cannot be un-applied here: leave the scene first, or change its prompt binding on the Scenes page.",
@@ -5151,7 +5270,9 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
5151
5270
  "compat.op.batch": "Batch archive", "compat.op.delete": "Delete permanently",
5152
5271
  "compat.op.native": "Host-native entry", "compat.op.adapter": "Plugin adapter", "compat.op.unavailable": "Unavailable (disabled)",
5153
5272
  "compat.degraded": "Degraded capabilities", "compat.degraded.hint": "what is missing + the consequence", "compat.reason": "Reason:",
5154
- "compat.others": "Other capability slots", "compat.others.hint": "not degraded: the plugin supplies its own implementation when the host has no such entry",
5273
+ "compat.others": "Other capability slots", "compat.others.hint": "not degraded: the plugin supplies its own implementation when the host has no such entry",
5274
+ "compat.contracts": "Host contracts", "compat.contracts.hint": "host members this release depends on: absence never throws, but the matching feature loses half of itself", "compat.contracts.missing": "absent", "compat.contracts.allOk": "Every host contract this release depends on is present.",
5275
+ "compat.bareModules": "Bare module resolution", "compat.bareModules.hint": "official packages the plugin requires by bare name at runtime; they are not peers, so they take no part in the module comparison above", "compat.bareModules.present": "present", "compat.bareModules.absent": "not shipped by host", "compat.bareModules.drift": "contract drift suspected", "compat.bareModules.presentWhy": "affects: {feature}", "compat.bareModules.absentWhy": "the host no longer ships this package: {feature} degrades", "compat.bareModules.driftWhy": "{feature} is missing contract markers ({markers}) — please re-check that package's implementation manually",
5155
5276
  "compat.fallback.substituted": "Plugin adapter (no host entry)",
5156
5277
  "compat.fallback.native": "Falls back to native entry", "compat.fallback.refuse": "Operation refused", "compat.fallback.blocked": "Affected buttons disabled", "compat.fallback.inform": "Writes unaffected",
5157
5278
  "compat.modules": "Module identity (plugin vs host)", "compat.modules.hint": "adapting only works on one shared module", "compat.module.same": "same module", "compat.module.separate": "separate copy (repair needed)", "compat.module.sameVersion": "same version (inside/outside the archive)", "compat.module.unknown": "not comparable", "compat.module.realpath": "not comparable (real path unavailable)", "compat.modules.realpath": "{count} package(s) could **not be compared**: their real path could not be read ({names}) — that is not \"a separate copy\", it means the check could not run.", "compat.modules.realpathSides": "Host side: {host}; plugin side: {plugin}. Fix the path (permissions / path length / reinstall) and reopen this page to re-check.", "compat.modules.realpathNone": "none", "compat.modules.unverified": "{count} package(s) could not be compared (not resolvable from the host anchor: {names}) — that is not \"a separate copy\", it means the check could not run. Try node scripts/doctor.mjs.",
@@ -5183,7 +5304,11 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
5183
5304
  "compat.ov.nativeDelete": "Host native delete entry", "compat.ov.nativeDelete.ok": "The host offers no native delete entry (this plugin ships its own full sequence)",
5184
5305
  "compat.ov.hostIdentity": "Host identity & modules", "compat.ov.hostIdentity.ok": "Modules are shared with the host (host DSH {version})", "compat.ov.hostIdentity.asar": "Official packages match the host version (DSH {version}), but the host runs inside {name} while the plugin sits outside it", "compat.ov.hostIdentity.degraded": "Blockers present: {list}",
5185
5306
  "compat.ov.memory": "Memory", "compat.ov.prompts": "Prompts", "compat.ov.subagents": "Subagents", "compat.ov.scenes": "Scene profiles",
5307
+ "compat.ov.subagentPersona": "Subagent persona delegation", "compat.ov.subagentToolRestrict": "Subagent tool restriction", "compat.ov.subagentCreatedEvent": "Subagent creation ordering", "compat.ov.webAdmission": "Web connection admission",
5186
5308
  "compat.ov.plain.ok": "No degradation reported (assembly failures would appear here)",
5309
+ "compat.ov.contract.ok": "Host contract present (its absence would silently halve the feature)",
5310
+ "compat.ov.contract.degraded": "Host contract absent: {detail}",
5311
+ "compat.ov.domain.contract": "Required host modules present: {detail}",
5187
5312
  // Slash-command "Tools" section (chat composer). Domain labels stay short — the row keeps
5188
5313
  // room on the right for the summary and the drill chevron.
5189
5314
  "slash.section.tools": "Tools",
@@ -5262,6 +5387,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
5262
5387
  "compat.cap.workspaceIndexHeader": "Index session headers", "compat.cap.workspaceArchiveNative": "Host native archive entry", "compat.cap.workspaceUnarchiveNative": "Host native restore entry",
5263
5388
  "compat.cap.workspaceBatchNative": "Host native batch entry", "compat.cap.workspaceDeleteNative": "Host native delete entry", "compat.cap.sessionsDetachLive": "Persist & detach live sessions",
5264
5389
  "compat.cap.sessionsColdAnnounce": "Cold session removal broadcast", "compat.cap.projectionWrite": "Projection cache write path", "compat.cap.projectionTableDelete": "Projection cache row deletion",
5390
+ "compat.cap.agentPresetRegister": "Temporary child-preset registration", "compat.cap.toolsRestrictMethod": "Tool restriction (restrict)", "compat.cap.agentCreatedEvent": "Subagent creation event (agent/created)", "compat.cap.connectionAdmit": "Connection admission (admit)",
5265
5391
  "compat.cap.opArchive": "Archive", "compat.cap.opUnarchive": "Restore", "compat.cap.opBatch": "Batch operations", "compat.cap.opDelete": "Delete", "compat.cap.opList": "History list",
5266
5392
  "compat.reach": "Preset injection reach", "compat.reach.hint": "whether these contents reach the model under each preset",
5267
5393
  "compat.reach.suppressing": "suppressing",
@@ -5455,7 +5581,6 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
5455
5581
  "scenes.skills.shadowed.hint": "Shadowed by a same-name skill, so it can never take effect in this scene and cannot be checked. To use this copy instead, click “Use this one” for it on the Skills page.",
5456
5582
  "scenes.skills.invalid.hint": "Incomplete skill (missing frontmatter / invalid name / empty description): it cannot be enabled or disabled, so it cannot be checked.", "scenes.subagents.hint": "Check the personas this scene should turn on; anything unchecked (or with no section at all) is turned off when you enter, and restored to its previous switch when you exit.", "scenes.subagents.hint.short": "Add the section, then check the personas to turn on", "scenes.subagents.empty": "No personas yet — create one on the Subagents page.",
5457
5583
  "scenes.filter.skills": "Filter skills / directories", "scenes.filter.subagents": "Filter personas (name or description)", "scenes.filter.servers": "Filter servers",
5458
-
5459
5584
  "scenes.mem.title": "Memories", "scenes.mem.search": "Filter memories in this scene (name or description)",
5460
5585
  "scenes.mem.noMatch": "Nothing matches", "scenes.mem.emptyScene": "This scene has no memories yet",
5461
5586
  "scenes.mem.createNeedScene": "Fill in the scene name first", "scenes.mem.pending": "{count} pending", "scenes.mem.pendingTag": "Pending", "scenes.mem.attach.tip": "Carries {count} attachment(s) · {size} total", "scenes.mem.removePending": "Remove",
@@ -5597,7 +5722,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
5597
5722
  "scenes.preview.bind.tool": "Tool table: switch to scheme “{name}” (hides {count})",
5598
5723
  "scenes.preview.bind.toolMissing": "Tool table: scheme “{name}” no longer exists — nothing switches this time",
5599
5724
  "scenes.preview.bind.toolKeep": "Tool table: stays as it is (this scene binds none)", "scenes.preview.row.notes": "Overrides the notes of {count} server(s) with scene notes", "scenes.field.toolTable": "Model tool-table scheme", "scenes.field.quickPrompts": "Quick prompts", "scenes.field.quickPrompts.current": "Current settings", "scenes.field.quickPrompts.customCount": "Custom ({picked}/{total})", "scenes.field.quickPrompts.none": "No quick prompts yet — add some on the Prompts page",
5600
- "scenes.field.quickPrompts.noneOption": "None", "scenes.field.quickPrompts.list": "Enabled in this scene", "scenes.field.quickPrompts.pick": "Choose", "scenes.quickPick.title": "Choose the quick prompts enabled in this scene", "scenes.quickPick.hint": "Checked ones are enabled when you enter this scene, unchecked ones are disabled; leaving the scene restores the switches from before you entered.", "scenes.quickPick.confirm": "Done", "scenes.form.section.basic": "Basics", "scenes.form.section.binding": "Scene bindings", "scenes.field.toolTable.none": "Current settings", "scenes.field.toolTable.factory": "Factory default (hides {count})", "scenes.field.toolTable.count": "hides {count}", "scenes.field.toolTable.missing": "scheme no longer exists", "scenes.preview.more": " etc.", "scenes.preview.truncated": "(each kind lists its first 60 entries only)", "scenes.preview.stale": "{count} profile entry(ies) no longer exist in this environment and will not take effect: {items}", "scenes.doctor.title": "{count} reference(s) point at a name that no longer exists", "scenes.doctor.noneButSkipped": "The check found no dangling references", "scenes.doctor.hint": "Left behind by a rename or a delete: it still looks bound here, but nothing matches that name at runtime. Point it at the right name, or untick it in the profile.", "scenes.doctor.row": "{where}: “{name}” no longer exists", "scenes.doctor.more": "{count} more not listed", "scenes.doctor.skipped": "({domains} could not be read, so that kind was not checked)", "agm.restore.tag": "Restored after the scene", "agm.restore.tag.hint": "The global prompt from before the scene; leaving the scene restores it from the snapshot's text, so deleting this preset does not break the restore", "agm.file.mismatch": "Not in AGENTS.md", "agm.file.mismatch.hint": "AGENTS.md does not currently match this preset (it may have been edited by hand); flip the switch on the left to write it back", "hist.restore.note.registered": "Restored {count} session(s); workspace “{title}” was registered again and the sessions are back in that group.", "hist.restore.note.attached": "Restored {count} session(s) and attached them to workspace “{title}”.", "hist.restore.note.skip": "Workspace ownership could not be restored for {count} session(s): {reason}", "hist.purge.title": "Delete forever?", "hist.purge.one": "Delete “{title}” and all of its records forever — this cannot be undone", "hist.purge.batch": "Delete the {count} selected sessions and all of their records forever — this cannot be undone", "hist.btn.restore": "Restore", "hist.btn.purge": "Delete forever", "hist.btn.confirmPurge": "Delete", "hist.selectGroup": "Select group {title}", "hist.select": "Select this session", "hist.group.count": "{count}", "hist.selectAll": "Select all", "hist.deselectAll": "Deselect all", "hist.btn.import": "Import", "hist.btn.export": "Export", "hist.btn.restoreSelected": "Restore selected ({count})", "hist.btn.deleteSelected": "Delete selected ({count})", "hist.stat.archived": "Archived", "hist.stat.projects": "Workspaces", "hist.stat.retention": "Retention", "hist.search.placeholder": "Search title / session id / project path", "hist.loading": "Loading…", "hist.empty.search": "No matching archived sessions", "hist.empty.none": "No archived sessions yet", "hist.import.cwd": "Workspace (optional)", "hist.import.cwd.placeholder": "Pick a registered workspace or type an absolute path; leave blank to go to Ungrouped", "hist.import.workspace": "Workspace", "hist.import.workspace.none": "No registered workspace on this machine yet — type an absolute path.", "hist.import.done": "Created session {id} ({count} messages); continue it from the DSH session list.", "hist.import.lossy": "⚠ No speaker markers were found in the text, so it was folded into a single user message: the original turn structure may have been lost. If the source is a JSONL or Markdown transcript, import it with its original extension.", "hist.export.hint": "Export the selected sessions as transcript files that can be imported again", "hist.export.dir": "Export directory (absolute path, created automatically)", "hist.export.browse": "Browse and choose a folder", "hist.btn.browse": "Choose", "hist.export.pickDir": "Choose export directory", "hist.export.format": "Format", "hist.export.detail": "Export detail", "hist.export.detail.simple": "Text only", "hist.export.detail.tools": "Text + tools", "hist.export.detail.full": "Text + tools + thinking", "hist.export.scope": "Session scope", "hist.export.workspace": "Workspace", "hist.export.sessions": "Sessions ({count})", "hist.export.loading": "Loading sessions…", "hist.cwd.missing": "⚠ The workspace directory no longer exists: ", "hist.tag.archived": "Archived", "hist.tag.live": "Not archived", "hist.export.empty": "No matching sessions", "hist.result.exported": "Exported {count} session(s) to {dir}", "hist.result.skipped": "; skipped {count} ({ids})", "hist.export.busy": "Working…", "hist.btn.exportSelected": "Export {count} session(s)", "hist.error.title": "Operation failed", "hist.error.unknown": "Unknown error",
5725
+ "scenes.field.quickPrompts.noneOption": "None", "scenes.field.quickPrompts.list": "Enabled in this scene", "scenes.field.quickPrompts.pick": "Choose", "scenes.quickPick.title": "Choose the quick prompts enabled in this scene", "scenes.quickPick.hint": "Checked ones are enabled when you enter this scene, unchecked ones are disabled; leaving the scene restores the switches from before you entered.", "scenes.quickPick.confirm": "Done", "scenes.form.section.basic": "Basics", "scenes.form.section.binding": "Scene bindings", "scenes.field.toolTable.none": "Current settings", "scenes.field.toolTable.factory": "Factory default (hides {count})", "scenes.field.toolTable.count": "hides {count}", "scenes.field.toolTable.missing": "scheme no longer exists", "scenes.preview.more": " etc.", "scenes.preview.truncated": "(each kind lists its first 60 entries only)", "scenes.preview.stale": "{count} profile entry(ies) no longer exist in this environment and will not take effect: {items}", "scenes.doctor.title": "{count} reference(s) point at a name that no longer exists", "scenes.doctor.noneButSkipped": "The check found no dangling references", "scenes.doctor.hint": "Left behind by a rename or a delete: it still looks bound here, but nothing matches that name at runtime. Point it at the right name, or untick it in the profile.", "scenes.doctor.row": "{where}: “{name}” no longer exists", "scenes.doctor.more": "{count} more not listed", "scenes.doctor.skipped": "({domains} could not be read, so that kind was not checked)", "agm.restore.tag": "Restored after the scene", "agm.restore.tag.hint": "The global prompt from before the scene; leaving the scene restores it from the snapshot's text, so deleting this preset does not break the restore", "agm.file.mismatch": "Not in AGENTS.md", "agm.file.mismatch.hint": "AGENTS.md does not currently match this preset (it may have been edited by hand); flip the switch on the left to write it back", "agm.agentsHome.present": "Another global instruction file at {path} is also injected (shared with other agent tools); edit that file to change it.", "hist.restore.note.registered": "Restored {count} session(s); workspace “{title}” was registered again and the sessions are back in that group.", "hist.restore.note.attached": "Restored {count} session(s) and attached them to workspace “{title}”.", "hist.restore.note.skip": "Workspace ownership could not be restored for {count} session(s): {reason}", "hist.purge.title": "Delete forever?", "hist.purge.one": "Delete “{title}” and all of its records forever — this cannot be undone", "hist.purge.batch": "Delete the {count} selected sessions and all of their records forever — this cannot be undone", "hist.btn.restore": "Restore", "hist.btn.purge": "Delete forever", "hist.btn.confirmPurge": "Delete", "hist.selectGroup": "Select group {title}", "hist.select": "Select this session", "hist.group.count": "{count}", "hist.selectAll": "Select all", "hist.deselectAll": "Deselect all", "hist.btn.import": "Import", "hist.btn.export": "Export", "hist.btn.restoreSelected": "Restore selected ({count})", "hist.btn.deleteSelected": "Delete selected ({count})", "hist.stat.archived": "Archived", "hist.stat.projects": "Workspaces", "hist.stat.retention": "Retention", "hist.search.placeholder": "Search title / session id / project path", "hist.loading": "Loading…", "hist.empty.search": "No matching archived sessions", "hist.empty.none": "No archived sessions yet", "hist.import.cwd": "Workspace (optional)", "hist.import.cwd.placeholder": "Pick a registered workspace or type an absolute path; leave blank to go to Ungrouped", "hist.import.workspace": "Workspace", "hist.import.workspace.none": "No registered workspace on this machine yet — type an absolute path.", "hist.import.done": "Created session {id} ({count} messages); continue it from the DSH session list.", "hist.import.lossy": "⚠ No speaker markers were found in the text, so it was folded into a single user message: the original turn structure may have been lost. If the source is a JSONL or Markdown transcript, import it with its original extension.", "hist.export.hint": "Export the selected sessions as transcript files that can be imported again", "hist.export.dir": "Export directory (absolute path, created automatically)", "hist.export.browse": "Browse and choose a folder", "hist.btn.browse": "Choose", "hist.export.pickDir": "Choose export directory", "hist.export.format": "Format", "hist.export.detail": "Export detail", "hist.export.detail.simple": "Text only", "hist.export.detail.tools": "Text + tools", "hist.export.detail.full": "Text + tools + thinking", "hist.export.scope": "Session scope", "hist.export.workspace": "Workspace", "hist.export.sessions": "Sessions ({count})", "hist.export.loading": "Loading sessions…", "hist.cwd.missing": "⚠ The workspace directory no longer exists: ", "hist.tag.archived": "Archived", "hist.tag.live": "Not archived", "hist.export.empty": "No matching sessions", "hist.result.exported": "Exported {count} session(s) to {dir}", "hist.result.skipped": "; skipped {count} ({ids})", "hist.export.busy": "Working…", "hist.btn.exportSelected": "Export {count} session(s)", "hist.error.title": "Operation failed", "hist.error.unknown": "Unknown error",
5601
5726
  }
5602
5727
  };
5603
5728
 
@@ -6662,7 +6787,9 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
6662
6787
  React.createElement('div', { className: 'dsm-source-head' },
6663
6788
  React.createElement('button', { type: 'button', className: 'dsm-source-head-main', 'aria-expanded': open, onClick: flip },
6664
6789
  React.createElement('span', { className: 'dsm-source-title', title: group.title }, group.title),
6665
- React.createElement('span', { className: 'dsm-count' }, mt('mcp.servers.count', { count: groupRows.length })),
6790
+ // 分组头只留数字(用户 2026-10-17):量词("个服务")在每一组的标题里已经说过
6791
+ // 一遍,再挂在数字后面只是重复;同一个数字在三页里一个样,扫起来才对得齐。
6792
+ React.createElement('span', { className: 'dsm-count' }, String(groupRows.length)),
6666
6793
  group.path ? React.createElement('span', { className: 'dsm-path', title: group.path }, group.path) : null)),
6667
6794
  open ? React.createElement('div', { className: 'dsm-source-body' },
6668
6795
  // 表头已随两行制拆除(REDESIGN §3.4 没有表头行;列对齐改由行内两行承担),
@@ -7356,7 +7483,7 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
7356
7483
  React.createElement('div', { className: 'dsm-source-head' },
7357
7484
  React.createElement('button', { type: 'button', className: 'dsm-source-head-main', 'aria-expanded': open, onClick: function () { toggleGroup(key, baseOpen) } },
7358
7485
  React.createElement('span', { className: 'dsm-source-title', title: label }, label),
7359
- React.createElement('span', { className: 'dsm-count' }, t('trash.items.count', { count: count }))),
7486
+ React.createElement('span', { className: 'dsm-count' }, String(count))),
7360
7487
  actions ? React.createElement('div', { className: 'dsm-source-actions' }, actions) : null),
7361
7488
  open ? React.createElement('div', { className: 'dsm-source-body' }, bodyNode) : null)
7362
7489
  }
@@ -7569,6 +7696,13 @@ button.dsm-rowselect-box:disabled{cursor:default;opacity:.45}
7569
7696
  // 「锁定时只显示锁定横幅」,提示词页此前漏了这层互斥,于是同一件事说了两遍
7570
7697
  // (用户 2026-09-19 截图:提示词页两条黄条,其他管理页只有一条)。
7571
7698
  (state.anyLocked !== true && sceneDriver) ? React.createElement('div', { className: 'dsm-feedback dsm-warning', role: 'status' }, t('agm.sceneLock.notice', { scene: sceneDriverLabel, id: sceneDriver.presetId })) : null,
7699
+ // 共享 agents 根那一份 AGENTS.md(`~/.agents/AGENTS.md`)**只读提示**:0.2.1 起
7700
+ // 官方把用户全局链扩成两份并列(`$DSH_HOME/AGENTS.md` → `<agentsHome>/AGENTS.md`),
7701
+ // 这一页只管得到前一份。所以"改了没生效"有了第二个解释 —— 真正在说话的是后一份。
7702
+ // 只提示、不给写入入口:这不是本插件的预设,写它会与官方的去重/刷新规则互相覆盖。
7703
+ (state.current && state.current.agentsHomeAgentsMd && state.current.agentsHomeAgentsMd.exists)
7704
+ ? React.createElement('div', { className: 'dsm-feedback', role: 'status' }, t('agm.agentsHome.present', { path: state.current.agentsHomeAgentsMd.path }))
7705
+ : null,
7572
7706
  // 一次性拒绝说明(被引用的预设删不掉):不替换列表,只加一条横幅。
7573
7707
  state.deny ? React.createElement(Notice, { kind: 'warn', text: state.deny }) : null,
7574
7708
  // 搜索框:页头之下、列表之前(与 MCP / 技能 / 记忆页同一位置与样式)。
@@ -9325,7 +9459,7 @@ function enabledMemoryCount(rules) {
9325
9459
  return React.createElement('section', { key: g.id, className: split ? 'dsm-trash-split-pane' : 'dsm-trash-pane-group' },
9326
9460
  g.title ? React.createElement('div', { className: 'dsm-trash-pane-group-head' },
9327
9461
  React.createElement('span', { className: 'dsm-trash-pane-group-title', title: g.note }, g.title),
9328
- React.createElement('span', { className: 'dsm-count' }, t('trash.items.count', { count: list.length }))) : null,
9462
+ React.createElement('span', { className: 'dsm-count' }, String(list.length))) : null,
9329
9463
  d.error ? React.createElement('div', { className: 'dsm-feedback dsm-error', role: 'alert' }, d.error) : null,
9330
9464
  list.length ? bulkRow(g) : null,
9331
9465
  d.loading
@@ -9407,7 +9541,7 @@ function enabledMemoryCount(rules) {
9407
9541
  key: 'trash-modal',
9408
9542
  title: React.createElement('span', { className: 'dsm-modal-title-row' },
9409
9543
  props.title || t('trash.title'),
9410
- totalReady ? React.createElement('span', { className: 'dsm-count' }, t('trash.total', { count: totalCount })) : null),
9544
+ totalReady ? React.createElement('span', { className: 'dsm-count' }, String(totalCount)) : null),
9411
9545
  closeLabel: t('btn.close'),
9412
9546
  list: true,
9413
9547
  className: 'dsm-modal-list dsm-modal-trash' + (split ? ' dsm-modal-trash-split' : ''),
@@ -9625,7 +9759,7 @@ function enabledMemoryCount(rules) {
9625
9759
  },
9626
9760
  h(CaretIcon, { key: 'caret', open: open, className: 'dsm-pick-caret' }),
9627
9761
  h('span', { className: 'dsm-pick-group-name', title: g.name }, g.name),
9628
- h('span', { className: 'dsm-pick-group-count' }, t('export.group.count', { count: g.rows.length }))) : null,
9762
+ h('span', { className: 'dsm-pick-group-count' }, String(g.rows.length))) : null,
9629
9763
  open ? g.rows.map(function (it) {
9630
9764
  return pickRow({ key: it.key, checked: picked.indexOf(it.key) >= 0, name: it.name, desc: it.desc, descTitle: it.descTitle || it.desc, onChange: function () { toggle(it.key) } })
9631
9765
  }) : null)
@@ -10559,7 +10693,7 @@ function enabledMemoryCount(rules) {
10559
10693
  var baseOpen = openFlag === undefined ? bulkTargets(root.key).some(function (x) { return x.on }) : !!openFlag;
10560
10694
  var open = baseOpen || !!dq;
10561
10695
  var rootCount = root.count == null ? root.skills.length : root.count;
10562
- var section = h("section", { key: root.key, className: "dsm-source" }, h("div", { className: "dsm-source-head dsm-source-head-pin" }, h("button", { type: "button", className: "dsm-source-head-main", "aria-expanded": open, onClick: function () { setExpanded(Object.assign({}, expanded, { [root.key]: !baseOpen })); } }, h("span", { className: "dsm-source-title", title: displayName }, displayName), h("span", { className: "dsm-count" }, t(countKey("summary.group", rootCount), { count: rootCount })), h("span", { className: "dsm-tag " + (root.scope === "project" ? "dsm-tag-on" : root.defaultSource === true ? "" : root.enabled ? "dsm-tag-on" : "") }, root.scope === "project" ? t("status.project") : root.defaultSource === true ? t("status.manageable") : t(root.enabled ? "status.source.on" : "status.source.off")), root.scope === "project" ? h("span", { className: "dsm-tag" }, t("status.rank", { rank: root.rank })) : null, h("span", { className: "dsm-path", title: root.path }, root.path)), (function () {
10696
+ var section = h("section", { key: root.key, className: "dsm-source" }, h("div", { className: "dsm-source-head dsm-source-head-pin" }, h("button", { type: "button", className: "dsm-source-head-main", "aria-expanded": open, onClick: function () { setExpanded(Object.assign({}, expanded, { [root.key]: !baseOpen })); } }, h("span", { className: "dsm-source-title", title: displayName }, displayName), h("span", { className: "dsm-count" }, String(rootCount)), h("span", { className: "dsm-tag " + (root.scope === "project" ? "dsm-tag-on" : root.defaultSource === true ? "" : root.enabled ? "dsm-tag-on" : "") }, root.scope === "project" ? t("status.project") : root.defaultSource === true ? t("status.manageable") : t(root.enabled ? "status.source.on" : "status.source.off")), root.scope === "project" ? h("span", { className: "dsm-tag" }, t("status.rank", { rank: root.rank })) : null, h("span", { className: "dsm-path", title: root.path }, root.path)), (function () {
10563
10697
  // 目录级「全选 / 取消全选」(用户裁定 2026-09-17):只作用于**这个目录下当前
10564
10698
  // 可见且可切换**的技能 —— 与页头那颗同一套目标集逻辑(bulkTargets 传 rootKey),
10565
10699
  // 同名技能只算生效的那一个(被覆盖的副本跳过)。
@@ -11303,7 +11437,7 @@ function enabledMemoryCount(rules) {
11303
11437
  React.createElement(RowCheck, { checked: allChecked, label: t('hist.selectGroup', { title: g.title }), onChange: function () { toggleGroup(g.key, groupIds) } }),
11304
11438
  React.createElement('button', { type: 'button', className: 'dsm-source-head-main', 'aria-expanded': open, onClick: function () { toggleGroupCollapse(g.key, baseOpen) } },
11305
11439
  React.createElement('span', { className: 'dsm-source-title dsm-hist-group-title', title: g.title }, g.title),
11306
- React.createElement('span', { className: 'dsm-count' }, t('hist.group.count', { count: g.items.length }))),
11440
+ React.createElement('span', { className: 'dsm-count' }, String(g.items.length))),
11307
11441
  // 目录已被删除(dirMissing)时:既不给「重新登记」入口,也不再并列「已移除」标签 ——
11308
11442
  // 只留一枚醒目告警,明确告诉用户这个工作区的磁盘文件夹已经不存在了
11309
11443
  // (用户 2026-10-12:「如果工作区已经被删除应该不能恢复……要怎么及时知道这个工作区
@@ -11475,7 +11609,7 @@ function enabledMemoryCount(rules) {
11475
11609
  React.createElement('button', { type: 'button', className: 'dsm-pick-group-head', 'aria-expanded': open ? 'true' : 'false', onClick: function () { toggleGroupCollapse(key, open) } },
11476
11610
  React.createElement(CaretIcon, { open: open, className: 'dsm-pick-caret' }),
11477
11611
  React.createElement('span', { className: 'dsm-pick-group-name', title: g.cwd || t('hist.ungrouped') }, g.cwd || t('hist.ungrouped')),
11478
- React.createElement('span', { className: 'dsm-pick-group-count' }, t('export.group.count', { count: g.rows.length }))),
11612
+ React.createElement('span', { className: 'dsm-pick-group-count' }, String(g.rows.length))),
11479
11613
  open ? g.rows.map(function (it) {
11480
11614
  // 导出列表整行可点勾选(REDESIGN §5.1,切片五):RowSelect 本尊,
11481
11615
  // 目录走 desc 槽(缺目录的带 ⚠),归档态标签走 meta 槽。
@@ -11626,6 +11760,16 @@ function enabledMemoryCount(rules) {
11626
11760
  var sceneForm = sfs[0], setSceneForm = sfs[1]
11627
11761
  // 切走这一页 → 场景编辑器关窗时那份暂存作废(见 42-shared-ui.js 的 useFormDraftScope)。
11628
11762
  useFormDraftScope('scene:')
11763
+ // 「新建记忆」子弹窗误关后的暂存槽(用户 2026-10-17:编辑器里那颗「创建」点开写完,
11764
+ // 手一滑点到背板就全没了,而记忆页 / 技能页都有 30 秒兜底)。
11765
+ // key 跟着**这次编辑的是哪个场景**走(`modal.draftKey` 就是 `scene:create` /
11766
+ // `scene:edit:<名字>`):固定一个槽会让「在 A 里关掉、到 B 里点创建」取回 A 的草稿。
11767
+ // 新建 / 编辑**两个槽分开** —— takeFormDraft 是取回即销账,共用一个槽的话,
11768
+ // 先误关一条编辑中的、再去点「创建」,那份就会被当成"不是新建"而白白丢掉。
11769
+ // 前缀仍是 `scene:`,切走这一页照旧整组作废。
11770
+ function memNewDraftKey(editing) {
11771
+ return String((modal && modal.draftKey) || 'scene:create') + (editing ? ':memedit' : ':memnew')
11772
+ }
11629
11773
  var pos = React.useState([])
11630
11774
  var presetOptions = pos[0], setPresetOptions = pos[1]
11631
11775
  // 当前**生效中**的提示词预设 id(`agentsmd-list` 里 active 的那份)。预览卡要用它把
@@ -12330,18 +12474,30 @@ function enabledMemoryCount(rules) {
12330
12474
  }
12331
12475
  function openMemoryCreate() {
12332
12476
  if (!memoryCreateReady()) return
12477
+ // 30 秒内重开:把刚才误关掉的那份填回来(与记忆页 / 技能页同一套,见
12478
+ // 42-shared-ui.js 的 stashFormDraft)。只认"新建"那一槽。
12479
+ var draft = takeFormDraft(memNewDraftKey(false))
12333
12480
  // `editing` = 这一窗改的是第几条草稿;null = 新建一条。
12334
- setMemNew({ editing: null, name: '', description: '', body: '', form: 'flat', files: [], error: null })
12481
+ setMemNew(draft
12482
+ ? Object.assign({}, draft, { editing: null, error: null })
12483
+ : { editing: null, name: '', description: '', body: '', form: 'flat', files: [], error: null })
12335
12484
  }
12336
12485
  /** 改一条待创建的记忆:草稿回填进弹窗,保存时**替换**原来那条,而不是又添一条。 */
12337
12486
  function openMemoryEdit(index) {
12338
12487
  if (!modal || modal.type !== 'scene-editor') return
12339
12488
  var m = (modal.pendingMems || [])[index]
12340
12489
  if (!m) return
12341
- setMemNew({
12342
- editing: index, name: m.name, description: m.description || '', body: m.body || '',
12343
- form: m.form || 'flat', files: (m.files || []).slice(), error: null,
12344
- })
12490
+ // 同一份草稿被误关掉:30 秒内重开填回改了一半的内容。索引会随「删掉别的草稿」
12491
+ // 漂移,所以连**目标名**一起核对 —— 只认索引与名字都对得上的那一份,否则宁可
12492
+ // 从 `pendingMems` 重读,也不能把 A 改了一半的内容塞进 B 的弹窗里。
12493
+ var draft = takeFormDraft(memNewDraftKey(true))
12494
+ var restore = draft && draft.editing === index && draft.linkedName === m.name ? draft : null
12495
+ setMemNew(restore
12496
+ ? Object.assign({}, restore, { editing: index, error: null })
12497
+ : {
12498
+ editing: index, name: m.name, description: m.description || '', body: m.body || '',
12499
+ form: m.form || 'flat', files: (m.files || []).slice(), error: null,
12500
+ })
12345
12501
  }
12346
12502
  /** 撤掉一条还没落盘的记忆草稿。 */
12347
12503
  function dropPendingMemory(index) {
@@ -12513,7 +12669,21 @@ function enabledMemoryCount(rules) {
12513
12669
  // 改草稿时标题与主按钮换成「编辑 / 保存」那一套(与记忆页同词条)——否则同一个弹窗
12514
12670
  // 在"新建"和"改一条已存在的东西"两种语义下长得一模一样,用户分不清点下去会加一条还是改一条。
12515
12671
  var editing = memNew.editing != null
12516
- return React.createElement(Modal, { key: 'memnew', className: 'dsm-modal-compact', title: editing ? t('memory.edit.title') : t('memory.create.title'), closeLabel: t('btn.close'), onClose: function () { setMemNew(null) } },
12672
+ // 关掉 ≠ 丢掉(与记忆页的 closeEditor 同一条口径):只有误关这条路进暂存 ——
12673
+ // 提交成功走 stageMemoryDraft,它不经过 onClose,所以不会存下一份已经用掉的草稿。
12674
+ // 编辑态额外记 `linkedName`:`editing` 是数组下标,期间删掉别的草稿就会漂移,
12675
+ // 取回时两个都对得上才认(见 openMemoryEdit)。
12676
+ var closeMemoryCreate = function () {
12677
+ var pending = (modal.pendingMems || [])[memNew.editing]
12678
+ stashFormDraft(memNewDraftKey(editing), {
12679
+ editing: editing ? memNew.editing : null,
12680
+ linkedName: editing && pending ? pending.name : null,
12681
+ name: memNew.name || '', description: memNew.description || '', body: memNew.body || '',
12682
+ form: memNew.form || 'flat', files: (memNew.files || []).slice(),
12683
+ })
12684
+ setMemNew(null)
12685
+ }
12686
+ return React.createElement(Modal, { key: 'memnew', className: 'dsm-modal-compact', title: editing ? t('memory.edit.title') : t('memory.create.title'), closeLabel: t('btn.close'), onClose: closeMemoryCreate },
12517
12687
  React.createElement('div', { className: 'dsm-form' },
12518
12688
  React.createElement('label', { className: 'dsm-field' },
12519
12689
  React.createElement('span', { className: 'dsm-label' }, t('memory.field.group')),
@@ -14443,8 +14613,8 @@ function enabledMemoryCount(rules) {
14443
14613
  //
14444
14614
  // 目录注入深度:人设目录注入到哪些会话(默认 1 = 只在顶层)。
14445
14615
  // ⚠️ 它**不是**递归上限。2026-09-17 用户实测后改名(原名 maxDepth /「委派预算」):
14446
- // 官方 `dsh-tool-subagent` 默认 `maxDepth: 3`,子代理本来就能继续嵌套,本插件
14447
- // 也不再向官方传 maxDepth。这个字段只决定常驻目录出现在哪些深度的会话里。
14616
+ // 官方按自己的 `maxDepth` 配置决定能不能继续嵌套(0.2.1 默认 1),本插件也不再
14617
+ // 向官方传 maxDepth。这个字段只决定常驻目录出现在哪些深度的会话里。
14448
14618
  React.createElement('div', { className: 'dsm-field-row' },
14449
14619
  React.createElement('div', { className: 'dsm-field' },
14450
14620
  React.createElement('span', { className: 'dsm-label' }, t('subagents.field.catalogDepth')),
@@ -14533,6 +14703,7 @@ function enabledMemoryCount(rules) {
14533
14703
  * 提到组件外是为了能被 `useMemo` 包住:这段要遍历全部记忆并逐桶排序,而本页的
14534
14704
  * 编辑器弹窗每敲一个字都会重渲染整页(`editor` 是页内 state)。传进来的 `q` 已防抖。
14535
14705
  */
14706
+
14536
14707
  function memoryView(data, q, sceneFilter) {
14537
14708
  function matchRule(r) {
14538
14709
  if (sceneFilter !== '' && sceneOfGroup(r.group) !== sceneFilter) return false