首页
首页/s04
s0490 行代码

Hook System

Hang on the Loop, Don't Write into It

核心洞察:

Cross-cutting behavior belongs around the loop, not tangled inside it.

钩子是生命周期回调函数,插件可在特定执行点注册:工具执行前后、消息创建时、配置加载时等。这种设计既保持了核心循环的简洁,又提供了丰富的可扩展性。慢钩子不会阻塞 agent。钩子执行带有超时机制,失败会被记录而不影响主流程。

架构流程图

问题:横切关注点而不污染核心

日志、遥测、自定义验证和审计追踪都属于横切关注点,不应混杂在 agent 循环或工具处理函数中。如果没有钩子机制,开发者面临两难:要么用可选回调污染核心逻辑,使其难以维护;要么 fork 代码库,导致无法跟随上游升级。钩子通过在核心循环周围定义扩展点来解决这一问题。任意数量的插件可为同一钩子注册,并按注册顺序依次执行。核心循环从不导入插件代码,只检查特定事件名称是否有已注册的钩子。

钩子设计:带类型化载荷的命名事件

每个钩子都是一个命名事件,配有类型化的上下文对象。'tool.execute.before' 接收 { tool, input, opts },'tool.execute.after' 接收 { tool, result, duration }。这种结构化设计使钩子具备可发现性(事件名遵循统一约定)、类型安全性(每个事件定义明确的载荷结构)和隔离性(单个钩子失败不会影响其他钩子,超时机制保证了这一点)。命名约定 domain.event 借鉴了 DOM 事件模式,使每个钩子的意图一目了然。'tool.execute.before' 在工具执行前触发,'config' 在配置加载时触发。

设计决策

钩子带有可配置的超时机制。行为异常的插件,无论是陷入死循环还是等待缓慢的网络调用,都会在超时后被终止。错误被记录,核心循环则不受影响地继续执行。

钩子的注册顺序非常重要。先注册的插件,其钩子优先执行。这实现了基于优先级的行为编排:安全插件可在日志插件记录输入之前先行验证。

对比 Claude Code

Claude Code 不提供传统意义上的插件系统。OpenCode 的生命周期钩子(beforeToolExecute、afterToolExecute、beforeLlmCall、onConfigLoad)是其显著特性。这些钩子允许任何外部代码拦截并增强核心循环的功能,而无需修改源代码。这更接近 VS Code 的扩展模型,而非 Claude Code 中的任何机制。

深入设计决策

钩子优于继承或中间件

插件在特定生命周期点注册钩子,而不是继承 agent。这保持了核心循环的完整性,并使插件行为可组合。

备选方案: 中间件或子类化也可以工作,但钩子提供了更清晰的生命周期语义,防止中间件排序错误。

钩子默认必须非阻塞

慢速或损坏的钩子不应阻塞 agent。每个钩子有超时,失败会被记录而不是传播。即使钩子出错,agent 也继续运行。

备选方案: 钩子错误时硬失败更简单,但会使系统脆弱。带日志的软失败更健壮。

Learn OpenCode — Built with Next.js