/** * AtoCore OpenClaw plugin — capture + pull. * * Two responsibilities: * * 1. CAPTURE (existing): On before_agent_start, buffer the user prompt. * On llm_output, POST prompt+response to AtoCore /interactions. * This is the "write" side — OpenClaw turns feed AtoCore's memory. * * 2. PULL (Phase 1 master brain): On before_prompt_build, call AtoCore * /context/build and inject the returned context via prependContext. * Every OpenClaw response is automatically grounded in what AtoCore * knows (project state, memories, relevant chunks). * * Fail-open throughout: AtoCore unreachable = no injection, no capture, * never blocks the agent. */ import { definePluginEntry } from "openclaw/plugin-sdk/core"; const BASE_URL = process.env.ATOCORE_BASE_URL || "http://dalidou:8100"; const MIN_LEN = 15; const MAX_RESP = 50000; const CONTEXT_TIMEOUT_MS = 6000; const CAPTURE_TIMEOUT_MS = 10000; function trim(v) { return typeof v === "string" ? v.trim() : ""; } function trunc(t, m) { return !t || t.length <= m ? t : t.slice(0, m) + "\n\n[truncated]"; } function detectProject(prompt) { const lower = (prompt || "").toLowerCase(); const hints = [ ["p04", "p04-gigabit"], ["gigabit", "p04-gigabit"], ["p05", "p05-interferometer"], ["interferometer", "p05-interferometer"], ["p06", "p06-polisher"], ["polisher", "p06-polisher"], ["fullum", "p06-polisher"], ["abb", "abb-space"], ["atomizer", "atomizer-v2"], ["atocore", "atocore"], ]; for (const [token, proj] of hints) { if (lower.includes(token)) return proj; } return ""; } export default definePluginEntry({ register(api) { const log = api.logger; let lastPrompt = null; // --- PULL: inject AtoCore context into every prompt --- api.on("before_prompt_build", async (event, ctx) => { if (process.env.ATOCORE_PULL_DISABLED === "1") return; const prompt = trim(event?.prompt || ""); if (prompt.length < MIN_LEN) return; const project = detectProject(prompt); try { const res = await fetch(BASE_URL.replace(/\/$/, "") + "/context/build", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt, project }), signal: AbortSignal.timeout(CONTEXT_TIMEOUT_MS), }); if (!res.ok) { log.info("atocore-pull:http_error", { status: res.status }); return; } const data = await res.json(); const contextPack = data.formatted_context || ""; if (!contextPack.trim()) return; log.info("atocore-pull:injected", { project: project || "(none)", chars: contextPack.length, }); return { prependContext: "--- AtoCore Context (auto-injected) ---\n" + contextPack + "\n--- End AtoCore Context ---\n", }; } catch (err) { log.info("atocore-pull:error", { error: String(err).slice(0, 200) }); } }); // --- CAPTURE: buffer user prompts on agent start --- api.on("before_agent_start", async (event, ctx) => { const prompt = trim(event?.prompt || event?.cleanedBody || ""); if (prompt.length < MIN_LEN || prompt.startsWith("<")) { lastPrompt = null; return; } lastPrompt = { text: prompt, sessionKey: ctx?.sessionKey || "", ts: Date.now() }; log.info("atocore-capture:prompt_buffered", { len: prompt.length }); }); // --- CAPTURE: send completed turns to AtoCore --- api.on("llm_output", async (event, ctx) => { if (!lastPrompt) return; const texts = Array.isArray(event?.assistantTexts) ? event.assistantTexts : []; const response = trunc(trim(texts.join("\n\n")), MAX_RESP); if (!response) return; const prompt = lastPrompt.text; const sessionKey = lastPrompt.sessionKey || ctx?.sessionKey || ""; const project = detectProject(prompt); lastPrompt = null; log.info("atocore-capture:posting", { promptLen: prompt.length, responseLen: response.length, project: project || "(none)", }); fetch(BASE_URL.replace(/\/$/, "") + "/interactions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt, response, client: "openclaw", session_id: sessionKey, project, reinforce: true, }), signal: AbortSignal.timeout(CAPTURE_TIMEOUT_MS), }).then(res => { log.info("atocore-capture:posted", { status: res.status }); }).catch(err => { log.warn("atocore-capture:post_error", { error: String(err).slice(0, 200) }); }); }); api.on("session_end", async () => { lastPrompt = null; }); } });