UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

567 lines (566 loc) • 28.8 kB
import { a as resolveGlobalSingleton } from "./global-singleton-0m-JziJW.mjs"; import { c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import { r as isPathInside } from "./path-guards-CH7noY0Z.mjs"; import { a as executeSqliteQueryTakeFirstSync, i as executeSqliteQuerySync, o as getNodeSqliteKysely } from "./kysely-sync-DUWzQvCU.mjs"; import { d as readDatabasePathIdentitySync } from "./sqlite-worker-identity-BRNRpRyc.mjs"; import { i as isSqliteCorruptionError } from "./sqlite-error-diagnostics-CWRyv4lz.mjs"; import { i as tableExists } from "./openclaw-state-db-schema-helpers-DKz5xGgh.mjs"; import { a as resolveOpenClawRegisteredAgentDatabasePath, c as resolveOpenClawStateSqlitePath, i as resolveOpenClawAgentDatabaseStoredPath } from "./openclaw-state-db.paths-CmKkMYdo.mjs"; import { a as resolveSqliteDatabaseFilePaths } from "./sqlite-files-D1rDNZzV.mjs"; import { n as ok, t as err } from "./result-BQGgYouL.mjs"; import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs"; import "./session-key-DY3MOX4N.mjs"; import { k as normalizeAgentDirRegistryPath } from "./agent-scope-config-BEk6-ThS.mjs"; import { v as resolveAgentCreationClaimAgentId } from "./openclaw-agent-db-resources-Cm-sH7kg.mjs"; import { l as resolveOpenClawAgentSqlitePath, r as createOpenClawAgentDatabasePathMatcher } from "./openclaw-agent-db.paths-BbmZlEP5.mjs"; import { i as sessionChanges } from "./session-row-changes-Dj2yVWo-.mjs"; import { r as isReservedSystemAgentId } from "./agent-id-DD2vkyR0.mjs"; import { l as runOpenClawStateWriteTransaction } from "./openclaw-state-db-DJZ7EKoI.mjs"; import { l as withExistingOpenClawStateDatabaseCurrentReadOnly } from "./openclaw-state-db-readonly-ywwvuxdQ.mjs"; import { r as captureOpenClawStateWorkerContext } from "./openclaw-state-worker-context-BvvgY6-t.mjs"; import { t as assertAgentDeletionJournalAvailable, y as reconstructAgentDeletionJournalSchema } from "./openclaw-state-db-schema-additive-S7iMdRlC.mjs"; import { a as recordLegacyMigrationReceipt } from "./state-migrations.receipts-CEp8vxSz.mjs"; import { a as decodeHolds, c as readReport, i as assertAgentDeletionRecoveryHoldsMatch, n as AGENT_DELETION_RECOVERY_SOURCE_KEY, s as readAgentDeletionRecoveryHolds, t as AGENT_DELETION_RECONSTRUCTION_DESCRIPTION } from "./agent-deletion-journal-recovery.kernel-CRZY70U8.mjs"; import { t as parseAgentDeletionDatabasePaths } from "./agent-deletion-journal.read-BRl4UyPn.mjs"; import { t as deleteAgentProvenanceForAgent } from "./agent-provenance-BibT5eoG.mjs"; import path from "node:path"; import { AsyncLocalStorage } from "node:async_hooks"; import { randomUUID } from "node:crypto"; import { isMainThread } from "node:worker_threads"; //#region src/state/agent-deletion-cleanup.ts const databaseCleanup = resolveGlobalSingleton(Symbol.for("openclaw.agentDeletionDatabaseCleanup"), () => new AsyncLocalStorage()); const cleanupHandles = resolveGlobalSingleton(Symbol.for("openclaw.agentDeletionDatabaseCleanupHandles"), () => /* @__PURE__ */ new Map()); const cleanupExecutions = resolveGlobalSingleton(Symbol.for("openclaw.agentDeletionDatabaseCleanupExecutions"), () => /* @__PURE__ */ new Map()); /** The lifecycle owner supplies live closures, never a transferable operation id. */ function createAgentDeletionDatabaseCleanup(owner) { return async (target, run) => { const targetAgentId = normalizeAgentId(target.agentId); const targetPath = path.resolve(target.path); const targetIdentity = readDatabasePathIdentitySync(targetPath); const assertTargetCurrent = () => { const current = readDatabasePathIdentitySync(targetPath); if (current.key !== targetIdentity.key || current.birthtime !== targetIdentity.birthtime || current.canonicalPath !== targetIdentity.canonicalPath) throw new Error("Agent deletion database target changed before cleanup"); }; const runCleanup = async (worker) => { let active = true; const closers = /* @__PURE__ */ new Set(); const closeHandles = async () => { const errors = []; for (const close of [...closers].toReversed()) try { await close(); closers.delete(close); } catch (error) { errors.push(error); } return errors; }; const assertActive = () => { if (!active) throw new Error("Agent deletion database cleanup is no longer active."); assertTargetCurrent(); }; const assertCurrentAsync = async () => { assertActive(); if (worker && owner.assertCurrentAsync) { await owner.assertCurrentAsync(); assertActive(); worker.lease.assertCurrent(); } else owner.assertCurrent(); }; const scope = { agentId: targetAgentId, path: targetPath, canonicalPath: targetIdentity.canonicalPath, statePath: path.resolve(owner.statePath), worker, assertCurrentHost: () => { assertActive(); if (worker) { worker.lease.assertCurrent(); owner.workerAuthority.assertCurrentHost(); } else owner.assertCurrent(); }, assertCurrent: () => { assertActive(); owner.assertCurrent(); }, assertJournal: (statePath, entries) => { assertActive(); return owner.assertJournal(statePath, entries); }, registerClose: (close) => { assertActive(); closers.add(close); }, retryClose: async () => { if (active) throw new Error("Agent database belongs to an active deletion cleanup."); const errors = await closeHandles(); if (errors.length > 0) throw new AggregateError(errors, "Agent deletion database close retry failed."); }, withCommit: (commit) => { assertActive(); owner.withCommit(commit); } }; return await databaseCleanup.run(scope, async () => { let outcome; const closeErrors = []; try { await assertCurrentAsync(); for (const previous of /* @__PURE__ */ new Set([...cleanupHandles.values(), ...cleanupExecutions.values()])) if (previous.statePath === scope.statePath && previous.agentId === scope.agentId && previous.path === scope.path) { await previous.retryClose(); await assertCurrentAsync(); } await owner.assertAdmission(); scope.assertCurrentHost(); const value = await run(); active = false; outcome = ok(value); } catch (error) { outcome = err(error); } finally { active = false; closeErrors.push(...await closeHandles()); } if (!outcome.ok) throw closeErrors.length > 0 ? new AggregateError([outcome.error, ...closeErrors], "Agent deletion database cleanup failed.") : outcome.error; if (closeErrors.length > 0) throw closeErrors.length === 1 ? closeErrors[0] : new AggregateError(closeErrors, "Agent deletion database cleanup failed."); if (worker && owner.assertCurrentAsync) await owner.assertCurrentAsync(); else owner.assertCurrent(); assertTargetCurrent(); return outcome.value; }); }; return owner.workerAuthority ? owner.workerAuthority.runWithLeaseAdmission((lease, guard) => runCleanup({ lease, guard })) : runCleanup(); }; } /** The canonical agent executor owns native close and settlement in its worker. */ function withAgentDeletionWorkerDatabaseCleanup(owner, run) { return databaseCleanup.run({ ...owner, workerOwned: true, assertCurrentHost: owner.assertCurrent, registerClose: () => {}, retryClose: async () => {} }, run); } function getAgentDeletionDatabaseCleanup(params) { const scope = databaseCleanup.getStore(); if (!scope) return; const pathname = resolveOpenClawAgentSqlitePath(params); if (scope.agentId !== normalizeAgentId(params.agentId) || scope.path !== pathname && scope.canonicalPath !== pathname) return; const statePath = params.statePath ?? resolveOpenClawStateSqlitePath(params.env ?? process.env); if (scope.statePath !== path.resolve(statePath)) throw new Error("Agent deletion database cleanup belongs to another state database."); return scope; } function assertAgentDeletionDatabaseCleanupAccess(database, options) { const scope = getAgentDeletionDatabaseCleanup(options); const owner = cleanupHandles.get(database); if (owner && owner !== scope) throw new Error("Agent database belongs to an active deletion cleanup."); scope?.assertCurrent(); } function assertAgentDeletionCleanupAliases(options, isSamePath) { const pathname = resolveOpenClawAgentSqlitePath(options); for (const owned of cleanupHandles.keys()) if (isSamePath(owned.path, pathname)) assertAgentDeletionDatabaseCleanupAccess(owned, options); for (const scope of cleanupExecutions.values()) if (isSamePath(scope.path, pathname) && getAgentDeletionDatabaseCleanup(options) !== scope) throw new Error("Agent database belongs to an active deletion cleanup."); } function assertAgentDeletionExecutionCleanupAccess(execution, options) { const owner = cleanupExecutions.get(execution); const scope = getAgentDeletionDatabaseCleanup(options); if (owner && owner !== scope) throw new Error("Agent database belongs to an active deletion cleanup."); scope?.assertCurrentHost(); } /** Only a cold executor belongs to cleanup; existing surviving stores retain their owner. */ function registerAgentDeletionExecutionCleanup(execution, options, close) { const scope = getAgentDeletionDatabaseCleanup(options); if (!scope?.worker) return; scope.assertCurrentHost(); if (cleanupExecutions.get(execution) === scope) return; cleanupExecutions.set(execution, scope); scope.registerClose(async () => { await close(); cleanupExecutions.delete(execution); }); } function registerAgentDeletionDatabaseCleanup(database, options) { const scope = getAgentDeletionDatabaseCleanup(options); scope?.assertCurrent(); if (!scope || scope.workerOwned) return; cleanupHandles.set(database, scope); return scope; } /** Release the tag only after the native owner has closed and released its lease. */ function releaseAgentDeletionDatabaseCleanup(database) { cleanupHandles.delete(database); } //#endregion //#region src/state/agent-deletion-journal-recovery.ts const JOURNAL_TABLE = "agent_deletion_journal"; /** Schema and Doctor producers cannot infer permission to repair an unverified store. */ function assertAgentDeletionRecoveryAllowsMutation(database, pathname) { assertAgentDeletionJournalAvailable(database.db); const samePath = createOpenClawAgentDatabasePathMatcher(); const held = readAgentDeletionRecoveryHolds(database).find((entry) => samePath(entry.path, pathname)); if (held) throw new Error(`Agent database ${held.path} is held after deletion journal reconstruction. Run openclaw doctor --fix for explicit restoration guidance before repairing agent ${held.agentId}.`); } /** The caller's transaction commits canonical reconstruction and its receipt together. */ function reconstructAgentDeletionJournal(database, held, now = Date.now()) { if (!database.db.isTransaction) throw new Error("Agent deletion journal reconstruction requires a shared-state transaction."); const previous = readReport(database); if (!reconstructAgentDeletionJournalSchema(database.db, database.path)) return decodeHolds(database, previous?.held ?? []); return recordAgentDeletionRecoveryHolds(database, held, { now, missingJournal: held.filter((target) => !previous?.held.some((entry) => entry.agentId === normalizeAgentId(target.agentId) && entry.path === resolveOpenClawAgentDatabaseStoredPath(database.path, target.path))) }); } /** Quarantine and reconstruction use the same durable maintenance holds. */ function recordAgentDeletionRecoveryHolds(database, held, { now = Date.now(), description = AGENT_DELETION_RECONSTRUCTION_DESCRIPTION, missingJournal = [], excludeMissingJournal = [] } = {}) { if (!database.db.isTransaction) throw new Error("Agent deletion recovery requires a shared-state transaction."); const previous = readReport(database); const encode = (target) => ({ agentId: normalizeAgentId(target.agentId), path: resolveOpenClawAgentDatabaseStoredPath(database.path, target.path) }); const key = (entry) => JSON.stringify([entry.agentId, entry.path]); const entries = /* @__PURE__ */ new Map(); for (const entry of [...previous?.held ?? [], ...held.filter((target) => !isReservedSystemAgentId(target.agentId)).map(encode)]) entries.set(key(entry), entry); const missing = /* @__PURE__ */ new Map(); for (const entry of [...previous?.missingJournal ?? [], ...missingJournal.map(encode)]) if (entries.has(key(entry))) missing.set(key(entry), entry); for (const entry of excludeMissingJournal.map(encode)) missing.delete(key(entry)); const report = { ...previous, description, held: [...entries.values()], missingJournal: [...missing.values()] }; recordLegacyMigrationReceipt(database.db, { sourceKey: AGENT_DELETION_RECOVERY_SOURCE_KEY, migrationKind: AGENT_DELETION_RECOVERY_SOURCE_KEY, sourcePath: database.path, targetTable: JOURNAL_TABLE, sourceSha256: null, sourceSizeBytes: null, sourceRecordCount: null, runId: `${AGENT_DELETION_RECOVERY_SOURCE_KEY}:${randomUUID()}`, now, reportJson: JSON.stringify(report), upsert: true }); return decodeHolds(database, report.held); } /** Resolve only explicit targets; the immutable run report keeps the original reconstruction. */ function resolveAgentDeletionRecoveryHolds(database, agentId, targetPaths, predicate) { if (!database.db.isTransaction) throw new Error("Agent deletion recovery resolution requires a shared-state transaction."); const report = readReport(database); if (predicate) assertAgentDeletionRecoveryHoldsMatch(decodeHolds(database, report?.held ?? []), predicate); if (!report) return 0; const id = normalizeAgentId(agentId); const targets = new Set(targetPaths.map((pathname) => resolveOpenClawAgentDatabaseStoredPath(database.path, pathname))); const held = report.held.filter((entry) => entry.agentId !== id || !targets.has(entry.path)); const missingJournal = report.missingJournal?.filter((entry) => entry.agentId !== id || !targets.has(entry.path)); const removed = report.held.length - held.length; if (removed > 0) executeSqliteQuerySync(database.db, getNodeSqliteKysely(database.db).updateTable("migration_sources").set({ report_json: JSON.stringify({ ...report, held, missingJournal }) }).where("source_key", "=", AGENT_DELETION_RECOVERY_SOURCE_KEY)); return removed; } //#endregion //#region src/state/agent-deletion-journal.ts const journalFenceFields = [ "agent_id", "operation_id", "agent_dir", "workspace_dir", "sessions_dir", "database_paths_json", "cleanup_paths_json" ]; function assertAgentDeletionIdentityClaimAllowed(claimAgentId, deletedAgentId) { if (deletedAgentId && normalizeAgentId(claimAgentId) === normalizeAgentId(deletedAgentId)) throw new Error(`OpenClaw agent database is unavailable while agent ${normalizeAgentId(deletedAgentId)} is deleted.`); } function readAgentDeletionPathFenceRows(database, purpose = "runtime", agentId) { if (purpose === "maintenance") assertAgentDeletionJournalAvailable(database); try { if (!tableExists(database, "agent_deletion_journal")) return { known: false, rows: [] }; const db = getNodeSqliteKysely(database); let known = true; let query = db.selectFrom("agent_deletion_journal").select([ ...journalFenceFields, "cleanup_completed", "created_at", "delete_files" ]); if (agentId !== void 0) query = query.where("agent_id", "=", normalizeAgentId(agentId)); const rows = executeSqliteQuerySync(database, query).rows.map((row) => { let databasePaths = []; let cleanupPaths = []; try { databasePaths = parseAgentDeletionDatabasePaths(row.database_paths_json); cleanupPaths = parseCleanupPaths(row.cleanup_paths_json); } catch (error) { if (purpose === "maintenance") throw error; known = false; } return Object.assign(row, { databasePaths, cleanupPaths }); }); return { known, rows }; } catch (error) { if (purpose === "maintenance" || isSqliteCorruptionError(error)) throw error; return { known: false, rows: [] }; } } function prepareAgentDeletionPathFence(claim, options = {}, purpose = claim.fenceAgentId ? "maintenance" : "runtime") { const { rows, known } = runOpenClawStateWriteTransaction((database) => readAgentDeletionPathFenceRows(database.db, purpose), { ...options, initializationAgentPaths: [...options.initializationAgentPaths ?? [], claim.path] }); const env = options.env ?? process.env; return { journal: known ? "known" : "unknown", purpose, claimAgentId: normalizeAgentId(claim.agentId), claimPath: path.resolve(claim.path), ...claim.fenceAgentId ? { fenceAgentId: normalizeAgentId(claim.fenceAgentId) } : {}, targetPaths: resolveSqliteDatabaseFilePaths(claim.path).map((filePath) => normalizeAgentDirRegistryPath(filePath, env)), entries: rows.map(({ databasePaths, cleanupPaths, ...row }) => ({ row: { ...row, cleanup_completed: row.cleanup_completed === 1 ? 1 : 0 }, fences: [...[ row.agent_dir, row.workspace_dir, row.sessions_dir, ...databasePaths ].map((pathname) => ({ path: pathname, canonicalPath: normalizeAgentDirRegistryPath(pathname, env) })), ...cleanupPaths.map((cleanupPath) => ({ path: cleanupPath.path, canonicalPath: normalizeAgentDirRegistryPath(cleanupPath.canonicalPath, env) }))] })) }; } function journalFenceFingerprint(rows) { return rows.map((row) => [...journalFenceFields.map((field) => row[field]), row.cleanup_completed].join("\0")).toSorted().join("\n"); } /** Refuse database claims beneath paths still owned by an unfinished deletion. */ function assertAgentDeletionPathFence(state, snapshot) { const database = state.db; const { rows: journalRows, known } = readAgentDeletionPathFenceRows(database, snapshot.purpose); if (!known && journalRows.length === 0) return; if (journalFenceFingerprint(snapshot.entries.map((entry) => entry.row)) !== journalFenceFingerprint(journalRows)) throw new Error("Agent deletion journal changed while preparing a database claim."); const cleanupAgentId = (snapshot.fenceAgentId ? void 0 : getAgentDeletionDatabaseCleanup({ agentId: snapshot.claimAgentId, path: snapshot.claimPath, statePath: state.path }))?.assertJournal(state.path, journalRows.map((row) => ({ agentId: row.agent_id, operationId: row.operation_id, cleanupCompleted: row.cleanup_completed === 1 }))); const creationAgentId = snapshot.fenceAgentId ? void 0 : resolveAgentCreationClaimAgentId(snapshot.claimAgentId, state.path); for (const row of journalRows) { if (snapshot.fenceAgentId && snapshot.fenceAgentId !== row.agent_id) continue; if (row.agent_id === cleanupAgentId) continue; if (row.cleanup_completed === 1 && row.agent_id === creationAgentId) continue; assertAgentDeletionIdentityClaimAllowed(snapshot.claimAgentId, row.agent_id); if (row.cleanup_completed === 1) continue; const entry = snapshot.entries.find((candidate) => journalFenceFields.every((field) => candidate.row[field] === row[field])); if (!entry) throw new Error("Agent deletion journal changed while preparing a database claim."); for (const fence of entry.fences) { const blockedPath = snapshot.targetPaths.find((targetPath) => targetPath === fence.canonicalPath || isPathInside(fence.canonicalPath, targetPath)); if (blockedPath) throw new Error(`OpenClaw agent database ${blockedPath} is unavailable while agent ${row.agent_id} deletion owns ${fence.path}.`); } } } function fromRow(row) { return { agentId: row.agent_id, operationId: row.operation_id, agentDir: row.agent_dir, workspaceDir: row.workspace_dir, sessionsDir: row.sessions_dir, databasePaths: row.databasePaths ?? parseAgentDeletionDatabasePaths(row.database_paths_json), cleanupPaths: row.cleanupPaths ?? parseCleanupPaths(row.cleanup_paths_json), createdAt: row.created_at, cleanupCompleted: row.cleanup_completed === 1, deleteFiles: row.delete_files === 1 }; } function parseCleanupPaths(value) { const parsed = JSON.parse(value); if (!Array.isArray(parsed) || !parsed.every((entry) => isRecord(entry) && typeof entry.path === "string" && typeof entry.canonicalPath === "string" && typeof entry.parentPath === "string" && (entry.kind === "target" || entry.kind === "symlink") && (entry.dev === null || typeof entry.dev === "number") && (entry.ino === null || typeof entry.ino === "number") && typeof entry.coversDescendants === "boolean" && typeof entry.done === "boolean" && (entry.note === void 0 || typeof entry.note === "string") && Array.isArray(entry.sourcePaths) && entry.sourcePaths.every((sourcePath) => typeof sourcePath === "string"))) throw new Error("Invalid agent deletion cleanup path journal."); return parsed; } /** Read the journal through an already validated shared-state connection. */ function readAgentDeletionJournalInDatabase(database, agentId, purpose = "maintenance") { const row = readAgentDeletionPathFenceRows(database.db, purpose, agentId).rows[0]; return row ? fromRow(row) : void 0; } function readAgentDeletionJournal(agentId, options = {}, purpose = "maintenance") { return withExistingOpenClawStateDatabaseCurrentReadOnly((database) => readAgentDeletionJournalInDatabase(database, agentId, purpose), purpose === "runtime" && !isMainThread ? { ...options, allowNativeRead: true } : options); } function beginAgentDeletionJournalInDatabase(database, entry, preserveDeleteFiles = false) { const normalized = { ...entry, agentId: normalizeAgentId(entry.agentId), databasePaths: [...new Set((entry.databasePaths ?? []).map((entryPath) => path.resolve(entryPath)))], cleanupPaths: entry.cleanupPaths ?? [] }; sessionChanges.emit({ all: true, scope: "stores" }, database.db); assertAgentDeletionJournalAvailable(database.db); const db = getNodeSqliteKysely(database.db); const existing = executeSqliteQueryTakeFirstSync(database.db, db.selectFrom("agent_deletion_journal").selectAll().where("agent_id", "=", normalized.agentId)); const registeredDatabasePaths = executeSqliteQuerySync(database.db, db.selectFrom("agent_databases").select("path").where("agent_id", "=", normalized.agentId)).rows.flatMap((row) => resolveSqliteDatabaseFilePaths(resolveOpenClawRegisteredAgentDatabasePath(database.path, row.path))); const previous = existing && fromRow(existing); if (preserveDeleteFiles && previous) normalized.deleteFiles = previous.deleteFiles; const databasePaths = [...new Set([ ...previous?.databasePaths ?? [], ...normalized.databasePaths, ...registeredDatabasePaths ].map((entryPath) => path.resolve(entryPath)))]; const cleanupPaths = previous?.cleanupPaths ?? normalized.cleanupPaths; if (previous) { executeSqliteQuerySync(database.db, db.updateTable("agent_deletion_journal").set({ operation_id: normalized.operationId, database_paths_json: JSON.stringify(databasePaths), cleanup_paths_json: JSON.stringify(cleanupPaths), cleanup_completed: 0, delete_files: normalized.deleteFiles ? 1 : 0 }).where("agent_id", "=", normalized.agentId)); return { previousEntry: previous, entry: { ...previous, operationId: normalized.operationId, databasePaths, cleanupPaths, cleanupCompleted: false, deleteFiles: normalized.deleteFiles } }; } const createdAt = Date.now(); executeSqliteQuerySync(database.db, db.insertInto("agent_deletion_journal").values({ agent_id: normalized.agentId, operation_id: normalized.operationId, agent_dir: normalized.agentDir, workspace_dir: normalized.workspaceDir, sessions_dir: normalized.sessionsDir, database_paths_json: JSON.stringify(databasePaths), cleanup_paths_json: JSON.stringify(cleanupPaths), created_at: createdAt, cleanup_completed: 0, delete_files: normalized.deleteFiles ? 1 : 0 })); return { entry: { ...normalized, databasePaths, cleanupPaths, createdAt, cleanupCompleted: false } }; } function updateAgentDeletionJournalPathsInDatabase(database, agentId, operationId, column, paths) { assertAgentDeletionJournalAvailable(database.db); const db = getNodeSqliteKysely(database.db); const result = executeSqliteQuerySync(database.db, db.updateTable("agent_deletion_journal").set({ [column]: JSON.stringify(paths) }).where("agent_id", "=", agentId).where("operation_id", "=", operationId).where("cleanup_completed", "=", 0)); const updated = Number(result.numAffectedRows ?? 0) > 0; if (updated) sessionChanges.emit({ all: true, scope: "stores" }, database.db); return updated; } /** Revoke an attempt while retaining its pending cleanup fence for a later owner. */ function handoffAgentDeletionJournalInDatabase(database, agentId, operationId, retryOperationId) { assertAgentDeletionJournalAvailable(database.db); const db = getNodeSqliteKysely(database.db); const handedOff = executeSqliteQuerySync(database.db, db.updateTable("agent_deletion_journal").set({ operation_id: retryOperationId }).where("agent_id", "=", normalizeAgentId(agentId)).where("operation_id", "=", operationId).where("cleanup_completed", "=", 0)).numAffectedRows === 1n; if (handedOff) sessionChanges.emit({ all: true, scope: "stores" }, database.db); return handedOff; } /** Complete a deletion journal inside a caller-owned shared-state transaction. */ function completeAgentDeletionJournalInDatabase(database, agentId, operationId) { const id = normalizeAgentId(agentId); assertAgentDeletionJournalAvailable(database.db); const db = getNodeSqliteKysely(database.db); const result = executeSqliteQuerySync(database.db, db.updateTable("agent_deletion_journal").set({ cleanup_completed: 1 }).where("agent_id", "=", id).where("operation_id", "=", operationId)); const completed = Number(result.numAffectedRows ?? 0) > 0; if (completed) { resolveAgentDeletionRecoveryHolds(database, id, readAgentDeletionJournalInDatabase(database, id)?.databasePaths ?? []); deleteAgentProvenanceForAgent(database.db, id); sessionChanges.emit({ all: true, scope: "stores" }, database.db); } return completed; } /** The caller owns journal authority, transaction admission, and committed publication. */ function deleteAgentDeletionJournalInDatabase(database, agentId, operationId, completedOnly) { const id = normalizeAgentId(agentId); assertAgentDeletionJournalAvailable(database.db); const query = getNodeSqliteKysely(database.db).deleteFrom("agent_deletion_journal").where("agent_id", "=", id).where("operation_id", "=", operationId); const result = executeSqliteQuerySync(database.db, completedOnly ? query.where("cleanup_completed", "=", 1) : query); const removed = Number(result.numAffectedRows ?? 0) > 0; if (removed) sessionChanges.emit({ all: true, scope: "stores" }, database.db); return removed; } /** Read current journal facts through the captured physical shared-state worker. */ async function readAgentDeletionJournalAsync(agentId, options = {}) { const context = captureOpenClawStateWorkerContext({ ...options, path: options.database?.path ?? options.path }); const { runOpenClawStateWorkerOperation } = await import("./openclaw-state-worker-store-x5r3Lkjl.mjs"); const result = await runOpenClawStateWorkerOperation(context, (scope) => scope.execute({ type: "agentDeletion.read", input: { agentId: normalizeAgentId(agentId) } }), { existingOnly: true }); context.admission.assertCurrent(); return result; } /** Administrative safety check; the caller retains its own deletion/monitor authority. */ async function assertNoAgentDatabaseLeasesAsync(agentId, options = {}) { const context = captureOpenClawStateWorkerContext({ ...options, path: options.database?.path ?? options.path }); const { executeOpenClawStateWorker } = await import("./openclaw-state-worker-store-x5r3Lkjl.mjs"); await executeOpenClawStateWorker(context, { type: "agentDeletion.assertNoDatabaseLeasesUnowned", input: { agentId: normalizeAgentId(agentId) } }); } //#endregion export { registerAgentDeletionExecutionCleanup as C, registerAgentDeletionDatabaseCleanup as S, withAgentDeletionWorkerDatabaseCleanup as T, assertAgentDeletionCleanupAliases as _, deleteAgentDeletionJournalInDatabase as a, createAgentDeletionDatabaseCleanup as b, prepareAgentDeletionPathFence as c, readAgentDeletionJournalInDatabase as d, updateAgentDeletionJournalPathsInDatabase as f, resolveAgentDeletionRecoveryHolds as g, recordAgentDeletionRecoveryHolds as h, completeAgentDeletionJournalInDatabase as i, readAgentDeletionJournal as l, reconstructAgentDeletionJournal as m, assertNoAgentDatabaseLeasesAsync as n, handoffAgentDeletionJournalInDatabase as o, assertAgentDeletionRecoveryAllowsMutation as p, beginAgentDeletionJournalInDatabase as r, parseCleanupPaths as s, assertAgentDeletionPathFence as t, readAgentDeletionJournalAsync as u, assertAgentDeletionDatabaseCleanupAccess as v, releaseAgentDeletionDatabaseCleanup as w, getAgentDeletionDatabaseCleanup as x, assertAgentDeletionExecutionCleanupAccess as y };