dsh-plugin-tool-management 0.6.0 → 0.8.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 +179 -0
- package/README.md +117 -221
- package/README_EN.md +116 -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 +56 -38
- package/lib/agents-md/service.js +84 -5
- package/lib/client.js +1488 -475
- package/lib/compat/preset-reach.js +425 -0
- package/lib/compat/probe.js +8 -0
- package/lib/http-fence.js +21 -0
- package/lib/hub.js +160 -2
- package/lib/index.js +687 -47
- package/lib/mcp/override-blocks.js +195 -0
- package/lib/mcp/state-section.js +90 -0
- package/lib/prompt-sections.js +148 -0
- package/lib/rules/archive-engine.js +99 -29
- package/lib/rules/archive.js +56 -28
- package/lib/rules/service.js +332 -35
- package/lib/skills/core.js +15 -1
- package/lib/skills/service.js +3 -0
- package/lib/subagents/catalog.js +89 -0
- package/lib/subagents/service.js +384 -41
- package/lib/subagents/tools.js +18 -5
- package/package.json +97 -96
- package/screenshots.json +10 -10
- package/docs/Changelog.md +0 -144
- package/lib/rules/provider.js +0 -121
package/lib/client.js
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
// Registers the "MCP 管理" settings page. Talks to the host half through the
|
|
3
3
|
// exact-path HTTP route /dsh-plugin-tool-management/api (same origin) instead of the
|
|
4
4
|
// dynamic-only host.call channel.
|
|
5
|
+
//
|
|
6
|
+
// 取词三件套(design-plan D25)——改文案前先确认当前处落在哪一支:
|
|
7
|
+
// * `t` —— apply 开头「取词服务」段绑定的那份取词(导航标题等**注册期**取值也走它);
|
|
8
|
+
// 页面组件则经 props 拿到同一份(`pages.t`)。
|
|
9
|
+
// * `mt` —— MCP 页专用包装(定义在 apply 内):模块作用域的 `t` 会被宿主 locale 覆盖,
|
|
10
|
+
// 它每次取值都取最新的那个,所以 MCP 页一律用 `mt`。
|
|
11
|
+
// * `translateOrFallback(t, key, fallback)` —— 动态键(宿主回传的错误码等)缺失时的兜底文案。
|
|
12
|
+
// 三者共用同一份 DICT:键必须在 zh / en 两侧都存在(`npm run check:i18n` 会拦)。
|
|
5
13
|
window.__ModuleLoader__.load({
|
|
6
14
|
id: 'dsh-plugin-tool-management',
|
|
7
15
|
factory: (require) => {
|
|
@@ -11,8 +19,8 @@ window.__ModuleLoader__.load({
|
|
|
11
19
|
const React = require('react')
|
|
12
20
|
|
|
13
21
|
const CSS =
|
|
14
|
-
'.dsm-
|
|
15
|
-
'.dsm-version{color:var(--dsw-alias-label-tertiary);font-size:
|
|
22
|
+
'.dsm-stat-row{display:flex;min-width:0;align-items:center;gap:10px}.dsm-stat-row .dsm-summary{flex:1;min-width:0}' +
|
|
23
|
+
'.dsm-version{color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:500;letter-spacing:.3px;line-height:1}' +
|
|
16
24
|
'.dsm-failed{color:var(--dsw-alias-state-error-primary);font-size:12px;white-space:nowrap}' +
|
|
17
25
|
'.dsm-row-hint{grid-column:1/-1;margin:-4px 0 9px;color:#d49245;font-size:11px;line-height:16px}' +
|
|
18
26
|
'.dsm-tools{display:flex;flex-direction:column;gap:8px}' +
|
|
@@ -32,14 +40,40 @@ window.__ModuleLoader__.load({
|
|
|
32
40
|
'.dsm-detail-title-row{display:flex;align-items:center;justify-content:space-between;gap:8px}' +
|
|
33
41
|
'.dsm-note-user{color:var(--dsw-alias-label-tertiary)}' +
|
|
34
42
|
`
|
|
35
|
-
.dsm-
|
|
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
|
|
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}
|
|
43
|
+
.dsm-hint{display:flex;min-width:0;align-items:center;gap:5px;margin:0;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px}.dsm-hint-text{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dsm-hint-mark{display:inline-flex;width:14px;height:14px;flex:none;align-items:center;justify-content:center;border:1px solid var(--dsw-alias-border-l2);border-radius:50%;font-size:9px;line-height:1;cursor:help}.dsm-pick-list{display:flex;max-height:240px;flex-direction:column;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;padding:4px}.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;flex-direction:column;align-items:flex-end;gap:2px}.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;display:-webkit-box;overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px;-webkit-box-orient:vertical;-webkit-line-clamp:2}.dsm-actions{display:flex;flex-wrap:wrap;gap:8px;margin-left:0;flex:none}
|
|
44
|
+
.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{border-color:var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary)}.dsm-btn-quiet{border-color:var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-secondary);min-height:28px;padding:0 9px;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-lock{border-color:#d49245;background:transparent;color:#d49245}.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}
|
|
45
|
+
.dsm-summary{display:grid;grid-template-columns:repeat(var(--dsm-stat-cols,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,.dsm-stat-num{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}
|
|
46
|
+
.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
47
|
.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
|
-
.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}
|
|
48
|
+
.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;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dsm-count{color:var(--dsw-alias-label-tertiary);font-size:12px;flex:none}.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
49
|
.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
50
|
.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
|
-
.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-
|
|
51
|
+
.dsm-mask{position:fixed;z-index:1100;inset:0;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(0,0,0,.62)}.dsm-modal{box-sizing:border-box;display:flex;width:min(560px,100%)!important;max-height:min(760px,calc(100vh - 48px));min-width:0;flex-direction:column;gap:16px;padding:22px;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:var(--dsw-alias-bg-layer-2);box-shadow:var(--dsw-shadow-lv3)}.dsm-modal-sm,.dsm-modal-import{width:min(480px,100%)!important}.dsm-modal-md{width:min(560px,100%)!important}.dsm-modal-lg,.dsm-modal-wide{width:min(720px,100%)!important}.dsm-modal-list{width:min(560px,100%)!important;height:min(640px,calc(100vh - 48px))!important;max-height:none!important;overflow:hidden}.dsm-modal-body{display:flex;min-height:0;flex-direction:column;gap:10px}.dsm-modal-list .dsm-modal-body{flex:1;overflow:auto;overscroll-behavior:contain}.dsm-modal-import{padding:24px}.dsm-modal-head{display:flex;align-items:flex-start;gap:12px}.dsm-modal-title{margin:0;flex:1;font-size:17px;line-height:24px;font-weight:670}.dsm-form,.dsm-field,.dsm-detail-section{display:flex;flex-direction:column}.dsm-form{gap:12px}.dsm-field{gap:6px}.dsm-label,.dsm-detail-title{font-size:12px}.dsm-label{color:var(--dsw-alias-label-secondary)}.dsm-detail-title{font-weight:650}.dsm-help{margin:0;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px}.dsm-modal-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px}.dsm-hidden-input{display:none}.dsm-dropzone{box-sizing:border-box;display:flex;width:100%;min-height:170px;flex-direction:column;align-items:center;justify-content:center;gap:7px;padding:22px;border:1px dashed var(--dsw-alias-border-l3);border-radius:12px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);transition:border-color 180ms ease,background 180ms ease;cursor:pointer}.dsm-dropzone:hover,.dsm-dropzone-active{border-color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsm-dropzone-title{color:var(--dsw-alias-label-primary);font-size:18px;font-weight:700}.dsm-dropzone-copy{font-size:12px;line-height:18px;text-align:center}.dsm-upload-choices{display:flex;align-items:center;gap:7px}.dsm-upload-link,.dsm-file-remove{padding:0;border:0;background:transparent;font:inherit;font-size:12px;cursor:pointer}.dsm-upload-link{color:var(--dsw-alias-label-secondary)}.dsm-upload-link:hover{color:var(--dsw-alias-label-primary);text-decoration:underline}.dsm-upload-link:disabled{opacity:.45;cursor:default}.dsm-upload-divider{color:var(--dsw-alias-label-tertiary);font-size:11px}.dsm-file{display:flex;align-items:center;gap:9px;padding:10px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:12px}.dsm-file-kind{display:inline-flex;min-width:30px;height:24px;align-items:center;justify-content:center;border-radius:5px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font-size:9px;font-weight:700}.dsm-file-name{min-width:0;overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}.dsm-file-meta{color:var(--dsw-alias-label-tertiary);font-size:11px;white-space:nowrap}.dsm-file-remove{width:24px;height:24px;color:var(--dsw-alias-label-secondary);font-size:18px}.dsm-upload-requirements{padding:1px 1px 0}.dsm-upload-requirements ul{display:flex;margin:7px 0 0;padding-left:18px;flex-direction:column;gap:5px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}.dsm-detail-section{gap:7px}.dsm-detail-path,.dsm-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px}.dsm-detail-path{padding:8px 10px;border-radius:7px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);word-break:break-all}.dsm-code{max-height:280px;margin:0;padding:12px;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);line-height:18px;white-space:pre-wrap}.dsm-diag{padding:8px 10px;border-left:2px solid #d49245;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:12px}.dsm-trash-group-sub{margin:0;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
|
|
52
|
+
.dsm-trash-group{display:flex;flex-direction:column;overflow:hidden;margin-bottom:12px;border:1px solid var(--dsw-alias-border-l2);border-left-width:3px;border-radius:10px;background:var(--dsw-alias-bg-layer-2)}
|
|
53
|
+
/* 「这组动的是磁盘文件」用左侧色条区分:中性 = 文件不动;危险色 = 真的会删文件。 */
|
|
54
|
+
.dsm-trash-group-danger{border-left-color:var(--dsw-alias-state-error-primary)}
|
|
55
|
+
.dsm-trash-group-danger .dsm-trash-group-title{color:var(--dsw-alias-state-error-primary)}.dsm-trash-group:last-child{margin-bottom:0}.dsm-trash-group-head{position:sticky;top:0;z-index:1;display:flex;flex-direction:column;align-items:stretch;gap:2px;min-height:40px;justify-content:center;padding:8px 13px;border-bottom:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2)}
|
|
56
|
+
.dsm-trash-group-head-row{display:flex;min-width:0;align-items:center;gap:8px}.dsm-trash-group-title{font-size:13px;font-weight:650;color:var(--dsw-alias-label-primary)}.dsm-trash-group-body{padding:0 13px}
|
|
57
|
+
.dsm-trash-item{display:flex;align-items:center;gap:9px;padding:6px 10px;border-bottom:1px solid var(--dsw-alias-border-l1)}
|
|
58
|
+
.dsm-trash-item:last-child{border-bottom:0}
|
|
59
|
+
.dsm-trash-main{min-width:0;flex:1}
|
|
60
|
+
/* 回收站列表体:固定高度 + 上下滑动(记忆 / 技能 / 子智能体 / 场景 / 提示词 五处共用) */
|
|
61
|
+
.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)}
|
|
62
|
+
|
|
63
|
+
/* 滚动条:宿主默认是「悬停才出现」的浮层滚动条 —— 固定高度的列表弹窗里,用户看不出
|
|
64
|
+
还能往下滚,等于下面的条目不存在。插件给自己的滚动容器画一条**常驻**细滚动条
|
|
65
|
+
(颜色走宿主变量,深浅主题都成立;不碰宿主其它地方的滚动条)。 */
|
|
66
|
+
.dsm-modal-list .dsm-modal-body,.dsm-trash-box,.dsm-pick-list,.dsm-seg-body,.dsm-dir-list,.dsm-trash-pane-body{scrollbar-width:thin;scrollbar-color:var(--dsw-alias-border-l2) transparent}
|
|
67
|
+
.dsm-modal-list .dsm-modal-body::-webkit-scrollbar,.dsm-trash-box::-webkit-scrollbar,.dsm-pick-list::-webkit-scrollbar,.dsm-seg-body::-webkit-scrollbar,.dsm-dir-list::-webkit-scrollbar,.dsm-trash-pane-body::-webkit-scrollbar{display:block;width:9px;height:9px}
|
|
68
|
+
.dsm-modal-list .dsm-modal-body::-webkit-scrollbar-track,.dsm-trash-box::-webkit-scrollbar-track,.dsm-pick-list::-webkit-scrollbar-track,.dsm-seg-body::-webkit-scrollbar-track,.dsm-dir-list::-webkit-scrollbar-track,.dsm-trash-pane-body::-webkit-scrollbar-track{background:var(--dsw-alias-bg-layer-1)}
|
|
69
|
+
.dsm-modal-list .dsm-modal-body::-webkit-scrollbar-thumb,.dsm-trash-box::-webkit-scrollbar-thumb,.dsm-pick-list::-webkit-scrollbar-thumb,.dsm-seg-body::-webkit-scrollbar-thumb,.dsm-dir-list::-webkit-scrollbar-thumb,.dsm-trash-pane-body::-webkit-scrollbar-thumb{border-radius:99px;background:var(--dsw-alias-border-l2)}
|
|
70
|
+
.dsm-modal-list .dsm-modal-body::-webkit-scrollbar-thumb:hover,.dsm-trash-box::-webkit-scrollbar-thumb:hover,.dsm-pick-list::-webkit-scrollbar-thumb:hover,.dsm-seg-body::-webkit-scrollbar-thumb:hover,.dsm-dir-list::-webkit-scrollbar-thumb:hover,.dsm-trash-pane-body::-webkit-scrollbar-thumb:hover{background:var(--dsw-alias-label-tertiary)}
|
|
71
|
+
.dsm-modal-list .dsm-modal-body::-webkit-scrollbar-corner,.dsm-trash-box::-webkit-scrollbar-corner,.dsm-pick-list::-webkit-scrollbar-corner,.dsm-seg-body::-webkit-scrollbar-corner,.dsm-dir-list::-webkit-scrollbar-corner,.dsm-trash-pane-body::-webkit-scrollbar-corner{background:transparent}
|
|
72
|
+
|
|
73
|
+
/* 列表弹窗的 body 是纵向 flex:直接子项一旦被 flex 收缩,而子项自己又是 overflow:hidden
|
|
74
|
+
(.dsm-trash-group 正是),超出的条目会被**直接裁掉** —— 看不见、也滚不到,表现就是
|
|
75
|
+
「没有滚动条 + 内容缺一截」。列表项的尺寸一律由内容决定,滚动只交给 body 这一层。 */
|
|
76
|
+
.dsm-modal-list .dsm-modal-body>*{flex:none}
|
|
43
77
|
.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
78
|
@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
79
|
.dsm-hist-row{display:flex;align-items:center;gap:11px;padding:10px 13px;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-hist-row:last-child{border-bottom:0}.dsm-hist-main{min-width:0;flex:1}.dsm-hist-title{overflow:hidden;font-size:13px;font-weight:580;text-overflow:ellipsis;white-space:nowrap}.dsm-hist-cwd{overflow:hidden;margin-top:2px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:17px;text-overflow:ellipsis;white-space:nowrap}.dsm-hist-cwd-missing{color:var(--dsw-alias-state-error-primary)}.dsm-hist-actions{display:flex;gap:6px;flex:none}.dsm-hist-check,.dsm-hist-group-check{flex:none;width:15px;height:15px;margin:0 4px 0 0;cursor:pointer;accent-color:var(--dsw-alias-state-success-primary)}.dsm-hist-group-head{gap:10px}.dsm-hist-group-extra{display:flex;align-items:center;gap:8px;flex:none;margin-left:4px}.dsm-hist-group-extra .dsm-tag{flex:none}.dsm-hist-register-path{margin-top:-4px;color:var(--dsw-alias-label-tertiary);font-family:var(--dsw-font-mono,monospace);font-size:11px;word-break:break-all}.dsm-hist-batch{display:flex;align-items:center;gap:8px;margin-left:2px;padding-left:10px;border-left:1px solid var(--dsw-alias-border-l2)}.dsm-hist-batch-count{color:var(--dsw-alias-label-secondary);font-size:12px;white-space:nowrap}.dsm-hist-batch-actions{display:flex;align-items:center;flex-wrap:wrap;gap:8px;flex:none}.dsm-dir-row{display:flex;gap:8px}.dsm-dir-row .dsm-control{flex:1}.dsm-dir-list{display:flex;max-height:220px;flex-direction:column;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}.dsm-dir-item{display:flex;width:100%;align-items:center;padding:8px 12px;border:0;border-bottom:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;text-align:left;cursor:pointer}.dsm-dir-item:last-child{border-bottom:0}.dsm-dir-item:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsm-dir-item::before{content:"📁";margin-right:8px;font-size:12px}
|
|
@@ -124,6 +158,20 @@ window.__ModuleLoader__.load({
|
|
|
124
158
|
.dsm-adv-caret{color:var(--dsw-alias-label-tertiary);font-size:10px}
|
|
125
159
|
.dsm-adv-body{display:flex;flex-direction:column;gap:12px;padding:11px;border-top:1px solid var(--dsw-alias-border-l1)}
|
|
126
160
|
.dsm-adv-note{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}
|
|
161
|
+
/* ── 工具限制四行(人设表单:一行一个 Agent 预设,白/黑名单互斥、默认折叠)──────── */
|
|
162
|
+
.dsm-modes{display:flex;flex-direction:column;gap:6px;margin-top:6px}
|
|
163
|
+
.dsm-mode-row{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}
|
|
164
|
+
.dsm-mode-row.dsm-mode-on{border-color:var(--dsw-alias-border-l3)}
|
|
165
|
+
.dsm-mode-head{display:flex;min-height:38px;align-items:center;gap:9px;padding:0 10px}
|
|
166
|
+
.dsm-mode-name{font-size:12px;font-weight:570;white-space:nowrap}
|
|
167
|
+
.dsm-mode-sum{color:var(--dsw-alias-label-tertiary);font-size:11px}
|
|
168
|
+
.dsm-mode-sum.dsm-mode-allow{color:var(--dsw-alias-state-success-primary)}
|
|
169
|
+
.dsm-mode-sum.dsm-mode-deny{color:#d49245}
|
|
170
|
+
.dsm-mode-actions{display:flex;align-items:center;gap:6px;margin-left:auto}
|
|
171
|
+
.dsm-mode-btn-allow{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}
|
|
172
|
+
.dsm-mode-btn-deny{border-color:#d49245;color:#d49245}
|
|
173
|
+
.dsm-mode-body{display:flex;flex-direction:column;gap:8px;padding:10px;border-top:1px solid var(--dsw-alias-border-l1)}
|
|
174
|
+
.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
175
|
/* ── 兼容页(宿主能力体检)──────────────────────────────────────────────
|
|
128
176
|
一块"体检报告":先给结论卡,再给动作可用性、降级项、模块实体、阻塞项。
|
|
129
177
|
状态色只用设计系统里已有的三档:成功 / 告警(#d49245) / 错误。 */
|
|
@@ -157,16 +205,44 @@ window.__ModuleLoader__.load({
|
|
|
157
205
|
.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
206
|
.dsm-compat-pill-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}
|
|
159
207
|
.dsm-compat-pill-warn{border-color:#d49245;color:#d49245}
|
|
208
|
+
.dsm-compat-reach-marks{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:6px;min-width:0}
|
|
209
|
+
.dsm-reach-name{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
|
|
210
|
+
.dsm-reach-chip{display:inline-flex;min-height:22px;align-items:center;padding:0 9px;border:1px solid var(--dsw-alias-border-l3);border-radius:6px;color:var(--dsw-alias-label-secondary);font-size:11px;white-space:nowrap}
|
|
211
|
+
.dsm-reach-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}
|
|
212
|
+
.dsm-reach-bad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
|
|
213
|
+
.dsm-reach-unknown{color:var(--dsw-alias-label-tertiary)}
|
|
160
214
|
.dsm-compat-pill-bad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
|
|
161
215
|
.dsm-compat-name{min-width:0;flex:1;font-size:12px;font-weight:560;word-break:break-word}
|
|
162
216
|
.dsm-compat-label{margin-right:6px;color:var(--dsw-alias-label-tertiary);font-size:11px;white-space:nowrap}
|
|
163
217
|
.dsm-compat-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px}
|
|
164
218
|
.dsm-compat-list{margin:0;padding-left:16px}.dsm-compat-list li+li{margin-top:4px}
|
|
165
219
|
@container(max-width:640px){.dsm-compat-grid{grid-template-columns:1fr}}
|
|
166
|
-
html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!important;scrollbar-gutter:stable}.dsm-tabs-shell{display:flex;min-width:0;align-items:center;gap:6px;margin-bottom:14px}.dsm-tabs{display:grid;min-width:0;flex:1;grid-template-columns:repeat(
|
|
220
|
+
html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!important;scrollbar-gutter:stable}.dsm-tabs-shell{position:sticky;top:0;z-index:6;display:flex;min-width:0;align-items:center;gap:6px;margin-bottom:14px;padding-top:2px;background:var(--dsw-alias-bg-layer-2)}.dsm-tabs{display:grid;min-width:0;flex:1;grid-template-columns:repeat(auto-fit,minmax(64px,1fr));gap:0;overflow:hidden;margin-bottom:0;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-tab{box-sizing:border-box;width:100%;min-width:0;padding-right:4px;padding-left:4px;overflow:hidden;font-size:12px;text-overflow:ellipsis;white-space:nowrap}.dsm-feedback-link{width:30px;height:30px;box-sizing:border-box;padding:0;justify-content:center}.dsm-feedback-link svg{width:16px;height:16px}@container(max-width:620px){.dsm-tab{padding-right:2px;padding-left:2px;font-size:11px}}
|
|
167
221
|
/* 动作可用性表:表头列宽与 dsm-compat-mod-row 一致;窄容器下隐藏表头。 */
|
|
168
222
|
.dsm-compat-table-head{display:grid;grid-template-columns:minmax(0,1fr) max-content;gap:10px;padding:6px 12px;border-bottom:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);font-size:11px}
|
|
169
223
|
.dsm-compat-cards .dsm-compat-table-head{display:none}
|
|
224
|
+
/* 导出弹窗(D14):宽高都固定;只有「条目列表」这一层滚动,动作条常驻底部。 */
|
|
225
|
+
.dsm-export-modal .dsm-modal-body{display:flex;min-height:0;flex-direction:column;gap:14px;overflow:hidden}
|
|
226
|
+
.dsm-export-modal .dsm-export-form{display:flex;min-height:0;flex:1;flex-direction:column;gap:10px}
|
|
227
|
+
.dsm-export-modal .dsm-pick-list{flex:1;min-height:0;max-height:none;overflow:auto}
|
|
228
|
+
.dsm-export-modal .dsm-export-body{display:flex;min-height:0;flex:1;flex-direction:column;gap:6px}
|
|
229
|
+
.dsm-pick-group{display:flex;flex-direction:column}
|
|
230
|
+
.dsm-pick-group-head{box-sizing:border-box;display:flex;width:100%;min-width:0;align-items:center;gap:7px;padding:7px 2px;border:0;background:transparent;color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;text-align:left;cursor:pointer}
|
|
231
|
+
.dsm-pick-group-head:hover{color:var(--dsw-alias-label-primary)}
|
|
232
|
+
.dsm-pick-caret{flex:0 0 auto;width:12px;color:var(--dsw-alias-label-tertiary)}
|
|
233
|
+
.dsm-pick-group-name{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
234
|
+
.dsm-pick-group-count{flex:0 0 auto;color:var(--dsw-alias-label-tertiary);font-weight:500}
|
|
235
|
+
.dsm-pick-group .dsm-pick{margin-left:16px}
|
|
236
|
+
.dsm-pick-group .dsm-pick+.dsm-pick{margin-top:2px}
|
|
237
|
+
|
|
238
|
+
/* 技能回收站:上下两块各占一半(目录 / 技能),滚动条在每块列表区里面,大弹窗自己不滚。
|
|
239
|
+
必须放在最后:盖过上面的 .dsm-modal-list .dsm-modal-body>*{flex:none},
|
|
240
|
+
否则外层不撑满、两块就各按内容长,50/50 失效。宽度沿用 .dsm-modal-list 的 560px。 */
|
|
241
|
+
.dsm-modal-trash-split .dsm-modal-body{overflow:hidden}
|
|
242
|
+
.dsm-modal-trash-split .dsm-trash-split{flex:1 1 auto;min-height:0;display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr) minmax(0,1fr);gap:12px}
|
|
243
|
+
.dsm-modal-trash-split .dsm-trash-group{margin-bottom:0;min-height:0}
|
|
244
|
+
.dsm-modal-trash-split .dsm-trash-pane-body{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:0 13px}
|
|
245
|
+
|
|
170
246
|
`
|
|
171
247
|
|
|
172
248
|
function ensureCss() {
|
|
@@ -194,6 +270,20 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
194
270
|
}
|
|
195
271
|
} catch (e) { /* storage unavailable */ }
|
|
196
272
|
|
|
273
|
+
/**
|
|
274
|
+
* 改令牌并落盘。明文机密 op(mcpm-reveal / mcpm-export)在宿主侧**必须**带对令牌,
|
|
275
|
+
* 没配令牌时一律拒绝 —— 所以界面得有个地方让用户把令牌填进来,否则那功能永远打不开。
|
|
276
|
+
* 令牌只存本机 localStorage,随请求以 x-dsh-token 发出。
|
|
277
|
+
*/
|
|
278
|
+
function setAccessToken(value) {
|
|
279
|
+
TOKEN = String(value || '').trim()
|
|
280
|
+
try {
|
|
281
|
+
if (TOKEN) window.localStorage.setItem('dsh-plugin-tool-management-token', TOKEN)
|
|
282
|
+
else window.localStorage.removeItem('dsh-plugin-tool-management-token')
|
|
283
|
+
} catch (e) { /* storage unavailable */ }
|
|
284
|
+
return TOKEN
|
|
285
|
+
}
|
|
286
|
+
|
|
197
287
|
function apiCall(op, args) {
|
|
198
288
|
// `x-dsh-plugin` is the cross-site (CSRF) gate header the host half
|
|
199
289
|
// requires on every request; a cross-origin page cannot attach it
|
|
@@ -247,6 +337,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
247
337
|
function CompatPage(props) {
|
|
248
338
|
const t = props.t
|
|
249
339
|
const [data, setData] = React.useState(null)
|
|
340
|
+
const [presetReach, setPresetReach] = React.useState(null)
|
|
250
341
|
const [busy, setBusy] = React.useState(false)
|
|
251
342
|
const [error, setError] = React.useState(null)
|
|
252
343
|
const apply = function (r, alive) {
|
|
@@ -254,12 +345,24 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
254
345
|
if (r && r.ok) { setData(r); setError(null) } else { setError((r && r.error) || 'unknown') }
|
|
255
346
|
setBusy(false)
|
|
256
347
|
}
|
|
348
|
+
// 预设可达性是独立的一面镜子:它取不到只意味着这一节不显示,
|
|
349
|
+
// 绝不让它的失败顶掉 compat-status 的结论。
|
|
350
|
+
const applyReach = function (r, alive) {
|
|
351
|
+
if (alive === false) return
|
|
352
|
+
if (r && r.ok) setPresetReach(r)
|
|
353
|
+
}
|
|
354
|
+
const loadReach = function (alive) {
|
|
355
|
+
apiCall('preset-reach', {})
|
|
356
|
+
.then(function (r) { applyReach(r, alive) })
|
|
357
|
+
.catch(function () { /* 未挂载预设服务 → 不渲染这一节 */ })
|
|
358
|
+
}
|
|
257
359
|
React.useEffect(function () {
|
|
258
360
|
let alive = true
|
|
259
361
|
setBusy(true)
|
|
260
362
|
apiCall('compat-status', { refresh: true })
|
|
261
363
|
.then(function (r) { apply(r, alive) })
|
|
262
364
|
.catch(function (e) { if (alive) { setError(String((e && e.message) || e)); setBusy(false) } })
|
|
365
|
+
loadReach(alive)
|
|
263
366
|
return function () { alive = false }
|
|
264
367
|
}, [])
|
|
265
368
|
const reload = function () {
|
|
@@ -267,6 +370,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
267
370
|
apiCall('compat-status', { refresh: true })
|
|
268
371
|
.then(function (r) { apply(r, true) })
|
|
269
372
|
.catch(function (e) { setError(String((e && e.message) || e)); setBusy(false) })
|
|
373
|
+
loadReach(true)
|
|
270
374
|
}
|
|
271
375
|
|
|
272
376
|
const findings = (data && data.findings) || []
|
|
@@ -334,7 +438,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
334
438
|
|
|
335
439
|
// 指标卡:宿主 / 本插件适配版本 / 可用能力(含插件自补的 optional 槽位)。
|
|
336
440
|
push(React.createElement('div', { className: 'dsm-compat-grid' },
|
|
337
|
-
card(t('compat.host'), (data.host && data.host.version) || '?', t('compat.peer') + ' ' + (data.
|
|
441
|
+
card(t('compat.host'), (data.host && data.host.version) || '?', t('compat.peer') + ' ≥ ' + (data.minHostVersion || '-'), false),
|
|
338
442
|
card(t('compat.verified'), data.verifiedVersion || '?', t('compat.verified.note'), false),
|
|
339
443
|
card(t('compat.caps'), String(usableCount) + '/' + String(findings.length),
|
|
340
444
|
blockedCount > 0
|
|
@@ -417,16 +521,51 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
417
521
|
React.createElement('code', { className: 'dsm-compat-code' }, ' node scripts/doctor.mjs')))
|
|
418
522
|
}
|
|
419
523
|
|
|
524
|
+
// 预设注入边界:五类能力(记忆 / 提示词 / 技能 / MCP / 子智能体)在**每个 Agent
|
|
525
|
+
// 预设**下到不到得了模型。回答的是"面板标着已注入,模型真的看得到吗"——官方
|
|
526
|
+
// minimal 预设的 persona 是 complete,会把除自己以外的提示词段全部压掉,而且它
|
|
527
|
+
// 连官方 subagent 工具都没挂。单独一块:取不到时这一节不显示,不影响上面的结论。
|
|
528
|
+
//
|
|
529
|
+
// 排版口径(用户裁定 2026-09-15):每格一枚描边标签,**绿框=能用、红框=不能用、
|
|
530
|
+
// 灰框=判断不了**;不再出现 ✓/✗ 符号,也不再有任何整行的解释文字 —— 预设损坏与
|
|
531
|
+
// 解析失败缩成预设名旁边的小标签。MCP 与子智能体两列由宿主侧的 preset-reach 给出。
|
|
532
|
+
if (presetReach && Array.isArray(presetReach.rows) && presetReach.rows.length) {
|
|
533
|
+
push(section(t('compat.reach'), t('compat.reach.hint'),
|
|
534
|
+
React.createElement('div', { className: 'dsm-compat-mod-list dsm-compat-cards' },
|
|
535
|
+
presetReach.rows.map(function (row) {
|
|
536
|
+
// ok → 绿框;absent / suppressed → 红框;其余(unknown,含宿主还没回传该字段)
|
|
537
|
+
// 一律灰框,绝不把"读不到"画成"不能用"。
|
|
538
|
+
const chip = function (label, state) {
|
|
539
|
+
const kind = state === 'ok' ? 'ok' : (state === 'absent' || state === 'suppressed') ? 'bad' : 'unknown'
|
|
540
|
+
return React.createElement('span', { className: 'dsm-reach-chip dsm-reach-' + kind, key: label }, label)
|
|
541
|
+
}
|
|
542
|
+
const tags = []
|
|
543
|
+
if (row.broken) tags.push(React.createElement('span', { className: 'dsm-tag dsm-tag-off', key: 'broken' }, t('compat.reach.broken')))
|
|
544
|
+
else if (row.reason) tags.push(React.createElement('span', { className: 'dsm-tag', key: 'reason' }, t('compat.reach.whyUnknown')))
|
|
545
|
+
return React.createElement('div', { className: 'dsm-compat-mod-row', key: row.presetId },
|
|
546
|
+
React.createElement('span', { className: 'dsm-compat-name dsm-reach-name' },
|
|
547
|
+
React.createElement('span', null, (row.name || row.presetId) + (row.isDefault ? ' · ' + t('compat.reach.default') : '')),
|
|
548
|
+
tags.length ? React.createElement('span', { className: 'dsm-tags' }, tags) : null),
|
|
549
|
+
React.createElement('span', { className: 'dsm-compat-reach-marks' },
|
|
550
|
+
chip(t('compat.reach.mark.memory'), row.memory),
|
|
551
|
+
chip(t('compat.reach.mark.agentsMd'), row.agentsMd),
|
|
552
|
+
chip(t('compat.reach.mark.skill'), row.skillCatalog),
|
|
553
|
+
chip(t('compat.reach.mark.mcp'), row.mcp),
|
|
554
|
+
chip(t('compat.reach.mark.subagent'), row.subagent)))
|
|
555
|
+
}))))
|
|
556
|
+
|
|
557
|
+
push(React.createElement('p', { className: 'dsm-help' }, t('compat.reach.tools')))
|
|
558
|
+
}
|
|
559
|
+
|
|
420
560
|
return React.createElement('div', { className: 'dsm-compat' },
|
|
421
561
|
React.createElement('div', { className: 'dsm-head' },
|
|
422
562
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
423
563
|
React.createElement('div', { className: 'dsm-title-row' },
|
|
424
|
-
React.createElement('h2', { className: 'dsm-title' }, t('compat.title')),
|
|
425
|
-
VersionBadgeComponent ? React.createElement(VersionBadgeComponent, null) : null),
|
|
564
|
+
React.createElement('h2', { className: 'dsm-title' }, t('compat.title'))),
|
|
426
565
|
React.createElement('p', { className: 'dsm-desc' }, t('compat.desc')))),
|
|
427
566
|
React.createElement('div', { className: 'dsm-actions' },
|
|
428
567
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: reload },
|
|
429
|
-
busy ? t('
|
|
568
|
+
busy ? t('btn.refreshing') : t('btn.refresh'))),
|
|
430
569
|
body)
|
|
431
570
|
}
|
|
432
571
|
|
|
@@ -440,13 +579,18 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
440
579
|
// 契约测试则直接从导出里读它(apply 提前返回也必须拿得到)。
|
|
441
580
|
const DICT = {
|
|
442
581
|
zh: {
|
|
443
|
-
"title": "技能", "desc": "
|
|
444
|
-
"btn.create": "
|
|
582
|
+
"title": "技能", "desc": "管理技能:启停、导入、创建、回收站。", "link.project": "GitHub", "link.feedback": "问题反馈",
|
|
583
|
+
"btn.create": "新增技能", "btn.import": "导入技能", "btn.refresh": "刷新", "btn.refreshing": "刷新中…", "btn.cancel": "取消", "btn.close": "关闭", "export.hint": "导出为 zip,写入你指定的目录;只读源文件,不改动任何数据。", "export.outDir": "导出目录", "export.outDir.placeholder": "例如 D:////backup", "export.pickDir": "选择导出目录", "export.submit": "导出", "export.busy": "导出中…", "export.done": "已导出 {count} 个文件到 {path}", "scenes.lock.lock": "锁定", "scenes.lock.unlock": "解锁", "scenes.lock.tag": "已锁定", "scenes.lock.hint": "锁定后五个管理域(MCP / 技能 / 子智能体 / 记忆 / 提示词)整体只读;场景启停不受影响", "scenes.lock.blockedEdit": "场景已锁定:先解锁再修改", "scenes.lock.notActive": "场景未启动:先启动再锁定", "scenes.lock.blockedExit": "场景已锁定:先解锁再关闭", "lock.banner": "有场景处于锁定状态:MCP / 技能 / 子智能体 / 记忆 / 提示词已整体冻结,先到场景页解锁再修改。", "export.empty": "没有可导出的条目", "export.pickAll": "全选", "export.pickNone": "全不选", "export.group.count": "{count} 项", "export.missing": ";跳过 {count} 项(文件不存在):{names}", "export.skills": "导出技能", "export.subagents": "导出子智能体", "export.presets": "导出提示词", "export.memories": "导出记忆", "btn.detail": "查看详情", "btn.trash": "移到回收站", "btn.restore": "恢复", "btn.delete.forever": "永久删除", "btn.file.pick": "选择文件", "btn.folder.pick": "选择文件夹", "btn.create.now": "创建技能", "btn.disable": "停用", "btn.enable": "启用", "btn.open.editor": "用系统编辑器打开", "btn.activate": "启用这个", "btn.activate.title": "把当前来源的版本设为同名技能的首选并启用(不改动任何源文件)", "btn.unprefer": "取消首选", "btn.unprefer.title": "取消同名首选,回到按来源优先级自动选择",
|
|
445
584
|
"btn.custom.add": "添加目录", "btn.custom.remove": "移除", "btn.openDir": "选择",
|
|
446
|
-
"btn.source.remove": "移除来源", "btn.source.remove.title": "
|
|
447
|
-
"
|
|
585
|
+
"btn.source.remove": "移除来源", "btn.source.remove.title": "不再读取这个来源(源文件不动,之后可在回收站里恢复)", "btn.source.restore": "恢复", "btn.source.forget": "永久删除",
|
|
586
|
+
"trash.section.dirs": "目录", "trash.section.skills": "技能", "trash.section.dirs.sub": "只是不再读取,磁盘文件一个字节都不动", "trash.section.dirs.empty": "没有被移除的来源", "trash.section.skills.sub": "文件真的被移走了:恢复能还原,永久删除会删掉文件", "trash.section.presets.sub": "预设目录已移走:恢复能还原,永久删除会删掉文件", "trash.section.scenes.sub": "场景记录、档案与它的记忆都在这里:恢复会整条放回", "trash.section.agents.sub": "人设文件已移走:恢复能还原,永久删除会删掉文件",
|
|
587
|
+
"trash.dirs.count.one": "{count} 个待处理目录", "trash.dirs.count.other": "{count} 个待处理目录",
|
|
588
|
+
"trash.row.summary": "技能 {skills} · 目录 {dirs}",
|
|
589
|
+
"confirm.source.forget.title": "永久删除这条来源记录?", "confirm.source.forget.desc": "「{name}」将从插件里彻底删掉:来源列表里不再有它,「恢复读取」也找不回来(需要时只能重新添加这个目录)。",
|
|
590
|
+
"confirm.source.forget.hint": "磁盘上的文件夹一个字节都不动:{path}。",
|
|
591
|
+
"result.sourceForgotten": "已永久删除来源记录:{name}",
|
|
448
592
|
"confirm.source.remove.title": "移除来源?", "confirm.source.remove.desc": "「{name}」将不再被读取:它下面的技能不再出现在本页,也不再参与同名优先级;已启用/停用的选择会保留。",
|
|
449
|
-
"confirm.source.remove.hint": "
|
|
593
|
+
"confirm.source.remove.hint": "不删除任何文件。移除后它出现在「回收站 → 目录」,在那里点「恢复读取」就能重新纳入(也可以永久删除这条记录)。",
|
|
450
594
|
"result.sourceRemoved": "已移除来源:{name}(不再读取)", "result.sourceRestored": "已恢复读取:{name}",
|
|
451
595
|
"custom.add.title": "添加自定义技能目录", "custom.add.submit": "添加",
|
|
452
596
|
"custom.add.path": "目录绝对路径", "custom.add.path.placeholder": "例如 D:\\skills\\my-skills",
|
|
@@ -457,14 +601,17 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
457
601
|
"confirm.custom.remove.desc": "移除后该目录的技能不再列出(文件不受影响):{name}",
|
|
458
602
|
"result.custom.added": "已添加自定义目录:{path}", "result.custom.removed": "已移除自定义目录:{name}",
|
|
459
603
|
"status.enabled": "已启用", "status.disabled": "已停用", "status.invalid": "诊断异常", "status.shadowed": "被覆盖", "status.shadowed.hint": "同名技能「{name}」正在生效;点「启用这个」可改用当前来源的版本", "status.preferred": "同名首选", "status.readonly": "源文件只读", "status.notDeletable": "不可删除", "status.notDeletable.hint": "删除只对插件可管理的来源开放(DSH 技能 / 导入技能 / 项目级 .dsh/skills);外部 Agent 与自定义目录只读,里面的技能不能删", "status.manageable": "可管理", "status.project": "项目级", "status.rank": "优先级 {rank}", "status.source.on": "已启用", "status.source.off": "已停用", "status.bundle": "目录技能", "status.single": "单文件",
|
|
460
|
-
"summary.total.one": "{count} 个技能", "summary.total.other": "{count} 个技能", "summary.enabled.one": "{count} 个已启用", "summary.enabled.other": "{count} 个已启用", "summary.
|
|
604
|
+
"summary.total.one": "{count} 个技能", "summary.total.other": "{count} 个技能", "summary.enabled.one": "{count} 个已启用", "summary.enabled.other": "{count} 个已启用", "summary.issues.one": "{count} 个诊断项", "summary.issues.other": "{count} 个诊断项", "summary.group.one": "{count} 个技能", "summary.group.other": "{count} 个技能", "table.skill": "技能名称与描述", "table.status": "调用状态",
|
|
461
605
|
"filter.source": "来源", "filter.all": "全部来源", "filter.option": "{name}({count})", "search": "搜索", "search.placeholder": "搜索技能名称或描述", "search.clear": "清除搜索",
|
|
462
606
|
"empty.search": "没有匹配的技能", "empty.source": "该来源不存在或暂无技能", "loading": "正在加载技能…", "note.missing": "未提供简介", "source.toggle": "启停来源", "skill.toggle": "启停技能",
|
|
463
607
|
"source.external.note": "只读接入,启停不改写源文件", "source.dsh.note": "可创建、导入、移到回收站;默认来源必须读取,不能移除或停用",
|
|
464
608
|
"detail.title": "技能详情", "detail.body": "正文", "detail.frontmatter": "元数据", "detail.noFrontmatter": "该技能未提供元数据。", "detail.diagnostics": "诊断", "detail.path": "源文件", "detail.noIssues": "未发现诊断问题。",
|
|
465
609
|
"create.title": "创建技能", "create.target": "创建位置", "create.name": "名称", "create.name.placeholder": "例如 code-review-helper", "create.description": "简介", "create.description.placeholder": "一句话说明什么时候使用", "create.body": "正文(Markdown)", "create.body.placeholder": "写下技能要遵循的指令、步骤和边界…", "create.chat.note": "对话里 create_skill 建的是用户级技能",
|
|
466
|
-
"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": "
|
|
610
|
+
"import.title": "导入技能", "upload.drop.title": "点击或拖入此处", "upload.drop.copy": "支持 .zip、技能文件夹或单个 SKILL.md", "upload.selected.one": "{count} 个文件 · {size}", "upload.selected.other": "{count} 个文件 · {size}", "upload.remove": "移除所选内容", "upload.requirements": "文件要求", "upload.requirement.skill": "压缩包或文件夹需包含 SKILL.md", "upload.requirement.frontmatter": "SKILL.md 需包含 YAML 格式的技能名称和描述", "upload.requirement.copy": "导入时复制完整内容,不修改原始来源", "upload.requirement.persona.1": "支持 .md / .zip(可多选、可拖入)", "upload.requirement.persona.2": "一个 .md = 一个人设,文件名即人设名", "upload.requirement.persona.3": "同名自动跳过,绝不覆盖原有文件", "upload.requirement.memory.1": "支持 .md / .zip(可多选、可拖入)", "upload.requirement.memory.2": "一个 .md = 一条记忆,文件名即记忆名;带 SKILL.md 的目录 = bundle 记忆(附件一起进来)", "upload.requirement.memory.3": "zip 里的目录名即场景;没有目录时用下面「导入到场景」的值(留空 = 全局)", "upload.requirement.prompt.1": "支持 .md(可多选、可拖入)", "upload.requirement.prompt.2": "一个 .md = 一份预设,文件名即 id(目录名)", "upload.requirement.prompt.3": "同名已存在则跳过,绝不覆盖", "upload.requirement.session.1": "支持 .jsonl / .json / .md / .txt(一次一个)", "upload.requirement.session.2": "Claude Code / Cursor / Codex 的转录文件,或任意文本", "upload.requirement.session.3": "项目目录可留空 = 归入未分组;留空不影响导入", "upload.importing": "正在导入…", "status.selected": "已选择", "select.file.invalid": "请选择 .zip 或单个 SKILL.md", "select.folder.invalid": "该文件夹里没有 SKILL.md", "error.browse.absolute": "目录路径必须是绝对路径:{path}", "error.browse.unreadable": "无法读取目录:{path}", "error.browse.notDirectory": "不是目录:{path}",
|
|
467
611
|
"trash.title": "回收站", "trash.count.one": "{count} 个待处理技能", "trash.count.other": "{count} 个待处理技能", "trash.empty": "回收站为空", "trash.deletedAt": "删除于 {time}", "trash.source": "来源:{source}",
|
|
612
|
+
"trash.btn.open": "回收站", "trash.items.count": "{count} 项", "trash.purge.confirm": "永久删除?",
|
|
613
|
+
"trash.group.agents": "人设", "trash.group.scenes": "场景", "trash.group.presets": "提示词预设",
|
|
614
|
+
"trash.restored": "已恢复「{name}」", "trash.purged": "已永久删除「{name}」", "trash.loadFailed": "读取回收站失败",
|
|
468
615
|
"confirm.trash.title": "移到回收站?", "confirm.trash.desc": "「{name}」将移入回收站,可恢复", "confirm.delete.title": "永久删除?", "confirm.delete.desc": "「{name}」将永久删除,无法恢复",
|
|
469
616
|
"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
617
|
"warning.scan.truncated": "技能目录较大或嵌套过深,部分技能未显示:{path}", "warning.state.invalid": "状态文件不可读,已安全停用所有技能:{path}", "warning.backupUncleaned": "旧版本备份未清理:{path}({error})", "warning.project.unavailable": "无法从宿主读取活动工作区,项目技能未显示:{path}",
|
|
@@ -478,13 +625,13 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
478
625
|
"error.proto.forbidden": "禁止的修改请求(缺少客户端标记)", "error.proto.forbiddenHost": "禁止的请求来源(非法 Host)", "error.proto.contentType": "请求体必须是 application/json", "error.proto.method": "不支持的请求方法", "error.proto.unknownAction": "未知操作", "error.proto.bodyTooLarge": "请求体过大", "error.proto.invalidJson": "请求体不是合法 JSON", "error.proto.nonJson": "服务端返回非 JSON 响应(HTTP {status})",
|
|
479
626
|
"diagnostic.frontmatter.missing": "缺少完整 YAML frontmatter", "diagnostic.name.missing": "frontmatter 缺少 name", "diagnostic.name.invalid": "技能名需 kebab-case:{name}", "diagnostic.description.missing": "frontmatter 缺少 description", "diagnostic.invocation.invalid": "调用策略字段值无效", "diagnostic.shadowed": "被更高优先级来源 {root} 覆盖",
|
|
480
627
|
"action.enable": "启用", "action.disable": "停用", "action.create": "创建", "action.delete": "删除", "action.restore": "恢复", "action.toggle": "启用或停用",
|
|
481
|
-
"mcp.desc": "
|
|
482
|
-
"mcp.level.all": "全部级别", "mcp.level.project": "
|
|
628
|
+
"mcp.desc": "管理 MCP:新增、启停、重启,以及工具级开关。",
|
|
629
|
+
"mcp.level.all": "全部级别", "mcp.level.project": "应用级", "mcp.level.global": "全局", "mcp.level.loader": "已加载",
|
|
483
630
|
"mcp.stat.total": "个服务", "mcp.stat.enabled": "个已启用", "mcp.stat.tools": "个工具",
|
|
484
631
|
"mcp.search": "搜索服务", "mcp.search.placeholder": "搜索服务名称、地址或命令",
|
|
485
|
-
"mcp.btn.new": "
|
|
632
|
+
"mcp.btn.new": "新增 MCP", "mcp.btn.add": "添加", "mcp.btn.save": "保存", "mcp.btn.detail": "详情", "mcp.btn.edit": "编辑", "mcp.btn.restart": "重启", "mcp.btn.remove": "删除",
|
|
486
633
|
"mcp.btn.enableAll": "全部启用", "mcp.btn.disableAll": "全部停用",
|
|
487
|
-
"mcp.loading": "正在加载 MCP 服务…", "mcp.empty": "暂无 MCP
|
|
634
|
+
"mcp.loading": "正在加载 MCP 服务…", "mcp.empty": "暂无 MCP,点「新增 MCP」添加", "mcp.empty.search": "没有匹配的服务。",
|
|
488
635
|
"mcp.servers.count": "{count} 个服务", "mcp.tools.count": "{count} 个工具", "mcp.duplicate": "重复 id",
|
|
489
636
|
"mcp.table.name": "服务名称与地址", "mcp.table.transport": "传输与工具", "mcp.table.status": "运行状态",
|
|
490
637
|
"mcp.live.notLoaded": "未加载", "mcp.live.failed": "启动失败", "mcp.live.stopped": "未运行", "mcp.live.loading": "加载中", "mcp.live.noTools": "无工具", "mcp.live.running": "运行中",
|
|
@@ -492,17 +639,22 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
492
639
|
"mcp.note.prefix": "备注:", "mcp.toggleServer": "启停服务", "mcp.toggleTool": "启停工具",
|
|
493
640
|
"mcp.msg.ok": "操作成功", "mcp.msg.failed": "操作失败", "mcp.msg.loadFailed": "加载失败", "mcp.msg.warn": "操作完成,但加载器有提示:{warning}",
|
|
494
641
|
"mcp.msg.toolOn": "已启用工具:{name}", "mcp.msg.toolOff": "已停用工具:{name}",
|
|
495
|
-
"mcp.form.addTitle": "新增 MCP
|
|
642
|
+
"mcp.form.addTitle": "新增 MCP", "mcp.form.editTitle": "编辑 MCP 服务:",
|
|
496
643
|
"mcp.field.serverName": "服务名称 serverName", "mcp.field.serverName.hint": "1-32 位 [A-Za-z0-9_-],补丁里按此名注册",
|
|
497
644
|
"mcp.field.transport": "传输方式", "mcp.field.level": "级别",
|
|
498
|
-
"mcp.field.level.project": "
|
|
645
|
+
"mcp.field.level.project": "应用级(仅当前应用:{path})", "mcp.field.level.global": "全局(跨应用:{path})",
|
|
499
646
|
"mcp.field.url": "服务 URL", "mcp.field.url.hint": "需以 http(s):// 开头",
|
|
500
647
|
"mcp.field.command": "启动命令", "mcp.field.args": "参数(空格或换行分隔)",
|
|
501
648
|
"mcp.field.env": "环境变量(每行 key=value)", "mcp.field.env.hint": "路径按系统路径写法填(Windows 用 \\,macOS/Linux 用 /)。",
|
|
502
649
|
"mcp.field.headers": "请求头(每行 key=value)",
|
|
503
650
|
"mcp.detail.title": "服务详情:", "mcp.detail.config": "配置", "mcp.detail.showSecret": "显示密钥", "mcp.detail.hideSecret": "隐藏密钥",
|
|
651
|
+
"mcp.token.label": "访问令牌", "mcp.token.placeholder": "与宿主配置的 token 相同", "mcp.token.save": "保存并重试",
|
|
652
|
+
"mcp.token.hint": "明文密钥必须带对的访问令牌:宿主配置里设 token(或环境变量 DSH_PLUGIN_TOOL_MANAGEMENT_TOKEN)并重启 DSH,这里填同一个值。",
|
|
653
|
+
"mcp.btn.compact": "整理补丁", "mcp.btn.compact.title": "清掉补丁文件里已经不起作用的旧开关记录(每台服务的启用/停用状态不变)",
|
|
654
|
+
"mcp.compact.title": "整理补丁文件", "mcp.compact.desc": "配置里会积累一些已经不起作用的旧开关记录(同一个服务被反复启用/停用留下的)。",
|
|
655
|
+
"mcp.compact.confirm": "确认整理", "mcp.compact.done": "补丁文件已整理:清掉 {count} 条失效的旧开关记录,服务的启用/停用状态未变", "mcp.compact.clean": "补丁文件已经很干净,没有可清理的旧记录",
|
|
504
656
|
"mcp.detail.entryId": "条目 ID", "mcp.detail.status": "运行状态", "mcp.detail.registered": "该服务已登记在 Loader 中。",
|
|
505
|
-
"mcp.detail.note": "
|
|
657
|
+
"mcp.detail.note": "备注(会注入系统提示词,随请求发送给模型)", "mcp.detail.note.placeholder": "例如:A 不可用时改用 B 兜底", "mcp.detail.note.save": "保存备注",
|
|
506
658
|
"mcp.detail.tools": "工具({count})",
|
|
507
659
|
"mcp.field.headersShort": "请求头", "mcp.field.envShort": "环境变量",
|
|
508
660
|
"mcp.tools.loading": "正在获取工具列表…", "mcp.tools.loadFailed": "加载工具失败:", "mcp.tools.none": "该服务暂无已注册工具。",
|
|
@@ -512,11 +664,11 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
512
664
|
"root.dsh": "DSH 技能", "root.hub": "导入技能", "root.agents": "公共 Agent", "root.ccswitch": "CC Switch", "root.projectDsh": "项目 DSH", "root.projectAgents": "项目 Agent", "root.codex": "Codex", "root.claude": "Claude", "root.gemini": "Gemini", "root.opencode": "OpenCode", "root.cursor": "Cursor",
|
|
513
665
|
"memory.title": "记忆",
|
|
514
666
|
"tabs.scenes": "场景", "tabs.skills": "技能", "tabs.subagents": "子智能体", "tabs.prompts": "提示词", "tabs.memory": "记忆", "tabs.sessions": "会话", "tabs.compat": "兼容",
|
|
515
|
-
"compat.title": "宿主兼容", "compat.desc": "
|
|
667
|
+
"compat.title": "宿主兼容", "compat.desc": "本插件与 DSH 的对接体检(只读)。",
|
|
516
668
|
"compat.state.ok": "一切正常", "compat.state.degraded": "有降级项", "compat.state.blocked": "有需要处理的问题",
|
|
517
669
|
"compat.checking": "检查中…", "compat.recheck": "重新检查", "compat.empty": "尚未检查", "compat.failed": "检查失败",
|
|
518
670
|
"compat.summary.ok": "宿主 {version} · 能力 {ok}/{total} · 全部可用", "compat.summary.degraded": "宿主 {version} · 能力 {ok}/{total} · 降级 {count} 项",
|
|
519
|
-
"compat.host": "宿主版本", "compat.peer": "
|
|
671
|
+
"compat.host": "宿主版本", "compat.peer": "最低要求", "compat.verified": "插件适配版本", "compat.verified.note": "本插件按此版本验证",
|
|
520
672
|
"compat.caps": "可用能力", "compat.caps.all": "全部通过", "compat.caps.degraded": "{count} 项不可用",
|
|
521
673
|
"compat.caps.substituted": "缺口由插件自补",
|
|
522
674
|
"compat.ops": "宿主动作可用性", "compat.ops.hint": "每个动作走哪条路径", "compat.ops.disabled": "有 {count} 个动作当前不可用(相关按钮已禁用并会说明原因)",
|
|
@@ -530,36 +682,41 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
530
682
|
"compat.fallback.native": "改走原生入口", "compat.fallback.blocked": "相关按钮已禁用",
|
|
531
683
|
"compat.modules": "模块实体(插件 vs 宿主)", "compat.modules.hint": "同一份模块才谈得上适配", "compat.module.same": "同一份模块", "compat.module.separate": "两份拷贝(需修复)", "compat.module.unknown": "无法比较",
|
|
532
684
|
"compat.blockers": "阻塞项(按此修复)",
|
|
685
|
+
"compat.reach": "预设注入边界", "compat.reach.hint": "面板说已注入,模型真的看得到吗",
|
|
686
|
+
"compat.reach.mark.memory": "记忆", "compat.reach.mark.agentsMd": "提示词", "compat.reach.mark.skill": "技能", "compat.reach.mark.mcp": "MCP", "compat.reach.mark.subagent": "子智能体",
|
|
687
|
+
"compat.reach.default": "默认",
|
|
688
|
+
"compat.reach.whyUnknown": "判断不了", "compat.reach.broken": "预设损坏",
|
|
689
|
+
"compat.reach.tools": "本插件的模型工具(含 subagent_list / subagent_run)与 MCP 都挂在宿主平面,不受预设影响,任何预设下都可调用。",
|
|
533
690
|
"compat.hint.doctor": "命令行体检:",
|
|
534
|
-
"scenes.title": "场景", "scenes.desc": "
|
|
691
|
+
"scenes.title": "场景", "scenes.desc": "管理场景:预设 MCP、技能、子智能体与记忆的组合,一键切换。",
|
|
535
692
|
"scenes.stat.total": "个场景", "scenes.stat.active": "个已启用", "scenes.stat.archives": "个有档案",
|
|
536
693
|
"scenes.noDesc": "还没有描述",
|
|
537
694
|
"scenes.mode.active": "当前模式:{name}", "scenes.mode.profile": "档案:{parts}", "scenes.mode.noProfile": "这个场景还没有档案",
|
|
538
|
-
"scenes.
|
|
695
|
+
"scenes.empty": "还没有专门设置的场景;全局记忆不需要场景就能注入,需要切换 MCP / 技能 / 人设时再新建。",
|
|
539
696
|
"scenes.field.desc.limit": "最多 {count} 字;超出部分在卡片上省略。",
|
|
540
697
|
"scenes.profile.mcp": "MCP {count} 台", "scenes.profile.skills": "技能 {count} 个", "scenes.profile.subagents": "子智能体 {count} 个", "scenes.profile.memories": "记忆 {count} 条",
|
|
541
698
|
"scenes.mcp.hint": "添加「MCP 工具集」后,勾选要启用的服务器(含未运行的);「选工具」可细化到具体工具。",
|
|
542
699
|
"scenes.seg.selectAll": "全选", "scenes.seg.clear": "清空", "scenes.seg.checked": "{checked}/{total} 已勾选",
|
|
543
700
|
"scenes.archive.sectionOff": "未定义",
|
|
544
|
-
"scenes.archive.summary": "档案:MCP {mcp} 台 · 技能 {skills} 个 · 子智能体 {subagents} 个
|
|
545
|
-
"scenes.archive.note": "
|
|
701
|
+
"scenes.archive.summary": "档案:MCP {mcp} 台 · 技能 {skills} 个 · 子智能体 {subagents} 个",
|
|
702
|
+
"scenes.archive.note": "段未定义 = 不改动该域;段已定义 = 按勾选集双向切换:勾的启用、没勾的停用。MCP 会真的启停服务器进程,技能会启停它所在的来源。",
|
|
546
703
|
"scenes.mcp.noServers": "还没有可选的 MCP 服务器", "scenes.mcp.toolCount": "{count} 个工具", "scenes.skills.empty": "还没有可选的技能",
|
|
547
704
|
"scenes.mcp.allTools": "全部工具", "scenes.mcp.pickedCount": "指定 {count} 个工具", "scenes.mcp.notRunning": "未运行", "scenes.mcp.pickTools": "选工具",
|
|
548
705
|
"scenes.mcp.toolsOf": "工具明细", "scenes.mcp.back": "返回", "scenes.mcp.noTools": "该服务器当前没有可列出的工具(未运行或无工具)", "scenes.mcp.drillHint": "勾选 = 该场景下启用;不勾 = 停用。整台勾选时默认全部工具。",
|
|
549
706
|
"scenes.skills.hint": "添加「技能集」后,勾选该场景下启用的技能。", "scenes.subagents.hint": "添加「子智能体绑定」后,勾选本场景可调用的人设。", "scenes.subagents.empty": "还没有人设——到「子智能体」页创建。",
|
|
550
|
-
"scenes.filter.skills": "
|
|
551
|
-
|
|
707
|
+
"scenes.filter.skills": "筛选技能/目录名称", "scenes.filter.subagents": "筛选人设(名称或描述)", "scenes.filter.servers": "筛选服务器",
|
|
708
|
+
|
|
552
709
|
"scenes.mem.title": "记忆", "scenes.mem.search": "在本场景内筛选记忆(名称或描述)",
|
|
553
710
|
"scenes.mem.noMatch": "没有匹配项", "scenes.mem.emptyScene": "该场景还没有记忆",
|
|
554
711
|
"memory.mode.current": "当前模式", "memory.mode.exit": "退出模式",
|
|
555
712
|
"memory.archive.edit": "档案", "memory.archive.title": "场景档案",
|
|
556
713
|
"memory.archive.tools": "MCP 工具集", "memory.archive.skills": "技能集", "memory.archive.subagents": "子智能体绑定", "memory.archive.memories": "记忆",
|
|
557
714
|
"memory.archive.removeSection": "移除段", "memory.archive.save": "保存到场景",
|
|
558
|
-
"memory.archive.addTools": "+ 添加 MCP 工具集", "memory.archive.addSkills": "+ 添加技能集", "memory.archive.addSubagents": "+ 添加子智能体绑定",
|
|
715
|
+
"memory.archive.addTools": "+ 添加 MCP 工具集", "memory.archive.addSkills": "+ 添加技能集", "memory.archive.addSubagents": "+ 添加子智能体绑定",
|
|
559
716
|
"memory.archive.stale": "失效项(已不存在,已跳过): {items}",
|
|
560
|
-
"memory.result.archiveSaved": "已保存场景档案:{name}", "memory.result.modeSet": "已进入模式:{name}", "memory.result.modeExited": "已退出模式",
|
|
561
|
-
"memory.desc": "
|
|
562
|
-
"memory.btn.refresh": "刷新", "memory.btn.new": "
|
|
717
|
+
"memory.result.archiveSaved": "已保存场景档案:{name}", "memory.result.modeSet": "已进入模式:{name}", "memory.result.modeSwitched": "已切换 {mcp} 台服务器 / {skills} 个来源", "memory.result.modeExited": "已退出模式",
|
|
718
|
+
"memory.desc": "管理记忆:启用的记忆进入系统提示词,可按场景分别启用。",
|
|
719
|
+
"memory.btn.refresh": "刷新", "memory.btn.new": "新增记忆", "memory.btn.create": "创建", "memory.btn.save": "保存",
|
|
563
720
|
"memory.btn.diagnose": "体检", "memory.btn.delete.confirm": "移入回收站",
|
|
564
721
|
"memory.diagnose.title": "记忆体检", "memory.diagnose.summary": "{rules} 条记忆 · {scenes} 个场景 · {issues} 个问题", "memory.diagnose.clean": "未发现问题,一切正常。",
|
|
565
722
|
"memory.diag.error": "错误", "memory.diag.warning": "警告", "memory.diag.info": "提示",
|
|
@@ -572,10 +729,10 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
572
729
|
"memory.scene.global": "全局", "memory.scene.count": "{count} 条记忆", "memory.scene.new": "新建记忆", "memory.scene.browse": "选择已有",
|
|
573
730
|
"memory.scene.empty": "该场景暂无记忆",
|
|
574
731
|
"memory.scene.orphan": "未归属场景", "memory.scene.orphan.tag": "不会注入", "memory.scene.orphan.hint": "这些记忆直接放在 memories/ 根层,没有归属场景,因此不会进入系统提示词。请把它们移入某个场景目录(或放到 memories/global/ 作为「全局」记忆)。",
|
|
575
|
-
"memory.scene.edit": "改描述", "memory.scene.editTitle": "修改场景", "memory.scene.editHelp": "
|
|
732
|
+
"memory.scene.edit": "改描述", "memory.scene.editTitle": "修改场景", "memory.scene.editHelp": "名称就是它的记忆目录名;改名会连目录、档案与绑定一起改,记忆正文不动。",
|
|
576
733
|
"memory.scene.field.name": "场景名", "memory.scene.field.name.placeholder": "例如 办公",
|
|
577
|
-
"memory.scene.field.name.hint": "
|
|
578
|
-
"memory.scene.field.name.lock": "
|
|
734
|
+
"memory.scene.field.name.hint": "名称就是它的一级目录名,之后仍可修改。",
|
|
735
|
+
"memory.scene.field.name.lock": "改名会一起改掉记忆目录、场景档案与启用集合;记忆正文不动。",
|
|
579
736
|
"memory.scene.field.desc": "描述(可选)", "memory.scene.field.desc.placeholder": "一句话说明这个场景是干什么的",
|
|
580
737
|
"memory.search.placeholder": "搜索名称 / 描述 / 场景", "memory.filter.all": "全部场景",
|
|
581
738
|
"memory.empty": "还没有记忆,点「新建记忆」", "memory.empty.search": "没有匹配的记忆",
|
|
@@ -599,54 +756,68 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
599
756
|
"memory.trash.count": "{count} 条已删除的记忆", "memory.trash.deletedAt": "删除于 {time}",
|
|
600
757
|
"memory.trash.restore": "恢复", "memory.trash.purge": "永久删除",
|
|
601
758
|
"memory.trash.confirmTitle": "永久删除?", "memory.trash.confirmDesc": "「{name}」将永久删除,无法恢复",
|
|
602
|
-
"memory.btn.newScene": "
|
|
759
|
+
"memory.btn.newScene": "新增场景", "memory.btn.deleteScene": "删除场景", "memory.btn.saveScene": "保存修改",
|
|
603
760
|
"memory.scene.createTitle": "新建场景",
|
|
604
|
-
"memory.deleteScene.title": "删除场景?", "memory.deleteScene.desc": "
|
|
605
|
-
"memory.result.sceneCreated": "已创建场景:{name}", "memory.result.sceneUpdated": "已保存场景:{name}", "memory.result.sceneRemoved": "已删除场景:{name}",
|
|
761
|
+
"memory.deleteScene.title": "删除场景?", "memory.deleteScene.desc": "把「{name}」连同它下面全部记忆(含未启用的)一起移入回收站,可在回收站整条恢复;正在使用的场景不能删。",
|
|
762
|
+
"memory.result.sceneCreated": "已创建场景:{name}", "memory.result.sceneUpdated": "已保存场景:{name}", "memory.result.sceneRemoved": "已删除场景:{name}", "memory.result.sceneRemoved.withMemories": "已删除场景:{name}(连同 {count} 项记忆,可在回收站整条恢复)",
|
|
606
763
|
"memory.table.name": "记忆名称与描述", "memory.table.tags": "标记", "memory.table.status": "状态",
|
|
607
|
-
"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": "该场景正处在当前模式,请先退出模式再删除",
|
|
764
|
+
"error.rules.invalidArgs": "缺少参数:需要 all:true(回到全部启用)或 scenes:[...](显式收窄)", "error.rules.invalidGroup": "场景/分组名不合法(非空、≤64 字符、不含路径分隔符与 < > : \" | ? *、不以 . 开头)", "error.rules.invalidName": "记忆名不合法(非空、≤64 字符、不含路径分隔符与 < > : \" | ? *、不以 . 开头)", "error.rules.descriptionRequired": "描述不能为空", "error.rules.descriptionTooLong": "描述过长(不能超过 500 字符)", "error.rules.bodyRequired": "正文不能为空", "error.rules.tooLarge": "规则内容过大", "error.rules.shadowed": "规则被同名 bundle 遮蔽,无法写入", "error.rules.notFound": "规则不存在", "error.rules.budgetExceeded": "场景记忆段超出预算", "error.rules.ioFailed": "文件操作失败", "error.rules.nameTaken": "目标名称已被占用", "error.rules.sceneNotEmpty": "场景不为空,无法删除", "error.rules.notBundle": "该记忆是 flat(单文件),不能带附件", "error.rules.noFiles": "没有选择附件", "error.rules.emptyFile": "附件内容为空", "error.rules.fileTooLarge": "附件过大(单个上限 {limit} MB)", "error.rules.tooManyFiles": "一次最多 {limit} 个附件", "error.rules.sceneInMode": "该场景正处在当前模式,请先退出模式再删除",
|
|
608
765
|
"memory.archive.emptySection": "该段已定义但没有勾选任何条目 = 全部停用", "memory.archive.emptySubagents": "该段已定义但没有勾选任何条目 = 不限制(全部人设可用)",
|
|
609
|
-
"subagents.title": "子智能体", "subagents.desc": "
|
|
610
|
-
"prompts.desc": "
|
|
766
|
+
"subagents.title": "子智能体", "subagents.stat.total": "个子智能体", "subagents.stat.limited": "个有工具限制", "subagents.desc": "管理子智能体:新建、导入人设,正文即子代理的系统提示词。",
|
|
767
|
+
"prompts.desc": "管理提示词:预设全局指令基线,应用后写入 AGENTS.md,下一轮对话生效。",
|
|
611
768
|
"sessions.desc": "管理已归档会话:可恢复或永久删除,超保留期自动清理。",
|
|
612
|
-
"subagents.new": "
|
|
769
|
+
"subagents.new": "新增子智能体", "subagents.empty": "还没有子智能体——点「新增子智能体」创建第一个。",
|
|
613
770
|
"subagents.create": "新建人设", "subagents.edit": "编辑人设",
|
|
614
771
|
"subagents.field.name": "人设名",
|
|
615
772
|
"subagents.field.description": "描述", "subagents.field.description.placeholder": "例如 擅长 Java 后端实现与重构",
|
|
616
773
|
"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
774
|
"subagents.model.inherit": "继承主会话(不指定)", "subagents.model.customOption": "自定义 / 目录里没有…",
|
|
618
|
-
"subagents.field.
|
|
619
|
-
"subagents.field.
|
|
620
|
-
"subagents.
|
|
621
|
-
"subagents.
|
|
622
|
-
"subagents.
|
|
623
|
-
"subagents.
|
|
775
|
+
"subagents.field.modes": "工具限制(按 Agent 预设)",
|
|
776
|
+
"subagents.field.modes.hint": "只有当前模式那一行生效,其他模式用该模式的全部工具。",
|
|
777
|
+
"subagents.mode.startAllow": "启动白名单", "subagents.mode.startDeny": "启动黑名单", "subagents.mode.stopAllow": "关闭白名单", "subagents.mode.stopDeny": "关闭黑名单",
|
|
778
|
+
"subagents.mode.off": "未限制", "subagents.mode.empty": "已启动但没勾选(= 不限制)",
|
|
779
|
+
"subagents.mode.allowOn": "白名单 {count} 个", "subagents.mode.denyOn": "黑名单 {count} 个",
|
|
780
|
+
"subagents.mode.allowEmpty": "一个都没勾 = 不限制(该模式的全部工具都能用)",
|
|
781
|
+
"subagents.mode.denyEmpty": "一个都没勾 = 不限制(该模式的全部工具都能用)",
|
|
782
|
+
"subagents.mode.noTools": "读不到这个模式的工具清单",
|
|
783
|
+
"subagents.mode.broken": "这个预设已损坏,读不到工具清单",
|
|
784
|
+
"subagents.mode.noPresets": "宿主没有回传 Agent 预设名单",
|
|
785
|
+
"subagents.legacy.note": "这个文件里还有一份旧写法的工具限制(白名单 {allow} 个 / 黑名单 {deny} 个),它对所有模式都生效。",
|
|
786
|
+
"subagents.legacy.convert": "转换到按模式设置",
|
|
787
|
+
"subagents.legacy.hint": "转换会把旧名单搬进你选的模式;不点它,文件里的旧键原样保留(运行时照旧生效)。",
|
|
788
|
+
"subagents.tools.filter": "筛选工具名", "subagents.tools.remove": "移除",
|
|
789
|
+
"subagents.adv.title": "高级选项", "subagents.adv.inherit": "继承主会话", "subagents.adv.summary": "模型 {model} · 模式限制 {modes} 项 · 旧格式 白名单 {allow} / 黑名单 {deny}", "subagents.adv.note": "白名单 = 子代理只能用勾选的工具;黑名单 = 除勾选的以外都能用。MCP 工具不在候选里:子代理始终能用当前在跑的 MCP。", "subagents.adv.loadFailed": "读取候选数据失败(模型目录 / 工具清单);仍可手动填写。",
|
|
790
|
+
"preset.name.standard": "标准模式", "preset.name.ptc": "PTC 模式", "preset.name.minimal": "极简模式", "preset.name.cordis": "创造模式",
|
|
791
|
+
"preset.short.standard": "标准", "preset.short.ptc": "PTC", "preset.short.minimal": "极简", "preset.short.cordis": "创造",
|
|
624
792
|
"subagents.field.body": "人设提示词", "subagents.field.body.placeholder": "写下这个人设的身份、职责与工作方式…",
|
|
625
793
|
"subagents.result.saved": "已保存人设:{name}", "subagents.result.deleted": "已删除人设:{name}",
|
|
626
|
-
"subagents.delete.title": "删除人设?", "subagents.delete.desc": "
|
|
627
|
-
"subagents.import": "
|
|
794
|
+
"subagents.delete.title": "删除人设?", "subagents.delete.desc": "将把「{name}」的人设文件移入回收站(子智能体页的「回收站」里可以恢复)。",
|
|
795
|
+
"subagents.import": "导入子智能体", "subagents.import.title": "导入子智能体", "subagents.toggle": "启用子智能体", "subagents.disabled": "已停用", "subagents.disabled.hint": "停用后不注入系统提示词,subagent_list / subagent_run 也看不到;文件保留,随时可再打开。",
|
|
628
796
|
"subagents.result.imported": "已导入 {count} 个人设:{names}",
|
|
629
797
|
"memory.import": "导入记忆", "memory.import.title": "导入记忆", "memory.import.scene": "导入到场景",
|
|
630
|
-
"memory.import.sceneHint": "留空 = 全局;zip 内的目录名会当作场景名。",
|
|
631
|
-
"memory.import.hint": "支持 .md 或 .zip(可多选、可拖入):一个 .md = 一条记忆;zip 内 <场景>/<名>/SKILL.md 按 bundle 导入,同层文件作附件;同名自动跳过。",
|
|
632
798
|
"memory.result.imported": "已导入 {count} 条记忆:{names}",
|
|
633
799
|
"memory.import.scenesCreated": "为此新建了场景:{names}",
|
|
634
800
|
"memory.scene.global.hint": "全局",
|
|
635
|
-
|
|
801
|
+
"import.clear": "清空选择", "import.submit": "导入",
|
|
636
802
|
"import.none": "没有导入任何文件(全部被跳过)", "import.skipped": "已跳过:{items}",
|
|
637
803
|
"error.import.noFiles": "没有选择要导入的文件",
|
|
638
804
|
"nav.title": "工具",
|
|
639
|
-
"agm.
|
|
805
|
+
"agm.stat.total": "个提示词", "agm.stat.active": "个生效中", "agm.loading": "加载中…", "agm.empty": "暂无提示词,点「新增提示词」创建", "agm.btn.delete.blocked": "正在生效的提示词预设不能删除;先启用别的场景、换绑提示词,或先把别的预设应用上去。", "agm.active": "生效中", "agm.active.hint.scene": "当前生效:场景「{scene}」绑定的提示词预设", "agm.active.hint.file": "当前生效:AGENTS.md 就是这一份", "agm.file.applied": "文件里是它", "agm.file.applied.hint": "AGENTS.md 当前内容是它;启用的场景绑了别的提示词,切换场景时已按场景把文件改成那一份", "agm.apply.hint": "应用后写入 AGENTS.md;宿主每轮重读该文件,下一轮对话生效", "agm.btn.reapply": "重新应用", "agm.btn.apply": "应用", "agm.applyModal.title": "应用预设", "agm.edit.title": "编辑预设", "agm.remove.title": "删除预设", "agm.btn.edit": "编辑", "agm.btn.delete": "删除", "agm.btn.new": "新增提示词", "agm.btn.import": "导入提示词", "agm.result.imported": "已导入 {count} 份预设:{names}", "agm.btn.save": "保存", "agm.btn.create": "创建", "agm.btn.confirmRemove": "确认删除", "agm.btn.confirmApply": "确认应用", "agm.field.id": "id(目录名)", "agm.field.id.hint": "中文、空格、点都可以;不能含斜杠、反斜杠与 : * ? < > 等符号;≤64 字符。改 id 等于目录改名。", "agm.field.id.placeholder": "例如:工作基线", "agm.field.content": "内容", "agm.field.content.placeholder": "直接写这份预设的正文(Markdown);留空 = 用空白模板", "agm.field.copyFrom": "或从现有预设复制", "agm.field.copyFrom.none": "不复制(空白模板)", "agm.field.desc": "描述(只给使用者看)", "agm.field.desc.placeholder": "这份预设是干什么的、什么时候用它", "agm.result.renamed": "已保存,id 改为「{id}」", "agm.result.rebound": "(同步了 {count} 个场景的绑定)", "agm.remove.suffix": " ?此操作不可恢复。", "agm.apply.prefix": "将把 ", "agm.apply.suffix": " 的内容写入 ~/.dsh/AGENTS.md。", "agm.apply.note": "下一轮对话生效;当前内容已备份到 __last-applied__(保留最近 5 代)", "hist.err.load": "加载失败", "hist.err.unarchive": "恢复失败", "hist.err.delete": "删除失败", "hist.err.retention": "设置失败", "hist.err.import": "导入失败", "hist.err.sessions": "加载会话列表失败", "hist.err.export": "导出失败", "hist.err.archive": "归档失败", "hist.retention.forever": "永久保留", "hist.retention.days": "{count} 天", "hist.arch.all": "全部会话", "hist.arch.archived": "仅已归档", "hist.arch.live": "仅未归档", "hist.arch.missing": "仅目录丢失", "hist.ws.all": "全部工作区", "hist.ungrouped": "未分组", "hist.group.removed": "已移除", "hist.group.removed.hint": "该目录当前在 DSH 里没有工作区登记;分组按会话目录重建,重新登记后会自动并回同一组", "hist.btn.registerWs": "重新登记", "hist.btn.registerWs.hint": "重新登记这个目录,并把该目录下已有的会话挂回该组(只写工作区记账,不移动、不删除文件与会话)", "hist.register.title": "重新登记?", "hist.register.body": "将为「{title}」在 DSH 中重新创建一条工作区登记,并把该目录下已有的会话挂回这个工作区(恢复其分组)。只写工作区记账,不移动、不删除任何文件与会话。", "hist.register.confirm": "确认登记", "hist.register.done.title": "已重新登记", "hist.register.done.body": "工作区「{title}」已登记;该目录下的归档会话会在刷新后并入这一组。", "hist.register.done.attached": "已把该目录下 {count} 个已有会话挂回这个工作区。", "hist.register.done.skipped": "{count} 个会话未能挂回:{reason}", "hist.err.register": "登记失败", "scenes.field.prompt": "提示词预设", "scenes.agents.note.applied": "已把「{id}」写入 AGENTS.md", "scenes.agents.note.restored": "已恢复进场景前的 AGENTS.md", "scenes.agents.note.error": "AGENTS.md 未写入:{reason}", "scenes.field.prompt.hint": "启用这个场景时,就用这份预设作为当前的全局指令基线。默认选中当前生效的那份。", "scenes.prompt.activeTag": "生效中", "scenes.prompt.noPresets": "还没有提示词预设;可先去「提示词」页新建一个。", "scenes.prompt.bound": "提示词:{id}", "scenes.prompt.live": "提示词生效中:{id}", "scenes.prompt.missing": "预设不存在:{id}", "scenes.prompt.tag.hint": "这个场景绑定的提示词预设", "scenes.enable.blocked": "已启用场景「{name}」;先关掉它才能启用别的", "scenes.result.created.collapsed": "场景「{name}」已创建,默认不启动(原先的「全部启用」已收敛为单选)", "scenes.legacyAll": "检测到 {count} 个场景同时处于启用状态(历史「全部启用」遗留);除「全局」外同时只能启用一个。", "scenes.legacyAll.fix": "收敛为单选", "agm.scene.tag": "场景「{scene}」正在使用", "agm.scene.tag.hint": "由这个场景绑定,随场景开关生效", "agm.scene.missing": "场景「{scene}」绑定了它,但文件不存在", "hist.restore.note.registered": "已恢复 {count} 个会话;工作区「{title}」已重新登记,会话已挂回该组。", "hist.restore.note.attached": "已恢复 {count} 个会话,并挂回工作区「{title}」。", "hist.restore.note.skip": "{count} 个会话的工作区归属未能恢复:{reason}", "hist.purge.title": "永久删除?", "hist.purge.one": "永久删除会话「{title}」及其全部记录,不可恢复", "hist.purge.batch": "永久删除所选 {count} 个会话及其全部记录,不可恢复", "hist.btn.restore": "恢复", "hist.btn.purge": "永久删除", "hist.btn.confirmPurge": "确认删除", "hist.selectGroup": "全选组 {title}", "hist.group.count": "{count} 个", "hist.selectAll": "全选", "hist.deselectAll": "取消全选", "hist.btn.import": "导入会话", "hist.btn.export": "导出会话", "hist.selected": "已选 {count} 项", "hist.btn.restoreSelected": "恢复所选 ({count})", "hist.btn.deleteSelected": "删除所选 ({count})", "hist.stat.archived": "个归档", "hist.stat.projects": "个项目", "hist.stat.retention": "天保留", "hist.search.placeholder": "搜索标题 / 会话 ID / 项目路径", "hist.loading": "加载中…", "hist.empty.search": "无匹配的归档会话", "hist.empty.none": "暂无归档会话", "hist.import.cwd": "项目目录(可留空)", "hist.import.cwd.placeholder": "绝对路径,可留空(会话归入未分组)", "hist.import.drop": "选择或拖入对话文件", "hist.import.done": "已创建会话 {id}({count} 条消息),可在 DSH 会话列表中继续对话。", "hist.export.hint": "导出选中会话为转录文件,可再次导入", "hist.export.dir": "导出目录(绝对路径,自动创建)", "hist.export.browse": "浏览并选择文件夹", "hist.btn.browse": "选择", "hist.export.pickDir": "选择导出目录", "hist.export.format": "格式", "hist.export.scope": "会话范围", "hist.export.workspace": "工作区", "hist.export.sessions": "会话({count} 个)", "hist.export.loading": "加载会话列表…", "hist.cwd.missing": "⚠ 工作区目录已不存在:", "hist.tag.archived": "已归档", "hist.tag.live": "未归档", "hist.export.empty": "没有符合条件的会话", "hist.result.exported": "已导出 {count} 个会话到 {dir}", "hist.result.skipped": ";跳过 {count} 个({ids})", "hist.result.archived": "已归档 {count} 个会话到 History,可到 History 页继续管理", "hist.btn.archiveSelected": "归档所选", "hist.btn.archiveSelected.title": "把选中的会话收进 History,纳入保留期管理", "hist.export.busy": "处理中…", "hist.btn.exportSelected": "导出 {count} 个会话", "hist.error.title": "操作失败", "hist.error.unknown": "未知错误",
|
|
640
806
|
},
|
|
641
807
|
en: {
|
|
642
|
-
"title": "Skills", "desc": "Manage skills
|
|
643
|
-
"btn.create": "
|
|
808
|
+
"title": "Skills", "desc": "Manage skills: enable, import, create, trash.", "link.project": "GitHub", "link.feedback": "Issues",
|
|
809
|
+
"btn.create": "New skill", "btn.import": "Import skills", "btn.refresh": "Refresh", "btn.refreshing": "Refreshing…", "btn.cancel": "Cancel", "btn.close": "Close", "export.hint": "Exports a zip into the directory you choose; source files are read-only and nothing is modified.", "export.outDir": "Output directory", "export.outDir.placeholder": "e.g. D:////backup", "export.pickDir": "Choose output directory", "export.submit": "Export", "export.busy": "Exporting…", "export.done": "Exported {count} file(s) to {path}", "scenes.lock.lock": "Lock", "scenes.lock.unlock": "Unlock", "scenes.lock.tag": "Locked", "scenes.lock.hint": "Locking makes all five domains (MCP / skills / subagents / memories / prompts) read-only; starting or stopping scenes still works", "scenes.lock.blockedEdit": "Scene is locked — unlock it to make changes", "scenes.lock.notActive": "Scene is not active — start it before locking", "scenes.lock.blockedExit": "Scene is locked — unlock it before turning it off", "lock.banner": "A scene is locked: MCP, skills, subagents, memories and prompts are frozen. Unlock it on the Scenes page to make changes.", "export.empty": "Nothing to export", "export.pickAll": "Select all", "export.pickNone": "Clear", "export.group.count": "{count}", "export.missing": "; skipped {count} (missing on disk): {names}", "export.skills": "Export skills", "export.subagents": "Export subagents", "export.presets": "Export prompts", "export.memories": "Export memories", "btn.detail": "View details", "btn.trash": "Move to trash", "btn.restore": "Restore", "btn.delete.forever": "Delete forever", "btn.file.pick": "Choose file", "btn.folder.pick": "Choose folder", "btn.create.now": "Create skill", "btn.disable": "Disable", "btn.enable": "Enable", "btn.open.editor": "Open in editor", "btn.activate": "Use this one", "btn.activate.title": "Make this source's copy the active one for this shared name and enable it (source files are never touched)", "btn.unprefer": "Auto again", "btn.unprefer.title": "Clear the manual choice and go back to automatic source priority",
|
|
644
810
|
"btn.custom.add": "Add folder",
|
|
645
811
|
"btn.custom.remove": "Remove",
|
|
646
|
-
"btn.source.remove": "Remove source", "btn.source.remove.title": "Stop reading this source (files stay on disk
|
|
647
|
-
"
|
|
812
|
+
"btn.source.remove": "Remove source", "btn.source.remove.title": "Stop reading this source (files stay on disk; restore it later from the trash)", "btn.source.restore": "Restore", "btn.source.forget": "Delete forever",
|
|
813
|
+
"trash.section.dirs": "Directories", "trash.section.skills": "Skills", "trash.section.dirs.sub": "No longer read — files on disk are untouched", "trash.section.dirs.empty": "No removed sources", "trash.section.skills.sub": "Files really were moved: restore brings them back, delete-forever removes them", "trash.section.presets.sub": "Preset folders were moved: restore brings them back, delete-forever removes them", "trash.section.scenes.sub": "Scene record, profile and its memories are all here: restore puts everything back", "trash.section.agents.sub": "Persona files were moved: restore brings them back, delete-forever removes them",
|
|
814
|
+
"trash.dirs.count.one": "{count} directory pending", "trash.dirs.count.other": "{count} directories pending",
|
|
815
|
+
"trash.row.summary": "{skills} skill(s) · {dirs} director(ies)",
|
|
816
|
+
"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).",
|
|
817
|
+
"confirm.source.forget.hint": "Not a single byte is deleted on disk: {path}.",
|
|
818
|
+
"result.sourceForgotten": "Source record deleted for good: {name}",
|
|
648
819
|
"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.
|
|
820
|
+
"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
821
|
"result.sourceRemoved": "Source removed: {name} (no longer read)", "result.sourceRestored": "Reading again: {name}",
|
|
651
822
|
"btn.openDir": "Browse",
|
|
652
823
|
"custom.add.title": "Add custom skill folder",
|
|
@@ -664,14 +835,17 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
664
835
|
"result.custom.removed": "Custom folder removed: {name}",
|
|
665
836
|
"result.opened": "Opened with the system default app: {path}",
|
|
666
837
|
"status.enabled": "Enabled", "status.disabled": "Disabled", "status.invalid": "Needs attention", "status.shadowed": "Shadowed", "status.shadowed.hint": "The copy “{name}” is the active one; click “Use this one” to switch to this source instead", "status.preferred": "Manually chosen", "status.readonly": "Source read-only", "status.notDeletable": "Cannot delete", "status.notDeletable.hint": "Deleting is available for plugin-managed sources (DSH skills / Imported skills / project .dsh/skills); external agent and custom directories are read-only", "status.manageable": "Manageable", "status.project": "Project scoped", "status.rank": "Rank {rank}", "status.source.on": "Enabled", "status.source.off": "Disabled", "status.bundle": "Bundle", "status.single": "Single file",
|
|
667
|
-
"summary.total.one": "{count} skill", "summary.total.other": "{count} skills", "summary.enabled.one": "{count} enabled", "summary.enabled.other": "{count} enabled", "summary.
|
|
838
|
+
"summary.total.one": "{count} skill", "summary.total.other": "{count} skills", "summary.enabled.one": "{count} enabled", "summary.enabled.other": "{count} enabled", "summary.issues.one": "{count} diagnostic", "summary.issues.other": "{count} diagnostics", "summary.group.one": "{count} skill", "summary.group.other": "{count} skills", "table.skill": "Skill name and description", "table.status": "Invocation status",
|
|
668
839
|
"filter.source": "Source", "filter.all": "All sources", "filter.option": "{name} ({count})", "search": "Search", "search.placeholder": "Search skill names or descriptions", "search.clear": "Clear search",
|
|
669
840
|
"empty.search": "No matching skills", "empty.source": "This source is missing or has no skills", "loading": "Loading skills…", "note.missing": "No description provided", "source.toggle": "Toggle source", "skill.toggle": "Toggle skill",
|
|
670
841
|
"source.external.note": "Read-only; toggles never rewrite source files", "source.dsh.note": "Create, import, trash; a default source, always read — it cannot be removed or toggled off",
|
|
671
842
|
"detail.title": "Skill details", "detail.body": "Body", "detail.frontmatter": "Metadata", "detail.noFrontmatter": "This skill provides no metadata.", "detail.diagnostics": "Diagnostics", "detail.path": "Source file", "detail.noIssues": "No diagnostic issues found.",
|
|
672
843
|
"create.title": "Create skill", "create.target": "Create in", "create.name": "Name", "create.name.placeholder": "e.g. code-review-helper", "create.description": "Description", "create.description.placeholder": "One sentence describing when to use it", "create.body": "Body (Markdown)", "create.body.placeholder": "Write the instructions, steps, and boundaries…", "create.chat.note": "create_skill in chat creates user-level skills",
|
|
673
|
-
"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": "
|
|
844
|
+
"import.title": "Import skill", "upload.drop.title": "Click or drop here", "upload.drop.copy": "Supported: .zip, a skill folder, or one SKILL.md", "upload.selected.one": "{count} file · {size}", "upload.selected.other": "{count} files · {size}", "upload.remove": "Remove selection", "upload.requirements": "File requirements", "upload.requirement.skill": "Archives and folders must contain SKILL.md", "upload.requirement.frontmatter": "SKILL.md must include a YAML name and description", "upload.requirement.copy": "Import copies all content and never modifies the source", "upload.requirement.persona.1": "Accepts .md / .zip (multi-select or drag in)", "upload.requirement.persona.2": "One .md = one persona; the file name is the persona name", "upload.requirement.persona.3": "Same names are skipped, never overwritten", "upload.requirement.memory.1": "Accepts .md / .zip (multi-select or drag in)", "upload.requirement.memory.2": "One .md = one memory; a folder with SKILL.md becomes a bundle memory (attachments included)", "upload.requirement.memory.3": "Folder names inside the zip are scenes; otherwise the scene field below applies (blank = global)", "upload.requirement.prompt.1": "Accepts .md (multi-select or drag in)", "upload.requirement.prompt.2": "One .md = one preset; the file name is the id", "upload.requirement.prompt.3": "A preset with the same name is skipped, never overwritten", "upload.requirement.session.1": "Accepts .jsonl / .json / .md / .txt (one file at a time)", "upload.requirement.session.2": "Transcripts from Claude Code / Cursor / Codex, or any plain text", "upload.requirement.session.3": "The project directory may be left blank (the session goes to Ungrouped)", "upload.importing": "Importing…", "status.selected": "Selected", "select.file.invalid": "Choose a .zip archive or one SKILL.md.", "select.folder.invalid": "No SKILL.md was found in the selected folder.", "error.browse.absolute": "Folder path must be absolute: {path}", "error.browse.unreadable": "Could not read folder: {path}", "error.browse.notDirectory": "Not a folder: {path}",
|
|
674
845
|
"trash.title": "Trash", "trash.count.one": "{count} skill pending", "trash.count.other": "{count} skills pending", "trash.empty": "Trash is empty", "trash.deletedAt": "Deleted {time}", "trash.source": "Source: {source}",
|
|
846
|
+
"trash.btn.open": "Trash", "trash.items.count": "{count} item(s)", "trash.purge.confirm": "Delete forever?",
|
|
847
|
+
"trash.group.agents": "Personas", "trash.group.scenes": "Scenes", "trash.group.presets": "Prompt presets",
|
|
848
|
+
"trash.restored": "Restored “{name}”", "trash.purged": "Permanently deleted “{name}”", "trash.loadFailed": "Could not read the trash",
|
|
675
849
|
"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
850
|
"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
851
|
"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}",
|
|
@@ -685,13 +859,13 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
685
859
|
"error.proto.forbidden": "Forbidden mutation request (missing client marker)", "error.proto.forbiddenHost": "Forbidden request origin (invalid host)", "error.proto.contentType": "Content type must be application/json", "error.proto.method": "Method not allowed", "error.proto.unknownAction": "Unknown action", "error.proto.bodyTooLarge": "Request body too large", "error.proto.invalidJson": "Invalid JSON request body", "error.proto.nonJson": "Server returned a non-JSON response (HTTP {status})",
|
|
686
860
|
"diagnostic.frontmatter.missing": "Missing complete YAML frontmatter", "diagnostic.name.missing": "Frontmatter is missing name", "diagnostic.name.invalid": "Skill name must be kebab-case: {name}", "diagnostic.description.missing": "Frontmatter is missing description", "diagnostic.invocation.invalid": "Invocation policy value is invalid", "diagnostic.shadowed": "Shadowed by higher-priority source {root}",
|
|
687
861
|
"action.enable": "enable", "action.disable": "disable", "action.create": "create", "action.delete": "delete", "action.restore": "restore", "action.toggle": "enabling or disabling",
|
|
688
|
-
"mcp.desc": "Manage
|
|
862
|
+
"mcp.desc": "Manage MCP: add, enable/disable, restart, and per-tool switches.",
|
|
689
863
|
"mcp.level.all": "All levels", "mcp.level.project": "Profile level", "mcp.level.global": "Global", "mcp.level.loader": "Loaded",
|
|
690
864
|
"mcp.stat.total": "server(s)", "mcp.stat.enabled": "enabled", "mcp.stat.tools": "tool(s)",
|
|
691
865
|
"mcp.search": "Search servers", "mcp.search.placeholder": "Search a server name, URL or command",
|
|
692
|
-
"mcp.btn.new": "
|
|
866
|
+
"mcp.btn.new": "New MCP", "mcp.btn.add": "Add", "mcp.btn.save": "Save", "mcp.btn.detail": "Details", "mcp.btn.edit": "Edit", "mcp.btn.restart": "Restart", "mcp.btn.remove": "Delete",
|
|
693
867
|
"mcp.btn.enableAll": "Enable all", "mcp.btn.disableAll": "Disable all",
|
|
694
|
-
"mcp.loading": "Loading MCP servers…", "mcp.empty": "No MCP servers yet — click “
|
|
868
|
+
"mcp.loading": "Loading MCP servers…", "mcp.empty": "No MCP servers yet — click “New MCP”.", "mcp.empty.search": "No matching server.",
|
|
695
869
|
"mcp.servers.count": "{count} server(s)", "mcp.tools.count": "{count} tool(s)", "mcp.duplicate": "duplicate id",
|
|
696
870
|
"mcp.table.name": "Server name and URL", "mcp.table.transport": "Transport and tools", "mcp.table.status": "Status",
|
|
697
871
|
"mcp.live.notLoaded": "not loaded", "mcp.live.failed": "start failed", "mcp.live.stopped": "stopped", "mcp.live.loading": "loading", "mcp.live.noTools": "no tools", "mcp.live.running": "running",
|
|
@@ -708,8 +882,13 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
708
882
|
"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
883
|
"mcp.field.headers": "Headers (one key=value per line)",
|
|
710
884
|
"mcp.detail.title": "Server details: ", "mcp.detail.config": "Configuration", "mcp.detail.showSecret": "Reveal secrets", "mcp.detail.hideSecret": "Hide secrets",
|
|
885
|
+
"mcp.token.label": "Access token", "mcp.token.placeholder": "Same value as the host token", "mcp.token.save": "Save and retry",
|
|
886
|
+
"mcp.token.hint": "Plaintext secrets need the matching access token: set token in the host config (or the DSH_PLUGIN_TOOL_MANAGEMENT_TOKEN env var), restart DSH, then enter the same value here.",
|
|
887
|
+
"mcp.btn.compact": "Tidy patch", "mcp.btn.compact.title": "Clear the stale on/off records left in the patch file (each server's enabled/disabled state is unchanged)",
|
|
888
|
+
"mcp.compact.title": "Tidy the patch file", "mcp.compact.desc": "The config accumulates stale on/off records — leftovers from switching the same server on and off over time. This clears them so the file stays short and readable.",
|
|
889
|
+
"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
890
|
"mcp.detail.entryId": "Entry id", "mcp.detail.status": "Status", "mcp.detail.registered": "This server is registered in the loader.",
|
|
712
|
-
"mcp.detail.note": "Note (
|
|
891
|
+
"mcp.detail.note": "Note (injected into the system prompt and sent to the model)", "mcp.detail.note.placeholder": "e.g. fall back to B when A is unavailable", "mcp.detail.note.save": "Save note",
|
|
713
892
|
"mcp.detail.tools": "Tools ({count})",
|
|
714
893
|
"mcp.field.headersShort": "Headers", "mcp.field.envShort": "Environment",
|
|
715
894
|
"mcp.tools.loading": "Fetching the tool list…", "mcp.tools.loadFailed": "Could not load tools: ", "mcp.tools.none": "This server has no registered tools.",
|
|
@@ -719,11 +898,11 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
719
898
|
"root.dsh": "DSH skills", "root.hub": "Imported skills", "root.agents": "Shared Agent", "root.ccswitch": "CC Switch", "root.projectDsh": "Project DSH", "root.projectAgents": "Project Agent", "root.codex": "Codex", "root.claude": "Claude", "root.gemini": "Gemini", "root.opencode": "OpenCode", "root.cursor": "Cursor",
|
|
720
899
|
"memory.title": "Memories",
|
|
721
900
|
"tabs.scenes": "Scenes", "tabs.skills": "Skills", "tabs.subagents": "Subagents", "tabs.prompts": "Prompts", "tabs.memory": "Memories", "tabs.sessions": "Sessions", "tabs.compat": "Host",
|
|
722
|
-
"compat.title": "Host compatibility", "compat.desc": "How this plugin attaches to
|
|
901
|
+
"compat.title": "Host compatibility", "compat.desc": "How this plugin attaches to DSH (read-only).",
|
|
723
902
|
"compat.state.ok": "All good", "compat.state.degraded": "Degraded", "compat.state.blocked": "Needs attention",
|
|
724
903
|
"compat.checking": "Checking…", "compat.recheck": "Check again", "compat.empty": "Not checked yet", "compat.failed": "Check failed",
|
|
725
904
|
"compat.summary.ok": "host {version} · capabilities {ok}/{total} · all usable", "compat.summary.degraded": "host {version} · capabilities {ok}/{total} · {count} degraded",
|
|
726
|
-
"compat.host": "Host version", "compat.peer": "
|
|
905
|
+
"compat.host": "Host version", "compat.peer": "Requires", "compat.verified": "Verified against", "compat.verified.note": "the release this plugin was tested with",
|
|
727
906
|
"compat.caps": "Usable capabilities", "compat.caps.all": "all present", "compat.caps.degraded": "{count} unavailable",
|
|
728
907
|
"compat.caps.substituted": "gaps covered by the plugin",
|
|
729
908
|
"compat.ops": "Host action availability", "compat.ops.hint": "which route each action takes", "compat.ops.disabled": "{count} action(s) are unavailable right now (the affected buttons are disabled and explain why)",
|
|
@@ -737,35 +916,40 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
737
916
|
"compat.fallback.native": "Falls back to native entry", "compat.fallback.blocked": "Affected buttons disabled",
|
|
738
917
|
"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
918
|
"compat.blockers": "Blockers (fix these)",
|
|
919
|
+
"compat.reach": "Preset injection reach", "compat.reach.hint": "the panel says injected — does the model actually see it?",
|
|
920
|
+
"compat.reach.mark.memory": "Memory", "compat.reach.mark.agentsMd": "Prompt", "compat.reach.mark.skill": "Skills", "compat.reach.mark.mcp": "MCP", "compat.reach.mark.subagent": "Subagents",
|
|
921
|
+
"compat.reach.default": "default",
|
|
922
|
+
"compat.reach.whyUnknown": "cannot tell", "compat.reach.broken": "preset broken",
|
|
923
|
+
"compat.reach.tools": "This plugin's model tools (including subagent_list / subagent_run) and MCP are host-plane and preset-independent: callable under every preset.",
|
|
740
924
|
"compat.hint.doctor": "Command-line check:",
|
|
741
|
-
"scenes.title": "Scenes", "scenes.desc": "Manage scenes: preset
|
|
925
|
+
"scenes.title": "Scenes", "scenes.desc": "Manage scenes: preset combinations of MCP, skills, subagents and memories, switch with one click.",
|
|
742
926
|
"scenes.stat.total": "scene(s)", "scenes.stat.active": "enabled", "scenes.stat.archives": "with a profile",
|
|
743
927
|
"scenes.noDesc": "No description yet",
|
|
744
928
|
"scenes.mode.active": "Active mode: {name}", "scenes.mode.profile": "Profile: {parts}", "scenes.mode.noProfile": "This scene has no profile yet",
|
|
745
|
-
"scenes.
|
|
929
|
+
"scenes.empty": "No dedicated scenes yet — global memories are injected without one; create a scene when you need to switch MCP servers, skills or personas.",
|
|
746
930
|
"scenes.field.desc.limit": "Up to {count} characters; longer text is clipped on the card.",
|
|
747
931
|
"scenes.profile.mcp": "{count} MCP", "scenes.profile.skills": "{count} skill(s)", "scenes.profile.subagents": "{count} subagent(s)", "scenes.profile.memories": "{count} memor(ies)",
|
|
748
932
|
"scenes.mcp.hint": "Add the MCP section first, then check the servers to enable (stopped ones included); use Pick tools to narrow to specific tools.",
|
|
749
933
|
"scenes.seg.selectAll": "Select all", "scenes.seg.clear": "Clear", "scenes.seg.checked": "{checked}/{total} selected",
|
|
750
934
|
"scenes.archive.sectionOff": "not defined",
|
|
751
|
-
"scenes.archive.summary": "Profile: {mcp} MCP server(s) · {skills} skill(s) · {subagents} subagent(s)
|
|
752
|
-
"scenes.archive.note": "
|
|
935
|
+
"scenes.archive.summary": "Profile: {mcp} MCP server(s) · {skills} skill(s) · {subagents} subagent(s)",
|
|
936
|
+
"scenes.archive.note": "An undefined section leaves that domain untouched; a defined section switches both ways: checked items are enabled, unchecked ones disabled. MCP really starts/stops the server process; skills toggle the source they live in.",
|
|
753
937
|
"scenes.mcp.noServers": "No MCP servers to pick from yet", "scenes.mcp.toolCount": "{count} tool(s)", "scenes.skills.empty": "No skills to pick from yet",
|
|
754
938
|
"scenes.mcp.allTools": "all tools", "scenes.mcp.pickedCount": "{count} tools picked", "scenes.mcp.notRunning": "not running", "scenes.mcp.pickTools": "Pick tools",
|
|
755
939
|
"scenes.mcp.toolsOf": "Tool details", "scenes.mcp.back": "Back", "scenes.mcp.noTools": "No tools listed for this server (not running or no tools)", "scenes.mcp.drillHint": "Checked = enabled in this scene; unchecked = disabled. A whole-server check defaults to all tools.",
|
|
756
940
|
"scenes.skills.hint": "Add the skills section first, then check the skills enabled in this scene.", "scenes.subagents.hint": "Add the subagent section first, then check the personas callable in this scene.", "scenes.subagents.empty": "No personas yet — create one on the Subagents page.",
|
|
757
|
-
"scenes.filter.skills": "Filter skills
|
|
758
|
-
|
|
941
|
+
"scenes.filter.skills": "Filter skills / directories", "scenes.filter.subagents": "Filter personas (name or description)", "scenes.filter.servers": "Filter servers",
|
|
942
|
+
|
|
759
943
|
"scenes.mem.title": "Memories", "scenes.mem.search": "Filter memories in this scene (name or description)",
|
|
760
944
|
"scenes.mem.noMatch": "Nothing matches", "scenes.mem.emptyScene": "This scene has no memories yet",
|
|
761
945
|
"memory.mode.current": "Active mode", "memory.mode.exit": "Exit mode",
|
|
762
946
|
"memory.archive.edit": "Profile", "memory.archive.title": "Scene profile",
|
|
763
947
|
"memory.archive.tools": "MCP tools", "memory.archive.skills": "Skills", "memory.archive.subagents": "Subagent binding", "memory.archive.memories": "Memories",
|
|
764
|
-
"memory.archive.addTools": "+ Tools", "memory.archive.addSkills": "+ Skills", "memory.archive.addSubagents": "+ Subagents",
|
|
948
|
+
"memory.archive.addTools": "+ Tools", "memory.archive.addSkills": "+ Skills", "memory.archive.addSubagents": "+ Subagents",
|
|
765
949
|
"memory.archive.removeSection": "Remove section", "memory.archive.save": "Save to scene",
|
|
766
950
|
"memory.archive.emptySection": "Section defined with nothing checked = all disabled", "memory.archive.emptySubagents": "Section defined with nothing checked = no restriction (all personas allowed)", "memory.archive.stale": "Stale entries (no longer exist, skipped): {items}",
|
|
767
|
-
"memory.result.archiveSaved": "Scene profile saved: {name}", "memory.result.modeSet": "Entered mode: {name}", "memory.result.modeExited": "Exited mode",
|
|
768
|
-
"memory.desc": "Manage
|
|
951
|
+
"memory.result.archiveSaved": "Scene profile saved: {name}", "memory.result.modeSet": "Entered mode: {name}", "memory.result.modeSwitched": "{mcp} server(s) / {skills} source(s) switched", "memory.result.modeExited": "Exited mode",
|
|
952
|
+
"memory.desc": "Manage memories: enabled memories enter the system prompt; enable them per scene.",
|
|
769
953
|
"memory.btn.refresh": "Refresh", "memory.btn.new": "New memory", "memory.btn.create": "Create", "memory.btn.save": "Save",
|
|
770
954
|
"memory.btn.diagnose": "Check", "memory.btn.delete.confirm": "Move to trash",
|
|
771
955
|
"memory.diagnose.title": "Memory checkup", "memory.diagnose.summary": "{rules} memories · {scenes} scenes · {issues} issues", "memory.diagnose.clean": "No issues found.",
|
|
@@ -779,10 +963,10 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
779
963
|
"memory.scene.global": "Global", "memory.scene.count": "{count} memories", "memory.scene.new": "New memory", "memory.scene.browse": "Pick existing",
|
|
780
964
|
"memory.scene.empty": "No memories in this scene yet",
|
|
781
965
|
"memory.scene.orphan": "No scene", "memory.scene.orphan.tag": "never injected", "memory.scene.orphan.hint": "These memories sit directly in the memories/ root and belong to no scene, so they never reach the system prompt. Move them into a scene directory (or into memories/global/ to make them “Global”).",
|
|
782
|
-
"memory.scene.edit": "Edit", "memory.scene.editTitle": "Edit scene", "memory.scene.editHelp": "
|
|
966
|
+
"memory.scene.edit": "Edit", "memory.scene.editTitle": "Edit scene", "memory.scene.editHelp": "The name is its memory folder name; renaming moves the folder, profile and bindings along. Memory files stay untouched.",
|
|
783
967
|
"memory.scene.field.name": "Scene name", "memory.scene.field.name.placeholder": "e.g. office",
|
|
784
|
-
"memory.scene.field.name.hint": "
|
|
785
|
-
"memory.scene.field.name.lock": "
|
|
968
|
+
"memory.scene.field.name.hint": "The name is its top-level folder name; you can still change it later.",
|
|
969
|
+
"memory.scene.field.name.lock": "Renaming moves its memory folder, scene profile and enable set along; memory files stay untouched.",
|
|
786
970
|
"memory.scene.field.desc": "Description (optional)", "memory.scene.field.desc.placeholder": "One line on what this scene is for",
|
|
787
971
|
"memory.search.placeholder": "Search name / description / scene", "memory.filter.all": "All scenes",
|
|
788
972
|
"memory.empty": "No memories yet — click “New memory”", "memory.empty.search": "No matching memories",
|
|
@@ -808,41 +992,50 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
808
992
|
"memory.trash.confirmTitle": "Delete forever?", "memory.trash.confirmDesc": "“{name}” is deleted forever and cannot be recovered",
|
|
809
993
|
"memory.btn.newScene": "New scene", "memory.btn.deleteScene": "Delete scene", "memory.btn.saveScene": "Save changes",
|
|
810
994
|
"memory.scene.createTitle": "New scene",
|
|
811
|
-
"memory.deleteScene.title": "Delete scene?", "memory.deleteScene.desc": "
|
|
812
|
-
"memory.result.sceneCreated": "Scene created: {name}", "memory.result.sceneUpdated": "Scene saved: {name}", "memory.result.sceneRemoved": "Scene deleted: {name}",
|
|
995
|
+
"memory.deleteScene.title": "Delete scene?", "memory.deleteScene.desc": "Moves “{name}” and every memory under it (including disabled ones) to the trash, restorable as one entry; a scene in use cannot be deleted.",
|
|
996
|
+
"memory.result.sceneCreated": "Scene created: {name}", "memory.result.sceneUpdated": "Scene saved: {name}", "memory.result.sceneRemoved": "Scene deleted: {name}", "memory.result.sceneRemoved.withMemories": "Scene deleted: {name} (with {count} memory file(s); restorable as one entry from the trash)",
|
|
813
997
|
"memory.table.name": "Memory name and description", "memory.table.tags": "Tags", "memory.table.status": "Status",
|
|
814
|
-
"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",
|
|
815
|
-
"subagents.title": "Subagents", "subagents.desc": "Manage
|
|
816
|
-
"prompts.desc": "Manage the global instruction baseline
|
|
998
|
+
"error.rules.invalidArgs": "Missing arguments: pass all:true (enable everything) or scenes:[...] (explicit narrowing)", "error.rules.invalidGroup": "Invalid scene/group name (non-empty, ≤64 chars, no path separators or < > : \" | ? *, must not start with a dot)", "error.rules.invalidName": "Invalid memory name (non-empty, ≤64 chars, no / \\ < > : \" | ? *, must not start with a dot)", "error.rules.descriptionRequired": "Description is required", "error.rules.descriptionTooLong": "Description is too long (max 500 chars)", "error.rules.bodyRequired": "Body is required", "error.rules.tooLarge": "Rule content is too large", "error.rules.shadowed": "Rule is shadowed by a bundle of the same name", "error.rules.notFound": "Rule not found", "error.rules.budgetExceeded": "Scene memory section exceeds its budget", "error.rules.ioFailed": "File operation failed", "error.rules.nameTaken": "That name is already taken", "error.rules.sceneNotEmpty": "Scene is not empty; cannot be deleted", "error.rules.notBundle": "This memory is flat (a single file) and cannot carry attachments", "error.rules.noFiles": "No files selected", "error.rules.emptyFile": "Empty file", "error.rules.fileTooLarge": "File too large (max {limit} MB each)", "error.rules.tooManyFiles": "At most {limit} files per upload", "error.rules.sceneInMode": "This scene is the active mode; exit the mode before deleting it",
|
|
999
|
+
"subagents.title": "Subagents", "subagents.stat.total": " subagents", "subagents.stat.limited": " tool-restricted", "subagents.desc": "Manage subagents: create and import personas; a persona's body is the subagent's system prompt.",
|
|
1000
|
+
"prompts.desc": "Manage prompts: preset the global instruction baseline; applying writes AGENTS.md and takes effect on the next turn.",
|
|
817
1001
|
"sessions.desc": "Manage archived sessions: restore or delete them permanently; expired ones are cleaned up automatically.",
|
|
818
|
-
"subagents.new": "New
|
|
1002
|
+
"subagents.new": "New subagent", "subagents.empty": "No subagents yet — click “New subagent” to create the first one.",
|
|
819
1003
|
"subagents.create": "New persona", "subagents.edit": "Edit persona",
|
|
820
1004
|
"subagents.field.name": "Persona name",
|
|
821
1005
|
"subagents.field.description": "Description", "subagents.field.description.placeholder": "e.g. Senior Java engineer",
|
|
822
1006
|
"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
1007
|
"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.
|
|
1008
|
+
"subagents.field.modes": "Tool limits (per agent preset)",
|
|
1009
|
+
"subagents.field.modes.hint": "Only the row for the current preset applies; other presets keep their full tool set.",
|
|
1010
|
+
"subagents.mode.startAllow": "Enable allowlist", "subagents.mode.startDeny": "Enable denylist", "subagents.mode.stopAllow": "Disable allowlist", "subagents.mode.stopDeny": "Disable denylist",
|
|
1011
|
+
"subagents.mode.off": "no restriction", "subagents.mode.empty": "enabled with nothing picked (= no restriction)",
|
|
1012
|
+
"subagents.mode.allowOn": "allowlist {count}", "subagents.mode.denyOn": "denylist {count}",
|
|
1013
|
+
"subagents.mode.allowEmpty": "Nothing picked = no restriction (every tool of this preset stays available)",
|
|
1014
|
+
"subagents.mode.denyEmpty": "Nothing picked = no restriction (every tool of this preset stays available)",
|
|
1015
|
+
"subagents.mode.noTools": "Could not read this preset's tool list",
|
|
1016
|
+
"subagents.mode.broken": "This preset is broken; its tool list is unreadable",
|
|
1017
|
+
"subagents.mode.noPresets": "The host returned no agent-preset roster",
|
|
1018
|
+
"subagents.legacy.note": "This file still carries the old-style tool limits (allowlist {allow} / denylist {deny}); they apply to every preset.",
|
|
1019
|
+
"subagents.legacy.convert": "Convert into a preset row",
|
|
1020
|
+
"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).",
|
|
1021
|
+
"subagents.tools.filter": "Filter tool names", "subagents.tools.remove": "Remove",
|
|
1022
|
+
"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.",
|
|
1023
|
+
"preset.name.standard": "Standard mode", "preset.name.ptc": "PTC mode", "preset.name.minimal": "Minimal mode", "preset.name.cordis": "Creator mode",
|
|
1024
|
+
"preset.short.standard": "Standard", "preset.short.ptc": "PTC", "preset.short.minimal": "Minimal", "preset.short.cordis": "Creator",
|
|
830
1025
|
"subagents.field.body": "Persona prompt", "subagents.field.body.placeholder": "Describe the persona's role, responsibilities, and working style…",
|
|
831
1026
|
"subagents.result.saved": "Persona saved: {name}", "subagents.result.deleted": "Persona deleted: {name}",
|
|
832
|
-
"subagents.delete.title": "Delete persona?", "subagents.delete.desc": "
|
|
833
|
-
"subagents.import": "Import", "subagents.import.title": "Import
|
|
1027
|
+
"subagents.delete.title": "Delete persona?", "subagents.delete.desc": "Moves the persona file “{name}” to the trash (restorable from the Trash button on the Subagents page).",
|
|
1028
|
+
"subagents.import": "Import subagents", "subagents.import.title": "Import subagents", "subagents.toggle": "Toggle subagent", "subagents.disabled": "Disabled", "subagents.disabled.hint": "Not injected into the system prompt and hidden from subagent_list / subagent_run; the file is kept and can be re-enabled anytime.",
|
|
834
1029
|
"subagents.result.imported": "Imported {count} persona(s): {names}",
|
|
835
1030
|
"memory.import": "Import memories", "memory.import.title": "Import memories", "memory.import.scene": "Import into scene",
|
|
836
|
-
"memory.import.sceneHint": "Blank = global; directory names inside the zip become scene names.",
|
|
837
|
-
"memory.import.hint": "Accepts .md or .zip (multi-select or drag in): one .md = one memory; <scene>/<name>/SKILL.md inside a zip imports as a bundle with sibling files as attachments; existing names are skipped.",
|
|
838
1031
|
"memory.result.imported": "Imported {count} memory/memories: {names}",
|
|
839
1032
|
"memory.import.scenesCreated": "Scenes created for them: {names}",
|
|
840
1033
|
"memory.scene.global.hint": "Global",
|
|
841
|
-
|
|
1034
|
+
"import.clear": "Clear", "import.submit": "Import",
|
|
842
1035
|
"import.none": "Nothing was imported (all skipped)", "import.skipped": "Skipped: {items}",
|
|
843
1036
|
"error.import.noFiles": "No files selected",
|
|
844
1037
|
"nav.title": "Tools",
|
|
845
|
-
"agm.
|
|
1038
|
+
"agm.stat.total": " prompts", "agm.stat.active": " active", "agm.loading": "Loading…", "agm.empty": "No prompts yet — click “New prompt” to create one", "agm.btn.delete.blocked": "The preset currently in effect cannot be deleted; enable another scene, rebind the prompt, or apply a different preset first.", "agm.active": "Active", "agm.active.hint.scene": "Currently active: the prompt preset bound to scene “{scene}”", "agm.active.hint.file": "Currently active: AGENTS.md is exactly this preset", "agm.file.applied": "In AGENTS.md", "agm.file.applied.hint": "AGENTS.md currently holds this one; the enabled scene binds a different preset, which now takes effect", "agm.apply.hint": "Writes AGENTS.md; the host re-reads that file every turn, so it takes effect on the next turn", "agm.btn.reapply": "Apply again", "agm.btn.apply": "Apply", "agm.applyModal.title": "Apply preset", "agm.edit.title": "Edit preset", "agm.remove.title": "Delete preset", "agm.btn.edit": "Edit", "agm.btn.delete": "Delete", "agm.btn.new": "New prompt", "agm.btn.import": "Import prompts", "agm.result.imported": "Imported {count} preset(s): {names}", "agm.btn.save": "Save", "agm.btn.create": "Create", "agm.btn.confirmRemove": "Delete", "agm.btn.confirmApply": "Apply", "agm.field.id": "id (directory name)", "agm.field.id.hint": "Letters, spaces and dots are fine; no slashes or : * ? < >; up to 64 chars. Changing it renames the directory.", "agm.field.id.placeholder": "e.g. work-baseline", "agm.field.content": "Content", "agm.field.content.placeholder": "Write the preset body (Markdown) here; leave blank to start from the empty template", "agm.field.copyFrom": "Or copy from an existing preset", "agm.field.copyFrom.none": "Do not copy (empty template)", "agm.field.desc": "Description (for humans only)", "agm.field.desc.placeholder": "What this preset is for and when to use it", "agm.result.renamed": "Saved; id is now “{id}”", "agm.result.rebound": " ({count} scene binding(s) updated)", "agm.remove.suffix": "? This cannot be undone.", "agm.apply.prefix": "Write the contents of ", "agm.apply.suffix": " into ~/.dsh/AGENTS.md.", "agm.apply.note": "Takes effect on the next turn; the current content is backed up to __last-applied__ (last 5 kept)", "hist.err.load": "Load failed", "hist.err.unarchive": "Restore failed", "hist.err.delete": "Delete failed", "hist.err.retention": "Update failed", "hist.err.import": "Import failed", "hist.err.sessions": "Failed to load sessions", "hist.err.export": "Export failed", "hist.err.archive": "Archive failed", "hist.retention.forever": "Keep forever", "hist.retention.days": "{count} days", "hist.arch.all": "All sessions", "hist.arch.archived": "Archived only", "hist.arch.live": "Not archived only", "hist.arch.missing": "Missing directory only", "hist.ws.all": "All workspaces", "hist.ungrouped": "Ungrouped", "hist.group.removed": "Removed", "hist.group.removed.hint": "This directory has no DSH workspace registration right now; the group is rebuilt from session directories and rejoins automatically once registered again", "hist.btn.registerWs": "Register", "hist.btn.registerWs.hint": "Register this directory again and attach its existing sessions back to that group (workspace accounting only — no file or session is moved or deleted)", "hist.register.title": "Register again?", "hist.register.body": "A workspace registration will be created in DSH for “{title}”, and the sessions under this directory will be attached back to it (restoring their grouping). This writes workspace accounting only — no file or session is moved or deleted.", "hist.register.confirm": "Register", "hist.register.done.title": "Registered", "hist.register.done.body": "Workspace “{title}” is registered; archived sessions under this directory join that group after a refresh.", "hist.register.done.attached": "Attached {count} existing session(s) under this directory back to the workspace.", "hist.register.done.skipped": "{count} session(s) could not be attached: {reason}", "hist.err.register": "Registration failed", "scenes.field.prompt": "Prompt preset", "scenes.agents.note.applied": "wrote “{id}” into AGENTS.md", "scenes.agents.note.restored": "restored the AGENTS.md baseline from before the scene", "scenes.agents.note.error": "AGENTS.md was not written: {reason}", "scenes.field.prompt.hint": "While this scene is enabled, its preset is the active global baseline. Defaults to the one currently in effect.", "scenes.prompt.activeTag": "in effect", "scenes.prompt.noPresets": "No prompt presets yet — create one on the Prompts page.", "scenes.prompt.bound": "Prompt: {id}", "scenes.prompt.live": "Prompt active: {id}", "scenes.prompt.missing": "Preset missing: {id}", "scenes.prompt.tag.hint": "The prompt preset bound to this scene", "scenes.enable.blocked": "Scene “{name}” is already enabled; turn it off first", "scenes.result.created.collapsed": "Scene “{name}” created and left off (the legacy “all enabled” default collapsed to a single choice)", "scenes.legacyAll": "{count} scenes are enabled at once (a leftover of the legacy “all enabled” default); only one scene besides “Global” may be enabled.", "scenes.legacyAll.fix": "Collapse to one", "agm.scene.tag": "In use by scene “{scene}”", "agm.scene.tag.hint": "Bound by that scene; takes effect with the scene switch", "agm.scene.missing": "Bound by scene “{scene}”, but the file is missing", "hist.restore.note.registered": "Restored {count} session(s); workspace “{title}” was registered again and the sessions are back in that group.", "hist.restore.note.attached": "Restored {count} session(s) and attached them to workspace “{title}”.", "hist.restore.note.skip": "Workspace ownership could not be restored for {count} session(s): {reason}", "hist.purge.title": "Delete forever?", "hist.purge.one": "Delete “{title}” and all of its records forever — this cannot be undone", "hist.purge.batch": "Delete the {count} selected sessions and all of their records forever — this cannot be undone", "hist.btn.restore": "Restore", "hist.btn.purge": "Delete forever", "hist.btn.confirmPurge": "Delete", "hist.selectGroup": "Select group {title}", "hist.group.count": "{count}", "hist.selectAll": "Select all", "hist.deselectAll": "Clear selection", "hist.btn.import": "Import sessions", "hist.btn.export": "Export sessions", "hist.selected": "{count} selected", "hist.btn.restoreSelected": "Restore selected ({count})", "hist.btn.deleteSelected": "Delete selected ({count})", "hist.stat.archived": " archived", "hist.stat.projects": " projects", "hist.stat.retention": " days kept", "hist.search.placeholder": "Search title / session id / project path", "hist.loading": "Loading…", "hist.empty.search": "No matching archived sessions", "hist.empty.none": "No archived sessions yet", "hist.import.cwd": "Project directory (optional)", "hist.import.cwd.placeholder": "Absolute path, optional (the session goes to Ungrouped)", "hist.import.drop": "Choose or drop a conversation file", "hist.import.done": "Created session {id} ({count} messages); continue it from the DSH session list.", "hist.export.hint": "Export the selected sessions as transcript files that can be imported again", "hist.export.dir": "Export directory (absolute path, created automatically)", "hist.export.browse": "Browse and choose a folder", "hist.btn.browse": "Choose", "hist.export.pickDir": "Choose export directory", "hist.export.format": "Format", "hist.export.scope": "Session scope", "hist.export.workspace": "Workspace", "hist.export.sessions": "Sessions ({count})", "hist.export.loading": "Loading sessions…", "hist.cwd.missing": "⚠ The workspace directory no longer exists: ", "hist.tag.archived": "Archived", "hist.tag.live": "Not archived", "hist.export.empty": "No matching sessions", "hist.result.exported": "Exported {count} session(s) to {dir}", "hist.result.skipped": "; skipped {count} ({ids})", "hist.result.archived": "Archived {count} session(s) to History; manage them on the History page", "hist.btn.archiveSelected": "Archive selected", "hist.btn.archiveSelected.title": "Move the selected sessions into History so retention applies to them", "hist.export.busy": "Working…", "hist.btn.exportSelected": "Export {count} session(s)", "hist.error.title": "Operation failed", "hist.error.unknown": "Unknown error",
|
|
846
1039
|
}
|
|
847
1040
|
};
|
|
848
1041
|
|
|
@@ -900,14 +1093,14 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
900
1093
|
? { kind: 'warn', text: mt('mcp.msg.warn', { warning: res.warning }) }
|
|
901
1094
|
: { kind: 'ok', text: mt('mcp.msg.ok') }
|
|
902
1095
|
)
|
|
903
|
-
const emptyForm = () => ({ serverName: '', transport: 'streamable-http', url: '', command: '', args: '', headers: '', env: '', level: '
|
|
1096
|
+
const emptyForm = () => ({ serverName: '', transport: 'streamable-http', url: '', command: '', args: '', headers: '', env: '', level: 'global' })
|
|
904
1097
|
const kvToLines = (obj) => (obj ? Object.keys(obj).map((k) => k + '=' + obj[k]).join('\n') : '')
|
|
905
1098
|
// 级别筛选选项:必须在**渲染时**取词。原来写成模块级常量,apply() 早于 locale
|
|
906
1099
|
// 注册,导致英文界面下这四项永远显示中文(且再也翻不过来)。
|
|
907
1100
|
const mcpLevelOptions = () => [
|
|
908
1101
|
{ value: '', label: mt('mcp.level.all') },
|
|
909
|
-
{ value: 'project', label: mt('mcp.level.project') },
|
|
910
1102
|
{ value: 'global', label: mt('mcp.level.global') },
|
|
1103
|
+
{ value: 'project', label: mt('mcp.level.project') },
|
|
911
1104
|
{ value: 'loader', label: mt('mcp.level.loader') },
|
|
912
1105
|
]
|
|
913
1106
|
const MCP_TRANSPORT_OPTIONS = [
|
|
@@ -919,16 +1112,17 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
919
1112
|
return s.length <= 4 ? '****' : s.slice(0, 4) + '****'
|
|
920
1113
|
}
|
|
921
1114
|
|
|
922
|
-
//
|
|
1115
|
+
// 顶部栏右端:GitHub 链接(issues 在仓库)+ 版本号(全界面唯一显示处)。
|
|
923
1116
|
function FeedbackLinks() {
|
|
924
1117
|
return React.createElement('div', { className: 'dsm-feedback-links' },
|
|
925
1118
|
React.createElement('a', { className: 'dsm-feedback-link', href: 'https://github.com/ouli-1242/dsh-plugin-tool-management', target: '_blank', rel: 'noreferrer', 'aria-label': t('link.project'), title: t('link.project') },
|
|
926
|
-
React.createElement(GithubMark16, null))
|
|
1119
|
+
React.createElement(GithubMark16, null)),
|
|
1120
|
+
React.createElement(VersionBadgeComponent, null))
|
|
927
1121
|
}
|
|
928
1122
|
|
|
929
1123
|
// MCP 服务页:与 Skills 页共用 dsm-* 设计语言(统计卡 / 筛选 / 分组卡片 / 模态框)。
|
|
930
1124
|
function MCPPage() {
|
|
931
|
-
const [state, setState] = React.useState({ loading: true, error: null, rows: [], paths: null, errors: [], warnings: [] })
|
|
1125
|
+
const [state, setState] = React.useState({ loading: true, error: null, rows: [], paths: null, errors: [], warnings: [], revealError: null })
|
|
932
1126
|
const [msg, setMsg] = React.useState(null)
|
|
933
1127
|
const [busy, setBusy] = React.useState(null)
|
|
934
1128
|
const [query, setQuery] = React.useState('')
|
|
@@ -941,13 +1135,15 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
941
1135
|
const [reveal, setReveal] = React.useState(false)
|
|
942
1136
|
const [settings, setSettings] = React.useState(null)
|
|
943
1137
|
const [noteDraft, setNoteDraft] = React.useState('')
|
|
1138
|
+
const [tokenDraft, setTokenDraft] = React.useState('')
|
|
1139
|
+
const [compactConfirm, setCompactConfirm] = React.useState(false)
|
|
944
1140
|
const [, setTick] = React.useState(0)
|
|
945
1141
|
|
|
946
1142
|
const refresh = (withReveal, onRows) => {
|
|
947
1143
|
const useReveal = withReveal === undefined ? reveal : withReveal === true
|
|
948
|
-
// 明文视图走独立的 mcpm-reveal op(host
|
|
949
|
-
// mcpm-list
|
|
950
|
-
|
|
1144
|
+
// 明文视图走独立的 mcpm-reveal op(host 端要求带对的访问令牌),
|
|
1145
|
+
// mcpm-list 始终脱敏、无令牌也能渲染页面。
|
|
1146
|
+
const applyRows = (res, revealError) => {
|
|
951
1147
|
const rows = (res && res.rows) || []
|
|
952
1148
|
setState({
|
|
953
1149
|
loading: false,
|
|
@@ -956,9 +1152,20 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
956
1152
|
paths: (res && res.paths) || null,
|
|
957
1153
|
errors: (res && res.errors) || [],
|
|
958
1154
|
warnings: (res && res.warnings) || [],
|
|
1155
|
+
revealError: revealError || null,
|
|
1156
|
+
locked: (res && res.anyLocked) === true,
|
|
959
1157
|
})
|
|
960
1158
|
if (onRows) onRows(rows)
|
|
961
|
-
}
|
|
1159
|
+
}
|
|
1160
|
+
apiCall(useReveal ? 'mcpm-reveal' : 'mcpm-list', {}).then((res) => {
|
|
1161
|
+
// 明文被拒(未配令牌 / 令牌不对):退回脱敏列表,把原因与令牌入口单独显示。
|
|
1162
|
+
// 直接落进 state.error 会把整页变成错误态 —— "看不了明文"不等于"页面坏了"。
|
|
1163
|
+
if (useReveal && res && res.ok === false) {
|
|
1164
|
+
setReveal(false)
|
|
1165
|
+
return apiCall('mcpm-list', {}).then((masked) => applyRows(masked, { code: res.code, text: res.error }))
|
|
1166
|
+
}
|
|
1167
|
+
applyRows(res, null)
|
|
1168
|
+
}).catch((e) => setState({ loading: false, error: String((e && e.message) || e), rows: [], paths: null, errors: [], warnings: [], revealError: null, locked: false }))
|
|
962
1169
|
}
|
|
963
1170
|
// The polling tick must always call the newest refresh closure, while
|
|
964
1171
|
// the interval itself only changes when the saved settings change.
|
|
@@ -1063,6 +1270,17 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1063
1270
|
if (fresh) setDetail(Object.assign({}, detail, { row: fresh }))
|
|
1064
1271
|
})
|
|
1065
1272
|
}
|
|
1273
|
+
// 明文被拒后的令牌入口:填对令牌 → 存 localStorage(随每个请求以 x-dsh-token 发出)
|
|
1274
|
+
// → 重试明文。宿主没配令牌时这一步不会成功,面板里的提示写了该怎么配。
|
|
1275
|
+
const applyToken = () => {
|
|
1276
|
+
if (!setAccessToken(tokenDraft)) return
|
|
1277
|
+
setReveal(true)
|
|
1278
|
+
refresh(true, (rows) => {
|
|
1279
|
+
if (!detail) return
|
|
1280
|
+
const fresh = rows.find((item) => item.id === detail.row.id && item.level === detail.row.level)
|
|
1281
|
+
if (fresh) setDetail(Object.assign({}, detail, { row: fresh }))
|
|
1282
|
+
})
|
|
1283
|
+
}
|
|
1066
1284
|
const saveNote = () => {
|
|
1067
1285
|
const row = detail && detail.row
|
|
1068
1286
|
if (!row) return
|
|
@@ -1080,6 +1298,20 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1080
1298
|
setConfirmRow(null)
|
|
1081
1299
|
if (row) run('mcpm-remove', { id: row.id, level: row.level }, row.id + ':remove')
|
|
1082
1300
|
}
|
|
1301
|
+
// 「整理补丁」:删掉多余的启停覆盖块(生效值不变,只删被盖住的块与纯 no-op)。
|
|
1302
|
+
// 成功文案带真实删除数量;一条都没删时如实说"已经是干净的",而不是笼统的"成功"。
|
|
1303
|
+
const doCompact = () => {
|
|
1304
|
+
setCompactConfirm(false)
|
|
1305
|
+
setMsg(null)
|
|
1306
|
+
setBusy('compact')
|
|
1307
|
+
apiCall('mcpm-compact', {}).then((res) => {
|
|
1308
|
+
if (res && res.ok) {
|
|
1309
|
+
const n = Number(res.removed || 0)
|
|
1310
|
+
setMsg({ kind: 'ok', text: n > 0 ? mt('mcp.compact.done', { count: n }) : mt('mcp.compact.clean') })
|
|
1311
|
+
refresh()
|
|
1312
|
+
} else setMsg({ kind: 'err', text: (res && res.error) || mt('mcp.msg.failed') })
|
|
1313
|
+
}).catch((e) => setMsg({ kind: 'err', text: String((e && e.message) || e) })).then(() => setBusy(null))
|
|
1314
|
+
}
|
|
1083
1315
|
const openDetail = (row) => {
|
|
1084
1316
|
setNoteDraft(row.notes || '')
|
|
1085
1317
|
setDetail({ row, loading: true, error: null, tools: [] })
|
|
@@ -1144,7 +1376,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1144
1376
|
const renderRow = (row) => {
|
|
1145
1377
|
const status = liveStatus(row)
|
|
1146
1378
|
const hint = liveHint(row)
|
|
1147
|
-
|
|
1379
|
+
// 场景锁定期间整页只读(开关/编辑/删除一并消失,重启也归入冻结)。
|
|
1380
|
+
const editable = row.level !== 'loader' && state.locked !== true
|
|
1148
1381
|
return React.createElement('div', { key: row.id, className: 'dsm-row' },
|
|
1149
1382
|
React.createElement('div', { className: 'dsm-main' },
|
|
1150
1383
|
React.createElement('div', { className: 'dsm-name' }, row.serverName),
|
|
@@ -1170,7 +1403,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1170
1403
|
return React.createElement('section', { key: group.key, className: 'dsm-source' },
|
|
1171
1404
|
React.createElement('div', { className: 'dsm-source-head' },
|
|
1172
1405
|
React.createElement('button', { type: 'button', className: 'dsm-source-head-main', 'aria-expanded': open, onClick: () => setCollapsed(Object.assign({}, collapsed, { [group.key]: !open })) },
|
|
1173
|
-
React.createElement('span', { className: 'dsm-source-title' }, group.title),
|
|
1406
|
+
React.createElement('span', { className: 'dsm-source-title', title: group.title }, group.title),
|
|
1174
1407
|
React.createElement('span', { className: 'dsm-count' }, mt('mcp.servers.count', { count: groupRows.length })),
|
|
1175
1408
|
group.path ? React.createElement('span', { className: 'dsm-path', title: group.path }, group.path) : null)),
|
|
1176
1409
|
open ? React.createElement('div', { className: 'dsm-source-body' },
|
|
@@ -1198,7 +1431,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1198
1431
|
React.createElement('div', { className: 'dsm-tools' }, (detail.tools || []).map((tool) => React.createElement('div', { className: 'dsm-tool' + (tool.enabled === false ? ' dsm-tool-off' : ''), key: tool.name },
|
|
1199
1432
|
React.createElement('div', { className: 'dsm-tool-name-row' },
|
|
1200
1433
|
React.createElement('div', { className: 'dsm-tool-name' }, tool.name),
|
|
1201
|
-
React.createElement(Switch, { on: tool.enabled !== false, disabled: busy !== null, label: mt('mcp.toggleTool') + ' ' + tool.name, onClick: () => toggleTool(detail.row, tool) })),
|
|
1434
|
+
React.createElement(Switch, { on: tool.enabled !== false, disabled: busy !== null || state.locked === true, label: mt('mcp.toggleTool') + ' ' + tool.name, onClick: () => toggleTool(detail.row, tool) })),
|
|
1202
1435
|
tool.description ? React.createElement('div', { className: 'dsm-tool-desc' }, tool.description) : null,
|
|
1203
1436
|
(tool.parameters && tool.parameters.length > 0) ? React.createElement('div', { className: 'dsm-tool-params' }, tool.parameters.map((param) => React.createElement('div', { className: 'dsm-tool-param', key: param.key },
|
|
1204
1437
|
React.createElement('span', { className: 'dsm-tool-param-key' }, param.key + (param.required ? ' *' : '')),
|
|
@@ -1231,8 +1464,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1231
1464
|
React.createElement(SourceSelect, {
|
|
1232
1465
|
value: formModal.level,
|
|
1233
1466
|
options: [
|
|
1234
|
-
{ value: 'project', label: mt('mcp.field.level.project', { path: state.paths ? state.paths.project : 'profiles/*/cordis.patch.yml' }) },
|
|
1235
1467
|
{ value: 'global', label: mt('mcp.field.level.global', { path: state.paths ? state.paths.global : '~/.dsh/cordis.patch.yml' }) },
|
|
1468
|
+
{ value: 'project', label: mt('mcp.field.level.project', { path: state.paths ? state.paths.project : 'profiles/*/cordis.patch.yml' }) },
|
|
1236
1469
|
],
|
|
1237
1470
|
onChange: (value) => setFormValue('level', value),
|
|
1238
1471
|
})),
|
|
@@ -1287,13 +1520,24 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1287
1520
|
detailStatus.text + ':' + (detailHint || mt('mcp.detail.registered')))),
|
|
1288
1521
|
React.createElement('div', { className: 'dsm-detail-section' },
|
|
1289
1522
|
React.createElement('div', { className: 'dsm-detail-title' }, mt('mcp.detail.note')),
|
|
1290
|
-
React.createElement('textarea', { className: 'dsm-control dsm-textarea-sm', value: noteDraft, placeholder: mt('mcp.detail.note.placeholder'), onChange: (ev) => setNoteDraft(ev.target.value) }),
|
|
1523
|
+
React.createElement('textarea', { className: 'dsm-control dsm-textarea-sm', value: noteDraft, maxLength: 200, placeholder: mt('mcp.detail.note.placeholder'), onChange: (ev) => setNoteDraft(ev.target.value) }),
|
|
1291
1524
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
1292
1525
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null || noteDraft.trim() === String(detail.row.notes || ''), onClick: saveNote }, mt('mcp.detail.note.save')))),
|
|
1293
1526
|
React.createElement('div', { className: 'dsm-detail-section' },
|
|
1294
1527
|
React.createElement('div', { className: 'dsm-detail-title' }, mt('mcp.detail.tools', { count: detail.loading ? '…' : (detail.tools || []).length })),
|
|
1295
1528
|
toolListNode))
|
|
1296
1529
|
|
|
1530
|
+
const compactNode = compactConfirm && React.createElement(Modal, {
|
|
1531
|
+
key: 'mcp-compact',
|
|
1532
|
+
title: mt('mcp.compact.title'),
|
|
1533
|
+
closeLabel: mt('btn.close'),
|
|
1534
|
+
onClose: () => setCompactConfirm(false),
|
|
1535
|
+
},
|
|
1536
|
+
React.createElement('p', { className: 'dsm-desc' }, mt('mcp.compact.desc')),
|
|
1537
|
+
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
1538
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: () => setCompactConfirm(false) }, mt('btn.close')),
|
|
1539
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: busy !== null, onClick: doCompact }, mt('mcp.compact.confirm'))))
|
|
1540
|
+
|
|
1297
1541
|
const confirmNode = confirmRow && React.createElement(Modal, {
|
|
1298
1542
|
key: 'mcp-remove',
|
|
1299
1543
|
title: mt('mcp.remove.title'),
|
|
@@ -1308,18 +1552,19 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1308
1552
|
React.createElement('div', { className: 'dsm-head' },
|
|
1309
1553
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
1310
1554
|
React.createElement('div', { className: 'dsm-title-row' },
|
|
1311
|
-
React.createElement('h2', { className: 'dsm-title' }, 'MCP'),
|
|
1312
|
-
VersionBadgeComponent ? React.createElement(VersionBadgeComponent, null) : null),
|
|
1555
|
+
React.createElement('h2', { className: 'dsm-title' }, 'MCP')),
|
|
1313
1556
|
React.createElement('p', { className: 'dsm-desc' }, mt('mcp.desc'))),
|
|
1314
1557
|
React.createElement('div', { className: 'dsm-actions' },
|
|
1315
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null || state.loading, onClick: () => refresh() }, mt('btn.refresh')),
|
|
1316
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: openAdd }, mt('mcp.btn.new')),
|
|
1317
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-
|
|
1318
|
-
|
|
1319
|
-
React.createElement('div', { className: 'dsm-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
React.createElement('
|
|
1558
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null || state.loading, onClick: () => refresh() }, (busy !== null || state.loading) ? mt('btn.refreshing') : mt('btn.refresh')),
|
|
1559
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: state.locked === true, onClick: openAdd }, mt('mcp.btn.new')),
|
|
1560
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary dsm-btn-danger', disabled: busy !== null || state.loading, title: mt('mcp.btn.compact.title'), disabled: busy !== null || state.loading || state.locked === true, onClick: () => setCompactConfirm(true) }, mt('mcp.btn.compact')))),
|
|
1561
|
+
state.locked === true ? React.createElement('div', { className: 'dsm-feedback dsm-warning', role: 'status' }, mt('lock.banner')) : null,
|
|
1562
|
+
React.createElement('div', { className: 'dsm-stat-row' },
|
|
1563
|
+
React.createElement('div', { className: 'dsm-summary' },
|
|
1564
|
+
[['total', summary.total, mt('mcp.stat.total')], ['enabled', summary.enabled, mt('mcp.stat.enabled')], ['tools', summary.tools, mt('mcp.stat.tools')]].map((item) =>
|
|
1565
|
+
React.createElement('div', { key: item[0], className: 'dsm-stat' },
|
|
1566
|
+
React.createElement('strong', null, item[1]), item[2]))),
|
|
1567
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null || state.loading || !summary.total, onClick: () => run('mcpm-set-all', { enabled: summary.enabled < summary.total }, 'setall') }, mt(summary.total > 0 && summary.enabled === summary.total ? 'mcp.btn.disableAll' : 'mcp.btn.enableAll'))),
|
|
1323
1568
|
React.createElement('div', { className: 'dsm-filters' },
|
|
1324
1569
|
React.createElement('input', { className: 'dsm-control dsm-search', value: query, 'aria-label': mt('mcp.search'), placeholder: mt('mcp.search.placeholder'), onChange: (ev) => setQuery(ev.target.value) }),
|
|
1325
1570
|
React.createElement('div', { className: 'dsm-source-filter' },
|
|
@@ -1329,9 +1574,22 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1329
1574
|
React.createElement(Notice, { kind: 'err', text: state.error }),
|
|
1330
1575
|
React.createElement(Notice, { kind: 'warn', text: (state.errors && state.errors.length > 0) ? mt('mcp.err.warnings') + state.errors.join(';') : null }),
|
|
1331
1576
|
React.createElement(Notice, { kind: 'warn', text: (state.warnings && state.warnings.length > 0) ? state.warnings.join(';') : null }),
|
|
1577
|
+
state.revealError ? React.createElement('div', { className: 'dsm-token-panel' },
|
|
1578
|
+
React.createElement(Notice, { kind: 'err', text: state.revealError.text }),
|
|
1579
|
+
// 令牌不对 → 给输入框;宿主根本没配令牌 → 输入框也没用,只给"该怎么配"的说明,
|
|
1580
|
+
// 免得摆一个按了必然失败的按钮。
|
|
1581
|
+
state.revealError.code === 'error.secret.badToken' ? React.createElement('div', { className: 'dsm-token-row' },
|
|
1582
|
+
React.createElement('input', {
|
|
1583
|
+
className: 'dsm-control', type: 'password', value: tokenDraft,
|
|
1584
|
+
placeholder: mt('mcp.token.placeholder'), 'aria-label': mt('mcp.token.label'),
|
|
1585
|
+
onChange: (ev) => setTokenDraft(ev.target.value),
|
|
1586
|
+
}),
|
|
1587
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: !tokenDraft.trim(), onClick: applyToken }, mt('mcp.token.save'))) : null,
|
|
1588
|
+
React.createElement('p', { className: 'dsm-help' }, mt('mcp.token.hint'))) : null,
|
|
1332
1589
|
groupsNode,
|
|
1333
1590
|
formModalNode,
|
|
1334
1591
|
detailNode,
|
|
1592
|
+
compactNode,
|
|
1335
1593
|
confirmNode)
|
|
1336
1594
|
}
|
|
1337
1595
|
|
|
@@ -1343,25 +1601,75 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1343
1601
|
var em = React.useState(null), editModal = em[0], setEditModal = em[1]
|
|
1344
1602
|
var cm = React.useState(null), createModal = cm[0], setCreateModal = cm[1]
|
|
1345
1603
|
var ac = React.useState(null), applyConfirm = ac[0], setApplyConfirm = ac[1]
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1604
|
+
// 回收站:null = 关闭;{loading, error, entries}
|
|
1605
|
+
var ts = React.useState(null), trash = ts[0], setTrash = ts[1]
|
|
1606
|
+
var tb = React.useState(false), trashBusy = tb[0], setTrashBusy = tb[1]
|
|
1607
|
+
// D14:导出弹窗(null = 关闭;{busy, result})。
|
|
1608
|
+
var xo = React.useState(null), exportState = xo[0], setExportState = xo[1]
|
|
1609
|
+
/** 打开/刷新回收站(每次操作后重读,界面永远显示磁盘上的真实条目)。 */
|
|
1610
|
+
function loadTrash(keepOpen) {
|
|
1611
|
+
var next = Object.assign({ loading: true, error: null, entries: [] }, keepOpen ? trash || {} : {})
|
|
1612
|
+
setTrash(next)
|
|
1613
|
+
apiCall('agentsmd-trash-list', {}).then(function (res) {
|
|
1614
|
+
if (res && res.ok) setTrash({ loading: false, error: null, entries: res.trash || [] })
|
|
1615
|
+
else setTrash({ loading: false, error: (res && res.error) || t('trash.loadFailed'), entries: [] })
|
|
1616
|
+
}).catch(function (e) { setTrash({ loading: false, error: String((e && e.message) || e), entries: [] }) })
|
|
1617
|
+
}
|
|
1618
|
+
function restorePreset(item) {
|
|
1619
|
+
setTrashBusy(true)
|
|
1620
|
+
apiCall('agentsmd-trash-restore', { id: item.id }).then(function (res) {
|
|
1621
|
+
setTrashBusy(false)
|
|
1622
|
+
if (res && res.ok) { setState(function (s) { return Object.assign({}, s, { notice: t('trash.restored', { name: item.name }) }) }); refresh(); loadTrash(true) }
|
|
1623
|
+
else setTrash(function (cur) { return Object.assign({}, cur, { error: (res && res.error) || t('mcp.msg.failed') }) })
|
|
1624
|
+
}).catch(function (e) { setTrashBusy(false); setTrash(function (cur) { return Object.assign({}, cur, { error: String((e && e.message) || e) }) }) })
|
|
1625
|
+
}
|
|
1626
|
+
function purgePreset(item) {
|
|
1627
|
+
setTrashBusy(true)
|
|
1628
|
+
apiCall('agentsmd-trash-delete', { id: item.id }).then(function (res) {
|
|
1629
|
+
setTrashBusy(false)
|
|
1630
|
+
if (res && res.ok) { setState(function (s) { return Object.assign({}, s, { notice: t('trash.purged', { name: item.name }) }) }); loadTrash(true) }
|
|
1631
|
+
else setTrash(function (cur) { return Object.assign({}, cur, { error: (res && res.error) || t('mcp.msg.failed') }) })
|
|
1632
|
+
}).catch(function (e) { setTrashBusy(false); setTrash(function (cur) { return Object.assign({}, cur, { error: String((e && e.message) || e) }) }) })
|
|
1633
|
+
}
|
|
1634
|
+
// 导入:与技能页同一个 ImportModal(拖拽区 + 文件要求清单 + 结果反馈)。
|
|
1635
|
+
// 一次可选多份 .md,逐个读取正文导入;同名/失败逐条回报,不因一个失败中断其余的。
|
|
1636
|
+
var impOpenState = React.useState(false); var importOpen = impOpenState[0], setImportOpen = impOpenState[1]
|
|
1637
|
+
var impBusyState = React.useState(false); var importBusy = impBusyState[0], setImportBusy = impBusyState[1]
|
|
1638
|
+
var impResultState = React.useState(null); var importResult = impResultState[0], setImportResult = impResultState[1]
|
|
1639
|
+
function importIdOf(name) {
|
|
1640
|
+
return String(name || '').replace(/\.(md|markdown|txt)$/i, '').trim().replace(/[\\/<>:"|?*]+/g, '-') || 'imported'
|
|
1641
|
+
}
|
|
1642
|
+
function doImportFiles(files) {
|
|
1643
|
+
var list = Array.prototype.slice.call(files || [])
|
|
1644
|
+
if (!list.length) return
|
|
1645
|
+
setImportBusy(true); setImportResult(null)
|
|
1646
|
+
var done = [], failed = []
|
|
1647
|
+
var chain = Promise.resolve()
|
|
1648
|
+
list.forEach(function (file) {
|
|
1649
|
+
chain = chain.then(function () {
|
|
1650
|
+
return file.text().then(function (text) {
|
|
1651
|
+
var id = importIdOf(file.name)
|
|
1652
|
+
return apiCall('agentsmd-import', { id: id, content: String(text) }).then(function (res) {
|
|
1653
|
+
if (res && res.ok) done.push(res.id || id)
|
|
1654
|
+
else failed.push(String(file.name) + '(' + ((res && res.error) || '') + ')')
|
|
1655
|
+
})
|
|
1656
|
+
})
|
|
1657
|
+
}).catch(function (e) { failed.push(String(file.name) + '(' + String((e && e.message) || e) + ')') })
|
|
1658
|
+
})
|
|
1659
|
+
chain.then(function () {
|
|
1660
|
+
setImportBusy(false)
|
|
1661
|
+
if (!done.length) setImportResult({ ok: false, text: failed.length ? t('import.skipped', { items: failed.join('、') }) : t('import.none') })
|
|
1662
|
+
else setImportResult({
|
|
1663
|
+
ok: true, warning: failed.length > 0,
|
|
1664
|
+
text: t('agm.result.imported', { count: done.length, names: done.join('、') }) + (failed.length ? t('import.skipped', { items: failed.join('、') }) : ''),
|
|
1665
|
+
})
|
|
1666
|
+
refresh()
|
|
1667
|
+
})
|
|
1360
1668
|
}
|
|
1361
1669
|
|
|
1362
1670
|
function refresh() {
|
|
1363
1671
|
apiCall('agentsmd-list', {}).then(function (res) {
|
|
1364
|
-
setState(function (s) { return { loading: false, error: res && res.ok ? null : ((res && res.error) || mt('mcp.msg.loadFailed')), presets: (res && res.presets) || [], current: s.current, scenePrompt: (res && res.scenePrompt) || null } })
|
|
1672
|
+
setState(function (s) { return { loading: false, error: res && res.ok ? null : ((res && res.error) || mt('mcp.msg.loadFailed')), presets: (res && res.presets) || [], current: s.current, scenePrompt: (res && res.scenePrompt) || null, anyLocked: (res && res.anyLocked) === true } })
|
|
1365
1673
|
}).catch(function (e) { setState({ loading: false, error: String((e && e.message) || e), presets: [], current: null, scenePrompt: null }) })
|
|
1366
1674
|
apiCall('agentsmd-get-current', {}).then(function (res) {
|
|
1367
1675
|
if (res && res.ok) setState(function (s) { return Object.assign({}, s, { current: res }) })
|
|
@@ -1370,11 +1678,6 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1370
1678
|
React.useEffect(function () { refresh() }, [])
|
|
1371
1679
|
|
|
1372
1680
|
var cur = state.current
|
|
1373
|
-
// 顶部「当前」:场景启用且绑了预设时,**以场景绑定的那份为准**(它就是生效中的基线)。
|
|
1374
|
-
var sceneLive = state.scenePrompt && state.scenePrompt.presetId && state.scenePrompt.scene && !state.scenePrompt.missing ? state.scenePrompt : null
|
|
1375
|
-
var curLabel = sceneLive
|
|
1376
|
-
? t('agm.current.byScene', { id: sceneLive.presetId, scene: sceneLive.scene })
|
|
1377
|
-
: (cur && cur.exists ? (cur.presetId || t('agm.current.external')) : t('agm.current.none'))
|
|
1378
1681
|
// 改名结果提示:短暂显示后自动消失(与场景页的结果条同一节奏)。
|
|
1379
1682
|
React.useEffect(function () {
|
|
1380
1683
|
if (!state.notice) return undefined
|
|
@@ -1385,7 +1688,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1385
1688
|
function openEdit(p) {
|
|
1386
1689
|
// id 可改:改名 = 目录改名,宿主会把场景绑定一起改名(见 rules-rebind-prompt)。
|
|
1387
1690
|
apiCall('agentsmd-read', { id: p.id }).then(function (res) {
|
|
1388
|
-
if (res && res.ok) setEditModal({ id: p.id, nextId: p.id, content: res.content })
|
|
1691
|
+
if (res && res.ok) setEditModal({ id: p.id, nextId: p.id, content: res.content, description: p.description || '' })
|
|
1389
1692
|
}).catch(function () {})
|
|
1390
1693
|
}
|
|
1391
1694
|
function doApply(id) {
|
|
@@ -1402,13 +1705,15 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1402
1705
|
else refresh()
|
|
1403
1706
|
}).catch(function (e) { setBusy(null); setApplyConfirm(null); setState(function (s) { return Object.assign({}, s, { error: String((e && e.message) || e) }) }) })
|
|
1404
1707
|
}
|
|
1405
|
-
function doCreate(id, from, content) {
|
|
1708
|
+
function doCreate(id, from, content, description) {
|
|
1406
1709
|
setBusy('create')
|
|
1407
1710
|
// content 优先(用户直接写的内容);留空才走 from 复制;都没有 = 宿主空模板。
|
|
1408
1711
|
apiCall('agentsmd-create', {
|
|
1409
1712
|
id: id,
|
|
1410
1713
|
...(content ? { content: content } : {}),
|
|
1411
1714
|
...(from ? { from: from } : {}),
|
|
1715
|
+
// 描述只给使用者看(存 meta.json,不进 AGENTS.md)。
|
|
1716
|
+
description: String(description == null ? '' : description),
|
|
1412
1717
|
}).then(function (res) {
|
|
1413
1718
|
setBusy(null)
|
|
1414
1719
|
if (res && res.ok) { setCreateModal(null); refresh() }
|
|
@@ -1417,7 +1722,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1417
1722
|
}
|
|
1418
1723
|
function doUpdate() {
|
|
1419
1724
|
setBusy('update')
|
|
1420
|
-
apiCall('agentsmd-update', { id: editModal.id, nextId: String(editModal.nextId || editModal.id), content: editModal.content }).then(function (res) {
|
|
1725
|
+
apiCall('agentsmd-update', { id: editModal.id, nextId: String(editModal.nextId || editModal.id), content: editModal.content, description: String(editModal.description == null ? '' : editModal.description) }).then(function (res) {
|
|
1421
1726
|
setBusy(null)
|
|
1422
1727
|
if (res && res.ok) {
|
|
1423
1728
|
setEditModal(null)
|
|
@@ -1449,7 +1754,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1449
1754
|
return React.createElement('div', { key: p.id, className: 'dsm-source' },
|
|
1450
1755
|
React.createElement('div', { className: 'dsm-source-head' },
|
|
1451
1756
|
React.createElement('div', { className: 'dsm-source-head-main' },
|
|
1452
|
-
React.createElement('span', { className: 'dsm-source-title' }, p.id),
|
|
1757
|
+
React.createElement('span', { className: 'dsm-source-title', title: p.id }, p.id),
|
|
1453
1758
|
p.active ? React.createElement('span', { className: 'dsm-tag dsm-tag-on', title: viaScene ? t('agm.active.hint.scene', { scene: (state.scenePrompt && state.scenePrompt.scene) || '' }) : t('agm.active.hint.file') }, t('agm.active')) : null,
|
|
1454
1759
|
!p.active && p.fileApplied ? React.createElement('span', { className: 'dsm-tag', title: t('agm.file.applied.hint') }, t('agm.file.applied')) : null,
|
|
1455
1760
|
boundByScene && !viaScene ? React.createElement('span', { className: 'dsm-tag' + (boundByScene.missing ? ' dsm-tag-off' : ''), title: t('agm.scene.tag.hint') },
|
|
@@ -1457,31 +1762,67 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1457
1762
|
? t('agm.scene.missing', { scene: boundByScene.scene })
|
|
1458
1763
|
: t('agm.scene.tag', { scene: boundByScene.scene })) : null),
|
|
1459
1764
|
React.createElement('div', { className: 'dsm-source-actions' },
|
|
1460
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', onClick: function () { openEdit(p) } }, t('agm.btn.edit')),
|
|
1461
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy !== null, onClick: function () { setApplyConfirm({ id: p.id }) }, title: t('agm.apply.hint') }, p.fileApplied ? t('agm.btn.reapply') : t('agm.btn.apply')),
|
|
1462
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: busy !== null || p.active === true, title: p.active === true ? t('agm.btn.delete.blocked') : '', onClick: function () { setApplyConfirm({ id: p.id, remove: true }) } }, t('agm.btn.delete')))))
|
|
1765
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: state.anyLocked === true, onClick: function () { openEdit(p) } }, t('agm.btn.edit')),
|
|
1766
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy !== null || state.anyLocked === true, onClick: function () { setApplyConfirm({ id: p.id }) }, title: t('agm.apply.hint') }, p.fileApplied ? t('agm.btn.reapply') : t('agm.btn.apply')),
|
|
1767
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: busy !== null || p.active === true || state.anyLocked === true, title: p.active === true ? t('agm.btn.delete.blocked') : t('scenes.lock.blockedEdit'), onClick: function () { setApplyConfirm({ id: p.id, remove: true }) } }, t('agm.btn.delete')))), p.description ? React.createElement("div", { key: "desc", className: "dsm-source-note", title: p.description }, p.description) : null)
|
|
1463
1768
|
}))
|
|
1464
1769
|
|
|
1465
1770
|
return React.createElement('section', { className: 'dsm-section' },
|
|
1466
1771
|
React.createElement('div', { className: 'dsm-head' },
|
|
1467
1772
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
1468
1773
|
React.createElement('div', { className: 'dsm-title-row' },
|
|
1469
|
-
React.createElement('h2', { className: 'dsm-title' }, t('tabs.prompts')),
|
|
1470
|
-
|
|
1471
|
-
React.createElement('p', { className: 'dsm-desc' }, t('prompts.desc')),
|
|
1472
|
-
// 当前基线:场景启用并绑了预设时,这里显示的就是**场景绑的那份**(用户裁定)。
|
|
1473
|
-
React.createElement('p', { className: 'dsm-help' }, t('agm.current.label', { label: curLabel }))),
|
|
1774
|
+
React.createElement('h2', { className: 'dsm-title' }, t('tabs.prompts'))),
|
|
1775
|
+
React.createElement('p', { className: 'dsm-desc' }, t('prompts.desc'))),
|
|
1474
1776
|
React.createElement('div', { className: 'dsm-actions' },
|
|
1475
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: function () {
|
|
1476
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled:
|
|
1777
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: state.loading, onClick: function () { refresh() } }, state.loading ? t('btn.refreshing') : t('btn.refresh')),
|
|
1778
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: state.anyLocked === true, onClick: function () { setCreateModal({ id: '', from: '', content: '', description: '', error: null }) } }, t('agm.btn.new')),
|
|
1779
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null || state.anyLocked === true, onClick: function () { setImportResult(null); setImportOpen(true) } }, t('agm.btn.import')),
|
|
1780
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null || !state.presets.length, onClick: function () { setExportState({ busy: false, result: null }) } }, t('export.presets')),
|
|
1781
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: function () { loadTrash(false) } }, t('trash.btn.open')))),
|
|
1782
|
+
state.anyLocked === true ? React.createElement('div', { className: 'dsm-feedback dsm-warning', role: 'status' }, t('lock.banner')) : null,
|
|
1783
|
+
React.createElement('div', { className: 'dsm-summary', style: { '--dsm-stat-cols': '2' } },
|
|
1784
|
+
[['total', state.presets.length, t('agm.stat.total')], ['active', state.presets.filter(function (p) { return p.active }).length, t('agm.stat.active')]].map(function (item) {
|
|
1785
|
+
return React.createElement('div', { key: item[0], className: 'dsm-stat' },
|
|
1786
|
+
React.createElement('strong', null, item[1]), item[2])
|
|
1787
|
+
})),
|
|
1477
1788
|
body,
|
|
1478
1789
|
state.notice ? React.createElement(Notice, { kind: 'ok', text: state.notice }) : null,
|
|
1790
|
+
exportState ? React.createElement(ExportModal, {
|
|
1791
|
+
key: 'export', t: t, title: t('export.presets'),
|
|
1792
|
+
items: state.presets.map(function (p) { return { key: p.id, name: p.id, desc: p.description || '' } }),
|
|
1793
|
+
busy: exportState.busy, result: exportState.result,
|
|
1794
|
+
onClose: function () { setExportState(null) },
|
|
1795
|
+
onSubmit: function (names, outDir) {
|
|
1796
|
+
setExportState({ busy: true, result: null });
|
|
1797
|
+
apiCall('bundle-export', { kind: 'presets', names: names, outDir: outDir }).then(function (res) {
|
|
1798
|
+
if (res && res.ok) setExportState({ busy: false, result: { ok: true, text: exportResultText(t, res) } });
|
|
1799
|
+
else setExportState({ busy: false, result: { ok: false, text: translateError(t, res) } });
|
|
1800
|
+
}).catch(function (e) { setExportState({ busy: false, result: { ok: false, text: String((e && e.message) || e) } }) });
|
|
1801
|
+
},
|
|
1802
|
+
}) : null,
|
|
1803
|
+
trash ? React.createElement(TrashModal, {
|
|
1804
|
+
t: t,
|
|
1805
|
+
title: t('trash.title'),
|
|
1806
|
+
groupTitle: t('trash.group.presets'),
|
|
1807
|
+
groupSub: t('trash.section.presets.sub'),
|
|
1808
|
+
locked: state.anyLocked === true,
|
|
1809
|
+
entries: trash.entries,
|
|
1810
|
+
loading: trash.loading,
|
|
1811
|
+
error: trash.error,
|
|
1812
|
+
busy: trashBusy,
|
|
1813
|
+
onClose: function () { setTrash(null) },
|
|
1814
|
+
onRestore: restorePreset,
|
|
1815
|
+
onPurge: purgePreset,
|
|
1816
|
+
}) : null,
|
|
1479
1817
|
// 编辑:id 与内容都可改(改 id = 目录改名,场景绑定由宿主一起改名)。
|
|
1480
|
-
editModal ? React.createElement(Modal, { title: t('agm.edit.title') + ' · ' + editModal.id, closeLabel: t('btn.
|
|
1818
|
+
editModal ? React.createElement(Modal, { title: t('agm.edit.title') + ' · ' + editModal.id, closeLabel: t('btn.close'), onClose: function () { setEditModal(null) } },
|
|
1481
1819
|
React.createElement('div', { className: 'dsm-field' },
|
|
1482
1820
|
React.createElement('label', { className: 'dsm-label' }, t('agm.field.id')),
|
|
1483
1821
|
React.createElement('input', { className: 'dsm-control', value: editModal.nextId || '', onChange: function (e) { setEditModal(Object.assign({}, editModal, { nextId: e.target.value, error: null })) } }),
|
|
1484
1822
|
React.createElement('p', { className: 'dsm-help' }, t('agm.field.id.hint'))),
|
|
1823
|
+
React.createElement('div', { className: 'dsm-field' },
|
|
1824
|
+
React.createElement('label', { className: 'dsm-label' }, t('agm.field.desc')),
|
|
1825
|
+
React.createElement('input', { className: 'dsm-control', placeholder: t('agm.field.desc.placeholder'), value: editModal.description || '', onChange: function (e) { setEditModal(Object.assign({}, editModal, { description: e.target.value })) } })),
|
|
1485
1826
|
React.createElement('div', { className: 'dsm-field' },
|
|
1486
1827
|
React.createElement('label', { className: 'dsm-label' }, t('agm.field.content')),
|
|
1487
1828
|
React.createElement('textarea', { className: 'dsm-control dsm-textarea-lg', value: editModal.content, onChange: function (e) { setEditModal(Object.assign({}, editModal, { content: e.target.value })) } })),
|
|
@@ -1490,11 +1831,14 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1490
1831
|
React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: busy !== null || !String(editModal.nextId || '').trim(), onClick: doUpdate }, t('agm.btn.save')))) : null,
|
|
1491
1832
|
// 新建:id + **正文文本框**(用户直接写内容);「从现有预设复制」是可选下拉,
|
|
1492
1833
|
// 选中后把那份正文填进文本框,仍可继续改——不再是容易被当成搜索框的文本输入。
|
|
1493
|
-
createModal ? React.createElement(Modal, { title: t('agm.btn.new'), closeLabel: t('btn.
|
|
1834
|
+
createModal ? React.createElement(Modal, { title: t('agm.btn.new'), closeLabel: t('btn.close'), onClose: function () { setCreateModal(null) } },
|
|
1494
1835
|
React.createElement('div', { className: 'dsm-field' },
|
|
1495
1836
|
React.createElement('label', { className: 'dsm-label' }, t('agm.field.id')),
|
|
1496
1837
|
React.createElement('input', { className: 'dsm-control', placeholder: t('agm.field.id.placeholder'), value: createModal.id, onChange: function (e) { setCreateModal(Object.assign({}, createModal, { id: e.target.value, error: null })) } }),
|
|
1497
1838
|
React.createElement('p', { className: 'dsm-help' }, t('agm.field.id.hint'))),
|
|
1839
|
+
React.createElement('div', { className: 'dsm-field' },
|
|
1840
|
+
React.createElement('label', { className: 'dsm-label' }, t('agm.field.desc')),
|
|
1841
|
+
React.createElement('input', { className: 'dsm-control', placeholder: t('agm.field.desc.placeholder'), value: createModal.description || '', onChange: function (e) { setCreateModal(Object.assign({}, createModal, { description: e.target.value })) } })),
|
|
1498
1842
|
(state.presets || []).length ? React.createElement('div', { className: 'dsm-field' },
|
|
1499
1843
|
React.createElement('label', { className: 'dsm-label' }, t('agm.field.copyFrom')),
|
|
1500
1844
|
React.createElement(SourceSelect, {
|
|
@@ -1507,8 +1851,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1507
1851
|
React.createElement('textarea', { className: 'dsm-control dsm-textarea-lg', placeholder: t('agm.field.content.placeholder'), value: createModal.content || '', onChange: function (e) { setCreateModal(Object.assign({}, createModal, { content: e.target.value })) } })),
|
|
1508
1852
|
createModal.error ? React.createElement('div', { className: 'dsm-feedback dsm-error' }, createModal.error) : null,
|
|
1509
1853
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
1510
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: busy !== null || !String(createModal.id || '').trim(), onClick: function () { doCreate(createModal.id, createModal.from, createModal.content) } }, t('agm.btn.create')))) : null,
|
|
1511
|
-
applyConfirm ? React.createElement(Modal, { title: applyConfirm.remove ? (t('agm.remove.title') + ' · ' + applyConfirm.id) : (t('agm.applyModal.title') + ' · ' + applyConfirm.id), closeLabel: t('btn.
|
|
1854
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: busy !== null || !String(createModal.id || '').trim(), onClick: function () { doCreate(createModal.id, createModal.from, createModal.content, createModal.description) } }, t('agm.btn.create')))) : null,
|
|
1855
|
+
applyConfirm ? React.createElement(Modal, { title: applyConfirm.remove ? (t('agm.remove.title') + ' · ' + applyConfirm.id) : (t('agm.applyModal.title') + ' · ' + applyConfirm.id), closeLabel: t('btn.close'), onClose: function () { setApplyConfirm(null) } },
|
|
1512
1856
|
applyConfirm.remove
|
|
1513
1857
|
? React.createElement('p', { className: 'dsm-help' }, t('agm.remove.title') + ' ' + applyConfirm.id + t('agm.remove.suffix'))
|
|
1514
1858
|
: React.createElement('div', null,
|
|
@@ -1516,7 +1860,13 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1516
1860
|
React.createElement('div', { className: 'dsm-feedback dsm-warning' }, t('agm.apply.note'))),
|
|
1517
1861
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
1518
1862
|
React.createElement('button', { type: 'button', className: 'dsm-btn ' + (applyConfirm.remove ? 'dsm-btn-danger' : ''), disabled: busy !== null, onClick: function () { if (applyConfirm.remove) doRemove({ id: applyConfirm.id }); else doApply(applyConfirm.id) } }, applyConfirm.remove ? t('agm.btn.confirmRemove') : t('agm.btn.confirmApply')))) : null,
|
|
1519
|
-
React.createElement(
|
|
1863
|
+
importOpen ? React.createElement(ImportModal, {
|
|
1864
|
+
key: 'imp', t: t, title: t('agm.btn.import'), accept: '.md',
|
|
1865
|
+
busy: importBusy, result: importResult,
|
|
1866
|
+
requirements: [t('upload.requirement.prompt.1'), t('upload.requirement.prompt.2'), t('upload.requirement.prompt.3')],
|
|
1867
|
+
onClose: function () { setImportOpen(false); setImportResult(null) },
|
|
1868
|
+
onSubmit: function (entries, files) { doImportFiles(files) },
|
|
1869
|
+
}) : null)
|
|
1520
1870
|
}
|
|
1521
1871
|
|
|
1522
1872
|
// 「工具」设置页:一个侧栏项,内部 tab 切换 场景 / MCP / 技能 / 子智能体 / 提示词 / 记忆 / 会话。
|
|
@@ -1527,8 +1877,21 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1527
1877
|
React.useEffect(function () {
|
|
1528
1878
|
// 设置宿主的真正滚动容器,而不是 body:不同页签内容高度不同,
|
|
1529
1879
|
// 让滚动条按需出现会改变 options 宽度,页签/GitHub 随之左右跳动。
|
|
1880
|
+
//
|
|
1881
|
+
// ⚠️ 必须**向上查找**而不是固定跳两级:`.dsm-tabs-shell` 的 position:sticky
|
|
1882
|
+
// 依赖祖先链上没有被 overflow 裁剪的层,宿主 DOM 结构一变,「上两级」就可能
|
|
1883
|
+
// 指到非滚动容器(sticky 随之失效,页签被内容滚走)。
|
|
1530
1884
|
var section = sectionRef.current
|
|
1531
|
-
|
|
1885
|
+
if (!section) return undefined
|
|
1886
|
+
var host = null
|
|
1887
|
+
var node = section.parentElement
|
|
1888
|
+
while (node && node !== document.body) {
|
|
1889
|
+
var oy = window.getComputedStyle(node).overflowY
|
|
1890
|
+
if (oy === 'auto' || oy === 'scroll') { host = node; break }
|
|
1891
|
+
node = node.parentElement
|
|
1892
|
+
}
|
|
1893
|
+
// 找不到(宿主换了实现)→ 退回原来的「上两级」,保持兼容。
|
|
1894
|
+
if (!host) host = section.parentElement && section.parentElement.parentElement
|
|
1532
1895
|
if (!host) return undefined
|
|
1533
1896
|
host.classList.add('dsm-settings-scroll-host')
|
|
1534
1897
|
return function () { host.classList.remove('dsm-settings-scroll-host') }
|
|
@@ -1544,14 +1907,14 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1544
1907
|
return React.createElement('section', { ref: sectionRef, className: 'dsm-section' },
|
|
1545
1908
|
React.createElement('div', { className: 'dsm-tabs-shell' },
|
|
1546
1909
|
React.createElement('div', { className: 'dsm-tabs' },
|
|
1547
|
-
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'scenes' ? ' dsm-tab-active' : ''), onClick: function () { setActive('scenes') } }, t('tabs.scenes')),
|
|
1548
|
-
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'mcp' ? ' dsm-tab-active' : ''), onClick: function () { setActive('mcp') } }, 'MCP'),
|
|
1549
|
-
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'skills' ? ' dsm-tab-active' : ''), onClick: function () { setActive('skills') } }, t('tabs.skills')),
|
|
1550
|
-
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'subagents' ? ' dsm-tab-active' : ''), onClick: function () { setActive('subagents') } }, t('tabs.subagents')),
|
|
1551
|
-
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'prompts' ? ' dsm-tab-active' : ''), onClick: function () { setActive('prompts') } }, t('tabs.prompts')),
|
|
1552
|
-
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'memory' ? ' dsm-tab-active' : ''), onClick: function () { setActive('memory') } }, t('tabs.memory')),
|
|
1553
|
-
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'sessions' ? ' dsm-tab-active' : ''), onClick: function () { setActive('sessions') } }, t('tabs.sessions')),
|
|
1554
|
-
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'compat' ? ' dsm-tab-active' : ''), onClick: function () { setActive('compat') } }, t('tabs.compat'))),
|
|
1910
|
+
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'scenes' ? ' dsm-tab-active' : ''), onClick: function () { setActive('scenes') }, title: t('tabs.scenes') }, t('tabs.scenes')),
|
|
1911
|
+
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'mcp' ? ' dsm-tab-active' : ''), onClick: function () { setActive('mcp') }, title: 'MCP' }, 'MCP'),
|
|
1912
|
+
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'skills' ? ' dsm-tab-active' : ''), onClick: function () { setActive('skills') }, title: t('tabs.skills') }, t('tabs.skills')),
|
|
1913
|
+
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'subagents' ? ' dsm-tab-active' : ''), onClick: function () { setActive('subagents') }, title: t('tabs.subagents') }, t('tabs.subagents')),
|
|
1914
|
+
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'prompts' ? ' dsm-tab-active' : ''), onClick: function () { setActive('prompts') }, title: t('tabs.prompts') }, t('tabs.prompts')),
|
|
1915
|
+
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'memory' ? ' dsm-tab-active' : ''), onClick: function () { setActive('memory') }, title: t('tabs.memory') }, t('tabs.memory')),
|
|
1916
|
+
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'sessions' ? ' dsm-tab-active' : ''), onClick: function () { setActive('sessions') }, title: t('tabs.sessions') }, t('tabs.sessions')),
|
|
1917
|
+
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'compat' ? ' dsm-tab-active' : ''), onClick: function () { setActive('compat') }, title: t('tabs.compat') }, t('tabs.compat'))),
|
|
1555
1918
|
React.createElement(FeedbackLinks, null)),
|
|
1556
1919
|
page)
|
|
1557
1920
|
}
|
|
@@ -1606,6 +1969,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1606
1969
|
var OP_BY_PATH = { "/state": "skill-state", "/enable": "skill-enable", "/disable": "skill-disable",
|
|
1607
1970
|
"/prefer": "skill-prefer", "/unprefer": "skill-unprefer",
|
|
1608
1971
|
"/source-enable": "skill-source-enable", "/source-disable": "skill-source-disable",
|
|
1972
|
+
// 「移除来源 / 恢复读取」两个按钮曾经打的是这两个路径,但映射表里没有它们 ——
|
|
1973
|
+
// callApi 直接 reject("unknown action"),弹窗又 catch 掉了,按钮点下去毫无反应。
|
|
1974
|
+
"/source-remove": "skill-source-remove", "/source-restore": "skill-source-restore",
|
|
1609
1975
|
"/delete": "skill-delete", "/trash-restore": "skill-trash-restore", "/trash-delete": "skill-trash-delete",
|
|
1610
1976
|
"/detail": "skill-detail", "/create": "skill-create", "/import": "skill-import",
|
|
1611
1977
|
"/upload": "skill-upload", "/browse": "skill-browse" };
|
|
@@ -1697,7 +2063,12 @@ function callApi(path, options) {
|
|
|
1697
2063
|
}
|
|
1698
2064
|
function Switch(props) { return h("button", { type: "button", className: "dsm-switch" + (props.on ? " dsm-switch-on" : ""), role: "switch", "aria-checked": props.on, "aria-label": props.label, disabled: props.disabled, onClick: props.onClick }); }
|
|
1699
2065
|
function GithubMark16() { return h("svg", { viewBox: "0 0 16 16", width: 16, height: 16, "aria-hidden": true, focusable: "false" }, h("path", { fill: "currentColor", d: "M8 0a8 8 0 0 0-2.53 15.59c.4.074.547-.173.547-.385 0-.19-.007-.693-.01-1.36-2.226.484-2.695-1.073-2.695-1.073-.364-.924-.89-1.17-.89-1.17-.726-.496.055-.486.055-.486.803.056 1.225.824 1.225.824.714 1.223 1.872.87 2.328.665.072-.517.28-.87.508-1.07-1.777-.202-3.645-.888-3.645-3.956 0-.874.31-1.588.823-2.148-.083-.202-.357-1.017.078-2.12 0 0 .672-.215 2.2.82A7.65 7.65 0 0 1 8 4.8c.68.003 1.365.092 2.004.27 1.527-1.035 2.197-.82 2.197-.82.437 1.103.162 1.918.08 2.12.513.56.822 1.274.822 2.148 0 3.076-1.872 3.752-3.654 3.95.288.248.544.735.544 1.482 0 1.07-.01 1.932-.01 2.195 0 .214.144.463.55.384A8.001 8.001 0 0 0 8 0Z" })); }
|
|
1700
|
-
|
|
2066
|
+
/**
|
|
2067
|
+
* 弹窗原语。`list: true` 时走「列表类」三段式:固定头 / 滚动体 / 固定底,
|
|
2068
|
+
* 配 `.dsm-modal-list`(宽高都固定),滚动只发生在 body 这一层 —— 条目数变化时
|
|
2069
|
+
* 弹窗不跳、也不会出现嵌套滚动条。普通表单/确认类**不传 list**,结构保持原样。
|
|
2070
|
+
*/
|
|
2071
|
+
function Modal(props) { var ref = react.useRef(null); react.useEffect(function () { if (ref.current) ref.current.focus(); }, []); var body = props.list ? h("div", { className: "dsm-modal-body" }, props.children) : props.children; return h("div", { className: "dsm-mask", onMouseDown: function (e) { if (e.target === e.currentTarget) props.onClose(); } }, h("div", { ref: ref, tabIndex: -1, className: "dsm-modal" + (props.wide ? " dsm-modal-wide" : "") + (props.className ? " " + props.className : ""), role: "dialog", "aria-modal": "true", onKeyDown: function (e) { if (!handleModalEscape(e, props.onClose)) trapModalFocus(e.currentTarget, e); } }, h("div", { className: "dsm-modal-head" }, h("h3", { className: "dsm-modal-title" }, props.title), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: props.onClose }, props.closeLabel)), body)); }
|
|
1701
2072
|
/**
|
|
1702
2073
|
* 全插件唯一的「操作结果」呈现方式:成功 = 右下角浮层(固定定位,不改变页面高度),
|
|
1703
2074
|
* 警告 / 错误 = 文档流内横幅(需要用户处理,常驻到下一次操作)。所有页面共用,
|
|
@@ -1722,6 +2093,63 @@ function callApi(path, options) {
|
|
|
1722
2093
|
props.body,
|
|
1723
2094
|
props.foot || null)
|
|
1724
2095
|
}
|
|
2096
|
+
/**
|
|
2097
|
+
* 通用回收站弹窗:一行一个条目(名字 + 删除时间),右侧「恢复 / 永久删除」。
|
|
2098
|
+
*
|
|
2099
|
+
* 子智能体 / 场景 / 提示词预设三处共用;永久删除就在本弹窗里就地二次确认,
|
|
2100
|
+
* 不再叠一层弹窗。恢复失败的原因显示在弹窗顶部 —— 不静默(同名已存在时必须说清)。
|
|
2101
|
+
*/
|
|
2102
|
+
function TrashModal(props) {
|
|
2103
|
+
const t = props.t
|
|
2104
|
+
const c = React.useState(null)
|
|
2105
|
+
const confirmId = c[0], setConfirmId = c[1]
|
|
2106
|
+
const entries = props.entries || []
|
|
2107
|
+
const row = function (item) {
|
|
2108
|
+
const confirming = confirmId === item.id
|
|
2109
|
+
const actions = confirming
|
|
2110
|
+
? [
|
|
2111
|
+
React.createElement('span', { className: 'dsm-note', key: 'ask' }, t('trash.purge.confirm')),
|
|
2112
|
+
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')),
|
|
2113
|
+
React.createElement('button', { key: 'no', type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: props.busy, onClick: function () { setConfirmId(null) } }, t('btn.cancel')),
|
|
2114
|
+
]
|
|
2115
|
+
: [
|
|
2116
|
+
React.createElement('button', { key: 'restore', type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: props.busy || props.locked === true, onClick: function () { props.onRestore(item) } }, t('btn.restore')),
|
|
2117
|
+
React.createElement('button', { key: 'purge', type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: props.busy || props.locked === true, onClick: function () { setConfirmId(item.id) } }, t('btn.delete.forever')),
|
|
2118
|
+
]
|
|
2119
|
+
return React.createElement('div', { className: 'dsm-trash-item', key: item.id },
|
|
2120
|
+
React.createElement('div', { className: 'dsm-trash-main' },
|
|
2121
|
+
React.createElement('div', { className: 'dsm-name' }, item.name),
|
|
2122
|
+
React.createElement('div', { className: 'dsm-note' }, t('trash.deletedAt', {
|
|
2123
|
+
time: item.deletedAt ? new Date(item.deletedAt).toLocaleString() : '—',
|
|
2124
|
+
}))),
|
|
2125
|
+
actions)
|
|
2126
|
+
}
|
|
2127
|
+
const body = props.loading
|
|
2128
|
+
? React.createElement('div', { className: 'dsm-empty' }, t('memory.loading'))
|
|
2129
|
+
: entries.length === 0
|
|
2130
|
+
? React.createElement('div', { className: 'dsm-empty' }, t('trash.empty'))
|
|
2131
|
+
: entries.map(row)
|
|
2132
|
+
// 列表类弹窗:宽高都固定(`.dsm-modal-list`),滚动只发生在 Modal 的 body 这一层 ——
|
|
2133
|
+
// 条目数变化时弹窗不跳,也不会出现「弹窗滚 + 组内滚」的嵌套滚动条。
|
|
2134
|
+
return React.createElement(Modal, {
|
|
2135
|
+
key: 'trash-modal',
|
|
2136
|
+
title: props.title,
|
|
2137
|
+
closeLabel: t('btn.close'),
|
|
2138
|
+
list: true,
|
|
2139
|
+
className: 'dsm-modal-list',
|
|
2140
|
+
onClose: props.onClose,
|
|
2141
|
+
},
|
|
2142
|
+
props.error ? React.createElement(Notice, { kind: 'err', text: props.error }) : null,
|
|
2143
|
+
React.createElement('div', { className: 'dsm-trash-group' },
|
|
2144
|
+
React.createElement('div', { className: 'dsm-trash-group-head' },
|
|
2145
|
+
React.createElement('div', { className: 'dsm-trash-group-head-row' },
|
|
2146
|
+
React.createElement('span', { className: 'dsm-trash-group-title' }, props.groupTitle),
|
|
2147
|
+
React.createElement('span', { className: 'dsm-count' }, t('trash.items.count', { count: entries.length }))),
|
|
2148
|
+
// 后果说明进组头(sticky 跟着滚):这组删的是记录还是文件,一眼可辨。
|
|
2149
|
+
props.groupSub ? React.createElement('p', { className: 'dsm-trash-group-sub' }, props.groupSub) : null),
|
|
2150
|
+
React.createElement('div', { className: 'dsm-trash-group-body' }, body)))
|
|
2151
|
+
}
|
|
2152
|
+
|
|
1725
2153
|
/** 勾选行:复选框 + 名称/说明 + 右侧指标 + 可选行内动作。 */
|
|
1726
2154
|
function pickRow(props) {
|
|
1727
2155
|
return h('label', { key: props.key, className: 'dsm-pick' },
|
|
@@ -1745,13 +2173,17 @@ function callApi(path, options) {
|
|
|
1745
2173
|
h('span', { className: 'dsm-seg-group-line' }))
|
|
1746
2174
|
}
|
|
1747
2175
|
/** 段头公共动作:全选 / 清空 / 移除段(未定义时改为「添加」)。 */
|
|
2176
|
+
/**
|
|
2177
|
+
* 段头动作。**批量动作合二为一**(D11):同一时刻只有一个方向是合理的 ——
|
|
2178
|
+
* 还有没勾的 → 「全选」;全勾了 → 「全不选」。并列两个必然有一个是废话。
|
|
2179
|
+
* 未定义的可选段只给「添加」;「移除段」不属于全选族,保留。
|
|
2180
|
+
*/
|
|
1748
2181
|
function segActions(props) {
|
|
1749
2182
|
if (!props.defined) return [h('button', { key: 'add', type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: props.busy, onClick: props.onAdd }, props.addLabel)]
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
]
|
|
2183
|
+
var out = [h('button', { key: 'all', type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: props.busy, onClick: props.allChecked ? props.onClear : props.onAll }, props.allChecked ? props.clearLabel : props.selectAllLabel)]
|
|
2184
|
+
// 常驻段(记忆,P5 单一真相源)没有「段定义」可移除 → 不传 onRemove 即不渲染。
|
|
2185
|
+
if (props.onRemove) out.push(h('button', { key: 'rm', type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: props.busy, onClick: props.onRemove }, props.removeLabel))
|
|
2186
|
+
return out
|
|
1755
2187
|
}
|
|
1756
2188
|
/** 段脚注:段已定义但一项未勾 = 该域全部停用(必须显式提示,否则像没保存上)。 */
|
|
1757
2189
|
function segFoot(defined, count, emptyLabel) {
|
|
@@ -1767,17 +2199,28 @@ function callApi(path, options) {
|
|
|
1767
2199
|
// 通用导入弹窗(人设 / 记忆共用):拖放或点选 .md / .zip → 已选计数 → 交由调用方走自己的写 op。
|
|
1768
2200
|
// t 由调用方注入(各页的 t 是局部函数,不在模块作用域);extra 用于注入「导入到场景」一类的额外字段。
|
|
1769
2201
|
function ImportModal(props) {
|
|
1770
|
-
|
|
2202
|
+
// 同时持有原始 File[] 与 base64 entries:前者给需要 `_dssmPath`(拖拽保留相对路径)
|
|
2203
|
+
// 或自己做校验的调用方(技能页),后者给只关心 `{name, data}` 的调用方。
|
|
2204
|
+
var st = react.useState([]); var picked = st[0], setPicked = st[1]
|
|
2205
|
+
var es = react.useState([]); var entries = es[0], setEntries = es[1]
|
|
1771
2206
|
var ref = react.useRef(null)
|
|
2207
|
+
var folderRef = react.useRef(null)
|
|
1772
2208
|
var t = props.t
|
|
1773
2209
|
function add(list) {
|
|
1774
|
-
var
|
|
1775
|
-
if (!
|
|
1776
|
-
Promise.all(
|
|
1777
|
-
.then(function (
|
|
2210
|
+
var next = Array.prototype.slice.call(list || [])
|
|
2211
|
+
if (!next.length) return
|
|
2212
|
+
Promise.all(next.map(function (f) { return f.arrayBuffer().then(function (buf) { return { name: uploadFilePath(f), data: bytesToBase64(buf) } }) }))
|
|
2213
|
+
.then(function (added) {
|
|
2214
|
+
setPicked(function (prev) { return prev.concat(next) })
|
|
2215
|
+
setEntries(function (prev) { return prev.concat(added) })
|
|
2216
|
+
})
|
|
1778
2217
|
.catch(function () {})
|
|
1779
2218
|
}
|
|
1780
|
-
|
|
2219
|
+
function dropAt(index) {
|
|
2220
|
+
setPicked(function (prev) { return prev.filter(function (_, j) { return j !== index }) })
|
|
2221
|
+
setEntries(function (prev) { return prev.filter(function (_, j) { return j !== index }) })
|
|
2222
|
+
}
|
|
2223
|
+
return h(Modal, { className: "dsm-modal-sm", title: props.title, closeLabel: t("btn.close"), onClose: props.onClose },
|
|
1781
2224
|
h("div", { className: "dsm-form" },
|
|
1782
2225
|
props.extra || null,
|
|
1783
2226
|
h("div", {
|
|
@@ -1787,13 +2230,97 @@ function callApi(path, options) {
|
|
|
1787
2230
|
onDragLeave: function (e) { if (e.currentTarget.classList) e.currentTarget.classList.remove("dsm-dropzone-active") },
|
|
1788
2231
|
onDrop: function (e) { e.preventDefault(); if (e.currentTarget.classList) e.currentTarget.classList.remove("dsm-dropzone-active"); droppedFiles(e.dataTransfer).then(add) },
|
|
1789
2232
|
},
|
|
1790
|
-
h("
|
|
1791
|
-
h("
|
|
1792
|
-
|
|
1793
|
-
|
|
2233
|
+
h("span", { className: "dsm-dropzone-title" }, t("upload.drop.title")),
|
|
2234
|
+
h("span", { className: "dsm-dropzone-copy" }, props.hint || t("upload.drop.copy"))),
|
|
2235
|
+
h("input", { ref: ref, type: "file", multiple: true, accept: props.accept || ".md,.zip", className: "dsm-hidden-input", onChange: function (e) { add(e.target.files); e.target.value = "" } }),
|
|
2236
|
+
// 「选择文件夹」:`webkitdirectory` 的隐藏 input 由组件自己渲染,
|
|
2237
|
+
// 调用方不必各自复制一份(技能页原来就有一份)。
|
|
2238
|
+
props.allowFolder ? h("input", { ref: folderRef, type: "file", multiple: true, webkitdirectory: "", directory: "", className: "dsm-hidden-input", onChange: function (e) { add(e.target.files); e.target.value = "" } }) : null,
|
|
2239
|
+
// 已选文件:一行一个(名字 + 大小 + 移除),与技能页同一形态。
|
|
2240
|
+
picked.length ? h("div", null, picked.map(function (f, i) {
|
|
2241
|
+
return h("div", { key: i, className: "dsm-file", title: f.name },
|
|
2242
|
+
h("span", { className: "dsm-file-kind", "aria-hidden": "true" }, "FILE"),
|
|
2243
|
+
h("span", { className: "dsm-file-name" }, f.name),
|
|
2244
|
+
h("span", { className: "dsm-file-meta" }, fmtBytes(f.size)),
|
|
2245
|
+
h("button", { type: "button", className: "dsm-file-remove", disabled: props.busy, "aria-label": t("upload.remove") + " " + f.name, onClick: function () { dropAt(i) } }, "×"))
|
|
2246
|
+
})) : null,
|
|
2247
|
+
props.result ? h("div", { className: "dsm-feedback" + (props.result.warning ? " dsm-warning" : props.result.ok ? "" : " dsm-error"), role: "alert" }, props.result.text) : null,
|
|
2248
|
+
// 文件要求清单(可选):调用方按页传,结构上限制「这页需要什么格式」的表达。
|
|
2249
|
+
props.requirements && props.requirements.length ? h("div", { className: "dsm-upload-requirements" },
|
|
2250
|
+
h("div", { className: "dsm-label" }, t("upload.requirements")),
|
|
2251
|
+
h("ul", null, props.requirements.map(function (r, i) { return h("li", { key: i }, r) }))) : null),
|
|
1794
2252
|
h("div", { className: "dsm-modal-actions" },
|
|
1795
|
-
|
|
1796
|
-
h("button", { type: "button", className: "dsm-btn dsm-btn-
|
|
2253
|
+
picked.length ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", disabled: props.busy, onClick: function () { setPicked([]); setEntries([]) } }, t("import.clear")) : null,
|
|
2254
|
+
props.allowFolder ? h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: props.busy, onClick: function () { if (folderRef.current) folderRef.current.click() } }, t("btn.folder.pick")) : null,
|
|
2255
|
+
h("button", { type: "button", className: "dsm-btn", disabled: props.busy || !picked.length, onClick: function () { props.onSubmit(entries, picked) } }, props.busy ? t("upload.importing") : t("import.submit"))))
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
/**
|
|
2259
|
+
* 通用导出弹窗(D14):选中项 + 目标目录 → 调 `bundle-export` 打包成 zip。
|
|
2260
|
+
* 技能 / 子智能体 / 提示词 / 记忆四页共用,避免各写一份。
|
|
2261
|
+
* 只读源文件、只往用户指定目录写 zip,不改动任何插件数据。
|
|
2262
|
+
*/
|
|
2263
|
+
function ExportModal(props) {
|
|
2264
|
+
var t = props.t
|
|
2265
|
+
var ds = react.useState(props.outDir || ''); var outDir = ds[0], setOutDir = ds[1]
|
|
2266
|
+
var ps = react.useState(false); var picking = ps[0], setPicking = ps[1]
|
|
2267
|
+
var items = props.items || []
|
|
2268
|
+
// 默认**不勾选**:导出是"把东西拿出去",一键全选最容易让人把不该带走的也带走。
|
|
2269
|
+
var ss = react.useState([]); var picked = ss[0], setPicked = ss[1]
|
|
2270
|
+
var cs = react.useState({}); var collapsed = cs[0], setCollapsed = cs[1]
|
|
2271
|
+
function toggle(key) {
|
|
2272
|
+
setPicked(function (prev) { return prev.indexOf(key) >= 0 ? prev.filter(function (k) { return k !== key }) : prev.concat([key]) })
|
|
2273
|
+
}
|
|
2274
|
+
/** 分组:`it.group` 为空 = 不分组(组头不渲染)。组内、组间都保持传入顺序。 */
|
|
2275
|
+
var groups = [], byGroup = {}
|
|
2276
|
+
items.forEach(function (it) {
|
|
2277
|
+
var g = String(it.group == null ? '' : it.group)
|
|
2278
|
+
if (!byGroup[g]) { byGroup[g] = []; groups.push({ name: g, rows: byGroup[g] }) }
|
|
2279
|
+
byGroup[g].push(it)
|
|
2280
|
+
})
|
|
2281
|
+
var allKeys = items.map(function (i) { return i.key })
|
|
2282
|
+
var allPicked = allKeys.length > 0 && picked.length === allKeys.length
|
|
2283
|
+
return h(Modal, { className: 'dsm-modal-list dsm-export-modal', list: true, title: props.title, closeLabel: t('btn.close'), onClose: props.onClose },
|
|
2284
|
+
h('div', { className: 'dsm-form dsm-export-form' },
|
|
2285
|
+
h('p', { className: 'dsm-help' }, t('export.hint')),
|
|
2286
|
+
h('div', { className: 'dsm-field' },
|
|
2287
|
+
h('span', { className: 'dsm-label' }, t('export.outDir')),
|
|
2288
|
+
h('div', { className: 'dsm-dir-row' },
|
|
2289
|
+
h('input', { className: 'dsm-control', type: 'text', value: outDir, placeholder: t('export.outDir.placeholder'), onChange: function (e) { setOutDir(e.target.value) } }),
|
|
2290
|
+
h('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: props.busy, onClick: function () { setPicking(true) } }, t('btn.openDir')))),
|
|
2291
|
+
// 条目列表:始终渲染(哪怕只有一条)—— 默认不勾选意味着「不列出来就没得勾」。
|
|
2292
|
+
items.length ? h('div', { className: 'dsm-pick-list' }, groups.map(function (g) {
|
|
2293
|
+
var open = collapsed[g.name] !== true
|
|
2294
|
+
return h('div', { key: 'g:' + g.name, className: 'dsm-pick-group' },
|
|
2295
|
+
g.name ? h('button', {
|
|
2296
|
+
type: 'button', className: 'dsm-pick-group-head', 'aria-expanded': open ? 'true' : 'false',
|
|
2297
|
+
onClick: function () { setCollapsed(function (prev) { var next = Object.assign({}, prev); next[g.name] = open; return next }) },
|
|
2298
|
+
},
|
|
2299
|
+
h('span', { className: 'dsm-pick-caret', 'aria-hidden': 'true' }, open ? '▾' : '▸'),
|
|
2300
|
+
h('span', { className: 'dsm-pick-group-name', title: g.name }, g.name),
|
|
2301
|
+
h('span', { className: 'dsm-pick-group-count' }, t('export.group.count', { count: g.rows.length }))) : null,
|
|
2302
|
+
open ? g.rows.map(function (it) {
|
|
2303
|
+
return pickRow({ key: it.key, checked: picked.indexOf(it.key) >= 0, name: it.name, desc: it.desc, descTitle: it.descTitle || it.desc, onChange: function () { toggle(it.key) } })
|
|
2304
|
+
}) : null)
|
|
2305
|
+
})) : h('div', { className: 'dsm-pick-empty' }, t('export.empty')),
|
|
2306
|
+
props.result ? h('div', { className: 'dsm-feedback' + (props.result.ok ? '' : ' dsm-error'), role: 'alert' }, props.result.text) : null),
|
|
2307
|
+
h('div', { className: 'dsm-modal-actions' },
|
|
2308
|
+
h('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: props.busy || !items.length, onClick: function () { setPicked(allPicked ? [] : allKeys.slice()) } }, allPicked ? t('export.pickNone') : t('export.pickAll')),
|
|
2309
|
+
h('button', { type: 'button', className: 'dsm-btn', disabled: props.busy || !picked.length || !outDir.trim(), onClick: function () { props.onSubmit(picked, outDir.trim()) } }, props.busy ? t('export.busy') : t('export.submit'))),
|
|
2310
|
+
picking ? h(DirPickerModal, { key: 'dir', title: t('export.pickDir'), initial: outDir, closeLabel: t('btn.close'), onClose: function () { setPicking(false) }, onPick: function (p) { setOutDir(p); setPicking(false) } }) : null)
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
/**
|
|
2314
|
+
* 导出结果文案(四个导出页共用):成功条数 + 被跳过的项。
|
|
2315
|
+
*
|
|
2316
|
+
* 服务端把读不到的项记进 `missing` 而不是整体失败(部分成功也有价值),但**必须说出来**
|
|
2317
|
+
* —— 只报「已导出 N 个文件」会把静默丢项显示成完全成功(bundle 型路径曾经就是这样丢的)。
|
|
2318
|
+
*/
|
|
2319
|
+
function exportResultText(t, res) {
|
|
2320
|
+
var text = t('export.done', { count: res.written, path: res.zipPath })
|
|
2321
|
+
var skipped = (res && res.missing) || []
|
|
2322
|
+
if (skipped.length) text += t('export.missing', { count: skipped.length, names: skipped.join(', ') })
|
|
2323
|
+
return text
|
|
1797
2324
|
}
|
|
1798
2325
|
|
|
1799
2326
|
// 目录选择器:经 host 的 dir-list 逐级浏览目录树,选中后把绝对路径回填给调用方。
|
|
@@ -1818,7 +2345,7 @@ function callApi(path, options) {
|
|
|
1818
2345
|
function goUp() { if (!st.parent) return; setText(st.parent); load(st.parent) }
|
|
1819
2346
|
function enter(path) { setText(path); load(path) }
|
|
1820
2347
|
function pick() { var target = String(st.current || '').trim() || String(text || '').trim(); if (target && props.onPick) props.onPick(target) }
|
|
1821
|
-
return h(Modal, { title: props.title || '选择文件夹', closeLabel: props.closeLabel || '
|
|
2348
|
+
return h(Modal, { title: props.title || '选择文件夹', closeLabel: props.closeLabel || t('btn.close'), onClose: props.onClose },
|
|
1822
2349
|
h("div", { className: "dsm-form" },
|
|
1823
2350
|
h("label", { className: "dsm-field" },
|
|
1824
2351
|
h("span", { className: "dsm-label" }, '当前路径'),
|
|
@@ -1849,8 +2376,6 @@ function callApi(path, options) {
|
|
|
1849
2376
|
var ds = react.useState(null), detail = ds[0], setDetail = ds[1];
|
|
1850
2377
|
var cs = react.useState({ path: "", label: "" }), customForm = cs[0], setCustomForm = cs[1];
|
|
1851
2378
|
var inflightRef = react.useRef(false);
|
|
1852
|
-
var importInputRef = react.useRef(null);
|
|
1853
|
-
var folderInputRef = react.useRef(null);
|
|
1854
2379
|
var pickerOpenRef = react.useRef(false);
|
|
1855
2380
|
function refresh(silent) { if (!silent) setSnapshot({ loading: true, error: null, data: snapshot.data }); return callApi("/state").then(function (data) { setSnapshot({ loading: false, error: null, data: data }); return data; }).catch(function (error) { setSnapshot({ loading: false, error: translateError(t, error), data: snapshot.data }); }); }
|
|
1856
2381
|
react.useEffect(function () { refresh(false); }, []);
|
|
@@ -1879,10 +2404,47 @@ function callApi(path, options) {
|
|
|
1879
2404
|
setResult({ ok: false, text: (res && res.error) || t("error.action", { error: "unknown" }) });
|
|
1880
2405
|
}).catch(function (error) { setResult({ ok: false, text: t("error.action", { error: translateError(t, error) }) }); }).finally(function () { setBusy(false); });
|
|
1881
2406
|
}
|
|
2407
|
+
// 回收站「目录」的永久删除:把这条来源记录从插件里删掉(磁盘上的文件夹与文件不动,
|
|
2408
|
+
// 这也正是「移除来源」与「永久删除」的区别)。与「自定义目录」的移除是同一个 op ——
|
|
2409
|
+
// 只有自定义来源有记录可删,按约定发现的外部来源(agents/codex/claude)删不掉,
|
|
2410
|
+
// 所以界面上只对 root.custom === true 的行给这个按钮。
|
|
2411
|
+
function submitSourceForget(key, name) {
|
|
2412
|
+
setBusy(true); setResult(null);
|
|
2413
|
+
apiCall("skill-custom-remove", { key: key }).then(function (res) {
|
|
2414
|
+
setModal("trash");
|
|
2415
|
+
if (res && res.ok) { setResult({ ok: true, text: t("result.sourceForgotten", { name: name }) }); return refresh(true); }
|
|
2416
|
+
setResult({ ok: false, text: t("error.action", { error: translateError(t, res) }) });
|
|
2417
|
+
}).catch(function (error) { setModal("trash"); setResult({ ok: false, text: t("error.action", { error: translateError(t, error) }) }); }).finally(function () { setBusy(false); });
|
|
2418
|
+
}
|
|
1882
2419
|
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
2420
|
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
|
-
|
|
1885
|
-
function
|
|
2421
|
+
/** D14:导出条目 = 按**来源文件夹**分组,组内列出该文件夹里的全部技能(含同名被遮蔽的)。 */
|
|
2422
|
+
function exportItems() {
|
|
2423
|
+
var out = [];
|
|
2424
|
+
for (var i = 0; i < allRoots.length; i++) {
|
|
2425
|
+
var root = allRoots[i], list = root.skills || [];
|
|
2426
|
+
var group = rootDisplayName(t, root);
|
|
2427
|
+
for (var j = 0; j < list.length; j++) {
|
|
2428
|
+
var sk = list[j];
|
|
2429
|
+
// key 用**条目名**(文件 / 目录名),不是 frontmatter 里的展示名:服务端按它定位源文件,
|
|
2430
|
+
// 用 declaredName 会在「文件名 ≠ 声明名」时找不到文件(静默丢项)。
|
|
2431
|
+
var nm = String(sk.name || "");
|
|
2432
|
+
if (!nm) continue;
|
|
2433
|
+
out.push({ key: String(root.key) + "/" + nm, name: String(sk.declaredName || nm), desc: sk.description || "", group: group });
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
return out;
|
|
2437
|
+
}
|
|
2438
|
+
function doExport(names, outDir) {
|
|
2439
|
+
setBusy(true); setResult(null);
|
|
2440
|
+
apiCall("bundle-export", { kind: "skills", names: names, outDir: outDir }).then(function (res) {
|
|
2441
|
+
setBusy(false);
|
|
2442
|
+
if (res && res.ok) setResult({ ok: true, text: exportResultText(t, res) });
|
|
2443
|
+
else setResult({ ok: false, text: translateError(t, res) });
|
|
2444
|
+
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }); });
|
|
2445
|
+
}
|
|
2446
|
+
function submitImport(selection) { var sel = selection || upload; if (!sel) return; buildUploadPayload(sel).then(function (payload) { return post("/upload", payload, null); }).then(function (data) { var summary = summarizeImportResult(t, data); setResult({ ok: summary.ok, warning: summary.warning, text: summary.text }); if (summary.imported) { setModal(null); setUpload(null); } }).catch(function () {}); }
|
|
2447
|
+
var anyLocked = !!(snapshot.data && snapshot.data.anyLocked === true);
|
|
1886
2448
|
var data = snapshot.data || { roots: [], trash: [], summary: { total: 0, enabled: 0, disabled: 0, issues: 0 } }, allRoots = data.roots || [], roots = visibleSkillRoots(allRoots), removedRoots = removedSkillRoots(allRoots), activeSource = roots.some(function (root) { return root.key === source; }) ? source : "";
|
|
1887
2449
|
var createRoots = allRoots.filter(function (root) { return root.mutable === true; }), createOptions = createRoots.map(function (root) { return { value: root.key, label: rootDisplayName(t, root) }; }); if (!createOptions.length) createOptions.push({ value: "hub", label: t("root.hub") });
|
|
1888
2450
|
// 新建技能的默认落点:hub(`tool-management/skills/`)。
|
|
@@ -1930,31 +2492,87 @@ function callApi(path, options) {
|
|
|
1930
2492
|
function renderSkill(root, skill) {
|
|
1931
2493
|
var enabled = isSkillEnabled(skill), key = skill.shadowedBy ? "status.shadowed" : skill.loadable === false ? "status.invalid" : enabled ? "status.enabled" : "status.disabled", cls = skill.shadowedBy ? "dsm-shadowed" : enabled ? "dsm-enabled" : "dsm-disabled";
|
|
1932
2494
|
var canToggle = root.toggleable !== false && skill.loadable !== false;
|
|
1933
|
-
return h("div", { key: skill.name, className: "dsm-row" }, h("div", { className: "dsm-main" }, h("div", { className: "dsm-name" }, skill.declaredName || skill.name), h("div", { className: "dsm-note" }, skill.description || t("note.missing")), skill.shadowedBy ? h("div", { className: "dsm-rule-hint" }, t("status.shadowed.hint", { name: skill.shadowedBy.name })) : null), h("div", { className: "dsm-tags" }, h("span", { className: "dsm-tag" }, rootDisplayName(t, root)), skill.preferred === true ? h("span", { className: "dsm-tag dsm-tag-on" }, t("status.preferred")) : null, !root.mutable ? h("span", { className: "dsm-tag" }, t("status.readonly")) : null, root.mutable && !root.deletable ? h("span", { className: "dsm-tag", title: t("status.notDeletable.hint") }, t("status.notDeletable")) : null), h("div", { className: "dsm-status " + cls }, t(key)), h("div", { className: "dsm-row-actions" }, canToggle && skill.shadowedBy ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", disabled: busy, title: t("btn.activate.title"), onClick: function () { activateShadowed(root, skill); } }, t("btn.activate")) : null, canToggle && !skill.shadowedBy ? h(Switch, { on: enabled, disabled: busy || root.enabled === false, label: t("skill.toggle") + " " + skill.name, onClick: function () { post(enabled ? "/disable" : "/enable", { root: root.key, name: skill.name }); } }) : null, skill.preferred === true ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", disabled: busy, title: t("btn.unprefer.title"), onClick: function () { clearPreferred(root, skill); } }, t("btn.unprefer")) : null, h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", onClick: function () { openDetail(root, skill); } }, t("btn.detail")), root.deletable === true ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", onClick: function () { setModal({ type: "trash-confirm", root: root.key, name: skill.name }); } }, t("btn.trash")) : null));
|
|
2495
|
+
return h("div", { key: skill.name, className: "dsm-row" }, h("div", { className: "dsm-main" }, h("div", { className: "dsm-name" }, skill.declaredName || skill.name), h("div", { className: "dsm-note" }, skill.description || t("note.missing")), skill.shadowedBy ? h("div", { className: "dsm-rule-hint" }, t("status.shadowed.hint", { name: skill.shadowedBy.name })) : null), h("div", { className: "dsm-tags" }, h("span", { className: "dsm-tag" }, rootDisplayName(t, root)), skill.preferred === true ? h("span", { className: "dsm-tag dsm-tag-on" }, t("status.preferred")) : null, !root.mutable ? h("span", { className: "dsm-tag" }, t("status.readonly")) : null, root.mutable && !root.deletable ? h("span", { className: "dsm-tag", title: t("status.notDeletable.hint") }, t("status.notDeletable")) : null), h("div", { className: "dsm-status " + cls }, t(key)), h("div", { className: "dsm-row-actions" }, canToggle && skill.shadowedBy ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", disabled: busy, title: t("btn.activate.title"), onClick: function () { activateShadowed(root, skill); } }, t("btn.activate")) : null, canToggle && !skill.shadowedBy ? h(Switch, { on: enabled, disabled: busy || root.enabled === false || anyLocked, label: t("skill.toggle") + " " + skill.name, onClick: function () { post(enabled ? "/disable" : "/enable", { root: root.key, name: skill.name }); } }) : null, skill.preferred === true ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", disabled: busy, title: t("btn.unprefer.title"), onClick: function () { clearPreferred(root, skill); } }, t("btn.unprefer")) : null, h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", onClick: function () { openDetail(root, skill); } }, t("btn.detail")), root.deletable === true ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", onClick: function () { setModal({ type: "trash-confirm", root: root.key, name: skill.name }); } }, t("btn.trash")) : null));
|
|
1934
2496
|
}
|
|
1935
2497
|
function renderRoot(root) {
|
|
1936
2498
|
if (activeSource && activeSource !== root.key) return null;
|
|
1937
2499
|
var displayName = rootDisplayName(t, root), filtered = root.skills.filter(function (skill) { return matchSkillQuery(Object.assign({}, skill, { rootKey: root.key, rootLabel: displayName }), query); }); if (query && !filtered.length) return null; var open = !!expanded[root.key] || !!query;
|
|
1938
2500
|
var rootCount = root.count == null ? root.skills.length : root.count;
|
|
1939
|
-
return h("section", { key: root.key, className: "dsm-source" }, h("div", { className: "dsm-source-head" }, h("button", { type: "button", className: "dsm-source-head-main", "aria-expanded": open, onClick: function () { setExpanded(Object.assign({}, expanded, { [root.key]: !open })); } }, h("span", { className: "dsm-source-title" }, displayName), h("span", { className: "dsm-count" }, t(countKey("summary.group", rootCount), { count: rootCount })), h("span", { className: "dsm-tag " + (root.scope === "project" ? "dsm-tag-on" : root.defaultSource === true ? "" : root.enabled ? "dsm-tag-on" : "dsm-tag-off") }, root.scope === "project" ? t("status.project") : root.defaultSource === true ? t("status.manageable") : t(root.enabled ? "status.source.on" : "status.source.off")), root.scope === "project" ? h("span", { className: "dsm-tag" }, t("status.rank", { rank: root.rank })) : null, h("span", { className: "dsm-path", title: root.path }, root.path)), root.scope !== "project" && root.defaultSource !== true && root.toggleable !== false ? h("span", { className: "dsm-source-actions" }, root.removable === true ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet dsm-btn-danger", disabled: busy, title: t("btn.source.remove.title"), onClick: function () { setModal({ type: "source-remove-confirm", key: root.key, name: displayName }); } }, t("btn.source.remove")) : null, h(Switch, { on: root.enabled, disabled: busy, label: t("source.toggle") + " " + displayName, onClick: function () { post(root.enabled ? "/source-disable" : "/source-enable", { root: root.key }); } })) : null), open ? h("div", { className: "dsm-source-body" }, filtered.length ? h(react.Fragment, null, h("div", { className: "dsm-table-head" }, h("span", null, t("table.skill")), h("span", null, t("filter.source")), h("span", null, t("table.status")), h("span", null, "")), filtered.map(function (skill) { return renderSkill(root, skill); })) : h("div", { className: "dsm-empty" }, query ? t("empty.search") : t("empty.source"))) : null);
|
|
2501
|
+
return h("section", { key: root.key, className: "dsm-source" }, h("div", { className: "dsm-source-head" }, h("button", { type: "button", className: "dsm-source-head-main", "aria-expanded": open, onClick: function () { setExpanded(Object.assign({}, expanded, { [root.key]: !open })); } }, h("span", { className: "dsm-source-title", title: displayName }, displayName), h("span", { className: "dsm-count" }, t(countKey("summary.group", rootCount), { count: rootCount })), h("span", { className: "dsm-tag " + (root.scope === "project" ? "dsm-tag-on" : root.defaultSource === true ? "" : root.enabled ? "dsm-tag-on" : "dsm-tag-off") }, root.scope === "project" ? t("status.project") : root.defaultSource === true ? t("status.manageable") : t(root.enabled ? "status.source.on" : "status.source.off")), root.scope === "project" ? h("span", { className: "dsm-tag" }, t("status.rank", { rank: root.rank })) : null, h("span", { className: "dsm-path", title: root.path }, root.path)), root.scope !== "project" && root.defaultSource !== true && root.toggleable !== false ? h("span", { className: "dsm-source-actions" }, root.removable === true ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet dsm-btn-danger", disabled: busy, title: t("btn.source.remove.title"), onClick: function () { setModal({ type: "source-remove-confirm", key: root.key, name: displayName }); } }, t("btn.source.remove")) : null, h(Switch, { on: root.enabled, disabled: busy || anyLocked, label: t("source.toggle") + " " + displayName, onClick: function () { post(root.enabled ? "/source-disable" : "/source-enable", { root: root.key }); } })) : null), open ? h("div", { className: "dsm-source-body" }, filtered.length ? h(react.Fragment, null, h("div", { className: "dsm-table-head" }, h("span", null, t("table.skill")), h("span", null, t("filter.source")), h("span", null, t("table.status")), h("span", null, "")), filtered.map(function (skill) { return renderSkill(root, skill); })) : h("div", { className: "dsm-empty" }, query ? t("empty.search") : t("empty.source"))) : null);
|
|
1940
2502
|
}
|
|
1941
2503
|
|
|
1942
2504
|
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"))
|
|
2505
|
+
var content = [h("style", { key: "css" }, CSS), h("div", { key: "head", className: "dsm-head" }, h("div", { className: "dsm-title-block" }, h("div", { className: "dsm-title-row" }, h("h2", { className: "dsm-title" }, t("title"))), h("p", { className: "dsm-desc" }, t("desc"))), h("div", { className: "dsm-actions" }, h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy || snapshot.loading, onClick: function () { refresh(false); } }, (busy || snapshot.loading) ? t("btn.refreshing") : t("btn.refresh")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: anyLocked, onClick: openCreate }, t("btn.create")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: anyLocked, onClick: function () { setResult(null); setUpload(null); setModal("import"); } }, t("btn.import")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy, onClick: function () { setResult(null); setModal("export"); } }, t("export.skills")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy, onClick: function () { setResult(null); setCustomForm({ path: "", label: "" }); setModal("custom-add"); } }, t("btn.custom.add")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: function () { setModal("trash"); } }, t("trash.btn.open")))), anyLocked ? h("div", { key: "lockbanner", className: "dsm-feedback dsm-warning", role: "status" }, t("lock.banner")) : null,
|
|
2506
|
+
h("div", { key: "summary", className: "dsm-summary" }, [[summary.total, "summary.total"], [summary.enabled, "summary.enabled"], [summary.issues, "summary.issues"]].map(function (item) { return h("div", { key: item[1], className: "dsm-stat" }, h("strong", null, item[0]), t(countKey(item[1], item[0]), { count: item[0] }).replace(String(item[0]), "")); })), h("div", { key: "filters", className: "dsm-filters" }, h("input", { className: "dsm-control dsm-search", value: query, "aria-label": t("search"), placeholder: t("search.placeholder"), onChange: function (e) { setQuery(e.target.value); } }), h("div", { className: "dsm-source-filter" }, h(SourceSelect, { value: activeSource, options: options, onChange: setSource }))), result && modal !== "import" ? h(Notice, { key: "result", kind: result.warning ? "warn" : result.ok ? "ok" : "err", text: result.text }) : null].concat((data.warnings || []).map(function (warning, index) { return h(Notice, { key: "warning-" + index, kind: "warn", text: translateError(t, warning) }); }), [snapshot.error ? h(Notice, { key: "error", kind: "err", text: snapshot.error }) : null, snapshot.loading && !snapshot.data ? h("div", { key: "loading", className: "dsm-empty" }, t("loading")) : h("div", { key: "sources", className: "dsm-sources" }, roots.map(renderRoot))]);
|
|
1944
2507
|
|
|
1945
2508
|
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
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
2509
|
+
// D13:导入弹窗统一用共享 ImportModal(能力取三者并集:拖拽区 + 已选文件行 +
|
|
2510
|
+
// 文件要求清单 + 选择文件夹 + 结果反馈)。技能页只保留自己的校验与提交逻辑。
|
|
2511
|
+
if (modal === "import") content.push(h(ImportModal, {
|
|
2512
|
+
key: "import", t: t, title: t("import.title"), accept: ".zip,.md", allowFolder: true,
|
|
2513
|
+
busy: busy, result: result,
|
|
2514
|
+
requirements: [t("upload.requirement.skill"), t("upload.requirement.frontmatter"), t("upload.requirement.copy")],
|
|
2515
|
+
onClose: function () { pickerOpenRef.current = false; setUpload(null); setResult(null); setModal(null); },
|
|
2516
|
+
onSubmit: function (entries, files) {
|
|
2517
|
+
var selected = inspectUploadSelection(files);
|
|
2518
|
+
if (!selected) return;
|
|
2519
|
+
if (selected.error) { setResult({ ok: false, text: translateError(t, selected.error) }); return; }
|
|
2520
|
+
setUpload(selected);
|
|
2521
|
+
submitImport(selected);
|
|
2522
|
+
},
|
|
2523
|
+
}));
|
|
2524
|
+
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"))));
|
|
2525
|
+
if (modal === "export") content.push(h(ExportModal, {
|
|
2526
|
+
key: "export", t: t, title: t("export.skills"),
|
|
2527
|
+
items: exportItems(), busy: busy, result: result,
|
|
2528
|
+
onClose: function () { setResult(null); setModal(null); },
|
|
2529
|
+
onSubmit: doExport,
|
|
2530
|
+
}));
|
|
2531
|
+
// 回收站分两部分:**目录**(被「移除来源」的目录,插件不再读取)与**技能**(移入
|
|
2532
|
+
// 回收站的技能)。两部分都给了「恢复」,永久删除在两边都指向"记录/文件真的没了":
|
|
2533
|
+
// 目录那边只删插件里的来源记录(磁盘上的文件夹一个字节都不动),技能那边才删文件。
|
|
2534
|
+
if (modal === "trash") {
|
|
2535
|
+
// 技能回收站里是**两类后果不同**的东西,弹窗里上下两块(用户要求「弹窗里嵌两个小弹窗」):
|
|
2536
|
+
// 上「不再读取的来源」——插件只是不再扫它,磁盘一个字节都不动(中性色,恢复读取 / 删除记录);
|
|
2537
|
+
// 下「回收站里的技能」——文件真的被移走了,永久删除会真删;两块的「永久删除」按钮都是红色。
|
|
2538
|
+
// 两块各自滚动,**大弹窗自己不滚**;两块一直在(空的那块显示自己的空态),别让人以为它没了。
|
|
2539
|
+
// 卡片结构复用共享回收站的 .dsm-trash-group(组头 + 计数 + 后果说明 + 行列表),观感与其它页一致。
|
|
2540
|
+
var skillTrashRows = data.trash || []
|
|
2541
|
+
var removedSourceRows = removedRoots || []
|
|
2542
|
+
function trashPane(key, title, count, note, danger, rows, emptyText) {
|
|
2543
|
+
return h("div", { key: key, className: "dsm-trash-group" + (danger ? " dsm-trash-group-danger" : "") },
|
|
2544
|
+
h("div", { className: "dsm-trash-group-head" },
|
|
2545
|
+
h("div", { className: "dsm-trash-group-head-row" },
|
|
2546
|
+
h("span", { className: "dsm-trash-group-title" }, title),
|
|
2547
|
+
h("span", { className: "dsm-count" }, count)),
|
|
2548
|
+
h("p", { className: "dsm-trash-group-sub" }, note)),
|
|
2549
|
+
h("div", { className: "dsm-trash-pane-body" }, rows.length ? rows : h("div", { className: "dsm-empty" }, emptyText)))
|
|
2550
|
+
}
|
|
2551
|
+
content.push(h(Modal, { key: "trash-modal", title: t("trash.title"), closeLabel: t("btn.close"), list: true, className: "dsm-modal-list dsm-modal-trash-split", onClose: function () { setModal(null); } },
|
|
2552
|
+
h("div", { className: "dsm-trash-split" },
|
|
2553
|
+
trashPane("pane-dirs", t("trash.section.dirs"), t(countKey("trash.dirs.count", removedSourceRows.length), { count: removedSourceRows.length }), t("trash.section.dirs.sub"), false,
|
|
2554
|
+
removedSourceRows.map(function (root) {
|
|
2555
|
+
var name = rootDisplayName(t, root)
|
|
2556
|
+
return h("div", { key: root.key, className: "dsm-trash-item" },
|
|
2557
|
+
h("div", { className: "dsm-trash-main" },
|
|
2558
|
+
h("div", { className: "dsm-name" }, name),
|
|
2559
|
+
h("div", { className: "dsm-note", title: root.path }, root.path)),
|
|
2560
|
+
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")),
|
|
2561
|
+
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.source.forget")) : null)
|
|
2562
|
+
}),
|
|
2563
|
+
t("trash.section.dirs.empty")),
|
|
2564
|
+
trashPane("pane-skills", t("trash.section.skills"), t(countKey("trash.count", skillTrashRows.length), { count: skillTrashRows.length }), t("trash.section.skills.sub"), false,
|
|
2565
|
+
skillTrashRows.map(function (item) {
|
|
2566
|
+
return h("div", { key: item.id, className: "dsm-trash-item" },
|
|
2567
|
+
h("div", { className: "dsm-trash-main" },
|
|
2568
|
+
h("div", { className: "dsm-name" }, item.name),
|
|
2569
|
+
h("div", { className: "dsm-note" }, t("trash.deletedAt", { time: new Date(item.deletedAt).toLocaleString() }))),
|
|
2570
|
+
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")),
|
|
2571
|
+
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")))
|
|
2572
|
+
}),
|
|
2573
|
+
t("trash.empty")))))
|
|
2574
|
+
}
|
|
2575
|
+
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
2576
|
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
2577
|
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
2578
|
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")))));
|
|
@@ -1984,7 +2602,6 @@ function callApi(path, options) {
|
|
|
1984
2602
|
// 导入对话弹窗:null = 关闭;{ busy, error, result, cwd } = 打开。
|
|
1985
2603
|
var impState = React.useState(null)
|
|
1986
2604
|
var imp = impState[0], setImp = impState[1]
|
|
1987
|
-
var importInputRef = React.useRef(null)
|
|
1988
2605
|
// 导出对话弹窗:null = 关闭;{ busy, error, result, outDir, format, wsFilter, selectedIds } = 打开。
|
|
1989
2606
|
var expState = React.useState(null)
|
|
1990
2607
|
var exp = expState[0], setExp = expState[1]
|
|
@@ -2217,13 +2834,6 @@ function callApi(path, options) {
|
|
|
2217
2834
|
}
|
|
2218
2835
|
reader.readAsText(file)
|
|
2219
2836
|
}
|
|
2220
|
-
function onImportDrop(e) {
|
|
2221
|
-
if (e && e.preventDefault) e.preventDefault()
|
|
2222
|
-
if (imp && imp.busy) return
|
|
2223
|
-
droppedFiles(e && e.dataTransfer).then(function (files) {
|
|
2224
|
-
if (files && files.length) doImportFile(files[0])
|
|
2225
|
-
}).catch(function () {})
|
|
2226
|
-
}
|
|
2227
2837
|
function openImportPicker() {
|
|
2228
2838
|
setImp({ busy: false, error: null, result: null, cwd: '' })
|
|
2229
2839
|
}
|
|
@@ -2380,8 +2990,8 @@ function callApi(path, options) {
|
|
|
2380
2990
|
React.createElement('span', { className: 'dsm-source-title dsm-hist-group-title', title: g.title }, g.title),
|
|
2381
2991
|
React.createElement('span', { className: 'dsm-count' }, t('hist.group.count', { count: g.items.length }))),
|
|
2382
2992
|
detached ? React.createElement('div', { className: 'dsm-hist-group-extra' },
|
|
2383
|
-
React.createElement('span', { className: 'dsm-tag' + (g.registered ? '' : ' dsm-tag-off'), title:
|
|
2384
|
-
|
|
2993
|
+
React.createElement('span', { className: 'dsm-tag' + (g.registered ? '' : ' dsm-tag-off'), title: t('hist.group.removed.hint') },
|
|
2994
|
+
t('hist.group.removed')),
|
|
2385
2995
|
g.canRegister ? React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, title: t('hist.btn.registerWs.hint'), onClick: function () { setModal({ type: 'register', path: g.path, title: g.title }) } }, t('hist.btn.registerWs')) : null) : null,
|
|
2386
2996
|
g.path ? React.createElement('span', { className: 'dsm-path', title: g.path }, g.path) : null),
|
|
2387
2997
|
open ? React.createElement('div', { className: 'dsm-source-body' }, g.items.map(renderRow)) : null)
|
|
@@ -2391,11 +3001,10 @@ function callApi(path, options) {
|
|
|
2391
3001
|
React.createElement('div', { className: 'dsm-head' },
|
|
2392
3002
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
2393
3003
|
React.createElement('div', { className: 'dsm-title-row' },
|
|
2394
|
-
React.createElement('h2', { className: 'dsm-title' }, t('tabs.sessions')),
|
|
2395
|
-
VersionBadgeComponent ? React.createElement(VersionBadgeComponent, null) : null),
|
|
3004
|
+
React.createElement('h2', { className: 'dsm-title' }, t('tabs.sessions'))),
|
|
2396
3005
|
React.createElement('p', { className: 'dsm-desc' }, t('sessions.desc'))),
|
|
2397
3006
|
React.createElement('div', { className: 'dsm-actions' },
|
|
2398
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-
|
|
3007
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: data.loading, onClick: function () { refresh() } }, data.loading ? t('btn.refreshing') : t('btn.refresh')),
|
|
2399
3008
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: openImportPicker }, t('hist.btn.import')),
|
|
2400
3009
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: openExportPicker }, t('hist.btn.export')),
|
|
2401
3010
|
selected.size > 0 ? React.createElement('div', { className: 'dsm-hist-batch' },
|
|
@@ -2403,11 +3012,13 @@ function callApi(path, options) {
|
|
|
2403
3012
|
React.createElement('div', { className: 'dsm-hist-batch-actions' },
|
|
2404
3013
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { doUnarchiveBatch() } }, t('hist.btn.restoreSelected', { count: selected.size })),
|
|
2405
3014
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { setModal({ type: 'deleteBatch', count: selected.size }) } }, t('hist.btn.deleteSelected', { count: selected.size })))) : null)),
|
|
2406
|
-
React.createElement('div', { className: 'dsm-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
React.createElement('
|
|
2410
|
-
|
|
3015
|
+
React.createElement('div', { className: 'dsm-stat-row' },
|
|
3016
|
+
React.createElement('div', { className: 'dsm-summary' },
|
|
3017
|
+
[['archived', data.items.length, t('hist.stat.archived')], ['projects', grouped ? groups.length : '—', t('hist.stat.projects')], ['retention', data.retentionDays ? String(data.retentionDays) : '∞', t('hist.stat.retention')]].map(function (item) {
|
|
3018
|
+
return React.createElement('div', { key: item[0], className: 'dsm-stat' },
|
|
3019
|
+
React.createElement('strong', null, item[1]), item[2])
|
|
3020
|
+
})),
|
|
3021
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: !filtered.length, onClick: function () { toggleAllVisible(visibleIds) } }, allVisible ? t('hist.deselectAll') : t('hist.selectAll'))),
|
|
2411
3022
|
React.createElement('div', { className: 'dsm-filters' },
|
|
2412
3023
|
React.createElement('input', { className: 'dsm-control dsm-search', type: 'text', placeholder: t('hist.search.placeholder'), value: query, onChange: function (e) { setQuery(e.target.value) } }),
|
|
2413
3024
|
React.createElement('div', { className: 'dsm-source-filter' },
|
|
@@ -2420,15 +3031,15 @@ function callApi(path, options) {
|
|
|
2420
3031
|
: filtered.length === 0 ? React.createElement('div', { className: 'dsm-empty' }, query ? t('hist.empty.search') : t('hist.empty.none'))
|
|
2421
3032
|
: React.createElement('div', { className: 'dsm-sources' },
|
|
2422
3033
|
grouped ? groups.map(renderGroup) : React.createElement('div', { className: 'dsm-source' }, filtered.map(renderRow))),
|
|
2423
|
-
modal && modal.type === 'delete' ? React.createElement(Modal, { key: 'del', title: t('hist.purge.title'), closeLabel: t('btn.
|
|
3034
|
+
modal && modal.type === 'delete' ? React.createElement(Modal, { key: 'del', title: t('hist.purge.title'), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
2424
3035
|
React.createElement('p', { className: 'dsm-help' }, t('hist.purge.one', { title: modal.title })),
|
|
2425
3036
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
2426
3037
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { doDelete(modal.sessionId) } }, t('hist.btn.confirmPurge')))) : null,
|
|
2427
|
-
modal && modal.type === 'deleteBatch' ? React.createElement(Modal, { key: 'delb', title: t('hist.purge.title'), closeLabel: t('btn.
|
|
3038
|
+
modal && modal.type === 'deleteBatch' ? React.createElement(Modal, { key: 'delb', title: t('hist.purge.title'), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
2428
3039
|
React.createElement('p', { className: 'dsm-help' }, t('hist.purge.batch', { count: modal.count })),
|
|
2429
3040
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
2430
3041
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { doDeleteBatch() } }, t('hist.btn.confirmPurge')))) : null,
|
|
2431
|
-
modal && modal.type === 'register' ? React.createElement(Modal, { key: 'reg', title: t('hist.register.title'), closeLabel: t('btn.
|
|
3042
|
+
modal && modal.type === 'register' ? React.createElement(Modal, { key: 'reg', title: t('hist.register.title'), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
2432
3043
|
React.createElement('p', { className: 'dsm-help' }, t('hist.register.body', { title: modal.title })),
|
|
2433
3044
|
React.createElement('p', { className: 'dsm-help dsm-hist-register-path' }, String(modal.path || '')),
|
|
2434
3045
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
@@ -2441,18 +3052,24 @@ function callApi(path, options) {
|
|
|
2441
3052
|
(modal.workspace && modal.workspace.attachSkipped && modal.workspace.attachSkipped.length)
|
|
2442
3053
|
? React.createElement('p', { className: 'dsm-help dsm-warning' }, t('hist.register.done.skipped', { count: modal.workspace.attachSkipped.length, reason: String(modal.workspace.attachSkipped[0].reason || '') }))
|
|
2443
3054
|
: null) : null,
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
imp.error ?
|
|
2454
|
-
|
|
2455
|
-
|
|
3055
|
+
// D13:会话页也改用共享 ImportModal(能力取三者并集)。差异只在 extra 区
|
|
3056
|
+
// (项目目录)与提交回调:选完文件直接导入,没有二次确认。
|
|
3057
|
+
imp ? React.createElement(ImportModal, {
|
|
3058
|
+
key: 'imp',
|
|
3059
|
+
t: t,
|
|
3060
|
+
title: t('hist.btn.import'),
|
|
3061
|
+
accept: '.jsonl,.json,.md,.markdown,.txt',
|
|
3062
|
+
busy: imp.busy === true,
|
|
3063
|
+
requirements: [t('upload.requirement.session.1'), t('upload.requirement.session.2'), t('upload.requirement.session.3')],
|
|
3064
|
+
result: imp.error ? { ok: false, text: String(imp.error) } : (imp.result ? { ok: true, text: t('hist.import.done', { id: imp.result.sessionId, count: imp.result.count }) } : null),
|
|
3065
|
+
extra: React.createElement('div', null,
|
|
3066
|
+
React.createElement('div', { className: 'dsm-field' },
|
|
3067
|
+
React.createElement('label', { className: 'dsm-label' }, t('hist.import.cwd')),
|
|
3068
|
+
React.createElement('input', { className: 'dsm-control', type: 'text', placeholder: t('hist.import.cwd.placeholder'), value: imp.cwd || '', onChange: function (e) { setImp(Object.assign({}, imp, { cwd: e.target.value })) } }))),
|
|
3069
|
+
onClose: function () { setImp(null) },
|
|
3070
|
+
onSubmit: function (entries, files) { doImportFile(files[0]) },
|
|
3071
|
+
}) : null,
|
|
3072
|
+
exp ? React.createElement(Modal, { key: 'exp', className: 'dsm-modal-list dsm-export-modal', list: true, title: t('hist.btn.export'), closeLabel: t('btn.close'), onClose: function () { setExp(null) } },
|
|
2456
3073
|
React.createElement('p', { className: 'dsm-help' }, t('hist.export.hint')),
|
|
2457
3074
|
React.createElement('div', { className: 'dsm-field' },
|
|
2458
3075
|
React.createElement('label', { className: 'dsm-label' }, t('hist.export.dir')),
|
|
@@ -2469,19 +3086,37 @@ function callApi(path, options) {
|
|
|
2469
3086
|
React.createElement('div', { className: 'dsm-field' },
|
|
2470
3087
|
React.createElement('label', { className: 'dsm-label' }, t('hist.export.workspace')),
|
|
2471
3088
|
React.createElement(SourceSelect, { options: exportWsOptions, value: exp.wsFilter, onChange: function (v) { setExp(Object.assign({}, exp, { wsFilter: v, selectedIds: new Set() })) } })),
|
|
2472
|
-
React.createElement('div', { className: 'dsm-field' },
|
|
3089
|
+
React.createElement('div', { className: 'dsm-field dsm-export-body' },
|
|
2473
3090
|
React.createElement('div', { className: 'dsm-label' }, t('hist.export.sessions', { count: exportItems.length })),
|
|
2474
|
-
React.createElement('div', { className: 'dsm-source' },
|
|
3091
|
+
React.createElement('div', { className: 'dsm-source dsm-pick-list' },
|
|
2475
3092
|
exp.loading ? React.createElement('div', { className: 'dsm-empty' }, t('hist.export.loading'))
|
|
2476
|
-
: exportItems.length ?
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
3093
|
+
: exportItems.length ? (function () {
|
|
3094
|
+
// 按**工作区目录**分组:不同文件夹的会话分开列,组头可折叠。
|
|
3095
|
+
var groups = [], byCwd = {}
|
|
3096
|
+
exportItems.forEach(function (it) {
|
|
3097
|
+
var ck = String(it.cwd || '')
|
|
3098
|
+
if (!byCwd[ck]) { byCwd[ck] = []; groups.push({ cwd: ck, rows: byCwd[ck] }) }
|
|
3099
|
+
byCwd[ck].push(it)
|
|
3100
|
+
})
|
|
3101
|
+
return groups.map(function (g) {
|
|
3102
|
+
var key = 'exp:' + g.cwd
|
|
3103
|
+
var open = collapsed[key] !== true
|
|
3104
|
+
return React.createElement('div', { key: key, className: 'dsm-pick-group' },
|
|
3105
|
+
React.createElement('button', { type: 'button', className: 'dsm-pick-group-head', 'aria-expanded': open ? 'true' : 'false', onClick: function () { toggleGroupCollapse(key) } },
|
|
3106
|
+
React.createElement('span', { className: 'dsm-pick-caret', 'aria-hidden': 'true' }, open ? '▾' : '▸'),
|
|
3107
|
+
React.createElement('span', { className: 'dsm-pick-group-name', title: g.cwd || t('hist.ungrouped') }, g.cwd || t('hist.ungrouped')),
|
|
3108
|
+
React.createElement('span', { className: 'dsm-pick-group-count' }, t('export.group.count', { count: g.rows.length }))),
|
|
3109
|
+
open ? g.rows.map(function (it) {
|
|
3110
|
+
return React.createElement('label', { key: it.sessionId, className: 'dsm-hist-row' },
|
|
3111
|
+
React.createElement('input', { type: 'checkbox', className: 'dsm-hist-check', checked: exp.selectedIds.has(it.sessionId), onChange: function () { toggleExportSelect(it.sessionId) } }),
|
|
3112
|
+
React.createElement('div', { className: 'dsm-hist-main' },
|
|
3113
|
+
React.createElement('div', { className: 'dsm-hist-title' }, it.title || ('Session ' + shortId(it.sessionId))),
|
|
3114
|
+
it.cwdMissing ? React.createElement('div', { className: 'dsm-hist-cwd dsm-hist-cwd-missing', title: t('hist.cwd.missing') + it.cwd }, '⚠ ' + it.cwd) : null),
|
|
3115
|
+
React.createElement('div', { className: 'dsm-hist-actions' },
|
|
3116
|
+
React.createElement('span', { className: 'dsm-tag' + (it.archived ? '' : ' dsm-tag-on') }, it.archived ? t('hist.tag.archived') : t('hist.tag.live'))))
|
|
3117
|
+
}) : null)
|
|
3118
|
+
})
|
|
3119
|
+
})() : React.createElement('div', { className: 'dsm-empty' }, t('hist.export.empty')))),
|
|
2485
3120
|
exp.error ? React.createElement('div', { className: 'dsm-feedback dsm-error' }, String(exp.error)) : null,
|
|
2486
3121
|
exp.result ? React.createElement('div', { className: 'dsm-feedback' },
|
|
2487
3122
|
exp.result.exported
|
|
@@ -2617,6 +3252,34 @@ function callApi(path, options) {
|
|
|
2617
3252
|
var presetOptions = pos[0], setPresetOptions = pos[1]
|
|
2618
3253
|
var dts = React.useState(null)
|
|
2619
3254
|
var drillTools = dts[0], setDrillTools = dts[1]
|
|
3255
|
+
// 场景回收站(记录 + 档案):null = 关闭;{loading, error, entries}
|
|
3256
|
+
var sts = React.useState(null)
|
|
3257
|
+
var trash = sts[0], setTrash = sts[1]
|
|
3258
|
+
var stb = React.useState(false)
|
|
3259
|
+
var trashBusy = stb[0], setTrashBusy = stb[1]
|
|
3260
|
+
function loadTrash(keepOpen) {
|
|
3261
|
+
setTrash(Object.assign({ loading: true, error: null, entries: [] }, keepOpen ? trash || {} : {}))
|
|
3262
|
+
apiCall('scene-trash-list', {}).then(function (res) {
|
|
3263
|
+
if (res && res.ok) setTrash({ loading: false, error: null, entries: res.trash || [] })
|
|
3264
|
+
else setTrash({ loading: false, error: (res && res.error) || t('trash.loadFailed'), entries: [] })
|
|
3265
|
+
}).catch(function (e) { setTrash({ loading: false, error: String((e && e.message) || e), entries: [] }) })
|
|
3266
|
+
}
|
|
3267
|
+
function restoreScene(item) {
|
|
3268
|
+
setTrashBusy(true)
|
|
3269
|
+
apiCall('scene-trash-restore', { id: item.id }).then(function (res) {
|
|
3270
|
+
setTrashBusy(false)
|
|
3271
|
+
if (res && res.ok) { setResult({ ok: true, text: t('trash.restored', { name: item.name }) }); refresh(true); loadTrash(true) }
|
|
3272
|
+
else setTrash(function (cur) { return Object.assign({}, cur, { error: (res && res.error) || t('mcp.msg.failed') }) })
|
|
3273
|
+
}).catch(function (e) { setTrashBusy(false); setTrash(function (cur) { return Object.assign({}, cur, { error: String((e && e.message) || e) }) }) })
|
|
3274
|
+
}
|
|
3275
|
+
function purgeScene(item) {
|
|
3276
|
+
setTrashBusy(true)
|
|
3277
|
+
apiCall('scene-trash-delete', { id: item.id }).then(function (res) {
|
|
3278
|
+
setTrashBusy(false)
|
|
3279
|
+
if (res && res.ok) { setResult({ ok: true, text: t('trash.purged', { name: item.name }) }); loadTrash(true) }
|
|
3280
|
+
else setTrash(function (cur) { return Object.assign({}, cur, { error: (res && res.error) || t('mcp.msg.failed') }) })
|
|
3281
|
+
}).catch(function (e) { setTrashBusy(false); setTrash(function (cur) { return Object.assign({}, cur, { error: String((e && e.message) || e) }) }) })
|
|
3282
|
+
}
|
|
2620
3283
|
React.useEffect(function () { if (!result || result.ok !== true) return undefined; var timer = setTimeout(function () { setResult(null) }, 2600); return function () { clearTimeout(timer) } }, [result])
|
|
2621
3284
|
function refresh(silent) {
|
|
2622
3285
|
if (!silent) setData(function (prev) { return Object.assign({}, prev, { loading: true, error: null }) })
|
|
@@ -2657,10 +3320,30 @@ function callApi(path, options) {
|
|
|
2657
3320
|
else setResult({ ok: false, text: translateError(t, res) })
|
|
2658
3321
|
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
2659
3322
|
}
|
|
3323
|
+
/**
|
|
3324
|
+
* 场景开关(P6):**唯一入口**。
|
|
3325
|
+
*
|
|
3326
|
+
* 开 = 进入该场景:应用档案(MCP/技能双向切换)+ 启用场景(提示词绑定生效 + 记忆范围收窄);
|
|
3327
|
+
* 关 = 回到全局默认:恢复档案快照 + 取消场景启用。
|
|
3328
|
+
*
|
|
3329
|
+
* ⚠️ 核心约束:`mode.scene` 与 `active` **必须恒等**。此前开关只写 `active`、
|
|
3330
|
+
* 「切入此模式」按钮只写 `mode.scene`,两者互不知情 —— 于是「拨开关不应用档案」与
|
|
3331
|
+
* 「顶部横幅永远不显示」同时发生。现在两条路径都走 enterMode / exitMode。
|
|
3332
|
+
*/
|
|
2660
3333
|
function toggleScene(scene) {
|
|
2661
3334
|
if (busy || scene.shared || scene.global) return
|
|
2662
|
-
|
|
2663
|
-
|
|
3335
|
+
if (scene.active === true) exitMode()
|
|
3336
|
+
else enterMode(scene.name)
|
|
3337
|
+
}
|
|
3338
|
+
/** 场景锁定(v0.8):锁上后五个管理域整体只读(场景页 + 各功能页);启停不受影响。 */
|
|
3339
|
+
function toggleSceneLock(scene) {
|
|
3340
|
+
if (busy || scene.shared || scene.global) return
|
|
3341
|
+
setBusy(true)
|
|
3342
|
+
apiCall('rules-scene-lock', { scene: scene.name, locked: scene.locked !== true }).then(function (res) {
|
|
3343
|
+
setBusy(false)
|
|
3344
|
+
if (res && res.ok) refresh(true)
|
|
3345
|
+
else setResult({ ok: false, text: translateError(t, res) })
|
|
3346
|
+
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
2664
3347
|
}
|
|
2665
3348
|
/**
|
|
2666
3349
|
* 提示词预设候选(打开表单时拉一次;失败就只留「不绑定」)。
|
|
@@ -2700,8 +3383,13 @@ function callApi(path, options) {
|
|
|
2700
3383
|
if (!isValidSceneName(name)) { setSceneForm(Object.assign({}, sceneForm, { error: t('error.rules.invalidGroup') })); return }
|
|
2701
3384
|
var description = String(sceneForm.description || '').trim()
|
|
2702
3385
|
var prompt = String(sceneForm.prompt || '').trim()
|
|
3386
|
+
// 编辑时表单里的 name 可能改过:改名走 nextName,服务端按 modal.name(原名)定位、改完再落新名。
|
|
3387
|
+
var originalName = isEdit ? String((modal && modal.name) || name) : name
|
|
2703
3388
|
setBusy(true)
|
|
2704
|
-
|
|
3389
|
+
var payload = isEdit
|
|
3390
|
+
? { name: originalName, nextName: name, description: description, prompt: prompt }
|
|
3391
|
+
: { name: name, description: description, prompt: prompt }
|
|
3392
|
+
apiCall(isEdit ? 'rules-update-scene' : 'rules-create-scene', payload).then(function (res) {
|
|
2705
3393
|
setBusy(false)
|
|
2706
3394
|
if (res && res.ok) {
|
|
2707
3395
|
setModal(null)
|
|
@@ -2715,8 +3403,17 @@ function callApi(path, options) {
|
|
|
2715
3403
|
setBusy(true)
|
|
2716
3404
|
apiCall('rules-remove-scene', { name: name }).then(function (res) {
|
|
2717
3405
|
setBusy(false); setModal(null)
|
|
2718
|
-
if (res && res.ok) {
|
|
2719
|
-
|
|
3406
|
+
if (res && res.ok) {
|
|
3407
|
+
var moved = Number((res && res.movedFiles) || 0)
|
|
3408
|
+
// 如实说清「一起删了多少条记忆」:删除场景现在会带走它下面的全部记忆。
|
|
3409
|
+
setResult({
|
|
3410
|
+
ok: true,
|
|
3411
|
+
text: moved > 0
|
|
3412
|
+
? t('memory.result.sceneRemoved.withMemories', { name: name, count: moved })
|
|
3413
|
+
: t('memory.result.sceneRemoved', { name: name }),
|
|
3414
|
+
})
|
|
3415
|
+
refresh(true)
|
|
3416
|
+
} else setResult({ ok: false, text: translateError(t, res) })
|
|
2720
3417
|
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
2721
3418
|
}
|
|
2722
3419
|
// ── 档案(mcp 两级 / skills / subagents 三段自由搭配)──
|
|
@@ -2737,6 +3434,8 @@ function callApi(path, options) {
|
|
|
2737
3434
|
},
|
|
2738
3435
|
inventory: {
|
|
2739
3436
|
mcpServers: inv.mcpServers || [], skills: inv.skills || [], subagents: inv.subagents || [], tools: inv.tools || [],
|
|
3437
|
+
// 模式名与顺序(标准 → PTC → 极简 → 创造 → 自建):人设行的限制摘要按这个顺序排。
|
|
3438
|
+
presets: inv.presets || [],
|
|
2740
3439
|
// 记忆段的数据源(宿主 scene-inventory 回传;老宿主缺失时退化为空列表而不是崩)。
|
|
2741
3440
|
// 只留扁平 memories——记忆段现在按「记忆所属场景」自己筛,不再需要场景清单。
|
|
2742
3441
|
memories: inv.memories || [],
|
|
@@ -2775,11 +3474,11 @@ function callApi(path, options) {
|
|
|
2775
3474
|
setSections(Object.assign({}, sections, { mcp: mcp }))
|
|
2776
3475
|
}
|
|
2777
3476
|
/** 档案各段共用的段头动作(包装共享的 segActions,把 busy / 文案在这里补上)。 */
|
|
2778
|
-
function archiveSegActions(defined, addLabel, onAdd, onRemove, onAll, onClear) {
|
|
3477
|
+
function archiveSegActions(defined, addLabel, onAdd, onRemove, onAll, onClear, allChecked) {
|
|
2779
3478
|
return segActions({
|
|
2780
3479
|
defined: defined, busy: busy, addLabel: addLabel,
|
|
2781
3480
|
selectAllLabel: t('scenes.seg.selectAll'), clearLabel: t('scenes.seg.clear'), removeLabel: t('memory.archive.removeSection'),
|
|
2782
|
-
onAdd: onAdd, onRemove: onRemove, onAll: onAll, onClear: onClear,
|
|
3481
|
+
onAdd: onAdd, onRemove: onRemove, onAll: onAll, onClear: onClear, allChecked: allChecked,
|
|
2783
3482
|
})
|
|
2784
3483
|
}
|
|
2785
3484
|
/**
|
|
@@ -2790,74 +3489,80 @@ function callApi(path, options) {
|
|
|
2790
3489
|
return segFoot(defined, count, emptyLabel || t('memory.archive.emptySection'))
|
|
2791
3490
|
}
|
|
2792
3491
|
|
|
2793
|
-
// ── 段 4
|
|
2794
|
-
//
|
|
2795
|
-
//
|
|
2796
|
-
//
|
|
2797
|
-
//
|
|
2798
|
-
//
|
|
2799
|
-
//
|
|
2800
|
-
//
|
|
2801
|
-
//
|
|
3492
|
+
// ── 段 4:记忆(P5:单一真相源 = `rules[*].enabled`)────────────────────
|
|
3493
|
+
// 这个段**没有自己的「段定义」概念**:勾选状态**就是**记忆自身的 `enabled`,
|
|
3494
|
+
// 与记忆页同一个写入口(`rules-toggle`)。于是「记忆页开了 → 档案页显示开启」
|
|
3495
|
+
// 天然成立,不存在两套状态、也不需要双向同步代码。
|
|
3496
|
+
//
|
|
3497
|
+
// 勾选**立即提交**(不等「保存到场景」):它是全局状态,不属于场景档案。
|
|
3498
|
+
//
|
|
3499
|
+
// **只列被编辑场景自己的记忆**:保留场景 `global` 恒定注入(任何对话都生效),
|
|
3500
|
+
// 不需要在这里配置,列出来只会让人以为「不勾就不注入」。
|
|
2802
3501
|
function sceneMemories(sceneName) {
|
|
2803
3502
|
return sceneMemoriesOf(modal.inventory.memories, sceneName)
|
|
2804
3503
|
}
|
|
2805
|
-
/**
|
|
3504
|
+
/** 「全选 / 全不选」作用的 id 集合 = 本场景的全部记忆。 */
|
|
2806
3505
|
function allMemoryIds() {
|
|
2807
3506
|
return sceneMemories(modal.name).map(function (m) { return String(m.id) })
|
|
2808
3507
|
}
|
|
2809
|
-
/**
|
|
2810
|
-
function
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
function memCheckedCount() {
|
|
2814
|
-
var sections = modalSections()
|
|
2815
|
-
return sections.memories ? sections.memories.length : 0
|
|
3508
|
+
/** 记忆是否开启 —— 直接读 `rules[*].enabled`(单一真相源)。 */
|
|
3509
|
+
function memoryEnabled(id) {
|
|
3510
|
+
var rule = (data.rules || []).find(function (r) { return r.id === id })
|
|
3511
|
+
return !!(rule && rule.enabled !== false)
|
|
2816
3512
|
}
|
|
2817
3513
|
function toggleMemory(id) {
|
|
2818
|
-
var
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
3514
|
+
var next = !memoryEnabled(id)
|
|
3515
|
+
setBusy(true)
|
|
3516
|
+
apiCall('rules-toggle', { id: id, enabled: next }).then(function (res) {
|
|
3517
|
+
setBusy(false)
|
|
3518
|
+
if (res && res.ok) refresh(true)
|
|
3519
|
+
else setResult({ ok: false, text: translateError(t, res) })
|
|
3520
|
+
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
3521
|
+
}
|
|
3522
|
+
/** 全选 / 全不选:批量写 `enabled`(顺序提交,失败即停并如实报错)。 */
|
|
3523
|
+
function setAllMemories(on) {
|
|
3524
|
+
var ids = allMemoryIds()
|
|
3525
|
+
if (!ids.length) return
|
|
3526
|
+
setBusy(true)
|
|
3527
|
+
Promise.all(ids.map(function (id) { return apiCall('rules-toggle', { id: id, enabled: on }) }))
|
|
3528
|
+
.then(function () { setBusy(false); refresh(true) })
|
|
3529
|
+
.catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
2823
3530
|
}
|
|
2824
3531
|
function memoriesSeg() {
|
|
2825
|
-
var sections = modalSections()
|
|
2826
|
-
var defined = !!sections.memories
|
|
2827
3532
|
var own = sceneMemories(modal.name)
|
|
2828
3533
|
var q = String(modal.memQuery || '').trim().toLowerCase()
|
|
2829
3534
|
var items = q ? own.filter(function (m) {
|
|
2830
3535
|
return String(m.name).toLowerCase().indexOf(q) >= 0
|
|
2831
3536
|
|| String(m.description || '').toLowerCase().indexOf(q) >= 0
|
|
2832
3537
|
}) : own
|
|
3538
|
+
var enabledCount = own.filter(function (m) { return memoryEnabled(String(m.id)) }).length
|
|
2833
3539
|
return seg({
|
|
2834
3540
|
title: t('memory.archive.memories'),
|
|
2835
|
-
count:
|
|
2836
|
-
actions:
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
function () {
|
|
2840
|
-
function () {
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
foot: archiveSegFoot(defined, memCheckedCount()),
|
|
3541
|
+
count: t('scenes.seg.checked', { checked: enabledCount, total: own.length }),
|
|
3542
|
+
actions: segActions({
|
|
3543
|
+
defined: true, busy: busy,
|
|
3544
|
+
selectAllLabel: t('scenes.seg.selectAll'), clearLabel: t('scenes.seg.clear'),
|
|
3545
|
+
onAll: function () { setAllMemories(true) },
|
|
3546
|
+
onClear: function () { setAllMemories(false) },
|
|
3547
|
+
allChecked: own.length > 0 && enabledCount === own.length,
|
|
3548
|
+
}),
|
|
3549
|
+
body: React.createElement('div', { className: 'dsm-seg-body' },
|
|
3550
|
+
segFilter(modal.memQuery, function (v) { setModal(Object.assign({}, modal, { memQuery: v })) }, t('scenes.mem.search')),
|
|
3551
|
+
items.length
|
|
3552
|
+
? React.createElement('div', null, items.map(function (m) {
|
|
3553
|
+
return pickRow({
|
|
3554
|
+
disabled: busy,
|
|
3555
|
+
key: m.id,
|
|
3556
|
+
checked: memoryEnabled(String(m.id)),
|
|
3557
|
+
name: m.name,
|
|
3558
|
+
// 记忆描述是自由正文的首行,可能很长:截断到 MEM_DESC_MAX,全文放 title。
|
|
3559
|
+
// CSS 侧还有单行省略兜底(超长不再横向溢出段边框)。
|
|
3560
|
+
desc: m.description ? clipText(m.description, MEM_DESC_MAX) : null,
|
|
3561
|
+
descTitle: m.description || null,
|
|
3562
|
+
onChange: function () { toggleMemory(String(m.id)) },
|
|
3563
|
+
})
|
|
3564
|
+
}))
|
|
3565
|
+
: React.createElement('div', { className: 'dsm-pick-empty' }, q ? t('scenes.mem.noMatch') : t('scenes.mem.emptyScene'))),
|
|
2861
3566
|
})
|
|
2862
3567
|
}
|
|
2863
3568
|
/** 段 1:MCP 工具集(服务器级勾选 → 行内「选工具」进工具明细,明细留在同一段内)。 */
|
|
@@ -2931,15 +3636,44 @@ function callApi(path, options) {
|
|
|
2931
3636
|
})
|
|
2932
3637
|
}
|
|
2933
3638
|
/** 段 2:技能集。 */
|
|
3639
|
+
/**
|
|
3640
|
+
* 技能行的人类可读展示:技能名 + 来源名。
|
|
3641
|
+
*
|
|
3642
|
+
* 勾选集里存的仍是 `key`(`<来源 key>/<技能名>`)——它才是身份的载体;但**显示**
|
|
3643
|
+
* 必须用技能名与来源名:自定义目录的来源 key 是 `custom-<hash>`,直接显示 key 会
|
|
3644
|
+
* 让导入的技能在场景里变成一串哈希(用户 2026-09-15 报的)。
|
|
3645
|
+
* 老宿主没回传 name/root* 时退回按 key 拆,至少不再显示整串哈希前缀之外的路径。
|
|
3646
|
+
*/
|
|
3647
|
+
function skillDisplay(item) {
|
|
3648
|
+
var key = String((item && item.key) || '')
|
|
3649
|
+
var slash = key.indexOf('/')
|
|
3650
|
+
var keyName = slash >= 0 ? key.slice(slash + 1) : key
|
|
3651
|
+
var keyRoot = slash >= 0 ? key.slice(0, slash) : ''
|
|
3652
|
+
var root = {
|
|
3653
|
+
key: String((item && item.rootKey) || keyRoot),
|
|
3654
|
+
label: String((item && item.rootLabel) || keyRoot),
|
|
3655
|
+
localeKey: item && item.rootLocaleKey,
|
|
3656
|
+
kind: item && item.rootKind,
|
|
3657
|
+
}
|
|
3658
|
+
return {
|
|
3659
|
+
name: String((item && item.name) || keyName || key),
|
|
3660
|
+
source: root.key ? rootDisplayName(t, root) : '',
|
|
3661
|
+
key: key,
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
2934
3664
|
function skillsSeg() {
|
|
2935
3665
|
var sections = modalSections()
|
|
2936
3666
|
var defined = !!sections.skills
|
|
3667
|
+
var allItems = modal.inventory.skills || []
|
|
2937
3668
|
var items = (function () {
|
|
2938
3669
|
var q = String(modal.skillQuery || '').trim().toLowerCase()
|
|
2939
|
-
|
|
2940
|
-
|
|
3670
|
+
if (!q) return allItems
|
|
3671
|
+
// 搜索同时匹配技能名与来源名(用户按"来源"找技能是常见动作)。
|
|
3672
|
+
return allItems.filter(function (x) {
|
|
3673
|
+
var d = skillDisplay(x)
|
|
3674
|
+
return (d.name + ' ' + d.source + ' ' + d.key).toLowerCase().indexOf(q) >= 0
|
|
3675
|
+
})
|
|
2941
3676
|
})()
|
|
2942
|
-
var allItems = modal.inventory.skills || []
|
|
2943
3677
|
return seg({
|
|
2944
3678
|
title: t('memory.archive.skills'),
|
|
2945
3679
|
count: defined ? t('scenes.seg.checked', { checked: sections.skills.length, total: allItems.length }) : t('scenes.archive.sectionOff'),
|
|
@@ -2953,12 +3687,17 @@ function callApi(path, options) {
|
|
|
2953
3687
|
segFilter(modal.skillQuery, function (v) { setModal(Object.assign({}, modal, { skillQuery: v })) }, t('scenes.filter.skills')),
|
|
2954
3688
|
items.length
|
|
2955
3689
|
? React.createElement('div', null, items.map(function (item) {
|
|
3690
|
+
var d = skillDisplay(item)
|
|
2956
3691
|
return pickRow({
|
|
2957
3692
|
disabled: busy,
|
|
2958
3693
|
key: item.key,
|
|
2959
3694
|
checked: sections.skills.indexOf(item.key) >= 0,
|
|
2960
|
-
name:
|
|
2961
|
-
|
|
3695
|
+
name: d.name,
|
|
3696
|
+
desc: d.source || null,
|
|
3697
|
+
descTitle: d.key,
|
|
3698
|
+
meta: item.shadowed
|
|
3699
|
+
? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, t('status.shadowed'))
|
|
3700
|
+
: (item.enabled === false ? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, t('memory.scene.off')) : null),
|
|
2962
3701
|
onChange: function () {
|
|
2963
3702
|
var list = sections.skills.slice(); var i = list.indexOf(item.key)
|
|
2964
3703
|
if (i >= 0) list.splice(i, 1); else list.push(item.key)
|
|
@@ -2972,6 +3711,35 @@ function callApi(path, options) {
|
|
|
2972
3711
|
})
|
|
2973
3712
|
}
|
|
2974
3713
|
/** 段 3:子智能体绑定。 */
|
|
3714
|
+
/** 模式名(官方四个走词典,自建预设用它自己的名字,不翻译)。 */
|
|
3715
|
+
function scenePresetLabel(id, short) {
|
|
3716
|
+
var presets = (modal.inventory && modal.inventory.presets) || []
|
|
3717
|
+
var hit = presets.filter(function (p) { return p.id === id })[0]
|
|
3718
|
+
var shipped = { minimal: 1, standard: 1, ptc: 1, cordis: 1 }
|
|
3719
|
+
if (shipped[id] === 1 && (!hit || String(hit.trust || 'system') !== 'user')) {
|
|
3720
|
+
return t((short ? 'preset.short.' : 'preset.name.') + id)
|
|
3721
|
+
}
|
|
3722
|
+
return hit ? String(hit.name || id) : id
|
|
3723
|
+
}
|
|
3724
|
+
/**
|
|
3725
|
+
* 人设的按模式工具限制摘要(「标准 白3 · PTC 黑2」)。场景本身不引入"模式"这个
|
|
3726
|
+
* 维度 —— 会话跑在哪个模式是建会话时定的,场景改不了;这里只如实显示配了什么。
|
|
3727
|
+
*/
|
|
3728
|
+
function personaRuleSummary(p) {
|
|
3729
|
+
var rules = (p && p.toolsByPreset) || {}
|
|
3730
|
+
var order = ((modal.inventory && modal.inventory.presets) || []).map(function (x) { return x.id })
|
|
3731
|
+
// 按 roster 顺序排(标准 → PTC → 极简 → 创造 → 自建),文件里的书写顺序不参与展示。
|
|
3732
|
+
var ids = Object.keys(rules).sort(function (a, b) {
|
|
3733
|
+
var ia = order.indexOf(a), ib = order.indexOf(b)
|
|
3734
|
+
return (ia < 0 ? 999 : ia) - (ib < 0 ? 999 : ib)
|
|
3735
|
+
})
|
|
3736
|
+
if (!ids.length) return null
|
|
3737
|
+
return ids.map(function (id) {
|
|
3738
|
+
var rule = rules[id] || {}
|
|
3739
|
+
var count = (rule.names || []).length
|
|
3740
|
+
return scenePresetLabel(id, true) + ' ' + t(rule.mode === 'deny' ? 'subagents.mode.denyOn' : 'subagents.mode.allowOn', { count: count })
|
|
3741
|
+
}).join(' · ')
|
|
3742
|
+
}
|
|
2975
3743
|
function subagentsSeg() {
|
|
2976
3744
|
var sections = modalSections()
|
|
2977
3745
|
var defined = !!sections.subagents
|
|
@@ -3001,6 +3769,9 @@ function callApi(path, options) {
|
|
|
3001
3769
|
checked: sections.subagents.indexOf(item.name) >= 0,
|
|
3002
3770
|
name: item.name,
|
|
3003
3771
|
desc: item.description || null,
|
|
3772
|
+
// 按模式配的工具限制摘要(没配就不显示):场景改不了会话的模式,
|
|
3773
|
+
// 但要让用户看得见"这条人设在某些模式下是被限工具的"。
|
|
3774
|
+
meta: personaRuleSummary(item),
|
|
3004
3775
|
onChange: function () {
|
|
3005
3776
|
var list = sections.subagents.slice(); var i = list.indexOf(item.name)
|
|
3006
3777
|
if (i >= 0) list.splice(i, 1); else list.push(item.name)
|
|
@@ -3019,14 +3790,13 @@ function callApi(path, options) {
|
|
|
3019
3790
|
*/
|
|
3020
3791
|
function archiveNode() {
|
|
3021
3792
|
if (!modal || modal.type !== 'scene-archive') return null
|
|
3022
|
-
return React.createElement(Modal, { key: 'sarch', wide: true, className: 'dsm-modal-archive', title: t('memory.archive.title') + ' · ' + (modal.name === '' ? t('memory.scene.global') : modal.name), closeLabel: t('btn.
|
|
3793
|
+
return React.createElement(Modal, { key: 'sarch', wide: true, className: 'dsm-modal-archive', title: t('memory.archive.title') + ' · ' + (modal.name === '' ? t('memory.scene.global') : modal.name), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
3023
3794
|
React.createElement('div', { className: 'dsm-form' },
|
|
3024
3795
|
React.createElement('div', { className: 'dsm-archive-meta' },
|
|
3025
3796
|
React.createElement('span', null, t('scenes.archive.summary', {
|
|
3026
3797
|
mcp: modal.sections.mcp ? Object.keys(modal.sections.mcp).length : 0,
|
|
3027
3798
|
skills: modal.sections.skills ? modal.sections.skills.length : 0,
|
|
3028
3799
|
subagents: modal.sections.subagents ? modal.sections.subagents.length : 0,
|
|
3029
|
-
memories: modal.sections.memories ? modal.sections.memories.length : 0,
|
|
3030
3800
|
})),
|
|
3031
3801
|
React.createElement('span', { className: 'dsm-help' }, t('scenes.archive.note'))),
|
|
3032
3802
|
mcpSeg(),
|
|
@@ -3056,7 +3826,9 @@ function callApi(path, options) {
|
|
|
3056
3826
|
function submitArchive() {
|
|
3057
3827
|
if (!modalSections()) return
|
|
3058
3828
|
setBusy(true)
|
|
3059
|
-
|
|
3829
|
+
// P5:档案里**不再写 memories 段** —— 记忆的开关是 `rules[*].enabled` 单一真相源,
|
|
3830
|
+
// 在段里勾选时已经即时提交了,这里没有「待保存」的记忆状态。
|
|
3831
|
+
apiCall('scene-archive-save', { scene: modal.name, archive: (function () { var payload = {}; if (modal.sections.mcp) payload.mcp = modal.sections.mcp; if (modal.sections.skills) payload.skills = modal.sections.skills; if (modal.sections.subagents) payload.subagents = modal.sections.subagents; return payload })() }).then(function (res) {
|
|
3060
3832
|
setBusy(false)
|
|
3061
3833
|
if (res && res.ok) {
|
|
3062
3834
|
setModal(null)
|
|
@@ -3074,18 +3846,31 @@ function callApi(path, options) {
|
|
|
3074
3846
|
apiCall('rules-set-active', { scenes: [name] }).then(function (act) {
|
|
3075
3847
|
setBusy(false)
|
|
3076
3848
|
var staleNote = res.stale && res.stale.length ? ' · ' + t('memory.archive.stale', { items: res.stale.join('、') }) : ''
|
|
3077
|
-
|
|
3078
|
-
|
|
3849
|
+
// 如实报「上层切了几台服务器 / 几个来源」——否则用户不知道勾选到底生效没有。
|
|
3850
|
+
var switchNote = res.switched && (res.switched.mcpServers || res.switched.skillSources)
|
|
3851
|
+
? ' · ' + t('memory.result.modeSwitched', { mcp: res.switched.mcpServers, skills: res.switched.skillSources })
|
|
3852
|
+
: ''
|
|
3853
|
+
if (act && act.ok) setResult({ ok: true, text: t('memory.result.modeSet', { name: name }) + switchNote + staleNote })
|
|
3854
|
+
else setResult({ ok: false, text: t('memory.result.modeSet', { name: name }) + switchNote + staleNote + ' · ' + translateError(t, act) })
|
|
3079
3855
|
refresh(true)
|
|
3080
3856
|
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }); refresh(true) })
|
|
3081
3857
|
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
3082
3858
|
}
|
|
3859
|
+
/**
|
|
3860
|
+
* 退出模式(P6):与开关**同一条路径** —— 恢复档案 + 取消场景启用,两者一起写。
|
|
3861
|
+
* 顶部横幅的「退出模式」按钮和卡片开关点它,效果完全一致(用户裁定:「是真的退出了」)。
|
|
3862
|
+
*/
|
|
3083
3863
|
function exitMode() {
|
|
3084
3864
|
setBusy(true)
|
|
3085
3865
|
apiCall('scene-mode-set', { scene: null }).then(function (res) {
|
|
3086
|
-
setBusy(false)
|
|
3087
|
-
|
|
3088
|
-
|
|
3866
|
+
if (!res || !res.ok) { setBusy(false); setResult({ ok: false, text: translateError(t, res) }); return }
|
|
3867
|
+
// 档案已恢复 → 同步取消场景启用,保证 mode.scene 与 active 恒等。
|
|
3868
|
+
apiCall('rules-set-active', { scenes: [] }).then(function (act) {
|
|
3869
|
+
setBusy(false)
|
|
3870
|
+
if (act && act.ok) setResult({ ok: true, text: t('memory.result.modeExited') })
|
|
3871
|
+
else setResult({ ok: false, text: t('memory.result.modeExited') + ' · ' + translateError(t, act) })
|
|
3872
|
+
refresh(true)
|
|
3873
|
+
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }); refresh(true) })
|
|
3089
3874
|
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
3090
3875
|
}
|
|
3091
3876
|
var modeScene = data.mode && data.mode.scene
|
|
@@ -3122,21 +3907,24 @@ function callApi(path, options) {
|
|
|
3122
3907
|
if (archive.mcp) parts.push(t('scenes.profile.mcp', { count: Object.keys(archive.mcp).length }))
|
|
3123
3908
|
if (Array.isArray(archive.skills)) parts.push(t('scenes.profile.skills', { count: archive.skills.length }))
|
|
3124
3909
|
if (Array.isArray(archive.subagents)) parts.push(t('scenes.profile.subagents', { count: archive.subagents.length }))
|
|
3125
|
-
|
|
3910
|
+
// P5:记忆**不再是档案的一部分**(开关是 `rules[*].enabled` 单一真相源),
|
|
3911
|
+
// 所以这里不再统计 memories —— 否则会显示一个已经不存在的段的数量。
|
|
3126
3912
|
return parts.join(' · ')
|
|
3127
3913
|
}
|
|
3914
|
+
// 当前模式场景被锁 → 「退出模式」按钮与开关一起禁用(服务端守卫兜底)。
|
|
3915
|
+
var modeSceneLocked = !!(modeScene && (data.scenes || []).some(function (s) { return s.name === modeScene && s.locked === true }))
|
|
3128
3916
|
var modeSummary = modeScene ? archiveSummary(data.archives[modeScene]) : ''
|
|
3129
3917
|
return React.createElement('section', { className: 'dsm-section' },
|
|
3130
3918
|
React.createElement('div', { className: 'dsm-head' },
|
|
3131
3919
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
3132
3920
|
React.createElement('div', { className: 'dsm-title-row' },
|
|
3133
|
-
React.createElement('h2', { className: 'dsm-title' }, t('scenes.title')),
|
|
3134
|
-
VersionBadgeComponent ? React.createElement(VersionBadgeComponent, null) : null),
|
|
3921
|
+
React.createElement('h2', { className: 'dsm-title' }, t('scenes.title'))),
|
|
3135
3922
|
React.createElement('p', { className: 'dsm-desc' }, t('scenes.desc'))),
|
|
3136
3923
|
React.createElement('div', { className: 'dsm-actions' },
|
|
3137
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || data.loading, onClick: function () { refresh() } }, t('
|
|
3138
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: openCreateScene }, t('memory.btn.newScene'))
|
|
3139
|
-
|
|
3924
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || data.loading, onClick: function () { refresh() } }, (busy || data.loading) ? t('btn.refreshing') : t('btn.refresh')),
|
|
3925
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: openCreateScene }, t('memory.btn.newScene')),
|
|
3926
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: function () { loadTrash(false) } }, t('trash.btn.open')))),
|
|
3927
|
+
React.createElement('div', { key: 'stats', className: 'dsm-summary' },
|
|
3140
3928
|
[[sceneStats.total, t('scenes.stat.total')], [sceneStats.active, t('scenes.stat.active')], [sceneStats.archives, t('scenes.stat.archives')]].map(function (item) {
|
|
3141
3929
|
return React.createElement('div', { key: item[1], className: 'dsm-stat' },
|
|
3142
3930
|
React.createElement('strong', null, item[0]), item[1])
|
|
@@ -3152,13 +3940,13 @@ function callApi(path, options) {
|
|
|
3152
3940
|
t('scenes.mode.active', { name: sceneLabel(data.scenes, modeScene) || modeScene })),
|
|
3153
3941
|
React.createElement('div', { className: 'dsm-mode-sub' },
|
|
3154
3942
|
modeSummary ? t('scenes.mode.profile', { parts: modeSummary }) : t('scenes.mode.noProfile'))),
|
|
3155
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: exitMode }, t('memory.mode.exit'))) : null,
|
|
3943
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || modeSceneLocked, title: modeSceneLocked ? t('scenes.lock.blockedExit') : '', onClick: exitMode }, t('memory.mode.exit'))) : null,
|
|
3156
3944
|
React.createElement(Notice, { key: 'notice', kind: result && result.ok ? 'ok' : 'err', text: result && result.text }),
|
|
3157
3945
|
// 历史「全部启用」(active=null)遗留:单选模型下多名场景同时在场是歧义状态。
|
|
3158
3946
|
// 不静默改写(那会改变注入范围),而是给一个显式收敛按钮,并说清点下去会发生什么。
|
|
3159
3947
|
legacyAllScenes.length > 1 ? React.createElement('div', { key: 'legacyall', className: 'dsm-feedback dsm-warning' },
|
|
3160
3948
|
React.createElement('span', null, t('scenes.legacyAll', { count: legacyAllScenes.length })),
|
|
3161
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () {
|
|
3949
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { enterMode(legacyAllScenes[0].name) } }, t('scenes.legacyAll.fix'))) : null,
|
|
3162
3950
|
data.error ? React.createElement('div', { key: 'gerr', className: 'dsm-feedback dsm-error' }, String(data.error)) : null,
|
|
3163
3951
|
data.loading ? React.createElement('div', { className: 'dsm-empty' }, t('memory.loading'))
|
|
3164
3952
|
: presetScenes.length ? React.createElement('div', { key: 'scenes', className: 'dsm-scenes' }, presetScenes.map(function (scene) {
|
|
@@ -3168,8 +3956,8 @@ function callApi(path, options) {
|
|
|
3168
3956
|
// 全局已被上面的 presetScenes 过滤掉,这里只会遇到可切换的预设;
|
|
3169
3957
|
// 历史保留场景 `_shared`(「常开」)仍然恒定注入,所以不给开关也不给删除。
|
|
3170
3958
|
var locked = scene.shared === true
|
|
3959
|
+
var sceneLocked = scene.locked === true
|
|
3171
3960
|
// 档案里有任何一段才值得「切入此模式」——空档案切进去等于什么都没变。
|
|
3172
|
-
var hasModeSections = !!(archive && (archive.mcp || Array.isArray(archive.skills) || Array.isArray(archive.subagents) || Array.isArray(archive.memories)))
|
|
3173
3961
|
var desc = sceneTileDesc(scene)
|
|
3174
3962
|
// 单选:别的场景已启用时,这个开关置灰(点也没用,服务端只接受一个)。
|
|
3175
3963
|
var blockedByOther = scene.active !== true && scene.selectable === false
|
|
@@ -3184,6 +3972,7 @@ function callApi(path, options) {
|
|
|
3184
3972
|
name === label ? null : React.createElement('span', { className: 'dsm-scene-tile-key' }, name),
|
|
3185
3973
|
locked ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('memory.scene.shared')) : null,
|
|
3186
3974
|
modeScene === name ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('memory.mode.current')) : null,
|
|
3975
|
+
sceneLocked ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('scenes.lock.tag')) : null,
|
|
3187
3976
|
!locked && scene.active === false ? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, t('memory.scene.off')) : null,
|
|
3188
3977
|
boundPrompt ? React.createElement('span', {
|
|
3189
3978
|
className: 'dsm-tag' + (promptMissing ? ' dsm-tag-off' : promptLive ? ' dsm-tag-on' : ''),
|
|
@@ -3193,25 +3982,25 @@ function callApi(path, options) {
|
|
|
3193
3982
|
: promptLive ? t('scenes.prompt.live', { id: boundPrompt }) : t('scenes.prompt.bound', { id: boundPrompt })) : null,
|
|
3194
3983
|
locked ? null : React.createElement('span', {
|
|
3195
3984
|
className: 'dsm-scene-tile-switch',
|
|
3196
|
-
title: blockedByOther ? t('scenes.enable.blocked', { name: sceneLabel(data.scenes, data.activeScene) || data.activeScene || '' }) : '',
|
|
3197
|
-
}, React.createElement(Switch, { on: scene.active === true, disabled: busy || blockedByOther, label: t('memory.scene.enable') + ' ' + name, onClick: function () { toggleScene(scene) } }))
|
|
3985
|
+
title: blockedByOther ? t('scenes.enable.blocked', { name: sceneLabel(data.scenes, data.activeScene) || data.activeScene || '' }) : (sceneLocked && scene.active === true ? t('scenes.lock.blockedExit') : ''),
|
|
3986
|
+
}, React.createElement(Switch, { on: scene.active === true, disabled: busy || blockedByOther || (sceneLocked && scene.active === true), label: t('memory.scene.enable') + ' ' + name, onClick: function () { toggleScene(scene) } })),
|
|
3987
|
+
locked ? null : React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-lock', disabled: busy || scene.active !== true, title: scene.active !== true ? t('scenes.lock.notActive') : t('scenes.lock.hint'), onClick: function () { toggleSceneLock(scene) } }, sceneLocked ? t('scenes.lock.unlock') : t('scenes.lock.lock'))),
|
|
3198
3988
|
// 描述行**只有描述**(数量都收进上面的模式条了);全文放 title,卡片本身永远一行。
|
|
3199
3989
|
React.createElement('p', { className: 'dsm-scene-tile-desc', title: desc || '' }, desc || t('scenes.noDesc')),
|
|
3990
|
+
// P6:「切入此模式」按钮已删除 —— 右上角开关就是唯一入口(开 = 进入,关 = 回全局默认)。
|
|
3200
3991
|
React.createElement('div', { className: 'dsm-scene-tile-foot' },
|
|
3201
|
-
hasModeSections && modeScene !== name ? React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: function () { enterMode(name) } }, t('scenes.mode.enter')) : null,
|
|
3202
3992
|
React.createElement('div', { className: 'dsm-scene-tile-links' },
|
|
3203
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { openArchive(name) } }, t('memory.archive.edit')),
|
|
3993
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || sceneLocked, title: sceneLocked ? t('scenes.lock.blockedEdit') : '', onClick: function () { openArchive(name) } }, t('memory.archive.edit')),
|
|
3204
3994
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { openEditScene(scene) } }, t('memory.scene.edit')),
|
|
3205
|
-
locked ? null : React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: busy, onClick: function () { setModal({ type: 'scene-delete', name: name }) } }, t('memory.btn.deleteScene')))))
|
|
3995
|
+
locked ? null : React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: busy || sceneLocked, title: sceneLocked ? t('scenes.lock.blockedEdit') : '', onClick: function () { setModal({ type: 'scene-delete', name: name }) } }, t('memory.btn.deleteScene')))))
|
|
3206
3996
|
})) : React.createElement('div', { key: 'empty', className: 'dsm-empty' }, t('scenes.empty')),
|
|
3207
|
-
modal && (modal.type === 'scene-create' || modal.type === 'scene-edit') ? React.createElement(Modal, { key: 'screate', title: modal.type === 'scene-create' ? t('memory.scene.createTitle') : t('memory.scene.editTitle'), closeLabel: t('btn.
|
|
3997
|
+
modal && (modal.type === 'scene-create' || modal.type === 'scene-edit') ? React.createElement(Modal, { key: 'screate', title: modal.type === 'scene-create' ? t('memory.scene.createTitle') : t('memory.scene.editTitle'), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
3208
3998
|
React.createElement('div', { className: 'dsm-form' },
|
|
3209
3999
|
React.createElement('label', { className: 'dsm-field' },
|
|
3210
4000
|
React.createElement('span', { className: 'dsm-label' }, t('memory.scene.field.name')),
|
|
3211
4001
|
React.createElement('input', {
|
|
3212
4002
|
className: 'dsm-control' + (sceneForm.error ? ' dsm-rule-invalid' : ''),
|
|
3213
4003
|
value: sceneForm.name || '',
|
|
3214
|
-
disabled: modal.type === 'scene-edit',
|
|
3215
4004
|
placeholder: t('memory.scene.field.name.placeholder'),
|
|
3216
4005
|
onChange: function (e) { setSceneForm(Object.assign({}, sceneForm, { name: e.target.value, error: null })) },
|
|
3217
4006
|
}),
|
|
@@ -3238,12 +4027,25 @@ function callApi(path, options) {
|
|
|
3238
4027
|
React.createElement('p', { className: 'dsm-help' }, t('scenes.field.prompt.hint'))),
|
|
3239
4028
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
3240
4029
|
React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: busy || !String(sceneForm.name || '').trim(), onClick: submitSceneForm }, t(modal.type === 'scene-create' ? 'memory.btn.create' : 'memory.btn.saveScene'))))) : null,
|
|
3241
|
-
modal && modal.type === 'scene-delete' ? React.createElement(Modal, { key: 'sdel', title: t('memory.deleteScene.title'), closeLabel: t('btn.
|
|
4030
|
+
modal && modal.type === 'scene-delete' ? React.createElement(Modal, { key: 'sdel', title: t('memory.deleteScene.title'), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
3242
4031
|
React.createElement('p', { className: 'dsm-help' }, t('memory.deleteScene.desc', { name: modal.name })),
|
|
3243
4032
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
3244
4033
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { submitDeleteScene(modal.name) } }, t('memory.btn.deleteScene')))) : null,
|
|
3245
4034
|
// 档案编辑器(见 archiveNode):三段共用「段卡片 + 勾选行」排版。
|
|
3246
4035
|
archiveNode(),
|
|
4036
|
+
trash ? React.createElement(TrashModal, {
|
|
4037
|
+
t: t,
|
|
4038
|
+
title: t('trash.title'),
|
|
4039
|
+
groupTitle: t('trash.group.scenes'),
|
|
4040
|
+
groupSub: t('trash.section.scenes.sub'),
|
|
4041
|
+
entries: trash.entries,
|
|
4042
|
+
loading: trash.loading,
|
|
4043
|
+
error: trash.error,
|
|
4044
|
+
busy: trashBusy,
|
|
4045
|
+
onClose: function () { setTrash(null) },
|
|
4046
|
+
onRestore: restoreScene,
|
|
4047
|
+
onPurge: purgeScene,
|
|
4048
|
+
}) : null,
|
|
3247
4049
|
)
|
|
3248
4050
|
}
|
|
3249
4051
|
// ---------- 子智能体页:人设文件(~/.dsh/subagents/*.md)管理 ----------
|
|
@@ -3256,20 +4058,63 @@ function callApi(path, options) {
|
|
|
3256
4058
|
var result = rs[0], setResult = rs[1]
|
|
3257
4059
|
var ms = React.useState(null)
|
|
3258
4060
|
var modal = ms[0], setModal = ms[1]
|
|
4061
|
+
// 场景锁定(v0.8):任一场景锁定 = 五个域整体冻结;本页全部写控件禁用。
|
|
4062
|
+
var anyLocked = data.anyLocked === true
|
|
4063
|
+
// 回收站:删除人设 = 移入回收站(宿主侧 subagent-delete),这里列出/恢复/永久删除。
|
|
4064
|
+
var ts = React.useState(null)
|
|
4065
|
+
var trash = ts[0], setTrash = ts[1]
|
|
4066
|
+
var tb = React.useState(false)
|
|
4067
|
+
var trashBusy = tb[0], setTrashBusy = tb[1]
|
|
4068
|
+
function loadTrash(keepOpen) {
|
|
4069
|
+
setTrash(Object.assign({ loading: true, error: null, entries: [] }, keepOpen ? trash || {} : {}))
|
|
4070
|
+
apiCall('subagent-trash-list', {}).then(function (res) {
|
|
4071
|
+
if (res && res.ok) setTrash({ loading: false, error: null, entries: res.trash || [] })
|
|
4072
|
+
else setTrash({ loading: false, error: (res && res.error) || t('trash.loadFailed'), entries: [] })
|
|
4073
|
+
}).catch(function (e) { setTrash({ loading: false, error: String((e && e.message) || e), entries: [] }) })
|
|
4074
|
+
}
|
|
4075
|
+
function restorePersona(item) {
|
|
4076
|
+
setTrashBusy(true)
|
|
4077
|
+
apiCall('subagent-trash-restore', { id: item.id }).then(function (res) {
|
|
4078
|
+
setTrashBusy(false)
|
|
4079
|
+
if (res && res.ok) { setResult({ ok: true, text: t('trash.restored', { name: item.name }) }); refresh(true); loadTrash(true) }
|
|
4080
|
+
else setTrash(function (cur) { return Object.assign({}, cur, { error: (res && res.error) || t('mcp.msg.failed') }) })
|
|
4081
|
+
}).catch(function (e) { setTrashBusy(false); setTrash(function (cur) { return Object.assign({}, cur, { error: String((e && e.message) || e) }) }) })
|
|
4082
|
+
}
|
|
4083
|
+
function purgePersona(item) {
|
|
4084
|
+
setTrashBusy(true)
|
|
4085
|
+
apiCall('subagent-trash-delete', { id: item.id }).then(function (res) {
|
|
4086
|
+
setTrashBusy(false)
|
|
4087
|
+
if (res && res.ok) { setResult({ ok: true, text: t('trash.purged', { name: item.name }) }); loadTrash(true) }
|
|
4088
|
+
else setTrash(function (cur) { return Object.assign({}, cur, { error: (res && res.error) || t('mcp.msg.failed') }) })
|
|
4089
|
+
}).catch(function (e) { setTrashBusy(false); setTrash(function (cur) { return Object.assign({}, cur, { error: String((e && e.message) || e) }) }) })
|
|
4090
|
+
}
|
|
3259
4091
|
React.useEffect(function () { if (!result || result.ok !== true) return undefined; var timer = setTimeout(function () { setResult(null) }, 2600); return function () { clearTimeout(timer) } }, [result])
|
|
3260
4092
|
function refresh(silent) {
|
|
3261
4093
|
if (!silent) setData(function (prev) { return Object.assign({}, prev, { loading: true, error: null }) })
|
|
3262
4094
|
apiCall('subagent-list', {}).then(function (r) {
|
|
3263
|
-
if (r && r.ok) setData({ loading: false, error: null, subagents: r.subagents || [] })
|
|
4095
|
+
if (r && r.ok) setData({ loading: false, error: null, subagents: r.subagents || [], anyLocked: r.anyLocked === true })
|
|
3264
4096
|
else setData({ loading: false, error: translateError(t, r), subagents: [] })
|
|
3265
4097
|
}).catch(function (e) { setData({ loading: false, error: String((e && e.message) || e), subagents: [] }) })
|
|
3266
4098
|
}
|
|
3267
4099
|
React.useEffect(function () { refresh() }, [])
|
|
4100
|
+
/**
|
|
4101
|
+
* 子智能体开关(与记忆页同一套交互):停用 = 不注入目录段、subagent_list/run
|
|
4102
|
+
* 不可见;人设文件不动。开关联动全局状态,立即提交。
|
|
4103
|
+
*/
|
|
4104
|
+
function togglePersona(p) {
|
|
4105
|
+
var next = !(p.enabled !== false)
|
|
4106
|
+
setBusy(true)
|
|
4107
|
+
apiCall('subagent-toggle', { name: p.name, enabled: next }).then(function (r) {
|
|
4108
|
+
setBusy(false)
|
|
4109
|
+
if (r && r.ok) refresh(true)
|
|
4110
|
+
else if (r) setResult({ ok: false, text: translateError(t, r) })
|
|
4111
|
+
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
4112
|
+
}
|
|
3268
4113
|
/**
|
|
3269
4114
|
* 人设表单的候选数据(模型目录 / 全体预设工具并集)只在**首次展开高级选项**时拉取:
|
|
3270
4115
|
* 宿主枚举预设需要为尚未挂载的预设建立 standing mount,不该在打开弹窗时就付这个代价。
|
|
3271
4116
|
*/
|
|
3272
|
-
var cands = React.useState({ loaded: false, loading: false, error: null, models: [], tools: [] })
|
|
4117
|
+
var cands = React.useState({ loaded: false, loading: false, error: null, models: [], tools: [], presets: [] })
|
|
3273
4118
|
var cand = cands[0], setCand = cands[1]
|
|
3274
4119
|
function loadCandidates() {
|
|
3275
4120
|
if (cand.loaded || cand.loading) return
|
|
@@ -3282,18 +4127,31 @@ function callApi(path, options) {
|
|
|
3282
4127
|
error: failed ? t('subagents.adv.loadFailed') : null,
|
|
3283
4128
|
models: (mres && mres.models) || [],
|
|
3284
4129
|
tools: (tres && tres.tools) || [],
|
|
4130
|
+
// presets 必须原样带过来:四行模式的名字、顺序、每行的工具清单全靠它,
|
|
4131
|
+
// 早先这里只留 models/tools,于是分组标题退回裸 id(中文界面里显示英文)。
|
|
4132
|
+
presets: (tres && tres.presets) || [],
|
|
3285
4133
|
})
|
|
3286
4134
|
}).catch(function (e) {
|
|
3287
|
-
setCand({ loaded: true, loading: false, error: String((e && e.message) || e), models: [], tools: [] })
|
|
4135
|
+
setCand({ loaded: true, loading: false, error: String((e && e.message) || e), models: [], tools: [], presets: [] })
|
|
3288
4136
|
})
|
|
3289
4137
|
}
|
|
3290
4138
|
/** 高级选项:默认收起;已经在用模型/工具限制的人设自动展开(否则用户看不见自己配了什么)。 */
|
|
3291
4139
|
function initialAdvanced(p) {
|
|
3292
|
-
|
|
4140
|
+
var modes = p && p.toolsByPreset ? Object.keys(p.toolsByPreset).length : 0
|
|
4141
|
+
return !!((p && (p.model || p.provider)) || (p && ((p.tools || []).length || (p.toolsDeny || []).length || modes)))
|
|
4142
|
+
}
|
|
4143
|
+
/** 编辑态里按模式分组的名单(深拷贝:取消编辑不留痕)。 */
|
|
4144
|
+
function cloneRules(source) {
|
|
4145
|
+
var out = {}
|
|
4146
|
+
Object.keys(source || {}).forEach(function (id) {
|
|
4147
|
+
var rule = source[id] || {}
|
|
4148
|
+
out[id] = { mode: rule.mode === 'deny' ? 'deny' : 'allow', names: (rule.names || []).slice() }
|
|
4149
|
+
})
|
|
4150
|
+
return out
|
|
3293
4151
|
}
|
|
3294
4152
|
function openEditor(name) {
|
|
3295
4153
|
if (!name) {
|
|
3296
|
-
setModal({ type: 'editor', mode: 'create', advanced: false, form: { name: '', description: '', provider: '', model: '', tools: [], toolsDeny: [], body: '', error: null } })
|
|
4154
|
+
setModal({ type: 'editor', mode: 'create', advanced: false, openMode: null, modeQuery: '', legacyTarget: '', form: { name: '', description: '', provider: '', model: '', tools: [], toolsDeny: [], toolsByPreset: {}, body: '', error: null } })
|
|
3297
4155
|
return
|
|
3298
4156
|
}
|
|
3299
4157
|
setBusy(true)
|
|
@@ -3301,9 +4159,10 @@ function callApi(path, options) {
|
|
|
3301
4159
|
setBusy(false)
|
|
3302
4160
|
if (res && res.ok) {
|
|
3303
4161
|
var p = res.persona || {}
|
|
3304
|
-
setModal({ type: 'editor', mode: 'edit', advanced: initialAdvanced(p), form: {
|
|
4162
|
+
setModal({ type: 'editor', mode: 'edit', originalName: String(p.name || name), advanced: initialAdvanced(p), openMode: null, modeQuery: '', legacyTarget: '', form: {
|
|
3305
4163
|
name: p.name || name, description: p.description || '', provider: p.provider || '', model: p.model || '',
|
|
3306
|
-
tools: (p.tools || []).slice(), toolsDeny: (p.toolsDeny || []).slice(),
|
|
4164
|
+
tools: (p.tools || []).slice(), toolsDeny: (p.toolsDeny || []).slice(),
|
|
4165
|
+
toolsByPreset: cloneRules(p.toolsByPreset), body: p.body || '', error: null,
|
|
3307
4166
|
} })
|
|
3308
4167
|
} else setResult({ ok: false, text: translateError(t, res) })
|
|
3309
4168
|
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
@@ -3329,72 +4188,176 @@ function callApi(path, options) {
|
|
|
3329
4188
|
return hit ? (f.provider + '\u0000' + f.model) : '__custom__'
|
|
3330
4189
|
}
|
|
3331
4190
|
/**
|
|
3332
|
-
*
|
|
3333
|
-
*
|
|
3334
|
-
*
|
|
3335
|
-
* 人设可能在任何预设下被子代理复用,只列当前会话的工具会让换预设后的子代理启动失败
|
|
3336
|
-
* (官方 toolFilter 对未知名直接拒绝启动)。因此这里宁可多列,并把手填入口留出来。
|
|
4191
|
+
* 官方四个预设的中英名走插件词典(官方 preset.yml 里只有中文名,直接用会让英文界面
|
|
4192
|
+
* 露出中文);**自建预设用它自己的名字,不翻译** —— 与官方 display 规则一致。
|
|
4193
|
+
* 四个预设 id → 词典键的映射只认官方发布的这四个 id。
|
|
3337
4194
|
*/
|
|
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
|
-
})
|
|
4195
|
+
var SHIPPED_PRESET_IDS = { minimal: 1, standard: 1, ptc: 1, cordis: 1 }
|
|
4196
|
+
function presetLabel(p) {
|
|
4197
|
+
if (!p) return ''
|
|
4198
|
+
var id = String(p.id || '')
|
|
4199
|
+
if (SHIPPED_PRESET_IDS[id] === 1 && String(p.trust || 'system') !== 'user') return t('preset.name.' + id)
|
|
4200
|
+
return String(p.name || id)
|
|
4201
|
+
}
|
|
4202
|
+
/** 该模式下可勾选的工具名;`null` = 宿主没给出该模式的清单(读不到,不能假装是空)。 */
|
|
4203
|
+
function modeTools(id) {
|
|
4204
|
+
var hit = (cand.presets || []).filter(function (p) { return p.id === id })[0]
|
|
4205
|
+
return hit && Array.isArray(hit.tools) ? hit.tools : null
|
|
4206
|
+
}
|
|
4207
|
+
function modeRule(id) { return (modal.form.toolsByPreset || {})[id] }
|
|
4208
|
+
function setModeRule(id, rule) {
|
|
4209
|
+
var next = Object.assign({}, modal.form.toolsByPreset || {})
|
|
4210
|
+
if (rule) next[id] = rule; else delete next[id]
|
|
4211
|
+
setModal(Object.assign({}, modal, {
|
|
4212
|
+
form: Object.assign({}, modal.form, { toolsByPreset: next }),
|
|
4213
|
+
openMode: modal.openMode === id && !rule ? null : modal.openMode,
|
|
4214
|
+
}))
|
|
4215
|
+
}
|
|
4216
|
+
/** 模式行上的一句话状态:未开启 / 白名单 N 个 / 黑名单 N 个 / 已开启但没勾(= 不限制)。 */
|
|
4217
|
+
function modeSummary(id) {
|
|
4218
|
+
var rule = modeRule(id)
|
|
4219
|
+
if (!rule) return { text: t('subagents.mode.off'), cls: 'dsm-mode-off' }
|
|
4220
|
+
var n = (rule.names || []).length
|
|
4221
|
+
if (!n) return { text: t('subagents.mode.empty'), cls: 'dsm-mode-off' }
|
|
4222
|
+
return {
|
|
4223
|
+
text: t(rule.mode === 'deny' ? 'subagents.mode.denyOn' : 'subagents.mode.allowOn', { count: n }),
|
|
4224
|
+
cls: rule.mode === 'deny' ? 'dsm-mode-deny' : 'dsm-mode-allow',
|
|
3369
4225
|
}
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
4226
|
+
}
|
|
4227
|
+
/**
|
|
4228
|
+
* 启动某一侧名单:生效 + 就地展开。同一模式内白/黑互斥 —— 启动一侧就把模式切过去,
|
|
4229
|
+
* **已勾选的名字保留**(只换语义),这样"改成黑名单"不用从头再勾一遍。
|
|
4230
|
+
*/
|
|
4231
|
+
function startMode(id, mode) {
|
|
4232
|
+
var prev = modeRule(id)
|
|
4233
|
+
var names = prev && Array.isArray(prev.names) ? prev.names.slice() : []
|
|
4234
|
+
setModal(Object.assign({}, modal, {
|
|
4235
|
+
openMode: id, modeQuery: '',
|
|
4236
|
+
form: Object.assign({}, modal.form, {
|
|
4237
|
+
toolsByPreset: Object.assign({}, modal.form.toolsByPreset || {}, { [id]: { mode: mode, names: names } }),
|
|
4238
|
+
}),
|
|
4239
|
+
}))
|
|
4240
|
+
}
|
|
4241
|
+
function stopMode(id) { setModeRule(id, null) }
|
|
4242
|
+
function toggleModeTool(id, name) {
|
|
4243
|
+
var rule = modeRule(id)
|
|
4244
|
+
if (!rule) return
|
|
4245
|
+
setModeRule(id, { mode: rule.mode, names: toggled(rule.names || [], name) })
|
|
4246
|
+
}
|
|
4247
|
+
/**
|
|
4248
|
+
* 展开后的勾选区:只列**该模式自己的**工具。这样勾出来的名字天然都属于这个预设,
|
|
4249
|
+
* 不会出现"在标准模式勾了只属于 PTC 的工具 → 换模式跑就启动失败"。
|
|
4250
|
+
* MCP 工具不在这里(用户裁定):子代理照旧能用当前在跑的 MCP,那份名单在运行时并入。
|
|
4251
|
+
*/
|
|
4252
|
+
function modeEditor(p) {
|
|
4253
|
+
var id = p.id
|
|
4254
|
+
var rule = modeRule(id)
|
|
4255
|
+
if (!rule) return null
|
|
4256
|
+
var all = modeTools(id)
|
|
4257
|
+
if (all === null || all.length === 0) {
|
|
4258
|
+
return React.createElement('div', { className: 'dsm-mode-body' },
|
|
4259
|
+
React.createElement('div', { className: 'dsm-pick-empty' }, p.broken ? t('subagents.mode.broken') : t('subagents.mode.noTools')))
|
|
4260
|
+
}
|
|
4261
|
+
var q = String(modal.modeQuery || '').trim().toLowerCase()
|
|
4262
|
+
var list = q ? all.filter(function (n) { return n.toLowerCase().indexOf(q) >= 0 }) : all
|
|
4263
|
+
var picked = rule.names || []
|
|
4264
|
+
return React.createElement('div', { className: 'dsm-mode-body' },
|
|
3373
4265
|
picked.length
|
|
3374
4266
|
? React.createElement('div', { className: 'dsm-tools-chips' }, picked.map(function (name) {
|
|
3375
|
-
return React.createElement('span', { key: 'c:' + name, className: 'dsm-chip' + (
|
|
4267
|
+
return React.createElement('span', { key: 'c:' + name, className: 'dsm-chip' + (rule.mode === 'deny' ? ' dsm-chip-deny' : '') },
|
|
3376
4268
|
name,
|
|
3377
|
-
React.createElement('button', { type: 'button', title: t('subagents.tools.remove'), onClick: function () {
|
|
4269
|
+
React.createElement('button', { type: 'button', title: t('subagents.tools.remove'), onClick: function () { toggleModeTool(id, name) } }, '×'))
|
|
3378
4270
|
}))
|
|
3379
|
-
: React.createElement('span', { className: 'dsm-adv-note' },
|
|
4271
|
+
: React.createElement('span', { className: 'dsm-adv-note' }, t(rule.mode === 'deny' ? 'subagents.mode.denyEmpty' : 'subagents.mode.allowEmpty')),
|
|
3380
4272
|
React.createElement('div', { className: 'dsm-combo-row' },
|
|
3381
4273
|
React.createElement('input', {
|
|
3382
4274
|
className: 'dsm-control',
|
|
3383
|
-
value: modal.
|
|
3384
|
-
placeholder:
|
|
3385
|
-
onChange: function (e) { setModal(Object.assign({}, modal, {
|
|
4275
|
+
value: modal.modeQuery || '',
|
|
4276
|
+
placeholder: t('subagents.tools.filter'),
|
|
4277
|
+
onChange: function (e) { setModal(Object.assign({}, modal, { modeQuery: e.target.value })) },
|
|
3386
4278
|
}),
|
|
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
|
-
|
|
4279
|
+
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')),
|
|
4280
|
+
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'))),
|
|
4281
|
+
list.length
|
|
4282
|
+
? React.createElement('div', { className: 'dsm-tools-grid' }, list.map(function (name) {
|
|
4283
|
+
return pickRow({ key: 'm:' + id + ':' + name, disabled: busy, checked: picked.indexOf(name) >= 0, name: name, onChange: function () { toggleModeTool(id, name) } })
|
|
4284
|
+
}))
|
|
4285
|
+
: React.createElement('div', { className: 'dsm-pick-empty' }, t('scenes.mem.noMatch')))
|
|
4286
|
+
}
|
|
4287
|
+
/**
|
|
4288
|
+
* 一行一个 Agent 预设(roster 顺序:标准 / PTC / 极简 / 创造 / 自建预设)。
|
|
4289
|
+
* 默认全部折叠、全部未启动;右侧两个按钮「启动 白名单」「启动 黑名单」互斥。
|
|
4290
|
+
*/
|
|
4291
|
+
function modeRows() {
|
|
4292
|
+
var presets = (cand.presets || []).slice()
|
|
4293
|
+
if (!presets.length) {
|
|
4294
|
+
return React.createElement('div', { className: 'dsm-field' },
|
|
4295
|
+
React.createElement('span', { className: 'dsm-label' }, t('subagents.field.modes')),
|
|
4296
|
+
React.createElement('div', { className: 'dsm-pick-empty' }, cand.loading ? t('memory.loading') : t('subagents.mode.noPresets')))
|
|
4297
|
+
}
|
|
4298
|
+
return React.createElement('div', { className: 'dsm-field' },
|
|
4299
|
+
React.createElement('span', { className: 'dsm-label' }, t('subagents.field.modes')),
|
|
4300
|
+
React.createElement('div', { className: 'dsm-modes' }, presets.map(function (p) {
|
|
4301
|
+
var id = p.id
|
|
4302
|
+
var rule = modeRule(id)
|
|
4303
|
+
var summary = modeSummary(id)
|
|
4304
|
+
var startButton = function (mode, isDeny) {
|
|
4305
|
+
var active = !!rule && rule.mode === mode
|
|
4306
|
+
return React.createElement('button', {
|
|
4307
|
+
key: mode,
|
|
4308
|
+
type: 'button',
|
|
4309
|
+
className: 'dsm-btn dsm-btn-quiet' + (active ? (mode === 'deny' ? ' dsm-mode-btn-deny' : ' dsm-mode-btn-allow') : ''),
|
|
4310
|
+
disabled: busy,
|
|
4311
|
+
onClick: function () { if (active) stopMode(id); else startMode(id, mode) },
|
|
4312
|
+
}, t(active
|
|
4313
|
+
? (isDeny ? 'subagents.mode.stopDeny' : 'subagents.mode.stopAllow')
|
|
4314
|
+
: (isDeny ? 'subagents.mode.startDeny' : 'subagents.mode.startAllow')))
|
|
4315
|
+
}
|
|
4316
|
+
return React.createElement('div', { className: 'dsm-mode-row' + (rule ? ' dsm-mode-on' : ''), key: id },
|
|
4317
|
+
React.createElement('div', { className: 'dsm-mode-head' },
|
|
4318
|
+
React.createElement('span', { className: 'dsm-mode-name' }, presetLabel(p)),
|
|
4319
|
+
p.broken ? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, t('compat.reach.broken')) : null,
|
|
4320
|
+
React.createElement('span', { className: 'dsm-mode-sum ' + summary.cls }, summary.text),
|
|
4321
|
+
React.createElement('span', { className: 'dsm-mode-actions' },
|
|
4322
|
+
startButton('allow', false),
|
|
4323
|
+
startButton('deny', true))),
|
|
4324
|
+
modal.openMode === id ? modeEditor(p) : null)
|
|
4325
|
+
})),
|
|
4326
|
+
React.createElement('p', { className: 'dsm-help' }, t('subagents.field.modes.hint')))
|
|
4327
|
+
}
|
|
4328
|
+
/**
|
|
4329
|
+
* 旧格式(全局 `tools:` / `toolsDeny:`)的只读小结 + 转换入口。
|
|
4330
|
+
* 旧键对所有模式生效,运行时仍然照旧执行;**不点转换就不动文件里的旧键**。
|
|
4331
|
+
*/
|
|
4332
|
+
function legacyNotice() {
|
|
4333
|
+
var allow = modal.form.tools || []
|
|
4334
|
+
var deny = modal.form.toolsDeny || []
|
|
4335
|
+
if (!allow.length && !deny.length) return null
|
|
4336
|
+
var presets = cand.presets || []
|
|
4337
|
+
var target = modal.legacyTarget || (presets[0] ? presets[0].id : '')
|
|
4338
|
+
return React.createElement('div', { className: 'dsm-legacy' },
|
|
4339
|
+
React.createElement('span', { className: 'dsm-adv-note' }, t('subagents.legacy.note', { allow: allow.length, deny: deny.length })),
|
|
4340
|
+
React.createElement('div', { className: 'dsm-combo-row' },
|
|
4341
|
+
React.createElement('div', { className: 'dsm-select' },
|
|
4342
|
+
React.createElement('select', {
|
|
4343
|
+
className: 'dsm-control',
|
|
4344
|
+
value: target,
|
|
4345
|
+
disabled: busy || !presets.length,
|
|
4346
|
+
onChange: function (e) { setModal(Object.assign({}, modal, { legacyTarget: e.target.value })) },
|
|
4347
|
+
}, presets.map(function (p) { return React.createElement('option', { key: p.id, value: p.id }, presetLabel(p)) }))),
|
|
4348
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || !target || !presets.length, onClick: function () {
|
|
4349
|
+
var rule = { mode: allow.length ? 'allow' : 'deny', names: (allow.length ? allow : deny).slice() }
|
|
4350
|
+
setModal(Object.assign({}, modal, {
|
|
4351
|
+
openMode: target,
|
|
4352
|
+
modeQuery: '',
|
|
4353
|
+
legacyTarget: target,
|
|
4354
|
+
form: Object.assign({}, modal.form, {
|
|
4355
|
+
tools: [], toolsDeny: [],
|
|
4356
|
+
toolsByPreset: Object.assign({}, modal.form.toolsByPreset || {}, { [target]: rule }),
|
|
4357
|
+
}),
|
|
4358
|
+
}))
|
|
4359
|
+
} }, t('subagents.legacy.convert'))),
|
|
4360
|
+
React.createElement('p', { className: 'dsm-help' }, t('subagents.legacy.hint')))
|
|
3398
4361
|
}
|
|
3399
4362
|
function toggleAdvanced() {
|
|
3400
4363
|
if (!modal || modal.type !== 'editor') return
|
|
@@ -3406,10 +4369,16 @@ function callApi(path, options) {
|
|
|
3406
4369
|
if (!modal || modal.type !== 'editor') return
|
|
3407
4370
|
setBusy(true)
|
|
3408
4371
|
var op = modal.mode === 'create' ? 'subagent-create' : 'subagent-update'
|
|
4372
|
+
// 编辑时名字可改:nextName 只在真的改过时才传(服务端按原名定位文件)。
|
|
4373
|
+
var renamed = modal.mode === 'edit' && modal.originalName && modal.originalName !== modal.form.name
|
|
3409
4374
|
apiCall(op, {
|
|
3410
|
-
name: modal.
|
|
4375
|
+
name: renamed ? String(modal.originalName) : modal.form.name,
|
|
4376
|
+
...(renamed ? { nextName: String(modal.form.name || '').trim() } : {}),
|
|
4377
|
+
description: modal.form.description,
|
|
3411
4378
|
provider: modal.form.provider, model: modal.form.model,
|
|
4379
|
+
// 旧格式的全局名单原样回写(旧键不点转换就不动),新模式名单另存一块。
|
|
3412
4380
|
tools: modal.form.tools || [], toolsDeny: modal.form.toolsDeny || [],
|
|
4381
|
+
toolsByPreset: modal.form.toolsByPreset || {},
|
|
3413
4382
|
body: modal.form.body,
|
|
3414
4383
|
}).then(function (res) {
|
|
3415
4384
|
setBusy(false)
|
|
@@ -3444,13 +4413,21 @@ function callApi(path, options) {
|
|
|
3444
4413
|
React.createElement('div', { className: 'dsm-head' },
|
|
3445
4414
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
3446
4415
|
React.createElement('div', { className: 'dsm-title-row' },
|
|
3447
|
-
React.createElement('h2', { className: 'dsm-title' }, t('subagents.title')),
|
|
3448
|
-
VersionBadgeComponent ? React.createElement(VersionBadgeComponent, null) : null),
|
|
4416
|
+
React.createElement('h2', { className: 'dsm-title' }, t('subagents.title'))),
|
|
3449
4417
|
React.createElement('p', { className: 'dsm-desc' }, t('subagents.desc'))),
|
|
3450
4418
|
React.createElement('div', { className: 'dsm-actions' },
|
|
3451
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || data.loading, onClick: function () { refresh() } }, t('
|
|
3452
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: function () {
|
|
3453
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: function () {
|
|
4419
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || data.loading, onClick: function () { refresh() } }, (busy || data.loading) ? t('btn.refreshing') : t('btn.refresh')),
|
|
4420
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || anyLocked, onClick: function () { openEditor(null) } }, t('subagents.new')),
|
|
4421
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || anyLocked, onClick: function () { setResult(null); setModal({ type: 'import' }) } }, t('subagents.import')),
|
|
4422
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || !data.subagents.length, onClick: function () { setResult(null); setModal({ type: 'export' }) } }, t('export.subagents')),
|
|
4423
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: function () { loadTrash(false) } }, t('trash.btn.open')))),
|
|
4424
|
+
anyLocked === true ? React.createElement('div', { className: 'dsm-feedback dsm-warning', role: 'status' }, t('lock.banner')) : null,
|
|
4425
|
+
React.createElement('div', { className: 'dsm-summary', style: { '--dsm-stat-cols': '2' } },
|
|
4426
|
+
[['total', data.subagents.length, t('subagents.stat.total')],
|
|
4427
|
+
['limited', data.subagents.filter(function (p) { return (p.tools && p.tools.length) || (p.toolsDeny && p.toolsDeny.length) || (p.toolsByPreset && Object.keys(p.toolsByPreset).length) }).length, t('subagents.stat.limited')]].map(function (item) {
|
|
4428
|
+
return React.createElement('div', { key: item[0], className: 'dsm-stat' },
|
|
4429
|
+
React.createElement('strong', null, item[1]), item[2])
|
|
4430
|
+
})),
|
|
3454
4431
|
React.createElement(Notice, { kind: result && result.ok ? 'ok' : 'err', text: result && result.text }),
|
|
3455
4432
|
data.error ? React.createElement('div', { className: 'dsm-feedback dsm-error' }, String(data.error)) : null,
|
|
3456
4433
|
data.loading ? React.createElement('div', { className: 'dsm-empty' }, t('memory.loading'))
|
|
@@ -3458,17 +4435,19 @@ function callApi(path, options) {
|
|
|
3458
4435
|
return React.createElement('div', { key: p.name, className: 'dsm-source' },
|
|
3459
4436
|
React.createElement('div', { className: 'dsm-source-head' },
|
|
3460
4437
|
React.createElement('div', { className: 'dsm-source-head-main' },
|
|
3461
|
-
React.createElement('span', { className: 'dsm-source-title' }, p.name),
|
|
3462
|
-
React.createElement('span', { className: 'dsm-note' }, p.description || '')
|
|
4438
|
+
React.createElement('span', { className: 'dsm-source-title', title: p.name }, p.name),
|
|
4439
|
+
React.createElement('span', { className: 'dsm-note' }, p.description || ''),
|
|
4440
|
+
p.enabled === false ? React.createElement('span', { className: 'dsm-tag', title: t('subagents.disabled.hint') }, t('subagents.disabled')) : null),
|
|
3463
4441
|
React.createElement('div', { className: 'dsm-source-actions' },
|
|
3464
|
-
React.createElement(
|
|
3465
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet
|
|
4442
|
+
React.createElement(Switch, { on: p.enabled !== false, disabled: busy || anyLocked, label: t('subagents.toggle') + ' ' + p.name, onClick: function () { togglePersona(p) } }),
|
|
4443
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || anyLocked, onClick: function () { openEditor(p.name) } }, t('memory.edit')),
|
|
4444
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: busy || anyLocked, onClick: function () { setModal({ type: 'delete', name: p.name }) } }, t('memory.delete')))))
|
|
3466
4445
|
})) : React.createElement('div', { className: 'dsm-empty' }, t('subagents.empty')),
|
|
3467
|
-
modal && modal.type === 'editor' ? React.createElement(Modal, { key: 'sedit', wide: true, title: modal.mode === 'create' ? t('subagents.create') : t('subagents.edit') + ' · ' + modal.form.name, closeLabel: t('btn.
|
|
4446
|
+
modal && modal.type === 'editor' ? React.createElement(Modal, { key: 'sedit', wide: true, title: modal.mode === 'create' ? t('subagents.create') : t('subagents.edit') + ' · ' + modal.form.name, closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
3468
4447
|
React.createElement('div', { className: 'dsm-form' },
|
|
3469
4448
|
React.createElement('label', { className: 'dsm-field' },
|
|
3470
4449
|
React.createElement('span', { className: 'dsm-label' }, t('subagents.field.name')),
|
|
3471
|
-
React.createElement('input', { className: 'dsm-control', value: modal.form.name || '',
|
|
4450
|
+
React.createElement('input', { className: 'dsm-control', value: modal.form.name || '', placeholder: 'code-review', onChange: function (e) { setForm({ name: e.target.value }) } })),
|
|
3472
4451
|
React.createElement('label', { className: 'dsm-field' },
|
|
3473
4452
|
React.createElement('span', { className: 'dsm-label' }, t('subagents.field.description')),
|
|
3474
4453
|
React.createElement('input', { className: 'dsm-control', value: modal.form.description || '', placeholder: t('subagents.field.description.placeholder'), onChange: function (e) { setForm({ description: e.target.value }) } })),
|
|
@@ -3485,6 +4464,7 @@ function callApi(path, options) {
|
|
|
3485
4464
|
React.createElement('span', { className: 'dsm-adv-note' },
|
|
3486
4465
|
modal.advanced ? '' : t('subagents.adv.summary', {
|
|
3487
4466
|
model: modal.form.model ? (modal.form.provider ? modal.form.provider + '/' + modal.form.model : modal.form.model) : t('subagents.adv.inherit'),
|
|
4467
|
+
modes: Object.keys(modal.form.toolsByPreset || {}).length,
|
|
3488
4468
|
allow: (modal.form.tools || []).length,
|
|
3489
4469
|
deny: (modal.form.toolsDeny || []).length,
|
|
3490
4470
|
}))),
|
|
@@ -3521,52 +4501,49 @@ function callApi(path, options) {
|
|
|
3521
4501
|
React.createElement('span', { className: 'dsm-label' }, t('subagents.field.provider')),
|
|
3522
4502
|
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
4503
|
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
|
-
}),
|
|
4504
|
+
// 工具限制:按 Agent 预设一行一个模式(默认全折叠、全部未启动,白/黑互斥)。
|
|
4505
|
+
// 旧格式的全局名单(老文件 / 别处导入)在这里只读呈现,点「转换」才搬进某个模式。
|
|
4506
|
+
legacyNotice(),
|
|
4507
|
+
modeRows(),
|
|
3559
4508
|
cand.error ? React.createElement('div', { className: 'dsm-feedback dsm-warning' }, String(cand.error)) : null,
|
|
3560
4509
|
React.createElement('p', { className: 'dsm-adv-note' }, t('subagents.adv.note')))
|
|
3561
4510
|
: null),
|
|
3562
4511
|
modal.form.error ? React.createElement('div', { className: 'dsm-feedback dsm-error', role: 'alert' }, String(modal.form.error)) : null),
|
|
3563
4512
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
3564
4513
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-primary', disabled: busy || (modal.mode === 'create' && !String(modal.form.name || '').trim()) || !String(modal.form.body || '').trim(), onClick: submitEditor }, t('memory.btn.save')))) : null,
|
|
3565
|
-
modal && modal.type === 'delete' ? React.createElement(Modal, { key: 'sdel2', title: t('subagents.delete.title'), closeLabel: t('btn.
|
|
4514
|
+
modal && modal.type === 'delete' ? React.createElement(Modal, { key: 'sdel2', title: t('subagents.delete.title'), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
3566
4515
|
React.createElement('p', { className: 'dsm-help' }, t('subagents.delete.desc', { name: modal.name })),
|
|
3567
4516
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
3568
4517
|
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'),
|
|
4518
|
+
modal && modal.type === 'import' ? React.createElement(ImportModal, { key: 'simp', t: t, title: t('subagents.import.title'), busy: busy, requirements: [t('upload.requirement.persona.1'), t('upload.requirement.persona.2'), t('upload.requirement.persona.3')], onClose: function () { setModal(null) }, onSubmit: submitImport }) : null,
|
|
4519
|
+
modal && modal.type === 'export' ? React.createElement(ExportModal, {
|
|
4520
|
+
key: 'export', t: t, title: t('export.subagents'),
|
|
4521
|
+
items: data.subagents.map(function (p) { return { key: p.name, name: p.name, desc: p.description || '' } }),
|
|
4522
|
+
busy: busy, result: result,
|
|
4523
|
+
onClose: function () { setResult(null); setModal(null) },
|
|
4524
|
+
onSubmit: function (names, outDir) {
|
|
4525
|
+
setBusy(true); setResult(null);
|
|
4526
|
+
apiCall('bundle-export', { kind: 'subagents', names: names, outDir: outDir }).then(function (res) {
|
|
4527
|
+
setBusy(false);
|
|
4528
|
+
if (res && res.ok) setResult({ ok: true, text: exportResultText(t, res) });
|
|
4529
|
+
else setResult({ ok: false, text: translateError(t, res) });
|
|
4530
|
+
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) });
|
|
4531
|
+
},
|
|
4532
|
+
}) : null,
|
|
4533
|
+
trash ? React.createElement(TrashModal, {
|
|
4534
|
+
t: t,
|
|
4535
|
+
title: t('trash.title'),
|
|
4536
|
+
groupTitle: t('trash.group.agents'),
|
|
4537
|
+
groupSub: t('trash.section.agents.sub'),
|
|
4538
|
+
locked: anyLocked,
|
|
4539
|
+
entries: trash.entries,
|
|
4540
|
+
loading: trash.loading,
|
|
4541
|
+
error: trash.error,
|
|
4542
|
+
busy: trashBusy,
|
|
4543
|
+
onClose: function () { setTrash(null) },
|
|
4544
|
+
onRestore: restorePersona,
|
|
4545
|
+
onPurge: purgePersona,
|
|
4546
|
+
}) : null)
|
|
3570
4547
|
}
|
|
3571
4548
|
|
|
3572
4549
|
function MemoryPage() {
|
|
@@ -3579,6 +4556,8 @@ function callApi(path, options) {
|
|
|
3579
4556
|
})
|
|
3580
4557
|
var data = state[0], setData = state[1]
|
|
3581
4558
|
var qs = React.useState('')
|
|
4559
|
+
// 场景锁定(v0.8):任一场景锁定 = 五个域整体冻结;本页全部写控件禁用。
|
|
4560
|
+
var anyLocked = data.anyLocked === true
|
|
3582
4561
|
var query = qs[0], setQuery = qs[1]
|
|
3583
4562
|
var sfs = React.useState('')
|
|
3584
4563
|
var sceneFilter = sfs[0], setSceneFilter = sfs[1]
|
|
@@ -3598,6 +4577,10 @@ function callApi(path, options) {
|
|
|
3598
4577
|
// 记忆回收站:列表在打开弹窗时按需拉取(rules-trash-list)。
|
|
3599
4578
|
var trs = React.useState({ loading: false, error: null, entries: [] })
|
|
3600
4579
|
var trash = trs[0], setTrash = trs[1]
|
|
4580
|
+
// D14:导出弹窗的状态(busy + 结果)。刻意不复用本页的 `result`:
|
|
4581
|
+
// 那个是页面级回执(会自动消失),导出结果只该出现在弹窗里。
|
|
4582
|
+
var exs = React.useState(null)
|
|
4583
|
+
var exportState = exs[0], setExportState = exs[1]
|
|
3601
4584
|
// bundle 附件的隐藏文件选择器;上限与服务端 MAX_ATTACH_ENTRY_BYTES 对齐。
|
|
3602
4585
|
var attachRef = React.useRef(null)
|
|
3603
4586
|
var ATTACH_MAX_MB = 8
|
|
@@ -3936,9 +4919,9 @@ function callApi(path, options) {
|
|
|
3936
4919
|
r.descriptionDerived ? React.createElement('span', { className: 'dsm-tag' }, t('memory.derived')) : null),
|
|
3937
4920
|
React.createElement('div', { className: 'dsm-status ' + statusCls }, t(statusKey)),
|
|
3938
4921
|
React.createElement('div', { className: 'dsm-row-actions' },
|
|
3939
|
-
React.createElement(Switch, { on: enabled, disabled: busy || shadowed, label: t('memory.enable') + ' ' + r.name, onClick: function () { toggleRule(r) } }),
|
|
3940
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || shadowed, onClick: function () { openEditor(r) } }, t('memory.edit')),
|
|
3941
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: busy || shadowed, onClick: function () { setModal({ type: 'delete', rule: r }) } }, t('memory.delete'))))
|
|
4922
|
+
React.createElement(Switch, { on: enabled, disabled: busy || shadowed || anyLocked, label: t('memory.enable') + ' ' + r.name, onClick: function () { toggleRule(r) } }),
|
|
4923
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || shadowed || anyLocked, onClick: function () { openEditor(r) } }, t('memory.edit')),
|
|
4924
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: busy || shadowed || anyLocked, onClick: function () { setModal({ type: 'delete', rule: r }) } }, t('memory.delete'))))
|
|
3942
4925
|
}
|
|
3943
4926
|
|
|
3944
4927
|
function renderSceneCard(name) {
|
|
@@ -3952,7 +4935,7 @@ function callApi(path, options) {
|
|
|
3952
4935
|
return React.createElement('div', { key: 's:' + name, className: 'dsm-source' + (meta.active === false || isOrphan ? ' dsm-rule-shadowed' : '') },
|
|
3953
4936
|
React.createElement('div', { className: 'dsm-source-head' },
|
|
3954
4937
|
React.createElement('button', { type: 'button', className: 'dsm-source-head-main', 'aria-expanded': open, onClick: function () { toggleCollapse('s:' + name) } },
|
|
3955
|
-
React.createElement('span', { className: 'dsm-source-title' }, sceneLabel(data.scenes, name)),
|
|
4938
|
+
React.createElement('span', { className: 'dsm-source-title', title: sceneLabel(data.scenes, name) }, sceneLabel(data.scenes, name)),
|
|
3956
4939
|
React.createElement('span', { className: 'dsm-count' }, t('memory.scene.count', { count: bucket.rules.length })),
|
|
3957
4940
|
isShared ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('memory.scene.shared')) : null,
|
|
3958
4941
|
isGlobal ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('memory.scene.global.tag')) : null,
|
|
@@ -3974,19 +4957,42 @@ function callApi(path, options) {
|
|
|
3974
4957
|
: React.createElement('div', { className: 'dsm-empty' }, t('memory.scene.empty'))) : null)
|
|
3975
4958
|
}
|
|
3976
4959
|
|
|
4960
|
+
// D14 导出:可导出的记忆 = 索引里的规则,排除被同名 bundle 遮蔽的 flat
|
|
4961
|
+
// (它们不会被加载,导出去只会让人以为能用)。key 用**记忆 id**:
|
|
4962
|
+
// 服务端按 id 在索引里查真实文件路径(bundle 型必须这么做,见 planMemoryExport)。
|
|
4963
|
+
var exportableRules = (data.rules || []).filter(function (r) { return r.shadowed !== true })
|
|
4964
|
+
/** 导出条目:按**场景**分组(记忆页的分组维度就是场景),组内给出名称 + 描述。 */
|
|
4965
|
+
function memoryExportItems() {
|
|
4966
|
+
var sceneMeta = {}
|
|
4967
|
+
;(data.scenes || []).forEach(function (s) { sceneMeta[s.name] = s })
|
|
4968
|
+
return exportableRules.map(function (r) {
|
|
4969
|
+
var scene = sceneOf(r.group)
|
|
4970
|
+
var meta = sceneMeta[scene]
|
|
4971
|
+
var label = (meta && (meta.label || meta.name)) || scene || t('memory.scene.orphan')
|
|
4972
|
+
return { key: r.id, name: r.name, desc: r.description || '', group: label }
|
|
4973
|
+
})
|
|
4974
|
+
}
|
|
4975
|
+
function submitExport(names, outDir) {
|
|
4976
|
+
setExportState({ busy: true, result: null })
|
|
4977
|
+
apiCall('bundle-export', { kind: 'memories', names: names, outDir: outDir }).then(function (res) {
|
|
4978
|
+
if (res && res.ok) setExportState({ busy: false, result: { ok: true, text: exportResultText(t, res) } })
|
|
4979
|
+
else setExportState({ busy: false, result: { ok: false, text: translateError(t, res) } })
|
|
4980
|
+
}).catch(function (e) { setExportState({ busy: false, result: { ok: false, text: String((e && e.message) || e) } }) })
|
|
4981
|
+
}
|
|
4982
|
+
|
|
3977
4983
|
return React.createElement('section', { className: 'dsm-section' },
|
|
3978
4984
|
React.createElement('div', { className: 'dsm-head' },
|
|
3979
4985
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
3980
4986
|
React.createElement('div', { className: 'dsm-title-row' },
|
|
3981
|
-
React.createElement('h2', { className: 'dsm-title' }, t('memory.title')),
|
|
3982
|
-
VersionBadgeComponent ? React.createElement(VersionBadgeComponent, null) : null),
|
|
4987
|
+
React.createElement('h2', { className: 'dsm-title' }, t('memory.title'))),
|
|
3983
4988
|
React.createElement('p', { className: 'dsm-desc' }, t('memory.desc'))),
|
|
3984
4989
|
React.createElement('div', { className: 'dsm-actions' },
|
|
3985
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || data.loading, onClick: function () { refresh() } }, t('
|
|
3986
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick:
|
|
3987
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: function () { setResult(null); setImportScene(''); setModal({ type: 'import' }) } }, t('memory.import')),
|
|
3988
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: function () {
|
|
3989
|
-
|
|
4990
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || data.loading, onClick: function () { refresh() } }, (busy || data.loading) ? t('btn.refreshing') : t('btn.refresh')),
|
|
4991
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || anyLocked, onClick: function () { openCreate(sceneFilter || '') } }, t('memory.btn.new')),
|
|
4992
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || anyLocked, onClick: function () { setResult(null); setImportScene(''); setModal({ type: 'import' }) } }, t('memory.import')),
|
|
4993
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || !exportableRules.length, onClick: function () { setResult(null); setExportState({ busy: false, result: null }) } }, t('export.memories')),
|
|
4994
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: openTrash }, t('memory.trash.open')))),
|
|
4995
|
+
React.createElement('div', { className: 'dsm-summary' },
|
|
3990
4996
|
React.createElement('div', { key: 'total', className: 'dsm-stat' }, React.createElement('strong', null, stats.total || 0), t('memory.stat.total')),
|
|
3991
4997
|
React.createElement('div', { key: 'scenes', className: 'dsm-stat' }, React.createElement('strong', null, (data.scenes || []).length), t('memory.stat.sceneCount')),
|
|
3992
4998
|
React.createElement('div', { key: 'active', className: 'dsm-stat' }, React.createElement('strong', null, stats.scenes || 0), t('memory.stat.scenes'))),
|
|
@@ -4010,20 +5016,27 @@ function callApi(path, options) {
|
|
|
4010
5016
|
data.loading && !hasAnyRule ? React.createElement('div', { key: 'loading', className: 'dsm-empty' }, t('memory.loading'))
|
|
4011
5017
|
: visibleBuckets.length ? React.createElement('div', { key: 'scenes', className: 'dsm-sources' }, visibleBuckets.map(renderSceneCard))
|
|
4012
5018
|
: React.createElement('div', { key: 'empty', className: 'dsm-empty' }, q || sceneFilter ? t('memory.empty.search') : t('memory.empty')),
|
|
4013
|
-
modal && modal.type === 'delete' ? React.createElement(Modal, { key: 'del', title: t('memory.delete.title'), closeLabel: t('btn.
|
|
5019
|
+
modal && modal.type === 'delete' ? React.createElement(Modal, { key: 'del', title: t('memory.delete.title'), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
4014
5020
|
React.createElement('p', { className: 'dsm-help' }, t('memory.delete.desc', { name: modal.rule.name })),
|
|
4015
5021
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
4016
5022
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { submitDelete(modal.rule) } }, t('memory.btn.delete.confirm')))) : null,
|
|
4017
5023
|
modal && modal.type === 'import' ? React.createElement(ImportModal, {
|
|
4018
|
-
key: 'mimp', t: t, title: t('memory.import.title'),
|
|
5024
|
+
key: 'mimp', t: t, title: t('memory.import.title'), busy: busy,
|
|
5025
|
+
requirements: [t('upload.requirement.memory.1'), t('upload.requirement.memory.2'), t('upload.requirement.memory.3')],
|
|
4019
5026
|
extra: React.createElement('label', { className: 'dsm-field' },
|
|
4020
5027
|
React.createElement('span', { className: 'dsm-label' }, t('memory.import.scene')),
|
|
4021
|
-
React.createElement('input', { className: 'dsm-control', value: importScene, placeholder: t('memory.scene.global.hint'), onChange: function (e) { setImportScene(e.target.value) } }),
|
|
4022
|
-
React.createElement('p', { className: 'dsm-help' }, t('memory.import.sceneHint'))),
|
|
5028
|
+
React.createElement('input', { className: 'dsm-control', value: importScene, placeholder: t('memory.scene.global.hint'), onChange: function (e) { setImportScene(e.target.value) } })),
|
|
4023
5029
|
onClose: function () { setModal(null) },
|
|
4024
5030
|
onSubmit: submitImport,
|
|
4025
5031
|
}) : null,
|
|
4026
|
-
|
|
5032
|
+
exportState ? React.createElement(ExportModal, {
|
|
5033
|
+
key: 'mexport', t: t, title: t('export.memories'),
|
|
5034
|
+
items: memoryExportItems(),
|
|
5035
|
+
busy: exportState.busy, result: exportState.result,
|
|
5036
|
+
onClose: function () { setExportState(null) },
|
|
5037
|
+
onSubmit: submitExport,
|
|
5038
|
+
}) : null,
|
|
5039
|
+
editor && modal && modal.type === 'editor' ? React.createElement(Modal, { key: 'editor', className: 'dsm-modal-wide', title: editor.mode === 'create' ? t('memory.create.title') : t('memory.edit.title'), closeLabel: t('btn.close'), onClose: closeEditor },
|
|
4027
5040
|
React.createElement('div', { className: 'dsm-form' },
|
|
4028
5041
|
React.createElement('label', { className: 'dsm-field' },
|
|
4029
5042
|
React.createElement('span', { className: 'dsm-label' }, t('memory.field.group')),
|
|
@@ -4093,7 +5106,7 @@ function callApi(path, options) {
|
|
|
4093
5106
|
trash.entries.length
|
|
4094
5107
|
? React.createElement(React.Fragment, null,
|
|
4095
5108
|
React.createElement('div', { className: 'dsm-count' }, t('memory.trash.count', { count: trash.entries.length })),
|
|
4096
|
-
trash.entries.map(function (entry) {
|
|
5109
|
+
React.createElement('div', { className: 'dsm-trash-box' }, trash.entries.map(function (entry) {
|
|
4097
5110
|
return React.createElement('div', { key: entry.trashId, className: 'dsm-trash-item' },
|
|
4098
5111
|
React.createElement('div', { className: 'dsm-trash-main' },
|
|
4099
5112
|
React.createElement('div', { className: 'dsm-name' }, entry.name),
|
|
@@ -4102,9 +5115,9 @@ function callApi(path, options) {
|
|
|
4102
5115
|
+ t('memory.trash.deletedAt', { time: entry.deletedAt ? new Date(entry.deletedAt).toLocaleString() : '' }))),
|
|
4103
5116
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { submitRestore(entry) } }, t('memory.trash.restore')),
|
|
4104
5117
|
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
|
-
}))
|
|
5118
|
+
})))
|
|
4106
5119
|
: React.createElement('div', { className: 'dsm-empty' }, t('memory.trash.empty')))) : null,
|
|
4107
|
-
modal && modal.type === 'trash-delete' ? React.createElement(Modal, { key: 'trash-delete', title: t('memory.trash.confirmTitle'), closeLabel: t('btn.
|
|
5120
|
+
modal && modal.type === 'trash-delete' ? React.createElement(Modal, { key: 'trash-delete', title: t('memory.trash.confirmTitle'), closeLabel: t('btn.close'), onClose: function () { setModal({ type: 'trash' }) } },
|
|
4108
5121
|
React.createElement('p', { className: 'dsm-help' }, t('memory.trash.confirmDesc', { name: modal.entry.name })),
|
|
4109
5122
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
4110
5123
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { submitTrashDelete(modal.entry) } }, t('memory.trash.purge')))) : null)
|