UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

225 lines (224 loc) 7.86 kB
import { n as closeOpenClawStateDatabase } from "../../openclaw-state-db-cache-C7ljO0xP.js"; import "../../openclaw-state-db-BRTnL-D8.js"; import { c as releaseOpenClawAgentDatabaseLease, s as claimOpenClawAgentDatabaseLease } from "../../openclaw-agent-db-lease-Djvd6LWN.js"; import { t as openOpenClawAgentDatabaseReadOnly } from "../../openclaw-agent-db-readonly-CHjf8FxN.js"; import { o as listSessionsNeedingTranscriptIndexReconcile, v as prepareMemorySessionTranscriptProjection, y as prepareSessionTranscriptProjection } from "../../session-transcript-index-DmCW_GtW.js"; import { parentPort, workerData } from "node:worker_threads"; //#region src/config/sessions/session-transcript-reconcile.worker.ts /** Worker entrypoint for transcript parsing and active-branch resolution only. */ const ACTIVE_ROWS_PER_CHUNK = 512; const FTS_ROWS_PER_CHUNK = 128; const FTS_TEXT_BYTES_PER_CHUNK = 262144; function parseWorkerInput(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return; const input = value; if (input.mode === "memory" && Array.isArray(input.sessionIds) && input.sessionIds.every((sessionId) => typeof sessionId === "string")) return { mode: "memory", sessionIds: input.sessionIds }; if (typeof input.stateDir !== "string" || typeof input.externallySupervised !== "boolean") return; const owner = { stateDir: input.stateDir, externallySupervised: input.externallySupervised }; if (input.mode === "release" && typeof input.leaseId === "string") return { ...owner, mode: "release", leaseId: input.leaseId }; if (typeof input.agentId !== "string" || typeof input.path !== "string") return; if (input.preferredSessionId !== void 0 && typeof input.preferredSessionId !== "string") return; const plan = { ...owner, agentId: input.agentId, path: input.path, ...typeof input.preferredSessionId === "string" ? { preferredSessionId: input.preferredSessionId } : {} }; if (input.mode === "disk" && typeof input.leaseId === "string") return { ...plan, mode: "disk", leaseId: input.leaseId }; } function orderSessionIds(sessionIds, preferredSessionId) { if (!preferredSessionId || !sessionIds.includes(preferredSessionId)) return sessionIds; return [preferredSessionId, ...sessionIds.filter((sessionId) => sessionId !== preferredSessionId)]; } const parsedInput = parseWorkerInput(workerData); if (!parentPort || !parsedInput) throw new Error("session transcript reconcile worker requires valid worker data"); const port = parentPort; const input = parsedInput; function resolveLeaseEnvironment(owner) { return { OPENCLAW_STATE_DIR: owner.stateDir, ...owner.externallySupervised ? { OPENCLAW_SUPERVISOR_MODE: "external" } : {} }; } function releaseLease(owner) { try { releaseOpenClawAgentDatabaseLease(owner.leaseId, { env: resolveLeaseEnvironment(owner) }); closeOpenClawStateDatabase(); port.postMessage({ type: "lease-released" }); } catch (error) { port.postMessage({ type: "lease-release-failed", error: error instanceof Error ? error.message : String(error) }); } finally { port.close(); } } function waitForContinue() { return new Promise((resolve, reject) => { port.once("message", (message) => { if (message?.type !== "continue" || typeof message.accepted !== "boolean") { reject(/* @__PURE__ */ new Error("session transcript reconcile worker received an invalid command")); return; } resolve(message.accepted); }); }); } async function postAndWait(message, transferList = []) { port.postMessage(message, transferList); return await waitForContinue(); } function encodeFtsChunk(rows) { const encoder = new TextEncoder(); const encoded = rows.map((row) => ({ bytes: encoder.encode(row.text), row })); const textBytes = new Uint8Array(encoded.reduce((total, entry) => total + entry.bytes.length, 0)); let textByteOffset = 0; return { rows: encoded.map(({ bytes, row }) => { textBytes.set(bytes, textByteOffset); const result = { messageId: row.messageId, role: row.role, textByteLength: bytes.length, textByteOffset, timestamp: row.timestamp }; textByteOffset += bytes.length; return result; }), textBytes }; } function takeFtsChunkEnd(rows, start) { let bytes = 0; let end = start; while (end < rows.length && end - start < FTS_ROWS_PER_CHUNK) { const rowBytes = Buffer.byteLength(rows[end]?.text ?? "", "utf8"); if (end > start && bytes + rowBytes > FTS_TEXT_BYTES_PER_CHUNK) break; bytes += rowBytes; end += 1; } return end; } async function streamPreparedProjection(plan) { const { activeRows, ftsRows, ...metadata } = plan; if (!await postAndWait({ type: "plan-start", plan: metadata })) return; for (let offset = 0; offset < activeRows.length; offset += ACTIVE_ROWS_PER_CHUNK) if (!await postAndWait({ type: "active-chunk", rows: activeRows.slice(offset, offset + ACTIVE_ROWS_PER_CHUNK), sessionId: plan.sessionId })) return; for (let offset = 0; offset < ftsRows.length;) { const end = takeFtsChunkEnd(ftsRows, offset); const chunk = encodeFtsChunk(ftsRows.slice(offset, end)); if (!await postAndWait({ type: "fts-chunk", chunk, sessionId: plan.sessionId }, [chunk.textBytes.buffer])) return; offset = end; } await postAndWait({ type: "plan-finish", sessionId: plan.sessionId }); } async function prepareMemoryProjection(sessionId) { const rows = /* @__PURE__ */ new Map(); const decoder = new TextDecoder(); let fragments = []; while (true) { const pending = new Promise((resolve) => { port.once("message", resolve); }); port.postMessage({ type: "source-read", sessionId }); const frame = await pending; if (frame.type === "source-unavailable") return; if (frame.type === "source-end") { const plan = prepareMemorySessionTranscriptProjection(sessionId, frame.snapshot.transcriptUpdatedAt, rows); rows.clear(); return plan; } fragments.push(decoder.decode(frame.bytes, { stream: !frame.final })); if (frame.final) { rows.set(frame.seq, { seq: frame.seq, created_at: frame.createdAt, event_json: fragments.join("") }); fragments = []; } } } async function run() { if (input.mode === "release") { releaseLease(input); return; } const reconcileInput = input; let closeDatabase; let terminalMessage; try { const database = (() => { if (reconcileInput.mode === "memory") return; const options = { agentId: reconcileInput.agentId, path: reconcileInput.path, env: resolveLeaseEnvironment(reconcileInput) }; claimOpenClawAgentDatabaseLease(options, reconcileInput.leaseId); const opened = openOpenClawAgentDatabaseReadOnly(options); if (!opened.found) throw new Error(`Cannot prepare transcript indexes: ${opened.reason}`); closeDatabase = opened.database.close; return opened.database; })(); const sessionIds = reconcileInput.mode === "memory" ? reconcileInput.sessionIds : orderSessionIds(listSessionsNeedingTranscriptIndexReconcile(database.db), reconcileInput.preferredSessionId); for (const sessionId of sessionIds) { const plan = reconcileInput.mode === "memory" ? await prepareMemoryProjection(sessionId) : prepareSessionTranscriptProjection(database.db, sessionId); if (plan) await streamPreparedProjection(plan); } terminalMessage = { type: "done" }; } catch (error) { terminalMessage = { type: "failed", error: error instanceof Error ? error.message : String(error) }; } try { closeDatabase?.(); port.postMessage(terminalMessage); if (reconcileInput.mode === "disk") port.once("message", (message) => { if (message?.type !== "release") throw new Error("session transcript reconcile worker expected lease release"); releaseLease(reconcileInput); }); } finally { if (reconcileInput.mode === "memory") port.close(); } } run(); //#endregion export {};