openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
304 lines (303 loc) • 11.8 kB
JavaScript
import { d as toStringifiedError } from "./error-coercion-CvJK4lmH.mjs";
import { t as createDeferredCore } from "./deferred-D0La5CRk.mjs";
import { n as resolveRuntimeWorkerArgv } from "./runtime-worker-url-DoBSDhBS.mjs";
import { Et as _enum, Fn as object, Jn as string, Pn as number, Yt as discriminatedUnion, xn as literal } from "./schemas-BOYIvvln.mjs";
import { a as readSqliteInspectionBudget, c as resolveSqliteInspectionSignal, g as withSqliteReadOnlyWorkerScope, h as sqliteInspectionTimeoutError, i as isSqliteInspectionDeadlineOwnedByCaller } from "./sqlite-readonly-worker-TSy0m8v6.mjs";
import { n as resolveRuntimeProcessEntrypointUrl } from "./runtime-process-url-D3H3_asn.mjs";
import { t as _usingCtx } from "./usingCtx-DwOBYOJf.mjs";
import { n as readSqliteIntegrityFileIdentity } from "./sqlite-file-generation-C_XgOkXx.mjs";
import { n as restoreAgentSchemaInspectionError, t as agentSchemaInspectionErrorSchema } from "./openclaw-agent-schema-inspection-response-POotE-xY.mjs";
import { availableParallelism } from "node:os";
import { fork } from "node:child_process";
//#region src/state/openclaw-agent-schema-inspection-worker.ts
const inspectionResponse = discriminatedUnion("ok", [object({
requestId: number().int().safe(),
ok: literal(false),
error: agentSchemaInspectionErrorSchema
}), object({
requestId: number().int().safe(),
ok: literal(true),
inspection: object({
version: number().int().safe(),
integrityGateOutcome: _enum(["cached", "healthy"]).optional(),
writerAppVersion: string().optional(),
reason: string().optional(),
failure: agentSchemaInspectionErrorSchema.optional(),
agentSchemaMeta: object({
agentId: string().nullable(),
role: string().nullable(),
schemaVersion: number().nullable()
}).nullable().optional()
}).nullable()
})]);
function retireReader(reader) {
if (reader.retired) return;
reader.retired = true;
if (reader.child.connected) reader.child.send({ type: "close" }, (error) => {
if (error) reader.child.kill("SIGKILL");
});
}
/** One scheduler slot reuses imports and canonical schema contracts, never database reads. */
function createAgentSchemaInspectionWorker() {
let reader;
let disposed = false;
let busy = false;
let processCount = 0;
let inspectionCount = 0;
let snapshotCount = 0;
let sequence = 0;
const startReader = (timeoutMs) => {
const entry = resolveRuntimeProcessEntrypointUrl("agentSchemaInspection");
const child = fork(entry, [], {
execArgv: resolveRuntimeWorkerArgv(entry).slice(0, -1),
serialization: "advanced",
stdio: [
"ignore",
"ignore",
"ignore",
"ipc"
]
});
processCount += 1;
const closed = createDeferredCore();
const started = {
child,
closed: closed.promise,
retired: false,
closeBudgetMs: timeoutMs
};
child.on("error", (error) => {
started.failure ??= error;
retireReader(started);
});
child.once("close", () => {
started.retired = true;
closed.resolve();
});
return started;
};
return {
get processCount() {
return processCount;
},
get inspectionCount() {
return inspectionCount;
},
get snapshotCount() {
return snapshotCount;
},
inspect: async (input, callerSignal, snapshotPath) => {
const signal = resolveSqliteInspectionSignal(callerSignal);
signal?.throwIfAborted();
if (disposed || busy) throw new Error(disposed ? "Agent schema inspection worker is closed" : "Agent schema inspection worker is busy");
busy = true;
try {
if (reader?.retired) {
await reader.closed;
reader = void 0;
}
signal?.throwIfAborted();
if (disposed) throw new Error("Agent schema inspection worker is closed");
const { timeoutMs, size } = readSqliteInspectionBudget(input.requireStartupMigrationReadiness ? "startup readiness" : "schema inspection", input.pathname);
const active = reader ??= startReader(timeoutMs);
active.closeBudgetMs = timeoutMs;
const snapshot = snapshotPath ? {
pathname: snapshotPath,
identity: readSqliteIntegrityFileIdentity(snapshotPath)
} : void 0;
const requestId = ++sequence;
const response = createDeferredCore();
let failure;
const kill = () => {
active.retired = true;
active.child.kill("SIGKILL");
};
const onAbort = () => {
failure = toStringifiedError(signal?.reason);
kill();
};
const onMessage = (message) => {
if (failure) return;
const parsed = inspectionResponse.safeParse(message);
if (!parsed.success || parsed.data.requestId !== requestId) {
failure = /* @__PURE__ */ new Error("Invalid agent schema inspection response");
kill();
} else if (!parsed.data.ok) {
failure = restoreAgentSchemaInspectionError(parsed.data.error);
retireReader(active);
} else {
const inspection = parsed.data.inspection;
response.resolve(inspection ? {
...inspection,
failure: inspection.failure ? restoreAgentSchemaInspectionError(inspection.failure) : void 0
} : null);
}
};
const onClose = (code, exitSignal) => {
response.reject(signal?.aborted ? toStringifiedError(signal.reason) : failure ?? active.failure ?? (exitSignal === "SIGKILL" ? sqliteInspectionTimeoutError("schema inspection", input.pathname, timeoutMs, size) : /* @__PURE__ */ new Error(`Agent schema inspection exited ${code} without a completed result`)));
};
const timeout = isSqliteInspectionDeadlineOwnedByCaller() ? void 0 : setTimeout(() => {
failure ??= sqliteInspectionTimeoutError("schema inspection", input.pathname, timeoutMs, size);
kill();
}, timeoutMs);
timeout?.unref();
active.child.on("message", onMessage);
active.child.once("close", onClose);
signal?.addEventListener("abort", onAbort, { once: true });
try {
active.child.send({
type: "inspect",
requestId,
input,
snapshot
}, (error) => {
if (error) {
failure ??= error;
kill();
}
});
const result = await response.promise;
if (signal?.aborted) {
kill();
await active.closed;
throw toStringifiedError(signal.reason);
}
if (snapshot) readSqliteIntegrityFileIdentity(snapshot.pathname, snapshot.identity);
if (result) {
inspectionCount += 1;
snapshotCount += snapshot ? 1 : 0;
}
return result;
} finally {
clearTimeout(timeout);
active.child.off("message", onMessage);
active.child.off("close", onClose);
signal?.removeEventListener("abort", onAbort);
}
} finally {
busy = false;
}
},
async [Symbol.asyncDispose]() {
disposed = true;
if (!reader) return;
const closing = reader;
const timeout = setTimeout(() => closing.child.kill("SIGKILL"), closing.closeBudgetMs);
timeout.unref();
try {
retireReader(closing);
await closing.closed;
} finally {
clearTimeout(timeout);
}
}
};
}
const AGENT_DATABASE_STARTUP_WAIT_MS = 5e3;
async function preflightAgentDatabasesBounded(targets, inspect, result, signal, startup) {
const inspectedAgentPaths = /* @__PURE__ */ new Set();
const inspectedAgentTargets = /* @__PURE__ */ new Set();
const claimAgentTarget = (realPath, agentId) => {
const inspectionKey = `${realPath}\0${agentId ?? ""}`;
if (inspectedAgentTargets.has(inspectionKey) || agentId === void 0 && inspectedAgentPaths.has(realPath)) return false;
inspectedAgentPaths.add(realPath);
inspectedAgentTargets.add(inspectionKey);
return true;
};
const inspections = [];
const failures = /* @__PURE__ */ new Map();
let nextInspectionIndex = 0;
const readers = [];
const foreground = createDeferredCore();
const completions = targets.map(() => createDeferredCore());
for (const completion of completions) completion.promise.catch(() => {});
const deferred = /* @__PURE__ */ new Set();
const concurrency = Math.min(2, availableParallelism(), targets.length);
let deferredRefusals = [];
const inspectionSignal = startup ? signal ? AbortSignal.any([startup.signal, signal]) : startup.signal : resolveSqliteInspectionSignal(signal);
const deadlineOwnedByCaller = startup !== void 0 || isSqliteInspectionDeadlineOwnedByCaller();
const settleForeground = () => {
if (!startup || deferred.size === 0 || deferredRefusals.length > 0 || failures.size > 0) return;
if (targets.some((_, index) => inspections[index] === void 0 && !deferred.has(index))) return;
try {
deferredRefusals = startup.defer([...deferred].map((index) => ({
target: targets[index],
result: completions[index].promise
})), `The ${AGENT_DATABASE_STARTUP_WAIT_MS / 1e3} second foreground startup budget elapsed; inspection continues.`);
foreground.resolve();
} catch (error) {
foreground.reject(error);
}
};
const worker = async () => {
try {
var _usingCtx$1 = _usingCtx();
const reader = _usingCtx$1.a(createAgentSchemaInspectionWorker());
readers.push(reader);
while (true) {
if (inspectionSignal?.aborted || failures.size > 0) return;
const index = nextInspectionIndex;
if (index >= targets.length) return;
nextInspectionIndex += 1;
const target = targets[index];
if (target === void 0) continue;
const inspection = {
incompatible: [],
indeterminate: []
};
try {
await inspect(target, inspection, claimAgentTarget, (input, requestSignal, snapshot) => reader.inspect(input, inspectionSignal ?? requestSignal, snapshot));
inspections[index] = inspection;
completions[index].resolve(inspection);
} catch (error) {
failures.set(index, error);
completions[index].reject(error);
return;
} finally {
settleForeground();
}
}
} catch (_) {
_usingCtx$1.e = _;
} finally {
await _usingCtx$1.d();
}
};
const completed = Promise.all(Array.from({ length: concurrency }, () => withSqliteReadOnlyWorkerScope(worker, {
signal: inspectionSignal ?? new AbortController().signal,
deadlineOwnedByCaller
}))).finally(() => {
for (let index = 0; index < targets.length; index += 1) if (inspections[index] === void 0) completions[index].reject(inspectionSignal?.reason ?? failures.values().next().value ?? /* @__PURE__ */ new Error("Inspection cancelled"));
});
startup?.track(completed);
const timer = startup && setTimeout(() => {
if (failures.size > 0 || inspectionSignal?.aborted) return;
targets.forEach((target, index) => {
if (inspections[index] === void 0 && startup.canDefer(target)) deferred.add(index);
});
settleForeground();
}, AGENT_DATABASE_STARTUP_WAIT_MS);
timer?.unref();
try {
await (startup ? Promise.race([completed, foreground.promise]) : completed);
} finally {
clearTimeout(timer);
}
for (let index = 0; index < targets.length; index += 1) if (failures.has(index) && (!deferred.has(index) || deferredRefusals.length === 0)) throw failures.get(index);
inspectionSignal?.throwIfAborted();
for (const [index, inspection] of inspections.entries()) {
if (inspection === void 0 || deferred.has(index)) continue;
result.incompatible.push(...inspection.incompatible);
result.indeterminate.push(...inspection.indeterminate);
if (inspection.agentRefusals?.length) (result.agentRefusals ??= []).push(...inspection.agentRefusals);
if (inspection.pendingMigrations?.length) (result.pendingMigrations ??= []).push(...inspection.pendingMigrations);
}
if (deferredRefusals.length) (result.agentRefusals ??= []).push(...deferredRefusals);
return {
schemaProcessCount: readers.reduce((count, reader) => count + reader.processCount, 0),
schemaInspectionCount: readers.reduce((count, reader) => count + reader.inspectionCount, 0),
schemaSnapshotCount: readers.reduce((count, reader) => count + reader.snapshotCount, 0)
};
}
//#endregion
export { preflightAgentDatabasesBounded as t };