HOOK.md 脚本,用于响应诸如 /new、
/reset、/stop、agent:bootstrap 或 gateway:startup 等命令和 Gateway 事件,
请改用 内部钩子。
快速开始
从插件入口使用api.on(...) 注册类型化钩子:
priority 降序顺序依次运行;相同 priority 的处理器保持注册顺序。仅观察类处理器会并行运行,而“即发即弃”的观察分发可能与后续事件重叠。不要使用 priority 来安排观察副作用的顺序。
api.on(name, handler, opts?) 接受:
触发器资格由宿主在调用处理器之前强制执行。因此,使用
eligibleTriggers: ["heartbeat", "cron"] 注册的钩子对于用户轮次处于非活动状态,包括恢复的用户轮次。省略、为空、格式错误或部分包含未知值的列表仍不受限制,因此钩子会在这些轮次中运行。其他钩子类型不接受此选项。
运维人员可以在不修改插件代码的情况下设置钩子预算:
hooks.timeouts.<hookName> 会覆盖 hooks.timeoutMs,后者又会覆盖插件作者通过 api.on(..., { timeoutMs }) 指定的值。每个值都必须是一个不超过 600000 ms 的正整数。对于已知较慢的钩子,优先使用按钩子覆盖,这样某个插件不会在所有地方都获得更长的预算。
处理器 Promise 超时后仍会继续运行,因为钩子回调不会接收由超时管理的取消信号。before_tool_call 会接收所属工具调用的 ctx.abortSignal,但钩子超时不会触发该信号。即使插件工作仍在进行,钩子分发也可以释放其 Gateway 准入。拥有长时间运行任务的插件必须提供自己的取消机制和关闭生命周期。
策略钩子 before_tool_call 和 before_install 默认对每个处理器使用 15 秒。超时将采取安全失败策略:工具调用或安装会被拒绝,而不是在没有策略决策的情况下继续执行。
gateway_stop 默认对每个处理器使用 5 秒。超时的处理器会被记录日志,关闭流程将继续,以免插件清理工作耗尽 Gateway 进程监视器的时间预算。
出站修改类钩子 message_sending 和 reply_payload_sending 默认每个处理器使用 15 秒。若某个处理器超时,OpenClaw 会记录插件错误并继续使用最新的 payload,以便序列化交付通道能够稳定下来。对于有意在交付前执行更慢工作的插件,请为每个钩子设置更大的预算。
使用 createReplyDispatcher 的通道插件同样可以通过 beforeDeliverOptions: { timeoutMs } 声明更大的正向每阶段预算,或者在通过 dispatcher.appendBeforeDeliver(handler, { timeoutMs }) 追加工作时指定。若没有所有者声明的预算,这些回调会使用相同的 15 秒默认值,这样一个卡住的回调就不会占用序列化交付通道。
每个钩子都会接收 event.context.pluginConfig,也就是为注册该处理器的插件解析后的配置。OpenClaw 会按处理器逐个注入,而不会修改其他插件看到的共享事件对象。
钩子目录
Hooks 按其扩展的界面进行分组。加粗名称接受决策结果(阻止、取消、覆盖或要求批准);其余仅用于观察。 Agent 回合
对话观察
工具
消息与传递
inbound_claim 不是全局的预路由广播。OpenClaw 仅对拥有该消息核心管理会话绑定的插件调用它。若要在模型输入之前抑制普通代理回合,同时不将原始提示词保留在转录中,请使用 before_agent_run。若要使用合成回复或静默来提前结束代理回合,请使用 before_agent_reply。
会话与压缩
关闭和重启会在所有活跃会话及插件处理程序之间共享一个总计 2 秒的
session_end 排空预算;该预算不是按处理程序分别计算的。请快速返回,或限制最终处理时间并确保持久化在崩溃时保持一致。如果预算耗尽,OpenClaw 会记录 session-end-drain timed out 并继续关闭,因此未完成的插件工作可能会被中断。
对于带有 parentSessionKey 和 emitCommandHooks: true 的 sessions.create 调用,独立的子会话始终会收到 session_start。调用方通过 succeedsParent 声明父会话是否也会收到终端 session_end:true 表示后继会话,false 表示并行子会话。省略该字段会保留旧版的父会话轮换行为。在两种情况下,command:new 和 before_reset 钩子仍会描述所请求的 /new 操作。
子代理
subagent_spawned/subagent_ended- 观察子代理的启动和完成。subagent_delivery_target- 当没有核心会话绑定可投射路由时,用于完成投递的兼容性钩子。subagent_spawning- 已弃用的兼容性钩子。现在核心会在subagent_spawned触发前,通过通道会话绑定适配器为thread: true的子代理绑定做准备。subagent_spawned在 OpenClaw 已在启动前解析出子会话原生模型时,会包含resolvedModel和resolvedProvider。subagent_ended包含targetSessionKey(标识 - 与subagent_spawned.childSessionKey匹配)、targetKind("subagent"或"acp")、reason、可选的outcome("ok"、"error"、"timeout"、"killed"、"reset"或"deleted")、可选的error、runId、endedAt、accountId和sendFarewell。它不包含agentId或childSessionKey;请使用targetSessionKey与匹配的subagent_spawned事件进行关联。
技能生命周期与评估
对于静态分析器、安全扫描器、基准测试、基于模型的评分器或其他第三方评估器,请使用skill_proposal_evaluate。OpenClaw 会传递包含文件哈希和树哈希的不可变候选包。更新提案还会将完整的当前技能作为 baseline 包含在内。文本文件使用 UTF-8 内容;二进制文件使用 base64。
评估器注册会并发运行。请为每个评估器提供稳定的 registrationId:
decision: "block" 时,应用提案才会被阻止。应用操作会在 Workshop 修改锁下重新验证已评估的目标树,因此任何实时技能资产漂移都需要重新评估。持久化的评估器合并结果上限为 512 KiB。
skill_proposal_changed 会在匹配的提案行和仅追加生命周期事件提交后触发。它携带事件 ID、序列号、精确的提案修订哈希、可选的关联 ID 以及评估结果。
skill_changed 会在实时技能创建、更新或移除提交后触发,并在可用时包含带有内容、树、声明和源版本的变更前后工件。
这些钩子是基础构件,而不是优化调度器。插件或外部控制器可以观察持久化的提案事件,评估其精确的修订哈希,使用该哈希和关联 ID 进行修订,然后重复此过程。OpenClaw 不会自动修订提案,也不会运行无界的评估循环。
事件重放受字节数限制;当还有可用的下一页时,会返回 nextSequence。
频道配对请求
当插件需要在未配对的私信发送者创建待处理配对请求后通知操作员或写入审计记录时,使用channel_pairing_requested。该钩子会在请求创建时派发;配对回复的通道投递不会因为缓慢或失败的钩子处理程序而延迟。
accountId、按通道范围的 senderId、配对 code 以及通道元数据。请将配对代码视为实时一次性批准凭证,并仅将其交付给受信任的操作员接收端。请将 metadata 视为不受信任的、由发送者提供的身份文本。该钩子不包含传入消息正文或媒体。
调试运行时钩子
在代理轮次中使用before_model_resolve 切换提供方或模型——它会在模型解析之前运行。llm_output 仅在一次模型尝试生成助手输出后运行。
要验证会话模型是否生效,请检查运行时注册信息,然后使用 openclaw sessions 或 Gateway 的 session/status 界面。要调试提供方载荷,请使用 --raw-stream 和 --raw-stream-path <path> 启动 Gateway,将原始模型流事件写入 jsonl 文件。
工具调用策略
before_tool_call 接收:
event.toolNameevent.params- 可选的
event.toolKind和event.toolInputKind,由宿主决定的 判别字段,用于有意共享名称的工具;例如,外层 code-modeexec调用使用toolKind: "code_mode_exec",并在已知输入语言时包含toolInputKind: "javascript" | "typescript" - 可选的
event.derivedPaths,由宿主尽力推导的目标路径提示, 适用于apply_patch等已知工具封装;这些路径可能不完整,或过度近似工具实际会修改的内容 (例如,对于格式错误或不完整的输入) - 可选的
event.runId - 可选的
event.toolCallId - 上下文字段,例如
ctx.agentId、ctx.sessionKey、ctx.sessionId、ctx.runId、ctx.toolKind、ctx.toolInputKind,以及诊断用的ctx.trace - 可选的
ctx.abortSignal,在所属工具调用被取消时触发;处理器应将其传递给可取消的 I/O,并移除其注册的任何监听器 - 可选的
ctx.requester,由宿主推导、发起当前消息运行的请求方。它可以包含channel、accountId、senderId、senderIsOwner以及提供方原生的roleIds。缺失字段表示无法证实,而不是可以据此认定为否定;策略要求时应默认拒绝。
block: true是终结性的,并会跳过更低优先级的处理器。block: false视为没有决策。params会重写用于执行的工具参数。requireApproval会暂停代理运行,并通过插件审批请求用户。/approve可以同时批准 exec 和插件审批。在 Codex app-server report-mode 原生PreToolUse转发中,这会委托给 匹配的 app-server 审批请求;参见 Codex harness runtime。- 更低优先级的
block: true即使在更高优先级钩子请求了批准之后,仍然可以阻止执行。 onResolution接收已解析的决策:allow-once、allow-always、deny、timeout或cancelled。
单文件中的发送者感知策略
独立插件文件可以将部署特定的策略保存在代码中,而无需添加其他配置模式。此示例为所有者提供每个工具的访问权限,让已配置的维护者使用一组保守的工具和消息操作,并向已获得频道配置授权的发送者开放/fix:
AGENT_ID 必须指向绑定到维护对话的代理。该绑定会为普通消息和 /fix 选择该代理;独立文件仍然是所有者与维护者工具策略的唯一控制方。
requireAuth: true 会复用每个频道现有的发送者准入机制。对于 Discord,服务器或频道的 users/roles 允许列表可以为维护受众授予授权。其他频道可以使用稳定的发送者 ID。随后,钩子会在运行中的每次工具调用上应用更细粒度的逐工具决策,包括 Codex 原生的 PreToolUse 调用。它可以否决模型可见的工具,但不能添加宿主省略的工具。现有的沙箱、exec 审批、仅所有者可用的核心工具以及频道策略仍然适用;钩子无法越过这些策略授予权限。
如上所示,将发送者 ID 和角色 ID 限定在精确的频道/账户对中;二者都是提供方本地的命名空间。保持允许列表保守。仅当部署的沙箱和审批策略能够确保安全时,才添加写入或执行工具。对于自动化或系统运行,请明确决定缺少 ctx.requester 时是否应当放行;此示例会拒绝其作用域代理的此类请求。
参见插件权限请求,了解审批路由、决策行为,以及何时应使用 requireApproval 而不是可选工具或 exec 审批。
需要宿主级策略的插件可以通过 api.registerTrustedToolPolicy(...) 注册受信任的工具策略。这些策略会在普通的 before_tool_call 钩子之前以及正常钩子决策之前运行。捆绑的受信任策略最先运行;已安装插件的受信任策略随后按插件加载顺序运行;普通的 before_tool_call 钩子在它们之后运行。捆绑插件保留现有的受信任策略路径。已安装插件必须显式启用,并在 contracts.trustedToolPolicies 中声明每个策略 id;未声明的 id 会在注册前被拒绝。策略 id 仅在注册该策略的插件范围内有效,因此不同插件可以重用相同的本地 id。仅在工作区策略、预算执行或保留工作流安全等宿主信任的门控场景中使用这一层。
受信任策略可以将 matcher 设置为与 before_tool_call 接受的相同规范工具 ID 列表。省略 matcher 可保留匹配全部工具的行为。
Exec 环境钩子
resolve_exec_env 允许插件在命令运行之前,为 exec
工具调用贡献环境变量。它接收:
event.sessionKeyevent.toolName,当前始终为"exec"event.host,取值为"gateway"、"sandbox"或"node"- 上下文字段,例如
ctx.agentId、ctx.sessionKey、ctx.messageProvider和ctx.channelId
Record<string, string> 以合并到 exec 环境中。处理器
按优先级顺序运行;后面的结果会覆盖较早结果中相同的
键。
在合并之前,钩子输出会经过宿主 exec 环境键策略过滤。PATH 始终会被丢弃(命令解析和 safe-bin 检查
依赖它)。无效键以及危险的宿主覆盖键,如 LD_*、
DYLD_*、NODE_OPTIONS、代理变量(HTTP_PROXY、HTTPS_PROXY、
ALL_PROXY、NO_PROXY)和 TLS 覆盖变量(NODE_TLS_REJECT_UNAUTHORIZED、
SSL_CERT_FILE 及类似项)都会被丢弃。过滤后的插件环境会包含在 Gateway 审批/审计元数据中,并转发给 node-host 执行
请求。
工具结果持久化
工具结果可以包含用于 UI 渲染、诊断、媒体路由或插件自有元数据的结构化details。请将 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或toolsAllow。toolsAllow只能缩小主机为当前回合解析出的工具范围;[]表示不提交任何可选工具,而省略该字段则保持现有范围不变。多个钩子返回的限制会取交集。嵌入式运行器和 Copilot harness 会将此字段应用于当前回合提交的工具范围。Codex app-server harness 会拒绝限制性值,因为其动态工具是线程级的,而 Codexturn/start不支持工具范围覆盖;当插件需要此策略时,请使用嵌入式或 Copilot 运行时。heartbeat_prompt_contribution: 仅在心跳回合运行,并返回prependContext或appendContext。用于需要在不改变用户发起回合的情况下总结当前状态的后台监控器。
before_agent_run 在提示词构建完成后、任何模型输入之前运行,包括提示词本地图片加载和 llm_input 观测。它接收作为 prompt 的当前用户输入,以及作为 messages 的已加载会话历史和当前活动系统提示词。返回 { outcome: "block", reason, message? } 可在模型读取提示词前停止运行。reason 是内部信息;message 是面向用户的替换文本。仅支持 pass 和 block 两种结果;不受支持的决策结构会以安全失败方式处理。
当运行被阻止时,OpenClaw 只会在 message.content 中存储替换文本,
以及非敏感的阻止元数据,例如阻止插件 id 和时间戳。原始用户文本
不会保留在转录或未来上下文中。内部阻止原因被视为敏感信息,
不会出现在转录、历史、广播、日志和诊断载荷中。可观测性应使用
已清洗字段,例如阻止者 id、结果、时间戳或安全分类。
包括 agent_end 在内的 agent 回合钩子,在 OpenClaw 能识别活动运行时会包含 event.runId;相同的值也会出现在 ctx.runId 中。由 Cron 驱动的运行还会在 agent 回合上下文中暴露 ctx.jobId(发起该运行的 cron 作业 id),因此钩子可以将指标、副作用或状态限定到特定的计划作业。ctx.jobId 不属于 before_tool_call 工具上下文。
对于通道发起的运行,ctx.channel 和 ctx.messageProvider 用于标识
提供方表面,例如 discord 或 telegram,而 ctx.channelId 是会话
目标标识符,当 OpenClaw 能从 session key 或投递元数据推导出时会提供。
当发送者身份可用时,agent 钩子上下文还包括:
ctx.senderId- 通道范围内的发送者 ID(例如 Feishuopen_id、Discord 用户 ID)。当运行源自带有已知发送者元数据的用户消息时填充。ctx.chatId- 传输层原生的会话标识符(例如 Feishuchat_id、 Telegramchat_id)。当来源通道提供原生会话 ID 时填充。ctx.channelContext.sender.id- 与ctx.senderId相同的发送者 ID, 位于一个由通道拥有的对象下,插件可以通过通道特定字段进行扩展。ctx.channelContext.chat.id- 与ctx.chatId相同的会话 ID, 位于一个由通道拥有的对象下,插件可以通过通道特定字段进行扩展。
id 字段。通过 inbound helper 传递更丰富发送者或聊天元数据的通道插件,可以从 openclaw/plugin-sdk/channel-inbound 扩展 PluginHookChannelSenderContext 或 PluginHookChannelChatContext:
ctx.senderExternalId 仍作为一个废弃的向后兼容字段保留给旧插件。
Core 不会填充它;新的通道特定发送者身份应通过模块增强放在
ctx.channelContext.sender 下。
agent_end 是一个观测钩子。Gateway 和持久化 harness 路径会在回合结束后
以 fire-and-forget 方式运行它,而短生命周期的一次性 CLI 路径会在进程清理前
等待该钩子 promise,以便受信任的插件可以刷新终端可观测性或捕获状态。
钩子运行器会应用 30 秒超时,因此卡住的插件或嵌入端点不会让 hook promise
永久悬挂。超时会被记录,OpenClaw 会继续执行;除非插件也使用自己的 abort
signal,否则不会取消插件拥有的网络工作。
使用 model_call_started 和 model_call_ended 来做 provider 调用遥测,这些
遥测不应接收原始提示词、历史、响应、请求头、请求体或 provider 请求 ID。
这些钩子包含稳定元数据,例如 runId、callId、provider、model、
可选的 api/transport、终态 durationMs/outcome,以及当 OpenClaw
能推导出受限 provider request-id hash 时的 upstreamRequestIdHash。
当运行时已经解析出上下文窗口元数据时,钩子事件和上下文还会包括
contextTokenBudget,即模型/配置/agent 限制后的有效 token 预算,以及
在施加更低上限时的 contextWindowSource 和 contextWindowReferenceTokens。
before_agent_finalize 仅在 harness 即将接受自然的最终助手回复时运行。它不是
/stop 取消路径,也不会在用户中止回合时运行。返回 { action: "revise", reason }
可要求 harness 在最终定稿前再进行一次模型传递,返回 { action: "finalize", reason? } 可强制最终定稿,或省略结果以继续。处理器默认预算为 15 秒;
超时后,OpenClaw 会记录失败并继续使用原始最终答案。
Codex 原生的 Stop 钩子会作为 OpenClaw 的 before_agent_finalize
决策转发到此钩子中。
当返回 action: "revise" 时,插件可以包含 retry 元数据,以便让额外的模型传递
保持有界且可重放:
instruction 会附加到发送给 harness 的修订原因中。
idempotencyKey 允许宿主在等价的 finalize 决策之间统计同一插件请求的重试次数,
而 maxAttempts 则限制宿主在继续使用自然最终答案之前允许的额外传递次数。
需要原始会话钩子(before_model_resolve、
agent_turn_prepare、before_prompt_build、before_agent_reply、llm_input、
llm_output、before_agent_finalize、agent_end 或 before_agent_run)的非捆绑插件必须
设置:
agent_turn_prepare 和 before_prompt_build 还会改变提示词构建,
因此需要会话访问权限,并且仍受
plugins.entries.<id>.hooks.allowPromptInjection 约束。
可以通过将该选项设置为 false,按插件禁用会修改提示词的钩子和持久化的下一回合注入。
会话扩展与下一回合注入
Workflow 插件可以使用api.session.state.registerSessionExtension(...) 持久化小型
JSON 兼容会话状态,并通过 Gateway 的 sessions.pluginPatch 方法更新它。会话行会将
已注册的扩展状态通过 pluginExtensions 映射出来,让 Control UI 和其他客户端在不
了解插件内部实现的情况下也能渲染插件拥有的状态。
api.registerSessionExtension(...) 仍然可用,但已弃用,建议改用
api.session.state 命名空间。
当插件需要让持久化上下文恰好一次地到达下一次模型回合时,请使用
api.session.workflow.enqueueNextTurnInjection(...)(顶层的
api.enqueueNextTurnInjection(...) 是一个具有相同行为的已弃用别名)。
OpenClaw 会在提示词钩子之前清空已排队的注入,丢弃过期注入,并按插件的
idempotencyKey 去重。这是批准恢复、策略摘要、后台监控增量以及命令续接的合适
接入点:这些内容应在下一回合对模型可见,但不应变成永久的系统提示词文本。
清理语义是契约的一部分。会话扩展清理和运行时生命周期清理回调会接收 reset、
delete、disable 或 restart。主机会在 reset/delete/disable 时移除拥有该插件的
持久会话扩展状态和待处理的下一回合注入;restart 会保留持久会话状态,而清理回调则
允许插件释放旧运行代的调度器任务、运行上下文以及其他带外资源。
消息钩子
将消息钩子用于通道级路由和投递策略:message_received:观察入站内容、发送者、threadId、messageId、senderId、可选的运行/会话关联、有序的media、规范化的location、通道提供时稳定的providerUpdate标识以及元数据。message_sending:重写content或返回{ cancel: true }。reply_payload_sending:重写规范化的ReplyPayload对象 (包括presentation、delivery、媒体引用和文本),或返回{ cancel: true }。message_sent:观察最终的成功或失败。
content 也可能包含隐藏的口语转写。
重写该 content 只会更新钩子可见的转写内容;它不会
作为媒体标题进行渲染。
reply_payload_sending 事件可能包含 usageState,这是对每次 turn 的模型/用量/上下文的尽力而为的实时快照。持久化投递、恢复回放以及没有精确运行关联的回复会省略它。
当可用时,消息钩子上文会暴露稳定的关联字段:
ctx.sessionKey、ctx.runId、ctx.messageId、ctx.senderId、ctx.trace、
ctx.traceId、ctx.spanId、ctx.parentSpanId 和 ctx.callDepth。入站
和 before_dispatch 上下文在通道具有可见性过滤的引用消息数据时也会暴露回复元数据:replyToId、replyToIdFull、
replyToBody、replyToSender 和 replyToIsQuote。在读取旧版元数据之前,请优先使用这些一等字段。
before_dispatch 在其事件和上下文中都会接收规范的入站 messageId。
在使用特定于通道的元数据之前,应优先使用类型化的 threadId 和 replyToId 字段。
入站声明和消息接收事件会通过 media?: PluginHookMediaFact[] 暴露规范的附件 API。每个事实可以包含
path、url、contentType、kind、transcribed、messageId 和
workspaceDir;数组位置即为附件标识。当远程附件尚未在本地暂存时,会省略
media,设置 mediaStagingPending: true,并由 originalMedia 包含提供方一侧的
事实。在后续暂存事件提供 media 之前,不要将 originalMedia.path 视为本地可读路径。
单数/复数形式的 mediaPath、mediaUrl、mediaType、mediaPaths、
mediaUrls、mediaTypes 以及匹配的 originalMedia* 元数据属性都是
已弃用的兼容性别名。新的钩子应使用顶层的类型化数组。
决策规则:
message_sending中的cancel: true是终态。message_sending中的cancel: false视为未作出决定。- 重写后的
content会继续传递给低优先级钩子,除非后续钩子 取消投递。 reply_payload_sending在负载规范化之后、通道 投递之前运行,包括路由回原始通道的回复。 处理程序按顺序运行,每个处理程序都会看到更高优先级处理程序生成的最新负载。reply_payload_sending负载不会暴露运行时信任标记,例如trustedLocalMedia;插件可以编辑负载形状,但不能授予本地 媒体信任。message_sending可以在取消时返回cancelReason和受限的metadata。新的消息生命周期 API 会将其作为被抑制的投递结果暴露,并给出原因cancelled_by_message_sending_hook;为兼容性起见,旧版直接投递仍会返回空结果数组。message_sent仅用于观察。处理程序失败会被记录,但不会 改变投递结果。
安装钩子
使用security.installPolicy 处理由运维方拥有的允许/阻止决策。该策略运行于 OpenClaw 配置中,覆盖 CLI 安装和更新路径,并且在启用但不可用时会默认拒绝(fail closed)。
before_install 是一个插件运行时生命周期钩子。它仅在 security.installPolicy 之后执行,并且只在已加载插件钩子的 OpenClaw 进程中运行,例如由 Gateway 支持的安装流程。它适用于插件自身的观测、警告和兼容性检查,但它并不是安装过程中主要的企业级或主机安全边界。builtinScan 字段仍保留在事件负载中以保持兼容性,但 OpenClaw 不再执行内置的安装时危险代码阻止逻辑,因此它会是一个空的 ok 结果。返回额外的发现结果,或返回 { block: true, blockReason } 以在该进程中停止安装。
block: true 为终止性结果。block: false 会被视为没有决策。处理程序失败会以 fail-closed 方式阻止安装。
网关生命周期
使用gateway_start 来启动通用插件服务,并使用 gateway_stop 来清理长期运行的资源。cron 调度器在 gateway_start 运行时仍可能处于加载中,因此不要把它作为外部 cron 投影的基线信号。
旧版的 api.on("deactivate", ...) 别名已于 2026 年 8 月移除。使用
gateway_stop 进行清理;请参见
迁移说明。
不要依赖内部的 gateway:startup 钩子来运行插件拥有的运行时
服务。
cron_reconciled 会在 Gateway 的 cron 调度器及其退出时监听器完成有状态协调后触发。它既会在初始启动时触发,也会在配置重载时调度器替换后触发。该事件会报告 reason(startup 或 reload)以及实际生效的 enabled 状态。即使 cron 被禁用,也会以 enabled: false 触发,从而允许外部投影清除过期的唤醒。使用 ctx.getCron?.() 获取完成协调的精确调度器实例;之后的重载不会重新指向该回调。ctx.abortSignal 持有同一份调度器快照。Gateway 会在有更新的调度器被启用或关闭开始时立刻中止它。请将它传递给每一个持久化副作用,并且在它中止后不要再接受该快照。
这是一个调度器生命周期信号,不是插件激活信号:仅插件热重载不会再次触发它。新启用的消费者会在下一次调度器替换或 Gateway 启动时收到它的第一个基线信号。
与其他观察钩子类似,gateway_start 和 cron_reconciled 的回调可能会重叠。如果两个处理器共享插件初始化,请使用插件本地的就绪 promise 来协调,而不要依赖回调顺序。
cron_changed 会针对 Gateway 拥有的 cron 生命周期事件触发,并带有类型化事件载荷,涵盖 added、updated、removed、started、finished 和 scheduled 这些原因。该事件携带一个 PluginHookGatewayCronJob 快照(在存在时包括 state.nextRunAtMs、state.lastRunStatus 和 state.lastError),以及一个 PluginHookGatewayCronDeliveryStatus,其值可以是 not-requested | delivered | not-delivered | unknown。removed 事件属于提交后事件:只有在持久化删除成功后才会触发,并且仍然携带已删除的作业快照,以便外部调度器协调状态。
scheduled 事件也属于提交后事件:它只会在一次成功的持久化写入改变了现有作业的有效 nextRunAtMs 之后触发,并且不包括该作业显式的 added、updated 或 removed 生命周期事件。顶层的 event.nextRunAtMs 是已提交的下一次唤醒时间;当它缺失时,表示该作业没有下一次唤醒。请把这些事件视为协调提示,而不是有序的增量日志。将它们作为可合并的提示,用来重新读取由 cron_reconciled 最后捕获的调度器;不要从 cron_changed 上下文中接管调度器。将 OpenClaw 作为到期检查和执行的唯一真实来源。
安全的外部 cron 投影
投影完整的唤醒快照,而不是转发 cron 事件增量。外部适配器的replaceAll 操作必须是原子且幂等的,并且只有在宿主已持久化接受该快照后才算完成。它还必须遵守所提供的中止信号:如果该信号在持久化接受之前中止,则适配器不得接受该快照。
这种模式使得同一时刻只有一个最新状态 worker 在运行。只有 cron_reconciled 会接管一个调度器实例;cron_changed 只是要求该 worker 重新读取权威实例,因此迟到的提示不会恢复较旧的调度器。更新的版本会在宿主尝试接受陈旧快照之前中止当前尝试。
cron_reconciled 报告 enabled: false 时,同一路径会调用 replaceAll([]) 并清除过期的外部唤醒。此示例中的重试/退避是进程本地的,并将运行时适配器失败视为暂时性错误;请在注册前验证不可重试的配置。OpenClaw 不为插件钩子副作用提供 outbox。如果进程在持久化接受之前退出,下一次 Gateway 启动会发出新的权威 cron_reconciled 快照。gateway_stop 会中止正在进行的宿主工作,等待 worker 稳定下来,然后关闭适配器。
即将弃用
有少数与钩子相邻的接口已弃用,但仍受支持。请在下一次重大版本发布前迁移:inbound_claim和message_received处理程序中的纯文本通道信封。读取BodyForAgent和结构化的用户上下文块,而不是解析扁平的信封文本。请参见 纯文本通道信封 → BodyForAgent。subagent_spawning仍为与旧版插件的兼容性而保留,但新插件不应从中返回线程路由。Core 会在subagent_spawned触发前,通过通道会话绑定适配器准备好带有thread: true的子代理绑定。before_tool_call中的onResolution现在使用类型化的PluginApprovalResolution联合(allow-once/allow-always/deny/timeout/cancelled),而不是自由格式的string。api.registerSessionExtension/api.enqueueNextTurnInjection仍作为顶层兼容性别名保留。新插件应使用api.session.state.registerSessionExtension(...)和api.session.workflow.enqueueNextTurnInjection(...)。
command-auth → command-status 重命名——请参见插件 SDK 迁移 → 活跃弃用项。