首页
/ claude-mem OpenClaw 插件测试指南:从单元测试到 Docker E2E 的四层验证体系

claude-mem OpenClaw 插件测试指南:从单元测试到 Docker E2E 的四层验证体系

2026-09-06 15:36:24作者:郜逊炳

本文基于 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-planskills/do
  • 通过 configSchema 声明了完整的插件配置契约(workerPortworkerHostprojectsyncMemoryFileobservationFeed 等),后文测试配置即以此为准。

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.she2e-verify.shtest-sse-consumer.js 为准。

2.1 单元测试:最快反馈回路

cd openclaw
npm test    # 编译 TypeScript 后运行测试(文档口径为 17 个用例)

index.test.ts 的结构看,测试用例至少覆盖以下断言面:

  • 加载注册claudeMemPlugin(api) 执行后,断言 service id 为 claude-mem-observation-feed、命令 claude_mem_feedclaude_mem_status 已注册,且 session_startafter_compactionbefore_agent_startbefore_prompt_buildtool_result_persistagent_endgateway_start 等事件处理器全部挂上,日志中出现 "plugin loaded";
  • 服务启停:feed 未启用时日志应含 feed disabled;启用但缺 channel 或缺 to 时应含 misconfiguredstop() 后应含 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_starttool_result_persistagent_endgateway_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 构建完即退出;--interactivedocker run -it 落入 bash 并打印容器内常用命令清单;默认则 docker run --rm 直接执行镜像内置的 CMD(即 /app/e2e-verify.sh)。

3.2 镜像构建过程(Dockerfile.e2e 逐步拆解)

Dockerfile.e2e 完整复刻了一次"真实用户安装"的链路:

  1. 基础镜像FROM ghcr.io/openclaw/openclaw:main,即以官方 OpenClaw 镜像为底座,并 apt-get 安装 curl、全局安装 typescript@5
  2. 构建插件:把 package.jsontsconfig.jsonopenclaw.plugin.jsonsrc/ 拷入 /tmp/claude-mem-plugin,执行 npm install && npx tsc 编译;
  3. 组装可安装包:在 /tmp/claude-mem-installable/ 生成精简 package.jsonname: "claude-mem"main: "dist/index.js"openclaw.extensions: ["./dist/index.js"]),并拷入编译产物与 openclaw.plugin.json
  4. 真实安装与启用:以 node 用户执行
    node openclaw.mjs plugins install /tmp/claude-mem-installable
    node openclaw.mjs plugins enable claude-mem
    
    与真实用户经 openclaw plugins install 的路径完全一致;
  5. 注入验证资产:把 e2e-verify.sh 与 mock worker(dist/mock-worker.js)拷贝到 /app/CMD 默认执行验证脚本。

3.3 e2e-verify.sh:16 项检查的四个阶段

e2e-verify.sh 是自动化的核心,带 set -euo pipefailPASS/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.jsondist/index.jspackage.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,插件入口函数从这里读取 workerPortobservationFeed(源码见 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.jsonopenclaw.extensions 指向 ./dist/index.js——与 openclaw.plugin.jsonpackage.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 接口(含 idmemory_session_idtitlesubtitlenarrativefactsconceptsproject 等字段)。

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.jsonconfigSchemaadditionalProperties: 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 频道:telegramdiscordsignalslackwhatsappline
observationFeed.to string 目标会话/用户 ID,缺省或 channel 缺省都会触发 misconfigured 警告
observationFeed.botToken string 可选:专用 Telegram bot token,绕过网关频道直连发送
observationFeed.emojis.* object 见下 流消息的 emoji 个性化

关于频道支持,源码 CHANNEL_SEND_MAP 定义了六个频道的命名空间与发送函数映射(sendMessageTelegramsendMessageDiscord 等),与文档列出的 telegramdiscordsignalslackwhatsappline 一致;其中 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_alertsecurity_notesensitivebugfixdecision 五类走"详细"格式(副标题截断 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 中的校验
  • 修复:observationFeedchannelto 二者必须同时给出。

Unknown channel type(未知频道)

  • 修复:只能使用 telegramdiscordsignalslackwhatsappline 之一(见 CHANNEL_SEND_MAP 的键集合)。

Feed disabled(流被禁用)

  • 症状:Observation feed disabledservice.start 开头
  • 修复:设置 observationFeed.enabled: true。注意 claude_mem_feed 命令的 on/off 参数只记录请求,不会持久化,仍需改配置。

Messages not arriving(消息没到)

  1. 确认目标频道中的 bot/集成已配置;
  2. 核对目标 ID(to)是否正确;
  3. 在日志中查找 Failed to send to <channel>(来自 sendToChannel 的 catch);
  4. 用 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.tssrc/index.test.tse2e-verify.sh 中逐条对上。改动插件后,建议按"单测 → 冒烟 → Docker E2E → 手动真实环境"的顺序逐层推进,任何一层失败都能借助排障手册与源码行级定位快速收敛。更多 OpenClaw 集成背景可参考仓库中的 OpenClaw 集成文档

登录后查看全文
热门项目推荐
相关项目推荐