openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
306 lines (305 loc) • 12.3 kB
JavaScript
import { r as resolveStateDir } from "./state-dir-CfAW6S2K.mjs";
import { t as formatErrorMessage } from "./errors-BSdPMFao.mjs";
import { a as normalizeDatabasePath, t as assertExistingDatabaseIdentity } from "./sqlite-worker-identity-B_aK_kcL.mjs";
import { a as createSqliteLifecycleAggregateError, s as throwSqliteLifecycleErrors } from "./sqlite-lifecycle-errors-DuOygjcn.mjs";
import { t as createSubsystemLogger } from "./subsystem-BlrgtxyM.mjs";
import { s as resolveOpenClawStateSqlitePath } from "./openclaw-state-db.paths-BvBEgHyD.mjs";
import { _ as registerOpenClawStateDatabaseAsyncResource, n as captureOpenClawStateDatabaseReadAdmission } from "./openclaw-state-db-cache-BEArcjbO.mjs";
import { _ as readOpenClawAgentDatabaseIdentity } from "./openclaw-agent-db-validation-cache-D5sa3PLF.mjs";
import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs";
import { o as registerOpenClawAgentDatabaseAsyncResource } from "./openclaw-agent-db-resources-DXGnJiTx.mjs";
import { n as createSqliteWorkerOperationAdmission } from "./sqlite-worker-operation-admission-DqFz0WIG.mjs";
import { r as cloneEnvWithPlatformSemantics } from "./config-env-vars-oFV7TqIg.mjs";
import "./session-key-BC4m_Ly5.mjs";
import { c as resolveOpenClawAgentSqlitePath } from "./openclaw-agent-db.paths-DiWMV5LG.mjs";
import { l as reserveSqliteWorkerInputPreparation } from "./sqlite-worker-store-DqYxJvP-.mjs";
import { y as retainAgentDatabase } from "./openclaw-agent-db-lifecycle-BmHpWusP.mjs";
import { o as getOpenClawAgentDatabaseIfOpen } from "./openclaw-agent-db-CFSa_Qik.mjs";
import { n as runOpenClawAgentWorkerWrite } from "./openclaw-agent-write-admission-xWSWPO-q.mjs";
import { s as getGatewayRestartDrainSignal } from "./gateway-work-admission-G8XukyHo.mjs";
import { t as captureOpenClawAgentDatabaseExecution } from "./openclaw-agent-execution-C-XzIRol.mjs";
import { deserialize, serialize } from "node:v8";
import { randomUUID } from "node:crypto";
//#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 {}
}
/** 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 assertHeld = (cleanup = false) => {
if (revoked && !cleanup) throw new Error("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;
closing ??= (async () => {
await Promise.allSettled(pending);
if (drainExecution) {
await drainExecution.release();
drainExecution = void 0;
}
releaseBorrow?.();
releaseBorrow = void 0;
unregisterAgent?.();
unregisterState?.();
})().catch((error) => {
closing = void 0;
throw error;
});
return closing;
};
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(/* @__PURE__ */ new Error("Agent database Worker owner is closed"));
const assert = () => {
assertHeld();
assertCurrent();
assertHeld();
};
const execution = captureOpenClawAgentDatabaseExecution(options, { expectedIdentity });
const source = {
assertCurrent: assert,
createAdmission(binding) {
return () => {
let phase = "waiting";
return {
nativeLocations: binding.nativeLocations,
admission: createSqliteWorkerOperationAdmission((request, grant) => {
binding.authorize(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()) throw new Error("Agent publication authority expired");
}, binding.attachment)
};
};
}
};
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) {
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;
};
return {
async execute(command, assertCurrent, 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) => {
const receipt = await execution.runExisting(source, async (scope) => ({ value: await preparation.handoff(() => scope.execute({
type: "database.domain.publish",
input: deserialize(captured)
}, commandOptions)) }));
if (!receipt) throw new Error("Agent database disappeared before publication");
return receipt.value;
}, commandOptions?.signal);
} finally {
preparation.release();
}
},
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 (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 t };