chore(工作区): 纳入版本控制基线(回收 411 MB 过程产物)

回收 411 MB(470 M → 58.8 M),全部经回收站,可恢复:
- 待清理/(146.2 M,含 relay 分片 128 M 与 42 项过程目录)
- tmp/(32.4 M,按接续棒命名的过程临时区)
- .workbuddy/tmp/(39.5 M)
- 4 份 workbuddy.db 冗余副本(101 M,09-23 事故的坏副本 / 抢救产物)
- tmp/im16/gw/centrifugo 二进制(63.9 M,可重下)+ 缓存残留

入库范围:常驻规则(CODEBUDDY.md / README.md / state.py)、在途接续入口与
接续包、docs/、交付物/、交接单/、归档/、scripts/、.codebuddy/、
.workbuddy/memory/;共 398 件,其中 >60 KB 的 26 件全为文档。

排除(.gitignore):tmp/、待清理/、运行态日志与缓存、*.db 与 DB 备份整目录、
打包二进制(*.tar.gz / *.tgz)、记忆修复前备份。
This commit is contained in:
admin committed 2026-09-24 07:51:03 +08:00
commit ce8e6ceed9
396 files changed
+66045

No files matched your search

@@ -0,0 +1,331 @@
# DSH 桌面客户端开发方案 —— 基于官方 Electron 壳迭代
- 版本:**v1 规划稿**(2026-09-16)
- 状态:⏳ **待评审**(本文只做规划,不含代码改动)
- **分层指针(2026-09-16 收口)**:本文管**交付载体层**(壳 / 打包 / 签名 / 自动更新);**平台侧**(Windows 子进程启动适配、形态开关)以 `dsh客户端化部署方案_20260916.md` 为准
- 上游:`deepseek-ai/deepseek-harness` 的 `apps/desktop`(Electron 壳)+ `apps/desktop-host`(Node 宿主)
- 上游基线:`master` 分支,`apps/desktop` 版本 `0.1.6-alpha.1`(11239 个文件的全仓快照)
- 上游定位提醒:官方自称 **developer preview**,明写「THERE WILL BE COMPATIBILITY-BREAKING CHANGES」⇒ 同步机制必须按"会变"来设计
---
## 0. 一句话方案
**以官方 Electron 壳为骨架,换掉它的内核** —— 保留启动生命周期、单实例锁、失败恢复页、自动更新、Windows 打包链;
把"启动内置 dsh 运行时 + 插件管理"替换为"**启动我们自己的平台进程,窗口加载它**"。
产出一个**独立仓库**的桌面客户端,用户双击打开即用。
---
## 1. 目标形态
| 项 | 目标 |
|---|---|
| 交付物 | Windows 安装包(`Setup.exe`),双击安装,桌面快捷方式启动 |
| 运行态 | Electron 主进程 → 拉起平台子进程(本机回环)→ 窗口加载平台页面 |
| 对外暴露 | **仅本机回环**(沿用 v3 单机自用形态) |
| 数据 | 平台数据目录(默认用户目录下),与官方 CLI/桌面版**互不干扰** |
| 与平台的关系 | 客户端只是平台的**启动器 + 窗口**;平台机制一行不改(除 §5 那一处) |
---
## 2. 官方壳解剖:取什么、换什么、丢什么
### 2.1 官方壳的组成
| 部分 | 内容 |
|---|---|
| `apps/desktop` | 100 个文件:`src/`(主进程逻辑)、`renderer/`(启动页 + 插件管理 UI)、`scripts/`(打包链)、`tests/`、`electron-builder.config.mjs` |
| `apps/desktop-host` | **仅 6 个文件**:`src/index.ts`、`src/wire.ts`、`config/desktop.cordis.patch.yml` + 3 个配置 |
| 运行时 | `resources/dsh` 内置一份完整的 dsh 与依赖树(打包时生成) |
| 通信 | **不开监听端口**,用"分帧字节管道"承载 Fetch 与流式响应;`dsh-app://` 服务客户端资源 |
> 关键认识:官方壳的**复杂度几乎全部服务于"内置 dsh 运行时"**(包管理、profile 独占、管道传输、插件生命周期)。
> 我们只要"拉起一个本机 HTTP 服务 + 开个窗口",**这些复杂度大多可以直接不背**。
### 2.2 逐模块处置
| 模块 | 官方作用 | 处置 |
|---|---|---|
| `src/main.ts` | Electron 生命周期、窗口、自定义协议、应急页 | **保留骨架**,改"加载目标"与"启动什么" |
| `src/backend-controller.ts` | 后端状态机(启动/停止/恢复) | **换内核** → 管理平台进程 |
| `src/single-instance.ts` | 进程级单实例锁 | ✅ **原样保留** |
| `src/startup-document.ts` / `startup-error.ts` | 启动失败页(自带诊断与恢复动作) | ✅ **保留并改编**(文案换成我们的) |
| `src/locale.ts` / `src/ipc.ts` / `src/paths.ts` / `src/preload*.ts` | 本地化 / IPC / 路径 | ✅ **保留** |
| `src/update-coordinator.ts` | 自动更新 | ✅ **保留**,更新源指向我们自己的通道 |
| `src/host-process.ts` / `host-protocol.ts` | 启动 Node 宿主 + 管道协议 | ❌ **删**(我们走 HTTP,不需要管道) |
| `src/project-manager.ts` / `profile-packages.ts` / `runtime-tree.ts` / `core-package-set.ts` / `owned-directory.ts` | profile 与运行时包管理 | ❌ **删或大幅简化**(运行时由平台负责) |
| `renderer/plugin-manager.*` | 插件管理器 UI | ❌ **删**(插件由平台的「功能管理」管) |
| `renderer/startup.*` | 启动加载页 | ✅ **保留并改编** |
| `scripts/package-target.ts` / `windows-sign.mjs` / `installer.nsh` / `smoke-windows.ps1` / `desktop-build-paths.mjs` / `desktop-release-environment.mjs` | **Windows 打包链**(electron-builder + NSIS + 签名 + 冒烟) | ✅ **保留 —— 这是最值得复用的部分** |
| `scripts/prepare-dsh.ts` / `prepare-runtime.ts` / `prepare-package-set.ts` / `runtime-file-policy.ts` / `macos-runtime.ts` | 为内置运行时准备文件 | ❌ **删**(改为准备我们的平台产物) |
| `electron-builder.config.mjs` | 打包配置(appId、签名、自动更新、NSIS) | ✅ **保留**,改 appId / productName / 更新源 |
| `tests/**` | 官方自己的测试(大量针对内置运行时) | 🟡 **按新内核重写**;与打包链相关的保留 |
### 2.3 改造后的架构
```
Electron 主进程
├─ 单实例锁(官方组件,原样)
├─ 启动页窗口(官方组件,改编)
├─ 平台进程控制器(替换 backend-controller)
│ └─ spawn 平台 → 轮询 127.0.0.1:<port> 就绪
├─ 主窗口 → 加载 http://127.0.0.1:<port>
├─ 失败恢复页(官方组件,改编)
└─ 自动更新协调器(官方组件,换更新源)
```
---
## 3. 代码仓库方案(**本节回答"要不要单独一个仓库"**)
### 3.1 判定
✅ **需要独立仓库。** 命名建议 `dsh-desktop`(或 `dsh-client`)。
### 3.2 五条理由
| # | 理由 |
|---|---|
| 1 | **发布物与节奏不同**:桌面客户端有自己的版本号、安装包、签名、自动更新通道;与平台服务的发版完全不同步 |
| 2 | **上游要持续同步**:fork 官方代码后必须能跟官方更新(官方明说会破坏性变更)—— 独立仓库才能把"官方代码"与"我们的改动"分开管理 |
| 3 | **红线 R2 的边界要清晰**:桌面壳是**官方源码的衍生品**。放进平台仓库,会让"我们自研的平台"与"官方衍生代码"混在一棵树里,合规边界与代码归属都变模糊 |
| 4 | **依赖形态差异大**:桌面端重 devDeps(Electron 44 / electron-builder / AWS SDK / TS 6),平台是轻量 Node 服务 —— 混在一起会拖慢平台 CI、放大依赖面 |
| 5 | **安全与信任模型不同**:桌面端跑在**用户电脑**上,平台跑在**服务器**上;两者的权限、密钥、发布审批要求不一样 |
### 3.3 可行性的关键证据(依赖能否在独立仓库解析)
这是"能不能独立"的硬前提,已核实:
| 项 | 事实 |
|---|---|
| `apps/desktop` 的 `dependencies` | **只有 2 个,且都是公开包**:`electron-updater`、`semver` |
| `apps/desktop` 的 `devDependencies` | 16 个,其中 **只有 1 个** 是 monorepo 内部引用(`workspace:^`):`@deepseek-ai/dsh-home-paths` |
| 上述那个包是否已发 npm | ✅ 已发布(`@deepseek-ai/[email protected]`) |
| `@deepseek-ai/dsh-desktop` / `dsh-desktop-host` 本身 | `private: true`,**未发布到 npm** ⇒ **必须 fork 源码,不能直接依赖** |
> **结论**:独立仓库里把唯一那个 `workspace:^` 换成 npm 版本即可,其余全是公开包。
> **依赖层面无障碍** —— 这是本方案成立的关键证据。
### 3.4 上游同步机制
**目标**:官方更新能进来,我们的改动不丢、冲突可控。
**做法(推荐:基线快照 + 补丁清单)**:
1. 仓库内设 `upstream-baseline/` —— 存放 fork 时刻的官方源码快照,**只读**,带上游 commit hash 与版本号
2. 仓库内设 `patches/` —— 存放**我们对官方文件的每一处改动**,一个改动一个 patch 文件
3. 我们的新增代码(平台进程控制器等)放在 `src/` 下**独立文件**,不修改官方文件 —— 从源头减少冲突
4. 同步流程:
```
取官方新版 → 放到 upstream-baseline-new/ → diff 两个 baseline
→ 人工判断哪些官方改动要跟进 → 重放 patches/ → 冲突处处理 → 更新 baseline + 文档
```
5. **判别原则**:能在**新增文件**里做的事,绝不改官方文件;必须改的,一律进 `patches/` 并写明理由
> 备选做法(若改动最终很少):只维护一份 **`DEVIATIONS.md` 改动清单** + 手工同步,省掉 patch 的机械开销。
> 采用哪一档,等第一轮改造完、看到实际改动面再定。
---
## 4. 关键技术设计
### 4.1 启动流程
```
1. 取得单实例锁(官方组件)—— 已有实例则聚焦它的窗口并退出
2. 显示启动页(官方组件,改编文案)
3. 解析平台启动参数(端口、数据目录、命令路径)
4. spawn 平台进程,捕获 stdout/stderr
5. 轮询 127.0.0.1:<port> 直到就绪(带超时)
6. 就绪 → 关闭启动页 → 打开主窗口加载平台地址
7. 失败/超时 → 显示失败页(含诊断信息与"重启"按钮)
```
### 4.2 平台进程的生命周期
| 事件 | 处置 |
|---|---|
| 窗口全部关闭 | 默认行为待定(见 §9 未决项) |
| 用户退出应用 | 先优雅终止平台进程,再退出 Electron |
| 平台进程意外退出 | 尝试拉起一次;连续失败 → 显示失败页 |
| 应用被强杀 | 下次启动时清理上一次残留的进程(记录 pid 文件) |
| 端口被占 | 改为探测可用端口(**不要**写死 3080) |
### 4.3 就绪探测
- 轮询本机回环端口,**不要**用"端口能连上"当就绪判据(TCP 可连不代表服务可用)
- 用一个轻量健康检查判断真正就绪
- 设总超时,超时给出可操作的失败页
### 4.4 数据目录与配置
| 项 | 设计 |
|---|---|
| 平台数据目录 | 应用私有目录(Windows: `%APPDATA%\<appname>`),**不要**与用户手动安装的平台混用 |
| 平台端口 | 每次启动探测可用端口,写进启动参数 |
| 平台命令 | 打包进应用资源里,不依赖用户系统上的 Node |
| 与官方桌面版/CLI 的关系 | **互不干扰**:官方桌面版独占它自己的 profile;我们的客户端用独立数据目录 |
### 4.5 失败恢复
沿用官方壳的三档恢复动作骨架:
| 动作 | 我们的含义 |
|---|---|
| 重启 | 杀掉平台进程后重新拉起 |
| 重置 | 清掉平台数据目录(**需二次确认**,会丢会话) |
| 查看日志 | 打开日志目录(官方没有这一档,建议新增 —— 用户遇到问题第一件事就是找日志) |
### 4.6 自动更新
- 官方用 `update-coordinator.ts` + `electron-updater`,配置由 `resolveDesktopAutoUpdateConfig` 从环境解析
- 我们要做的:把更新源指向自己的通道;更新前**先停平台进程**
- ⚠️ 官方 README 提到签名/公证/更新托管"需要生产发布环境" ⇒ 自动更新是**发布期**才需要的能力,开发期可以先关掉
---
### 4.7 形态开关:从单机自用到多人访问(**零改动保留**)
客户端**默认**按单机自用启动(不配域名、平台监听回环)。要把它当服务器给多人用,**不需要改客户端代码** —— 只需让它把域名配置**透传**给平台:
| 档 | 做法 | 改动量 |
|---|---|---|
| **最小** | 用户在系统里设环境变量(平台读 `DSHS_BASE_DOMAIN` / `DSHS_COOKIE_DOMAIN`),客户端 spawn 平台时**原样继承** | **0** |
| 更好(该做项) | 客户端提供一个"服务设置"入口:填域名 → 写进平台配置 → 重启生效 | 小 |
| 配套(用户侧) | 内网 DNS 泛解析 `*.<域名>` → 本机地址 | 用户操作 |
- ⚠️ **改为"白名单透传"(2026-09-16 修正)**:原表述「不要清理环境变量、也不要白名单化」会把**平台级凭据**(共享模型密钥等)一并继承给一个装到用户机器上的进程。**正确做法 = 只放形态开关三项**(`DSHS_BASE_DOMAIN` / `DSHS_COOKIE_DOMAIN` / `DSHS_SECURE_COOKIES`),其余不放行 —— 既保住"零改动切多人",又不把平台凭据发出去
- ⚠️ 多人形态下隔离档位仍是默认 `soft`(用户之间无隔离),与部署方案 §1 的取舍一致
- ⚠️ 不需要 HTTPS 也能跑(同 site 下的 `SameSite=Lax` 足够),但**有 HTTPS 更规范**
---
## 5. 平台侧配套改动
桌面客户端要能跑起来,平台侧**必须**先解决一处(详见客户端化部署方案 §4.1,已实测):
| # | 事项 | 说明 |
|---|---|---|
| P1 | **Windows 子进程启动适配** | 平台现在用不带 shell 的方式启动实例,Windows 上会 `ENOENT` / `EINVAL`;必须走 `cmd.exe /c` 或 `shell: true` |
| P2 | 安装包运行方式确认 | 平台以什么形态打包进客户端资源(源码 + node_modules / 预编译产物),需与实际打包方式对齐 |
| P3 | 端口可配置 | 客户端要探测可用端口 ⇒ 平台需支持任意端口启动(当前已支持 `--port`) |
> 这三条都在**平台仓库**里改,与桌面客户端仓库分开 —— 这也是独立仓库的好处之一。
---
## 6. 打包与发布
### 6.1 可复用的官方打包链
| 文件 | 作用 |
|---|---|
| `electron-builder.config.mjs` | 打包配置工厂(appId / 签名 / 自动更新 / NSIS) |
| `scripts/package-target.ts` | 打包入口(按 target 打包) |
| `scripts/desktop-build-paths.mjs` | 各 target 的输出路径 |
| `scripts/desktop-release-environment.mjs` | 从环境变量解析发布参数 |
| `scripts/windows-sign.mjs` + `installer.nsh` | Windows 签名与 NSIS 安装器定制 |
| `scripts/smoke-windows.ps1` | Windows 原生冒烟(需要 Electron / Makensis / 7-zip 路径) |
### 6.2 Windows 签名需要准备的东西
官方配置从环境变量读取,意味着**签名是外部依赖,需要提前准备**:
| 环境变量 | 含义 |
|---|---|
| `DSH_DESKTOP_WINDOWS_CER_FILE` | 证书文件 |
| `DSH_DESKTOP_WINDOWS_SIGNTOOL` | 签名工具路径 |
| `DSH_DESKTOP_WINDOWS_TOKEN_PIN` | 令牌 PIN |
| `DSH_DESKTOP_WINDOWS_KEY_CONTAINER` | 密钥容器 |
| `DSH_DESKTOP_UNSIGNED` | 置 1 出**未签名**包(仅 Windows,**用于内部测试**) |
> ✅ 好消息:官方支持 `package:win:x64:unsigned` ⇒ **开发与内测完全不需要证书**,出正式包才需要。
### 6.3 应用标识
`appId` 与 `productName` 由 `resolveDesktopAppId(env)` 从环境解析 ⇒ 属于**配置项**,不需要改代码。
---
## 7. 开发里程碑
| 阶段 | 目标 | 验收 |
|---|---|---|
| **M0 打通** | 最薄的路:Electron 壳 + spawn 平台 + 窗口加载 | 本机双击启动,能看到平台页面 |
| **M1 生命周期** | 进程管理、就绪探测、退出收尾、崩溃恢复 | 反复启停 20 次无残留进程;拔掉平台进程能自动恢复或给失败页 |
| **M2 体验** | 启动页、失败页含日志入口、单实例、数据目录 | 二次启动聚焦已有窗口;失败页能一键看日志 |
| **M3 打包** | 出未签名 Windows 安装包 | 干净机器上安装 → 启动 → 可用 |
| **M4 发布** | 签名包 + 自动更新通道 | 安装 → 自动更新到新版本成功 |
> **M0 是关键**:它同时验证桌面壳改造与平台侧 P1(Windows 子进程适配)两件事。
---
## 8. 验收标准
| # | 验收项 | 判据 |
|---|---|---|
| V1 | 安装即用 | 干净 Windows 机器上装包后,双击可用,无需预先装 Node |
| V2 | 无残留进程 | 退出应用后,平台进程不残留 |
| V3 | 异常可恢复 | 平台进程被杀 → 应用能恢复或给出可操作的失败页 |
| V4 | 单实例 | 重复启动只聚焦已有窗口,不起第二个平台 |
| V5 | 数据隔离 | 与官方桌面版/CLI 互不干扰,各自数据目录独立 |
| V6 | 卸载干净 | 卸载后程序目录清空(用户数据是否保留需明确策略) |
| V7 | 打包链可复现 | 在干净构建机上能按文档打出安装包 |
---
## 9. 风险与未决项
### 9.1 风险
| # | 风险 | 等级 | 应对 |
|---|---|---|---|
| R1 | 上游快速迭代导致同步成本高 | 🔴 高 | 改动集中在新增文件 + `patches/` 留痕;官方破坏性变更时评估"跟 or 不跟" |
| R2 | Electron 体积大(安装包通常 80–150 MB) | 🟡 中 | 若体积敏感,评估 Tauri 等替代;但会失去官方打包链的复用价值 |
| R3 | 无签名证书时用户会看到安全警告 | 🟡 中 | 内测用未签名包;正式交付需采购代码签名证书 |
| R4 | 平台进程的跨平台启动方式还需实测 | 🟡 中 | M0 阶段同时验证 §5 的 P1 |
| R5 | 官方桌面版若正式发布,我们的定位会变 | 🟡 中 | 定期评估"是否可以直接用官方版 + 我们的插件" |
### 9.2 未决项(**开发前需要定**)
**D1 · 关闭窗口时的行为**
甲:退出应用并停掉平台进程 —— 优点:干净、不留后台;缺点:再次使用要重新启动。
乙:最小化到系统托盘,平台继续跑 —— 优点:随时可用,AI 长任务不被中断;缺点:常驻后台占内存。
**D2 · 客户端与官方桌面版的关系**
甲:完全独立(独立数据目录、独立产品名)—— 优点:互不干扰;缺点:用户装了官方版会有两套。
乙:共享数据目录 —— 优点:会话/设置互通;缺点:要处理与官方版的 profile 冲突(官方版独占它自己的 profile)。
**D3 · 是否保留"插件管理"界面**
甲:删掉,插件全部由平台的「功能管理」管 —— 优点:单一入口、与平台机制一致;缺点:客户端内少一个入口。
乙:保留官方那套插件管理器 —— 优点:复用现成 UI;缺点:与平台的插件机制并存会产生两条路径,容易互相打架。
---
## 附录 A · 证据与复核方式
| 结论 | 来源 |
|---|---|
| 桌面壳 = Electron + 内置运行时 + 私有宿主进程 | `apps/desktop/package.json`(description)+ `apps/desktop/README.md` |
| 不开监听端口、用字节管道 | 官方 README 首段 |
| `apps/desktop` 100 个文件 / `apps/desktop-host` 6 个文件 | GitHub 全仓 tree 快照 |
| `dependencies` 仅 2 个公开包 | `apps/desktop/package.json` |
| `devDependencies` 仅 1 个 `workspace:^` | 同上 |
| 该包已发 npm | npm registry `@deepseek-ai/dsh-home-paths` |
| `@deepseek-ai/dsh-desktop` 为 `private` | 同上 `package.json` |
| 打包链文件清单 | `apps/desktop/scripts/**` |
| Windows 签名靠环境变量 | `electron-builder.config.mjs` |
| 支持未签名包 | `package.json` 的 `package:win:x64:unsigned` |
| 官方宣称会破坏性变更 | 官方 README「Developer preview」段 |
## 附录 B · 需要 fork 的官方文件清单(按处置分组)
| 分组 | 文件 |
|---|---|
| **保留**(含打包链) | `electron-builder.config.mjs`、`scripts/package-target.ts`、`scripts/desktop-build-paths.mjs`、`scripts/desktop-release-environment.mjs`、`scripts/desktop-auto-update-environment.mjs`、`scripts/windows-sign.*`、`scripts/installer.nsh`、`scripts/smoke-windows.ps1`、`scripts/macos-runtime.ts`、`scripts/package-macos.ts`、`scripts/verify-macos-signature.*`、`scripts/notarize-macos-disk-images.*`、`src/single-instance.ts`、`src/locale.ts`、`src/ipc.ts`、`src/paths.ts`、`src/preload*.ts`、`src/update-coordinator.ts`、`renderer/startup.*` |
| **改**(换内核/改编) | `src/main.ts`、`src/startup-document.ts`、`src/startup-error.ts`、`src/backend-controller.ts` |
| **删**(为内置运行时服务) | `src/host-process.ts`、`src/host-protocol.ts`、`src/project-manager.ts`、`src/profile-packages.ts`、`src/runtime-tree.ts`、`src/core-package-set.ts`、`src/owned-directory.ts`、`src/release.ts`、`renderer/plugin-manager.*`、`scripts/prepare-dsh.ts`、`scripts/prepare-runtime.ts`、`scripts/prepare-package-set.ts`、`scripts/runtime-file-policy.ts`、`apps/desktop-host/**` |
| **重写** | `tests/**` 中针对内置运行时的部分 |