Repository navigation
Replies: 6 comments
|
补充一个可复跑的最小验证脚本,用来说明"手工 boot 的等价树不复现"以及 同一台机器、同一份打包运行时( // probe.mjs —— 用打包运行时手工 boot 一个等价树,并直接调用 reconcileProfilePatches
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
const APP = 'D:\\DeepSeek Harness\\resources\\app.asar' // 按实际安装路径修改
const RUNTIME = `${APP}\\dsh`
const base = `file:///${APP.replace(/\\/g, '/')}/dsh/node_modules/@deepseek-ai`
const ab = await import(`${base}/dsh-app-boot/lib/index.js`)
const home = mkdtempSync(join(tmpdir(), 'dsh-probe-'))
process.env.DSH_HOME = home
const dir = join(home, 'profiles', 'probe') // 必须位于 profiles 树下,才会命中 runtime interception
mkdirSync(dir, { recursive: true })
writeFileSync(join(dir, 'cordis.yml'), '[]\n')
writeFileSync(join(dir, 'cordis.patch.yml'), '[]\n')
writeFileSync(join(dir, 'package.json'), JSON.stringify({ name: 'dsh-profile-probe', private: true, dsh: { profile: { bundles: [] } } }))
const installAnchor = join(RUNTIME, 'package.json')
const resolution = await ab.createRuntimeResolution({ installAnchor, home })
const profileContext = {
name: 'probe', dir, patchPath: join(dir, 'cordis.patch.yml'), installAnchor,
startedBundles: [], cwd: dir, home, overlays: [], telemetryDisabledEnv: undefined,
}
const ctx = await ab.boot('dsh', join(dir, 'cordis.yml'), [{
insert: [
{ id: 'llm', name: '@deepseek-ai/dsh-llm' },
{ id: 'llm-pi-ai', name: '@deepseek-ai/dsh-llm-pi-ai', config: { providers: { openrouter: { apiKeyEnv: 'OPENROUTER_API_KEY' } } } },
],
}], async (hostCtx) => {
hostCtx.provide('profileContext', profileContext)
await hostCtx.plugin(ab.PluginPackages, { resolution })
})
console.log('ctx.root === ctx ?', ctx.root === ctx) // true
console.log('reconcile:', (await ab.reconcileProfilePatches(ctx, [], 'dsh')).length, 'warnings') // 0 warnings,正常返回
await ctx.fiber.dispose()观察结果:
|
补充:影响面比首帖更大 —— 所有设置页都无法保存,不只是模型页现场追加:设置 → 模型 之外的其他设置页(通用、账号、Subagent、Shell、Web 搜索、Agent 循环、会话日志、插件等)保存同样失败,报错文本完全一致。 代码链路(0.2.0-rc.2 打包运行时):只有一个设置后端,且唯一的写入路径无条件经过 1. Client 侧:所有设置页共用同一个写入面
const response = await this.ctx.remote.settings.mutate(this.spec.namespace, ownedOps, revision)使用它的客户端插件包括: 2. Host 侧: async write(ns, change, expected, paths = []) {
const entry = this.ownerContext.configEditor.entries().find((row) => row.options.id === ns);
...
await this.ownerContext.configEditor.edit(entry, (raw, inherited) => { ... }); // ← 进入 reconcileProfilePatches()
}
3. 不存在"换个后端就能保存"的旁路
结论:任何走 settings 命名空间表单的写入都必然抛 一个加重感知的细节:
|
更正 + 两条排除结果(均为本机实测)更正:这条缺陷是"被事件触发",不是"装上 rc.2 就必然如此"首帖我写的是"每次保存必失败、与状态无关"。补充事实:报障用户反馈今天早些时候设置还能正常保存并生效,之后才开始失败。所以它更像是由某个事件触发的状态问题,而不是该版本自带的恒定行为。请据此调整判断。 排除 1:路径残留与权限(不是环境脏)
排除 2:第三方/实验组合包与 Plugin Manager 安装的插件(在最小组合包 + 全新进程下仍复现)
附带确认:profile 补丁文件的热重载路径同样是坏的
小结第三方/实验组合包、Agent 经 Plugin Manager 安装的插件、路径残留、文件权限 —— 都已排除。缺陷位于打包运行时自身的启动/解析路径,并同时影响三条路径:设置保存( 最小组合下仍复现,也使"外部 profile 内容"这一整类解释可以搁置: 仍然建议按首帖那段在抛错前加一行诊断: if (entry === undefined) {
ctx.logger?.error?.("app-boot: bootstrapIncludes miss", {
has: bootstrapIncludes.has(ctx), // 本版本里 false 是唯一可能(EntryTree.resolve 是 throw)
ctxIsRoot: ctx === ctx.root,
moduleUrl: import.meta.url, // 与 boot 侧对比即可确认是否两份模块实例
loaderPresent: ctx.get("loader") !== undefined,
});
throw new Error(`${binName}: profile reload requires the root Include entry`);
} |
最终排除:profile 层面彻底结案在上一轮"最小组合包"的基础上,再把今天经 Plugin Manager 安装的那个插件的残留一并摘掉:
实验成立性核对(这一步很重要,否则结论无效):
此时 profile 的组成已经退到最干净的可运行形态: "dependencies": { "dsh-whale-widget": "0.3.17" },
"dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"] } }即:无第三方/实验组合包、无 Plugin Manager 安装的插件、无 linked root、无插件依赖声明。 结果:设置保存仍然失败,报错文本完全相同。 至此完整排除清单
结论缺陷稳定存在于打包运行时自身(0.2.0-rc.2,Electron/asar 环境),并同时使三条路径失效:设置保存( 现场:一张几乎全清空的 profile + 全新进程,第一次调用 仍然建议按首帖那段在抛错前加一行诊断( |
|
补一条与修复优先级有关的事实(本机实测): 桌面端更新源( version: 0.2.0-rc.2
releaseDate: '2026-09-29T10:35:27.666Z'
path: .../deepseek-harness-0.2.0-rc.2-win-x64.exe即报障用户正在运行的正是 feed 上的最新桌面版;同一目录下 因此用户无法通过升级规避这个缺陷(设置页、补丁热重载、插件管理器 apply 三条路径全断),只能等包含修复的新构建。 |
已解决:重装同一版本后恢复正常 —— 撤回"打包运行时缺陷"的结论结论先行:本缺陷不是可复现的产品缺陷,现场是本机安装被改坏;重装同一版本后自動恢复。首帖及后续评论里"缺陷稳定存在于打包运行时自身"的判断,请作废。 关键事实(均为本机实测)
对维护者仍值得保留的一点(与根因无关,但是设计上的自隐)
建议:键缺失时在启动阶段显式报错/自检,而不是静默退化——否则一次安装损伤会伪装成一个"设置页坏了"的疑难杂症。 处置建议
|
Uh oh!
There was an error while loading. Please reload this page.
[Bug][Windows][Desktop 0.2.0-rc.2] 设置页保存任一供应商配置必失败:
dsh: profile reload requires the root Include entrySummary
桌面版 0.2.0-rc.2(Windows,Electron 打包)中,设置 → 模型 保存任何供应商配置都会立即失败并报:
原因是
dsh-config-editor的ConfigEditor.edit()在写入cordis.patch.yml之前就先调用reconcileProfilePatches(),而该函数在本进程里恒定抛错。因此这条路径对所有 profile 配置写入都是硬失败:用户无法通过 UI 修改任何供应商 / 模型 / 插件配置。同一函数还有两个调用点,受同一缺陷影响:
dsh-config-editoredit()内dsh-hmrrefresh()cordis.patch.yml热重载失效dsh-plugin-managerreload()Reproduction
复现率 100%(每次保存必失败),与选择哪个供应商、哪个字段无关。
Current behavior
报错点位于
@deepseek-ai/dsh-app-boot@0.2.0-rc.2(打包路径resources/app.asar/dsh/node_modules/@deepseek-ai/dsh-app-boot/lib/index.js):bootstrapIncludes的唯一写入点是mountRootInclude(),而mountRootInclude()的唯一调用点是boot():调用方传入的是
this.ownerContext.root(dsh-config-editor/dsh-hmr/dsh-plugin-manager三处一致)。已经排除的两条路径:
.root恒等性不是问题。 同一运行时手工boot()一个等价树后实测ctx.root === ctx为true(Context构造器里this.root = self,extend()用Object.create继承),因此子上下文的.root与 boot 的根 Context 是同一对象。undefined。cordis-plugin-loader@1.0.5的EntryTree.resolve()在条目缺失时是 throw(cannot resolve entry ${id})而不是返回undefined,所以WeakMap.get()拿到undefined只能说明键不存在。同时具备的旁证:
boot()明明跑过(auditStartupEntries只用bootstrapIncludes.get(ctx)判断 include 是否属于 required,键缺失时不会报错,所以启动照样成功);include:hmr处于 active 状态,说明profile HMR requires application readiness这条前置检查通过、runProfile()已经appReady.commit()。ELECTRON_RUN_AS_NODE=1加载app.asar/dsh/node_modules/...)手工boot()一个等价树后,直接调用reconcileProfilePatches(ctx, patches, 'dsh')正常返回 0 warning。只有应用自身组合出的树会失败。ctx.get('configEditor')取到的ConfigEditor实例,其ownerContext在访问loader时抛cannot get required service "loader" in inactive context,即可达的 service 实例可能挂在已被替换/失活的 fiber 上。由此指向的两个分支
键不存在意味着
mountRootInclude()那次set()不在reconcileProfilePatches()读到的那张 WeakMap 里,即二者必居其一:@deepseek-ai/dsh-app-boot被实例化了两份(例如一份来自boot()的静态导入链,另一份来自PluginPackages/ runtime interception 为插件的解析结果)。两份模块各有自己的bootstrapIncludes。mountRootInclude()记录)。一行诊断即可定性(建议加在抛错前)
建议的加固(无论上面哪个分支)
boot()把 entry 交给ctx(ctx.provide或PluginPackages之类已随树存活的载体),使reconcileProfilePatches与mountRootInclude不依赖模块实例同一性。auditStartupEntries()同样用bootstrapIncludes.get(ctx)判定 required;键缺失时应显式报错而不是静默退化为“include 非必需”,否则启动会带着一个永久坏掉的 reload 路径继续跑。ctx的取法应统一并在 miss 时给出可诊断信息(当前错误文本无法区分“没记录”与“记录了 undefined”)。Expected behavior
设置页保存成功:完整 config 写入 profile 的
cordis.patch.yml,随后通过 Loader 正常重载;bootstrapIncludes必须命中boot()时记录的根 Include 条目。Environment
Microsoft Windows NT 10.0.26200.0,x64D:\DeepSeek Harness\resources\app.asar(121,348,951 bytes,sha256983CA71114E6DFD353FC79AF5A1F9481A250EE64C2A3C757673029B811B23BC2;运行时未做任何修改)@deepseek-ai/dsh-app-boot0.2.0-rc.2、dsh-config-editor0.2.0-rc.2、dsh-settings0.2.0-rc.2、dsh-hmr0.2.0-rc.2、cordis-plugin-loader1.0.5、cordis4.0.4desktop(%USERPROFILE%\.dsh\profiles\desktop),DSH_HOME=%USERPROFILE%\.dsh%USERPROFILE%\.dsh\profiles\desktop\cordis.patch.yml并重启应用有效;同一份 route 配置在设置页之外完全正常(例如新增 OpenRouter 路由后llm.listProviders()返回[{"id":"openrouter","name":"OpenRouter"}],内置目录解析出 386 个模型)All reactions