dsh-plugin-tool-management 0.16.5 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +94 -0
- package/README.md +2 -1
- package/README_EN.md +2 -1
- package/docs/images/1-EN.png +0 -0
- package/docs/images/1.png +0 -0
- package/docs/images/2-EN.png +0 -0
- package/docs/images/2.png +0 -0
- package/docs/images/3-EN.png +0 -0
- package/docs/images/3.png +0 -0
- package/docs/images/4-EN.png +0 -0
- package/docs/images/4.png +0 -0
- package/docs/images/5-EN.png +0 -0
- package/docs/images/5.png +0 -0
- package/docs/images/6-EN.png +0 -0
- package/docs/images/6.png +0 -0
- package/docs/images/7-EN.png +0 -0
- package/docs/images/7.png +0 -0
- package/docs/images/8-EN.png +0 -0
- package/docs/images/8.png +0 -0
- package/docs/update.md +53 -0
- package/lib/audit-log.js +6 -2
- package/lib/client.js +44 -17
- package/lib/compat/probe.js +75 -24
- package/lib/context-inject.js +73 -7
- package/lib/host-names.js +150 -0
- package/lib/hub.js +82 -11
- package/lib/imports/parsers.js +40 -15
- package/lib/index.js +168 -74
- package/lib/mcp/loader-token.js +26 -0
- package/lib/mcp/manager.js +314 -87
- package/lib/mcp/patch-yaml.js +207 -40
- package/lib/mcp/secret-guard.js +122 -16
- package/lib/memories/archive-engine.js +21 -11
- package/lib/memories/index-io.js +125 -12
- package/lib/memories/service.js +163 -40
- package/lib/memories/snapshot.js +32 -9
- package/lib/op-registry.js +51 -8
- package/lib/ops/compat.js +6 -2
- package/lib/ops/prompts.js +18 -5
- package/lib/ops/scene-records.js +117 -10
- package/lib/ops/sessions.js +50 -4
- package/lib/ops/snapshot.js +105 -12
- package/lib/ops/trash.js +45 -9
- package/lib/prompts/service.js +7 -2
- package/lib/request-gate.js +115 -56
- package/lib/scene-prompt-sync.js +46 -7
- package/lib/scenes/candidates.js +21 -3
- package/lib/sessions/workspace.js +40 -5
- package/lib/skills/core.js +206 -43
- package/lib/skills/service.js +9 -4
- package/lib/subagents/service.js +6 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1714,6 +1714,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1714
1714
|
switch (row.detailKey) {
|
|
1715
1715
|
case 'skills-provider': return t('compat.note.skillsProvider', params)
|
|
1716
1716
|
case 'context-injection': return t('compat.note.contextInjection', params)
|
|
1717
|
+
case 'context-injection-runtime.domain': return t('compat.note.contextInjectionRuntime.domain', params)
|
|
1718
|
+
case 'context-injection-runtime.throw': return t('compat.note.contextInjectionRuntime.throw')
|
|
1717
1719
|
case 'cordis-original-symbol': return t('compat.note.cordisOriginalSymbol')
|
|
1718
1720
|
case 'patch-write-guard.no-dep': return t('compat.note.patchWriteGuard.noDep', params)
|
|
1719
1721
|
case 'patch-write-guard.stale': return t('compat.note.patchWriteGuard.stale', params)
|
|
@@ -1724,6 +1726,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
1724
1726
|
case 'mcp-tool-visibility.no-restrict': return t('compat.note.mcpToolVisibility.noRestrict')
|
|
1725
1727
|
case 'mcp-tool-visibility.partial': return t('compat.note.mcpToolVisibility.partial', params)
|
|
1726
1728
|
case 'workspace.delete-native': return t('compat.note.workspaceDeleteNative', params)
|
|
1729
|
+
case 'mcp-server-name-ambiguous.separator': return t('compat.note.mcpServerNameAmbiguous.separator', params)
|
|
1730
|
+
case 'mcp-server-name-ambiguous.prefix': return t('compat.note.mcpServerNameAmbiguous.prefix', params)
|
|
1727
1731
|
}
|
|
1728
1732
|
}
|
|
1729
1733
|
if (row.key === 'mount' && row.state === 'ok') return t('compat.ov.mount.ok', params)
|
|
@@ -2242,6 +2246,9 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2242
2246
|
if (row.state === 'cleared') return { cls: '', text: t('compat.live.state.cleared'), title: '' }
|
|
2243
2247
|
// 子会话不适用:与「已关闭 / 无内容」同档的灰 —— 它是设计选择,不是故障。
|
|
2244
2248
|
if (row.state === 'child') return { cls: '', text: t('compat.live.state.child'), title: t('compat.live.state.child.title') }
|
|
2249
|
+
// 取数抛异常:这是**故障**(该域内容还在,但这一步取不到),所以给琥珀 ——
|
|
2250
|
+
// 与「未投递」(压缩后待补发)不同档,混在一起会把故障读成延迟(2026-09-30 P1-8)。
|
|
2251
|
+
if (row.state === 'error') return { cls: ' dsm-compat-pill-warn', text: t('compat.live.state.error'), title: t('compat.live.state.error.title') }
|
|
2245
2252
|
return { cls: '', text: t('compat.live.state.unknown'), title: '' }
|
|
2246
2253
|
}
|
|
2247
2254
|
// 采纳一行:注入 N 次 / 调用 M 次 / 其中"调用时正文就在眼前"K 次。
|
|
@@ -2770,9 +2777,10 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
2770
2777
|
snapIn.result ? React.createElement('div', { className: 'dsm-feedback' + (snapIn.result.ok ? '' : ' dsm-error'), role: snapIn.result.ok ? 'status' : 'alert' },
|
|
2771
2778
|
React.createElement('div', null, snapIn.result.text),
|
|
2772
2779
|
(snapIn.result.report || []).map(function (row, i) {
|
|
2773
|
-
|
|
2780
|
+
var warn = (row.warnings || []).length ? ' ' + t('snapshot.import.reportWarning', { warnings: row.warnings.join(';') }) : ''
|
|
2781
|
+
return React.createElement('div', { key: i, className: 'dsm-help' + (warn ? ' dsm-warning' : '') }, (row.error
|
|
2774
2782
|
? t('snapshot.import.reportFailed', { domain: t('snapshot.domain.' + row.domain), error: row.error })
|
|
2775
|
-
: t('snapshot.import.report', { domain: t('snapshot.domain.' + row.domain), imported: row.imported, skipped: row.skipped }))
|
|
2783
|
+
: t('snapshot.import.report', { domain: t('snapshot.domain.' + row.domain), imported: row.imported, skipped: row.skipped })) + warn)
|
|
2776
2784
|
})) : null)
|
|
2777
2785
|
|
|
2778
2786
|
// ── 清理旧备份(兼容页,紧挨「刷新」)──────────────────────────────────────
|
|
@@ -3178,7 +3186,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3178
3186
|
"confirm.custom.remove.title": "移除自定义目录",
|
|
3179
3187
|
"confirm.custom.remove.desc": "移除后该目录的技能不再列出(文件不受影响):{name}",
|
|
3180
3188
|
"result.custom.added": "已添加自定义目录:{path}", "result.custom.removed": "已移除自定义目录:{name}",
|
|
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": "单文件",
|
|
3189
|
+
"status.enabled": "已启用", "status.disabled": "未启用", "status.invalid": "诊断异常", "status.shadowed": "被覆盖", "status.shadowed.hint": "同名技能「{name}」正在生效;点「启用这个」可改用当前来源的版本", "status.shadowed.inert": "同名技能「{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
3190
|
"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": "技能名称与描述",
|
|
3183
3191
|
"filter.source": "来源", "filter.all": "全部来源", "filter.option": "{name}({count})", "search": "搜索", "search.placeholder": "搜索技能名称或描述", "search.clear": "清除搜索",
|
|
3184
3192
|
"empty.search": "没有匹配的技能", "empty.source": "该来源不存在或暂无技能", "loading": "正在加载技能…", "note.missing": "未提供简介", "source.toggle": "启停来源", "skill.toggle": "启停技能",
|
|
@@ -3204,7 +3212,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3204
3212
|
"error.rules.reservedScene": "「{name}」是保留场景,不可{action}{reason}",
|
|
3205
3213
|
"error.rules.singleSceneOnly": "除「全局」外同时只能启用一个场景{detail}",
|
|
3206
3214
|
"error.agentsMd.sceneRebindFailed": "无法把场景「{scene}」的提示词绑定改成「{target}」:{reason}",
|
|
3207
|
-
"error.agentsMd.referenced": "「{id}」仍被引用,不能删除:{refs}。先改掉引用(换绑提示词 / 退出场景 / 应用别的预设)再删除。",
|
|
3215
|
+
"error.agentsMd.referenced": "「{id}」仍被引用,不能删除:{refs}。先改掉引用(换绑提示词 / 退出场景 / 应用别的预设)再删除。", "error.agentsMd.refProbeFailed": "无法确认「{id}」是否被引用,已拒绝删除:引用探测失败(场景绑定 / AGENTS.md 当前内容 / 进场景前的基线,这三类里至少有一类没读到)。请稍后重试。",
|
|
3208
3216
|
"error.source.reserved": "{origin}:不能停用或移除(里面的技能可以删除)",
|
|
3209
3217
|
"action.rename": "改名", "action.lock": "锁定",
|
|
3210
3218
|
"trash.title": "回收站", "trash.count.one": "{count} 个待处理技能", "trash.count.other": "{count} 个待处理技能", "trash.empty": "回收站为空", "trash.deletedAt": "删除于 {time}", "trash.source": "来源:{source}",
|
|
@@ -3329,7 +3337,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3329
3337
|
"snapshot.import.conflict.skip": "跳过重名", "snapshot.import.conflict.overwrite": "覆盖重名",
|
|
3330
3338
|
"snapshot.import.run": "执行导入", "snapshot.import.done": "导入完成", "snapshot.import.failed": "导入失败",
|
|
3331
3339
|
"snapshot.import.none": "没有勾选任何域", "snapshot.import.notFound": "这个快照里没有可导入的部分",
|
|
3332
|
-
"snapshot.import.report": "{domain}:成功 {imported} · 跳过 {skipped}", "snapshot.import.reportFailed": "{domain}:失败({error})",
|
|
3340
|
+
"snapshot.import.report": "{domain}:成功 {imported} · 跳过 {skipped}", "snapshot.import.reportFailed": "{domain}:失败({error})", "snapshot.import.reportWarning": "⚠ 需要注意:{warnings}",
|
|
3333
3341
|
"snapshot.import.locked": "{names} 已锁定:先解锁再导入。",
|
|
3334
3342
|
"snapshot.import.versionNote": "来自插件 {version}",
|
|
3335
3343
|
"snapshot.import.masked": "密钥已打码",
|
|
@@ -3408,6 +3416,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3408
3416
|
// 降级 note 的模板(detailKey 见 src/compat/runtime-notes.ts;占位符与英文版一一对应)。
|
|
3409
3417
|
"compat.note.skillsProvider": "技能 provider 装配失败({reason}):技能启用/停用与 agent 技能目录这一轮不生效。",
|
|
3410
3418
|
"compat.note.contextInjection": "注入通道装配失败({reason}):全部注入域的内容这一轮不会被送进模型。",
|
|
3419
|
+
"compat.note.contextInjectionRuntime.domain": "注入域「{domain}」这一轮取数失败:该域本轮没有发送任何内容,也不会误发一条「已清空」(保留上一轮内容不动),下一步会自动重试。",
|
|
3420
|
+
"compat.note.contextInjectionRuntime.throw": "本步注入抛异常,这一轮全部注入域的内容都没有送进模型。通道仍在,下一步会自动重试。",
|
|
3411
3421
|
"compat.note.cordisOriginalSymbol": "cordis 导出的 symbols.original 与 Symbol.for(\"cordis.original\") 不是同一个符号:能力探测可能把代理当原始对象,身份判定会失真。",
|
|
3412
3422
|
"compat.note.patchWriteGuard.noDep": "校验依赖不可用:{detail}(写入照常进行,仅少一道「把启动配置写坏」的拦截)",
|
|
3413
3423
|
"compat.note.patchWriteGuard.stale": "复刻可能已过期:{detail}(写入照常进行,仅少一道「把启动配置写坏」的拦截)",
|
|
@@ -3418,6 +3428,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3418
3428
|
"compat.note.mcpToolVisibility.noRestrict": "agent scope 上没有可用的 tools.restrict(官方接口变了或该 scope 未暴露 tools):停用的 MCP 工具与兼容页关掉的本插件工具仍会出现在模型可见的工具表里,执行侧拦截仍然生效。",
|
|
3419
3429
|
"compat.note.mcpToolVisibility.partial": "有工具没能从模型可见的工具表里摘掉({reason}):执行侧拦截仍然生效,模型仍能看到该工具的名字。",
|
|
3420
3430
|
"compat.note.workspaceDeleteNative": "宿主已提供原生 {entry}:本插件仍走自有完整序列(级联子会话 + spill + 记账 + 缓存行),尚未切换到原生入口。",
|
|
3431
|
+
"compat.note.mcpServerNameAmbiguous.separator": "这些 MCP 服务器名里含 __({names}):工具全名 mcp__<server>__<tool> 因此切不唯一,界面上的勾选可能落不到停用表里那条键上(工具本身照常可用)。改名可消除;不改也能用。",
|
|
3432
|
+
"compat.note.mcpServerNameAmbiguous.prefix": "这些 MCP 服务器名互为 __ 前缀关系({pairs}):例如 mcp__a__b__c 既可以读成 a 的 b__c,也可以读成 a__b 的 c,界面上的勾选可能落不到停用表里那条键上(工具本身照常可用)。改名可消除;不改也能用。",
|
|
3421
3433
|
// 路由缺失能力(refusal)的标签:id 对齐 src/compat/probe.ts 的 spec id 与 operation 兜底。
|
|
3422
3434
|
"compat.cap.workspaceReadState": "读取工作区状态", "compat.cap.workspaceReadTable": "读取工作区表", "compat.cap.workspaceIndexShape": "工作区索引结构",
|
|
3423
3435
|
"compat.cap.workspaceReadHeader": "读取会话头部", "compat.cap.workspaceEnqueue": "串行写事务", "compat.cap.workspaceSetState": "写工作区状态",
|
|
@@ -3561,6 +3573,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3561
3573
|
"compat.live.state.inContext": "已注入", "compat.live.state.absent": "未投递", "compat.live.state.cleared": "已清空",
|
|
3562
3574
|
"compat.live.state.off": "已关闭", "compat.live.state.unknown": "没有会话",
|
|
3563
3575
|
"compat.live.state.child": "不在本会话注入",
|
|
3576
|
+
"compat.live.state.error": "取数失败",
|
|
3577
|
+
"compat.live.state.error.title": "这个域这一轮取数抛了异常(内容还在,是这一步没取到)—— 兼容页「上下文注入通道」有一行降级写着原因。它不会误发「已清空」,下一步会自动重试。",
|
|
3564
3578
|
"compat.live.state.child.title": "本会话的深度超过了人设的目录注入深度(catalogDepth),所以常驻目录不在这里注入。要让目录出现在子会话,把对应人设的「目录注入」调大(或选「不限制嵌套」)。注意:这不影响委派 —— 子代理始终可以继续委派。",
|
|
3565
3579
|
"compat.live.adopt": "注入 {injected} 次 · 调用 {used} 次 · 采纳 {adopted} 次",
|
|
3566
3580
|
"compat.live.adopt.none": "注入 {injected} 次 · 从未调用",
|
|
@@ -3713,7 +3727,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3713
3727
|
"import.none": "没有导入任何文件(全部被跳过)", "import.skipped": "已跳过:{items}",
|
|
3714
3728
|
"error.import.noFiles": "没有选择要导入的文件",
|
|
3715
3729
|
"nav.title": "工具",
|
|
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": "未知错误",
|
|
3730
|
+
"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.agents.note.baselineLost": "⚠ 退出场景时将恢复不回原来的 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.import.lossy": "⚠ 整份文本没有识别出任何说话人标记,已折成一条用户消息:原对话的分轮结构可能已丢失。若源文件是 JSONL 或 Markdown 转录稿,请按原扩展名重新导入。", "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": "未知错误",
|
|
3717
3731
|
},
|
|
3718
3732
|
en: {
|
|
3719
3733
|
"title": "Skills", "desc": "Manage skills: enable, import, create, trash.", "link.project": "GitHub", "link.feedback": "Issues",
|
|
@@ -3745,7 +3759,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3745
3759
|
"result.custom.added": "Custom folder added: {path}",
|
|
3746
3760
|
"result.custom.removed": "Custom folder removed: {name}",
|
|
3747
3761
|
"result.opened": "Opened with the system default app: {path}",
|
|
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",
|
|
3762
|
+
"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.shadowed.inert": "The copy “{name}” is disabled, so this source's version is blocked without taking effect either — nothing is active; enable one of the two to fix it", "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",
|
|
3749
3763
|
"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",
|
|
3750
3764
|
"filter.source": "Source", "filter.all": "All sources", "filter.option": "{name} ({count})", "search": "Search", "search.placeholder": "Search skill names or descriptions", "search.clear": "Clear search",
|
|
3751
3765
|
"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",
|
|
@@ -3766,7 +3780,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3766
3780
|
"error.rules.reservedScene": "「{name}」 is a reserved scene: {action} is not allowed{reason}",
|
|
3767
3781
|
"error.rules.singleSceneOnly": "Only one scene besides 「global」 may be enabled at a time{detail}",
|
|
3768
3782
|
"error.agentsMd.sceneRebindFailed": "Cannot change the prompt binding of scene 「{scene}」 to 「{target}」: {reason}",
|
|
3769
|
-
"error.agentsMd.referenced": "「{id}」 is still referenced and cannot be deleted: {refs}. Remove those references first (rebind the prompt / leave the scene / apply another preset).",
|
|
3783
|
+
"error.agentsMd.referenced": "「{id}」 is still referenced and cannot be deleted: {refs}. Remove those references first (rebind the prompt / leave the scene / apply another preset).", "error.agentsMd.refProbeFailed": "Cannot tell whether “{id}” is still referenced, so the delete was refused: the reference probe failed (at least one of scene bindings / the current AGENTS.md content / the pre-scene baseline could not be read). Please try again later.",
|
|
3770
3784
|
"error.source.reserved": "{origin}: cannot be disabled or removed (the skills inside can still be deleted)",
|
|
3771
3785
|
"action.rename": "rename", "action.lock": "locking",
|
|
3772
3786
|
"trash.title": "Trash", "trash.count.one": "{count} skill pending", "trash.count.other": "{count} skills pending", "trash.empty": "Trash is empty", "trash.deletedAt": "Deleted {time}", "trash.source": "Source: {source}",
|
|
@@ -3891,7 +3905,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3891
3905
|
"snapshot.import.conflict.skip": "Skip duplicates", "snapshot.import.conflict.overwrite": "Overwrite duplicates",
|
|
3892
3906
|
"snapshot.import.run": "Run import", "snapshot.import.done": "Import finished", "snapshot.import.failed": "Import failed",
|
|
3893
3907
|
"snapshot.import.none": "No area selected", "snapshot.import.notFound": "Nothing importable in this snapshot",
|
|
3894
|
-
"snapshot.import.report": "{domain}: {imported} in · {skipped} skipped", "snapshot.import.reportFailed": "{domain}: failed ({error})",
|
|
3908
|
+
"snapshot.import.report": "{domain}: {imported} in · {skipped} skipped", "snapshot.import.reportFailed": "{domain}: failed ({error})", "snapshot.import.reportWarning": "⚠ Needs attention: {warnings}",
|
|
3895
3909
|
"snapshot.import.locked": "{names} is locked — unlock it before importing.",
|
|
3896
3910
|
"snapshot.import.versionNote": "Made by plugin {version}",
|
|
3897
3911
|
"snapshot.import.masked": "secrets masked",
|
|
@@ -3970,6 +3984,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3970
3984
|
// Templates for degraded runtime notes (detailKey per src/compat/runtime-notes.ts; placeholders mirror the zh side one for one).
|
|
3971
3985
|
"compat.note.skillsProvider": "Skills provider setup failed ({reason}): skill enable/disable and the agent skill catalog will not take effect this round.",
|
|
3972
3986
|
"compat.note.contextInjection": "Injection channel setup failed ({reason}): no injection domain's content will reach the model this round.",
|
|
3987
|
+
"compat.note.contextInjectionRuntime.domain": "Injection domain \"{domain}\" failed to produce its text this round: nothing was sent for it, no false \"cleared\" notice was emitted (the previous content is left in place), and the next step retries automatically.",
|
|
3988
|
+
"compat.note.contextInjectionRuntime.throw": "Injection threw this step: no injection domain's content reached the model this round. The channel is still mounted and the next step retries automatically.",
|
|
3973
3989
|
"compat.note.cordisOriginalSymbol": "The exported symbols.original is not the same symbol as Symbol.for(\"cordis.original\"): capability probing may treat proxies as raw objects, so identity checks can be skewed.",
|
|
3974
3990
|
"compat.note.patchWriteGuard.noDep": "Validation dependency unavailable: {detail} (the write proceeds; only the guard against corrupting the boot config is missing)",
|
|
3975
3991
|
"compat.note.patchWriteGuard.stale": "The replica may be outdated: {detail} (the write proceeds; only the guard against corrupting the boot config is missing)",
|
|
@@ -3980,6 +3996,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
3980
3996
|
"compat.note.mcpToolVisibility.noRestrict": "No tools.restrict is available on the agent scope (the official interface changed, or the scope does not expose tools): disabled MCP tools and plugin tools turned off on the compat page still appear in the model-visible tool table; call-side interception still applies.",
|
|
3981
3997
|
"compat.note.mcpToolVisibility.partial": "Some tools could not be removed from the model-visible tool table ({reason}): call-side interception still applies, but the model still sees those tool names.",
|
|
3982
3998
|
"compat.note.workspaceDeleteNative": "The host now offers native {entry}: this plugin still uses its own full sequence (cascade sub-sessions + spill + bookkeeping + cache rows) and has not switched to the native entry.",
|
|
3999
|
+
"compat.note.mcpServerNameAmbiguous.separator": "These MCP server names contain __ ({names}): the tool full name mcp__<server>__<tool> is therefore not uniquely splittable, so a checkbox in the UI may not land on the key used in the disabled-tools table (the tool itself still works). Renaming removes the ambiguity; leaving it as is still works.",
|
|
4000
|
+
"compat.note.mcpServerNameAmbiguous.prefix": "These MCP server names are __-prefix related ({pairs}): for example mcp__a__b__c can be read as a's b__c or as a__b's c, so a checkbox in the UI may not land on the key used in the disabled-tools table (the tool itself still works). Renaming removes the ambiguity; leaving it as is still works.",
|
|
3983
4001
|
// Labels for route refusals: ids mirror the spec ids in src/compat/probe.ts plus the operation fallback.
|
|
3984
4002
|
"compat.cap.workspaceReadState": "Read workspace state", "compat.cap.workspaceReadTable": "Read the workspace table", "compat.cap.workspaceIndexShape": "Workspace index shape",
|
|
3985
4003
|
"compat.cap.workspaceReadHeader": "Read session header", "compat.cap.workspaceEnqueue": "Serialized write transaction", "compat.cap.workspaceSetState": "Write workspace state",
|
|
@@ -4119,6 +4137,8 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4119
4137
|
"compat.live.state.inContext": "Injected", "compat.live.state.absent": "Not delivered", "compat.live.state.cleared": "Cleared",
|
|
4120
4138
|
"compat.live.state.off": "Off", "compat.live.state.unknown": "No session",
|
|
4121
4139
|
"compat.live.state.child": "Not injected here",
|
|
4140
|
+
"compat.live.state.error": "Failed to load",
|
|
4141
|
+
"compat.live.state.error.title": "This domain threw while producing its text this round (the content exists; this step just could not read it). The Compat page's \"Context injection channel\" row carries a degraded note with the reason. No false \"cleared\" notice is sent, and the next step retries automatically.",
|
|
4122
4142
|
"compat.live.state.child.title": "This session is deeper than the personas' catalog injection depth (catalogDepth), so the standing catalog is not injected here. To let the catalog reach subagent sessions, raise the persona's catalog injection depth (or pick “No nesting limit”). Note: this does not affect delegation — subagents can always delegate further.",
|
|
4123
4143
|
"compat.live.adopt": "injected {injected} · called {used} · adopted {adopted}",
|
|
4124
4144
|
"compat.live.adopt.none": "injected {injected} · never called",
|
|
@@ -4270,7 +4290,7 @@ html,body{scrollbar-gutter:stable}.dsm-settings-scroll-host{overflow-y:scroll!im
|
|
|
4270
4290
|
"import.none": "Nothing was imported (all skipped)", "import.skipped": "Skipped: {items}",
|
|
4271
4291
|
"error.import.noFiles": "No files selected",
|
|
4272
4292
|
"nav.title": "Tools",
|
|
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",
|
|
4293
|
+
"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.agents.note.baselineLost": "⚠ Leaving the scene will not restore the previous AGENTS.md: {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.import.lossy": "⚠ No speaker markers were found in the text, so it was folded into a single user message: the original turn structure may have been lost. If the source is a JSONL or Markdown transcript, import it with its original extension.", "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",
|
|
4274
4294
|
}
|
|
4275
4295
|
};
|
|
4276
4296
|
|
|
@@ -7182,7 +7202,10 @@ function enabledMemoryCount(rules) {
|
|
|
7182
7202
|
// 胶囊配色:橙=诊断异常(要人去看),灰=没生效但不是故障(被同名覆盖、本条停用),绿=生效中。
|
|
7183
7203
|
var tone = skill.shadowedBy ? "muted" : skill.loadable === false ? "warn" : enabled ? "ok" : "muted";
|
|
7184
7204
|
var canToggle = root.toggleable !== false && skill.loadable !== false;
|
|
7185
|
-
|
|
7205
|
+
// 被覆盖 ≠ 另一份在跑:赢家自己被停用时两份都不生效,这时必须换一句实话,
|
|
7206
|
+
// 否则界面会一边把这一行标成「被覆盖」、一边说「同名技能 X 正在生效」(审查 F8)。
|
|
7207
|
+
var shadowedHint = skill.shadowedBy ? t(skill.shadowedBy.enabled === false ? "status.shadowed.inert" : "status.shadowed.hint", { name: skill.shadowedBy.name }) : null;
|
|
7208
|
+
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")), shadowedHint ? h("div", { className: "dsm-rule-hint" }, shadowedHint) : 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));
|
|
7186
7209
|
}
|
|
7187
7210
|
function renderRoot(root) {
|
|
7188
7211
|
if (activeSource && activeSource !== root.key) return null;
|
|
@@ -7914,7 +7937,7 @@ function enabledMemoryCount(rules) {
|
|
|
7914
7937
|
accept: '.jsonl,.json,.md,.markdown,.txt',
|
|
7915
7938
|
busy: imp.busy === true,
|
|
7916
7939
|
requirements: [t('upload.requirement.session.1'), t('upload.requirement.session.2'), t('upload.requirement.session.3')],
|
|
7917
|
-
result: imp.error ? { ok: false, text: String(imp.error) } : (imp.result ? { ok: true, text: t('hist.import.done', { id: imp.result.sessionId, count: imp.result.count }) } : null),
|
|
7940
|
+
result: imp.error ? { ok: false, text: String(imp.error) } : (imp.result ? { ok: true, warning: imp.result.lossy === true, text: t('hist.import.done', { id: imp.result.sessionId, count: imp.result.count }) + (imp.result.lossy === true ? ' ' + t('hist.import.lossy') : '') } : null),
|
|
7918
7941
|
extra: React.createElement('div', null,
|
|
7919
7942
|
React.createElement('div', { className: 'dsm-field' },
|
|
7920
7943
|
React.createElement('label', { className: 'dsm-label' }, t('hist.import.cwd')),
|
|
@@ -8200,10 +8223,14 @@ function enabledMemoryCount(rules) {
|
|
|
8200
8223
|
function agentsMdNote(res) {
|
|
8201
8224
|
var sync = res && res.agentsMd
|
|
8202
8225
|
if (!sync) return ''
|
|
8203
|
-
|
|
8204
|
-
if (sync.
|
|
8205
|
-
if (sync.
|
|
8206
|
-
|
|
8226
|
+
var note = ''
|
|
8227
|
+
if (sync.error) note += ' · ' + t('scenes.agents.note.error', { reason: String(sync.error) })
|
|
8228
|
+
else if (sync.applied) note += ' · ' + t('scenes.agents.note.applied', { id: String(sync.applied) })
|
|
8229
|
+
else if (sync.restored) note += ' · ' + t('scenes.agents.note.restored')
|
|
8230
|
+
// 「基线快照没能写入」与「AGENTS.md 有没有写」是两件事:前者是**切进去了但退不回来**,
|
|
8231
|
+
// 必须单独说,否则用户以为还能退出场景恢复原样(审查 F14)。
|
|
8232
|
+
if (sync.baselineError) note += ' · ' + t('scenes.agents.note.baselineLost', { reason: String(sync.baselineError) })
|
|
8233
|
+
return note
|
|
8207
8234
|
}
|
|
8208
8235
|
// ── 场景启用:**除「全局」外同时只能启用一个**(用户裁定)──
|
|
8209
8236
|
// 空数组 = 全部关闭(只留恒常的 `_shared` 与 `global`);服务端也会拒绝多个。
|
|
@@ -8212,7 +8239,7 @@ function enabledMemoryCount(rules) {
|
|
|
8212
8239
|
setBusy(true); setResult(null)
|
|
8213
8240
|
apiCall('rules-set-active', { scenes: names }).then(function (res) {
|
|
8214
8241
|
setBusy(false)
|
|
8215
|
-
if (res && res.ok) { setResult({ ok: !(res.agentsMd && res.agentsMd.error), text: t('memory.result.active') + agentsMdNote(res) }); refresh(true) }
|
|
8242
|
+
if (res && res.ok) { setResult({ ok: !(res.agentsMd && (res.agentsMd.error || res.agentsMd.baselineError)), text: t('memory.result.active') + agentsMdNote(res) }); refresh(true) }
|
|
8216
8243
|
else setResult({ ok: false, text: translateError(t, res) })
|
|
8217
8244
|
}).catch(function (e) { setBusy(false); setResult({ ok: false, text: errMsg(e) }) })
|
|
8218
8245
|
}
|
package/lib/compat/probe.js
CHANGED
|
@@ -55,15 +55,32 @@ export function isSourceInstall() {
|
|
|
55
55
|
return SOURCE_INSTALL;
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
|
-
* peer range
|
|
59
|
-
* 预发布规则(prerelease 版本只匹配同 [major,minor,patch] 元组的比较器)意味着**每一代
|
|
60
|
-
* rc 都要显式列进范围**:`>=0.1.5-rc.2` 匹配不了 `0.1.7-rc.2`(实测),所以范围是逐代
|
|
61
|
-
* 枚举的并集。上界依旧不设:宿主跨代升级由运行时能力探测兜底(见下)。
|
|
58
|
+
* peer range 与已验证版本。
|
|
62
59
|
*
|
|
63
|
-
*
|
|
60
|
+
* 官方全程走 prerelease 渠道(x.y.z-rc.N),而 semver 有一条硬规则:**带 prerelease 标签的
|
|
61
|
+
* 版本,只有在"同 [major,minor,patch] 元组"的比较器也带 prerelease 标签时才能匹配**。所以
|
|
62
|
+
* `>=0.1.5-rc.2` 匹配不了 `0.1.7-rc.2`,`^0.2.0-rc.2` 也匹配不了 `0.2.1-rc.1`(实测:
|
|
63
|
+
* `0.1.8-rc.1` / `0.2.1-rc.1` / `0.3.0-rc.1` 全部 false)—— 范围只能**逐代枚举并集**,
|
|
64
|
+
* 每适配一代新 rc 就补一段(见 README 的升级清单)。
|
|
65
|
+
*
|
|
66
|
+
* 上界刻意不设:宿主跨代升级由**运行时能力探测**兜底(`assessHost` 逐项探测并降级),
|
|
67
|
+
* 而不是在安装期拒绝整包。所以这里用不带上界的 `>=`,**不用** `^`(`^0.2.0` 的隐含上界
|
|
68
|
+
* `<0.3.0` 会在宿主发稳定版 0.3.0 时把插件挡在门外 —— 与"不设上界"自相矛盾)。
|
|
69
|
+
*
|
|
70
|
+
* 2026-09-30 审查 §5 F9 + §5.4:这一段此前与 `package.json` 的 peerDependencies **漂移**成
|
|
71
|
+
* 两种写法(这里是 `^` 三段、那里是 `>=` 两段,且下界不同)。现在两处是**同一个字符串**,
|
|
72
|
+
* 并由 `test/contracts.test.mjs` 的一条断言钉住 —— 漂移会让"界面/doctor 报的范围"与
|
|
73
|
+
* "安装期实际校验的范围"各说各话,而两者都自称是插件的兼容范围。
|
|
74
|
+
*
|
|
75
|
+
* 已知且**无法**用 range 消除的缺口(如实标注):尚未枚举的新 rc 元组(如 0.2.1-rc.1)
|
|
76
|
+
* 在 npm7+ 下会触发 ERESOLVE 告警。这不是写法问题 —— semver 规则决定了不存在能匹配
|
|
77
|
+
* "任意未来 prerelease"的 range。缓解方式:README 写明宿主升级时同步这段范围,
|
|
78
|
+
* 以及 `--legacy-peer-deps` 这条逃生门。
|
|
79
|
+
*
|
|
80
|
+
* 0.2.0-rc.2(桌面版首发)已实测通过:`assessHost` 全通过,宿主版本读得出来,
|
|
64
81
|
* 归档宿主的身份判定见 `hostPackageRoot` / `isAsarPath`。所以它同时进范围与「已验证」。
|
|
65
82
|
*/
|
|
66
|
-
export const EXPECTED_PEER_RANGE = '
|
|
83
|
+
export const EXPECTED_PEER_RANGE = '>=0.1.7-rc.2 || >=0.2.0-rc.1';
|
|
67
84
|
/** The release this plugin's adapters were last verified against. */
|
|
68
85
|
export const VERIFIED_HOST_VERSION = '0.2.0-rc.2';
|
|
69
86
|
/**
|
|
@@ -591,6 +608,27 @@ const CAPABILITY_SPECS = [
|
|
|
591
608
|
}
|
|
592
609
|
},
|
|
593
610
|
},
|
|
611
|
+
// ---- ctx.fs:MCP 域补丁写路径的唯一出口(审查 §5 F5)----------------------
|
|
612
|
+
// 为什么值得单列:补丁文件在插件自己的数据目录**之外**(`~/.dsh/cordis.patch.yml` 与
|
|
613
|
+
// `profiles/<名>/cordis.patch.yml`),读写都得经 `ctx.fs` 显式升级沙箱策略。五个方法里
|
|
614
|
+
// 任何一个改名,19 处补丁写路径会**一起**报错 —— 报错是可见的(不是静默),但要等到用户
|
|
615
|
+
// 点下去才发现;钉在兼容页上就能在升级后第一眼看见。
|
|
616
|
+
//
|
|
617
|
+
// 能探到 / 探不到(如实标注):成员存在性可探;`writeText` 的**实参个数**可观测(健康行
|
|
618
|
+
// 附注)。「五个位置参数的语义变了」**探不到** —— 真调它会写文件(有副作用),而本探针
|
|
619
|
+
// 的纪律是零副作用(见 callableWithSentinel 的副作用口径)。那半条只能靠宿主升级清单人工核对。
|
|
620
|
+
{
|
|
621
|
+
id: 'fs.patch-io',
|
|
622
|
+
label: '宿主文件读写(补丁文件出口)',
|
|
623
|
+
kind: 'write',
|
|
624
|
+
owner: 'fs',
|
|
625
|
+
fallback: 'refuse-operation',
|
|
626
|
+
methods: ['resolve', 'stat', 'readText', 'writeText', 'listDir'],
|
|
627
|
+
describe: (t) => {
|
|
628
|
+
const w = t.writeText;
|
|
629
|
+
return typeof w === 'function' ? `观测 writeText 实参个数=${w.length}` : undefined;
|
|
630
|
+
},
|
|
631
|
+
},
|
|
594
632
|
// ---- agent preset roster(0.1.7 的两处适配面之二)-------------------------
|
|
595
633
|
// 0.1.7 把 `read(id)`(直返组合文本)改名成 `readDocument(id)`(返回文档对象,组合
|
|
596
634
|
// YAML 在 `.content`)。读取口是「read 优先、readDocument 兜底」双入口
|
|
@@ -830,6 +868,8 @@ export function assessHost(ctx) {
|
|
|
830
868
|
// ('agentPresets' 与 preset-reach.ts 的 presetRosterOf 同名 —— 注入矩阵实际用的就是它)。
|
|
831
869
|
const settings = unwrap(ctx.settings !== undefined ? ctx.settings : get('settings'));
|
|
832
870
|
const roster = unwrap(get('agentPresets'));
|
|
871
|
+
// fs 走 ctx 属性优先(它在插件 inject 清单里),与 settings 同一条取法。
|
|
872
|
+
const fsService = unwrap(ctx.fs !== undefined ? ctx.fs : get('fs'));
|
|
833
873
|
const references = referencePrototypes();
|
|
834
874
|
const targets = {
|
|
835
875
|
workspace: { target: registry, reference: references.workspace },
|
|
@@ -838,6 +878,7 @@ export function assessHost(ctx) {
|
|
|
838
878
|
persistence: { target: unwrap(get('sessionPersistence')), reference: undefined },
|
|
839
879
|
settings: { target: settings, reference: references.settings },
|
|
840
880
|
presetRoster: { target: roster, reference: references.roster },
|
|
881
|
+
fs: { target: fsService, reference: undefined },
|
|
841
882
|
};
|
|
842
883
|
const findings = CAPABILITY_SPECS.map((spec) => {
|
|
843
884
|
const slot = targets[spec.owner];
|
|
@@ -991,20 +1032,38 @@ export function assessHost(ctx) {
|
|
|
991
1032
|
* 调用方:从锚点解析不到的包会被判成 `same = null`,见 assessHost 里的 `unverified`。
|
|
992
1033
|
*/
|
|
993
1034
|
function hostPackageRoot() {
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1035
|
+
for (const candidate of hostRootCandidates()) {
|
|
1036
|
+
if (existsSync(join(candidate, 'dsh', 'package.json')))
|
|
1037
|
+
return candidate;
|
|
1038
|
+
}
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* 宿主 `@deepseek-ai` 目录的**候选序列**(按优先级)—— 全仓"宿主在哪"的唯一口径。
|
|
1043
|
+
*
|
|
1044
|
+
* 为什么单独导出(2026-09-30 审查 §5 F10):运行时(本文件)与 `scripts/doctor.mjs` /
|
|
1045
|
+
* `scripts/host-deps.mjs` 此前**各写一份**,而且顺序不一样 —— doctor 少了桌面版那一条,
|
|
1046
|
+
* 于是在桌面宿主上要么报 "host installation not found",要么拿 npx 缓存里**另一代宿主**
|
|
1047
|
+
* 当锚点比出假 SEPARATE COPY。那正是本文件注释里记过的实测事故,只修了运行时。
|
|
1048
|
+
*
|
|
1049
|
+
* 顺序是有讲究的:
|
|
1050
|
+
* ① 桌面版归档(`resources/app.asar`)—— 必须排在 npx 缓存**之前**,否则装过 web 版的
|
|
1051
|
+
* 机器上会先命中缓存里那个 0.1.7;
|
|
1052
|
+
* ② `$DSH_HOME/profiles/node_modules/@deepseek-ai`(web 版旧布局的 junction 树);
|
|
1053
|
+
* ③ 从本插件自身解析到的锚点逐级上溯(dev 布局:仓库/安装目录自己的 node_modules);
|
|
1054
|
+
* ④ npx 缓存(`dsh web` 经 npx 跑时的落点;多命中取最新)。
|
|
1055
|
+
*
|
|
1056
|
+
* 只列候选、不判存在性:调用方各有自己的"像不像一份安装"的判据(运行时要求
|
|
1057
|
+
* `dsh/package.json`,host-deps 还要读出版本号)。
|
|
1058
|
+
*/
|
|
1059
|
+
export function hostRootCandidates() {
|
|
998
1060
|
const home = process.env.DSH_HOME || join(process.env.USERPROFILE || process.env.HOME || '', '.dsh');
|
|
999
1061
|
const candidates = [];
|
|
1000
|
-
if (home !== '')
|
|
1001
|
-
candidates.push(join(home, 'profiles', 'node_modules', '@deepseek-ai'));
|
|
1002
|
-
// 桌面版:整个 DSH 装进 `resources/app.asar`,宿主的 `@deepseek-ai/*` 就在归档里。
|
|
1003
|
-
// 这一候选必须排在 npx 缓存**之前** —— 否则装过 web 版的机器上会先命中 npx 缓存里
|
|
1004
|
-
// 那个 0.1.7,于是拿「另一个安装」当锚点比出 6 条假的「两份不同拷贝」(实测事故)。
|
|
1005
1062
|
const asarHost = asarHostPackageRoot();
|
|
1006
1063
|
if (asarHost !== null)
|
|
1007
1064
|
candidates.push(asarHost);
|
|
1065
|
+
if (home !== '')
|
|
1066
|
+
candidates.push(join(home, 'profiles', 'node_modules', '@deepseek-ai'));
|
|
1008
1067
|
for (const anchor of IDENTITY_PACKAGES) {
|
|
1009
1068
|
// 锚点候选只认**真实形态**:取不到就跳过(这里没有可比的对象,如实跳过即可,
|
|
1010
1069
|
// 与身份比对不同 —— 那边取不到必须报出来,见 assessHost 的 realpath 说明)。
|
|
@@ -1024,16 +1083,8 @@ function hostPackageRoot() {
|
|
|
1024
1083
|
dir = parent;
|
|
1025
1084
|
}
|
|
1026
1085
|
}
|
|
1027
|
-
// 0.1.7 起宿主不再维护 `profiles/node_modules` 那棵 junction 树(link-backend 已移除),
|
|
1028
|
-
// `dsh web` 经 npx 跑在 `<npm 缓存>/_npx/<hash>/node_modules` 里 —— 缓存扫描是 dev 场景
|
|
1029
|
-
// (上溯只找到 checkout 自己的副本)下最后的宿主发现手段。多命中时取最新(npx 升级会
|
|
1030
|
-
// 换 hash 目录、删旧目录)。
|
|
1031
1086
|
candidates.push(...npxCacheHostRoots());
|
|
1032
|
-
|
|
1033
|
-
if (existsSync(join(candidate, 'dsh', 'package.json')))
|
|
1034
|
-
return candidate;
|
|
1035
|
-
}
|
|
1036
|
-
return null;
|
|
1087
|
+
return candidates;
|
|
1037
1088
|
}
|
|
1038
1089
|
/**
|
|
1039
1090
|
* 正在运行的桌面版 DSH 的 `@deepseek-ai` 目录(在 `resources/app.asar` 里),没有则 null。
|