主题
OpenClaw 源码调试指南
版本:2026.3.2 | 编写日期:2026-03-25 目标:通过调试手段深入理解 OpenClaw 每个核心模块的运行原理
一、环境准备
1.1 前置要求
| 工具 | 版本要求 | 安装方式 |
|---|---|---|
| Node.js | 22+ | brew install node 或 nvm install 22 |
| pnpm | 最新版 | npm install -g pnpm |
| Bun | 最新版 | brew install oven-sh/bun/bun |
| VS Code | 最新版 | 推荐 IDE |
| Vitest 插件 | VS Code 扩展 | 搜索 vitest.explorer 安装 |

1.2 初始化项目
bash
cd /Users/bytedance/Desktop/ai-study/openclaw-2026.3.2
# 安装所有依赖
pnpm install
# 验证安装成功
pnpm vitest --version
# 构建项目(某些模块的测试需要构建产物)
pnpm build1.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 使用方式
- 打开任意
.test.ts文件 - 在想要暂停的代码行左侧点击设置断点(红色圆点)
- 按
F5或点击左侧调试面板 → 选择 "调试当前测试文件" → 点击运行 - 程序会在断点处暂停,你可以:
- 查看变量:左侧 VARIABLES 面板
- 调用栈:左侧 CALL STACK 面板
- 监视表达式:在 WATCH 面板添加表达式
- 单步执行:F10(Step Over)、F11(Step Into)、Shift+F11(Step Out)
- 调试控制台:底部 DEBUG CONSOLE 可以执行任意表达式

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 测试需要真实密钥 → 默认排除,仅手动运行

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四、测试代码中的关键模式
在阅读和调试测试代码前,了解以下模式可以让你事半功倍。

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 | 环境变量快照/恢复(captureEnv、withEnv、withEnvAsync) | 修改 env 的测试 |
temp-home.ts | 创建隔离的 HOME 目录 | 读写 ~/.openclaw 的测试 |
temp-dir.ts | 临时目录管理 | 文件系统操作 |
fetch-mock.ts | HTTP 请求 mock | 网络相关测试 |
channel-plugins.ts | 渠道插件注册表工厂 | 渠道/路由测试 |
ports.ts | 动态端口分配 | 需要启动服务器的测试 |
frozen-time.ts | 时间冻结 | 定时任务/过期判断 |
command-runner.ts | CLI 命令执行辅助 | 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 导出时,说明这些函数是模块的内部实现细节,非常适合打断点深入理解内部机制。
五、分模块调试实战

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.ts→resolveStateDir()— 观察~/.openclaw路径如何确定src/config/paths.ts→resolveConfigPath()— 观察配置文件路径解析链
调试步骤 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.ts→computeMMRScore()— 观察相关性 vs 多样性的平衡计算src/memory/mmr.ts→mmrRerank()— 观察贪心选择过程
调试步骤 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.ts→src/cli/— Commander.js 命令行框架
调试步骤 1:启动流程
bash
pnpm vitest run src/cli/run-main.test.ts或直接用 VS Code 调试 CLI 命令:
- 选择 "调试 CLI 命令" 配置
- 修改
args中的命令参数,如["scripts/run-node.mjs", "config", "get", "agent.model"] - 在
src/entry.ts第 43 行process.title = "openclaw"处打断点 - F5 运行,逐步跟踪启动流程
推荐断点位置:
src/entry.ts:43— 进程初始化起点src/cli/run-main.ts→runCli()— 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.ts5.8 插件系统调试
入口:
src/plugins/+src/plugin-sdk/— 插件加载/注册/生命周期
调试步骤 1:插件发现与加载
bash
pnpm vitest run src/plugins/loader.test.ts推荐断点位置:
src/plugins/loader.ts→loadOpenClawPlugins()— 插件发现入口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.ts→runBootOnce()— 观察 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
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 tui5.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六、进阶调试技巧

6.1 打印调试 — 快速定位
在代码中临时加入 console.log 或 console.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 === 36.3 Logpoint(日志断点)
VS Code 中右键 → "Add Logpoint",在不暂停执行的情况下打印变量:
config.agent.model = {config.agent.model}, channels = {Object.keys(config.channels)}6.4 调试单个 describe 或 it
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.ts6.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)
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 → 理解日志脱敏每个文件的学习方法:
- 先读测试文件,理解输入输出期望
- 在被测函数入口打断点
- 用 F11(Step Into)逐行走完一个测试用例
- 在 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. 编写一个简单插件,调试插件生命周期八、常见问题排查

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 断点不生效
确认以下设置:
launch.json中autoAttachChildProcesses: true(Vitest 在子进程中运行测试)- 断点打在
.ts源文件而非.js编译产物 - 使用
--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.tsQ6: 内存不足
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 |