UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

655 lines (654 loc) 30.1 kB
import { i as resolveGlobalSingleton } from "./global-singleton-Dc_stLtU.js"; import { r as isPathInside } from "./path-guards-Cp-mGr3-.js"; import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js"; import { w as resolveStateDir } from "./paths-D2sRr1a_.js"; import "./session-key-BnWWjqNc.js"; import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js"; import { m as setSqliteBusyTimeout, t as openNodeSqliteDatabase, u as runSqliteImmediateTransactionSync } from "./node-sqlite-BpQX3W0e.js"; import { n as enableNodeSqliteKyselyStatementCache } from "./kysely-sync-COmh4HWh.js"; import { R as clearOpenClawDatabaseQuarantine, V as createSqliteTerminalOpenLatch, d as createOpenClawDatabaseVerificationError, z as readOpenClawDatabaseQuarantine } from "./openclaw-state-db-cache-C7ljO0xP.js"; import { o as OPENCLAW_SQLITE_BUSY_TIMEOUT_MS } from "./openclaw-state-db-contract-DYCYxE4w.js"; import { i as isSqliteSchemaVersionError } from "./sqlite-user-version-DFJCxX41.js"; import { a as runSqliteIntegrityOperationSync, i as isTerminalSqliteIntegrityError, r as confirmSqliteFileIntegrity } from "./sqlite-integrity-NpEtFIdK.js"; import { Ht as deferSqlitePostCommitPublication, Ut as withSqlitePostCommitPublications, ut as quarantineOrphanedSqliteSidecars } from "./openclaw-state-db-BRTnL-D8.js"; import { i as registerSqliteCacheExitClose, n as configureSqlitePreSchemaPragmas, t as configureSqliteConnectionPragmas } from "./sqlite-wal-B4waQq_w.js"; import { o as isGatewayExternallySupervised } from "./gateway-supervision-D7p37rG2.js"; import { t as createDeferredCore } from "./deferred-D0La5CRk.js"; import { n as withOpenClawStateLease } from "./openclaw-state-lease-Ciyr2uVu.js"; import { t as assertSqliteIntegrityInWorker } from "./sqlite-integrity-worker-CWVt3BRk.js"; import { D as isIncognitoOpenClawAgentSqlitePath, E as releaseAgentDeletionDatabaseCleanup, S as assertAgentDeletionDatabaseCleanupAccess, T as registerAgentDeletionDatabaseCleanup, a as assertNoOpenClawAgentDatabaseLeases, c as releaseOpenClawAgentDatabaseLease, k as resolveOpenClawAgentSqlitePath, o as assertOpenClawAgentDatabaseLease, s as claimOpenClawAgentDatabaseLease, t as AGENT_DATABASE_MAINTENANCE_LEASE, u as runWithAgentDatabaseMaintenanceAuthority, w as getAgentDeletionDatabaseCleanup, x as assertAgentDeletionCleanupAliases } from "./openclaw-agent-db-lease-Djvd6LWN.js"; import { C as assertExistingAgentSchemaOwner, E as readExistingAgentSchemaMeta, S as assertCanonicalAgentPersistenceVersion, T as assertSupportedAgentSchemaVersion, _ as setValidatedOpenClawAgentDatabaseOwner, a as ensureAgentSchema, d as registerOpenClawAgentDatabase, f as unregisterOpenClawAgentDatabase, g as invalidateOpenClawAgentDatabaseValidation, h as getValidatedOpenClawAgentDatabaseOwner, i as agentDatabaseIntegrityBeforeMutationSteps, m as clearOpenClawAgentDatabaseValidationCache, u as isSameOpenClawAgentDatabasePath, x as ensureOpenClawAgentDatabasePermissions } from "./openclaw-agent-db-maintenance-wTIy-jt-.js"; import { existsSync } from "node:fs"; import path from "node:path"; //#region src/state/openclaw-agent-db-lifecycle.ts const OPENCLAW_AGENT_DB_OPEN_HANDLE_CAP = 64; const agentDbLog = createSubsystemLogger("state/agent-db"); const cache = resolveGlobalSingleton(Symbol.for("openclaw.agentDatabaseLifecycle"), () => ({ databases: /* @__PURE__ */ new Map(), borrowers: /* @__PURE__ */ new WeakMap(), incognito: /* @__PURE__ */ new WeakSet(), generation: 0, failures: /* @__PURE__ */ new Map(), leases: /* @__PURE__ */ new Map(), terminal: createSqliteTerminalOpenLatch({ closeByPath: closeOpenClawAgentDatabaseByPath }), unregisterExitClose: null, pending: /* @__PURE__ */ new Map(), activePending: /* @__PURE__ */ new Set(), retainedCloses: /* @__PURE__ */ new Set() })); function retainFailedAgentDatabaseClose(agentId, pathname, close) { const retained = { agentId, path: pathname, close: () => { close(); cache.retainedCloses.delete(retained); } }; cache.retainedCloses.add(retained); cache.unregisterExitClose ??= registerSqliteCacheExitClose(closeOpenClawAgentDatabases); } function revokePendingAgentDatabaseOpen(pathname, expectedAgentId) { for (const pending of cache.activePending) if (pending.path === pathname && (expectedAgentId === void 0 || pending.agentId === expectedAgentId)) pending.controller.abort(/* @__PURE__ */ new Error(`Agent database open was revoked: ${pathname}`)); } function retainAgentDatabase(db) { const borrowers = cache.borrowers.get(db) ?? /* @__PURE__ */ new Set(); const borrower = {}; borrowers.add(borrower); cache.borrowers.set(db, borrowers); return () => { borrowers.delete(borrower); }; } function closeCachedOpenClawAgentDatabase(database, options = {}) { database.walMaintenance.close(options.eviction ? { checkpointMode: "PASSIVE" } : void 0); if (database.db.isOpen) database.db.close(); const lease = cache.leases.get(database.path); if (lease) { releaseOpenClawAgentDatabaseLease(lease.leaseId, { env: lease.env }); cache.leases.delete(database.path); } releaseAgentDeletionDatabaseCleanup(database); } function evictLruAgentDatabaseHandles() { while (cache.databases.size >= 64) { let evicted = false; for (const [pathname, database] of cache.databases) { if (database.db.isOpen && (database.db.isTransaction || cache.borrowers.get(database.db)?.size || cache.incognito.has(database))) continue; closeCachedOpenClawAgentDatabase(database, { eviction: true }); cache.databases.delete(pathname); cache.failures.delete(pathname); if (cache.incognito.has(database)) cache.generation += 1; agentDbLog.debug("evicted OpenClaw agent database handle", { agentId: database.agentId, openHandles: cache.databases.size, path: pathname }); evicted = true; break; } if (!evicted) { agentDbLog.warn("agent database handle cap exceeded; all cached handles are retained", { cap: 64, openHandles: cache.databases.size }); return; } } } /** Close one cached agent database identified by its exact resolved pathname. */ function closeOpenClawAgentDatabaseByPath(pathname, expectedAgentId) { const resolvedPath = path.resolve(pathname); revokePendingAgentDatabaseOpen(resolvedPath, expectedAgentId); for (const retained of cache.retainedCloses) if (retained.path === resolvedPath && (expectedAgentId === void 0 || retained.agentId === expectedAgentId)) retained.close(); const database = cache.databases.get(resolvedPath); if (!database || expectedAgentId !== void 0 && database.agentId !== expectedAgentId) return false; const incognito = cache.incognito.has(database); closeCachedOpenClawAgentDatabase(database); cache.databases.delete(resolvedPath); cache.failures.delete(resolvedPath); if (incognito) cache.generation += 1; if (cache.databases.size === 0 && cache.retainedCloses.size === 0) { cache.unregisterExitClose?.(); cache.unregisterExitClose = null; } return true; } /** * Converge a terminating worker's cached handle and durable lease without * turning an already committed worker result into an operation failure. * Callers own a bounded retry policy and must surface an unsettled result. */ function settleOpenClawAgentDatabaseWorkerClose(pathname) { const resolvedPath = path.resolve(pathname); const errors = []; const database = cache.databases.get(resolvedPath); if (database) { try { database.walMaintenance.close(); } catch (error) { errors.push(error instanceof Error ? error : new Error(String(error))); } if (database.db.isOpen) try { database.db.close(); } catch (error) { errors.push(error instanceof Error ? error : new Error(String(error))); } if (!database.db.isOpen) { const incognito = cache.incognito.has(database); cache.databases.delete(resolvedPath); cache.failures.delete(resolvedPath); if (incognito) cache.generation += 1; if (cache.databases.size === 0 && cache.retainedCloses.size === 0) { cache.unregisterExitClose?.(); cache.unregisterExitClose = null; } } } if (!cache.databases.get(resolvedPath)?.db.isOpen) { const lease = cache.leases.get(resolvedPath); if (lease) try { releaseOpenClawAgentDatabaseLease(lease.leaseId, { env: lease.env }); cache.leases.delete(resolvedPath); } catch (error) { errors.push(error instanceof Error ? error : new Error(String(error))); } } return { errors, settled: !cache.databases.get(resolvedPath)?.db.isOpen && !cache.leases.has(resolvedPath) }; } /** Close cached agent handles, optionally restricted to one runtime root. */ function closeOpenClawAgentDatabases(rootPath) { for (const pathname of cache.pending.keys()) if (rootPath === void 0 || isPathInside(rootPath, pathname)) revokePendingAgentDatabaseOpen(pathname); for (const retained of cache.retainedCloses) if (rootPath === void 0 || isPathInside(rootPath, retained.path)) retained.close(); for (const pathname of cache.databases.keys()) if (rootPath === void 0 || isPathInside(rootPath, pathname)) closeOpenClawAgentDatabaseByPath(pathname); } /** Drain native opens before a lifecycle owner releases shared state or removes its root. */ async function closeOpenClawAgentDatabasesAsync(rootPath) { while (true) { const pending = [...cache.activePending].filter((owner) => rootPath === void 0 || isPathInside(rootPath, owner.path)); if (pending.length === 0) break; for (const owner of pending) revokePendingAgentDatabaseOpen(owner.path); await Promise.allSettled(pending.map((owner) => owner.promise)); } closeOpenClawAgentDatabases(rootPath); } /** Read a database's durable role and agent owner without mutating it. */ function inspectOpenClawAgentDatabaseOwner(pathname) { let db; try { const resolvedPath = path.resolve(pathname); const opened = cache.databases.get(resolvedPath); if (opened?.db.isOpen && !cache.failures.has(resolvedPath)) { assertSupportedAgentSchemaVersion(opened.db, pathname); return { status: "owned", agentId: opened.agentId }; } db = openNodeSqliteDatabase(pathname, { readOnly: true }); setSqliteBusyTimeout(db, OPENCLAW_SQLITE_BUSY_TIMEOUT_MS); assertSupportedAgentSchemaVersion(db, pathname); const existing = readExistingAgentSchemaMeta(db); if (!existing) return { status: "unowned" }; if (existing.role !== "agent" || !existing.agentId) return { status: "unreadable" }; return { status: "owned", agentId: normalizeAgentId(existing.agentId) }; } catch { return { status: "unreadable" }; } finally { db?.close(); } } //#endregion //#region src/state/openclaw-agent-db.ts /** * Per-agent SQLite database lifecycle and shared-state registration. * * Each opened agent database is schema-owned by one normalized agent id, cached * per pathname, protected with private file modes, and registered in the shared * OpenClaw state database for discovery and maintenance. */ const OPENCLAW_AGENT_DB_SLOW_OPEN_MS = 1e3; var IncognitoAgentDatabasePathCollisionError = class extends Error { constructor(pathname) { super(`Incognito agent database sentinel path already exists: ${pathname}. This filename is reserved for in-memory incognito state; move or rename the file and retry.`); this.name = "IncognitoAgentDatabasePathCollisionError"; this.path = pathname; } }; /** Reconfirm an advisory worker failure on the live owner connection. */ function confirmOpenClawAgentDatabaseIntegrity(pathname) { const resolvedPath = path.resolve(pathname); closeOpenClawAgentDatabaseByPath(resolvedPath); invalidateOpenClawAgentDatabaseValidation(resolvedPath); return confirmSqliteFileIntegrity(resolvedPath, resolvedPath); } /** Latch background verification damage so later opens fail without rescanning. */ function recordOpenClawAgentDatabaseOpenFailure(pathname, error, generation) { const recorded = cache.terminal.record(pathname, error, generation); if (recorded) invalidateOpenClawAgentDatabaseValidation(pathname); return recorded; } /** * Clear a terminal open failure after doctor rewrites the database file. * Returns false when the persisted quarantine row survived; callers must * surface that, or the next open re-quarantines the repaired file. */ function clearOpenClawAgentDatabaseOpenFailure(pathname, options = {}) { const resolvedPath = path.resolve(pathname); const cleared = clearOpenClawDatabaseQuarantine(resolvedPath, { env: options.env }); cache.terminal.clear(resolvedPath); return cleared; } /** Open or return a cached per-agent database after schema and owner validation. */ function openOpenClawAgentDatabase(options) { return runSqliteIntegrityOperationSync(openOpenClawAgentDatabaseSteps(options)); } /** Retain the verified connection through an async caller's operation; disposal still revokes it. */ function withOpenClawAgentDatabaseAsync(inputOptions, operation) { const options = { ...inputOptions, env: { ...inputOptions.env ?? process.env } }; const agentId = normalizeAgentId(options.agentId); const pathname = resolveOpenClawAgentSqlitePath({ ...options, agentId }); const existing = cache.pending.get(pathname); if (existing?.agentId !== void 0 && existing.agentId !== agentId) return Promise.reject(/* @__PURE__ */ new Error(`Agent database ${pathname} is opening for ${existing.agentId}`)); if (existing?.controller.signal.aborted) return existing.promise.then(() => withOpenClawAgentDatabaseAsync(options, operation), () => withOpenClawAgentDatabaseAsync(options, operation)); const pending = existing ?? startOpenClawAgentDatabaseAdmission(options, agentId, pathname); pending.operations += 1; return pending.promise.then((database) => { pending.controller.signal.throwIfAborted(); if (cache.databases.get(pathname) !== database || !database.db.isOpen) throw new Error(`Agent database closed before its admitted operation: ${pathname}`); assertAgentDeletionDatabaseCleanupAccess(database, options); return operation(database); }).finally(() => { pending.operations -= 1; if (!pending.operations) pending.releaseBorrow?.(); }); } function startOpenClawAgentDatabaseAdmission(options, agentId, pathname) { const completion = createDeferredCore(); const pending = { agentId, path: pathname, controller: new AbortController(), promise: completion.promise, operations: 0 }; cache.pending.set(pathname, pending); cache.activePending.add(pending); const operation = openOpenClawAgentDatabaseSteps(options, pending); (async () => { let step = operation.next(); while (!step.done) { let failure; let failed = false; try { await assertSqliteIntegrityInWorker(pathname, OPENCLAW_SQLITE_BUSY_TIMEOUT_MS, pending.controller.signal); } catch (error) { failure = error; failed = true; } try { pending.controller.signal.throwIfAborted(); if (cache.pending.get(pathname) !== pending) throw new Error(`Agent database open was replaced: ${pathname}`); getAgentDeletionDatabaseCleanup(options)?.assertCurrent(); pending.assertHeld?.(); assertSupportedAgentSchemaVersion(step.value.database, pathname); assertExistingAgentSchemaOwner(readExistingAgentSchemaMeta(step.value.database), agentId, pathname); } catch (error) { failure = error; failed = true; } step = failed ? operation.throw(failure) : operation.next(); } pending.releaseBorrow = retainAgentDatabase(step.value.db); return step.value; })().then((database) => { if (cache.pending.get(pathname) === pending) cache.pending.delete(pathname); cache.activePending.delete(pending); if (pending.controller.signal.aborted || cache.databases.get(pathname) !== database || !database.db.isOpen) completion.reject(pending.controller.signal.reason ?? /* @__PURE__ */ new Error(`Agent database closed before admission completed: ${pathname}`)); else completion.resolve(database); }, (error) => { if (cache.pending.get(pathname) === pending) cache.pending.delete(pathname); cache.activePending.delete(pending); completion.reject(error); }); return pending; } function* openOpenClawAgentDatabaseSteps(options, pending) { const agentId = normalizeAgentId(options.agentId); const databaseOptions = { ...options, agentId }; const pathname = resolveOpenClawAgentSqlitePath(databaseOptions); getAgentDeletionDatabaseCleanup(databaseOptions)?.assertCurrent(); const incognito = isIncognitoOpenClawAgentSqlitePath(pathname, databaseOptions); const opened = getOpenClawAgentDatabaseIfOpen(databaseOptions); if (opened) { cache.databases.delete(pathname); cache.databases.set(pathname, opened); return opened; } if (!pending) revokePendingAgentDatabaseOpen(pathname); const cached = cache.databases.get(pathname); if (incognito) { if (existsSync(pathname)) throw new IncognitoAgentDatabasePathCollisionError(pathname); if (cached) { closeCachedOpenClawAgentDatabase(cached); cache.databases.delete(pathname); cache.failures.delete(pathname); } const db = openNodeSqliteDatabase(":memory:", { allowExtension: !process.permission }); db.enableLoadExtension(false); configureSqlitePreSchemaPragmas(db, { busyTimeoutMs: OPENCLAW_SQLITE_BUSY_TIMEOUT_MS }); const walMaintenance = configureSqliteConnectionPragmas(db, { busyTimeoutMs: OPENCLAW_SQLITE_BUSY_TIMEOUT_MS, databaseLabel: `openclaw-agent-incognito:${agentId}`, foreignKeys: true, synchronous: "NORMAL" }); ensureAgentSchema(db, agentId, pathname); const database = { agentId, db, path: pathname, walMaintenance }; cache.incognito.add(database); cache.unregisterExitClose ??= registerSqliteCacheExitClose(closeOpenClawAgentDatabases); cache.databases.set(pathname, database); cache.generation += 1; return database; } quarantineOrphanedSqliteSidecars(pathname); const terminalFailure = cache.terminal.get(pathname); if (terminalFailure) throw terminalFailure; let persistedFailure; try { const quarantine = readOpenClawDatabaseQuarantine(pathname, { env: databaseOptions.env }); if (quarantine) persistedFailure = createOpenClawDatabaseVerificationError("agent", pathname, quarantine.reason); } catch {} if (persistedFailure) { recordOpenClawAgentDatabaseOpenFailure(pathname, persistedFailure); throw persistedFailure; } if (cached) { closeCachedOpenClawAgentDatabase(cached); cache.databases.delete(pathname); cache.failures.delete(pathname); } const leaseEnvironment = { OPENCLAW_STATE_DIR: resolveStateDir(options.env ?? process.env), ...isGatewayExternallySupervised(options.env ?? process.env) ? { OPENCLAW_SUPERVISOR_MODE: "external" } : {} }; const leaseId = claimOpenClawAgentDatabaseLease({ agentId, path: pathname, env: leaseEnvironment }); if (pending) pending.assertHeld = () => assertOpenClawAgentDatabaseLease(leaseId, { agentId, path: pathname, env: leaseEnvironment }); const openStartedAt = Date.now(); let openedDb; let openedDatabase; let openedWalMaintenance; try { ensureOpenClawAgentDatabasePermissions(pathname, databaseOptions); evictLruAgentDatabaseHandles(); const db = openNodeSqliteDatabase(pathname, { allowExtension: !process.permission }); db.enableLoadExtension(false); enableNodeSqliteKyselyStatementCache(db); openedDb = db; let isValidatedReopen = getValidatedOpenClawAgentDatabaseOwner(pathname) === agentId; const walMaintenance = yield* (function* () { let maintenance; try { db.exec(`PRAGMA busy_timeout = ${OPENCLAW_SQLITE_BUSY_TIMEOUT_MS};`); assertSupportedAgentSchemaVersion(db, pathname); const existingSchema = readExistingAgentSchemaMeta(db); assertExistingAgentSchemaOwner(existingSchema, agentId, pathname); const requiresCurrentVersionConvergence = yield* agentDatabaseIntegrityBeforeMutationSteps(db, agentId, pathname); if (isValidatedReopen && (!existingSchema || requiresCurrentVersionConvergence)) { invalidateOpenClawAgentDatabaseValidation(pathname); isValidatedReopen = false; } assertCanonicalAgentPersistenceVersion(db, pathname); configureSqlitePreSchemaPragmas(db, { busyTimeoutMs: OPENCLAW_SQLITE_BUSY_TIMEOUT_MS }); maintenance = configureSqliteConnectionPragmas(db, { busyTimeoutMs: OPENCLAW_SQLITE_BUSY_TIMEOUT_MS, databaseLabel: `openclaw-agent:${agentId}`, databasePath: pathname, foreignKeys: true, synchronous: "NORMAL" }); openedWalMaintenance = maintenance; if (!isValidatedReopen) ensureAgentSchema(db, agentId, pathname); return maintenance; } catch (err) { maintenance?.close(); db.close(); const current = cache.databases.get(pathname); if (!current || current.db === db) invalidateOpenClawAgentDatabaseValidation(pathname); if (err instanceof Error && (isSqliteSchemaVersionError(err) || isTerminalSqliteIntegrityError(err))) recordOpenClawAgentDatabaseOpenFailure(pathname, err); throw err; } })(); if (pending) evictLruAgentDatabaseHandles(); ensureOpenClawAgentDatabasePermissions(pathname, databaseOptions); const database = { agentId, db, path: pathname, walMaintenance }; openedDatabase = database; const cleanup = registerAgentDeletionDatabaseCleanup(database, databaseOptions); if (cleanup) { const release = retainAgentDatabase(db); cleanup.registerClose(() => { release(); if (cache.databases.get(database.path) === database) closeOpenClawAgentDatabaseByPath(database.path, database.agentId); else if (database.db.isOpen) throw new Error("Agent deletion cleanup lost its database close owner."); }); } if (!isValidatedReopen) { registerOpenClawAgentDatabase({ agentId, path: pathname, env: options.env }); setValidatedOpenClawAgentDatabaseOwner(pathname, agentId); } cache.terminal.clear(pathname); cache.unregisterExitClose ??= registerSqliteCacheExitClose(closeOpenClawAgentDatabases); const elapsedMs = Date.now() - openStartedAt; if (elapsedMs >= OPENCLAW_AGENT_DB_SLOW_OPEN_MS) agentDbLog.warn("slow OpenClaw agent database open", { agentId, elapsedMs, path: pathname, thresholdMs: OPENCLAW_AGENT_DB_SLOW_OPEN_MS }); cache.leases.set(pathname, { leaseId, env: leaseEnvironment }); cache.databases.set(pathname, database); return database; } catch (error) { let closeError; if (openedDatabase) try { closeCachedOpenClawAgentDatabase(openedDatabase); } catch (caught) { closeError = caught; } if (openedDb?.isOpen) { if (pending && cache.databases.has(pathname) && cache.databases.get(pathname)?.db !== openedDb) { const retainedDb = openedDb; retainFailedAgentDatabaseClose(agentId, pathname, () => { openedWalMaintenance?.close(); if (retainedDb.isOpen) retainedDb.close(); releaseOpenClawAgentDatabaseLease(leaseId, { env: leaseEnvironment }); }); throw error; } invalidateOpenClawAgentDatabaseValidation(pathname); const retainedDatabase = openedDatabase ?? { agentId, db: openedDb, path: pathname, walMaintenance: openedWalMaintenance ?? { checkpoint: () => false, close: () => false } }; cache.databases.set(pathname, retainedDatabase); cache.leases.set(pathname, { leaseId, env: leaseEnvironment }); cache.failures.set(pathname, closeError ?? error); cache.unregisterExitClose ??= registerSqliteCacheExitClose(closeOpenClawAgentDatabases); } else try { releaseOpenClawAgentDatabaseLease(leaseId, { env: leaseEnvironment }); } catch (releaseError) { retainFailedAgentDatabaseClose(agentId, pathname, () => releaseOpenClawAgentDatabaseLease(leaseId, { env: leaseEnvironment })); throw releaseError; } throw closeError ?? error; } } /** Queue a non-throwing runtime publication on the outer database commit edge. */ function deferOpenClawAgentPostCommitPublication(database, publish) { return deferSqlitePostCommitPublication(database.db, publish); } function runOpenClawAgentWriteTransaction(operation, options, transactionOptions = {}) { const database = openOpenClawAgentDatabase(options); const enteredNestedTransaction = database.db.isTransaction; return withSqlitePostCommitPublications(database.db, () => runSqliteImmediateTransactionSync(database.db, () => { assertAgentDeletionDatabaseCleanupAccess(database, options); const operationResult = operation(database); if (!enteredNestedTransaction) { if (!cache.incognito.has(database)) ensureOpenClawAgentDatabasePermissions(database.path, options); } return operationResult; }, { busyTimeoutMs: transactionOptions.busyTimeoutMs ?? 5e3, databaseLabel: database.path, ...transactionOptions, operationLabel: transactionOptions.operationLabel ?? "agent.write" })); } /** Retain the exact verified connection across awaits; explicit disposal still revokes it. */ function borrowOpenClawAgentDatabase(options) { const { db } = openOpenClawAgentDatabase(options); return { db, release: retainAgentDatabase(db) }; } /** Return whether the exact cached agent database pathname is still open. */ function isOpenClawAgentDatabaseOpen(pathname) { return cache.databases.get(path.resolve(pathname))?.db.isOpen === true; } /** Return the matching live cache entry without materializing a database. */ function getOpenClawAgentDatabaseIfOpen(options) { const agentId = normalizeAgentId(options.agentId); const pathname = resolveOpenClawAgentSqlitePath({ ...options, agentId }); const database = cache.databases.get(pathname); if (!database?.db.isOpen) { assertAgentDeletionCleanupAliases(options, isSameOpenClawAgentDatabasePath); return; } if (cache.failures.has(pathname)) throw cache.failures.get(pathname); if (database.agentId !== agentId) throw new Error(`OpenClaw agent database ${pathname} is already open for agent ${database.agentId}; requested agent ${agentId}.`); assertAgentDeletionDatabaseCleanupAccess(database, options); return database; } /** Lists process-held incognito databases without opening new sentinel handles. */ function listOpenIncognitoAgentDatabases() { return [...cache.databases.values()].filter((database) => database.db.isOpen && cache.incognito.has(database)).map((database) => ({ agentId: database.agentId, storePath: database.path })).toSorted((left, right) => left.agentId.localeCompare(right.agentId) || left.storePath.localeCompare(right.storePath)); } /** Return the generation of process-held incognito database membership. */ function readOpenIncognitoAgentDatabaseGeneration() { return cache.generation; } /** Returns whether this exact process-held database is incognito/in-memory. */ function isIncognitoOpenClawAgentDatabase(database) { return cache.incognito.has(database); } /** List process-held agent databases without opening or inspecting fixture state. */ function listOpenClawAgentDatabasesForTest() { return [...cache.databases.values()].filter((database) => database.db.isOpen).map((database) => ({ agentId: database.agentId, path: database.path })).toSorted((left, right) => left.agentId.localeCompare(right.agentId) || left.path.localeCompare(right.path)); } /** Close and unregister one unambiguous transient agent database by filesystem identity. */ function disposeOpenClawAgentDatabaseByPath(pathname, options = {}) { const resolvedPath = path.resolve(pathname); for (const pendingPath of cache.pending.keys()) if (isSameOpenClawAgentDatabasePath(pendingPath, resolvedPath)) revokePendingAgentDatabaseOpen(pendingPath); for (const retained of cache.retainedCloses) if (isSameOpenClawAgentDatabasePath(retained.path, resolvedPath)) retained.close(); invalidateOpenClawAgentDatabaseValidation(resolvedPath); const matchingDatabases = [...cache.databases.values()].filter((candidate) => isSameOpenClawAgentDatabasePath(candidate.path, resolvedPath)); if (matchingDatabases.length > 1) return false; const database = matchingDatabases[0]; if (database && cache.incognito.has(database)) return closeOpenClawAgentDatabaseByPath(database.path); if (!database) return false; try { unregisterOpenClawAgentDatabase({ agentId: database.agentId, path: database.path, ...options.env ? { env: options.env } : {} }); } finally { closeOpenClawAgentDatabaseByPath(database.path); } return true; } /** Fence cross-process agent writers while Doctor reconciles shared plugin state. */ function withAgentDatabaseMaintenanceLease(options, run) { return withOpenClawStateLease({ ...AGENT_DATABASE_MAINTENANCE_LEASE, database: { scope: "shared", options }, leaseMs: 6e4, waitMs: 5e3, heartbeat: "worker", leaseLabel: "agent database maintenance lease", operationLabel: "agent.database.maintenance.lease" }, async (maintenance) => { await closeOpenClawAgentDatabasesAsync(); assertNoOpenClawAgentDatabaseLeases(maintenance, options); return runWithAgentDatabaseMaintenanceAuthority(maintenance, () => run(maintenance)); }); } /** Release fixture handles and pathname trust before a test root is recreated. */ function closeOpenClawAgentDatabasesForTest(rootPath) { closeOpenClawAgentDatabases(rootPath); clearOpenClawAgentDatabaseValidationCache(rootPath); cache.terminal.clearAll(rootPath); } //#endregion export { inspectOpenClawAgentDatabaseOwner as C, closeOpenClawAgentDatabasesAsync as S, withAgentDatabaseMaintenanceLease as _, confirmOpenClawAgentDatabaseIntegrity as a, closeOpenClawAgentDatabaseByPath as b, getOpenClawAgentDatabaseIfOpen as c, listOpenClawAgentDatabasesForTest as d, listOpenIncognitoAgentDatabases as f, runOpenClawAgentWriteTransaction as g, recordOpenClawAgentDatabaseOpenFailure as h, closeOpenClawAgentDatabasesForTest as i, isIncognitoOpenClawAgentDatabase as l, readOpenIncognitoAgentDatabaseGeneration as m, borrowOpenClawAgentDatabase as n, deferOpenClawAgentPostCommitPublication as o, openOpenClawAgentDatabase as p, clearOpenClawAgentDatabaseOpenFailure as r, disposeOpenClawAgentDatabaseByPath as s, IncognitoAgentDatabasePathCollisionError as t, isOpenClawAgentDatabaseOpen as u, withOpenClawAgentDatabaseAsync as v, settleOpenClawAgentDatabaseWorkerClose as w, closeOpenClawAgentDatabases as x, OPENCLAW_AGENT_DB_OPEN_HANDLE_CAP as y };