@ocdb/plugin
v0.1.5
Published
openclaw-debug-toolkit 的 OpenClaw plugin bundle。安装后随 OpenClaw gateway 启动,自动启动 Debug Service(默认 `:7887`)。
Readme
@ocdb/plugin
openclaw-debug-toolkit 的 OpenClaw plugin bundle。安装后随 OpenClaw gateway 启动,自动启动 Debug Service(默认 :7887)。
安装
openclaw plugins install @ocdb/plugin或在本地开发时:
openclaw plugins install /path/to/openclaw-debug-toolkit/packages/plugin快速验证
安装并重启 gateway 后:
curl http://localhost:7887/health
# → {"status":"ok"}组件说明
plugin bundle 内部由 6 个组件组成,通过 createPlugin() 统一组装。
Capture Hooks
挂载在 OpenClaw 的 7 个生命周期 hook 上,将每次 Agent 执行的关键状态写入 CheckpointStore。
| Hook | 产出 | 说明 |
|------|------|------|
| before_agent_run | DebugRun 记录 | run 开始,创建元数据记录 |
| before_prompt_build | Checkpoint(before_prompt_build) | 记录当时的 messages + runtimeContext |
| before_model_resolve | — | 记录 model 配置(附在 llm_input checkpoint) |
| llm_input | Checkpoint(before_model_call) | 发给模型的最终 prompt + system prompt |
| llm_output | Checkpoint(after_model_call) | 模型输出 + finishReason + token usage |
| before_tool_call | Checkpoint(before_tool_call) | 工具调用前快照;fork 模式下执行 cassette 拦截 |
| after_tool_call | Checkpoint(after_tool_call) | 工具结果;更新 ToolCallRecord |
| agent_end | Checkpoint(agent_end) | 更新 run status + endedAt |
fork 模式下的额外行为:
before_prompt_build— 调用DebugContextEngine.assemble(),注入历史 messages 和调试上下文before_tool_call— 调用ToolIntercept.check(),按 replayPolicy 决定是否跳过真实执行llm_input— 若ReplayProvider.isReplayMode()为 true,返回历史 LLM 输出(--captured模式)
CheckpointStore
本地持久化存储,内存 LRU + 磁盘 JSONL 双层设计。
存储位置: ~/.ocdt/runs/<run-id>/
run.json — DebugRun 元数据(不含 checkpoints 和 toolLedger 数组)
checkpoints.jsonl — 追加写,每行一个 Checkpoint
tool-ledger.jsonl — 追加写(预留,目前 ledger 存于 checkpoint 快照中)API:
const store = new CheckpointStore({ storeDir: '~/.ocdt/runs', maxRuns: 100 })
await store.init() // 初始化目录,加载磁盘数据到内存索引
await store.saveRun(run) // 保存/更新 run 元数据
await store.updateRun(id, patch) // 局部更新(如更新 status)
await store.appendCheckpoint(ckp) // 追加 checkpoint(append-only)
await store.getRun(id) // → DebugRun | undefined
await store.getCheckpoints(id) // → Checkpoint[]
await store.listRuns(limit?) // → DebugRun[](按 startedAt 降序)maxRuns 控制内存中保留的最近 run 数量;超出时按 startedAt 淘汰最旧的记录(磁盘文件保留,不删除)。
HTTP Debug Service
plugin 内嵌的轻量 HTTP server,使用 Node.js 内置 http 模块,无框架依赖。
默认端口: 7887(可通过配置修改)
端点
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /health | 健康检查,返回 {"status":"ok"} |
| GET | /runs | 列出最近 runs(支持 ?status= 和 ?limit= 参数) |
| GET | /runs/:id | 获取单个 run 元数据 |
| GET | /runs/:id/checkpoints | 获取 run 的所有 checkpoints |
| GET | /runs/:id/diff/:id2 | 对比两次 run |
| POST | /fork | 从 checkpoint 创建 fork run |
| POST | /import | 导入 trajectory JSONL(当前版本仅计数) |
示例
# 获取最近失败的 run
curl 'http://localhost:7887/runs?status=failed&limit=5' | jq .
# 查看 checkpoints
curl http://localhost:7887/runs/run-abc123/checkpoints | jq '.checkpoints[].boundary'
# 发起 fork
curl -X POST http://localhost:7887/fork \
-H 'Content-Type: application/json' \
-d '{
"sourceRunId": "run-abc123",
"sourceCheckpointId": "ckp-xxx",
"modelOverride": { "mode": "captured" },
"toolPolicyMap": { "execute_sql": "reuse_result", "save_report": "forbid_replay" }
}'ReplayProvider
管理 LLM 输出的捕获和重放。在 --captured 模式下,按 runId + callIndex 顺序返回历史输出,实现完全确定性重放。
// 正常 live run 时:capture.ts 的 llmOutput hook 调用
replayProvider.storeCapture(runId, checkpointId, callIndex, output)
// fork run 启动时(mode=captured):
replayProvider.enableReplayMode(forkRunId, sourceRunId, sourceCheckpointId)
// fork run 的 before_model_resolve hook 中调用:
const output = replayProvider.getNextCapturedOutput(forkRunId)
// → 返回 { content, finishReason, usage } 或 undefinedDebugContextEngine
管理 fork 模式下的消息注入。在 before_prompt_build hook 中调用,将历史 messages 替换进新 session。
// fork 创建时注册
contextEngine.registerFork(forkRunId, historicMessages, forkConfig)
// before_prompt_build hook 中调用
const result = contextEngine.assemble(runId, currentMessages)
// → { messages, systemPromptAddition? }
// fork 模式:messages = 历史消息,systemPromptAddition = [DEBUG REPLAY CONTEXT]...
// live 模式:messages = currentMessages,systemPromptAddition = undefined
// fork run 结束时清理
contextEngine.deregisterFork(forkRunId)systemPromptAddition 的内容示例:
[DEBUG REPLAY CONTEXT]
sourceRunId: run-abc123
sourceCheckpointId: ckp-xxx
mode: fork
promptVersion: v2
toolPolicies: {"*":"reuse_result","save_report":"forbid_replay"}ForkEngine
准备和管理 fork session。
const engine = new ForkEngine(store)
// 发起 fork:创建 fork DebugRun,注册活跃 session
const forkRun = await engine.prepareFork(forkConfig)
// forkRun.mode === 'fork'
// forkRun.parentRunId === forkConfig.sourceRunId
// 查询活跃 fork session(供 hooks 使用)
const session = engine.getForkSession(forkRun.runId)
// → { forkRun, config } | undefined
// fork run 结束时清理
engine.completeFork(forkRun.runId)DiffEngine
对比两次 run 的差异,覆盖 status、checkpoint 数量、工具调用 args 和 result。
const engine = new DiffEngine()
const diff = engine.diff(runA, checkpointsA, runB, checkpointsB)
// → RunDiff {
// runA, runB,
// status: { changed, a, b },
// checkpointCount: { changed, a, b },
// toolCallCount: { changed, a, b },
// toolCallDiffs: ToolCallDiff[],
// summary: string
// }Tool Cassette(createToolIntercept)
before_tool_call hook 中的拦截逻辑,根据 ForkConfig 中的 toolPolicyMap 决定每个工具调用的处理方式。
const intercept = createToolIntercept()
// fork 创建时注册(含历史 tool ledger 供 reuse_result 使用)
intercept.registerFork(forkRunId, forkConfig, historicToolLedger)
// before_tool_call hook 中调用
const result = await intercept.check(runId, toolCallId, toolName, args)
// → undefined (re_execute: 让工具正常执行)
// → { intercepted, result } (reuse_result / mock_result: 直接返回数据)
// → { intercepted, error } (forbid_replay: 报错阻止执行)策略优先级: 具名工具 > '*' 通配符 > 默认 re_execute
createPlugin() API
import { createPlugin } from '@ocdb/plugin'
const plugin = createPlugin({
port?: number, // Debug Service 端口,默认 7887
storeDir?: string, // 存储目录,默认 ~/.ocdt/runs
maxRuns?: number, // 内存保留数量,默认 100
})
// 返回值供 OpenClaw 注册:
plugin.name // 'openclaw-debug-toolkit'
plugin.version // '0.1.0'
plugin.hooks // { beforeAgentRun, beforePromptBuild, llmInput, ... }
// 内部组件(测试 / 扩展用):
plugin._store
plugin._server
plugin._replayProvider
plugin._contextEngine
plugin._toolIntercept
plugin._forkEngine
await plugin._ready // 等待 store + server 启动完成OpenClaw 接入配置
安装插件后,需在 OpenClaw gateway 配置文件中启用 conversation access(否则 LLM 输入/输出 hook 无法工作):
{
"plugins": {
"entries": {
"openclaw-debug-toolkit": {
"hooks": {
"allowConversationAccess": true
}
}
}
}
}然后重启 gateway:
openclaw gateway restart插件入口由 @ocdb/plugin/openclaw 子路径导出,OpenClaw gateway 在运行时加载该入口(而非 @ocdb/plugin 默认入口)。definePluginEntry 来自 openclaw peer dependency,需在 gateway 环境中提供。
对接真实 OpenClaw SDK
packages/plugin/src/openclaw.d.ts 现为内部适配器类型文件(非 SDK 类型)。src/openclaw-entry.ts 通过 definePluginEntry 挂载真实 hook,将 OpenClaw 原生事件字段映射到内部适配器类型后交给 capture.ts 处理。
若 openclaw 包的实际事件字段与 openclaw-entry.ts 中的映射有出入,按实际字段调整 openclaw-entry.ts 中的 event.* 取值逻辑即可,无需改动 capture.ts。
