dsh-plugin-tool-management 0.6.0 → 0.7.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 +131 -0
- package/README.md +106 -221
- package/README_EN.md +105 -220
- 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/docs/update.md +39 -38
- package/lib/agents-md/service.js +43 -2
- package/lib/client.js +708 -140
- package/lib/compat/preset-reach.js +425 -0
- package/lib/http-fence.js +21 -0
- package/lib/hub.js +160 -2
- package/lib/index.js +256 -39
- package/lib/mcp/override-blocks.js +195 -0
- package/lib/rules/service.js +78 -1
- package/lib/skills/core.js +11 -0
- package/lib/skills/service.js +3 -0
- package/lib/subagents/service.js +197 -23
- package/lib/subagents/tools.js +8 -2
- package/package.json +3 -2
- package/screenshots.json +10 -10
- package/docs/Changelog.md +0 -144
package/lib/client.js
CHANGED
|
@@ -35,11 +35,19 @@ window.__ModuleLoader__.load({
|
|
|
35
35
|
.dsm-tabs{display:flex;gap:4px;border-bottom:1px solid var(--dsw-alias-border-l1);margin-bottom:14px}.dsm-tabs-end{display:flex;align-items:center;gap:4px;margin-left:auto}.dsm-tab{padding:8px 14px;border:0;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px}.dsm-tab-active{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-success-primary)}.dsm-section{box-sizing:border-box;display:flex;width:100%;max-width:820px;min-width:0;margin:0 auto;padding:2px 0 36px;container-type:inline-size;flex-direction:column;gap:14px;color:var(--dsw-alias-label-primary);font-family:inherit}.dsm-head{display:flex;flex-direction:column;align-items:stretch;gap:16px}.dsm-title-block{min-width:0}.dsm-title-row{display:flex;align-items:center;gap:8px 12px;min-width:0;flex-wrap:wrap}.dsm-feedback-links{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.dsm-title{margin:0;font-size:24px;line-height:32px;font-weight:600;letter-spacing:-.4px;white-space:nowrap}.dsm-feedback-link{display:inline-flex;min-height:28px;align-items:center;gap:5px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;font-weight:500;line-height:18px;text-decoration:none;white-space:nowrap}.dsm-feedback-link:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.dsm-feedback-link:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsm-feedback-link svg{flex:none}.dsm-desc{margin:12px 0 0;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px}.dsm-actions{display:flex;flex-wrap:wrap;gap:8px;margin-left:0;flex:none}
|
|
36
36
|
.dsm-btn{box-sizing:border-box;display:inline-flex;min-height:34px;align-items:center;justify-content:center;padding:0 13px;border:1px solid transparent;border-radius:8px;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);font:inherit;font-size:13px;font-weight:580;white-space:nowrap;cursor:pointer}.dsm-btn:hover:not(:disabled){filter:brightness(1.08)}.dsm-btn:disabled{opacity:.48;cursor:default}.dsm-btn-secondary,.dsm-btn-quiet{border-color:var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary)}.dsm-btn-quiet{min-height:28px;padding:0 9px;color:var(--dsw-alias-label-secondary);font-size:12px}.dsm-btn-danger{border-color:var(--dsw-alias-state-error-primary);background:transparent;color:var(--dsw-alias-state-error-primary)}.dsm-btn:focus-visible,.dsm-control:focus-visible,.dsm-select-trigger:focus-visible,.dsm-source-head:focus-visible,.dsm-switch:focus-visible,.dsm-upload-link:focus-visible,.dsm-file-remove:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}
|
|
37
37
|
.dsm-summary{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}.dsm-stat{padding:12px 14px;border-right:1px solid var(--dsw-alias-border-l1);font-size:13px;color:var(--dsw-alias-label-secondary)}.dsm-stat:last-child{border-right:0}.dsm-stat strong{margin-right:5px;color:var(--dsw-alias-label-primary);font-size:17px;font-weight:680}.dsm-filters{display:flex;gap:9px}.dsm-search{flex:1}.dsm-source-filter{width:210px;flex:none}
|
|
38
|
+
.dsm-token-panel{display:flex;flex-direction:column;gap:8px}.dsm-token-row{display:flex;gap:8px;align-items:center;min-width:0}.dsm-token-row .dsm-control{flex:1;min-width:0}
|
|
38
39
|
.dsm-control,.dsm-select-trigger{box-sizing:border-box;width:100%;min-height:34px;padding:0 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}.dsm-control::placeholder{color:var(--dsw-alias-label-tertiary)}textarea.dsm-control{min-height:160px;padding-top:9px;resize:vertical;line-height:20px}.dsm-select{position:relative}.dsm-select-trigger{display:flex;align-items:center;justify-content:space-between;text-align:left;cursor:pointer}.dsm-select-menu{position:absolute;z-index:40;top:calc(100% + 5px);right:0;left:0;display:flex;max-height:260px;padding:5px;overflow:auto;flex-direction:column;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-layer-3);box-shadow:var(--dsw-shadow-lv2)}.dsm-option{padding:8px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;text-align:left;cursor:pointer}.dsm-option:hover,.dsm-option[aria-selected=true]{background:var(--dsw-alias-interactive-bg-hover)}
|
|
39
40
|
.dsm-sources{display:flex;flex-direction:column;gap:9px}.dsm-source{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}.dsm-source-head{box-sizing:border-box;display:flex;width:100%;min-height:48px;align-items:center;padding:0 13px}.dsm-source-head-main{display:flex;min-width:0;min-height:48px;flex:1;align-items:center;gap:10px;padding:0;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}.dsm-source-head:hover,.dsm-row:hover,.dsm-trash-row:hover,.dsm-hist-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsm-source-title{font-size:14px;font-weight:650}.dsm-count{color:var(--dsw-alias-label-tertiary);font-size:12px}.dsm-path{min-width:0;margin-left:auto;overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:11px;text-overflow:ellipsis;white-space:nowrap}.dsm-source-actions{display:flex;align-items:center;gap:9px;margin-left:8px}.dsm-source-body{border-top:1px solid var(--dsw-alias-border-l1)}.dsm-source-note{padding:9px 13px;border-bottom:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}
|
|
40
41
|
.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}
|
|
41
42
|
.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)}
|
|
42
43
|
.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-wide{width:min(720px,100%)!important}.dsm-modal-import{width:min(480px,100%)!important;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-item{display:flex;align-items:center;gap:10px;padding:11px 0;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-trash-item:last-child{border-bottom:0}.dsm-trash-main{min-width:0;flex:1}
|
|
44
|
+
.dsm-trash-group{display:flex;flex-direction:column;overflow:hidden;margin-bottom:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}.dsm-trash-group:last-child{margin-bottom:0}.dsm-trash-group-head{display:flex;align-items:center;gap:8px;min-height:40px;padding:0 13px;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-trash-group-title{font-size:13px;font-weight:650;color:var(--dsw-alias-label-primary)}.dsm-trash-group-body{padding:0 13px}
|
|
45
|
+
.dsm-trash-item{display:flex;align-items:center;gap:9px;padding:6px 10px;border-bottom:1px solid var(--dsw-alias-border-l1)}
|
|
46
|
+
.dsm-trash-item:last-child{border-bottom:0}
|
|
47
|
+
.dsm-trash-main{min-width:0;flex:1}
|
|
48
|
+
/* 回收站列表体:固定高度 + 上下滑动(记忆 / 技能 / 子智能体 / 场景 / 提示词 五处共用) */
|
|
49
|
+
.dsm-trash-box{max-height:min(46vh,340px);overflow-y:auto;overscroll-behavior:contain;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}
|
|
50
|
+
.dsm-trash-scroll{max-height:min(32vh,220px);overflow-y:auto;overscroll-behavior:contain}
|
|
43
51
|
.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}
|
|
44
52
|
@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}}
|
|
45
53
|
.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}
|
|
@@ -124,6 +132,20 @@ window.__ModuleLoader__.load({
|
|
|
124
132
|
.dsm-adv-caret{color:var(--dsw-alias-label-tertiary);font-size:10px}
|
|
125
133
|
.dsm-adv-body{display:flex;flex-direction:column;gap:12px;padding:11px;border-top:1px solid var(--dsw-alias-border-l1)}
|
|
126
134
|
.dsm-adv-note{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}
|
|
135
|
+
/* ── 工具限制四行(人设表单:一行一个 Agent 预设,白/黑名单互斥、默认折叠)──────── */
|
|
136
|
+
.dsm-modes{display:flex;flex-direction:column;gap:6px;margin-top:6px}
|
|
137
|
+
.dsm-mode-row{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}
|
|
138
|
+
.dsm-mode-row.dsm-mode-on{border-color:var(--dsw-alias-border-l3)}
|
|
139
|
+
.dsm-mode-head{display:flex;min-height:38px;align-items:center;gap:9px;padding:0 10px}
|
|
140
|
+
.dsm-mode-name{font-size:12px;font-weight:570;white-space:nowrap}
|
|
141
|
+
.dsm-mode-sum{color:var(--dsw-alias-label-tertiary);font-size:11px}
|
|
142
|
+
.dsm-mode-sum.dsm-mode-allow{color:var(--dsw-alias-state-success-primary)}
|
|
143
|
+
.dsm-mode-sum.dsm-mode-deny{color:#d49245}
|
|
144
|
+
.dsm-mode-actions{display:flex;align-items:center;gap:6px;margin-left:auto}
|
|
145
|
+
.dsm-mode-btn-allow{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}
|
|
146
|
+
.dsm-mode-btn-deny{border-color:#d49245;color:#d49245}
|
|
147
|
+
.dsm-mode-body{display:flex;flex-direction:column;gap:8px;padding:10px;border-top:1px solid var(--dsw-alias-border-l1)}
|
|
148
|
+
.dsm-legacy{display:flex;flex-direction:column;gap:6px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}
|
|
127
149
|
/* ── 兼容页(宿主能力体检)──────────────────────────────────────────────
|
|
128
150
|
一块"体检报告":先给结论卡,再给动作可用性、降级项、模块实体、阻塞项。
|
|
129
151
|
状态色只用设计系统里已有的三档:成功 / 告警(#d49245) / 错误。 */
|
|
@@ -157,6 +179,12 @@ window.__ModuleLoader__.load({
|
|
|
157
179
|
.dsm-compat-pill{display:inline-flex;min-height:20px;flex:none;align-items:center;padding:0 8px;border:1px solid var(--dsw-alias-border-l3);border-radius:999px;color:var(--dsw-alias-label-secondary);font-size:11px;white-space:nowrap}
|
|
158
180
|
.dsm-compat-pill-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}
|
|
159
181
|
.dsm-compat-pill-warn{border-color:#d49245;color:#d49245}
|
|
182
|
+
.dsm-compat-reach-marks{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:6px;min-width:0}
|
|
183
|
+
.dsm-reach-name{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
|
|
184
|
+
.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}
|
|
185
|
+
.dsm-reach-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}
|
|
186
|
+
.dsm-reach-bad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
|
|
187
|
+
.dsm-reach-unknown{color:var(--dsw-alias-label-tertiary)}
|
|
160
188
|
.dsm-compat-pill-bad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
|
|
161
189
|
.dsm-compat-name{min-width:0;flex:1;font-size:12px;font-weight:560;word-break:break-word}
|
|
162
190
|
.dsm-compat-label{margin-right:6px;color:var(--dsw-alias-label-tertiary);font-size:11px;white-space:nowrap}
|
|
@@ -194,6 +222,20 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
194
222
|
}
|
|
195
223
|
} catch (e) { /* storage unavailable */ }
|
|
196
224
|
|
|
225
|
+
/**
|
|
226
|
+
* 改令牌并落盘。明文机密 op(mcpm-reveal / mcpm-export)在宿主侧**必须**带对令牌,
|
|
227
|
+
* 没配令牌时一律拒绝 —— 所以界面得有个地方让用户把令牌填进来,否则那功能永远打不开。
|
|
228
|
+
* 令牌只存本机 localStorage,随请求以 x-dsh-token 发出。
|
|
229
|
+
*/
|
|
230
|
+
function setAccessToken(value) {
|
|
231
|
+
TOKEN = String(value || '').trim()
|
|
232
|
+
try {
|
|
233
|
+
if (TOKEN) window.localStorage.setItem('dsh-plugin-tool-management-token', TOKEN)
|
|
234
|
+
else window.localStorage.removeItem('dsh-plugin-tool-management-token')
|
|
235
|
+
} catch (e) { /* storage unavailable */ }
|
|
236
|
+
return TOKEN
|
|
237
|
+
}
|
|
238
|
+
|
|
197
239
|
function apiCall(op, args) {
|
|
198
240
|
// `x-dsh-plugin` is the cross-site (CSRF) gate header the host half
|
|
199
241
|
// requires on every request; a cross-origin page cannot attach it
|
|
@@ -247,6 +289,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
247
289
|
function CompatPage(props) {
|
|
248
290
|
const t = props.t
|
|
249
291
|
const [data, setData] = React.useState(null)
|
|
292
|
+
const [presetReach, setPresetReach] = React.useState(null)
|
|
250
293
|
const [busy, setBusy] = React.useState(false)
|
|
251
294
|
const [error, setError] = React.useState(null)
|
|
252
295
|
const apply = function (r, alive) {
|
|
@@ -254,12 +297,24 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
254
297
|
if (r && r.ok) { setData(r); setError(null) } else { setError((r && r.error) || 'unknown') }
|
|
255
298
|
setBusy(false)
|
|
256
299
|
}
|
|
300
|
+
// 预设可达性是独立的一面镜子:它取不到只意味着这一节不显示,
|
|
301
|
+
// 绝不让它的失败顶掉 compat-status 的结论。
|
|
302
|
+
const applyReach = function (r, alive) {
|
|
303
|
+
if (alive === false) return
|
|
304
|
+
if (r && r.ok) setPresetReach(r)
|
|
305
|
+
}
|
|
306
|
+
const loadReach = function (alive) {
|
|
307
|
+
apiCall('preset-reach', {})
|
|
308
|
+
.then(function (r) { applyReach(r, alive) })
|
|
309
|
+
.catch(function () { /* 未挂载预设服务 → 不渲染这一节 */ })
|
|
310
|
+
}
|
|
257
311
|
React.useEffect(function () {
|
|
258
312
|
let alive = true
|
|
259
313
|
setBusy(true)
|
|
260
314
|
apiCall('compat-status', { refresh: true })
|
|
261
315
|
.then(function (r) { apply(r, alive) })
|
|
262
316
|
.catch(function (e) { if (alive) { setError(String((e && e.message) || e)); setBusy(false) } })
|
|
317
|
+
loadReach(alive)
|
|
263
318
|
return function () { alive = false }
|
|
264
319
|
}, [])
|
|
265
320
|
const reload = function () {
|
|
@@ -267,6 +322,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
267
322
|
apiCall('compat-status', { refresh: true })
|
|
268
323
|
.then(function (r) { apply(r, true) })
|
|
269
324
|
.catch(function (e) { setError(String((e && e.message) || e)); setBusy(false) })
|
|
325
|
+
loadReach(true)
|
|
270
326
|
}
|
|
271
327
|
|
|
272
328
|
const findings = (data && data.findings) || []
|
|
@@ -417,6 +473,42 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
417
473
|
React.createElement('code', { className: 'dsm-compat-code' }, ' node scripts/doctor.mjs')))
|
|
418
474
|
}
|
|
419
475
|
|
|
476
|
+
// 预设注入边界:五类能力(记忆 / 提示词 / 技能 / MCP / 子智能体)在**每个 Agent
|
|
477
|
+
// 预设**下到不到得了模型。回答的是"面板标着已注入,模型真的看得到吗"——官方
|
|
478
|
+
// minimal 预设的 persona 是 complete,会把除自己以外的提示词段全部压掉,而且它
|
|
479
|
+
// 连官方 subagent 工具都没挂。单独一块:取不到时这一节不显示,不影响上面的结论。
|
|
480
|
+
//
|
|
481
|
+
// 排版口径(用户裁定 2026-09-15):每格一枚描边标签,**绿框=能用、红框=不能用、
|
|
482
|
+
// 灰框=判断不了**;不再出现 ✓/✗ 符号,也不再有任何整行的解释文字 —— 预设损坏与
|
|
483
|
+
// 解析失败缩成预设名旁边的小标签。MCP 与子智能体两列由宿主侧的 preset-reach 给出。
|
|
484
|
+
if (presetReach && Array.isArray(presetReach.rows) && presetReach.rows.length) {
|
|
485
|
+
push(section(t('compat.reach'), t('compat.reach.hint'),
|
|
486
|
+
React.createElement('div', { className: 'dsm-compat-mod-list dsm-compat-cards' },
|
|
487
|
+
presetReach.rows.map(function (row) {
|
|
488
|
+
// ok → 绿框;absent / suppressed → 红框;其余(unknown,含宿主还没回传该字段)
|
|
489
|
+
// 一律灰框,绝不把"读不到"画成"不能用"。
|
|
490
|
+
const chip = function (label, state) {
|
|
491
|
+
const kind = state === 'ok' ? 'ok' : (state === 'absent' || state === 'suppressed') ? 'bad' : 'unknown'
|
|
492
|
+
return React.createElement('span', { className: 'dsm-reach-chip dsm-reach-' + kind, key: label }, label)
|
|
493
|
+
}
|
|
494
|
+
const tags = []
|
|
495
|
+
if (row.broken) tags.push(React.createElement('span', { className: 'dsm-tag dsm-tag-off', key: 'broken' }, t('compat.reach.broken')))
|
|
496
|
+
else if (row.reason) tags.push(React.createElement('span', { className: 'dsm-tag', key: 'reason' }, t('compat.reach.whyUnknown')))
|
|
497
|
+
return React.createElement('div', { className: 'dsm-compat-mod-row', key: row.presetId },
|
|
498
|
+
React.createElement('span', { className: 'dsm-compat-name dsm-reach-name' },
|
|
499
|
+
React.createElement('span', null, (row.name || row.presetId) + (row.isDefault ? ' · ' + t('compat.reach.default') : '')),
|
|
500
|
+
tags.length ? React.createElement('span', { className: 'dsm-tags' }, tags) : null),
|
|
501
|
+
React.createElement('span', { className: 'dsm-compat-reach-marks' },
|
|
502
|
+
chip(t('compat.reach.mark.memory'), row.memory),
|
|
503
|
+
chip(t('compat.reach.mark.agentsMd'), row.agentsMd),
|
|
504
|
+
chip(t('compat.reach.mark.skill'), row.skillCatalog),
|
|
505
|
+
chip(t('compat.reach.mark.mcp'), row.mcp),
|
|
506
|
+
chip(t('compat.reach.mark.subagent'), row.subagent)))
|
|
507
|
+
}))))
|
|
508
|
+
|
|
509
|
+
push(React.createElement('p', { className: 'dsm-help' }, t('compat.reach.tools')))
|
|
510
|
+
}
|
|
511
|
+
|
|
420
512
|
return React.createElement('div', { className: 'dsm-compat' },
|
|
421
513
|
React.createElement('div', { className: 'dsm-head' },
|
|
422
514
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
@@ -443,10 +535,15 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
443
535
|
"title": "技能", "desc": "管理本机技能:启停、导入、创建与回收站;同名技能可指定哪个版本生效。", "link.project": "GitHub", "link.feedback": "问题反馈",
|
|
444
536
|
"btn.create": "创建技能", "btn.import": "导入", "btn.refresh": "刷新", "btn.cancel": "取消", "btn.close": "关闭", "btn.detail": "查看详情", "btn.trash": "移到回收站", "btn.restore": "恢复", "btn.delete.forever": "永久删除", "btn.file.pick": "选择文件", "btn.folder.pick": "选择文件夹", "btn.import.now": "安装", "btn.create.now": "创建技能", "btn.disable": "停用", "btn.enable": "启用", "btn.open.editor": "用系统编辑器打开", "btn.activate": "启用这个", "btn.activate.title": "把当前来源的版本设为同名技能的首选并启用(不改动任何源文件)", "btn.unprefer": "取消首选", "btn.unprefer.title": "取消同名首选,回到按来源优先级自动选择",
|
|
445
537
|
"btn.custom.add": "添加目录", "btn.custom.remove": "移除", "btn.openDir": "选择",
|
|
446
|
-
"btn.source.remove": "移除来源", "btn.source.remove.title": "
|
|
447
|
-
"
|
|
538
|
+
"btn.source.remove": "移除来源", "btn.source.remove.title": "不再读取这个来源(源文件不动,之后可在回收站里恢复)", "btn.source.restore": "恢复读取",
|
|
539
|
+
"trash.section.dirs": "目录", "trash.section.skills": "技能", "trash.section.dirs.empty": "没有被移除的来源",
|
|
540
|
+
"trash.dirs.count.one": "{count} 个", "trash.dirs.count.other": "{count} 个",
|
|
541
|
+
"trash.dir.note": "插件不再读取(磁盘文件不动)", "trash.row.summary": "技能 {skills} · 目录 {dirs}",
|
|
542
|
+
"confirm.source.forget.title": "永久删除这条来源记录?", "confirm.source.forget.desc": "「{name}」将从插件里彻底删掉:来源列表里不再有它,「恢复读取」也找不回来(需要时只能重新添加这个目录)。",
|
|
543
|
+
"confirm.source.forget.hint": "磁盘上的文件夹一个字节都不动:{path}。",
|
|
544
|
+
"result.sourceForgotten": "已永久删除来源记录:{name}",
|
|
448
545
|
"confirm.source.remove.title": "移除来源?", "confirm.source.remove.desc": "「{name}」将不再被读取:它下面的技能不再出现在本页,也不再参与同名优先级;已启用/停用的选择会保留。",
|
|
449
|
-
"confirm.source.remove.hint": "
|
|
546
|
+
"confirm.source.remove.hint": "不删除任何文件。移除后它出现在「回收站 → 目录」,在那里点「恢复读取」就能重新纳入(也可以永久删除这条记录)。",
|
|
450
547
|
"result.sourceRemoved": "已移除来源:{name}(不再读取)", "result.sourceRestored": "已恢复读取:{name}",
|
|
451
548
|
"custom.add.title": "添加自定义技能目录", "custom.add.submit": "添加",
|
|
452
549
|
"custom.add.path": "目录绝对路径", "custom.add.path.placeholder": "例如 D:\\skills\\my-skills",
|
|
@@ -465,6 +562,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
465
562
|
"create.title": "创建技能", "create.target": "创建位置", "create.name": "名称", "create.name.placeholder": "例如 code-review-helper", "create.description": "简介", "create.description.placeholder": "一句话说明什么时候使用", "create.body": "正文(Markdown)", "create.body.placeholder": "写下技能要遵循的指令、步骤和边界…", "create.chat.note": "对话里 create_skill 建的是用户级技能",
|
|
466
563
|
"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.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}",
|
|
467
564
|
"trash.title": "回收站", "trash.count.one": "{count} 个待处理技能", "trash.count.other": "{count} 个待处理技能", "trash.empty": "回收站为空", "trash.deletedAt": "删除于 {time}", "trash.source": "来源:{source}",
|
|
565
|
+
"trash.btn.open": "回收站", "trash.items.count": "{count} 项", "trash.purge.confirm": "永久删除?",
|
|
566
|
+
"trash.group.agents": "人设", "trash.group.scenes": "场景", "trash.group.presets": "提示词预设",
|
|
567
|
+
"trash.restored": "已恢复「{name}」", "trash.purged": "已永久删除「{name}」", "trash.loadFailed": "读取回收站失败",
|
|
468
568
|
"confirm.trash.title": "移到回收站?", "confirm.trash.desc": "「{name}」将移入回收站,可恢复", "confirm.delete.title": "永久删除?", "confirm.delete.desc": "「{name}」将永久删除,无法恢复",
|
|
469
569
|
"result.created": "已创建技能:{name}", "result.imported": "导入完成:{names}", "result.importPartial": "已导入:{imported};已跳过同名技能:{skipped}", "result.importSkipped": "未导入任何技能;已跳过同名技能:{names}", "result.importEmpty": "未导入任何技能。", "result.importWarnings": "{result};警告:{warnings}", "result.restored": "已恢复技能:{name}", "result.trashed": "已移到回收站:{name}", "result.deleted": "已永久删除:{name}", "result.updated": "状态已更新。", "result.preferred": "已改为生效:{name}({source})", "result.unpreferred": "已取消同名首选:{name}", "result.opened": "已用系统默认程序打开:{path}", "error.action": "操作失败:{error}",
|
|
470
570
|
"warning.scan.truncated": "技能目录较大或嵌套过深,部分技能未显示:{path}", "warning.state.invalid": "状态文件不可读,已安全停用所有技能:{path}", "warning.backupUncleaned": "旧版本备份未清理:{path}({error})", "warning.project.unavailable": "无法从宿主读取活动工作区,项目技能未显示:{path}",
|
|
@@ -501,6 +601,11 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
501
601
|
"mcp.field.env": "环境变量(每行 key=value)", "mcp.field.env.hint": "路径按系统路径写法填(Windows 用 \\,macOS/Linux 用 /)。",
|
|
502
602
|
"mcp.field.headers": "请求头(每行 key=value)",
|
|
503
603
|
"mcp.detail.title": "服务详情:", "mcp.detail.config": "配置", "mcp.detail.showSecret": "显示密钥", "mcp.detail.hideSecret": "隐藏密钥",
|
|
604
|
+
"mcp.token.label": "访问令牌", "mcp.token.placeholder": "与宿主配置的 token 相同", "mcp.token.save": "保存并重试",
|
|
605
|
+
"mcp.token.hint": "明文密钥必须带对的访问令牌:宿主侧在本插件配置里加 token(或设环境变量 DSH_PLUGIN_TOOL_MANAGEMENT_TOKEN)并重启 DSH,这里填同一个值(只存在本机浏览器里,随请求以 x-dsh-token 发送)。",
|
|
606
|
+
"mcp.btn.compact": "整理补丁", "mcp.btn.compact.title": "清掉补丁文件里已经不起作用的旧开关记录(每台服务的启用/停用状态不变)",
|
|
607
|
+
"mcp.compact.title": "整理补丁文件", "mcp.compact.desc": "配置里会积累一些已经不起作用的旧开关记录(同一个服务被反复启用/停用留下的)。",
|
|
608
|
+
"mcp.compact.confirm": "确认整理", "mcp.compact.done": "补丁文件已整理:清掉 {count} 条失效的旧开关记录,服务的启用/停用状态未变", "mcp.compact.clean": "补丁文件已经很干净,没有可清理的旧记录",
|
|
504
609
|
"mcp.detail.entryId": "条目 ID", "mcp.detail.status": "运行状态", "mcp.detail.registered": "该服务已登记在 Loader 中。",
|
|
505
610
|
"mcp.detail.note": "备注(仅本机可见)", "mcp.detail.note.placeholder": "例如:A 不可用时改用 B 兜底", "mcp.detail.note.save": "保存备注",
|
|
506
611
|
"mcp.detail.tools": "工具({count})",
|
|
@@ -530,6 +635,11 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
530
635
|
"compat.fallback.native": "改走原生入口", "compat.fallback.blocked": "相关按钮已禁用",
|
|
531
636
|
"compat.modules": "模块实体(插件 vs 宿主)", "compat.modules.hint": "同一份模块才谈得上适配", "compat.module.same": "同一份模块", "compat.module.separate": "两份拷贝(需修复)", "compat.module.unknown": "无法比较",
|
|
532
637
|
"compat.blockers": "阻塞项(按此修复)",
|
|
638
|
+
"compat.reach": "预设注入边界", "compat.reach.hint": "面板说已注入,模型真的看得到吗",
|
|
639
|
+
"compat.reach.mark.memory": "记忆", "compat.reach.mark.agentsMd": "提示词", "compat.reach.mark.skill": "技能", "compat.reach.mark.mcp": "MCP", "compat.reach.mark.subagent": "子智能体",
|
|
640
|
+
"compat.reach.default": "默认",
|
|
641
|
+
"compat.reach.whyUnknown": "判断不了", "compat.reach.broken": "预设损坏",
|
|
642
|
+
"compat.reach.tools": "本插件的模型工具(含 subagent_list / subagent_run)与 MCP 都挂在宿主平面,不受预设影响,任何预设下都可调用。",
|
|
533
643
|
"compat.hint.doctor": "命令行体检:",
|
|
534
644
|
"scenes.title": "场景", "scenes.desc": "管理场景:可以预设不同的使用场景,包含 MCP、Skills、子智能体、记忆",
|
|
535
645
|
"scenes.stat.total": "个场景", "scenes.stat.active": "个已启用", "scenes.stat.archives": "个有档案",
|
|
@@ -547,7 +657,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
547
657
|
"scenes.mcp.allTools": "全部工具", "scenes.mcp.pickedCount": "指定 {count} 个工具", "scenes.mcp.notRunning": "未运行", "scenes.mcp.pickTools": "选工具",
|
|
548
658
|
"scenes.mcp.toolsOf": "工具明细", "scenes.mcp.back": "返回", "scenes.mcp.noTools": "该服务器当前没有可列出的工具(未运行或无工具)", "scenes.mcp.drillHint": "勾选 = 该场景下启用;不勾 = 停用。整台勾选时默认全部工具。",
|
|
549
659
|
"scenes.skills.hint": "添加「技能集」后,勾选该场景下启用的技能。", "scenes.subagents.hint": "添加「子智能体绑定」后,勾选本场景可调用的人设。", "scenes.subagents.empty": "还没有人设——到「子智能体」页创建。",
|
|
550
|
-
"scenes.filter.skills": "
|
|
660
|
+
"scenes.filter.skills": "筛选技能/目录名称", "scenes.filter.subagents": "筛选人设(名称或描述)", "scenes.filter.servers": "筛选服务器",
|
|
551
661
|
"scenes.mem.hint": "添加「记忆」段后,勾选本场景要注入的记忆(只列本场景自己的记忆;全局记忆恒定注入、不需要在这里配置);不勾的记忆不会进系统提示词(文件保留)。",
|
|
552
662
|
"scenes.mem.title": "记忆", "scenes.mem.search": "在本场景内筛选记忆(名称或描述)",
|
|
553
663
|
"scenes.mem.noMatch": "没有匹配项", "scenes.mem.emptyScene": "该场景还没有记忆",
|
|
@@ -601,7 +711,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
601
711
|
"memory.trash.confirmTitle": "永久删除?", "memory.trash.confirmDesc": "「{name}」将永久删除,无法恢复",
|
|
602
712
|
"memory.btn.newScene": "新建场景", "memory.btn.deleteScene": "删除场景", "memory.btn.saveScene": "保存修改",
|
|
603
713
|
"memory.scene.createTitle": "新建场景",
|
|
604
|
-
"memory.deleteScene.title": "删除场景?", "memory.deleteScene.desc": "
|
|
714
|
+
"memory.deleteScene.title": "删除场景?", "memory.deleteScene.desc": "将把「{name}」的场景记录与档案移入回收站(场景页的「回收站」里可以恢复;里面还有记忆或正处在当前模式时不能删;「全局」是保留场景,不可删除)",
|
|
605
715
|
"memory.result.sceneCreated": "已创建场景:{name}", "memory.result.sceneUpdated": "已保存场景:{name}", "memory.result.sceneRemoved": "已删除场景:{name}",
|
|
606
716
|
"memory.table.name": "记忆名称与描述", "memory.table.tags": "标记", "memory.table.status": "状态",
|
|
607
717
|
"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.sceneNotEmpty": "场景不为空,无法删除", "error.rules.notBundle": "该记忆是 flat(单文件),不能带附件", "error.rules.noFiles": "没有选择附件", "error.rules.emptyFile": "附件内容为空", "error.rules.fileTooLarge": "附件过大(单个上限 {limit} MB)", "error.rules.tooManyFiles": "一次最多 {limit} 个附件", "error.rules.sceneInMode": "该场景正处在当前模式,请先退出模式再删除",
|
|
@@ -615,15 +725,26 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
615
725
|
"subagents.field.description": "描述", "subagents.field.description.placeholder": "例如 擅长 Java 后端实现与重构",
|
|
616
726
|
"subagents.field.model": "模型", "subagents.field.model.placeholder": "自定义模型 id", "subagents.field.model.hint": "留空继承主会话", "subagents.field.provider": "模型来源", "subagents.field.provider.placeholder": "sensenova", "subagents.field.provider.hint": "「模型来源」与「模型」是一对:只填模型会落在主会话的来源上,跨来源会解析失败。",
|
|
617
727
|
"subagents.model.inherit": "继承主会话(不指定)", "subagents.model.customOption": "自定义 / 目录里没有…",
|
|
618
|
-
"subagents.field.
|
|
619
|
-
"subagents.field.
|
|
620
|
-
"subagents.
|
|
621
|
-
"subagents.
|
|
622
|
-
"subagents.
|
|
623
|
-
"subagents.
|
|
728
|
+
"subagents.field.modes": "工具限制(按 Agent 预设)",
|
|
729
|
+
"subagents.field.modes.hint": "子代理跑在父会话的模式里:只有当前模式那一行启动了名单才生效,其他模式照旧用该模式的全部工具。",
|
|
730
|
+
"subagents.mode.startAllow": "启动白名单", "subagents.mode.startDeny": "启动黑名单", "subagents.mode.stopAllow": "关闭白名单", "subagents.mode.stopDeny": "关闭黑名单",
|
|
731
|
+
"subagents.mode.off": "未限制", "subagents.mode.empty": "已启动但没勾选(= 不限制)",
|
|
732
|
+
"subagents.mode.allowOn": "白名单 {count} 个", "subagents.mode.denyOn": "黑名单 {count} 个",
|
|
733
|
+
"subagents.mode.allowEmpty": "一个都没勾 = 不限制(该模式的全部工具都能用)",
|
|
734
|
+
"subagents.mode.denyEmpty": "一个都没勾 = 不限制(该模式的全部工具都能用)",
|
|
735
|
+
"subagents.mode.noTools": "读不到这个模式的工具清单",
|
|
736
|
+
"subagents.mode.broken": "这个预设已损坏,读不到工具清单",
|
|
737
|
+
"subagents.mode.noPresets": "宿主没有回传 Agent 预设名单",
|
|
738
|
+
"subagents.legacy.note": "这个文件里还有一份旧写法的工具限制(白名单 {allow} 个 / 黑名单 {deny} 个),它对所有模式都生效。",
|
|
739
|
+
"subagents.legacy.convert": "转换到按模式设置",
|
|
740
|
+
"subagents.legacy.hint": "转换会把旧名单搬进你选的模式;不点它,文件里的旧键原样保留(运行时照旧生效)。",
|
|
741
|
+
"subagents.tools.filter": "筛选工具名", "subagents.tools.remove": "移除",
|
|
742
|
+
"subagents.adv.title": "高级选项", "subagents.adv.inherit": "继承主会话", "subagents.adv.summary": "模型 {model} · 模式限制 {modes} 项 · 旧格式 白名单 {allow} / 黑名单 {deny}", "subagents.adv.note": "白名单 = 子代理只能用勾选的工具;黑名单 = 除勾选的以外都能用。MCP 工具不在候选里:子代理始终能用当前在跑的 MCP。", "subagents.adv.loadFailed": "读取候选数据失败(模型目录 / 工具清单);仍可手动填写。",
|
|
743
|
+
"preset.name.standard": "标准模式", "preset.name.ptc": "PTC 模式", "preset.name.minimal": "极简模式", "preset.name.cordis": "创造模式",
|
|
744
|
+
"preset.short.standard": "标准", "preset.short.ptc": "PTC", "preset.short.minimal": "极简", "preset.short.cordis": "创造",
|
|
624
745
|
"subagents.field.body": "人设提示词", "subagents.field.body.placeholder": "写下这个人设的身份、职责与工作方式…",
|
|
625
746
|
"subagents.result.saved": "已保存人设:{name}", "subagents.result.deleted": "已删除人设:{name}",
|
|
626
|
-
"subagents.delete.title": "删除人设?", "subagents.delete.desc": "
|
|
747
|
+
"subagents.delete.title": "删除人设?", "subagents.delete.desc": "将把「{name}」的人设文件移入回收站(子智能体页的「回收站」里可以恢复)。",
|
|
627
748
|
"subagents.import": "导入", "subagents.import.title": "导入人设", "subagents.import.hint": "支持 .md 或 .zip(可多选、可拖入):一个 .md = 一个人设,文件名即人设名;同名自动跳过。",
|
|
628
749
|
"subagents.result.imported": "已导入 {count} 个人设:{names}",
|
|
629
750
|
"memory.import": "导入记忆", "memory.import.title": "导入记忆", "memory.import.scene": "导入到场景",
|
|
@@ -643,10 +764,15 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
643
764
|
"btn.create": "Create skill", "btn.import": "Import", "btn.refresh": "Refresh", "btn.cancel": "Cancel", "btn.close": "Close", "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.import.now": "Install", "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",
|
|
644
765
|
"btn.custom.add": "Add folder",
|
|
645
766
|
"btn.custom.remove": "Remove",
|
|
646
|
-
"btn.source.remove": "Remove source", "btn.source.remove.title": "Stop reading this source (files stay on disk
|
|
647
|
-
"
|
|
767
|
+
"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": "Read again",
|
|
768
|
+
"trash.section.dirs": "Directories", "trash.section.skills": "Skills", "trash.section.dirs.empty": "No removed sources",
|
|
769
|
+
"trash.dirs.count.one": "{count} directory", "trash.dirs.count.other": "{count} directories",
|
|
770
|
+
"trash.dir.note": "No longer read (files stay on disk)", "trash.row.summary": "{skills} skill(s) · {dirs} director(ies)",
|
|
771
|
+
"confirm.source.forget.title": "Delete this source record for good?", "confirm.source.forget.desc": "“{name}” is removed from the plugin entirely: it disappears from the source list and “Read again” cannot bring it back (you would have to add the folder again).",
|
|
772
|
+
"confirm.source.forget.hint": "Not a single byte is deleted on disk: {path}.",
|
|
773
|
+
"result.sourceForgotten": "Source record deleted for good: {name}",
|
|
648
774
|
"confirm.source.remove.title": "Remove this source?", "confirm.source.remove.desc": "“{name}” will no longer be read: its skills disappear from this page and stop taking part in name resolution; your enable/disable choices are kept.",
|
|
649
|
-
"confirm.source.remove.hint": "No file is deleted.
|
|
775
|
+
"confirm.source.remove.hint": "No file is deleted. Once removed it shows up under Trash → Directories, where “Read again” brings it back (or you can delete the record for good).",
|
|
650
776
|
"result.sourceRemoved": "Source removed: {name} (no longer read)", "result.sourceRestored": "Reading again: {name}",
|
|
651
777
|
"btn.openDir": "Browse",
|
|
652
778
|
"custom.add.title": "Add custom skill folder",
|
|
@@ -672,6 +798,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
672
798
|
"create.title": "Create skill", "create.target": "Create in", "create.name": "Name", "create.name.placeholder": "e.g. code-review-helper", "create.description": "Description", "create.description.placeholder": "One sentence describing when to use it", "create.body": "Body (Markdown)", "create.body.placeholder": "Write the instructions, steps, and boundaries…", "create.chat.note": "create_skill in chat creates user-level skills",
|
|
673
799
|
"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.importing": "Installing…", "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}",
|
|
674
800
|
"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}",
|
|
801
|
+
"trash.btn.open": "Trash", "trash.items.count": "{count} item(s)", "trash.purge.confirm": "Delete forever?",
|
|
802
|
+
"trash.group.agents": "Personas", "trash.group.scenes": "Scenes", "trash.group.presets": "Prompt presets",
|
|
803
|
+
"trash.restored": "Restored “{name}”", "trash.purged": "Permanently deleted “{name}”", "trash.loadFailed": "Could not read the trash",
|
|
675
804
|
"confirm.trash.title": "Move to trash?", "confirm.trash.desc": "“{name}” moves to trash and can be restored", "confirm.delete.title": "Delete forever?", "confirm.delete.desc": "“{name}” is deleted forever and cannot be recovered",
|
|
676
805
|
"result.created": "Created skill: {name}", "result.imported": "Import complete: {names}", "result.importPartial": "Imported: {imported}; skipped existing skills: {skipped}", "result.importSkipped": "No skills were imported; existing skills were skipped: {names}", "result.importEmpty": "No skills were imported.", "result.importWarnings": "{result}; warnings: {warnings}", "result.restored": "Restored skill: {name}", "result.trashed": "Moved to trash: {name}", "result.deleted": "Permanently deleted: {name}", "result.updated": "Status updated.", "result.preferred": "Now active: {name} ({source})", "result.unpreferred": "Cleared the manual choice: {name}", "error.action": "Action failed: {error}",
|
|
677
806
|
"warning.scan.truncated": "Some skills were not shown because the directory is too large or deeply nested: {path}", "warning.state.invalid": "The manager state file could not be read; all skills are disabled and state writes are blocked until it is repaired: {path}", "warning.backupUncleaned": "Old version backup was not cleaned up: {path} ({error})", "warning.project.unavailable": "The active workspace could not be read from the host, so its project skills are hidden: {path}",
|
|
@@ -708,6 +837,11 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
708
837
|
"mcp.field.env": "Environment (one key=value per line)", "mcp.field.env.hint": "Use the platform's own path syntax (backslashes on Windows, / on macOS/Linux).",
|
|
709
838
|
"mcp.field.headers": "Headers (one key=value per line)",
|
|
710
839
|
"mcp.detail.title": "Server details: ", "mcp.detail.config": "Configuration", "mcp.detail.showSecret": "Reveal secrets", "mcp.detail.hideSecret": "Hide secrets",
|
|
840
|
+
"mcp.token.label": "Access token", "mcp.token.placeholder": "Same value as the host token", "mcp.token.save": "Save and retry",
|
|
841
|
+
"mcp.token.hint": "Plaintext secrets require the matching access token: set token in this plugin's host config (or the DSH_PLUGIN_TOOL_MANAGEMENT_TOKEN env var), restart DSH, then enter the same value here (kept in this browser only and sent as x-dsh-token).",
|
|
842
|
+
"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)",
|
|
843
|
+
"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.",
|
|
844
|
+
"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",
|
|
711
845
|
"mcp.detail.entryId": "Entry id", "mcp.detail.status": "Status", "mcp.detail.registered": "This server is registered in the loader.",
|
|
712
846
|
"mcp.detail.note": "Note (local only)", "mcp.detail.note.placeholder": "e.g. fall back to B when A is unavailable", "mcp.detail.note.save": "Save note",
|
|
713
847
|
"mcp.detail.tools": "Tools ({count})",
|
|
@@ -737,6 +871,11 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
737
871
|
"compat.fallback.native": "Falls back to native entry", "compat.fallback.blocked": "Affected buttons disabled",
|
|
738
872
|
"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",
|
|
739
873
|
"compat.blockers": "Blockers (fix these)",
|
|
874
|
+
"compat.reach": "Preset injection reach", "compat.reach.hint": "the panel says injected — does the model actually see it?",
|
|
875
|
+
"compat.reach.mark.memory": "Memory", "compat.reach.mark.agentsMd": "Prompt", "compat.reach.mark.skill": "Skills", "compat.reach.mark.mcp": "MCP", "compat.reach.mark.subagent": "Subagents",
|
|
876
|
+
"compat.reach.default": "default",
|
|
877
|
+
"compat.reach.whyUnknown": "cannot tell", "compat.reach.broken": "preset broken",
|
|
878
|
+
"compat.reach.tools": "This plugin's model tools (including subagent_list / subagent_run) and MCP are host-plane and preset-independent: callable under every preset.",
|
|
740
879
|
"compat.hint.doctor": "Command-line check:",
|
|
741
880
|
"scenes.title": "Scenes", "scenes.desc": "Manage scenes: preset different usage scenes combining MCP servers, skills, subagents and memories",
|
|
742
881
|
"scenes.stat.total": "scene(s)", "scenes.stat.active": "enabled", "scenes.stat.archives": "with a profile",
|
|
@@ -754,7 +893,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
754
893
|
"scenes.mcp.allTools": "all tools", "scenes.mcp.pickedCount": "{count} tools picked", "scenes.mcp.notRunning": "not running", "scenes.mcp.pickTools": "Pick tools",
|
|
755
894
|
"scenes.mcp.toolsOf": "Tool details", "scenes.mcp.back": "Back", "scenes.mcp.noTools": "No tools listed for this server (not running or no tools)", "scenes.mcp.drillHint": "Checked = enabled in this scene; unchecked = disabled. A whole-server check defaults to all tools.",
|
|
756
895
|
"scenes.skills.hint": "Add the skills section first, then check the skills enabled in this scene.", "scenes.subagents.hint": "Add the subagent section first, then check the personas callable in this scene.", "scenes.subagents.empty": "No personas yet — create one on the Subagents page.",
|
|
757
|
-
"scenes.filter.skills": "Filter skills
|
|
896
|
+
"scenes.filter.skills": "Filter skills / directories", "scenes.filter.subagents": "Filter personas (name or description)", "scenes.filter.servers": "Filter servers",
|
|
758
897
|
"scenes.mem.hint": "Add the memory section, then check the memories injected for this scene (only this scene's own memories are listed — global memories are always injected and need no configuration here); unchecked memories never reach the system prompt (their files stay).",
|
|
759
898
|
"scenes.mem.title": "Memories", "scenes.mem.search": "Filter memories in this scene (name or description)",
|
|
760
899
|
"scenes.mem.noMatch": "Nothing matches", "scenes.mem.emptyScene": "This scene has no memories yet",
|
|
@@ -808,7 +947,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
808
947
|
"memory.trash.confirmTitle": "Delete forever?", "memory.trash.confirmDesc": "“{name}” is deleted forever and cannot be recovered",
|
|
809
948
|
"memory.btn.newScene": "New scene", "memory.btn.deleteScene": "Delete scene", "memory.btn.saveScene": "Save changes",
|
|
810
949
|
"memory.scene.createTitle": "New scene",
|
|
811
|
-
"memory.deleteScene.title": "Delete scene?", "memory.deleteScene.desc": "
|
|
950
|
+
"memory.deleteScene.title": "Delete scene?", "memory.deleteScene.desc": "Moves the scene record and its archive for “{name}” to the trash (restorable from the Trash button on the Scenes page; refused while it still holds memories or is the active mode; “Global” is reserved and cannot be deleted)",
|
|
812
951
|
"memory.result.sceneCreated": "Scene created: {name}", "memory.result.sceneUpdated": "Scene saved: {name}", "memory.result.sceneRemoved": "Scene deleted: {name}",
|
|
813
952
|
"memory.table.name": "Memory name and description", "memory.table.tags": "Tags", "memory.table.status": "Status",
|
|
814
953
|
"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.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",
|
|
@@ -821,15 +960,26 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
821
960
|
"subagents.field.description": "Description", "subagents.field.description.placeholder": "e.g. Senior Java engineer",
|
|
822
961
|
"subagents.field.model": "Model", "subagents.field.model.placeholder": "Custom model id", "subagents.field.model.hint": "Blank inherits the main session", "subagents.field.provider": "Model provider", "subagents.field.provider.placeholder": "sensenova", "subagents.field.provider.hint": "Provider and model are a pair: a model alone resolves against the main session's provider and fails across providers.",
|
|
823
962
|
"subagents.model.inherit": "Inherit the main session (unspecified)", "subagents.model.customOption": "Custom / not in the catalogue…",
|
|
824
|
-
"subagents.field.
|
|
825
|
-
"subagents.field.
|
|
826
|
-
"subagents.
|
|
827
|
-
"subagents.
|
|
828
|
-
"subagents.
|
|
829
|
-
"subagents.
|
|
963
|
+
"subagents.field.modes": "Tool limits (per agent preset)",
|
|
964
|
+
"subagents.field.modes.hint": "A subagent runs inside its parent session's preset: only the row for the current preset takes effect; every other preset keeps that preset's full tool set.",
|
|
965
|
+
"subagents.mode.startAllow": "Enable allowlist", "subagents.mode.startDeny": "Enable denylist", "subagents.mode.stopAllow": "Disable allowlist", "subagents.mode.stopDeny": "Disable denylist",
|
|
966
|
+
"subagents.mode.off": "no restriction", "subagents.mode.empty": "enabled with nothing picked (= no restriction)",
|
|
967
|
+
"subagents.mode.allowOn": "allowlist {count}", "subagents.mode.denyOn": "denylist {count}",
|
|
968
|
+
"subagents.mode.allowEmpty": "Nothing picked = no restriction (every tool of this preset stays available)",
|
|
969
|
+
"subagents.mode.denyEmpty": "Nothing picked = no restriction (every tool of this preset stays available)",
|
|
970
|
+
"subagents.mode.noTools": "Could not read this preset's tool list",
|
|
971
|
+
"subagents.mode.broken": "This preset is broken; its tool list is unreadable",
|
|
972
|
+
"subagents.mode.noPresets": "The host returned no agent-preset roster",
|
|
973
|
+
"subagents.legacy.note": "This file still carries the old-style tool limits (allowlist {allow} / denylist {deny}); they apply to every preset.",
|
|
974
|
+
"subagents.legacy.convert": "Convert into a preset row",
|
|
975
|
+
"subagents.legacy.hint": "Converting moves the old list into the preset you pick; until then the old keys stay untouched in the file (and keep working at run time).",
|
|
976
|
+
"subagents.tools.filter": "Filter tool names", "subagents.tools.remove": "Remove",
|
|
977
|
+
"subagents.adv.title": "Advanced options", "subagents.adv.inherit": "inherit", "subagents.adv.summary": "model {model} · preset limits {modes} · legacy allow {allow} / deny {deny}", "subagents.adv.note": "Allowlist = the subagent may only use the picked tools; denylist = everything except the picked ones. MCP tools are not listed: a subagent always keeps the MCP servers currently running.", "subagents.adv.loadFailed": "Could not read the candidate data (model catalogue / tool list); you can still fill the fields by hand.",
|
|
978
|
+
"preset.name.standard": "Standard mode", "preset.name.ptc": "PTC mode", "preset.name.minimal": "Minimal mode", "preset.name.cordis": "Creator mode",
|
|
979
|
+
"preset.short.standard": "Standard", "preset.short.ptc": "PTC", "preset.short.minimal": "Minimal", "preset.short.cordis": "Creator",
|
|
830
980
|
"subagents.field.body": "Persona prompt", "subagents.field.body.placeholder": "Describe the persona's role, responsibilities, and working style…",
|
|
831
981
|
"subagents.result.saved": "Persona saved: {name}", "subagents.result.deleted": "Persona deleted: {name}",
|
|
832
|
-
"subagents.delete.title": "Delete persona?", "subagents.delete.desc": "
|
|
982
|
+
"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).",
|
|
833
983
|
"subagents.import": "Import", "subagents.import.title": "Import personas", "subagents.import.hint": "Accepts .md or .zip (multi-select or drag in): one .md = one persona, the file name is the persona name; existing names are skipped.",
|
|
834
984
|
"subagents.result.imported": "Imported {count} persona(s): {names}",
|
|
835
985
|
"memory.import": "Import memories", "memory.import.title": "Import memories", "memory.import.scene": "Import into scene",
|
|
@@ -928,7 +1078,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
928
1078
|
|
|
929
1079
|
// MCP 服务页:与 Skills 页共用 dsm-* 设计语言(统计卡 / 筛选 / 分组卡片 / 模态框)。
|
|
930
1080
|
function MCPPage() {
|
|
931
|
-
const [state, setState] = React.useState({ loading: true, error: null, rows: [], paths: null, errors: [], warnings: [] })
|
|
1081
|
+
const [state, setState] = React.useState({ loading: true, error: null, rows: [], paths: null, errors: [], warnings: [], revealError: null })
|
|
932
1082
|
const [msg, setMsg] = React.useState(null)
|
|
933
1083
|
const [busy, setBusy] = React.useState(null)
|
|
934
1084
|
const [query, setQuery] = React.useState('')
|
|
@@ -941,13 +1091,15 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
941
1091
|
const [reveal, setReveal] = React.useState(false)
|
|
942
1092
|
const [settings, setSettings] = React.useState(null)
|
|
943
1093
|
const [noteDraft, setNoteDraft] = React.useState('')
|
|
1094
|
+
const [tokenDraft, setTokenDraft] = React.useState('')
|
|
1095
|
+
const [compactConfirm, setCompactConfirm] = React.useState(false)
|
|
944
1096
|
const [, setTick] = React.useState(0)
|
|
945
1097
|
|
|
946
1098
|
const refresh = (withReveal, onRows) => {
|
|
947
1099
|
const useReveal = withReveal === undefined ? reveal : withReveal === true
|
|
948
|
-
// 明文视图走独立的 mcpm-reveal op(host
|
|
949
|
-
// mcpm-list
|
|
950
|
-
|
|
1100
|
+
// 明文视图走独立的 mcpm-reveal op(host 端要求带对的访问令牌),
|
|
1101
|
+
// mcpm-list 始终脱敏、无令牌也能渲染页面。
|
|
1102
|
+
const applyRows = (res, revealError) => {
|
|
951
1103
|
const rows = (res && res.rows) || []
|
|
952
1104
|
setState({
|
|
953
1105
|
loading: false,
|
|
@@ -956,9 +1108,19 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
956
1108
|
paths: (res && res.paths) || null,
|
|
957
1109
|
errors: (res && res.errors) || [],
|
|
958
1110
|
warnings: (res && res.warnings) || [],
|
|
1111
|
+
revealError: revealError || null,
|
|
959
1112
|
})
|
|
960
1113
|
if (onRows) onRows(rows)
|
|
961
|
-
}
|
|
1114
|
+
}
|
|
1115
|
+
apiCall(useReveal ? 'mcpm-reveal' : 'mcpm-list', {}).then((res) => {
|
|
1116
|
+
// 明文被拒(未配令牌 / 令牌不对):退回脱敏列表,把原因与令牌入口单独显示。
|
|
1117
|
+
// 直接落进 state.error 会把整页变成错误态 —— "看不了明文"不等于"页面坏了"。
|
|
1118
|
+
if (useReveal && res && res.ok === false) {
|
|
1119
|
+
setReveal(false)
|
|
1120
|
+
return apiCall('mcpm-list', {}).then((masked) => applyRows(masked, { code: res.code, text: res.error }))
|
|
1121
|
+
}
|
|
1122
|
+
applyRows(res, null)
|
|
1123
|
+
}).catch((e) => setState({ loading: false, error: String((e && e.message) || e), rows: [], paths: null, errors: [], warnings: [], revealError: null }))
|
|
962
1124
|
}
|
|
963
1125
|
// The polling tick must always call the newest refresh closure, while
|
|
964
1126
|
// the interval itself only changes when the saved settings change.
|
|
@@ -1063,6 +1225,17 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1063
1225
|
if (fresh) setDetail(Object.assign({}, detail, { row: fresh }))
|
|
1064
1226
|
})
|
|
1065
1227
|
}
|
|
1228
|
+
// 明文被拒后的令牌入口:填对令牌 → 存 localStorage(随每个请求以 x-dsh-token 发出)
|
|
1229
|
+
// → 重试明文。宿主没配令牌时这一步不会成功,面板里的提示写了该怎么配。
|
|
1230
|
+
const applyToken = () => {
|
|
1231
|
+
if (!setAccessToken(tokenDraft)) return
|
|
1232
|
+
setReveal(true)
|
|
1233
|
+
refresh(true, (rows) => {
|
|
1234
|
+
if (!detail) return
|
|
1235
|
+
const fresh = rows.find((item) => item.id === detail.row.id && item.level === detail.row.level)
|
|
1236
|
+
if (fresh) setDetail(Object.assign({}, detail, { row: fresh }))
|
|
1237
|
+
})
|
|
1238
|
+
}
|
|
1066
1239
|
const saveNote = () => {
|
|
1067
1240
|
const row = detail && detail.row
|
|
1068
1241
|
if (!row) return
|
|
@@ -1080,6 +1253,20 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1080
1253
|
setConfirmRow(null)
|
|
1081
1254
|
if (row) run('mcpm-remove', { id: row.id, level: row.level }, row.id + ':remove')
|
|
1082
1255
|
}
|
|
1256
|
+
// 「整理补丁」:删掉多余的启停覆盖块(生效值不变,只删被盖住的块与纯 no-op)。
|
|
1257
|
+
// 成功文案带真实删除数量;一条都没删时如实说"已经是干净的",而不是笼统的"成功"。
|
|
1258
|
+
const doCompact = () => {
|
|
1259
|
+
setCompactConfirm(false)
|
|
1260
|
+
setMsg(null)
|
|
1261
|
+
setBusy('compact')
|
|
1262
|
+
apiCall('mcpm-compact', {}).then((res) => {
|
|
1263
|
+
if (res && res.ok) {
|
|
1264
|
+
const n = Number(res.removed || 0)
|
|
1265
|
+
setMsg({ kind: 'ok', text: n > 0 ? mt('mcp.compact.done', { count: n }) : mt('mcp.compact.clean') })
|
|
1266
|
+
refresh()
|
|
1267
|
+
} else setMsg({ kind: 'err', text: (res && res.error) || mt('mcp.msg.failed') })
|
|
1268
|
+
}).catch((e) => setMsg({ kind: 'err', text: String((e && e.message) || e) })).then(() => setBusy(null))
|
|
1269
|
+
}
|
|
1083
1270
|
const openDetail = (row) => {
|
|
1084
1271
|
setNoteDraft(row.notes || '')
|
|
1085
1272
|
setDetail({ row, loading: true, error: null, tools: [] })
|
|
@@ -1294,6 +1481,17 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1294
1481
|
React.createElement('div', { className: 'dsm-detail-title' }, mt('mcp.detail.tools', { count: detail.loading ? '…' : (detail.tools || []).length })),
|
|
1295
1482
|
toolListNode))
|
|
1296
1483
|
|
|
1484
|
+
const compactNode = compactConfirm && React.createElement(Modal, {
|
|
1485
|
+
key: 'mcp-compact',
|
|
1486
|
+
title: mt('mcp.compact.title'),
|
|
1487
|
+
closeLabel: mt('btn.close'),
|
|
1488
|
+
onClose: () => setCompactConfirm(false),
|
|
1489
|
+
},
|
|
1490
|
+
React.createElement('p', { className: 'dsm-desc' }, mt('mcp.compact.desc')),
|
|
1491
|
+
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
1492
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: () => setCompactConfirm(false) }, mt('btn.cancel')),
|
|
1493
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: busy !== null, onClick: doCompact }, mt('mcp.compact.confirm'))))
|
|
1494
|
+
|
|
1297
1495
|
const confirmNode = confirmRow && React.createElement(Modal, {
|
|
1298
1496
|
key: 'mcp-remove',
|
|
1299
1497
|
title: mt('mcp.remove.title'),
|
|
@@ -1315,7 +1513,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1315
1513
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null || state.loading, onClick: () => refresh() }, mt('btn.refresh')),
|
|
1316
1514
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: openAdd }, mt('mcp.btn.new')),
|
|
1317
1515
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy !== null || state.loading, onClick: () => run('mcpm-set-all', { enabled: true }, 'setall') }, mt('mcp.btn.enableAll')),
|
|
1318
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy !== null || state.loading, onClick: () => run('mcpm-set-all', { enabled: false }, 'setall') }, mt('mcp.btn.disableAll'))
|
|
1516
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy !== null || state.loading, onClick: () => run('mcpm-set-all', { enabled: false }, 'setall') }, mt('mcp.btn.disableAll')),
|
|
1517
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy !== null || state.loading, title: mt('mcp.btn.compact.title'), onClick: () => setCompactConfirm(true) }, mt('mcp.btn.compact')))),
|
|
1319
1518
|
React.createElement('div', { className: 'dsm-summary dsm-summary-3' },
|
|
1320
1519
|
[[summary.total, mt('mcp.stat.total')], [summary.enabled, mt('mcp.stat.enabled')], [summary.tools, mt('mcp.stat.tools')]].map((item) =>
|
|
1321
1520
|
React.createElement('div', { key: item[1], className: 'dsm-stat' },
|
|
@@ -1329,9 +1528,22 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1329
1528
|
React.createElement(Notice, { kind: 'err', text: state.error }),
|
|
1330
1529
|
React.createElement(Notice, { kind: 'warn', text: (state.errors && state.errors.length > 0) ? mt('mcp.err.warnings') + state.errors.join(';') : null }),
|
|
1331
1530
|
React.createElement(Notice, { kind: 'warn', text: (state.warnings && state.warnings.length > 0) ? state.warnings.join(';') : null }),
|
|
1531
|
+
state.revealError ? React.createElement('div', { className: 'dsm-token-panel' },
|
|
1532
|
+
React.createElement(Notice, { kind: 'err', text: state.revealError.text }),
|
|
1533
|
+
// 令牌不对 → 给输入框;宿主根本没配令牌 → 输入框也没用,只给"该怎么配"的说明,
|
|
1534
|
+
// 免得摆一个按了必然失败的按钮。
|
|
1535
|
+
state.revealError.code === 'error.secret.badToken' ? React.createElement('div', { className: 'dsm-token-row' },
|
|
1536
|
+
React.createElement('input', {
|
|
1537
|
+
className: 'dsm-control', type: 'password', value: tokenDraft,
|
|
1538
|
+
placeholder: mt('mcp.token.placeholder'), 'aria-label': mt('mcp.token.label'),
|
|
1539
|
+
onChange: (ev) => setTokenDraft(ev.target.value),
|
|
1540
|
+
}),
|
|
1541
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: !tokenDraft.trim(), onClick: applyToken }, mt('mcp.token.save'))) : null,
|
|
1542
|
+
React.createElement('p', { className: 'dsm-help' }, mt('mcp.token.hint'))) : null,
|
|
1332
1543
|
groupsNode,
|
|
1333
1544
|
formModalNode,
|
|
1334
1545
|
detailNode,
|
|
1546
|
+
compactNode,
|
|
1335
1547
|
confirmNode)
|
|
1336
1548
|
}
|
|
1337
1549
|
|
|
@@ -1343,6 +1555,34 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1343
1555
|
var em = React.useState(null), editModal = em[0], setEditModal = em[1]
|
|
1344
1556
|
var cm = React.useState(null), createModal = cm[0], setCreateModal = cm[1]
|
|
1345
1557
|
var ac = React.useState(null), applyConfirm = ac[0], setApplyConfirm = ac[1]
|
|
1558
|
+
// 回收站:null = 关闭;{loading, error, entries}
|
|
1559
|
+
var ts = React.useState(null), trash = ts[0], setTrash = ts[1]
|
|
1560
|
+
var tb = React.useState(false), trashBusy = tb[0], setTrashBusy = tb[1]
|
|
1561
|
+
/** 打开/刷新回收站(每次操作后重读,界面永远显示磁盘上的真实条目)。 */
|
|
1562
|
+
function loadTrash(keepOpen) {
|
|
1563
|
+
var next = Object.assign({ loading: true, error: null, entries: [] }, keepOpen ? trash || {} : {})
|
|
1564
|
+
setTrash(next)
|
|
1565
|
+
apiCall('agentsmd-trash-list', {}).then(function (res) {
|
|
1566
|
+
if (res && res.ok) setTrash({ loading: false, error: null, entries: res.trash || [] })
|
|
1567
|
+
else setTrash({ loading: false, error: (res && res.error) || t('trash.loadFailed'), entries: [] })
|
|
1568
|
+
}).catch(function (e) { setTrash({ loading: false, error: String((e && e.message) || e), entries: [] }) })
|
|
1569
|
+
}
|
|
1570
|
+
function restorePreset(item) {
|
|
1571
|
+
setTrashBusy(true)
|
|
1572
|
+
apiCall('agentsmd-trash-restore', { id: item.id }).then(function (res) {
|
|
1573
|
+
setTrashBusy(false)
|
|
1574
|
+
if (res && res.ok) { setState(function (s) { return Object.assign({}, s, { notice: t('trash.restored', { name: item.name }) }) }); refresh(); loadTrash(true) }
|
|
1575
|
+
else setTrash(function (cur) { return Object.assign({}, cur, { error: (res && res.error) || t('mcp.msg.failed') }) })
|
|
1576
|
+
}).catch(function (e) { setTrashBusy(false); setTrash(function (cur) { return Object.assign({}, cur, { error: String((e && e.message) || e) }) }) })
|
|
1577
|
+
}
|
|
1578
|
+
function purgePreset(item) {
|
|
1579
|
+
setTrashBusy(true)
|
|
1580
|
+
apiCall('agentsmd-trash-delete', { id: item.id }).then(function (res) {
|
|
1581
|
+
setTrashBusy(false)
|
|
1582
|
+
if (res && res.ok) { setState(function (s) { return Object.assign({}, s, { notice: t('trash.purged', { name: item.name }) }) }); loadTrash(true) }
|
|
1583
|
+
else setTrash(function (cur) { return Object.assign({}, cur, { error: (res && res.error) || t('mcp.msg.failed') }) })
|
|
1584
|
+
}).catch(function (e) { setTrashBusy(false); setTrash(function (cur) { return Object.assign({}, cur, { error: String((e && e.message) || e) }) }) })
|
|
1585
|
+
}
|
|
1346
1586
|
var importInput = React.useRef(null)
|
|
1347
1587
|
function doImport(file) {
|
|
1348
1588
|
if (!file) return
|
|
@@ -1473,9 +1713,22 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1473
1713
|
React.createElement('p', { className: 'dsm-help' }, t('agm.current.label', { label: curLabel }))),
|
|
1474
1714
|
React.createElement('div', { className: 'dsm-actions' },
|
|
1475
1715
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: function () { setCreateModal({ id: '', from: '', content: '', error: null }) } }, t('agm.btn.new')),
|
|
1476
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null, onClick: function () { if (importInput.current) importInput.current.click() } }, t('agm.btn.import'))
|
|
1716
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null, onClick: function () { if (importInput.current) importInput.current.click() } }, t('agm.btn.import')),
|
|
1717
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', onClick: function () { loadTrash(false) } }, t('trash.btn.open')))),
|
|
1477
1718
|
body,
|
|
1478
1719
|
state.notice ? React.createElement(Notice, { kind: 'ok', text: state.notice }) : null,
|
|
1720
|
+
trash ? React.createElement(TrashModal, {
|
|
1721
|
+
t: t,
|
|
1722
|
+
title: t('trash.title'),
|
|
1723
|
+
groupTitle: t('trash.group.presets'),
|
|
1724
|
+
entries: trash.entries,
|
|
1725
|
+
loading: trash.loading,
|
|
1726
|
+
error: trash.error,
|
|
1727
|
+
busy: trashBusy,
|
|
1728
|
+
onClose: function () { setTrash(null) },
|
|
1729
|
+
onRestore: restorePreset,
|
|
1730
|
+
onPurge: purgePreset,
|
|
1731
|
+
}) : null,
|
|
1479
1732
|
// 编辑:id 与内容都可改(改 id = 目录改名,场景绑定由宿主一起改名)。
|
|
1480
1733
|
editModal ? React.createElement(Modal, { title: t('agm.edit.title') + ' · ' + editModal.id, closeLabel: t('btn.cancel'), onClose: function () { setEditModal(null) } },
|
|
1481
1734
|
React.createElement('div', { className: 'dsm-field' },
|
|
@@ -1606,6 +1859,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1606
1859
|
var OP_BY_PATH = { "/state": "skill-state", "/enable": "skill-enable", "/disable": "skill-disable",
|
|
1607
1860
|
"/prefer": "skill-prefer", "/unprefer": "skill-unprefer",
|
|
1608
1861
|
"/source-enable": "skill-source-enable", "/source-disable": "skill-source-disable",
|
|
1862
|
+
// 「移除来源 / 恢复读取」两个按钮曾经打的是这两个路径,但映射表里没有它们 ——
|
|
1863
|
+
// callApi 直接 reject("unknown action"),弹窗又 catch 掉了,按钮点下去毫无反应。
|
|
1864
|
+
"/source-remove": "skill-source-remove", "/source-restore": "skill-source-restore",
|
|
1609
1865
|
"/delete": "skill-delete", "/trash-restore": "skill-trash-restore", "/trash-delete": "skill-trash-delete",
|
|
1610
1866
|
"/detail": "skill-detail", "/create": "skill-create", "/import": "skill-import",
|
|
1611
1867
|
"/upload": "skill-upload", "/browse": "skill-browse" };
|
|
@@ -1722,6 +1978,56 @@ function callApi(path, options) {
|
|
|
1722
1978
|
props.body,
|
|
1723
1979
|
props.foot || null)
|
|
1724
1980
|
}
|
|
1981
|
+
/**
|
|
1982
|
+
* 通用回收站弹窗:一行一个条目(名字 + 删除时间),右侧「恢复 / 永久删除」。
|
|
1983
|
+
*
|
|
1984
|
+
* 子智能体 / 场景 / 提示词预设三处共用;永久删除就在本弹窗里就地二次确认,
|
|
1985
|
+
* 不再叠一层弹窗。恢复失败的原因显示在弹窗顶部 —— 不静默(同名已存在时必须说清)。
|
|
1986
|
+
*/
|
|
1987
|
+
function TrashModal(props) {
|
|
1988
|
+
const t = props.t
|
|
1989
|
+
const c = React.useState(null)
|
|
1990
|
+
const confirmId = c[0], setConfirmId = c[1]
|
|
1991
|
+
const entries = props.entries || []
|
|
1992
|
+
const row = function (item) {
|
|
1993
|
+
const confirming = confirmId === item.id
|
|
1994
|
+
const actions = confirming
|
|
1995
|
+
? [
|
|
1996
|
+
React.createElement('span', { className: 'dsm-note', key: 'ask' }, t('trash.purge.confirm')),
|
|
1997
|
+
React.createElement('button', { key: 'yes', type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: props.busy, onClick: function () { setConfirmId(null); props.onPurge(item) } }, t('btn.delete.forever')),
|
|
1998
|
+
React.createElement('button', { key: 'no', type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: props.busy, onClick: function () { setConfirmId(null) } }, t('btn.cancel')),
|
|
1999
|
+
]
|
|
2000
|
+
: [
|
|
2001
|
+
React.createElement('button', { key: 'restore', type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: props.busy, onClick: function () { props.onRestore(item) } }, t('btn.restore')),
|
|
2002
|
+
React.createElement('button', { key: 'purge', type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: props.busy, onClick: function () { setConfirmId(item.id) } }, t('btn.delete.forever')),
|
|
2003
|
+
]
|
|
2004
|
+
return React.createElement('div', { className: 'dsm-trash-item', key: item.id },
|
|
2005
|
+
React.createElement('div', { className: 'dsm-trash-main' },
|
|
2006
|
+
React.createElement('div', { className: 'dsm-name' }, item.name),
|
|
2007
|
+
React.createElement('div', { className: 'dsm-note' }, t('trash.deletedAt', {
|
|
2008
|
+
time: item.deletedAt ? new Date(item.deletedAt).toLocaleString() : '—',
|
|
2009
|
+
}))),
|
|
2010
|
+
actions)
|
|
2011
|
+
}
|
|
2012
|
+
const body = props.loading
|
|
2013
|
+
? React.createElement('div', { className: 'dsm-empty' }, t('memory.loading'))
|
|
2014
|
+
: entries.length === 0
|
|
2015
|
+
? React.createElement('div', { className: 'dsm-empty' }, t('trash.empty'))
|
|
2016
|
+
: entries.map(row)
|
|
2017
|
+
return React.createElement(Modal, {
|
|
2018
|
+
key: 'trash-modal',
|
|
2019
|
+
title: props.title,
|
|
2020
|
+
closeLabel: t('btn.close'),
|
|
2021
|
+
onClose: props.onClose,
|
|
2022
|
+
},
|
|
2023
|
+
props.error ? React.createElement(Notice, { kind: 'err', text: props.error }) : null,
|
|
2024
|
+
React.createElement('div', { className: 'dsm-trash-group' },
|
|
2025
|
+
React.createElement('div', { className: 'dsm-trash-group-head' },
|
|
2026
|
+
React.createElement('span', { className: 'dsm-trash-group-title' }, props.groupTitle),
|
|
2027
|
+
React.createElement('span', { className: 'dsm-count' }, t('trash.items.count', { count: entries.length }))),
|
|
2028
|
+
React.createElement('div', { className: 'dsm-trash-group-body dsm-trash-scroll' }, body)))
|
|
2029
|
+
}
|
|
2030
|
+
|
|
1725
2031
|
/** 勾选行:复选框 + 名称/说明 + 右侧指标 + 可选行内动作。 */
|
|
1726
2032
|
function pickRow(props) {
|
|
1727
2033
|
return h('label', { key: props.key, className: 'dsm-pick' },
|
|
@@ -1879,6 +2185,18 @@ function callApi(path, options) {
|
|
|
1879
2185
|
setResult({ ok: false, text: (res && res.error) || t("error.action", { error: "unknown" }) });
|
|
1880
2186
|
}).catch(function (error) { setResult({ ok: false, text: t("error.action", { error: translateError(t, error) }) }); }).finally(function () { setBusy(false); });
|
|
1881
2187
|
}
|
|
2188
|
+
// 回收站「目录」的永久删除:把这条来源记录从插件里删掉(磁盘上的文件夹与文件不动,
|
|
2189
|
+
// 这也正是「移除来源」与「永久删除」的区别)。与「自定义目录」的移除是同一个 op ——
|
|
2190
|
+
// 只有自定义来源有记录可删,按约定发现的外部来源(agents/codex/claude)删不掉,
|
|
2191
|
+
// 所以界面上只对 root.custom === true 的行给这个按钮。
|
|
2192
|
+
function submitSourceForget(key, name) {
|
|
2193
|
+
setBusy(true); setResult(null);
|
|
2194
|
+
apiCall("skill-custom-remove", { key: key }).then(function (res) {
|
|
2195
|
+
setModal("trash");
|
|
2196
|
+
if (res && res.ok) { setResult({ ok: true, text: t("result.sourceForgotten", { name: name }) }); return refresh(true); }
|
|
2197
|
+
setResult({ ok: false, text: t("error.action", { error: translateError(t, res) }) });
|
|
2198
|
+
}).catch(function (error) { setModal("trash"); setResult({ ok: false, text: t("error.action", { error: translateError(t, error) }) }); }).finally(function () { setBusy(false); });
|
|
2199
|
+
}
|
|
1882
2200
|
function submitCreate() { post("/create", form, null).then(function (data) { setModal(null); setForm({ root: "dsh", name: "", description: "", body: "" }); setResult({ ok: true, text: t("result.created", { name: data.name }) }); }).catch(function () {}); }
|
|
1883
2201
|
function selectUploadFiles(files) { pickerOpenRef.current = false; var selected = inspectUploadSelection(files); if (!selected) return; if (selected.error) { setUpload(null); setResult({ ok: false, text: translateError(t, selected.error) }); return; } setUpload(selected); setResult(null); }
|
|
1884
2202
|
function openNativePicker(ref) { if (busy || pickerOpenRef.current || !ref.current) return; pickerOpenRef.current = true; ref.current.value = ""; function release() { setTimeout(function () { pickerOpenRef.current = false; }, 0); } window.addEventListener("focus", release, { once: true }); ref.current.click(); setTimeout(function () { pickerOpenRef.current = false; }, 30000); }
|
|
@@ -1940,7 +2258,7 @@ function callApi(path, options) {
|
|
|
1940
2258
|
}
|
|
1941
2259
|
|
|
1942
2260
|
var summary = data.summary || { total: 0, enabled: 0, disabled: 0, issues: 0 };
|
|
1943
|
-
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(VersionBadgeComponent, null)), 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); } }, t("btn.refresh")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: openCreate }, t("btn.create")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", 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); setCustomForm({ path: "", label: "" }); setModal("custom-add"); } }, t("btn.custom.add")))), h("div", { key: "summary", className: "dsm-summary dsm-summary-3" }, [[summary.total, "summary.total"], [summary.enabled, "summary.enabled"], [summary.disabled, "summary.disabled"]].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)),
|
|
2261
|
+
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(VersionBadgeComponent, null)), 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); } }, t("btn.refresh")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: openCreate }, t("btn.create")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", 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); setCustomForm({ path: "", label: "" }); setModal("custom-add"); } }, t("btn.custom.add")))), h("div", { key: "summary", className: "dsm-summary dsm-summary-3" }, [[summary.total, "summary.total"], [summary.enabled, "summary.enabled"], [summary.disabled, "summary.disabled"]].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)), h("button", { key: "trash", type: "button", className: "dsm-trash-row", onClick: function () { setModal("trash"); } }, h("span", null, t("trash.title")), h("span", { className: "dsm-trash-count" }, t("trash.row.summary", { skills: (data.trash || []).length, dirs: removedRoots.length })))]);
|
|
1944
2262
|
|
|
1945
2263
|
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")))));
|
|
1946
2264
|
if (modal === "import") content.push(h(Modal, { key: "import", className: "dsm-modal-import", title: t("import.title"), closeLabel: t("btn.close"), onClose: function () { pickerOpenRef.current = false; setUpload(null); setResult(null); setModal(null); } },
|
|
@@ -1954,7 +2272,17 @@ function callApi(path, options) {
|
|
|
1954
2272
|
h("div", { className: "dsm-upload-requirements" }, h("div", { className: "dsm-label" }, t("upload.requirements")), h("ul", null, h("li", null, t("upload.requirement.skill")), h("li", null, t("upload.requirement.frontmatter")), h("li", null, t("upload.requirement.copy")))),
|
|
1955
2273
|
h("div", { className: "dsm-modal-actions" }, h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy, onClick: function () { openNativePicker(folderInputRef); } }, t("btn.folder.pick")), h("button", { type: "button", className: "dsm-btn", disabled: busy || !upload, onClick: submitImport }, busy ? t("upload.importing") : t("btn.import.now")))
|
|
1956
2274
|
)); if (modal === "detail") content.push(h(Modal, { key: "detail", wide: true, title: t("detail.title"), closeLabel: t("btn.close"), onClose: function () { setModal(null); } }, detail ? h(react.Fragment, null, h("div", { className: "dsm-detail-path" }, detail.path), h("div", { className: "dsm-modal-actions" }, h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy, onClick: openSource }, t("btn.open.editor"))), h("div", { className: "dsm-detail-section" }, h("div", { className: "dsm-detail-title" }, t("detail.diagnostics")), detail.diagnostics.length ? detail.diagnostics.map(function (item, index) { return h("div", { key: index, className: "dsm-diag" }, t(item.code, item.params || {})); }) : h("div", { className: "dsm-note" }, t("detail.noIssues"))), h("div", { className: "dsm-detail-section" }, h("div", { className: "dsm-detail-title" }, t("detail.frontmatter")), renderFrontmatter(t, detail.frontmatter)), h("div", { className: "dsm-detail-section" }, h("div", { className: "dsm-detail-title" }, t("detail.body")), h("pre", { className: "dsm-code" }, detail.body || ""))) : h("div", { className: "dsm-empty" }, t("loading"))));
|
|
1957
|
-
|
|
2275
|
+
// 回收站分两部分:**目录**(被「移除来源」的目录,插件不再读取)与**技能**(移入
|
|
2276
|
+
// 回收站的技能)。两部分都给了「恢复」,永久删除在两边都指向"记录/文件真的没了":
|
|
2277
|
+
// 目录那边只删插件里的来源记录(磁盘上的文件夹一个字节都不动),技能那边才删文件。
|
|
2278
|
+
if (modal === "trash") content.push(h(Modal, { key: "trash-modal", title: t("trash.title"), closeLabel: t("btn.close"), onClose: function () { setModal(null); } },
|
|
2279
|
+
h("div", { className: "dsm-trash-group" },
|
|
2280
|
+
h("div", { className: "dsm-trash-group-head" }, h("span", { className: "dsm-trash-group-title" }, t("trash.section.dirs")), h("span", { className: "dsm-count" }, t(countKey("trash.dirs.count", removedRoots.length), { count: removedRoots.length }))),
|
|
2281
|
+
h("div", { className: "dsm-trash-group-body dsm-trash-scroll" }, removedRoots.length ? removedRoots.map(function (root) { var name = rootDisplayName(t, root); return h("div", { key: root.key, className: "dsm-trash-item" }, h("div", { className: "dsm-trash-main" }, h("div", { className: "dsm-name" }, name), h("div", { className: "dsm-note", title: root.path }, t("trash.dir.note") + " · " + root.path)), h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", disabled: busy, onClick: function () { post("/source-restore", { root: root.key }, "result.sourceRestored", { name: name }); } }, t("btn.source.restore")), root.custom === true ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet dsm-btn-danger", disabled: busy, onClick: function () { setModal({ type: "source-forget-confirm", key: root.key, name: name, path: root.path }); } }, t("btn.delete.forever")) : null); }) : h("div", { className: "dsm-empty" }, t("trash.section.dirs.empty")))),
|
|
2282
|
+
h("div", { className: "dsm-trash-group" },
|
|
2283
|
+
h("div", { className: "dsm-trash-group-head" }, h("span", { className: "dsm-trash-group-title" }, t("trash.section.skills")), h("span", { className: "dsm-count" }, t(countKey("trash.count", data.trash.length), { count: data.trash.length }))),
|
|
2284
|
+
h("div", { className: "dsm-trash-group-body dsm-trash-scroll" }, data.trash.length ? data.trash.map(function (item) { return h("div", { key: item.id, className: "dsm-trash-item" }, h("div", { className: "dsm-trash-main" }, h("div", { className: "dsm-name" }, item.name), h("div", { className: "dsm-note" }, t("trash.deletedAt", { time: new Date(item.deletedAt).toLocaleString() }) + " · " + t("trash.source", { source: trashRootLabel(item) }))), h("button", { className: "dsm-btn dsm-btn-quiet", disabled: busy, onClick: function () { post("/trash-restore", { id: item.id }, "result.restored", { name: item.name }); } }, t("btn.restore")), h("button", { className: "dsm-btn dsm-btn-quiet dsm-btn-danger", disabled: busy, onClick: function () { setModal({ type: "delete-confirm", id: item.id, name: item.name }); } }, t("btn.delete.forever"))); }) : h("div", { className: "dsm-empty" }, t("trash.empty"))))));
|
|
2285
|
+
if (modal && modal.type === "source-forget-confirm") content.push(h(Modal, { key: "source-forget-confirm", title: t("confirm.source.forget.title"), closeLabel: t("btn.close"), onClose: function () { setModal("trash"); } }, h("p", { className: "dsm-desc" }, t("confirm.source.forget.desc", { name: modal.name })), h("p", { className: "dsm-help" }, t("confirm.source.forget.hint", { path: modal.path })), h("div", { className: "dsm-modal-actions" }, h("button", { className: "dsm-btn dsm-btn-danger", disabled: busy, onClick: function () { submitSourceForget(modal.key, modal.name); } }, t("btn.delete.forever")))));
|
|
1958
2286
|
if (modal && modal.type === "trash-confirm") content.push(h(Modal, { key: "trash-confirm", title: t("confirm.trash.title"), closeLabel: t("btn.close"), onClose: function () { setModal(null); } }, h("p", { className: "dsm-desc" }, t("confirm.trash.desc", { name: modal.name })), h("div", { className: "dsm-modal-actions" }, h("button", { className: "dsm-btn", disabled: busy, onClick: function () { post("/delete", { root: modal.root, name: modal.name }, "result.trashed", { name: modal.name }).then(function () { setModal(null); }).catch(function () {}); } }, t("btn.trash")))));
|
|
1959
2287
|
if (modal && modal.type === "delete-confirm") content.push(h(Modal, { key: "delete-confirm", title: t("confirm.delete.title"), closeLabel: t("btn.close"), onClose: function () { setModal("trash"); } }, h("p", { className: "dsm-desc" }, t("confirm.delete.desc", { name: modal.name })), h("div", { className: "dsm-modal-actions" }, h("button", { className: "dsm-btn dsm-btn-danger", disabled: busy, onClick: function () { post("/trash-delete", { id: modal.id }, "result.deleted", { name: modal.name }).then(function () { setModal("trash"); }).catch(function () {}); } }, t("btn.delete.forever")))));
|
|
1960
2288
|
if (modal === "custom-add") content.push(h(Modal, { key: "custom-add", title: t("custom.add.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("custom.add.path")), h("div", { className: "dsm-dir-row" }, h("input", { className: "dsm-control", value: customForm.path, placeholder: t("custom.add.path.placeholder"), onChange: function (e) { setCustomForm(Object.assign({}, customForm, { path: e.target.value })); } }), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy, onClick: openCustomDir, title: t("custom.add.openDir.title") }, t("btn.openDir")))), h("label", { className: "dsm-field" }, h("span", { className: "dsm-label" }, t("custom.add.label")), h("input", { className: "dsm-control", value: customForm.label, placeholder: t("custom.add.label.placeholder"), onChange: function (e) { setCustomForm(Object.assign({}, customForm, { label: e.target.value })); } })), h("p", { className: "dsm-help" }, t("custom.add.help")), result ? h("div", { className: "dsm-feedback" + (result.warning ? " dsm-warning" : result.ok ? "" : " dsm-error"), role: "alert" }, result.text) : null), customForm.picking ? h(DirPickerModal, { key: "custom-dir-picker", title: t("custom.add.picker.title"), initial: String(customForm.path || ""), closeLabel: t("btn.close"), onClose: function () { setCustomForm(Object.assign({}, customForm, { picking: false })); }, onPick: function (path) { setCustomForm(Object.assign({}, customForm, { path: path, picking: false })); } }) : null, h("div", { className: "dsm-modal-actions" }, h("button", { type: "button", className: "dsm-btn", disabled: busy || !String(customForm.path || "").trim(), onClick: submitCustomAdd }, t("custom.add.submit")))));
|
|
@@ -2617,6 +2945,34 @@ function callApi(path, options) {
|
|
|
2617
2945
|
var presetOptions = pos[0], setPresetOptions = pos[1]
|
|
2618
2946
|
var dts = React.useState(null)
|
|
2619
2947
|
var drillTools = dts[0], setDrillTools = dts[1]
|
|
2948
|
+
// 场景回收站(记录 + 档案):null = 关闭;{loading, error, entries}
|
|
2949
|
+
var sts = React.useState(null)
|
|
2950
|
+
var trash = sts[0], setTrash = sts[1]
|
|
2951
|
+
var stb = React.useState(false)
|
|
2952
|
+
var trashBusy = stb[0], setTrashBusy = stb[1]
|
|
2953
|
+
function loadTrash(keepOpen) {
|
|
2954
|
+
setTrash(Object.assign({ loading: true, error: null, entries: [] }, keepOpen ? trash || {} : {}))
|
|
2955
|
+
apiCall('scene-trash-list', {}).then(function (res) {
|
|
2956
|
+
if (res && res.ok) setTrash({ loading: false, error: null, entries: res.trash || [] })
|
|
2957
|
+
else setTrash({ loading: false, error: (res && res.error) || t('trash.loadFailed'), entries: [] })
|
|
2958
|
+
}).catch(function (e) { setTrash({ loading: false, error: String((e && e.message) || e), entries: [] }) })
|
|
2959
|
+
}
|
|
2960
|
+
function restoreScene(item) {
|
|
2961
|
+
setTrashBusy(true)
|
|
2962
|
+
apiCall('scene-trash-restore', { id: item.id }).then(function (res) {
|
|
2963
|
+
setTrashBusy(false)
|
|
2964
|
+
if (res && res.ok) { setResult({ ok: true, text: t('trash.restored', { name: item.name }) }); refresh(true); loadTrash(true) }
|
|
2965
|
+
else setTrash(function (cur) { return Object.assign({}, cur, { error: (res && res.error) || t('mcp.msg.failed') }) })
|
|
2966
|
+
}).catch(function (e) { setTrashBusy(false); setTrash(function (cur) { return Object.assign({}, cur, { error: String((e && e.message) || e) }) }) })
|
|
2967
|
+
}
|
|
2968
|
+
function purgeScene(item) {
|
|
2969
|
+
setTrashBusy(true)
|
|
2970
|
+
apiCall('scene-trash-delete', { id: item.id }).then(function (res) {
|
|
2971
|
+
setTrashBusy(false)
|
|
2972
|
+
if (res && res.ok) { setResult({ ok: true, text: t('trash.purged', { name: item.name }) }); loadTrash(true) }
|
|
2973
|
+
else setTrash(function (cur) { return Object.assign({}, cur, { error: (res && res.error) || t('mcp.msg.failed') }) })
|
|
2974
|
+
}).catch(function (e) { setTrashBusy(false); setTrash(function (cur) { return Object.assign({}, cur, { error: String((e && e.message) || e) }) }) })
|
|
2975
|
+
}
|
|
2620
2976
|
React.useEffect(function () { if (!result || result.ok !== true) return undefined; var timer = setTimeout(function () { setResult(null) }, 2600); return function () { clearTimeout(timer) } }, [result])
|
|
2621
2977
|
function refresh(silent) {
|
|
2622
2978
|
if (!silent) setData(function (prev) { return Object.assign({}, prev, { loading: true, error: null }) })
|
|
@@ -2737,6 +3093,8 @@ function callApi(path, options) {
|
|
|
2737
3093
|
},
|
|
2738
3094
|
inventory: {
|
|
2739
3095
|
mcpServers: inv.mcpServers || [], skills: inv.skills || [], subagents: inv.subagents || [], tools: inv.tools || [],
|
|
3096
|
+
// 模式名与顺序(标准 → PTC → 极简 → 创造 → 自建):人设行的限制摘要按这个顺序排。
|
|
3097
|
+
presets: inv.presets || [],
|
|
2740
3098
|
// 记忆段的数据源(宿主 scene-inventory 回传;老宿主缺失时退化为空列表而不是崩)。
|
|
2741
3099
|
// 只留扁平 memories——记忆段现在按「记忆所属场景」自己筛,不再需要场景清单。
|
|
2742
3100
|
memories: inv.memories || [],
|
|
@@ -2931,15 +3289,44 @@ function callApi(path, options) {
|
|
|
2931
3289
|
})
|
|
2932
3290
|
}
|
|
2933
3291
|
/** 段 2:技能集。 */
|
|
3292
|
+
/**
|
|
3293
|
+
* 技能行的人类可读展示:技能名 + 来源名。
|
|
3294
|
+
*
|
|
3295
|
+
* 勾选集里存的仍是 `key`(`<来源 key>/<技能名>`)——它才是身份的载体;但**显示**
|
|
3296
|
+
* 必须用技能名与来源名:自定义目录的来源 key 是 `custom-<hash>`,直接显示 key 会
|
|
3297
|
+
* 让导入的技能在场景里变成一串哈希(用户 2026-09-15 报的)。
|
|
3298
|
+
* 老宿主没回传 name/root* 时退回按 key 拆,至少不再显示整串哈希前缀之外的路径。
|
|
3299
|
+
*/
|
|
3300
|
+
function skillDisplay(item) {
|
|
3301
|
+
var key = String((item && item.key) || '')
|
|
3302
|
+
var slash = key.indexOf('/')
|
|
3303
|
+
var keyName = slash >= 0 ? key.slice(slash + 1) : key
|
|
3304
|
+
var keyRoot = slash >= 0 ? key.slice(0, slash) : ''
|
|
3305
|
+
var root = {
|
|
3306
|
+
key: String((item && item.rootKey) || keyRoot),
|
|
3307
|
+
label: String((item && item.rootLabel) || keyRoot),
|
|
3308
|
+
localeKey: item && item.rootLocaleKey,
|
|
3309
|
+
kind: item && item.rootKind,
|
|
3310
|
+
}
|
|
3311
|
+
return {
|
|
3312
|
+
name: String((item && item.name) || keyName || key),
|
|
3313
|
+
source: root.key ? rootDisplayName(t, root) : '',
|
|
3314
|
+
key: key,
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
2934
3317
|
function skillsSeg() {
|
|
2935
3318
|
var sections = modalSections()
|
|
2936
3319
|
var defined = !!sections.skills
|
|
3320
|
+
var allItems = modal.inventory.skills || []
|
|
2937
3321
|
var items = (function () {
|
|
2938
3322
|
var q = String(modal.skillQuery || '').trim().toLowerCase()
|
|
2939
|
-
|
|
2940
|
-
|
|
3323
|
+
if (!q) return allItems
|
|
3324
|
+
// 搜索同时匹配技能名与来源名(用户按"来源"找技能是常见动作)。
|
|
3325
|
+
return allItems.filter(function (x) {
|
|
3326
|
+
var d = skillDisplay(x)
|
|
3327
|
+
return (d.name + ' ' + d.source + ' ' + d.key).toLowerCase().indexOf(q) >= 0
|
|
3328
|
+
})
|
|
2941
3329
|
})()
|
|
2942
|
-
var allItems = modal.inventory.skills || []
|
|
2943
3330
|
return seg({
|
|
2944
3331
|
title: t('memory.archive.skills'),
|
|
2945
3332
|
count: defined ? t('scenes.seg.checked', { checked: sections.skills.length, total: allItems.length }) : t('scenes.archive.sectionOff'),
|
|
@@ -2953,12 +3340,17 @@ function callApi(path, options) {
|
|
|
2953
3340
|
segFilter(modal.skillQuery, function (v) { setModal(Object.assign({}, modal, { skillQuery: v })) }, t('scenes.filter.skills')),
|
|
2954
3341
|
items.length
|
|
2955
3342
|
? React.createElement('div', null, items.map(function (item) {
|
|
3343
|
+
var d = skillDisplay(item)
|
|
2956
3344
|
return pickRow({
|
|
2957
3345
|
disabled: busy,
|
|
2958
3346
|
key: item.key,
|
|
2959
3347
|
checked: sections.skills.indexOf(item.key) >= 0,
|
|
2960
|
-
name:
|
|
2961
|
-
|
|
3348
|
+
name: d.name,
|
|
3349
|
+
desc: d.source || null,
|
|
3350
|
+
descTitle: d.key,
|
|
3351
|
+
meta: item.shadowed
|
|
3352
|
+
? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, t('status.shadowed'))
|
|
3353
|
+
: (item.enabled === false ? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, t('memory.scene.off')) : null),
|
|
2962
3354
|
onChange: function () {
|
|
2963
3355
|
var list = sections.skills.slice(); var i = list.indexOf(item.key)
|
|
2964
3356
|
if (i >= 0) list.splice(i, 1); else list.push(item.key)
|
|
@@ -2972,6 +3364,35 @@ function callApi(path, options) {
|
|
|
2972
3364
|
})
|
|
2973
3365
|
}
|
|
2974
3366
|
/** 段 3:子智能体绑定。 */
|
|
3367
|
+
/** 模式名(官方四个走词典,自建预设用它自己的名字,不翻译)。 */
|
|
3368
|
+
function scenePresetLabel(id, short) {
|
|
3369
|
+
var presets = (modal.inventory && modal.inventory.presets) || []
|
|
3370
|
+
var hit = presets.filter(function (p) { return p.id === id })[0]
|
|
3371
|
+
var shipped = { minimal: 1, standard: 1, ptc: 1, cordis: 1 }
|
|
3372
|
+
if (shipped[id] === 1 && (!hit || String(hit.trust || 'system') !== 'user')) {
|
|
3373
|
+
return t((short ? 'preset.short.' : 'preset.name.') + id)
|
|
3374
|
+
}
|
|
3375
|
+
return hit ? String(hit.name || id) : id
|
|
3376
|
+
}
|
|
3377
|
+
/**
|
|
3378
|
+
* 人设的按模式工具限制摘要(「标准 白3 · PTC 黑2」)。场景本身不引入"模式"这个
|
|
3379
|
+
* 维度 —— 会话跑在哪个模式是建会话时定的,场景改不了;这里只如实显示配了什么。
|
|
3380
|
+
*/
|
|
3381
|
+
function personaRuleSummary(p) {
|
|
3382
|
+
var rules = (p && p.toolsByPreset) || {}
|
|
3383
|
+
var order = ((modal.inventory && modal.inventory.presets) || []).map(function (x) { return x.id })
|
|
3384
|
+
// 按 roster 顺序排(标准 → PTC → 极简 → 创造 → 自建),文件里的书写顺序不参与展示。
|
|
3385
|
+
var ids = Object.keys(rules).sort(function (a, b) {
|
|
3386
|
+
var ia = order.indexOf(a), ib = order.indexOf(b)
|
|
3387
|
+
return (ia < 0 ? 999 : ia) - (ib < 0 ? 999 : ib)
|
|
3388
|
+
})
|
|
3389
|
+
if (!ids.length) return null
|
|
3390
|
+
return ids.map(function (id) {
|
|
3391
|
+
var rule = rules[id] || {}
|
|
3392
|
+
var count = (rule.names || []).length
|
|
3393
|
+
return scenePresetLabel(id, true) + ' ' + t(rule.mode === 'deny' ? 'subagents.mode.denyOn' : 'subagents.mode.allowOn', { count: count })
|
|
3394
|
+
}).join(' · ')
|
|
3395
|
+
}
|
|
2975
3396
|
function subagentsSeg() {
|
|
2976
3397
|
var sections = modalSections()
|
|
2977
3398
|
var defined = !!sections.subagents
|
|
@@ -3001,6 +3422,9 @@ function callApi(path, options) {
|
|
|
3001
3422
|
checked: sections.subagents.indexOf(item.name) >= 0,
|
|
3002
3423
|
name: item.name,
|
|
3003
3424
|
desc: item.description || null,
|
|
3425
|
+
// 按模式配的工具限制摘要(没配就不显示):场景改不了会话的模式,
|
|
3426
|
+
// 但要让用户看得见"这条人设在某些模式下是被限工具的"。
|
|
3427
|
+
meta: personaRuleSummary(item),
|
|
3004
3428
|
onChange: function () {
|
|
3005
3429
|
var list = sections.subagents.slice(); var i = list.indexOf(item.name)
|
|
3006
3430
|
if (i >= 0) list.splice(i, 1); else list.push(item.name)
|
|
@@ -3135,7 +3559,8 @@ function callApi(path, options) {
|
|
|
3135
3559
|
React.createElement('p', { className: 'dsm-desc' }, t('scenes.desc'))),
|
|
3136
3560
|
React.createElement('div', { className: 'dsm-actions' },
|
|
3137
3561
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || data.loading, onClick: function () { refresh() } }, t('memory.btn.refresh')),
|
|
3138
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: openCreateScene }, t('memory.btn.newScene'))
|
|
3562
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: openCreateScene }, t('memory.btn.newScene')),
|
|
3563
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', onClick: function () { loadTrash(false) } }, t('trash.btn.open')))),
|
|
3139
3564
|
React.createElement('div', { key: 'stats', className: 'dsm-summary dsm-summary-3' },
|
|
3140
3565
|
[[sceneStats.total, t('scenes.stat.total')], [sceneStats.active, t('scenes.stat.active')], [sceneStats.archives, t('scenes.stat.archives')]].map(function (item) {
|
|
3141
3566
|
return React.createElement('div', { key: item[1], className: 'dsm-stat' },
|
|
@@ -3244,6 +3669,18 @@ function callApi(path, options) {
|
|
|
3244
3669
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { submitDeleteScene(modal.name) } }, t('memory.btn.deleteScene')))) : null,
|
|
3245
3670
|
// 档案编辑器(见 archiveNode):三段共用「段卡片 + 勾选行」排版。
|
|
3246
3671
|
archiveNode(),
|
|
3672
|
+
trash ? React.createElement(TrashModal, {
|
|
3673
|
+
t: t,
|
|
3674
|
+
title: t('trash.title'),
|
|
3675
|
+
groupTitle: t('trash.group.scenes'),
|
|
3676
|
+
entries: trash.entries,
|
|
3677
|
+
loading: trash.loading,
|
|
3678
|
+
error: trash.error,
|
|
3679
|
+
busy: trashBusy,
|
|
3680
|
+
onClose: function () { setTrash(null) },
|
|
3681
|
+
onRestore: restoreScene,
|
|
3682
|
+
onPurge: purgeScene,
|
|
3683
|
+
}) : null,
|
|
3247
3684
|
)
|
|
3248
3685
|
}
|
|
3249
3686
|
// ---------- 子智能体页:人设文件(~/.dsh/subagents/*.md)管理 ----------
|
|
@@ -3256,6 +3693,34 @@ function callApi(path, options) {
|
|
|
3256
3693
|
var result = rs[0], setResult = rs[1]
|
|
3257
3694
|
var ms = React.useState(null)
|
|
3258
3695
|
var modal = ms[0], setModal = ms[1]
|
|
3696
|
+
// 回收站:删除人设 = 移入回收站(宿主侧 subagent-delete),这里列出/恢复/永久删除。
|
|
3697
|
+
var ts = React.useState(null)
|
|
3698
|
+
var trash = ts[0], setTrash = ts[1]
|
|
3699
|
+
var tb = React.useState(false)
|
|
3700
|
+
var trashBusy = tb[0], setTrashBusy = tb[1]
|
|
3701
|
+
function loadTrash(keepOpen) {
|
|
3702
|
+
setTrash(Object.assign({ loading: true, error: null, entries: [] }, keepOpen ? trash || {} : {}))
|
|
3703
|
+
apiCall('subagent-trash-list', {}).then(function (res) {
|
|
3704
|
+
if (res && res.ok) setTrash({ loading: false, error: null, entries: res.trash || [] })
|
|
3705
|
+
else setTrash({ loading: false, error: (res && res.error) || t('trash.loadFailed'), entries: [] })
|
|
3706
|
+
}).catch(function (e) { setTrash({ loading: false, error: String((e && e.message) || e), entries: [] }) })
|
|
3707
|
+
}
|
|
3708
|
+
function restorePersona(item) {
|
|
3709
|
+
setTrashBusy(true)
|
|
3710
|
+
apiCall('subagent-trash-restore', { id: item.id }).then(function (res) {
|
|
3711
|
+
setTrashBusy(false)
|
|
3712
|
+
if (res && res.ok) { setResult({ ok: true, text: t('trash.restored', { name: item.name }) }); refresh(true); loadTrash(true) }
|
|
3713
|
+
else setTrash(function (cur) { return Object.assign({}, cur, { error: (res && res.error) || t('mcp.msg.failed') }) })
|
|
3714
|
+
}).catch(function (e) { setTrashBusy(false); setTrash(function (cur) { return Object.assign({}, cur, { error: String((e && e.message) || e) }) }) })
|
|
3715
|
+
}
|
|
3716
|
+
function purgePersona(item) {
|
|
3717
|
+
setTrashBusy(true)
|
|
3718
|
+
apiCall('subagent-trash-delete', { id: item.id }).then(function (res) {
|
|
3719
|
+
setTrashBusy(false)
|
|
3720
|
+
if (res && res.ok) { setResult({ ok: true, text: t('trash.purged', { name: item.name }) }); loadTrash(true) }
|
|
3721
|
+
else setTrash(function (cur) { return Object.assign({}, cur, { error: (res && res.error) || t('mcp.msg.failed') }) })
|
|
3722
|
+
}).catch(function (e) { setTrashBusy(false); setTrash(function (cur) { return Object.assign({}, cur, { error: String((e && e.message) || e) }) }) })
|
|
3723
|
+
}
|
|
3259
3724
|
React.useEffect(function () { if (!result || result.ok !== true) return undefined; var timer = setTimeout(function () { setResult(null) }, 2600); return function () { clearTimeout(timer) } }, [result])
|
|
3260
3725
|
function refresh(silent) {
|
|
3261
3726
|
if (!silent) setData(function (prev) { return Object.assign({}, prev, { loading: true, error: null }) })
|
|
@@ -3269,7 +3734,7 @@ function callApi(path, options) {
|
|
|
3269
3734
|
* 人设表单的候选数据(模型目录 / 全体预设工具并集)只在**首次展开高级选项**时拉取:
|
|
3270
3735
|
* 宿主枚举预设需要为尚未挂载的预设建立 standing mount,不该在打开弹窗时就付这个代价。
|
|
3271
3736
|
*/
|
|
3272
|
-
var cands = React.useState({ loaded: false, loading: false, error: null, models: [], tools: [] })
|
|
3737
|
+
var cands = React.useState({ loaded: false, loading: false, error: null, models: [], tools: [], presets: [] })
|
|
3273
3738
|
var cand = cands[0], setCand = cands[1]
|
|
3274
3739
|
function loadCandidates() {
|
|
3275
3740
|
if (cand.loaded || cand.loading) return
|
|
@@ -3282,18 +3747,31 @@ function callApi(path, options) {
|
|
|
3282
3747
|
error: failed ? t('subagents.adv.loadFailed') : null,
|
|
3283
3748
|
models: (mres && mres.models) || [],
|
|
3284
3749
|
tools: (tres && tres.tools) || [],
|
|
3750
|
+
// presets 必须原样带过来:四行模式的名字、顺序、每行的工具清单全靠它,
|
|
3751
|
+
// 早先这里只留 models/tools,于是分组标题退回裸 id(中文界面里显示英文)。
|
|
3752
|
+
presets: (tres && tres.presets) || [],
|
|
3285
3753
|
})
|
|
3286
3754
|
}).catch(function (e) {
|
|
3287
|
-
setCand({ loaded: true, loading: false, error: String((e && e.message) || e), models: [], tools: [] })
|
|
3755
|
+
setCand({ loaded: true, loading: false, error: String((e && e.message) || e), models: [], tools: [], presets: [] })
|
|
3288
3756
|
})
|
|
3289
3757
|
}
|
|
3290
3758
|
/** 高级选项:默认收起;已经在用模型/工具限制的人设自动展开(否则用户看不见自己配了什么)。 */
|
|
3291
3759
|
function initialAdvanced(p) {
|
|
3292
|
-
|
|
3760
|
+
var modes = p && p.toolsByPreset ? Object.keys(p.toolsByPreset).length : 0
|
|
3761
|
+
return !!((p && (p.model || p.provider)) || (p && ((p.tools || []).length || (p.toolsDeny || []).length || modes)))
|
|
3762
|
+
}
|
|
3763
|
+
/** 编辑态里按模式分组的名单(深拷贝:取消编辑不留痕)。 */
|
|
3764
|
+
function cloneRules(source) {
|
|
3765
|
+
var out = {}
|
|
3766
|
+
Object.keys(source || {}).forEach(function (id) {
|
|
3767
|
+
var rule = source[id] || {}
|
|
3768
|
+
out[id] = { mode: rule.mode === 'deny' ? 'deny' : 'allow', names: (rule.names || []).slice() }
|
|
3769
|
+
})
|
|
3770
|
+
return out
|
|
3293
3771
|
}
|
|
3294
3772
|
function openEditor(name) {
|
|
3295
3773
|
if (!name) {
|
|
3296
|
-
setModal({ type: 'editor', mode: 'create', advanced: false, form: { name: '', description: '', provider: '', model: '', tools: [], toolsDeny: [], body: '', error: null } })
|
|
3774
|
+
setModal({ type: 'editor', mode: 'create', advanced: false, openMode: null, modeQuery: '', legacyTarget: '', form: { name: '', description: '', provider: '', model: '', tools: [], toolsDeny: [], toolsByPreset: {}, body: '', error: null } })
|
|
3297
3775
|
return
|
|
3298
3776
|
}
|
|
3299
3777
|
setBusy(true)
|
|
@@ -3301,9 +3779,10 @@ function callApi(path, options) {
|
|
|
3301
3779
|
setBusy(false)
|
|
3302
3780
|
if (res && res.ok) {
|
|
3303
3781
|
var p = res.persona || {}
|
|
3304
|
-
setModal({ type: 'editor', mode: 'edit', advanced: initialAdvanced(p), form: {
|
|
3782
|
+
setModal({ type: 'editor', mode: 'edit', advanced: initialAdvanced(p), openMode: null, modeQuery: '', legacyTarget: '', form: {
|
|
3305
3783
|
name: p.name || name, description: p.description || '', provider: p.provider || '', model: p.model || '',
|
|
3306
|
-
tools: (p.tools || []).slice(), toolsDeny: (p.toolsDeny || []).slice(),
|
|
3784
|
+
tools: (p.tools || []).slice(), toolsDeny: (p.toolsDeny || []).slice(),
|
|
3785
|
+
toolsByPreset: cloneRules(p.toolsByPreset), body: p.body || '', error: null,
|
|
3307
3786
|
} })
|
|
3308
3787
|
} else setResult({ ok: false, text: translateError(t, res) })
|
|
3309
3788
|
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
@@ -3329,72 +3808,176 @@ function callApi(path, options) {
|
|
|
3329
3808
|
return hit ? (f.provider + '\u0000' + f.model) : '__custom__'
|
|
3330
3809
|
}
|
|
3331
3810
|
/**
|
|
3332
|
-
*
|
|
3333
|
-
*
|
|
3334
|
-
*
|
|
3335
|
-
* 人设可能在任何预设下被子代理复用,只列当前会话的工具会让换预设后的子代理启动失败
|
|
3336
|
-
* (官方 toolFilter 对未知名直接拒绝启动)。因此这里宁可多列,并把手填入口留出来。
|
|
3811
|
+
* 官方四个预设的中英名走插件词典(官方 preset.yml 里只有中文名,直接用会让英文界面
|
|
3812
|
+
* 露出中文);**自建预设用它自己的名字,不翻译** —— 与官方 display 规则一致。
|
|
3813
|
+
* 四个预设 id → 词典键的映射只认官方发布的这四个 id。
|
|
3337
3814
|
*/
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
var
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
})
|
|
3815
|
+
var SHIPPED_PRESET_IDS = { minimal: 1, standard: 1, ptc: 1, cordis: 1 }
|
|
3816
|
+
function presetLabel(p) {
|
|
3817
|
+
if (!p) return ''
|
|
3818
|
+
var id = String(p.id || '')
|
|
3819
|
+
if (SHIPPED_PRESET_IDS[id] === 1 && String(p.trust || 'system') !== 'user') return t('preset.name.' + id)
|
|
3820
|
+
return String(p.name || id)
|
|
3821
|
+
}
|
|
3822
|
+
/** 该模式下可勾选的工具名;`null` = 宿主没给出该模式的清单(读不到,不能假装是空)。 */
|
|
3823
|
+
function modeTools(id) {
|
|
3824
|
+
var hit = (cand.presets || []).filter(function (p) { return p.id === id })[0]
|
|
3825
|
+
return hit && Array.isArray(hit.tools) ? hit.tools : null
|
|
3826
|
+
}
|
|
3827
|
+
function modeRule(id) { return (modal.form.toolsByPreset || {})[id] }
|
|
3828
|
+
function setModeRule(id, rule) {
|
|
3829
|
+
var next = Object.assign({}, modal.form.toolsByPreset || {})
|
|
3830
|
+
if (rule) next[id] = rule; else delete next[id]
|
|
3831
|
+
setModal(Object.assign({}, modal, {
|
|
3832
|
+
form: Object.assign({}, modal.form, { toolsByPreset: next }),
|
|
3833
|
+
openMode: modal.openMode === id && !rule ? null : modal.openMode,
|
|
3834
|
+
}))
|
|
3835
|
+
}
|
|
3836
|
+
/** 模式行上的一句话状态:未开启 / 白名单 N 个 / 黑名单 N 个 / 已开启但没勾(= 不限制)。 */
|
|
3837
|
+
function modeSummary(id) {
|
|
3838
|
+
var rule = modeRule(id)
|
|
3839
|
+
if (!rule) return { text: t('subagents.mode.off'), cls: 'dsm-mode-off' }
|
|
3840
|
+
var n = (rule.names || []).length
|
|
3841
|
+
if (!n) return { text: t('subagents.mode.empty'), cls: 'dsm-mode-off' }
|
|
3842
|
+
return {
|
|
3843
|
+
text: t(rule.mode === 'deny' ? 'subagents.mode.denyOn' : 'subagents.mode.allowOn', { count: n }),
|
|
3844
|
+
cls: rule.mode === 'deny' ? 'dsm-mode-deny' : 'dsm-mode-allow',
|
|
3369
3845
|
}
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3846
|
+
}
|
|
3847
|
+
/**
|
|
3848
|
+
* 启动某一侧名单:生效 + 就地展开。同一模式内白/黑互斥 —— 启动一侧就把模式切过去,
|
|
3849
|
+
* **已勾选的名字保留**(只换语义),这样"改成黑名单"不用从头再勾一遍。
|
|
3850
|
+
*/
|
|
3851
|
+
function startMode(id, mode) {
|
|
3852
|
+
var prev = modeRule(id)
|
|
3853
|
+
var names = prev && Array.isArray(prev.names) ? prev.names.slice() : []
|
|
3854
|
+
setModal(Object.assign({}, modal, {
|
|
3855
|
+
openMode: id, modeQuery: '',
|
|
3856
|
+
form: Object.assign({}, modal.form, {
|
|
3857
|
+
toolsByPreset: Object.assign({}, modal.form.toolsByPreset || {}, { [id]: { mode: mode, names: names } }),
|
|
3858
|
+
}),
|
|
3859
|
+
}))
|
|
3860
|
+
}
|
|
3861
|
+
function stopMode(id) { setModeRule(id, null) }
|
|
3862
|
+
function toggleModeTool(id, name) {
|
|
3863
|
+
var rule = modeRule(id)
|
|
3864
|
+
if (!rule) return
|
|
3865
|
+
setModeRule(id, { mode: rule.mode, names: toggled(rule.names || [], name) })
|
|
3866
|
+
}
|
|
3867
|
+
/**
|
|
3868
|
+
* 展开后的勾选区:只列**该模式自己的**工具。这样勾出来的名字天然都属于这个预设,
|
|
3869
|
+
* 不会出现"在标准模式勾了只属于 PTC 的工具 → 换模式跑就启动失败"。
|
|
3870
|
+
* MCP 工具不在这里(用户裁定):子代理照旧能用当前在跑的 MCP,那份名单在运行时并入。
|
|
3871
|
+
*/
|
|
3872
|
+
function modeEditor(p) {
|
|
3873
|
+
var id = p.id
|
|
3874
|
+
var rule = modeRule(id)
|
|
3875
|
+
if (!rule) return null
|
|
3876
|
+
var all = modeTools(id)
|
|
3877
|
+
if (all === null || all.length === 0) {
|
|
3878
|
+
return React.createElement('div', { className: 'dsm-mode-body' },
|
|
3879
|
+
React.createElement('div', { className: 'dsm-pick-empty' }, p.broken ? t('subagents.mode.broken') : t('subagents.mode.noTools')))
|
|
3880
|
+
}
|
|
3881
|
+
var q = String(modal.modeQuery || '').trim().toLowerCase()
|
|
3882
|
+
var list = q ? all.filter(function (n) { return n.toLowerCase().indexOf(q) >= 0 }) : all
|
|
3883
|
+
var picked = rule.names || []
|
|
3884
|
+
return React.createElement('div', { className: 'dsm-mode-body' },
|
|
3373
3885
|
picked.length
|
|
3374
3886
|
? React.createElement('div', { className: 'dsm-tools-chips' }, picked.map(function (name) {
|
|
3375
|
-
return React.createElement('span', { key: 'c:' + name, className: 'dsm-chip' + (
|
|
3887
|
+
return React.createElement('span', { key: 'c:' + name, className: 'dsm-chip' + (rule.mode === 'deny' ? ' dsm-chip-deny' : '') },
|
|
3376
3888
|
name,
|
|
3377
|
-
React.createElement('button', { type: 'button', title: t('subagents.tools.remove'), onClick: function () {
|
|
3889
|
+
React.createElement('button', { type: 'button', title: t('subagents.tools.remove'), onClick: function () { toggleModeTool(id, name) } }, '×'))
|
|
3378
3890
|
}))
|
|
3379
|
-
: React.createElement('span', { className: 'dsm-adv-note' },
|
|
3891
|
+
: React.createElement('span', { className: 'dsm-adv-note' }, t(rule.mode === 'deny' ? 'subagents.mode.denyEmpty' : 'subagents.mode.allowEmpty')),
|
|
3380
3892
|
React.createElement('div', { className: 'dsm-combo-row' },
|
|
3381
3893
|
React.createElement('input', {
|
|
3382
3894
|
className: 'dsm-control',
|
|
3383
|
-
value: modal.
|
|
3384
|
-
placeholder:
|
|
3385
|
-
onChange: function (e) { setModal(Object.assign({}, modal, {
|
|
3895
|
+
value: modal.modeQuery || '',
|
|
3896
|
+
placeholder: t('subagents.tools.filter'),
|
|
3897
|
+
onChange: function (e) { setModal(Object.assign({}, modal, { modeQuery: e.target.value })) },
|
|
3386
3898
|
}),
|
|
3387
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy
|
|
3388
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || !picked.length, onClick:
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3899
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { setModeRule(id, { mode: rule.mode, names: all.slice() }) } }, t('scenes.seg.selectAll')),
|
|
3900
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || !picked.length, onClick: function () { setModeRule(id, { mode: rule.mode, names: [] }) } }, t('scenes.seg.clear'))),
|
|
3901
|
+
list.length
|
|
3902
|
+
? React.createElement('div', { className: 'dsm-tools-grid' }, list.map(function (name) {
|
|
3903
|
+
return pickRow({ key: 'm:' + id + ':' + name, disabled: busy, checked: picked.indexOf(name) >= 0, name: name, onChange: function () { toggleModeTool(id, name) } })
|
|
3904
|
+
}))
|
|
3905
|
+
: React.createElement('div', { className: 'dsm-pick-empty' }, t('scenes.mem.noMatch')))
|
|
3906
|
+
}
|
|
3907
|
+
/**
|
|
3908
|
+
* 一行一个 Agent 预设(roster 顺序:标准 / PTC / 极简 / 创造 / 自建预设)。
|
|
3909
|
+
* 默认全部折叠、全部未启动;右侧两个按钮「启动 白名单」「启动 黑名单」互斥。
|
|
3910
|
+
*/
|
|
3911
|
+
function modeRows() {
|
|
3912
|
+
var presets = (cand.presets || []).slice()
|
|
3913
|
+
if (!presets.length) {
|
|
3914
|
+
return React.createElement('div', { className: 'dsm-field' },
|
|
3915
|
+
React.createElement('span', { className: 'dsm-label' }, t('subagents.field.modes')),
|
|
3916
|
+
React.createElement('div', { className: 'dsm-pick-empty' }, cand.loading ? t('memory.loading') : t('subagents.mode.noPresets')))
|
|
3917
|
+
}
|
|
3918
|
+
return React.createElement('div', { className: 'dsm-field' },
|
|
3919
|
+
React.createElement('span', { className: 'dsm-label' }, t('subagents.field.modes')),
|
|
3920
|
+
React.createElement('div', { className: 'dsm-modes' }, presets.map(function (p) {
|
|
3921
|
+
var id = p.id
|
|
3922
|
+
var rule = modeRule(id)
|
|
3923
|
+
var summary = modeSummary(id)
|
|
3924
|
+
var startButton = function (mode, isDeny) {
|
|
3925
|
+
var active = !!rule && rule.mode === mode
|
|
3926
|
+
return React.createElement('button', {
|
|
3927
|
+
key: mode,
|
|
3928
|
+
type: 'button',
|
|
3929
|
+
className: 'dsm-btn dsm-btn-quiet' + (active ? (mode === 'deny' ? ' dsm-mode-btn-deny' : ' dsm-mode-btn-allow') : ''),
|
|
3930
|
+
disabled: busy,
|
|
3931
|
+
onClick: function () { if (active) stopMode(id); else startMode(id, mode) },
|
|
3932
|
+
}, t(active
|
|
3933
|
+
? (isDeny ? 'subagents.mode.stopDeny' : 'subagents.mode.stopAllow')
|
|
3934
|
+
: (isDeny ? 'subagents.mode.startDeny' : 'subagents.mode.startAllow')))
|
|
3935
|
+
}
|
|
3936
|
+
return React.createElement('div', { className: 'dsm-mode-row' + (rule ? ' dsm-mode-on' : ''), key: id },
|
|
3937
|
+
React.createElement('div', { className: 'dsm-mode-head' },
|
|
3938
|
+
React.createElement('span', { className: 'dsm-mode-name' }, presetLabel(p)),
|
|
3939
|
+
p.broken ? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, t('compat.reach.broken')) : null,
|
|
3940
|
+
React.createElement('span', { className: 'dsm-mode-sum ' + summary.cls }, summary.text),
|
|
3941
|
+
React.createElement('span', { className: 'dsm-mode-actions' },
|
|
3942
|
+
startButton('allow', false),
|
|
3943
|
+
startButton('deny', true))),
|
|
3944
|
+
modal.openMode === id ? modeEditor(p) : null)
|
|
3945
|
+
})),
|
|
3946
|
+
React.createElement('p', { className: 'dsm-help' }, t('subagents.field.modes.hint')))
|
|
3947
|
+
}
|
|
3948
|
+
/**
|
|
3949
|
+
* 旧格式(全局 `tools:` / `toolsDeny:`)的只读小结 + 转换入口。
|
|
3950
|
+
* 旧键对所有模式生效,运行时仍然照旧执行;**不点转换就不动文件里的旧键**。
|
|
3951
|
+
*/
|
|
3952
|
+
function legacyNotice() {
|
|
3953
|
+
var allow = modal.form.tools || []
|
|
3954
|
+
var deny = modal.form.toolsDeny || []
|
|
3955
|
+
if (!allow.length && !deny.length) return null
|
|
3956
|
+
var presets = cand.presets || []
|
|
3957
|
+
var target = modal.legacyTarget || (presets[0] ? presets[0].id : '')
|
|
3958
|
+
return React.createElement('div', { className: 'dsm-legacy' },
|
|
3959
|
+
React.createElement('span', { className: 'dsm-adv-note' }, t('subagents.legacy.note', { allow: allow.length, deny: deny.length })),
|
|
3960
|
+
React.createElement('div', { className: 'dsm-combo-row' },
|
|
3961
|
+
React.createElement('div', { className: 'dsm-select' },
|
|
3962
|
+
React.createElement('select', {
|
|
3963
|
+
className: 'dsm-control',
|
|
3964
|
+
value: target,
|
|
3965
|
+
disabled: busy || !presets.length,
|
|
3966
|
+
onChange: function (e) { setModal(Object.assign({}, modal, { legacyTarget: e.target.value })) },
|
|
3967
|
+
}, presets.map(function (p) { return React.createElement('option', { key: p.id, value: p.id }, presetLabel(p)) }))),
|
|
3968
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || !target || !presets.length, onClick: function () {
|
|
3969
|
+
var rule = { mode: allow.length ? 'allow' : 'deny', names: (allow.length ? allow : deny).slice() }
|
|
3970
|
+
setModal(Object.assign({}, modal, {
|
|
3971
|
+
openMode: target,
|
|
3972
|
+
modeQuery: '',
|
|
3973
|
+
legacyTarget: target,
|
|
3974
|
+
form: Object.assign({}, modal.form, {
|
|
3975
|
+
tools: [], toolsDeny: [],
|
|
3976
|
+
toolsByPreset: Object.assign({}, modal.form.toolsByPreset || {}, { [target]: rule }),
|
|
3977
|
+
}),
|
|
3978
|
+
}))
|
|
3979
|
+
} }, t('subagents.legacy.convert'))),
|
|
3980
|
+
React.createElement('p', { className: 'dsm-help' }, t('subagents.legacy.hint')))
|
|
3398
3981
|
}
|
|
3399
3982
|
function toggleAdvanced() {
|
|
3400
3983
|
if (!modal || modal.type !== 'editor') return
|
|
@@ -3409,7 +3992,9 @@ function callApi(path, options) {
|
|
|
3409
3992
|
apiCall(op, {
|
|
3410
3993
|
name: modal.form.name, description: modal.form.description,
|
|
3411
3994
|
provider: modal.form.provider, model: modal.form.model,
|
|
3995
|
+
// 旧格式的全局名单原样回写(旧键不点转换就不动),新模式名单另存一块。
|
|
3412
3996
|
tools: modal.form.tools || [], toolsDeny: modal.form.toolsDeny || [],
|
|
3997
|
+
toolsByPreset: modal.form.toolsByPreset || {},
|
|
3413
3998
|
body: modal.form.body,
|
|
3414
3999
|
}).then(function (res) {
|
|
3415
4000
|
setBusy(false)
|
|
@@ -3450,7 +4035,8 @@ function callApi(path, options) {
|
|
|
3450
4035
|
React.createElement('div', { className: 'dsm-actions' },
|
|
3451
4036
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || data.loading, onClick: function () { refresh() } }, t('memory.btn.refresh')),
|
|
3452
4037
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: function () { setResult(null); setModal({ type: 'import' }) } }, t('subagents.import')),
|
|
3453
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: function () { openEditor(null) } }, t('subagents.new'))
|
|
4038
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: function () { openEditor(null) } }, t('subagents.new')),
|
|
4039
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', onClick: function () { loadTrash(false) } }, t('trash.btn.open')))),
|
|
3454
4040
|
React.createElement(Notice, { kind: result && result.ok ? 'ok' : 'err', text: result && result.text }),
|
|
3455
4041
|
data.error ? React.createElement('div', { className: 'dsm-feedback dsm-error' }, String(data.error)) : null,
|
|
3456
4042
|
data.loading ? React.createElement('div', { className: 'dsm-empty' }, t('memory.loading'))
|
|
@@ -3485,6 +4071,7 @@ function callApi(path, options) {
|
|
|
3485
4071
|
React.createElement('span', { className: 'dsm-adv-note' },
|
|
3486
4072
|
modal.advanced ? '' : t('subagents.adv.summary', {
|
|
3487
4073
|
model: modal.form.model ? (modal.form.provider ? modal.form.provider + '/' + modal.form.model : modal.form.model) : t('subagents.adv.inherit'),
|
|
4074
|
+
modes: Object.keys(modal.form.toolsByPreset || {}).length,
|
|
3488
4075
|
allow: (modal.form.tools || []).length,
|
|
3489
4076
|
deny: (modal.form.toolsDeny || []).length,
|
|
3490
4077
|
}))),
|
|
@@ -3521,41 +4108,10 @@ function callApi(path, options) {
|
|
|
3521
4108
|
React.createElement('span', { className: 'dsm-label' }, t('subagents.field.provider')),
|
|
3522
4109
|
React.createElement('input', { className: 'dsm-control', value: modal.form.provider || '', placeholder: t('subagents.field.provider.placeholder'), onChange: function (e) { setForm({ provider: e.target.value }) } }),
|
|
3523
4110
|
React.createElement('p', { className: 'dsm-help' }, t('subagents.field.provider.hint'))),
|
|
3524
|
-
//
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
hint: t('subagents.field.tools.hint'),
|
|
3529
|
-
selected: modal.form.tools || [],
|
|
3530
|
-
onToggle: function (name) { setForm({ tools: toggled(modal.form.tools || [], name) }) },
|
|
3531
|
-
onClear: function () { setForm({ tools: [] }) },
|
|
3532
|
-
onAll: function () { setForm({ tools: (cand.tools || []).map(function (x) { return x.name }) }) },
|
|
3533
|
-
chosen: t('subagents.tools.chosen'),
|
|
3534
|
-
filterPlaceholder: t('subagents.tools.filter'),
|
|
3535
|
-
empty: t('subagents.tools.empty'),
|
|
3536
|
-
unavailable: t('subagents.tools.unavailable'),
|
|
3537
|
-
current: t('subagents.tools.current'),
|
|
3538
|
-
groupCurrent: t('subagents.tools.groupCurrent'),
|
|
3539
|
-
groupOther: t('subagents.tools.groupOther'),
|
|
3540
|
-
}),
|
|
3541
|
-
// 工具黑名单:勾选 = 从子代理可见集合里移除(官方 ToolRestriction.deny,优先级高于白名单)。
|
|
3542
|
-
toolPicker({
|
|
3543
|
-
key: 'deny',
|
|
3544
|
-
label: t('subagents.field.toolsDeny'),
|
|
3545
|
-
hint: t('subagents.field.toolsDeny.hint'),
|
|
3546
|
-
deny: true,
|
|
3547
|
-
selected: modal.form.toolsDeny || [],
|
|
3548
|
-
onToggle: function (name) { setForm({ toolsDeny: toggled(modal.form.toolsDeny || [], name) }) },
|
|
3549
|
-
onClear: function () { setForm({ toolsDeny: [] }) },
|
|
3550
|
-
onAll: function () { setForm({ toolsDeny: (cand.tools || []).map(function (x) { return x.name }) }) },
|
|
3551
|
-
chosen: t('subagents.tools.chosen'),
|
|
3552
|
-
filterPlaceholder: t('subagents.tools.filter'),
|
|
3553
|
-
empty: t('subagents.tools.empty'),
|
|
3554
|
-
unavailable: t('subagents.tools.unavailable'),
|
|
3555
|
-
current: t('subagents.tools.current'),
|
|
3556
|
-
groupCurrent: t('subagents.tools.groupCurrent'),
|
|
3557
|
-
groupOther: t('subagents.tools.groupOther'),
|
|
3558
|
-
}),
|
|
4111
|
+
// 工具限制:按 Agent 预设一行一个模式(默认全折叠、全部未启动,白/黑互斥)。
|
|
4112
|
+
// 旧格式的全局名单(老文件 / 别处导入)在这里只读呈现,点「转换」才搬进某个模式。
|
|
4113
|
+
legacyNotice(),
|
|
4114
|
+
modeRows(),
|
|
3559
4115
|
cand.error ? React.createElement('div', { className: 'dsm-feedback dsm-warning' }, String(cand.error)) : null,
|
|
3560
4116
|
React.createElement('p', { className: 'dsm-adv-note' }, t('subagents.adv.note')))
|
|
3561
4117
|
: null),
|
|
@@ -3566,7 +4122,19 @@ function callApi(path, options) {
|
|
|
3566
4122
|
React.createElement('p', { className: 'dsm-help' }, t('subagents.delete.desc', { name: modal.name })),
|
|
3567
4123
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
3568
4124
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { submitDelete(modal.name) } }, t('memory.btn.delete.confirm')))) : null,
|
|
3569
|
-
modal && modal.type === 'import' ? React.createElement(ImportModal, { key: 'simp', t: t, title: t('subagents.import.title'), hint: t('subagents.import.hint'), busy: busy, onClose: function () { setModal(null) }, onSubmit: submitImport }) : null
|
|
4125
|
+
modal && modal.type === 'import' ? React.createElement(ImportModal, { key: 'simp', t: t, title: t('subagents.import.title'), hint: t('subagents.import.hint'), busy: busy, onClose: function () { setModal(null) }, onSubmit: submitImport }) : null,
|
|
4126
|
+
trash ? React.createElement(TrashModal, {
|
|
4127
|
+
t: t,
|
|
4128
|
+
title: t('trash.title'),
|
|
4129
|
+
groupTitle: t('trash.group.agents'),
|
|
4130
|
+
entries: trash.entries,
|
|
4131
|
+
loading: trash.loading,
|
|
4132
|
+
error: trash.error,
|
|
4133
|
+
busy: trashBusy,
|
|
4134
|
+
onClose: function () { setTrash(null) },
|
|
4135
|
+
onRestore: restorePersona,
|
|
4136
|
+
onPurge: purgePersona,
|
|
4137
|
+
}) : null)
|
|
3570
4138
|
}
|
|
3571
4139
|
|
|
3572
4140
|
function MemoryPage() {
|
|
@@ -4093,7 +4661,7 @@ function callApi(path, options) {
|
|
|
4093
4661
|
trash.entries.length
|
|
4094
4662
|
? React.createElement(React.Fragment, null,
|
|
4095
4663
|
React.createElement('div', { className: 'dsm-count' }, t('memory.trash.count', { count: trash.entries.length })),
|
|
4096
|
-
trash.entries.map(function (entry) {
|
|
4664
|
+
React.createElement('div', { className: 'dsm-trash-box' }, trash.entries.map(function (entry) {
|
|
4097
4665
|
return React.createElement('div', { key: entry.trashId, className: 'dsm-trash-item' },
|
|
4098
4666
|
React.createElement('div', { className: 'dsm-trash-main' },
|
|
4099
4667
|
React.createElement('div', { className: 'dsm-name' }, entry.name),
|
|
@@ -4102,7 +4670,7 @@ function callApi(path, options) {
|
|
|
4102
4670
|
+ t('memory.trash.deletedAt', { time: entry.deletedAt ? new Date(entry.deletedAt).toLocaleString() : '' }))),
|
|
4103
4671
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { submitRestore(entry) } }, t('memory.trash.restore')),
|
|
4104
4672
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: busy, onClick: function () { setModal({ type: 'trash-delete', entry: entry }) } }, t('memory.trash.purge')))
|
|
4105
|
-
}))
|
|
4673
|
+
})))
|
|
4106
4674
|
: React.createElement('div', { className: 'dsm-empty' }, t('memory.trash.empty')))) : null,
|
|
4107
4675
|
modal && modal.type === 'trash-delete' ? React.createElement(Modal, { key: 'trash-delete', title: t('memory.trash.confirmTitle'), closeLabel: t('btn.cancel'), onClose: function () { setModal({ type: 'trash' }) } },
|
|
4108
4676
|
React.createElement('p', { className: 'dsm-help' }, t('memory.trash.confirmDesc', { name: modal.entry.name })),
|