跳到主要内容
路径文档

事件系统

一句话版:DSH 有两套事件。Cordis 进程内事件(ctx.on,插件间通信、hook 链)与 SessionEvent 会话日志(持久化、事件溯源)。它们不是一回事:别把"监听"和"持久化"混为一谈。

这一篇是 DSH"插件间怎么通信、会话怎么被记录"的总纲。读完你清楚:什么时候用 ctx.on,什么时候用 session.append,事件都有哪些,(复数的 tools/)(单数的 tool/) 有什么区别。

一、两套事件平面

Cordis 进程内事件SessionEvent 会话日志
载体内存,ctx.on/ctx.emitsession append-only 日志(默认 zstd 压缩)
语义瞬时通知、hook 链事件溯源、可恢复的会话事实
生命随插件卸载自动注销持久化,重启不丢
agent/statustools/pre-executesession/createduser/messagerequest/headertool/calltool/result

关键:会话日志("唯一真源")承载遥测、投影、恢复;Cordis 事件是进程内反应式扩展。别以为挂 ctx.on 就会落持久化:要持久化用 session.append

二、五类分发给式(ctx.emit/*)

Cordis 事件的分发模式决定监听器怎么被组合:

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

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

三、agent/* :agent 生命周期与控制面

事件时机 / dispatch
agent/created / agent/disposed创建 / 销毁(配对)
agent/status运行状态变迁
agent/pre-step每 step 装配(waterfall)
agent/request请求装配替换(waterfall)
agent/request-error模型请求失败恢复(waterfall,dsh-llm-retry 挂这)
agent/turn-stopping轮次开始停止(serial)
agent/error / agent/session-start错误 / 会话开始
agent/inbox/*inserted / claimed / discarded(见主循环)

agent/* 是控制面;真正的对话内容走 session 日志。

四、tools 流水线(Cordis,复数 tools/)

每次工具调用的扩展点,从 Agent 主循环 延伸:

tools/result(复数,Cordis 通知)≠ tool/result(单数,会话日志事件):前者是进程内通知,后者是持久化会话事件。这是最容易混的一对。

五、SessionEvent 会话日志词汇

单数 tool/,持久化。一个会话日志:

{"type":"session","version":0,"id":"...","createdAt":0}
{"type":"user/message","seq":1,"data":{...}}
{"type":"request/header","seq":2,"data":{...}}
{"type":"tool/call","seq":3,"data":{...}}
{"type":"tool/result","seq":4,"data":{...}}

常见类型:user/messageassistant/messageassistant/chunkrequest/headerrequest/contexttool/calltool/resultstep/startturn/startturn/endsession/end-seedcompaction/*permission/presetfeedback/recordtodo/writegoal/change。完整目录见 $SRC/packages/core/session/src/known-event-types.ts

事件信封字段(每个 event 可能有):

字段含义
seq单调递增的持久化排序键
sourceEventSeqs引用的源事件 seq(chunk→message、压缩替换→被遮蔽条目)
surfaceOpappend/replace,仅 user/message/assistant/message/tool/result 合法
ignorable读到不认识可跳过;缺失 = 必选,未知类型拒绝重建

六、事件类型约定与格式闸门

  • seq 单调递增(排序键)
  • 格式版本闸门:SESSION_FORMAT_VERSION 不匹配(final > 支持)时,加载直接拒绝(no-migration),提示升级 harness
  • 未知类型:无 ignorable → 拒绝;有 ignorable → 跳过
  • 遥测 / 投影 / 恢复消费同一事件流(这就是"日志是唯一真源")

七、插件的两类参与

你想怎么做
监听反应式扩展ctx.on('tools/result', handler)(Cordis)
发布持久事实session.append(type, data),然后 await ctx.sessions.flush(session)
自定义持久事件需先 declare module '@deepseek-ai/dsh-session/types' augment SessionEventMap,否则 append 拒绝未知类型
自定义 Cordis 事件ctx.emit,随插件卸载自动清理

详细的双事件平面 + 代码见 监听事件

八、消费同一流的几个 seam

seam用事件流干什么
session-persistence存储/重载(eager write-behind)
session-projection派生视图(缓存)
session-telemetryOTel 导出
session-title标题生成
UIsession/event + agent/* 控制事件渲染对话

九、验证

# 事件流 = 完整会话日志(默认 zstd 压缩、两级目录)
zstdcat ~/.dsh/sessions/*/*/session.jsonl.zstd | jq -r '.type' | sort | uniq -c | sort -rn | head -12

# 看 agent 控制事件
zstdcat ~/.dsh/sessions/*/*/session.jsonl.zstd | grep -E '"agent/' | head

下一步