Repository navigation
[Windows] 设置写入与插件安装全部失败:dsh: profile reload requires the root Include entry(DSH Desktop 0.2.1-rc.2) #10407
Replies: 1 comment
|
Excellent root-cause analysis. The distinction between identical package contents and separate ESM module instances is particularly important here. I agree that recovering the root Include entry through the existing loader is a reasonable minimal fix, given the current architecture. One addition I would suggest before merging this upstream is a regression test covering module-instance isolation explicitly. The test should reproduce the actual failure condition:
I would also add three negative tests:
These cases should preserve the original failure behavior rather than silently accepting an invalid runtime state. One subtle point: checking only the entry ID and name may not be sufficient if a loader can contain entries associated with different contexts or lifecycles. If the API exposes an ownership or parent-context relationship, validating it would reduce the risk of adopting an unrelated entry. For the longer term, I would also consider eliminating the module-local WeakMap as the authoritative source of this state. If the loader already owns the root Include entry, making the loader the single source of truth could prevent similar bugs when package duplication occurs through Electron bundling, pnpm dependency layouts, or plugin isolation. Your workaround appears well suited for a targeted regression test because it reproduces the original failure without requiring a complete Desktop installation. Would you be able to turn the two-module reproduction into an automated test? That would help ensure future packaging changes don't reintroduce the same issue. |
Uh oh!
There was an error while loading. Please reload this page.
[Windows] 设置写入与插件安装全部失败:
dsh: profile reload requires the root Include entry(DSH Desktop 0.2.1-rc.2)摘要
在 DSH Desktop(0.2.1-rc.2)里,任何会触发 profile 重载的操作都会失败并回滚:
ctx.remote.settings.update()的插件统一报同一条错误:
原因不是配置写错,而是:
@deepseek-ai/dsh-app-boot在同一个进程里存在两个独立副本(一个在app.asar内,一个在 profile 的node_modules内)。Desktop 宿主用 asar 内的副本调用boot(),该副本把 root Include 的 Entry 存进模块内私有的 WeakMapbootstrapIncludes;而改配置时被调用的是 profile 副本的recompose(),它查自己那份 WeakMap,永远查不到。两个副本的文件内容完全一致(SHA256 相同),差别只在模块实例。
环境
@deepseek-ai/dsh-desktop0.2.1-rc.2desktop,路径<USERPROFILE>\.dsh\profiles\desktop复现步骤
实际行为
操作失败,界面提示重启;插件市场日志(
.dsh-market/log.ndjson)记录:{"level":"error","event":"install","detail":"dsh-image-generation@0.1.3 exit=1 err=dsh: profile reload requires the root Include entry"} {"level":"error","event":"update","detail":"@michengai/dsh-archive-manager -> 1.1.6 exit=1 err=dsh: profile reload requires the root Include entry"} {"level":"error","event":"update","detail":"cc-safety-net -> 2.6.16 exit=1 err=dsh: profile reload requires the root Include entry"}预期行为
设置写入与插件安装应当直接生效,或至少给出可操作的诊断,而不是把整个写入回滚。
根因分析
1. 同一份代码被加载了两次
两份文件字节相同(SHA256 同为
5135179A...,此处省略完整值,可用Get-FileHash自行核对),但用 URL 分别 import 得到的是不同的模块对象:2. Entry 存在模块私有的 WeakMap 里
dsh-app-boot内部:Desktop 走 asar 副本的
boot()→ Entry 写进 asar 副本的 WeakMap。dsh-hmr/dsh-plugin-manager/dsh-config-editor/dsh-agent-preset-registry从 profile 的node_modules解析到 profile 副本 → 查 profile 副本的 WeakMap → 空 → 抛错。这是同一进程内的两份独立模块状态,与插件质量、配置内容、网络均无关。
3. 调用链佐证
dsh-hmr/lib/index.js:import { recomposeProfile } from "@deepseek-ai/dsh-app-boot"dsh-agent-preset-registry的agentPresets.select→ 改 preset 走 settings 写入node_modules解析,即 profile 副本影响面
settings.update()的第三方插件用户侧的观感是“这个软件每次改设置都要重启、装了插件也不生效”,且错误信息完全指向错误方向。
临时规避(已验证)
root Include 的 Entry 本身在同进程的 loader 里可查——
mountRootInclude()用固定 idinclude创建它。所以在 profile 副本的recompose()前加一步认领即可:验证结果:
reconcileProfilePatches(),能越过原来的报错点继续执行dsh: profile reload requires the root Include entry(不会静默吞掉真故障)这是绕过方案,不是修复:改动位于
node_modules,插件市场重装或应用升级后被覆盖。建议的上游修法
任选其一,均为小改动:
Symbol.for()或ctx.effect/ctx.set之类的进程级共享槽位,避免依赖模块实例身份。recompose()在 WeakMap 未命中时,回退到ctx.loader.resolve("include")并校验options.name === "cordis:include"(即上面的规避方案)。@deepseek-ai/dsh-app-boot指向 asar 内副本(package.json里imports/overrides,或在 Loader 的import()里做一次 URL 归一化),让全进程只存在一个模块实例。代价最低、风险最小的是第 2 种:只影响失败路径,命中 WeakMap 时行为完全不变。
English summary
Symptom. On DSH Desktop 0.2.1-rc.2 (Windows), every operation that triggers a profile reload fails and is rolled back with:
This covers settings writes (e.g. setting the default Agent preset), plugin-market installs and updates, and any third-party plugin calling
settings.update().Root cause.
@deepseek-ai/dsh-app-bootis loaded twice in one process: once fromapp.asar(used by the Desktop host'sboot()) and once from the profile'snode_modules(used bydsh-hmr,dsh-plugin-manager,dsh-config-editor,dsh-agent-preset-registry). The two files are byte-identical (same SHA256) but are distinct module instances.mountRootInclude()stores the root Include entry in a module-privateWeakMapnamedbootstrapIncludes;recompose()reads that same private WeakMap. The host writes to the asar copy's WeakMap; the profile-side participants read the profile copy's, getundefined, and throw.Evidence.
Call sites confirming the profile-side resolution:
dsh-hmr/lib/index.jsimportsrecomposeProfilefrom@deepseek-ai/dsh-app-boot;agentPresets.selecttriggers the settings write path.Workaround (verified). The entry is still reachable by id, because
mountRootInclude()creates it under the well-known idincludewithname: "cordis:include". Resolving it before the WeakMap lookup lets the normal path proceed:Validated: module still imports cleanly with the same 59 exports; a context that has a root Include but an empty local WeakMap passes the gate; a context with no loader still throws the original diagnostic (no silent masking).
Suggested fix (pick one).
ctxvia a process-wide shared slot instead of a module-local closure.ctx.loader.resolve("include")on a WeakMap miss, validatingoptions.name(the workaround above; smallest blast radius — only the failure path changes).@deepseek-ai/dsh-app-bootto the copy insideapp.asarso only one instance exists per process.All reactions