承接上篇:这个桌面应用是 AI 从规划到实现全程完成的,本篇把可复现的落地步骤完整贴出来——从同级 clone 到 2 个补丁到跑通,附 Windows 受限网络三坑。
上一篇《让AI把 DeepSeek Harness 做成桌面应用》,我讲了架构——进程内 Host + webserver + localhost 同源,零上游改动,而且这整个应用是 AI 从规划到实现全程完成的。
这一篇直接上干货:怎么把 AI 写好的代码跑起来。从同级 clone、到 2 个补丁、到一键构建,全程照抄就能复现,文末还附上我在 Windows 受限网络下踩的 3 个坑。
如果你也想过给某个 agent 框架套个桌面壳,却被「Node 内部 API 在 Electron 下不可用」劝退过——这篇就是写给你的。
一、先交代:要 clone 什么
这个工程消费 deepseek-harness(dsh)和 dsh-market(插件市场),方式是同级目录源码引用(不是 git submodule)。构建前先把它们 clone 到本工程的同级目录:
# dsh:锁定 tag = dsh-v0.1.5-rc.2(与 patches/dsh-v0.1.5-rc.2/ 对应)
git clone --branch dsh-v0.1.5-rc.2 https://github.com/deepseek-ai/deepseek-harness.git ../deepseek-harness
git clone --branch v1.26.0 https://github.com/dsh-market/dsh-market.git ../dsh-market
为什么同级目录而非 submodule?因为
@deepseek-ai/dsh(apps/cli)只暴露bin,没有main/exports,runProfile只能从源码 import。源码引用能直接 import 任意 host/client 包、能自建前端 dist、能紧跟上游迭代。
如果缺 ../dsh-market,collect-dsh.mjs 会硬失败(打包产物需要把它物化为 dsh-dist/node_modules/dshmarket)。
二、自定义 desktop profile
dsh 用 profile 声明「组合哪些插件」。我们要的是「web 组合,微调为桌面」,所以新建一个 desktop profile:
profiles/desktop/
├── package.json # dsh.profile.bundles = [dsh-base, dsh-web-app]
└── cordis.patch.yml # 覆盖 web-runtime.printUrl: false(不打印 URL 行)
关键就两点:
dsh.profile.bundles = [dsh-base, dsh-web-app]——复用 web 组合,不 fork。cordis.patch.yml里把web-runtime的printUrl关掉(桌面应用启动时不需要往终端打 URL),端口则由启动代码注入(--port 0)。
三、主进程挂起 dsh Host
桌面壳的主进程就干一件事:动态 import dsh 的 runProfile,把 Host 挂起来,就绪后建窗加载。
// src/main/host.ts
const { ctx, shutdown } = await runProfile('desktop', ['--port', '0']);
// --port 0 = OS 分配空闲端口;读 ctx.webServer.port 拿实际端口
const url = `http://127.0.0.1:${ctx.webServer.port}/`;
// src/main/windows.ts
win.loadURL(url); // 渲染进程同源加载 dsh Web UI
托盘/通知这类桌面能力,主进程直接订阅 ctx 的 session/event(进程内,无需 HTTP/WS),触发 Electron Notification / Tray。
四、两个补丁,为什么必须有
dsh 依赖 Node 内部 API(HMR、原生目录对话框),而 Electron 的 V8 是 -electron 分支,缺了关键符号,导致这些能力在 Electron 下不可用。所以构建前要打两个补丁:
| 补丁 | 作用 |
|---|---|
dsh-disable-hmr.patch | 给 runProfile 加 DSH_DISABLE_HMR 开关,跳过依赖 --expose-internals 的 watch-only HMR |
dsh-disable-native-picker.patch | 让目录选择器在 Electron 下强制用 browse(原生对话框 worker 用 electron.exe 启动会失败) |
根因:dsh 的 loader 经
node-addon-require-builtin原生模块获取 Node 内部 ESM loader,该模块依赖 Electron V8 缺失的GetAlignedPointerFromEmbedderData符号而失效。
补丁按 dsh 版本分目录存放(patches/<dsh-tag>/),升级 dsh 时新增对应目录即可。
五、完整命令清单(一键跑通)
# ① 本工程依赖(npm:Electron / Forge / Vite)
npm install
# ② dsh 依赖(在 ../deepseek-harness 下执行;pnpm 工程)
cd ../deepseek-harness && pnpm i
# ③ 一键构建 dsh:git apply 2 个补丁 → pnpm install → build host/client/web → build dsh-market
# (幂等,--reverse --check 检测已应用则跳过)
npm run build:dsh
# ④ 开发模式:Vite 构建 + 启动 Electron,主进程挂起 dsh Host 并加载 Web UI
npm start
# ⑤ 打包:prepackage 自动 collect(pnpm deploy 物化 dsh 产物到 dsh-dist/)
npm run package
# ⑥ 出分发制品:Windows Squirrel 安装器 / 免安装 ZIP
npm run make
打包产物 out/DeepSeek Harness Desktop-win32-x64/ 已含 dsh(lib + node_modules + web dist + profile),exe 可直接运行。
六、目录结构对照
deepseek-harness-desktop/ # 本工程(Electron 桌面壳)
├── docs/ # 产品概念设计
├── specs/ # 规范文档(8 模块 + 201-dsh-market)
├── patches/ # dsh 上游补丁(git apply)
├── scripts/ # build-dsh.mjs / collect-dsh.mjs / fetch-runtime.mjs
├── profiles/desktop/ # 自定义 desktop profile
├── src/
│ ├── main/ # index.ts / host.ts / windows.ts / tray.ts / notifications.ts ...
│ ├── preload/index.ts
│ └── renderer/renderer.ts # 极薄渲染入口
├── forge.config.ts
└── resources/ # 应用图标、托盘图标
七、Windows 受限网络的 3 个坑
在「GitHub 不可达 / Corepack 受限」的 Windows 环境,npm run package 会踩三个坑。均已验证解法:
坑 1:pnpm 版本不一致
deepseek-harness 锁定 pnpm@11.7.0,但 corepack pnpm --filter 在子工作区解析到旧版本报错:
This project is configured to use 11.7.0 of pnpm
解法——手工建一个调用 corepack 的 pnpm.cmd shim,放到用户可写目录并置于 PATH 前部:
$shimDir = "C:\Users\$env:USERNAME\AppData\Local\pnpm-shim"
New-Item -ItemType Directory -Path $shimDir -Force | Out-Null
$corepackCmd = (Get-Command corepack.cmd).Source
Set-Content -Path (Join-Path $shimDir "pnpm.cmd") -Value @"
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
call "$corepackCmd" pnpm %*
"@ -Encoding ASCII
$env:PATH = "$shimDir;$env:PATH"
坑 2:fetch-runtime 重复下载
首次下载被中断后,runtime/node/ 与 runtime/pnpm/ 已就位但版本戳没写,下次又重下。
解法——确认 runtime/node/node.exe 与 runtime/pnpm/pnpm.exe 存在后,手写版本戳:
'{"node":"24.11.1","pnpm":"9.15.9","platform":"win32","arch":"x64"}' |
Set-Content -Path .\runtime\.versions.json -Encoding ASCII -NoNewline
坑 3:Electron 二进制下载超时
electron-forge package 从 github.com 下载 Electron 二进制,受限网络时报 connect ETIMEDOUT。
解法——改用 npmmirror 国内镜像:
$env:ELECTRON_MIRROR = "https://registry.npmmirror.com/-/binary/electron/"
$env:ELECTRON_CUSTOM_DIR = "v{{ version }}"
npm run package
三者叠加(pnpm shim 入 PATH + 版本戳就绪 + Electron 镜像)即可在受限 Windows 环境稳定跑通
npm run package/npm run make。
八、写在最后
以上就是 Electron 封装 DeepSeek Harness 的完整落地路径:同级 clone → desktop profile → runProfile → 2 个补丁 → 一键构建。踩过的坑(Windows 受限网络三连)也都贴了报错原文和解法。
代码已开源:github.com/fellow99/de…,欢迎 star 支持。
感谢各位关注,欢迎访问我的 GitHub 主页:fellow99.github.io/
下一篇预告:把同一个 dsh 搬上鸿蒙——Electron-on-HarmonyOS 运行时 + 6 个补丁,在鸿蒙真机上跑起 AI Agent。关注我,别错过。