HoRain云--DeepSeek Harness 事件系统
发布时间:2026/8/19 9:40:24 作者:尧图编辑部 阅读量:1,286

插件之间怎么松耦合通信事件就是 Cordis 的通信核心机制。Harness 大量使用事件来实现可扩展的扩展点这一篇把五种分发模式讲清楚。基本用法事件分监听与触发两端。实例// 监听事件注册一个回调ctx.on(event-name, (payload) {// 处理事件})// 触发事件广播给所有监听器ctx.emit(event-name, payload)通过 ctx.on 注册的监听器会在插件卸载时自动移除。五种分发模式Cordis 提供多种分发模式适用于不同的交互契约。模式分发方法是否 await顺序是否有返回值典型场景emit广播ctx.emit否按注册顺序否通知类所有监听者观察bail短路ctx.bail否按注册顺序是决策类第一个有效返回值胜出serial顺序ctx.serial是按注册顺序是分阶段初始化按序执行并等待waterfall流水线ctx.waterfall否按注册顺序是处理链逐层包装下游返回值parallel并行ctx.parallel是全部并行否扇出多个监听者并行处理每个事件有且只有一种分发模式且只能通过对应方法分发。官方入门文档把 waterfall 标为「不 await」但由于监听器通常是异步函数教程示例里普遍写 await ctx.waterfall(...) 来等待整条处理链的最终结果。记住这个差别即可两种写法都不算错。emit广播所有监听器同步执行返回值会被忽略。实例// 触发方广播一条「就绪」消息ctx.emit(my-plugin/ready, { id: runoob-worker-1 })// 监听方收到就绪消息打印日志ctx.on(my-plugin/ready, ({ id }) {console.log(${id} is ready)})bail短路监听器按顺序运行第一个不是 null、false 或 undefined 的返回值会成为最终结果。实例// 分发方做一次检查取第一个「有意见」的结果const result ctx.bail(some-check, input)// 监听方命中拦截条件就返回 blocked否则继续ctx.on(some-check, (input) {if (shouldBlock(input)) return blocked// 返回 null / false / undefined 表示「我没意见」让后面的监听器继续})serial顺序执行监听器按注册顺序依次执行并等待异步结果。第一个不是 null、false 或 undefined 的返回值会终止后续执行。实例// 顺序执行一段分阶段初始化等每一阶段完成await ctx.serial(setup-phase, context)waterfall流水线每个监听器可以包装下游返回值形成处理链。监听器接收 (...args, next)调用 next() 会执行下游监听器下游返回值通过 next() 返回给当前包装层。实例// 分发方初始值就是 input传给第一个监听器const output await ctx.waterfall(my-plugin/transform, input, async () input)// 监听方必须调用 next()拿到下游结果后做一次加工再返回ctx.on(my-plugin/transform, async (_input, next) {const downstream await next()return downstream.trim()})waterfall 监听器必须调用 next()。不调用 next 会短路整个流水线这是故意为之的设计用于实现拦截或网关逻辑。策略监听器在拥有决策权时可以不调用 next() 直接返回从而拦住整条链。仅做标注或观察的监听器则必须委托给下游。类型安全的事件用 TypeScript 声明合并为事件提供类型安全。实例// 文件路径scratch-plugin/src/events.tsimport deepseek-ai/cordis// 声明合并注册事件名与签名declare module deepseek-ai/cordis {interface Events {my-plugin/ready: (payload: { id: string }) voidmy-plugin/check: (input: string) boolean | undefinedmy-plugin/transform: (input: string, next: () Promisestring) Promisestring}}// 此后 ctx.on(my-plugin/ready, ...) 与 ctx.emit(my-plugin/ready, ...)// 的参数会被自动推断拼错事件名或参数类型都会在编译期报错Cordis 事件与会话记录Harness 的 Cordis 事件遵循 namespace/action 命名例如 agent/step、agent/request、agent/request-error、tools/result 和 session/event。注意区分两类事件事件是什么如何观察agent/step、tools/result 等Cordis 事件实时分发直接 ctx.on(tools/result, ...)turn/*、step/*、tool/call、tool/result、compaction/*持久化的会话事件类型监听 session/event检查 event.typeturn/*、step/*、tool/call、tool/result 和 compaction/* 是持久化的会话事件类型不是同名 Cordis 事件。需要观察它们时监听 session/event 并检查 event.type。动手示例日志插件官方文档用下面这个插件记录工具调用与工具结果监听的是 Cordis 事件 tools/result。实例// 文件路径scratch-plugin/src/tool-logger.tsimport type { Context } from deepseek-ai/cordisimport deepseek-ai/dsh-toolsexport const name tool-loggerexport function apply(ctx: Context) {// 监听 tools/result 事件每次工具执行完成都会触发ctx.on(tools/result, (exec, result) {// 打印工具名与参数console.log([tool] ${exec.name}(${JSON.stringify(exec.arguments)}))// 把结果内容里的文本块拼起来只打印前 100 个字符const text result.content.map(block block.type text ? block.text : ).join()console.log([tool result] ${text.slice(0, 100)})})}这个插件在 Agent 每次调用工具后把工具名、参数和结果摘要打到终端。监听器通过 ctx.on 注册插件卸载时会被自动移除。小结自测五种分发模式覆盖「广播、决策、按序、流水线、并行」五类契约waterfall 必须调用 next()declare module 合并让事件名与参数有类型保障。自测一下五种分发模式分别用哪个方法触发哪些有返回值waterfall 监听器不调用 next() 会怎样这是 bug 还是设计turn/start 这类持久化会话事件应该监听哪个事件、检查哪个字段