# DSH Desktop 产品方案 —— 本地桌面端 + 官方同步升级 > 目标:不用手动开终端跑 `dsh web`,点图标即用;官方一发版就能跟上,永不停留在旧版。 > 本方案基于对本机环境的实际调研(路径、版本、升级链路均已验证),不是泛泛设想。 --- ## 1. 现状诊断(本机已验证的事实) | 事实 | 值 | |---|---| | dsh 安装方式 | npm 全局,prefix = `~/.local`(升级**无需 sudo**) | | 安装位置 | `~/.local/lib/node_modules/@deepseek-ai/dsh`,bin 在 `~/.local/bin/dsh` | | 本机版本 | `0.1.0-rc.6` | | npm 官方最新 | `0.1.1-rc.2`(dist-tags:`latest` 与 `next` 同版本)→ **本机已落后,升级需求真实存在** | | Web 服务 | `dsh web` → `http://127.0.0.1:3080`,支持 `--host` / `--port`(`--port 0` 让 OS 分配空闲端口)/ `--trusted-host` | | 数据目录 | `~/.dsh`(profiles / sessions / settings.yaml / storages / web.log) | | 优雅停机 | dsh 内建 SIGINT/SIGTERM 有界停机(5 秒优雅退出),**可被安全托管** | | 痛点 1 | 无 daemon 模式:必须手动开终端、终端不能关 | | 痛点 2 | 升级全靠手记:要自己记得查 npm、跑 `npm i -g`、重启服务 | | 环境坑 | `~/.npm` 缓存里有 root 属主文件,npm 目前报 EPERM;用独立 `--cache` 目录可绕过(已验证),彻底修复需一次性 `sudo chown -R 501:20 ~/.npm` | ### 关键架构事实(决定升级方案的正确性) - Web UI 的全部官方代码(`dsh-base`、`dsh-web-app` 等几十个 bundle)**都是 CLI 包 `@deepseek-ai/dsh` 的依赖**,profile(`~/.dsh/profiles/web`)只引用 bundle 名,实际解析到 CLI 安装目录的 node_modules。 - 所以:**`npm install -g @deepseek-ai/dsh@latest` + 重启 `dsh web` = 整个 Web UI 立即变成官方最新版**。官方 UI 必须由 `dsh web` 服务注入 `window.__DSH_BOOT__` 才能跑,任何"把 UI 打包进桌面端"的做法都会造成分叉。 - 用户自有插件(如本机的 `dsh-voice-client`,file: 本地依赖)在 profile 自己的 node_modules 里,不受 CLI 升级影响,但 rc 版本间 plugin API 可能不兼容 → 升级后必须做启动冒烟检查 + 可回滚。 - 会话持久化在 `~/.dsh/sessions`(按工作区目录分片),服务重启不丢。 ### 社区已有方案对比(为什么还要自己做) | 方案 | 做法 | 与本需求的差距 | |---|---|---| | [LBurny/deepseek-harness-desktop](https://github.com/LBurny/deepseek-harness-desktop) | Tauri,安装包**内置** Node + dsh(Windows) | 升级 = 重下安装包,无法实时跟随官方 | | [sdkwork-ai/deepseek-harness-desktop](https://github.com/sdkwork-ai/deepseek-harness-desktop) | 跨平台安装包,随发版打包 dsh | 同上:官方发版到桌面端跟上有时差,且替换了你自己的 npm 安装 | | **本方案** | **薄壳监督者:不打包 dsh,托管你本机的 npm 安装,通过 npm 原地升级** | 官方发版 → 桌面端检测(小时级)→ 一键/自动升级 → 重启即最新 | --- ## 2. 产品定位 **"薄壳 + 管家"**:桌面端不复制、不分发、不 fork 任何官方代码。它只做三件事: 1. **进程托管**(Supervisor):后台拉起/守护 `dsh web`,崩溃自动重启 2. **窗口**(Shell):原生窗口加载 `http://127.0.0.1:` 3. **升级管家**(Updater):盯 npm 官方发版,升级你本机的 dsh 并无感重启 一句话:**官方代码永远只有一个来源——npm;桌面端只是让它"开机即在、永不过期"的那层壳。** --- ## 3. 产品形态(macOS) - **菜单栏常驻**(托盘图标,不占 Dock):图标即状态(运行中 / 已停止 / 有新版本小角标) - **主窗口**:加载本地 dsh Web UI,关窗 = 隐藏到托盘(服务继续跑),退出 = 优雅停机 - **托盘菜单**: - 打开 DSH(主窗口) - 状态行:`运行中 · 0.1.1-rc.2 · 端口 3080` - 检查更新 / `⬆ 新版 0.1.2 可用,点击升级` - 重启服务 / 停止服务 - 诊断(日志 tail、会话目录、打开 ~/.dsh) - 开机自启开关、退出 - **通知**:新版本可用、升级完成并已重启、服务异常退出与自动恢复 - **首启引导**:分阶段进度(检测 Node/dsh → 拉起服务 → 就绪开窗),首次使用即修复 `~/.npm` 权限问题 --- ## 4. 架构 ``` ┌────────────────────────────────────────────────┐ │ DSH Desktop(Electron,约 200 行主进程代码) │ │ │ │ ┌──────────┐ ┌────────────┐ ┌───────────┐ │ │ │ Supervisor│ │ Window │ │ Updater │ │ │ │ 进程托管 │ │ BrowserWindow│ │ 升级管家 │ │ │ └────┬─────┘ └─────┬──────┘ └─────┬─────┘ │ └───────┼──────────────┼───────────────┼────────┘ │ spawn/守护 │ load URL │ npm view / npm i -g ▼ ▼ ▼ dsh web --port 3080 http://127.0.0.1:3080 npm registry (官方代码,npm 安装) (官方 UI,随包升级) (@deepseek-ai/dsh) ``` ### Supervisor(进程托管) - `spawn('dsh', ['web', '--port', '3080'])`,detached=false,stdio 管道(日志 → 内存 ring buffer + `~/.dsh/web.log` 落盘) - **就绪探测**:轮询 `GET http://127.0.0.1:3080` 直至 2xx 再显示窗口(避免白屏);`--port 0` + 从 stdout 解析实际端口作为端口冲突时的降级方案 - **端口冲突**:3080 被占时先探测是否已是健康的 dsh → 是则直接 attach 复用;否则换 `--port 0` - **崩溃自愈**:异常退出按指数退避自动重启(1s/5s/30s…),托盘与通知可见 - **优雅停机**:退出时发 SIGTERM,利用 dsh 内建的 5 秒有界停机;超时才 SIGKILL - 单实例锁:二次启动只聚焦已有窗口 ### Window(窗口壳) - `BrowserWindow` 加载 `http://127.0.0.1:`,启动期显示 splash(阶段进度) - 升级重启后窗口自动 reload;主题跟随系统 - 快捷键:`Cmd+Shift+D` 唤起/隐藏(可配置) ### Updater(升级管家,核心差异点) - **发现**:`npm view @deepseek-ai/dsh dist-tags --cache <独立缓存目录>`(独立 cache 规避 `~/.npm` 权限坑,已验证可用)。频率:启动时 + 每 6 小时(可配 1h/6h/24h)+ 手动"立即检查"。可选跟随 `latest` 或 `next` tag - **对比**:`dsh -V`(读本地真实版本)vs registry - **执行**:`npm install -g @deepseek-ai/dsh@`(prefix `~/.local`,无需 sudo,不动你的 profile 与插件) - **生效**:SIGTERM 优雅停机 → 重新 spawn → 就绪后窗口 reload → 托盘通知 `已升级到 0.1.2` - **冒烟与回滚**:升级后若服务 60 秒内起不来(rc 版本可能有破坏性变更或本地插件不兼容),自动 `npm i -g @deepseek-ai/dsh@<旧版本>` 回滚并通知 - **策略**:默认"发现即提示、一键升级";可开"自动升级"(检查到新版直接升,全程通知可溯) - **版本史**:设置页保留最近 5 个版本号,随时手动切换(本质就是 `npm i -g` 指定版本) --- ## 5. 技术选型 | 维度 | Electron(推荐 MVP) | Tauri 2 | |---|---|---| | 语言 | 全 JS,与 dsh 生态一致 | 主进程 Rust,需 Rust 工具链 | | 体积/内存 | ~90MB / ~150MB | ~10MB / ~80MB | | 托盘/窗口/子进程 | 全部成熟 API,~200 行搞定 | 需在 Rust 侧写进程监督 | | 迭代速度 | 快(你本人可维护) | 慢 | **推荐:Electron 起步。** 壳极薄(三个模块),日后若嫌重,迁移 Tauri 的成本也很低——因为所有复杂度都在 dsh 侧,壳里没有业务逻辑。Tauri 版可作为二期优化。 --- ## 6. 路线图 ### P0 —— 能用(1~2 天) - [ ] Electron 工程 + 单实例 + 托盘 + 开机自启 - [ ] Supervisor:拉起 `dsh web`、就绪探测、崩溃退避重启、优雅退出 - [ ] 主窗口加载 `127.0.0.1:3080`,splash 引导,关窗隐藏 - [ ] 首启环境体检:Node/dsh 存在性、`~/.npm` 权限检测与修复引导 ### P1 —— 永不过期(1~2 天) - [ ] Updater:版本轮询、新版本通知、一键升级 + 无感重启 - [ ] 升级冒烟失败自动回滚;版本史手动切换 - [ ] 独立 npm cache 目录(根治 EPERM) ### P2 —— 好用(按需) - [ ] 诊断面板:实时日志(tail `~/.dsh/web.log`)、端口/PID、一键重启 - [ ] 插件面板:转发 `dsh plugin --profile web add/remove/update`,流式输出 - [ ] 多 Profile 切换:web / tui / headless 一键切换或并存 - [ ] 会话快捷入口:按工作区列出 `~/.dsh/sessions` 最近会话,直达恢复 - [ ] 手机远程访问(Cloudflare Quick Tunnel + token 门禁,参考社区实现) - [ ] Tauri 瘦身版 / 上游贡献 `dsh desktop` 子命令 --- ## 7. 风险与对策 | 风险 | 对策 | |---|---| | rc 版本破坏性变更(官方明确 developer preview 会有) | 升级后启动冒烟 + 自动回滚;保守用户可锁版本不自动升 | | 本地插件(dsh-voice-client)与新版本 API 不兼容 | 同上冒烟回滚;日志面板能直接看到插件加载错误 | | 3080 端口冲突 | 先探测 attach 健康实例,否则 `--port 0` 动态端口 | | `~/.npm` root 属主文件(当前真实存在) | 首启检测 + 引导一次性 `sudo chown`;日常升级走独立 cache | | npm 网络失败 | 升级是幂等的 `npm i -g`,失败保留旧版运行中,仅提示重试 | | 用户终端里已有 `dsh web` 在跑 | 端口探测 attach,不重复拉起 | ## 8. Non-goals(明确不做) - 不 fork / 不内嵌官方 UI 代码(保证"随官方升级"永远成立的前提) - 不做账号、云同步、多用户 - 不替代 dsh 的 profile / 插件体系,只做转发与展示