openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
8,433 lines • 345 kB
JavaScript
import { i as normalizeOptionalString, l as isRecord, o as asNullableRecord, s as asOptionalObjectRecord, t as normalizeLowercaseStringOrEmpty } from "./string-coerce-6O-WySZM.js";
import { C as hasErrnoCode, _ as resolveGlobalSingleton, m as pruneMapToMaxSize, w as isMissingPathError, x as parseBooleanValue } from "./runtime-doctor-migrations-BdgZ9Dfo.js";
import { a as extractErrorCode, o as toErrorObject, t as formatErrorMessage } from "./errors-njERyofs.js";
import { r as resolveIdentityPathViaExistingAncestorSync } from "./types.secrets-C41nwEA3.js";
import { n as MAX_TIMER_TIMEOUT_MS } from "./number-coercion-C4aQ0B2l.js";
import { f as resolveGatewayLockDir, h as resolveStateDir, p as resolveGatewayLockDirForCanonicalStateDir, x as containsEnvVarReference } from "./redact-B_47uiIQ.js";
import { c as literal, d as object, h as string, t as _enum, u as number } from "./schemas-BDyHDkT2.js";
import { n as normalizeWindowsPathPreservingCase, o as resolveRequiredOsHomeDir, t as isPathInside } from "./utils-DhELI9oB.js";
import { a as registerSignalExitFinalizer, l as resolvePreferredOpenClawTmpDir, s as getChildLogger, t as createSubsystemLogger } from "./subsystem-Bga3ZpNK.js";
import { n as createDeferredCore, t as isPromiseLike } from "./promise-like-ByIJ_1fR.js";
import { t as compareValidSemver } from "./semver-Bf9GAGQM.js";
import { createRequire } from "node:module";
import { realpath, stat } from "node:fs/promises";
import { normalizeWindowsPathForComparison } from "@openclaw/fs-safe/path";
import "node:util";
import path, { posix } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { AsyncLocalStorage, AsyncResource } from "node:async_hooks";
import fs$1, { chmodSync, closeSync, existsSync, openSync, realpathSync, statSync, unlinkSync, writeFileSync, default as openClawRootFsSync } from "node:fs";
import { readFileWindowFullySync, sameFileIdentity } from "@openclaw/fs-safe/advanced";
import os, { endianness } from "node:os";
import { hash, randomUUID } from "node:crypto";
import { performance as performance$1 } from "node:perf_hooks";
import { MessageChannel, MessagePort, getEnvironmentData, isMainThread, receiveMessageOnPort, setEnvironmentData, threadId } from "node:worker_threads";
import "semver";
import childProcess, { spawnSync } from "node:child_process";
import { sha256FileSync } from "@openclaw/fs-safe/durability";
import { InsertQueryNode, Kysely, SelectQueryNode, SqliteDialect, sql } from "kysely";
import "node:v8";
import { acquireFileLockSync } from "@openclaw/fs-safe/file-lock";
import "node:util/types";
import { setImmediate } from "node:timers/promises";
import { probeTreeClone } from "@openclaw/fs-safe/copy";
//#region src/infra/abort-signal.ts
function isAbortError(error) {
if (!error || typeof error !== "object") return false;
try {
if (("name" in error ? String(error.name) : "") === "AbortError") return true;
return ("message" in error && typeof error.message === "string" ? error.message : "") === "This operation was aborted";
} catch {
return false;
}
}
//#endregion
//#region src/infra/process-env.ts
/** Read one environment value using the same Windows key precedence as child_process. */
function resolveEnvironmentValue(env, name, platform = process.platform) {
if (!env) return;
if (platform !== "win32") return env[name] ?? (name === "PATH" ? env.Path : void 0);
const normalizedName = name.toUpperCase();
const key = Object.keys(env).toSorted().find((candidate) => candidate.toUpperCase() === normalizedName);
return key === void 0 ? void 0 : env[key];
}
/** Merge child environments while preserving Node's platform-specific key semantics. */
function mergeProcessEnv(sources, platform = process.platform) {
const merged = {};
for (const source of sources) {
if (!source) continue;
const keys = Object.keys(source);
const sourceKeys = /* @__PURE__ */ new Set();
for (const key of platform === "win32" ? keys.toSorted() : keys) {
if (platform === "win32") {
const normalizedKey = key.toUpperCase();
if (sourceKeys.has(normalizedKey)) continue;
sourceKeys.add(normalizedKey);
for (const previousKey of Object.keys(merged)) if (previousKey.toUpperCase() === normalizedKey) delete merged[previousKey];
}
const value = source[key];
if (value === void 0) delete merged[key];
else merged[key] = value;
}
}
return merged;
}
const DIAGNOSTIC_PROCESS_ENV_KEYS = /* @__PURE__ */ new Set([
"PATH",
"Path",
"HOME",
"USER",
"LOGNAME",
"TMPDIR",
"TMP",
"TEMP",
"LANG",
"LANGUAGE",
"TZ",
"LC_ALL",
"LC_COLLATE",
"LC_CTYPE",
"LC_MESSAGES",
"LC_MONETARY",
"LC_NUMERIC",
"LC_TIME",
"LC_ADDRESS",
"LC_IDENTIFICATION",
"LC_MEASUREMENT",
"LC_NAME",
"LC_PAPER",
"LC_TELEPHONE",
"SYSTEMROOT",
"WINDIR",
"COMSPEC",
"PATHEXT",
"SYSTEMDRIVE",
"USERPROFILE",
"HOMEDRIVE",
"HOMEPATH",
"USERNAME",
"USERDOMAIN",
"APPDATA",
"LOCALAPPDATA",
"PROGRAMDATA",
"ALLUSERSPROFILE",
"PROGRAMFILES",
"PROGRAMFILES(X86)",
"PROGRAMW6432",
"COMMONPROGRAMFILES",
"COMMONPROGRAMFILES(X86)",
"COMMONPROGRAMW6432",
"PSMODULEANALYSISCACHEPATH"
]);
/** Project only native port/process diagnostic context; never mutate the parent environment. */
function resolveDiagnosticProcessEnv(env = process.env, platform = process.platform) {
return Object.fromEntries(Object.entries(mergeProcessEnv([env], platform)).filter(([key]) => DIAGNOSTIC_PROCESS_ENV_KEYS.has(platform === "win32" ? key.toUpperCase() : key)));
}
//#endregion
//#region src/infra/windows-process-start.ts
const DEFAULT_TIMEOUT_MS = 5e3;
const DEFAULT_PROCESS_START_TIMEOUT_MS = 1e4;
const DEFAULT_WINDOWS_SYSTEM_ROOT = "C:\\Windows";
let nativeProcessStartTime;
function loadNativeProcessStartTime() {
const kernel32 = createRequire(import.meta.url)("koffi").load("kernel32.dll");
const openProcess = kernel32.func("void * __stdcall OpenProcess(uint32_t access, int32_t inheritHandle, uint32_t processId)");
const getProcessTimes = kernel32.func("int32_t __stdcall GetProcessTimes(void *process, void *creation, void *exit, void *kernel, void *user)");
const closeHandle = kernel32.func("int32_t __stdcall CloseHandle(void *handle)");
return (pid) => {
const handle = openProcess(4096, 0, pid);
if (handle === null) return null;
try {
const creation = Buffer.alloc(8);
if (!getProcessTimes(handle, creation, Buffer.alloc(8), Buffer.alloc(8), Buffer.alloc(8))) return null;
const ticks = creation.readBigUInt64LE();
if (ticks === 0n) return null;
return Number(ticks / 10000n - 11644473600000n);
} finally {
closeHandle(handle);
}
};
}
function readNativeProcessStartTime(pid) {
if (process.platform !== "win32" || typeof SEALED_RUNTIME_BUILD === "boolean" && SEALED_RUNTIME_BUILD) return null;
try {
nativeProcessStartTime ??= loadNativeProcessStartTime();
return nativeProcessStartTime(pid);
} catch {
return null;
}
}
function windowsSystemRoot(env) {
const configured = resolveEnvironmentValue(env, "SystemRoot", "win32") ?? resolveEnvironmentValue(env, "WINDIR", "win32");
if (!configured) return DEFAULT_WINDOWS_SYSTEM_ROOT;
const normalized = path.win32.normalize(configured);
return /^[A-Za-z]:\\/.test(normalized) && !normalized.startsWith("\\\\") ? normalized : DEFAULT_WINDOWS_SYSTEM_ROOT;
}
function windowsPowerShellPath(env) {
return path.win32.join(windowsSystemRoot(env), "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
}
function windowsWmicPath(env) {
return path.win32.join(windowsSystemRoot(env), "System32", "wbem", "wmic.exe");
}
function decodeWindowsProcessOutput(output) {
if (!Buffer.isBuffer(output)) return output;
return output.length >= 2 && output[0] === 255 && output[1] === 254 ? output.toString("utf16le") : output.toString("utf8");
}
function parseWindowsProcessStartTime(raw) {
const lines = decodeWindowsProcessOutput(raw).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
const value = lines.find((line) => line.toLowerCase().startsWith("creationdate="))?.slice(13).trim() ?? lines.find((line) => line.toLowerCase() !== "creationdate") ?? "";
const parsedIso = Date.parse(value);
if (Number.isFinite(parsedIso)) return parsedIso;
const dmtf = value.match(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(\d{6})([+-])(\d{3})$/);
if (!dmtf) return null;
const [, year, month, day, hour, minute, second, microseconds, offsetSign, offset] = dmtf;
return Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second), Math.floor(Number(microseconds) / 1e3)) - Number(offset) * 6e4 * (offsetSign === "+" ? 1 : -1);
}
/** Read a stable Windows process creation time for lock-owner identity checks. */
function readWindowsProcessStartTimeSync(pid, timeoutMs = DEFAULT_PROCESS_START_TIMEOUT_MS, env = process.env) {
if (!Number.isInteger(pid) || pid <= 0 || pid > 4294967295) return null;
const deadline = Date.now() + timeoutMs;
const nativeStartTime = readNativeProcessStartTime(pid);
if (nativeStartTime !== null) return nativeStartTime;
const powershellBudgetMs = deadline - Date.now();
if (powershellBudgetMs <= 0) return null;
const powershell = spawnSync(windowsPowerShellPath(env), [
"-NoProfile",
"-NonInteractive",
"-Command",
`$process = [System.Diagnostics.Process]::GetProcessById(${pid}); try { [Console]::Out.Write($process.StartTime.ToUniversalTime().ToString("o")) } finally { $process.Dispose() }`
], {
encoding: "utf8",
env: resolveDiagnosticProcessEnv(env, "win32"),
timeout: Math.min(powershellBudgetMs, DEFAULT_TIMEOUT_MS),
windowsHide: true
});
if (!powershell.error && powershell.status === 0) {
const startTime = parseWindowsProcessStartTime(powershell.stdout);
if (startTime !== null) return startTime;
}
const remainingMs = deadline - Date.now();
if (remainingMs <= 0) return null;
const wmic = spawnSync(windowsWmicPath(env), [
"process",
"where",
`ProcessId=${pid}`,
"get",
"CreationDate",
"/value"
], {
env: resolveDiagnosticProcessEnv(env, "win32"),
timeout: remainingMs,
windowsHide: true,
stdio: [
"ignore",
"pipe",
"ignore"
]
});
return !wmic.error && wmic.status === 0 ? parseWindowsProcessStartTime(wmic.stdout) : null;
}
//#endregion
//#region src/shared/freebsd-process-identity-native.ts
/** Normal installations use the dependency's public loader. */
function loadFreeBsdProcessIdentityNative() {
if (typeof SEALED_RUNTIME_BUILD === "boolean" && SEALED_RUNTIME_BUILD) throw new Error("FreeBSD process identity is unavailable in this sealed runtime");
return createRequire(import.meta.url)("koffi/indirect");
}
//#endregion
//#region src/shared/freebsd-process-identity.ts
let native;
function getSysctl() {
if (!native) {
const library = loadFreeBsdProcessIdentityNative().load(null);
native = {
library,
sysctl: library.func("int sysctl(const int *name, unsigned int namelen, _Out_ void *oldp, _Inout_ size_t *oldlenp, const void *newp, size_t newlen)")
};
}
return native.sysctl;
}
function readTimeval(bytes, offset) {
const seconds = bytes.readBigInt64LE(offset);
const microseconds = bytes.readBigInt64LE(offset + 8);
if (seconds < 0n || microseconds < 0n || microseconds >= 1000000n) throw new Error("Invalid FreeBSD process identity timeval");
return seconds * 1000000n + microseconds;
}
/** Read the kernel's monotonic process start time in microseconds. */
function readFreeBsdProcessStartTime(pid) {
if (process.platform !== "freebsd" || process.arch !== "x64" && process.arch !== "arm64" || endianness() !== "LE" || !Number.isInteger(pid) || pid <= 0 || pid > 2147483647) return null;
try {
const sysctl = getSysctl();
const bytes = Buffer.alloc(1120);
for (const [mib, offset, length] of [
[
[1, 21],
0,
16
],
[
[
1,
14,
1,
pid
],
16,
1088
],
[
[1, 21],
1104,
16
]
]) {
const actual = [length];
if (sysctl(mib, mib.length, bytes.subarray(offset, offset + length), actual, null, 0) !== 0 || actual[0] !== length) return null;
}
if (bytes.readInt32LE(16) !== 1088 || bytes.readInt32LE(20) !== 0 || bytes.readInt32LE(88) !== pid || !bytes.subarray(0, 16).equals(bytes.subarray(1104))) return null;
const start = readTimeval(bytes, 352) - readTimeval(bytes, 0);
return start >= 0n && start <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(start) : null;
} catch {
return null;
}
}
//#endregion
//#region src/shared/pid-alive.ts
const PROCESS_START_TIMEOUT_MS = 1e3;
let darwinNative;
function readDarwinNativeIdentity(pid) {
if (process.platform !== "darwin" || process.arch !== "arm64" || pid > 2147483647 || typeof SEALED_RUNTIME_BUILD === "boolean" && SEALED_RUNTIME_BUILD) return null;
try {
if (!darwinNative) {
const library = createRequire(import.meta.url)("koffi").load("/usr/lib/libproc.dylib");
darwinNative = {
library,
query: library.func("int proc_pidinfo(int pid, int flavor, uint64_t arg, _Out_ void *buffer, int buffersize)")
};
}
const bytes = Buffer.alloc(136);
if (darwinNative.query(pid, 3, 0, bytes, bytes.length) !== bytes.length) return null;
const parentPid = bytes.readUInt32LE(16);
const seconds = bytes.readBigUInt64LE(120);
const microseconds = bytes.readBigUInt64LE(128);
if (bytes.readUInt32LE(12) !== pid || parentPid > 2147483647 || seconds === 0n || seconds > BigInt(Number.MAX_SAFE_INTEGER) || microseconds >= 1000000n) return null;
return {
parentPid,
startedAt: Number(seconds),
microseconds: Number(microseconds)
};
} catch {
return null;
}
}
let selfStartTime = null;
function isValidPid(pid) {
return Number.isInteger(pid) && pid > 0;
}
/**
* Check if every thread has exited by reading Linux /proc/<pid>/status.
* Returns false on non-Linux platforms or if the proc file can't be read.
*/
function isZombieProcess(pid) {
if (process.platform !== "linux") return false;
try {
const status = fs$1.readFileSync(`/proc/${pid}/status`, "utf8");
return status.match(/^State:\s+(\S)/m)?.[1] === "Z" && /^Threads:[ \t]+1[ \t]*$/m.test(status);
} catch {
return false;
}
}
/** Returns true only when a positive PID exists and is not a Linux zombie process. */
function isPidAlive(pid) {
if (!isValidPid(pid)) return false;
try {
process.kill(pid, 0);
} catch (err) {
if (err.code !== "EPERM") return false;
}
return !isZombieProcess(pid);
}
/** Returns true only when the PID is invalid, missing, or known to be a Linux zombie. */
function isPidDefinitelyDead(pid) {
if (!isValidPid(pid)) return true;
try {
process.kill(pid, 0);
} catch (err) {
return err.code === "ESRCH";
}
return isZombieProcess(pid);
}
function getDarwinProcessStartTime(pid, env, timeoutMs) {
const started = performance.now();
const native = readDarwinNativeIdentity(pid);
if (native) return native.startedAt;
const remainingMs = timeoutMs === void 0 ? PROCESS_START_TIMEOUT_MS : Math.ceil(timeoutMs - (performance.now() - started));
if (remainingMs <= 0) return null;
try {
const startedAt = childProcess.execFileSync("/bin/ps", [
"-o",
"lstart=",
"-p",
String(pid)
], {
encoding: "utf8",
env: {
...resolveDiagnosticProcessEnv(env),
LC_ALL: "C",
TZ: "UTC"
},
stdio: [
"ignore",
"pipe",
"ignore"
],
timeout: remainingMs,
killSignal: "SIGKILL"
}).trim();
const startedAtMs = Date.parse(`${startedAt} UTC`);
return Number.isFinite(startedAtMs) ? Math.floor(startedAtMs / 1e3) : null;
} catch {
return null;
}
}
/** Read the Linux procfs start identity used by Linux-owned runtime state. */
function getProcessStartTime(pid) {
if (!isValidPid(pid) || process.platform !== "linux") return null;
try {
const stat = fs$1.readFileSync(`/proc/${pid}/stat`, "utf8");
const commEndIndex = stat.lastIndexOf(")");
if (commEndIndex < 0) return null;
const fields = stat.slice(commEndIndex + 1).trimStart().split(/\s+/);
const starttime = Number(fields[19]);
return Number.isInteger(starttime) && starttime >= 0 ? starttime : null;
} catch {
return null;
}
}
/** Read a cross-platform process identity for filesystem lock ownership. */
function getFileLockProcessStartTime(pid, env = process.env, timeoutMs) {
if (!isValidPid(pid)) return null;
const isSelf = pid === process.pid;
if (isSelf && selfStartTime !== null) return selfStartTime;
const startTime = process.platform === "darwin" ? getDarwinProcessStartTime(pid, env, timeoutMs) : process.platform === "win32" ? readWindowsProcessStartTimeSync(pid, timeoutMs, env) : process.platform === "freebsd" ? readFreeBsdProcessStartTime(pid) : getProcessStartTime(pid);
if (isSelf && startTime !== null) selfStartTime = startTime;
return startTime;
}
//#endregion
//#region packages/normalization-core/src/node-crypto.ts
function sha256Hex(input) {
return hash("sha256", input, "hex");
}
function sha256HexPrefixCore(input, length) {
return sha256Hex(input).slice(0, length);
}
//#endregion
//#region src/infra/crypto-digest.ts
function sha256Base64Url(input) {
return hash("sha256", input, "base64url");
}
/** Matches the streaming helper's symlink-following path contract. */
function sha256FileSync$1(filePath) {
const descriptor = openSync(filePath, "r");
try {
return sha256FileSync(descriptor).digest;
} finally {
closeSync(descriptor);
}
}
//#endregion
//#region node-version.mjs
const NODE_RELEASE_VERSION_RE = /^v?((?:0|[1-9]\d*))\.((?:0|[1-9]\d*))\.((?:0|[1-9]\d*))(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u;
const NODE_RELEASE_FLOORS = [{
major: 24,
minor: 16,
patch: 0
}, {
major: 26,
minor: 1,
patch: 0
}];
NODE_RELEASE_FLOORS[NODE_RELEASE_FLOORS.length - 1];
`${NODE_RELEASE_FLOORS.map(({ major, minor, patch }, index) => `>=${major}.${minor}.${patch}${index < NODE_RELEASE_FLOORS.length - 1 ? ` <${major + 1}` : ""}`).join(" || ").replaceAll(" || ", ", ").replace(/, ([^,]+)$/, ", or $1")}`;
/** Parses an anchored release SemVer, allowing a leading v and valid build metadata. */
function parseNodeReleaseVersion(value) {
if (typeof value !== "string") return null;
const match = NODE_RELEASE_VERSION_RE.exec(value.trim());
if (!match) return null;
const version = {
major: Number(match[1]),
minor: Number(match[2]),
patch: Number(match[3])
};
return Object.values(version).every(Number.isSafeInteger) ? version : null;
}
function isNodeVersionAtLeast(version, minimum) {
if (!version) return false;
if (version.major !== minimum.major) return version.major > minimum.major;
if (version.minor !== minimum.minor) return version.minor > minimum.minor;
return version.patch >= minimum.patch;
}
function renderProcessNodeVersionCheck() {
return `((value) => {
if (typeof value !== "string") return false;
const match = new RegExp(${JSON.stringify(NODE_RELEASE_VERSION_RE.source)}, "u").exec(value.trim());
if (!match) return false;
const version = { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) };
if (!Object.values(version).every(Number.isSafeInteger)) return false;
const floors = ${JSON.stringify(NODE_RELEASE_FLOORS)};
const minimum = floors.find((floor) => floor.major === version.major);
const atLeast = (floor) =>
version.major > floor.major ||
(version.major === floor.major &&
(version.minor > floor.minor ||
(version.minor === floor.minor && version.patch >= floor.patch)));
return minimum ? atLeast(minimum) : version.major > floors[floors.length - 1].major;
})(process.versions.node)`;
}
renderProcessNodeVersionCheck();
//#endregion
//#region src/infra/kysely-sync-cache-state.ts
const { kyselyByDatabase, queryErrorHandlerByDatabase } = resolveGlobalSingleton(Symbol.for("openclaw.sqliteKyselyCacheState"), () => ({
kyselyByDatabase: /* @__PURE__ */ new WeakMap(),
queryErrorHandlerByDatabase: /* @__PURE__ */ new WeakMap()
}));
const statementCacheSymbol = Symbol.for("openclaw.kyselySyncStatementCache");
const statementInvalidationSymbol = Symbol.for("openclaw.kyselySyncStatementInvalidation");
const statementCacheEnabledSymbol = Symbol.for("openclaw.kyselySyncStatementCacheEnabled");
const authorizerActiveSymbol = Symbol.for("openclaw.kyselySyncAuthorizerActive");
const disposeCallbacksSymbol = Symbol.for("openclaw.sqliteDisposeCallbacks");
const statementCacheCapacity = 64;
const statementCacheEntryBytes = 65536;
/** Retire dependent native resources before this connection closes or is replaced. */
function registerNodeSqliteDisposeCallback(db, callback) {
const owner = db;
installStatementInvalidation(owner);
const callbacks = owner[disposeCallbacksSymbol] ??= /* @__PURE__ */ new Set();
callbacks.add(callback);
return () => {
callbacks.delete(callback);
};
}
function disposeNodeSqliteDependents(owner, reason = "close") {
for (const callback of owner[disposeCallbacksSymbol] ?? []) callback(reason);
}
/** Register the lifecycle owner's handler for synchronous Kysely query failures. */
function registerNodeSqliteKyselyQueryErrorHandler(db, handler) {
queryErrorHandlerByDatabase.set(db, handler);
}
/** Drop cached Kysely state for a DatabaseSync. */
function clearNodeSqliteKyselyCacheForDatabase(db) {
delete db[statementCacheSymbol];
kyselyByDatabase.delete(db);
queryErrorHandlerByDatabase.delete(db);
}
function installStatementInvalidation(owner) {
if (owner[statementInvalidationSymbol]) return;
if (typeof owner.setAuthorizer === "function") {
const setAuthorizer = owner.setAuthorizer.bind(owner);
Object.defineProperty(owner, "setAuthorizer", {
configurable: true,
writable: true,
value(callback) {
setAuthorizer(callback);
this[authorizerActiveSymbol] = callback !== null;
delete this[statementCacheSymbol];
}
});
}
if (typeof owner.deserialize === "function") {
const deserialize = owner.deserialize.bind(owner);
Object.defineProperty(owner, "deserialize", {
configurable: true,
writable: true,
value(...args) {
disposeNodeSqliteDependents(this, "replace");
try {
deserialize(...args);
} finally {
delete this[statementCacheSymbol];
}
}
});
}
for (const method of ["close", Symbol.dispose]) if (typeof owner[method] === "function") {
const dispose = owner[method].bind(owner);
Object.defineProperty(owner, method, {
configurable: true,
writable: true,
value() {
disposeNodeSqliteDependents(this);
clearNodeSqliteKyselyCacheForDatabase(this);
return dispose();
}
});
}
Object.defineProperty(owner, statementInvalidationSymbol, {
configurable: true,
value: true
});
}
/**
* Enable bounded statement caching for a lifecycle-owned database that has not
* installed an authorizer before this call.
*/
function enableNodeSqliteKyselyStatementCache(db) {
const owner = db;
installStatementInvalidation(owner);
owner[statementCacheEnabledSymbol] = true;
}
function queryFitsStatementCache(sql, parameters) {
let bytes = Buffer.byteLength(sql);
if (bytes > statementCacheEntryBytes) return false;
for (const parameter of parameters) {
if (typeof parameter === "string") {
if (parameter.length > statementCacheEntryBytes - bytes) return false;
bytes += Buffer.byteLength(parameter);
} else if (ArrayBuffer.isView(parameter)) bytes += parameter.byteLength;
if (bytes > statementCacheEntryBytes) return false;
}
return true;
}
function executeWithCachedStatement(db, sql, parameters, execute) {
const owner = db;
if (!owner[statementCacheEnabledSymbol] || owner[authorizerActiveSymbol] || !queryFitsStatementCache(sql, parameters)) return execute(db.prepare(sql));
let cache = owner[statementCacheSymbol];
if (!cache) {
cache = {
statements: /* @__PURE__ */ new Map(),
candidates: /* @__PURE__ */ new Set(),
active: /* @__PURE__ */ new WeakSet()
};
Object.defineProperty(owner, statementCacheSymbol, {
configurable: true,
value: cache
});
}
const cached = cache.statements.get(sql);
let statement;
if (cached && !cache.active.has(cached)) {
cache.statements.delete(sql);
cache.statements.set(sql, cached);
statement = cached;
} else {
statement = db.prepare(sql);
if (!cached && cache.candidates.delete(sql)) {
cache.statements.set(sql, statement);
pruneMapToMaxSize(cache.statements, statementCacheCapacity);
} else if (!cached) {
cache.candidates.add(sql);
if (cache.candidates.size > statementCacheCapacity) {
const oldestCandidate = cache.candidates.values().next().value;
if (oldestCandidate !== void 0) cache.candidates.delete(oldestCandidate);
}
}
}
cache.active.add(statement);
try {
return execute(statement);
} finally {
cache.active.delete(statement);
}
}
//#endregion
//#region src/infra/sqlite-reader-lifecycle.ts
const readerOwners = resolveGlobalSingleton(Symbol.for("openclaw.sqliteReaderOwners"), () => new AsyncLocalStorage());
const activeReaders = resolveGlobalSingleton(Symbol.for("openclaw.sqliteActiveReaders"), () => ({
byDatabase: /* @__PURE__ */ new WeakMap(),
byPath: /* @__PURE__ */ new Map()
}));
const connections = resolveGlobalSingleton(Symbol.for("openclaw.sqliteReaderConnections"), () => ({
nextId: 0,
byDatabase: /* @__PURE__ */ new WeakMap(),
byPath: /* @__PURE__ */ new Map(),
finalizer: new FinalizationRegistry((connection) => forgetConnection(connection))
}));
/** The same Windows file can arrive with a namespaced or differently cased path. */
function sqliteReaderDatabasePathKey(databasePath) {
const resolved = path.resolve(databasePath);
return process.platform === "win32" ? normalizeWindowsPathForComparison(resolved) : resolved;
}
function boundedOperation(operation) {
return (operation.trim() || "sqlite reader").slice(0, 120);
}
function captureSqliteReaderOwner() {
const owner = readerOwners.getStore();
return owner ? { ...owner } : void 0;
}
function currentOwner(fallbackOperation, capturedOwner) {
const inherited = capturedOwner ?? readerOwners.getStore();
return {
operation: boundedOperation(inherited?.operation ?? fallbackOperation),
ownerKind: inherited?.ownerKind ?? (isMainThread ? "main" : "worker"),
...inherited?.actorId !== void 0 ? { actorId: inherited.actorId } : {}
};
}
function connectionFor(database) {
let connection = connections.byDatabase.get(database);
if (!connection) {
const now = Date.now();
const location = database.location();
connection = {
id: ++connections.nextId,
path: location ? sqliteReaderDatabasePathKey(location) : void 0,
database: new WeakRef(database),
owner: currentOwner("sqlite connection"),
openedAtMs: now
};
connections.byDatabase.set(database, connection);
if (connection.path) {
const entries = connections.byPath.get(connection.path) ?? /* @__PURE__ */ new Map();
entries.set(connection.id, connection);
connections.byPath.set(connection.path, entries);
}
connections.finalizer.register(database, connection, connection);
}
return connection;
}
/** Forget observations only; explicit reader custody belongs to its caller. */
function forgetConnection(connection) {
if (connection.path) {
const entries = connections.byPath.get(connection.path);
entries?.delete(connection.id);
if (entries?.size === 0) connections.byPath.delete(connection.path);
}
const database = connection.database.deref();
if (database && connections.byDatabase.get(database) === connection) connections.byDatabase.delete(database);
connections.finalizer.unregister(connection);
}
/** Register weak metadata without replacing native methods or acquiring lifecycle custody. */
function registerSqliteReaderConnection(database) {
if (database.isOpen) connectionFor(database);
}
function retainSqliteReader(database, fallbackOperation, capturedOwner) {
const connection = connectionFor(database);
const now = Date.now();
const reader = {
...currentOwner(fallbackOperation, capturedOwner),
kind: "iterator",
connectionId: connection.id,
threadId,
startedAtMs: now,
lastProgressAtMs: now
};
const token = Symbol(reader.operation);
const databaseReaders = activeReaders.byDatabase.get(database) ?? /* @__PURE__ */ new Map();
databaseReaders.set(token, reader);
activeReaders.byDatabase.set(database, databaseReaders);
const databasePath = connection.path;
const pathReaders = databasePath ? activeReaders.byPath.get(databasePath) ?? /* @__PURE__ */ new Map() : void 0;
pathReaders?.set(token, reader);
if (databasePath && pathReaders) activeReaders.byPath.set(databasePath, pathReaders);
let released = false;
return {
progress() {
if (!released) reader.lastProgressAtMs = Date.now();
},
release() {
if (released) return;
released = true;
databaseReaders.delete(token);
if (databaseReaders.size === 0 && activeReaders.byDatabase.get(database) === databaseReaders) activeReaders.byDatabase.delete(database);
pathReaders?.delete(token);
if (databasePath && pathReaders?.size === 0 && activeReaders.byPath.get(databasePath) === pathReaders) activeReaders.byPath.delete(databasePath);
}
};
}
function diagnostics(readers) {
const now = Date.now();
return [...readers].map(({ startedAtMs, lastProgressAtMs, ...reader }) => Object.assign(reader, {
ageMs: Math.max(0, now - startedAtMs),
idleMs: Math.max(0, now - lastProgressAtMs)
})).toSorted((left, right) => right.ageMs - left.ageMs).slice(0, 8);
}
/** Observed local activity is diagnostic evidence, not proof of which connection owns a WAL lock. */
function readSqliteReaderDiagnosticsForPath(databasePath) {
const key = sqliteReaderDatabasePathKey(databasePath);
const entries = connections.byPath.get(key);
const localConnections = [];
const now = Date.now();
for (const connection of entries?.values() ?? []) {
const database = connection.database.deref();
if (!database?.isOpen) {
forgetConnection(connection);
continue;
}
const location = database.location();
if (!location || sqliteReaderDatabasePathKey(location) !== key) {
forgetConnection(connection);
continue;
}
const readers = activeReaders.byDatabase.get(database);
localConnections.push({
...connection.owner,
connectionId: connection.id,
threadId,
ageMs: Math.max(0, now - connection.openedAtMs),
transactionOpen: database.isTransaction,
trackedReaders: readers?.size ?? 0
});
}
const readers = activeReaders.byPath.get(key);
return {
scope: "current-thread",
blockingOwner: "unknown",
nativeStatements: "unobserved",
threadId,
observedAtMs: now,
connectionCount: localConnections.length,
readerCount: readers?.size ?? 0,
connections: localConnections.toSorted((left, right) => right.trackedReaders - left.trackedReaders || Number(right.transactionOpen) - Number(left.transactionOpen)).slice(0, 8),
activeReaders: diagnostics(readers?.values() ?? [])
};
}
//#endregion
//#region src/infra/kysely-sync.ts
const nodeVersion = parseNodeReleaseVersion(process.versions.node);
const supportsRepreparedAll = !process.versions.bun && (nodeVersion?.major === 24 && isNodeVersionAtLeast(nodeVersion, {
major: 24,
minor: 20,
patch: 0
}) || isNodeVersionAtLeast(nodeVersion, {
major: 26,
minor: 6,
patch: 0
}));
const compileOnlySqliteDialect = new SqliteDialect({ database: async () => {
throw new Error("getNodeSqliteKysely() returns a compile-only Kysely facade; use executeSqliteQuerySync() to execute node:sqlite queries.");
} });
function getNodeSqliteKysely(db) {
const existing = kyselyByDatabase.get(db);
if (existing) return existing;
const kysely = new Kysely({ dialect: compileOnlySqliteDialect });
kyselyByDatabase.set(db, kysely);
return kysely;
}
function reportNodeSqliteKyselyQueryError(db, error) {
try {
queryErrorHandlerByDatabase.get(db)?.(error);
} catch {}
}
function throwSqliteIteratorCleanupError(error) {
throw toErrorObject(error, "SQLite iterator cleanup failed");
}
/** Execute a compiled Kysely query synchronously against node:sqlite. */
function executeCompiledSqliteQuerySync(db, compiledQuery, firstRowOnly = false, parameters = compiledQuery.parameters) {
try {
const sql = compiledQuery.sql;
installStatementInvalidation(db);
return executeWithCachedStatement(db, sql, parameters, (statement) => {
if (firstRowOnly && SelectQueryNode.is(compiledQuery.query)) {
const row = statement.get(...parameters);
return { rows: row === void 0 ? [] : [row] };
}
if (SelectQueryNode.is(compiledQuery.query) || statement.columns().length > 0) {
if (supportsRepreparedAll) return { rows: statement.all(...parameters) };
const iterator = statement.iterate(...parameters);
const reader = retainSqliteReader(db, "kysely eager query");
let cleanupError;
let failed = false;
let failure;
const rows = [];
try {
for (const row of iterator) {
reader.progress();
rows.push(row);
}
} catch (error) {
failed = true;
failure = error;
}
try {
iterator.return?.();
} catch (error) {
cleanupError = error;
}
reader.release();
if (failed) throw toErrorObject(failure, "SQLite query failed");
if (cleanupError !== void 0) throw toErrorObject(cleanupError, "SQLite query cleanup failed");
return { rows };
}
statement.setReadBigInts(true);
let outcome;
try {
outcome = statement.run(...parameters);
} finally {
statement.setReadBigInts(false);
}
const { changes, lastInsertRowid } = outcome;
const result = {
numAffectedRows: BigInt(changes),
rows: []
};
if (InsertQueryNode.is(compiledQuery.query) && changes > 0) return {
...result,
insertId: BigInt(lastInsertRowid)
};
return result;
});
} catch (error) {
reportNodeSqliteKyselyQueryError(db, error);
throw error;
}
}
/** Compile and execute a Kysely query synchronously. */
function executeSqliteQuerySync(db, query) {
return executeCompiledSqliteQuerySync(db, query.compile());
}
/** Compile fixed SQL and fresh bindings without taking ownership of a native statement. */
function compileSqliteQueryBindings(build) {
const bindings = /* @__PURE__ */ new Map();
const compiled = build((read) => {
const marker = Symbol("sqlite-query-parameter");
bindings.set(marker, read);
return sql`${marker}`;
}).compile();
const readers = compiled.parameters.map((value) => bindings.get(value) ?? (() => value));
return {
compiled,
bind: (params) => readers.map((read) => read(params))
};
}
/** Compile a fixed query once; bind fresh values through the normal sync executor on each call. */
function prepareSqliteQuerySync(db, build) {
const { compiled, bind } = compileSqliteQueryBindings(build);
return (params) => executeCompiledSqliteQuerySync(db, compiled, false, bind(params));
}
/** Compile and lazily iterate a Kysely query synchronously against node:sqlite. */
function iterateSqliteQuerySync(db, query) {
const owner = captureSqliteReaderOwner();
return (function* () {
const compiledQuery = query.compile();
try {
const statement = db.prepare(compiledQuery.sql);
if (!SelectQueryNode.is(compiledQuery.query) && statement.columns().length === 0) return;
const parameters = compiledQuery.parameters;
const iterator = statement.iterate(...parameters);
const reader = retainSqliteReader(db, "kysely iterator", owner);
let cleanupError;
let failed = false;
try {
for (const row of iterator) {
reader.progress();
yield row;
}
} catch (error) {
failed = true;
throw toErrorObject(error, "SQLite iterator failed");
} finally {
try {
iterator.return?.();
} catch (error) {
cleanupError = error;
}
reader.release();
if (!failed && cleanupError !== void 0) throwSqliteIteratorCleanupError(cleanupError);
}
} catch (error) {
reportNodeSqliteKyselyQueryError(db, error);
throw error;
}
})();
}
/** Execute a Kysely query synchronously and return its first row. */
function executeSqliteQueryTakeFirstSync(db, query) {
return executeCompiledSqliteQuerySync(db, query.compile(), true).rows[0];
}
//#endregion
//#region node-sqlite.mjs
function probeSqlite(DatabaseSync) {
const result = {
available: false,
version: null,
text: false,
blob: false,
json: false
};
let database;
try {
database = new DatabaseSync(":memory:");
result.available = true;
const version = database.prepare("SELECT sqlite_version() AS version").get()?.version;
result.version = typeof version === "string" ? version : null;
const text = "a\0b\0";
const bytes = Buffer.from(text, "utf8");
const json = JSON.stringify({ value: text });
database.exec("CREATE TABLE probe (text_value TEXT, blob_value BLOB, json_value TEXT)");
database.prepare("INSERT INTO probe VALUES (?, ?, ?)").run(text, bytes, json);
const row = database.prepare("SELECT text_value, blob_value, json_value FROM probe").get();
result.text = typeof row?.text_value === "string" && row.text_value.length === 4 && Buffer.from(row.text_value, "utf8").equals(bytes);
result.blob = row?.blob_value instanceof Uint8Array && Buffer.from(row.blob_value).equals(bytes);
result.json = row?.json_value === json && JSON.parse(row.json_value).value === text;
} catch (error) {
result.error = error instanceof Error ? error.message : String(error);
} finally {
database?.close();
}
return result;
}
function isSqliteWalResetSafeVersion(value) {
const match = /^(\d+)\.(\d+)\.(\d+)$/u.exec(value.trim());
if (!match) return false;
const [major, minor, patch] = match.slice(1).map(Number);
if (![
major,
minor,
patch
].every(Number.isSafeInteger)) return false;
return major > 3 || major === 3 && (minor > 51 || minor === 51 && patch >= 3 || minor === 50 && patch >= 7 || minor === 44 && patch >= 6);
}
`${probeSqlite.toString()}`;
//#endregion
//#region src/infra/bun-sqlite-library.ts
const WORKER_SELECTION_KEY = "openclaw.bunSqliteLibrarySelection";
function probeLibrary(path) {
const { dlopen, FFIType } = createRequire(import.meta.url)("bun:ffi");
const library = dlopen(path, {
sqlite3_libversion: {
args: [],
returns: FFIType.cstring
},
sqlite3_compileoption_used: {
args: [FFIType.cstring],
returns: FFIType.i32
}
});
try {
return {
version: String(library.symbols.sqlite3_libversion()),
extensionLoadingSupported: library.symbols.sqlite3_compileoption_used(Buffer.from("OMIT_LOAD_EXTENSION\0")) === 0
};
} finally {
library.close();
}
}
function selectLibrary(path) {
const { Database } = createRequire(import.meta.url)("bun:sqlite");
Database.setCustomSQLite(path);
}
function createSelector(deps) {
let selection;
let failure;
return (options = {}) => {
if (failure) throw failure;
if (selection) return selection;
const override = options.explicitPath ?? (deps.env.OPENCLAW_SQLITE_LIBRARY?.trim() || void 0);
if (!deps.isBun || deps.platform !== "darwin") {
selection = override ? {
source: "runtime",
ignoredOverride: "OPENCLAW_SQLITE_LIBRARY requires Bun on macOS"
} : { source: "runtime" };
return selection;
}
const prefix = deps.env.HOMEBREW_PREFIX?.trim();
const candidates = override !== void 0 ? [override] : [.../* @__PURE__ */ new Set([
...prefix ? [posix.join(prefix, "opt/sqlite/lib/libsqlite3.dylib")] : [],
"/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib",
"/usr/local/opt/sqlite/lib/libsqlite3.dylib",
"/opt/local/lib/libsqlite3.dylib"
])];
for (const path of candidates) {
let probe;
try {
try {
probe = deps.probe(path);
} catch (error) {
throw deps.exists(path) ? error : new Error("missing file", { cause: error });
}
if (!isSqliteWalResetSafeVersion(probe.version)) throw new Error(`SQLite version ${probe.version} below the WAL safety floor`);
if (!probe.extensionLoadingSupported) throw new Error("built with SQLITE_OMIT_LOAD_EXTENSION");
} catch (error) {
if (override === void 0) continue;
failure = selectionError(path, error);
throw failure;
}
try {
deps.select(path);
} catch (error) {
failure = selectionError(path, error);
throw failure;
}
selection = {
source: override === void 0 ? "discovered" : "env",
path,
version: probe.version,
extensionLoadingSupported: true
};
return selection;
}
selection = { source: "runtime" };
return selection;
};
}
function selectionError(path, error) {
return new Error(`Cannot use SQLite library ${path}: ${error instanceof Error ? error.message : String(error)}. Fix or unset OPENCLAW_SQLITE_LIBRARY; install a supported library with brew install sqlite.`, { cause: error });
}
function inheritedSelection() {
const value = getEnvironmentData(WORKER_SELECTION_KEY);
if (value === void 0) return;
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
const source = "source" in value ? value.source : void 0;
if (source === "runtime") return { source: "runtime" };
const path = "path" in value ? value.path : void 0;
const version = "version" in value ? value.version : void 0;
const extensionLoadingSupported = "extensionLoadingSupported" in value ? value.extensionLoadingSupported : void 0;
if ((source === "env" || source === "discovered") && typeof path === "string" && typeof version === "string" && extensionLoadingSupported === true) return {
source,
path,
version,
extensionLoadingSupported: true
};
}
throw new Error("Invalid inherited SQLite library selection");
}
function createRuntimeSelector() {
const isBun = Boolean(process.versions.bun);
const sharedLibrary = isBun && process.platform === "darwin";
const inherited = sharedLibrary && !isMainThread ? inheritedSelection() : void 0;
if (inherited) return () => inherited;
const select = createSelector({
isBun,
platform: process.platform,
env: process.env,
exists: existsSync,
probe: probeLibrary,
select: selectLibrary
});
let published = false;
return (options) => {
const selection = select(options);
if (sharedLibrary && isMainThread && !published) {
setEnvironmentData(WORKER_SELECTION_KEY, Object.freeze({ ...selection }));
published = true;
}
return selection;
};
}
/** Select once, before any SQLite open; shared across CLI and bundled SDK module graphs. */
function ensureSqliteLibrarySelected(options) {
const dependencies = options?.internals;
return (dependencies ? dependencies.selector ??= createSelector(dependencies) : resolveGlobalSingleton(Symbol.for("openclaw.bunSqliteLibrarySelection"), createRuntimeSelector))(options);
}
//#endregion
//#region src/infra/sqlite-pinned-read-snapshot.ts
const snapshots = resolveGlobalSingleton(Symbol.for("openclaw.sqlitePinnedReadSnapshots"), () => /* @__PURE__ */ new WeakMap());
function getSqlitePinnedReadSnapshot(db) {
return snapshots.get(db);
}
/** Pin an implicit read snapshot without requiring transaction-control authorization. */
function runSqlitePinnedReadSnapshotSync(db, operation) {
const parent = snapshots.get(db);
snapshots.set(db, parent ?? {});
try {
return executeWithCachedStatement(db, "PRAGMA schema_version", [], (statement) => {
const snapshot = statement.iterate();
try {
const first = snapshot.next();
if (first.done) throw new Error("SQLite schema version query returned no row");
return operation(Number(first.value.schema_version));
} finally {
snapshot.return?.();
}
});
} finally {
if (!parent) snapshots.delete(db);
}
}
//#endregion
//#region src/infra/sqlite-schema-sql.ts
const TABLE_CONSTRAINT_KEYWORDS = /* @__PURE__ */ new Set([
"CHECK",
"FOREIGN",
"PRIMARY",
"UNIQUE"
]);
/** Select canonical DDL without opening a database or changing its installation lifecycle. */
function extractSqliteTableSchema(schema, table, options = {}) {
const start = schema.indexOf(`CREATE TABLE IF NOT EXISTS ${table} (`);
const endMarker = options.endMarker ?? "\n) STRICT;";
const end = schema.indexOf(endMarker, start);
if (start < 0 || end < start) throw new Error(options.errorMessage ?? `Canonical schema markers are missing for ${table}`);
return schema.slice(start, end + (options.includeEndMarker === false ? 0 : endMarker.length));
}
function readTableConstraintKeyword(sql, first) {
let token = first;
if (token.keyword === "CONSTRAINT") {
const name = readSqlToken(sql, token.end);
token = name ? readSqlToken(sql, name.end) : null;
}
return token?.keyword && TABLE_CONSTRAINT_KEYWORDS.has(token.keyword) ? token.keyword : null;
}
function readSqlToken(sql, start) {
let index = start;
while (index < sql.length && /\s/u.test(sql[index] ?? "")) index += 1;
const char = sql[index];
if (!char) return null;
if (char === "\"" || char === "`" || char === "[") {
const end = skipSqlQuoted(sql, index, char);
return {
end,
keyword: null,
raw: sql.slice(index, end)
};
}
let end = index;
while (end < sql.length && !/[\s(,]/u.test(sql[end] ?? "")) end += 1;
const raw = sql.slice(index, end);
return {
end,
keyword: raw.toUpperCase(),
raw
};
}
function normalizeSqlIdentifier(identifier) {
if (identifier.startsWith("\"") && identifier.endsWith("\"")) return identifier.slice(1, -1).replaceAll("\"\"", "\"").toLowerCase();
if (identifier.startsWith("`") && identifier.endsWith("`")) return identifier.slice(1, -1).replaceAll("``", "`").toLowerCase();
if (identifier.startsWith("[") && identifier.endsWith("]")) return identifier.slice(1, -1).toLowerCase();
return identifier.toLowerCase();
}
function normalizeSchemaSql(sql) {
if (sql === null) return null;
return normalizeSqlWhitespace(sql).replace(/;\s*$/u, "").trim().replace(/^(CREATE (?:TABLE|VIRTUAL TABLE|UNIQUE INDEX|INDEX|TRIGGER)) IF NOT EXISTS /iu, "$1 ");
}
function splitSqlList(sql) {
const items = [];
let depth = 0;
let start = 0;
let index = 0;
while (index < sql.length) {
const next = skipSqlQuotedOrComment(sql, index);
if (next !== index) {
index = next;
continue;
}
const char = sql[index];
if (char === "(") depth += 1;
else if (char === ")") depth -= 1;
else if (char === "," && depth === 0) {
items.push(sql.slice(start, index));
start = index + 1;
}
index += 1;
}
items.push(sql.slice(start));
return items;
}
function findSqlCharacter(sql, character) {
let index = 0;
while (index < sql.length) {
const next = skipSqlQuotedOrComment(sql, index);
if (next !== index) {
index = next;
continue;
}
if (sql[index] === character) return index;
index += 1;
}
return -1;
}
function findSqlClosingParenthesis(sql, open) {
let depth = 0;
let index = open;
while (index < sql.length) {
const next = skipSqlQuotedOrComment(sql, index);
if (next !== index) {
index = next;
continue;
}
const char = sql[index];
if (char === "(") depth += 1;
else if (char === ")") {
depth -= 1;
if (depth === 0) return index;
}
index += 1;
}
throw new Error("SQLite schema contains an unterminated table definition.");
}
function normalizeSqlWhitespace(sql) {
let normalized = "";
let pendingSpace = false;
const segments = /[^\s'"`[/-]+|\s+|./gsu;
let segment;
while (segment = segments.exec(sql)) {
const index = segment.index;
const char = segment[0][0] ?? "";
const quoted = skipSqlQuoted(sql, index, char);
if (quoted !== index) {
if (pendingSpace && normalized.length > 0) normalized += " ";
normalized += sql.slice(index, quoted);
pendingSpace = false;
segments.lastIndex = quoted;
continue;
}
const comment = skipSqlComment(sql, index);
if (comment !== index) {
pendingSpace = true;
segments.lastIndex = comment;
continue;
}
if (/\s/u.test(char)) pendingSpace = true;
else {
if (pendingSpace && normalized.length > 0) normalized += " ";
normalized += segment[0];
pendingSpace = false;
}
}
return normalized.trim();
}
function quoteSqliteIdentifier(identifier) {
return `"${identifier.replaceAll("\"", "\"\"")}"`;
}
function skipSqlQuotedOrComment(sql, index) {
const quoted = skipSqlQuoted(sql, index, sql[index] ?? "");
return quoted !== index ? quoted : skipSqlComment(sql, index);
}
function skipSqlQuoted(sql, index, quote) {
if (quote !== "'" && quote !== "\"" && quote !== "`" && quote !== "[") return index;
const closingQuote = quote === "[" ? "]" : quote;
let cursor = index + 1;
while (cursor < sql.length) {
if (sql[cursor] !== closingQuote) {
cursor += 1;
continue;
}
if (quote !== "[" && sql[cursor + 1] === closingQuote) {
cursor += 2;
continue;
}
return cursor + 1;
}
return sql.length;
}
function skipSqlComment(sql, index) {
if (sql.startsWith("--", index)) {
const newline = sql.indexOf("\n", index + 2);
return newline === -1 ? sql.length : newline + 1;
}
if (sql.startsWith("/*", index)) {
const close = sql.indexOf("*/", index + 2);
return close === -1 ? sql.length : close + 2;
}
return index;
}
//#endregion
//#region src/infra/sqlite-worker-identity.ts
/** Native SQLite namespaces are filesystem locators, not distinct database owners. */
function normalizeDatabasePath(location) {
const normalized = process.platform === "win32" ? normalizeWindowsPathPreservingCase(location) : location;
return process.platform === "win32" && !/^(?:[a-z]:[\\/]|[\\/]{2})/iu.test(normalized) ? location : normalized;
}
const useDatabaseBirthtime = process.platform !== "linux";
function databaseFileIdentityKey(file) {
return `${file.dev}:${file.ino}`;
}
function readDatabaseIdentityBirthtime(file) {
return useDatabaseBirthtime ? file.birthtimeNs.toString() : "0";
}
function existingIdentity(file, canonicalFile, canonicalPath) {
if (!file.isFile()) throw new Error("SQLite worker database path must identify a regular file");
if (file.dev !== canonicalFile.dev || file.ino !== canonicalFile.ino || readDatabaseIdentityBirthtime(file) !== readDatabaseIdentityBirthtime(canonicalFile)) throw new Error("SQLite database pathname changed during admission");
return {
key: `file:${databaseFileIdentityKey(file)}`,
canonicalPath: normalizeDatabasePath(canonicalPath),
birthtime: readDatabaseIdentityBirthtime(file)
};
}
/** In-process owner key only; never rewrite a configured or persisted database locator. */
function resolveDatabasePathKey(databasePath) {
const missing = [];
let ancestor = path.resolve(databasePath);
while (true) try {
return normalizeDatabasePath(path.join(realpathSync.native(ancestor), ...missing));
} catch (error) {
if (!hasErrnoCode(error, "ENOENT")) throw error;
missing.unshift(path.basename(ancestor));
const parent = path.dirname(ancestor);
if (parent === ancestor) throw error;
ancestor = parent;
}
}
/** Inspect a native-owner path without replacing its diagnostic for a non-file target. */
function inspectDatabasePathIdentitySync(databasePath) {
const resolvedPath = path.resolve(databasePath);
let file;
try {
file = statSync(resolvedPath, { bigint: true });
} catch (error) {
if (!hasErrnoCode(error, "ENOENT")) throw error;
}
if (file) {
if (!file.isFile()) return;
const canonicalPath = resolveDatabasePathKey(resolvedPath);
return existingIdentity(file, statSync(canonicalPath, { bigint: true }), canonicalPath);
}
const canonicalPath = resolveDatabasePathKey(resolvedPath);
return {
key: `path:${canonicalPath}`,
canonicalPath
};
}
/** Capture identity before yielding; worker admission requires a regular file or absent path. */
function readDatabasePathIdentitySync(databasePath) {
const identity = inspectDatabasePathIdentitySync(databasePath);
if (!identity) throw new Error("SQLite worker database path must identify a regular file");
return identity;
}
/** Inspect retained aliases only while binding a newly observed database path. */
function findChangedDatabasePaths(paths, observed) {
return [...paths].filter((pathname) => inspectDatabasePathIdentitySync(pathname)?.key !== observed.key);
}
async function readDatabasePathIdentity(databasePath) {
const file = await stat(databasePath, { bigint: true }).catch((error) => {
if (hasErrnoCode(error, "ENOENT")) return;
throw error;
});
if (file) try {
const canonicalPath = await realpath(databasePath);
return existingIdentity(file, await stat(canonicalPath, { bigint: true }), canonicalPath);
} catch (error) {
if (hasErrnoCode(error, "ENOENT")) throw new Error("SQLite database pathname changed during admission", { cause: error });
throw error;
}
const missing = [];
let ancestor = databasePath;
while (true) try {
const canonicalPath = normalizeDatabasePath(path.join(await realpath(ancestor), ...missing));
return {
key: `path:${canonicalPath}`,
canonicalPath
};
} catch (error) {
if (!hasErrnoCode(error, "ENOENT")) throw error;
missing.unshift(path.basename(ancestor));
const parent = path.dirname(ancestor);
if (parent === ancestor) throw error;
ancestor = parent;
}
}
function assertExistingDatabaseIdentity(databasePath, expected, expectedBirthtime) {
const file = statSync(databasePath, { bigint: true });
if (!file.isFile() || `file:${file.dev}:${file.ino}` !== expected || expectedBirthtime !== void 0 && readDatabaseIdentityBirthtime(file) !== expectedBirthtime) throw new Error("SQLite database file identity changed before existing-only open");
}
//#endregion
//#region src/infra/sqlite-schema-facts.ts
const scopes = resolveGlobalSingleton(Symbol.for("openclaw.sqliteSchemaScopes"), () => {
const byIdentity = /* @__PURE__ */ new Map();
const release = (scope) => {
scope.users -= 1;
if (scope.users === 0 && scope.key && byIdentity.get(scope.key) === scope) byIdentity.delete(scope.key);
};
return {
byIdentity,
release,
finalizer: new FinalizationRegistry(release)
};
});
const owners$1 = resolveGlobalSingleton(Symbol.for("openclaw.sqliteSchemaFacts"), () => /* @__PURE__ */ new WeakMap());
function invalidate(owner) {
owner.revision += 1;
owner.facts = void 0;
}
function bindScope(database, owner) {
if (owner.scope) return owner.scope;
const location = database.location();
const key = location ? readDatabasePathIdentitySync(location).key : void 0;
const scope = key && scopes.byIdentity.get(key) || {
key,
revision: 0,
users: 0
};
if (key) scopes.byIdentity.set(key, scope);
scope.users += 1;
owner.scope = scope;
owner.scopeRevision = scope.revision;
scopes.finalizer.register(database, scope, owner);
return scope;
}
function publishSchemaChange(database, owner) {
const scope = bindScope(database, owner);
scope.revision += 1;
owner.scopeRevision = scope.revision;
}
/** Schema publications outside DDL (such as a deferred version marker) share this revision. */
function invalidateSqliteSchemaFacts(database) {
const owner = owners$1.get(database);
if (owner) {
bindScope(database, owner);
invalidate(owner);
owner.transactionalSchema ||= database.isTransaction;
if (!database.isTransaction) publishSchemaChange(database, owner);
}
}
function changesSchema(sql) {
return /\b(?:CREATE|ALTER|DROP|REINDEX|VACUUM)\b|\bPRAGMA\b[\s\S]*\b(?:user_version|schema_version|writable_schema)\b[\s\S]*[=(]/i.test(sql);
}
const transactionControlPrefix = /^(?:\s|;|--[^\n]*(?:\n|$)|\/\*(?:[^*]|\*(?!\/))*\*\/)*(BEGIN|SAVEPOINT|COMMIT|END|RELEASE|ROLLBACK)\b/i;
function batchTransactionControl(sql) {
let control;
let remaining = sql;
while (remaining) {
const next = transactionControlPrefix.exec(remaining)?.[1]?.toUpperCase();
if (next === "ROLLBACK") return next;
control ||= next;
const end = remaining.includes(";") ? findSqlCharacter(remaining, ";") : -1;
if (end < 0) break;
remaining = remaining.slice(end + 1);
}
return control;
}
function callStatement(method, [first, ...remaining]) {
if (first === void 0) return method();
if (typeof first === "object" && first !== null && !ArrayBuffer.isView(first)) return method(first, ...remaining);
return method(first, ...remaining);
}
function trackSchemaChanges(database, owner, native) {
const settle = (boundary = false) => {
if ((boundary || !database.isTransaction) && (owner.transactionalSchema || owner.transactionalFacts)) {
if (owner.transactionalSchema) publishSchemaChange(database, owner);
invalidate(owner);
owner.transactionalSchema &&= database.isTransaction;
owner.transactionalFacts = false;
}
};
const execute = (operation, schemaChange, control) => {
if (owner.transactionalSchema && !owner.scope) bindScope(database, owner);
settle();
const invalidates = schemaChange || control === "ROLLBACK" && owner.transactionalSchema;
if (invalidates) invalidateSqliteSchemaFacts(database);
try {
return operation();
} finally {
if (invalidates) invalidateSqliteSchemaFacts(database);
settle(Boolean(control));
}
};
database.exec = (sql) => execute(() => native.DatabaseSync.prototype.exec.call(database, sql), changesSchema(sql), batchTransactionControl(sql));
database.prepare = (...prepareArgs) => {
const [sql] = prepareArgs;
const statement = native.DatabaseSync.prototype.prepare.call(database, ...prepareArgs);
const schemaChange = changesSchema(sql);
const control = transactionControlPrefix.exec(sql)?.[1]?.toUpperCase();
if (schemaChange || control) {
const run = Object.hasOwn(statement, "run") ? statement.run.bind(statement) : void 0;
const get = Object.hasOwn(statement, "get") ? statement.get.bind(statement) : void 0;
const all = Object.hasOwn(statement, "all") ? statement.all.bind(statement) : void 0;
const iterate = Object.hasOwn(statement, "iterate") ? statement.iterate.bind(statement) : void 0;
statement.run = (...bindings) => execute(() => callStatement(run ?? native.StatementSync.prototype.run.bind(statement), bindings), schemaChange, control);
statement.get = (...bindings) => execute(() => callStatement(get ?? native.StatementSync.prototype.get.bind(statement), bindings), schemaChange, control);
statement.all = (...bindings) => execute(() => callStatement(all ?? native.StatementSync.prototype.all.bind(statement), bindings), schemaChange, control);
statement.iterate = function* (...bindings) {
if (owner.transactionalSchema && !owner.scope) bindScope(database, owner);
settle();
const invalidates = schemaChange || control === "ROLLBACK" && owner.transactionalSchema;
if (invalidates) invalidateSqliteSchemaFacts(database);
try {
yield* callStatement(iterate ?? native.StatementSync.prototype.iterate.bind(statement), bindings);
} finally {
if (invalidates) invalidateSqliteSchemaFacts(database);
settle(Boolean(control));
}
};
}
return statement;
};
if (typeof database.setAuthorizer === "function") database.setAuthorizer = (callback) => {
native.DatabaseSync.prototype.setAuthorizer.call(database, callback);
owner.authorizerActive = callback !== null;
invalidate(owner);
};
registerNodeSqliteDisposeCallback(database, () => {
invalidate(owner);
owner.dataVersion = void 0;
owner.readDataVersion = void 0;
if (owner.scope) {
scopes.finalizer.unregister(owner);
scopes.release(owner.scope);
owner.scope = void 0;
owner.scopeRevision = void 0;
}
});
}
/** Share freshness only within this synchronous call stack, never across an await. */
function runSqliteReadOperationSync(database, operation) {
const owner = owners$1.get(database);
if (!owner?.admitted || owner.authorizerActive) return operation();
owner.readDepth += 1;
try {
return operation();
} finally {
owner.readDepth -= 1;
if (owner.readDepth === 0) owner.readDataVersion = void 0;
}
}
/** Foreign commits are observed on the next operation; SQLite owns snapshot visibility. */
function readSqliteCacheDataVersion(database) {
const tracked = owners$1.get(database);
const owner = tracked?.admitted ? tracked : void 0;
if (owner && !owner.authorizerActive && owner.readDataVersion !== void 0) return owner.readDataVersion;
const row = executeWithCachedStatement(database, "PRAGMA data_version", [], (statement) => statement.get());
if (typeof row?.data_version !== "number") throw new Error("SQLite did not return a numeric PRAGMA data_version");
if (owner) {
if (owner.dataVersion !== row.data_version) {
const facts = owner.facts;
if (!(facts && runSqlitePinnedReadSnapshotSync(database, (schemaVersion) => {
const userVersion = executeWithCachedStatement(database, "PRAGMA user_version", [], (s) => s.get());
return facts.schemaVersion === schemaVersion && facts.userVersion === userVersion?.user_version;
}))) invalidate(owner);
owner.dataVersion = row.data_version;
}
if (owner.readDepth > 0 && !owner.authorizerActive) owner.readDataVersion = row.data_version;
}
return row.data_version;
}
/** Install at native open, before callers can retain statements or install an authorizer. */
function trackSqliteSchema(database, native) {
if (!owners$1.has(database)) {
const owner = {
admitted: false,
revision: 0,
readDepth: 0,
transactionalSchema: false,
transactionalFacts: false,
authorizerActive: false
};
owners$1.set(database, owner);
trackSchemaChanges(database, owner, native);
}
}
/** Only database admission opts a connection into retained schema facts. */
function admitSqliteSchema(database) {
const owner = owners$1.get(database);
if (!owner) throw new Error("SQLite schema admission requires a connection tracked from native open");
owner.admitted = true;
getAdmittedSqliteSchemaFacts(database);
}
/** Schema changes revoke the admission; ordinary reads consume its recorded facts. */
function getAdmittedSqliteSchemaFacts(database) {
const owner = owners$1.get(database);
if (!owner?.admitted || owner.authorizerActive) return;
readSqliteCacheDataVersion(database);
const snapshot = getSqlitePinnedReadSnapshot(database);
if (owner.snapshot && owner.snapshot !== snapshot) {
invalidate(owner);
owner.snapshot = void 0;
}
const scope = bindScope(database, owner);
if (owner.scopeRevision !== scope.revision) {
invalidate(owner);
owner.scopeRevision = scope.revision;
}
if ((owner.transactionalSchema || owner.transactionalFacts) && !database.isTransaction) {
if (owner.transactionalSchema) publishSchemaChange(database, owner);
invalidate(owner);
owner.transactionalSchema = false;
owner.transactionalFacts = false;
}
if (!owner.facts) {
owner.snapshot = snapshot;
owner.transactionalFacts = database.isTransaction;
owner.facts = runSqlitePinnedReadSnapshotSync(database, (schemaVersion) => {
const userVersion = executeWithCachedStatement(database, "PRAGMA user_version", [], (s) => s.get());
const tables = executeWithCachedStatement(database, "SELECT name, sql FROM main.sqlite_schema WHERE type = 'table'", [], (s) => s.all());
return {
revision: owner.revision,
userVersion: Number(userVersion?.user_version ?? 0),
schemaVersion,
tables: new Set(tables.flatMap((row) => typeof row.name === "string" ? [row.name] : [])),
tableSql: new Map(tables.flatMap((row) => typeof row.name === "string" ? [[row.name, typeof row.sql === "string" ? row.sql : null]] : []))
};
});
}
return owner.facts;
}
//#endregion
//#region src/infra/warning-filter.ts
const warningFilterKey = Symbol.for("openclaw.warning-filter");
function shouldIgnoreWarning(warning) {
return Boolean(warning.code === "DEP0040" && warning.message?.includes("punycode") || warning.code === "DEP0060" && warning.message?.includes("util._extend") || warning.name === "ExperimentalWarning" && warning.message?.includes("SQLite is an experimental feature"));
}
function normalizeWarningArgs(args) {
const warningArg = args[0];
const secondArg = args[1];
const thirdArg = args[2];
let name;
let code;
let message;
if (warningArg instanceof Error) {
name = warningArg.name;
message = warningArg.message;
code = warningArg.code;
} else if (typeof warningArg === "string") message = warningArg;
if (secondArg && typeof secondArg === "object" && !Array.isArray(secondArg)) {
const options = secondArg;
if (typeof options.type === "string") name = options.type;
if (typeof options.code === "string") code = options.code;
} else {
if (typeof secondArg === "string") name = secondArg;
if (typeof thirdArg === "string") code = thirdArg;
}
return {
name,
code,
message
};
}
/** Installs the global process warning filter once for the current JS realm. */
function installProcessWarningFilter() {
const state = resolveGlobalSingleton(warningFilterKey, () => ({ installed: false }));
if (state.installed) return;
const originalEmitWarning = process.emitWarning.bind(process);
const wrappedEmitWarning = ((...args) => {
if (shouldIgnoreWarning(normalizeWarningArgs(args))) return;
if (args[0] instanceof Error && args[1] && typeof args[1] === "object" && !Array.isArray(args[1])) {
const warning = args[0];
const emitted = Object.assign(new Error(warning.message), {
name: warning.name,
code: warning.code
});
process.emit("warning", emitted);
return;
}
Reflect.apply(originalEmitWarning, process, args);
});
process.emitWarning = wrappedEmitWarning;
state.installed = true;
}
//#endregion
//#region src/infra/node-sqlite.ts
const require$1 = createRequire(import.meta.url);
let validatedSqliteModule;
function resolveSqliteFilesystemPath(pathname) {
if (process.platform !== "win32") return pathname;
return path.toNamespacedPath(path.resolve(pathname));
}
function resolveNodeSqliteLocation(location) {
if (location === "" || location === ":memory:" || location.startsWith("file:")) return location;
return resolveSqliteFilesystemPath(location);
}
/** Preserve native Windows path prefixes before adding SQLite URI parameters. */
function resolveSqliteFileUriPath(pathname, platform) {
if (platform === "win32") {
const namespacedPath = path.win32.toNamespacedPath(path.win32.resolve(pathname));
return `file:${encodeURIComponent(namespacedPath)}`;
}
return pathToFileURL(path.resolve(pathname)).href;
}
/** Open an existing writable database without SQLite's create-if-missing flag. */
function resolveExistingSqliteFileUri(pathname, platform = process.platform) {
return `${resolveSqliteFileUriPath(pathname, platform)}?mode=rw`;
}
function assertSqliteWalResetSafeVersion(version, nodeVersion) {
if (isSqliteWalResetSafeVersion(version)) return;
const variables = process.config?.variables;
const isShared = variables?.node_shared_sqlite === true || variables?.node_shared_sqlite === "true";
throw new Error(`OpenClaw requires SQLite 3.51.3+, 3.50.7+ within 3.50.x, or 3.44.6+ within 3.44.x for WAL safety; Node ${nodeVersion} ${isShared ? "uses shared system" : "embeds"} SQLite ${version}, which is affected by the upstream WAL-reset database corruption bug. ${isShared ? "Upgrade the system SQLite library to one of those safe versions, or use a Node build embedding a safe version." : "Upgrade to Node 24.16.0+ or 26.1.0+ before retrying."}`);
}
function assertSafeSqliteRuntime(sqlite) {
if (validatedSqliteModule === sqlite) return;
const database = new sqlite.DatabaseSync(":memory:");
try {
const row = database.prepare("SELECT sqlite_version() AS version").get();
const version = typeof row?.version === "string" ? row.version : "unknown";
assertSqliteWalResetSafeVersion(version, process.versions.node);
compareValidSemver(version, "3.45.0");
database.prepare("SELECT sqlite_compileoption_used('OMIT_LOAD_EXTENSION') AS omitted").get()?.omitted;
validatedSqliteModule = sqlite;
} finally {
database.close();
}
}
/** Load node:sqlite after installing the process warning filter. */
function requireNodeSqlite() {
installProcessWarningFilter();
try {
ensureSqliteLibrarySelected();
const sqlite = require$1("node:sqlite");
assertSafeSqliteRuntime(sqlite);
return sqlite;
} catch (err) {
const message = formatErrorMessage(err);
throw new Error(`SQLite support is unavailable or unsafe in this Node runtime. ${message}`, { cause: err });
}
}
/** Open node:sqlite through OpenClaw's runtime and filesystem-location boundary. */
function openNodeSqliteDatabase(location, options) {
const sqlite = requireNodeSqlite();
const resolvedLocation = resolveNodeSqliteLocation(location);
const database = options === void 0 ? new sqlite.DatabaseSync(resolvedLocation) : new sqlite.DatabaseSync(resolvedLocation, options);
trackSqliteSchema(database, sqlite);
registerSqliteReaderConnection(database);
return database;
}
//#endregion
//#region src/infra/sqlite-error-diagnostics.ts
const inspectionOperations = resolveGlobalSingleton(Symbol.for("openclaw.sqliteInspectionOperations"), () => /* @__PURE__ */ new WeakMap());
const nativeOpenFailures = resolveGlobalSingleton(Symbol.for("openclaw.sqliteNativeOpenFailures"), () => /* @__PURE__ */ new WeakSet());
function markSqliteNativeOpenFailure(error) {
if (error !== null && typeof error === "object") nativeOpenFailures.add(error);
}
/** Record the native effect without changing its error or tagging surrounding authority checks. */
function withSqliteNativeOpen(open) {
try {
return open();
} catch (error) {
markSqliteNativeOpenFailure(error);
throw error;
}
}
function markSqliteInspectionOperation(error, operation) {
if (error !== null && typeof error === "object" && !inspectionOperations.has(error)) inspectionOperations.set(error, operation);
return error;
}
const SQLITE_LOCK_ERROR_CODES = /* @__PURE__ */ new Set(["SQLITE_BUSY", "SQLITE_LOCKED"]);
const SQLITE_BUSY_RESULT_CODE = 5;
const SQLITE_LOCKED_RESULT_CODE = 6;
const SQLITE_CORRUPT_RESULT_CODE = 11;
const SQLITE_NOTADB_RESULT_CODE = 26;
const SQLITE_PRIMARY_RESULT_CODE_MASK = 255;
function sqliteErrorCode(error) {
const code = asOptionalObjectRecord(error)?.code;
return typeof code === "string" ? code : void 0;
}
function sqliteExtendedResultCode(error) {
const errcode = asOptionalObjectRecord(error)?.errcode;
return typeof errcode === "number" && Number.isInteger(errcode) ? errcode : void 0;
}
function sqlitePrimaryResultCode(error) {
const errcode = sqliteExtendedResultCode(error);
return errcode === void 0 ? void 0 : errcode & SQLITE_PRIMARY_RESULT_CODE_MASK;
}
function isSqliteLockError(error) {
const code = sqliteErrorCode(error);
if (code !== void 0 && SQLITE_LOCK_ERROR_CODES.has(code)) return true;
const primaryCode = sqlitePrimaryResultCode(error);
return primaryCode === SQLITE_BUSY_RESULT_CODE || primaryCode === SQLITE_LOCKED_RESULT_CODE;
}
/** Report proven file damage (corrupt page or non-database header), not transient failure. */
function isSqliteCorruptionError(error) {
const primaryCode = sqlitePrimaryResultCode(error);
return primaryCode === SQLITE_CORRUPT_RESULT_CODE || primaryCode === SQLITE_NOTADB_RESULT_CODE;
}
//#endregion
//#region src/infra/sqlite-staging-token.ts
const SQLITE_STAGING_TOKEN_FILES = [
"owner.sqlite",
"owner.sqlite-journal",
"owner.sqlite-wal",
"owner.sqlite-shm"
];
var SqliteStagingRetiredError = class extends Error {
constructor() {
super("SQLite snapshot parent retired; aborting snapshot allocation");
}
};
/** Native transactions fence private staging admission and committed retirement. */
function acquireSqliteStagingToken(directory, mode, options = {}) {
const location = path.join(directory, SQLITE_STAGING_TOKEN_FILES[0]);
const readIdentity = (pathname, kind) => {
const stat = fs$1.lstatSync(pathname, { bigint: true });
if (!(kind === "directory" ? stat.isDirectory() : stat.isFile()) || process.platform === "win32" && (stat.dev === 0n || stat.ino === 0n)) throw new Error("SQLite staging ownership is unknown");
return stat;
};
const directoryIdentity = readIdentity(directory, "directory");
const family = SQLITE_STAGING_TOKEN_FILES.map((file) => fs$1.lstatSync(path.join(directory, file), { throwIfNoEntry: false }));
const existing = family[0];
if (family.some((file) => file && (!file.isFile() || process.getuid && file.uid !== process.getuid())) || !existing && mode !== "create" && !options.allowMissing) throw new Error("SQLite snapshot token ownership is unknown");
const existingIdentity = existing ? readIdentity(location, "file") : void 0;
const db = withSqliteNativeOpen(() => openNodeSqliteDatabase(existing ? resolveExistingSqliteFileUri(location) : location));
let tokenIdentity;
const kysely = getNodeSqliteKysely(db);
const execute = (statement) => executeSqliteQueryTakeFirstSync(db, { compile: () => statement.compile(kysely) });
let exclusive = mode === "reclaim";
let retired = false;
const assertIdentity = () => {
const currentDirectory = readIdentity(directory, "directory");
if (directoryIdentity.dev !== currentDirectory.dev || directoryIdentity.ino !== currentDirectory.ino) throw new Error("SQLite staging ownership changed before retirement");
const currentToken = readIdentity(location, "file");
if (tokenIdentity.dev !== currentToken.dev || tokenIdentity.ino !== currentToken.ino) throw new Error("SQLite staging ownership changed before retirement");
};
const readVersion = () => {
const row = execute(sql`PRAGMA user_version`);
return isRecord(row) ? row.user_version : void 0;
};
const beginRetirement = () => {
assertIdentity();
if (!db.isOpen) return acquireSqliteStagingToken(directory, "reclaim");
if (!db.isTransaction || !exclusive) {
if (db.isTransaction) execute(sql`ROLLBACK`);
execute(sql`BEGIN EXCLUSIVE`);
exclusive = true;
}
assertIdentity();
const version = readVersion();
if (version !== (retired ? 1 : 0)) {
retired = version === 1;
throw new SqliteStagingRetiredError();
}
return token;
};
const release = (retiring = false) => {
if (!db.isOpen) return;
if (retiring) {
beginRetirement();
if (!retired) execute(sql`PRAGMA user_version=1`);
execute(sql`COMMIT`);
retired = true;
} else if (db.isTransaction) execute(sql`ROLLBACK`);
db.close();
};
const token = Object.assign(release, { beginRetirement });
try {
tokenIdentity = existingIdentity ?? readIdentity(location, "file");
execute(sql`PRAGMA busy_timeout=0`);
if (mode === "create") execute(sql`BEGIN IMMEDIATE`);
else if (mode === "reclaim") execute(sql`BEGIN EXCLUSIVE`);
else {
execute(sql`BEGIN`);
execute(sql`SELECT rootpage FROM sqlite_schema LIMIT 1`);
}
const journalMode = execute(sql`PRAGMA journal_mode`);
if (!isRecord(journalMode) || journalMode.journal_mode !== "delete") throw new Error("SQLite snapshot token journal mode is unknown");
const version = readVersion();
if (version !== 0 && (mode !== "reclaim" || version !== 1)) throw new SqliteStagingRetiredError();
retired = version === 1;
assertIdentity();
return token;
} catch (error) {
release();
throw error;
}
}
//#endregion
//#region src/infra/sqlite-snapshot-retirement.ts
const SQLITE_SNAPSHOT_PREFIX = "openclaw-sqlite-readonly-v2-";
const suffix = "(?:[A-Za-z0-9]{6}|[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12})$";
const SQLITE_SNAPSHOT_LEGACY_MARKER = new RegExp(`^openclaw-sqlite-readonly-[1-9]\\d*-${suffix}`, "u");
const tokenMarker = new RegExp(`^${SQLITE_SNAPSHOT_PREFIX}${suffix}`, "u");
const SQLITE_SNAPSHOT_LEGACY_AGE_MS = 864e5;
const pendingTokenReleases = /* @__PURE__ */ new Map();
function releaseTokens(directory, tokens) {
const pending = pendingTokenReleases.get(directory) ?? /* @__PURE__ */ new Set();
const failures = [];
for (const token of tokens) try {
token();
pending.delete(token);
} catch (error) {
pending.add(token);
failures.push(error);
}
if (pending.size) pendingTokenReleases.set(directory, pending);
else pendingTokenReleases.delete(directory);
if (failures.length) throw new AggregateError(failures, "SQLite snapshot retirement token release failed");
}
/** A failed native close retains its actual handle until retry, even after commit or unlink. */
function drainPendingSqliteSnapshotTokens(directory) {
releaseTokens(directory, pendingTokenReleases.get(directory) ?? []);
}
const isSqliteSnapshotStagingName = (name) => SQLITE_SNAPSHOT_LEGACY_MARKER.test(name) || tokenMarker.test(name);
function acquireSqliteSnapshotToken(directory, mode) {
return acquireSqliteStagingToken(directory, mode, { allowMissing: SQLITE_SNAPSHOT_LEGACY_MARKER.test(path.basename(directory)) });
}
/** Hold every nested lifetime before selecting payload; a parent lock alone cannot fence children. */
function beginSqliteSnapshotRetirement(directory, options = {}) {
drainPendingSqliteSnapshotTokens(directory);
const tokens = [];
const payload = [];
const release = () => releaseTokens(directory, tokens.filter((token) => token !== options.token));
if (!fs$1.existsSync(directory)) {
options.token?.();
return {
bytes: 0,
payload,
release,
retire: () => {}
};
}
function inspect(current, inheritedCutoff, layout = "", lock = true, parentFenced = false) {
const stat = fs$1.lstatSync(current);
if (!stat.isDirectory() || process.getuid && stat.uid !== process.getuid()) throw new Error("Snapshot directory ownership is unknown");
const legacy = !layout && SQLITE_SNAPSHOT_LEGACY_MARKER.test(path.basename(current));
const cutoff = options.cutoff !== void 0 && legacy ? Math.min(inheritedCutoff, Date.now() - SQLITE_SNAPSHOT_LEGACY_AGE_MS) : inheritedCutoff;
if (legacy && lock && options.cutoff !== void 0) inspect(current, cutoff, layout, false);
if (!layout && lock) {
const unfinishedAllocation = parentFenced && tokenMarker.test(path.basename(current)) && fs$1.readdirSync(current).length === 0;
tokens.push(current === directory && options.token ? options.token.beginRetirement() : unfinishedAllocation ? acquireSqliteStagingToken(current, "reclaim", { allowMissing: true }) : acquireSqliteSnapshotToken(current, "reclaim"));
}
let bytes = 0;
let newest = stat.mtimeMs;
for (const entry of fs$1.readdirSync(current, { withFileTypes: true })) {
const location = path.join(current, entry.name);
const item = fs$1.lstatSync(location);
if (process.getuid && item.uid !== process.getuid()) throw new Error("Snapshot file ownership is unknown");
if ((!layout || options.cutoff === void 0) && item.isFile() && SQLITE_STAGING_TOKEN_FILES.some((file) => file === entry.name)) continue;
newest = Math.max(newest, item.mtimeMs);
if (item.isDirectory()) {
const nested = isSqliteSnapshotStagingName(entry.name);
const childLayout = nested ? "" : [layout, entry.name].filter(Boolean).join("/");
if (options.cutoff !== void 0 && (!nested && ![
"openclaw",
"openclaw-state",
"openclaw-state/state"
].includes(childLayout) || nested && layout !== "" && layout !== "openclaw")) throw new Error("Unrecognized snapshot directory");
const allocationParent = layout === "openclaw" ? path.dirname(current) : current;
const child = inspect(location, cutoff, childLayout, lock, nested && (layout === "" || layout === "openclaw") && isSqliteSnapshotStagingName(path.basename(allocationParent)));
bytes += child.bytes;
newest = Math.max(newest, child.newest);
} else if (options.cutoff === void 0 || item.isFile() && (layout === "openclaw-state/state" ? /^openclaw\.sqlite(?:-wal|-shm|-journal)?$/u.test(entry.name) : !layout && /^(?:first|database\.sqlite(?:\.partial)?(?:-wal|-shm|-journal)?)$/u.test(entry.name))) {
bytes += item.size;
if (lock) payload.push(location);
} else throw new Error("Unrecognized snapshot artifact");
}
if (newest >= cutoff) throw new Error(legacy ? "Legacy snapshot contains activity newer than 24 hours" : "Snapshot contains activity within the reclamation grace period");
return {
bytes,
newest
};
}
try {
const { bytes } = inspect(directory, options.cutoff ?? Number.POSITIVE_INFINITY);
return {
bytes,
payload,
release,
retire: () => {
for (const token of tokens) token(true);
}
};
} catch (error) {
release();
throw error;
}
}
//#endregion
//#region src/infra/sqlite-readonly-location-cleanup.ts
var SqliteSnapshotCleanupError = class extends Error {};
const pendingTempDirectoryCleanup = /* @__PURE__ */ new Map();
let cleanupExitHandlerInstalled = false;
const activeSnapshotWork = /* @__PURE__ */ new Map();
let pendingSignalCleanup;
function snapshotDirectory(directory) {
let owner = pendingTempDirectoryCleanup.get(directory);
if (!owner) {
owner = { readers: /* @__PURE__ */ new Set() };
pendingTempDirectoryCleanup.set(directory, owner);
}
return owner;
}
function cleanupSnapshotOperations() {
pendingSignalCleanup ??= (async () => {
const directories = pendingTempDirectoryCleanup.keys();
while (true) {
while (activeSnapshotWork.size > 0) {
for (const stop of activeSnapshotWork.values()) stop();
await Promise.allSettled(activeSnapshotWork.keys());
}
const next = directories.next();
if (next.done) break;
const directory = next.value;
await removeTempDirectoryAsync(directory, (error) => emitSnapshotCleanupFailure({
cleanupRoot: directory,
operation: "rm",
code: extractErrorCode(error)
}));
}
})().finally(() => {
pendingSignalCleanup = void 0;
});
return pendingSignalCleanup;
}
/** Join native backup work or a terminated child before removing its private bytes. */
function retainSnapshotWork(work, stop = () => {}) {
registerSignalExitFinalizer(cleanupSnapshotOperations);
activeSnapshotWork.set(work, stop);
const release = () => activeSnapshotWork.delete(work);
work.then(release, release);
return work;
}
function registerSnapshotTempDirectory(directory, release) {
const owner = snapshotDirectory(directory);
owner.release = release ?? owner.release;
if (!cleanupExitHandlerInstalled) {
cleanupExitHandlerInstalled = true;
process.once("exit", () => {
for (const stop of activeSnapshotWork.values()) stop();
if (activeSnapshotWork.size === 0) for (const pendingDir of pendingTempDirectoryCleanup.keys()) removeTempDirectory(pendingDir);
});
}
registerSignalExitFinalizer(cleanupSnapshotOperations);
}
const tempDirectoryRemovalOptions = {
force: true,
maxRetries: 3,
recursive: true,
retryDelay: 20
};
function emitSnapshotCleanupFailure(report, onCleanupFailure) {
if (onCleanupFailure) try {
onCleanupFailure(report);
return;
} catch {}
try {
getChildLogger({ subsystem: "infra/sqlite-snapshot" }).warn({
path: report.cleanupRoot,
operation: report.operation,
errorCode: report.code
}, "SQLite read-only snapshot cleanup failed. Check directory permissions and available storage before retrying.");
} catch {}
}
function assertSnapshotReadersRetired(owner) {
if (owner?.readers.size) throw new SqliteSnapshotCleanupError("SQLite snapshot still belongs to an active reader");
}
function prepareSnapshotRetirement(directory) {
drainPendingSqliteSnapshotTokens(directory);
const owner = pendingTempDirectoryCleanup.get(directory);
assertSnapshotReadersRetired(owner);
if (owner?.releaseAsync) throw new SqliteSnapshotCleanupError("SQLite snapshot requires asynchronous cleanup");
if (owner?.retired || !owner?.release && !fs$1.existsSync(path.join(directory, SQLITE_STAGING_TOKEN_FILES[0]))) return;
if (owner) owner.retirementStarted = true;
return beginSqliteSnapshotRetirement(directory, { token: owner?.release });
}
/** Delete in the token process: owner death must stop unlinking when its locks disappear. */
function retireSqliteSnapshotPayload(retirement) {
for (const file of retirement.payload) fs$1.rmSync(file, tempDirectoryRemovalOptions);
retirement.retire();
}
function removeTempDirectory(tempDir, onFailure) {
try {
const retirement = prepareSnapshotRetirement(tempDir);
try {
if (retirement) {
retireSqliteSnapshotPayload(retirement);
const owner = snapshotDirectory(tempDir);
owner.release = void 0;
owner.retired = true;
}
fs$1.rmSync(tempDir, tempDirectoryRemovalOptions);
} finally {
retirement?.release();
}
pendingTempDirectoryCleanup.delete(tempDir);
return true;
} catch (error) {
onFailure?.(error);
registerSnapshotTempDirectory(tempDir);
return false;
}
}
async function removeTempDirectoryAsync(tempDir, onFailure) {
try {
const owner = pendingTempDirectoryCleanup.get(tempDir);
assertSnapshotReadersRetired(owner);
if (owner?.releaseAsync) {
owner.retirementStarted = true;
await (owner.retiring ??= owner.releaseAsync().then(() => {
owner.releaseAsync = void 0;
owner.retired = true;
}).finally(() => {
owner.retiring = void 0;
}));
}
const retirement = prepareSnapshotRetirement(tempDir);
try {
for (const file of retirement?.payload ?? []) await retainSnapshotWork(fs$1.promises.rm(file, tempDirectoryRemovalOptions));
if (retirement) {
retirement.retire();
const current = snapshotDirectory(tempDir);
current.release = void 0;
current.retired = true;
}
await retainSnapshotWork(fs$1.promises.rm(tempDir, tempDirectoryRemovalOptions));
} finally {
retirement?.release();
}
pendingTempDirectoryCleanup.delete(tempDir);
return true;
} catch (error) {
onFailure?.(error);
registerSnapshotTempDirectory(tempDir);
return false;
}
}
function adoptPreparedLocation(location, ownedRoot, requireCleanup = false, onCleanupFailure) {
const tempDir = ownedRoot ?? path.dirname(location);
registerSnapshotTempDirectory(tempDir);
let active = true;
let pending;
let reported = false;
const reportFailure = (error) => {
if (!requireCleanup && !reported) {
reported = true;
emitSnapshotCleanupFailure({
cleanupRoot: tempDir,
operation: "rm",
code: extractErrorCode(error)
}, onCleanupFailure);
}
};
const complete = (removed) => {
if (removed) active = false;
else if (requireCleanup) throw new Error(`SQLite read-only worker snapshot cleanup failed: ${tempDir}`);
return removed;
};
return {
location,
cleanupRoot: tempDir,
cleanup: () => {
if (pending) return requireCleanup ? complete(false) : false;
if (!active) return true;
return complete(removeTempDirectory(tempDir, reportFailure));
},
cleanupAsync: () => {
if (pending) return pending;
if (!active) return Promise.resolve(true);
pending = Promise.resolve().then(() => removeTempDirectoryAsync(tempDir, reportFailure)).then(complete).finally(() => {
pending = void 0;
});
return pending;
}
};
}
//#endregion
//#region src/infra/sqlite-lifecycle-errors.ts
var OpenClawStateOwnershipError = class extends Error {};
var OpenClawStateOwnershipMetadataError = class extends OpenClawStateOwnershipError {
constructor(databasePath, message) {
super(`OpenClaw shared state ownership metadata is invalid at ${databasePath}: ${message}. Repair it with OPENCLAW_SUPERVISOR_MODE=external openclaw database ownership claim --manager <manager-id>.`);
this.databasePath = databasePath;
this.name = "OpenClawStateOwnershipMetadataError";
}
};
var OpenClawStateExternalOwnershipError = class extends OpenClawStateOwnershipError {
constructor(databasePath, managerId) {
super(`OpenClaw shared state database ${databasePath} is externally supervised by ${managerId}. Use that external supervisor with OPENCLAW_SUPERVISOR_MODE=external for writable operations.`);
this.databasePath = databasePath;
this.managerId = managerId;
this.name = "OpenClawStateExternalOwnershipError";
}
};
const SqliteCoordinatorError = resolveGlobalSingleton(Symbol.for("openclaw.sqliteCoordinatorError"), () => class CoordinatorError extends Error {
constructor(message, cause) {
super(message);
this.cause = cause;
this.name = "SqliteCoordinatorError";
}
});
function createSqliteLifecycleAggregateError(errors, message, cause) {
return new AggregateError(errors, message, { cause });
}
/** Keep the first failure as the cause while retaining independent cleanup errors. */
function throwSqliteLifecycleErrors(errors, message) {
if (errors.length === 1) throw errors[0];
if (errors.length > 1) throw createSqliteLifecycleAggregateError(errors, message, errors[0]);
}
/** Settle a synchronous SQLite operation and preserve both operation and cleanup failures. */
function runWithSqliteCleanup(resource, operationLabel, operation) {
let result;
try {
result = operation();
if (isPromiseLike(result)) throw new SqliteCoordinatorError(`${operationLabel} must remain synchronous`);
} catch (operationError) {
try {
resource.release();
} catch (releaseError) {
throw createSqliteLifecycleAggregateError([operationError, releaseError], `${operationLabel} and resource release both failed`, operationError);
}
throw operationError;
}
try {
resource.release();
} catch (releaseError) {
throw new SqliteCoordinatorError(`${operationLabel} completed, but releasing its resource failed`, releaseError);
}
return result;
}
//#endregion
//#region src/state/openclaw-quarantine-error.ts
const DATABASE_QUARANTINE_READ_CLEANUP_ERROR_NAME = "OpenClawQuarantineReadCleanupError";
const OpenClawQuarantineReadCleanupError = resolveGlobalSingleton(Symbol.for("openclaw.quarantineReadCleanupError"), () => class QuarantineReadCleanupError extends AggregateError {
constructor(errors, quarantine) {
super(errors, "OpenClaw quarantine reader cleanup failed.", { cause: errors[0] });
this.quarantine = quarantine;
this.name = DATABASE_QUARANTINE_READ_CLEANUP_ERROR_NAME;
}
});
//#endregion
//#region src/state/openclaw-state-db-failure.ts
const failurePaths = resolveGlobalSingleton(Symbol.for("openclaw.stateDatabaseFailurePaths"), () => /* @__PURE__ */ new WeakMap());
/** Preserve the terminal admission owner's path through worker error transport. */
function markOpenClawStateDatabaseFailure(error, pathname) {
failurePaths.set(error, pathname);
}
/** Cleanup aggregates must retain the original refusal without attributing unrelated errors. */
function findOpenClawStateDatabaseFailure(error, pathname) {
const pending = [error];
const seen = /* @__PURE__ */ new Set();
for (const current of pending) {
if (!(current instanceof Error) || seen.has(current)) continue;
seen.add(current);
if (failurePaths.get(current) === pathname) return current;
pending.push(current.cause);
if (current instanceof AggregateError) pending.push(...current.errors);
}
}
//#endregion
//#region src/state/openclaw-state-db-async-lifecycle.ts
const STATE_DATABASE_READ_ADMISSION_INVALIDATED = "STATE_DATABASE_READ_ADMISSION_INVALIDATED";
var StateDatabaseReadAdmissionInvalidatedError = class extends Error {
constructor(..._args) {
super(..._args);
this.code = STATE_DATABASE_READ_ADMISSION_INVALIDATED;
}
};
function isStateDatabaseReadAdmissionInvalidatedError(error) {
return extractErrorCode(error) === STATE_DATABASE_READ_ADMISSION_INVALIDATED;
}
const maintenanceResources = resolveGlobalSingleton(Symbol.for("openclaw.databaseMaintenanceResources"), () => ({
current: new AsyncLocalStorage(),
claims: /* @__PURE__ */ new WeakMap(),
parents: /* @__PURE__ */ new WeakMap()
}));
function getOpenClawDatabaseMaintenanceScope() {
return maintenanceResources.current.getStore()?.scope;
}
function isOpenClawDatabaseMaintenanceResourceOwned(resource, scope) {
return maintenanceResources.claims.get(resource)?.scope === scope;
}
function getOpenClawDatabaseMaintenanceResourceScope(resource) {
return maintenanceResources.claims.get(resource)?.scope;
}
function runMaintenance(scope, operation) {
const accepted = {
scope,
active: true
};
try {
const result = maintenanceResources.current.run(accepted, operation);
if (result instanceof Promise) {
const settled = () => {
accepted.active = false;
};
result.then(settled, settled);
scope.track(result);
} else accepted.active = false;
return result;
} catch (error) {
accepted.active = false;
throw error;
}
}
/** Retain one exact resource claim for finite commands while its maintenance scope drains. */
function captureOpenClawDatabaseMaintenanceResource(resource, expectedScope) {
const claim = maintenanceResources.claims.get(resource);
const assertCurrent = () => {
expectedScope.assertOwnerCurrent();
if (claim?.scope !== expectedScope || maintenanceResources.claims.get(resource) !== claim) throw new Error("Database maintenance resource owner changed");
};
assertCurrent();
return {
assertCurrent,
async run(operation) {
assertCurrent();
return runMaintenance(expectedScope, operation);
}
};
}
/** A cached handle used by an independent caller remains with the ordinary cache owner. */
function observeOpenClawDatabaseMaintenanceResource(resource) {
if (!resource) return;
const claim = maintenanceResources.claims.get(resource);
const current = getOpenClawDatabaseMaintenanceScope();
if (!claim) return;
const owner = commonMaintenanceAncestor(claim.scope, current);
if (owner === claim.scope) return;
claim.scope.assertAdmission();
claim.release();
maintenanceResources.claims.delete(resource);
if (owner) owner.own(resource, claim.phase, claim.close);
}
function commonMaintenanceAncestor(owner, scope) {
const ancestors = /* @__PURE__ */ new Set();
for (let current = owner; current; current = maintenanceResources.parents.get(current)) ancestors.add(current);
for (let current = scope; current; current = maintenanceResources.parents.get(current)) if (ancestors.has(current)) return current;
}
/** The cache owns physical identity and admission across drainage and file exclusion. */
function createOpenClawStateDatabaseAsyncLifecycle() {
const resources = /* @__PURE__ */ new Set();
const records = /* @__PURE__ */ new Map();
const recordsByPath = /* @__PURE__ */ new Map();
const seals = /* @__PURE__ */ new Set();
const attempts = /* @__PURE__ */ new Map();
let tail = Promise.resolve();
const known = (pathname) => recordsByPath.get(pathname) ?? recordsByPath.get(path.resolve(pathname));
const bindPath = (record, pathname) => {
record.paths.add(pathname);
if (!recordsByPath.has(pathname)) recordsByPath.set(pathname, record);
};
const overlaps = (left, right) => {
if (left.identity.key === right.identity.key) return true;
for (const pathname of left.paths) if (right.paths.has(pathname)) return true;
return false;
};
const isSealed = (record) => {
if (seals.size === 0) return false;
for (const held of seals) if (held.record === void 0 || overlaps(held.record, record)) return true;
return false;
};
const hasCloseCustody = (record) => isSealed(record) || attempts.has(record);
const assertOpen = (record) => {
if (isSealed(record)) throw new StateDatabaseReadAdmissionInvalidatedError("OpenClaw state database read admission is closed");
};
const findPhysicalRecord = (identity) => {
const record = records.get(identity.key);
if (!record || !identity.key.startsWith("file:") || hasCloseCustody(record)) return record;
for (const pathname of findChangedDatabasePaths(record.paths, identity)) {
record.admissions.delete(pathname);
record.paths.delete(pathname);
if (recordsByPath.get(pathname) === record) recordsByPath.delete(pathname);
}
if (record.paths.size > 0) {
if (!record.paths.has(record.identity.canonicalPath)) record.identity = identity;
return record;
}
invalidate(record);
forget(record);
};
const resolve = (resolvedPath, preparedIdentity) => {
const cached = recordsByPath.get(resolvedPath);
if (cached && (!preparedIdentity || cached.identity.key === preparedIdentity.key)) return !preparedIdentity && cached.identity.key.startsWith("path:") ? resolve(resolvedPath, readDatabasePathIdentitySync(resolvedPath)) : cached;
const identity = preparedIdentity ?? readDatabasePathIdentitySync(resolvedPath);
let record = findPhysicalRecord(identity);
if (!record && identity.key.startsWith("file:")) {
record = [...records.values()].find((candidate) => {
if (!candidate.identity.key.startsWith("path:")) return false;
try {
return readDatabasePathIdentitySync(candidate.identity.canonicalPath).key === identity.key;
} catch {
return false;
}
});
if (record) {
records.delete(record.identity.key);
record.identity = identity;
records.set(identity.key, record);
}
}
if (!record) {
record = {
coordinationKey: identity.key,
identity,
paths: /* @__PURE__ */ new Set(),
generation: {},
admissions: /* @__PURE__ */ new Map()
};
records.set(identity.key, record);
}
bindPath(record, resolvedPath);
bindPath(record, identity.canonicalPath);
return record;
};
const resolveForNative = (pathname) => {
const resolvedPath = path.resolve(pathname);
const cached = recordsByPath.get(resolvedPath);
if (cached) return cached;
const identity = inspectDatabasePathIdentitySync(resolvedPath);
return identity ? resolve(resolvedPath, identity) : void 0;
};
const invalidate = (record) => {
for (const current of record ? [record] : records.values()) {
current.generation = {};
current.admissions.clear();
}
};
const seal = (record) => {
invalidate(record);
const held = { record };
seals.add(held);
return held;
};
const release = (attempt) => {
if (attempt.seal) {
seals.delete(attempt.seal);
attempt.seal = void 0;
}
};
const forget = (record) => {
if (!hasCloseCustody(record) && records.get(record.identity.key) === record) {
records.delete(record.identity.key);
for (const pathname of record.paths) if (recordsByPath.get(pathname) === record) {
recordsByPath.delete(pathname);
for (const replacement of records.values()) if (replacement.paths.has(pathname)) {
recordsByPath.set(pathname, replacement);
break;
}
}
}
};
const captureRecord = (record, databasePath) => {
const previous = record.admissions.get(databasePath);
if (previous) return previous;
const generation = record.generation;
const admission = Object.freeze({
databasePath,
coordinationKey: record.coordinationKey,
get identity() {
return record.identity;
},
assertCurrent() {
assertOpen(record);
if (records.get(record.identity.key) !== record || record.generation !== generation || record.admissions.get(databasePath) !== admission) throw new StateDatabaseReadAdmissionInvalidatedError("OpenClaw state database read admission changed");
}
});
record.admissions.set(databasePath, admission);
return admission;
};
const captureResolved = (databasePath) => {
const record = resolve(databasePath);
assertOpen(record);
return captureRecord(record, databasePath);
};
return {
identity(pathname) {
return known(pathname)?.identity ?? inspectDatabasePathIdentitySync(pathname);
},
knownIdentity(pathname) {
return known(pathname)?.identity;
},
publish(pathname) {
const resolvedPath = path.resolve(pathname);
const identity = readDatabasePathIdentitySync(resolvedPath);
const previous = recordsByPath.get(resolvedPath);
let record = findPhysicalRecord(identity);
if (previous && previous.identity.key !== identity.key) {
if (previous.identity.key.startsWith("path:") && !record) {
records.delete(previous.identity.key);
previous.identity = identity;
records.set(identity.key, previous);
record = previous;
} else {
const failed = attempts.get(previous);
if (failed) {
failed.superseded = true;
failed.seal ??= seal(previous);
} else invalidate(previous);
forget(previous);
}
}
if (!record) record = resolve(resolvedPath, identity);
bindPath(record, resolvedPath);
bindPath(record, identity.canonicalPath);
return {
identity,
admission: captureRecord(record, resolvedPath)
};
},
invalidate(pathname) {
if (pathname === void 0) invalidate();
else {
const record = known(pathname);
if (record) invalidate(record);
}
},
register(resource) {
resources.add(resource);
for (const attempt of attempts.values()) attempt.queue?.add(resource);
return () => {
resources.delete(resource);
};
},
capture(pathname) {
const cached = recordsByPath.get(pathname);
const retained = cached?.admissions.get(pathname);
if (cached && retained && cached.identity.key.startsWith("file:")) {
retained.assertCurrent();
return retained;
}
return captureResolved(path.resolve(pathname));
},
holdExclusion(pathname) {
const record = resolve(path.resolve(pathname));
const held = seal(record);
return () => {
seals.delete(held);
for (const current of records.values()) if (overlaps(record, current)) forget(current);
};
},
close(pathname, retireNative) {
const record = pathname === void 0 ? void 0 : resolveForNative(pathname);
if (pathname !== void 0 && !record) return Promise.resolve(retireNative());
let attempt = attempts.get(record);
if (attempt?.pending) return attempt.pending;
if (!attempt) {
attempt = { retained: /* @__PURE__ */ new Set() };
attempts.set(record, attempt);
}
attempt.seal ??= seal(record);
const current = attempt;
const pending = tail.then(async () => {
const closing = /* @__PURE__ */ new Set([...resources, ...current.retained]);
for (const entry of attempts.values()) for (const resource of entry.retained) closing.add(resource);
current.queue = closing;
const errors = [];
while (current.queue.size) {
const ordinary = [...current.queue].filter((resource) => resource.phase !== "after-resources");
if (!ordinary.length && errors.length) {
for (const resource of current.queue) current.retained.add(resource);
break;
}
const batch = ordinary.length ? ordinary : [...current.queue];
for (const resource of batch) current.queue.delete(resource);
await Promise.all(batch.map(async (resource) => {
try {
await resource.close(record?.identity);
current.retained.delete(resource);
} catch (error) {
current.retained.add(resource);
errors.push(error);
}
}));
}
if (errors.length && !current.superseded) release(current);
throwSqliteLifecycleErrors(errors, "OpenClaw state resource drainage failed");
const retired = retireNative(record?.identity);
attempts.delete(record);
release(current);
if (record === void 0) {
for (const [key, entry] of attempts) if (!entry.pending) {
attempts.delete(key);
release(entry);
}
for (const entry of records.values()) forget(entry);
} else forget(record);
return retired;
}).finally(() => {
current.queue = void 0;
});
current.pending = pending;
tail = pending.then(() => void 0, () => void 0);
pending.catch(() => {
current.pending = void 0;
});
return pending;
}
};
}
//#endregion
//#region src/state/openclaw-state-db-contract.ts
const DOCTOR_OWNED_STATE_TABLES = ["agent_deletion_journal"];
const FIRST_USE_STATE_TABLES = [
"user_profile_identities",
"local_workspace_projections",
"update_runs",
"session_repository_workspaces",
"github_repository_publication_requests",
"github_publication_session_lifecycles",
"skill_library_entries",
"skill_library_revisions",
"skill_library_events",
"skill_library_uploads",
"github_personal_publication_requests",
"cron_job_runtime_authorities",
"cron_run_trigger_state_retirements",
"execution_identity_contexts",
"mcp_oauth_pending_authorizations",
"node_worker_launch_containers",
"node_worker_launch_cleanup",
"node_worker_launches",
"node_worker_prepared_workspaces",
"node_worker_turns",
"operator_approval_execution_identities",
"operator_approval_standing_grants",
"operator_approval_standing_grant_generations",
"web_push_approval_deliveries",
"execution_decision_facts",
"execution_owner_lifecycle_bindings",
"outbound_message_execution_bindings",
"outbound_message_progress"
];
const FIRST_USE_STATE_INDEXES = [
"idx_user_profile_identities_profile_id",
"idx_user_profile_identities_authorization",
"idx_update_runs_created",
"idx_update_runs_active",
"idx_github_repository_publication_shared_request",
"idx_github_repository_publication_personal_request",
"idx_github_personal_publication_owner_session",
"idx_github_personal_publication_pending",
"idx_node_worker_launches_terminal_completed",
"idx_node_worker_turns_terminal_completed",
"idx_node_worker_turns_active_owner",
"idx_operator_approval_standing_grants_binding",
"idx_web_push_approval_deliveries_subscription",
"execution_identity_contexts_run_created_idx",
"execution_decision_facts_context_occurred_idx",
"execution_decision_facts_run_occurred_idx",
"outbound_message_execution_bindings_execution_event_idx",
"outbound_message_progress_occurred_idx",
"outbound_message_progress_run_occurred_idx"
];
const LAZY_ADDITIVE_STATE_TABLES = [
...FIRST_USE_STATE_TABLES,
"agent_provenance",
"cron_run_receipts",
"config_revision_keys",
"secret_store_entries",
"projects",
"worktree_templates",
"user_preferences",
"device_pair_setup_completions",
"github_publication_requests",
"device_pairing_join_codes",
"skill_workshop_proposal_events",
"skill_workshop_collection_reviews",
"skill_workshop_proposal_rollbacks",
"skill_workshop_proposals",
"worker_environment_ssh_fallback_ports",
"worker_session_placement_moves"
];
const LAZY_ADDITIVE_STATE_INDEXES = [
...FIRST_USE_STATE_INDEXES,
"idx_cron_run_receipts_active_job",
"idx_cron_run_receipts_job_history",
"idx_github_publication_requests_pending",
"secret_store_entries_live_idx",
"idx_skill_workshop_collection_reviews_owner_time"
];
/** Maximum time one synchronous SQLite call may wait for a lock. */
const OPENCLAW_SQLITE_BUSY_TIMEOUT_MS = 5e3;
/** User-facing guide for schema refusals; lives here so error sites avoid import cycles. */
const OPENCLAW_DATABASE_SCHEMA_DOCS_URL = "https://docs.openclaw.ai/reference/database-schemas";
//#endregion
//#region src/state/openclaw-state-db.paths.ts
function existingPathOrUndefined(pathname) {
try {
statSync(pathname);
return pathname;
} catch (error) {
if (hasErrnoCode(error, "ENOENT")) return;
throw error;
}
}
/** Resolve the directory that contains the shared state SQLite file. */
function resolveOpenClawStateSqliteDir(env = process.env) {
return path.join(resolveStateDir(env), "state");
}
/** Resolve the shared state SQLite file path. */
function resolveOpenClawStateSqlitePath(env = process.env) {
return path.join(resolveStateDir(env), "state", "openclaw.sqlite");
}
/** Resolve the state owner directory for a canonical or explicit shared database path. */
function resolveOpenClawStateDirForDatabasePath(databasePath) {
const databaseDir = path.dirname(path.resolve(databasePath));
return path.basename(databaseDir) === "state" ? path.dirname(databaseDir) : databaseDir;
}
/** Resolve the integrity/quarantine store that survives loss of the primary state database. */
function resolveQuarantineStorePath(env) {
return path.join(resolveOpenClawStateSqliteDir(env), "openclaw-quarantine.sqlite");
}
/** Resolve the durable registry form for one agent database path. */
function resolveOpenClawAgentDatabaseStoredPath(registryDatabasePath, agentDatabasePath) {
const rawStateDir = resolveOpenClawStateDirForDatabasePath(registryDatabasePath);
const stateDir = process.platform === "win32" ? normalizeWindowsPathPreservingCase(rawStateDir) : rawStateDir;
const absolutePath = path.resolve(agentDatabasePath);
const comparisonPath = process.platform === "win32" ? normalizeWindowsPathPreservingCase(absolutePath) : absolutePath;
if (!path.isAbsolute(stateDir) || !path.isAbsolute(comparisonPath)) return absolutePath;
const relativePath = path.relative(stateDir, comparisonPath);
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
const physicalRelative = path.relative(resolveIdentityPathViaExistingAncestorSync(stateDir), comparisonPath);
return physicalRelative.startsWith("..") || path.isAbsolute(physicalRelative) ? absolutePath : physicalRelative;
}
const rawPrefix = [stateDir, path.toNamespacedPath(stateDir)].map((root) => `${root}${root.endsWith(path.sep) ? "" : path.sep}`).find((prefix) => agentDatabasePath.startsWith(prefix));
return path.isAbsolute(agentDatabasePath) && rawPrefix !== void 0 ? agentDatabasePath.slice(rawPrefix.length) : relativePath;
}
function describeAgentPathMigration(summary) {
const { relativized, reanchored, deleted } = summary;
if (relativized === 0 && reanchored.length === 0 && deleted.length === 0) return [];
const decisions = reanchored.length + deleted.length;
const counts = [
`${relativized} relativized`,
reanchored.length > 0 && `${reanchored.length} re-anchored`,
deleted.length > 0 && `${deleted.length} removed`
].filter(Boolean);
return [
`Migrated agent database registry paths to state-relative storage${decisions > 0 ? ` (${counts.join(", ")})` : ""}`,
...reanchored.map((registeredPath) => `Re-anchored agent database registry path ${registeredPath} to the current state directory`),
...deleted.map((registeredPath) => `Removed duplicate agent database registry path ${registeredPath}`)
];
}
function warnAgentPathMigration(log, summary, databasePath) {
if (summary.reanchored.length === 0 && summary.deleted.length === 0) return;
log.warn("agent database registry rows re-anchored or removed during v9 migration", {
reanchored: summary.reanchored,
deleted: summary.deleted,
path: databasePath
});
}
//#endregion
//#region src/utils/zod-parse.ts
/**
* Null-returning Zod parse helpers for plugin and runtime boundaries.
*
* Callers use these where invalid external payloads should be ignored or
* recovered from without constructing and catching validation errors.
*/
/** Safely validates an unknown value with a Zod schema, returning null on validation failure. */
function safeParseWithSchema(schema, value) {
const parsed = schema.safeParse(value);
return parsed.success ? parsed.data : null;
}
/** Parses JSON, then safely validates it with a Zod schema, returning null for parse or schema failures. */
function safeParseJsonWithSchema(schema, raw) {
try {
return safeParseWithSchema(schema, JSON.parse(raw));
} catch {
return null;
}
}
//#endregion
//#region src/infra/gateway-lock-payload.ts
const LockPayloadSchema = object({
pid: number(),
ownerId: string().min(1).optional(),
/** A cold opener may wait for this transient owner, never enter while it is held. */
stateOwnerKind: literal("schema").optional(),
/** Present when Gateway cron writes use the dynamic-default ownership projection. */
cronOwnerProjection: literal("dynamic-default-v1").optional(),
createdAt: string(),
configPath: string(),
port: number().int().min(1).max(65535).optional(),
role: _enum([
"gateway",
"agent-embedded",
"skill-workshop-apply",
"sqlite-maintenance"
]).optional(),
stateDir: string().optional(),
startTime: number().optional()
});
function parseGatewayLockPayload(raw) {
return safeParseJsonWithSchema(LockPayloadSchema, raw);
}
//#endregion
//#region src/infra/private-mode.ts
const CHMOD_UNSUPPORTED_CODES = /* @__PURE__ */ new Set([
"ENOTSUP",
"EOPNOTSUPP",
"EINVAL"
]);
const PRIVATE_PROBE_FILE_MODE = 384;
function hasRestrictivePermissions(target) {
try {
return (statSync(target).mode & 63) === 0;
} catch {
return false;
}
}
function filesystemRejectsChmod(target) {
let probePath;
try {
const probeDir = statSync(target).isDirectory() ? target : path.dirname(target);
probePath = path.join(probeDir, `.openclaw-chmod-probe-${randomUUID()}`);
writeFileSync(probePath, "", {
flag: "wx",
mode: PRIVATE_PROBE_FILE_MODE
});
} catch {
return false;
}
try {
chmodSync(probePath, PRIVATE_PROBE_FILE_MODE);
return false;
} catch (err) {
return err.code === "EPERM";
} finally {
try {
unlinkSync(probePath);
} catch {}
}
}
function canIgnorePrivateChmodError(target, code) {
if (code && CHMOD_UNSUPPORTED_CODES.has(code)) return true;
if (code === "EROFS") return hasRestrictivePermissions(target);
if (code !== "EPERM") return false;
return hasRestrictivePermissions(target) || filesystemRejectsChmod(target);
}
/**
* Applies a private POSIX mode, reporting unsupported filesystems without
* weakening real permission failures.
*/
function applyPrivateModeSync(target, mode) {
try {
chmodSync(target, mode);
return { applied: true };
} catch (err) {
if (!canIgnorePrivateChmodError(target, err.code)) throw err;
return {
applied: false,
error: err
};
}
}
//#endregion
//#region src/infra/gateway-state-owner-directory.ts
function ensureOwnerDirectory(directory, created) {
const firstCreated = fs$1.mkdirSync(directory, {
recursive: true,
mode: 448
});
if (firstCreated && created) {
const boundary = path.toNamespacedPath(firstCreated);
const directories = [];
for (let current = directory;;) {
const { dev, ino } = fs$1.lstatSync(current, { bigint: true });
directories.push({
path: current,
dev,
ino
});
if (path.toNamespacedPath(current) === boundary) break;
const parent = path.dirname(current);
if (parent === current) throw new Error("Created state ownership directory is outside its expected ancestry");
current = parent;
}
for (const entry of directories) {
const previous = created.findIndex((candidate) => candidate.path === entry.path);
if (previous < 0) created.push(entry);
else created[previous] = entry;
}
}
const observed = fs$1.lstatSync(directory);
const uid = process.getuid?.();
if (!observed.isDirectory() || uid !== void 0 && observed.uid !== uid) throw new Error("State ownership directory must be a user-owned real directory");
if (process.platform !== "win32" && (observed.mode & 4095) !== 448) {
applyPrivateModeSync(directory, 448);
if ((fs$1.lstatSync(directory).mode & 63) !== 0) throw new Error("State ownership directory permissions are not private");
}
}
function removeCreatedProjectionDirectories(directories) {
let directory = directories[0];
while (directory) {
try {
const observed = fs$1.lstatSync(directory.path, { bigint: true });
if (observed.isDirectory() && observed.dev === directory.dev && observed.ino === directory.ino) fs$1.rmdirSync(directory.path);
} catch (error) {
const code = extractErrorCode(error);
if (code !== "ENOENT" && code !== "ENOTEMPTY" && code !== "EEXIST") throw error;
}
directories.shift();
directory = directories[0];
}
}
//#endregion
//#region src/infra/sqlite-busy-timeout.ts
const lockFailureReportingByDatabase = /* @__PURE__ */ new WeakMap();
function normalizeSqliteNonNegativeInteger(value, label) {
if (!Number.isInteger(value) || value < 0) throw new Error(`${label} must be a non-negative integer`);
return value;
}
function readSqliteBusyTimeout(database) {
const row = database.prepare("PRAGMA busy_timeout").get();
const value = row?.busy_timeout ?? row?.timeout;
return typeof value === "bigint" ? Number(value) : Number(value ?? 0);
}
function setSqliteBusyTimeout(database, busyTimeoutMs) {
const normalizedTimeoutMs = normalizeSqliteNonNegativeInteger(busyTimeoutMs, "busyTimeoutMs");
database.exec(`PRAGMA busy_timeout = ${normalizedTimeoutMs}`);
}
function shouldReportSqliteLockFailure(database) {
return lockFailureReportingByDatabase.get(database) !== "suppress";
}
/** Run with a temporary busy policy; restore early when write admission finishes. */
function runWithSqliteBusyTimeout(database, busyTimeoutMs, operation, options = {}) {
const normalizedTimeoutMs = normalizeSqliteNonNegativeInteger(busyTimeoutMs, "busyTimeoutMs");
const previousBusyTimeoutMs = readSqliteBusyTimeout(database);
const previousLockFailureReporting = lockFailureReportingByDatabase.get(database);
if (options.lockFailureReporting) lockFailureReportingByDatabase.set(database, options.lockFailureReporting);
if (previousBusyTimeoutMs !== normalizedTimeoutMs) setSqliteBusyTimeout(database, normalizedTimeoutMs);
const restore = () => {
if (database.isOpen && previousBusyTimeoutMs !== normalizedTimeoutMs) setSqliteBusyTimeout(database, previousBusyTimeoutMs);
if (previousLockFailureReporting) lockFailureReportingByDatabase.set(database, previousLockFailureReporting);
else lockFailureReportingByDatabase.delete(database);
};
try {
return operation(restore);
} finally {
restore();
}
}
//#endregion
//#region src/infra/stale-lock-file.ts
function readLockFileOwnerPayload(payload) {
if (!payload) return null;
return {
pid: typeof payload.pid === "number" && Number.isInteger(payload.pid) && payload.pid > 0 ? payload.pid : void 0,
createdAt: typeof payload.createdAt === "string" ? payload.createdAt : void 0,
starttime: typeof payload.starttime === "number" && Number.isInteger(payload.starttime) && payload.starttime >= 0 ? payload.starttime : void 0
};
}
/** Capture only the bounded facts used by this exact stale decision. Never
* resample the owner after failure or copy arbitrary sidecar payload fields. */
function inspectStaleLockOwner(params) {
const payload = readLockFileOwnerPayload(params.payload);
if (!payload?.pid) return null;
const observedStarttime = payload.starttime === void 0 ? null : (params.getProcessStartTime ?? getFileLockProcessStartTime)(payload.pid);
const identity = {
pid: payload.pid,
recordedStarttime: payload.starttime ?? null,
observedStarttime
};
const normalizedStored = process.platform === "darwin" && payload.starttime !== void 0 && payload.starttime > 1e10 ? Math.floor(payload.starttime / 1e6) : payload.starttime;
if (observedStarttime !== null && observedStarttime !== normalizedStored) return {
reason: "owner-starttime-changed",
...identity
};
return (params.isPidDefinitelyDead ?? isPidDefinitelyDead)(payload.pid) ? {
reason: "owner-process-exited",
...identity
} : null;
}
function isLockOwnerDefinitelyStale(params) {
return inspectStaleLockOwner(params) !== null;
}
//#endregion
//#region src/infra/gateway-state-owner.ts
function hasPhysicalOwnership(owner) {
return verifyOwnerLock(owner, owner.locks.values().next().value);
}
function verifyOwnerLock(owner, lock) {
owner.verifiedAt = void 0;
if (!lock?.verifyStillHeld()) return false;
owner.verifiedAt = performance.now();
return true;
}
const READ_OWNERSHIP_MAX_AGE_MS = 1e3;
const readOwnerPaths = resolveGlobalSingleton(Symbol.for("openclaw.gatewayStateReadOwnerPaths"), () => /* @__PURE__ */ new Map());
const owners = resolveGlobalSingleton(Symbol.for("openclaw.gatewayStateOwners"), () => /* @__PURE__ */ new Map());
const schemaOwners = resolveGlobalSingleton(Symbol.for("openclaw.stateDatabaseSchemaOwners"), () => new AsyncLocalStorage());
/** The synchronous lexical owner is live authority, not the existence of its sidecar. */
function getStateDatabaseSchemaLease(databasePath) {
const entry = schemaOwners.getStore()?.get(resolveGatewayStateOwnerPath(databasePath));
if (!entry) return;
if (!entry.active) throw new Error("State schema maintenance scope is no longer current");
entry.lease.assertCurrent();
return entry.lease;
}
const GatewayStateOwnerContentionError = resolveGlobalSingleton(Symbol.for("openclaw.gatewayStateOwnerContentionError"), () => class StateOwnerContentionError extends Error {
constructor(databasePath, cause) {
super(`OpenClaw state database is busy at ${databasePath}. Wait for the other OpenClaw process to finish, then retry. If it persists, run \`openclaw gateway status\` and check for other OpenClaw processes using the same state directory. A running Gateway can hold this ownership until it stops; stop it through its service manager or original terminal before retrying.`);
this.databasePath = databasePath;
this.cause = cause;
this.name = "GatewayStateOwnerContentionError";
}
});
const StateDatabaseAdmissionPendingError = resolveGlobalSingleton(Symbol.for("openclaw.stateDatabaseAdmissionPendingError"), () => class extends Error {
constructor(databasePath, message) {
super(message);
this.databasePath = databasePath;
}
});
/** Retry cold admission only; the same budget covers opening and its first unentered write. */
function withStateDatabaseColdAdmission(params, open) {
const budget = normalizeSqliteNonNegativeInteger(params.busyTimeoutMs, "busyTimeoutMs");
const deadline = performance.now() + budget;
const canonical = resolveIdentityPathViaExistingAncestorSync(params.databasePath);
const remainingBusyTimeoutMs = () => Math.max(0, Math.ceil(deadline - performance.now()));
const waiting = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
for (;;) try {
assertStateDatabaseAccessAllowed(params.databasePath);
return open(remainingBusyTimeoutMs);
} catch (error) {
if (!(error instanceof StateDatabaseAdmissionPendingError) || resolveIdentityPathViaExistingAncestorSync(error.databasePath) !== canonical || params.canRetry?.() === false) throw error;
const remaining = deadline - performance.now();
if (remaining <= 0) throw error;
Atomics.wait(waiting, 0, 0, Math.min(10, remaining));
}
}
/** State cleanup preserves this owner until destructive work and native handles settle. */
function resolveGatewayStateOwnerPath(databasePath) {
const canonical = resolveIdentityPathViaExistingAncestorSync(databasePath);
const uid = process.getuid?.();
const directory = process.platform === "win32" ? path.join(os.homedir(), "AppData", "Local", "OpenClaw", "locks", uid === void 0 ? "openclaw-state-owners" : `openclaw-state-owners-${uid}`) : resolveGatewayLockDirForCanonicalStateDir(resolveOpenClawStateDirForDatabasePath(canonical));
return path.join(resolveIdentityPathViaExistingAncestorSync(directory), `state.${sha256HexPrefixCore(canonical, 16)}.lock`);
}
function defaultPayload(databasePath, role = "sqlite-maintenance") {
const stateDir = resolveOpenClawStateDirForDatabasePath(databasePath);
const startTime = getFileLockProcessStartTime(process.pid);
return {
pid: process.pid,
ownerId: randomUUID(),
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
stateDir,
configPath: path.join(stateDir, "openclaw.json"),
role,
...startTime === null ? {} : { startTime }
};
}
function acquireOwnerFile(databasePath, pathname, payload, busyTimeoutMs = 0, createdDirectories) {
const deadline = performance.now() + busyTimeoutMs;
ensureOwnerDirectory(path.dirname(pathname), createdDirectories);
const stale = ({ payload: value }) => {
return isLockOwnerDefinitelyStale({ payload: isRecord(value) ? {
pid: value.pid,
starttime: value.startTime
} : null });
};
if (busyTimeoutMs > 0) try {
const previous = parseGatewayLockPayload(fs$1.readFileSync(pathname, "utf8"));
if (previous && !stale({ payload: previous }) && (previous.pid === process.pid || (previous.role ?? "gateway") === "gateway" && isPidAlive(previous.pid))) throw new GatewayStateOwnerContentionError(databasePath);
} catch (error) {
if (extractErrorCode(error) !== "ENOENT") throw error;
}
for (let retriedMissingParent = false;;) try {
return acquireFileLockSync(pathname, {
lockPath: pathname,
retry: busyTimeoutMs > 0 ? {
factor: 1.25,
minTimeout: 10,
maxTimeout: 25,
randomize: false
} : { retries: 0 },
timeoutMs: Math.max(0, Math.ceil(deadline - performance.now())),
staleMs: Infinity,
staleRecovery: "remove-if-unchanged",
reentrantOwner: payload.ownerId,
payload: () => payload,
parsePayload: parseGatewayLockPayload,
shouldReclaim: stale,
shouldRemoveStaleLock: stale
});
} catch (error) {
const code = extractErrorCode(error);
if (code === "ENOENT" && !retriedMissingParent) {
retriedMissingParent = true;
ensureOwnerDirectory(path.dirname(pathname), createdDirectories);
continue;
}
if (code === "file_lock_timeout" || code === "file_lock_stale") throw new GatewayStateOwnerContentionError(databasePath, error);
throw error;
}
}
function leaseForFile(pathname, lock, owner, projection) {
let released = false;
const lease = {
path: pathname,
assertCurrent() {
if (released || !verifyOwnerLock(owner, lock) || projection && !projection.verifyStillHeld()) throw new Error("OpenClaw state ownership is no longer current");
},
assertDatabaseAccess(databasePath) {
if (released || owners.get(pathname) !== owner || resolveGatewayStateOwnerPath(databasePath) !== pathname || !verifyOwnerLock(owner, lock) || projection && !projection.verifyStillHeld()) throw new Error("OpenClaw state maintenance does not own this database");
},
run(operation) {
lease.assertCurrent();
readOwnerPaths.clear();
const inherited = new Map(schemaOwners.getStore());
const entry = {
lease,
active: true
};
inherited.set(pathname, entry);
try {
return schemaOwners.run(inherited, operation);
} finally {
entry.active = false;
readOwnerPaths.clear();
}
},
release() {
readOwnerPaths.clear();
if (!released) {
projection?.release();
lock.release();
released = true;
owner.locks.delete(lock);
if (owner.locks.size === 0 && owners.get(pathname) === owner) {
owner.accepting = false;
owners.delete(pathname);
}
}
if (owner.locks.size === 0) removeCreatedProjectionDirectories(owner.projectionDirectories);
}
};
return lease;
}
/** Accepted schema work retains the sidecar even when its process owner stops lending. */
function acquireStateDatabaseSchemaLease(databasePath, options = {}) {
readOwnerPaths.clear();
const pathname = resolveGatewayStateOwnerPath(databasePath);
let owner = owners.get(pathname);
if (owner && (!owner.accepting || !hasPhysicalOwnership(owner))) throw new GatewayStateOwnerContentionError(databasePath);
if (owner) assertStateDatabaseAccessAllowed(databasePath);
const payload = owner?.payload ?? {
...defaultPayload(databasePath),
stateOwnerKind: "schema"
};
let lock;
const projectionDirectories = [];
try {
lock = acquireOwnerFile(databasePath, pathname, payload, owner ? 0 : options.busyTimeoutMs ?? 5e3, projectionDirectories);
} catch (error) {
if (error instanceof GatewayStateOwnerContentionError) try {
assertStateDatabaseAccessAllowed(databasePath);
} catch (currentOwnerError) {
if (currentOwnerError instanceof StateDatabaseAdmissionPendingError) throw currentOwnerError;
}
throw error;
}
const projectionPath = owner?.projectionPath ?? path.join(resolveGatewayLockDir(resolveOpenClawStateDirForDatabasePath(resolveIdentityPathViaExistingAncestorSync(databasePath))), "gateway.state.lock");
let projection;
try {
projection = owner?.getProjection?.()?.retain() ?? acquireOwnerFile(databasePath, projectionPath, {
...payload,
role: payload.role === "gateway" ? "gateway" : "agent-embedded"
}, 0, projectionDirectories);
} catch (error) {
return runWithSqliteCleanup({ release() {
lock.release();
removeCreatedProjectionDirectories(projectionDirectories);
} }, "state schema projection admission", () => {
throw error;
});
}
if (owner) {
owner.locks.add(lock);
owner.projectionDirectories.push(...projectionDirectories);
} else {
owner = {
kind: "schema",
payload,
projectionPath,
locks: /* @__PURE__ */ new Set([lock]),
projectionDirectories,
accepting: true
};
owners.set(pathname, owner);
}
const lease = leaseForFile(pathname, lock, owner, projection);
try {
lease.assertCurrent();
return lease;
} catch (error) {
return runWithSqliteCleanup(lease, "state schema ownership verification", () => {
throw error;
});
}
}
function hasRecentVerification(verifiedAt, now) {
return verifiedAt !== void 0 && now - verifiedAt < READ_OWNERSHIP_MAX_AGE_MS;
}
/** Only explicit reads reuse recent physical verification; mutations always check freshly. */
function assertStateDatabaseReadAllowed(databasePath) {
if (owners.size === 0) {
assertStateDatabaseAccessAllowed(databasePath);
return;
}
const key = path.resolve(databasePath);
const now = performance.now();
const cached = readOwnerPaths.get(key);
const projection = cached?.owner.getProjection?.();
if (cached && now < cached.expiresAt && owners.get(cached.pathname) === cached.owner && cached.owner.accepting && hasRecentVerification(cached.owner.verifiedAt, now) && (!cached.owner.getProjection || projection && hasRecentVerification(projection.verifiedAt, now))) return;
readOwnerPaths.delete(key);
const pathname = resolveGatewayStateOwnerPath(databasePath);
const owner = owners.get(pathname);
const role = owner?.payload.role ?? "gateway";
if (!owner || owner.kind !== "process" || !owner.accepting || role !== "gateway" && role !== "agent-embedded") {
assertStateDatabaseAccessAllowed(databasePath);
return;
}
if (!hasPhysicalOwnership(owner) || owner.getProjection && !owner.getProjection()?.verifyStillHeld()) throw new Error(`OpenClaw state ownership at ${databasePath} could not be verified; retry after maintenance finishes.`);
readOwnerPaths.set(key, {
pathname,
owner,
expiresAt: now + READ_OWNERSHIP_MAX_AGE_MS
});
}
/** Ordinary SQLite access observes maintenance; it never borrows schema authority. */
function assertStateDatabaseAccessAllowed(databasePath, captured) {
const assertMaintenance = () => {
const schemaLease = captured ? captured.schemaLease : getStateDatabaseSchemaLease(databasePath);
if (schemaLease) {
schemaLease.assertDatabaseAccess(databasePath);
return;
}
const scope = captured ? captured.maintenanceScope : getOpenClawDatabaseMaintenanceScope();
if (!scope) throw new Error(`OpenClaw state at ${databasePath} is undergoing offline maintenance; retry when it finishes.`);
scope.assertDatabaseAccess(databasePath);
};
const pathname = resolveGatewayStateOwnerPath(databasePath);
const local = owners.get(pathname);
const unavailable = `OpenClaw state ownership at ${databasePath} could not be verified; retry after maintenance finishes.`;
if (local) {
if (!hasPhysicalOwnership(local)) throw new Error(unavailable);
const role = local.payload.role ?? "gateway";
if (!local.accepting || role !== "gateway" && role !== "agent-embedded") assertMaintenance();
return;
}
let raw;
try {
raw = fs$1.readFileSync(pathname, "utf8");
} catch (error) {
if (extractErrorCode(error) === "ENOENT") return;
throw new Error(unavailable, { cause: error });
}
const owner = parseGatewayLockPayload(raw);
if (!owner) throw new StateDatabaseAdmissionPendingError(databasePath, unavailable);
if (!Number.isSafeInteger(owner.pid) || owner.pid <= 0) throw new Error(unavailable);
if (isLockOwnerDefinitelyStale({ payload: {
pid: owner.pid,
starttime: owner.startTime
} })) return;
if (owner.pid === process.pid && !isMainThread) return;
if (!isPidAlive(owner.pid)) throw new Error(unavailable);
const role = owner.role ?? "gateway";
if (role === "gateway" || role === "agent-embedded") return;
if (owner.pid === process.pid) {
assertMaintenance();
return;
}
if (owner.stateOwnerKind === "schema" && owner.role === "sqlite-maintenance") throw new StateDatabaseAdmissionPendingError(databasePath, `OpenClaw state at ${databasePath} is undergoing offline maintenance; retry when it finishes.`);
throw new Error(`OpenClaw state at ${databasePath} is undergoing offline maintenance; retry when it finishes.`);
}
//#endregion
//#region src/infra/git-root.ts
function walkUpFrom(startDir, opts, resolveAtDir) {
let current = path.resolve(startDir);
for (let i = 0; opts.maxDepth === void 0 || i < opts.maxDepth; i += 1) {
const resolved = resolveAtDir(current);
if (resolved !== null && resolved !== void 0) return resolved;
const parent = path.dirname(current);
if (parent === current) break;
current = parent;
}
return null;
}
function resolveGitDirFromMarker(repoRoot) {
const gitPath = path.join(repoRoot, ".git");
try {
const stat = fs$1.statSync(gitPath);
if (stat.isDirectory()) return gitPath;
if (!stat.isFile()) return null;
const match = fs$1.readFileSync(gitPath, "utf-8").match(/gitdir:\s*(.+)/i);
if (!match?.[1]) return null;
return path.resolve(repoRoot, match[1].trim());
} catch {
return null;
}
}
function resolveGitHeadPath(startDir, opts = {}) {
return walkUpFrom(startDir, opts, (repoRoot) => {
const gitDir = resolveGitDirFromMarker(repoRoot);
return gitDir ? path.join(gitDir, "HEAD") : null;
});
}
/** Read at most `limit` bytes from Git or build metadata. */
function readGitMetadataPrefix(filePath, limit = 256) {
const fd = fs$1.openSync(filePath, "r");
try {
const buf = Buffer.alloc(limit);
const bytesRead = readFileWindowFullySync(fd, buf, 0);
return buf.subarray(0, bytesRead).toString("utf-8");
} finally {
fs$1.closeSync(fd);
}
}
function readGitHead(startDir, opts = {}) {
const headPath = resolveGitHeadPath(startDir, opts);
if (!headPath) return;
const head = fs$1.readFileSync(headPath, "utf-8").trim();
if (!head.startsWith("ref:")) return {
headPath,
ref: null,
value: head || null
};
const ref = head.replace(/^ref:\s*/i, "").trim();
const refsBase = resolveGitRefsBase(headPath);
return {
headPath,
ref,
value: readGitRefs(refsBase, [ref]).get(ref) ?? null,
refsBase
};
}
function resolveGitRefsBase(headPath) {
const gitDir = path.dirname(headPath);
try {
const commonDir = readGitMetadataPrefix(path.join(gitDir, "commondir")).trim();
if (commonDir) return path.resolve(gitDir, commonDir);
} catch (error) {
if (!isMissingPathError(error)) throw error;
}
return gitDir;
}
/** Raw ref contents, sharing one packed inventory across the requested names. */
function readGitRefs(refsBase, refs) {
const values = new Map(refs.map((ref) => [ref, null]));
const missing = /* @__PURE__ */ new Set();
for (const ref of refs) {
const refPath = resolveRefPath(refsBase, ref);
if (!refPath) continue;
try {
values.set(ref, readGitMetadataPrefix(refPath).trim());
} catch (error) {
if (!isMissingPathError(error)) throw error;
missing.add(ref);
}
}
if (missing.size === 0) return values;
try {
const packedRefs = fs$1.readFileSync(path.join(refsBase, "packed-refs"), "utf-8");
for (const line of packedRefs.split("\n")) {
if (!line || line.startsWith("#") || line.startsWith("^")) continue;
const [value, packedRef] = line.trim().split(/\s+/, 2);
if (packedRef && missing.delete(packedRef)) values.set(packedRef, value ?? null);
}
} catch (error) {
if (!isMissingPathError(error)) throw error;
}
return values;
}
/** Safely resolve a Git ref path, rejecting traversal from a crafted HEAD file. */
function resolveRefPath(refsBase, ref) {
if (!ref.startsWith("refs/")) return null;
if (path.isAbsolute(ref)) return null;
if (ref.split(/[/]/).includes("..")) return null;
const resolved = path.resolve(refsBase, ref);
const rel = path.relative(refsBase, resolved);
if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) return null;
return resolved;
}
//#endregion
//#region src/shared/async-work-scope.ts
const currentWorkScope = resolveGlobalSingleton(Symbol.for("openclaw.asyncWorkScope"), () => new AsyncLocalStorage());
const currentWorkScopeAncestry = resolveGlobalSingleton(Symbol.for("openclaw.asyncWorkScopeAncestry"), () => new AsyncLocalStorage());
const detachedAsyncContext = resolveGlobalSingleton(Symbol.for("openclaw.detachedAsyncContext"), () => new AsyncResource("openclaw.detached-async-context"));
/** Joins cooperating descendants even when their caller returns a cached value first. */
var AsyncWorkScope = class AsyncWorkScope {
constructor(failures) {
this.failures = failures;
this.pending = /* @__PURE__ */ new Set();
this.controller = new AbortController();
this.phase = "open";
}
get signal() {
return this.controller.signal;
}
get hasPendingWork() {
return this.pending.size > 0;
}
get isClosing() {
return this.phase !== "open";
}
enter(run) {
const owner = currentWorkScope.getStore();
const ancestry = currentWorkScopeAncestry.getStore();
const parent = owner ? ancestry?.scope === owner ? ancestry : { scope: owner } : void 0;
return currentWorkScopeAncestry.run({
scope: this,
parent
}, () => currentWorkScope.run(this, run));
}
/** Enters synchronous work without inspecting or assimilating its return value. */
run(run) {
if (this.phase === "closed") throw new Error("Async work scope is closed");
const operation = createDeferredCore();
this.pending.add(operation.promise);
try {
return this.enter(run);
} finally {
operation.resolve();
this.pending.delete(operation.promise);
}
}
track(run) {
if (this.phase === "closed") return Promise.reject(/* @__PURE__ */ new Error("Async work scope is closed"));
const operation = this.registerWork();
try {
operation.resolve(this.enter(run));
} catch (error) {
operation.reject(error);
}
return operation.promise;
}
registerWork() {
const operation = createDeferredCore();
this.pending.add(operation.promise);
operation.promise.then(() => this.pending.delete(operation.promise), (error) => {
this.pending.delete(operation.promise);
this.failures?.add(error);
});
return operation;
}
beginClose(reason) {
if (this.phase !== "open") return;
this.phase = "closing";
this.controller.abort(reason);
}
/** Starts the next phase in the same continuation that observes settled pending work. */
runWhenIdle(run) {
return AsyncWorkScope.runWhenAllIdle(() => [this], () => this.track(run));
}
/** Reselects owners so work admitted into a later phase is not mistaken for earlier work. */
static async runWhenAllIdle(selectScopes, run) {
let scopes = selectScopes();
while (scopes.some((scope) => scope.pending.size > 0)) {
await Promise.allSettled(scopes.flatMap((scope) => Array.from(scope.pending)));
scopes = selectScopes();
}
return run();
}
async drain() {
this.beginClose();
while (this.pending.size > 0) await Promise.allSettled(this.pending);
this.phase = "closed";
}
};
/** Outside a managed scope, the returned promise remains the caller's responsibility. */
async function trackAsyncWork(run) {
const scope = currentWorkScope.getStore();
return await (scope ? scope.track(run) : run());
}
/** Runs under the context-free async root initialized before managed work can begin. */
function runInDetachedAsyncContext(run) {
return detachedAsyncContext.runInAsyncScope(run);
}
//#endregion
//#region src/plugins/host-hook-cleanup-result.ts
/** Merge raw instance outcomes without duplicating their existing host or instance report. */
function appendPluginInstanceCleanupFailures(failures, pluginId, result) {
for (const error of result.errors) {
if (failures.some((failure) => failure.pluginId === pluginId && failure.error === error && (failure.hookId === "instance" || result.hostCleanupErrors?.includes(error)))) continue;
failures.push({
pluginId,
hookId: "instance",
error
});
}
}
//#endregion
//#region src/plugins/plugin-cache-artifacts.ts
function createPluginCacheArtifacts() {
return {
moduleLoaders: /* @__PURE__ */ new Map(),
sources: /* @__PURE__ */ new Map(),
sourceAliases: /* @__PURE__ */ new Map(),
runtimeRecordRoots: /* @__PURE__ */ new WeakMap()
};
}
//#endregion
//#region src/plugins/plugin-cache-sdk.ts
/** Derived SDK facts share the plugin cache lifetime; none owns a separate expiry. */
function createPluginCacheSdk() {
return {
hosts: /* @__PURE__ */ new Map(),
contexts: /* @__PURE__ */ new Map(),
packageNames: /* @__PURE__ */ new Map(),
packageSearches: /* @__PURE__ */ new Map(),
argvDirectories: /* @__PURE__ */ new Map(),
devSourceRoots: /* @__PURE__ */ new Map(),
runtimeModules: /* @__PURE__ */ new Map(),
usableDistArtifacts: /* @__PURE__ */ new Map(),
normalizedJitiAliases: /* @__PURE__ */ new Map(),
aliasFacts: /* @__PURE__ */ new WeakMap(),
native: {
sdkProviders: /* @__PURE__ */ new Map(),
nextSdkProviderOrder: 0,
aliases: /* @__PURE__ */ new Map(),
registeredHosts: /* @__PURE__ */ new Set(),
hostRoots: /* @__PURE__ */ new Map(),
nearestPackageRoots: /* @__PURE__ */ new Map(),
loaderPackageRoots: /* @__PURE__ */ new Map(),
allowedParentRoots: /* @__PURE__ */ new Map()
}
};
}
resolveGlobalSingleton(Symbol.for("openclaw.pluginSourceRecoveryUnavailableError"), () => class SourceRecoveryUnavailableError extends Error {
constructor(cause) {
super("Captured plugin source is missing; its previous code cannot be recovered.", { cause });
this.name = "PluginSourceRecoveryUnavailableError";
}
});
const PluginInstanceDrainTimeoutError = resolveGlobalSingleton(Symbol.for("openclaw.pluginInstanceDrainTimeoutError"), () => class DrainTimeoutError extends Error {
constructor(message, settled, options, forcedRetirement) {
super(message, options);
this.settled = settled;
this.forcedRetirement = forcedRetirement;
this.name = "PluginInstanceDrainTimeoutError";
}
});
//#endregion
//#region src/plugins/plugin-instance-invocation.ts
var InvocationFrame = class InvocationFrame {
constructor(scopes) {
this.invocation = scopes.invocation;
this.metadataScope = scopes.metadataScope;
this.cacheScope = scopes.cacheScope;
}
withScopes(scopes) {
return new InvocationFrame(scopes);
}
};
/** Copy core scopes through the current owner so runtime-only fields survive. */
function createPluginExecutionFrame(scopes, current) {
return current ? current.withScopes(scopes) : new InvocationFrame(scopes);
}
const pluginExecutionContext = resolveGlobalSingleton(Symbol.for("openclaw.pluginInstanceInvocation"), () => {
const frames = new AsyncLocalStorage();
return {
invocation: {
getStore() {
return frames.getStore()?.invocation;
},
run(invocation, run) {
const current = frames.getStore();
return frames.run(current?.invocation === invocation ? current : createPluginExecutionFrame({
...current,
invocation
}, current), run);
},
exit(run) {
const current = frames.getStore();
return current?.invocation ? frames.run(current.withScopes({
...current,
invocation: void 0
}), run) : run();
}
},
getFrame() {
return frames.getStore();
},
runFrame(frame, run) {
return frames.run(frame, run);
}
};
});
pluginExecutionContext.invocation;
const getPluginExecutionFrame = pluginExecutionContext.getFrame;
pluginExecutionContext.runFrame;
//#endregion
//#region src/plugins/plugin-cache.ts
/** Cached diagnostics must not retain the caller through V8's lazy stack frames. */
function materializePluginCacheError(failure) {
let error = failure;
const seen = /* @__PURE__ */ new Set();
while (error instanceof Error && !seen.has(error)) {
seen.add(error);
try {
error.stack = String(error.stack);
} catch {
error.stack = "Stack trace unavailable: custom formatter failed";
}
error = error.cause;
}
}
const state = resolveGlobalSingleton(Symbol.for("openclaw.pluginCache"), () => ({
snapshotOwners: /* @__PURE__ */ new WeakMap(),
retirements: []
}));
const cacheRetainers = resolveGlobalSingleton(Symbol.for("openclaw.pluginCacheRetainers"), () => /* @__PURE__ */ new WeakMap());
const instanceCacheOwners = resolveGlobalSingleton(Symbol.for("openclaw.pluginInstanceCacheOwners"), () => /* @__PURE__ */ new WeakMap());
/** A retiring inventory releases its own custody; terminal disposal releases every birth cache. */
function releasePluginCacheInstance(instance, cache) {
const owners = instanceCacheOwners.get(instance);
if (cache) {
cache.instances.delete(instance);
owners?.delete(cache);
} else {
for (const owner of owners ?? []) owner.instances.delete(instance);
owners?.clear();
}
if (owners?.size === 0) instanceCacheOwners.delete(instance);
}
function getPluginCacheRetainers(cache) {
let retained = cacheRetainers.get(cache);
if (!retained) {
retained = {
references: /* @__PURE__ */ new Set(),
controller: new AbortController(),
settled: createDeferredCore()
};
cacheRetainers.set(cache, retained);
}
return retained;
}
function createPluginMetadataCache() {
return {
current: {
snapshot: void 0,
owner: "operation",
configFingerprint: void 0,
agentWorkspaceFingerprint: void 0,
envFingerprint: void 0,
defaultDiscoveryCompatible: false,
compatiblePolicyHashes: void 0,
compatibleConfigFingerprints: void 0,
revision: Symbol("plugin-metadata-snapshot"),
configIdentities: /* @__PURE__ */ new WeakSet()
},
snapshots: /* @__PURE__ */ new Map(),
discovery: /* @__PURE__ */ new Map(),
sharedDiscovery: /* @__PURE__ */ new Map(),
projections: /* @__PURE__ */ new WeakMap(),
projectionSources: /* @__PURE__ */ new WeakMap(),
completions: /* @__PURE__ */ new WeakMap(),
indexFacts: /* @__PURE__ */ new WeakMap(),
providerPolicyOwners: /* @__PURE__ */ new WeakMap(),
channelAdapters: /* @__PURE__ */ new WeakMap(),
bundledChannelCatalogs: /* @__PURE__ */ new Map(),
bundledProviderPolicySurfaces: /* @__PURE__ */ new Map(),
staticCatalogStates: /* @__PURE__ */ new WeakMap(),
modelSuppressionResolvers: /* @__PURE__ */ new WeakMap(),
mcpAuthDeclarations: /* @__PURE__ */ new WeakMap()
};
}
/** Each inventory owns its acquired facts and reusable load results; publication owns activation. */
function createPluginCache(options = {}) {
return {
async [Symbol.asyncDispose]() {
await retirePluginCache(this);
},
kind: options.kind ?? "operation",
roots: /* @__PURE__ */ new Map(),
rootAliases: /* @__PURE__ */ new Map(),
sdk: createPluginCacheSdk(),
setupModules: /* @__PURE__ */ new Map(),
instances: /* @__PURE__ */ new Set(),
metadata: createPluginMetadataCache(),
installRecords: /* @__PURE__ */ new Map(),
persistedInstalledIndex: /* @__PURE__ */ new Map(),
preparedBundledDiscoveryModes: /* @__PURE__ */ new Map(),
dependencyStatus: /* @__PURE__ */ new WeakMap(),
...createPluginCacheArtifacts()
};
}
function getProcessPluginCache() {
return state.current ??= createPluginCache({ kind: "process" });
}
function getScopedPluginCache() {
return getPluginExecutionFrame()?.cacheScope?.cache;
}
function getPluginCache() {
return getScopedPluginCache() ?? getProcessPluginCache();
}
/** Stop new setup calls immediately; the owner awaits in-flight calls and graph cleanup. */
function retirePluginCache(cache, beforeRetire) {
const retained = getPluginCacheRetainers(cache);
if (retained.retirement) return retained.retirement;
const completion = createDeferredCore();
retained.retirement = completion.promise;
const trackRetirement = async (run) => {
const work = new AsyncWorkScope();
try {
return await work.track(run);
} finally {
await work.run(() => work.drain());
}
};
retained.beginRetirement = (track = trackAsyncWork) => {
let admitted = false;
track(() => {
admitted = true;
retained.beginRetirement = void 0;
return beginPluginCacheRetirement(cache, beforeRetire);
}).then(completion.resolve, (error) => {
if (admitted) completion.reject(error);
else retained.beginRetirement?.(trackRetirement);
});
};
retained.controller.abort();
materializePluginCacheError(retained.controller.signal.reason);
if (retained.references.size === 0) retained.beginRetirement?.();
else retained.settled.promise.then(() => {
retained.beginRetirement?.(trackRetirement);
});
return completion.promise;
}
function beginPluginCacheRetirement(cache, beforeRetire) {
if (cache.retirement) return cache.retirement;
const retirement = createDeferredCore();
cache.retirement = retirement.promise;
const cleanup = async () => {
beforeRetire?.();
const registries = cache.retireRegistryLoads?.();
const resources = /* @__PURE__ */ new Set([...cache.setupModules.values(), ...cache.instances]);
for (const resource of resources) resource.quiesce();
const [registry] = await Promise.allSettled([registries]);
const outcomes = await Promise.allSettled([...resources].map(async (resource) => ({
resource,
result: await resource.dispose()
})));
const nativeAdmissions = cache.sourceAdmissions;
if (nativeAdmissions) {
const pending = outcomes.flatMap((outcome) => outcome.status === "fulfilled" ? outcome.value.result.errors.flatMap((error) => error instanceof PluginInstanceDrainTimeoutError ? [error.settled] : []) : []);
if (pending.length > 0) Promise.allSettled(pending).then(() => nativeAdmissions.dispose()).catch((error) => process.emitWarning(`Plugin native capture cleanup failed: ${String(error)}`));
else await nativeAdmissions.dispose();
}
cache.setupModules.clear();
for (const instance of cache.instances) releasePluginCacheInstance(instance, cache);
const unexpected = [...registry.status === "rejected" ? [registry.reason] : [], ...outcomes.flatMap((result) => result.status === "rejected" ? [result.reason] : [])];
if (unexpected.length) throw new AggregateError(unexpected, "Plugin cache resources failed to retire");
const host = registry.status === "fulfilled" ? registry.value : void 0;
const failures = [...host?.failures ?? []];
for (const outcome of outcomes) {
if (outcome.status !== "fulfilled") continue;
const { resource, result } = outcome.value;
appendPluginInstanceCleanupFailures(failures, resource.pluginId, result);
}
return {
cleanupCount: host?.cleanupCount ?? 0,
failures
};
};
cleanup().then(retirement.resolve, retirement.reject);
return retirement.promise;
}
//#endregion
//#region src/infra/openclaw-root.ts
function parsePackageName(raw) {
const parsed = JSON.parse(raw);
return typeof parsed.name === "string" ? parsed.name : null;
}
function readPackageNameSync(dir) {
const packageNameCache = getPluginCache().sdk.packageNames;
const packageJsonPath = path.join(path.resolve(dir), "package.json");
if (packageNameCache.has(packageJsonPath)) return packageNameCache.get(packageJsonPath) ?? null;
try {
const name = parsePackageName(openClawRootFsSync.readFileSync(packageJsonPath, "utf-8"));
packageNameCache.set(packageJsonPath, name);
return name;
} catch {
packageNameCache.set(packageJsonPath, null);
return null;
}
}
function findPackageRootSync(startDir, maxDepth = 12) {
for (const current of iterAncestorDirs(startDir, maxDepth)) if (readPackageNameSync(current) === "openclaw") return current;
return null;
}
function* iterAncestorDirs(startDir, maxDepth) {
let current = path.resolve(startDir);
for (let i = 0; i < maxDepth; i += 1) {
yield current;
if (path.basename(current) === "node_modules") break;
const parent = path.dirname(current);
if (parent === current) break;
current = parent;
}
}
function candidateDirsFromArgv1(argv1) {
const argv1CandidateCache = getPluginCache().sdk.argvDirectories;
const cacheKey = path.resolve(argv1);
const cached = argv1CandidateCache.get(cacheKey);
if (cached) return [...cached];
const normalized = cacheKey;
const candidates = [];
try {
const resolved = openClawRootFsSync.realpathSync(normalized);
if (resolved !== normalized) candidates.push(path.dirname(resolved));
} catch {}
candidates.push(path.dirname(normalized));
const parts = normalized.split(path.sep);
const binIndex = parts.lastIndexOf(".bin");
if (binIndex > 0 && parts[binIndex - 1] === "node_modules") {
const binName = path.basename(normalized);
const nodeModulesDir = parts.slice(0, binIndex).join(path.sep);
candidates.push(path.join(nodeModulesDir, binName));
}
const deduped = dedupeCandidates(candidates);
argv1CandidateCache.set(cacheKey, deduped);
return [...deduped];
}
function resolveOpenClawPackageRootSync(opts) {
const candidates = buildCandidates(opts);
const cacheKey = candidates.join("\0");
const searches = getPluginCache().sdk.packageSearches;
const cached = searches.get(cacheKey);
if (cached?.all) return cached.all[0] ?? null;
if (cached?.first !== void 0) return cached.first;
for (const candidate of candidates) {
const found = findPackageRootSync(candidate);
if (found) {
searches.set(cacheKey, { first: found });
return found;
}
}
searches.set(cacheKey, { first: null });
return null;
}
function buildCandidates(opts) {
const candidates = [];
if (opts.moduleUrl) try {
candidates.push(path.dirname(fileURLToPath(opts.moduleUrl)));
} catch {}
if (opts.argv1) candidates.push(...candidateDirsFromArgv1(opts.argv1));
if (opts.cwd) candidates.push(opts.cwd);
return dedupeCandidates(candidates);
}
function dedupeCandidates(candidates) {
return [...new Set(candidates.map((candidate) => path.resolve(candidate)))];
}
//#endregion
//#region src/infra/git-commit.ts
const formatCommit = (value) => {
const match = value?.match(/[0-9a-fA-F]{7,40}/);
return match ? normalizeLowercaseStringOrEmpty(match[0].slice(0, 7)) : null;
};
const cachedGitCommitBySearchDir = /* @__PURE__ */ new Map();
const GIT_COMMIT_CACHE_LIMIT = 256;
const resolveCommitSearchDir = (options) => {
if (options.cwd) return path.resolve(options.cwd);
if (options.moduleUrl) try {
return path.dirname(fileURLToPath(options.moduleUrl));
} catch {}
return process.cwd();
};
const cacheGitCommit = (searchDir, commit) => {
cachedGitCommitBySearchDir.set(searchDir, commit);
pruneMapToMaxSize(cachedGitCommitBySearchDir, GIT_COMMIT_CACHE_LIMIT);
return commit;
};
const resolveGitLookupDepth = (searchDir, packageRoot) => {
if (!packageRoot) return;
const relative = path.relative(packageRoot, searchDir);
if (relative.startsWith("..") || path.isAbsolute(relative)) return;
return (relative ? relative.split(path.sep).filter(Boolean).length : 0) + 1;
};
const readCommitFromGit = (searchDir, packageRoot) => {
const head = readGitHead(searchDir, { maxDepth: resolveGitLookupDepth(searchDir, packageRoot) });
return head === void 0 ? void 0 : formatCommit(head.value);
};
const readCommitFromPackageJson = () => {
try {
const pkg = createRequire(import.meta.url)("../../package.json");
return formatCommit(pkg.gitHead ?? pkg.githead ?? null);
} catch {
return null;
}
};
const readCommitProbe = (moduleUrl, candidates, field) => {
try {
for (const candidate of candidates) {
const filePath = fileURLToPath(new URL(candidate, moduleUrl));
let raw;
try {
raw = readGitMetadataPrefix(filePath, 1024);
} catch (error) {
if (isMissingPathError(error)) continue;
return null;
}
try {
const value = asNullableRecord(JSON.parse(raw))?.[field];
return typeof value === "string" ? formatCommit(value) : null;
} catch {
return null;
}
}
} catch {}
};
const readCommitFromBuildInfo = (moduleUrl = import.meta.url) => {
return readCommitProbe(moduleUrl, ["../build-info.json", "./build-info.json"], "commit") ?? null;
};
const readLoadedCommit = (moduleUrl) => {
const buildStamp = readCommitProbe(moduleUrl, ["../.buildstamp", "./.buildstamp"], "head");
const runtimeStamp = readCommitProbe(moduleUrl, ["../.runtime-postbuildstamp", "./.runtime-postbuildstamp"], "head");
if (buildStamp !== void 0 || runtimeStamp !== void 0) {
if (buildStamp || runtimeStamp) return buildStamp && buildStamp === runtimeStamp ? buildStamp : null;
return readCommitFromBuildInfo(moduleUrl);
}
return readCommitProbe(moduleUrl, ["../build-info.json", "./build-info.json"], "commit");
};
const resolveCommitHash = (options = {}) => {
const env = options.env ?? process.env;
const readers = options.readers ?? {};
const readGitCommit = readers.readGitCommit ?? readCommitFromGit;
const envCommit = env.GIT_COMMIT?.trim() || env.GIT_SHA?.trim();
const normalized = formatCommit(envCommit);
if (normalized) return normalized;
const searchDir = resolveCommitSearchDir(options);
if (cachedGitCommitBySearchDir.has(searchDir)) {
const cached = cachedGitCommitBySearchDir.get(searchDir) ?? null;
cachedGitCommitBySearchDir.delete(searchDir);
cachedGitCommitBySearchDir.set(searchDir, cached);
return cached;
}
const packageRoot = resolveOpenClawPackageRootSync({
cwd: options.cwd,
moduleUrl: options.moduleUrl
});
try {
const gitCommit = readGitCommit(searchDir, packageRoot);
if (gitCommit !== void 0) return cacheGitCommit(searchDir, gitCommit);
} catch {}
const buildInfoCommit = readers.readBuildInfoCommit?.() ?? readCommitFromBuildInfo();
if (buildInfoCommit) return cacheGitCommit(searchDir, buildInfoCommit);
const pkgCommit = readers.readPackageJsonCommit?.() ?? readCommitFromPackageJson();
if (pkgCommit) return cacheGitCommit(searchDir, pkgCommit);
try {
return cacheGitCommit(searchDir, readGitCommit(searchDir, packageRoot) ?? null);
} catch {
return cacheGitCommit(searchDir, null);
}
};
/** Resolve the commit that produced the loaded artifact, not the checkout's current revision. */
function resolveLoadedCommitHash(options = {}) {
const moduleUrl = options.moduleUrl ?? import.meta.url;
const loaded = readLoadedCommit(moduleUrl);
return loaded === void 0 ? resolveCommitHash({
moduleUrl,
...options.env ? { env: options.env } : {}
}) : loaded;
}
//#endregion
//#region src/version.ts
const CORE_PACKAGE_NAME = "openclaw";
const PACKAGE_JSON_CANDIDATES = [
"../package.json",
"../../package.json",
"../../../package.json",
"./package.json"
];
const BUILD_INFO_CANDIDATES = [
"../build-info.json",
"../../build-info.json",
"./build-info.json"
];
function readVersionFromJsonCandidates(moduleUrl, candidates, opts = {}) {
try {
const require = createRequire(moduleUrl);
for (const candidate of candidates) try {
const parsed = require(candidate);
const version = normalizeOptionalString(parsed.version);
if (!version) continue;
if (opts.requirePackageName && parsed.name !== CORE_PACKAGE_NAME) continue;
return version;
} catch {}
return null;
} catch {
return null;
}
}
function readBuildIdFromJsonCandidates(moduleUrl) {
try {
const require = createRequire(moduleUrl);
for (const candidate of BUILD_INFO_CANDIDATES) try {
const parsed = require(candidate);
const buildId = normalizeOptionalString(parsed.buildId);
if (buildId && buildId.length <= 96) return buildId;
} catch {}
return null;
} catch {
return null;
}
}
function firstNonEmpty(...values) {
for (const value of values) {
const trimmed = normalizeOptionalString(value);
if (trimmed && trimmed.toLowerCase() !== "undefined" && trimmed.toLowerCase() !== "null") return trimmed;
}
}
function readVersionFromPackageJsonForModuleUrl(moduleUrl) {
return readVersionFromJsonCandidates(moduleUrl, PACKAGE_JSON_CANDIDATES, { requirePackageName: true });
}
function readVersionFromBuildInfoForModuleUrl(moduleUrl) {
return readVersionFromJsonCandidates(moduleUrl, BUILD_INFO_CANDIDATES);
}
function readBuildIdFromBuildInfoForModuleUrl(moduleUrl) {
return readBuildIdFromJsonCandidates(moduleUrl);
}
function resolveVersionFromModuleUrl(moduleUrl) {
return readVersionFromBuildInfoForModuleUrl(moduleUrl) || readVersionFromPackageJsonForModuleUrl(moduleUrl);
}
function resolveBinaryVersion(params) {
return resolveVersionFromModuleUrl(params.moduleUrl) || firstNonEmpty(params.bundledVersion) || params.fallback || "0.0.0";
}
readBuildIdFromBuildInfoForModuleUrl(import.meta.url);
const RUNTIME_SERVICE_COMMIT = resolveLoadedCommitHash({ moduleUrl: import.meta.url });
function resolveRuntimeServiceCommit() {
return RUNTIME_SERVICE_COMMIT;
}
const VERSION = resolveBinaryVersion({
moduleUrl: import.meta.url,
bundledVersion: process.env.OPENCLAW_BUNDLED_VERSION
});
//#endregion
//#region src/infra/startup-maintenance-required.ts
const GATEWAY_STARTUP_MAINTENANCE_REQUIRED_REASON = "gateway.maintenance_required";
const maintenanceReasons = {
"state-migrations": "state migration",
"newer-schema": "a newer OpenClaw build",
"agent-media": "offline media migration",
"agent-databases-composite-primary-key": "state database schema migration",
"audit-events-v2": "state database schema migration",
"legacy-workshop-review-index": "state database schema migration",
"legacy-cron-run-logs": "cron run history migration",
"legacy-workspace": "workspace setup state migration",
"legacy-session-store": "session store migration"
};
function isStartupMaintenanceKind(value) {
return typeof value === "string" && Object.hasOwn(maintenanceReasons, value);
}
var StartupMaintenanceRequiredError = class extends Error {
constructor(kind, message, options) {
super(message, options);
this.kind = kind;
this.code = GATEWAY_STARTUP_MAINTENANCE_REQUIRED_REASON;
this.name = "StartupMaintenanceRequiredError";
}
get reason() {
return maintenanceReasons[this.kind];
}
};
//#endregion
//#region src/infra/sqlite-user-version.ts
const SQLITE_SCHEMA_VERSION_ERROR_NAME = "SqliteSchemaVersionError";
var SqliteSchemaVersionError = class extends StartupMaintenanceRequiredError {
constructor(message) {
super("newer-schema", message);
this.name = SQLITE_SCHEMA_VERSION_ERROR_NAME;
}
};
function isSqliteSchemaVersionError(error) {
return error instanceof SqliteSchemaVersionError || error instanceof Error && error.name === SQLITE_SCHEMA_VERSION_ERROR_NAME;
}
function readSqliteUserVersion(db) {
const row = executeWithCachedStatement(db, "PRAGMA user_version", [], (statement) => statement.get());
return Number(row?.user_version ?? 0);
}
/**
* Name the refusing build from immutable loaded metadata, plus its install root.
* The path remains actionable when multiple installs share a version or build.
*/
function describeRunningOpenClawBuild() {
const commit = resolveRuntimeServiceCommit();
const root = resolveOpenClawPackageRootSync({ moduleUrl: import.meta.url });
const identity = commit ? `OpenClaw ${VERSION} (${commit})` : `OpenClaw ${VERSION}`;
return root ? `${identity} installed at ${root}` : identity;
}
function createNewerSqliteSchemaVersionError(databaseLabel, pathname, schemaVersion, supportedVersion) {
return new SqliteSchemaVersionError(`This OpenClaw build cannot open your existing data.
${databaseLabel} ${pathname} uses newer schema version ${schemaVersion}; this build supports ${supportedVersion}.\nRefused by ${describeRunningOpenClawBuild()}.\nUse a build that supports schema ${schemaVersion} or newer with this state directory. To use an older build, restore your pre-update backup created with openclaw backup create.\nSee ${OPENCLAW_DATABASE_SCHEMA_DOCS_URL}.`);
}
var OpenClawStateDatabaseSchemaMigrationRequiredError = class extends StartupMaintenanceRequiredError {
constructor(kind, pathname) {
super(kind, `OpenClaw state database schema migration required (${kind}) at ${pathname}; run openclaw doctor --fix to migrate it.`);
this.kind = kind;
this.pathname = pathname;
this.name = "OpenClawStateDatabaseSchemaMigrationRequiredError";
}
};
/** Runtime readers report malformed legacy state without entering repair mode. */
function normalizeOpenClawStateSchemaReadError(error, pathname) {
if (error instanceof Error && error.message.startsWith(`malformed database schema (idx_skill_workshop_collection_reviews_workspace_time)`)) {
const required = new OpenClawStateDatabaseSchemaMigrationRequiredError("legacy-workshop-review-index", pathname);
required.cause = error;
return required;
}
return error;
}
//#endregion
//#region src/infra/sqlite-post-commit.ts
const pendingPublications = resolveGlobalSingleton(Symbol.for("openclaw.sqlitePostCommitPublications"), () => /* @__PURE__ */ new WeakMap());
const pendingTransactionState = resolveGlobalSingleton(Symbol.for("openclaw.sqliteTransactionState"), () => /* @__PURE__ */ new WeakMap());
/** Publications are non-throwing observers, never part of a durable transaction's result. */
function deferSqlitePostCommitPublication(db, publish) {
const pending = pendingPublications.get(db);
if (!pending) return false;
pending.push(publish);
return true;
}
/** A lost transaction invalidates every savepoint's staged state and observers. */
function discardSqliteTransactionState(db, error) {
pendingPublications.get(db)?.splice(0);
const rolledBackState = pendingTransactionState.get(db)?.splice(0) ?? [];
pendingPublications.delete(db);
pendingTransactionState.delete(db);
for (const state of rolledBackState.toReversed()) state.rollback(error);
}
/** Nested rollback restores staged state and discards observers; savepoints wait for outer commit. */
function withSqlitePostCommitPublications(db, transaction) {
const nested = db.isTransaction;
const publications = nested ? pendingPublications.get(db) : [];
const transactionState = nested ? pendingTransactionState.get(db) : [];
const publicationStart = publications?.length ?? 0;
const stateStart = transactionState?.length ?? 0;
if (!nested && publications && transactionState) {
pendingPublications.set(db, publications);
pendingTransactionState.set(db, transactionState);
}
let result;
try {
result = transaction();
} catch (error) {
publications?.splice(publicationStart);
const rolledBackState = transactionState?.splice(stateStart) ?? [];
for (const state of rolledBackState.toReversed()) state.rollback(error);
throw error;
} finally {
if (!nested) {
pendingPublications.delete(db);
pendingTransactionState.delete(db);
}
}
if (!nested) {
for (const state of transactionState ?? []) state.commit();
for (const state of transactionState ?? []) state.prepareObservers?.();
for (const publish of publications ?? []) publish();
}
return result;
}
//#endregion
//#region src/infra/sqlite-worker-contract.ts
const SQLITE_WORKER_MAX_MESSAGE_BYTES = 33554432;
const retainedWorkerErrorCode = Symbol.for("openclaw.sqliteWorkerErrorCode");
const SqliteWorkerAdmissionTimeoutError = resolveGlobalSingleton(Symbol.for("openclaw.sqliteWorkerAdmissionTimeoutError"), () => class AdmissionTimeoutError extends Error {
constructor() {
super("SQLite host admission timed out; retry after transaction rollback");
this.code = "admission-timeout";
this.name = "SqliteWorkerAdmissionTimeoutError";
}
});
var SqliteWorkerError = class extends Error {
constructor(message, code) {
super(message);
this.code = code;
this.name = "SqliteWorkerError";
Object.defineProperty(this, retainedWorkerErrorCode, { value: code });
}
};
/** Carry only canonical worker classification through a local cleanup aggregate. */
function retainSqliteWorkerErrorCode(error, source) {
let code;
try {
code = Object.getOwnPropertyDescriptor(source, retainedWorkerErrorCode)?.value;
} catch {
return error;
}
if (code === "closed" || code === "overloaded" || code === "unavailable" || code === "outcome-unknown") {
Object.defineProperty(error, retainedWorkerErrorCode, { value: code });
Object.assign(error, { code });
}
return error;
}
//#endregion
//#region src/infra/sqlite-worker-operation-admission.ts
const REQUESTED = 0;
const GRANTED = 1;
const REFUSED = 2;
const TIMED_OUT = 3;
resolveGlobalSingleton(Symbol.for("openclaw.sqliteWorkerOpenRefusedError"), () => class OpenRefusedError extends Error {
constructor(originalError) {
super("SQLite worker admission was refused before agent open", { cause: originalError });
this.originalError = originalError;
this.name = "SqliteWorkerOpenRefusedError";
}
});
/** The caller retains real source custody before invoking the synchronous grant. */
function createSqliteWorkerOperationAdmission(admit, attachment) {
const { port1, port2 } = new MessageChannel();
if (attachment !== void 0) try {
port1.postMessage({
kind: "sqlite-operation-attachment",
value: attachment
}, []);
} catch (error) {
port1.close();
port2.close();
throw error;
}
const inOwnerContext = AsyncLocalStorage.snapshot();
const decisions = /* @__PURE__ */ new Set();
const cleanupFailures = [];
let closed = false;
let failure;
let committed;
let settlement;
let databaseAuthority;
const waiting = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
const recordFailure = (error, source) => {
if (!failure || failure.source === "domain" && source !== "domain") failure = {
error,
source
};
};
const refuse = (decision, error, source) => {
if (Atomics.compareExchange(decision, 0, REQUESTED, REFUSED) === REQUESTED) {
recordFailure(error, source);
Atomics.notify(decision, 0);
} else if (Atomics.load(decision, 0) === GRANTED) cleanupFailures.push(error);
};
const receive = (message) => {
if (isRecord(message) && message.kind === "native-commit") {
if (!isRecord(message.committed) || settlement) {
recordFailure(new SqliteWorkerError("SQLite worker commit receipt is invalid", "outcome-unknown"), "protocol");
return;
}
committed = { facts: message.committed.facts };
return;
}
if (isRecord(message) && message.kind === "native-settlement") {
const value = message.settlement;
if (!isRecord(value) || value.kind !== "completed" && value.kind !== "unknown" || value.committed !== void 0 && !isRecord(value.committed) || settlement) {
recordFailure(new SqliteWorkerError("SQLite worker native settlement is invalid", "outcome-unknown"), "protocol");
return;
}
if (isRecord(value.committed)) committed = { facts: value.committed.facts };
settlement = {
kind: value.kind,
...committed ? { committed } : {}
};
return;
}
if (!isRecord(message) || !(message.decision instanceof SharedArrayBuffer) || message.decision.byteLength !== Int32Array.BYTES_PER_ELEMENT || message.stage !== "open" && message.stage !== "prepare" && message.stage !== "transaction" && message.stage !== "commit") {
recordFailure(new SqliteWorkerError("SQLite worker admission request is invalid", "unavailable"), "protocol");
return;
}
const decision = new Int32Array(message.decision);
const expired = () => {
if (typeof message.deadlineNs === "bigint" && process.hrtime.bigint() >= message.deadlineNs) {
if (Atomics.compareExchange(decision, 0, REQUESTED, TIMED_OUT) === REQUESTED) Atomics.notify(decision, 0);
}
if (Atomics.load(decision, 0) !== TIMED_OUT) return false;
if (Array.isArray(message.ports)) {
for (const port of message.ports) if (port instanceof MessagePort) port.close();
}
return true;
};
if (expired()) return;
decisions.add(decision);
if (closed) {
refuse(decision, new SqliteWorkerError("SQLite worker admission is closed", "closed"), "authority");
return;
}
const request = {
stage: message.stage,
facts: message.facts
};
const grant = () => {
if (closed || expired() || Atomics.load(decision, 0) !== REQUESTED) return false;
try {
inOwnerContext(() => databaseAuthority?.assertAccess());
} catch (error) {
refuse(decision, error, "authority");
return false;
}
if (closed || expired() || Atomics.load(decision, 0) !== REQUESTED) return false;
const granted = Atomics.compareExchange(decision, 0, REQUESTED, GRANTED) === REQUESTED;
if (granted) Atomics.notify(decision, 0);
return granted;
};
let source = "authority";
try {
inOwnerContext(() => {
databaseAuthority?.assertRequest?.();
databaseAuthority?.assertAccess();
});
if (request.stage === "prepare" && isRecord(request.facts) && request.facts.kind === "schema-maintenance") {
const authority = databaseAuthority;
if (!authority || typeof request.facts.databasePath !== "string" || resolveIdentityPathViaExistingAncestorSync(request.facts.databasePath) !== authority.databasePath) throw new SqliteWorkerError("SQLite schema maintenance target differs from its admitted database", "closed");
inOwnerContext(() => {
authority.lease ??= authority.acquireSchema();
authority.lease.assertCurrent();
grant();
});
} else {
source = "domain";
inOwnerContext(admit, request, grant);
}
} catch (error) {
refuse(decision, error, source);
return;
} finally {
decisions.delete(decision);
}
if (Atomics.load(decision, 0) === REQUESTED) refuse(decision, new SqliteWorkerError("SQLite worker admission was not granted", "closed"), "domain");
};
port1.on("message", receive);
port1.unref();
const service = () => {
for (let queued = receiveMessageOnPort(port1); queued; queued = receiveMessageOnPort(port1)) receive(queued.message);
};
return {
port: port2,
bindDatabaseAuthority(authority) {
if (closed || databaseAuthority) throw new SqliteWorkerError("SQLite database authority is already bound or closed", "closed");
databaseAuthority = {
...authority,
databasePath: resolveIdentityPathViaExistingAncestorSync(authority.databasePath)
};
},
get failure() {
return failure?.error;
},
get failureSource() {
return failure?.source;
},
get cleanupFailures() {
return cleanupFailures;
},
get committed() {
service();
return committed;
},
get settlement() {
return settlement;
},
waitForSettlement(deadlineMs) {
while (true) {
service();
if (failure !== void 0) throw toErrorObject(failure.error, "SQLite worker admission failed");
if (settlement?.kind === "completed") return settlement;
const remaining = deadlineMs - performance.now();
if (settlement?.kind === "unknown" || closed || remaining <= 0) throw new SqliteWorkerError("SQLite worker native settlement is unknown", "outcome-unknown");
Atomics.wait(waiting, 0, 0, Math.min(5, remaining));
}
},
service,
finish() {
closed = true;
service();
for (const decision of decisions) if (Atomics.load(decision, 0) === REQUESTED) refuse(decision, new SqliteWorkerError("SQLite worker admission is closed", "closed"), "authority");
port1.close();
port2.close();
if (databaseAuthority?.lease) try {
databaseAuthority.lease.release();
databaseAuthority.lease = void 0;
} catch (error) {
cleanupFailures.push(error);
}
}
};
}
const currentAdmission = resolveGlobalSingleton(Symbol.for("openclaw.sqliteWorkerOperationAdmission"), () => new AsyncLocalStorage());
/** Called on the SQLite worker, after transaction entry and before its row mutation. */
function requestSqliteWorkerOperationAdmission(request, transferList = []) {
const scope = currentAdmission.getStore();
if (!scope?.active) throw new SqliteWorkerError("SQLite operation requires its retained admission", "unavailable");
const decision = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
const deadlineNs = request.deadlineMs === void 0 ? void 0 : process.hrtime.bigint() + BigInt(request.deadlineMs) * 1000000n;
scope.port.postMessage({
...request,
decision: decision.buffer,
deadlineNs,
ports: transferList.filter((value) => value instanceof MessagePort)
}, transferList);
while (Atomics.load(decision, 0) === REQUESTED) {
const remainingMs = deadlineNs === void 0 ? void 0 : Number(deadlineNs - process.hrtime.bigint()) / 1e6;
if (remainingMs !== void 0 && remainingMs <= 0) Atomics.compareExchange(decision, 0, REQUESTED, TIMED_OUT);
else Atomics.wait(decision, 0, REQUESTED, remainingMs);
}
if (Atomics.load(decision, 0) !== GRANTED) {
const refusal = Atomics.load(decision, 0) === TIMED_OUT ? new SqliteWorkerAdmissionTimeoutError() : new SqliteWorkerError("SQLite transaction admission was refused", "closed");
scope.owner.refusal = refusal;
throw refusal;
}
}
/** Schema work borrows live host authority through the same retained job port. */
function requestSqliteWorkerSchemaMaintenance(databasePath) {
if (!currentAdmission.getStore()) return false;
requestSqliteWorkerOperationAdmission({
stage: "prepare",
facts: {
kind: "schema-maintenance",
databasePath
}
});
return true;
}
//#endregion
//#region src/state/openclaw-state-db-schema-policy.ts
const schemaPolicies = resolveGlobalSingleton(Symbol.for("openclaw.stateDatabaseSchemaPolicies"), () => ({
scopes: new AsyncLocalStorage(),
existingDatabases: /* @__PURE__ */ new WeakMap()
}));
function canonicalPath(pathname) {
const resolved = path.resolve(pathname);
try {
return realpathSync.native(resolved);
} catch (error) {
if (hasErrnoCode(error, "ENOENT")) return resolved;
throw error;
}
}
function assertSchemaScopeActive(scope) {
if (scope && !scope.active) throw new Error("Existing shared-state schema admission has ended.");
}
function getExistingOpenClawStateSchemaPath() {
const scope = schemaPolicies.scopes.getStore();
assertSchemaScopeActive(scope);
return scope?.path;
}
/** Admit this source once; retained reads only need the original scope's live lifetime. */
function captureOpenClawStateSchemaReadAdmission(pathname) {
const scope = schemaPolicies.scopes.getStore();
if (!scope) return;
isExistingOpenClawStateSchema(pathname);
return {
path: scope.path,
assertCurrent: () => assertSchemaScopeActive(scope)
};
}
/** Check supplied and cached handles before exposing them to another admission policy. */
function isExistingOpenClawStateSchema(pathname, database) {
const scopedPath = getExistingOpenClawStateSchemaPath();
const scope = schemaPolicies.scopes.getStore();
const admittedPath = database && schemaPolicies.existingDatabases.get(database);
const resolvedPath = path.resolve(pathname);
const existing = scopedPath !== void 0 && (scopedPath === resolvedPath || scope?.canonicalPath === resolvedPath || scope?.canonicalPath === canonicalPath(resolvedPath));
if (scopedPath && !existing) throw new Error(`Existing shared-state schema admission is bound to ${scopedPath}, not ${pathname}.`);
if (admittedPath && (!existing || admittedPath !== scope?.canonicalPath)) throw new Error(`Shared-state database ${pathname} was admitted without schema repair; close its existing handle before ordinary admission.`);
return existing;
}
function recordExistingOpenClawStateSchemaDatabase(database, pathname) {
const scope = schemaPolicies.scopes.getStore();
if (!scope || !isExistingOpenClawStateSchema(pathname, database)) throw new Error("Existing shared-state schema admission requires its active path scope.");
schemaPolicies.existingDatabases.set(database, scope.canonicalPath);
}
/** The cache owns the handle inventory, including owners retained after failed cleanup. */
function assertExistingOpenClawStateSchemaCacheAdmission(pathname, cache) {
const scopedPath = getExistingOpenClawStateSchemaPath();
const scope = schemaPolicies.scopes.getStore();
const requested = path.resolve(pathname);
let resolved = scope && (scopedPath === requested || scope.canonicalPath === requested) ? scope.canonicalPath : void 0;
for (const databases of [cache.cachedDatabases, cache.retainedDatabaseHandles]) for (const { db } of databases.values()) {
const admittedPath = schemaPolicies.existingDatabases.get(db);
if (admittedPath && db.isOpen) {
resolved ??= canonicalPath(pathname);
if (admittedPath === resolved) isExistingOpenClawStateSchema(pathname, db);
}
}
}
//#endregion
//#region src/infra/sqlite-transaction.ts
const DEFAULT_SLOW_BUSY_WAIT_MS = 1e3;
const DEFAULT_SLOW_TRANSACTION_HOLD_MS = 1e3;
const abortedTransactionSymbol = Symbol.for("openclaw.sqliteAbortedTransaction");
function assertTransactionUsable(db) {
const aborted = db[abortedTransactionSymbol];
if (aborted) throw aborted.error;
}
const transactionLog = createSubsystemLogger("sqlite/transaction");
const writeAdmissionServices = resolveGlobalSingleton(Symbol.for("openclaw.sqliteWriteAdmissionServices"), () => /* @__PURE__ */ new Map());
const writeAdmissionLocations = /* @__PURE__ */ new WeakMap();
function writeAdmissionLocation(database) {
const cached = writeAdmissionLocations.get(database);
if (cached !== void 0) return cached;
const location = database.location();
const canonical = location === null ? null : normalizeDatabasePath(location);
writeAdmissionLocations.set(database, canonical);
return canonical;
}
/** Locations come from the retained native owner; registration grants no write authority. */
function retainSqliteWriteAdmissionService(nativeLocations, service) {
const registrations = [...new Set(nativeLocations.map(normalizeDatabasePath))].map((location) => {
const services = writeAdmissionServices.get(location) ?? /* @__PURE__ */ new Set();
const retained = () => service();
services.add(retained);
writeAdmissionServices.set(location, services);
return {
location,
services,
retained
};
});
return () => {
for (const { location, services, retained } of registrations) {
services.delete(retained);
if (services.size === 0 && writeAdmissionServices.get(location) === services) writeAdmissionServices.delete(location);
}
};
}
function execNativeBegin(db, diagnostics) {
const startedAt = Date.now();
diagnostics.nativeAttempts += 1;
try {
db.exec("BEGIN IMMEDIATE");
} finally {
diagnostics.nativeMs += Date.now() - startedAt;
}
}
function beginImmediateTransaction(db, diagnostics) {
const location = writeAdmissionServices.size > 0 ? writeAdmissionLocation(db) : null;
const services = location === null ? void 0 : writeAdmissionServices.get(location);
if (!services) {
execNativeBegin(db, diagnostics);
return;
}
const deadline = performance.now() + readSqliteBusyTimeout(db);
while (true) try {
runWithSqliteBusyTimeout(db, Math.min(25, Math.max(0, Math.ceil(deadline - performance.now()))), () => execNativeBegin(db, diagnostics));
return;
} catch (error) {
if (!isSqliteLockError(error) || performance.now() >= deadline) throw error;
for (const service of services) {
const startedAt = Date.now();
diagnostics.serviceCalls += 1;
try {
service();
} finally {
diagnostics.serviceMs += Date.now() - startedAt;
}
}
if (performance.now() >= deadline) throw error;
}
}
function assertSyncTransactionResult(value) {
if (isPromiseLike(value)) throw new Error("SQLite write transactions must be synchronous; Promise returns are not supported.");
}
function slowBusyWaitThresholdMs(options) {
if (options?.busyTimeoutMs === void 0 || options.busyTimeoutMs <= 0) return DEFAULT_SLOW_BUSY_WAIT_MS;
return Math.min(DEFAULT_SLOW_BUSY_WAIT_MS, options.busyTimeoutMs);
}
function slowTransactionHoldThresholdMs(options) {
return options?.slowTransactionHoldMs ?? DEFAULT_SLOW_TRANSACTION_HOLD_MS;
}
function transactionLogger(options) {
return options?.logger ?? transactionLog;
}
function transactionDiagnosticLabels(db, options) {
let database = options?.databaseLabel;
if (!database) try {
database = db ? db.location() ?? ":memory:" : "unavailable";
} catch {
database = "unavailable";
}
return {
database,
operation: options?.operationLabel || captureSqliteReaderOwner()?.operation || "unlabeled"
};
}
function logSlowTransactionHold(params) {
if (params.elapsedMs < slowTransactionHoldThresholdMs(params.options)) return;
transactionLogger(params.options).warn("slow SQLite transaction hold", {
async: false,
...transactionDiagnosticLabels(params.db, params.options),
elapsedMs: params.elapsedMs,
isMainThread,
mode: params.mode,
pid: process.pid,
threadId,
thresholdMs: slowTransactionHoldThresholdMs(params.options)
});
}
function logSlowTransactionStep(params) {
if (params.elapsedMs < slowBusyWaitThresholdMs(params.options)) return;
transactionLogger(params.options).warn("slow SQLite transaction step", {
async: false,
...params.options?.busyTimeoutMs !== void 0 ? { busyTimeoutMs: params.options.busyTimeoutMs } : {},
...transactionDiagnosticLabels(params.db, params.options),
elapsedMs: params.elapsedMs,
isMainThread,
pid: process.pid,
step: params.step,
threadId,
...params.beginAdmission ? { beginAdmission: { ...params.beginAdmission } } : {}
});
}
function execTimedTransactionStep(params) {
const startedAt = Date.now();
const beginAdmission = params.sql === "BEGIN IMMEDIATE" ? {
nativeAttempts: 0,
nativeMs: 0,
serviceCalls: 0,
serviceMs: 0
} : void 0;
try {
if (beginAdmission) beginImmediateTransaction(params.db, beginAdmission);
else params.db.exec(params.sql);
const elapsedMs = Date.now() - startedAt;
logSlowTransactionStep({
beginAdmission,
db: params.db,
elapsedMs,
options: params.options,
step: params.step
});
return elapsedMs;
} catch (error) {
const elapsedMs = Date.now() - startedAt;
if (isSqliteLockError(error) && shouldReportSqliteLockFailure(params.db)) {
const sqliteErrcode = sqliteExtendedResultCode(error);
const sqlitePrimaryCode = sqlitePrimaryResultCode(error);
transactionLogger(params.options).warn("SQLite transaction lock wait failed", {
async: false,
...params.options?.busyTimeoutMs !== void 0 ? { busyTimeoutMs: params.options.busyTimeoutMs } : {},
...transactionDiagnosticLabels(params.db, params.options),
code: sqliteErrorCode(error),
elapsedMs,
failureKind: "lock-contention",
isMainThread,
pid: process.pid,
...sqliteErrcode !== void 0 ? { sqliteErrcode } : {},
...sqlitePrimaryCode !== void 0 ? { sqlitePrimaryCode } : {},
step: params.step,
threadId,
...beginAdmission ? { beginAdmission: { ...beginAdmission } } : {}
});
}
throw error;
}
}
function beginTransaction(db, options, mode) {
execTimedTransactionStep({
db,
options,
sql: mode === "immediate" ? "BEGIN IMMEDIATE" : "BEGIN",
step: "begin"
});
}
function commitImmediateTransaction(db, options) {
execTimedTransactionStep({
db,
options,
sql: "COMMIT",
step: "commit"
});
}
function discardUnsafeConnection(db, error) {
db[abortedTransactionSymbol] ??= { error };
discardSqliteTransactionState(db, error);
clearNodeSqliteKyselyCacheForDatabase(db);
try {
db.close();
} catch {}
}
function abortImmediateTransaction(db, error, commitStarted) {
if (db[abortedTransactionSymbol]) return;
try {
if (!commitStarted && db.isOpen && !db.isTransaction) {
discardSqliteTransactionState(db, error);
return;
}
db.exec("ROLLBACK");
} catch {
discardUnsafeConnection(db, error);
}
}
function runSqliteTransactionSync(db, operation, mode, options) {
assertTransactionUsable(db);
if (db.isTransaction) {
db.exec("SAVEPOINT openclaw_tx_nested");
try {
const result = operation();
assertSyncTransactionResult(result);
assertTransactionUsable(db);
db.exec("RELEASE SAVEPOINT openclaw_tx_nested");
return result;
} catch (error) {
const failure = db[abortedTransactionSymbol];
if (failure) throw failure.error;
try {
db.exec("ROLLBACK TO SAVEPOINT openclaw_tx_nested");
db.exec("RELEASE SAVEPOINT openclaw_tx_nested");
} catch {
discardUnsafeConnection(db, error);
}
throw error;
}
}
beginTransaction(db, options, mode);
const transactionStartedAt = Date.now();
let commitStarted = false;
try {
const result = operation();
assertSyncTransactionResult(result);
assertTransactionUsable(db);
commitStarted = true;
if (options?.withCommit) assertSyncTransactionResult(options.withCommit(() => commitImmediateTransaction(db, options)));
else commitImmediateTransaction(db, options);
return result;
} catch (error) {
abortImmediateTransaction(db, error, commitStarted);
assertTransactionUsable(db);
throw error;
} finally {
try {
logSlowTransactionHold({
db,
elapsedMs: Date.now() - transactionStartedAt,
mode,
options
});
} catch {}
}
}
/** Run synchronous reads against one deferred SQLite snapshot. */
function runSqliteDeferredTransactionSync(db, operation, options) {
return runSqliteTransactionSync(db, operation, "deferred", options);
}
function runSqliteImmediateTransactionSync(db, operation, options) {
return runSqliteTransactionSync(db, operation, "immediate", options);
}
//#endregion
//#region src/infra/file-descriptor.ts
/** Strict field equality; callers own any platform-specific identity tolerance. */
function sameFileMutationFingerprint(left, right) {
return left.birthtimeNs === right.birthtimeNs && left.ctimeNs === right.ctimeNs && left.dev === right.dev && left.ino === right.ino && left.mtimeNs === right.mtimeNs && left.size === right.size;
}
/** Maps the borrowed-descriptor digest to OpenClaw's persisted artifact fields. */
function hashFileDescriptorSync(fd, maxBytes) {
const { digest, bytes } = sha256FileSync(fd, { maxBytes });
return {
sha256: digest,
sizeBytes: bytes
};
}
//#endregion
//#region src/infra/sqlite-file-generation.ts
function assertRegularFile(stat) {
if (!stat.isFile()) throw new Error("SQLite generation target must be a regular file");
}
function sameFileState(left, right) {
return sameFileIdentity(left, right) && left.birthtimeNs === right.birthtimeNs && left.ctimeNs === right.ctimeNs && left.mtimeNs === right.mtimeNs && left.size === right.size;
}
function fingerprintFile(pathname) {
const fd = fs$1.openSync(pathname, "r");
try {
const before = fs$1.fstatSync(fd, { bigint: true });
assertRegularFile(before);
const { sha256 } = hashFileDescriptorSync(fd);
const after = fs$1.fstatSync(fd, { bigint: true });
const current = fs$1.statSync(pathname, { bigint: true });
if (!sameFileState(before, after) || !sameFileState(after, current)) throw new Error(`SQLite generation target changed while hashing: ${pathname}`);
return {
birthtimeNs: after.birthtimeNs,
ctimeNs: after.ctimeNs,
dev: after.dev,
ino: after.ino,
mtimeNs: after.mtimeNs,
sha256,
size: after.size
};
} finally {
fs$1.closeSync(fd);
}
}
function readOptionalFile(pathname) {
try {
return fingerprintFile(pathname);
} catch (error) {
if (error.code === "ENOENT") return;
throw error;
}
}
function readGeneration(pathname) {
const database = fingerprintFile(pathname);
const journal = readOptionalFile(`${pathname}-journal`);
const wal = readOptionalFile(`${pathname}-wal`);
return {
database,
...journal ? { journal } : {},
...wal ? { wal } : {}
};
}
function readStableSqliteFileGeneration(pathname) {
const first = readGeneration(pathname);
const second = readGeneration(pathname);
if (!sameSqliteFileGeneration(first, second)) throw new Error(`SQLite file generation changed while reading: ${pathname}`);
return second;
}
function sameFileFingerprint(left, right) {
return sameFileMutationFingerprint(left, right) && left.sha256 === right.sha256;
}
function sameOptionalFileFingerprint(left, right) {
return left === void 0 ? right === void 0 : right !== void 0 && sameFileFingerprint(left, right);
}
function sameSqliteFileGeneration(left, right) {
return sameFileFingerprint(left.database, right.database) && sameOptionalFileFingerprint(left.journal, right.journal) && sameOptionalFileFingerprint(left.wal, right.wal);
}
function parseFileFingerprint(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("SQLite file fingerprint must be an object");
const fingerprint = value;
for (const field of [
"birthtimeNs",
"ctimeNs",
"dev",
"ino",
"mtimeNs",
"size"
]) if (typeof fingerprint[field] !== "string" || !/^-?\d+$/u.test(fingerprint[field])) throw new Error(`SQLite file fingerprint ${field} is invalid`);
if (typeof fingerprint.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(fingerprint.sha256)) throw new Error("SQLite file fingerprint sha256 is invalid");
return {
birthtimeNs: BigInt(fingerprint.birthtimeNs),
ctimeNs: BigInt(fingerprint.ctimeNs),
dev: BigInt(fingerprint.dev),
ino: BigInt(fingerprint.ino),
mtimeNs: BigInt(fingerprint.mtimeNs),
sha256: fingerprint.sha256,
size: BigInt(fingerprint.size)
};
}
function parseSqliteFileGeneration(serialized) {
const value = JSON.parse(serialized);
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("SQLite file generation must be an object");
const generation = value;
return {
database: parseFileFingerprint(generation.database),
...generation.journal === void 0 ? {} : { journal: parseFileFingerprint(generation.journal) },
...generation.wal === void 0 ? {} : { wal: parseFileFingerprint(generation.wal) }
};
}
//#endregion
//#region src/infra/sqlite-integrity.ts
/** The gate includes the driver's check, awaited lifetime, and admission revalidation. */
function* sqliteIntegrityCheckSteps(database, databaseLabel, diagnostics, tables) {
const startedAt = performance$1.now();
const check = {
database,
databaseLabel,
tables
};
if (diagnostics) {
check.timing = {};
diagnostics.integrityGateMode = tables ? "tables" : "full";
delete diagnostics.integrityTableTimings;
delete diagnostics.integrityTableTotals;
delete diagnostics.integrityCheckSyncMs;
delete diagnostics.integrityOutsideCheckMs;
delete diagnostics.integrityWorkerCheckMs;
delete diagnostics.integrityWorkerLifetimeMs;
delete diagnostics.integrityOutsideWorkerMs;
}
try {
yield check;
if (diagnostics) diagnostics.integrityGateOutcome = "healthy";
} catch (error) {
if (diagnostics) diagnostics.integrityGateOutcome = "failed";
throw error;
} finally {
if (diagnostics) {
diagnostics.integrityGateMs = Math.floor(performance$1.now() - startedAt);
if (check.timing?.tables) {
diagnostics.integrityTableTotals = {};
for (const table of check.timing.tables) {
const total = diagnostics.integrityTableTotals[table.check] ??= {
tableCount: 0,
elapsedMs: 0
};
total.tableCount += 1;
total.elapsedMs += table.elapsedMs;
}
diagnostics.integrityTableTimings = check.timing.tables.toSorted((left, right) => right.elapsedMs - left.elapsedMs).slice(0, 10);
}
if (check.timing?.syncElapsedMs !== void 0) {
diagnostics.integrityCheckSyncMs = Math.floor(check.timing.syncElapsedMs);
diagnostics.integrityOutsideCheckMs = diagnostics.integrityGateMs - diagnostics.integrityCheckSyncMs;
}
if (check.timing?.workerCheckElapsedMs !== void 0) diagnostics.integrityWorkerCheckMs = Math.floor(check.timing.workerCheckElapsedMs);
if (check.timing?.workerLifetimeElapsedMs !== void 0) {
diagnostics.integrityWorkerLifetimeMs = Math.floor(check.timing.workerLifetimeElapsedMs);
diagnostics.integrityOutsideWorkerMs = diagnostics.integrityGateMs - diagnostics.integrityWorkerLifetimeMs;
}
}
}
}
/** Measure only the calling driver's synchronous check, excluding admission and resumption. */
function runSqliteIntegrityCheckSync(check) {
const timing = check.timing;
const startedAt = timing ? performance$1.now() : 0;
try {
assertSqliteIntegrity(check.database, check.databaseLabel, "integrity_check", check.tables, timing);
} finally {
if (timing) timing.syncElapsedMs = performance$1.now() - startedAt;
}
}
/** Run the same admission steps synchronously when the caller cannot yield. */
function runSqliteIntegrityOperationSync(operation) {
let step = operation.next();
while (!step.done) {
try {
runSqliteIntegrityCheckSync(step.value);
} catch (error) {
step = operation.throw(error);
continue;
}
step = operation.next();
}
return step.value;
}
const MAX_REPORTED_FOREIGN_KEY_VIOLATIONS = 5;
var SqliteRepairableForeignKeyError = class extends Error {
constructor(databaseLabel, orphanCount) {
super(`SQLite foreign_key_check failed for ${databaseLabel}: repairable task_delivery_state.task_id references task_runs.task_id cascade-owned orphans (${orphanCount} rows). Run openclaw doctor --fix to migrate this legacy state before retrying.`);
this.name = "SqliteRepairableForeignKeyError";
this.repair = {
kind: "task-delivery-orphans",
relation: "task_delivery_state.task_id",
parentTable: "task_runs",
orphanCount
};
}
};
/** Return whether a named integrity failure proves persistent database damage. */
function isTerminalSqliteIntegrityError(error) {
if (error.name !== "SqliteIntegrityError") return false;
if (!error.cause) return true;
return isSqliteCorruptionError(error.cause);
}
/** Require structural, table/index, and referential consistency before trusting a database. */
function assertSqliteIntegrity(database, databaseLabel, check = "integrity_check", tables, timing) {
if (tables) {
if (timing) timing.tables = [];
for (const entry of tables) {
const startedAt = performance$1.now();
try {
runSqliteCheck(database, databaseLabel, entry.check, entry.table);
runSqliteForeignKeyCheck(database, databaseLabel, entry.table);
} finally {
timing?.tables?.push({
...entry,
elapsedMs: Math.floor(performance$1.now() - startedAt)
});
}
}
return { integrityCheck: "ok" };
}
const integrityCheck = runSqliteCheck(database, databaseLabel, check);
runSqliteForeignKeyCheck(database, databaseLabel);
return { integrityCheck };
}
/** Require table and associated index consistency before trusting indexed reads. */
function assertSqliteTableIntegrity(database, databaseLabel, tableName) {
runSqliteCheck(database, `${databaseLabel} table ${tableName}`, "integrity_check", tableName);
}
function runSqliteCheck(database, databaseLabel, pragma, tableName) {
const argument = tableName ? `('${tableName.replaceAll("'", "''")}')` : "";
let rows;
try {
rows = database.prepare(`PRAGMA ${pragma}${argument};`).all();
} catch (error) {
throw createSqliteIntegrityError(`SQLite ${pragma} failed for ${databaseLabel}: ${error instanceof Error ? error.message : String(error)}`, error);
}
const results = rows.map((row) => row[pragma] ?? Object.values(row)[0]);
if (results.length === 1 && results[0] === "ok") return "ok";
throw createSqliteIntegrityError(`SQLite ${pragma} failed for ${databaseLabel}: ${results.map((result) => String(result)).join("; ") || "no result"}. Run openclaw doctor --fix for explicit repair; if repair is refused, preserve the database and WAL and restore a verified backup.`);
}
function runSqliteForeignKeyCheck(database, databaseLabel, table) {
let violationCount = 0;
let repairable = true;
let taskDeliveryForeignKeyId;
const violations = [];
try {
const argument = table ? `('${table.replaceAll("'", "''")}')` : "";
const statement = database.prepare(`PRAGMA foreign_key_check${argument};`);
statement.setReadBigInts(true);
for (const violation of statement.iterate()) {
violationCount += 1;
retainSortedForeignKeyViolation(violations, violation);
if (repairable) {
if (violation.table === "task_delivery_state" && violation.parent === "task_runs") {
taskDeliveryForeignKeyId ??= readTaskDeliveryCascadeForeignKeyId(database);
repairable = violation.fkid === taskDeliveryForeignKeyId;
} else repairable = false;
}
}
} catch (error) {
throw createSqliteIntegrityError(`SQLite foreign_key_check failed for ${databaseLabel}: ${error instanceof Error ? error.message : String(error)}`, error);
}
if (violations.length === 0) return;
if (repairable) throw new SqliteRepairableForeignKeyError(databaseLabel, violationCount);
const details = violations.map(formatSqliteForeignKeyViolation);
if (violationCount > MAX_REPORTED_FOREIGN_KEY_VIOLATIONS) details.push("additional violations omitted");
throw createSqliteIntegrityError(`SQLite foreign_key_check failed for ${databaseLabel}: ${details.join("; ")}`);
}
function readTaskDeliveryCascadeForeignKeyId(database) {
const statement = database.prepare("PRAGMA foreign_key_list(task_delivery_state);");
statement.setReadBigInts(true);
const foreignKeys = statement.all();
const taskKey = foreignKeys.find((key) => key.table === "task_runs" && key.from === "task_id" && key.to === "task_id" && key.on_delete === "CASCADE");
return taskKey && typeof taskKey.id === "bigint" && foreignKeys.filter((key) => key.id === taskKey.id).length === 1 ? taskKey.id : void 0;
}
function createSqliteIntegrityError(message, cause) {
const error = cause === void 0 ? new Error(message) : new Error(message, { cause });
error.name = "SqliteIntegrityError";
return error;
}
function retainSortedForeignKeyViolation(retained, violation) {
retained.push(violation);
retained.sort(compareSqliteForeignKeyViolations);
if (retained.length > MAX_REPORTED_FOREIGN_KEY_VIOLATIONS) retained.pop();
}
function compareSqliteForeignKeyViolations(left, right) {
const tableOrder = Buffer.compare(Buffer.from(left.table), Buffer.from(right.table));
if (tableOrder !== 0) return tableOrder;
if (left.rowid === null || right.rowid === null) {
if (left.rowid !== right.rowid) return left.rowid === null ? -1 : 1;
} else if (left.rowid !== right.rowid) return left.rowid < right.rowid ? -1 : 1;
const parentOrder = Buffer.compare(Buffer.from(left.parent), Buffer.from(right.parent));
if (parentOrder !== 0) return parentOrder;
if (left.fkid === right.fkid) return 0;
return left.fkid < right.fkid ? -1 : 1;
}
function formatSqliteForeignKeyViolation(violation) {
const row = violation.rowid === null ? "row without rowid" : `row ${violation.rowid.toString()}`;
return `${violation.table} ${row} references ${violation.parent} (foreign key ${violation.fkid.toString()})`;
}
//#endregion
//#region src/infra/windows-private-directory.ts
const require = createRequire(import.meta.url);
let creators;
function loadPrivatePathCreators() {
const koffi = require("koffi");
const kernel32 = koffi.load("kernel32.dll");
const advapi32 = koffi.load("advapi32.dll");
const attributes = koffi.struct({
length: "uint32_t",
descriptor: "void *",
inheritHandle: "int32_t"
});
const getLastError = kernel32.func("uint32_t __stdcall GetLastError()");
const getCurrentProcess = kernel32.func("void * __stdcall GetCurrentProcess()");
const closeHandle = kernel32.func("int32_t __stdcall CloseHandle(void *handle)");
const localFree = kernel32.func("void * __stdcall LocalFree(void *memory)");
const openToken = advapi32.func("int32_t __stdcall OpenProcessToken(void *process, uint32_t access, _Out_ void **token)");
const getTokenInformation = advapi32.func("int32_t __stdcall GetTokenInformation(void *token, int32_t informationClass, _Out_ void *information, uint32_t length, _Out_ uint32_t *required)");
const convertSid = advapi32.func("int32_t __stdcall ConvertSidToStringSidW(void *sid, _Out_ void **text)");
const convertDescriptor = advapi32.func("int32_t __stdcall ConvertStringSecurityDescriptorToSecurityDescriptorW(str16 text, uint32_t revision, _Out_ void **descriptor, void *size)");
const createDirectory = kernel32.func("__stdcall", "CreateDirectoryW", "int32_t", ["str16", koffi.pointer(attributes)]);
const createFile = kernel32.func("__stdcall", "CreateFileW", "void *", [
"str16",
"uint32_t",
"uint32_t",
koffi.pointer(attributes),
"uint32_t",
"uint32_t",
"void *"
]);
const failure = (operation) => {
const errorCode = getLastError();
return Object.assign(/* @__PURE__ */ new Error(`${operation} failed (Win32 error ${errorCode})`), {
code: errorCode === 80 || errorCode === 183 ? "EEXIST" : "EIO",
errno: errorCode
});
};
const withPrivateAttributes = (inherit, operation) => {
const token = [null];
const sidText = [null];
const descriptor = [null];
if (!openToken(getCurrentProcess(), 8, token)) throw failure("OpenProcessToken");
try {
const required = [0];
getTokenInformation(token[0], 1, null, 0, required);
if (getLastError() !== 122 || required[0] === 0) throw failure("GetTokenInformation(size)");
const user = Buffer.alloc(required[0]);
if (!getTokenInformation(token[0], 1, user, user.length, required)) throw failure("GetTokenInformation");
if (!convertSid(koffi.decode(user, "void *"), sidText)) throw failure("ConvertSidToStringSidW");
const sid = koffi.decode(sidText[0], "char16_t", -1);
const flags = inherit ? "OICI" : "";
const sddl = `O:${sid}D:P(A;${flags};FA;;;${sid})(A;${flags};FA;;;SY)(A;${flags};FA;;;BA)`;
if (!convertDescriptor(sddl, 1, descriptor, null)) throw failure("ConvertStringSecurityDescriptorToSecurityDescriptorW");
return operation({
length: koffi.sizeof(attributes),
descriptor: descriptor[0],
inheritHandle: 0
});
} finally {
if (descriptor[0] !== null) localFree(descriptor[0]);
if (sidText[0] !== null) localFree(sidText[0]);
closeHandle(token[0]);
}
};
return {
directory: (directoryPath) => withPrivateAttributes(true, (security) => {
if (!createDirectory(path.toNamespacedPath(path.resolve(directoryPath)), security)) throw failure(`CreateDirectoryW(${directoryPath})`);
}),
file: (filePath) => withPrivateAttributes(false, (security) => {
const resolved = path.toNamespacedPath(path.resolve(filePath));
const handle = createFile(resolved, 3221225472, 3, security, 1, 128, null);
if (handle === null || BigInt.asIntN(64, koffi.address(handle)) === -1n) throw failure(`CreateFileW(${filePath})`);
try {
return fs$1.openSync(resolved, fs$1.constants.O_RDWR | (fs$1.constants.O_NOFOLLOW ?? 0));
} finally {
closeHandle(handle);
}
})
};
}
function createPrivateWindowsDirectory(directoryPath) {
creators ??= loadPrivatePathCreators();
creators.directory(directoryPath);
}
//#endregion
//#region src/infra/sqlite-private-directory.ts
function resolvePrivateSqliteSnapshotStagingRoot(env = process.env) {
const appData = process.platform === "win32" ? env.LOCALAPPDATA?.trim() : void 0;
const defaultRoot = process.platform === "win32" ? "AppData/Local" : ".cache";
const platformRoot = process.platform === "darwin" ? "Library/Caches" : defaultRoot;
const cacheRoot = [env.XDG_CACHE_HOME?.trim(), appData].find((root) => root && path.isAbsolute(root)) ?? path.join(resolveRequiredOsHomeDir(env), platformRoot);
return resolvePreferredOpenClawTmpDir({
preferredDir: path.join(cacheRoot, "openclaw"),
tmpdir: () => cacheRoot
});
}
function createPrivateSqliteTempDirectorySync(rootPath, prefix) {
if (process.platform !== "win32") return fs$1.mkdtempSync(path.join(rootPath, prefix));
const directoryPath = path.join(rootPath, `${prefix}${randomUUID()}`);
createPrivateWindowsDirectory(directoryPath);
return directoryPath;
}
//#endregion
//#region src/infra/sqlite-schema-issues.ts
/** A completed inspection proved schema drift; native read failures retain their own type. */
var SqliteSchemaMismatchError = class extends Error {
constructor(..._args) {
super(..._args);
this.name = "SqliteSchemaMismatchError";
}
};
function defaultIssueMessage(code, objectName) {
const tableName = objectName.split(".", 1)[0];
switch (code) {
case "missing-table": return `missing table ${objectName}`;
case "missing-column":
case "unexpected-column":
case "column-definition-drift": return `column definitions differ for ${tableName}`;
case "table-constraint-drift": return `table constraints differ for ${objectName}`;
case "table-definition-drift": return `table definition differs for ${objectName}`;
case "missing-or-drifted-index": return `missing or drifted index ${objectName}`;
case "unexpected-unique-index": return `unexpected unique index ${objectName}`;
case "missing-or-drifted-trigger": return `missing or drifted trigger ${objectName}`;
case "unexpected-trigger": return `unexpected trigger ${objectName}`;
case "virtual-table-definition-drift": return `virtual table definition differs for ${objectName}`;
case "table-options-drift": return `table options differ for ${objectName}`;
}
throw new Error("Unsupported SQLite schema issue code", { cause: code });
}
function createSqliteSchemaIssue(code, objectName, message) {
return {
code,
objectName,
message: message ?? defaultIssueMessage(code, objectName)
};
}
function legacySqliteSchemaIssueMessages(issues) {
const isColumnIssue = (issue) => issue.code === "column-definition-drift" || issue.code === "missing-column" || issue.code === "unexpected-column";
const columnIssueTables = new Set(issues.filter(isColumnIssue).map((issue) => issue.objectName.split(".", 1)[0]));
return [...new Set(issues.filter((issue) => issue.code !== "table-constraint-drift" || !columnIssueTables.has(issue.objectName)).map((issue) => issue.message))];
}
function throwSqliteSchemaMismatches(databaseLabel, mismatches) {
const shown = mismatches.slice(0, 8);
if (mismatches.length > shown.length) shown.push(`${mismatches.length - shown.length} additional mismatch(es)`);
throw new SqliteSchemaMismatchError(`SQLite schema is incomplete or noncanonical for ${databaseLabel}: ${shown.join("; ")}; run openclaw doctor --fix to repair it.`);
}
//#endregion
//#region src/state/openclaw-state-db-schema-helpers.ts
function tableHasColumn(db, tableName, columnName) {
return tableHasColumns(db, tableName, [columnName]);
}
function tableHasColumns(db, tableName, columnNames) {
const existing = readTableColumns$1(db, tableName);
return columnNames.every((columnName) => existing.has(columnName));
}
function readTableColumns$1(db, tableName) {
const rows = db.prepare(`PRAGMA table_info(${tableName})`).all();
return new Set(rows.flatMap((row) => typeof row.name === "string" ? [row.name] : []));
}
/** Inspect only failed queries; native unavailability must not become a schema refusal. */
function classifySqliteTableReadError(db, tableName, columnNames, error) {
if (sqlitePrimaryResultCode(error) !== 1) return error;
try {
const existing = readTableColumns$1(db, tableName);
if (existing.size > 0 && columnNames.some((column) => !existing.has(column))) return new SqliteSchemaMismatchError(`SQLite table ${tableName} is missing required columns; run openclaw doctor --fix to repair it.`, { cause: error });
} catch {}
return error;
}
function tablePrimaryKeyColumns(db, tableName) {
return db.prepare(`PRAGMA table_info(${tableName})`).all().filter((row) => Number(row.pk ?? 0) > 0 && typeof row.name === "string").toSorted((left, right) => Number(left.pk ?? 0) - Number(right.pk ?? 0)).map((row) => row.name);
}
function tableExists(db, tableName) {
const schema = getAdmittedSqliteSchemaFacts(db);
if (schema) return schema.tables.has(tableName);
return executeWithCachedStatement(db, "SELECT 1 AS ok FROM sqlite_master WHERE type = 'table' AND name = ?", [tableName], (statement) => statement.get(tableName))?.ok === 1;
}
function ensureColumn(db, tableName, columnSql) {
const columnName = columnSql.trim().split(/\s+/, 1)[0];
if (!columnName || !tableExists(db, tableName) || tableHasColumn(db, tableName, columnName)) return false;
db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnSql};`);
return true;
}
//#endregion
//#region packages/normalization-core/src/mountinfo-path.ts
const MOUNT_PATH_OCTAL_ESCAPE_RE = /\\([0-7]{3})/g;
/** Decodes an octal-escaped path field from a Linux procfs mount table. */
function decodeMountInfoPath(value) {
return value.replace(MOUNT_PATH_OCTAL_ESCAPE_RE, (_match, octal) => String.fromCharCode(Number.parseInt(octal, 8)));
}
//#endregion
//#region src/infra/sqlite-number.ts
const MAX_SAFE_INTEGER_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
function coerceRequiredSqliteNumber(value) {
return typeof value === "bigint" ? Number(value) : value;
}
/** Converts a SQLite number or safely representable bigint column into a JavaScript number. */
function normalizeSqliteNumber(value) {
if (typeof value === "bigint") {
if (value > MAX_SAFE_INTEGER_BIGINT || value < -MAX_SAFE_INTEGER_BIGINT) return;
return Number(value);
}
return typeof value === "number" ? value : void 0;
}
//#endregion
//#region src/infra/sqlite-wal-checkpoint.ts
const checkpointListeners = resolveGlobalSingleton(Symbol.for("openclaw.sqliteWalCheckpointListeners"), () => /* @__PURE__ */ new Set());
/** A relayed worker result adds host observations without claiming visibility into other threads. */
function observeSqliteWalCheckpointHealth(databasePath, health) {
const { activeReaders: previousReaders, readerDiagnostics: previousDiagnostics, ...observation } = health;
if (health.state === "complete") return observation;
const { activeReaders, ...local } = readSqliteReaderDiagnosticsForPath(databasePath);
return {
...observation,
activeReaders: [...(previousReaders ?? []).filter((reader) => reader.threadId !== local.threadId), ...activeReaders].toSorted((left, right) => right.ageMs - left.ageMs).slice(0, 8),
readerDiagnostics: [...(previousDiagnostics ?? []).filter((diagnostic) => diagnostic.threadId !== local.threadId), local].slice(-8)
};
}
function notifyCheckpoint(databasePath, snapshot) {
for (const listener of checkpointListeners) try {
listener({
databasePath: sqliteReaderDatabasePathKey(databasePath),
health: structuredClone(snapshot.health),
observedAtNs: snapshot.observedAtNs
});
} catch {}
}
function sqliteFileBytes(pathname) {
try {
return fs$1.statSync(pathname).size;
} catch (error) {
if (hasErrnoCode(error, "ENOENT")) return 0;
throw error;
}
}
function readCheckpointResult(row) {
const [busy, logFrames, checkpointedFrames] = Object.values(row ?? {}).map((value) => normalizeSqliteNumber(typeof value === "number" || typeof value === "bigint" ? value : null));
if (busy === void 0 || logFrames === void 0 || checkpointedFrames === void 0) throw new Error("SQLite returned an invalid WAL checkpoint result");
return {
busy,
logFrames,
checkpointedFrames
};
}
function checkpoint(database, mode) {
return database.prepare(`PRAGMA wal_checkpoint(${mode});`).get();
}
/** The maintenance lifecycle owns this checkpoint result and its last observation. */
function createSqliteWalCheckpoint(database, options, journalSizeLimitBytes) {
let snapshot;
const checkpointObservation = () => ({
state: "error",
observedAtMs: Date.now(),
walBytes: null,
databaseBytes: null,
logFrames: null,
checkpointedFrames: null,
lastCompletedAtMs: snapshot?.health.lastCompletedAtMs ?? null,
consecutiveBlocked: 0,
warning: true
});
const recordCheckpointError = (error, observation = checkpointObservation()) => {
const failed = {
...observation,
observedAtMs: Date.now(),
state: "error",
consecutiveBlocked: 0,
warning: true,
error: formatErrorMessage(error)
};
snapshot = {
observedAtNs: process.hrtime.bigint(),
health: options.databasePath ? observeSqliteWalCheckpointHealth(options.databasePath, failed) : failed
};
if (options.databasePath) notifyCheckpoint(options.databasePath, snapshot);
options.onCheckpointError?.(error);
};
const recordCheckpoint = (mode, row) => {
const observedAtNs = process.hrtime.bigint();
const observation = checkpointObservation();
let busy;
let sizeError;
try {
const { busy: busyResult, logFrames, checkpointedFrames } = readCheckpointResult(row);
busy = busyResult !== 0;
observation.logFrames = logFrames;
observation.checkpointedFrames = checkpointedFrames;
observation.state = busy || checkpointedFrames < logFrames ? "blocked" : "complete";
if (observation.state === "complete") observation.lastCompletedAtMs = observation.observedAtMs;
else observation.consecutiveBlocked = (snapshot?.health.consecutiveBlocked ?? 0) + 1;
if (options.databasePath) try {
observation.databaseBytes = sqliteFileBytes(options.databasePath);
observation.walBytes = sqliteFileBytes(`${options.databasePath}-wal`);
} catch (error) {
sizeError = error;
observation.error = formatErrorMessage(error);
}
observation.warning = observation.state === "blocked" && (observation.consecutiveBlocked >= 2 || observation.walBytes !== null && observation.databaseBytes !== null && observation.walBytes > Math.max(2 * observation.databaseBytes, journalSizeLimitBytes));
snapshot = {
observedAtNs,
health: options.databasePath ? observeSqliteWalCheckpointHealth(options.databasePath, observation) : observation
};
if (options.databasePath) notifyCheckpoint(options.databasePath, snapshot);
} catch (error) {
recordCheckpointError(error, observation);
return false;
}
if (observation.error !== void 0) options.onCheckpointError?.(sizeError);
if (busy || observation.warning) {
const label = options.databaseLabel ?? "sqlite database";
options.onCheckpointError?.(/* @__PURE__ */ new Error(`${label} WAL checkpoint ${mode} ${busy ? "remained busy" : "blocked by a reader"}`));
}
return observation.state === "complete";
};
return {
adopt(received) {
if (snapshot && snapshot.observedAtNs >= received.observedAtNs) return;
snapshot = structuredClone(received);
if (options.databasePath) {
snapshot.health = observeSqliteWalCheckpointHealth(options.databasePath, snapshot.health);
notifyCheckpoint(options.databasePath, snapshot);
}
},
checkpoint(mode) {
try {
return recordCheckpoint(mode, checkpoint(database, mode));
} catch (error) {
recordCheckpointError(error);
return false;
}
},
recordError: recordCheckpointError,
inspectIdle() {
const { busy, logFrames, checkpointedFrames } = readCheckpointResult(checkpoint(database, "PASSIVE"));
return busy === 0 && logFrames >= 0 && checkpointedFrames >= 0 && checkpointedFrames <= logFrames;
},
get health() {
return snapshot ? structuredClone(snapshot.health) : void 0;
},
get snapshot() {
return snapshot ? structuredClone(snapshot) : void 0;
}
};
}
//#endregion
//#region src/infra/sqlite-wal-reclamation.ts
const VACUUM_UNIT_TARGET_MS = 25;
const vacuumPageBudgets = /* @__PURE__ */ new WeakMap();
function createSqliteWalReclamationResult() {
return {
checkpointCompleted: false,
freePagesBefore: null,
remainingFreePages: null,
checkpointCalls: 0,
checkpointIncomplete: 0,
checkpointMs: 0,
checkpointMaxMs: 0,
queryMs: 0,
vacuumMs: 0,
vacuumPasses: 0,
vacuumPagesRequested: 0
};
}
/** One online unit never waits for readers or adds vacuum frames behind a blocked checkpoint. */
function reclaimSqliteWalFreePages(database, runCheckpoint, options) {
const result = createSqliteWalReclamationResult();
const checkpoint = () => {
options.beforeMutation?.();
const startedAt = performance$1.now();
try {
const mode = options.checkpointMode ?? "TRUNCATE";
let completed = runCheckpoint(mode);
result.checkpointCalls++;
if (!completed && mode === "TRUNCATE") {
options.beforeMutation?.();
completed = runCheckpoint("PASSIVE");
result.checkpointCalls++;
}
result.checkpointCompleted = completed;
result.checkpointIncomplete += Number(!completed);
return completed;
} finally {
const elapsed = performance$1.now() - startedAt;
result.checkpointMs += elapsed;
result.checkpointMaxMs = Math.max(result.checkpointMaxMs, elapsed);
}
};
const freePages = () => {
const startedAt = performance$1.now();
try {
return Number(database.prepare("PRAGMA freelist_count").get()?.freelist_count ?? 0);
} finally {
result.queryMs += performance$1.now() - startedAt;
}
};
return runWithSqliteBusyTimeout(database, 0, () => {
if (!checkpoint()) return result;
const before = freePages();
result.freePagesBefore = before;
result.remainingFreePages = before;
if (!Number.isSafeInteger(before) || before <= 0) return result;
const pages = Math.min(vacuumPageBudgets.get(database) ?? 8, before, options.maxPages ?? 512);
if (!Number.isSafeInteger(pages) || pages <= 0) throw new Error("SQLite page reclamation requires a positive integer page limit");
const startedAt = performance$1.now();
let entered = false;
let completed = false;
try {
runSqliteImmediateTransactionSync(database, () => {
entered = true;
options.beforeMutation?.();
result.vacuumPasses++;
result.vacuumPagesRequested += pages;
database.exec(`PRAGMA incremental_vacuum(${pages});`);
options.onCommit?.();
}, {
busyTimeoutMs: 0,
operationLabel: "incremental-vacuum"
});
completed = true;
} catch (error) {
if (entered || !isSqliteLockError(error)) throw error;
return result;
} finally {
const elapsedMs = performance$1.now() - startedAt;
result.vacuumMs += elapsedMs;
if (completed) vacuumPageBudgets.set(database, Math.max(1, Math.min(512, pages * 2, Math.floor(pages * VACUUM_UNIT_TARGET_MS / Math.max(elapsedMs, .001)))));
}
options.afterCommit?.();
if (checkpoint()) result.remainingFreePages = freePages();
return result;
});
}
//#endregion
//#region src/infra/sqlite-wal-split-brain.ts
const PROC_SELF_FD_PATH = "/proc/self/fd";
const SQLITE_WAL_SPLIT_BRAIN_FATAL_MESSAGE = "SQLite WAL sidecar identity mismatch; terminating without SQLite cleanup";
function statSqliteSidecarTarget(pathname) {
try {
return fs$1.statSync(pathname, { bigint: true });
} catch (error) {
if (hasErrnoCode(error, "ENOENT")) return;
throw error;
}
}
function isSqliteWalSidecarSplitBrain(descriptor, target) {
return descriptor.nlink === 0n || !target || descriptor.dev !== target.dev || descriptor.ino !== target.ino;
}
function detectSqliteWalSplitBrain(databasePath) {
let descriptors;
try {
descriptors = fs$1.readdirSync(PROC_SELF_FD_PATH);
} catch (error) {
if (hasErrnoCode(error, "ENOENT")) return;
throw error;
}
const sidecarPaths = [`${databasePath}-wal`, `${databasePath}-shm`];
for (const descriptorName of descriptors) {
const descriptorPath = path.join(PROC_SELF_FD_PATH, descriptorName);
let linkedPath;
try {
linkedPath = fs$1.readlinkSync(descriptorPath);
} catch (error) {
if (hasErrnoCode(error, "ENOENT")) continue;
throw error;
}
const sidecarPath = sidecarPaths.find((candidate) => linkedPath === candidate || linkedPath === `${candidate} (deleted)`);
if (!sidecarPath) continue;
let descriptor;
try {
descriptor = fs$1.fstatSync(Number(descriptorName), { bigint: true });
} catch (error) {
if (hasErrnoCode(error, "EBADF") || hasErrnoCode(error, "ENOENT")) continue;
throw error;
}
try {
if (fs$1.readlinkSync(descriptorPath) !== linkedPath) continue;
} catch (error) {
if (hasErrnoCode(error, "ENOENT")) continue;
throw error;
}
const target = statSqliteSidecarTarget(sidecarPath);
if (!isSqliteWalSidecarSplitBrain(descriptor, target)) continue;
return {
event: "sqlite_wal_sidecar_identity_mismatch",
databasePath,
descriptorDevice: descriptor.dev.toString(),
descriptorInode: descriptor.ino.toString(),
sidecarPath,
...target ? {
targetDevice: target.dev.toString(),
targetInode: target.ino.toString()
} : {}
};
}
}
function terminateForSqliteWalSplitBrain(splitBrain, databaseLabel) {
try {
fs$1.writeSync(2, `${JSON.stringify({
level: "fatal",
subsystem: "infra/sqlite-wal",
message: SQLITE_WAL_SPLIT_BRAIN_FATAL_MESSAGE,
...splitBrain,
databaseLabel,
pid: process.pid
})}\n`);
} catch {}
try {
process.kill(process.pid, "SIGKILL");
} finally {
process.abort();
}
}
//#endregion
//#region src/infra/sqlite-wal-write-admission.ts
const admissions = resolveGlobalSingleton(Symbol.for("openclaw.sqliteWalWriteAdmissions"), () => /* @__PURE__ */ new WeakMap());
function registerSqliteWalWorkerMaintenance(database, execute, cancel) {
admissions.set(database, {
execute,
cancel
});
}
function cancelSqliteWalWriteAdmission(database) {
return admissions.get(database)?.cancel?.();
}
function createSqliteWalMaintenanceScheduler(database, operation, prepare, observe, onError, pageBudget) {
let pending;
return () => {
if (!pending) {
const run = async () => {
let remaining = pageBudget;
while (remaining > 0) {
const request = prepare(remaining);
if (!request) return;
let result;
const admitted = () => {
if (prepare(remaining)) result = operation(request);
};
const admission = admissions.get(database);
if (admission?.execute) {
result = await admission.execute(request);
if (!prepare(remaining)) return;
if (result?.checkpoint) observe(result.checkpoint);
} else if (admission?.admit) await admission.admit(admitted);
else admitted();
const reclaimed = result?.reclaimedPages ?? 0;
remaining -= reclaimed;
if (reclaimed <= 0 || remaining <= 0) return;
await setImmediate();
}
};
pending = run().catch((error) => {
if (prepare(pageBudget)) onError(error);
}).finally(() => {
pending = void 0;
});
}
return pending;
};
}
//#endregion
//#region src/infra/sqlite-wal.ts
const DEFAULT_SQLITE_WAL_AUTOCHECKPOINT_PAGES = 1e3;
const DEFAULT_SQLITE_WAL_CHECKPOINT_INTERVAL_MS = 18e5;
const DEFAULT_SQLITE_WAL_JOURNAL_SIZE_LIMIT_BYTES = 67108864;
const LINUX_NFS_SUPER_MAGIC = 26985;
const LINUX_SMB_SUPER_MAGIC = 20859;
const LINUX_CIFS_SUPER_MAGIC = 4283649346;
const LINUX_SMB2_SUPER_MAGIC = 4266872130;
const LINUX_V9FS_SUPER_MAGIC = 16914839;
const PROC_MOUNTINFO_PATH = "/proc/self/mountinfo";
const MOUNT_COMMAND_TIMEOUT_MS = 1e3;
const NETWORK_FILESYSTEM_TYPES = /* @__PURE__ */ new Set([
"cifs",
"smbfs",
"smb2",
"smb3"
]);
const CROSS_VM_FILESYSTEM_TYPES = /* @__PURE__ */ new Set([
"virtiofs",
"fuse.virtiofs",
"9p",
"9p2000.l"
]);
const JOURNAL_MODE_RETRY_INTERVAL_MS = 10;
const JOURNAL_MODE_RETRY_SLEEP = new Int32Array(new SharedArrayBuffer(4));
const log$1 = createSubsystemLogger("infra/sqlite-wal");
const runInSqliteMaintenanceContext = AsyncLocalStorage.snapshot();
function configureSqliteBusyTimeout(db, busyTimeoutMs) {
const normalizedTimeoutMs = normalizeSqliteNonNegativeInteger(busyTimeoutMs, "busyTimeoutMs");
db.exec(`PRAGMA busy_timeout = ${normalizedTimeoutMs};`);
return normalizedTimeoutMs;
}
function enableIncrementalAutoVacuumForFreshDatabase(db) {
if (db.prepare("PRAGMA page_count").get()?.page_count === 0) db.exec("PRAGMA auto_vacuum = INCREMENTAL;");
}
/**
* Configure lock retry before inspecting or mutating a fresh database header.
* Concurrent first opens can otherwise fail before schema transactions begin.
*/
function configureSqlitePreSchemaPragmas(db, options = {}) {
if (options.busyTimeoutMs !== void 0) configureSqliteBusyTimeout(db, options.busyTimeoutMs);
enableIncrementalAutoVacuumForFreshDatabase(db);
}
function findExistingVolumePaths(targetPath) {
let current = path.resolve(targetPath);
while (true) {
let stats;
try {
stats = fs$1.statSync(current);
} catch {
const parent = path.dirname(current);
if (parent === current) return null;
current = parent;
continue;
}
const existingPath = fs$1.realpathSync(current);
return {
canonicalPath: stats.isDirectory() ? existingPath : path.dirname(existingPath),
originalPath: stats.isDirectory() ? current : path.dirname(current)
};
}
}
function parseProcMountInfoEntries(contents) {
const entries = [];
for (const line of contents.split("\n")) {
const separator = line.indexOf(" - ");
if (separator === -1) continue;
const fields = line.slice(0, separator).split(" ");
const suffixFields = line.slice(separator + 3).split(" ");
const mountPoint = fields[4];
const fsType = suffixFields[0];
if (mountPoint && fsType) entries.push({
mountPoint: decodeMountInfoPath(mountPoint),
fsType,
...suffixFields[1] ? { source: decodeMountInfoPath(suffixFields[1]) } : {}
});
}
return entries;
}
function parseMountCommandEntries(contents) {
const entries = [];
for (const line of contents.split("\n")) {
const match = /^(.+) on (.+) type ([^,\s)]+) \(/.exec(line) ?? /^(.+) on (.+) \(([^,\s)]+)/.exec(line);
if (match) {
const [, source, mountPoint, fsType] = match;
if (source && mountPoint && fsType) entries.push({
source,
mountPoint,
fsType
});
}
}
return entries;
}
function isMountCommandTimeout(error) {
return error !== null && typeof error === "object" && "code" in error && error.code === "ETIMEDOUT";
}
function readMountEntries() {
try {
return {
ok: true,
value: parseProcMountInfoEntries(fs$1.readFileSync(PROC_MOUNTINFO_PATH, "utf8"))
};
} catch {}
try {
return {
ok: true,
value: parseMountCommandEntries(String(process.getBuiltinModule("node:child_process").execFileSync("mount", [], {
killSignal: "SIGKILL",
timeout: MOUNT_COMMAND_TIMEOUT_MS
})))
};
} catch (error) {
return isMountCommandTimeout(error) ? {
ok: false,
error: "timeout"
} : {
ok: true,
value: []
};
}
}
function isPathWithinMount(targetPath, mountPoint) {
const resolvedTarget = path.resolve(targetPath);
const resolvedMountPoint = path.resolve(mountPoint);
return resolvedTarget === resolvedMountPoint || resolvedMountPoint === path.parse(resolvedMountPoint).root || resolvedTarget.startsWith(`${resolvedMountPoint}${path.sep}`);
}
function isSshfsMountSource(source) {
if (!source) return false;
const normalized = source.toLowerCase();
return normalized === "sshfs" || normalized.startsWith("sshfs#") || normalized.startsWith("sshfs@") || /^(?:[^/\s:]+@)?[^/\s:]+:.*/u.test(source);
}
function resolveMountTypeJournalPolicy(entry) {
const normalized = entry.fsType.toLowerCase();
if (normalized.startsWith("nfs") || NETWORK_FILESYSTEM_TYPES.has(normalized)) return "rollback";
if (CROSS_VM_FILESYSTEM_TYPES.has(normalized) || normalized.startsWith("9p")) return "rollback";
if (normalized === "fuse.sshfs") return "unsupported";
if ((normalized === "macfuse" || normalized === "osxfuse") && isSshfsMountSource(entry.source)) return "unsupported";
return "wal";
}
function resolveMountEntryJournalPolicy(targetPath, mountEntries) {
const mountEntry = mountEntries.filter((entry) => isPathWithinMount(targetPath, entry.mountPoint)).toSorted((a, b) => b.mountPoint.length - a.mountPoint.length)[0];
return mountEntry ? resolveMountTypeJournalPolicy(mountEntry) : "wal";
}
function combineMountEntryJournalPolicies(targetPaths) {
const mountResult = readMountEntries();
if (!mountResult.ok) {
const [originalPath, canonicalPath] = targetPaths;
if (process.platform === "darwin" && originalPath === canonicalPath) try {
if (probeTreeClone(canonicalPath) === "apfs") return "wal";
} catch {}
return "rollback";
}
const policies = new Set(targetPaths.map((targetPath) => resolveMountEntryJournalPolicy(targetPath, mountResult.value)));
if (policies.has("unsupported")) return "unsupported";
return policies.has("rollback") ? "rollback" : "wal";
}
function isWindowsUncPath(targetPath) {
return /^\\\\\?\\UNC\\[^\\]+\\[^\\]+/i.test(targetPath) || /^\\\\(?![?.]\\)[^\\]+\\[^\\]+/.test(targetPath);
}
function isWindowsDrivePath(targetPath) {
return /^[A-Za-z]:[\\/]/.test(targetPath) || /^\\\\\?\\[A-Za-z]:[\\/]/i.test(targetPath);
}
function resolvePathJournalPolicy(targetPath) {
if (process.platform === "win32") {
const normalizedTargetPath = path.win32.normalize(targetPath);
if (isWindowsUncPath(normalizedTargetPath)) return "rollback";
if (isWindowsDrivePath(normalizedTargetPath)) try {
return isWindowsUncPath(path.win32.normalize(fs$1.realpathSync.native(targetPath))) ? "rollback" : "wal";
} catch {
return "rollback";
}
}
const checkedPaths = findExistingVolumePaths(targetPath);
if (!checkedPaths) return "wal";
const mountLookupPaths = [checkedPaths.originalPath, checkedPaths.canonicalPath];
if (typeof fs$1.statfsSync !== "function") return combineMountEntryJournalPolicies(mountLookupPaths);
try {
const filesystemType = fs$1.statfsSync(checkedPaths.canonicalPath).type;
if (filesystemType === LINUX_NFS_SUPER_MAGIC || filesystemType === LINUX_SMB_SUPER_MAGIC || filesystemType === LINUX_CIFS_SUPER_MAGIC || filesystemType === LINUX_SMB2_SUPER_MAGIC || filesystemType === LINUX_V9FS_SUPER_MAGIC) return "rollback";
} catch {
return combineMountEntryJournalPolicies(mountLookupPaths);
}
return combineMountEntryJournalPolicies(mountLookupPaths);
}
function readJournalModeResult(row) {
if (!row || typeof row !== "object") return null;
const record = row;
const value = record.journal_mode ?? Object.values(record)[0];
return typeof value === "string" ? value.toLowerCase() : null;
}
function hasInMemoryMainDatabase(db) {
return db.prepare("PRAGMA database_list;").all().find((row) => row.name === "main")?.file === "";
}
function requireRollbackJournalMode(db, options) {
const journalMode = readJournalModeResult(db.prepare("PRAGMA journal_mode = DELETE;").get());
if (journalMode !== "delete") {
const label = options.databaseLabel ?? "sqlite database";
const location = options.databasePath ? ` at ${options.databasePath}` : "";
throw new Error(`${label}${location} is on a network-backed volume but SQLite kept journal_mode=${journalMode ?? "unknown"}; refusing to continue with WAL on network storage.`);
}
}
function enableWalJournalMode(db, retryTimeoutMs, options) {
const deadline = performance$1.now() + retryTimeoutMs;
let restoreBusyTimeout = false;
try {
while (true) try {
db.exec("PRAGMA journal_mode = WAL;");
const journalMode = readJournalModeResult(db.prepare("PRAGMA journal_mode;").get());
if (journalMode === "wal") return true;
if (journalMode === "memory" && hasInMemoryMainDatabase(db)) return false;
const label = options.databaseLabel ?? "sqlite database";
const location = options.databasePath ? ` at ${options.databasePath}` : "";
throw new Error(`${label}${location} could not enable WAL; SQLite kept journal_mode=${journalMode ?? "unknown"}.`);
} catch (error) {
const remainingMs = Math.max(0, deadline - performance$1.now());
if (!isSqliteLockError(error) || remainingMs <= 0) throw error;
if (!restoreBusyTimeout) {
configureSqliteBusyTimeout(db, 0);
restoreBusyTimeout = true;
}
Atomics.wait(JOURNAL_MODE_RETRY_SLEEP, 0, 0, Math.min(JOURNAL_MODE_RETRY_INTERVAL_MS, remainingMs));
}
} finally {
if (restoreBusyTimeout) configureSqliteBusyTimeout(db, retryTimeoutMs);
}
}
function enableMacosCheckpointFullfsync(db) {
if (process.platform !== "darwin") return;
try {
db.exec("PRAGMA checkpoint_fullfsync = 1;");
} catch {}
}
function refuseUnsupportedFilesystem(options) {
const label = options.databaseLabel ?? "sqlite database";
const location = options.databasePath ? ` at ${options.databasePath}` : "";
throw new Error(`${label}${location} is on SSHFS, which cannot safely coordinate SQLite writes across mounts; refusing to open the database.`);
}
/** Configure safe journaling pragmas and return a handle for checkpoint/close maintenance. */
function configureSqliteWalMaintenance(db, options = {}) {
const busyTimeoutMs = options.busyTimeoutMs === void 0 ? 0 : configureSqliteBusyTimeout(db, options.busyTimeoutMs);
const autoCheckpointPages = normalizeSqliteNonNegativeInteger(options.autoCheckpointPages ?? DEFAULT_SQLITE_WAL_AUTOCHECKPOINT_PAGES, "autoCheckpointPages");
const checkpointIntervalMs = normalizeSqliteNonNegativeInteger(options.checkpointIntervalMs ?? DEFAULT_SQLITE_WAL_CHECKPOINT_INTERVAL_MS, "checkpointIntervalMs");
const timerIntervalMs = Math.min(checkpointIntervalMs, MAX_TIMER_TIMEOUT_MS);
const checkpointMode = options.checkpointMode ?? "TRUNCATE";
const periodicCheckpointMode = options.checkpointMode ?? "PASSIVE";
const journalPolicy = options.databasePath ? resolvePathJournalPolicy(options.databasePath) : "wal";
if (journalPolicy === "unsupported") refuseUnsupportedFilesystem(options);
if (journalPolicy === "rollback") requireRollbackJournalMode(db, options);
if (journalPolicy === "rollback" || !enableWalJournalMode(db, busyTimeoutMs, options)) return {
checkpoint: () => true,
reclaimFreePages: (reclaimOptions = {}) => reclaimSqliteWalFreePages(db, () => true, reclaimOptions),
close: () => true
};
enableMacosCheckpointFullfsync(db);
db.exec(`PRAGMA wal_autocheckpoint = ${autoCheckpointPages};`);
db.exec(`PRAGMA journal_size_limit = ${DEFAULT_SQLITE_WAL_JOURNAL_SIZE_LIMIT_BYTES};`);
const tripwireDatabasePath = process.platform === "linux" && options.databasePath && fs$1.existsSync(options.databasePath) ? fs$1.realpathSync.native(options.databasePath) : void 0;
let invalidated = false;
let splitBrainDetectionEnabled = Boolean(tripwireDatabasePath);
const checkpointOwner = createSqliteWalCheckpoint(db, options, DEFAULT_SQLITE_WAL_JOURNAL_SIZE_LIMIT_BYTES);
const runCheckpoint = checkpointOwner.checkpoint;
const runMaintenance = (operation) => {
if (invalidated) return false;
try {
return options.runMaintenance ? options.runMaintenance(operation) : operation();
} catch (error) {
checkpointOwner.recordError(error);
return false;
}
};
const checkpoint = () => runMaintenance(() => runCheckpoint(checkpointMode));
const reclaimFreePages = (reclaimOptions = {}) => {
let result;
let failure;
runMaintenance(() => {
try {
result = reclaimSqliteWalFreePages(db, runCheckpoint, reclaimOptions);
return result.checkpointCompleted;
} catch (error) {
failure = { error };
throw error;
}
});
if (failure) throw failure.error;
if (!result) throw new Error("SQLite page reclamation owner is unavailable");
return {
...result,
checkpoint: checkpointOwner.snapshot
};
};
let timer = null;
const maintainPeriodic = (request, admit) => {
if (invalidated) return { reclaimedPages: 0 };
if (request.checkpoint) checkpointOwner.adopt(request.checkpoint);
let reclaimedPages = 0;
runMaintenance(() => {
const reclaimed = reclaimSqliteWalFreePages(db, runCheckpoint, {
checkpointMode: request.checkpointMode,
maxPages: request.maxPages,
beforeMutation: () => admit?.("transaction"),
onCommit: () => admit?.("commit")
});
const checkpointed = reclaimed.checkpointCompleted;
if (checkpointed && reclaimed.freePagesBefore !== null && reclaimed.remainingFreePages !== null) reclaimedPages = Math.min(reclaimed.vacuumPagesRequested, reclaimed.freePagesBefore - reclaimed.remainingFreePages);
if (checkpointed && request.checkpointMode === "PASSIVE" && (checkpointOwner.health?.walBytes ?? 0) > DEFAULT_SQLITE_WAL_JOURNAL_SIZE_LIMIT_BYTES) {
admit?.("transaction");
runWithSqliteBusyTimeout(db, 0, () => runCheckpoint("TRUNCATE"));
}
return checkpointed;
});
return {
reclaimedPages,
checkpoint: checkpointOwner.snapshot
};
};
const maintain = createSqliteWalMaintenanceScheduler(db, maintainPeriodic, (maxPages) => timer && !invalidated ? {
maxPages,
checkpointMode: periodicCheckpointMode,
checkpoint: checkpointOwner.snapshot
} : void 0, checkpointOwner.adopt, (error) => checkpointOwner.recordError(error), 512);
if (timerIntervalMs > 0) {
timer = runInSqliteMaintenanceContext(() => setInterval(() => {
if (!timer || invalidated) return;
if (tripwireDatabasePath && splitBrainDetectionEnabled) {
let splitBrain;
try {
splitBrain = detectSqliteWalSplitBrain(tripwireDatabasePath);
} catch (error) {
splitBrainDetectionEnabled = false;
log$1.warn("SQLite WAL split-brain detection disabled", {
databaseLabel: options.databaseLabel,
databasePath: tripwireDatabasePath,
error: error instanceof Error ? error.message : String(error)
});
}
if (splitBrain) {
invalidated = true;
if (timer) {
clearInterval(timer);
timer = null;
}
terminateForSqliteWalSplitBrain(splitBrain, options.databaseLabel);
}
}
maintain();
}, timerIntervalMs));
timer.unref?.();
}
return {
get health() {
return checkpointOwner.health;
},
checkpoint,
maintainPeriodic,
reclaimFreePages,
inspectIdle: () => runMaintenance(checkpointOwner.inspectIdle) ? "healthy" : "retire",
close: (closeOptions) => {
clearInterval(timer ?? void 0);
timer = null;
cancelSqliteWalWriteAdmission(db);
if (invalidated) return false;
return runMaintenance(() => runCheckpoint(closeOptions?.checkpointMode ?? checkpointMode));
}
};
}
let lastSqliteExitGroup;
function detachEmptySqliteExitGroup(group) {
if (group.pending.size > 0) return;
if (lastSqliteExitGroup === group) lastSqliteExitGroup = void 0;
process.removeListener("exit", group.dispatch);
}
/**
* Register a best-effort exit-time close for a SQLite handle cache. Returns an
* unregister callback the cache's orderly close path must invoke, so tests and
* runtime shutdowns do not accumulate listeners on shared worker processes.
*/
function registerSqliteCacheExitClose(closeAll) {
const registration = {
close: closeAll,
fired: false
};
let group = lastSqliteExitGroup;
if (!group || process.listeners("exit").at(-1) !== group.dispatch) {
const pending = /* @__PURE__ */ new Set([registration]);
const created = {
pending,
dispatch: () => {
const snapshot = [...pending];
for (const entry of snapshot) {
if (entry.fired) continue;
entry.fired = true;
pending.delete(entry);
detachEmptySqliteExitGroup(created);
try {
entry.close();
} catch {}
}
}
};
process.on("exit", created.dispatch);
lastSqliteExitGroup = group = created;
} else group.pending.add(registration);
const owner = group;
return () => {
owner.pending.delete(registration);
detachEmptySqliteExitGroup(owner);
};
}
/** Configure per-connection SQLite pragmas in the safe lock-retry/WAL order. */
function configureSqliteConnectionPragmas(db, options = {}) {
const { foreignKeys, synchronous, ...walOptions } = options;
const maintenance = configureSqliteWalMaintenance(db, walOptions);
try {
if (synchronous) db.exec(`PRAGMA synchronous = ${synchronous};`);
if (foreignKeys) db.exec("PRAGMA foreign_keys = ON;");
return maintenance;
} catch (error) {
try {
maintenance.close();
} catch (closeError) {
throw createSqliteLifecycleAggregateError([error, closeError], "SQLite connection pragma configuration and WAL maintenance cleanup both failed.", error);
}
throw error;
}
}
//#endregion
//#region src/infra/sqlite-snapshot-staging.ts
function stagingParent(root) {
const parent = path.basename(root) === "openclaw" ? path.dirname(root) : root;
return isSqliteSnapshotStagingName(path.basename(parent)) ? parent : void 0;
}
/** A private reader protects its bytes independently of the staging child. */
function acquireSqliteSnapshotReadToken(directory) {
return acquireSqliteSnapshotToken(directory, "read");
}
function sqliteSnapshotStagingError(tempDir, cause, allocation = false) {
markSqliteInspectionOperation(cause, "snapshot");
let destination = allocation;
let capacity = false;
let sqliteErrcode;
for (let depth = 0, error = cause; depth < 8 && error instanceof Error; depth += 1) {
const { code, errcode, path: errorPath } = error;
capacity ||= ["ENOSPC", "EDQUOT"].includes(code ?? "") || errcode === 13;
if (capacity || typeof errcode === "number" && [
13,
778,
1034,
1290
].includes(errcode) || `${errorPath ?? ""}${path.sep}`.startsWith(`${tempDir}${path.sep}`)) {
destination = true;
if (typeof errcode === "number") sqliteErrcode = errcode;
}
error = error.cause;
}
if (!destination) return cause;
const guidance = capacity ? "free disk space/quota" : "check filesystem health and write permissions";
const message = `${cause instanceof Error ? cause.message : String(cause)}${sqliteErrcode !== void 0 ? ` (SQLite errcode=${sqliteErrcode})` : ""}; snapshot staging root ${allocation ? tempDir : path.dirname(tempDir)}: ${guidance} or set XDG_CACHE_HOME to a writable filesystem`;
return new Error(message, { cause });
}
function createSqliteSnapshotStagingDirectorySync(root = resolvePrivateSqliteSnapshotStagingRoot(), allowLegacyWorker = false) {
const owned = createSqliteSnapshotStagingTokenSync(root, allowLegacyWorker);
registerSnapshotTempDirectory(owned.directory, owned.release);
return owned.directory;
}
/** Token workers remove disposable payload under their own native retirement fences. */
function createSqliteSnapshotStagingTokenSync(root = resolvePrivateSqliteSnapshotStagingRoot(), allowLegacyWorker = false) {
const parentDirectory = stagingParent(root);
const parent = parentDirectory ? acquireSqliteSnapshotToken(parentDirectory, "read") : void 0;
let directory;
try {
try {
directory = createPrivateSqliteTempDirectorySync(root, allowLegacyWorker ? `openclaw-sqlite-readonly-${process.pid}-` : SQLITE_SNAPSHOT_PREFIX);
} catch (error) {
throw sqliteSnapshotStagingError(root, error, true);
}
return {
directory,
release: acquireSqliteSnapshotToken(directory, "create")
};
} catch (error) {
if (directory) removeTempDirectory(directory);
throw error;
} finally {
parent?.();
}
}
Buffer.from([
217,
213,
5,
249,
32,
161,
99,
215
]);
resolveGlobalSingleton(Symbol.for("openclaw.sqliteSnapshotFlights"), () => /* @__PURE__ */ new Map());
//#endregion
//#region src/infra/sqlite-live-snapshot.ts
const liveOwners = resolveGlobalSingleton(Symbol.for("openclaw.sqliteLiveSnapshotOwners"), () => /* @__PURE__ */ new Map());
function registerLiveSqliteSnapshotOwner(options) {
const identity = readDatabasePathIdentitySync(options.databasePath);
const registration = {
assertCurrent: options.assertCurrent,
database: options.database,
owner: options.owner
};
liveOwners.set(identity.key, registration);
return () => {
if (liveOwners.get(identity.key) === registration) liveOwners.delete(identity.key);
};
}
//#endregion
//#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/infra/sqlite-schema-contract-assembly.ts
function createSqliteTableContract(tableName, table, tableList, indexes, triggers) {
const normalizedTriggers = triggers.map((trigger) => ({
name: trigger.name,
sql: normalizeSchemaSql(trigger.sql)
}));
const normalizedTableSql = table.sql?.startsWith("CREATE TABLE ") ? null : normalizeSchemaSql(table.sql);
const isVirtualTable = normalizedTableSql !== null && /^CREATE VIRTUAL TABLE /iu.test(normalizedTableSql);
return {
definition: isVirtualTable ? null : parseSqliteTableDefinition(table.sql, tableName),
indexes,
strict: tableList.strict,
triggers: normalizedTriggers,
virtualTableSql: isVirtualTable ? normalizedTableSql : null,
withoutRowid: tableList.wr
};
}
function createSqliteIndexContract(index, schemaSql, rows) {
const terms = rows.map(({ cid, coll, desc, key, name, seqno }) => ({
coll,
desc,
key,
kind: sqliteIndexTermKind(cid),
name,
seqno
}));
return {
name: index.name.startsWith("sqlite_autoindex_") ? null : index.name,
origin: index.origin,
partial: index.partial,
sql: normalizeSchemaSql(schemaSql),
terms,
unique: index.unique
};
}
function sqliteIndexTermKind(cid) {
return cid === -2 ? "expression" : cid === -1 ? "rowid" : "column";
}
function parseSqliteTableDefinition(sql, tableName) {
if (sql === null) throw new Error(`Could not inspect SQLite table definition for ${tableName}.`);
const open = findSqlCharacter(sql, "(");
if (open === -1) throw new Error(`SQLite table ${tableName} has no column definition.`);
const close = findSqlClosingParenthesis(sql, open);
const columns = /* @__PURE__ */ new Map();
const constraints = [];
for (const rawDefinition of splitSqlList(sql.slice(open + 1, close))) {
const definition = normalizeSqlWhitespace(rawDefinition);
if (!definition) continue;
const token = readSqlToken(definition, 0);
if (!token) throw new Error(`SQLite table ${tableName} contains an unreadable definition.`);
if (readTableConstraintKeyword(definition, token)) {
constraints.push(definition);
continue;
}
const columnName = normalizeSqlIdentifier(token.raw);
if (columns.has(columnName)) throw new Error(`SQLite table ${tableName} contains duplicate column ${columnName}.`);
columns.set(columnName, definition);
}
return {
columns: new Map([...columns].toSorted(([left], [right]) => left.localeCompare(right))),
constraints: constraints.toSorted()
};
}
//#endregion
//#region src/infra/sqlite-schema-contract.ts
const schemaContractCache = /* @__PURE__ */ new Map();
/** Reuse actual table facts only within one unchanged read transaction on this connection. */
function createSqliteTableContractReader(database) {
const tables = /* @__PURE__ */ new Map();
return (tableName) => {
if (!tables.has(tableName)) tables.set(tableName, collectSqliteTableContract(database, tableName));
return tables.get(tableName);
};
}
/**
* Require every object from one committed schema while allowing unrelated
* tables and indexes that do not replace a canonical object.
*/
function assertSqliteSchemaContains(database, databaseLabel, schemaSql, compatibility = {}, readTable) {
const issues = collectSqliteSchemaIssues(database, schemaSql, compatibility, readTable);
if (issues.length > 0) throwSqliteSchemaMismatches(databaseLabel, legacySqliteSchemaIssueMessages(issues));
}
/** Collect stable, machine-readable differences from one committed schema. */
function collectSqliteSchemaIssues(database, schemaSql, compatibility = {}, readTable) {
return runSqlitePinnedReadSnapshotSync(database, () => collectSqliteSchemaIssuesInSnapshot(database, schemaSql, compatibility, readTable));
}
function collectSqliteSchemaIssuesInSnapshot(database, schemaSql, compatibility, readTable) {
const expected = getSqliteSchemaContract(schemaSql);
const allowedMissingTables = new Set(compatibility.allowedMissingTables ?? []);
const allowedMissingIndexes = new Set(compatibility.allowedMissingIndexes ?? []);
const issues = [];
const add = (code, objectName, message) => {
issues.push(createSqliteSchemaIssue(code, objectName, message));
};
for (const [tableName, expectedTable] of expected) {
const actualTable = readTable ? readTable(tableName) : collectSqliteTableContract(database, tableName);
if (!actualTable) {
if (allowedMissingTables.has(tableName)) continue;
add("missing-table", tableName);
continue;
}
issues.push(...compareTableDefinitions(tableName, actualTable.definition, expectedTable.definition, compatibility, !allowedMissingTables.has(tableName)));
const actualIndexFingerprints = new Set(actualTable.indexes.map((index) => JSON.stringify(index)));
const expectedIndexFingerprints = /* @__PURE__ */ new Set();
for (const expectedIndex of expectedTable.indexes) {
const fingerprint = JSON.stringify(expectedIndex);
expectedIndexFingerprints.add(fingerprint);
if (!actualIndexFingerprints.has(fingerprint)) {
const objectName = expectedIndex.name ?? tableName;
if (expectedIndex.name && allowedMissingIndexes.has(expectedIndex.name) && !database.prepare("SELECT 1 FROM main.sqlite_schema WHERE type = 'index' AND name = ? COLLATE NOCASE LIMIT 1").get(expectedIndex.name)) continue;
add("missing-or-drifted-index", objectName, `missing or drifted index ${expectedIndex.name ?? `on ${tableName}`}`);
}
}
for (const actualIndex of actualTable.indexes) if (actualIndex.unique === 1 && !expectedIndexFingerprints.has(JSON.stringify(actualIndex))) add("unexpected-unique-index", actualIndex.name ?? tableName, `unexpected unique index ${actualIndex.name ?? `on ${tableName}`}`);
const optionalCanonicalTriggerGroups = collectOptionalCanonicalTriggerGroups(database, compatibility, tableName);
const optionalCanonicalTriggers = optionalCanonicalTriggerGroups.flatMap((group) => group.triggers);
const allowedMissingCanonicalTriggers = optionalCanonicalTriggerGroups.filter((group) => group.optional).flatMap((group) => group.triggers);
for (const expectedTrigger of expectedTable.triggers) {
if (allowedMissingCanonicalTriggers.some((canonicalTrigger) => canonicalTrigger.name === expectedTrigger.name)) continue;
if (!containsTrigger(actualTable.triggers, expectedTrigger)) add("missing-or-drifted-trigger", expectedTrigger.name);
}
for (const triggerGroup of optionalCanonicalTriggerGroups) {
const isPresent = actualTable.triggers.some((actualTrigger) => triggerGroup.triggers.some((canonicalTrigger) => actualTrigger.name === canonicalTrigger.name));
if (triggerGroup.optional && !isPresent) continue;
for (const canonicalTrigger of triggerGroup.triggers) if (!containsTrigger(actualTable.triggers, canonicalTrigger)) add("missing-or-drifted-trigger", canonicalTrigger.name);
}
for (const actualTrigger of actualTable.triggers) if (!containsTrigger(expectedTable.triggers, actualTrigger) && !containsTrigger(optionalCanonicalTriggers, actualTrigger)) add("unexpected-trigger", actualTrigger.name);
if (actualTable.virtualTableSql !== expectedTable.virtualTableSql) add("virtual-table-definition-drift", tableName);
if (actualTable.strict !== expectedTable.strict || actualTable.withoutRowid !== expectedTable.withoutRowid) add("table-options-drift", tableName);
}
return issues;
}
/** Require stable canonical tables before a version-specific additive migration. */
function assertSqliteSchemaTablesPresent(database, databaseLabel, schemaSql, options = {}) {
const allowedMissingTables = new Set(options.allowedMissingTables ?? []);
const requiredTables = getCanonicalSqliteTableNames(schemaSql).filter((tableName) => !allowedMissingTables.has(tableName));
const missingTables = [];
const batchSize = 500;
for (let offset = 0; offset < requiredTables.length; offset += batchSize) {
const tables = requiredTables.slice(offset, offset + batchSize);
const expected = tables.map((_table, ordinal) => `(${ordinal}, ?)`).join(", ");
const present = database.prepare(`WITH expected(ordinal, name) AS (VALUES ${expected})
SELECT ordinal FROM expected
WHERE EXISTS (
SELECT 1 FROM main.sqlite_schema
WHERE type = 'table' AND name = expected.name LIMIT 1
)`).all(...tables);
const presentOrdinals = new Set(present.map((row) => row.ordinal));
for (const [ordinal, tableName] of tables.entries()) if (!presentOrdinals.has(ordinal)) missingTables.push(`missing table ${tableName}`);
}
if (missingTables.length > 0) throwSqliteSchemaMismatches(databaseLabel, missingTables);
}
/** Return every explicit named index owned by one committed schema. */
function getCanonicalSqliteNamedIndexContracts(schemaSql) {
const schema = getSqliteSchemaContract(schemaSql);
const indexes = [];
for (const [tableName, table] of schema) for (const fingerprint of table.indexes) {
if (fingerprint.name === null || fingerprint.sql === null || fingerprint.origin !== "c") continue;
indexes.push({
definition: readCanonicalIndexDefinition(fingerprint),
fingerprint,
name: fingerprint.name,
tableName,
unique: fingerprint.unique === 1
});
}
return indexes;
}
/** Return every table owned by one committed schema. */
function getCanonicalSqliteTableNames(schemaSql) {
return [...getSqliteSchemaContract(schemaSql).keys()];
}
/** Inspect one explicit main-schema index using the canonical schema fingerprint shape. */
function collectSqliteNamedIndexContract(database, indexName) {
const row = database.prepare(`
SELECT tbl_name FROM (
SELECT name, sql, tbl_name FROM main.sqlite_schema WHERE type = 'index' AND name = ?
)
`).get(indexName);
if (!row || typeof row.tbl_name !== "string") return;
const index = database.prepare(`PRAGMA main.index_list(${quoteSqliteIdentifier(row.tbl_name)})`).all()?.find((candidate) => candidate.name === indexName);
return index ? collectSqliteIndexContract(database, index) : void 0;
}
function collectOptionalCanonicalTriggerGroups(database, compatibility, tableName) {
return (compatibility.optionalCanonicalTriggerGroups ?? []).filter((group) => group.tableName === tableName).map((group) => ({
optional: !group.optionalWhenTableMissing || !database.prepare("SELECT 1 FROM main.sqlite_schema WHERE type = 'table' AND name = ? LIMIT 1").get(group.optionalWhenTableMissing),
triggers: group.triggers.map((trigger) => ({
name: trigger.name,
sql: normalizeOptionalCanonicalTriggerSql(trigger.sql)
}))
}));
}
function normalizeOptionalCanonicalTriggerSql(sql) {
return normalizeSchemaSql(sql)?.replace(/^(CREATE TRIGGER) main\./iu, "$1 ") ?? null;
}
function getSqliteSchemaContract(schemaSql) {
let expected = schemaContractCache.get(schemaSql);
if (!expected) {
expected = buildSqliteSchemaContract(schemaSql);
schemaContractCache.set(schemaSql, expected);
}
return expected;
}
function collectCanonicalSqliteFacts(database) {
const tableOptions = database.prepare("PRAGMA table_list").all();
if (tableOptions.some((row) => {
const name = row.name.toLowerCase();
return name === "pragma_index_list" || name === "pragma_index_xinfo";
})) return;
const indexes = database.prepare(`
SELECT CAST(t.rowid AS TEXT) AS table_id, i.seq AS index_seq,
i.name, i.origin, i.partial, i."unique", d.sql
FROM sqlite_schema AS t
CROSS JOIN pragma_index_list(t.name) AS i
LEFT JOIN sqlite_schema AS d ON d.type = 'index' AND d.name = i.name
WHERE t.type = 'table' AND t.name NOT LIKE 'sqlite_%'
`).all();
const terms = database.prepare(`
SELECT CAST(t.rowid AS TEXT) AS table_id, i.seq AS index_seq,
x.seqno, x.cid, x.name, x."desc", x.coll, x."key"
FROM sqlite_schema AS t
CROSS JOIN pragma_index_list(t.name) AS i
CROSS JOIN pragma_index_xinfo(i.name) AS x
WHERE t.type = 'table' AND t.name NOT LIKE 'sqlite_%'
ORDER BY t.rowid, i.seq, x.seqno
`).all();
const triggers = database.prepare(`
SELECT tbl_name, name, sql FROM sqlite_schema WHERE type = 'trigger' ORDER BY tbl_name, name
`).all();
return {
tableOptions,
indexes: groupCanonicalRows(indexes, (row) => row.table_id),
terms: groupCanonicalRows(terms, (row) => row.table_id),
triggers: groupCanonicalRows(triggers, (row) => row.tbl_name)
};
}
function groupCanonicalRows(rows, key) {
const groups = /* @__PURE__ */ new Map();
for (const row of rows) {
const name = key(row);
const group = groups.get(name);
if (group) group.push(row);
else groups.set(name, [row]);
}
return groups;
}
function buildSqliteSchemaContract(schemaSql) {
const database = openNodeSqliteDatabase(":memory:");
try {
database.exec(schemaSql);
const rows = database.prepare(`
SELECT CAST(rowid AS TEXT) AS table_id, name, sql
FROM sqlite_schema
WHERE type = 'table'
AND name NOT LIKE 'sqlite_%'
ORDER BY name
`).all();
if (rows.length === 0) return /* @__PURE__ */ new Map();
const facts = collectCanonicalSqliteFacts(database);
if (!facts) return new Map(rows.map((table) => [table.name, collectSqliteTableContractFromRow(database, table.name, table)]));
return new Map(rows.map((table) => {
const tableList = facts.tableOptions.find((entry) => entry.name === table.name);
if (!tableList) throw new Error(`Could not inspect SQLite table options for ${table.name}.`);
const termsByIndex = groupCanonicalRows(facts.terms.get(table.table_id) ?? [], (term) => term.index_seq);
const indexes = (facts.indexes.get(table.table_id) ?? []).map((index) => createSqliteIndexContract(index, index.sql, termsByIndex.get(index.index_seq) ?? [])).toSorted(compareJson);
return [table.name, createSqliteTableContract(table.name, table, tableList, indexes, facts.triggers.get(table.name) ?? [])];
}));
} finally {
database.close();
}
}
function readCanonicalIndexDefinition(index) {
if (index.name === null || index.sql === null) throw new Error("Canonical SQLite named index is missing its schema definition.");
const prefix = (index.unique === 1 ? /^CREATE\s+UNIQUE\s+INDEX\s+/iu : /^CREATE\s+INDEX\s+/iu).exec(index.sql);
if (!prefix) throw new Error(`Canonical SQLite index ${index.name} has an unreadable definition.`);
const name = readSqlToken(index.sql, prefix[0].length);
if (!name || normalizeSqlIdentifier(name.raw) !== index.name.toLowerCase()) throw new Error(`Canonical SQLite index ${index.name} has an unexpected schema name.`);
const definition = index.sql.slice(name.end).trim();
if (!/^ON\s+/iu.test(definition)) throw new Error(`Canonical SQLite index ${index.name} has an unreadable target.`);
return definition;
}
function collectSqliteTableContract(database, tableName) {
const table = executeWithCachedStatement(database, "SELECT name, sql FROM sqlite_schema WHERE type = 'table' AND name = ?", [tableName], (statement) => statement.get(tableName));
if (!table) return;
return collectSqliteTableContractFromRow(database, tableName, table);
}
function collectSqliteTableContractFromRow(database, tableName, table) {
const quotedTable = quoteSqliteIdentifier(tableName);
const tableList = database.prepare(`PRAGMA table_list(${quotedTable})`).all().find((entry) => entry.name === tableName);
if (!tableList) throw new Error(`Could not inspect SQLite table options for ${tableName}.`);
return createSqliteTableContract(tableName, table, tableList, database.prepare(`PRAGMA index_list(${quotedTable})`).all().map((index) => collectSqliteIndexContract(database, index)).toSorted(compareJson), executeWithCachedStatement(database, `
SELECT name, sql
FROM sqlite_schema
WHERE type = 'trigger' AND tbl_name = ?
ORDER BY name
`, [tableName], (statement) => statement.all(tableName)));
}
function compareTableDefinitions(tableName, actual, expected, compatibility, allowCompatibleAdditiveColumns) {
const issues = [];
const add = (code, objectName) => {
issues.push(createSqliteSchemaIssue(code, objectName));
};
if (!actual || !expected) {
if (actual !== expected) add("table-definition-drift", tableName);
return issues;
}
const allowedMissingColumns = new Set(compatibility.allowedMissingColumns ?? []);
for (const [columnName, definition] of actual.columns) if (!expected.columns.has(columnName)) {
if (allowCompatibleAdditiveColumns && compatibility.allowCompatibleAdditiveColumns && isCompatibleAdditiveColumnDefinition(definition)) continue;
add("unexpected-column", `${tableName}.${columnName}`);
}
for (const [columnName, expectedDefinition] of expected.columns) {
const objectName = `${tableName}.${columnName}`;
const actualDefinition = actual.columns.get(columnName);
if (actualDefinition === void 0) {
if (!allowedMissingColumns.has(objectName)) add("missing-column", objectName);
continue;
}
if (actualDefinition === expectedDefinition) continue;
if (!(compatibility.allowedColumnDefinitions?.[objectName] ?? []).some((definition) => normalizeSqlWhitespace(definition) === actualDefinition)) add("column-definition-drift", objectName);
}
if (JSON.stringify(actual.constraints) !== JSON.stringify(expected.constraints)) add("table-constraint-drift", tableName);
return issues;
}
const SQLITE_STRICT_DATATYPES = /* @__PURE__ */ new Set([
"ANY",
"BLOB",
"INT",
"INTEGER",
"REAL",
"TEXT"
]);
function isCompatibleAdditiveColumnDefinition(definition) {
const name = readSqlToken(definition, 0);
const type = name ? readSqlToken(definition, name.end) : null;
return Boolean(type?.keyword && SQLITE_STRICT_DATATYPES.has(type.keyword) && definition.slice(type.end).trim().length === 0);
}
function collectSqliteIndexContract(database, index) {
const row = executeWithCachedStatement(database, "SELECT sql FROM sqlite_schema WHERE type = 'index' AND name = ?", [index.name], (statement) => statement.get(index.name));
const terms = database.prepare(`PRAGMA index_xinfo(${quoteSqliteIdentifier(index.name)})`).all();
return createSqliteIndexContract(index, typeof row?.sql === "string" ? row.sql : null, terms);
}
function containsTrigger(triggers, expected) {
return triggers.some((trigger) => trigger.name === expected.name && trigger.sql === expected.sql);
}
function compareJson(left, right) {
return JSON.stringify(left).localeCompare(JSON.stringify(right));
}
function readSqliteSchemaCookie(database) {
return database.prepare("PRAGMA schema_version").get()?.schema_version;
}
//#endregion
//#region src/infra/sqlite-strict.ts
const DEFAULT_STRICT_MIGRATION_BUSY_TIMEOUT_MS = 5e3;
const STRICT_MIGRATION_TABLE_PREFIX = "__openclaw_strict_migration_";
const SQLITE_ROWID_ALIASES = [
"_rowid_",
"rowid",
"oid"
];
function readMainTableList(db) {
return db.prepare("PRAGMA table_list").all().filter((row) => row.schema === "main" && typeof row.name === "string" && !row.name.startsWith("sqlite_"));
}
function readTableColumns(db, tableName) {
return db.prepare(`PRAGMA table_xinfo(${quoteSqliteIdentifier(tableName)})`).all();
}
function readVisibleColumns(db, tableName) {
return readTableColumns(db, tableName).filter((row) => Number(row.hidden ?? 0) === 0).map((row) => {
if (typeof row.name !== "string" || row.name.length === 0) throw new Error(`SQLite table ${tableName} has an invalid column name`);
return row.name;
});
}
function readTableRowidModel(db, tableName, tableRow) {
if (Number(tableRow.wr ?? 0) === 1) return {
alias: null,
storage: "without-rowid"
};
const columns = readTableColumns(db, tableName);
const primaryKeyColumns = columns.filter((column) => Number(column.pk ?? 0) > 0);
const primaryKeyIndex = db.prepare(`SELECT 1 AS found FROM pragma_index_list(?) WHERE origin = 'pk' LIMIT 1`).get(tableName);
const primaryKeyType = primaryKeyColumns[0]?.type;
if (primaryKeyColumns.length === 1 && typeof primaryKeyType === "string" && primaryKeyType.toUpperCase() === "INTEGER" && !primaryKeyIndex) return {
alias: null,
storage: "integer-primary-key"
};
const declaredNames = new Set(columns.flatMap((column) => typeof column.name === "string" ? [column.name.toLowerCase()] : []));
const alias = SQLITE_ROWID_ALIASES.find((candidate) => !declaredNames.has(candidate)) ?? null;
if (!alias) throw new Error(`SQLite table ${tableName} shadows every rowid alias; its implicit rowids cannot be migrated safely`);
return {
alias,
storage: "implicit"
};
}
function readCanonicalStrictTables(schemaSql) {
const canonical = openNodeSqliteDatabase(":memory:");
try {
canonical.exec(schemaSql);
const tables = readMainTableList(canonical).filter((row) => row.type === "table");
const nonStrict = tables.filter((row) => Number(row.strict ?? 0) !== 1).map((row) => row.name);
if (nonStrict.length > 0) throw new Error(`Canonical SQLite schema contains non-STRICT tables: ${nonStrict.toSorted().join(", ")}`);
return tables.map((row) => {
const schemaRow = canonical.prepare("SELECT sql FROM sqlite_schema WHERE type = 'table' AND name = ?").get(row.name);
if (typeof schemaRow?.sql !== "string") throw new Error(`Canonical SQLite table ${row.name} has no CREATE statement`);
const rowidModel = readTableRowidModel(canonical, row.name, row);
return {
columns: readVisibleColumns(canonical, row.name),
createSql: schemaRow.sql,
name: row.name,
rowidAlias: rowidModel.alias,
rowidStorage: rowidModel.storage,
usesAutoincrement: /\bAUTOINCREMENT\b/iu.test(schemaRow.sql)
};
}).toSorted((left, right) => left.name.localeCompare(right.name));
} finally {
canonical.close();
}
}
function rewriteCreateTableName(createSql, replacementName) {
const openingParen = createSql.indexOf("(");
if (openingParen === -1) throw new Error("Canonical SQLite table CREATE statement has no column list");
return `CREATE TABLE ${quoteSqliteIdentifier(replacementName)} ${createSql.slice(openingParen)}`;
}
function readPreservedSchemaObjects(db, tableNames) {
return db.prepare("SELECT type, name, tbl_name, sql FROM sqlite_schema WHERE type IN ('index', 'trigger', 'view')").all().flatMap((row) => {
if (row.type !== "index" && row.type !== "trigger" && row.type !== "view" || typeof row.name !== "string" || typeof row.tbl_name !== "string" || typeof row.sql !== "string" || row.type === "index" && !tableNames.has(row.tbl_name)) return [];
return [{
name: row.name,
sql: row.sql,
type: row.type
}];
}).toSorted((left, right) => {
const typeOrder = {
view: 0,
index: 1,
trigger: 2
};
return typeOrder[left.type] - typeOrder[right.type] || left.name.localeCompare(right.name);
});
}
function readAutoincrementHighWater(db, tableName) {
if (!db.prepare("SELECT 1 AS found FROM sqlite_schema WHERE type = 'table' AND name = 'sqlite_sequence'").get()) return null;
const row = db.prepare("SELECT CAST(seq AS TEXT) AS seq FROM sqlite_sequence WHERE name = ?").get(tableName);
if (row === void 0) return null;
const normalized = typeof row.seq === "string" ? /^(\d+)(?:\.0+)?$/u.exec(row.seq)?.[1] : null;
if (!normalized) throw new Error(`SQLite table ${tableName} has an invalid AUTOINCREMENT high-water mark (${typeof row.seq}: ${String(row.seq)})`);
return normalized;
}
function restoreAutoincrementHighWater(db, tableName, previousHighWater) {
if (previousHighWater === null) return;
const currentHighWater = readAutoincrementHighWater(db, tableName);
const restored = currentHighWater === null || BigInt(previousHighWater) > BigInt(currentHighWater) ? previousHighWater : currentHighWater;
db.prepare("DELETE FROM sqlite_sequence WHERE name = ?").run(tableName);
db.prepare("INSERT INTO sqlite_sequence (name, seq) VALUES (?, CAST(? AS INTEGER))").run(tableName, restored);
}
function assertMatchingColumns(tableName, currentColumns, canonicalColumns) {
const current = new Set(currentColumns);
const canonical = new Set(canonicalColumns);
const missing = canonicalColumns.filter((column) => !current.has(column));
const extra = currentColumns.filter((column) => !canonical.has(column));
if (missing.length === 0 && extra.length === 0) return;
const details = [missing.length > 0 ? `missing ${missing.join(", ")}` : "", extra.length > 0 ? `extra ${extra.join(", ")}` : ""].filter(Boolean).join("; ");
throw new Error(`SQLite table ${tableName} does not match its canonical columns (${details})`);
}
function readForeignKeysEnabled(db) {
const row = db.prepare("PRAGMA foreign_keys").get();
return Number(row?.foreign_keys ?? 0) === 1;
}
/**
* Rebuild canonical non-STRICT tables inside the caller's transaction.
* Foreign-key enforcement must be disabled before BEGIN; integrity is checked
* before this function returns so any bad row or relationship rolls back.
*/
function migrateSqliteSchemaToStrictInTransaction(db, schemaSql, options = {}) {
if (!db.isTransaction) throw new Error("SQLite STRICT schema migration requires an active transaction");
const canonicalTables = readCanonicalStrictTables(schemaSql);
db.exec(schemaSql);
const currentTableRows = new Map(readMainTableList(db).filter((row) => row.type === "table").map((row) => [row.name, row]));
const tablesToMigrate = canonicalTables.filter((table) => Number(currentTableRows.get(table.name)?.strict ?? 0) !== 1);
if (tablesToMigrate.length === 0) return { migratedTables: [] };
if (readForeignKeysEnabled(db)) throw new Error("SQLite STRICT schema migration requires foreign_keys=OFF before BEGIN");
const preservedObjects = readPreservedSchemaObjects(db, new Set(tablesToMigrate.map((table) => table.name)));
for (const object of preservedObjects) if (object.type === "trigger") db.exec(`DROP TRIGGER ${quoteSqliteIdentifier(object.name)};`);
for (const object of preservedObjects) if (object.type === "view") db.exec(`DROP VIEW ${quoteSqliteIdentifier(object.name)};`);
for (const [index, table] of tablesToMigrate.entries()) {
const migrationTable = `${STRICT_MIGRATION_TABLE_PREFIX}${index}_${table.name}`;
if (currentTableRows.has(migrationTable)) throw new Error(`SQLite STRICT migration table already exists: ${migrationTable}`);
const currentColumns = readVisibleColumns(db, table.name);
assertMatchingColumns(table.name, currentColumns, table.columns);
const currentTableRow = currentTableRows.get(table.name);
if (!currentTableRow) throw new Error(`SQLite table ${table.name} disappeared during STRICT migration`);
const currentRowidModel = readTableRowidModel(db, table.name, currentTableRow);
if (currentRowidModel.storage !== table.rowidStorage) throw new Error(`SQLite table ${table.name} changes rowid storage from ${currentRowidModel.storage} to ${table.rowidStorage}; refusing an identity-changing STRICT migration`);
const previousHighWater = table.usesAutoincrement ? readAutoincrementHighWater(db, table.name) : null;
db.exec(rewriteCreateTableName(table.createSql, migrationTable));
const columns = table.columns.map(quoteSqliteIdentifier);
if (table.rowidAlias) columns.unshift(quoteSqliteIdentifier(table.rowidAlias));
const copyColumns = columns.join(", ");
try {
db.exec(`INSERT INTO ${quoteSqliteIdentifier(migrationTable)} (${copyColumns}) SELECT ${copyColumns} FROM ${quoteSqliteIdentifier(table.name)};`);
} catch (error) {
throw new Error(`Failed migrating SQLite table ${table.name} to STRICT`, { cause: error });
}
db.exec(`DROP TABLE ${quoteSqliteIdentifier(table.name)};`);
db.exec(`ALTER TABLE ${quoteSqliteIdentifier(migrationTable)} RENAME TO ${quoteSqliteIdentifier(table.name)};`);
restoreAutoincrementHighWater(db, table.name, previousHighWater);
}
db.exec(schemaSql);
const findObject = db.prepare("SELECT 1 AS found FROM sqlite_schema WHERE type = ? AND name = ? LIMIT 1");
for (const object of preservedObjects) if (!findObject.get(object.type, object.name)) db.exec(object.sql);
assertSqliteIntegrity(db, options.databaseLabel ?? "SQLite STRICT schema migration");
return { migratedTables: tablesToMigrate.map((table) => table.name) };
}
/** Atomically upgrade OpenClaw-owned tables described by a canonical STRICT schema. */
function migrateSqliteSchemaToStrict(db, schemaSql, options = {}) {
if (db.isTransaction) throw new Error("SQLite STRICT schema migration cannot start inside a transaction");
const foreignKeysWereEnabled = readForeignKeysEnabled(db);
if (foreignKeysWereEnabled) db.exec("PRAGMA foreign_keys = OFF;");
try {
return runSqliteImmediateTransactionSync(db, () => migrateSqliteSchemaToStrictInTransaction(db, schemaSql, options), {
busyTimeoutMs: options.busyTimeoutMs ?? DEFAULT_STRICT_MIGRATION_BUSY_TIMEOUT_MS,
databaseLabel: options.databaseLabel,
operationLabel: "sqlite.strict-schema-migration"
});
} finally {
if (foreignKeysWereEnabled) db.exec("PRAGMA foreign_keys = ON;");
}
}
[
"insert",
"update",
"delete"
].map((event) => ({
name: `memory_index_chunks_revision_after_${event}`,
sql: `CREATE TRIGGER IF NOT EXISTS memory_index_chunks_revision_after_${event}
AFTER ${event.toUpperCase()} ON memory_index_chunks
BEGIN UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1; END;`
}));
//#endregion
//#region src/state/openclaw-agent-schema.ts
const OPENCLAW_AGENT_SCHEMA_SQL = process.getBuiltinModule("node:fs").readFileSync(fileURLToPath(new URL("./openclaw-agent-schema.sql", import.meta.url)), "utf8");
//#endregion
//#region src/state/openclaw-agent-db-additive-columns.ts
const SESSION_OWNER_COLUMN_DEFINITIONS = [
{
columnName: "owner_actor_type",
dataType: "TEXT",
tableName: "session_nodes"
},
{
columnName: "owner_actor_id",
dataType: "TEXT",
tableName: "session_nodes"
},
{
columnName: "owner_assigned_by_type",
dataType: "TEXT",
tableName: "session_nodes"
},
{
columnName: "owner_assigned_by_id",
dataType: "TEXT",
tableName: "session_nodes"
},
{
columnName: "owner_assigned_at",
dataType: "INTEGER",
tableName: "session_nodes"
}
];
const LEGACY_ACP_MIGRATION_COLUMN_DEFINITION = {
columnName: "legacy_acp_migration_json",
dataType: "TEXT",
tableName: "session_nodes"
};
const CANONICAL_READY_COLUMN_DEFINITION = {
columnName: "canonical_ready",
dataType: "TEXT",
tableName: "session_key_contract"
};
const FIRST_USE_ADDITIVE_AGENT_COLUMN_DEFINITIONS = [
...SESSION_OWNER_COLUMN_DEFINITIONS,
LEGACY_ACP_MIGRATION_COLUMN_DEFINITION,
CANONICAL_READY_COLUMN_DEFINITION
];
//#endregion
//#region src/state/openclaw-agent-board-schema.ts
const OPENCLAW_AGENT_BOARD_SCHEMA_SQL = extractSqliteTableSchema(OPENCLAW_AGENT_SCHEMA_SQL, "board_tabs", {
endMarker: "CREATE TABLE IF NOT EXISTS session_progress_cards (",
includeEndMarker: false,
errorMessage: "OpenClaw agent board schema markers are missing from the canonical schema."
});
const OPENCLAW_AGENT_SCHEMA_WITHOUT_BOARD_SQL = OPENCLAW_AGENT_SCHEMA_SQL.replace(OPENCLAW_AGENT_BOARD_SCHEMA_SQL, "");
const AGENT_PROGRESS_CARD_SCHEMA_SQL = extractSqliteTableSchema(OPENCLAW_AGENT_SCHEMA_WITHOUT_BOARD_SQL, "session_progress_cards", {
endMarker: "CREATE TABLE IF NOT EXISTS heartbeat_outcomes (",
includeEndMarker: false,
errorMessage: "OpenClaw agent progress-card schema markers are missing."
});
OPENCLAW_AGENT_SCHEMA_WITHOUT_BOARD_SQL.replace(AGENT_PROGRESS_CARD_SCHEMA_SQL, "");
[...FIRST_USE_ADDITIVE_AGENT_COLUMN_DEFINITIONS.map(({ columnName, tableName }) => `${tableName}.${columnName}`)];
resolveGlobalSingleton(Symbol.for("openclaw.agentDatabaseSettledReadOpenFailures"), () => /* @__PURE__ */ new WeakSet());
resolveGlobalSingleton(Symbol.for("openclaw.agentCanonicalValidationSchemas"), () => /* @__PURE__ */ new WeakMap());
resolveGlobalSingleton(Symbol.for("openclaw.agentDatabaseIdentities"), () => /* @__PURE__ */ new WeakMap());
resolveGlobalSingleton(Symbol.for("openclaw.agentDatabaseAsyncResources"), () => ({
active: /* @__PURE__ */ new Set(),
closing: /* @__PURE__ */ new Map(),
selections: /* @__PURE__ */ new Map()
}));
//#endregion
//#region src/state/openclaw-agent-db-validation-cache.ts
const validatedPaths = resolveGlobalSingleton(Symbol.for("openclaw.agentDatabaseValidatedPaths"), () => /* @__PURE__ */ new Map(), () => clearOpenClawAgentDatabaseValidationCache());
resolveGlobalSingleton(Symbol.for("openclaw.agentDatabaseValidationBindings"), () => /* @__PURE__ */ new WeakMap());
function invalidateOpenClawAgentDatabaseValidation(pathname, identity) {
const resolved = resolveDatabasePathKey(pathname);
const currentIdentity = identity ?? validatedPaths.get(resolved)?.validation?.identity;
const paths = /* @__PURE__ */ new Set([resolved]);
if (currentIdentity) {
for (const [candidate, entry] of validatedPaths) if (entry.validation?.identity === currentIdentity) paths.add(candidate);
}
for (const candidate of paths) {
const entry = validatedPaths.get(candidate);
const validation = entry?.validation;
if (validation) Atomics.store(new Int32Array(validation.valid), 0, 0);
validatedPaths.set(candidate, {
agentId: entry?.agentId,
validation,
integrityVerified: false,
revoked: true
});
}
}
function clearOpenClawAgentDatabaseValidationCache(rootPath) {
const rootKey = rootPath === void 0 ? void 0 : resolveDatabasePathKey(rootPath);
for (const pathname of validatedPaths.keys()) if (rootKey === void 0 || isPathInside(rootKey, pathname)) {
invalidateOpenClawAgentDatabaseValidation(pathname);
validatedPaths.delete(pathname);
}
}
//#endregion
//#region src/state/openclaw-quarantine-store.ts
const OPENCLAW_QUARANTINE_SCHEMA_VERSION = 2;
const OPENCLAW_QUARANTINE_BUSY_TIMEOUT_MS = 5e3;
function createOpenClawDatabaseVerificationError(kind, pathname, storedError) {
const error = /* @__PURE__ */ new Error(`OpenClaw ${kind} database ${pathname} is quarantined after integrity verification failed: ${storedError ?? "unknown integrity error"}. Restore the database from a backup or repair it, then run openclaw doctor --fix to clear the quarantine. See ${OPENCLAW_DATABASE_SCHEMA_DOCS_URL}.`);
error.name = "SqliteIntegrityError";
return error;
}
function readQuarantineSchemaVersion(database, storePath) {
const userVersion = database.prepare("PRAGMA user_version").get()?.user_version;
if (typeof userVersion !== "number" || !Number.isInteger(userVersion)) throw new Error(`OpenClaw quarantine store ${storePath} has an invalid schema version.`);
return userVersion;
}
/** Read one authoritative quarantine decision without creating the store. */
function readOpenClawDatabaseQuarantine(pathname, options = {}) {
const storePath = resolveQuarantineStorePath(options.env ?? process.env);
if (!existsSync(storePath)) return;
const database = openNodeSqliteDatabase(storePath);
let outcome;
try {
outcome = { value: readQuarantineDecision(database, pathname, storePath) };
} catch (error) {
outcome = { error };
}
try {
database.close();
} catch (closeError) {
throw new OpenClawQuarantineReadCleanupError("error" in outcome ? [outcome.error, closeError] : [closeError], "value" in outcome ? outcome.value : void 0);
}
if ("error" in outcome) throw outcome.error;
return outcome.value;
}
function readQuarantineDecision(database, pathname, storePath) {
database.exec(`PRAGMA busy_timeout = ${OPENCLAW_QUARANTINE_BUSY_TIMEOUT_MS};`);
const userVersion = readQuarantineSchemaVersion(database, storePath);
if (userVersion === 0) return;
if (userVersion > OPENCLAW_QUARANTINE_SCHEMA_VERSION) throw new Error(`OpenClaw quarantine store ${storePath} uses newer schema version ${userVersion}.`);
const generationColumn = userVersion >= 2 ? ", verified_generation" : "";
const row = database.prepare(`SELECT kind, reason, quarantined_at${generationColumn} FROM quarantined_databases WHERE path = ? LIMIT 1`).get(path.resolve(pathname));
if (!row) return;
if (row.kind !== "agent" && row.kind !== "state" || typeof row.reason !== "string" || typeof row.quarantined_at !== "number" || !Number.isInteger(row.quarantined_at) || row.verified_generation !== void 0 && row.verified_generation !== null && typeof row.verified_generation !== "string") throw new Error(`OpenClaw quarantine store ${storePath} contains an invalid row.`);
if (typeof row.verified_generation === "string") {
let verifiedGeneration;
try {
verifiedGeneration = parseSqliteFileGeneration(row.verified_generation);
} catch {
throw new Error(`OpenClaw quarantine store ${storePath} contains an invalid row.`);
}
try {
const currentGeneration = readStableSqliteFileGeneration(path.resolve(pathname));
if (!sameSqliteFileGeneration(verifiedGeneration, currentGeneration)) return;
} catch {
return;
}
}
return {
kind: row.kind,
quarantinedAt: row.quarantined_at,
reason: row.reason
};
}
/** Runtime opens refuse recorded damage while tolerating a broken quarantine index. */
function readOpenClawDatabaseQuarantineFailure(kind, pathname, options = {}) {
let quarantine;
let cleanupFailure;
try {
quarantine = readOpenClawDatabaseQuarantine(pathname, options);
} catch (error) {
if (!(error instanceof OpenClawQuarantineReadCleanupError)) return;
if (!error.quarantine) throw error;
quarantine = error.quarantine;
cleanupFailure = error;
}
if (!quarantine) return;
const failure = createOpenClawDatabaseVerificationError(kind, pathname, quarantine.reason);
if (cleanupFailure) failure.cause = cleanupFailure;
return failure;
}
//#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);
}
},
retainFailedClose: () => operations.retainFailed(database)
});
scope?.own(reference, "shared-references", () => reference.release());
return reference;
};
const findReadDatabase = (pathname, ownership) => {
const scope = admit(pathname, ownership);
const database = state.cachedDatabases.get(path.resolve(pathname));
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();
operations.touch(database);
}
};
};
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;
return { release() {
if (released || owner.cleanupComplete) {
released = true;
return;
}
owner.references.delete(reference);
if (owner.retirement && !owner.retirement.isCurrent()) {
owner.retirement = void 0;
owner.retiring = false;
}
if (params.retirement?.isCurrent() && !owner.retirement?.ordinary) owner.retirement = params.retirement;
if (owner.references.size > 0 || !owner.retirement) {
released = true;
return;
}
owner.retiring = true;
try {
owner.retirement.retire();
} catch (error) {
params.retainFailedClose();
throw error;
}
owner.retirement = void 0;
released = true;
} };
}
//#endregion
//#region src/infra/sqlite-handle-lifecycle.ts
/** Idle native connections share one retention window across SQLite owners. */
const SQLITE_IDLE_HANDLE_TTL_MS = 18e5;
//#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 || borrowers.get(database.db)?.references.size || idleReferences.get(database.db)?.size)) {
touch(database);
return;
}
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/shared/lazy-promise.ts
/**
* Creates a small promise cache that dedupes concurrent loads and can be cleared manually.
*
* Rejections are evicted by default so transient dynamic-import/runtime failures can recover.
*/
function createLazyPromiseLoader(load, options = {}) {
let promise;
const createPromise = () => {
const loaded = Promise.resolve().then(load);
if (options.cacheRejections !== true) loaded.catch(() => {
if (promise === loaded) promise = void 0;
});
return loaded;
};
return {
load() {
promise ??= createPromise();
return promise;
},
peek() {
return promise;
},
clear() {
promise = void 0;
}
};
}
/** Creates a reusable function that resolves one cached promise at a time. */
function createLazyPromise(load, options) {
const loader = createLazyPromiseLoader(load, options);
return () => loader.load();
}
createLazyPromise(() => import("./subsystem-Bga3ZpNK.js").then((n) => n.n).then(({ createSubsystemLogger }) => createSubsystemLogger("env")), { cacheRejections: true });
const ENV_NORMALIZATION_KEY_GROUPS = [["ZAI_API_KEY", "Z_AI_API_KEY"]];
/** Normalizes the legacy Z_AI_API_KEY spelling into the canonical ZAI_API_KEY env var. */
function normalizeZaiEnv(env = process.env) {
if (!env.ZAI_API_KEY?.trim() && env.Z_AI_API_KEY?.trim()) env.ZAI_API_KEY = env.Z_AI_API_KEY;
}
/** Expands env keys to include aliases that process-wide normalization treats as equivalent. */
function expandEnvNormalizationKeys(keys) {
const expanded = /* @__PURE__ */ new Set();
for (const key of keys) for (const normalizedKey of resolveEnvNormalizationKeys(key)) expanded.add(normalizedKey);
return expanded;
}
/** Resolves one env key to its canonical-first runtime normalization group. */
function resolveEnvNormalizationKeys(key) {
const normalizedKey = process.platform === "win32" ? key.toUpperCase() : key;
return ENV_NORMALIZATION_KEY_GROUPS.find((group) => group.some((candidate) => candidate === normalizedKey)) ?? [normalizedKey];
}
/** Interprets common human/operator truthy env strings. */
function isTruthyEnvValue(value) {
return parseBooleanValue(value) === true;
}
//#endregion
//#region src/infra/host-env-security-policy.json
var host_env_security_policy_default = {
blockedEverywhereKeys: [
"NODE_OPTIONS",
"NODE_PATH",
"NODE_REDIRECT_WARNINGS",
"NODE_REPL_EXTERNAL_MODULE",
"NODE_REPL_HISTORY",
"NODE_V8_COVERAGE",
"PYTHONHOME",
"PYTHONPATH",
"PERL5LIB",
"PERL5OPT",
"RUBYLIB",
"RUBYOPT",
"BASHOPTS",
"BASH_ENV",
"ENV",
"KSH_ENV",
"BROWSER",
"GIT_ALLOW_PROTOCOL",
"GIT_EDITOR",
"GIT_EXTERNAL_DIFF",
"GIT_DIR",
"GIT_WORK_TREE",
"GIT_COMMON_DIR",
"GIT_EXEC_PATH",
"GIT_INDEX_FILE",
"GIT_OBJECT_DIRECTORY",
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
"GIT_NAMESPACE",
"GIT_PROTOCOL_FROM_USER",
"GIT_SEQUENCE_EDITOR",
"GIT_TEMPLATE_DIR",
"GIT_SSL_NO_VERIFY",
"GIT_SSL_CAINFO",
"GIT_SSL_CAPATH",
"CC",
"CPP",
"CXX",
"CXXCPP",
"CARGO_BUILD_RUSTC",
"CARGO_BUILD_RUSTC_WRAPPER",
"CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER",
"CARGO_BUILD_RUSTDOC",
"RUSTC",
"RUSTC_WRAPPER",
"RUSTC_WORKSPACE_WRAPPER",
"RUSTDOC",
"CMAKE_C_COMPILER",
"CMAKE_CXX_COMPILER",
"SHELL",
"SHELLOPTS",
"PS4",
"GCONV_PATH",
"IFS",
"SSLKEYLOGFILE",
"JAVA_OPTS",
"JAVA_TOOL_OPTIONS",
"_JAVA_OPTIONS",
"JDK_JAVA_OPTIONS",
"PYTHONBREAKPOINT",
"DOTNET_STARTUP_HOOKS",
"DOTNET_ADDITIONAL_DEPS",
"FPATH",
"GLIBC_TUNABLES",
"MAVEN_OPTS",
"MAKE",
"MAKEFLAGS",
"MFLAGS",
"SBT_OPTS",
"GRADLE_OPTS",
"ANT_OPTS",
"HGRCPATH",
"HGEDITOR",
"HGMERGE",
"EXINIT",
"VIMINIT",
"MYVIMRC",
"GVIMINIT",
"LUA_INIT",
"LUA_INIT_5_1",
"LUA_INIT_5_2",
"LUA_INIT_5_3",
"LUA_INIT_5_4",
"EMACSLOADPATH",
"RUBYSHELL",
"GIT_HOOK_PATH",
"SVN_EDITOR",
"SVN_SSH",
"BZR_EDITOR",
"BZR_SSH",
"BZR_PLUGIN_PATH",
"SUDO_ASKPASS",
"JULIA_EDITOR",
"CONFIG_SITE",
"CONFIG_SHELL",
"CMAKE_TOOLCHAIN_FILE",
"CATALINA_OPTS",
"CORECLR_PROFILER",
"HELM_PLUGINS",
"PACKER_PLUGIN_PATH",
"VAGRANT_VAGRANTFILE",
"ERL_AFLAGS",
"ERL_FLAGS",
"ERL_ZFLAGS",
"ELIXIR_ERL_OPTIONS",
"R_ENVIRON",
"R_PROFILE",
"R_ENVIRON_USER",
"R_PROFILE_USER",
"TCLLIBPATH",
"HOSTALIASES"
],
blockedOverrideOnlyKeys: [
"HOME",
"GRADLE_USER_HOME",
"ZDOTDIR",
"GIT_DIR",
"GIT_WORK_TREE",
"GIT_COMMON_DIR",
"GIT_INDEX_FILE",
"GIT_OBJECT_DIRECTORY",
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
"GIT_NAMESPACE",
"GIT_SSH_COMMAND",
"GIT_SSH",
"GIT_PROXY_COMMAND",
"GIT_ASKPASS",
"GIT_SSL_NO_VERIFY",
"GIT_SSL_CAINFO",
"GIT_SSL_CAPATH",
"SSH_ASKPASS",
"LESSOPEN",
"LESSCLOSE",
"PAGER",
"MANPAGER",
"GIT_PAGER",
"EDITOR",
"VISUAL",
"FCEDIT",
"SUDO_EDITOR",
"PROMPT_COMMAND",
"HISTFILE",
"PERL5DB",
"PERL5DBCMD",
"OPENSSL_CONF",
"OPENSSL_ENGINES",
"PYTHONSTARTUP",
"WGETRC",
"CURL_HOME",
"CLASSPATH",
"CFLAGS",
"CGO_CFLAGS",
"CGO_LDFLAGS",
"GOFLAGS",
"MAKEFLAGS",
"MFLAGS",
"CORECLR_PROFILER_PATH",
"PHPRC",
"PHP_INI_SCAN_DIR",
"DENO_DIR",
"BUN_CONFIG_REGISTRY",
"YARN_RC_FILENAME",
"HTTP_PROXY",
"HTTPS_PROXY",
"ALL_PROXY",
"NO_PROXY",
"NODE_TLS_REJECT_UNAUTHORIZED",
"NODE_EXTRA_CA_CERTS",
"SSL_CERT_FILE",
"SSL_CERT_DIR",
"REQUESTS_CA_BUNDLE",
"CURL_CA_BUNDLE",
"DOCKER_HOST",
"DOCKER_TLS_VERIFY",
"DOCKER_CERT_PATH",
"PIP_INDEX_URL",
"PIP_PYPI_URL",
"PIP_EXTRA_INDEX_URL",
"PIP_CONFIG_FILE",
"PIP_FIND_LINKS",
"PIP_TRUSTED_HOST",
"UV_INDEX",
"UV_INDEX_URL",
"UV_PYTHON",
"UV_EXTRA_INDEX_URL",
"UV_DEFAULT_INDEX",
"DOCKER_CONTEXT",
"LIBRARY_PATH",
"LDFLAGS",
"CPATH",
"C_INCLUDE_PATH",
"CPLUS_INCLUDE_PATH",
"OBJC_INCLUDE_PATH",
"GOPROXY",
"GONOSUMCHECK",
"GONOSUMDB",
"GONOPROXY",
"GOPRIVATE",
"GOENV",
"GOPATH",
"HGRCPATH",
"PYTHONUSERBASE",
"RUSTC_WRAPPER",
"RUSTFLAGS",
"RUSTUP_DIST_ROOT",
"RUSTUP_DIST_SERVER",
"RUSTUP_HOME",
"RUSTUP_TOOLCHAIN",
"RUSTUP_UPDATE_ROOT",
"CARGO_HOME",
"VIRTUAL_ENV",
"LUA_PATH",
"LUA_CPATH",
"GEM_HOME",
"GEM_PATH",
"BUNDLE_GEMFILE",
"COMPOSER_HOME",
"CONDA_DEFAULT_ENV",
"CONDA_PREFIX",
"CARGO_BUILD_RUSTC_WRAPPER",
"XDG_CACHE_HOME",
"XDG_CONFIG_DIRS",
"XDG_CONFIG_HOME",
"XDG_DATA_DIRS",
"XDG_DATA_HOME",
"XDG_RUNTIME_DIR",
"XDG_STATE_HOME",
"AWS_CONFIG_FILE",
"KUBECONFIG",
"GOOGLE_APPLICATION_CREDENTIALS",
"AWS_SHARED_CREDENTIALS_FILE",
"AWS_WEB_IDENTITY_TOKEN_FILE",
"AZURE_AUTH_LOCATION",
"HELM_HOME",
"ANSIBLE_CONFIG",
"ANSIBLE_LIBRARY",
"ANSIBLE_CALLBACK_PLUGINS",
"ANSIBLE_COLLECTIONS_PATH",
"ANSIBLE_CONNECTION_PLUGINS",
"ANSIBLE_FILTER_PLUGINS",
"ANSIBLE_INVENTORY_PLUGINS",
"ANSIBLE_LOOKUP_PLUGINS",
"ANSIBLE_MODULE_UTILS",
"ANSIBLE_REMOTE_TEMP",
"ANSIBLE_ROLES_PATH",
"ANSIBLE_STRATEGY_PLUGINS",
"R_LIBS_USER",
"TF_CLI_CONFIG_FILE",
"TF_PLUGIN_CACHE_DIR",
"AMQP_URL",
"AWS_ACCESS_KEY_ID",
"AWS_CONTAINER_CREDENTIALS_FULL_URI",
"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",
"AWS_SECRET_ACCESS_KEY",
"AWS_SECURITY_TOKEN",
"AWS_SESSION_TOKEN",
"AZURE_CLIENT_ID",
"AZURE_CLIENT_SECRET",
"DATABASE_URL",
"GH_TOKEN",
"GITHUB_TOKEN",
"GITLAB_TOKEN",
"MONGODB_URI",
"NODE_AUTH_TOKEN",
"NPM_TOKEN",
"REDIS_URL",
"SSH_AUTH_SOCK",
"SYSTEMROOT",
"WINDIR"
],
allowedInheritedOverrideOnlyKeys: [
"ALL_PROXY",
"AWS_CONFIG_FILE",
"AWS_SHARED_CREDENTIALS_FILE",
"AWS_WEB_IDENTITY_TOKEN_FILE",
"AZURE_AUTH_LOCATION",
"CURL_CA_BUNDLE",
"DOCKER_CERT_PATH",
"DOCKER_CONTEXT",
"DOCKER_HOST",
"DOCKER_TLS_VERIFY",
"GIT_PAGER",
"GOOGLE_APPLICATION_CREDENTIALS",
"GRADLE_USER_HOME",
"HISTFILE",
"HOME",
"HTTPS_PROXY",
"HTTP_PROXY",
"KUBECONFIG",
"MANPAGER",
"NODE_EXTRA_CA_CERTS",
"NODE_TLS_REJECT_UNAUTHORIZED",
"NO_PROXY",
"PAGER",
"REQUESTS_CA_BUNDLE",
"RUSTUP_DIST_ROOT",
"RUSTUP_DIST_SERVER",
"RUSTUP_HOME",
"RUSTUP_TOOLCHAIN",
"RUSTUP_UPDATE_ROOT",
"SSH_AUTH_SOCK",
"SSL_CERT_DIR",
"SSL_CERT_FILE",
"SYSTEMROOT",
"WINDIR",
"XDG_CACHE_HOME",
"XDG_CONFIG_DIRS",
"XDG_CONFIG_HOME",
"XDG_DATA_DIRS",
"XDG_DATA_HOME",
"XDG_RUNTIME_DIR",
"XDG_STATE_HOME",
"ZDOTDIR"
],
blockedOverridePrefixes: [
"GIT_CONFIG_",
"NPM_CONFIG_",
"CARGO_REGISTRIES_",
"TF_VAR_"
],
blockedPrefixes: [
"DYLD_",
"LD_",
"BASH_FUNC_"
]
};
//#endregion
//#region src/infra/host-env-security-policy.js
function sortUniqueUppercase(values) {
return Object.freeze(Array.from(new Set(values.map((value) => value.toUpperCase()))).toSorted((left, right) => left < right ? -1 : left > right ? 1 : 0));
}
function derivePolicyArrays(policy) {
const blockedEverywhereKeys = policy.blockedEverywhereKeys ?? [];
const blockedOverrideOnlyKeys = policy.blockedOverrideOnlyKeys ?? [];
const allowedInheritedOverrideOnlyKeys = policy.allowedInheritedOverrideOnlyKeys ?? [];
const allowedInheritedOverrideOnlyUpper = new Set(allowedInheritedOverrideOnlyKeys.map((value) => value.toUpperCase()));
const blockedPrefixes = policy.blockedPrefixes ?? [];
const blockedOverridePrefixes = policy.blockedOverridePrefixes ?? [];
const blockedInheritedPrefixes = policy.blockedInheritedPrefixes ?? blockedPrefixes;
return {
blockedInheritedKeys: sortUniqueUppercase([...blockedEverywhereKeys, ...blockedOverrideOnlyKeys.filter((value) => !allowedInheritedOverrideOnlyUpper.has(value.toUpperCase()))]),
blockedInheritedPrefixes: sortUniqueUppercase(blockedInheritedPrefixes),
blockedKeys: sortUniqueUppercase(blockedEverywhereKeys),
blockedOverrideKeys: sortUniqueUppercase(blockedOverrideOnlyKeys),
blockedPrefixes: sortUniqueUppercase(blockedPrefixes),
blockedOverridePrefixes: sortUniqueUppercase(blockedOverridePrefixes)
};
}
/**
* Normalizes raw host environment policy JSON into immutable lookup arrays.
*/
function loadHostEnvSecurityPolicy(rawPolicy = host_env_security_policy_default) {
const derived = derivePolicyArrays(rawPolicy);
return Object.freeze({
blockedEverywhereKeys: Object.freeze(rawPolicy.blockedEverywhereKeys ?? []),
blockedOverrideOnlyKeys: Object.freeze(rawPolicy.blockedOverrideOnlyKeys ?? []),
allowedInheritedOverrideOnlyKeys: Object.freeze(rawPolicy.allowedInheritedOverrideOnlyKeys ?? []),
blockedInheritedKeys: derived.blockedInheritedKeys,
blockedInheritedPrefixes: derived.blockedInheritedPrefixes,
blockedPrefixes: derived.blockedPrefixes,
blockedOverridePrefixes: derived.blockedOverridePrefixes,
blockedKeys: derived.blockedKeys,
blockedOverrideKeys: derived.blockedOverrideKeys
});
}
/**
* Process-wide host environment security policy derived from generated JSON.
*/
const HOST_ENV_SECURITY_POLICY = loadHostEnvSecurityPolicy();
//#endregion
//#region src/infra/host-env-security.ts
const PORTABLE_ENV_VAR_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
const HOST_DANGEROUS_ENV_KEYS = new Set(HOST_ENV_SECURITY_POLICY.blockedKeys);
new Set(HOST_ENV_SECURITY_POLICY.blockedInheritedKeys);
const HOST_DANGEROUS_OVERRIDE_ENV_KEYS = new Set(HOST_ENV_SECURITY_POLICY.blockedOverrideKeys);
const CARGO_TARGET_EXECUTABLE_OVERRIDE_ENV_KEY = /^CARGO_TARGET_[A-Z0-9_]+_(?:LINKER|RUNNER)$/;
new AsyncLocalStorage();
function normalizeEnvVarKey(rawKey, options) {
const key = rawKey.trim();
if (!key) return null;
if (options?.portable && !PORTABLE_ENV_VAR_KEY.test(key)) return null;
return key;
}
function isDangerousHostEnvVarName(rawKey) {
const key = normalizeEnvVarKey(rawKey);
if (!key) return false;
const upper = key.toUpperCase();
if (HOST_DANGEROUS_ENV_KEYS.has(upper)) return true;
return HOST_ENV_SECURITY_POLICY.blockedPrefixes.some((prefix) => upper.startsWith(prefix));
}
function isDangerousHostEnvOverrideVarName(rawKey) {
const key = normalizeEnvVarKey(rawKey);
if (!key) return false;
const upper = key.toUpperCase();
if (HOST_DANGEROUS_OVERRIDE_ENV_KEYS.has(upper)) return true;
if (CARGO_TARGET_EXECUTABLE_OVERRIDE_ENV_KEY.test(upper)) return true;
return HOST_ENV_SECURITY_POLICY.blockedOverridePrefixes.some((prefix) => upper.startsWith(prefix));
}
//#endregion
//#region src/config/config-env-values.ts
function isBlockedConfigEnvVar(key) {
return key.toUpperCase() === "OPENCLAW_ALLOW_OLDER_BINARY_DESTRUCTIVE_ACTIONS" || key.toUpperCase() === "OPENCLAW_INCLUDE_ROOTS" || key.toUpperCase() === "OPENCLAW_CONFIG_READONLY" || isDangerousHostEnvVarName(key) || isDangerousHostEnvOverrideVarName(key);
}
/** Returns whether a config-controlled environment entry is safe to apply at runtime. */
function isConfigRuntimeEnvVarAllowed(key, value) {
return Boolean(value.trim()) && !isBlockedConfigEnvVar(key) && !containsEnvVarReference(value);
}
/** Collects config env vars safe to inject into runtime process environments. */
function collectConfigRuntimeEnvVars(cfg) {
const envConfig = cfg?.env;
if (!envConfig) return {};
const entries = {};
const candidates = [...Object.entries(envConfig.vars ?? {}), ...Object.entries(envConfig).filter(([key]) => key !== "shellEnv" && key !== "vars")];
for (const [rawKey, value] of candidates) {
if (typeof value !== "string" || !value.trim()) continue;
const key = normalizeEnvVarKey(rawKey, { portable: true });
if (!key) continue;
if (!isConfigRuntimeEnvVarAllowed(key, value)) continue;
entries[key] = value;
}
return entries;
}
function findCaseInsensitiveEnvKey(env, key) {
if (Object.hasOwn(env, key)) return key;
const upperKey = key.toUpperCase();
return Object.keys(env).find((candidate) => candidate.toUpperCase() === upperKey);
}
function envSnapshotKey(key) {
return process.platform === "win32" ? key.toUpperCase() : key;
}
function indexConfigRuntimeEnvValues(entries) {
const allowedValues = /* @__PURE__ */ new Map();
for (const [key, value] of Object.entries(entries)) for (const normalizedKey of resolveEnvNormalizationKeys(key)) {
const values = allowedValues.get(normalizedKey) ?? /* @__PURE__ */ new Set();
values.add(value);
allowedValues.set(normalizedKey, values);
}
return allowedValues;
}
//#endregion
//#region src/config/config-env-vars.ts
const appliedConfigEnvOwnership = /* @__PURE__ */ new WeakMap();
function resolveAppliedConfigEnvOwnership(env) {
return {
...appliedConfigEnvOwnership.get(env),
...env === process.env ? publishedConfigRuntimeEnvState.ownedEnv : {}
};
}
new AsyncLocalStorage();
function cloneEnvWithPlatformSemantics(env) {
const cloned = { ...env };
const ownedEnv = resolveAppliedConfigEnvOwnership(env);
if (process.platform !== "win32") {
appliedConfigEnvOwnership.set(cloned, ownedEnv);
return cloned;
}
const proxy = new Proxy(cloned, {
deleteProperty(target, property) {
if (typeof property !== "string") return Reflect.deleteProperty(target, property);
const key = findCaseInsensitiveEnvKey(target, property);
return key ? Reflect.deleteProperty(target, key) : true;
},
get(target, property, receiver) {
if (typeof property !== "string") return Reflect.get(target, property, receiver);
const key = findCaseInsensitiveEnvKey(target, property);
return key ? target[key] : Reflect.get(target, property, receiver);
},
getOwnPropertyDescriptor(target, property) {
if (typeof property !== "string") return Reflect.getOwnPropertyDescriptor(target, property);
const key = findCaseInsensitiveEnvKey(target, property);
if (!key) return;
return {
configurable: true,
enumerable: true,
value: target[key],
writable: true
};
},
has(target, property) {
return typeof property === "string" ? findCaseInsensitiveEnvKey(target, property) !== void 0 : Reflect.has(target, property);
},
set(target, property, value) {
if (typeof property !== "string") return Reflect.set(target, property, value);
target[findCaseInsensitiveEnvKey(target, property) ?? property] = value;
return true;
}
});
appliedConfigEnvOwnership.set(proxy, ownedEnv);
return proxy;
}
let publishedConfigRuntimeEnvState = {
generation: 0,
ownedEnv: {},
sourceConfig: null
};
function collectConfigRuntimeEnvOwnership(sourceConfig, before, after, options = {}) {
const ownedEnv = {};
const replacedLowerPrecedenceKeys = new Set((options.replacedLowerPrecedenceKeys ?? []).map(envSnapshotKey));
for (const [key, value] of Object.entries(collectConfigRuntimeEnvVars(sourceConfig))) for (const normalizedKey of resolveEnvNormalizationKeys(key)) {
const afterKey = findCaseInsensitiveEnvKey(after, normalizedKey);
if (!afterKey || after[afterKey] !== value) continue;
const beforeKey = findCaseInsensitiveEnvKey(before, normalizedKey);
if (beforeKey && before[beforeKey] === value && !replacedLowerPrecedenceKeys.has(envSnapshotKey(afterKey))) continue;
ownedEnv[afterKey] = value;
}
return ownedEnv;
}
function filterConfigRuntimeEnvOwnership(sourceConfig, env, ownedEnv) {
const allowedValues = indexConfigRuntimeEnvValues(collectConfigRuntimeEnvVars(sourceConfig));
const filtered = {};
for (const [key, value] of Object.entries(ownedEnv)) {
const normalizedKey = resolveEnvNormalizationKeys(key)[0] ?? key;
const actualKey = findCaseInsensitiveEnvKey(env, key);
if (actualKey && env[actualKey] === value && allowedValues.get(normalizedKey)?.has(value)) filtered[actualKey] = value;
}
return filtered;
}
/** Applies config env vars to an environment without overwriting existing non-empty values. */
function applyConfigEnvVars(cfg, env = process.env, options = {}) {
const before = { ...env };
const previousOwnedEnv = resolveAppliedConfigEnvOwnership(env);
const entries = collectConfigRuntimeEnvVars(cfg);
const lowerPrecedenceEntries = Object.entries(options.lowerPrecedenceEnv ?? {});
const lowerPrecedenceEnv = new Map(lowerPrecedenceEntries.map(([key, value]) => [envSnapshotKey(key), value]));
const configEnvKeys = expandEnvNormalizationKeys(Object.keys(entries));
const configValuesByKey = indexConfigRuntimeEnvValues(entries);
const higherPrecedenceValues = /* @__PURE__ */ new Map();
for (const key of Object.keys(entries)) {
const normalizedKeys = resolveEnvNormalizationKeys(key);
const winningValue = normalizedKeys.map((normalizedKey) => [normalizedKey, env[normalizedKey]]).find(([normalizedKey, currentValue]) => currentValue?.trim() && lowerPrecedenceEnv.get(normalizedKey) !== currentValue && !configValuesByKey.get(normalizedKey)?.has(currentValue))?.[1];
if (winningValue !== void 0) for (const normalizedKey of normalizedKeys) higherPrecedenceValues.set(normalizedKey, winningValue);
}
const replacedLowerPrecedenceKeys = [];
for (const [key, value] of lowerPrecedenceEntries) if (configEnvKeys.has(envSnapshotKey(key)) && env[key] === value) {
delete env[key];
replacedLowerPrecedenceKeys.push(key);
}
if (replacedLowerPrecedenceKeys.length > 0) options.onLowerPrecedenceKeysReplaced?.(replacedLowerPrecedenceKeys);
for (const [key, value] of Object.entries(entries)) {
const higherPrecedenceValue = higherPrecedenceValues.get(envSnapshotKey(key));
if (higherPrecedenceValue !== void 0) {
env[key] = higherPrecedenceValue;
continue;
}
const currentValue = env[key];
if (currentValue?.trim() && lowerPrecedenceEnv.get(envSnapshotKey(key)) !== currentValue) continue;
env[key] = value;
}
normalizeZaiEnv(env);
appliedConfigEnvOwnership.set(env, {
...filterConfigRuntimeEnvOwnership(cfg, env, previousOwnedEnv),
...collectConfigRuntimeEnvOwnership(cfg, before, env, { replacedLowerPrecedenceKeys })
});
}
//#endregion
//#region src/infra/gateway-supervision.ts
const GATEWAY_SUPERVISOR_MODE_ENV = "OPENCLAW_SUPERVISOR_MODE";
function isGatewayExternallySupervised(env = process.env) {
return env[GATEWAY_SUPERVISOR_MODE_ENV]?.trim().toLowerCase() === "external";
}
//#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);
let admission = capturedAdmission;
let runInCapturedSchemaScope;
if (schema) {
const inCapturedScope = AsyncLocalStorage.snapshot();
admission = {
databasePath: capturedAdmission.databasePath,
coordinationKey: capturedAdmission.coordinationKey,
get identity() {
return capturedAdmission.identity;
},
assertCurrent() {
capturedAdmission.assertCurrent();
schema.assertCurrent();
}
};
runInCapturedSchemaScope = (operation) => inCapturedScope(() => {
admission.assertCurrent();
return operation();
});
}
return {
maintenanceScope: getOpenClawDatabaseMaintenanceScope(),
admission,
existingSchemaPath: schema?.path,
runInCapturedSchemaScope
};
}
/** 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, asyncResources }, retainForIdle) {
return {
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 pending?.catch(() => {});
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-CvAPsCV2.js").then((n) => n.n);
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-handle.ts
function openTrackedStateDatabase(pathname, options) {
const result = openTrackedStateDatabaseResult(pathname, options);
if (result.status === "unavailable") throw result.error;
return result.database;
}
/** Native open failure is an ordinary read failure; admitted handles retain their own cleanup. */
function openTrackedStateDatabaseResult(pathname, options) {
assertStateDatabaseAccessAllowed(pathname);
try {
if (options?.expectedIdentity !== void 0) assertExistingDatabaseIdentity(pathname, options.expectedIdentity);
const location = options?.existingOnly || options?.expectedIdentity !== void 0 ? resolveExistingSqliteFileUri(pathname) : pathname;
const nativeOptions = options?.readOnly ? {
readOnly: true,
timeout: options.timeout
} : { enableForeignKeyConstraints: options?.enableForeignKeyConstraints };
const database = withSqliteNativeOpen(() => openNodeSqliteDatabase(location, nativeOptions));
try {
assertStateDatabaseAccessAllowed(pathname);
} catch (error) {
database.close();
throw error;
}
return {
status: "available",
database
};
} catch (error) {
return {
status: "unavailable",
error
};
}
}
function closeTrackedStateDatabase(database) {
if (database.isOpen) database.close();
}
//#endregion
//#region src/state/openclaw-state-db-schema-version.ts
const CONTENT_VERSION_KEY = "state.schema.contentVersion";
const contentVersions = /* @__PURE__ */ new WeakMap();
const contentVersionQueries = /* @__PURE__ */ new WeakMap();
/** Content and its marker commit together, even while older readers retain their version floor. */
function readStateSchemaContentVersion(db) {
const schema = getAdmittedSqliteSchemaFacts(db);
const cached = schema && contentVersions.get(schema);
if (cached !== void 0) return cached;
const version = readContentVersion(db, schema?.userVersion ?? readSqliteUserVersion(db));
if (schema) contentVersions.set(schema, version);
return version;
}
function readContentVersion(db, published) {
if (!tableExists(db, "config_machine_state")) return published;
let query = contentVersionQueries.get(db);
if (!query) {
query = prepareSqliteQuerySync(db, () => getNodeSqliteKysely(db).selectFrom("config_machine_state").select("value_json").where("state_key", "=", CONTENT_VERSION_KEY));
contentVersionQueries.set(db, query);
}
const row = query().rows[0];
if (!row) return published;
let contentVersion;
try {
contentVersion = JSON.parse(row.value_json);
} catch (cause) {
throw new SqliteSchemaMismatchError(`Invalid shared state schema content version in ${CONTENT_VERSION_KEY}.`, { cause });
}
if (typeof contentVersion !== "number" || !Number.isSafeInteger(contentVersion) || contentVersion < 0) throw new SqliteSchemaMismatchError(`Invalid shared state schema content version in ${CONTENT_VERSION_KEY}.`);
return Math.max(published, contentVersion);
}
/** Cold migration planning checks physical content; admission still uses the recorded version. */
function readStateSchemaMigrationVersion(db) {
const version = readStateSchemaContentVersion(db);
if (version !== 16) return version;
const reviewWorkspace = tableHasColumn(db, "skill_workshop_collection_reviews", "workspace_dir");
const proposalWorkspace = tableHasColumn(db, "skill_workshop_proposals", "workspace_dir");
const releasedClaim = tableHasColumn(db, "skill_workshop_proposals", "claim_released_time");
if (!reviewWorkspace && !proposalWorkspace && !releasedClaim) return version;
const missingAttribution = reviewWorkspace && !proposalWorkspace;
const issues = collectSqliteSchemaIssues(db, `
CREATE TABLE skill_workshop_collection_reviews (
review_id TEXT NOT NULL PRIMARY KEY,
${reviewWorkspace ? "workspace_dir" : "owner_agent_id"} TEXT NOT NULL,
backup_id TEXT NOT NULL,
create_time INTEGER NOT NULL,
kept_names_json TEXT NOT NULL,
written_names_json TEXT NOT NULL,
dropped_json TEXT NOT NULL
) STRICT;
CREATE TABLE skill_workshop_proposals (
proposal_id TEXT NOT NULL PRIMARY KEY,
record_json TEXT NOT NULL,
owner_agent_id TEXT,
${proposalWorkspace ? "workspace_dir TEXT NOT NULL," : ""}
kind TEXT NOT NULL CHECK (kind IN ('create', 'update')),
status TEXT NOT NULL CHECK (status IN ('pending', 'applied', 'rejected', 'quarantined', 'stale')),
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
draft_hash TEXT NOT NULL,
origin_agent_id TEXT,
origin_session_key TEXT,
origin_run_id TEXT,
origin_message_id TEXT,
applied_at TEXT,
rejected_at TEXT,
quarantined_at TEXT,
stale_at TEXT,
status_reason TEXT
${releasedClaim ? ", claim_released_time INTEGER" : ""}
) STRICT;
`, {
allowedMissingTables: ["skill_workshop_collection_reviews"],
allowedColumnDefinitions: {
"skill_workshop_collection_reviews.workspace_dir": ["workspace_dir TEXT NOT NULL DEFAULT ''"],
"skill_workshop_proposals.workspace_dir": ["workspace_dir TEXT NOT NULL DEFAULT ''"]
}
});
if (!missingAttribution && issues.length === 0) return 15;
throw new SqliteSchemaMismatchError("Unrecognized Skill Workshop ownership schema; cannot apply the schema 16 migration.");
}
function assertSupportedStateSchemaVersion(db, pathname) {
try {
const userVersion = getAdmittedSqliteSchemaFacts(db)?.userVersion ?? readSqliteUserVersion(db);
const contentVersion = userVersion > 19 ? userVersion : readStateSchemaContentVersion(db);
if (contentVersion > 19) throw createNewerSqliteSchemaVersionError("OpenClaw state database", pathname, contentVersion, 19);
return userVersion;
} catch (error) {
throw normalizeOpenClawStateSchemaReadError(error, pathname);
}
}
//#endregion
//#region src/state/openclaw-state-db-runtime-failure.ts
/** Runtime validation uses the cache's existing handles, version counters, and terminal latch. */
function createOpenClawStateDatabaseRuntimeFailureOwner(owner) {
return {
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");
},
get: (pathname) => {
const resolvedPath = path.resolve(pathname);
const latched = owner.latch.get(resolvedPath);
if (latched) return latched;
const cached = owner.cachedDatabases.get(resolvedPath);
if (!cached?.db.isOpen) return;
try {
assertSupportedStateSchemaVersion(cached.db, resolvedPath);
return;
} catch (error) {
const failure = error instanceof Error ? error : new Error(String(error));
if (isSqliteCorruptionError(failure)) {
owner.evict(cached);
return;
}
if (isSqliteSchemaVersionError(failure)) owner.recordSchemaFailure(resolvedPath, failure);
return failure;
}
}
};
}
//#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 stateDatabaseLifecycle = resolveGlobalSingleton(Symbol.for("openclaw.stateDatabaseLifecycle"), () => ({
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) => runtimeFailures.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 } = createStateDatabaseWalOwner(stateDatabaseLifecycle, retainOpenClawStateDatabaseForIdle);
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 runtimeFailures = createOpenClawStateDatabaseRuntimeFailureOwner({
cachedDatabases,
latch: terminalOpenLatch,
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 cancelSqliteWalWriteAdmission(database.db);
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,
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) {
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;
admitSqliteSchema(db);
assertSupportedStateSchemaVersion(db, pathname);
const { identity, admission } = asyncResources.publish(pathname);
databaseIdentities.set(db, identity);
cachedDatabases.set(pathname, database);
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) {
const maintenance = getOpenClawDatabaseMaintenanceScope();
if (options?.readOnly) maintenance?.assertReadAdmission();
else maintenance?.assertAdmission();
assertExistingOpenClawStateSchemaCacheAdmission(pathname, stateDatabaseLifecycle);
const runtimeFailure = runtimeFailures.get(pathname);
if (runtimeFailure) throw runtimeFailure;
const database = cachedDatabases.get(path.resolve(pathname));
if (database && borrowers.get(database.db)?.retiring) throw new Error(`OpenClaw state database native borrower cleanup is pending: ${pathname}`);
if (database) touchStateDatabase(database);
return 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(pathname, error, generation);
}
/** Clear a terminal open failure after doctor rewrites the database file. */
function clearOpenClawStateDatabaseOpenFailure(pathname) {
const resolvedPath = path.resolve(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) {
context.admission.assertCurrent();
const failure = await terminalOpenLatch.getAsync(context.admission.databasePath, async (_path, generation) => {
const { inspectOpenClawStateDatabase } = await import("./openclaw-state-worker-store-CvAPsCV2.js").then((n) => n.n);
const matches = await inspectOpenClawStateDatabase(context, {
type: "database.generationMatches",
input: { generation }
});
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) {
if (ownership === "cached-read") assertStateDatabaseReadAllowed(pathname);
else assertStateDatabaseAccessAllowed(pathname);
const { identity } = asyncResources.capture(pathname);
const terminalFailure = terminalOpenLatch.get(pathname);
if (terminalFailure) throw terminalFailure;
const resolvedPath = path.resolve(pathname);
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: path.resolve(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) {
runtimeFailures.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;
}
/** Close one cached shared state database handle by exact pathname. */
function closeOpenClawStateDatabaseByPath(pathname, options) {
return retireOpenClawStateDatabaseHandles(path.resolve(pathname), options, asyncResources.identity(pathname));
}
/** Close all cached shared state database handles. */
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;
/** Bind worker-created storage to its captured admission without publishing a native handle. */
function publishOpenClawStateDatabaseWorkerAdmission(admission) {
admission.assertCurrent();
asyncResources.publish(admission.databasePath);
admission.assertCurrent();
}
/** Orderly lifecycle close; synchronous close remains native/exit cleanup only. */
async function closeOpenClawStateDatabaseAsync(options) {
await asyncResources.close(void 0, () => retireOpenClawStateDatabaseHandles(void 0, options));
}
/** Test whether a cached shared state database handle is still open, optionally at one path. */
function isOpenClawStateDatabaseOpen(pathname) {
if (pathname !== void 0) return cachedDatabases.get(path.resolve(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();
}
/** 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,
getOpenClawStateDatabaseRuntimeFailure: runtimeFailures.get,
getOpenClawStateDatabaseRecordedFailure: terminalOpenLatch.peek,
getOpenClawStateDatabaseIfOpenAtPath,
getKnownOpenClawStateDatabaseIdentity: asyncResources.knownIdentity,
isOpenClawStateDatabaseOpen,
publishOpenClawStateDatabase,
recordOpenClawStateDatabaseOpenFailure,
recordOpenClawStateDatabaseLifecycleOpenError,
touchStateDatabase
};
//#endregion
export { runSqliteDeferredTransactionSync as $, OpenClawStateOwnershipMetadataError as $t, readSqliteSchemaCookie as A, registerNodeSqliteDisposeCallback as An, existingPathOrUndefined as At, classifySqliteTableReadError as B, LAZY_ADDITIVE_STATE_INDEXES as Bt, assertSqliteSchemaContains as C, executeSqliteQuerySync as Cn, assertStateDatabaseAccessAllowed as Ct, createSqliteTableContractReader as D, prepareSqliteQuerySync as Dn, setSqliteBusyTimeout as Dt, collectSqliteSchemaIssues as E, iterateSqliteQuerySync as En, runWithSqliteBusyTimeout as Et, registerSqliteCacheExitClose as F, resolveQuarantineStorePath as Ft, SqliteSchemaMismatchError as G, getOpenClawDatabaseMaintenanceScope as Gt, tableExists as H, OPENCLAW_SQLITE_BUSY_TIMEOUT_MS as Ht, runInSqliteMaintenanceContext as I, warnAgentPathMigration as It, isTerminalSqliteIntegrityError as J, findOpenClawStateDatabaseFailure as Jt, assertSqliteIntegrity as K, isStateDatabaseReadAdmissionInvalidatedError as Kt, createSqliteWalReclamationResult as L, DOCTOR_OWNED_STATE_TABLES as Lt, createSqliteSnapshotStagingDirectorySync as M, sha256FileSync$1 as Mn, resolveOpenClawStateDirForDatabasePath as Mt, configureSqliteConnectionPragmas as N, sha256Hex as Nn, resolveOpenClawStateSqliteDir as Nt, getCanonicalSqliteNamedIndexContracts as O, enableNodeSqliteKyselyStatementCache as On, applyPrivateModeSync as Ot, configureSqlitePreSchemaPragmas as P, resolveOpenClawStateSqlitePath as Pt, retainSqliteWriteAdmissionService as Q, OpenClawStateOwnershipError as Qt, coerceRequiredSqliteNumber as R, FIRST_USE_STATE_INDEXES as Rt, migrateSqliteSchemaToStrictInTransaction as S, compileSqliteQueryBindings as Sn, acquireStateDatabaseSchemaLease as St, collectSqliteNamedIndexContract as T, getNodeSqliteKysely as Tn, withStateDatabaseColdAdmission as Tt, tableHasColumn as U, StateDatabaseReadAdmissionInvalidatedError as Ut, ensureColumn as V, LAZY_ADDITIVE_STATE_TABLES as Vt, tablePrimaryKeyColumns as W, captureOpenClawDatabaseMaintenanceResource as Wt, sqliteIntegrityCheckSteps as X, DATABASE_QUARANTINE_READ_CLEANUP_ERROR_NAME as Xt, runSqliteIntegrityOperationSync as Y, markOpenClawStateDatabaseFailure as Yt, assertTransactionUsable as Z, OpenClawStateExternalOwnershipError as Zt, applyConfigEnvVars as _, extractSqliteTableSchema as _n, StartupMaintenanceRequiredError as _t, recordOpenClawStateDatabaseOpenFailure as a, adoptPreparedLocation as an, requestSqliteWorkerSchemaMaintenance as at, SQLITE_IDLE_HANDLE_TTL_MS as b, runSqlitePinnedReadSnapshotSync as bn, runInDetachedAsyncContext as bt, retainOpenClawStateDatabaseForIdle as c, markSqliteNativeOpenFailure as cn, SqliteWorkerError as ct, readStateSchemaContentVersion as d, invalidateSqliteSchemaFacts as dn, withSqlitePostCommitPublications as dt, SqliteCoordinatorError as en, runSqliteImmediateTransactionSync as et, readStateSchemaMigrationVersion as f, runSqliteReadOperationSync as fn, OpenClawStateDatabaseSchemaMigrationRequiredError as ft, isGatewayExternallySupervised as g, readDatabasePathIdentitySync as gn, readSqliteUserVersion as gt, captureOpenClawStateWorkerContextWithAdmission as h, readDatabasePathIdentity as hn, isSqliteSchemaVersionError as ht, publishOpenClawStateDatabaseWorkerAdmission as i, SqliteSnapshotCleanupError as in, createSqliteWorkerOperationAdmission as it, acquireSqliteSnapshotReadToken as j, sha256Base64Url as jn, resolveOpenClawAgentDatabaseStoredPath as jt, getCanonicalSqliteTableNames as k, executeWithCachedStatement as kn, describeAgentPathMigration as kt, CONTENT_VERSION_KEY as l, openNodeSqliteDatabase as ln, retainSqliteWorkerErrorCode as lt, openTrackedStateDatabaseResult as m, readDatabaseIdentityBirthtime as mn, SqliteSchemaVersionError as mt, getOpenClawStateDatabaseTerminalFailureAsync as n, runWithSqliteCleanup as nn, isExistingOpenClawStateSchema as nt, registerOpenClawStateDatabaseAsyncResource as o, removeTempDirectory as on, SQLITE_WORKER_MAX_MESSAGE_BYTES as ot, openTrackedStateDatabase as p, assertExistingDatabaseIdentity as pn, normalizeOpenClawStateSchemaReadError as pt, assertSqliteTableIntegrity as q, observeOpenClawDatabaseMaintenanceResource as qt, openClawStateDatabaseCache as r, throwSqliteLifecycleErrors as rn, recordExistingOpenClawStateSchemaDatabase as rt, registerOpenClawStateDatabaseLifecycleListener as s, isSqliteLockError as sn, SqliteWorkerAdmissionTimeoutError as st, captureOpenClawStateDatabaseReadAdmission as t, createSqliteLifecycleAggregateError as tn, getExistingOpenClawStateSchemaPath as tt, assertSupportedStateSchemaVersion as u, admitSqliteSchema as un, deferSqlitePostCommitPublication as ut, cloneEnvWithPlatformSemantics as v, quoteSqliteIdentifier as vn, isStartupMaintenanceKind as vt, assertSqliteSchemaTablesPresent as w, executeSqliteQueryTakeFirstSync as wn, getStateDatabaseSchemaLease as wt, migrateSqliteSchemaToStrict as x, ensureSqliteLibrarySelected as xn, GatewayStateOwnerContentionError as xt, isTruthyEnvValue as y, splitSqlList as yn, VERSION as yt, normalizeSqliteNumber as z, FIRST_USE_STATE_TABLES as zt };