dsh-plugin-tool-management 0.12.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +84 -0
- package/README.md +90 -114
- package/README_EN.md +98 -119
- package/docs/images/1-EN.png +0 -0
- package/docs/images/1.png +0 -0
- package/docs/images/2-EN.png +0 -0
- package/docs/images/2.png +0 -0
- package/docs/images/3-EN.png +0 -0
- package/docs/images/3.png +0 -0
- package/docs/images/4-EN.png +0 -0
- package/docs/images/4.png +0 -0
- package/docs/images/5-EN.png +0 -0
- package/docs/images/5.png +0 -0
- package/docs/images/6-EN.png +0 -0
- package/docs/images/6.png +0 -0
- package/docs/images/7-EN.png +0 -0
- package/docs/images/7.png +0 -0
- package/docs/images/8-EN.png +0 -0
- package/docs/images/8.png +0 -0
- package/docs/update.md +63 -0
- package/lib/client.js +1086 -95
- package/lib/compat/preset-reach.js +6 -3
- package/lib/context-inject.js +183 -59
- package/lib/index.js +501 -49
- package/lib/mcp/manager.js +118 -71
- package/lib/mcp/secret-guard.js +21 -0
- package/lib/mcp/state-section.js +19 -14
- package/lib/memories/archive-engine.js +199 -0
- package/lib/memories/constants.js +72 -8
- package/lib/memories/index-io.js +1 -1
- package/lib/memories/projection.js +67 -31
- package/lib/memories/service.js +17 -2
- package/lib/memories/snapshot.js +123 -22
- package/lib/op-registry.js +279 -0
- package/lib/ops/candidates.js +11 -0
- package/lib/ops/compat.js +23 -2
- package/lib/ops/sessions.js +1 -1
- package/lib/ops/state-doctor.js +115 -0
- package/lib/prompts/service.js +25 -2
- package/lib/request-gate.js +21 -78
- package/lib/scene-settings.js +17 -0
- package/lib/scenes/candidates.js +56 -0
- package/lib/skills/catalog.js +10 -5
- package/lib/skills/core.js +102 -0
- package/lib/skills/service.js +34 -2
- package/lib/subagents/catalog.js +9 -5
- package/lib/subagents/service.js +37 -4
- package/lib/subagents/tools.js +19 -5
- package/lib/tools/deps.js +15 -0
- package/lib/tools/mcp.js +291 -50
- package/lib/tools/memory.js +170 -19
- package/lib/tools/prompt.js +28 -14
- package/lib/tools/scene.js +351 -0
- package/lib/tools/skills.js +168 -14
- package/lib/tools/subagent.js +129 -3
- package/lib/tools/table.js +290 -0
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -20,6 +20,17 @@ window.__ModuleLoader__.load({
|
|
|
20
20
|
|
|
21
21
|
// 本插件自己的设计变量(宿主那套是 `--dsw-*`)。告警色此前在 12 条规则里各写一遍
|
|
22
22
|
// 十六进制字面量,换主题要改 12 次 —— 收敛成 `--dsm-warn`,字面量只留下面那一处。
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 取词三件套里的兜底那一件:动态键(按名字拼出来的键)缺失时返回 `fallback`,
|
|
26
|
+
* 而不是把键名当文案显示出来(`t` 缺键时原样返回键名)。
|
|
27
|
+
*
|
|
28
|
+
* 定义放在**文件头这一片**(2026-09-23 从那几页的共用区搬来):它要服务的是**早于它**
|
|
29
|
+
* 出现的分片(兼容页在 20 片,按名字拼 `compat.tools.about.<工具名>`),而分片共用一个
|
|
30
|
+
* factory 作用域 —— 声明在后面时早于它的调用点取不到这个名字(`npm run typecheck:client`
|
|
31
|
+
* 当场会红)。与 DICT 无依赖,`t` 是传进来的,所以放这儿不影响求值顺序。
|
|
32
|
+
*/
|
|
33
|
+
function translateOrFallback(t, key, fallback, params) { var value = t(key, params); return typeof value === "string" && value !== key ? value : fallback; }
|
|
23
34
|
const CSS =
|
|
24
35
|
':root{--dsm-warn:#d49245;--dsm-warn-bg:rgba(212,146,69,.12)}' +
|
|
25
36
|
'.dsm-stat-row{display:flex;min-width:0;align-items:center;gap:10px}.dsm-stat-row .dsm-summary{flex:1;min-width:0}' +
|
|
@@ -104,7 +115,10 @@ button.dsm-tag:disabled{cursor:default;opacity:.6}
|
|
|
104
115
|
.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}
|
|
105
116
|
/* 头部这一层包住「标题行 + 令牌提示」:提示挂在头部里面(而不是 head 与 body 之间)是为了
|
|
106
117
|
让 body 的子节点位置稳定 —— 位置一变 React 会把 body 整棵重挂(输入框失焦、下拉归零)。 */
|
|
107
|
-
.dsm-modal-head-wrap{display:flex;flex-direction:column;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-help-list{display:flex;margin:0;padding-left:18px;flex-direction:column;gap:5px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}.dsm-modal-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px}
|
|
118
|
+
.dsm-modal-head-wrap{display:flex;flex-direction:column;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-help-list{display:flex;margin:0;padding-left:18px;flex-direction:column;gap:5px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}.dsm-modal-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px}
|
|
119
|
+
/* 两动作性质不同的弹窗底部(目前只有「进入场景」的预览卡):左边「不再显示」、右边「确认进入」。
|
|
120
|
+
都靠右排会读成"两选一",而它们不是同一层意思(一个是不想被拦,一个是要这么做)。 */
|
|
121
|
+
.dsm-modal-actions-split{justify-content:space-between}.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;font:inherit;text-align:center}.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 var(--dsm-warn);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}
|
|
108
122
|
.dsm-modal-title-row{display:inline-flex;align-items:center;gap:9px;min-width:0}
|
|
109
123
|
.dsm-pill{display:inline-flex;min-height:20px;align-items:center;padding:0 8px;border:1px solid var(--dsw-alias-border-l3);border-radius:999px;font-size:11px;font-weight:500;white-space:nowrap}
|
|
110
124
|
.dsm-pill-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}
|
|
@@ -149,7 +163,7 @@ button.dsm-tag:disabled{cursor:default;opacity:.6}
|
|
|
149
163
|
.dsm-row>.dsm-tags:empty{display:none}
|
|
150
164
|
.dsm-row>.dsm-tags:empty~.dsm-row-actions{grid-row:1 / span 2}}@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}}
|
|
151
165
|
.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}
|
|
152
|
-
.dsm-rule-shadowed .dsm-name,.dsm-rule-shadowed .dsm-note{color:var(--dsw-alias-state-error-primary)}.dsm-rule-off .dsm-name,.dsm-rule-off .dsm-note{color:var(--dsw-alias-label-tertiary)}.dsm-rule-shadow-hint{margin-top:3px;color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:17px}.dsm-rule-invalid{border-color:var(--dsw-alias-state-error-primary)!important}.dsm-rule-hint{color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:17px}.dsm-rule-budget{display:flex;flex-direction:column;gap:6px}.dsm-budget-meta{display:flex;align-items:baseline;justify-content:space-between;gap:8px}.dsm-budget-meta strong{margin-right:0;font-size:13px;font-weight:680}.dsm-budget-over-text{color:var(--dsw-alias-state-error-primary);font-size:11px}.dsm-budget-bar{height:6px;overflow:hidden;border-radius:99px;background:var(--dsw-alias-interactive-bg-hover)}.dsm-budget-fill{height:100%;border-radius:99px;background:var(--dsw-alias-state-success-primary);transition:width 160ms ease}.dsm-budget-fill.dsm-budget-over{background:var(--dsw-alias-state-error-primary)}.dsm-char-count{color:var(--dsw-alias-label-tertiary);font-size:11px}.dsm-char-over{color:var(--dsw-alias-state-error-primary)}
|
|
166
|
+
.dsm-rule-shadowed .dsm-name,.dsm-rule-shadowed .dsm-note{color:var(--dsw-alias-state-error-primary)}.dsm-rule-off .dsm-name,.dsm-rule-off .dsm-note{color:var(--dsw-alias-label-tertiary)}.dsm-rule-shadow-hint{margin-top:3px;color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:17px}.dsm-rule-invalid{border-color:var(--dsw-alias-state-error-primary)!important}.dsm-rule-hint{color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:17px}.dsm-rule-budget{display:flex;flex-direction:column;gap:6px}.dsm-budget-meta{display:flex;align-items:baseline;justify-content:space-between;gap:8px}.dsm-budget-meta strong{margin-right:0;font-size:13px;font-weight:680}.dsm-budget-over-text{color:var(--dsw-alias-state-error-primary);font-size:11px}.dsm-budget-bar{height:6px;overflow:hidden;border-radius:99px;background:var(--dsw-alias-interactive-bg-hover)}.dsm-budget-fill{height:100%;border-radius:99px;background:var(--dsw-alias-state-success-primary);transition:width 160ms ease}.dsm-budget-fill.dsm-budget-over{background:var(--dsw-alias-state-error-primary)}.dsm-char-count{color:var(--dsw-alias-label-tertiary);font-size:11px}.dsm-char-over{color:var(--dsw-alias-state-error-primary)}.dsm-field-row{display:flex;gap:12px;align-items:flex-start}.dsm-field-row>.dsm-field{flex:1;min-width:0}
|
|
153
167
|
.dsm-combo-row{display:flex;gap:8px}.dsm-combo-row .dsm-control{flex:1}
|
|
154
168
|
.dsm-attach-list{display:flex;flex-direction:column;gap:6px}.dsm-attach-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
|
|
155
169
|
.dsm-toast{position:fixed;right:22px;bottom:22px;z-index:1250;box-sizing:border-box;max-width:min(420px,calc(100vw - 44px));padding:10px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-3);box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;animation:dsm-toast-in 160ms ease-out}@keyframes dsm-toast-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}/* 动效降级:系统开了「减少动态效果」就一律关掉。新增 transition / animation 时记得同步加到这里,
|
|
@@ -229,7 +243,11 @@ button.dsm-tag:disabled{cursor:default;opacity:.6}
|
|
|
229
243
|
.dsm-adv{border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-layer-1)}
|
|
230
244
|
.dsm-adv-head{display:flex;width:100%;min-height:40px;align-items:center;gap:8px;padding:0 11px;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;text-align:left;cursor:pointer}
|
|
231
245
|
.dsm-adv-head:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
232
|
-
.dsm-adv-caret{color:var(--dsw-alias-label-tertiary);font-size:10px}
|
|
246
|
+
.dsm-adv-caret{flex:none;color:var(--dsw-alias-label-tertiary);font-size:10px}
|
|
247
|
+
/* 「高级选项」永不折行:它在收起时与两行摘要并排,被挤成两字一行就不再像个标题了。 */
|
|
248
|
+
.dsm-adv-title{flex:none;white-space:nowrap}
|
|
249
|
+
/* 折叠摘要固定两行(模型/思考强度/目录注入 + 工具名单),靠 align-items:center 与标题垂直居中。 */
|
|
250
|
+
.dsm-adv-sum{display:flex;min-width:0;flex-direction:column;gap:1px}
|
|
233
251
|
.dsm-adv-body{display:flex;flex-direction:column;gap:12px;padding:11px;border-top:1px solid var(--dsw-alias-border-l1)}
|
|
234
252
|
.dsm-adv-note{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}
|
|
235
253
|
/* ── 工具限制四行(人设表单:一行一个 Agent 预设,白/黑名单互斥、默认折叠)──────── */
|
|
@@ -328,6 +346,40 @@ button.dsm-tag:disabled{cursor:default;opacity:.6}
|
|
|
328
346
|
.dsm-inject-domains{display:flex;flex-wrap:wrap;gap:7px 18px;padding:0 2px}
|
|
329
347
|
.dsm-inject-domain{display:inline-flex;align-items:center;gap:7px;color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;white-space:nowrap;cursor:pointer}
|
|
330
348
|
.dsm-inject-domain input[type=checkbox]{flex:none;width:15px;height:15px;margin:0;cursor:pointer;accent-color:var(--dsw-alias-state-success-primary)}
|
|
349
|
+
/* 模型工具表:与「注入」同屏的第二种开关。**一行一个工具、四列固定宽度**(勾选框 / 名字 /
|
|
350
|
+
≈tok / 一句话介绍)—— 列宽不固定就竖着扫不出任何一列(用户 2026-09-23 指出挤成一排很难看)。
|
|
351
|
+
名字用等宽、数字用 tabular-nums 右对齐;介绍超长省略,完整那句在悬停里。
|
|
352
|
+
出厂默认关着哪几条**不在行上标**(用户 2026-09-23 要求去掉「默认关」小标):合计下面那句
|
|
353
|
+
"其中 ≈X 是出厂默认关掉的 N 条"是唯一的出处,加上悬停那一行会说"出厂默认就是关着的"。 */
|
|
354
|
+
.dsm-tooltable{display:flex;flex-direction:column;gap:10px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}
|
|
355
|
+
/* 保存往返期间:整块不吃点击、略灰 —— 这一块刻意没有乐观更新(见 saveToolTable 注释)。 */
|
|
356
|
+
.dsm-tooltable-busy{pointer-events:none;opacity:.55}
|
|
357
|
+
.dsm-tooltable-summary{display:flex;align-items:center;gap:10px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
|
|
358
|
+
/* 「保存方案 / 恢复方案」钉在合计那一行的最右(用户 2026-09-23 指定位置)。
|
|
359
|
+
按钮压到与 18px 行高齐平:宿主默认的 .dsm-btn 比这行字高,直接放会把整行顶开。 */
|
|
360
|
+
.dsm-tooltable-preset-actions{display:flex;flex:none;align-items:center;gap:6px;margin-left:auto}
|
|
361
|
+
.dsm-tooltable-preset-actions .dsm-btn{padding:0 9px;font-size:11px;line-height:22px}
|
|
362
|
+
/* 方案清单(恢复弹窗):一行一份,名字吃剩余宽度,数字与两颗按钮靠右。 */
|
|
363
|
+
.dsm-preset-list{display:flex;flex-direction:column;gap:6px}
|
|
364
|
+
.dsm-preset-row{display:flex;align-items:center;gap:8px;padding:7px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-layer-1)}
|
|
365
|
+
.dsm-preset-name{min-width:0;overflow:hidden;flex:1;color:var(--dsw-alias-label-primary);font-size:12px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}
|
|
366
|
+
.dsm-preset-meta{flex:none;color:var(--dsw-alias-label-tertiary);font-size:11px;font-variant-numeric:tabular-nums}
|
|
367
|
+
.dsm-preset-row .dsm-btn{padding:0 9px;font-size:11px;line-height:22px}
|
|
368
|
+
.dsm-preset-tag{margin-left:6px;padding:0 5px;border:1px solid var(--dsw-alias-border-l2);border-radius:4px;color:var(--dsw-alias-label-tertiary);font-size:10px;font-weight:400}
|
|
369
|
+
/* 合计的第二句:把"省下的"拆出出厂默认那一截。比第一句弱一档,它是解释不是结论。 */
|
|
370
|
+
.dsm-tooltable-sub{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}
|
|
371
|
+
.dsm-tooltable-group{display:flex;flex-direction:column;gap:3px}
|
|
372
|
+
.dsm-tooltable-ghead{display:flex;align-items:center;gap:7px;color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;cursor:pointer}
|
|
373
|
+
.dsm-tooltable-ghead input[type=checkbox]{flex:none;width:15px;height:15px;margin:0;cursor:pointer;accent-color:var(--dsw-alias-state-success-primary)}
|
|
374
|
+
.dsm-tooltable-gname{font-weight:600}
|
|
375
|
+
.dsm-tooltable-gcount{color:var(--dsw-alias-label-tertiary);font-size:11px;font-variant-numeric:tabular-nums}
|
|
376
|
+
.dsm-tooltable-tools{display:flex;flex-direction:column;padding-left:22px}
|
|
377
|
+
.dsm-tooltable-tool{display:grid;grid-template-columns:15px 186px 48px minmax(0,1fr);align-items:center;gap:0 8px;padding:2px 4px;border-radius:5px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:18px;cursor:pointer}
|
|
378
|
+
.dsm-tooltable-tool:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
379
|
+
.dsm-tooltable-tool input[type=checkbox]{width:14px;height:14px;margin:0;cursor:pointer;accent-color:var(--dsw-alias-state-success-primary)}
|
|
380
|
+
.dsm-tooltable-name{overflow:hidden;color:var(--dsw-alias-label-primary);font-family:ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}
|
|
381
|
+
.dsm-tooltable-tok{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
382
|
+
.dsm-tooltable-about{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
331
383
|
/* 注入实况(只读):与「注入」设置同屏。行 = 域 + 体积 + 状态胶囊,点域名展开正文。 */
|
|
332
384
|
.dsm-inject-live-actions{display:flex;flex:none;align-items:center;gap:6px;margin-left:auto}
|
|
333
385
|
.dsm-inject-live-toggle{padding:0;border:0;background:transparent;color:inherit;font:inherit;cursor:pointer}
|
|
@@ -340,7 +392,7 @@ button.dsm-tag:disabled{cursor:default;opacity:.6}
|
|
|
340
392
|
.dsm-inject-live-mark-blank{visibility:hidden}
|
|
341
393
|
.dsm-inject-live-size{color:var(--dsw-alias-label-tertiary);font-size:11px;white-space:nowrap}
|
|
342
394
|
/* 实况面板的域名格里有多个行内元素(域名 / 体积 / 采纳统计),行内元素之间没有空白节点,
|
|
343
|
-
|
|
395
|
+
不设间距就会挤成「场景· 3.2 KB」。
|
|
344
396
|
刻意**不复用** .dsm-compat-name:那一类在另外五个面板里只放一个文本子节点,改成 flex 会让
|
|
345
397
|
长标签变成不可收缩的 flex 项 → 窄容器下溢出而不是换行。所以只在实况面板这一处加 gap。 */
|
|
346
398
|
.dsm-inject-live-name{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 6px}
|
|
@@ -839,7 +891,25 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
839
891
|
// 注入设置(本插件五个注入域的开关;见 src/context-inject.ts)。它和可达性矩阵是一体两面:
|
|
840
892
|
// 矩阵说"到不到得了",这里决定"要不要"。读不到时这一节不显示。
|
|
841
893
|
const [inject, setInject] = React.useState(null)
|
|
842
|
-
//
|
|
894
|
+
// 模型工具表(哪些工具整份不发给模型;见 src/tools/table.ts)。与「注入」是两种省法:
|
|
895
|
+
// 注入关掉省的是正文,这里关掉省的是工具 schema(工具表按每个请求付钱)。
|
|
896
|
+
const [toolTable, setToolTable] = React.useState(null)
|
|
897
|
+
// 保存往返期间把整块置灰(见 saveToolTable 的注释:不做乐观更新)。
|
|
898
|
+
const [toolTableBusy, setToolTableBusy] = React.useState(false)
|
|
899
|
+
// 「方案」弹窗:null = 未打开。`mode:'save'` 给当前这套勾选起名存下来;
|
|
900
|
+
// `mode:'apply'` 列出已存的方案 + 那条不可删的出厂默认,点一行就应用。
|
|
901
|
+
const [toolPreset, setToolPreset] = React.useState(null)
|
|
902
|
+
const toolPresetDialogRef = React.useRef(null)
|
|
903
|
+
const toolPresetInputRef = React.useRef(null)
|
|
904
|
+
// 与备份弹窗同一条依赖口径:用"开 / 关"这个布尔,不是整个 state —— 后者每改一次
|
|
905
|
+
// (输入名字、busy)都会变,会把用户刚聚焦的输入框抢回弹窗根节点。
|
|
906
|
+
React.useEffect(function () {
|
|
907
|
+
if (!toolPreset || !toolPresetDialogRef.current) return
|
|
908
|
+
toolPresetDialogRef.current.focus()
|
|
909
|
+
// 存方案那条路用户是来打字的,光标直接放进输入框,省一次点击。
|
|
910
|
+
if (toolPreset.mode === 'save' && toolPresetInputRef.current) toolPresetInputRef.current.focus()
|
|
911
|
+
}, [!!toolPreset])
|
|
912
|
+
// 注入实况(只读):最近活跃会话里模型**真正看到**的五域文本 + 那一段对话的投递统计。
|
|
843
913
|
// 与「注入」设置是一体两面:设置说"要送什么",这里说"实际送到了什么"。
|
|
844
914
|
const [live, setLive] = React.useState(null)
|
|
845
915
|
const [liveOpen, setLiveOpen] = React.useState('')
|
|
@@ -1078,6 +1148,123 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1078
1148
|
.then(function (r) { if (alive !== false && r && r.ok && r.settings) setInject(r.settings) })
|
|
1079
1149
|
.catch(function () { /* 读不到 → 不渲染这一节 */ })
|
|
1080
1150
|
}
|
|
1151
|
+
// 工具表实况(注册时量到的体积 + 关掉了哪些)。服务端返回的 `report` 自带分组与
|
|
1152
|
+
// ≈token 合计 —— 界面不自己算,也不缓存第二份数字。
|
|
1153
|
+
const applyToolTable = function (r, alive) {
|
|
1154
|
+
if (alive === false) return
|
|
1155
|
+
if (r && r.ok && r.report) setToolTable(toolTableFrom(r))
|
|
1156
|
+
}
|
|
1157
|
+
/** 服务端回包 → 界面那一份状态(三条路径共用:读、改勾选、存/删方案)。 */
|
|
1158
|
+
const toolTableFrom = function (r, hiddenFallback) {
|
|
1159
|
+
return {
|
|
1160
|
+
hidden: r.hidden || hiddenFallback || [], presets: r.presets || [],
|
|
1161
|
+
defaultHidden: r.defaultHidden || [], report: r.report,
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
const loadToolTable = function (alive) {
|
|
1165
|
+
apiCall('tool-table', {})
|
|
1166
|
+
.then(function (r) { applyToolTable(r, alive) })
|
|
1167
|
+
.catch(function () { /* 读不到 → 不渲染这一节 */ })
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* 保存工具表开关。
|
|
1171
|
+
*
|
|
1172
|
+
* 刻意**不做乐观更新**(与上面的注入设置不同):勾选框状态与"关掉后每轮少发多少"
|
|
1173
|
+
* 这两个数字必须来自同一份服务端数据(注册时量到的体积),本地先改一个就会在
|
|
1174
|
+
* 往返的这几毫秒里出现"框已经勾了、合计还没变"的两种真相。这里是本机 HTTP,
|
|
1175
|
+
* 往返可以忽略,所以选择在等待期间把整块变灰。
|
|
1176
|
+
*/
|
|
1177
|
+
const saveToolTable = function (names) {
|
|
1178
|
+
if (!toolTable || toolTableBusy) return
|
|
1179
|
+
setToolTableBusy(true)
|
|
1180
|
+
apiCall('tool-table', { set: true, hidden: names.slice() })
|
|
1181
|
+
.then(function (r) {
|
|
1182
|
+
if (r && r.ok && r.report) setToolTable(toolTableFrom(r, names))
|
|
1183
|
+
else loadToolTable(true)
|
|
1184
|
+
// 功能总览那一行写着"一轮发出去多少",它得跟着变。
|
|
1185
|
+
loadFeatures(true)
|
|
1186
|
+
})
|
|
1187
|
+
.catch(function () { loadToolTable(true) })
|
|
1188
|
+
.then(function () { setToolTableBusy(false) })
|
|
1189
|
+
}
|
|
1190
|
+
/** 单个工具的勾选:在 hidden 名单里加 / 删一个名字。 */
|
|
1191
|
+
const toggleToolTableTool = function (name, on) {
|
|
1192
|
+
if (!toolTable) return
|
|
1193
|
+
const hid = (toolTable.hidden || []).filter(function (n) { return n !== name })
|
|
1194
|
+
if (!on) hid.push(name)
|
|
1195
|
+
saveToolTable(hid)
|
|
1196
|
+
}
|
|
1197
|
+
/** 整个域一起开 / 关(分组勾选框)。 */
|
|
1198
|
+
const toggleToolTableGroup = function (group, on) {
|
|
1199
|
+
if (!toolTable) return
|
|
1200
|
+
const names = group.tools.map(function (tool) { return tool.name })
|
|
1201
|
+
const hid = (toolTable.hidden || []).filter(function (n) { return names.indexOf(n) < 0 })
|
|
1202
|
+
if (!on) Array.prototype.push.apply(hid, names)
|
|
1203
|
+
saveToolTable(hid)
|
|
1204
|
+
}
|
|
1205
|
+
// ---------- 「方案」:把一组勾选存成名字,之后一键换回来 ----------
|
|
1206
|
+
// 为什么要它(用户 2026-09-23):这一块的开关是**成批**用的 —— "这次要让模型自己配
|
|
1207
|
+
// 服务器"与"平时只让它查"是两套选择,挨个点二十个框没人愿意重复第二遍。
|
|
1208
|
+
// 三条口径:① 方案存的是**关掉的名单**(与 `hidden` 同口径),不是"开着的",否则
|
|
1209
|
+
// 新注册一条工具会让所有旧方案静默地少管一条;② 应用 = 走 `saveToolTable` 那条既有
|
|
1210
|
+
// 落盘路径(可见性重排与两处目录刷新都在那边,不开第二条真相);③ 出厂默认**不在文件里**
|
|
1211
|
+
// (它是代码里的 `DEFAULT_HIDDEN_TOOLS`),所以删不掉,界面把它排在第一行。
|
|
1212
|
+
/** 工具名 → ≈tok:方案行上那句"开着 X 个 · ≈Y tok/轮"要用,数字仍全部来自服务端报告。 */
|
|
1213
|
+
const toolTokByName = function () {
|
|
1214
|
+
const map = {}
|
|
1215
|
+
const groups = (toolTable && toolTable.report && toolTable.report.groups) || []
|
|
1216
|
+
groups.forEach(function (g) {
|
|
1217
|
+
(g.tools || []).forEach(function (tool) { map[tool.name] = tool.tok })
|
|
1218
|
+
})
|
|
1219
|
+
return map
|
|
1220
|
+
}
|
|
1221
|
+
/** 弹窗里的错误行:服务端回 `code` 时按当前语言出话;回 `error`(落盘失败)只能原样。
|
|
1222
|
+
* 认不出的 code 退回通用那句 —— 拼出来的键名如果落空,`t` 会把键本身吐到界面上。 */
|
|
1223
|
+
const presetErrorText = function (r) {
|
|
1224
|
+
const code = r && r.code
|
|
1225
|
+
if (code === 'nameEmpty' || code === 'nameTaken' || code === 'limit' || code === 'notFound') {
|
|
1226
|
+
return t('compat.tools.preset.err.' + code, { max: r.limit || 0 })
|
|
1227
|
+
}
|
|
1228
|
+
return (r && r.error) || t('compat.tools.preset.err.net')
|
|
1229
|
+
}
|
|
1230
|
+
/** 名字归一化与服务端 `presetNameOf` 同形(折叠空白 + 去首尾),否则本地查重会漏。 */
|
|
1231
|
+
const presetNameNorm = function (value) { return String(value || '').replace(/\s+/g, ' ').trim() }
|
|
1232
|
+
/** 存方案:空名与撞名在本地先挡住(服务端也会挡,但那是白跑一趟)。 */
|
|
1233
|
+
const submitToolPreset = function () {
|
|
1234
|
+
if (!toolPreset || toolPreset.busy) return
|
|
1235
|
+
const name = presetNameNorm(toolPreset.name)
|
|
1236
|
+
if (!name) {
|
|
1237
|
+
setToolPreset(Object.assign({}, toolPreset, { error: t('compat.tools.preset.err.nameEmpty') }))
|
|
1238
|
+
return
|
|
1239
|
+
}
|
|
1240
|
+
if (((toolTable && toolTable.presets) || []).some(function (p) { return p.name === name })) {
|
|
1241
|
+
setToolPreset(Object.assign({}, toolPreset, { name: name, error: t('compat.tools.preset.err.nameTaken', { name: name }) }))
|
|
1242
|
+
return
|
|
1243
|
+
}
|
|
1244
|
+
setToolPreset(Object.assign({}, toolPreset, { busy: true, error: '' }))
|
|
1245
|
+
apiCall('tool-table', { presetSave: name })
|
|
1246
|
+
.then(function (r) {
|
|
1247
|
+
if (r && r.ok) { setToolTable(toolTableFrom(r)); setToolPreset(null) }
|
|
1248
|
+
else setToolPreset({ mode: 'save', name: name, error: presetErrorText(r) })
|
|
1249
|
+
})
|
|
1250
|
+
.catch(function () { setToolPreset({ mode: 'save', name: name, error: presetErrorText(null) }) })
|
|
1251
|
+
}
|
|
1252
|
+
/** 删方案:留在弹窗里(用户多半还要接着挑),只把清单换成服务端那份。 */
|
|
1253
|
+
const deleteToolPreset = function (name) {
|
|
1254
|
+
if (toolPreset && toolPreset.busy) return
|
|
1255
|
+
setToolPreset({ mode: 'apply', busy: true })
|
|
1256
|
+
apiCall('tool-table', { presetDelete: name })
|
|
1257
|
+
.then(function (r) {
|
|
1258
|
+
if (r && r.ok) { setToolTable(toolTableFrom(r)); setToolPreset({ mode: 'apply' }) }
|
|
1259
|
+
else setToolPreset({ mode: 'apply', error: presetErrorText(r) })
|
|
1260
|
+
})
|
|
1261
|
+
.catch(function () { setToolPreset({ mode: 'apply', error: presetErrorText(null) }) })
|
|
1262
|
+
}
|
|
1263
|
+
/** 应用一份方案 = 用它那组名单走既有的保存路径。 */
|
|
1264
|
+
const applyToolPreset = function (hidden) {
|
|
1265
|
+
setToolPreset(null)
|
|
1266
|
+
saveToolTable((hidden || []).slice())
|
|
1267
|
+
}
|
|
1081
1268
|
const loadLive = function (alive) {
|
|
1082
1269
|
apiCall('injection-live', {})
|
|
1083
1270
|
.then(function (r) { if (alive !== false && r && r.ok) setLive(r) })
|
|
@@ -1098,6 +1285,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1098
1285
|
.catch(function (e) { if (alive) { setError(errMsg(e)); setBusy(false) } })
|
|
1099
1286
|
loadReach(alive)
|
|
1100
1287
|
loadInject(alive)
|
|
1288
|
+
loadToolTable(alive)
|
|
1101
1289
|
loadLive(alive)
|
|
1102
1290
|
loadFeatures(alive)
|
|
1103
1291
|
loadToken(alive)
|
|
@@ -1111,6 +1299,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1111
1299
|
.catch(function (e) { setError(errMsg(e)); setBusy(false) })
|
|
1112
1300
|
loadReach(true)
|
|
1113
1301
|
loadInject(true)
|
|
1302
|
+
loadToolTable(true)
|
|
1114
1303
|
loadLive(true)
|
|
1115
1304
|
loadFeatures(true)
|
|
1116
1305
|
loadToken(true)
|
|
@@ -1458,7 +1647,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1458
1647
|
React.createElement('span', null, t('compat.inject.domains')),
|
|
1459
1648
|
React.createElement('span', { className: 'dsm-seg-group-line' })),
|
|
1460
1649
|
React.createElement('div', { className: 'dsm-inject-domains' },
|
|
1461
|
-
['memory', 'mcp', 'skills', 'subagents', 'prompt'].map(function (key) {
|
|
1650
|
+
['scene', 'memory', 'mcp', 'skills', 'subagents', 'prompt'].map(function (key) {
|
|
1462
1651
|
// 技能与提示词两域在标准类预设下由宿主送、本插件让位 —— 关掉它们时本插件会连
|
|
1463
1652
|
// 宿主那条一起拦下(见 context-inject.ts 的 officialKindsToSuppress)。这里给
|
|
1464
1653
|
// 那两项一句 title,免得用户以为"关了没用"。
|
|
@@ -1472,6 +1661,78 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1472
1661
|
React.createElement('span', null, t('compat.inject.domain.' + key)))
|
|
1473
1662
|
})))))
|
|
1474
1663
|
}
|
|
1664
|
+
// 模型工具表:与「注入」并列的第二种省法 —— 那边省正文,这边省工具 schema。
|
|
1665
|
+
// 工具表**每个请求都发一遍**(哪怕这一轮用不上),所以关掉的工具是整份不进请求,
|
|
1666
|
+
// 这是本插件省 token 最实在的一处;代价是模型调不到它(面板不受影响)。
|
|
1667
|
+
// 排版(用户 2026-09-23 指出太乱):**一行一个工具**、四列固定宽度 —— 勾选框 / 名字 /
|
|
1668
|
+
// ≈tok / 一句话介绍。挤成一排的小块每行宽度都不一样,竖着扫不出任何一列。
|
|
1669
|
+
// 文字两档:行内那句是"干什么用的"(统一动词开头),悬停给稍详细的一句(含边界与代价)。
|
|
1670
|
+
// 两档都按工具名去词典取,取不到就让那一格空着(`translateOrFallback`),
|
|
1671
|
+
// 绝不把 `compat.tools.about.x` 这种键名当文案显示出来。
|
|
1672
|
+
if (toolTable) {
|
|
1673
|
+
const report = toolTable.report
|
|
1674
|
+
const hidden = toolTable.hidden || []
|
|
1675
|
+
const groups = report.groups || []
|
|
1676
|
+
const groupLabel = function (key) { return key === 'other' ? t('compat.tools.other') : t('compat.inject.domain.' + key) }
|
|
1677
|
+
const rows = []
|
|
1678
|
+
groups.forEach(function (group) {
|
|
1679
|
+
const onCount = group.tools.filter(function (tool) { return !tool.hidden }).length
|
|
1680
|
+
rows.push(React.createElement('div', { className: 'dsm-tooltable-group', key: 'g-' + group.key },
|
|
1681
|
+
React.createElement('label', { className: 'dsm-tooltable-ghead' },
|
|
1682
|
+
React.createElement('input', {
|
|
1683
|
+
type: 'checkbox',
|
|
1684
|
+
checked: onCount === group.tools.length,
|
|
1685
|
+
// 半开半关时显示成"未全选",点一下 = 全开(与其余全选控件同款:勾选框只表达
|
|
1686
|
+
// 两种意思,第三种状态靠计数说清)。
|
|
1687
|
+
onChange: function () { toggleToolTableGroup(group, onCount !== group.tools.length) },
|
|
1688
|
+
}),
|
|
1689
|
+
React.createElement('span', { className: 'dsm-tooltable-gname' }, groupLabel(group.key)),
|
|
1690
|
+
React.createElement('span', { className: 'dsm-tooltable-gcount' }, t('compat.tools.groupCount', { on: onCount, total: group.tools.length, tok: group.tok }))),
|
|
1691
|
+
React.createElement('div', { className: 'dsm-tooltable-tools' },
|
|
1692
|
+
group.tools.map(function (tool) {
|
|
1693
|
+
const about = translateOrFallback(t, 'compat.tools.about.' + tool.name, '')
|
|
1694
|
+
// 悬停 = 稍详细那句 + 空行 + 当前状态。取不到详细那句时退回行内那句,
|
|
1695
|
+
// 于是"悬停什么也没有"不会发生。
|
|
1696
|
+
const hover = translateOrFallback(t, 'compat.tools.hover.' + tool.name, about)
|
|
1697
|
+
return React.createElement('label', {
|
|
1698
|
+
className: 'dsm-tooltable-tool',
|
|
1699
|
+
key: tool.name,
|
|
1700
|
+
title: (hover ? hover + '\n\n' : '') + (tool.hidden
|
|
1701
|
+
? (tool.defaultHidden ? t('compat.tools.defaultOffTitle') : t('compat.tools.offTitle'))
|
|
1702
|
+
: t('compat.tools.onTitle')),
|
|
1703
|
+
},
|
|
1704
|
+
React.createElement('input', { type: 'checkbox', checked: !tool.hidden, onChange: function () { toggleToolTableTool(tool.name, !!tool.hidden) } }),
|
|
1705
|
+
React.createElement('span', { className: 'dsm-tooltable-name' }, tool.name),
|
|
1706
|
+
React.createElement('span', { className: 'dsm-tooltable-tok' }, '≈' + tool.tok),
|
|
1707
|
+
React.createElement('span', { className: 'dsm-tooltable-about' }, about))
|
|
1708
|
+
}))))
|
|
1709
|
+
})
|
|
1710
|
+
const presetCount = (toolTable.presets || []).length
|
|
1711
|
+
push(section(t('compat.tools'), t('compat.tools.hint'),
|
|
1712
|
+
React.createElement('div', { className: 'dsm-tooltable' + (toolTableBusy ? ' dsm-tooltable-busy' : '') },
|
|
1713
|
+
React.createElement('div', { className: 'dsm-tooltable-summary' },
|
|
1714
|
+
React.createElement('span', null, hidden.length === 0
|
|
1715
|
+
? t('compat.tools.summaryAll', { total: report.totalCount, tok: report.totalTok })
|
|
1716
|
+
: t('compat.tools.summary', { on: report.visibleCount, total: report.totalCount, tok: report.visibleTok, saved: report.hiddenTok })),
|
|
1717
|
+
// 两颗按钮钉在合计那一行的最右(用户 2026-09-23 指定位置):方案管的是**整块**
|
|
1718
|
+
// 勾选,不属于任何一个域,所以既不放分组头也不放表尾。
|
|
1719
|
+
React.createElement('span', { className: 'dsm-tooltable-preset-actions' },
|
|
1720
|
+
React.createElement('button', {
|
|
1721
|
+
type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: toolTableBusy,
|
|
1722
|
+
title: t('compat.tools.preset.save.tip'),
|
|
1723
|
+
onClick: function () { setToolPreset({ mode: 'save', name: '', error: '' }) },
|
|
1724
|
+
}, t('compat.tools.preset.save')),
|
|
1725
|
+
React.createElement('button', {
|
|
1726
|
+
type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: toolTableBusy,
|
|
1727
|
+
title: presetCount ? t('compat.tools.preset.apply.tip') : t('compat.tools.preset.apply.tipEmpty'),
|
|
1728
|
+
onClick: function () { setToolPreset({ mode: 'apply' }) },
|
|
1729
|
+
}, t('compat.tools.preset.apply')))),
|
|
1730
|
+
// 合计里拆一句"出厂关的":不然第一句那个"省下 ≈2,679"会被读成用户自己关出来的。
|
|
1731
|
+
report.defaultHiddenCount > 0
|
|
1732
|
+
? React.createElement('div', { className: 'dsm-tooltable-sub' }, t('compat.tools.summaryDefault', { n: report.defaultHiddenCount, tok: report.defaultHiddenTok }))
|
|
1733
|
+
: null,
|
|
1734
|
+
rows)))
|
|
1735
|
+
}
|
|
1475
1736
|
// 注入实况:紧挨「注入」设置块下方。数据来自 `injection-live`(宿主读最近活跃会话的
|
|
1476
1737
|
// 可见表面),是"回放"不是"重算"—— 界面显示已注入而模型实际没收到时(场景接管、
|
|
1477
1738
|
// 压制型预设、开关被关、压缩后还没补发),这里一眼可见。
|
|
@@ -1537,17 +1798,30 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1537
1798
|
const deliveredText = delivered.count > 0
|
|
1538
1799
|
? t('compat.live.delivered', { count: delivered.count, time: new Date(delivered.lastAt).toTimeString().slice(0, 5) })
|
|
1539
1800
|
: t('compat.live.never')
|
|
1540
|
-
//
|
|
1801
|
+
// 观测面:这段对话里到底看到过几次本插件的工具调用。必须显示 —— 采纳全是 0 时,
|
|
1541
1802
|
// 要能分清"模型真的没用"(结论)和"遥测没接上"(故障)。
|
|
1542
1803
|
const observedCalls = Number((live.observed || {}).toolCalls) || 0
|
|
1543
1804
|
const observedText = observedCalls > 0
|
|
1544
1805
|
? t('compat.live.observe.some', { count: observedCalls })
|
|
1545
1806
|
: t('compat.live.observe.none')
|
|
1807
|
+
// 成本一行:总量 / 最费的域 / 重发最多的域。只在**真投过东西**时出现 ——
|
|
1808
|
+
// 全 0 时摆一行「累计约 0 B」是噪声(与上面 adoptText 的取向一致:没投过的域不报统计)。
|
|
1809
|
+
// 字节数是「当前正文 × 投递次数」的近似(服务端已注明),所以句子带「约」。
|
|
1810
|
+
const cost = live.cost || {}
|
|
1811
|
+
const costName = function (entry) { return entry ? (domainLabel(entry.key) || String(entry.label || '')) : '' }
|
|
1812
|
+
const costBytes = Number(cost.injectedBytes) || 0
|
|
1813
|
+
const costText = costBytes > 0 && cost.topDomain
|
|
1814
|
+
? t('compat.live.cost', {
|
|
1815
|
+
size: fmtSize(costBytes),
|
|
1816
|
+
domain: costName(cost.topDomain),
|
|
1817
|
+
resent: costName(cost.mostDelivered),
|
|
1818
|
+
})
|
|
1819
|
+
: ''
|
|
1546
1820
|
const canCopy = (live.domains || []).some(hasText)
|
|
1547
1821
|
push(React.createElement('section', { className: 'dsm-compat-section' },
|
|
1548
1822
|
React.createElement('div', { className: 'dsm-compat-section-head' },
|
|
1549
1823
|
React.createElement('h3', { className: 'dsm-compat-section-title' }, t('compat.live.title')),
|
|
1550
|
-
React.createElement('span', { className: 'dsm-compat-section-hint' }, t('compat.live.hint') + ' · ' + deliveredText + ' · ' + observedText),
|
|
1824
|
+
React.createElement('span', { className: 'dsm-compat-section-hint' }, t('compat.live.hint') + ' · ' + deliveredText + ' · ' + observedText + (costText ? ' · ' + costText : '')),
|
|
1551
1825
|
React.createElement('span', { className: 'dsm-inject-live-actions' },
|
|
1552
1826
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: !canCopy, onClick: copyAll },
|
|
1553
1827
|
liveCopied ? t('compat.live.copied') : t('compat.live.copy')))),
|
|
@@ -1777,9 +2051,10 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1777
2051
|
React.createElement('span', { className: 'dsm-compat-name dsm-reach-name' },
|
|
1778
2052
|
React.createElement('span', null, (row.name || row.presetId) + (row.isDefault ? ' · ' + t('compat.reach.default') : '')),
|
|
1779
2053
|
tags.length ? React.createElement('span', { className: 'dsm-tags' }, tags) : null),
|
|
1780
|
-
// 芯片顺序与「注入」块的勾选顺序一致(用户裁定 2026-09-16
|
|
1781
|
-
//
|
|
2054
|
+
// 芯片顺序与「注入」块的勾选顺序一致(用户裁定 2026-09-16;2026-09-23 起
|
|
2055
|
+
// 场景与记忆是两个域,各占一枚):场景 → 记忆 → MCP → 技能 → 子智能体 → 提示词。
|
|
1782
2056
|
React.createElement('span', { className: 'dsm-compat-reach-marks' },
|
|
2057
|
+
chip(t('compat.reach.mark.scene'), row.scene),
|
|
1783
2058
|
chip(t('compat.reach.mark.memory'), row.memory),
|
|
1784
2059
|
chip(t('compat.reach.mark.mcp'), row.mcp),
|
|
1785
2060
|
chip(t('compat.reach.mark.skill'), row.skillCatalog),
|
|
@@ -2012,6 +2287,107 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2012
2287
|
React.createElement('div', { className: 'dsm-modal-body' }, rows)))
|
|
2013
2288
|
}
|
|
2014
2289
|
|
|
2290
|
+
// 「方案」弹窗(保存与恢复共用一颗入口、两种 mode):结构与令牌 / 备份清理弹窗同款 ——
|
|
2291
|
+
// 遮罩点击关闭 + 头部标题与关闭键 + body + 底部动作行,Esc 与焦点陷阱复用模块作用域助手。
|
|
2292
|
+
// 为什么恢复要能删:方案是试出来的一旦攒多了就没人在清单里认得出哪个是哪个,
|
|
2293
|
+
// 不给出口就只能到磁盘上改文件(用户 2026-09-23 明确要"在恢复弹窗里也能删")。
|
|
2294
|
+
let toolPresetModal = null
|
|
2295
|
+
if (toolPreset) {
|
|
2296
|
+
const closePreset = function () { setToolPreset(null) }
|
|
2297
|
+
const presetBusy = !!toolPreset.busy || toolTableBusy
|
|
2298
|
+
const presetRows = []
|
|
2299
|
+
if (toolPreset.mode === 'save') {
|
|
2300
|
+
const report0 = (toolTable && toolTable.report) || {}
|
|
2301
|
+
const nameDraft = String(toolPreset.name || '')
|
|
2302
|
+
presetRows.push(React.createElement('p', { className: 'dsm-help', key: 'hint' },
|
|
2303
|
+
t('compat.tools.preset.saveHint', {
|
|
2304
|
+
on: report0.visibleCount || 0, total: report0.totalCount || 0, tok: report0.visibleTok || 0,
|
|
2305
|
+
})))
|
|
2306
|
+
presetRows.push(React.createElement('label', { className: 'dsm-field', key: 'name' },
|
|
2307
|
+
React.createElement('span', { className: 'dsm-label' }, t('compat.tools.preset.name')),
|
|
2308
|
+
React.createElement('input', {
|
|
2309
|
+
ref: toolPresetInputRef, className: 'dsm-control', type: 'text',
|
|
2310
|
+
maxLength: 40, value: nameDraft, placeholder: t('compat.tools.preset.namePlaceholder'),
|
|
2311
|
+
onChange: function (e) { setToolPreset(Object.assign({}, toolPreset, { name: e.target.value, error: '' })) },
|
|
2312
|
+
onKeyDown: function (e) { if (e.key === 'Enter') submitToolPreset() },
|
|
2313
|
+
})))
|
|
2314
|
+
if (toolPreset.error) {
|
|
2315
|
+
presetRows.push(React.createElement('p', { className: 'dsm-feedback dsm-warning', role: 'alert', key: 'err' }, toolPreset.error))
|
|
2316
|
+
}
|
|
2317
|
+
presetRows.push(React.createElement('div', { className: 'dsm-modal-actions', key: 'act' },
|
|
2318
|
+
React.createElement('button', {
|
|
2319
|
+
type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: presetBusy, onClick: closePreset,
|
|
2320
|
+
}, t('btn.cancel')),
|
|
2321
|
+
React.createElement('button', {
|
|
2322
|
+
type: 'button', className: 'dsm-btn', disabled: presetBusy || !nameDraft.trim(), onClick: submitToolPreset,
|
|
2323
|
+
}, t('compat.tools.preset.saveYes'))))
|
|
2324
|
+
} else {
|
|
2325
|
+
const tokOf = toolTokByName()
|
|
2326
|
+
const rep = (toolTable && toolTable.report) || {}
|
|
2327
|
+
const total = rep.totalCount || 0
|
|
2328
|
+
const totalTok = rep.totalTok || 0
|
|
2329
|
+
const entries = [{
|
|
2330
|
+
key: '', name: t('compat.tools.preset.default'), builtin: true,
|
|
2331
|
+
hidden: (toolTable && toolTable.defaultHidden) || [],
|
|
2332
|
+
}]
|
|
2333
|
+
const storedPresets = (toolTable && toolTable.presets) || []
|
|
2334
|
+
storedPresets.forEach(function (p) {
|
|
2335
|
+
// `builtin` 必须显式给 false:数组类型由首元素(出厂默认,builtin: true)推断,
|
|
2336
|
+
// 漏了它 typecheck:client 会红;运行时 `e.builtin ? 删除键 : null` 依赖这个假值。
|
|
2337
|
+
entries.push({ key: p.name, name: p.name, builtin: false, hidden: p.hidden || [] })
|
|
2338
|
+
})
|
|
2339
|
+
presetRows.push(React.createElement('p', { className: 'dsm-help', key: 'hint' }, t('compat.tools.preset.applyHint')))
|
|
2340
|
+
presetRows.push(React.createElement('div', { className: 'dsm-preset-list', key: 'list' },
|
|
2341
|
+
entries.map(function (e) {
|
|
2342
|
+
// 只数**注册表里真有的**名字:方案可能存自更早的版本(那条工具已经不叫这个了),
|
|
2343
|
+
// 把陌生名字算进"关着的"会让这一行报出一个假的合计。应用时服务端本来也会把这些
|
|
2344
|
+
// 名字滤掉(`tool-table` 的 set 路径),所以这里显示的与真会生效的是同一份。
|
|
2345
|
+
const known = e.hidden.filter(function (n) { return tokOf[n] !== undefined })
|
|
2346
|
+
let offTok = 0
|
|
2347
|
+
known.forEach(function (n) { offTok += tokOf[n] })
|
|
2348
|
+
return React.createElement('div', { className: 'dsm-preset-row', key: e.key },
|
|
2349
|
+
React.createElement('span', { className: 'dsm-preset-name' },
|
|
2350
|
+
e.name,
|
|
2351
|
+
e.builtin ? React.createElement('span', { className: 'dsm-preset-tag' }, t('compat.tools.preset.builtin')) : null),
|
|
2352
|
+
React.createElement('span', { className: 'dsm-preset-meta' }, t('compat.tools.preset.count', {
|
|
2353
|
+
on: total - known.length, total: total, tok: totalTok - offTok,
|
|
2354
|
+
})),
|
|
2355
|
+
React.createElement('button', {
|
|
2356
|
+
type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: presetBusy,
|
|
2357
|
+
onClick: function () { applyToolPreset(e.hidden) },
|
|
2358
|
+
}, t('compat.tools.preset.use')),
|
|
2359
|
+
// 出厂默认不在文件里(它是代码里那份名单),所以没有删除键 —— 删它没有意义,
|
|
2360
|
+
// 下次读盘它还会回来;能删的只有用户自己存的那几份。
|
|
2361
|
+
e.builtin ? null : React.createElement('button', {
|
|
2362
|
+
type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: presetBusy,
|
|
2363
|
+
title: t('compat.tools.preset.removeTitle', { name: e.name }),
|
|
2364
|
+
onClick: function () { deleteToolPreset(e.name) },
|
|
2365
|
+
}, t('btn.custom.remove')))
|
|
2366
|
+
})))
|
|
2367
|
+
if (toolPreset.error) {
|
|
2368
|
+
presetRows.push(React.createElement('p', { className: 'dsm-feedback dsm-warning', role: 'alert', key: 'err' }, toolPreset.error))
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
toolPresetModal = React.createElement('div', {
|
|
2372
|
+
key: 'preset-modal',
|
|
2373
|
+
className: 'dsm-mask',
|
|
2374
|
+
onMouseDown: function (e) { if (e.target === e.currentTarget) closePreset() },
|
|
2375
|
+
}, React.createElement('div', {
|
|
2376
|
+
ref: toolPresetDialogRef, tabIndex: -1,
|
|
2377
|
+
className: 'dsm-modal dsm-modal-sm', role: 'dialog', 'aria-modal': 'true',
|
|
2378
|
+
'aria-labelledby': 'dsm-preset-title',
|
|
2379
|
+
onKeyDown: function (e) { if (!handleModalEscape(e, closePreset)) trapModalFocus(e.currentTarget, e) },
|
|
2380
|
+
},
|
|
2381
|
+
React.createElement('div', { className: 'dsm-modal-head-wrap' },
|
|
2382
|
+
React.createElement('div', { className: 'dsm-modal-head' },
|
|
2383
|
+
React.createElement('h3', { className: 'dsm-modal-title', id: 'dsm-preset-title' },
|
|
2384
|
+
t(toolPreset.mode === 'save' ? 'compat.tools.preset.saveTitle' : 'compat.tools.preset.applyTitle')),
|
|
2385
|
+
React.createElement('button', {
|
|
2386
|
+
type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: closePreset,
|
|
2387
|
+
}, t('btn.close')))),
|
|
2388
|
+
React.createElement('div', { className: 'dsm-modal-body' }, presetRows)))
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2015
2391
|
return React.createElement('div', { className: 'dsm-compat' },
|
|
2016
2392
|
React.createElement('div', { className: 'dsm-head' },
|
|
2017
2393
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
@@ -2033,7 +2409,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2033
2409
|
}, backups && backups.total ? t('compat.backups.btnCount', { count: backups.total }) : t('compat.backups.btn'))),
|
|
2034
2410
|
body,
|
|
2035
2411
|
backupModal,
|
|
2036
|
-
tokenModalEl
|
|
2412
|
+
tokenModalEl,
|
|
2413
|
+
toolPresetModal)
|
|
2037
2414
|
}
|
|
2038
2415
|
|
|
2039
2416
|
// 曾经把导出写成 apply 方法体的最后两条语句(`module.exports.DICT = ...` /
|
|
@@ -2073,7 +2450,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2073
2450
|
"empty.search": "没有匹配的技能", "empty.source": "该来源不存在或暂无技能", "loading": "正在加载技能…", "note.missing": "未提供简介", "source.toggle": "启停来源", "skill.toggle": "启停技能",
|
|
2074
2451
|
"source.external.note": "只读接入,启停不改写源文件", "source.dsh.note": "可创建、导入、移到回收站;默认来源必须读取,不能移除或停用",
|
|
2075
2452
|
"detail.title": "技能详情", "detail.body": "正文", "detail.frontmatter": "元数据", "detail.noFrontmatter": "该技能未提供元数据。", "detail.diagnostics": "诊断", "detail.path": "源文件", "detail.noIssues": "未发现诊断问题。",
|
|
2076
|
-
"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": "对话里
|
|
2453
|
+
"create.title": "创建技能", "create.target": "创建位置", "create.name": "名称", "create.name.placeholder": "例如 code-review-helper", "create.description": "简介", "create.description.placeholder": "一句话说明什么时候使用", "create.body": "正文(Markdown)", "create.body.placeholder": "写下技能要遵循的指令、步骤和边界…", "create.chat.note": "对话里 skill_manager_save 建的是用户级技能",
|
|
2077
2454
|
"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 = 一条记忆,文件名即记忆名", "upload.requirement.memory.3": "带 SKILL.md 的目录 = bundle 记忆(附件一起进来)", "upload.requirement.memory.4": "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}",
|
|
2078
2455
|
// F-029:服务端错误码补词条(zh 逐字复现服务端文案,命中词条前后中文显示不变;EN 不再回退中文原文)。
|
|
2079
2456
|
"error.custom.absolute": "目录路径必须是绝对路径: {path}", "error.custom.notDirectory": "不是目录: {path}", "error.custom.unreadable": "无法读取目录: {path}", "error.custom.overlap": "目录与已有技能来源重叠: {path} ↔ {other}", "error.custom.invalidKey": "非法的自定义来源 key: {key}", "error.custom.notFound": "自定义来源不存在: {key}",
|
|
@@ -2131,6 +2508,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2131
2508
|
"mcp.form.err.serverName": "服务名需为 1-32 位字母、数字、下划线或短横线(不能有空格与中文)", "mcp.form.err.url": "地址需以 http:// 或 https:// 开头", "mcp.form.err.command": "命令不能为空",
|
|
2132
2509
|
"mcp.form.maskedHint": "密钥框里的打码值(如 ••••••)不是原文:直接保存不会改动它们,插件会保留补丁文件里的原值。要看原文请点「显示密钥」(本插件配了访问令牌的话,先填对令牌)。",
|
|
2133
2510
|
"mcp.field.serverName": "服务名称 serverName", "mcp.field.serverName.hint": "1-32 位 [A-Za-z0-9_-],补丁里按此名注册",
|
|
2511
|
+
"mcp.field.note": "备注(可选)", "mcp.field.note.placeholder": "给模型看的决策提示,比如「这台要 sudo 免密」;留空不写",
|
|
2134
2512
|
"mcp.field.transport": "传输方式", "mcp.field.level": "级别",
|
|
2135
2513
|
"mcp.field.level.project": "应用级(仅当前应用:{path})", "mcp.field.level.global": "全局(跨应用:{path})",
|
|
2136
2514
|
"mcp.field.url": "服务 URL", "mcp.field.url.hint": "需以 http(s):// 开头",
|
|
@@ -2232,12 +2610,91 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2232
2610
|
"compat.token.mismatch": "与宿主配置的令牌不一致:请填入配置里的那个值。",
|
|
2233
2611
|
"compat.token.failed": "读取令牌状态失败,请刷新后重试。",
|
|
2234
2612
|
"compat.inject.domains": "注入内容",
|
|
2235
|
-
"compat.inject.domain.memory": "
|
|
2613
|
+
"compat.inject.domain.scene": "场景", "compat.inject.domain.memory": "记忆", "compat.inject.domain.mcp": "MCP",
|
|
2236
2614
|
"compat.inject.domain.subagents": "子智能体", "compat.inject.domain.prompt": "提示词",
|
|
2237
2615
|
"compat.inject.domain.skills": "技能",
|
|
2238
2616
|
"compat.inject.domain.carrier.title": "关掉后,系统自带的那份也会一并停掉",
|
|
2617
|
+
"compat.tools": "模型工具表", "compat.tools.hint": "哪些工具发给模型:工具表每一轮都随请求发一遍,关掉的整份不发(≈ 是估算)",
|
|
2618
|
+
"compat.tools.summaryAll": "{total} 个工具全部下发 · ≈{tok} tok/轮",
|
|
2619
|
+
"compat.tools.summary": "下发 {on}/{total} 个 · ≈{tok} tok/轮(关掉的省下 ≈{saved})",
|
|
2620
|
+
"compat.tools.groupCount": "{on}/{total} 个 · ≈{tok}",
|
|
2621
|
+
"compat.tools.other": "其它",
|
|
2622
|
+
"compat.tools.onTitle": "已下发:模型能看到并调用它",
|
|
2623
|
+
"compat.tools.offTitle": "已关掉:整份不进请求,模型看不到也调不到(面板不受影响)",
|
|
2624
|
+
"compat.tools.summaryDefault": "其中 ≈{tok} 是出厂默认关掉的 {n} 条(不需要模型做这些事就不用管;要用在这里勾上,只改本机)",
|
|
2625
|
+
"compat.tools.defaultOffTitle": "出厂默认就是关着的:模型看不到也调不到,面板不受影响。要模型做这件事,在这里勾上即可(只改本机)。",
|
|
2626
|
+
// 「方案」:把一整组勾选存成名字,之后一键换回来(合计行右侧那两颗按钮)。
|
|
2627
|
+
"compat.tools.preset.save": "保存方案",
|
|
2628
|
+
"compat.tools.preset.apply": "恢复方案",
|
|
2629
|
+
"compat.tools.preset.save.tip": "把现在这套勾选存成一个名字,以后一键换回来",
|
|
2630
|
+
"compat.tools.preset.apply.tip": "换到已存的方案(含那条不可删的出厂默认)",
|
|
2631
|
+
"compat.tools.preset.apply.tipEmpty": "还没有存过方案;这里可以先看出厂默认那一套",
|
|
2632
|
+
"compat.tools.preset.saveTitle": "保存当前方案",
|
|
2633
|
+
"compat.tools.preset.applyTitle": "恢复方案",
|
|
2634
|
+
"compat.tools.preset.saveHint": "现在这套是:开着 {on}/{total} 个 · ≈{tok} tok/轮。",
|
|
2635
|
+
"compat.tools.preset.name": "方案名字",
|
|
2636
|
+
"compat.tools.preset.namePlaceholder": "例如:日常最小 / 让它自己配服务器",
|
|
2637
|
+
"compat.tools.preset.saveYes": "存下来",
|
|
2638
|
+
"compat.tools.preset.applyHint": "选一份应用:立刻按它重排发给模型的工具表,面板不受影响。",
|
|
2639
|
+
"compat.tools.preset.default": "出厂默认",
|
|
2640
|
+
"compat.tools.preset.builtin": "不可删",
|
|
2641
|
+
"compat.tools.preset.count": "开着 {on}/{total} · ≈{tok} tok/轮",
|
|
2642
|
+
"compat.tools.preset.use": "应用这一份",
|
|
2643
|
+
"compat.tools.preset.removeTitle": "删掉方案「{name}」(只删这条记录,不改当前的勾选)",
|
|
2644
|
+
"compat.tools.preset.err.nameEmpty": "名字不能是空的",
|
|
2645
|
+
"compat.tools.preset.err.nameTaken": "已经有叫「{name}」的方案了 —— 保存不会覆盖它,换个名字(或先删掉那份)",
|
|
2646
|
+
"compat.tools.preset.err.limit": "方案最多存 {max} 份,先删一份再存",
|
|
2647
|
+
"compat.tools.preset.err.notFound": "那份方案已经不在了(可能在别处删掉了)",
|
|
2648
|
+
"compat.tools.preset.err.net": "没存上:与本机服务没接上",
|
|
2649
|
+
// 每个工具两档文案(行内一句 / 悬停稍详细)。格式统一:行内一律动词开头、不带句号,
|
|
2650
|
+
// 而且是**这个工具真能做的事**的缩写;一个工具能做几件事时用 `/` 分隔,**不用「或」**
|
|
2651
|
+
// —— 「或」读起来像"二选一",而这些工具是真能都做的(`save` 既是新建也是修改,
|
|
2652
|
+
// `switch` 既能开也能关)。0.14.x 之前写成「保存一条记忆」这类带量词的说法,读起来
|
|
2653
|
+
// 只像"新建",与 upsert 的实际行为对不上(用户 2026-09-23 指出)。
|
|
2654
|
+
// 悬停补"什么时候用、会改到什么、边界在哪"。键按工具名拼(`compat.tools.about.<工具名>`),
|
|
2655
|
+
// 工具改名 = 换身份,这两组键要跟着改(取不到时界面留空、不显示键名)。
|
|
2656
|
+
"compat.tools.about.memory_manager_list": "列出记忆与启停",
|
|
2657
|
+
"compat.tools.about.memory_manager_read": "按 id 读记忆正文",
|
|
2658
|
+
"compat.tools.about.memory_manager_switch": "启用/停用记忆",
|
|
2659
|
+
"compat.tools.about.memory_manager_save": "新建/修改记忆",
|
|
2660
|
+
"compat.tools.about.scene_manager_list": "列出场景、档案与启停",
|
|
2661
|
+
"compat.tools.about.scene_manager_switch": "进入/退出场景",
|
|
2662
|
+
"compat.tools.about.scene_manager_save": "新建/修改场景、写档案、绑提示词",
|
|
2663
|
+
"compat.tools.about.mcp_manager_list": "列出服务器与工具",
|
|
2664
|
+
"compat.tools.about.mcp_manager_switch": "启停/重启服务器",
|
|
2665
|
+
"compat.tools.about.mcp_manager_save": "新增/修改服务器",
|
|
2666
|
+
"compat.tools.about.skill_manager_list": "列出技能、启停与路径",
|
|
2667
|
+
"compat.tools.about.skill_manager_read": "按名字读技能正文",
|
|
2668
|
+
"compat.tools.about.skill_manager_switch": "启用/停用技能或技能目录",
|
|
2669
|
+
"compat.tools.about.skill_manager_save": "新建/修改技能",
|
|
2670
|
+
"compat.tools.about.subagent_manager_list": "列出人设与描述",
|
|
2671
|
+
"compat.tools.about.subagent_manager_run": "按人设派子智能体",
|
|
2672
|
+
"compat.tools.about.subagent_manager_switch": "启用/停用人设",
|
|
2673
|
+
"compat.tools.about.subagent_manager_save": "新建/修改人设",
|
|
2674
|
+
"compat.tools.about.prompt_manager_list": "列出提示词预设",
|
|
2675
|
+
"compat.tools.about.prompt_manager_apply": "应用一份提示词预设",
|
|
2676
|
+
"compat.tools.hover.memory_manager_list": "列出记忆的 id、所属场景、启停与一句话描述,并标明注入按哪个场景走。默认只列这次真的会注入的记忆;all=true 会把停用、场景未启用、被同名覆盖的一起列出来。场景本身(档案、绑定的预设、锁没锁)在 scene_manager_list。只看不改。",
|
|
2677
|
+
"compat.tools.hover.memory_manager_read": "按 id(<场景>/<名字>)读一条记忆的完整正文。清单里只有一句话描述,要看正文就用它 —— 尤其是被停用、场景没启用、或被体积预算挤掉的记忆。",
|
|
2678
|
+
"compat.tools.hover.memory_manager_switch": "只拨索引里的开关,.md 文件一个字不动,随时能拨回来。停用的记忆不再进上下文,等于把注入预算让给别人 —— 想省 token 优先用它,而不是删。",
|
|
2679
|
+
"compat.tools.hover.memory_manager_save": "新建一条记忆,或改掉已经存在的那条同名记忆 —— 回执会说清走了哪条。记忆会进之后每一轮请求,所以只记从代码、配置、git 推不出来的事;建之前先看清单里有没有已经讲了同一件事的。没传的字段保持原样;改名会把旧文件留在回收站。",
|
|
2680
|
+
"compat.tools.hover.scene_manager_list": "列出每个场景:场景名(界面显示名不同时会一并标出 —— 工具要的是前者)、启用 / 已进入 / 已锁定、场景说明、绑定的预设、记忆条数,以及档案:进入它时会开哪些 MCP / 技能 / 人设(没勾的一律关掉)。要改某个场景的档案之前先看它 —— 保存是整段替换,看不到现有勾选就会把原来勾的挤掉;要进入哪个场景也在这一份里挑。只看不改。",
|
|
2681
|
+
"compat.tools.hover.scene_manager_switch": "进入一个场景:按它的档案把 MCP / 技能 / 人设整套拨到位(没勾的一律关掉),注入范围收窄成它的记忆,并把场景绑定的预设正文写进全局 AGENTS.md。退出则按进场景前的快照精确还原,基线也恢复。切之前先用 scene_manager_list 看一眼那个场景的档案。改的是整个运行时环境,所以默认要用户确认。",
|
|
2682
|
+
"compat.tools.hover.scene_manager_save": "建一个场景,或改掉已有的那个 —— 回执会说清走了哪条。场景是「进入时按它切换」的一套:档案里勾了什么,进入后就开什么,其余全部关掉。只写档案不会让场景生效 —— 要生效得进入它(scene_manager_switch,或你在「场景」页点)。没传的段保持原样;传空段 = 全不勾(不是「不限制」)。改一个段是整段替换,先用 scene_manager_list 看现有勾选,回执会列出被挤掉的项。",
|
|
2683
|
+
"compat.tools.hover.mcp_manager_list": "列出配好的服务器、连没连上、各自有哪些工具,以及你写给这台服务器的备注。曾经连上过、现在连不上的会标出来 —— 那说的是「这台有问题」,不是「没配」。tools=true 会把每台的工具名也列出来,被单独关掉的标 (off);整台停用的服务器只有上次运行记下的名单。",
|
|
2684
|
+
"compat.tools.hover.mcp_manager_switch": "action=on/off 启用或停用整台服务器;给了 tool 就只动那一个工具(停用的工具会从模型可见的工具表里消失,执行侧也拦住,重启 DSH 后仍生效);action=restart 把服务器停用、等它断开、再按原状态启用 —— 改了配置或服务器卡住时用它。同名服务器在项目层与全局层各有一台时,要传 level 指定动哪台。",
|
|
2685
|
+
"compat.tools.hover.mcp_manager_save": "新增一台服务器(streamable-http 或 stdio),或改掉同名的那台 —— 回执会说清走了哪条。改的时候**没传的字段保持原样**,所以改一个 URL 不必重述整条配置(密钥也不会因此丢);stdio 的 command+args 就是宿主真正会 spawn 的命令,改它会弹确认卡。只在用户明确要求时用。",
|
|
2686
|
+
"compat.tools.hover.skill_manager_list": "列出技能的启停、生效还是被同名覆盖、源文件路径。常驻目录只带名字和摘要,所以「我怎么关了它」「同名哪份生效」要看这个;all=true 连停用与影子副本一起列。",
|
|
2687
|
+
"compat.tools.hover.skill_manager_read": "按名字读某个技能的 SKILL.md 正文(目录里只有名字和摘要)。同名多份时取真正生效的那一份;要特意看某个被覆盖的副本就传 root。会话里已有官方 skill 工具时这条不下发。",
|
|
2688
|
+
"compat.tools.hover.skill_manager_switch": "按技能名或按来源目录启停。停用的技能不进目录也不能被加载;同名冲突时只有生效的那份有用 —— 启错了副本等于没启。",
|
|
2689
|
+
"compat.tools.hover.skill_manager_save": "新建一个技能,或改掉本插件自己写的那份同名技能 —— 回执会说清走了哪条。落点是 hub 的 tool-management/skills/(与界面「创建技能」一致),新建的默认停用、要用得先启用;官方根里的技能不改。技能目录会进每一轮上下文,描述要写清「什么时候用它」。",
|
|
2690
|
+
"compat.tools.hover.subagent_manager_list": "列出可用人设(子智能体的角色定义)与各自的描述。常驻目录给的是同一份清单;这个工具拿到的永远是当前完整版,包括目录没注入到的会话。",
|
|
2691
|
+
"compat.tools.hover.subagent_manager_run": "把任务交给某个具名人设:它拿到人设当自己的系统提示词、在自己的上下文里做完、只回最终结果。开 inherit 它会看到本次会话已完成的对话。花真 token,默认要用户确认。",
|
|
2692
|
+
"compat.tools.hover.subagent_manager_switch": "停用的人设不进目录也不能被委派;被场景绑定的人设,在进入该场景时会自动启用。",
|
|
2693
|
+
"compat.tools.hover.subagent_manager_save": "新建人设,或改掉已存在的那份 —— 回执会说清走了哪条。可改名字、描述、正文(正文会作为子代理的系统提示词)与输出要求,没传的字段保持原样;改名会连带改场景与档案里的绑定。provider / 模型 / 工具限制只留给面板(模型看不到自己改对了没有,所以不给)。描述决定模型什么时候会选它。",
|
|
2694
|
+
"compat.tools.hover.prompt_manager_list": "列出提示词预设与摘要,标明当前生效的是哪一份 —— 同时只有一份生效。要换用哪份之前先用它确认名字。",
|
|
2695
|
+
"compat.tools.hover.prompt_manager_apply": "把某份预设应用到用户级 AGENTS.md,之后的对话立刻按它走。一次只有一份在生效,换一份就是替换上一份。",
|
|
2239
2696
|
"compat.live.title": "注入实况", "compat.live.hint": "模型实际看到的内容(只读)",
|
|
2240
|
-
"compat.live.never": "
|
|
2697
|
+
"compat.live.never": "这段对话还没投过", "compat.live.delivered": "本段对话已投递 {count} 条 · 最近 {time}",
|
|
2241
2698
|
"compat.live.copy": "复制全文", "compat.live.copied": "已复制", "compat.live.noAgent": "还没有会话:发一条消息后再来看",
|
|
2242
2699
|
"compat.live.state.inContext": "已注入", "compat.live.state.absent": "未投递", "compat.live.state.cleared": "已清空",
|
|
2243
2700
|
"compat.live.state.off": "已关闭", "compat.live.state.unknown": "没有会话",
|
|
@@ -2245,18 +2702,19 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2245
2702
|
"compat.live.state.child.title": "本会话的深度超过了人设的目录注入深度(catalogDepth),所以常驻目录不在这里注入。要让目录出现在子会话,把对应人设的「目录注入」调大(或选「不限制嵌套」)。注意:这不影响委派 —— 子代理始终可以继续委派。",
|
|
2246
2703
|
"compat.live.adopt": "注入 {injected} 次 · 调用 {used} 次 · 采纳 {adopted} 次",
|
|
2247
2704
|
"compat.live.adopt.none": "注入 {injected} 次 · 从未调用",
|
|
2248
|
-
"compat.live.adopt.title": "
|
|
2249
|
-
"compat.live.observe.some": "
|
|
2250
|
-
"compat.live.observe.none": "
|
|
2705
|
+
"compat.live.adopt.title": "只数**最近这一段对话**(换一段重新数,重启归零)。采纳 = 调用发生时该域正文正在这个会话的上下文里;调用次数不区分成功失败",
|
|
2706
|
+
"compat.live.observe.some": "这段对话观测到 {count} 次调用",
|
|
2707
|
+
"compat.live.observe.none": "这段对话还没观测到调用",
|
|
2708
|
+
"compat.live.cost": "本段对话累计约 {size} · 最费 {domain} · 重发最多 {resent}",
|
|
2251
2709
|
"compat.live.state.official": "官方注入", "compat.live.state.official.title": "提示词与技能目录在标准类预设下由官方通道送达,本插件不重复注入(极简这类官方没挂的预设才由本插件兜底)",
|
|
2252
2710
|
"compat.live.state.empty": "无内容", "compat.live.est": "≈{count} token",
|
|
2253
2711
|
"compat.live.state.absent.title": "本插件负责这个域,但当前不在上下文里(可能刚被压缩,下一步会自动补发)",
|
|
2254
|
-
"compat.reach.mark.memory": "
|
|
2712
|
+
"compat.reach.mark.scene": "场景", "compat.reach.mark.memory": "记忆", "compat.reach.mark.agentsMd": "提示词", "compat.reach.mark.skill": "技能", "compat.reach.mark.mcp": "MCP", "compat.reach.mark.subagent": "子智能体",
|
|
2255
2713
|
"compat.reach.default": "默认",
|
|
2256
2714
|
"compat.reach.whyUnknown": "判断不了", "compat.reach.broken": "预设损坏",
|
|
2257
2715
|
"compat.reach.tools.title": "工具能用 ≠ 模型知道",
|
|
2258
2716
|
"compat.reach.tools.1": "工具由本插件注册,任何预设下都能调用;怎么送到模型由预设决定。",
|
|
2259
|
-
"compat.reach.tools.2": "
|
|
2717
|
+
"compat.reach.tools.2": "场景 / 记忆 / MCP / 技能 / 子智能体 / 提示词 —— 由本插件按上面的「注入」块补齐。",
|
|
2260
2718
|
"compat.reach.tools.3": "提示词与技能官方自己也会送;官方有的不重复送,官方缺的(如极简)由本插件补上。",
|
|
2261
2719
|
"compat.hint.doctor": "命令行体检:",
|
|
2262
2720
|
"scenes.title": "场景", "scenes.desc": "管理场景:预设 MCP、技能、子智能体与记忆的组合,一键切换。",
|
|
@@ -2264,7 +2722,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2264
2722
|
"scenes.noDesc": "还没有描述",
|
|
2265
2723
|
"scenes.mode.active": "当前模式:{name}", "scenes.mode.profile": "档案:{parts}", "scenes.mode.noProfile": "这个场景还没有档案",
|
|
2266
2724
|
"scenes.empty": "还没有专门设置的场景;全局记忆不需要场景就能注入,需要切换 MCP / 技能 / 人设时再新建。",
|
|
2267
|
-
"scenes.
|
|
2725
|
+
"scenes.search.placeholder": "搜索场景名或描述", "scenes.empty.search": "没有匹配的场景",
|
|
2268
2726
|
"scenes.profile.mcp": "MCP {count} 台", "scenes.profile.skills": "技能 {count} 个", "scenes.profile.subagents": "子智能体 {count} 个", "scenes.profile.memories": "记忆 {count} 条",
|
|
2269
2727
|
"scenes.mcp.hint": "添加「MCP 工具集」后,勾选要启用的服务器(含未运行的);「选工具」可细化到具体工具。没勾的、以及整段没建的,进场景时一律停用(含停掉正在运行的进程)。",
|
|
2270
2728
|
"scenes.seg.checked": "{checked}/{total} 已勾选",
|
|
@@ -2328,6 +2786,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2328
2786
|
"memory.attach.count.one": "{count} 个附件", "memory.attach.count.other": "{count} 个附件",
|
|
2329
2787
|
"memory.attach.tip": "点击管理附件:{count} 个 · 共 {size} —— {names}",
|
|
2330
2788
|
"memory.attach.tip.empty": "还没有附件——点击添加",
|
|
2789
|
+
"memory.size.tip": "正文 {size}:启用后每一轮对话都要占这么多上下文",
|
|
2331
2790
|
"memory.attach.more": ",另 {count} 个", "memory.attach.sep": "、", "memory.attach.flat": "flat 是单文件,不能带附件", "memory.attach.tooLarge": "附件过大:{name}(上限 {limit} MB)",
|
|
2332
2791
|
"memory.delete.title": "移入回收站?", "memory.delete.desc": "「{name}」将移入回收站,可恢复",
|
|
2333
2792
|
"memory.result.created": "已创建记忆:{name}", "memory.result.updated": "已保存记忆:{name}", "memory.result.removed": "已移入回收站:{name}", "memory.result.toggled": "已更新记忆状态:{name}", "memory.result.active": "已更新启用场景",
|
|
@@ -2353,6 +2812,10 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2353
2812
|
"subagents.field.description": "描述", "subagents.field.description.placeholder": "例如 擅长 Java 后端实现与重构",
|
|
2354
2813
|
"subagents.field.model": "模型", "subagents.field.model.placeholder": "自定义模型 id", "subagents.field.model.hint": "留空继承主会话", "subagents.field.provider": "模型来源", "subagents.field.provider.placeholder": "deepseek", "subagents.field.provider.hint": "「模型来源」与「模型」是一对:只填模型会落在主会话的来源上,跨来源会解析失败。",
|
|
2355
2814
|
"subagents.field.catalogDepth": "目录注入", "subagents.field.catalogDepth.onlyTop": "只在顶层", "subagents.field.catalogDepth.toChild": "顶层和子会话", "subagents.field.catalogDepth.toGrand": "顶层和两层子会话", "subagents.field.catalogDepth.unlimited": "不限制嵌套", "subagents.field.catalogDepth.hint": "决定人设目录注入到哪几层会话。\n只影响目录注入,不影响委派 —— 子代理始终可以继续委派。\n子会话看不到目录时,可用 subagent_manager_list 查询。",
|
|
2815
|
+
// 思考强度(0.14.0)。界面上只留"这一格现在能选什么",不解释机制 ——
|
|
2816
|
+
// 官方语义(改 provider/model 会丢掉继承值、不支持的档位直接拒)记在 CHANGELOG / update.md,
|
|
2817
|
+
// 不搬进表单。
|
|
2818
|
+
"subagents.field.effort": "思考强度", "subagents.effort.default": "默认(模型的默认强度)", "subagents.effort.none": "默认", "subagents.effort.needModel": "当前默认主会话思考强度", "subagents.effort.loading": "正在问该模型支持哪些档位…", "subagents.effort.unavailable": "该模型没有暴露可选档位,默认思考强度", "subagents.effort.hint": "当前可选择多种思考强度", "subagents.effort.dropped": "换模型后「{effort}」不在新模型的档位里,已清空。",
|
|
2356
2819
|
"subagents.model.inherit": "继承主会话(不指定)", "subagents.model.customOption": "自定义 / 目录里没有…",
|
|
2357
2820
|
"subagents.field.modes": "工具限制(按 Agent 预设)",
|
|
2358
2821
|
"subagents.field.modes.hint": "只有当前模式那一行生效,其他模式用该模式的全部工具。",
|
|
@@ -2369,7 +2832,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2369
2832
|
"subagents.legacy.convert": "转换到按模式设置",
|
|
2370
2833
|
"subagents.legacy.hint": "转换会把旧名单搬进你选的模式;不点它,文件里的旧键原样保留(运行时照旧生效)。",
|
|
2371
2834
|
"subagents.tools.filter": "筛选工具名", "subagents.tools.remove": "移除", "subagents.toolFailed": "子代理工具没注册上:{names}({reason})。这几个工具在子智能体流程里用不了;重启 DSH 后仍如此请核对宿主版本。",
|
|
2372
|
-
"subagents.adv.title": "高级选项", "subagents.adv.inherit": "继承主会话", "subagents.adv.summary": "
|
|
2835
|
+
"subagents.adv.title": "高级选项", "subagents.adv.inherit": "继承主会话", "subagents.adv.summary": "模型:{model};思考强度:{effort};目录注入:{depth}", "subagents.adv.summary2": "模式限制:{modes} 项;旧格式:白名单 {allow} / 黑名单 {deny}", "subagents.adv.note": "白名单 = 子代理只能用勾选的工具。\n黑名单 = 除勾选的以外都能用。\nMCP 工具不在候选里:子代理始终能用当前在跑的 MCP。", "subagents.adv.loadFailed": "读取候选数据失败(模型目录 / 工具清单);仍可手动填写。",
|
|
2373
2836
|
"preset.name.standard": "标准模式", "preset.name.ptc": "PTC 模式", "preset.name.minimal": "极简模式", "preset.name.cordis": "创造模式",
|
|
2374
2837
|
"preset.short.standard": "标准", "preset.short.ptc": "PTC", "preset.short.minimal": "极简", "preset.short.cordis": "创造",
|
|
2375
2838
|
"subagents.field.body": "人设提示词", "subagents.field.body.placeholder": "写下这个人设的身份、职责与工作方式…",
|
|
@@ -2387,7 +2850,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2387
2850
|
"import.none": "没有导入任何文件(全部被跳过)", "import.skipped": "已跳过:{items}",
|
|
2388
2851
|
"error.import.noFiles": "没有选择要导入的文件",
|
|
2389
2852
|
"nav.title": "工具",
|
|
2390
|
-
"agm.stat.total": "个提示词", "agm.stat.active": "个生效中", "agm.loading": "加载中…", "agm.empty": "暂无提示词,点「新增提示词」创建", "agm.empty.search": "没有匹配的提示词", "agm.search.placeholder": "搜索提示词 id 或描述", "agm.btn.delete.blocked.refs": "不能删除:{refs}。先改掉引用(换绑提示词 / 退出场景 / 应用别的预设)再删除。", "agm.ref.sep": ";", "agm.ref.scene": "场景「{scene}」绑定了它", "agm.ref.sceneActive": "场景「{scene}」(已启用)绑定了它", "agm.ref.file": "AGENTS.md 当前内容就是它", "agm.ref.restore": "退出场景后要恢复的全局提示词", "agm.sceneLock.notice": "场景「{scene}」接管中:当前生效的是它绑定的「{id}」。在这里应用别的预设 = 把该场景改绑到那一份(会同步写入场景档案)。", "agm.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": "
|
|
2853
|
+
"agm.stat.total": "个提示词", "agm.stat.active": "个生效中", "agm.loading": "加载中…", "agm.empty": "暂无提示词,点「新增提示词」创建", "agm.empty.search": "没有匹配的提示词", "agm.search.placeholder": "搜索提示词 id 或描述", "agm.btn.delete.blocked.refs": "不能删除:{refs}。先改掉引用(换绑提示词 / 退出场景 / 应用别的预设)再删除。", "agm.ref.sep": ";", "agm.ref.scene": "场景「{scene}」绑定了它", "agm.ref.sceneActive": "场景「{scene}」(已启用)绑定了它", "agm.ref.file": "AGENTS.md 当前内容就是它", "agm.ref.restore": "退出场景后要恢复的全局提示词", "agm.sceneLock.notice": "场景「{scene}」接管中:当前生效的是它绑定的「{id}」。在这里应用别的预设 = 把该场景改绑到那一份(会同步写入场景档案)。", "agm.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": "启用场景即以它为全局基线。\n启用期间在提示词页「应用」别的预设 = 改绑到那一份(同步写进档案)。", "scenes.prompt.activeTag": "生效中", "scenes.prompt.noPresets": "还没有提示词预设;可先去「提示词」页新建一个。", "scenes.prompt.bound": "提示词:{id}", "scenes.prompt.live": "提示词生效中:{id}", "scenes.prompt.missing": "预设不存在:{id}", "scenes.prompt.tag.hint": "这个场景绑定的提示词预设", "scenes.prompt.mismatch.hint": "场景绑定的「{id}」与 AGENTS.md 当前内容不一致(可能被手动改过);到提示词页对它点「重新应用」写回", "scenes.enable.blocked": "已启用场景「{name}」;先关掉它才能启用别的", "scenes.result.created.collapsed": "场景「{name}」已创建,默认不启动(原先的「全部启用」已收敛为单选)", "scenes.legacyAll": "检测到 {count} 个场景同时处于启用状态(历史「全部启用」遗留);除「全局」外同时只能启用一个。", "scenes.legacyAll.fix": "收敛为单选", "scenes.preview.title": "进入场景「{name}」前,先看一眼会改什么", "scenes.preview.loading": "正在计算这次会改什么…", "scenes.preview.failed": "预览没算出来:{error}", "scenes.preview.enter": "确认进入", "scenes.preview.dismiss": "不再显示", "scenes.preview.dismiss.title": "关闭这张卡,并且以后进入场景前不再弹(想恢复用页面右上角的「开启提醒」)", "scenes.preview.toggle.off": "关闭提醒", "scenes.preview.toggle.on": "开启提醒", "scenes.preview.toggle.title": "进入场景前先弹一张「会改什么」的卡", "scenes.preview.nochange": "什么都不用改:当前运行时已经和这个场景要的一致了。", "scenes.preview.noArchive": "这个场景还没保存过档案 —— MCP / 技能 / 人设三个域都按「一个都没勾」处理,进入即全部停用。", "scenes.preview.exitFirst": "先退出「{scene}」:它改过的东西按进场景前的快照还原。", "scenes.preview.exitNoSnapshot": "当前在场景「{scene}」里,但进场景前的快照已丢失:退出只能清掉模式标记,运行时回不到原样。", "scenes.preview.row.serversOn": "启用 {count} 台 MCP 服务器:{names}", "scenes.preview.row.serversOff": "停用 {count} 台 MCP 服务器:{names}", "scenes.preview.row.toolsOn": "放回 {count} 个 MCP 工具:{names}", "scenes.preview.row.toolsOff": "停用 {count} 个 MCP 工具:{names}", "scenes.preview.row.sourcesOn": "启用 {count} 个技能目录:{names}", "scenes.preview.row.sourcesOff": "停用 {count} 个技能目录:{names}", "scenes.preview.row.skillsOn": "启用 {count} 个技能:{names}", "scenes.preview.row.skillsOff": "停用 {count} 个技能:{names}", "scenes.preview.row.personasOn": "启用 {count} 个人设:{names}", "scenes.preview.row.personasOff": "停用 {count} 个人设:{names}", "scenes.preview.enter.servers": "启用 {count} 台 MCP:{names}", "scenes.preview.enter.memories": "启用 {count} 条记忆:{names}", "scenes.preview.row.notes": "用场景备注覆盖 {count} 台服务器的备注", "scenes.preview.more": " 等", "scenes.preview.truncated": "(每一类最多列前 60 项)", "scenes.preview.stale": "档案里有 {count} 项在当前环境里已经不存在,这次不会生效:{items}", "scenes.doctor.title": "有 {count} 处引用指向已经不存在的名字", "scenes.doctor.noneButSkipped": "体检没发现悬空引用", "scenes.doctor.hint": "改名或删除留下的:这一条看着还绑着,运行时按那个名字已经找不到东西了。改回正确的名字,或到档案里取消勾选。", "scenes.doctor.row": "{where}:「{name}」已不存在", "scenes.doctor.more": "另有 {count} 处未列出", "scenes.doctor.skipped": "({domains} 这次没读到,这一类没有算进体检)", "agm.restore.tag": "退出场景后恢复它", "agm.restore.tag.hint": "它是进场景前的全局提示词,退出场景时会按它恢复——删了就没法恢复了", "agm.file.mismatch": "文件里不是它", "agm.file.mismatch.hint": "AGENTS.md 当前内容与这份预设不一致(可能被手动改过);点「重新应用」把它写回", "hist.restore.note.registered": "已恢复 {count} 个会话;工作区「{title}」已重新登记,会话已挂回该组。", "hist.restore.note.attached": "已恢复 {count} 个会话,并挂回工作区「{title}」。", "hist.restore.note.skip": "{count} 个会话的工作区归属未能恢复:{reason}", "hist.purge.title": "永久删除?", "hist.purge.one": "永久删除会话「{title}」及其全部记录,不可恢复", "hist.purge.batch": "永久删除所选 {count} 个会话及其全部记录,不可恢复", "hist.btn.restore": "恢复", "hist.btn.purge": "永久删除", "hist.btn.confirmPurge": "确认删除", "hist.selectGroup": "全选组 {title}", "hist.group.count": "{count} 个", "hist.selectAll": "全选", "hist.deselectAll": "取消全选", "hist.btn.import": "导入会话", "hist.btn.export": "导出会话", "hist.selected": "已选 {count} 项", "hist.btn.restoreSelected": "恢复所选 ({count})", "hist.btn.deleteSelected": "删除所选 ({count})", "hist.stat.archived": "个归档", "hist.stat.projects": "个项目", "hist.stat.retention": "天保留", "hist.search.placeholder": "搜索标题 / 会话 ID / 项目路径", "hist.loading": "加载中…", "hist.empty.search": "无匹配的归档会话", "hist.empty.none": "暂无归档会话", "hist.import.cwd": "项目目录(可留空)", "hist.import.cwd.placeholder": "绝对路径,可留空(会话归入未分组)", "hist.import.drop": "选择或拖入对话文件", "hist.import.done": "已创建会话 {id}({count} 条消息),可在 DSH 会话列表中继续对话。", "hist.export.hint": "导出选中会话为转录文件,可再次导入", "hist.export.dir": "导出目录(绝对路径,自动创建)", "hist.export.browse": "浏览并选择文件夹", "hist.btn.browse": "选择", "hist.export.pickDir": "选择导出目录", "hist.export.format": "格式", "hist.export.scope": "会话范围", "hist.export.workspace": "工作区", "hist.export.sessions": "会话({count} 个)", "hist.export.loading": "加载会话列表…", "hist.cwd.missing": "⚠ 工作区目录已不存在:", "hist.tag.archived": "已归档", "hist.tag.live": "未归档", "hist.export.empty": "没有符合条件的会话", "hist.result.exported": "已导出 {count} 个会话到 {dir}", "hist.result.skipped": ";跳过 {count} 个({ids})", "hist.result.archived": "已归档 {count} 个会话到 History,可到 History 页继续管理", "hist.result.archiveFailed": ";其中 {count} 个未能归档", "hist.btn.archiveSelected": "归档所选", "hist.btn.archiveSelected.title": "把选中的会话收进 History,纳入保留期管理", "hist.export.busy": "处理中…", "hist.btn.exportSelected": "导出 {count} 个会话", "hist.error.title": "操作失败", "hist.error.unknown": "未知错误",
|
|
2391
2854
|
},
|
|
2392
2855
|
en: {
|
|
2393
2856
|
"title": "Skills", "desc": "Manage skills: enable, import, create, trash.", "link.project": "GitHub", "link.feedback": "Issues",
|
|
@@ -2425,7 +2888,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2425
2888
|
"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",
|
|
2426
2889
|
"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",
|
|
2427
2890
|
"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.",
|
|
2428
|
-
"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": "
|
|
2891
|
+
"create.title": "Create skill", "create.target": "Create in", "create.name": "Name", "create.name.placeholder": "e.g. code-review-helper", "create.description": "Description", "create.description.placeholder": "One sentence describing when to use it", "create.body": "Body (Markdown)", "create.body.placeholder": "Write the instructions, steps, and boundaries…", "create.chat.note": "skill_manager_save in chat creates user-level skills",
|
|
2429
2892
|
"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; the file name is the memory name", "upload.requirement.memory.3": "A folder with SKILL.md becomes a bundle memory (attachments included)", "upload.requirement.memory.4": "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}",
|
|
2430
2893
|
"error.custom.absolute": "The folder path must be absolute: {path}", "error.custom.notDirectory": "Not a folder: {path}", "error.custom.unreadable": "Could not read the folder: {path}", "error.custom.overlap": "The folder overlaps an existing skill source: {path} ↔ {other}", "error.custom.invalidKey": "Invalid custom source key: {key}", "error.custom.notFound": "Custom source not found: {key}",
|
|
2431
2894
|
// 逐字对应宿主 http-fence.ts 的 TOKEN_MSG_*(见中文侧注释)。
|
|
@@ -2478,6 +2941,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2478
2941
|
"mcp.form.err.serverName": "Server name must be 1-32 chars of letters, digits, underscore or hyphen (no spaces, no CJK)", "mcp.form.err.url": "URL must start with http:// or https://", "mcp.form.err.command": "Command cannot be empty",
|
|
2479
2942
|
"mcp.form.maskedHint": "A masked value in a secret field (e.g. ••••••) is not the real value: saving leaves those entries untouched, because the plugin writes back the value found in the patch file. To read a secret in plain text, click Reveal secrets (if this plugin has an access token configured, enter the matching token first).",
|
|
2480
2943
|
"mcp.field.serverName": "Server name (serverName)", "mcp.field.serverName.hint": "1-32 chars of [A-Za-z0-9_-]; the patch registers it under this name",
|
|
2944
|
+
"mcp.field.note": "Note (optional)", "mcp.field.note.placeholder": "A hint for the model, e.g. \“this one needs passwordless sudo\”; empty means nothing is stored",
|
|
2481
2945
|
"mcp.field.transport": "Transport", "mcp.field.level": "Level",
|
|
2482
2946
|
"mcp.field.level.project": "Profile level (this app: {path})", "mcp.field.level.global": "Global (across profiles: {path})",
|
|
2483
2947
|
"mcp.field.url": "Server URL", "mcp.field.url.hint": "Must start with http(s)://",
|
|
@@ -2577,12 +3041,89 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2577
3041
|
"compat.token.failed": "Could not read the token status; refresh and retry.",
|
|
2578
3042
|
"compat.inject.force": "Also inject in Minimal mode", "compat.inject.force.desc": "Off by default (follow the preset)",
|
|
2579
3043
|
"compat.inject.domains": "What to inject",
|
|
2580
|
-
"compat.inject.domain.
|
|
3044
|
+
"compat.inject.domain.scene": "Scene", "compat.inject.domain.memory": "Memory", "compat.inject.domain.mcp": "MCP",
|
|
2581
3045
|
"compat.inject.domain.subagents": "Subagents", "compat.inject.domain.prompt": "Prompt",
|
|
2582
3046
|
"compat.inject.domain.skills": "Skills",
|
|
2583
3047
|
"compat.inject.domain.carrier.title": "Turning this off also stops the copy the system sends by itself",
|
|
3048
|
+
"compat.tools": "Model tool table", "compat.tools.hint": "which tools reach the model: the table is sent with every request, so a switched-off tool is not sent at all (≈ is an estimate)",
|
|
3049
|
+
"compat.tools.summaryAll": "all {total} tools sent · ≈{tok} tok per turn",
|
|
3050
|
+
"compat.tools.summary": "{on}/{total} sent · ≈{tok} tok per turn ({saved} tok saved)",
|
|
3051
|
+
"compat.tools.groupCount": "{on}/{total} · ≈{tok}",
|
|
3052
|
+
"compat.tools.other": "Other",
|
|
3053
|
+
"compat.tools.onTitle": "Sent: the model can see and call it",
|
|
3054
|
+
"compat.tools.offTitle": "Switched off: not sent at all, so the model cannot see or call it (the panel is unaffected)",
|
|
3055
|
+
"compat.tools.summaryDefault": "of which ≈{tok} is saved by the {n} tools that ship switched off (leave them alone if the model should not do those things; ticking one is local to this machine)",
|
|
3056
|
+
"compat.tools.defaultOffTitle": "Switched off in the shipped default: the model cannot see or call it, the panel is unaffected. Tick it here if you want the model to do this (this machine only).",
|
|
3057
|
+
// "Sets": store a whole tick pattern under a name and switch back to it in one click.
|
|
3058
|
+
"compat.tools.preset.save": "Save set",
|
|
3059
|
+
"compat.tools.preset.apply": "Restore set",
|
|
3060
|
+
"compat.tools.preset.save.tip": "Store this tick pattern under a name so you can switch back to it in one click",
|
|
3061
|
+
"compat.tools.preset.apply.tip": "Switch to a stored set (including the undeletable shipped default)",
|
|
3062
|
+
"compat.tools.preset.apply.tipEmpty": "No sets stored yet; you can still look at the shipped default here",
|
|
3063
|
+
"compat.tools.preset.saveTitle": "Save the current set",
|
|
3064
|
+
"compat.tools.preset.applyTitle": "Restore a set",
|
|
3065
|
+
"compat.tools.preset.saveHint": "Right now: {on}/{total} sent · ≈{tok} tok per request.",
|
|
3066
|
+
"compat.tools.preset.name": "Set name",
|
|
3067
|
+
"compat.tools.preset.namePlaceholder": "e.g. minimal / let it manage servers",
|
|
3068
|
+
"compat.tools.preset.saveYes": "Save it",
|
|
3069
|
+
"compat.tools.preset.applyHint": "Pick one: the tool table the model receives is rebuilt immediately, and the panel is unaffected.",
|
|
3070
|
+
"compat.tools.preset.default": "Shipped default",
|
|
3071
|
+
"compat.tools.preset.builtin": "cannot delete",
|
|
3072
|
+
"compat.tools.preset.count": "{on}/{total} sent · ≈{tok} tok per request",
|
|
3073
|
+
"compat.tools.preset.use": "Use this",
|
|
3074
|
+
"compat.tools.preset.removeTitle": "Delete the set “{name}” (only that record; your current ticks stay)",
|
|
3075
|
+
"compat.tools.preset.err.nameEmpty": "The name cannot be empty",
|
|
3076
|
+
"compat.tools.preset.err.nameTaken": "There is already a set called “{name}” — saving never overwrites it. Choose another name (or delete that one first).",
|
|
3077
|
+
"compat.tools.preset.err.limit": "At most {max} sets — delete one before saving another",
|
|
3078
|
+
"compat.tools.preset.err.notFound": "That set is gone (someone else deleted it)",
|
|
3079
|
+
"compat.tools.preset.err.net": "Not saved: no reply from the local service",
|
|
3080
|
+
// Two tiers per tool (inline one-liner / slightly longer hover). Same shape everywhere:
|
|
3081
|
+
// the inline line starts with a verb and has no full stop, and it is an abbreviation of
|
|
3082
|
+
// what the tool can actually do — several behaviours are joined with "/", never "or"
|
|
3083
|
+
// (these tools really do both: `save` creates *and* updates, `switch` enables *and*
|
|
3084
|
+
// disables). Keys are built from the tool name.
|
|
3085
|
+
"compat.tools.about.memory_manager_list": "List memories and their state",
|
|
3086
|
+
"compat.tools.about.memory_manager_read": "Read a memory body by id",
|
|
3087
|
+
"compat.tools.about.memory_manager_switch": "Enable/disable a memory",
|
|
3088
|
+
"compat.tools.about.memory_manager_save": "Create/update a memory",
|
|
3089
|
+
"compat.tools.about.scene_manager_list": "List scenes and their archives",
|
|
3090
|
+
"compat.tools.about.scene_manager_switch": "Enter/leave a scene",
|
|
3091
|
+
"compat.tools.about.scene_manager_save": "Create/update a scene, write its archive, bind a prompt",
|
|
3092
|
+
"compat.tools.about.mcp_manager_list": "List servers and tools",
|
|
3093
|
+
"compat.tools.about.mcp_manager_switch": "Enable/disable/restart a server",
|
|
3094
|
+
"compat.tools.about.mcp_manager_save": "Add/update a server",
|
|
3095
|
+
"compat.tools.about.skill_manager_list": "List skills with state and path",
|
|
3096
|
+
"compat.tools.about.skill_manager_read": "Read a skill body by name",
|
|
3097
|
+
"compat.tools.about.skill_manager_switch": "Enable/disable a skill or source",
|
|
3098
|
+
"compat.tools.about.skill_manager_save": "Create/update a skill",
|
|
3099
|
+
"compat.tools.about.subagent_manager_list": "List personas and descriptions",
|
|
3100
|
+
"compat.tools.about.subagent_manager_run": "Run a persona as a subagent",
|
|
3101
|
+
"compat.tools.about.subagent_manager_switch": "Enable/disable a persona",
|
|
3102
|
+
"compat.tools.about.subagent_manager_save": "Create/update a persona",
|
|
3103
|
+
"compat.tools.about.prompt_manager_list": "List prompt presets",
|
|
3104
|
+
"compat.tools.about.prompt_manager_apply": "Apply a prompt preset",
|
|
3105
|
+
"compat.tools.hover.memory_manager_list": "Lists each memory's id, scene, on/off state and one-line description, and says which scene injection follows. Only the memories that would actually be injected by default; all=true adds disabled, inactive-scene and shadowed entries. The scenes themselves (archives, bound presets, locks) are in scene_manager_list. Read-only.",
|
|
3106
|
+
"compat.tools.hover.memory_manager_read": "Reads the full body of one memory by id (<scene>/<name>). The list carries a one-line description only — use this for the body, especially for memories that are off, in an inactive scene, or dropped by the byte budget.",
|
|
3107
|
+
"compat.tools.hover.memory_manager_switch": "Flips the index switch only — the .md file is untouched and it is reversible. A disabled memory stops being injected, which frees byte budget for the others; prefer this over deleting when you want to spend fewer tokens.",
|
|
3108
|
+
"compat.tools.hover.memory_manager_save": "Creates a memory, or updates the existing one with that id — the receipt says which. Memories enter every later request, so record only what cannot be re-derived from code, config or git — and check the list first for an entry that already says this. Omitted fields are kept; a rename leaves the old file in the trash.",
|
|
3109
|
+
"compat.tools.hover.scene_manager_list": "Lists every scene: its name (with the display label when it differs — tools need the name), enabled / entered / locked state, its description, bound preset, memory count, and its archive: which MCP servers, skills and personas entering it switches on, everything else going off. Look here before changing a scene's archive — saving replaces a whole section, so an unseen selection gets dropped — and use it to pick which scene to enter. Read-only.",
|
|
3110
|
+
"compat.tools.hover.scene_manager_switch": "Enters a scene: its archive is applied across MCP, skills and personas (anything it does not list goes off), injection narrows to its memories, and its bound preset is written into the global AGENTS.md. Leaving restores the runtime snapshot taken on entry, including the previous baseline. Check the scene's archive in scene_manager_list first. It rewrites the whole runtime environment, so it asks for confirmation by default.",
|
|
3111
|
+
"compat.tools.hover.scene_manager_save": "Saves a scene — a named bundle the user enters: whatever its archive lists gets switched on, everything else off. Writing the archive does not turn the scene on — enter it with scene_manager_switch (or the Scenes page) for that. Sections you omit keep their current value; an empty section means \"switch nothing on\", not \"no restriction\". A section you do pass is replaced whole, so read the current one in scene_manager_list first; the receipt lists what got dropped.",
|
|
3112
|
+
"compat.tools.hover.mcp_manager_list": "Lists the configured servers, whether each is connected, which tools it exposes, and the note you wrote for it. Servers that connected before but not now are marked — that means this one is broken, not unconfigured. tools=true also lists each server's tool names, marking the switched-off ones (off); a server that is off only has the names recorded from its last run.",
|
|
3113
|
+
"compat.tools.hover.mcp_manager_switch": "action=on/off enables or disables a whole server; pass `tool` to switch just that one tool (a disabled tool disappears from the model-visible tool table and is blocked at execution, and it survives a DSH restart); action=restart disables the server, waits for it to drop, then enables it back the way it was — use it after a config change or when a server is stuck. Pass level when the same server name exists at both project and global level.",
|
|
3114
|
+
"compat.tools.hover.mcp_manager_save": "Adds a server (streamable-http or stdio) or updates the one with that name — the receipt says which. When updating, every field you omit keeps its current value, so changing one URL does not mean restating the whole config (and the secrets survive); for stdio, command+args is the command the host actually spawns, and changing it raises a confirmation card. Only on the user's explicit request.",
|
|
3115
|
+
"compat.tools.hover.skill_manager_list": "Lists each skill's on/off state, whether it wins or is shadowed, and its source path. The always-on catalog carries names and summaries only — use this to see what you turned off and which copy of a name wins; all=true includes disabled and shadowed copies.",
|
|
3116
|
+
"compat.tools.hover.skill_manager_read": "Reads one skill's SKILL.md body by name (the catalog has names and summaries only). Name collisions resolve to the copy actually in effect; pass root to inspect a shadowed copy instead. Not sent in conversations where the official skill tool is mounted.",
|
|
3117
|
+
"compat.tools.hover.skill_manager_switch": "Toggles by skill name or by source directory. Disabled skills leave the catalog and cannot be loaded; when a name exists in several roots only the winning copy counts, so enabling the wrong one changes nothing.",
|
|
3118
|
+
"compat.tools.hover.skill_manager_save": "Creates a skill, or updates the copy this plugin wrote itself — the receipt says which. It lands in hub (tool-management/skills/), matching the panel's \"Create skill\"; a new skill starts disabled, so enable it before use, and skills living in the official roots are never touched. The catalog is injected into every request, so make the description say when to use it.",
|
|
3119
|
+
"compat.tools.hover.subagent_manager_list": "Lists the available personas (pre-configured subagent roles) with their descriptions. The always-on catalog carries the same list; this tool always returns the current, complete version — including conversations the catalog does not reach.",
|
|
3120
|
+
"compat.tools.hover.subagent_manager_run": "Hands a task to a named persona: it gets the persona as its own system prompt, works in its own context and returns only the final result. With inherit it also sees this conversation's finished turns. It spends real tokens and asks the user by default.",
|
|
3121
|
+
"compat.tools.hover.subagent_manager_switch": "A disabled persona leaves the catalog and cannot be delegated to; personas bound to a scene are enabled automatically when that scene is entered.",
|
|
3122
|
+
"compat.tools.hover.subagent_manager_save": "Creates a persona, or updates the existing one — the receipt says which. You can set the name, description, body (the body becomes the subagent's system prompt) and output contract; omitted fields keep their current values, and a rename makes scene and archive bindings follow. Provider, model and tool limits stay in the panel (the model cannot tell whether it got them right, so it is not offered them). The description decides when the model picks it.",
|
|
3123
|
+
"compat.tools.hover.prompt_manager_list": "Lists the prompt presets with their summaries and marks the one currently in effect — only one is active at a time. Use it to confirm the name before switching.",
|
|
3124
|
+
"compat.tools.hover.prompt_manager_apply": "Applies a preset to the user-level AGENTS.md; later conversations follow it immediately. Only one preset is in effect, so applying another replaces it.",
|
|
2584
3125
|
"compat.live.title": "Live injection", "compat.live.hint": "What the model actually sees (read-only)",
|
|
2585
|
-
"compat.live.never": "nothing delivered
|
|
3126
|
+
"compat.live.never": "nothing delivered in this conversation yet", "compat.live.delivered": "{count} delivered in this conversation · last {time}",
|
|
2586
3127
|
"compat.live.copy": "Copy all", "compat.live.copied": "Copied", "compat.live.noAgent": "No session yet — send a message first",
|
|
2587
3128
|
"compat.live.state.inContext": "Injected", "compat.live.state.absent": "Not delivered", "compat.live.state.cleared": "Cleared",
|
|
2588
3129
|
"compat.live.state.off": "Off", "compat.live.state.unknown": "No session",
|
|
@@ -2590,18 +3131,19 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2590
3131
|
"compat.live.state.child.title": "This session is deeper than the personas' catalog injection depth (catalogDepth), so the standing catalog is not injected here. To let the catalog reach subagent sessions, raise the persona's catalog injection depth (or pick “No nesting limit”). Note: this does not affect delegation — subagents can always delegate further.",
|
|
2591
3132
|
"compat.live.adopt": "injected {injected} · called {used} · adopted {adopted}",
|
|
2592
3133
|
"compat.live.adopt.none": "injected {injected} · never called",
|
|
2593
|
-
"compat.live.adopt.title": "Counts
|
|
2594
|
-
"compat.live.observe.some": "{count} tool calls observed this
|
|
2595
|
-
"compat.live.observe.none": "no tool calls observed
|
|
3134
|
+
"compat.live.adopt.title": "Counts the most recent conversation only (it restarts when you switch, and on every launch). Adopted = the domain text was in context when the call happened; calls are counted whether or not they succeeded",
|
|
3135
|
+
"compat.live.observe.some": "{count} tool calls observed in this conversation",
|
|
3136
|
+
"compat.live.observe.none": "no tool calls observed in this conversation yet",
|
|
3137
|
+
"compat.live.cost": "≈{size} this conversation · biggest: {domain} · most resent: {resent}",
|
|
2596
3138
|
"compat.live.state.official": "Official", "compat.live.state.official.title": "The official carrier delivers these under standard presets; the plugin does not duplicate (it only fills in for Minimal-like presets)",
|
|
2597
3139
|
"compat.live.state.empty": "Nothing to send", "compat.live.est": "≈{count} tokens",
|
|
2598
3140
|
"compat.live.state.absent.title": "This plugin owns this domain but it is not in context right now (it may have just been compacted; the next step re-sends it)",
|
|
2599
|
-
"compat.reach.mark.
|
|
3141
|
+
"compat.reach.mark.scene": "Scene", "compat.reach.mark.memory": "Memory", "compat.reach.mark.agentsMd": "Prompt", "compat.reach.mark.skill": "Skills", "compat.reach.mark.mcp": "MCP", "compat.reach.mark.subagent": "Subagents",
|
|
2600
3142
|
"compat.reach.default": "default",
|
|
2601
3143
|
"compat.reach.whyUnknown": "cannot tell", "compat.reach.broken": "preset broken",
|
|
2602
3144
|
"compat.reach.tools.title": "Tools work ≠ the model knows",
|
|
2603
3145
|
"compat.reach.tools.1": "The tools come from this plugin and are callable under any preset; how they reach the model is up to the preset.",
|
|
2604
|
-
"compat.reach.tools.2": "Scene
|
|
3146
|
+
"compat.reach.tools.2": "Scene / memory / MCP / skills / subagents / prompt — filled in by this plugin according to the Injection block above.",
|
|
2605
3147
|
"compat.reach.tools.3": "The prompt and the skill catalog also come from the official rows; this plugin only fills in what they miss (e.g. under Minimal).",
|
|
2606
3148
|
"compat.hint.doctor": "Command-line check:",
|
|
2607
3149
|
"scenes.title": "Scenes", "scenes.desc": "Manage scenes: preset combinations of MCP, skills, subagents and memories, switch with one click.",
|
|
@@ -2609,7 +3151,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2609
3151
|
"scenes.noDesc": "No description yet",
|
|
2610
3152
|
"scenes.mode.active": "Active mode: {name}", "scenes.mode.profile": "Profile: {parts}", "scenes.mode.noProfile": "This scene has no profile yet",
|
|
2611
3153
|
"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.",
|
|
2612
|
-
"scenes.
|
|
3154
|
+
"scenes.search.placeholder": "Search scene names or descriptions", "scenes.empty.search": "No matching scenes",
|
|
2613
3155
|
"scenes.profile.mcp": "{count} MCP", "scenes.profile.skills": "{count} skill(s)", "scenes.profile.subagents": "{count} subagent(s)", "scenes.profile.memories": "{count} memor(ies)",
|
|
2614
3156
|
"scenes.mcp.hint": "Add the MCP section first, then check the servers to enable (stopped ones included); use Pick tools to narrow to specific tools. Unchecked servers — and scenes with no section at all — are turned off on entry, running processes included.",
|
|
2615
3157
|
"scenes.seg.checked": "{checked}/{total} selected",
|
|
@@ -2673,6 +3215,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2673
3215
|
"memory.attach.count.one": "{count} attachment", "memory.attach.count.other": "{count} attachments",
|
|
2674
3216
|
"memory.attach.tip": "Click to manage attachments: {count} · {size} total — {names}",
|
|
2675
3217
|
"memory.attach.tip.empty": "No files yet — click to add",
|
|
3218
|
+
"memory.size.tip": "{size} of body: once enabled it costs this much context on every turn",
|
|
2676
3219
|
"memory.attach.more": ", +{count} more", "memory.attach.sep": ", ", "memory.attach.flat": "The flat form is a single file and cannot carry attachments", "memory.attach.tooLarge": "File too large: {name} (max {limit} MB)",
|
|
2677
3220
|
"memory.delete.title": "Move to trash?", "memory.delete.desc": "“{name}” moves to trash and can be restored",
|
|
2678
3221
|
"memory.result.created": "Created memory: {name}", "memory.result.updated": "Saved memory: {name}", "memory.result.removed": "Moved to trash: {name}", "memory.result.toggled": "Memory state updated: {name}", "memory.result.active": "Enabled scenes updated",
|
|
@@ -2697,6 +3240,10 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2697
3240
|
"subagents.field.description": "Description", "subagents.field.description.placeholder": "e.g. Senior Java engineer",
|
|
2698
3241
|
"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": "deepseek", "subagents.field.provider.hint": "Provider and model are a pair: a model alone resolves against the main session's provider and fails across providers.",
|
|
2699
3242
|
"subagents.field.catalogDepth": "Catalog injection", "subagents.field.catalogDepth.onlyTop": "Top level only", "subagents.field.catalogDepth.toChild": "Top level and subagents", "subagents.field.catalogDepth.toGrand": "Top level and two levels down", "subagents.field.catalogDepth.unlimited": "No nesting limit", "subagents.field.catalogDepth.hint": "Which levels of sessions get the persona catalog.\nIt affects the catalog only — subagents can always delegate further.\nWhen the catalog is not injected, subagent_manager_list still works.",
|
|
3243
|
+
// Reasoning effort (0.14.0). The form only says what this field offers right now; the
|
|
3244
|
+
// official semantics (the host drops the inherited level when provider/model change, and an
|
|
3245
|
+
// unsupported level is rejected rather than clamped) live in CHANGELOG / update.md, not here.
|
|
3246
|
+
"subagents.field.effort": "Reasoning effort", "subagents.effort.default": "Default (the model's default)", "subagents.effort.none": "default", "subagents.effort.needModel": "Currently the main session's default reasoning effort.", "subagents.effort.loading": "Asking the model which levels it supports…", "subagents.effort.unavailable": "This model exposes no selectable levels — default reasoning effort.", "subagents.effort.hint": "Several reasoning-effort levels are available.", "subagents.effort.dropped": "“{effort}” is not among the new model's levels, so it was cleared.",
|
|
2700
3247
|
"subagents.model.inherit": "Inherit the main session (unspecified)", "subagents.model.customOption": "Custom / not in the catalogue…",
|
|
2701
3248
|
"subagents.field.modes": "Tool limits (per agent preset)",
|
|
2702
3249
|
"subagents.field.modes.hint": "Only the row for the current preset applies; other presets keep their full tool set.",
|
|
@@ -2713,7 +3260,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2713
3260
|
"subagents.legacy.convert": "Convert into a preset row",
|
|
2714
3261
|
"subagents.legacy.hint": "Converting moves the old list into the preset you pick; until then the old keys stay untouched in the file (and keep working at run time).",
|
|
2715
3262
|
"subagents.tools.filter": "Filter tool names", "subagents.tools.remove": "Remove", "subagents.toolFailed": "Subagent tools failed to register: {names} ({reason}). They cannot be used in the subagent flow; if this persists after a DSH restart, check the host version.",
|
|
2716
|
-
"subagents.adv.title": "Advanced options", "subagents.adv.inherit": "inherit", "subagents.adv.summary": "model {model}
|
|
3263
|
+
"subagents.adv.title": "Advanced options", "subagents.adv.inherit": "inherit", "subagents.adv.summary": "model: {model}; effort: {effort}; catalog: {depth}", "subagents.adv.summary2": "preset limits: {modes}; legacy allow: {allow} / deny: {deny}", "subagents.adv.note": "Allowlist = the subagent may only use the picked tools.\nDenylist = everything except the picked ones.\nMCP tools are not listed: a subagent always keeps the MCP servers currently running.", "subagents.adv.loadFailed": "Could not read the candidate data (model catalogue / tool list); you can still fill the fields by hand.",
|
|
2717
3264
|
"preset.name.standard": "Standard mode", "preset.name.ptc": "PTC mode", "preset.name.minimal": "Minimal mode", "preset.name.cordis": "Creator mode",
|
|
2718
3265
|
"preset.short.standard": "Standard", "preset.short.ptc": "PTC", "preset.short.minimal": "Minimal", "preset.short.cordis": "Creator",
|
|
2719
3266
|
"subagents.field.body": "Persona prompt", "subagents.field.body.placeholder": "Describe the persona's role, responsibilities, and working style…",
|
|
@@ -2731,7 +3278,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2731
3278
|
"import.none": "Nothing was imported (all skipped)", "import.skipped": "Skipped: {items}",
|
|
2732
3279
|
"error.import.noFiles": "No files selected",
|
|
2733
3280
|
"nav.title": "Tools",
|
|
2734
|
-
"agm.stat.total": " prompts", "agm.stat.active": " active", "agm.loading": "Loading…", "agm.empty": "No prompts yet — click “New prompt” to create one", "agm.empty.search": "No matching prompts", "agm.search.placeholder": "Search prompt ids or descriptions", "agm.btn.delete.blocked.refs": "Cannot delete: {refs}. Change those references first (rebind the prompt, leave the scene, or apply another preset).", "agm.ref.sep": "; ", "agm.ref.scene": "bound by scene “{scene}”", "agm.ref.sceneActive": "bound by scene “{scene}” (enabled)", "agm.ref.file": "this is what AGENTS.md currently holds", "agm.ref.restore": "the global prompt to restore after leaving the scene", "agm.sceneLock.notice": "Scene “{scene}” is driving the baseline: the prompt in effect is its bound “{id}”. Applying another preset here rebinds that scene to it (written into the scene profile as well).", "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 the scene is on, this preset is the global baseline.\nApplying another preset on the Prompts page rebinds the scene to it (written into the profile too).", "scenes.prompt.activeTag": "in effect", "scenes.prompt.noPresets": "No prompt presets yet — create one on the Prompts page.", "scenes.prompt.bound": "Prompt: {id}", "scenes.prompt.live": "Prompt active: {id}", "scenes.prompt.missing": "Preset missing: {id}", "scenes.prompt.tag.hint": "The prompt preset bound to this scene", "scenes.prompt.mismatch.hint": "The scene-bound “{id}” does not match the current contents of AGENTS.md (it may have been edited by hand); click “Apply again” for it on the Prompts page to write it back", "scenes.enable.blocked": "Scene “{name}” is already enabled; turn it off first", "scenes.result.created.collapsed": "Scene “{name}” created and left off (the legacy “all enabled” default collapsed to a single choice)", "scenes.legacyAll": "{count} scenes are enabled at once (a leftover of the legacy “all enabled” default); only one scene besides “Global” may be enabled.", "scenes.legacyAll.fix": "Collapse to one", "agm.restore.tag": "Restored after the scene", "agm.restore.tag.hint": "The global prompt from before the scene; leaving the scene restores it, so deleting it would lose that fallback", "agm.file.mismatch": "Not in AGENTS.md", "agm.file.mismatch.hint": "AGENTS.md does not currently match this preset (it may have been edited by hand); click “Apply again” to write it back", "hist.restore.note.registered": "Restored {count} session(s); workspace “{title}” was registered again and the sessions are back in that group.", "hist.restore.note.attached": "Restored {count} session(s) and attached them to workspace “{title}”.", "hist.restore.note.skip": "Workspace ownership could not be restored for {count} session(s): {reason}", "hist.purge.title": "Delete forever?", "hist.purge.one": "Delete “{title}” and all of its records forever — this cannot be undone", "hist.purge.batch": "Delete the {count} selected sessions and all of their records forever — this cannot be undone", "hist.btn.restore": "Restore", "hist.btn.purge": "Delete forever", "hist.btn.confirmPurge": "Delete", "hist.selectGroup": "Select group {title}", "hist.group.count": "{count}", "hist.selectAll": "Select all", "hist.deselectAll": "Deselect all", "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.result.archiveFailed": "; {count} could not be archived", "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",
|
|
3281
|
+
"agm.stat.total": " prompts", "agm.stat.active": " active", "agm.loading": "Loading…", "agm.empty": "No prompts yet — click “New prompt” to create one", "agm.empty.search": "No matching prompts", "agm.search.placeholder": "Search prompt ids or descriptions", "agm.btn.delete.blocked.refs": "Cannot delete: {refs}. Change those references first (rebind the prompt, leave the scene, or apply another preset).", "agm.ref.sep": "; ", "agm.ref.scene": "bound by scene “{scene}”", "agm.ref.sceneActive": "bound by scene “{scene}” (enabled)", "agm.ref.file": "this is what AGENTS.md currently holds", "agm.ref.restore": "the global prompt to restore after leaving the scene", "agm.sceneLock.notice": "Scene “{scene}” is driving the baseline: the prompt in effect is its bound “{id}”. Applying another preset here rebinds that scene to it (written into the scene profile as well).", "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 (the model sees this in the list)", "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 the scene is on, this preset is the global baseline.\nApplying another preset on the Prompts page rebinds the scene to it (written into the profile too).", "scenes.prompt.activeTag": "in effect", "scenes.prompt.noPresets": "No prompt presets yet — create one on the Prompts page.", "scenes.prompt.bound": "Prompt: {id}", "scenes.prompt.live": "Prompt active: {id}", "scenes.prompt.missing": "Preset missing: {id}", "scenes.prompt.tag.hint": "The prompt preset bound to this scene", "scenes.prompt.mismatch.hint": "The scene-bound “{id}” does not match the current contents of AGENTS.md (it may have been edited by hand); click “Apply again” for it on the Prompts page to write it back", "scenes.enable.blocked": "Scene “{name}” is already enabled; turn it off first", "scenes.result.created.collapsed": "Scene “{name}” created and left off (the legacy “all enabled” default collapsed to a single choice)", "scenes.legacyAll": "{count} scenes are enabled at once (a leftover of the legacy “all enabled” default); only one scene besides “Global” may be enabled.", "scenes.legacyAll.fix": "Collapse to one", "scenes.preview.title": "Before entering scene “{name}” — what this will change", "scenes.preview.loading": "Working out what changes…", "scenes.preview.failed": "Preview failed: {error}", "scenes.preview.enter": "Enter the scene", "scenes.preview.dismiss": "Don't show again", "scenes.preview.dismiss.title": "Close this card and stop showing it before entering scenes (bring it back with “Turn the reminder on” at the top of this page)", "scenes.preview.toggle.off": "Turn the reminder off", "scenes.preview.toggle.on": "Turn the reminder on", "scenes.preview.toggle.title": "Show a “what will change” card before entering a scene", "scenes.preview.nochange": "Nothing to change: the runtime already matches what this scene asks for.", "scenes.preview.noArchive": "This scene has no saved profile — MCP, skills and personas are all read as “none checked”, so entering disables every one of them.", "scenes.preview.exitFirst": "Leaves “{scene}” first: what it changed is restored from the before-entering snapshot.", "scenes.preview.exitNoSnapshot": "“{scene}” is active but its before-entering snapshot is gone: leaving only clears the mode flag, the runtime will not go back.", "scenes.preview.row.serversOn": "Enables {count} MCP server(s): {names}", "scenes.preview.row.serversOff": "Disables {count} MCP server(s): {names}", "scenes.preview.row.toolsOn": "Puts back {count} MCP tool(s): {names}", "scenes.preview.row.toolsOff": "Disables {count} MCP tool(s): {names}", "scenes.preview.row.sourcesOn": "Enables {count} skill director(ies): {names}", "scenes.preview.row.sourcesOff": "Disables {count} skill director(ies): {names}", "scenes.preview.row.skillsOn": "Enables {count} skill(s): {names}", "scenes.preview.row.skillsOff": "Disables {count} skill(s): {names}", "scenes.preview.row.personasOn": "Enables {count} persona(s): {names}", "scenes.preview.row.personasOff": "Disables {count} persona(s): {names}", "scenes.preview.enter.servers": "{count} MCP server(s) stay on: {names}", "scenes.preview.enter.memories": "{count} memory(ies) enabled: {names}", "scenes.preview.row.notes": "Overrides the notes of {count} server(s) with scene notes", "scenes.preview.more": " etc.", "scenes.preview.truncated": "(each kind lists its first 60 entries only)", "scenes.preview.stale": "{count} profile entry(ies) no longer exist in this environment and will not take effect: {items}", "scenes.doctor.title": "{count} reference(s) point at a name that no longer exists", "scenes.doctor.noneButSkipped": "The check found no dangling references", "scenes.doctor.hint": "Left behind by a rename or a delete: it still looks bound here, but nothing matches that name at runtime. Point it at the right name, or untick it in the profile.", "scenes.doctor.row": "{where}: “{name}” no longer exists", "scenes.doctor.more": "{count} more not listed", "scenes.doctor.skipped": "({domains} could not be read, so that kind was not checked)", "agm.restore.tag": "Restored after the scene", "agm.restore.tag.hint": "The global prompt from before the scene; leaving the scene restores it, so deleting it would lose that fallback", "agm.file.mismatch": "Not in AGENTS.md", "agm.file.mismatch.hint": "AGENTS.md does not currently match this preset (it may have been edited by hand); click “Apply again” to write it back", "hist.restore.note.registered": "Restored {count} session(s); workspace “{title}” was registered again and the sessions are back in that group.", "hist.restore.note.attached": "Restored {count} session(s) and attached them to workspace “{title}”.", "hist.restore.note.skip": "Workspace ownership could not be restored for {count} session(s): {reason}", "hist.purge.title": "Delete forever?", "hist.purge.one": "Delete “{title}” and all of its records forever — this cannot be undone", "hist.purge.batch": "Delete the {count} selected sessions and all of their records forever — this cannot be undone", "hist.btn.restore": "Restore", "hist.btn.purge": "Delete forever", "hist.btn.confirmPurge": "Delete", "hist.selectGroup": "Select group {title}", "hist.group.count": "{count}", "hist.selectAll": "Select all", "hist.deselectAll": "Deselect all", "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.result.archiveFailed": "; {count} could not be archived", "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",
|
|
2735
3282
|
}
|
|
2736
3283
|
};
|
|
2737
3284
|
|
|
@@ -3084,7 +3631,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3084
3631
|
? { kind: 'warn', text: mt('mcp.msg.warn', { warning: res.warning }) }
|
|
3085
3632
|
: { kind: 'ok', text: mt('mcp.msg.ok') }
|
|
3086
3633
|
)
|
|
3087
|
-
const emptyForm = () => ({ serverName: '', transport: 'streamable-http', url: '', command: '', args: '', headers: '', env: '', level: 'global' })
|
|
3634
|
+
const emptyForm = () => ({ serverName: '', transport: 'streamable-http', url: '', command: '', args: '', headers: '', env: '', level: 'global', note: '' })
|
|
3088
3635
|
const kvToLines = (obj) => (obj ? Object.keys(obj).map((k) => k + '=' + obj[k]).join('\n') : '')
|
|
3089
3636
|
// 级别筛选选项:必须在**渲染时**取词。原来写成模块级常量,apply() 早于 locale
|
|
3090
3637
|
// 注册,导致英文界面下这四项永远显示中文(且再也翻不过来)。
|
|
@@ -3311,6 +3858,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3311
3858
|
env: payload.env,
|
|
3312
3859
|
}
|
|
3313
3860
|
if (payload.mode === 'edit') args.id = payload.id
|
|
3861
|
+
// 备注只在新增时带:服务端在写补丁的同一次锁里落进侧车(见 mcpmAdd)。
|
|
3862
|
+
else args.note = payload.note
|
|
3314
3863
|
run(payload.mode === 'edit' ? 'mcpm-edit' : 'mcpm-add', args, 'form', closeForm)
|
|
3315
3864
|
}
|
|
3316
3865
|
|
|
@@ -3642,7 +4191,15 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3642
4191
|
? React.createElement('label', { className: 'dsm-field' },
|
|
3643
4192
|
React.createElement('span', { className: 'dsm-label' }, mt('mcp.field.headers')),
|
|
3644
4193
|
React.createElement('textarea', { className: 'dsm-control dsm-textarea-sm', value: formModal.headers, placeholder: 'Authorization=Bearer xxx', onChange: setFormField('headers') }))
|
|
3645
|
-
: null
|
|
4194
|
+
: null,
|
|
4195
|
+
// 备注放在表单**最下面**:它是可选项,说的又是"这台怎么用",不该插在必填的连接参数中间。
|
|
4196
|
+
// 默认不填 = 侧车里不产生条目;只在新增时出现(`mcpm-edit` 不碰侧车,改备注走详情里那枚
|
|
4197
|
+
// 输入框)。上限就是注入段的截断长度 `MCP_NOTE_MAX` —— 写多少模型就能读到多少。
|
|
4198
|
+
formModal.mode === 'edit' ? null : React.createElement('label', { className: 'dsm-field' },
|
|
4199
|
+
React.createElement('div', { className: 'dsm-budget-meta' },
|
|
4200
|
+
React.createElement('span', { className: 'dsm-label' }, mt('mcp.field.note')),
|
|
4201
|
+
React.createElement('span', { className: 'dsm-char-count' }, String(formModal.note || '').length + ' / ' + MCP_NOTE_MAX)),
|
|
4202
|
+
React.createElement('input', { className: 'dsm-control', value: formModal.note || '', maxLength: MCP_NOTE_MAX, placeholder: mt('mcp.field.note.placeholder'), onChange: setFormField('note') }))),
|
|
3646
4203
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
3647
4204
|
React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: busy === 'form' || !formModal.serverName.trim(), onClick: submitForm }, formModal.mode === 'edit' ? mt('mcp.btn.save') : mt('mcp.btn.add'))))
|
|
3648
4205
|
|
|
@@ -3684,8 +4241,13 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3684
4241
|
React.createElement('div', { className: 'dsm-detail-title' }, mt('mcp.detail.status')),
|
|
3685
4242
|
React.createElement('div', { className: 'dsm-feedback' + (detailStatus.cls === 'dsm-failed' ? ' dsm-error' : ' dsm-warning') }, detailHint)) : null,
|
|
3686
4243
|
React.createElement('div', { className: 'dsm-detail-section' },
|
|
3687
|
-
|
|
3688
|
-
|
|
4244
|
+
// 字数计数摆在小标题同一行右侧:备注的上限就是注入段的截断长度(`MCP_NOTE_MAX`)。
|
|
4245
|
+
// 超限标红沿用记忆页描述那套(`dsm-char-over`)—— 服务端不校验备注长度,
|
|
4246
|
+
// 这条之前存的长备注会整条存着、注入时被截掉,得让人看得见超了多少。
|
|
4247
|
+
React.createElement('div', { className: 'dsm-budget-meta' },
|
|
4248
|
+
React.createElement('div', { className: 'dsm-detail-title' }, mt('mcp.detail.note')),
|
|
4249
|
+
React.createElement('span', { className: 'dsm-char-count' + (String(noteDraft || '').length > MCP_NOTE_MAX ? ' dsm-char-over' : '') }, String(noteDraft || '').length + ' / ' + MCP_NOTE_MAX)),
|
|
4250
|
+
React.createElement('textarea', { className: 'dsm-control dsm-textarea-sm', value: noteDraft, maxLength: MCP_NOTE_MAX, placeholder: mt('mcp.detail.note.placeholder'), onChange: (ev) => setNoteDraft(ev.target.value) }),
|
|
3689
4251
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
3690
4252
|
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')))),
|
|
3691
4253
|
React.createElement('div', { className: 'dsm-detail-section' },
|
|
@@ -3939,7 +4501,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3939
4501
|
id: id,
|
|
3940
4502
|
...(content ? { content: content } : {}),
|
|
3941
4503
|
...(from ? { from: from } : {}),
|
|
3942
|
-
//
|
|
4504
|
+
// 描述存 meta.json,**不写进 AGENTS.md**(那个文件的正文会被原样注入)。
|
|
4505
|
+
// 0.14.0 起模型也在 `prompt_manager_list` 的清单里看得到它,所以有了字数上限
|
|
4506
|
+
// (输入框那枚 maxLength / 计数就是它)。
|
|
3943
4507
|
description: String(description == null ? '' : description),
|
|
3944
4508
|
}).then(function (res) {
|
|
3945
4509
|
setBusy(null)
|
|
@@ -4096,8 +4660,14 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4096
4660
|
React.createElement('input', { className: 'dsm-control', value: editModal.nextId || '', onChange: function (e) { setEditModal(Object.assign({}, editModal, { nextId: e.target.value, error: null })) } }),
|
|
4097
4661
|
React.createElement('p', { className: 'dsm-help' }, t('agm.field.id.hint'))),
|
|
4098
4662
|
React.createElement('div', { className: 'dsm-field' },
|
|
4099
|
-
|
|
4100
|
-
|
|
4663
|
+
// 上限与宿主侧 `src/prompts/service.ts` 的 `DEFAULT_PRESET_DESC_MAX_LENGTH`
|
|
4664
|
+
// 同数(客户端镜像 `PRESET_DESC_MAX`):描述从 0.14.0 起会进模型清单
|
|
4665
|
+
// (`prompt_manager_list` 要打出来,模型才能在预设之间做选择),所以它有了
|
|
4666
|
+
// 预算 —— 写多少模型就能读到多少,与 MCP 备注那条同一条口径。
|
|
4667
|
+
React.createElement('div', { className: 'dsm-budget-meta' },
|
|
4668
|
+
React.createElement('label', { className: 'dsm-label' }, t('agm.field.desc')),
|
|
4669
|
+
React.createElement('span', { className: 'dsm-char-count' + (String(editModal.description || '').length > PRESET_DESC_MAX ? ' dsm-char-over' : '') }, String(editModal.description || '').length + ' / ' + PRESET_DESC_MAX)),
|
|
4670
|
+
React.createElement('input', { className: 'dsm-control', maxLength: PRESET_DESC_MAX, placeholder: t('agm.field.desc.placeholder'), value: editModal.description || '', onChange: function (e) { setEditModal(Object.assign({}, editModal, { description: e.target.value })) } })),
|
|
4101
4671
|
React.createElement('div', { className: 'dsm-field' },
|
|
4102
4672
|
React.createElement('label', { className: 'dsm-label' }, t('agm.field.content')),
|
|
4103
4673
|
React.createElement('textarea', { className: 'dsm-control dsm-textarea-lg', value: editModal.content, onChange: function (e) { setEditModal(Object.assign({}, editModal, { content: e.target.value })) } })),
|
|
@@ -4112,8 +4682,10 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4112
4682
|
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 })) } }),
|
|
4113
4683
|
React.createElement('p', { className: 'dsm-help' }, t('agm.field.id.hint'))),
|
|
4114
4684
|
React.createElement('div', { className: 'dsm-field' },
|
|
4115
|
-
React.createElement('
|
|
4116
|
-
|
|
4685
|
+
React.createElement('div', { className: 'dsm-budget-meta' },
|
|
4686
|
+
React.createElement('label', { className: 'dsm-label' }, t('agm.field.desc')),
|
|
4687
|
+
React.createElement('span', { className: 'dsm-char-count' + (String(createModal.description || '').length > PRESET_DESC_MAX ? ' dsm-char-over' : '') }, String(createModal.description || '').length + ' / ' + PRESET_DESC_MAX)),
|
|
4688
|
+
React.createElement('input', { className: 'dsm-control', maxLength: PRESET_DESC_MAX, placeholder: t('agm.field.desc.placeholder'), value: createModal.description || '', onChange: function (e) { setCreateModal(Object.assign({}, createModal, { description: e.target.value })) } })),
|
|
4117
4689
|
(state.presets || []).length ? React.createElement('div', { className: 'dsm-field' },
|
|
4118
4690
|
React.createElement('label', { className: 'dsm-label' }, t('agm.field.copyFrom')),
|
|
4119
4691
|
React.createElement(SourceSelect, {
|
|
@@ -4203,10 +4775,44 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4203
4775
|
var el = document.getElementById('dsm-tab-' + TABS[next][0])
|
|
4204
4776
|
if (el && el.focus) el.focus()
|
|
4205
4777
|
}
|
|
4778
|
+
// 键盘导航第一步:`/` 聚焦当前页的搜索框、Esc 清空它 —— 只做这两个键。
|
|
4779
|
+
//
|
|
4780
|
+
// 为什么挂在 `section` 上而不是 `window`:本插件只是宿主设置面板里的一个块,
|
|
4781
|
+
// 全局监听会抢走宿主自己的键。挂在根上则只覆盖我们自己的 UI(焦点落在哪一页
|
|
4782
|
+
// 就筛哪一页),也不需要在 effect 里管监听器的增删。
|
|
4783
|
+
var onSectionKeyDown = function (e) {
|
|
4784
|
+
var el = e.target
|
|
4785
|
+
if (!el || typeof el.closest !== 'function') return
|
|
4786
|
+
// 弹窗开着时整个放行:Esc 关窗与 Tab 焦点陷阱由 Modal 自己处理(见 trapModalFocus)。
|
|
4787
|
+
if (el.closest('.dsm-modal')) return
|
|
4788
|
+
if (e.key === '/') {
|
|
4789
|
+
// 焦点已在任意文本控件里 → `/` 是用户要打的字符,不是快捷键。
|
|
4790
|
+
if (el.closest('input, textarea, select, [contenteditable]')) return
|
|
4791
|
+
var boxes = sectionRef.current ? sectionRef.current.querySelectorAll('input.dsm-search') : []
|
|
4792
|
+
for (var i = 0; i < boxes.length; i += 1) {
|
|
4793
|
+
// 只挑真正可见的那个:MCP 页的弹窗里还藏着一枚搜索框,聚焦到
|
|
4794
|
+
// `display:none` 的子树里等于把焦点弄丢(用户只会觉得"按了没反应")。
|
|
4795
|
+
var box = /** @type {HTMLInputElement} */ (boxes[i])
|
|
4796
|
+
if (box.offsetParent !== null) { e.preventDefault(); box.focus(); return }
|
|
4797
|
+
}
|
|
4798
|
+
return
|
|
4799
|
+
}
|
|
4800
|
+
if (e.key !== 'Escape') return
|
|
4801
|
+
var search = /** @type {HTMLInputElement} */ (el.closest('input.dsm-search'))
|
|
4802
|
+
// 已经是空的就不拦:让这次 Esc 继续往宿主冒(关设置窗口),别让快捷键吃掉别的行为。
|
|
4803
|
+
if (!search || !search.value) return
|
|
4804
|
+
// 清空要走原生 setter 再复演一次 input 事件:直接改 `search.value` 不触发 React 的
|
|
4805
|
+
// onChange,界面上看着清了、过滤条件还留着(列表仍是筛过的)。
|
|
4806
|
+
var desc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')
|
|
4807
|
+
if (!desc || !desc.set) return
|
|
4808
|
+
e.preventDefault()
|
|
4809
|
+
desc.set.call(search, '')
|
|
4810
|
+
search.dispatchEvent(new Event('input', { bubbles: true }))
|
|
4811
|
+
}
|
|
4206
4812
|
// 子节点用**带 key 的数组**给(而不是并列的三个实参):并列实参会退化成按位置
|
|
4207
4813
|
// 认子节点,令牌提示一出现/消失就把 `page` 换一个位置 → React 把整页卸载重挂
|
|
4208
4814
|
// (页面状态与列表全丢,看起来像"页面自己刷新了")。带 key 之后位置永远稳定。
|
|
4209
|
-
return React.createElement('section', { ref: sectionRef, className: 'dsm-section' }, [
|
|
4815
|
+
return React.createElement('section', { ref: sectionRef, className: 'dsm-section', onKeyDown: onSectionKeyDown }, [
|
|
4210
4816
|
React.createElement('div', { key: 'tabs', className: 'dsm-tabs-shell' },
|
|
4211
4817
|
React.createElement('div', { className: 'dsm-tabs', role: 'tablist', 'aria-label': t('nav.title'), onKeyDown: onTabKeyDown },
|
|
4212
4818
|
TABS.map(function (entry) {
|
|
@@ -4269,7 +4875,6 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4269
4875
|
|
|
4270
4876
|
|
|
4271
4877
|
|
|
4272
|
-
function translateOrFallback(t, key, fallback, params) { var value = t(key, params); return typeof value === "string" && value !== key ? value : fallback; }
|
|
4273
4878
|
/**
|
|
4274
4879
|
* 字段下的多行说明 → 条目清单(与导入弹窗的「文件要求」同款样式,见 dsm-help-list)。
|
|
4275
4880
|
* 词典值按 `\n` 分段,一段一条:一整句话里塞三件事时,条目比句子好扫。
|
|
@@ -4486,6 +5091,17 @@ function enabledMemoryCount(rules) {
|
|
|
4486
5091
|
/** 已移除的来源(不在主列表里,但要让用户看得到并恢复)。 */
|
|
4487
5092
|
function removedSkillRoots(roots) { return (roots || []).filter(function (root) { return root.removed === true; }); }
|
|
4488
5093
|
var MAX_UPLOAD_ARCHIVE_BYTES = 32 << 20, MAX_UPLOAD_ENTRY_BYTES = 32 << 20, MAX_UPLOAD_TOTAL_BYTES = 64 << 20, MAX_UPLOAD_ENTRIES = 1000;
|
|
5094
|
+
/**
|
|
5095
|
+
* 注入段里"模型实际能看到多长"的截断长度,同时当输入框的 `maxLength` 与字数计数的分母
|
|
5096
|
+
* —— 写多少就能被看到多少,不会出现"界面让你写 500 字、模型只读到前 300"。
|
|
5097
|
+
*
|
|
5098
|
+
* ⚠️ 宿主侧各有一份真相:`src/mcp/state-section.ts` 的 `DEFAULT_MCP_NOTE_MAX_LENGTH`、
|
|
5099
|
+
* `src/skills/catalog.ts` 与 `src/subagents/catalog.ts` 的 `..._DESCRIPTION_MAX_LENGTH`、
|
|
5100
|
+
* `src/prompts/service.ts` 的 `DEFAULT_PRESET_DESC_MAX_LENGTH`(提示词预设描述 —— 0.14.0
|
|
5101
|
+
* 起它会进模型清单,所以有了预算)。
|
|
5102
|
+
* 两边没有编译期约束(计划文档 A3 说的"同一事实两处写"),改一处必须改另一处。
|
|
5103
|
+
*/
|
|
5104
|
+
var MCP_NOTE_MAX = 300, CATALOG_DESC_MAX = 500, PRESET_DESC_MAX = 300;
|
|
4489
5105
|
function uploadFilePath(file) { return String(file && (file._dssmPath || file.webkitRelativePath || file.name) || "").replace(/\\/g, "/"); }
|
|
4490
5106
|
function inspectUploadSelection(files) {
|
|
4491
5107
|
var list = Array.prototype.slice.call(files || []); if (!list.length) return null;
|
|
@@ -5373,7 +5989,7 @@ function enabledMemoryCount(rules) {
|
|
|
5373
5989
|
(!anyLocked && sceneName) ? h("div", { key: "scenebanner", className: "dsm-feedback dsm-warning", role: "status" }, t("scene.switch.banner", { scene: sceneName })) : null,
|
|
5374
5990
|
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 }))), undo && undo.scope == null ? h(UndoBar, { key: "undo", undo: undo, busy: busy, t: t, onUndo: undoBulk, onClose: dismissUndo }) : null, 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", ref: flipRef }, roots.map(renderRoot))]);
|
|
5375
5991
|
|
|
5376
|
-
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")))));
|
|
5992
|
+
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("div", { className: "dsm-budget-meta" }, h("span", { className: "dsm-label" }, t(field[1])), field[0] === "description" ? h("span", { className: "dsm-char-count" }, String(form.description || "").length + " / " + CATALOG_DESC_MAX) : null), h("input", { className: "dsm-control", value: form[field[0]], placeholder: t(field[2]), maxLength: field[0] === "description" ? CATALOG_DESC_MAX : undefined, 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")))));
|
|
5377
5993
|
// D13:导入弹窗统一用共享 ImportModal(能力取三者并集:拖拽区 + 已选文件行 +
|
|
5378
5994
|
// 文件要求清单 + 结果反馈)。技能页只保留自己的校验与提交逻辑。
|
|
5379
5995
|
if (modal === "import") content.push(h(ImportModal, {
|
|
@@ -6066,13 +6682,12 @@ function enabledMemoryCount(rules) {
|
|
|
6066
6682
|
}
|
|
6067
6683
|
|
|
6068
6684
|
/**
|
|
6069
|
-
*
|
|
6685
|
+
* 场景描述最多存多少个字(新建/编辑表单的 `maxLength` 与字数计数都用它)。
|
|
6070
6686
|
*
|
|
6071
|
-
*
|
|
6072
|
-
*
|
|
6073
|
-
* 上限同时写在新建/编辑表单的 `maxLength` 上(见场景表单),显示层再裁一次兜住历史数据。
|
|
6687
|
+
* 卡片那一行**不靠这个数撑腰**:`.dsm-scene-tile-desc` 是 `nowrap` + 省略号,
|
|
6688
|
+
* 再长也只占一行、不会把卡片撑成纵向。显示层仍按它裁一次,兜住历史数据。
|
|
6074
6689
|
*/
|
|
6075
|
-
var SCENE_DESC_MAX =
|
|
6690
|
+
var SCENE_DESC_MAX = 300
|
|
6076
6691
|
/** 记忆描述在勾选行里最多显示多少个字:记忆正文可能很长,行内只留一行,超长截断(全文进 title)。 */
|
|
6077
6692
|
var MEM_DESC_MAX = 80
|
|
6078
6693
|
/** 压平空白并裁到 max 个字符(含省略号);返回空串 = 没有描述。 */
|
|
@@ -6151,6 +6766,19 @@ function enabledMemoryCount(rules) {
|
|
|
6151
6766
|
var trash = sts[0], setTrash = sts[1]
|
|
6152
6767
|
var stb = React.useState(false)
|
|
6153
6768
|
var trashBusy = stb[0], setTrashBusy = stb[1]
|
|
6769
|
+
// 「进入场景」前的改动预览卡(null = 关闭;{name, loading, error, plan})。
|
|
6770
|
+
// 只有**进入**这条路走它:退出是按快照回原位,不是新做一次改动,再问一遍纯添摩擦。
|
|
6771
|
+
var pvs = React.useState(null)
|
|
6772
|
+
var preview = pvs[0], setPreview = pvs[1]
|
|
6773
|
+
// 要不要弹那张卡(侧车 `scene-settings.json`)。默认开;卡上「不再显示」与右上那颗
|
|
6774
|
+
// 按钮改的都是它。读不到时按默认(弹)—— 少弹一次卡是"我没被告知就改了环境",
|
|
6775
|
+
// 多弹一次只是多按一下。
|
|
6776
|
+
var eps = React.useState(true)
|
|
6777
|
+
var enterPreview = eps[0], setEnterPreview = eps[1]
|
|
6778
|
+
// 场景页搜索:与 MCP / 技能 / 提示词 / 子智能体 / 记忆页同一形态(防抖 + `matchByText`)。
|
|
6779
|
+
var qs = React.useState('')
|
|
6780
|
+
var query = qs[0], setQuery = qs[1]
|
|
6781
|
+
var dq = useDebouncedValue(query)
|
|
6154
6782
|
var flipRef = React.useRef(null)
|
|
6155
6783
|
useFlipReorder(flipRef)
|
|
6156
6784
|
function loadTrash(keepOpen) {
|
|
@@ -6182,6 +6810,13 @@ function enabledMemoryCount(rules) {
|
|
|
6182
6810
|
apiCall('scene-mode-get', {}).then(function (m) {
|
|
6183
6811
|
if (m && m.ok) setData(function (prev) { return Object.assign({}, prev, { mode: m.mode || EMPTY_MODE, archives: m.archives || {} }) })
|
|
6184
6812
|
}).catch(function () { /* 模式信息拿不到按空模式渲染;主数据 rules-list 的错误有自己的通道 */ })
|
|
6813
|
+
// 悬空引用体检:跟着列表一起读一次(这一页就是档案与绑定的所在地)。
|
|
6814
|
+
// **只在首次加载与手动刷新时跑** —— 它要把四个域各枚举一遍(服务器 / 技能 /
|
|
6815
|
+
// 人设 / 预设),而页内那些"操作完顺手重读"的 silent 刷新一次点开关就来一趟,
|
|
6816
|
+
// 为一个横幅把列表拖慢不值。横幅因此可能滞后一次操作,可它本来就是提示,不是控件。
|
|
6817
|
+
if (!silent) apiCall('state-doctor', {}).then(function (d) {
|
|
6818
|
+
setData(function (prev) { return Object.assign({}, prev, { doctor: d && d.ok ? { findings: d.findings || [], skipped: d.skipped || [] } : null }) })
|
|
6819
|
+
}).catch(function () { setData(function (prev) { return Object.assign({}, prev, { doctor: null }) }) })
|
|
6185
6820
|
apiCall('rules-list', {}).then(function (r) {
|
|
6186
6821
|
// rules 也留下来:档案弹窗「记忆」段的默认勾选要用每条记忆的启用状态
|
|
6187
6822
|
//(scene-inventory 的 memories 里没有 enabled,客户端自己算,省一次宿主改动)。
|
|
@@ -6190,6 +6825,24 @@ function enabledMemoryCount(rules) {
|
|
|
6190
6825
|
}).catch(function (e) { setData(function (prev) { return Object.assign({}, prev, { loading: false, error: errMsg(e) }) }) })
|
|
6191
6826
|
}
|
|
6192
6827
|
React.useEffect(function () { refresh() }, [])
|
|
6828
|
+
/** 读一次界面设置(读不到就保持默认 = 弹卡,本页其它功能不受影响)。 */
|
|
6829
|
+
function loadSceneSettings() {
|
|
6830
|
+
apiCall('scene-settings', {}).then(function (res) {
|
|
6831
|
+
if (res && res.ok && res.settings) setEnterPreview(res.settings.enterPreview !== false)
|
|
6832
|
+
}).catch(function () { /* 读不到 → 保持默认(弹卡) */ })
|
|
6833
|
+
}
|
|
6834
|
+
React.useEffect(function () { loadSceneSettings() }, [])
|
|
6835
|
+
/**
|
|
6836
|
+
* 改「进入场景前弹不弹卡」。乐观更新(点一下就该翻转),失败回读一次并弹结果条 ——
|
|
6837
|
+
* 这颗按钮的状态必须与侧车一致,否则会出现"以为关了、下次还弹"。
|
|
6838
|
+
*/
|
|
6839
|
+
function saveEnterPreview(next) {
|
|
6840
|
+
setEnterPreview(next)
|
|
6841
|
+
apiCall('scene-settings', { set: true, enterPreview: next }).then(function (res) {
|
|
6842
|
+
if (res && res.ok && res.settings) setEnterPreview(res.settings.enterPreview !== false)
|
|
6843
|
+
else { loadSceneSettings(); setResult({ ok: false, text: translateError(t, res) }) }
|
|
6844
|
+
}).catch(function (e) { loadSceneSettings(); setResult({ ok: false, text: errMsg(e) }) })
|
|
6845
|
+
}
|
|
6193
6846
|
/**
|
|
6194
6847
|
* 切场景的结果提示:宿主会把场景绑定的提示词**写进 `~/.dsh/AGENTS.md`**,
|
|
6195
6848
|
* 因此必须如实说清写没写、写的是哪份、或者为什么没写成。
|
|
@@ -6226,6 +6879,7 @@ function enabledMemoryCount(rules) {
|
|
|
6226
6879
|
function toggleScene(scene) {
|
|
6227
6880
|
if (busy || scene.shared || scene.global) return
|
|
6228
6881
|
if (scene.active === true) exitMode()
|
|
6882
|
+
else if (enterPreview) askEnter(scene.name)
|
|
6229
6883
|
else enterMode(scene.name)
|
|
6230
6884
|
}
|
|
6231
6885
|
/** 场景锁定(v0.8):锁上后五个管理域整体只读(场景页 + 各功能页);启停不受影响。 */
|
|
@@ -6574,8 +7228,12 @@ function enabledMemoryCount(rules) {
|
|
|
6574
7228
|
},
|
|
6575
7229
|
React.createElement('div', { className: 'dsm-modal-body' },
|
|
6576
7230
|
React.createElement('div', { className: 'dsm-field' },
|
|
6577
|
-
|
|
6578
|
-
|
|
7231
|
+
// 上限 = 注入段的截断长度(`MCP_NOTE_MAX`),计数与标签同行(与场景描述框同款);
|
|
7232
|
+
// 存量的超长备注标红(服务端不校验长度,只有注入时截)。
|
|
7233
|
+
React.createElement('div', { className: 'dsm-budget-meta' },
|
|
7234
|
+
React.createElement('span', { className: 'dsm-label' }, t('scenes.mcp.noteLabel')),
|
|
7235
|
+
React.createElement('span', { className: 'dsm-char-count' + (String(noteVal || '').length > MCP_NOTE_MAX ? ' dsm-char-over' : '') }, String(noteVal || '').length + ' / ' + MCP_NOTE_MAX)),
|
|
7236
|
+
React.createElement('input', { className: 'dsm-control', type: 'text', value: noteVal, maxLength: MCP_NOTE_MAX, placeholder: t('scenes.mcp.notePlaceholder'), onChange: function (e) { setMcpNote(server, e.target.value) } }),
|
|
6579
7237
|
React.createElement('p', { className: 'dsm-help' }, t('scenes.mcp.noteHint'))),
|
|
6580
7238
|
React.createElement('div', { className: 'dsm-field' },
|
|
6581
7239
|
React.createElement('div', { className: 'dsm-mcp-edit-tools-head' },
|
|
@@ -6837,6 +7495,118 @@ function enabledMemoryCount(rules) {
|
|
|
6837
7495
|
} else setResult({ ok: false, text: translateError(t, res) })
|
|
6838
7496
|
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: errMsg(e) }) })
|
|
6839
7497
|
}
|
|
7498
|
+
/**
|
|
7499
|
+
* 「进入场景」先问一次:拉 `scene-mode-preview` 把这次会改的东西列出来。
|
|
7500
|
+
*
|
|
7501
|
+
* 为什么值得多这一步:进入场景是本页**唯一一次改运行时环境**的动作(服务器级 +
|
|
7502
|
+
* 工具级 + 来源级 + 技能级 + 人设 + 备注六处),而它此前的反馈只有事后那句结果条。
|
|
7503
|
+
* 用户实测的用法是"点进去看看",出来才发现十几个技能被停了 —— 只能靠退出还原,
|
|
7504
|
+
* 而还原读的是快照,快照记的是"进场景前",不是"你以为你改之前的样子"。
|
|
7505
|
+
* 预览失败**不拦**:卡上留一条错误 + 「仍然进入」,别让一个附属读数变成进入场景的
|
|
7506
|
+
* 新前置条件。
|
|
7507
|
+
*/
|
|
7508
|
+
function askEnter(name) {
|
|
7509
|
+
setPreview({ name: name, loading: true, error: null, plan: null })
|
|
7510
|
+
apiCall('scene-mode-preview', { scene: name }).then(function (res) {
|
|
7511
|
+
if (res && res.ok) setPreview({ name: name, loading: false, error: null, plan: res })
|
|
7512
|
+
else setPreview({ name: name, loading: false, error: translateError(t, res), plan: null })
|
|
7513
|
+
}).catch(function (e) { setPreview({ name: name, loading: false, error: errMsg(e), plan: null }) })
|
|
7514
|
+
}
|
|
7515
|
+
function confirmEnter() {
|
|
7516
|
+
if (!preview) return
|
|
7517
|
+
var name = preview.name
|
|
7518
|
+
setPreview(null)
|
|
7519
|
+
enterMode(name)
|
|
7520
|
+
}
|
|
7521
|
+
/**
|
|
7522
|
+
* 名单只铺前若干个:一行塞 60 个名字,卡就变成另一份档案编辑器了。
|
|
7523
|
+
*
|
|
7524
|
+
* `nameOnly`:技能键是 `<来源>/<名字>`,而自定义来源的键是一串机器名
|
|
7525
|
+
* (`custom-d38da02b873bdb5c`)—— 铺在卡上没人认得出那是哪个技能。技能只取名字
|
|
7526
|
+
* (最后一段),来源与人设本来就是短名(用户 2026-09-23 裁定)。
|
|
7527
|
+
*/
|
|
7528
|
+
function previewNames(list, nameOnly) {
|
|
7529
|
+
var names = (list || []).map(function (item) {
|
|
7530
|
+
var text = String(item)
|
|
7531
|
+
if (!nameOnly) return text
|
|
7532
|
+
var parts = text.split('/')
|
|
7533
|
+
return parts[parts.length - 1] || text
|
|
7534
|
+
})
|
|
7535
|
+
var shown = names.slice(0, 12).join('、')
|
|
7536
|
+
return names.length > 12 ? shown + t('scenes.preview.more') : shown
|
|
7537
|
+
}
|
|
7538
|
+
/** 预览卡正文:`scene-mode-preview` 算出的每个方向一行,没有改动的方向不出行。 */
|
|
7539
|
+
function previewRows(plan) {
|
|
7540
|
+
var out = []
|
|
7541
|
+
if (plan.hasArchive !== true) out.push(React.createElement('div', { key: 'noarch', className: 'dsm-feedback dsm-warning' }, t('scenes.preview.noArchive')))
|
|
7542
|
+
if (plan.exit && plan.exit.scene) out.push(React.createElement('p', { key: 'exit', className: 'dsm-help' }, t(plan.exit.snapshotMissing ? 'scenes.preview.exitNoSnapshot' : 'scenes.preview.exitFirst', { scene: plan.exit.scene })))
|
|
7543
|
+
var mcp = plan.mcp || {}
|
|
7544
|
+
var sk = plan.skills || {}
|
|
7545
|
+
var sub = plan.subagents || {}
|
|
7546
|
+
var changed = 0
|
|
7547
|
+
/**
|
|
7548
|
+
* 进入 `target` 之后**会注入**的记忆:该场景自己 + 恒常启用的那两个(服务端给
|
|
7549
|
+
* `_shared`(公共基线)与 `global`(全局)的 `active` 恒为 true,所以不必在客户端
|
|
7550
|
+
* 写死场景名)。逐条判据与记忆页那一行同源(`!shadowed && enabled !== false && 场景 active`),
|
|
7551
|
+
* 差别只有一处:目标场景按"进入后它就是活动场景"算 —— 它现在的 `meta.active` 还是 false。
|
|
7552
|
+
*/
|
|
7553
|
+
function memoriesInScopeAfterEnter(target) {
|
|
7554
|
+
var meta = {}
|
|
7555
|
+
;(data.scenes || []).forEach(function (s) { meta[String(s.name)] = s })
|
|
7556
|
+
return (data.rules || []).filter(function (r) {
|
|
7557
|
+
if (!r || r.shadowed === true || r.enabled === false) return false
|
|
7558
|
+
var scene = sceneOfGroup(r.group)
|
|
7559
|
+
if (scene === target) return true
|
|
7560
|
+
var m = meta[scene]
|
|
7561
|
+
return !!(m && m.active !== false)
|
|
7562
|
+
})
|
|
7563
|
+
}
|
|
7564
|
+
/** 一行改动:`names` 为空就不出行(卡片只报真的会变的方向)。 */
|
|
7565
|
+
function pushRow(key, list, nameOnly) {
|
|
7566
|
+
var names = list || []
|
|
7567
|
+
if (!names.length) return
|
|
7568
|
+
changed += 1
|
|
7569
|
+
out.push(React.createElement('p', { key: key, className: 'dsm-help' }, t(key, { count: names.length, names: previewNames(names, nameOnly === true) })))
|
|
7570
|
+
}
|
|
7571
|
+
// 按域分组:MCP 改动 → MCP 结果 → 技能 → 人设 → 记忆(改动 + 结果)。
|
|
7572
|
+
pushRow('scenes.preview.row.serversOn', mcp.serversOn)
|
|
7573
|
+
pushRow('scenes.preview.row.serversOff', mcp.serversOff)
|
|
7574
|
+
pushRow('scenes.preview.row.toolsOn', mcp.toolsOn)
|
|
7575
|
+
pushRow('scenes.preview.row.toolsOff', mcp.toolsOff)
|
|
7576
|
+
// 「进入后启用 N 台 MCP 服务器」:**结果**口径,与上面四行的"会改什么"互补 ——
|
|
7577
|
+
// 上面全空时这一段就整段消失,用户读起来像缺了信息(2026-09-23 裁定)。
|
|
7578
|
+
var enterServers = (plan.enter && plan.enter.servers) || []
|
|
7579
|
+
if (enterServers.length && plan.noChange !== true) {
|
|
7580
|
+
out.push(React.createElement('p', { key: 'enter-servers', className: 'dsm-help' }, t('scenes.preview.enter.servers', { count: enterServers.length, names: previewNames(enterServers) })))
|
|
7581
|
+
}
|
|
7582
|
+
pushRow('scenes.preview.row.sourcesOn', sk.sourcesOn)
|
|
7583
|
+
pushRow('scenes.preview.row.sourcesOff', sk.sourcesOff)
|
|
7584
|
+
pushRow('scenes.preview.row.skillsOn', sk.on, true)
|
|
7585
|
+
pushRow('scenes.preview.row.skillsOff', sk.off, true)
|
|
7586
|
+
pushRow('scenes.preview.row.personasOn', sub.on)
|
|
7587
|
+
pushRow('scenes.preview.row.personasOff', sub.off)
|
|
7588
|
+
if (mcp.notes) {
|
|
7589
|
+
changed += 1
|
|
7590
|
+
out.push(React.createElement('p', { key: 'notes', className: 'dsm-help' }, t('scenes.preview.row.notes', { count: mcp.notes })))
|
|
7591
|
+
}
|
|
7592
|
+
// 「进入后启用 N 条记忆」:同上,结果口径(含公共基线与全局,它们也在范围里)。
|
|
7593
|
+
// 此前这里还有一行"记忆注入范围收窄到「X」(公共基线与全局不受影响)"—— 用户
|
|
7594
|
+
// 2026-09-23 裁定删掉:下面这句把"有几条、是哪些"说全了,那一行只是把同一件事
|
|
7595
|
+
// 换个说法再说一遍。
|
|
7596
|
+
if (plan.target && plan.noChange !== true) {
|
|
7597
|
+
var memScope = memoriesInScopeAfterEnter(plan.target)
|
|
7598
|
+
if (memScope.length) {
|
|
7599
|
+
out.push(React.createElement('p', { key: 'enter-memories', className: 'dsm-help' }, t('scenes.preview.enter.memories', {
|
|
7600
|
+
count: memScope.length,
|
|
7601
|
+
names: previewNames(memScope.map(function (r) { return String(r.name || r.id) })),
|
|
7602
|
+
})))
|
|
7603
|
+
}
|
|
7604
|
+
}
|
|
7605
|
+
if (plan.truncated) out.push(React.createElement('p', { key: 'more', className: 'dsm-help' }, t('scenes.preview.truncated')))
|
|
7606
|
+
if ((plan.stale || []).length) out.push(React.createElement('p', { key: 'stale', className: 'dsm-help' }, t('scenes.preview.stale', { count: plan.stale.length, items: previewNames(plan.stale) })))
|
|
7607
|
+
if (!changed && !out.length) out.push(React.createElement('p', { key: 'none', className: 'dsm-help' }, t('scenes.preview.nochange')))
|
|
7608
|
+
return out
|
|
7609
|
+
}
|
|
6840
7610
|
// 「进入某个场景」= 应用它的档案 + 启用它(单选),于是它的记忆与绑定的提示词
|
|
6841
7611
|
// 一起生效——用户裁定:「进入其中一个场景,提示词就启动成场景的设置的」。
|
|
6842
7612
|
function enterMode(name) {
|
|
@@ -6896,17 +7666,27 @@ function enabledMemoryCount(rules) {
|
|
|
6896
7666
|
active: presets.filter(function (s) { return s.active === true }).length,
|
|
6897
7667
|
archives: presets.filter(function (s) { return !!data.archives[s.name] }).length,
|
|
6898
7668
|
}
|
|
7669
|
+
/**
|
|
7670
|
+
* 搜索只过滤列表,不动三格统计 —— 与子智能体页同口径:统计答"这台机器上有几个",
|
|
7671
|
+
* 列表答"当前筛选下看得见几个"。匹配范围 = 卡片上真会显示的四样:目录名、显示名、
|
|
7672
|
+
* 一行描述、绑定的提示词预设 id(`_shared` / `global` 这些保留名不在卡片上,不匹配)。
|
|
7673
|
+
*/
|
|
7674
|
+
var visible = presets.filter(function (s) {
|
|
7675
|
+
return matchByText([s.name, sceneLabel(data.scenes, s.name), sceneTileDesc(s), s.prompt], dq)
|
|
7676
|
+
})
|
|
6899
7677
|
return {
|
|
6900
7678
|
legacyAllScenes: legacy,
|
|
6901
7679
|
presetScenes: presets,
|
|
6902
7680
|
sceneStats: stats,
|
|
6903
|
-
|
|
7681
|
+
visibleScenes: visible,
|
|
7682
|
+
orderedScenes: enabledFirst(visible, function (scene) { return scene.active === true; }),
|
|
6904
7683
|
}
|
|
6905
|
-
}, [data.scenes, data.archives])
|
|
7684
|
+
}, [data.scenes, data.archives, dq])
|
|
6906
7685
|
var legacyAllScenes = sceneView.legacyAllScenes
|
|
6907
7686
|
var presetScenes = sceneView.presetScenes
|
|
6908
7687
|
var sceneStats = sceneView.sceneStats
|
|
6909
7688
|
var orderedScenes = sceneView.orderedScenes
|
|
7689
|
+
var visibleScenes = sceneView.visibleScenes
|
|
6910
7690
|
/**
|
|
6911
7691
|
* 档案里已配的东西,一行摘要(没绑的域不出现)。
|
|
6912
7692
|
*
|
|
@@ -6935,12 +7715,25 @@ function enabledMemoryCount(rules) {
|
|
|
6935
7715
|
React.createElement('div', { className: 'dsm-actions' },
|
|
6936
7716
|
refreshButton(t, busy || data.loading, { className: 'dsm-btn dsm-btn-secondary', disabled: busy || data.loading, onClick: function () { refresh() } }),
|
|
6937
7717
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: openCreateScene }, t('memory.btn.newScene')),
|
|
7718
|
+
// 进入场景前的提醒开关(用户 2026-09-23 裁定放在「回收站」左边)。按钮文字是
|
|
7719
|
+
// **动作**:现在会弹 → 写「关闭提醒」;已经关掉 → 写「开启提醒」。
|
|
7720
|
+
React.createElement('button', {
|
|
7721
|
+
type: 'button', className: 'dsm-btn dsm-btn-secondary', title: t('scenes.preview.toggle.title'),
|
|
7722
|
+
onClick: function () { saveEnterPreview(!enterPreview) },
|
|
7723
|
+
}, t(enterPreview ? 'scenes.preview.toggle.off' : 'scenes.preview.toggle.on')),
|
|
6938
7724
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: function () { loadTrash(false) } }, t('trash.btn.open')))),
|
|
6939
7725
|
React.createElement('div', { key: 'stats', className: 'dsm-summary' },
|
|
6940
7726
|
[[sceneStats.total, t('scenes.stat.total')], [sceneStats.active, t('scenes.stat.active')], [sceneStats.archives, t('scenes.stat.archives')]].map(function (item) {
|
|
6941
7727
|
return React.createElement('div', { key: item[1], className: 'dsm-stat' },
|
|
6942
7728
|
React.createElement('strong', null, item[0]), item[1])
|
|
6943
7729
|
})),
|
|
7730
|
+
// 搜索框:统计条之后(与 MCP / 技能 / 记忆 / 提示词 / 子智能体页同一位置与样式)。
|
|
7731
|
+
React.createElement('div', { key: 'filters', className: 'dsm-filters' },
|
|
7732
|
+
React.createElement('input', {
|
|
7733
|
+
className: 'dsm-control dsm-search', value: query, 'aria-label': t('search'),
|
|
7734
|
+
placeholder: t('scenes.search.placeholder'),
|
|
7735
|
+
onChange: function (e) { setQuery(e.target.value) },
|
|
7736
|
+
})),
|
|
6944
7737
|
// 当前模式条:**只在真的进入了模式时才出现**。
|
|
6945
7738
|
// 以前没有模式时也常驻一条「自由模式 + 一句解释」,用户反馈「这个是干什么的,感觉没什么用」——
|
|
6946
7739
|
// 静态解释占一整条,而「没有条 = 没有模式」本来就不言自明。现在它只承载有状态的信息:
|
|
@@ -6960,9 +7753,29 @@ function enabledMemoryCount(rules) {
|
|
|
6960
7753
|
legacyAllScenes.length > 1 ? React.createElement('div', { key: 'legacyall', className: 'dsm-feedback dsm-warning' },
|
|
6961
7754
|
React.createElement('span', null, t('scenes.legacyAll', { count: legacyAllScenes.length })),
|
|
6962
7755
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { enterMode(legacyAllScenes[0].name) } }, t('scenes.legacyAll.fix'))) : null,
|
|
7756
|
+
// 改名 / 删除留下的悬空引用:这一条列表上看着还绑着,运行时按那个名字已经找不到了。
|
|
7757
|
+
// 只说"哪一条指向谁、那个谁不在了",不代做修复 —— 猜着改名字比留着更危险。
|
|
7758
|
+
(function () {
|
|
7759
|
+
var doctor = data.doctor
|
|
7760
|
+
if (!doctor) return null
|
|
7761
|
+
var findings = doctor.findings || []
|
|
7762
|
+
if (!findings.length && !(doctor.skipped || []).length) return null
|
|
7763
|
+
var shown = findings.slice(0, 6)
|
|
7764
|
+
return React.createElement('div', { key: 'doctor', className: 'dsm-feedback dsm-warning' },
|
|
7765
|
+
React.createElement('div', { key: 'head' }, findings.length
|
|
7766
|
+
? t('scenes.doctor.title', { count: findings.length })
|
|
7767
|
+
: t('scenes.doctor.noneButSkipped'), React.createElement('span', null, ' ' + t('scenes.doctor.hint'))),
|
|
7768
|
+
shown.map(function (row, i) {
|
|
7769
|
+
return React.createElement('div', { key: 'd' + i, className: 'dsm-help' }, t('scenes.doctor.row', { where: row.where, name: row.name }))
|
|
7770
|
+
}),
|
|
7771
|
+
findings.length > shown.length ? React.createElement('div', { key: 'more', className: 'dsm-help' }, t('scenes.doctor.more', { count: findings.length - shown.length })) : null,
|
|
7772
|
+
(doctor.skipped || []).length ? React.createElement('div', { key: 'skip', className: 'dsm-help' }, t('scenes.doctor.skipped', { domains: doctor.skipped.join('、') })) : null)
|
|
7773
|
+
})(),
|
|
6963
7774
|
data.error ? React.createElement(Notice, { key: 'gerr', kind: 'err', text: String(data.error) }) : null,
|
|
6964
7775
|
data.loading && !presetScenes.length ? React.createElement('div', { className: 'dsm-empty' }, t('memory.loading'))
|
|
6965
|
-
: presetScenes.length ? React.createElement('div', { key: '
|
|
7776
|
+
: !presetScenes.length ? React.createElement('div', { key: 'empty', className: 'dsm-empty' }, t('scenes.empty'))
|
|
7777
|
+
: !visibleScenes.length ? React.createElement('div', { key: 'emptysearch', className: 'dsm-empty' }, t('scenes.empty.search'))
|
|
7778
|
+
: React.createElement('div', { key: 'scenes', className: 'dsm-scenes', ref: flipRef }, orderedScenes.map(function (scene) {
|
|
6966
7779
|
var name = scene.name
|
|
6967
7780
|
var label = sceneLabel(data.scenes, name) || name
|
|
6968
7781
|
var archive = data.archives[name]
|
|
@@ -7010,7 +7823,7 @@ function enabledMemoryCount(rules) {
|
|
|
7010
7823
|
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')),
|
|
7011
7824
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { openEditScene(scene) } }, t('memory.scene.edit')),
|
|
7012
7825
|
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')))))
|
|
7013
|
-
}))
|
|
7826
|
+
})),
|
|
7014
7827
|
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) } },
|
|
7015
7828
|
React.createElement('div', { className: 'dsm-form' },
|
|
7016
7829
|
React.createElement('label', { className: 'dsm-field' },
|
|
@@ -7025,7 +7838,7 @@ function enabledMemoryCount(rules) {
|
|
|
7025
7838
|
React.createElement('label', { className: 'dsm-field' },
|
|
7026
7839
|
React.createElement('div', { className: 'dsm-budget-meta' },
|
|
7027
7840
|
React.createElement('span', { className: 'dsm-label' }, t('memory.scene.field.desc')),
|
|
7028
|
-
//
|
|
7841
|
+
// 字数上限 = 存多少(`SCENE_DESC_MAX`);卡片那一行靠 CSS 省略号截,不靠这个数。
|
|
7029
7842
|
React.createElement('span', { className: 'dsm-char-count' }, String(String(sceneForm.description || '').length) + '/' + SCENE_DESC_MAX)),
|
|
7030
7843
|
React.createElement('input', {
|
|
7031
7844
|
className: 'dsm-control',
|
|
@@ -7033,8 +7846,7 @@ function enabledMemoryCount(rules) {
|
|
|
7033
7846
|
maxLength: SCENE_DESC_MAX,
|
|
7034
7847
|
placeholder: t('memory.scene.field.desc.placeholder'),
|
|
7035
7848
|
onChange: function (e) { setSceneForm(Object.assign({}, sceneForm, { description: e.target.value, error: null })) },
|
|
7036
|
-
}),
|
|
7037
|
-
React.createElement('p', { className: 'dsm-help' }, t('scenes.field.desc.limit', { count: SCENE_DESC_MAX }))),
|
|
7849
|
+
})),
|
|
7038
7850
|
// 提示词预设:一个场景**只能绑一个**(单值字段天然单选);「不绑定」= 解绑。
|
|
7039
7851
|
React.createElement('label', { className: 'dsm-field' },
|
|
7040
7852
|
React.createElement('span', { className: 'dsm-label' }, t('scenes.field.prompt')),
|
|
@@ -7048,6 +7860,22 @@ function enabledMemoryCount(rules) {
|
|
|
7048
7860
|
React.createElement('p', { className: 'dsm-help' }, t('memory.deleteScene.desc', { name: modal.name })),
|
|
7049
7861
|
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
7050
7862
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { submitDeleteScene(modal.name) } }, t('memory.btn.deleteScene')))) : null,
|
|
7863
|
+
// 「进入场景」的改动预览卡(askEnter)。预览拿不到时不拦进入这条路 ——
|
|
7864
|
+
// 这是一张说明卡,不是一道新门禁;把它做成前置条件就等于新增了"进不去场景"。
|
|
7865
|
+
//
|
|
7866
|
+
// 按钮(用户 2026-09-23 裁定):**只有「确认进入」与「不再显示」**。原先那颗
|
|
7867
|
+
// 「先不进入」与右上角的「关闭」是同一个动作的两颗按钮,删掉那颗;「不再显示」
|
|
7868
|
+
// = 关掉卡片 + 以后不再弹(也不进入),想恢复用右上那颗「开启提醒」。
|
|
7869
|
+
preview ? React.createElement(Modal, { key: 'spreview', title: t('scenes.preview.title', { name: preview.name }), closeLabel: t('btn.close'), onClose: function () { setPreview(null) } },
|
|
7870
|
+
preview.loading ? React.createElement('p', { className: 'dsm-help' }, t('scenes.preview.loading'))
|
|
7871
|
+
: preview.error ? React.createElement('div', { className: 'dsm-feedback dsm-error', role: 'alert' }, t('scenes.preview.failed', { error: preview.error }))
|
|
7872
|
+
: previewRows(preview.plan || { target: preview.name }),
|
|
7873
|
+
React.createElement('div', { className: 'dsm-modal-actions dsm-modal-actions-split' },
|
|
7874
|
+
React.createElement('button', {
|
|
7875
|
+
type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, title: t('scenes.preview.dismiss.title'),
|
|
7876
|
+
onClick: function () { setPreview(null); saveEnterPreview(false) },
|
|
7877
|
+
}, t('scenes.preview.dismiss')),
|
|
7878
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: busy || preview.loading, onClick: confirmEnter }, t('scenes.preview.enter')))) : null,
|
|
7051
7879
|
// 档案编辑器(见 archiveNode):三段共用「段卡片 + 勾选行」排版。
|
|
7052
7880
|
archiveNode(),
|
|
7053
7881
|
trash ? React.createElement(TrashModal, {
|
|
@@ -7066,6 +7894,22 @@ function enabledMemoryCount(rules) {
|
|
|
7066
7894
|
)
|
|
7067
7895
|
}
|
|
7068
7896
|
// ---------- 子智能体页:人设文件(~/.dsh/subagents/*.md)管理 ----------
|
|
7897
|
+
/**
|
|
7898
|
+
* 「思考强度」档位的按 (provider, model) 缓存。
|
|
7899
|
+
*
|
|
7900
|
+
* 为什么放在**组件外**:放进组件里每次渲染都会重建,等于没有缓存,而档位清单要问
|
|
7901
|
+
* adapter(`llm.resolveModelInfo`,异步、可能联网)—— 编辑人设时来回切模型不该每次都问。
|
|
7902
|
+
* TTL 5 分钟:档位是模型能力声明,短时间内不会变;换模型时才重新问。
|
|
7903
|
+
*/
|
|
7904
|
+
var EFFORT_CACHE_TTL_MS = 5 * 60 * 1000
|
|
7905
|
+
var effortCacheByRoute = {}
|
|
7906
|
+
function readEffortCache(key) {
|
|
7907
|
+
var hit = effortCacheByRoute[key]
|
|
7908
|
+
if (!hit) return null
|
|
7909
|
+
if (Date.now() - hit.at > EFFORT_CACHE_TTL_MS) { delete effortCacheByRoute[key]; return null }
|
|
7910
|
+
return hit.value
|
|
7911
|
+
}
|
|
7912
|
+
function writeEffortCache(key, value) { effortCacheByRoute[key] = { at: Date.now(), value: value } }
|
|
7069
7913
|
function SubagentsPage() {
|
|
7070
7914
|
var state = React.useState({ loading: true, error: null, subagents: [] })
|
|
7071
7915
|
var data = state[0], setData = state[1]
|
|
@@ -7075,6 +7919,16 @@ function enabledMemoryCount(rules) {
|
|
|
7075
7919
|
var result = rs[0], setResult = rs[1]
|
|
7076
7920
|
var ms = React.useState(null)
|
|
7077
7921
|
var modal = ms[0], setModal = ms[1]
|
|
7922
|
+
/**
|
|
7923
|
+
* 「思考强度」的加载状态。key = 已拉取/正在拉取的 `provider\0model`(空串 = 还没选模型)。
|
|
7924
|
+
* 与 `cands` 分开两个 state:档位是**按模型**懒加载的,而候选目录是一次性拉的,
|
|
7925
|
+
* 混在一起会让"切换模型"误触发整份候选重拉。
|
|
7926
|
+
*/
|
|
7927
|
+
var es = React.useState({ key: '', loading: false, error: null, efforts: [], defaultEffort: null })
|
|
7928
|
+
var eff = es[0], setEff = es[1]
|
|
7929
|
+
/** 换模型导致已存档位被清掉时的一次性提示(下次换模型或关弹窗即消失)。 */
|
|
7930
|
+
var ens = React.useState(null)
|
|
7931
|
+
var effNotice = ens[0], setEffNotice = ens[1]
|
|
7078
7932
|
// 搜索词独立一处:`data` 在 refresh 时被整对象替换,寄在它里面会被刷新清空。
|
|
7079
7933
|
// 过滤用防抖值,输入框仍绑原值(与提示词页同一写法)。
|
|
7080
7934
|
var qs = React.useState('')
|
|
@@ -7168,6 +8022,76 @@ function enabledMemoryCount(rules) {
|
|
|
7168
8022
|
setCand({ loaded: true, loading: false, error: errMsg(e), models: [], tools: [], presets: [] })
|
|
7169
8023
|
})
|
|
7170
8024
|
}
|
|
8025
|
+
/**
|
|
8026
|
+
* 拉取某个 (provider, model) 的思考强度档位。**只有真正展开了高级选项、且选了模型**才会
|
|
8027
|
+
* 被调到(三个调用点:展开高级选项、模型下拉变更、自定义模型输入失焦)。
|
|
8028
|
+
*
|
|
8029
|
+
* 两个语义要点(都来自官方源码,见 src/subagents/service.ts 的 `reasoningEffort` 注释):
|
|
8030
|
+
* · 档位跟模型走 —— 换模型必须重拉,旧清单对新模型无效;
|
|
8031
|
+
* · **拉取失败一律不动已存的值**:只有"成功且当前值不在清单里"才清档。否则一次网络
|
|
8032
|
+
* 抖动就把用户设置抹了,而失败本身已经有提示。
|
|
8033
|
+
*/
|
|
8034
|
+
function loadEfforts(provider, model) {
|
|
8035
|
+
setEffNotice(null)
|
|
8036
|
+
var pv = String(provider || ''), md = String(model || '')
|
|
8037
|
+
if (!pv || !md) { setEff({ key: '', loading: false, error: null, efforts: [], defaultEffort: null }); return }
|
|
8038
|
+
var key = pv + '\u0000' + md
|
|
8039
|
+
var cached = readEffortCache(key)
|
|
8040
|
+
if (cached) {
|
|
8041
|
+
setEff({ key: key, loading: false, error: cached.ok ? null : cached.error, efforts: cached.efforts || [], defaultEffort: cached.defaultEffort || null })
|
|
8042
|
+
if (cached.ok) dropEffortIfGone(cached.efforts || [])
|
|
8043
|
+
return
|
|
8044
|
+
}
|
|
8045
|
+
setEff({ key: key, loading: true, error: null, efforts: [], defaultEffort: null })
|
|
8046
|
+
apiCall('model-reasoning', { provider: pv, model: md }).then(function (res) {
|
|
8047
|
+
var value = res && res.ok !== false
|
|
8048
|
+
? { ok: true, efforts: res.efforts || [], defaultEffort: res.defaultEffort || null }
|
|
8049
|
+
: { ok: false, error: (res && res.error) || t('subagents.effort.unavailable'), efforts: [], defaultEffort: null }
|
|
8050
|
+
writeEffortCache(key, value)
|
|
8051
|
+
setEff({ key: key, loading: false, error: value.ok ? null : value.error, efforts: value.efforts, defaultEffort: value.defaultEffort })
|
|
8052
|
+
if (value.ok) dropEffortIfGone(value.efforts)
|
|
8053
|
+
}).catch(function (e) {
|
|
8054
|
+
var value = { ok: false, error: errMsg(e), efforts: [], defaultEffort: null }
|
|
8055
|
+
writeEffortCache(key, value)
|
|
8056
|
+
setEff({ key: key, loading: false, error: value.error, efforts: [], defaultEffort: null })
|
|
8057
|
+
})
|
|
8058
|
+
}
|
|
8059
|
+
/**
|
|
8060
|
+
* 自动清档:清单里没有当前存的那一档 → 清掉并给一次性提示。
|
|
8061
|
+
*
|
|
8062
|
+
* 为什么必须清:官方对**不支持**的显式档位是在 provider I/O 之前直接拒(不夹紧、不别名),
|
|
8063
|
+
* 留着它 = 下一次委派直接起不来,而错误要到那时才看得见。
|
|
8064
|
+
* 为什么只在"拉取成功"时判断:失败时清单是空的,拿空清单当"没有这一档"会把设置误清。
|
|
8065
|
+
*/
|
|
8066
|
+
function dropEffortIfGone(list) {
|
|
8067
|
+
if (!modal || modal.type !== 'editor') return
|
|
8068
|
+
var cur = String(modal.form.reasoningEffort || '')
|
|
8069
|
+
if (!cur) return
|
|
8070
|
+
var has = (list || []).some(function (e) { return String(e && e.id) === cur })
|
|
8071
|
+
if (has) return
|
|
8072
|
+
setEffNotice(t('subagents.effort.dropped', { effort: cur }))
|
|
8073
|
+
setForm({ reasoningEffort: '' })
|
|
8074
|
+
}
|
|
8075
|
+
/** 当前表单的 (provider, model) 路由键 —— 用来与 `eff.key` 比,确认清单属于当前模型。 */
|
|
8076
|
+
function currentRouteKey() {
|
|
8077
|
+
return String((modal && modal.form.provider) || '') + '\u0000' + String((modal && modal.form.model) || '')
|
|
8078
|
+
}
|
|
8079
|
+
/**
|
|
8080
|
+
* 当前模型**已就绪**的档位。未就绪一律返回空数组:切换模型后 `eff` 里还留着上一个模型的
|
|
8081
|
+
* 清单(新请求还没回来),照它渲染会让用户选中一个不属于这个模型的档位 —— 而官方对不支持的
|
|
8082
|
+
* 档位是直接拒,等于埋一次"委派起不来"。
|
|
8083
|
+
*/
|
|
8084
|
+
function currentEfforts() {
|
|
8085
|
+
if (!modal || !modal.form.model || eff.loading || eff.error) return []
|
|
8086
|
+
return eff.key === currentRouteKey() ? (eff.efforts || []) : []
|
|
8087
|
+
}
|
|
8088
|
+
/** 空档(不指定)那一行的悬停说明:把 adapter 给的默认档位名字显示出来(拿不到就不显示)。 */
|
|
8089
|
+
function defaultEffortTitle() {
|
|
8090
|
+
var id = eff.defaultEffort
|
|
8091
|
+
if (!id || eff.key !== currentRouteKey()) return undefined
|
|
8092
|
+
var hit = (eff.efforts || []).filter(function (e) { return String(e && e.id) === String(id) })[0]
|
|
8093
|
+
return hit ? String(hit.name || hit.id) : String(id)
|
|
8094
|
+
}
|
|
7171
8095
|
/** 高级选项:默认收起;已经在用模型/工具限制的人设自动展开(否则用户看不见自己配了什么)。 */
|
|
7172
8096
|
/** 「不限制嵌套」的取值(与 src/subagents/service.ts 的 UNLIMITED_PERSONA_CATALOG_DEPTH 同值)。 */
|
|
7173
8097
|
var CATALOG_DEPTH_UNLIMITED = 99
|
|
@@ -7183,7 +8107,9 @@ function enabledMemoryCount(rules) {
|
|
|
7183
8107
|
// 目录注入深度不是默认值(1)时也算"配过":它决定常驻目录出现在哪些会话,
|
|
7184
8108
|
// 藏起来会让"为什么子会话看不到目录"变得无从查起。
|
|
7185
8109
|
var budget = !!(p && typeof p.catalogDepth === 'number' && p.catalogDepth !== 1)
|
|
7186
|
-
|
|
8110
|
+
// 思考强度同理:配过就得让人看得见 —— 它是"下一次委派会怎么跑"的一部分,
|
|
8111
|
+
// 藏在收起的高级选项里等于用户改过又忘了自己改过。
|
|
8112
|
+
return !!(budget || (p && (p.model || p.provider || p.reasoningEffort)) || (p && ((p.tools || []).length || (p.toolsDeny || []).length || modes)))
|
|
7187
8113
|
}
|
|
7188
8114
|
/** 编辑态里按模式分组的名单(深拷贝:取消编辑不留痕)。 */
|
|
7189
8115
|
function cloneRules(source) {
|
|
@@ -7196,7 +8122,7 @@ function enabledMemoryCount(rules) {
|
|
|
7196
8122
|
}
|
|
7197
8123
|
function openEditor(name) {
|
|
7198
8124
|
if (!name) {
|
|
7199
|
-
setModal({ type: 'editor', mode: 'create', advanced: false, openMode: null, stoppedRules: {}, modeQuery: '', legacyTarget: '', form: { name: '', description: '', provider: '', model: '', catalogDepth: 1, tools: [], toolsDeny: [], toolsByPreset: {}, body: '', output: '', error: null } })
|
|
8125
|
+
setModal({ type: 'editor', mode: 'create', advanced: false, openMode: null, stoppedRules: {}, modeQuery: '', legacyTarget: '', form: { name: '', description: '', provider: '', model: '', reasoningEffort: '', catalogDepth: 1, tools: [], toolsDeny: [], toolsByPreset: {}, body: '', output: '', error: null } })
|
|
7200
8126
|
return
|
|
7201
8127
|
}
|
|
7202
8128
|
setBusy(true)
|
|
@@ -7207,6 +8133,8 @@ function enabledMemoryCount(rules) {
|
|
|
7207
8133
|
var advanced = initialAdvanced(p)
|
|
7208
8134
|
setModal({ type: 'editor', mode: 'edit', originalName: String(p.name || name), advanced: advanced, openMode: null, stoppedRules: {}, modeQuery: '', legacyTarget: '', form: {
|
|
7209
8135
|
name: p.name || name, description: p.description || '', provider: p.provider || '', model: p.model || '',
|
|
8136
|
+
// 思考强度:服务端回空串 = 这份人设没指定(不是"没有档位")。空值不落盘,见 serializePersona。
|
|
8137
|
+
reasoningEffort: p.reasoningEffort || '',
|
|
7210
8138
|
// 服务端回的是**生效值**(没写就是默认 1),所以这里不必再兜默认。
|
|
7211
8139
|
catalogDepth: typeof p.catalogDepth === 'number' ? p.catalogDepth : 1,
|
|
7212
8140
|
tools: (p.tools || []).slice(), toolsDeny: (p.toolsDeny || []).slice(),
|
|
@@ -7214,7 +8142,8 @@ function enabledMemoryCount(rules) {
|
|
|
7214
8142
|
} })
|
|
7215
8143
|
// 已配过限制的人设**一打开就是展开的**(initialAdvanced)→ 候选数据必须在这里也拉,
|
|
7216
8144
|
// 否则四行模式先亮"宿主没有回传预设名单",非得点两次「高级选项」才补上(用户实测)。
|
|
7217
|
-
|
|
8145
|
+
// 思考强度同理:展开着就要有档位清单(它按模型懒加载,所以这里显式带上 model)。
|
|
8146
|
+
if (advanced) { loadCandidates(); loadEfforts(p.provider, p.model) }
|
|
7218
8147
|
} else setResult({ ok: false, text: translateError(t, res) })
|
|
7219
8148
|
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: errMsg(e) }) })
|
|
7220
8149
|
}
|
|
@@ -7453,7 +8382,12 @@ function enabledMemoryCount(rules) {
|
|
|
7453
8382
|
if (!modal || modal.type !== 'editor') return
|
|
7454
8383
|
var next = !modal.advanced
|
|
7455
8384
|
setModal(Object.assign({}, modal, { advanced: next }))
|
|
7456
|
-
if (next)
|
|
8385
|
+
if (next) {
|
|
8386
|
+
loadCandidates()
|
|
8387
|
+
// 档位清单与候选目录**分开拉**:候选是本地目录(一次性),档位要问 adapter
|
|
8388
|
+
// (按模型、可能联网),所以这里用表单里当前的模型去拉;没选模型就什么都不发。
|
|
8389
|
+
loadEfforts(modal.form.provider, modal.form.model)
|
|
8390
|
+
}
|
|
7457
8391
|
}
|
|
7458
8392
|
function submitEditor() {
|
|
7459
8393
|
if (!modal || modal.type !== 'editor') return
|
|
@@ -7580,8 +8514,12 @@ function enabledMemoryCount(rules) {
|
|
|
7580
8514
|
React.createElement('span', { className: 'dsm-label' }, t('subagents.field.name')),
|
|
7581
8515
|
React.createElement('input', { className: 'dsm-control', value: modal.form.name || '', placeholder: 'code-review', onChange: function (e) { setForm({ name: e.target.value }) } })),
|
|
7582
8516
|
React.createElement('label', { className: 'dsm-field' },
|
|
7583
|
-
|
|
7584
|
-
|
|
8517
|
+
// 描述会整段进「可委派的子智能体」目录,上限就是注入侧的截断长度(`CATALOG_DESC_MAX`);
|
|
8518
|
+
// 存量人设的超长描述标红(服务端不校验这个长度,只有注入时截)。
|
|
8519
|
+
React.createElement('div', { className: 'dsm-budget-meta' },
|
|
8520
|
+
React.createElement('span', { className: 'dsm-label' }, t('subagents.field.description')),
|
|
8521
|
+
React.createElement('span', { className: 'dsm-char-count' + (String(modal.form.description || '').length > CATALOG_DESC_MAX ? ' dsm-char-over' : '') }, String(modal.form.description || '').length + ' / ' + CATALOG_DESC_MAX)),
|
|
8522
|
+
React.createElement('input', { className: 'dsm-control', value: modal.form.description || '', maxLength: CATALOG_DESC_MAX, placeholder: t('subagents.field.description.placeholder'), onChange: function (e) { setForm({ description: e.target.value }) } })),
|
|
7585
8523
|
React.createElement('label', { className: 'dsm-field' },
|
|
7586
8524
|
React.createElement('span', { className: 'dsm-label' }, t('subagents.field.body')),
|
|
7587
8525
|
React.createElement('textarea', { className: 'dsm-control dsm-textarea-md', value: modal.form.body || '', placeholder: t('subagents.field.body.placeholder'), onChange: function (e) { setForm({ body: e.target.value }) } })),
|
|
@@ -7598,43 +8536,89 @@ function enabledMemoryCount(rules) {
|
|
|
7598
8536
|
React.createElement('div', { className: 'dsm-adv' },
|
|
7599
8537
|
React.createElement('button', { type: 'button', className: 'dsm-adv-head', 'aria-expanded': modal.advanced === true, onClick: toggleAdvanced },
|
|
7600
8538
|
React.createElement('span', { className: 'dsm-adv-caret' }, modal.advanced ? '▼' : '▶'),
|
|
7601
|
-
React.createElement('span',
|
|
7602
|
-
React.createElement('span', { className: 'dsm-adv-note' },
|
|
7603
|
-
|
|
8539
|
+
React.createElement('span', { className: 'dsm-adv-title' }, t('subagents.adv.title')),
|
|
8540
|
+
modal.advanced ? null : React.createElement('span', { className: 'dsm-adv-note dsm-adv-sum' },
|
|
8541
|
+
// 摘要固定两行:六项挤在一行会整句折行,把「高级选项」顶得七零八落。
|
|
8542
|
+
// 第一行是"下一次委派会怎么跑",第二行是工具名单的存量。
|
|
8543
|
+
React.createElement('span', null, t('subagents.adv.summary', {
|
|
7604
8544
|
model: modal.form.model ? (modal.form.provider ? modal.form.provider + '/' + modal.form.model : modal.form.model) : t('subagents.adv.inherit'),
|
|
7605
8545
|
depth: catalogDepthLabel(typeof modal.form.catalogDepth === 'number' ? modal.form.catalogDepth : 1),
|
|
8546
|
+
// 思考强度收起来也要看得见:它是"下一次委派会怎么跑"的一部分。
|
|
8547
|
+
// 没配时报「默认」而**不是**「继承」—— 官方语义里换模型会把继承来的那一档
|
|
8548
|
+
// 删掉(`dsh-subagent/lib/index.js:482`),报「继承」是句假话。
|
|
8549
|
+
effort: modal.form.reasoningEffort ? String(modal.form.reasoningEffort) : t('subagents.effort.none'),
|
|
8550
|
+
})),
|
|
8551
|
+
React.createElement('span', null, t('subagents.adv.summary2', {
|
|
7606
8552
|
modes: Object.keys(modal.form.toolsByPreset || {}).length,
|
|
7607
8553
|
allow: (modal.form.tools || []).length,
|
|
7608
8554
|
deny: (modal.form.toolsDeny || []).length,
|
|
7609
|
-
}))),
|
|
8555
|
+
})))),
|
|
7610
8556
|
modal.advanced ? React.createElement('div', { className: 'dsm-adv-body' },
|
|
7611
|
-
//
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
8557
|
+
// 模型 + 思考强度并排(`.dsm-field-row`):它们是同一件事的两半 —— 档位清单是
|
|
8558
|
+
// **按模型**给的(adapter 的能力声明),换模型必须同时看这一格。
|
|
8559
|
+
// 不复用 `.dsm-combo-row`:那个的语义是"一个控件 + 它的自定义输入",
|
|
8560
|
+
// 它的 `.dsm-control{flex:1}` 会把两个控件都拉满。
|
|
8561
|
+
React.createElement('div', { className: 'dsm-field-row' },
|
|
8562
|
+
// 模型:宿主 LLM 目录里的 (provider, model) 对 + 自定义兜底。
|
|
8563
|
+
React.createElement('div', { className: 'dsm-field' },
|
|
8564
|
+
React.createElement('span', { className: 'dsm-label' }, t('subagents.field.model')),
|
|
8565
|
+
React.createElement('div', { className: 'dsm-combo-row' },
|
|
8566
|
+
React.createElement('div', { className: 'dsm-select' },
|
|
8567
|
+
React.createElement('select', {
|
|
8568
|
+
className: 'dsm-control',
|
|
8569
|
+
value: modelSelectValue(),
|
|
8570
|
+
disabled: busy || cand.loading,
|
|
8571
|
+
onChange: function (e) {
|
|
8572
|
+
var v = e.target.value
|
|
8573
|
+
if (v === '') { setForm({ provider: '', model: '' }); loadEfforts('', '') }
|
|
8574
|
+
else if (v !== '__custom__') {
|
|
8575
|
+
var parts = v.split('\u0000')
|
|
8576
|
+
setForm({ provider: parts[0], model: parts[1] })
|
|
8577
|
+
// 档位跟模型走:换模型必须重拉(缓存命中时是同步的,不会闪)。
|
|
8578
|
+
loadEfforts(parts[0], parts[1])
|
|
8579
|
+
} else { setForm({ model: modal.form.model || '' }); loadEfforts(modal.form.provider || '', modal.form.model || '') }
|
|
8580
|
+
},
|
|
8581
|
+
},
|
|
8582
|
+
React.createElement('option', { value: '' }, t('subagents.model.inherit')),
|
|
8583
|
+
(cand.models || []).map(function (m) {
|
|
8584
|
+
return React.createElement('option', { key: m.provider + '/' + m.id, value: m.provider + '\u0000' + m.id }, m.provider + ' · ' + m.name)
|
|
8585
|
+
}),
|
|
8586
|
+
React.createElement('option', { value: '__custom__' }, t('subagents.model.customOption')))),
|
|
8587
|
+
modelSelectValue() === '__custom__'
|
|
8588
|
+
? React.createElement('input', {
|
|
8589
|
+
className: 'dsm-control',
|
|
8590
|
+
value: modal.form.model || '',
|
|
8591
|
+
placeholder: t('subagents.field.model.placeholder'),
|
|
8592
|
+
onChange: function (e) { setForm({ model: e.target.value }) },
|
|
8593
|
+
// 手填模型 id 时**不按键触发**拉取(一次一个字符 = 一串请求),
|
|
8594
|
+
// 失焦时再问一次;没变的话缓存会直接命中。
|
|
8595
|
+
onBlur: function () { loadEfforts(modal.form.provider || '', modal.form.model || '') },
|
|
8596
|
+
})
|
|
8597
|
+
: null),
|
|
8598
|
+
React.createElement('p', { className: 'dsm-help' }, cand.loading ? t('memory.loading') : t('subagents.field.model.hint'))),
|
|
8599
|
+
// 思考强度:值就是 adapter 给的档位 id(不校验合法性,官方在 provider I/O 前会拒)。
|
|
8600
|
+
React.createElement('div', { className: 'dsm-field' },
|
|
8601
|
+
React.createElement('span', { className: 'dsm-label' }, t('subagents.field.effort')),
|
|
7615
8602
|
React.createElement('div', { className: 'dsm-select' },
|
|
7616
8603
|
React.createElement('select', {
|
|
7617
8604
|
className: 'dsm-control',
|
|
7618
|
-
value:
|
|
7619
|
-
disabled: busy ||
|
|
7620
|
-
onChange: function (e) {
|
|
7621
|
-
var v = e.target.value
|
|
7622
|
-
if (v === '') setForm({ provider: '', model: '' })
|
|
7623
|
-
else if (v !== '__custom__') {
|
|
7624
|
-
var parts = v.split('\u0000')
|
|
7625
|
-
setForm({ provider: parts[0], model: parts[1] })
|
|
7626
|
-
} else setForm({ model: modal.form.model || '' })
|
|
7627
|
-
},
|
|
8605
|
+
value: String(modal.form.reasoningEffort || ''),
|
|
8606
|
+
disabled: busy || eff.loading || !modal.form.model,
|
|
8607
|
+
onChange: function (e) { setEffNotice(null); setForm({ reasoningEffort: e.target.value }) },
|
|
7628
8608
|
},
|
|
7629
|
-
React.createElement('option', { value: '' }, t('subagents.
|
|
7630
|
-
(
|
|
7631
|
-
return React.createElement('option', { key:
|
|
7632
|
-
}),
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
8609
|
+
React.createElement('option', { value: '', title: defaultEffortTitle() }, t('subagents.effort.default')),
|
|
8610
|
+
currentEfforts().map(function (x) {
|
|
8611
|
+
return React.createElement('option', { key: x.id, value: x.id, title: x.description || undefined }, x.name)
|
|
8612
|
+
}))),
|
|
8613
|
+
React.createElement('p', { className: 'dsm-help' },
|
|
8614
|
+
!modal.form.model
|
|
8615
|
+
? t('subagents.effort.needModel')
|
|
8616
|
+
: eff.loading
|
|
8617
|
+
? t('subagents.effort.loading')
|
|
8618
|
+
: eff.error
|
|
8619
|
+
? String(eff.error)
|
|
8620
|
+
: currentEfforts().length ? t('subagents.effort.hint') : t('subagents.effort.unavailable')),
|
|
8621
|
+
effNotice ? React.createElement('p', { className: 'dsm-feedback dsm-warning' }, effNotice) : null)),
|
|
7638
8622
|
// provider 独立成一项:跨来源模型(如 sensenova)需要 provider+model 两个键同时给。
|
|
7639
8623
|
React.createElement('label', { className: 'dsm-field' },
|
|
7640
8624
|
React.createElement('span', { className: 'dsm-label' }, t('subagents.field.provider')),
|
|
@@ -8242,6 +9226,10 @@ function enabledMemoryCount(rules) {
|
|
|
8242
9226
|
React.createElement('div', { className: 'dsm-note' }, r.description || ''),
|
|
8243
9227
|
shadowed ? React.createElement('div', { className: 'dsm-rule-shadow-hint' }, t('memory.shadowed.hint')) : null),
|
|
8244
9228
|
React.createElement('div', { className: 'dsm-tags' },
|
|
9229
|
+
// 体积:记忆是唯一**直接吃注入预算**的域(场景段 128 KiB),此前页面上却
|
|
9230
|
+
// 看不出哪条大 —— 一列字节数就是那一域的成本读数。宿主读不到文件时不带
|
|
9231
|
+
// `bytes`,这里也就什么都不显示(而不是显示 0 B,那是个假数字)。
|
|
9232
|
+
typeof r.bytes === 'number' ? React.createElement('span', { className: 'dsm-tag', title: t('memory.size.tip', { size: fmtBytes(r.bytes) }) }, fmtBytes(r.bytes)) : null,
|
|
8245
9233
|
r.group && r.group.indexOf('/') >= 0 ? React.createElement('span', { className: 'dsm-tag' }, r.group) : null,
|
|
8246
9234
|
r.form === 'bundle' ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('memory.form.bundle')) : null,
|
|
8247
9235
|
attachTag(r, shadowed),
|
|
@@ -8419,9 +9407,12 @@ function enabledMemoryCount(rules) {
|
|
|
8419
9407
|
React.createElement('input', { className: 'dsm-control' + (editorNameInvalid ? ' dsm-rule-invalid' : ''), value: editor.name || '', placeholder: t('memory.field.name.placeholder'), onChange: function (e) { setEditor(Object.assign({}, editor, { name: e.target.value })) } }),
|
|
8420
9408
|
editorNameInvalid ? React.createElement('p', { className: 'dsm-rule-hint' }, t('memory.name.invalid')) : null),
|
|
8421
9409
|
React.createElement('label', { className: 'dsm-field' },
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
React.createElement('div', { className: 'dsm-
|
|
9410
|
+
// 字数计数与「描述」同一行、贴右缘(与 MCP 备注 / 人设与技能简介同款排版)——
|
|
9411
|
+
// 以前它单起一行落在输入框下面,看着像是另一个字段。
|
|
9412
|
+
React.createElement('div', { className: 'dsm-budget-meta' },
|
|
9413
|
+
React.createElement('span', { className: 'dsm-label' }, t('memory.field.description')),
|
|
9414
|
+
React.createElement('span', { className: 'dsm-char-count' + (editorDescLen > 500 ? ' dsm-char-over' : '') }, editorDescLen + ' / 500')),
|
|
9415
|
+
React.createElement('textarea', { className: 'dsm-control dsm-textarea-sm', value: editor.description || '', placeholder: t('memory.field.description.placeholder'), onChange: function (e) { setEditor(Object.assign({}, editor, { description: e.target.value })) } })),
|
|
8425
9416
|
React.createElement('label', { className: 'dsm-field' },
|
|
8426
9417
|
React.createElement('span', { className: 'dsm-label' }, t('memory.field.body')),
|
|
8427
9418
|
React.createElement('textarea', { className: 'dsm-control dsm-textarea-md', value: editor.body || '', placeholder: t('memory.field.body.placeholder'), onChange: function (e) { setEditor(Object.assign({}, editor, { body: e.target.value })) } })),
|