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; } 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) { 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_dispatch", async (event, ctx) => { const config = api.getConfig?.() || {}; const minPromptLength = Number(config.minPromptLength || DEFAULT_MIN_PROMPT_LENGTH); 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)) { clearPending(pendingBySession, keys); return; } rememberPending(pendingBySession, keys, { prompt, sessionId: trimText(ctx?.sessionId || event?.sessionId || ""), sessionKey: trimText(ctx?.sessionKey || event?.sessionKey || ""), conversationId: trimText(ctx?.conversationId || event?.conversationId || ""), project: "" }); }); api.on("message_sending", async (event, ctx) => { const keys = buildKeys(ctx?.sessionKey, ctx?.sessionId, ctx?.conversationId); const pending = takePending(pendingBySession, keys); if (!pending) return; const response = truncateResponse( trimText(event?.content || ""), 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 || pending.conversationId, project: pending.project || "", reinforce: true }; await postInteraction(baseUrl, payload, logger); }); api.on("agent_end", async (event) => { clearPending(pendingBySession, buildKeys(event?.sessionKey, event?.sessionId)); }); api.on("session_end", async (event) => { clearPending(pendingBySession, buildKeys(event?.sessionKey, event?.sessionId)); }); } });