Compare commits
4 Commits
main
...
codex/open
| Author | SHA1 | Date | |
|---|---|---|---|
| 500a29aeba | |||
| 0371739877 | |||
| f2ec5d43de | |||
| 72ca823206 |
@@ -152,6 +152,10 @@ One branch `codex/extractor-eval-loop` for Day 1-5, a second `codex/retrieval-ha
|
|||||||
|
|
||||||
## Session Log
|
## Session Log
|
||||||
|
|
||||||
|
- **2026-04-12 Codex (branch `codex/openclaw-capture-plugin`, verification close)** verified the final capture-plugin behavior on Dalidou after the `message_sending` reliability fix. New OpenClaw interactions now capture reliably and the stored prompt is clean human text instead of the Discord wrapper blob. Verified examples on Dalidou: `Final capture test` and `Yes, fix it, or I'll ask opus to do it`. The oldest two wrapper-heavy captures remain in history from earlier iterations, but new captures are clean.
|
||||||
|
- **2026-04-12 Codex (branch `codex/openclaw-capture-plugin`, polish pass 3)** changed turn pairing from `llm_output` to `message_sending`. The plugin now caches the human prompt at `before_dispatch` and posts to AtoCore only when OpenClaw emits the real outbound assistant message. This should restore reliability while keeping prompt cleanliness. Awaiting one more post-restart validation turn.
|
||||||
|
- **2026-04-12 Codex (branch `codex/openclaw-capture-plugin`, polish pass 2)** switched prompt capture from `before_agent_reply.cleanedBody` to `before_dispatch.body` / `content`, because the earlier path still stored Discord wrapper metadata. This should bind capture to the dispatch-stage human message instead of the prompt-builder artifact. Awaiting one more post-restart turn to verify on Dalidou.
|
||||||
|
- **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 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.
|
- **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.
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
Minimal OpenClaw plugin that mirrors Claude Code's `capture_stop.py` behavior:
|
Minimal OpenClaw plugin that mirrors Claude Code's `capture_stop.py` behavior:
|
||||||
|
|
||||||
- watches user-triggered assistant turns
|
- watches user-triggered assistant turns
|
||||||
|
- uses OpenClaw's dispatch-stage message body (`before_dispatch.body`) for the human prompt, then pairs it with the actual outbound assistant message on `message_sending`
|
||||||
- POSTs `prompt` + `response` to `POST /interactions`
|
- POSTs `prompt` + `response` to `POST /interactions`
|
||||||
- sets `client="openclaw"`
|
- sets `client="openclaw"`
|
||||||
- sets `reinforce=true`
|
- sets `reinforce=true`
|
||||||
@@ -25,5 +26,7 @@ If `baseUrl` is omitted, the plugin uses `ATOCORE_BASE_URL` or defaults to `http
|
|||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Project detection is intentionally left empty for now. Unscoped capture is acceptable because AtoCore's extraction pipeline handles unscoped interactions.
|
- 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 dispatch-stage message body instead of the raw prompt-build input.
|
||||||
|
- Turn pairing is done by caching the prompt on dispatch and posting only when OpenClaw emits the outbound assistant message, which is more reliable than pairing against raw model output events.
|
||||||
- Extraction is **not** part of the capture path. This plugin only records interactions and lets AtoCore reinforcement run automatically.
|
- 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.
|
- The plugin captures only user-triggered turns, not heartbeats or system-only runs.
|
||||||
|
|||||||
@@ -20,6 +20,30 @@ function shouldCapturePrompt(prompt, minLength) {
|
|||||||
return text.length >= minLength;
|
return text.length >= minLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildKeys(...values) {
|
||||||
|
return [...new Set(values.map((v) => trimText(v)).filter(Boolean))];
|
||||||
|
}
|
||||||
|
|
||||||
|
function rememberPending(store, keys, payload) {
|
||||||
|
for (const key of keys) store.set(key, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
function takePending(store, keys) {
|
||||||
|
for (const key of keys) {
|
||||||
|
const value = store.get(key);
|
||||||
|
if (value) {
|
||||||
|
for (const k of keys) store.delete(k);
|
||||||
|
store.delete(key);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearPending(store, keys) {
|
||||||
|
for (const key of keys) store.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
async function postInteraction(baseUrl, payload, logger) {
|
async function postInteraction(baseUrl, payload, logger) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/interactions`, {
|
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/interactions`, {
|
||||||
@@ -46,30 +70,34 @@ export default definePluginEntry({
|
|||||||
const logger = api.logger;
|
const logger = api.logger;
|
||||||
const pendingBySession = new Map();
|
const pendingBySession = new Map();
|
||||||
|
|
||||||
api.on("before_agent_start", async (event, ctx) => {
|
api.on("before_dispatch", async (event, ctx) => {
|
||||||
if (ctx?.trigger && ctx.trigger !== "user") return;
|
|
||||||
const config = api.getConfig?.() || {};
|
const config = api.getConfig?.() || {};
|
||||||
const minPromptLength = Number(config.minPromptLength || DEFAULT_MIN_PROMPT_LENGTH);
|
const minPromptLength = Number(config.minPromptLength || DEFAULT_MIN_PROMPT_LENGTH);
|
||||||
const prompt = trimText(event?.prompt || "");
|
const prompt = trimText(event?.body || event?.content || "");
|
||||||
|
const keys = buildKeys(ctx?.sessionKey, ctx?.sessionId, event?.sessionKey, event?.sessionId, ctx?.conversationId, event?.conversationId);
|
||||||
|
if (!keys.length) return;
|
||||||
if (!shouldCapturePrompt(prompt, minPromptLength)) {
|
if (!shouldCapturePrompt(prompt, minPromptLength)) {
|
||||||
pendingBySession.delete(ctx.sessionId);
|
clearPending(pendingBySession, keys);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pendingBySession.set(ctx.sessionId, {
|
rememberPending(pendingBySession, keys, {
|
||||||
prompt,
|
prompt,
|
||||||
sessionId: ctx.sessionId,
|
sessionId: trimText(ctx?.sessionId || event?.sessionId || ""),
|
||||||
sessionKey: ctx.sessionKey || "",
|
sessionKey: trimText(ctx?.sessionKey || event?.sessionKey || ""),
|
||||||
|
conversationId: trimText(ctx?.conversationId || event?.conversationId || ""),
|
||||||
project: ""
|
project: ""
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
api.on("llm_output", async (event, ctx) => {
|
api.on("message_sending", async (event, ctx) => {
|
||||||
if (ctx?.trigger && ctx.trigger !== "user") return;
|
const keys = buildKeys(ctx?.sessionKey, ctx?.sessionId, ctx?.conversationId);
|
||||||
const pending = pendingBySession.get(ctx.sessionId);
|
const pending = takePending(pendingBySession, keys);
|
||||||
if (!pending) return;
|
if (!pending) return;
|
||||||
|
|
||||||
const assistantTexts = Array.isArray(event?.assistantTexts) ? event.assistantTexts : [];
|
const response = truncateResponse(
|
||||||
const response = truncateResponse(trimText(assistantTexts.join("\n\n")), Number((api.getConfig?.() || {}).maxResponseLength || DEFAULT_MAX_RESPONSE_LENGTH));
|
trimText(event?.content || ""),
|
||||||
|
Number((api.getConfig?.() || {}).maxResponseLength || DEFAULT_MAX_RESPONSE_LENGTH)
|
||||||
|
);
|
||||||
if (!response) return;
|
if (!response) return;
|
||||||
|
|
||||||
const config = api.getConfig?.() || {};
|
const config = api.getConfig?.() || {};
|
||||||
@@ -78,17 +106,20 @@ export default definePluginEntry({
|
|||||||
prompt: pending.prompt,
|
prompt: pending.prompt,
|
||||||
response,
|
response,
|
||||||
client: "openclaw",
|
client: "openclaw",
|
||||||
session_id: pending.sessionKey || pending.sessionId,
|
session_id: pending.sessionKey || pending.sessionId || pending.conversationId,
|
||||||
project: pending.project || "",
|
project: pending.project || "",
|
||||||
reinforce: true
|
reinforce: true
|
||||||
};
|
};
|
||||||
|
|
||||||
await postInteraction(baseUrl, payload, logger);
|
await postInteraction(baseUrl, payload, logger);
|
||||||
pendingBySession.delete(ctx.sessionId);
|
});
|
||||||
|
|
||||||
|
api.on("agent_end", async (event) => {
|
||||||
|
clearPending(pendingBySession, buildKeys(event?.sessionKey, event?.sessionId));
|
||||||
});
|
});
|
||||||
|
|
||||||
api.on("session_end", async (event) => {
|
api.on("session_end", async (event) => {
|
||||||
if (event?.sessionId) pendingBySession.delete(event.sessionId);
|
clearPending(pendingBySession, buildKeys(event?.sessionKey, event?.sessionId));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user