claude-mem OpenClaw 插件测试指南:从单元测试到 Docker E2E 的四层验证体系
本文基于 claude-mem 仓库的 OpenClaw 插件测试指南 展开,系统讲解该插件从最快路径(npm test 单元测试、SSE 冒烟脚本)到最重路径(Docker 内安装真实 OpenClaw 网关 + mock worker 跑 16 项 E2E 检查)的完整测试方法,并结合 插件源码 深入剖析其 SSE 重连、熔断器与频道投递的实现原理。读完本文,你可以独立完成 OpenClaw 插件的构建、安装、配置与端到端验证,并能按源码逻辑定位"消息不送达、SSE 断连、端口错配"等典型故障。
一、被测对象:OpenClaw 插件是什么
claude-mem 的 OpenClaw 插件位于 openclaw/ 目录,其定位由 openclaw.plugin.json 声明:
"id": "claude-mem"、"kind": "memory"——作为 OpenClaw 网关的 memory 槽位插件接入;- 挂载两个技能目录:
skills/make-plan、skills/do; - 通过
configSchema声明了完整的插件配置契约(workerPort、workerHost、project、syncMemoryFile、observationFeed等),后文测试配置即以此为准。
从 package.json 可见,插件包名为 @openclaw/claude-mem(version 1.0.0),main 指向 dist/index.js,并带有 openclaw.extensions 字段声明扩展入口——这正是 E2E 中 plugins info 检查的关键字段。构建与测试脚本非常轻量:
{
"scripts": {
"build": "tsc",
"test": "tsc && node --test dist/index.test.js"
}
}
也就是说:单元测试 = 先用 TypeScript 编译器编译,再用 Node 内置的 node:test 运行器执行。测试文件 src/index.test.ts 使用 Node 原生测试框架,构造了一个 mock 的 OpenClawPluginApi(含 6 个频道发送函数的桩实现),覆盖插件注册、服务生命周期、命令处理与 SSE 集成。
二、测试金字塔:五层验证结构
TESTING.md 将测试分为由快到重的多层,按需选择即可:
| 层级 | 命令 | 用途 | 依赖 |
|---|---|---|---|
| 1. 单元测试 | npm test |
编译 TS 并运行测试套件,验证注册/生命周期/命令/SSE/6 类频道 | 仅需 Node + npm |
| 2. 冒烟测试 | node test-sse-consumer.js |
快速确认插件能加载并正确注册 service 与 command | 需要先 npm run build 产出 dist/ |
| 3. 容器单元测试 | ./test-container.sh |
在干净 Docker 中跑单测/集成测试(文档注明有 --full 模式) |
Docker |
| 4. 真实 OpenClaw E2E(Docker) | ./test-e2e.sh |
在官方 OpenClaw 镜像中走真实安装 + 网关 + mock worker 全链路 | Docker |
| 5. 手动 E2E(真实环境) | 见"手动 E2E"一节 | 真实 worker + 真实消息频道验证投递 | 本机部署 |
其中第 4 层是"最全面的测试",必须全部 16 项检查通过才算成功(后文会逐项拆解这 16 项检查来自哪里)。
说明:TESTING.md 中还提到
./test-container.sh容器测试入口,但在当前仓库的openclaw/目录清单中未见该脚本文件;实际可用入口以test-e2e.sh、e2e-verify.sh、test-sse-consumer.js为准。
2.1 单元测试:最快反馈回路
cd openclaw
npm test # 编译 TypeScript 后运行测试(文档口径为 17 个用例)
从 index.test.ts 的结构看,测试用例至少覆盖以下断言面:
- 加载注册:
claudeMemPlugin(api)执行后,断言 service id 为claude-mem-observation-feed、命令claude_mem_feed与claude_mem_status已注册,且session_start、after_compaction、before_agent_start、before_prompt_build、tool_result_persist、agent_end、gateway_start等事件处理器全部挂上,日志中出现 "plugin loaded"; - 服务启停:feed 未启用时日志应含
feed disabled;启用但缺channel或缺to时应含misconfigured;stop()后应含feed stopped; - 命令处理:
claude_mem_feed无配置时返回 "not configured",带配置时返回Enabled / Channel / Target / Connection四行状态,on/off参数返回"请求持久化到配置"的提示。
2.2 冒烟测试:test-sse-consumer.js
node test-sse-consumer.js
test-sse-consumer.js 直接 import 编译产物 ./dist/index.js,用一个最小 mock API 调起插件,然后逐项检查:
- 注册了 id 为
claude-mem-observation-feed的 service; - 注册了 feed / status 命令;
before_agent_start、tool_result_persist、agent_end、gateway_start事件均有处理器;- 日志包含 "plugin loaded"。
任何一项失败即以退出码 1 终止。它是改动插件后"30 秒内确认没把注册逻辑改坏"的兜底手段。
三、Docker E2E:一键安装真实 OpenClaw 并跑 16 项检查
3.1 三种运行模式
cd openclaw
# 自动化测试(构建镜像 → 安装插件到真实 OpenClaw → 验证全部检查项)
./test-e2e.sh
# 交互式 shell(手工探索用)
./test-e2e.sh --interactive
# 只构建镜像
./test-e2e.sh --build-only
test-e2e.sh 的逻辑很简单:用 Dockerfile.e2e 构建名为 openclaw-claude-mem-e2e 的镜像,然后按参数走三条分支之一:--build-only 构建完即退出;--interactive 以 docker run -it 落入 bash 并打印容器内常用命令清单;默认则 docker run --rm 直接执行镜像内置的 CMD(即 /app/e2e-verify.sh)。
3.2 镜像构建过程(Dockerfile.e2e 逐步拆解)
Dockerfile.e2e 完整复刻了一次"真实用户安装"的链路:
- 基础镜像:
FROM ghcr.io/openclaw/openclaw:main,即以官方 OpenClaw 镜像为底座,并apt-get安装curl、全局安装typescript@5; - 构建插件:把
package.json、tsconfig.json、openclaw.plugin.json与src/拷入/tmp/claude-mem-plugin,执行npm install && npx tsc编译; - 组装可安装包:在
/tmp/claude-mem-installable/生成精简package.json(name: "claude-mem"、main: "dist/index.js"、openclaw.extensions: ["./dist/index.js"]),并拷入编译产物与openclaw.plugin.json; - 真实安装与启用:以 node 用户执行
与真实用户经node openclaw.mjs plugins install /tmp/claude-mem-installable node openclaw.mjs plugins enable claude-memopenclaw plugins install的路径完全一致; - 注入验证资产:把
e2e-verify.sh与 mock worker(dist/mock-worker.js)拷贝到/app/,CMD默认执行验证脚本。
3.3 e2e-verify.sh:16 项检查的四个阶段
e2e-verify.sh 是自动化的核心,带 set -euo pipefail 与 PASS/FAIL/TOTAL 计数器,任何一项失败都会打印完整 gateway 日志并以退出码 1 结束。四个阶段合计正好 16 项检查,与文档"All 16 checks must pass"对应:
Phase 1:插件发现(4 项)
node /app/openclaw.mjs plugins list # ① 列表中出现 claude-mem
node /app/openclaw.mjs plugins info claude-mem # ② info 显示详情
# ③ list/info 中出现 "enabled" 或 "loaded"
node /app/openclaw.mjs plugins doctor # ④ doctor 报告无问题
Phase 2:插件文件(4 项):先定位扩展目录(优先 /home/node/.openclaw/extensions/openclaw-plugin,回退到 claude-mem 子目录,再回退到 find 定位),断言目录存在,以及 openclaw.plugin.json、dist/index.js、package.json 三个文件齐全。
Phase 3:mock worker + SSE(2 项):后台启动 node /app/mock-worker.js,最多轮询 10 次(每次 0.5s)等待 http://localhost:37777/health 就绪;随后 curl --max-time 2 http://localhost:37777/stream,断言流中返回 connected 事件。
Phase 4:网关启动(6 项):写入网关与插件配置(下节给出),以
OPENCLAW_GATEWAY_TOKEN=e2e-test-token timeout 15 node /app/openclaw.mjs gateway \
--allow-unconfigured --verbose --token e2e-test-token
启动网关 15 秒,然后断言:进程存活、日志提及 claude-mem、出现 "plugin loaded"/"v1.0.0" 加载日志、出现 observation feed 活动、出现 SSE 连接活动。
3.4 E2E 使用的完整配置
Docker E2E 中写入 /home/node/.openclaw/openclaw.json 的配置(与 e2e-verify.sh 中的 EOFCONFIG 块一致):
{
"gateway": {
"mode": "local",
"auth": { "mode": "token", "token": "e2e-test-token" }
},
"plugins": {
"slots": { "memory": "claude-mem" },
"entries": {
"claude-mem": {
"enabled": true,
"config": {
"workerPort": 37777,
"observationFeed": {
"enabled": true,
"channel": "telegram",
"to": "test-chat-id-12345"
}
}
}
}
}
}
这里有两个关键点:plugins.slots.memory 必须指向 claude-mem(否则会与默认 memory-core 冲突,见排障一节);config 字段即插件的 pluginConfig,插件入口函数从这里读取 workerPort 与 observationFeed(源码见 src/index.ts 第 620-625 行)。
四、人工 E2E:交互式容器手工走查
./test-e2e.sh --interactive 会把你放入一个已预装插件的完整 OpenClaw 容器,按 TESTING.md 的清单逐步验证:
4.1 验证插件安装
node openclaw.mjs plugins list
node openclaw.mjs plugins info claude-mem
node openclaw.mjs plugins doctor
预期:claude-mem 出现在列表且状态为 enabled/loaded;info 显示 source 位于 /home/node/.openclaw/extensions/claude-mem/;doctor 报告无问题。
4.2 检查插件文件
ls -la /home/node/.openclaw/extensions/claude-mem/
cat /home/node/.openclaw/extensions/claude-mem/openclaw.plugin.json
cat /home/node/.openclaw/extensions/claude-mem/package.json
预期:dist/index.js 存在(编译产物);openclaw.plugin.json 含 "id": "claude-mem" 与 "kind": "memory";package.json 的 openclaw.extensions 指向 ./dist/index.js——与 openclaw.plugin.json 和 package.json 的源定义一致。
4.3 启动 mock worker 并验证 SSE
node /app/mock-worker.js &
curl -s http://localhost:37777/health
# → {"status":"ok"}
curl -s --max-time 3 http://localhost:37777/stream
# → data: {"type":"connected","message":"Mock worker SSE stream"}
# → data: {"type":"new_observation","observation":{...}}
mock worker 模拟了真实 worker 的 /health 与 /stream 端点;new_observation 事件的 payload 结构对应源码中的 ObservationSSEPayload 接口(含 id、memory_session_id、title、subtitle、narrative、facts、concepts、project 等字段)。
4.4 配置并启动网关
按 3.4 节的 JSON 写入 openclaw.json 后:
node openclaw.mjs gateway --allow-unconfigured --verbose --token e2e-test-token
网关日志预期(这些字符串正是插件源码中的固定日志):
[claude-mem] OpenClaw plugin loaded — v1.0.0(插件加载,见 src/index.ts 第 1135 行)[claude-mem] Observation feed starting — channel: telegram, target: test-chat-id-12345(见 第 905 行)[claude-mem] Connecting to SSE stream at http://localhost:37777/stream[claude-mem] Connected to SSE stream
4.5 可选:跑自动化验证
在容器第二个 shell(或停掉网关后)执行:
/bin/bash /app/e2e-verify.sh
五、配置项全解:configSchema 与源码默认值对照
observationFeed 相关的测试配置背后,是一份完整的配置契约。openclaw.plugin.json 的 configSchema(additionalProperties: false,即不允许未声明字段)与 源码类型定义 ClaudeMemPluginConfig 共同给出全部可选项:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
workerPort |
number | 37777 |
Claude-Mem worker 服务端口(源码常量 DEFAULT_WORKER_PORT) |
workerHost |
string | 127.0.0.1 |
worker 主机名;网关跑在 Docker 内而 worker 跑在宿主机时,须设为 host.docker.internal |
project |
string | openclaw |
记忆库中用于隔离观测记录的项目名 |
syncMemoryFile |
boolean | true |
通过 before_prompt_build 钩子把观测上下文注入 agent 系统提示;false 时完全不注入 |
syncMemoryFileExclude |
string[] | [] |
排除自动注入的 agent ID 列表(观测仍会记录,仅跳过提示注入) |
observationFeed.enabled |
boolean | false |
是否开启实时观测流投递 |
observationFeed.channel |
string | 无 | 频道:telegram、discord、signal、slack、whatsapp、line |
observationFeed.to |
string | 无 | 目标会话/用户 ID,缺省或 channel 缺省都会触发 misconfigured 警告 |
observationFeed.botToken |
string | 无 | 可选:专用 Telegram bot token,绕过网关频道直连发送 |
observationFeed.emojis.* |
object | 见下 | 流消息的 emoji 个性化 |
关于频道支持,源码 CHANNEL_SEND_MAP 定义了六个频道的命名空间与发送函数映射(sendMessageTelegram、sendMessageDiscord 等),与文档列出的 telegram、discord、signal、slack、whatsapp、line 一致;其中 WhatsApp 会额外附带 { verbose: false } 选项。
emoji 配置(observationFeed.emojis)支持 primary(主网关,默认龙虾 emoji)、claudeCode(Claude Code 会话,默认键盘 emoji)、claudeCodeLabel(默认 "Claude Code Session")、default(兜底)、agents(按 agent ID 钉住指定 emoji)。未钉住的 agent 会由 poolEmojiForAgent 对 agentId 做字符串哈希后从 20 个 emoji 池中取模分配,保证同一 agent 的 emoji 稳定一致。
流消息的排版由 formatObservationMessage 决定:security_alert、security_note、sensitive、bugfix、decision 五类走"详细"格式(副标题截断 500 字符、含 Narrative/Facts/Concepts 段、整体上限 2200 字符),其余走"紧凑"格式(副标题 260 字符、整体上限 900 字符)。这就是最终推送到聊天频道的那条 🧠 Claude-Mem Observation 消息的成型逻辑。
六、手动 E2E:真实 OpenClaw + 真实 Worker
当需要验证"真实消息真的送到了"时,按 TESTING.md 的手动流程操作:
前置条件:OpenClaw 网关已安装配置;Claude-Mem worker 运行在 37777 端口;插件已构建。
1. 构建并安装插件
cd openclaw && npm run build
# 从 openclaw/ 目录安装
openclaw plugins install .
# 启用
openclaw plugins enable claude-mem
2. 配置:编辑 ~/.openclaw/openclaw.json,加入插件条目(workerPort + observationFeed 结构与 3.4 节相同,to 换成真实 YOUR_CHAT_ID)。
3. 重启网关
openclaw restart
日志中应看到 [claude-mem] OpenClaw plugin loaded — v1.0.0 与 [claude-mem] Connected to SSE stream。
4. 触发一次观测:启用 claude-mem 的 Claude Code 会话中执行任意操作,worker 会发出 new_observation SSE 事件。插件端的 connectToSSEStream 收到该事件后解析 observation 字段,经 formatObservationMessage 排版,再由 sendToChannel 投递到目标频道。
5. 验证投递:目标消息频道应收到形如:
🧠 Claude-Mem Observation
**Observation Title**
Optional subtitle
七、可靠性机制:从源码看 SSE 客户端如何抗抖动
E2E 之所以能只依赖"日志出现特定字符串"就判定通过,是因为插件对网络故障有明确的自恢复机制,值得结合排障一起理解:
- 指数退避重连:SSE 断开后从 1 秒起等比重试、上限 30 秒(第 536-537、613-614 行);日志会出现
SSE stream error: fetch failed. Reconnecting in 1s——这正是排障一节"Worker not running"症状的由来。 - 熔断器:对 worker 的 REST 调用(
/api/sessions/init、/api/sessions/observations、/api/sessions/summarize等)统一走带熔断的workerPost/workerGetText:连续 3 次失败即打开熔断、30 秒内不再发起请求(CIRCUIT_BREAKER_THRESHOLD / CIRCUIT_BREAKER_COOLDOWN_MS),半开态只放行单个探测请求;恢复后日志打印Worker connection restored — circuit closed。 - SSE 缓冲上限:解码缓冲超过 1MB(
MAX_SSE_BUFFER_SIZE)即清空并告警,防止异常帧撑爆内存(第 571-574 行)。 - 上下文缓存:
before_prompt_build注入的上下文有 60 秒 TTL 缓存(CONTEXT_CACHE_TTL_MS),按项目组合为 key 请求/api/context/inject,避免每条提示都打 worker。 - 命令面:插件还注册了
claude-mem-search(/api/search/observations)、claude-mem-recent(/api/context/recent)、claude-mem-timeline(/api/timeline/by-query)与claude_mem_status(/api/health+ 本地会话/连接状态),可用于在真实渠道里人工抽查 worker 数据链路。
八、排障手册(症状 → 根因 → 修复)
以下条目完整继承自 TESTING.md,并标注了症状字符串在源码中的对应位置:
api.log is not a function
插件构建时对着错误的 API 编译。确认 src/index.ts 使用的是 api.logger.info() 而非 api.log(),然后 npm run build 重新构建。
Worker not running
- 症状:
SSE stream error: fetch failed. Reconnecting in 1s(来自 connectToSSEStream 的 catch 分支) - 修复:启动 worker,文档给出的命令为
cd /path/to/claude-mem && npm run build-and-sync
Port mismatch(端口错配)
- 修复:确保配置中
workerPort与 worker 实际监听端口一致(默认 37777,即源码中的DEFAULT_WORKER_PORT)。若网关在容器内、worker 在宿主机,还要检查workerHost是否应为host.docker.internal。
Channel not configured(频道未配置)
- 症状:
Observation feed misconfigured — channel or target missing(对应 service.start 中的校验) - 修复:
observationFeed中channel与to二者必须同时给出。
Unknown channel type(未知频道)
- 修复:只能使用
telegram、discord、signal、slack、whatsapp、line之一(见CHANNEL_SEND_MAP的键集合)。
Feed disabled(流被禁用)
- 症状:
Observation feed disabled(service.start 开头) - 修复:设置
observationFeed.enabled: true。注意claude_mem_feed命令的on/off参数只记录请求,不会持久化,仍需改配置。
Messages not arriving(消息没到)
- 确认目标频道中的 bot/集成已配置;
- 核对目标 ID(
to)是否正确; - 在日志中查找
Failed to send to <channel>(来自 sendToChannel 的 catch); - 用 OpenClaw 内置工具单独测试该频道可用性。
Memory slot conflict(记忆槽位冲突)
- 症状:
plugin disabled (memory slot set to "memory-core") - 修复:在 plugins 配置中加入
"slots": { "memory": "claude-mem" },让 claude-mem 抢占 memory 槽位(E2E 配置 3.4 节即采用了此写法)。
九、小结
claude-mem 的 OpenClaw 插件测试体系可以概括为:npm test 保注册逻辑、test-sse-consumer.js 保加载产物、./test-e2e.sh 保真实安装链路(16 项检查全过)、交互式容器与手动 E2E 保人工走查与真实投递。每一层的判定依据——服务 id、命令名、事件处理器、固定日志字符串、/health 与 /stream 响应——都能在 src/index.ts、src/index.test.ts 与 e2e-verify.sh 中逐条对上。改动插件后,建议按"单测 → 冒烟 → Docker E2E → 手动真实环境"的顺序逐层推进,任何一层失败都能借助排障手册与源码行级定位快速收敛。更多 OpenClaw 集成背景可参考仓库中的 OpenClaw 集成文档。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00