OpenClaw 钩子(Hooks)CLI 完全指南:事件驱动自动化与 `openclaw hooks` 命令实战

原创2026-10-03 16:26:351,721 阅读
文章标签:人工智能AI Agent即时通讯后端本地部署语音

OpenClaw 钩子(Hooks)CLI 完全指南:事件驱动自动化与 openclaw hooks 命令实战

本文围绕 OpenClaw(中文社区版 openclaw-cn)的钩子(Hooks)体系展开,系统讲解 openclaw hooks 系列命令的用法、钩子的发现机制、HOOK.md 元数据格式、事件类型、配置方式以及内置钩子的实战细节。读完本文,你将掌握如何用钩子实现会话记忆快照、命令审计、网关启动自动化等事件驱动能力,并能独立编写、安装、调试属于自己的自定义钩子。

什么是 Hooks:网关内的事件驱动自动化

Hooks 是一类运行在 OpenClaw 网关(Gateway)进程内部的轻量脚本,它们在代理事件(如 /new、/reset、/stop)或生命周期事件(如网关启动)触发时被同步调用。与外部 HTTP 回调的 Webhooks 不同(见 Webhook Hooks),本页所述的 Hooks 直接嵌入命令处理流程,与 skills 的管理方式类似——自动发现、CLI 管理、可开关。

常见用途包括:

  • 执行 /new 重置会话时,自动把会话上下文保存为记忆快照;
  • 记录所有命令事件,形成集中审计日志,便于排查问题或满足合规要求;
  • 会话开始/结束时触发后续自动化动作;
  • 事件触发时向代理工作区写入文件或调用外部 API。

只要你"会写一个小的 TypeScript 函数",就能写一个 hook。钩子由系统自动发现,通过 CLI 启用或禁用。

从源码结构看,钩子系统的核心实现位于 src/hooks/:internal-hooks.ts 维护事件注册表与触发逻辑,workspace.ts 负责目录扫描,frontmatter.ts 负责解析 HOOK.md 元数据,config.ts 负责资格(eligibility)判定,loader.ts 负责动态加载处理器模块,hooks-cli.ts 实现全部 CLI 命令。

CLI 命令速查

所有命令均以 openclaw hooks 为前缀,管理钩子的发现、状态、开关与安装:

命令 作用
openclaw hooks list 列出工作区、托管、内置目录中发现的所有钩子
openclaw hooks info <name> 查看单个钩子的详细信息
openclaw hooks check 汇总钩子资格状态(就绪/未就绪数量)
openclaw hooks enable <name> 启用钩子(写入配置)
openclaw hooks disable <name> 禁用钩子(更新配置)
openclaw hooks install <path-or-spec> 从本地目录/压缩包或 npm 安装钩子包
openclaw hooks update <id> / --all 更新已安装的 npm 钩子包

列出所有钩子:openclaw hooks list

openclaw hooks list

列出从 workspace、managed、bundled 三个目录发现的所有钩子。

选项:

  • --eligible:只显示满足资格条件的钩子(requirements 已满足);
  • --json:以 JSON 结构输出,便于程序化处理;
  • -v, --verbose:显示详细信息,包括未满足的 requirements。

示例输出:

Hooks (3/3 ready)

Ready:
  🚀 boot-md ✓ - Run BOOT.md on gateway startup
  📝 command-logger ✓ - Log all command events to a centralized audit file
  💾 session-memory ✓ - Save session context to memory when /new command is issued

verbose 模式:

openclaw hooks list --verbose

会进一步展示未就绪钩子缺失的 requirements(例如缺少某个 PATH 上的二进制、环境变量或配置项)。

JSON 模式:

openclaw hooks list --json

返回结构化 JSON,适合脚本集成。

查看钩子详情:openclaw hooks info <name>

openclaw hooks info <name>

参数:

  • <name>:钩子名,如 session-memory。

选项:

  • --json:JSON 输出。

示例:

openclaw hooks info session-memory

输出:

💾 session-memory ✓ Ready

Save session context to memory when /new command is issued

Details:
  Source: openclaw-bundled
  Path: /path/to/openclaw/hooks/bundled/session-memory/HOOK.md
  Handler: /path/to/openclaw/hooks/bundled/session-memory/handler.ts
  Homepage: https://docs.openclaw.ai/hooks#session-memory
  Events: command:new

Requirements:
  Config: ✓ workspace.dir

输出中的 Source 字段标识钩子来源(openclaw-bundled、openclaw-managed、openclaw-workspace、openclaw-plugin),Events 列出监听的事件键,Requirements 展示资格判定结果。

检查资格:openclaw hooks check

openclaw hooks check

汇总显示有多少钩子就绪、多少未就绪。

选项:

  • --json:JSON 输出。

示例输出:

Hooks Status

Total hooks: 4
Ready: 4
Not ready: 0

启用/禁用钩子

openclaw hooks enable <name>
openclaw hooks disable <name>

enable 的具体行为(从 CLI 实现看):

  1. 检查钩子是否存在且满足资格条件;
  2. 在配置中写入 hooks.internal.entries.<name>.enabled = true(配置文件位于 ~/.openclaw/config.json);
  3. 将配置保存到磁盘。

示例输出:

✓ Enabled hook: 💾 session-memory
⏸ Disabled hook: 📝 command-logger

重要限制:由插件托管的钩子在 openclaw hooks list 中显示为 plugin:<id>,不能在此处单独启用/禁用,应整体启用或禁用对应插件(详见 Plugins)。

启用/禁用后必须重启网关:macOS 上重启菜单栏应用,开发环境重启你的网关进程,钩子才会重新加载。

钩子的发现机制:三个目录与优先级

Hooks 从三个目录自动发现,优先级从高到低:

  1. Workspace hooks:<workspace>/hooks/(按代理隔离,优先级最高);
  2. Managed hooks:~/.openclaw/hooks/(用户安装,跨工作区共享);
  3. Bundled hooks:<openclaw>/dist/hooks/bundled/(随 OpenClaw 内置发布)。

Managed 目录下的每个子目录既可以是一个单个钩子,也可以是一个钩子包(hook pack)。在 src/hooks/workspace.ts 中实现了上述目录的扫描与加载逻辑;src/hooks/bundled/ 目录则展示了内置钩子的真实结构:

session-memory/
├── HOOK.md          # 元数据 + 文档
└── handler.ts       # 处理器实现

HOOK.md 元数据格式

每个钩子目录都包含一个 HOOK.md,由 YAML frontmatter 元数据与 Markdown 文档组成:

---
name: my-hook
description: "Short description of what this hook does"
homepage: https://docs.openclaw.ai/hooks#my-hook
metadata:
  { "openclaw": { "emoji": "🔗", "events": ["command:new"], "requires": { "bins": ["node"] } } }
---

# My Hook

Detailed documentation goes here...

frontmatter 解析在 src/hooks/frontmatter.ts 中实现,它基于 parseFrontmatterBlock 读取 YAML,并用 normalizeStringList 将 events 等字段统一归一化为字符串数组(兼容数组写法与逗号分隔字符串),metadata.openclaw 对象支持的字段如下:

  • emoji:CLI 中展示的图标(如 "💾");
  • events:要监听的事件数组(如 ["command:new", "command:reset"]);
  • export:要使用的命名导出(默认 "default");
  • homepage:文档 URL;
  • requires:可选资格要求:
    • bins:PATH 上必须存在的二进制(如 ["git", "node"]);
    • anyBins:这些二进制中至少有一个存在即可;
    • env:必须存在的环境变量;
    • config:必须为真的配置路径(如 ["workspace.dir"]);
    • os:要求的平台(如 ["darwin", "linux"]);
  • always:布尔值,为 true 时跳过所有资格检查;
  • install:安装方式说明(内置钩子为 [{"id":"bundled","kind":"bundled"}])。

requires 的判定逻辑集中在 src/hooks/config.ts 的 shouldIncludeHook() 中:它会依次检查显式禁用标记、OS 平台匹配、always 旁路、bins(全部必须存在,通过 hasBinary() 遍历 PATH 并用 fs.accessSync 验证可执行位)、anyBins(任一存在)、env 环境变量以及 config 配置路径(resolveConfigPath 按点号路径逐层取值,且 workspace.dir、browser.enabled 等在缺省时默认视为真)。也就是说,一个钩子只有在满足全部要求时才显示为"Ready"。

处理器实现与事件上下文

handler.ts 默认导出一个 HookHandler 函数:

import type { HookHandler } from "../../src/hooks/hooks.js";

const myHandler: HookHandler = async (event) => {
  // 只处理 'new' 命令
  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()}`);

  // 你的自定义逻辑

  // 可选:向用户发送消息
  event.messages.push("✨ My hook executed!");
};

export default myHandler;

每个事件携带以下上下文:

{
  type: 'command' | 'session' | 'agent' | 'gateway',
  action: string,              // 如 'new'、'reset'、'stop'
  sessionKey: string,          // 会话标识
  timestamp: Date,             // 事件发生时间
  messages: string[],          // 向这里 push 消息即可发送给用户
  context: {
    sessionEntry?: SessionEntry,
    sessionId?: string,
    sessionFile?: string,
    commandSource?: string,    // 如 'whatsapp'、'telegram'
    senderId?: string,
    workspaceDir?: string,
    bootstrapFiles?: WorkspaceBootstrapFile[],
    cfg?: OpenClawConfig
  }
}

事件触发原理(源码级)

src/hooks/internal-hooks.ts 中,事件注册表是一个 Map<string, InternalHookHandler[]>。triggerInternalHook(event) 触发一个事件时,会依次执行通用类型处理器(handlers.get(event.type))与特定 action 处理器(handlers.get(\event.type:{event.type}:{event.action}`)),两者按注册顺序串联调用;每个处理器的异常都会被捕获并打印 Hook error [type:action]: ...`,不会阻断其他处理器继续执行,也不会中断命令处理主流程。

事件触发入口位于命令处理链路中:从源码结构看,命令事件由 src/auto-reply/reply/commands-core.ts 触发,网关启动时由 src/gateway/server-startup.ts 加载并注册钩子,这与文档所述的"钩子在网关启动时加载、命令处理时触发"一致。

事件类型

命令事件(Command Events)

代理命令发出时触发:

  • command:所有命令事件的通用监听;
  • command:new:发出 /new 时;
  • command:reset:发出 /reset 时;
  • command:stop:发出 /stop 时。

代理事件(Agent Events)

  • agent:bootstrap:工作区 bootstrap 文件注入前触发(钩子可以改写 context.bootstrapFiles,从而实现动态注入初始文件)。

网关事件(Gateway Events)

  • gateway:startup:通道启动且钩子加载完成后触发,boot-md 钩子正是监听此事件。

工具结果钩子(Tool Result Hooks,插件 API)

tool_result_persist 不是事件流监听器,而是让插件在 OpenClaw 持久化工具结果之前同步调整工具结果;必须同步执行,返回更新后的工具结果载荷或 undefined 保持原样。参见 Agent Loop。

规划中的事件

文档列出了计划中的事件类型:session:start、session:end、agent:error、message:sent、message:received。注意这些尚在规划阶段,不应在生产钩子中依赖。

配置方式

新配置格式(推荐)

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

单钩子自定义配置

钩子可携带自定义环境变量:

{
  "hooks": {
    "internal": {
      "enabled": true,
      "entries": {
        "my-hook": {
          "enabled": true,
          "env": {
            "MY_CUSTOM_VAR": "value"
          }
        }
      }
    }
  }
}

配置中的 env 在资格判定时也会被计入(见 src/hooks/config.ts 中对 hookConfig?.env?.[envName] 的检查),即通过配置提供的环境变量可以满足 requires.env 要求。

额外加载目录

{
  "hooks": {
    "internal": {
      "enabled": true,
      "load": {
        "extraDirs": ["/path/to/more/hooks"]
      }
    }
  }
}

openclaw hooks install -l 链接本地目录时,也正是写入该 hooks.internal.load.extraDirs。

旧版配置格式(仍受支持)

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

注意:module 必须是工作区相对路径,绝对路径与越出工作区的路径会被拒绝。旧版 handlers 在目录式钩子之后加载;新钩子请使用发现式系统。

安装与更新钩子

安装:openclaw hooks install <path-or-spec>

从本地文件夹/压缩包或 npm 安装钩子包,具体行为:

  1. 将钩子包复制到 ~/.openclaw/hooks/<id>;
  2. 在 hooks.internal.entries.* 中启用已安装的钩子;
  3. 在 hooks.internal.installs 中记录安装来源。

选项:

  • -l, --link:链接本地目录而非复制(写入 hooks.internal.load.extraDirs)。

支持的压缩包格式:.zip、.tgz、.tar.gz、.tar。

示例:

# 本地目录
openclaw hooks install ./my-hook-pack

# 本地压缩包
openclaw hooks install ./my-hook-pack.zip

# NPM 包
openclaw hooks install @openclaw/my-hook-pack

# 链接本地目录(不复制)
openclaw hooks install -l ./my-hook-pack

Hook Packs(npm/压缩包)

Hook packs 是标准的 npm 包,通过 package.json 中的 openclaw.hooks 字段导出一个或多个钩子:

{
  "name": "@acme/my-hooks",
  "version": "0.1.0",
  "openclaw": {
    "hooks": ["./hooks/my-hook", "./hooks/other-hook"]
  }
}

每个条目指向一个包含 HOOK.md 与 handler.ts(或 index.ts)的钩子目录。Hook packs 可自带依赖,依赖会随包一起安装到 ~/.openclaw/hooks/<id> 下。

更新:openclaw hooks update

openclaw hooks update <id>
openclaw hooks update --all

仅支持 npm 安装的钩子包更新。

选项:

  • --all:更新所有被跟踪的钩子包;
  • --dry-run:只显示将要发生的变更,不实际写入。

内置钩子(Bundled Hooks)详解

OpenClaw 自带三个自动发现的内置钩子,位于仓库 src/hooks/bundled/:

💾 session-memory

在发出 /new 时将会话上下文保存到记忆文件。监听 command:new 事件,要求配置了 workspace.dir。

工作流程:

  1. 利用重置前的 session entry 定位正确的 transcript;
  2. 提取对话最后 15 行;
  3. 用 LLM 根据对话内容生成有意义的文件名 slug(见 src/hooks/llm-slug-generator.ts);
  4. 将会话元数据保存到带日期的记忆文件。

输出位置:<workspace>/memory/YYYY-MM-DD-slug.md(默认 ~/.openclaw/workspace)。

示例输出:

# Session: 2026-01-16 14:30:00 UTC

- **Session Key**: agent:main:main
- **Session ID**: abc123def456
- **Source**: telegram

文件名示例:

  • 2026-01-16-vendor-pitch.md
  • 2026-01-16-api-design.md
  • 2026-01-16-1430.md(slug 生成失败时的回退时间戳)

启用:

openclaw hooks enable session-memory

该钩子的真实 HOOK.md 可在 src/hooks/bundled/session-memory/HOOK.md 中查看,其中明确记录了 requires.config = <a href="https://link.gitcode.com/i/cc7238d0e1ed304b0608381cdc284c5f" target="_blank">"workspace.dir"] 以及 slug 生成失败时的回退逻辑。完整文档见 [automation/hooks 的 session-memory 一节。

📝 command-logger

将所有命令事件记录到集中审计文件。监听 command 事件(所有命令),无额外要求。

工作流程:

  1. 捕获事件细节(命令动作、时间戳、会话键、发送者 ID、来源);
  2. 以 JSONL 格式追加写入日志文件;
  3. 后台静默运行。

输出位置:~/.openclaw/logs/commands.log。

示例日志条目:

{"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":"user@example.com","source":"whatsapp"}

查看日志:

# 最近命令
tail -n 20 ~/.openclaw/logs/commands.log

# jq 美化输出
cat ~/.openclaw/logs/commands.log | jq .

# 按动作过滤
grep '"action":"new"' ~/.openclaw/logs/commands.log | jq .

启用:

openclaw hooks enable command-logger

🚀 boot-md

网关启动(通道启动之后)时运行 BOOT.md。监听 gateway:startup 事件,要求配置 workspace.dir,且需开启 internal hooks。

工作流程:

  1. 从工作区读取 BOOT.md;
  2. 通过 agent runner 执行其中的指令;
  3. 通过消息工具发送任何请求的出站消息。

启用:

openclaw hooks enable boot-md

创建自定义钩子:从零到实战

1. 选择位置

  • Workspace hooks(<workspace>/hooks/):按代理隔离,优先级最高;
  • Managed hooks(~/.openclaw/hooks/):跨工作区共享。

2. 创建目录结构

mkdir -p ~/.openclaw/hooks/my-hook
cd ~/.openclaw/hooks/my-hook

3. 创建 HOOK.md

---
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`.

4. 创建 handler.ts

import type { HookHandler } from "../../src/hooks/hooks.js";

const handler: HookHandler = async (event) => {
  if (event.type !== "command" || event.action !== "new") {
    return;
  }

  console.log("[my-hook] Running!");
  // Your logic here
};

export default handler;

5. 启用并测试

# 验证钩子已被发现
openclaw hooks list

# 启用它
openclaw hooks enable my-hook

# 重启网关进程(macOS 重启菜单栏应用,或重启开发进程)

# 触发事件:通过你的消息通道发送 /new

6. 随网关开机自启(onboarding)

在执行 openclaw onboard 时,向导会自动发现符合条件的钩子并提示你选择启用推荐钩子,无需手动编辑配置。

最佳实践

保持处理器轻量

钩子运行在命令处理流程中,应保持轻量:

// ✓ 好:异步工作,立即返回
const handler: HookHandler = async (event) => {
  void processInBackground(event); // Fire and forget
};

// ✗ 坏:阻塞命令处理
const handler: HookHandler = async (event) => {
  await slowDatabaseQuery(event);
  await evenSlowerAPICall(event);
};

优雅处理错误

始终用 try/catch 包裹风险操作,且不要抛出(源码 triggerInternalHook 虽会捕获并记录异常,但最佳实践是让其他处理器继续运行):

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

尽早过滤事件

与事件无关时立即 return:

const handler: HookHandler = async (event) => {
  // 只处理 'new' 命令
  if (event.type !== "command" || event.action !== "new") {
    return;
  }
  // Your logic here
};

使用具体事件键

元数据中尽量指定精确事件:

metadata: { "openclaw": { "events": ["command:new"] } } # 具体,开销小

而不是:

metadata: { "openclaw": { "events": ["command"] } } # 通用,开销更大

调试与测试

查看网关启动日志

网关启动时会打印钩子加载信息:

Registered hook: session-memory -> command:new
Registered hook: command-logger -> command
Registered hook: boot-md -> gateway:startup

检查发现与注册

openclaw hooks list --verbose

在 handler 中打印触发信息:

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

验证资格

openclaw hooks info my-hook

输出中查找缺失的 requirements(二进制、环境变量、配置值、OS 兼容性)。

监控执行日志

# macOS
./scripts/clawlog.sh -f

# 其他平台
tail -f ~/.openclaw/gateway.log

单元测试(vitest)

可用 createHookEvent 构造事件对象,在隔离环境中直接测试处理器:

import { test } from "vitest";
import { createHookEvent } from "./src/hooks/hooks.js";
import myHandler from "./hooks/my-hook/handler.js";

test("my handler works", async () => {
  const event = createHookEvent("command", "new", "test-session", {
    foo: "bar",
  });

  await myHandler(event);

  // Assert side effects
});

注意:createHookEvent 的源码实现(src/hooks/internal-hooks.ts 中的 createInternalHookEvent)会自动填充 timestamp: new Date() 与空 messages 数组,因此测试中无需手动构造这两个字段。

常见故障排查

钩子未被发现

  1. 检查目录结构:
    ls -la ~/.openclaw/hooks/my-hook/
    # 应显示:HOOK.md, handler.ts
    
  2. 检查 HOOK.md 格式:
    cat ~/.openclaw/hooks/my-hook/HOOK.md
    # 应有包含 name 与 metadata 的 YAML frontmatter
    
  3. 列出所有已发现钩子:
    openclaw hooks list
    

钩子不满足资格

openclaw hooks info my-hook

对照检查缺失项:二进制(PATH)、环境变量、配置值、OS 兼容性。

钩子不执行

  1. 确认已启用:openclaw hooks list 中启用钩子旁应显示 ✓;
  2. 重启网关进程使钩子重新加载;
  3. 检查网关日志:
    ./scripts/clawlog.sh | grep hook
    

处理器报错

检查 TypeScript/import 错误:

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. 创建钩子目录并移动 handler:
    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. 验证并重启网关:
    openclaw hooks list
    # 应显示:🎯 my-hook ✓
    

迁移收益:自动发现、CLI 管理、资格检查、更好的文档化与统一结构。

架构速览

核心组件(源码路径)

发现流程

Gateway startup
    ↓
Scan directories (workspace → managed → bundled)
    ↓
Parse HOOK.md files
    ↓
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

参考链接

登录后查看全文
openclaw-cn