UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

213 lines (212 loc) • 12.7 kB
import { a as resolveGlobalSingleton } from "./global-singleton-0m-JziJW.mjs"; import { c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import { n as resolveRuntimeWorkerArgv } from "./runtime-worker-url-DoBSDhBS.mjs"; import { t as resolveIdentityPathViaExistingAncestorSync } from "./boundary-path-CAv_plX7.mjs"; import { t as openNodeSqliteDatabase } from "./node-sqlite-XIcwmOrl.mjs"; import { c as readSourceJournalMode, l as readSourceSidecars, s as prepareSqliteReadOnlyLocationSyncInProcess } from "./sqlite-readonly-location-OdGkIPCl.mjs"; import { a as isSqliteLockError, p as withSqliteNativeOpen } from "./sqlite-error-diagnostics-DCvjvszg.mjs"; import { o as runWithSqliteCleanup, r as OpenClawStateOwnershipMetadataError, t as OpenClawStateExternalOwnershipError } from "./sqlite-lifecycle-errors-DuOygjcn.mjs"; import { i as tableExists } from "./openclaw-state-db-schema-helpers-BlZefrPy.mjs"; import { s as OPENCLAW_SQLITE_BUSY_TIMEOUT_MS } from "./openclaw-state-db-contract-Cm9fKF2T.mjs"; import { r as runSqliteDeferredTransactionSync } from "./sqlite-transaction-DsZ3X6nD.mjs"; import { l as getOpenClawDatabaseMaintenanceScope } from "./openclaw-state-db-async-lifecycle-DLKxSPCk.mjs"; import { c as getStateDatabaseSchemaLease, i as assertStateDatabaseAccessAllowed, r as acquireStateDatabaseSchemaLease, t as GatewayStateOwnerContentionError } from "./gateway-state-owner-DmHycK5o.mjs"; import { t as captureRuntimeWorkerSource } from "./runtime-worker-generation-DCJaEqeS.mjs"; import { t as withSqliteSourceReadDatabase } from "./sqlite-source-handle-CLpCZnZK.mjs"; import { o as createSqliteReadOnlyWorkerError } from "./sqlite-readonly-worker-session-DK-j16po.mjs"; import { l as runOneShotSqliteInspection, u as runScopedSqliteInspection } from "./sqlite-readonly-worker-TSy0m8v6.mjs"; import { n as resolveRuntimeProcessEntrypointUrl } from "./runtime-process-url-D3H3_asn.mjs"; import { n as hydrateOpenClawStateWorkerError, r as retainOpenClawStateWorkerErrorPayload } from "./openclaw-state-worker-error-Cmtk9G4_.mjs"; import { r as normalizeOpenClawStateSchemaReadError } from "./openclaw-state-db-schema-migration-required-BpllCelk.mjs"; import { n as prepareSqliteReadOnlyLocationSync, t as prepareSqliteReadOnlyLocation } from "./sqlite-snapshot-source-BPapZs5n.mjs"; import { i as quarantineOrphanedSqliteSidecars, n as hasOrphanedSqliteSidecars } from "./sqlite-files-BEC3zbaw.mjs"; import { o as isGatewayExternallySupervised } from "./gateway-supervision-CUdBruRD.mjs"; import { a as requestSqliteWorkerSchemaMaintenance } from "./sqlite-worker-operation-admission-cNGSH5v0.mjs"; import { existsSync } from "node:fs"; import path from "node:path"; //#region src/infra/state-database-maintenance.ts const StateSchemaMutationConflictError = resolveGlobalSingleton(Symbol.for("openclaw.stateSchemaMutationConflictError"), () => class SchemaMutationConflictError extends Error { constructor(databasePath, cause) { super(`OpenClaw refused shared state schema mutation at ${databasePath} because another Gateway owns that state directory. Stop that Gateway or perform the update through its managed restart path, then retry.`, { cause }); this.name = "StateSchemaMutationConflictError"; } }); /** Ordinary transactions use SQLite; schema changes retain the installation's process owner. */ function withStateDatabaseSchemaMaintenance({ databasePath, busyTimeoutMs }, operation) { if (requestSqliteWorkerSchemaMaintenance(databasePath)) return operation(); const canonical = resolveIdentityPathViaExistingAncestorSync(databasePath); const inherited = getStateDatabaseSchemaLease(canonical); if (inherited) { inherited.assertCurrent(); return operation(); } let lease; try { lease = acquireStateDatabaseSchemaLease(canonical, { busyTimeoutMs }); } catch (error) { if (error instanceof GatewayStateOwnerContentionError) throw new StateSchemaMutationConflictError(canonical, error); throw error; } return runWithSqliteCleanup(lease, "state schema mutation", () => lease.run(operation)); } //#endregion //#region src/state/openclaw-state-ownership-worker.ts function readOwnershipWorkerReply(stdout, stderr) { let reply; try { reply = JSON.parse(stdout); } catch { throw createSqliteReadOnlyWorkerError("returned invalid ownership JSON", stderr); } if (!isRecord(reply) || Object.keys(reply).length !== 2 || typeof reply.ok !== "boolean") throw createSqliteReadOnlyWorkerError("returned an invalid ownership result", stderr); if (reply.ok && typeof reply.ownershipJson === "string") return reply.ownershipJson; if (!reply.ok && "workerError" in reply) { const error = /* @__PURE__ */ new Error("Shared-state ownership inspection failed"); retainOpenClawStateWorkerErrorPayload(error, reply.workerError); throw hydrateOpenClawStateWorkerError(error, { includeOrdinary: true }); } throw createSqliteReadOnlyWorkerError("returned an invalid ownership result", stderr); } /** Read live ownership in a child so closing the reader cannot release this process's locks. */ function inspectOpenClawStateOwnershipWithWorker(databasePath, signal) { return runScopedSqliteInspection(signal, (scopedSignal) => { scopedSignal?.throwIfAborted(); const { moduleUrl, runtimeGeneration } = captureRuntimeWorkerSource(resolveRuntimeProcessEntrypointUrl("stateOwnership")); return runOneShotSqliteInspection({ pathname: databasePath, operation: "state ownership", argv: [...resolveRuntimeWorkerArgv(moduleUrl), databasePath], runtimeGeneration, signal: scopedSignal, stoppedFailure: "ownership owner stopped", read: (output) => { scopedSignal?.throwIfAborted(); if (output.failure) throw createSqliteReadOnlyWorkerError(output.failure, output.stderr); return readOwnershipWorkerReply(output.stdout, output.stderr); } }); }); } //#endregion //#region src/state/openclaw-state-ownership.ts const STATE_SUPERVISION_KEY = "gateway.supervision"; const MAX_OWNERSHIP_TIMESTAMP_MS = 864e13; const MANAGER_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; function isOpenClawStateWriteContentionError(error) { return error instanceof GatewayStateOwnerContentionError || error instanceof StateSchemaMutationConflictError || isSqliteLockError(error); } function normalizeOpenClawStateManagerId(managerId) { const normalized = managerId.trim(); if (!MANAGER_ID_PATTERN.test(normalized)) throw new Error("External state ownership manager id must be a 1-128 character ASCII identifier."); return normalized; } function parseExternalOwnership(valueJson, databasePath) { let value; try { value = JSON.parse(valueJson); } catch { throw new OpenClawStateOwnershipMetadataError(databasePath, "reserved value is not valid JSON"); } const record = isRecord(value) ? value : void 0; const keys = record ? Object.keys(record).toSorted().join(",") : ""; const managerId = record?.managerId; const claimedAt = record?.claimedAt; if (keys !== "claimedAt,managerId,mode,version" || record?.version !== 1 || record?.mode !== "external" || typeof managerId !== "string" || !MANAGER_ID_PATTERN.test(managerId) || typeof claimedAt !== "number" || !Number.isSafeInteger(claimedAt) || claimedAt < 0 || claimedAt > MAX_OWNERSHIP_TIMESTAMP_MS) throw new OpenClawStateOwnershipMetadataError(databasePath, "reserved value does not match the version 1 external ownership contract"); return { version: 1, mode: "external", managerId, claimedAt }; } /** Inspect the reserved ownership row without entering the shared-state lifecycle. */ function inspectOpenClawStateOwnershipFromDatabase(database, databasePath, configMachineStateTableReady = false) { try { if (!configMachineStateTableReady && !tableExists(database, "config_machine_state")) return null; const ownershipSql = configMachineStateTableReady ? "SELECT value_json FROM config_machine_state WHERE state_key = ? LIMIT 1" : "SELECT value_json FROM config_machine_state NOT INDEXED WHERE state_key = ? LIMIT 1"; const row = database.prepare(ownershipSql).get(STATE_SUPERVISION_KEY); if (!row) return null; if (typeof row.value_json !== "string") throw new OpenClawStateOwnershipMetadataError(databasePath, "reserved value is not text"); return parseExternalOwnership(row.value_json, databasePath); } catch (error) { throw normalizeOpenClawStateSchemaReadError(error, databasePath); } } function inspectOwnershipThroughConnection(location, databasePath, openStateSchemaReadAdmission) { const database = withSqliteNativeOpen(() => openNodeSqliteDatabase(location, { readOnly: true })); let closeAdmission; try { closeAdmission = openStateSchemaReadAdmission?.(database); database.exec(`PRAGMA busy_timeout = ${OPENCLAW_SQLITE_BUSY_TIMEOUT_MS}; PRAGMA query_only = ON; PRAGMA trusted_schema = OFF;`); return inspectOpenClawStateOwnershipFromDatabase(database, databasePath); } finally { try { closeAdmission?.(); } finally { database.close(); } } } /** Inspect one resolved state database path without mutating its state tree. */ function inspectOpenClawStateOwnershipAtPath(databasePath) { const resolvedPath = path.resolve(databasePath); if (!existsSync(resolvedPath)) return null; const prepared = prepareSqliteReadOnlyLocationSync(resolvedPath); try { return inspectOwnershipThroughConnection(prepared.location, resolvedPath); } finally { prepared.cleanup(); } } /** Runs only in an isolated child: closing a source must not release a live writer's locks. */ function inspectOpenClawStateOwnershipInProcess(databasePath) { const sidecars = readSourceSidecars(databasePath); if (readSourceJournalMode(databasePath) === "wal" && sidecars.wal && sidecars.shm && !sidecars.journal) return withSqliteSourceReadDatabase(databasePath, "source", (database) => { database.exec(`PRAGMA busy_timeout = ${OPENCLAW_SQLITE_BUSY_TIMEOUT_MS}; PRAGMA query_only = ON; PRAGMA trusted_schema = OFF;`); return runSqliteDeferredTransactionSync(database, () => inspectOpenClawStateOwnershipFromDatabase(database, databasePath)); }); const prepared = prepareSqliteReadOnlyLocationSyncInProcess(databasePath); try { return inspectOwnershipThroughConnection(prepared.location, databasePath); } finally { prepared.cleanup(); } } function assertOwnershipAllowsWrite(status, databasePath, env) { if (status && !isGatewayExternallySupervised(env)) throw new OpenClawStateExternalOwnershipError(databasePath, status.managerId); } /** Check write admission; callers may defer orphan-sidecar recovery until mutation is certain. */ async function assertOpenClawStateWriteAllowedAtPath(options) { options.signal?.throwIfAborted(); const databasePath = path.resolve(options.databasePath); assertStateDatabaseAccessAllowed(databasePath); if (options.recoverOrphanedSidecars !== false && hasOrphanedSqliteSidecars(databasePath)) withStateDatabaseSchemaMaintenance({ databasePath }, () => quarantineOrphanedSqliteSidecars(databasePath)); if (!existsSync(databasePath)) return; const env = options.env ?? process.env; if (!options.openStateSchemaReadAdmission && !getOpenClawDatabaseMaintenanceScope() && !getStateDatabaseSchemaLease(databasePath)) { const ownershipJson = await inspectOpenClawStateOwnershipWithWorker(databasePath, options.signal); options.signal?.throwIfAborted(); assertStateDatabaseAccessAllowed(databasePath); assertOwnershipAllowsWrite(ownershipJson === "null" ? null : parseExternalOwnership(ownershipJson, databasePath), databasePath, env); return; } const prepared = await prepareSqliteReadOnlyLocation(databasePath, { preserveSourceArtifacts: true, signal: options.signal }); try { options.signal?.throwIfAborted(); assertStateDatabaseAccessAllowed(databasePath); assertOwnershipAllowsWrite(inspectOwnershipThroughConnection(prepared.location, databasePath, options.openStateSchemaReadAdmission), databasePath, env); } finally { await prepared.cleanupAsync(); } } /** Fence shared-state writes once an external manager has claimed ownership. */ function assertOpenClawStateWriteAllowed(options) { const resolvedPath = path.resolve(options.databasePath); assertStateDatabaseAccessAllowed(resolvedPath); assertOwnershipAllowsWrite(inspectOpenClawStateOwnershipFromDatabase(options.database, resolvedPath, options.schemaReady), resolvedPath, options.env ?? process.env); } //#endregion export { inspectOpenClawStateOwnershipFromDatabase as a, normalizeOpenClawStateManagerId as c, inspectOpenClawStateOwnershipAtPath as i, StateSchemaMutationConflictError as l, assertOpenClawStateWriteAllowed as n, inspectOpenClawStateOwnershipInProcess as o, assertOpenClawStateWriteAllowedAtPath as r, isOpenClawStateWriteContentionError as s, STATE_SUPERVISION_KEY as t, withStateDatabaseSchemaMaintenance as u };