Skip to content

OpenClaw 源码调试指南

版本:2026.3.2 | 编写日期:2026-03-25 目标:通过调试手段深入理解 OpenClaw 每个核心模块的运行原理


一、环境准备

1.1 前置要求

工具版本要求安装方式
Node.js22+brew install nodenvm install 22
pnpm最新版npm install -g pnpm
Bun最新版brew install oven-sh/bun/bun
VS Code最新版推荐 IDE
Vitest 插件VS Code 扩展搜索 vitest.explorer 安装

![开发环境工具栈:Node.js、pnpm、Bun、VS Code、Vitest 插件](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码调试指南/01-infographic-dev-environment-1775150827176.png)

1.2 初始化项目

bash
cd /Users/bytedance/Desktop/ai-study/openclaw-2026.3.2

# 安装所有依赖
pnpm install

# 验证安装成功
pnpm vitest --version

# 构建项目(某些模块的测试需要构建产物)
pnpm build

1.3 验证测试能通过

bash
# 快速运行一个简单测试验证环境
pnpm vitest run src/infra/retry.test.ts

# 应该看到类似输出:
# ✓ src/infra/retry.test.ts (x tests) xxms
# Test Files  1 passed (1)

二、IDE 调试配置

项目没有内置 .vscode/launch.json,需要手动创建。

2.1 创建 VS Code 调试配置

在项目根目录创建 .vscode/launch.json

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "调试当前测试文件",
      "type": "node",
      "request": "launch",
      "autoAttachChildProcesses": true,
      "runtimeExecutable": "pnpm",
      "runtimeArgs": [
        "vitest",
        "run",
        "--no-file-parallelism",
        "${relativeFile}"
      ],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "skipFiles": ["<node_internals>/**", "**/node_modules/**"]
    },
    {
      "name": "调试当前测试文件 (Gateway 配置)",
      "type": "node",
      "request": "launch",
      "autoAttachChildProcesses": true,
      "runtimeExecutable": "pnpm",
      "runtimeArgs": [
        "vitest",
        "run",
        "--config",
        "vitest.gateway.config.ts",
        "--no-file-parallelism",
        "${relativeFile}"
      ],
      "console": "integratedTerminal",
      "skipFiles": ["<node_internals>/**", "**/node_modules/**"]
    },
    {
      "name": "调试 Gateway 启动流程",
      "type": "node",
      "request": "launch",
      "runtimeExecutable": "node",
      "args": [
        "scripts/run-node.mjs",
        "gateway",
        "run",
        "--verbose",
        "--port",
        "18789"
      ],
      "env": {
        "OPENCLAW_SKIP_CHANNELS": "1",
        "NODE_OPTIONS": "--import tsx"
      },
      "console": "integratedTerminal",
      "skipFiles": ["<node_internals>/**", "**/node_modules/**"]
    },
    {
      "name": "调试 CLI 命令",
      "type": "node",
      "request": "launch",
      "runtimeExecutable": "node",
      "args": ["scripts/run-node.mjs", "--version"],
      "console": "integratedTerminal",
      "skipFiles": ["<node_internals>/**", "**/node_modules/**"]
    },
    {
      "name": "调试指定测试文件 (手动输入)",
      "type": "node",
      "request": "launch",
      "autoAttachChildProcesses": true,
      "runtimeExecutable": "pnpm",
      "runtimeArgs": [
        "vitest",
        "run",
        "--no-file-parallelism",
        "${input:testFile}"
      ],
      "console": "integratedTerminal",
      "skipFiles": ["<node_internals>/**", "**/node_modules/**"]
    }
  ],
  "inputs": [
    {
      "id": "testFile",
      "type": "promptString",
      "description": "输入测试文件路径 (如: src/config/paths.test.ts)"
    }
  ]
}

2.2 使用方式

  1. 打开任意 .test.ts 文件
  2. 在想要暂停的代码行左侧点击设置断点(红色圆点)
  3. F5 或点击左侧调试面板 → 选择 "调试当前测试文件" → 点击运行
  4. 程序会在断点处暂停,你可以:
    • 查看变量:左侧 VARIABLES 面板
    • 调用栈:左侧 CALL STACK 面板
    • 监视表达式:在 WATCH 面板添加表达式
    • 单步执行:F10(Step Over)、F11(Step Into)、Shift+F11(Step Out)
    • 调试控制台:底部 DEBUG CONSOLE 可以执行任意表达式

![VS Code 断点调试工作流:打开文件 → 设置断点 → F5 启动 → 查看变量/调用栈/监视表达式/单步执行](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码调试指南/02-infographic-debug-workflow-1775150827982.png)

2.3 Vitest UI 模式(可视化调试)

bash
# 启动 Vitest UI(浏览器中可视化运行/查看测试)
pnpm vitest --ui

# 启动指定模块的 Vitest UI
pnpm vitest --ui src/config/

Vitest UI 提供了一个 Web 界面,可以:

  • 可视化浏览所有测试用例
  • 点击运行/重新运行单个测试
  • 查看测试输出和错误栈
  • 适合在浏览器中快速浏览测试结构

三、项目测试体系详解

3.1 Vitest 配置分层

OpenClaw 将测试拆分为 6 套独立的 Vitest 配置,理解这个分层是调试的基础:

vitest.config.ts              ← 基础配置(所有配置的父类)
├── vitest.unit.config.ts     ← 单元测试(排除 gateway/agents/browser/channels)
├── vitest.gateway.config.ts  ← Gateway 网关专用(--pool=forks 进程隔离)
├── vitest.channels.config.ts ← 渠道测试(telegram/discord/browser/line)
├── vitest.extensions.config.ts ← 扩展插件测试
├── vitest.e2e.config.ts      ← 端到端测试(*.e2e.test.ts)
└── vitest.live.config.ts     ← 真实 API 测试(*.live.test.ts,需要 API Key)

为什么要分层? 每套测试的依赖和运行环境不同:

  • Gateway 测试需要启动 HTTP 服务器 → 进程隔离避免端口冲突
  • 渠道测试 mock 了第三方 SDK → 与 Gateway 测试分离避免 mock 泄漏
  • Live 测试需要真实密钥 → 默认排除,仅手动运行

![Vitest 6 套配置分层架构:基础配置到 unit/gateway/channels/extensions/e2e/live](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码调试指南/03-infographic-vitest-config-layers-1775150828793.png)

3.2 全局 Setup 文件

每个测试进程启动时都会运行 test/setup.ts,它负责:

操作目的
设置 process.env.VITEST = "true"让业务代码感知测试环境
withIsolatedTestHome()创建临时 HOME 目录,隔离真实配置文件
installProcessWarningFilter()过滤 Node.js 实验性警告噪音
setActivePluginRegistry(...)注入 stub 渠道插件注册表
afterEach 中清理 fake timers防止 vi.useFakeTimers() 泄漏

核心洞察: 测试环境中 HOME 指向一个临时目录(/tmp/openclaw-test-home-xxx),所有读取 ~/.openclaw/ 的逻辑都会去读临时目录。这意味着你的真实配置永远不会被测试影响。

3.3 测试命令速查表

bash
# ═══════════════ 基础命令 ═══════════════
pnpm test                  # 运行所有测试(并行调度)
pnpm test:fast             # 仅运行单元测试(最快)
pnpm test:gateway          # 仅运行 Gateway 测试
pnpm test:channels         # 仅运行渠道测试
pnpm test:extensions       # 仅运行扩展插件测试
pnpm test:e2e              # 仅运行 E2E 测试
pnpm test:coverage         # 单元测试 + 覆盖率报告

# ═══════════════ 精确运行 ═══════════════
# 运行单个文件
pnpm vitest run src/config/paths.test.ts

# 运行目录下所有测试
pnpm vitest run src/config/

# 运行匹配关键词的测试
pnpm vitest run -t "resolves oauth dir"

# 运行 Gateway 下单个文件(必须指定 config)
pnpm vitest run --config vitest.gateway.config.ts src/gateway/auth.test.ts

# ═══════════════ 观察模式 ═══════════════
# 文件修改自动重跑
pnpm vitest src/config/paths.test.ts

# 全局观察模式
pnpm test:watch

# ═══════════════ 调试模式 ═══════════════
# Node.js inspect 模式(可连接 Chrome DevTools 或 VS Code)
node --inspect-brk node_modules/.bin/vitest run src/config/paths.test.ts

# 串行运行(避免并发干扰,方便调试)
pnpm vitest run --no-file-parallelism src/gateway/

# 低内存模式(适合配置较低的机器)
OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test

四、测试代码中的关键模式

在阅读和调试测试代码前,了解以下模式可以让你事半功倍。

![测试代码 5 种关键模式:ESM Mock、依赖注入、临时文件系统、辅助工具库、__testing 导出](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码调试指南/04-infographic-test-patterns-1775150829698.png)

4.1 ESM 模块 Mock 模式

OpenClaw 是 ESM 项目,mock 模块的标准写法是 先声明 mock → 再动态导入

typescript
// ❶ 先创建 mock 函数
const agentCommand = vi.fn();

// ❷ 模块级 mock(Vitest 会自动提升到文件顶部执行)
vi.mock("../commands/agent.js", () => ({ agentCommand }));

// ❸ 动态导入被测模块(此时 mock 已生效)
const { runBootOnce } = await import("./boot.js");

调试技巧:vi.mock 的工厂函数中打断点,可以观察模块何时被替换。

4.2 依赖注入模式(makeDeps / createDefaultDeps

项目大量使用显式依赖注入,被测函数通过参数接收所有外部依赖:

typescript
// 生产代码
export function processMessage(deps: {
  logger: Logger;
  config: Config;
  send: SendFn;
}) {
  deps.logger.info("processing...");
  // ...
}

// 测试代码
const deps = {
  logger: { info: vi.fn(), error: vi.fn() },
  config: { model: "test-model" },
  send: vi.fn(),
};
processMessage(deps);
expect(deps.send).toHaveBeenCalledWith(/* ... */);

调试技巧:deps 的 mock 函数中打断点(如 deps.send = vi.fn(() => { /* 断点 */ })),可以捕获被测代码对依赖的每次调用。

4.3 临时文件系统辅助

涉及文件操作的测试使用 RAII 风格的临时目录管理:

typescript
async function withTempRoot(prefix: string, run: (root: string) => Promise<void>) {
  const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
  try {
    await run(root);
  } finally {
    await fs.rm(root, { recursive: true, force: true });
  }
}

// 测试中使用
it("reads config from custom path", async () => {
  await withTempRoot("config-test-", async (root) => {
    // root 是一个全新的临时目录
    await fs.writeFile(path.join(root, "openclaw.json"), JSON.stringify({ ... }));
    const config = readConfig(root);
    expect(config.agent.model).toBe("test");
  });
});

调试技巧:finally 前打断点,可以在清理前用 Finder/Terminal 查看临时目录内容。

4.4 测试辅助工具库

项目提供了丰富的测试辅助工具,位于 src/test-utils/

工具文件用途使用场景
env.ts环境变量快照/恢复(captureEnvwithEnvwithEnvAsync修改 env 的测试
temp-home.ts创建隔离的 HOME 目录读写 ~/.openclaw 的测试
temp-dir.ts临时目录管理文件系统操作
fetch-mock.tsHTTP 请求 mock网络相关测试
channel-plugins.ts渠道插件注册表工厂渠道/路由测试
ports.ts动态端口分配需要启动服务器的测试
frozen-time.ts时间冻结定时任务/过期判断
command-runner.tsCLI 命令执行辅助CLI 集成测试

4.5 __testing 内部导出

部分模块通过 __testing 导出内部实现,专供测试使用:

typescript
// 生产代码 (loader.ts)
export const __testing = { parseManifest, validateSchema };

// 测试代码 (loader.test.ts)
import { __testing, loadOpenClawPlugins } from "./loader.js";
const { parseManifest } = __testing;

调试技巧: 遇到 __testing 导出时,说明这些函数是模块的内部实现细节,非常适合打断点深入理解内部机制。


五、分模块调试实战

![OpenClaw 模块调试全景地图:基础层/功能层/应用层三层架构](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码调试指南/05-infographic-module-landscape-1775150830622.png)

5.1 配置系统调试(推荐首先学习)

入口:src/config/ — 纯数据处理模块,零外部依赖,最适合上手

学习目标: 理解 Zod schema 验证、配置文件读写、默认值合并

调试步骤 1:Schema 验证

bash
pnpm vitest run src/config/schema.test.ts

src/config/schema.ts 中找到 buildConfigSchema() 函数,打断点:

  • 观察 Zod schema 如何从零构建
  • 观察插件如何注入额外的 schema 字段

调试步骤 2:配置路径解析

bash
pnpm vitest run src/config/paths.test.ts

推荐断点位置:

  • src/config/paths.tsresolveStateDir() — 观察 ~/.openclaw 路径如何确定
  • src/config/paths.tsresolveConfigPath() — 观察配置文件路径解析链

调试步骤 3:配置读写

bash
pnpm vitest run src/config/io.write-config.test.ts
pnpm vitest run src/config/io.compat.test.ts

推荐断点位置:

  • src/config/io.ts → 写入配置函数 — 观察 JSON5 序列化过程
  • src/config/merge-patch.ts → 合并函数 — 观察配置 patch 合并逻辑

调试步骤 4:环境变量覆盖

bash
pnpm vitest run src/config/env-substitution.test.ts

观察 ${ENV_VAR} 语法在配置值中如何被替换为实际环境变量值。


5.2 基础设施层调试

入口:src/infra/ — 通用工具库,理解项目的底层能力

学习目标: 理解重试机制、安全防护、文件系统安全操作

调试步骤 1:重试机制

bash
pnpm vitest run src/infra/retry.test.ts

推荐断点位置:

  • src/infra/retry.ts → 重试循环体 — 观察指数退避、最大次数逻辑

调试步骤 2:SSRF 防护

bash
pnpm vitest run src/infra/net/ssrf.test.ts

推荐断点位置:

  • src/infra/net/ssrf.ts → IP 地址检查函数 — 理解为何 127.0.0.1 被拒绝

调试步骤 3:文件系统安全操作

bash
pnpm vitest run src/infra/fs-safe.test.ts

推荐断点位置:

  • src/infra/fs-safe.ts → 原子写入函数 — 观察"写临时文件 → rename"的安全写入模式

5.3 日志系统调试

入口:src/logging/ — 理解全局日志基础设施

bash
pnpm vitest run src/logging/

推荐断点位置:

  • src/logging/logger.ts → 日志创建函数 — 观察子系统标签如何附加
  • src/logging/redact.ts → 敏感信息脱敏 — 观察 token/密钥如何被过滤

5.4 媒体处理调试

入口:src/media/ — 纯工具模块,图片/音频处理

bash
# MIME 类型检测
pnpm vitest run src/media/mime.test.ts

# Base64 编解码
pnpm vitest run src/media/base64.test.ts

# 图片处理辅助
pnpm vitest run src/media/image-ops.helpers.test.ts

# 音频处理
pnpm vitest run src/media/audio.test.ts

推荐断点位置:

  • src/media/mime.ts → MIME 检测函数 — 观察 magic bytes 匹配过程
  • src/media/store.ts → 媒体存储函数 — 观察临时文件生命周期管理

5.5 记忆系统调试

入口:src/memory/ — 向量搜索 + SQLite 存储

调试步骤 1:纯算法(不需要数据库)

bash
# MMR (Maximal Marginal Relevance) 重排序算法
pnpm vitest run src/memory/mmr.test.ts

推荐断点位置:

  • src/memory/mmr.tscomputeMMRScore() — 观察相关性 vs 多样性的平衡计算
  • src/memory/mmr.tsmmrRerank() — 观察贪心选择过程

调试步骤 2:嵌入向量

bash
pnpm vitest run src/memory/embeddings.test.ts
pnpm vitest run src/memory/embeddings-ollama.test.ts

调试步骤 3:混合检索

bash
pnpm vitest run src/memory/hybrid.test.ts

推荐断点位置:

  • src/memory/hybrid.ts → 混合检索函数 — 观察关键词搜索和向量搜索结果如何融合

5.6 定时任务调试

入口:src/cron/ — 任务调度系统

bash
# Cron 表达式解析
pnpm vitest run src/cron/schedule.test.ts

# 错峰执行策略
pnpm vitest run src/cron/stagger.test.ts

# 持久化存储
pnpm vitest run src/cron/store.test.ts

# 任务调度服务
pnpm vitest run src/cron/service.jobs.test.ts

推荐断点位置:

  • src/cron/schedule.ts → cron 表达式解析 — 观察 0 9 * * 1-5 如何被解析
  • src/cron/stagger.ts → 错峰计算 — 观察同一时刻多任务如何被分散

特别说明: Cron 测试大量使用 vi.useFakeTimers() 来加速时间流逝,调试时在 vi.advanceTimersByTime(...) 前后打断点,可以观察定时器触发机制。


5.7 CLI 入口系统调试

入口:src/entry.tssrc/cli/ — Commander.js 命令行框架

调试步骤 1:启动流程

bash
pnpm vitest run src/cli/run-main.test.ts

或直接用 VS Code 调试 CLI 命令:

  1. 选择 "调试 CLI 命令" 配置
  2. 修改 args 中的命令参数,如 ["scripts/run-node.mjs", "config", "get", "agent.model"]
  3. src/entry.ts 第 43 行 process.title = "openclaw" 处打断点
  4. F5 运行,逐步跟踪启动流程

推荐断点位置:

  • src/entry.ts:43 — 进程初始化起点
  • src/cli/run-main.tsrunCli() — CLI 框架初始化
  • src/cli/program/build-program.ts → 命令注册 — 观察 Commander.js 命令树构建

调试步骤 2:命令解析

bash
pnpm vitest run src/cli/argv.test.ts
pnpm vitest run src/cli/program/command-tree.test.ts

调试步骤 3:Profile 机制

bash
pnpm vitest run src/cli/profile.test.ts

5.8 插件系统调试

入口:src/plugins/ + src/plugin-sdk/ — 插件加载/注册/生命周期

调试步骤 1:插件发现与加载

bash
pnpm vitest run src/plugins/loader.test.ts

推荐断点位置:

  • src/plugins/loader.tsloadOpenClawPlugins() — 插件发现入口
  • src/plugins/loader.ts → 使用 __testing 导出的 parseManifest — manifest 解析

调试步骤 2:插件安装

bash
pnpm vitest run src/plugins/install.test.ts
pnpm vitest run src/plugins/installs.test.ts

调试步骤 3:Plugin SDK

bash
pnpm vitest run src/plugin-sdk/index.test.ts
pnpm vitest run src/plugin-sdk/webhook-targets.test.ts

推荐断点位置:

  • src/plugin-sdk/index.ts — SDK 导出入口,观察插件 API 暴露了什么能力

调试步骤 4:钩子系统

bash
pnpm vitest run src/plugins/hooks.phase-hooks.test.ts
pnpm vitest run src/plugins/wired-hooks-message.test.ts
pnpm vitest run src/plugins/wired-hooks-gateway.test.ts

推荐断点位置:

  • wired-hooks-*.ts 文件 — 观察消息到达、Agent 启动、会话压缩等生命周期钩子

5.9 Channel 渠道系统调试

入口:src/channels/ — 渠道抽象层 + 适配器模式

调试步骤 1:渠道注册表

bash
pnpm vitest run --config vitest.channels.config.ts src/channels/dock.test.ts
pnpm vitest run --config vitest.channels.config.ts src/channels/registry.helpers.test.ts

推荐断点位置:

  • src/channels/dock.ts → dock 初始化 — 观察渠道插件如何被加载和注册

调试步骤 2:DM 配对与白名单

bash
pnpm vitest run --config vitest.channels.config.ts src/channels/allow-from.test.ts
pnpm vitest run --config vitest.channels.config.ts src/channels/command-gating.test.ts

推荐断点位置:

  • src/channels/allow-from.ts — 观察白名单匹配逻辑

调试步骤 3:出站消息路由

bash
pnpm vitest run --config vitest.channels.config.ts src/channels/plugins/outbound/

推荐断点位置:

  • src/channels/plugins/outbound/*.ts — 观察消息如何被格式化为各渠道的原生格式

调试步骤 4:Telegram 渠道(典型渠道实现)

bash
pnpm vitest run --config vitest.channels.config.ts src/channels/telegram/

5.10 Gateway 网关调试

入口:src/gateway/ — 系统核心控制平面

⚠️ 重要提示: Gateway 测试必须使用 vitest.gateway.config.ts 配置。

调试步骤 1:认证系统

bash
pnpm vitest run --config vitest.gateway.config.ts src/gateway/auth.test.ts
pnpm vitest run --config vitest.gateway.config.ts src/gateway/auth-rate-limit.test.ts

推荐断点位置:

  • src/gateway/auth.ts → token 验证中间件 — 观察请求如何被认证
  • src/gateway/auth.ts → rate limit 逻辑 — 观察暴力破解防护

调试步骤 2:启动编排

bash
pnpm vitest run --config vitest.gateway.config.ts src/gateway/boot.test.ts

推荐断点位置:

  • src/gateway/boot.tsrunBootOnce() — 观察 Gateway 启动的完整编排流程

调试步骤 3:WebSocket 协议

bash
pnpm vitest run --config vitest.gateway.config.ts src/gateway/protocol/index.test.ts

调试步骤 4:实际启动 Gateway 实例

这是最强大的调试方式 —— 启动一个真实的 Gateway:

bash
# 方式 1:跳过所有渠道连接,纯调试 Gateway 逻辑
OPENCLAW_SKIP_CHANNELS=1 pnpm gateway:dev

# 方式 2:开发模式带热重载
pnpm gateway:watch

# 方式 3:VS Code 调试器
# 选择 "调试 Gateway 启动流程" 配置,F5 运行

启动后可以用以下方式验证:

bash
# 检查端口是否监听
lsof -i :18789

# 检查健康状态
curl http://127.0.0.1:18789/health

# 通过 CLI 检查状态
pnpm openclaw status

调试步骤 5:事件系统与钩子

bash
pnpm vitest run --config vitest.gateway.config.ts src/gateway/hooks.test.ts
pnpm vitest run --config vitest.gateway.config.ts src/gateway/hooks-mapping.test.ts

![Gateway 网关核心流程:Boot → Auth → WebSocket → Hooks → Channels](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码调试指南/06-infographic-gateway-flow-1775150831461.png)


5.11 Agent 智能体系统调试

入口:src/agents/ — AI Agent 运行时,最复杂的模块

调试步骤 1:上下文管理

bash
pnpm vitest run src/agents/context.test.ts
pnpm vitest run src/agents/context.lookup.test.ts

推荐断点位置:

  • src/agents/context.ts → 上下文窗口计算 — 观察不同模型的 token 上限如何确定

调试步骤 2:系统提示词构建

bash
pnpm vitest run src/agents/system-prompt.test.ts

推荐断点位置:

  • src/agents/system-prompt.ts — 观察系统提示词如何从 AGENTS.md + SOUL.md + 工具列表动态构建

调试步骤 3:技能系统

bash
pnpm vitest run src/agents/skills.resolveskillspromptforrun.test.ts
pnpm vitest run src/agents/skills.buildworkspaceskillstatus.test.ts
pnpm vitest run src/agents/skills/filter.test.ts

推荐断点位置:

  • src/agents/skills.ts → 技能解析 — 观察 SKILL.md 文件如何被解析为运行时技能对象

调试步骤 4:模型配置与选择

bash
pnpm vitest run src/agents/model-selection.test.ts
pnpm vitest run src/agents/model-compat.test.ts
pnpm vitest run src/agents/models-config.providers.ollama.test.ts

推荐断点位置:

  • src/agents/model-selection.ts → 模型选择函数 — 观察 anthropic/claude-opus-4-6 如何被解析和路由

调试步骤 5:工具执行

bash
pnpm vitest run src/agents/bash-tools.exec.pty.test.ts
pnpm vitest run src/agents/pi-tools.policy.test.ts

推荐断点位置:

  • src/agents/bash-tools.ts → exec 执行函数 — 观察 Agent 如何安全执行 shell 命令

调试步骤 6:Pi Agent 运行时

bash
pnpm vitest run src/agents/pi-embedded-runner.limithistoryturns.test.ts
pnpm vitest run src/agents/pi-embedded-runner/model.test.ts
pnpm vitest run src/agents/pi-embedded-runner/thinking.test.ts

推荐断点位置:

  • src/agents/pi-embedded-runner.ts — Agent 运行循环入口
  • src/agents/pi-embedded-subscribe.ts — 订阅 Agent 事件流(消息块、工具调用等)

调试步骤 7:通过 CLI 直接对话调试

bash
# 与 Agent 单轮对话
pnpm openclaw agent --message "Hello" --thinking low

# TUI 模式(交互式终端)
pnpm openclaw tui

5.12 浏览器控制调试

入口:src/browser/ — Playwright + CDP 浏览器自动化

调试步骤 1:配置与路径

bash
pnpm vitest run --config vitest.channels.config.ts src/browser/config.test.ts
pnpm vitest run --config vitest.channels.config.ts src/browser/paths.test.ts
pnpm vitest run --config vitest.channels.config.ts src/browser/profiles.test.ts

调试步骤 2:CDP 连接

bash
pnpm vitest run --config vitest.channels.config.ts src/browser/cdp.test.ts
pnpm vitest run --config vitest.channels.config.ts src/browser/chrome.test.ts

调试步骤 3:浏览器状态

bash
pnpm openclaw browser status

六、进阶调试技巧

![进阶调试技巧工具箱:8 种调试技巧速览](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码调试指南/07-infographic-debug-techniques-1775150832284.png)

6.1 打印调试 — 快速定位

在代码中临时加入 console.logconsole.dir 是最快的方式:

typescript
// 在被测代码中加入临时打印
console.log(">>> config value:", JSON.stringify(config, null, 2));
console.dir(deps, { depth: 5 });

// 运行测试时加上 --reporter=verbose 查看详细输出
pnpm vitest run --reporter=verbose src/config/paths.test.ts

注意: 调试完成后务必删除临时打印。项目 lint 规则会阻止提交包含 console.log 的代码。

6.2 条件断点

在 VS Code 中右键断点 → "Edit Breakpoint" → 输入条件表达式:

// 仅当 channelId 为 "telegram" 时暂停
channelId === "telegram"

// 仅当数组长度大于 5 时暂停
results.length > 5

// 仅当第 3 次经过时暂停
hitCount === 3

6.3 Logpoint(日志断点)

VS Code 中右键 → "Add Logpoint",在不暂停执行的情况下打印变量:

config.agent.model = {config.agent.model}, channels = {Object.keys(config.channels)}

6.4 调试单个 describeit

typescript
// 将 describe 改为 describe.only — 只运行这一个 suite
describe.only("resolveStateDir", () => {
  it("uses HOME when no override", () => { ... });
});

// 将 it 改为 it.only — 只运行这一个测试用例
it.only("uses OPENCLAW_HOME for tilde expansion", () => { ... });

也可以通过命令行过滤:

bash
# 仅运行名称匹配 "uses HOME" 的测试
pnpm vitest run -t "uses HOME" src/config/paths.test.ts

6.5 查看 Mock 调用记录

typescript
const sendFn = vi.fn();
// ... 运行被测代码后 ...

// 查看被调用了几次
console.log(sendFn.mock.calls.length);

// 查看每次调用的参数
console.log(JSON.stringify(sendFn.mock.calls, null, 2));

// 查看返回值
console.log(sendFn.mock.results);

6.6 调试异步代码

对于 async/await 代码,在 await 语句前后分别打断点:

typescript
async function processMessage(msg) {
  const config = await loadConfig(); // ← 断点1:观察配置加载
  const result = await agent.run(msg); // ← 断点2:观察 Agent 处理
  await send(result); // ← 断点3:观察消息发送
}

6.7 调试 Gateway WebSocket 通信

启动 Gateway 后,可以用 wscat 或 Chrome DevTools 的 Network 面板观察 WebSocket 消息:

bash
# 安装 wscat
npm install -g wscat

# 连接 Gateway
wscat -c ws://127.0.0.1:18789 -H "Authorization: Bearer <your-token>"

6.8 环境变量开关速查

环境变量作用
OPENCLAW_SKIP_CHANNELS=1启动 Gateway 但不连接任何渠道
OPENCLAW_LIVE_TEST=1允许运行需要真实 API Key 的测试
VITEST=true标识测试环境(自动设置)
OPENCLAW_TEST_FAST=1跳过耗时的初始化步骤
OPENCLAW_TEST_PROFILE=low低内存测试模式
OPENCLAW_TEST_SERIAL_GATEWAY=1串行运行 Gateway 测试
OPENCLAW_PLUGIN_MANIFEST_CACHE_MS插件 manifest 缓存时间(测试中默认 60s)

七、推荐的调试学习路线

按照从简单到复杂的顺序,建议按以下路线用调试法学习源码:

![调试学习四阶段路线图:Day 1-3 纯函数 → Day 4-7 系统服务 → Day 8-14 核心运行时 → Day 15+ 集成调试](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码调试指南/08-infographic-learning-roadmap-1775150833172.png)

第一阶段:纯函数模块(Day 1-3)

src/infra/retry.ts          → 理解重试机制
src/config/paths.ts          → 理解路径解析
src/config/schema.ts         → 理解 Zod 验证
src/media/mime.ts            → 理解 MIME 检测
src/memory/mmr.ts            → 理解 MMR 算法
src/cron/schedule.ts         → 理解 cron 解析
src/logging/redact.ts        → 理解日志脱敏

每个文件的学习方法:

  1. 先读测试文件,理解输入输出期望
  2. 在被测函数入口打断点
  3. 用 F11(Step Into)逐行走完一个测试用例
  4. 在 WATCH 面板添加关键变量

第二阶段:系统服务模块(Day 4-7)

src/config/io.ts             → 配置文件读写
src/plugins/loader.ts        → 插件加载机制
src/channels/dock.ts         → 渠道注册表
src/channels/allow-from.ts   → 白名单与安全
src/cron/service.ts          → 定时任务调度

第三阶段:核心运行时(Day 8-14)

src/gateway/auth.ts          → Gateway 认证
src/gateway/boot.ts          → Gateway 启动编排
src/agents/context.ts        → Agent 上下文
src/agents/system-prompt.ts  → 系统提示词构建
src/agents/skills.ts         → 技能加载与执行
src/agents/pi-embedded-runner.ts → Agent 运行循环

第四阶段:集成调试(Day 15+)

1. 启动 Gateway 实例 → 用 curl/wscat 手动交互
2. 通过 TUI 模式与 Agent 对话,同时断点 Agent 运行循环
3. 配置一个渠道(如 Telegram),观察完整的消息收发流程
4. 编写一个简单插件,调试插件生命周期

八、常见问题排查

![常见问题排查速查:6 个 FAQ 及解决方案](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码调试指南/09-infographic-troubleshooting-1775150834210.png)

Q1: 测试运行报 "Cannot find module"

bash
# 重新安装依赖
pnpm install

# 如果是 plugin-sdk 相关,确保 vitest.config.ts 中的 alias 配置正确
# 项目已内置 alias,通常重新安装即可

Q2: Gateway 测试端口冲突

bash
# 检查端口占用
lsof -i :18789

# 杀掉占用进程
kill -9 <PID>

# 或使用不同端口
OPENCLAW_GATEWAY_PORT=19000 pnpm vitest run --config vitest.gateway.config.ts <file>

Q3: 测试超时

bash
# 增加超时时间
pnpm vitest run --test-timeout 300000 src/gateway/boot.test.ts

# 或在测试文件中设置
describe("slow test", { timeout: 300_000 }, () => { ... });

Q4: VS Code 断点不生效

确认以下设置:

  1. launch.jsonautoAttachChildProcesses: true(Vitest 在子进程中运行测试)
  2. 断点打在 .ts 源文件而非 .js 编译产物
  3. 使用 --no-file-parallelism 避免多进程导致断点丢失

Q5: 测试隔离问题(测试间相互影响)

bash
# 串行运行测试
pnpm vitest run --no-file-parallelism src/config/

# 单独运行一个文件
pnpm vitest run src/config/paths.test.ts

# 使用 --pool=forks 进程隔离
pnpm vitest run --pool=forks src/config/paths.test.ts

Q6: 内存不足

bash
# 使用低内存配置
OPENCLAW_TEST_PROFILE=low pnpm test

# 限制并发
pnpm vitest run --maxWorkers=2 src/config/

九、调试 Cheat Sheet

bash
# ══════════ 快速上手 ══════════
pnpm vitest run <file>                              # 运行单个测试
pnpm vitest run <dir>/                              # 运行目录下所有测试
pnpm vitest run -t "test name" <file>               # 运行匹配名称的测试
pnpm vitest <file>                                  # 观察模式(自动重跑)
pnpm vitest --ui                                    # 浏览器 UI 模式

# ══════════ 分模块调试 ══════════
pnpm test:fast                                      # 单元测试
pnpm test:gateway                                   # Gateway 测试
pnpm test:channels                                  # 渠道测试
pnpm test:extensions                                # 扩展插件测试

# ══════════ 启动服务 ══════════
OPENCLAW_SKIP_CHANNELS=1 pnpm gateway:dev           # 无渠道 Gateway
pnpm gateway:watch                                  # 热重载 Gateway
pnpm openclaw agent --message "hi" --thinking low   # CLI Agent

# ══════════ VS Code 调试 ══════════
# F5        → 启动调试
# F9        → 切换断点
# F10       → Step Over(单步跳过)
# F11       → Step Into(单步进入)
# Shift+F11 → Step Out(单步跳出)
# Ctrl+Shift+D → 打开调试面板

# ══════════ 实用技巧 ══════════
describe.only("...", () => {})                      # 只运行一个 suite
it.only("...", () => {})                            # 只运行一个用例
console.dir(obj, { depth: 5 })                      # 深层打印对象
vi.fn().mock.calls                                  # 查看 mock 调用记录

十、进一步学习资源

资源链接/路径
Vitest 官方文档https://vitest.dev
VS Code 调试文档https://code.visualstudio.com/docs/editor/debugging
OpenClaw 测试文档docs/testing.md
项目编码规范AGENTS.md
配置参考docs/configuration.md
安全模型SECURITY.md

读文档、看源码、写代码,理解 AI Agent 本质 🤖