import { getDbInstance, rowToCamel } from "./core"; export interface HandoffPayload { id?: string; sessionId: string; comboName: string; fromAccount: string; summary: string; keyDecisions: string[]; taskProgress: string; activeEntities: string[]; messageCount: number; model: string; warningThresholdPct: number; generatedAt: string; expiresAt: string; createdAt?: string; } type JsonRecord = Record; interface StatementLike { get: (...params: unknown[]) => TRow | undefined; run: (...params: unknown[]) => { changes: number }; } interface DbLike { prepare: (sql: string) => StatementLike; } const CLEANUP_THROTTLE_MS = 30 * 60 * 1000; let lastCleanupAt = 0; function parseJsonArray(value: unknown): string[] { if (Array.isArray(value)) { return value.map((item) => (typeof item === "string" ? item : String(item))).filter(Boolean); } if (typeof value !== "string" || value.trim().length === 0) { return []; } try { const parsed = JSON.parse(value); return Array.isArray(parsed) ? parsed.map((item) => (typeof item === "string" ? item : String(item))).filter(Boolean) : []; } catch { return []; } } function toHandoffPayload(row: unknown): HandoffPayload | null { const camel = rowToCamel(row) as JsonRecord | null; if (!camel) return null; return { id: typeof camel.id === "string" ? camel.id : undefined, sessionId: typeof camel.sessionId === "string" ? camel.sessionId : "", comboName: typeof camel.comboName === "string" ? camel.comboName : "", fromAccount: typeof camel.fromAccount === "string" ? camel.fromAccount : "", summary: typeof camel.summary === "string" ? camel.summary : "", keyDecisions: parseJsonArray(camel.keyDecisions), taskProgress: typeof camel.taskProgress === "string" ? camel.taskProgress : "", activeEntities: parseJsonArray(camel.activeEntities), messageCount: Number.isFinite(Number(camel.messageCount)) ? Number(camel.messageCount) : 0, model: typeof camel.model === "string" ? camel.model : "", warningThresholdPct: Number.isFinite(Number(camel.warningThresholdPct)) ? Number(camel.warningThresholdPct) : 0.85, generatedAt: typeof camel.generatedAt === "string" ? camel.generatedAt : "", expiresAt: typeof camel.expiresAt === "string" ? camel.expiresAt : "", createdAt: typeof camel.createdAt === "string" ? camel.createdAt : undefined, }; } export function upsertHandoff(payload: HandoffPayload): void { const db = getDbInstance() as unknown as DbLike; const createdAt = new Date().toISOString(); db.prepare( `INSERT INTO context_handoffs (session_id, combo_name, from_account, summary, key_decisions, task_progress, active_entities, message_count, model, warning_threshold_pct, generated_at, expires_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(session_id, combo_name) DO UPDATE SET from_account = excluded.from_account, summary = excluded.summary, key_decisions = excluded.key_decisions, task_progress = excluded.task_progress, active_entities = excluded.active_entities, message_count = excluded.message_count, model = excluded.model, warning_threshold_pct = excluded.warning_threshold_pct, generated_at = excluded.generated_at, expires_at = excluded.expires_at, created_at = excluded.created_at` ).run( payload.sessionId, payload.comboName, payload.fromAccount, payload.summary, JSON.stringify(payload.keyDecisions || []), payload.taskProgress, JSON.stringify(payload.activeEntities || []), payload.messageCount, payload.model, payload.warningThresholdPct, payload.generatedAt, payload.expiresAt, createdAt ); } export function getHandoff(sessionId: string, comboName: string): HandoffPayload | null { const db = getDbInstance() as unknown as DbLike; const now = new Date().toISOString(); const row = db .prepare( `SELECT * FROM context_handoffs WHERE session_id = ? AND combo_name = ? AND expires_at > ? ORDER BY created_at DESC LIMIT 1` ) .get(sessionId, comboName, now); return toHandoffPayload(row); } export function deleteHandoff(sessionId: string, comboName: string): void { const db = getDbInstance() as unknown as DbLike; db.prepare("DELETE FROM context_handoffs WHERE session_id = ? AND combo_name = ?").run( sessionId, comboName ); } export function cleanupExpiredHandoffs(): number { const nowMs = Date.now(); if (nowMs - lastCleanupAt < CLEANUP_THROTTLE_MS) { return 0; } const db = getDbInstance() as unknown as DbLike; const now = new Date(nowMs).toISOString(); const result = db.prepare("DELETE FROM context_handoffs WHERE expires_at <= ?").run(now); lastCleanupAt = nowMs; return result.changes; } export function hasActiveHandoff(sessionId: string, comboName: string): boolean { const db = getDbInstance() as unknown as DbLike; const now = new Date().toISOString(); const row = db .prepare( `SELECT 1 FROM context_handoffs WHERE session_id = ? AND combo_name = ? AND expires_at > ? LIMIT 1` ) .get(sessionId, comboName, now); return !!row; }