11.2 关键事件列表

模型: 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.created

新会话被创建

TUI 更新会话列表

session.updated

会话元信息更新(标题、时间等)

TUI 刷新会话标题

session.deleted

会话被删除

TUI 移除会话条目

session.diff

会话产生了文件变更

TUI 展示 diff 摘要

session.error

会话遇到错误(如 API 调用失败)

TUI 显示错误提示

状态事件

session.status 在会话状态(idle / busy / pending)切换时触发,session.idlestatus 的特化——仅在变为空闲时触发。TUI 据此更新状态指示器(例如显示加载动画或恢复输入框)。

消息事件

消息事件的粒度很细——不仅有消息级别的更新和删除,还有 Part 级别的更新和删除。回顾第 4 章的内容,一条消息(Message)由多个 Part 组成(文本、思考链、工具调用等),Part 级别的事件使得 TUI 可以做到增量渲染——当 AI 正在流式输出时,每收到一个 text-delta,就通过 message.part.updated 通知 TUI 更新对应的 Part,而不需要重新渲染整条消息。

压缩与 Todo 事件

session.compacted 在上下文压缩完成后触发,携带被压缩的消息 ID 列表。todo.updated 在 Todo 列表变化时触发,TUI 据此刷新 Todo 面板。

11.2.2 MCP 相关事件

事件
触发时机
用途

mcp.tools.changed

MCP Server 的工具列表发生变化

刷新工具注册表,使新工具对 Agent 可用

mcp.browser.open.failed

MCP OAuth 认证时浏览器无法打开

向用户显示手动打开 URL 的提示

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 相关事件

事件
触发时机
用途

server.connected

HTTP Server 启动并就绪

通知 TUI 可以建立 SSE 连接

global.disposed

全局资源被释放

清理全局状态

server.instance.disposed

特定 Instance 被销毁

清理该 Instance 的资源和订阅

11.2.5 文件系统相关事件

file.edited 由 OpenCode 的工具(如 Edit、Write)在修改文件后发布,file.watcher.updated 由文件系统监控器(基于 @parcel/watcher)在检测到外部文件变更时发布。两者的区别在于:

  • file.edited:Agent 主动修改文件产生的事件,携带 sessionID 标识是哪个会话发起的修改。

  • file.watcher.updated:外部变更(用户手动编辑、其他工具修改等),携带变更类型(新增/修改/删除)。

11.2.6 其他模块事件

PTY(伪终端)事件

Question(交互式问答)事件

Question 事件与 Permission 事件类似,也是请求-响应对。Agent 通过 question 工具向用户提问时发布 question.asked,用户回答后触发 question.replied,如果用户拒绝回答则触发 question.rejected

Project 与 VCS 事件

LSP 事件

其他事件

TUI 事件比较特殊——它们不是由后端模块发布的,而是在 TUI 内部使用,用于组件之间的通信。例如 tui.toast.show 由任意组件发布,被 Toast 组件订阅并显示通知。

11.2.7 事件全景图

将所有事件按模块分类,可以得到以下全景图:

11.2.8 Config 变更事件

值得特别说明的是,OpenCode 的配置变更并没有通过 Bus 事件来通知。配置系统使用了不同的机制——当配置文件被修改时,文件监控器(FileWatcher)检测到变更并触发配置重新加载。这是因为配置变更通常需要立即生效(如更新 API Key),而事件总线的异步特性可能导致延迟。

11.2.9 小结

46 个事件构成了 OpenCode 模块间通信的"语言"。几个设计规律值得注意:

  1. 事件粒度分层:从粗粒度(session.created)到细粒度(message.part.updated),不同消费者可以选择自己需要的粒度层级。

  2. 请求-响应事件对permission.asked/repliedquestion.asked/replied 展示了如何用事件实现异步的请求-响应通信,避免模块间的直接调用依赖。

  3. 领域聚合:事件按业务领域自然聚合,命名中的前缀(session.mcp.pty.)使得事件类型一目了然。

  4. Schema 即文档:每个事件的 Zod Schema 精确定义了事件携带的数据结构,既是类型检查的依据,也是自动生成 API 文档的数据源。

Last updated