openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
341 lines (340 loc) • 14 kB
JavaScript
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 };