UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,044 lines (1,043 loc) • 46.2 kB
import { a as resolveGlobalSingleton, n as readGlobalSingleton } from "./global-singleton-0m-JziJW.mjs"; import { t as mergeProcessEnv } from "./process-env-DlZFJzq6.mjs"; import { t as isAbortError } from "./abort-signal-QqAmLWTl.mjs"; import { r as isPathInside } from "./path-guards-CH7noY0Z.mjs"; import { r as resolveStateDir } from "./state-dir-CYkBUd_G.mjs"; import { l as registerNodeSqliteKyselyQueryErrorHandler, t as clearNodeSqliteKyselyCacheForDatabase } from "./kysely-sync-cache-state-DbAMR_DD.mjs"; import { h as runSqliteReadOperationSync, i as getAdmittedSqliteSchemaFacts, t as admitSqliteSchema } from "./sqlite-schema-facts-BVf0E_0z.mjs"; import { r as assertExistingDatabaseIdentity } from "./sqlite-worker-identity-BRNRpRyc.mjs"; import { a as resolveExistingSqliteFileUri, r as openNodeSqliteDatabase } from "./node-sqlite-DyeJqxPf.mjs"; import { a as createSqliteLifecycleAggregateError, s as throwSqliteLifecycleErrors } from "./sqlite-lifecycle-errors-DuOygjcn.mjs"; import { n as isArtifactPreservingStateRead } from "./artifact-preserving-state-reads-BQnQLAJR.mjs"; import { a as isSqliteLockError, d as sqlitePrimaryResultCode, i as isSqliteCorruptionError } from "./sqlite-error-diagnostics-CWRyv4lz.mjs"; import { t as createSubsystemLogger } from "./subsystem-4NzR43uz.mjs"; import { i as isSqliteSchemaVersionError } from "./sqlite-user-version-DLMl1A7W.mjs"; import { a as registerSqliteCacheExitClose, l as registerSqliteWalWorkerMaintenance, o as runInSqliteMaintenanceContext, s as cancelSqliteWalWriteAdmission } from "./sqlite-wal-DfKCrDTQ.mjs"; import { c as resolveOpenClawStateSqlitePath, r as resolveDatabasePath } from "./openclaw-state-db.paths-CmKkMYdo.mjs"; import { a as isStateDatabaseReadAdmissionInvalidatedError, d as isOpenClawDatabaseMaintenanceResourceOwned, i as createOpenClawStateDatabaseAsyncLifecycle, l as getOpenClawDatabaseMaintenanceResourceScope, m as observeOpenClawDatabaseMaintenanceResource, n as StateDatabaseReadAdmissionInvalidatedError, u as getOpenClawDatabaseMaintenanceScope } from "./openclaw-state-db-async-lifecycle-DwFy2ZTk.mjs"; import { a as assertStateDatabaseReadAllowed, i as assertStateDatabaseAccessAllowed } from "./gateway-state-owner-eDtEELE9.mjs"; import { s as registerLiveSqliteSnapshotOwner } from "./sqlite-snapshot-source-ChTpAJ4b.mjs"; import { T as OpenClawQuarantineReadCleanupError, w as markOpenClawStateDatabaseFailure } from "./openclaw-state-worker-error-Bi-BnNrV.mjs"; import { n as assertSupportedStateSchemaVersion } from "./openclaw-state-db-schema-version-DZI_59qS.mjs"; import { i as sameSqliteFileGeneration, r as readStableSqliteFileGeneration } from "./sqlite-file-generation-HEkrmgCv.mjs"; import { t as SQLITE_IDLE_HANDLE_TTL_MS } from "./sqlite-handle-lifecycle-dWd9h3ii.mjs"; import { o as readOpenClawDatabaseQuarantineFailure } from "./openclaw-quarantine-store-CUJxC7wy.mjs"; import { o as isGatewayExternallySupervised } from "./gateway-supervision-D9M_dluZ.mjs"; import { r as createSqliteWorkerOperationAdmission } from "./sqlite-worker-operation-admission-C5lBzzTk.mjs"; import { r as cloneEnvWithPlatformSemantics } from "./config-env-vars-BzUE_dOh.mjs"; import { r as captureOpenClawStateSchemaReadAdmission, t as assertExistingOpenClawStateSchemaCacheAdmission } from "./openclaw-state-db-schema-policy-B65l9xTe.mjs"; import { n as invalidateOpenClawStateRuntimeIntegrity, o as readTrackedStateDatabaseIdentity, r as closeTrackedStateDatabase } from "./openclaw-state-db-integrity-admission-D2ZPZec-.mjs"; import { existsSync } from "node:fs"; import path from "node:path"; import { AsyncLocalStorage } from "node:async_hooks"; import { isMainThread } from "node:worker_threads"; //#region src/infra/sqlite-terminal-open-latch.ts function generationMatchesPath(pathname, expected) { try { return sameSqliteFileGeneration(expected, readStableSqliteFileGeneration(pathname)); } catch { return false; } } /** * Per-path latch for terminal database-open failures (newer schema, proven * corruption). Recording quarantines the path: any live handle is closed and * every later open fails fast until doctor repairs the file and clears it. */ function createSqliteTerminalOpenLatch(options) { const failures = /* @__PURE__ */ new Map(); return { /** Read the owner's recorded fact without running filesystem or database inspection. */ peek: (pathname) => failures.get(path.resolve(pathname))?.error, get: (pathname) => { const resolvedPath = path.resolve(pathname); const failure = failures.get(resolvedPath); if (!failure) return; if (failure.generation && !generationMatchesPath(resolvedPath, failure.generation)) { failures.delete(resolvedPath); return; } return failure.error; }, async getAsync(pathname, isCurrentGeneration) { const resolvedPath = path.resolve(pathname); for (;;) { const failure = failures.get(resolvedPath); if (!failure?.generation) return failure?.error; const current = await isCurrentGeneration(resolvedPath, failure.generation); if (failures.get(resolvedPath) !== failure) continue; if (!current) { failures.delete(resolvedPath); return; } return failure.error; } }, record: (pathname, error, generation) => { const resolvedPath = path.resolve(pathname); if (generation && !generationMatchesPath(resolvedPath, generation)) return false; failures.set(resolvedPath, { error, ...generation ? { generation } : {} }); options.closeByPath(resolvedPath, error); if (generation && !generationMatchesPath(resolvedPath, generation)) { failures.delete(resolvedPath); return false; } return true; }, clear: (pathname) => { failures.delete(path.resolve(pathname)); }, clearAll: (rootPath) => { for (const pathname of failures.keys()) if (rootPath === void 0 || isPathInside(rootPath, pathname)) failures.delete(pathname); } }; } //#endregion //#region src/state/openclaw-state-db-borrow.ts /** The canonical cache supplies identity and custody; this owner manages its native references. */ function createStateDatabaseRetainer(state, operations) { const admit = (pathname, ownership) => { const scope = getOpenClawDatabaseMaintenanceScope(); if (ownership === "cached-read") scope?.assertReadAdmission(); else scope?.assertAdmission(); operations.assertOpen(pathname, ownership); return scope; }; const retain = (database, scope, readOnly = false) => { operations.capture(database.path).assertCurrent(); if (state.cachedDatabases.get(database.path) !== database || !database.db.isOpen) throw new Error("OpenClaw state database borrow requires its current canonical handle"); const owner = state.borrowers.get(database.db) ?? { references: /* @__PURE__ */ new Set(), retiring: false, cleanupComplete: false }; if (owner.retiring) throw new Error("OpenClaw state database native owner is retiring"); if (!readOnly) observeOpenClawDatabaseMaintenanceResource(database.db); state.borrowers.set(database.db, owner); const isCurrent = () => !scope || isOpenClawDatabaseMaintenanceResourceOwned(database.db, scope); const reference = retainStateDatabaseReference({ owner, retirement: readOnly ? void 0 : { ordinary: scope === void 0, isCurrent, retire: () => { if (!isCurrent()) { owner.retiring = false; return; } operations.retire(database, scope === void 0); } }, stopBeforeRetirement: () => database.walMaintenance.stop(), retainFailedClose: () => operations.retainFailed(database), ownRetirement: (close) => operations.ownRetirement(database, close), onIdle: () => operations.touch(database) }); scope?.own(reference, "shared-references", () => reference.releaseAsync()); return reference; }; const findReadDatabase = (pathname, ownership) => { const resolvedPath = resolveDatabasePath({ path: pathname }); const scope = admit(resolvedPath, ownership); const database = state.cachedDatabases.get(resolvedPath); return { database: database?.db.isOpen ? database : void 0, scope }; }; const retainReadReference = (database, scope) => { const reference = retain(database, scope, true); const assertCurrent = () => { if (state.cachedDatabases.get(database.path) !== database || !database.db.isOpen) throw new Error("Shared-state read lost its original native owner"); }; return { assertCurrent, observe() { assertCurrent(); observeOpenClawDatabaseMaintenanceResource(database.db); }, release: () => reference.release() }; }; return { retain: (database) => retain(database, admit(database.path)), retainForIndependentRead(pathname, ownership) { const { database, scope } = findReadDatabase(pathname, ownership); return database ? retainReadReference(database, scope) : void 0; }, borrowForRead(pathname, ownership) { const { database, scope } = findReadDatabase(pathname, ownership); if (!database) return; if (database.db.isTransaction) throw new Error("Asynchronous shared-state reads cannot run inside a native transaction"); return { database, ...retainReadReference(database, scope) }; } }; } function assertStateDatabaseBorrowersReleased(owner, pathname) { if (owner?.references.size) throw new Error(`OpenClaw state database still has active native borrowers: ${pathname}`); } /** Preserve the requesting owner's retirement when the last reference is only a read pin. */ function retainStateDatabaseReference(params) { const { owner } = params; const reference = {}; owner.references.add(reference); let released = false; const release = () => { if (released || owner.cleanupComplete) { released = true; return; } released = true; owner.references.delete(reference); if (owner.retirement && !owner.retirement.isCurrent()) { owner.retirement = void 0; owner.asyncRetirement = void 0; owner.retiring = false; } if (params.retirement?.isCurrent() && !owner.retirement?.ordinary) owner.retirement = params.retirement; if (owner.references.size > 0) return; if (!owner.retirement) { params.onIdle(); return; } owner.retiring = true; if (owner.asyncRetirement) { owner.asyncRetirement.start(); return; } try { owner.retirement.retire(); } catch (error) { released = false; params.retainFailedClose(); throw error; } owner.retirement = void 0; params.onIdle(); }; return { release, async releaseAsync() { if (owner.cleanupComplete) return; if ((params.retirement?.isCurrent() && !owner.retirement?.ordinary ? params.retirement : owner.retirement)?.isCurrent()) owner.asyncRetirement ??= createAsyncStateDatabaseRetirement(params); release(); await owner.asyncRetirement?.close(); } }; } function createAsyncStateDatabaseRetirement(params) { const { owner } = params; const retained = {}; let pending; let unregister; const finish = (retirement) => { owner.references.delete(retained); if (retirement?.isCurrent()) retirement.retire(); else owner.retiring = false; owner.retirement = void 0; owner.asyncRetirement = void 0; unregister?.(); unregister = void 0; params.onIdle(); }; const run = () => { if (pending) return pending; if (owner.cleanupComplete || owner.references.size > (owner.references.has(retained) ? 1 : 0)) return Promise.resolve(); if (!owner.retirement?.isCurrent()) { finish(); return Promise.resolve(); } owner.retiring = true; owner.references.add(retained); unregister ??= params.ownRetirement(close); pending = Promise.resolve().then(async () => { await params.stopBeforeRetirement(); finish(owner.retirement); }).catch((error) => { owner.references.add(retained); params.retainFailedClose(); throw error; }); pending.catch(() => {}); return pending; }; function close() { const attempt = run(); return attempt.catch((error) => { if (pending === attempt) pending = void 0; throw error; }); } return { start: () => void run(), close }; } //#endregion //#region src/state/openclaw-state-db-cache.admission.ts /** Refresh cached-handle admission and settle failures against its exact native owner. */ function createStateDatabaseCacheAdmission(owner) { return { initialize(database) { admitSqliteSchema(database.db); return runSqliteReadOperationSync(database.db, () => { assertSupportedStateSchemaVersion(database.db, database.path); return getAdmittedSqliteSchemaFacts(database.db); }); }, closeTerminalFailure(pathname, error) { markOpenClawStateDatabaseFailure(error, pathname); owner.invalidate(pathname); const cached = owner.cachedDatabases.get(pathname); const errors = []; try { if (cached) owner.evict(cached); } catch (cleanupError) { errors.push(cleanupError); } try { owner.notifyTerminalFailure(pathname, error); } catch (notificationError) { errors.push(notificationError); } throwSqliteLifecycleErrors(errors, "Terminal shared-state failure cleanup failed"); }, read(pathname, operation) { const database = owner.cachedDatabases.get(pathname); if (!database) return; if (!database.db.isOpen) return operation(database); let admitted = false; try { return runSqliteReadOperationSync(database.db, () => { const facts = getAdmittedSqliteSchemaFacts(database.db); if (!facts || facts !== database.schemaFacts) { assertSupportedStateSchemaVersion(database.db, database.path); database.schemaFacts = facts; } admitted = true; return operation(database); }); } catch (error) { if (admitted) throw error; const failure = error instanceof Error ? error : new Error(String(error)); if (isSqliteCorruptionError(failure)) { owner.evict(database); return; } if (isSqliteSchemaVersionError(failure)) owner.recordSchemaFailure(database.path, failure); throw failure; } } }; } //#endregion //#region src/state/openclaw-state-db-cache.idle.ts const log = createSubsystemLogger("state/db"); /** Schedule native retirement against the canonical cache's handles and borrow pins. */ function createStateDatabaseIdleRetirement({ cachedDatabases, retainedDatabaseHandles, idleTimers, idleReferences, borrowers }, retire) { const touch = (database) => { if (!(cachedDatabases.get(database.path) === database && database.db.isOpen) && retainedDatabaseHandles.get(database.db) !== database) return; const previous = idleTimers.get(database.db); if (previous) { previous.refresh(); return; } const timer = runInSqliteMaintenanceContext(() => setTimeout(() => { idleTimers.delete(database.db); try { if (database.db.isOpen && database.db.isTransaction) touch(database); else if (!borrowers.get(database.db)?.references.size && !idleReferences.get(database.db)?.size) retire(database, false, { busyTimeoutMs: 0, checkpointMode: "PASSIVE" }); } catch (error) { log.warn("Idle shared-state database cleanup failed", { path: database.path, error }); touch(database); } }, SQLITE_IDLE_HANDLE_TTL_MS)); timer.unref(); idleTimers.set(database.db, timer); }; return { touch, /** Retained consumers postpone idle eviction without blocking explicit retirement. */ retain(database) { if (cachedDatabases.get(database.path) !== database || !database.db.isOpen || borrowers.get(database.db)?.retiring) throw new Error("OpenClaw state database idle retention requires its current canonical handle"); const references = idleReferences.get(database.db) ?? /* @__PURE__ */ new Set(); const reference = {}; references.add(reference); idleReferences.set(database.db, references); return () => { if (!references.delete(reference)) return; if (cachedDatabases.get(database.path) === database && database.db.isOpen) touch(database); }; } }; } //#endregion //#region src/state/openclaw-state-worker-context.capture.ts /** Capture read authority without constructing a worker environment. */ function captureOpenClawStateReadContextWithAdmission(pathname, captureAdmission) { const schema = captureOpenClawStateSchemaReadAdmission(pathname); const capturedAdmission = captureAdmission(pathname); const integrity = capturedAdmission.captureIntegrity?.(); const assertPublicationCurrent = capturedAdmission.assertCurrent; let admission = capturedAdmission; let runInCapturedSchemaScope; if (schema) { const inCapturedScope = AsyncLocalStorage.snapshot(); admission = { databasePath: capturedAdmission.databasePath, coordinationKey: capturedAdmission.coordinationKey, get identity() { return capturedAdmission.identity; }, captureIntegrity: capturedAdmission.captureIntegrity, assertCurrent() { capturedAdmission.assertCurrent(); schema.assertCurrent(); } }; runInCapturedSchemaScope = (operation) => inCapturedScope(() => { admission.assertCurrent(); return operation(); }); } return { maintenanceScope: getOpenClawDatabaseMaintenanceScope(), admission, assertPublicationCurrent, existingSchemaPath: schema?.path, stateIntegrity: integrity, runInCapturedSchemaScope, artifactPreservingReads: isArtifactPreservingStateRead("agent", pathname) || void 0 }; } /** Read-only workers need resolved runtime facts, not the initialization environment. */ function captureOpenClawStateReadWorkerContextWithAdmission(options, captureAdmission) { const source = options.env ?? process.env; const env = process.platform === "win32" ? cloneEnvWithPlatformSemantics(source) : source; const environment = { OPENCLAW_STATE_DIR: resolveStateDir(env), ...isGatewayExternallySupervised(env) ? { OPENCLAW_SUPERVISOR_MODE: "external" } : {} }; return { ...captureOpenClawStateReadContextWithAdmission(options.path ?? resolveOpenClawStateSqlitePath(environment), captureAdmission), environment }; } /** Capture host facts before asynchronous work, without opening SQLite. */ function captureOpenClawStateWorkerContextWithAdmission(options, captureAdmission) { const context = captureOpenClawStateReadWorkerContextWithAdmission(options, captureAdmission); return { ...context, initializationEnvironment: mergeProcessEnv([ options.env ?? process.env, { OPENCLAW_STATE_DIR: void 0, OPENCLAW_SUPERVISOR_MODE: void 0 }, context.environment ]), ...options.initializationAgentPaths ? { initializationAgentPaths: options.initializationAgentPaths.map((agentPath) => path.resolve(agentPath)) } : {} }; } //#endregion //#region src/state/openclaw-state-db-cache.wal.ts /** Bind periodic maintenance and its observations to the cache's exact native owner. */ function createStateDatabaseWalOwner({ cachedDatabases, retainedDatabaseHandles, databaseIdentities, asyncResources }, retainForIdle, requireIdentity) { return { ownRetirement(database, close) { const identity = requireIdentity(database); const resource = { async close(selected) { if (!selected || selected.key === identity.key) await close(); } }; const unregister = asyncResources.register(resource); getOpenClawDatabaseMaintenanceResourceScope(database.db)?.own(resource, "shared-references", () => resource.close()); return unregister; }, async stop(pathname, identity) { const databases = /* @__PURE__ */ new Set([...retainedDatabaseHandles.values(), ...cachedDatabases.values()]); await Promise.all([...databases].filter((database) => pathname === void 0 || database.path === pathname || identity !== void 0 && databaseIdentities.get(database.db)?.key === identity.key).flatMap(({ walMaintenance }) => walMaintenance ? [walMaintenance.stop()] : [])); }, register(database, identity, admission, env) { if (!isMainThread) return; const capturedContext = captureOpenClawStateWorkerContextWithAdmission({ path: database.path, env }, () => admission); const controller = new AbortController(); let pending; const cancel = () => { controller.abort(); if (!pending) { unregister(); return; } return pending.then(() => void 0, () => void 0); }; const unregister = asyncResources.register({ async close(selected) { if (selected && selected.key !== identity.key) return; cancel(); await database.walMaintenance.stop(); unregister(); } }); const run = async (request) => { const maintenanceScope = getOpenClawDatabaseMaintenanceResourceScope(database.db); const context = { ...capturedContext, maintenanceScope }; const assertCurrent = () => { controller.signal.throwIfAborted(); context.admission.assertCurrent(); maintenanceScope?.assertAdmission(); if (getOpenClawDatabaseMaintenanceResourceScope(database.db) !== maintenanceScope || cachedDatabases.get(database.path) !== database || !database.db.isOpen) throw new StateDatabaseReadAdmissionInvalidatedError("Shared-state WAL maintenance owner changed"); assertExistingDatabaseIdentity(database.path, identity.key, identity.birthtime); }; let releaseIdle; let operationStarted = false; try { assertCurrent(); releaseIdle = retainForIdle(database); const { runOpenClawStateWorkerOperation } = await import("./openclaw-state-worker-store-x5r3Lkjl.mjs"); assertCurrent(); const result = await runOpenClawStateWorkerOperation(context, (worker) => worker.execute({ type: "database.walMaintenance", input: request }, { signal: controller.signal }), { existingOnly: true, assertCurrent, createAdmission: () => ({ nativeLocations: [database.path, identity.canonicalPath], admission: createSqliteWorkerOperationAdmission((_request, grant) => { assertCurrent(); if (!grant()) throw new StateDatabaseReadAdmissionInvalidatedError("Shared-state WAL maintenance authority expired"); operationStarted = true; }) }) }); assertCurrent(); return result; } catch (error) { if (!operationStarted && (isStateDatabaseReadAdmissionInvalidatedError(error) || controller.signal.aborted && isAbortError(error))) return; throw error; } finally { releaseIdle?.(); } }; registerSqliteWalWorkerMaintenance(database.db, (operation) => { if (controller.signal.aborted) return Promise.resolve(void 0); const active = run(operation); pending = active; return active.finally(() => { if (pending === active) pending = void 0; if (controller.signal.aborted) unregister(); }); }, cancel); }, /** Report the last observation without opening or querying SQLite. */ readHealth() { const database = cachedDatabases.get(path.resolve(resolveOpenClawStateSqlitePath())); return database?.db.isOpen ? database.walMaintenance.health : void 0; } }; } //#endregion //#region src/state/openclaw-state-db-snapshot-owner.ts function createOpenClawStateSnapshotOwnerRegistry() { const releases = /* @__PURE__ */ new WeakMap(); return { register(database, getCurrent) { releases.set(database.db, registerLiveSqliteSnapshotOwner({ database: database.db, databasePath: database.path, owner: "openclaw-state", assertCurrent: () => { if (getCurrent() !== database || !database.db.isOpen) throw new Error("OpenClaw state snapshot owner is no longer current"); } })); }, release(database) { releases.get(database)?.(); releases.delete(database); } }; } const openClawStateSnapshotOwners = resolveGlobalSingleton(Symbol.for("openclaw.stateSnapshotOwners"), createOpenClawStateSnapshotOwnerRegistry); //#endregion //#region src/state/openclaw-state-db-cache.ts const STATE_DATABASE_LIFECYCLE_KEY = Symbol.for("openclaw.stateDatabaseLifecycle"); const stateDatabaseLifecycle = resolveGlobalSingleton(STATE_DATABASE_LIFECYCLE_KEY, () => ({ cachedDatabases: /* @__PURE__ */ new Map(), retainedDatabaseHandles: /* @__PURE__ */ new Map(), idleTimers: /* @__PURE__ */ new WeakMap(), idleReferences: /* @__PURE__ */ new WeakMap(), unregisterRetainedExitClose: void 0, databaseIdentities: /* @__PURE__ */ new WeakMap(), borrowers: /* @__PURE__ */ new WeakMap(), databaseLifecycleListeners: /* @__PURE__ */ new Set(), terminalOpenLatch: createSqliteTerminalOpenLatch({ closeByPath: (pathname, error) => cacheAdmission.closeTerminalFailure(pathname, error) }), asyncResources: createOpenClawStateDatabaseAsyncLifecycle() }), () => closeOpenClawStateDatabaseAsync()); const { cachedDatabases, retainedDatabaseHandles, idleTimers, idleReferences, databaseIdentities, borrowers, databaseLifecycleListeners, terminalOpenLatch, asyncResources } = stateDatabaseLifecycle; const { touch: touchStateDatabase, retain: retainOpenClawStateDatabaseForIdle } = createStateDatabaseIdleRetirement(stateDatabaseLifecycle, retireOpenClawStateDatabaseHandle); const { register: registerStateDatabaseWalAdmission, readHealth: readOpenClawStateWalHealth, ownRetirement: ownStateDatabaseRetirement, stop: stopOpenClawStateDatabaseMaintenance } = createStateDatabaseWalOwner(stateDatabaseLifecycle, retainOpenClawStateDatabaseForIdle, requireOpenClawStateDatabaseIdentity); function notifyOpenClawStateDatabaseLifecycle(event) { const notification = event.kind === "open-error" ? { ...event, identity: event.identity ?? asyncResources.knownIdentity(event.path) } : event; for (const listener of databaseLifecycleListeners) listener(notification); } function notifyOpenClawStateDatabaseClosed(database) { notifyOpenClawStateDatabaseLifecycle({ kind: "closed", path: database.path, identity: requireOpenClawStateDatabaseIdentity(database) }); } function requireOpenClawStateDatabaseIdentity(database) { const identity = databaseIdentities.get(database.db); if (!identity) throw new Error("Published shared-state owner has no recorded database identity"); return identity; } const cacheAdmission = createStateDatabaseCacheAdmission({ cachedDatabases, evict: evictCachedOpenClawStateDatabase, invalidate: (pathname) => asyncResources.invalidate(pathname), notifyTerminalFailure: (pathname, error) => notifyOpenClawStateDatabaseLifecycle({ kind: "terminal-failure", path: pathname, error, identity: asyncResources.knownIdentity(pathname) }), recordSchemaFailure: (pathname, error) => { terminalOpenLatch.record(pathname, error); notifyOpenClawStateDatabaseLifecycle({ kind: "open-error", path: pathname, error }); } }); function registerOpenClawStateDatabaseLifecycleListener(listener) { databaseLifecycleListeners.add(listener); for (const database of cachedDatabases.values()) if (database.db.isOpen) listener({ kind: "opened", database, identity: requireOpenClawStateDatabaseIdentity(database) }); return () => databaseLifecycleListeners.delete(listener); } function retainStateDatabaseClose(database) { retainedDatabaseHandles.set(database.db, database); stateDatabaseLifecycle.unregisterRetainedExitClose ??= registerSqliteCacheExitClose(closeOpenClawStateDatabase); } function ownMaintenanceStateDatabaseHandle(database) { getOpenClawDatabaseMaintenanceScope()?.own(database.db, "shared-handles", async () => { const closingScope = getOpenClawDatabaseMaintenanceScope(); if (!closingScope || !isOpenClawDatabaseMaintenanceResourceOwned(database.db, closingScope)) return; if (cachedDatabases.get(database.path) === database || retainedDatabaseHandles.get(database.db) === database) { await database.walMaintenance?.stop(); if (isOpenClawDatabaseMaintenanceResourceOwned(database.db, closingScope) && (cachedDatabases.get(database.path) === database || retainedDatabaseHandles.get(database.db) === database)) retireOpenClawStateDatabaseHandle(database, false); } }); } function closeUnpublishedOpenClawStateDatabaseHandle(database) { const errors = closeOpenClawStateDatabaseHandle(database); if (retainedDatabaseHandles.get(database.db) === database) ownMaintenanceStateDatabaseHandle(database); return errors; } /** Retain one exact canonical native owner; only the final reference retires its handle. */ const { retain: retainOpenClawStateDatabase, borrowForRead: borrowOpenClawStateDatabaseForAsyncRead, retainForIndependentRead: retainOpenClawStateDatabaseForIndependentRead } = createStateDatabaseRetainer(stateDatabaseLifecycle, { assertOpen(pathname, ownership) { assertOpenClawStateDatabaseOpenAllowed(pathname, ownership); assertExistingOpenClawStateSchemaCacheAdmission(pathname, stateDatabaseLifecycle); }, capture: (pathname) => asyncResources.capture(pathname), retire: retireOpenClawStateDatabaseHandle, retainFailed: retainStateDatabaseClose, ownRetirement: ownStateDatabaseRetirement, touch: touchStateDatabase }); /** Close both physical-handle owners while retaining every cleanup failure. */ function closeOpenClawStateDatabaseHandle(database, options) { clearTimeout(idleTimers.get(database.db)); idleTimers.delete(database.db); try { assertStateDatabaseBorrowersReleased(borrowers.get(database.db), database.path); } catch (error) { const owner = borrowers.get(database.db); if (owner) { owner.retiring = true; owner.retirement = { ordinary: true, isCurrent: () => true, retire: () => { throwSqliteLifecycleErrors(closeOpenClawStateDatabaseHandle(database, options), `OpenClaw state database cleanup failed for ${database.path}.`); owner.cleanupComplete = true; borrowers.delete(database.db); } }; } retainStateDatabaseClose(database); return [error]; } idleReferences.delete(database.db); const errors = []; openClawStateSnapshotOwners.release(database.db); try { cancelSqliteWalWriteAdmission(database.db); database.walMaintenance?.close(options); } catch (error) { errors.push(error); } try { clearNodeSqliteKyselyCacheForDatabase(database.db); } catch (error) { errors.push(error); } try { closeTrackedStateDatabase(database.db); } catch (error) { errors.push(error); } let cleanupPending = false; if (!database.db.isOpen) try { database.afterClose?.(); } catch (error) { errors.push(error); cleanupPending = true; } if (database.db.isOpen || cleanupPending) retainStateDatabaseClose(database); else retainedDatabaseHandles.delete(database.db); if (cachedDatabases.get(database.path)?.db === database.db) cachedDatabases.delete(database.path); if (retainedDatabaseHandles.size === 0) { stateDatabaseLifecycle.unregisterRetainedExitClose?.(); stateDatabaseLifecycle.unregisterRetainedExitClose = void 0; } return errors; } function evictCachedOpenClawStateDatabase(database) { invalidateOpenClawStateRuntimeIntegrity(database.db); if (cachedDatabases.get(database.path) !== database) return false; asyncResources.invalidate(database.path); cachedDatabases.delete(database.path); notifyOpenClawStateDatabaseClosed(database); closeOpenClawStateDatabaseHandle(database, { checkpointMode: "PASSIVE" }); return true; } /** Evict an exact cached shared-state owner after a proven corruption read. */ function evictOpenClawStateDatabaseAfterCorruption(database, error) { return isSqliteCorruptionError(error) && evictCachedOpenClawStateDatabase(database); } /** Publish a fully opened handle and bind query corruption to its exact cache owner. */ function publishOpenClawStateDatabase(database, env) { const { db, path: pathname } = database; const schemaFacts = cacheAdmission.initialize(database); const { identity, admission } = asyncResources.publish(pathname); databaseIdentities.set(db, readTrackedStateDatabaseIdentity(db) ?? identity); cachedDatabases.set(pathname, Object.assign(database, { schemaFacts })); registerStateDatabaseWalAdmission(database, identity, admission, env); touchStateDatabase(database); openClawStateSnapshotOwners.register(database, () => cachedDatabases.get(pathname)); ownMaintenanceStateDatabaseHandle(database); notifyOpenClawStateDatabaseLifecycle({ kind: "opened", database, identity }); registerNodeSqliteKyselyQueryErrorHandler(db, (error) => { if (!db.isTransaction && isSqliteCorruptionError(error)) evictCachedOpenClawStateDatabase(database); }); terminalOpenLatch.clear(pathname); return database; } function getCachedOpenClawStateDatabase(pathname, options) { return withCachedOpenClawStateDatabase(pathname, options, (database) => database); } /** Keep the admitted revision live while a synchronous reader consumes its row facts. */ function withCachedOpenClawStateDatabase(pathname, options, operation) { const resolvedPath = resolveDatabasePath({ path: pathname }); const maintenance = getOpenClawDatabaseMaintenanceScope(); if (options?.readOnly) maintenance?.assertReadAdmission(); else maintenance?.assertAdmission(); assertExistingOpenClawStateSchemaCacheAdmission(resolvedPath, stateDatabaseLifecycle); const runtimeFailure = terminalOpenLatch.get(resolvedPath); if (runtimeFailure) throw runtimeFailure; return cacheAdmission.read(resolvedPath, (database) => { if (borrowers.get(database.db)?.retiring) throw new Error(`OpenClaw state database native borrower cleanup is pending: ${pathname}`); touchStateDatabase(database); return operation(database); }); } function getOpenClawStateDatabaseIfOpenAtPath(pathname) { const cached = getCachedOpenClawStateDatabase(pathname); observeOpenClawDatabaseMaintenanceResource(cached?.db.isOpen ? cached.db : void 0); return cached?.db.isOpen ? cached : void 0; } /** Remove a closed cached owner while fresh-open access is held. */ function closeStaleCachedOpenClawStateDatabase(database) { if (cachedDatabases.get(database.path) !== database) return; asyncResources.invalidate(database.path); const errors = closeOpenClawStateDatabaseHandle(database); notifyOpenClawStateDatabaseClosed(database); throwSqliteLifecycleErrors(errors, `Stale OpenClaw state database cleanup failed for ${database.path}.`); } /** Latch background verification damage so later opens fail without rescanning. */ function recordOpenClawStateDatabaseOpenFailure(pathname, error, generation) { return terminalOpenLatch.record(resolveDatabasePath({ path: pathname }), error, generation); } /** Clear a terminal open failure after doctor rewrites the database file. */ function clearOpenClawStateDatabaseOpenFailure(pathname) { const resolvedPath = resolveDatabasePath({ path: pathname }); terminalOpenLatch.clear(resolvedPath); asyncResources.invalidate(resolvedPath); notifyOpenClawStateDatabaseLifecycle({ kind: "failure-cleared", path: resolvedPath, identity: asyncResources.knownIdentity(resolvedPath) }); } /** Validate the canonical terminal fact before acquiring a domain-operation lease. */ async function getOpenClawStateDatabaseTerminalFailureAsync(context, signal) { context.admission.assertCurrent(); const failure = await terminalOpenLatch.getAsync(context.admission.databasePath, async (_path, generation) => { const { inspectOpenClawStateDatabaseGeneration } = await import("./openclaw-state-worker-store-x5r3Lkjl.mjs"); const matches = await inspectOpenClawStateDatabaseGeneration(context, generation, signal); if (matches === void 0) throw new Error("Recorded shared-state database generation is unavailable"); return matches; }); context.admission.assertCurrent(); return failure; } /** Reject shared-state access after a process-local terminal failure. */ function assertOpenClawStateDatabaseOpenAllowed(pathname, ownership) { const resolvedPath = resolveDatabasePath({ path: pathname }); if (ownership === "cached-read") assertStateDatabaseReadAllowed(pathname); else assertStateDatabaseAccessAllowed(pathname); const { identity } = asyncResources.capture(resolvedPath); const terminalFailure = terminalOpenLatch.get(resolvedPath); if (terminalFailure) throw terminalFailure; for (const database of retainedDatabaseHandles.values()) if (borrowers.get(database.db)?.retiring && (database.path === resolvedPath || databaseIdentities.get(database.db)?.key === identity.key)) throw new Error(`OpenClaw state database native borrower cleanup is pending: ${pathname}`); } function recordOpenClawStateDatabaseLifecycleOpenError(pathname, error) { notifyOpenClawStateDatabaseLifecycle({ kind: "open-error", path: resolveDatabasePath({ path: pathname }), error }); } /** Reject a fresh shared-state open after known corruption until repair clears it. */ function assertOpenClawStateDatabaseFreshOpenAllowedAtPath(pathname, env, onNativeCleanupFailure) { assertOpenClawStateDatabaseOpenAllowed(pathname); let quarantineFailure; try { quarantineFailure = readOpenClawDatabaseQuarantineFailure("state", pathname, { env }); } catch (error) { if (!(error instanceof OpenClawQuarantineReadCleanupError)) throw error; onNativeCleanupFailure?.(error); return; } if (quarantineFailure?.cause instanceof OpenClawQuarantineReadCleanupError) onNativeCleanupFailure?.(quarantineFailure.cause); if (quarantineFailure) { cacheAdmission.closeTerminalFailure(pathname, quarantineFailure); throw quarantineFailure; } } /** SQLite owns checkpoint exclusion; retirement joins the actual native handle. */ function retireOpenClawStateDatabaseHandle(database, retireAdmission = true, options) { assertStateDatabaseBorrowersReleased(borrowers.get(database.db), database.path); const borrowedOwner = borrowers.get(database.db); try { const wasCached = cachedDatabases.get(database.path)?.db === database.db; if (retireAdmission) asyncResources.invalidate(database.path); const errors = closeOpenClawStateDatabaseHandle(database, options); if (wasCached && retireAdmission) try { notifyOpenClawStateDatabaseClosed(database); } catch (error) { errors.push(error); } throwSqliteLifecycleErrors(errors, `OpenClaw state database cleanup failed for ${database.path}.`); } catch (error) { if (borrowedOwner) retainStateDatabaseClose(database); throw error; } if (borrowedOwner) { borrowedOwner.cleanupComplete = true; borrowers.delete(database.db); } } /** Close cached and disposal-only handles, preserving independent cleanup failures. */ function retireOpenClawStateDatabaseHandles(pathname, options, identity) { const databases = /* @__PURE__ */ new Set([...retainedDatabaseHandles.values(), ...cachedDatabases.values()]); const errors = []; let found = false; for (const database of databases) { if (pathname !== void 0 && database.path !== pathname && (identity === void 0 || databaseIdentities.get(database.db)?.key !== identity.key)) continue; found = true; try { retireOpenClawStateDatabaseHandle(database, true, options); } catch (error) { errors.push(error); } } throwSqliteLifecycleErrors(errors, "OpenClaw state database cleanup failed."); return found; } function closeOpenClawStateDatabaseByPath(pathname, options) { return retireOpenClawStateDatabaseHandles(resolveDatabasePath({ path: pathname }), options, asyncResources.identity(pathname)); } function closeOpenClawStateDatabase(options) { retireOpenClawStateDatabaseHandles(void 0, options); } /** Register a resource owner before it can admit any shared-state worker opens. */ function registerOpenClawStateDatabaseAsyncResource(resource) { return asyncResources.register(resource); } /** Capture the canonical read generation before any asynchronous worker admission. */ const captureOpenClawStateDatabaseReadAdmission = asyncResources.capture; const captureOpenClawStateIntegrityAdmission = asyncResources.integrity; /** Bind worker-created storage to its captured admission without publishing a native handle. */ function publishOpenClawStateDatabaseWorkerAdmission(admission) { asyncResources.publish(admission.databasePath); admission.assertCurrent(); } /** Drain worker resources before native checkpoint/close at one exact path. */ function closeOpenClawStateDatabaseByPathAsync(pathname, options) { const resolvedPath = resolveDatabasePath({ path: pathname }); return asyncResources.close(resolvedPath, async (identity) => { await stopOpenClawStateDatabaseMaintenance(resolvedPath, identity); return retireOpenClawStateDatabaseHandles(resolvedPath, options, identity); }); } /** Orderly lifecycle close; synchronous close remains native/exit cleanup only. */ async function closeOpenClawStateDatabaseAsync(options) { await asyncResources.close(void 0, async () => { await stopOpenClawStateDatabaseMaintenance(); return retireOpenClawStateDatabaseHandles(void 0, options); }); } function isOpenClawStateDatabaseOpen(pathname) { if (pathname !== void 0) return cachedDatabases.get(resolveDatabasePath({ path: pathname }))?.db.isOpen === true; return Array.from(cachedDatabases.values()).some((database) => database.db.isOpen); } /** * Close shared state handles and clear terminal failure latches for test isolation. * Worker retirement continues after return; await closeOpenClawStateDatabaseAsync() * before raw SQLite or file access to a database that workers have used. */ function closeOpenClawStateDatabaseForTest() { closeOpenClawStateDatabase(); terminalOpenLatch.clearAll(); } /** * Owners that register with this cache must serve only the published lifecycle. * Every runtime chunk shares that lifecycle; only a retired module generation * (a re-evaluated test graph whose detached work outlived its file) can differ. */ function assertPublishedOpenClawStateDatabaseLifecycle() { if (readGlobalSingleton(STATE_DATABASE_LIFECYCLE_KEY) !== stateDatabaseLifecycle) throw new Error("OpenClaw state database lifecycle is retired; its owners cannot serve the published lifecycle"); } /** Process-wide owner for cached shared-state handles and terminal open failures. */ const openClawStateDatabaseCache = { assertOpenClawStateDatabaseFreshOpenAllowedAtPath, assertOpenClawStateDatabaseOpenAllowed, clearOpenClawStateDatabaseOpenFailure, closeOpenClawStateDatabase, closeOpenClawStateDatabaseByPath, closeOpenClawStateDatabaseForTest, closeOpenClawStateDatabaseHandle, closeUnpublishedOpenClawStateDatabaseHandle, closeStaleCachedOpenClawStateDatabase, evictCachedOpenClawStateDatabase, evictOpenClawStateDatabaseAfterCorruption, getCachedOpenClawStateDatabase, withCachedOpenClawStateDatabase, getOpenClawStateDatabaseRecordedFailure: terminalOpenLatch.peek, getOpenClawStateDatabaseIfOpenAtPath, getKnownOpenClawStateDatabaseIdentity: asyncResources.knownIdentity, isOpenClawStateDatabaseOpen, /** Only the exact published, open owner carries canonical schema readiness. */ isOpenClawStateDatabaseSchemaReady: (database) => cachedDatabases.get(database.path) === database && database.db.isOpen, publishOpenClawStateDatabase, recordOpenClawStateDatabaseOpenFailure, recordOpenClawStateDatabaseLifecycleOpenError, touchStateDatabase }; /** Offline removal drains local work and refuses an active native SQLite owner. */ async function prepareOpenClawStateDatabaseRemoval(pathname, assertOwnerCurrent) { const databasePath = resolveDatabasePath({ path: pathname }); const maintenance = getOpenClawDatabaseMaintenanceScope(); if (!maintenance?.ownsSchemaMaintenance) throw new Error("State removal requires the installation's maintenance owner"); maintenance.assertAdmission(); const releaseAdmission = asyncResources.holdExclusion(databasePath); try { await closeOpenClawStateDatabaseByPathAsync(databasePath); maintenance.assertOwnerCurrent(); retainSqliteDatabaseRemovalExclusion(databasePath, maintenance); maintenance.assertOwnerCurrent(); } catch (error) { releaseAdmission(); throw error; } let active = true; return { assertCurrent() { if (!active) throw new Error("State removal admission has been released"); assertOwnerCurrent(); }, release() { if (active) { active = false; releaseAdmission(); } } }; } function retainSqliteDatabaseRemovalExclusion(databasePath, maintenance) { if (!existsSync(databasePath)) return; const database = openNodeSqliteDatabase(resolveExistingSqliteFileUri(databasePath)); const close = () => { if (!database.isOpen) return; const errors = []; try { if (database.isTransaction) database.exec("ROLLBACK"); } catch (error) { errors.push(error); } try { database.close(); } catch (error) { errors.push(error); } throwSqliteLifecycleErrors(errors, "SQLite state removal exclusion cleanup failed."); }; try { maintenance.own(database, "shared-handles", close); database.exec("PRAGMA busy_timeout = 0; PRAGMA locking_mode = EXCLUSIVE; BEGIN EXCLUSIVE;"); } catch (error) { try { close(); } catch (cleanupError) { throw createSqliteLifecycleAggregateError([error, cleanupError], "SQLite state removal admission and cleanup both failed.", error); } if (isSqliteLockError(error)) throw new Error("Cannot remove OpenClaw state directory while another SQLite connection is active", { cause: error }); const code = sqlitePrimaryResultCode(error); if (code !== 11 && code !== 26) throw error; return; } if (process.platform === "win32") close(); } //#endregion export { retainOpenClawStateDatabaseForIndependentRead as C, createSqliteTerminalOpenLatch as D, captureOpenClawStateWorkerContextWithAdmission as E, retainOpenClawStateDatabaseForIdle as S, captureOpenClawStateReadWorkerContextWithAdmission as T, recordOpenClawStateDatabaseOpenFailure as _, clearOpenClawStateDatabaseOpenFailure as a, requireOpenClawStateDatabaseIdentity as b, closeOpenClawStateDatabaseByPath as c, getOpenClawStateDatabaseTerminalFailureAsync as d, isOpenClawStateDatabaseOpen as f, readOpenClawStateWalHealth as g, publishOpenClawStateDatabaseWorkerAdmission as h, captureOpenClawStateIntegrityAdmission as i, closeOpenClawStateDatabaseByPathAsync as l, prepareOpenClawStateDatabaseRemoval as m, borrowOpenClawStateDatabaseForAsyncRead as n, closeOpenClawStateDatabase as o, openClawStateDatabaseCache as p, captureOpenClawStateDatabaseReadAdmission as r, closeOpenClawStateDatabaseAsync as s, assertPublishedOpenClawStateDatabaseLifecycle as t, closeOpenClawStateDatabaseForTest as u, registerOpenClawStateDatabaseAsyncResource as v, captureOpenClawStateReadContextWithAdmission as w, retainOpenClawStateDatabase as x, registerOpenClawStateDatabaseLifecycleListener as y };