UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

315 lines (314 loc) • 10.3 kB
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 };