UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

150 lines (149 loc) • 7.4 kB
import { t as createSubsystemLogger } from "./subsystem-BlrgtxyM.mjs"; import { s as resolveOpenClawStateSqlitePath } from "./openclaw-state-db.paths-BvBEgHyD.mjs"; import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs"; import { r as resolveAgentConfig } from "./agent-scope-config-BU4qc9bA.mjs"; import "./session-key-BC4m_Ly5.mjs"; import { l as runOpenClawStateWriteTransaction } from "./openclaw-state-db-VZFEEleX.mjs"; import { t as withOpenClawStateLease } from "./openclaw-state-lease-D_rzQ4lw.mjs"; import { h as readAgentDatabaseAdmissionRefusal } from "./agent-database-admission-BBZaKb2P.mjs"; import { b as createAgentDeletionDatabaseCleanup, c as removeAgentDeletionJournal, f as readAgentProvenance, i as completeAgentDeletionJournalInDatabase, l as updateAgentDeletionJournalCleanupPaths, n as beginAgentDeletionJournal, o as readAgentDeletionJournal, r as claimCompletedAgentDeletionJournal, s as readAgentDeletionJournalInDatabase, u as updateAgentDeletionJournalDatabasePaths } from "./agent-deletion-journal-DWltVExw.mjs"; import { a as assertNoOpenClawAgentDatabaseLeases } from "./openclaw-agent-db-lease-BALzR11f.mjs"; import path from "node:path"; import { isDeepStrictEqual } from "node:util"; import crypto from "node:crypto"; //#region src/agents/agent-lifecycle-registry.ts var AgentDeletionAuthorityRollbackError = class extends AggregateError {}; var AgentDeletionCommitUncertainError = class extends Error { constructor(cause) { super(cause instanceof Error ? cause.message : String(cause), { cause }); } }; const log = createSubsystemLogger("agents/lifecycle"); /** Acquire before the config lock and retain ownership through cleanup and recovery. */ function withAgentDeletion(agentId, run, options = {}) { const id = normalizeAgentId(agentId); const statePath = path.resolve(options.path ?? resolveOpenClawStateSqlitePath(options.env ?? process.env)); const stateOptions = { ...options, path: statePath, env: { ...options.env ?? process.env } }; return withOpenClawStateLease({ scope: "core:agent-deletion", key: id, database: { scope: "shared", options: stateOptions }, leaseMs: 6e4, waitMs: 5e3, heartbeat: "worker", leaseLabel: "agent deletion", operationLabel: "agent.deletion.lease" }, async (lease) => { let begun = false; let closed = false; try { return await run((entry) => { if (closed || begun || normalizeAgentId(entry.agentId) !== id) throw new Error(`Agent ${id} deletion already began or has a different target.`); begun = true; const operationId = crypto.randomUUID(); const journal = runOpenClawStateWriteTransaction((database) => { lease.assertOwnedInTransaction(database.db); return beginAgentDeletionJournal({ ...entry, agentId: id, operationId, deleteFiles: entry.deleteFiles !== false }, stateOptions); }, stateOptions); const assertJournal = (currentStatePath, entries, database) => { if (closed || path.resolve(currentStatePath) !== statePath || !entries.some((current) => current.agentId === id && current.operationId === operationId && !current.cleanupCompleted)) throw new Error(`Agent ${id} deletion no longer owns database cleanup.`); if (database) lease.assertOwnedInTransaction(database.db); else lease.assertOwned(); return id; }; const assertCurrent = (database) => { const current = closed ? void 0 : database ? readAgentDeletionJournalInDatabase(database, id) : readAgentDeletionJournal(id, stateOptions); assertJournal(database?.path ?? statePath, current ? [current] : [], database); }; const mutateJournal = (mutate) => runOpenClawStateWriteTransaction((database) => { assertCurrent(database); return mutate(); }, stateOptions); const completeInTransaction = (database) => { assertCurrent(database); if (!completeAgentDeletionJournalInDatabase(database, id, operationId)) throw new Error(`Failed to complete deletion journal for agent ${id}.`); closed = true; }; return { entry: journal, assertCurrent, runDatabaseCleanup: createAgentDeletionDatabaseCleanup({ statePath, assertAdmission: () => assertNoOpenClawAgentDatabaseLeases(id, stateOptions), assertCurrent, assertJournal, withCommit: (commit) => { let committed = false; try { mutateJournal(() => { commit(); committed = true; }); } catch (error) { if (!committed) throw error; try { log.warn("Agent deletion committed, but releasing its state guard failed", { agentId: id, error }); } catch {} } } }), fenceDatabasePaths: (paths) => mutateJournal(() => { if (!updateAgentDeletionJournalDatabasePaths(id, operationId, paths, stateOptions)) throw new Error(`Failed to fence database cleanup paths for agent ${id}.`); journal.databasePaths = [...new Set(paths.map((entryPath) => path.resolve(entryPath)))]; }), fenceCleanupPaths: (paths) => mutateJournal(() => { if (!updateAgentDeletionJournalCleanupPaths(id, operationId, paths, stateOptions)) throw new Error(`Failed to fence cleanup paths for agent ${id}.`); journal.cleanupPaths = [...paths]; }), completeInTransaction, finish: () => runOpenClawStateWriteTransaction(completeInTransaction, stateOptions), rollback: () => mutateJournal(() => { if (!removeAgentDeletionJournal(id, operationId, stateOptions)) throw new Error(`Failed to roll back deletion journal for agent ${id}.`); closed = true; }) }; }); } finally { closed = true; } }); } /** Atomically claim a completed deletion tombstone for a newly created identity. */ function claimCompletedAgentDeletion(agentId, operationId, options = {}) { return claimCompletedAgentDeletionJournal(normalizeAgentId(agentId), operationId, options); } /** Return whether this process must refuse new authority for an agent id. */ function isAgentDeletionBlocked(agentId, options = {}, database) { return Boolean(database ? readAgentDeletionJournalInDatabase({ db: database }, agentId, "runtime") : readAgentDeletionJournal(agentId, options, "runtime")); } /** Captures the exact durable incarnation of an existing, deletion-safe agent. */ function captureAgentLifecycleBinding(config, agentId, options = {}) { const id = normalizeAgentId(agentId); if (!resolveAgentConfig(config, id) || readAgentDatabaseAdmissionRefusal(id, options) || isAgentDeletionBlocked(id, options)) return; return Object.freeze({ agentId: id, provenance: readAgentProvenance(id, options) ?? null }); } /** Revalidates an agent binding against both the roster and lifecycle owner. */ function matchesAgentLifecycleBinding(config, binding, options = {}) { const id = normalizeAgentId(binding.agentId); return id === binding.agentId && Boolean(resolveAgentConfig(config, id)) && !readAgentDatabaseAdmissionRefusal(id, options) && !isAgentDeletionBlocked(id, options) && isDeepStrictEqual(readAgentProvenance(id, options) ?? null, binding.provenance); } //#endregion export { isAgentDeletionBlocked as a, claimCompletedAgentDeletion as i, AgentDeletionCommitUncertainError as n, matchesAgentLifecycleBinding as o, captureAgentLifecycleBinding as r, withAgentDeletion as s, AgentDeletionAuthorityRollbackError as t };