openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
225 lines (224 loc) • 7.86 kB
JavaScript
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 {};