DeepSeek Harness 插件入门
本文是面向新手的插件入门:不预设任何框架知识,从"是什么"讲到"能干什么"、"怎么装"、"怎么学"。所有结论实测于 DSH 0.1.2-rc.1;想看拆到零件级的深度版,见姊妹篇《以学习为目的:我用一个桌宠插件,把 DeepSeek Harness 的插件系统拆到了零件级》。
一、插件是什么?
一句话:插件就是一个 JS 文件(或 npm 包),它告诉 harness"我是谁、我依赖什么、我要干什么",然后被装进正在运行的 harness 进程里,获得和内核平起平坐的能力。
打个比方:harness 是一部手机,官方出厂装好了电话、相机、浏览器(agent 循环、工具、会话管理)。插件就是 App——你自己写的、别人发布的,装上就多一个能力,卸掉就恢复原样,而且 App 崩了不影响手机系统。
官方自己的几十个能力包(dsh-skill、dsh-terminal-bash、dsh-webhook-github……)全部都是用同一套插件机制写出来的。没有"内核特权代码"和"插件代码"的区别——你写的插件和官方内核在架构地位上完全平等。
这带来一个初学者最容易忽略的事实:你不需要任何特殊许可。没有"签名""审核""白名单"——一个 file:// 路径指向你硬盘上的 JS 文件,它就是合法的插件。门槛低到"一个文件",天花板高到"订阅智能体的每一次心跳"。
二、用来干嘛?(三类用途)
你想做的事 | 用插件 |
让 harness 看到外部世界 | 订阅内部事件(智能体每调一次工具、每回一条消息都产生事件)、接 webhook(比如 GitHub PR 触发评审)、接 MCP 外部工具 |
让 harness 多一双手 | 注册新工具给模型用——比如一个"查公司内部数据库"的工具、一个"发钉钉消息"的工具 |
让 harness 多一个器官 | 提供新服务挂到总线上,供其他插件消费;或者改掉现有行为(换人设、换沙箱策略、换界面) |
真实例子(官方包里现成的):dsh-webhook-github 收到 PR 事件自动让智能体评审代码;dsh-schedule 让智能体定时干活;本文作者写的 dsh-pet-state 让桌面小猫实时看到智能体在干嘛。
如果非要提炼一个总纲:插件回答的是"harness 和世界之间缺一层胶水"的问题。世界是多样的(你的公司 API、你的手机、你的桌面、你的代码仓库),harness 的内核不可能内置所有对接——于是它把对接面做成了标准插槽,插件就是你自己造的插头。
三、插件长什么样?(最小形态就 8 行)
// my-plugin/index.js
export const name = "my-plugin"; // ① 我是谁
export const inject = []; // ② 我依赖哪些服务(可以先空着)
export function apply(ctx) { // ③ 我要干什么
console.log("我装进 harness 了!");
}就这三样,业内叫"三件套":
name:身份证。框架靠它识别插件、管理装卸与热更新。inject:购物清单——"我需要sessions服务、tools服务",框架会在启动你之前把这些服务备好递到你手上。你不用管它们从哪来、加载顺序谁先谁后。apply(ctx):干活的入口。ctx是一根"总线",上面挂着所有服务(ctx.sessions会话管理、ctx.tools工具注册表、ctx.skills技能表……)和所有事件。
一个重要的心智校准:apply 不是"被调用的函数",而是"装配动作"。你在里面干的事只有一类——往 ctx 总线上挂东西(挂监听器、挂服务、挂工具),挂完就退场,之后的世界是事件驱动的。如果你的 apply 里写了个死循环,你写的不是插件,是抢占宿主线程的寄生虫。
一个最小但能说明问题的 demo
8 行骨架只证明了"能装上",还没证明"有用"。下面这个 demo 只多 7 行,却把插件最有魔力的能力——实时看见智能体的心跳——变成了可验证的事实:
// my-plugin/index.js —— 事件记录器:智能体的每个动作都会留下一行日志
import { appendFileSync } from "node:fs";
export const name = "my-first-listener";
export const inject = []; // 只听不借,零依赖
export function apply(ctx) {
ctx.on("session/event", (session, event) => {
appendFileSync("/tmp/heartbeat.log",
`${new Date().toISOString()} ${event.type}\n`);
}, { global: true }); // global:所有会话的事件都要
}装上(下一节的两行 YAML),然后正常和智能体聊一句,再看 /tmp/heartbeat.log:
2026-09-14T03:12:01.412Z user/message ← 你开口
2026-09-14T03:12:01.473Z turn/start
2026-09-14T03:12:01.483Z step/start
2026-09-14T03:12:08.826Z reasoning-chunks ← 它在想
2026-09-14T03:12:19.995Z tool/call ← 它动手了
2026-09-14T03:12:20.141Z tool/result
2026-09-14T03:12:54.338Z text-chunks ← 它在回答你
2026-09-14T03:13:56.029Z turn/end ← 一轮结束这个 demo 说明问题的点在于:你写的 15 行代码,此刻运行在 harness 进程内部,和 agent 循环共享同一个事件总线——不需要轮询、不需要猜、不需要任何"接口对接",智能体的每一次思考、每一次调用工具,都主动流经了你写的函数。这就是插件和"从外部观察"的本质区别。
两个小讲究:日志写文件而不是 console.log,是因为 web profile 的 stdout 你看不到,文件是最直观的验证通道;appendFileSync 而非打缓冲,是因为 demo 追求"保存即见",真实插件应该用节流+原子写(第七节的桌宠会示范)。
四、怎么安装?
这是 DSH 插件系统最友好的地方——不用打包、不用发布。
方式一:本地文件(学插件就用这个)
在 profile 的补丁文件 ~/.dsh/profiles/web/cordis.patch.yml 里加两行:
- insert:
- id: my-plugin
name: file:///你电脑上/my-plugin/index.js保存,约 2~5 秒后插件就活在运行中的 harness 里了。零安装命令,一个 file:// 路径直指你硬盘上的 JS 文件。
方式二:npm 包(用别人写好的插件)
dsh plugin --profile web add @deepseek-ai/dsh-webhook-github然后在同一个 patch 文件里 name 写包名即可。
方式三:什么都不装,只改配置
很多定制根本不需要写 JS——patch 文件本身就能改配置:换系统提示词、改端口、开关某个功能。可以理解为"插件系统提供了一个纯配置的档位"。
卸载 = 删掉那两行 YAML。热更新 = 改了插件代码后,把 URL 加个版本号 index.js?v=2 再递增(这是实测出来的最大的坑:不改 URL 的话 harness 会用缓存的旧代码——loader 只在模块 URL 变化时才重新加载,内容变化它不管)。
五、具体能干什么?(能力清单)
进入 apply(ctx) 之后,你手上有这些牌:
能力 | 怎么做 | 例子 |
订阅智能体的一切动态 |
| 每次工具调用、每条消息、每次思考都流经这里 |
注册新工具给模型用 | 用 | 让模型能"查天气""操作你的智能家居" |
注册新命令给用户用 | 类似地挂到用户命令层 | 类似斜杠命令 |
提供新服务 |
| 别的插件可以 |
按野心从小到大,可以分成四级,这也是作者推荐的学习纵深:
- 观察者(只读):订阅事件流,看清智能体的每一次心跳。桌宠就是这一级。
- 造工具:用
defineTool给模型加一双手。 - 拧旋钮:连代码都不写,用 patch 改配置改变 harness 行为。
- 造地基:提供新服务让别的插件消费——到这一级你在给生态写代码。
一个直观的能力天花板参照:桌宠插件——订阅全局事件流、跑状态机、写文件、定时器、可热重载——总共 160 行,其中真正核心的逻辑不到 60 行。
框架同时也在替你兜底:插件监听器抛错只记一条警告、会话照常跑;插件的文件写入照样过沙箱审批。扩展性从不以稳定性为抵押——这是你敢随便写、随便试的底气。
六、它和 Skill 是什么关系?
Skill(技能)是 DSH 的另一个扩展机制,经常和插件放在一起比。一句话分清:
Skill 改变模型"知道什么"(一份指令文档),Plugin 改变运行时"能做什么/看得到什么"(一段真代码)。
Skill | Plugin | |
本质 | Markdown + frontmatter | JS 模块 |
进入哪里 | 模型的上下文(按需加载的指令) | harness 进程(真实运行的代码) |
适合 | 部署流程、代码规范、回复格式 | webhook、新工具、事件监听、新服务 |
触发 | 模型按目录自主判断,或用户 | 随 harness 启动,事件驱动 |
想让模型学会你的部署流程 → Skill。想让 harness 收 GitHub webhook、多一个查数据库的工具 → Plugin。
有意思的是它们共享同一套地基:skill 的注册表本身就是某个插件(dsh-skill)提供的 ctx.skills 服务。所以学懂插件,skill 对你而言只是"内容",而不是另一套系统。
七、毕业实例:我刚刚做的桌面宠物
用这套知识实际造了个东西——一只能看到智能体在干活的桌面 ASCII 猫。它思考时头顶冒泡泡,跑 bash 时抡🔨,单轮调 15 次工具会流汗💦,工具报错炸毛❗,两分钟没人理就蜷起来💤。这就是第三节那个 15 行 demo 的完全体,总共约 160 行插件 + 160 行 Swift 渲染。
它由三个文件构成
① dsh-pet-plugin/index.js 插件(本文全部知识的应用)
② ~/.dsh/profiles/web/cordis.patch.yml 挂载(第四节的两行 YAML)
③ desktop-pet/Sources/main.swift 显示端(与 harness 零耦合,只读一个 JSON)① 插件端:三件套 + 状态机 + 原子写
export const name = "dsh-pet-state";
export const inject = ["sessions"]; // 声明依赖:我要会话系统就绪后再启动
export function apply(ctx) {
const state = { state: "idle", tool: null, toolCalls: 0, … };
// 订阅全局事件流 → 状态机裁决
ctx.on("session/event", (session, event) => {
switch (event?.type) {
case "user/message": setState("listen", 2000); break; // 👂 竖耳2秒
case "reasoning-chunks": if (state.state !== "work") setState("think"); break;
case "tool/call":
state.toolCalls += 1;
state.sweat = state.toolCalls > 15; // 💦 彩蛋
state.tool = event.data?.name;
setState("work"); break;
case "tool/result":
if (detectFailure(event.data)) setState("shock", 2000); break; // ❗
case "turn/end": setState("cheer", 2000); break; // 🎉
}
}, { global: true });
// ctx.effect:心跳定时器,插件卸载时自动回收
ctx.effect(() => {
const t = setInterval(tick, 1000); // 瞬态过期→idle;静默2分钟→sleep
return () => clearInterval(t);
});
}状态机每变一次,就把完整状态原子地写进 ~/.dsh/pet-state.json(250ms 节流 + tmp/rename 原子替换——读端是另一个进程,绝不能让它读到半个 JSON)。
② 挂载:第四节的两行 YAML 原样上场
- insert:
- id: dsh-pet-state
name: file:///…/dsh-pet-plugin/index.js?v=4那个 ?v=4 就是第四节说的热更新坑——这是我第四次修改插件代码,每次递增一次。保存 YAML 后约 3 秒插件上线。
③ 显示端:另一个进程,只认识 JSON
Swift 写的猫窗口每 0.125 秒读一次 pet-state.json,按状态换 ASCII 帧(idle 眨眼、work 敲键盘、shock 炸毛……),按工具名举道具(bash🔨 write🖌️ web_search🔭)。它对 harness 一无所知——不知道 Cordis、不知道事件流,只认识那份 JSON 的字段契约。
这个设计里藏着的插件哲学
发布端与显示端解耦,靠一个 JSON 文件握手。 为什么不直接在插件里画窗口?因为插件的宿主是 harness 进程,显示端有自己的生命周期(退出、重启、多开);而一个磁盘上的 JSON 文件是最朴素的进程间契约——谁都能读,harness 升级不受影响,你甚至可以再写一个浏览器版、手机版猫,读同一个文件。
它顺便验证了插件的边界纪律:插件监听器抛错只警告不崩溃(有一次状态机写崩了,harness 和会话毫发无伤,猫坏了修猫就行);插件文件写入照样被沙箱审批拦截过一次(patch 文件在工作区外);热更新让猫的状态机迭代了四个版本,harness 一次都没重启。
从第三节的 15 行 demo 到这里的 160 行,多出来的每一行——状态机、节流、原子写、effect 清理、版本号——都不是插件知识,而是普通工程知识。这正是入门该建立的手感:插件的学习曲线短,门槛之后全是熟悉的工程世界。
八、动手前的最后一张图
把全文收进一张图:
你写的 JS 文件(name / inject / apply 三件套)
│ 一行 YAML(patch 文件里的 file:// 或包名)
▼
正在运行的 harness 进程
│ 框架按 inject 备好服务,调用 apply(ctx)
▼
ctx 总线 ──┬── 订阅事件:智能体的每次思考/调用/回复流经你
├── 注册工具:模型下一轮就能用你的工具
├── 挂新服务:别的插件可以依赖你
└── ctx.effect:卸载时自动回收,不留僵尸记住三个关键词就够了:三件套(你是谁/要什么/干什么)、总线(一切能力的交换所)、一行 YAML(从硬盘到进程的全部旅程)。第七节的猫就是这三个关键词乘以一点工程耐心。
实测环境:DSH 0.1.2-rc.1 / Node 24 / macOS 26(Apple Silicon)。深度续篇:《以学习为目的:我用一个桌宠插件,把 DeepSeek Harness 的插件系统拆到了零件级》;概念七问版:《DSH 插件入门:用七个问题和一只猫,建立你的心智模型》。
