DeepSeek Harness 插件分发架构收敛:移除专用 repository 插件路径,统一为 profile 组合包模型
2026/9/20 19:53:26 网站建设 项目流程

DeepSeek Harness 插件分发架构收敛:移除专用 repository 插件路径,统一为 profile 组合包模型

【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness

本篇技术指南剖析 DeepSeek Harness(一切皆插件)在外部第三方插件分发机制上的一次重要架构简化:彻底移除@deepseek-ai/dsh-repository-plugin专用路径(.dsh-plugin清单格式、dsh-plugin-prepare准备可执行文件、生成式包装层、不可变 repository 缓存、Loader 内置项及专用 skill/MCP 适配器),将外部插件分发收敛为唯一的 profile 组合包(bundle)模型。读完本文,你将理解这一决策的来龙去脉、组合包模型下的完整安装与配置工作流(dsh plugin --profiledsh.bundle.patchcordis.patch.yml三层架构),以及仓库源码中与之对应的具体实现路径。

该决策记录位于 .agents/notes/implemented/simplification/2026-08-09-remove-repository-plugin.zh.md(英文版见 2026-08-09-remove-repository-plugin.md),状态标记为implemented,即该简化已经落地。

问题:两条并行的外部插件分发路径

在移除之前,DeepSeek Harness 存在两条重复实现第三方包安装与组合的路径:

  • repository 插件路径:通过repositories源列表选择外部包,再依赖一整套专属基础设施——.dsh-pluginmanifest(元数据清单)、生成的包装层(generated wrapper)、dsh-plugin-prepare准备工作可执行文件、第二套 Git/包缓存、Loader 内置项,以及 repository 专用的 skill 与 MCP 适配器。
  • profile 组合包路径:通过 profile 包管理器直接安装 npm 或 Git 包说明符,保留正常的依赖与生命周期语义,并贡献一个有序的cordis.patch.yml层,其中可以挂载普通 Cordis 插件。

问题的本质在于:repository 路径不仅重复造轮子,而且能力更弱。其repositories列表只能选择源字符串,生成的包装层在挂载代码入口时无法传入用户提供的插件配置——也就是说,一个通用分发机制却比它试图替代的组合包暴露更少的配置面。结果是 repository 专用的准备流程增加了大量代码和 CI 工作,却始终没有成长为通用的外部插件分发机制。

决策:只保留 profile 组合包这一条外部插件分发路径

决策明确且彻底——DeepSeek Harness 只保留一种独立的外部插件分发路径:可安装的 profile 组合包。核心工作流为:

dsh plugin --profile <name> add <package-or-git-spec>

该命令将依赖记录到 profile 包($DSH_HOME/profiles/<name>/package.jsondependencies)中;安装的包通过声明dsh.bundle.patch贡献自己的 patch 层。职责划分清晰:

职责归属方
获取源码、管理版本与依赖、运行构建生命周期、维护锁文件包管理器(pnpm)
选择要挂载的 Cordis 插件、提供完整插件配置组合包 patch 层

移除清单:被清理的具体资产

按决策,以下内容全部移除:

  • @deepseek-ai/dsh-repository-plugin包本体;
  • .dsh-plugin编写格式(manifest 格式);
  • dsh-plugin-prepare可执行文件;
  • 生成的包装层(generated wrapper);
  • 不可变 repository 缓存(immutable repository cache);
  • base 组合包中的repository-plugins配置项;
  • 专用 GitHub 验收流水线(acceptance lane);
  • vendor 中不再使用的@cordisjs/plugin-loader/repository子路径及其随附的 pnpm 依赖(随唯一消费方一并移除)。

关于旧缓存数据需要特别注意:现有 repository 缓存目录只是不会再产生作用的用户数据。DSH 既不会读取这些目录,也不会删除它们;用户可以自行删除,但系统不做迁移或自动清理。

组合包如何直接组合现有归属方

移除 repository 专用适配器后,组合包不再需要定制运行时代码,而是直接组合现有归属方

  • 提供 skill 的组合包:挂载@deepseek-ai/dsh-skill-filesystem(见 packages/skill/skill-filesystem/package.json);
  • 提供 MCP 服务器的组合包:挂载@deepseek-ai/dsh-mcp-client
  • 需要原生行为的组合包:挂载普通的已编译 Cordis 插件。

这些包继续保有各自的校验、生命周期、注册和 teardown 契约,无需任何 repository 专属适配。同时,静态资源需要一种由组合包拥有、可相对于包解析的路径形式,使声明式组合包可以将dsh-skill-filesystemdsh-mcp-client或其他插件指向它随包交付的文件——该能力归属于组合包格式本身,而不是 repository 适配器。

根据预发布兼容政策,不保留针对.dsh-plugin的兼容解析器或迁移机制。

源码实现:dsh plugin如何工作

dsh plugin子命令的核心实现在 apps/cli/src/plugin.ts。从模块注释可以看出它的定位:一个薄的 pnpm 转发器——首次使用时初始化 profile,在 profile 目录中运行pnpm <args...>,然后将dsh.profile.bundles层列表与已安装状态进行协调(reconcile)。

关键流程

  1. 初始化:若$DSH_HOME/profiles/<name>/package.json不存在,则用PROFILE_TEMPLATES[profile]DEFAULT_PROFILE_BUNDLES初始化(见runPlugin)。
  2. 转发 pnpmspawnSync('pnpm', ...)在 profile 目录中执行,继承 stdio。Windows 下通过.cmdshim 需要shell: true(这是 CVE-2024-27980 加固后的要求)。
  3. 协调层列表reconcilePlugins依据安装后的实际状态(而非依赖 diff)决定层列表——这是update能自动激活新版本中才获得dsh.bundle声明的包的原因。

exportsPatch:判断一个依赖是否为组合包

function exportsPatch(packageName: string, profileDir: string): boolean { ... const manifest = readProfileManifest(NAME, dir) return manifest.dsh?.bundle?.patch !== undefined }

dsh.bundle.patch是组合包的签名:只要解析到的包 manifest 声明了dsh.bundle.patch,该依赖即加入层栈(按依赖顺序追加);反之,被移除或新版本丢掉声明的依赖则从层栈中退出。对新加入的非组合包依赖会输出一次警告(普通库可正常安装,警告仅作方向提示)。

相对路径锚定

anchorPathSpec处理了一个容易被忽视的坑:pnpm 以 profile 目录为 cwd 运行,裸的.../plugin(及其file:/link:形式)会静默解析到 profile 内部——add .会让插件自身链接到 profile。因此相对路径说明符会被重写为锚定到用户调用dsh时的目录,而绝对路径、registry 名称及其他 pnpm 参数原样透传。

包管理失败诊断

pnpm 失败时,runPlugin会特别针对Git 托管插件输出诊断:git 依赖通过prepare(build)脚本在安装时构建,而 pnpm ≥10 默认阻止这类脚本直到在白名单中放行——提示用户将 pnpm 打印的精确 key 加入pnpm-workspace.yamlallowBuilds后重试。注意 profile 目录拥有自己的pnpm-workspace.yamlnodeLinker: hoistedautoInstallPeers: false,见 packages/boot/app-boot/src/profile.ts 中的PROFILE_PNPM_WORKSPACE)。

源码实现:profile 的组合与 patch 层排序

组合包格式

在 packages/boot/app-boot/src/profile.ts 中定义了组合包的 manifest 契约:

/** The bundle half of the `dsh` manifest section: what a bundle package exports. */ export interface DshBundleManifest { /** The patch layer this bundle exports, relative to its package root. */ patch: string }

即一个 npm 包只要在package.json中声明"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }就成为一个组合包。loadProfile会为dsh.profile.bundles中的每个条目解析包目录、读取其dsh.bundle.patch并加载 patch 文件——列出的包若没有dsh.bundle声明会直接 fail-loud,因为把非组合包作为层是配置错误,而不是"没有 patch"。

内置 profile 模板

PROFILE_TEMPLATES定义了随安装附带的 profile:

profilebundlespatchReload
acp@deepseek-ai/dsh-base+@deepseek-ai/dsh-acp-appstartup
web@deepseek-ai/dsh-base+@deepseek-ai/dsh-web-applive
headless@deepseek-ai/dsh-base+@deepseek-ai/dsh-headlessstartup
sdk@deepseek-ai/dsh-base+@deepseek-ai/dsh-sdk-appstartup
sdk-minimal@deepseek-ai/dsh-sdk-minimalstartup

无模板的自定义 profile 初始化时使用DEFAULT_PROFILE_BUNDLES = ['@deepseek-ai/dsh-base'],并默认采用live热重载。

层组合顺序

在 apps/cli/src/profile-boot.ts 中,profile 的完整 patch 栈按如下顺序应用:

bundle 层(按 dsh.profile.bundles 顺序) → profile 自身的 cordis.patch.yml → 家目录层 $DSH_HOME/cordis.patch.yml(机器级偏好,高于 profile 层) → --patch 覆盖层(按 argv 顺序) → 遥测开关补丁

profile 根配置cordis.yml是一个空条目列表,整个树都是 patch 组合出来的——每次启动都会重写该空根(否则 Loader 的树写回会把组合出的行烘焙进根文件,导致下次启动重复插入每个 bundle)。对live热重载 profile,用户 patch 文件(profile 层与家目录层)会被监视,编辑即生效;重组时 bundle 层在下方、覆盖层在上方,用户编辑永远无法顶替 bundle 层。这一实现直接呼应了决策文档的后果条款:"用户 patch 的 HMR 仍可配置已安装组合包所提供的配置项"。

曾考虑的替代方案及其否决理由

决策文档明确记录了四个被否决的方案,理解它们有助于把握设计边界:

  1. 保留 repository 插件作为组合包的便利包装层——否决:同一个包会保留两条安装命令、两种 manifest 格式、两套失败/缓存标识;而且无法传递普通插件配置的便利包装,能力仍不及它所包装的机制。
  2. 让 repository 包装层加载组合包 patch——否决:repository 缓存和准备协议仍会重复 profile 依赖安装;组合包已能通过 pnpm 接受 npm、Git、file 和 link 说明符。
  3. 为未来可能出现的消费方保留通用 Loader repository 缓存——否决:移除相关包后已无当前消费方,却仍让 vendor 中与浏览器相邻的包携带固定版本的包管理器运行时。只有当"无需显式安装即可在配置阶段激活"成为 profile 依赖无法满足的产品需求时,才值得重新引入专用缓存,届时由该消费方自选缓存约定。
  4. 禁用 repository 插件但保留磁盘格式以供迁移——预发布方针下否决:保留解析器或兼容 loader 会在没有外部兼容义务的情况下,让已移除的契约继续存在。

后果与迁移注意事项

决策落地后产生的关键影响:

  • 第三方包统一使用一种安装与组合模型——普通依赖声明 + 完整的 patch 层插件配置,不再有第二套语义。
  • 安装或更新外部组合包是显式包管理操作:必须通过dsh plugin,而不是编辑受监听的源列表。
  • profile 安装要求宿主机PATH中存在pnpm:对显式包管理操作可接受,也避免了仅为配置阶段激活而随产品附带已移除缓存所使用的固定版本包管理器运行时。pnpm not found on PATHdsh plugin返回退出码 127 并给出提示。
  • .dsh-plugin包和现有 repository 源列表 patch 停止工作;旧缓存文件可自行删除,但不会被迁移或自动删除。
  • 专用 pnpm 运行时、准备可执行文件、包装层生成器、Git 凭据 CI 设置、repository 缓存和 repository 专用测试全部消失

测试与验证

本层移除有两条测试保障(见决策文档"测试"一节及 apps/cli/tests 目录):

  1. 静态门禁会拒绝残留的包、配置、文档、图和 workspace 引用——防止移除不彻底。
  2. 现有dsh plugin内置 CLI 验收测试覆盖 profile 初始化、包管理器安装、组合包发现和层调和。仓库中相关测试包括 profile-hmr.spec.ts、built-bin.e2e.ts 等。

同时决策文档明确记录了已命名覆盖缺口:声明式、相对于包解析的 skill 与 MCP 组合包资源仍未被本移除层的测试覆盖——这是该格式能力中已知待补强的部分。

小结

这次架构简化体现了"每个能力只保留一个归属方"的设计原则:外部插件的获取与生命周期归包管理器,插件选择与配置归组合包 patch 层,skill/MCP/原生行为的贡献归各自现有归属方(dsh-skill-filesystemdsh-mcp-client、普通 Cordis 插件)。由此换来的是统一的安装模型、完整的 patch 层配置能力,以及大幅缩减的代码与 CI 面。对使用方而言,对外分发外部插件只剩一条路:让包声明dsh.bundle.patch,然后dsh plugin --profile <name> add <spec>

【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询