Skip to content

[Bug] DSH 桌面版:profile 被认成 web,profile 配置完全不被快照;undo_doctor 启动预检误报核心 bundle 不可解析 #43

Open

Description

@hanlinluo

[Bug] DSH 桌面版:profile 被认成 web,profile 配置完全不被快照;undo_doctor 启动预检误报核心 bundle 不可解析

环境

项 值
DSH DeepSeek Harness 桌面版 0.1.7-rc.2(profile 目录 ~/.dsh/profiles/desktop)
插件 dsh-undo-savepoint@0.4.9(npm latest)
系统 Windows 11 x64
Node 宿主内置 24.21.0

问题 1(严重):桌面版下 profile 被认成 web,profile 配置完全不被快照

现象

装好之后 undo_list 显示:

Profile: web

而实际 profile 是 desktop。后果是快照里只有 home 级的文件,profile 里的配置一个都没有。实测快照目录内容:

undo-snapshots\auto\20260927-175456-a5ba\home-.credentials.yaml   ← 只有这一个
undo-snapshots\auto\20260927-175456-a5ba\manifest.json

cordis.patch.yml / package.json / cordis.yml / pnpm-workspace.yaml / pnpm-lock.yaml 全部缺失 —— 而 profile 配置恰恰是这个插件最该保护的东西。手工指定 profileName: desktop 后同一个基线快照立刻变成 6 个文件 / 50.3 KB,其中就有 profile-cordis.patch.yml。

根因

桌面版由 Electron 应用启动,启动参数里没有 --profile。宿主进程的完整命令行是:

"D:\dsh\dsh\DeepSeek Harness.exe" --expose-internals \
  ...\app.asar\dsh\node_modules\@deepseek-ai\dsh-desktop-host\lib\index.js \
  ...\app.asar\dsh \
  C:\Users\<user>\.dsh\profiles\desktop \      ← profile 目录在这里,但只是位置参数
  D:\dsh\dsh\resources\runtime\primary-runtime \
  ...

lib/index.js:235 是 config.profileName ?? detectProfileName(),而 detectProfileName() 只解析 --profile(桌面版没有),于是回退到 web;profileDir 随之变成 ~/.dsh/profiles/web(不存在),导致 profile 文件全部扫不到 —— 而且是静默失败,界面上看不出任何异常。

建议修法

桌面宿主会在进程环境里设置这两个变量(我在宿主进程的子 shell 里实测到):

DSH_PROFILE     = desktop
DSH_PROFILE_DIR = C:\Users\<user>\.dsh\profiles\desktop

所以建议把优先级改成:

const profileName = config.profileName
  ?? process.env.DSH_PROFILE
  ?? detectProfileName();
// 并且优先直接采用 DSH_PROFILE_DIR
const profileDir = config.profileDir
  ?? process.env.DSH_PROFILE_DIR
  ?? join(DSH_HOME, 'profiles', profileName);

另:settings.json 里的 profileName 对宿主插件无效 —— lib/index.js:235 只读 config.profileName,而其它设置都是 config.X ?? fileSettings.X。只有离线 PowerShell CLI 读 settings 里的 profileName。这个不一致也值得顺手统一。


问题 2:undo_doctor 的启动预检在桌面版误报,并给出会误伤的建议

现象

❌ bundle @deepseek-ai/dsh-base: cannot resolve @deepseek-ai/dsh-base, DSH aborts before loading any plugin
   → run dsh-undo.ps1 safe-mode -Label on, then restart DSH
❌ bundle @deepseek-ai/dsh-web-app: cannot resolve ...
❌ bundle @deepseek-ai/dsh-experimental-agent-team-profile: cannot resolve ...
❌ bundle @deepseek-ai/dsh-experimental-auto-review: cannot resolve ...

为什么是误报

桌面版的核心 bundle 不在 profile 的 node_modules 里,而是从应用载荷 app.asar/dsh/node_modules/ 解析。实测该 profile 的 node_modules/@deepseek-ai/ 下只有 cosmokit 和 schemastery 两个包 —— 找不到 dsh-base 是正常的,DSH 运行得好好的(当前会话就在里面跑)。

按提示去执行 safe-mode 会把用户所有插件全部禁用,属于误伤。

建议

预检在桌面版下要么跳过 bundle 解析检查,要么改用与 DSH 启动器相同的解析根(可用 DSH_ROOT / DSH_PROFILE_DIR 推导)。至少要能区分"确实不可解析"和"这是桌面版"。


问题 3(不确定责任方,仅供参考):写进 profile patch 的覆盖项会在约 2 秒后被删除

这一条我不确定是不是本插件造成的,但现象稳定复现,贴出来供你判断。

在 <profile>/cordis.patch.yml 末尾追加:

# some comment
- id: dsh-undo
  name: dsh-undo-savepoint
  config:
    profileName: desktop

结果是:插件确实读到了配置(undo_list 立刻显示 Profile: desktop,并随即产生 6 文件的基线快照),但大约 2 秒后,那 4 行条目被删掉了,而前面的注释行原样保留。连续两次,两次的现象完全一致。

快照时间戳为证:

时间 类型 文件数 说明
17:56:06 baseline 6 配置生效
17:56:08 patch-change 6 文件已被改写(截断状态)
17:56:08 baseline 1 重新挂载,配置丢失 → 回到 web
18:43:25 baseline 6 第二次写入,配置生效
18:43:27 patch-change 6 又被改写
18:43:27 baseline 1 再次丢失

我怀疑是"撤销不会撤销掉自己"那套"恢复后自动检查并重新写入挂载条"的逻辑在做归一化时丢掉了带 config 的覆盖项(也可能是 dsh-plugin-manager 的重写,所以我标为不确定)。

我目前的绕过方式:把 config.profileName 写进包自己的 bundle patch(node_modules/dsh-undo-savepoint/cordis.patch.yml),并用 pnpm patch 持久化。那份文件没有别的组件在动,配置能稳定存活。


复现

任何桌面版用户都能复现问题 1 和 2,不需要特殊配置:装完插件,undo_list 的 Profile: 一定是 web,快照里一定没有 profile 配置。

macOS 的 DeepSeek Harness.app 应该同理(它同样不带 --profile 启动)—— 我这边是 Windows,只验了 Windows。

我本地的补丁

如果短期内不打算修问题 1,我可以继续用 pnpm patch 自我维持;贴出来只是想说:这个默认行为在桌面版上是静默失效的,用户不会收到任何提示,很容易以为配置已经受保护了。

Activity

  1. lire1131 commented on Sep 29, 2026

    @lire1131
    Owner

    Thanks for a remarkably thorough report — all three problems reproduce or trace to confirmed root causes on our side. Findings first, fix plan at the end.

    Problem 1 (profile detected as web on desktop) — confirmed, and it's a capability gap, not a regression. detectProfileName() (v0.3.3) only parses --profile from argv and falls back to web. The desktop host never passes that flag: it launches via runProfile({ profile: 'desktop', ... }) and injects the profile through the profileContext service (name/dir/patchPath) before plugins mount. In-process plugins therefore see an Electron argv with no --profile, and the fallback fires silently. You're also right that profileName in undo/settings.json has no effect for the host-mounted plugin: our config resolution chain for that key only consults the plugin config object — every other setting falls back to the settings file; profileName doesn't. Two genuine bugs; the fix plan below covers both.

    Problem 2 (doctor false-positive on core bundles) — confirmed, with a caveat that matters more than the false positive. bundleCheck resolves bundles from exactly two anchors (~/.dsh and the profile directory). Desktop's core bundles live inside app.asar, outside both, so every one of them reports "cannot resolve". The dangerous part: safe-mode consumes the same check. If anyone follows the doctor's advice and runs safe-mode on a desktop profile, it will prune those bundles as "unrecoverable" — if you're on the desktop build, please don't run safe-mode until the fix ships.

    Problem 3 (mount entry with config deleted ~2s after save) — confirmed, reproduced, and it's ours, not dsh-plugin-manager's. The culprit is our own mount deduplication (the startup self-heal that keeps exactly one canonical mount): when the profile's package.json lists dsh-undo-savepoint in dsh.profile.bundles, the bundle mount ranks as canonical and any patch-file entry for the same plugin is treated as a duplicate and removed. Your entry carried a config block — that's configuration injection, not a redundant mount — but the dedup logic doesn't distinguish. We reproduced it end-to-end: the entry is stripped, the comment line survives, and (a detail worse than what you reported) the deletion leaves a dangling - insert: behind because the block splitter misreads indented list items as new blocks. The 2-second delay you observed is the watcher debounce picking up the rewrite, followed by the re-mount landing back on web — exactly your timeline table. The .dsh-undo-bak sibling should hold your pre-deletion state if you want the exact block back.

    Your pnpm patch workaround is sound for now — that file is outside the dedup scan surface, so nothing will touch it.

    Fix plan (all three land in 0.5.0, currently in canary staging):

    • Mount entries carrying a config block are exempt from dedup removal; the block splitter is also being fixed so partial deletions can't leave dangling insert: keys.
    • Profile detection chain becomes: --profile argv → profileContext service (the desktop host's official channel) → DSH_PROFILE env → web; and profileName gains the settings-file tier like every other option.
    • Doctor learns the desktop shape: bundles the host started from outside the profile tree (visible via profileContext.startedBundles) are reported as host-managed instead of "cannot resolve", and safe-mode refuses to touch desktop profiles rather than pruning asar-hosted bundles.

    One note on DSH_PROFILE/DSH_PROFILE_DIR: we didn't find either being set by the host in 0.1.7-rc.2's desktop-host source; the channel we verified is profileContext. If you observed those variables in your process, we'd genuinely like to know — it may be a newer build than the tag we inspected.

  2. lire1131 commented on Sep 30, 2026

    @lire1131
    Owner

    Scheduling update on my earlier reply: all three land in canary.4, together with the desktop adaptation itself (internal ticket W40), rather than in the canary.3 batch I mentioned. The fix plan I described stands unchanged: mount entries carrying a config block are exempt from dedup removal and the block splitter stops leaving dangling insert: keys; the profile detection chain becomes argv --profile, then the host's profile channel, then DSH_PROFILE, then web, with profileName gaining the settings-file tier; and the doctor learns the desktop shape so host-started bundles are reported as host-managed and safe mode refuses to touch desktop profiles.

    Until then the warning from my earlier reply still holds: on the desktop build, do not run safe mode.

    One open question from that reply, in case you can check it: we did not find DSH_PROFILE or DSH_PROFILE_DIR being set by the 0.1.7-rc.2 desktop host, and the channel we verified is the host's profile context. If you saw those variables in your process, tell us which build, since it may be newer than the tag we inspected.

    Your pnpm patch workaround holds for now. Note that plugin upgrades overwrite node_modules, so keep the persistence step.

  3. lire1131 commented on Sep 30, 2026

    @lire1131
    Owner

    Correction to my previous comment: the adaptation work is scoped to DSH 0.2.0, not 0.1.7. The 0.1.7 wording was a typo. The fix plan and the scheduling are otherwise unchanged.

  4. lire1131 commented on Oct 3, 2026

    @lire1131
    Owner

    All three problems are fixed in v0.4.10, now on npm latest. Thanks for the exceptionally detailed report, the manifest diff between auto-detected and manual profileName snapshots is what pinned problem 1.

    On why this shipped ahead of the canary.4 schedule mentioned above. The fixes were originally planned for 0.5.0, but 0.4.9 on npm latest also carried #42 (write/edit unusable) and #39 (a security leak), so we cut an emergency 0.4.x release with the verified fixes instead of letting fresh installs hit known bugs while 0.5.0 finishes its desktop install chain and real-machine validation.

    What 0.4.10 ships, problem by problem.

    Problem 1 (profile detected as web). detectProfileName now runs a four-layer chain, argv --profile first, then the host profileContext (the desktop host's actual source of truth), then DSH_PROFILE/DSH_PROFILE_DIR shell env, then the web default. settings.json also gained a profileName key, explicit config wins over auto-detection. Desktop snapshots now capture the full profile config set, your manual profileName workaround is no longer needed.

    Problem 2 (doctor false positives). bundleAnchors gained the installation anchor, matching the official order where the dsh installation resolves before the profile directory. Host-started bundles are reported as host-managed at ok level instead of cannot resolve. One deliberate behavior change, undo_safe_mode now refuses to enable for desktop profiles, both when a desktop host context is present and when targeting --profile desktop directly, pointing to DSH's own fatal-recovery dialog. Pruning desktop bundle sets is riskier than the mode is worth until the 0.5.0 install chain lands. Turning safe-mode off remains always allowed, snapshots and undo are unaffected.

    Problem 3 (configured mount entries deleted). Entries carrying a user config block now win deduplication, the bundle face is withdrawn instead, and the block splitter no longer leaves dangling - insert: keys. Historical leftovers are swept on the next pass.

    The suite adds 21 cases across these fixes, 15 for the detection chain and the #44 anchor matrix, 6 for the dedupe guard. Smoke total is 371 passed, 0 failed. Desktop real-machine validation and the install-chain polish remain on the 0.5.0 track, a confirmation from your side after updating would be welcome, this issue stays open until then.

  5. lire1131 commented on Oct 5, 2026

    @lire1131
    Owner

    Status update on the three problems, closing the loop opened in my 2026-10-03 reply.

    Verdict. All three are fixed, and the 0.5.0-canary.4 build now carries the same verified implementations. The stable channel got them first: v0.4.10, on npm latest since 2026-10-03, is where most of this landed for regular users.

    Root causes, restated in one line each. Profile detection had no path to the host's profile channel, so the desktop build fell back to web. bundleAnchors() walked only the home and profile directories, so host-started bundles under the install tree never resolved and safe mode, which consumes the same check, treated them as prunable. Mount deduplication ranked the bundle mount as canonical and removed the patch entry, without checking that the entry carried a config block.

    What is fixed. Entries carrying a config block are exempt from dedup removal and the bundle face is withdrawn instead, and the block splitter no longer leaves dangling insert: keys. Detection now walks explicit config, the settings file, the command line, the host profile channel, the environment and a default, in that order, and both undo_list and undo_doctor report which tier won. bundleAnchors() gained the installation anchor in the official order, host-started @deepseek-ai/dsh bundles are reported as host-managed, and undo_safe_mode refuses to enable on a desktop profile before anything is written, while off still works.

    Tests. The canary.4 batch brings the smoke gate to 509 passing, 0 failing, with dedicated cases for the dedupe exemption, the six-tier detection chain with source reporting, the anchor order, host-managed exemption, the non-desktop shape staying byte-identical, and the desktop refusal. The v0.4.10 build that introduced these fixes reported its own smaller total, and that remains accurate for that build.

    Known boundaries. Desktop real-machine validation of the install chain is still in progress, so the desktop install path is not yet declared verified. On a desktop profile, do not run safe mode until that validation lands, and note that the plugin now refuses the operation rather than attempting a prune. The pnpm patch workaround is no longer needed: update past 0.4.10, and if you installed from master the dedupe exemption applies. One more boundary worth knowing: the official sanitize trace detection requires all three elements (a backup file exists, the current patch does not mention this plugin, and the manifest's bundles do not list it), so a partial sanitize where the bundles section was restored but the patch content was not is not detected yet, and that gap is tracked separately.

    A confirmation from your side after updating would close this out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions