从 npm 版 dsh 迁移到 DSH 桌面版:彻底告别 dsh web

从 npm 版 dsh 迁移到 DSH 桌面版:彻底告别 dsh web

适用读者:已经用 npm i -g @deepseek-ai/dsh 装了 CLI、日常靠 dsh web 起 GUI 的同学。 目标形态:只保留桌面版一个运行时 ,原 web profile 的插件和配置全部搬过去,dsh 命令指向桌面版自带的那一份。 环境:Windows + dsh 0.2.0-rc.2 + 官方桌面版 0.2.0-rc.2(macOS / Linux 路径形态不同,思路一致)。


TL;DR

powershell 复制代码
# 1. 找到桌面版安装目录
$desktop = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*,
                              HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\* |
            Where-Object DisplayName -like 'DeepSeek Harness*' |
            Select-Object -First 1).InstallLocation

# 2. 用【桌面版自带的 CLI】把插件装进桌面版自己的 profile
& "$desktop\resources\runtime\cli\bin\dsh.cmd" plugin --profile desktop add <你的插件>

# 3. 重启桌面版,确认插件都在

# 4. 卸掉 npm 全局那份,dsh 才会指向桌面版
npm uninstall -g @deepseek-ai/dsh

最容易踩的两个坑 :桌面版不会 继承你 profiles/web 里的插件;以及 Windows 上机器 PATH 优先于用户 PATH ,不卸掉 npm 全局那份,dsh 永远指向旧的。


一、为什么要迁:两条版本线注定会漂移

dsh 的桌面版和 CLI 版是两个独立更新的东西 ,但它们写的是同一个 ~/.dsh:

更新方式 运行时来源
桌面版 应用内更新(走 download.deepseek.com,通道 nightly) 内嵌锁定,如 Node 24 + pnpm 11.7 + Python 3.12
npm 全局 CLI 你手动 npm i -g 系统的 Node,比如 22.x

我扒过桌面版主进程的打包代码,更新通道是硬编码的,没有开关:

js 复制代码
this.updater.autoDownload = false;
this.updater.autoInstallOnAppQuit = false;
this.updater.channel = "nightly";   // ← 仅此一处赋值,无设置项、无环境变量可覆盖
this.updater.allowPrerelease = true;

好消息是 autoDownload = false + autoInstallOnAppQuit = false 意味着它只检查、只提示 ,不会背着你装。但它同时带了一个 dshMandatoryUpdatePolicy(服务端可在版本不再受支持时强制更新)。

于是问题来了:桌面版往前跑、npm 那份停在原地,而两者共用一套 session / projection / task-board 数据。版本一旦分叉,就是数据格式分叉。

而且两个宿主同时跑会直接撞车。我本机就在 ~/.dsh/logs/ 里留下过这样的启动失败日志:

vbnet 复制代码
profile: 'web'
error: StartupError: dsh: startup failed: 2 required plugins did not activate
  webserver (required)
    Error: listen EADDRINUSE: address already in use 127.0.0.1:3080

所以"只留一个运行时"不只是整洁问题,是稳定性问题。


二、先搞懂一件事:桌面版不读你的 web profile

这是整个迁移里最容易误解 的地方。很多人(包括我一开始)以为桌面版就是把 dsh web 套了个 Electron 壳,会自动用你现有的配置。不是的。

桌面版主进程里是硬编码的:

js 复制代码
const paths = resolveDesktopPaths();   // { profile: join(dshHome, "profiles", "desktop") }
const activeProject = paths.profile;
const host = new DesktopHostProcess(resources.node, resources.dsh, activeProject, ...);
//                        node,             runtimeDir,        projectDir

首次启动时它只做一次初始化,用的是出厂模板:

js 复制代码
const WEB_PROFILE = PROFILE_TEMPLATES.web;          // { bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"] }
function createPluginProfile(projectDir) {
  initProfile(projectDir, WEB_PROFILE.bundles);     // ← 出厂列表,不是你的 profiles/web
}

结论:桌面版永远启动 $DSH_HOME/profiles/desktop,而它是从出厂模板种出来的空壳 ,只有 dsh-base + dsh-web-app,你在 profiles/web 装的一切都不会跟过来。

怎么验证自己现在跑在哪个 profile

在任意一个 dsh 会话里问它自己:

powershell 复制代码
echo $env:DSH_PROFILE
# web      -> 你现在在浏览器里,跑的是 npm 版
# desktop  -> 你已经切到桌面版了

或者直接对比两个 profile:

powershell 复制代码
$h = if ($env:DSH_HOME) { $env:DSH_HOME } else { "$HOME\.dsh" }
foreach ($p in 'web','desktop') {
  $f = "$h\profiles\$p\package.json"
  if (Test-Path $f) {
    "[$p] " + (((Get-Content $f -Raw -Encoding UTF8 | ConvertFrom-Json).dsh.profile.bundles) -join ', ')
  }
}

我迁移前的实测输出------差距一目了然:

scss 复制代码
[web]     @deepseek-ai/dsh-base, @deepseek-ai/dsh-web-app, @tt-a1i/archify-dsh,
          @sanqi-normal/dsh-webui-market-plugin, @linxin666/dsh-web-all
[desktop] @deepseek-ai/dsh-base, @deepseek-ai/dsh-web-app          ← 光秃秃

三、迁移前:先备份

powershell 复制代码
$h = if ($env:DSH_HOME) { $env:DSH_HOME } else { "$HOME\.dsh" }
$d = "$h\profiles\desktop"
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
foreach ($f in 'package.json','cordis.patch.yml','pnpm-workspace.yaml') {
  if (Test-Path "$d\$f") { Copy-Item "$d\$f" "$d\$f.bak-$stamp" -Force }
}

顺便确认 profiles/web 一个字都别动------那是你的回退路径。


四、第一步:把插件装进 desktop profile

关键:用桌面版自带的 CLI,不要用系统 node

桌面版内嵌了自己的 Node 24 和 pnpm 11.7,而且它的 CLI 已经把这些接好了:

js 复制代码
// dsh-desktop-host/lib/cli.js
await runCli({
  manageDesktopProfile: true,
  packageManager: {
    command: process.execPath,
    args: ["--expose-internals", join(supportDir, "pnpm", "bin", "pnpm.mjs")],
    env: { ELECTRON_RUN_AS_NODE: "1", ... }
  }
});

为什么必须用它 :@linxin666/dsh-web-all 这类插件依赖树里有 node-pty、ssh2、cpu-features 等原生模块。用系统 Node 22 去装,产物可能和桌面版的 Node 24 ABI 不匹配,装得上但加载会炸。用桌面版自带的 CLI 就没这个问题。

安装

powershell 复制代码
$desktop = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*,
                              HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\* |
            Where-Object DisplayName -like 'DeepSeek Harness*').InstallLocation
$cli = "$desktop\resources\runtime\cli\bin\dsh.cmd"

& $cli plugin --profile desktop add @linxin666/dsh-web-all@0.4.5
& $cli plugin --profile desktop add @tt-a1i/archify-dsh@0.1.0

为什么要锁精确版本 :pnpm 11 默认开启了 24 小时 minimumReleaseAge 供应链策略。我迁移当天正好撞上 @linxin666/dsh-web-all@0.4.5 是当天刚发布 的(2026-10-05T08:22:04Z 发布,我两小时后安装),写 ^0.4.5 有可能解析到更新构建而被策略拦下。pnpm 的实际行为是自动往 pnpm-workspace.yaml 追加白名单:

css 复制代码
Added 19 entries to minimumReleaseAgeExclude in pnpm-workspace.yaml

但锁版本更可控,尤其是在写教程给别人复现的时候。

顺手把 pnpm 构建策略对齐

原 web profile 的 pnpm-workspace.yaml 里有 allowBuilds,desktop 那份是空的。建议先写上,避免 pnpm 11 报 ERR_PNPM_IGNORED_BUILDS:

yaml 复制代码
packages:
  - .

nodeLinker: hoisted
autoInstallPeers: false
allowBuilds:
  cloudflared: false
  cpu-features: false
  node-pty: true
  ssh2: false

注意:不兼容的插件别硬搬

我在 web profile 里还装过一个 @sanqi-normal/dsh-webui-market-plugin,dsh 在启动时会直接拒绝它:

less 复制代码
dsh: skipping profile bundle "@sanqi-normal/dsh-webui-market-plugin":
Error: Plugin @sanqi-normal/dsh-webui-market-plugin@0.5.5 is incompatible with dsh 0.2.0-rc.2:
peerDependencies {"@deepseek-ai/dsh-client-runtime":">=0.0.1-rc.2 <0.2.0-0 || ..."}
Running it may cause crashes or data loss.

它的 peer 上限是 <0.2.0-0,而我们已经是 0.2.x。而且它依赖的 @deepseek-ai/dsh-client-runtime 在新版 dsh 里已经不存在了 。所以迁移时直接跳过,别装------装了也只会被跳过。

顺带说,dsh 这个"检测到不兼容就跳过该 bundle 并继续启动"的行为值得点赞:0.2 时代插件生态断代严重,没有这层闸门,一个坏插件就能让你开不了机。

迁移后 profile 长这样

json 复制代码
{
  "name": "dsh-profile-desktop",
  "private": true,
  "dependencies": {
    "@linxin666/dsh-web-all": "0.4.5",
    "@tt-a1i/archify-dsh": "0.1.0"
  },
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app",
        "@linxin666/dsh-web-all",
        "@tt-a1i/archify-dsh"
      ]
    }
  }
}

五、第二步:搬 cordis.patch.yml 里的配置

插件配置不会自动跟过来。 你的主题、桌宠、默认模型这些都在 profiles/web/cordis.patch.yml 里,需要合并进 profiles/desktop/cordis.patch.yml。

这里有个细节容易搞混:$DSH_HOME/cordis.patch.yml(home 层 )是全局补丁,桌面版会自动继承,不用搬。我本机那份只注册了一个 radar 钩子:

yaml 复制代码
[{insert: [{id: radar-post-tool-use, name: 'file:///C:/Users/xxx/.dsh/radar/radar-deepseek-harness.mjs', ...}]}]

真正要搬的是 profile 层那些。以我的为例:

条目 id 作用 是否要搬
web-ui-skin-center 皮肤中心 ✅ 前提是装了对应插件
web-ui-pet 桌宠(ouo-neko / 尺寸 / 位置) ✅ 同上
agent-default-model 默认模型 / 推理强度 ✅
ui-theme 暗色主题 / 字号 ✅
ui-settings-general 欢迎提示版本号 ✅ 可选
ui-chat / ui-settings / ui-settings-account 桌面版自己写的,保留 保留

合并时保留桌面版自己写的条目,追加你的。注意 id 不要重复。

yaml 复制代码
- id: ui-chat
  name: "@deepseek-ai/dsh-client-ui-chat"
  config:
    transcriptView: standard
    performanceUsage: detailed
# ... 桌面版原有条目保留 ...
- id: web-ui-pet
  name: "@linxin666/dsh-pet"
  config:
    petId: ouo-neko
    visible: true
    size: 160
- id: agent-default-model
  name: "@deepseek-ai/dsh-agent-default-model"
  config:
    provider: deepseek-official
    model: deepseek-flash
    reasoningEffort: high
- id: ui-theme
  name: "@deepseek-ai/dsh-client-ui-theme"
  config:
    preference: dark
    fontSize: 16

六、第三步:重启桌面版并验证

完全退出桌面版再启动(托盘图标右键退出,不是关窗口)。

一个反直觉的验证限制

你不能用 CLI 去"预览"desktop profile 的组合结果:

powershell 复制代码
& $cli --profile desktop --dump-config
# error: profile "desktop" is managed exclusively by the Electron application

desktop 是保留 profile:CLI 可以给它装插件,但只有 Electron 应用能引导它。 所以端到端验证只能靠重启后肉眼看。

重启后的检查清单

  • 设置 → 插件:能看到你迁入的插件(我的有任务看板、Git 图谱、市场、皮肤中心等 20 个包)
  • 桌宠出现、皮肤可切换
  • 主题是暗色 + 16px
  • 默认模型是 deepseek-flash + 推理强度 high
  • 让 dsh 用一下自定义 Skill(我用 archify 验证技能源挂载)

也可以用文件时间戳 旁证插件确实活着------我重启后确认 pet.json / skin-center-active.json 都在被持续写入:

powershell 复制代码
Get-ChildItem "$h" -File | Where-Object Name -match 'pet|skin' | Select-Object Name, LastWriteTime

好消息:重启后桌面版保留了我迁入的第三方 bundle,还自己追加了它的实验性 bundle(agent-team-profile、auto-review、schedule-bundle、voice-input-bundle)。两者共存互不干扰------我原本担心它会用出厂列表把自己的 profile 重置掉,实测正常重启不会。


七、第四步:让 dsh 指向桌面版

坑:Windows 的 PATH 顺序

桌面版自己会注册 PATH 归属,而且在注册表里留了所有权记录:

ini 复制代码
HKCU\Software\DeepSeekHarness\Command
  Directory = D:\dsh desktop\resources\runtime\cli\bin

它也把自己写进了用户 PATH (还排在第一位)。但进程生效的 PATH 是**「机器 PATH 拼上用户 PATH」------机器那一段在前,而 C:\nvm4w\nodejs(nvm 的 node 目录)在机器 PATH** 里:

ini 复制代码
机器 PATH  [13] C:\nvm4w\nodejs                            ← 先命中,npm 全局的 dsh 在这
用户 PATH  [16] D:\dsh desktop\resources\runtime\cli\bin    ← 桌面版的,永远轮不到

所以光加 PATH 没用,必须把 npm 全局那份移走。机器 PATH 不能为了这个去改(会连带影响 node/npm)。

卸载 npm 全局版

先关掉 dsh web。 你现在正在用的浏览器 GUI 就是它托管的,宿主进程还在跑的时候删它的文件,Windows 上可能删不干净、留下半残安装。

确认 3080(dsh web 的默认端口)已经没有监听:

powershell 复制代码
netstat -ano | Select-String 'LISTENING' | Select-String ':3080'
# 没有任何输出 = 已经关了

然后:

powershell 复制代码
npm uninstall -g @deepseek-ai/dsh

我的输出:

复制代码
removed 538 packages in 1m

验证

powershell 复制代码
where.exe dsh
# D:\dsh desktop\resources\runtime\cli\bin\dsh.cmd

dsh --version
# 0.2.0-rc.2

注意 :已经开着的终端不会立刻生效(PATH 是进程启动时的快照),新开终端才是准的。

顺手确认没有别处的同名文件会抢占:

powershell 复制代码
$all = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' +
       [Environment]::GetEnvironmentVariable('Path','User')
foreach ($e in ($all -split ';')) {
  $x = [Environment]::ExpandEnvironmentVariables($e.Trim().Trim('"'))
  if ($x -and (Test-Path $x)) {
    foreach ($n in 'dsh','dsh.cmd','dsh.bat','dsh.exe','dsh.ps1') {
      if (Test-Path (Join-Path $x $n)) { "命中: $(Join-Path $x $n)" }
    }
  }
}

我这边只命中一条,干净。


八、看一眼 dsh 到底启动了什么

桌面版自带的 dsh.cmd 是个转发壳(243 字节):

bat 复制代码
@echo off
setlocal DisableDelayedExpansion
set "ELECTRON_RUN_AS_NODE=1"
"%~dp0..\..\..\..\DeepSeek Harness.exe" --expose-internals "%~dp0..\..\..\app.asar\dsh\node_modules\@deepseek-ai\dsh-desktop-host\lib\cli.js" %*
exit /b %errorlevel%

展开后的执行链:

css 复制代码
dsh.cmd
 └─ D:\dsh desktop\DeepSeek Harness.exe          (以 ELECTRON_RUN_AS_NODE=1 当 Node 用)
      └─ app.asar\dsh\node_modules\@deepseek-ai\dsh-desktop-host\lib\cli.js
           └─ runCli({ manageDesktopProfile: true, packageManager: 内嵌 pnpm })

一个容易误判的点 :Test-Path "...\app.asar\...\cli.js" 会返回 False,这不是坏了 。app.asar 是个约 116 MB(121,348,951 字节)的单个文件 (归档),Electron 会 hook fs 让它像目录一样可读,而普通 PowerShell 没有这层 hook,所以任何 app.asar\... 子路径都判为不存在。dsh --version 能正常返回就是最好的证明。


九、确认全局环境没被误伤

npm uninstall -g 删掉的是 @deepseek-ai/dsh 及其依赖树,检查一下其他全局包还在:

powershell 复制代码
npm ls -g --depth=0

我的结果(只少了 dsh,其余完好):

kotlin 复制代码
+-- @anthropic-ai/claude-code@2.1.272
+-- @deepseek-harness-tui/dsh-tui@0.10.0
+-- corepack@0.34.6
`-- npm@10.9.8

十、回滚

迁移全程 profiles/web 没动过,所以回退有两条路:

只回退桌面版 profile:

powershell 复制代码
Copy-Item "$d\package.json.bak-$stamp"        "$d\package.json" -Force
Copy-Item "$d\cordis.patch.yml.bak-$stamp"    "$d\cordis.patch.yml" -Force
Copy-Item "$d\pnpm-workspace.yaml.bak-$stamp" "$d\pnpm-workspace.yaml" -Force

彻底回到 CLI 版:

powershell 复制代码
npm i -g @deepseek-ai/dsh@0.2.0-rc.2

(注意版本要和桌面版内嵌的一致,装完 dsh -V 和 desktop-runtime.json 里的版本对一下。)


十一、坑位清单

坑 现象 解法
以为插件会自动继承 桌面版里空无一物 插件装在 profiles/desktop,用桌面版自带 CLI
用系统 Node 装插件 原生模块 ABI 不匹配,装了加载失败 用 <桌面版>\resources\runtime\cli\bin\dsh.cmd
pnpm 11 供应链策略 ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION 锁精确版本;pnpm 会自动写 minimumReleaseAgeExclude
装了不兼容插件 启动日志 skipping profile bundle 检查插件 dsh.engines.dsh / peer 范围;0.2.x 时代老插件直接跳过
改了 PATH 但 dsh 不变 where dsh 还是旧路径 Windows 机器 PATH 优先;必须卸掉 npm 全局那份
新加 PATH 后当前终端无效 dsh 找不到 PATH 是进程快照,开新终端
想 --dump-config 预览 profile "desktop" is managed exclusively by the Electron application 保留 profile 只能被 Electron 引导,重启应用肉眼验证
app.asar\... 路径 Test-Path 为 False 以为文件丢失 asar 是归档文件,Electron 才能读,非故障
两个宿主同时跑 端口冲突 / 数据并发写 迁移完成后别再 dsh web

十二、结束语

迁移完成后的状态:

  • 日常只开桌面版
  • 插件、主题、桌宠、默认模型全在
  • dsh 命令指向桌面版内嵌的那份 CLI,和桌面版同一条版本线------版本漂移在结构上不可能了
  • npm 全局那份已卸载

最后提一句取舍:桌面版更新走 nightly 通道(硬编码,切不了 Stable)。但因为它 autoDownload = false,不会背着你升级 ,提示新版本时你不点就行。真正无法拒绝的只有服务端下发的强制更新。所以日常用法是:不急着点更新,装着没问题就先用着 。真要更新,装完跑一次 dsh -V 和 desktop-runtime.json 里的 desktopVersion 对一下即可------因为现在只有一条线,这两个值本来就该一致。


本文基于 Windows + dsh 0.2.0-rc.2 实测整理,命令可直接复制。路径中的 D:\dsh desktop 请替换为你自己的安装目录。

相关推荐
Ai小way18 小时前
【deepseek实战·28】倒计时看板:多个日子一起盯,环形进度走到零,Web Audio 替你响一声
deepseek
AC赳赳老秦1 天前
OpenClaw 与 FineBI 联动方案:公开数据自动采集与实时业务分析看板实践
大数据·开发语言·python·php·finebi·deepseek·openclaw
章鱼哥19712 天前
我给 DeepSeek 的编程智能体写了三个插件:余额胶囊、任务面板、番茄钟
ai编程·deepseek
AC赳赳老秦3 天前
采集行为合规自检:OpenClaw 自动校验 robots 协议与采集频率,规避违规采集风险
java·开发语言·c++·python·php·deepseek·openclaw
Am-Chestnuts3 天前
DeepSeek 回答怎么落成可编辑文档?DS随心转 三种导出路径对比怎么选
word导出·文档转换·办公效率·deepseek
AC赳赳老秦3 天前
OpenClaw 数据引用规范自动生成:为公开数据构建可信来源标注与标准引用体系
大数据·开发语言·汇编·数据库·人工智能·deepseek·openclaw
云卷云舒___________3 天前
Qwen 4首测泄露!DeepSeek V4 mini展示名为flash?蚂蚁Ling-3.1-flash同步炸场 | 10月1日 AI日报
人工智能·开源模型·deepseek·ai日报·qwen4·ling31flash·蚂蚁百灵
潘锦3 天前
从 Pi 到 DSH:Agent Harness 如何从「可扩展」走向「自生长」
deepseek
潘锦3 天前
DeepSeek Harness 的 Cordis 插件架构
ai编程·deepseek