跳转到内容

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 hooksopenclaw hooks list 会显示独立的 hook 和插件管理的 hook。

常见用途:

  • 重置会话时保存内存快照
  • 保留命令的审计跟踪以进行故障排除或合规性检查
  • 在会话开始或结束时触发后续自动化
  • 当事件触发时,将文件写入代理工作区或调用外部 API

如果您能编写一个小型的 TypeScript 函数,就能编写 hook。托管和打包的 hooks 是受信任的本地代码。Workspace hooks 会被自动发现,但 OpenClaw 会保持禁用状态,直到您通过 CLI 或配置明确启用它们。

Hooks 系统允许您:

  • 当发出 /new 时,将会话上下文保存到内存
  • 记录所有命令以供审计
  • 在代理生命周期事件上触发自定义自动化
  • 扩展 OpenClaw 的行为而无需修改核心代码

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)

列出可用的挂钩:

Terminal window
openclaw hooks list

启用挂钩:

Terminal window
openclaw hooks enable session-memory

检查挂钩状态:

Terminal window
openclaw hooks check

获取详细信息:

Terminal window
openclaw hooks info session-memory

在 新手引导(openclaw onboard)期间,系统将提示您启用推荐的 hooks。向导会自动发现符合条件的 hooks 并将其列出供您选择。

Hooks 在 Gateway(网关) 进程内运行。请将捆绑 hooks、托管 hooks 和 hooks.internal.load.extraDirs 视为受信任的本地代码。<workspace>/hooks/ 下的工作区 hooks 是仓库本地代码,因此 OpenClaw 需要在加载之前执行明确的启用步骤。

Hooks 会按覆盖优先级从高到低的顺序,自动从以下目录中发现:

  1. Bundled hooks:随 OpenClaw 一起提供;位于 <openclaw>/dist/hooks/bundled/ 供 npm 安装使用(或编译二进制文件的同级 hooks/bundled/
  2. 插件钩子:捆绑在已安装插件内部的钩子(请参阅 插件钩子
  3. 托管钩子~/.openclaw/hooks/(用户安装的,在工作区之间共享;可以覆盖捆绑和插件钩子)。通过 hooks.internal.load.extraDirs 配置的额外钩子目录也被视为托管钩子,并共享相同的覆盖优先级。
  4. 工作区钩子: <workspace>/hooks/ (每个代理,默认禁用,直到明确启用;无法覆盖来自其他源的钩子)

Workspace hooks 可以为仓库添加新的 hook 名称,但它们无法覆盖具有相同名称的打包、托管或插件提供的 hooks。

托管的 hook 目录可以是 单个 hookhook pack(包目录)。

每个 hook 都是一个包含以下内容的目录:

my-hook/
├── HOOK.md # Metadata + documentation
└── handler.ts # Handler implementation

Hook packs 是标准的 npm 包,通过 openclaw.hookspackage.json 中导出一个或多个 hooks。请使用以下命令安装:

Terminal window
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.tshandler.jsindex.tsindex.js。 钩子包可以附带依赖项;它们将被安装在 ~/.openclaw/hooks/<id> 下。 每个 openclaw.hooks 条目在解析符号链接后必须保留在包目录内;超出该范围的条目将被拒绝。

安全提示:openclaw plugins install 使用 npm install --ignore-scripts 安装 hook-pack 依赖项 (不包含生命周期脚本)。请保持 hook pack 的依赖树为“纯 JS/TS”,并避免依赖 postinstall 构建的包。

HOOK.md 文件包含 YAML frontmatter 中的元数据以及 Markdown 文档:

---
name: my-hook
description: "Short description of what this hook does"
homepage: https://docs.openclaw.ai/automation/hooks#my-hook
metadata: { "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:文档 URL
  • os:必需平台(例如 ["darwin", "linux"]
  • requires:可选要求
    • bins:PATH 中必需的二进制文件(例如 ["git", "node"]
    • anyBins:至少必须存在这些二进制文件之一
    • env:必需的环境变量
    • config:必需的配置路径(例如 ["workspace.dir"]
  • always:绕过资格检查(布尔值)
  • install:安装方法(对于内置 hooks:[{"id":"bundled","kind":"bundled"}]

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:beforesession:compact:after

session:compact:before 上下文字段:

  • sessionId:内部会话 UUID
  • missingSessionKey:当没有可用的会话密钥时为 true
  • messageCount:压缩前的消息数量
  • tokenCount: 压缩前的 token 计数(可能不存在)
  • messageCountOriginal: 来自完整未截断会话历史记录的消息计数
  • tokenCountOriginal: 完整原始历史的 token 计数(可能不存在)

session:compact:after 上下文字段(除了 sessionIdmissingSessionKey):

  • messageCount:压缩后的消息计数
  • tokenCount:压缩后的 token 计数(可能不存在)
  • compactedCount:被压缩/移除的消息数量
  • summaryLength: 生成的压缩摘要的字符长度
  • tokensBefore: 压缩前的 token 计数(用于增量计算)
  • tokensAfter:压缩后的 token 计数
  • firstKeptEntryId:压缩后保留的第一条消息条目的 ID
  • agent:bootstrap: 在注入工作区引导文件之前(Hooks 可能会修改 context.bootstrapFiles

在网关启动时触发:

  • gateway:startup: 在通道启动并加载钩子之后

当会话属性被修改时触发:

  • session:patch: 当会话被更新时

会话事件包含有关会话及其更改的丰富上下文:

{
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

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:当出站消息成功发送时触发。

消息事件包含关于消息的丰富上下文:

// 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,
}
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;

这些钩子不是事件流监听器;它们允许插件在 OpenClaw 持久化工具结果之前同步调整它们。

  • tool_result_persist:在将工具结果写入会话记录之前转换它们。必须是同步的;返回更新后的工具结果负载或 undefined 以保持原样。请参阅 代理循环

在每个工具调用之前运行。插件可以修改参数、阻止调用或请求用户批准。

返回字段:

  • 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: truerequireApproval 字段,则工具调用将被立即阻止,而不会触发审批流程。这确保了高优先级插件的阻止不能被低优先级插件的审批请求覆盖。

如果网关不可用或不支持插件审批,工具调用将使用 description 作为阻止原因回退到软阻止。

在内置安装安全扫描之后、安装继续之前运行。OpenClaw 会针对交互式技能安装以及插件包、包和单文件安装触发此钩子。

默认行为因目标类型而异:

  • 除非操作员明确使用 openclaw plugins install --dangerously-force-unsafe-install,否则插件安装在内置扫描 critical 发现结果和扫描错误上默认失败。
  • 技能安装仍会将内置扫描发现结果和扫描错误显示为警告,并默认继续。

返回字段:

  • findings:作为警告显示的额外扫描发现结果
  • block:设置为 true 以阻止安装
  • blockReason:阻止时显示的人类可读原因

事件字段:

  • targetType:安装目标类别(skillplugin
  • targetName:安装目标的人类可读技能名称或插件 ID
  • sourcePath:正在扫描的安装目标内容的绝对路径
  • sourcePathKind:扫描的内容是 file 还是 directory
  • origin:可用的标准化安装来源(例如 openclaw-bundledopenclaw-workspaceplugin-bundleplugin-packageplugin-file
  • request:安装请求的出处,包括 kindmode 和可选的 requestedSpecifier
  • builtinScan:内置扫描器的结构化结果,包括 status、摘要计数、发现的问题以及可选的 error
  • skill:当 targetTypeskill 时的技能安装元数据,包括 installId 和所选的 installSpec
  • plugin:当 targetTypeplugin 时的插件安装元数据,包括规范的 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",
"requestedSpecifier": "@acme/[email protected]"
},
"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 } 被视为未做决策。

将此钩子用于需要在安装之前审计安装来源的外部安全扫描器、策略引擎或企业审批网关。

通过插件钩子运行器公开的压缩生命周期钩子:

  • before_compaction: 在压缩之前运行,包含计数/token 元数据
  • after_compaction: 在压缩之后运行,包含压缩摘要元数据

通过 Plugin SDK 注册的所有 27 个 hook。标记为 sequential 的 hook 按优先级顺序运行并可以修改结果;parallel hook 即发即弃。

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
Hook时间执行返回
agent_endAgent 运行完成后(成功或失败)并行void
before_reset/new/reset 清除会话时并行void
before_compaction压缩总结历史记录之前并行void
after_compaction压缩完成后并行void
Hook时间执行返回
session_start当新会话开始时并行void
session_end当会话结束时并行void
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? }
钩子时机执行返回
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_start网关进程完全启动之后并行void
gateway_stop当网关正在关闭时并行void
钩子时机执行返回
before_install内置安全扫描之后,安装继续之前顺序{ findings?, block?, blockReason? }

有关完整的处理程序签名和上下文类型,请参阅插件架构

以下事件类型已计划用于内部 hook 事件流。 请注意,session_startsession_end 已作为插件 Hook API hook 存在 但在 HOOK.md 元数据中尚不可用作内部 hook 事件键:

  • session:start:当新会话开始时(计划用于内部 hook 流;作为插件 hook session_start 可用)
  • session:end:当会话结束时(计划用于内部 hook 流;作为插件 hook session_end 可用)
  • agent:error:当代理遇到错误时
  • 工作区 hooks (<workspace>/hooks/):针对每个代理;可以添加新的 hook 名称,但不能覆盖同名的捆绑、托管或插件 hooks
  • 托管 hooks (~/.openclaw/hooks/):在工作区之间共享;可以覆盖捆绑和插件 hooks
Terminal window
mkdir -p ~/.openclaw/hooks/my-hook
cd ~/.openclaw/hooks/my-hook
---
name: my-hook
description: "Does something useful"
metadata: { "openclaw": { "emoji": "🎯", "events": ["command:new"] } }
---
# My Custom Hook
This hook does something useful when you issue `/new`.
const handler = async (event) => {
if (event.type !== "command" || event.action !== "new") {
return;
}
console.log("[my-hook] Running!");
// Your logic here
};
export default handler;
Terminal window
# Verify hook is discovered
openclaw hooks list
# Enable it
openclaw 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
{
"hooks": {
"internal": {
"enabled": true,
"entries": {
"session-memory": { "enabled": true },
"command-logger": { "enabled": false }
}
}
}
}

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"]
}
}
}
}

为了向后兼容,旧配置格式仍然有效:

{
"hooks": {
"internal": {
"enabled": true,
"handlers": [
{
"event": "command:new",
"module": "./hooks/handlers/my-handler.ts",
"export": "default"
}
]
}
}
}

注意:module 必须是相对于工作区的路径。绝对路径和遍历工作区外部的路径将被拒绝。

迁移:请对新的 hooks 使用基于发现的系统。旧的处理程序在基于目录的 hooks 之后加载。

Terminal window
# List all hooks
openclaw hooks list
# Show only eligible hooks
openclaw hooks list --eligible
# Verbose output (show missing requirements)
openclaw hooks list --verbose
# JSON output
openclaw hooks list --json
Terminal window
# Show detailed info about a hook
openclaw hooks info session-memory
# JSON output
openclaw hooks info session-memory --json
Terminal window
# Show eligibility summary
openclaw hooks check
# JSON output
openclaw hooks check --json
Terminal window
# Enable a hook
openclaw hooks enable session-memory
# Disable a hook
openclaw hooks disable command-logger

当您发出 /new/reset 时,将会话上下文保存到内存中。

事件command:newcommand:reset

要求:必须配置 workspace.dir

输出<workspace>/memory/YYYY-MM-DD-slug.md(默认为 ~/.openclaw/workspace

作用

  1. 使用重置前的会话条目来定位正确的转录记录
  2. 从对话中提取最后 15 条用户/助手消息(可配置)
  3. 使用 LLM 生成描述性的文件名别名
  4. 将会话元数据保存到带日期的内存文件中

输出示例

# 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.md
  • 2026-01-16-api-design.md
  • 2026-01-16-1430.md(如果别名生成失败则使用后备时间戳)

启用

Terminal window
openclaw hooks enable session-memory

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.mdSOUL.mdTOOLS.mdIDENTITY.mdUSER.mdHEARTBEAT.mdBOOTSTRAP.mdMEMORY.mdmemory.md)。
  • 对于子代理/定时会话,适用更严格的允许列表(AGENTS.mdTOOLS.mdSOUL.mdIDENTITY.mdUSER.md)。

启用

Terminal window
openclaw hooks enable bootstrap-extra-files

将所有命令事件记录到集中式审计文件中。

事件command

要求:无

输出~/.openclaw/logs/commands.log

作用

  1. 捕获事件详细信息(命令操作、时间戳、会话密钥、发送者 ID、来源)
  2. 以 JSONL 格式追加到日志文件
  3. 在后台静默运行

示例日志条目

{"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"}

查看日志

Terminal window
# View recent commands
tail -n 20 ~/.openclaw/logs/commands.log
# Pretty-print with jq
cat ~/.openclaw/logs/commands.log | jq .
# Filter by action
grep '"action":"new"' ~/.openclaw/logs/commands.log | jq .

启用

Terminal window
openclaw hooks enable command-logger

当 Gateway(网关) 启动时(通道启动之后)运行 BOOT.md。 必须启用内部 Hooks 才能运行此操作。

事件gateway:startup

要求: 必须配置 workspace.dir

作用

  1. 从工作区读取 BOOT.md
  2. 通过代理运行器运行指令
  3. 通过消息工具发送任何请求的出站消息

启用

Terminal window
openclaw hooks enable boot-md

Hooks 在命令处理期间运行。请保持轻量级:

// ✓ Good - async work, returns immediately
const handler: HookHandler = async (event) => {
void processInBackground(event); // Fire and forget
};
// ✗ Bad - blocks command processing
const handler: HookHandler = async (event) => {
await slowDatabaseQuery(event);
await evenSlowerAPICall(event);
};

始终包裹有风险的操作:

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
}
};

如果事件不相关,请尽早返回:

const handler: HookHandler = async (event) => {
// Only handle 'new' commands
if (event.type !== "command" || event.action !== "new") {
return;
}
// Your logic here
};

尽可能在元数据中指定确切事件:

metadata: { "openclaw": { "events": ["command:new"] } } # Specific

而不是:

metadata: { "openclaw": { "events": ["command"] } } # General - more overhead

Gateway(网关) 在启动时记录 Hook 加载情况:

Registered hook: session-memory -> command:new, command:reset
Registered hook: bootstrap-extra-files -> agent:bootstrap
Registered hook: command-logger -> command
Registered hook: boot-md -> gateway:startup

列出所有发现的 Hooks:

Terminal window
openclaw hooks list --verbose

在处理程序中,记录调用时间:

const handler: HookHandler = async (event) => {
console.log("[my-handler] Triggered:", event.type, event.action);
// Your logic
};

检查 Hook 不符合资格的原因:

Terminal window
openclaw hooks info my-hook

在输出中查找缺失的要求。

监控 Gateway(网关) 日志以查看 Hook 执行情况:

Terminal window
# macOS
./scripts/clawlog.sh -f
# Other platforms
tail -f ~/.openclaw/gateway.log

单独测试处理程序:

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(网关) 启动时加载 Hooks
  • src/auto-reply/reply/commands-core.ts: 触发命令事件
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 events
User sends /new
Command validation
Create hook event
Trigger hook (all registered handlers)
Command processing continues
Session reset
  1. 检查目录结构:

    Terminal window
    ls -la ~/.openclaw/hooks/my-hook/
    # Should show: HOOK.md, handler.ts
  2. 验证 HOOK.md 格式:

    Terminal window
    cat ~/.openclaw/hooks/my-hook/HOOK.md
    # Should have YAML frontmatter with name and metadata
  3. 列出所有发现的 Hooks:

    Terminal window
    openclaw hooks list

检查要求:

Terminal window
openclaw hooks info my-hook

查找缺失项:

  • 二进制文件(检查 PATH)
  • 环境变量
  • 配置值
  • 操作系统兼容性
  1. 验证 hook 是否已启用:

    Terminal window
    openclaw hooks list
    # Should show ✓ next to enabled hooks
  2. 重启您的网关进程以便重新加载 hooks。

  3. 检查网关日志中的错误:

    Terminal window
    ./scripts/clawlog.sh | grep hook

检查 TypeScript/导入错误:

Terminal window
# Test import directly
node -e "import('./path/to/handler.ts').then(console.log)"

之前

{
"hooks": {
"internal": {
"enabled": true,
"handlers": [
{
"event": "command:new",
"module": "./hooks/handlers/my-handler.ts"
}
]
}
}
}

之后

  1. 创建 hook 目录:

    Terminal window
    mkdir -p ~/.openclaw/hooks/my-hook
    mv ./hooks/handlers/my-handler.ts ~/.openclaw/hooks/my-hook/handler.ts
  2. 创建 HOOK.md:

    ---
    name: my-hook
    description: "My custom hook"
    metadata: { "openclaw": { "emoji": "🎯", "events": ["command:new"] } }
    ---
    # My Hook
    Does something useful.
  3. 更新配置:

    {
    "hooks": {
    "internal": {
    "enabled": true,
    "entries": {
    "my-hook": { "enabled": true }
    }
    }
    }
    }
  4. 验证并重启您的网关进程:

    Terminal window
    openclaw hooks list
    # Should show: 🎯 my-hook ✓

迁移的好处

  • 自动设备发现
  • CLI 管理
  • 资格检查
  • 更好的文档
  • 一致的结构