首页 / DeepSeek Harness 入门教程 / 事件发布与订阅

DeepSeek Harness 入门教程

事件发布与订阅

本教程共 32 篇 · 第 23 篇 · 更新于 2026-08-15 · 约 5 分钟阅读

事件发布订阅emitwaterfall跨插件通信扩展点解耦

本节目标:学会声明事件类型、发布与监听事件,掌握五种分发模式,并知道事件与服务怎么选。

事件是什么

服务支持直接调用;事件让插件不知道谁在监听就能发出通知。harness 用事件处理工具结果、模型请求、审批决定等交互。监听方通过 ctx.on 注册回调,触发方通过 ctx.emit 广播,两边互不引用。

发布与监听

// 触发方:广播一条消息
ctx.emit('my-plugin/ready', { id: 'worker-1' })

// 监听方:收到后处理
ctx.on('my-plugin/ready', ({ id }) => {
  console.log(`${id} is ready`)
})

ctx.on 属于 effect,监听器随插件卸载自动移除,不需要手动维护 removeListener。

事件类型定义

事件名和监听器签名可以用声明合并注册,让 ctx.emitctx.on 都有完整类型,拼错事件名或参数类型会在编译期报错:

declare module '@deepseek-ai/cordis' {
  interface Events {
    'my-plugin/ready': (payload: { id: string }) => void
    'my-plugin/check': (input: string) => boolean | undefined
    'my-plugin/transform': (input: string, next: () => Promise<string>) => Promise<string>
  }
}

命名遵循 namespace/action 约定,让扁平的事件命名空间保持易读。harness 的实例:agent/stepagent/requesttools/resultsession/event

五种分发模式

事件采用哪种模式是契约的一部分,决定监听器能否返回值、能否并发、能否短路:

模式调用语义
emitctx.emit()同步广播,不等待、不收集返回值
parallelawait ctx.parallel()所有监听器并发运行,一同等待
serialawait ctx.serial()按序运行并等待;第一个非 null/false/undefined 返回值胜出
bailctx.bail()serial 的同步版本
waterfallawait ctx.waterfall()环绕中间件,见下

每个 harness 事件都在所属子系统页面上记录它的分发模式。

waterfall:转换或短路

waterfall 是实现拦截的模式。每个监听器收到参数和一个 next() 续延,可以转换 next() 的返回值,也可以不调用 next() 直接返回——这会把链条其余部分短路,Cordis 称为否决:

ctx.on('demo/transform', async (input, next) => {
  if (input.includes('blocked')) return '** blocked **'
  return next()
})
Warning

只负责观察或标注的 waterfall 监听器必须调用 next()。不调用代表有意短路——日志监听器忘记调 next(),会悄无声息吞掉所有下游默认行为。

harness 用 waterfall 处理可包装或可回答的决策:agent/request 允许插件替换模型调用配置,approval/request 允许策略代替用户作答。

跨插件通信示例

一个统计服务在每次变化时发出事件,另一个插件负责打印。两个插件互不知道对方存在,由服务注册表和事件连接:

export class StatsService extends Service {
  private counts = new Map<string, number>()

  constructor(ctx: Context) {
    super(ctx, 'stats')
  }

  bump(name: string) {
    const next = (this.counts.get(name) ?? 0) + 1
    this.counts.set(name, next)
    this.ctx.emit('stats/report', name, next)
  }
}

// 监听方插件
export const name = 'reporter'
export const inject = ['stats']

export function apply(ctx: Context) {
  ctx.on('stats/report', (name, count) => {
    console.log(`[stats] ${name} -> ${count}`)
  })
  ctx.stats.bump('tool_call')
}

组合加载后输出 [stats] tool_call -> 1。想给统计加新消费者?再写一个监听插件即可,发布方一行不用改。

harness 的真实事件

三大事件域(会话 / Agent / 能力)在第 12 章讲过,这里不再复述。写代码时只要记住:harness 里每个事件都有明确的分发模式,写监听器前先查它属于哪一域、什么模式,再决定用 ctx.on 还是哪种 emit。

Note

turn/*step/*tool/callcompaction/*持久化的会话事件类型,不是同名 Cordis 事件。要观察它们,监听 session/event 再检查 event.type

事件 vs 服务怎么选

两条通信通道各有定位:

  • 直接调用、要拿返回值:用服务。调用方明确知道要谁的能力。
  • 通知广播、不关心谁响应:用事件。发布方不知道监听者存在。
  • 拦截/改写/决策链:用 waterfall 事件,这是 harness 扩展点的主形态。
  • 可替换实现:用服务(换提供方),事件负责观察与策略。

一条经验:能力提供用服务,能力观察与策略用事件。两者经常组合——服务负责干活,事件负责把干完的活广播出去,并允许其他插件在干活的路上插手。

小结

  • 事件 = 不知道谁在听也能广播的通知;ctx.on 订阅、ctx.emit 发布。
  • 事件类型用声明合并注册,拼错事件名编译期就报错。
  • 五种分发模式:emit / parallel / serial / bail / waterfall。
  • waterfall 是环绕中间件;观察型监听器必须调 next()
  • 能力提供用服务,能力观察与策略用事件。