openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
315 lines (314 loc) • 10.3 kB
JavaScript
import { f as toStringifiedError } from "./error-coercion-W9BtigRm.mjs";
import { t as createDeferredCore } from "./deferred-D0La5CRk.mjs";
import { n as resolveRuntimeWorkerArgv } from "./runtime-worker-url-CFqA9Dvi.mjs";
import { Et as _enum, Fn as object, Jn as string, Lt as boolean, Nt as array, Pn as number, Tn as map, Yt as discriminatedUnion, xn as literal } from "./schemas-BOYIvvln.mjs";
import { n as resolveRuntimeProcessEntrypointUrl } from "./runtime-process-url-CYKlArl-.mjs";
import { i as readSqliteInspectionBudget, m as sqliteInspectionTimeoutError, r as isSqliteInspectionDeadlineOwnedByCaller, s as resolveSqliteInspectionSignal } from "./sqlite-readonly-worker-RO6waSGe.mjs";
import { n as readSqliteIntegrityFileIdentity } from "./sqlite-file-generation-HEkrmgCv.mjs";
import { n as restoreAgentSchemaInspectionError, t as agentSchemaInspectionErrorSchema } from "./openclaw-agent-schema-inspection-response-C2HIQKzs.mjs";
import { fork } from "node:child_process";
//#region src/state/openclaw-agent-schema-inspection-worker.ts
const stateSchemaRow = object({
kind: literal("state"),
path: string(),
foundVersion: number(),
supportedVersion: number(),
writerAppVersion: string().optional()
});
const deletionFacts = object({
entries: array(object({
agentId: string(),
agentDir: string(),
databasePaths: array(string())
})),
held: array(object({
agentId: string(),
path: string()
}))
});
const schemaContract = object({
schemaSql: string(),
tables: map(string(), object({
definition: object({
columns: map(string(), string()),
constraints: array(string())
}).nullable(),
indexes: array(object({
name: string().nullable(),
origin: string(),
partial: number(),
sql: string().nullable(),
terms: array(object({
coll: string(),
desc: number(),
key: number(),
kind: _enum([
"column",
"expression",
"rowid"
]),
name: string().nullable(),
seqno: number()
})),
unique: number()
})),
strict: number(),
triggers: array(object({
name: string(),
sql: string().nullable()
})),
virtualTableSql: string().nullable(),
withoutRowid: number()
}))
});
const stateInspectionSchema = object({
schemas: object({
incompatible: array(stateSchemaRow),
indeterminate: array(object({
kind: literal("state"),
path: string(),
reason: string()
})),
pendingMigrations: array(stateSchemaRow).optional(),
deferredSchemaPublications: array(object({
kind: literal("state"),
path: string(),
foundVersion: number(),
contentVersion: number(),
runId: string().optional(),
publishAfterMs: number().nullable().optional(),
message: string()
})).optional()
}),
registeredDatabases: array(object({
agentId: string(),
path: string()
})).optional(),
deletionJournal: discriminatedUnion("status", [
object({ status: literal("empty") }),
deletionFacts.extend({ status: literal("present") }),
object({
status: literal("unavailable"),
cause: _enum(["missing", "unreadable"]),
reason: string(),
known: deletionFacts.optional()
})
]).optional(),
inspectionErrors: array(agentSchemaInspectionErrorSchema)
});
const inspectionResponse = discriminatedUnion("ok", [object({
requestId: number().int().safe(),
ok: literal(false),
error: agentSchemaInspectionErrorSchema
}), object({
requestId: number().int().safe(),
ok: literal(true),
stateInspection: stateInspectionSchema.optional(),
schemaContracts: array(schemaContract).optional(),
inspection: object({
version: number().int().safe(),
integrityGateOutcome: _enum(["cached", "healthy"]).optional(),
preparationPending: literal(true).optional(),
emptyStore: boolean().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;
};
const operations = { inspect: async (input, callerSignal, snapshotPath, kind) => {
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 {
const snapshot = snapshotPath ? {
pathname: snapshotPath,
identity: readSqliteIntegrityFileIdentity(snapshotPath)
} : void 0;
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 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 {
if (kind === "state") {
const stateInspection = parsed.data.stateInspection;
if (!stateInspection) {
failure = /* @__PURE__ */ new Error("Invalid state schema inspection response");
kill();
return;
}
response.resolve({
...stateInspection,
schemaContracts: parsed.data.schemaContracts,
inspectionErrors: stateInspection.inspectionErrors.map(restoreAgentSchemaInspectionError)
});
return;
}
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: kind === "state" ? "inspect-state" : "inspect",
requestId,
input: {
...input,
..."env" in input && input.env ? { env: { ...input.env } } : {}
},
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;
}
} };
return {
get processCount() {
return processCount;
},
get inspectionCount() {
return inspectionCount;
},
get snapshotCount() {
return snapshotCount;
},
inspect: async (input, callerSignal, snapshotPath) => {
const result = await operations.inspect(input, callerSignal, snapshotPath);
if (result && "schemas" in result) throw new Error("Unexpected state schema inspection result");
return result;
},
inspectState: async (input, callerSignal, snapshotPath) => {
const result = await operations.inspect(input, callerSignal, snapshotPath, "state");
if (!result || !("schemas" in result)) throw new Error("Missing state schema inspection result");
return result;
},
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);
}
}
};
}
//#endregion
export { createAgentSchemaInspectionWorker as t };