anywhere-labs_deepseek-harness-desktop 如何围绕上游演进:Submodule、版本溯源与非 Fork 架构
anywhere-labs/deepseek-harness-desktop 如何围绕上游演进:Submodule、版本溯源与非 Fork 架构
摘要
作为维护过上游依赖、企业发行版和社区插件生态的开发者,我认为“基于某个开源项目做产品”最难的并不是第一次把功能跑起来,而是半年后还能准确回答三个问题:哪些代码属于上游、产品实际运行的是哪个版本、产品增强是否已经悄悄变成无法合并的长期分叉。本文专门从开源治理与架构所有权角度分析社区仓库 anywhere-labs/deepseek-harness-desktop。它围绕官方 deepseek-ai/deepseek-harness 构建桌面产品,却没有把上游源码复制到自己的普通目录里随意修改:deepseek-harness/ 是固定官方 URL 与精确 commit 的只读 Git submodule,桌面功能由 dsh-plugin-desktop/ 独立拥有;外层使用 Yarn 4 与 node_modules linker,上游继续保持自己的 pnpm workspace;正常产品构建消费 npm 发布的 DSH package family,而不是把未发布的子模块源码 link 进产品。更值得注意的是,仓库没有假定“源码标签与 npm artifact 必然一一对应”,而是在 upstream.json 中分别记录源码 commit、source version 和 runtime package version,当前公开源码为 rc.5、运行时包族为 rc.6,这种不强行制造对应关系的做法比一个看似整齐、实际不可证明的版本号更诚实。产品能力也遵守同样边界:兼容模式必须运行上游默认 Client,不做 Desktop presentation override;高级模式只能通过 Profile composition 替换文档化的 layout/root seam,继续复用官方 sidebar、conversation 与 Web carrier;第三方集成通过 Desktop 自有 service contract 完成,而不是修改上游 Profile、CLI 或 Loader。本文会结合仓库拓扑、版本记录、包管理器隔离、策略即代码检查与升级流程,解释如何围绕快速演进的开源核心构建产品,同时保留审计、更新和回退能力。
项目身份说明:本文研究的是社区项目
anywhere-labs/deepseek-harness-desktop的治理方式。它基于 DeepSeek Harness,但不是 DeepSeek 官方仓库或官方产品。

图1 Desktop 是围绕官方 Harness 构建的产品层,而不是另一份核心实现
一、为什么“复制上游再修改”迟早会失控
最直接的桌面化方式,是复制上游 Web/Host 源码到产品仓库,改窗口、布局、命令和打包。短期不用设计接口,长期却会积累不可见的 fork debt:上游安全修复无法直接比较;产品提交同时包含官方代码和自有改动;合并冲突无法判断是业务差异还是格式漂移;发布包依赖本地 monorepo 偶然布局;贡献者也不知道问题应该提交到哪个仓库。
| 做法 | 短期收益 | 长期代价 |
|---|---|---|
| 复制源码 | 改动最快 | 归属模糊、同步困难 |
| Git subtree/vendor snapshot | 单仓库方便 | 上游仍表现为可编辑普通文件 |
| 直接 link 上游 workspace | 开发调试方便 | 产品依赖未发布源码布局 |
| 修改上游包管理器 | 统一命令 | 破坏官方 lockfile 与可比性 |
| 固定 submodule + 产品插件层 | 需要先设计边界 | 所有权、溯源和升级清晰 |
DSH Desktop 选择最后一种:把上游当作可审查但不可随桌面分支修改的输入,把产品差异放在明确拥有的包中。
二、仓库所有权地图
flowchart TD
ROOT["anywhere-labs/deepseek-harness-desktop"] --> DOC["外层 README / docs / assets<br/>产品叙事与用户文档"]
ROOT --> DESK["dsh-plugin-desktop/<br/>Host + Client + Electron + Packaging"]
ROOT --> META["upstream.json / .gitmodules<br/>来源与版本事实"]
ROOT --> SUB["deepseek-harness/<br/>只读 Git Submodule"]
SUB --> OFF["deepseek-ai/deepseek-harness<br/>官方源码"]
DESK --> PUB["npm 发布的 DSH Package Family"]
classDef product fill:#2563eb,color:#fff,stroke:#1d4ed8,stroke-width:2px;
classDef metadata fill:#f59e0b,color:#111827,stroke:#d97706,stroke-width:2px;
classDef upstream fill:#10b981,color:#fff,stroke:#047857,stroke-width:2px;
classDef artifact fill:#8b5cf6,color:#fff,stroke:#6d28d9,stroke-width:2px;
class ROOT,DOC,DESK product;
class META metadata;
class SUB,OFF upstream;
class PUB artifact;
图2 所有权地图:产品代码、来源记录、官方源码和运行时包彼此分离
| 区域 | 所有者 | 允许的变化 |
|---|---|---|
| 根 README/docs/assets | Desktop 产品 | 产品定位、用户指南、架构文档 |
dsh-plugin-desktop/ |
Desktop 产品 | Electron、Cordis faces、发布测试 |
deepseek-harness/ |
官方上游 | 桌面分支禁止直接修改 |
upstream.json |
Desktop 溯源记录 | 独立记录 pin 与 runtime family |
.agents/notes/implemented/ |
架构决策 | 与实现和校验保持同步 |
这张地图让代码评审可以先问“改动是否落在正确所有者”,再讨论实现质量。若一个桌面需求要求直接编辑 submodule,说明当前扩展 seam 可能不足,应先评估上游贡献或 Desktop adapter,而不是默认越界。
三、Submodule Pin 解决的是可比性
.gitmodules 固定上游仓库地址,外层 gitlink 固定精确 commit,upstream.json 再记录可读元数据:
{
"repository": "https://github.com/deepseek-ai/deepseek-harness.git",
"commit": "47f943859bef60e4160492346772ded9b24f765a",
"sourceVersion": "0.1.0-rc.5",
"runtimePackageVersion": "0.1.0-rc.6"
}
Submodule 的价值不只是减少仓库体积,而是官方 checkout 可以直接执行 git status、git rev-parse HEAD 和 remote 比对。只要工作树为空、commit 与 URL 相符,审计者就知道这棵源码没有混入 Desktop patch。
flowchart LR
A[".gitmodules 官方 URL"] --> V["Layout Verifier"]
B["Gitlink Commit"] --> V
C["Submodule HEAD/Status/Remote"] --> V
D["upstream.json"] --> V
V --> E{"所有事实一致?"}
E -- "是" --> F["上游源码可比较"]
E -- "否" --> G["检查失败,拒绝发布"]
classDef fact fill:#2563eb,color:#fff,stroke:#1d4ed8,stroke-width:2px;
classDef verify fill:#f59e0b,color:#111827,stroke:#d97706,stroke-width:2px;
classDef pass fill:#10b981,color:#fff,stroke:#047857,stroke-width:2px;
classDef fail fill:#ef4444,color:#fff,stroke:#b91c1c,stroke-width:2px;
class A,B,C,D fact;
class V,E verify;
class F pass;
class G fail;
图3 上游身份验证:URL、gitlink、checkout 与元数据必须同时一致
更新上游时,应在独立提交中修改 gitlink 与元数据,而不是和桌面行为改动揉在一起。这样回归出现时可以判断是“上游 pin 变化”还是“Desktop adapter 变化”。
四、为什么 Source Version 与 Runtime Version 分开
桌面仓库保留一份可审查的官方源码,但正常产品构建从 npm registry 安装已发布 DSH package family。当前公开 submodule 的 package version 是 0.1.0-rc.5,产品依赖使用 0.1.0-rc.6。如果 npm artifact 没有公开对应源码 commit,仓库不能凭版本字符串臆造一个来源。
| 事实 | 当前记录 | 用途 |
|---|---|---|
| 官方仓库 | deepseek-ai/deepseek-harness |
来源身份 |
| 固定 commit | 47f943... |
可审查源码快照 |
| Source version | 0.1.0-rc.5 |
checkout 自己声明的版本 |
| Runtime package family | 0.1.0-rc.6 |
产品实际安装/测试的包接口 |
这意味着源码审查证据和运行时兼容证据要分别提供。测试应该验证 rc.6 的公开 package exports 与 contract,而不是声称相邻 rc.5 源码可以证明所有 rc.6 artifact 行为。诚实记录“不知道精确对应提交”比一个错误 provenance claim 更专业。
五、Yarn 与 pnpm 为什么刻意不统一
外层产品固定 Yarn 4.18,使用 nodeLinker: node-modules,唯一 workspace 是 dsh-plugin-desktop;上游 submodule 继续使用自己固定的 pnpm。根脚本只在执行 upstream:* 命令时显式 cd deepseek-harness,再通过 Corepack 调用上游 pnpm。
{
"packageManager": "yarn@4.18.0",
"workspaces": ["dsh-plugin-desktop"],
"scripts": {
"upstream:version": "cd deepseek-harness && corepack pnpm --version",
"upstream:build": "cd deepseek-harness && corepack pnpm run build"
}
}
flowchart LR
ROOT["根产品命令"] --> Y["Yarn 4 Lockfile"]
Y --> DESK["dsh-plugin-desktop 构建/测试/打包"]
ROOT --> UP["upstream:* Portable Shell"]
UP --> P["Submodule Pnpm Lockfile"]
P --> SRC["官方源码验证"]
DESK -. "禁止 workspace/link/file 越界" .-> SRC
classDef root fill:#334155,color:#fff,stroke:#0f172a,stroke-width:2px;
classDef product fill:#2563eb,color:#fff,stroke:#1d4ed8,stroke-width:2px;
classDef upstream fill:#10b981,color:#fff,stroke:#047857,stroke-width:2px;
class ROOT root;
class Y,DESK product;
class UP,P,SRC upstream;
图4 双包管理器边界:各自复现自己的所有权范围
统一包管理器看似简洁,实际会修改官方 lockfile 或让产品依赖上游 monorepo hoist。隔离的代价是两套 cache 与命令,但换来的是可复现产品 release 和不受污染的上游验证。
六、正常构建必须消费发布包
布局检查会扫描根与插件 manifest,拒绝 workspace:、portal:、link:,以及指向 deepseek-harness 的 file: dependency。这个规则确保开发者不能为了本地调试方便,让产品测试绕过真实用户安装的 npm artifact。
if (/^(?:workspace|portal|link):/u.test(range)
|| (range.startsWith('file:') && range.includes('deepseek-harness'))) {
fail(`${owner} ${field}.${name} bypasses the published DSH package boundary`)
}
这条约束很严格,但意义明确:Desktop 兼容性是对发布 contract 的兼容,不是对某个开发 checkout 内部路径的兼容。若需要验证未来上游改动,应在单独实验分支或上游仓库完成,不能让产品 lockfile悄悄指向本地源码后仍称为 release test。
七、Compatibility Mode 是升级基线
治理不仅存在于 Git 和 package manager,也存在于产品行为。仓库规则要求 Compatibility 使用上游默认 Client,不覆盖 layout、sidebar、conversation 或 presentation。Desktop Client 校验 mode/platform 后直接返回,不注册 service、slot 或 styles。
这个“什么都不做”的模式提供了一条重要基线:
-
上游 Web UI 更新能在 Desktop 中原样观察。
-
第三方插件可以判断问题来自普通 DSH 组合还是高级桌面 frame。
-
Desktop 不会因为品牌样式小修补,逐渐形成隐形 UI fork。
-
维护者可以把兼容回归视为高优先级 contract 破坏。
Compatibility 不是功能较少的临时模式,而是证明 Desktop 仍能承载上游默认产品的治理工具。
八、Advanced Mode 通过组合而不是复制
高级模式确实改变 presentation,但它先验证官方 row 身份,只禁用 ui-layout,继续保留官方 ui-sidebar 与 ui-conversation。Desktop 自己提供 layout service 和 root occupant,为 sidebar、conversation、details 与 overlay 声明 seat;它拥有 frame 几何、原生材质和拖动区,不复制会话、设置、工作区或 feature state。
| Advanced 所有 | 继续由上游/生态所有 |
|---|---|
| BrowserWindow 材质 | Agent/Model/Tool 语义 |
| root frame 与三栏几何 | sidebar 组件与行为 |
| layout service | conversation/details 内容 |
| caption/drag region | Web carrier 与 Client Loader |
| theme document projection | 第三方 slot contribution |
这种 seam-based composition 让上游改进可以继续流入产品,只要公开 slot/service contract 不变。若 contract 改变,Desktop 需要在自己的包中显式适配并补测试,而不是在上游源码中藏补丁。
九、Desktop Service 解决差异,不污染上游 Contract
当前上游没有 typed active-profile/package-manager service,Desktop launcher 又确实知道当前 Profile 和打包 pnpm。项目选择发布 desktopProfiles 与 desktopPnpm 两个 generation-scoped Host service,而不是修改上游 CLI、Profile manifest 或 Renderer IPC。
跨环境插件动态探测 Desktop service;存在时使用权威 current 与 runPlugin(),不存在时保留普通 DSH fallback。这种 adapter 让桌面特性成为可选增强,而不是迫使整个上游生态依赖 Electron。
第三方 package 是否预装还要同时通过 API 和许可 gate。例如某个旧市场包即使用户可以主动安装,如果它不消费 Desktop service、没有可注入 seam,且发布 tarball 缺少再分发所需的完整许可 notice,Desktop 也不会为了功能宣传直接 fork 或嵌入。技术兼容与再分发合规是两条独立责任线。
十、把架构决策变成可执行策略
只写一篇“不要改上游”的文档很容易失效。仓库用 yarn check:layout 把关键决策机械化:
| 检查项 | 防止的漂移 |
|---|---|
| Yarn version/workspace members | 产品工作区无意扩大 |
| 根与包内遗留 pnpm 文件 | 两套 lockfile 混入同一所有者 |
submodule mode 160000 |
上游退化成普通目录 |
| URL/gitlink/HEAD/remote/status | pin 与 checkout 不一致或被修改 |
| source/runtime version | 元数据与实际依赖不一致 |
| 禁止 workspace/link/file | 构建绕过发布 package |
| Agent Note hash | 决策文档与记录漂移 |
flowchart TD
ADR["Agent Note / Repository Rules"] --> CODE["verify-layout.mjs"]
CODE --> CHECK["yarn check:layout"]
CHECK --> A{"拓扑与版本一致?"}
A -- "否" --> FAIL["Fail Loud"]
A -- "是" --> BUILD["进入 Build/Test/Loader Smoke"]
BUILD --> RELEASE["产品发布证据"]
classDef decision fill:#f59e0b,color:#111827,stroke:#d97706,stroke-width:2px;
classDef policy fill:#2563eb,color:#fff,stroke:#1d4ed8,stroke-width:2px;
classDef failure fill:#ef4444,color:#fff,stroke:#b91c1c,stroke-width:2px;
classDef success fill:#10b981,color:#fff,stroke:#047857,stroke-width:2px;
class ADR,CODE,CHECK policy;
class A decision;
class FAIL failure;
class BUILD,RELEASE success;
图5 策略即代码:架构文档通过可执行检查进入发布链
这比依赖维护者记忆更可靠。新贡献者即使没读完历史,也会在越过边界时得到具体失败信息。
十一、一次上游升级应怎样拆分
一个稳健的更新流程可以分成以下步骤:
-
在独立提交中更新 submodule gitlink 与
upstream.json的 source facts。 -
确认 checkout remote、HEAD、status 与新记录一致。
-
单独评估 npm runtime family 是否有对应发布;没有证据时不强行同步版本。
-
以 immutable 模式更新产品 Yarn lockfile,并检查全部 DSH package family 一致。
-
先运行 Compatibility Loader/Profile smoke,确认上游默认组合仍成立。
-
再验证 Desktop services、Advanced slot/theme/window contract。
-
扩大到 runtime closure、CLI、packaged runtime 和目标平台 gate。
-
将必要的 Desktop adapter 作为独立行为提交,保留升级前后差异。
sequenceDiagram
participant U as Upstream Pin
participant M as Metadata
participant P as Published Packages
participant C as Compatibility
participant D as Desktop Adapters
participant R as Release Gates
U->>M: 独立记录新 commit/source version
P->>M: 单独决定 runtime family
M->>C: 验证默认 Host/Client 组合
C->>D: 检查 service/slot contract
D->>R: Closure/CLI/Package/Platform
R-->>U: 形成可审计升级证据
图6 上游升级顺序:来源变化、运行时 artifact 与产品适配分别给证据
若升级需要重大方向变化,应先提交当前已知状态,再开始新的架构分支,避免一次工作树同时包含 pin、迁移和回退试验。
十二、这种治理方式的真实代价
没有任何边界是免费的。首次 clone 必须初始化 submodule;贡献者需要理解 Yarn 与 pnpm 两套命令;公开源码与 npm package 版本可能不一致,需要分别解释;Advanced mode 依赖上游 slot/theme contract,升级时仍可能需要适配;不能随手改上游意味着某些功能要等待正式 seam 或贡献 upstream。
但这些成本是显性的,可以进入文档、脚本和评审。长期 fork 的成本则常常是隐性的,直到安全修复、插件兼容或发布溯源出现问题才爆发。治理的目标不是消灭升级工作,而是让升级工作发生在正确层,并且能被独立审查、测试与回退。
十三、贡献者 Checklist
-
当前需求属于上游核心、Desktop 产品还是第三方插件?
-
改动是否触碰
deepseek-harness/?若是,是否应提交 upstream? -
产品依赖是否仍来自发布 package,而非 workspace/link/file?
-
source pin 与 runtime package version 是否分别记录?
-
Compatibility 是否保持上游默认 Client,无隐藏 presentation override?
-
Advanced 是否只替换文档化 service/slot,不复制业务 surface?
-
新 Desktop 能力能否通过窄 service contract 表达?
-
第三方嵌入是否同时通过 API、版本完整性和许可 gate?
-
架构决策是否有对应的可执行布局或测试检查?
-
上游 pin 与 Desktop 行为是否拆成可独立回滚的提交?
十四、一个需求究竟应该放在哪一层
治理规则最终要帮助日常决策,而不是只在发布时检查。面对新需求,可以先判断它是否改变 Harness 在所有载体中的核心语义。如果一个 bug 影响 Agent、模型、工具、会话、普通 Web Profile 或跨平台 sandbox policy,它通常属于上游,应在官方仓库修复并等待发布 contract;Desktop 可以临时阻止危险路径或记录兼容限制,但不应长期复制核心实现。
如果需求只与 Electron 生命周期、BrowserWindow、Tray、系统终端、安装器或打包路径有关,它属于 dsh-plugin-desktop。例如关闭窗口时隐藏、Profile 切换后 relaunch、macOS vibrancy、Windows Mica、私有 pnpm shim 与 NSIS 验证,都是产品原生适配,不应该要求上游 Web 应用理解 Electron。
如果功能是一个可选模型、工具、工作流或 UI contribution,应优先做普通 DSH 插件。只有当它需要当前 Desktop Profile 或受管包操作时,才动态探测 desktopProfiles/desktopPnpm adapter,同时保留普通 DSH fallback。这样生态功能不会因为接入桌面产品就失去在 CLI/Web 环境的可移植性。
| 需求示例 | 推荐归属 | 判断依据 |
|---|---|---|
| Agent 会话状态错误 | 上游 Harness | 所有载体共享核心语义 |
| 新的模型/工具集成 | 普通第三方插件 | 可组合、非桌面专属 |
| BrowserWindow 导航限制 | Desktop | Electron 原生安全边界 |
| 当前 Profile 插件管理 UI | 跨环境插件 + Desktop adapter | 共享业务,桌面增强 |
| 官方 Client 缺少通用 slot | 优先贡献上游 seam | Desktop 不应 DOM 劫持 |
| 安装器签名与更新交接 | Desktop release | 平台产品责任 |
flowchart TD
A["收到新需求"] --> B{"是否改变所有载体的<br/>Harness 核心语义?"}
B -- "是" --> U["贡献上游"]
B -- "否" --> C{"是否依赖 Electron/OS?"}
C -- "是" --> D["Desktop-owned Adapter/Plugin"]
C -- "否" --> E{"是否可选生态能力?"}
E -- "是" --> P["普通 DSH 插件"]
E -- "需要 Desktop 增强" --> X["普通实现 + 可选 Desktop Service"]
classDef decision fill:#f59e0b,color:#111827,stroke:#d97706,stroke-width:2px;
classDef upstream fill:#10b981,color:#fff,stroke:#047857,stroke-width:2px;
classDef desktop fill:#2563eb,color:#fff,stroke:#1d4ed8,stroke-width:2px;
classDef plugin fill:#8b5cf6,color:#fff,stroke:#6d28d9,stroke-width:2px;
class A,B,C,E decision;
class U upstream;
class D desktop;
class P,X plugin;
图7 需求归属决策:先按语义和平台边界分层,再选择代码位置
当现有上游缺少必要 seam 时,最危险的捷径是直接复制组件或私有模块。更稳妥的顺序是先提出通用扩展点、用 Compatibility 验证默认行为,再让 Advanced 或插件消费公开 contract。即使短期需要 adapter,也要把它限制在 Desktop 自有目录,写明移除条件和对应上游版本,避免临时方案永久化。
参考资料
总结
从开源治理角度看 anywhere-labs/deepseek-harness-desktop,我认为它最值得借鉴的地方,是把“基于上游”从一句 README 声明变成了可以验证的仓库结构和产品行为。官方 Harness 以固定 URL、commit 和干净工作树存在于只读 submodule 中,桌面代码集中在自己拥有的 package;产品 Yarn workspace 与上游 pnpm workspace 各自维护 lockfile,普通构建只消费用户真正会安装的发布 package,不依赖未发布源码布局。upstream.json 又把 source commit/version 与 runtime package family 分开,承认 rc.5 源码不能自动证明 rc.6 artifact,而不是用整齐版本号掩盖来源空白。更深一层,Compatibility 保持上游默认 Client,成为持续升级的行为基线;Advanced 只通过明确的 layout、root、theme 和 native-window seam 组合增强,Desktop service 也只补足 launcher 独有事实,不修改上游 Profile、CLI 或 renderer transport。新增需求还可以沿“核心语义、平台能力、可选生态”三层判断归属:共享 Harness 行为贡献上游,Electron/OS 责任留在 Desktop,可组合功能优先成为普通插件,只有真正需要时再接入可选 Desktop adapter。最后,check:layout 把 workspace、submodule、dependency range、version family 和决策文档一致性变成 fail-loud gate,使架构不再依赖某位维护者的记忆。对我而言,这套方法并不会消除上游升级成本,却把成本变成可定位的工作:来源更新、artifact 更新、兼容验证和产品适配可以分别提交、分别测试、分别回滚;临时 adapter 也能明确写出移除条件,而不是沉入无法追踪的复制代码。任何准备围绕快速发展的开源核心做商业或社区产品的团队,都应该先建立这种所有权与证据链,再开始添加功能;否则每一次“临时改一下上游”都会把未来同步的选择空间变小,最终让产品既无法跟上官方,也无法准确解释自己究竟运行了什么。
更多推荐


所有评论(0)