理解“一切皆插件”最快的方式是亲手写一个。契约小到可以整个装进脑子——比多数 HTTP 框架的中间件还小——其余一切都是叠加其上的约定。
契约的全部
插件是一个导出 name 和 apply 函数的 TypeScript 模块:
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello-plugin'
export function apply(ctx: Context) {
// 必需的依赖在 apply 运行前就已就绪。
console.log('[hello-plugin] plugin loaded!')
}
就这么多。框架加载模块、以一个上下文对象调用 apply,你通过这个 ctx 注册能力——工具、事件监听、定时器、UI 贡献。上下文就是全部 API 面;这套架构的承诺是:这个界面上没有二等公民。
一个值得尽早内化的细节:凡是经 ctx 注册的东西,卸载时自动清理。 事件监听、工具、定时器——你永远不用为它们写 removeListener 或 clearInterval。需要手动收尾的资源(网络连接、子进程)用 ctx.effect():
export function apply(ctx: Context) {
ctx.effect(() => {
const timer = setInterval(() => console.log('heartbeat'), 5000)
return () => clearInterval(timer) // 卸载时执行
})
}
插件之所以能彼此组合,原因就在这:走的时候什么都不泄漏。
实时开发:patch 层
开发阶段不“安装”插件——而是把它 patch 进去。从 harness 检出目录出发(教程的默认前提),建一个 scratch 项目和一份覆盖层文件:
mkdir -p scratch-plugin/src
# scratch-plugin/cordis.yml
- insert:
- id: hello
name: '/absolute/path/to/deepseek-harness/scratch-plugin/src/my-plugin.ts'
路径必须是绝对路径——patch 文件只贡献配置,不改变 loader 解析模块的方式。然后带着覆盖层启动:
pnpm dsh web --patch ./scratch-plugin/cordis.yml
打开 http://127.0.0.1:3080,盯着终端:[hello-plugin] plugin loaded!。此刻你正对着挂载了自己插件的线上 Web UI 开发。这个 insert/patch 机制不是调试用的临时手段——下一节会说明,它就是分发格式。
打包:bundle
要分发,就把插件做成 bundle——一个在 package.json 里声明 dsh.bundle manifest 的 npm 包:
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" } }
}
把 manifest 读成在回答一个问题——这个包贡献什么?——答案是:一个插入或覆盖插件行的 patch 文件。
两个名词,两种 manifest
整个体系建立在一组刻意的双词汇上,混淆它们是经典新手错误:
- bundle 是你编写并发布的东西:声明
dsh.bundle的 npm 包,贡献一个配置层。 - profile 是用户启动的东西:位于
$DSH_HOME/profiles/<name>下、声明dsh.profile的目录,描述哪些 bundle、按什么顺序组成启动配置。
层的顺序决定最终合并出的配置——这就是 profile 是有序组合而非集合的原因。用户用 dsh plugin add 把你的 bundle 装进某个 profile,再以 dsh --profile <name> 启动。没有任何东西同时是 bundle 和 profile。
如果你用过 Web UI,那你一直在跑一个 profile——所谓装插件,就是往它里面加 bundle。
发布
- 发布 npm 包。
files数组必须包含 patch 文件——漏掉它是经典的“装完就坏”bug。 - 给仓库打上
dsh-plugintopic——这是生态的发现信号。 - 向社区 awesome 清单提交一行:对应分类下加一行,中英两份 README 都要加。
最后带一句原样的警告,来自社区清单的精神内核:安装插件等于以你自己的权限运行第三方代码。 工具审批不会给插件代码做沙箱。装前审源码;发布经得起陌生人审查的代码。生态跑得这么快,正是这个信任模型的直接后果——请善待它。
再往深处走
官方教程继续讲配置与工具注册(docs/user/develop/basic/),框架文档覆盖事件与生命周期。想要可运行的参照物,社区插件模板是清理过的起点——clone、改名、发布。
你的第一个插件离“别人装得到的东西”只剩十五行代码。这是架构故意的。