跳到主要内容
路径文档

事件系统

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

审计基线 0.1.5-alpha.1 @ 5dda764ed3:事件名、分发模式与信封字段均与官方源码逐点核对。

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

一、两套事件平面​

Cordis 进程内事件SessionEvent 会话日志
载体内存,ctx.on/ctx.emitsession append-only 日志(默认 zstd 压缩)
语义瞬时通知、hook 链事件溯源、可恢复的会话事实
生命随插件卸载自动注销持久化,重启不丢
例agent/status、tools/pre-execute、session/createduser/message、request/header、tool/call、tool/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-execute、agent/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":3,"id":"...","createdAt":0,"isSeeded":false,"delegationDepth":0}
{"type":"user/message","seq":1,"time":0,"data":{...},"surfaceOp":"append"}
{"type":"request/header","seq":2,"time":0,"data":{...}}
{"type":"tool/call","seq":3,"time":0,"data":{...}}
{"type":"tool/result","seq":4,"time":0,"data":{...},"surfaceOp":"append"}

常见类型:system/message、user/message、assistant/message、assistant/attempt、request/header、request/context、tool/call、tool/result、step/start、step/end、turn/start、turn/end、session/end-seed、compaction/*、permission/preset、feedback/record、todo/write、goal/change、llm/retry、llm/retry-started。完整目录见 $SRC/packages/core/session/src/known-event-types.ts。

事件信封字段(按事件类型可选):

字段含义
seq单调递增的持久化排序键
timeUnix epoch 毫秒
sourceEventSeqs引用的源事件 seq(压缩替换→被遮蔽条目);只存在于 surface 事件
surfaceOpappend 或 { op:'replace', startSeq, endSeq },仅 system/message/user/message/assistant/message/tool/result 合法
ignorable读到不认识可跳过;缺失 = 必选,未知类型拒绝重建

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

  • seq 单调递增(排序键)
  • 格式版本闸门:日志首行的 version 比当前 SESSION_FORMAT_VERSION(=3)新时,加载直接拒绝(提示升级 harness);已发布的 v0/v1/v2 由格式目录迁移到 v3
  • 未知类型:无 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

下一步​