从 npm 版 dsh 迁移到 DSH 桌面版:彻底告别 dsh web
适用读者:已经用
npm i -g @deepseek-ai/dsh装了 CLI、日常靠dsh web起 GUI 的同学。 目标形态:只保留桌面版一个运行时 ,原 web profile 的插件和配置全部搬过去,dsh命令指向桌面版自带的那一份。 环境:Windows + dsh0.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 请替换为你自己的安装目录。