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.
- package/CHANGELOG.md +67 -1
- package/README.md +53 -48
- package/README_EN.md +47 -44
- package/docs/images/1-EN.png +0 -0
- package/docs/images/1.png +0 -0
- package/docs/images/2-EN.png +0 -0
- package/docs/images/2.png +0 -0
- package/docs/images/3-EN.png +0 -0
- package/docs/images/3.png +0 -0
- package/docs/images/4-EN.png +0 -0
- package/docs/images/4.png +0 -0
- package/docs/images/5-EN.png +0 -0
- package/docs/images/5.png +0 -0
- package/docs/images/6-EN.png +0 -0
- package/docs/images/6.png +0 -0
- package/docs/images/7-EN.png +0 -0
- package/docs/images/7.png +0 -0
- package/docs/images/8-EN.png +0 -0
- package/docs/images/8.png +0 -0
- package/docs/update.md +39 -0
- package/lib/agents-md/preset-id.js +1 -1
- package/lib/agents-md/service.js +46 -9
- package/lib/approval-policy.js +1 -1
- package/lib/client.js +431 -135
- package/lib/compat/preset-reach.js +155 -93
- package/lib/compat/probe.js +6 -4
- package/lib/context-inject.js +304 -0
- package/lib/hub.js +155 -13
- package/lib/imports/upload.js +34 -14
- package/lib/index.js +870 -226
- package/lib/mcp/state-section.js +46 -15
- package/lib/rules/archive-engine.js +182 -57
- package/lib/rules/archive.js +57 -1
- package/lib/rules/service.js +386 -156
- package/lib/scene-prompt-sync.js +101 -12
- package/lib/skills/catalog.js +125 -0
- package/lib/skills/core.js +38 -4
- package/lib/skills/service.js +36 -10
- package/lib/subagents/catalog.js +15 -5
- package/lib/subagents/service.js +39 -28
- package/lib/subagents/tools.js +18 -13
- package/package.json +14 -9
- package/screenshots.json +8 -8
- package/docs/images/1/345/234/272/346/231/257.png +0 -0
- package/docs/images/1/345/234/272/346/231/257_en.png +0 -0
- package/docs/images/2MCP.png +0 -0
- package/docs/images/2MCP_en.png +0 -0
- package/docs/images/3/346/212/200/350/203/275.png +0 -0
- package/docs/images/3/346/212/200/350/203/275_en.png +0 -0
- package/docs/images/4/345/255/220/346/231/272/350/203/275/344/275/223.png +0 -0
- package/docs/images/4/345/255/220/346/231/272/350/203/275/344/275/223_en.png +0 -0
- package/docs/images/5/346/217/220/347/244/272/350/257/215.png +0 -0
- package/docs/images/5/346/217/220/347/244/272/350/257/215_en.png +0 -0
- package/docs/images/6/350/256/260/345/277/206.png +0 -0
- package/docs/images/6/350/256/260/345/277/206_en.png +0 -0
- package/docs/images/7/344/274/232/350/257/235.png +0 -0
- package/docs/images/7/344/274/232/350/257/235_en.png +0 -0
- package/docs/images/8/345/205/274/345/256/271.png +0 -0
- package/docs/images/8/345/205/274/345/256/271_en.png +0 -0
- 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;
|
|
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
|
-
//
|
|
525
|
-
// 预设**下到不到得了模型。回答的是"面板标着已注入,模型真的看得到吗"
|
|
526
|
-
//
|
|
527
|
-
//
|
|
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.
|
|
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
|
-
|
|
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": "对话里
|
|
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.
|
|
657
|
-
"mcp.
|
|
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.
|
|
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": "
|
|
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": "
|
|
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.
|
|
705
|
-
"scenes.mcp.toolsOf": "
|
|
706
|
-
"scenes.skills.hint": "
|
|
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/
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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.
|
|
891
|
-
"mcp.
|
|
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": "
|
|
920
|
-
"compat.reach.
|
|
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": "
|
|
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": "
|
|
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.
|
|
939
|
-
"scenes.mcp.toolsOf": "
|
|
940
|
-
"scenes.skills.hint": "Add the skills section first, then check the skills enabled in this scene.", "scenes.subagents.hint": "
|
|
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": "
|
|
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
|
|
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": "
|
|
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": "
|
|
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:
|
|
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
|
-
(
|
|
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
|
-
|
|
1435
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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' :
|
|
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
|
-
|
|
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
|
-
|
|
1673
|
-
|
|
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
|
|
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
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
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 ||
|
|
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'
|
|
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
|
|
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
|
-
* 空段 =
|
|
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
|
-
|
|
3814
|
+
disabled: busy,
|
|
3621
3815
|
key: server.name,
|
|
3622
3816
|
checked: selected,
|
|
3623
3817
|
name: server.name,
|
|
3624
|
-
desc:
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
* 子智能体开关(与记忆页同一套交互):停用 = 不注入目录段、
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
|
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
|
-
|
|
4319
|
-
|
|
4320
|
-
React.createElement('
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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')))),
|