dsh-plugin-tool-management 0.15.0 → 0.16.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/docs/update.md +29 -0
- package/lib/client.js +242 -123
- package/lib/compat/probe.js +207 -19
- package/lib/mcp/manager.js +177 -14
- package/lib/memories/service.js +9 -0
- package/lib/ops/compat.js +19 -3
- package/lib/skills/core.js +183 -9
- package/lib/skills/service.js +5 -1
- package/package.json +24 -23
package/lib/client.js
CHANGED
|
@@ -31,11 +31,24 @@ window.__ModuleLoader__.load({
|
|
|
31
31
|
* 当场会红)。与 DICT 无依赖,`t` 是传进来的,所以放这儿不影响求值顺序。
|
|
32
32
|
*/
|
|
33
33
|
function translateOrFallback(t, key, fallback, params) { var value = t(key, params); return typeof value === "string" && value !== key ? value : fallback; }
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 字段下的多行说明 → 条目清单(样式见 dsm-help-list)。词典值按 `\n` 分段,一段一条:
|
|
37
|
+
* 一整句话里塞三件事时,条目比句子好扫。「修改场景」表单的字段说明一律走这一套,
|
|
38
|
+
* 只有一件的也照挂 —— 那颗符号是字段的左边界(2026-09-29 用户裁定)。
|
|
39
|
+
*
|
|
40
|
+
* 与 `translateOrFallback` 同一个理由放在这一片:调用点有 factory 作用域里的兼容页(20 片),
|
|
41
|
+
* 也有 `apply` 里的那几页(45 / 46 片)—— 声明在 `apply` 内部时前者取不到这个名字。
|
|
42
|
+
*/
|
|
43
|
+
function helpBullets(t, key) {
|
|
44
|
+
return React.createElement('ul', { className: 'dsm-help-list' }, String(t(key)).split('\n').map(function (line, i) {
|
|
45
|
+
return React.createElement('li', { key: i }, line)
|
|
46
|
+
}))
|
|
47
|
+
}
|
|
34
48
|
const CSS =
|
|
35
49
|
':root{--dsm-warn:#d49245;--dsm-warn-bg:rgba(212,146,69,.12)}' +
|
|
36
50
|
'.dsm-stat-row{display:flex;min-width:0;align-items:center;gap:10px}.dsm-stat-row .dsm-summary{flex:1;min-width:0}' +
|
|
37
51
|
'.dsm-version{color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:500;letter-spacing:.3px;line-height:1}' +
|
|
38
|
-
'.dsm-failed{color:var(--dsw-alias-state-error-primary);font-size:12px;white-space:nowrap}' +
|
|
39
52
|
'.dsm-row-hint{grid-column:1/-1;margin:-4px 0 9px;color:var(--dsm-warn);font-size:11px;line-height:16px}' +
|
|
40
53
|
'.dsm-tools{display:flex;flex-direction:column;gap:8px}' +
|
|
41
54
|
'.dsm-tool{padding:9px 11px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}' +
|
|
@@ -110,7 +123,17 @@ button.dsm-tag:disabled{cursor:default;opacity:.6}
|
|
|
110
123
|
.dsm-persona-name-row{display:flex;width:100%;min-width:0;align-items:center;gap:7px}
|
|
111
124
|
.dsm-persona-name-row .dsm-tag{flex:none}
|
|
112
125
|
.dsm-persona-desc{width:100%;margin-top:1px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:17px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
113
|
-
.dsm-table-head,.dsm-row{display:grid;grid-template-columns:minmax(180px,1fr) 120px
|
|
126
|
+
.dsm-table-head,.dsm-row{display:grid;grid-template-columns:minmax(180px,1fr) 120px max-content;align-items:center;column-gap:12px;padding:0 13px}.dsm-table-head{min-height:32px;border-bottom:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);font-size:11px}.dsm-row{min-height:58px;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-row:last-child{border-bottom:0}.dsm-main{min-width:0}.dsm-name{overflow:hidden;font-size:13px;font-weight:570;text-overflow:ellipsis;white-space:nowrap}.dsm-name-row{display:flex;width:100%;min-width:0;align-items:center;gap:6px}.dsm-name-row .dsm-name{min-width:0}.dsm-name-row .dsm-tag{flex:none}
|
|
127
|
+
/* 行首挂了圆点的列(MCP):点占 8px + 6px 间距 = 14px 的槽。名字与描述、表头一起让出这一槽,
|
|
128
|
+
点再负边距挂回槽的左沿 —— 否则名字比下面的描述和表头右移 14px,看着就是没对齐(2026-09-29 用户反馈)。 */
|
|
129
|
+
.dsm-dot-indent{padding-left:14px}
|
|
130
|
+
.dsm-dot-indent .dsm-name-row{margin-left:-14px}
|
|
131
|
+
/* MCP 行的两行预算(用户裁定 2026-09-29):名字一行、备注与标签一行 —— 标签不再单独占一行,
|
|
132
|
+
带不带备注行高都一样;那一列空出来后,行网格从三列收到两列(表头同步)。 */
|
|
133
|
+
.dsm-row-compact{grid-template-columns:minmax(180px,1fr) max-content}
|
|
134
|
+
.dsm-meta-row{display:flex;min-width:0;align-items:center;gap:5px;margin-top:2px}.dsm-meta-row:empty{display:none}
|
|
135
|
+
.dsm-meta-row .dsm-note{min-width:0;margin-top:0}
|
|
136
|
+
.dsm-meta-row .dsm-tag{flex:none}.dsm-note{overflow:hidden;margin-top:2px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:17px;text-overflow:ellipsis;white-space:nowrap}.dsm-tags{display:flex;align-items:center;gap:5px;flex-wrap:wrap}.dsm-tag{display:inline-flex;min-height:19px;align-items:center;padding:0 6px;border:1px solid var(--dsw-alias-border-l3);border-radius:4px;color:var(--dsw-alias-label-secondary);font-size:10px;white-space:nowrap}.dsm-tag-on{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.dsm-tag-off{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.dsm-tag-warn{border-color:var(--dsm-warn);color:var(--dsm-warn)}.dsm-row-actions{display:flex;align-items:center;justify-content:flex-end;gap:7px}
|
|
114
137
|
.dsm-switch{position:relative;width:34px;height:20px;flex:none;padding:0;border:0;border-radius:999px;background:var(--dsw-alias-border-l3);cursor:pointer}.dsm-switch:after{position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:50%;background:#fff;content:"";transition:transform 160ms ease}.dsm-switch-on{background:var(--dsw-alias-state-success-primary)}.dsm-switch-on:after{transform:translateX(14px)}.dsm-switch:disabled{opacity:.45;cursor:default}.dsm-trash-row{display:flex;min-height:48px;align-items:center;padding:0 13px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2);color:inherit;font:inherit;font-size:13px;cursor:pointer}.dsm-trash-count{margin-left:auto;padding:2px 7px;border-radius:99px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font-size:11px}.dsm-empty{padding:25px 14px;color:var(--dsw-alias-label-tertiary);font-size:12px;text-align:center}.dsm-feedback{padding:9px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-secondary);font-size:12px}.dsm-undo{display:flex;align-items:center;gap:10px}.dsm-undo-text{min-width:0;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dsm-undo-close{padding:0;border:0;background:transparent;font:inherit;font-size:18px;cursor:pointer;width:24px;height:24px;color:var(--dsw-alias-label-secondary)}.dsm-undo-close:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsm-undo.dsm-warning{background:var(--dsm-warn-bg)}.dsm-undo.dsm-warning .dsm-undo-text{font-weight:570}.dsm-undo.dsm-warning .dsm-undo-close{color:var(--dsm-warn)}.dsm-warning{border-color:var(--dsm-warn);color:var(--dsm-warn)}.dsm-error{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
|
|
115
138
|
.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}
|
|
116
139
|
/* 头部这一层包住「标题行 + 令牌提示」:提示挂在头部里面(而不是 head 与 body 之间)是为了
|
|
@@ -155,7 +178,15 @@ button.dsm-tag:disabled{cursor:default;opacity:.6}
|
|
|
155
178
|
「没有滚动条 + 内容缺一截」。列表项的尺寸一律由内容决定,滚动只交给 body 这一层。 */
|
|
156
179
|
.dsm-modal-list .dsm-modal-body>*{flex:none}
|
|
157
180
|
.dsm-fm{display:flex;flex-direction:column;overflow:hidden;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}.dsm-fm-row{display:grid;grid-template-columns:minmax(96px,150px) minmax(0,1fr);gap:10px;padding:8px 12px;border-bottom:1px solid var(--dsw-alias-border-l1);font-size:12px;line-height:18px}.dsm-fm-row:last-child{border-bottom:0}.dsm-fm-key{color:var(--dsw-alias-label-secondary);word-break:break-word}.dsm-fm-raw{margin-left:5px;color:var(--dsw-alias-label-tertiary);font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:10px}.dsm-fm-val{min-width:0;color:var(--dsw-alias-label-primary);word-break:break-word;white-space:pre-wrap}
|
|
158
|
-
@container(max-width:780px){.dsm-table-head{display:none}.dsm-row{grid-template-columns:minmax(0,1fr) max-content;gap:8px;padding:11px 13px}
|
|
181
|
+
@container(max-width:780px){.dsm-table-head{display:none}.dsm-row{grid-template-columns:minmax(0,1fr) max-content;gap:8px;padding:11px 13px}
|
|
182
|
+
/* MCP 行在这一档只有两行内容,58px 的下限是三行时代留下的:加上自带的一截空白,行高 80px
|
|
183
|
+
太空(2026-09-29 用户反馈)。下限收到 46px(两行内容 42.7px 装得下,单行也仍撑到同一高度),
|
|
184
|
+
行高 68px —— 上下各留 11px 内边距,不挤。技能 / 记忆两页仍是 58px 下限,未动。 */
|
|
185
|
+
.dsm-row.dsm-row-compact{min-height:46px}
|
|
186
|
+
/* 动作组别跨到第 2、3 行:那一格是空的,行距却照算(两行 16px 凭空多出来)。MCP 行在这
|
|
187
|
+
一档只有「主列 + 动作组」两项,跨一行就够。 */
|
|
188
|
+
.dsm-row.dsm-row-compact .dsm-row-actions{grid-row:1}
|
|
189
|
+
.dsm-row>.dsm-tags{grid-column:1}.dsm-row-actions{grid-column:2;grid-row:1 / span 3}.dsm-path{display:none}
|
|
159
190
|
/* 一个标签都没有的记忆(flat 且没有场景 / 派生标签):那一行是空的,高度 0 却仍要付两个
|
|
160
191
|
8px 的行间距 —— 于是「描述」和「已启用」之间凭空多出 16px(用户实测)。让它不占位,
|
|
161
192
|
并把右侧动作组的跨度跟着收到两行。**只在这一档布局里做**:宽布局下标签是固定 120px 的
|
|
@@ -1742,6 +1773,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1742
1773
|
if (row.key === 'host-identity') {
|
|
1743
1774
|
if (row.state === 'unknown') return t('compat.ov.route.unknown')
|
|
1744
1775
|
if (row.state === 'degraded') return t('compat.ov.hostIdentity.degraded', { list: list(row.blockers) })
|
|
1776
|
+
// 归档宿主:同版本但不同文件,不能沿用"同源"那句。
|
|
1777
|
+
if (row.asarHost) return t('compat.ov.hostIdentity.asar', { version: row.version, name: row.asarName || 'app.asar' })
|
|
1745
1778
|
return t('compat.ov.hostIdentity.ok', { version: row.version })
|
|
1746
1779
|
}
|
|
1747
1780
|
if (row.state === 'ok' && (row.key === 'memory' || row.key === 'prompts' || row.key === 'subagents' || row.key === 'scenes')) {
|
|
@@ -2326,27 +2359,39 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2326
2359
|
// 它自带的 summary 是中文,直接渲染会让英文界面露出中文)。
|
|
2327
2360
|
const findingsN = Array.isArray(data.findings) ? data.findings.length : 0
|
|
2328
2361
|
const degradedN = Array.isArray(data.degraded) ? data.degraded.length : 0
|
|
2362
|
+
// 摘要三形态:能力降级 / 仅身份待处理 / 全部可用。仅身份待处理时**不能**说
|
|
2363
|
+
// 「全部可用」—— 横幅已转红(有阻塞项),摘要却报一切正常,两句自相矛盾
|
|
2364
|
+
// (2026-09-30 桌面版实测)。
|
|
2365
|
+
const summaryKey = degradedN > 0 ? 'compat.summary.degraded'
|
|
2366
|
+
: blockedCount > 0 ? 'compat.summary.identity' : 'compat.summary.ok'
|
|
2329
2367
|
const summaryText = findingsN === 0
|
|
2330
2368
|
? (data.summary || '')
|
|
2331
|
-
: t(
|
|
2369
|
+
: t(summaryKey)
|
|
2332
2370
|
.replace('{version}', String((data.host && data.host.version) || '?'))
|
|
2333
2371
|
.replace('{ok}', String(findingsN - degradedN))
|
|
2334
2372
|
.replace('{total}', String(findingsN))
|
|
2335
|
-
.replace('{count}', String(degradedN))
|
|
2373
|
+
.replace('{count}', String(degradedN > 0 ? degradedN : blockedCount))
|
|
2336
2374
|
push(React.createElement('div', { className: 'dsm-compat-bar' + (blockedCount > 0 ? ' dsm-compat-bar-warn' : '') },
|
|
2337
2375
|
React.createElement('span', { className: 'dsm-compat-dot' + (blockedCount > 0 ? ' dsm-compat-dot-warn' : '') }),
|
|
2338
2376
|
React.createElement('strong', null, blockedCount > 0 ? t('compat.state.blocked') : t('compat.state.ok')),
|
|
2339
2377
|
React.createElement('span', { className: 'dsm-compat-sum' }, summaryText)))
|
|
2340
2378
|
|
|
2341
2379
|
// 指标卡:宿主 / 本插件适配版本 / 可用能力(含插件自补的 optional 槽位)。
|
|
2380
|
+
// 三张卡都只在**有事要说**时带注脚:
|
|
2381
|
+
// * 宿主、适配版本:一个值就够(2026-09-29 用户裁定:删掉「最低要求 ≥ …」与
|
|
2382
|
+
// 「本插件按此版本验证」—— 前者是安装期门槛,后者是同一件事说两遍);
|
|
2383
|
+
// * 可用能力:全通过时不再写「全部通过」(分子分母已经说了这件事),只在有降级
|
|
2384
|
+
// 或有插件自补时说清楚是哪一种(2026-09-29 用户裁定)。
|
|
2385
|
+
// * 注脚与红色只跟**能力**降级走(degraded):身份阻塞项(两份拷贝 / 待处理)
|
|
2386
|
+
// 不是能力不可用 —— 此前用 blockedCount(能力 + 身份)当注脚数,桌面版上出现
|
|
2387
|
+
// 过「可用能力 18/18」旁边标红「5 项不可用」的自相矛盾(2026-09-30 实测)。
|
|
2388
|
+
const capsNote = degraded.length > 0
|
|
2389
|
+
? t('compat.caps.degraded').replace('{count}', String(degraded.length))
|
|
2390
|
+
: (usableCount === findings.length ? null : t('compat.caps.substituted'))
|
|
2342
2391
|
push(React.createElement('div', { className: 'dsm-compat-grid' },
|
|
2343
|
-
card(t('compat.host'), (data.host && data.host.version) || '?',
|
|
2344
|
-
card(t('compat.verified'), data.verifiedVersion || '?',
|
|
2345
|
-
card(t('compat.caps'), String(usableCount) + '/' + String(findings.length),
|
|
2346
|
-
blockedCount > 0
|
|
2347
|
-
? t('compat.caps.degraded').replace('{count}', String(blockedCount))
|
|
2348
|
-
: (usableCount === findings.length ? t('compat.caps.all') : t('compat.caps.substituted')),
|
|
2349
|
-
blockedCount > 0)))
|
|
2392
|
+
card(t('compat.host'), (data.host && data.host.version) || '?', null, false),
|
|
2393
|
+
card(t('compat.verified'), data.verifiedVersion || '?', null, false),
|
|
2394
|
+
card(t('compat.caps'), String(usableCount) + '/' + String(findings.length), capsNote, degraded.length > 0)))
|
|
2350
2395
|
|
|
2351
2396
|
// 版本栅栏(B3):实际宿主与本插件验证过的版本不同时说出来 —— 否则"照旧版本的印象
|
|
2352
2397
|
// 判断"会让人把探测结果当成 bug。只提示、不拦(peer 保持无上界是既定决策)。
|
|
@@ -2445,28 +2490,88 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2445
2490
|
}
|
|
2446
2491
|
|
|
2447
2492
|
// 模块实体:一行一个包,右侧是"同一份模块 / 两份拷贝"。
|
|
2493
|
+
// 归档宿主(桌面版)先把清单读出来:那几个包两侧的**版本相同**,只是宿主那份在
|
|
2494
|
+
// app.asar 里 —— 它们不能显示成「无法比较」后就此打住,得说清是哪种比不了。
|
|
2495
|
+
const identityNotes = (data && data.notes) || []
|
|
2496
|
+
const asarNote = identityNotes.filter(function (note) {
|
|
2497
|
+
return note && note.kind === 'asar-host'
|
|
2498
|
+
})[0]
|
|
2499
|
+
const asarPackages = (asarNote && asarNote.packages) || []
|
|
2500
|
+
const asarNames = asarPackages.map(function (item) { return String(item && item.name || '') })
|
|
2501
|
+
// 真实形态取不到的包:那一格同样「无法比较」,但原因与归档无关,得分开标。
|
|
2502
|
+
const realpathNoteForPill = identityNotes.filter(function (note) {
|
|
2503
|
+
return note && note.kind === 'realpath'
|
|
2504
|
+
})[0]
|
|
2505
|
+
const realpathNames = ((realpathNoteForPill && realpathNoteForPill.failures) || []).map(function (item) {
|
|
2506
|
+
return String(item && item.name || '')
|
|
2507
|
+
})
|
|
2448
2508
|
push(section(t('compat.modules'), t('compat.modules.hint'),
|
|
2449
2509
|
React.createElement('div', { className: 'dsm-compat-mod-list' },
|
|
2450
2510
|
Object.keys(sameAsHost).map(function (name) {
|
|
2451
2511
|
const same = sameAsHost[name]
|
|
2512
|
+
const inRealpath = realpathNames.indexOf(name) !== -1
|
|
2513
|
+
const inAsar = asarNames.indexOf(name) !== -1
|
|
2452
2514
|
const cls = same === true ? 'dsm-compat-pill-ok' : same === false ? 'dsm-compat-pill-bad' : ''
|
|
2453
|
-
const text = same === true ? t('compat.module.same')
|
|
2515
|
+
const text = same === true ? t('compat.module.same')
|
|
2516
|
+
: same === false ? t('compat.module.separate')
|
|
2517
|
+
: inRealpath ? t('compat.module.realpath')
|
|
2518
|
+
: inAsar ? t('compat.module.sameVersion') : t('compat.module.unknown')
|
|
2454
2519
|
return React.createElement('div', { className: 'dsm-compat-mod-row', key: name },
|
|
2455
2520
|
React.createElement('span', { className: 'dsm-compat-name dsm-compat-mod' }, name),
|
|
2456
2521
|
React.createElement('span', { className: 'dsm-compat-pill ' + cls }, text))
|
|
2457
2522
|
}))))
|
|
2458
2523
|
|
|
2459
2524
|
// 「无法比对」必须给原因:否则一排未知标记看起来只是噪音,用户会当成正常现象。
|
|
2525
|
+
// 归档宿主的说明排最前 —— 它解释的正是这一排「无法比较」的成因,而且必须讲明白
|
|
2526
|
+
// **不要去跑 host-deps --fix**:那条命令会把插件的依赖指向 npx 缓存里另一个版本。
|
|
2527
|
+
if (asarNote) {
|
|
2528
|
+
const count = asarPackages.length
|
|
2529
|
+
const version = asarNote.hostVersion || '?'
|
|
2530
|
+
push(React.createElement('p', { className: 'dsm-help' },
|
|
2531
|
+
t('compat.modules.asarHost')
|
|
2532
|
+
.replace('{name}', String(asarNote.asarName || 'app.asar'))
|
|
2533
|
+
.replace('{count}', String(count))
|
|
2534
|
+
.replace('{version}', version)))
|
|
2535
|
+
push(React.createElement('p', { className: 'dsm-help' },
|
|
2536
|
+
t('compat.modules.asarHostPackages')
|
|
2537
|
+
.replace('{count}', String(count))
|
|
2538
|
+
.replace('{version}', version)
|
|
2539
|
+
.replace('{names}', asarNames.join('、'))))
|
|
2540
|
+
}
|
|
2460
2541
|
if (unverified.length) {
|
|
2461
2542
|
push(React.createElement('p', { className: 'dsm-help' },
|
|
2462
2543
|
t('compat.modules.unverified')
|
|
2463
2544
|
.replace('{count}', String(unverified.length))
|
|
2464
2545
|
.replace('{names}', unverified.join('、'))))
|
|
2465
2546
|
}
|
|
2547
|
+
// 路径取不到真实形态:这一格是「比不了」,不是「两份拷贝」。必须把原因摆出来 ——
|
|
2548
|
+
// 否则用户只会看到一排「无法比较」,而不知道是路径/权限的问题(issue #1)。
|
|
2549
|
+
const realpathNote = identityNotes.filter(function (note) {
|
|
2550
|
+
return note && note.kind === 'realpath'
|
|
2551
|
+
})[0]
|
|
2552
|
+
if (realpathNote) {
|
|
2553
|
+
const failures = realpathNote.failures || []
|
|
2554
|
+
const names = failures.map(function (item) { return String(item && item.name || '') })
|
|
2555
|
+
const sideText = function (side) {
|
|
2556
|
+
return failures.filter(function (item) { return item && item.side === side })
|
|
2557
|
+
.map(function (item) { return String(item && item.path || '') + '(' + String(item && item.reason || '') + ')' })
|
|
2558
|
+
.join(';')
|
|
2559
|
+
}
|
|
2560
|
+
push(React.createElement('p', { className: 'dsm-help' },
|
|
2561
|
+
t('compat.modules.realpath')
|
|
2562
|
+
.replace('{count}', String(failures.length))
|
|
2563
|
+
.replace('{names}', names.join('、'))))
|
|
2564
|
+
push(React.createElement('p', { className: 'dsm-help' },
|
|
2565
|
+
t('compat.modules.realpathSides')
|
|
2566
|
+
.replace('{host}', sideText('host') || t('compat.modules.realpathNone'))
|
|
2567
|
+
.replace('{plugin}', sideText('plugin') || t('compat.modules.realpathNone'))))
|
|
2568
|
+
}
|
|
2466
2569
|
|
|
2467
2570
|
push(React.createElement('p', { className: 'dsm-help' },
|
|
2468
|
-
t('compat.hint.doctor'),
|
|
2469
|
-
|
|
2571
|
+
t(data && data.sourceInstall === true ? 'compat.hint.doctor' : 'compat.hint.doctorRelease'),
|
|
2572
|
+
data && data.sourceInstall === true
|
|
2573
|
+
? React.createElement('code', { className: 'dsm-compat-code' }, ' node scripts/doctor.mjs')
|
|
2574
|
+
: null))
|
|
2470
2575
|
}
|
|
2471
2576
|
|
|
2472
2577
|
// 预设注入边界:五类内容(记忆 / 全局提示词 / 技能 / MCP / 子智能体)在**每个 Agent
|
|
@@ -2995,7 +3100,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2995
3100
|
}
|
|
2996
3101
|
const auditDialog = compatDialogOpen === 'audit'
|
|
2997
3102
|
? compatDialog('audit', t('compat.audit'), [
|
|
2998
|
-
React.
|
|
3103
|
+
React.cloneElement(helpBullets(t, 'compat.audit.hint'), { key: 'hint' }),
|
|
2999
3104
|
auditContent ? React.cloneElement(auditContent, { key: 'body' }) : dialogFallback(),
|
|
3000
3105
|
], true)
|
|
3001
3106
|
: null
|
|
@@ -3073,8 +3178,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3073
3178
|
"confirm.custom.remove.title": "移除自定义目录",
|
|
3074
3179
|
"confirm.custom.remove.desc": "移除后该目录的技能不再列出(文件不受影响):{name}",
|
|
3075
3180
|
"result.custom.added": "已添加自定义目录:{path}", "result.custom.removed": "已移除自定义目录:{name}",
|
|
3076
|
-
"status.enabled": "已启用", "status.disabled": "
|
|
3077
|
-
"summary.total.one": "{count} 个技能", "summary.total.other": "{count} 个技能", "summary.enabled.one": "{count} 个已启用", "summary.enabled.other": "{count} 个已启用", "summary.issues.one": "{count} 个诊断项", "summary.issues.other": "{count} 个诊断项", "summary.group.one": "{count} 个技能", "summary.group.other": "{count} 个技能", "table.skill": "技能名称与描述",
|
|
3181
|
+
"status.enabled": "已启用", "status.disabled": "未启用", "status.invalid": "诊断异常", "status.shadowed": "被覆盖", "status.shadowed.hint": "同名技能「{name}」正在生效;点「启用这个」可改用当前来源的版本", "status.preferred": "同名首选", "status.readonly": "源文件只读", "status.notDeletable": "不可删除", "status.notDeletable.hint": "删除只对插件可管理的来源开放(DSH 技能 / 导入技能 / 项目级 .dsh/skills);外部 Agent 与自定义目录只读,里面的技能不能删", "status.manageable": "可管理", "status.project": "项目级", "status.rank": "优先级 {rank}", "status.source.on": "已启用", "status.source.off": "已停用", "status.bundle": "目录技能", "status.single": "单文件",
|
|
3182
|
+
"summary.total.one": "{count} 个技能", "summary.total.other": "{count} 个技能", "summary.enabled.one": "{count} 个已启用", "summary.enabled.other": "{count} 个已启用", "summary.issues.one": "{count} 个诊断项", "summary.issues.other": "{count} 个诊断项", "summary.group.one": "{count} 个技能", "summary.group.other": "{count} 个技能", "table.skill": "技能名称与描述",
|
|
3078
3183
|
"filter.source": "来源", "filter.all": "全部来源", "filter.option": "{name}({count})", "search": "搜索", "search.placeholder": "搜索技能名称或描述", "search.clear": "清除搜索",
|
|
3079
3184
|
"empty.search": "没有匹配的技能", "empty.source": "该来源不存在或暂无技能", "loading": "正在加载技能…", "note.missing": "未提供简介", "source.toggle": "启停来源", "skill.toggle": "启停技能",
|
|
3080
3185
|
"source.external.note": "只读接入,启停不改写源文件", "source.dsh.note": "可创建、导入、移到回收站;默认来源必须读取,不能移除或停用",
|
|
@@ -3123,13 +3228,13 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3123
3228
|
"mcp.level.all": "全部级别", "mcp.level.project": "应用级", "mcp.level.global": "全局", "mcp.level.loader": "已加载",
|
|
3124
3229
|
"mcp.stat.total": "个服务", "mcp.stat.enabled": "个已启用", "mcp.stat.tools": "个工具",
|
|
3125
3230
|
"mcp.search": "搜索服务", "mcp.search.placeholder": "搜索服务名称、地址或命令",
|
|
3126
|
-
"mcp.btn.new": "新增 MCP", "mcp.btn.add": "添加", "mcp.btn.save": "保存", "mcp.btn.detail": "详情", "mcp.btn.edit": "编辑", "mcp.btn.
|
|
3231
|
+
"mcp.btn.new": "新增 MCP", "mcp.btn.add": "添加", "mcp.btn.save": "保存", "mcp.btn.detail": "详情", "mcp.btn.edit": "编辑", "mcp.btn.remove": "删除",
|
|
3127
3232
|
"mcp.btn.enableAll": "全部启用", "mcp.btn.disableAll": "全部停用",
|
|
3128
3233
|
"mcp.loading": "正在加载 MCP 服务…", "mcp.empty": "暂无 MCP,点「新增 MCP」添加", "mcp.empty.search": "没有匹配的服务。",
|
|
3129
3234
|
"mcp.servers.count": "{count} 个服务", "mcp.tools.count": "{count} 个工具", "mcp.tools.countPartial": "{enabled}/{total} 个工具", "mcp.duplicate": "重复 id",
|
|
3130
|
-
"mcp.table.name": "服务名称与地址",
|
|
3131
|
-
"mcp.live.notLoaded": "未加载", "mcp.live.failed": "启动失败", "mcp.live.stopped": "
|
|
3132
|
-
"mcp.live.failedHint": "
|
|
3235
|
+
"mcp.table.name": "服务名称与地址",
|
|
3236
|
+
"mcp.live.notLoaded": "未加载", "mcp.live.failed": "启动失败", "mcp.live.stopped": "未启用", "mcp.live.loading": "加载中", "mcp.live.noTools": "无可用工具", "mcp.live.running": "已启用",
|
|
3237
|
+
"mcp.live.failedHint": "启动失败,检查配置后把开关关掉再打开重试", "mcp.live.offlineHint": "未连上:一个工具都没注册,但上次连上时它有 {count} 个工具。检查网络、命令或凭据后把开关关掉再打开重试", "mcp.live.neverHint": "从未连上过:一个工具都没注册过。检查命令、参数或凭据后把开关关掉再打开重试", "mcp.live.allOffHint": "工具全部被停用:当前没有可调用的工具(取消停用即恢复)",
|
|
3133
3238
|
"mcp.note.prefix": "备注:", "mcp.toggleServer": "启停服务", "mcp.toggleTool": "启停工具",
|
|
3134
3239
|
"mcp.msg.ok": "操作成功", "mcp.msg.failed": "操作失败", "mcp.msg.loadFailed": "加载失败", "mcp.msg.warn": "操作完成,但加载器有提示:{warning}",
|
|
3135
3240
|
"mcp.msg.toolOn": "已启用工具:{name}", "mcp.msg.toolOff": "已停用工具:{name}",
|
|
@@ -3156,7 +3261,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3156
3261
|
"mcp.tools.loading": "正在获取工具列表…", "mcp.tools.loadFailed": "加载工具失败:", "mcp.tools.none": "该服务暂无已注册工具。",
|
|
3157
3262
|
"mcp.tools.note": "停用的工具对模型不可见,改动即时生效",
|
|
3158
3263
|
"mcp.remove.title": "删除 MCP 服务", "mcp.remove.desc": "确定要删除「{name}」?配置将从补丁文件移除、工具立即下线,不可撤销",
|
|
3159
|
-
"mcp.
|
|
3264
|
+
"mcp.err.warnings": "读取补丁告警:",
|
|
3160
3265
|
"mcp.importJson.btn": "导入", "mcp.importJson.title": "从 JSON 导入 MCP 服务器",
|
|
3161
3266
|
"mcp.importJson.hint": "认三种写法:{\"mcpServers\": {…}} 映射、{\"rows\": […]} 与裸数组。提交前在这里核对清楚 —— 名字净化、传输方式判定、重名处理都在这一屏说完。",
|
|
3162
3267
|
"mcp.importJson.placeholder": "粘贴 {\"mcpServers\": {…}}、{\"rows\": […]} 或数组",
|
|
@@ -3202,11 +3307,12 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3202
3307
|
"mcp.inspect.check.kvNotString": "{where} 里有不是字符串的值:{keys}",
|
|
3203
3308
|
"mcp.inspect.check.noUrl": "没有填地址",
|
|
3204
3309
|
"mcp.inspect.check.badUrl": "地址不是 http(s):// 开头:{url}",
|
|
3310
|
+
"mcp.inspect.check.httpUnreachable": "端点探测失败:{url} 连不上({reason})——配置格式没问题,但这个地址现在用不了",
|
|
3205
3311
|
"mcp.inspect.check.transportMissing": "没写 transport,界面按字段形状推断",
|
|
3206
3312
|
"mcp.inspect.check.badTransport": "transport 取值不认识:{transport}",
|
|
3207
3313
|
"mcp.inspect.check.noEndpoint": "既没有 command 也没有 url",
|
|
3208
3314
|
"mcp.inspect.check.nameDup": "服务名「{name}」被 {count} 个不同 id 共用:模型看到的工具名会撞车",
|
|
3209
|
-
"root.dsh": "DSH 技能", "root.hub": "导入技能", "root.agents": "公共 Agent", "root.ccswitch": "CC Switch", "root.projectDsh": "项目 DSH", "root.projectAgents": "项目 Agent", "root.codex": "Codex", "root.claude": "Claude", "root.gemini": "Gemini", "root.opencode": "OpenCode", "root.cursor": "Cursor",
|
|
3315
|
+
"root.dsh": "DSH 技能", "root.hub": "导入技能", "root.official": "官方内置", "root.agents": "公共 Agent", "root.ccswitch": "CC Switch", "root.projectDsh": "项目 DSH", "root.projectAgents": "项目 Agent", "root.codex": "Codex", "root.claude": "Claude", "root.gemini": "Gemini", "root.opencode": "OpenCode", "root.cursor": "Cursor",
|
|
3210
3316
|
"memory.title": "记忆",
|
|
3211
3317
|
"tabs.scenes": "场景", "tabs.skills": "技能", "tabs.subagents": "子智能体", "tabs.prompts": "提示词", "tabs.memory": "记忆", "tabs.sessions": "会话", "tabs.compat": "兼容",
|
|
3212
3318
|
"compat.title": "宿主兼容", "compat.desc": "本插件与 DSH 的对接体检(只读)。",
|
|
@@ -3229,7 +3335,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3229
3335
|
"snapshot.import.masked": "密钥已打码",
|
|
3230
3336
|
"snapshot.import.unmasked": "含明文凭据",
|
|
3231
3337
|
"snapshot.preview.failed": "读取快照失败",
|
|
3232
|
-
"compat.audit": "最近改动", "compat.dialog.empty": "这一块的还没读到数据:点右上角「刷新」再看。", "compat.audit.hint": "谁在什么时候改了什么。面板 / 模型 /
|
|
3338
|
+
"compat.audit": "最近改动", "compat.dialog.empty": "这一块的还没读到数据:点右上角「刷新」再看。", "compat.audit.hint": "谁在什么时候改了什么。面板 / 模型 / 场景引擎三个来源各标各的;流水只记对象名,不记参数内容。\n不会按时间自动清 —— 攒到 512KB 才只保留最近 500 条;要立刻清空点「清除记录」。", "compat.audit.clear": "清除记录", "compat.audit.clear.title": "清空这份改动记录(不可恢复)", "compat.audit.clear.confirm": "确认清除", "compat.audit.clearFailed": "清除失败", "compat.audit.op.auditClear": "清空了改动记录",
|
|
3233
3339
|
"compat.audit.domain": "按域过滤", "compat.audit.domain.all": "全部改动", "compat.audit.domain.mcpm": "MCP", "compat.audit.domain.skill": "技能", "compat.audit.domain.rules": "记忆", "compat.audit.domain.scene": "场景", "compat.audit.domain.subagent": "子智能体", "compat.audit.domain.agentsmd": "提示词", "compat.audit.domain.history": "会话", "compat.audit.domain.toolTable": "模型工具表", "compat.audit.domain.injectSettings": "注入设置", "compat.audit.domain.backups": "备份清理",
|
|
3234
3340
|
"compat.audit.reload": "重新读取", "compat.audit.loading": "正在读取…", "compat.audit.failed": "读取失败",
|
|
3235
3341
|
"compat.audit.empty": "还没有记录:做过一次启停、导入或进场景,这里就会出现第一条",
|
|
@@ -3256,8 +3362,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3256
3362
|
"compat.state.ok": "一切正常", "compat.state.blocked": "有需要处理的问题",
|
|
3257
3363
|
"compat.checking": "检查中…", "compat.empty": "尚未检查", "compat.failed": "检查失败",
|
|
3258
3364
|
"compat.summary.ok": "宿主 {version} · 能力 {ok}/{total} · 全部可用", "compat.summary.degraded": "宿主 {version} · 能力 {ok}/{total} · 降级 {count} 项",
|
|
3259
|
-
"compat.
|
|
3260
|
-
"compat.
|
|
3365
|
+
"compat.summary.identity": "宿主 {version} · 能力 {ok}/{total} · 身份 {count} 项待处理",
|
|
3366
|
+
"compat.host": "宿主版本", "compat.verified": "插件适配版本",
|
|
3367
|
+
"compat.caps": "可用能力", "compat.caps.degraded": "{count} 项不可用",
|
|
3261
3368
|
"compat.caps.substituted": "缺口由插件自补",
|
|
3262
3369
|
"compat.ops": "宿主动作可用性", "compat.ops.hint": "每个动作走哪条路径", "compat.ops.disabled": "有 {count} 个动作当前不可用(相关按钮已禁用并会说明原因)",
|
|
3263
3370
|
"compat.op.name": "动作", "compat.op.state": "路径",
|
|
@@ -3268,7 +3375,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3268
3375
|
"compat.others": "其它能力项", "compat.others.hint": "不算降级:宿主没有这些入口时,由插件自己顶上",
|
|
3269
3376
|
"compat.fallback.substituted": "插件适配层(宿主无此入口)",
|
|
3270
3377
|
"compat.fallback.native": "改走原生入口", "compat.fallback.refuse": "该操作会被拒绝", "compat.fallback.blocked": "相关按钮已禁用", "compat.fallback.inform": "不影响写入",
|
|
3271
|
-
"compat.modules": "模块实体(插件 vs 宿主)", "compat.modules.hint": "同一份模块才谈得上适配", "compat.module.same": "同一份模块", "compat.module.separate": "两份拷贝(需修复)", "compat.module.unknown": "无法比较", "compat.modules.unverified": "有 {count} 个包没能比对(宿主的解析锚点里找不到:{names})—— 这不是「两份拷贝」,而是这项校验没做成。可运行 node scripts/doctor.mjs 查看。",
|
|
3378
|
+
"compat.modules": "模块实体(插件 vs 宿主)", "compat.modules.hint": "同一份模块才谈得上适配", "compat.module.same": "同一份模块", "compat.module.separate": "两份拷贝(需修复)", "compat.module.sameVersion": "同版本(分处归档内外)", "compat.module.unknown": "无法比较", "compat.module.realpath": "无法比较(路径取不到真实形态)", "compat.modules.realpath": "有 {count} 个包**比不了**:它们的路径取不到真实形态({names})—— 这不是「两份拷贝」,而是这项校验没做成。", "compat.modules.realpathSides": "宿主侧:{host};插件侧:{plugin}。修好路径(权限 / 路径长度 / 重新安装)后重新打开本页即重算。", "compat.modules.realpathNone": "无", "compat.modules.unverified": "有 {count} 个包没能比对(宿主的解析锚点里找不到:{names})—— 这不是「两份拷贝」,而是这项校验没做成。可运行 node scripts/doctor.mjs 查看。",
|
|
3379
|
+
"compat.modules.asarHost": "宿主是桌面版:官方包装在 {name} 里,插件在归档外,两边不可能是同一份文件。下面 {count} 个包按版本核对:{version},一致。",
|
|
3380
|
+
"compat.modules.asarHostPackages": "同版本、仅打包方式不同的 {count} 个包({version}):{names}。本机不要跑 host-deps.mjs --fix —— 那条命令会把插件依赖指向 npx 缓存里旧版宿主,而不是归档里的当前宿主。",
|
|
3272
3381
|
"compat.blockers": "阻塞项(按此修复)",
|
|
3273
3382
|
"compat.host.mismatch": "实际宿主 {host} 与本插件验证过的 {verified} 不同:能力按实际逐项探测,下面出现降级项时以实际能力为准,不要按旧版本的印象判断。",
|
|
3274
3383
|
"compat.features": "功能总览", "compat.features.hint": "一行一个功能点:装配层 + 宿主能力层 + 你当前配置的合成结果;点一行跳到对应页签",
|
|
@@ -3293,7 +3402,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3293
3402
|
"compat.ov.toolTable": "模型工具表", "compat.ov.toolTable.all": "{total} 个工具全部下发",
|
|
3294
3403
|
"compat.ov.toolTable.factory": "出厂默认关掉 {hidden}/{total} 个", "compat.ov.toolTable.mixed": "关掉 {hidden}/{total} 个(含出厂默认 {defaults} 个)",
|
|
3295
3404
|
"compat.ov.nativeDelete": "宿主原生删除入口", "compat.ov.nativeDelete.ok": "宿主未提供原生删除入口(本插件自有完整序列)",
|
|
3296
|
-
"compat.ov.hostIdentity": "宿主身份与模块", "compat.ov.hostIdentity.ok": "模块与宿主同源(宿主 DSH {version})", "compat.ov.hostIdentity.degraded": "存在阻塞项:{list}",
|
|
3405
|
+
"compat.ov.hostIdentity": "宿主身份与模块", "compat.ov.hostIdentity.ok": "模块与宿主同源(宿主 DSH {version})", "compat.ov.hostIdentity.asar": "官方包与宿主同版本(DSH {version}),但宿主跑在 {name} 里、插件在归档外", "compat.ov.hostIdentity.degraded": "存在阻塞项:{list}",
|
|
3297
3406
|
"compat.ov.memory": "记忆", "compat.ov.prompts": "提示词", "compat.ov.subagents": "子智能体", "compat.ov.scenes": "场景档案",
|
|
3298
3407
|
"compat.ov.plain.ok": "无降级上报(装配失败会出现在这里)",
|
|
3299
3408
|
// 降级 note 的模板(detailKey 见 src/compat/runtime-notes.ts;占位符与英文版一一对应)。
|
|
@@ -3470,6 +3579,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3470
3579
|
"compat.reach.tools.2": "场景 / 记忆 / MCP / 技能 / 子智能体 / 提示词 —— 由本插件按上面的「注入」块补齐。",
|
|
3471
3580
|
"compat.reach.tools.3": "提示词与技能官方自己也会送;官方有的不重复送,官方缺的(如极简)由本插件补上。",
|
|
3472
3581
|
"compat.hint.doctor": "命令行体检:",
|
|
3582
|
+
"compat.hint.doctorRelease": "本插件是从 npm 安装的(不带开发脚本);要命令行自查请在插件源码目录运行 node scripts/doctor.mjs。",
|
|
3473
3583
|
"scenes.title": "场景", "scenes.desc": "管理场景:预设 MCP、技能、子智能体与记忆的组合,一键切换。",
|
|
3474
3584
|
"scenes.stat.total": "个场景", "scenes.stat.active": "个已启用", "scenes.stat.archives": "个有档案",
|
|
3475
3585
|
"scenes.noDesc": "还没有描述",
|
|
@@ -3508,7 +3618,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3508
3618
|
"memory.btn.diagnose": "体检", "memory.btn.delete.confirm": "移入回收站",
|
|
3509
3619
|
"memory.diagnose.title": "记忆体检", "memory.diagnose.summary": "{rules} 条记忆 · {scenes} 个场景 · {issues} 个问题", "memory.diagnose.clean": "未发现问题,一切正常。",
|
|
3510
3620
|
"memory.diag.error": "错误", "memory.diag.warning": "警告", "memory.diag.info": "提示",
|
|
3511
|
-
"memory.stat.total": "条记忆", "memory.stat.scenes": "个启用场景", "memory.stat.sceneCount": "个场景", "memory.status.sceneOff": "场景未启用", "memory.status.off": "
|
|
3621
|
+
"memory.stat.total": "条记忆", "memory.stat.scenes": "个启用场景", "memory.stat.sceneCount": "个场景", "memory.status.sceneOff": "场景未启用", "memory.status.off": "未启用",
|
|
3512
3622
|
"memory.budget.label": "注入预算", "memory.budget.bytes": "{used} / {max} 字节",
|
|
3513
3623
|
"memory.budget.over": "已超限",
|
|
3514
3624
|
"memory.budget.truncated": "超出预算,段尾已列出未注入的记忆",
|
|
@@ -3553,7 +3663,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3553
3663
|
"memory.scene.createTitle": "新建场景",
|
|
3554
3664
|
"memory.deleteScene.title": "删除场景?", "memory.deleteScene.desc": "把「{name}」连同它下面全部记忆(含未启用的)一起移入回收站,可在回收站整条恢复;正在使用的场景不能删。",
|
|
3555
3665
|
"memory.result.sceneCreated": "已创建场景:{name}", "memory.result.sceneUpdated": "已保存场景:{name}", "memory.result.sceneRemoved": "已删除场景:{name}", "memory.result.sceneRemoved.withMemories": "已删除场景:{name}(连同 {count} 项记忆,可在回收站整条恢复)",
|
|
3556
|
-
"memory.table.name": "记忆名称与描述", "memory.table.tags": "标记",
|
|
3666
|
+
"memory.table.name": "记忆名称与描述", "memory.table.tags": "标记",
|
|
3557
3667
|
"error.rules.invalidArgs": "缺少参数:需要 all:true(回到全部启用)或 scenes:[...](显式收窄)", "error.rules.invalidGroup": "场景/分组名不合法(非空、≤64 字符、不含路径分隔符与 < > : \" | ? *、不以 . 开头)", "error.rules.invalidName": "记忆名不合法(非空、≤64 字符、不含路径分隔符与 < > : \" | ? *、不以 . 开头)", "error.rules.descriptionRequired": "描述不能为空", "error.rules.descriptionTooLong": "描述过长(不能超过 500 字符)", "error.rules.bodyRequired": "正文不能为空", "error.rules.tooLarge": "规则内容过大", "error.rules.shadowed": "规则被同名 bundle 遮蔽,无法写入", "error.rules.exists": "同名记忆已存在,无法写入", "error.rules.notFound": "规则不存在", "error.rules.budgetExceeded": "场景记忆段超出预算", "error.rules.ioFailed": "文件操作失败", "error.rules.outsideRoot": "拒绝操作:解析后的落点不在记忆根目录内(可能存在指向根外的目录链接)", "error.rules.nameTaken": "目标名称已被占用", "error.rules.sceneNotEmpty": "场景不为空,无法删除", "error.rules.notBundle": "该记忆是 flat(单文件),不能带附件", "error.rules.noFiles": "没有选择附件", "error.rules.emptyFile": "附件内容为空", "error.rules.fileTooLarge": "附件过大(单个上限 {limit} MB)", "error.rules.tooManyFiles": "一次最多 {limit} 个附件", "error.rules.sceneInMode": "该场景正处在当前模式,请先退出模式再删除",
|
|
3558
3668
|
"memory.archive.emptySection": "没有勾选任何条目 = 全部停用(整段没建也一样)", "memory.archive.emptySubagents": "没有勾选任何条目 = 本场景不启用任何人设(整段没建也一样)",
|
|
3559
3669
|
"subagents.title": "子智能体", "subagents.stat.total": "个子智能体", "subagents.stat.limited": "个有工具限制", "subagents.desc": "管理子智能体:新建、导入人设,正文即子代理的系统提示词。",
|
|
@@ -3593,7 +3703,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3593
3703
|
"subagents.field.output.hint": "子代理系统提示词里单独成节,写「产出必须长什么样」—— 只写可检验的要求。",
|
|
3594
3704
|
"subagents.result.saved": "已保存人设:{name}", "subagents.result.deleted": "已删除人设:{name}",
|
|
3595
3705
|
"subagents.delete.title": "删除人设?", "subagents.delete.desc": "将把「{name}」的人设文件移入回收站(子智能体页的「回收站」里可以恢复)。",
|
|
3596
|
-
"subagents.import": "导入子智能体", "subagents.import.title": "导入子智能体", "subagents.toggle": "启用子智能体", "subagents.disabled": "
|
|
3706
|
+
"subagents.import": "导入子智能体", "subagents.import.title": "导入子智能体", "subagents.toggle": "启用子智能体", "subagents.disabled": "未启用", "subagents.enabled": "已启用", "subagents.disabled.hint": "停用后不注入上下文,subagent_manager_list / subagent_manager_run 也看不到;文件保留,随时可再打开。",
|
|
3597
3707
|
"subagents.result.imported": "已导入 {count} 个人设:{names}",
|
|
3598
3708
|
"memory.import": "导入记忆", "memory.import.title": "导入记忆", "memory.import.scene": "导入到场景",
|
|
3599
3709
|
"memory.result.imported": "已导入 {count} 条记忆:{names}",
|
|
@@ -3603,7 +3713,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3603
3713
|
"import.none": "没有导入任何文件(全部被跳过)", "import.skipped": "已跳过:{items}",
|
|
3604
3714
|
"error.import.noFiles": "没有选择要导入的文件",
|
|
3605
3715
|
"nav.title": "工具",
|
|
3606
|
-
"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": "
|
|
3716
|
+
"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": "为当前场景设置全局提示词", "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.field.toolTable": "模型工具表方案", "scenes.field.toolTable.hint": "为当前场景设置可使用的模型工具方案", "scenes.field.toolTable.none": "不绑定(保持现状)", "scenes.field.toolTable.factory": "出厂默认(关 {count} 条)", "scenes.field.toolTable.count": "关 {count} 条", "scenes.field.toolTable.missing": "方案已不存在", "scenes.preview.row.toolPreset": "工具表将切到方案「{name}」(关 {count} 条)", "scenes.preview.row.toolPresetMissing": "工具表绑的方案「{name}」已不存在,这次不切换", "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": "未知错误",
|
|
3607
3717
|
},
|
|
3608
3718
|
en: {
|
|
3609
3719
|
"title": "Skills", "desc": "Manage skills: enable, import, create, trash.", "link.project": "GitHub", "link.feedback": "Issues",
|
|
@@ -3636,7 +3746,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3636
3746
|
"result.custom.removed": "Custom folder removed: {name}",
|
|
3637
3747
|
"result.opened": "Opened with the system default app: {path}",
|
|
3638
3748
|
"status.enabled": "Enabled", "status.disabled": "Disabled", "status.invalid": "Needs attention", "status.shadowed": "Shadowed", "status.shadowed.hint": "The copy “{name}” is the active one; click “Use this one” to switch to this source instead", "status.preferred": "Manually chosen", "status.readonly": "Source read-only", "status.notDeletable": "Cannot delete", "status.notDeletable.hint": "Deleting is available for plugin-managed sources (DSH skills / Imported skills / project .dsh/skills); external agent and custom directories are read-only", "status.manageable": "Manageable", "status.project": "Project scoped", "status.rank": "Rank {rank}", "status.source.on": "Enabled", "status.source.off": "Disabled", "status.bundle": "Bundle", "status.single": "Single file",
|
|
3639
|
-
"summary.total.one": "{count} skill", "summary.total.other": "{count} skills", "summary.enabled.one": "{count} enabled", "summary.enabled.other": "{count} enabled", "summary.issues.one": "{count} diagnostic", "summary.issues.other": "{count} diagnostics", "summary.group.one": "{count} skill", "summary.group.other": "{count} skills", "table.skill": "Skill name and description",
|
|
3749
|
+
"summary.total.one": "{count} skill", "summary.total.other": "{count} skills", "summary.enabled.one": "{count} enabled", "summary.enabled.other": "{count} enabled", "summary.issues.one": "{count} diagnostic", "summary.issues.other": "{count} diagnostics", "summary.group.one": "{count} skill", "summary.group.other": "{count} skills", "table.skill": "Skill name and description",
|
|
3640
3750
|
"filter.source": "Source", "filter.all": "All sources", "filter.option": "{name} ({count})", "search": "Search", "search.placeholder": "Search skill names or descriptions", "search.clear": "Clear search",
|
|
3641
3751
|
"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",
|
|
3642
3752
|
"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",
|
|
@@ -3680,13 +3790,13 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3680
3790
|
"mcp.level.all": "All levels", "mcp.level.project": "Profile level", "mcp.level.global": "Global", "mcp.level.loader": "Loaded",
|
|
3681
3791
|
"mcp.stat.total": "server(s)", "mcp.stat.enabled": "enabled", "mcp.stat.tools": "tool(s)",
|
|
3682
3792
|
"mcp.search": "Search servers", "mcp.search.placeholder": "Search a server name, URL or command",
|
|
3683
|
-
"mcp.btn.new": "New MCP", "mcp.btn.add": "Add", "mcp.btn.save": "Save", "mcp.btn.detail": "Details", "mcp.btn.edit": "Edit", "mcp.btn.
|
|
3793
|
+
"mcp.btn.new": "New MCP", "mcp.btn.add": "Add", "mcp.btn.save": "Save", "mcp.btn.detail": "Details", "mcp.btn.edit": "Edit", "mcp.btn.remove": "Delete",
|
|
3684
3794
|
"mcp.btn.enableAll": "Enable all", "mcp.btn.disableAll": "Disable all",
|
|
3685
3795
|
"mcp.loading": "Loading MCP servers…", "mcp.empty": "No MCP servers yet — click “New MCP”.", "mcp.empty.search": "No matching server.",
|
|
3686
3796
|
"mcp.servers.count": "{count} server(s)", "mcp.tools.count": "{count} tool(s)", "mcp.tools.countPartial": "{enabled}/{total} tool(s)", "mcp.duplicate": "duplicate id",
|
|
3687
|
-
"mcp.table.name": "Server name and URL",
|
|
3688
|
-
"mcp.live.notLoaded": "not loaded", "mcp.live.failed": "start failed", "mcp.live.stopped": "
|
|
3689
|
-
"mcp.live.failedHint": "Start failed — check the configuration and
|
|
3797
|
+
"mcp.table.name": "Server name and URL",
|
|
3798
|
+
"mcp.live.notLoaded": "not loaded", "mcp.live.failed": "start failed", "mcp.live.stopped": "disabled", "mcp.live.loading": "loading", "mcp.live.noTools": "no usable tools", "mcp.live.running": "enabled",
|
|
3799
|
+
"mcp.live.failedHint": "Start failed — check the configuration, then toggle the switch off and on to retry", "mcp.live.offlineHint": "Not connected: no tools are registered, but it had {count} tools when it last connected. Check the network, command or credentials, then toggle the switch off and on", "mcp.live.neverHint": "Never connected: no tools have ever been registered. Check the command, arguments or credentials, then toggle the switch off and on", "mcp.live.allOffHint": "All tools are disabled: nothing is callable right now (re-enable them to restore)",
|
|
3690
3800
|
"mcp.note.prefix": "Note: ", "mcp.toggleServer": "Toggle server", "mcp.toggleTool": "Toggle tool",
|
|
3691
3801
|
"mcp.msg.ok": "Done", "mcp.msg.failed": "Operation failed", "mcp.msg.loadFailed": "Load failed", "mcp.msg.warn": "Done, but the loader reported: {warning}",
|
|
3692
3802
|
"mcp.msg.toolOn": "Enabled tool: {name}", "mcp.msg.toolOff": "Disabled tool: {name}",
|
|
@@ -3713,7 +3823,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3713
3823
|
"mcp.tools.loading": "Fetching the tool list…", "mcp.tools.loadFailed": "Could not load tools: ", "mcp.tools.none": "This server has no registered tools.",
|
|
3714
3824
|
"mcp.tools.note": "Disabled tools are invisible to the model; changes take effect immediately",
|
|
3715
3825
|
"mcp.remove.title": "Delete MCP server", "mcp.remove.desc": "Delete “{name}”? Its configuration is removed from the patch file and the tools go offline at once; this cannot be undone",
|
|
3716
|
-
"mcp.
|
|
3826
|
+
"mcp.err.warnings": "Patch warnings: ",
|
|
3717
3827
|
"mcp.importJson.btn": "Import", "mcp.importJson.title": "Import MCP servers from JSON",
|
|
3718
3828
|
"mcp.importJson.hint": "Three shapes are accepted: an {\"mcpServers\": {…}} map, {\"rows\": […]} or a bare array. Check everything on this screen first — name sanitizing, transport detection and duplicate handling are all decided here.",
|
|
3719
3829
|
"mcp.importJson.placeholder": "Paste {\"mcpServers\": {…}}, {\"rows\": […]} or an array",
|
|
@@ -3759,11 +3869,12 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3759
3869
|
"mcp.inspect.check.kvNotString": "{where} contains non-string values: {keys}",
|
|
3760
3870
|
"mcp.inspect.check.noUrl": "No URL set",
|
|
3761
3871
|
"mcp.inspect.check.badUrl": "URL does not start with http(s):// : {url}",
|
|
3872
|
+
"mcp.inspect.check.httpUnreachable": "Endpoint probe failed: {url} did not respond ({reason}) — the config is well-formed, but this address is not usable right now",
|
|
3762
3873
|
"mcp.inspect.check.transportMissing": "No transport written — inferred from the fields present",
|
|
3763
3874
|
"mcp.inspect.check.badTransport": "Unrecognized transport: {transport}",
|
|
3764
3875
|
"mcp.inspect.check.noEndpoint": "Neither command nor URL",
|
|
3765
3876
|
"mcp.inspect.check.nameDup": "Server name “{name}” is shared by {count} different ids — the model sees colliding tool names",
|
|
3766
|
-
"root.dsh": "DSH skills", "root.hub": "Imported skills", "root.agents": "Shared Agent", "root.ccswitch": "CC Switch", "root.projectDsh": "Project DSH", "root.projectAgents": "Project Agent", "root.codex": "Codex", "root.claude": "Claude", "root.gemini": "Gemini", "root.opencode": "OpenCode", "root.cursor": "Cursor",
|
|
3877
|
+
"root.dsh": "DSH skills", "root.hub": "Imported skills", "root.official": "Official built-in", "root.agents": "Shared Agent", "root.ccswitch": "CC Switch", "root.projectDsh": "Project DSH", "root.projectAgents": "Project Agent", "root.codex": "Codex", "root.claude": "Claude", "root.gemini": "Gemini", "root.opencode": "OpenCode", "root.cursor": "Cursor",
|
|
3767
3878
|
"memory.title": "Memories",
|
|
3768
3879
|
"tabs.scenes": "Scenes", "tabs.skills": "Skills", "tabs.subagents": "Subagents", "tabs.prompts": "Prompts", "tabs.memory": "Memories", "tabs.sessions": "Sessions", "tabs.compat": "Host",
|
|
3769
3880
|
"compat.title": "Host compatibility", "compat.desc": "How this plugin attaches to DSH (read-only).",
|
|
@@ -3786,7 +3897,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3786
3897
|
"snapshot.import.masked": "secrets masked",
|
|
3787
3898
|
"snapshot.import.unmasked": "plaintext credentials",
|
|
3788
3899
|
"snapshot.preview.failed": "Could not read the snapshot",
|
|
3789
|
-
"compat.audit": "Recent changes", "compat.dialog.empty": "Nothing has been read for this panel yet — use Refresh at the top right.", "compat.audit.hint": "Who changed what, and when. Panel / model / scene engine each get their own badge; the log stores object names only, never argument payloads
|
|
3900
|
+
"compat.audit": "Recent changes", "compat.dialog.empty": "Nothing has been read for this panel yet — use Refresh at the top right.", "compat.audit.hint": "Who changed what, and when. Panel / model / scene engine each get their own badge; the log stores object names only, never argument payloads.\nNothing expires by time — once the file passes 512KB only the newest 500 rows are kept; use “Clear log” to empty it now.", "compat.audit.clear": "Clear log", "compat.audit.clear.title": "Empty this change log (cannot be undone)", "compat.audit.clear.confirm": "Confirm clear", "compat.audit.clearFailed": "Could not clear the log", "compat.audit.op.auditClear": "cleared the change log",
|
|
3790
3901
|
"compat.audit.domain": "Filter by area", "compat.audit.domain.all": "All changes", "compat.audit.domain.mcpm": "MCP", "compat.audit.domain.skill": "Skills", "compat.audit.domain.rules": "Memories", "compat.audit.domain.scene": "Scenes", "compat.audit.domain.subagent": "Personas", "compat.audit.domain.agentsmd": "Prompts", "compat.audit.domain.history": "Sessions", "compat.audit.domain.toolTable": "Model tool table", "compat.audit.domain.injectSettings": "Injection settings", "compat.audit.domain.backups": "Backup cleanup",
|
|
3791
3902
|
"compat.audit.reload": "Reload", "compat.audit.loading": "Loading…", "compat.audit.failed": "Could not read the log",
|
|
3792
3903
|
"compat.audit.empty": "Nothing logged yet — one toggle, import or scene visit will put the first row here",
|
|
@@ -3813,8 +3924,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3813
3924
|
"compat.state.ok": "All good", "compat.state.blocked": "Needs attention",
|
|
3814
3925
|
"compat.checking": "Checking…", "compat.empty": "Not checked yet", "compat.failed": "Check failed",
|
|
3815
3926
|
"compat.summary.ok": "host {version} · capabilities {ok}/{total} · all usable", "compat.summary.degraded": "host {version} · capabilities {ok}/{total} · {count} degraded",
|
|
3816
|
-
"compat.
|
|
3817
|
-
"compat.
|
|
3927
|
+
"compat.summary.identity": "host {version} · capabilities {ok}/{total} · {count} identity issue(s) to resolve",
|
|
3928
|
+
"compat.host": "Host version", "compat.verified": "Verified against",
|
|
3929
|
+
"compat.caps": "Usable capabilities", "compat.caps.degraded": "{count} unavailable",
|
|
3818
3930
|
"compat.caps.substituted": "gaps covered by the plugin",
|
|
3819
3931
|
"compat.ops": "Host action availability", "compat.ops.hint": "which route each action takes", "compat.ops.disabled": "{count} action(s) are unavailable right now (the affected buttons are disabled and explain why)",
|
|
3820
3932
|
"compat.op.name": "Action", "compat.op.state": "Route",
|
|
@@ -3825,7 +3937,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3825
3937
|
"compat.others": "Other capability slots", "compat.others.hint": "not degraded: the plugin supplies its own implementation when the host has no such entry",
|
|
3826
3938
|
"compat.fallback.substituted": "Plugin adapter (no host entry)",
|
|
3827
3939
|
"compat.fallback.native": "Falls back to native entry", "compat.fallback.refuse": "Operation refused", "compat.fallback.blocked": "Affected buttons disabled", "compat.fallback.inform": "Writes unaffected",
|
|
3828
|
-
"compat.modules": "Module identity (plugin vs host)", "compat.modules.hint": "adapting only works on one shared module", "compat.module.same": "same module", "compat.module.separate": "separate copy (repair needed)", "compat.module.unknown": "not comparable", "compat.modules.unverified": "{count} package(s) could not be compared (not resolvable from the host anchor: {names}) — that is not \"a separate copy\", it means the check could not run. Try node scripts/doctor.mjs.",
|
|
3940
|
+
"compat.modules": "Module identity (plugin vs host)", "compat.modules.hint": "adapting only works on one shared module", "compat.module.same": "same module", "compat.module.separate": "separate copy (repair needed)", "compat.module.sameVersion": "same version (inside/outside the archive)", "compat.module.unknown": "not comparable", "compat.module.realpath": "not comparable (real path unavailable)", "compat.modules.realpath": "{count} package(s) could **not be compared**: their real path could not be read ({names}) — that is not \"a separate copy\", it means the check could not run.", "compat.modules.realpathSides": "Host side: {host}; plugin side: {plugin}. Fix the path (permissions / path length / reinstall) and reopen this page to re-check.", "compat.modules.realpathNone": "none", "compat.modules.unverified": "{count} package(s) could not be compared (not resolvable from the host anchor: {names}) — that is not \"a separate copy\", it means the check could not run. Try node scripts/doctor.mjs.",
|
|
3941
|
+
"compat.modules.asarHost": "The host is the desktop app: the official packages live inside {name} while the plugin sits outside the archive, so the two can never be the same file. The {count} package(s) below were checked by version: {version} on both sides.",
|
|
3942
|
+
"compat.modules.asarHostPackages": "{count} package(s) at the same version ({version}), differing only in packaging: {names}. Do not run host-deps.mjs --fix on this machine — it would point the plugin's dependencies at an older host in the npx cache instead of the running one inside the archive.",
|
|
3829
3943
|
"compat.blockers": "Blockers (fix these)",
|
|
3830
3944
|
"compat.host.mismatch": "The running host {host} differs from the verified {verified}: capabilities are probed as installed, so trust the degraded list below over any assumption about that version.",
|
|
3831
3945
|
"compat.features": "Feature overview", "compat.features.hint": "one row per feature point: assembly + host capability + your current configuration; click a row to open its tab",
|
|
@@ -3850,7 +3964,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3850
3964
|
"compat.ov.toolTable": "Model tool table", "compat.ov.toolTable.all": "All {total} tools are sent",
|
|
3851
3965
|
"compat.ov.toolTable.factory": "Factory defaults turn off {hidden}/{total}", "compat.ov.toolTable.mixed": "{hidden}/{total} are off (including {defaults} factory defaults)",
|
|
3852
3966
|
"compat.ov.nativeDelete": "Host native delete entry", "compat.ov.nativeDelete.ok": "The host offers no native delete entry (this plugin ships its own full sequence)",
|
|
3853
|
-
"compat.ov.hostIdentity": "Host identity & modules", "compat.ov.hostIdentity.ok": "Modules are shared with the host (host DSH {version})", "compat.ov.hostIdentity.degraded": "Blockers present: {list}",
|
|
3967
|
+
"compat.ov.hostIdentity": "Host identity & modules", "compat.ov.hostIdentity.ok": "Modules are shared with the host (host DSH {version})", "compat.ov.hostIdentity.asar": "Official packages match the host version (DSH {version}), but the host runs inside {name} while the plugin sits outside it", "compat.ov.hostIdentity.degraded": "Blockers present: {list}",
|
|
3854
3968
|
"compat.ov.memory": "Memory", "compat.ov.prompts": "Prompts", "compat.ov.subagents": "Subagents", "compat.ov.scenes": "Scene profiles",
|
|
3855
3969
|
"compat.ov.plain.ok": "No degradation reported (assembly failures would appear here)",
|
|
3856
3970
|
// Templates for degraded runtime notes (detailKey per src/compat/runtime-notes.ts; placeholders mirror the zh side one for one).
|
|
@@ -4023,6 +4137,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4023
4137
|
"compat.reach.tools.2": "Scene / memory / MCP / skills / subagents / prompt — filled in by this plugin according to the Injection block above.",
|
|
4024
4138
|
"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).",
|
|
4025
4139
|
"compat.hint.doctor": "Command-line check:",
|
|
4140
|
+
"compat.hint.doctorRelease": "This plugin was installed from npm, which does not ship the developer scripts; to run a command-line check, use node scripts/doctor.mjs inside a source checkout.",
|
|
4026
4141
|
"scenes.title": "Scenes", "scenes.desc": "Manage scenes: preset combinations of MCP, skills, subagents and memories, switch with one click.",
|
|
4027
4142
|
"scenes.stat.total": "scene(s)", "scenes.stat.active": "enabled", "scenes.stat.archives": "with a profile",
|
|
4028
4143
|
"scenes.noDesc": "No description yet",
|
|
@@ -4061,7 +4176,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4061
4176
|
"memory.btn.diagnose": "Check", "memory.btn.delete.confirm": "Move to trash",
|
|
4062
4177
|
"memory.diagnose.title": "Memory checkup", "memory.diagnose.summary": "{rules} memories · {scenes} scenes · {issues} issues", "memory.diagnose.clean": "No issues found.",
|
|
4063
4178
|
"memory.diag.error": "Error", "memory.diag.warning": "Warning", "memory.diag.info": "Info",
|
|
4064
|
-
"memory.stat.total": "memories", "memory.stat.scenes": "enabled scenes", "memory.stat.sceneCount": "scenes", "memory.status.sceneOff": "scene off", "memory.status.off": "
|
|
4179
|
+
"memory.stat.total": "memories", "memory.stat.scenes": "enabled scenes", "memory.stat.sceneCount": "scenes", "memory.status.sceneOff": "scene off", "memory.status.off": "disabled",
|
|
4065
4180
|
"memory.budget.label": "Injection budget", "memory.budget.bytes": "{used} / {max} bytes",
|
|
4066
4181
|
"memory.budget.over": "Over budget",
|
|
4067
4182
|
"memory.budget.truncated": "Over budget — the memories left out are listed at the end",
|
|
@@ -4106,7 +4221,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4106
4221
|
"memory.scene.createTitle": "New scene",
|
|
4107
4222
|
"memory.deleteScene.title": "Delete scene?", "memory.deleteScene.desc": "Moves “{name}” and every memory under it (including disabled ones) to the trash, restorable as one entry; a scene in use cannot be deleted.",
|
|
4108
4223
|
"memory.result.sceneCreated": "Scene created: {name}", "memory.result.sceneUpdated": "Scene saved: {name}", "memory.result.sceneRemoved": "Scene deleted: {name}", "memory.result.sceneRemoved.withMemories": "Scene deleted: {name} (with {count} memory file(s); restorable as one entry from the trash)",
|
|
4109
|
-
"memory.table.name": "Memory name and description", "memory.table.tags": "Tags",
|
|
4224
|
+
"memory.table.name": "Memory name and description", "memory.table.tags": "Tags",
|
|
4110
4225
|
"error.rules.invalidArgs": "Missing arguments: pass all:true (enable everything) or scenes:[...] (explicit narrowing)", "error.rules.invalidGroup": "Invalid scene/group name (non-empty, ≤64 chars, no path separators or < > : \" | ? *, must not start with a dot)", "error.rules.invalidName": "Invalid memory name (non-empty, ≤64 chars, no / \\ < > : \" | ? *, must not start with a dot)", "error.rules.descriptionRequired": "Description is required", "error.rules.descriptionTooLong": "Description is too long (max 500 chars)", "error.rules.bodyRequired": "Body is required", "error.rules.tooLarge": "Rule content is too large", "error.rules.shadowed": "Rule is shadowed by a bundle of the same name", "error.rules.exists": "A memory with this name already exists", "error.rules.notFound": "Rule not found", "error.rules.budgetExceeded": "Scene memory section exceeds its budget", "error.rules.ioFailed": "File operation failed", "error.rules.outsideRoot": "Refused: the resolved path falls outside the memories root (a directory link may point outside)", "error.rules.nameTaken": "That name is already taken", "error.rules.sceneNotEmpty": "Scene is not empty; cannot be deleted", "error.rules.notBundle": "This memory is flat (a single file) and cannot carry attachments", "error.rules.noFiles": "No files selected", "error.rules.emptyFile": "Empty file", "error.rules.fileTooLarge": "File too large (max {limit} MB each)", "error.rules.tooManyFiles": "At most {limit} files per upload", "error.rules.sceneInMode": "This scene is the active mode; exit the mode before deleting it",
|
|
4111
4226
|
"subagents.title": "Subagents", "subagents.stat.total": " subagents", "subagents.stat.limited": " tool-restricted", "subagents.desc": "Manage subagents: create and import personas; a persona's body is the subagent's system prompt.",
|
|
4112
4227
|
"prompts.desc": "Manage prompts: preset the global instruction baseline; applying writes AGENTS.md and takes effect on the next turn.",
|
|
@@ -4145,7 +4260,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4145
4260
|
"subagents.field.output.hint": "Goes into the subagent's system prompt as its own section — say what the output must look like, and only write checkable requirements.",
|
|
4146
4261
|
"subagents.result.saved": "Persona saved: {name}", "subagents.result.deleted": "Persona deleted: {name}",
|
|
4147
4262
|
"subagents.delete.title": "Delete persona?", "subagents.delete.desc": "Moves the persona file “{name}” to the trash (restorable from the Trash button on the Subagents page).",
|
|
4148
|
-
"subagents.import": "Import subagents", "subagents.import.title": "Import subagents", "subagents.toggle": "Toggle subagent", "subagents.disabled": "Disabled", "subagents.disabled.hint": "Disabled personas are not injected into the context and hidden from subagent_manager_list / subagent_manager_run; the file is kept and can be re-enabled anytime.",
|
|
4263
|
+
"subagents.import": "Import subagents", "subagents.import.title": "Import subagents", "subagents.toggle": "Toggle subagent", "subagents.disabled": "Disabled", "subagents.enabled": "Enabled", "subagents.disabled.hint": "Disabled personas are not injected into the context and hidden from subagent_manager_list / subagent_manager_run; the file is kept and can be re-enabled anytime.",
|
|
4149
4264
|
"subagents.result.imported": "Imported {count} persona(s): {names}",
|
|
4150
4265
|
"memory.import": "Import memories", "memory.import.title": "Import memories", "memory.import.scene": "Import into scene",
|
|
4151
4266
|
"memory.result.imported": "Imported {count} memory/memories: {names}",
|
|
@@ -4155,7 +4270,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4155
4270
|
"import.none": "Nothing was imported (all skipped)", "import.skipped": "Skipped: {items}",
|
|
4156
4271
|
"error.import.noFiles": "No files selected",
|
|
4157
4272
|
"nav.title": "Tools",
|
|
4158
|
-
"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.field.toolTable": "Model tool-table scheme", "scenes.field.toolTable.hint": "Choose the model tool-table scheme this scene may use", "scenes.field.toolTable.none": "Not bound (keep as is)", "scenes.field.toolTable.factory": "Factory default (hides {count})", "scenes.field.toolTable.count": "hides {count}", "scenes.field.toolTable.missing": "scheme no longer exists", "scenes.preview.row.toolPreset": "Tool table switches to scheme “{name}” (hides {count})", "scenes.preview.row.toolPresetMissing": "The tool-table scheme “{name}” bound here no longer exists — nothing switches this time", "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",
|
|
4273
|
+
"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": "Sets the global prompt for this scene", "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.field.toolTable": "Model tool-table scheme", "scenes.field.toolTable.hint": "Choose the model tool-table scheme this scene may use", "scenes.field.toolTable.none": "Not bound (keep as is)", "scenes.field.toolTable.factory": "Factory default (hides {count})", "scenes.field.toolTable.count": "hides {count}", "scenes.field.toolTable.missing": "scheme no longer exists", "scenes.preview.row.toolPreset": "Tool table switches to scheme “{name}” (hides {count})", "scenes.preview.row.toolPresetMissing": "The tool-table scheme “{name}” bound here no longer exists — nothing switches this time", "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",
|
|
4159
4274
|
}
|
|
4160
4275
|
};
|
|
4161
4276
|
|
|
@@ -4545,9 +4660,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4545
4660
|
const [msg, setMsg] = React.useState(null)
|
|
4546
4661
|
const [busy, setBusy] = React.useState(null)
|
|
4547
4662
|
const [query, setQuery] = React.useState('')
|
|
4548
|
-
// 过滤用防抖值(输入框仍绑 `query`):本页每 5
|
|
4549
|
-
//
|
|
4550
|
-
//
|
|
4663
|
+
// 过滤用防抖值(输入框仍绑 `query`):本页每 5 秒轮询一次(内容没变不重渲染,
|
|
4664
|
+
// 见 applyRows 的指纹去重),不防抖就是"每个按键"都全量扫一遍列表。轮询改的是
|
|
4665
|
+
// rows 不是 query,所以输入期间不会被轮询打断。
|
|
4551
4666
|
const debouncedQuery = useDebouncedValue(query)
|
|
4552
4667
|
const [levelFilter, setLevelFilter] = React.useState('')
|
|
4553
4668
|
const [collapsed, setCollapsed] = React.useState({})
|
|
@@ -4558,7 +4673,6 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4558
4673
|
// 详情弹窗的请求序号:见 openDetail / closeDetail(晚到响应不得复活弹窗)。
|
|
4559
4674
|
const detailSeqRef = React.useRef(0)
|
|
4560
4675
|
const [confirmRow, setConfirmRow] = React.useState(null)
|
|
4561
|
-
const [restartInfo, setRestartInfo] = React.useState(null)
|
|
4562
4676
|
const [reveal, setReveal] = React.useState(false)
|
|
4563
4677
|
const [settings, setSettings] = React.useState(null)
|
|
4564
4678
|
const [noteDraft, setNoteDraft] = React.useState('')
|
|
@@ -4575,6 +4689,12 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4575
4689
|
const flipRef = React.useRef(null)
|
|
4576
4690
|
useFlipReorder(flipRef)
|
|
4577
4691
|
|
|
4692
|
+
// 上一次列表响应的内容指纹(applyRows 写入)。轮询通常带回一模一样的数据:
|
|
4693
|
+
// 指纹没变就跳过 setState,否则每 5 秒整页换一批新对象,所有 useMemo、React
|
|
4694
|
+
// 协调、开着的弹窗全部跟着空转一遍 —— 自动化点击与用户输入都可能被这阵重渲染
|
|
4695
|
+
// 打断(2026-09-29 GUI 测试实录)。
|
|
4696
|
+
const lastListFingerprint = React.useRef(null)
|
|
4697
|
+
|
|
4578
4698
|
const refresh = (withReveal, onRows, silent) => {
|
|
4579
4699
|
const useReveal = withReveal === undefined ? reveal : withReveal === true
|
|
4580
4700
|
// 手动刷新(含首次加载)先把按钮切到「刷新中…」并禁用 —— 点完毫无反馈等于没点。
|
|
@@ -4584,7 +4704,16 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4584
4704
|
// mcpm-list 始终脱敏、无令牌也能渲染页面。
|
|
4585
4705
|
const applyRows = (res) => {
|
|
4586
4706
|
const rows = (res && res.rows) || []
|
|
4587
|
-
setState
|
|
4707
|
+
// 指纹必须覆盖 setState 会写进 state 的每个字段(loading / error 除外:
|
|
4708
|
+
// 它们由下面的 updater 单独判定 —— 手动刷新挂起的 loading、上一轮的错误
|
|
4709
|
+
// 提示,即使数据没变也要用这次响应收尾)。
|
|
4710
|
+
const fingerprint = JSON.stringify([
|
|
4711
|
+
res && res.ok, res && res.error, rows, res && res.paths, res && res.errors,
|
|
4712
|
+
res && res.warnings, (res && res.anyLocked) === true, (res && res.activeScene) || null,
|
|
4713
|
+
])
|
|
4714
|
+
const changed = fingerprint !== lastListFingerprint.current
|
|
4715
|
+
lastListFingerprint.current = fingerprint
|
|
4716
|
+
setState((prev) => (changed || prev.loading || prev.error ? {
|
|
4588
4717
|
loading: false,
|
|
4589
4718
|
error: res && res.ok ? null : ((res && res.error) || mt('mcp.msg.loadFailed')),
|
|
4590
4719
|
rows,
|
|
@@ -4594,7 +4723,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4594
4723
|
locked: (res && res.anyLocked) === true,
|
|
4595
4724
|
// 场景内开关由档案定义:页面上这些开关置灰,并说明去哪儿改。
|
|
4596
4725
|
scene: (res && res.activeScene) || null,
|
|
4597
|
-
}))
|
|
4726
|
+
} : prev))
|
|
4598
4727
|
if (onRows) onRows(rows)
|
|
4599
4728
|
}
|
|
4600
4729
|
apiCall(useReveal ? 'mcpm-reveal' : 'mcpm-list', {}).then((res) => {
|
|
@@ -4646,7 +4775,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4646
4775
|
refresh()
|
|
4647
4776
|
if (onOk) onOk()
|
|
4648
4777
|
} else setMsg({ kind: 'err', text: (res && res.error) || mt('mcp.msg.failed') })
|
|
4649
|
-
}).catch((e) => setMsg({ kind: 'err', text: errMsg(e) })).then(() =>
|
|
4778
|
+
}).catch((e) => setMsg({ kind: 'err', text: errMsg(e) })).then(() => setBusy(null))
|
|
4650
4779
|
}
|
|
4651
4780
|
|
|
4652
4781
|
/**
|
|
@@ -4794,10 +4923,6 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4794
4923
|
setDetail(Object.assign({}, detail, { row: Object.assign({}, row, { notes: noteDraft.trim() }) }))
|
|
4795
4924
|
})
|
|
4796
4925
|
}
|
|
4797
|
-
const restartRow = (row) => {
|
|
4798
|
-
setRestartInfo({ id: row.id, name: row.serverName, startedAt: Date.now() })
|
|
4799
|
-
run('mcpm-restart', { id: row.id, level: row.level }, row.id + ':restart')
|
|
4800
|
-
}
|
|
4801
4926
|
const confirmRemove = () => {
|
|
4802
4927
|
const row = confirmRow
|
|
4803
4928
|
setConfirmRow(null)
|
|
@@ -4951,14 +5076,15 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4951
5076
|
[groups, visibleRows],
|
|
4952
5077
|
)
|
|
4953
5078
|
|
|
5079
|
+
// 状态口径:tone 只决定胶囊配色(ok=能用、bad=坏了、warn=当前没工具、muted=没在跑)。
|
|
4954
5080
|
const liveStatus = (row) => {
|
|
4955
|
-
if (!row.live) return { text: mt('mcp.live.notLoaded'),
|
|
4956
|
-
if (row.live.phase === 'failed') return { text: mt('mcp.live.failed'),
|
|
4957
|
-
if (!row.live.enabled) return { text: mt('mcp.live.stopped'),
|
|
4958
|
-
if (row.live.phase && row.live.phase !== 'active') return { text: mt('mcp.live.loading'),
|
|
5081
|
+
if (!row.live) return { text: mt('mcp.live.notLoaded'), tone: 'muted' }
|
|
5082
|
+
if (row.live.phase === 'failed') return { text: mt('mcp.live.failed'), tone: 'bad' }
|
|
5083
|
+
if (!row.live.enabled) return { text: mt('mcp.live.stopped'), tone: 'muted' }
|
|
5084
|
+
if (row.live.phase && row.live.phase !== 'active') return { text: mt('mcp.live.loading'), tone: 'muted' }
|
|
4959
5085
|
// 判"有没有可用工具"只看**真实注册**的那个数(缓存不算)。
|
|
4960
|
-
if (liveEnabledOf(row) === 0) return { text: mt('mcp.live.noTools'),
|
|
4961
|
-
return { text: mt('mcp.live.running'),
|
|
5086
|
+
if (liveEnabledOf(row) === 0) return { text: mt('mcp.live.noTools'), tone: 'warn' }
|
|
5087
|
+
return { text: mt('mcp.live.running'), tone: 'ok' }
|
|
4962
5088
|
}
|
|
4963
5089
|
const liveHint = (row) => {
|
|
4964
5090
|
if (!row.live) return null
|
|
@@ -4976,9 +5102,6 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4976
5102
|
}
|
|
4977
5103
|
return null
|
|
4978
5104
|
}
|
|
4979
|
-
// 状态标签配色:绿=能用、红=坏了、橙=当前没有可用工具(未连上或工具全被停用)、灰=没在跑 / 还没定。
|
|
4980
|
-
const pillKind = (cls) => cls === 'dsm-enabled' ? 'ok' : cls === 'dsm-failed' ? 'bad' : cls === 'dsm-disabled' ? 'warn' : 'muted'
|
|
4981
|
-
|
|
4982
5105
|
// 工具数标签:有被停用的工具时给「可用/总数」(场景档案收窄与手动逐工具开关
|
|
4983
5106
|
// 写的是同一张停用表),否则就是总数 —— 与详情页每个工具的开关状态一致。
|
|
4984
5107
|
//
|
|
@@ -4996,7 +5119,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4996
5119
|
const renderRow = (row) => {
|
|
4997
5120
|
const status = liveStatus(row)
|
|
4998
5121
|
const hint = liveHint(row)
|
|
4999
|
-
//
|
|
5122
|
+
// 场景锁定期间整页只读(开关 / 编辑 / 删除一并消失)。
|
|
5000
5123
|
const editable = row.level !== 'loader' && state.locked !== true
|
|
5001
5124
|
// 体检状态:未体检=灰(不是"没问题")、进行中=闪、有 warn=黄、info 级说明不点黄。
|
|
5002
5125
|
const insp = inspections[inspectKeyOf(row)]
|
|
@@ -5007,22 +5130,26 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
5007
5130
|
: !insp ? mt('mcp.inspect.idle')
|
|
5008
5131
|
: warnCount > 0 ? mt('mcp.inspect.warnCount', { count: warnCount })
|
|
5009
5132
|
: mt('mcp.inspect.pass')
|
|
5010
|
-
return React.createElement('div', { key: row.id, className: 'dsm-row', 'data-flip-key': String(row.level || '') + '/' + row.id, 'data-flip-on': row.disabled ? '0' : '1' },
|
|
5011
|
-
React.createElement('div', { className: 'dsm-main' },
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5133
|
+
return React.createElement('div', { key: row.id, className: 'dsm-row dsm-row-compact', 'data-flip-key': String(row.level || '') + '/' + row.id, 'data-flip-on': row.disabled ? '0' : '1' },
|
|
5134
|
+
React.createElement('div', { className: 'dsm-main dsm-dot-indent' },
|
|
5135
|
+
// 体检点与状态都收进名字这一行(用户裁定 2026-09-29):点要在名字前面才第一眼
|
|
5136
|
+
// 看得到,状态改成胶囊跟着名字走,与子智能体那一页同形。
|
|
5137
|
+
React.createElement('div', { className: 'dsm-name-row' },
|
|
5138
|
+
React.createElement(InspectDot, { tone: inspTone, title: inspTitle }),
|
|
5139
|
+
React.createElement('div', { className: 'dsm-name' }, row.serverName),
|
|
5140
|
+
React.createElement(StatusTag, { tone: status.tone, text: status.text })),
|
|
5141
|
+
// 备注与标签同一行(用户裁定 2026-09-29):标签以前自己占一行,带备注的行就比
|
|
5142
|
+
// 不带的高出一截,同一页里行高不齐 —— 两行是这行的固定预算。
|
|
5143
|
+
React.createElement('div', { className: 'dsm-meta-row' },
|
|
5144
|
+
(levelFilter === 'loader' && row.level && row.level !== 'loader') ? React.createElement('span', { className: 'dsm-tag' }, mt('mcp.level.' + row.level)) : null,
|
|
5145
|
+
toolCountTag(row),
|
|
5146
|
+
row.duplicate ? React.createElement('span', { className: 'dsm-tag dsm-tag-off' }, mt('mcp.duplicate')) : null,
|
|
5147
|
+
row.notes ? React.createElement('div', { className: 'dsm-note dsm-note-user', title: row.notes }, mt('mcp.note.prefix') + row.notes) : null)),
|
|
5020
5148
|
React.createElement('div', { className: 'dsm-row-actions' },
|
|
5021
5149
|
editable ? React.createElement(Switch, { on: !row.disabled, disabled: busy !== null || state.locked === true, label: mt('mcp.toggleServer') + ' ' + row.serverName, onClick: () => toggleRow(row) }) : null,
|
|
5022
5150
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: !!(insp && insp.busy), onClick: () => runInspect(row) }, mt('mcp.inspect.row.btn')),
|
|
5023
5151
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', onClick: () => openDetail(row) }, mt('mcp.btn.detail')),
|
|
5024
5152
|
editable ? React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy !== null, onClick: () => openEdit(row) }, mt('mcp.btn.edit')) : null,
|
|
5025
|
-
editable ? React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy !== null, onClick: () => restartRow(row) }, mt('mcp.btn.restart')) : null,
|
|
5026
5153
|
editable ? React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: busy !== null, onClick: () => setConfirmRow(row) }, mt('mcp.btn.remove')) : null),
|
|
5027
5154
|
(insp && insp.checks && insp.checks.length > 0)
|
|
5028
5155
|
? React.createElement('div', { className: 'dsm-inspect' }, insp.checks.map((c) => React.createElement('div', { key: c.id, className: 'dsm-inspect-line' + (c.level === 'warn' ? ' dsm-inspect-warn' : '') }, mt('mcp.inspect.check.' + c.id, c.params || {}))))
|
|
@@ -5041,10 +5168,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
5041
5168
|
group.path ? React.createElement('span', { className: 'dsm-path', title: group.path }, group.path) : null)),
|
|
5042
5169
|
open ? React.createElement('div', { className: 'dsm-source-body' },
|
|
5043
5170
|
React.createElement(React.Fragment, null,
|
|
5044
|
-
React.createElement('div', { className: 'dsm-table-head' },
|
|
5045
|
-
React.createElement('span',
|
|
5046
|
-
React.createElement('span', null, mt('mcp.table.transport')),
|
|
5047
|
-
React.createElement('span', null, mt('mcp.table.status')),
|
|
5171
|
+
React.createElement('div', { className: 'dsm-table-head dsm-row-compact' },
|
|
5172
|
+
React.createElement('span', { className: 'dsm-dot-indent' }, mt('mcp.table.name')),
|
|
5048
5173
|
React.createElement('span', null, '')),
|
|
5049
5174
|
groupRows.map(renderRow))) : null)
|
|
5050
5175
|
}
|
|
@@ -5158,7 +5283,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
5158
5283
|
// 不该占一整块,也不该跟配置事实(是否登记在 Loader)用冒号拼成一句话。
|
|
5159
5284
|
title: React.createElement('span', { className: 'dsm-modal-title-row' },
|
|
5160
5285
|
mt('mcp.detail.title') + detail.row.serverName,
|
|
5161
|
-
React.createElement('span', { className: 'dsm-pill dsm-pill-' +
|
|
5286
|
+
React.createElement('span', { className: 'dsm-pill dsm-pill-' + detailStatus.tone }, detailStatus.text)),
|
|
5162
5287
|
closeLabel: mt('btn.close'),
|
|
5163
5288
|
onClose: closeDetail,
|
|
5164
5289
|
},
|
|
@@ -5179,7 +5304,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
5179
5304
|
// 状态本身已在标题标签里,再复述一遍就是噪音。
|
|
5180
5305
|
detailHint ? React.createElement('div', { className: 'dsm-detail-section' },
|
|
5181
5306
|
React.createElement('div', { className: 'dsm-detail-title' }, mt('mcp.detail.status')),
|
|
5182
|
-
React.createElement('div', { className: 'dsm-feedback' + (detailStatus.
|
|
5307
|
+
React.createElement('div', { className: 'dsm-feedback' + (detailStatus.tone === 'bad' ? ' dsm-error' : ' dsm-warning') }, detailHint)) : null,
|
|
5183
5308
|
React.createElement('div', { className: 'dsm-detail-section' },
|
|
5184
5309
|
// 字数计数摆在小标题同一行右侧:备注的上限就是注入段的截断长度(`MCP_NOTE_MAX`)。
|
|
5185
5310
|
// 超限标红沿用记忆页描述那套(`dsm-char-over`)—— 服务端不校验备注长度,
|
|
@@ -5229,7 +5354,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
5229
5354
|
// 导出是只读动作,锁定期间照常用(拿配置不该要求先解锁环境)。
|
|
5230
5355
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: state.locked === true, onClick: () => setJsonImportOpen(true) }, mt('mcp.importJson.btn')),
|
|
5231
5356
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: state.loading, onClick: () => setJsonExportOpen(true) }, mt('mcp.exportJson.btn')),
|
|
5232
|
-
//
|
|
5357
|
+
// 体检不改本插件的任何状态(补丁只读;streamable-http 行会向配置地址发一次
|
|
5358
|
+
// 连通性探测),所以锁定期间照点 —— 冻的是改动,不是"看一眼配置对不对"。
|
|
5233
5359
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: state.loading || !summary.total, onClick: () => runInspect(null) }, mt('mcp.inspect.btn')),
|
|
5234
5360
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy !== null || state.loading || state.locked === true, title: mt('mcp.btn.compact.title'), onClick: () => setCompactConfirm(true) }, mt('mcp.btn.compact')),
|
|
5235
5361
|
// 批量启停与页头其余动作同栏(用户 2026-09-17 裁定)。场景内**照常可用**:
|
|
@@ -5249,7 +5375,6 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
5249
5375
|
React.createElement('input', { className: 'dsm-control dsm-search', value: query, 'aria-label': mt('mcp.search'), placeholder: mt('mcp.search.placeholder'), onChange: (ev) => setQuery(ev.target.value) }),
|
|
5250
5376
|
React.createElement('div', { className: 'dsm-source-filter' },
|
|
5251
5377
|
React.createElement(SourceSelect, { value: levelFilter, options: mcpLevelOptions(), onChange: setLevelFilter }))),
|
|
5252
|
-
restartInfo ? React.createElement(RestartNotice, { key: 'restart', ctx: ctx, info: restartInfo, t: mt }) : null,
|
|
5253
5378
|
React.createElement(Notice, { kind: msg && msg.kind, text: msg && msg.text }),
|
|
5254
5379
|
React.createElement(Notice, { kind: 'err', text: state.error }),
|
|
5255
5380
|
React.createElement(Notice, { kind: 'warn', text: (state.errors && state.errors.length > 0) ? mt('mcp.err.warnings') + state.errors.join(';') : null }),
|
|
@@ -5830,18 +5955,6 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
5830
5955
|
}))
|
|
5831
5956
|
}
|
|
5832
5957
|
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
/**
|
|
5836
|
-
* 字段下的多行说明 → 条目清单(与导入弹窗的「文件要求」同款样式,见 dsm-help-list)。
|
|
5837
|
-
* 词典值按 `\n` 分段,一段一条:一整句话里塞三件事时,条目比句子好扫。
|
|
5838
|
-
* 只有一件事实的说明不要用它 —— 一条的清单比一句话更难读。
|
|
5839
|
-
*/
|
|
5840
|
-
function helpBullets(t, key) {
|
|
5841
|
-
return React.createElement('ul', { className: 'dsm-help-list' }, String(t(key)).split('\n').map(function (line, i) {
|
|
5842
|
-
return React.createElement('li', { key: i }, line)
|
|
5843
|
-
}))
|
|
5844
|
-
}
|
|
5845
5958
|
function translateError(t, payload) {
|
|
5846
5959
|
// HTTP 层抛的 Error 还带 code/params(见 parseApiResponse),声明里没有这两个字段。
|
|
5847
5960
|
if (payload instanceof Error && !(/** @type {any} */ (payload)).code) return payload.message;
|
|
@@ -5902,19 +6015,6 @@ function joinWarn() {
|
|
|
5902
6015
|
for (var i = 0; i < arguments.length; i++) if (arguments[i]) out.push(arguments[i]);
|
|
5903
6016
|
return out.join(" ");
|
|
5904
6017
|
}
|
|
5905
|
-
/**
|
|
5906
|
-
* 重启等待秒数。**单独成组件,自带 1 秒定时器。**
|
|
5907
|
-
*
|
|
5908
|
-
* 原先这个数字靠 MCP 页每秒 `setTick` 强制整页重渲染来"走"起来 —— 而那个 state 本身
|
|
5909
|
-
* 从没被读过,渲染时直接算 `Date.now()`。于是整页(含长列表)每秒重渲染一次,只为让
|
|
5910
|
-
* 一行文字里的秒数 +1。隔离之后只有这一行每秒重渲染。
|
|
5911
|
-
*/
|
|
5912
|
-
function RestartNotice(props) {
|
|
5913
|
-
var [now, setNow] = React.useState(Date.now());
|
|
5914
|
-
React.useEffect(function () { return props.ctx.interval(function () { setNow(Date.now()); }, 1000); }, []);
|
|
5915
|
-
return React.createElement('div', { className: 'dsm-feedback' },
|
|
5916
|
-
props.t('mcp.restarting', { name: props.info.name, seconds: Math.max(0, Math.floor((now - props.info.startedAt) / 1000)) }));
|
|
5917
|
-
}
|
|
5918
6018
|
/**
|
|
5919
6019
|
* 批量开关的撤销条状态:`{ text, items, revert }`。四域共用(MCP / 技能 / 记忆)。
|
|
5920
6020
|
*
|
|
@@ -6612,6 +6712,15 @@ function enabledMemoryCount(rules) {
|
|
|
6612
6712
|
})
|
|
6613
6713
|
}
|
|
6614
6714
|
|
|
6715
|
+
/**
|
|
6716
|
+
* 行内状态标签:挂在名字后面的那颗描边胶囊,四个域共用一个出口(配色口径一致)。
|
|
6717
|
+
* tone:ok=能用、bad=坏了、warn=当前不能用但不是故障、muted=没在跑 / 不是问题(灰)。
|
|
6718
|
+
*/
|
|
6719
|
+
function StatusTag(props) {
|
|
6720
|
+
var tone = props.tone === 'ok' ? ' dsm-tag-on' : props.tone === 'bad' ? ' dsm-tag-off' : props.tone === 'warn' ? ' dsm-tag-warn' : ''
|
|
6721
|
+
return h('span', { className: 'dsm-tag' + tone, title: props.title }, props.text)
|
|
6722
|
+
}
|
|
6723
|
+
|
|
6615
6724
|
// ---------- MCP JSON 导入 / 导出(0.15.0 A1)------------------------------------
|
|
6616
6725
|
// 服务端 `mcpm-import` / `mcpm-export` 早就实现完整,缺的只是界面与**格式转换**:
|
|
6617
6726
|
// 社区流通的块是 `{"mcpServers": {...}}`,而服务端只认数组或 `{rows:[...]}`,
|
|
@@ -7069,9 +7178,11 @@ function enabledMemoryCount(rules) {
|
|
|
7069
7178
|
.catch(swallowPostError);
|
|
7070
7179
|
}
|
|
7071
7180
|
function renderSkill(root, skill) {
|
|
7072
|
-
var enabled = isSkillEnabled(skill), key = skill.shadowedBy ? "status.shadowed" : skill.loadable === false ? "status.invalid" : enabled ? "status.enabled" : "status.disabled"
|
|
7181
|
+
var enabled = isSkillEnabled(skill), key = skill.shadowedBy ? "status.shadowed" : skill.loadable === false ? "status.invalid" : enabled ? "status.enabled" : "status.disabled";
|
|
7182
|
+
// 胶囊配色:橙=诊断异常(要人去看),灰=没生效但不是故障(被同名覆盖、本条停用),绿=生效中。
|
|
7183
|
+
var tone = skill.shadowedBy ? "muted" : skill.loadable === false ? "warn" : enabled ? "ok" : "muted";
|
|
7073
7184
|
var canToggle = root.toggleable !== false && skill.loadable !== false;
|
|
7074
|
-
return h("div", { key: skill.name, className: "dsm-row", "data-flip-key": root.key + "/" + skill.name, "data-flip-on": enabled ? "1" : "0" }, h("div", { className: "dsm-main" }, h("div", { className: "dsm-name" }, skill.declaredName || skill.name), h("div", { className: "dsm-note" }, skill.description || t("note.missing")), skill.shadowedBy ? h("div", { className: "dsm-rule-hint" }, t("status.shadowed.hint", { name: skill.shadowedBy.name })) : null), h("div", { className: "dsm-tags" }, h("span", { className: "dsm-tag" }, rootDisplayName(t, root)), skill.preferred === true ? h("span", { className: "dsm-tag dsm-tag-on" }, t("status.preferred")) : null, !root.mutable ? h("span", { className: "dsm-tag" }, t("status.readonly")) : null, root.mutable && !root.deletable ? h("span", { className: "dsm-tag", title: t("status.notDeletable.hint") }, t("status.notDeletable")) : null), h("div", { className: "dsm-
|
|
7185
|
+
return h("div", { key: skill.name, className: "dsm-row", "data-flip-key": root.key + "/" + skill.name, "data-flip-on": enabled ? "1" : "0" }, h("div", { className: "dsm-main" }, h("div", { className: "dsm-name-row" }, h("div", { className: "dsm-name" }, skill.declaredName || skill.name), h(StatusTag, { tone: tone, text: t(key) })), h("div", { className: "dsm-note" }, skill.description || t("note.missing")), skill.shadowedBy ? h("div", { className: "dsm-rule-hint" }, t("status.shadowed.hint", { name: skill.shadowedBy.name })) : null), h("div", { className: "dsm-tags" }, h("span", { className: "dsm-tag" }, rootDisplayName(t, root)), skill.preferred === true ? h("span", { className: "dsm-tag dsm-tag-on" }, t("status.preferred")) : null, !root.mutable ? h("span", { className: "dsm-tag" }, t("status.readonly")) : null, root.mutable && !root.deletable ? h("span", { className: "dsm-tag", title: t("status.notDeletable.hint") }, t("status.notDeletable")) : null), h("div", { className: "dsm-row-actions" }, canToggle && skill.shadowedBy ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", disabled: busy, title: t("btn.activate.title"), onClick: function () { activateShadowed(root, skill); } }, t("btn.activate")) : null, canToggle && !skill.shadowedBy ? h(Switch, { on: enabled, disabled: busy || root.enabled === false || anyLocked, label: t("skill.toggle") + " " + skill.name, onClick: function () { post(enabled ? "/disable" : "/enable", { root: root.key, name: skill.name }); } }) : null, skill.preferred === true ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", disabled: busy, title: t("btn.unprefer.title"), onClick: function () { clearPreferred(root, skill); } }, t("btn.unprefer")) : null, h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", onClick: function () { openDetail(root, skill); } }, t("btn.detail")), root.deletable === true ? h("button", { type: "button", className: "dsm-btn dsm-btn-quiet", onClick: function () { setModal({ type: "trash-confirm", root: root.key, name: skill.name }); } }, t("btn.trash")) : null));
|
|
7075
7186
|
}
|
|
7076
7187
|
function renderRoot(root) {
|
|
7077
7188
|
if (activeSource && activeSource !== root.key) return null;
|
|
@@ -7102,7 +7213,7 @@ function enabledMemoryCount(rules) {
|
|
|
7102
7213
|
acts.push(h(Switch, { key: "sw", on: root.enabled, disabled: busy || anyLocked, label: t("source.toggle") + " " + displayName, onClick: function () { setExpanded(Object.assign({}, expanded, { [root.key]: true })); post(root.enabled ? "/source-disable" : "/source-enable", { root: root.key }).then(function () { dropUndoForRoot(root.key, displayName); }); } }));
|
|
7103
7214
|
}
|
|
7104
7215
|
return acts.length ? h("span", { className: "dsm-source-actions" }, acts) : null;
|
|
7105
|
-
})()), open ? h("div", { className: "dsm-source-body" }, filtered.length ? h(react.Fragment, null, h("div", { className: "dsm-table-head" }, h("span", null, t("table.skill")), h("span", null, t("filter.source")), h("span", null,
|
|
7216
|
+
})()), open ? h("div", { className: "dsm-source-body" }, filtered.length ? h(react.Fragment, null, h("div", { className: "dsm-table-head" }, h("span", null, t("table.skill")), h("span", null, t("filter.source")), h("span", null, "")), enabledFirst(filtered, function (skill) { return isSkillEnabled(skill); }).map(function (skill) { return renderSkill(root, skill); })) : h("div", { className: "dsm-empty" }, dq ? t("empty.search") : t("empty.source"))) : null);
|
|
7106
7217
|
// 目录那颗「全选」的撤销条贴在这张卡片上方:它只作用于这个目录,而页头那颗的撤销条
|
|
7107
7218
|
// 在搜索框下面(见 content)。撤销条跟着**触发它的按钮**走,别让人回头找。
|
|
7108
7219
|
return h(react.Fragment, { key: root.key },
|
|
@@ -9119,7 +9230,10 @@ function enabledMemoryCount(rules) {
|
|
|
9119
9230
|
placeholder: t('memory.scene.field.name.placeholder'),
|
|
9120
9231
|
onChange: function (e) { setSceneForm(Object.assign({}, sceneForm, { name: e.target.value, error: null })) },
|
|
9121
9232
|
}),
|
|
9122
|
-
|
|
9233
|
+
// 校验没过的那句红字是报错,不进条目清单;说明句一律带条目符号(与下面两个字段同款)。
|
|
9234
|
+
sceneForm.error
|
|
9235
|
+
? React.createElement('p', { className: 'dsm-rule-hint' }, sceneForm.error)
|
|
9236
|
+
: helpBullets(t, modal.type === 'scene-create' ? 'memory.scene.field.name.hint' : 'memory.scene.field.name.lock')),
|
|
9123
9237
|
React.createElement('label', { className: 'dsm-field' },
|
|
9124
9238
|
React.createElement('div', { className: 'dsm-budget-meta' },
|
|
9125
9239
|
React.createElement('span', { className: 'dsm-label' }, t('memory.scene.field.desc')),
|
|
@@ -9796,7 +9910,11 @@ function enabledMemoryCount(rules) {
|
|
|
9796
9910
|
React.createElement('div', { className: 'dsm-source-head-main dsm-persona-main' },
|
|
9797
9911
|
React.createElement('div', { className: 'dsm-persona-name-row' },
|
|
9798
9912
|
React.createElement('span', { className: 'dsm-source-title', title: p.name }, p.name),
|
|
9799
|
-
|
|
9913
|
+
// 两个状态都给胶囊:以前只在停用时挂标签,启用的一行什么都不留,
|
|
9914
|
+
// 用户没法一眼看出「没标签 = 启用」。
|
|
9915
|
+
p.enabled === false
|
|
9916
|
+
? React.createElement(StatusTag, { tone: 'muted', text: t('subagents.disabled'), title: t('subagents.disabled.hint') })
|
|
9917
|
+
: React.createElement(StatusTag, { tone: 'ok', text: t('subagents.enabled') })),
|
|
9800
9918
|
// 没有描述就不渲染这一行:空 div 仍占一行行高,行会变成一条无内容的空隙。
|
|
9801
9919
|
p.description ? React.createElement('div', { className: 'dsm-persona-desc', title: p.description }, p.description) : null),
|
|
9802
9920
|
React.createElement('div', { className: 'dsm-source-actions' },
|
|
@@ -10515,10 +10633,13 @@ function enabledMemoryCount(rules) {
|
|
|
10515
10633
|
// 单条停用(索引层开关)优先于场景状态:原来只有 已启用/被覆盖/场景未启用 三个分支,
|
|
10516
10634
|
// 单条停用的记忆会被误显示为「已启用」。
|
|
10517
10635
|
var statusKey = shadowed ? 'status.shadowed' : !enabled ? 'memory.status.off' : sceneActive === false ? 'memory.status.sceneOff' : 'status.enabled'
|
|
10518
|
-
|
|
10636
|
+
// 灰=不进上下文但不是故障(被遮蔽 / 本条停用 / 场景没启用),绿=这条真的会注入。
|
|
10637
|
+
var statusTone = shadowed || !enabled || sceneActive === false ? 'muted' : 'ok'
|
|
10519
10638
|
return React.createElement('div', { key: r.id, className: 'dsm-row', 'data-flip-key': r.id, 'data-flip-on': enabled ? '1' : '0' },
|
|
10520
10639
|
React.createElement('div', { className: 'dsm-main' },
|
|
10521
|
-
React.createElement('div', { className: 'dsm-name' },
|
|
10640
|
+
React.createElement('div', { className: 'dsm-name-row' },
|
|
10641
|
+
React.createElement('div', { className: 'dsm-name' }, r.name),
|
|
10642
|
+
React.createElement(StatusTag, { tone: statusTone, text: t(statusKey) })),
|
|
10522
10643
|
React.createElement('div', { className: 'dsm-note' }, r.description || ''),
|
|
10523
10644
|
shadowed ? React.createElement('div', { className: 'dsm-rule-shadow-hint' }, t('memory.shadowed.hint')) : null),
|
|
10524
10645
|
React.createElement('div', { className: 'dsm-tags' },
|
|
@@ -10530,7 +10651,6 @@ function enabledMemoryCount(rules) {
|
|
|
10530
10651
|
r.form === 'bundle' ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, t('memory.form.bundle')) : null,
|
|
10531
10652
|
attachTag(r, shadowed),
|
|
10532
10653
|
r.descriptionDerived ? React.createElement('span', { className: 'dsm-tag' }, t('memory.derived')) : null),
|
|
10533
|
-
React.createElement('div', { className: 'dsm-status ' + statusCls }, t(statusKey)),
|
|
10534
10654
|
React.createElement('div', { className: 'dsm-row-actions' },
|
|
10535
10655
|
React.createElement(Switch, { on: enabled, disabled: busy || shadowed || anyLocked, label: t('memory.enable') + ' ' + r.name, onClick: function () { toggleRule(r) } }),
|
|
10536
10656
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy || shadowed || anyLocked, onClick: function () { openEditor(r) } }, t('memory.edit')),
|
|
@@ -10573,7 +10693,6 @@ function enabledMemoryCount(rules) {
|
|
|
10573
10693
|
React.createElement('div', { className: 'dsm-table-head' },
|
|
10574
10694
|
React.createElement('span', null, t('memory.table.name')),
|
|
10575
10695
|
React.createElement('span', null, t('memory.table.tags')),
|
|
10576
|
-
React.createElement('span', null, t('memory.table.status')),
|
|
10577
10696
|
React.createElement('span', null, '')),
|
|
10578
10697
|
(rowsByBucket[name] || []).map(function (r) { return renderRuleRow(r, meta.active !== false) }))
|
|
10579
10698
|
: React.createElement('div', { className: 'dsm-empty' }, t('memory.scene.empty'))) : null)
|