openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
285 lines (284 loc) • 14 kB
JavaScript
import { a as writeRuntimeJson, r as defaultRuntime } from "./runtime-CF2WjnNZ.js";
import { r as isPathInside } from "./path-guards-Cp-mGr3-.js";
import { r as normalizeAgentIdStrict } from "./agent-id-CeT3w4ap.js";
import { t as formatCliCommand } from "./command-format-C7YfyMTd.js";
import { O as tryResolveSoleAgentId, i as listAgentEntries, m as resolveAgentWorkspaceDir, o as listAgentIds, u as resolveAgentDir } from "./agent-scope-config-DcbEhP0R.js";
import "./session-key-BnWWjqNc.js";
import { n as normalizeAgentDirRegistryPath } from "./agent-dir-registry-Dkh28921.js";
import { l as resolveSessionTranscriptsDirForAgent } from "./paths-CXdaYWF_.js";
import "./agent-scope-DbtJyKUL.js";
import { i as GATEWAY_CLIENT_NAMES, r as GATEWAY_CLIENT_MODES } from "./client-info-B1bPgeKr.js";
import { r as replaceConfigFile } from "./mutate-ZNN4iFCn.js";
import "./config-Cs0XXL3x.js";
import { o as callGateway, p as isGatewayCredentialsRequiredError } from "./call-BWR5pPgw.js";
import { r as isGatewayTransportError } from "./transport-error-D_LRKgla.js";
import { r as isTerminalInteractive } from "./terminal-interactivity-DXUXAq5U.js";
import "./message-channel-BQrhwUEA.js";
import { r as formatCliJsonFailure } from "./failure-output-Dc5ce9_F.js";
import { o as resolveSharedAuthStoreOwnership, s as resolveSharedAuthStorePath } from "./path-resolve-oRkRBkQd.js";
import { _ as readAgentDeletionJournal } from "./openclaw-agent-db-lease-Djvd6LWN.js";
import { p as unregisterOpenClawAgentDatabases } from "./openclaw-agent-db-maintenance-wTIy-jt-.js";
import { h as resolveAuthProfileDatabasePath } from "./sqlite-MN_7y26V.js";
import { b as prepareLegacyWorkspaceStateReset, i as deleteWorkspaceState, s as prepareWorkspaceStateDeletion, x as removeLegacyWorkspaceStateForReset } from "./workspace-state-store-DdpcP8H6.js";
import "./sessions-9nxpeTwt.js";
import { t as purgeAgentSessionStoreEntries } from "./cleanup-service-DoQBUGSQ.js";
import "./exec-approvals-BSZ-fPIY.js";
import { a as claimCompletedAgentDeletion, r as beginAgentDeletion } from "./agent-lifecycle-registry-WgCc3vx1.js";
import { p as withAgentExecApprovalsRemoved } from "./exec-approvals-store-DBR0neS0.js";
import { a as moveToTrashResult } from "./cleanup-utils-BDhDB9DZ.js";
import { t as createClackPrompter } from "./clack-prompter-BSpWH_ak.js";
import { r as logConfigUpdated } from "./logging-Idqd9ylL.js";
import { t as withLocalAgentCronJobsRemoved } from "./local-service--BKmgr3i.js";
import { i as pruneAgentConfig, r as findAgentEntryIndex } from "./agents.config-CFXmxe4B.js";
import { i as isSharedAuthStoreOwner, n as formatSharedAuthStoreOwnerDeleteError, r as isInheritedAuthStoreOwner, t as findOverlappingWorkspaceAgentIds } from "./agent-delete-safety-CIoplT3h.js";
import { i as resolveSurvivingDatabaseFilePaths, n as prepareAgentDeleteDatabases, r as readAgentDeleteDatabaseRegistry, t as isPathOwnedBySurvivingAgent } from "./agent-delete-databases-Kbifmfvu.js";
import { n as requireValidConfigFileSnapshot } from "./config-validation-C1Wo5r0Q.js";
//#region src/commands/agents.command-shared.ts
/** Wrap a runtime so helper setup work stays silent in JSON output paths. */
function createQuietRuntime(runtime) {
return {
...runtime,
log: () => {}
};
}
//#endregion
//#region src/commands/agents.commands.delete.ts
function failAgentsDelete(opts, runtime, message) {
if (opts.json) {
writeRuntimeJson(runtime, formatCliJsonFailure(message));
runtime.exit(1, { resetStream: process.stderr });
} else {
runtime.error(message);
runtime.exit(1);
}
}
function logClearedOwnerRefs(runtime, clearedOwnerRefs) {
if (clearedOwnerRefs.length > 0) runtime.log(`Cleared owner references: ${clearedOwnerRefs.join(", ")}`);
}
function logSessionPurgeWarning(runtime, agentId, purgeFailed) {
if (purgeFailed) runtime.error(`Warning: session-store purge failed for deleted agent "${agentId}"; stale shared-store rows may remain.`);
}
async function maybeDeleteAgentThroughGateway(params) {
try {
return {
kind: "deleted",
result: await callGateway({
method: "agents.delete",
params: {
agentId: params.agentId,
deleteFiles: params.deleteFiles
},
mode: GATEWAY_CLIENT_MODES.CLI,
clientName: GATEWAY_CLIENT_NAMES.CLI,
requiredMethods: ["agents.delete"]
})
};
} catch (error) {
if (isGatewayTransportError(error) && error.kind === "closed" && error.code === void 0) return { kind: "fallback-unreachable" };
if (isGatewayCredentialsRequiredError(error)) return { kind: "fallback-credentials-required" };
throw error;
}
}
/** Delete an agent, pruning config plus workspace/session state when it is safe to do so. */
async function agentsDeleteCommand(opts, runtime = defaultRuntime) {
const configSnapshot = await requireValidConfigFileSnapshot(runtime);
if (!configSnapshot) return;
const cfg = configSnapshot.sourceConfig ?? configSnapshot.config;
const baseHash = configSnapshot.hash;
const input = opts.id?.trim();
if (!input) {
failAgentsDelete(opts, runtime, `Agent id is required. Run ${formatCliCommand("openclaw agents list")} to choose one.`);
return;
}
const normalized = normalizeAgentIdStrict(input);
if (!normalized.ok) {
failAgentsDelete(opts, runtime, `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 : readAgentDeletionJournal(agentId);
if (!configured && (!existingJournal || existingJournal.cleanupCompleted)) {
failAgentsDelete(opts, runtime, `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.`);
const sharedAuthOwnership = resolveSharedAuthStoreOwnership();
if (isSharedAuthStoreOwner({
ownership: sharedAuthOwnership,
agentAuthDbPath: resolveAuthProfileDatabasePath(safetyAgentDir),
sharedAuthDbPath: resolveSharedAuthStorePath()
})) {
failAgentsDelete(opts, runtime, formatSharedAuthStoreOwnerDeleteError(agentId));
return;
}
if (configured && agentId === tryResolveSoleAgentId(cfg)) {
failAgentsDelete(opts, runtime, `Agent "${agentId}" is the only configured agent and cannot be deleted.`);
return;
}
if (isInheritedAuthStoreOwner(cfg, agentId)) {
failAgentsDelete(opts, runtime, `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 = readAgentDeletionJournal(agentId);
if (existingJournal?.cleanupCompleted) {
if (!claimCompletedAgentDeletion(agentId, existingJournal.operationId)) throw new Error(`Agent "${agentId}" deletion tombstone changed before fresh deletion.`);
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(opts, runtime, "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 gatewayAttempt = await maybeDeleteAgentThroughGateway({
agentId,
deleteFiles: true
});
if (gatewayAttempt.kind === "deleted") {
const gatewayResult = gatewayAttempt.result;
if (opts.json) {
const workspaceSharedWith = findOverlappingWorkspaceAgentIds(cfg, agentId, workspaceDir);
const workspaceRetained = workspaceSharedWith.length > 0;
writeRuntimeJson(runtime, {
agentId,
workspace: workspaceDir,
workspaceRetained: workspaceRetained || void 0,
workspaceRetainedReason: workspaceRetained ? "shared" : void 0,
workspaceSharedWith: workspaceRetained ? workspaceSharedWith : void 0,
agentDir,
sessionsDir,
removedBindings: gatewayResult.removedBindings,
removedAllow: result.removedAllow,
clearedOwnerRefs: result.clearedOwnerRefs.length > 0 ? result.clearedOwnerRefs : void 0,
removed: gatewayResult.removed,
failed: gatewayResult.failed,
...gatewayResult.purgeFailed ? { purgeFailed: true } : {},
transport: "gateway"
});
} else {
runtime.log(`Deleted agent: ${agentId}`);
logClearedOwnerRefs(runtime, result.clearedOwnerRefs);
logSessionPurgeWarning(runtime, agentId, gatewayResult.purgeFailed === true);
for (const failure of gatewayResult.failed ?? []) runtime.error(`Warning: path could not be moved to Trash: ${failure.reason}; remove it manually at ${failure.path}`);
}
return;
}
const workspaceSharedWith = findOverlappingWorkspaceAgentIds(cfg, agentId, workspaceDir);
const deleteFiles = existingJournal?.deleteFiles ?? true;
const deletion = beginAgentDeletion(existingJournal ?? {
agentId,
agentDir,
workspaceDir,
sessionsDir,
deleteFiles
});
try {
prepareAgentDeleteDatabases(cfg, agentId, agentDir);
const commitRoster = async () => await withAgentExecApprovalsRemoved(agentId, async () => {
if (configured) {
await replaceConfigFile({
nextConfig: result.config,
...baseHash !== void 0 ? { baseHash } : {},
writeOptions: {
allowedAgentRosterRemovals: [agentId],
...opts.json ? { skipOutputLogs: true } : {}
}
});
if (!opts.json) logConfigUpdated(runtime);
}
});
if (gatewayAttempt.kind === "fallback-unreachable") await withLocalAgentCronJobsRemoved(agentId, () => cfg, commitRoster);
else await commitRoster();
} catch (error) {
if (!existingJournal) deletion.rollback();
throw error;
}
const purgeFailed = await purgeAgentSessionStoreEntries(cfg, agentId, { runDatabaseCleanup: deletion.runDatabaseCleanup });
for (const survivingAgentId of listAgentIds(result.config)) resolveAgentDir(result.config, survivingAgentId);
const survivingDatabaseFilePaths = resolveSurvivingDatabaseFilePaths(readAgentDeleteDatabaseRegistry(), agentId);
const sharedWithSurvivor = (pathname) => isPathOwnedBySurvivingAgent(result.config, agentId, pathname, survivingDatabaseFilePaths);
const workspaceRetained = sharedWithSurvivor(workspaceDir);
const quietRuntime = opts.json ? createQuietRuntime(runtime) : runtime;
let workspaceCleanupError;
const removed = [];
const failed = [];
const removePath = async (pathname) => {
const outcome = await moveToTrashResult(pathname, quietRuntime);
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);
for (const warning of legacyCleanup.warnings) quietRuntime.log(warning);
deleteWorkspaceState(statePlan);
} 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;
if (failed.length === 0 && !purgeFailed) {
if (deleteFiles) unregisterOpenClawAgentDatabases({ agentId });
deletion.finish();
}
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: result.removedBindings,
removedAllow: result.removedAllow,
clearedOwnerRefs: result.clearedOwnerRefs.length > 0 ? result.clearedOwnerRefs : void 0,
removed,
failed,
...purgeFailed ? { purgeFailed: true } : {},
...gatewayAttempt.kind === "fallback-credentials-required" ? { cronCleanupSkipped: true } : {}
});
else {
runtime.log(`Deleted agent: ${agentId}`);
logClearedOwnerRefs(runtime, result.clearedOwnerRefs);
logSessionPurgeWarning(runtime, agentId, purgeFailed);
}
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 };