写一个 DSH 插件(bundle 声明 + patch 层)

别名:插件骨架、写 DSH 插件、bundle 声明、dsh.bundle.patch

从零讲清一个 DSH 插件到底要声明什么:package.json 里的 dsh.bundle.patch、一份 YAML 补丁数组、宿主半端与可选的客户端半端,以及本地怎么挂上去验证——顺带说清「它为什么不是普通 npm 依赖」。

先弄清一件事:插件不是普通的依赖

装到 profile 里的包有两种命运。一种是普通库:装上了,被谁的代码 import 才起作用。另一种是层:它自己带来一段补丁,参与那棵插件树的装配。DSH 判断依据只有一句——这个包的 package.json 里有没有 dsh.bundle。

所以「写一个 DSH 插件」要交的作业不是一份功能代码,而是:一段声明 + 一份补丁 + 一个能被当成 cordis 插件挂起来的入口。@deepseek-ai/dsh-app-boot 的 loadProfile 对这段声明的处理很直接:解析不出 dsh.bundle.patch 就抛错(declares no dsh.bundle in its package.json),不会「当作没有补丁」放过去。

骨架:三样东西

my-plugin/
├── package.json        # 声明自己是 bundle,并指出补丁文件
├── cordis.patch.yml    # 那层补丁(顶层 YAML 数组)
└── lib/host.js         # 宿主半端入口:一个 cordis 插件

package.json 的最小声明:

{
  "name": "my-dsh-plugin",
  "version": "0.1.0",
  "type": "module",
  "main": "lib/host.js",
  "files": ["lib", "cordis.patch.yml"],
  "dsh": {
    "bundle": { "patch": "./cordis.patch.yml" }
  }
}

要点只有三条,但每一条都有人踩:

  1. dsh.bundle.patch 是「我是层」的唯一凭证。少了它,dsh plugin add 会成功(包确实装上了),但只会给你一行告警:declares no dsh.bundle — installed as a plain dependency, not a profile layer。装上了不等于生效。
  2. 补丁文件是顶层 YAML 数组,不是对象。写成一个带键的 mapping 会直接解析失败。
  3. 入口要能被当作 cordis 插件挂起来:要么导出 apply,要么是带 name / inject / apply 的形状。它跑在启动 DSH 的那个 Node 进程里,不是在浏览器里。

补丁里写什么:两种操作

补丁列表每项要么插行、要么改行,语义与层序的权威说明在 concept/1,这里给两种最常见的写法:

# 1) 插一行:把宿主插件挂进树
- insert:
    - id: my-plugin-host          # 这行自己的 id,别的补丁靠它定位
      name: my-dsh-plugin          # 可解析的包名
      inject: [webServer]          # 我依赖哪些服务(服务名是 DSH 的,写错就起不来)

# 2) 改一行:按 id 改已有行的顶层键
- id: some-existing-row
  disabled: true                   # 摘掉一行

两个必须记住的边界:

  • config 是整段替换,不是深合并。只想改一个键,也要把想保留的键一起写全;否则新版给这一行补的键会被你这份 config 抹掉。这是 tutorial/1 里「插件在、功能没了」那一类失效的直接成因。
  • 命不中的补丁只告警、不报错。这对启动稳定性是好事,对你调试是坏事——写完一定要看启动输出,别只看退出码。

真实样本:一个包两半怎么声明

plugin/2 的 package.json 是最省事的形态:宿主侧靠 cordis.patch.yml,客户端侧靠 dsh.client,两段都在同一个包里。

"exports": {
  ".":                  { "default": "./lib/host.js" },
  "./client":           "./lib/client.js",
  "./cordis.patch.yml": "./cordis.patch.yml"
},
"dsh": {
  "bundle": { "patch": "./cordis.patch.yml" },
  "client": { "platform": "web", "inject": ["@deepseek-ai/dsh-client-runtime", "…"] }
}

它的 cordis.patch.yml 短到只有一行 insert,宿主插件按 id dspack-host 挂上去,inject 写 [connection, webServer, skills]。

需要两半还是只要宿主半端,取决于你有没有界面:纯后端能力(工具、采集、索引)一个宿主半端就够;要往侧边栏、设置页、输入框附近加东西,才需要客户端半端——它的声明字段、./client 产物、window.__DSH_BOOT__ 入口图的完整规则见 concept/4,这里不重复。

dsh-packforge-app 的 monorepo 是另一种组织方式:把插件放在 packages/plugin,与打包引擎(core)、命令行(cli)、Electron 界面(gui)并列,带 dsh 声明的仍然只有插件那一个包。「哪个包才是插件」与「我在哪个目录开发」是两件事,别把 workspace 根当成插件根。

本地怎么挂上去验证

不需要先发布。profile 的插件管理本质上是「在该 profile 目录里跑 pnpm」,所以本地路径可以直接装:

dsh plugin --profile tui add /abs/path/to/my-plugin      # 绝对路径最稳
dsh --profile tui --dump-config                          # 看组合后的树:你的行进来了吗
dsh --profile tui --dump-default-config                  # 只看 bundle 层,不含用户层
dsh --profile tui                                        # 真正启动

三点提示:

  • 用绝对路径。dsh plugin 会把 . / ../plugin 这类相对路径按你当前所在目录重写;写成绝对路径就不用记这条规则。
  • config 想改就写进 profile 的 cordis.patch.yml(或临时用 --patch 叠一个文件),不要去改插件目录里那份——插件那份是随包分发的,你改了下次更新就没了。
  • GitHub 坐标来源会在安装时跑构建脚本:pnpm 默认拦下 prepare,第一次装会失败并打印一个键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 再装一次。卸载、落点与这条授权链路见 tutorial/3。

常见坑清单

现象大概率原因
安装成功,启动后毫无变化少了 dsh.bundle.patch,被当成普通依赖
启动时抛 declares no dsh.bundle被列进了 dsh.profile.bundles,但包本身没有声明
补丁文件解析失败顶层写成了对象;它必须是数组
启动报错说找不到某个服务inject 里的服务名不是 DSH 实际提供的(大小写与命名都要对)
配置项莫名回默认补丁里的 config 整段替换,把新版的键抹掉了
插件两半只活一半宿主那一行没挂上,客户端入口图里自然也没有它
界面上的东西没出现声明写了但 exports["./client"] 指不到真实产物文件

插件装好之后每次启动到底走了哪几步、为什么会有该 profile 专属的 node_modules,见 tutorial/3。

未核实 / 未声明

  • 「插件的入口可以是 apply 之外什么形状」:本轮只核实了 dsh.bundle.patch 的解析与补丁语义,没有逐条核实 cordis 插件入口的全部合法形态;上面那条按最小可用写法给。
  • dsh.client 的字段全集:以 concept/4 的核对为准(platform 必需,inject / immediately 可选);本教程没有独立复核。
  • 发布与收录的完整流程:不在本篇范围内,本站另有整合包发布向的内容;本篇到「本地挂上去能跑」为止。

相关教程

谁引用了这一条

信息表

appliesTo
dsh 0.1.0-rc.6(实测,本机 @deepseek-ai/dsh 与 @deepseek-ai/dsh-app-boot 的实际实现);插件声明格式由 @deepseek-ai/dsh-app-boot 的 loadProfile 决定,更高版本未核实
category
ecosystem.publish
difficulty
intermediate
origin
original
plugins
[object Object]、[object Object]、[object Object]
prereq
concept/1、concept/4
related
tutorial/1、tutorial/3
updatedAt
2026-10-02

标签:插件、开发、bundle、patch 层