| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- import fs from "fs";
- const LOG_FILE = "/tmp/openclaw-chat.log";
- const sessionKeys = new Map<string, string>();
- let lastKnownSessionKey = "";
- function appendLog(entry: Record<string, unknown>) {
- try {
- fs.appendFileSync(LOG_FILE, JSON.stringify(entry) + "\n", "utf8");
- } catch {}
- }
- function extractText(content: any, seen?: Set<any>): string {
- if (typeof content === "string") return content;
- if (content === null || content === undefined) return "";
- const seenSet = seen ?? new Set<any>();
- if (seenSet.has(content)) return "";
- seenSet.add(content);
- if (Array.isArray(content)) {
- return content
- .map((item) => extractText(item, seenSet))
- .filter((text) => !!text)
- .join("");
- }
- if (typeof content === "object") {
- return Object.values(content)
- .map((value) => extractText(value, seenSet))
- .filter((text) => !!text)
- .join("");
- }
- return "";
- }
- function extractUserText(content: any): string {
- const raw = extractText(content);
- const match = raw.match(/\[.*?\]\s*([\s\S]+)$/);
- return match ? match[1].trim() : raw.trim();
- }
- function cleanAssistant(content: any): string {
- return extractText(content)
- .replace(/^\[\[reply_to_current\]\]\s*/, "")
- .trim();
- }
- function isRealAssistant(msg: any): boolean {
- const text = cleanAssistant(msg.content);
- return !!text && !text.startsWith("Model reset");
- }
- function normalizeSessionKey(value: unknown): string | undefined {
- if (typeof value !== "string") return undefined;
- const v = value.trim();
- if (!v || v === "unknown") return undefined;
- return v;
- }
- function deepFindSessionKey(obj: any, maxDepth = 6): string | undefined {
- const seen = new Set<any>();
- function walk(node: any, depth: number): string | undefined {
- if (!node || depth > maxDepth) return undefined;
- if (typeof node !== "object") return undefined;
- if (seen.has(node)) return undefined;
- seen.add(node);
- for (const [k, v] of Object.entries(node)) {
- if (typeof v === "string") {
- const normalized = normalizeSessionKey(v);
- if (!normalized) continue;
- if (k.toLowerCase().includes("session") && normalized.includes(":")) {
- return normalized;
- }
- if (/^agent:[^:\s]+:[^:\s]+$/.test(normalized)) {
- return normalized;
- }
- }
- }
- for (const v of Object.values(node)) {
- const found = walk(v, depth + 1);
- if (found) return found;
- }
- return undefined;
- }
- return walk(obj, 0);
- }
- function pickSessionKey(ctx: any): string | undefined {
- const candidate =
- ctx.sessionKey ??
- ctx.result?.sessionKey ??
- ctx.context?.sessionKey ??
- ctx.metadata?.sessionKey ??
- ctx.message?.sessionKey ??
- ctx.session?.sessionKey ??
- ctx.session?.key ??
- ctx.user?.sessionKey;
- const direct = normalizeSessionKey(candidate);
- if (direct) return direct;
- return deepFindSessionKey(ctx);
- }
- function cacheSessionKey(ctx: any) {
- const key = pickSessionKey(ctx);
- if (!key) return;
- const ids = [
- ctx.sessionId,
- ctx.context?.sessionId,
- ctx.metadata?.sessionId,
- ctx.session?.id,
- ctx.session?.sessionId,
- ctx.result?.sessionId,
- ctx.message?.sessionId,
- ].filter((v) => typeof v === "string" && !!v);
- for (const id of ids) sessionKeys.set(id, key);
- // also keep the key itself as a lookup alias
- sessionKeys.set(key, key);
- lastKnownSessionKey = key;
- }
- function resolveSessionKey(ctx: any): string {
- const id =
- ctx.sessionKey ??
- ctx.context?.sessionKey ??
- ctx.metadata?.sessionKey ??
- ctx.session?.Key ??
- ctx.session?.sessionkey ??
- ctx.result?.sessionKey ??
- ctx.message?.sessionkey;
- if (id) {
- const stored = sessionKeys.get(id);
- if (stored) return stored;
- }
- const direct = pickSessionKey(ctx);
- if (direct) return direct;
- if (lastKnownSessionKey) return lastKnownSessionKey;
- return "unknown";
- }
- export default function chatLoggerPlugin(api: any) {
- api.on("session_start", (ctx: any) => {
- cacheSessionKey(ctx);
- });
- api.on("message_received", (ctx: any) => {
- cacheSessionKey(ctx);
- });
- api.on("agent_end", (ctx: any) => {
- cacheSessionKey(ctx);
- const messages: any[] = ctx.result?.messages ?? ctx.messages ?? [];
- const sessionKey = resolveSessionKey(ctx);
- const turns: { role: string; content: string }[] = [];
- for (const msg of messages) {
- if (msg.role === "user") {
- const text = extractUserText(msg.content);
- if (text) turns.push({ role: "user", content: text });
- } else if (msg.role === "assistant" && isRealAssistant(msg)) {
- turns.push({ role: "assistant", content: cleanAssistant(msg.content) });
- }
- }
- const last = turns.slice(-2);
- if (last.length === 0) return;
- appendLog({
- ts: new Date().toISOString(),
- sessionKey,
- messages: last,
- });
- if (sessionKey === "unknown") {
- console.warn("[chat-logger] could not resolve sessionKey on agent_end");
- }
- });
- api.on("gateway_start", () => {
- sessionKeys.clear();
- lastKnownSessionKey = "";
- });
- }
|