feat: add OpenClaw AtoCore capture plugin
This commit is contained in:
29
openclaw-plugins/atocore-capture/README.md
Normal file
29
openclaw-plugins/atocore-capture/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# AtoCore Capture Plugin for OpenClaw
|
||||
|
||||
Minimal OpenClaw plugin that mirrors Claude Code's `capture_stop.py` behavior:
|
||||
|
||||
- watches user-triggered assistant turns
|
||||
- POSTs `prompt` + `response` to `POST /interactions`
|
||||
- sets `client="openclaw"`
|
||||
- sets `reinforce=true`
|
||||
- fails open on network or API errors
|
||||
|
||||
## Config
|
||||
|
||||
Optional plugin config:
|
||||
|
||||
```json
|
||||
{
|
||||
"baseUrl": "http://dalidou:8100",
|
||||
"minPromptLength": 15,
|
||||
"maxResponseLength": 50000
|
||||
}
|
||||
```
|
||||
|
||||
If `baseUrl` is omitted, the plugin uses `ATOCORE_BASE_URL` or defaults to `http://dalidou:8100`.
|
||||
|
||||
## Notes
|
||||
|
||||
- Project detection is intentionally left empty for now. Unscoped capture is acceptable because AtoCore's extraction pipeline handles unscoped interactions.
|
||||
- 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.
|
||||
94
openclaw-plugins/atocore-capture/index.js
Normal file
94
openclaw-plugins/atocore-capture/index.js
Normal file
@@ -0,0 +1,94 @@
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
|
||||
const DEFAULT_BASE_URL = process.env.ATOCORE_BASE_URL || "http://dalidou:8100";
|
||||
const DEFAULT_MIN_PROMPT_LENGTH = 15;
|
||||
const DEFAULT_MAX_RESPONSE_LENGTH = 50_000;
|
||||
|
||||
function trimText(value) {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
function truncateResponse(text, maxLength) {
|
||||
if (!text || text.length <= maxLength) return text;
|
||||
return `${text.slice(0, maxLength)}\n\n[truncated]`;
|
||||
}
|
||||
|
||||
function shouldCapturePrompt(prompt, minLength) {
|
||||
const text = trimText(prompt);
|
||||
if (!text) return false;
|
||||
if (text.startsWith("<")) return false;
|
||||
return text.length >= minLength;
|
||||
}
|
||||
|
||||
async function postInteraction(baseUrl, payload, logger) {
|
||||
try {
|
||||
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/interactions`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
signal: AbortSignal.timeout(10_000)
|
||||
});
|
||||
if (!res.ok) {
|
||||
logger?.debug?.("atocore_capture_post_failed", { status: res.status });
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger?.debug?.("atocore_capture_post_error", {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default definePluginEntry({
|
||||
register(api) {
|
||||
const logger = api.logger;
|
||||
const pendingBySession = new Map();
|
||||
|
||||
api.on("before_agent_start", 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 || "");
|
||||
if (!shouldCapturePrompt(prompt, minPromptLength)) {
|
||||
pendingBySession.delete(ctx.sessionId);
|
||||
return;
|
||||
}
|
||||
pendingBySession.set(ctx.sessionId, {
|
||||
prompt,
|
||||
sessionId: ctx.sessionId,
|
||||
sessionKey: ctx.sessionKey || "",
|
||||
project: ""
|
||||
});
|
||||
});
|
||||
|
||||
api.on("llm_output", async (event, ctx) => {
|
||||
if (ctx?.trigger && ctx.trigger !== "user") return;
|
||||
const pending = pendingBySession.get(ctx.sessionId);
|
||||
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));
|
||||
if (!response) return;
|
||||
|
||||
const config = api.getConfig?.() || {};
|
||||
const baseUrl = trimText(config.baseUrl) || DEFAULT_BASE_URL;
|
||||
const payload = {
|
||||
prompt: pending.prompt,
|
||||
response,
|
||||
client: "openclaw",
|
||||
session_id: pending.sessionKey || pending.sessionId,
|
||||
project: pending.project || "",
|
||||
reinforce: true
|
||||
};
|
||||
|
||||
await postInteraction(baseUrl, payload, logger);
|
||||
pendingBySession.delete(ctx.sessionId);
|
||||
});
|
||||
|
||||
api.on("session_end", async (event) => {
|
||||
if (event?.sessionId) pendingBySession.delete(event.sessionId);
|
||||
});
|
||||
}
|
||||
});
|
||||
29
openclaw-plugins/atocore-capture/openclaw.plugin.json
Normal file
29
openclaw-plugins/atocore-capture/openclaw.plugin.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"id": "atocore-capture",
|
||||
"name": "AtoCore Capture",
|
||||
"description": "Captures completed OpenClaw assistant turns to AtoCore interactions for reinforcement.",
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"baseUrl": {
|
||||
"type": "string",
|
||||
"description": "Override AtoCore base URL. Defaults to ATOCORE_BASE_URL or http://dalidou:8100"
|
||||
},
|
||||
"minPromptLength": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"description": "Minimum user prompt length required before capture"
|
||||
},
|
||||
"maxResponseLength": {
|
||||
"type": "integer",
|
||||
"minimum": 100,
|
||||
"description": "Maximum assistant response length to store"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"uiHints": {
|
||||
"category": "automation",
|
||||
"displayName": "AtoCore Capture"
|
||||
}
|
||||
}
|
||||
7
openclaw-plugins/atocore-capture/package.json
Normal file
7
openclaw-plugins/atocore-capture/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "@atomaste/atocore-openclaw-capture",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"description": "OpenClaw plugin that captures assistant turns to AtoCore interactions"
|
||||
}
|
||||
Reference in New Issue
Block a user