UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

205 lines (204 loc) • 8.09 kB
import { a as resolveGlobalSingleton } from "./global-singleton-0m-JziJW.mjs"; import { t as createSubsystemLogger } from "./subsystem-I-uqdx4f.mjs"; import { a as registerSqliteCacheExitClose, o as runInSqliteMaintenanceContext } from "./sqlite-wal-DUSYRhyv.mjs"; import { p as observeOpenClawDatabaseMaintenanceResource } from "./openclaw-state-db-async-lifecycle-DLKxSPCk.mjs"; import { _ as isOpenClawAgentDatabasePathCurrent, g as findOpenClawAgentDatabaseIdentity, h as createOpenClawAgentDatabaseClaim } from "./openclaw-agent-db-validation-cache-_HDeZNQr.mjs"; import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs"; import { s as registerOpenClawAgentDatabaseSyncResource } from "./openclaw-agent-db-resources-x2MmUEok.mjs"; import { t as SQLITE_IDLE_HANDLE_TTL_MS } from "./sqlite-handle-lifecycle-dWd9h3ii.mjs"; import { i as withFreshOpenClawAgentDatabaseReadOnly, n as openOpenClawAgentDatabaseReadOnly, r as readOpenClawAgentDatabase, t as hasOpenClawAgentReadOnlySchema } from "./openclaw-agent-db-readonly-open-hQ4ohjUT.mjs"; import { AsyncLocalStorage } from "node:async_hooks"; //#region src/state/openclaw-agent-db-readonly-scope.ts const readOnlyScope = new AsyncLocalStorage(); const log = createSubsystemLogger("state/agent-db"); const retainedScopes = resolveGlobalSingleton(Symbol.for("openclaw.agentDatabaseReadOnlyScopes"), () => ({ paths: /* @__PURE__ */ new Map(), active: /* @__PURE__ */ new Set() })); /** One retained connection, revoked by its caller, database lifecycle, or idle expiry. */ var OpenClawAgentDatabaseReadOnlyScope = class { constructor(cached = false) { this.cached = cached; this.borrowers = 0; this.closing = false; } get hasRetainedConnection() { return this.database !== void 0; } invalidateProjection(databaseIdentity, invalidate) { if (this.database && findOpenClawAgentDatabaseIdentity(this.database)?.identity === databaseIdentity) invalidate(this.database.db); } closeIfIdle() { if (this.borrowers === 0 && (!this.database?.db.isOpen || !this.database.db.isTransaction)) this.discardConnection(); } close() { this.closing = true; clearTimeout(this.idleTimer); this.idleTimer = void 0; this.database?.close(); this.database = void 0; this.borrowers = 0; if (this.target && retainedScopes.paths.get(this.target.path) === this) retainedScopes.paths.delete(this.target.path); this.target = void 0; this.unregisterResource?.(); this.unregisterResource = void 0; retainedScopes.active.delete(this); if (retainedScopes.active.size === 0) { retainedScopes.unregisterExit?.(); retainedScopes.unregisterExit = void 0; } this.closing = false; } discardConnection() { const target = this.target; this.close(); if (!this.cached) this.target = target; } touch() { if (!this.database) return; if (this.idleTimer) { this.idleTimer.refresh(); return; } this.idleTimer = runInSqliteMaintenanceContext(() => setTimeout(() => { this.idleTimer = void 0; try { this.closeIfIdle(); } catch (error) { log.warn("Idle agent read-only database cleanup failed", { path: this.database?.path, error }); } finally { this.touch(); } }, SQLITE_IDLE_HANDLE_TTL_MS)); this.idleTimer.unref(); } run(target, operation) { if (this.target?.agentId !== target.agentId || this.target.path !== target.path) this.close(); this.target = target; return readOnlyScope.run(this, operation); } matches(agentId, pathname) { return this.target?.agentId === agentId && this.target.path === pathname; } acquire(options) { if (this.database && !isOpenClawAgentDatabasePathCurrent(this.database)) this.discardConnection(); if (!this.database) { let opened; try { opened = openOpenClawAgentDatabaseReadOnly(options); } catch (error) { this.discardConnection(); throw error; } if (!opened.found) { this.discardConnection(); return opened; } this.database = opened.database; this.target = { agentId: this.database.agentId, path: this.database.path }; try { this.unregisterResource = registerOpenClawAgentDatabaseSyncResource({ ...this.target, revoke: () => this.close(), close: () => this.close() }); retainedScopes.active.add(this); if (this.cached) retainedScopes.paths.set(this.database.path, this); retainedScopes.unregisterExit ??= registerSqliteCacheExitClose(() => { for (const scope of retainedScopes.active) scope.close(); }); } catch (error) { this.discardConnection(); throw error; } } else if (!hasOpenClawAgentReadOnlySchema(this.database)) { this.discardConnection(); return { found: false, reason: "schema-missing" }; } const requestedAgentId = normalizeAgentId(options.agentId); if (this.database.agentId !== requestedAgentId) throw new Error(`OpenClaw agent database ${this.database.path} belongs to agent ${this.database.agentId}; requested agent ${requestedAgentId}.`); observeOpenClawDatabaseMaintenanceResource(this.unregisterResource); this.touch(); return { found: true, database: this.database }; } releaseBorrow(database) { if (this.database !== database) return; this.borrowers--; if (this.cached && this.borrowers === 0 && this.database && (!this.database.db.isOpen || this.database.db.isTransaction)) this.discardConnection(); else this.touch(); } assertUsable() { if (this.closing) throw new Error("Agent read-only database native cleanup is pending"); } retain(options) { this.assertUsable(); const opened = this.database?.db.isOpen && this.database.db.isTransaction ? openOpenClawAgentDatabaseReadOnly(options) : this.acquire(options); if (!opened.found) return opened; const { database } = opened; const shared = database === this.database; if (shared) this.borrowers++; return { found: true, database, claim: createOpenClawAgentDatabaseClaim(database, () => { if (shared) this.releaseBorrow(database); else database.close(); }) }; } read(operation, options) { this.assertUsable(); if (this.database?.db.isOpen && this.database.db.isTransaction) return withFreshOpenClawAgentDatabaseReadOnly(operation, options); const opened = this.acquire(options); if (!opened.found) return opened; this.borrowers++; try { return readOpenClawAgentDatabase(opened.database, operation); } catch (error) { if (this.cached && this.borrowers === 1) this.discardConnection(); throw error; } finally { this.releaseBorrow(opened.database); } } }; function cachedScope(options) { let scope = retainedScopes.paths.get(options.path); if (!scope) { scope = new OpenClawAgentDatabaseReadOnlyScope(true); scope.run(options, () => {}); retainedScopes.paths.set(options.path, scope); } return scope; } /** Committed worker receipts invalidate projections on retained readers without running SQL. */ function invalidateOpenClawAgentReadOnlyProjections(databaseIdentity, invalidate) { for (const scope of retainedScopes.active) scope.invalidateProjection(databaseIdentity, invalidate); } /** Writable admission retires an idle reader before opening the same physical file. */ function closeIdleOpenClawAgentDatabaseReadOnly(pathname) { retainedScopes.paths.get(pathname)?.closeIfIdle(); } function retainCachedOpenClawAgentDatabaseReadOnly(options) { return cachedScope(options).retain(options); } /** Reuse the caller's matching read scope, or this thread's idle-expiring reader. */ function withScopedOpenClawAgentDatabaseReadOnly(operation, options, behavior = {}) { if (behavior.allowExtension) return withFreshOpenClawAgentDatabaseReadOnly(operation, options, behavior); const scope = readOnlyScope.getStore(); return (scope?.matches(options.agentId, options.path) ? scope : cachedScope(options)).read(operation, options); } //#endregion export { withScopedOpenClawAgentDatabaseReadOnly as a, retainCachedOpenClawAgentDatabaseReadOnly as i, closeIdleOpenClawAgentDatabaseReadOnly as n, invalidateOpenClawAgentReadOnlyProjections as r, OpenClawAgentDatabaseReadOnlyScope as t };