diff --git a/DEV-LEDGER.md b/DEV-LEDGER.md index 3289be3..a4a7b53 100644 --- a/DEV-LEDGER.md +++ b/DEV-LEDGER.md @@ -152,6 +152,7 @@ One branch `codex/extractor-eval-loop` for Day 1-5, a second `codex/retrieval-ha ## Session Log +- **2026-04-12 Codex (branch `codex/openclaw-capture-plugin`, polish pass)** tightened the OpenClaw capture plugin to use `before_agent_reply.cleanedBody` instead of the raw prompt-build input, which should prevent Discord wrapper metadata from being stored as the interaction prompt. Added `agent_end` cleanup and updated plugin docs. A fresh post-restart user turn is still needed to verify prompt cleanliness on Dalidou. - **2026-04-12 Codex (branch `codex/openclaw-capture-plugin`)** added a minimal external OpenClaw plugin at `openclaw-plugins/atocore-capture/` that mirrors Claude Code capture semantics: user-triggered assistant turns are POSTed to AtoCore `/interactions` with `client="openclaw"` and `reinforce=true`, fail-open, no extraction in-path. For live verification, temporarily added the local plugin load path to OpenClaw config and restarted the gateway so the plugin can load. Branch truth is ready; end-to-end verification still needs one fresh post-restart OpenClaw user turn to confirm new `client=openclaw` interactions appear on Dalidou. - **2026-04-12 Claude** Batch 3 (R9 fix): `144dbbd..e5e9a99`. Trust hierarchy for project attribution — interaction scope always wins when set, model project only used for unscoped interactions + registered check. 7 case tests (A-G) cover every combination. Harness 17/18 (no regression). Tests 286->290. Before: wrong registered project could silently override interaction scope. After: interaction.project is the strongest signal; model project is only a fallback for unscoped captures. Not yet guaranteed: nothing prevents the *same* project's model output from being semantically wrong within that project. R9 marked fixed. diff --git a/openclaw-plugins/atocore-capture/README.md b/openclaw-plugins/atocore-capture/README.md index f24c187..a7212bf 100644 --- a/openclaw-plugins/atocore-capture/README.md +++ b/openclaw-plugins/atocore-capture/README.md @@ -3,6 +3,7 @@ Minimal OpenClaw plugin that mirrors Claude Code's `capture_stop.py` behavior: - watches user-triggered assistant turns +- uses OpenClaw's cleaned user message (`before_agent_reply.cleanedBody`) so AtoCore stores the real prompt instead of the full inbound wrapper - POSTs `prompt` + `response` to `POST /interactions` - sets `client="openclaw"` - sets `reinforce=true` @@ -25,5 +26,6 @@ If `baseUrl` is omitted, the plugin uses `ATOCORE_BASE_URL` or defaults to `http ## Notes - Project detection is intentionally left empty for now. Unscoped capture is acceptable because AtoCore's extraction pipeline handles unscoped interactions. +- Prompt cleaning is done inside the plugin by reading OpenClaw's finalized cleaned message body instead of the raw prompt-build input. - Extraction is **not** part of the capture path. This plugin only records interactions and lets AtoCore reinforcement run automatically. - The plugin captures only user-triggered turns, not heartbeats or system-only runs. diff --git a/openclaw-plugins/atocore-capture/index.js b/openclaw-plugins/atocore-capture/index.js index dd27258..bbda756 100644 --- a/openclaw-plugins/atocore-capture/index.js +++ b/openclaw-plugins/atocore-capture/index.js @@ -46,11 +46,11 @@ export default definePluginEntry({ const logger = api.logger; const pendingBySession = new Map(); - api.on("before_agent_start", async (event, ctx) => { + api.on("before_agent_reply", async (event, ctx) => { if (ctx?.trigger && ctx.trigger !== "user") return; const config = api.getConfig?.() || {}; const minPromptLength = Number(config.minPromptLength || DEFAULT_MIN_PROMPT_LENGTH); - const prompt = trimText(event?.prompt || ""); + const prompt = trimText(event?.cleanedBody || ""); if (!shouldCapturePrompt(prompt, minPromptLength)) { pendingBySession.delete(ctx.sessionId); return; @@ -69,7 +69,10 @@ export default definePluginEntry({ if (!pending) return; const assistantTexts = Array.isArray(event?.assistantTexts) ? event.assistantTexts : []; - const response = truncateResponse(trimText(assistantTexts.join("\n\n")), Number((api.getConfig?.() || {}).maxResponseLength || DEFAULT_MAX_RESPONSE_LENGTH)); + const response = truncateResponse( + trimText(assistantTexts.join("\n\n")), + Number((api.getConfig?.() || {}).maxResponseLength || DEFAULT_MAX_RESPONSE_LENGTH) + ); if (!response) return; const config = api.getConfig?.() || {}; @@ -87,6 +90,10 @@ export default definePluginEntry({ pendingBySession.delete(ctx.sessionId); }); + api.on("agent_end", async (event) => { + if (event?.sessionId) pendingBySession.delete(event.sessionId); + }); + api.on("session_end", async (event) => { if (event?.sessionId) pendingBySession.delete(event.sessionId); });