dsh-plugin-tool-management 0.8.0 → 0.9.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 (60) hide show
  1. package/CHANGELOG.md +67 -1
  2. package/README.md +53 -48
  3. package/README_EN.md +47 -44
  4. package/docs/images/1-EN.png +0 -0
  5. package/docs/images/1.png +0 -0
  6. package/docs/images/2-EN.png +0 -0
  7. package/docs/images/2.png +0 -0
  8. package/docs/images/3-EN.png +0 -0
  9. package/docs/images/3.png +0 -0
  10. package/docs/images/4-EN.png +0 -0
  11. package/docs/images/4.png +0 -0
  12. package/docs/images/5-EN.png +0 -0
  13. package/docs/images/5.png +0 -0
  14. package/docs/images/6-EN.png +0 -0
  15. package/docs/images/6.png +0 -0
  16. package/docs/images/7-EN.png +0 -0
  17. package/docs/images/7.png +0 -0
  18. package/docs/images/8-EN.png +0 -0
  19. package/docs/images/8.png +0 -0
  20. package/docs/update.md +39 -0
  21. package/lib/agents-md/preset-id.js +1 -1
  22. package/lib/agents-md/service.js +46 -9
  23. package/lib/approval-policy.js +1 -1
  24. package/lib/client.js +431 -135
  25. package/lib/compat/preset-reach.js +155 -93
  26. package/lib/compat/probe.js +6 -4
  27. package/lib/context-inject.js +304 -0
  28. package/lib/hub.js +155 -13
  29. package/lib/imports/upload.js +34 -14
  30. package/lib/index.js +870 -226
  31. package/lib/mcp/state-section.js +46 -15
  32. package/lib/rules/archive-engine.js +182 -57
  33. package/lib/rules/archive.js +57 -1
  34. package/lib/rules/service.js +386 -156
  35. package/lib/scene-prompt-sync.js +101 -12
  36. package/lib/skills/catalog.js +125 -0
  37. package/lib/skills/core.js +38 -4
  38. package/lib/skills/service.js +36 -10
  39. package/lib/subagents/catalog.js +15 -5
  40. package/lib/subagents/service.js +39 -28
  41. package/lib/subagents/tools.js +18 -13
  42. package/package.json +14 -9
  43. package/screenshots.json +8 -8
  44. package/docs/images/1/345/234/272/346/231/257.png +0 -0
  45. package/docs/images/1/345/234/272/346/231/257_en.png +0 -0
  46. package/docs/images/2MCP.png +0 -0
  47. package/docs/images/2MCP_en.png +0 -0
  48. package/docs/images/3/346/212/200/350/203/275.png +0 -0
  49. package/docs/images/3/346/212/200/350/203/275_en.png +0 -0
  50. package/docs/images/4/345/255/220/346/231/272/350/203/275/344/275/223.png +0 -0
  51. package/docs/images/4/345/255/220/346/231/272/350/203/275/344/275/223_en.png +0 -0
  52. package/docs/images/5/346/217/220/347/244/272/350/257/215.png +0 -0
  53. package/docs/images/5/346/217/220/347/244/272/350/257/215_en.png +0 -0
  54. package/docs/images/6/350/256/260/345/277/206.png +0 -0
  55. package/docs/images/6/350/256/260/345/277/206_en.png +0 -0
  56. package/docs/images/7/344/274/232/350/257/235.png +0 -0
  57. package/docs/images/7/344/274/232/350/257/235_en.png +0 -0
  58. package/docs/images/8/345/205/274/345/256/271.png +0 -0
  59. package/docs/images/8/345/205/274/345/256/271_en.png +0 -0
  60. package/lib/prompt-sections.js +0 -148
package/lib/client.js CHANGED
@@ -26,9 +26,12 @@ window.__ModuleLoader__.load({
26
26
  '.dsm-tools{display:flex;flex-direction:column;gap:8px}' +
27
27
  '.dsm-tool{padding:9px 11px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}' +
28
28
  '.dsm-tool-name{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;font-weight:600;word-break:break-all}' +
29
- '.dsm-tool-name-row{display:flex;align-items:center;justify-content:space-between;gap:8px}' +
29
+ '.dsm-tool-name-row{display:flex;align-items:center;gap:8px}' +
30
+ // 标签紧跟工具名(原来是 space-between,标签被推到中间、与名字隔着一大片空白);
31
+ // 开关单独用 margin-left:auto 顶到最右,标签不再受它的位置影响。
32
+ '.dsm-tool-name-row .dsm-switch{margin-left:auto;flex:none}' +
30
33
  '.dsm-tool-off .dsm-tool-name,.dsm-tool-off .dsm-tool-desc{opacity:.45}' +
31
- '.dsm-tool-desc{margin-top:3px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:17px}' +
34
+ '.dsm-tool-desc{min-width:0;flex:1;margin-top:3px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:17px}' +
32
35
  '.dsm-tool-params{display:flex;flex-direction:column;gap:3px;margin-top:7px;padding-top:7px;border-top:1px dashed var(--dsw-alias-border-l2)}' +
33
36
  '.dsm-tool-param{display:flex;gap:8px;font-size:11px;align-items:baseline}' +
34
37
  '.dsm-tool-param-key{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-weight:600;min-width:80px;word-break:break-all}' +
@@ -49,6 +52,16 @@ window.__ModuleLoader__.load({
49
52
  .dsm-table-head,.dsm-row{display:grid;grid-template-columns:minmax(180px,1fr) 120px 90px max-content;align-items:center;column-gap:12px;padding:0 13px}.dsm-table-head{min-height:32px;border-bottom:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);font-size:11px}.dsm-row{min-height:58px;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-row:last-child{border-bottom:0}.dsm-main{min-width:0}.dsm-name{overflow:hidden;font-size:13px;font-weight:570;text-overflow:ellipsis;white-space:nowrap}.dsm-note{overflow:hidden;margin-top:2px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:17px;text-overflow:ellipsis;white-space:nowrap}.dsm-tags{display:flex;align-items:center;gap:5px;flex-wrap:wrap}.dsm-tag{display:inline-flex;min-height:19px;align-items:center;padding:0 6px;border:1px solid var(--dsw-alias-border-l3);border-radius:4px;color:var(--dsw-alias-label-secondary);font-size:10px;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-enabled{color:var(--dsw-alias-state-success-primary);font-size:12px;white-space:nowrap}.dsm-disabled{color:#d49245;font-size:12px;white-space:nowrap}.dsm-shadowed{color:var(--dsw-alias-label-tertiary);font-size:12px;white-space:nowrap}.dsm-row-actions{display:flex;align-items:center;justify-content:flex-end;gap:7px}
50
53
  .dsm-switch{position:relative;width:34px;height:20px;flex:none;padding:0;border:0;border-radius:999px;background:var(--dsw-alias-border-l3);cursor:pointer}.dsm-switch:after{position:absolute;top:3px;left:3px;width:14px;height:14px;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-row{display:flex;min-height:48px;align-items:center;padding:0 13px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2);color:inherit;font:inherit;font-size:13px;cursor:pointer}.dsm-trash-count{margin-left:auto;padding:2px 7px;border-radius:99px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font-size:11px}.dsm-empty{padding:25px 14px;color:var(--dsw-alias-label-tertiary);font-size:12px;text-align:center}.dsm-feedback{padding:9px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-secondary);font-size:12px}.dsm-warning{border-color:#d49245;color:#d49245}.dsm-error{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
51
54
  .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:22px;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:var(--dsw-alias-bg-layer-2);box-shadow:var(--dsw-shadow-lv3)}.dsm-modal-sm,.dsm-modal-import{width:min(480px,100%)!important}.dsm-modal-md{width:min(560px,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-body{display:flex;min-height:0;flex-direction:column;gap:10px}.dsm-modal-list .dsm-modal-body{flex:1;overflow:auto;overscroll-behavior:contain}.dsm-modal-import{padding:24px}.dsm-modal-head{display:flex;align-items:flex-start;gap:12px}.dsm-modal-title{margin:0;flex:1;font-size:17px;line-height:24px;font-weight:670}.dsm-form,.dsm-field,.dsm-detail-section{display:flex;flex-direction:column}.dsm-form{gap:12px}.dsm-field{gap:6px}.dsm-label,.dsm-detail-title{font-size:12px}.dsm-label{color:var(--dsw-alias-label-secondary)}.dsm-detail-title{font-weight:650}.dsm-help{margin:0;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px}.dsm-modal-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px}.dsm-hidden-input{display:none}.dsm-dropzone{box-sizing:border-box;display:flex;width:100%;min-height:170px;flex-direction:column;align-items:center;justify-content:center;gap:7px;padding:22px;border:1px dashed var(--dsw-alias-border-l3);border-radius:12px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);transition:border-color 180ms ease,background 180ms ease;cursor:pointer}.dsm-dropzone:hover,.dsm-dropzone-active{border-color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsm-dropzone-title{color:var(--dsw-alias-label-primary);font-size:18px;font-weight:700}.dsm-dropzone-copy{font-size:12px;line-height:18px;text-align:center}.dsm-upload-choices{display:flex;align-items:center;gap:7px}.dsm-upload-link,.dsm-file-remove{padding:0;border:0;background:transparent;font:inherit;font-size:12px;cursor:pointer}.dsm-upload-link{color:var(--dsw-alias-label-secondary)}.dsm-upload-link:hover{color:var(--dsw-alias-label-primary);text-decoration:underline}.dsm-upload-link:disabled{opacity:.45;cursor:default}.dsm-upload-divider{color:var(--dsw-alias-label-tertiary);font-size:11px}.dsm-file{display:flex;align-items:center;gap:9px;padding:10px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:12px}.dsm-file-kind{display:inline-flex;min-width:30px;height:24px;align-items:center;justify-content:center;border-radius:5px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font-size:9px;font-weight:700}.dsm-file-name{min-width:0;overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}.dsm-file-meta{color:var(--dsw-alias-label-tertiary);font-size:11px;white-space:nowrap}.dsm-file-remove{width:24px;height:24px;color:var(--dsw-alias-label-secondary);font-size:18px}.dsm-upload-requirements{padding:1px 1px 0}.dsm-upload-requirements ul{display:flex;margin:7px 0 0;padding-left:18px;flex-direction:column;gap:5px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}.dsm-detail-section{gap:7px}.dsm-detail-path,.dsm-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px}.dsm-detail-path{padding:8px 10px;border-radius:7px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);word-break:break-all}.dsm-code{max-height:280px;margin:0;padding:12px;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);line-height:18px;white-space:pre-wrap}.dsm-diag{padding:8px 10px;border-left:2px solid #d49245;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:12px}.dsm-trash-group-sub{margin:0;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
55
+ .dsm-modal-title-row{display:inline-flex;align-items:center;gap:9px;min-width:0}
56
+ .dsm-pill{display:inline-flex;min-height:20px;align-items:center;padding:0 8px;border:1px solid var(--dsw-alias-border-l3);border-radius:999px;font-size:11px;font-weight:500;white-space:nowrap}
57
+ .dsm-pill-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}
58
+ .dsm-pill-bad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
59
+ .dsm-pill-warn{border-color:#d49245;color:#d49245}
60
+ .dsm-pill-muted{color:var(--dsw-alias-label-tertiary)}
61
+ .dsm-tool-desc-wrap{display:flex;align-items:flex-end;gap:8px}
62
+ .dsm-tool-desc-clamp{display:-webkit-box;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:2}
63
+ .dsm-tool-desc-toggle{flex:none;margin-left:auto;padding:0;border:0;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;cursor:pointer}
64
+ .dsm-tool-desc-toggle:hover{color:var(--dsw-alias-label-primary);text-decoration:underline}
52
65
  .dsm-trash-group{display:flex;flex-direction:column;overflow:hidden;margin-bottom:12px;border:1px solid var(--dsw-alias-border-l2);border-left-width:3px;border-radius:10px;background:var(--dsw-alias-bg-layer-2)}
53
66
  /* 「这组动的是磁盘文件」用左侧色条区分:中性 = 文件不动;危险色 = 真的会删文件。 */
54
67
  .dsm-trash-group-danger{border-left-color:var(--dsw-alias-state-error-primary)}
@@ -76,8 +89,8 @@ window.__ModuleLoader__.load({
76
89
  .dsm-modal-list .dsm-modal-body>*{flex:none}
77
90
  .dsm-fm{display:flex;flex-direction:column;overflow:hidden;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}.dsm-fm-row{display:grid;grid-template-columns:minmax(96px,150px) minmax(0,1fr);gap:10px;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:5px;color:var(--dsw-alias-label-tertiary);font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:10px}.dsm-fm-val{min-width:0;color:var(--dsw-alias-label-primary);word-break:break-word;white-space:pre-wrap}
78
91
  @container(max-width:780px){.dsm-table-head{display:none}.dsm-row{grid-template-columns:minmax(0,1fr) max-content;gap:8px;padding:11px 13px}.dsm-row>.dsm-tags,.dsm-row>.dsm-status{grid-column:1}.dsm-row-actions{grid-column:2;grid-row:1 / span 3}.dsm-path{display:none}}@media(max-width:760px){.dsm-summary{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:720px){.dsm-title-row{flex-wrap:wrap}}@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%}.dsm-summary{grid-template-columns:1fr}}
79
- .dsm-hist-row{display:flex;align-items:center;gap:11px;padding:10px 13px;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-hist-row:last-child{border-bottom:0}.dsm-hist-main{min-width:0;flex:1}.dsm-hist-title{overflow:hidden;font-size:13px;font-weight:580;text-overflow:ellipsis;white-space:nowrap}.dsm-hist-cwd{overflow:hidden;margin-top:2px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:17px;text-overflow:ellipsis;white-space:nowrap}.dsm-hist-cwd-missing{color:var(--dsw-alias-state-error-primary)}.dsm-hist-actions{display:flex;gap:6px;flex:none}.dsm-hist-check,.dsm-hist-group-check{flex:none;width:15px;height:15px;margin:0 4px 0 0;cursor:pointer;accent-color:var(--dsw-alias-state-success-primary)}.dsm-hist-group-head{gap:10px}.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-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{display:flex;align-items:center;gap:8px;margin-left:2px;padding-left:10px;border-left:1px solid var(--dsw-alias-border-l2)}.dsm-hist-batch-count{color:var(--dsw-alias-label-secondary);font-size:12px;white-space:nowrap}.dsm-hist-batch-actions{display:flex;align-items:center;flex-wrap:wrap;gap:8px;flex:none}.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-l1);border-radius:8px;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}
80
- .dsm-rule-shadowed .dsm-name,.dsm-rule-shadowed .dsm-note{color:var(--dsw-alias-state-error-primary)}.dsm-rule-shadow-hint{margin-top:3px;color:var(--dsw-alias-state-error-primary);font-size:11px;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:11px;line-height:17px}.dsm-rule-budget{display:flex;flex-direction:column;gap:6px}.dsm-budget-meta{display:flex;align-items:baseline;justify-content:space-between;gap:8px}.dsm-budget-meta strong{margin-right:0;font-size:13px;font-weight:680}.dsm-budget-over-text{color:var(--dsw-alias-state-error-primary);font-size:11px}.dsm-budget-bar{height:6px;overflow:hidden;border-radius:99px;background:var(--dsw-alias-interactive-bg-hover)}.dsm-budget-fill{height:100%;border-radius:99px;background:var(--dsw-alias-state-success-primary);transition:width 160ms ease}.dsm-budget-fill.dsm-budget-over{background:var(--dsw-alias-state-error-primary)}.dsm-char-count{color:var(--dsw-alias-label-tertiary);font-size:11px}.dsm-char-over{color:var(--dsw-alias-state-error-primary)}
92
+ .dsm-hist-row{display:flex;align-items:center;gap:11px;padding:10px 13px;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-hist-row:last-child{border-bottom:0}.dsm-hist-main{min-width:0;flex:1}.dsm-hist-title{overflow:hidden;font-size:13px;font-weight:580;text-overflow:ellipsis;white-space:nowrap}.dsm-hist-cwd{overflow:hidden;margin-top:2px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:17px;text-overflow:ellipsis;white-space:nowrap}.dsm-hist-cwd-missing{color:var(--dsw-alias-state-error-primary)}.dsm-hist-actions{display:flex;gap:6px;flex:none}.dsm-hist-check,.dsm-hist-group-check{flex:none;width:15px;height:15px;margin:0 4px 0 0;cursor:pointer;accent-color:var(--dsw-alias-state-success-primary)}.dsm-hist-group-head{gap:10px}.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;color:var(--dsw-alias-state-error-primary);font-size:11px;white-space:nowrap}.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{display:flex;align-items:center;gap:8px;margin-left:2px;padding-left:10px;border-left:1px solid var(--dsw-alias-border-l2)}.dsm-hist-batch-count{color:var(--dsw-alias-label-secondary);font-size:12px;white-space:nowrap}.dsm-hist-batch-actions{display:flex;align-items:center;flex-wrap:wrap;gap:8px;flex:none}.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-l1);border-radius:8px;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}
93
+ .dsm-rule-shadowed .dsm-name,.dsm-rule-shadowed .dsm-note{color:var(--dsw-alias-state-error-primary)}.dsm-rule-off .dsm-name,.dsm-rule-off .dsm-note{color:var(--dsw-alias-label-tertiary)}.dsm-rule-shadow-hint{margin-top:3px;color:var(--dsw-alias-state-error-primary);font-size:11px;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:11px;line-height:17px}.dsm-rule-budget{display:flex;flex-direction:column;gap:6px}.dsm-budget-meta{display:flex;align-items:baseline;justify-content:space-between;gap:8px}.dsm-budget-meta strong{margin-right:0;font-size:13px;font-weight:680}.dsm-budget-over-text{color:var(--dsw-alias-state-error-primary);font-size:11px}.dsm-budget-bar{height:6px;overflow:hidden;border-radius:99px;background:var(--dsw-alias-interactive-bg-hover)}.dsm-budget-fill{height:100%;border-radius:99px;background:var(--dsw-alias-state-success-primary);transition:width 160ms ease}.dsm-budget-fill.dsm-budget-over{background:var(--dsw-alias-state-error-primary)}.dsm-char-count{color:var(--dsw-alias-label-tertiary);font-size:11px}.dsm-char-over{color:var(--dsw-alias-state-error-primary)}
81
94
  .dsm-combo-row{display:flex;gap:8px}.dsm-combo-row .dsm-control{flex:1}
82
95
  .dsm-attach-list{display:flex;flex-direction:column;gap:6px}.dsm-attach-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
83
96
  .dsm-toast{position:fixed;right:22px;bottom:22px;z-index:1250;box-sizing:border-box;max-width:min(420px,calc(100vw - 44px));padding:10px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-3);box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;animation:dsm-toast-in 160ms ease-out}@keyframes dsm-toast-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.dsm-toast{animation:none}}
@@ -93,7 +106,7 @@ window.__ModuleLoader__.load({
93
106
  /* 滚动容器里的子项不许被压缩:否则内容一多,筛选框会被压到比 input 还矮、与下面的条目叠在一起。 */
94
107
  .dsm-seg-body>*{flex:none}
95
108
  .dsm-seg-foot{display:flex;align-items:center;gap:8px;padding:7px 12px;border-top:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
96
- .dsm-pick{display:flex;align-items:center;gap:9px;padding:6px 8px;border-radius:7px}
109
+ .dsm-pick{display:flex;align-items:center;gap:9px;padding:6px 8px;border-radius:7px}.dsm-mcp-edit-tools-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.dsm-mcp-edit-tools-actions{display:flex;align-items:center;gap:8px}.dsm-seg-empty-actions{display:flex;justify-content:center;padding:4px 0 12px}.dsm-seg-empty-error{padding:0 10px 10px;color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:16px;text-align:center}
97
110
  .dsm-pick:hover{background:var(--dsw-alias-interactive-bg-hover)}
98
111
  .dsm-pick input[type=checkbox]{flex:none;width:15px;height:15px;margin:0;cursor:pointer;accent-color:var(--dsw-alias-state-success-primary)}
99
112
  /* .dsm-pick-main 必须是**纵向 flex**:它原本只是个行内 span,名称与说明会在同一行里内联排布,
@@ -163,6 +176,11 @@ window.__ModuleLoader__.load({
163
176
  .dsm-mode-row{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}
164
177
  .dsm-mode-row.dsm-mode-on{border-color:var(--dsw-alias-border-l3)}
165
178
  .dsm-mode-head{display:flex;min-height:38px;align-items:center;gap:9px;padding:0 10px}
179
+ /* 行首是按钮:已配好的模式点它就展开/收起名单(右侧两个按钮只管启用/停用,两者别混)。 */
180
+ .dsm-mode-head-main{display:flex;min-width:0;flex:1;align-items:center;gap:9px;padding:0;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}
181
+ .dsm-mode-head-main:disabled{cursor:default}
182
+ .dsm-mode-head-main:not(:disabled):hover .dsm-mode-name{color:var(--dsw-alias-label-primary)}
183
+ .dsm-mode-caret{flex:none;width:10px;color:var(--dsw-alias-label-tertiary);font-size:10px}
166
184
  .dsm-mode-name{font-size:12px;font-weight:570;white-space:nowrap}
167
185
  .dsm-mode-sum{color:var(--dsw-alias-label-tertiary);font-size:11px}
168
186
  .dsm-mode-sum.dsm-mode-allow{color:var(--dsw-alias-state-success-primary)}
@@ -206,6 +224,11 @@ window.__ModuleLoader__.load({
206
224
  .dsm-compat-pill-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}
207
225
  .dsm-compat-pill-warn{border-color:#d49245;color:#d49245}
208
226
  .dsm-compat-reach-marks{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:6px;min-width:0}
227
+ .dsm-inject-settings{display:flex;flex-direction:column;gap:8px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}
228
+ /* 注入域勾选:横排、窄容器自动换行;标签一律短名(记忆 / MCP / …),不截断也不竖排。 */
229
+ .dsm-inject-domains{display:flex;flex-wrap:wrap;gap:7px 18px;padding:0 2px}
230
+ .dsm-inject-domain{display:inline-flex;align-items:center;gap:7px;color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;white-space:nowrap;cursor:pointer}
231
+ .dsm-inject-domain input[type=checkbox]{flex:none;width:15px;height:15px;margin:0;cursor:pointer;accent-color:var(--dsw-alias-state-success-primary)}
209
232
  .dsm-reach-name{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
210
233
  .dsm-reach-chip{display:inline-flex;min-height:22px;align-items:center;padding:0 9px;border:1px solid var(--dsw-alias-border-l3);border-radius:6px;color:var(--dsw-alias-label-secondary);font-size:11px;white-space:nowrap}
211
234
  .dsm-reach-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}
@@ -338,6 +361,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
338
361
  const t = props.t
339
362
  const [data, setData] = React.useState(null)
340
363
  const [presetReach, setPresetReach] = React.useState(null)
364
+ // 注入设置(本插件五个注入域的开关;见 src/context-inject.ts)。它和可达性矩阵是一体两面:
365
+ // 矩阵说"到不到得了",这里决定"要不要"。读不到时这一节不显示。
366
+ const [inject, setInject] = React.useState(null)
341
367
  const [busy, setBusy] = React.useState(false)
342
368
  const [error, setError] = React.useState(null)
343
369
  const apply = function (r, alive) {
@@ -356,6 +382,11 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
356
382
  .then(function (r) { applyReach(r, alive) })
357
383
  .catch(function () { /* 未挂载预设服务 → 不渲染这一节 */ })
358
384
  }
385
+ const loadInject = function (alive) {
386
+ apiCall('inject-settings', {})
387
+ .then(function (r) { if (alive !== false && r && r.ok && r.settings) setInject(r.settings) })
388
+ .catch(function () { /* 读不到 → 不渲染这一节 */ })
389
+ }
359
390
  React.useEffect(function () {
360
391
  let alive = true
361
392
  setBusy(true)
@@ -363,6 +394,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
363
394
  .then(function (r) { apply(r, alive) })
364
395
  .catch(function (e) { if (alive) { setError(String((e && e.message) || e)); setBusy(false) } })
365
396
  loadReach(alive)
397
+ loadInject(alive)
366
398
  return function () { alive = false }
367
399
  }, [])
368
400
  const reload = function () {
@@ -371,6 +403,20 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
371
403
  .then(function (r) { apply(r, true) })
372
404
  .catch(function (e) { setError(String((e && e.message) || e)); setBusy(false) })
373
405
  loadReach(true)
406
+ loadInject(true)
407
+ }
408
+ // 保存注入设置:先乐观更新界面(点一下就该有反馈),服务端返回值到达后以其为准;
409
+ // 失败则重新拉一次。可达性矩阵跟着刷新 —— 芯片要反映新的开关状态。
410
+ const saveInject = function (patch) {
411
+ if (!inject) return
412
+ const next = Object.assign({}, inject, patch)
413
+ setInject(next)
414
+ apiCall('inject-settings', Object.assign({ set: true }, next))
415
+ .then(function (r) {
416
+ if (r && r.ok && r.settings) setInject(r.settings)
417
+ loadReach(true)
418
+ })
419
+ .catch(function () { loadInject(true); loadReach(true) })
374
420
  }
375
421
 
376
422
  const findings = (data && data.findings) || []
@@ -414,6 +460,34 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
414
460
  const push = function (node) {
415
461
  body.push(React.cloneElement(node, { key: 'compat-' + String(body.length) }))
416
462
  }
463
+ // 注入设置块(就放在刷新按钮下面):本插件注入给模型哪些内容的开关。总开关只管
464
+ // "极简这类预设下要不要破例",五个域勾选在任何预设下都生效。
465
+ //
466
+ // 排版口径(用户裁定 2026-09-16):勾选**横排**(短标签一行放得下,不再一人一行竖着排),
467
+ // 文案只说到"有这个内容"为止 —— 用户是普通使用者,不解释 persona/载体/路径这些内部机制。
468
+ if (inject) {
469
+ const domains = (inject && inject.domains) || {}
470
+ push(section(t('compat.inject'), t('compat.inject.hint'),
471
+ React.createElement('div', { className: 'dsm-inject-settings' },
472
+ React.createElement('label', { className: 'dsm-pick' },
473
+ React.createElement('input', { type: 'checkbox', checked: inject.underSuppressingPresets === true, onChange: function () { saveInject({ underSuppressingPresets: inject.underSuppressingPresets !== true }) } }),
474
+ React.createElement('span', { className: 'dsm-pick-main' },
475
+ React.createElement('span', { className: 'dsm-pick-name' }, t('compat.inject.force')),
476
+ React.createElement('span', { className: 'dsm-pick-desc' }, t('compat.inject.force.desc')))),
477
+ React.createElement('div', { className: 'dsm-seg-group' },
478
+ React.createElement('span', null, t('compat.inject.domains')),
479
+ React.createElement('span', { className: 'dsm-seg-group-line' })),
480
+ React.createElement('div', { className: 'dsm-inject-domains' },
481
+ ['memory', 'mcp', 'skills', 'subagents', 'prompt'].map(function (key) {
482
+ return React.createElement('label', { className: 'dsm-inject-domain', key: 'domain-' + key },
483
+ React.createElement('input', { type: 'checkbox', checked: domains[key] !== false, onChange: function () {
484
+ const nextDomains = Object.assign({}, domains)
485
+ nextDomains[key] = domains[key] === false
486
+ saveInject({ domains: nextDomains })
487
+ } }),
488
+ React.createElement('span', null, t('compat.inject.domain.' + key)))
489
+ })))))
490
+ }
417
491
  if (error) body.push(React.createElement('div', { className: 'dsm-feedback dsm-error' }, t('compat.failed') + ': ' + error))
418
492
  if (!busy && !data && !error) body.push(React.createElement('div', { className: 'dsm-empty' }, t('compat.empty')))
419
493
 
@@ -521,10 +595,11 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
521
595
  React.createElement('code', { className: 'dsm-compat-code' }, ' node scripts/doctor.mjs')))
522
596
  }
523
597
 
524
- // 预设注入边界:五类能力(记忆 / 提示词 / 技能 / MCP / 子智能体)在**每个 Agent
525
- // 预设**下到不到得了模型。回答的是"面板标着已注入,模型真的看得到吗"——官方
526
- // minimal 预设的 persona 是 complete,会把除自己以外的提示词段全部压掉,而且它
527
- // 连官方 subagent 工具都没挂。单独一块:取不到时这一节不显示,不影响上面的结论。
598
+ // 预设注入边界:五类内容(记忆 / 全局提示词 / 技能 / MCP / 子智能体)在**每个 Agent
599
+ // 预设**下到不到得了模型。回答的是"面板标着已注入,模型真的看得到吗"——本插件的
600
+ // 注入由它自己在每步发出(极简这类预设下默认不发,可用上面的「注入」块强制),
601
+ // 而全局提示词与技能目录还要看官方那两条行挂没挂(极简都没挂)。单独一块:取不到时
602
+ // 这一节不显示,不影响上面的结论。
528
603
  //
529
604
  // 排版口径(用户裁定 2026-09-15):每格一枚描边标签,**绿框=能用、红框=不能用、
530
605
  // 灰框=判断不了**;不再出现 ✓/✗ 符号,也不再有任何整行的解释文字 —— 预设损坏与
@@ -540,21 +615,31 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
540
615
  return React.createElement('span', { className: 'dsm-reach-chip dsm-reach-' + kind, key: label }, label)
541
616
  }
542
617
  const tags = []
618
+ if (row.suppressing) tags.push(React.createElement('span', { className: 'dsm-tag dsm-tag-off', key: 'suppressing' }, t('compat.reach.suppressing')))
543
619
  if (row.broken) tags.push(React.createElement('span', { className: 'dsm-tag dsm-tag-off', key: 'broken' }, t('compat.reach.broken')))
544
620
  else if (row.reason) tags.push(React.createElement('span', { className: 'dsm-tag', key: 'reason' }, t('compat.reach.whyUnknown')))
545
621
  return React.createElement('div', { className: 'dsm-compat-mod-row', key: row.presetId },
546
622
  React.createElement('span', { className: 'dsm-compat-name dsm-reach-name' },
547
623
  React.createElement('span', null, (row.name || row.presetId) + (row.isDefault ? ' · ' + t('compat.reach.default') : '')),
548
624
  tags.length ? React.createElement('span', { className: 'dsm-tags' }, tags) : null),
625
+ // 芯片顺序与「注入」块的勾选顺序一致(用户裁定 2026-09-16):
626
+ // 场景和记忆 → MCP → 技能 → 子智能体 → 提示词。
549
627
  React.createElement('span', { className: 'dsm-compat-reach-marks' },
550
628
  chip(t('compat.reach.mark.memory'), row.memory),
551
- chip(t('compat.reach.mark.agentsMd'), row.agentsMd),
552
- chip(t('compat.reach.mark.skill'), row.skillCatalog),
553
629
  chip(t('compat.reach.mark.mcp'), row.mcp),
554
- chip(t('compat.reach.mark.subagent'), row.subagent)))
630
+ chip(t('compat.reach.mark.skill'), row.skillCatalog),
631
+ chip(t('compat.reach.mark.subagent'), row.subagent),
632
+ chip(t('compat.reach.mark.agentsMd'), row.agentsMd)))
555
633
  }))))
556
634
 
557
- push(React.createElement('p', { className: 'dsm-help' }, t('compat.reach.tools')))
635
+ // 页脚用「标题 + 条目」清单,与导入弹窗的「文件要求」同款结构与样式(用户裁定
636
+ // 2026-09-16 第二版:条目要短、不列路径与内部机制,使用者知道"有这回事"就够)。
637
+ push(React.createElement('div', { className: 'dsm-upload-requirements' },
638
+ React.createElement('div', { className: 'dsm-label' }, t('compat.reach.tools.title')),
639
+ React.createElement('ul', null,
640
+ [1, 2, 3].map(function (n) {
641
+ return React.createElement('li', { key: n }, t('compat.reach.tools.' + n))
642
+ }))))
558
643
  }
559
644
 
560
645
  return React.createElement('div', { className: 'dsm-compat' },
@@ -580,7 +665,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
580
665
  const DICT = {
581
666
  zh: {
582
667
  "title": "技能", "desc": "管理技能:启停、导入、创建、回收站。", "link.project": "GitHub", "link.feedback": "问题反馈",
583
- "btn.create": "新增技能", "btn.import": "导入技能", "btn.refresh": "刷新", "btn.refreshing": "刷新中…", "btn.cancel": "取消", "btn.close": "关闭", "export.hint": "导出为 zip,写入你指定的目录;只读源文件,不改动任何数据。", "export.outDir": "导出目录", "export.outDir.placeholder": "例如 D:////backup", "export.pickDir": "选择导出目录", "export.submit": "导出", "export.busy": "导出中…", "export.done": "已导出 {count} 个文件到 {path}", "scenes.lock.lock": "锁定", "scenes.lock.unlock": "解锁", "scenes.lock.tag": "已锁定", "scenes.lock.hint": "锁定后五个管理域(MCP / 技能 / 子智能体 / 记忆 / 提示词)整体只读;场景启停不受影响", "scenes.lock.blockedEdit": "场景已锁定:先解锁再修改", "scenes.lock.notActive": "场景未启动:先启动再锁定", "scenes.lock.blockedExit": "场景已锁定:先解锁再关闭", "lock.banner": "有场景处于锁定状态:MCP / 技能 / 子智能体 / 记忆 / 提示词已整体冻结,先到场景页解锁再修改。", "export.empty": "没有可导出的条目", "export.pickAll": "全选", "export.pickNone": "全不选", "export.group.count": "{count} 项", "export.missing": ";跳过 {count} 项(文件不存在):{names}", "export.skills": "导出技能", "export.subagents": "导出子智能体", "export.presets": "导出提示词", "export.memories": "导出记忆", "btn.detail": "查看详情", "btn.trash": "移到回收站", "btn.restore": "恢复", "btn.delete.forever": "永久删除", "btn.file.pick": "选择文件", "btn.folder.pick": "选择文件夹", "btn.create.now": "创建技能", "btn.disable": "停用", "btn.enable": "启用", "btn.open.editor": "用系统编辑器打开", "btn.activate": "启用这个", "btn.activate.title": "把当前来源的版本设为同名技能的首选并启用(不改动任何源文件)", "btn.unprefer": "取消首选", "btn.unprefer.title": "取消同名首选,回到按来源优先级自动选择",
668
+ "btn.create": "新增技能", "btn.import": "导入技能", "btn.refresh": "刷新", "btn.refreshing": "刷新中…", "btn.cancel": "取消", "btn.close": "关闭", "export.hint": "导出为 zip,写入你指定的目录;只读源文件,不改动任何数据。", "export.outDir": "导出目录", "export.outDir.placeholder": "例如 D:////backup", "export.pickDir": "选择导出目录", "export.submit": "导出", "export.busy": "导出中…", "export.done": "已导出 {count} 个文件到 {path}", "scenes.lock.lock": "锁定", "scenes.lock.unlock": "解锁", "scenes.lock.tag": "已锁定", "scenes.lock.hint": "锁定后五个管理域(MCP / 技能 / 子智能体 / 记忆 / 提示词)整体只读;场景启停不受影响", "scenes.lock.blockedEdit": "场景已锁定:先解锁再修改", "scenes.lock.notActive": "场景未启动:先启动再锁定", "scenes.lock.blockedExit": "场景已锁定:先解锁再关闭", "lock.banner": "有场景处于锁定状态:MCP / 技能 / 子智能体 / 记忆 / 提示词已整体冻结,先到场景页解锁再修改。", "scene.switch.banner": "当前处于场景「{scene}」:开关由场景档案定义,页面上的开关已停用——到场景页的档案编辑器里改(改完立即生效,档案会存下来、下次进这个场景照旧生效;退出场景仍按进场景前的状态还原),或先退出场景。", "scene.switch.title": "场景「{scene}」中:开关请到场景档案里改", "export.empty": "没有可导出的条目", "export.pickAll": "全选", "export.pickNone": "全不选", "export.group.count": "{count} 项", "export.missing": ";跳过 {count} 项(文件不存在):{names}", "export.skills": "导出技能", "export.subagents": "导出子智能体", "export.presets": "导出提示词", "export.memories": "导出记忆", "btn.detail": "查看详情", "btn.trash": "移到回收站", "btn.restore": "恢复", "btn.delete.forever": "永久删除", "btn.file.pick": "选择文件", "btn.folder.pick": "选择文件夹", "btn.create.now": "创建技能", "btn.disable": "停用", "btn.enable": "启用", "btn.open.editor": "用系统编辑器打开", "btn.activate": "启用这个", "btn.activate.title": "把当前来源的版本设为同名技能的首选并启用(不改动任何源文件)", "btn.unprefer": "取消首选", "btn.unprefer.title": "取消同名首选,回到按来源优先级自动选择",
584
669
  "btn.custom.add": "添加目录", "btn.custom.remove": "移除", "btn.openDir": "选择",
585
670
  "btn.source.remove": "移除来源", "btn.source.remove.title": "不再读取这个来源(源文件不动,之后可在回收站里恢复)", "btn.source.restore": "恢复", "btn.source.forget": "永久删除",
586
671
  "trash.section.dirs": "目录", "trash.section.skills": "技能", "trash.section.dirs.sub": "只是不再读取,磁盘文件一个字节都不动", "trash.section.dirs.empty": "没有被移除的来源", "trash.section.skills.sub": "文件真的被移走了:恢复能还原,永久删除会删掉文件", "trash.section.presets.sub": "预设目录已移走:恢复能还原,永久删除会删掉文件", "trash.section.scenes.sub": "场景记录、档案与它的记忆都在这里:恢复会整条放回", "trash.section.agents.sub": "人设文件已移走:恢复能还原,永久删除会删掉文件",
@@ -606,7 +691,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
606
691
  "empty.search": "没有匹配的技能", "empty.source": "该来源不存在或暂无技能", "loading": "正在加载技能…", "note.missing": "未提供简介", "source.toggle": "启停来源", "skill.toggle": "启停技能",
607
692
  "source.external.note": "只读接入,启停不改写源文件", "source.dsh.note": "可创建、导入、移到回收站;默认来源必须读取,不能移除或停用",
608
693
  "detail.title": "技能详情", "detail.body": "正文", "detail.frontmatter": "元数据", "detail.noFrontmatter": "该技能未提供元数据。", "detail.diagnostics": "诊断", "detail.path": "源文件", "detail.noIssues": "未发现诊断问题。",
609
- "create.title": "创建技能", "create.target": "创建位置", "create.name": "名称", "create.name.placeholder": "例如 code-review-helper", "create.description": "简介", "create.description.placeholder": "一句话说明什么时候使用", "create.body": "正文(Markdown)", "create.body.placeholder": "写下技能要遵循的指令、步骤和边界…", "create.chat.note": "对话里 create_skill 建的是用户级技能",
694
+ "create.title": "创建技能", "create.target": "创建位置", "create.name": "名称", "create.name.placeholder": "例如 code-review-helper", "create.description": "简介", "create.description.placeholder": "一句话说明什么时候使用", "create.body": "正文(Markdown)", "create.body.placeholder": "写下技能要遵循的指令、步骤和边界…", "create.chat.note": "对话里 skill_manager_create 建的是用户级技能",
610
695
  "import.title": "导入技能", "upload.drop.title": "点击或拖入此处", "upload.drop.copy": "支持 .zip、技能文件夹或单个 SKILL.md", "upload.selected.one": "{count} 个文件 · {size}", "upload.selected.other": "{count} 个文件 · {size}", "upload.remove": "移除所选内容", "upload.requirements": "文件要求", "upload.requirement.skill": "压缩包或文件夹需包含 SKILL.md", "upload.requirement.frontmatter": "SKILL.md 需包含 YAML 格式的技能名称和描述", "upload.requirement.copy": "导入时复制完整内容,不修改原始来源", "upload.requirement.persona.1": "支持 .md / .zip(可多选、可拖入)", "upload.requirement.persona.2": "一个 .md = 一个人设,文件名即人设名", "upload.requirement.persona.3": "同名自动跳过,绝不覆盖原有文件", "upload.requirement.memory.1": "支持 .md / .zip(可多选、可拖入)", "upload.requirement.memory.2": "一个 .md = 一条记忆,文件名即记忆名;带 SKILL.md 的目录 = bundle 记忆(附件一起进来)", "upload.requirement.memory.3": "zip 里的目录名即场景;没有目录时用下面「导入到场景」的值(留空 = 全局)", "upload.requirement.prompt.1": "支持 .md(可多选、可拖入)", "upload.requirement.prompt.2": "一个 .md = 一份预设,文件名即 id(目录名)", "upload.requirement.prompt.3": "同名已存在则跳过,绝不覆盖", "upload.requirement.session.1": "支持 .jsonl / .json / .md / .txt(一次一个)", "upload.requirement.session.2": "Claude Code / Cursor / Codex 的转录文件,或任意文本", "upload.requirement.session.3": "项目目录可留空 = 归入未分组;留空不影响导入", "upload.importing": "正在导入…", "status.selected": "已选择", "select.file.invalid": "请选择 .zip 或单个 SKILL.md", "select.folder.invalid": "该文件夹里没有 SKILL.md", "error.browse.absolute": "目录路径必须是绝对路径:{path}", "error.browse.unreadable": "无法读取目录:{path}", "error.browse.notDirectory": "不是目录:{path}",
611
696
  "trash.title": "回收站", "trash.count.one": "{count} 个待处理技能", "trash.count.other": "{count} 个待处理技能", "trash.empty": "回收站为空", "trash.deletedAt": "删除于 {time}", "trash.source": "来源:{source}",
612
697
  "trash.btn.open": "回收站", "trash.items.count": "{count} 项", "trash.purge.confirm": "永久删除?",
@@ -632,9 +717,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
632
717
  "mcp.btn.new": "新增 MCP", "mcp.btn.add": "添加", "mcp.btn.save": "保存", "mcp.btn.detail": "详情", "mcp.btn.edit": "编辑", "mcp.btn.restart": "重启", "mcp.btn.remove": "删除",
633
718
  "mcp.btn.enableAll": "全部启用", "mcp.btn.disableAll": "全部停用",
634
719
  "mcp.loading": "正在加载 MCP 服务…", "mcp.empty": "暂无 MCP,点「新增 MCP」添加", "mcp.empty.search": "没有匹配的服务。",
635
- "mcp.servers.count": "{count} 个服务", "mcp.tools.count": "{count} 个工具", "mcp.duplicate": "重复 id",
720
+ "mcp.servers.count": "{count} 个服务", "mcp.tools.count": "{count} 个工具", "mcp.tools.countPartial": "{enabled}/{total} 个工具", "mcp.duplicate": "重复 id",
636
721
  "mcp.table.name": "服务名称与地址", "mcp.table.transport": "传输与工具", "mcp.table.status": "运行状态",
637
- "mcp.live.notLoaded": "未加载", "mcp.live.failed": "启动失败", "mcp.live.stopped": "未运行", "mcp.live.loading": "加载中", "mcp.live.noTools": "无工具", "mcp.live.running": "运行中",
722
+ "mcp.live.notLoaded": "未加载", "mcp.live.failed": "启动失败", "mcp.live.stopped": "未运行", "mcp.live.loading": "加载中", "mcp.live.noTools": "无工具", "mcp.live.running": "已运行",
638
723
  "mcp.live.failedHint": "启动失败,检查配置后点「重启」重试", "mcp.live.noToolsHint": "已连接但没有工具:服务端可能未就绪",
639
724
  "mcp.note.prefix": "备注:", "mcp.toggleServer": "启停服务", "mcp.toggleTool": "启停工具",
640
725
  "mcp.msg.ok": "操作成功", "mcp.msg.failed": "操作失败", "mcp.msg.loadFailed": "加载失败", "mcp.msg.warn": "操作完成,但加载器有提示:{warning}",
@@ -653,8 +738,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
653
738
  "mcp.btn.compact": "整理补丁", "mcp.btn.compact.title": "清掉补丁文件里已经不起作用的旧开关记录(每台服务的启用/停用状态不变)",
654
739
  "mcp.compact.title": "整理补丁文件", "mcp.compact.desc": "配置里会积累一些已经不起作用的旧开关记录(同一个服务被反复启用/停用留下的)。",
655
740
  "mcp.compact.confirm": "确认整理", "mcp.compact.done": "补丁文件已整理:清掉 {count} 条失效的旧开关记录,服务的启用/停用状态未变", "mcp.compact.clean": "补丁文件已经很干净,没有可清理的旧记录",
656
- "mcp.detail.entryId": "条目 ID", "mcp.detail.status": "运行状态", "mcp.detail.registered": "该服务已登记在 Loader 中。",
657
- "mcp.detail.note": "备注(会注入系统提示词,随请求发送给模型)", "mcp.detail.note.placeholder": "例如:A 不可用时改用 B 兜底", "mcp.detail.note.save": "保存备注",
741
+ "mcp.detail.entryId": "条目 ID", "mcp.detail.status": "运行状态", "mcp.detail.loader": "Loader 登记", "mcp.detail.loader.yes": "已登记", "mcp.detail.loader.no": "未登记",
742
+ "mcp.tools.stale": "上次运行时", "mcp.tools.expand": "展开", "mcp.tools.collapse": "收起",
743
+ "mcp.detail.note": "备注(随上下文注入发给模型;压制型预设下不注入时,用 mcp_manager_list 仍能读到)", "mcp.detail.note.placeholder": "例如:A 不可用时改用 B 兜底", "mcp.detail.note.save": "保存备注",
658
744
  "mcp.detail.tools": "工具({count})",
659
745
  "mcp.field.headersShort": "请求头", "mcp.field.envShort": "环境变量",
660
746
  "mcp.tools.loading": "正在获取工具列表…", "mcp.tools.loadFailed": "加载工具失败:", "mcp.tools.none": "该服务暂无已注册工具。",
@@ -682,11 +768,21 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
682
768
  "compat.fallback.native": "改走原生入口", "compat.fallback.blocked": "相关按钮已禁用",
683
769
  "compat.modules": "模块实体(插件 vs 宿主)", "compat.modules.hint": "同一份模块才谈得上适配", "compat.module.same": "同一份模块", "compat.module.separate": "两份拷贝(需修复)", "compat.module.unknown": "无法比较",
684
770
  "compat.blockers": "阻塞项(按此修复)",
685
- "compat.reach": "预设注入边界", "compat.reach.hint": "面板说已注入,模型真的看得到吗",
686
- "compat.reach.mark.memory": "记忆", "compat.reach.mark.agentsMd": "提示词", "compat.reach.mark.skill": "技能", "compat.reach.mark.mcp": "MCP", "compat.reach.mark.subagent": "子智能体",
771
+ "compat.reach": "预设注入边界", "compat.reach.hint": "每个预设下这些内容到不到得了模型",
772
+ "compat.reach.suppressing": "压制型",
773
+ "compat.inject": "注入", "compat.inject.hint": "本插件注入给模型的内容(不变不重发)",
774
+ "compat.inject.force": "极简模式也注入", "compat.inject.force.desc": "默认关(跟随预设)",
775
+ "compat.inject.domains": "注入内容",
776
+ "compat.inject.domain.memory": "场景和记忆", "compat.inject.domain.mcp": "MCP",
777
+ "compat.inject.domain.subagents": "子智能体", "compat.inject.domain.prompt": "提示词",
778
+ "compat.inject.domain.skills": "技能",
779
+ "compat.reach.mark.memory": "场景和记忆", "compat.reach.mark.agentsMd": "提示词", "compat.reach.mark.skill": "技能", "compat.reach.mark.mcp": "MCP", "compat.reach.mark.subagent": "子智能体",
687
780
  "compat.reach.default": "默认",
688
781
  "compat.reach.whyUnknown": "判断不了", "compat.reach.broken": "预设损坏",
689
- "compat.reach.tools": "本插件的模型工具(含 subagent_list / subagent_run)与 MCP 都挂在宿主平面,不受预设影响,任何预设下都可调用。",
782
+ "compat.reach.tools.title": "工具能用 ≠ 模型知道",
783
+ "compat.reach.tools.1": "工具由本插件注册,任何预设下都能调用;怎么送到模型由预设决定。",
784
+ "compat.reach.tools.2": "场景和记忆 / MCP / 技能 / 子智能体 / 提示词 —— 由本插件按上面的「注入」块补齐。",
785
+ "compat.reach.tools.3": "提示词与技能官方自己也会送;官方有的不重复送,官方缺的(如极简)由本插件补上。",
690
786
  "compat.hint.doctor": "命令行体检:",
691
787
  "scenes.title": "场景", "scenes.desc": "管理场景:预设 MCP、技能、子智能体与记忆的组合,一键切换。",
692
788
  "scenes.stat.total": "个场景", "scenes.stat.active": "个已启用", "scenes.stat.archives": "个有档案",
@@ -695,22 +791,22 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
695
791
  "scenes.empty": "还没有专门设置的场景;全局记忆不需要场景就能注入,需要切换 MCP / 技能 / 人设时再新建。",
696
792
  "scenes.field.desc.limit": "最多 {count} 字;超出部分在卡片上省略。",
697
793
  "scenes.profile.mcp": "MCP {count} 台", "scenes.profile.skills": "技能 {count} 个", "scenes.profile.subagents": "子智能体 {count} 个", "scenes.profile.memories": "记忆 {count} 条",
698
- "scenes.mcp.hint": "添加「MCP 工具集」后,勾选要启用的服务器(含未运行的);「选工具」可细化到具体工具。",
794
+ "scenes.mcp.hint": "添加「MCP 工具集」后,勾选要启用的服务器(含未运行的);「选工具」可细化到具体工具。没勾的、以及整段没建的,进场景时一律停用(含停掉正在运行的进程)。",
699
795
  "scenes.seg.selectAll": "全选", "scenes.seg.clear": "清空", "scenes.seg.checked": "{checked}/{total} 已勾选",
700
- "scenes.archive.sectionOff": "未定义",
796
+ "scenes.archive.sectionOff": "未定义 = 全部停用",
701
797
  "scenes.archive.summary": "档案:MCP {mcp} 台 · 技能 {skills} 个 · 子智能体 {subagents} 个",
702
- "scenes.archive.note": "段未定义 = 不改动该域;段已定义 = 按勾选集双向切换:勾的启用、没勾的停用。MCP 会真的启停服务器进程,技能会启停它所在的来源。",
798
+ "scenes.archive.note": "勾选集语义:勾的启用、没勾的停用;段未定义 = 一个都没勾 = 该域全部停用。MCP 会真的启停服务器进程,技能会启停它所在的来源。退出场景时按进场景前的状态精确还原。",
703
799
  "scenes.mcp.noServers": "还没有可选的 MCP 服务器", "scenes.mcp.toolCount": "{count} 个工具", "scenes.skills.empty": "还没有可选的技能",
704
- "scenes.mcp.allTools": "全部工具", "scenes.mcp.pickedCount": "指定 {count} 个工具", "scenes.mcp.notRunning": "未运行", "scenes.mcp.pickTools": "选工具",
705
- "scenes.mcp.toolsOf": "工具明细", "scenes.mcp.back": "返回", "scenes.mcp.noTools": "该服务器当前没有可列出的工具(未运行或无工具)", "scenes.mcp.drillHint": "勾选 = 该场景下启用;不勾 = 停用。整台勾选时默认全部工具。",
706
- "scenes.skills.hint": "添加「技能集」后,勾选该场景下启用的技能。", "scenes.subagents.hint": "添加「子智能体绑定」后,勾选本场景可调用的人设。", "scenes.subagents.empty": "还没有人设——到「子智能体」页创建。",
800
+ "scenes.mcp.allTools": "全部工具", "scenes.mcp.pickedCount": "指定 {count} 个工具", "scenes.mcp.notRunning": "未运行", "scenes.mcp.edit": "编辑", "scenes.mcp.done": "完成", "scenes.mcp.noteLabel": "场景备注", "scenes.mcp.notePlaceholder": "进入该场景时覆盖全局备注;退出后恢复", "scenes.mcp.hasNote": "有备注",
801
+ "scenes.mcp.toolsOf": "选择工具", "scenes.mcp.probeTools": "启动服务器读取工具", "scenes.mcp.unknownCount": "工具数未知 · 点「编辑」读取", "scenes.mcp.noteHint": "此备注随场景保存;仅在该场景本段**勾选**了这台服务器时,进入场景才会覆盖「MCP」页与系统提示词里的备注,退出后自动恢复。", "scenes.mcp.noTools": "该服务器当前没有可列出的工具(从未运行过,或已配置的工具尚未被记录)", "scenes.mcp.drillHint": "勾选 = 该场景下启用;不勾 = 停用。「全选」= 全部工具。",
802
+ "scenes.skills.hint": "添加「技能集」后,勾选该场景下启用的技能。没勾的、以及整段没建的,进场景时一律停用(退出按进场景前的开关还原)。", "scenes.subagents.hint": "勾选本场景要打开的人设;没勾的、以及整段没建的,进场景时一律关闭(退出按进场景前的开关还原)。", "scenes.subagents.empty": "还没有人设——到「子智能体」页创建。",
707
803
  "scenes.filter.skills": "筛选技能/目录名称", "scenes.filter.subagents": "筛选人设(名称或描述)", "scenes.filter.servers": "筛选服务器",
708
804
 
709
805
  "scenes.mem.title": "记忆", "scenes.mem.search": "在本场景内筛选记忆(名称或描述)",
710
806
  "scenes.mem.noMatch": "没有匹配项", "scenes.mem.emptyScene": "该场景还没有记忆",
711
807
  "memory.mode.current": "当前模式", "memory.mode.exit": "退出模式",
712
808
  "memory.archive.edit": "档案", "memory.archive.title": "场景档案",
713
- "memory.archive.tools": "MCP 工具集", "memory.archive.skills": "技能集", "memory.archive.subagents": "子智能体绑定", "memory.archive.memories": "记忆",
809
+ "memory.archive.tools": "MCP 工具集", "memory.archive.skills": "技能集", "memory.archive.subagents": "子智能体绑定", "memory.archive.memories": "记忆", "memory.archive.applyFailed": "运行时未能应用:{reason}",
714
810
  "memory.archive.removeSection": "移除段", "memory.archive.save": "保存到场景",
715
811
  "memory.archive.addTools": "+ 添加 MCP 工具集", "memory.archive.addSkills": "+ 添加技能集", "memory.archive.addSubagents": "+ 添加子智能体绑定",
716
812
  "memory.archive.stale": "失效项(已不存在,已跳过): {items}",
@@ -728,7 +824,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
728
824
  "memory.scene.enable": "启用", "memory.scene.enableAll": "全部启用", "memory.scene.off": "未启用", "memory.scene.shared": "常开", "memory.scene.global.tag": "常驻",
729
825
  "memory.scene.global": "全局", "memory.scene.count": "{count} 条记忆", "memory.scene.new": "新建记忆", "memory.scene.browse": "选择已有",
730
826
  "memory.scene.empty": "该场景暂无记忆",
731
- "memory.scene.orphan": "未归属场景", "memory.scene.orphan.tag": "不会注入", "memory.scene.orphan.hint": "这些记忆直接放在 memories/ 根层,没有归属场景,因此不会进入系统提示词。请把它们移入某个场景目录(或放到 memories/global/ 作为「全局」记忆)。",
827
+ "memory.scene.orphan": "未归属场景", "memory.scene.orphan.tag": "不会注入", "memory.scene.orphan.hint": "这些记忆直接放在 memories/ 根层,没有归属场景,因此不会被注入上下文。请把它们移入某个场景目录(或放到 memories/global/ 作为「全局」记忆)。",
732
828
  "memory.scene.edit": "改描述", "memory.scene.editTitle": "修改场景", "memory.scene.editHelp": "名称就是它的记忆目录名;改名会连目录、档案与绑定一起改,记忆正文不动。",
733
829
  "memory.scene.field.name": "场景名", "memory.scene.field.name.placeholder": "例如 办公",
734
830
  "memory.scene.field.name.hint": "名称就是它的一级目录名,之后仍可修改。",
@@ -761,8 +857,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
761
857
  "memory.deleteScene.title": "删除场景?", "memory.deleteScene.desc": "把「{name}」连同它下面全部记忆(含未启用的)一起移入回收站,可在回收站整条恢复;正在使用的场景不能删。",
762
858
  "memory.result.sceneCreated": "已创建场景:{name}", "memory.result.sceneUpdated": "已保存场景:{name}", "memory.result.sceneRemoved": "已删除场景:{name}", "memory.result.sceneRemoved.withMemories": "已删除场景:{name}(连同 {count} 项记忆,可在回收站整条恢复)",
763
859
  "memory.table.name": "记忆名称与描述", "memory.table.tags": "标记", "memory.table.status": "状态",
764
- "error.rules.invalidArgs": "缺少参数:需要 all:true(回到全部启用)或 scenes:[...](显式收窄)", "error.rules.invalidGroup": "场景/分组名不合法(非空、≤64 字符、不含路径分隔符与 < > : \" | ? *、不以 . 开头)", "error.rules.invalidName": "记忆名不合法(非空、≤64 字符、不含路径分隔符与 < > : \" | ? *、不以 . 开头)", "error.rules.descriptionRequired": "描述不能为空", "error.rules.descriptionTooLong": "描述过长(不能超过 500 字符)", "error.rules.bodyRequired": "正文不能为空", "error.rules.tooLarge": "规则内容过大", "error.rules.shadowed": "规则被同名 bundle 遮蔽,无法写入", "error.rules.notFound": "规则不存在", "error.rules.budgetExceeded": "场景记忆段超出预算", "error.rules.ioFailed": "文件操作失败", "error.rules.nameTaken": "目标名称已被占用", "error.rules.sceneNotEmpty": "场景不为空,无法删除", "error.rules.notBundle": "该记忆是 flat(单文件),不能带附件", "error.rules.noFiles": "没有选择附件", "error.rules.emptyFile": "附件内容为空", "error.rules.fileTooLarge": "附件过大(单个上限 {limit} MB)", "error.rules.tooManyFiles": "一次最多 {limit} 个附件", "error.rules.sceneInMode": "该场景正处在当前模式,请先退出模式再删除",
765
- "memory.archive.emptySection": "该段已定义但没有勾选任何条目 = 全部停用", "memory.archive.emptySubagents": "该段已定义但没有勾选任何条目 = 不限制(全部人设可用)",
860
+ "error.rules.invalidArgs": "缺少参数:需要 all:true(回到全部启用)或 scenes:[...](显式收窄)", "error.rules.invalidGroup": "场景/分组名不合法(非空、≤64 字符、不含路径分隔符与 < > : \" | ? *、不以 . 开头)", "error.rules.invalidName": "记忆名不合法(非空、≤64 字符、不含路径分隔符与 < > : \" | ? *、不以 . 开头)", "error.rules.descriptionRequired": "描述不能为空", "error.rules.descriptionTooLong": "描述过长(不能超过 500 字符)", "error.rules.bodyRequired": "正文不能为空", "error.rules.tooLarge": "规则内容过大", "error.rules.shadowed": "规则被同名 bundle 遮蔽,无法写入", "error.rules.exists": "同名记忆已存在,无法写入", "error.rules.notFound": "规则不存在", "error.rules.budgetExceeded": "场景记忆段超出预算", "error.rules.ioFailed": "文件操作失败", "error.rules.nameTaken": "目标名称已被占用", "error.rules.sceneNotEmpty": "场景不为空,无法删除", "error.rules.notBundle": "该记忆是 flat(单文件),不能带附件", "error.rules.noFiles": "没有选择附件", "error.rules.emptyFile": "附件内容为空", "error.rules.fileTooLarge": "附件过大(单个上限 {limit} MB)", "error.rules.tooManyFiles": "一次最多 {limit} 个附件", "error.rules.sceneInMode": "该场景正处在当前模式,请先退出模式再删除",
861
+ "memory.archive.emptySection": "没有勾选任何条目 = 全部停用(整段没建也一样)", "memory.archive.emptySubagents": "没有勾选任何条目 = 本场景不启用任何人设(整段没建也一样)",
766
862
  "subagents.title": "子智能体", "subagents.stat.total": "个子智能体", "subagents.stat.limited": "个有工具限制", "subagents.desc": "管理子智能体:新建、导入人设,正文即子代理的系统提示词。",
767
863
  "prompts.desc": "管理提示词:预设全局指令基线,应用后写入 AGENTS.md,下一轮对话生效。",
768
864
  "sessions.desc": "管理已归档会话:可恢复或永久删除,超保留期自动清理。",
@@ -781,6 +877,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
781
877
  "subagents.mode.denyEmpty": "一个都没勾 = 不限制(该模式的全部工具都能用)",
782
878
  "subagents.mode.noTools": "读不到这个模式的工具清单",
783
879
  "subagents.mode.broken": "这个预设已损坏,读不到工具清单",
880
+ "subagents.mode.toggleHint": "展开 / 收起这个模式的名单设置(右侧按钮负责启用 / 停用)",
784
881
  "subagents.mode.noPresets": "宿主没有回传 Agent 预设名单",
785
882
  "subagents.legacy.note": "这个文件里还有一份旧写法的工具限制(白名单 {allow} 个 / 黑名单 {deny} 个),它对所有模式都生效。",
786
883
  "subagents.legacy.convert": "转换到按模式设置",
@@ -792,7 +889,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
792
889
  "subagents.field.body": "人设提示词", "subagents.field.body.placeholder": "写下这个人设的身份、职责与工作方式…",
793
890
  "subagents.result.saved": "已保存人设:{name}", "subagents.result.deleted": "已删除人设:{name}",
794
891
  "subagents.delete.title": "删除人设?", "subagents.delete.desc": "将把「{name}」的人设文件移入回收站(子智能体页的「回收站」里可以恢复)。",
795
- "subagents.import": "导入子智能体", "subagents.import.title": "导入子智能体", "subagents.toggle": "启用子智能体", "subagents.disabled": "已停用", "subagents.disabled.hint": "停用后不注入系统提示词,subagent_list / subagent_run 也看不到;文件保留,随时可再打开。",
892
+ "subagents.import": "导入子智能体", "subagents.import.title": "导入子智能体", "subagents.toggle": "启用子智能体", "subagents.disabled": "已停用", "subagents.disabled.hint": "停用后不注入上下文,subagent_manager_list / subagent_manager_run 也看不到;文件保留,随时可再打开。",
796
893
  "subagents.result.imported": "已导入 {count} 个人设:{names}",
797
894
  "memory.import": "导入记忆", "memory.import.title": "导入记忆", "memory.import.scene": "导入到场景",
798
895
  "memory.result.imported": "已导入 {count} 条记忆:{names}",
@@ -802,11 +899,11 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
802
899
  "import.none": "没有导入任何文件(全部被跳过)", "import.skipped": "已跳过:{items}",
803
900
  "error.import.noFiles": "没有选择要导入的文件",
804
901
  "nav.title": "工具",
805
- "agm.stat.total": "个提示词", "agm.stat.active": "个生效中", "agm.loading": "加载中…", "agm.empty": "暂无提示词,点「新增提示词」创建", "agm.btn.delete.blocked": "正在生效的提示词预设不能删除;先启用别的场景、换绑提示词,或先把别的预设应用上去。", "agm.active": "生效中", "agm.active.hint.scene": "当前生效:场景「{scene}」绑定的提示词预设", "agm.active.hint.file": "当前生效:AGENTS.md 就是这一份", "agm.file.applied": "文件里是它", "agm.file.applied.hint": "AGENTS.md 当前内容是它;启用的场景绑了别的提示词,切换场景时已按场景把文件改成那一份", "agm.apply.hint": "应用后写入 AGENTS.md;宿主每轮重读该文件,下一轮对话生效", "agm.btn.reapply": "重新应用", "agm.btn.apply": "应用", "agm.applyModal.title": "应用预设", "agm.edit.title": "编辑预设", "agm.remove.title": "删除预设", "agm.btn.edit": "编辑", "agm.btn.delete": "删除", "agm.btn.new": "新增提示词", "agm.btn.import": "导入提示词", "agm.result.imported": "已导入 {count} 份预设:{names}", "agm.btn.save": "保存", "agm.btn.create": "创建", "agm.btn.confirmRemove": "确认删除", "agm.btn.confirmApply": "确认应用", "agm.field.id": "id(目录名)", "agm.field.id.hint": "中文、空格、点都可以;不能含斜杠、反斜杠与 : * ? < > 等符号;≤64 字符。改 id 等于目录改名。", "agm.field.id.placeholder": "例如:工作基线", "agm.field.content": "内容", "agm.field.content.placeholder": "直接写这份预设的正文(Markdown);留空 = 用空白模板", "agm.field.copyFrom": "或从现有预设复制", "agm.field.copyFrom.none": "不复制(空白模板)", "agm.field.desc": "描述(只给使用者看)", "agm.field.desc.placeholder": "这份预设是干什么的、什么时候用它", "agm.result.renamed": "已保存,id 改为「{id}」", "agm.result.rebound": "(同步了 {count} 个场景的绑定)", "agm.remove.suffix": " ?此操作不可恢复。", "agm.apply.prefix": "将把 ", "agm.apply.suffix": " 的内容写入 ~/.dsh/AGENTS.md。", "agm.apply.note": "下一轮对话生效;当前内容已备份到 __last-applied__(保留最近 5 代)", "hist.err.load": "加载失败", "hist.err.unarchive": "恢复失败", "hist.err.delete": "删除失败", "hist.err.retention": "设置失败", "hist.err.import": "导入失败", "hist.err.sessions": "加载会话列表失败", "hist.err.export": "导出失败", "hist.err.archive": "归档失败", "hist.retention.forever": "永久保留", "hist.retention.days": "{count} 天", "hist.arch.all": "全部会话", "hist.arch.archived": "仅已归档", "hist.arch.live": "仅未归档", "hist.arch.missing": "仅目录丢失", "hist.ws.all": "全部工作区", "hist.ungrouped": "未分组", "hist.group.removed": "已移除", "hist.group.removed.hint": "该目录当前在 DSH 里没有工作区登记;分组按会话目录重建,重新登记后会自动并回同一组", "hist.btn.registerWs": "重新登记", "hist.btn.registerWs.hint": "重新登记这个目录,并把该目录下已有的会话挂回该组(只写工作区记账,不移动、不删除文件与会话)", "hist.register.title": "重新登记?", "hist.register.body": "将为「{title}」在 DSH 中重新创建一条工作区登记,并把该目录下已有的会话挂回这个工作区(恢复其分组)。只写工作区记账,不移动、不删除任何文件与会话。", "hist.register.confirm": "确认登记", "hist.register.done.title": "已重新登记", "hist.register.done.body": "工作区「{title}」已登记;该目录下的归档会话会在刷新后并入这一组。", "hist.register.done.attached": "已把该目录下 {count} 个已有会话挂回这个工作区。", "hist.register.done.skipped": "{count} 个会话未能挂回:{reason}", "hist.err.register": "登记失败", "scenes.field.prompt": "提示词预设", "scenes.agents.note.applied": "已把「{id}」写入 AGENTS.md", "scenes.agents.note.restored": "已恢复进场景前的 AGENTS.md", "scenes.agents.note.error": "AGENTS.md 未写入:{reason}", "scenes.field.prompt.hint": "启用这个场景时,就用这份预设作为当前的全局指令基线。默认选中当前生效的那份。", "scenes.prompt.activeTag": "生效中", "scenes.prompt.noPresets": "还没有提示词预设;可先去「提示词」页新建一个。", "scenes.prompt.bound": "提示词:{id}", "scenes.prompt.live": "提示词生效中:{id}", "scenes.prompt.missing": "预设不存在:{id}", "scenes.prompt.tag.hint": "这个场景绑定的提示词预设", "scenes.enable.blocked": "已启用场景「{name}」;先关掉它才能启用别的", "scenes.result.created.collapsed": "场景「{name}」已创建,默认不启动(原先的「全部启用」已收敛为单选)", "scenes.legacyAll": "检测到 {count} 个场景同时处于启用状态(历史「全部启用」遗留);除「全局」外同时只能启用一个。", "scenes.legacyAll.fix": "收敛为单选", "agm.scene.tag": "场景「{scene}」正在使用", "agm.scene.tag.hint": "由这个场景绑定,随场景开关生效", "agm.scene.missing": "场景「{scene}」绑定了它,但文件不存在", "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.group.count": "{count} 个", "hist.selectAll": "全选", "hist.deselectAll": "取消全选", "hist.btn.import": "导入会话", "hist.btn.export": "导出会话", "hist.selected": "已选 {count} 项", "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.drop": "选择或拖入对话文件", "hist.import.done": "已创建会话 {id}({count} 条消息),可在 DSH 会话列表中继续对话。", "hist.export.hint": "导出选中会话为转录文件,可再次导入", "hist.export.dir": "导出目录(绝对路径,自动创建)", "hist.export.browse": "浏览并选择文件夹", "hist.btn.browse": "选择", "hist.export.pickDir": "选择导出目录", "hist.export.format": "格式", "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.result.archived": "已归档 {count} 个会话到 History,可到 History 页继续管理", "hist.btn.archiveSelected": "归档所选", "hist.btn.archiveSelected.title": "把选中的会话收进 History,纳入保留期管理", "hist.export.busy": "处理中…", "hist.btn.exportSelected": "导出 {count} 个会话", "hist.error.title": "操作失败", "hist.error.unknown": "未知错误",
902
+ "agm.stat.total": "个提示词", "agm.stat.active": "个生效中", "agm.loading": "加载中…", "agm.empty": "暂无提示词,点「新增提示词」创建", "agm.btn.delete.blocked.refs": "不能删除:{refs}。先改掉引用(换绑提示词 / 退出场景 / 应用别的预设)再删除。", "agm.ref.sep": ";", "agm.ref.scene": "场景「{scene}」绑定了它", "agm.ref.sceneActive": "场景「{scene}」(已启用)绑定了它", "agm.ref.file": "AGENTS.md 当前内容就是它", "agm.ref.restore": "退出场景后要恢复的全局提示词", "agm.sceneLock.notice": "场景「{scene}」接管中:当前生效的是它绑定的「{id}」。应用别的预设会绕过场景绑定——要换提示词,请到场景页修改该场景的绑定,或先退出场景。", "agm.apply.blockedScene": "场景「{scene}」接管中:应用别的预设会绕过场景绑定。要换提示词,请到场景页修改该场景的绑定,或先退出场景。", "agm.active": "生效中", "agm.active.hint.scene": "当前生效:场景「{scene}」绑定的提示词预设", "agm.active.hint.file": "当前生效:AGENTS.md 就是这一份", "agm.file.applied": "文件里是它", "agm.file.applied.hint": "AGENTS.md 当前内容是它;启用的场景绑了别的提示词,切换场景时已按场景把文件改成那一份", "agm.apply.hint": "应用后写入 AGENTS.md;宿主每轮重读该文件,下一轮对话生效", "agm.btn.reapply": "重新应用", "agm.btn.apply": "应用", "agm.applyModal.title": "应用预设", "agm.edit.title": "编辑预设", "agm.remove.title": "删除预设", "agm.btn.edit": "编辑", "agm.btn.delete": "删除", "agm.btn.new": "新增提示词", "agm.btn.import": "导入提示词", "agm.result.imported": "已导入 {count} 份预设:{names}", "agm.btn.save": "保存", "agm.btn.create": "创建", "agm.btn.confirmRemove": "确认删除", "agm.btn.confirmApply": "确认应用", "agm.field.id": "id(目录名)", "agm.field.id.hint": "中文、空格、点都可以;不能含斜杠、反斜杠与 : * ? < > 等符号;≤64 字符。改 id 等于目录改名。", "agm.field.id.placeholder": "例如:工作基线", "agm.field.content": "内容", "agm.field.content.placeholder": "直接写这份预设的正文(Markdown);留空 = 用空白模板", "agm.field.copyFrom": "或从现有预设复制", "agm.field.copyFrom.none": "不复制(空白模板)", "agm.field.desc": "描述(只给使用者看)", "agm.field.desc.placeholder": "这份预设是干什么的、什么时候用它", "agm.result.renamed": "已保存,id 改为「{id}」", "agm.result.rebound": "(同步了 {count} 个场景的绑定)", "agm.remove.suffix": " ?此操作不可恢复。", "agm.apply.prefix": "将把 ", "agm.apply.suffix": " 的内容写入 ~/.dsh/AGENTS.md。", "agm.apply.note": "下一轮对话生效;当前内容已备份到 __last-applied__(保留最近 5 代)", "hist.err.load": "加载失败", "hist.err.unarchive": "恢复失败", "hist.err.delete": "删除失败", "hist.err.retention": "设置失败", "hist.err.import": "导入失败", "hist.err.sessions": "加载会话列表失败", "hist.err.export": "导出失败", "hist.err.archive": "归档失败", "hist.retention.forever": "永久保留", "hist.retention.days": "{count} 天", "hist.arch.all": "全部会话", "hist.arch.archived": "仅已归档", "hist.arch.live": "仅未归档", "hist.arch.missing": "仅目录丢失", "hist.ws.all": "全部工作区", "hist.ungrouped": "未分组", "hist.group.removed": "已移除", "hist.group.removed.hint": "该目录当前在 DSH 里没有工作区登记;分组按会话目录重建,重新登记后会自动并回同一组", "hist.group.dirMissing": "目录已不存在", "hist.group.dirMissing.hint": "工作区目录已从磁盘删除,无法重新登记;组内会话仍可恢复(归入未分组)或永久删除", "hist.btn.registerWs": "重新登记", "hist.btn.registerWs.hint": "重新登记这个目录,并把该目录下已有的会话挂回该组(只写工作区记账,不移动、不删除文件与会话)", "hist.register.title": "重新登记?", "hist.register.body": "将为「{title}」在 DSH 中重新创建一条工作区登记,并把该目录下已有的会话挂回这个工作区(恢复其分组)。只写工作区记账,不移动、不删除任何文件与会话。", "hist.register.confirm": "确认登记", "hist.register.done.title": "已重新登记", "hist.register.done.body": "工作区「{title}」已登记;该目录下的归档会话会在刷新后并入这一组。", "hist.register.done.attached": "已把该目录下 {count} 个已有会话挂回这个工作区。", "hist.register.done.skipped": "{count} 个会话未能挂回:{reason}", "hist.err.register": "登记失败", "scenes.field.prompt": "提示词预设", "scenes.agents.note.applied": "已把「{id}」写入 AGENTS.md", "scenes.agents.note.restored": "已恢复进场景前的 AGENTS.md", "scenes.agents.note.error": "AGENTS.md 未写入:{reason}", "scenes.field.prompt.hint": "启用这个场景时,就用这份预设作为当前的全局指令基线。默认选中当前生效的那份。场景启用期间提示词页的「应用」不可用——要换提示词就在这里改绑定。", "scenes.prompt.activeTag": "生效中", "scenes.prompt.noPresets": "还没有提示词预设;可先去「提示词」页新建一个。", "scenes.prompt.bound": "提示词:{id}", "scenes.prompt.live": "提示词生效中:{id}", "scenes.prompt.missing": "预设不存在:{id}", "scenes.prompt.tag.hint": "这个场景绑定的提示词预设", "scenes.prompt.mismatch.hint": "场景绑定的「{id}」与 AGENTS.md 当前内容不一致(可能被手动改过);到提示词页对它点「重新应用」写回", "scenes.enable.blocked": "已启用场景「{name}」;先关掉它才能启用别的", "scenes.result.created.collapsed": "场景「{name}」已创建,默认不启动(原先的「全部启用」已收敛为单选)", "scenes.legacyAll": "检测到 {count} 个场景同时处于启用状态(历史「全部启用」遗留);除「全局」外同时只能启用一个。", "scenes.legacyAll.fix": "收敛为单选", "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.group.count": "{count} 个", "hist.selectAll": "全选", "hist.deselectAll": "取消全选", "hist.btn.import": "导入会话", "hist.btn.export": "导出会话", "hist.selected": "已选 {count} 项", "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.drop": "选择或拖入对话文件", "hist.import.done": "已创建会话 {id}({count} 条消息),可在 DSH 会话列表中继续对话。", "hist.export.hint": "导出选中会话为转录文件,可再次导入", "hist.export.dir": "导出目录(绝对路径,自动创建)", "hist.export.browse": "浏览并选择文件夹", "hist.btn.browse": "选择", "hist.export.pickDir": "选择导出目录", "hist.export.format": "格式", "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.result.archived": "已归档 {count} 个会话到 History,可到 History 页继续管理", "hist.btn.archiveSelected": "归档所选", "hist.btn.archiveSelected.title": "把选中的会话收进 History,纳入保留期管理", "hist.export.busy": "处理中…", "hist.btn.exportSelected": "导出 {count} 个会话", "hist.error.title": "操作失败", "hist.error.unknown": "未知错误",
806
903
  },
807
904
  en: {
808
905
  "title": "Skills", "desc": "Manage skills: enable, import, create, trash.", "link.project": "GitHub", "link.feedback": "Issues",
809
- "btn.create": "New skill", "btn.import": "Import skills", "btn.refresh": "Refresh", "btn.refreshing": "Refreshing…", "btn.cancel": "Cancel", "btn.close": "Close", "export.hint": "Exports a zip into the directory you choose; source files are read-only and nothing is modified.", "export.outDir": "Output directory", "export.outDir.placeholder": "e.g. D:////backup", "export.pickDir": "Choose output directory", "export.submit": "Export", "export.busy": "Exporting…", "export.done": "Exported {count} file(s) to {path}", "scenes.lock.lock": "Lock", "scenes.lock.unlock": "Unlock", "scenes.lock.tag": "Locked", "scenes.lock.hint": "Locking makes all five domains (MCP / skills / subagents / memories / prompts) read-only; starting or stopping scenes still works", "scenes.lock.blockedEdit": "Scene is locked — unlock it to make changes", "scenes.lock.notActive": "Scene is not active — start it before locking", "scenes.lock.blockedExit": "Scene is locked — unlock it before turning it off", "lock.banner": "A scene is locked: MCP, skills, subagents, memories and prompts are frozen. Unlock it on the Scenes page to make changes.", "export.empty": "Nothing to export", "export.pickAll": "Select all", "export.pickNone": "Clear", "export.group.count": "{count}", "export.missing": "; skipped {count} (missing on disk): {names}", "export.skills": "Export skills", "export.subagents": "Export subagents", "export.presets": "Export prompts", "export.memories": "Export memories", "btn.detail": "View details", "btn.trash": "Move to trash", "btn.restore": "Restore", "btn.delete.forever": "Delete forever", "btn.file.pick": "Choose file", "btn.folder.pick": "Choose folder", "btn.create.now": "Create skill", "btn.disable": "Disable", "btn.enable": "Enable", "btn.open.editor": "Open in editor", "btn.activate": "Use this one", "btn.activate.title": "Make this source's copy the active one for this shared name and enable it (source files are never touched)", "btn.unprefer": "Auto again", "btn.unprefer.title": "Clear the manual choice and go back to automatic source priority",
906
+ "btn.create": "New skill", "btn.import": "Import skills", "btn.refresh": "Refresh", "btn.refreshing": "Refreshing…", "btn.cancel": "Cancel", "btn.close": "Close", "export.hint": "Exports a zip into the directory you choose; source files are read-only and nothing is modified.", "export.outDir": "Output directory", "export.outDir.placeholder": "e.g. D:////backup", "export.pickDir": "Choose output directory", "export.submit": "Export", "export.busy": "Exporting…", "export.done": "Exported {count} file(s) to {path}", "scenes.lock.lock": "Lock", "scenes.lock.unlock": "Unlock", "scenes.lock.tag": "Locked", "scenes.lock.hint": "Locking makes all five domains (MCP / skills / subagents / memories / prompts) read-only; starting or stopping scenes still works", "scenes.lock.blockedEdit": "Scene is locked — unlock it to make changes", "scenes.lock.notActive": "Scene is not active — start it before locking", "scenes.lock.blockedExit": "Scene is locked — unlock it before turning it off", "lock.banner": "A scene is locked: MCP, skills, subagents, memories and prompts are frozen. Unlock it on the Scenes page to make changes.", "scene.switch.banner": "Scene “{scene}” is active: switches are defined by the scene profile, so the switches on this page are disabled — change them in the profile editor on the Scenes page (the save takes effect immediately and is kept for the next visit; leaving the scene still restores the state from before), or exit the scene first.", "scene.switch.title": "In scene “{scene}”: change switches in the scene profile", "export.empty": "Nothing to export", "export.pickAll": "Select all", "export.pickNone": "Clear", "export.group.count": "{count}", "export.missing": "; skipped {count} (missing on disk): {names}", "export.skills": "Export skills", "export.subagents": "Export subagents", "export.presets": "Export prompts", "export.memories": "Export memories", "btn.detail": "View details", "btn.trash": "Move to trash", "btn.restore": "Restore", "btn.delete.forever": "Delete forever", "btn.file.pick": "Choose file", "btn.folder.pick": "Choose folder", "btn.create.now": "Create skill", "btn.disable": "Disable", "btn.enable": "Enable", "btn.open.editor": "Open in editor", "btn.activate": "Use this one", "btn.activate.title": "Make this source's copy the active one for this shared name and enable it (source files are never touched)", "btn.unprefer": "Auto again", "btn.unprefer.title": "Clear the manual choice and go back to automatic source priority",
810
907
  "btn.custom.add": "Add folder",
811
908
  "btn.custom.remove": "Remove",
812
909
  "btn.source.remove": "Remove source", "btn.source.remove.title": "Stop reading this source (files stay on disk; restore it later from the trash)", "btn.source.restore": "Restore", "btn.source.forget": "Delete forever",
@@ -840,7 +937,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
840
937
  "empty.search": "No matching skills", "empty.source": "This source is missing or has no skills", "loading": "Loading skills…", "note.missing": "No description provided", "source.toggle": "Toggle source", "skill.toggle": "Toggle skill",
841
938
  "source.external.note": "Read-only; toggles never rewrite source files", "source.dsh.note": "Create, import, trash; a default source, always read — it cannot be removed or toggled off",
842
939
  "detail.title": "Skill details", "detail.body": "Body", "detail.frontmatter": "Metadata", "detail.noFrontmatter": "This skill provides no metadata.", "detail.diagnostics": "Diagnostics", "detail.path": "Source file", "detail.noIssues": "No diagnostic issues found.",
843
- "create.title": "Create skill", "create.target": "Create in", "create.name": "Name", "create.name.placeholder": "e.g. code-review-helper", "create.description": "Description", "create.description.placeholder": "One sentence describing when to use it", "create.body": "Body (Markdown)", "create.body.placeholder": "Write the instructions, steps, and boundaries…", "create.chat.note": "create_skill in chat creates user-level skills",
940
+ "create.title": "Create skill", "create.target": "Create in", "create.name": "Name", "create.name.placeholder": "e.g. code-review-helper", "create.description": "Description", "create.description.placeholder": "One sentence describing when to use it", "create.body": "Body (Markdown)", "create.body.placeholder": "Write the instructions, steps, and boundaries…", "create.chat.note": "skill_manager_create in chat creates user-level skills",
844
941
  "import.title": "Import skill", "upload.drop.title": "Click or drop here", "upload.drop.copy": "Supported: .zip, a skill folder, or one SKILL.md", "upload.selected.one": "{count} file · {size}", "upload.selected.other": "{count} files · {size}", "upload.remove": "Remove selection", "upload.requirements": "File requirements", "upload.requirement.skill": "Archives and folders must contain SKILL.md", "upload.requirement.frontmatter": "SKILL.md must include a YAML name and description", "upload.requirement.copy": "Import copies all content and never modifies the source", "upload.requirement.persona.1": "Accepts .md / .zip (multi-select or drag in)", "upload.requirement.persona.2": "One .md = one persona; the file name is the persona name", "upload.requirement.persona.3": "Same names are skipped, never overwritten", "upload.requirement.memory.1": "Accepts .md / .zip (multi-select or drag in)", "upload.requirement.memory.2": "One .md = one memory; a folder with SKILL.md becomes a bundle memory (attachments included)", "upload.requirement.memory.3": "Folder names inside the zip are scenes; otherwise the scene field below applies (blank = global)", "upload.requirement.prompt.1": "Accepts .md (multi-select or drag in)", "upload.requirement.prompt.2": "One .md = one preset; the file name is the id", "upload.requirement.prompt.3": "A preset with the same name is skipped, never overwritten", "upload.requirement.session.1": "Accepts .jsonl / .json / .md / .txt (one file at a time)", "upload.requirement.session.2": "Transcripts from Claude Code / Cursor / Codex, or any plain text", "upload.requirement.session.3": "The project directory may be left blank (the session goes to Ungrouped)", "upload.importing": "Importing…", "status.selected": "Selected", "select.file.invalid": "Choose a .zip archive or one SKILL.md.", "select.folder.invalid": "No SKILL.md was found in the selected folder.", "error.browse.absolute": "Folder path must be absolute: {path}", "error.browse.unreadable": "Could not read folder: {path}", "error.browse.notDirectory": "Not a folder: {path}",
845
942
  "trash.title": "Trash", "trash.count.one": "{count} skill pending", "trash.count.other": "{count} skills pending", "trash.empty": "Trash is empty", "trash.deletedAt": "Deleted {time}", "trash.source": "Source: {source}",
846
943
  "trash.btn.open": "Trash", "trash.items.count": "{count} item(s)", "trash.purge.confirm": "Delete forever?",
@@ -866,7 +963,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
866
963
  "mcp.btn.new": "New MCP", "mcp.btn.add": "Add", "mcp.btn.save": "Save", "mcp.btn.detail": "Details", "mcp.btn.edit": "Edit", "mcp.btn.restart": "Restart", "mcp.btn.remove": "Delete",
867
964
  "mcp.btn.enableAll": "Enable all", "mcp.btn.disableAll": "Disable all",
868
965
  "mcp.loading": "Loading MCP servers…", "mcp.empty": "No MCP servers yet — click “New MCP”.", "mcp.empty.search": "No matching server.",
869
- "mcp.servers.count": "{count} server(s)", "mcp.tools.count": "{count} tool(s)", "mcp.duplicate": "duplicate id",
966
+ "mcp.servers.count": "{count} server(s)", "mcp.tools.count": "{count} tool(s)", "mcp.tools.countPartial": "{enabled}/{total} tool(s)", "mcp.duplicate": "duplicate id",
870
967
  "mcp.table.name": "Server name and URL", "mcp.table.transport": "Transport and tools", "mcp.table.status": "Status",
871
968
  "mcp.live.notLoaded": "not loaded", "mcp.live.failed": "start failed", "mcp.live.stopped": "stopped", "mcp.live.loading": "loading", "mcp.live.noTools": "no tools", "mcp.live.running": "running",
872
969
  "mcp.live.failedHint": "Start failed — check the configuration and click “Restart” to retry", "mcp.live.noToolsHint": "Connected but with no tools: the server may not be ready yet",
@@ -887,8 +984,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
887
984
  "mcp.btn.compact": "Tidy patch", "mcp.btn.compact.title": "Clear the stale on/off records left in the patch file (each server's enabled/disabled state is unchanged)",
888
985
  "mcp.compact.title": "Tidy the patch file", "mcp.compact.desc": "The config accumulates stale on/off records — leftovers from switching the same server on and off over time. This clears them so the file stays short and readable.",
889
986
  "mcp.compact.confirm": "Tidy now", "mcp.compact.done": "Patch file tidied: cleared {count} stale on/off record(s); no server changed state", "mcp.compact.clean": "The patch file is already clean — nothing stale to clear",
890
- "mcp.detail.entryId": "Entry id", "mcp.detail.status": "Status", "mcp.detail.registered": "This server is registered in the loader.",
891
- "mcp.detail.note": "Note (injected into the system prompt and sent to the model)", "mcp.detail.note.placeholder": "e.g. fall back to B when A is unavailable", "mcp.detail.note.save": "Save note",
987
+ "mcp.detail.entryId": "Entry id", "mcp.detail.status": "Status", "mcp.detail.loader": "Loader registration", "mcp.detail.loader.yes": "Registered", "mcp.detail.loader.no": "Not registered",
988
+ "mcp.tools.stale": "from last run", "mcp.tools.expand": "Show more", "mcp.tools.collapse": "Show less",
989
+ "mcp.detail.note": "Note (sent to the model with the injected runtime snapshot; when a suppressing preset keeps it out, mcp_manager_list still reads it back)", "mcp.detail.note.placeholder": "e.g. fall back to B when A is unavailable", "mcp.detail.note.save": "Save note",
892
990
  "mcp.detail.tools": "Tools ({count})",
893
991
  "mcp.field.headersShort": "Headers", "mcp.field.envShort": "Environment",
894
992
  "mcp.tools.loading": "Fetching the tool list…", "mcp.tools.loadFailed": "Could not load tools: ", "mcp.tools.none": "This server has no registered tools.",
@@ -916,11 +1014,21 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
916
1014
  "compat.fallback.native": "Falls back to native entry", "compat.fallback.blocked": "Affected buttons disabled",
917
1015
  "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.unknown": "not comparable",
918
1016
  "compat.blockers": "Blockers (fix these)",
919
- "compat.reach": "Preset injection reach", "compat.reach.hint": "the panel says injected — does the model actually see it?",
920
- "compat.reach.mark.memory": "Memory", "compat.reach.mark.agentsMd": "Prompt", "compat.reach.mark.skill": "Skills", "compat.reach.mark.mcp": "MCP", "compat.reach.mark.subagent": "Subagents",
1017
+ "compat.reach": "Preset injection reach", "compat.reach.hint": "whether these contents reach the model under each preset",
1018
+ "compat.reach.suppressing": "suppressing",
1019
+ "compat.inject": "Injection", "compat.inject.hint": "what this plugin injects for the model (unchanged text is not resent)",
1020
+ "compat.inject.force": "Also inject in Minimal mode", "compat.inject.force.desc": "Off by default (follow the preset)",
1021
+ "compat.inject.domains": "What to inject",
1022
+ "compat.inject.domain.memory": "Scene + memory", "compat.inject.domain.mcp": "MCP",
1023
+ "compat.inject.domain.subagents": "Subagents", "compat.inject.domain.prompt": "Prompt",
1024
+ "compat.inject.domain.skills": "Skills",
1025
+ "compat.reach.mark.memory": "Scene + memory", "compat.reach.mark.agentsMd": "Prompt", "compat.reach.mark.skill": "Skills", "compat.reach.mark.mcp": "MCP", "compat.reach.mark.subagent": "Subagents",
921
1026
  "compat.reach.default": "default",
922
1027
  "compat.reach.whyUnknown": "cannot tell", "compat.reach.broken": "preset broken",
923
- "compat.reach.tools": "This plugin's model tools (including subagent_list / subagent_run) and MCP are host-plane and preset-independent: callable under every preset.",
1028
+ "compat.reach.tools.title": "Tools work ≠ the model knows",
1029
+ "compat.reach.tools.1": "The tools come from this plugin and are callable under any preset; how they reach the model is up to the preset.",
1030
+ "compat.reach.tools.2": "Scene + memory / MCP / skills / subagents / prompt — filled in by this plugin according to the Injection block above.",
1031
+ "compat.reach.tools.3": "The prompt and the skill catalog also come from the official rows; this plugin only fills in what they miss (e.g. under Minimal).",
924
1032
  "compat.hint.doctor": "Command-line check:",
925
1033
  "scenes.title": "Scenes", "scenes.desc": "Manage scenes: preset combinations of MCP, skills, subagents and memories, switch with one click.",
926
1034
  "scenes.stat.total": "scene(s)", "scenes.stat.active": "enabled", "scenes.stat.archives": "with a profile",
@@ -929,25 +1037,25 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
929
1037
  "scenes.empty": "No dedicated scenes yet — global memories are injected without one; create a scene when you need to switch MCP servers, skills or personas.",
930
1038
  "scenes.field.desc.limit": "Up to {count} characters; longer text is clipped on the card.",
931
1039
  "scenes.profile.mcp": "{count} MCP", "scenes.profile.skills": "{count} skill(s)", "scenes.profile.subagents": "{count} subagent(s)", "scenes.profile.memories": "{count} memor(ies)",
932
- "scenes.mcp.hint": "Add the MCP section first, then check the servers to enable (stopped ones included); use Pick tools to narrow to specific tools.",
1040
+ "scenes.mcp.hint": "Add the MCP section first, then check the servers to enable (stopped ones included); use Pick tools to narrow to specific tools. Unchecked servers — and scenes with no section at all — are turned off on entry, running processes included.",
933
1041
  "scenes.seg.selectAll": "Select all", "scenes.seg.clear": "Clear", "scenes.seg.checked": "{checked}/{total} selected",
934
- "scenes.archive.sectionOff": "not defined",
1042
+ "scenes.archive.sectionOff": "not defined = all off",
935
1043
  "scenes.archive.summary": "Profile: {mcp} MCP server(s) · {skills} skill(s) · {subagents} subagent(s)",
936
- "scenes.archive.note": "An undefined section leaves that domain untouched; a defined section switches both ways: checked items are enabled, unchecked ones disabled. MCP really starts/stops the server process; skills toggle the source they live in.",
1044
+ "scenes.archive.note": "Checked means enabled, unchecked means disabled — and an undefined section means nothing is checked, so that whole domain is off. MCP really starts/stops server processes; skills toggle the source they live in. Leaving the scene restores the exact state from before.",
937
1045
  "scenes.mcp.noServers": "No MCP servers to pick from yet", "scenes.mcp.toolCount": "{count} tool(s)", "scenes.skills.empty": "No skills to pick from yet",
938
- "scenes.mcp.allTools": "all tools", "scenes.mcp.pickedCount": "{count} tools picked", "scenes.mcp.notRunning": "not running", "scenes.mcp.pickTools": "Pick tools",
939
- "scenes.mcp.toolsOf": "Tool details", "scenes.mcp.back": "Back", "scenes.mcp.noTools": "No tools listed for this server (not running or no tools)", "scenes.mcp.drillHint": "Checked = enabled in this scene; unchecked = disabled. A whole-server check defaults to all tools.",
940
- "scenes.skills.hint": "Add the skills section first, then check the skills enabled in this scene.", "scenes.subagents.hint": "Add the subagent section first, then check the personas callable in this scene.", "scenes.subagents.empty": "No personas yet — create one on the Subagents page.",
1046
+ "scenes.mcp.allTools": "all tools", "scenes.mcp.pickedCount": "{count} tools picked", "scenes.mcp.notRunning": "not running", "scenes.mcp.edit": "Edit", "scenes.mcp.done": "Done", "scenes.mcp.noteLabel": "Scene note", "scenes.mcp.notePlaceholder": "Overrides the global note in this scene; restored on exit", "scenes.mcp.hasNote": "has note",
1047
+ "scenes.mcp.toolsOf": "Pick tools", "scenes.mcp.probeTools": "Start the server to load tools", "scenes.mcp.unknownCount": "unknown tool count — open Edit to load", "scenes.mcp.noteHint": "Saved with the scene. It overrides the note shown on the MCP page and in the system prompt only while this scene is entered — and only for servers checked in this section. Restored on exit.", "scenes.mcp.noTools": "No tools listed for this server (it has never run, or its tools were never recorded)", "scenes.mcp.drillHint": "Checked = enabled in this scene; unchecked = disabled. “All” = every tool.",
1048
+ "scenes.skills.hint": "Add the skills section first, then check the skills enabled in this scene. Unchecked skills — and scenes with no section at all — are disabled on entry, and restored to their previous switch when you exit.", "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.empty": "No personas yet — create one on the Subagents page.",
941
1049
  "scenes.filter.skills": "Filter skills / directories", "scenes.filter.subagents": "Filter personas (name or description)", "scenes.filter.servers": "Filter servers",
942
1050
 
943
1051
  "scenes.mem.title": "Memories", "scenes.mem.search": "Filter memories in this scene (name or description)",
944
1052
  "scenes.mem.noMatch": "Nothing matches", "scenes.mem.emptyScene": "This scene has no memories yet",
945
1053
  "memory.mode.current": "Active mode", "memory.mode.exit": "Exit mode",
946
1054
  "memory.archive.edit": "Profile", "memory.archive.title": "Scene profile",
947
- "memory.archive.tools": "MCP tools", "memory.archive.skills": "Skills", "memory.archive.subagents": "Subagent binding", "memory.archive.memories": "Memories",
1055
+ "memory.archive.tools": "MCP tools", "memory.archive.skills": "Skills", "memory.archive.subagents": "Subagent binding", "memory.archive.memories": "Memories", "memory.archive.applyFailed": "Could not apply at runtime: {reason}",
948
1056
  "memory.archive.addTools": "+ Tools", "memory.archive.addSkills": "+ Skills", "memory.archive.addSubagents": "+ Subagents",
949
1057
  "memory.archive.removeSection": "Remove section", "memory.archive.save": "Save to scene",
950
- "memory.archive.emptySection": "Section defined with nothing checked = all disabled", "memory.archive.emptySubagents": "Section defined with nothing checked = no restriction (all personas allowed)", "memory.archive.stale": "Stale entries (no longer exist, skipped): {items}",
1058
+ "memory.archive.emptySection": "Nothing checked = all disabled (a missing section means the same)", "memory.archive.emptySubagents": "Nothing checked = no personas enabled in this scene (a missing section means the same)", "memory.archive.stale": "Stale entries (no longer exist, skipped): {items}",
951
1059
  "memory.result.archiveSaved": "Scene profile saved: {name}", "memory.result.modeSet": "Entered mode: {name}", "memory.result.modeSwitched": "{mcp} server(s) / {skills} source(s) switched", "memory.result.modeExited": "Exited mode",
952
1060
  "memory.desc": "Manage memories: enabled memories enter the system prompt; enable them per scene.",
953
1061
  "memory.btn.refresh": "Refresh", "memory.btn.new": "New memory", "memory.btn.create": "Create", "memory.btn.save": "Save",
@@ -962,7 +1070,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
962
1070
  "memory.scene.enable": "Enabled", "memory.scene.enableAll": "Enable all", "memory.scene.off": "Off", "memory.scene.shared": "always on", "memory.scene.global.tag": "always injected",
963
1071
  "memory.scene.global": "Global", "memory.scene.count": "{count} memories", "memory.scene.new": "New memory", "memory.scene.browse": "Pick existing",
964
1072
  "memory.scene.empty": "No memories in this scene yet",
965
- "memory.scene.orphan": "No scene", "memory.scene.orphan.tag": "never injected", "memory.scene.orphan.hint": "These memories sit directly in the memories/ root and belong to no scene, so they never reach the system prompt. Move them into a scene directory (or into memories/global/ to make them “Global”).",
1073
+ "memory.scene.orphan": "No scene", "memory.scene.orphan.tag": "never injected", "memory.scene.orphan.hint": "These memories sit directly in the memories/ root and belong to no scene, so they are never injected into the context. Move them into a scene directory (or into memories/global/ to make them “Global”).",
966
1074
  "memory.scene.edit": "Edit", "memory.scene.editTitle": "Edit scene", "memory.scene.editHelp": "The name is its memory folder name; renaming moves the folder, profile and bindings along. Memory files stay untouched.",
967
1075
  "memory.scene.field.name": "Scene name", "memory.scene.field.name.placeholder": "e.g. office",
968
1076
  "memory.scene.field.name.hint": "The name is its top-level folder name; you can still change it later.",
@@ -995,7 +1103,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
995
1103
  "memory.deleteScene.title": "Delete scene?", "memory.deleteScene.desc": "Moves “{name}” and every memory under it (including disabled ones) to the trash, restorable as one entry; a scene in use cannot be deleted.",
996
1104
  "memory.result.sceneCreated": "Scene created: {name}", "memory.result.sceneUpdated": "Scene saved: {name}", "memory.result.sceneRemoved": "Scene deleted: {name}", "memory.result.sceneRemoved.withMemories": "Scene deleted: {name} (with {count} memory file(s); restorable as one entry from the trash)",
997
1105
  "memory.table.name": "Memory name and description", "memory.table.tags": "Tags", "memory.table.status": "Status",
998
- "error.rules.invalidArgs": "Missing arguments: pass all:true (enable everything) or scenes:[...] (explicit narrowing)", "error.rules.invalidGroup": "Invalid scene/group name (non-empty, ≤64 chars, no path separators or < > : \" | ? *, must not start with a dot)", "error.rules.invalidName": "Invalid memory name (non-empty, ≤64 chars, no / \\ < > : \" | ? *, must not start with a dot)", "error.rules.descriptionRequired": "Description is required", "error.rules.descriptionTooLong": "Description is too long (max 500 chars)", "error.rules.bodyRequired": "Body is required", "error.rules.tooLarge": "Rule content is too large", "error.rules.shadowed": "Rule is shadowed by a bundle of the same name", "error.rules.notFound": "Rule not found", "error.rules.budgetExceeded": "Scene memory section exceeds its budget", "error.rules.ioFailed": "File operation failed", "error.rules.nameTaken": "That name is already taken", "error.rules.sceneNotEmpty": "Scene is not empty; cannot be deleted", "error.rules.notBundle": "This memory is flat (a single file) and cannot carry attachments", "error.rules.noFiles": "No files selected", "error.rules.emptyFile": "Empty file", "error.rules.fileTooLarge": "File too large (max {limit} MB each)", "error.rules.tooManyFiles": "At most {limit} files per upload", "error.rules.sceneInMode": "This scene is the active mode; exit the mode before deleting it",
1106
+ "error.rules.invalidArgs": "Missing arguments: pass all:true (enable everything) or scenes:[...] (explicit narrowing)", "error.rules.invalidGroup": "Invalid scene/group name (non-empty, ≤64 chars, no path separators or < > : \" | ? *, must not start with a dot)", "error.rules.invalidName": "Invalid memory name (non-empty, ≤64 chars, no / \\ < > : \" | ? *, must not start with a dot)", "error.rules.descriptionRequired": "Description is required", "error.rules.descriptionTooLong": "Description is too long (max 500 chars)", "error.rules.bodyRequired": "Body is required", "error.rules.tooLarge": "Rule content is too large", "error.rules.shadowed": "Rule is shadowed by a bundle of the same name", "error.rules.exists": "A memory with this name already exists", "error.rules.notFound": "Rule not found", "error.rules.budgetExceeded": "Scene memory section exceeds its budget", "error.rules.ioFailed": "File operation failed", "error.rules.nameTaken": "That name is already taken", "error.rules.sceneNotEmpty": "Scene is not empty; cannot be deleted", "error.rules.notBundle": "This memory is flat (a single file) and cannot carry attachments", "error.rules.noFiles": "No files selected", "error.rules.emptyFile": "Empty file", "error.rules.fileTooLarge": "File too large (max {limit} MB each)", "error.rules.tooManyFiles": "At most {limit} files per upload", "error.rules.sceneInMode": "This scene is the active mode; exit the mode before deleting it",
999
1107
  "subagents.title": "Subagents", "subagents.stat.total": " subagents", "subagents.stat.limited": " tool-restricted", "subagents.desc": "Manage subagents: create and import personas; a persona's body is the subagent's system prompt.",
1000
1108
  "prompts.desc": "Manage prompts: preset the global instruction baseline; applying writes AGENTS.md and takes effect on the next turn.",
1001
1109
  "sessions.desc": "Manage archived sessions: restore or delete them permanently; expired ones are cleaned up automatically.",
@@ -1014,6 +1122,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1014
1122
  "subagents.mode.denyEmpty": "Nothing picked = no restriction (every tool of this preset stays available)",
1015
1123
  "subagents.mode.noTools": "Could not read this preset's tool list",
1016
1124
  "subagents.mode.broken": "This preset is broken; its tool list is unreadable",
1125
+ "subagents.mode.toggleHint": "Expand / collapse this preset's list settings (the buttons on the right enable / disable it)",
1017
1126
  "subagents.mode.noPresets": "The host returned no agent-preset roster",
1018
1127
  "subagents.legacy.note": "This file still carries the old-style tool limits (allowlist {allow} / denylist {deny}); they apply to every preset.",
1019
1128
  "subagents.legacy.convert": "Convert into a preset row",
@@ -1025,7 +1134,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1025
1134
  "subagents.field.body": "Persona prompt", "subagents.field.body.placeholder": "Describe the persona's role, responsibilities, and working style…",
1026
1135
  "subagents.result.saved": "Persona saved: {name}", "subagents.result.deleted": "Persona deleted: {name}",
1027
1136
  "subagents.delete.title": "Delete persona?", "subagents.delete.desc": "Moves the persona file “{name}” to the trash (restorable from the Trash button on the Subagents page).",
1028
- "subagents.import": "Import subagents", "subagents.import.title": "Import subagents", "subagents.toggle": "Toggle subagent", "subagents.disabled": "Disabled", "subagents.disabled.hint": "Not injected into the system prompt and hidden from subagent_list / subagent_run; the file is kept and can be re-enabled anytime.",
1137
+ "subagents.import": "Import subagents", "subagents.import.title": "Import subagents", "subagents.toggle": "Toggle subagent", "subagents.disabled": "Disabled", "subagents.disabled.hint": "Disabled personas are not injected into the context and hidden from subagent_manager_list / subagent_manager_run; the file is kept and can be re-enabled anytime.",
1029
1138
  "subagents.result.imported": "Imported {count} persona(s): {names}",
1030
1139
  "memory.import": "Import memories", "memory.import.title": "Import memories", "memory.import.scene": "Import into scene",
1031
1140
  "memory.result.imported": "Imported {count} memory/memories: {names}",
@@ -1035,7 +1144,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1035
1144
  "import.none": "Nothing was imported (all skipped)", "import.skipped": "Skipped: {items}",
1036
1145
  "error.import.noFiles": "No files selected",
1037
1146
  "nav.title": "Tools",
1038
- "agm.stat.total": " prompts", "agm.stat.active": " active", "agm.loading": "Loading…", "agm.empty": "No prompts yet — click “New prompt” to create one", "agm.btn.delete.blocked": "The preset currently in effect cannot be deleted; enable another scene, rebind the prompt, or apply a different preset first.", "agm.active": "Active", "agm.active.hint.scene": "Currently active: the prompt preset bound to scene “{scene}”", "agm.active.hint.file": "Currently active: AGENTS.md is exactly this preset", "agm.file.applied": "In AGENTS.md", "agm.file.applied.hint": "AGENTS.md currently holds this one; the enabled scene binds a different preset, which now takes effect", "agm.apply.hint": "Writes AGENTS.md; the host re-reads that file every turn, so it takes effect on the next turn", "agm.btn.reapply": "Apply again", "agm.btn.apply": "Apply", "agm.applyModal.title": "Apply preset", "agm.edit.title": "Edit preset", "agm.remove.title": "Delete preset", "agm.btn.edit": "Edit", "agm.btn.delete": "Delete", "agm.btn.new": "New prompt", "agm.btn.import": "Import prompts", "agm.result.imported": "Imported {count} preset(s): {names}", "agm.btn.save": "Save", "agm.btn.create": "Create", "agm.btn.confirmRemove": "Delete", "agm.btn.confirmApply": "Apply", "agm.field.id": "id (directory name)", "agm.field.id.hint": "Letters, spaces and dots are fine; no slashes or : * ? < >; up to 64 chars. Changing it renames the directory.", "agm.field.id.placeholder": "e.g. work-baseline", "agm.field.content": "Content", "agm.field.content.placeholder": "Write the preset body (Markdown) here; leave blank to start from the empty template", "agm.field.copyFrom": "Or copy from an existing preset", "agm.field.copyFrom.none": "Do not copy (empty template)", "agm.field.desc": "Description (for humans only)", "agm.field.desc.placeholder": "What this preset is for and when to use it", "agm.result.renamed": "Saved; id is now “{id}”", "agm.result.rebound": " ({count} scene binding(s) updated)", "agm.remove.suffix": "? This cannot be undone.", "agm.apply.prefix": "Write the contents of ", "agm.apply.suffix": " into ~/.dsh/AGENTS.md.", "agm.apply.note": "Takes effect on the next turn; the current content is backed up to __last-applied__ (last 5 kept)", "hist.err.load": "Load failed", "hist.err.unarchive": "Restore failed", "hist.err.delete": "Delete failed", "hist.err.retention": "Update failed", "hist.err.import": "Import failed", "hist.err.sessions": "Failed to load sessions", "hist.err.export": "Export failed", "hist.err.archive": "Archive failed", "hist.retention.forever": "Keep forever", "hist.retention.days": "{count} days", "hist.arch.all": "All sessions", "hist.arch.archived": "Archived only", "hist.arch.live": "Not archived only", "hist.arch.missing": "Missing directory only", "hist.ws.all": "All workspaces", "hist.ungrouped": "Ungrouped", "hist.group.removed": "Removed", "hist.group.removed.hint": "This directory has no DSH workspace registration right now; the group is rebuilt from session directories and rejoins automatically once registered again", "hist.btn.registerWs": "Register", "hist.btn.registerWs.hint": "Register this directory again and attach its existing sessions back to that group (workspace accounting only — no file or session is moved or deleted)", "hist.register.title": "Register again?", "hist.register.body": "A workspace registration will be created in DSH for “{title}”, and the sessions under this directory will be attached back to it (restoring their grouping). This writes workspace accounting only — no file or session is moved or deleted.", "hist.register.confirm": "Register", "hist.register.done.title": "Registered", "hist.register.done.body": "Workspace “{title}” is registered; archived sessions under this directory join that group after a refresh.", "hist.register.done.attached": "Attached {count} existing session(s) under this directory back to the workspace.", "hist.register.done.skipped": "{count} session(s) could not be attached: {reason}", "hist.err.register": "Registration failed", "scenes.field.prompt": "Prompt preset", "scenes.agents.note.applied": "wrote “{id}” into AGENTS.md", "scenes.agents.note.restored": "restored the AGENTS.md baseline from before the scene", "scenes.agents.note.error": "AGENTS.md was not written: {reason}", "scenes.field.prompt.hint": "While this scene is enabled, its preset is the active global baseline. Defaults to the one currently in effect.", "scenes.prompt.activeTag": "in effect", "scenes.prompt.noPresets": "No prompt presets yet — create one on the Prompts page.", "scenes.prompt.bound": "Prompt: {id}", "scenes.prompt.live": "Prompt active: {id}", "scenes.prompt.missing": "Preset missing: {id}", "scenes.prompt.tag.hint": "The prompt preset bound to this scene", "scenes.enable.blocked": "Scene “{name}” is already enabled; turn it off first", "scenes.result.created.collapsed": "Scene “{name}” created and left off (the legacy “all enabled” default collapsed to a single choice)", "scenes.legacyAll": "{count} scenes are enabled at once (a leftover of the legacy “all enabled” default); only one scene besides “Global” may be enabled.", "scenes.legacyAll.fix": "Collapse to one", "agm.scene.tag": "In use by scene “{scene}”", "agm.scene.tag.hint": "Bound by that scene; takes effect with the scene switch", "agm.scene.missing": "Bound by scene “{scene}”, but the file is missing", "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.group.count": "{count}", "hist.selectAll": "Select all", "hist.deselectAll": "Clear selection", "hist.btn.import": "Import sessions", "hist.btn.export": "Export sessions", "hist.selected": "{count} selected", "hist.btn.restoreSelected": "Restore selected ({count})", "hist.btn.deleteSelected": "Delete selected ({count})", "hist.stat.archived": " archived", "hist.stat.projects": " projects", "hist.stat.retention": " days kept", "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": "Project directory (optional)", "hist.import.cwd.placeholder": "Absolute path, optional (the session goes to Ungrouped)", "hist.import.drop": "Choose or drop a conversation file", "hist.import.done": "Created session {id} ({count} messages); continue it from the DSH session list.", "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.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.result.archived": "Archived {count} session(s) to History; manage them on the History page", "hist.btn.archiveSelected": "Archive selected", "hist.btn.archiveSelected.title": "Move the selected sessions into History so retention applies to them", "hist.export.busy": "Working…", "hist.btn.exportSelected": "Export {count} session(s)", "hist.error.title": "Operation failed", "hist.error.unknown": "Unknown error",
1147
+ "agm.stat.total": " prompts", "agm.stat.active": " active", "agm.loading": "Loading…", "agm.empty": "No prompts yet — click “New prompt” to create one", "agm.btn.delete.blocked.refs": "Cannot delete: {refs}. Change those references first (rebind the prompt, leave the scene, or apply another preset).", "agm.ref.sep": "; ", "agm.ref.scene": "bound by scene “{scene}”", "agm.ref.sceneActive": "bound by scene “{scene}” (enabled)", "agm.ref.file": "this is what AGENTS.md currently holds", "agm.ref.restore": "the global prompt to restore after leaving the scene", "agm.sceneLock.notice": "Scene “{scene}” is driving the baseline: the prompt in effect is its bound “{id}”. Applying another preset would bypass the scene binding — to change the prompt, rebind it on the Scenes page or exit the scene first.", "agm.apply.blockedScene": "Scene “{scene}” is driving the baseline: applying another preset would bypass the scene binding. Rebind it on the Scenes page, or exit the scene first.", "agm.active": "Active", "agm.active.hint.scene": "Currently active: the prompt preset bound to scene “{scene}”", "agm.active.hint.file": "Currently active: AGENTS.md is exactly this preset", "agm.file.applied": "In AGENTS.md", "agm.file.applied.hint": "AGENTS.md currently holds this one; the enabled scene binds a different preset, which now takes effect", "agm.apply.hint": "Writes AGENTS.md; the host re-reads that file every turn, so it takes effect on the next turn", "agm.btn.reapply": "Apply again", "agm.btn.apply": "Apply", "agm.applyModal.title": "Apply preset", "agm.edit.title": "Edit preset", "agm.remove.title": "Delete preset", "agm.btn.edit": "Edit", "agm.btn.delete": "Delete", "agm.btn.new": "New prompt", "agm.btn.import": "Import prompts", "agm.result.imported": "Imported {count} preset(s): {names}", "agm.btn.save": "Save", "agm.btn.create": "Create", "agm.btn.confirmRemove": "Delete", "agm.btn.confirmApply": "Apply", "agm.field.id": "id (directory name)", "agm.field.id.hint": "Letters, spaces and dots are fine; no slashes or : * ? < >; up to 64 chars. Changing it renames the directory.", "agm.field.id.placeholder": "e.g. work-baseline", "agm.field.content": "Content", "agm.field.content.placeholder": "Write the preset body (Markdown) here; leave blank to start from the empty template", "agm.field.copyFrom": "Or copy from an existing preset", "agm.field.copyFrom.none": "Do not copy (empty template)", "agm.field.desc": "Description (for humans only)", "agm.field.desc.placeholder": "What this preset is for and when to use it", "agm.result.renamed": "Saved; id is now “{id}”", "agm.result.rebound": " ({count} scene binding(s) updated)", "agm.remove.suffix": "? This cannot be undone.", "agm.apply.prefix": "Write the contents of ", "agm.apply.suffix": " into ~/.dsh/AGENTS.md.", "agm.apply.note": "Takes effect on the next turn; the current content is backed up to __last-applied__ (last 5 kept)", "hist.err.load": "Load failed", "hist.err.unarchive": "Restore failed", "hist.err.delete": "Delete failed", "hist.err.retention": "Update failed", "hist.err.import": "Import failed", "hist.err.sessions": "Failed to load sessions", "hist.err.export": "Export failed", "hist.err.archive": "Archive failed", "hist.retention.forever": "Keep forever", "hist.retention.days": "{count} days", "hist.arch.all": "All sessions", "hist.arch.archived": "Archived only", "hist.arch.live": "Not archived only", "hist.arch.missing": "Missing directory only", "hist.ws.all": "All workspaces", "hist.ungrouped": "Ungrouped", "hist.group.removed": "Removed", "hist.group.removed.hint": "This directory has no DSH workspace registration right now; the group is rebuilt from session directories and rejoins automatically once registered again", "hist.group.dirMissing": "Directory missing", "hist.group.dirMissing.hint": "The workspace directory was deleted from disk, so it cannot be registered again; sessions in this group can still be restored (they join Ungrouped) or deleted forever", "hist.btn.registerWs": "Register", "hist.btn.registerWs.hint": "Register this directory again and attach its existing sessions back to that group (workspace accounting only — no file or session is moved or deleted)", "hist.register.title": "Register again?", "hist.register.body": "A workspace registration will be created in DSH for “{title}”, and the sessions under this directory will be attached back to it (restoring their grouping). This writes workspace accounting only — no file or session is moved or deleted.", "hist.register.confirm": "Register", "hist.register.done.title": "Registered", "hist.register.done.body": "Workspace “{title}” is registered; archived sessions under this directory join that group after a refresh.", "hist.register.done.attached": "Attached {count} existing session(s) under this directory back to the workspace.", "hist.register.done.skipped": "{count} session(s) could not be attached: {reason}", "hist.err.register": "Registration failed", "scenes.field.prompt": "Prompt preset", "scenes.agents.note.applied": "wrote “{id}” into AGENTS.md", "scenes.agents.note.restored": "restored the AGENTS.md baseline from before the scene", "scenes.agents.note.error": "AGENTS.md was not written: {reason}", "scenes.field.prompt.hint": "While this scene is enabled, its preset is the active global baseline. Defaults to the one currently in effect. While the scene is on, Apply on the Prompts page is disabled — change the prompt here instead.", "scenes.prompt.activeTag": "in effect", "scenes.prompt.noPresets": "No prompt presets yet — create one on the Prompts page.", "scenes.prompt.bound": "Prompt: {id}", "scenes.prompt.live": "Prompt active: {id}", "scenes.prompt.missing": "Preset missing: {id}", "scenes.prompt.tag.hint": "The prompt preset bound to this scene", "scenes.prompt.mismatch.hint": "The scene-bound “{id}” does not match the current contents of AGENTS.md (it may have been edited by hand); click “Apply again” for it on the Prompts page to write it back", "scenes.enable.blocked": "Scene “{name}” is already enabled; turn it off first", "scenes.result.created.collapsed": "Scene “{name}” created and left off (the legacy “all enabled” default collapsed to a single choice)", "scenes.legacyAll": "{count} scenes are enabled at once (a leftover of the legacy “all enabled” default); only one scene besides “Global” may be enabled.", "scenes.legacyAll.fix": "Collapse to one", "agm.restore.tag": "Restored after the scene", "agm.restore.tag.hint": "The global prompt from before the scene; leaving the scene restores it, so deleting it would lose that fallback", "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); click “Apply again” 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.group.count": "{count}", "hist.selectAll": "Select all", "hist.deselectAll": "Clear selection", "hist.btn.import": "Import sessions", "hist.btn.export": "Export sessions", "hist.selected": "{count} selected", "hist.btn.restoreSelected": "Restore selected ({count})", "hist.btn.deleteSelected": "Delete selected ({count})", "hist.stat.archived": " archived", "hist.stat.projects": " projects", "hist.stat.retention": " days kept", "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": "Project directory (optional)", "hist.import.cwd.placeholder": "Absolute path, optional (the session goes to Ungrouped)", "hist.import.drop": "Choose or drop a conversation file", "hist.import.done": "Created session {id} ({count} messages); continue it from the DSH session list.", "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.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.result.archived": "Archived {count} session(s) to History; manage them on the History page", "hist.btn.archiveSelected": "Archive selected", "hist.btn.archiveSelected.title": "Move the selected sessions into History so retention applies to them", "hist.export.busy": "Working…", "hist.btn.exportSelected": "Export {count} session(s)", "hist.error.title": "Operation failed", "hist.error.unknown": "Unknown error",
1039
1148
  }
1040
1149
  };
1041
1150
 
@@ -1154,6 +1263,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1154
1263
  warnings: (res && res.warnings) || [],
1155
1264
  revealError: revealError || null,
1156
1265
  locked: (res && res.anyLocked) === true,
1266
+ // 场景内开关由档案定义:页面上这些开关置灰,并说明去哪儿改。
1267
+ scene: (res && res.activeScene) || null,
1157
1268
  })
1158
1269
  if (onRows) onRows(rows)
1159
1270
  }
@@ -1344,10 +1455,20 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1344
1455
  return [row.serverName, row.id, row.url, row.command, row.transport].some((value) => String(value == null ? '' : value).toLowerCase().indexOf(normalizedQuery) >= 0)
1345
1456
  }
1346
1457
  const visibleRows = rows.filter(matchesRow)
1458
+ // 工具数两个口径:total = 已知总数;enabled = 可用数(停用表扣减后,与详情页
1459
+ // 每行的开关一致)。两者不同时显示「可用/总数」,不让全部数量冒充可用数。
1460
+ const toolTotals = rows.reduce((acc, row) => {
1461
+ const total = typeof row.toolCount === 'number' ? row.toolCount : 0
1462
+ const enabled = typeof row.enabledToolCount === 'number' ? row.enabledToolCount : total
1463
+ acc.total += total
1464
+ acc.enabled += enabled
1465
+ return acc
1466
+ }, { total: 0, enabled: 0 })
1347
1467
  const summary = {
1348
1468
  total: rows.length,
1349
1469
  enabled: rows.filter((row) => !row.disabled).length,
1350
- tools: rows.reduce((sum, row) => sum + (typeof row.toolCount === 'number' ? row.toolCount : 0), 0),
1470
+ tools: toolTotals.enabled,
1471
+ toolsTotal: toolTotals.total,
1351
1472
  }
1352
1473
  const profilePath = state.paths && state.paths.profile ? 'profile: ' + state.paths.profile : null
1353
1474
  const groups = levelFilter === 'loader'
@@ -1372,6 +1493,18 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1372
1493
  if (row.live.phase === 'active' && typeof row.toolCount === 'number' && row.toolCount === 0) return mt('mcp.live.noToolsHint')
1373
1494
  return null
1374
1495
  }
1496
+ // 状态标签配色:绿=能用、红=坏了、橙=能连但没工具、灰=没在跑 / 还没定。
1497
+ const pillKind = (cls) => cls === 'dsm-enabled' ? 'ok' : cls === 'dsm-failed' ? 'bad' : cls === 'dsm-disabled' ? 'warn' : 'muted'
1498
+
1499
+ // 工具数标签:有被停用的工具时给「可用/总数」(场景档案收窄与手动逐工具开关
1500
+ // 写的是同一张停用表),否则就是总数 —— 与详情页每个工具的开关状态一致。
1501
+ const toolCountTag = (row) => {
1502
+ if (typeof row.toolCount !== 'number' || row.toolCount <= 0) return null
1503
+ const enabled = typeof row.enabledToolCount === 'number' ? row.enabledToolCount : row.toolCount
1504
+ return enabled < row.toolCount
1505
+ ? React.createElement('span', { className: 'dsm-tag' }, mt('mcp.tools.countPartial', { enabled: enabled, total: row.toolCount }))
1506
+ : React.createElement('span', { className: 'dsm-tag' }, mt('mcp.tools.count', { count: row.toolCount }))
1507
+ }
1375
1508
 
1376
1509
  const renderRow = (row) => {
1377
1510
  const status = liveStatus(row)
@@ -1384,11 +1517,11 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1384
1517
  row.notes ? React.createElement('div', { className: 'dsm-note dsm-note-user', title: row.notes }, mt('mcp.note.prefix') + row.notes) : null),
1385
1518
  React.createElement('div', { className: 'dsm-tags' },
1386
1519
  (levelFilter === 'loader' && row.level && row.level !== 'loader') ? React.createElement('span', { className: 'dsm-tag' }, mt('mcp.level.' + row.level)) : null,
1387
- (typeof row.toolCount === 'number' && row.toolCount > 0) ? React.createElement('span', { className: 'dsm-tag' }, mt('mcp.tools.count', { count: row.toolCount })) : null,
1520
+ toolCountTag(row),
1388
1521
  row.duplicate ? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, mt('mcp.duplicate')) : null),
1389
1522
  React.createElement('div', { className: 'dsm-status ' + status.cls }, status.text),
1390
1523
  React.createElement('div', { className: 'dsm-row-actions' },
1391
- editable ? React.createElement(Switch, { on: !row.disabled, disabled: busy !== null, label: mt('mcp.toggleServer') + ' ' + row.serverName, onClick: () => toggleRow(row) }) : null,
1524
+ editable ? React.createElement(Switch, { on: !row.disabled, disabled: busy !== null || state.locked === true || !!state.scene, title: state.scene ? mt('scene.switch.title', { scene: state.scene }) : '', label: mt('mcp.toggleServer') + ' ' + row.serverName, onClick: () => toggleRow(row) }) : null,
1392
1525
  React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', onClick: () => openDetail(row) }, mt('mcp.btn.detail')),
1393
1526
  editable ? React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy !== null, onClick: () => openEdit(row) }, mt('mcp.btn.edit')) : null,
1394
1527
  editable ? React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy !== null, onClick: () => restartRow(row) }, mt('mcp.btn.restart')) : null,
@@ -1431,8 +1564,10 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1431
1564
  React.createElement('div', { className: 'dsm-tools' }, (detail.tools || []).map((tool) => React.createElement('div', { className: 'dsm-tool' + (tool.enabled === false ? ' dsm-tool-off' : ''), key: tool.name },
1432
1565
  React.createElement('div', { className: 'dsm-tool-name-row' },
1433
1566
  React.createElement('div', { className: 'dsm-tool-name' }, tool.name),
1434
- React.createElement(Switch, { on: tool.enabled !== false, disabled: busy !== null || state.locked === true, label: mt('mcp.toggleTool') + ' ' + tool.name, onClick: () => toggleTool(detail.row, tool) })),
1435
- tool.description ? React.createElement('div', { className: 'dsm-tool-desc' }, tool.description) : null,
1567
+ // 描述来自「已知工具」缓存(服务器未运行)→ 标明是旧数据,不假装实时。
1568
+ tool.stale ? React.createElement('span', { className: 'dsm-tag' }, mt('mcp.tools.stale')) : null,
1569
+ React.createElement(Switch, { on: tool.enabled !== false, disabled: busy !== null || state.locked === true || !!state.scene, title: state.scene ? mt('scene.switch.title', { scene: state.scene }) : '', label: mt('mcp.toggleTool') + ' ' + tool.name, onClick: () => toggleTool(detail.row, tool) })),
1570
+ tool.description ? React.createElement(ToolDesc, { text: tool.description, expandLabel: mt('mcp.tools.expand'), collapseLabel: mt('mcp.tools.collapse') }) : null,
1436
1571
  (tool.parameters && tool.parameters.length > 0) ? React.createElement('div', { className: 'dsm-tool-params' }, tool.parameters.map((param) => React.createElement('div', { className: 'dsm-tool-param', key: param.key },
1437
1572
  React.createElement('span', { className: 'dsm-tool-param-key' }, param.key + (param.required ? ' *' : '')),
1438
1573
  React.createElement('span', { className: 'dsm-tool-param-type' }, param.type || 'any'),
@@ -1498,7 +1633,11 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1498
1633
  const detailNode = detail && React.createElement(Modal, {
1499
1634
  key: 'mcp-detail',
1500
1635
  wide: true,
1501
- title: mt('mcp.detail.title') + detail.row.serverName,
1636
+ // 运行状态做成标题右侧的描边标签(用户裁定 2026-09-16):状态是「一眼」信息,
1637
+ // 不该占一整块,也不该跟配置事实(是否登记在 Loader)用冒号拼成一句话。
1638
+ title: React.createElement('span', { className: 'dsm-modal-title-row' },
1639
+ mt('mcp.detail.title') + detail.row.serverName,
1640
+ React.createElement('span', { className: 'dsm-pill dsm-pill-' + pillKind(detailStatus.cls) }, detailStatus.text)),
1502
1641
  closeLabel: mt('btn.close'),
1503
1642
  onClose: () => setDetail(null),
1504
1643
  },
@@ -1510,14 +1649,16 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1510
1649
  configRow(mt('mcp.detail.entryId'), detail.row.id),
1511
1650
  configRow(mt('mcp.field.level'), mt('mcp.level.' + detail.row.level)),
1512
1651
  configRow(mt('mcp.field.transport'), detail.row.transport || '—'),
1652
+ configRow(mt('mcp.detail.loader'), detail.row.live ? mt('mcp.detail.loader.yes') : mt('mcp.detail.loader.no')),
1513
1653
  detail.row.url ? configRow(mt('mcp.field.url'), detail.row.url) : null,
1514
1654
  detail.row.command ? configRow(mt('mcp.field.command'), detail.row.command + ((detail.row.args && detail.row.args.length) ? ' ' + detail.row.args.join(' ') : '')) : null,
1515
1655
  (detail.row.headers && Object.keys(detail.row.headers).length > 0) ? configRow(mt('mcp.field.headersShort'), Object.keys(detail.row.headers).map((k) => k + ': ' + maskSecret(detail.row.headers[k])).join('\n')) : null,
1516
1656
  (detail.row.env && Object.keys(detail.row.env).length > 0) ? configRow(mt('mcp.field.envShort'), Object.keys(detail.row.env).map((k) => k + ' = ' + maskSecret(detail.row.env[k])).join('\n')) : null)),
1517
- React.createElement('div', { className: 'dsm-detail-section' },
1657
+ // 运行状态一节只在**有可操作信息**时出现(启动失败 / 连上了但没工具)——
1658
+ // 状态本身已在标题标签里,再复述一遍就是噪音。
1659
+ detailHint ? React.createElement('div', { className: 'dsm-detail-section' },
1518
1660
  React.createElement('div', { className: 'dsm-detail-title' }, mt('mcp.detail.status')),
1519
- React.createElement('div', { className: 'dsm-feedback' + (detailStatus.cls === 'dsm-failed' ? ' dsm-error' : detailStatus.cls === 'dsm-disabled' ? ' dsm-warning' : '') },
1520
- detailStatus.text + ':' + (detailHint || mt('mcp.detail.registered')))),
1661
+ React.createElement('div', { className: 'dsm-feedback' + (detailStatus.cls === 'dsm-failed' ? ' dsm-error' : ' dsm-warning') }, detailHint)) : null,
1521
1662
  React.createElement('div', { className: 'dsm-detail-section' },
1522
1663
  React.createElement('div', { className: 'dsm-detail-title' }, mt('mcp.detail.note')),
1523
1664
  React.createElement('textarea', { className: 'dsm-control dsm-textarea-sm', value: noteDraft, maxLength: 200, placeholder: mt('mcp.detail.note.placeholder'), onChange: (ev) => setNoteDraft(ev.target.value) }),
@@ -1559,12 +1700,15 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1559
1700
  React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: state.locked === true, onClick: openAdd }, mt('mcp.btn.new')),
1560
1701
  React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary dsm-btn-danger', disabled: busy !== null || state.loading, title: mt('mcp.btn.compact.title'), disabled: busy !== null || state.loading || state.locked === true, onClick: () => setCompactConfirm(true) }, mt('mcp.btn.compact')))),
1561
1702
  state.locked === true ? React.createElement('div', { className: 'dsm-feedback dsm-warning', role: 'status' }, mt('lock.banner')) : null,
1703
+ // 场景内的开关由档案定义(页面开关在场景里本来就会被退出时的快照回滚):
1704
+ // 置灰 + 说明去哪儿改,而不是点了没反应。
1705
+ state.scene ? React.createElement('div', { className: 'dsm-feedback dsm-warning', role: 'status' }, mt('scene.switch.banner', { scene: state.scene })) : null,
1562
1706
  React.createElement('div', { className: 'dsm-stat-row' },
1563
1707
  React.createElement('div', { className: 'dsm-summary' },
1564
- [['total', summary.total, mt('mcp.stat.total')], ['enabled', summary.enabled, mt('mcp.stat.enabled')], ['tools', summary.tools, mt('mcp.stat.tools')]].map((item) =>
1708
+ [['total', summary.total, mt('mcp.stat.total')], ['enabled', summary.enabled, mt('mcp.stat.enabled')], ['tools', summary.tools < summary.toolsTotal ? summary.tools + '/' + summary.toolsTotal : summary.tools, mt('mcp.stat.tools')]].map((item) =>
1565
1709
  React.createElement('div', { key: item[0], className: 'dsm-stat' },
1566
1710
  React.createElement('strong', null, item[1]), item[2]))),
1567
- React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null || state.loading || !summary.total, onClick: () => run('mcpm-set-all', { enabled: summary.enabled < summary.total }, 'setall') }, mt(summary.total > 0 && summary.enabled === summary.total ? 'mcp.btn.disableAll' : 'mcp.btn.enableAll'))),
1711
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null || state.loading || !summary.total || !!state.scene, title: state.scene ? mt('scene.switch.title', { scene: state.scene }) : '', onClick: () => run('mcpm-set-all', { enabled: summary.enabled < summary.total }, 'setall') }, mt(summary.total > 0 && summary.enabled === summary.total ? 'mcp.btn.disableAll' : 'mcp.btn.enableAll'))),
1568
1712
  React.createElement('div', { className: 'dsm-filters' },
1569
1713
  React.createElement('input', { className: 'dsm-control dsm-search', value: query, 'aria-label': mt('mcp.search'), placeholder: mt('mcp.search.placeholder'), onChange: (ev) => setQuery(ev.target.value) }),
1570
1714
  React.createElement('div', { className: 'dsm-source-filter' },
@@ -1595,7 +1739,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1595
1739
 
1596
1740
  // ---------- AGENTS.md 预设页:多套全局指令基线,应用=写入 ~/.dsh/AGENTS.md ----------
1597
1741
  function AgentsMdPage() {
1598
- var st = React.useState({ loading: true, error: null, presets: [], current: null })
1742
+ // deny = 一次性「拒绝」说明(如:被引用的预设不能删)。它**不替换列表**——
1743
+ // 复用 error 会让整页列表消失,用户删不动之后连列表都看不到了。
1744
+ var st = React.useState({ loading: true, error: null, presets: [], current: null, deny: null })
1599
1745
  var state = st[0], setState = st[1]
1600
1746
  var busyState = React.useState(null), busy = busyState[0], setBusy = busyState[1]
1601
1747
  var em = React.useState(null), editModal = em[0], setEditModal = em[1]
@@ -1669,8 +1815,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1669
1815
 
1670
1816
  function refresh() {
1671
1817
  apiCall('agentsmd-list', {}).then(function (res) {
1672
- setState(function (s) { return { loading: false, error: res && res.ok ? null : ((res && res.error) || mt('mcp.msg.loadFailed')), presets: (res && res.presets) || [], current: s.current, scenePrompt: (res && res.scenePrompt) || null, anyLocked: (res && res.anyLocked) === true } })
1673
- }).catch(function (e) { setState({ loading: false, error: String((e && e.message) || e), presets: [], current: null, scenePrompt: null }) })
1818
+ // 刷新即清掉上一次的「拒绝」说明(它解释的是那一次点击,不是常驻状态)。
1819
+ setState(function (s) { return { loading: false, error: res && res.ok ? null : ((res && res.error) || mt('mcp.msg.loadFailed')), presets: (res && res.presets) || [], current: s.current, scenePrompt: (res && res.scenePrompt) || null, anyLocked: (res && res.anyLocked) === true, deny: res && res.ok ? null : s.deny } })
1820
+ }).catch(function (e) { setState({ loading: false, error: String((e && e.message) || e), presets: [], current: null, scenePrompt: null, deny: null }) })
1674
1821
  apiCall('agentsmd-get-current', {}).then(function (res) {
1675
1822
  if (res && res.ok) setState(function (s) { return Object.assign({}, s, { current: res }) })
1676
1823
  }).catch(function () {})
@@ -1678,6 +1825,19 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1678
1825
  React.useEffect(function () { refresh() }, [])
1679
1826
 
1680
1827
  var cur = state.current
1828
+ // 场景接管基线时(scenePrompt 指向某个启用场景的绑定),提示词页的「应用」只对
1829
+ // 场景绑定的那一份可用:应用别的预设会绕过场景绑定,造成「场景页显示 A、实际注入 B、
1830
+ // A 不能删而 B 能删」这类各说各话的状态(用户实测)。同一份 = 「重新应用」
1831
+ // (把被手改的 AGENTS.md 写回场景绑定的内容)→ 仍放行。
1832
+ var sceneDriver = state.scenePrompt && state.scenePrompt.scene && state.scenePrompt.presetId && state.scenePrompt.missing !== true ? state.scenePrompt : null
1833
+ var sceneDriverLabel = sceneDriver ? (sceneDriver.label || sceneDriver.scene) : ''
1834
+ /** 引用说明:判定在宿主(scene-prompt-sync),这里只负责把它说成人话。 */
1835
+ function refText(r) {
1836
+ if (r.kind === 'scene') return t(r.active ? 'agm.ref.sceneActive' : 'agm.ref.scene', { scene: r.label || r.scene })
1837
+ if (r.kind === 'restore') return t('agm.ref.restore')
1838
+ return t('agm.ref.file')
1839
+ }
1840
+ function refsOf(p) { return (p.refs || []).map(refText) }
1681
1841
  // 改名结果提示:短暂显示后自动消失(与场景页的结果条同一节奏)。
1682
1842
  React.useEffect(function () {
1683
1843
  if (!state.notice) return undefined
@@ -1750,21 +1910,32 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1750
1910
  // 「生效中」= 当前真正在起作用的基线:启用的场景绑了它,就是它(用户裁定);
1751
1911
  // 否则看文件比对。p.fileApplied 单独留着,说明「AGENTS.md 里确实是它」。
1752
1912
  var viaScene = p.activeVia === 'scene'
1753
- var boundByScene = state.scenePrompt && state.scenePrompt.presetId === p.id ? state.scenePrompt : null
1913
+ var refs = p.refs || []
1914
+ var restoreRef = refs.some(function (r) { return r.kind === 'restore' })
1915
+ var refTexts = refsOf(p)
1916
+ // 场景绑的就是它,但 AGENTS.md 里的内容不是它(被手改过?)→ 说清楚,
1917
+ // 并留「重新应用」这条路写回。
1918
+ var fileMismatch = !!sceneDriver && sceneDriver.presetId === p.id && !p.fileApplied
1919
+ var applyBlocked = !!sceneDriver && sceneDriver.presetId !== p.id
1754
1920
  return React.createElement('div', { key: p.id, className: 'dsm-source' },
1755
1921
  React.createElement('div', { className: 'dsm-source-head' },
1756
1922
  React.createElement('div', { className: 'dsm-source-head-main' },
1757
1923
  React.createElement('span', { className: 'dsm-source-title', title: p.id }, p.id),
1758
- p.active ? React.createElement('span', { className: 'dsm-tag dsm-tag-on', title: viaScene ? t('agm.active.hint.scene', { scene: (state.scenePrompt && state.scenePrompt.scene) || '' }) : t('agm.active.hint.file') }, t('agm.active')) : null,
1924
+ p.active ? React.createElement('span', { className: 'dsm-tag dsm-tag-on', title: viaScene ? t('agm.active.hint.scene', { scene: (state.scenePrompt && state.scenePrompt.label) || (state.scenePrompt && state.scenePrompt.scene) || '' }) : t('agm.active.hint.file') }, t('agm.active')) : null,
1759
1925
  !p.active && p.fileApplied ? React.createElement('span', { className: 'dsm-tag', title: t('agm.file.applied.hint') }, t('agm.file.applied')) : null,
1760
- boundByScene && !viaScene ? React.createElement('span', { className: 'dsm-tag' + (boundByScene.missing ? ' dsm-tag-off' : ''), title: t('agm.scene.tag.hint') },
1761
- boundByScene.missing
1762
- ? t('agm.scene.missing', { scene: boundByScene.scene })
1763
- : t('agm.scene.tag', { scene: boundByScene.scene })) : null),
1926
+ fileMismatch ? React.createElement('span', { className: 'dsm-tag dsm-tag-off', title: t('agm.file.mismatch.hint') }, t('agm.file.mismatch')) : null,
1927
+ // 场景绑定**不在这里标标签**(用户裁定):多场景绑同一份时标签会把行挤爆;
1928
+ // 谁在引用它,点「删除」时会逐条说出来(`agm.btn.delete.blocked.refs`)。
1929
+ restoreRef ? React.createElement('span', { className: 'dsm-tag', title: t('agm.restore.tag.hint') }, t('agm.restore.tag')) : null),
1764
1930
  React.createElement('div', { className: 'dsm-source-actions' },
1765
1931
  React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: state.anyLocked === true, onClick: function () { openEdit(p) } }, t('agm.btn.edit')),
1766
- React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy !== null || state.anyLocked === true, onClick: function () { setApplyConfirm({ id: p.id }) }, title: t('agm.apply.hint') }, p.fileApplied ? t('agm.btn.reapply') : t('agm.btn.apply')),
1767
- React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: busy !== null || p.active === true || state.anyLocked === true, title: p.active === true ? t('agm.btn.delete.blocked') : t('scenes.lock.blockedEdit'), onClick: function () { setApplyConfirm({ id: p.id, remove: true }) } }, t('agm.btn.delete')))), p.description ? React.createElement("div", { key: "desc", className: "dsm-source-note", title: p.description }, p.description) : null)
1932
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy !== null || state.anyLocked === true || applyBlocked, onClick: function () { setApplyConfirm({ id: p.id }) }, title: applyBlocked ? t('agm.apply.blockedScene', { scene: sceneDriverLabel }) : t('agm.apply.hint') }, p.fileApplied ? t('agm.btn.reapply') : t('agm.btn.apply')),
1933
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: busy !== null || state.anyLocked === true, title: refTexts.length > 0 ? t('agm.btn.delete.blocked.refs', { refs: refTexts.join(t('agm.ref.sep')) }) : t('scenes.lock.blockedEdit'), onClick: function () {
1934
+ // 被引用的预设**点得动但删不掉**:当面说出「谁在用」,而不是让用户对着
1935
+ // 禁用按钮猜(disabled 的按钮连 title 都不弹)。宿主侧同样拒绝,这里只是先说。
1936
+ if (refTexts.length > 0) { setState(function (s) { return Object.assign({}, s, { deny: t('agm.btn.delete.blocked.refs', { refs: refTexts.join(t('agm.ref.sep')) }) }) }); return }
1937
+ setApplyConfirm({ id: p.id, remove: true })
1938
+ } }, t('agm.btn.delete')))), p.description ? React.createElement("div", { key: "desc", className: "dsm-source-note", title: p.description }, p.description) : null)
1768
1939
  }))
1769
1940
 
1770
1941
  return React.createElement('section', { className: 'dsm-section' },
@@ -1780,6 +1951,10 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
1780
1951
  React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null || !state.presets.length, onClick: function () { setExportState({ busy: false, result: null }) } }, t('export.presets')),
1781
1952
  React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: function () { loadTrash(false) } }, t('trash.btn.open')))),
1782
1953
  state.anyLocked === true ? React.createElement('div', { className: 'dsm-feedback dsm-warning', role: 'status' }, t('lock.banner')) : null,
1954
+ // 场景接管时把「为什么应用按钮点不动」直接写出来,而不是让用户对着置灰的按钮猜。
1955
+ sceneDriver ? React.createElement('div', { className: 'dsm-feedback dsm-warning', role: 'status' }, t('agm.sceneLock.notice', { scene: sceneDriverLabel, id: sceneDriver.presetId })) : null,
1956
+ // 一次性拒绝说明(被引用的预设删不掉):不替换列表,只加一条横幅。
1957
+ state.deny ? React.createElement(Notice, { kind: 'warn', text: state.deny }) : null,
1783
1958
  React.createElement('div', { className: 'dsm-summary', style: { '--dsm-stat-cols': '2' } },
1784
1959
  [['total', state.presets.length, t('agm.stat.total')], ['active', state.presets.filter(function (p) { return p.active }).length, t('agm.stat.active')]].map(function (item) {
1785
1960
  return React.createElement('div', { key: item[0], className: 'dsm-stat' },
@@ -2061,13 +2236,36 @@ function callApi(path, options) {
2061
2236
  h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", "aria-haspopup": "listbox", "aria-expanded": open, disabled: props.disabled || !options.length, onClick: function () { setOpen(!open); } }, props.browseLabel)),
2062
2237
  open && options.length ? h("div", { className: "dsm-select-menu", role: "listbox" }, options.map(function (o) { return h("button", { key: o.value, type: "button", role: "option", className: "dsm-option", "aria-selected": o.value === props.value, onClick: function () { props.onChange(o.value); setOpen(false); } }, o.label); })) : null);
2063
2238
  }
2064
- function Switch(props) { return h("button", { type: "button", className: "dsm-switch" + (props.on ? " dsm-switch-on" : ""), role: "switch", "aria-checked": props.on, "aria-label": props.label, disabled: props.disabled, onClick: props.onClick }); }
2239
+ function Switch(props) { return h("button", { type: "button", className: "dsm-switch" + (props.on ? " dsm-switch-on" : ""), role: "switch", "aria-checked": props.on, "aria-label": props.label, title: props.title, disabled: props.disabled, onClick: props.onClick }); }
2065
2240
  function GithubMark16() { return h("svg", { viewBox: "0 0 16 16", width: 16, height: 16, "aria-hidden": true, focusable: "false" }, h("path", { fill: "currentColor", d: "M8 0a8 8 0 0 0-2.53 15.59c.4.074.547-.173.547-.385 0-.19-.007-.693-.01-1.36-2.226.484-2.695-1.073-2.695-1.073-.364-.924-.89-1.17-.89-1.17-.726-.496.055-.486.055-.486.803.056 1.225.824 1.225.824.714 1.223 1.872.87 2.328.665.072-.517.28-.87.508-1.07-1.777-.202-3.645-.888-3.645-3.956 0-.874.31-1.588.823-2.148-.083-.202-.357-1.017.078-2.12 0 0 .672-.215 2.2.82A7.65 7.65 0 0 1 8 4.8c.68.003 1.365.092 2.004.27 1.527-1.035 2.197-.82 2.197-.82.437 1.103.162 1.918.08 2.12.513.56.822 1.274.822 2.148 0 3.076-1.872 3.752-3.654 3.95.288.248.544.735.544 1.482 0 1.07-.01 1.932-.01 2.195 0 .214.144.463.55.384A8.001 8.001 0 0 0 8 0Z" })); }
2066
2241
  /**
2067
2242
  * 弹窗原语。`list: true` 时走「列表类」三段式:固定头 / 滚动体 / 固定底,
2068
2243
  * 配 `.dsm-modal-list`(宽高都固定),滚动只发生在 body 这一层 —— 条目数变化时
2069
2244
  * 弹窗不跳、也不会出现嵌套滚动条。普通表单/确认类**不传 list**,结构保持原样。
2070
2245
  */
2246
+ /**
2247
+ * 工具描述:超过阈值就默认只显示两行,点「展开」看全文。
2248
+ *
2249
+ * 为什么需要:MCP 工具描述动辄几百字(官方 server 的说明普遍很长),全展开会把详情弹窗
2250
+ * 撑成一面墙,用户反而找不到工具名。截断 + 展开把「扫一遍有哪些工具」和「细读某一个」
2251
+ * 分成两步。纯 CSS 行数裁剪(`-webkit-line-clamp`),不做字符截断 —— 展开时给的是原文,
2252
+ * 所以宿主的 `toolDescriptionMaxLength` 设置不影响这里(默认 0 = 不截断)。
2253
+ */
2254
+ function ToolDesc(props) {
2255
+ var state = react.useState(false)
2256
+ var open = state[0]
2257
+ var setOpen = state[1]
2258
+ var text = String(props.text || '')
2259
+ var collapsible = text.length > 120
2260
+ return h("div", { className: "dsm-tool-desc-wrap" },
2261
+ h("div", { className: "dsm-tool-desc" + (collapsible && !open ? " dsm-tool-desc-clamp" : "") }, text),
2262
+ collapsible ? h("button", {
2263
+ type: "button",
2264
+ className: "dsm-tool-desc-toggle",
2265
+ onClick: function () { setOpen(!open) },
2266
+ }, open ? props.collapseLabel : props.expandLabel) : null)
2267
+ }
2268
+
2071
2269
  function Modal(props) { var ref = react.useRef(null); react.useEffect(function () { if (ref.current) ref.current.focus(); }, []); var body = props.list ? h("div", { className: "dsm-modal-body" }, props.children) : props.children; return h("div", { className: "dsm-mask", onMouseDown: function (e) { if (e.target === e.currentTarget) props.onClose(); } }, h("div", { ref: ref, tabIndex: -1, className: "dsm-modal" + (props.wide ? " dsm-modal-wide" : "") + (props.className ? " " + props.className : ""), role: "dialog", "aria-modal": "true", onKeyDown: function (e) { if (!handleModalEscape(e, props.onClose)) trapModalFocus(e.currentTarget, e); } }, h("div", { className: "dsm-modal-head" }, h("h3", { className: "dsm-modal-title" }, props.title), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: props.onClose }, props.closeLabel)), body)); }
2072
2270
  /**
2073
2271
  * 全插件唯一的「操作结果」呈现方式:成功 = 右下角浮层(固定定位,不改变页面高度),
@@ -2445,6 +2643,8 @@ function callApi(path, options) {
2445
2643
  }
2446
2644
  function submitImport(selection) { var sel = selection || upload; if (!sel) return; buildUploadPayload(sel).then(function (payload) { return post("/upload", payload, null); }).then(function (data) { var summary = summarizeImportResult(t, data); setResult({ ok: summary.ok, warning: summary.warning, text: summary.text }); if (summary.imported) { setModal(null); setUpload(null); } }).catch(function () {}); }
2447
2645
  var anyLocked = !!(snapshot.data && snapshot.data.anyLocked === true);
2646
+ // 场景内开关由档案定义:技能开关 / 来源开关 / 首选都置灰(用户裁定)。
2647
+ var sceneName = (snapshot.data && snapshot.data.activeScene) || null;
2448
2648
  var data = snapshot.data || { roots: [], trash: [], summary: { total: 0, enabled: 0, disabled: 0, issues: 0 } }, allRoots = data.roots || [], roots = visibleSkillRoots(allRoots), removedRoots = removedSkillRoots(allRoots), activeSource = roots.some(function (root) { return root.key === source; }) ? source : "";
2449
2649
  var createRoots = allRoots.filter(function (root) { return root.mutable === true; }), createOptions = createRoots.map(function (root) { return { value: root.key, label: rootDisplayName(t, root) }; }); if (!createOptions.length) createOptions.push({ value: "hub", label: t("root.hub") });
2450
2650
  // 新建技能的默认落点:hub(`tool-management/skills/`)。
@@ -2492,17 +2692,18 @@ function callApi(path, options) {
2492
2692
  function renderSkill(root, skill) {
2493
2693
  var enabled = isSkillEnabled(skill), key = skill.shadowedBy ? "status.shadowed" : skill.loadable === false ? "status.invalid" : enabled ? "status.enabled" : "status.disabled", cls = skill.shadowedBy ? "dsm-shadowed" : enabled ? "dsm-enabled" : "dsm-disabled";
2494
2694
  var canToggle = root.toggleable !== false && skill.loadable !== false;
2495
- return h("div", { key: skill.name, className: "dsm-row" }, h("div", { className: "dsm-main" }, h("div", { className: "dsm-name" }, skill.declaredName || skill.name), h("div", { className: "dsm-note" }, skill.description || t("note.missing")), skill.shadowedBy ? h("div", { className: "dsm-rule-hint" }, t("status.shadowed.hint", { name: skill.shadowedBy.name })) : null), h("div", { className: "dsm-tags" }, h("span", { className: "dsm-tag" }, rootDisplayName(t, root)), skill.preferred === true ? h("span", { className: "dsm-tag dsm-tag-on" }, t("status.preferred")) : null, !root.mutable ? h("span", { className: "dsm-tag" }, t("status.readonly")) : null, root.mutable && !root.deletable ? h("span", { className: "dsm-tag", title: t("status.notDeletable.hint") }, t("status.notDeletable")) : null), h("div", { className: "dsm-status " + cls }, t(key)), h("div", { className: "dsm-row-actions" }, canToggle && skill.shadowedBy ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", disabled: busy, title: t("btn.activate.title"), onClick: function () { activateShadowed(root, skill); } }, t("btn.activate")) : null, canToggle && !skill.shadowedBy ? h(Switch, { on: enabled, disabled: busy || root.enabled === false || anyLocked, label: t("skill.toggle") + " " + skill.name, onClick: function () { post(enabled ? "/disable" : "/enable", { root: root.key, name: skill.name }); } }) : null, skill.preferred === true ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", disabled: busy, title: t("btn.unprefer.title"), onClick: function () { clearPreferred(root, skill); } }, t("btn.unprefer")) : null, h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", onClick: function () { openDetail(root, skill); } }, t("btn.detail")), root.deletable === true ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", onClick: function () { setModal({ type: "trash-confirm", root: root.key, name: skill.name }); } }, t("btn.trash")) : null));
2695
+ return h("div", { key: skill.name, className: "dsm-row" }, h("div", { className: "dsm-main" }, h("div", { className: "dsm-name" }, skill.declaredName || skill.name), h("div", { className: "dsm-note" }, skill.description || t("note.missing")), skill.shadowedBy ? h("div", { className: "dsm-rule-hint" }, t("status.shadowed.hint", { name: skill.shadowedBy.name })) : null), h("div", { className: "dsm-tags" }, h("span", { className: "dsm-tag" }, rootDisplayName(t, root)), skill.preferred === true ? h("span", { className: "dsm-tag dsm-tag-on" }, t("status.preferred")) : null, !root.mutable ? h("span", { className: "dsm-tag" }, t("status.readonly")) : null, root.mutable && !root.deletable ? h("span", { className: "dsm-tag", title: t("status.notDeletable.hint") }, t("status.notDeletable")) : null), h("div", { className: "dsm-status " + cls }, t(key)), h("div", { className: "dsm-row-actions" }, canToggle && skill.shadowedBy ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", disabled: busy || !!sceneName, title: sceneName ? t("scene.switch.title", { scene: sceneName }) : t("btn.activate.title"), onClick: function () { activateShadowed(root, skill); } }, t("btn.activate")) : null, canToggle && !skill.shadowedBy ? h(Switch, { on: enabled, disabled: busy || root.enabled === false || anyLocked || !!sceneName, title: sceneName ? t("scene.switch.title", { scene: sceneName }) : "", label: t("skill.toggle") + " " + skill.name, onClick: function () { post(enabled ? "/disable" : "/enable", { root: root.key, name: skill.name }); } }) : null, skill.preferred === true ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", disabled: busy || !!sceneName, title: sceneName ? t("scene.switch.title", { scene: sceneName }) : t("btn.unprefer.title"), onClick: function () { clearPreferred(root, skill); } }, t("btn.unprefer")) : null, h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", onClick: function () { openDetail(root, skill); } }, t("btn.detail")), root.deletable === true ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", onClick: function () { setModal({ type: "trash-confirm", root: root.key, name: skill.name }); } }, t("btn.trash")) : null));
2496
2696
  }
2497
2697
  function renderRoot(root) {
2498
2698
  if (activeSource && activeSource !== root.key) return null;
2499
2699
  var displayName = rootDisplayName(t, root), filtered = root.skills.filter(function (skill) { return matchSkillQuery(Object.assign({}, skill, { rootKey: root.key, rootLabel: displayName }), query); }); if (query && !filtered.length) return null; var open = !!expanded[root.key] || !!query;
2500
2700
  var rootCount = root.count == null ? root.skills.length : root.count;
2501
- return h("section", { key: root.key, className: "dsm-source" }, h("div", { className: "dsm-source-head" }, h("button", { type: "button", className: "dsm-source-head-main", "aria-expanded": open, onClick: function () { setExpanded(Object.assign({}, expanded, { [root.key]: !open })); } }, 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" : "dsm-tag-off") }, 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)), root.scope !== "project" && root.defaultSource !== true && root.toggleable !== false ? h("span", { className: "dsm-source-actions" }, root.removable === true ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet dsm-btn-danger", disabled: busy, title: t("btn.source.remove.title"), onClick: function () { setModal({ type: "source-remove-confirm", key: root.key, name: displayName }); } }, t("btn.source.remove")) : null, h(Switch, { on: root.enabled, disabled: busy || anyLocked, label: t("source.toggle") + " " + displayName, onClick: function () { post(root.enabled ? "/source-disable" : "/source-enable", { root: root.key }); } })) : null), open ? h("div", { className: "dsm-source-body" }, filtered.length ? h(react.Fragment, null, h("div", { className: "dsm-table-head" }, h("span", null, t("table.skill")), h("span", null, t("filter.source")), h("span", null, t("table.status")), h("span", null, "")), filtered.map(function (skill) { return renderSkill(root, skill); })) : h("div", { className: "dsm-empty" }, query ? t("empty.search") : t("empty.source"))) : null);
2701
+ return h("section", { key: root.key, className: "dsm-source" }, h("div", { className: "dsm-source-head" }, h("button", { type: "button", className: "dsm-source-head-main", "aria-expanded": open, onClick: function () { setExpanded(Object.assign({}, expanded, { [root.key]: !open })); } }, 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" : "dsm-tag-off") }, 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)), root.scope !== "project" && root.defaultSource !== true && root.toggleable !== false ? h("span", { className: "dsm-source-actions" }, root.removable === true ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet dsm-btn-danger", disabled: busy, title: t("btn.source.remove.title"), onClick: function () { setModal({ type: "source-remove-confirm", key: root.key, name: displayName }); } }, t("btn.source.remove")) : null, h(Switch, { on: root.enabled, disabled: busy || anyLocked || !!sceneName, title: sceneName ? t("scene.switch.title", { scene: sceneName }) : "", label: t("source.toggle") + " " + displayName, onClick: function () { post(root.enabled ? "/source-disable" : "/source-enable", { root: root.key }); } })) : null), open ? h("div", { className: "dsm-source-body" }, filtered.length ? h(react.Fragment, null, h("div", { className: "dsm-table-head" }, h("span", null, t("table.skill")), h("span", null, t("filter.source")), h("span", null, t("table.status")), h("span", null, "")), filtered.map(function (skill) { return renderSkill(root, skill); })) : h("div", { className: "dsm-empty" }, query ? t("empty.search") : t("empty.source"))) : null);
2502
2702
  }
2503
2703
 
2504
2704
  var summary = data.summary || { total: 0, enabled: 0, disabled: 0, issues: 0 };
2505
2705
  var content = [h("style", { key: "css" }, CSS), h("div", { key: "head", className: "dsm-head" }, h("div", { className: "dsm-title-block" }, h("div", { className: "dsm-title-row" }, h("h2", { className: "dsm-title" }, t("title"))), h("p", { className: "dsm-desc" }, t("desc"))), h("div", { className: "dsm-actions" }, h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy || snapshot.loading, onClick: function () { refresh(false); } }, (busy || snapshot.loading) ? t("btn.refreshing") : t("btn.refresh")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: anyLocked, onClick: openCreate }, t("btn.create")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: anyLocked, onClick: function () { setResult(null); setUpload(null); setModal("import"); } }, t("btn.import")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy, onClick: function () { setResult(null); setModal("export"); } }, t("export.skills")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy, onClick: function () { setResult(null); setCustomForm({ path: "", label: "" }); setModal("custom-add"); } }, t("btn.custom.add")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: function () { setModal("trash"); } }, t("trash.btn.open")))), anyLocked ? h("div", { key: "lockbanner", className: "dsm-feedback dsm-warning", role: "status" }, t("lock.banner")) : null,
2706
+ sceneName ? h("div", { key: "scenebanner", className: "dsm-feedback dsm-warning", role: "status" }, t("scene.switch.banner", { scene: sceneName })) : null,
2506
2707
  h("div", { key: "summary", className: "dsm-summary" }, [[summary.total, "summary.total"], [summary.enabled, "summary.enabled"], [summary.issues, "summary.issues"]].map(function (item) { return h("div", { key: item[1], className: "dsm-stat" }, h("strong", null, item[0]), t(countKey(item[1], item[0]), { count: item[0] }).replace(String(item[0]), "")); })), h("div", { key: "filters", className: "dsm-filters" }, h("input", { className: "dsm-control dsm-search", value: query, "aria-label": t("search"), placeholder: t("search.placeholder"), onChange: function (e) { setQuery(e.target.value); } }), h("div", { className: "dsm-source-filter" }, h(SourceSelect, { value: activeSource, options: options, onChange: setSource }))), result && modal !== "import" ? h(Notice, { key: "result", kind: result.warning ? "warn" : result.ok ? "ok" : "err", text: result.text }) : null].concat((data.warnings || []).map(function (warning, index) { return h(Notice, { key: "warning-" + index, kind: "warn", text: translateError(t, warning) }); }), [snapshot.error ? h(Notice, { key: "error", kind: "err", text: snapshot.error }) : null, snapshot.loading && !snapshot.data ? h("div", { key: "loading", className: "dsm-empty" }, t("loading")) : h("div", { key: "sources", className: "dsm-sources" }, roots.map(renderRoot))]);
2507
2708
 
2508
2709
  if (modal === "create") content.push(h(Modal, { key: "create", title: t("create.title"), closeLabel: t("btn.close"), onClose: function () { setModal(null); } }, h("div", { className: "dsm-form" }, h("label", { className: "dsm-field" }, h("span", { className: "dsm-label" }, t("create.target")), h(SourceSelect, { value: form.root, options: createOptions, onChange: function (value) { updateForm("root", value); } })), [["name", "create.name", "create.name.placeholder"], ["description", "create.description", "create.description.placeholder"]].map(function (field) { return h("label", { key: field[0], className: "dsm-field" }, h("span", { className: "dsm-label" }, t(field[1])), h("input", { className: "dsm-control", value: form[field[0]], placeholder: t(field[2]), onChange: function (e) { updateForm(field[0], e.target.value); } })); }), h("label", { className: "dsm-field" }, h("span", { className: "dsm-label" }, t("create.body")), h("textarea", { className: "dsm-control", value: form.body, placeholder: t("create.body.placeholder"), onChange: function (e) { updateForm("body", e.target.value); } })), h("p", { className: "dsm-help" }, t("create.chat.note"))), h("div", { className: "dsm-modal-actions" }, h("button", { className: "dsm-btn", disabled: busy || !form.name.trim() || !form.description.trim() || !form.body.trim(), onClick: submitCreate }, t("btn.create.now")))));
@@ -2669,6 +2870,7 @@ function callApi(path, options) {
2669
2870
  kind: meta ? meta.kind : null,
2670
2871
  registered: !!(meta && meta.registered),
2671
2872
  canRegister: !!(meta && meta.canRegister),
2873
+ dirMissing: !!(meta && meta.dirMissing),
2672
2874
  ungrouped: key === 'ungrouped',
2673
2875
  items: items2, maxAt: maxAt,
2674
2876
  })
@@ -2695,7 +2897,7 @@ function callApi(path, options) {
2695
2897
  items2.sort(function (a, b) { return ((b.archivedAt || b.createdAt) || 0) - ((a.archivedAt || a.createdAt) || 0) })
2696
2898
  var maxAt = 0
2697
2899
  items2.forEach(function (it) { var at = (it.archivedAt || it.createdAt) || 0; if (at > maxAt) maxAt = at })
2698
- groups.push({ key: key, title: title, workspaceId: wid, path: wsRec && wsRec.path, kind: null, registered: false, canRegister: false, ungrouped: key === 'ungrouped', items: items2, maxAt: maxAt })
2900
+ groups.push({ key: key, title: title, workspaceId: wid, path: wsRec && wsRec.path, kind: null, registered: false, canRegister: false, dirMissing: false, ungrouped: key === 'ungrouped', items: items2, maxAt: maxAt })
2699
2901
  })
2700
2902
  // 组按组内最新时间戳降序;未分组固定最后。
2701
2903
  groups.sort(function (g1, g2) {
@@ -2990,9 +3192,10 @@ function callApi(path, options) {
2990
3192
  React.createElement('span', { className: 'dsm-source-title dsm-hist-group-title', title: g.title }, g.title),
2991
3193
  React.createElement('span', { className: 'dsm-count' }, t('hist.group.count', { count: g.items.length }))),
2992
3194
  detached ? React.createElement('div', { className: 'dsm-hist-group-extra' },
2993
- React.createElement('span', { className: 'dsm-tag' + (g.registered ? '' : ' dsm-tag-off'), title: t('hist.group.removed.hint') },
3195
+ React.createElement('span', { className: 'dsm-tag', title: t('hist.group.removed.hint') },
2994
3196
  t('hist.group.removed')),
2995
- g.canRegister ? React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, title: t('hist.btn.registerWs.hint'), onClick: function () { setModal({ type: 'register', path: g.path, title: g.title }) } }, t('hist.btn.registerWs')) : null) : null,
3197
+ g.canRegister ? React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, title: t('hist.btn.registerWs.hint'), onClick: function () { setModal({ type: 'register', path: g.path, title: g.title }) } }, t('hist.btn.registerWs')) : null,
3198
+ g.dirMissing ? React.createElement('span', { className: 'dsm-hist-dir-missing', title: t('hist.group.dirMissing.hint') }, '⚠ ' + t('hist.group.dirMissing')) : null) : null,
2996
3199
  g.path ? React.createElement('span', { className: 'dsm-path', title: g.path }, g.path) : null),
2997
3200
  open ? React.createElement('div', { className: 'dsm-source-body' }, g.items.map(renderRow)) : null)
2998
3201
  }
@@ -3428,6 +3631,7 @@ function callApi(path, options) {
3428
3631
  setModal({ type: 'scene-archive', name: name, drill: null, memQuery: '', skillQuery: '', subQuery: '', mcpQuery: '',
3429
3632
  sections: {
3430
3633
  mcp: archive.mcp ? Object.assign({}, archive.mcp) : null,
3634
+ mcpNotes: archive.mcpNotes && Object.keys(archive.mcpNotes).length ? Object.assign({}, archive.mcpNotes) : null,
3431
3635
  skills: Array.isArray(archive.skills) ? archive.skills.slice() : null,
3432
3636
  subagents: Array.isArray(archive.subagents) ? archive.subagents.slice() : null,
3433
3637
  memories: Array.isArray(archive.memories) ? archive.memories.slice() : null,
@@ -3451,14 +3655,26 @@ function callApi(path, options) {
3451
3655
  else mcp[server] = '*'
3452
3656
  setSections(Object.assign({}, sections, { mcp: mcp }))
3453
3657
  }
3658
+ /**
3659
+ * 场景级 MCP 备注(v0.8.1):该场景进入时覆盖全局备注、退出恢复。
3660
+ * 空值 = 删除该服务器的场景备注;mcpNotes 全空 → null(等同未定义,保存时不上报)。
3661
+ */
3662
+ function setMcpNote(server, value) {
3663
+ var sections = modalSections(); if (!sections) return
3664
+ var notes = Object.assign({}, sections.mcpNotes || {})
3665
+ var v = String(value == null ? '' : value).trim()
3666
+ if (v) notes[server] = v
3667
+ else delete notes[server]
3668
+ setSections(Object.assign({}, sections, { mcpNotes: Object.keys(notes).length ? notes : null }))
3669
+ }
3454
3670
  /**
3455
3671
  * 新加段一律**默认不勾选**(用户明确要求:MCP 工具集 / 技能集 / 子智能体绑定都不预勾)。
3456
3672
  *
3457
3673
  * 取 2026-09-13 前的「预勾当前运行时状态」是错的方向:用户点「添加」只是想开始配,
3458
3674
  * 不是想把自己现有的启用状态抄进档案。代价要讲清楚——勾选集语义是「勾 = 启用」,
3459
- * 空段 = 全部停用,所以「添加 MCP 工具集」当下就等价于「该场景下全部 MCP 停用」,
3675
+ * 空段 = 全部停用(未定义段也是同一个意思,见 archive-engine 的四域同口径),
3676
+ * 所以「添加 MCP 工具集」当下就等价于「该场景下全部 MCP 停用」,
3460
3677
  * 段脚注(`memory.archive.emptySection`)与弹窗顶部说明会同时把这句话显示出来。
3461
- * 子智能体段例外:空段 = 不限制(全部人设可用,见 filterBySceneBinding)。
3462
3678
  */
3463
3679
  function emptyMcpPreset() { return {} }
3464
3680
  /** 全选:列出全部服务器(含未运行的)并各勾「全部工具」。 */
@@ -3565,38 +3781,11 @@ function callApi(path, options) {
3565
3781
  : React.createElement('div', { className: 'dsm-pick-empty' }, q ? t('scenes.mem.noMatch') : t('scenes.mem.emptyScene'))),
3566
3782
  })
3567
3783
  }
3568
- /** 段 1:MCP 工具集(服务器级勾选 → 行内「选工具」进工具明细,明细留在同一段内)。 */
3784
+ /** 段 1:MCP 工具集(服务器级勾选 → 行内「编辑」打开固定尺寸弹窗:备注 + 工具勾选)。 */
3569
3785
  function mcpSeg() {
3570
3786
  var sections = modalSections()
3571
3787
  var defined = !!sections.mcp
3572
3788
  var servers = modal.inventory.mcpServers || []
3573
- if (modal.drill) {
3574
- var spec = (sections.mcp || {})[modal.drill]
3575
- var known = drillTools
3576
- var checkedCount = spec === '*' ? (known || []).length : (Array.isArray(spec) ? spec.length : 0)
3577
- return seg({
3578
- title: t('scenes.mcp.toolsOf') + ' · ' + modal.drill,
3579
- count: t('scenes.seg.checked', { checked: checkedCount, total: (known || []).length }),
3580
- actions: [
3581
- React.createElement('button', { key: 'back', type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { setModal(Object.assign({}, modal, { drill: null })) } }, t('scenes.mcp.back')),
3582
- React.createElement('button', { key: 'all', type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || !known, onClick: function () { mcpDrillSpec('*') } }, t('scenes.seg.selectAll')),
3583
- React.createElement('button', { key: 'none', type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { mcpDrillSpec([]) } }, t('scenes.seg.clear')),
3584
- ],
3585
- body: known === null
3586
- ? React.createElement('div', { className: 'dsm-seg-body' }, React.createElement('div', { className: 'dsm-pick-empty' }, t('memory.loading')))
3587
- : segList(known.map(function (item) {
3588
- var on = spec === '*' ? true : (Array.isArray(spec) && spec.indexOf(item.short) >= 0)
3589
- return pickRow({
3590
- disabled: busy,
3591
- key: item.key,
3592
- checked: on,
3593
- name: item.short,
3594
- onChange: function () { toggleDrillTool(modal.drill, item.short) },
3595
- })
3596
- }), t('scenes.mcp.noTools')),
3597
- foot: React.createElement('div', { className: 'dsm-seg-foot' }, t('scenes.mcp.drillHint')),
3598
- })
3599
- }
3600
3789
  return seg({
3601
3790
  title: t('memory.archive.tools'),
3602
3791
  count: defined ? t('scenes.seg.checked', { checked: Object.keys(sections.mcp).length, total: servers.length }) : t('scenes.archive.sectionOff'),
@@ -3616,17 +3805,23 @@ function callApi(path, options) {
3616
3805
  var selected = sections.mcp[server.name] !== undefined
3617
3806
  var spec2 = sections.mcp[server.name]
3618
3807
  var specText = !selected ? '' : spec2 === '*' ? t('scenes.mcp.allTools') : t('scenes.mcp.pickedCount', { count: spec2.length })
3808
+ var hasNote = Boolean((sections.mcpNotes || {})[server.name])
3809
+ // 工具数未知(从未运行过且无记录):显示引导而不是误导性的 0。
3810
+ var desc = server.toolCount === null || server.toolCount === undefined
3811
+ ? (server.live || server.serverDisabled ? null : t('scenes.mcp.unknownCount'))
3812
+ : t('scenes.mcp.toolCount', { count: server.toolCount })
3619
3813
  return pickRow({
3620
- disabled: busy,
3814
+ disabled: busy,
3621
3815
  key: server.name,
3622
3816
  checked: selected,
3623
3817
  name: server.name,
3624
- desc: server.toolCount === null || server.toolCount === undefined ? null : t('scenes.mcp.toolCount', { count: server.toolCount }),
3818
+ desc: desc,
3625
3819
  meta: [
3626
3820
  server.live ? null : React.createElement('span', { key: 'nr', className: 'dsm-tag dsm-tag-off' }, t('scenes.mcp.notRunning')),
3627
3821
  specText ? React.createElement('span', { key: 'spec' }, specText) : null,
3822
+ hasNote ? React.createElement('span', { key: 'note', className: 'dsm-tag' }, t('scenes.mcp.hasNote')) : null,
3628
3823
  ],
3629
- actions: selected ? React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function (e) { e.preventDefault(); e.stopPropagation(); openDrill(server.name) } }, t('scenes.mcp.pickTools')) : null,
3824
+ actions: selected ? React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function (e) { e.preventDefault(); e.stopPropagation(); openDrill(server.name) } }, t('scenes.mcp.edit')) : null,
3630
3825
  onChange: function () { toggleMcpServer(server.name) },
3631
3826
  })
3632
3827
  }))
@@ -3635,6 +3830,53 @@ function callApi(path, options) {
3635
3830
  foot: archiveSegFoot(defined, defined ? Object.keys(sections.mcp).length : 0),
3636
3831
  })
3637
3832
  }
3833
+ /**
3834
+ * 服务器编辑弹窗(v0.8.1 重做):固定尺寸的次级弹窗,第一栏是场景备注输入框,
3835
+ * 第二栏是工具勾选列表 —— 「选工具」并入这里,行内不再塞备注输入框。
3836
+ */
3837
+ function mcpEditNode() {
3838
+ var sections = modalSections(); if (!sections) return null
3839
+ var server = modal.drill
3840
+ var spec = (sections.mcp || {})[server]
3841
+ var noteVal = (sections.mcpNotes || {})[server] || ''
3842
+ var known = drillTools
3843
+ var checkedCount = spec === '*' ? (known || []).length : (Array.isArray(spec) ? spec.length : 0)
3844
+ return React.createElement('div', { className: 'dsm-mask', key: 'mcp-edit' },
3845
+ React.createElement('div', { className: 'dsm-modal' },
3846
+ React.createElement('div', { className: 'dsm-modal-head' },
3847
+ React.createElement('h3', { className: 'dsm-modal-title' }, t('scenes.mcp.edit') + ' · ' + server + '(' + t('scenes.seg.checked', { checked: checkedCount, total: (known || []).length }) + ')'),
3848
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { setModal(Object.assign({}, modal, { drill: null })) } }, t('scenes.mcp.done'))),
3849
+ React.createElement('div', { className: 'dsm-modal-body' },
3850
+ React.createElement('div', { className: 'dsm-field' },
3851
+ React.createElement('label', { className: 'dsm-label' }, t('scenes.mcp.noteLabel')),
3852
+ React.createElement('input', { className: 'dsm-control', type: 'text', value: noteVal, maxLength: 200, placeholder: t('scenes.mcp.notePlaceholder'), onChange: function (e) { setMcpNote(server, e.target.value) } }),
3853
+ React.createElement('p', { className: 'dsm-help' }, t('scenes.mcp.noteHint'))),
3854
+ React.createElement('div', { className: 'dsm-field' },
3855
+ React.createElement('div', { className: 'dsm-mcp-edit-tools-head' },
3856
+ React.createElement('label', { className: 'dsm-label' }, t('scenes.mcp.toolsOf')),
3857
+ React.createElement('div', { className: 'dsm-mcp-edit-tools-actions' },
3858
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || !known, onClick: function () { mcpDrillSpec('*') } }, t('scenes.seg.selectAll')),
3859
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { mcpDrillSpec([]) } }, t('scenes.seg.clear')))),
3860
+ known === null
3861
+ ? React.createElement('div', { className: 'dsm-seg-body' }, React.createElement('div', { className: 'dsm-pick-empty' }, t('memory.loading')))
3862
+ : (known || []).length
3863
+ ? segList((known || []).map(function (item) {
3864
+ var on = spec === '*' ? true : (Array.isArray(spec) && spec.indexOf(item.short) >= 0)
3865
+ return pickRow({
3866
+ disabled: busy,
3867
+ key: item.key,
3868
+ checked: on,
3869
+ name: item.short,
3870
+ onChange: function () { toggleDrillTool(server, item.short) },
3871
+ })
3872
+ }), t('scenes.mcp.noTools'))
3873
+ : React.createElement('div', { className: 'dsm-seg-body' },
3874
+ React.createElement('div', { className: 'dsm-pick-empty' }, t('scenes.mcp.noTools')),
3875
+ modal.drillError ? React.createElement('div', { className: 'dsm-seg-empty-error' }, modal.drillError) : null,
3876
+ React.createElement('div', { className: 'dsm-seg-empty-actions' },
3877
+ React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: drillTools === null, onClick: probeMcpTools }, t('scenes.mcp.probeTools')))),
3878
+ React.createElement('div', { className: 'dsm-seg-foot' }, t('scenes.mcp.drillHint'))))))
3879
+ }
3638
3880
  /** 段 2:技能集。 */
3639
3881
  /**
3640
3882
  * 技能行的人类可读展示:技能名 + 来源名。
@@ -3697,7 +3939,7 @@ function callApi(path, options) {
3697
3939
  descTitle: d.key,
3698
3940
  meta: item.shadowed
3699
3941
  ? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, t('status.shadowed'))
3700
- : (item.enabled === false ? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, t('memory.scene.off')) : null),
3942
+ : (item.enabled === false ? React.createElement('span', { className: 'dsm-tag' }, t('memory.scene.off')) : null),
3701
3943
  onChange: function () {
3702
3944
  var list = sections.skills.slice(); var i = list.indexOf(item.key)
3703
3945
  if (i >= 0) list.splice(i, 1); else list.push(item.key)
@@ -3790,7 +4032,7 @@ function callApi(path, options) {
3790
4032
  */
3791
4033
  function archiveNode() {
3792
4034
  if (!modal || modal.type !== 'scene-archive') return null
3793
- return React.createElement(Modal, { key: 'sarch', wide: true, className: 'dsm-modal-archive', title: t('memory.archive.title') + ' · ' + (modal.name === '' ? t('memory.scene.global') : modal.name), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
4035
+ var main = React.createElement(Modal, { key: 'sarch', wide: true, className: 'dsm-modal-archive', title: t('memory.archive.title') + ' · ' + (modal.name === '' ? t('memory.scene.global') : modal.name), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
3794
4036
  React.createElement('div', { className: 'dsm-form' },
3795
4037
  React.createElement('div', { className: 'dsm-archive-meta' },
3796
4038
  React.createElement('span', null, t('scenes.archive.summary', {
@@ -3805,15 +4047,27 @@ function callApi(path, options) {
3805
4047
  memoriesSeg(),
3806
4048
  React.createElement('div', { className: 'dsm-modal-actions' },
3807
4049
  React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-primary', disabled: busy, onClick: submitArchive }, t('memory.archive.save')))))
4050
+ // MCP 服务器编辑弹窗渲染在外层:不能放进列表 Modal 的滚动容器里(会被裁掉)。
4051
+ var edit = modal.drill ? mcpEditNode() : null
4052
+ return edit ? React.createElement(React.Fragment, { key: 'sarch-wrap' }, main, edit) : main
3808
4053
  }
3809
4054
  function openDrill(server) {
3810
- setModal(Object.assign({}, modal, { drill: server }))
4055
+ setModal(Object.assign({}, modal, { drill: server, drillError: null }))
3811
4056
  setDrillTools(null)
3812
4057
  apiCall('mcpm-tools', { serverName: server }).then(function (res) {
3813
4058
  if (res && res.ok) setDrillTools((res.tools || []).map(function (x) { return { key: server + '/' + x.name, short: x.name, enabled: x.enabled !== false } }))
3814
4059
  else setDrillTools([])
3815
4060
  }).catch(function () { setDrillTools([]) })
3816
4061
  }
4062
+ /** 从未运行过的服务器:临时启动枚举工具(后端会轮询并在完成后恢复原启停状态)。 */
4063
+ function probeMcpTools() {
4064
+ var server = modal.drill
4065
+ setDrillTools(null)
4066
+ apiCall('mcpm-tools-refresh', { serverName: server }).then(function (res) {
4067
+ if (res && res.ok) setDrillTools((res.tools || []).map(function (x) { return { key: server + '/' + x.name, short: x.name, enabled: x.enabled !== false } }))
4068
+ else { setDrillTools([]); setModal(Object.assign({}, modal, { drillError: res && res.error ? String(res.error) : '' })) }
4069
+ }).catch(function (e) { setDrillTools([]); setModal(Object.assign({}, modal, { drillError: String((e && e.message) || e) })) })
4070
+ }
3817
4071
  function toggleDrillTool(server, short) {
3818
4072
  var sections = modalSections(); if (!sections) return
3819
4073
  var mcp = Object.assign({}, sections.mcp || {})
@@ -3828,11 +4082,13 @@ function callApi(path, options) {
3828
4082
  setBusy(true)
3829
4083
  // P5:档案里**不再写 memories 段** —— 记忆的开关是 `rules[*].enabled` 单一真相源,
3830
4084
  // 在段里勾选时已经即时提交了,这里没有「待保存」的记忆状态。
3831
- apiCall('scene-archive-save', { scene: modal.name, archive: (function () { var payload = {}; if (modal.sections.mcp) payload.mcp = modal.sections.mcp; if (modal.sections.skills) payload.skills = modal.sections.skills; if (modal.sections.subagents) payload.subagents = modal.sections.subagents; return payload })() }).then(function (res) {
4085
+ apiCall('scene-archive-save', { scene: modal.name, archive: (function () { var payload = {}; if (modal.sections.mcp) payload.mcp = modal.sections.mcp; if (modal.sections.mcpNotes) payload.mcpNotes = modal.sections.mcpNotes; if (modal.sections.skills) payload.skills = modal.sections.skills; if (modal.sections.subagents) payload.subagents = modal.sections.subagents; return payload })() }).then(function (res) {
3832
4086
  setBusy(false)
3833
4087
  if (res && res.ok) {
3834
4088
  setModal(null)
3835
- setResult({ ok: true, text: t('memory.result.archiveSaved', { name: modal.name }) + (res.stale && res.stale.length ? ' · ' + t('memory.archive.stale', { items: res.stale.join('、') }) : '') })
4089
+ // 当前模式的档案保存后**就地生效**;运行时没应用成功要如实说(别假装生效了)。
4090
+ var applyNote = res.applyError ? ' · ' + t('memory.archive.applyFailed', { reason: res.applyError }) : ''
4091
+ setResult({ ok: !res.applyError, text: t('memory.result.archiveSaved', { name: modal.name }) + (res.stale && res.stale.length ? ' · ' + t('memory.archive.stale', { items: res.stale.join('、') }) : '') + applyNote })
3836
4092
  refresh(true)
3837
4093
  } else setResult({ ok: false, text: translateError(t, res) })
3838
4094
  }).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
@@ -3965,6 +4221,9 @@ function callApi(path, options) {
3965
4221
  var boundPrompt = scene.prompt || ''
3966
4222
  var promptLive = !!(boundPrompt && data.scenePrompt && data.scenePrompt.scene === name)
3967
4223
  var promptMissing = promptLive && data.scenePrompt.missing === true
4224
+ // 生效中但 AGENTS.md 里不是它(被手动改过?)——「显示必须等于实际注入」,
4225
+ // 不一致就如实标出来,别让用户以为文件里就是它。
4226
+ var promptMismatch = promptLive && !promptMissing && data.scenePrompt.duplicate !== true
3968
4227
  return React.createElement('article', { key: 's:' + name, className: 'dsm-scene-tile' + (modeScene === name ? ' dsm-scene-tile-mode' : '') },
3969
4228
  React.createElement('div', { className: 'dsm-scene-tile-head' },
3970
4229
  React.createElement('span', { className: 'dsm-scene-tile-name', title: label }, label),
@@ -3973,13 +4232,14 @@ function callApi(path, options) {
3973
4232
  locked ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('memory.scene.shared')) : null,
3974
4233
  modeScene === name ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('memory.mode.current')) : null,
3975
4234
  sceneLocked ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('scenes.lock.tag')) : null,
3976
- !locked && scene.active === false ? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, t('memory.scene.off')) : null,
4235
+ !locked && scene.active === false ? React.createElement('span', { className: 'dsm-tag' }, t('memory.scene.off')) : null,
3977
4236
  boundPrompt ? React.createElement('span', {
3978
4237
  className: 'dsm-tag' + (promptMissing ? ' dsm-tag-off' : promptLive ? ' dsm-tag-on' : ''),
3979
- title: t('scenes.prompt.tag.hint'),
4238
+ title: promptMismatch ? t('scenes.prompt.mismatch.hint', { id: boundPrompt }) : t('scenes.prompt.tag.hint'),
3980
4239
  }, promptMissing
3981
4240
  ? t('scenes.prompt.missing', { id: boundPrompt })
3982
4241
  : promptLive ? t('scenes.prompt.live', { id: boundPrompt }) : t('scenes.prompt.bound', { id: boundPrompt })) : null,
4242
+ promptMismatch ? React.createElement('span', { className: 'dsm-tag dsm-tag-off', title: t('scenes.prompt.mismatch.hint', { id: boundPrompt }) }, t('agm.file.mismatch')) : null,
3983
4243
  locked ? null : React.createElement('span', {
3984
4244
  className: 'dsm-scene-tile-switch',
3985
4245
  title: blockedByOther ? t('scenes.enable.blocked', { name: sceneLabel(data.scenes, data.activeScene) || data.activeScene || '' }) : (sceneLocked && scene.active === true ? t('scenes.lock.blockedExit') : ''),
@@ -4060,6 +4320,8 @@ function callApi(path, options) {
4060
4320
  var modal = ms[0], setModal = ms[1]
4061
4321
  // 场景锁定(v0.8):任一场景锁定 = 五个域整体冻结;本页全部写控件禁用。
4062
4322
  var anyLocked = data.anyLocked === true
4323
+ // 场景内开关由档案定义:人设开关置灰(要改就去场景页的档案编辑器里改)。
4324
+ var sceneName = data.activeScene || null
4063
4325
  // 回收站:删除人设 = 移入回收站(宿主侧 subagent-delete),这里列出/恢复/永久删除。
4064
4326
  var ts = React.useState(null)
4065
4327
  var trash = ts[0], setTrash = ts[1]
@@ -4098,7 +4360,7 @@ function callApi(path, options) {
4098
4360
  }
4099
4361
  React.useEffect(function () { refresh() }, [])
4100
4362
  /**
4101
- * 子智能体开关(与记忆页同一套交互):停用 = 不注入目录段、subagent_list/run
4363
+ * 子智能体开关(与记忆页同一套交互):停用 = 不注入目录段、subagent_manager_list/run
4102
4364
  * 不可见;人设文件不动。开关联动全局状态,立即提交。
4103
4365
  */
4104
4366
  function togglePersona(p) {
@@ -4151,7 +4413,7 @@ function callApi(path, options) {
4151
4413
  }
4152
4414
  function openEditor(name) {
4153
4415
  if (!name) {
4154
- setModal({ type: 'editor', mode: 'create', advanced: false, openMode: null, modeQuery: '', legacyTarget: '', form: { name: '', description: '', provider: '', model: '', tools: [], toolsDeny: [], toolsByPreset: {}, body: '', error: null } })
4416
+ setModal({ type: 'editor', mode: 'create', advanced: false, openMode: null, stoppedRules: {}, modeQuery: '', legacyTarget: '', form: { name: '', description: '', provider: '', model: '', tools: [], toolsDeny: [], toolsByPreset: {}, body: '', error: null } })
4155
4417
  return
4156
4418
  }
4157
4419
  setBusy(true)
@@ -4159,11 +4421,15 @@ function callApi(path, options) {
4159
4421
  setBusy(false)
4160
4422
  if (res && res.ok) {
4161
4423
  var p = res.persona || {}
4162
- setModal({ type: 'editor', mode: 'edit', originalName: String(p.name || name), advanced: initialAdvanced(p), openMode: null, modeQuery: '', legacyTarget: '', form: {
4424
+ var advanced = initialAdvanced(p)
4425
+ setModal({ type: 'editor', mode: 'edit', originalName: String(p.name || name), advanced: advanced, openMode: null, stoppedRules: {}, modeQuery: '', legacyTarget: '', form: {
4163
4426
  name: p.name || name, description: p.description || '', provider: p.provider || '', model: p.model || '',
4164
4427
  tools: (p.tools || []).slice(), toolsDeny: (p.toolsDeny || []).slice(),
4165
4428
  toolsByPreset: cloneRules(p.toolsByPreset), body: p.body || '', error: null,
4166
4429
  } })
4430
+ // 已配过限制的人设**一打开就是展开的**(initialAdvanced)→ 候选数据必须在这里也拉,
4431
+ // 否则四行模式先亮"宿主没有回传预设名单",非得点两次「高级选项」才补上(用户实测)。
4432
+ if (advanced) loadCandidates()
4167
4433
  } else setResult({ ok: false, text: translateError(t, res) })
4168
4434
  }).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
4169
4435
  }
@@ -4229,7 +4495,9 @@ function callApi(path, options) {
4229
4495
  * **已勾选的名字保留**(只换语义),这样"改成黑名单"不用从头再勾一遍。
4230
4496
  */
4231
4497
  function startMode(id, mode) {
4232
- var prev = modeRule(id)
4498
+ // 刚被「关闭」掉的名字记在 stoppedRules 里:重新启用时带回,免得"关一下再开"把
4499
+ // 已勾好的名单清空(用户实测踩到:以为「关闭白名单」是收起设置,再启用就空了)。
4500
+ var prev = modeRule(id) || (modal.stoppedRules || {})[id]
4233
4501
  var names = prev && Array.isArray(prev.names) ? prev.names.slice() : []
4234
4502
  setModal(Object.assign({}, modal, {
4235
4503
  openMode: id, modeQuery: '',
@@ -4238,7 +4506,18 @@ function callApi(path, options) {
4238
4506
  }),
4239
4507
  }))
4240
4508
  }
4241
- function stopMode(id) { setModeRule(id, null) }
4509
+ function stopMode(id) {
4510
+ var prev = modeRule(id)
4511
+ var stopped = Object.assign({}, modal.stoppedRules || {})
4512
+ if (prev) stopped[id] = { mode: prev.mode, names: (prev.names || []).slice() }
4513
+ var next = Object.assign({}, modal.form.toolsByPreset || {})
4514
+ delete next[id]
4515
+ setModal(Object.assign({}, modal, {
4516
+ stoppedRules: stopped,
4517
+ openMode: modal.openMode === id ? null : modal.openMode,
4518
+ form: Object.assign({}, modal.form, { toolsByPreset: next }),
4519
+ }))
4520
+ }
4242
4521
  function toggleModeTool(id, name) {
4243
4522
  var rule = modeRule(id)
4244
4523
  if (!rule) return
@@ -4313,15 +4592,29 @@ function callApi(path, options) {
4313
4592
  ? (isDeny ? 'subagents.mode.stopDeny' : 'subagents.mode.stopAllow')
4314
4593
  : (isDeny ? 'subagents.mode.startDeny' : 'subagents.mode.startAllow')))
4315
4594
  }
4595
+ var open = modal.openMode === id
4316
4596
  return React.createElement('div', { className: 'dsm-mode-row' + (rule ? ' dsm-mode-on' : ''), key: id },
4317
4597
  React.createElement('div', { className: 'dsm-mode-head' },
4318
- React.createElement('span', { className: 'dsm-mode-name' }, presetLabel(p)),
4319
- p.broken ? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, t('compat.reach.broken')) : null,
4320
- React.createElement('span', { className: 'dsm-mode-sum ' + summary.cls }, summary.text),
4598
+ // 行首按钮 = 展开 / 收起名单(用户实测:已配好的模式只有「关闭白名单」可点,
4599
+ // 点下去是把模式停掉、名单设置再也进不去 —— 查看/编辑与启用/停用必须分开)。
4600
+ React.createElement('button', {
4601
+ type: 'button',
4602
+ className: 'dsm-mode-head-main',
4603
+ disabled: !rule,
4604
+ title: rule ? t('subagents.mode.toggleHint') : '',
4605
+ 'aria-expanded': open ? 'true' : 'false',
4606
+ onClick: function () {
4607
+ setModal(Object.assign({}, modal, { openMode: open ? null : id, modeQuery: '' }))
4608
+ },
4609
+ },
4610
+ React.createElement('span', { className: 'dsm-mode-caret', 'aria-hidden': 'true' }, rule ? (open ? '▾' : '▸') : ''),
4611
+ React.createElement('span', { className: 'dsm-mode-name' }, presetLabel(p)),
4612
+ p.broken ? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, t('compat.reach.broken')) : null,
4613
+ React.createElement('span', { className: 'dsm-mode-sum ' + summary.cls }, summary.text)),
4321
4614
  React.createElement('span', { className: 'dsm-mode-actions' },
4322
4615
  startButton('allow', false),
4323
4616
  startButton('deny', true))),
4324
- modal.openMode === id ? modeEditor(p) : null)
4617
+ open ? modeEditor(p) : null)
4325
4618
  })),
4326
4619
  React.createElement('p', { className: 'dsm-help' }, t('subagents.field.modes.hint')))
4327
4620
  }
@@ -4422,6 +4715,7 @@ function callApi(path, options) {
4422
4715
  React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || !data.subagents.length, onClick: function () { setResult(null); setModal({ type: 'export' }) } }, t('export.subagents')),
4423
4716
  React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: function () { loadTrash(false) } }, t('trash.btn.open')))),
4424
4717
  anyLocked === true ? React.createElement('div', { className: 'dsm-feedback dsm-warning', role: 'status' }, t('lock.banner')) : null,
4718
+ sceneName ? React.createElement('div', { className: 'dsm-feedback dsm-warning', role: 'status' }, t('scene.switch.banner', { scene: sceneName })) : null,
4425
4719
  React.createElement('div', { className: 'dsm-summary', style: { '--dsm-stat-cols': '2' } },
4426
4720
  [['total', data.subagents.length, t('subagents.stat.total')],
4427
4721
  ['limited', data.subagents.filter(function (p) { return (p.tools && p.tools.length) || (p.toolsDeny && p.toolsDeny.length) || (p.toolsByPreset && Object.keys(p.toolsByPreset).length) }).length, t('subagents.stat.limited')]].map(function (item) {
@@ -4439,7 +4733,7 @@ function callApi(path, options) {
4439
4733
  React.createElement('span', { className: 'dsm-note' }, p.description || ''),
4440
4734
  p.enabled === false ? React.createElement('span', { className: 'dsm-tag', title: t('subagents.disabled.hint') }, t('subagents.disabled')) : null),
4441
4735
  React.createElement('div', { className: 'dsm-source-actions' },
4442
- React.createElement(Switch, { on: p.enabled !== false, disabled: busy || anyLocked, label: t('subagents.toggle') + ' ' + p.name, onClick: function () { togglePersona(p) } }),
4736
+ React.createElement(Switch, { on: p.enabled !== false, disabled: busy || anyLocked || !!sceneName, title: sceneName ? t('scene.switch.title', { scene: sceneName }) : '', label: t('subagents.toggle') + ' ' + p.name, onClick: function () { togglePersona(p) } }),
4443
4737
  React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || anyLocked, onClick: function () { openEditor(p.name) } }, t('memory.edit')),
4444
4738
  React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: busy || anyLocked, onClick: function () { setModal({ type: 'delete', name: p.name }) } }, t('memory.delete')))))
4445
4739
  })) : React.createElement('div', { className: 'dsm-empty' }, t('subagents.empty')),
@@ -4931,8 +5225,10 @@ function callApi(path, options) {
4931
5225
  var isGlobal = meta.global === true || name === 'global'
4932
5226
  var isOrphan = name === '' && !bucket.meta
4933
5227
  var open = !collapsed['s:' + name]
4934
- // 游离记忆(没有归属场景)用错误色提示:它们不会进系统提示词。
4935
- return React.createElement('div', { key: 's:' + name, className: 'dsm-source' + (meta.active === false || isOrphan ? ' dsm-rule-shadowed' : '') },
5228
+ // 两种「不进上下文」要分开:游离记忆(没有归属场景)是**真问题**,用错误色;
5229
+ // 场景只是没启用是**正常状态**(同时只有一个场景在用),整块灰色置灰即可 ——
5230
+ // 此前两者共用一个红类,关着的场景一片红,读起来像出了错。
5231
+ return React.createElement('div', { key: 's:' + name, className: 'dsm-source' + (isOrphan ? ' dsm-rule-shadowed' : meta.active === false ? ' dsm-rule-off' : '') },
4936
5232
  React.createElement('div', { className: 'dsm-source-head' },
4937
5233
  React.createElement('button', { type: 'button', className: 'dsm-source-head-main', 'aria-expanded': open, onClick: function () { toggleCollapse('s:' + name) } },
4938
5234
  React.createElement('span', { className: 'dsm-source-title', title: sceneLabel(data.scenes, name) }, sceneLabel(data.scenes, name)),
@@ -4940,7 +5236,7 @@ function callApi(path, options) {
4940
5236
  isShared ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('memory.scene.shared')) : null,
4941
5237
  isGlobal ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('memory.scene.global.tag')) : null,
4942
5238
  isOrphan ? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, t('memory.scene.orphan.tag')) : null,
4943
- meta.active === false ? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, t('memory.scene.off')) : null,
5239
+ meta.active === false ? React.createElement('span', { className: 'dsm-tag' }, t('memory.scene.off')) : null,
4944
5240
  isOrphan ? null : React.createElement('span', { className: 'dsm-note' }, meta.description || '')),
4945
5241
  React.createElement('div', { className: 'dsm-source-actions' },
4946
5242
  isOrphan ? null : React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { openCreate(name) } }, t('memory.scene.new')))),