UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

201 lines (200 loc) • 10.5 kB
import { t as createSubsystemLogger } from "./subsystem-I-uqdx4f.mjs"; import { i as stageSqliteTransactionState } from "./sqlite-post-commit-CQ_AyDCk.mjs"; import { c as resolveOpenClawStateSqlitePath } from "./openclaw-state-db.paths-DfifnSGp.mjs"; import { y as requireOpenClawStateDatabaseIdentity } from "./openclaw-state-db-cache-BKCnj4of.mjs"; import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs"; import { n as captureOpenClawStateReadWorkerContext } from "./openclaw-state-worker-context-DRG_I6tz.mjs"; import { r as resolveAgentConfig } from "./agent-scope-config-CDYpzA0r.mjs"; import "./session-key-C09ow4h0.mjs"; import { l as runOpenClawStateWriteTransaction } from "./openclaw-state-db-D1jWDzK3.mjs"; import { a as handoffAgentDeletionJournalInDatabase, c as readAgentDeletionJournal, d as updateAgentDeletionJournalCleanupPaths, f as updateAgentDeletionJournalDatabasePaths, i as completeAgentDeletionJournalInDatabase, l as readAgentDeletionJournalInDatabase, n as beginAgentDeletionJournal, r as claimCompletedAgentDeletionJournal, u as removeAgentDeletionJournal, y as createAgentDeletionDatabaseCleanup } from "./agent-deletion-journal-CDljXcnp.mjs"; import { g as readAgentDatabaseAdmissionRefusal, x as isReservedSystemAgentId } from "./agent-database-admission-dWNS6Rcs.mjs"; import { i as readAgentDeletionJournalAuthorityInWorker } from "./agent-deletion-journal.read-Bkjt0nDs.mjs"; import { r as readAgentProvenance } from "./agent-provenance-H6O0H8k6.mjs"; import { a as assertNoOpenClawAgentDatabaseLeases } from "./openclaw-agent-db-lease-D8zYIRzn.mjs"; import { t as withOpenClawStateLease } from "./openclaw-state-lease-CdKflcqg.mjs"; import { i as captureActiveCronJobAgentDeletion } from "./active-jobs-BS2UT6i2.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); if (isReservedSystemAgentId(id)) throw new Error(`System agent ${id} cannot be deleted; run openclaw doctor --fix to quarantine invalid deletion history.`); 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); const cancelCronRuns = captureActiveCronJobAgentDeletion(id, requireOpenClawStateDatabaseIdentity(database).key); const entryJournal = beginAgentDeletionJournal({ ...entry, agentId: id, operationId, deleteFiles: entry.deleteFiles !== false }, stateOptions); if (!stageSqliteTransactionState(database.db, { stage() {}, rollback() {}, commit: cancelCronRuns })) throw new Error("Agent deletion requires a managed transaction"); return entryJournal; }, stateOptions); const readContext = captureOpenClawStateReadWorkerContext(stateOptions); const assertJournalIdentity = (currentStatePath, entries) => { 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.`); return id; }; const assertJournal = (currentStatePath, entries, database) => { assertJournalIdentity(currentStatePath, entries); 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 assertAsyncScopeCurrent = () => { if (closed) throw new Error(`Agent ${id} deletion no longer owns database cleanup.`); lease.signal.throwIfAborted(); readContext.maintenanceScope?.assertAdmission(); readContext.admission.assertCurrent(); }; const assertCurrentAsync = async () => { assertAsyncScopeCurrent(); const verifyLease = lease.assertOwnedAsync; if (!verifyLease) throw new Error("Agent deletion requires asynchronous worker-heartbeat verification."); const current = await readAgentDeletionJournalAuthorityInWorker(id, readContext, lease.signal); assertAsyncScopeCurrent(); assertJournalIdentity(readContext.admission.databasePath, current ? [current] : []); await verifyLease(); assertAsyncScopeCurrent(); }; 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, assertCurrentAsync, handoffToRetry: (database) => { assertCurrent(database); if (!handoffAgentDeletionJournalInDatabase(database, id, operationId, crypto.randomUUID())) throw new Error(`Failed to hand off deletion journal for agent ${id}.`); }, 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")); } /** Keep persisted identity stable until the winning deletion completes or rolls back. */ function assertAgentDeletionAllowsMutation(database, agentId, deletion) { const id = normalizeAgentId(agentId); const journal = readAgentDeletionJournalInDatabase(database, id); if (deletion) { if (deletion.entry.agentId !== id || !journal || journal.operationId !== deletion.entry.operationId || journal.cleanupCompleted) throw new Error(`Agent ${id} mutation does not belong to the current deletion.`); deletion.assertCurrent(database); return; } if (journal && !journal.cleanupCompleted) throw new Error(`Agent ${id} has pending deletion; retry after removal completes.`); } /** 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 { claimCompletedAgentDeletion as a, withAgentDeletion as c, captureAgentLifecycleBinding as i, AgentDeletionCommitUncertainError as n, isAgentDeletionBlocked as o, assertAgentDeletionAllowsMutation as r, matchesAgentLifecycleBinding as s, AgentDeletionAuthorityRollbackError as t };