DeepSeek Harness101 服务与依赖 返回首页 官方文档 ↗

松耦合通知与协作决策

发出事实,不必认识监听者。

事件把插件解耦,同时用明确的分发模式约束广播、顺序、并发和拦截。

stats.ts
declare module '@deepseek-ai/cordis' {
  interface Events {
    'stats/report'(name: string, count: number): void
  }
}

ctx.on('stats/report', (name, count) => {
  console.log(`[stats] ${name} -> ${count}`)
})

ctx.emit('stats/report', 'tool_call', 1)

事件名和监听器签名共同组成契约

interface Events 声明合并让 ctx.emit 与 ctx.on 同时获得参数和返回值类型。它不会产生运行时代码。

命名stats/reportnamespace/action 让扁平命名空间保持可读
类型可见性import type {} from './stats.ts'只让 TypeScript 看到声明合并,不产生运行时导入
作用域ctx.on(name, listener)监听器属于当前 Fiber,卸载时自动移除

分发模式决定谁运行、是否等待、何时停止

模式是事件公开契约的一部分。开发时应以所属子系统页面生成的 cordis-surface 区块为准。

emitctx.emit(name, ...args)

同步广播给全部监听器。返回值和 Promise 都不会被等待或收集。

parallelawait ctx.parallel(name, ...args)

所有监听器并发运行,调用方等待它们全部完成。

serialawait ctx.serial(name, ...args)

按注册顺序等待。第一个有效返回值胜出并停止后续监听器。

bailctx.bail(name, ...args)

serial 的同步版本。null、false 和 undefined 表示继续。

waterfallctx.waterfall(name, ...args, next)

每层可以包装下游结果,也可以不调用 next() 来主动短路。

serial 与 bail 的停止条件

第一个不是 null、false 或 undefined 的结果成为最终值。零、空字符串和 true 都会停止后续监听器。

Waterfall 是环绕中间件,不是普通广播

监听器调用 next() 进入下游,再在返回路径上转换结果。不调用 next() 就代表当前监听器接管决定。

监听器 1await next()返回时转为大写
监听器 2命中 blocked直接返回替代值
×
默认逻辑未运行
waterfall-demo.ts
ctx.on('demo/transform', async (input, next) => {
  const downstream = await next()
  return downstream.toUpperCase()
})

ctx.on('demo/transform', async (input, next) => {
  if (input.includes('blocked')) return '** blocked **'
  return next()
})
观察型监听器也必须调用 next()

日志监听器忘记 next() 会静默吞掉所有下游默认行为。不调用只能用于有意的拦截、网关或否决。

agent/request插件可以替换模型调用配置
approval/request策略可以代替用户回答审批

Cordis 事件与持久化会话记录不是同一层

名字相似不代表可以直接 ctx.on。先判断目标属于运行时通信还是会话历史。

Cordis 事件agent/stepagent/requesttools/resultsession/event直接按官方签名监听
会话记录类型turn/*step/*tool/calltool/resultcompaction/*监听 session/event,再检查 event.type

ctx.on() 注册的监听器随插件自动消失

监听器属于当前 Fiber 的 effect。卸载、热替换或依赖消失时,无需手工 removeListener。

ctx.on('tools/result', handler)插件运行Fiber DISPOSED监听器已移除
tool-logger.ts
export function apply(ctx: Context) {
  ctx.on('tools/result', (exec, result) => {
    console.log(`[tool] ${exec.name}`)
    const text = result.content
      .map(block => block.type === 'text' ? block.text : '')
      .join('')
    console.log(`[tool result] ${text.slice(0, 100)}`)
  })
}

下一步,诊断配置组合与热替换