UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

646 lines (645 loc) • 28.4 kB
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import { a as executeSqliteQueryTakeFirstSync, o as getNodeSqliteKysely } from "./kysely-sync-DUWzQvCU.mjs"; import { i as getAdmittedSqliteSchemaFacts } from "./sqlite-schema-facts-BVf0E_0z.mjs"; import { i as tableExists } from "./openclaw-state-db-schema-helpers-DKz5xGgh.mjs"; import { t as createSubsystemLogger } from "./subsystem-4NzR43uz.mjs"; import { c as resolveOpenClawStateSqlitePath } from "./openclaw-state-db.paths-CmKkMYdo.mjs"; import { a as isStateDatabaseReadAdmissionInvalidatedError } from "./openclaw-state-db-async-lifecycle-DwFy2ZTk.mjs"; import { a as createOpenClawStateLeaseLostError } from "./openclaw-state-lease-error-Ds9BTWol.mjs"; import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs"; import "./session-key-DY3MOX4N.mjs"; import { r as resolveAgentConfig } from "./agent-scope-config-BEk6-ThS.mjs"; import { g as invalidateOpenClawAgentDatabaseValidationsForAgent } from "./openclaw-agent-db-validation-cache-CHrtutWN.mjs"; import { a as observeSqliteWorkerCommittedFacts, r as createSqliteWorkerOperationAdmission } from "./sqlite-worker-operation-admission-C5lBzzTk.mjs"; import { _ as readAgentDatabaseAdmissionRefusal, o as captureAgentDatabasePreparationDeletionForIdentity } from "./agent-database-admission-CRwW7FsI.mjs"; import { i as sessionChanges } from "./session-row-changes-Dj2yVWo-.mjs"; import { r as isReservedSystemAgentId } from "./agent-id-DD2vkyR0.mjs"; import { b as createAgentDeletionDatabaseCleanup, d as readAgentDeletionJournalInDatabase, l as readAgentDeletionJournal, u as readAgentDeletionJournalAsync } from "./agent-deletion-journal-DkfGlzu8.mjs"; import { l as runOpenClawStateWriteTransaction } from "./openclaw-state-db-DJZ7EKoI.mjs"; import { l as withExistingOpenClawStateDatabaseCurrentReadOnly, t as executeExistingOpenClawStateRead } from "./openclaw-state-db-readonly-ywwvuxdQ.mjs"; import { n as captureOpenClawStateReadWorkerContext, r as captureOpenClawStateWorkerContext } from "./openclaw-state-worker-context-BvvgY6-t.mjs"; import { i as emitOpenClawAgentDatabaseRegistryChange, o as invalidateRegisteredAgentDatabasesMemo } from "./openclaw-agent-db-registry-listing-DIA5wIwi.mjs"; import { o as runOpenClawStateWorkerOperation } from "./openclaw-state-worker-store-BxZXTKna.mjs"; import { s as verifyOpenClawStateLeaseOwnership } from "./openclaw-state-lease-storage-g4qzeMmW.mjs"; import { n as withOpenClawStateLeaseAsync } from "./openclaw-state-lease-BVIgxmmg.mjs"; import { i as withOpenClawStateLeasesWorkerAdmission, n as withOpenClawStateLeaseRemoteAdmission, r as withOpenClawStateLeaseWorkerAdmission } from "./openclaw-state-lease-worker-owner-BhM4d-xx.mjs"; import { i as captureActiveCronJobAgentDeletion } from "./active-jobs-DcrjhGDv.mjs"; import { o as withCronReceiptAuthorityMutation } from "./receipt-authority-owner-CGp53wXR.mjs"; import { t as clawInstallRecordFromRow } from "./provenance-read.kernel-BQe6L4vF.mjs"; import { t as agentProvenanceFromRow } from "./agent-provenance.kernel-D7leE4pB.mjs"; import path from "node:path"; import { isDeepStrictEqual } from "node:util"; import crypto from "node:crypto"; //#region src/state/agent-deletion-final-guard.ts /** Foreign package writers require a current read immediately before filesystem effects. */ function assertAgentDeletionLeaseFinal(identity, options, assertCurrentHost) { assertCurrentHost(); if (!withExistingOpenClawStateDatabaseCurrentReadOnly(({ db }) => { verifyOpenClawStateLeaseOwnership({ ...identity, transaction: db }); return true; }, { ...options, allowNativeRead: true })) throw createOpenClawStateLeaseLostError(identity); assertCurrentHost(); } /** The minted host owner surrounds this current point read; comparison fields alone grant nothing. */ function assertAgentDeletionFinalInDatabase(database, guard) { const { lease, predicate } = guard; const query = getNodeSqliteKysely(database).selectFrom("state_leases as lease").leftJoin("agent_deletion_journal as journal", (join) => join.on("journal.agent_id", "=", predicate.agentId)).select([ "lease.owner as leaseOwner", "lease.expires_at as leaseExpiresAt", "journal.operation_id as operationId", "journal.cleanup_completed as cleanupCompleted" ]).where("lease.scope", "=", lease.scope).where("lease.lease_key", "=", lease.key); const assertLeaseAndJournal = (row) => { if (!row || row.leaseOwner !== lease.owner || row.leaseExpiresAt === null || row.leaseExpiresAt <= Date.now()) throw createOpenClawStateLeaseLostError({ ...lease, leaseLabel: "agent deletion" }); if (row.operationId !== predicate.operationId || row.cleanupCompleted !== 0) throw new Error(`Agent ${predicate.agentId} deletion no longer owns database cleanup.`); }; const changedInstall = () => Object.assign(/* @__PURE__ */ new Error(`Claw removal no longer owns agent ${predicate.agentId}.`), { code: "CLAW_INSTALL_CHANGED" }); if (predicate.expectedClawInstall === void 0) { assertLeaseAndJournal(executeSqliteQueryTakeFirstSync(database, query)); return; } if (getAdmittedSqliteSchemaFacts(database)?.tables.has("claw_installs") === false) { if (predicate.expectedClawInstall !== null) throw changedInstall(); assertLeaseAndJournal(executeSqliteQueryTakeFirstSync(database, query)); return; } if (predicate.expectedClawInstall === null) { const row = executeSqliteQueryTakeFirstSync(database, query.leftJoin("claw_installs as install", "install.agent_id", "journal.agent_id").select("install.agent_id as installAgentId")); assertLeaseAndJournal(row); if (row?.installAgentId != null) throw changedInstall(); return; } const row = executeSqliteQueryTakeFirstSync(database, query.innerJoin("claw_installs as install", "install.agent_id", "journal.agent_id").select([ "install.agent_id", "install.schema_version", "install.source_kind", "install.claw_name", "install.claw_version", "install.package_root", "install.manifest_path", "install.integrity_kind", "install.integrity", "install.source_byte_length", "install.manifest_schema_version", "install.plan_integrity", "install.workspace", "install.agent_config_digest", "install.agent_owned_paths_json", "install.bootstrap_source_path", "install.bootstrap_content_digest", "install.status", "install.added_at_ms", "install.updated_at_ms" ])); if (!row) throw changedInstall(); assertLeaseAndJournal(row); if (!isDeepStrictEqual(clawInstallRecordFromRow(row), predicate.expectedClawInstall)) throw changedInstall(); } //#endregion //#region src/state/agent-lifecycle-read.kernel.ts /** One current snapshot for the incarnation and its deletion fence; never cached as authority. */ function readAgentLifecycleStoreFacts(database, agentId) { const db = getNodeSqliteKysely(database); const base = db.selectFrom(db.selectNoFrom((eb) => eb.val(normalizeAgentId(agentId)).as("agent_id")).as("target")); const hasProvenance = tableExists(database, "agent_provenance"); const hasJournal = tableExists(database, "agent_deletion_journal"); const lifecycleQuery = base.$if(hasProvenance, (query) => query.leftJoin("agent_provenance as provenance", "provenance.agent_id", "target.agent_id").select([ "provenance.agent_id as provenanceAgentId", "provenance.created_via as createdVia", "provenance.creator_agent_id as creatorAgentId", "provenance.created_at_ms as createdAtMs" ])).$if(!hasProvenance, (query) => query.select((eb) => [ eb.val(null).as("provenanceAgentId"), eb.val(null).as("createdVia"), eb.val(null).as("creatorAgentId"), eb.val(null).as("createdAtMs") ])).$if(hasJournal, (query) => query.leftJoin("agent_deletion_journal as deletion", "deletion.agent_id", "target.agent_id").select("deletion.agent_id as deletedAgentId")).$if(!hasJournal, (query) => query.select((eb) => eb.val(null).as("deletedAgentId"))); const row = executeSqliteQueryTakeFirstSync(database, lifecycleQuery); if (!row) throw new Error("Agent lifecycle lookup returned no target row"); if (row.deletedAgentId != null || row.provenanceAgentId == null) return { deletionBlocked: row.deletedAgentId != null, provenance: null }; if (row.createdVia == null || row.createdAtMs == null) throw new Error("Agent provenance is missing its durable incarnation fields"); return { deletionBlocked: row.deletedAgentId != null, provenance: agentProvenanceFromRow({ agent_id: row.provenanceAgentId, created_via: row.createdVia, creator_agent_id: row.creatorAgentId ?? null, created_at_ms: row.createdAtMs }) }; } //#endregion //#region src/agents/agent-deletion-errors.ts var AgentDeletionAuthorityRollbackError = class extends AggregateError {}; var AgentDeletionCommitUncertainError = class extends Error { constructor(cause) { super(cause instanceof Error ? cause.message : String(cause), { cause }); } }; //#endregion //#region src/agents/agent-deletion-journal-remote.ts /** The serving Cron owner mutates history; the original deletion lease retains the remote outcome. */ async function beginRemoteAgentDeletionJournal(owner, entry, operationId) { owner.assertCurrent(); const previousEntry = await readAgentDeletionJournalAsync(entry.agentId, { path: owner.context.admission.databasePath, env: owner.context.environment }); owner.assertCurrent(); const journal = await withOpenClawStateLeaseRemoteAdmission(owner.lease, owner.context.admission.databasePath, (authority) => owner.transport({ kind: "begin", entry, operationId, expectedJournal: previousEntry ?? null }, authority)); if (!journal || journal.agentId !== entry.agentId || journal.operationId !== operationId || journal.cleanupCompleted) throw new AgentDeletionCommitUncertainError("Gateway returned a different deletion journal"); owner.assertCurrent(); return { entry: journal, previousEntry }; } async function rollbackRemoteAgentDeletionJournal(owner, agentId, operationId) { owner.assertCurrent(); const journal = await readAgentDeletionJournalAsync(agentId, { path: owner.context.admission.databasePath, env: owner.context.environment }); owner.assertCurrent(); if (!journal || journal.operationId !== operationId || journal.cleanupCompleted) throw new Error(`Agent ${agentId} deletion lost its journal before rollback.`); if (await withOpenClawStateLeaseRemoteAdmission(owner.lease, owner.context.admission.databasePath, (authority) => owner.transport({ kind: "rollback", journal }, authority)) !== null) throw new AgentDeletionCommitUncertainError("Gateway rollback returned a journal"); owner.assertCurrent(); } //#endregion //#region src/agents/agent-deletion-claim.ts /** Atomically claim a completed deletion tombstone for a newly created identity. */ function claimCompletedAgentDeletion(agentId, operationId, options = {}) { const context = captureOpenClawStateWorkerContext({ ...options, path: options.database?.path ?? options.path }); return withCronReceiptAuthorityMutation(context, async (mutation) => runOpenClawStateWorkerOperation(mutation.context, (scope) => scope.execute({ type: "agentDeletion.claimCompleted", input: { agentId: normalizeAgentId(agentId), operationId, nonce: mutation.attachment.nonce } }), { assertCurrent: mutation.assertCurrent, createAdmission: (operation) => { const admission = createSqliteWorkerOperationAdmission((request, grant) => { if (request.stage !== "transaction" && request.stage !== "commit") throw new Error("Agent creation claim requires transaction admission"); mutation.assertCurrent(); grant(); }); mutation.observe(admission, operation, (facts) => { if (!isRecord(facts) || facts.kind !== "agent-deletion-claimed" || facts.agentId !== normalizeAgentId(agentId) || facts.operationId !== operationId || typeof facts.claimed !== "boolean") throw new Error("Agent creation claim lost its committed journal facts"); if (facts.claimed) { try { (context.assertPublicationCurrent ?? context.admission.assertCurrent)(); } catch { return; } sessionChanges.emit({ all: true, scope: "stores" }); } }); return { admission, nativeLocations: [context.admission.databasePath] }; } })); } //#endregion //#region src/agents/agent-lifecycle-registry.ts 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 journalTransport = options.journalTransport; 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.database?.path ?? options.path ?? resolveOpenClawStateSqlitePath(options.env ?? process.env)); const context = captureOpenClawStateWorkerContext({ ...options, path: statePath, env: { ...options.env ?? process.env } }); return withOpenClawStateLeaseAsync({ scope: "core:agent-deletion", key: id, leaseMs: 6e4, waitMs: 5e3, heartbeat: "worker", leaseLabel: "agent deletion", operationLabel: "agent.deletion.lease" }, context, async (lease) => withOpenClawStateLeaseWorkerAdmission(lease, statePath, async (lifetime) => { let begun = false; let closed = false; let currentOperationId; const assertCurrentHost = () => { if (closed) throw new Error(`Agent ${id} deletion no longer owns database cleanup.`); lifetime.assertCurrent(); }; const execute = (apply, publication) => { assertCurrentHost(); const invoke = (admission, identities) => { const identity = identities[0]; if (!identity) throw new Error("Agent deletion requires a retained lease"); return runOpenClawStateWorkerOperation(publication?.mutation?.context ?? context, (scope) => apply(scope, identity, identities.slice(1)), { assertCurrent: admission.assertCurrent, createAdmission: (retained) => { const created = admission.createAdmission(retained); if (publication?.onAdmission) created.admission.observeRequests((request) => publication.onAdmission?.(request, context.admission.identity.key)); const onCommitted = (facts) => { if (!isRecord(facts) || facts.kind !== "agent-deletion-mutated" && facts.kind !== "agent-deletion-began") { publication?.onCommitted?.(facts); return; } if (facts.agentId !== id || facts.operationId !== currentOperationId) throw new Error("Agent deletion commit receipt does not match its owner"); publication?.onCommitted?.(facts); try { (context.assertPublicationCurrent ?? context.admission.assertCurrent)(); } catch (error) { if (isStateDatabaseReadAdmissionInvalidatedError(error)) return; throw error; } if (facts.unregisterDatabases === true) { invalidateRegisteredAgentDatabasesMemo({ path: statePath }); invalidateOpenClawAgentDatabaseValidationsForAgent(id, []); emitOpenClawAgentDatabaseRegistryChange(id); } sessionChanges.emit({ all: true, scope: "stores" }); }; if (publication?.mutation) publication.mutation.observe(created.admission, retained, onCommitted); else observeSqliteWorkerCommittedFacts(created.admission, ({ facts }) => onCommitted(facts)); return created; } }); }; const authority = { assertCurrent: () => { assertCurrentHost(); publication?.mutation?.assertCurrent(); publication?.assertCurrent?.(); } }; return publication?.additionalLeases?.length ? withOpenClawStateLeasesWorkerAdmission([lease, ...publication.additionalLeases], context, (admission) => invoke(admission, admission.identities), authority) : withOpenClawStateLeaseWorkerAdmission(lease, statePath, (admission) => invoke(admission, [admission.identity]), authority); }; try { return await run(async (entry, beginOptions = {}) => { assertCurrentHost(); if (begun || normalizeAgentId(entry.agentId) !== id) throw new Error(`Agent ${id} deletion already began or has a different target.`); begun = true; const capturedEntry = structuredClone(entry); const preserveDeleteFiles = beginOptions.preserveDeleteFiles; const operationId = crypto.randomUUID(); currentOperationId = operationId; const predicate = { agentId: id, operationId, expectedClawInstall: structuredClone(beginOptions.expectedClawInstall) }; const cancelCronRuns = captureActiveCronJobAgentDeletion(id, context.admission.identity.key); const invalidatePreparation = captureAgentDatabasePreparationDeletionForIdentity(id, { databasePath: statePath, identityKey: context.admission.identity.key }); const remoteOwner = journalTransport ? { lease, context, transport: journalTransport, assertCurrent: assertCurrentHost } : void 0; const { entry: journal, previousEntry } = remoteOwner ? await beginRemoteAgentDeletionJournal(remoteOwner, { ...capturedEntry, agentId: id }, operationId) : await withCronReceiptAuthorityMutation(context, async (mutation) => execute((scope, identity) => scope.execute({ type: "agentDeletion.begin", input: { entry: { ...capturedEntry, agentId: id, operationId, deleteFiles: capturedEntry.deleteFiles !== false }, lease: identity, expectedClawInstall: predicate.expectedClawInstall, preserveDeleteFiles, nonce: mutation.attachment.nonce } }), { mutation, onCommitted: () => { invalidatePreparation(); cancelCronRuns(); } })); if (remoteOwner) { invalidatePreparation(); cancelCronRuns(); sessionChanges.emit({ all: true, scope: "stores" }); } assertCurrentHost(); const authority = { assertCurrentHost, withStateLease: (leaseOptions, apply) => withOpenClawStateLeaseAsync(leaseOptions, context, (additionalLease) => withOpenClawStateLeaseWorkerAdmission(additionalLease, statePath, (admission) => { const assertLeaseCurrentHost = () => { assertCurrentHost(); admission.assertCurrent(); }; return apply(additionalLease, assertLeaseCurrentHost, () => assertAgentDeletionLeaseFinal({ ...admission.identity, leaseLabel: leaseOptions.leaseLabel ?? "state lease" }, { path: statePath, env: context.environment }, assertLeaseCurrentHost)); }, { assertCurrent: assertCurrentHost })), runWithLeaseAdmission: (operation) => withOpenClawStateLeaseWorkerAdmission(lease, statePath, (scope) => operation(scope, { lease: scope.identity, predicate }), { assertCurrent: assertCurrentHost }), runWithWorker: (operation, publication) => execute((scope, identity, additionalIdentities) => operation(scope, { lease: identity, predicate }, additionalIdentities), publication) }; const assertCurrentAsync = async () => { await authority.runWithWorker((scope, guard) => scope.execute({ type: "agentDeletion.assertCurrent", input: { guard } })); assertCurrentHost(); }; const assertCurrentFinal = () => { assertCurrentHost(); if (!withExistingOpenClawStateDatabaseCurrentReadOnly(({ db }) => { assertAgentDeletionFinalInDatabase(db, { lease: lifetime.identity, predicate }); return true; }, { path: statePath, env: context.environment })) throw new Error(`Agent ${id} deletion no longer owns database cleanup.`); assertCurrentHost(); }; const assertNativeCurrent = (database) => { assertCurrentHost(); const current = database ? readAgentDeletionJournalInDatabase(database, id) : readAgentDeletionJournal(id, { path: statePath, env: context.environment }); if (!current || current.operationId !== operationId || current.cleanupCompleted) throw new Error(`Agent ${id} deletion no longer owns database cleanup.`); verifyOpenClawStateLeaseOwnership({ ...lifetime.identity, leaseLabel: "agent deletion", ...database ? { transaction: database.db } : { database: { scope: "shared", options: { path: statePath, env: context.environment } } } }); assertCurrentHost(); }; return { ...authority, entry: journal, previousEntry, assertCurrentAsync, assertCurrentFinal, runDatabaseCleanup: createAgentDeletionDatabaseCleanup({ statePath, workerAuthority: authority, assertCurrent: assertNativeCurrent, assertCurrentAsync, assertAdmission: () => authority.runWithWorker((scope, guard) => scope.execute({ type: "agentDeletion.assertNoDatabaseLeases", input: { guard } })), assertJournal: (currentStatePath, entries) => { assertCurrentHost(); if (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; }, withCommit: (commit) => { let committed = false; try { runOpenClawStateWriteTransaction((database) => { assertNativeCurrent(database); commit(); committed = true; }, { path: statePath, env: context.environment }); } catch (error) { if (!committed) throw error; try { log.warn("Agent deletion committed, but releasing its state guard failed", { agentId: id, error }); } catch {} } } }), fenceDatabasePaths: async (paths) => { const normalized = [...new Set(paths.map((pathname) => path.resolve(pathname)))]; await authority.runWithWorker((scope, guard) => scope.execute({ type: "agentDeletion.fencePaths", input: { guard, paths: { kind: "database", paths: normalized } } }), { onCommitted: () => { journal.databasePaths = normalized; } }); }, fenceCleanupPaths: async (paths) => { const captured = structuredClone([...paths]); await authority.runWithWorker((scope, guard) => scope.execute({ type: "agentDeletion.fencePaths", input: { guard, paths: { kind: "cleanup", paths: captured } } }), { onCommitted: () => { journal.cleanupPaths = captured; } }); }, finish: async (finishOptions) => { await authority.runWithWorker((scope, guard) => scope.execute({ type: "agentDeletion.finish", input: { guard, ...finishOptions } }), { onCommitted: () => { closed = true; } }); }, releaseClawRows: async (input) => { const completed = await authority.runWithWorker((scope, guard) => scope.execute({ type: "agentDeletion.releaseClawRows", input: { guard, ...input } }), { onCommitted: () => { if (input.complete) closed = true; } }); if (completed) closed = true; return completed; }, handoffClawRetry: async () => { if (closed || !predicate.expectedClawInstall) return; if (await authority.runWithWorker((scope, guard) => scope.execute({ type: "agentDeletion.handoffClawRetry", input: { guard, retryOperationId: crypto.randomUUID(), nowMs: Date.now() } }), { onCommitted: () => { closed = true; } })) closed = true; }, rollback: async () => { if (remoteOwner) { await rollbackRemoteAgentDeletionJournal(remoteOwner, id, operationId); closed = true; sessionChanges.emit({ all: true, scope: "stores" }); return; } await withCronReceiptAuthorityMutation(context, (mutation) => execute((scope, identity) => scope.execute({ type: "agentDeletion.rollback", input: { guard: { lease: identity, predicate }, nonce: mutation.attachment.nonce } }), { mutation, onCommitted: () => { closed = true; } }), { settlement: true }); } }; }); } finally { closed = true; } })); } /** 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) { const id = normalizeAgentId(agentId); const journal = readAgentDeletionJournalInDatabase(database, id); if (journal && !journal.cleanupCompleted) throw new Error(`Agent ${id} has pending deletion; retry after removal completes.`); } async function readLifecycleFactsInWorker(agentId, options) { const context = captureOpenClawStateReadWorkerContext({ path: options.database?.path ?? options.path, env: options.env }); const reply = await executeExistingOpenClawStateRead({ path: context.admission.databasePath, env: context.environment }, { type: "agentLifecycle.read", input: agentId }, { context, current: true }); context.admission.assertCurrent(); if (!reply) return { deletionBlocked: false, provenance: null }; if (!reply.ok) throw new Error(reply.message); if (reply.type !== "agentLifecycle.read") throw new Error("Unexpected agent lifecycle result"); return reply.facts; } function admitsLifecycleBinding(config, agentId, options) { return Boolean(resolveAgentConfig(config, agentId)) && !readAgentDatabaseAdmissionRefusal(agentId, options); } /** Prepare an incarnation comparison; final consumers still recheck current authority. */ async function captureAgentLifecycleBinding(getConfig, agentId, options = {}) { const capturedOptions = { path: options.database?.path ?? options.path, env: { ...options.env ?? process.env } }; const id = normalizeAgentId(agentId); if (!admitsLifecycleBinding(getConfig(), id, capturedOptions)) return; const facts = await readLifecycleFactsInWorker(id, capturedOptions); if (facts.deletionBlocked || !admitsLifecycleBinding(getConfig(), id, capturedOptions)) return; return Object.freeze({ agentId: id, provenance: facts.provenance }); } /** Preparatory checks may yield; the final effect guard below remains synchronous. */ async function matchesAgentLifecycleBindingAsync(getConfig, binding, options = {}) { const capturedBinding = structuredClone(binding); const capturedOptions = { path: options.database?.path ?? options.path, env: { ...options.env ?? process.env } }; const id = normalizeAgentId(capturedBinding.agentId); if (id !== capturedBinding.agentId || !admitsLifecycleBinding(getConfig(), id, capturedOptions)) return false; const facts = await readLifecycleFactsInWorker(id, capturedOptions); return !facts.deletionBlocked && admitsLifecycleBinding(getConfig(), id, capturedOptions) && isDeepStrictEqual(facts.provenance, capturedBinding.provenance); } /** Native/SDK and foreign writers require a current point read at the final effect boundary. */ function matchesAgentLifecycleBinding(config, binding, options = {}) { const id = normalizeAgentId(binding.agentId); if (id !== binding.agentId || !admitsLifecycleBinding(config, id, options)) return false; const facts = withExistingOpenClawStateDatabaseCurrentReadOnly(({ db }) => readAgentLifecycleStoreFacts(db, id), options); return !facts?.deletionBlocked && isDeepStrictEqual(facts?.provenance ?? null, binding.provenance); } //#endregion export { matchesAgentLifecycleBindingAsync as a, AgentDeletionAuthorityRollbackError as c, matchesAgentLifecycleBinding as i, AgentDeletionCommitUncertainError as l, captureAgentLifecycleBinding as n, withAgentDeletion as o, isAgentDeletionBlocked as r, claimCompletedAgentDeletion as s, assertAgentDeletionAllowsMutation as t };