UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

672 lines (671 loc) • 34.7 kB
import { t as createDeferredCore } from "./deferred-D0La5CRk.mjs"; import { t as hasNodeErrorCode } from "./path-guards-CH7noY0Z.mjs"; import { r as resolveStateDir } from "./state-dir-CYkBUd_G.mjs"; import "./paths-BP0DYpE8.mjs"; import { t as formatErrorMessage } from "./errors-BvOkmQM5.mjs"; import "./kysely-sync-DUWzQvCU.mjs"; import { t as clearNodeSqliteKyselyCacheForDatabase } from "./kysely-sync-cache-state-DbAMR_DD.mjs"; import { o as resolveImmutableSqliteFileUri, r as openNodeSqliteDatabase } from "./node-sqlite-DyeJqxPf.mjs"; import { s as throwSqliteLifecycleErrors } from "./sqlite-lifecycle-errors-DuOygjcn.mjs"; import { n as isArtifactPreservingStateRead, r as withArtifactPreservingStateReads } from "./artifact-preserving-state-reads-BQnQLAJR.mjs"; import { i as setSqliteBusyTimeout } from "./sqlite-busy-timeout-D3v1uB5d.mjs"; import { t as SqliteSnapshotCleanupError } 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 createSubsystemLogger } from "./subsystem-4NzR43uz.mjs"; import { a as readSqliteUserVersion, n as createNewerSqliteSchemaVersionError } from "./sqlite-user-version-DLMl1A7W.mjs"; import { r as configureSqliteReadOnlyPragmas } from "./sqlite-wal-DfKCrDTQ.mjs"; import { c as resolveOpenClawStateSqlitePath } from "./openclaw-state-db.paths-CmKkMYdo.mjs"; import { n as prepareSqliteReadOnlyLocation } from "./sqlite-snapshot-source-ChTpAJ4b.mjs"; import { h as withSqliteReadOnlyWorkerScope, r as isSqliteInspectionDeadlineOwnedByCaller, s as resolveSqliteInspectionSignal } from "./sqlite-readonly-worker-RO6waSGe.mjs"; import { r as normalizeOpenClawStateSchemaReadError } from "./openclaw-state-db-schema-migration-required-DBia8oMG.mjs"; import { r as readStateSchemaContentVersion } from "./openclaw-state-db-schema-version-DZI_59qS.mjs"; import { n as assertNoLegacyStateRuntimeRepair, ot as readStateSchemaPublicationBlocker } from "./openclaw-state-db-fast-path-Kg79WElU.mjs"; import { n as normalizeAgentId, t as isValidAgentId } from "./agent-id-D1-Q6vR4.mjs"; import "./session-key-DY3MOX4N.mjs"; import { i as listAgentIds } from "./agent-roster-DH6r_8f4.mjs"; import { g as resolveUnsuffixedSqliteTargetFromSessionStorePath } from "./paths-Bonddxfk.mjs"; import "./agent-scope-config-BEk6-ThS.mjs"; import { t as adoptSqliteSchemaContracts } from "./sqlite-schema-contract-BXfI47YR.mjs"; import { n as assertSqliteIntegrity } from "./sqlite-integrity-D3yHPOet.mjs"; import "./openclaw-agent-db-contract-OYt2DCoK.mjs"; import { a as isPersistentOpenClawAgentDatabasePath, o as isSameOpenClawAgentDatabasePath, r as createOpenClawAgentDatabasePathMatcher } from "./openclaw-agent-db.paths-BbmZlEP5.mjs"; import { l as createAgentDatabaseInspectionRefusal, p as inspectAgentDatabaseAdmission, r as canIsolateAgentDatabase, t as AgentDatabaseAdmissionError, v as recordAgentDatabaseAdmissions } from "./agent-database-admission-CRwW7FsI.mjs"; import { a as inspectOpenClawStateOwnershipFromDatabase } from "./openclaw-state-ownership-C6zxQPsi.mjs"; import "./openclaw-state-db-DJZ7EKoI.mjs"; import { n as getActiveOpenClawStateDatabaseReadSnapshot } from "./openclaw-state-db-readonly-ywwvuxdQ.mjs"; import { s as readAgentDeletionRecoveryHoldsInWorker } from "./agent-deletion-journal.read-BRl4UyPn.mjs"; import { n as requestOpenClawAgentDatabaseIntegrityCheck } from "./openclaw-database-verify-CGIYTxWq.mjs"; import { n as isConfiguredAgentDatabaseTarget, s as resolveConfiguredAgentDatabaseCandidatePaths } from "./targets-kx08xomQ.mjs"; import { t as _usingCtx } from "./usingCtx-BhiZp_rS.mjs"; import { n as describeDeferredStateSchemaPublication, r as formatIndeterminateDatabaseReadiness, t as OpenClawDatabaseSchemaPreflightError } from "./openclaw-database-preflight.messages-BMcrDOus.mjs"; import { n as inspectStateDatabaseSchema, r as inspectCurrentStateStartupSchema, t as captureStateSchemaInspectionContracts } from "./openclaw-state-schema-preflight-Dw6OWE3o.mjs"; import { t as getAgentDatabaseStartupAdmission } from "./agent-database-startup-BYk3phjn.mjs"; import { t as createAgentSchemaInspectionWorker } from "./openclaw-agent-schema-inspection-worker-CL_cMrgm.mjs"; import { t as createAgentDatabaseDeletionClassifier } from "./agent-deletion-discovery-DxqezB7f.mjs"; import { n as discoverAgentDatabaseMigrationTargets } from "./state-migrations.media-persistence-targets-DNBi9N4w.mjs"; import { existsSync, realpathSync, statSync } from "node:fs"; import path from "node:path"; import { availableParallelism } from "node:os"; //#region src/state/openclaw-database-preflight-agent-scheduler.ts const AGENT_DATABASE_STARTUP_WAIT_MS = 5e3; async function preflightAgentDatabasesBounded(targets, inspect, result, signal, startup) { const inspectedAgentPaths = /* @__PURE__ */ new Set(); const inspectedAgentTargets = /* @__PURE__ */ new Set(); const claimAgentTarget = (realPath, agentId) => { const inspectionKey = `${realPath}\0${agentId ?? ""}`; if (inspectedAgentTargets.has(inspectionKey) || agentId === void 0 && inspectedAgentPaths.has(realPath)) return false; inspectedAgentPaths.add(realPath); inspectedAgentTargets.add(inspectionKey); return true; }; const inspections = []; const failures = /* @__PURE__ */ new Map(); let nextInspectionIndex = 0; const readers = []; const foreground = createDeferredCore(); const completions = targets.map(() => createDeferredCore()); for (const completion of completions) completion.promise.catch(() => {}); const deferred = /* @__PURE__ */ new Set(); const concurrency = Math.min(2, availableParallelism(), targets.length); let deferredRefusals = []; const inspectionSignal = startup ? signal ? AbortSignal.any([startup.signal, signal]) : startup.signal : resolveSqliteInspectionSignal(signal); const deadlineOwnedByCaller = startup !== void 0 || isSqliteInspectionDeadlineOwnedByCaller(); const settleForeground = () => { if (!startup || deferred.size === 0 || deferredRefusals.length > 0 || failures.size > 0) return; if (targets.some((_, index) => inspections[index] === void 0 && !deferred.has(index))) return; try { deferredRefusals = startup.defer([...deferred].map((index) => ({ target: targets[index], result: completions[index].promise })), "Inspection and writable admission continue after the Gateway listener binds."); foreground.resolve(); } catch (error) { foreground.reject(error); } }; const worker = async () => { try { var _usingCtx$2 = _usingCtx(); const reader = _usingCtx$2.a(createAgentSchemaInspectionWorker()); readers.push(reader); while (true) { if (inspectionSignal?.aborted || failures.size > 0) return; const index = nextInspectionIndex; if (index >= targets.length) return; nextInspectionIndex += 1; const target = targets[index]; if (target === void 0) continue; const inspection = { incompatible: [], indeterminate: [] }; try { const disposition = await inspect(target, inspection, claimAgentTarget, (input, requestSignal, snapshot) => reader.inspect(input, inspectionSignal ?? requestSignal, snapshot)); inspections[index] = inspection; completions[index].resolve(inspection); if (disposition === "defer" && startup?.canDefer(target)) deferred.add(index); } catch (error) { failures.set(index, error); completions[index].reject(error); return; } finally { settleForeground(); } } } catch (_) { _usingCtx$2.e = _; } finally { await _usingCtx$2.d(); } }; const completed = Promise.all(Array.from({ length: concurrency }, () => withSqliteReadOnlyWorkerScope(worker, { signal: inspectionSignal ?? new AbortController().signal, deadlineOwnedByCaller }))).finally(() => { for (let index = 0; index < targets.length; index += 1) if (inspections[index] === void 0) completions[index].reject(inspectionSignal?.reason ?? failures.values().next().value ?? /* @__PURE__ */ new Error("Inspection cancelled")); }); startup?.track(completed); const timer = startup && setTimeout(() => { if (failures.size > 0 || inspectionSignal?.aborted) return; targets.forEach((target, index) => { if (inspections[index] === void 0 && startup.canDefer(target)) deferred.add(index); }); settleForeground(); }, AGENT_DATABASE_STARTUP_WAIT_MS); timer?.unref(); try { await (startup ? Promise.race([completed, foreground.promise]) : completed); } finally { clearTimeout(timer); } for (let index = 0; index < targets.length; index += 1) if (failures.has(index) && (!deferred.has(index) || deferredRefusals.length === 0)) throw failures.get(index); inspectionSignal?.throwIfAborted(); for (const [index, inspection] of inspections.entries()) { if (inspection === void 0 || deferred.has(index)) continue; result.incompatible.push(...inspection.incompatible); result.indeterminate.push(...inspection.indeterminate); if (inspection.agentRefusals?.length) (result.agentRefusals ??= []).push(...inspection.agentRefusals); if (inspection.pendingMigrations?.length) (result.pendingMigrations ??= []).push(...inspection.pendingMigrations); } if (deferredRefusals.length) (result.agentRefusals ??= []).push(...deferredRefusals); return { schemaProcessCount: readers.reduce((count, reader) => count + reader.processCount, 0), schemaInspectionCount: readers.reduce((count, reader) => count + reader.inspectionCount, 0), schemaSnapshotCount: readers.reduce((count, reader) => count + reader.snapshotCount, 0) }; } //#endregion //#region src/state/openclaw-database-preflight-cleanup.ts /** Failed retirement keeps its existing snapshot owner and cannot admit more schemas. */ async function cleanupOpenClawStatePreflight(options) { const cleanupErrors = []; if (options.database) { try { options.closeAdmission?.(); } catch (error) { cleanupErrors.push(error); } try { clearNodeSqliteKyselyCacheForDatabase(options.database); options.database.close(); } catch (error) { cleanupErrors.push(error); } } try { if (options.snapshot && !await options.snapshot.cleanupAsync()) cleanupErrors.push(new SqliteSnapshotCleanupError(`SQLite read-only worker snapshot cleanup failed: ${options.snapshot.location}`)); } catch (error) { cleanupErrors.push(error); } if (cleanupErrors.length > 0) throwSqliteLifecycleErrors([...options.inspectionErrors, ...cleanupErrors], "State database schema inspection cleanup failed"); } //#endregion //#region src/state/openclaw-database-preflight-targets.ts function inspectDatabaseCandidatePresence(databasePath) { try { statSync(databasePath); return { status: "present" }; } catch (error) { return hasNodeErrorCode(error, "ENOENT") ? { status: "absent" } : { status: "indeterminate", reason: formatErrorMessage(error) }; } } /** An inspected custom path supplies recovery ownership, never migration admission. */ function recordAgentDatabaseRecoveryInspection(prepared, pathname, realPath, inspection) { const metadata = inspection.agentSchemaMeta; if (inspection.failure || inspection.reason || metadata?.role !== "agent" || !metadata.agentId || !isValidAgentId(metadata.agentId)) prepared?.discovery.failures.push({ path: pathname, reason: "Could not verify the owner of a held custom session store." }); else prepared?.discovery.unverifiedTargets.push({ agentId: normalizeAgentId(metadata.agentId), path: pathname, realPath, source: "configured" }); } /** Select read-only inspection targets from the captured shared-state ownership facts. */ function collectAgentDatabasePreflightTargets(options) { const { registeredDatabases, deletionJournal, purpose } = options; const retainedDeletions = deletionJournal.status === "present" ? deletionJournal.entries : []; let agentTargets = registeredDatabases; let configuredTargets = []; const retainedAgentIds = new Set(retainedDeletions.map((deletion) => deletion.agentId)); const retainedPaths = /* @__PURE__ */ new Set(); const failures = []; let preparedDiscovery; if (options.configuredAgentDatabaseTargets !== void 0) configuredTargets = typeof options.configuredAgentDatabaseTargets === "function" ? options.configuredAgentDatabaseTargets(registeredDatabases) : options.configuredAgentDatabaseTargets; if (purpose === "maintenance" && options.configuredAgentDatabaseTargets !== void 0) { const discovery = discoverAgentDatabaseMigrationTargets({ env: options.env, configuredAgentDatabaseTargets: configuredTargets, registeredAgentDatabases: registeredDatabases, deletionJournal }); preparedDiscovery = { stateDir: resolveStateDir(options.env), configuredAgentDatabaseTargets: configuredTargets, registeredAgentDatabases: registeredDatabases, discovery }; agentTargets = discovery.targets; for (const retained of [...discovery.retainedTargets, ...discovery.unverifiedTargets]) retainedPaths.add(retained.realPath); failures.push(...discovery.failures); } const candidates = [ ...configuredTargets, ...agentTargets, ...options.configuredAgentDatabaseTargets !== void 0 ? registeredDatabases.filter((database) => isPersistentOpenClawAgentDatabasePath(database.path, options.env)) : [], ...(options.configuredAgentDatabaseCandidatePaths ?? []).map((candidatePath) => ({ agentId: options.inspectCandidateOwners ? resolveUnsuffixedSqliteTargetFromSessionStorePath(candidatePath).agentId : void 0, path: candidatePath })) ]; const samePath = createOpenClawAgentDatabasePathMatcher(); const retained = [...retainedPaths]; const classifyDeletion = createAgentDatabaseDeletionClassifier({ env: options.env, retainedDeletions: deletionJournal, configuredAgentDatabaseTargets: configuredTargets, registeredAgentDatabases: registeredDatabases }); return { configuredTargets, candidates: purpose === "runtime" ? candidates.filter((row) => typeof classifyDeletion(row.path, row.agentId) !== "object") : deletionJournal.status === "unavailable" ? preparedDiscovery && options.onAgentDatabaseDiscovery ? (options.configuredAgentDatabaseCandidatePaths ?? []).filter((pathname) => !retained.some((candidate) => samePath(candidate, pathname))).map((pathname) => ({ agentId: void 0, path: pathname, holdForDeletionRecovery: true })) : [] : candidates.filter((row) => { if (deletionJournal.status === "present" && deletionJournal.held.some((target) => samePath(target.path, row.path))) return options.inspectHeldAgentDatabaseTargets?.some((target) => target.agentId === row.agentId && samePath(target.path, row.path)) === true; return row.agentId === void 0 || !retainedAgentIds.has(row.agentId); }), isRetainedPath: (pathname) => purpose === "runtime" ? typeof classifyDeletion(pathname) === "object" : retained.some((candidate) => samePath(candidate, pathname)), failures, preparedDiscovery }; } //#endregion //#region src/state/openclaw-database-preflight-state-path.ts /** Compare one explicit SQLite file with this release's canonical shared-state schema. */ async function preflightOpenClawStateDatabasePath(databasePath) { const resolvedPath = path.resolve(databasePath); const base = { schema: "openclaw.state-schema-preflight.v1", databasePath: resolvedPath, targetVersion: 20 }; let database; let foundVersion = null; let contentVersion; let deferredPublication; let ownership = null; const result = (status, details = {}) => ({ ...base, foundVersion, ...contentVersion !== void 0 && contentVersion !== foundVersion ? { contentVersion } : {}, ...deferredPublication ? { deferredPublication } : {}, ownership, issues: details.issues ?? [], status, requiresWrite: details.requiresWrite ?? false, ...details.reason ? { reason: details.reason } : {} }); try { const inspectionPath = realpathSync.native(resolvedPath); const sidecars = [ "-wal", "-shm", "-journal" ].filter((suffix) => existsSync(`${inspectionPath}${suffix}`)); if (sidecars.length > 0) throw new Error(`SQLite preflight requires a consolidated snapshot with no sidecars; found ${sidecars.join(", ")}. Create a WAL-aware online backup and preflight the resulting standalone file.`); database = openNodeSqliteDatabase(resolveImmutableSqliteFileUri(inspectionPath), { readOnly: true }); setSqliteBusyTimeout(database, OPENCLAW_SQLITE_BUSY_TIMEOUT_MS); configureSqliteReadOnlyPragmas(database); foundVersion = readSqliteUserVersion(database); if (!Number.isSafeInteger(foundVersion) || foundVersion < 0) throw new Error(`OpenClaw state database ${resolvedPath} has invalid schema version metadata.`); contentVersion = foundVersion > 20 ? foundVersion : readStateSchemaContentVersion(database); if (contentVersion > 20) { try { ownership = inspectOpenClawStateOwnershipFromDatabase(database, resolvedPath); } catch {} return result("incompatible", { reason: createNewerSqliteSchemaVersionError("OpenClaw state database", resolvedPath, contentVersion, 20).message }); } assertSqliteIntegrity(database, resolvedPath); ownership = inspectOpenClawStateOwnershipFromDatabase(database, resolvedPath); if (readStateSchemaContentVersion(database) < 20) return result("migration-required", { requiresWrite: true }); if (foundVersion < contentVersion) deferredPublication = describeDeferredStateSchemaPublication(readStateSchemaPublicationBlocker(database), resolvedPath, foundVersion, contentVersion); const { blockingIssues, startupRepairableIssues } = inspectCurrentStateStartupSchema(database, resolvedPath, foundVersion); if (blockingIssues.length > 0) return result("incompatible", { issues: blockingIssues }); assertNoLegacyStateRuntimeRepair(database, resolvedPath); return result(startupRepairableIssues.length > 0 ? "startup-repairable" : "exact", { issues: startupRepairableIssues, requiresWrite: startupRepairableIssues.length > 0 }); } catch (error) { return result("indeterminate", { reason: formatErrorMessage(error) }); } finally { database?.close(); } } //#endregion //#region src/state/openclaw-database-preflight.ts const indeterminateCauses = /* @__PURE__ */ new WeakMap(); var AgentDatabaseRecoveryHeldError = class extends Error {}; /** Verify persisted runtime schemas before certifying repair or accepting restart. */ async function assertOpenClawDatabasesReady(options) { const schemas = await preflightOpenClawDatabaseSchemas({ env: options.env, onAgentInspection: options.onAgentInspection, verifyCurrentSchemaShape: true, ...options.config ? { agentAdmissionConfig: options.config, configuredAgentDatabaseTargets: [], configuredAgentDatabaseCandidatePaths: resolveConfiguredAgentDatabaseCandidatePaths(options.config, { env: options.env }) } : {}, ...options.operation === "gateway-startup" ? { requireStartupMigrationReadiness: true } : {}, ...options.operation === "doctor" ? { configuredAgentDatabaseTargets: options.configuredAgentDatabaseTargets, inspectHeldAgentDatabaseTargets: options.configuredAgentDatabaseTargets.filter((target) => !options.config || !canIsolateAgentDatabase(options.config, target.agentId)) } : {} }, options.operation === "doctor" ? "maintenance" : "runtime"); const failures = []; for (const refusal of schemas.agentRefusals ?? []) { const error = new AgentDatabaseAdmissionError(refusal); if (!(error.cause instanceof AgentDatabaseRecoveryHeldError) && (!options.config || (refusal.code === "agent-database-ownership-mismatch" || options.operation === "gateway-startup" && refusal.code !== "agent-database-inspection-pending") && !canIsolateAgentDatabase(options.config, refusal.agentId))) failures.push(error); } if (schemas.incompatible.length > 0) failures.push(new OpenClawDatabaseSchemaPreflightError(schemas.incompatible, { operation: options.operation })); if (schemas.indeterminate.length > 0) { const causes = schemas.indeterminate.flatMap((row) => indeterminateCauses.has(row) ? [indeterminateCauses.get(row)] : []); failures.push(options.operation === "gateway-startup" && schemas.indeterminate.length === 1 && causes.length === 1 ? causes[0] : new Error(formatIndeterminateDatabaseReadiness(schemas.indeterminate, options.operation, indeterminateCauses), { cause: new AggregateError(causes) })); } if (failures.length > 0) throw failures.length === 1 ? failures[0] : new AggregateError(failures, failures.map((error) => formatErrorMessage(error)).join("\n")); if (options.operation === "gateway-startup") recordAgentDatabaseAdmissions(schemas.agentRefusals ?? [], { env: options.env, source: "startup" }); if (options.operation === "doctor") { for (const publication of schemas.deferredSchemaPublications ?? []) options.onDeferredSchemaPublication?.(publication); options.onVerified?.(schemas); } } /** Read schema headers and optionally verify current schema shape without repairing it. */ async function preflightOpenClawDatabaseSchemas(inputOptions, purpose = "maintenance") { const options = isArtifactPreservingStateRead("agent") ? { ...inputOptions, preserveSourceArtifacts: true } : inputOptions; options.signal?.throwIfAborted(); const supportedVersions = options.supportedVersions ?? { state: 20, agent: 25 }; const result = { incompatible: [], indeterminate: [] }; const startup = options.requireStartupMigrationReadiness ? getAgentDatabaseStartupAdmission() : void 0; const admissionConfig = options.agentAdmissionConfig; const admittedAgentIds = new Set(admissionConfig ? listAgentIds(admissionConfig) : []); const scheduling = startup?.scheduling(options.env, admissionConfig ? options.configuredAgentDatabaseCandidatePaths ?? resolveConfiguredAgentDatabaseCandidatePaths(admissionConfig, { env: options.env }) : [], admittedAgentIds); const prepareSchemaHeader = startup?.prepareSchemaHeaders(options.env); const preparedStartup = options.reuseStartupSchemaPreparation && !options.requireStartupMigrationReadiness && !options.verifyCurrentSchemaShape ? getAgentDatabaseStartupAdmission() : void 0; const readPreparedSchemaHeader = preparedStartup?.takePreparedSchemaHeaders(options.env); const refusalOwner = startup ?? preparedStartup; const priorRefusals = refusalOwner?.captureRefusals(options.env); const statePath = path.resolve(resolveOpenClawStateSqlitePath(options.env)); let registeredDatabases = []; let deletionJournal = { status: "unavailable", cause: "missing", reason: "shared state database missing" }; let stateDatabase; let closeStateSchemaReadAdmission; let stateSnapshot; const stateInspectionErrors = []; const statePresence = inspectDatabaseCandidatePresence(statePath); if (statePresence.status === "indeterminate") { result.indeterminate.push({ kind: "state", path: statePath, reason: statePresence.reason }); return result; } try { if (statePresence.status === "present") { let stateLocation = getActiveOpenClawStateDatabaseReadSnapshot({ env: options.env })?.location; if (stateLocation === void 0) { stateSnapshot = await prepareSqliteReadOnlyLocation(realpathSync.native(statePath), { preserveSourceArtifacts: options.preserveSourceArtifacts ?? true, allowLiveOwner: options.preserveSourceArtifacts !== false, signal: options.signal }); stateLocation = stateSnapshot.location; } options.signal?.throwIfAborted(); const input = { pathname: statePath, supportedVersion: supportedVersions.state, requireStartupMigrationReadiness: options.requireStartupMigrationReadiness, verifyCurrentSchemaShape: options.verifyCurrentSchemaShape, scope: options.scope, purpose }; let inspection; if (purpose === "runtime" && !options.requireStartupMigrationReadiness && !options.openStateSchemaReadAdmission) try { var _usingCtx$1 = _usingCtx(); inspection = await _usingCtx$1.a(createAgentSchemaInspectionWorker()).inspectState({ ...input, schemaContracts: captureStateSchemaInspectionContracts() }, options.signal, stateLocation); adoptSqliteSchemaContracts(inspection.schemaContracts ?? []); } catch (_) { _usingCtx$1.e = _; } finally { await _usingCtx$1.d(); } else { stateDatabase = openNodeSqliteDatabase(stateLocation, { readOnly: true }); closeStateSchemaReadAdmission = options.openStateSchemaReadAdmission?.(stateDatabase); stateDatabase.exec(`PRAGMA busy_timeout = ${OPENCLAW_SQLITE_BUSY_TIMEOUT_MS};`); inspection = inspectStateDatabaseSchema(stateDatabase, input); } Object.assign(result, inspection.schemas); stateInspectionErrors.push(...inspection.inspectionErrors); if (options.scope === "state" || !inspection.deletionJournal) return result; registeredDatabases = inspection.registeredDatabases ?? []; deletionJournal = inspection.deletionJournal; } } catch (error) { const failure = normalizeOpenClawStateSchemaReadError(error, statePath); stateInspectionErrors.push(failure); if (options.signal?.aborted || options.requireStartupMigrationReadiness) throw failure; const row = { kind: "state", path: statePath, reason: formatErrorMessage(failure) }; indeterminateCauses.set(row, failure); result.indeterminate.push(row); return result; } finally { await cleanupOpenClawStatePreflight({ database: stateDatabase, closeAdmission: closeStateSchemaReadAdmission, snapshot: stateSnapshot, inspectionErrors: stateInspectionErrors }); } if (options.scope === "state") return result; const { candidates, configuredTargets, isRetainedPath, failures, preparedDiscovery } = collectAgentDatabasePreflightTargets({ ...options, registeredDatabases, deletionJournal, purpose, inspectCandidateOwners: Boolean(options.requireStartupMigrationReadiness || options.agentAdmissionConfig) }); const recordRecoveryFailure = (pathname, reason) => { preparedDiscovery?.discovery.failures.push({ path: pathname, reason }); }; for (const failure of failures) result.indeterminate.push({ kind: "agent", ...failure }); const admissionTargets = admissionConfig && (purpose === "runtime" || options.requireStartupMigrationReadiness || options.reuseStartupSchemaPreparation) ? [...configuredTargets, ...(options.configuredAgentDatabaseCandidatePaths ?? resolveConfiguredAgentDatabaseCandidatePaths(admissionConfig, { env: options.env })).map((pathname) => ({ path: pathname }))] : void 0; const recordInspectionFailure = (row, inspection, error) => { if (startup?.recordInspectionFailure(row, inspection, error)) return true; if (!admissionTargets || !admissionConfig || options.requireStartupMigrationReadiness || !row.agentId || !canIsolateAgentDatabase(admissionConfig, row.agentId)) return false; (inspection.agentRefusals ??= []).push(createAgentDatabaseInspectionRefusal({ agentId: row.agentId, paths: [row.path], reason: formatErrorMessage(error), cause: error })); return true; }; const skipped = /* @__PURE__ */ new Set(); let recoveryHolds; const stats = await preflightAgentDatabasesBounded(candidates.filter((row) => { if (!admissionTargets || isConfiguredAgentDatabaseTarget(admissionTargets, row.agentId, row.path)) return true; if (options.requireStartupMigrationReadiness && !skipped.has(row.path)) { createSubsystemLogger("state/agent-admission").warn(`Skipped ${path.basename(row.path)}: database is not a configured target; run openclaw doctor to inspect retained data.`); skipped.add(row.path); } return false; }).map((row) => Object.assign({}, row, { presence: inspectDatabaseCandidatePresence(row.path) })).filter((row) => row.presence.status !== "absent"), async (row, inspection, claimAgentTarget, inspectSchema) => { const agentPath = row.path; if (refusalOwner?.reuseRefusal(row, inspection, priorRefusals)) return; const { presence } = row; if (presence.status === "indeterminate") { if (row.holdForDeletionRecovery) { recordRecoveryFailure(agentPath, presence.reason); return; } if (!recordInspectionFailure(row, inspection, new Error(presence.reason))) inspection.indeterminate.push({ kind: "agent", path: agentPath, reason: presence.reason }); return; } let agentSnapshot; try { const realAgentPath = realpathSync.native(agentPath); if (row.agentId === void 0 && isRetainedPath(realAgentPath)) return; if (!claimAgentTarget(realAgentPath, row.agentId)) return; let schemaInspection = readPreparedSchemaHeader?.(realAgentPath, supportedVersions.agent) ?? null; const recordPreparedSchemaHeader = prepareSchemaHeader?.(realAgentPath); const inspectOwnership = row.holdForDeletionRecovery || row.agentId !== void 0 && admittedAgentIds.has(row.agentId); const schemaInput = { pathname: realAgentPath, agentId: row.agentId, supportedVersion: supportedVersions.agent, inspectOwnership, verifyCurrentSchemaShape: row.holdForDeletionRecovery ? false : options.verifyCurrentSchemaShape, requireStartupMigrationReadiness: row.holdForDeletionRecovery ? false : options.requireStartupMigrationReadiness, deferRuntimeIntegrity: purpose === "runtime" && options.preserveSourceArtifacts !== true && scheduling?.canDefer(row), startupIntegrityStateDir: options.requireStartupMigrationReadiness ? resolveStateDir(options.env) : void 0 }; if (!schemaInspection && options.preserveSourceArtifacts !== true) schemaInspection = await inspectSchema(schemaInput, options.signal); if (!schemaInspection) { agentSnapshot = await prepareSqliteReadOnlyLocation(realAgentPath, { preserveSourceArtifacts: options.preserveSourceArtifacts ?? true, allowLiveOwner: options.preserveSourceArtifacts !== false, signal: options.signal }); options.signal?.throwIfAborted(); schemaInspection = await inspectSchema(schemaInput, options.signal, agentSnapshot.location); } if (!schemaInspection) throw new Error(`Agent database inspection returned no result: ${agentPath}`); const { version: agentVersion, writerAppVersion, agentSchemaMeta } = schemaInspection; if (row.holdForDeletionRecovery) { recordAgentDatabaseRecoveryInspection(preparedDiscovery, agentPath, realAgentPath, schemaInspection); return; } if (agentVersion < supportedVersions.agent && purpose === "runtime" && row.agentId && (await (recoveryHolds ??= withArtifactPreservingStateReads(() => readAgentDeletionRecoveryHoldsInWorker({ env: options.env })))).some((held) => held.agentId === row.agentId && isSameOpenClawAgentDatabasePath(held.path, realAgentPath))) { const cause = new AgentDatabaseRecoveryHeldError(`Agent ${row.agentId} database is held for deletion recovery at schema ${agentVersion}; migration to schema ${supportedVersions.agent} requires Doctor.`); const refusal = createAgentDatabaseInspectionRefusal({ agentId: row.agentId, paths: [agentPath], reason: cause.message, cause }); (inspection.agentRefusals ??= []).push(refusal); createSubsystemLogger("state/agent-admission").warn(`${refusal.reason} ${refusal.repairHint}`); return; } if (agentVersion <= supportedVersions.agent && inspectOwnership && row.agentId) { const refusal = inspectAgentDatabaseAdmission({ agentId: row.agentId, path: agentPath, metadata: agentSchemaMeta ?? null }); if (refusal) { (inspection.agentRefusals ??= []).push(refusal); return; } } if (agentVersion < supportedVersions.agent) (inspection.pendingMigrations ??= []).push({ kind: "agent", path: agentPath, ...row.agentId !== void 0 ? { agentId: row.agentId } : {}, foundVersion: agentVersion, supportedVersion: supportedVersions.agent }); if (schemaInspection.failure && !schemaInspection.reason) throw schemaInspection.failure; if (schemaInspection?.reason) { if (startup || admissionTargets && !options.requireStartupMigrationReadiness) throw schemaInspection.failure ?? new Error(schemaInspection.reason); const failure = { kind: "agent", path: agentPath, reason: schemaInspection.reason, ...options.requireStartupMigrationReadiness ? { agentId: row.agentId } : {} }; if (schemaInspection.failure) indeterminateCauses.set(failure, schemaInspection.failure); inspection.indeterminate.push(failure); return; } if (agentVersion > supportedVersions.agent) { const incompatible = { kind: "agent", path: agentPath, ...row.agentId !== void 0 ? { agentId: row.agentId } : {}, foundVersion: agentVersion, supportedVersion: supportedVersions.agent, ...writerAppVersion ? { writerAppVersion } : {} }; if (!admissionTargets || !admissionConfig || !row.agentId || !canIsolateAgentDatabase(admissionConfig, row.agentId) || !recordInspectionFailure(row, inspection, new OpenClawDatabaseSchemaPreflightError([incompatible]))) inspection.incompatible.push(incompatible); } if (schemaInspection.integrityGateOutcome === "cached") requestOpenClawAgentDatabaseIntegrityCheck({ check: "quick", path: agentPath, env: options.env ?? process.env }); recordPreparedSchemaHeader?.(schemaInspection); return schemaInspection.preparationPending ? "defer" : void 0; } catch (error) { if (options.signal?.aborted) throw error; if (row.holdForDeletionRecovery) { recordRecoveryFailure(agentPath, formatErrorMessage(error)); return; } if (recordInspectionFailure(row, inspection, error)) return; if (options.requireStartupMigrationReadiness) throw error; const failure = { kind: "agent", path: agentPath, reason: formatErrorMessage(error) }; indeterminateCauses.set(failure, error); inspection.indeterminate.push(failure); } finally { if (agentSnapshot) { let failure; try { if (!await agentSnapshot.cleanupAsync()) failure = { error: /* @__PURE__ */ new Error(`SQLite read-only worker snapshot cleanup failed: ${agentSnapshot.location}`) }; } catch (error) { failure = { error }; } if (failure && !recordInspectionFailure(row, inspection, failure.error)) { if (row.holdForDeletionRecovery) recordRecoveryFailure(agentPath, formatErrorMessage(failure.error)); else inspection.indeterminate.push({ kind: "agent", path: agentPath, reason: formatErrorMessage(failure.error) }); } } } }, result, options.signal, scheduling); if (preparedDiscovery) options.onAgentDatabaseDiscovery?.(preparedDiscovery); options.onAgentInspection?.(stats); return result; } //#endregion export { preflightOpenClawDatabaseSchemas as n, preflightOpenClawStateDatabasePath as r, assertOpenClawDatabasesReady as t };