Skip to content
今天更新

Hooks 钩子

概述

Hooks 允许你在消息处理的不同阶段插入自定义逻辑,实现消息过滤、内容转换、日志记录等功能。它是 OpenClaw 自动化体系中最灵活的扩展点。

Hook 类型

Hook触发时机典型用途
pre-process消息到达 Agent 前消息过滤、内容清洗、敏感词拦截
post-processAgent 响应后格式转换、日志记录、通知转发
on-error出错时触发错误告警、自动重试、降级处理

配置方式

openclaw.json 中配置 Hooks:

json
{
  "hooks": {
    "pre-process": [
      {
        "name": "敏感词过滤",
        "script": "scripts/filter.js",
        "enabled": true
      }
    ],
    "post-process": [
      {
        "name": "日志记录",
        "script": "scripts/logger.js",
        "enabled": true
      }
    ],
    "on-error": [
      {
        "name": "错误通知",
        "script": "scripts/error-notify.js",
        "enabled": true
      }
    ]
  }
}

使用场景

消息过滤

在消息到达 Agent 前进行预处理:

  • 过滤垃圾信息和广告
  • 拦截包含敏感信息的消息
  • 将语音消息自动转为文字

响应增强

在 Agent 响应后进行后处理:

  • 自动翻译为指定语言
  • 添加格式化(如 Markdown 渲染)
  • 将重要响应同步到其他渠道

安全审计

记录和监控所有交互:

  • 记录所有工具调用到审计日志
  • 检测异常行为模式
  • 在执行危险操作前发送确认请求

二次确认

对危险操作要求用户确认:

json
{
  "security": {
    "requireConfirmation": ["delete", "rm", "restart", "shutdown"]
  }
}

当指令包含这些关键词时,OpenClaw 会先询问「确定要执行吗?回复 yes 确认」。

内置 Hooks

OpenClaw 预置了 4 个开箱即用的 Hook,在安装时可选择启用。初次使用建议先跳过(Skip for now),后续按需在设置菜单中逐个开启。

Hook触发时机功能适用场景
boot-md网关启动时执行 BOOT.md 文件中的预设规则需要固定启动流程、强制遵循规则
bootstrap-extra-filesAgent 引导阶段将额外工作区规则文件注入上下文拥有完整的 Bootstrap 文件体系
command-logger每次命令执行时将所有命令事件记录到审计文件需要追踪操作历史、安全审计
session-memory执行 /new 命令时自动保存当前会话上下文希望在新建会话前保留记忆

Bootstrap 文件体系

bootstrap-extra-files 支持注入以下工作区文件:

  • AGENTS.md — Agent 角色定义
  • SOUL.md — Agent 人格与风格
  • TOOLS.md — 可用工具清单
  • IDENTITY.md — 身份标识
  • USER.md — 用户偏好
  • HEARTBEAT.md — 心跳规则
  • BOOTSTRAP.md — 引导入口

按需启用

  • 固定启动规则 → 启用 boot-md
  • 操作审计需求 → 启用 command-logger
  • 会话记忆保留 → 启用 session-memory
  • 不确定 → 先跳过,不影响核心功能,后续随时可在设置中开启

Hook 开发教程

Hook 是 Agent 的自动化 SOP。特定事件发生时,代码直接跑,不经过 LLM 思考。和 Skill 完全不同:

维度SkillHook
本质知识注入(SKILL.md)代码执行(handler.ts)
执行LLM 读后自己决定怎么用事件触发直接跑,不问 LLM
可靠性软——LLM 可能忽略硬——代码一定会执行
类比给员工操作手册,他可能看可能不看膝跳反射,锤子敲上去腿一定弹

Skill 影响 Agent 的想法,Hook 控制系统的行为。一个是建议,一个是铁律。

Hook 的结构

一个文件夹,两个文件:

text
my-hook/
├── HOOK.md         # 元数据:名字、监听什么事件
└── handler.ts      # 代码:事件来了干什么

<工作区>/hooks/ 是项目专用,放 ~/.openclaw/hooks/ 是全局生效。

第一步:写 HOOK.md

HOOK.md 用 YAML front matter 声明元数据:

yaml
---
name: my-first-hook            # 唯一标识,CLI 管理和配置引用用这个
description: 这个 Hook 干什么的  # 简短描述
metadata:
  openclaw:
    emoji: "G"                 # CLI 列表里显示的图标,可选
    events:                    # 监听哪些事件,数组格式
      - "session:compact:before"
      - "command:new"
    requires:
      bins:                    # 运行环境依赖
        - "node"               # handler.ts 需要 node
---

# 这里写文档(可选)
这个 Hook 的用途说明,给人看的。

几个要注意的点:

  • name 用 kebab-case(小写+连字符),是标识符
  • events 是数组,一个 Hook 可以监听多个事件
  • requires.bins 声明运行依赖,handler.ts 跑在 node 上所以要写 node
  • emojidescription 都是可选的,不写也能跑

第二步:写 handler.ts

handler 接收 event 对象,三个关键属性:

  • event.type — 事件大类(command、session、message)
  • event.action — 具体动作(new、compact:before、received)
  • event.messages.push() — 往 Agent 的 context 推消息,这是 Hook 和 Agent 沟通的唯一桥梁

判断当前事件用 type + action 组合。比如 command:new 这个事件:event.type === "command", event.action === "new"

你不需要会 TypeScript。给 Agent 这个 prompt 就行:

text
写一个 OpenClaw Hook 的 handler.ts
触发事件:[事件名]
触发时做:[自然语言描述动作]
要求:export default handler,用 event.type/action 判断事件,
用 event.messages.push() 推送消息

关键语法点:

  • 必须 export default handler,不是 module.exports,不是 export { handler }
  • handler 是 async 函数,异步操作要 await
  • 一个 handler 可以处理多个事件
  • event.messages.push() 是 Hook 和 Agent 沟通的唯一桥梁

第三步:放目录 + 验证

bash
mkdir -p <工作>/hooks/my-first-hook/
openclaw hooks list            # 确认系统发现了它
openclaw hooks info my-first-hook  # 看配置对不对

handler.ts 模板

模板 A:最简——只推一条消息

typescript
const handler = async (event) => {
  if (event.type !== "command" || event.action !== "new") return;
  event.messages.push("新 session 已启动,检查 temp/ 下有没有未完成的计划文件。");
};
export default handler;

模板 B:中等——读写文件

typescript
import { readFileSync, existsSync } from "fs";
import { join } from "path";

const handler = async (event) => {
  if (event.type !== "command" || event.action !== "new") return;
  const today = new Date().toISOString().split("T")[0];
  const path = join(process.cwd(), "memory", `${today}.md`);

  if (existsSync(path)) {
    const content = readFileSync(path, "utf-8");
    event.messages.push(`工作日志已加载: \n${content}`);
  } else {
    event.messages.push("今天还没有工作日志,开始新的一天");
  }
};
export default handler;

模板 C:进阶——条件判断 + 多事件处理

typescript
import { readFileSync, writeFileSync, existsSync, readdirSync } from "fs";
import { join } from "path";

const handler = async (event) => {
  const tempDir = join(process.cwd(), "temp");

  // 处理压缩前事件
  if (event.type === "session" && event.action === "compact:before") {
    if (!existsSync(tempDir)) return;
    const plans = readdirSync(tempDir).filter(f => f.endsWith("-plan.md"));
    if (plans.length === 0) return;
    const snapshot = {
      timestamp: new Date().toISOString(),
      plans: plans.map(f => ({
        name: f, content: readFileSync(join(tempDir, f), "utf-8")
      }))
    };
    writeFileSync(join(tempDir, "compact-snapshot.json"),
      JSON.stringify(snapshot, null, 2));
    event.messages.push(`快照已保存,含 ${plans.length} 个计划文件`);
  }

  // 处理压缩后事件
  if (event.type === "session" && event.action === "compact:after") {
    const path = join(tempDir, "compact-snapshot.json");
    if (!existsSync(path)) return;
    const snapshot = JSON.parse(readFileSync(path, "utf-8"));
    const list = snapshot.plans.map(p => `- ${p.name}`).join("\n");
    event.messages.push(
      `context 刚被压缩。压缩前的任务: \n${list}\n` +
      `请读取 temp/ 下的计划文件恢复上下文`);
  }
};
export default handler;

事件速览

11 种事件分 4 类。不用全背,知道有哪些,用到回来查。

命令类(用户操作触发)

事件触发时机适合做什么
command:new用户执行 /new 开新 session保存上轮会话关键信息、初始化新 session 上下文
command:reset用户执行 /reset 重置清理临时文件、保存当前状态快照
command:stop用户执行 /stop 停止收尾工作、保存进度到持久化文件

会话类(系统自动触发)

事件触发时机适合做什么
session:compact:beforecontext 压缩之前赶紧把当前任务状态、关键上下文保存到文件
session:compact:aftercontext 压缩之后把刚保存的关键信息重新注入到压缩后的新 context

这一对是金矿。context 压缩会让 Agent 变傻——跑着跑着突然忘了自己在干什么。Hook 可以把这个手动操作变成自动的。

引导类(启动时触发)

事件触发时机适合做什么
agent:bootstrap工作区引导前注入额外引导文件、设置初始状态
gateway:startup网关启动后执行启动脚本、加载外部配置

消息类(消息流转触发)

事件触发时机适合做什么
message:received接收到消息消息预处理、格式转换
message:transcribed音频转录完成转录文本的后处理、清洗
message:preprocessed媒体处理完成处理完图片/文件后的后续操作
message:sent消息发送发送日志、消息存档

代理回复类(2026.4.2+)

事件触发时机适合做什么
before_agent_reply在调用 LLM 之前,可以直接短路规则命中时合成回复、命中缓存直接返回、只读命令快速应答

before_agent_reply2026.4.2 新增的 Hook,允许插件在不触发 LLM 的情况下直接返回合成的 Agent 回复。典型用途:

  • 机器人受到触发词如 /help 时直接推送帮助文本
  • 命中 FAQ / RAG 缓存的问题直接复用历史答案,省掉一次 LLM 调用
  • 合规审核未通过时输出固定的拒绝话术

与 pre-process 的区别

pre-process 是在消息进入 Agent 之前修改上下文;before_agent_reply 是在 Agent 准备调用 LLM 时短路掉整个调用链,直接给用户回复。前者改输入,后者改输出。

3 个最值得优先关注的事件

  1. session:compact:before/after — 解决 Agent 变傻的痛点
  2. command:new — 每次开新 session 时自动保存和恢复上下文
  3. agent:bootstrap — 动态注入引导信息,比写死更灵活

Hook 发现与管理

三级目录扫描

OpenClaw 启动时自动扫描三个目录找 Hook,优先级递减:

优先级位置说明
最高<workspace>/hooks/工作区 Hook,当前项目专用
中等~/.openclaw/hooks/托管 Hook,全局可用
最低随 OpenClaw 安装自带捆绑 Hook,官方内置

优先级的意思是:工作区和托管目录有同名 Hook 时,工作区的优先。

CLI 命令参考

bash
openclaw hooks list                    # 列出所有已发现的 Hook
openclaw hooks list --eligible         # 只显示符合运行条件的(依赖满足的)
openclaw hooks info <hook-name>        # 查看某个 Hook 的详细信息
openclaw hooks check                   # 检查所有 Hook 的合格性
openclaw hooks enable <hook-name>      # 启用某个 Hook
openclaw hooks disable <hook-name>     # 禁用某个 Hook

最常用的是 listinfo——写完 Hook 放到目录后,先 list 确认系统发现了,再 info 看配置对不对。

list --eligible 排查问题时好用:Hook 出现在 list 但没出现在 list --eligible,说明依赖没满足。

配置文件启用/禁用

openclaw.json 里可以精细控制每个 Hook 的开关:

json
{
  "hooks": {
    "internal": {
      "enabled": true,
      "entries": {
        "session-memory": { "enabled": true },
        "command-logger": { "enabled": false },
        "compact-guardian": { "enabled": true }
      }
    }
  }
}

顶层 enabled 控制整个 Hook 系统的总开关。entries 里按 Hook 名称单独控制每一个的启用/禁用。

Hook 常见坑

坑 1:事件名拼错

session:compact:before 写成 session:compactBefore——拼错了没报错,默默不触发。解决:复制粘贴事件名,不要手打。

坑 2:目录放错

放在工作区根目录而不是 hooks/ 子目录下。正确路径是 <workspace>/hooks/<hook-name>/。解决:openclaw hooks list 确认系统发现了你的 Hook。

坑 3:导出写法错

必须 export default handler。不是 module.exports,不是 export { handler }

坑 4:node 没装或没声明

HOOK.md 里要写 requires.bins: ["node"]。没装 node 的话 Hook 静默失败,不报错。

坑 5:异步忘了 await

handler 是 async 函数。忘了 await 异步操作,代码可能没执行完就返回了。

下一步

觉得有帮助?