Hooks
Hooks 提供了一个可扩展的事件驱动系统,用于响应代理命令和事件而自动执行操作。Hooks 会自动从目录中发现,并可以使用 openclaw hooks 进行检查,而 hook-pack 的安装和更新现在通过 openclaw plugins 进行。
Hooks 是在发生某些事情时运行的小脚本。有两种类型:
- Hooks(本页):在 Gateway(网关) 内部运行,当代理事件触发时,例如
/new、/reset、/stop或生命周期事件。 - Webhooks:外部 HTTP webhooks,允许其他系统在 OpenClaw 中触发工作。请参阅 Webhook Hooks 或使用
openclaw webhooks获取 Gmail 辅助命令。
Hooks 也可以打包在插件内部;请参阅 Plugin hooks。openclaw hooks list 会显示独立的 hook 和插件管理的 hook。
常见用途:
- 重置会话时保存内存快照
- 保留命令的审计跟踪以进行故障排除或合规性检查
- 在会话开始或结束时触发后续自动化
- 当事件触发时,将文件写入代理工作区或调用外部 API
如果您能编写一个小型的 TypeScript 函数,就能编写 hook。托管和打包的 hooks 是受信任的本地代码。Workspace hooks 会被自动发现,但 OpenClaw 会保持禁用状态,直到您通过 CLI 或配置明确启用它们。
Hooks 系统允许您:
- 当发出
/new时,将会话上下文保存到内存 - 记录所有命令以供审计
- 在代理生命周期事件上触发自定义自动化
- 扩展 OpenClaw 的行为而无需修改核心代码
内置 Hooks
Section titled “内置 Hooks”OpenClaw 附带了四个自动发现的内置 hooks:
- 💾 会话-memory:当您发出
/new或/reset时,将会话上下文保存到您的代理工作区(默认~/.openclaw/workspace/memory/) - 📎 bootstrap-extra-files:在
agent:bootstrap期间,根据配置的 glob/路径模式注入额外的工作区引导文件 - 📝 command-logger:将所有命令事件记录到
~/.openclaw/logs/commands.log - 🚀 boot-md:在网关启动时运行
BOOT.md(需要启用内部 hooks)
列出可用的挂钩:
openclaw hooks list启用挂钩:
openclaw hooks enable session-memory检查挂钩状态:
openclaw hooks check获取详细信息:
openclaw hooks info session-memory在 新手引导(openclaw onboard)期间,系统将提示您启用推荐的 hooks。向导会自动发现符合条件的 hooks 并将其列出供您选择。
Hooks 在 Gateway(网关) 进程内运行。请将捆绑 hooks、托管 hooks 和 hooks.internal.load.extraDirs 视为受信任的本地代码。<workspace>/hooks/ 下的工作区 hooks 是仓库本地代码,因此 OpenClaw 需要在加载之前执行明确的启用步骤。
Hook 设备发现
Section titled “Hook 设备发现”Hooks 会按覆盖优先级从高到低的顺序,自动从以下目录中发现:
- Bundled hooks:随 OpenClaw 一起提供;位于
<openclaw>/dist/hooks/bundled/供 npm 安装使用(或编译二进制文件的同级hooks/bundled/) - 插件钩子:捆绑在已安装插件内部的钩子(请参阅 插件钩子)
- 托管钩子:
~/.openclaw/hooks/(用户安装的,在工作区之间共享;可以覆盖捆绑和插件钩子)。通过hooks.internal.load.extraDirs配置的额外钩子目录也被视为托管钩子,并共享相同的覆盖优先级。 - 工作区钩子:
<workspace>/hooks/(每个代理,默认禁用,直到明确启用;无法覆盖来自其他源的钩子)
Workspace hooks 可以为仓库添加新的 hook 名称,但它们无法覆盖具有相同名称的打包、托管或插件提供的 hooks。
托管的 hook 目录可以是 单个 hook 或 hook pack(包目录)。
每个 hook 都是一个包含以下内容的目录:
my-hook/├── HOOK.md # Metadata + documentation└── handler.ts # Handler implementationHook Packs (npm/archives)
Section titled “Hook Packs (npm/archives)”Hook packs 是标准的 npm 包,通过 openclaw.hooks 在
package.json 中导出一个或多个 hooks。请使用以下命令安装:
openclaw plugins install <path-or-spec>Npm 规范仅限于注册表(包名称 + 可选的确切版本或 dist-tag)。 Git/URL/file 规范和 semver 范围将被拒绝。
裸规格和 @latest 保持在稳定轨道上。如果 npm 将其中任何一个解析为预发布版本,OpenClaw 将停止并要求您使用预发布标签(例如 @beta/@rc)或确切的预发布版本显式选择加入。
示例 package.json:
{ "name": "@acme/my-hooks", "version": "0.1.0", "openclaw": { "hooks": ["./hooks/my-hook", "./hooks/other-hook"] }}每个条目指向一个包含 HOOK.md 和处理程序文件的钩子目录。加载程序依次尝试 handler.ts、handler.js、index.ts、index.js。
钩子包可以附带依赖项;它们将被安装在 ~/.openclaw/hooks/<id> 下。
每个 openclaw.hooks 条目在解析符号链接后必须保留在包目录内;超出该范围的条目将被拒绝。
安全提示:openclaw plugins install 使用 npm install --ignore-scripts 安装 hook-pack 依赖项
(不包含生命周期脚本)。请保持 hook pack 的依赖树为“纯 JS/TS”,并避免依赖
postinstall 构建的包。
Hook 结构
Section titled “Hook 结构”HOOK.md 格式
Section titled “HOOK.md 格式”HOOK.md 文件包含 YAML frontmatter 中的元数据以及 Markdown 文档:
---name: my-hookdescription: "Short description of what this hook does"homepage: https://docs.openclaw.ai/automation/hooks#my-hookmetadata: { "openclaw": { "emoji": "🔗", "events": ["command:new"], "requires": { "bins": ["node"] } } }---
# My Hook
Detailed documentation goes here...
## What It Does
- Listens for `/new` commands- Performs some action- Logs the result
## Requirements
- Node.js must be installed
## Configuration
No configuration needed.metadata.openclaw 对象支持:
emoji:显示 CLI 的表情符号(例如"💾")events:要监听的事件数组(例如["command:new", "command:reset"])export:要使用的命名导出(默认为"default")homepage:文档 URLos:必需平台(例如["darwin", "linux"])requires:可选要求bins:PATH 中必需的二进制文件(例如["git", "node"])anyBins:至少必须存在这些二进制文件之一env:必需的环境变量config:必需的配置路径(例如["workspace.dir"])
always:绕过资格检查(布尔值)install:安装方法(对于内置 hooks:[{"id":"bundled","kind":"bundled"}])
处理程序实现
Section titled “处理程序实现”handler.ts 文件导出一个 HookHandler 函数:
const myHandler = async (event) => { // Only trigger on 'new' command if (event.type !== "command" || event.action !== "new") { return; }
console.log(`[my-hook] New command triggered`); console.log(` Session: ${event.sessionKey}`); console.log(` Timestamp: ${event.timestamp.toISOString()}`);
// Your custom logic here
// Optionally send message to user event.messages.push("✨ My hook executed!");};
export default myHandler;每个事件包括:
{ type: 'command' | 'session' | 'agent' | 'gateway' | 'message', action: string, // e.g., 'new', 'reset', 'stop', 'received', 'sent' sessionKey: string, // Session identifier timestamp: Date, // When the event occurred messages: string[], // Push messages here to send to user context: { // Command events (command:new, command:reset): sessionEntry?: SessionEntry, // current session entry previousSessionEntry?: SessionEntry, // pre-reset entry (preferred for session-memory) commandSource?: string, // e.g., 'whatsapp', 'telegram' senderId?: string, workspaceDir?: string, cfg?: OpenClawConfig, // Command events (command:stop only): sessionId?: string, // Agent bootstrap events (agent:bootstrap): bootstrapFiles?: WorkspaceBootstrapFile[], sessionKey?: string, // routing session key sessionId?: string, // internal session UUID agentId?: string, // resolved agent ID // Message events (see Message Events section for full details): from?: string, // message:received to?: string, // message:sent content?: string, channelId?: string, success?: boolean, // message:sent }}当发出代理命令时触发:
command:所有命令事件(通用监听器)command:new:发出/new命令时command:reset:发出/reset命令时command:stop:发出/stop命令时
session:compact:before:紧接在压缩总结历史之前session:compact:after:压缩完成后,带有摘要元数据
内部 hook 有效负载将这些作为带有 action: "compact:before" / action: "compact:after" 的 type: "session" 发出;监听器使用上述组合键进行订阅。
特定的处理程序注册使用字面键格式 ${type}:${action}。对于这些事件,请注册 session:compact:before 和 session:compact:after。
session:compact:before 上下文字段:
sessionId:内部会话 UUIDmissingSessionKey:当没有可用的会话密钥时为 truemessageCount:压缩前的消息数量tokenCount: 压缩前的 token 计数(可能不存在)messageCountOriginal: 来自完整未截断会话历史记录的消息计数tokenCountOriginal: 完整原始历史的 token 计数(可能不存在)
session:compact:after 上下文字段(除了 sessionId 和 missingSessionKey):
messageCount:压缩后的消息计数tokenCount:压缩后的 token 计数(可能不存在)compactedCount:被压缩/移除的消息数量summaryLength: 生成的压缩摘要的字符长度tokensBefore: 压缩前的 token 计数(用于增量计算)tokensAfter:压缩后的 token 计数firstKeptEntryId:压缩后保留的第一条消息条目的 ID
agent:bootstrap: 在注入工作区引导文件之前(Hooks 可能会修改context.bootstrapFiles)
Gateway(网关) Events
Section titled “Gateway(网关) Events”在网关启动时触发:
gateway:startup: 在通道启动并加载钩子之后
会话修补事件
Section titled “会话修补事件”当会话属性被修改时触发:
session:patch: 当会话被更新时
会话事件上下文
Section titled “会话事件上下文”会话事件包含有关会话及其更改的丰富上下文:
{ sessionEntry: SessionEntry, // The complete updated session entry patch: { // The patch object (only changed fields) // Session identity & labeling label?: string | null, // Human-readable session label
// AI model configuration model?: string | null, // Model override (e.g., "claude-sonnet-4-6") thinkingLevel?: string | null, // Thinking level ("off"|"low"|"med"|"high") verboseLevel?: string | null, // Verbose output level reasoningLevel?: string | null, // Reasoning mode override elevatedLevel?: string | null, // Elevated mode override responseUsage?: "off" | "tokens" | "full" | "on" | null, // Usage display mode ("on" is backwards-compat alias for "full") fastMode?: boolean | null, // Fast/turbo mode toggle spawnedWorkspaceDir?: string | null, // Workspace dir override for spawned subagents subagentRole?: "orchestrator" | "leaf" | null, // Subagent role assignment subagentControlScope?: "children" | "none" | null, // Scope of subagent control
// Tool execution settings execHost?: string | null, // Exec host (sandbox|gateway|node) execSecurity?: string | null, // Security mode (deny|allowlist|full) execAsk?: string | null, // Approval mode (off|on-miss|always) execNode?: string | null, // Node ID for host=node
// Subagent coordination spawnedBy?: string | null, // Parent session key (for subagents) spawnDepth?: number | null, // Nesting depth (0 = root)
// Communication policies sendPolicy?: "allow" | "deny" | null, // Message send policy groupActivation?: "mention" | "always" | null, // Group chat activation }, cfg: OpenClawConfig // Current gateway config}安全提示: 只有特权客户端(包括控制 UI)才能触发 session:patch 事件。标准 WebChat 客户端被禁止修补会话,因此该挂钩不会从这些连接中触发。
有关完整的类型定义,请参阅 src/gateway/protocol/schema/sessions.ts 中的 SessionsPatchParamsSchema。
示例:会话补全记录器 Hook
Section titled “示例:会话补全记录器 Hook”const handler = async (event) => { if (event.type !== "session" || event.action !== "patch") { return; } const { patch } = event.context; console.log(`[session-patch] Session updated: ${event.sessionKey}`); console.log(`[session-patch] Changes:`, patch);};
export default handler;在接收或发送消息时触发:
message:所有消息事件(通用侦听器)message:received:当从任何渠道接收到入站消息时触发。在处理早期、媒体理解之前触发。内容可能包含针对尚未处理的媒体附件的原始占位符,例如<media:audio>。message:transcribed:当消息已被完全处理(包括音频转录和链接理解)时触发。此时,transcript包含音频消息的完整转录文本。当您需要访问转录后的音频内容时,请使用此钩子。message:preprocessed:在所有媒体 + 链接理解完成后,为每条消息触发,允许钩子在代理看到消息之前访问完全丰富化的正文(转录文本、图像描述、链接摘要)。message:sent:当出站消息成功发送时触发。
消息事件上下文
Section titled “消息事件上下文”消息事件包含关于消息的丰富上下文:
// message:received context{ from: string, // Sender identifier (phone number, user ID, etc.) content: string, // Message content timestamp?: number, // Unix timestamp when received channelId: string, // Channel (e.g., "whatsapp", "telegram", "discord") accountId?: string, // Provider account ID for multi-account setups conversationId?: string, // Chat/conversation ID messageId?: string, // Message ID from the provider metadata?: { // Additional provider-specific data to?: string, provider?: string, surface?: string, threadId?: string | number, senderId?: string, senderName?: string, senderUsername?: string, senderE164?: string, guildId?: string, // Discord guild / server ID channelName?: string, // Channel name (e.g., Discord channel name) }}
// message:sent context{ to: string, // Recipient identifier content: string, // Message content that was sent success: boolean, // Whether the send succeeded error?: string, // Error message if sending failed channelId: string, // Channel (e.g., "whatsapp", "telegram", "discord") accountId?: string, // Provider account ID conversationId?: string, // Chat/conversation ID messageId?: string, // Message ID returned by the provider isGroup?: boolean, // Whether this outbound message belongs to a group/channel context groupId?: string, // Group/channel identifier for correlation with message:received}
// message:transcribed context{ from?: string, // Sender identifier to?: string, // Recipient identifier body?: string, // Raw inbound body before enrichment bodyForAgent?: string, // Enriched body visible to the agent transcript: string, // Audio transcript text timestamp?: number, // Unix timestamp when received channelId: string, // Channel (e.g., "telegram", "whatsapp") conversationId?: string, messageId?: string, senderId?: string, // Sender user ID senderName?: string, // Sender display name senderUsername?: string, provider?: string, // Provider name surface?: string, // Surface name mediaPath?: string, // Path to the media file that was transcribed mediaType?: string, // MIME type of the media}
// message:preprocessed context{ from?: string, // Sender identifier to?: string, // Recipient identifier body?: string, // Raw inbound body bodyForAgent?: string, // Final enriched body after media/link understanding transcript?: string, // Transcript when audio was present timestamp?: number, // Unix timestamp when received channelId: string, // Channel (e.g., "telegram", "whatsapp") conversationId?: string, messageId?: string, senderId?: string, // Sender user ID senderName?: string, // Sender display name senderUsername?: string, provider?: string, // Provider name surface?: string, // Surface name mediaPath?: string, // Path to the media file mediaType?: string, // MIME type of the media isGroup?: boolean, groupId?: string,}示例:消息记录器钩子
Section titled “示例:消息记录器钩子”const isMessageReceivedEvent = (event: { type: string; action: string }) => event.type === "message" && event.action === "received";const isMessageSentEvent = (event: { type: string; action: string }) => event.type === "message" && event.action === "sent";
const handler = async (event) => { if (isMessageReceivedEvent(event as { type: string; action: string })) { console.log(`[message-logger] Received from ${event.context.from}: ${event.context.content}`); } else if (isMessageSentEvent(event as { type: string; action: string })) { console.log(`[message-logger] Sent to ${event.context.to}: ${event.context.content}`); }};
export default handler;工具结果钩子(插件 API)
Section titled “工具结果钩子(插件 API)”这些钩子不是事件流监听器;它们允许插件在 OpenClaw 持久化工具结果之前同步调整它们。
tool_result_persist:在将工具结果写入会话记录之前转换它们。必须是同步的;返回更新后的工具结果负载或undefined以保持原样。请参阅 代理循环。
插件钩子事件
Section titled “插件钩子事件”before_tool_call
Section titled “before_tool_call”在每个工具调用之前运行。插件可以修改参数、阻止调用或请求用户批准。
返回字段:
params:覆盖工具参数(与原始参数合并)block:设置为true以阻止工具调用blockReason:被阻止时向代理显示的原因requireApproval:暂停执行并等待通过渠道的用户批准
requireApproval 字段触发原生平台批准(Telegram 按钮、Discord 组件、/approve 命令),而不是依赖代理配合:
{ requireApproval: { title: "Sensitive operation", description: "This tool call modifies production data", severity: "warning", // "info" | "warning" | "critical" timeoutMs: 120000, // default: 120s timeoutBehavior: "deny", // "allow" | "deny" (default) onResolution: async (decision) => { // Called after the user resolves: "allow-once", "allow-always", "deny", "timeout", or "cancelled" }, }}当审批解决、超时或取消后,将使用最终决策字符串调用 onResolution 回调。它在插件进程内运行(不发送到网关)。使用它来持久化决策、更新缓存或执行清理。
pluginId 字段由钩子运行器根据插件注册自动标记。当多个插件返回 requireApproval 时,第一个(优先级最高)获胜。
block 优先于 requireApproval:如果合并的钩子结果同时具有 block: true 和 requireApproval 字段,则工具调用将被立即阻止,而不会触发审批流程。这确保了高优先级插件的阻止不能被低优先级插件的审批请求覆盖。
如果网关不可用或不支持插件审批,工具调用将使用 description 作为阻止原因回退到软阻止。
before_install
Section titled “before_install”在内置安装安全扫描之后、安装继续之前运行。OpenClaw 会针对交互式技能安装以及插件包、包和单文件安装触发此钩子。
默认行为因目标类型而异:
- 除非操作员明确使用
openclaw plugins install --dangerously-force-unsafe-install,否则插件安装在内置扫描critical发现结果和扫描错误上默认失败。 - 技能安装仍会将内置扫描发现结果和扫描错误显示为警告,并默认继续。
返回字段:
findings:作为警告显示的额外扫描发现结果block:设置为true以阻止安装blockReason:阻止时显示的人类可读原因
事件字段:
targetType:安装目标类别(skill或plugin)targetName:安装目标的人类可读技能名称或插件 IDsourcePath:正在扫描的安装目标内容的绝对路径sourcePathKind:扫描的内容是file还是directoryorigin:可用的标准化安装来源(例如openclaw-bundled、openclaw-workspace、plugin-bundle、plugin-package或plugin-file)request:安装请求的出处,包括kind、mode和可选的requestedSpecifierbuiltinScan:内置扫描器的结构化结果,包括status、摘要计数、发现的问题以及可选的errorskill:当targetType为skill时的技能安装元数据,包括installId和所选的installSpecplugin:当targetType为plugin时的插件安装元数据,包括规范的pluginId、标准化的contentType、可选的packageName/manifestId/version以及extensions
事件示例(插件包安装):
{ "targetType": "plugin", "targetName": "acme-audit", "sourcePath": "/var/folders/.../openclaw-plugin-acme-audit/package", "sourcePathKind": "directory", "origin": "plugin-package", "request": { "kind": "plugin-npm", "mode": "install", }, "builtinScan": { "status": "ok", "scannedFiles": 12, "critical": 0, "warn": 1, "info": 0, "findings": [ { "severity": "warn", "ruleId": "network_fetch", "file": "dist/index.js", "line": 88, "message": "Dynamic network fetch detected during install review." } ] }, "plugin": { "pluginId": "acme-audit", "contentType": "package", "packageName": "@acme/openclaw-plugin-audit", "manifestId": "acme-audit", "version": "1.4.2", "extensions": ["./dist/index.js"] }}技能安装使用相同的事件结构,但使用 targetType: "skill" 和 skill 对象来代替 plugin。
决策语义:
before_install:{ block: true }是终止性的,并会停止优先级较低的处理程序。before_install:{ block: false }被视为未做决策。
将此钩子用于需要在安装之前审计安装来源的外部安全扫描器、策略引擎或企业审批网关。
压缩生命周期
Section titled “压缩生命周期”通过插件钩子运行器公开的压缩生命周期钩子:
before_compaction: 在压缩之前运行,包含计数/token 元数据after_compaction: 在压缩之后运行,包含压缩摘要元数据
完整插件 Hook 参考
Section titled “完整插件 Hook 参考”通过 Plugin SDK 注册的所有 27 个 hook。标记为 sequential 的 hook 按优先级顺序运行并可以修改结果;parallel hook 即发即弃。
模型和提示词 hooks
Section titled “模型和提示词 hooks”| Hook | 时间 | 执行 | 返回 |
|---|---|---|---|
before_model_resolve | 查找模型/提供商之前 | 顺序 | { modelOverride?, providerOverride? } |
before_prompt_build | 模型解析后,会话消息就绪 | 顺序 | { systemPrompt?, prependContext?, appendSystemContext? } |
before_agent_start | 旧版组合 hook(优先使用上述两个) | 顺序 | 两种结果类型的联合 |
llm_input | 紧接在 LLM API 调用之前 | 并行 | void |
llm_output | 收到 LLM 响应后立即执行 | 并行 | void |
Agent 生命周期 hooks
Section titled “Agent 生命周期 hooks”| Hook | 时间 | 执行 | 返回 |
|---|---|---|---|
agent_end | Agent 运行完成后(成功或失败) | 并行 | void |
before_reset | 当 /new 或 /reset 清除会话时 | 并行 | void |
before_compaction | 压缩总结历史记录之前 | 并行 | void |
after_compaction | 压缩完成后 | 并行 | void |
会话生命周期 hooks
Section titled “会话生命周期 hooks”| Hook | 时间 | 执行 | 返回 |
|---|---|---|---|
session_start | 当新会话开始时 | 并行 | void |
session_end | 当会话结束时 | 并行 | void |
消息流 hooks
Section titled “消息流 hooks”| Hook | 时间 | 执行 | 返回 |
|---|---|---|---|
inbound_claim | 命令/代理分发之前;先到先得 | 顺序 | { handled: boolean } |
message_received | 收到入站消息后 | 并行 | void |
before_dispatch | 命令解析之后,模型调度之前 | 顺序 | { handled: boolean, text? } |
message_sending | 出站消息投递之前 | 顺序 | { content?, cancel? } |
message_sent | 出站消息投递之后 | 并行 | void |
before_message_write | 消息写入会话记录之前 | 同步,顺序 | { block?, message? } |
工具执行钩子
Section titled “工具执行钩子”| 钩子 | 时机 | 执行 | 返回 |
|---|---|---|---|
before_tool_call | 每次工具调用之前 | 顺序 | { params?, block?, blockReason?, requireApproval? } |
after_tool_call | 工具调用完成之后 | 并行 | void |
tool_result_persist | 工具结果写入记录之前 | 同步,顺序 | { message? } |
| 钩子 | 时机 | 执行 | 返回 |
|---|---|---|---|
subagent_spawning | 子代理会话创建之前 | 顺序 | { status, threadBindingReady? } |
subagent_delivery_target | 生成之后,以解析投递目标 | 顺序 | { origin? } |
subagent_spawned | 子代理完全生成之后 | 并行 | void |
subagent_ended | 当子代理会话终止时 | 并行 | void |
Gateway(网关) hooks
Section titled “Gateway(网关) hooks”| 钩子 | 时机 | 执行 | 返回 |
|---|---|---|---|
gateway_start | 网关进程完全启动之后 | 并行 | void |
gateway_stop | 当网关正在关闭时 | 并行 | void |
| 钩子 | 时机 | 执行 | 返回 |
|---|---|---|---|
before_install | 内置安全扫描之后,安装继续之前 | 顺序 | { findings?, block?, blockReason? } |
有关完整的处理程序签名和上下文类型,请参阅插件架构。
以下事件类型已计划用于内部 hook 事件流。
请注意,session_start 和 session_end 已作为插件 Hook API hook 存在
但在 HOOK.md 元数据中尚不可用作内部 hook 事件键:
session:start:当新会话开始时(计划用于内部 hook 流;作为插件 hooksession_start可用)session:end:当会话结束时(计划用于内部 hook 流;作为插件 hooksession_end可用)agent:error:当代理遇到错误时
创建自定义 Hooks
Section titled “创建自定义 Hooks”1. 选择位置
Section titled “1. 选择位置”- 工作区 hooks (
<workspace>/hooks/):针对每个代理;可以添加新的 hook 名称,但不能覆盖同名的捆绑、托管或插件 hooks - 托管 hooks (
~/.openclaw/hooks/):在工作区之间共享;可以覆盖捆绑和插件 hooks
2. 创建目录结构
Section titled “2. 创建目录结构”mkdir -p ~/.openclaw/hooks/my-hookcd ~/.openclaw/hooks/my-hook3. 创建 HOOK.md
Section titled “3. 创建 HOOK.md”---name: my-hookdescription: "Does something useful"metadata: { "openclaw": { "emoji": "🎯", "events": ["command:new"] } }---
# My Custom Hook
This hook does something useful when you issue `/new`.4. 创建 handler.ts
Section titled “4. 创建 handler.ts”const handler = async (event) => { if (event.type !== "command" || event.action !== "new") { return; }
console.log("[my-hook] Running!"); // Your logic here};
export default handler;5. 启用和测试
Section titled “5. 启用和测试”# Verify hook is discoveredopenclaw hooks list
# Enable itopenclaw hooks enable my-hook
# Restart your gateway process (menu bar app restart on macOS, or restart your dev process)
# Trigger the event# Send /new via your messaging channel新配置格式(推荐)
Section titled “新配置格式(推荐)”{ "hooks": { "internal": { "enabled": true, "entries": { "session-memory": { "enabled": true }, "command-logger": { "enabled": false } } } }}每个 Hook 的配置
Section titled “每个 Hook 的配置”Hooks 可以具有自定义配置:
{ "hooks": { "internal": { "enabled": true, "entries": { "my-hook": { "enabled": true, "env": { "MY_CUSTOM_VAR": "value" } } } } }}从其他目录加载 hooks(被视为托管 hooks,具有相同的覆盖优先级):
{ "hooks": { "internal": { "enabled": true, "load": { "extraDirs": ["/path/to/more/hooks"] } } }}旧配置格式(仍然支持)
Section titled “旧配置格式(仍然支持)”为了向后兼容,旧配置格式仍然有效:
{ "hooks": { "internal": { "enabled": true, "handlers": [ { "event": "command:new", "module": "./hooks/handlers/my-handler.ts", "export": "default" } ] } }}注意:module 必须是相对于工作区的路径。绝对路径和遍历工作区外部的路径将被拒绝。
迁移:请对新的 hooks 使用基于发现的系统。旧的处理程序在基于目录的 hooks 之后加载。
CLI 命令
Section titled “CLI 命令”列出 Hooks
Section titled “列出 Hooks”# List all hooksopenclaw hooks list
# Show only eligible hooksopenclaw hooks list --eligible
# Verbose output (show missing requirements)openclaw hooks list --verbose
# JSON outputopenclaw hooks list --jsonHook 信息
Section titled “Hook 信息”# Show detailed info about a hookopenclaw hooks info session-memory
# JSON outputopenclaw hooks info session-memory --json# Show eligibility summaryopenclaw hooks check
# JSON outputopenclaw hooks check --json# Enable a hookopenclaw hooks enable session-memory
# Disable a hookopenclaw hooks disable command-logger捆绑 hook 参考
Section titled “捆绑 hook 参考”会话-memory
Section titled “会话-memory”当您发出 /new 或 /reset 时,将会话上下文保存到内存中。
事件:command:new,command:reset
要求:必须配置 workspace.dir
输出:<workspace>/memory/YYYY-MM-DD-slug.md(默认为 ~/.openclaw/workspace)
作用:
- 使用重置前的会话条目来定位正确的转录记录
- 从对话中提取最后 15 条用户/助手消息(可配置)
- 使用 LLM 生成描述性的文件名别名
- 将会话元数据保存到带日期的内存文件中
输出示例:
# Session: 2026-01-16 14:30:00 UTC
- **Session Key**: agent:main:main- **Session ID**: abc123def456- **Source**: telegram
## Conversation Summary
user: Can you help me design the API?assistant: Sure! Let's start with the endpoints...文件名示例:
2026-01-16-vendor-pitch.md2026-01-16-api-design.md2026-01-16-1430.md(如果别名生成失败则使用后备时间戳)
启用:
openclaw hooks enable session-memorybootstrap-extra-files
Section titled “bootstrap-extra-files”在 agent:bootstrap 期间注入额外的引导文件(例如 monorepo 本地 AGENTS.md / TOOLS.md)。
事件:agent:bootstrap
要求:必须配置 workspace.dir
输出:不写入文件;仅在内存中修改引导上下文。
配置:
{ "hooks": { "internal": { "enabled": true, "entries": { "bootstrap-extra-files": { "enabled": true, "paths": ["packages/*/AGENTS.md", "packages/*/TOOLS.md"] } } } }}配置选项:
paths(string[]):从工作区解析的 glob/路径模式。patterns(string[]):paths的别名。files(string[]):paths的别名。
备注:
- 路径是相对于工作区解析的。
- 文件必须位于工作区内(经 realpath 检查)。
- 仅加载已识别的引导基本名称(
AGENTS.md、SOUL.md、TOOLS.md、IDENTITY.md、USER.md、HEARTBEAT.md、BOOTSTRAP.md、MEMORY.md、memory.md)。 - 对于子代理/定时会话,适用更严格的允许列表(
AGENTS.md、TOOLS.md、SOUL.md、IDENTITY.md、USER.md)。
启用:
openclaw hooks enable bootstrap-extra-filescommand-logger
Section titled “command-logger”将所有命令事件记录到集中式审计文件中。
事件:command
要求:无
输出:~/.openclaw/logs/commands.log
作用:
- 捕获事件详细信息(命令操作、时间戳、会话密钥、发送者 ID、来源)
- 以 JSONL 格式追加到日志文件
- 在后台静默运行
示例日志条目:
{"timestamp":"2026-01-16T14:30:00.000Z","action":"new","sessionKey":"agent:main:main","senderId":"+1234567890","source":"telegram"}{"timestamp":"2026-01-16T15:45:22.000Z","action":"stop","sessionKey":"agent:main:main","senderId":"[email protected]","source":"whatsapp"}查看日志:
# View recent commandstail -n 20 ~/.openclaw/logs/commands.log
# Pretty-print with jqcat ~/.openclaw/logs/commands.log | jq .
# Filter by actiongrep '"action":"new"' ~/.openclaw/logs/commands.log | jq .启用:
openclaw hooks enable command-loggerboot-md
Section titled “boot-md”当 Gateway(网关) 启动时(通道启动之后)运行 BOOT.md。
必须启用内部 Hooks 才能运行此操作。
事件: gateway:startup
要求: 必须配置 workspace.dir
作用:
- 从工作区读取
BOOT.md - 通过代理运行器运行指令
- 通过消息工具发送任何请求的出站消息
启用:
openclaw hooks enable boot-md保持处理程序快速
Section titled “保持处理程序快速”Hooks 在命令处理期间运行。请保持轻量级:
// ✓ Good - async work, returns immediatelyconst handler: HookHandler = async (event) => { void processInBackground(event); // Fire and forget};
// ✗ Bad - blocks command processingconst handler: HookHandler = async (event) => { await slowDatabaseQuery(event); await evenSlowerAPICall(event);};优雅地处理错误
Section titled “优雅地处理错误”始终包裹有风险的操作:
const handler: HookHandler = async (event) => { try { await riskyOperation(event); } catch (err) { console.error("[my-handler] Failed:", err instanceof Error ? err.message : String(err)); // Don't throw - let other handlers run }};尽早过滤事件
Section titled “尽早过滤事件”如果事件不相关,请尽早返回:
const handler: HookHandler = async (event) => { // Only handle 'new' commands if (event.type !== "command" || event.action !== "new") { return; }
// Your logic here};使用特定事件键
Section titled “使用特定事件键”尽可能在元数据中指定确切事件:
metadata: { "openclaw": { "events": ["command:new"] } } # Specific而不是:
metadata: { "openclaw": { "events": ["command"] } } # General - more overhead启用 Hook 日志记录
Section titled “启用 Hook 日志记录”Gateway(网关) 在启动时记录 Hook 加载情况:
Registered hook: session-memory -> command:new, command:resetRegistered hook: bootstrap-extra-files -> agent:bootstrapRegistered hook: command-logger -> commandRegistered hook: boot-md -> gateway:startup检查设备发现
Section titled “检查设备发现”列出所有发现的 Hooks:
openclaw hooks list --verbose在处理程序中,记录调用时间:
const handler: HookHandler = async (event) => { console.log("[my-handler] Triggered:", event.type, event.action); // Your logic};检查 Hook 不符合资格的原因:
openclaw hooks info my-hook在输出中查找缺失的要求。
Gateway(网关) 日志
Section titled “Gateway(网关) 日志”监控 Gateway(网关) 日志以查看 Hook 执行情况:
# macOS./scripts/clawlog.sh -f
# Other platformstail -f ~/.openclaw/gateway.log直接测试 Hooks
Section titled “直接测试 Hooks”单独测试处理程序:
import { test } from "vitest";import myHandler from "./hooks/my-hook/handler.js";
test("my handler works", async () => { const event = { type: "command", action: "new", sessionKey: "test-session", timestamp: new Date(), messages: [], context: { foo: "bar" }, };
await myHandler(event);
// Assert side effects});src/hooks/types.ts: 类型定义src/hooks/workspace.ts: 目录扫描和加载src/hooks/frontmatter.ts: HOOK.md 元数据解析src/hooks/config.ts: 资格检查src/hooks/hooks-status.ts: 状态报告src/hooks/loader.ts: 动态模块加载器src/cli/hooks-cli.ts: CLI 命令src/gateway/server-startup.ts: 在 Gateway(网关) 启动时加载 Hookssrc/auto-reply/reply/commands-core.ts: 触发命令事件
设备发现流程
Section titled “设备发现流程”Gateway startup ↓Scan directories (bundled → plugin → managed + extra dirs → workspace) ↓Parse HOOK.md files ↓Sort by override precedence (bundled < plugin < managed < workspace) ↓Check eligibility (bins, env, config, os) ↓Load handlers from eligible hooks ↓Register handlers for eventsUser sends /new ↓Command validation ↓Create hook event ↓Trigger hook (all registered handlers) ↓Command processing continues ↓Session reset未发现 Hook
Section titled “未发现 Hook”-
检查目录结构:
Terminal window ls -la ~/.openclaw/hooks/my-hook/# Should show: HOOK.md, handler.ts -
验证 HOOK.md 格式:
Terminal window cat ~/.openclaw/hooks/my-hook/HOOK.md# Should have YAML frontmatter with name and metadata -
列出所有发现的 Hooks:
Terminal window openclaw hooks list
Hook 不符合资格
Section titled “Hook 不符合资格”检查要求:
openclaw hooks info my-hook查找缺失项:
- 二进制文件(检查 PATH)
- 环境变量
- 配置值
- 操作系统兼容性
Hook 未执行
Section titled “Hook 未执行”-
验证 hook 是否已启用:
Terminal window openclaw hooks list# Should show ✓ next to enabled hooks -
重启您的网关进程以便重新加载 hooks。
-
检查网关日志中的错误:
Terminal window ./scripts/clawlog.sh | grep hook
处理程序错误
Section titled “处理程序错误”检查 TypeScript/导入错误:
# Test import directlynode -e "import('./path/to/handler.ts').then(console.log)"从旧版配置到设备发现
Section titled “从旧版配置到设备发现”之前:
{ "hooks": { "internal": { "enabled": true, "handlers": [ { "event": "command:new", "module": "./hooks/handlers/my-handler.ts" } ] } }}之后:
-
创建 hook 目录:
Terminal window mkdir -p ~/.openclaw/hooks/my-hookmv ./hooks/handlers/my-handler.ts ~/.openclaw/hooks/my-hook/handler.ts -
创建 HOOK.md:
---name: my-hookdescription: "My custom hook"metadata: { "openclaw": { "emoji": "🎯", "events": ["command:new"] } }---# My HookDoes something useful. -
更新配置:
{"hooks": {"internal": {"enabled": true,"entries": {"my-hook": { "enabled": true }}}}} -
验证并重启您的网关进程:
Terminal window openclaw hooks list# Should show: 🎯 my-hook ✓
迁移的好处:
- 自动设备发现
- CLI 管理
- 资格检查
- 更好的文档
- 一致的结构