openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
239 lines (238 loc) • 8.69 kB
JavaScript
import { d as toStructuredErrorObject } from "./error-coercion-D_-xJ90S.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { t as runtimeProcessEntrypoints } from "./runtime-process-entrypoints-CEMkwAJ_.js";
import { r as resolveRuntimeWorkerUrl } from "./runtime-worker-url-CpdriB1D.js";
import { B as recordOpenClawDatabaseQuarantine, s as recordOpenClawStateDatabaseOpenFailure } from "./openclaw-state-db-cache-C7ljO0xP.js";
import { s as resolveOpenClawStateSqlitePath } from "./openclaw-state-db-schema-version-c1ZL6JGz.js";
import { t as confirmOpenClawStateDatabaseIntegrity } from "./openclaw-state-db-BRTnL-D8.js";
import { y as listOpenClawRegisteredAgentDatabases } from "./openclaw-agent-db-maintenance-wTIy-jt-.js";
import { a as confirmOpenClawAgentDatabaseIntegrity, h as recordOpenClawAgentDatabaseOpenFailure } from "./openclaw-agent-db-CWtDoRbC.js";
import { existsSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";
import { fork } from "node:child_process";
//#region src/state/openclaw-database-verify.impl.ts
const OPENCLAW_DATABASE_VERIFY_INITIAL_DELAY_MS = 3e5;
const OPENCLAW_DATABASE_VERIFY_INTERVAL_MS = 864e5;
const log$1 = createSubsystemLogger("state/database-verify");
const DATABASE_VERIFY_CHILD_ARG = "--openclaw-database-verify-child";
function isVerifyResult(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
const result = value;
return typeof result.path === "string" && typeof result.ok === "boolean" && (result.error === void 0 || typeof result.error === "string") && (result.terminal === void 0 || typeof result.terminal === "boolean");
}
function runDatabaseVerifyWorker(targets, options = {}) {
const workerUrl = options.workerUrl ?? resolveRuntimeWorkerUrl(runtimeProcessEntrypoints.databaseVerify);
const execArgv = workerUrl.pathname.endsWith(".ts") ? ["--import", "tsx"] : void 0;
let worker;
try {
worker = fork(fileURLToPath(workerUrl), [DATABASE_VERIFY_CHILD_ARG], {
execArgv,
stdio: [
"ignore",
"ignore",
"ignore",
"ipc"
]
});
} catch (error) {
return Promise.reject(toStructuredErrorObject(error));
}
options.onWorker?.(worker);
return new Promise((resolve, reject) => {
let settled = false;
let result;
let protocolError;
let exit;
let disconnected = !worker.connected;
const settle = (finish) => {
if (settled) return;
settled = true;
worker.removeAllListeners();
options.onWorker?.(void 0);
finish();
};
const settleAfterExitAndDisconnect = () => {
const completedExit = exit;
if (!completedExit || !disconnected) return;
settle(() => {
if (protocolError) reject(toStructuredErrorObject(protocolError));
else if (completedExit.code !== 0) reject(/* @__PURE__ */ new Error(`database verification worker exited with ${completedExit.signal ? `signal ${completedExit.signal}` : `code ${completedExit.code}`}`));
else if (!result) reject(/* @__PURE__ */ new Error("database verification worker exited without results"));
else resolve(result);
});
};
worker.once("message", (message) => {
if (!Array.isArray(message) || !message.every(isVerifyResult)) {
protocolError = /* @__PURE__ */ new Error("database verification worker returned invalid results");
worker.kill();
return;
}
result = message;
});
worker.once("error", (error) => settle(() => reject(toStructuredErrorObject(error))));
worker.once("disconnect", () => {
disconnected = true;
settleAfterExitAndDisconnect();
});
worker.once("exit", (code, signal) => {
exit = {
code,
signal
};
disconnected ||= !worker.connected;
settleAfterExitAndDisconnect();
});
worker.send(targets, (error) => {
if (!error) return;
worker.kill();
settle(() => reject(toStructuredErrorObject(error)));
});
});
}
async function terminateDatabaseVerifyWorker(worker) {
if (worker.exitCode !== null || worker.signalCode !== null) return;
await new Promise((resolve) => {
worker.once("exit", () => resolve());
if (!worker.kill()) resolve();
});
}
/** Resolve the state database and current registered agent database paths. */
function collectOpenClawDatabaseVerifyTargets(options) {
const targets = /* @__PURE__ */ new Map();
const statePath = path.resolve(resolveOpenClawStateSqlitePath(options.env));
if (existsSync(statePath)) targets.set(statePath, {
kind: "state",
label: "OpenClaw state database",
path: statePath
});
let registeredDatabases = [];
try {
registeredDatabases = listOpenClawRegisteredAgentDatabases({ env: options.env });
} catch (error) {
log$1.warn("failed to collect registered agent databases for integrity verification", { error: String(error) });
}
for (const registered of registeredDatabases) {
const agentPath = path.resolve(registered.path);
if (!existsSync(agentPath) || targets.has(agentPath)) continue;
targets.set(agentPath, {
kind: "agent",
label: `OpenClaw agent database ${registered.agentId}`,
path: agentPath
});
}
return [...targets.values()];
}
/** Reconfirm worker failures on live owners before quarantine and latching. */
function applyOpenClawDatabaseVerificationResults(options) {
const targetByPath = new Map(options.targets.map((target) => [target.path, target]));
for (const result of options.results) {
const target = targetByPath.get(result.path);
if (!target) continue;
if (result.ok) {
log$1.info("database integrity verification passed", {
kind: target.kind,
label: target.label,
path: result.path
});
continue;
}
if (!result.terminal) {
log$1.warn("database integrity verification was inconclusive", {
kind: target.kind,
label: target.label,
path: result.path,
error: result.error
});
continue;
}
const confirmation = target.kind === "state" ? confirmOpenClawStateDatabaseIntegrity(result.path) : confirmOpenClawAgentDatabaseIntegrity(result.path);
if (confirmation.status === "healthy") {
log$1.info("discarding stale database integrity verification result", {
kind: target.kind,
label: target.label,
path: result.path
});
continue;
}
if (!confirmation.terminal) {
log$1.warn("database integrity verification was inconclusive", {
kind: target.kind,
label: target.label,
path: result.path,
error: confirmation.error.message
});
continue;
}
if (!(target.kind === "state" ? recordOpenClawStateDatabaseOpenFailure(result.path, confirmation.error, confirmation.generation) : recordOpenClawAgentDatabaseOpenFailure(result.path, confirmation.error, confirmation.generation))) {
log$1.info("discarding database integrity result after database generation changed", {
kind: target.kind,
label: target.label,
path: result.path
});
continue;
}
if (!recordOpenClawDatabaseQuarantine({
env: options.env,
generation: confirmation.generation,
kind: target.kind,
path: result.path,
reason: confirmation.error.message
})) log$1.error("failed to persist database quarantine; quarantine is process-local", {
kind: target.kind,
path: result.path
});
log$1.error("database integrity verification failed", {
kind: target.kind,
label: target.label,
path: result.path,
error: confirmation.error.message
});
}
}
//#endregion
//#region src/state/openclaw-database-verify.ts
const log = createSubsystemLogger("state/database-verify");
/** Start the Gateway-owned delayed daily integrity verifier. */
function startOpenClawDatabaseIntegrityVerifier(options) {
let activeWorker;
let stopped = false;
let timer;
const schedule = (delayMs) => {
timer = setTimeout(() => void run(), delayMs);
timer.unref?.();
};
const run = async () => {
timer = void 0;
try {
const targets = collectOpenClawDatabaseVerifyTargets(options);
if (targets.length > 0) {
const results = await runDatabaseVerifyWorker(targets, { onWorker: (worker) => {
activeWorker = worker;
} });
if (!stopped) applyOpenClawDatabaseVerificationResults({
...options,
results,
targets
});
}
} catch (error) {
if (!stopped) log.error("database integrity verifier failed", { error: String(error) });
} finally {
activeWorker = void 0;
if (!stopped) schedule(OPENCLAW_DATABASE_VERIFY_INTERVAL_MS);
}
};
schedule(OPENCLAW_DATABASE_VERIFY_INITIAL_DELAY_MS);
return { stop: async () => {
stopped = true;
if (timer) {
clearTimeout(timer);
timer = void 0;
}
if (activeWorker) await terminateDatabaseVerifyWorker(activeWorker);
activeWorker = void 0;
} };
}
//#endregion
export { startOpenClawDatabaseIntegrityVerifier };