UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

136 lines (135 loc) • 6.93 kB
import { u as toErrorObject } from "./error-coercion-1P2One0X.mjs"; import { t as createDeferredCore } from "./deferred-D0La5CRk.mjs"; import { a as resolveGlobalSingleton } from "./global-singleton-0m-JziJW.mjs"; import { r as isPathInside } from "./path-guards-CH7noY0Z.mjs"; import { o as getOpenClawDatabaseMaintenanceScope } from "./openclaw-state-db-async-lifecycle-8N3pIeYH.mjs"; import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs"; import path from "node:path"; //#region src/state/openclaw-agent-db-resources.ts const resources = resolveGlobalSingleton(Symbol.for("openclaw.agentDatabaseAsyncResources"), () => ({ active: /* @__PURE__ */ new Set(), closing: /* @__PURE__ */ new Map(), selections: /* @__PURE__ */ new Map() })); /** CLI cleanup can skip loading native database owners when no Worker was admitted. */ function hasOpenClawAgentDatabaseAsyncResources() { return resources.active.size > 0 || resources.closing.size > 0; } /** Observe an existing full close without revoking resources or selecting a successor. */ function captureAgentDatabaseCloseFence(target) { const owned = { agentId: normalizeAgentId(target.agentId), path: path.resolve(target.path) }; const pending = [...resources.selections].flatMap(([selection, completion]) => matchesAgentDatabaseClose(selection, owned) ? [completion] : []); return pending.length ? Promise.all(pending).then(() => void 0) : void 0; } /** Match captured read custody without inspecting files or inferring their owners. */ function matchesAgentDatabaseReadCandidatePath(candidate, pathname) { const capturedPath = path.resolve(candidate.path); const resolvedPath = path.resolve(pathname); if (capturedPath === resolvedPath) return true; if (candidate.scope !== "sibling-family") return false; const captured = path.parse(capturedPath); const selected = path.parse(resolvedPath); return selected.dir === captured.dir && selected.base.startsWith(`${captured.name}.`) && selected.base.endsWith(captured.ext); } function matchesAgentDatabaseClose(selection, resource) { return (selection.path === void 0 || selection.path === resource.path || resource.ownership === "unresolved" && matchesAgentDatabaseReadCandidatePath(resource, selection.path)) && (selection.rootPath === void 0 || isPathInside(selection.rootPath, resource.path) || resource.ownership === "unresolved" && matchesAgentDatabaseReadCandidatePath(resource, selection.rootPath)) && (selection.agentId === void 0 || resource.ownership === "unresolved" || selection.agentId === resource.agentId); } /** Register before admitting a Worker; revocation is synchronous, native drainage is joined. */ function registerOpenClawAgentDatabaseAsyncResource(resource) { return registerAgentDatabaseResource({ ...resource, ownership: "known", agentId: normalizeAgentId(resource.agentId) }); } /** Native readers close synchronously, so successful retirement leaves no asynchronous barrier. */ function registerOpenClawAgentDatabaseSyncResource(resource) { return registerAgentDatabaseResource({ ...resource, ownership: "known", agentId: normalizeAgentId(resource.agentId), close: async () => resource.close(), closeSync: resource.close }); } /** Retain discovery until the known owner is registered, then release this candidate. */ function registerOpenClawAgentDatabaseReadCandidateResource(resource) { return registerAgentDatabaseResource({ ...resource, ownership: "unresolved" }); } function registerAgentDatabaseResource(resource) { const owned = { ...resource, path: path.resolve(resource.path) }; if ([...resources.selections.keys()].some((selection) => matchesAgentDatabaseClose(selection, owned)) || [...resources.closing.keys()].some((closing) => (closing.path === owned.path || closing.ownership === "unresolved" && matchesAgentDatabaseReadCandidatePath(closing, owned.path) || owned.ownership === "unresolved" && matchesAgentDatabaseReadCandidatePath(owned, closing.path)) && (closing.ownership === "unresolved" || owned.ownership === "unresolved" || closing.agentId === owned.agentId))) throw new Error(`Agent database resources are closing: ${owned.path}`); const unregister = () => resources.active.delete(owned); getOpenClawDatabaseMaintenanceScope()?.own(unregister, "agent-resources", () => closeAgentDatabaseResource(owned)); resources.active.add(owned); return unregister; } function closeAgentDatabaseResource(resource, onCloseError) { if (resource.ownership === "known" && resource.closeSync) { resources.closing.set(resource, void 0); try { resource.revoke(); resource.closeSync(); resources.active.delete(resource); resources.closing.delete(resource); return Promise.resolve(); } catch (error) { if (onCloseError) onCloseError(resource.path, error); const failed = Promise.reject(toErrorObject(error, "Agent database cleanup failed")); failed.catch(() => {}); return failed; } } let operation = resources.closing.get(resource); if (!operation) { operation = Promise.resolve().then(() => resource.close()); resources.closing.set(resource, operation); operation.then(() => { resources.active.delete(resource); resources.closing.delete(resource); }, () => { resources.closing.set(resource, void 0); }).catch(() => {}); } if (onCloseError) operation.catch((error) => onCloseError(resource.path, error)).catch(() => {}); resource.revoke(); return operation; } function revokeAgentDatabaseResources(selection, onCloseError) { const closing = /* @__PURE__ */ new Set([...resources.active, ...resources.closing.keys()]); const pending = []; for (const resource of closing) { if (!matchesAgentDatabaseClose(selection, resource)) continue; pending.push(closeAgentDatabaseResource(resource, onCloseError)); } return pending; } async function drainAgentDatabaseResources(selection, closeNative) { const ownedSelection = { ...selection }; const completion = createDeferredCore(); completion.promise.catch(() => {}); resources.selections.set(ownedSelection, completion.promise); try { const errors = (await Promise.allSettled(revokeAgentDatabaseResources(ownedSelection))).flatMap((result) => result.status === "rejected" ? [result.reason] : []); if (errors.length > 0) throw new AggregateError(errors, "Agent database resource drainage failed"); const result = await closeNative(); completion.resolve(); return result; } catch (error) { completion.reject(error); throw error; } finally { resources.selections.delete(ownedSelection); } } //#endregion export { matchesAgentDatabaseReadCandidatePath as a, registerOpenClawAgentDatabaseSyncResource as c, matchesAgentDatabaseClose as i, revokeAgentDatabaseResources as l, drainAgentDatabaseResources as n, registerOpenClawAgentDatabaseAsyncResource as o, hasOpenClawAgentDatabaseAsyncResources as r, registerOpenClawAgentDatabaseReadCandidateResource as s, captureAgentDatabaseCloseFence as t };