import crypto from "node:crypto"; import { buildWorkspaceSkillSnapshot } from "../../agents/skills.js"; import type { ClawdbotConfig } from "../../config/config.js"; import { type SessionEntry, saveSessionStore } from "../../config/sessions.js"; import { buildProviderSummary } from "../../infra/provider-summary.js"; import { drainSystemEvents } from "../../infra/system-events.js"; export async function prependSystemEvents(params: { cfg: ClawdbotConfig; sessionKey: string; isMainSession: boolean; isNewSession: boolean; prefixedBodyBase: string; }): Promise { const compactSystemEvent = (line: string): string | null => { const trimmed = line.trim(); if (!trimmed) return null; const lower = trimmed.toLowerCase(); if (lower.includes("reason periodic")) return null; if (lower.includes("heartbeat")) return null; if (trimmed.startsWith("Node:")) { return trimmed.replace(/ · last input [^·]+/i, "").trim(); } return trimmed; }; const systemLines: string[] = []; const queued = drainSystemEvents(params.sessionKey); systemLines.push( ...queued.map(compactSystemEvent).filter((v): v is string => Boolean(v)), ); if (params.isMainSession && params.isNewSession) { const summary = await buildProviderSummary(params.cfg); if (summary.length > 0) systemLines.unshift(...summary); } if (systemLines.length === 0) return params.prefixedBodyBase; const block = systemLines.map((l) => `System: ${l}`).join("\n"); return `${block}\n\n${params.prefixedBodyBase}`; } export async function ensureSkillSnapshot(params: { sessionEntry?: SessionEntry; sessionStore?: Record; sessionKey?: string; storePath?: string; sessionId?: string; isFirstTurnInSession: boolean; workspaceDir: string; cfg: ClawdbotConfig; /** If provided, only load skills with these names (for per-channel skill filtering) */ skillFilter?: string[]; }): Promise<{ sessionEntry?: SessionEntry; skillsSnapshot?: SessionEntry["skillsSnapshot"]; systemSent: boolean; }> { const { sessionEntry, sessionStore, sessionKey, storePath, sessionId, isFirstTurnInSession, workspaceDir, cfg, skillFilter, } = params; let nextEntry = sessionEntry; let systemSent = sessionEntry?.systemSent ?? false; if (isFirstTurnInSession && sessionStore && sessionKey) { const current = nextEntry ?? sessionStore[sessionKey] ?? { sessionId: sessionId ?? crypto.randomUUID(), updatedAt: Date.now(), }; const skillSnapshot = isFirstTurnInSession || !current.skillsSnapshot ? buildWorkspaceSkillSnapshot(workspaceDir, { config: cfg, skillFilter, }) : current.skillsSnapshot; nextEntry = { ...current, sessionId: sessionId ?? current.sessionId ?? crypto.randomUUID(), updatedAt: Date.now(), systemSent: true, skillsSnapshot: skillSnapshot, }; sessionStore[sessionKey] = { ...sessionStore[sessionKey], ...nextEntry }; if (storePath) { await saveSessionStore(storePath, sessionStore); } systemSent = true; } const skillsSnapshot = nextEntry?.skillsSnapshot ?? (isFirstTurnInSession ? undefined : buildWorkspaceSkillSnapshot(workspaceDir, { config: cfg, skillFilter, })); if ( skillsSnapshot && sessionStore && sessionKey && !isFirstTurnInSession && !nextEntry?.skillsSnapshot ) { const current = nextEntry ?? { sessionId: sessionId ?? crypto.randomUUID(), updatedAt: Date.now(), }; nextEntry = { ...current, sessionId: sessionId ?? current.sessionId ?? crypto.randomUUID(), updatedAt: Date.now(), skillsSnapshot, }; sessionStore[sessionKey] = { ...sessionStore[sessionKey], ...nextEntry }; if (storePath) { await saveSessionStore(storePath, sessionStore); } } return { sessionEntry: nextEntry, skillsSnapshot, systemSent }; } export async function incrementCompactionCount(params: { sessionEntry?: SessionEntry; sessionStore?: Record; sessionKey?: string; storePath?: string; now?: number; }): Promise { const { sessionEntry, sessionStore, sessionKey, storePath, now = Date.now(), } = params; if (!sessionStore || !sessionKey) return undefined; const entry = sessionStore[sessionKey] ?? sessionEntry; if (!entry) return undefined; const nextCount = (entry.compactionCount ?? 0) + 1; sessionStore[sessionKey] = { ...entry, compactionCount: nextCount, updatedAt: now, }; if (storePath) { await saveSessionStore(storePath, sessionStore); } return nextCount; }