DeepSeek Harness 插件使用指南
dsh 没有需要打补丁的特权内核——一切都是可替换的配置层。先搞懂下面这五件事,其余的都能自己推出来。
插件是什么
dsh 里没有需要打补丁的内核。模型适配器、工具、会话日志,连 agent 循环本身都是插件——所以每一样都能从配置里换掉。
一个插件就是一个导出 apply 函数的模块。框架加载时调用它,传进来一个 ctx(上下文),你通过 ctx 注册能力。
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello-plugin'
export const inject = ['tools'] // 依赖的服务就绪后才会调用 apply
export function apply(ctx: Context) {
ctx.tools.register(/* ... */)
}ctx 是服务的容器。每个服务占一个稳定的键——ctx.tools、ctx.llm、ctx.sessions、ctx.agents——别的插件按键去找服务,而不是 import 具体实现。这就是替换一个提供方就能改变整个产品行为的原因。
注册是可撤销的
通过 ctx 注册的一切——事件监听、工具、定时器——在插件卸载时自动清理,你不需要手动 removeListener。需要手动清理的资源用 ctx.effect() 交回一个清理函数。
export function apply(ctx: Context) {
ctx.effect(() => {
const timer = setInterval(() => console.log('heartbeat'), 5000)
return () => clearInterval(timer) // 插件卸载时执行
})
}这个性质是整个架构的地基:因为没有一个注册是永久的,所以插件可以在运行时挂载和卸载,agent 甚至能在对话中间写一个插件把自己装上去。
装一个插件
插件装进 profile,不是装进 dsh。搞清楚 profile 是什么,安装和卸载就都是一条命令。
profile 是 $DSH_HOME/profiles/<名字> 下的一个目录,描述一套可启动的组合:它列出自己叠了哪些包,存放自己装的插件,并保存你自己的 cordis.patch.yml。dsh 随发行版带了 web 和 headless 两个模板。
先跑起来
npx @deepseek-ai/dsh web # Web UI → http://127.0.0.1:3080需要 Node ^22.19 或 >=24。低于这个版本 dsh 起不来。
装、看、卸
dsh plugin --profile web add dsh-hello-plugin
# 装完先别启动,看看它往配置树里插了什么:
dsh --profile web --dump-config
dsh plugin --profile web remove dsh-hello-plugindsh plugin 本质是把参数转发给 profile 目录里的 pnpm,所以 pnpm 的子命令都能用(add、remove、update、list)。装完它会检查这个包有没有声明 dsh.bundle——有就把它追加进 profile 的层列表,没有就只当普通依赖装着并打印一条警告。
--dump-config 是你最重要的排查工具。它打印实际组合出来的配置树;你装的插件如果没出现在里面,问题就在安装这一步,不在插件代码里。
层与覆盖
跑起来的 dsh 是一棵插件树,由若干 patch 层按顺序叠成。理解叠放顺序,你就能改掉任何一行——包括别人插件里的行。
配置在一个空的根之上按这个顺序逐层叠加:
- 1profile 的 dsh.profile.bundles 列出的每个组合包的 patch,按列表顺序——@deepseek-ai/dsh-base 永远是第一层
- 2profile 自己的 cordis.patch.yml
- 3$DSH_HOME/cordis.patch.yml(各 profile 共享的机器本地偏好)
- 4每个 --patch <路径> 覆盖层,按命令行顺序
一条 patch 按 id 定位某一行,然后替换它。后叠的层按行胜出。
patch 替换的是目标行的整个 config 值,不是深度合并各个键。只想改一个字段,也必须把这一行需要的所有键重新写一遍——这是最常见的踩坑点。
# ~/.dsh/profiles/web/cordis.patch.yml
# 覆盖默认模型(必须重述整行 config)
- replace:
- id: agent-default-model
name: '@deepseek-ai/dsh-agent-default-model'
config:
provider: deepseek-official
model: deepseek-v4-flash推论有两个。作为插件作者:你的 patch 可以按 id 覆盖前面层的行,但要重述整行;同时也要预期用户会在他自己的层里覆盖你,所以给出的默认值应该是大多数人会保留的那种。作为用户:任何一行你都能改,不需要动别人的包。
本地开发时的临时层
dsh web --patch ./my-plugin/cordis.yml临时层里的插件路径必须写绝对路径。patch 文件只贡献配置,不改变 loader 解析模块用的目录。
装之前该问什么
插件运行在 dsh 进程里,权限和 dsh 一样大。生态只有几天,装之前值得花一分钟看清楚。
从 npm 装 vs 从 GitHub 装
从 npm 装的是作者发布时已经构建好的产物,安装过程不执行这个包的任何代码。从 git 装拉到的是源码,pnpm 需要跑这个包的 prepare 脚本才能得到能加载的产物。
pnpm 10 默认拒绝执行 git 依赖的构建脚本,所以第一次 add 会失败。要装成功,你必须在这个 profile 的 pnpm-workspace.yaml 里写 allowBuilds: { 包名: true }。这一步的真实含义是:允许这个包的代码在安装时于你的机器上执行,而且不在 agent 的任何沙箱里。只对源码可信的包这样做,并把 commit 锁死(github:you/plugin#<sha>),否则作者之后的推送可以悄悄改变实际运行的东西。
目录里的复核结果怎么读
- 1patch-shipped 失败:包声明了配置层,但那个文件根本没打进发布包——装上去加载会失败。
- 2rows-resolvable 失败:patch 里的插件行指向作者本机的绝对路径。这是照抄教程发布造成的,装了必然坏。
- 3client-half-shipped 失败:声明了浏览器界面但 ./client 导出缺失。这个最阴——装完一切正常,界面永远不出现。
- 4prebuilt 失败:只发了源码,安装要执行构建脚本(见上)。
- 5版本过期:依赖区间已经排除了当前发布的 dsh 版本。
这些检查看的是包装是否正确,不是代码是否安全,也不代表插件功能好用。它们能排除"装了必坏",不能替你判断"值不值得装"。
写一个并发布
从一个文件到能被 dsh plugin add 装上的 npm 包,中间只差一份 manifest 和一个 patch 文件。
三个文件
hello-plugin/
├── package.json # 声明 dsh.bundle
├── cordis.patch.yml # profile 列入这个包时应用的层
└── index.js # patch 行引用的插件模块{
"name": "dsh-hello-plugin",
"version": "0.1.0",
"type": "module",
"main": "index.js",
"files": ["index.js", "cordis.patch.yml"],
"keywords": ["dsh-plugin", "deepseek-harness"],
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}# cordis.patch.yml — 按包名引用,不是相对路径
- insert:
- id: hello
name: dsh-hello-pluginfiles 数组必须包含 cordis.patch.yml。漏掉它是这份目录里最常见的失败——包能装上,但那个层根本不存在。
发布
pnpm publish # 发布时构建好产物,用户安装无需任何构建授权
dsh plugin --profile web add dsh-hello-plugin发布到 npm 而不是让用户从 git 装,用户就不需要开 allowBuilds。TypeScript 包要么在 publish 前构建好 lib/,要么提供一个自包含的 prepare 脚本——但后者会把构建授权的负担推给每一个用户。
让它被找到
- 1package.json 的 keywords 里加 dsh-plugin —— 这份目录就是按它抓的
- 2给 GitHub 仓库打 dsh-plugin 话题
- 3填 description、license、repository —— 目录评分会算这些,而且这是读者判断的唯一依据
双语描述可以写进 dsh.plugin.summary(en / zh-CN 两个键),显示名写 dsh.plugin.displayName。这个约定还不是官方的,目前 719 个插件里只有 7 个填了——填了的会在目录里显示得完整得多。
接下来:挑一个插件装上
719 个可安装插件,688 个通过全部七项结构复核。每一条都标注了它挂载什么、依赖版本是否还有效、以及安装时会不会在你机器上执行代码。