UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

152 lines (151 loc) • 6.04 kB
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import { t as formatErrorMessage } from "./errors-BvOkmQM5.mjs"; import { r as openNodeSqliteDatabase } from "./node-sqlite-DyeJqxPf.mjs"; import { a as tableHasColumn, i as tableExists } from "./openclaw-state-db-schema-helpers-DKz5xGgh.mjs"; import { c as transcriptEventJsonSql } from "./transcript-payload-K8XYDkGU.mjs"; import { t as getSessionKysely } from "./session-accessor.sqlite-scope-helpers-CfDTAEsL.mjs"; import { o as resolveAllAgentSessionStoreTargetsSync } from "./targets-kx08xomQ.mjs"; import "./session-accessor.sqlite-scope-BkgwxatT.mjs"; import { t as note } from "./note-Bi0VsI6R.mjs"; import { i as projectExistingAgentDatabaseTargets, u as resolveTargetSqliteOptions } from "./session-sqlite-migration-readers-BZ1nJUkY.mjs"; //#region src/commands/doctor-session-sqlite-transcript-readers.ts function resetReadIterator(iterator) { try { iterator?.return?.(); } catch {} } var ReadOnlySqliteTranscriptReader = class { constructor(database) { this.database = database; this.eventJsonSql = tableHasColumn(database, "transcript_events", "event_zstd") ? transcriptEventJsonSql(database).compile(getSessionKysely(database)).sql : "event_json"; this.labelRowsSql = `SELECT ${this.eventJsonSql} AS event_json, seq FROM transcript_events WHERE session_id = ? ORDER BY seq ASC`; } sessionIds() { if (!tableExists(this.database, "transcript_events")) return []; return this.database.prepare("SELECT DISTINCT session_id FROM transcript_events ORDER BY session_id ASC").all().flatMap((row) => typeof row.session_id === "string" ? [row.session_id] : []); } repairSnapshot(sessionId, needsRepair, mayNeedRepair) { let iterator; try { this.labelDetection ??= this.database.prepare(this.labelRowsSql); iterator = this.labelDetection.iterate(sessionId); for (const row of iterator) { if (typeof row.event_json !== "string" || typeof row.seq !== "number") continue; if (!mayNeedRepair(row.event_json)) continue; let event; try { event = JSON.parse(row.event_json); } catch { continue; } if (!needsRepair(event)) continue; const rows = []; this.labelSnapshot ??= this.database.prepare(this.labelRowsSql); iterator = this.labelSnapshot.iterate(sessionId); for (const candidate of iterator) if (typeof candidate.event_json === "string" && typeof candidate.seq === "number") rows.push({ eventJson: candidate.event_json, seq: candidate.seq }); return { ok: true, rows }; } return { ok: true, rows: [] }; } catch (error) { resetReadIterator(iterator); return { ok: false, error }; } } /** Reads exact row metadata for a guarded transcript replacement without opening a writer. */ headerlessSnapshot(sessionId, acceptRow) { let iterator; try { this.firstHeaderRow ??= this.database.prepare(`SELECT ${this.eventJsonSql} AS event_json FROM transcript_events WHERE session_id = ? ORDER BY seq ASC LIMIT 1`); const firstRow = this.firstHeaderRow.get(sessionId); if (typeof firstRow?.event_json === "string") { let first; try { first = JSON.parse(firstRow.event_json); } catch { return { ok: true, rows: [] }; } if (!isRecord(first) || first.type === "session") return { ok: true, rows: [] }; } this.sessionKey ??= this.database.prepare("SELECT session_key FROM session_windows WHERE session_id = ? LIMIT 1"); const sessionKeyRow = this.sessionKey.get(sessionId); const storageRows = []; this.headerSnapshot ??= this.database.prepare(`SELECT created_at, ${this.eventJsonSql} AS event_json, seq FROM transcript_events WHERE session_id = ? ORDER BY seq ASC`); iterator = this.headerSnapshot.iterate(sessionId); for (const row of iterator) { if (typeof row.created_at !== "number" || typeof row.event_json !== "string" || typeof row.seq !== "number") return { ok: false, error: /* @__PURE__ */ new Error(`Invalid transcript row metadata for session ${sessionId}`) }; const storageRow = { createdAt: row.created_at, eventJson: row.event_json, seq: row.seq }; if (!acceptRow(storageRow)) return { ok: true, rows: [] }; storageRows.push(storageRow); } return { ok: true, rows: storageRows, ...typeof sessionKeyRow?.session_key === "string" ? { sessionKey: sessionKeyRow.session_key } : {} }; } catch (error) { resetReadIterator(iterator); return { ok: false, error }; } } }; //#endregion //#region src/commands/doctor-session-transcript-scan.ts /** Each snapshot closes its cursors before a repair; each pass owns only its read connection. */ function scanDoctorSessionTranscripts(params, visit) { const reportError = (message, error) => note(`${message}: ${formatErrorMessage(error).replace(/\s+/g, " ").trim()}`, params.title); for (const target of projectExistingAgentDatabaseTargets(resolveAllAgentSessionStoreTargetsSync(params.cfg, { env: params.env }), params.env, params.cfg)) { const databaseOptions = resolveTargetSqliteOptions(target, params.env); let database; try { database = openNodeSqliteDatabase(target.sqlitePath, { readOnly: true }); const reader = new ReadOnlySqliteTranscriptReader(database); for (const sessionId of reader.sessionIds()) visit({ reader, sessionId, target, databaseOptions, reportError }); } catch (error) { reportError(`- ${params.failureLabel} for ${target.agentId} (${target.sqlitePath})`, error); } finally { database?.close(); } } } function transcriptSnapshotsMatch(expected, current, compareCreatedAt = false) { return expected.length === current.length && expected.every((row, index) => row.seq === current[index]?.seq && (!compareCreatedAt || row.createdAt === current[index]?.createdAt) && row.eventJson === current[index]?.eventJson); } //#endregion export { transcriptSnapshotsMatch as n, scanDoctorSessionTranscripts as t };