UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

341 lines (340 loc) • 14 kB
import { r as resolveStateDir } from "./state-dir-CYkBUd_G.mjs"; import { t as formatErrorMessage } from "./errors-BvOkmQM5.mjs"; import { r as assertExistingDatabaseIdentity, s as normalizeDatabasePath } from "./sqlite-worker-identity-BRNRpRyc.mjs"; import { a as createSqliteLifecycleAggregateError, s as throwSqliteLifecycleErrors } from "./sqlite-lifecycle-errors-DuOygjcn.mjs"; import { t as createSubsystemLogger } from "./subsystem-4NzR43uz.mjs"; import { c as resolveOpenClawStateSqlitePath } from "./openclaw-state-db.paths-CmKkMYdo.mjs"; import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs"; import "./session-key-DY3MOX4N.mjs"; import { a as readOpenClawAgentDatabaseIdentity } from "./openclaw-agent-db-identity-BlmER9-X.mjs"; import { o as registerOpenClawAgentDatabaseAsyncResource, x as AgentDatabaseExecutionAdmissionClosedError } from "./openclaw-agent-db-resources-Cm-sH7kg.mjs"; import { l as resolveOpenClawAgentSqlitePath } from "./openclaw-agent-db.paths-BbmZlEP5.mjs"; import { r as createSqliteWorkerOperationAdmission } from "./sqlite-worker-operation-admission-C5lBzzTk.mjs"; import { r as captureOpenClawStateDatabaseReadAdmission, v as registerOpenClawStateDatabaseAsyncResource } from "./openclaw-state-db-cache-CmYLFRSt.mjs"; import { r as cloneEnvWithPlatformSemantics } from "./config-env-vars-BzUE_dOh.mjs"; import { i as getOpenClawAgentDatabaseIfOpen } from "./openclaw-agent-db-DWI9Bgur.mjs"; import { l as getGatewayRestartDrainSignal, u as getGatewayShutdownCleanupSignal } from "./gateway-work-admission-DAV5kkgV.mjs"; import { S as retainAgentDatabase } from "./openclaw-agent-db-lifecycle-C9dal1XJ.mjs"; import { t as runOpenClawAgentWorkerWrite } from "./openclaw-agent-write-admission-CXJiE5VB.mjs"; import { d as reserveSqliteWorkerInputPreparation } from "./sqlite-worker-store-C0QRqven.mjs"; import { n as captureOpenClawAgentDatabaseExecution } from "./openclaw-agent-execution-C2B2fucX.mjs"; import { deserialize, serialize } from "node:v8"; import { randomUUID } from "node:crypto"; import { addAbortListener } from "node:events"; //#region src/state/openclaw-agent-worker-store.ts const log = createSubsystemLogger("state/agent-db"); function reportCompletedPublicationCleanupFailure(error) { try { log.warn(`Agent publication completed before cleanup failed: ${formatErrorMessage(error)}`); } catch {} } /** Send a paired module's command through the caller's already-admitted executor. */ function executeOpenClawAgentWorkerPublication(scope, publication, options) { const { command } = publication; if (typeof command.type !== "string") throw new Error("Agent publication commands require a string type"); return scope.execute({ type: "database.domain.publish", input: { ...publication, command: { type: command.type, input: command.input } } }, options); } /** Retains a native borrow or checks a caller-held executor; each operation borrows the canonical executor. */ async function openOpenClawAgentSqliteWorkerStore(inputOptions, publicationSource, worker) { const env = cloneEnvWithPlatformSemantics(inputOptions.env ?? process.env); env.OPENCLAW_STATE_DIR = resolveStateDir(env); const options = { ...inputOptions, agentId: normalizeAgentId(inputOptions.agentId), env, path: resolveOpenClawAgentSqlitePath({ ...inputOptions, env }) }; const capturedExecution = "execution" in publicationSource ? publicationSource.execution : void 0; const expectedDatabase = "execution" in publicationSource ? void 0 : publicationSource; if (capturedExecution && (capturedExecution.agentId !== options.agentId || capturedExecution.path !== options.path)) throw new Error("Agent publication source differs from its captured executor"); const prepared = expectedDatabase ? readOpenClawAgentDatabaseIdentity({ db: expectedDatabase }) : void 0; if (prepared && typeof prepared.identity !== "string") throw new Error("Agent Worker requires its existing file owner"); const expectedIdentity = prepared && typeof prepared.identity === "string" ? { kind: "file", physicalIdentity: prepared.identity, nativeLocation: prepared.filename, birthtime: prepared.birthtime } : void 0; const moduleUrl = new URL(worker.moduleUrl).href; const input = structuredClone(worker.input); const state = captureOpenClawStateDatabaseReadAdmission(resolveOpenClawStateSqlitePath(options.env)); let revoked = false; let closing; let drainExecution; let releaseBorrow; let unregisterAgent; let unregisterState; const pending = /* @__PURE__ */ new Set(); const cleanupSignal = getGatewayShutdownCleanupSignal(); const releaseDrainExecution = async () => { await drainExecution?.release(); drainExecution = void 0; }; const assertHeld = (cleanup = false) => { if (revoked && !cleanup) throw new AgentDatabaseExecutionAdmissionClosedError("Agent database Worker owner is closed"); state.assertCurrent(); if (capturedExecution) { capturedExecution.assertCurrent(); return; } const current = getOpenClawAgentDatabaseIfOpen(options); if (!expectedDatabase || !current || current.db !== expectedDatabase || !expectedDatabase.isOpen || normalizeDatabasePath(expectedDatabase.location() ?? "") !== prepared?.filename) throw new Error("Borrowed agent database closed or changed before Worker admission"); }; assertHeld(); if (expectedIdentity) assertExistingDatabaseIdentity(options.path, `file:${expectedIdentity.physicalIdentity}`, expectedIdentity.birthtime); const close = () => { revoked = true; cleanupListener[Symbol.dispose](); closing ??= (async () => { await Promise.allSettled(pending); await releaseDrainExecution(); releaseBorrow?.(); releaseBorrow = void 0; unregisterAgent?.(); unregisterState?.(); })().catch((error) => { closing = void 0; throw error; }); return closing; }; const cleanupListener = addAbortListener(cleanupSignal, () => { const release = releaseDrainExecution(); pending.add(release); release.finally(() => pending.delete(release)).catch(reportCompletedPublicationCleanupFailure); }); try { unregisterAgent = registerOpenClawAgentDatabaseAsyncResource({ agentId: options.agentId, path: options.path, revoke: () => { revoked = true; }, close }); unregisterState = registerOpenClawStateDatabaseAsyncResource({ close: async (closedIdentity) => { if (!closedIdentity || closedIdentity.key === state.identity.key) await close(); } }); if (expectedDatabase) releaseBorrow = retainAgentDatabase(expectedDatabase); } catch (error) { try { await close(); } catch (cleanupError) { throw new AggregateError([error, cleanupError], "Agent publication setup and cleanup failed", { cause: cleanupError }); } throw error; } const runPublication = (assertCurrent, operation, signal) => { if (revoked) return Promise.reject(new AgentDatabaseExecutionAdmissionClosedError("Agent database Worker owner is closed")); const assert = () => { assertHeld(); assertCurrent(); assertHeld(); }; const execution = captureOpenClawAgentDatabaseExecution(options, { expectedIdentity }); const source = { assertCurrent: assert, createAdmission(binding) { return (retained) => { let phase = "waiting"; const admission = createSqliteWorkerOperationAdmission((request, grant) => { binding.authorize(worker.assertAdmission?.(request) ?? request); if (request.stage === "transaction" || request.stage === "commit") { if (!(phase === "waiting" && request.stage === "transaction" || phase === "transaction" && request.stage === "commit")) throw new Error("Agent publication authority requested out of order"); phase = request.stage; } if (!grant(() => worker.onAdmitted?.(request))) throw new Error("Agent publication authority expired"); }, binding.attachment); try { worker.observeAdmission?.(admission, retained); } catch (error) { admission.finish(); throw error; } return { nativeLocations: binding.nativeLocations, admission }; }; } }; const result = (async () => { let completed; try { completed = { ok: true, value: await runOpenClawAgentWorkerWrite(options, () => operation(execution, source), void 0, signal) }; } catch (error) { completed = { ok: false, error }; } let releasing = execution; if (completed.ok && !revoked && getGatewayRestartDrainSignal().aborted && !cleanupSignal.aborted) { const previous = drainExecution; drainExecution = execution; releasing = previous; } try { await releasing?.release(); } catch (releaseError) { if (!completed.ok) throw createSqliteLifecycleAggregateError([completed.error, releaseError], "Agent publication and executor release failed", completed.error); reportCompletedPublicationCleanupFailure(releaseError); } if (!completed.ok) throw completed.error; return completed.value; })(); pending.add(result); result.finally(() => pending.delete(result)).catch(() => {}); return result; }; const executeCommand = async (command, assertCurrent, select, commandOptions) => { commandOptions?.signal?.throwIfAborted(); if (typeof command.type !== "string") throw new Error("Agent publication commands require a string type"); const publication = { id: randomUUID(), moduleUrl, input, command: { type: command.type, input: command.input } }; const captured = serialize(publication); const preparation = reserveSqliteWorkerInputPreparation(captured.byteLength, "snapshot"); try { return await runPublication(assertCurrent, async (execution, source) => { return select(await execution.runExisting(source, async (scope) => ({ value: await preparation.handoff(() => executeOpenClawAgentWorkerPublication(scope, deserialize(captured), commandOptions)) }))); }, commandOptions?.signal); } finally { preparation.release(); } }; return { execute(command, assertCurrent, commandOptions) { return executeCommand(command, assertCurrent, (receipt) => { if (!receipt) throw new Error("Agent database disappeared before publication"); return receipt.value; }, commandOptions); }, executeExisting(command, assertCurrent, commandOptions) { return executeCommand(command, assertCurrent, (receipt) => receipt, commandOptions); }, run(operation, assertCurrent) { return runPublication(assertCurrent, async (execution, source) => { const cleanupSource = { assertCurrent: () => assertHeld(true), createAdmission(binding) { return () => ({ nativeLocations: binding.nativeLocations, admission: createSqliteWorkerOperationAdmission((request, grant) => { if (request.stage !== "prepare") throw new Error("Publication cleanup cannot open storage or admit a transaction"); binding.authorize(request); if (!grant()) throw new Error("Agent publication cleanup authority expired"); }, binding.attachment) }); } }; const id = randomUUID(); let bound = false; const failures = []; let outcome; try { const executed = await execution.runExisting(source, async (scope) => { await scope.execute({ type: "database.domain.bind", input: { id, moduleUrl, input } }); bound = true; let accepting = true; const commands = /* @__PURE__ */ new Set(); const commandFailures = []; let callback; try { callback = { ok: true, value: await operation({ execute: (command, commandOptions) => { if (!accepting) return Promise.reject(/* @__PURE__ */ new Error("Agent publication operation is closed")); if (typeof command.type !== "string") return Promise.reject(/* @__PURE__ */ new Error("Agent publication commands require a string type")); const commandResult = scope.execute({ type: "database.domain.execute", input: { id, command: { type: command.type, input: command.input } } }, commandOptions); commands.add(commandResult); commandResult.then(() => commands.delete(commandResult), (error) => { if (!commandFailures.includes(error)) commandFailures.push(error); commands.delete(commandResult); }); return commandResult; } }) }; } catch (error) { callback = { ok: false, error }; } accepting = false; await Promise.allSettled(commands); if (!callback.ok) failures.push(callback.error); for (const error of commandFailures) if (!failures.includes(error)) failures.push(error); if (!callback.ok) throw callback.error; return { value: callback.value }; }); outcome = executed ? { ok: true, value: executed.value } : { ok: false, error: /* @__PURE__ */ new Error("Agent database disappeared before publication") }; } catch (error) { outcome = { ok: false, error }; } if (!outcome.ok && !failures.includes(outcome.error)) failures.push(outcome.error); if (bound) try { await execution.runExisting(cleanupSource, (scope) => scope.execute({ type: "database.domain.close", input: { id } }), { retireNativeOnFailure: true }); } catch (error) { if (error instanceof AgentDatabaseExecutionAdmissionClosedError) {} else if (outcome.ok && failures.length === 0) reportCompletedPublicationCleanupFailure(error); else failures.push(error); } throwSqliteLifecycleErrors(failures, "Agent publication and cleanup failed"); if (!outcome.ok) throw outcome.error; return outcome.value; }); }, close }; } //#endregion export { openOpenClawAgentSqliteWorkerStore as n, executeOpenClawAgentWorkerPublication as t };