openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
719 lines (718 loc) • 31.9 kB
JavaScript
import { a as resolveGlobalSingleton } from "./global-singleton-0m-JziJW.mjs";
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import { t as formatErrorMessage } from "./errors-BSdPMFao.mjs";
import { c as readDatabasePathIdentitySync, 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 { f as publishSqliteWalCheckpointObservation } from "./sqlite-wal-BeoOh_h5.mjs";
import { l as observeOpenClawDatabaseMaintenanceResource, o as getOpenClawDatabaseMaintenanceScope, u as runOutsideOpenClawDatabaseMaintenanceScope } from "./openclaw-state-db-async-lifecycle-8N3pIeYH.mjs";
import { s as resolveOpenClawStateSqlitePath } from "./openclaw-state-db.paths-BvBEgHyD.mjs";
import { n as runtimeProcessEntrypoints } from "./runtime-process-entrypoints-Bh4reDp0.mjs";
import { r as resolveRuntimeWorkerUrl } from "./runtime-worker-url-S0NIVxUR.mjs";
import { _ as registerOpenClawStateDatabaseAsyncResource, m as publishOpenClawStateDatabaseWorkerAdmission } from "./openclaw-state-db-cache-BEArcjbO.mjs";
import { a as getOpenClawAgentDatabaseValidationForTransfer, n as captureOpenClawAgentDatabaseValidationTransfer } 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 { t as SQLITE_IDLE_HANDLE_TTL_MS } from "./sqlite-handle-lifecycle-dWd9h3ii.mjs";
import { y as retainSqliteWorkerErrorCode } from "./sqlite-worker-operation-admission-DqFz0WIG.mjs";
import { r as cloneEnvWithPlatformSemantics } from "./config-env-vars-oFV7TqIg.mjs";
import { r as captureOpenClawStateWorkerContext, t as captureOpenClawStateReadContext } from "./openclaw-state-worker-context-epCM4Gdh.mjs";
import "./session-key-BC4m_Ly5.mjs";
import { c as resolveOpenClawAgentSqlitePath, i as isIncognitoOpenClawAgentSqlitePath } from "./openclaw-agent-db.paths-DiWMV5LG.mjs";
import { a as isSqliteWorkerStoreAvailable, d as runSqliteWorkerStoreOperation, o as openAgentDatabaseSqliteWorkerStore, t as closeUnclaimedSharedStateSqliteWorkers } from "./sqlite-worker-store-DqYxJvP-.mjs";
import { i as captureAgentDatabaseAdmission, o as captureAgentDatabasePreparationJournal } from "./agent-database-admission-BBZaKb2P.mjs";
import { x as getAgentDeletionDatabaseCleanup } from "./agent-deletion-journal-DWltVExw.mjs";
import { l as hasAgentDatabaseMaintenanceAuthority } from "./openclaw-agent-db-lease-BALzR11f.mjs";
import { t as cache } from "./openclaw-agent-db-lifecycle-BmHpWusP.mjs";
import { n as captureOpenClawAgentDatabaseRegistration } from "./openclaw-agent-db-registry-listing-Bbgu0YJA.mjs";
import { t as requestOpenClawAgentDatabaseQuickCheck } from "./openclaw-database-verify-C2QLTOMt.mjs";
import { s as getGatewayRestartDrainSignal } from "./gateway-work-admission-G8XukyHo.mjs";
import { t as cleanupRetiredAgentDatabaseLease } from "./openclaw-agent-execution-cleanup-DDvEvdYr.mjs";
import { AsyncLocalStorage } from "node:async_hooks";
import { isDeepStrictEqual } from "node:util";
import { randomUUID } from "node:crypto";
import { MessagePort } from "node:worker_threads";
import { addAbortListener } from "node:events";
//#region src/state/openclaw-agent-execution-native.ts
async function settleAgentRegistration(registration, operation) {
let result;
try {
result = {
ok: true,
value: await operation()
};
} catch (error) {
result = {
ok: false,
error
};
}
try {
registration.finish();
} catch (error) {
if (!result.ok) throw createSqliteLifecycleAggregateError([result.error, error], "Agent open and registration publication failed", result.error);
throw error;
}
if (!result.ok) throw result.error;
return result.value;
}
/** A logical execution owner can replace this generation only after its native close settles. */
function createAgentDatabaseNativeGeneration(agentId, pathname, context, assertLogicalCurrent, assertCleanupOwned, expectedIdentity, acceptFileIdentity, creatingIdentity) {
const input = {
leaseId: randomUUID(),
agentId,
databasePath: pathname,
stateDatabasePath: context.admission.databasePath,
environment: context.environment,
...expectedIdentity ? { expectedIdentity } : {},
...creatingIdentity ? { creatingIdentity } : {}
};
let retiring = false;
let opening;
let openedStore;
let openingFailed = false;
let closing;
let nativeIdentity;
let nativeStopped;
let readCloseReceipt;
let lease;
let quickCheckPending = false;
let receiveValidation;
const assertCurrent = () => {
assertLogicalCurrent();
if (retiring) throw new Error("Agent native generation is retiring");
if (openedStore && !isSqliteWorkerStoreAvailable(openedStore)) throw new Error("Agent database execution lost its native owner");
};
const admission = (source, registration, assertCallerCurrent) => (operation) => {
const assertPreparationJournal = captureAgentDatabasePreparationJournal(agentId, { env: context.environment });
const nativeLocations = [
pathname,
...nativeIdentity ? [nativeIdentity.nativeLocation] : [],
context.admission.databasePath,
context.admission.identity.canonicalPath
];
const assertSourceCurrent = (identity) => {
source.assertCurrent();
assertCurrent();
assertCallerCurrent?.(identity);
if (identity) assertExistingDatabaseIdentity(pathname, `file:${identity.physicalIdentity}`, identity.birthtime);
};
const authorizeNative = (request) => {
const facts = request.facts;
if (request.stage === "prepare" && isRecord(facts) && facts.kind === "agent-registration-committed") {
const received = facts.registration;
if (!registration || !lease || !isRecord(received) || received.agentId !== input.agentId || received.agentPath !== pathname || received.stateDatabasePath !== lease.sharedStatePath || received.stateDatabaseIdentity !== lease.sharedStateIdentity) throw new Error("Agent registration commit differs from its admitted native owner");
registration.recordCommitted({
agentId: input.agentId,
agentPath: pathname,
stateDatabasePath: lease.sharedStatePath,
stateDatabaseIdentity: lease.sharedStateIdentity
});
return;
}
assertCurrent();
if (!nativeIdentity || creatingIdentity) assertCallerCurrent?.();
if (request.stage === "prepare" && isRecord(facts) && facts.kind === "agent-registration-start") {
assertSourceCurrent();
if (!registration || !lease || !isDeepStrictEqual(facts.lease, lease)) throw new Error("Agent registration start differs from its admitted native owner");
registration.begin();
return;
}
if (request.stage === "prepare" && isRecord(facts) && facts.kind === "shared-owner") {
if (!(facts.validationPort instanceof MessagePort)) throw new Error("Agent worker lost its validation handoff port");
try {
assertSourceCurrent();
publishOpenClawStateDatabaseWorkerAdmission(context.admission);
const received = facts.lease;
if (!isDeepStrictEqual(facts.identity, context.admission.identity) || !isRecord(received) || received.leaseId !== input.leaseId || received.agentId !== input.agentId || received.path !== pathname || received.ownerPid !== process.pid || received.ownerStartTime !== null && typeof received.ownerStartTime !== "number" || received.sharedStatePath !== context.admission.databasePath || received.sharedStateIdentity !== context.admission.identity.key) throw new Error("Agent worker lease differs from its captured native owner");
lease = {
leaseId: input.leaseId,
agentId: input.agentId,
path: pathname,
ownerPid: process.pid,
ownerStartTime: received.ownerStartTime,
sharedStatePath: context.admission.databasePath,
sharedStateIdentity: context.admission.identity.key
};
receiveValidation = captureOpenClawAgentDatabaseValidationTransfer({
agentId,
path: pathname
});
facts.validationPort.postMessage(getOpenClawAgentDatabaseValidationForTransfer({
agentId,
path: pathname
}), []);
} finally {
facts.validationPort.close();
}
return;
}
if (request.stage === "prepare" && isRecord(facts) && facts.kind === "agent-integrity-cached") {
assertSourceCurrent();
if (!lease || !isDeepStrictEqual(facts.lease, lease)) throw new Error("Agent integrity notice differs from its captured native lease");
quickCheckPending = true;
return;
}
if (request.stage === "open") {
if (!isDeepStrictEqual(facts, input)) throw new Error("Agent database open differs from its captured owner");
} else {
const received = isRecord(facts) ? facts.identity : void 0;
if (!isRecord(received) || received.kind !== "file" || typeof received.physicalIdentity !== "string" || typeof received.birthtime !== "string" || typeof received.incarnation !== "string" || typeof received.nativeLocation !== "string" || nativeIdentity && !isDeepStrictEqual(received, nativeIdentity)) throw new Error("Agent database operation belongs to another native owner");
const receivedIdentity = nativeIdentity ?? Object.freeze({
kind: "file",
physicalIdentity: received.physicalIdentity,
birthtime: received.birthtime,
incarnation: received.incarnation,
nativeLocation: received.nativeLocation
});
if (expectedIdentity && (receivedIdentity.physicalIdentity !== expectedIdentity.physicalIdentity || expectedIdentity.birthtime !== void 0 && receivedIdentity.birthtime !== expectedIdentity.birthtime)) throw new Error("Agent database operation differs from its expected physical file");
if (!nativeIdentity) assertExistingDatabaseIdentity(pathname, `file:${receivedIdentity.physicalIdentity}`, receivedIdentity.birthtime);
acceptFileIdentity({
kind: "file",
physicalIdentity: receivedIdentity.physicalIdentity,
birthtime: receivedIdentity.birthtime,
nativeLocation: receivedIdentity.nativeLocation
});
assertPreparationJournal?.(isRecord(facts) ? facts.agentDeletionJournalPresent : void 0);
nativeIdentity ??= receivedIdentity;
return nativeIdentity;
}
};
return source.createAdmission({
attachment: {
kind: "agent-execution",
startupJournal: assertPreparationJournal !== void 0
},
nativeLocations,
assertCurrent,
authorize(request) {
const identity = authorizeNative(request);
if (request.stage === "open" && registration) {
assertSourceCurrent();
if (creatingIdentity ? creatingIdentity.key.startsWith("path:") : !nativeIdentity && !expectedIdentity && readDatabasePathIdentitySync(pathname).key.startsWith("path:")) registration.begin();
}
if (request.stage === "prepare" && identity && receiveValidation && isRecord(request.facts)) {
assertSourceCurrent(identity);
receiveValidation(identity.physicalIdentity, request.facts.validation);
receiveValidation = void 0;
}
assertSourceCurrent(identity);
}
})(operation);
};
const open = (source, assertCallerCurrent, createIfMissing = false, signal) => {
assertCurrent();
source.assertCurrent();
assertCallerCurrent?.();
opening ??= (async () => {
const registration = createIfMissing ? captureOpenClawAgentDatabaseRegistration({
agentId,
agentPath: pathname,
admission: context.admission,
onRegistryChange: source.onRegistryChange
}) : void 0;
const openStore = async () => {
const assertOpening = () => {
assertCurrent();
source.assertCurrent();
assertCallerCurrent?.();
signal?.throwIfAborted();
};
const createAdmission = admission(source, registration, assertCallerCurrent);
return await openAgentDatabaseSqliteWorkerStore({
moduleUrl: resolveRuntimeWorkerUrl(runtimeProcessEntrypoints.agentDatabaseExecution),
databasePath: pathname,
input,
existingOnly: !createIfMissing
}, {
stateContext: context,
stateDatabasePath: context.admission.databasePath,
assertCurrent: assertOpening,
signal,
createAdmission,
onNativeStopped: (stopped, readReceipt) => {
nativeStopped = stopped;
readCloseReceipt = readReceipt;
}
});
};
const store = registration ? await settleAgentRegistration(registration, openStore) : await openStore();
if (!store) return;
openedStore = store;
try {
assertCurrent();
return store;
} catch (error) {
try {
await store.close();
} catch (cleanupError) {
throw new AggregateError([error, cleanupError], "Agent open and cleanup failed", { cause: cleanupError });
}
throw error;
}
})().catch((error) => {
openingFailed = true;
throw error;
});
const attempt = opening;
return attempt.then((store) => {
if (!store && opening === attempt) opening = void 0;
if (!store && createIfMissing) return open(source, assertCallerCurrent, true, signal);
return store;
});
};
async function run(source, operation, assertCallerCurrent, createIfMissing = false, signal) {
const assertOperationCurrent = () => {
assertCurrent();
source.assertCurrent();
assertCallerCurrent?.();
signal?.throwIfAborted();
};
const store = openedStore ?? await open(source, assertCallerCurrent, createIfMissing, signal);
assertOperationCurrent();
if (!store) return;
if (!nativeIdentity) {
const registration = captureOpenClawAgentDatabaseRegistration({
agentId,
agentPath: pathname,
admission: context.admission,
onRegistryChange: source.onRegistryChange
});
await settleAgentRegistration(registration, async () => {
const createAdmission = admission(source, registration, assertCallerCurrent);
await runSqliteWorkerStoreOperation(store, (scope) => scope.execute({
type: "database.prepareWrite",
input: void 0
}, { signal }), void 0, assertOperationCurrent, createAdmission);
assertCurrent();
source.assertCurrent();
});
}
if (quickCheckPending) {
quickCheckPending = false;
requestOpenClawAgentDatabaseQuickCheck({
path: pathname,
env: input.environment
});
}
return runSqliteWorkerStoreOperation(store, operation, void 0, assertOperationCurrent, admission(source, void 0, assertCallerCurrent));
}
const readConfirmedClose = () => {
const receipt = readCloseReceipt?.();
if (!receipt || !nativeIdentity || !lease || receipt.incarnation !== nativeIdentity.incarnation || receipt.identity.key !== `file:${nativeIdentity.physicalIdentity}` || receipt.identity.canonicalPath !== nativeIdentity.nativeLocation) return;
return {
receipt,
identity: nativeIdentity,
lease
};
};
const publishCloseCheckpoint = () => {
const closed = readConfirmedClose();
if (!closed) return;
const { receipt, identity, lease: closedLease } = closed;
try {
assertCleanupOwned();
assertExistingDatabaseIdentity(pathname, receipt.identity.key);
assertExistingDatabaseIdentity(identity.nativeLocation, receipt.identity.key);
assertExistingDatabaseIdentity(closedLease.sharedStatePath, closedLease.sharedStateIdentity);
publishSqliteWalCheckpointObservation(pathname, receipt.checkpoint);
} catch {}
};
return {
failed: () => openingFailed || Boolean(openedStore && !isSqliteWorkerStoreAvailable(openedStore)),
captureClaim() {
assertCurrent();
const captured = nativeIdentity;
if (!captured) throw new Error("Agent database generation has not been admitted");
return {
identity: captured.physicalIdentity,
incarnation: captured.incarnation,
assertCurrent() {
assertCurrent();
if (nativeIdentity !== captured) throw new Error("Agent database generation changed");
}
};
},
run,
close() {
retiring = true;
closing ??= (async () => {
const errors = [];
let storeClosed = false;
if (opening) try {
await opening.then(async (store) => {
await store?.close();
storeClosed = store !== void 0;
}, () => openedStore ? openedStore.close() : closeUnclaimedSharedStateSqliteWorkers(pathname));
} catch (error) {
errors.push(error);
}
if (nativeStopped && lease) try {
await nativeStopped;
assertCleanupOwned();
if (storeClosed && readConfirmedClose()) assertExistingDatabaseIdentity(lease.sharedStatePath, lease.sharedStateIdentity);
else await cleanupRetiredAgentDatabaseLease({
context,
stopped: nativeStopped,
assertOwned: assertCleanupOwned,
lease
});
} catch (error) {
errors.push(error);
}
throwSqliteLifecycleErrors(errors, "Agent native close and lease cleanup failed");
publishCloseCheckpoint();
})().catch((error) => {
closing = void 0;
throw error;
});
return closing;
}
};
}
//#endregion
//#region src/state/openclaw-agent-execution.ts
const log = createSubsystemLogger("state/agent-db");
const executionState = resolveGlobalSingleton(Symbol.for("openclaw.agentDatabaseExecutionOwners"), () => ({ owners: /* @__PURE__ */ new Map() }));
const executions = executionState.owners;
const runInExecutionOwnerContext = AsyncLocalStorage.snapshot();
function supportsAgentDatabaseExecutionScope(options) {
return getOpenClawDatabaseMaintenanceScope()?.ownsSchemaMaintenance !== true && !hasAgentDatabaseMaintenanceAuthority() && !getAgentDeletionDatabaseCleanup(options);
}
/** These native-only scopes still need their complete owning caller cutover. */
function supportsOpenClawAgentDatabaseExecution(options) {
return !isIncognitoOpenClawAgentSqlitePath(resolveOpenClawAgentSqlitePath(options), options) && supportsAgentDatabaseExecutionScope(options);
}
/** Borrow before callers yield; native opening stays lazy and release joins owned work. */
function captureOpenClawAgentDatabaseExecution(options, constraints = {}) {
const agentId = normalizeAgentId(options.agentId);
const pathname = resolveOpenClawAgentSqlitePath(options);
if (!supportsOpenClawAgentDatabaseExecution(options)) throw new Error("This agent database scope still requires its existing native owner");
let existing = executions.get(pathname);
const expectedCreationIdentity = constraints.expectedCreationIdentity ? Object.freeze({ ...constraints.expectedCreationIdentity }) : void 0;
if (!existing || expectedCreationIdentity) {
const identity = readDatabasePathIdentitySync(pathname);
existing ??= executions.get(identity.canonicalPath);
if (expectedCreationIdentity) {
const capturesAbsence = expectedCreationIdentity.key.startsWith("path:");
if (constraints.expectedIdentity || capturesAbsence && (existing || cache.databases.has(pathname) || cache.pending.has(pathname)) || !capturesAbsence && (!expectedCreationIdentity.key.startsWith("file:") || typeof expectedCreationIdentity.birthtime !== "string") || identity.key !== expectedCreationIdentity.key || identity.canonicalPath !== expectedCreationIdentity.canonicalPath || identity.birthtime !== expectedCreationIdentity.birthtime) throw new Error("Agent creation no longer owns its originally observed target");
}
if (!existing) return createAgentDatabaseExecution(options, {
agentId,
pathname,
identity,
initialIdentity: constraints.expectedIdentity,
expectedCreationIdentity
});
}
if (existing.agentId !== agentId) throw new Error(`OpenClaw agent database ${pathname} is already open for agent ${existing.agentId}; requested agent ${agentId}.`);
const env = process.platform === "win32" ? cloneEnvWithPlatformSemantics(options.env ?? process.env) : options.env;
const state = captureOpenClawStateReadContext(resolveOpenClawStateSqlitePath(env));
if (existing.sharedDatabaseKey !== state.admission.identity.key) throw new Error("Agent database execution belongs to another shared-state database; drain its existing resources before changing the state directory.");
return existing.borrow(pathname, constraints.expectedIdentity, expectedCreationIdentity);
}
function createAgentDatabaseExecution(options, prepared) {
const { agentId, pathname, identity, initialIdentity, expectedCreationIdentity } = prepared;
const context = captureOpenClawStateWorkerContext({ env: options.env });
const executionOptions = {
agentId,
path: pathname,
env: context.environment
};
const aliases = /* @__PURE__ */ new Map();
const assertAgentAdmitted = captureAgentDatabaseAdmission(agentId, { env: context.environment });
let retired = false;
let revoked = false;
let borrowers = 0;
let creationReference;
let generation;
let fileIdentity;
let nativeClosing;
let cleanupFailure;
let closing;
let unregisterShared;
let idleTimer;
let idleDrainListener;
const clearIdleTimer = () => {
idleDrainListener?.[Symbol.dispose]();
idleDrainListener = void 0;
clearTimeout(idleTimer);
idleTimer = void 0;
};
const reportCleanupFailure = (error) => {
try {
log.warn(`Agent database idle cleanup failed: ${formatErrorMessage(error)}`);
} catch {}
};
const finishRetirement = () => {
retired = true;
if (executions.get(pathname) !== owner) return;
unregisterAgent();
unregisterShared?.();
};
const assertCurrent = () => {
if (retired || executions.get(pathname) !== owner || !supportsAgentDatabaseExecutionScope(executionOptions)) throw new Error("Agent database execution admission is closed");
context.admission.assertCurrent();
assertAgentAdmitted();
};
const closeNative = (expected) => {
if (expected && generation !== expected) return Promise.resolve();
clearIdleTimer();
if (nativeClosing) return nativeClosing;
const captured = generation;
if (!captured) return Promise.resolve();
const result = captured.close().then(() => {
if (generation === captured) {
generation = void 0;
cleanupFailure = void 0;
if (executionState.idle === owner) executionState.idle = void 0;
}
}, (error) => {
cleanupFailure = { error };
throw error;
});
nativeClosing = result;
result.finally(() => {
if (nativeClosing === result) nativeClosing = void 0;
}).catch(() => void 0);
return result;
};
async function run(source, operation, assertCallerCurrent, expectedIdentity, retireNativeOnFailure = false, createIfMissing = false, creatingTarget, signal) {
const pending = cache.pending.get(pathname);
if (pending) {
if (creatingTarget && !fileIdentity) throw new Error("Agent creation cannot adopt another pending opener");
if (pending.agentId !== agentId) throw new Error(`Agent database ${pathname} is opening for ${pending.agentId}`);
await pending.promise;
pending.controller.signal.throwIfAborted();
assertCallerCurrent();
}
if (nativeClosing) {
await nativeClosing;
assertCallerCurrent();
}
if (cleanupFailure) {
await closeNative();
assertCurrent();
source.assertCurrent();
assertCallerCurrent();
}
if (!generation) {
for (let idle = executionState.idle; idle && idle !== owner; idle = executionState.idle) {
await idle.closeIdle();
assertCurrent();
source.assertCurrent();
assertCallerCurrent();
}
if (!generation) {
const created = createAgentDatabaseNativeGeneration(agentId, identity.canonicalPath, context, assertCurrent, () => {
if (executions.get(pathname) !== owner || generation !== created || !nativeClosing) throw new Error("Agent cleanup no longer owns its original execution reference");
}, expectedIdentity ?? fileIdentity, (received) => {
if (fileIdentity && (fileIdentity.physicalIdentity !== received.physicalIdentity || fileIdentity.birthtime !== received.birthtime)) throw new Error("Agent database execution belongs to another physical file");
fileIdentity ??= Object.freeze({ ...received });
}, fileIdentity ? void 0 : creatingTarget);
generation = created;
}
}
const current = generation;
try {
const result = await current.run(source, operation, assertCallerCurrent, createIfMissing, signal);
if (generation === current && current.failed()) try {
await owner.close();
} catch (error) {
reportCleanupFailure(error);
}
return result;
} catch (error) {
const nativeFailed = current.failed();
if (generation === current && (nativeFailed || retireNativeOnFailure)) try {
if (nativeFailed) await owner.close();
else await closeNative(current);
} catch (cleanupError) {
throw retainSqliteWorkerErrorCode(new AggregateError([error, cleanupError], "Agent operation and cleanup failed", { cause: error }), error);
}
throw error;
}
}
const owner = {
agentId,
get sharedDatabaseKey() {
return context.admission.identity.key;
},
assertCurrent,
borrow(borrowedPath, expected, creating) {
const expectedIdentity = expected ? Object.freeze({ ...expected }) : void 0;
const creatingTarget = creating ? Object.freeze({ ...creating }) : void 0;
const assertReferenceCurrent = (nativeIdentity) => {
assertCurrent();
if (!fileIdentity || creatingTarget) {
const current = readDatabasePathIdentitySync(borrowedPath);
if (current.canonicalPath !== identity.canonicalPath || creatingTarget?.key.startsWith("file:") && (current.key !== creatingTarget.key || current.birthtime !== creatingTarget.birthtime)) throw new Error("Agent database borrower changed its originally observed target");
}
if (fileIdentity && expectedIdentity && (fileIdentity.physicalIdentity !== expectedIdentity.physicalIdentity || fileIdentity.birthtime !== void 0 && expectedIdentity.birthtime !== void 0 && fileIdentity.birthtime !== expectedIdentity.birthtime)) throw new Error("Agent database borrower belongs to another physical file");
const file = fileIdentity ?? expectedIdentity;
const birthtime = fileIdentity?.birthtime ?? expectedIdentity?.birthtime;
if (file) {
if (nativeIdentity && (nativeIdentity.physicalIdentity !== file.physicalIdentity || birthtime !== void 0 && nativeIdentity.birthtime !== birthtime)) throw new Error("Agent database borrower belongs to another physical file");
if (!nativeIdentity || borrowedPath !== nativeIdentity.nativeLocation) assertExistingDatabaseIdentity(borrowedPath, `file:${file.physicalIdentity}`, birthtime);
}
};
assertReferenceCurrent();
if (creatingTarget && !fileIdentity && generation) throw new Error("Agent creation cannot capture another pending native opener");
retainAlias(borrowedPath);
observeOpenClawDatabaseMaintenanceResource(aliases.get(pathname));
borrowers += 1;
clearIdleTimer();
if (executionState.idle === owner && !nativeClosing && !cleanupFailure) executionState.idle = void 0;
const reference = {};
if (creatingTarget && !fileIdentity) creationReference ??= reference;
const assertCreationReference = (create) => {
if (creatingTarget && !fileIdentity && !create) throw new Error("Originally observed agent target requires creating admission first");
if (creationReference && !fileIdentity && (!create || creationReference !== reference)) throw new Error("Originally observed agent target requires its captured creating reference");
};
let released = false;
let release;
const pending = /* @__PURE__ */ new Set();
const assertBorrowed = () => {
if (released) throw new Error("Agent database execution reference is released");
assertReferenceCurrent();
};
return {
agentId,
path: borrowedPath,
get fileIdentity() {
assertBorrowed();
return fileIdentity;
},
assertCurrent: assertBorrowed,
captureGenerationClaim() {
assertBorrowed();
const captured = generation;
if (!captured) throw new Error("Agent database execution has no admitted generation");
const claim = captured.captureClaim();
return {
identity: claim.identity,
incarnation: claim.incarnation,
assertCurrent() {
assertBorrowed();
if (generation !== captured) throw new Error("Agent database execution generation was replaced");
claim.assertCurrent();
}
};
},
async prepare(source, signal) {
assertBorrowed();
assertCreationReference(true);
const result = run(source, async () => void 0, (nativeIdentity) => {
assertReferenceCurrent(nativeIdentity);
assertCreationReference(true);
}, expectedIdentity, false, true, creatingTarget, signal);
pending.add(result);
result.finally(() => pending.delete(result)).catch(() => void 0);
await result;
},
async runExisting(source, operation, runOptions) {
assertBorrowed();
assertCreationReference(false);
const result = run(source, operation, (nativeIdentity) => {
assertReferenceCurrent(nativeIdentity);
assertCreationReference(false);
}, expectedIdentity, runOptions?.retireNativeOnFailure);
pending.add(result);
result.finally(() => pending.delete(result)).catch(() => void 0);
return result;
},
release() {
released = true;
release ??= (async () => {
await Promise.allSettled(pending);
if (creationReference === reference && !fileIdentity && !nativeClosing && !cleanupFailure) {
if (generation) try {
await closeNative(generation);
} catch (error) {
reportCleanupFailure(error);
}
if (!generation && !nativeClosing && !cleanupFailure) creationReference = void 0;
}
borrowers -= 1;
if (borrowers !== 0 || retired || cleanupFailure) return;
const drainSignal = getGatewayRestartDrainSignal();
if (generation && !nativeClosing && !executionState.idle && !drainSignal.aborted) {
executionState.idle = owner;
const timer = runInExecutionOwnerContext(() => setTimeout(() => {
if (idleTimer !== timer || executionState.idle !== owner) return;
owner.closeIdle().catch(reportCleanupFailure);
}, SQLITE_IDLE_HANDLE_TTL_MS));
idleTimer = timer;
timer.unref();
idleDrainListener = addAbortListener(drainSignal, () => {
runInExecutionOwnerContext(() => {
owner.closeIdle().catch(reportCleanupFailure);
});
});
return;
}
try {
await owner.closeIdle();
} catch (error) {
reportCleanupFailure(error);
}
})();
return release;
}
};
},
async closeIdle() {
await closeNative();
if (borrowers === 0 && !generation) finishRetirement();
},
close() {
retired = true;
closing ??= (async () => {
await closeNative();
finishRetirement();
})().catch((error) => {
closing = void 0;
if (!revoked) retired = false;
throw error;
});
return closing;
}
};
const unregisterAgent = () => {
for (const [alias, unregister] of aliases) {
if (executions.get(alias) === owner) executions.delete(alias);
unregister();
}
aliases.clear();
};
const retainAlias = (alias) => {
if (aliases.has(alias)) return;
const register = () => registerOpenClawAgentDatabaseAsyncResource({
agentId,
path: alias,
revoke() {
revoked = true;
retired = true;
clearIdleTimer();
},
close: () => owner.close()
});
const unregister = aliases.size === 0 ? register() : runOutsideOpenClawDatabaseMaintenanceScope(register);
aliases.set(alias, unregister);
executions.set(alias, owner);
};
try {
retainAlias(pathname);
retainAlias(identity.canonicalPath);
unregisterShared = registerOpenClawStateDatabaseAsyncResource({ close: async (sharedIdentity) => {
if (!sharedIdentity || sharedIdentity.key === context.admission.identity.key) await owner.close();
} });
return owner.borrow(pathname, initialIdentity, expectedCreationIdentity);
} catch (error) {
unregisterAgent();
unregisterShared?.();
throw error;
}
}
//#endregion
export { supportsOpenClawAgentDatabaseExecution as n, captureOpenClawAgentDatabaseExecution as t };