openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
646 lines (645 loc) • 28.4 kB
JavaScript
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 };