Appearance
插件 hooks 是 OpenClaw 在进程内扩展点,用于拦截或修改智能体运行、工具调用、消息流程、会话生命周期和网关启动事件。使用 api.on() 注册钩子,按优先级顺序执行。before_tool_call 可拦截工具调用并要求审批,超时可通过插件配置或 api.on 的 timeoutMs 设置。非内置插件使用 conversation hooks 需设置 allowConversationAccess 为 true。
OpenClaw 插件 hooks 配置:生命周期钩子与工具调用拦截
插件 hooks 是 OpenClaw 插件的进程内扩展点。当插件需要查看或修改智能体运行、工具调用、消息流程、会话生命周期、子智能体路由、安装或网关启动时,使用插件 hooks。
如果你只需要一个由操作员安装的小型 HOOK.md 脚本来处理命令和网关事件(如 /new、/reset、/stop、agent:bootstrap、gateway:startup),请改用内部 hooks。
快速开始
在插件入口中使用 api.on(...) 注册类型化插件钩子:
typescript
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
export default definePluginEntry({
id: "tool-preflight",
name: "Tool Preflight",
register(api) {
api.on(
"before_tool_call",
async (event) => {
if (event.toolName !== "web_search") {
return;
}
return {
requireApproval: {
title: "Run web search",
description: `Allow search query: ${String(event.params.query ?? "")}`,
severity: "info",
timeoutMs: 60_000,
timeoutBehavior: "deny",
},
};
},
{ priority: 50 },
);
},
});钩子处理器按 priority 降序顺序执行。相同优先级的钩子保持注册顺序。
api.on(name, handler, opts?) 接受:
priority- 处理器排序(数值越高越先执行)timeoutMs- 可选的每个钩子预算。设置后,钩子运行器在预算耗尽时将中止该处理器并继续执行下一个,而不是让慢速的设置或召回工作消耗调用方配置的模型超时。省略此参数则使用钩子运行器默认的观察/决策超时。
操作员无需修改插件代码即可设置钩子预算:
json
{
"plugins": {
"entries": {
"my-plugin": {
"hooks": {
"timeoutMs": 30000,
"timeouts": {
"before_prompt_build": 90000,
"agent_end": 60000
}
}
}
}
}
}hooks.timeouts.<hookName> 覆盖 hooks.timeoutMs,后者覆盖插件编写的 api.on(..., { timeoutMs }) 值。每个配置值必须为正整数且不超过 600000 毫秒。对于已知较慢的钩子,优先使用每个钩子的覆盖值,这样不会导致一个插件在所有地方获得更长的预算。
每个钩子接收 event.context.pluginConfig,即注册该处理器的插件的已解析配置。当钩子决策需要当前插件选项时使用它;OpenClaw 在每个处理器中注入它,而不会改变其他插件看到的共享事件对象。
钩子目录
钩子按其扩展的表面分组。粗体名称接受决策结果(阻塞、取消、覆盖或要求审批);其他均为仅观察。
智能体轮次
before_model_resolve- 在会话消息加载前覆盖提供者或模型agent_turn_prepare- 在提示钩子之前消耗排队的插件轮次注入并添加同轮次上下文before_prompt_build- 在模型调用前添加动态上下文或系统提示文本before_agent_start- 仅兼容性的合并阶段;请优先使用上面两个钩子before_agent_run- 在模型提交前检查最终提示和会话消息,并可选择阻塞运行before_agent_reply- 用合成回复或静默短接模型轮次before_agent_finalize- 检查自然最终答案并请求再进行一次模型传递agent_end- 观察最终消息、成功状态和运行时长heartbeat_prompt_contribution- 为后台监控和生命周期插件添加仅心跳的上下文
对话观察
model_call_started/model_call_ended- 观察经过清理的提供者/模型调用元数据、时间、结果和有限制的请求 ID 哈希,不包含提示或响应内容llm_input- 观察提供者输入(系统提示、提示、历史记录)llm_output- 观察提供者输出、使用情况以及解析后的contextTokenBudget(如果可用)
工具
before_tool_call- 重写工具参数、阻塞执行或要求审批after_tool_call- 观察工具结果、错误和时长tool_result_persist- 重写从工具结果生成的助手消息before_message_write- 检查或阻塞正在进行的消息写入(很少使用)
消息与投递
inbound_claim- 在智能体路由前声明入站消息(合成回复)message_received- 观察入站内容、发送者、线程和元数据message_sending- 重写出站内容或取消投递message_sent- 观察出站投递成功或失败before_dispatch- 在渠道交接前检查或重写出站调度reply_dispatch- 参与最终回复调度管道
会话与压缩
session_start/session_end- 跟踪会话生命周期边界。事件的reason为以下之一:new、reset、idle、daily、compaction、deleted、shutdown、restart、unknown。shutdown和restart值在网关关闭终结器运行时触发(当进程停止或重启时会话仍处于活动状态),以便下游插件(如内存或日志存储)可以终结原本在重启后会保持打开状态的幽灵行。终结器有超时限制,因此慢速插件不能阻塞 SIGTERM/SIGINT。before_compaction/after_compaction- 观察或注释压缩周期before_reset- 观察会话重置事件(/reset、编程重置)
子智能体
subagent_spawning/subagent_delivery_target/subagent_spawned/subagent_ended- 协调子智能体路由和完成投递
生命周期
gateway_start/gateway_stop- 与网关一起启动或停止插件拥有的服务deactivate- 废弃的兼容性别名,用于gateway_stop;新插件请使用gateway_stopcron_changed- 观察网关拥有的 cron 生命周期变化(添加、更新、删除、开始、完成、调度)before_install- 检查技能或插件安装扫描并可选择阻塞
调试运行时钩子
当插件需要为智能体轮次切换提供者或模型时,使用 before_model_resolve。它在模型解析之前运行;llm_output 仅在模型尝试产生助手输出之后运行。
要验证有效的会话模型,检查运行时注册表,然后使用 openclaw sessions 或网关会话/状态界面。调试提供者有效载荷时,使用 --raw-stream 和 --raw-stream-path <path> 启动网关;这些标志将原始模型流事件写入 jsonl 文件。
工具调用策略
before_tool_call 接收:
event.toolNameevent.params- 可选的
event.toolKind和event.toolInputKind,用于名称故意相同的工具的宿主权威鉴别器;例如,外部代码模式的exec调用使用toolKind: "code_mode_exec",并在输入语言已知时包含toolInputKind: "javascript" | "typescript" - 可选的
event.derivedPaths,包含针对已知工具信封(如apply_patch)的最佳努力宿主演绎的目标路径提示;当存在时,这些路径可能不完整或可能过度近似工具实际将触及的内容(例如,对于格式错误或部分输入) - 可选的
event.runId - 可选的
event.toolCallId - 上下文字段如
ctx.agentId、ctx.sessionKey、ctx.sessionId、ctx.runId、ctx.jobId(在 cron 驱动的运行上设置)、ctx.toolKind、ctx.toolInputKind和诊断信息ctx.trace
它可以返回:
typescript
type BeforeToolCallResult = {
params?: Record<string, unknown>;
block?: boolean;
blockReason?: string;
requireApproval?: {
title: string;
description: string;
severity?: "info" | "warning" | "critical";
timeoutMs?: number;
timeoutBehavior?: "allow" | "deny";
pluginId?: string;
onResolution?: (
decision: "allow-once" | "allow-always" | "deny" | "timeout" | "cancelled",
) => Promise<void> | void;
};
};类型化生命周期钩子的钩子防护行为:
block: true是终止性的,并跳过较低优先级的处理器。block: false被视为无决策。params重写工具参数以便执行。requireApproval暂停智能体运行并通过插件审批向用户询问。/approve命令可以批准执行和插件审批。- 较低优先级的
block: true仍然可以在较高优先级的钩子请求审批后阻塞。 onResolution接收已解析的审批决策 -allow-once、allow-always、deny、timeout或cancelled。
需要宿主编策略的捆绑插件可以使用 api.registerTrustedToolPolicy(...) 注册受信任的工具策略。这些在普通的 before_tool_call 钩子之前以及外部插件决策之前运行。仅用于宿主编信任的关卡,如工作区策略、预算执行或保留工作流安全。外部插件应使用正常的 before_tool_call 钩子。
工具结果持久化
工具结果可以包含结构化的 details,用于 UI 渲染、诊断、媒体路由或插件拥有的元数据。将 details 视为运行时元数据,而不是提示内容:
- OpenClaw 在提供者重放和压缩输入前剥离
toolResult.details,以便元数据不会成为模型上下文。 - 持久化的会话条目仅保留有界的
details。过大的details被替换为紧凑摘要和persistedDetailsTruncated: true。 tool_result_persist和before_message_write在最终持久化上限之前运行。钩子仍应保持返回的details较小,并避免将提示相关文本仅放在details中;将模型可见的工具输出放在content中。
提示与模型钩子
新插件请使用阶段特定的钩子:
before_model_resolve:仅接收当前提示和附件元数据。返回providerOverride或modelOverride。agent_turn_prepare:接收当前提示、准备好的会话消息以及为此会话排空的任何一次性排队的注入。返回prependContext或appendContext。before_prompt_build:接收当前提示和会话消息。返回prependContext、appendContext、systemPrompt、prependSystemContext或appendSystemContext。heartbeat_prompt_contribution:仅对心跳轮次运行,并返回prependContext或appendContext。它适用于需要总结当前状态但又不改变用户发起轮次的背景监视器。
before_agent_start 保留以供兼容之用。新插件请优先使用上面的显式钩子,这样你的插件就不依赖于遗留的合并阶段。
before_agent_run 在提示构造后、任何模型输入(包括提示本地的图像加载和 llm_input 观察)之前运行。它接收当前用户输入作为 prompt,以及加载的会话历史记录 messages 和活动系统提示。返回 { outcome: "block", reason, message? } 以在模型读取提示之前停止运行。reason 是内部的;message 是显示给用户的替代文本。唯一支持的结果是 pass 和 block;不受支持的决策形状导致失败关闭。
当运行被阻塞时,OpenClaw 仅在 message.content 中存储替代文本,加上非敏感的阻塞元数据,如阻塞插件 ID 和时间戳。原始用户文本不会保留在转录或未来上下文中。内部阻塞原因被视为敏感信息,并从转录、历史记录、广播、日志和诊断有效载荷中排除。可观测性应使用清理过的字段,如阻塞器 ID、结果、时间戳或安全类别。
before_agent_start 和 agent_end 在 OpenClaw 可以识别活动运行时会包含 event.runId。相同的值也可在 ctx.runId 上使用。cron 驱动的运行还公开 ctx.jobId(原始 cron 作业 ID),以便插件钩子可以将指标、副作用或状态限定到特定的计划作业。
对于渠道发起的运行,ctx.messageProvider 是提供者表面,如 discord 或 telegram,而 ctx.channelId 是会话目标标识符(当 OpenClaw 可以从会话密钥或投递元数据派生时)。
agent_end 是一个观察钩子。网关和持久化 harness 路径在轮次后以 fire-and-forget 方式运行它,而短生命周期的单次 CLI 路径在进程清理之前等待钩子 promise,以便受信任的插件可以刷新终端可观测性或捕获状态。钩子运行器应用 30 秒超时,因此卡住的插件或嵌入端点无法使钩子 promise 永远挂起。超时会被记录,OpenClaw 继续;它不会取消插件拥有的网络工作,除非插件也使用自己的中止信号。
使用 model_call_started 和 model_call_ended 进行提供者调用遥测,这些遥测不应接收原始提示、历史记录、响应、标头、请求体或提供者请求 ID。这些钩子包含稳定的元数据,如 runId、callId、provider、model、可选的 api/transport、终端 durationMs/outcome 以及当 OpenClaw 可以派生有界提供者请求 ID 哈希时的 upstreamRequestIdHash。当运行时已解析上下文窗口元数据时,钩子事件和上下文还包括 contextTokenBudget(在模型/配置/智能体上限后的有效令牌预算),以及当应用了较低上限时的 contextWindowSource 和 contextWindowReferenceTokens。
before_agent_finalize 仅在 harness 即将接受自然最终助手答案时运行。它不是 /stop 取消路径,并且在用户中止轮次时不运行。返回 { action: "revise", reason } 以在最终确定前要求 harness 再进行一次模型传递,返回 { action: "finalize", reason? } 以强制最终确定,或省略结果以继续。Codex 原生 Stop 钩子作为 OpenClaw before_agent_finalize 决策被中继到该钩子。
当返回 action: "revise" 时,插件可以包含 retry 元数据,使额外的模型传递有界且可重放安全:
typescript
type BeforeAgentFinalizeRetry = {
instruction: string;
idempotencyKey?: string;
maxAttempts?: number;
};instruction 被附加到发送给 harness 的修订原因中。idempotencyKey 让宿主可以跨等效的最终决策计数同一插件请求的重试次数,maxAttempts 限制宿主在继续使用自然最终答案之前将允许多少额外传递。
非捆绑插件如果需要原始对话钩子(before_model_resolve、before_agent_reply、llm_input、llm_output、before_agent_finalize、agent_end 或 before_agent_run),必须设置:
json
{
"plugins": {
"entries": {
"my-plugin": {
"hooks": {
"allowConversationAccess": true
}
}
}
}
}提示修改钩子和持久的下轮次注入可以通过 plugins.entries.<id>.hooks.allowPromptInjection=false 按插件禁用。
会话扩展与下轮次注入
工作流插件可以使用 api.registerSessionExtension(...) 持久化小的 JSON 兼容会话状态,并通过网关的 sessions.pluginPatch 方法更新它。会话行通过 pluginExtensions 投射注册的扩展状态,让控制 UI 和其他客户端无需了解插件内部即可渲染插件拥有的状态。
当插件需要持久的上下文以便确切一次地到达下一个模型轮次时,使用 api.enqueueNextTurnInjection(...)。OpenClaw 在提示钩子之前排空排队的注入,丢弃过期的注入,并按每个插件的 idempotencyKey 去重。这是审批恢复、策略摘要、背景监视器增量和命令延续的正确接缝,这些应在下一个轮次对模型可见,但不应成为永久的系统提示文本。
清理语义是合约的一部分。会话扩展清理和运行时生命周期清理回调接收 reset、delete、disable 或 restart。宿主为 reset/delete/disable 移除拥有插件的持久会话扩展状态和待处理的下轮次注入;restart 保持持久的会话状态,而清理回调让插件释放调度器作业、运行上下文和其他带外资源给旧的运行时代。
消息钩子
使用消息钩子实现渠道级路由和投递策略:
message_received:观察入站内容、发送者、threadId、messageId、senderId、可选的运行/会话关联和元数据。message_sending:重写content或返回{ cancel: true }。message_sent:观察最终成功或失败。
对于纯音频 TTS 回复,即使渠道负载没有可见文本/标题,content 也可能包含隐藏的口语转录。重写该 content 仅更新钩子可见的转录;它不会渲染为媒体标题。
消息钩子上下文在可用时公开稳定的关联字段:ctx.sessionKey、ctx.runId、ctx.messageId、ctx.senderId、ctx.trace、ctx.traceId、ctx.spanId、ctx.parentSpanId 和 ctx.callDepth。在读取遗留元数据之前,优先使用这些一等字段。
优先使用类型化的 threadId 和 replyToId 字段,然后再使用渠道特定的元数据。
决策规则:
message_sending中cancel: true是终止性的。message_sending中cancel: false被视为无决策。- 重写的
content会继续传递给较低优先级的钩子,除非后面的钩子取消投递。 message_sending可以在取消时返回cancelReason和有界的metadata。新的消息生命周期 API 将其暴露为取消投递结果,原因为cancelled_by_message_sending_hook;遗留直接投递为了兼容性继续返回空结果数组。message_sent是仅观察的。处理器失败会被记录,不会改变投递结果。
安装钩子
before_install 在内置扫描技能和插件安装后运行。返回额外的发现或 { block: true, blockReason } 以停止安装。
block: true 是终止性的。block: false 被视为无决策。
网关生命周期
使用 gateway_start 启动需要网关状态的插件服务。上下文公开 ctx.config、ctx.workspaceDir 和 ctx.getCron?.() 用于 cron 检查和更新。使用 gateway_stop 清理长时间运行的资源。
不要依赖内部的 gateway:startup 钩子来运行插件拥有的运行时服务。
cron_changed 在网关拥有的 cron 生命周期事件时触发,事件类型有效载荷覆盖 added、updated、removed、started、finished 和 scheduled 原因。事件携带一个 PluginHookGatewayCronJob 快照(包括 state.nextRunAtMs、state.lastRunStatus 和 state.lastError,如果存在)加上一个 PluginHookGatewayCronDeliveryStatus,值为 not-requested | delivered | not-delivered | unknown。已删除的事件仍然携带已删除作业的快照,以便外部调度器可以协调状态。在同步外部唤醒调度器时,请使用运行时上下文中的 ctx.getCron?.() 和 ctx.config,并保持 OpenClaw 作为到期检查和执行的真相来源。
即将弃用的功能
一些与钩子相邻的表面已被弃用但仍受支持。在下一次主要版本发布前迁移:
- 纯文本渠道信封在
inbound_claim和message_received处理器中。请读取BodyForAgent和结构化用户上下文块,而不是解析纯文本信封文本。参见纯文本渠道信封 → BodyForAgent。 before_agent_start保留以供兼容。新插件应使用before_model_resolve和before_prompt_build代替合并阶段。deactivate作为弃用的清理兼容性别名保留,直到 2026-08-16 之后。新插件应使用gateway_stop。onResolution在before_tool_call中现在使用类型化的PluginApprovalResolution联合(allow-once/allow-always/deny/timeout/cancelled)而不是自由形式的string。
完整列表——内存能力注册、提供者思考配置文件、外部认证提供者、提供者发现类型、任务运行时访问器以及 command-auth → command-status 重命名——请参见插件 SDK 迁移 → 活跃弃用。
相关
常见问题
怎么在 OpenClaw 插件里用 before_tool_call 拦截工具调用并要求审批?
在插件入口的 register 函数中用 api.on("before_tool_call", handler, opts) 注册。处理器返回 { requireApproval: { title, description, timeoutMs, timeoutBehavior } } 即可暂停智能体运行并弹出审批请求。用户可以通过 /approve 命令批准或拒绝。
非内置插件使用 before_agent_reply 等对话钩子为什么不生效?
非捆绑插件使用 before_model_resolve、before_agent_reply、llm_input、llm_output、before_agent_finalize、agent_end 或 before_agent_run 时,必须在插件配置中设置 "hooks": { "allowConversationAccess": true },否则钩子不会被触发。
插件钩子超时怎么设置?优先级如何控制?
超时可在 api.on 的 opts.timeoutMs 设置,也可在插件配置的 hooks.timeoutMs 全局设置,或在 hooks.timeouts.<hookName> 按钩子覆盖。优先级通过 api.on 的 opts.priority 控制,数值越大越先执行;相同优先级的按注册顺序执行。