PRODUCT-PLAN.zh.md 10.0 KB

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:latestnext 同版本)→ 本机已落后,升级需求真实存在
Web 服务 dsh webhttp://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-basedsh-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 Tauri,安装包内置 Node + dsh(Windows) 升级 = 重下安装包,无法实时跟随官方
sdkwork-ai/deepseek-harness-desktop 跨平台安装包,随发版打包 dsh 同上:官方发版到桌面端跟上有时差,且替换了你自己的 npm 安装
本方案 薄壳监督者:不打包 dsh,托管你本机的 npm 安装,通过 npm 原地升级 官方发版 → 桌面端检测(小时级)→ 一键/自动升级 → 重启即最新

2. 产品定位

"薄壳 + 管家":桌面端不复制、不分发、不 fork 任何官方代码。它只做三件事:

  1. 进程托管(Supervisor):后台拉起/守护 dsh web,崩溃自动重启
  2. 窗口(Shell):原生窗口加载 http://127.0.0.1:<port>
  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:<port>,启动期显示 splash(阶段进度)
  • 升级重启后窗口自动 reload;主题跟随系统
  • 快捷键:Cmd+Shift+D 唤起/隐藏(可配置)

Updater(升级管家,核心差异点)

  • 发现npm view @deepseek-ai/dsh dist-tags --cache <独立缓存目录>(独立 cache 规避 ~/.npm 权限坑,已验证可用)。频率:启动时 + 每 6 小时(可配 1h/6h/24h)+ 手动"立即检查"。可选跟随 latestnext tag
  • 对比dsh -V(读本地真实版本)vs registry
  • 执行npm install -g @deepseek-ai/dsh@<tag>(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 / 插件体系,只做转发与展示