UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

295 lines (294 loc) • 15.2 kB
import { a as writeRuntimeJson, r as defaultRuntime } from "./runtime-BRg2NubB.mjs"; import { r as isPathInside } from "./path-guards-CH7noY0Z.mjs"; import { t as formatErrorMessage } from "./errors-BvOkmQM5.mjs"; import { t as formatCliCommand } from "./command-format-DwEdrDKL.mjs"; import { r as formatCliJsonFailure } from "./failure-output-Cq0Ej4cE.mjs"; import { r as normalizeAgentIdStrict } from "./agent-id-D1-Q6vR4.mjs"; import "./session-key-DY3MOX4N.mjs"; import { i as listAgentIds, l as tryResolveSoleAgentId, n as listAgentEntries } from "./agent-roster-DH6r_8f4.mjs"; import { d as resolveSessionTranscriptsDirForAgent } from "./paths-Bonddxfk.mjs"; import { a as resolveAgentDir, d as resolveAgentWorkspaceDir, k as normalizeAgentDirRegistryPath } from "./agent-scope-config-BEk6-ThS.mjs"; import { u as readAgentDeletionJournalAsync } from "./agent-deletion-journal-DkfGlzu8.mjs"; import "./agent-scope-bJOokm5r.mjs"; import { r as replaceConfigFile } from "./mutate-C5g37HdW.mjs"; import "./config-VnHbY_QV.mjs"; import { i as GATEWAY_CLIENT_NAMES, r as GATEWAY_CLIENT_MODES } from "./client-info-BgcoFakQ.mjs"; import "./message-channel-AvA3r_Xp.mjs"; import { c as resolveSharedAuthStoreOwnership, u as resolveSharedAuthStorePath } from "./path-resolve-caruxX2d.mjs"; import { v as resolveAuthProfileDatabasePath } from "./sqlite-Bk_bX_Ka.mjs"; import { l as prepareLegacyWorkspaceStateReset, u as removeLegacyWorkspaceStateForReset } from "./workspace-legacy-state-BsjDP5O1.mjs"; import { i as prepareWorkspaceStateDeletion, n as deleteWorkspaceState } from "./workspace-state-store-F6TjGJpy.mjs"; import { c as AgentDeletionAuthorityRollbackError, l as AgentDeletionCommitUncertainError, o as withAgentDeletion, s as claimCompletedAgentDeletion } from "./agent-lifecycle-registry-CxGB4gfC.mjs"; import { p as withAgentExecApprovalsRemoved } from "./exec-approvals-store-B-TRUVtg.mjs"; import "./exec-approvals-BkzkbwrB.mjs"; import { f as isImplicitLocalGatewayTarget, r as callGateway, t as buildGatewayConnectionDetails } from "./call-D6fWfxgB.mjs"; import "./sessions-CRUJeu5H.mjs"; import { t as purgeAgentSessionStoreEntries } from "./cleanup-service-C2aUjKxo.mjs"; import { i as pruneAgentConfig, r as findAgentEntryIndex } from "./agents.config-BnYY-aiX.mjs"; import { r as moveToTrashResult } from "./cleanup-utils-JqeW-k9O.mjs"; import { r as isTerminalInteractive } from "./terminal-interactivity-D92bdO16.mjs"; import { t as createClackPrompter } from "./clack-prompter-D2lt2mof.mjs"; import { r as logConfigUpdated } from "./logging-CmVv4Afn.mjs"; import { t as withLocalAgentCronJobsRemoved } from "./local-service-BS_aHk8_.mjs"; import { i as isSharedAuthStoreOwner, n as formatSharedAuthStoreOwnerDeleteError, r as isInheritedAuthStoreOwner, t as findOverlappingWorkspaceAgentIds } from "./agent-delete-safety-DLY4aeLQ.mjs"; import { t as AgentSharedStoreOwnerError } from "./agent-delete-session-store-safety-ji1TpSo4.mjs"; import { i as prepareAgentDeleteDatabases, o as readAgentDeleteDatabaseRegistry, r as isPathOwnedBySurvivingAgent, s as resolveSurvivingDatabaseFilePaths, t as assertAgentSessionStoreDeletionSafe } from "./agent-delete-databases-D5IIwLXW.mjs"; import { r as requireValidConfigForWrite } from "./config-validation-Drf1V2Dj.mjs"; import { t as resolveGatewayMutationFallback } from "./call-mutation-fallback-Co-jtxk-.mjs"; //#region src/commands/agents.commands.delete.ts async function maybeDeleteAgentThroughGateway(params) { const { url } = buildGatewayConnectionDetails({ config: params.config }); const localTarget = await isImplicitLocalGatewayTarget({ config: params.config }); try { return { kind: "deleted", result: await callGateway({ config: params.config, expectUrl: url, method: "agents.delete", params: { agentId: params.agentId, deleteFiles: true }, mode: GATEWAY_CLIENT_MODES.CLI, clientName: GATEWAY_CLIENT_NAMES.CLI, requiredMethods: ["agents.delete"] }) }; } catch (error) { const fallback = resolveGatewayMutationFallback({ error, localTarget }); if (fallback === "unreachable") return { kind: "fallback-unreachable" }; if (fallback === "credentials-required") return { kind: "fallback-credentials-required" }; if (fallback === "non-local") throw new Error(`${formatErrorMessage(error)}\nLocal agent state was left unchanged. Restore the Gateway connection and credentials, or run this command on the Gateway host (the far end of any SSH tunnel).`, { cause: error }); throw error; } } async function agentsDeleteCommand(opts, runtime = defaultRuntime) { const failAgentsDelete = (message) => { if (opts.json) { writeRuntimeJson(runtime, formatCliJsonFailure(message)); runtime.exit(1, { resetStream: process.stderr }); } else { runtime.error(message); runtime.exit(1); } }; const writeSnapshot = await requireValidConfigForWrite(runtime); if (!writeSnapshot) return; const cfg = writeSnapshot.snapshot.sourceConfig; const input = opts.id?.trim(); if (!input) { failAgentsDelete(`Agent id is required. Run ${formatCliCommand("openclaw agents list")} to choose one.`); return; } const normalized = normalizeAgentIdStrict(input); if (!normalized.ok) { failAgentsDelete(`Agent "${input}" not found. Run ${formatCliCommand("openclaw agents list")} to see configured agents.`); return; } const agentId = normalized.value; if (!opts.json && agentId !== input) runtime.log(`Normalized agent id to "${agentId}".`); const configured = findAgentEntryIndex(listAgentEntries(cfg), agentId) >= 0; let existingJournal = configured ? void 0 : await readAgentDeletionJournalAsync(agentId); if (!configured && (!existingJournal || existingJournal.cleanupCompleted)) { failAgentsDelete(`Agent "${agentId}" not found. Run ${formatCliCommand("openclaw agents list")} to see configured agents.`); return; } const configuredAgentDir = configured ? resolveAgentDir(cfg, agentId) : void 0; const safetyAgentDir = existingJournal?.agentDir ?? configuredAgentDir; if (!safetyAgentDir) throw new Error(`Agent "${agentId}" deletion has no state directory.`); try { await assertAgentSessionStoreDeletionSafe(cfg, agentId); } catch (error) { if (!(error instanceof AgentSharedStoreOwnerError)) throw error; failAgentsDelete(error.message); return; } const sharedAuthOwnership = resolveSharedAuthStoreOwnership(); if (isSharedAuthStoreOwner({ ownership: sharedAuthOwnership, agentAuthDbPath: resolveAuthProfileDatabasePath(safetyAgentDir), sharedAuthDbPath: resolveSharedAuthStorePath() })) { failAgentsDelete(formatSharedAuthStoreOwnerDeleteError(agentId)); return; } if (configured && agentId === tryResolveSoleAgentId(cfg)) { failAgentsDelete(`Agent "${agentId}" is the only configured agent and cannot be deleted.`); return; } if (isInheritedAuthStoreOwner(cfg, agentId)) { failAgentsDelete(`Agent "${agentId}" owns inherited credentials through agents.defaults.authInheritance.agentId and cannot be deleted. Relocate those credentials, then re-point or remove that binding before retrying.`); return; } if (configured) { existingJournal = await readAgentDeletionJournalAsync(agentId); if (existingJournal?.cleanupCompleted) existingJournal = void 0; } const agentDir = existingJournal?.agentDir ?? configuredAgentDir; if (!agentDir) throw new Error(`Agent "${agentId}" deletion has no state directory.`); if (!opts.force) { if (!isTerminalInteractive()) { failAgentsDelete("Non-interactive session. Re-run with --force."); return; } if (!await createClackPrompter().confirm({ message: `Delete agent "${agentId}" and prune workspace/state?`, initialValue: false })) { runtime.log("Cancelled."); return; } } const workspaceDir = existingJournal?.workspaceDir ?? resolveAgentWorkspaceDir(cfg, agentId); const sessionsDir = existingJournal?.sessionsDir ?? resolveSessionTranscriptsDirForAgent(agentId); const result = configured ? pruneAgentConfig(cfg, agentId) : { config: cfg, removedBindings: 0, removedAllow: 0, clearedOwnerRefs: [] }; const reportDeletion = (cleanup, workspaceRetained, workspaceSharedWith, extra) => { if (opts.json) writeRuntimeJson(runtime, { agentId, workspace: workspaceDir, workspaceRetained: workspaceRetained || void 0, workspaceRetainedReason: workspaceRetained ? "shared" : void 0, workspaceSharedWith: workspaceRetained ? workspaceSharedWith : void 0, agentDir, sessionsDir, removedBindings: cleanup.removedBindings, removedAllow: result.removedAllow, clearedOwnerRefs: result.clearedOwnerRefs.length > 0 ? result.clearedOwnerRefs : void 0, removed: cleanup.removed, failed: cleanup.failed, ...cleanup.purgeFailed ? { purgeFailed: true } : {}, ...extra }); else { runtime.log(`Deleted agent: ${agentId}`); if (result.clearedOwnerRefs.length > 0) runtime.log(`Cleared owner references: ${result.clearedOwnerRefs.join(", ")}`); if (cleanup.purgeFailed === true) runtime.error(`Warning: session-store purge failed for deleted agent "${agentId}"; source data was retained. Retry deletion after resolving the storage error.`); for (const failure of cleanup.failed ?? []) runtime.error(`Warning: path could not be moved to Trash: ${failure.reason}; remove it manually at ${failure.path}`); } }; const gatewayAttempt = await maybeDeleteAgentThroughGateway({ config: cfg, agentId }); if (gatewayAttempt.kind === "deleted") { const workspaceSharedWith = opts.json ? findOverlappingWorkspaceAgentIds(cfg, agentId, workspaceDir) : []; reportDeletion(gatewayAttempt.result, workspaceSharedWith.length > 0, workspaceSharedWith, { transport: "gateway" }); return; } return await withAgentDeletion(agentId, async (begin) => { existingJournal = await readAgentDeletionJournalAsync(agentId); if (configured && existingJournal?.cleanupCompleted) { if (!await claimCompletedAgentDeletion(agentId, existingJournal.operationId)) throw new Error(`Agent "${agentId}" deletion tombstone changed before fresh deletion.`); existingJournal = void 0; } if (!configured && (!existingJournal || existingJournal.cleanupCompleted)) throw new Error(`Agent "${agentId}" deletion already completed.`); await assertAgentSessionStoreDeletionSafe(cfg, agentId); const workspaceSharedWith = findOverlappingWorkspaceAgentIds(cfg, agentId, workspaceDir); const deleteFiles = existingJournal?.deleteFiles ?? true; const deletion = await begin(existingJournal ?? { agentId, agentDir, workspaceDir, sessionsDir, deleteFiles }); let rosterCommitted = !configured; try { await prepareAgentDeleteDatabases(cfg, agentId, agentDir, {}, deletion); await deletion.assertCurrentAsync(); const commitRoster = async () => await withAgentExecApprovalsRemoved(agentId, async () => { await deletion.assertCurrentAsync(); if (configured) { await replaceConfigFile({ ...writeSnapshot, sourceConfig: result.config, writeOptions: { ...writeSnapshot.writeOptions, allowedAgentRosterRemovals: [agentId], beforeCommit: async () => { await writeSnapshot.writeOptions.beforeCommit?.(); await deletion.assertCurrentAsync(); }, assertConfigPathForWrite: () => { writeSnapshot.writeOptions.assertConfigPathForWrite?.(); deletion.assertCurrentFinal(); }, ...opts.json ? { skipOutputLogs: true } : {} } }); rosterCommitted = true; if (!opts.json) logConfigUpdated(runtime); } }, deletion); if (gatewayAttempt.kind === "fallback-unreachable") await withLocalAgentCronJobsRemoved(agentId, () => cfg, commitRoster); else await commitRoster(); await deletion.assertCurrentAsync(); } catch (error) { if (!existingJournal && !rosterCommitted && !(error instanceof AgentDeletionAuthorityRollbackError) && !(error instanceof AgentDeletionCommitUncertainError)) await deletion.rollback(); throw error; } const purgeFailed = await purgeAgentSessionStoreEntries(cfg, agentId, { runDatabaseCleanup: deletion.runDatabaseCleanup }); await deletion.assertCurrentAsync(); for (const survivingAgentId of listAgentIds(result.config)) resolveAgentDir(result.config, survivingAgentId); const survivingDatabaseFilePaths = resolveSurvivingDatabaseFilePaths(await readAgentDeleteDatabaseRegistry(), agentId); const sharedWithSurvivor = (pathname) => isPathOwnedBySurvivingAgent(result.config, agentId, pathname, survivingDatabaseFilePaths); const workspaceRetained = sharedWithSurvivor(workspaceDir); const quietRuntime = opts.json ? { ...runtime, log: () => {} } : runtime; let workspaceCleanupError; const removed = []; const failed = []; const removePath = async (pathname) => { const outcome = await moveToTrashResult(pathname, quietRuntime, deletion.assertCurrentFinal); await deletion.assertCurrentAsync(); if ("removed" in outcome) removed.push(outcome.removed); else failed.push(outcome.failed); return outcome; }; if (deleteFiles && !purgeFailed && workspaceRetained) quietRuntime.log(`Skipped workspace removal (shared with other agents${workspaceSharedWith.length ? `: ${workspaceSharedWith.join(", ")}` : ""}): ${workspaceDir}`); else if (deleteFiles && !purgeFailed) { const legacyPlan = prepareLegacyWorkspaceStateReset(workspaceDir); const statePlan = prepareWorkspaceStateDeletion(workspaceDir); if ("removed" in await removePath(workspaceDir)) try { const legacyCleanup = await removeLegacyWorkspaceStateForReset(legacyPlan, { assertCurrent: deletion.assertCurrentFinal }); for (const warning of legacyCleanup.warnings) quietRuntime.log(warning); await deletion.assertCurrentAsync(); await deleteWorkspaceState(statePlan, { deletion }); } catch (error) { workspaceCleanupError = error instanceof Error ? error : new Error(String(error)); } } if (deleteFiles && !purgeFailed) { const canonicalAgentDir = normalizeAgentDirRegistryPath(agentDir); const databasePaths = deletion.entry.databasePaths.filter((pathname) => { const canonicalPath = normalizeAgentDirRegistryPath(pathname); return !isPathInside(canonicalAgentDir, canonicalPath) && !sharedWithSurvivor(pathname); }); for (const directory of [agentDir, sessionsDir]) if (!sharedWithSurvivor(directory)) await removePath(directory); for (const databasePath of databasePaths) await removePath(databasePath); } if (workspaceCleanupError) throw workspaceCleanupError; await deletion.assertCurrentAsync(); if (failed.length === 0 && !purgeFailed) await deletion.finish({ unregisterDatabases: deleteFiles }); reportDeletion({ removedBindings: result.removedBindings, removed, failed, ...purgeFailed ? { purgeFailed: true } : {} }, workspaceRetained, workspaceSharedWith, gatewayAttempt.kind === "fallback-credentials-required" ? { cronCleanupSkipped: true } : void 0); if (gatewayAttempt.kind === "fallback-credentials-required") runtime.error(`Warning: cron cleanup was skipped for deleted agent "${agentId}" because the Gateway could not be authenticated; scheduled jobs may remain.`); }); } //#endregion export { agentsDeleteCommand };