dsh-plugin-tool-management 0.7.0 → 0.8.5
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 +75 -0
- package/README.md +19 -5
- package/README_EN.md +19 -5
- 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 +31 -0
- package/lib/agents-md/service.js +41 -3
- package/lib/client.js +913 -415
- package/lib/compat/preset-reach.js +1 -1
- package/lib/compat/probe.js +12 -2
- package/lib/index.js +651 -31
- package/lib/mcp/state-section.js +90 -0
- package/lib/prompt-sections.js +148 -0
- package/lib/rules/archive-engine.js +151 -30
- package/lib/rules/archive.js +78 -28
- package/lib/rules/service.js +273 -41
- package/lib/skills/core.js +36 -1
- package/lib/skills/service.js +36 -10
- package/lib/subagents/catalog.js +90 -0
- package/lib/subagents/service.js +187 -20
- package/lib/subagents/tools.js +10 -3
- package/package.json +97 -97
- 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,25 +40,43 @@ 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}
|
|
38
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}
|
|
39
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)}
|
|
40
|
-
.dsm-sources{display:flex;flex-direction:column;gap:9px}.dsm-source{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}.dsm-source-head{box-sizing:border-box;display:flex;width:100%;min-height:48px;align-items:center;padding:0 13px}.dsm-source-head-main{display:flex;min-width:0;min-height:48px;flex:1;align-items:center;gap:10px;padding:0;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}.dsm-source-head:hover,.dsm-row:hover,.dsm-trash-row:hover,.dsm-hist-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsm-source-title{font-size:14px;font-weight:650}.dsm-count{color:var(--dsw-alias-label-tertiary);font-size:12px}.dsm-path{min-width:0;margin-left:auto;overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:11px;text-overflow:ellipsis;white-space:nowrap}.dsm-source-actions{display:flex;align-items:center;gap:9px;margin-left:8px}.dsm-source-body{border-top:1px solid var(--dsw-alias-border-l1)}.dsm-source-note{padding:9px 13px;border-bottom:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}
|
|
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}
|
|
41
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}
|
|
42
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)}
|
|
43
|
-
.dsm-mask{position:fixed;z-index:1100;inset:0;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(0,0,0,.62)}.dsm-modal{box-sizing:border-box;display:flex;width:min(560px,100%)!important;max-height:min(760px,calc(100vh - 48px));min-width:0;flex-direction:column;gap:16px;padding:22px;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:var(--dsw-alias-bg-layer-2);box-shadow:var(--dsw-shadow-lv3)}.dsm-modal-wide{width:min(720px,100%)!important}.dsm-modal-
|
|
44
|
-
.dsm-trash-group{display:flex;flex-direction:column;overflow:hidden;margin-bottom:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}
|
|
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}
|
|
45
57
|
.dsm-trash-item{display:flex;align-items:center;gap:9px;padding:6px 10px;border-bottom:1px solid var(--dsw-alias-border-l1)}
|
|
46
58
|
.dsm-trash-item:last-child{border-bottom:0}
|
|
47
59
|
.dsm-trash-main{min-width:0;flex:1}
|
|
48
60
|
/* 回收站列表体:固定高度 + 上下滑动(记忆 / 技能 / 子智能体 / 场景 / 提示词 五处共用) */
|
|
49
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)}
|
|
50
|
-
|
|
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}
|
|
51
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}
|
|
52
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}}
|
|
53
|
-
.dsm-hist-row{display:flex;align-items:center;gap:11px;padding:10px 13px;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-hist-row:last-child{border-bottom:0}.dsm-hist-main{min-width:0;flex:1}.dsm-hist-title{overflow:hidden;font-size:13px;font-weight:580;text-overflow:ellipsis;white-space:nowrap}.dsm-hist-cwd{overflow:hidden;margin-top:2px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:17px;text-overflow:ellipsis;white-space:nowrap}.dsm-hist-cwd-missing{color:var(--dsw-alias-state-error-primary)}.dsm-hist-actions{display:flex;gap:6px;flex:none}.dsm-hist-check,.dsm-hist-group-check{flex:none;width:15px;height:15px;margin:0 4px 0 0;cursor:pointer;accent-color:var(--dsw-alias-state-success-primary)}.dsm-hist-group-head{gap:10px}.dsm-hist-group-extra{display:flex;align-items:center;gap:8px;flex:none;margin-left:4px}.dsm-hist-group-extra .dsm-tag{flex:none}.dsm-hist-register-path{margin-top:-4px;color:var(--dsw-alias-label-tertiary);font-family:var(--dsw-font-mono,monospace);font-size:11px;word-break:break-all}.dsm-hist-batch{display:flex;align-items:center;gap:8px;margin-left:2px;padding-left:10px;border-left:1px solid var(--dsw-alias-border-l2)}.dsm-hist-batch-count{color:var(--dsw-alias-label-secondary);font-size:12px;white-space:nowrap}.dsm-hist-batch-actions{display:flex;align-items:center;flex-wrap:wrap;gap:8px;flex:none}.dsm-dir-row{display:flex;gap:8px}.dsm-dir-row .dsm-control{flex:1}.dsm-dir-list{display:flex;max-height:220px;flex-direction:column;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}.dsm-dir-item{display:flex;width:100%;align-items:center;padding:8px 12px;border:0;border-bottom:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;text-align:left;cursor:pointer}.dsm-dir-item:last-child{border-bottom:0}.dsm-dir-item:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsm-dir-item::before{content:"📁";margin-right:8px;font-size:12px}
|
|
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-dir-missing{flex:none;color:var(--dsw-alias-state-error-primary);font-size:11px;white-space:nowrap}.dsm-hist-register-path{margin-top:-4px;color:var(--dsw-alias-label-tertiary);font-family:var(--dsw-font-mono,monospace);font-size:11px;word-break:break-all}.dsm-hist-batch{display:flex;align-items:center;gap:8px;margin-left:2px;padding-left:10px;border-left:1px solid var(--dsw-alias-border-l2)}.dsm-hist-batch-count{color:var(--dsw-alias-label-secondary);font-size:12px;white-space:nowrap}.dsm-hist-batch-actions{display:flex;align-items:center;flex-wrap:wrap;gap:8px;flex:none}.dsm-dir-row{display:flex;gap:8px}.dsm-dir-row .dsm-control{flex:1}.dsm-dir-list{display:flex;max-height:220px;flex-direction:column;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}.dsm-dir-item{display:flex;width:100%;align-items:center;padding:8px 12px;border:0;border-bottom:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;text-align:left;cursor:pointer}.dsm-dir-item:last-child{border-bottom:0}.dsm-dir-item:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsm-dir-item::before{content:"📁";margin-right:8px;font-size:12px}
|
|
54
80
|
.dsm-rule-shadowed .dsm-name,.dsm-rule-shadowed .dsm-note{color:var(--dsw-alias-state-error-primary)}.dsm-rule-shadow-hint{margin-top:3px;color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:17px}.dsm-rule-invalid{border-color:var(--dsw-alias-state-error-primary)!important}.dsm-rule-hint{color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:17px}.dsm-rule-budget{display:flex;flex-direction:column;gap:6px}.dsm-budget-meta{display:flex;align-items:baseline;justify-content:space-between;gap:8px}.dsm-budget-meta strong{margin-right:0;font-size:13px;font-weight:680}.dsm-budget-over-text{color:var(--dsw-alias-state-error-primary);font-size:11px}.dsm-budget-bar{height:6px;overflow:hidden;border-radius:99px;background:var(--dsw-alias-interactive-bg-hover)}.dsm-budget-fill{height:100%;border-radius:99px;background:var(--dsw-alias-state-success-primary);transition:width 160ms ease}.dsm-budget-fill.dsm-budget-over{background:var(--dsw-alias-state-error-primary)}.dsm-char-count{color:var(--dsw-alias-label-tertiary);font-size:11px}.dsm-char-over{color:var(--dsw-alias-state-error-primary)}
|
|
55
81
|
.dsm-combo-row{display:flex;gap:8px}.dsm-combo-row .dsm-control{flex:1}
|
|
56
82
|
.dsm-attach-list{display:flex;flex-direction:column;gap:6px}.dsm-attach-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
|
|
@@ -67,7 +93,7 @@ window.__ModuleLoader__.load({
|
|
|
67
93
|
/* 滚动容器里的子项不许被压缩:否则内容一多,筛选框会被压到比 input 还矮、与下面的条目叠在一起。 */
|
|
68
94
|
.dsm-seg-body>*{flex:none}
|
|
69
95
|
.dsm-seg-foot{display:flex;align-items:center;gap:8px;padding:7px 12px;border-top:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
|
|
70
|
-
.dsm-pick{display:flex;align-items:center;gap:9px;padding:6px 8px;border-radius:7px}
|
|
96
|
+
.dsm-pick{display:flex;align-items:center;gap:9px;padding:6px 8px;border-radius:7px}.dsm-mcp-edit-tools-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.dsm-mcp-edit-tools-actions{display:flex;align-items:center;gap:8px}.dsm-seg-empty-actions{display:flex;justify-content:center;padding:4px 0 12px}.dsm-seg-empty-error{padding:0 10px 10px;color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:16px;text-align:center}
|
|
71
97
|
.dsm-pick:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
72
98
|
.dsm-pick input[type=checkbox]{flex:none;width:15px;height:15px;margin:0;cursor:pointer;accent-color:var(--dsw-alias-state-success-primary)}
|
|
73
99
|
/* .dsm-pick-main 必须是**纵向 flex**:它原本只是个行内 span,名称与说明会在同一行里内联排布,
|
|
@@ -191,10 +217,32 @@ window.__ModuleLoader__.load({
|
|
|
191
217
|
.dsm-compat-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px}
|
|
192
218
|
.dsm-compat-list{margin:0;padding-left:16px}.dsm-compat-list li+li{margin-top:4px}
|
|
193
219
|
@container(max-width:640px){.dsm-compat-grid{grid-template-columns:1fr}}
|
|
194
|
-
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}}
|
|
195
221
|
/* 动作可用性表:表头列宽与 dsm-compat-mod-row 一致;窄容器下隐藏表头。 */
|
|
196
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}
|
|
197
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
|
+
|
|
198
246
|
`
|
|
199
247
|
|
|
200
248
|
function ensureCss() {
|
|
@@ -390,7 +438,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
390
438
|
|
|
391
439
|
// 指标卡:宿主 / 本插件适配版本 / 可用能力(含插件自补的 optional 槽位)。
|
|
392
440
|
push(React.createElement('div', { className: 'dsm-compat-grid' },
|
|
393
|
-
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),
|
|
394
442
|
card(t('compat.verified'), data.verifiedVersion || '?', t('compat.verified.note'), false),
|
|
395
443
|
card(t('compat.caps'), String(usableCount) + '/' + String(findings.length),
|
|
396
444
|
blockedCount > 0
|
|
@@ -513,12 +561,11 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
513
561
|
React.createElement('div', { className: 'dsm-head' },
|
|
514
562
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
515
563
|
React.createElement('div', { className: 'dsm-title-row' },
|
|
516
|
-
React.createElement('h2', { className: 'dsm-title' }, t('compat.title')),
|
|
517
|
-
VersionBadgeComponent ? React.createElement(VersionBadgeComponent, null) : null),
|
|
564
|
+
React.createElement('h2', { className: 'dsm-title' }, t('compat.title'))),
|
|
518
565
|
React.createElement('p', { className: 'dsm-desc' }, t('compat.desc')))),
|
|
519
566
|
React.createElement('div', { className: 'dsm-actions' },
|
|
520
567
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: reload },
|
|
521
|
-
busy ? t('
|
|
568
|
+
busy ? t('btn.refreshing') : t('btn.refresh'))),
|
|
522
569
|
body)
|
|
523
570
|
}
|
|
524
571
|
|
|
@@ -532,13 +579,13 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
532
579
|
// 契约测试则直接从导出里读它(apply 提前返回也必须拿得到)。
|
|
533
580
|
const DICT = {
|
|
534
581
|
zh: {
|
|
535
|
-
"title": "技能", "desc": "
|
|
536
|
-
"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": "取消同名首选,回到按来源优先级自动选择",
|
|
537
584
|
"btn.custom.add": "添加目录", "btn.custom.remove": "移除", "btn.openDir": "选择",
|
|
538
|
-
"btn.source.remove": "移除来源", "btn.source.remove.title": "不再读取这个来源(源文件不动,之后可在回收站里恢复)", "btn.source.restore": "
|
|
539
|
-
"trash.section.dirs": "目录", "trash.section.skills": "技能", "trash.section.dirs.empty": "没有被移除的来源",
|
|
540
|
-
"trash.dirs.count.one": "{count}
|
|
541
|
-
"trash.
|
|
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}",
|
|
542
589
|
"confirm.source.forget.title": "永久删除这条来源记录?", "confirm.source.forget.desc": "「{name}」将从插件里彻底删掉:来源列表里不再有它,「恢复读取」也找不回来(需要时只能重新添加这个目录)。",
|
|
543
590
|
"confirm.source.forget.hint": "磁盘上的文件夹一个字节都不动:{path}。",
|
|
544
591
|
"result.sourceForgotten": "已永久删除来源记录:{name}",
|
|
@@ -554,13 +601,13 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
554
601
|
"confirm.custom.remove.desc": "移除后该目录的技能不再列出(文件不受影响):{name}",
|
|
555
602
|
"result.custom.added": "已添加自定义目录:{path}", "result.custom.removed": "已移除自定义目录:{name}",
|
|
556
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": "单文件",
|
|
557
|
-
"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": "调用状态",
|
|
558
605
|
"filter.source": "来源", "filter.all": "全部来源", "filter.option": "{name}({count})", "search": "搜索", "search.placeholder": "搜索技能名称或描述", "search.clear": "清除搜索",
|
|
559
606
|
"empty.search": "没有匹配的技能", "empty.source": "该来源不存在或暂无技能", "loading": "正在加载技能…", "note.missing": "未提供简介", "source.toggle": "启停来源", "skill.toggle": "启停技能",
|
|
560
607
|
"source.external.note": "只读接入,启停不改写源文件", "source.dsh.note": "可创建、导入、移到回收站;默认来源必须读取,不能移除或停用",
|
|
561
608
|
"detail.title": "技能详情", "detail.body": "正文", "detail.frontmatter": "元数据", "detail.noFrontmatter": "该技能未提供元数据。", "detail.diagnostics": "诊断", "detail.path": "源文件", "detail.noIssues": "未发现诊断问题。",
|
|
562
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 建的是用户级技能",
|
|
563
|
-
"import.title": "导入技能", "upload.drop.title": "点击或拖入此处", "upload.drop.copy": "支持 .zip、技能文件夹或单个 SKILL.md", "upload.selected.one": "{count} 个文件 · {size}", "upload.selected.other": "{count} 个文件 · {size}", "upload.remove": "移除所选内容", "upload.requirements": "文件要求", "upload.requirement.skill": "压缩包或文件夹需包含 SKILL.md", "upload.requirement.frontmatter": "SKILL.md 需包含 YAML 格式的技能名称和描述", "upload.requirement.copy": "导入时复制完整内容,不修改原始来源", "upload.importing": "
|
|
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}",
|
|
564
611
|
"trash.title": "回收站", "trash.count.one": "{count} 个待处理技能", "trash.count.other": "{count} 个待处理技能", "trash.empty": "回收站为空", "trash.deletedAt": "删除于 {time}", "trash.source": "来源:{source}",
|
|
565
612
|
"trash.btn.open": "回收站", "trash.items.count": "{count} 项", "trash.purge.confirm": "永久删除?",
|
|
566
613
|
"trash.group.agents": "人设", "trash.group.scenes": "场景", "trash.group.presets": "提示词预设",
|
|
@@ -578,13 +625,13 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
578
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})",
|
|
579
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} 覆盖",
|
|
580
627
|
"action.enable": "启用", "action.disable": "停用", "action.create": "创建", "action.delete": "删除", "action.restore": "恢复", "action.toggle": "启用或停用",
|
|
581
|
-
"mcp.desc": "
|
|
582
|
-
"mcp.level.all": "全部级别", "mcp.level.project": "
|
|
628
|
+
"mcp.desc": "管理 MCP:新增、启停、重启,以及工具级开关。",
|
|
629
|
+
"mcp.level.all": "全部级别", "mcp.level.project": "应用级", "mcp.level.global": "全局", "mcp.level.loader": "已加载",
|
|
583
630
|
"mcp.stat.total": "个服务", "mcp.stat.enabled": "个已启用", "mcp.stat.tools": "个工具",
|
|
584
631
|
"mcp.search": "搜索服务", "mcp.search.placeholder": "搜索服务名称、地址或命令",
|
|
585
|
-
"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": "删除",
|
|
586
633
|
"mcp.btn.enableAll": "全部启用", "mcp.btn.disableAll": "全部停用",
|
|
587
|
-
"mcp.loading": "正在加载 MCP 服务…", "mcp.empty": "暂无 MCP
|
|
634
|
+
"mcp.loading": "正在加载 MCP 服务…", "mcp.empty": "暂无 MCP,点「新增 MCP」添加", "mcp.empty.search": "没有匹配的服务。",
|
|
588
635
|
"mcp.servers.count": "{count} 个服务", "mcp.tools.count": "{count} 个工具", "mcp.duplicate": "重复 id",
|
|
589
636
|
"mcp.table.name": "服务名称与地址", "mcp.table.transport": "传输与工具", "mcp.table.status": "运行状态",
|
|
590
637
|
"mcp.live.notLoaded": "未加载", "mcp.live.failed": "启动失败", "mcp.live.stopped": "未运行", "mcp.live.loading": "加载中", "mcp.live.noTools": "无工具", "mcp.live.running": "运行中",
|
|
@@ -592,22 +639,22 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
592
639
|
"mcp.note.prefix": "备注:", "mcp.toggleServer": "启停服务", "mcp.toggleTool": "启停工具",
|
|
593
640
|
"mcp.msg.ok": "操作成功", "mcp.msg.failed": "操作失败", "mcp.msg.loadFailed": "加载失败", "mcp.msg.warn": "操作完成,但加载器有提示:{warning}",
|
|
594
641
|
"mcp.msg.toolOn": "已启用工具:{name}", "mcp.msg.toolOff": "已停用工具:{name}",
|
|
595
|
-
"mcp.form.addTitle": "新增 MCP
|
|
642
|
+
"mcp.form.addTitle": "新增 MCP", "mcp.form.editTitle": "编辑 MCP 服务:",
|
|
596
643
|
"mcp.field.serverName": "服务名称 serverName", "mcp.field.serverName.hint": "1-32 位 [A-Za-z0-9_-],补丁里按此名注册",
|
|
597
644
|
"mcp.field.transport": "传输方式", "mcp.field.level": "级别",
|
|
598
|
-
"mcp.field.level.project": "
|
|
645
|
+
"mcp.field.level.project": "应用级(仅当前应用:{path})", "mcp.field.level.global": "全局(跨应用:{path})",
|
|
599
646
|
"mcp.field.url": "服务 URL", "mcp.field.url.hint": "需以 http(s):// 开头",
|
|
600
647
|
"mcp.field.command": "启动命令", "mcp.field.args": "参数(空格或换行分隔)",
|
|
601
648
|
"mcp.field.env": "环境变量(每行 key=value)", "mcp.field.env.hint": "路径按系统路径写法填(Windows 用 \\,macOS/Linux 用 /)。",
|
|
602
649
|
"mcp.field.headers": "请求头(每行 key=value)",
|
|
603
650
|
"mcp.detail.title": "服务详情:", "mcp.detail.config": "配置", "mcp.detail.showSecret": "显示密钥", "mcp.detail.hideSecret": "隐藏密钥",
|
|
604
651
|
"mcp.token.label": "访问令牌", "mcp.token.placeholder": "与宿主配置的 token 相同", "mcp.token.save": "保存并重试",
|
|
605
|
-
"mcp.token.hint": "
|
|
652
|
+
"mcp.token.hint": "明文密钥必须带对的访问令牌:宿主配置里设 token(或环境变量 DSH_PLUGIN_TOOL_MANAGEMENT_TOKEN)并重启 DSH,这里填同一个值。",
|
|
606
653
|
"mcp.btn.compact": "整理补丁", "mcp.btn.compact.title": "清掉补丁文件里已经不起作用的旧开关记录(每台服务的启用/停用状态不变)",
|
|
607
654
|
"mcp.compact.title": "整理补丁文件", "mcp.compact.desc": "配置里会积累一些已经不起作用的旧开关记录(同一个服务被反复启用/停用留下的)。",
|
|
608
655
|
"mcp.compact.confirm": "确认整理", "mcp.compact.done": "补丁文件已整理:清掉 {count} 条失效的旧开关记录,服务的启用/停用状态未变", "mcp.compact.clean": "补丁文件已经很干净,没有可清理的旧记录",
|
|
609
656
|
"mcp.detail.entryId": "条目 ID", "mcp.detail.status": "运行状态", "mcp.detail.registered": "该服务已登记在 Loader 中。",
|
|
610
|
-
"mcp.detail.note": "
|
|
657
|
+
"mcp.detail.note": "备注(会注入系统提示词,随请求发送给模型)", "mcp.detail.note.placeholder": "例如:A 不可用时改用 B 兜底", "mcp.detail.note.save": "保存备注",
|
|
611
658
|
"mcp.detail.tools": "工具({count})",
|
|
612
659
|
"mcp.field.headersShort": "请求头", "mcp.field.envShort": "环境变量",
|
|
613
660
|
"mcp.tools.loading": "正在获取工具列表…", "mcp.tools.loadFailed": "加载工具失败:", "mcp.tools.none": "该服务暂无已注册工具。",
|
|
@@ -617,11 +664,11 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
617
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",
|
|
618
665
|
"memory.title": "记忆",
|
|
619
666
|
"tabs.scenes": "场景", "tabs.skills": "技能", "tabs.subagents": "子智能体", "tabs.prompts": "提示词", "tabs.memory": "记忆", "tabs.sessions": "会话", "tabs.compat": "兼容",
|
|
620
|
-
"compat.title": "宿主兼容", "compat.desc": "
|
|
667
|
+
"compat.title": "宿主兼容", "compat.desc": "本插件与 DSH 的对接体检(只读)。",
|
|
621
668
|
"compat.state.ok": "一切正常", "compat.state.degraded": "有降级项", "compat.state.blocked": "有需要处理的问题",
|
|
622
669
|
"compat.checking": "检查中…", "compat.recheck": "重新检查", "compat.empty": "尚未检查", "compat.failed": "检查失败",
|
|
623
670
|
"compat.summary.ok": "宿主 {version} · 能力 {ok}/{total} · 全部可用", "compat.summary.degraded": "宿主 {version} · 能力 {ok}/{total} · 降级 {count} 项",
|
|
624
|
-
"compat.host": "宿主版本", "compat.peer": "
|
|
671
|
+
"compat.host": "宿主版本", "compat.peer": "最低要求", "compat.verified": "插件适配版本", "compat.verified.note": "本插件按此版本验证",
|
|
625
672
|
"compat.caps": "可用能力", "compat.caps.all": "全部通过", "compat.caps.degraded": "{count} 项不可用",
|
|
626
673
|
"compat.caps.substituted": "缺口由插件自补",
|
|
627
674
|
"compat.ops": "宿主动作可用性", "compat.ops.hint": "每个动作走哪条路径", "compat.ops.disabled": "有 {count} 个动作当前不可用(相关按钮已禁用并会说明原因)",
|
|
@@ -641,35 +688,35 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
641
688
|
"compat.reach.whyUnknown": "判断不了", "compat.reach.broken": "预设损坏",
|
|
642
689
|
"compat.reach.tools": "本插件的模型工具(含 subagent_list / subagent_run)与 MCP 都挂在宿主平面,不受预设影响,任何预设下都可调用。",
|
|
643
690
|
"compat.hint.doctor": "命令行体检:",
|
|
644
|
-
"scenes.title": "场景", "scenes.desc": "
|
|
691
|
+
"scenes.title": "场景", "scenes.desc": "管理场景:预设 MCP、技能、子智能体与记忆的组合,一键切换。",
|
|
645
692
|
"scenes.stat.total": "个场景", "scenes.stat.active": "个已启用", "scenes.stat.archives": "个有档案",
|
|
646
693
|
"scenes.noDesc": "还没有描述",
|
|
647
694
|
"scenes.mode.active": "当前模式:{name}", "scenes.mode.profile": "档案:{parts}", "scenes.mode.noProfile": "这个场景还没有档案",
|
|
648
|
-
"scenes.
|
|
695
|
+
"scenes.empty": "还没有专门设置的场景;全局记忆不需要场景就能注入,需要切换 MCP / 技能 / 人设时再新建。",
|
|
649
696
|
"scenes.field.desc.limit": "最多 {count} 字;超出部分在卡片上省略。",
|
|
650
697
|
"scenes.profile.mcp": "MCP {count} 台", "scenes.profile.skills": "技能 {count} 个", "scenes.profile.subagents": "子智能体 {count} 个", "scenes.profile.memories": "记忆 {count} 条",
|
|
651
698
|
"scenes.mcp.hint": "添加「MCP 工具集」后,勾选要启用的服务器(含未运行的);「选工具」可细化到具体工具。",
|
|
652
699
|
"scenes.seg.selectAll": "全选", "scenes.seg.clear": "清空", "scenes.seg.checked": "{checked}/{total} 已勾选",
|
|
653
700
|
"scenes.archive.sectionOff": "未定义",
|
|
654
|
-
"scenes.archive.summary": "档案:MCP {mcp} 台 · 技能 {skills} 个 · 子智能体 {subagents} 个
|
|
655
|
-
"scenes.archive.note": "
|
|
701
|
+
"scenes.archive.summary": "档案:MCP {mcp} 台 · 技能 {skills} 个 · 子智能体 {subagents} 个",
|
|
702
|
+
"scenes.archive.note": "段未定义 = 不改动该域;段已定义 = 按勾选集双向切换:勾的启用、没勾的停用。MCP 会真的启停服务器进程,技能会启停它所在的来源。",
|
|
656
703
|
"scenes.mcp.noServers": "还没有可选的 MCP 服务器", "scenes.mcp.toolCount": "{count} 个工具", "scenes.skills.empty": "还没有可选的技能",
|
|
657
|
-
"scenes.mcp.allTools": "全部工具", "scenes.mcp.pickedCount": "指定 {count} 个工具", "scenes.mcp.notRunning": "未运行", "scenes.mcp.
|
|
658
|
-
"scenes.mcp.toolsOf": "
|
|
704
|
+
"scenes.mcp.allTools": "全部工具", "scenes.mcp.pickedCount": "指定 {count} 个工具", "scenes.mcp.notRunning": "未运行", "scenes.mcp.edit": "编辑", "scenes.mcp.done": "完成", "scenes.mcp.noteLabel": "场景备注", "scenes.mcp.notePlaceholder": "进入该场景时覆盖全局备注;退出后恢复", "scenes.mcp.hasNote": "有备注",
|
|
705
|
+
"scenes.mcp.toolsOf": "选择工具", "scenes.mcp.probeTools": "启动服务器读取工具", "scenes.mcp.unknownCount": "工具数未知 · 点「编辑」读取", "scenes.mcp.noteHint": "此备注随场景保存;仅在该场景本段**勾选**了这台服务器时,进入场景才会覆盖「MCP」页与系统提示词里的备注,退出后自动恢复。", "scenes.mcp.noTools": "该服务器当前没有可列出的工具(从未运行过,或已配置的工具尚未被记录)", "scenes.mcp.drillHint": "勾选 = 该场景下启用;不勾 = 停用。「全选」= 全部工具。",
|
|
659
706
|
"scenes.skills.hint": "添加「技能集」后,勾选该场景下启用的技能。", "scenes.subagents.hint": "添加「子智能体绑定」后,勾选本场景可调用的人设。", "scenes.subagents.empty": "还没有人设——到「子智能体」页创建。",
|
|
660
707
|
"scenes.filter.skills": "筛选技能/目录名称", "scenes.filter.subagents": "筛选人设(名称或描述)", "scenes.filter.servers": "筛选服务器",
|
|
661
|
-
|
|
708
|
+
|
|
662
709
|
"scenes.mem.title": "记忆", "scenes.mem.search": "在本场景内筛选记忆(名称或描述)",
|
|
663
710
|
"scenes.mem.noMatch": "没有匹配项", "scenes.mem.emptyScene": "该场景还没有记忆",
|
|
664
711
|
"memory.mode.current": "当前模式", "memory.mode.exit": "退出模式",
|
|
665
712
|
"memory.archive.edit": "档案", "memory.archive.title": "场景档案",
|
|
666
713
|
"memory.archive.tools": "MCP 工具集", "memory.archive.skills": "技能集", "memory.archive.subagents": "子智能体绑定", "memory.archive.memories": "记忆",
|
|
667
714
|
"memory.archive.removeSection": "移除段", "memory.archive.save": "保存到场景",
|
|
668
|
-
"memory.archive.addTools": "+ 添加 MCP 工具集", "memory.archive.addSkills": "+ 添加技能集", "memory.archive.addSubagents": "+ 添加子智能体绑定",
|
|
715
|
+
"memory.archive.addTools": "+ 添加 MCP 工具集", "memory.archive.addSkills": "+ 添加技能集", "memory.archive.addSubagents": "+ 添加子智能体绑定",
|
|
669
716
|
"memory.archive.stale": "失效项(已不存在,已跳过): {items}",
|
|
670
|
-
"memory.result.archiveSaved": "已保存场景档案:{name}", "memory.result.modeSet": "已进入模式:{name}", "memory.result.modeExited": "已退出模式",
|
|
671
|
-
"memory.desc": "
|
|
672
|
-
"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": "保存",
|
|
673
720
|
"memory.btn.diagnose": "体检", "memory.btn.delete.confirm": "移入回收站",
|
|
674
721
|
"memory.diagnose.title": "记忆体检", "memory.diagnose.summary": "{rules} 条记忆 · {scenes} 个场景 · {issues} 个问题", "memory.diagnose.clean": "未发现问题,一切正常。",
|
|
675
722
|
"memory.diag.error": "错误", "memory.diag.warning": "警告", "memory.diag.info": "提示",
|
|
@@ -682,10 +729,10 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
682
729
|
"memory.scene.global": "全局", "memory.scene.count": "{count} 条记忆", "memory.scene.new": "新建记忆", "memory.scene.browse": "选择已有",
|
|
683
730
|
"memory.scene.empty": "该场景暂无记忆",
|
|
684
731
|
"memory.scene.orphan": "未归属场景", "memory.scene.orphan.tag": "不会注入", "memory.scene.orphan.hint": "这些记忆直接放在 memories/ 根层,没有归属场景,因此不会进入系统提示词。请把它们移入某个场景目录(或放到 memories/global/ 作为「全局」记忆)。",
|
|
685
|
-
"memory.scene.edit": "改描述", "memory.scene.editTitle": "修改场景", "memory.scene.editHelp": "
|
|
732
|
+
"memory.scene.edit": "改描述", "memory.scene.editTitle": "修改场景", "memory.scene.editHelp": "名称就是它的记忆目录名;改名会连目录、档案与绑定一起改,记忆正文不动。",
|
|
686
733
|
"memory.scene.field.name": "场景名", "memory.scene.field.name.placeholder": "例如 办公",
|
|
687
|
-
"memory.scene.field.name.hint": "
|
|
688
|
-
"memory.scene.field.name.lock": "
|
|
734
|
+
"memory.scene.field.name.hint": "名称就是它的一级目录名,之后仍可修改。",
|
|
735
|
+
"memory.scene.field.name.lock": "改名会一起改掉记忆目录、场景档案与启用集合;记忆正文不动。",
|
|
689
736
|
"memory.scene.field.desc": "描述(可选)", "memory.scene.field.desc.placeholder": "一句话说明这个场景是干什么的",
|
|
690
737
|
"memory.search.placeholder": "搜索名称 / 描述 / 场景", "memory.filter.all": "全部场景",
|
|
691
738
|
"memory.empty": "还没有记忆,点「新建记忆」", "memory.empty.search": "没有匹配的记忆",
|
|
@@ -709,24 +756,24 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
709
756
|
"memory.trash.count": "{count} 条已删除的记忆", "memory.trash.deletedAt": "删除于 {time}",
|
|
710
757
|
"memory.trash.restore": "恢复", "memory.trash.purge": "永久删除",
|
|
711
758
|
"memory.trash.confirmTitle": "永久删除?", "memory.trash.confirmDesc": "「{name}」将永久删除,无法恢复",
|
|
712
|
-
"memory.btn.newScene": "
|
|
759
|
+
"memory.btn.newScene": "新增场景", "memory.btn.deleteScene": "删除场景", "memory.btn.saveScene": "保存修改",
|
|
713
760
|
"memory.scene.createTitle": "新建场景",
|
|
714
|
-
"memory.deleteScene.title": "删除场景?", "memory.deleteScene.desc": "
|
|
715
|
-
"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} 项记忆,可在回收站整条恢复)",
|
|
716
763
|
"memory.table.name": "记忆名称与描述", "memory.table.tags": "标记", "memory.table.status": "状态",
|
|
717
|
-
"error.rules.invalidArgs": "缺少参数:需要 all:true(回到全部启用)或 scenes:[...](显式收窄)", "error.rules.invalidGroup": "场景/分组名不合法(非空、≤64 字符、不含路径分隔符与 < > : \" | ? *、不以 . 开头)", "error.rules.invalidName": "记忆名不合法(非空、≤64 字符、不含路径分隔符与 < > : \" | ? *、不以 . 开头)", "error.rules.descriptionRequired": "描述不能为空", "error.rules.descriptionTooLong": "描述过长(不能超过 500 字符)", "error.rules.bodyRequired": "正文不能为空", "error.rules.tooLarge": "规则内容过大", "error.rules.shadowed": "规则被同名 bundle 遮蔽,无法写入", "error.rules.notFound": "规则不存在", "error.rules.budgetExceeded": "场景记忆段超出预算", "error.rules.ioFailed": "文件操作失败", "error.rules.sceneNotEmpty": "场景不为空,无法删除", "error.rules.notBundle": "该记忆是 flat(单文件),不能带附件", "error.rules.noFiles": "没有选择附件", "error.rules.emptyFile": "附件内容为空", "error.rules.fileTooLarge": "附件过大(单个上限 {limit} MB)", "error.rules.tooManyFiles": "一次最多 {limit} 个附件", "error.rules.sceneInMode": "该场景正处在当前模式,请先退出模式再删除",
|
|
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": "该场景正处在当前模式,请先退出模式再删除",
|
|
718
765
|
"memory.archive.emptySection": "该段已定义但没有勾选任何条目 = 全部停用", "memory.archive.emptySubagents": "该段已定义但没有勾选任何条目 = 不限制(全部人设可用)",
|
|
719
|
-
"subagents.title": "子智能体", "subagents.desc": "
|
|
720
|
-
"prompts.desc": "
|
|
766
|
+
"subagents.title": "子智能体", "subagents.stat.total": "个子智能体", "subagents.stat.limited": "个有工具限制", "subagents.desc": "管理子智能体:新建、导入人设,正文即子代理的系统提示词。",
|
|
767
|
+
"prompts.desc": "管理提示词:预设全局指令基线,应用后写入 AGENTS.md,下一轮对话生效。",
|
|
721
768
|
"sessions.desc": "管理已归档会话:可恢复或永久删除,超保留期自动清理。",
|
|
722
|
-
"subagents.new": "
|
|
769
|
+
"subagents.new": "新增子智能体", "subagents.empty": "还没有子智能体——点「新增子智能体」创建第一个。",
|
|
723
770
|
"subagents.create": "新建人设", "subagents.edit": "编辑人设",
|
|
724
771
|
"subagents.field.name": "人设名",
|
|
725
772
|
"subagents.field.description": "描述", "subagents.field.description.placeholder": "例如 擅长 Java 后端实现与重构",
|
|
726
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": "「模型来源」与「模型」是一对:只填模型会落在主会话的来源上,跨来源会解析失败。",
|
|
727
774
|
"subagents.model.inherit": "继承主会话(不指定)", "subagents.model.customOption": "自定义 / 目录里没有…",
|
|
728
775
|
"subagents.field.modes": "工具限制(按 Agent 预设)",
|
|
729
|
-
"subagents.field.modes.hint": "
|
|
776
|
+
"subagents.field.modes.hint": "只有当前模式那一行生效,其他模式用该模式的全部工具。",
|
|
730
777
|
"subagents.mode.startAllow": "启动白名单", "subagents.mode.startDeny": "启动黑名单", "subagents.mode.stopAllow": "关闭白名单", "subagents.mode.stopDeny": "关闭黑名单",
|
|
731
778
|
"subagents.mode.off": "未限制", "subagents.mode.empty": "已启动但没勾选(= 不限制)",
|
|
732
779
|
"subagents.mode.allowOn": "白名单 {count} 个", "subagents.mode.denyOn": "黑名单 {count} 个",
|
|
@@ -745,29 +792,27 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
745
792
|
"subagents.field.body": "人设提示词", "subagents.field.body.placeholder": "写下这个人设的身份、职责与工作方式…",
|
|
746
793
|
"subagents.result.saved": "已保存人设:{name}", "subagents.result.deleted": "已删除人设:{name}",
|
|
747
794
|
"subagents.delete.title": "删除人设?", "subagents.delete.desc": "将把「{name}」的人设文件移入回收站(子智能体页的「回收站」里可以恢复)。",
|
|
748
|
-
"subagents.import": "
|
|
795
|
+
"subagents.import": "导入子智能体", "subagents.import.title": "导入子智能体", "subagents.toggle": "启用子智能体", "subagents.disabled": "已停用", "subagents.disabled.hint": "停用后不注入系统提示词,subagent_list / subagent_run 也看不到;文件保留,随时可再打开。",
|
|
749
796
|
"subagents.result.imported": "已导入 {count} 个人设:{names}",
|
|
750
797
|
"memory.import": "导入记忆", "memory.import.title": "导入记忆", "memory.import.scene": "导入到场景",
|
|
751
|
-
"memory.import.sceneHint": "留空 = 全局;zip 内的目录名会当作场景名。",
|
|
752
|
-
"memory.import.hint": "支持 .md 或 .zip(可多选、可拖入):一个 .md = 一条记忆;zip 内 <场景>/<名>/SKILL.md 按 bundle 导入,同层文件作附件;同名自动跳过。",
|
|
753
798
|
"memory.result.imported": "已导入 {count} 条记忆:{names}",
|
|
754
799
|
"memory.import.scenesCreated": "为此新建了场景:{names}",
|
|
755
800
|
"memory.scene.global.hint": "全局",
|
|
756
|
-
|
|
801
|
+
"import.clear": "清空选择", "import.submit": "导入",
|
|
757
802
|
"import.none": "没有导入任何文件(全部被跳过)", "import.skipped": "已跳过:{items}",
|
|
758
803
|
"error.import.noFiles": "没有选择要导入的文件",
|
|
759
804
|
"nav.title": "工具",
|
|
760
|
-
"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.group.dirMissing": "目录已不存在", "hist.group.dirMissing.hint": "工作区目录已从磁盘删除,无法重新登记;组内会话仍可恢复(归入未分组)或永久删除", "hist.btn.registerWs": "重新登记", "hist.btn.registerWs.hint": "重新登记这个目录,并把该目录下已有的会话挂回该组(只写工作区记账,不移动、不删除文件与会话)", "hist.register.title": "重新登记?", "hist.register.body": "将为「{title}」在 DSH 中重新创建一条工作区登记,并把该目录下已有的会话挂回这个工作区(恢复其分组)。只写工作区记账,不移动、不删除任何文件与会话。", "hist.register.confirm": "确认登记", "hist.register.done.title": "已重新登记", "hist.register.done.body": "工作区「{title}」已登记;该目录下的归档会话会在刷新后并入这一组。", "hist.register.done.attached": "已把该目录下 {count} 个已有会话挂回这个工作区。", "hist.register.done.skipped": "{count} 个会话未能挂回:{reason}", "hist.err.register": "登记失败", "scenes.field.prompt": "提示词预设", "scenes.agents.note.applied": "已把「{id}」写入 AGENTS.md", "scenes.agents.note.restored": "已恢复进场景前的 AGENTS.md", "scenes.agents.note.error": "AGENTS.md 未写入:{reason}", "scenes.field.prompt.hint": "启用这个场景时,就用这份预设作为当前的全局指令基线。默认选中当前生效的那份。", "scenes.prompt.activeTag": "生效中", "scenes.prompt.noPresets": "还没有提示词预设;可先去「提示词」页新建一个。", "scenes.prompt.bound": "提示词:{id}", "scenes.prompt.live": "提示词生效中:{id}", "scenes.prompt.missing": "预设不存在:{id}", "scenes.prompt.tag.hint": "这个场景绑定的提示词预设", "scenes.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": "未知错误",
|
|
761
806
|
},
|
|
762
807
|
en: {
|
|
763
|
-
"title": "Skills", "desc": "Manage skills
|
|
764
|
-
"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",
|
|
765
810
|
"btn.custom.add": "Add folder",
|
|
766
811
|
"btn.custom.remove": "Remove",
|
|
767
|
-
"btn.source.remove": "Remove source", "btn.source.remove.title": "Stop reading this source (files stay on disk; restore it later from the trash)", "btn.source.restore": "
|
|
768
|
-
"trash.section.dirs": "Directories", "trash.section.skills": "Skills", "trash.section.dirs.empty": "No removed sources",
|
|
769
|
-
"trash.dirs.count.one": "{count} directory", "trash.dirs.count.other": "{count} directories",
|
|
770
|
-
"trash.
|
|
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)",
|
|
771
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).",
|
|
772
817
|
"confirm.source.forget.hint": "Not a single byte is deleted on disk: {path}.",
|
|
773
818
|
"result.sourceForgotten": "Source record deleted for good: {name}",
|
|
@@ -790,13 +835,13 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
790
835
|
"result.custom.removed": "Custom folder removed: {name}",
|
|
791
836
|
"result.opened": "Opened with the system default app: {path}",
|
|
792
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",
|
|
793
|
-
"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",
|
|
794
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",
|
|
795
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",
|
|
796
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",
|
|
797
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.",
|
|
798
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",
|
|
799
|
-
"import.title": "Import skill", "upload.drop.title": "Click or drop here", "upload.drop.copy": "Supported: .zip, a skill folder, or one SKILL.md", "upload.selected.one": "{count} file · {size}", "upload.selected.other": "{count} files · {size}", "upload.remove": "Remove selection", "upload.requirements": "File requirements", "upload.requirement.skill": "Archives and folders must contain SKILL.md", "upload.requirement.frontmatter": "SKILL.md must include a YAML name and description", "upload.requirement.copy": "Import copies all content and never modifies the source", "upload.importing": "
|
|
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}",
|
|
800
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}",
|
|
801
846
|
"trash.btn.open": "Trash", "trash.items.count": "{count} item(s)", "trash.purge.confirm": "Delete forever?",
|
|
802
847
|
"trash.group.agents": "Personas", "trash.group.scenes": "Scenes", "trash.group.presets": "Prompt presets",
|
|
@@ -814,13 +859,13 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
814
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})",
|
|
815
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}",
|
|
816
861
|
"action.enable": "enable", "action.disable": "disable", "action.create": "create", "action.delete": "delete", "action.restore": "restore", "action.toggle": "enabling or disabling",
|
|
817
|
-
"mcp.desc": "Manage
|
|
862
|
+
"mcp.desc": "Manage MCP: add, enable/disable, restart, and per-tool switches.",
|
|
818
863
|
"mcp.level.all": "All levels", "mcp.level.project": "Profile level", "mcp.level.global": "Global", "mcp.level.loader": "Loaded",
|
|
819
864
|
"mcp.stat.total": "server(s)", "mcp.stat.enabled": "enabled", "mcp.stat.tools": "tool(s)",
|
|
820
865
|
"mcp.search": "Search servers", "mcp.search.placeholder": "Search a server name, URL or command",
|
|
821
|
-
"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",
|
|
822
867
|
"mcp.btn.enableAll": "Enable all", "mcp.btn.disableAll": "Disable all",
|
|
823
|
-
"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.",
|
|
824
869
|
"mcp.servers.count": "{count} server(s)", "mcp.tools.count": "{count} tool(s)", "mcp.duplicate": "duplicate id",
|
|
825
870
|
"mcp.table.name": "Server name and URL", "mcp.table.transport": "Transport and tools", "mcp.table.status": "Status",
|
|
826
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",
|
|
@@ -838,12 +883,12 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
838
883
|
"mcp.field.headers": "Headers (one key=value per line)",
|
|
839
884
|
"mcp.detail.title": "Server details: ", "mcp.detail.config": "Configuration", "mcp.detail.showSecret": "Reveal secrets", "mcp.detail.hideSecret": "Hide secrets",
|
|
840
885
|
"mcp.token.label": "Access token", "mcp.token.placeholder": "Same value as the host token", "mcp.token.save": "Save and retry",
|
|
841
|
-
"mcp.token.hint": "Plaintext secrets
|
|
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.",
|
|
842
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)",
|
|
843
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.",
|
|
844
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",
|
|
845
890
|
"mcp.detail.entryId": "Entry id", "mcp.detail.status": "Status", "mcp.detail.registered": "This server is registered in the loader.",
|
|
846
|
-
"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",
|
|
847
892
|
"mcp.detail.tools": "Tools ({count})",
|
|
848
893
|
"mcp.field.headersShort": "Headers", "mcp.field.envShort": "Environment",
|
|
849
894
|
"mcp.tools.loading": "Fetching the tool list…", "mcp.tools.loadFailed": "Could not load tools: ", "mcp.tools.none": "This server has no registered tools.",
|
|
@@ -853,11 +898,11 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
853
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",
|
|
854
899
|
"memory.title": "Memories",
|
|
855
900
|
"tabs.scenes": "Scenes", "tabs.skills": "Skills", "tabs.subagents": "Subagents", "tabs.prompts": "Prompts", "tabs.memory": "Memories", "tabs.sessions": "Sessions", "tabs.compat": "Host",
|
|
856
|
-
"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).",
|
|
857
902
|
"compat.state.ok": "All good", "compat.state.degraded": "Degraded", "compat.state.blocked": "Needs attention",
|
|
858
903
|
"compat.checking": "Checking…", "compat.recheck": "Check again", "compat.empty": "Not checked yet", "compat.failed": "Check failed",
|
|
859
904
|
"compat.summary.ok": "host {version} · capabilities {ok}/{total} · all usable", "compat.summary.degraded": "host {version} · capabilities {ok}/{total} · {count} degraded",
|
|
860
|
-
"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",
|
|
861
906
|
"compat.caps": "Usable capabilities", "compat.caps.all": "all present", "compat.caps.degraded": "{count} unavailable",
|
|
862
907
|
"compat.caps.substituted": "gaps covered by the plugin",
|
|
863
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)",
|
|
@@ -877,34 +922,34 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
877
922
|
"compat.reach.whyUnknown": "cannot tell", "compat.reach.broken": "preset broken",
|
|
878
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.",
|
|
879
924
|
"compat.hint.doctor": "Command-line check:",
|
|
880
|
-
"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.",
|
|
881
926
|
"scenes.stat.total": "scene(s)", "scenes.stat.active": "enabled", "scenes.stat.archives": "with a profile",
|
|
882
927
|
"scenes.noDesc": "No description yet",
|
|
883
928
|
"scenes.mode.active": "Active mode: {name}", "scenes.mode.profile": "Profile: {parts}", "scenes.mode.noProfile": "This scene has no profile yet",
|
|
884
|
-
"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.",
|
|
885
930
|
"scenes.field.desc.limit": "Up to {count} characters; longer text is clipped on the card.",
|
|
886
931
|
"scenes.profile.mcp": "{count} MCP", "scenes.profile.skills": "{count} skill(s)", "scenes.profile.subagents": "{count} subagent(s)", "scenes.profile.memories": "{count} memor(ies)",
|
|
887
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.",
|
|
888
933
|
"scenes.seg.selectAll": "Select all", "scenes.seg.clear": "Clear", "scenes.seg.checked": "{checked}/{total} selected",
|
|
889
934
|
"scenes.archive.sectionOff": "not defined",
|
|
890
|
-
"scenes.archive.summary": "Profile: {mcp} MCP server(s) · {skills} skill(s) · {subagents} subagent(s)
|
|
891
|
-
"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.",
|
|
892
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",
|
|
893
|
-
"scenes.mcp.allTools": "all tools", "scenes.mcp.pickedCount": "{count} tools picked", "scenes.mcp.notRunning": "not running", "scenes.mcp.
|
|
894
|
-
"scenes.mcp.toolsOf": "
|
|
938
|
+
"scenes.mcp.allTools": "all tools", "scenes.mcp.pickedCount": "{count} tools picked", "scenes.mcp.notRunning": "not running", "scenes.mcp.edit": "Edit", "scenes.mcp.done": "Done", "scenes.mcp.noteLabel": "Scene note", "scenes.mcp.notePlaceholder": "Overrides the global note in this scene; restored on exit", "scenes.mcp.hasNote": "has note",
|
|
939
|
+
"scenes.mcp.toolsOf": "Pick tools", "scenes.mcp.probeTools": "Start the server to load tools", "scenes.mcp.unknownCount": "unknown tool count — open Edit to load", "scenes.mcp.noteHint": "Saved with the scene. It overrides the note shown on the MCP page and in the system prompt only while this scene is entered — and only for servers checked in this section. Restored on exit.", "scenes.mcp.noTools": "No tools listed for this server (it has never run, or its tools were never recorded)", "scenes.mcp.drillHint": "Checked = enabled in this scene; unchecked = disabled. “All” = every tool.",
|
|
895
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.",
|
|
896
941
|
"scenes.filter.skills": "Filter skills / directories", "scenes.filter.subagents": "Filter personas (name or description)", "scenes.filter.servers": "Filter servers",
|
|
897
|
-
|
|
942
|
+
|
|
898
943
|
"scenes.mem.title": "Memories", "scenes.mem.search": "Filter memories in this scene (name or description)",
|
|
899
944
|
"scenes.mem.noMatch": "Nothing matches", "scenes.mem.emptyScene": "This scene has no memories yet",
|
|
900
945
|
"memory.mode.current": "Active mode", "memory.mode.exit": "Exit mode",
|
|
901
946
|
"memory.archive.edit": "Profile", "memory.archive.title": "Scene profile",
|
|
902
947
|
"memory.archive.tools": "MCP tools", "memory.archive.skills": "Skills", "memory.archive.subagents": "Subagent binding", "memory.archive.memories": "Memories",
|
|
903
|
-
"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",
|
|
904
949
|
"memory.archive.removeSection": "Remove section", "memory.archive.save": "Save to scene",
|
|
905
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}",
|
|
906
|
-
"memory.result.archiveSaved": "Scene profile saved: {name}", "memory.result.modeSet": "Entered mode: {name}", "memory.result.modeExited": "Exited mode",
|
|
907
|
-
"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.",
|
|
908
953
|
"memory.btn.refresh": "Refresh", "memory.btn.new": "New memory", "memory.btn.create": "Create", "memory.btn.save": "Save",
|
|
909
954
|
"memory.btn.diagnose": "Check", "memory.btn.delete.confirm": "Move to trash",
|
|
910
955
|
"memory.diagnose.title": "Memory checkup", "memory.diagnose.summary": "{rules} memories · {scenes} scenes · {issues} issues", "memory.diagnose.clean": "No issues found.",
|
|
@@ -918,10 +963,10 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
918
963
|
"memory.scene.global": "Global", "memory.scene.count": "{count} memories", "memory.scene.new": "New memory", "memory.scene.browse": "Pick existing",
|
|
919
964
|
"memory.scene.empty": "No memories in this scene yet",
|
|
920
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”).",
|
|
921
|
-
"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.",
|
|
922
967
|
"memory.scene.field.name": "Scene name", "memory.scene.field.name.placeholder": "e.g. office",
|
|
923
|
-
"memory.scene.field.name.hint": "
|
|
924
|
-
"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.",
|
|
925
970
|
"memory.scene.field.desc": "Description (optional)", "memory.scene.field.desc.placeholder": "One line on what this scene is for",
|
|
926
971
|
"memory.search.placeholder": "Search name / description / scene", "memory.filter.all": "All scenes",
|
|
927
972
|
"memory.empty": "No memories yet — click “New memory”", "memory.empty.search": "No matching memories",
|
|
@@ -947,21 +992,21 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
947
992
|
"memory.trash.confirmTitle": "Delete forever?", "memory.trash.confirmDesc": "“{name}” is deleted forever and cannot be recovered",
|
|
948
993
|
"memory.btn.newScene": "New scene", "memory.btn.deleteScene": "Delete scene", "memory.btn.saveScene": "Save changes",
|
|
949
994
|
"memory.scene.createTitle": "New scene",
|
|
950
|
-
"memory.deleteScene.title": "Delete scene?", "memory.deleteScene.desc": "Moves
|
|
951
|
-
"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)",
|
|
952
997
|
"memory.table.name": "Memory name and description", "memory.table.tags": "Tags", "memory.table.status": "Status",
|
|
953
|
-
"error.rules.invalidArgs": "Missing arguments: pass all:true (enable everything) or scenes:[...] (explicit narrowing)", "error.rules.invalidGroup": "Invalid scene/group name (non-empty, ≤64 chars, no path separators or < > : \" | ? *, must not start with a dot)", "error.rules.invalidName": "Invalid memory name (non-empty, ≤64 chars, no / \\ < > : \" | ? *, must not start with a dot)", "error.rules.descriptionRequired": "Description is required", "error.rules.descriptionTooLong": "Description is too long (max 500 chars)", "error.rules.bodyRequired": "Body is required", "error.rules.tooLarge": "Rule content is too large", "error.rules.shadowed": "Rule is shadowed by a bundle of the same name", "error.rules.notFound": "Rule not found", "error.rules.budgetExceeded": "Scene memory section exceeds its budget", "error.rules.ioFailed": "File operation failed", "error.rules.sceneNotEmpty": "Scene is not empty; cannot be deleted", "error.rules.notBundle": "This memory is flat (a single file) and cannot carry attachments", "error.rules.noFiles": "No files selected", "error.rules.emptyFile": "Empty file", "error.rules.fileTooLarge": "File too large (max {limit} MB each)", "error.rules.tooManyFiles": "At most {limit} files per upload", "error.rules.sceneInMode": "This scene is the active mode; exit the mode before deleting it",
|
|
954
|
-
"subagents.title": "Subagents", "subagents.desc": "Manage
|
|
955
|
-
"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.",
|
|
956
1001
|
"sessions.desc": "Manage archived sessions: restore or delete them permanently; expired ones are cleaned up automatically.",
|
|
957
|
-
"subagents.new": "New
|
|
1002
|
+
"subagents.new": "New subagent", "subagents.empty": "No subagents yet — click “New subagent” to create the first one.",
|
|
958
1003
|
"subagents.create": "New persona", "subagents.edit": "Edit persona",
|
|
959
1004
|
"subagents.field.name": "Persona name",
|
|
960
1005
|
"subagents.field.description": "Description", "subagents.field.description.placeholder": "e.g. Senior Java engineer",
|
|
961
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.",
|
|
962
1007
|
"subagents.model.inherit": "Inherit the main session (unspecified)", "subagents.model.customOption": "Custom / not in the catalogue…",
|
|
963
1008
|
"subagents.field.modes": "Tool limits (per agent preset)",
|
|
964
|
-
"subagents.field.modes.hint": "
|
|
1009
|
+
"subagents.field.modes.hint": "Only the row for the current preset applies; other presets keep their full tool set.",
|
|
965
1010
|
"subagents.mode.startAllow": "Enable allowlist", "subagents.mode.startDeny": "Enable denylist", "subagents.mode.stopAllow": "Disable allowlist", "subagents.mode.stopDeny": "Disable denylist",
|
|
966
1011
|
"subagents.mode.off": "no restriction", "subagents.mode.empty": "enabled with nothing picked (= no restriction)",
|
|
967
1012
|
"subagents.mode.allowOn": "allowlist {count}", "subagents.mode.denyOn": "denylist {count}",
|
|
@@ -980,19 +1025,17 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
980
1025
|
"subagents.field.body": "Persona prompt", "subagents.field.body.placeholder": "Describe the persona's role, responsibilities, and working style…",
|
|
981
1026
|
"subagents.result.saved": "Persona saved: {name}", "subagents.result.deleted": "Persona deleted: {name}",
|
|
982
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).",
|
|
983
|
-
"subagents.import": "Import", "subagents.import.title": "Import
|
|
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.",
|
|
984
1029
|
"subagents.result.imported": "Imported {count} persona(s): {names}",
|
|
985
1030
|
"memory.import": "Import memories", "memory.import.title": "Import memories", "memory.import.scene": "Import into scene",
|
|
986
|
-
"memory.import.sceneHint": "Blank = global; directory names inside the zip become scene names.",
|
|
987
|
-
"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.",
|
|
988
1031
|
"memory.result.imported": "Imported {count} memory/memories: {names}",
|
|
989
1032
|
"memory.import.scenesCreated": "Scenes created for them: {names}",
|
|
990
1033
|
"memory.scene.global.hint": "Global",
|
|
991
|
-
|
|
1034
|
+
"import.clear": "Clear", "import.submit": "Import",
|
|
992
1035
|
"import.none": "Nothing was imported (all skipped)", "import.skipped": "Skipped: {items}",
|
|
993
1036
|
"error.import.noFiles": "No files selected",
|
|
994
1037
|
"nav.title": "Tools",
|
|
995
|
-
"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.group.dirMissing": "Directory missing", "hist.group.dirMissing.hint": "The workspace directory was deleted from disk, so it cannot be registered again; sessions in this group can still be restored (they join Ungrouped) or deleted forever", "hist.btn.registerWs": "Register", "hist.btn.registerWs.hint": "Register this directory again and attach its existing sessions back to that group (workspace accounting only — no file or session is moved or deleted)", "hist.register.title": "Register again?", "hist.register.body": "A workspace registration will be created in DSH for “{title}”, and the sessions under this directory will be attached back to it (restoring their grouping). This writes workspace accounting only — no file or session is moved or deleted.", "hist.register.confirm": "Register", "hist.register.done.title": "Registered", "hist.register.done.body": "Workspace “{title}” is registered; archived sessions under this directory join that group after a refresh.", "hist.register.done.attached": "Attached {count} existing session(s) under this directory back to the workspace.", "hist.register.done.skipped": "{count} session(s) could not be attached: {reason}", "hist.err.register": "Registration failed", "scenes.field.prompt": "Prompt preset", "scenes.agents.note.applied": "wrote “{id}” into AGENTS.md", "scenes.agents.note.restored": "restored the AGENTS.md baseline from before the scene", "scenes.agents.note.error": "AGENTS.md was not written: {reason}", "scenes.field.prompt.hint": "While this scene is enabled, its preset is the active global baseline. Defaults to the one currently in effect.", "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",
|
|
996
1039
|
}
|
|
997
1040
|
};
|
|
998
1041
|
|
|
@@ -1050,14 +1093,14 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1050
1093
|
? { kind: 'warn', text: mt('mcp.msg.warn', { warning: res.warning }) }
|
|
1051
1094
|
: { kind: 'ok', text: mt('mcp.msg.ok') }
|
|
1052
1095
|
)
|
|
1053
|
-
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' })
|
|
1054
1097
|
const kvToLines = (obj) => (obj ? Object.keys(obj).map((k) => k + '=' + obj[k]).join('\n') : '')
|
|
1055
1098
|
// 级别筛选选项:必须在**渲染时**取词。原来写成模块级常量,apply() 早于 locale
|
|
1056
1099
|
// 注册,导致英文界面下这四项永远显示中文(且再也翻不过来)。
|
|
1057
1100
|
const mcpLevelOptions = () => [
|
|
1058
1101
|
{ value: '', label: mt('mcp.level.all') },
|
|
1059
|
-
{ value: 'project', label: mt('mcp.level.project') },
|
|
1060
1102
|
{ value: 'global', label: mt('mcp.level.global') },
|
|
1103
|
+
{ value: 'project', label: mt('mcp.level.project') },
|
|
1061
1104
|
{ value: 'loader', label: mt('mcp.level.loader') },
|
|
1062
1105
|
]
|
|
1063
1106
|
const MCP_TRANSPORT_OPTIONS = [
|
|
@@ -1069,11 +1112,12 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1069
1112
|
return s.length <= 4 ? '****' : s.slice(0, 4) + '****'
|
|
1070
1113
|
}
|
|
1071
1114
|
|
|
1072
|
-
//
|
|
1115
|
+
// 顶部栏右端:GitHub 链接(issues 在仓库)+ 版本号(全界面唯一显示处)。
|
|
1073
1116
|
function FeedbackLinks() {
|
|
1074
1117
|
return React.createElement('div', { className: 'dsm-feedback-links' },
|
|
1075
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') },
|
|
1076
|
-
React.createElement(GithubMark16, null))
|
|
1119
|
+
React.createElement(GithubMark16, null)),
|
|
1120
|
+
React.createElement(VersionBadgeComponent, null))
|
|
1077
1121
|
}
|
|
1078
1122
|
|
|
1079
1123
|
// MCP 服务页:与 Skills 页共用 dsm-* 设计语言(统计卡 / 筛选 / 分组卡片 / 模态框)。
|
|
@@ -1109,6 +1153,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1109
1153
|
errors: (res && res.errors) || [],
|
|
1110
1154
|
warnings: (res && res.warnings) || [],
|
|
1111
1155
|
revealError: revealError || null,
|
|
1156
|
+
locked: (res && res.anyLocked) === true,
|
|
1112
1157
|
})
|
|
1113
1158
|
if (onRows) onRows(rows)
|
|
1114
1159
|
}
|
|
@@ -1120,7 +1165,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1120
1165
|
return apiCall('mcpm-list', {}).then((masked) => applyRows(masked, { code: res.code, text: res.error }))
|
|
1121
1166
|
}
|
|
1122
1167
|
applyRows(res, null)
|
|
1123
|
-
}).catch((e) => setState({ loading: false, error: String((e && e.message) || e), rows: [], paths: null, errors: [], warnings: [], revealError: null }))
|
|
1168
|
+
}).catch((e) => setState({ loading: false, error: String((e && e.message) || e), rows: [], paths: null, errors: [], warnings: [], revealError: null, locked: false }))
|
|
1124
1169
|
}
|
|
1125
1170
|
// The polling tick must always call the newest refresh closure, while
|
|
1126
1171
|
// the interval itself only changes when the saved settings change.
|
|
@@ -1331,7 +1376,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1331
1376
|
const renderRow = (row) => {
|
|
1332
1377
|
const status = liveStatus(row)
|
|
1333
1378
|
const hint = liveHint(row)
|
|
1334
|
-
|
|
1379
|
+
// 场景锁定期间整页只读(开关/编辑/删除一并消失,重启也归入冻结)。
|
|
1380
|
+
const editable = row.level !== 'loader' && state.locked !== true
|
|
1335
1381
|
return React.createElement('div', { key: row.id, className: 'dsm-row' },
|
|
1336
1382
|
React.createElement('div', { className: 'dsm-main' },
|
|
1337
1383
|
React.createElement('div', { className: 'dsm-name' }, row.serverName),
|
|
@@ -1357,7 +1403,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1357
1403
|
return React.createElement('section', { key: group.key, className: 'dsm-source' },
|
|
1358
1404
|
React.createElement('div', { className: 'dsm-source-head' },
|
|
1359
1405
|
React.createElement('button', { type: 'button', className: 'dsm-source-head-main', 'aria-expanded': open, onClick: () => setCollapsed(Object.assign({}, collapsed, { [group.key]: !open })) },
|
|
1360
|
-
React.createElement('span', { className: 'dsm-source-title' }, group.title),
|
|
1406
|
+
React.createElement('span', { className: 'dsm-source-title', title: group.title }, group.title),
|
|
1361
1407
|
React.createElement('span', { className: 'dsm-count' }, mt('mcp.servers.count', { count: groupRows.length })),
|
|
1362
1408
|
group.path ? React.createElement('span', { className: 'dsm-path', title: group.path }, group.path) : null)),
|
|
1363
1409
|
open ? React.createElement('div', { className: 'dsm-source-body' },
|
|
@@ -1385,7 +1431,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1385
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 },
|
|
1386
1432
|
React.createElement('div', { className: 'dsm-tool-name-row' },
|
|
1387
1433
|
React.createElement('div', { className: 'dsm-tool-name' }, tool.name),
|
|
1388
|
-
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) })),
|
|
1389
1435
|
tool.description ? React.createElement('div', { className: 'dsm-tool-desc' }, tool.description) : null,
|
|
1390
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 },
|
|
1391
1437
|
React.createElement('span', { className: 'dsm-tool-param-key' }, param.key + (param.required ? ' *' : '')),
|
|
@@ -1418,8 +1464,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1418
1464
|
React.createElement(SourceSelect, {
|
|
1419
1465
|
value: formModal.level,
|
|
1420
1466
|
options: [
|
|
1421
|
-
{ value: 'project', label: mt('mcp.field.level.project', { path: state.paths ? state.paths.project : 'profiles/*/cordis.patch.yml' }) },
|
|
1422
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' }) },
|
|
1423
1469
|
],
|
|
1424
1470
|
onChange: (value) => setFormValue('level', value),
|
|
1425
1471
|
})),
|
|
@@ -1474,7 +1520,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1474
1520
|
detailStatus.text + ':' + (detailHint || mt('mcp.detail.registered')))),
|
|
1475
1521
|
React.createElement('div', { className: 'dsm-detail-section' },
|
|
1476
1522
|
React.createElement('div', { className: 'dsm-detail-title' }, mt('mcp.detail.note')),
|
|
1477
|
-
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) }),
|
|
1478
1524
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
1479
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')))),
|
|
1480
1526
|
React.createElement('div', { className: 'dsm-detail-section' },
|
|
@@ -1489,7 +1535,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1489
1535
|
},
|
|
1490
1536
|
React.createElement('p', { className: 'dsm-desc' }, mt('mcp.compact.desc')),
|
|
1491
1537
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
1492
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: () => setCompactConfirm(false) }, mt('btn.
|
|
1538
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: () => setCompactConfirm(false) }, mt('btn.close')),
|
|
1493
1539
|
React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: busy !== null, onClick: doCompact }, mt('mcp.compact.confirm'))))
|
|
1494
1540
|
|
|
1495
1541
|
const confirmNode = confirmRow && React.createElement(Modal, {
|
|
@@ -1506,19 +1552,19 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1506
1552
|
React.createElement('div', { className: 'dsm-head' },
|
|
1507
1553
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
1508
1554
|
React.createElement('div', { className: 'dsm-title-row' },
|
|
1509
|
-
React.createElement('h2', { className: 'dsm-title' }, 'MCP'),
|
|
1510
|
-
VersionBadgeComponent ? React.createElement(VersionBadgeComponent, null) : null),
|
|
1555
|
+
React.createElement('h2', { className: 'dsm-title' }, 'MCP')),
|
|
1511
1556
|
React.createElement('p', { className: 'dsm-desc' }, mt('mcp.desc'))),
|
|
1512
1557
|
React.createElement('div', { className: 'dsm-actions' },
|
|
1513
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null || state.loading, onClick: () => refresh() }, mt('btn.refresh')),
|
|
1514
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: openAdd }, mt('mcp.btn.new')),
|
|
1515
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
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'))),
|
|
1522
1568
|
React.createElement('div', { className: 'dsm-filters' },
|
|
1523
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) }),
|
|
1524
1570
|
React.createElement('div', { className: 'dsm-source-filter' },
|
|
@@ -1558,6 +1604,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1558
1604
|
// 回收站:null = 关闭;{loading, error, entries}
|
|
1559
1605
|
var ts = React.useState(null), trash = ts[0], setTrash = ts[1]
|
|
1560
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]
|
|
1561
1609
|
/** 打开/刷新回收站(每次操作后重读,界面永远显示磁盘上的真实条目)。 */
|
|
1562
1610
|
function loadTrash(keepOpen) {
|
|
1563
1611
|
var next = Object.assign({ loading: true, error: null, entries: [] }, keepOpen ? trash || {} : {})
|
|
@@ -1583,25 +1631,45 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1583
1631
|
else setTrash(function (cur) { return Object.assign({}, cur, { error: (res && res.error) || t('mcp.msg.failed') }) })
|
|
1584
1632
|
}).catch(function (e) { setTrashBusy(false); setTrash(function (cur) { return Object.assign({}, cur, { error: String((e && e.message) || e) }) }) })
|
|
1585
1633
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
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
|
+
})
|
|
1600
1668
|
}
|
|
1601
1669
|
|
|
1602
1670
|
function refresh() {
|
|
1603
1671
|
apiCall('agentsmd-list', {}).then(function (res) {
|
|
1604
|
-
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 } })
|
|
1605
1673
|
}).catch(function (e) { setState({ loading: false, error: String((e && e.message) || e), presets: [], current: null, scenePrompt: null }) })
|
|
1606
1674
|
apiCall('agentsmd-get-current', {}).then(function (res) {
|
|
1607
1675
|
if (res && res.ok) setState(function (s) { return Object.assign({}, s, { current: res }) })
|
|
@@ -1610,11 +1678,6 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1610
1678
|
React.useEffect(function () { refresh() }, [])
|
|
1611
1679
|
|
|
1612
1680
|
var cur = state.current
|
|
1613
|
-
// 顶部「当前」:场景启用且绑了预设时,**以场景绑定的那份为准**(它就是生效中的基线)。
|
|
1614
|
-
var sceneLive = state.scenePrompt && state.scenePrompt.presetId && state.scenePrompt.scene && !state.scenePrompt.missing ? state.scenePrompt : null
|
|
1615
|
-
var curLabel = sceneLive
|
|
1616
|
-
? t('agm.current.byScene', { id: sceneLive.presetId, scene: sceneLive.scene })
|
|
1617
|
-
: (cur && cur.exists ? (cur.presetId || t('agm.current.external')) : t('agm.current.none'))
|
|
1618
1681
|
// 改名结果提示:短暂显示后自动消失(与场景页的结果条同一节奏)。
|
|
1619
1682
|
React.useEffect(function () {
|
|
1620
1683
|
if (!state.notice) return undefined
|
|
@@ -1625,7 +1688,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1625
1688
|
function openEdit(p) {
|
|
1626
1689
|
// id 可改:改名 = 目录改名,宿主会把场景绑定一起改名(见 rules-rebind-prompt)。
|
|
1627
1690
|
apiCall('agentsmd-read', { id: p.id }).then(function (res) {
|
|
1628
|
-
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 || '' })
|
|
1629
1692
|
}).catch(function () {})
|
|
1630
1693
|
}
|
|
1631
1694
|
function doApply(id) {
|
|
@@ -1642,13 +1705,15 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1642
1705
|
else refresh()
|
|
1643
1706
|
}).catch(function (e) { setBusy(null); setApplyConfirm(null); setState(function (s) { return Object.assign({}, s, { error: String((e && e.message) || e) }) }) })
|
|
1644
1707
|
}
|
|
1645
|
-
function doCreate(id, from, content) {
|
|
1708
|
+
function doCreate(id, from, content, description) {
|
|
1646
1709
|
setBusy('create')
|
|
1647
1710
|
// content 优先(用户直接写的内容);留空才走 from 复制;都没有 = 宿主空模板。
|
|
1648
1711
|
apiCall('agentsmd-create', {
|
|
1649
1712
|
id: id,
|
|
1650
1713
|
...(content ? { content: content } : {}),
|
|
1651
1714
|
...(from ? { from: from } : {}),
|
|
1715
|
+
// 描述只给使用者看(存 meta.json,不进 AGENTS.md)。
|
|
1716
|
+
description: String(description == null ? '' : description),
|
|
1652
1717
|
}).then(function (res) {
|
|
1653
1718
|
setBusy(null)
|
|
1654
1719
|
if (res && res.ok) { setCreateModal(null); refresh() }
|
|
@@ -1657,7 +1722,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1657
1722
|
}
|
|
1658
1723
|
function doUpdate() {
|
|
1659
1724
|
setBusy('update')
|
|
1660
|
-
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) {
|
|
1661
1726
|
setBusy(null)
|
|
1662
1727
|
if (res && res.ok) {
|
|
1663
1728
|
setEditModal(null)
|
|
@@ -1689,7 +1754,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1689
1754
|
return React.createElement('div', { key: p.id, className: 'dsm-source' },
|
|
1690
1755
|
React.createElement('div', { className: 'dsm-source-head' },
|
|
1691
1756
|
React.createElement('div', { className: 'dsm-source-head-main' },
|
|
1692
|
-
React.createElement('span', { className: 'dsm-source-title' }, p.id),
|
|
1757
|
+
React.createElement('span', { className: 'dsm-source-title', title: p.id }, p.id),
|
|
1693
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,
|
|
1694
1759
|
!p.active && p.fileApplied ? React.createElement('span', { className: 'dsm-tag', title: t('agm.file.applied.hint') }, t('agm.file.applied')) : null,
|
|
1695
1760
|
boundByScene && !viaScene ? React.createElement('span', { className: 'dsm-tag' + (boundByScene.missing ? ' dsm-tag-off' : ''), title: t('agm.scene.tag.hint') },
|
|
@@ -1697,30 +1762,50 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1697
1762
|
? t('agm.scene.missing', { scene: boundByScene.scene })
|
|
1698
1763
|
: t('agm.scene.tag', { scene: boundByScene.scene })) : null),
|
|
1699
1764
|
React.createElement('div', { className: 'dsm-source-actions' },
|
|
1700
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', onClick: function () { openEdit(p) } }, t('agm.btn.edit')),
|
|
1701
|
-
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')),
|
|
1702
|
-
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)
|
|
1703
1768
|
}))
|
|
1704
1769
|
|
|
1705
1770
|
return React.createElement('section', { className: 'dsm-section' },
|
|
1706
1771
|
React.createElement('div', { className: 'dsm-head' },
|
|
1707
1772
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
1708
1773
|
React.createElement('div', { className: 'dsm-title-row' },
|
|
1709
|
-
React.createElement('h2', { className: 'dsm-title' }, t('tabs.prompts')),
|
|
1710
|
-
|
|
1711
|
-
React.createElement('p', { className: 'dsm-desc' }, t('prompts.desc')),
|
|
1712
|
-
// 当前基线:场景启用并绑了预设时,这里显示的就是**场景绑的那份**(用户裁定)。
|
|
1713
|
-
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'))),
|
|
1714
1776
|
React.createElement('div', { className: 'dsm-actions' },
|
|
1715
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: function () {
|
|
1716
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled:
|
|
1717
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-
|
|
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
|
+
})),
|
|
1718
1788
|
body,
|
|
1719
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,
|
|
1720
1803
|
trash ? React.createElement(TrashModal, {
|
|
1721
1804
|
t: t,
|
|
1722
1805
|
title: t('trash.title'),
|
|
1723
1806
|
groupTitle: t('trash.group.presets'),
|
|
1807
|
+
groupSub: t('trash.section.presets.sub'),
|
|
1808
|
+
locked: state.anyLocked === true,
|
|
1724
1809
|
entries: trash.entries,
|
|
1725
1810
|
loading: trash.loading,
|
|
1726
1811
|
error: trash.error,
|
|
@@ -1730,11 +1815,14 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1730
1815
|
onPurge: purgePreset,
|
|
1731
1816
|
}) : null,
|
|
1732
1817
|
// 编辑:id 与内容都可改(改 id = 目录改名,场景绑定由宿主一起改名)。
|
|
1733
|
-
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) } },
|
|
1734
1819
|
React.createElement('div', { className: 'dsm-field' },
|
|
1735
1820
|
React.createElement('label', { className: 'dsm-label' }, t('agm.field.id')),
|
|
1736
1821
|
React.createElement('input', { className: 'dsm-control', value: editModal.nextId || '', onChange: function (e) { setEditModal(Object.assign({}, editModal, { nextId: e.target.value, error: null })) } }),
|
|
1737
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 })) } })),
|
|
1738
1826
|
React.createElement('div', { className: 'dsm-field' },
|
|
1739
1827
|
React.createElement('label', { className: 'dsm-label' }, t('agm.field.content')),
|
|
1740
1828
|
React.createElement('textarea', { className: 'dsm-control dsm-textarea-lg', value: editModal.content, onChange: function (e) { setEditModal(Object.assign({}, editModal, { content: e.target.value })) } })),
|
|
@@ -1743,11 +1831,14 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1743
1831
|
React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: busy !== null || !String(editModal.nextId || '').trim(), onClick: doUpdate }, t('agm.btn.save')))) : null,
|
|
1744
1832
|
// 新建:id + **正文文本框**(用户直接写内容);「从现有预设复制」是可选下拉,
|
|
1745
1833
|
// 选中后把那份正文填进文本框,仍可继续改——不再是容易被当成搜索框的文本输入。
|
|
1746
|
-
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) } },
|
|
1747
1835
|
React.createElement('div', { className: 'dsm-field' },
|
|
1748
1836
|
React.createElement('label', { className: 'dsm-label' }, t('agm.field.id')),
|
|
1749
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 })) } }),
|
|
1750
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 })) } })),
|
|
1751
1842
|
(state.presets || []).length ? React.createElement('div', { className: 'dsm-field' },
|
|
1752
1843
|
React.createElement('label', { className: 'dsm-label' }, t('agm.field.copyFrom')),
|
|
1753
1844
|
React.createElement(SourceSelect, {
|
|
@@ -1760,8 +1851,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1760
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 })) } })),
|
|
1761
1852
|
createModal.error ? React.createElement('div', { className: 'dsm-feedback dsm-error' }, createModal.error) : null,
|
|
1762
1853
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
1763
|
-
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,
|
|
1764
|
-
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) } },
|
|
1765
1856
|
applyConfirm.remove
|
|
1766
1857
|
? React.createElement('p', { className: 'dsm-help' }, t('agm.remove.title') + ' ' + applyConfirm.id + t('agm.remove.suffix'))
|
|
1767
1858
|
: React.createElement('div', null,
|
|
@@ -1769,7 +1860,13 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1769
1860
|
React.createElement('div', { className: 'dsm-feedback dsm-warning' }, t('agm.apply.note'))),
|
|
1770
1861
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
1771
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,
|
|
1772
|
-
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)
|
|
1773
1870
|
}
|
|
1774
1871
|
|
|
1775
1872
|
// 「工具」设置页:一个侧栏项,内部 tab 切换 场景 / MCP / 技能 / 子智能体 / 提示词 / 记忆 / 会话。
|
|
@@ -1780,8 +1877,21 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1780
1877
|
React.useEffect(function () {
|
|
1781
1878
|
// 设置宿主的真正滚动容器,而不是 body:不同页签内容高度不同,
|
|
1782
1879
|
// 让滚动条按需出现会改变 options 宽度,页签/GitHub 随之左右跳动。
|
|
1880
|
+
//
|
|
1881
|
+
// ⚠️ 必须**向上查找**而不是固定跳两级:`.dsm-tabs-shell` 的 position:sticky
|
|
1882
|
+
// 依赖祖先链上没有被 overflow 裁剪的层,宿主 DOM 结构一变,「上两级」就可能
|
|
1883
|
+
// 指到非滚动容器(sticky 随之失效,页签被内容滚走)。
|
|
1783
1884
|
var section = sectionRef.current
|
|
1784
|
-
|
|
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
|
|
1785
1895
|
if (!host) return undefined
|
|
1786
1896
|
host.classList.add('dsm-settings-scroll-host')
|
|
1787
1897
|
return function () { host.classList.remove('dsm-settings-scroll-host') }
|
|
@@ -1797,14 +1907,14 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1797
1907
|
return React.createElement('section', { ref: sectionRef, className: 'dsm-section' },
|
|
1798
1908
|
React.createElement('div', { className: 'dsm-tabs-shell' },
|
|
1799
1909
|
React.createElement('div', { className: 'dsm-tabs' },
|
|
1800
|
-
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'scenes' ? ' dsm-tab-active' : ''), onClick: function () { setActive('scenes') } }, t('tabs.scenes')),
|
|
1801
|
-
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'mcp' ? ' dsm-tab-active' : ''), onClick: function () { setActive('mcp') } }, 'MCP'),
|
|
1802
|
-
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'skills' ? ' dsm-tab-active' : ''), onClick: function () { setActive('skills') } }, t('tabs.skills')),
|
|
1803
|
-
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'subagents' ? ' dsm-tab-active' : ''), onClick: function () { setActive('subagents') } }, t('tabs.subagents')),
|
|
1804
|
-
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'prompts' ? ' dsm-tab-active' : ''), onClick: function () { setActive('prompts') } }, t('tabs.prompts')),
|
|
1805
|
-
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'memory' ? ' dsm-tab-active' : ''), onClick: function () { setActive('memory') } }, t('tabs.memory')),
|
|
1806
|
-
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'sessions' ? ' dsm-tab-active' : ''), onClick: function () { setActive('sessions') } }, t('tabs.sessions')),
|
|
1807
|
-
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'))),
|
|
1808
1918
|
React.createElement(FeedbackLinks, null)),
|
|
1809
1919
|
page)
|
|
1810
1920
|
}
|
|
@@ -1953,7 +2063,12 @@ function callApi(path, options) {
|
|
|
1953
2063
|
}
|
|
1954
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 }); }
|
|
1955
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" })); }
|
|
1956
|
-
|
|
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)); }
|
|
1957
2072
|
/**
|
|
1958
2073
|
* 全插件唯一的「操作结果」呈现方式:成功 = 右下角浮层(固定定位,不改变页面高度),
|
|
1959
2074
|
* 警告 / 错误 = 文档流内横幅(需要用户处理,常驻到下一次操作)。所有页面共用,
|
|
@@ -1998,8 +2113,8 @@ function callApi(path, options) {
|
|
|
1998
2113
|
React.createElement('button', { key: 'no', type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: props.busy, onClick: function () { setConfirmId(null) } }, t('btn.cancel')),
|
|
1999
2114
|
]
|
|
2000
2115
|
: [
|
|
2001
|
-
React.createElement('button', { key: 'restore', type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: props.busy, onClick: function () { props.onRestore(item) } }, t('btn.restore')),
|
|
2002
|
-
React.createElement('button', { key: 'purge', type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: props.busy, onClick: function () { setConfirmId(item.id) } }, t('btn.delete.forever')),
|
|
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')),
|
|
2003
2118
|
]
|
|
2004
2119
|
return React.createElement('div', { className: 'dsm-trash-item', key: item.id },
|
|
2005
2120
|
React.createElement('div', { className: 'dsm-trash-main' },
|
|
@@ -2014,18 +2129,25 @@ function callApi(path, options) {
|
|
|
2014
2129
|
: entries.length === 0
|
|
2015
2130
|
? React.createElement('div', { className: 'dsm-empty' }, t('trash.empty'))
|
|
2016
2131
|
: entries.map(row)
|
|
2132
|
+
// 列表类弹窗:宽高都固定(`.dsm-modal-list`),滚动只发生在 Modal 的 body 这一层 ——
|
|
2133
|
+
// 条目数变化时弹窗不跳,也不会出现「弹窗滚 + 组内滚」的嵌套滚动条。
|
|
2017
2134
|
return React.createElement(Modal, {
|
|
2018
2135
|
key: 'trash-modal',
|
|
2019
2136
|
title: props.title,
|
|
2020
2137
|
closeLabel: t('btn.close'),
|
|
2138
|
+
list: true,
|
|
2139
|
+
className: 'dsm-modal-list',
|
|
2021
2140
|
onClose: props.onClose,
|
|
2022
2141
|
},
|
|
2023
2142
|
props.error ? React.createElement(Notice, { kind: 'err', text: props.error }) : null,
|
|
2024
2143
|
React.createElement('div', { className: 'dsm-trash-group' },
|
|
2025
2144
|
React.createElement('div', { className: 'dsm-trash-group-head' },
|
|
2026
|
-
React.createElement('
|
|
2027
|
-
|
|
2028
|
-
|
|
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)))
|
|
2029
2151
|
}
|
|
2030
2152
|
|
|
2031
2153
|
/** 勾选行:复选框 + 名称/说明 + 右侧指标 + 可选行内动作。 */
|
|
@@ -2051,13 +2173,17 @@ function callApi(path, options) {
|
|
|
2051
2173
|
h('span', { className: 'dsm-seg-group-line' }))
|
|
2052
2174
|
}
|
|
2053
2175
|
/** 段头公共动作:全选 / 清空 / 移除段(未定义时改为「添加」)。 */
|
|
2176
|
+
/**
|
|
2177
|
+
* 段头动作。**批量动作合二为一**(D11):同一时刻只有一个方向是合理的 ——
|
|
2178
|
+
* 还有没勾的 → 「全选」;全勾了 → 「全不选」。并列两个必然有一个是废话。
|
|
2179
|
+
* 未定义的可选段只给「添加」;「移除段」不属于全选族,保留。
|
|
2180
|
+
*/
|
|
2054
2181
|
function segActions(props) {
|
|
2055
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)]
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
]
|
|
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
|
|
2061
2187
|
}
|
|
2062
2188
|
/** 段脚注:段已定义但一项未勾 = 该域全部停用(必须显式提示,否则像没保存上)。 */
|
|
2063
2189
|
function segFoot(defined, count, emptyLabel) {
|
|
@@ -2073,17 +2199,28 @@ function callApi(path, options) {
|
|
|
2073
2199
|
// 通用导入弹窗(人设 / 记忆共用):拖放或点选 .md / .zip → 已选计数 → 交由调用方走自己的写 op。
|
|
2074
2200
|
// t 由调用方注入(各页的 t 是局部函数,不在模块作用域);extra 用于注入「导入到场景」一类的额外字段。
|
|
2075
2201
|
function ImportModal(props) {
|
|
2076
|
-
|
|
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]
|
|
2077
2206
|
var ref = react.useRef(null)
|
|
2207
|
+
var folderRef = react.useRef(null)
|
|
2078
2208
|
var t = props.t
|
|
2079
2209
|
function add(list) {
|
|
2080
|
-
var
|
|
2081
|
-
if (!
|
|
2082
|
-
Promise.all(
|
|
2083
|
-
.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
|
+
})
|
|
2084
2217
|
.catch(function () {})
|
|
2085
2218
|
}
|
|
2086
|
-
|
|
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 },
|
|
2087
2224
|
h("div", { className: "dsm-form" },
|
|
2088
2225
|
props.extra || null,
|
|
2089
2226
|
h("div", {
|
|
@@ -2093,13 +2230,97 @@ function callApi(path, options) {
|
|
|
2093
2230
|
onDragLeave: function (e) { if (e.currentTarget.classList) e.currentTarget.classList.remove("dsm-dropzone-active") },
|
|
2094
2231
|
onDrop: function (e) { e.preventDefault(); if (e.currentTarget.classList) e.currentTarget.classList.remove("dsm-dropzone-active"); droppedFiles(e.dataTransfer).then(add) },
|
|
2095
2232
|
},
|
|
2096
|
-
h("
|
|
2097
|
-
h("
|
|
2098
|
-
|
|
2099
|
-
|
|
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),
|
|
2100
2252
|
h("div", { className: "dsm-modal-actions" },
|
|
2101
|
-
|
|
2102
|
-
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
|
|
2103
2324
|
}
|
|
2104
2325
|
|
|
2105
2326
|
// 目录选择器:经 host 的 dir-list 逐级浏览目录树,选中后把绝对路径回填给调用方。
|
|
@@ -2124,7 +2345,7 @@ function callApi(path, options) {
|
|
|
2124
2345
|
function goUp() { if (!st.parent) return; setText(st.parent); load(st.parent) }
|
|
2125
2346
|
function enter(path) { setText(path); load(path) }
|
|
2126
2347
|
function pick() { var target = String(st.current || '').trim() || String(text || '').trim(); if (target && props.onPick) props.onPick(target) }
|
|
2127
|
-
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 },
|
|
2128
2349
|
h("div", { className: "dsm-form" },
|
|
2129
2350
|
h("label", { className: "dsm-field" },
|
|
2130
2351
|
h("span", { className: "dsm-label" }, '当前路径'),
|
|
@@ -2155,8 +2376,6 @@ function callApi(path, options) {
|
|
|
2155
2376
|
var ds = react.useState(null), detail = ds[0], setDetail = ds[1];
|
|
2156
2377
|
var cs = react.useState({ path: "", label: "" }), customForm = cs[0], setCustomForm = cs[1];
|
|
2157
2378
|
var inflightRef = react.useRef(false);
|
|
2158
|
-
var importInputRef = react.useRef(null);
|
|
2159
|
-
var folderInputRef = react.useRef(null);
|
|
2160
2379
|
var pickerOpenRef = react.useRef(false);
|
|
2161
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 }); }); }
|
|
2162
2381
|
react.useEffect(function () { refresh(false); }, []);
|
|
@@ -2199,8 +2418,33 @@ function callApi(path, options) {
|
|
|
2199
2418
|
}
|
|
2200
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 () {}); }
|
|
2201
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); }
|
|
2202
|
-
|
|
2203
|
-
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);
|
|
2204
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 : "";
|
|
2205
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") });
|
|
2206
2450
|
// 新建技能的默认落点:hub(`tool-management/skills/`)。
|
|
@@ -2248,40 +2492,86 @@ function callApi(path, options) {
|
|
|
2248
2492
|
function renderSkill(root, skill) {
|
|
2249
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";
|
|
2250
2494
|
var canToggle = root.toggleable !== false && skill.loadable !== false;
|
|
2251
|
-
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));
|
|
2252
2496
|
}
|
|
2253
2497
|
function renderRoot(root) {
|
|
2254
2498
|
if (activeSource && activeSource !== root.key) return null;
|
|
2255
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;
|
|
2256
2500
|
var rootCount = root.count == null ? root.skills.length : root.count;
|
|
2257
|
-
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);
|
|
2258
2502
|
}
|
|
2259
2503
|
|
|
2260
2504
|
var summary = data.summary || { total: 0, enabled: 0, disabled: 0, issues: 0 };
|
|
2261
|
-
var content = [h("style", { key: "css" }, CSS), h("div", { key: "head", className: "dsm-head" }, h("div", { className: "dsm-title-block" }, h("div", { className: "dsm-title-row" }, h("h2", { className: "dsm-title" }, t("title"))
|
|
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))]);
|
|
2262
2507
|
|
|
2263
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")))));
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
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
|
+
}));
|
|
2275
2531
|
// 回收站分两部分:**目录**(被「移除来源」的目录,插件不再读取)与**技能**(移入
|
|
2276
2532
|
// 回收站的技能)。两部分都给了「恢复」,永久删除在两边都指向"记录/文件真的没了":
|
|
2277
2533
|
// 目录那边只删插件里的来源记录(磁盘上的文件夹一个字节都不动),技能那边才删文件。
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
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
|
+
}
|
|
2285
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")))));
|
|
2286
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")))));
|
|
2287
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")))));
|
|
@@ -2312,7 +2602,6 @@ function callApi(path, options) {
|
|
|
2312
2602
|
// 导入对话弹窗:null = 关闭;{ busy, error, result, cwd } = 打开。
|
|
2313
2603
|
var impState = React.useState(null)
|
|
2314
2604
|
var imp = impState[0], setImp = impState[1]
|
|
2315
|
-
var importInputRef = React.useRef(null)
|
|
2316
2605
|
// 导出对话弹窗:null = 关闭;{ busy, error, result, outDir, format, wsFilter, selectedIds } = 打开。
|
|
2317
2606
|
var expState = React.useState(null)
|
|
2318
2607
|
var exp = expState[0], setExp = expState[1]
|
|
@@ -2380,6 +2669,7 @@ function callApi(path, options) {
|
|
|
2380
2669
|
kind: meta ? meta.kind : null,
|
|
2381
2670
|
registered: !!(meta && meta.registered),
|
|
2382
2671
|
canRegister: !!(meta && meta.canRegister),
|
|
2672
|
+
dirMissing: !!(meta && meta.dirMissing),
|
|
2383
2673
|
ungrouped: key === 'ungrouped',
|
|
2384
2674
|
items: items2, maxAt: maxAt,
|
|
2385
2675
|
})
|
|
@@ -2406,7 +2696,7 @@ function callApi(path, options) {
|
|
|
2406
2696
|
items2.sort(function (a, b) { return ((b.archivedAt || b.createdAt) || 0) - ((a.archivedAt || a.createdAt) || 0) })
|
|
2407
2697
|
var maxAt = 0
|
|
2408
2698
|
items2.forEach(function (it) { var at = (it.archivedAt || it.createdAt) || 0; if (at > maxAt) maxAt = at })
|
|
2409
|
-
groups.push({ key: key, title: title, workspaceId: wid, path: wsRec && wsRec.path, kind: null, registered: false, canRegister: false, ungrouped: key === 'ungrouped', items: items2, maxAt: maxAt })
|
|
2699
|
+
groups.push({ key: key, title: title, workspaceId: wid, path: wsRec && wsRec.path, kind: null, registered: false, canRegister: false, dirMissing: false, ungrouped: key === 'ungrouped', items: items2, maxAt: maxAt })
|
|
2410
2700
|
})
|
|
2411
2701
|
// 组按组内最新时间戳降序;未分组固定最后。
|
|
2412
2702
|
groups.sort(function (g1, g2) {
|
|
@@ -2545,13 +2835,6 @@ function callApi(path, options) {
|
|
|
2545
2835
|
}
|
|
2546
2836
|
reader.readAsText(file)
|
|
2547
2837
|
}
|
|
2548
|
-
function onImportDrop(e) {
|
|
2549
|
-
if (e && e.preventDefault) e.preventDefault()
|
|
2550
|
-
if (imp && imp.busy) return
|
|
2551
|
-
droppedFiles(e && e.dataTransfer).then(function (files) {
|
|
2552
|
-
if (files && files.length) doImportFile(files[0])
|
|
2553
|
-
}).catch(function () {})
|
|
2554
|
-
}
|
|
2555
2838
|
function openImportPicker() {
|
|
2556
2839
|
setImp({ busy: false, error: null, result: null, cwd: '' })
|
|
2557
2840
|
}
|
|
@@ -2708,9 +2991,10 @@ function callApi(path, options) {
|
|
|
2708
2991
|
React.createElement('span', { className: 'dsm-source-title dsm-hist-group-title', title: g.title }, g.title),
|
|
2709
2992
|
React.createElement('span', { className: 'dsm-count' }, t('hist.group.count', { count: g.items.length }))),
|
|
2710
2993
|
detached ? React.createElement('div', { className: 'dsm-hist-group-extra' },
|
|
2711
|
-
React.createElement('span', { className: 'dsm-tag'
|
|
2712
|
-
|
|
2713
|
-
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
|
|
2994
|
+
React.createElement('span', { className: 'dsm-tag', title: t('hist.group.removed.hint') },
|
|
2995
|
+
t('hist.group.removed')),
|
|
2996
|
+
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,
|
|
2997
|
+
g.dirMissing ? React.createElement('span', { className: 'dsm-hist-dir-missing', title: t('hist.group.dirMissing.hint') }, '⚠ ' + t('hist.group.dirMissing')) : null) : null,
|
|
2714
2998
|
g.path ? React.createElement('span', { className: 'dsm-path', title: g.path }, g.path) : null),
|
|
2715
2999
|
open ? React.createElement('div', { className: 'dsm-source-body' }, g.items.map(renderRow)) : null)
|
|
2716
3000
|
}
|
|
@@ -2719,11 +3003,10 @@ function callApi(path, options) {
|
|
|
2719
3003
|
React.createElement('div', { className: 'dsm-head' },
|
|
2720
3004
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
2721
3005
|
React.createElement('div', { className: 'dsm-title-row' },
|
|
2722
|
-
React.createElement('h2', { className: 'dsm-title' }, t('tabs.sessions')),
|
|
2723
|
-
VersionBadgeComponent ? React.createElement(VersionBadgeComponent, null) : null),
|
|
3006
|
+
React.createElement('h2', { className: 'dsm-title' }, t('tabs.sessions'))),
|
|
2724
3007
|
React.createElement('p', { className: 'dsm-desc' }, t('sessions.desc'))),
|
|
2725
3008
|
React.createElement('div', { className: 'dsm-actions' },
|
|
2726
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-
|
|
3009
|
+
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')),
|
|
2727
3010
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: openImportPicker }, t('hist.btn.import')),
|
|
2728
3011
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: openExportPicker }, t('hist.btn.export')),
|
|
2729
3012
|
selected.size > 0 ? React.createElement('div', { className: 'dsm-hist-batch' },
|
|
@@ -2731,11 +3014,13 @@ function callApi(path, options) {
|
|
|
2731
3014
|
React.createElement('div', { className: 'dsm-hist-batch-actions' },
|
|
2732
3015
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { doUnarchiveBatch() } }, t('hist.btn.restoreSelected', { count: selected.size })),
|
|
2733
3016
|
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)),
|
|
2734
|
-
React.createElement('div', { className: 'dsm-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
React.createElement('
|
|
2738
|
-
|
|
3017
|
+
React.createElement('div', { className: 'dsm-stat-row' },
|
|
3018
|
+
React.createElement('div', { className: 'dsm-summary' },
|
|
3019
|
+
[['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) {
|
|
3020
|
+
return React.createElement('div', { key: item[0], className: 'dsm-stat' },
|
|
3021
|
+
React.createElement('strong', null, item[1]), item[2])
|
|
3022
|
+
})),
|
|
3023
|
+
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'))),
|
|
2739
3024
|
React.createElement('div', { className: 'dsm-filters' },
|
|
2740
3025
|
React.createElement('input', { className: 'dsm-control dsm-search', type: 'text', placeholder: t('hist.search.placeholder'), value: query, onChange: function (e) { setQuery(e.target.value) } }),
|
|
2741
3026
|
React.createElement('div', { className: 'dsm-source-filter' },
|
|
@@ -2748,15 +3033,15 @@ function callApi(path, options) {
|
|
|
2748
3033
|
: filtered.length === 0 ? React.createElement('div', { className: 'dsm-empty' }, query ? t('hist.empty.search') : t('hist.empty.none'))
|
|
2749
3034
|
: React.createElement('div', { className: 'dsm-sources' },
|
|
2750
3035
|
grouped ? groups.map(renderGroup) : React.createElement('div', { className: 'dsm-source' }, filtered.map(renderRow))),
|
|
2751
|
-
modal && modal.type === 'delete' ? React.createElement(Modal, { key: 'del', title: t('hist.purge.title'), closeLabel: t('btn.
|
|
3036
|
+
modal && modal.type === 'delete' ? React.createElement(Modal, { key: 'del', title: t('hist.purge.title'), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
2752
3037
|
React.createElement('p', { className: 'dsm-help' }, t('hist.purge.one', { title: modal.title })),
|
|
2753
3038
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
2754
3039
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { doDelete(modal.sessionId) } }, t('hist.btn.confirmPurge')))) : null,
|
|
2755
|
-
modal && modal.type === 'deleteBatch' ? React.createElement(Modal, { key: 'delb', title: t('hist.purge.title'), closeLabel: t('btn.
|
|
3040
|
+
modal && modal.type === 'deleteBatch' ? React.createElement(Modal, { key: 'delb', title: t('hist.purge.title'), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
2756
3041
|
React.createElement('p', { className: 'dsm-help' }, t('hist.purge.batch', { count: modal.count })),
|
|
2757
3042
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
2758
3043
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { doDeleteBatch() } }, t('hist.btn.confirmPurge')))) : null,
|
|
2759
|
-
modal && modal.type === 'register' ? React.createElement(Modal, { key: 'reg', title: t('hist.register.title'), closeLabel: t('btn.
|
|
3044
|
+
modal && modal.type === 'register' ? React.createElement(Modal, { key: 'reg', title: t('hist.register.title'), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
2760
3045
|
React.createElement('p', { className: 'dsm-help' }, t('hist.register.body', { title: modal.title })),
|
|
2761
3046
|
React.createElement('p', { className: 'dsm-help dsm-hist-register-path' }, String(modal.path || '')),
|
|
2762
3047
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
@@ -2769,18 +3054,24 @@ function callApi(path, options) {
|
|
|
2769
3054
|
(modal.workspace && modal.workspace.attachSkipped && modal.workspace.attachSkipped.length)
|
|
2770
3055
|
? 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 || '') }))
|
|
2771
3056
|
: null) : null,
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
imp.error ?
|
|
2782
|
-
|
|
2783
|
-
|
|
3057
|
+
// D13:会话页也改用共享 ImportModal(能力取三者并集)。差异只在 extra 区
|
|
3058
|
+
// (项目目录)与提交回调:选完文件直接导入,没有二次确认。
|
|
3059
|
+
imp ? React.createElement(ImportModal, {
|
|
3060
|
+
key: 'imp',
|
|
3061
|
+
t: t,
|
|
3062
|
+
title: t('hist.btn.import'),
|
|
3063
|
+
accept: '.jsonl,.json,.md,.markdown,.txt',
|
|
3064
|
+
busy: imp.busy === true,
|
|
3065
|
+
requirements: [t('upload.requirement.session.1'), t('upload.requirement.session.2'), t('upload.requirement.session.3')],
|
|
3066
|
+
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),
|
|
3067
|
+
extra: React.createElement('div', null,
|
|
3068
|
+
React.createElement('div', { className: 'dsm-field' },
|
|
3069
|
+
React.createElement('label', { className: 'dsm-label' }, t('hist.import.cwd')),
|
|
3070
|
+
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 })) } }))),
|
|
3071
|
+
onClose: function () { setImp(null) },
|
|
3072
|
+
onSubmit: function (entries, files) { doImportFile(files[0]) },
|
|
3073
|
+
}) : null,
|
|
3074
|
+
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) } },
|
|
2784
3075
|
React.createElement('p', { className: 'dsm-help' }, t('hist.export.hint')),
|
|
2785
3076
|
React.createElement('div', { className: 'dsm-field' },
|
|
2786
3077
|
React.createElement('label', { className: 'dsm-label' }, t('hist.export.dir')),
|
|
@@ -2797,19 +3088,37 @@ function callApi(path, options) {
|
|
|
2797
3088
|
React.createElement('div', { className: 'dsm-field' },
|
|
2798
3089
|
React.createElement('label', { className: 'dsm-label' }, t('hist.export.workspace')),
|
|
2799
3090
|
React.createElement(SourceSelect, { options: exportWsOptions, value: exp.wsFilter, onChange: function (v) { setExp(Object.assign({}, exp, { wsFilter: v, selectedIds: new Set() })) } })),
|
|
2800
|
-
React.createElement('div', { className: 'dsm-field' },
|
|
3091
|
+
React.createElement('div', { className: 'dsm-field dsm-export-body' },
|
|
2801
3092
|
React.createElement('div', { className: 'dsm-label' }, t('hist.export.sessions', { count: exportItems.length })),
|
|
2802
|
-
React.createElement('div', { className: 'dsm-source' },
|
|
3093
|
+
React.createElement('div', { className: 'dsm-source dsm-pick-list' },
|
|
2803
3094
|
exp.loading ? React.createElement('div', { className: 'dsm-empty' }, t('hist.export.loading'))
|
|
2804
|
-
: exportItems.length ?
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
3095
|
+
: exportItems.length ? (function () {
|
|
3096
|
+
// 按**工作区目录**分组:不同文件夹的会话分开列,组头可折叠。
|
|
3097
|
+
var groups = [], byCwd = {}
|
|
3098
|
+
exportItems.forEach(function (it) {
|
|
3099
|
+
var ck = String(it.cwd || '')
|
|
3100
|
+
if (!byCwd[ck]) { byCwd[ck] = []; groups.push({ cwd: ck, rows: byCwd[ck] }) }
|
|
3101
|
+
byCwd[ck].push(it)
|
|
3102
|
+
})
|
|
3103
|
+
return groups.map(function (g) {
|
|
3104
|
+
var key = 'exp:' + g.cwd
|
|
3105
|
+
var open = collapsed[key] !== true
|
|
3106
|
+
return React.createElement('div', { key: key, className: 'dsm-pick-group' },
|
|
3107
|
+
React.createElement('button', { type: 'button', className: 'dsm-pick-group-head', 'aria-expanded': open ? 'true' : 'false', onClick: function () { toggleGroupCollapse(key) } },
|
|
3108
|
+
React.createElement('span', { className: 'dsm-pick-caret', 'aria-hidden': 'true' }, open ? '▾' : '▸'),
|
|
3109
|
+
React.createElement('span', { className: 'dsm-pick-group-name', title: g.cwd || t('hist.ungrouped') }, g.cwd || t('hist.ungrouped')),
|
|
3110
|
+
React.createElement('span', { className: 'dsm-pick-group-count' }, t('export.group.count', { count: g.rows.length }))),
|
|
3111
|
+
open ? g.rows.map(function (it) {
|
|
3112
|
+
return React.createElement('label', { key: it.sessionId, className: 'dsm-hist-row' },
|
|
3113
|
+
React.createElement('input', { type: 'checkbox', className: 'dsm-hist-check', checked: exp.selectedIds.has(it.sessionId), onChange: function () { toggleExportSelect(it.sessionId) } }),
|
|
3114
|
+
React.createElement('div', { className: 'dsm-hist-main' },
|
|
3115
|
+
React.createElement('div', { className: 'dsm-hist-title' }, it.title || ('Session ' + shortId(it.sessionId))),
|
|
3116
|
+
it.cwdMissing ? React.createElement('div', { className: 'dsm-hist-cwd dsm-hist-cwd-missing', title: t('hist.cwd.missing') + it.cwd }, '⚠ ' + it.cwd) : null),
|
|
3117
|
+
React.createElement('div', { className: 'dsm-hist-actions' },
|
|
3118
|
+
React.createElement('span', { className: 'dsm-tag' + (it.archived ? '' : ' dsm-tag-on') }, it.archived ? t('hist.tag.archived') : t('hist.tag.live'))))
|
|
3119
|
+
}) : null)
|
|
3120
|
+
})
|
|
3121
|
+
})() : React.createElement('div', { className: 'dsm-empty' }, t('hist.export.empty')))),
|
|
2813
3122
|
exp.error ? React.createElement('div', { className: 'dsm-feedback dsm-error' }, String(exp.error)) : null,
|
|
2814
3123
|
exp.result ? React.createElement('div', { className: 'dsm-feedback' },
|
|
2815
3124
|
exp.result.exported
|
|
@@ -3013,10 +3322,30 @@ function callApi(path, options) {
|
|
|
3013
3322
|
else setResult({ ok: false, text: translateError(t, res) })
|
|
3014
3323
|
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
3015
3324
|
}
|
|
3325
|
+
/**
|
|
3326
|
+
* 场景开关(P6):**唯一入口**。
|
|
3327
|
+
*
|
|
3328
|
+
* 开 = 进入该场景:应用档案(MCP/技能双向切换)+ 启用场景(提示词绑定生效 + 记忆范围收窄);
|
|
3329
|
+
* 关 = 回到全局默认:恢复档案快照 + 取消场景启用。
|
|
3330
|
+
*
|
|
3331
|
+
* ⚠️ 核心约束:`mode.scene` 与 `active` **必须恒等**。此前开关只写 `active`、
|
|
3332
|
+
* 「切入此模式」按钮只写 `mode.scene`,两者互不知情 —— 于是「拨开关不应用档案」与
|
|
3333
|
+
* 「顶部横幅永远不显示」同时发生。现在两条路径都走 enterMode / exitMode。
|
|
3334
|
+
*/
|
|
3016
3335
|
function toggleScene(scene) {
|
|
3017
3336
|
if (busy || scene.shared || scene.global) return
|
|
3018
|
-
|
|
3019
|
-
|
|
3337
|
+
if (scene.active === true) exitMode()
|
|
3338
|
+
else enterMode(scene.name)
|
|
3339
|
+
}
|
|
3340
|
+
/** 场景锁定(v0.8):锁上后五个管理域整体只读(场景页 + 各功能页);启停不受影响。 */
|
|
3341
|
+
function toggleSceneLock(scene) {
|
|
3342
|
+
if (busy || scene.shared || scene.global) return
|
|
3343
|
+
setBusy(true)
|
|
3344
|
+
apiCall('rules-scene-lock', { scene: scene.name, locked: scene.locked !== true }).then(function (res) {
|
|
3345
|
+
setBusy(false)
|
|
3346
|
+
if (res && res.ok) refresh(true)
|
|
3347
|
+
else setResult({ ok: false, text: translateError(t, res) })
|
|
3348
|
+
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
3020
3349
|
}
|
|
3021
3350
|
/**
|
|
3022
3351
|
* 提示词预设候选(打开表单时拉一次;失败就只留「不绑定」)。
|
|
@@ -3056,8 +3385,13 @@ function callApi(path, options) {
|
|
|
3056
3385
|
if (!isValidSceneName(name)) { setSceneForm(Object.assign({}, sceneForm, { error: t('error.rules.invalidGroup') })); return }
|
|
3057
3386
|
var description = String(sceneForm.description || '').trim()
|
|
3058
3387
|
var prompt = String(sceneForm.prompt || '').trim()
|
|
3388
|
+
// 编辑时表单里的 name 可能改过:改名走 nextName,服务端按 modal.name(原名)定位、改完再落新名。
|
|
3389
|
+
var originalName = isEdit ? String((modal && modal.name) || name) : name
|
|
3059
3390
|
setBusy(true)
|
|
3060
|
-
|
|
3391
|
+
var payload = isEdit
|
|
3392
|
+
? { name: originalName, nextName: name, description: description, prompt: prompt }
|
|
3393
|
+
: { name: name, description: description, prompt: prompt }
|
|
3394
|
+
apiCall(isEdit ? 'rules-update-scene' : 'rules-create-scene', payload).then(function (res) {
|
|
3061
3395
|
setBusy(false)
|
|
3062
3396
|
if (res && res.ok) {
|
|
3063
3397
|
setModal(null)
|
|
@@ -3071,8 +3405,17 @@ function callApi(path, options) {
|
|
|
3071
3405
|
setBusy(true)
|
|
3072
3406
|
apiCall('rules-remove-scene', { name: name }).then(function (res) {
|
|
3073
3407
|
setBusy(false); setModal(null)
|
|
3074
|
-
if (res && res.ok) {
|
|
3075
|
-
|
|
3408
|
+
if (res && res.ok) {
|
|
3409
|
+
var moved = Number((res && res.movedFiles) || 0)
|
|
3410
|
+
// 如实说清「一起删了多少条记忆」:删除场景现在会带走它下面的全部记忆。
|
|
3411
|
+
setResult({
|
|
3412
|
+
ok: true,
|
|
3413
|
+
text: moved > 0
|
|
3414
|
+
? t('memory.result.sceneRemoved.withMemories', { name: name, count: moved })
|
|
3415
|
+
: t('memory.result.sceneRemoved', { name: name }),
|
|
3416
|
+
})
|
|
3417
|
+
refresh(true)
|
|
3418
|
+
} else setResult({ ok: false, text: translateError(t, res) })
|
|
3076
3419
|
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
3077
3420
|
}
|
|
3078
3421
|
// ── 档案(mcp 两级 / skills / subagents 三段自由搭配)──
|
|
@@ -3087,6 +3430,7 @@ function callApi(path, options) {
|
|
|
3087
3430
|
setModal({ type: 'scene-archive', name: name, drill: null, memQuery: '', skillQuery: '', subQuery: '', mcpQuery: '',
|
|
3088
3431
|
sections: {
|
|
3089
3432
|
mcp: archive.mcp ? Object.assign({}, archive.mcp) : null,
|
|
3433
|
+
mcpNotes: archive.mcpNotes && Object.keys(archive.mcpNotes).length ? Object.assign({}, archive.mcpNotes) : null,
|
|
3090
3434
|
skills: Array.isArray(archive.skills) ? archive.skills.slice() : null,
|
|
3091
3435
|
subagents: Array.isArray(archive.subagents) ? archive.subagents.slice() : null,
|
|
3092
3436
|
memories: Array.isArray(archive.memories) ? archive.memories.slice() : null,
|
|
@@ -3110,6 +3454,18 @@ function callApi(path, options) {
|
|
|
3110
3454
|
else mcp[server] = '*'
|
|
3111
3455
|
setSections(Object.assign({}, sections, { mcp: mcp }))
|
|
3112
3456
|
}
|
|
3457
|
+
/**
|
|
3458
|
+
* 场景级 MCP 备注(v0.8.1):该场景进入时覆盖全局备注、退出恢复。
|
|
3459
|
+
* 空值 = 删除该服务器的场景备注;mcpNotes 全空 → null(等同未定义,保存时不上报)。
|
|
3460
|
+
*/
|
|
3461
|
+
function setMcpNote(server, value) {
|
|
3462
|
+
var sections = modalSections(); if (!sections) return
|
|
3463
|
+
var notes = Object.assign({}, sections.mcpNotes || {})
|
|
3464
|
+
var v = String(value == null ? '' : value).trim()
|
|
3465
|
+
if (v) notes[server] = v
|
|
3466
|
+
else delete notes[server]
|
|
3467
|
+
setSections(Object.assign({}, sections, { mcpNotes: Object.keys(notes).length ? notes : null }))
|
|
3468
|
+
}
|
|
3113
3469
|
/**
|
|
3114
3470
|
* 新加段一律**默认不勾选**(用户明确要求:MCP 工具集 / 技能集 / 子智能体绑定都不预勾)。
|
|
3115
3471
|
*
|
|
@@ -3133,11 +3489,11 @@ function callApi(path, options) {
|
|
|
3133
3489
|
setSections(Object.assign({}, sections, { mcp: mcp }))
|
|
3134
3490
|
}
|
|
3135
3491
|
/** 档案各段共用的段头动作(包装共享的 segActions,把 busy / 文案在这里补上)。 */
|
|
3136
|
-
function archiveSegActions(defined, addLabel, onAdd, onRemove, onAll, onClear) {
|
|
3492
|
+
function archiveSegActions(defined, addLabel, onAdd, onRemove, onAll, onClear, allChecked) {
|
|
3137
3493
|
return segActions({
|
|
3138
3494
|
defined: defined, busy: busy, addLabel: addLabel,
|
|
3139
3495
|
selectAllLabel: t('scenes.seg.selectAll'), clearLabel: t('scenes.seg.clear'), removeLabel: t('memory.archive.removeSection'),
|
|
3140
|
-
onAdd: onAdd, onRemove: onRemove, onAll: onAll, onClear: onClear,
|
|
3496
|
+
onAdd: onAdd, onRemove: onRemove, onAll: onAll, onClear: onClear, allChecked: allChecked,
|
|
3141
3497
|
})
|
|
3142
3498
|
}
|
|
3143
3499
|
/**
|
|
@@ -3148,108 +3504,87 @@ function callApi(path, options) {
|
|
|
3148
3504
|
return segFoot(defined, count, emptyLabel || t('memory.archive.emptySection'))
|
|
3149
3505
|
}
|
|
3150
3506
|
|
|
3151
|
-
// ── 段 4
|
|
3152
|
-
//
|
|
3153
|
-
//
|
|
3154
|
-
//
|
|
3155
|
-
//
|
|
3156
|
-
//
|
|
3157
|
-
//
|
|
3158
|
-
//
|
|
3159
|
-
//
|
|
3507
|
+
// ── 段 4:记忆(P5:单一真相源 = `rules[*].enabled`)────────────────────
|
|
3508
|
+
// 这个段**没有自己的「段定义」概念**:勾选状态**就是**记忆自身的 `enabled`,
|
|
3509
|
+
// 与记忆页同一个写入口(`rules-toggle`)。于是「记忆页开了 → 档案页显示开启」
|
|
3510
|
+
// 天然成立,不存在两套状态、也不需要双向同步代码。
|
|
3511
|
+
//
|
|
3512
|
+
// 勾选**立即提交**(不等「保存到场景」):它是全局状态,不属于场景档案。
|
|
3513
|
+
//
|
|
3514
|
+
// **只列被编辑场景自己的记忆**:保留场景 `global` 恒定注入(任何对话都生效),
|
|
3515
|
+
// 不需要在这里配置,列出来只会让人以为「不勾就不注入」。
|
|
3160
3516
|
function sceneMemories(sceneName) {
|
|
3161
3517
|
return sceneMemoriesOf(modal.inventory.memories, sceneName)
|
|
3162
3518
|
}
|
|
3163
|
-
/**
|
|
3519
|
+
/** 「全选 / 全不选」作用的 id 集合 = 本场景的全部记忆。 */
|
|
3164
3520
|
function allMemoryIds() {
|
|
3165
3521
|
return sceneMemories(modal.name).map(function (m) { return String(m.id) })
|
|
3166
3522
|
}
|
|
3167
|
-
/**
|
|
3168
|
-
function
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
function memCheckedCount() {
|
|
3172
|
-
var sections = modalSections()
|
|
3173
|
-
return sections.memories ? sections.memories.length : 0
|
|
3523
|
+
/** 记忆是否开启 —— 直接读 `rules[*].enabled`(单一真相源)。 */
|
|
3524
|
+
function memoryEnabled(id) {
|
|
3525
|
+
var rule = (data.rules || []).find(function (r) { return r.id === id })
|
|
3526
|
+
return !!(rule && rule.enabled !== false)
|
|
3174
3527
|
}
|
|
3175
3528
|
function toggleMemory(id) {
|
|
3176
|
-
var
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3529
|
+
var next = !memoryEnabled(id)
|
|
3530
|
+
setBusy(true)
|
|
3531
|
+
apiCall('rules-toggle', { id: id, enabled: next }).then(function (res) {
|
|
3532
|
+
setBusy(false)
|
|
3533
|
+
if (res && res.ok) refresh(true)
|
|
3534
|
+
else setResult({ ok: false, text: translateError(t, res) })
|
|
3535
|
+
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
3536
|
+
}
|
|
3537
|
+
/** 全选 / 全不选:批量写 `enabled`(顺序提交,失败即停并如实报错)。 */
|
|
3538
|
+
function setAllMemories(on) {
|
|
3539
|
+
var ids = allMemoryIds()
|
|
3540
|
+
if (!ids.length) return
|
|
3541
|
+
setBusy(true)
|
|
3542
|
+
Promise.all(ids.map(function (id) { return apiCall('rules-toggle', { id: id, enabled: on }) }))
|
|
3543
|
+
.then(function () { setBusy(false); refresh(true) })
|
|
3544
|
+
.catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
3181
3545
|
}
|
|
3182
3546
|
function memoriesSeg() {
|
|
3183
|
-
var sections = modalSections()
|
|
3184
|
-
var defined = !!sections.memories
|
|
3185
3547
|
var own = sceneMemories(modal.name)
|
|
3186
3548
|
var q = String(modal.memQuery || '').trim().toLowerCase()
|
|
3187
3549
|
var items = q ? own.filter(function (m) {
|
|
3188
3550
|
return String(m.name).toLowerCase().indexOf(q) >= 0
|
|
3189
3551
|
|| String(m.description || '').toLowerCase().indexOf(q) >= 0
|
|
3190
3552
|
}) : own
|
|
3553
|
+
var enabledCount = own.filter(function (m) { return memoryEnabled(String(m.id)) }).length
|
|
3191
3554
|
return seg({
|
|
3192
3555
|
title: t('memory.archive.memories'),
|
|
3193
|
-
count:
|
|
3194
|
-
actions:
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
function () {
|
|
3198
|
-
function () {
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
foot: archiveSegFoot(defined, memCheckedCount()),
|
|
3556
|
+
count: t('scenes.seg.checked', { checked: enabledCount, total: own.length }),
|
|
3557
|
+
actions: segActions({
|
|
3558
|
+
defined: true, busy: busy,
|
|
3559
|
+
selectAllLabel: t('scenes.seg.selectAll'), clearLabel: t('scenes.seg.clear'),
|
|
3560
|
+
onAll: function () { setAllMemories(true) },
|
|
3561
|
+
onClear: function () { setAllMemories(false) },
|
|
3562
|
+
allChecked: own.length > 0 && enabledCount === own.length,
|
|
3563
|
+
}),
|
|
3564
|
+
body: React.createElement('div', { className: 'dsm-seg-body' },
|
|
3565
|
+
segFilter(modal.memQuery, function (v) { setModal(Object.assign({}, modal, { memQuery: v })) }, t('scenes.mem.search')),
|
|
3566
|
+
items.length
|
|
3567
|
+
? React.createElement('div', null, items.map(function (m) {
|
|
3568
|
+
return pickRow({
|
|
3569
|
+
disabled: busy,
|
|
3570
|
+
key: m.id,
|
|
3571
|
+
checked: memoryEnabled(String(m.id)),
|
|
3572
|
+
name: m.name,
|
|
3573
|
+
// 记忆描述是自由正文的首行,可能很长:截断到 MEM_DESC_MAX,全文放 title。
|
|
3574
|
+
// CSS 侧还有单行省略兜底(超长不再横向溢出段边框)。
|
|
3575
|
+
desc: m.description ? clipText(m.description, MEM_DESC_MAX) : null,
|
|
3576
|
+
descTitle: m.description || null,
|
|
3577
|
+
onChange: function () { toggleMemory(String(m.id)) },
|
|
3578
|
+
})
|
|
3579
|
+
}))
|
|
3580
|
+
: React.createElement('div', { className: 'dsm-pick-empty' }, q ? t('scenes.mem.noMatch') : t('scenes.mem.emptyScene'))),
|
|
3219
3581
|
})
|
|
3220
3582
|
}
|
|
3221
|
-
/** 段 1:MCP 工具集(服务器级勾选 →
|
|
3583
|
+
/** 段 1:MCP 工具集(服务器级勾选 → 行内「编辑」打开固定尺寸弹窗:备注 + 工具勾选)。 */
|
|
3222
3584
|
function mcpSeg() {
|
|
3223
3585
|
var sections = modalSections()
|
|
3224
3586
|
var defined = !!sections.mcp
|
|
3225
3587
|
var servers = modal.inventory.mcpServers || []
|
|
3226
|
-
if (modal.drill) {
|
|
3227
|
-
var spec = (sections.mcp || {})[modal.drill]
|
|
3228
|
-
var known = drillTools
|
|
3229
|
-
var checkedCount = spec === '*' ? (known || []).length : (Array.isArray(spec) ? spec.length : 0)
|
|
3230
|
-
return seg({
|
|
3231
|
-
title: t('scenes.mcp.toolsOf') + ' · ' + modal.drill,
|
|
3232
|
-
count: t('scenes.seg.checked', { checked: checkedCount, total: (known || []).length }),
|
|
3233
|
-
actions: [
|
|
3234
|
-
React.createElement('button', { key: 'back', type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { setModal(Object.assign({}, modal, { drill: null })) } }, t('scenes.mcp.back')),
|
|
3235
|
-
React.createElement('button', { key: 'all', type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || !known, onClick: function () { mcpDrillSpec('*') } }, t('scenes.seg.selectAll')),
|
|
3236
|
-
React.createElement('button', { key: 'none', type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { mcpDrillSpec([]) } }, t('scenes.seg.clear')),
|
|
3237
|
-
],
|
|
3238
|
-
body: known === null
|
|
3239
|
-
? React.createElement('div', { className: 'dsm-seg-body' }, React.createElement('div', { className: 'dsm-pick-empty' }, t('memory.loading')))
|
|
3240
|
-
: segList(known.map(function (item) {
|
|
3241
|
-
var on = spec === '*' ? true : (Array.isArray(spec) && spec.indexOf(item.short) >= 0)
|
|
3242
|
-
return pickRow({
|
|
3243
|
-
disabled: busy,
|
|
3244
|
-
key: item.key,
|
|
3245
|
-
checked: on,
|
|
3246
|
-
name: item.short,
|
|
3247
|
-
onChange: function () { toggleDrillTool(modal.drill, item.short) },
|
|
3248
|
-
})
|
|
3249
|
-
}), t('scenes.mcp.noTools')),
|
|
3250
|
-
foot: React.createElement('div', { className: 'dsm-seg-foot' }, t('scenes.mcp.drillHint')),
|
|
3251
|
-
})
|
|
3252
|
-
}
|
|
3253
3588
|
return seg({
|
|
3254
3589
|
title: t('memory.archive.tools'),
|
|
3255
3590
|
count: defined ? t('scenes.seg.checked', { checked: Object.keys(sections.mcp).length, total: servers.length }) : t('scenes.archive.sectionOff'),
|
|
@@ -3269,17 +3604,23 @@ function callApi(path, options) {
|
|
|
3269
3604
|
var selected = sections.mcp[server.name] !== undefined
|
|
3270
3605
|
var spec2 = sections.mcp[server.name]
|
|
3271
3606
|
var specText = !selected ? '' : spec2 === '*' ? t('scenes.mcp.allTools') : t('scenes.mcp.pickedCount', { count: spec2.length })
|
|
3607
|
+
var hasNote = Boolean((sections.mcpNotes || {})[server.name])
|
|
3608
|
+
// 工具数未知(从未运行过且无记录):显示引导而不是误导性的 0。
|
|
3609
|
+
var desc = server.toolCount === null || server.toolCount === undefined
|
|
3610
|
+
? (server.live || server.serverDisabled ? null : t('scenes.mcp.unknownCount'))
|
|
3611
|
+
: t('scenes.mcp.toolCount', { count: server.toolCount })
|
|
3272
3612
|
return pickRow({
|
|
3273
|
-
|
|
3613
|
+
disabled: busy,
|
|
3274
3614
|
key: server.name,
|
|
3275
3615
|
checked: selected,
|
|
3276
3616
|
name: server.name,
|
|
3277
|
-
desc:
|
|
3617
|
+
desc: desc,
|
|
3278
3618
|
meta: [
|
|
3279
3619
|
server.live ? null : React.createElement('span', { key: 'nr', className: 'dsm-tag dsm-tag-off' }, t('scenes.mcp.notRunning')),
|
|
3280
3620
|
specText ? React.createElement('span', { key: 'spec' }, specText) : null,
|
|
3621
|
+
hasNote ? React.createElement('span', { key: 'note', className: 'dsm-tag' }, t('scenes.mcp.hasNote')) : null,
|
|
3281
3622
|
],
|
|
3282
|
-
actions: selected ? React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function (e) { e.preventDefault(); e.stopPropagation(); openDrill(server.name) } }, t('scenes.mcp.
|
|
3623
|
+
actions: selected ? React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function (e) { e.preventDefault(); e.stopPropagation(); openDrill(server.name) } }, t('scenes.mcp.edit')) : null,
|
|
3283
3624
|
onChange: function () { toggleMcpServer(server.name) },
|
|
3284
3625
|
})
|
|
3285
3626
|
}))
|
|
@@ -3288,6 +3629,53 @@ function callApi(path, options) {
|
|
|
3288
3629
|
foot: archiveSegFoot(defined, defined ? Object.keys(sections.mcp).length : 0),
|
|
3289
3630
|
})
|
|
3290
3631
|
}
|
|
3632
|
+
/**
|
|
3633
|
+
* 服务器编辑弹窗(v0.8.1 重做):固定尺寸的次级弹窗,第一栏是场景备注输入框,
|
|
3634
|
+
* 第二栏是工具勾选列表 —— 「选工具」并入这里,行内不再塞备注输入框。
|
|
3635
|
+
*/
|
|
3636
|
+
function mcpEditNode() {
|
|
3637
|
+
var sections = modalSections(); if (!sections) return null
|
|
3638
|
+
var server = modal.drill
|
|
3639
|
+
var spec = (sections.mcp || {})[server]
|
|
3640
|
+
var noteVal = (sections.mcpNotes || {})[server] || ''
|
|
3641
|
+
var known = drillTools
|
|
3642
|
+
var checkedCount = spec === '*' ? (known || []).length : (Array.isArray(spec) ? spec.length : 0)
|
|
3643
|
+
return React.createElement('div', { className: 'dsm-mask', key: 'mcp-edit' },
|
|
3644
|
+
React.createElement('div', { className: 'dsm-modal' },
|
|
3645
|
+
React.createElement('div', { className: 'dsm-modal-head' },
|
|
3646
|
+
React.createElement('h3', { className: 'dsm-modal-title' }, t('scenes.mcp.edit') + ' · ' + server + '(' + t('scenes.seg.checked', { checked: checkedCount, total: (known || []).length }) + ')'),
|
|
3647
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { setModal(Object.assign({}, modal, { drill: null })) } }, t('scenes.mcp.done'))),
|
|
3648
|
+
React.createElement('div', { className: 'dsm-modal-body' },
|
|
3649
|
+
React.createElement('div', { className: 'dsm-field' },
|
|
3650
|
+
React.createElement('label', { className: 'dsm-label' }, t('scenes.mcp.noteLabel')),
|
|
3651
|
+
React.createElement('input', { className: 'dsm-control', type: 'text', value: noteVal, maxLength: 200, placeholder: t('scenes.mcp.notePlaceholder'), onChange: function (e) { setMcpNote(server, e.target.value) } }),
|
|
3652
|
+
React.createElement('p', { className: 'dsm-help' }, t('scenes.mcp.noteHint'))),
|
|
3653
|
+
React.createElement('div', { className: 'dsm-field' },
|
|
3654
|
+
React.createElement('div', { className: 'dsm-mcp-edit-tools-head' },
|
|
3655
|
+
React.createElement('label', { className: 'dsm-label' }, t('scenes.mcp.toolsOf')),
|
|
3656
|
+
React.createElement('div', { className: 'dsm-mcp-edit-tools-actions' },
|
|
3657
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || !known, onClick: function () { mcpDrillSpec('*') } }, t('scenes.seg.selectAll')),
|
|
3658
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { mcpDrillSpec([]) } }, t('scenes.seg.clear')))),
|
|
3659
|
+
known === null
|
|
3660
|
+
? React.createElement('div', { className: 'dsm-seg-body' }, React.createElement('div', { className: 'dsm-pick-empty' }, t('memory.loading')))
|
|
3661
|
+
: (known || []).length
|
|
3662
|
+
? segList((known || []).map(function (item) {
|
|
3663
|
+
var on = spec === '*' ? true : (Array.isArray(spec) && spec.indexOf(item.short) >= 0)
|
|
3664
|
+
return pickRow({
|
|
3665
|
+
disabled: busy,
|
|
3666
|
+
key: item.key,
|
|
3667
|
+
checked: on,
|
|
3668
|
+
name: item.short,
|
|
3669
|
+
onChange: function () { toggleDrillTool(server, item.short) },
|
|
3670
|
+
})
|
|
3671
|
+
}), t('scenes.mcp.noTools'))
|
|
3672
|
+
: React.createElement('div', { className: 'dsm-seg-body' },
|
|
3673
|
+
React.createElement('div', { className: 'dsm-pick-empty' }, t('scenes.mcp.noTools')),
|
|
3674
|
+
modal.drillError ? React.createElement('div', { className: 'dsm-seg-empty-error' }, modal.drillError) : null,
|
|
3675
|
+
React.createElement('div', { className: 'dsm-seg-empty-actions' },
|
|
3676
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: drillTools === null, onClick: probeMcpTools }, t('scenes.mcp.probeTools')))),
|
|
3677
|
+
React.createElement('div', { className: 'dsm-seg-foot' }, t('scenes.mcp.drillHint'))))))
|
|
3678
|
+
}
|
|
3291
3679
|
/** 段 2:技能集。 */
|
|
3292
3680
|
/**
|
|
3293
3681
|
* 技能行的人类可读展示:技能名 + 来源名。
|
|
@@ -3443,14 +3831,13 @@ function callApi(path, options) {
|
|
|
3443
3831
|
*/
|
|
3444
3832
|
function archiveNode() {
|
|
3445
3833
|
if (!modal || modal.type !== 'scene-archive') return null
|
|
3446
|
-
|
|
3834
|
+
var main = React.createElement(Modal, { key: 'sarch', wide: true, className: 'dsm-modal-archive', title: t('memory.archive.title') + ' · ' + (modal.name === '' ? t('memory.scene.global') : modal.name), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
3447
3835
|
React.createElement('div', { className: 'dsm-form' },
|
|
3448
3836
|
React.createElement('div', { className: 'dsm-archive-meta' },
|
|
3449
3837
|
React.createElement('span', null, t('scenes.archive.summary', {
|
|
3450
3838
|
mcp: modal.sections.mcp ? Object.keys(modal.sections.mcp).length : 0,
|
|
3451
3839
|
skills: modal.sections.skills ? modal.sections.skills.length : 0,
|
|
3452
3840
|
subagents: modal.sections.subagents ? modal.sections.subagents.length : 0,
|
|
3453
|
-
memories: modal.sections.memories ? modal.sections.memories.length : 0,
|
|
3454
3841
|
})),
|
|
3455
3842
|
React.createElement('span', { className: 'dsm-help' }, t('scenes.archive.note'))),
|
|
3456
3843
|
mcpSeg(),
|
|
@@ -3459,15 +3846,27 @@ function callApi(path, options) {
|
|
|
3459
3846
|
memoriesSeg(),
|
|
3460
3847
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
3461
3848
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-primary', disabled: busy, onClick: submitArchive }, t('memory.archive.save')))))
|
|
3849
|
+
// MCP 服务器编辑弹窗渲染在外层:不能放进列表 Modal 的滚动容器里(会被裁掉)。
|
|
3850
|
+
var edit = modal.drill ? mcpEditNode() : null
|
|
3851
|
+
return edit ? React.createElement(React.Fragment, { key: 'sarch-wrap' }, main, edit) : main
|
|
3462
3852
|
}
|
|
3463
3853
|
function openDrill(server) {
|
|
3464
|
-
setModal(Object.assign({}, modal, { drill: server }))
|
|
3854
|
+
setModal(Object.assign({}, modal, { drill: server, drillError: null }))
|
|
3465
3855
|
setDrillTools(null)
|
|
3466
3856
|
apiCall('mcpm-tools', { serverName: server }).then(function (res) {
|
|
3467
3857
|
if (res && res.ok) setDrillTools((res.tools || []).map(function (x) { return { key: server + '/' + x.name, short: x.name, enabled: x.enabled !== false } }))
|
|
3468
3858
|
else setDrillTools([])
|
|
3469
3859
|
}).catch(function () { setDrillTools([]) })
|
|
3470
3860
|
}
|
|
3861
|
+
/** 从未运行过的服务器:临时启动枚举工具(后端会轮询并在完成后恢复原启停状态)。 */
|
|
3862
|
+
function probeMcpTools() {
|
|
3863
|
+
var server = modal.drill
|
|
3864
|
+
setDrillTools(null)
|
|
3865
|
+
apiCall('mcpm-tools-refresh', { serverName: server }).then(function (res) {
|
|
3866
|
+
if (res && res.ok) setDrillTools((res.tools || []).map(function (x) { return { key: server + '/' + x.name, short: x.name, enabled: x.enabled !== false } }))
|
|
3867
|
+
else { setDrillTools([]); setModal(Object.assign({}, modal, { drillError: res && res.error ? String(res.error) : '' })) }
|
|
3868
|
+
}).catch(function (e) { setDrillTools([]); setModal(Object.assign({}, modal, { drillError: String((e && e.message) || e) })) })
|
|
3869
|
+
}
|
|
3471
3870
|
function toggleDrillTool(server, short) {
|
|
3472
3871
|
var sections = modalSections(); if (!sections) return
|
|
3473
3872
|
var mcp = Object.assign({}, sections.mcp || {})
|
|
@@ -3480,7 +3879,9 @@ function callApi(path, options) {
|
|
|
3480
3879
|
function submitArchive() {
|
|
3481
3880
|
if (!modalSections()) return
|
|
3482
3881
|
setBusy(true)
|
|
3483
|
-
|
|
3882
|
+
// P5:档案里**不再写 memories 段** —— 记忆的开关是 `rules[*].enabled` 单一真相源,
|
|
3883
|
+
// 在段里勾选时已经即时提交了,这里没有「待保存」的记忆状态。
|
|
3884
|
+
apiCall('scene-archive-save', { scene: modal.name, archive: (function () { var payload = {}; if (modal.sections.mcp) payload.mcp = modal.sections.mcp; if (modal.sections.mcpNotes) payload.mcpNotes = modal.sections.mcpNotes; if (modal.sections.skills) payload.skills = modal.sections.skills; if (modal.sections.subagents) payload.subagents = modal.sections.subagents; return payload })() }).then(function (res) {
|
|
3484
3885
|
setBusy(false)
|
|
3485
3886
|
if (res && res.ok) {
|
|
3486
3887
|
setModal(null)
|
|
@@ -3498,18 +3899,31 @@ function callApi(path, options) {
|
|
|
3498
3899
|
apiCall('rules-set-active', { scenes: [name] }).then(function (act) {
|
|
3499
3900
|
setBusy(false)
|
|
3500
3901
|
var staleNote = res.stale && res.stale.length ? ' · ' + t('memory.archive.stale', { items: res.stale.join('、') }) : ''
|
|
3501
|
-
|
|
3502
|
-
|
|
3902
|
+
// 如实报「上层切了几台服务器 / 几个来源」——否则用户不知道勾选到底生效没有。
|
|
3903
|
+
var switchNote = res.switched && (res.switched.mcpServers || res.switched.skillSources)
|
|
3904
|
+
? ' · ' + t('memory.result.modeSwitched', { mcp: res.switched.mcpServers, skills: res.switched.skillSources })
|
|
3905
|
+
: ''
|
|
3906
|
+
if (act && act.ok) setResult({ ok: true, text: t('memory.result.modeSet', { name: name }) + switchNote + staleNote })
|
|
3907
|
+
else setResult({ ok: false, text: t('memory.result.modeSet', { name: name }) + switchNote + staleNote + ' · ' + translateError(t, act) })
|
|
3503
3908
|
refresh(true)
|
|
3504
3909
|
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }); refresh(true) })
|
|
3505
3910
|
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
3506
3911
|
}
|
|
3912
|
+
/**
|
|
3913
|
+
* 退出模式(P6):与开关**同一条路径** —— 恢复档案 + 取消场景启用,两者一起写。
|
|
3914
|
+
* 顶部横幅的「退出模式」按钮和卡片开关点它,效果完全一致(用户裁定:「是真的退出了」)。
|
|
3915
|
+
*/
|
|
3507
3916
|
function exitMode() {
|
|
3508
3917
|
setBusy(true)
|
|
3509
3918
|
apiCall('scene-mode-set', { scene: null }).then(function (res) {
|
|
3510
|
-
setBusy(false)
|
|
3511
|
-
|
|
3512
|
-
|
|
3919
|
+
if (!res || !res.ok) { setBusy(false); setResult({ ok: false, text: translateError(t, res) }); return }
|
|
3920
|
+
// 档案已恢复 → 同步取消场景启用,保证 mode.scene 与 active 恒等。
|
|
3921
|
+
apiCall('rules-set-active', { scenes: [] }).then(function (act) {
|
|
3922
|
+
setBusy(false)
|
|
3923
|
+
if (act && act.ok) setResult({ ok: true, text: t('memory.result.modeExited') })
|
|
3924
|
+
else setResult({ ok: false, text: t('memory.result.modeExited') + ' · ' + translateError(t, act) })
|
|
3925
|
+
refresh(true)
|
|
3926
|
+
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }); refresh(true) })
|
|
3513
3927
|
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
3514
3928
|
}
|
|
3515
3929
|
var modeScene = data.mode && data.mode.scene
|
|
@@ -3546,22 +3960,24 @@ function callApi(path, options) {
|
|
|
3546
3960
|
if (archive.mcp) parts.push(t('scenes.profile.mcp', { count: Object.keys(archive.mcp).length }))
|
|
3547
3961
|
if (Array.isArray(archive.skills)) parts.push(t('scenes.profile.skills', { count: archive.skills.length }))
|
|
3548
3962
|
if (Array.isArray(archive.subagents)) parts.push(t('scenes.profile.subagents', { count: archive.subagents.length }))
|
|
3549
|
-
|
|
3963
|
+
// P5:记忆**不再是档案的一部分**(开关是 `rules[*].enabled` 单一真相源),
|
|
3964
|
+
// 所以这里不再统计 memories —— 否则会显示一个已经不存在的段的数量。
|
|
3550
3965
|
return parts.join(' · ')
|
|
3551
3966
|
}
|
|
3967
|
+
// 当前模式场景被锁 → 「退出模式」按钮与开关一起禁用(服务端守卫兜底)。
|
|
3968
|
+
var modeSceneLocked = !!(modeScene && (data.scenes || []).some(function (s) { return s.name === modeScene && s.locked === true }))
|
|
3552
3969
|
var modeSummary = modeScene ? archiveSummary(data.archives[modeScene]) : ''
|
|
3553
3970
|
return React.createElement('section', { className: 'dsm-section' },
|
|
3554
3971
|
React.createElement('div', { className: 'dsm-head' },
|
|
3555
3972
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
3556
3973
|
React.createElement('div', { className: 'dsm-title-row' },
|
|
3557
|
-
React.createElement('h2', { className: 'dsm-title' }, t('scenes.title')),
|
|
3558
|
-
VersionBadgeComponent ? React.createElement(VersionBadgeComponent, null) : null),
|
|
3974
|
+
React.createElement('h2', { className: 'dsm-title' }, t('scenes.title'))),
|
|
3559
3975
|
React.createElement('p', { className: 'dsm-desc' }, t('scenes.desc'))),
|
|
3560
3976
|
React.createElement('div', { className: 'dsm-actions' },
|
|
3561
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || data.loading, onClick: function () { refresh() } }, t('
|
|
3977
|
+
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')),
|
|
3562
3978
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: openCreateScene }, t('memory.btn.newScene')),
|
|
3563
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-
|
|
3564
|
-
React.createElement('div', { key: 'stats', className: 'dsm-summary
|
|
3979
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: function () { loadTrash(false) } }, t('trash.btn.open')))),
|
|
3980
|
+
React.createElement('div', { key: 'stats', className: 'dsm-summary' },
|
|
3565
3981
|
[[sceneStats.total, t('scenes.stat.total')], [sceneStats.active, t('scenes.stat.active')], [sceneStats.archives, t('scenes.stat.archives')]].map(function (item) {
|
|
3566
3982
|
return React.createElement('div', { key: item[1], className: 'dsm-stat' },
|
|
3567
3983
|
React.createElement('strong', null, item[0]), item[1])
|
|
@@ -3577,13 +3993,13 @@ function callApi(path, options) {
|
|
|
3577
3993
|
t('scenes.mode.active', { name: sceneLabel(data.scenes, modeScene) || modeScene })),
|
|
3578
3994
|
React.createElement('div', { className: 'dsm-mode-sub' },
|
|
3579
3995
|
modeSummary ? t('scenes.mode.profile', { parts: modeSummary }) : t('scenes.mode.noProfile'))),
|
|
3580
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: exitMode }, t('memory.mode.exit'))) : null,
|
|
3996
|
+
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,
|
|
3581
3997
|
React.createElement(Notice, { key: 'notice', kind: result && result.ok ? 'ok' : 'err', text: result && result.text }),
|
|
3582
3998
|
// 历史「全部启用」(active=null)遗留:单选模型下多名场景同时在场是歧义状态。
|
|
3583
3999
|
// 不静默改写(那会改变注入范围),而是给一个显式收敛按钮,并说清点下去会发生什么。
|
|
3584
4000
|
legacyAllScenes.length > 1 ? React.createElement('div', { key: 'legacyall', className: 'dsm-feedback dsm-warning' },
|
|
3585
4001
|
React.createElement('span', null, t('scenes.legacyAll', { count: legacyAllScenes.length })),
|
|
3586
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () {
|
|
4002
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { enterMode(legacyAllScenes[0].name) } }, t('scenes.legacyAll.fix'))) : null,
|
|
3587
4003
|
data.error ? React.createElement('div', { key: 'gerr', className: 'dsm-feedback dsm-error' }, String(data.error)) : null,
|
|
3588
4004
|
data.loading ? React.createElement('div', { className: 'dsm-empty' }, t('memory.loading'))
|
|
3589
4005
|
: presetScenes.length ? React.createElement('div', { key: 'scenes', className: 'dsm-scenes' }, presetScenes.map(function (scene) {
|
|
@@ -3593,8 +4009,8 @@ function callApi(path, options) {
|
|
|
3593
4009
|
// 全局已被上面的 presetScenes 过滤掉,这里只会遇到可切换的预设;
|
|
3594
4010
|
// 历史保留场景 `_shared`(「常开」)仍然恒定注入,所以不给开关也不给删除。
|
|
3595
4011
|
var locked = scene.shared === true
|
|
4012
|
+
var sceneLocked = scene.locked === true
|
|
3596
4013
|
// 档案里有任何一段才值得「切入此模式」——空档案切进去等于什么都没变。
|
|
3597
|
-
var hasModeSections = !!(archive && (archive.mcp || Array.isArray(archive.skills) || Array.isArray(archive.subagents) || Array.isArray(archive.memories)))
|
|
3598
4014
|
var desc = sceneTileDesc(scene)
|
|
3599
4015
|
// 单选:别的场景已启用时,这个开关置灰(点也没用,服务端只接受一个)。
|
|
3600
4016
|
var blockedByOther = scene.active !== true && scene.selectable === false
|
|
@@ -3609,6 +4025,7 @@ function callApi(path, options) {
|
|
|
3609
4025
|
name === label ? null : React.createElement('span', { className: 'dsm-scene-tile-key' }, name),
|
|
3610
4026
|
locked ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('memory.scene.shared')) : null,
|
|
3611
4027
|
modeScene === name ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('memory.mode.current')) : null,
|
|
4028
|
+
sceneLocked ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('scenes.lock.tag')) : null,
|
|
3612
4029
|
!locked && scene.active === false ? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, t('memory.scene.off')) : null,
|
|
3613
4030
|
boundPrompt ? React.createElement('span', {
|
|
3614
4031
|
className: 'dsm-tag' + (promptMissing ? ' dsm-tag-off' : promptLive ? ' dsm-tag-on' : ''),
|
|
@@ -3618,25 +4035,25 @@ function callApi(path, options) {
|
|
|
3618
4035
|
: promptLive ? t('scenes.prompt.live', { id: boundPrompt }) : t('scenes.prompt.bound', { id: boundPrompt })) : null,
|
|
3619
4036
|
locked ? null : React.createElement('span', {
|
|
3620
4037
|
className: 'dsm-scene-tile-switch',
|
|
3621
|
-
title: blockedByOther ? t('scenes.enable.blocked', { name: sceneLabel(data.scenes, data.activeScene) || data.activeScene || '' }) : '',
|
|
3622
|
-
}, React.createElement(Switch, { on: scene.active === true, disabled: busy || blockedByOther, label: t('memory.scene.enable') + ' ' + name, onClick: function () { toggleScene(scene) } }))
|
|
4038
|
+
title: blockedByOther ? t('scenes.enable.blocked', { name: sceneLabel(data.scenes, data.activeScene) || data.activeScene || '' }) : (sceneLocked && scene.active === true ? t('scenes.lock.blockedExit') : ''),
|
|
4039
|
+
}, React.createElement(Switch, { on: scene.active === true, disabled: busy || blockedByOther || (sceneLocked && scene.active === true), label: t('memory.scene.enable') + ' ' + name, onClick: function () { toggleScene(scene) } })),
|
|
4040
|
+
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'))),
|
|
3623
4041
|
// 描述行**只有描述**(数量都收进上面的模式条了);全文放 title,卡片本身永远一行。
|
|
3624
4042
|
React.createElement('p', { className: 'dsm-scene-tile-desc', title: desc || '' }, desc || t('scenes.noDesc')),
|
|
4043
|
+
// P6:「切入此模式」按钮已删除 —— 右上角开关就是唯一入口(开 = 进入,关 = 回全局默认)。
|
|
3625
4044
|
React.createElement('div', { className: 'dsm-scene-tile-foot' },
|
|
3626
|
-
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,
|
|
3627
4045
|
React.createElement('div', { className: 'dsm-scene-tile-links' },
|
|
3628
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { openArchive(name) } }, t('memory.archive.edit')),
|
|
4046
|
+
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')),
|
|
3629
4047
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { openEditScene(scene) } }, t('memory.scene.edit')),
|
|
3630
|
-
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')))))
|
|
4048
|
+
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')))))
|
|
3631
4049
|
})) : React.createElement('div', { key: 'empty', className: 'dsm-empty' }, t('scenes.empty')),
|
|
3632
|
-
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.
|
|
4050
|
+
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) } },
|
|
3633
4051
|
React.createElement('div', { className: 'dsm-form' },
|
|
3634
4052
|
React.createElement('label', { className: 'dsm-field' },
|
|
3635
4053
|
React.createElement('span', { className: 'dsm-label' }, t('memory.scene.field.name')),
|
|
3636
4054
|
React.createElement('input', {
|
|
3637
4055
|
className: 'dsm-control' + (sceneForm.error ? ' dsm-rule-invalid' : ''),
|
|
3638
4056
|
value: sceneForm.name || '',
|
|
3639
|
-
disabled: modal.type === 'scene-edit',
|
|
3640
4057
|
placeholder: t('memory.scene.field.name.placeholder'),
|
|
3641
4058
|
onChange: function (e) { setSceneForm(Object.assign({}, sceneForm, { name: e.target.value, error: null })) },
|
|
3642
4059
|
}),
|
|
@@ -3663,7 +4080,7 @@ function callApi(path, options) {
|
|
|
3663
4080
|
React.createElement('p', { className: 'dsm-help' }, t('scenes.field.prompt.hint'))),
|
|
3664
4081
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
3665
4082
|
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,
|
|
3666
|
-
modal && modal.type === 'scene-delete' ? React.createElement(Modal, { key: 'sdel', title: t('memory.deleteScene.title'), closeLabel: t('btn.
|
|
4083
|
+
modal && modal.type === 'scene-delete' ? React.createElement(Modal, { key: 'sdel', title: t('memory.deleteScene.title'), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
3667
4084
|
React.createElement('p', { className: 'dsm-help' }, t('memory.deleteScene.desc', { name: modal.name })),
|
|
3668
4085
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
3669
4086
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { submitDeleteScene(modal.name) } }, t('memory.btn.deleteScene')))) : null,
|
|
@@ -3673,6 +4090,7 @@ function callApi(path, options) {
|
|
|
3673
4090
|
t: t,
|
|
3674
4091
|
title: t('trash.title'),
|
|
3675
4092
|
groupTitle: t('trash.group.scenes'),
|
|
4093
|
+
groupSub: t('trash.section.scenes.sub'),
|
|
3676
4094
|
entries: trash.entries,
|
|
3677
4095
|
loading: trash.loading,
|
|
3678
4096
|
error: trash.error,
|
|
@@ -3693,6 +4111,8 @@ function callApi(path, options) {
|
|
|
3693
4111
|
var result = rs[0], setResult = rs[1]
|
|
3694
4112
|
var ms = React.useState(null)
|
|
3695
4113
|
var modal = ms[0], setModal = ms[1]
|
|
4114
|
+
// 场景锁定(v0.8):任一场景锁定 = 五个域整体冻结;本页全部写控件禁用。
|
|
4115
|
+
var anyLocked = data.anyLocked === true
|
|
3696
4116
|
// 回收站:删除人设 = 移入回收站(宿主侧 subagent-delete),这里列出/恢复/永久删除。
|
|
3697
4117
|
var ts = React.useState(null)
|
|
3698
4118
|
var trash = ts[0], setTrash = ts[1]
|
|
@@ -3725,11 +4145,24 @@ function callApi(path, options) {
|
|
|
3725
4145
|
function refresh(silent) {
|
|
3726
4146
|
if (!silent) setData(function (prev) { return Object.assign({}, prev, { loading: true, error: null }) })
|
|
3727
4147
|
apiCall('subagent-list', {}).then(function (r) {
|
|
3728
|
-
if (r && r.ok) setData({ loading: false, error: null, subagents: r.subagents || [] })
|
|
4148
|
+
if (r && r.ok) setData({ loading: false, error: null, subagents: r.subagents || [], anyLocked: r.anyLocked === true })
|
|
3729
4149
|
else setData({ loading: false, error: translateError(t, r), subagents: [] })
|
|
3730
4150
|
}).catch(function (e) { setData({ loading: false, error: String((e && e.message) || e), subagents: [] }) })
|
|
3731
4151
|
}
|
|
3732
4152
|
React.useEffect(function () { refresh() }, [])
|
|
4153
|
+
/**
|
|
4154
|
+
* 子智能体开关(与记忆页同一套交互):停用 = 不注入目录段、subagent_list/run
|
|
4155
|
+
* 不可见;人设文件不动。开关联动全局状态,立即提交。
|
|
4156
|
+
*/
|
|
4157
|
+
function togglePersona(p) {
|
|
4158
|
+
var next = !(p.enabled !== false)
|
|
4159
|
+
setBusy(true)
|
|
4160
|
+
apiCall('subagent-toggle', { name: p.name, enabled: next }).then(function (r) {
|
|
4161
|
+
setBusy(false)
|
|
4162
|
+
if (r && r.ok) refresh(true)
|
|
4163
|
+
else if (r) setResult({ ok: false, text: translateError(t, r) })
|
|
4164
|
+
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) })
|
|
4165
|
+
}
|
|
3733
4166
|
/**
|
|
3734
4167
|
* 人设表单的候选数据(模型目录 / 全体预设工具并集)只在**首次展开高级选项**时拉取:
|
|
3735
4168
|
* 宿主枚举预设需要为尚未挂载的预设建立 standing mount,不该在打开弹窗时就付这个代价。
|
|
@@ -3779,7 +4212,7 @@ function callApi(path, options) {
|
|
|
3779
4212
|
setBusy(false)
|
|
3780
4213
|
if (res && res.ok) {
|
|
3781
4214
|
var p = res.persona || {}
|
|
3782
|
-
setModal({ type: 'editor', mode: 'edit', advanced: initialAdvanced(p), openMode: null, modeQuery: '', legacyTarget: '', form: {
|
|
4215
|
+
setModal({ type: 'editor', mode: 'edit', originalName: String(p.name || name), advanced: initialAdvanced(p), openMode: null, modeQuery: '', legacyTarget: '', form: {
|
|
3783
4216
|
name: p.name || name, description: p.description || '', provider: p.provider || '', model: p.model || '',
|
|
3784
4217
|
tools: (p.tools || []).slice(), toolsDeny: (p.toolsDeny || []).slice(),
|
|
3785
4218
|
toolsByPreset: cloneRules(p.toolsByPreset), body: p.body || '', error: null,
|
|
@@ -3989,8 +4422,12 @@ function callApi(path, options) {
|
|
|
3989
4422
|
if (!modal || modal.type !== 'editor') return
|
|
3990
4423
|
setBusy(true)
|
|
3991
4424
|
var op = modal.mode === 'create' ? 'subagent-create' : 'subagent-update'
|
|
4425
|
+
// 编辑时名字可改:nextName 只在真的改过时才传(服务端按原名定位文件)。
|
|
4426
|
+
var renamed = modal.mode === 'edit' && modal.originalName && modal.originalName !== modal.form.name
|
|
3992
4427
|
apiCall(op, {
|
|
3993
|
-
name: modal.
|
|
4428
|
+
name: renamed ? String(modal.originalName) : modal.form.name,
|
|
4429
|
+
...(renamed ? { nextName: String(modal.form.name || '').trim() } : {}),
|
|
4430
|
+
description: modal.form.description,
|
|
3994
4431
|
provider: modal.form.provider, model: modal.form.model,
|
|
3995
4432
|
// 旧格式的全局名单原样回写(旧键不点转换就不动),新模式名单另存一块。
|
|
3996
4433
|
tools: modal.form.tools || [], toolsDeny: modal.form.toolsDeny || [],
|
|
@@ -4029,14 +4466,21 @@ function callApi(path, options) {
|
|
|
4029
4466
|
React.createElement('div', { className: 'dsm-head' },
|
|
4030
4467
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
4031
4468
|
React.createElement('div', { className: 'dsm-title-row' },
|
|
4032
|
-
React.createElement('h2', { className: 'dsm-title' }, t('subagents.title')),
|
|
4033
|
-
VersionBadgeComponent ? React.createElement(VersionBadgeComponent, null) : null),
|
|
4469
|
+
React.createElement('h2', { className: 'dsm-title' }, t('subagents.title'))),
|
|
4034
4470
|
React.createElement('p', { className: 'dsm-desc' }, t('subagents.desc'))),
|
|
4035
4471
|
React.createElement('div', { className: 'dsm-actions' },
|
|
4036
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || data.loading, onClick: function () { refresh() } }, t('
|
|
4037
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: function () {
|
|
4038
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: function () {
|
|
4039
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-
|
|
4472
|
+
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')),
|
|
4473
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || anyLocked, onClick: function () { openEditor(null) } }, t('subagents.new')),
|
|
4474
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || anyLocked, onClick: function () { setResult(null); setModal({ type: 'import' }) } }, t('subagents.import')),
|
|
4475
|
+
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')),
|
|
4476
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: function () { loadTrash(false) } }, t('trash.btn.open')))),
|
|
4477
|
+
anyLocked === true ? React.createElement('div', { className: 'dsm-feedback dsm-warning', role: 'status' }, t('lock.banner')) : null,
|
|
4478
|
+
React.createElement('div', { className: 'dsm-summary', style: { '--dsm-stat-cols': '2' } },
|
|
4479
|
+
[['total', data.subagents.length, t('subagents.stat.total')],
|
|
4480
|
+
['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) {
|
|
4481
|
+
return React.createElement('div', { key: item[0], className: 'dsm-stat' },
|
|
4482
|
+
React.createElement('strong', null, item[1]), item[2])
|
|
4483
|
+
})),
|
|
4040
4484
|
React.createElement(Notice, { kind: result && result.ok ? 'ok' : 'err', text: result && result.text }),
|
|
4041
4485
|
data.error ? React.createElement('div', { className: 'dsm-feedback dsm-error' }, String(data.error)) : null,
|
|
4042
4486
|
data.loading ? React.createElement('div', { className: 'dsm-empty' }, t('memory.loading'))
|
|
@@ -4044,17 +4488,19 @@ function callApi(path, options) {
|
|
|
4044
4488
|
return React.createElement('div', { key: p.name, className: 'dsm-source' },
|
|
4045
4489
|
React.createElement('div', { className: 'dsm-source-head' },
|
|
4046
4490
|
React.createElement('div', { className: 'dsm-source-head-main' },
|
|
4047
|
-
React.createElement('span', { className: 'dsm-source-title' }, p.name),
|
|
4048
|
-
React.createElement('span', { className: 'dsm-note' }, p.description || '')
|
|
4491
|
+
React.createElement('span', { className: 'dsm-source-title', title: p.name }, p.name),
|
|
4492
|
+
React.createElement('span', { className: 'dsm-note' }, p.description || ''),
|
|
4493
|
+
p.enabled === false ? React.createElement('span', { className: 'dsm-tag', title: t('subagents.disabled.hint') }, t('subagents.disabled')) : null),
|
|
4049
4494
|
React.createElement('div', { className: 'dsm-source-actions' },
|
|
4050
|
-
React.createElement(
|
|
4051
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet
|
|
4495
|
+
React.createElement(Switch, { on: p.enabled !== false, disabled: busy || anyLocked, label: t('subagents.toggle') + ' ' + p.name, onClick: function () { togglePersona(p) } }),
|
|
4496
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || anyLocked, onClick: function () { openEditor(p.name) } }, t('memory.edit')),
|
|
4497
|
+
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')))))
|
|
4052
4498
|
})) : React.createElement('div', { className: 'dsm-empty' }, t('subagents.empty')),
|
|
4053
|
-
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.
|
|
4499
|
+
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) } },
|
|
4054
4500
|
React.createElement('div', { className: 'dsm-form' },
|
|
4055
4501
|
React.createElement('label', { className: 'dsm-field' },
|
|
4056
4502
|
React.createElement('span', { className: 'dsm-label' }, t('subagents.field.name')),
|
|
4057
|
-
React.createElement('input', { className: 'dsm-control', value: modal.form.name || '',
|
|
4503
|
+
React.createElement('input', { className: 'dsm-control', value: modal.form.name || '', placeholder: 'code-review', onChange: function (e) { setForm({ name: e.target.value }) } })),
|
|
4058
4504
|
React.createElement('label', { className: 'dsm-field' },
|
|
4059
4505
|
React.createElement('span', { className: 'dsm-label' }, t('subagents.field.description')),
|
|
4060
4506
|
React.createElement('input', { className: 'dsm-control', value: modal.form.description || '', placeholder: t('subagents.field.description.placeholder'), onChange: function (e) { setForm({ description: e.target.value }) } })),
|
|
@@ -4118,15 +4564,31 @@ function callApi(path, options) {
|
|
|
4118
4564
|
modal.form.error ? React.createElement('div', { className: 'dsm-feedback dsm-error', role: 'alert' }, String(modal.form.error)) : null),
|
|
4119
4565
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
4120
4566
|
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,
|
|
4121
|
-
modal && modal.type === 'delete' ? React.createElement(Modal, { key: 'sdel2', title: t('subagents.delete.title'), closeLabel: t('btn.
|
|
4567
|
+
modal && modal.type === 'delete' ? React.createElement(Modal, { key: 'sdel2', title: t('subagents.delete.title'), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
4122
4568
|
React.createElement('p', { className: 'dsm-help' }, t('subagents.delete.desc', { name: modal.name })),
|
|
4123
4569
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
4124
4570
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { submitDelete(modal.name) } }, t('memory.btn.delete.confirm')))) : null,
|
|
4125
|
-
modal && modal.type === 'import' ? React.createElement(ImportModal, { key: 'simp', t: t, title: t('subagents.import.title'),
|
|
4571
|
+
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,
|
|
4572
|
+
modal && modal.type === 'export' ? React.createElement(ExportModal, {
|
|
4573
|
+
key: 'export', t: t, title: t('export.subagents'),
|
|
4574
|
+
items: data.subagents.map(function (p) { return { key: p.name, name: p.name, desc: p.description || '' } }),
|
|
4575
|
+
busy: busy, result: result,
|
|
4576
|
+
onClose: function () { setResult(null); setModal(null) },
|
|
4577
|
+
onSubmit: function (names, outDir) {
|
|
4578
|
+
setBusy(true); setResult(null);
|
|
4579
|
+
apiCall('bundle-export', { kind: 'subagents', names: names, outDir: outDir }).then(function (res) {
|
|
4580
|
+
setBusy(false);
|
|
4581
|
+
if (res && res.ok) setResult({ ok: true, text: exportResultText(t, res) });
|
|
4582
|
+
else setResult({ ok: false, text: translateError(t, res) });
|
|
4583
|
+
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: String((e && e.message) || e) }) });
|
|
4584
|
+
},
|
|
4585
|
+
}) : null,
|
|
4126
4586
|
trash ? React.createElement(TrashModal, {
|
|
4127
4587
|
t: t,
|
|
4128
4588
|
title: t('trash.title'),
|
|
4129
4589
|
groupTitle: t('trash.group.agents'),
|
|
4590
|
+
groupSub: t('trash.section.agents.sub'),
|
|
4591
|
+
locked: anyLocked,
|
|
4130
4592
|
entries: trash.entries,
|
|
4131
4593
|
loading: trash.loading,
|
|
4132
4594
|
error: trash.error,
|
|
@@ -4147,6 +4609,8 @@ function callApi(path, options) {
|
|
|
4147
4609
|
})
|
|
4148
4610
|
var data = state[0], setData = state[1]
|
|
4149
4611
|
var qs = React.useState('')
|
|
4612
|
+
// 场景锁定(v0.8):任一场景锁定 = 五个域整体冻结;本页全部写控件禁用。
|
|
4613
|
+
var anyLocked = data.anyLocked === true
|
|
4150
4614
|
var query = qs[0], setQuery = qs[1]
|
|
4151
4615
|
var sfs = React.useState('')
|
|
4152
4616
|
var sceneFilter = sfs[0], setSceneFilter = sfs[1]
|
|
@@ -4166,6 +4630,10 @@ function callApi(path, options) {
|
|
|
4166
4630
|
// 记忆回收站:列表在打开弹窗时按需拉取(rules-trash-list)。
|
|
4167
4631
|
var trs = React.useState({ loading: false, error: null, entries: [] })
|
|
4168
4632
|
var trash = trs[0], setTrash = trs[1]
|
|
4633
|
+
// D14:导出弹窗的状态(busy + 结果)。刻意不复用本页的 `result`:
|
|
4634
|
+
// 那个是页面级回执(会自动消失),导出结果只该出现在弹窗里。
|
|
4635
|
+
var exs = React.useState(null)
|
|
4636
|
+
var exportState = exs[0], setExportState = exs[1]
|
|
4169
4637
|
// bundle 附件的隐藏文件选择器;上限与服务端 MAX_ATTACH_ENTRY_BYTES 对齐。
|
|
4170
4638
|
var attachRef = React.useRef(null)
|
|
4171
4639
|
var ATTACH_MAX_MB = 8
|
|
@@ -4504,9 +4972,9 @@ function callApi(path, options) {
|
|
|
4504
4972
|
r.descriptionDerived ? React.createElement('span', { className: 'dsm-tag' }, t('memory.derived')) : null),
|
|
4505
4973
|
React.createElement('div', { className: 'dsm-status ' + statusCls }, t(statusKey)),
|
|
4506
4974
|
React.createElement('div', { className: 'dsm-row-actions' },
|
|
4507
|
-
React.createElement(Switch, { on: enabled, disabled: busy || shadowed, label: t('memory.enable') + ' ' + r.name, onClick: function () { toggleRule(r) } }),
|
|
4508
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || shadowed, onClick: function () { openEditor(r) } }, t('memory.edit')),
|
|
4509
|
-
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'))))
|
|
4975
|
+
React.createElement(Switch, { on: enabled, disabled: busy || shadowed || anyLocked, label: t('memory.enable') + ' ' + r.name, onClick: function () { toggleRule(r) } }),
|
|
4976
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || shadowed || anyLocked, onClick: function () { openEditor(r) } }, t('memory.edit')),
|
|
4977
|
+
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'))))
|
|
4510
4978
|
}
|
|
4511
4979
|
|
|
4512
4980
|
function renderSceneCard(name) {
|
|
@@ -4520,7 +4988,7 @@ function callApi(path, options) {
|
|
|
4520
4988
|
return React.createElement('div', { key: 's:' + name, className: 'dsm-source' + (meta.active === false || isOrphan ? ' dsm-rule-shadowed' : '') },
|
|
4521
4989
|
React.createElement('div', { className: 'dsm-source-head' },
|
|
4522
4990
|
React.createElement('button', { type: 'button', className: 'dsm-source-head-main', 'aria-expanded': open, onClick: function () { toggleCollapse('s:' + name) } },
|
|
4523
|
-
React.createElement('span', { className: 'dsm-source-title' }, sceneLabel(data.scenes, name)),
|
|
4991
|
+
React.createElement('span', { className: 'dsm-source-title', title: sceneLabel(data.scenes, name) }, sceneLabel(data.scenes, name)),
|
|
4524
4992
|
React.createElement('span', { className: 'dsm-count' }, t('memory.scene.count', { count: bucket.rules.length })),
|
|
4525
4993
|
isShared ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('memory.scene.shared')) : null,
|
|
4526
4994
|
isGlobal ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('memory.scene.global.tag')) : null,
|
|
@@ -4542,19 +5010,42 @@ function callApi(path, options) {
|
|
|
4542
5010
|
: React.createElement('div', { className: 'dsm-empty' }, t('memory.scene.empty'))) : null)
|
|
4543
5011
|
}
|
|
4544
5012
|
|
|
5013
|
+
// D14 导出:可导出的记忆 = 索引里的规则,排除被同名 bundle 遮蔽的 flat
|
|
5014
|
+
// (它们不会被加载,导出去只会让人以为能用)。key 用**记忆 id**:
|
|
5015
|
+
// 服务端按 id 在索引里查真实文件路径(bundle 型必须这么做,见 planMemoryExport)。
|
|
5016
|
+
var exportableRules = (data.rules || []).filter(function (r) { return r.shadowed !== true })
|
|
5017
|
+
/** 导出条目:按**场景**分组(记忆页的分组维度就是场景),组内给出名称 + 描述。 */
|
|
5018
|
+
function memoryExportItems() {
|
|
5019
|
+
var sceneMeta = {}
|
|
5020
|
+
;(data.scenes || []).forEach(function (s) { sceneMeta[s.name] = s })
|
|
5021
|
+
return exportableRules.map(function (r) {
|
|
5022
|
+
var scene = sceneOf(r.group)
|
|
5023
|
+
var meta = sceneMeta[scene]
|
|
5024
|
+
var label = (meta && (meta.label || meta.name)) || scene || t('memory.scene.orphan')
|
|
5025
|
+
return { key: r.id, name: r.name, desc: r.description || '', group: label }
|
|
5026
|
+
})
|
|
5027
|
+
}
|
|
5028
|
+
function submitExport(names, outDir) {
|
|
5029
|
+
setExportState({ busy: true, result: null })
|
|
5030
|
+
apiCall('bundle-export', { kind: 'memories', names: names, outDir: outDir }).then(function (res) {
|
|
5031
|
+
if (res && res.ok) setExportState({ busy: false, result: { ok: true, text: exportResultText(t, res) } })
|
|
5032
|
+
else setExportState({ busy: false, result: { ok: false, text: translateError(t, res) } })
|
|
5033
|
+
}).catch(function (e) { setExportState({ busy: false, result: { ok: false, text: String((e && e.message) || e) } }) })
|
|
5034
|
+
}
|
|
5035
|
+
|
|
4545
5036
|
return React.createElement('section', { className: 'dsm-section' },
|
|
4546
5037
|
React.createElement('div', { className: 'dsm-head' },
|
|
4547
5038
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
4548
5039
|
React.createElement('div', { className: 'dsm-title-row' },
|
|
4549
|
-
React.createElement('h2', { className: 'dsm-title' }, t('memory.title')),
|
|
4550
|
-
VersionBadgeComponent ? React.createElement(VersionBadgeComponent, null) : null),
|
|
5040
|
+
React.createElement('h2', { className: 'dsm-title' }, t('memory.title'))),
|
|
4551
5041
|
React.createElement('p', { className: 'dsm-desc' }, t('memory.desc'))),
|
|
4552
5042
|
React.createElement('div', { className: 'dsm-actions' },
|
|
4553
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || data.loading, onClick: function () { refresh() } }, t('
|
|
4554
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick:
|
|
4555
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: function () { setResult(null); setImportScene(''); setModal({ type: 'import' }) } }, t('memory.import')),
|
|
4556
|
-
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: function () {
|
|
4557
|
-
|
|
5043
|
+
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')),
|
|
5044
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy || anyLocked, onClick: function () { openCreate(sceneFilter || '') } }, t('memory.btn.new')),
|
|
5045
|
+
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')),
|
|
5046
|
+
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')),
|
|
5047
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: openTrash }, t('memory.trash.open')))),
|
|
5048
|
+
React.createElement('div', { className: 'dsm-summary' },
|
|
4558
5049
|
React.createElement('div', { key: 'total', className: 'dsm-stat' }, React.createElement('strong', null, stats.total || 0), t('memory.stat.total')),
|
|
4559
5050
|
React.createElement('div', { key: 'scenes', className: 'dsm-stat' }, React.createElement('strong', null, (data.scenes || []).length), t('memory.stat.sceneCount')),
|
|
4560
5051
|
React.createElement('div', { key: 'active', className: 'dsm-stat' }, React.createElement('strong', null, stats.scenes || 0), t('memory.stat.scenes'))),
|
|
@@ -4578,20 +5069,27 @@ function callApi(path, options) {
|
|
|
4578
5069
|
data.loading && !hasAnyRule ? React.createElement('div', { key: 'loading', className: 'dsm-empty' }, t('memory.loading'))
|
|
4579
5070
|
: visibleBuckets.length ? React.createElement('div', { key: 'scenes', className: 'dsm-sources' }, visibleBuckets.map(renderSceneCard))
|
|
4580
5071
|
: React.createElement('div', { key: 'empty', className: 'dsm-empty' }, q || sceneFilter ? t('memory.empty.search') : t('memory.empty')),
|
|
4581
|
-
modal && modal.type === 'delete' ? React.createElement(Modal, { key: 'del', title: t('memory.delete.title'), closeLabel: t('btn.
|
|
5072
|
+
modal && modal.type === 'delete' ? React.createElement(Modal, { key: 'del', title: t('memory.delete.title'), closeLabel: t('btn.close'), onClose: function () { setModal(null) } },
|
|
4582
5073
|
React.createElement('p', { className: 'dsm-help' }, t('memory.delete.desc', { name: modal.rule.name })),
|
|
4583
5074
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
4584
5075
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { submitDelete(modal.rule) } }, t('memory.btn.delete.confirm')))) : null,
|
|
4585
5076
|
modal && modal.type === 'import' ? React.createElement(ImportModal, {
|
|
4586
|
-
key: 'mimp', t: t, title: t('memory.import.title'),
|
|
5077
|
+
key: 'mimp', t: t, title: t('memory.import.title'), busy: busy,
|
|
5078
|
+
requirements: [t('upload.requirement.memory.1'), t('upload.requirement.memory.2'), t('upload.requirement.memory.3')],
|
|
4587
5079
|
extra: React.createElement('label', { className: 'dsm-field' },
|
|
4588
5080
|
React.createElement('span', { className: 'dsm-label' }, t('memory.import.scene')),
|
|
4589
|
-
React.createElement('input', { className: 'dsm-control', value: importScene, placeholder: t('memory.scene.global.hint'), onChange: function (e) { setImportScene(e.target.value) } }),
|
|
4590
|
-
React.createElement('p', { className: 'dsm-help' }, t('memory.import.sceneHint'))),
|
|
5081
|
+
React.createElement('input', { className: 'dsm-control', value: importScene, placeholder: t('memory.scene.global.hint'), onChange: function (e) { setImportScene(e.target.value) } })),
|
|
4591
5082
|
onClose: function () { setModal(null) },
|
|
4592
5083
|
onSubmit: submitImport,
|
|
4593
5084
|
}) : null,
|
|
4594
|
-
|
|
5085
|
+
exportState ? React.createElement(ExportModal, {
|
|
5086
|
+
key: 'mexport', t: t, title: t('export.memories'),
|
|
5087
|
+
items: memoryExportItems(),
|
|
5088
|
+
busy: exportState.busy, result: exportState.result,
|
|
5089
|
+
onClose: function () { setExportState(null) },
|
|
5090
|
+
onSubmit: submitExport,
|
|
5091
|
+
}) : null,
|
|
5092
|
+
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 },
|
|
4595
5093
|
React.createElement('div', { className: 'dsm-form' },
|
|
4596
5094
|
React.createElement('label', { className: 'dsm-field' },
|
|
4597
5095
|
React.createElement('span', { className: 'dsm-label' }, t('memory.field.group')),
|
|
@@ -4672,7 +5170,7 @@ function callApi(path, options) {
|
|
|
4672
5170
|
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')))
|
|
4673
5171
|
})))
|
|
4674
5172
|
: React.createElement('div', { className: 'dsm-empty' }, t('memory.trash.empty')))) : null,
|
|
4675
|
-
modal && modal.type === 'trash-delete' ? React.createElement(Modal, { key: 'trash-delete', title: t('memory.trash.confirmTitle'), closeLabel: t('btn.
|
|
5173
|
+
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' }) } },
|
|
4676
5174
|
React.createElement('p', { className: 'dsm-help' }, t('memory.trash.confirmDesc', { name: modal.entry.name })),
|
|
4677
5175
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
4678
5176
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { submitTrashDelete(modal.entry) } }, t('memory.trash.purge')))) : null)
|