模型: claude-opus-4-6 (anthropic/claude-opus-4-6)
生成日期: 2026-02-17
上一节我们介绍了事件总线的架构设计,本节将系统性地梳理 OpenCode 中定义的所有事件。截至 1.2.5 版本,OpenCode 共定义了 46 个事件,分布在 24 个源文件中,覆盖了从会话管理到文件监控、从权限请求到 IDE 安装的各个方面。
理解这些事件的含义和时机,对于后续章节(特别是第 12 章 TUI 实现、第 13 章 Plugin 系统)的学习至关重要——因为 TUI 的界面刷新和 Plugin 的 Hook 机制都高度依赖事件驱动。
11.2.1 Session 相关事件
Session 是事件最密集的模块,共定义了 12 个事件,覆盖了会话、消息、压缩和 Todo 的完整生命周期。
// session/index.ts
Session.Event = {
Created: BusEvent.define("session.created", z.object({ info: Session.Info })),
Updated: BusEvent.define("session.updated", z.object({ info: Session.Info })),
Deleted: BusEvent.define("session.deleted", z.object({ info: Session.Info })),
Diff: BusEvent.define("session.diff", z.object({
sessionID: z.string(),
diff: Snapshot.FileDiff.array(),
})),
Error: BusEvent.define("session.error", z.object({
sessionID: z.string().optional(),
error: MessageV2.Assistant.shape.error,
})),
}
session.status 在会话状态(idle / busy / pending)切换时触发,session.idle 是 status 的特化——仅在变为空闲时触发。TUI 据此更新状态指示器(例如显示加载动画或恢复输入框)。
消息事件的粒度很细——不仅有消息级别的更新和删除,还有 Part 级别的更新和删除。回顾第 4 章的内容,一条消息(Message)由多个 Part 组成(文本、思考链、工具调用等),Part 级别的事件使得 TUI 可以做到增量渲染——当 AI 正在流式输出时,每收到一个 text-delta,就通过 message.part.updated 通知 TUI 更新对应的 Part,而不需要重新渲染整条消息。
session.compacted 在上下文压缩完成后触发,携带被压缩的消息 ID 列表。todo.updated 在 Todo 列表变化时触发,TUI 据此刷新 Todo 面板。
11.2.2 MCP 相关事件
mcp.tools.changed 事件在第 8 章中提到过——MCP Server 支持 ToolsChanged 通知,当 Server 端的工具列表更新时,Client 接收到通知并发布此事件,触发工具注册表的重新加载。
11.2.3 Permission 相关事件
权限系统定义了两组事件,分别位于新版(permission/next.ts)和旧版(permission/index.ts)权限模块中:
permission.asked 事件是权限系统中最关键的事件——当 Agent 尝试执行需要用户授权的操作时,权限引擎发布此事件。TUI 接收到事件后弹出权限确认对话框,用户做出决定后通过 permission.replied 事件将结果返回给权限引擎。
这是一个典型的请求-响应式事件对(Request-Response Event Pair)——发布者发出请求事件,然后通过订阅响应事件来获取结果。这种模式避免了模块之间的直接依赖:权限引擎不需要直接调用 TUI 的代码,TUI 也不需要知道权限引擎的实现细节。
11.2.4 Server 相关事件
11.2.5 文件系统相关事件
file.edited 由 OpenCode 的工具(如 Edit、Write)在修改文件后发布,file.watcher.updated 由文件系统监控器(基于 @parcel/watcher)在检测到外部文件变更时发布。两者的区别在于:
file.edited:Agent 主动修改文件产生的事件,携带 sessionID 标识是哪个会话发起的修改。
file.watcher.updated:外部变更(用户手动编辑、其他工具修改等),携带变更类型(新增/修改/删除)。
Question(交互式问答)事件
Question 事件与 Permission 事件类似,也是请求-响应对。Agent 通过 question 工具向用户提问时发布 question.asked,用户回答后触发 question.replied,如果用户拒绝回答则触发 question.rejected。
Project 与 VCS 事件
TUI 事件比较特殊——它们不是由后端模块发布的,而是在 TUI 内部使用,用于组件之间的通信。例如 tui.toast.show 由任意组件发布,被 Toast 组件订阅并显示通知。
将所有事件按模块分类,可以得到以下全景图:
11.2.8 Config 变更事件
值得特别说明的是,OpenCode 的配置变更并没有通过 Bus 事件来通知。配置系统使用了不同的机制——当配置文件被修改时,文件监控器(FileWatcher)检测到变更并触发配置重新加载。这是因为配置变更通常需要立即生效(如更新 API Key),而事件总线的异步特性可能导致延迟。
46 个事件构成了 OpenCode 模块间通信的"语言"。几个设计规律值得注意:
事件粒度分层:从粗粒度(session.created)到细粒度(message.part.updated),不同消费者可以选择自己需要的粒度层级。
请求-响应事件对:permission.asked/replied 和 question.asked/replied 展示了如何用事件实现异步的请求-响应通信,避免模块间的直接调用依赖。
领域聚合:事件按业务领域自然聚合,命名中的前缀(session.、mcp.、pty.)使得事件类型一目了然。
Schema 即文档:每个事件的 Zod Schema 精确定义了事件携带的数据结构,既是类型检查的依据,也是自动生成 API 文档的数据源。