UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

428 lines (427 loc) • 20.3 kB
import { r as getAsyncWorkSignal } from "./async-work-scope-DDiH0P7W.mjs"; import { r as enableNodeSqliteKyselyStatementCache } from "./kysely-sync-cache-state-DbAMR_DD.mjs"; import { s as getSqliteRuntimeCapabilities } from "./bun-sqlite-library-DlNkYpEb.mjs"; import { _ as getSqlitePinnedReadSnapshot, h as runSqliteReadOperationSync, l as isSqliteSchemaAdmissionCold, t as admitSqliteSchema } from "./sqlite-schema-facts-BVf0E_0z.mjs"; import { d as readDatabasePathIdentitySync, r as assertExistingDatabaseIdentity } from "./sqlite-worker-identity-BRNRpRyc.mjs"; import { r as openNodeSqliteDatabase } from "./node-sqlite-DyeJqxPf.mjs"; import { t as isPromiseLike } from "./promise-like-D7-l5Fsp.mjs"; import { a as createSqliteLifecycleAggregateError, i as SqliteCoordinatorError, s as throwSqliteLifecycleErrors } from "./sqlite-lifecycle-errors-DuOygjcn.mjs"; import { i as isSqliteCorruptionError } from "./sqlite-error-diagnostics-CWRyv4lz.mjs"; import { t as acquireSqliteSnapshotReadToken } from "./sqlite-snapshot-staging-DskavSq5.mjs"; import { u as retainSnapshotTempDirectory } from "./sqlite-readonly-location-cleanup-CoQX1AVj.mjs"; import { s as OPENCLAW_SQLITE_BUSY_TIMEOUT_MS } from "./openclaw-state-db-contract-BYv0nUQr.mjs"; import { t as assertTransactionUsable } from "./sqlite-transaction-DBRLvCDM.mjs"; import { a as readSqliteUserVersion, n as createNewerSqliteSchemaVersionError } from "./sqlite-user-version-DLMl1A7W.mjs"; import { a as registerSqliteCacheExitClose, o as runInSqliteMaintenanceContext } from "./sqlite-wal-DfKCrDTQ.mjs"; import { d as isOpenClawDatabaseMaintenanceResourceOwned, m as observeOpenClawDatabaseMaintenanceResource, u as getOpenClawDatabaseMaintenanceScope } from "./openclaw-state-db-async-lifecycle-DwFy2ZTk.mjs"; import { r as normalizeOpenClawStateSchemaReadError } from "./openclaw-state-db-schema-migration-required-DBia8oMG.mjs"; import { n as assertSupportedStateSchemaVersion } from "./openclaw-state-db-schema-version-DZI_59qS.mjs"; import { t as SQLITE_IDLE_HANDLE_TTL_MS } from "./sqlite-handle-lifecycle-dWd9h3ii.mjs"; import { p as openClawStateDatabaseCache, v as registerOpenClawStateDatabaseAsyncResource, y as registerOpenClawStateDatabaseLifecycleListener } from "./openclaw-state-db-cache-CmYLFRSt.mjs"; import { a as isExistingOpenClawStateSchema } from "./openclaw-state-db-schema-policy-B65l9xTe.mjs"; import { a as openTrackedStateDatabaseResult, n as invalidateOpenClawStateRuntimeIntegrity } from "./openclaw-state-db-integrity-admission-D2ZPZec-.mjs"; import { n as assertExistingOpenClawStateRuntimeSchema } from "./openclaw-state-db-existing-schema-CdYR46xe.mjs"; import { a as getSqliteWorkerStateIntegrityAdmission, o as runWithSqliteWorkerStateContext } from "./sqlite-worker-state-context-G3Xbcj1W.mjs"; import path from "node:path"; //#region src/state/openclaw-state-db-read-schema.ts function assertStateReadSchemaForPolicy(database, pathname, existingSchema, integrityPolicy, readContentVersionRow) { if (existingSchema) assertExistingOpenClawStateRuntimeSchema(database, pathname, getSqliteWorkerStateIntegrityAdmission(), integrityPolicy); else assertSupportedStateSchemaVersion(database, pathname, void 0, readContentVersionRow); } function admitStateReadSchemaFacts(database, pathname) { try { admitSqliteSchema(database, (userVersion) => assertSupportedStateSchemaVersion(database, pathname, { userVersion, contentVersion: userVersion })); } catch (error) { let version; try { version = readSqliteUserVersion(database); } catch { throw normalizeOpenClawStateSchemaReadError(error, pathname); } if (version > 20) throw createNewerSqliteSchemaVersionError("OpenClaw state database", pathname, version, 20); throw normalizeOpenClawStateSchemaReadError(error, pathname); } } //#endregion //#region src/state/openclaw-state-db-read-connection.ts const retainedReaders = /* @__PURE__ */ new Map(); const corruptedReaders = /* @__PURE__ */ new WeakSet(); let unregisterExitClose; /** Ordered partial results may carry corruption as data; the same reader owner still retires it. */ function retireOpenClawStateReadConnectionAfterCorruption(database) { invalidateOpenClawStateRuntimeIntegrity(database); corruptedReaders.add(database); } function retireReader(reader) { if (retainedReaders.get(reader.identity.key) !== reader) return; clearTimeout(reader.idleTimer); reader.retiring = true; reader.connection.close(); retainedReaders.delete(reader.identity.key); reader.unregisterDirectClose?.(); if (!retainedReaders.size) { unregisterExitClose?.(); unregisterExitClose = void 0; } } function ownDirectReaderLifecycle(reader) { if (reader.unregisterDirectClose) return; const { identity } = reader; const unregisterResource = registerOpenClawStateDatabaseAsyncResource({ async close(closingIdentity) { if (!closingIdentity || closingIdentity.key === identity.key || closingIdentity.canonicalPath === identity.canonicalPath) retireReader(reader); } }); const unregisterLifecycle = registerOpenClawStateDatabaseLifecycleListener((event) => { if ((event.kind === "closed" || event.kind === "terminal-failure") && (event.path === reader.connection.database.path || event.identity?.key === identity.key)) retireReader(reader); }); reader.unregisterDirectClose = () => { unregisterResource(); unregisterLifecycle(); }; } /** Prepare a live authority reader; each later use executes only its caller's synchronous query. */ function prepareOpenClawStateDirectReader(context) { const pathname = context.admission.databasePath; const identity = { ...context.admission.identity }; const signal = getAsyncWorkSignal(); const assertSource = () => { signal?.throwIfAborted(); getAsyncWorkSignal()?.throwIfAborted(); context.maintenanceScope?.assertReadAdmission(); context.admission.assertCurrent(); openClawStateDatabaseCache.assertOpenClawStateDatabaseOpenAllowed(pathname, "cached-read"); assertExistingDatabaseIdentity(pathname, identity.key, identity.birthtime); const integrity = context.stateIntegrity; if (integrity && Atomics.load(new BigInt64Array(integrity.revision), 0) !== integrity.epoch || context.existingSchemaPath !== void 0 && (!integrity || Atomics.load(new BigInt64Array(integrity.proof), 0) === -1n)) throw new Error("Direct shared-state reader requires current integrity admission"); }; const inContext = (operation) => { const run = () => runWithSqliteWorkerStateContext(context, operation); return context.runInCapturedSchemaScope ? context.runInCapturedSchemaScope(run) : run(); }; const reader = inContext(() => { assertSource(); if (!identity.key.startsWith("file:")) throw new Error("Direct shared-state reader requires an existing physical source"); const previous = retainedReaders.get(identity.key); const borrowed = borrowStateReadConnection(pathname, identity.key); if (borrowed.status === "unavailable") throw borrowed.error; const retained = retainedReaders.get(identity.key); if (!retained || retained.connection.database.db !== borrowed.value.database.db) { borrowed.value.close(); throw new Error("Direct shared-state reader lost its retained connection"); } if (retained.connection.database.db.isTransaction || getSqlitePinnedReadSnapshot(retained.connection.database.db)) throw new Error("Direct shared-state reader cannot admit a transaction or snapshot"); try { const existingSchema = isExistingOpenClawStateSchema(pathname, retained.connection.database.db); const admitted = retained.directAdmission; if (!admitted || admitted.existingSchema !== existingSchema || admitted.integrity?.revision !== context.stateIntegrity?.revision || admitted.integrity?.epoch !== context.stateIntegrity?.epoch) { runSqliteReadOperationSync(retained.connection.database.db, () => { admitStateReadSchemaFacts(retained.connection.database.db, pathname); assertStateReadSchemaForPolicy(retained.connection.database.db, pathname, existingSchema, existingSchema ? "require-proof" : void 0); }); retained.directAdmission = { existingSchema, integrity: context.stateIntegrity }; } ownDirectReaderLifecycle(retained); if (retained !== previous) context.maintenanceScope?.own(retained, "shared-resources", () => { const closingScope = getOpenClawDatabaseMaintenanceScope(); if (closingScope && isOpenClawDatabaseMaintenanceResourceOwned(retained, closingScope)) retireReader(retained); }); assertSource(); scheduleReaderRetirement(retained); return retained; } catch (error) { try { retireReader(retained); } catch (cleanupError) { throwSqliteLifecycleErrors([error, cleanupError], "Direct shared-state reader admission and cleanup failed"); } throw error; } }); const assertCurrent = () => { assertSource(); const db = reader.connection.database.db; if (retainedReaders.get(identity.key) !== reader || reader.retiring || !db.isOpen) throw new Error("Direct shared-state reader is closed"); if (db.isTransaction || getSqlitePinnedReadSnapshot(db)) throw new Error("Direct shared-state reader cannot use a transaction or snapshot"); assertTransactionUsable(db); }; return { read(operation) { return inContext(() => { assertCurrent(); try { const value = operation(reader.connection.database); if (isPromiseLike(value)) throw new SqliteCoordinatorError("Direct shared-state read must remain synchronous"); assertCurrent(); scheduleReaderRetirement(reader); return value; } catch (error) { if (isSqliteCorruptionError(error)) { invalidateOpenClawStateRuntimeIntegrity(reader.connection.database.db); try { retireReader(reader); } catch (cleanupError) { throwSqliteLifecycleErrors([error, cleanupError], "Direct shared-state read and cleanup failed"); } } throw error; } }); } }; } function scheduleReaderRetirement(reader) { if (!getSqliteRuntimeCapabilities().explicitSqliteCloseReleasesNativeResources || retainedReaders.get(reader.identity.key) !== reader) return; clearTimeout(reader.idleTimer); reader.idleTimer = runInSqliteMaintenanceContext(() => setTimeout(() => { try { retireReader(reader); } catch (error) { process.emitWarning(`Idle shared-state reader cleanup failed: ${String(error)}`); scheduleReaderRetirement(reader); } }, SQLITE_IDLE_HANDLE_TTL_MS)); reader.idleTimer.unref?.(); } /** The host joins this receipt before allowing replacement or deletion of live state. */ function closeRetainedOpenClawStateReadConnections(identity) { const errors = []; for (const reader of retainedReaders.values()) if (identity === void 0 || reader.identity.key === identity) try { retireReader(reader); } catch (error) { errors.push(error); } throwSqliteLifecycleErrors(errors, "Retained shared-state reader cleanup failed."); } function borrowStateReadConnection(pathname, expectedIdentity) { isExistingOpenClawStateSchema(pathname); const identity = readDatabasePathIdentitySync(pathname); if (expectedIdentity !== void 0) assertExistingDatabaseIdentity(pathname, expectedIdentity); for (const previous of retainedReaders.values()) if (previous.identity.canonicalPath === identity.canonicalPath && previous.identity.key !== identity.key) retireReader(previous); if (!identity.key.startsWith("file:")) return openStateReadConnectionResult(pathname, pathname, expectedIdentity); let reader = retainedReaders.get(identity.key); if (reader?.retiring || reader && !reader.connection.database.db.isOpen) { retireReader(reader); reader = void 0; } if (!reader) { const opening = openStateReadConnectionResult(pathname, pathname, identity.key); if (opening.status === "unavailable") return opening; reader = { connection: opening.value, identity, retiring: false }; retainedReaders.set(identity.key, reader); unregisterExitClose ??= registerSqliteCacheExitClose(closeRetainedOpenClawStateReadConnections); } const retained = reader; observeOpenClawDatabaseMaintenanceResource(retained); clearTimeout(retained.idleTimer); return { status: "available", value: { database: { db: retained.connection.database.db, path: pathname }, close(keep) { if (keep && retained.connection.database.db.isOpen && !retained.connection.database.db.isTransaction) scheduleReaderRetirement(retained); else retireReader(retained); return true; } } }; } var SnapshotCleanupIncompleteError = class extends Error {}; function assertStateReadSchema(database, pathname, integrityPolicy) { assertStateReadSchemaForPolicy(database, pathname, isExistingOpenClawStateSchema(pathname, database), integrityPolicy); } function withOpenClawStateReadOnlyLocation(operation, pathname, source, openStateSchemaReadAdmission, expectedIdentity, snapshotRoot, retainConnection = false, readContentVersionRow) { const result = readOpenClawStateReadOnlyLocation(operation, pathname, source, openStateSchemaReadAdmission, expectedIdentity, snapshotRoot, retainConnection, readContentVersionRow); if (result.status === "unavailable") throw result.error; return result.value; } /** Return a failed read only after its native reader and admission have settled. */ function readOpenClawStateReadOnlyLocation(operation, pathname, source, openStateSchemaReadAdmission, expectedIdentity, snapshotRoot, retainConnection = false, readContentVersionRow) { const opening = retainConnection && source === pathname && !snapshotRoot ? borrowStateReadConnection(pathname, expectedIdentity) : openStateReadConnectionResult(pathname, source, expectedIdentity, snapshotRoot, true); if (opening.status === "unavailable") return opening; const opened = opening.value; const errors = []; let closeAdmission; let result; try { closeAdmission = openStateSchemaReadAdmission?.(opened.database.db); const existingSchema = isExistingOpenClawStateSchema(pathname, opened.database.db); try { result = { status: "available", value: runSqliteReadOperationSync(opened.database.db, () => { const coldAdmission = !existingSchema && isSqliteSchemaAdmissionCold(opened.database.db); admitStateReadSchemaFacts(opened.database.db, pathname); if (coldAdmission) return runSqliteReadOperationSync(opened.database.db, () => { assertStateReadSchemaForPolicy(opened.database.db, pathname, existingSchema, void 0, readContentVersionRow); return operation(opened.database); }, "fresh"); assertStateReadSchemaForPolicy(opened.database.db, pathname, existingSchema, void 0, readContentVersionRow); return operation(opened.database); }) }; } catch (error) { if (isSqliteCorruptionError(error)) retireOpenClawStateReadConnectionAfterCorruption(opened.database.db); result = { status: "unavailable", error }; } const location = typeof source === "string" ? source : source.location; if (result.status === "available" && location === pathname && isPromiseLike(result.value)) throw new SqliteCoordinatorError("SQLite source read must remain synchronous"); assertTransactionUsable(opened.database.db); } catch (error) { errors.push(error); } try { closeAdmission?.(); } catch (error) { errors.push(error); } try { const allowDeferredSnapshotCleanup = typeof source !== "string" && errors.length === 0 && result?.status === "available" && !getSqliteRuntimeCapabilities().explicitSqliteCloseReleasesNativeResources; if (!opened.close(errors.length === 0 && result?.status === "available" && !corruptedReaders.has(opened.database.db)) && !allowDeferredSnapshotCleanup) throw new SnapshotCleanupIncompleteError("Shared-state snapshot cleanup is incomplete."); } catch (error) { errors.push(error); } if (errors.length) { if (result?.status === "unavailable" && !errors.includes(result.error)) errors.unshift(result.error); throwSqliteLifecycleErrors(errors, "Shared-state read and reader cleanup failed."); } return result; } function openOpenClawStateReadOnlyLocation(pathname, source) { const connection = openOpenClawStateReadConnection(pathname, source); try { runSqliteReadOperationSync(connection.database.db, () => { admitStateReadSchemaFacts(connection.database.db, pathname); assertStateReadSchema(connection.database.db, pathname); }); } catch (error) { try { connection.close(); } catch (cleanupError) { throwSqliteLifecycleErrors([error, cleanupError], "Shared-state reader admission and cleanup failed."); } throw error; } return connection; } /** Own one native reader; callers retain their runtime or maintenance schema policy. */ function openOpenClawStateReadConnection(pathname, source, expectedIdentity, snapshotRoot) { const result = openStateReadConnectionResult(pathname, source, expectedIdentity, snapshotRoot); if (result.status === "unavailable") throw result.error; return result.value; } function openStateReadConnectionResult(pathname, source, expectedIdentity, snapshotRoot, checkSchemaPolicy = false) { const snapshot = typeof source === "string" ? void 0 : source; const location = typeof source === "string" ? source : source.location; const options = { readOnly: true, timeout: OPENCLAW_SQLITE_BUSY_TIMEOUT_MS }; let releaseToken; const cleanupFailedOpen = (error) => { const errors = [error]; try { releaseToken?.(); } catch (cleanupError) { errors.push(cleanupError); } try { if (snapshot && !snapshot.cleanup()) errors.push(new SnapshotCleanupIncompleteError("Shared-state snapshot cleanup is incomplete.")); } catch (cleanupError) { errors.push(cleanupError); } if (errors.length > 1) throw createSqliteLifecycleAggregateError(errors, "Shared-state reader open and cleanup failed.", error); }; let native; try { if (expectedIdentity !== void 0) assertExistingDatabaseIdentity(location, expectedIdentity); releaseToken = snapshotRoot ? acquireSqliteSnapshotReadToken(snapshotRoot) : void 0; if (checkSchemaPolicy) isExistingOpenClawStateSchema(pathname); if (location === pathname) native = openTrackedStateDatabaseResult(pathname, options); else try { native = { status: "available", database: openNodeSqliteDatabase(location, options) }; } catch (error) { native = { status: "unavailable", error }; } } catch (error) { cleanupFailedOpen(error); throw error; } if (native.status === "unavailable") { cleanupFailedOpen(native.error); return native; } const db = native.database; let closed = false; let closing = false; const database = { db, path: pathname, afterClose: () => { releaseToken?.(); if (snapshot && !snapshot.cleanup()) throw new SnapshotCleanupIncompleteError("Shared-state snapshot cleanup is incomplete."); } }; const connection = { database: { db, path: pathname }, snapshotSource: snapshot ? { retain() { const assertCurrent = () => { if (closing || closed) throw new Error("Shared-state snapshot source is closing or closed"); }; assertCurrent(); return { location: snapshot.location, cleanupRoot: snapshot.cleanupRoot, assertCurrent, release: retainSnapshotTempDirectory(snapshot.cleanupRoot ?? path.dirname(snapshot.location)) }; } } : void 0, close() { if (closed) return false; closing = true; const errors = openClawStateDatabaseCache.closeOpenClawStateDatabaseHandle(database); if (errors.length === 1 && errors[0] instanceof SnapshotCleanupIncompleteError) return false; throwSqliteLifecycleErrors(errors, "Shared-state reader cleanup failed."); closed = true; return true; } }; try { enableNodeSqliteKyselyStatementCache(db); if (expectedIdentity !== void 0) assertExistingDatabaseIdentity(location, expectedIdentity); } catch (error) { try { if (!connection.close()) throw new SnapshotCleanupIncompleteError("Shared-state snapshot cleanup is incomplete."); } catch (cleanupError) { throw createSqliteLifecycleAggregateError([error, cleanupError], "Shared-state reader identity and cleanup failed.", error); } throw error; } return { status: "available", value: connection }; } //#endregion export { withOpenClawStateReadOnlyLocation as a, prepareOpenClawStateDirectReader as i, openOpenClawStateReadConnection as n, openOpenClawStateReadOnlyLocation as r, assertStateReadSchema as t };