DSH plugin 打包成 bundle:bundle 与 profile 的区别、三个文件与安装验证

插件开发发布于 2026-09-12作者: DeepSeek Plugin 插件市场
DSH pluginDeepSeek Harnessbundlecordis.patch.yml打包
DeepSeek Harness(DSH)插件打包:用 package.json 的 dsh.bundle 声明 bundle、写 cordis.patch.yml 按包名插入行,再用 dsh plugin add 装进 profile 并用 --dump-config 验证配置层。

DSH plugin 打包的本质是把你写好的插件变成一个携带配置层的 npm 包(bundle):package.jsondsh.bundle.patch 指向一份 cordis.patch.yml,这份 patch 里的插件行按包名引用模块;用户执行 dsh plugin --profile <name> add 后,这一层被追加进 profile 的 dsh.profile.bundles 无论叫 DSH插件 还是 DeepSeek插件,打包要遵守的约束完全一致。

DSH plugin 的 bundle 与 profile:两个概念、两份 manifest

作者产出 bundle,用户启动 profile,两者不能混为一谈。 官方对这两个概念的界定很清晰(来源):

概念是什么manifest 字段回答的问题
bundle携带配置层的 npm 包dsh.bundle这个包贡献什么?
profile$DSH_HOME/profiles/<name> 下的组合目录dsh.profile哪些 bundle 按什么顺序组合?

「没有东西同时是两者」:bundle 是你编写并分发的,profile 是用户用 dsh --profile <name> 启动的。profile 永远不要手写——dsh plugin 会创建并维护它。

DSH plugin 最小可安装 bundle 的三个文件

一个可安装 bundle 只有三个文件。 官方结构如下:

hello-plugin/
├── package.json       # 声明 dsh.bundle
├── cordis.patch.yml   # profile 列出该 bundle 时应用的层
└── index.js           # patch 行引用的插件模块
json
{
  "name": "dsh-hello-plugin",
  "version": "0.1.0",
  "type": "module",
  "main": "index.js",
  "files": ["index.js", "cordis.patch.yml"],
  "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}
js
export const name = 'hello-plugin'
export function apply() {
  console.log('[hello-plugin] plugin loaded!')
}

files 里必须包含 cordis.patch.yml,否则安装方拿不到配置层——插件装上了却不会被挂载,这是打包最常见的漏项。

少了 dsh.bundle 的包仍能安装,但只作为普通依赖、不激活任何层dsh plugin 会给出警告。这个形态是给「被插件包 import 的库」用的,不是给用户启用的插件。

DSH plugin 的 cordis.patch.yml 怎么写

打包后的 patch 按包名引用模块,不写源码路径。 官方最小写法:

yaml
- insert:
    - id: hello
      name: dsh-hello-plugin

与本地调试的区别只有一处:覆盖层里写的是绝对路径(因为 loader 从 profile 目录解析模块),而 bundle 里写包名,交给 Node 模块解析去找已安装的代码。本地调试见 本地调试

把 DSH plugin 装进 profile 并验证

dsh plugin --profile <name> <args...> 会把参数转发给 profile 目录里的 pnpm,所以任何 pnpm 动词都可用。 从包含 hello-plugin 的目录执行(来源):

  1. 装进 profiledsh plugin --profile demo add ./hello-plugin首次使用会初始化 profile(以 @deepseek-ai/dsh-base 作为第一个 bundle),然后把你的包链接进去并追加到 dsh.profile.bundles
  2. 先不启动,检查配置层dsh --profile demo --dump-config预期能看到一条 # == dsh-hello-plugin 层。
  3. 再启动dsh --profile demo预期看到插件加载日志、行为生效。

卸载用同一条通道:dsh plugin --profile demo remove dsh-hello-plugin,它会同时移除依赖与该层。层顺序的完整规则见 本地调试

DSH plugin 的层顺序与「整行替换」的坑

理解两件事就能避免绝大多数「装了没生效」和「覆盖后配置丢了」。

  1. 层顺序:bundle patch(按 dsh.profile.bundles 顺序)→ profile 自己的 cordis.patch.yml → 家目录 $DSH_HOME/cordis.patch.yml--patch 覆盖层;越靠后越优先
  2. 整行替换、不做深合并:patch 按行覆盖 config 时,必须重述该行需要的每一个 key,只写改动项会丢掉其余 key。

由此得到两条打包建议:① 你的 patch 可以按 id 覆盖前层的行(官方 dsh-web-app 就是这样覆盖 dsh-base 的),但要写全;② 用户可以不改你的包、在自己的 profile 里覆盖你的行,所以默认值应当设成用户大概率会保留的值,剩下的交给 schema 兜底。

让 DSH plugin bundle 带上自己的命令行(进阶)

需要自定义启动参数时,挂一个 provider 插件即可,launcher 不需要改。 做法是让插件 inject = ['cmdlineArgs'],用 @deepseek-ai/dsh-cmdlineparseCmdline 解析自己那套 commander 程序,并从 action 里提供 app 自有服务;需要读这些参数的行再注入该服务,并用 !!js 取值:

yaml
- id: my-app
  name: '@example/my-app'
  inject: [myAppStartup]
  config:
    port: !!js ctx.myAppStartup.port ?? 8080

--help 时 provider 不发布服务,这些行就不会激活——这正是「帮助信息不该触发业务行」的实现方式。

本地可安装之后,下一步是选择分发方式:走 npm 发包见 发布到 npm;提交到插件目录收录见 发布到插件中心。装好后可在 DSH Plugin Hub 核对已安装列表与配置项。写代码阶段的硬性自检项见 开发规范

常见问题

DSH plugin 的 bundle 和 profile 有什么区别?

**在 DSH plugin 体系中,bundle 是你编写并分发的 npm 包,profile 是用户启动时用的组合目录。** 两者都用 package.json 描述,但 manifest 挂在 dsh 键下的不同字段:bundle 声明 dsh.bundle(回答「这个包贡献什么」),profile 声明 dsh.profile(回答「哪些 bundle 按什么顺序组合」)。**没有东西同时是两者**(来源:官方「打包与安装插件」)。

DSH plugin 打包最少需要几个文件?

**一个 DSH plugin 打包最少需要三个文件**:package.json(声明 dsh.bundle.patch 指向 patch 文件)、cordis.patch.yml(profile 列出该 bundle 时应用的层)、以及插件入口文件。官方最小示例的 package.json 还带 type: modulemainfiles(必须包含入口与 patch 文件,否则安装方拿不到配置层)。

DSH plugin 的 cordis.patch.yml 里插件行怎么写?

**DSH plugin 的 cordis.patch.yml 里插件行按包名引用,而不是相对源码路径。** 打包后的 patch 是一个 YAML 数组,行里写 name: dsh-hello-plugin 这样的包名,交给 Node 的模块解析去找已安装的代码;本地调试阶段那种绝对路径写法只适用于 --patch 覆盖层(来源:官方「打包与安装插件」)。

package.json 里少了 dsh.bundle 会怎样?

**DSH plugin 的 package.json 里少了 dsh.bundle 仍然能装,但只作为普通依赖,不会激活任何配置层**——dsh plugin 会打印警告。这个形态适用于「被插件包 import 的库」,而不是用户要启用的插件。所以打包插件时漏掉 dsh.bundle 的表现是「装上了但没生效」(来源:官方「打包与安装插件」)。

为什么 DSH plugin 的 patch 覆盖别人一行时要重述所有 key?

因为 **DSH plugin 的 patch 是按行整行替换 config,不做深合并**。你的 patch 可以按 id 覆盖前层的行,但必须把该行需要的**每一个 key 都重新写全**,只写改动的那一个会把其余 key 丢掉。同理,用户可以不改你的包就在自己的 profile 里覆盖你的行,所以默认值最好设成用户愿意保留的值(来源:官方「打包与安装插件」)。

相关术语

bundle
bundle 是 DSH plugin 作者编写并分发的单位:一个携带配置层的 npm 包,manifest 用 dsh.bundle 声明它贡献的 patch 文件(插入或覆盖插件行)。https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/publish.md
profile
profile 是 $DSH_HOME/profiles/<name> 下的目录,描述一次可运行的组合,manifest 用 dsh.profile 声明有序的 bundles 列表;它由 dsh plugin 创建与维护,用户不手写。https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/publish.md
cordis.patch.yml
cordis.patch.yml 是 DSH plugin bundle 携带的配置层文件,是一个 YAML 数组;profile 列出该 bundle 时这一层被应用,行内用包名引用插件模块。https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/publish.md
dsh.bundle.patch
dsh.bundle.patch 是 DSH plugin 的 package.json 字段,指向该包携带的 patch 文件,是 dsh 判断「这个包是插件 bundle 而不是普通依赖」的依据。https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/publish.md

来源