跳到主要内容
路径文档

监听事件

一句话版ctx.on(event, handler) 让插件在会话/工具/agent 生命周期上挂钩:最常用的是工具流水线(tools/pre-execute 门禁、tools/post-execute 改写)。这是绝大多数"行为扩展"的入口。

一、监听基础

import { Context } from '@deepseek-ai/cordis'

export const name = 'event-watcher'

export function apply(ctx: Context) {
// 会话生命周期
ctx.on('session/created', (session) => {
console.log('新会话:', session.id)
})

// 工具门禁(可 veto):签名 (execution, next),返回 PreToolDecision
ctx.on('tools/pre-execute', async (execution, next) => {
if (execution.name === 'forbidden_tool') {
return { kind: 'deny', reason: '此工具被策略禁止' }
}
return next() // 放行,交给后续监听器
})

// 工具结果改写
ctx.on('tools/post-execute', async (execution, result, next) => {
const decision = await next()
// 检查结果、附加信息、或改判
return decision
})
}

二、事件钩子模式

模式用途示例
监听通知只观察ctx.on('agent/error', ...) 记录
门禁拦截/放行tools/pre-execute 返回 deny
环绕包一层tools/execute 加超时/指标
改写改结果tools/post-execute 附加上下文

三、五种 dispatch 模式

模式语义典型
emit同步触发,不等agent/error
parallel异步并行,await 全部session/flush
serial串行依次部分 agent/*
bail首个 bail 即停
waterfall连成 next()tools/pre-executeagent/request

waterfall 必须调 next():漏调会短路整条链。发布侧用 ctx.emit()/ctx.parallel()/ctx.waterfall()

四、可监听事件目录(节选)

事件
agent 生命周期agent/created /disposed /status /session-start;waterfall 的 /pre-step /request /request-error;/turn-stoppingagent/inbox/*
会话session/created /disposed /event /flush
工具流水线tools/pre-execute execute post-execute(waterfall)、tools/code-dispatch-logtools/result(emit)
跨域fs/edit-intent fs/write-intentllm/streamapproval/requestgoal/changedsettings/document-updatedcredentials/updatedcommands/change

完整矩阵见 $SRC/docs/event-producer-consumer.md(含每个事件的 dispatch 模式)。

五、双事件平面(别混用)

平面 A:Cordis 进程内平面 B:SessionEvent 持久日志
何时用瞬时信号、插件通信需要持久化/可恢复的事实
发布ctx.emit('my/event', ...)session.append('my/thing-happened', {...})
订阅ctx.on('my/event', handler)只能经 session/event 观察

tools/result(宿主内通知)≠ tool/result(会话日志事件)。

自定义持久事件要先声明(augment 进 SessionEventMap),否则 session.append 拒绝未知类型:

declare module '@deepseek-ai/dsh-session/types' {
interface SessionEventMap {
'my-plugin/thing-happened': { type: 'my-plugin/thing-happened'; detail: number }
}
}

下游监听走 session/event,不能直接 ctx.on('my-plugin/thing-happened'):

ctx.on('session/event', (session, event) => {
if (event.type === 'my-plugin/thing-happened') {
console.log('thing:', event.detail)
}
})

六、实战:一个"策略门禁"插件

场景:某环境禁止模型调用 web_fetch,但允许 web_search

export function apply(ctx: Context) {
ctx.on('tools/pre-execute', async (execution, next) => {
if (execution.name === 'web_fetch') {
return { kind: 'deny', reason: '此环境策略禁止 web_fetch' }
}
return next()
})
}

packages/guard不是这个门禁:它是循环卫生守卫族(repeat-tool-reminder / timeout-policy),职责不同。allow/deny/ask 通过 tools/pre-executePreToolDecision 实现。

七、验证

# 事件流都在会话 JSONL 里(默认 zstd、两级目录)
zstdcat ~/.dsh/sessions/*/*/session.jsonl.zstd | jq -r '.type' | sort | uniq -c

下一步