openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
5,201 lines • 344 kB
JavaScript
import { i as extractErrorCode, n as collectErrorGraphCandidates, t as coerceErrorMessage } from "./error-coercion-CwIUUFnu.mjs";
import { n as runtimeProcessEntrypoints, t as SQLITE_READONLY_CHILD_ARG } from "./runtime-process-entrypoints-Bh4reDp0.mjs";
import { c as resolveRuntimeWorkerUrl, o as resolveRuntimeWorkerArgv } from "./worker-cpu-BzVGEcKM.mjs";
import { B as FIRST_USE_STATE_INDEXES, C as VERSION, G as OPENCLAW_SQLITE_BUSY_TIMEOUT_MS, H as LAZY_ADDITIVE_STATE_INDEXES, U as LAZY_ADDITIVE_STATE_TABLES, V as FIRST_USE_STATE_TABLES, a as resolveOpenClawStateSqliteDir, c as warnAgentPathMigration, d as isSqliteSchemaVersionError, f as readSqliteUserVersion, g as enableNodeSqliteKyselyStatementCache, i as resolveOpenClawStateDirForDatabasePath, n as existingPathOrUndefined, o as resolveOpenClawStateSqlitePath, p as StartupMaintenanceRequiredError, r as resolveOpenClawAgentDatabaseStoredPath, s as resolveQuarantineStorePath, t as describeAgentPathMigration, x as registerNodeSqliteDisposeCallback, z as DOCTOR_OWNED_STATE_TABLES } from "./openclaw-state-db.paths-JnUZZAu1.mjs";
import { A as asOptionalRecord, D as resolveTimerTimeoutMs, F as normalizeOptionalLowercaseString, I as normalizeOptionalString, L as readStringValue, M as isRecord, O as asNullableRecord, S as asSafeIntegerInRange, T as resolveNonNegativeIntegerOption, f as resolveRequiredHomeDir, g as MAX_DATE_TIMESTAMP_MS, m as resolveUserPath, u as expandHomePrefix, v as asFiniteNumber, x as asPositiveSafeInteger } from "./utils-Dor5uhSv.mjs";
import { D as resolveConfigIncludeWritePath, E as readConfigIncludeFileWithGuards, H as resolveConfigEnvVars, O as resolveConfigIncludes, P as resolveIdentityPathViaExistingAncestorSync, T as hashConfigIncludeRaw, U as coerceSecretRef, b as resolveStateDir, ct as sliceUtf16Safe, d as resolveGlobalSingleton, j as hasErrnoCode, k as parseJsonWithJson5Fallback, lt as truncateUtf16Safe, m as resolveConfigPath, ot as pruneMapToMaxSize, q as DEFAULT_SECRET_PROVIDER_ALIAS, st as expectDefined, v as resolveIncludeRoots, w as resolveProfileStateDir } from "./redact-BwKH5fIe.mjs";
import { t as formatErrorMessage } from "./errors-C_Op1bVg.mjs";
import "./worker-task-pool-Csp_W8Qg.mjs";
import { o as isTruthyEnvValue } from "./host-env-security-Dw0EukUB.mjs";
import { $ as SqliteSchemaMismatchError, $t as unknown, A as isGatewayExternallySupervised, At as extractSqliteTableSchema, B as tableExists, Bt as runWithSqliteBusyTimeout, Ct as sqliteIntegrityCheckSteps, Dt as admitSqliteSchema, Et as openNodeSqliteDatabase, Ft as GatewayStateOwnerContentionError, G as assertSqliteSchemaContains, Gt as boolean, H as tablePrimaryKeyColumns, Ht as applyPrivateModeSync, It as acquireStateDatabaseSchemaLease, J as collectSqliteSchemaIssues, Jt as looseObject, K as assertSqliteSchemaTablesPresent, Kt as custom, L as SQLITE_IDLE_HANDLE_TTL_MS, Lt as assertStateDatabaseAccessAllowed, M as requestSqliteWorkerSchemaMaintenance, Mt as splitSqlList, Nt as runSqlitePinnedReadSnapshotSync, O as isExistingOpenClawStateSchema, Ot as invalidateSqliteSchemaFacts, Q as readSqliteSchemaCookie, R as classifySqliteTableReadError, Rt as getStateDatabaseSchemaLease, St as runSqliteIntegrityOperationSync, T as openTrackedStateDatabaseResult, Ut as _enum, V as tableHasColumn, Vt as setSqliteBusyTimeout, W as migrateSqliteSchemaToStrictInTransaction, X as getCanonicalSqliteNamedIndexContracts, Xt as object, Y as createSqliteTableContractReader, Yt as number, Z as getCanonicalSqliteTableNames, Zt as string, _ as assertSupportedStateSchemaVersion, _n as withSqlitePostCommitPublications, _t as runSqliteDeferredTransactionSync, a as recordOpenClawStateDatabaseOpenFailure, an as OpenClawStateExternalOwnershipError, at as prepareSqliteQuerySync, b as OpenClawStateDatabaseSchemaMigrationRequiredError, bt as assertSqliteTableIntegrity, cn as SqliteCoordinatorError, ct as configureSqlitePreSchemaPragmas, d as SqliteSnapshotCleanupError, dn as throwSqliteLifecycleErrors, dt as createSqliteWalReclamationResult, en as StateDatabaseReadAdmissionInvalidatedError, f as adoptPreparedLocation, fn as assertExistingDatabaseIdentity, ft as coerceRequiredSqliteNumber, g as CONTENT_VERSION_KEY, gn as deferSqlitePostCommitPublication, hn as readDatabasePathIdentitySync, ht as assertTransactionUsable, in as observeOpenClawDatabaseMaintenanceResource, it as iterateSqliteQuerySync, jt as quoteSqliteIdentifier, k as recordExistingOpenClawStateSchemaDatabase, kt as runSqliteReadOperationSync, l as acquireSqliteSnapshotReadToken, ln as createSqliteLifecycleAggregateError, lt as registerSqliteCacheExitClose, nn as getOpenClawDatabaseMaintenanceScope, nt as executeSqliteQueryTakeFirstSync, on as OpenClawStateOwnershipError, p as removeTempDirectory, pn as readDatabaseIdentityBirthtime, pt as normalizeSqliteNumber, q as collectSqliteNamedIndexContract, qt as literal, r as openClawStateDatabaseCache, rt as getNodeSqliteKysely, sn as OpenClawStateOwnershipMetadataError, st as configureSqliteConnectionPragmas, tt as executeSqliteQuerySync, u as createSqliteSnapshotStagingDirectorySync, un as runWithSqliteCleanup, ut as runInSqliteMaintenanceContext, v as readStateSchemaContentVersion, vn as applyConfigEnvVars, vt as runSqliteImmediateTransactionSync, w as openTrackedStateDatabase, wt as isSqliteLockError, x as normalizeOpenClawStateSchemaReadError, xt as isTerminalSqliteIntegrityError, y as readStateSchemaMigrationVersion, yn as cloneEnvWithPlatformSemantics, yt as assertSqliteIntegrity, z as ensureColumn, zt as withStateDatabaseColdAdmission } from "./openclaw-state-db-cache-D6Da40pQ.mjs";
import { n as sha256FileSync$1, t as sha256Base64Url } from "./crypto-digest-DFe1GlHl.mjs";
import { t as isPromiseLike } from "./promise-like-D7-l5Fsp.mjs";
import { t as createSubsystemLogger } from "./subsystem-EYGCijzb.mjs";
import { c as LEGACY_IMPLICIT_AGENT_ID, i as captureRuntimeWorkerSource, o as resolveNodeCompileCacheEnv, s as getSqliteWorkerExistingDatabaseIdentity } from "./openclaw-state-worker-store-Ca0Joqz6.mjs";
import "./openclaw-state-worker-context-DchKMzil.mjs";
import "./spawn-diagnostics--ipLVwZR.mjs";
import { spawnSync } from "node:child_process";
import fsSync, { existsSync, mkdirSync, readdirSync, statSync } from "node:fs";
import path from "node:path";
import os from "node:os";
import "@openclaw/fs-safe/path";
import "@openclaw/fs-safe/advanced";
import { createHash } from "node:crypto";
import { FsSafeError as FsSafeError$1 } from "@openclaw/fs-safe/errors";
import { AsyncLocalStorage } from "node:async_hooks";
import { performance } from "node:perf_hooks";
import "node:fs/promises";
import { parse } from "semver";
import { toUSVString } from "node:util";
import "@openclaw/fs-safe/durability";
import { isProxy } from "node:util/types";
import JSON5 from "json5";
import "@openclaw/fs-safe/walk";
import zlib from "node:zlib";
import "@openclaw/fs-safe/output";
import "@openclaw/fs-safe/root";
import { readSecureFile } from "@openclaw/fs-safe/secure-file";
//#region packages/normalization-core/src/format.ts
const BYTE_SIZE_UNITS = [
"byte",
"kilo",
"mega",
"giga",
"tera"
];
const BYTE_SIZE_STYLES = {
iec: {
base: 1024,
labels: [
"B",
"KiB",
"MiB",
"GiB",
"TiB"
]
},
"legacy-binary": {
base: 1024,
labels: [
"B",
"KB",
"MB",
"GB",
"TB"
]
}
};
/** Formats a byte count with caller-explicit scale, labels, precision, and unit cap. */
function formatByteSize(bytes, options) {
const { base, labels } = BYTE_SIZE_STYLES[options.style];
const maxUnitIndex = BYTE_SIZE_UNITS.indexOf(options.maxUnit);
let unitIndex = 0;
let value = bytes;
while (value >= base && unitIndex < maxUnitIndex) {
value /= base;
unitIndex += 1;
}
const unit = expectDefined(BYTE_SIZE_UNITS[unitIndex], "byte-size unit");
const label = expectDefined(labels[unitIndex], "byte-size label");
const fractionDigits = typeof options.fractionDigits === "function" ? options.fractionDigits(value, unit) : options.fractionDigits;
if (fractionDigits === null) return `${value}${options.separator}${label}`;
if (options.floorUnits?.includes(unit)) value = Math.floor(value * 10 ** fractionDigits) / 10 ** fractionDigits;
return `${value.toFixed(fractionDigits)}${options.separator}${label}`;
}
//#endregion
//#region packages/normalization-core/src/json-coercion.ts
/** Parses JSON without throwing, returning undefined for invalid input. */
function safeParseJson(value) {
try {
return JSON.parse(value);
} catch {
return;
}
}
/** Parses JSON into a non-array record, returning undefined for every other result. */
function safeParseJsonRecord(value) {
return /^[\t\n\r ]*\{/.test(value) ? asOptionalRecord(safeParseJson(value)) : void 0;
}
//#endregion
//#region packages/normalization-core/src/stable-stringify.ts
const preserveString = (value) => value;
/** Deterministically stringifies values, optionally normalizing strings before key ordering. */
function stableStringify(value, normalizeString = preserveString) {
return stringifyStableValue(value, /* @__PURE__ */ new WeakSet(), normalizeString);
}
function stringifyStableValue(value, stack, normalizeString, write) {
if (value === null || value === void 0) return String(value);
if (typeof value === "number" && !Number.isFinite(value)) return JSON.stringify(String(value));
if (typeof value === "bigint") return JSON.stringify(value.toString());
if (typeof value === "string") return JSON.stringify(normalizeString(value));
if (typeof value !== "object") return JSON.stringify(value) ?? "null";
if (stack.has(value)) return JSON.stringify("[Circular]");
stack.add(value);
try {
return stringifyObjectValue(value, stack, normalizeString, write);
} finally {
stack.delete(value);
}
}
function stringifyObjectValue(value, stack, normalizeString, write) {
if (value instanceof Error) return stringifyStableValue({
name: value.name,
message: value.message,
stack: value.stack
}, stack, normalizeString, write);
if (value instanceof Uint8Array) return stringifyStableValue({
type: "Uint8Array",
data: encodeBase64(value)
}, stack, normalizeString, write);
if (Array.isArray(value)) {
if (write) {
write("[");
let separator = "";
for (const entry of value) {
write(separator);
write(stringifyStableValue(entry, stack, normalizeString, write));
separator = ",";
}
write("]");
return "";
}
const serializedEntries = [];
for (const entry of value) serializedEntries.push(stringifyStableValue(entry, stack, normalizeString));
return `[${serializedEntries.join(",")}]`;
}
const record = value;
if (normalizeString === preserveString) {
const fields = Object.keys(record).sort();
if (write) {
write("{");
let separator = "";
for (const key of fields) {
write(`${separator}${JSON.stringify(key)}:`);
write(stringifyStableValue(record[key], stack, normalizeString, write));
separator = ",";
}
write("}");
return "";
}
let fieldIndex = 0;
for (const key of fields) fields[fieldIndex++] = `${JSON.stringify(key)}:${stringifyStableValue(record[key], stack, normalizeString)}`;
return `{${fields.join(",")}}`;
}
const entries = Object.keys(record).map((key) => ({
key,
normalizedKey: normalizeString(key)
})).sort((left, right) => {
return compareStableStrings(left.normalizedKey, right.normalizedKey) || compareStableStrings(left.key, right.key);
});
const serializedFields = [];
if (write) {
write("{");
let separator = "";
for (const { key, normalizedKey } of entries) {
write(`${separator}${JSON.stringify(normalizedKey)}:`);
write(stringifyStableValue(record[key], stack, normalizeString, write));
separator = ",";
}
write("}");
return "";
}
for (const { key, normalizedKey } of entries) serializedFields.push(`${JSON.stringify(normalizedKey)}:${stringifyStableValue(record[key], stack, normalizeString)}`);
return `{${serializedFields.join(",")}}`;
}
function encodeBase64(value) {
let binary = "";
for (const byte of value) binary += String.fromCharCode(byte);
return btoa(binary);
}
function compareStableStrings(left, right) {
return left < right ? -1 : left > right ? 1 : 0;
}
//#endregion
//#region src/shared/immutable-data.ts
const deeplyFrozenPlainData = /* @__PURE__ */ new WeakSet();
function isPlainDataObject(value) {
if (isProxy(value)) return false;
const prototype = Object.getPrototypeOf(value);
return prototype === null || prototype === Object.prototype || Array.isArray(value) && prototype === Array.prototype;
}
/** Immutable graphs keep their classification for exactly as long as their objects live. */
function isDeeplyFrozenPlainData(value) {
if (!value || typeof value !== "object") return typeof value !== "function";
if (deeplyFrozenPlainData.has(value)) return true;
if (!isPlainDataObject(value) || !Object.isFrozen(value)) return false;
const inspected = /* @__PURE__ */ new Set();
const pending = [value];
while (pending.length) {
const candidate = pending.pop();
if (inspected.has(candidate)) continue;
if (!isPlainDataObject(candidate) || !Object.isFrozen(candidate)) return false;
inspected.add(candidate);
const childStart = pending.length;
for (const key of Reflect.ownKeys(candidate)) {
const descriptor = Object.getOwnPropertyDescriptor(candidate, key);
if (!("value" in descriptor) || typeof descriptor.value === "function") return false;
if (descriptor.value && typeof descriptor.value === "object" && !deeplyFrozenPlainData.has(descriptor.value)) pending.push(descriptor.value);
}
for (let left = childStart, right = pending.length - 1; left < right; left++, right--) {
const child = pending[left];
pending[left] = pending[right];
pending[right] = child;
}
}
for (const candidate of inspected) deeplyFrozenPlainData.add(candidate);
return true;
}
//#endregion
//#region src/config/resolution-facts.ts
const configResolutionFacts = /* @__PURE__ */ new WeakMap();
const envSecretRefsByFacts = /* @__PURE__ */ new WeakMap();
function createConfigResolutionFacts(warnings, pendingEnvSecretRefs = /* @__PURE__ */ new Map(), envProvider = DEFAULT_SECRET_PROVIDER_ALIAS, resolvedEnvSecretRefs = /* @__PURE__ */ new Map()) {
const facts = new Set(warnings.map(({ configPath }) => configPath));
const provider = envProvider?.trim() || "default";
if (pendingEnvSecretRefs.size > 0 || resolvedEnvSecretRefs.size > 0) {
const envSecretRefs = /* @__PURE__ */ new Map();
for (const [path, id] of pendingEnvSecretRefs) envSecretRefs.set(path, {
ref: {
source: "env",
provider,
id
},
state: "pending"
});
for (const [path, id] of resolvedEnvSecretRefs) envSecretRefs.set(path, {
ref: {
source: "env",
provider,
id
},
state: "resolved"
});
envSecretRefsByFacts.set(facts, envSecretRefs);
}
return facts;
}
function setConfigResolutionFacts(target, facts) {
if (!target || typeof target !== "object") return;
if (facts === null) {
configResolutionFacts.delete(target);
return;
}
configResolutionFacts.set(target, facts);
}
function getConfigResolutionFacts(target) {
return target && typeof target === "object" ? configResolutionFacts.get(target) ?? null : null;
}
function copyConfigResolutionFacts(source, target) {
setConfigResolutionFacts(target, getConfigResolutionFacts(source));
}
function cloneConfigWithResolutionFacts(value) {
const cloned = structuredClone(value);
copyConfigResolutionFacts(value, cloned);
return cloned;
}
function copyConfigResolutionFactsExcept(source, target, paths) {
const facts = getConfigResolutionFacts(source);
if (facts === null) {
setConfigResolutionFacts(target, null);
return;
}
const envSecretRefs = envSecretRefsByFacts.get(facts);
if (!paths.some((path) => facts.has(path) || envSecretRefs?.has(path) === true)) {
setConfigResolutionFacts(target, facts);
return;
}
const remaining = new Set(facts);
paths.forEach((path) => remaining.delete(path));
if (envSecretRefs) {
const remainingEnvSecretRefs = new Map(envSecretRefs);
paths.forEach((path) => remainingEnvSecretRefs.delete(path));
if (remainingEnvSecretRefs.size > 0) envSecretRefsByFacts.set(remaining, remainingEnvSecretRefs);
}
setConfigResolutionFacts(target, remaining);
}
function hasUnresolvedConfigPath(target, path) {
return getConfigResolutionFacts(target)?.has(path) === true;
}
/** Returns only a still-pending reference recorded from the authored config source. */
function getAuthoredConfigSecretRef(target, path) {
const facts = getConfigResolutionFacts(target);
const fact = facts ? envSecretRefsByFacts.get(facts)?.get(path) : void 0;
return fact?.state === "pending" ? fact.ref : null;
}
/** Returns the env source of a value that config substitution already materialized. */
function getResolvedConfigEnvSecretRef(target, path) {
const facts = getConfigResolutionFacts(target);
const fact = facts ? envSecretRefsByFacts.get(facts)?.get(path) : void 0;
return fact?.state === "resolved" ? fact.ref : null;
}
/** Reads inline references from authored facts and structured references from their values. */
function resolveConfigSecretRef(params) {
return typeof params.value === "string" && getConfigResolutionFacts(params.config) !== null ? getAuthoredConfigSecretRef(params.config, params.path) ?? (params.includeResolved ? getResolvedConfigEnvSecretRef(params.config, params.path) : null) : coerceSecretRef(params.value, params.defaults);
}
//#endregion
//#region src/config/legacy.default-agent-owner-state.ts
const legacyDefaultAgentIdByConfig = /* @__PURE__ */ new WeakMap();
function getRetainedLegacyDefaultAgentId(config) {
return legacyDefaultAgentIdByConfig.get(config);
}
//#endregion
//#region packages/normalization-core/src/result.ts
/** Create a successful {@link Result}. */
function ok(value) {
return {
ok: true,
value
};
}
/** Create a failed {@link Result}. */
function err(error) {
return {
ok: false,
error
};
}
//#endregion
//#region packages/normalization-core/src/agent-id.ts
const DEFAULT_AGENT_ID = "main";
const VALID_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
const INVALID_CHARS_RE = /[^a-z0-9_-]+/g;
const LEADING_DASH_RE = /^-+/;
const TRAILING_DASH_RE = /-+$/;
/** Normalizes an OpenClaw agent id to its filesystem-safe canonical form. */
function normalizeAgentId(value) {
const result = normalizeAgentIdStrict(value);
return result.ok ? result.value : DEFAULT_AGENT_ID;
}
/** Normalizes an explicitly supplied agent id without falling back to the default agent. */
function normalizeAgentIdStrict(value) {
const trimmed = (value ?? "").trim();
const normalized = trimmed.toLowerCase();
if (VALID_ID_RE.test(trimmed)) return ok(normalized);
const agentId = normalized.replace(INVALID_CHARS_RE, "-").replace(LEADING_DASH_RE, "").replace(TRAILING_DASH_RE, "").slice(0, 64);
return agentId ? ok(agentId) : err("unrepresentable");
}
new AsyncLocalStorage();
//#endregion
//#region src/infra/sqlite-readonly-worker-protocol.ts
const SQLITE_READONLY_WORKER_MAX_BUFFER = 1048576;
function isSqliteSnapshotStagingMode(mode) {
return mode === "staging-create" || mode === "staging-create-legacy" || mode === "staging-reconcile" || mode === "staging-retire";
}
var SqliteReadOnlyInspectionContentionError = class extends Error {};
function isSqliteReadOnlyWorkerResult(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
if (Object.keys(value).length !== 2 || !("ok" in value)) return false;
return value.ok === true && "location" in value && typeof value.location === "string" || value.ok === true && "contentVersion" in value && typeof value.contentVersion === "string" && /^(?:[a-f0-9]{64})?$/.test(value.contentVersion) || value.ok === true && "warnings" in value && Array.isArray(value.warnings) && value.warnings.every((warning) => typeof warning === "string") || value.ok === false && "message" in value && typeof value.message === "string";
}
function createSqliteReadOnlyWorkerError(message, stderr) {
const stderrTail = toUSVString(sliceUtf16Safe(stderr.trim(), -4e3));
return /* @__PURE__ */ new Error(`SQLite read-only worker ${message}${stderrTail ? `\nstderr (tail): ${stderrTail}` : ""}`);
}
function parseSqliteReadOnlyWorkerResult(stdout, stderr) {
if (!stdout.trim()) throw createSqliteReadOnlyWorkerError("returned no JSON result", stderr);
let message;
try {
message = JSON.parse(stdout);
} catch {
throw createSqliteReadOnlyWorkerError("returned invalid JSON", stderr);
}
if (!isSqliteReadOnlyWorkerResult(message)) throw createSqliteReadOnlyWorkerError("returned an invalid result", stderr);
return message;
}
function readSqliteReadOnlyWorkerValue(params, mode) {
let result;
try {
result = parseSqliteReadOnlyWorkerResult(params.stdout, params.stderr);
} catch (error) {
if (params.failure) throw createSqliteReadOnlyWorkerError(params.failure, params.stderr);
throw error;
}
if (params.failure || !result.ok) {
const contention = !result.ok && result.message.startsWith("Retryable SQLite inspection contention: ");
const error = createSqliteReadOnlyWorkerError(!result.ok ? contention ? result.message.slice(40) : result.message : params.failure ?? "failed", params.stderr);
if (contention) throw new SqliteReadOnlyInspectionContentionError(error.message);
throw error;
}
if ((mode === "sync" || mode === "async" || mode === "consolidated" || isSqliteSnapshotStagingMode(mode)) && "location" in result) return result.location;
if (mode === "content-version" && "contentVersion" in result) return result.contentVersion;
if (mode === "reclaim" && "warnings" in result) return result.warnings;
throw createSqliteReadOnlyWorkerError("returned a result for a different operation", params.stderr);
}
//#endregion
//#region src/infra/sqlite-readonly-worker.ts
const SQLITE_INSPECTION_TIMEOUT_MS = 3e5;
const SQLITE_INSPECTION_BYTES_PER_SECOND = 33554432;
const log = createSubsystemLogger("state/sqlite");
function resolveSqliteInspectionBudget(operation, pathname, sizeBytes) {
const timeoutMs = resolveTimerTimeoutMs(SQLITE_INSPECTION_TIMEOUT_MS + Math.ceil(40 * Number(sizeBytes ?? 0) / SQLITE_INSPECTION_BYTES_PER_SECOND) * 1e3, SQLITE_INSPECTION_TIMEOUT_MS);
const size = sizeBytes === void 0 ? "unknown size" : formatByteSize(Number(sizeBytes), {
style: "iec",
maxUnit: "giga",
separator: " ",
fractionDigits: sizeBytes < 1024n ? 0 : 1
});
if (timeoutMs > SQLITE_INSPECTION_TIMEOUT_MS) log.debug(`SQLite ${operation} for ${pathname}: ${size}, budget ${timeoutMs / 1e3} seconds`);
return {
timeoutMs,
size
};
}
function readSqliteInspectionBudget(operation, pathname, mainSizeBytes) {
let sizeBytes = mainSizeBytes;
try {
sizeBytes ??= fsSync.statSync(pathname, { bigint: true }).size;
for (const suffix of [
"-wal",
"-shm",
"-journal"
]) try {
sizeBytes += fsSync.statSync(pathname + suffix, { bigint: true }).size;
} catch (error) {
if (!hasErrnoCode(error, "ENOENT")) throw error;
}
} catch {}
return resolveSqliteInspectionBudget(operation, pathname, sizeBytes);
}
function sqliteInspectionTimeoutError(operation, pathname, timeoutMs, size) {
return /* @__PURE__ */ new Error(`SQLite ${operation} timed out after ${timeoutMs / 1e3} seconds (budget for ${size}) for ${pathname}. Stop the Gateway service and other OpenClaw processes using this database, then retry; if already stopped, check storage performance.`);
}
function sqliteReadOnlyWorkerRequestArgs(pathname, options) {
return [
options.mode,
path.resolve(pathname),
...options.stagingRoot ? [options.stagingRoot] : []
];
}
function sqliteReadOnlyWorkerArgv(pathname, options) {
const { moduleUrl, runtimeGeneration } = captureRuntimeWorkerSource(resolveRuntimeWorkerUrl(options.mode === "content-version" ? runtimeProcessEntrypoints.sqliteSourceRevision : runtimeProcessEntrypoints.sqliteReadOnly));
return {
runtimeGeneration,
argv: [
...resolveRuntimeWorkerArgv(moduleUrl),
SQLITE_READONLY_CHILD_ARG,
...sqliteReadOnlyWorkerRequestArgs(pathname, options)
]
};
}
function runSqliteReadOnlyWorkerSync(pathname, stagingRoot, mode = "sync") {
const { timeoutMs, size } = readSqliteInspectionBudget("read-only snapshot", pathname);
const started = log.isEnabled("trace") ? performance.now() : void 0;
const result = spawnSync(process.execPath, sqliteReadOnlyWorkerArgv(pathname, {
mode,
stagingRoot
}).argv, {
encoding: "utf8",
env: resolveNodeCompileCacheEnv(),
maxBuffer: SQLITE_READONLY_WORKER_MAX_BUFFER,
timeout: timeoutMs,
killSignal: "SIGKILL"
});
if (started !== void 0) log.trace(`SQLite read-only snapshot child durationMs=${performance.now() - started}`);
return readSqliteReadOnlyWorkerValue({
failure: result.error ? hasErrnoCode(result.error, "ETIMEDOUT") ? sqliteInspectionTimeoutError("read-only snapshot", pathname, timeoutMs, size).message : `failed to start: ${result.error.message}` : result.status === 0 ? void 0 : `exited with ${result.signal ? `signal ${result.signal}` : `code ${result.status}`}`,
stderr: result.stderr,
stdout: result.stdout
}, mode);
}
//#endregion
//#region src/infra/sqlite-snapshot-source.ts
function prepareSqliteReadOnlyLocationSync(pathname) {
const stagingRoot = createSqliteSnapshotStagingDirectorySync();
try {
return adoptPreparedLocation(runSqliteReadOnlyWorkerSync(pathname, stagingRoot), stagingRoot);
} catch (error) {
if (!removeTempDirectory(stagingRoot)) throw new SqliteSnapshotCleanupError(`${coerceErrorMessage(error)}; SQLite snapshot cleanup failed: ${stagingRoot}`, { cause: error });
throw error;
}
}
//#endregion
//#region packages/gateway-protocol/src/failover-reasons.ts
const FAILOVER_REASONS = [
"auth",
"auth_permanent",
"format",
"rate_limit",
"overloaded",
"billing",
"server_error",
"timeout",
"tls_certificate",
"context_overflow",
"model_not_found",
"session_expired",
"empty_response",
"no_error_details",
"unclassified",
"unknown"
];
//#endregion
//#region src/cron/completion-status.ts
/** Resolves authored completion from an admitted job, or legacy completion from stored facts. */
function resolveCronCompletionStatus(params) {
if (params.status === "error" || params.status === "skipped") return "failed";
if (params.status !== "ok") return "unknown";
if (params.requiredDelivery === void 0) return params.delivered === true || params.deliveryStatus === "delivered" || params.deliveryStatus === "not-requested" ? "succeeded" : "unknown";
if (!params.requiredDelivery || params.deliveryStatus === "delivered" || params.deliveryStatus === "not-delivered" && params.deliverySuppressionReason !== void 0) return "succeeded";
return params.deliveryStatus === "not-delivered" ? "failed" : "unknown";
}
const CRON_TIMEOUT_ERROR_PREFIXES = [
"cron: job execution timed out",
"cron: isolated agent setup timed out before runner start",
"cron: isolated agent run stalled before execution start"
];
/** Recognizes watchdog timeouts without loading agent or execution-phase runtime. */
function isCronTimeoutErrorText(error) {
return typeof error === "string" && CRON_TIMEOUT_ERROR_PREFIXES.some((prefix) => error === prefix || error.startsWith(`${prefix} `));
}
//#endregion
//#region src/cron/run-diagnostics-normalize.ts
const MAX_ENTRIES = 10;
const MAX_ENTRY_CHARS = 1e3;
const MAX_SUMMARY_CHARS = 2e3;
function normalizeSeverity(value) {
return value === "info" || value === "warn" || value === "error" ? value : "error";
}
function normalizeSource(value) {
switch (value) {
case "cron-preflight":
case "cron-setup":
case "model-preflight":
case "agent-run":
case "tool":
case "exec":
case "delivery": return value;
default: return "agent-run";
}
}
function normalizeTimestamp$1(value, nowMs) {
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : nowMs();
}
function normalizeDiagnosticMessage(value, redactText) {
if (typeof value !== "string") return {};
const normalized = normalizeOptionalString(value);
if (!normalized) return {};
const redacted = redactText(normalized);
if (redacted.length <= MAX_ENTRY_CHARS) return { message: redacted };
return {
message: `${truncateUtf16Safe(redacted, 999)}…`,
truncated: true
};
}
function normalizeCronRunDiagnosticSummary(value) {
const normalized = normalizeOptionalString(value);
if (!normalized) return;
if (normalized.length <= MAX_SUMMARY_CHARS) return normalized;
return `${truncateUtf16Safe(normalized, 1999)}…`;
}
/** Normalizes stored cron diagnostic payloads into bounded entries. */
function normalizeCronRunDiagnosticsCore(value, opts) {
if (!value || typeof value !== "object") return;
const record = value;
const nowMs = opts?.nowMs ?? Date.now;
const redactText = opts?.redactText ?? ((text) => text);
const entriesRaw = Array.isArray(record.entries) ? record.entries : [];
const entries = [];
for (const item of entriesRaw) {
if (!item || typeof item !== "object") continue;
const entry = item;
const normalized = normalizeDiagnosticMessage(entry.message, redactText);
if (!normalized.message) continue;
entries.push({
ts: normalizeTimestamp$1(entry.ts, nowMs),
source: normalizeSource(entry.source),
severity: normalizeSeverity(entry.severity),
message: normalized.message,
...typeof entry.toolName === "string" && entry.toolName.trim() ? { toolName: entry.toolName.trim() } : {},
...typeof entry.exitCode === "number" && Number.isFinite(entry.exitCode) ? { exitCode: entry.exitCode } : entry.exitCode === null ? { exitCode: null } : {},
...entry.truncated === true || normalized.truncated ? { truncated: true } : {}
});
if (entries.length > MAX_ENTRIES) entries.shift();
}
const summary = normalizeCronRunDiagnosticSummary(typeof record.summary === "string" ? redactText(record.summary) : void 0);
if (entries.length === 0 && !summary) return;
return {
...summary ? { summary } : {},
entries
};
}
//#endregion
//#region src/cron/run-history-detail.ts
/** Read-side cron codec between cron history detail and the stable run-history wire shape.
* Deliberately free of agent/runtime imports so history reads stay dependency-light;
* the event->entry write codec lives in run-event-codec.ts. */
const CRON_RUN_DETAIL_KIND = "cron-run";
const cronRunStatusSchema = _enum([
"ok",
"error",
"skipped"
]);
const cronCompletionStatusSchema = _enum([
"succeeded",
"failed",
"unknown"
]);
const cronDeliveryStatusSchema = _enum([
"delivered",
"not-delivered",
"unknown",
"not-requested"
]);
const optionalCronStringSchema = string().optional().catch(void 0);
const optionalNonBlankCronStringSchema = string().refine((value) => value.trim().length > 0).optional().catch(void 0);
const optionalCronTimestampSchema = unknown().optional().transform((value) => normalizeTimestamp(value));
const optionalCronNonnegativeIntegerSchema = unknown().optional().transform((value) => asSafeIntegerInRange(value, { min: 0 }));
const cronUsageSchema = object({
input_tokens: optionalCronNonnegativeIntegerSchema,
output_tokens: optionalCronNonnegativeIntegerSchema,
total_tokens: optionalCronNonnegativeIntegerSchema,
cache_read_tokens: optionalCronNonnegativeIntegerSchema,
cache_write_tokens: optionalCronNonnegativeIntegerSchema
}).transform((usage) => Object.values(usage).some((tokenCount) => tokenCount !== void 0) ? usage : void 0).optional().catch(void 0);
const cronFailureNotificationDeliverySchema = looseObject({
status: cronDeliveryStatusSchema,
delivered: boolean().optional().catch(void 0),
error: optionalCronStringSchema
}).transform(({ status, delivered, error }) => ({
status,
...delivered !== void 0 ? { delivered } : {},
...error !== void 0 ? { error } : {}
})).optional().catch(void 0);
const cronRunLogEntrySchema = looseObject({
action: literal("finished"),
jobId: string().refine((value) => value.trim().length > 0),
ts: unknown().transform((value) => normalizeTimestamp(value)).pipe(number()),
status: cronRunStatusSchema.optional().catch(void 0),
completionStatus: cronCompletionStatusSchema.optional().catch(void 0),
error: optionalCronStringSchema,
errorReason: _enum(FAILOVER_REASONS).optional().catch(void 0),
summary: optionalCronStringSchema,
runId: optionalNonBlankCronStringSchema,
diagnostics: unknown().optional(),
runAtMs: optionalCronTimestampSchema,
durationMs: optionalCronNonnegativeIntegerSchema,
nextRunAtMs: optionalCronTimestampSchema,
triggerFired: unknown().optional().transform((value) => value === true ? true : void 0),
model: optionalNonBlankCronStringSchema,
provider: optionalNonBlankCronStringSchema,
usage: cronUsageSchema,
delivered: boolean().optional().catch(void 0),
deliveryStatus: cronDeliveryStatusSchema.optional().catch(void 0),
deliveryError: optionalCronStringSchema,
deliverySuppressionReason: _enum([
"empty",
"silent",
"heartbeat",
"channel_transform"
]).optional().catch(void 0),
failureNotificationDelivery: cronFailureNotificationDeliverySchema,
delivery: custom(isJsonObject).optional().catch(void 0),
sessionId: optionalNonBlankCronStringSchema,
sessionKey: optionalNonBlankCronStringSchema
});
function isJsonValue(value) {
const pending = [value];
while (pending.length > 0) {
const current = pending.pop();
if (current === null || typeof current === "string" || typeof current === "boolean" || typeof current === "number") continue;
if (!Array.isArray(current) && !isRecord(current)) return false;
for (const child of Object.values(current)) pending.push(child);
}
return true;
}
/** Native JSON parsing keeps released scalar/null and numeric-overflow semantics. */
function parseCronRunDetailJson(serialized) {
const value = safeParseJson(serialized);
return isJsonValue(value) ? value : void 0;
}
function toJsonValue(value) {
const serialized = JSON.stringify(value);
return serialized === void 0 ? void 0 : parseCronRunDetailJson(serialized);
}
function isJsonObject(value) {
return isRecord(value);
}
function normalizeTimestamp(value) {
return asSafeIntegerInRange(value, {
min: 0,
max: MAX_DATE_TIMESTAMP_MS
});
}
/** Parses stored or migrated cron history while preserving the stable wire shape. */
function parseCronRunLogEntryObject(obj, opts) {
const jobId = normalizeOptionalString(opts?.jobId);
const parsed = cronRunLogEntrySchema.safeParse(obj);
if (!parsed.success) return null;
const entryObj = parsed.data;
if (jobId && entryObj.jobId !== jobId) return null;
const entry = {
ts: entryObj.ts,
jobId: entryObj.jobId,
action: "finished",
status: entryObj.status,
completionStatus: entryObj.completionStatus ?? resolveCronCompletionStatus({
status: entryObj.status,
delivered: entryObj.delivered,
deliveryStatus: entryObj.deliveryStatus
}),
error: entryObj.error,
errorReason: entryObj.errorReason,
summary: entryObj.summary,
runId: entryObj.runId,
diagnostics: normalizeCronRunDiagnosticsCore(entryObj.diagnostics),
runAtMs: entryObj.runAtMs,
durationMs: entryObj.durationMs,
nextRunAtMs: entryObj.nextRunAtMs,
triggerFired: entryObj.triggerFired,
model: entryObj.model,
provider: entryObj.provider,
usage: entryObj.usage
};
for (const field of [
"delivered",
"deliveryStatus",
"deliveryError",
"deliverySuppressionReason",
"failureNotificationDelivery",
"delivery",
"sessionId",
"sessionKey"
]) if (entryObj[field] !== void 0) Object.assign(entry, { [field]: entryObj[field] });
return entry;
}
/** Encodes Cron-owned outcome fields for retained history. */
function cronRunLogEntryToDetail(entry, options) {
return toJsonValue({
kind: CRON_RUN_DETAIL_KIND,
status: entry.status,
completionStatus: entry.completionStatus,
error: entry.error ?? null,
summary: entry.summary ?? null,
storeKey: options.storeKey,
errorReason: entry.errorReason,
diagnostics: entry.diagnostics,
delivered: entry.delivered,
deliveryStatus: entry.deliveryStatus,
deliveryError: entry.deliveryError,
deliverySuppressionReason: entry.deliverySuppressionReason,
failureNotificationDelivery: entry.failureNotificationDelivery,
delivery: entry.delivery,
sessionId: entry.sessionId,
runId: entry.runId,
runAtMs: entry.runAtMs,
durationMs: entry.durationMs,
nextRunAtMs: entry.nextRunAtMs,
triggerFired: entry.triggerFired,
triggerStateChanged: options.triggerEval?.fired === true ? options.triggerEval.stateChanged : void 0,
triggerState: options.triggerEval?.fired === true && options.triggerEval.stateChanged ? options.triggerEval.state : void 0,
scriptStateChanged: options.scriptResult?.scriptStateChanged === true ? true : void 0,
scriptState: options.scriptResult?.scriptStateChanged === true ? options.scriptResult.scriptState : void 0,
model: entry.model,
provider: entry.provider,
usage: entry.usage
}) ?? { kind: CRON_RUN_DETAIL_KIND };
}
/** Preserves the released history row status vocabulary. */
function cronRunStorageStatus(entry) {
if (entry.status === "ok") return (entry.completionStatus ?? resolveCronCompletionStatus({
status: entry.status,
delivered: entry.delivered,
deliveryStatus: entry.deliveryStatus
})) === "succeeded" ? "succeeded" : "failed";
return entry.status === "error" && isCronTimeoutErrorText(entry.error) ? "timed_out" : "failed";
}
//#endregion
//#region src/infra/state-migrations.cron-run-logs.ts
const CRON_RUN_LOG_TASK_IMPORT_MIGRATION_ID = "state:cron-run-logs-to-task-runs:v1";
const CRON_RUN_LOG_IMPORT_BATCH_SIZE = 500;
function hasLegacyCronRunLogs(db) {
return Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'cron_run_logs' LIMIT 1").get());
}
function collectMirroredTasks(db) {
const rows = db.prepare(`SELECT source_id, ended_at, detail_json
FROM task_runs
WHERE runtime = 'cron' AND source_id IS NOT NULL AND detail_json IS NOT NULL`).all();
const bySource = /* @__PURE__ */ new Map();
for (const row of rows) {
const detail = row.detail_json ? safeParseJsonRecord(row.detail_json) : void 0;
if (!row.source_id || detail?.kind !== "cron-run") continue;
const identities = bySource.get(row.source_id) ?? [];
identities.push({
endedAt: normalizeSqliteNumber(row.ended_at) ?? null,
...typeof detail.runId === "string" && detail.runId ? { runId: detail.runId } : {}
});
bySource.set(row.source_id, identities);
}
return bySource;
}
function hasMirroredIdentity(identities, runId, endedAt) {
return identities.some((identity) => runId && identity.runId ? identity.runId === runId : identity.endedAt === endedAt);
}
function integerToBoolean(value) {
return value === null || value === void 0 ? void 0 : coerceRequiredSqliteNumber(value) !== 0;
}
/** Legacy rows trust write-time errorReason and diagnostic redaction without recomputation. */
function parseLegacyRow(row) {
const parsed = parseCronRunLogEntryObject(safeParseJson(row.entry_json ?? ""), { jobId: row.job_id });
if (!parsed) return null;
return {
...parsed,
ts: normalizeSqliteNumber(row.ts) ?? parsed.ts,
jobId: row.job_id,
status: row.status ?? parsed.status,
error: row.error ?? parsed.error,
summary: row.summary ?? parsed.summary,
delivered: integerToBoolean(row.delivered) ?? parsed.delivered,
deliveryStatus: row.delivery_status ?? parsed.deliveryStatus,
deliveryError: row.delivery_error ?? parsed.deliveryError,
sessionId: row.session_id ?? parsed.sessionId,
sessionKey: row.session_key ?? parsed.sessionKey,
runId: row.run_id ?? parsed.runId,
runAtMs: normalizeSqliteNumber(row.run_at_ms ?? null) ?? parsed.runAtMs,
durationMs: normalizeSqliteNumber(row.duration_ms ?? null) ?? parsed.durationMs,
nextRunAtMs: normalizeSqliteNumber(row.next_run_at_ms ?? null) ?? parsed.nextRunAtMs,
model: row.model ?? parsed.model,
provider: row.provider ?? parsed.provider
};
}
/** Runs inside the state schema transaction and removes the retired table after import. */
function migrateLegacyCronRunLogsToTaskRuns(db) {
if (!hasLegacyCronRunLogs(db)) return {
imported: 0,
alreadyMirrored: 0,
malformed: 0,
skipped: true
};
const mirrored = collectMirroredTasks(db);
const ordinals = /* @__PURE__ */ new Map();
const insert = db.prepare(`
INSERT INTO task_runs (
task_id, runtime, task_kind, source_id, requester_session_key, owner_key, scope_kind,
child_session_key, parent_flow_id, parent_task_id, agent_id, requester_agent_id, run_id,
label, task, status, delivery_status, notify_policy, created_at, started_at, ended_at,
last_event_at, cleanup_after, error, progress_summary, terminal_summary, terminal_outcome,
detail_json
) VALUES (
@task_id, 'cron', NULL, @source_id, '', '', 'system', @child_session_key, NULL, NULL,
NULL, NULL, @run_id, NULL, @task, @status, 'not_applicable', 'silent', @created_at,
@started_at, @ended_at, @ended_at, NULL, @error, NULL, @terminal_summary,
@terminal_outcome, @detail_json
)
`);
let imported = 0;
let alreadyMirrored = 0;
let malformed = 0;
let offset = 0;
while (true) {
const rows = db.prepare(`SELECT * FROM cron_run_logs
ORDER BY job_id, ts, store_key, seq
LIMIT ? OFFSET ?`).all(CRON_RUN_LOG_IMPORT_BATCH_SIZE, offset);
if (rows.length === 0) break;
offset += rows.length;
for (const row of rows) {
const entry = parseLegacyRow(row);
if (!entry) {
malformed++;
continue;
}
const key = `${entry.jobId}\0${entry.ts}`;
const ordinal = (ordinals.get(key) ?? 0) + 1;
ordinals.set(key, ordinal);
if (hasMirroredIdentity(mirrored.get(entry.jobId) ?? [], entry.runId, entry.ts)) {
alreadyMirrored++;
continue;
}
const taskId = `cron-runlog-import:${entry.jobId}:${entry.ts}:${ordinal}`;
const status = cronRunStorageStatus(entry);
insert.run({
task_id: taskId,
source_id: entry.jobId,
child_session_key: entry.sessionKey?.trim() || null,
run_id: taskId,
task: entry.jobId,
status,
created_at: entry.runAtMs ?? entry.ts,
started_at: entry.runAtMs ?? null,
ended_at: entry.ts,
error: entry.error ?? null,
terminal_summary: entry.summary ?? null,
terminal_outcome: status === "succeeded" ? "succeeded" : null,
detail_json: JSON.stringify(cronRunLogEntryToDetail(entry, { storeKey: row.store_key }))
});
imported++;
}
}
db.exec(`
DROP INDEX IF EXISTS idx_cron_run_logs_store_ts;
DROP INDEX IF EXISTS idx_cron_run_logs_job_status;
DROP INDEX IF EXISTS idx_cron_run_logs_delivery;
DROP TABLE cron_run_logs;
`);
const result = {
imported,
alreadyMirrored,
malformed,
skipped: false
};
const now = Date.now();
db.prepare(`INSERT INTO migration_runs (id, started_at, finished_at, status, report_json)
VALUES (?, ?, ?, 'completed', ?)
ON CONFLICT(id) DO UPDATE SET
finished_at = excluded.finished_at,
status = excluded.status,
report_json = excluded.report_json`).run(CRON_RUN_LOG_TASK_IMPORT_MIGRATION_ID, now, now, JSON.stringify(result));
return result;
}
//#endregion
//#region src/infra/sqlite-audit-record.kernel.ts
const LEGACY_AUDIT_SEQUENCE_BASE = Number.MIN_SAFE_INTEGER;
function getAuditRecordKysely(database) {
return getNodeSqliteKysely(database);
}
function parseAuditRecord(row) {
return {
key: row.event_key,
value: JSON.parse(row.payload_json),
createdAt: row.created_at,
sequence: row.sequence
};
}
function countAuditRecords(database, scope) {
const row = executeSqliteQueryTakeFirstSync(database, getAuditRecordKysely(database).selectFrom("diagnostic_events").select((eb) => eb.fn.countAll().as("count")).where("scope", "=", scope));
return typeof row?.count === "bigint" ? coerceRequiredSqliteNumber(row.count) : row?.count ?? 0;
}
function nextAuditSequence(params) {
const next = (executeSqliteQueryTakeFirstSync(params.database, getAuditRecordKysely(params.database).selectFrom("diagnostic_events").select((eb) => eb.fn.max("sequence").as("sequence")).where("scope", "=", params.scope).where("sequence", params.legacy ? "<" : ">=", 0))?.sequence ?? (params.legacy ? LEGACY_AUDIT_SEQUENCE_BASE : 0)) + 1;
if (!Number.isSafeInteger(next) || params.legacy && next >= 0) throw new Error(`Audit sequence exhausted for scope ${params.scope}`);
return next;
}
function pruneAuditRecords(params) {
const overflow = countAuditRecords(params.database, params.scope) - params.maxEntries;
if (overflow <= 0) return;
const protectedKey = params.protectedKey;
const baseCandidates = getAuditRecordKysely(params.database).selectFrom("diagnostic_events").select("event_key").where("scope", "=", params.scope);
const candidates = (protectedKey === void 0 ? baseCandidates : baseCandidates.where("event_key", "!=", protectedKey)).orderBy("sequence", "asc").limit(overflow);
executeSqliteQuerySync(params.database, getAuditRecordKysely(params.database).deleteFrom("diagnostic_events").where("scope", "=", params.scope).where("event_key", "in", candidates));
}
function prepareSqliteAuditRecord(scope, record) {
const payloadJson = JSON.stringify(record.value);
if (payloadJson === void 0) throw new Error(`Audit record ${scope}/${record.key} is not JSON-serializable`);
return {
event_key: record.key,
payload_json: payloadJson,
created_at: record.createdAt
};
}
function createAuditRecordInsert(database) {
return prepareSqliteQuerySync(database, (parameter) => getAuditRecordKysely(database).insertInto("diagnostic_events").values({
scope: parameter((record) => record.scope),
event_key: parameter((record) => record.event_key),
payload_json: parameter((record) => record.payload_json),
created_at: parameter((record) => record.created_at),
sequence: parameter((record) => record.sequence)
}).onConflict((conflict) => conflict.columns(["scope", "event_key"]).doNothing()));
}
const auditRecordInserts = /* @__PURE__ */ new WeakMap();
/** Connection-bound operations; mutation callers retain the complete transaction. */
function createSqliteAuditRecordKernel(database, options) {
const scope = options.scope;
const maxEntries = options.maxEntries;
function insertRecord(record) {
let insert = auditRecordInserts.get(database);
if (!insert) {
insert = createAuditRecordInsert(database);
auditRecordInserts.set(database, insert);
}
insert({
...record,
scope
});
}
function upsertPreparedRecord(record) {
executeSqliteQuerySync(database, getAuditRecordKysely(database).insertInto("diagnostic_events").values({
scope,
event_key: record.event_key,
payload_json: record.payload_json,
created_at: record.created_at,
sequence: nextAuditSequence({
database,
scope,
legacy: false
})
}).onConflict((conflict) => conflict.columns(["scope", "event_key"]).doUpdateSet({
payload_json: record.payload_json,
created_at: record.created_at
})));
pruneAuditRecords({
database,
scope,
maxEntries,
protectedKey: record.event_key
});
}
function deleteRecord(key) {
executeSqliteQuerySync(database, getAuditRecordKysely(database).deleteFrom("diagnostic_events").where("scope", "=", scope).where("event_key", "=", key));
}
return {
register(record) {
insertRecord({
...record,
sequence: nextAuditSequence({
database,
scope,
legacy: false
})
});
pruneAuditRecords({
database,
scope,
maxEntries,
protectedKey: record.event_key
});
},
upsert: upsertPreparedRecord,
delete: deleteRecord,
compareAndSet(key, expectedPayloadJson, record) {
if ((executeSqliteQueryTakeFirstSync(database, getAuditRecordKysely(database).selectFrom("diagnostic_events").select("payload_json").where("scope", "=", scope).where("event_key", "=", key))?.payload_json ?? null) !== expectedPayloadJson) return false;
if (record) upsertPreparedRecord(record);
else deleteRecord(key);
return true;
},
registerLegacyMany(records) {
let sequence = nextAuditSequence({
database,
scope,
legacy: true
});
for (const record of records) {
insertRecord({
...record,
sequence
});
sequence += 1;
}
pruneAuditRecords({
database,
scope,
maxEntries
});
},
size() {
return countAuditRecords(database, scope);
},
entries() {
return executeSqliteQuerySync(database, getAuditRecordKysely(database).selectFrom("diagnostic_events").select([
"event_key",
"payload_json",
"created_at",
"sequence"
]).where("scope", "=", scope).orderBy("sequence", "asc")).rows.map((row) => {
const { sequence: _sequence, ...entry } = parseAuditRecord(row);
return entry;
});
},
latest(params) {
const baseQuery = getAuditRecordKysely(database).selectFrom("diagnostic_events").select([
"event_key",
"payload_json",
"created_at",
"sequence"
]).where("scope", "=", scope);
const query = params.beforeSequence === void 0 ? baseQuery : baseQuery.where("sequence", "<", params.beforeSequence);
return executeSqliteQuerySync(database, query.orderBy("sequence", "desc").limit(params.limit)).rows.map((row) => parseAuditRecord(row));
}
};
}
//#endregion
//#region src/state/openclaw-state-db-additive-columns.ts
const lazyColumns = [
[
"user_profile_identities",
"authorization_id",
"TEXT"
],
[
"user_profile_identities",
"authorization_basis_json",
"TEXT"
],
[
"claw_installs",
"bootstrap_content_digest",
"TEXT"
],
[
"claw_installs",
"bootstrap_source_path",
"TEXT"
],
[
"worker_environments",
"desktop_json",
"TEXT"
],
[
"worker_environments",
"bootstrap_install_kind",
"TEXT"
],
[
"worker_environments",
"preparation_purpose",
"TEXT"
],
[
"claw_package_refs",
"extension_adapter_identity",
"TEXT"
],
[
"claw_package_refs",
"extension_detected_format",
"TEXT"
],
[
"claw_package_refs",
"extension_format",
"TEXT"
],
[
"claw_package_refs",
"extension_id",
"TEXT"
],
[
"claw_package_refs",
"extension_mapped_json",
"TEXT"
],
[
"claw_package_refs",
"extension_unavailable_json",
"TEXT"
],
[
"worker_environments",
"shared_host",
"INTEGER"
],
[
"worker_environments",
"node_setup_id",
"TEXT"
],
[
"worker_environments",
"node_device_id",
"TEXT"
],
[
"worker_session_placements",
"terminal_reason",
"TEXT"
],
[
"worker_session_placements",
"terminal_at_ms",
"INTEGER"
],
[
"worker_workspace_pending_results",
"repository_workspace_id",
"TEXT",
true
],
[
"worker_session_placement_moves",
"abandon_source",
"INTEGER",
true
],
[
"worker_session_placement_moves",
"target_machine_class",
"TEXT",
true
],
[
"worker_session_placement_moves",
"target_os",
"TEXT",
true
],
[
"worktrees",
"run_end_cleanup_json",
"TEXT"
],
[
"worktrees",
"gc_protection_json",
"TEXT"
],
[
"device_bootstrap_tokens",
"setup_id",
"TEXT",
true
],
[
"session_groups",
"cwd",
"TEXT",
true
],
[
"session_groups",
"worktree",
"INTEGER",
true
],
[
"secret_store_entries",
"allowed_hosts",
"TEXT"
],
[
"web_push_subscriptions",
"device_id",
"TEXT",
true
],
[
"web_push_subscriptions",
"user_profile_id",
"TEXT",
true
],
[
"web_push_subscriptions",
"preferences_json",
"TEXT",
true
],
[
"task_runs",
"execution_owner_host",
"TEXT",
true
],
[
"task_runs",
"execution_owner_pid",
"INTEGER",
true
],
[
"task_runs",
"execution_owner_start_identity",
"INTEGER",
true
],
[
"session_watch_cursors",
"watcher_store_path",
"TEXT",
true
],
[
"subagent_runs",
"requester_store_path",
"TEXT",
true
],
[
"subagent_runs",
"controller_store_path",
"TEXT",
true
],
[
"cron_jobs",
"grant_definition_revision",
"TEXT"
],
[
"cron_jobs",
"grant_definition_generation",
"INTEGER"
],
[
"cron_jobs",
"grant_definition_updated_at",
"INTEGER"
]
];
function lazyColumnDefinitions(firstUseOnly) {
return lazyColumns.filter((definition) => firstUseOnly === void 0 || Boolean(definition[3]) === firstUseOnly).map(([tableName, columnName, dataType]) => ({
columnName,
dataType,
tableName
}));
}
const CLAW_LAZY_ADDITIVE_STATE_COLUMN_DEFINITIONS = lazyColumnDefinitions();
const CLAW_STARTUP_ADDITIVE_STATE_COLUMN_DEFINITIONS = lazyColumnDefinitions(false);
const CLAW_FIRST_USE_ADDITIVE_STATE_COLUMN_DEFINITIONS = lazyColumnDefinitions(true);
const ORDERED_STARTUP_ADDITIVE_STATE_COLUMNS = {
packageUpdatedAt: [["claw_package_refs", "updated_at_ms INTEGER NOT NULL DEFAULT 0"]],
packageIntegrity: [["claw_package_refs", "package_integrity TEXT NOT NULL DEFAULT 'sha256:0000000000000000000000000000000000000000000000000000000000000000'"]],
diagnosticSequence: [["diagnostic_events", "sequence INTEGER NOT NULL DEFAULT 0"]],
cronRunLogs: [
["worktrees", "provisioned_paths_json TEXT"],
["apns_registrations", "relay_origin TEXT"],
["device_pairing_pending", "refreshed_at_ms INTEGER"],
["device_pairing_pending", "browser_origin TEXT"],
["device_pairing_paired", "approved_via TEXT"],
["device_pairing_paired", "browser_origin TEXT"],
["device_pairing_paired", "operator_label TEXT"],
["device_pairing_paired", "node_surface_json TEXT"],
["device_pairing_paired", "pending_node_surface_json TEXT"],
["cron_run_logs", "status TEXT"],
["cron_run_logs", "error TEXT"],
["cron_run_logs", "summary TEXT"],
["cron_run_logs", "diagnostics_summary TEXT"],
["cron_run_logs", "delivery_status TEXT"],
["cron_run_logs", "delivery_error TEXT"],
["cron_run_logs", "delivered INTEGER"],
["cron_run_logs", "session_id TEXT"],
["cron_run_logs", "session_key TEXT"],
["cron_run_logs", "run_id TEXT"],
["cron_run_logs", "run_at_ms INTEGER"],
["cron_run_logs", "duration_ms INTEGER"],
["cron_run_logs", "next_run_at_ms INTEGER"],
["cron_run_logs", "model TEXT"],
["cron_run_logs", "provider TEXT"],
["cron_run_logs", "total_tokens INTEGER"],
["cron_run_logs", "entry_json TEXT NOT NULL DEFAULT '{}'"],
["cron_run_logs", "created_at INTEGER NOT NULL DEFAULT 0"]
],
acpReplay: [["acp_replay_events", "estimated_bytes INTEGER NOT NULL DEFAULT 0"], ["acp_replay_sessions", "estimated_bytes INTEGER NOT NULL DEFAULT 0"]],
cronJobs: [
["cron_jobs", "description TEXT"],
["cron_jobs", "declaration_key TEXT"],
["cron_jobs", "owner_agent_id TEXT"],
["cron_jobs", "name TEXT NOT NULL DEFAULT ''"],
["cron_jobs", "enabled INTEGER NOT NULL DEFAULT 1"],
["cron_jobs", "agent_id TEXT"],
["cron_jobs", "payload_kind TEXT NOT NULL DEFAULT 'message'"],
["cron_jobs", "state_json TEXT NOT NULL DEFAULT '{}'"],
["cron_jobs", "runtime_updated_at_ms INTEGER"],
["cron_jobs", "schedule_identity TEXT"],
["cron_jobs", "sort_order INTEGER NOT NULL DEFAULT 0"]
],
deliveryQueue: [
["sandbox_registry_entries", "session_key TEXT"],
["sandbox_registry_entries", "backend_id TEXT"],
["sandbox_registry_entries", "runtime_label TEXT"],
["sandbox_registry_entries", "image TEXT"],
["sandbox_registry_entries", "created_at_ms INTEGER"],
["sandbox_registry_entries", "last_used_at_ms INTEGER"],
["sandbox_registry_entries", "config_label_kind TEXT"],
["sandbox_registry_entries", "config_hash TEXT"],
["sandbox_registry_entries", "cdp_port INTEGER"],
["sandbox_registry_entries", "no_vnc_port INTEGER"],
["delivery_queue_entries", "entry_kind TEXT"],
["delivery_queue_entries", "session_key TEXT"],
["delivery_queue_entries", "channel TEXT"],
["delivery_queue_entries", "target TEXT"],
["delivery_queue_entries", "account_id TEXT"],
["delivery_queue_entries", "retry_count INTEGER NOT NULL DEFAULT 0"],
["delivery_queue_entries", "last_attempt_at INTEGER"],
["delivery_queue_entries", "last_error TEXT"],
["delivery_queue_entries", "recovery_state TEXT"],
["delivery_queue_entries", "platform_send_started_at INTEGER"]
],
originalMediaRoot: [["managed_outgoing_image_records", "original_media_root TEXT NOT NULL DEFAULT ''"]],
beforeTaskAttribution: [
["managed_outgoing_image_records", "agent_id TEXT"],
["managed_outgoing_image_records", "cleanup_pending INTEGER NOT NULL DEFAULT 0 CHECK (cleanup_pending IN (0, 1))"],
["current_conversation_bindings", "conversation_kind TEXT NOT NULL DEFAULT 'channel'"],
["device_bootstrap_tokens", "pending_profile_json TEXT"],
["gateway_restart_handoff", "restart_trace_started_at INTEGER"],
["gateway_restart_handoff", "restart_trace_last_at INTEGER"],
["gateway_restart_intent", "reason TEXT"],
["gateway_restart_sentinel", "delivery_channel TEXT"],
["gateway_restart_sentinel", "delivery_to TEXT"],
["gateway_restart_sentinel", "delivery_account_id TEXT"],
["gateway_restart_sentinel", "message TEXT"],
["gateway_restart_sentinel", "continuation_json TEXT"],
["gateway_restart_sentinel", "doctor_hint TEXT"],
["gateway_restart_sentinel", "stats_json TEXT"],
["gateway_boot_lifecycle", "startup_reason TEXT"],
["official_external_plugin_catalog_snapshots", "trust_mode TEXT"],
["official_external_plugin_catalog_snapshots", "trust_key_id TEXT"],
["official_external_plugin_catalog_snapshots", "trust_signature_count INTEGER"],
["official_external_plugin_catalog_snapshots", "trust_threshold INTEGER"],
["official_external_plugin_catalog_snapshots", "trust_verified_at TEXT"]
],
taskRequester: [["task_runs", "requester_agent_id TEXT"]],
taskRunDetails: [
["task_runs", "tool_use_count INTEGER"],
["task_runs", "last_tool_name TEXT"],
["task_runs", "detail_json TEXT"]
],
workerEnvironments: [
["worker_environments", "bootstrap_bundle_hash TEXT"],
["worker_environments", "bootstrap_openclaw_version TEXT"],
["worker_environments", "bootstrap_protocol_features_json TEXT"],
["worker_environments", "bootstrap_install_kind TEXT"],
["worker_environments", "owner_epoch INTEGER NOT NULL DEFAULT 0 CHECK (owner_epoch >= 0)"],
["worker_environments", "ssh_host_key TEXT"],
["worker_workspace_pending_results", "staged_result_ref TEXT"],
["worker_environments", "teardown_terminal_state TEXT CHECK (teardown_terminal_state IN ('destroyed', 'failed'))"]
]
};
//#endregion
//#region src/acp/event-ledger-bytes.ts
/** Retained UTF-8 text footprint, including the existing fixed allowance per row. */
function estimateAcpSessionRowBytes(params) {
return Buffer.byteLength(params.sessionId, "utf8") + Buffer.byteLength(params.sessionKey, "utf8") + Buffer.byteLength(params.cwd, "utf8") + 32;
}
function estimateAcpEventRowBytes(params) {
return Buffer.byteLength(params.sessionId, "utf8") + Buffer.byteLength(params.sessionKey, "utf8") + Buffer.byteLength(params.runId ?? "", "utf8") + Buffer.byteLength(params.updateJson, "utf8") + 32;
}
//#endregion
//#region src/cron/definition-hash.ts
/** Hashes one canonical cron definition while preserving meaningful env-key order. */
function hashCronJobDefinition(definition) {
const payload = isRecord(definition.payload) ? definition.payload : void 0;
const env = payload && isRecord(payload.env) ? payload.env : void 0;
if (payload?.kind !== "command" || !env) return `sha256:${sha256Base64Url(stableStringify(definition))}`;
const foldedKeys = /* @__PURE__ */ new Set();
if (!Object.keys(env).some((key) => {
const folded = key.toLowerCase();
if (foldedKeys.has(folded)) return true;
foldedKeys.add(folded);
return false;
})) return `sha256:${sha256Base64Url(stableStringify(definition))}`;
const { env: _env, ...payloadWithoutEnv } = payload;
const orderedDefinition = {
...definition,
payload: {
...payloadWithoutEnv,
envEntries: Object.entries(env)
}
};
return `sha256:${sha256Base64Url(stableStringify(orderedDefinition))}`;
}
//#endregion
//#region src/infra/approval-resolution-ref.ts
/** Build the full SHA-256 base64url locator used only when a transport cannot carry the exact id. */
function buildApprovalResolutionRef(params) {
return createHash("sha256").update(params.approvalKind, "utf8").update("\0", "utf8").update(params.approvalId, "utf8").digest("base64url");
}
//#endregion
//#region src/infra/delivery-queue-sqlite-bound.ts
const COMPLETED_TOMBSTONE_RETENTION_MS = 2592e6;
const BOUNDED_DELIVERY_RECEIPTS_SQL = `
SELECT * FROM (
SELECT rowid receipt_rowid, queue_name, id, enqueued_at,
json_extract(entry_json, '$.completionRetention.idPrefix') id_prefix,
json_extract(entry_json, '$.completionRetention.maxAgeMs') max_age_ms,
json_extract(entry_json, '$.completionRetention.maxEntries') max_entries
FROM delivery_queue_entries WHERE status IN ('completed', 'failed')
AND recovery_state = 'completed_bounded' AND json_valid(entry_json)
AND json_type(entry_json, '$.completionRetention') = 'object'
)
WHERE typeof(id_prefix) = 'text' AND id_prefix <> ''
AND substr(id, 1, length(id_prefix)) = id_prefix
AND typeof(max_age_ms) = 'integer' AND max_age_ms BETWEEN 1 AND 9007199254740991
AND typeof(max_entries) = 'integer' AND max_entries BETWEEN 1 AND 9007199254740991`;
/** Prunes bounded receipts globally or for one exact producer namespace. */
function pruneDeliveryQueueTombstones(db, now, prefix) {
const result = db.prepare(`WITH policies AS (
${BOUNDED_DELIVERY_RECEIPTS_SQL}
${prefix ? "AND queue_name = @queueName AND id_prefix = @idPrefix" : ""}
), ranked AS (
SELECT *, row_number() OVER (PARTITION BY queue_name, id_prefix
ORDER BY enqueued_at DESC, id DESC) retention_rank FROM policies
) DELETE FROM delivery_queue_entries WHERE rowid IN (
SELECT receipt_rowid FROM ranked
WHERE enqueued_at < @now - max_age_ms OR retention_rank > max_entries
)`).run(prefix ? {
now,
...prefix
} : { now });
const ordinaryPruned = prefix ? false : pruneOrdinaryDeliveryReceipts(db, now);
return result.changes > 0 || ordinaryPruned;
}
function pruneOrdinaryDeliveryReceipts(db, now) {
return (executeSqliteQuerySync(db, getNodeSqliteKysely(db).deleteFrom("delivery_queue_entries").where("status", "=", "completed").where("enqueued_at", "<", now - COMPLETED_TOMBSTONE_RETENTION_MS).where((eb) => eb.or([eb("recovery_state", "is", null), eb("recovery_state", "not in", ["completed_permanent", "completed_bounded"])]))).numAffectedRows ?? 0n) > 0n;
}
//#endregion
//#region src/infra/delivery-queue-sqlite.types.ts
/** Parse only the shipped completion-retention shape for one exact producer ID. */
function parseDeliveryQueueCompletionRetention(value, id) {
if (value === "permanent") return value;
if (!value || typeof value !== "object" || Array.isArray(value)) return;
const retention = value;
const idPrefix = typeof retention.idPrefix === "string" ? retention.idPrefix : "";
const maxAgeMs = asPositiveSafeInteger(retention.maxAgeMs);
const maxEntries = asPositiveSafeInteger(retention.maxEntries);
if (!idPrefix || !id.startsWith(idPrefix) || maxAgeMs === void 0 || maxEntries === void 0) return;
return {
idPrefix,
maxAgeMs,
maxEntries
};
}
const finite = (value) => typeof value === "number" && Number.isFinite(value);
/** Recover only authored or shipped producer ownership from a failed entry. */
function inferDeliveryQueueFailureRetention(entry, id, queueName, legacyAmbiguousSendEvidence = false) {
const explicit = parseDeliveryQueueCompletionRetention(entry.completionRetention, id) ?? parseDeliveryQueueCompletionRetention(entry.failureRetention, id);
if (explicit) return explicit;
const fence = asNullableRecord(asNullableRecord(entry.terminalPolicy)?.fence);
if (fence?.kind === "none") return;
const fenced = fence?.kind === "permanent" ? "permanent" : parseDeliveryQueueCompletionRetention(fence, id);
if (fenced) return fenced;
const durable = queueName === "outbound-preparing-v1" || queueName === "outbound-legacy-preparing-v1" || queueName === "outbound-prepared-migration-v1" || entry.retainOnFailure === true || asNullableRecord(entry.deliveryCompletion) !== null || queueName === "session" && finite(entry.availableAt);
const ambiguous = legacyAmbiguousSendEvidence && (typeof entry.platformSendAttemptId === "string" && entry.platformSendAttemptId.length > 0 || finite(entry.platformSendStartedAt) || entry.recoveryState === "send_attempt_started" || entry.recoveryState === "unknown_after_send" || queueName === "session" && (finite(entry.deliveryStartedAt) || typeof entry.settlementOutcome === "string" && entry.settlementOutcome.length > 0 || finite(entry.acknowledgedAt)));
return durable || ambiguous ? "permanent" : void 0;
}
/** Strip a terminal queue row to the producer policy needed for admission. */
function projectDeliveryQueueTerminalEntry(entry, terminalAt, terminal, completionRetention) {
const retryCount = Number.isSafeInteger(entry.retryCount) && entry.retryCount >= 0 ? entry.retryCount : 0;
const recoveryState = completionRetention === "permanent" ? "completed_permanent" : completionRetention ? "completed_bounded" : void 0;
return {
id: entry.id,
enqueuedAt: terminalAt,
retryCount,
...terminal === "completed" ? { acknowledgedAt: terminalAt } : { failedAt: terminalAt },
...completionRetention ? { completionRetention } : {},
...recoveryState ? { recoveryState } : {}
};
}
//#endregion
//#region src/state/openclaw-state-db-delivery-queue-backfill.ts
function nonNegativeSafeInteger(value) {
const number = typeof value === "bigint" ? Number(value) : value;
return typeof number === "number" && Number.isSafeInteger(number) && number >= 0 ? number : void 0;
}
const inferLegacyRetention = (entry, id, queue) => inferDeliveryQueueFailureRetention(entry ?? {}, id, queue, true);
/** Compact every preexisting failed row without inferring replay or owner policy. */
function compactLegacyDeliveryQueueFailures(db) {
const migrationNow = Date.now();
const retainPending = db.prepare(`UPDATE delivery_queue_entries SET entry_json = ?
WHERE queue_name = ? AND id = ? AND status = 'pending' AND entry_json = ?`);
const select = db.prepare(`SELECT queue_name, id, status, retry_count, entry_json, updated_at, failed_at, recovery_state
FROM delivery_queue_entries WHERE status IN ('pending', 'failed')`);
select.setReadBigInts(true);
const rows = select.all();
const remove = db.prepare(`DELETE FROM delivery_queue_entries WHERE queue_name = ? AND id = ? AND status = 'failed'`);
const compact = db.prepare(`UPDATE delivery_queue_entries
SET entry_kind = NULL, session_key = NULL, channel = NULL, target = NULL,
account_id = NULL, retry_count = @retryCount, last_attempt_at = NULL,
last_error = NULL, platform_send_started_at = NULL, entry_json = @entryJson,
enqueued_at = @failedAt, failed_at = @failedAt, recovery_state = @recoveryState
WHERE queue_name = @queueName AND id = @id AND status = 'failed'`);
for (const row of rows) {
if (row.recovery_state === "settlement_pending") continue;
const parsedEntry = safeParseJsonRecord(String(row.entry_json));
const queueName = String(row.queue_name);
const id = String(row.id);
if (row.status === "pending") {
if (parsedEntry?.retainOnFailure !== true && inferLegacyRetention(parsedEntry, id, queueName)) retainPending.run(JSON.stringify({
...parsedEntry,
retainOnFailure: true
}), queueName, id, String(row.entry_json));
continue;
}
const failedAt = nonNegativeSafeInteger(row.failed_at) ?? nonNegativeSafeInteger(row.updated_at) ?? migrationNow;
const entry = parsedEntry ?? {};
const retryCount = Math.max(nonNegativeSafeInteger(row.retry_count) ?? 0, nonNegativeSafeInteger(entry.retryCount) ?? 0);
const retention = parsedEntry ? inferLegacyRetention(entry, id, queueName) : "permanent";
if (!retention) {
remove.run(queueName, id);
continue;
}
const failedEntry = projectDeliveryQueueTerminalEntry({
id,
retryCount
}, failedAt, "failed", retention);
compact.run({
retryCount,
entryJson: JSON.stringify(failedEntry),
failedAt,
recoveryState: failedEntry.recoveryState ?? null,
queueName,
id
});
}
pruneDeliveryQueueTombstones(db, migrationNow);
}
//#endregion
//#region src/state/openclaw-state-schema.ts
const OPENCLAW_STATE_SCHEMA_SQL = "\n\nCREATE TABLE IF NOT EXISTS mcp_oauth_stores (\n store_key TEXT NOT NULL PRIMARY KEY,\n format_version INTEGER NOT NULL CHECK (format_version = 1),\n store_json TEXT NOT NULL,\n updated_at INTEGER NOT NULL\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS mcp_oauth_pending_authorizations (\n state TEXT NOT NULL PRIMARY KEY,\n store_key TEXT NOT NULL,\n create_time INTEGER NOT NULL\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS diagnostic_events (\n scope TEXT NOT NULL,\n event_key TEXT NOT NULL,\n payload_json TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n sequence INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (scope, event_key)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_diagnostic_events_scope_sequence\n ON diagnostic_events(scope, sequence, event_key);\n\nCREATE TABLE IF NOT EXISTS skill_usage (\n skill_file TEXT NOT NULL PRIMARY KEY,\n skill_key TEXT NOT NULL,\n skill_name TEXT NOT NULL,\n skill_source TEXT NOT NULL,\n first_used_at_ms INTEGER NOT NULL,\n last_used_at_ms INTEGER NOT NULL,\n use_count INTEGER NOT NULL,\n last_agent_id TEXT\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_skill_usage_key\n ON skill_usage(skill_key, skill_file);\n\n-- Profile-owned skill library: additive, absent until first publication/import.\nCREATE TABLE IF NOT EXISTS skill_library_entries (\n skill_id TEXT NOT NULL PRIMARY KEY,\n owner_profile_id TEXT,\n author_profile_id TEXT NOT NULL,\n slug TEXT NOT NULL,\n current_revision TEXT NOT NULL,\n shared INT NOT NULL,\n enabled INT NOT NULL,\n removed INT NOT NULL,\n created_at INT NOT NULL,\n updated_at INT NOT NULL\n) STRICT;\nCREATE TABLE IF NOT EXISTS skill_library_revisions (\n skill_id TEXT NOT NULL,\n revision TEXT NOT NULL,\n description TEXT NOT NULL,\n files_json TEXT NOT NULL,\n created_at INT NOT NULL,\n PRIMARY KEY (skill_id, revision)\n) STRICT;\nCREATE TABLE IF NOT EXISTS skill_library_events (\n event_id TEXT NOT NULL PRIMARY KEY,\n skill_id TEXT NOT NULL,\n revision TEXT NOT NULL,\n action TEXT NOT NULL,\n actor_profile_id TEXT NOT NULL,\n created_at INT NOT NULL\n) STRICT;\nCREATE TABLE IF NOT EXISTS skill_library_uploads (\n upload_id TEXT NOT NULL PRIMARY KEY,\n owner_profile_id TEXT NOT NULL,\n slug TEXT NOT NULL,\n size_bytes INT NOT NULL,\n sha256 TEXT NOT NULL,\n archive_blob BLOB NOT NULL,\n expires_at INT NOT NULL,\n published_skill_id TEXT\n) STRICT;\n-- End profile-owned skill library.\n\nCREATE TABLE IF NOT EXISTS skill_workshop_proposals (\n proposal_id TEXT NOT NULL PRIMARY KEY,\n record_json TEXT NOT NULL,\n owner_agent_id TEXT,\n kind TEXT NOT NULL CHECK (kind IN ('create', 'update')),\n status TEXT NOT NULL CHECK (status IN ('pending', 'applied', 'rejected', 'quarantined', 'stale')),\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n draft_hash TEXT NOT NULL,\n origin_agent_id TEXT,\n origin_session_key TEXT,\n origin_run_id TEXT,\n origin_message_id TEXT,\n applied_at TEXT,\n rejected_at TEXT,\n quarantined_at TEXT,\n stale_at TEXT,\n status_reason TEXT\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS skill_workshop_collection_reviews (\n review_id TEXT NOT NULL PRIMARY KEY,\n owner_agent_id TEXT NOT NULL,\n backup_id TEXT NOT NULL,\n create_time INTEGER NOT NULL,\n kept_names_json TEXT NOT NULL,\n written_names_json TEXT NOT NULL,\n dropped_json TEXT NOT NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_skill_workshop_collection_reviews_owner_time\n ON skill_workshop_collection_reviews(owner_agent_id, create_time DESC, review_id);\n\nCREATE TABLE IF NOT EXISTS skill_workshop_proposal_rollbacks (\n proposal_id TEXT NOT NULL PRIMARY KEY,\n written_at TEXT NOT NULL,\n target_skill_file TEXT NOT NULL,\n action TEXT NOT NULL CHECK (action IN ('create', 'update')),\n previous_content_hash TEXT,\n previous_content TEXT,\n support_files_json TEXT,\n FOREIGN KEY (proposal_id) REFERENCES skill_workshop_proposals(proposal_id) ON DELETE CASCADE\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS skill_workshop_proposal_events (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n event_id TEXT NOT NULL UNIQUE,\n proposal_id TEXT NOT NULL,\n proposed_version TEXT NOT NULL,\n revision_hash TEXT NOT NULL,\n event_type TEXT NOT NULL CHECK (event_type IN (\n 'created',\n 'revised',\n 'evaluation_completed',\n 'applied',\n 'rejected',\n 'quarantined',\n 'stale'\n )),\n occurred_at TEXT NOT NULL,\n actor_json TEXT NOT NULL,\n correlation_id TEXT,\n payload_json TEXT,\n FOREIGN KEY (proposal_id) REFERENCES skill_workshop_proposals(proposal_id) ON DELETE CASCADE\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS audit_events (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n event_id TEXT NOT NULL UNIQUE,\n source_id TEXT NOT NULL UNIQUE,\n schema_version INTEGER NOT NULL DEFAULT 1,\n source_sequence INTEGER NOT NULL,\n occurred_at INTEGER NOT NULL,\n kind TEXT NOT NULL,\n action TEXT NOT NULL,\n status TEXT NOT NULL,\n error_code TEXT,\n actor_type TEXT NOT NULL,\n actor_id TEXT NOT NULL,\n agent_id TEXT,\n session_key TEXT,\n session_id TEXT,\n run_id TEXT,\n tool_call_id TEXT,\n tool_name TEXT,\n direction TEXT,\n channel TEXT,\n conversation_kind TEXT,\n message_outcome TEXT,\n reason_code TEXT,\n delivery_kind TEXT,\n failure_stage TEXT,\n duration_ms INTEGER,\n result_count INTEGER,\n account_ref TEXT,\n conversation_ref TEXT,\n message_ref TEXT,\n target_ref TEXT\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_audit_events_time\n ON audit_events(occurred_at DESC, sequence DESC);\n\nCREATE INDEX IF NOT EXISTS idx_audit_events_agent_sequence\n ON audit_events(agent_id, sequence DESC);\n\nCREATE INDEX IF NOT EXISTS idx_audit_events_session_sequence\n ON audit_events(session_key, sequence DESC);\n\nCREATE INDEX IF NOT EXISTS idx_audit_events_run_sequence\n ON audit_events(run_id, sequence DESC);\n\nCREATE INDEX IF NOT EXISTS idx_audit_events_kind_sequence\n ON audit_events(kind, sequence DESC);\n\nCREATE INDEX IF NOT EXISTS idx_audit_events_status_sequence\n ON audit_events(status, sequence DESC);\n\nCREATE INDEX IF NOT EXISTS idx_audit_events_channel_sequence\n ON audit_events(channel, sequence DESC);\n\nCREATE INDEX IF NOT EXISTS idx_audit_events_direction_sequence\n ON audit_events(direction, sequence DESC);\n\nCREATE TABLE IF NOT EXISTS outbound_message_execution_bindings (\n event_id TEXT NOT NULL PRIMARY KEY,\n context_id TEXT NOT NULL CHECK (length(context_id) BETWEEN 1 AND 256),\n execution_id TEXT NOT NULL CHECK (length(execution_id) BETWEEN 1 AND 256),\n run_id TEXT NOT NULL CHECK (length(run_id) BETWEEN 1 AND 256),\n FOREIGN KEY (event_id) REFERENCES audit_events(event_id) ON DELETE CASCADE\n) STRICT;\nCREATE INDEX IF NOT EXISTS outbound_message_execution_bindings_execution_event_idx\n ON outbound_message_execution_bindings (context_id, execution_id, run_id, event_id);\n\nCREATE TABLE IF NOT EXISTS outbound_message_progress (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n progress_id TEXT NOT NULL UNIQUE CHECK (length(progress_id) BETWEEN 1 AND 256),\n source_id TEXT NOT NULL UNIQUE CHECK (length(source_id) BETWEEN 1 AND 512),\n source_sequence INTEGER NOT NULL CHECK (source_sequence >= 1),\n schema_version INTEGER NOT NULL CHECK (schema_version = 1),\n occurred_at INTEGER NOT NULL CHECK (occurred_at >= 0),\n action TEXT NOT NULL CHECK (\n action IN ('message.outbound.queued', 'message.outbound.platform-started')\n ),\n outcome TEXT NOT NULL CHECK (outcome IN ('queued', 'platform_started')),\n actor_type TEXT NOT NULL CHECK (actor_type IN ('agent', 'system')),\n actor_id TEXT NOT NULL CHECK (length(actor_id) BETWEEN 1 AND 256),\n agent_id TEXT CHECK (agent_id IS NULL OR length(agent_id) BETWEEN 1 AND 256),\n run_id TEXT CHECK (run_id IS NULL OR length(run_id) BETWEEN 1 AND 256),\n context_id TEXT,\n execution_id TEXT,\n channel TEXT NOT NULL CHECK (length(channel) BETWEEN 1 AND 256),\n conversation_kind TEXT NOT NULL CHECK (\n conversation_kind IN ('direct', 'group', 'channel', 'unknown')\n ),\n duration_ms INTEGER CHECK (duration_ms IS NULL OR duration_ms >= 0),\n account_ref TEXT,\n conversation_ref TEXT,\n target_ref TEXT,\n UNIQUE (occurred_at, progress_id)\n) STRICT;\nCREATE INDEX IF NOT EXISTS outbound_message_progress_occurred_idx\n ON outbound_message_progress (occurred_at, sequence);\nCREATE INDEX IF NOT EXISTS outbound_message_progress_run_occurred_idx\n ON outbound_message_progress (run_id, occurred_at, sequence);\n\nCREATE TABLE IF NOT EXISTS audit_identity_keys (\n id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1),\n key_id TEXT NOT NULL,\n key BLOB NOT NULL,\n created_at INTEGER NOT NULL\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS config_revision_keys (\n id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1),\n hmac_key BLOB NOT NULL CHECK (length(hmac_key) = 32)\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS execution_identity_contexts (\n context_id TEXT NOT NULL PRIMARY KEY CHECK (length(context_id) BETWEEN 1 AND 256),\n execution_id TEXT NOT NULL UNIQUE CHECK (length(execution_id) BETWEEN 1 AND 256),\n run_id TEXT NOT NULL CHECK (length(run_id) BETWEEN 1 AND 256),\n created_at INTEGER NOT NULL CHECK (created_at >= 0),\n coverage_state TEXT NOT NULL CHECK (\n coverage_state IN ('attribution-only', 'unattributed', 'unknown', 'unsupported')\n ),\n context_bytes INTEGER NOT NULL CHECK (context_bytes BETWEEN 1 AND 16384),\n context_json TEXT NOT NULL CHECK (length(context_json) > 0),\n UNIQUE (created_at, context_id)\n) STRICT;\nCREATE INDEX IF NOT EXISTS execution_identity_contexts_run_created_idx\n ON execution_identity_contexts (run_id, created_at, execution_id);\n\nCREATE TABLE IF NOT EXISTS execution_decision_facts (\n receipt_id TEXT NOT NULL PRIMARY KEY CHECK (length(receipt_id) BETWEEN 1 AND 256),\n context_id TEXT NOT NULL CHECK (length(context_id) BETWEEN 1 AND 256),\n execution_id TEXT NOT NULL CHECK (length(execution_id) BETWEEN 1 AND 256),\n run_id TEXT NOT NULL CHECK (length(run_id) BETWEEN 1 AND 256),\n action_id TEXT CHECK (action_id IS NULL OR length(action_id) BETWEEN 1 AND 256),\n action_family TEXT NOT NULL CHECK (length(action_family) BETWEEN 1 AND 256),\n decision_outcome TEXT NOT NULL CHECK (\n decision_outcome IN ('allowed', 'denied', 'not-applicable', 'unknown')\n ),\n coverage_state TEXT NOT NULL CHECK (\n coverage_state IN ('enforced', 'attribution-only', 'unattributed', 'unknown', 'unsupported')\n ),\n reason_code TEXT NOT NULL CHECK (length(reason_code) BETWEEN 1 AND 256),\n owner TEXT NOT NULL CHECK (length(owner) BETWEEN 1 AND 256),\n source_ref TEXT NOT NULL CHECK (length(source_ref) BETWEEN 1 AND 256),\n occurred_at INTEGER NOT NULL CHECK (occurred_at >= 0),\n receipt_bytes INTEGER NOT NULL CHECK (receipt_bytes BETWEEN 1 AND 16384),\n receipt_json TEXT NOT NULL CHECK (length(receipt_json) > 0),\n UNIQUE (occurred_at, receipt_id)\n) STRICT;\nCREATE INDEX IF NOT EXISTS execution_decision_facts_context_occurred_idx\n ON execution_decision_facts (context_id, occurred_at, receipt_id);\nCREATE INDEX IF NOT EXISTS execution_decision_facts_run_occurred_idx\n ON execution_decision_facts (run_id, occurred_at, receipt_id);\n\n-- Exact admission identity stays separate from owner-native lifecycle rows so\n-- older readers retain byte-compatible cron/task/flow table definitions.\nCREATE TABLE IF NOT EXISTS execution_owner_lifecycle_bindings (\n owner_kind TEXT NOT NULL,\n owner_id TEXT NOT NULL,\n context_id TEXT NOT NULL,\n execution_id TEXT NOT NULL,\n PRIMARY KEY (owner_kind, owner_id)\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS session_state_events (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n dedupe_key TEXT UNIQUE,\n session_key TEXT NOT NULL,\n session_id TEXT,\n agent_id TEXT NOT NULL,\n kind TEXT NOT NULL,\n actor_type TEXT NOT NULL,\n actor_id TEXT,\n run_id TEXT,\n occurred_at INTEGER NOT NULL,\n summary TEXT NOT NULL,\n payload_json TEXT\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_session_state_events_session_sequence\n ON session_state_events(session_key, sequence DESC);\n\nCREATE INDEX IF NOT EXISTS idx_session_state_events_time\n ON session_state_events(occurred_at DESC, sequence DESC);\n\nCREATE TABLE IF NOT EXISTS session_state_heads (\n session_key TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n last_sequence INTEGER NOT NULL,\n pruned_max_sequence INTEGER NOT NULL DEFAULT 0,\n updated_at INTEGER NOT NULL,\n PRIMARY KEY (session_key, agent_id)\n) STRICT;\n\n-- Notifiable watcher identity is the bare session key, matching the process-local\n-- system-event queue it feeds. Provenance distinguishes explicit immediate-wake\n-- watches from ambient queue-only group watches. Other bare keys\n-- (session.scope=\"global\") are ambiguous across agents and excluded until watcher\n-- identity is agent-scoped end-to-end.\nCREATE TABLE IF NOT EXISTS session_watch_cursors (\n watcher_session_key TEXT NOT NULL,\n watcher_store_path TEXT,\n target_session_key TEXT NOT NULL,\n last_seen_sequence INTEGER NOT NULL DEFAULT 0,\n notified_sequence INTEGER NOT NULL DEFAULT 0,\n material_sequence INTEGER NOT NULL DEFAULT 0,\n provenance TEXT NOT NULL DEFAULT 'explicit' CHECK (provenance IN ('explicit', 'ambient-group')),\n updated_at INTEGER NOT NULL,\n PRIMARY KEY (watcher_session_key, target_session_key)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_session_watch_cursors_target\n ON session_watch_cursors(target_session_key);\n\nCREATE TABLE IF NOT EXISTS session_upstream_links (\n session_key TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n catalog_id TEXT NOT NULL,\n host_id TEXT NOT NULL,\n thread_id TEXT NOT NULL,\n upstream_kind TEXT NOT NULL,\n upstream_ref_json TEXT,\n last_marker_json TEXT,\n last_scanned_at INTEGER,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n -- (session_key, agent_id) composite identity: under session.scope=\"global\" agents\n -- share bare keys; a key-only row would let one agent overwrite another's upstream.\n PRIMARY KEY (session_key, agent_id)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_session_upstream_links_catalog_id\n ON session_upstream_links(catalog_id);\n\nCREATE TABLE IF NOT EXISTS state_leases (\n scope TEXT NOT NULL,\n lease_key TEXT NOT NULL,\n owner TEXT NOT NULL,\n expires_at INTEGER,\n heartbeat_at INTEGER,\n payload_json TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n PRIMARY KEY (scope, lease_key)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_state_leases_expiry\n ON state_leases(expires_at, scope, lease_key)\n WHERE expires_at IS NOT NULL;\n\nCREATE INDEX IF NOT EXISTS idx_state_leases_owner\n ON state_leases(owner, updated_at DESC);\n\nCREATE TABLE IF NOT EXISTS exec_approvals_config (\n config_key TEXT NOT NULL PRIMARY KEY,\n raw_json TEXT NOT NULL,\n socket_path TEXT,\n has_socket_token INTEGER NOT NULL,\n default_security TEXT,\n default_ask TEXT,\n default_ask_fallback TEXT,\n auto_allow_skills INTEGER,\n agent_count INTEGER NOT NULL,\n allowlist_count INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS operator_approvals (\n approval_id TEXT NOT NULL PRIMARY KEY CHECK (\n length(approval_id) > 0 AND approval_id NOT IN ('.', '..')\n ),\n resolution_ref TEXT NOT NULL CHECK (\n length(resolution_ref) = 43 AND resolution_ref NOT GLOB '*[^A-Za-z0-9_-]*'\n ),\n kind TEXT NOT NULL CHECK (kind IN ('exec', 'plugin', 'system-agent')),\n status TEXT NOT NULL CHECK (status IN ('pending', 'allowed', 'denied', 'expired', 'cancelled')),\n presentation_json TEXT NOT NULL,\n requested_by_device_id TEXT,\n requested_by_client_id TEXT,\n requested_by_device_token_auth INTEGER NOT NULL DEFAULT 0,\n reviewer_device_ids_json TEXT NOT NULL,\n source_agent_id TEXT,\n source_session_key TEXT,\n source_session_id TEXT,\n source_run_id TEXT,\n source_tool_call_id TEXT,\n source_tool_name TEXT,\n audience_session_keys_json TEXT NOT NULL,\n runtime_epoch TEXT NOT NULL,\n created_at_ms INTEGER NOT NULL,\n expires_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n decision TEXT CHECK (decision IN ('allow-once', 'allow-always', 'deny')),\n terminal_reason TEXT CHECK (\n terminal_reason IN (\n 'user',\n 'timeout',\n 'malformed-verdict',\n 'no-route',\n 'run-aborted',\n 'gateway-restart',\n 'storage-corrupt'\n )\n ),\n resolved_at_ms INTEGER,\n resolver_kind TEXT CHECK (resolver_kind IN ('device', 'channel', 'runtime', 'system')),\n resolver_id TEXT,\n consumed_at_ms INTEGER,\n consumed_by TEXT,\n CHECK (expires_at_ms >= created_at_ms),\n CHECK (updated_at_ms >= created_at_ms),\n CHECK (resolved_at_ms IS NULL OR resolved_at_ms >= created_at_ms),\n CHECK (resolved_at_ms IS NULL OR resolved_at_ms <= updated_at_ms),\n CHECK (consumed_at_ms IS NULL OR consumed_at_ms >= resolved_at_ms),\n CHECK (consumed_at_ms IS NULL OR consumed_at_ms <= updated_at_ms),\n CHECK (requested_by_device_token_auth IN (0, 1)),\n CHECK (\n (\n status = 'pending'\n AND decision IS NULL\n AND terminal_reason IS NULL\n AND resolved_at_ms IS NULL\n AND resolver_kind IS NULL\n AND resolver_id IS NULL\n AND consumed_at_ms IS NULL\n AND consumed_by IS NULL\n )\n OR (\n status = 'allowed'\n AND decision IN ('allow-once', 'allow-always')\n AND terminal_reason = 'user'\n AND resolved_at_ms IS NOT NULL\n AND resolver_kind IS NOT NULL\n )\n OR (\n status = 'denied'\n AND decision = 'deny'\n AND terminal_reason IN ('user', 'malformed-verdict', 'no-route', 'storage-corrupt')\n AND resolved_at_ms IS NOT NULL\n AND resolver_kind IS NOT NULL\n AND consumed_at_ms IS NULL\n AND consumed_by IS NULL\n )\n OR (\n status = 'expired'\n AND decision = 'deny'\n AND terminal_reason = 'timeout'\n AND resolved_at_ms IS NOT NULL\n AND resolver_kind IS NOT NULL\n AND consumed_at_ms IS NULL\n AND consumed_by IS NULL\n )\n OR (\n status = 'cancelled'\n AND decision = 'deny'\n AND terminal_reason IN ('run-aborted', 'gateway-restart')\n AND resolved_at_ms IS NOT NULL\n AND resolver_kind IS NOT NULL\n AND consumed_at_ms IS NULL\n AND consumed_by IS NULL\n )\n ),\n CHECK (\n (consumed_at_ms IS NULL AND consumed_by IS NULL)\n OR (\n status = 'allowed'\n AND decision = 'allow-once'\n AND consumed_at_ms IS NOT NULL\n AND consumed_by IS NOT NULL\n )\n )\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_operator_approvals_status_expiry\n ON operator_approvals(status, expires_at_ms, approval_id);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_operator_approvals_resolution_ref\n ON operator_approvals(resolution_ref);\n\nCREATE INDEX IF NOT EXISTS idx_operator_approvals_source_session_created\n ON operator_approvals(source_session_key, created_at_ms DESC, approval_id);\n\nCREATE INDEX IF NOT EXISTS idx_operator_approvals_source_run_resolved\n ON operator_approvals(source_run_id, resolved_at_ms, approval_id)\n WHERE source_run_id IS NOT NULL AND resolved_at_ms IS NOT NULL;\n\nCREATE INDEX IF NOT EXISTS idx_operator_approvals_resolved\n ON operator_approvals(resolved_at_ms, approval_id)\n WHERE resolved_at_ms IS NOT NULL;\n\nCREATE INDEX IF NOT EXISTS idx_operator_approvals_runtime_pending\n ON operator_approvals(runtime_epoch, approval_id)\n WHERE status = 'pending';\n\nCREATE TABLE IF NOT EXISTS operator_approval_execution_identities (\n approval_id TEXT NOT NULL PRIMARY KEY\n REFERENCES operator_approvals(approval_id) ON DELETE CASCADE,\n source_context_id TEXT NOT NULL CHECK (\n length(source_context_id) BETWEEN 1 AND 256 AND source_context_id = trim(source_context_id)\n ),\n source_execution_id TEXT NOT NULL CHECK (\n length(source_execution_id) BETWEEN 1 AND 256 AND source_execution_id = trim(source_execution_id)\n )\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS operator_approval_standing_grants (\n grant_id TEXT NOT NULL PRIMARY KEY CHECK (length(grant_id) > 0),\n minted_by_approval_id TEXT NOT NULL\n REFERENCES operator_approvals(approval_id) ON DELETE CASCADE,\n agent_id TEXT NOT NULL CHECK (length(agent_id) > 0),\n cron_job_id TEXT NOT NULL CHECK (length(cron_job_id) > 0),\n job_config_revision TEXT NOT NULL CHECK (length(job_config_revision) > 0),\n operation_binding TEXT NOT NULL CHECK (length(operation_binding) > 0),\n created_at_ms INTEGER NOT NULL,\n expires_at_ms INTEGER CHECK (expires_at_ms IS NULL OR expires_at_ms >= created_at_ms),\n revoked_at_ms INTEGER,\n revoked_by TEXT,\n last_used_at_ms INTEGER,\n use_count INTEGER NOT NULL DEFAULT 0\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_operator_approval_standing_grants_binding\n ON operator_approval_standing_grants(agent_id, cron_job_id, operation_binding, created_at_ms DESC);\n\nCREATE TABLE IF NOT EXISTS operator_approval_standing_grant_generations (\n grant_id TEXT NOT NULL PRIMARY KEY\n REFERENCES operator_approval_standing_grants(grant_id) ON DELETE CASCADE,\n job_definition_generation INTEGER NOT NULL CHECK (job_definition_generation >= 1)\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS schema_meta (\n meta_key TEXT NOT NULL PRIMARY KEY,\n role TEXT NOT NULL,\n schema_version INTEGER NOT NULL,\n agent_id TEXT,\n app_version TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS config_machine_state (\n state_key TEXT NOT NULL PRIMARY KEY,\n value_json TEXT NOT NULL,\n updated_at_ms INTEGER NOT NULL\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS device_pairing_pending (\n request_id TEXT NOT NULL PRIMARY KEY,\n device_id TEXT NOT NULL,\n public_key TEXT NOT NULL,\n display_name TEXT,\n platform TEXT,\n device_family TEXT,\n client_id TEXT,\n client_mode TEXT,\n browser_origin TEXT,\n role TEXT,\n roles_json TEXT,\n scopes_json TEXT,\n remote_ip TEXT,\n silent INTEGER,\n is_repair INTEGER,\n ts INTEGER NOT NULL,\n refreshed_at_ms INTEGER\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_device_pairing_pending_device\n ON device_pairing_pending(device_id, ts DESC);\n\nCREATE TABLE IF NOT EXISTS device_pairing_paired (\n device_id TEXT NOT NULL PRIMARY KEY,\n public_key TEXT NOT NULL,\n display_name TEXT,\n operator_label TEXT,\n platform TEXT,\n device_family TEXT,\n client_id TEXT,\n client_mode TEXT,\n browser_origin TEXT,\n role TEXT,\n roles_json TEXT,\n scopes_json TEXT,\n approved_scopes_json TEXT,\n remote_ip TEXT,\n tokens_json TEXT,\n approved_via TEXT,\n node_surface_json TEXT,\n pending_node_surface_json TEXT,\n created_at_ms INTEGER NOT NULL,\n approved_at_ms INTEGER NOT NULL,\n last_seen_at_ms INTEGER,\n last_seen_reason TEXT\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_device_pairing_paired_approved\n ON device_pairing_paired(approved_at_ms DESC, device_id);\n\nCREATE TABLE IF NOT EXISTS device_bootstrap_tokens (\n token_key TEXT NOT NULL PRIMARY KEY,\n token TEXT NOT NULL,\n setup_id TEXT,\n ts INTEGER NOT NULL,\n device_id TEXT,\n public_key TEXT,\n profile_json TEXT,\n redeemed_profile_json TEXT,\n pending_profile_json TEXT,\n issued_at_ms INTEGER NOT NULL,\n last_used_at_ms INTEGER\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_device_bootstrap_tokens_ts\n ON device_bootstrap_tokens(ts);\n\n-- Terminal outcome of a redeemed setup credential. The bootstrap row is deleted\n-- on redemption, so this is the only durable proof a setup code succeeded; the\n-- presenting client reconciles it when the completion broadcast is missed.\n-- Non-secret only: never the bootstrap token or anything derived from it.\n-- Bounded by retention to a handful of live rows, so the primary key is the\n-- only access path worth having.\nCREATE TABLE IF NOT EXISTS device_pair_setup_completions (\n setup_id TEXT NOT NULL PRIMARY KEY,\n device_id TEXT NOT NULL,\n device_name TEXT,\n access TEXT NOT NULL,\n completed_at_ms INTEGER NOT NULL,\n delivery_state TEXT NOT NULL CHECK (delivery_state IN ('uncertain', 'confirmed')),\n retain_until_ms INTEGER NOT NULL\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS device_pairing_join_codes (\n shortcode TEXT,\n payload_json TEXT,\n created_at_ms INTEGER,\n expires_at_ms INTEGER\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS device_identities (\n identity_key TEXT NOT NULL PRIMARY KEY,\n device_id TEXT NOT NULL,\n public_key_pem TEXT NOT NULL,\n private_key_pem TEXT NOT NULL,\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_device_identities_device\n ON device_identities(device_id, updated_at_ms DESC);\n\nCREATE TABLE IF NOT EXISTS device_auth_tokens (\n device_id TEXT NOT NULL,\n role TEXT NOT NULL,\n token TEXT NOT NULL,\n scopes_json TEXT NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n PRIMARY KEY (device_id, role)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_device_auth_tokens_updated\n ON device_auth_tokens(updated_at_ms DESC, device_id, role);\n\nCREATE TABLE IF NOT EXISTS gateway_origin_device_tokens (\n gateway_scope TEXT NOT NULL,\n device_id TEXT NOT NULL,\n role TEXT NOT NULL,\n token TEXT NOT NULL,\n scopes_json TEXT NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n PRIMARY KEY (gateway_scope, device_id, role)\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS macos_port_guardian_records (\n pid INTEGER NOT NULL PRIMARY KEY,\n port INTEGER NOT NULL,\n command TEXT NOT NULL,\n mode TEXT NOT NULL,\n timestamp REAL NOT NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_macos_port_guardian_records_port\n ON macos_port_guardian_records(port, timestamp DESC);\n\nCREATE TABLE IF NOT EXISTS workspace_setup_state (\n workspace_key TEXT NOT NULL PRIMARY KEY,\n -- NULL only for attestation-only rows whose legacy source never recorded a\n -- path (orphan hashed-key attestations); setup rows always carry one.\n workspace_path TEXT,\n -- NULL setup columns mean an attestation-only row: replaceWorkspaceAttestation\n -- may record hashes before any setup milestone exists for the workspace.\n version INTEGER,\n bootstrap_seeded_at TEXT,\n setup_completed_at TEXT,\n updated_at INTEGER,\n attested_at_ms INTEGER,\n attestation_updated_at_ms INTEGER,\n CHECK (version IS NULL OR workspace_path IS NOT NULL)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_workspace_setup_state_path\n ON workspace_setup_state(workspace_path);\n\nCREATE TABLE IF NOT EXISTS workspace_path_aliases (\n alias_key TEXT NOT NULL PRIMARY KEY,\n alias_path TEXT NOT NULL,\n workspace_key TEXT NOT NULL,\n workspace_path TEXT NOT NULL,\n updated_at_ms INTEGER NOT NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_workspace_path_aliases_workspace\n ON workspace_path_aliases(workspace_key);\n\n\n\nCREATE TABLE IF NOT EXISTS workspace_generated_bootstrap_hashes (\n workspace_key TEXT NOT NULL,\n filename TEXT NOT NULL,\n sha256 TEXT NOT NULL,\n PRIMARY KEY (workspace_key, filename),\n FOREIGN KEY (workspace_key) REFERENCES workspace_setup_state(workspace_key) ON DELETE CASCADE\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS native_hook_relay_bridges (\n relay_id TEXT NOT NULL PRIMARY KEY,\n pid INTEGER NOT NULL,\n hostname TEXT NOT NULL,\n port INTEGER NOT NULL,\n token TEXT NOT NULL,\n expires_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_native_hook_relay_bridges_expires\n ON native_hook_relay_bridges(expires_at_ms, relay_id);\n\nCREATE TABLE IF NOT EXISTS managed_outgoing_image_records (\n attachment_id TEXT NOT NULL PRIMARY KEY,\n session_key TEXT NOT NULL,\n agent_id TEXT,\n message_id TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT,\n retention_class TEXT,\n alt TEXT NOT NULL,\n original_media_root TEXT NOT NULL,\n original_media_id TEXT NOT NULL,\n original_media_subdir TEXT NOT NULL,\n original_content_type TEXT NOT NULL,\n original_width INTEGER,\n original_height INTEGER,\n original_size_bytes INTEGER,\n original_filename TEXT,\n record_json TEXT NOT NULL,\n cleanup_pending INTEGER NOT NULL DEFAULT 0 CHECK (cleanup_pending IN (0, 1))\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_managed_outgoing_images_session\n ON managed_outgoing_image_records(session_key, created_at DESC, attachment_id);\n\nCREATE INDEX IF NOT EXISTS idx_managed_outgoing_images_message\n ON managed_outgoing_image_records(session_key, message_id, attachment_id)\n WHERE message_id IS NOT NULL;\n\nCREATE INDEX IF NOT EXISTS idx_managed_outgoing_images_agent_session\n ON managed_outgoing_image_records(session_key, agent_id, created_at DESC, attachment_id);\n\nCREATE INDEX IF NOT EXISTS idx_managed_outgoing_images_agent_message\n ON managed_outgoing_image_records(session_key, agent_id, message_id, attachment_id)\n WHERE message_id IS NOT NULL;\n\nCREATE TABLE IF NOT EXISTS channel_pairing_requests (\n channel_key TEXT NOT NULL,\n account_id TEXT NOT NULL,\n request_id TEXT NOT NULL,\n code TEXT NOT NULL,\n created_at TEXT NOT NULL,\n last_seen_at TEXT NOT NULL,\n meta_json TEXT,\n PRIMARY KEY (channel_key, account_id, request_id)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_channel_pairing_requests_code\n ON channel_pairing_requests(channel_key, code);\n\nCREATE INDEX IF NOT EXISTS idx_channel_pairing_requests_created\n ON channel_pairing_requests(channel_key, created_at, request_id);\n\nCREATE TABLE IF NOT EXISTS channel_pairing_allow_entries (\n channel_key TEXT NOT NULL,\n account_id TEXT NOT NULL,\n entry TEXT NOT NULL,\n sort_order INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n PRIMARY KEY (channel_key, account_id, entry)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_channel_pairing_allow_account\n ON channel_pairing_allow_entries(channel_key, account_id, sort_order, entry);\n\nCREATE TABLE IF NOT EXISTS web_push_subscriptions (\n endpoint_hash TEXT NOT NULL PRIMARY KEY,\n subscription_id TEXT NOT NULL UNIQUE,\n endpoint TEXT NOT NULL,\n p256dh TEXT NOT NULL,\n auth TEXT NOT NULL,\n device_id TEXT,\n user_profile_id TEXT,\n preferences_json TEXT,\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_web_push_subscriptions_updated\n ON web_push_subscriptions(updated_at_ms DESC, subscription_id);\n\nCREATE TABLE IF NOT EXISTS web_push_approval_deliveries (\n approval_id TEXT NOT NULL\n REFERENCES operator_approvals(approval_id) ON DELETE CASCADE,\n subscription_id TEXT NOT NULL\n REFERENCES web_push_subscriptions(subscription_id) ON DELETE CASCADE,\n device_id TEXT NOT NULL,\n user_profile_id TEXT,\n prepared_at_ms INTEGER NOT NULL,\n PRIMARY KEY (approval_id, subscription_id)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_web_push_approval_deliveries_subscription\n ON web_push_approval_deliveries(subscription_id, approval_id);\n\nCREATE TABLE IF NOT EXISTS apns_registrations (\n node_id TEXT NOT NULL PRIMARY KEY,\n transport TEXT NOT NULL,\n token TEXT,\n relay_handle TEXT,\n send_grant TEXT,\n installation_id TEXT,\n relay_origin TEXT,\n topic TEXT NOT NULL,\n environment TEXT NOT NULL,\n distribution TEXT,\n token_debug_suffix TEXT,\n updated_at_ms INTEGER NOT NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_apns_registrations_updated\n ON apns_registrations(updated_at_ms DESC, node_id);\n\nCREATE TABLE IF NOT EXISTS apns_registration_tombstones (\n node_id TEXT NOT NULL PRIMARY KEY,\n deleted_at_ms INTEGER NOT NULL\n) STRICT;\n\n-- Node-host-owned launch journal. The descriptor and its credential remain\n-- process memory only; this table records bounded supervision facts.\nCREATE TABLE IF NOT EXISTS node_worker_launches (\n launch_id TEXT NOT NULL PRIMARY KEY\n CHECK (length(launch_id) BETWEEN 1 AND 256 AND instr(launch_id, char(0)) = 0),\n plan_hash TEXT NOT NULL\n CHECK (length(plan_hash) = 64 AND plan_hash NOT GLOB '*[^0-9a-f]*'),\n gateway_namespace TEXT NOT NULL\n CHECK (\n length(gateway_namespace) BETWEEN 1 AND 128\n AND gateway_namespace NOT GLOB '*[^A-Za-z0-9._-]*'\n AND gateway_namespace GLOB '[A-Za-z0-9]*'\n ),\n environment_id TEXT NOT NULL\n CHECK (length(environment_id) BETWEEN 1 AND 256 AND instr(environment_id, char(0)) = 0),\n session_id TEXT NOT NULL\n CHECK (length(session_id) BETWEEN 1 AND 256 AND instr(session_id, char(0)) = 0),\n owner_epoch INTEGER NOT NULL CHECK (owner_epoch BETWEEN 1 AND 9007199254740991),\n placement_generation INTEGER NOT NULL\n CHECK (placement_generation BETWEEN 0 AND 9007199254740991),\n run_id TEXT NOT NULL\n CHECK (length(run_id) BETWEEN 1 AND 256 AND instr(run_id, char(0)) = 0),\n state TEXT NOT NULL\n CHECK (state IN ('pending', 'running', 'completed', 'failed', 'interrupted', 'cancelled')),\n supervisor_pid INTEGER NOT NULL CHECK (supervisor_pid BETWEEN 1 AND 2147483647),\n supervisor_start_time INTEGER NOT NULL\n CHECK (supervisor_start_time BETWEEN 0 AND 9007199254740991),\n worker_pid INTEGER CHECK (worker_pid IS NULL OR worker_pid BETWEEN 1 AND 2147483647),\n worker_start_time INTEGER CHECK (\n worker_start_time IS NULL OR worker_start_time BETWEEN 0 AND 9007199254740991\n ),\n result_json TEXT CHECK (\n result_json IS NULL\n OR (\n length(CAST(result_json AS BLOB)) BETWEEN 1 AND 65536\n AND instr(result_json, char(0)) = 0\n AND json_valid(result_json)\n )\n ),\n error_text TEXT CHECK (\n error_text IS NULL\n OR (\n length(CAST(error_text AS BLOB)) BETWEEN 1 AND 4096\n AND instr(error_text, char(0)) = 0\n AND instr(error_text, char(10)) = 0\n AND instr(error_text, char(13)) = 0\n )\n ),\n completed_at_ms INTEGER CHECK (\n completed_at_ms IS NULL OR completed_at_ms BETWEEN 0 AND 9007199254740991\n ),\n created_at_ms INTEGER NOT NULL CHECK (created_at_ms BETWEEN 0 AND 9007199254740991),\n updated_at_ms INTEGER NOT NULL CHECK (\n updated_at_ms BETWEEN created_at_ms AND 9007199254740991\n ),\n CHECK ((worker_pid IS NULL) = (worker_start_time IS NULL)),\n CHECK (\n (state = 'pending'\n AND worker_pid IS NULL AND result_json IS NULL AND error_text IS NULL\n AND completed_at_ms IS NULL)\n OR\n (state = 'running'\n AND worker_pid IS NOT NULL AND result_json IS NULL AND error_text IS NULL\n AND completed_at_ms IS NULL)\n OR\n (state = 'completed'\n AND result_json IS NOT NULL AND error_text IS NULL\n AND completed_at_ms BETWEEN created_at_ms AND updated_at_ms)\n OR\n (state IN ('failed', 'interrupted', 'cancelled')\n AND result_json IS NULL AND error_text IS NOT NULL\n AND completed_at_ms BETWEEN created_at_ms AND updated_at_ms)\n )\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_node_worker_launches_terminal_completed\n ON node_worker_launches(completed_at_ms, launch_id)\n WHERE completed_at_ms IS NOT NULL;\n\nCREATE TABLE IF NOT EXISTS node_worker_launch_containers (\n launch_id TEXT PRIMARY KEY,\n container_json TEXT\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS node_worker_launch_cleanup (\n launch_id TEXT NOT NULL PRIMARY KEY\n REFERENCES node_worker_launches(launch_id) ON DELETE CASCADE,\n cleanup_mode TEXT NOT NULL CHECK (cleanup_mode IN ('process-group', 'owned-anchor')),\n lineage_settled INTEGER CHECK (lineage_settled IS NULL OR lineage_settled = 1)\n) STRICT;\n\n-- Turn receipts have a shorter lifetime than their physical worker owner.\n-- Keeping the launch running preserves capacity and predecessor cleanup semantics.\nCREATE TABLE IF NOT EXISTS node_worker_turns (\n turn_id TEXT NOT NULL PRIMARY KEY\n CHECK (length(turn_id) BETWEEN 1 AND 256 AND instr(turn_id, char(0)) = 0),\n owner_launch_id TEXT NOT NULL\n REFERENCES node_worker_launches(launch_id) ON DELETE CASCADE,\n plan_hash TEXT NOT NULL\n CHECK (length(plan_hash) = 64 AND plan_hash NOT GLOB '*[^0-9a-f]*'),\n run_id TEXT NOT NULL\n CHECK (length(run_id) BETWEEN 1 AND 256 AND instr(run_id, char(0)) = 0),\n state TEXT NOT NULL\n CHECK (state IN ('running', 'completed', 'failed', 'interrupted', 'cancelled')),\n result_json TEXT CHECK (\n result_json IS NULL\n OR (\n length(CAST(result_json AS BLOB)) BETWEEN 1 AND 65536\n AND instr(result_json, char(0)) = 0\n AND json_valid(result_json)\n )\n ),\n error_text TEXT CHECK (\n error_text IS NULL\n OR (\n length(CAST(error_text AS BLOB)) BETWEEN 1 AND 4096\n AND instr(error_text, char(0)) = 0\n AND instr(error_text, char(10)) = 0\n AND instr(error_text, char(13)) = 0\n )\n ),\n completed_at_ms INTEGER CHECK (\n completed_at_ms IS NULL OR completed_at_ms BETWEEN 0 AND 9007199254740991\n ),\n created_at_ms INTEGER NOT NULL CHECK (created_at_ms BETWEEN 0 AND 9007199254740991),\n updated_at_ms INTEGER NOT NULL CHECK (\n updated_at_ms BETWEEN created_at_ms AND 9007199254740991\n ),\n CHECK (\n (state = 'running'\n AND result_json IS NULL AND error_text IS NULL AND completed_at_ms IS NULL)\n OR\n (state = 'completed'\n AND result_json IS NOT NULL AND error_text IS NULL\n AND completed_at_ms BETWEEN created_at_ms AND updated_at_ms)\n OR\n (state IN ('failed', 'interrupted', 'cancelled')\n AND result_json IS NULL AND error_text IS NOT NULL\n AND completed_at_ms BETWEEN created_at_ms AND updated_at_ms)\n )\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_node_worker_turns_terminal_completed\n ON node_worker_turns(completed_at_ms, turn_id)\n WHERE completed_at_ms IS NOT NULL;\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_node_worker_turns_active_owner\n ON node_worker_turns(owner_launch_id)\n WHERE state = 'running';\n\nCREATE TABLE IF NOT EXISTS config_health_entries (\n config_path TEXT NOT NULL PRIMARY KEY,\n last_known_good_json TEXT,\n last_promoted_good_json TEXT,\n last_observed_suspicious_signature TEXT,\n updated_at_ms INTEGER NOT NULL\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS clawhub_promotion_claims (\n slug TEXT NOT NULL PRIMARY KEY,\n provider TEXT,\n model_keys_json TEXT NOT NULL,\n ends_at_ms INTEGER NOT NULL,\n claimed_at_ms INTEGER NOT NULL\n) STRICT;\n\n\n\nCREATE TABLE IF NOT EXISTS official_external_plugin_catalog_snapshots (\n feed_url TEXT NOT NULL PRIMARY KEY,\n body TEXT NOT NULL,\n status INTEGER NOT NULL,\n etag TEXT,\n last_modified TEXT,\n checksum TEXT NOT NULL,\n saved_at TEXT NOT NULL,\n trust_mode TEXT,\n trust_key_id TEXT,\n trust_signature_count INTEGER,\n trust_threshold INTEGER,\n trust_verified_at TEXT,\n updated_at_ms INTEGER NOT NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_official_external_plugin_catalog_snapshots_updated\n ON official_external_plugin_catalog_snapshots(updated_at_ms DESC, feed_url);\n\nCREATE TABLE IF NOT EXISTS update_runs (\n run_id TEXT PRIMARY KEY NOT NULL,\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n trigger TEXT NOT NULL CHECK (trigger IN ('chat', 'control-ui', 'cli', 'campaign', 'mac-app', 'api')),\n phase TEXT NOT NULL CHECK (phase IN ('requested', 'staging', 'validating', 'repairing', 'activating', 'restarting', 'verifying', 'finished')),\n status TEXT NOT NULL CHECK (status IN ('running', 'succeeded', 'failed', 'rolled-back', 'skipped')),\n reason TEXT,\n origin_json TEXT NOT NULL CHECK (length(CAST(origin_json AS BLOB)) <= 16384),\n target_json TEXT NOT NULL CHECK (length(CAST(target_json AS BLOB)) <= 16384),\n before_json TEXT NOT NULL CHECK (length(CAST(before_json AS BLOB)) <= 16384),\n after_json TEXT NOT NULL CHECK (length(CAST(after_json AS BLOB)) <= 16384),\n steps_json TEXT NOT NULL CHECK (length(CAST(steps_json AS BLOB)) <= 16384),\n verification_json TEXT NOT NULL CHECK (length(CAST(verification_json AS BLOB)) <= 16384),\n repair_json TEXT NOT NULL CHECK (length(CAST(repair_json AS BLOB)) <= 16384),\n confirmed_at_ms INTEGER,\n finished_at_ms INTEGER,\n downtime_ms INTEGER,\n CHECK ((status = 'running' AND phase != 'finished' AND finished_at_ms IS NULL) OR\n (status != 'running' AND phase = 'finished' AND finished_at_ms IS NOT NULL))\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_update_runs_created\n ON update_runs(created_at_ms DESC, run_id);\nCREATE INDEX IF NOT EXISTS idx_update_runs_active\n ON update_runs(status, created_at_ms DESC, run_id);\n\nCREATE TABLE IF NOT EXISTS gateway_restart_sentinel (\n sentinel_key TEXT NOT NULL PRIMARY KEY,\n version INTEGER NOT NULL,\n kind TEXT NOT NULL,\n status TEXT NOT NULL,\n ts INTEGER NOT NULL,\n session_key TEXT,\n thread_id TEXT,\n delivery_channel TEXT,\n delivery_to TEXT,\n delivery_account_id TEXT,\n message TEXT,\n continuation_json TEXT,\n doctor_hint TEXT,\n stats_json TEXT,\n payload_json TEXT NOT NULL,\n updated_at_ms INTEGER NOT NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_gateway_restart_sentinel_ts\n ON gateway_restart_sentinel(ts DESC, sentinel_key);\n\nCREATE TABLE IF NOT EXISTS gateway_restart_intent (\n intent_key TEXT NOT NULL PRIMARY KEY,\n kind TEXT NOT NULL,\n pid INTEGER NOT NULL,\n created_at INTEGER NOT NULL,\n reason TEXT,\n force INTEGER,\n wait_ms INTEGER,\n updated_at_ms INTEGER NOT NULL\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS gateway_restart_handoff (\n handoff_key TEXT NOT NULL PRIMARY KEY,\n kind TEXT NOT NULL,\n version INTEGER NOT NULL,\n intent_id TEXT NOT NULL,\n pid INTEGER NOT NULL,\n process_instance_id TEXT,\n created_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL,\n reason TEXT,\n restart_trace_started_at INTEGER,\n restart_trace_last_at INTEGER,\n source TEXT NOT NULL,\n restart_kind TEXT NOT NULL,\n supervisor_mode TEXT NOT NULL,\n updated_at_ms INTEGER NOT NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_gateway_restart_handoff_expiry\n ON gateway_restart_handoff(expires_at, pid);\n\nCREATE TABLE IF NOT EXISTS gateway_boot_lifecycle (\n boot_id TEXT NOT NULL PRIMARY KEY,\n pid INTEGER NOT NULL,\n started_at_ms INTEGER NOT NULL,\n completed_at_ms INTEGER,\n outcome TEXT,\n startup_reason TEXT,\n reason TEXT\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_gateway_boot_lifecycle_started\n ON gateway_boot_lifecycle(started_at_ms);\n\nCREATE TABLE IF NOT EXISTS acp_sessions (\n session_key TEXT NOT NULL PRIMARY KEY,\n session_id TEXT,\n backend TEXT NOT NULL,\n agent TEXT NOT NULL,\n runtime_session_name TEXT NOT NULL,\n identity_json TEXT,\n mode TEXT NOT NULL,\n runtime_options_json TEXT,\n cwd TEXT,\n state TEXT NOT NULL,\n last_activity_at INTEGER NOT NULL,\n last_error TEXT,\n updated_at INTEGER NOT NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_acp_sessions_state_activity\n ON acp_sessions(state, last_activity_at DESC, session_key);\n\nCREATE INDEX IF NOT EXISTS idx_acp_sessions_agent_activity\n ON acp_sessions(agent, last_activity_at DESC, session_key);\n\nCREATE TABLE IF NOT EXISTS acp_replay_sessions (\n session_id TEXT NOT NULL PRIMARY KEY,\n session_key TEXT NOT NULL,\n cwd TEXT NOT NULL,\n complete INTEGER NOT NULL,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n next_seq INTEGER NOT NULL,\n -- Running estimate of this session's ledger footprint (row overhead plus\n -- all event rows), maintained at insert/trim so budget checks never scan\n -- acp_replay_events (#100622).\n estimated_bytes INTEGER NOT NULL DEFAULT 0\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_acp_replay_sessions_key_updated\n ON acp_replay_sessions(session_key, complete, updated_at DESC, session_id);\n\nCREATE INDEX IF NOT EXISTS idx_acp_replay_sessions_updated\n ON acp_replay_sessions(updated_at DESC, session_id);\n\nCREATE TABLE IF NOT EXISTS acp_replay_events (\n session_id TEXT NOT NULL,\n seq INTEGER NOT NULL,\n at INTEGER NOT NULL,\n session_key TEXT NOT NULL,\n run_id TEXT,\n update_json TEXT NOT NULL,\n estimated_bytes INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (session_id, seq),\n FOREIGN KEY (session_id) REFERENCES acp_replay_sessions(session_id) ON DELETE CASCADE\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_acp_replay_events_session_seq\n ON acp_replay_events(session_id, seq);\n\nCREATE TABLE IF NOT EXISTS agent_databases (\n agent_id TEXT NOT NULL,\n path TEXT NOT NULL,\n schema_version INTEGER NOT NULL,\n last_seen_at INTEGER NOT NULL,\n size_bytes INTEGER,\n PRIMARY KEY (agent_id, path)\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS agent_deletion_journal (\n agent_id TEXT PRIMARY KEY,\n operation_id TEXT NOT NULL DEFAULT '',\n agent_dir TEXT NOT NULL,\n workspace_dir TEXT NOT NULL,\n sessions_dir TEXT NOT NULL,\n database_paths_json TEXT NOT NULL DEFAULT '[]',\n cleanup_paths_json TEXT NOT NULL DEFAULT '[]',\n created_at INTEGER NOT NULL,\n cleanup_completed INTEGER NOT NULL DEFAULT 0,\n delete_files INTEGER NOT NULL DEFAULT 1\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS agent_provenance (\n agent_id TEXT PRIMARY KEY,\n created_via TEXT NOT NULL CHECK (created_via IN ('operator', 'agent', 'claw')),\n creator_agent_id TEXT,\n created_at_ms INTEGER NOT NULL\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS agent_database_leases (\n lease_id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL,\n path TEXT NOT NULL,\n owner_pid INTEGER NOT NULL,\n owner_start_time INTEGER,\n opened_at INTEGER NOT NULL\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS plugin_state_entries (\n plugin_id TEXT NOT NULL,\n namespace TEXT NOT NULL,\n entry_key TEXT NOT NULL,\n value_json TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n expires_at INTEGER,\n PRIMARY KEY (plugin_id, namespace, entry_key)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_plugin_state_expiry\n ON plugin_state_entries(expires_at)\n WHERE expires_at IS NOT NULL;\n\nCREATE INDEX IF NOT EXISTS idx_plugin_state_listing\n ON plugin_state_entries(plugin_id, namespace, created_at, entry_key, expires_at);\n\nCREATE TABLE IF NOT EXISTS channel_ingress_events (\n queue_name TEXT NOT NULL,\n event_id TEXT NOT NULL,\n channel_id TEXT NOT NULL,\n account_id TEXT NOT NULL,\n status TEXT NOT NULL,\n lane_key TEXT,\n payload_json TEXT NOT NULL,\n metadata_json TEXT,\n received_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n claim_token TEXT,\n claim_owner TEXT,\n claimed_at INTEGER,\n attempts INTEGER NOT NULL DEFAULT 0,\n last_attempt_at INTEGER,\n last_error TEXT,\n failed_reason TEXT,\n failed_at INTEGER,\n completed_at INTEGER,\n completed_metadata_json TEXT,\n PRIMARY KEY (queue_name, event_id)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_channel_ingress_pending\n ON channel_ingress_events(queue_name, status, received_at, event_id);\n\nCREATE INDEX IF NOT EXISTS idx_channel_ingress_claims\n ON channel_ingress_events(queue_name, status, claimed_at);\n\nCREATE INDEX IF NOT EXISTS idx_channel_ingress_lane\n ON channel_ingress_events(queue_name, status, lane_key);\n\nCREATE TABLE IF NOT EXISTS plugin_blob_entries (\n plugin_id TEXT NOT NULL,\n namespace TEXT NOT NULL,\n entry_key TEXT NOT NULL,\n metadata_json TEXT NOT NULL,\n blob BLOB NOT NULL,\n created_at INTEGER NOT NULL,\n expires_at INTEGER,\n PRIMARY KEY (plugin_id, namespace, entry_key)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_plugin_blob_expiry\n ON plugin_blob_entries(expires_at)\n WHERE expires_at IS NOT NULL;\n\nCREATE INDEX IF NOT EXISTS idx_plugin_blob_listing\n ON plugin_blob_entries(plugin_id, namespace, created_at, entry_key);\n\nCREATE TABLE IF NOT EXISTS skill_uploads (\n upload_id TEXT NOT NULL PRIMARY KEY,\n kind TEXT NOT NULL,\n slug TEXT NOT NULL,\n force INTEGER NOT NULL,\n size_bytes INTEGER NOT NULL,\n sha256 TEXT,\n actual_sha256 TEXT,\n received_bytes INTEGER NOT NULL,\n archive_blob BLOB NOT NULL,\n created_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL,\n committed INTEGER NOT NULL,\n committed_at INTEGER,\n idempotency_key_hash TEXT UNIQUE\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_skill_uploads_expiry\n ON skill_uploads(expires_at);\n\nCREATE INDEX IF NOT EXISTS idx_skill_uploads_idempotency\n ON skill_uploads(idempotency_key_hash)\n WHERE idempotency_key_hash IS NOT NULL;\n\nCREATE TABLE IF NOT EXISTS skill_upload_chunks (\n upload_id TEXT NOT NULL,\n byte_offset INTEGER NOT NULL CHECK (byte_offset >= 0),\n size_bytes INTEGER NOT NULL CHECK (size_bytes > 0),\n chunk_blob BLOB NOT NULL,\n PRIMARY KEY (upload_id, byte_offset),\n FOREIGN KEY (upload_id) REFERENCES skill_uploads(upload_id) ON DELETE CASCADE,\n CHECK (length(chunk_blob) = size_bytes)\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS capture_sessions (\n id TEXT NOT NULL PRIMARY KEY,\n started_at INTEGER NOT NULL,\n ended_at INTEGER,\n mode TEXT NOT NULL,\n source_scope TEXT NOT NULL,\n source_process TEXT NOT NULL,\n proxy_url TEXT\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS capture_blobs (\n blob_id TEXT NOT NULL PRIMARY KEY,\n content_type TEXT,\n encoding TEXT NOT NULL,\n size_bytes INTEGER NOT NULL,\n sha256 TEXT NOT NULL,\n data BLOB NOT NULL,\n created_at INTEGER NOT NULL\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS capture_events (\n id INTEGER NOT NULL PRIMARY KEY,\n session_id TEXT NOT NULL,\n ts INTEGER NOT NULL,\n source_scope TEXT NOT NULL,\n source_process TEXT NOT NULL,\n protocol TEXT NOT NULL,\n direction TEXT NOT NULL,\n kind TEXT NOT NULL,\n flow_id TEXT NOT NULL,\n method TEXT,\n host TEXT,\n path TEXT,\n status INTEGER,\n close_code INTEGER,\n content_type TEXT,\n headers_json TEXT,\n data_text TEXT,\n data_blob_id TEXT,\n data_sha256 TEXT,\n error_text TEXT,\n meta_json TEXT,\n FOREIGN KEY (session_id) REFERENCES capture_sessions(id) ON DELETE CASCADE,\n FOREIGN KEY (data_blob_id) REFERENCES capture_blobs(blob_id) ON DELETE SET NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS capture_events_session_ts_idx\n ON capture_events(session_id, ts);\n\nCREATE INDEX IF NOT EXISTS capture_events_flow_idx\n ON capture_events(flow_id, ts);\n\nCREATE TABLE IF NOT EXISTS sandbox_registry_entries (\n registry_kind TEXT NOT NULL,\n container_name TEXT NOT NULL,\n session_key TEXT,\n backend_id TEXT,\n runtime_label TEXT,\n image TEXT,\n created_at_ms INTEGER,\n last_used_at_ms INTEGER,\n config_label_kind TEXT,\n config_hash TEXT,\n cdp_port INTEGER,\n no_vnc_port INTEGER,\n entry_json TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n PRIMARY KEY (registry_kind, container_name)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_sandbox_registry_updated\n ON sandbox_registry_entries(registry_kind, updated_at DESC, container_name);\n\nCREATE INDEX IF NOT EXISTS idx_sandbox_registry_session\n ON sandbox_registry_entries(registry_kind, session_key, last_used_at_ms DESC, container_name)\n WHERE session_key IS NOT NULL;\n\nCREATE INDEX IF NOT EXISTS idx_sandbox_registry_last_used\n ON sandbox_registry_entries(registry_kind, last_used_at_ms DESC, container_name)\n WHERE last_used_at_ms IS NOT NULL;\n\nCREATE TABLE IF NOT EXISTS cron_jobs (\n store_key TEXT NOT NULL,\n job_id TEXT NOT NULL,\n declaration_key TEXT,\n owner_agent_id TEXT,\n name TEXT NOT NULL,\n description TEXT,\n enabled INTEGER NOT NULL,\n agent_id TEXT,\n payload_kind TEXT NOT NULL,\n job_json TEXT NOT NULL,\n grant_definition_revision TEXT,\n grant_definition_generation INTEGER,\n grant_definition_updated_at INTEGER,\n state_json TEXT NOT NULL DEFAULT '{}',\n runtime_updated_at_ms INTEGER,\n schedule_identity TEXT,\n sort_order INTEGER NOT NULL DEFAULT 0,\n updated_at INTEGER NOT NULL,\n PRIMARY KEY (store_key, job_id)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_cron_jobs_store_order\n ON cron_jobs(store_key, sort_order ASC, updated_at ASC, job_id);\n\n-- One owner-native receipt is also the durable execution fence. Receipts\n-- survive job deletion so operators can distinguish a run from log inference.\nCREATE TABLE IF NOT EXISTS cron_run_receipts (\n receipt_id TEXT PRIMARY KEY,\n store_key TEXT NOT NULL,\n job_id TEXT NOT NULL,\n config_revision TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n request_run_id TEXT,\n status TEXT NOT NULL,\n owner_pid INTEGER NOT NULL,\n owner_start_time INTEGER,\n started_at_ms INTEGER NOT NULL,\n finished_at_ms INTEGER,\n error_text TEXT,\n CHECK (status IN ('running', 'ok', 'error', 'skipped', 'interrupted', 'superseded')),\n CHECK (\n (status = 'running' AND finished_at_ms IS NULL)\n OR\n (status != 'running' AND finished_at_ms IS NOT NULL)\n )\n) STRICT;\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_cron_run_receipts_active_job\n ON cron_run_receipts(store_key, job_id)\n WHERE status = 'running';\n\nCREATE INDEX IF NOT EXISTS idx_cron_run_receipts_job_history\n ON cron_run_receipts(store_key, job_id, started_at_ms DESC, receipt_id DESC);\n\n-- Retirement follows the receipt's retention without changing its released shape.\nCREATE TABLE IF NOT EXISTS cron_run_trigger_state_retirements (\n receipt_id TEXT PRIMARY KEY\n REFERENCES cron_run_receipts(receipt_id) ON DELETE CASCADE\n) STRICT;\n\n-- Runtime-private authority is independent of job_json so downgraded writers\n-- can rewrite recognized job config without erasing or silently widening it.\nCREATE TABLE IF NOT EXISTS cron_job_runtime_authorities (\n store_key TEXT NOT NULL,\n job_id TEXT NOT NULL,\n authority_json TEXT,\n authority_input_fingerprint TEXT,\n recovery_required INTEGER NOT NULL,\n PRIMARY KEY (store_key, job_id),\n FOREIGN KEY (store_key, job_id)\n REFERENCES cron_jobs(store_key, job_id) ON DELETE CASCADE,\n CHECK (recovery_required IN (0, 1)),\n CHECK (\n (recovery_required = 0 AND authority_json IS NOT NULL AND authority_input_fingerprint IS NOT NULL)\n OR\n (recovery_required = 1 AND authority_json IS NULL AND authority_input_fingerprint IS NULL)\n )\n) STRICT;\n\n-- Scratch is separate from cron_jobs so scheduler state writes and downgraded\n-- full-row replacement preserve it. New builds prune rows explicitly on job removal.\n-- content NULL is a tombstone: it keeps the revision lineage monotonic across\n-- unset/recreate so stale compare-and-swap writes cannot resurrect old content.\nCREATE TABLE IF NOT EXISTS cron_job_scratch (\n store_key TEXT NOT NULL,\n job_id TEXT NOT NULL,\n content TEXT,\n revision INTEGER NOT NULL,\n source_sha256 TEXT,\n updated_at_ms INTEGER NOT NULL,\n PRIMARY KEY (store_key, job_id),\n CHECK (revision >= 1),\n CHECK (content IS NULL OR length(CAST(content AS BLOB)) <= 262144)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_cron_job_scratch_store_updated\n ON cron_job_scratch(store_key, updated_at_ms DESC, job_id);\n\nCREATE TABLE IF NOT EXISTS delivery_queue_entries (\n queue_name TEXT NOT NULL,\n id TEXT NOT NULL,\n status TEXT NOT NULL,\n entry_kind TEXT,\n session_key TEXT,\n channel TEXT,\n target TEXT,\n account_id TEXT,\n retry_count INTEGER NOT NULL DEFAULT 0,\n last_attempt_at INTEGER,\n last_error TEXT,\n recovery_state TEXT,\n platform_send_started_at INTEGER,\n entry_json TEXT NOT NULL,\n enqueued_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n failed_at INTEGER,\n PRIMARY KEY (queue_name, id)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_delivery_queue_pending\n ON delivery_queue_entries(queue_name, status, enqueued_at, id);\n\nCREATE INDEX IF NOT EXISTS idx_delivery_queue_failed\n ON delivery_queue_entries(queue_name, status, failed_at, id);\n\nCREATE INDEX IF NOT EXISTS idx_delivery_queue_session\n ON delivery_queue_entries(queue_name, status, session_key, enqueued_at, id)\n WHERE session_key IS NOT NULL;\n\nCREATE INDEX IF NOT EXISTS idx_delivery_queue_target\n ON delivery_queue_entries(queue_name, status, channel, target, enqueued_at, id)\n WHERE channel IS NOT NULL AND target IS NOT NULL;\n\nCREATE TABLE IF NOT EXISTS task_runs (\n task_id TEXT NOT NULL PRIMARY KEY,\n runtime TEXT NOT NULL,\n task_kind TEXT,\n source_id TEXT,\n requester_session_key TEXT,\n owner_key TEXT NOT NULL,\n scope_kind TEXT NOT NULL,\n child_session_key TEXT,\n parent_flow_id TEXT,\n parent_task_id TEXT,\n agent_id TEXT,\n requester_agent_id TEXT,\n run_id TEXT,\n execution_owner_host TEXT,\n execution_owner_pid INTEGER,\n execution_owner_start_identity INTEGER,\n label TEXT,\n task TEXT NOT NULL,\n status TEXT NOT NULL,\n delivery_status TEXT NOT NULL,\n notify_policy TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n started_at INTEGER,\n ended_at INTEGER,\n last_event_at INTEGER,\n cleanup_after INTEGER,\n tool_use_count INTEGER,\n last_tool_name TEXT,\n error TEXT,\n progress_summary TEXT,\n terminal_summary TEXT,\n terminal_outcome TEXT,\n detail_json TEXT\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_task_runs_run_id ON task_runs(run_id);\nCREATE INDEX IF NOT EXISTS idx_task_runs_status ON task_runs(status);\nCREATE INDEX IF NOT EXISTS idx_task_runs_runtime_status ON task_runs(runtime, status);\nCREATE INDEX IF NOT EXISTS idx_task_runs_cleanup_after ON task_runs(cleanup_after);\nCREATE INDEX IF NOT EXISTS idx_task_runs_last_event_at ON task_runs(last_event_at);\nCREATE INDEX IF NOT EXISTS idx_task_runs_owner_key ON task_runs(owner_key);\nCREATE INDEX IF NOT EXISTS idx_task_runs_parent_flow_id ON task_runs(parent_flow_id);\nCREATE INDEX IF NOT EXISTS idx_task_runs_child_session_key ON task_runs(child_session_key);\nCREATE INDEX IF NOT EXISTS idx_task_runs_requester_session_key ON task_runs(requester_session_key);\nCREATE INDEX IF NOT EXISTS idx_task_runs_runtime_source_ended\n ON task_runs(runtime, source_id, ended_at, created_at, task_id);\nCREATE INDEX IF NOT EXISTS idx_task_runs_runtime_ended\n ON task_runs(runtime, ended_at, created_at, task_id);\n\nCREATE TABLE IF NOT EXISTS subagent_runs (\n run_id TEXT NOT NULL PRIMARY KEY,\n child_session_key TEXT NOT NULL,\n controller_session_key TEXT,\n controller_store_path TEXT,\n requester_session_key TEXT NOT NULL,\n requester_store_path TEXT,\n created_at INTEGER NOT NULL,\n payload_json TEXT NOT NULL DEFAULT '{}'\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_subagent_runs_child_session_key\n ON subagent_runs(child_session_key, created_at DESC, run_id);\nCREATE INDEX IF NOT EXISTS idx_subagent_runs_requester_session_key\n ON subagent_runs(requester_session_key, created_at DESC, run_id);\nCREATE INDEX IF NOT EXISTS idx_subagent_runs_controller_session_key\n ON subagent_runs(controller_session_key, created_at DESC, run_id);\n\nCREATE TABLE IF NOT EXISTS current_conversation_bindings (\n binding_key TEXT NOT NULL PRIMARY KEY,\n binding_id TEXT NOT NULL,\n target_session_key TEXT NOT NULL,\n channel TEXT NOT NULL,\n account_id TEXT NOT NULL,\n conversation_kind TEXT NOT NULL,\n parent_conversation_id TEXT,\n conversation_id TEXT NOT NULL,\n target_kind TEXT NOT NULL,\n status TEXT NOT NULL,\n bound_at INTEGER NOT NULL,\n expires_at INTEGER,\n metadata_json TEXT,\n record_json TEXT NOT NULL,\n updated_at INTEGER NOT NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_current_conversation_bindings_target\n ON current_conversation_bindings(target_session_key, updated_at DESC, binding_key);\nCREATE INDEX IF NOT EXISTS idx_current_conversation_bindings_conversation\n ON current_conversation_bindings(channel, account_id, conversation_kind, conversation_id);\nCREATE INDEX IF NOT EXISTS idx_current_conversation_bindings_expires\n ON current_conversation_bindings(expires_at, binding_key);\n\nCREATE TABLE IF NOT EXISTS plugin_binding_approvals (\n plugin_root TEXT NOT NULL,\n channel TEXT NOT NULL,\n account_id TEXT NOT NULL,\n plugin_id TEXT NOT NULL,\n plugin_name TEXT,\n approved_at INTEGER NOT NULL,\n PRIMARY KEY (plugin_root, channel, account_id)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_plugin_binding_approvals_plugin\n ON plugin_binding_approvals(plugin_id, approved_at DESC);\n\nCREATE TABLE IF NOT EXISTS task_delivery_state (\n task_id TEXT NOT NULL PRIMARY KEY,\n requester_origin_json TEXT,\n last_notified_event_at INTEGER,\n FOREIGN KEY (task_id) REFERENCES task_runs(task_id) ON DELETE CASCADE\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS flow_runs (\n flow_id TEXT NOT NULL PRIMARY KEY,\n shape TEXT,\n sync_mode TEXT NOT NULL DEFAULT 'managed',\n owner_key TEXT NOT NULL,\n requester_origin_json TEXT,\n controller_id TEXT,\n revision INTEGER NOT NULL DEFAULT 0,\n status TEXT NOT NULL,\n notify_policy TEXT NOT NULL,\n goal TEXT NOT NULL,\n current_step TEXT,\n blocked_task_id TEXT,\n blocked_summary TEXT,\n state_json TEXT,\n wait_json TEXT,\n cancel_requested_at INTEGER,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n ended_at INTEGER\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_flow_runs_status ON flow_runs(status);\nCREATE INDEX IF NOT EXISTS idx_flow_runs_owner_key ON flow_runs(owner_key);\nCREATE INDEX IF NOT EXISTS idx_flow_runs_updated_at ON flow_runs(updated_at);\n\n-- Durable meeting-capture sessions are gateway-global rather than agent-session\n-- transcripts. JSON/JSONL files are doctor import inputs or explicit CLI exports.\nCREATE TABLE IF NOT EXISTS meeting_transcript_sessions (\n session_id TEXT NOT NULL,\n started_at TEXT NOT NULL,\n selector TEXT NOT NULL UNIQUE,\n export_key TEXT NOT NULL,\n session_slug TEXT NOT NULL,\n provider_id TEXT NOT NULL,\n title TEXT,\n source_json TEXT NOT NULL,\n stopped_at TEXT,\n metadata_json TEXT,\n export_manifest_json TEXT NOT NULL DEFAULT '{}',\n export_pending_json TEXT NOT NULL DEFAULT '[]',\n next_utterance_seq INTEGER NOT NULL DEFAULT 0 CHECK (next_utterance_seq >= 0),\n created_at_ms INTEGER NOT NULL CHECK (created_at_ms >= 0),\n updated_at_ms INTEGER NOT NULL CHECK (updated_at_ms >= 0),\n PRIMARY KEY (session_id, started_at)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_meeting_transcript_sessions_started\n ON meeting_transcript_sessions(started_at DESC, session_id);\n\nCREATE INDEX IF NOT EXISTS idx_meeting_transcript_sessions_id\n ON meeting_transcript_sessions(session_id, started_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_meeting_transcript_sessions_slug\n ON meeting_transcript_sessions(session_slug, started_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_meeting_transcript_sessions_export_key\n ON meeting_transcript_sessions(export_key);\n\nCREATE TABLE IF NOT EXISTS meeting_transcript_utterances (\n session_id TEXT NOT NULL,\n session_started_at TEXT NOT NULL,\n sequence INTEGER NOT NULL CHECK (sequence >= 0),\n utterance_id TEXT,\n started_at TEXT,\n ended_at TEXT,\n speaker_id TEXT,\n speaker_label TEXT,\n text TEXT NOT NULL,\n final INTEGER CHECK (final IN (0, 1)),\n metadata_json TEXT,\n PRIMARY KEY (session_id, session_started_at, sequence),\n FOREIGN KEY (session_id, session_started_at)\n REFERENCES meeting_transcript_sessions(session_id, started_at)\n ON DELETE CASCADE\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS meeting_transcript_summaries (\n session_id TEXT NOT NULL,\n session_started_at TEXT NOT NULL,\n generated_at TEXT,\n summary_json TEXT,\n markdown TEXT,\n utterance_count INTEGER NOT NULL CHECK (utterance_count >= 0),\n PRIMARY KEY (session_id, session_started_at),\n FOREIGN KEY (session_id, session_started_at)\n REFERENCES meeting_transcript_sessions(session_id, started_at)\n ON DELETE CASCADE,\n CHECK (summary_json IS NOT NULL OR markdown IS NOT NULL)\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS migration_runs (\n id TEXT NOT NULL PRIMARY KEY,\n started_at INTEGER NOT NULL,\n finished_at INTEGER,\n status TEXT NOT NULL,\n report_json TEXT NOT NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_migration_runs_started\n ON migration_runs(started_at DESC, id);\n\nCREATE TABLE IF NOT EXISTS migration_sources (\n source_key TEXT NOT NULL PRIMARY KEY,\n migration_kind TEXT NOT NULL,\n source_path TEXT NOT NULL,\n target_table TEXT NOT NULL,\n source_sha256 TEXT,\n source_size_bytes INTEGER,\n source_record_count INTEGER,\n last_run_id TEXT NOT NULL,\n status TEXT NOT NULL,\n imported_at INTEGER NOT NULL,\n removed_source INTEGER NOT NULL DEFAULT 0,\n report_json TEXT NOT NULL,\n FOREIGN KEY (last_run_id) REFERENCES migration_runs(id) ON DELETE CASCADE\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_migration_sources_path\n ON migration_sources(source_path, migration_kind, target_table);\n\nCREATE INDEX IF NOT EXISTS idx_migration_sources_run\n ON migration_sources(last_run_id, source_path);\n\nCREATE TABLE IF NOT EXISTS backup_runs (\n id TEXT NOT NULL PRIMARY KEY,\n created_at INTEGER NOT NULL,\n archive_path TEXT NOT NULL,\n status TEXT NOT NULL,\n manifest_json TEXT NOT NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_backup_runs_created\n ON backup_runs(created_at DESC, id);\n\nCREATE TABLE IF NOT EXISTS worktrees (\n id TEXT NOT NULL PRIMARY KEY,\n repo_fingerprint TEXT NOT NULL,\n repo_root TEXT NOT NULL,\n path TEXT NOT NULL,\n branch TEXT NOT NULL,\n base_ref TEXT NOT NULL,\n owner_kind TEXT NOT NULL CHECK (owner_kind IN ('manual', 'workboard', 'session')),\n owner_id TEXT,\n snapshot_ref TEXT,\n provisioned_paths_json TEXT,\n created_at INTEGER NOT NULL,\n last_active_at INTEGER NOT NULL,\n removed_at INTEGER,\n run_end_cleanup_json TEXT,\n gc_protection_json TEXT\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_worktrees_repo_fingerprint\n ON worktrees(repo_fingerprint);\n\nCREATE INDEX IF NOT EXISTS idx_worktrees_removed_at\n ON worktrees(removed_at);\n\nCREATE TABLE IF NOT EXISTS worktree_provisioned_file_chunks (\n worktree_id TEXT NOT NULL,\n path TEXT NOT NULL,\n chunk_index INTEGER NOT NULL CHECK (chunk_index >= 0),\n data BLOB NOT NULL,\n PRIMARY KEY (worktree_id, path, chunk_index)\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS worktree_templates (\n cache_key TEXT NOT NULL PRIMARY KEY,\n id TEXT NOT NULL UNIQUE,\n repo_root TEXT NOT NULL,\n common_dir TEXT NOT NULL,\n worktree_root TEXT NOT NULL,\n path TEXT NOT NULL,\n backend TEXT NOT NULL,\n source_commit TEXT NOT NULL,\n content_key TEXT NOT NULL,\n status TEXT NOT NULL CHECK (status IN ('preparing', 'ready')),\n created_at INTEGER NOT NULL,\n last_used_at INTEGER NOT NULL\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS projects (\n id TEXT NOT NULL PRIMARY KEY,\n display_name TEXT NOT NULL,\n repo_root TEXT NOT NULL,\n origin_url TEXT,\n source TEXT NOT NULL CHECK (source IN ('registered', 'cloned')),\n created_at_ms INT NOT NULL,\n updated_at_ms INT NOT NULL\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS user_preferences (\n profile_id TEXT NOT NULL,\n pref_key TEXT NOT NULL,\n value_json TEXT NOT NULL,\n updated_at_ms INT NOT NULL,\n PRIMARY KEY (profile_id, pref_key)\n) STRICT;\n\n-- Gateway-owned custom session group catalog (names + display order).\n-- Membership stays on each session entry's category field; this table only\n-- owns which groups exist and how operator UIs order them.\nCREATE TABLE IF NOT EXISTS session_groups (\n name TEXT NOT NULL PRIMARY KEY,\n position INTEGER NOT NULL,\n created_at INTEGER NOT NULL,\n cwd TEXT,\n worktree INTEGER\n) STRICT;\n\n-- Gateway-owned durable cloud worker lifecycle. Provider-specific execution\n-- stays in plugins; this table records only core reconciliation facts.\nCREATE TABLE IF NOT EXISTS worker_environments (\n environment_id TEXT NOT NULL PRIMARY KEY,\n provider_id TEXT NOT NULL,\n profile_id TEXT NOT NULL,\n profile_snapshot_json TEXT NOT NULL,\n last_activated_at_ms INTEGER,\n preparation_key TEXT,\n preparation_purpose TEXT,\n preparation_demand_at_ms INTEGER,\n preparation_expires_at_ms INTEGER,\n preparation_consumed_at_ms INTEGER CHECK (\n (preparation_key IS NULL AND preparation_demand_at_ms IS NULL\n AND preparation_expires_at_ms IS NULL AND preparation_consumed_at_ms IS NULL)\n OR\n (preparation_key IS NOT NULL AND length(preparation_key) = 64\n AND preparation_key NOT GLOB '*[^0-9a-f]*'\n AND preparation_demand_at_ms IS NOT NULL\n AND preparation_demand_at_ms BETWEEN 0 AND 9007199254740991\n AND preparation_expires_at_ms IS NOT NULL\n AND preparation_expires_at_ms > preparation_demand_at_ms\n AND preparation_expires_at_ms <= 9007199254740991\n AND (preparation_consumed_at_ms IS NULL\n OR (preparation_consumed_at_ms >= preparation_demand_at_ms\n AND preparation_consumed_at_ms < preparation_expires_at_ms)))\n ),\n provision_operation_id TEXT NOT NULL UNIQUE,\n lease_id TEXT,\n node_setup_id TEXT,\n node_device_id TEXT,\n ssh_host TEXT,\n ssh_port INTEGER CHECK (ssh_port IS NULL OR (ssh_port >= 1 AND ssh_port <= 65535)),\n ssh_user TEXT,\n ssh_host_key TEXT,\n ssh_key_ref_json TEXT,\n desktop_json TEXT,\n state TEXT NOT NULL CHECK (\n state IN (\n 'requested',\n 'provisioning',\n 'bootstrapping',\n 'ready',\n 'attached',\n 'idle',\n 'draining',\n 'destroying',\n 'destroyed',\n 'failed',\n 'orphaned'\n )\n ),\n bootstrap_bundle_hash TEXT,\n bootstrap_openclaw_version TEXT,\n bootstrap_protocol_features_json TEXT,\n bootstrap_install_kind TEXT,\n owner_epoch INTEGER NOT NULL DEFAULT 0 CHECK (owner_epoch >= 0),\n teardown_terminal_state TEXT CHECK (teardown_terminal_state IN ('destroyed', 'failed')),\n attached_session_ids_json TEXT NOT NULL DEFAULT '[]',\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n state_changed_at_ms INTEGER NOT NULL,\n idle_since_at_ms INTEGER,\n destroy_requested_at_ms INTEGER,\n last_error TEXT,\n shared_host INTEGER\n) STRICT;\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_worker_environments_provider_lease\n ON worker_environments(provider_id, lease_id)\n WHERE lease_id IS NOT NULL;\n\nCREATE INDEX IF NOT EXISTS idx_worker_environments_terminal_changed\n ON worker_environments(state_changed_at_ms, environment_id);\n\n-- A dedicated node registers its fixed build paths before ready, then binds\n-- them once. The environment belongs to the Gateway's separate database.\nCREATE TABLE IF NOT EXISTS node_worker_prepared_workspaces (\n preparation_key TEXT NOT NULL PRIMARY KEY CHECK (\n length(preparation_key) = 64 AND preparation_key NOT GLOB '*[^0-9a-f]*'\n ),\n cache_key TEXT NOT NULL CHECK (\n length(cache_key) = 64 AND cache_key NOT GLOB '*[^0-9a-f]*'\n ),\n gateway_namespace TEXT NOT NULL CHECK (length(gateway_namespace) > 0),\n workspace_dir TEXT NOT NULL UNIQUE CHECK (length(workspace_dir) > 0),\n home_dir TEXT NOT NULL CHECK (length(home_dir) > 0),\n source_manifest_ref TEXT NOT NULL CHECK (\n length(source_manifest_ref) = 71 AND substr(source_manifest_ref, 1, 7) = 'sha256:'\n AND substr(source_manifest_ref, 8) NOT GLOB '*[^0-9a-f]*'\n ),\n prepared_manifest_ref TEXT NOT NULL CHECK (\n length(prepared_manifest_ref) = 71 AND substr(prepared_manifest_ref, 1, 7) = 'sha256:'\n AND substr(prepared_manifest_ref, 8) NOT GLOB '*[^0-9a-f]*'\n ),\n state TEXT NOT NULL CHECK (state IN ('available', 'bound', 'retiring', 'retired')),\n environment_id TEXT NOT NULL CHECK (length(environment_id) > 0),\n session_id TEXT,\n session_key TEXT,\n owner_epoch INTEGER CHECK (owner_epoch BETWEEN 1 AND 9007199254740991),\n created_at_ms INTEGER NOT NULL CHECK (created_at_ms BETWEEN 0 AND 9007199254740991),\n bound_at_ms INTEGER CHECK (bound_at_ms BETWEEN created_at_ms AND 9007199254740991),\n retired_at_ms INTEGER CHECK (\n retired_at_ms BETWEEN coalesce(bound_at_ms, created_at_ms) AND 9007199254740991\n ),\n CHECK (\n (session_id IS NULL AND session_key IS NULL AND owner_epoch IS NULL AND bound_at_ms IS NULL)\n OR\n (session_id IS NOT NULL AND length(session_id) > 0\n AND session_key IS NOT NULL AND length(session_key) > 0\n AND owner_epoch IS NOT NULL AND bound_at_ms IS NOT NULL)\n ),\n CHECK (\n (state = 'available' AND bound_at_ms IS NULL AND retired_at_ms IS NULL)\n OR (state = 'bound' AND bound_at_ms IS NOT NULL AND retired_at_ms IS NULL)\n OR (state = 'retiring' AND retired_at_ms IS NULL)\n OR (state = 'retired' AND retired_at_ms IS NOT NULL)\n )\n) STRICT;\n\n-- Provider-advertised fallback ports preserve stable retry order separately\n-- from the downgrade-sensitive canonical worker environment row.\nCREATE TABLE IF NOT EXISTS worker_environment_ssh_fallback_ports (\n environment_id TEXT NOT NULL,\n position INTEGER NOT NULL CHECK (position >= 0 AND position <= 9),\n port INTEGER NOT NULL CHECK (port >= 1 AND port <= 65535),\n PRIMARY KEY (environment_id, position),\n UNIQUE (environment_id, port),\n FOREIGN KEY (environment_id) REFERENCES worker_environments(environment_id) ON DELETE CASCADE\n) STRICT;\n\n-- Logical sessions own repository intent and accepted artifact references,\n-- independently of the worker or rotating transcript session id.\nCREATE TABLE IF NOT EXISTS session_repository_workspaces (\n workspace_id TEXT NOT NULL PRIMARY KEY CHECK (length(workspace_id) = 36),\n agent_id TEXT NOT NULL CHECK (length(agent_id) BETWEEN 1 AND 128),\n session_key TEXT NOT NULL CHECK (length(session_key) BETWEEN 1 AND 1024),\n url TEXT NOT NULL CHECK (length(url) BETWEEN 1 AND 4096),\n requested_ref TEXT CHECK (requested_ref IS NULL OR length(requested_ref) BETWEEN 1 AND 1024),\n run_setup_script INTEGER NOT NULL DEFAULT 0 CHECK (run_setup_script IN (0, 1)),\n base_commit TEXT CHECK (base_commit IS NULL OR length(base_commit) IN (40, 64)),\n base_manifest_hash TEXT,\n branch TEXT NOT NULL CHECK (length(branch) BETWEEN 1 AND 256),\n checkpoint_ref TEXT,\n manifest_hash TEXT,\n revision INTEGER NOT NULL CHECK (revision >= 0),\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n UNIQUE (agent_id, session_key),\n CHECK (base_manifest_hash IS NULL OR base_commit IS NOT NULL),\n CHECK ((checkpoint_ref IS NULL AND manifest_hash IS NULL)\n OR (checkpoint_ref IS NOT NULL AND manifest_hash IS NOT NULL AND base_manifest_hash IS NOT NULL))\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS github_repository_publication_requests (\n request_id TEXT NOT NULL PRIMARY KEY,\n owner_profile_id TEXT,\n connection_generation TEXT,\n idempotency_key TEXT NOT NULL,\n request_digest TEXT NOT NULL,\n session_id TEXT NOT NULL,\n session_lifecycle_revision TEXT,\n requester_authority_json TEXT,\n session_key TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n workspace_id TEXT NOT NULL,\n checkpoint_ref TEXT,\n checkpoint_digest TEXT,\n claim_id TEXT,\n run_id TEXT,\n environment_id TEXT,\n owner_epoch INTEGER,\n placement_generation INTEGER,\n identity_source TEXT NOT NULL CHECK (identity_source IN ('system-detected', 'system-configured', 'agent-override', 'personal')),\n identity_profile_id TEXT,\n identity_account_id INTEGER NOT NULL,\n identity_login TEXT NOT NULL,\n title TEXT,\n body TEXT,\n status TEXT NOT NULL CHECK (status IN ('requested', 'publishing', 'needs_confirmation', 'published', 'failed')),\n gateway_instance_id TEXT,\n execution_id TEXT,\n last_effect TEXT CHECK (last_effect IN ('push', 'pull_request')),\n effect_state TEXT CHECK (effect_state IN ('dispatched', 'observed')),\n push_repository TEXT,\n repository TEXT,\n branch TEXT NOT NULL,\n base_branch TEXT,\n source_head_commit TEXT,\n source_index_tree TEXT,\n workspace_tree TEXT,\n previous_head_commit TEXT,\n pushed_head_commit TEXT,\n head_commit TEXT,\n pull_request_url TEXT,\n error_code TEXT,\n next_action TEXT,\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n reported_at_ms INTEGER,\n CHECK ((identity_source = 'personal' AND owner_profile_id IS NOT NULL AND connection_generation IS NOT NULL)\n OR (identity_source <> 'personal' AND owner_profile_id IS NULL AND connection_generation IS NULL)),\n CHECK ((checkpoint_ref IS NULL AND checkpoint_digest IS NULL) OR (checkpoint_ref IS NOT NULL AND checkpoint_digest IS NOT NULL)),\n CHECK ((last_effect IS NULL AND effect_state IS NULL) OR (last_effect IS NOT NULL AND effect_state IS NOT NULL))\n) STRICT;\nCREATE UNIQUE INDEX IF NOT EXISTS idx_github_repository_publication_shared_request\n ON github_repository_publication_requests(session_id, idempotency_key) WHERE owner_profile_id IS NULL;\nCREATE UNIQUE INDEX IF NOT EXISTS idx_github_repository_publication_personal_request\n ON github_repository_publication_requests(owner_profile_id, session_id, idempotency_key) WHERE owner_profile_id IS NOT NULL;\n\n-- Session placement lives in the shared state database so local admission,\n-- worker admission, and environment attachment use one durable authority.\nCREATE TABLE IF NOT EXISTS worker_session_placements (\n session_id TEXT NOT NULL PRIMARY KEY,\n agent_id TEXT NOT NULL,\n session_key TEXT NOT NULL,\n execution_mode TEXT CHECK (execution_mode IN ('worker-turn', 'remote-exec')),\n state TEXT NOT NULL CHECK (\n state IN (\n 'local',\n 'requested',\n 'provisioning',\n 'syncing',\n 'starting',\n 'active',\n 'draining',\n 'reconciling',\n 'reclaimed',\n 'failed'\n )\n ),\n environment_id TEXT,\n transition_generation INTEGER NOT NULL DEFAULT 0 CHECK (transition_generation >= 0),\n active_owner_epoch INTEGER CHECK (active_owner_epoch IS NULL OR active_owner_epoch >= 1),\n workspace_base_manifest_ref TEXT,\n remote_workspace_dir TEXT,\n worker_bundle_hash TEXT,\n last_transcript_ack_cursor INTEGER CHECK (\n last_transcript_ack_cursor IS NULL OR last_transcript_ack_cursor >= 0\n ),\n last_live_event_ack_cursor INTEGER CHECK (\n last_live_event_ack_cursor IS NULL OR last_live_event_ack_cursor >= 0\n ),\n recovery_error TEXT,\n turn_claim_owner TEXT CHECK (turn_claim_owner IN ('local', 'worker')),\n turn_claim_id TEXT,\n turn_claim_run_id TEXT,\n turn_claim_generation INTEGER CHECK (\n turn_claim_generation IS NULL OR turn_claim_generation >= 0\n ),\n turn_claim_owner_epoch INTEGER CHECK (\n turn_claim_owner_epoch IS NULL OR turn_claim_owner_epoch >= 1\n ),\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n state_changed_at_ms INTEGER NOT NULL,\n terminal_reason TEXT,\n terminal_at_ms INTEGER,\n CHECK (\n (state IN ('local', 'requested')\n AND environment_id IS NULL AND active_owner_epoch IS NULL\n AND workspace_base_manifest_ref IS NULL AND remote_workspace_dir IS NULL\n AND worker_bundle_hash IS NULL\n AND last_transcript_ack_cursor IS NULL AND last_live_event_ack_cursor IS NULL\n AND recovery_error IS NULL)\n OR\n (state IS 'provisioning'\n AND active_owner_epoch IS NULL\n AND workspace_base_manifest_ref IS NULL AND remote_workspace_dir IS NULL\n AND worker_bundle_hash IS NULL\n AND last_transcript_ack_cursor IS NULL AND last_live_event_ack_cursor IS NULL\n AND recovery_error IS NULL)\n OR\n (state IS 'syncing'\n AND environment_id IS NOT NULL AND active_owner_epoch IS NULL\n AND workspace_base_manifest_ref IS NULL AND remote_workspace_dir IS NULL\n AND worker_bundle_hash IS NOT NULL\n AND last_transcript_ack_cursor IS NULL AND last_live_event_ack_cursor IS NULL\n AND recovery_error IS NULL)\n OR\n (state IS 'starting'\n AND environment_id IS NOT NULL AND active_owner_epoch IS NULL\n AND workspace_base_manifest_ref IS NOT NULL AND remote_workspace_dir IS NOT NULL\n AND worker_bundle_hash IS NOT NULL\n AND last_transcript_ack_cursor IS NULL AND last_live_event_ack_cursor IS NULL\n AND recovery_error IS NULL)\n OR\n (state IN ('active', 'draining', 'reconciling')\n AND environment_id IS NOT NULL AND active_owner_epoch IS NOT NULL\n AND workspace_base_manifest_ref IS NOT NULL AND remote_workspace_dir IS NOT NULL\n AND worker_bundle_hash IS NOT NULL AND recovery_error IS NULL)\n OR\n (state IS 'reclaimed'\n AND environment_id IS NOT NULL AND active_owner_epoch IS NOT NULL\n AND workspace_base_manifest_ref IS NOT NULL AND remote_workspace_dir IS NOT NULL\n AND worker_bundle_hash IS NOT NULL AND recovery_error IS NULL\n AND turn_claim_owner IS NULL AND turn_claim_id IS NULL AND turn_claim_run_id IS NULL\n AND turn_claim_generation IS NULL AND turn_claim_owner_epoch IS NULL)\n OR\n (state IS 'failed' AND recovery_error IS NOT NULL)\n ),\n CHECK (\n (turn_claim_owner IS NULL AND turn_claim_id IS NULL AND turn_claim_run_id IS NULL\n AND turn_claim_generation IS NULL AND turn_claim_owner_epoch IS NULL)\n OR\n (turn_claim_owner IS 'local' AND turn_claim_id IS NOT NULL\n AND turn_claim_run_id IS NOT NULL AND turn_claim_generation IS NOT NULL\n AND turn_claim_owner_epoch IS NULL)\n OR\n (turn_claim_owner IS 'worker' AND turn_claim_id IS NOT NULL\n AND turn_claim_run_id IS NOT NULL AND turn_claim_generation IS NOT NULL\n AND turn_claim_owner_epoch IS NOT NULL)\n ),\n CHECK (\n turn_claim_owner IS NULL\n OR\n (turn_claim_owner IS 'local' AND (\n state IN ('local', 'requested', 'failed')\n OR (state IN ('active', 'draining') AND execution_mode IS 'remote-exec')\n ))\n OR\n (turn_claim_owner IS 'worker' AND state IN ('active', 'draining')\n AND (execution_mode IS NULL OR execution_mode IS 'worker-turn')\n AND turn_claim_owner_epoch IS active_owner_epoch)\n )\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_worker_session_placements_session_key\n ON worker_session_placements(agent_id, session_key);\n\nCREATE INDEX IF NOT EXISTS idx_worker_session_placements_reconcile\n ON worker_session_placements(updated_at_ms, session_id);\n\nCREATE INDEX IF NOT EXISTS idx_worker_session_placements_environment\n ON worker_session_placements(environment_id)\n WHERE environment_id IS NOT NULL;\n\n-- Planned placement moves retain their exact source CAS and bounded target\n-- without widening the stable placement-state vocabulary. The opaque operation\n-- id fences stale asynchronous completion; it is correlation, never authority.\nCREATE TABLE IF NOT EXISTS worker_session_placement_moves (\n operation_id TEXT NOT NULL PRIMARY KEY,\n session_id TEXT NOT NULL UNIQUE\n REFERENCES worker_session_placements(session_id) ON DELETE CASCADE,\n source_generation INTEGER NOT NULL CHECK (source_generation >= 0),\n source_environment_id TEXT NOT NULL CHECK (\n length(source_environment_id) BETWEEN 1 AND 256\n AND source_environment_id = trim(source_environment_id)\n ),\n source_owner_epoch INTEGER NOT NULL CHECK (source_owner_epoch >= 1),\n target_kind TEXT NOT NULL CHECK (target_kind IN ('gateway', 'profile', 'device')),\n target_id TEXT,\n -- Keep these nullable columns constraint-free so lazy ALTER TABLE produces the\n -- same shape as fresh databases; placement-move code validates their values.\n target_machine_class TEXT,\n target_os TEXT,\n -- Explicit source abandonment is a durable operator decision. Keep the bit\n -- bare and nullable so same-version older readers can safely omit it.\n abandon_source INTEGER,\n last_error TEXT,\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n CHECK (\n (target_kind IS 'gateway' AND target_id IS NULL)\n OR\n (target_kind IN ('profile', 'device')\n AND target_id IS NOT NULL\n AND length(target_id) BETWEEN 1 AND 256\n AND target_id = trim(target_id))\n )\n) STRICT;\n\n-- Worker-visible session RPC authority is persisted against the exact turn\n-- claim. The launch descriptor is informative only; Gateway dispatch always\n-- revalidates this record and the live placement claim before executing.\nCREATE TABLE IF NOT EXISTS worker_turn_tool_authorities (\n session_id TEXT NOT NULL PRIMARY KEY,\n environment_id TEXT NOT NULL,\n owner_epoch INTEGER NOT NULL CHECK (owner_epoch >= 1),\n placement_generation INTEGER NOT NULL CHECK (placement_generation >= 0),\n claim_id TEXT NOT NULL,\n run_id TEXT NOT NULL,\n tool_names_json TEXT NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n FOREIGN KEY (session_id) REFERENCES worker_session_placements(session_id) ON DELETE CASCADE\n) STRICT;\n\n-- Tool-call ids are idempotency keys only within one exact source turn claim.\n-- A running operation from another Gateway instance is ambiguous and is never\n-- replayed. A persisted random seed separates durable downstream identities\n-- from Gateway authentication keys and survives ordinary process restarts.\nCREATE TABLE IF NOT EXISTS worker_session_tool_operations (\n source_session_id TEXT NOT NULL,\n source_claim_id TEXT NOT NULL,\n tool_call_id TEXT NOT NULL,\n tool_name TEXT NOT NULL CHECK (tool_name IN ('sessions_spawn', 'sessions_send')),\n request_digest TEXT NOT NULL,\n operation_seed TEXT NOT NULL,\n status TEXT NOT NULL CHECK (status IN ('running', 'succeeded', 'failed', 'unknown')),\n child_session_key TEXT,\n result_json TEXT,\n gateway_instance_id TEXT NOT NULL,\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n PRIMARY KEY (source_session_id, source_claim_id, tool_call_id),\n FOREIGN KEY (source_session_id)\n REFERENCES worker_session_placements(session_id) ON DELETE CASCADE\n) STRICT;\n\n-- Local sandbox execution is a projection of a session-owned managed worktree.\n-- No cascade: an older binary must not discard pending edits with a session row.\nCREATE TABLE IF NOT EXISTS local_workspace_projections (\n worktree_id TEXT NOT NULL PRIMARY KEY,\n agent_id TEXT NOT NULL,\n session_key TEXT NOT NULL,\n session_id TEXT NOT NULL,\n lifecycle_revision TEXT,\n projection_path TEXT NOT NULL UNIQUE,\n base_commit TEXT NOT NULL,\n source_paths_json TEXT NOT NULL,\n baseline_json TEXT,\n baseline_ref TEXT,\n pending_ref TEXT,\n pending_target TEXT CHECK (pending_target IN ('canonical', 'projection')),\n paused_runtimes_json TEXT,\n journal_json TEXT,\n journal_pack BLOB CHECK (journal_pack IS NULL OR length(journal_pack) <= 268435456),\n revision INTEGER NOT NULL DEFAULT 0 CHECK (revision >= 0),\n created_at_ms INTEGER NOT NULL,\n CHECK ((baseline_json IS NULL) = (baseline_ref IS NULL)),\n CHECK ((journal_json IS NULL) = (journal_pack IS NULL))\n) STRICT;\n\n-- A reconciliation journal is written before managed-worktree mutation. The\n-- bounded Git base snapshot repairs any subset left by an interrupted apply.\nCREATE TABLE IF NOT EXISTS worker_workspace_reconciliations (\n session_id TEXT NOT NULL PRIMARY KEY,\n environment_id TEXT NOT NULL,\n owner_epoch INTEGER NOT NULL CHECK (owner_epoch >= 1),\n placement_generation INTEGER NOT NULL CHECK (placement_generation >= 0),\n base_manifest_ref TEXT NOT NULL,\n current_manifest_ref TEXT NOT NULL,\n plan_json TEXT NOT NULL,\n base_pack BLOB NOT NULL CHECK (length(base_pack) <= 268435456),\n created_at_ms INTEGER NOT NULL,\n FOREIGN KEY (session_id) REFERENCES worker_session_placements(session_id) ON DELETE CASCADE\n) STRICT;\n\n-- A completed remote turn is fenced from stale-claim teardown until its\n-- workspace result is durably reconciled into the managed worktree.\nCREATE TABLE IF NOT EXISTS worker_workspace_pending_results (\n session_id TEXT NOT NULL PRIMARY KEY,\n environment_id TEXT NOT NULL,\n owner_epoch INTEGER NOT NULL CHECK (owner_epoch >= 1),\n placement_generation INTEGER NOT NULL CHECK (placement_generation >= 0),\n claim_id TEXT NOT NULL,\n run_id TEXT NOT NULL,\n gateway_instance_id TEXT NOT NULL,\n recovery_requested_at_ms INTEGER,\n workspace_accepted_at_ms INTEGER,\n staged_result_ref TEXT,\n repository_workspace_id TEXT,\n created_at_ms INTEGER NOT NULL,\n FOREIGN KEY (session_id) REFERENCES worker_session_placements(session_id) ON DELETE CASCADE\n) STRICT;\n\n-- GitHub publication intent records the authoritative session worktree. Cloud\n-- requests execute only after the exact turn claim's result is accepted locally.\n-- Secrets stay in the effective Gateway-owned GitHub profile and never enter\n-- this row or the worker protocol.\nCREATE TABLE IF NOT EXISTS github_publication_requests (\n request_id TEXT NOT NULL PRIMARY KEY,\n idempotency_key TEXT NOT NULL,\n request_digest TEXT NOT NULL,\n session_id TEXT NOT NULL,\n session_key TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n worktree_id TEXT NOT NULL,\n repository_fingerprint TEXT NOT NULL,\n claim_id TEXT,\n run_id TEXT,\n environment_id TEXT,\n owner_epoch INTEGER CHECK (owner_epoch IS NULL OR owner_epoch >= 1),\n placement_generation INTEGER CHECK (\n placement_generation IS NULL OR placement_generation >= 0\n ),\n identity_source TEXT NOT NULL CHECK (\n identity_source IN ('system-detected', 'system-configured', 'agent-override')\n ),\n identity_profile_id TEXT,\n identity_account_id INTEGER NOT NULL CHECK (identity_account_id >= 1),\n identity_login TEXT NOT NULL,\n title TEXT,\n body TEXT,\n status TEXT NOT NULL CHECK (\n status IN ('requested', 'publishing', 'published', 'failed')\n ),\n gateway_instance_id TEXT,\n repository TEXT,\n branch TEXT NOT NULL,\n base_branch TEXT,\n source_head_commit TEXT,\n source_index_tree TEXT,\n workspace_tree TEXT,\n head_commit TEXT,\n pull_request_url TEXT,\n error_code TEXT,\n next_action TEXT,\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n reported_at_ms INTEGER,\n UNIQUE (session_id, idempotency_key),\n CHECK (\n (claim_id IS NULL AND run_id IS NULL AND environment_id IS NULL\n AND owner_epoch IS NULL AND placement_generation IS NULL)\n OR\n (claim_id IS NOT NULL AND run_id IS NOT NULL AND placement_generation IS NOT NULL\n AND ((environment_id IS NULL AND owner_epoch IS NULL)\n OR (environment_id IS NOT NULL AND owner_epoch IS NOT NULL)))\n ),\n CHECK (\n (identity_source IS 'system-detected' AND identity_profile_id IS NULL)\n OR\n (identity_source IN ('system-configured', 'agent-override')\n AND identity_profile_id IS NOT NULL)\n ),\n CHECK (\n (source_head_commit IS NULL AND source_index_tree IS NULL AND workspace_tree IS NULL)\n OR\n (source_head_commit IS NOT NULL AND workspace_tree IS NOT NULL)\n ),\n CHECK (\n (status IS 'published' AND pull_request_url IS NOT NULL AND error_code IS NULL\n AND next_action IS NULL)\n OR\n (status IS 'failed' AND pull_request_url IS NULL AND error_code IS NOT NULL\n AND next_action IS NOT NULL)\n OR\n (status IN ('requested', 'publishing') AND pull_request_url IS NULL\n AND error_code IS NULL AND next_action IS NULL)\n )\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_github_publication_requests_pending\n ON github_publication_requests(status, updated_at_ms, request_id);\n\n-- Personal requests cannot be interpreted or resumed by older shared publishers.\nCREATE TABLE IF NOT EXISTS github_personal_publication_requests (\n request_id TEXT NOT NULL PRIMARY KEY CHECK (length(request_id) = 36),\n owner_profile_id TEXT NOT NULL CHECK (length(owner_profile_id) BETWEEN 1 AND 128),\n connection_generation TEXT NOT NULL CHECK (length(connection_generation) = 36),\n idempotency_key TEXT NOT NULL CHECK (length(idempotency_key) BETWEEN 1 AND 128),\n request_digest TEXT NOT NULL CHECK (length(request_digest) = 64),\n session_id TEXT NOT NULL CHECK (length(session_id) BETWEEN 1 AND 128),\n session_key TEXT NOT NULL CHECK (length(session_key) BETWEEN 1 AND 1024),\n agent_id TEXT NOT NULL CHECK (length(agent_id) BETWEEN 1 AND 128),\n worktree_id TEXT NOT NULL CHECK (length(worktree_id) BETWEEN 1 AND 128),\n repository_fingerprint TEXT NOT NULL CHECK (length(repository_fingerprint) BETWEEN 1 AND 256),\n identity_source TEXT NOT NULL CHECK (identity_source = 'personal'),\n identity_profile_id TEXT NOT NULL CHECK (length(identity_profile_id) = 36),\n identity_account_id INTEGER NOT NULL CHECK (identity_account_id >= 1),\n identity_login TEXT NOT NULL CHECK (length(identity_login) BETWEEN 1 AND 39),\n title TEXT CHECK (title IS NULL OR length(title) BETWEEN 1 AND 256),\n body TEXT CHECK (body IS NULL OR length(body) BETWEEN 1 AND 8192),\n status TEXT NOT NULL CHECK (status IN ('requested', 'publishing', 'needs_confirmation', 'published', 'failed')),\n gateway_instance_id TEXT CHECK (gateway_instance_id IS NULL OR length(gateway_instance_id) BETWEEN 1 AND 128),\n execution_id TEXT CHECK (execution_id IS NULL OR length(execution_id) = 36),\n last_effect TEXT CHECK (last_effect IS NULL OR last_effect IN ('push', 'pull_request')),\n effect_state TEXT CHECK (effect_state IS NULL OR effect_state IN ('dispatched', 'observed')),\n push_repository TEXT NOT NULL CHECK (length(push_repository) BETWEEN 3 AND 256),\n repository TEXT NOT NULL CHECK (length(repository) BETWEEN 3 AND 256),\n branch TEXT NOT NULL CHECK (length(branch) BETWEEN 1 AND 256),\n base_branch TEXT NOT NULL CHECK (length(base_branch) BETWEEN 1 AND 256),\n source_head_commit TEXT NOT NULL CHECK (length(source_head_commit) IN (40, 64)),\n source_index_tree TEXT NOT NULL CHECK (length(source_index_tree) IN (40, 64)),\n workspace_tree TEXT NOT NULL CHECK (length(workspace_tree) IN (40, 64)),\n head_commit TEXT CHECK (head_commit IS NULL OR length(head_commit) IN (40, 64)),\n pull_request_url TEXT CHECK (pull_request_url IS NULL OR length(pull_request_url) BETWEEN 1 AND 2048),\n error_code TEXT CHECK (error_code IS NULL OR length(error_code) BETWEEN 1 AND 64),\n next_action TEXT CHECK (next_action IS NULL OR length(next_action) BETWEEN 1 AND 1024),\n created_at_ms INTEGER NOT NULL CHECK (created_at_ms >= 0),\n updated_at_ms INTEGER NOT NULL CHECK (updated_at_ms >= created_at_ms),\n reported_at_ms INTEGER,\n UNIQUE (owner_profile_id, session_id, idempotency_key),\n CHECK ((status = 'publishing' AND gateway_instance_id IS NOT NULL AND execution_id IS NOT NULL) OR status <> 'publishing'),\n CHECK ((last_effect IS NULL AND effect_state IS NULL) OR (last_effect IS NOT NULL AND effect_state IS NOT NULL)),\n CHECK ((status = 'published' AND pull_request_url IS NOT NULL AND head_commit IS NOT NULL AND error_code IS NULL AND next_action IS NULL)\n OR (status = 'failed' AND error_code IS NOT NULL AND next_action IS NOT NULL)\n OR (status IN ('requested', 'publishing', 'needs_confirmation') AND error_code IS NULL AND next_action IS NULL))\n) STRICT;\nCREATE INDEX IF NOT EXISTS idx_github_personal_publication_owner_session\n ON github_personal_publication_requests(owner_profile_id, session_id, created_at_ms);\nCREATE INDEX IF NOT EXISTS idx_github_personal_publication_pending\n ON github_personal_publication_requests(status, updated_at_ms, request_id);\n\n-- Older readers validate both local receipt tables exactly. Their immutable\n-- lifecycle binding stays in a first-use companion so those schemas remain readable.\nCREATE TABLE IF NOT EXISTS github_publication_session_lifecycles (\n publication_kind TEXT NOT NULL CHECK (publication_kind IN ('shared', 'personal')),\n request_id TEXT NOT NULL,\n lifecycle_revision TEXT,\n requester_authority_json TEXT,\n PRIMARY KEY (publication_kind, request_id)\n) STRICT;\n\n-- One active, opaque admission credential per worker environment. Plaintext\n-- may be retried until delivery acknowledgement but never enters durable state.\nCREATE TABLE IF NOT EXISTS worker_environment_credentials (\n environment_id TEXT NOT NULL PRIMARY KEY,\n credential_hash TEXT NOT NULL UNIQUE,\n bundle_hash TEXT NOT NULL,\n session_id TEXT,\n rpc_set_version INTEGER NOT NULL CHECK (rpc_set_version >= 1),\n owner_epoch INTEGER NOT NULL CHECK (owner_epoch >= 0),\n expires_at_ms INTEGER NOT NULL CHECK (expires_at_ms >= 0),\n delivered_at_ms INTEGER CHECK (delivered_at_ms >= 0),\n FOREIGN KEY (environment_id) REFERENCES worker_environments(environment_id) ON DELETE CASCADE\n) STRICT;\n\n-- One durable sequence cursor per attached session owner epoch. The environment\n-- binding prevents independent workers with coincident epochs from sharing replay state.\nCREATE TABLE IF NOT EXISTS worker_transcript_commit_heads (\n session_id TEXT NOT NULL,\n run_epoch INTEGER NOT NULL CHECK (run_epoch >= 0),\n environment_id TEXT NOT NULL,\n next_seq INTEGER NOT NULL CHECK (next_seq >= 1),\n updated_at_ms INTEGER NOT NULL CHECK (updated_at_ms >= 0),\n PRIMARY KEY (session_id, run_epoch)\n) STRICT;\n\n-- Pending rows preserve a claimed request across gateway restarts. Terminal rows\n-- cache the exact result returned for deterministic at-least-once replay.\nCREATE TABLE IF NOT EXISTS worker_transcript_commits (\n session_id TEXT NOT NULL,\n run_epoch INTEGER NOT NULL CHECK (run_epoch >= 0),\n seq INTEGER NOT NULL CHECK (seq >= 1),\n request_hash TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('pending', 'terminal')),\n result_json TEXT,\n created_at_ms INTEGER NOT NULL CHECK (created_at_ms >= 0),\n updated_at_ms INTEGER NOT NULL CHECK (updated_at_ms >= 0),\n PRIMARY KEY (session_id, run_epoch, seq),\n FOREIGN KEY (session_id, run_epoch)\n REFERENCES worker_transcript_commit_heads(session_id, run_epoch)\n ON DELETE CASCADE,\n CHECK (\n (state = 'pending' AND result_json IS NULL) OR\n (state = 'terminal' AND result_json IS NOT NULL)\n )\n) STRICT;\n\n-- Pending rows preserve a claimed inference turn across gateway restarts.\n-- Terminal rows cache the exact outcome returned for deterministic replay.\nCREATE TABLE IF NOT EXISTS worker_inference_turns (\n session_id TEXT NOT NULL,\n run_epoch INTEGER NOT NULL CHECK (run_epoch >= 0),\n run_id TEXT NOT NULL,\n turn_id TEXT NOT NULL,\n environment_id TEXT NOT NULL,\n request_hash TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('pending', 'terminal')),\n terminal_json TEXT,\n created_at_ms INTEGER NOT NULL CHECK (created_at_ms >= 0),\n updated_at_ms INTEGER NOT NULL CHECK (updated_at_ms >= 0),\n PRIMARY KEY (session_id, run_epoch, run_id, turn_id),\n FOREIGN KEY (environment_id) REFERENCES worker_environments(environment_id) ON DELETE CASCADE,\n CHECK (\n (state = 'pending' AND terminal_json IS NULL) OR\n (state = 'terminal' AND terminal_json IS NOT NULL)\n )\n) STRICT;\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_worker_inference_turns_pending_run\n ON worker_inference_turns(session_id, run_epoch, run_id)\n WHERE state = 'pending';\n\nCREATE TABLE IF NOT EXISTS fleet_cells (\n tenant_id TEXT NOT NULL PRIMARY KEY,\n created_at_ms INTEGER NOT NULL,\n image TEXT NOT NULL,\n runtime TEXT NOT NULL,\n host_port INTEGER NOT NULL,\n container_name TEXT NOT NULL,\n data_dir TEXT NOT NULL\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS claw_installs (\n agent_id TEXT NOT NULL PRIMARY KEY,\n schema_version TEXT NOT NULL,\n source_kind TEXT NOT NULL,\n claw_name TEXT NOT NULL,\n claw_version TEXT NOT NULL,\n package_root TEXT NOT NULL,\n manifest_path TEXT NOT NULL,\n integrity_kind TEXT NOT NULL,\n integrity TEXT NOT NULL,\n source_byte_length INTEGER NOT NULL,\n manifest_schema_version INTEGER NOT NULL,\n plan_integrity TEXT NOT NULL,\n workspace TEXT NOT NULL UNIQUE,\n agent_config_digest TEXT NOT NULL,\n agent_owned_paths_json TEXT NOT NULL,\n bootstrap_source_path TEXT,\n bootstrap_content_digest TEXT,\n status TEXT NOT NULL CHECK (\n status IN ('pending', 'workspace_ready', 'config_committed', 'complete', 'partial')\n ),\n added_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS claw_workspace_files (\n agent_id TEXT NOT NULL,\n target_path TEXT NOT NULL,\n schema_version TEXT NOT NULL,\n workspace TEXT NOT NULL,\n source_path TEXT NOT NULL,\n content_digest TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n PRIMARY KEY (agent_id, target_path)\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS claw_package_refs (\n agent_id TEXT NOT NULL,\n package_kind TEXT NOT NULL,\n package_source TEXT NOT NULL,\n package_ref TEXT NOT NULL,\n package_version TEXT NOT NULL,\n package_integrity TEXT NOT NULL,\n schema_version TEXT NOT NULL,\n claw_name TEXT NOT NULL,\n package_status TEXT NOT NULL,\n relationship TEXT NOT NULL CHECK (relationship IN ('managed', 'referenced')),\n origin TEXT NOT NULL CHECK (origin IN ('claw-introduced', 'pre-existing')),\n independent_owner INTEGER NOT NULL CHECK (independent_owner IN (0, 1)),\n extension_id TEXT,\n extension_format TEXT,\n extension_detected_format TEXT,\n extension_mapped_json TEXT,\n extension_unavailable_json TEXT,\n extension_adapter_identity TEXT,\n installed_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n PRIMARY KEY (agent_id, package_kind, package_source, package_ref, package_version)\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS claw_cron_refs (\n agent_id TEXT NOT NULL,\n manifest_id TEXT NOT NULL,\n schema_version TEXT NOT NULL,\n declaration_key TEXT NOT NULL UNIQUE,\n scheduler_job_id TEXT UNIQUE,\n status TEXT NOT NULL,\n job_json TEXT NOT NULL,\n error TEXT,\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n PRIMARY KEY (agent_id, manifest_id)\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS claw_mcp_server_refs (\n agent_id TEXT NOT NULL,\n name TEXT NOT NULL,\n schema_version TEXT NOT NULL,\n config_digest TEXT NOT NULL,\n relationship TEXT NOT NULL CHECK (relationship IN ('managed', 'referenced')),\n origin TEXT NOT NULL CHECK (origin IN ('claw-introduced', 'pre-existing')),\n independent_owner INTEGER NOT NULL DEFAULT 0 CHECK (independent_owner IN (0, 1)),\n status TEXT NOT NULL,\n error TEXT,\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n PRIMARY KEY (agent_id, name)\n) STRICT;\n\nCREATE TABLE IF NOT EXISTS user_profile_identities (\n provider TEXT NOT NULL,\n subject TEXT NOT NULL,\n profile_id TEXT NOT NULL,\n canonical_login TEXT,\n created_at INTEGER NOT NULL,\n authorization_id TEXT,\n authorization_basis_json TEXT,\n PRIMARY KEY (provider, subject)\n) STRICT;\nCREATE INDEX IF NOT EXISTS idx_user_profile_identities_profile_id\n ON user_profile_identities(profile_id);\nCREATE UNIQUE INDEX IF NOT EXISTS idx_user_profile_identities_authorization\n ON user_profile_identities(authorization_id);\n\nCREATE TABLE IF NOT EXISTS outbound_media_provenance (\n realpath TEXT NOT NULL PRIMARY KEY,\n kind TEXT NOT NULL,\n version INTEGER NOT NULL,\n sha256 TEXT NOT NULL,\n size_bytes INTEGER NOT NULL,\n created_at_ms INTEGER NOT NULL\n) STRICT;\n\n-- scope_id is non-null because SQLite treats NULLs as distinct in unique indexes/PKs,\n-- which would allow duplicate team rows. This PK also avoids a rebuild for identity scope.\nCREATE TABLE IF NOT EXISTS secret_store_entries (\n scope_kind TEXT NOT NULL CHECK (scope_kind IN ('team', 'identity')),\n scope_id TEXT NOT NULL,\n name TEXT NOT NULL,\n value TEXT NOT NULL,\n kind TEXT NOT NULL CHECK (kind IN ('secret', 'env')),\n created_at_ms INTEGER NOT NULL CHECK (created_at_ms >= 0),\n updated_at_ms INTEGER NOT NULL CHECK (updated_at_ms >= 0),\n updated_by TEXT,\n deleted_at_ms INTEGER,\n allowed_hosts TEXT,\n CHECK ((scope_kind = 'team' AND scope_id = '') OR (scope_kind = 'identity' AND length(scope_id) > 0)),\n PRIMARY KEY (scope_kind, scope_id, name)\n) STRICT;\nCREATE INDEX IF NOT EXISTS secret_store_entries_live_idx\n ON secret_store_entries (scope_kind, scope_id, name) WHERE deleted_at_ms IS NULL;\n";
//#endregion
//#region src/state/openclaw-state-db-operator-approval-migration.ts
function tableSql$1(db) {
const row = db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'operator_approvals'").get();
return typeof row?.sql === "string" ? row.sql : void 0;
}
function hasCanonicalOperatorApprovalKinds(db) {
if (!tableExists(db, "operator_approvals")) return true;
return /kind\s+text\s+not\s+null\s+check\s*\(\s*kind\s+in\s*\(\s*'exec'\s*,\s*'plugin'\s*,\s*'system-agent'\s*\)\s*\)/.test(tableSql$1(db)?.toLowerCase() ?? "");
}
function assertCanonicalOperatorApprovalKinds(db, pathname) {
if (!hasCanonicalOperatorApprovalKinds(db)) throw new SqliteSchemaMismatchError(`OpenClaw state database ${pathname} has a legacy operator approval schema; run openclaw doctor --fix to migrate it.`);
}
function isCanonicalOperatorApprovalKind(value) {
return value === "exec" || value === "plugin" || value === "system-agent";
}
//#endregion
//#region src/state/openclaw-state-db-legacy-backfills.ts
function ensureOperatorApprovalResolutionRefs(db) {
if (!tableExists(db, "operator_approvals")) return;
runSqliteImmediateTransactionSync(db, () => {
ensureColumn(db, "operator_approvals", "resolution_ref TEXT");
const rows = db.prepare("SELECT approval_id, kind, resolution_ref FROM operator_approvals").all();
const update = db.prepare("UPDATE operator_approvals SET resolution_ref = ? WHERE approval_id = ?");
for (const row of rows) {
if (typeof row.approval_id !== "string" || !isCanonicalOperatorApprovalKind(row.kind)) throw new Error("operator approval row cannot be assigned a transport reference");
const resolutionRef = buildApprovalResolutionRef({
approvalId: row.approval_id,
approvalKind: row.kind
});
if (row.resolution_ref !== resolutionRef) update.run(resolutionRef, row.approval_id);
}
if (db.prepare(`SELECT canonical.approval_id
FROM operator_approvals AS canonical
JOIN operator_approvals AS referenced
ON canonical.approval_id = referenced.resolution_ref
WHERE canonical.approval_id <> referenced.approval_id
LIMIT 1`).get()) throw new Error("operator approval ids conflict with durable transport references");
db.exec(`
CREATE UNIQUE INDEX IF NOT EXISTS idx_operator_approvals_resolution_ref
ON operator_approvals(resolution_ref);
`);
});
}
function nullableTextValue(record, key) {
if (!record || !Object.hasOwn(record, key)) return;
const value = record[key];
return typeof value === "string" || value === null ? value : void 0;
}
/** Promote shipped retained results before runtime hydrates canonical subagent state. */
function repairLegacySubagentRetainedResults(db) {
if (!tableExists(db, "subagent_runs")) return;
const repair = () => {
const hasLegacyPendingPayload = tableHasColumn(db, "subagent_runs", "pending_final_delivery_payload_json");
const rows = db.prepare(hasLegacyPendingPayload ? "SELECT run_id, payload_json, pending_final_delivery_payload_json FROM subagent_runs" : "SELECT run_id, payload_json FROM subagent_runs").all();
const updateRun = db.prepare(`UPDATE subagent_runs
SET payload_json = ?
WHERE run_id = ?`);
for (const row of rows) {
const stored = safeParseJsonRecord(row.payload_json) ?? null;
const parent = stored ? asNullableRecord(stored.parentCompletion) : null;
const payload = parent?.completionTarget === "parent" ? parent : stored;
const completion = payload ? asNullableRecord(payload.completion) : null;
if (!payload || !completion) continue;
const delivery = asNullableRecord(payload.delivery);
const deliveryPayload = delivery ? asNullableRecord(delivery.payload) : null;
const pendingPayload = row.pending_final_delivery_payload_json ? safeParseJsonRecord(row.pending_final_delivery_payload_json) ?? null : null;
if (!Boolean(deliveryPayload && (Object.hasOwn(deliveryPayload, "frozenResultText") || Object.hasOwn(deliveryPayload, "fallbackFrozenResultText")) || pendingPayload && (Object.hasOwn(pendingPayload, "frozenResultText") || Object.hasOwn(pendingPayload, "fallbackFrozenResultText")))) continue;
const legacyPrimary = nullableTextValue(deliveryPayload, "frozenResultText") ?? nullableTextValue(pendingPayload, "frozenResultText");
const legacyFallback = nullableTextValue(deliveryPayload, "fallbackFrozenResultText") ?? nullableTextValue(pendingPayload, "fallbackFrozenResultText");
if (nullableTextValue(completion, "resultText") == null && legacyPrimary !== void 0) completion.resultText = legacyPrimary;
if (nullableTextValue(completion, "fallbackResultText") == null && legacyFallback !== void 0) completion.fallbackResultText = legacyFallback;
delete deliveryPayload?.frozenResultText;
delete deliveryPayload?.fallbackFrozenResultText;
updateRun.run(JSON.stringify(stored), row.run_id);
}
};
if (db.isTransaction) {
repair();
return;
}
runSqliteImmediateTransactionSync(db, repair);
}
/** Canonicalize shipped subagent rows whose pause/kill owner only wrote root terminal fields. */
function repairLegacySubagentExecutionPayloads(db) {
if (!tableExists(db, "subagent_runs")) return;
db.exec(`
UPDATE subagent_runs
SET payload_json = json_remove(
CASE
WHEN json_extract(payload_json, '$.pauseReason') = 'sessions_yield'
AND json_extract(payload_json, '$.execution.status') <> 'terminal'
AND json_type(payload_json, '$.endedAt') IN ('integer', 'real')
THEN json_remove(json_set(
payload_json,
'$.execution.status', 'terminal',
'$.execution.endedAt', json_extract(payload_json, '$.endedAt')
), '$.execution.outcome')
WHEN (json_type(payload_json, '$.killReconciliation') = 'object'
OR json_extract(payload_json, '$.endedReason') = 'subagent-killed')
AND json_extract(payload_json, '$.execution.status') <> 'terminal'
AND json_type(payload_json, '$.endedAt') IN ('integer', 'real')
AND json_type(payload_json, '$.outcome') = 'object'
THEN json_set(
payload_json,
'$.execution.status', 'terminal',
'$.execution.endedAt', json_extract(payload_json, '$.endedAt'),
'$.execution.outcome', json_extract(payload_json, '$.outcome')
)
ELSE payload_json
END,
'$.startedAt', '$.endedAt', '$.outcome'
)
WHERE json_valid(payload_json)
AND (json_type(payload_json, '$.startedAt') IS NOT NULL
OR json_type(payload_json, '$.endedAt') IS NOT NULL
OR json_type(payload_json, '$.outcome') IS NOT NULL);
`);
}
/** Canonicalize the shipped suspension reason before runtime hydrates subagent state. */
function repairLegacySubagentSuspensionReasons(db) {
if (!tableExists(db, "subagent_runs")) return;
db.exec(`
UPDATE subagent_runs
SET payload_json = json_set(payload_json, '$.delivery.suspendedReason', 'permanent_failure')
WHERE json_valid(payload_json)
AND json_extract(payload_json, '$.delivery.suspendedReason') = 'retry-limit';
`);
}
function backfillAcpReplayEstimatedBytes(db) {
if (!tableExists(db, "acp_replay_events") || !tableHasColumn(db, "acp_replay_events", "estimated_bytes")) return;
const replayDb = getNodeSqliteKysely(db);
const updateEvent = db.prepare("UPDATE acp_replay_events SET estimated_bytes = ? WHERE session_id = ? AND seq = ?");
for (const row of iterateSqliteQuerySync(db, replayDb.selectFrom("acp_replay_events").select([
"session_id",
"seq",
"session_key",
"run_id",
"update_json",
"estimated_bytes"
]))) {
const expected = estimateAcpEventRowBytes({
sessionId: row.session_id,
sessionKey: row.session_key,
runId: row.run_id,
updateJson: row.update_json
});
if (coerceRequiredSqliteNumber(row.estimated_bytes) !== expected) updateEvent.run(expected, row.session_id, row.seq);
}
const updateSession = db.prepare("UPDATE acp_replay_sessions SET estimated_bytes = ? WHERE session_id = ?");
for (const row of iterateSqliteQuerySync(db, replayDb.selectFrom("acp_replay_sessions as s").select([
"s.session_id",
"s.session_key",
"s.cwd",
"s.estimated_bytes"
]).select((eb) => eb.fn.coalesce(eb.selectFrom("acp_replay_events as e").select((events) => events.fn.sum("e.estimated_bytes").as("total")).whereRef("e.session_id", "=", "s.session_id"), eb.val(0)).as("event_bytes")))) {
const expected = estimateAcpSessionRowBytes({
sessionId: row.session_id,
sessionKey: row.session_key,
cwd: row.cwd
}) + coerceRequiredSqliteNumber(row.event_bytes);
if (coerceRequiredSqliteNumber(row.estimated_bytes) !== expected) updateSession.run(expected, row.session_id);
}
}
function backfillCronRunLogEntryJson(db) {
if (!tableExists(db, "cron_run_logs") || !tableHasColumn(db, "cron_run_logs", "entry_json")) return;
const rows = db.prepare(`SELECT store_key, job_id, seq, ts
FROM cron_run_logs
WHERE entry_json = '{}'`).all();
if (rows.length === 0) return;
const update = db.prepare(`UPDATE cron_run_logs
SET entry_json = ?
WHERE store_key = ? AND job_id = ? AND seq = ?`);
for (const row of rows) update.run(JSON.stringify({
ts: coerceRequiredSqliteNumber(row.ts),
jobId: row.job_id,
action: "finished"
}), row.store_key, row.job_id, row.seq);
}
function textField(record, key) {
const value = record[key];
return typeof value === "string" && value.trim() ? value : null;
}
function backfillCronJobsFromJobJson(db) {
if (!tableExists(db, "cron_jobs") || !tableHasColumn(db, "cron_jobs", "job_json") || !tableHasColumn(db, "cron_jobs", "payload_kind")) return;
const rows = db.prepare(`SELECT store_key, job_id, job_json, updated_at
FROM cron_jobs
WHERE payload_kind = 'message'
OR name = ''`).all();
if (rows.length === 0) return;
const update = db.prepare(`UPDATE cron_jobs
SET name = ?,
enabled = ?,
agent_id = ?,
payload_kind = ?,
runtime_updated_at_ms = ?
WHERE store_key = ?
AND job_id = ?`);
for (const row of rows) {
const job = safeParseJsonRecord(row.job_json) ?? null;
if (!job) continue;
const schedule = asNullableRecord(job.schedule);
const payload = asNullableRecord(job.payload);
const scheduleKind = textField(schedule ?? {}, "kind");
const payloadKind = textField(payload ?? {}, "kind");
const isAt = scheduleKind === "at" && textField(schedule ?? {}, "at");
const isEvery = scheduleKind === "every" && asFiniteNumber((schedule ?? {}).everyMs) != null;
const isCron = scheduleKind === "cron" && textField(schedule ?? {}, "expr");
const isSystemEvent = payloadKind === "systemEvent" && textField(payload ?? {}, "text");
const isAgentTurn = payloadKind === "agentTurn" && textField(payload ?? {}, "message");
if (!schedule || !payload || !isAt && !isEvery && !isCron || !isSystemEvent && !isAgentTurn) continue;
update.run(textField(job, "name") ?? row.job_id, job.enabled === false ? 0 : 1, textField(job, "agentId"), payloadKind, asFiniteNumber(job.updatedAtMs) ?? (coerceRequiredSqliteNumber(row.updated_at) || 0), row.store_key, row.job_id);
}
}
const STANDING_GRANT_GENERATION_TABLE = "operator_approval_standing_grant_generations";
/**
* Binds live standing grants minted before definition generations existed
* (2026.8.x and earlier) to their job's first generation. Run only in the
* schema transaction that installs the cron_jobs generation projection: until
* then no generation-aware writer touched the database. Afterward, a grant
* without a companion row stays legacy and fails closed, so an edit restored
* after upgrade cannot revive it.
*
* Binding grants no new authority. Consumption still requires the job's current
* definition to match the config revision recorded at mint, the check earlier
* releases enforced, and any later substantive edit advances the generation.
* The projection hashes the stored job_json the way the cron codec does; a job
* whose stored JSON the current codec would canonicalize differently fails
* closed to prompting instead of matching.
*/
function carryForwardLegacyCronStandingGrants(db, nowMs) {
if (!tableExists(db, "operator_approval_standing_grants") || !tableExists(db, "cron_jobs") || tableExists(db, STANDING_GRANT_GENERATION_TABLE)) return;
const stateDb = getNodeSqliteKysely(db);
const legacyGrants = executeSqliteQuerySync(db, stateDb.selectFrom("operator_approval_standing_grants").select(["grant_id", "cron_job_id"]).where("revoked_at_ms", "is", null).where((eb) => eb.or([eb("expires_at_ms", "is", null), eb("expires_at_ms", ">", nowMs)]))).rows;
if (legacyGrants.length === 0) return;
db.exec(extractSqliteTableSchema(OPENCLAW_STATE_SCHEMA_SQL, STANDING_GRANT_GENERATION_TABLE));
for (const jobId of new Set(legacyGrants.map((grant) => grant.cron_job_id))) {
const jobRows = executeSqliteQuerySync(db, stateDb.selectFrom("cron_jobs").select([
"store_key",
"job_json",
"updated_at"
]).where("job_id", "=", jobId).limit(2)).rows;
const definition = jobRows.length === 1 ? safeParseJsonRecord(jobRows[0].job_json) : null;
if (!definition) continue;
const { enabled: _enabled, state: _state, ...grantDefinition } = definition;
executeSqliteQuerySync(db, stateDb.updateTable("cron_jobs").set({
grant_definition_revision: hashCronJobDefinition(grantDefinition),
grant_definition_generation: 1,
grant_definition_updated_at: jobRows[0].updated_at
}).where("store_key", "=", jobRows[0].store_key).where("job_id", "=", jobId));
executeSqliteQuerySync(db, stateDb.insertInto(STANDING_GRANT_GENERATION_TABLE).values(legacyGrants.filter((grant) => grant.cron_job_id === jobId).map((grant) => ({
grant_id: grant.grant_id,
job_definition_generation: 1
}))));
}
}
function backfillDeliveryQueueEntriesFromEntryJson(db) {
if (!tableExists(db, "delivery_queue_entries") || !tableHasColumn(db, "delivery_queue_entries", "entry_json") || !tableHasColumn(db, "delivery_queue_entries", "retry_count")) return;
compactLegacyDeliveryQueueFailures(db);
const rows = db.prepare(`SELECT queue_name, id, entry_json
FROM delivery_queue_entries
WHERE status = 'pending'
AND (retry_count = 0
OR last_attempt_at IS NULL
OR last_error IS NULL
OR recovery_state IS NULL
OR platform_send_started_at IS NULL
OR entry_kind IS NULL
OR session_key IS NULL
OR channel IS NULL
OR target IS NULL
OR account_id IS NULL)`).all();
if (rows.length === 0) return;
const update = db.prepare(`UPDATE delivery_queue_entries
SET entry_kind = COALESCE(?, entry_kind),
session_key = COALESCE(?, session_key),
channel = COALESCE(?, channel),
target = COALESCE(?, target),
account_id = COALESCE(?, account_id),
retry_count = ?,
last_attempt_at = COALESCE(?, last_attempt_at),
last_error = COALESCE(?, last_error),
recovery_state = COALESCE(?, recovery_state),
platform_send_started_at = COALESCE(?, platform_send_started_at)
WHERE queue_name = ?
AND id = ?`);
for (const row of rows) {
const entry = safeParseJsonRecord(row.entry_json) ?? null;
if (!entry) continue;
const session = asNullableRecord(entry.session);
const route = asNullableRecord(entry.route);
const deliveryContext = asNullableRecord(entry.deliveryContext);
update.run(textField(entry, "kind"), textField(entry, "sessionKey") ?? (session ? textField(session, "key") : null), textField(entry, "channel") ?? (route ? textField(route, "channel") : null) ?? (deliveryContext ? textField(deliveryContext, "channel") : null), textField(entry, "to") ?? (route ? textField(route, "to") : null) ?? (deliveryContext ? textField(deliveryContext, "to") : null), textField(entry, "accountId") ?? (route ? textField(route, "accountId") : null) ?? (deliveryContext ? textField(deliveryContext, "accountId") : null), asSafeIntegerInRange(entry.retryCount, { min: 0 }) ?? 0, asSafeIntegerInRange(entry.lastAttemptAt, { min: 0 }) ?? null, textField(entry, "lastError"), textField(entry, "recoveryState"), asSafeIntegerInRange(entry.platformSendStartedAt, { min: 0 }) ?? null, row.queue_name, row.id);
}
}
//#endregion
//#region src/state/openclaw-state-db-schema-v13-widerow.ts
const FAILURE_DESTINATION_COLUMNS = [
["failure_delivery_mode", "mode"],
["failure_delivery_channel", "channel"],
["failure_delivery_to", "to"],
["failure_delivery_account_id", "accountId"]
];
function reprojectLegacyCronJson(db) {
const projectionColumns = FAILURE_DESTINATION_COLUMNS.map(([columnName]) => tableHasColumn(db, "cron_jobs", columnName) ? quoteSqliteIdentifier(columnName) : `NULL AS ${quoteSqliteIdentifier(columnName)}`);
const lastRunStatus = tableHasColumn(db, "cron_jobs", "last_run_status") ? "last_run_status" : "NULL AS last_run_status";
const rows = db.prepare(`SELECT store_key, job_id, enabled, job_json, state_json, ${lastRunStatus}, ${projectionColumns.join(", ")}
FROM cron_jobs`).all();
const update = db.prepare("UPDATE cron_jobs SET job_json = ?, state_json = ? WHERE store_key = ? AND job_id = ?");
for (const row of rows) {
if (typeof row.store_key !== "string" || typeof row.job_id !== "string" || typeof row.job_json !== "string" || typeof row.state_json !== "string") throw new Error("OpenClaw v12 cron job row is not canonical");
const job = asNullableRecord(safeParseJson(row.job_json));
const state = asNullableRecord(safeParseJson(row.state_json));
if (!job || !state) continue;
let changed = false;
const delivery = asNullableRecord(job.delivery);
const destination = asNullableRecord(delivery?.failureDestination);
if ((!Object.hasOwn(job, "delivery") || delivery !== null) && (!delivery || !Object.hasOwn(delivery, "failureDestination") || destination !== null)) {
const nextDelivery = delivery ?? {};
const nextDestination = destination ?? {};
for (const [columnName, fieldName] of FAILURE_DESTINATION_COLUMNS) {
const value = row[columnName];
if (typeof value !== "string" || Object.hasOwn(nextDestination, fieldName)) continue;
nextDestination[fieldName] = value === "" ? null : value;
changed = true;
}
if (changed) {
nextDelivery.failureDestination = nextDestination;
job.delivery = nextDelivery;
}
}
if (typeof job.enabled !== "boolean") {
job.enabled = row.enabled !== 0;
changed = true;
}
const hasLegacyStatus = Object.hasOwn(state, "lastStatus");
if (!Object.hasOwn(state, "lastRunStatus") && (hasLegacyStatus || typeof row.last_run_status === "string")) {
state.lastRunStatus = hasLegacyStatus ? state.lastStatus : row.last_run_status;
changed = true;
}
if (changed) update.run(JSON.stringify(job), JSON.stringify(state), row.store_key, row.job_id);
}
}
function rebuildJsonCanonicalTable(db, tableName) {
const migrationTable = `${tableName}_migration_v13`;
if (tableExists(db, migrationTable)) throw new Error(`OpenClaw v13 migration table already exists: ${migrationTable}`);
const migrationSchema = extractSqliteTableSchema(OPENCLAW_STATE_SCHEMA_SQL, tableName, { errorMessage: `Canonical ${tableName} schema block is missing` }).replace(`CREATE TABLE IF NOT EXISTS ${tableName} (`, `CREATE TABLE ${migrationTable} (`);
db.exec(migrationSchema);
const columns = db.prepare(`PRAGMA table_xinfo(${migrationTable})`).all().flatMap((column) => column.hidden === 0 && typeof column.name === "string" ? [column.name] : []);
const projection = columns.map((columnName) => {
return CLAW_LAZY_ADDITIVE_STATE_COLUMN_DEFINITIONS.some((column) => column.tableName === tableName && column.columnName === columnName) && !tableHasColumn(db, tableName, columnName) ? "NULL" : quoteSqliteIdentifier(columnName);
});
db.exec(`INSERT INTO ${migrationTable} (${columns.map(quoteSqliteIdentifier).join(", ")}) SELECT ${projection.join(", ")} FROM ${tableName};`);
db.exec(`DROP TABLE ${tableName};`);
db.exec(`ALTER TABLE ${migrationTable} RENAME TO ${tableName};`);
}
/** Fold obsolete physical projections into canonical JSON before removing their columns. */
function migrateJsonCanonicalWideRowsV13(db, previousVersion) {
if (previousVersion >= 13) return false;
let migrated = false;
if (tableExists(db, "cron_jobs") && tableHasColumn(db, "cron_jobs", "schedule_kind")) {
reprojectLegacyCronJson(db);
rebuildJsonCanonicalTable(db, "cron_jobs");
migrated = true;
}
const hasSetupState = tableExists(db, "workspace_setup_state");
const hasAttestations = tableExists(db, "workspace_attestations");
if (hasSetupState && !tableHasColumn(db, "workspace_setup_state", "attested_at_ms")) {
db.exec("ALTER TABLE workspace_setup_state ADD COLUMN attested_at_ms INTEGER;");
db.exec("ALTER TABLE workspace_setup_state ADD COLUMN attestation_updated_at_ms INTEGER;");
rebuildJsonCanonicalTable(db, "workspace_setup_state");
migrated = true;
}
if (hasAttestations) {
db.exec(`
UPDATE workspace_setup_state
SET attested_at_ms = (
SELECT attested_at_ms FROM workspace_attestations
WHERE workspace_attestations.workspace_key = workspace_setup_state.workspace_key
),
attestation_updated_at_ms = (
SELECT updated_at_ms FROM workspace_attestations
WHERE workspace_attestations.workspace_key = workspace_setup_state.workspace_key
)
WHERE workspace_key IN (SELECT workspace_key FROM workspace_attestations);
`);
const workspacePath = tableExists(db, "workspace_path_aliases") ? `(SELECT alias.workspace_path FROM workspace_path_aliases alias
WHERE alias.workspace_key = a.workspace_key LIMIT 1)` : "NULL";
db.exec(`
INSERT INTO workspace_setup_state (
workspace_key, workspace_path, attested_at_ms, attestation_updated_at_ms
)
SELECT a.workspace_key,
${workspacePath},
a.attested_at_ms,
a.updated_at_ms
FROM workspace_attestations a
WHERE a.workspace_key NOT IN (SELECT workspace_key FROM workspace_setup_state);
`);
db.exec("DROP TABLE workspace_attestations;");
migrated = true;
}
if ((hasSetupState || hasAttestations) && tableExists(db, "workspace_generated_bootstrap_hashes")) {
rebuildJsonCanonicalTable(db, "workspace_generated_bootstrap_hashes");
db.exec(`
DELETE FROM workspace_generated_bootstrap_hashes
WHERE workspace_key NOT IN (SELECT workspace_key FROM workspace_setup_state);
`);
}
for (const [tableName, jsonColumn, stateKey] of [[
"auth_profile_stores",
"store_json",
"authProfiles.store"
], [
"auth_profile_state",
"state_json",
"authProfiles.state"
]]) {
if (!tableExists(db, tableName)) continue;
db.prepare(`INSERT INTO config_machine_state (state_key, value_json, updated_at_ms)
SELECT ?, ${jsonColumn}, updated_at FROM ${tableName} WHERE store_key = 'shared'
ON CONFLICT(state_key) DO NOTHING`).run(stateKey);
db.exec(`DROP TABLE ${tableName};`);
migrated = true;
}
if (tableExists(db, "installed_plugin_index")) {
const row = db.prepare("SELECT * FROM installed_plugin_index WHERE index_key = 'installed-plugin-index'").get();
if (row) {
const installRecords = asNullableRecord(safeParseJson(String(row.install_records_json)));
const plugins = safeParseJson(String(row.plugins_json));
const diagnostics = safeParseJson(String(row.diagnostics_json));
if (!installRecords || !Array.isArray(plugins) || !Array.isArray(diagnostics)) {
const scope = "plugins.installedIndex.quarantine";
const message = `Preserved invalid legacy installed_plugin_index row in diagnostic_events (${scope}). Run openclaw doctor --fix or openclaw plugins registry --refresh to repair the plugin index; inspect the preserved row if install records need recovery.`;
createSqliteAuditRecordKernel(db, {
scope,
maxEntries: Number.MAX_SAFE_INTEGER
}).register(prepareSqliteAuditRecord(scope, {
key: createHash("sha256").update(JSON.stringify(row)).digest("hex"),
value: {
level: "warn",
message,
raw: row
},
createdAt: Date.now()
}));
deferSqlitePostCommitPublication(db, () => createSubsystemLogger("state/db").warn(message));
}
const index = {
version: Number(row.version),
...typeof row.warning === "string" && row.warning ? { warning: row.warning } : {},
hostContractVersion: row.host_contract_version,
compatRegistryVersion: row.compat_registry_version,
migrationVersion: Number(row.migration_version),
policyHash: row.policy_hash,
generatedAtMs: Number(row.generated_at_ms),
...typeof row.workspace_dir === "string" ? { workspaceDir: row.workspace_dir } : {},
...typeof row.refresh_reason === "string" && row.refresh_reason ? { refreshReason: row.refresh_reason } : {},
installRecords,
plugins,
diagnostics
};
db.prepare(`INSERT INTO config_machine_state (state_key, value_json, updated_at_ms)
VALUES (?, ?, ?) ON CONFLICT(state_key) DO NOTHING`).run("plugins.installedIndex", JSON.stringify({
revision: Number(row.updated_at_ms),
index
}), Number(row.updated_at_ms));
}
db.exec("DROP TABLE installed_plugin_index;");
migrated = true;
}
if (tableExists(db, "subagent_runs") && tableHasColumn(db, "subagent_runs", "task")) {
repairLegacySubagentRetainedResults(db);
rebuildJsonCanonicalTable(db, "subagent_runs");
migrated = true;
}
return migrated;
}
//#endregion
//#region src/infra/sqlite-files.ts
/** SQLite main database plus every journal-mode sidecar that can contain database pages. */
const SQLITE_DATABASE_FILE_SUFFIXES = [
"",
"-wal",
"-shm",
"-journal"
];
SQLITE_DATABASE_FILE_SUFFIXES.slice(1);
const SQLITE_WAL_HEADER_BYTES = 32;
Buffer.from([
0,
5,
22,
7
]);
const sqliteFilesLog = createSubsystemLogger("state/sqlite");
var SqliteOrphanedSidecarsError = class extends Error {
constructor(pathname, sidecarPaths, cause) {
super(`SQLite database is missing at ${pathname}, and orphaned sidecars could not be copied: ${sidecarPaths.join(", ")}. Refusing to open because SQLite could delete orphan WAL or journal state. Preserve the sidecar bytes, restore the main database, and pair it with the matching sidecar before retrying.`, { cause });
this.name = "SqliteOrphanedSidecarsError";
}
};
/** Resolves the main database and all possible journal-mode sidecar paths. */
function resolveSqliteDatabaseFilePaths(pathname) {
return SQLITE_DATABASE_FILE_SUFFIXES.map((suffix) => `${pathname}${suffix}`);
}
function findMatchingOrphanedSidecarCopy(sourcePath, sourceSize) {
const directory = path.dirname(sourcePath);
const prefix = `${path.basename(sourcePath)}.orphaned-`;
const candidates = fsSync.readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.startsWith(prefix)).map((entry) => path.join(directory, entry.name)).filter((candidate) => fsSync.statSync(candidate).size === sourceSize);
if (candidates.length === 0) return;
const sourceHash = sha256FileSync$1(sourcePath);
for (const candidate of candidates) if (sha256FileSync$1(candidate) === sourceHash) return candidate;
}
function copyOrphanedSidecar(sourcePath, epochMs) {
const basePath = `${sourcePath}.orphaned-${epochMs}`;
for (let suffix = 0;; suffix += 1) {
const candidate = suffix === 0 ? basePath : `${basePath}-${suffix}`;
try {
fsSync.copyFileSync(sourcePath, candidate, fsSync.constants.COPYFILE_EXCL);
return candidate;
} catch (error) {
if (error.code !== "EEXIST") throw error;
}
}
}
/** Preserve durable orphan sidecars before SQLite creates a replacement main database. */
function quarantineOrphanedSqliteSidecars(pathname) {
if (fsSync.existsSync(pathname)) return;
const sidecars = [{
path: `${pathname}-wal`,
minimumBytes: SQLITE_WAL_HEADER_BYTES
}, {
path: `${pathname}-journal`,
minimumBytes: 0
}].flatMap((sidecar) => {
const stat = fsSync.statSync(sidecar.path, { throwIfNoEntry: false });
return stat?.isFile() === true && stat.size > sidecar.minimumBytes ? [{
path: sidecar.path,
size: stat.size
}] : [];
});
if (sidecars.length === 0) return;
const epochMs = Date.now();
const copied = [];
try {
for (const sidecar of sidecars) {
if (findMatchingOrphanedSidecarCopy(sidecar.path, sidecar.size)) continue;
const quarantinePath = copyOrphanedSidecar(sidecar.path, epochMs);
copied.push({
quarantinePath,
sourcePath: sidecar.path
});
}
} catch (error) {
throw new SqliteOrphanedSidecarsError(pathname, sidecars.map((sidecar) => sidecar.path), error);
}
if (copied.length === 0) return;
const copies = copied.map(({ sourcePath, quarantinePath }) => `${sourcePath} -> ${quarantinePath}`);
sqliteFilesLog.warn(`SQLite database is missing at ${pathname}; copied orphaned sidecars: ${copies.join(", ")}. Committed frames could not be applied because the main database is missing. The bytes are preserved. Recovery requires restoring the main database and pairing it with the quarantined file.`, {
databasePath: pathname,
copiedSidecars: copied
});
}
//#endregion
//#region src/infra/state-database-maintenance.ts
const StateSchemaMutationConflictError = resolveGlobalSingleton(Symbol.for("openclaw.stateSchemaMutationConflictError"), () => class SchemaMutationConflictError extends Error {
constructor(databasePath, cause) {
super(`OpenClaw refused shared state schema mutation at ${databasePath} because another Gateway owns that state directory. Stop that Gateway or perform the update through its managed restart path, then retry.`, { cause });
this.name = "StateSchemaMutationConflictError";
}
});
/** Ordinary transactions use SQLite; schema changes retain the installation's process owner. */
function withStateDatabaseSchemaMaintenance({ databasePath, busyTimeoutMs }, operation) {
if (requestSqliteWorkerSchemaMaintenance(databasePath)) return operation();
const canonical = resolveIdentityPathViaExistingAncestorSync(databasePath);
const inherited = getStateDatabaseSchemaLease(canonical);
if (inherited) {
inherited.assertCurrent();
return operation();
}
let lease;
try {
lease = acquireStateDatabaseSchemaLease(canonical, { busyTimeoutMs });
} catch (error) {
if (error instanceof GatewayStateOwnerContentionError) throw new StateSchemaMutationConflictError(canonical, error);
throw error;
}
return runWithSqliteCleanup(lease, "state schema mutation", () => lease.run(operation));
}
//#endregion
//#region src/state/openclaw-state-ownership.ts
const STATE_SUPERVISION_KEY = "gateway.supervision";
const MAX_OWNERSHIP_TIMESTAMP_MS = 864e13;
const MANAGER_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
function isOpenClawStateWriteContentionError(error) {
return error instanceof GatewayStateOwnerContentionError || error instanceof StateSchemaMutationConflictError || isSqliteLockError(error);
}
function parseExternalOwnership(valueJson, databasePath) {
let value;
try {
value = JSON.parse(valueJson);
} catch {
throw new OpenClawStateOwnershipMetadataError(databasePath, "reserved value is not valid JSON");
}
const record = isRecord(value) ? value : void 0;
const keys = record ? Object.keys(record).toSorted().join(",") : "";
const managerId = record?.managerId;
const claimedAt = record?.claimedAt;
if (keys !== "claimedAt,managerId,mode,version" || record?.version !== 1 || record?.mode !== "external" || typeof managerId !== "string" || !MANAGER_ID_PATTERN.test(managerId) || typeof claimedAt !== "number" || !Number.isSafeInteger(claimedAt) || claimedAt < 0 || claimedAt > MAX_OWNERSHIP_TIMESTAMP_MS) throw new OpenClawStateOwnershipMetadataError(databasePath, "reserved value does not match the version 1 external ownership contract");
return {
version: 1,
mode: "external",
managerId,
claimedAt
};
}
/** Inspect the reserved ownership row without entering the shared-state lifecycle. */
function inspectOpenClawStateOwnershipFromDatabase(database, databasePath, configMachineStateTableReady = false) {
try {
if (!configMachineStateTableReady && !tableExists(database, "config_machine_state")) return null;
const ownershipSql = configMachineStateTableReady ? "SELECT value_json FROM config_machine_state WHERE state_key = ? LIMIT 1" : "SELECT value_json FROM config_machine_state NOT INDEXED WHERE state_key = ? LIMIT 1";
const row = database.prepare(ownershipSql).get(STATE_SUPERVISION_KEY);
if (!row) return null;
if (typeof row.value_json !== "string") throw new OpenClawStateOwnershipMetadataError(databasePath, "reserved value is not text");
return parseExternalOwnership(row.value_json, databasePath);
} catch (error) {
throw normalizeOpenClawStateSchemaReadError(error, databasePath);
}
}
function assertOwnershipAllowsWrite(status, databasePath, env) {
if (status && !isGatewayExternallySupervised(env)) throw new OpenClawStateExternalOwnershipError(databasePath, status.managerId);
}
/** Fence shared-state writes once an external manager has claimed ownership. */
function assertOpenClawStateWriteAllowed(options) {
const resolvedPath = path.resolve(options.databasePath);
assertStateDatabaseAccessAllowed(resolvedPath);
assertOwnershipAllowsWrite(inspectOpenClawStateOwnershipFromDatabase(options.database, resolvedPath, options.schemaReady), resolvedPath, options.env ?? process.env);
}
//#endregion
//#region src/state/openclaw-state-schema-compatibility.ts
const CLAW_LAZY_ADDITIVE_STATE_COLUMNS = CLAW_LAZY_ADDITIVE_STATE_COLUMN_DEFINITIONS.map(({ columnName, tableName }) => `${tableName}.${columnName}`);
const CLAW_FIRST_USE_ADDITIVE_STATE_COLUMNS = CLAW_FIRST_USE_ADDITIVE_STATE_COLUMN_DEFINITIONS.map(({ columnName, tableName }) => `${tableName}.${columnName}`);
new Set(CLAW_FIRST_USE_ADDITIVE_STATE_COLUMNS);
const CLAW_STARTUP_ADDITIVE_STATE_COLUMN_SET = /* @__PURE__ */ new Set([...CLAW_STARTUP_ADDITIVE_STATE_COLUMN_DEFINITIONS.map(({ columnName, tableName }) => `${tableName}.${columnName}`), ...Object.values(ORDERED_STARTUP_ADDITIVE_STATE_COLUMNS).flat().map(([tableName, definition]) => `${tableName}.${definition.split(" ", 1)[0]}`)]);
const CLAW_STARTUP_ADDITIVE_STATE_TABLES = ["worker_session_tool_operations", "worker_turn_tool_authorities"];
const CLAW_STARTUP_ADDITIVE_STATE_TABLE_SET = new Set(CLAW_STARTUP_ADDITIVE_STATE_TABLES);
const CLAW_READONLY_OPTIONAL_STATE_INDEXES = [
"idx_operator_approvals_source_run_resolved",
"idx_task_runs_requester_session_key",
"idx_worker_session_placements_environment"
];
let openClawStateCanonicalNamedIndexSet;
function getOpenClawStateCanonicalNamedIndexSet() {
openClawStateCanonicalNamedIndexSet ??= new Set(getCanonicalSqliteNamedIndexContracts(OPENCLAW_STATE_SCHEMA_SQL).map((index) => index.name));
return openClawStateCanonicalNamedIndexSet;
}
const runtimeSchemaCache = /* @__PURE__ */ new Map();
/** Project canonical SQL to the tables the shared runtime may create during this open. */
function getOpenClawStateRuntimeSchema(options) {
const { includeVersionLazyAdditiveTables, includeAgentDeletionJournal = true } = options;
const key = `${includeVersionLazyAdditiveTables}:${includeAgentDeletionJournal}`;
const cached = runtimeSchemaCache.get(key);
if (cached !== void 0) return cached;
let schema = OPENCLAW_STATE_SCHEMA_SQL;
const omittedTables = [...includeVersionLazyAdditiveTables ? FIRST_USE_STATE_TABLES : LAZY_ADDITIVE_STATE_TABLES, ...includeAgentDeletionJournal ? [] : DOCTOR_OWNED_STATE_TABLES];
const omittedIndexes = includeVersionLazyAdditiveTables ? FIRST_USE_STATE_INDEXES : LAZY_ADDITIVE_STATE_INDEXES;
for (const tableName of omittedTables) {
const start = schema.indexOf(`CREATE TABLE IF NOT EXISTS ${tableName} (`);
const end = start >= 0 ? schema.indexOf("\n) STRICT;", start) : -1;
if (start < 0 || end < 0) throw new Error(`lazy additive state schema block is missing for ${tableName}`);
schema = `${schema.slice(0, start)}${schema.slice(end + 10)}`;
}
for (const indexName of omittedIndexes) {
const plainStart = schema.indexOf(`CREATE INDEX IF NOT EXISTS ${indexName}`);
const uniqueStart = schema.indexOf(`CREATE UNIQUE INDEX IF NOT EXISTS ${indexName}`);
const start = plainStart >= 0 ? plainStart : uniqueStart;
const end = start >= 0 ? schema.indexOf(";", start) : -1;
if (start < 0 || end < 0) throw new Error(`lazy additive state schema index is missing for ${indexName}`);
schema = `${schema.slice(0, start)}${schema.slice(end + 1)}`;
}
runtimeSchemaCache.set(key, schema);
return schema;
}
const STATE_PERSISTENT_SCHEMA_COMPATIBILITY = {
allowCompatibleAdditiveColumns: true,
allowedMissingTables: DOCTOR_OWNED_STATE_TABLES,
allowedMissingColumns: CLAW_FIRST_USE_ADDITIVE_STATE_COLUMNS,
allowedColumnDefinitions: {
"diagnostic_events.sequence": ["sequence INTEGER NOT NULL DEFAULT 0"],
"claw_package_refs.package_integrity": ["package_integrity TEXT NOT NULL DEFAULT 'sha256:0000000000000000000000000000000000000000000000000000000000000000'"],
"claw_package_refs.updated_at_ms": ["updated_at_ms INTEGER NOT NULL DEFAULT 0"],
"cron_jobs.enabled": ["enabled INTEGER NOT NULL DEFAULT 1"],
"cron_jobs.name": ["name TEXT NOT NULL DEFAULT ''"],
"cron_jobs.payload_kind": ["payload_kind TEXT NOT NULL DEFAULT 'message'"],
"current_conversation_bindings.conversation_kind": ["conversation_kind TEXT NOT NULL DEFAULT 'channel'"],
"operator_approvals.resolution_ref": ["resolution_ref TEXT"],
"worker_environments.desktop_json": ["desktop_json TEXT"],
"worker_environments.bootstrap_install_kind": ["bootstrap_install_kind TEXT"],
"worker_environments.shared_host": ["shared_host INTEGER CHECK (shared_host IN (0, 1))"],
"worker_environments.node_setup_id": ["node_setup_id TEXT"],
"worker_environments.node_device_id": ["node_device_id TEXT"],
"worker_session_placements.terminal_reason": ["terminal_reason TEXT"],
"worker_session_placements.terminal_at_ms": ["terminal_at_ms INTEGER"]
}
};
const OPENCLAW_STATE_MAINTENANCE_SCHEMA_COMPATIBILITY = {
...STATE_PERSISTENT_SCHEMA_COMPATIBILITY,
allowedMissingTables: [
...LAZY_ADDITIVE_STATE_TABLES,
...CLAW_STARTUP_ADDITIVE_STATE_TABLES,
...DOCTOR_OWNED_STATE_TABLES
],
allowedMissingIndexes: CLAW_READONLY_OPTIONAL_STATE_INDEXES,
allowedMissingColumns: CLAW_LAZY_ADDITIVE_STATE_COLUMNS
};
/** Identify schema differences that the writable shared-state cold open repairs. */
function isOpenClawStateStartupRepairableSchemaIssue(issue) {
if (issue.code === "missing-table") return CLAW_STARTUP_ADDITIVE_STATE_TABLE_SET.has(issue.objectName);
if (issue.code === "missing-column") return CLAW_STARTUP_ADDITIVE_STATE_COLUMN_SET.has(issue.objectName);
return issue.code === "missing-or-drifted-index" && getOpenClawStateCanonicalNamedIndexSet().has(issue.objectName);
}
//#endregion
//#region src/infra/update-run-timeouts.ts
/** Shared legacy-reader grace and inactive update reconciliation threshold. */
const ABANDONED_UPDATE_RUN_MS = 18e5;
//#endregion
//#region src/state/openclaw-state-schema-publication.ts
const TERMINAL_GRACE_MS = 3e5;
/** Only the 2026.9.2 release line reopens the ledger without the transaction fence. */
function isUnfencedUpdateDriver(version) {
const parsed = typeof version === "string" ? parse(version) : null;
return parsed !== null && `${parsed.major}.${parsed.minor}.${parsed.patch}` === "2026.9.2";
}
/** Every unfenced driver must clear its own deadline; a newer run cannot hide an older one. */
function readStateSchemaPublicationBlocker(db, nowMs = Date.now()) {
if (!tableExists(db, "update_runs")) return;
const rows = executeSqliteQuerySync(db, getNodeSqliteKysely(db).selectFrom("update_runs").select([
"run_id",
"before_json",
"status",
"updated_at_ms",
"finished_at_ms"
]).where((eb) => eb.or([eb.and([eb("status", "=", "running"), eb("updated_at_ms", ">=", nowMs - ABANDONED_UPDATE_RUN_MS)]), eb.and([eb("status", "!=", "running"), eb.or([eb("finished_at_ms", ">", nowMs - TERMINAL_GRACE_MS), eb("finished_at_ms", "is", null)])])])).orderBy("run_id")).rows;
let blocker;
for (const row of rows) {
const before = JSON.parse(row.before_json);
if (!isRecord(before) || typeof before.version !== "string" || !isUnfencedUpdateDriver(before.version)) continue;
const deadline = row.status === "running" ? row.updated_at_ms + ABANDONED_UPDATE_RUN_MS + 1 : row.finished_at_ms === null ? null : row.finished_at_ms + TERMINAL_GRACE_MS;
if (!blocker || deadline === null || blocker.publishAfterMs !== null && deadline > blocker.publishAfterMs) blocker = {
runId: row.run_id,
updaterVersion: before.version,
publishAfterMs: deadline
};
}
return blocker;
}
/** Called inside the schema write transaction, after all content migrations succeed. */
function resolveStateSchemaVersionToPublish(db) {
const published = readSqliteUserVersion(db);
if (published >= 19 || !readStateSchemaPublicationBlocker(db)) return 19;
if (!tableExists(db, "config_machine_state")) throw new Error("Shared state schema publication cannot be deferred without config_machine_state.");
executeSqliteQuerySync(db, getNodeSqliteKysely(db).insertInto("config_machine_state").values({
state_key: CONTENT_VERSION_KEY,
value_json: String(19),
updated_at_ms: Date.now()
}).onConflict((conflict) => conflict.column("state_key").doUpdateSet({
value_json: String(19),
updated_at_ms: Date.now()
}).where("config_machine_state.value_json", "!=", String(19))));
invalidateSqliteSchemaFacts(db);
return published;
}
//#endregion
//#region src/state/openclaw-update-schema-refusal.ts
/** An unfenced updater needs a manual update when safe publication deferral is unavailable. */
var UpdateSchemaRefusalError = class extends Error {
constructor(databases, updaterVersion, options) {
const { targetVersion } = options;
const commands = options.recovery?.commands ?? [
"openclaw gateway stop",
`npm install -g openclaw@${targetVersion} --allow-scripts=openclaw`,
"openclaw doctor --fix",
"openclaw gateway start"
];
const reason = options.cause === void 0 ? "" : ` Deferral failed: ${formatErrorMessage(options.cause).slice(0, 600)}.`;
const summary = `Doctor refused update-time schema repair driven by OpenClaw ${updaterVersion}: this updater reopens the ledger with old code after migration, and version publication could not be deferred safely.`;
const unchanged = `${databases.map((database) => `${database.kind} database ${database.path}: on-disk schema ${database.foundVersion}, this build's schema ${database.supportedVersion}.`).join(" ")}${reason} The blocked schema change was not applied.`;
super(options.recovery ? `${summary} ${unchanged}\n${options.recovery.message}` : [
summary,
unchanged,
"Let the updater restore the previous package and exit. Then use an independent shell with the original service account, package prefix, profile, and state/config overrides.",
`Manual update: ${commands.join(" && ")}.`,
`Use the package manager that owns this install (pnpm: pnpm add -g --allow-build=openclaw openclaw@${targetVersion}; Bun: bun add -g --trust openclaw@${targetVersion}). On npm 11.15 and earlier, omit --allow-scripts=openclaw.`
].join("\n"), options);
this.databases = databases;
this.updaterVersion = updaterVersion;
this.code = "update-schema-bump-unfenced";
this.name = "UpdateSchemaRefusalError";
this.targetVersion = targetVersion;
this.commands = commands;
}
};
//#endregion
//#region src/state/openclaw-state-db-maintenance.ts
const STATE_V6_ADDITIVE_TABLES = [
"gateway_origin_device_tokens",
...LAZY_ADDITIVE_STATE_TABLES,
"worker_session_tool_operations",
"worker_turn_tool_authorities"
];
const STATE_MIGRATION_ALLOWED_MISSING_TABLES = {
5: [
"agent_database_leases",
"agent_deletion_journal",
"claw_cron_refs",
"claw_installs",
"claw_mcp_server_refs",
"claw_package_refs",
"claw_workspace_files",
"config_machine_state",
"cron_job_scratch",
"meeting_transcript_sessions",
"meeting_transcript_summaries",
"meeting_transcript_utterances",
"outbound_media_provenance",
"worker_environment_credentials",
"worker_transcript_commit_heads",
"worker_transcript_commits",
...STATE_V6_ADDITIVE_TABLES
],
6: STATE_V6_ADDITIVE_TABLES,
7: STATE_V6_ADDITIVE_TABLES,
8: STATE_V6_ADDITIVE_TABLES,
9: STATE_V6_ADDITIVE_TABLES,
10: STATE_V6_ADDITIVE_TABLES,
11: STATE_V6_ADDITIVE_TABLES,
12: STATE_V6_ADDITIVE_TABLES,
13: LAZY_ADDITIVE_STATE_TABLES,
14: LAZY_ADDITIVE_STATE_TABLES,
15: LAZY_ADDITIVE_STATE_TABLES,
16: LAZY_ADDITIVE_STATE_TABLES,
17: LAZY_ADDITIVE_STATE_TABLES
};
/** Require canonical shared-state ownership without requiring the latest schema. */
function assertOpenClawStateDatabaseOwner(database, options) {
const hasMetadataTable = database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'schema_meta' LIMIT 1").get();
let metadata;
try {
metadata = hasMetadataTable ? database.prepare("SELECT role, schema_version FROM schema_meta WHERE meta_key = 'primary' LIMIT 1").get() : void 0;
} catch (error) {
throw classifySqliteTableReadError(database, "schema_meta", [
"meta_key",
"role",
"schema_version"
], error);
}
if (metadata?.role !== "global") {
const role = typeof metadata?.role === "string" ? metadata.role : "missing";
throw new SqliteSchemaMismatchError(`OpenClaw state database ${options.pathname} has schema role ${role}; expected global. Run openclaw doctor --fix to inspect and repair its ownership.`);
}
return metadata;
}
/** Require the canonical shared-state owner and schema before offline file maintenance. */
function assertOpenClawStateDatabaseForMaintenance(database, options, readTable) {
const userVersion = assertSupportedStateSchemaVersion(database, options.pathname);
if (readStateSchemaContentVersion(database) !== 19) throw new SqliteSchemaMismatchError(`OpenClaw state database ${options.pathname} uses schema version ${userVersion}; run openclaw doctor --fix before compacting it.`);
const metadata = assertOpenClawStateDatabaseOwner(database, options);
if (metadata?.schema_version !== userVersion) {
const schemaVersion = typeof metadata?.schema_version === "number" ? metadata.schema_version : "invalid";
throw new SqliteSchemaMismatchError(`OpenClaw state database ${options.pathname} metadata schema version ${schemaVersion} does not match ${userVersion}; run openclaw doctor --fix before compacting it.`);
}
assertSqliteSchemaContains(database, options.pathname, OPENCLAW_STATE_SCHEMA_SQL, OPENCLAW_STATE_MAINTENANCE_SCHEMA_COMPATIBILITY, readTable);
}
function assertOpenClawStateDatabaseVersionForMigration(database, options) {
const userVersion = readSqliteUserVersion(database);
if (readStateSchemaMigrationVersion(database) !== options.version) throw new SqliteSchemaMismatchError(`OpenClaw state database ${options.pathname} uses schema version ${userVersion}; expected ${options.version} before migrating it.`);
const metadata = assertOpenClawStateDatabaseOwner(database, options);
if (metadata?.schema_version !== userVersion) {
const schemaVersion = typeof metadata?.schema_version === "number" ? metadata.schema_version : "invalid";
throw new SqliteSchemaMismatchError(`OpenClaw state database ${options.pathname} metadata schema version ${schemaVersion} does not match ${userVersion}; repair the ownership metadata before migrating it.`);
}
assertSqliteSchemaTablesPresent(database, options.pathname, OPENCLAW_STATE_SCHEMA_SQL, { allowedMissingTables: [...STATE_MIGRATION_ALLOWED_MISSING_TABLES[options.version], ...DOCTOR_OWNED_STATE_TABLES] });
}
/** Keep historical migration gates beside their version-specific ownership assertions. */
const openClawStateMigrationAssertions = new Map([
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17
].map((version) => [version, (database, options) => assertOpenClawStateDatabaseVersionForMigration(database, {
...options,
version
})]));
function markCurrentStateSchemaVersion(db, options = {}) {
if (!tableExists(db, "audit_events")) return;
const version = resolveStateSchemaVersionToPublish(db);
db.exec(`PRAGMA user_version = ${version};`);
if (tableExists(db, "schema_meta") && [
"meta_key",
"schema_version",
"updated_at"
].every((column) => tableHasColumn(db, "schema_meta", column))) {
const now = Date.now();
if (options.createMetadataIfMissing) {
db.prepare(`INSERT INTO schema_meta (
meta_key, role, schema_version, agent_id, app_version, created_at, updated_at
) VALUES ('primary', 'global', ?, NULL, NULL, ?, ?)
ON CONFLICT(meta_key) DO UPDATE SET
schema_version = excluded.schema_version,
updated_at = excluded.updated_at`).run(version, now, now);
return;
}
db.prepare("UPDATE schema_meta SET schema_version = ?, updated_at = ? WHERE meta_key = 'primary'").run(version, now);
}
}
function resolveDatabasePath(options = {}) {
return path.resolve(options.path ?? resolveOpenClawStateSqlitePath(options.env ?? process.env));
}
/** Historical jobs lost the creator's origin; preserve attribution without guessing authority. */
function migrateCronCreatorNamespaces(db, previousVersion) {
if (previousVersion >= 14 || !tableExists(db, "cron_jobs")) return false;
db.exec(`
UPDATE cron_jobs
SET job_json = json_set(job_json, '$.createdActor.source', 'unknown')
WHERE json_valid(job_json)
AND json_extract(job_json, '$.createdActor.type') = 'human';
`);
return true;
}
/** Keep opaque plugin targets independent of agent identity without rewriting binding records. */
function migrateConversationBindingTargets(db, previousVersion) {
if (previousVersion >= 15) return false;
const columns = ["target_agent_id", "target_session_id"].filter((column) => tableHasColumn(db, "current_conversation_bindings", column));
if (columns.length === 0) return false;
db.exec("DROP INDEX IF EXISTS idx_current_conversation_bindings_target;");
for (const column of columns) db.exec(`ALTER TABLE current_conversation_bindings DROP COLUMN ${column};`);
return true;
}
/** Add preparation and activation facts without rebuilding the referenced environment table. */
function migratePreparedWorkerOwnership(db, previousVersion) {
if (previousVersion >= 17 || !tableExists(db, "worker_environments")) return false;
const start = OPENCLAW_STATE_SCHEMA_SQL.indexOf("CREATE TABLE IF NOT EXISTS worker_environments (");
const end = OPENCLAW_STATE_SCHEMA_SQL.indexOf("\n) STRICT;", start);
if (start < 0 || end < start) throw new Error("OpenClaw worker environment schema marker is missing.");
const columns = splitSqlList(OPENCLAW_STATE_SCHEMA_SQL.slice(start + 48, end)).map((column) => column.trim()).filter((column) => column.startsWith("last_activated_at_ms ") || column.startsWith("preparation_"));
let changed = false;
for (const column of columns) changed = ensureColumn(db, "worker_environments", column) || changed;
return changed;
}
/** Historical publication rows retain unknown requesters; first use still owns absent tables. */
function migrateGitHubPublicationRequesterAuthority(db, previousVersion) {
if (previousVersion >= 18) return false;
let changed = false;
for (const table of ["github_publication_session_lifecycles", "github_repository_publication_requests"]) if (tableExists(db, table)) changed = ensureColumn(db, table, "requester_authority_json TEXT") || changed;
return changed;
}
const RELEASED_WORKSHOP_CLAIM_REASON = "Skill Workshop released this skill in a collection review; the path stays user-owned.";
function migrateSkillWorkshopCollectionReviewOwnership(db) {
const retainedObjects = db.prepare(`
SELECT sql FROM sqlite_schema
WHERE tbl_name = 'skill_workshop_collection_reviews'
AND type IN ('index', 'trigger') AND sql IS NOT NULL
AND name NOT IN ('idx_skill_workshop_collection_reviews_workspace_time',
'idx_skill_workshop_collection_reviews_owner_time')
ORDER BY type, name
`).all();
db.exec(`
CREATE TABLE skill_workshop_collection_reviews_v16 (
review_id TEXT NOT NULL PRIMARY KEY,
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;
`);
if (tableExists(db, "skill_workshop_proposals")) db.exec(`
INSERT INTO skill_workshop_collection_reviews_v16 (
review_id, owner_agent_id, backup_id, create_time,
kept_names_json, written_names_json, dropped_json
)
SELECT review.review_id,
(
SELECT MIN(proposal.owner_agent_id)
FROM skill_workshop_proposals AS proposal
WHERE proposal.workspace_dir = review.workspace_dir
AND proposal.owner_agent_id IS NOT NULL
AND (
SELECT COUNT(DISTINCT owner_agent_id)
FROM skill_workshop_proposals AS matching
WHERE matching.workspace_dir = review.workspace_dir
AND matching.owner_agent_id IS NOT NULL
) = 1
),
review.backup_id,
review.create_time,
review.kept_names_json,
review.written_names_json,
review.dropped_json
FROM skill_workshop_collection_reviews AS review
WHERE (
SELECT COUNT(DISTINCT proposal.owner_agent_id)
FROM skill_workshop_proposals AS proposal
WHERE proposal.workspace_dir = review.workspace_dir
AND proposal.owner_agent_id IS NOT NULL
) = 1;
`);
db.exec(`
DROP TABLE skill_workshop_collection_reviews;
ALTER TABLE skill_workshop_collection_reviews_v16
RENAME TO skill_workshop_collection_reviews;
CREATE INDEX idx_skill_workshop_collection_reviews_owner_time
ON skill_workshop_collection_reviews(owner_agent_id, create_time DESC, review_id);
`);
for (const object of retainedObjects) if (typeof object.sql === "string") db.exec(object.sql);
}
/** Remove row provenance after the Workshop directory becomes the ownership boundary. */
function migrateSkillWorkshopDirectoryOwnership(db, previousVersion) {
if (previousVersion >= 16) return false;
const proposalColumns = ["workspace_dir", "claim_released_time"].filter((column) => tableHasColumn(db, "skill_workshop_proposals", column));
const reviewHasWorkspace = tableHasColumn(db, "skill_workshop_collection_reviews", "workspace_dir");
if (proposalColumns.length === 0 && !reviewHasWorkspace) return false;
if (proposalColumns.includes("claim_released_time")) {
const released = db.prepare("SELECT proposal_id, record_json FROM skill_workshop_proposals WHERE claim_released_time IS NOT NULL").all();
if (released.length > 0) {
const staleAt = (/* @__PURE__ */ new Date()).toISOString();
const update = db.prepare(`UPDATE skill_workshop_proposals
SET record_json = ?, status = 'stale', updated_at = ?, stale_at = ?, status_reason = ?
WHERE proposal_id = ?`);
for (const row of released) {
const staleRecord = {
...JSON.parse(row.record_json),
status: "stale",
updatedAt: staleAt,
staleAt,
statusReason: RELEASED_WORKSHOP_CLAIM_REASON
};
update.run(JSON.stringify(staleRecord), staleAt, staleAt, RELEASED_WORKSHOP_CLAIM_REASON, row.proposal_id);
}
}
}
if (reviewHasWorkspace) migrateSkillWorkshopCollectionReviewOwnership(db);
for (const column of proposalColumns) db.exec(`ALTER TABLE skill_workshop_proposals DROP COLUMN ${column};`);
return true;
}
/** Version-gated column and row migrations, oldest first; each runs inside the caller's schema transaction. */
const versionedStateMigrations = [
{
migrate: migrateJsonCanonicalWideRowsV13,
applied: "Consolidated shared state tables (v13)"
},
{
migrate: migrateCronCreatorNamespaces,
applied: "Qualified historical cron creator attribution as unknown (v14)"
},
{
migrate: migrateConversationBindingTargets,
applied: "Removed redundant conversation binding target projections (v15)"
},
{
migrate: migrateSkillWorkshopDirectoryOwnership,
applied: "Moved Skill Workshop ownership to per-agent directories (v16)"
},
{
migrate: migratePreparedWorkerOwnership,
applied: "Recorded prepared worker ownership and one-use lifecycle (v17)"
},
{
migrate: migrateGitHubPublicationRequesterAuthority,
applied: "Added original requester authority to GitHub publication receipts (v18)"
}
];
function runStateSchemaMigrationTransaction(db, pathname, migrate, transactionOptions, prepareSchema) {
const foreignKeysWereEnabled = Number(db.prepare("PRAGMA foreign_keys").get()?.foreign_keys) === 1;
if (foreignKeysWereEnabled) db.exec("PRAGMA foreign_keys = OFF;");
try {
return runSqliteImmediateTransactionSync(db, () => {
prepareSchema?.();
const publishedVersion = readSqliteUserVersion(db);
const blocker = publishedVersion < 19 ? readStateSchemaPublicationBlocker(db) : void 0;
if (!blocker) return migrate();
try {
if (!tableExists(db, "config_machine_state")) throw new Error("Shared state schema publication requires config_machine_state.");
return migrate();
} catch (cause) {
if (cause instanceof OpenClawStateOwnershipError) throw cause;
throw new UpdateSchemaRefusalError([{
kind: "state",
path: pathname,
foundVersion: publishedVersion,
supportedVersion: 19
}], blocker.updaterVersion, {
targetVersion: VERSION,
cause
});
}
}, transactionOptions);
} finally {
if (foreignKeysWereEnabled && db.isOpen) db.exec("PRAGMA foreign_keys = ON;");
}
}
function writeCurrentStateSchemaMetadata(db, now) {
const kysely = getNodeSqliteKysely(db);
const schemaVersion = resolveStateSchemaVersionToPublish(db);
db.exec(`PRAGMA user_version = ${schemaVersion};`);
executeSqliteQuerySync(db, kysely.insertInto("schema_meta").values({
meta_key: "primary",
role: "global",
schema_version: schemaVersion,
agent_id: null,
app_version: VERSION,
created_at: now,
updated_at: now
}).onConflict((conflict) => conflict.column("meta_key").doUpdateSet({
role: "global",
schema_version: schemaVersion,
agent_id: null,
app_version: VERSION,
updated_at: now
}).where((eb) => eb.or([
eb("schema_meta.schema_version", "!=", schemaVersion),
eb("schema_meta.app_version", "is not", VERSION),
eb("schema_meta.role", "!=", "global")
]))));
}
function executeCanonicalStateSchema(database, options) {
database.exec(getOpenClawStateRuntimeSchema({
...options,
includeAgentDeletionJournal: options.includeAgentDeletionJournal ?? tableExists(database, "agent_deletion_journal")
}));
}
//#endregion
//#region src/state/openclaw-state-db-audit-migration.ts
const AUDIT_EVENT_STATE_SCHEMA_VERSION = 2;
const AUDIT_EVENT_LEGACY_COLUMNS = [
"sequence",
"event_id",
"source_id",
"source_sequence",
"occurred_at",
"kind",
"action",
"status",
"error_code",
"actor_type",
"actor_id",
"agent_id",
"session_key",
"session_id",
"run_id",
"tool_call_id",
"tool_name"
];
const AUDIT_EVENT_V2_COLUMNS = [
"sequence",
"event_id",
"source_id",
"schema_version",
"source_sequence",
"occurred_at",
"kind",
"action",
"status",
"error_code",
"actor_type",
"actor_id",
"agent_id",
"session_key",
"session_id",
"run_id",
"tool_call_id",
"tool_name",
"direction",
"channel",
"conversation_kind",
"message_outcome",
"reason_code",
"delivery_kind",
"failure_stage",
"duration_ms",
"result_count",
"account_ref",
"conversation_ref",
"message_ref",
"target_ref"
];
function tableColumnInfo(db, tableName) {
return db.prepare(`PRAGMA table_info(${tableName})`).all();
}
function tableHasExactColumns(db, tableName, expected) {
const names = tableColumnInfo(db, tableName).map((column) => column.name);
return names.length === expected.length && names.every((name, index) => name === expected[index]);
}
function tableHasRequiredColumns(db, tableName, required) {
const columns = new Map(tableColumnInfo(db, tableName).map((column) => [column.name, column]));
return required.every((name) => Number(columns.get(name)?.notnull ?? 0) === 1);
}
function tableSql(db, tableName) {
const row = db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?").get(tableName);
return typeof row?.sql === "string" ? row.sql : void 0;
}
function tableHasUniqueColumn(db, tableName, columnName) {
return db.prepare(`PRAGMA index_list(${tableName})`).all().some((index) => {
if (Number(index.unique ?? 0) !== 1 || typeof index.name !== "string") return false;
const escaped = index.name.replaceAll("'", "''");
const columns = db.prepare(`PRAGMA index_info('${escaped}')`).all();
return columns.length === 1 && columns[0]?.name === columnName;
});
}
function hasCanonicalAuditEventTable(db, expectedColumns, requiredColumns) {
const sql = tableSql(db, "audit_events")?.toLowerCase();
return tableHasExactColumns(db, "audit_events", expectedColumns) && tablePrimaryKeyColumns(db, "audit_events").join(",") === "sequence" && tableHasRequiredColumns(db, "audit_events", requiredColumns) && typeof sql === "string" && /\bsequence\s+integer\s+primary\s+key\s+autoincrement\b/.test(sql) && tableHasUniqueColumn(db, "audit_events", "event_id") && tableHasUniqueColumn(db, "audit_events", "source_id");
}
function hasCanonicalAuditIdentityKeyTable(db) {
if (!tableExists(db, "audit_identity_keys")) return false;
const sql = tableSql(db, "audit_identity_keys")?.toLowerCase();
return tableHasExactColumns(db, "audit_identity_keys", [
"id",
"key_id",
"key",
"created_at"
]) && tablePrimaryKeyColumns(db, "audit_identity_keys").join(",") === "id" && tableHasRequiredColumns(db, "audit_identity_keys", [
"id",
"key_id",
"key",
"created_at"
]) && typeof sql === "string" && /\bcheck\s*\(\s*id\s*=\s*1\s*\)/.test(sql);
}
function hasCanonicalAuditEventsSchema(db) {
if (!tableExists(db, "audit_events")) return readSqliteUserVersion(db) < AUDIT_EVENT_STATE_SCHEMA_VERSION && !tableExists(db, "audit_identity_keys");
return hasCanonicalAuditEventTable(db, AUDIT_EVENT_V2_COLUMNS, [
"event_id",
"source_id",
"schema_version",
"source_sequence",
"occurred_at",
"kind",
"action",
"status",
"actor_type",
"actor_id"
]) && hasCanonicalAuditIdentityKeyTable(db);
}
function canRepairLegacyAuditEventsSchema(db) {
if (!tableExists(db, "audit_events") || tableExists(db, "audit_events_migration_new") || tableHasColumn(db, "audit_events", "schema_version")) return false;
return (!tableExists(db, "audit_identity_keys") || hasCanonicalAuditIdentityKeyTable(db)) && hasCanonicalAuditEventTable(db, AUDIT_EVENT_LEGACY_COLUMNS, [
"event_id",
"source_id",
"source_sequence",
"occurred_at",
"kind",
"action",
"status",
"actor_type",
"actor_id",
"agent_id",
"run_id"
]);
}
//#endregion
//#region src/state/openclaw-state-db-schema-v12-foldin.ts
const FOLDED_SINGLETON_STATE_TABLES_V12 = [
"skill_curator_state",
"update_check_state",
"clawhub_promotions_feed_state",
"model_catalog_remote",
"voicewake_triggers",
"voicewake_routing_routes",
"voicewake_routing_config",
"onboarding_recommendations",
"cron_store_epochs",
"tui_last_sessions",
"sidebar_sections",
"node_host_config",
"web_push_vapid_keys"
];
function migrateSingletonStateFoldInV12(db, previousVersion) {
if (previousVersion >= 12) return false;
db.exec(`
CREATE TABLE IF NOT EXISTS config_machine_state (
state_key TEXT NOT NULL PRIMARY KEY,
value_json TEXT NOT NULL,
updated_at_ms INTEGER NOT NULL
) STRICT;
`);
const importState = db.prepare("INSERT INTO config_machine_state (state_key, value_json, updated_at_ms) VALUES (?, ?, ?) ON CONFLICT(state_key) DO NOTHING");
if (tableExists(db, "update_check_state")) {
const row = db.prepare("SELECT * FROM update_check_state WHERE state_key = 'default'").get();
if (row) importState.run("update.checkState", JSON.stringify({
lastCheckedAt: row.last_checked_at ?? void 0,
lastNotifiedVersion: row.last_notified_version ?? void 0,
lastNotifiedTag: row.last_notified_tag ?? void 0,
lastAvailableVersion: row.last_available_version ?? void 0,
lastAvailableTag: row.last_available_tag ?? void 0,
autoInstallId: row.auto_install_id ?? void 0,
autoFirstSeenVersion: row.auto_first_seen_version ?? void 0,
autoFirstSeenTag: row.auto_first_seen_tag ?? void 0,
autoFirstSeenAt: row.auto_first_seen_at ?? void 0,
autoLastAttemptVersion: row.auto_last_attempt_version ?? void 0,
autoLastAttemptAt: row.auto_last_attempt_at ?? void 0,
autoLastSuccessVersion: row.auto_last_success_version ?? void 0,
autoLastSuccessAt: row.auto_last_success_at ?? void 0
}), Number(row.updated_at_ms));
}
if (tableExists(db, "voicewake_triggers")) {
const rows = db.prepare("SELECT trigger, updated_at_ms FROM voicewake_triggers WHERE config_key = 'default' ORDER BY position").all();
if (rows.length > 0) importState.run("voicewake.triggers", JSON.stringify(rows.map((row) => row.trigger)), Math.max(...rows.map((row) => Number(row.updated_at_ms))));
}
if (tableExists(db, "voicewake_routing_config")) {
const config = db.prepare("SELECT * FROM voicewake_routing_config WHERE config_key = 'default'").get();
if (config) {
const routes = tableExists(db, "voicewake_routing_routes") ? db.prepare("SELECT trigger, target_mode, target_agent_id, target_session_key FROM voicewake_routing_routes WHERE config_key = 'default' ORDER BY position").all() : [];
const targetFromColumns = (mode, agentId, sessionKey) => mode === "agent" && typeof agentId === "string" && agentId ? { agentId } : mode === "session" && typeof sessionKey === "string" && sessionKey ? { sessionKey } : { mode: "current" };
importState.run("voicewake.routing", JSON.stringify({
version: 1,
defaultTarget: targetFromColumns(config.default_target_mode, config.default_target_agent_id, config.default_target_session_key),
routes: routes.map((route) => ({
trigger: route.trigger,
target: targetFromColumns(route.target_mode, route.target_agent_id, route.target_session_key)
})),
updatedAtMs: config.updated_at_ms
}), Number(config.updated_at_ms));
}
}
if (tableExists(db, "onboarding_recommendations")) {
const rows = db.prepare("SELECT * FROM onboarding_recommendations").all();
for (const row of rows) importState.run(`onboarding.recommendations.${String(row.config_key)}`, JSON.stringify({
inventoryHash: row.inventory_hash,
matches: JSON.parse(String(row.matches_json)),
offeredAt: row.offered_at_ms,
acceptedAt: row.accepted_at_ms,
updatedAt: row.updated_at_ms
}), Number(row.updated_at_ms));
}
if (tableExists(db, "sidebar_sections")) {
const sections = db.prepare("SELECT section_id FROM sidebar_sections ORDER BY position, section_id").all();
if (sections.length > 0) importState.run("sidebar.sectionOrder", JSON.stringify(sections.map((section) => section.section_id)), Date.now());
}
if (tableExists(db, "node_host_config")) {
const nodeHost = db.prepare("SELECT * FROM node_host_config WHERE config_key = 'current'").get();
if (nodeHost) {
const gateway = {
...nodeHost.gateway_host == null ? {} : { host: nodeHost.gateway_host },
...nodeHost.gateway_port == null ? {} : { port: nodeHost.gateway_port },
...nodeHost.gateway_tls == null ? {} : { tls: nodeHost.gateway_tls === 1 },
...nodeHost.gateway_tls_fingerprint == null ? {} : { tlsFingerprint: nodeHost.gateway_tls_fingerprint },
...nodeHost.gateway_context_path == null ? {} : { contextPath: nodeHost.gateway_context_path },
...nodeHost.gateway_cloudflare_access_json == null ? {} : { cloudflareAccess: JSON.parse(String(nodeHost.gateway_cloudflare_access_json)) }
};
importState.run("nodeHost.config", JSON.stringify({
version: nodeHost.version,
nodeId: nodeHost.node_id,
...nodeHost.display_name == null ? {} : { displayName: nodeHost.display_name },
...Object.keys(gateway).length === 0 ? {} : { gateway },
installedAppsSharing: nodeHost.installed_apps_sharing === 1
}), Number(nodeHost.updated_at_ms));
}
}
if (tableExists(db, "web_push_vapid_keys")) {
const vapidKeys = db.prepare("SELECT * FROM web_push_vapid_keys WHERE key_id = 'default'").get();
if (vapidKeys) importState.run("webPush.vapidKeys", JSON.stringify({
publicKey: vapidKeys.public_key,
privateKey: vapidKeys.private_key,
subject: vapidKeys.subject
}), Number(vapidKeys.updated_at_ms));
}
let dropped = false;
for (const tableName of FOLDED_SINGLETON_STATE_TABLES_V12) if (tableExists(db, tableName)) {
db.exec(`DROP TABLE IF EXISTS ${tableName};`);
dropped = true;
}
return dropped;
}
//#endregion
//#region src/state/session-watch-cursor-provenance.ts
const SESSION_WATCH_PROVENANCE_EXPLICIT = "explicit";
const SESSION_WATCH_PROVENANCE_AMBIENT_GROUP = "ambient-group";
//#endregion
//#region src/state/openclaw-state-db-session-watch-migration.ts
const LEGACY_AMBIENT_GROUP_WATCH_MARKER_PREFIX = "ambient-group-watch:";
const SESSION_WATCH_PROVENANCE_COLUMN_SQL = `provenance TEXT NOT NULL DEFAULT '${SESSION_WATCH_PROVENANCE_EXPLICIT}' CHECK (provenance IN ('${SESSION_WATCH_PROVENANCE_EXPLICIT}', '${SESSION_WATCH_PROVENANCE_AMBIENT_GROUP}'))`;
function getSessionWatchCursorKysely(db) {
return getNodeSqliteKysely(db);
}
function decodeLegacyAmbientWatchMarkerKey(markerKey) {
const encoded = markerKey.slice(20);
if (!encoded || encoded.length % 2 !== 0 || !/^[0-9a-f]+$/.test(encoded)) return;
try {
return new TextDecoder("utf-8", {
fatal: true,
ignoreBOM: true
}).decode(Buffer.from(encoded, "hex"));
} catch {
return;
}
}
function migrateSessionWatchCursorProvenance(db) {
if (!tableExists(db, "session_watch_cursors")) return {
addedColumn: false,
migratedAmbientWatches: 0,
removedLegacySentinels: 0
};
const addedColumn = ensureColumn(db, "session_watch_cursors", SESSION_WATCH_PROVENANCE_COLUMN_SQL);
const kysely = getSessionWatchCursorKysely(db);
const legacyMarkers = executeSqliteQuerySync(db, kysely.selectFrom("session_watch_cursors").select([
"watcher_session_key",
"target_session_key",
"updated_at"
]).where("watcher_session_key", "like", `${LEGACY_AMBIENT_GROUP_WATCH_MARKER_PREFIX}%`)).rows;
let migratedAmbientWatches = 0;
for (const marker of legacyMarkers) {
const watcherSessionKey = decodeLegacyAmbientWatchMarkerKey(marker.watcher_session_key);
if (watcherSessionKey) {
const watch = executeSqliteQueryTakeFirstSync(db, kysely.selectFrom("session_watch_cursors").select("updated_at").where("watcher_session_key", "=", watcherSessionKey).where("target_session_key", "=", marker.target_session_key));
if (watch) {
const promoted = executeSqliteQuerySync(db, kysely.updateTable("session_watch_cursors").set({
provenance: SESSION_WATCH_PROVENANCE_AMBIENT_GROUP,
updated_at: Math.max(watch.updated_at, marker.updated_at)
}).where("watcher_session_key", "=", watcherSessionKey).where("target_session_key", "=", marker.target_session_key));
migratedAmbientWatches += Number(promoted.numAffectedRows ?? 0n);
}
}
executeSqliteQuerySync(db, kysely.deleteFrom("session_watch_cursors").where("watcher_session_key", "=", marker.watcher_session_key).where("target_session_key", "=", marker.target_session_key));
}
return {
addedColumn,
migratedAmbientWatches,
removedLegacySentinels: legacyMarkers.length
};
}
//#endregion
//#region src/state/openclaw-state-db-table-retirements.ts
const stateDbLog$3 = createSubsystemLogger("state/db");
const logRetiredStateTableMigration = (message) => stateDbLog$3.info(message);
const RETIRED_DEAD_STATE_TABLES_V10 = [
"agent_model_catalogs",
"android_notification_recent_packages",
"command_log_entries",
"diagnostic_stability_bundles",
"media_blobs",
"model_capability_cache"
];
const RETIRED_COMMITMENTS_COLUMNS_SQL = `
id TEXT NOT NULL PRIMARY KEY,
agent_id TEXT NOT NULL,
session_key TEXT NOT NULL,
channel TEXT NOT NULL,
account_id TEXT,
recipient_id TEXT,
thread_id TEXT,
sender_id TEXT,
kind TEXT NOT NULL,
sensitivity TEXT NOT NULL,
source TEXT NOT NULL,
status TEXT NOT NULL,
reason TEXT NOT NULL,
suggested_text TEXT NOT NULL,
dedupe_key TEXT NOT NULL,
confidence REAL NOT NULL,
due_earliest_ms INTEGER NOT NULL,
due_latest_ms INTEGER NOT NULL,
due_timezone TEXT NOT NULL,
source_message_id TEXT,
source_run_id TEXT,
created_at_ms INTEGER NOT NULL,
updated_at_ms INTEGER NOT NULL,
attempts INTEGER NOT NULL,
last_attempt_at_ms INTEGER,
sent_at_ms INTEGER,
dismissed_at_ms INTEGER,
snoozed_until_ms INTEGER,
expired_at_ms INTEGER,
record_json TEXT NOT NULL
`;
const RETIRED_COMMITMENTS_BASE_INDEXES_SQL = `CREATE INDEX idx_commitments_scope_due
ON commitments(agent_id, session_key, status, due_earliest_ms, due_latest_ms);
CREATE INDEX idx_commitments_status_due
ON commitments(status, due_earliest_ms, due_latest_ms);
CREATE INDEX idx_commitments_scope_dedupe
ON commitments(agent_id, session_key, channel, dedupe_key, status);`;
const RETIRED_COMMITMENTS_SCHEMA_SQL = `
CREATE TABLE commitments (${RETIRED_COMMITMENTS_COLUMNS_SQL.slice(1, -1)}
) STRICT;
${RETIRED_COMMITMENTS_BASE_INDEXES_SQL}
CREATE INDEX idx_commitments_agent_due
ON commitments(agent_id, status, due_earliest_ms, due_latest_ms, session_key);
CREATE INDEX idx_commitments_agent_sent
ON commitments(agent_id, status, sent_at_ms, session_key);
`;
const SHIPPED_RETIRED_COMMITMENTS_SCHEMA_SQL = `
CREATE TABLE commitments (${RETIRED_COMMITMENTS_COLUMNS_SQL.slice(1, -1)}
);
${RETIRED_COMMITMENTS_BASE_INDEXES_SQL}
`;
const RETIRED_COMMITMENTS_ADDITIVE_COLUMNS = [
"commitments.account_id",
"commitments.recipient_id",
"commitments.thread_id",
"commitments.sender_id",
"commitments.kind",
"commitments.sensitivity",
"commitments.source",
"commitments.reason",
"commitments.suggested_text",
"commitments.dedupe_key",
"commitments.confidence",
"commitments.due_timezone",
"commitments.source_message_id",
"commitments.source_run_id",
"commitments.created_at_ms",
"commitments.attempts",
"commitments.last_attempt_at_ms",
"commitments.sent_at_ms",
"commitments.dismissed_at_ms",
"commitments.snoozed_until_ms",
"commitments.expired_at_ms"
];
function deriveRetiredCommitmentsContract() {
const indexFingerprints = new Map(getCanonicalSqliteNamedIndexContracts(RETIRED_COMMITMENTS_SCHEMA_SQL).map(({ fingerprint, name }) => [name, JSON.stringify(fingerprint)]));
return {
indexFingerprints,
compatibility: {
allowedColumnDefinitions: {
"commitments.attempts": ["attempts INTEGER NOT NULL DEFAULT 0"],
"commitments.confidence": ["confidence REAL NOT NULL DEFAULT 0"],
"commitments.created_at_ms": ["created_at_ms INTEGER NOT NULL DEFAULT 0"],
"commitments.dedupe_key": ["dedupe_key TEXT NOT NULL DEFAULT ''"],
"commitments.due_timezone": ["due_timezone TEXT NOT NULL DEFAULT 'UTC'"],
"commitments.kind": ["kind TEXT NOT NULL DEFAULT 'followup'"],
"commitments.reason": ["reason TEXT NOT NULL DEFAULT ''"],
"commitments.sensitivity": ["sensitivity TEXT NOT NULL DEFAULT 'normal'"],
"commitments.source": ["source TEXT NOT NULL DEFAULT 'unknown'"],
"commitments.suggested_text": ["suggested_text TEXT NOT NULL DEFAULT ''"]
},
allowedMissingColumns: RETIRED_COMMITMENTS_ADDITIVE_COLUMNS,
allowedMissingIndexes: [...indexFingerprints.keys()]
}
};
}
function hasSupportedRetiredCommitmentsSchema(db, schemaSql, { compatibility, indexFingerprints }) {
if (collectSqliteSchemaIssues(db, schemaSql, compatibility).length > 0) return false;
return db.prepare(`SELECT type, name
FROM sqlite_schema
WHERE type IN ('index', 'trigger')
AND tbl_name = 'commitments'
AND sql IS NOT NULL
ORDER BY type, name`).all().every((object) => object.type === "index" && JSON.stringify(collectSqliteNamedIndexContract(db, object.name)) === indexFingerprints.get(object.name));
}
function assertRecognizedRetiredCommitmentsSchema(db) {
if (hasRecognizedRetiredCommitmentsSchema(db)) return;
assertSqliteSchemaContains(db, "retired OpenClaw commitments schema", RETIRED_COMMITMENTS_SCHEMA_SQL, deriveRetiredCommitmentsContract().compatibility);
throw new Error("Retired OpenClaw commitments schema has unsupported additional indexes; refusing destructive migration.");
}
function hasRecognizedRetiredCommitmentsSchema(db) {
const contract = deriveRetiredCommitmentsContract();
return hasSupportedRetiredCommitmentsSchema(db, RETIRED_COMMITMENTS_SCHEMA_SQL, contract) || hasSupportedRetiredCommitmentsSchema(db, SHIPPED_RETIRED_COMMITMENTS_SCHEMA_SQL, contract);
}
function assertNoRetiredCommitmentsForeignKeys(db) {
const tables = db.prepare(`SELECT name
FROM sqlite_schema
WHERE type = 'table' AND name <> 'commitments'
ORDER BY name`).all();
for (const table of tables) if (db.prepare(`PRAGMA foreign_key_list(${quoteSqliteIdentifier(table.name)})`).all().some((foreignKey) => typeof foreignKey.table === "string" && foreignKey.table.toLowerCase() === "commitments")) throw new Error(`Retired OpenClaw commitments schema is referenced by table ${table.name}; refusing destructive migration.`);
}
function collectRetainedSchemaSql(db) {
return new Map(db.prepare(`SELECT type, name, sql
FROM sqlite_schema
WHERE type IN ('trigger', 'view')
AND tbl_name <> 'commitments'
AND sql IS NOT NULL
ORDER BY type, name`).all().map((object) => [`${object.type}:${object.name}`, object.sql]));
}
function assertNoRetiredCommitmentsSchemaDependencies(db) {
const probeTable = "__openclaw_retired_commitments_probe";
if (tableExists(db, probeTable)) throw new Error(`OpenClaw state database already contains ${probeTable}; refusing destructive migration.`);
const before = collectRetainedSchemaSql(db);
const savepoint = "openclaw_probe_commitments_dependencies";
db.exec(`SAVEPOINT ${savepoint};`);
let changedObject;
try {
db.exec(`ALTER TABLE commitments RENAME TO ${quoteSqliteIdentifier(probeTable)};`);
const after = collectRetainedSchemaSql(db);
changedObject = [...before].find(([object, sql]) => after.get(object) !== sql)?.[0];
} catch (error) {
db.exec(`ROLLBACK TO ${savepoint}; RELEASE ${savepoint};`);
throw new Error("Could not prove retained SQLite views and triggers independent of commitments; refusing destructive migration.", { cause: error });
}
db.exec(`ROLLBACK TO ${savepoint}; RELEASE ${savepoint};`);
if (changedObject) {
const [type, name] = changedObject.split(":", 2);
throw new Error(`Retired OpenClaw commitments schema is referenced by ${type} ${name}; refusing destructive migration.`);
}
}
function assertVirtualTablesUsable(db, phase) {
const virtualTables = db.prepare(`SELECT name
FROM sqlite_schema
WHERE type = 'table' AND lower(sql) LIKE 'create virtual table%'
ORDER BY name`).all();
for (const table of virtualTables) try {
db.prepare(`SELECT * FROM ${quoteSqliteIdentifier(table.name)} LIMIT 1`).all();
} catch (error) {
throw new Error(`SQLite virtual table ${table.name} is unusable ${phase} commitments retirement.`, { cause: error });
}
}
function migrateRetiredCommitmentsSchema(db, previousVersion) {
if (previousVersion >= 7) return false;
if (!tableExists(db, "commitments")) return false;
assertRecognizedRetiredCommitmentsSchema(db);
assertNoRetiredCommitmentsForeignKeys(db);
assertNoRetiredCommitmentsSchemaDependencies(db);
assertVirtualTablesUsable(db, "before");
const savepoint = "openclaw_retire_commitments_v7";
db.exec(`SAVEPOINT ${savepoint};`);
try {
db.exec("DROP TABLE commitments;");
assertVirtualTablesUsable(db, "after");
db.exec(`RELEASE ${savepoint};`);
return true;
} catch (error) {
db.exec(`ROLLBACK TO ${savepoint}; RELEASE ${savepoint};`);
throw error;
}
}
function migrateRetiredDeadStateTablesV10(db, previousVersion) {
if (previousVersion >= 10) return false;
let dropped = false;
for (const tableName of RETIRED_DEAD_STATE_TABLES_V10) if (tableExists(db, tableName)) {
db.exec(`DROP TABLE IF EXISTS ${tableName};`);
dropped = true;
}
return dropped;
}
const RETIRED_SKILL_CURATOR_TABLES_V11 = ["skill_lifecycle", "skill_workshop_proposal_origin_runs"];
function migrateRetiredSkillCuratorTablesV11(db, previousVersion) {
if (previousVersion >= 11) return false;
const retiredTables = RETIRED_SKILL_CURATOR_TABLES_V11.filter((table) => tableExists(db, table));
if (retiredTables.length === 0) return false;
if (retiredTables.includes("skill_lifecycle")) {
const archivedCount = Number(db.prepare("SELECT COUNT(*) AS archived_count FROM skill_lifecycle WHERE state = 'archived'").get()?.archived_count);
if (archivedCount > 0) stateDbLog$3.info(`${archivedCount} previously archived workshop skills return to the active collection; the weekly collection review will judge them`);
}
for (const table of retiredTables) db.exec(`DROP TABLE IF EXISTS ${table};`);
return true;
}
/**
* Runs every retired-table migration in schema order and names what it changed.
* Both the repair path and the ordinary open path go through here so the order
* and the operator-visible labels cannot drift apart.
*/
function runRetiredStateTableMigrations(db, previousVersion) {
const applied = [];
if (migrateRetiredCommitmentsSchema(db, previousVersion)) applied.push("Discarded retired shared-state commitments rows, table, and indexes");
if (migrateRetiredDeadStateTablesV10(db, previousVersion)) applied.push("Retired six dead shared-state tables (v10)");
if (migrateRetiredSkillCuratorTablesV11(db, previousVersion)) applied.push("Retired legacy skill curator lifecycle and proposal origin-run tables");
return applied;
}
//#endregion
//#region src/state/openclaw-state-db-schema-repair.ts
function dropLegacyStateTables(db) {
const transientHistoryTable = ["database", "verifications"].join("_");
db.exec(`DROP TABLE IF EXISTS ${transientHistoryTable};`);
db.exec("DROP TABLE IF EXISTS node_pairing_pending; DROP TABLE IF EXISTS node_pairing_paired;");
}
function migrateWorkerPlacementExecutionModeSchema(db, previousVersion) {
if (previousVersion >= 8 || !tableExists(db, "worker_session_placements")) return false;
for (const definition of [
"execution_mode TEXT",
"terminal_reason TEXT",
"terminal_at_ms INTEGER"
]) {
const column = definition.split(" ", 1)[0];
if (!tableHasColumn(db, "worker_session_placements", column)) db.exec(`ALTER TABLE worker_session_placements ADD COLUMN ${definition};`);
}
const placementSchema = extractSqliteTableSchema(OPENCLAW_STATE_SCHEMA_SQL, "worker_session_placements", { errorMessage: "Canonical worker placement schema block is missing" });
const canonical = openNodeSqliteDatabase(":memory:");
let canonicalColumns;
try {
canonical.exec(placementSchema);
canonicalColumns = canonical.prepare("PRAGMA table_xinfo(worker_session_placements)").all().filter((column) => column.hidden === 0).map((column) => column.name);
} finally {
canonical.close();
}
const currentColumns = db.prepare("PRAGMA table_xinfo(worker_session_placements)").all().filter((column) => column.hidden === 0).map((column) => column.name);
const expected = new Set(canonicalColumns);
if (currentColumns.length !== canonicalColumns.length || currentColumns.some((column) => !expected.has(column))) throw new Error("OpenClaw v7 worker placement columns are not canonical");
if (db.prepare(`SELECT type, name
FROM sqlite_schema
WHERE tbl_name = 'worker_session_placements'
AND type IN ('index', 'trigger')
AND sql IS NOT NULL
AND name NOT IN (
'idx_worker_session_placements_session_key',
'idx_worker_session_placements_reconcile'
)`).all().length > 0) throw new Error("OpenClaw v7 worker placement schema has unsupported attached objects");
const migrationTable = "worker_session_placements_migration_v8";
if (tableExists(db, migrationTable)) throw new Error(`OpenClaw worker placement migration table already exists: ${migrationTable}`);
const migrationSchema = placementSchema.replace("CREATE TABLE IF NOT EXISTS worker_session_placements", `CREATE TABLE ${migrationTable}`);
const columns = canonicalColumns.map(quoteSqliteIdentifier).join(", ");
db.exec(migrationSchema);
db.exec(`INSERT INTO ${migrationTable} (${columns}) SELECT ${columns} FROM worker_session_placements;`);
db.exec("DROP TABLE worker_session_placements;");
db.exec(`ALTER TABLE ${migrationTable} RENAME TO worker_session_placements;`);
return true;
}
function isDefaultAgentDatabasePath(pathname, agentId) {
const agentDir = path.dirname(pathname);
const agentIdDir = path.dirname(agentDir);
return path.basename(pathname) === "openclaw-agent.sqlite" && path.basename(agentDir) === "agent" && path.basename(agentIdDir) === agentId && path.basename(path.dirname(agentIdDir)) === "agents";
}
function migrateAgentDatabaseRelativePaths(db, previousVersion, databasePath) {
if (previousVersion >= 9 || !tableExists(db, "agent_databases")) return {
relativized: 0,
reanchored: [],
deleted: [],
preserved: 0
};
const rows = db.prepare("SELECT agent_id, path FROM agent_databases").all();
const updatePath = db.prepare("UPDATE agent_databases SET path = ? WHERE agent_id = ? AND path = ?");
const deletePath = db.prepare("DELETE FROM agent_databases WHERE agent_id = ? AND path = ?");
const hasPath = db.prepare("SELECT 1 FROM agent_databases WHERE agent_id = ? AND path = ? LIMIT 1");
const retainNewerFacts = db.prepare(`
UPDATE agent_databases AS canonical
SET schema_version = source.schema_version,
last_seen_at = source.last_seen_at,
size_bytes = source.size_bytes
FROM agent_databases AS source
WHERE canonical.agent_id = ? AND canonical.path = ?
AND source.agent_id = canonical.agent_id AND source.path = ?
AND source.last_seen_at > canonical.last_seen_at
`);
let relativized = 0;
const reanchored = [];
const deleted = [];
for (const row of rows) {
const agentId = row.agent_id;
const registeredPath = row.path;
if (typeof agentId !== "string" || typeof registeredPath !== "string") throw new Error("OpenClaw v8 agent database registry paths are not canonical");
if (!path.isAbsolute(registeredPath)) continue;
const storedPath = resolveOpenClawAgentDatabaseStoredPath(databasePath, registeredPath);
if (!path.isAbsolute(storedPath)) {
if (hasPath.get(agentId, storedPath)) {
retainNewerFacts.run(agentId, storedPath, registeredPath);
deletePath.run(agentId, registeredPath);
deleted.push(registeredPath);
} else {
updatePath.run(storedPath, agentId, registeredPath);
relativized += 1;
}
}
}
const stateDir = resolveOpenClawStateDirForDatabasePath(databasePath);
for (const row of rows) {
const agentId = row.agent_id;
const registeredPath = row.path;
if (typeof agentId !== "string" || typeof registeredPath !== "string" || !path.isAbsolute(registeredPath) || !path.isAbsolute(resolveOpenClawAgentDatabaseStoredPath(databasePath, registeredPath))) continue;
if (isDefaultAgentDatabasePath(path.resolve(registeredPath), agentId)) {
const counterpartAbsolute = path.join(stateDir, "agents", agentId, "agent", "openclaw-agent.sqlite");
const counterpartStored = resolveOpenClawAgentDatabaseStoredPath(databasePath, counterpartAbsolute);
if (hasPath.get(agentId, counterpartStored)) {
deletePath.run(agentId, registeredPath);
deleted.push(registeredPath);
} else if (existsSync(counterpartAbsolute)) {
updatePath.run(counterpartStored, agentId, registeredPath);
reanchored.push(registeredPath);
}
}
}
return {
relativized,
reanchored,
deleted,
preserved: rows.length - relativized - reanchored.length - deleted.length
};
}
function hasCanonicalAgentDatabasesPrimaryKey(db) {
if (!tableExists(db, "agent_databases")) return true;
const primaryKey = tablePrimaryKeyColumns(db, "agent_databases");
return primaryKey.length === 2 && primaryKey[0] === "agent_id" && primaryKey[1] === "path";
}
function canRepairAgentDatabasesPrimaryKey(db) {
if (!tableExists(db, "agent_databases")) return false;
return [
"agent_id",
"path",
"schema_version",
"last_seen_at",
"size_bytes"
].every((column) => tableHasColumn(db, "agent_databases", column));
}
function repairLegacyGatewayRestartHandoffsForStrictMigration(db) {
if (!tableExists(db, "gateway_restart_handoff")) return;
db.prepare("DELETE FROM gateway_restart_handoff WHERE expires_at <= ?").run(Date.now());
db.exec(`
UPDATE gateway_restart_handoff
SET
restart_trace_started_at = CASE
WHEN typeof(restart_trace_started_at) = 'real'
THEN CAST(restart_trace_started_at AS INTEGER)
ELSE restart_trace_started_at
END,
restart_trace_last_at = CASE
WHEN typeof(restart_trace_last_at) = 'real'
THEN CAST(restart_trace_last_at AS INTEGER)
ELSE restart_trace_last_at
END
WHERE typeof(restart_trace_started_at) = 'real'
OR typeof(restart_trace_last_at) = 'real';
`);
}
function assertCanonicalStateSchemaShape(db, pathname) {
assertCanonicalOperatorApprovalKinds(db, pathname);
if (!hasCanonicalAgentDatabasesPrimaryKey(db)) {
if (canRepairAgentDatabasesPrimaryKey(db)) throw new OpenClawStateDatabaseSchemaMigrationRequiredError("agent-databases-composite-primary-key", pathname);
throw new SqliteSchemaMismatchError(`OpenClaw state database ${pathname} has a noncanonical agent database registry schema that cannot be repaired automatically; restore the canonical agent_databases shape before retrying.`);
}
if (!hasCanonicalAuditEventsSchema(db)) {
if (canRepairLegacyAuditEventsSchema(db)) throw new OpenClawStateDatabaseSchemaMigrationRequiredError("audit-events-v2", pathname);
throw new SqliteSchemaMismatchError(`OpenClaw state database ${pathname} has a noncanonical audit event schema that cannot be repaired automatically; restore the canonical audit_events shape before retrying.`);
}
}
//#endregion
//#region src/state/openclaw-state-db-fast-path.ts
function assertCurrentStateRuntimeSchema(database, pathname, readTable) {
assertCanonicalStateSchemaShape(database, pathname);
assertOpenClawStateDatabaseForMaintenance(database, { pathname }, readTable);
}
/** Catalog presence is enough to refuse retired history without reading or rewriting its rows. */
function assertNoLegacyStateRuntimeRepair(database, pathname) {
if (hasLegacyCronRunLogs(database)) throw new OpenClawStateDatabaseSchemaMigrationRequiredError("legacy-cron-run-logs", pathname);
}
function isOpenClawStateSchemaFastPathEligible(database, pathname) {
return runSqliteDeferredTransactionSync(database, () => {
assertSupportedStateSchemaVersion(database, pathname);
if (readStateSchemaMigrationVersion(database) !== 19) return false;
assertSqliteIntegrity(database, pathname);
const readTable = createSqliteTableContractReader(database);
assertCurrentStateRuntimeSchema(database, pathname, readTable);
if (collectSqliteSchemaIssues(database, getOpenClawStateRuntimeSchema({ includeVersionLazyAdditiveTables: false }), STATE_PERSISTENT_SCHEMA_COMPATIBILITY, readTable).some(isOpenClawStateStartupRepairableSchemaIssue)) return false;
assertNoLegacyStateRuntimeRepair(database, pathname);
return true;
});
}
//#endregion
//#region src/state/openclaw-state-db-existing-schema.ts
const validatedSchemas = /* @__PURE__ */ new WeakMap();
/** Prove the existing runtime contract without certifying this release's repairs. */
function assertExistingOpenClawStateRuntimeSchema(database, pathname) {
const schemaCookie = runSqliteDeferredTransactionSync(database, () => {
const version = assertSupportedStateSchemaVersion(database, pathname);
if (readStateSchemaMigrationVersion(database) !== 19) throw new Error(`Existing shared-state database ${pathname} requires schema migration by its owning installation before this node can use it.`);
let metadata;
try {
metadata = executeSqliteQueryTakeFirstSync(database, getNodeSqliteKysely(database).selectFrom("schema_meta").select(["role", "schema_version"]).where("meta_key", "=", "primary").limit(1));
} catch (error) {
throw classifySqliteTableReadError(database, "schema_meta", [
"meta_key",
"role",
"schema_version"
], error);
}
if (metadata?.role !== "global" || metadata.schema_version !== version) throw new Error(`Existing shared-state database ${pathname} has inconsistent ownership or schema metadata.`);
const currentCookie = readSqliteSchemaCookie(database);
if (typeof currentCookie !== "number") throw new Error(`Existing shared-state database ${pathname} schema version is unavailable.`);
const cached = validatedSchemas.get(database);
if (cached?.cookie !== currentCookie) {
cached?.unregister();
validatedSchemas.delete(database);
assertSqliteIntegrity(database, pathname);
const readTable = createSqliteTableContractReader(database);
assertCurrentStateRuntimeSchema(database, pathname, readTable);
assertNoLegacyStateRuntimeRepair(database, pathname);
assertSqliteSchemaContains(database, pathname, getOpenClawStateRuntimeSchema({ includeVersionLazyAdditiveTables: false }), STATE_PERSISTENT_SCHEMA_COMPATIBILITY, readTable);
}
return currentCookie;
});
if (!database.isTransaction && validatedSchemas.get(database)?.cookie !== schemaCookie) {
const unregister = registerNodeSqliteDisposeCallback(database, () => {
validatedSchemas.delete(database);
unregister();
});
validatedSchemas.set(database, {
cookie: schemaCookie,
unregister
});
}
}
//#endregion
//#region src/state/openclaw-state-db-read-connection.ts
const retainedReaders = /* @__PURE__ */ new Map();
let unregisterExitClose;
function retireReader(reader) {
clearTimeout(reader.idleTimer);
reader.retiring = true;
reader.connection.close();
retainedReaders.delete(reader.identity.key);
if (!retainedReaders.size) {
unregisterExitClose?.();
unregisterExitClose = void 0;
}
}
function scheduleReaderRetirement(reader) {
if (retainedReaders.get(reader.identity.key) !== reader) return;
clearTimeout(reader.idleTimer);
reader.idleTimer = runInSqliteMaintenanceContext(() => setTimeout(() => {
try {
retireReader(reader);
} catch (error) {
process.emitWarning(`Idle shared-state reader cleanup failed: ${String(error)}`);
scheduleReaderRetirement(reader);
}
}, SQLITE_IDLE_HANDLE_TTL_MS));
reader.idleTimer.unref?.();
}
/** The host joins this receipt before allowing replacement or deletion of live state. */
function closeRetainedOpenClawStateReadConnections(identity) {
const errors = [];
for (const reader of retainedReaders.values()) if (identity === void 0 || reader.identity.key === identity) try {
retireReader(reader);
} catch (error) {
errors.push(error);
}
throwSqliteLifecycleErrors(errors, "Retained shared-state reader cleanup failed.");
}
function borrowStateReadConnection(pathname, expectedIdentity) {
isExistingOpenClawStateSchema(pathname);
const identity = readDatabasePathIdentitySync(pathname);
if (expectedIdentity !== void 0) assertExistingDatabaseIdentity(pathname, expectedIdentity);
for (const previous of retainedReaders.values()) if (previous.identity.canonicalPath === identity.canonicalPath && previous.identity.key !== identity.key) retireReader(previous);
if (!identity.key.startsWith("file:")) return openStateReadConnectionResult(pathname, pathname, expectedIdentity);
let reader = retainedReaders.get(identity.key);
if (reader?.retiring || reader && !reader.connection.database.db.isOpen) {
retireReader(reader);
reader = void 0;
}
if (!reader) {
const opening = openStateReadConnectionResult(pathname, pathname, identity.key);
if (opening.status === "unavailable") return opening;
reader = {
connection: opening.value,
identity,
retiring: false
};
retainedReaders.set(identity.key, reader);
unregisterExitClose ??= registerSqliteCacheExitClose(closeRetainedOpenClawStateReadConnections);
}
const retained = reader;
clearTimeout(retained.idleTimer);
return {
status: "available",
value: {
database: {
db: retained.connection.database.db,
path: pathname
},
close(keep) {
if (keep && retained.connection.database.db.isOpen && !retained.connection.database.db.isTransaction) scheduleReaderRetirement(retained);
else retireReader(retained);
return true;
}
}
};
}
var SnapshotCleanupIncompleteError = class extends Error {};
function assertStateReadSchema(database, pathname) {
assertStateReadSchemaForPolicy(database, pathname, isExistingOpenClawStateSchema(pathname, database));
}
function assertStateReadSchemaForPolicy(database, pathname, existingSchema) {
if (existingSchema) assertExistingOpenClawStateRuntimeSchema(database, pathname);
else assertSupportedStateSchemaVersion(database, pathname);
}
function withOpenClawStateReadOnlyLocation(operation, pathname, source, openStateSchemaReadAdmission, expectedIdentity, snapshotRoot, retainConnection = false) {
const result = readOpenClawStateReadOnlyLocation(operation, pathname, source, openStateSchemaReadAdmission, expectedIdentity, snapshotRoot, retainConnection);
if (result.status === "unavailable") throw result.error;
return result.value;
}
/** Return a failed read only after its native reader and admission have settled. */
function readOpenClawStateReadOnlyLocation(operation, pathname, source, openStateSchemaReadAdmission, expectedIdentity, snapshotRoot, retainConnection = false) {
const opening = retainConnection && source === pathname && !snapshotRoot && !process.versions.bun ? borrowStateReadConnection(pathname, expectedIdentity) : openStateReadConnectionResult(pathname, source, expectedIdentity, snapshotRoot, true);
if (opening.status === "unavailable") return opening;
const opened = opening.value;
const errors = [];
let closeAdmission;
let result;
try {
closeAdmission = openStateSchemaReadAdmission?.(opened.database.db);
const existingSchema = isExistingOpenClawStateSchema(pathname, opened.database.db);
try {
runSqliteReadOperationSync(opened.database.db, () => {
assertStateReadSchemaForPolicy(opened.database.db, pathname, existingSchema);
admitSqliteSchema(opened.database.db);
});
result = {
status: "available",
value: operation(opened.database)
};
} catch (error) {
result = {
status: "unavailable",
error
};
}
const location = typeof source === "string" ? source : source.location;
if (result.status === "available" && location === pathname && isPromiseLike(result.value)) throw new SqliteCoordinatorError("SQLite source read must remain synchronous");
assertTransactionUsable(opened.database.db);
} catch (error) {
errors.push(error);
}
try {
closeAdmission?.();
} catch (error) {
errors.push(error);
}
try {
if (!opened.close(errors.length === 0 && result?.status === "available")) throw new SnapshotCleanupIncompleteError("Shared-state snapshot cleanup is incomplete.");
} catch (error) {
errors.push(error);
}
if (errors.length) {
if (result?.status === "unavailable" && !errors.includes(result.error)) errors.unshift(result.error);
throwSqliteLifecycleErrors(errors, "Shared-state read and reader cleanup failed.");
}
return result;
}
function openOpenClawStateReadOnlyLocation(pathname, source) {
const connection = openOpenClawStateReadConnection(pathname, source);
try {
runSqliteReadOperationSync(connection.database.db, () => {
assertStateReadSchema(connection.database.db, pathname);
admitSqliteSchema(connection.database.db);
});
} catch (error) {
try {
connection.close();
} catch (cleanupError) {
throwSqliteLifecycleErrors([error, cleanupError], "Shared-state reader admission and cleanup failed.");
}
throw error;
}
return connection;
}
/** Own one native reader; callers retain their runtime or maintenance schema policy. */
function openOpenClawStateReadConnection(pathname, source, expectedIdentity, snapshotRoot) {
const result = openStateReadConnectionResult(pathname, source, expectedIdentity, snapshotRoot);
if (result.status === "unavailable") throw result.error;
return result.value;
}
function openStateReadConnectionResult(pathname, source, expectedIdentity, snapshotRoot, checkSchemaPolicy = false) {
const snapshot = typeof source === "string" ? void 0 : source;
const location = typeof source === "string" ? source : source.location;
const options = {
readOnly: true,
timeout: OPENCLAW_SQLITE_BUSY_TIMEOUT_MS
};
let releaseToken;
const cleanupFailedOpen = (error) => {
const errors = [error];
try {
releaseToken?.();
} catch (cleanupError) {
errors.push(cleanupError);
}
try {
if (snapshot && !snapshot.cleanup()) errors.push(new SnapshotCleanupIncompleteError("Shared-state snapshot cleanup is incomplete."));
} catch (cleanupError) {
errors.push(cleanupError);
}
if (errors.length > 1) throw createSqliteLifecycleAggregateError(errors, "Shared-state reader open and cleanup failed.", error);
};
let native;
try {
if (expectedIdentity !== void 0) assertExistingDatabaseIdentity(location, expectedIdentity);
releaseToken = snapshotRoot ? acquireSqliteSnapshotReadToken(snapshotRoot) : void 0;
if (checkSchemaPolicy) isExistingOpenClawStateSchema(pathname);
if (location === pathname) native = openTrackedStateDatabaseResult(pathname, options);
else try {
native = {
status: "available",
database: openNodeSqliteDatabase(location, options)
};
} catch (error) {
native = {
status: "unavailable",
error
};
}
} catch (error) {
cleanupFailedOpen(error);
throw error;
}
if (native.status === "unavailable") {
cleanupFailedOpen(native.error);
return native;
}
const db = native.database;
let closed = false;
const database = {
db,
path: pathname,
afterClose: () => {
releaseToken?.();
if (snapshot && !snapshot.cleanup()) throw new SnapshotCleanupIncompleteError("Shared-state snapshot cleanup is incomplete.");
}
};
const connection = {
database: {
db,
path: pathname
},
close() {
if (closed) return false;
const errors = openClawStateDatabaseCache.closeOpenClawStateDatabaseHandle(database);
if (errors.length === 1 && errors[0] instanceof SnapshotCleanupIncompleteError) return false;
throwSqliteLifecycleErrors(errors, "Shared-state reader cleanup failed.");
closed = true;
return true;
}
};
try {
if (expectedIdentity !== void 0) assertExistingDatabaseIdentity(location, expectedIdentity);
} catch (error) {
try {
if (!connection.close()) throw new SnapshotCleanupIncompleteError("Shared-state snapshot cleanup is incomplete.");
} catch (cleanupError) {
throw createSqliteLifecycleAggregateError([error, cleanupError], "Shared-state reader identity and cleanup failed.", error);
}
throw error;
}
return {
status: "available",
value: connection
};
}
//#endregion
//#region src/state/openclaw-state-db-transaction.ts
const managedStateTransactions = resolveGlobalSingleton(Symbol.for("openclaw.managedStateTransactions"), () => /* @__PURE__ */ new WeakSet());
/** Only the synchronous transaction owner may lend its uncommitted authority rows. */
function isManagedStateTransaction(database) {
return database.isTransaction && managedStateTransactions.has(database);
}
function runManagedStateTransaction(database, operation, options) {
if (database.isTransaction && !managedStateTransactions.has(database)) throw new Error("Cannot join an unmanaged shared-state transaction; enter through runOpenClawStateWriteTransaction before BEGIN.");
const transaction = () => withSqlitePostCommitPublications(database, () => {
const outer = !database.isTransaction;
if (outer) managedStateTransactions.add(database);
try {
return runSqliteImmediateTransactionSync(database, operation, options);
} finally {
if (outer) managedStateTransactions.delete(database);
}
});
return options.busyTimeoutMs === void 0 ? transaction() : runWithSqliteBusyTimeout(database, options.busyTimeoutMs, transaction);
}
//#endregion
//#region src/state/openclaw-state-db-readonly-reuse.ts
/** Current-authority guards can borrow their writer; discovery sees committed rows. */
function withCachedOpenClawStateDatabaseReadOnly(operation, pathname, currentAuthority) {
const opened = openClawStateDatabaseCache.getCachedOpenClawStateDatabase(pathname, { readOnly: true });
if (!opened?.db.isOpen) return { reused: false };
const ownedTransaction = currentAuthority && isManagedStateTransaction(opened.db);
if (opened.db.isTransaction && !ownedTransaction) return { reused: false };
try {
assertStateReadSchema(opened.db, pathname);
observeOpenClawDatabaseMaintenanceResource(opened.db);
const value = operation(opened);
if (ownedTransaction && isPromiseLike(value)) throw new SqliteCoordinatorError("SQLite current-authority read must remain synchronous");
return {
reused: true,
value
};
} catch (error) {
openClawStateDatabaseCache.evictOpenClawStateDatabaseAfterCorruption(opened, error);
throw error;
}
}
//#endregion
//#region src/state/openclaw-state-read-scope.ts
/** Closing retains custody for accepted readers, but never admits a new reader. */
function assertRetainedReadScopeAdmission(pathname, scopes) {
if (scopes.some((scope) => scope?.path === pathname && (!scope.active || scope.work.isClosing))) throw new StateDatabaseReadAdmissionInvalidatedError("Shared-state read scope is closing or closed; retry the operation in a current scope.");
}
//#endregion
//#region src/state/openclaw-state-db-readonly.ts
const artifactPreservingReads = resolveGlobalSingleton(Symbol.for("openclaw.artifactPreservingStateReads"), () => new AsyncLocalStorage());
const disposableStateReads = resolveGlobalSingleton(Symbol.for("openclaw.disposableStateReads"), () => new AsyncLocalStorage());
const stateSnapshotReads = resolveGlobalSingleton(Symbol.for("openclaw.stateSnapshotReads"), () => new AsyncLocalStorage());
/** Opaque identity for derived facts scoped to these owned private database bytes. */
function getActiveOpenClawStateDatabaseReadSnapshot(options = {}) {
const current = stateSnapshotReads.getStore();
return current?.path === resolveReadOnlyPath(options) ? current : void 0;
}
function requiresArtifactPreservingSnapshot(pathname) {
return isArtifactPreservingStateRead() && !disposableStateReads.getStore()?.some((scope) => scope.active && scope.path === pathname);
}
/** Admission scopes every nested reader without changing normal live-read semantics. */
function withArtifactPreservingStateReads(operation) {
return artifactPreservingReads.run(true, operation);
}
function isArtifactPreservingStateRead() {
return artifactPreservingReads.getStore() === true;
}
const synchronousReadSnapshots = resolveGlobalSingleton(Symbol.for("openclaw.synchronousStateReadSnapshots"), () => ({ current: void 0 }));
function resolveReadOnlyPath(options) {
const pathname = path.resolve(options.path ?? resolveOpenClawStateSqlitePath(options.env ?? process.env));
assertRetainedReadScopeAdmission(pathname, [stateSnapshotReads.getStore(), ...disposableStateReads.getStore() ?? []]);
isExistingOpenClawStateSchema(pathname);
return pathname;
}
function withOpenClawStateDatabaseReadOnlyIfOpen(operation, pathname, currentAuthority = false) {
const snapshot = stateSnapshotReads.getStore();
if (snapshot?.active && snapshot.path === pathname) {
openClawStateDatabaseCache.assertOpenClawStateDatabaseFreshOpenAllowedAtPath(pathname, snapshot.env);
return {
reused: true,
value: withOpenClawStateReadOnlyLocation(operation, pathname, snapshot.location)
};
}
return withCachedOpenClawStateDatabaseReadOnly(operation, pathname, currentAuthority);
}
function withFreshOpenClawStateDatabaseReadOnly(operation, options, pathname) {
const env = options.env ?? process.env;
openClawStateDatabaseCache.assertOpenClawStateDatabaseFreshOpenAllowedAtPath(pathname, env);
const readers = synchronousReadSnapshots.current;
if (readers && requiresArtifactPreservingSnapshot(pathname)) {
let opened = readers.get(pathname);
if (!opened) {
opened = openOpenClawStateReadOnlyLocation(pathname, prepareSqliteReadOnlyLocationSync(pathname));
readers.set(pathname, opened);
}
assertStateReadSchema(opened.database.db, pathname);
const result = operation(opened.database);
if (isPromiseLike(result)) throw new SqliteCoordinatorError("SQLite metadata snapshot read must remain synchronous");
return result;
}
return withOpenClawStateReadOnlyLocation(operation, pathname, (requiresArtifactPreservingSnapshot(pathname) ? prepareSqliteReadOnlyLocationSync(pathname) : void 0) ?? pathname);
}
/** Read existing shared state while preserving non-missing filesystem failures. */
function withExistingOpenClawStateDatabaseReadOnly(operation, options = {}) {
const pathname = resolveReadOnlyPath(options);
if (synchronousReadSnapshots.current?.has(pathname)) return withFreshOpenClawStateDatabaseReadOnly(operation, options, pathname);
const reused = withOpenClawStateDatabaseReadOnlyIfOpen(operation, pathname);
if (reused.reused) return reused.value;
const existingPath = existingPathOrUndefined(pathname);
return existingPath === void 0 ? void 0 : withFreshOpenClawStateDatabaseReadOnly(operation, options, existingPath);
}
/** Read existing shared state without creating or updating its SQLite sidecars. */
function withExistingOpenClawStateDatabaseArtifactPreservingReadOnly(operation, options = {}, openStateSchemaReadAdmission) {
if (openStateSchemaReadAdmission) return withExistingOpenClawStateDatabaseCurrentReadOnly(operation, options, openStateSchemaReadAdmission);
return withArtifactPreservingStateReads(() => withExistingOpenClawStateDatabaseReadOnly(operation, options));
}
/** Publication guards need current rows, never an inherited discovery snapshot. */
function withExistingOpenClawStateDatabaseCurrentReadOnly(operation, options = {}, openStateSchemaReadAdmission) {
const pathname = resolveReadOnlyPath(options);
return stateSnapshotReads.exit(() => {
if (!openStateSchemaReadAdmission) {
const reused = withOpenClawStateDatabaseReadOnlyIfOpen(operation, pathname, true);
if (reused.reused) return reused.value;
}
if (existingPathOrUndefined(pathname) === void 0) return;
openClawStateDatabaseCache.assertOpenClawStateDatabaseFreshOpenAllowedAtPath(pathname, options.env ?? process.env);
return withOpenClawStateReadOnlyLocation(operation, pathname, prepareSqliteReadOnlyLocationSync(pathname), openStateSchemaReadAdmission);
});
}
//#endregion
//#region src/config/model-policy-allowlist-migration.ts
/** A per-agent policy replaces inherited defaults only when it owns `allow`. */
function hasExplicitModelPolicyAllow(value) {
return isRecord(value) && Object.hasOwn(value, "allow");
}
//#endregion
//#region src/agents/agent-roster.ts
function collectAgentEntries(cfg, withSource, limit) {
const roster = readAgentRosterProperty(cfg);
if (roster?.kind === "entries" && isRecord(roster.value)) {
const result = [];
for (const id in roster.value) {
if (!Object.hasOwn(roster.value, id)) continue;
const entry = roster.value[id];
if (isRecord(entry)) {
const projected = {
...entry,
id
};
result.push(withSource ? {
entry: projected,
source: {
kind: "entries",
key: id
}
} : projected);
if (result.length === limit) break;
}
}
return result;
}
if (roster?.kind !== "list" || !Array.isArray(roster.value)) return [];
const listed = [];
roster.value.some((entry, index) => {
if (entry !== null && typeof entry === "object") listed.push({
entry,
source: {
kind: "list",
index
}
});
return listed.length === limit;
});
return withSource ? listed : listed.map(({ entry }) => entry);
}
/** Lists valid configured agent entries from config. */
function listAgentEntriesWithSource(cfg) {
return collectAgentEntries(cfg, true);
}
/** Lists valid configured agent entries from either supported representation. */
function listAgentEntries(cfg) {
return collectAgentEntries(cfg, false);
}
/** Reads the explicitly owned raw roster without normalizing malformed values. */
function readAgentRosterProperty(raw) {
if (!isRecord(raw)) return;
const agents = raw.agents;
if (!isRecord(agents)) return;
const entries = agents["entries"];
if (Object.hasOwn(agents, "entries") && entries !== void 0) return {
kind: "entries",
value: entries
};
const list = agents["list"];
if (Object.hasOwn(agents, "list") && list !== void 0) return {
kind: "list",
value: list
};
}
/** True when raw config explicitly owns either supported roster representation. */
function hasAgentRosterProperty(raw) {
return readAgentRosterProperty(raw) !== void 0;
}
/** Lists unique configured agent ids. */
function listAgentIds(cfg) {
const agents = listAgentEntries(cfg);
if (agents.length === 0 && !hasAgentRosterProperty(cfg)) return [LEGACY_IMPLICIT_AGENT_ID];
return [...new Set(agents.map((entry) => normalizeAgentId(entry?.id)))];
}
function tryResolveSoleAgentId(cfg) {
const agents = collectAgentEntries(cfg, false, 2);
if (agents.length === 0) {
if (!hasAgentRosterProperty(cfg)) return LEGACY_IMPLICIT_AGENT_ID;
return;
}
return agents.length === 1 ? normalizeAgentId(agents[0].id) : void 0;
}
function tryResolveRawLegacyDefaultAgentId(cfg) {
if (cfg.agents?.ownership === "explicit") return;
const marked = listAgentEntries(cfg).filter((entry) => entry.default === true);
return marked.length === 1 ? normalizeAgentId(marked[0].id) : void 0;
}
/** @deprecated Use tryResolveSoleAgentId; accepts raw shipped markers only for input compatibility. */
function tryResolveDefaultAgentId(cfg) {
return tryResolveRawLegacyDefaultAgentId(cfg) ?? tryResolveSoleAgentId(cfg);
}
/** Preserves legacy data locators independently of the configured runtime owner. */
function tryResolveLegacyDataOwner(cfg) {
const retained = getRetainedLegacyDefaultAgentId(cfg);
return retained && listAgentIds(cfg).includes(retained) ? retained : tryResolveDefaultAgentId(cfg);
}
//#endregion
//#region src/agents/workspace-default-path.ts
/**
* Default agent workspace resolver.
*
* Derives the process workspace directory from env, profile, and home-directory state.
*/
/** Resolve the default agent workspace directory from env/profile/home state. */
function resolveDefaultAgentWorkspaceDir(env = process.env, homedir = os.homedir) {
const workspaceDir = env.OPENCLAW_WORKSPACE_DIR?.trim();
if (workspaceDir) return path.resolve(workspaceDir);
if (env.OPENCLAW_STATE_DIR?.trim()) return path.join(resolveStateDir(env, homedir), "workspace");
const home = resolveRequiredHomeDir(env, homedir);
const profile = env.OPENCLAW_PROFILE?.trim();
if (profile && normalizeOptionalLowercaseString(profile) !== "default") return path.join(resolveProfileStateDir(profile, env, homedir), "workspace");
return path.join(home, ".openclaw", "workspace");
}
resolveDefaultAgentWorkspaceDir();
//#endregion
//#region src/agents/agent-scope-config.ts
/** Resolves configured agent ids, directories, workspaces, and merged agent defaults. */
/** Strip null bytes from paths to prevent ENOTDIR errors. */
function stripNullBytes(s) {
return s.replaceAll("\0", "");
}
let activeAgentRosterFactsBatch;
const immutableAgentRosterFacts = /* @__PURE__ */ new WeakMap();
function readAgentRosterFacts(cfg) {
if (void 0 === cfg) return activeAgentRosterFactsBatch.facts;
if (!isDeeplyFrozenPlainData(cfg)) return;
const legacyOwner = getRetainedLegacyDefaultAgentId(cfg);
let cached = immutableAgentRosterFacts.get(cfg);
if (!cached || cached.legacyOwner !== legacyOwner) {
cached = {
legacyOwner,
facts: {}
};
immutableAgentRosterFacts.set(cfg, cached);
}
return cached.facts;
}
/** Preserves legacy data locators independently of the configured runtime owner. */
function tryResolveLegacyDataOwnerAgentId(cfg) {
const facts = readAgentRosterFacts(cfg);
if (facts?.legacyDataOwnerAgentId) return facts.legacyDataOwnerAgentId.value;
const value = tryResolveLegacyDataOwner(cfg);
if (facts) facts.legacyDataOwnerAgentId = { value };
return value;
}
function resolveAgentEntry(cfg, agentId) {
const id = normalizeAgentId(agentId);
const facts = readAgentRosterFacts(cfg);
if (facts) {
const found = (facts.entryByNormalizedId ??= buildAgentEntryIndex(cfg)).get(id);
return found ? found.clone ? { ...found.entry } : found.entry : void 0;
}
const roster = readAgentRosterProperty(cfg);
if (roster?.kind === "entries" && isRecord(roster.value)) {
const entries = roster.value;
for (const key in entries) {
if (!Object.hasOwn(entries, key)) continue;
const entry = entries[key];
if (isRecord(entry) && normalizeAgentId(key) === id) return {
...entry,
id: key
};
}
return;
}
if (roster?.kind === "list" && Array.isArray(roster.value)) return roster.value.find((entry) => entry !== null && typeof entry === "object" && normalizeAgentId(entry.id) === id);
}
/**
* First-match index over the projected roster for batch point lookups.
*
* Keyed entries must stay clone-on-read (callers may mutate the returned
* entry); list entries keep the original object, matching the direct
* traversal semantics of `resolveAgentEntry` outside a batch.
*/
function buildAgentEntryIndex(cfg) {
const index = /* @__PURE__ */ new Map();
for (const { entry, source } of listAgentEntriesWithSource(cfg)) {
const normalizedId = normalizeAgentId(entry?.id);
if (!index.has(normalizedId)) index.set(normalizedId, {
clone: source.kind === "entries",
entry
});
}
return index;
}
/** Resolves merged config for one agent id. */
function resolveAgentConfig(cfg, agentId) {
const id = normalizeAgentId(agentId);
const entry = resolveAgentEntry(cfg, id) ?? (!hasAgentRosterProperty(cfg) && id === "main" ? { id } : void 0);
if (!entry) return;
const agentDefaults = cfg.agents?.defaults;
return {
name: readStringValue(entry.name),
workspace: readStringValue(entry.workspace),
agentDir: readStringValue(entry.agentDir),
model: typeof entry.model === "string" || entry.model && typeof entry.model === "object" ? entry.model : void 0,
...entry.models ? { models: entry.models } : {},
...entry.params ? { params: entry.params } : {},
...entry.runtime ? { runtime: entry.runtime } : {},
...hasExplicitModelPolicyAllow(entry.modelPolicy) ? { modelPolicy: entry.modelPolicy } : {},
...entry.agentRuntime ? { agentRuntime: entry.agentRuntime } : {},
utilityModel: readStringValue(entry.utilityModel),
decisionModel: readStringValue(entry.decisionModel),
thinkingDefault: entry.thinkingDefault,
verboseDefault: entry.verboseDefault ?? agentDefaults?.verboseDefault,
toolProgressDetail: entry.toolProgressDetail ?? agentDefaults?.toolProgressDetail,
reasoningDefault: entry.reasoningDefault,
fastModeDefault: entry.fastModeDefault ?? agentDefaults?.fastModeDefault,
contextInjection: entry.contextInjection,
bootstrapMaxChars: entry.bootstrapMaxChars,
bootstrapTotalMaxChars: entry.bootstrapTotalMaxChars,
experimental: typeof entry.experimental === "object" && entry.experimental ? {
...agentDefaults?.experimental,
...entry.experimental
} : agentDefaults?.experimental,
skills: Array.isArray(entry.skills) ? entry.skills : void 0,
memory: entry.memory,
humanDelay: entry.humanDelay,
typingMode: entry.typingMode ?? agentDefaults?.typingMode,
tts: entry.tts,
contextLimits: typeof entry.contextLimits === "object" && entry.contextLimits ? {
...agentDefaults?.contextLimits,
...entry.contextLimits
} : agentDefaults?.contextLimits,
heartbeat: entry.heartbeat,
identity: entry.identity,
groupChat: entry.groupChat,
subagents: typeof entry.subagents === "object" && entry.subagents ? entry.subagents : void 0,
embeddedAgent: typeof entry.embeddedAgent === "object" && entry.embeddedAgent ? entry.embeddedAgent : void 0,
sandbox: entry.sandbox,
tools: entry.tools
};
}
function resolveAgentWorkspaceDir(cfg, agentId, env = process.env) {
const id = normalizeAgentId(agentId);
const configured = resolveAgentConfig(cfg, id)?.workspace?.trim();
if (configured) return stripNullBytes(resolveUserPath(configured, env));
const inheritedWorkspaceAgentId = tryResolveLegacyDataOwnerAgentId(cfg);
const fallback = cfg.agents?.defaults?.workspace?.trim();
if (inheritedWorkspaceAgentId && id === inheritedWorkspaceAgentId) {
if (fallback) return stripNullBytes(resolveUserPath(fallback, env));
return stripNullBytes(resolveDefaultAgentWorkspaceDir(env));
}
if (fallback) return stripNullBytes(path.join(resolveUserPath(fallback, env), id));
const stateDir = resolveStateDir(env);
return stripNullBytes(path.join(stateDir, `workspace-${id}`));
}
function resolveEffectiveAgentDir(cfg, agentId, deps) {
const id = normalizeAgentId(agentId);
const configured = resolveAgentConfig(cfg, id)?.agentDir?.trim();
const env = deps?.env ?? process.env;
return configured ? resolveUserPath(configured, env, deps?.homedir) : path.join(resolveStateDir(env, deps?.homedir), "agents", id, "agent");
}
//#endregion
//#region src/config/io.read-helpers.ts
function coerceConfig(value) {
return asOptionalRecord(value) ?? {};
}
function normalizeConfigIoDeps(overrides = {}) {
const env = overrides.env ?? process.env;
return {
fs: overrides.fs ?? fsSync,
json5: overrides.json5 ?? JSON5,
env,
lowerPrecedenceEnv: overrides.lowerPrecedenceEnv ?? {},
homedir: overrides.homedir ?? (() => resolveRequiredHomeDir(env, os.homedir)),
configPath: overrides.configPath ?? "",
logger: overrides.logger ?? console,
measure: overrides.measure ?? (async (_name, run) => await run()),
suppressFutureVersionWarning: overrides.suppressFutureVersionWarning ?? (isTruthyEnvValue(env.OPENCLAW_UPDATE_IN_PROGRESS) || isTruthyEnvValue(env.OPENCLAW_UPDATE_POST_CORE)),
observe: overrides.observe ?? true
};
}
function parseConfigJson5(raw, json5 = JSON5) {
try {
return {
ok: true,
parsed: parseJsonWithJson5Fallback(raw, json5)
};
} catch (err) {
return {
ok: false,
error: String(err)
};
}
}
function resolveConfigIncludesForRead(parsed, configPath, deps, includeFileHashesForWrite, includeFileTargetsForWrite, includeFilePathsForWatch, onIncludeResolved) {
const allowedRoots = resolveIncludeRoots(deps.env, deps.homedir);
const recordIncludeWatchPath = (resolvedPath) => {
includeFilePathsForWatch?.add(path.normalize(resolvedPath));
};
const recordIncludeTarget = (resolvedPath, canonicalPath) => {
if (!includeFileTargetsForWrite) return;
const normalizedPath = path.normalize(resolvedPath);
try {
includeFileTargetsForWrite[normalizedPath] = path.normalize(canonicalPath ?? resolveConfigIncludeWritePath({
configPath,
includePath: resolvedPath,
allowedRoots
}));
} catch {}
};
return resolveConfigIncludes(parsed, configPath, {
readFile: (candidate) => deps.fs.readFileSync(candidate, "utf-8"),
onLexicalPath: recordIncludeWatchPath,
onIncludeResolved,
readFileWithGuards: ({ includePath, resolvedPath, rootRealDir }) => {
try {
const raw = readConfigIncludeFileWithGuards({
includePath,
resolvedPath,
rootRealDir,
ioFs: deps.fs,
onResolvedPath: (canonicalPath) => {
recordIncludeWatchPath(canonicalPath);
recordIncludeTarget(resolvedPath, canonicalPath);
}
});
if (includeFileHashesForWrite) includeFileHashesForWrite[path.normalize(resolvedPath)] = hashConfigIncludeRaw(raw);
return raw;
} catch (error) {
const missing = collectErrorGraphCandidates(error, (current) => [current.cause]).some((candidate) => extractErrorCode(candidate) === "ENOENT");
if (includeFileHashesForWrite && missing) includeFileHashesForWrite[path.normalize(resolvedPath)] = hashConfigIncludeRaw(null);
if (missing) recordIncludeTarget(resolvedPath);
throw error;
}
},
parseJson: (raw) => deps.json5.parse(raw)
}, { allowedRoots });
}
function resolveConfigForRead(resolvedIncludes, env, lowerPrecedenceEnv = {}) {
if (resolvedIncludes && typeof resolvedIncludes === "object" && "env" in resolvedIncludes) applyConfigEnvVars(resolvedIncludes, env, { lowerPrecedenceEnv });
const envWarnings = [];
const pendingEnvSecretRefs = /* @__PURE__ */ new Map();
const resolvedEnvSecretRefs = /* @__PURE__ */ new Map();
const resolvedConfigRaw = resolveConfigEnvVars(resolvedIncludes, env, {
onMissing: (warning) => envWarnings.push(warning),
onPendingEnvSecretRef: (id, configPath) => pendingEnvSecretRefs.set(configPath, id),
onResolvedEnvSecretRef: (id, configPath) => resolvedEnvSecretRefs.set(configPath, id)
});
return {
resolvedConfigRaw,
envSnapshotForRestore: cloneEnvWithPlatformSemantics(env),
envWarnings,
resolutionFacts: createConfigResolutionFacts(envWarnings, pendingEnvSecretRefs, coerceConfig(resolvedConfigRaw).secrets?.defaults?.env, resolvedEnvSecretRefs)
};
}
//#endregion
//#region src/infra/zstd-codec.ts
let resolvedCodec;
/** Resolve the process runtime once; persisted readers must reject unsupported compressed data. */
function resolveZstdCodec() {
if (resolvedCodec !== void 0) return resolvedCodec;
if (typeof zlib.zstdCompressSync !== "function" || typeof zlib.zstdDecompressSync !== "function") {
resolvedCodec = null;
return resolvedCodec;
}
const compress = zlib.zstdCompressSync.bind(zlib);
const decompress = zlib.zstdDecompressSync.bind(zlib);
resolvedCodec = {
compress: (data, level, checksum) => {
if (level === void 0 && checksum === void 0) return compress(data);
return compress(data, { params: {
...level === void 0 ? {} : { [zlib.constants.ZSTD_c_compressionLevel]: level },
...checksum === void 0 ? {} : { [zlib.constants.ZSTD_c_checksumFlag]: checksum }
} });
},
decompress: (data, maxOutputLength) => decompress(data, maxOutputLength === void 0 ? void 0 : { maxOutputLength })
};
return resolvedCodec;
}
resolveZstdCodec();
//#endregion
//#region src/config/sessions/paths.ts
function resolveSessionTranscriptsDirForAgent(agentId, env = process.env, homedir = () => resolveRequiredHomeDir(env, os.homedir)) {
if (!agentId?.trim()) throw new Error("Session storage path requires an explicit agent id.");
const root = resolveStateDir(env, homedir);
const id = normalizeAgentId(agentId);
return path.join(root, "agents", id, "sessions");
}
var SessionStoreAgentIdRequiredError = class extends Error {
constructor() {
super("Session store path requires an explicit agent id.");
this.name = "SessionStoreAgentIdRequiredError";
}
};
/** Resolves fixed literal paths without an owner; derived or templated paths require agentId. */
function resolveSessionStorePathCore(store, opts) {
return resolveSessionStorePathWithContext(store, opts, { cwd: process.cwd() });
}
/** Internal async readers capture their relative-path base before yielding. */
function resolveSessionStorePathWithContext(store, opts, context) {
const env = opts?.env ?? process.env;
const homedir = () => resolveRequiredHomeDir(env, os.homedir);
if (!store) {
if (!opts?.agentId?.trim()) throw new SessionStoreAgentIdRequiredError();
const agentId = normalizeAgentId(opts.agentId);
return path.join(resolveSessionTranscriptsDirForAgent(agentId, env, homedir), "sessions.json");
}
let expandedStore = store;
if (expandedStore.includes("{agentId}")) {
if (!opts?.agentId?.trim()) throw new SessionStoreAgentIdRequiredError();
const agentId = normalizeAgentId(opts.agentId);
expandedStore = expandedStore.replaceAll("{agentId}", agentId);
}
if (expandedStore.startsWith("~")) return path.resolve(context.cwd, expandHomePrefix(expandedStore, {
home: resolveRequiredHomeDir(env, homedir),
env,
homedir
}));
return path.resolve(context.cwd, expandedStore);
}
//#endregion
//#region src/config/sessions/session-sqlite-target-paths.ts
/** Inventory main-file locators, including families whose main file is missing. */
function listSqliteTargetCandidatePathsInDirectory(directory) {
let entries;
try {
entries = readdirSync(directory, { withFileTypes: true });
} catch (error) {
if (hasErrnoCode(error, "ENOENT")) return [];
throw error;
}
return [...new Set(entries.filter((entry) => (entry.isFile() || entry.isSymbolicLink()) && /\.sqlite(?:-(?:wal|shm|journal))?$/u.test(entry.name)).map((entry) => path.join(directory, entry.name.replace(/-(?:wal|shm|journal)$/u, ""))))].toSorted();
}
/** Resolves only the legacy unsuffixed target, without reading ownership state. */
function resolveUnsuffixedSqliteTargetFromSessionStorePath(storePath) {
const resolved = path.resolve(storePath);
if (resolved.endsWith(".sqlite")) {
const agentId = resolveAgentIdFromSqliteDatabasePath(resolved);
return {
path: resolved,
...agentId ? { agentId } : { shared: true }
};
}
const sessionsDir = path.dirname(resolved);
if (path.basename(resolved) !== "sessions.json") {
const sqliteBaseName = path.basename(resolved, path.extname(resolved)) || "openclaw-agent";
return { path: path.join(sessionsDir, `${sqliteBaseName}.sqlite`) };
}
if (path.basename(sessionsDir) !== "sessions") return { path: path.join(sessionsDir, "openclaw-agent.sqlite") };
const agentDir = path.dirname(sessionsDir);
if (path.basename(path.dirname(agentDir)) !== "agents") return { path: path.join(sessionsDir, "openclaw-agent.sqlite") };
return {
agentId: normalizeAgentId(path.basename(agentDir)),
path: path.join(agentDir, "agent", "openclaw-agent.sqlite")
};
}
/** List inspection candidates without opening stores or assigning writable ownership. */
function listSqliteTargetCandidatePathsForSessionStorePath(storePath) {
const unsuffixedTarget = resolveUnsuffixedSqliteTargetFromSessionStorePath(storePath);
if (unsuffixedTarget.agentId || unsuffixedTarget.shared) return [unsuffixedTarget.path];
const directory = path.dirname(unsuffixedTarget.path);
const baseName = path.basename(unsuffixedTarget.path, ".sqlite");
const candidateNames = /* @__PURE__ */ new Set([path.basename(unsuffixedTarget.path)]);
try {
for (const fileName of readdirSync(directory)) {
const databaseName = fileName.replace(/-(?:wal|shm|journal)$/u, "");
if (databaseName.startsWith(`${baseName}.`) && databaseName.endsWith(".sqlite")) candidateNames.add(databaseName);
}
} catch (error) {
if (!hasErrnoCode(error, "ENOENT")) throw error;
}
return [...candidateNames].map((fileName) => path.join(directory, fileName));
}
/** Extracts the agent id from the canonical per-agent SQLite database path. */
function resolveAgentIdFromSqliteDatabasePath(databasePath) {
if (path.basename(databasePath) !== "openclaw-agent.sqlite") return;
const agentDbDir = path.dirname(databasePath);
if (path.basename(agentDbDir) !== "agent") return;
const agentDir = path.dirname(agentDbDir);
if (path.basename(path.dirname(agentDir)) !== "agents") return;
return normalizeAgentId(path.basename(agentDir));
}
//#endregion
//#region src/config/sessions/targets-configured-agents.ts
/** Lists agent ids whose session stores should be considered configured. */
function listConfiguredSessionStoreAgentIds(cfg) {
const ids = new Set(listAgentIds(cfg).map((agentId) => normalizeAgentId(agentId)));
const addAcpAgentId = (agentId) => {
const raw = agentId?.trim() ?? "";
if (!raw || raw === "*") return;
ids.add(normalizeAgentId(raw));
};
addAcpAgentId(cfg.acp?.defaultAgent);
for (const agentId of cfg.acp?.allowedAgents ?? []) addAcpAgentId(agentId);
for (const agent of listAgentEntries(cfg)) if (agent.runtime?.type === "acp") addAcpAgentId(agent.runtime.acp?.agent ?? agent.id);
return [...ids];
}
//#endregion
//#region src/config/agent-store-source.ts
/** Inspect authored SQLite locators without runtime config, plugin loading, or state access. */
function readAgentStorePathsFromConfig(env, stateDir) {
const readEnv = cloneEnvWithPlatformSemantics(env);
readEnv.OPENCLAW_STATE_DIR = stateDir;
const configPath = resolveConfigPath(readEnv, stateDir);
let raw = "{}";
try {
raw = fsSync.readFileSync(configPath, "utf8");
} catch (error) {
if (!hasErrnoCode(error, "ENOENT")) throw error;
}
const parsed = parseConfigJson5(raw);
if (!parsed.ok) throw new Error(`Cannot parse agent storage configuration at ${configPath}.`);
const deps = normalizeConfigIoDeps({
env: readEnv,
configPath,
observe: false
});
const source = resolveConfigIncludesForRead(parsed.parsed, configPath, deps);
if (!isRecord(source) || ["agents", "session"].some((key) => source[key] !== void 0 && !isRecord(source[key]))) throw new Error(`Invalid agent storage configuration at ${configPath}.`);
const roster = readAgentRosterProperty(source);
const hasValidStorageLocator = (entry) => isRecord(entry) && (entry.agentDir === void 0 || typeof entry.agentDir === "string");
if (roster && (roster.kind === "entries" ? !isRecord(roster.value) || Object.values(roster.value).some((entry) => !hasValidStorageLocator(entry)) : !Array.isArray(roster.value) || roster.value.some((entry) => !hasValidStorageLocator(entry) || typeof entry.id !== "string"))) throw new Error(`Invalid agent roster at ${configPath}.`);
if (isRecord(source.session) && source.session.store !== void 0 && typeof source.session.store !== "string") throw new Error(`Invalid session storage configuration at ${configPath}.`);
const resolved = resolveConfigForRead(source, readEnv);
if (resolved.envWarnings.some((warning) => warning.configPath === "session.store" || warning.configPath.endsWith(".agentDir") || warning.configPath.startsWith("agents.list[") && warning.configPath.endsWith(".id") || warning.configPath === "acp.defaultAgent" || warning.configPath.startsWith("acp.allowedAgents") || warning.configPath.endsWith(".runtime.type") || warning.configPath.endsWith(".runtime.acp.agent"))) throw new Error(`Unresolved environment reference in agent storage configuration at ${configPath}.`);
const config = coerceConfig(resolved.resolvedConfigRaw);
const agentIds = listAgentIds(config);
const paths = new Set(agentIds.map((agentId) => path.join(resolveEffectiveAgentDir(config, agentId, { env: readEnv }), "openclaw-agent.sqlite")));
const stores = new Set(listConfiguredSessionStoreAgentIds(config).map((agentId) => resolveSessionStorePathCore(config.session?.store, {
agentId,
env: readEnv
})));
for (const store of stores) for (const candidate of listSqliteTargetCandidatePathsForSessionStorePath(store)) paths.add(candidate);
for (const directory of [readEnv.OPENCLAW_AGENT_DIR, readEnv.PI_CODING_AGENT_DIR]) if (directory?.trim()) paths.add(path.join(resolveUserPath(directory, readEnv), "openclaw-agent.sqlite"));
return [...paths];
}
//#endregion
//#region src/state/openclaw-state-db-initialization.ts
/** Capture deletion-history evidence before a native open can create the shared database. */
function prepareStateDatabaseInitialization(pathname, env, initializationAgentPaths = []) {
try {
if ([pathname, resolveQuarantineStorePath(env)].some((databasePath) => resolveSqliteDatabaseFilePaths(databasePath).some((file) => fsSync.lstatSync(file, { throwIfNoEntry: false })))) return { kind: "existing" };
const stateDir = resolveOpenClawStateDirForDatabasePath(pathname);
const candidates = /* @__PURE__ */ new Set([...initializationAgentPaths, ...readAgentStorePathsFromConfig(env, stateDir)]);
const agentsDir = path.join(stateDir, "agents");
if (fsSync.lstatSync(agentsDir, { throwIfNoEntry: false })?.isSymbolicLink()) return { kind: "unavailable" };
let entries = [];
try {
entries = fsSync.readdirSync(agentsDir, { withFileTypes: true });
} catch (error) {
if (!hasErrnoCode(error, "ENOENT")) throw error;
}
for (const entry of entries) {
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
const agentDir = path.join(agentsDir, entry.name, "agent");
if (entry.isSymbolicLink() || fsSync.lstatSync(agentDir, { throwIfNoEntry: false })?.isSymbolicLink()) return { kind: "unavailable" };
candidates.add(path.join(agentDir, "openclaw-agent.sqlite"));
for (const candidate of listSqliteTargetCandidatePathsInDirectory(agentDir)) candidates.add(candidate);
}
for (const candidate of candidates) {
const family = resolveSqliteDatabaseFilePaths(candidate);
const main = fsSync.lstatSync(candidate, { throwIfNoEntry: false });
if (main && (!main.isFile() || main.size > 0) || family.slice(1).some((file) => fsSync.lstatSync(file, { throwIfNoEntry: false }))) return { kind: "existing" };
}
return { kind: "fresh" };
} catch {
return { kind: "unavailable" };
}
}
//#endregion
//#region src/infra/dedupe.ts
/** Creates a bounded in-memory dedupe cache with optional TTL expiry. */
function createDedupeCache(options) {
const ttlMs = resolveNonNegativeIntegerOption(options.ttlMs, 0);
const maxSize = resolveNonNegativeIntegerOption(options.maxSize, 0);
const cache = /* @__PURE__ */ new Map();
let oldestRecordedAt = Number.POSITIVE_INFINITY;
let newestRecordedAt = Number.NEGATIVE_INFINITY;
let timestampsOrdered = true;
const prune = (now) => {
const cutoff = ttlMs > 0 ? now - ttlMs : void 0;
if (cutoff !== void 0 && cutoff >= oldestRecordedAt) {
oldestRecordedAt = Number.POSITIVE_INFINITY;
for (const [entryKey, entry] of cache) if (entry.recordedAt <= cutoff) cache.delete(entryKey);
else if (entry.recordedAt < oldestRecordedAt) {
oldestRecordedAt = entry.recordedAt;
if (timestampsOrdered) break;
}
}
if (maxSize <= 0) {
cache.clear();
oldestRecordedAt = Number.POSITIVE_INFINITY;
return;
}
pruneMapToMaxSize(cache, maxSize);
};
const hasUnexpired = (key, now, touchOnRead) => {
const existing = cache.get(key);
if (!existing) return false;
if (ttlMs > 0 && now - existing.recordedAt >= ttlMs) {
cache.delete(key);
return false;
}
if (touchOnRead) {
existing.recordedAt = now;
cache.delete(key);
cache.set(key, existing);
}
return true;
};
return {
check: (key, now, ownerToken) => {
if (!key) return false;
const checkedAt = now ?? Date.now();
if (ttlMs > 0) {
if (checkedAt < oldestRecordedAt) oldestRecordedAt = checkedAt;
if (timestampsOrdered) {
timestampsOrdered = checkedAt >= newestRecordedAt;
newestRecordedAt = checkedAt;
}
}
if (hasUnexpired(key, checkedAt, true)) return true;
cache.set(key, {
recordedAt: checkedAt,
...ownerToken ? { ownerToken } : {}
});
prune(checkedAt);
return false;
},
peek: (key, now = Date.now()) => {
if (!key) return false;
return hasUnexpired(key, now, false);
},
delete: (key, ownerToken) => {
if (!key) return;
if (ownerToken && cache.get(key)?.ownerToken !== ownerToken) return;
cache.delete(key);
},
clear: () => {
cache.clear();
oldestRecordedAt = Number.POSITIVE_INFINITY;
newestRecordedAt = Number.NEGATIVE_INFINITY;
timestampsOrdered = true;
},
size: () => cache.size
};
}
//#endregion
//#region src/state/openclaw-state-db-permissions.ts
const OPENCLAW_STATE_DIR_MODE = 448;
const OPENCLAW_STATE_FILE_MODE = 384;
const stateDbLog$2 = createSubsystemLogger("state/db");
/** Targets already warned about, so chmod-less filesystems warn once per path. */
const chmodWarnedTargets = createDedupeCache({
ttlMs: 0,
maxSize: 4096
});
function bestEffortChmodSync(target, mode) {
const result = applyPrivateModeSync(target, mode);
if (result.applied || chmodWarnedTargets.check(target)) return;
stateDbLog$2.warn(`skipped permission hardening for ${target}: ${String(result.error)}`);
}
function ensureOpenClawStatePermissions(pathname, env, options = {}) {
const dir = path.dirname(pathname);
const defaultDir = resolveOpenClawStateSqliteDir(env);
const isDefaultStateDatabase = path.resolve(pathname) === path.resolve(resolveOpenClawStateSqlitePath(env));
if (isDefaultStateDatabase && dir !== defaultDir) throw new Error(`OpenClaw state database path resolved outside its state dir: ${pathname}`);
const dirExisted = existsSync(dir);
if (options.createDirectory) mkdirSync(dir, {
recursive: true,
mode: OPENCLAW_STATE_DIR_MODE
});
if (isDefaultStateDatabase || !dirExisted) bestEffortChmodSync(dir, OPENCLAW_STATE_DIR_MODE);
for (const candidate of resolveSqliteDatabaseFilePaths(pathname)) if (existsSync(candidate)) try {
bestEffortChmodSync(candidate, OPENCLAW_STATE_FILE_MODE);
} catch (error) {
if (candidate === pathname || !hasErrnoCode(error, "ENOENT")) throw error;
}
}
//#endregion
//#region src/state/openclaw-state-db-open.ts
const stateDbLog$1 = createSubsystemLogger("state/db");
function assertStateDatabaseIntegrityBeforeMutation(database, pathname) {
const contentVersion = readStateSchemaMigrationVersion(database);
const hasApplicationSchema = database.prepare("SELECT 1 FROM sqlite_master WHERE name NOT LIKE 'sqlite_%' LIMIT 1").get();
if (contentVersion === 0 && hasApplicationSchema || contentVersion > 0 && contentVersion < 19) stateDbLog$1.info("state database schema migration pending; verifying integrity first", {
fromVersion: contentVersion,
path: pathname,
toVersion: 19
});
if (contentVersion !== 19) assertSqliteIntegrity(database, pathname);
}
function openUnpublishedStateDatabase(params) {
const open = (schemaOwned) => {
const existingIdentity = getSqliteWorkerExistingDatabaseIdentity(params.pathname);
const original = params.existingSchema || existingIdentity ? statSync(params.pathname, { bigint: true }) : statSync(params.pathname, {
bigint: true,
throwIfNoEntry: false
});
if (existingIdentity && (!original || `file:${original.dev}:${original.ino}` !== existingIdentity)) throw new Error("SQLite database file identity changed before existing-only open");
if (!original && !schemaOwned) return withStateDatabaseSchemaMaintenance({
databasePath: params.pathname,
busyTimeoutMs: params.busyTimeoutMs
}, () => open(true));
if (original && !original.isFile()) throw new Error(`Existing shared-state database must be a regular file: ${params.pathname}`);
const initialization = prepareStateDatabaseInitialization(params.pathname, params.env, params.initializationAgentPaths);
if (!original) {
quarantineOrphanedSqliteSidecars(params.pathname);
ensureOpenClawStatePermissions(params.pathname, params.env, { createDirectory: true });
}
return openNativeStateDatabase(params, initialization, original);
};
return open(false);
}
function openNativeStateDatabase(params, initialization, original) {
const { busyTimeoutMs, lockFailureReporting } = params;
const assertSameFile = () => {
if (original) {
const current = statSync(params.pathname, { bigint: true });
if (!current.isFile() || current.dev !== original.dev || current.ino !== original.ino || readDatabaseIdentityBirthtime(current) !== readDatabaseIdentityBirthtime(original)) throw new Error(`Existing shared-state database generation changed: ${params.pathname}`);
}
};
const db = openTrackedStateDatabase(params.pathname, {
existingOnly: original !== void 0,
expectedIdentity: original ? `file:${original.dev}:${original.ino}` : void 0
});
let walMaintenance;
try {
assertSameFile();
enableNodeSqliteKyselyStatementCache(db);
setSqliteBusyTimeout(db, busyTimeoutMs);
assertOpenClawStateWriteAllowed({
database: db,
databasePath: params.pathname,
env: params.env
});
if (params.existingSchema) {
assertSameFile();
params.ensureSchema(db, initialization);
assertSameFile();
return {
db,
path: params.pathname,
walMaintenance: {
checkpoint: () => false,
close: () => true,
reclaimFreePages: createSqliteWalReclamationResult
}
};
}
ensureOpenClawStatePermissions(params.pathname, params.env);
const maintenance = runWithSqliteBusyTimeout(db, busyTimeoutMs, () => {
assertSupportedStateSchemaVersion(db, params.pathname);
assertStateDatabaseIntegrityBeforeMutation(db, params.pathname);
configureSqlitePreSchemaPragmas(db, { busyTimeoutMs });
walMaintenance = configureSqliteConnectionPragmas(db, {
busyTimeoutMs,
databaseLabel: "openclaw-state",
databasePath: params.pathname,
onCheckpointError: (error) => stateDbLog$1.warn("Shared-state WAL maintenance failed", {
error: formatErrorMessage(error),
path: params.pathname,
checkpoint: walMaintenance?.health
}),
runMaintenance: (operation) => {
assertStateDatabaseAccessAllowed(params.pathname);
return operation();
},
foreignKeys: true,
synchronous: "NORMAL"
});
params.ensureSchema(db, initialization);
return walMaintenance;
}, { lockFailureReporting });
ensureOpenClawStatePermissions(params.pathname, params.env);
assertSameFile();
return {
db,
path: params.pathname,
walMaintenance: maintenance
};
} catch (error) {
const errors = openClawStateDatabaseCache.closeUnpublishedOpenClawStateDatabaseHandle({
db,
path: params.pathname,
walMaintenance
});
if (error instanceof Error && (isSqliteSchemaVersionError(error) || isTerminalSqliteIntegrityError(error))) params.recordOpenFailure(params.pathname, error);
if (errors.length > 0) throw createSqliteLifecycleAggregateError([error, ...errors], `OpenClaw state database acquisition and cleanup failed for ${params.pathname}.`, error);
throw error;
}
}
//#endregion
//#region src/infra/sqlite-index-schema.ts
const SQLITE_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/u;
/**
* Verify the whole file before schema convergence. Physical corruption belongs
* to explicit Doctor maintenance, including when only an index is damaged.
*/
function verifyAndRepairCanonicalSqliteIndexes(db, databaseLabel, schemaSql, options = {}) {
return runSqliteIntegrityOperationSync(verifyAndRepairCanonicalSqliteIndexSteps(db, databaseLabel, schemaSql, options));
}
function* verifyAndRepairCanonicalSqliteIndexSteps(db, databaseLabel, schemaSql, options = {}) {
const { diagnostics, reuseIntegrity, integrityTables, ...repairOptions } = options;
if (reuseIntegrity) {
if (diagnostics) diagnostics.integrityGateOutcome = "cached";
} else yield* sqliteIntegrityCheckSteps(db, databaseLabel, diagnostics, integrityTables);
const indexesStartedAt = performance.now();
const repairedIndexes = repairCanonicalSqliteIndexes(db, databaseLabel, schemaSql, {
...repairOptions,
verifyPhysicalIntegrity: false
});
if (diagnostics) {
diagnostics.canonicalIndexMs = Math.floor(performance.now() - indexesStartedAt);
diagnostics.repairedIndexCount = repairedIndexes.length;
}
return repairedIndexes;
}
/**
* Restore every named index when SQLite's IF NOT EXISTS semantics preserve a
* same-name definition that no longer matches the committed schema.
*/
function repairCanonicalSqliteIndexes(db, databaseLabel, schemaSql, options = {}) {
const indexes = getCanonicalSqliteNamedIndexContracts(schemaSql);
const indexesByTable = /* @__PURE__ */ new Map();
const repairIndexes = /* @__PURE__ */ new Set();
runSqlitePinnedReadSnapshotSync(db, () => {
for (const index of indexes) {
assertSqliteIdentifier(index.name);
assertSqliteIdentifier(index.tableName);
if (!db.prepare("SELECT 1 FROM main.sqlite_schema WHERE type = 'table' AND name = ?").get(index.tableName)) continue;
const tableIndexes = indexesByTable.get(index.tableName) ?? [];
tableIndexes.push(index);
indexesByTable.set(index.tableName, tableIndexes);
const actual = collectSqliteNamedIndexContract(db, index.name);
if (JSON.stringify(actual) !== JSON.stringify(index.fingerprint)) repairIndexes.add(index);
}
assertNoUnexpectedUniqueIndexes(db, databaseLabel, schemaSql, indexesByTable);
if (options.verifyPhysicalIntegrity !== false) assertSqliteIntegrity(db, databaseLabel);
});
if (repairIndexes.size === 0) return [];
const savepoint = "repair_canonical_indexes";
let activeIndex;
db.exec(`SAVEPOINT ${savepoint};`);
try {
for (const index of repairIndexes) {
activeIndex = index;
const probeName = findUnusedProbeIndexName(db, index.name);
try {
db.exec(createIndexSql(index, probeName));
} catch (error) {
if (options.allowMissingColumns && isMissingColumnError(error)) {
repairIndexes.delete(index);
continue;
}
throw error;
}
db.exec(`DROP INDEX IF EXISTS main.${index.name};`);
db.exec(createIndexSql(index, index.name));
db.exec(`DROP INDEX main.${probeName};`);
}
if (repairIndexes.size === 0) {
db.exec(`RELEASE SAVEPOINT ${savepoint};`);
return [];
}
for (const tableName of indexesByTable.keys()) assertSqliteTableIntegrity(db, databaseLabel, tableName);
assertSqliteIntegrity(db, databaseLabel);
options.validateAfterRepair?.();
db.exec(`RELEASE SAVEPOINT ${savepoint};`);
} catch (error) {
try {
db.exec(`ROLLBACK TO SAVEPOINT ${savepoint};`);
} finally {
db.exec(`RELEASE SAVEPOINT ${savepoint};`);
}
if (error instanceof Error && isTerminalSqliteIntegrityError(error)) throw error;
const detail = error instanceof Error ? error.message : String(error);
throw new Error(`SQLite canonical index ${activeIndex?.name ?? "repair"} failed for ${databaseLabel}: ${detail}`, { cause: error });
}
return [...repairIndexes].map((index) => index.name).toSorted();
}
function assertNoUnexpectedUniqueIndexes(db, databaseLabel, schemaSql, indexesByTable) {
for (const tableName of getCanonicalSqliteTableNames(schemaSql)) {
assertSqliteIdentifier(tableName);
if (!db.prepare("SELECT 1 FROM main.sqlite_schema WHERE type = 'table' AND name = ?").get(tableName)) continue;
const canonicalIndexNames = new Set((indexesByTable.get(tableName) ?? []).map((index) => index.name));
const unexpected = db.prepare(`PRAGMA main.index_list(${tableName})`).all().find((index) => index.unique === 1 && index.origin === "c" && !canonicalIndexNames.has(index.name));
if (unexpected) throw new Error(`SQLite schema is incomplete or noncanonical for ${databaseLabel}: unexpected unique index ${unexpected.name}`);
}
}
function createIndexSql(index, name) {
assertSqliteIdentifier(name);
return `${index.unique ? "CREATE UNIQUE INDEX" : "CREATE INDEX"} main.${name} ${index.definition};`;
}
function findUnusedProbeIndexName(db, canonicalName) {
const prefix = `openclaw_probe_${canonicalName}`;
for (let suffix = 0; suffix < 100; suffix += 1) {
const candidate = suffix === 0 ? prefix : `${prefix}_${suffix}`;
if (!db.prepare("SELECT 1 AS found FROM main.sqlite_schema WHERE name = ?").get(candidate)) return candidate;
}
throw new Error(`could not allocate a probe index name for ${canonicalName}`);
}
function assertSqliteIdentifier(identifier) {
if (!SQLITE_IDENTIFIER_PATTERN.test(identifier)) throw new Error(`invalid SQLite identifier: ${identifier}`);
}
function isMissingColumnError(error) {
return error instanceof Error && error.code === "ERR_SQLITE_ERROR" && /^no such column:/iu.test(error.message);
}
//#endregion
//#region src/state/agent-deletion-journal-history.ts
function hasPreJournalStateSchema(database) {
if (readStateSchemaContentVersion(database) !== 1 || tableExists(database, "config_machine_state") || tableExists(database, "agent_database_leases") || !tableExists(database, "agent_databases") || !tableExists(database, "migration_sources")) return false;
const db = getNodeSqliteKysely(database);
const metadata = executeSqliteQueryTakeFirstSync(database, db.selectFrom("schema_meta").selectAll().where("meta_key", "=", "primary"));
return metadata?.role === "global" && metadata.schema_version === 1 && metadata.agent_id === null && !executeSqliteQueryTakeFirstSync(database, db.selectFrom("migration_sources").select("source_key").where("target_table", "=", "agent_deletion_journal").limit(1));
}
//#endregion
//#region src/state/openclaw-state-db-schema-additive.ts
function resolveLegacyManagedImageRoot(recordJson) {
if (typeof recordJson !== "string") return null;
const record = safeParseJsonRecord(recordJson);
if (!record || !isRecord(record.original)) return null;
const mediaRoot = record.original.mediaRoot;
if (typeof mediaRoot === "string" && mediaRoot.trim()) return path.resolve(mediaRoot);
const originalPath = record.original.path;
if (typeof originalPath !== "string" || !originalPath.trim()) return null;
const resolvedOriginalPath = path.resolve(originalPath);
return path.dirname(path.dirname(path.dirname(resolvedOriginalPath)));
}
function backfillLegacyManagedImageRoots(db) {
const rows = db.prepare("SELECT attachment_id, record_json FROM managed_outgoing_image_records").all();
const updateRoot = db.prepare("UPDATE managed_outgoing_image_records SET original_media_root = ? WHERE attachment_id = ?");
const deleteRecord = db.prepare("DELETE FROM managed_outgoing_image_records WHERE attachment_id = ?");
for (const row of rows) {
const mediaRoot = resolveLegacyManagedImageRoot(row.record_json);
if (mediaRoot) updateRoot.run(mediaRoot, row.attachment_id);
else deleteRecord.run(row.attachment_id);
}
}
function ensureWorkerSessionToolStateSchema(db) {
db.exec([extractSqliteTableSchema(OPENCLAW_STATE_SCHEMA_SQL, "worker_turn_tool_authorities"), extractSqliteTableSchema(OPENCLAW_STATE_SCHEMA_SQL, "worker_session_tool_operations")].join("\n"));
}
/**
* Add the feature-owned first-use columns that a STRICT rebuild cannot skip.
*
* These columns normally stay absent until their owning feature first writes
* them, and the persistent schema contract accepts that shape. The STRICT
* table rebuild is the one caller that cannot: it recreates each table from
* canonical SQL, which already declares these columns, so a database missing
* them fails the canonical column check and rolls the entire repair back.
* Ensuring them immediately before that rebuild matches the shape the rebuild
* produces anyway, and stays scoped to databases old enough to need it.
*/
function ensureFirstUseAdditiveStateColumnsForStrictMigration(db) {
for (const { columnName, dataType, tableName } of CLAW_FIRST_USE_ADDITIVE_STATE_COLUMN_DEFINITIONS) ensureColumn(db, tableName, `${columnName} ${dataType}`);
}
function ensureColumns(db, definitions) {
const added = [];
for (const [tableName, definition] of definitions) {
const columnName = definition.trim().split(/\s+/, 1)[0];
if (columnName && ensureColumn(db, tableName, definition)) added.push({
tableName,
columnName
});
}
return added;
}
/** Runtime pairs new columns with their transforms; full historical repair stays explicit. */
function ensureAdditiveStateColumns(db, scope) {
const repairHistoricalRows = scope === "repair";
const installingGrantGenerations = tableExists(db, "cron_jobs") && !tableHasColumn(db, "cron_jobs", "grant_definition_generation");
ensureWorkerSessionToolStateSchema(db);
for (const { columnName, dataType, tableName } of CLAW_STARTUP_ADDITIVE_STATE_COLUMN_DEFINITIONS) ensureColumn(db, tableName, `${columnName} ${dataType}`);
if (ensureColumn(db, ...ORDERED_STARTUP_ADDITIVE_STATE_COLUMNS.packageUpdatedAt[0])) db.exec("UPDATE claw_package_refs SET updated_at_ms = installed_at_ms;");
ensureColumns(db, ORDERED_STARTUP_ADDITIVE_STATE_COLUMNS.packageIntegrity);
const addedDiagnosticEventSequence = ensureColumn(db, ...ORDERED_STARTUP_ADDITIVE_STATE_COLUMNS.diagnosticSequence[0]);
if (addedDiagnosticEventSequence) db.exec(`
WITH ranked AS (
SELECT
rowid AS event_rowid,
ROW_NUMBER() OVER (
PARTITION BY scope
ORDER BY created_at ASC, rowid ASC
) AS sequence
FROM diagnostic_events
)
UPDATE diagnostic_events
SET sequence = (
SELECT ranked.sequence
FROM ranked
WHERE ranked.event_rowid = diagnostic_events.rowid
);
`);
if (addedDiagnosticEventSequence || repairHistoricalRows) db.exec("DROP INDEX IF EXISTS idx_diagnostic_events_scope_created;");
const addedCronLogColumns = ensureColumns(db, ORDERED_STARTUP_ADDITIVE_STATE_COLUMNS.cronRunLogs);
if (repairHistoricalRows || addedCronLogColumns.some(({ tableName }) => tableName === "cron_run_logs")) backfillCronRunLogEntryJson(db);
if (ensureColumns(db, ORDERED_STARTUP_ADDITIVE_STATE_COLUMNS.acpReplay).length > 0 || repairHistoricalRows) backfillAcpReplayEstimatedBytes(db);
const addedCronJobColumns = ensureColumns(db, ORDERED_STARTUP_ADDITIVE_STATE_COLUMNS.cronJobs);
if (repairHistoricalRows || addedCronJobColumns.some(({ columnName }) => [
"name",
"enabled",
"agent_id",
"payload_kind",
"runtime_updated_at_ms"
].includes(columnName))) backfillCronJobsFromJobJson(db);
if (installingGrantGenerations) carryForwardLegacyCronStandingGrants(db, Date.now());
const addedDeliveryColumns = ensureColumns(db, ORDERED_STARTUP_ADDITIVE_STATE_COLUMNS.deliveryQueue);
if (repairHistoricalRows || addedDeliveryColumns.some(({ tableName }) => tableName === "delivery_queue_entries")) backfillDeliveryQueueEntriesFromEntryJson(db);
if (ensureColumn(db, ...ORDERED_STARTUP_ADDITIVE_STATE_COLUMNS.originalMediaRoot[0])) backfillLegacyManagedImageRoots(db);
ensureColumns(db, ORDERED_STARTUP_ADDITIVE_STATE_COLUMNS.beforeTaskAttribution);
ensureColumns(db, ORDERED_STARTUP_ADDITIVE_STATE_COLUMNS.taskRequester);
ensureColumns(db, ORDERED_STARTUP_ADDITIVE_STATE_COLUMNS.taskRunDetails);
if (repairHistoricalRows) {
repairLegacySubagentSuspensionReasons(db);
repairLegacySubagentExecutionPayloads(db);
repairLegacySubagentRetainedResults(db);
}
ensureColumns(db, ORDERED_STARTUP_ADDITIVE_STATE_COLUMNS.workerEnvironments);
if (repairHistoricalRows || !tableHasColumn(db, "operator_approvals", "resolution_ref")) ensureOperatorApprovalResolutionRefs(db);
}
//#endregion
//#region src/state/openclaw-state-db-startup-checkpoint.ts
const NATIVE_STARTUP_BOOTSTRAP_OBJECTS = /* @__PURE__ */ new Set([
"table:device_auth_tokens",
"index:idx_device_auth_tokens_updated",
"table:device_identities",
"index:idx_device_identities_device",
"table:exec_approvals_config",
"table:macos_port_guardian_records",
"index:idx_macos_port_guardian_records_port",
"table:schema_meta",
"table:state_leases",
"index:idx_state_leases_expiry",
"index:idx_state_leases_owner"
]);
function isUninitializedNativeStartupDatabase(db) {
if (readSqliteUserVersion(db) !== 0) return false;
const objects = db.prepare("SELECT type, name FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%'").all();
if (objects.some(({ type, name }) => typeof type !== "string" || typeof name !== "string" || !NATIVE_STARTUP_BOOTSTRAP_OBJECTS.has(`${type}:${name}`))) return false;
const tableNames = new Set(objects.filter(({ type }) => type === "table").map(({ name }) => name));
if (tableNames.has("schema_meta") && db.prepare("SELECT 1 FROM schema_meta LIMIT 1").get()) return false;
return !(tableNames.has("state_leases") && db.prepare("SELECT 1 FROM state_leases LIMIT 1").get());
}
//#endregion
//#region src/state/openclaw-state-db-schema-runtime.ts
const stateDbLog = createSubsystemLogger("state/db");
/** Runtime converges schema; historical row repair belongs to explicit Doctor maintenance. */
function ensureOpenClawStateRuntimeSchema(db, pathname, env, initialization, busyTimeoutMs = OPENCLAW_SQLITE_BUSY_TIMEOUT_MS, initializeNativeOnly = false) {
if (isExistingOpenClawStateSchema(pathname, db)) {
assertExistingOpenClawStateRuntimeSchema(db, pathname);
assertOpenClawStateWriteAllowed({
database: db,
databasePath: pathname,
env
});
return [];
}
try {
if (isOpenClawStateSchemaFastPathEligible(db, pathname)) {
assertOpenClawStateWriteAllowed({
database: db,
databasePath: pathname,
env
});
return [];
}
} catch (error) {
if (!db.isOpen || error instanceof StartupMaintenanceRequiredError) throw error;
}
return withStateDatabaseSchemaMaintenance({
databasePath: pathname,
busyTimeoutMs
}, () => {
const now = Date.now();
const retiredTableChanges = [];
const applied = runStateSchemaMigrationTransaction(db, pathname, () => {
assertOpenClawStateWriteAllowed({
database: db,
databasePath: pathname,
env
});
assertSupportedStateSchemaVersion(db, pathname);
if (initializeNativeOnly && !isUninitializedNativeStartupDatabase(db)) return [];
const previousVersion = readStateSchemaMigrationVersion(db);
const includeAgentDeletionJournal = tableExists(db, "agent_deletion_journal") || hasPreJournalStateSchema(db) || initialization.kind === "fresh" && isUninitializedNativeStartupDatabase(db);
if (previousVersion === 19) {
assertNoLegacyStateRuntimeRepair(db, pathname);
const indexes = verifyAndRepairCanonicalSqliteIndexes(db, pathname, OPENCLAW_STATE_SCHEMA_SQL, {
allowMissingColumns: true,
validateAfterRepair: () => assertCurrentStateRuntimeSchema(db, pathname)
});
ensureAdditiveStateColumns(db, "runtime");
assertCurrentStateRuntimeSchema(db, pathname);
writeCurrentStateSchemaMetadata(db, now);
return indexes.length > 0 ? [`Rebuilt canonical shared-state SQLite indexes (${indexes.length})`] : [];
}
openClawStateMigrationAssertions.get(previousVersion)?.(db, { pathname });
assertSqliteIntegrity(db, pathname);
dropLegacyStateTables(db);
const changes = runRetiredStateTableMigrations(db, previousVersion);
retiredTableChanges.push(...changes);
if (migrateSingletonStateFoldInV12(db, previousVersion)) changes.push("Folded singleton state tables into config_machine_state (v12)");
if (migrateWorkerPlacementExecutionModeSchema(db, previousVersion)) changes.push("Migrated cloud worker placements to execution modes");
const pathMigration = migrateAgentDatabaseRelativePaths(db, previousVersion, pathname);
changes.push(...describeAgentPathMigration(pathMigration));
ensureAdditiveStateColumns(db, "repair");
for (const migration of versionedStateMigrations) if (migration.migrate(db, previousVersion)) changes.push(migration.applied);
migrateSessionWatchCursorProvenance(db);
assertCanonicalStateSchemaShape(db, pathname);
executeCanonicalStateSchema(db, {
includeVersionLazyAdditiveTables: true,
includeAgentDeletionJournal
});
migrateLegacyCronRunLogsToTaskRuns(db);
if (previousVersion < 3) {
repairLegacyGatewayRestartHandoffsForStrictMigration(db);
ensureFirstUseAdditiveStateColumnsForStrictMigration(db);
const strict = migrateSqliteSchemaToStrictInTransaction(db, getOpenClawStateRuntimeSchema({
includeVersionLazyAdditiveTables: true,
includeAgentDeletionJournal
}), { databaseLabel: pathname });
if (strict.migratedTables.length > 0) changes.push(`Migrated shared state tables to SQLite STRICT typing (${strict.migratedTables.length})`);
}
repairCanonicalSqliteIndexes(db, pathname, OPENCLAW_STATE_SCHEMA_SQL, { verifyPhysicalIntegrity: false });
writeCurrentStateSchemaMetadata(db, now);
assertOpenClawStateDatabaseForMaintenance(db, { pathname });
warnAgentPathMigration(stateDbLog, pathMigration, pathname);
return changes;
}, {
busyTimeoutMs,
databaseLabel: pathname,
operationLabel: "state.schema.ensure"
});
retiredTableChanges.forEach(logRetiredStateTableMigration);
return applied;
});
}
//#endregion
//#region src/state/openclaw-state-db.ts
/** Reject a fresh shared-state open after known corruption until repair clears it. */
function assertOpenClawStateDatabaseFreshOpenAllowed(options = {}) {
const env = options.env ?? process.env;
openClawStateDatabaseCache.assertOpenClawStateDatabaseFreshOpenAllowedAtPath(resolveDatabasePath(options), env);
}
const deferredStateDatabases = /* @__PURE__ */ new WeakSet();
/** Open or return a cached shared state database after schema and migration checks. */
function openOpenClawStateDatabaseWithBusyTimeout(options = {}, busyTimeoutMs = OPENCLAW_SQLITE_BUSY_TIMEOUT_MS, lockFailureReporting = "report", remainingColdAdmissionTimeout) {
getOpenClawDatabaseMaintenanceScope()?.assertAdmission();
const env = options.env ?? process.env;
if (options.database) {
assertStateDatabaseSchemaAdmission(options.database);
assertOpenClawStateWriteAllowed({
database: options.database.db,
databasePath: options.database.path,
env
});
observeOpenClawDatabaseMaintenanceResource(options.database.db);
openClawStateDatabaseCache.touchStateDatabase(options.database);
return options.database;
}
const pathname = resolveDatabasePath(options);
const existingSchema = isExistingOpenClawStateSchema(pathname);
const cached = openClawStateDatabaseCache.getCachedOpenClawStateDatabase(pathname);
if (cached?.db.isOpen) {
openClawStateDatabaseCache.assertOpenClawStateDatabaseOpenAllowed(pathname);
assertStateDatabaseSchemaAdmission(cached);
assertOpenClawStateWriteAllowed({
database: cached.db,
databasePath: pathname,
env,
schemaReady: true
});
observeOpenClawDatabaseMaintenanceResource(cached.db);
if (!existingSchema && deferredStateDatabases.has(cached.db)) {
reconcileOpenClawStateSchemaPublication(options);
if (readSqliteUserVersion(cached.db) === 19) deferredStateDatabases.delete(cached.db);
}
return cached;
}
let unpublished;
try {
const open = (remaining) => {
getOpenClawDatabaseMaintenanceScope()?.assertAdmission();
openClawStateDatabaseCache.assertOpenClawStateDatabaseOpenAllowed(pathname);
assertOpenClawStateDatabaseFreshOpenAllowed(options);
if (cached) openClawStateDatabaseCache.closeStaleCachedOpenClawStateDatabase(cached);
return openUnpublishedStateDatabase({
pathname,
env,
busyTimeoutMs: remaining(),
lockFailureReporting,
existingSchema,
initializationAgentPaths: options.initializationAgentPaths,
ensureSchema: (database, initialization) => ensureOpenClawStateRuntimeSchema(database, pathname, env, initialization, remaining()),
recordOpenFailure: recordOpenClawStateDatabaseOpenFailure
});
};
unpublished = remainingColdAdmissionTimeout ? open(remainingColdAdmissionTimeout) : withStateDatabaseColdAdmission({
databasePath: pathname,
busyTimeoutMs
}, open);
setSqliteBusyTimeout(unpublished.db, busyTimeoutMs);
} catch (error) {
if (lockFailureReporting === "report" || !isOpenClawStateWriteContentionError(error)) openClawStateDatabaseCache.recordOpenClawStateDatabaseLifecycleOpenError(pathname, error);
if (unpublished) {
const errors = openClawStateDatabaseCache.closeUnpublishedOpenClawStateDatabaseHandle(unpublished);
if (errors.length > 0) throw createSqliteLifecycleAggregateError([error, ...errors], `Fresh OpenClaw state database open failed releasing access and closing its unpublished handle for ${pathname}.`, error);
}
throw error;
}
if (existingSchema) recordExistingOpenClawStateSchemaDatabase(unpublished.db, pathname);
const database = openClawStateDatabaseCache.publishOpenClawStateDatabase(unpublished, env);
try {
if (!existingSchema && readSqliteUserVersion(database.db) < 19) {
deferredStateDatabases.add(database.db);
reconcileOpenClawStateSchemaPublication(options);
}
return database;
} catch (error) {
if (database.db.isOpen) setSqliteBusyTimeout(database.db, OPENCLAW_SQLITE_BUSY_TIMEOUT_MS);
throw error;
}
}
/** Open or return a cached shared state database after schema and migration checks. */
function openOpenClawStateDatabase(options = {}) {
return openOpenClawStateDatabaseWithBusyTimeout(options);
}
/** The Gateway watcher also publishes without requiring a new physical database open. */
function reconcileOpenClawStateSchemaPublication(options = {}) {
if (isExistingOpenClawStateSchema(options.database?.path ?? resolveDatabasePath(options))) return;
const pending = withExistingOpenClawStateDatabaseReadOnly(({ db }) => {
if (readSqliteUserVersion(db) >= 19 || readStateSchemaContentVersion(db) < 19) return;
return { blocker: readStateSchemaPublicationBlocker(db) };
}, options);
if (!pending || pending.blocker) return pending?.blocker;
const pathname = resolveDatabasePath(options);
try {
return withStateDatabaseSchemaMaintenance({ databasePath: pathname }, () => runOpenClawStateWriteTransaction(({ db }) => {
const blocker = readStateSchemaPublicationBlocker(db);
if (blocker) return blocker;
assertOpenClawStateDatabaseForMaintenance(db, { pathname });
markCurrentStateSchemaVersion(db);
}, options, { operationLabel: "state.schema.publish" }));
} catch (error) {
if (error instanceof StateSchemaMutationConflictError) return;
throw error;
}
}
/** Run a synchronous immediate transaction against the shared state database. */
function runOpenClawStateWriteTransaction(operation, options = {}, transactionOptions = {}) {
getOpenClawDatabaseMaintenanceScope()?.assertAdmission();
const existing = options.database ?? getOpenClawStateDatabaseIfOpen(options);
if (existing) isExistingOpenClawStateSchema(existing.path, existing.db);
let database = existing;
let callbackEntered = false;
let committed;
const execute = (remaining) => {
const acquired = options.database ? openOpenClawStateDatabase(options) : database ?? openOpenClawStateDatabaseWithBusyTimeout(options, 5e3, "report", remaining);
database = acquired;
return {
database: acquired,
value: runManagedStateTransaction(acquired.db, () => {
assertStateDatabaseSchemaAdmission(acquired);
assertOpenClawStateWriteAllowed({
database: acquired.db,
databasePath: acquired.path,
env: options.env ?? process.env,
schemaReady: !options.database && acquired === getOpenClawStateDatabaseIfOpen(options)
});
observeOpenClawDatabaseMaintenanceResource(acquired.db);
callbackEntered = true;
return operation(acquired);
}, {
databaseLabel: acquired.path,
...transactionOptions,
...remaining ? { busyTimeoutMs: remaining() } : {},
operationLabel: transactionOptions.operationLabel ?? "state.write"
})
};
};
try {
committed = existing ? execute() : withStateDatabaseColdAdmission({
databasePath: resolveDatabasePath(options),
busyTimeoutMs: transactionOptions.busyTimeoutMs ?? 5e3,
canRetry() {
if (callbackEntered || database && (!database.db.isOpen || database.db.isTransaction)) return false;
if (database) assertTransactionUsable(database.db);
return true;
}
}, execute);
} catch (error) {
if (database) openClawStateDatabaseCache.evictOpenClawStateDatabaseAfterCorruption(database, error);
throw error;
}
try {
if (!isExistingOpenClawStateSchema(committed.database.path, committed.database.db)) ensureOpenClawStatePermissions(committed.database.path, options.env ?? process.env);
} catch {}
return committed.value;
}
/**
* Return a shared state handle this process already holds open, if any.
*
* Read-only callers use this to avoid opening a connection per call; it never
* creates, repairs, or registers a handle.
*/
function getOpenClawStateDatabaseIfOpen(options = {}) {
const pathname = resolveDatabasePath(options);
isExistingOpenClawStateSchema(pathname);
const cached = openClawStateDatabaseCache.getCachedOpenClawStateDatabase(pathname);
if (cached?.db.isOpen) isExistingOpenClawStateSchema(cached.path, cached.db);
return cached?.db.isOpen ? cached : void 0;
}
function assertStateDatabaseSchemaAdmission(database) {
if (isExistingOpenClawStateSchema(database.path, database.db)) {
const location = database.db.location();
if (!location) throw new Error("Existing shared-state schema admission requires a filesystem-backed database.");
if (!isExistingOpenClawStateSchema(location, database.db)) throw new Error("Existing shared-state schema admission requires its selected physical database.");
assertExistingOpenClawStateRuntimeSchema(database.db, database.path);
}
}
//#endregion
export { safeParseJson as S, getConfigResolutionFacts as _, resolveAgentWorkspaceDir as a, isDeeplyFrozenPlainData as b, isArtifactPreservingStateRead as c, resolveSqliteDatabaseFilePaths as d, err as f, getAuthoredConfigSecretRef as g, copyConfigResolutionFactsExcept as h, readSecureFile as i, withExistingOpenClawStateDatabaseArtifactPreservingReadOnly as l, cloneConfigWithResolutionFacts as m, runOpenClawStateWriteTransaction as n, listAgentIds as o, ok as p, FsSafeError$1 as r, getActiveOpenClawStateDatabaseReadSnapshot as s, openOpenClawStateDatabase as t, withExistingOpenClawStateDatabaseReadOnly as u, hasUnresolvedConfigPath as v, stableStringify as x, resolveConfigSecretRef as y };