2026-04-12 22:06:07 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 23:39:46 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 22:06:07 +00:00
|
|
|
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();
|
|
|
|
|
|
2026-04-12 23:29:14 +00:00
|
|
|
api.on("before_dispatch", async (event, ctx) => {
|
2026-04-12 22:06:07 +00:00
|
|
|
const config = api.getConfig?.() || {};
|
|
|
|
|
const minPromptLength = Number(config.minPromptLength || DEFAULT_MIN_PROMPT_LENGTH);
|
2026-04-12 23:29:14 +00:00
|
|
|
const prompt = trimText(event?.body || event?.content || "");
|
2026-04-12 23:39:46 +00:00
|
|
|
const keys = buildKeys(ctx?.sessionKey, ctx?.sessionId, event?.sessionKey, event?.sessionId, ctx?.conversationId, event?.conversationId);
|
|
|
|
|
if (!keys.length) return;
|
2026-04-12 22:06:07 +00:00
|
|
|
if (!shouldCapturePrompt(prompt, minPromptLength)) {
|
2026-04-12 23:39:46 +00:00
|
|
|
clearPending(pendingBySession, keys);
|
2026-04-12 22:06:07 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2026-04-12 23:39:46 +00:00
|
|
|
rememberPending(pendingBySession, keys, {
|
2026-04-12 22:06:07 +00:00
|
|
|
prompt,
|
2026-04-12 23:39:46 +00:00
|
|
|
sessionId: trimText(ctx?.sessionId || event?.sessionId || ""),
|
|
|
|
|
sessionKey: trimText(ctx?.sessionKey || event?.sessionKey || ""),
|
|
|
|
|
conversationId: trimText(ctx?.conversationId || event?.conversationId || ""),
|
2026-04-12 22:06:07 +00:00
|
|
|
project: ""
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-12 23:39:46 +00:00
|
|
|
api.on("message_sending", async (event, ctx) => {
|
|
|
|
|
const keys = buildKeys(ctx?.sessionKey, ctx?.sessionId, ctx?.conversationId);
|
|
|
|
|
const pending = takePending(pendingBySession, keys);
|
2026-04-12 22:06:07 +00:00
|
|
|
if (!pending) return;
|
|
|
|
|
|
2026-04-12 22:36:59 +00:00
|
|
|
const response = truncateResponse(
|
2026-04-12 23:39:46 +00:00
|
|
|
trimText(event?.content || ""),
|
2026-04-12 22:36:59 +00:00
|
|
|
Number((api.getConfig?.() || {}).maxResponseLength || DEFAULT_MAX_RESPONSE_LENGTH)
|
|
|
|
|
);
|
2026-04-12 22:06:07 +00:00
|
|
|
if (!response) return;
|
|
|
|
|
|
|
|
|
|
const config = api.getConfig?.() || {};
|
|
|
|
|
const baseUrl = trimText(config.baseUrl) || DEFAULT_BASE_URL;
|
|
|
|
|
const payload = {
|
|
|
|
|
prompt: pending.prompt,
|
|
|
|
|
response,
|
|
|
|
|
client: "openclaw",
|
2026-04-12 23:39:46 +00:00
|
|
|
session_id: pending.sessionKey || pending.sessionId || pending.conversationId,
|
2026-04-12 22:06:07 +00:00
|
|
|
project: pending.project || "",
|
|
|
|
|
reinforce: true
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await postInteraction(baseUrl, payload, logger);
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-12 22:36:59 +00:00
|
|
|
api.on("agent_end", async (event) => {
|
2026-04-12 23:39:46 +00:00
|
|
|
clearPending(pendingBySession, buildKeys(event?.sessionKey, event?.sessionId));
|
2026-04-12 22:36:59 +00:00
|
|
|
});
|
|
|
|
|
|
2026-04-12 22:06:07 +00:00
|
|
|
api.on("session_end", async (event) => {
|
2026-04-12 23:39:46 +00:00
|
|
|
clearPending(pendingBySession, buildKeys(event?.sessionKey, event?.sessionId));
|
2026-04-12 22:06:07 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|