DSH plugin 打包成 bundle:bundle 与 profile 的区别、三个文件与安装验证
DSH plugin 打包的本质是把你写好的插件变成一个携带配置层的 npm 包(bundle):package.json 用 dsh.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 行引用的插件模块
{
"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" } }
}
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 按包名引用模块,不写源码路径。 官方最小写法:
- insert:
- id: hello
name: dsh-hello-plugin
与本地调试的区别只有一处:覆盖层里写的是绝对路径(因为 loader 从 profile 目录解析模块),而 bundle 里写包名,交给 Node 模块解析去找已安装的代码。本地调试见 本地调试。
把 DSH plugin 装进 profile 并验证
dsh plugin --profile <name> <args...> 会把参数转发给 profile 目录里的 pnpm,所以任何 pnpm 动词都可用。 从包含 hello-plugin 的目录执行(来源):
- 装进 profile:
dsh plugin --profile demo add ./hello-plugin。首次使用会初始化 profile(以@deepseek-ai/dsh-base作为第一个 bundle),然后把你的包链接进去并追加到dsh.profile.bundles。 - 先不启动,检查配置层:
dsh --profile demo --dump-config,预期能看到一条# == dsh-hello-plugin层。 - 再启动:
dsh --profile demo,预期看到插件加载日志、行为生效。
卸载用同一条通道:dsh plugin --profile demo remove dsh-hello-plugin,它会同时移除依赖与该层。层顺序的完整规则见 本地调试。
DSH plugin 的层顺序与「整行替换」的坑
理解两件事就能避免绝大多数「装了没生效」和「覆盖后配置丢了」。
- 层顺序:bundle patch(按
dsh.profile.bundles顺序)→ profile 自己的cordis.patch.yml→ 家目录$DSH_HOME/cordis.patch.yml→--patch覆盖层;越靠后越优先。 - 整行替换、不做深合并:patch 按行覆盖
config时,必须重述该行需要的每一个 key,只写改动项会丢掉其余 key。
由此得到两条打包建议:① 你的 patch 可以按 id 覆盖前层的行(官方 dsh-web-app 就是这样覆盖 dsh-base 的),但要写全;② 用户可以不改你的包、在自己的 profile 里覆盖你的行,所以默认值应当设成用户大概率会保留的值,剩下的交给 schema 兜底。
让 DSH plugin bundle 带上自己的命令行(进阶)
需要自定义启动参数时,挂一个 provider 插件即可,launcher 不需要改。 做法是让插件 inject = ['cmdlineArgs'],用 @deepseek-ai/dsh-cmdline 的 parseCmdline 解析自己那套 commander 程序,并从 action 里提供 app 自有服务;需要读这些参数的行再注入该服务,并用 !!js 取值:
- 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 是你编写并分发的 npm 包,profile 是用户启动时用的组合目录。** 两者都用 package.json 描述,但 manifest 挂在 dsh 键下的不同字段:bundle 声明 dsh.bundle(回答「这个包贡献什么」),profile 声明 dsh.profile(回答「哪些 bundle 按什么顺序组合」)。**没有东西同时是两者**(来源:官方「打包与安装插件」)。
**一个 DSH plugin 打包最少需要三个文件**:package.json(声明 dsh.bundle.patch 指向 patch 文件)、cordis.patch.yml(profile 列出该 bundle 时应用的层)、以及插件入口文件。官方最小示例的 package.json 还带 type: module、main 与 files(必须包含入口与 patch 文件,否则安装方拿不到配置层)。
**DSH plugin 的 cordis.patch.yml 里插件行按包名引用,而不是相对源码路径。** 打包后的 patch 是一个 YAML 数组,行里写 name: dsh-hello-plugin 这样的包名,交给 Node 的模块解析去找已安装的代码;本地调试阶段那种绝对路径写法只适用于 --patch 覆盖层(来源:官方「打包与安装插件」)。
**DSH plugin 的 package.json 里少了 dsh.bundle 仍然能装,但只作为普通依赖,不会激活任何配置层**——dsh plugin 会打印警告。这个形态适用于「被插件包 import 的库」,而不是用户要启用的插件。所以打包插件时漏掉 dsh.bundle 的表现是「装上了但没生效」(来源:官方「打包与安装插件」)。
因为 **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
来源
- DeepSeek Harness 官方文档 - 打包与安装插件· deepseek-ai
- dsh CLI README(profile 与层优先级)· deepseek-ai
- DeepSeek Harness 官方文档 - 你的第一个插件· deepseek-ai