q2224273481-afk
cc-dsh-desktop
DSH 桌面壳
- Stars
- 1
- Language
- TypeScript
- Created
- Aug 16, 2026
- Updated
- Aug 16, 2026
Introduction
dsh-desktop
DSH 桌面壳(Phase 1) — 一个薄壳,把 DeepSeek Harness 的完整 Web 界面与插件生态装进原生桌面窗口。
这是什么
dsh-desktop 是一层薄壳:不重写任何 UI、不 fork 前端、不替代任何插件机制。它只做两件事——
- 在 Electron main 进程里,用
@deepseek-ai/dsh-app-boot的公开 API boot 出与dsh --profile web完全相同的 Cordis 树:同一 bundle 层、同一 patch 层、 同一--patchoverlay、同一 agent-preset 根、同一 telemetry 开关、同一 patch 热更新; - 让一个 sandbox 渲染进程加载原装
@deepseek-ai/dsh-web-frontenddist,指向http://127.0.0.1:<随机端口>。
因此网页端的全部功能与全部拓展能力原样保留——它们都属于 DSH 本体,壳一行都没碰。
✨ 特性
| 🪟 原生桌面窗口 | Electron 窗口承载 DSH 完整 Web 界面,支持托盘驻留、开机最大化 |
| 🧩 100% 插件兼容 | dsh plugin add、patch 层、agent presets、client-plugin HMR、原生目录选择器全部保留 |
| 🎨 主题精修 | 走官方 ThemeRuntime.overrideTokens 扩展点,深浅色双套 token + Windows 原生字体栈 |
| 🖱️ 自绘标题栏 | frameless + Window Controls Overlay,标题栏颜色跟随明暗主题实时切换 |
| 🛟 启动自救 | 插件炸了自动进入 GUI 插件管理器:禁用元凶或安全模式启动 |
| 🔄 核心更新检查 | 启动自动 + 托盘手动检查 DSH 核心(npm 为准),提示后确认才更新 |
| 📦 一键打包 | electron-builder --win 产出 Windows 安装包,原生模块免重建 |
目录
快速开始
前置:Node ≥ 22、npm。(打包时需要 VS Build Tools,见打包。)
npm install
npm run make-icon # 生成 assets/icon.png(托盘 / 打包图标)
npm run smoke # 纯 Node 冒烟:boot 整树 → 校验 HTTP 面 → dispose
npm start # 打开桌面窗口(开发模式)
Electron 冒烟(验证进程内树在 Electron ABI 下完整启动):
npm run smoke:electron
注意:若执行过
npm run rebuild:pty(把 node-pty 重建为 Electron ABI), 纯 Node 冒烟会因 ABI 不匹配失败;用npm rebuild node-pty可切回 Node ABI。 两者是互斥的单份node_modules状态,开发时按需切换。
架构
Electron main(Node 22,进程内)
├─ Cordis host 树 = web profile
│ (dsh-base + dsh-web-app + $DSH_HOME 两层 patch + --patch)
│ ├─ host-webserver @ 127.0.0.1:<OS 随机端口>
│ │ ├─ /api ← fetch / WebSocket(网关)
│ │ └─ /plugins/*/client.js ← 客户端插件包
│ ├─ client-modules → 注入 window.__DSH_BOOT__
│ └─ storage / session / llm / tools / sandbox / agent-presets / …
│ (与 CLI 完全一致)
└─ BrowserWindow(sandbox 渲染进程,原装 dist,无 nodeIntegration)
├─ 客户端 Cordis 树 ← 与浏览器版同一份代码
├─ preload 只暴露 dshDesktop 桥(标题栏颜色回传 + frameless 标记)
└─ dsh-desktop-polish 客户端插件(主题精修,可选)
进程内 boot 的关键决策
| 决策 | 说明 |
|---|---|
安装锚点 = @deepseek-ai/dsh | 与 CLI 完全一致,healProfilesModuleFallback 重建的扁平 fallback($DSH_HOME/profiles/node_modules)逐包相同,新增插件的 peer 依赖解析路径不变 |
--port 0 | 随机端口,从根上消灭 3080 冲突;web-runtime 把实际端口写进 DSH_WEB_URL 与 URL 行 |
| 失败诊断 | installFailLoud 的 stderr/exit 接入壳的日志与退出路径;GUI 启动失败弹窗并指向日志(<userData>/logs/desktop-YYYYMMDD.log) |
| 优雅退出 | before-quit → 树 dispose(5 秒兜底)→ app.exit,镜像 CLI 的 bounded shutdown |
原生模块与 ABI
| 模块 | 使用方 | Electron 兼容性 |
|---|---|---|
node-addon-require-builtin | cordis-plugin-loader | ✅ N-API(免重建) |
koffi | sandbox ACL / fs / 目录选择器 | ✅ N-API |
sharp | attachment 缩略图 | ✅ N-API(0.33+) |
node-pty | dsh-subprocess-local | ✅ N-API(1.1.0 起基于 node-addon-api,免重建) |
web 树内的全部原生模块都是 N-API,Node 与 Electron 双兼容,无需重建
(npm run rebuild:pty 保留为通用工具,仅当某新增插件带非 N-API 原生 addon 时才需要)。
遇到这种插件:对 profile 的 node_modules 跑 electron-rebuild,或用 --backend child
回退到系统 Node(此时原生模块按 Node ABI 正常加载)。
命令行参数
| 参数 | 说明 |
|---|---|
--smoke | boot + HTTP 校验 + 输出 JSON + 退出(0 通过 / 3 失败) |
--backend child | 用系统 Node 以子进程方式跑 dsh CLI(回退模式) |
--home <dir> | DSH_HOME 覆盖 |
--user-data <dir> | Electron userData 覆盖(日志在其下 logs/) |
--cwd <dir> | 树的启动目录(默认用户主目录) |
--port <n> | 固定端口(默认 0 = 系统随机分配) |
--no-tray | 关闭托盘 |
--quit-on-close | 关窗即退出(默认关窗隐藏到托盘) |
--safe | 安全模式启动(跳过全部用户补丁层) |
--plugin-manager | 直接打开插件管理器 |
--headless | 无头启动失败诊断(打印 RECOVERY-JSON 后退出) |
--no-check-update | 关闭启动时的 DSH 核心更新自动检查 |
--update-probe | 无头跑一次更新检查并打印 UPDATE-PROBE-JSON 后退出 |
插件与扩展
安装(dsh plugin add)、组合(bundle reconcile)、解析(双锚点 + 扁平 fallback)、
扫描(dsh.client 花名册)、服务(/plugins/<id>/client.js)五个环节全部复用
DSH 本体,桌面壳只消费 boot 结果。已有插件与新增插件均与浏览器版行为一致;唯一例外
见上文「非 N-API 原生 addon」。
主题精修
plugins/dsh-desktop-polish 是一个纯 client 插件(node 半边为空 apply),走官方
ThemeRuntime.overrideTokens 扩展点覆盖 --dsw-alias-* 语义 token(浅色/深色各一套),
并注入一层 CSS(Windows 原生字体栈 Segoe UI Variable + 渲染优化):
- 品牌强调:主按钮纯黑/纯白 → 墨蓝 slate-indigo;
- 表面层次:浅色模式面板/浮层获得轻柔层级;
- 交互悬停:灰黑 → 淡墨蓝。
安装(profile 级,桌面壳与浏览器版同时生效):
dsh plugin --profile web add ./plugins/dsh-desktop-polish
# 然后在 $DSH_HOME/profiles/web/cordis.patch.yml 里加:
# - insert:
# - id: dsh-desktop-polish
# name: dsh-desktop-polish
迭代即改即生效:插件以 pnpm link 方式挂进 profile,直接编辑
plugins/dsh-desktop-polish/client.js 保存后,patch 热更新 + client 插件图重载会让
运行中的窗口立即套用新 token——无需重启、无需重新构建前端。
壳层观感(src/main/main.ts):启动底色跟随系统主题防白闪、窗口渐显(约 120ms)、
DeepSeek 鲸鱼图标(assets/icon.svg,npm run make-icon 渲染);Windows/Linux 上移除了
Electron 默认菜单栏(File/Edit/View/…)及其快捷键(Ctrl+R/F12 等),窗口只剩纯 UI,
macOS 保留最小默认菜单。
自绘标题栏
窗口为 frameless + titleBarStyle: "hidden" + Window Controls Overlay(原生最小化/
最大化/关闭按钮叠加在页面上,保留 Win11 贴靠布局等原生行为)。顶部 40px 标题栏由
壳注入(src/main/titlebar.ts,dom-ready 时 executeJavaScript):
- 颜色直接取
--dsw-alias-bg-base/--dsw-alias-label-secondary/--dsw-alias-border-l1等语义 token,跟随明暗主题自动切换;文字实时跟随document.title; -webkit-app-region: drag:整条可拖动、双击最大化;- 壳把计算后的主题色经
window.dshDesktop.setTitleBarColors(bg, fg)回传主进程 (preload 桥),主进程据此设置原生按钮的symbolColor与窗口底色,保证按钮与主题同步; - 主进程
TITLEBAR_HEIGHT与插件--dsh-desktop-titlebar-height必须一致(现为 40); 可用--probe无头校验几何参数,--tb-mode切换窗口配置变体(当前默认 d)。
注意:此 Electron 版本(37.x)中,仅
frame:false不启用 WCO(报 “Titlebar overlay is not enabled”),必须同时设置titleBarStyle:"hidden"。
标题栏归壳管、不属于任何客户端插件——即使全部用户插件加载失败,窗口和标题栏依然可用。
启动自救
插件故障分两类,各有对策:
- 客户端插件 bundle 加载失败:内核本就非致命——页面顶部显示 “Failed to load plugins” 横幅,App 照常运行(标题栏不受影响,因为它归壳管)。
- node 侧插件让整树 boot 失败(最常见:补丁里插了装不上/写坏的插件):见下。
恢复对话框
启动失败时弹出,四个选择:
| 选项 | 说明 |
|---|---|
| 重试 | 可能只是暂时性问题 |
| 安全模式启动 | --safe 重启,跳过全部用户补丁层(profile + home 两层 patch),只加载内置 bundle;标题栏标注 “Safe Mode”,内置功能完整 |
| 打开补丁文件 | 用系统编辑器打开 $DSH_HOME/profiles/web/cordis.patch.yml,手动注释掉出错的 insert 行,回来点重试 |
| 退出 | 直接退出 |
- 安全模式实现:
boot.ts的safe选项让loadProfile以userLayer:false加载、 home patch 置空、热重载停用(composeLive同步缩减,保证热更新组合与实际加载一致)。 - boot 另有 60s 看门狗:卡死超时同样进入恢复流程。
- 命令行等效:
electron . --safe;无头自动化用--headless(打印RECOVERY-JSON后退出,便于 CI/诊断)。
插件管理器(GUI)
启动失败时管理器直接弹出(不经过对话框):自动高亮元凶插件,窗口内提供
「安全模式启动」「应用并重试启动」;关闭窗口后才出现恢复对话框兜底。平时也可从托盘
菜单的**插件管理器…**或 --plugin-manager 随时打开:
- 列出两份用户补丁(
profiles/web/cordis.patch.yml与cordis.patch.yml)里的全部 插件:名称、id、来源、insert 行/独立行、启用状态;壳自带插件(如dsh-desktop-polish) 标记「壳自带 · 不可管理」,不提供禁用/启用(IPC 层同样拒绝),避免自救工具误伤壳自身; - 自动高亮启动失败的元凶(从 boot 错误里解析
failed to import loader entry <id> (<name>)), 顶部横幅直接提示「禁用它然后重启」; - 每行一个禁用/启用开关:禁用 = 向补丁文件追加一行
- { id: "...", disabled: true }(幂等、不重写文件、注释原样保留),启用 = 精确删除该行;改动即时生效(运行中的树通过 补丁热更新感知); - 应用并重启一键重启(恢复场景下自动去掉
--safe重试完整启动); - 补丁文件解析失败(手改坏的 YAML)会以红色错误行提示,可一键打开文件修复。
实现:src/main/patch-ops.ts(纯文件操作,含 !!js 占位 schema,node 单测
scripts/pm-ops-test.mjs)+ src/main/plugin-manager.ts(窗口 + pm:* IPC + 本地
http 页;本环境 data:/file: 加载被拦截,http 是唯一通道)。无头演练 --pm-probe:
破坏补丁 → 管理器识别元凶 → IPC 禁用 → 普通启动成功,全链路自动化验证。
DSH 核心更新
壳可以检查 DSH 核心(@deepseek-ai/dsh 等 npm 包)是否有新版本,只提示、确认后才更新,绝不强制:
- 版本源以 npm 为准:读
@deepseek-ai/dsh在 registry 的latestdist-tag(可安装的权威源); deepseek-harness 仓库master分支的apps/cli版本仅作对照展示(可能落后于 npm)。 - 触发:启动时自动静默检查(有新版本才弹窗)+ 托盘菜单「检查 DSH 核心更新…」手动检查。
- UI:一个与插件管理器同款观感的无边框小窗口,显示 当前版本 / 最新版本 / master 分支, 有「更新」「稍后」按钮。
- 更新动作:确认后运行
npm install @deepseek-ai/dsh@<latest> @deepseek-ai/dsh-app-boot@<latest>再提示「重启以生效」。仅 dev 源码模式支持就地更新;打包版按钮会提示重新安装安装包 (完整自动更新属 Phase 2)。 - 关闭自动检查:
--no-check-update;无头诊断用--update-probe。
实现:src/main/update-check.ts(Electron-free:读已装版本 + 查 registry + 语义版本比较,可
用纯 Node 冒烟)+ src/main/updater.ts(窗口 + upd:* IPC + npm 更新动作)。
打包
npm run dist # electron-builder --win → release/
关键配置(electron-builder.yml):
asar: false——healProfilesModuleFallback会把$DSH_HOME/profiles/node_modules以 junction 链接到真实依赖目录,而 junction 目标、原生模块与worker_threads都不能 存在于 asar 归档内;因此全部以真实文件落盘(resources/app)。- 全部原生模块为 N-API →
npmRebuild: false。 electronDist: node_modules/electron/dist—— 复用本地已下载的 Electron,避免打包机 重复下载(离线/受限网络环境适用;正常机器可删掉此行使用默认下载)。
封闭打包运行时的模块解析
打包后配置树在用户目录、依赖树在应用目录,两者之间的桥就是 junction 层。壳在 boot 时做了 三层处理:
bareModuleBaseUrl:把应用自身的node_modules作为裸包名的解析基座传给boot()(上游dsh-app-boot为封闭打包运行时预留的接口),host 侧插件导入不依赖 junction;NODE_PATH+Module._initPaths():client 插件花名册扫描走createRequire(profileDir)的 CJS 解析,NODE_PATH指向应用node_modules让它同样不依赖 junction(_initPaths是因为打包运行时早已缓存过全局路径);- junction heal 降级为 best-effort:正常机器上 heal 照常成功(与 CLI 行为一致,510 个 junction 覆盖完整依赖闭包);heal 失败时(受限环境/杀软)自动钉住 directory-picker 原生 后端、禁用 patch 热更新并打印警告,树仍可用。
已验证矩阵
| 验证 | 结果 |
|---|---|
纯 Node 冒烟(npm run smoke) | ✅ 133 宿主条目 / 38 客户端插件 / 1.6s boot |
Electron dev 冒烟(npm run smoke:electron) | ✅ 同上,Electron 37.2 / Node 22.17 |
子进程回退冒烟(--backend child) | ✅ 捕获 CLI URL 行,38 插件照常服务 |
| GUI 窗口启动 | ✅ 日志确认 [boot] web tree settled,窗口加载 |
| 打包产物冒烟(junction 正常路径) | ✅ 133 / 38 全绿,无警告 |
| 打包产物冒烟(junction 被禁的退化模式) | ✅ 131 / 37 + 2 条降级警告,功能面完整 |
注:「退化模式」行是作者测试沙箱(会拦截未知进程创建 junction)的实测结果;普通用户机器 无此限制,heal 正常执行,走 133/38 的正常路径。
数据与目录
壳的运行数据分三类,落在三处互不重叠的目录:
1. DSH_HOME —— 会话 / 设置 / 插件(DSH 本体数据)
默认 ~/.dsh(Windows:C:\Users\<你>\.dsh),可用 --home <dir> 覆盖。这是 DSH 本体的数据根,与 CLI 完全共享:
~/.dsh/
├─ settings.yaml # 全局设置
├─ .credentials.yaml # 凭据(密钥,勿提交 / 外传)
├─ cordis.patch.yml # home 级用户补丁(未启用过 home 补丁则不存在)
├─ storages/ # 会话、存储等 DSH 持久化数据
└─ profiles/
├─ node_modules/ # heal 出的扁平模块 fallback(junction,自动重建)
└─ web/ # web profile
├─ cordis.yml # profile 根配置(空 entry 列表,勿手改)
├─ cordis.patch.yml # profile 级用户补丁(插件 insert / 禁用行)
├─ package.json # profile 包清单(dsh plugin add 写入)
├─ pnpm-lock.yaml / pnpm-workspace.yaml
└─ node_modules/ # profile 内安装的插件依赖
- 插件补丁:插件管理器(
--plugin-manager)读写的就是profiles/web/cordis.patch.yml与 home 级cordis.patch.yml。 - 换壳不换数据:会话、设置、凭据都在这里,桌面壳与浏览器版 / CLI 同源。
2. Electron userData —— 壳自身日志与缓存
默认 %APPDATA%\dsh-desktop(开发模式;打包后为 %APPDATA%\DSH Desktop),可用
--user-data <dir> 覆盖:
<userData>/
├─ logs/desktop-YYYYMMDD.log # 壳 + 树全部日志(启动失败时看这里)
└─ Cache / Code Cache / Local Storage / … # Electron 浏览器缓存(可随时清)
3. 项目目录 —— 源码 / 构建 / 打包产物
dsh-desktop/
├─ src/ # 源码(tsc → dist/)
├─ dist/ # 编译产物(npm run build 生成,勿手改)
├─ plugins/ # 自带客户端插件(dsh-desktop-polish)
├─ assets/ # 图标(npm run make-icon 重新生成)
├─ scripts/ # 冒烟 / 单测 / 图标生成脚本
├─ release/ # electron-builder 打包产物(npm run dist 生成)
└─ node_modules/ # 依赖(npm install 安装)
开发隔离:本仓库开发时用
--home .dsh-run-home --user-data .electron-userdata把运行数据 钉在项目内(均已 .gitignore),避免污染真实~/.dsh;纯 Node 冒烟则用.dsh-smoke-home作为独立 DSH_HOME。
已知局限(Phase 1)
- 本地 HTTP 端点仍在(
127.0.0.1随机端口),信任面与浏览器版相同;Phase 2 用file://+ IPC 桥移除端口。 - 壳自身 UI(窗口/托盘)暂不在插件树内;Phase 3 以
dsh.desktop双面包并入。 - 无自动更新、无签名/公证;
dsh plugin依赖 pnpm(与 CLI 相同)。 - 壳与 CLI 共享
$DSH_HOME(会话/设置/插件同源,这是设计目标)。
License
MIT © 2026 q2224273481-afk