Skip to content

插件 hooks 是 OpenClaw 在进程内扩展点,用于拦截或修改智能体运行、工具调用、消息流程、会话生命周期和网关启动事件。使用 api.on() 注册钩子,按优先级顺序执行。before_tool_call 可拦截工具调用并要求审批,超时可通过插件配置或 api.ontimeoutMs 设置。非内置插件使用 conversation hooks 需设置 allowConversationAccess 为 true。

OpenClaw 插件 hooks 配置:生命周期钩子与工具调用拦截

插件 hooks 是 OpenClaw 插件的进程内扩展点。当插件需要查看或修改智能体运行、工具调用、消息流程、会话生命周期、子智能体路由、安装或网关启动时,使用插件 hooks。

如果你只需要一个由操作员安装的小型 HOOK.md 脚本来处理命令和网关事件(如 /new/reset/stopagent:bootstrapgateway: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 为以下之一:newresetidledailycompactiondeletedshutdownrestartunknownshutdownrestart 值在网关关闭终结器运行时触发(当进程停止或重启时会话仍处于活动状态),以便下游插件(如内存或日志存储)可以终结原本在重启后会保持打开状态的幽灵行。终结器有超时限制,因此慢速插件不能阻塞 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_stop
  • cron_changed - 观察网关拥有的 cron 生命周期变化(添加、更新、删除、开始、完成、调度)
  • before_install - 检查技能或插件安装扫描并可选择阻塞

调试运行时钩子

当插件需要为智能体轮次切换提供者或模型时,使用 before_model_resolve。它在模型解析之前运行;llm_output 仅在模型尝试产生助手输出之后运行。

要验证有效的会话模型,检查运行时注册表,然后使用 openclaw sessions 或网关会话/状态界面。调试提供者有效载荷时,使用 --raw-stream--raw-stream-path &lt;path&gt; 启动网关;这些标志将原始模型流事件写入 jsonl 文件。

工具调用策略

before_tool_call 接收:

  • event.toolName
  • event.params
  • 可选的 event.toolKindevent.toolInputKind,用于名称故意相同的工具的宿主权威鉴别器;例如,外部代码模式的 exec 调用使用 toolKind: "code_mode_exec",并在输入语言已知时包含 toolInputKind: "javascript" | "typescript"
  • 可选的 event.derivedPaths,包含针对已知工具信封(如 apply_patch)的最佳努力宿主演绎的目标路径提示;当存在时,这些路径可能不完整或可能过度近似工具实际将触及的内容(例如,对于格式错误或部分输入)
  • 可选的 event.runId
  • 可选的 event.toolCallId
  • 上下文字段如 ctx.agentIdctx.sessionKeyctx.sessionIdctx.runIdctx.jobId(在 cron 驱动的运行上设置)、ctx.toolKindctx.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-onceallow-alwaysdenytimeoutcancelled

需要宿主编策略的捆绑插件可以使用 api.registerTrustedToolPolicy(...) 注册受信任的工具策略。这些在普通的 before_tool_call 钩子之前以及外部插件决策之前运行。仅用于宿主编信任的关卡,如工作区策略、预算执行或保留工作流安全。外部插件应使用正常的 before_tool_call 钩子。

工具结果持久化

工具结果可以包含结构化的 details,用于 UI 渲染、诊断、媒体路由或插件拥有的元数据。将 details 视为运行时元数据,而不是提示内容:

  • OpenClaw 在提供者重放和压缩输入前剥离 toolResult.details,以便元数据不会成为模型上下文。
  • 持久化的会话条目仅保留有界的 details。过大的 details 被替换为紧凑摘要和 persistedDetailsTruncated: true
  • tool_result_persistbefore_message_write 在最终持久化上限之前运行。钩子仍应保持返回的 details 较小,并避免将提示相关文本仅放在 details 中;将模型可见的工具输出放在 content 中。

提示与模型钩子

新插件请使用阶段特定的钩子:

  • before_model_resolve:仅接收当前提示和附件元数据。返回 providerOverridemodelOverride
  • agent_turn_prepare:接收当前提示、准备好的会话消息以及为此会话排空的任何一次性排队的注入。返回 prependContextappendContext
  • before_prompt_build:接收当前提示和会话消息。返回 prependContextappendContextsystemPromptprependSystemContextappendSystemContext
  • heartbeat_prompt_contribution:仅对心跳轮次运行,并返回 prependContextappendContext。它适用于需要总结当前状态但又不改变用户发起轮次的背景监视器。

before_agent_start 保留以供兼容之用。新插件请优先使用上面的显式钩子,这样你的插件就不依赖于遗留的合并阶段。

before_agent_run 在提示构造后、任何模型输入(包括提示本地的图像加载和 llm_input 观察)之前运行。它接收当前用户输入作为 prompt,以及加载的会话历史记录 messages 和活动系统提示。返回 { outcome: "block", reason, message? } 以在模型读取提示之前停止运行。reason 是内部的;message 是显示给用户的替代文本。唯一支持的结果是 passblock;不受支持的决策形状导致失败关闭。

当运行被阻塞时,OpenClaw 仅在 message.content 中存储替代文本,加上非敏感的阻塞元数据,如阻塞插件 ID 和时间戳。原始用户文本不会保留在转录或未来上下文中。内部阻塞原因被视为敏感信息,并从转录、历史记录、广播、日志和诊断有效载荷中排除。可观测性应使用清理过的字段,如阻塞器 ID、结果、时间戳或安全类别。

before_agent_startagent_end 在 OpenClaw 可以识别活动运行时会包含 event.runId。相同的值也可在 ctx.runId 上使用。cron 驱动的运行还公开 ctx.jobId(原始 cron 作业 ID),以便插件钩子可以将指标、副作用或状态限定到特定的计划作业。

对于渠道发起的运行,ctx.messageProvider 是提供者表面,如 discordtelegram,而 ctx.channelId 是会话目标标识符(当 OpenClaw 可以从会话密钥或投递元数据派生时)。

agent_end 是一个观察钩子。网关和持久化 harness 路径在轮次后以 fire-and-forget 方式运行它,而短生命周期的单次 CLI 路径在进程清理之前等待钩子 promise,以便受信任的插件可以刷新终端可观测性或捕获状态。钩子运行器应用 30 秒超时,因此卡住的插件或嵌入端点无法使钩子 promise 永远挂起。超时会被记录,OpenClaw 继续;它不会取消插件拥有的网络工作,除非插件也使用自己的中止信号。

使用 model_call_startedmodel_call_ended 进行提供者调用遥测,这些遥测不应接收原始提示、历史记录、响应、标头、请求体或提供者请求 ID。这些钩子包含稳定的元数据,如 runIdcallIdprovidermodel、可选的 api/transport、终端 durationMs/outcome 以及当 OpenClaw 可以派生有界提供者请求 ID 哈希时的 upstreamRequestIdHash。当运行时已解析上下文窗口元数据时,钩子事件和上下文还包括 contextTokenBudget(在模型/配置/智能体上限后的有效令牌预算),以及当应用了较低上限时的 contextWindowSourcecontextWindowReferenceTokens

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_resolvebefore_agent_replyllm_inputllm_outputbefore_agent_finalizeagent_endbefore_agent_run),必须设置:

json
{
  "plugins": {
    "entries": {
      "my-plugin": {
        "hooks": {
          "allowConversationAccess": true
        }
      }
    }
  }
}

提示修改钩子和持久的下轮次注入可以通过 plugins.entries.&lt;id&gt;.hooks.allowPromptInjection=false 按插件禁用。

会话扩展与下轮次注入

工作流插件可以使用 api.registerSessionExtension(...) 持久化小的 JSON 兼容会话状态,并通过网关的 sessions.pluginPatch 方法更新它。会话行通过 pluginExtensions 投射注册的扩展状态,让控制 UI 和其他客户端无需了解插件内部即可渲染插件拥有的状态。

当插件需要持久的上下文以便确切一次地到达下一个模型轮次时,使用 api.enqueueNextTurnInjection(...)。OpenClaw 在提示钩子之前排空排队的注入,丢弃过期的注入,并按每个插件的 idempotencyKey 去重。这是审批恢复、策略摘要、背景监视器增量和命令延续的正确接缝,这些应在下一个轮次对模型可见,但不应成为永久的系统提示文本。

清理语义是合约的一部分。会话扩展清理和运行时生命周期清理回调接收 resetdeletedisablerestart。宿主为 reset/delete/disable 移除拥有插件的持久会话扩展状态和待处理的下轮次注入;restart 保持持久的会话状态,而清理回调让插件释放调度器作业、运行上下文和其他带外资源给旧的运行时代。

消息钩子

使用消息钩子实现渠道级路由和投递策略:

  • message_received:观察入站内容、发送者、threadIdmessageIdsenderId、可选的运行/会话关联和元数据。
  • message_sending:重写 content 或返回 { cancel: true }
  • message_sent:观察最终成功或失败。

对于纯音频 TTS 回复,即使渠道负载没有可见文本/标题,content 也可能包含隐藏的口语转录。重写该 content 仅更新钩子可见的转录;它不会渲染为媒体标题。

消息钩子上下文在可用时公开稳定的关联字段:ctx.sessionKeyctx.runIdctx.messageIdctx.senderIdctx.tracectx.traceIdctx.spanIdctx.parentSpanIdctx.callDepth。在读取遗留元数据之前,优先使用这些一等字段。

优先使用类型化的 threadIdreplyToId 字段,然后再使用渠道特定的元数据。

决策规则:

  • message_sendingcancel: true 是终止性的。
  • message_sendingcancel: 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.configctx.workspaceDirctx.getCron?.() 用于 cron 检查和更新。使用 gateway_stop 清理长时间运行的资源。

不要依赖内部的 gateway:startup 钩子来运行插件拥有的运行时服务。

cron_changed 在网关拥有的 cron 生命周期事件时触发,事件类型有效载荷覆盖 addedupdatedremovedstartedfinishedscheduled 原因。事件携带一个 PluginHookGatewayCronJob 快照(包括 state.nextRunAtMsstate.lastRunStatusstate.lastError,如果存在)加上一个 PluginHookGatewayCronDeliveryStatus,值为 not-requested | delivered | not-delivered | unknown。已删除的事件仍然携带已删除作业的快照,以便外部调度器可以协调状态。在同步外部唤醒调度器时,请使用运行时上下文中的 ctx.getCron?.()ctx.config,并保持 OpenClaw 作为到期检查和执行的真相来源。

即将弃用的功能

一些与钩子相邻的表面已被弃用但仍受支持。在下一次主要版本发布前迁移:

  • 纯文本渠道信封inbound_claimmessage_received 处理器中。请读取 BodyForAgent 和结构化用户上下文块,而不是解析纯文本信封文本。参见纯文本渠道信封 → BodyForAgent
  • before_agent_start 保留以供兼容。新插件应使用 before_model_resolvebefore_prompt_build 代替合并阶段。
  • deactivate 作为弃用的清理兼容性别名保留,直到 2026-08-16 之后。新插件应使用 gateway_stop
  • onResolutionbefore_tool_call现在使用类型化的 PluginApprovalResolution 联合(allow-once / allow-always / deny / timeout / cancelled)而不是自由形式的 string

完整列表——内存能力注册、提供者思考配置文件、外部认证提供者、提供者发现类型、任务运行时访问器以及 command-authcommand-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_resolvebefore_agent_replyllm_inputllm_outputbefore_agent_finalizeagent_endbefore_agent_run 时,必须在插件配置中设置 "hooks": { "allowConversationAccess": true },否则钩子不会被触发。

插件钩子超时怎么设置?优先级如何控制?

超时可在 api.onopts.timeoutMs 设置,也可在插件配置的 hooks.timeoutMs 全局设置,或在 hooks.timeouts.<hookName> 按钩子覆盖。优先级通过 api.onopts.priority 控制,数值越大越先执行;相同优先级的按注册顺序执行。