Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
import fs from "node:fs";
import path from "node:path";
import { resolveAgentDir, resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
import { replaceConfigFile } from "../config/config.js";
import { logConfigUpdated } from "../config/logging.js";
import { resolveSessionTranscriptsDirForAgent } from "../config/sessions.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { DEFAULT_AGENT_ID, normalizeAgentId } from "../routing/session-key.js";
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
import { defaultRuntime } from "../runtime.js";
import { lowercasePreservingWhitespace } from "../shared/string-coerce.js";
import { createClackPrompter } from "../wizard/clack-prompter.js";
import {
createQuietRuntime,
purgeAgentSessionStoreEntries,
requireValidConfigFileSnapshot,
} from "./agents.command-shared.js";
import { findAgentEntryIndex, listAgentEntries, pruneAgentConfig } from "./agents.config.js";
import { moveToTrash } from "./onboard-helpers.js";
function normalizeWorkspacePathForComparison(input: string): string {
const resolved = path.resolve(input.replaceAll("\0", ""));
let normalized = resolved;
try {
normalized = fs.realpathSync.native(resolved);
} catch {
// Keep lexical path for non-existent directories.
}
if (process.platform === "win32") {
return lowercasePreservingWhitespace(normalized);
}
return normalized;
}
function isPathWithinRoot(candidatePath: string, rootPath: string): boolean {
const relative = path.relative(rootPath, candidatePath);
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
}
function workspacePathsOverlap(left: string, right: string): boolean {
const normalizedLeft = normalizeWorkspacePathForComparison(left);
const normalizedRight = normalizeWorkspacePathForComparison(right);
return (
isPathWithinRoot(normalizedLeft, normalizedRight) ||
isPathWithinRoot(normalizedRight, normalizedLeft)
);
}
function findOverlappingWorkspaceAgentIds(
cfg: OpenClawConfig,
agentId: string,
workspaceDir: string,
): string[] {
const entries = listAgentEntries(cfg);
const normalizedAgentId = normalizeAgentId(agentId);
const overlappingAgentIds: string[] = [];
for (const entry of entries) {
const otherAgentId = normalizeAgentId(entry.id);
if (otherAgentId === normalizedAgentId) {
continue;
}
const otherWorkspace = resolveAgentWorkspaceDir(cfg, otherAgentId);
if (workspacePathsOverlap(workspaceDir, otherWorkspace)) {
overlappingAgentIds.push(otherAgentId);
}
}
return overlappingAgentIds;
}
type AgentsDeleteOptions = {
id: string;
force?: boolean;
json?: boolean;
};
export async function agentsDeleteCommand(
opts: AgentsDeleteOptions,
runtime: RuntimeEnv = 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) {
runtime.error("Agent id is required.");
runtime.exit(1);
return;
}
const agentId = normalizeAgentId(input);
if (agentId !== input) {
runtime.log(`Normalized agent id to "${agentId}".`);
}
if (agentId === DEFAULT_AGENT_ID) {
runtime.error(`"${DEFAULT_AGENT_ID}" cannot be deleted.`);
runtime.exit(1);
return;
}
if (findAgentEntryIndex(listAgentEntries(cfg), agentId) < 0) {
runtime.error(`Agent "${agentId}" not found.`);
runtime.exit(1);
return;
}
if (!opts.force) {
if (!process.stdin.isTTY) {
runtime.error("Non-interactive session. Re-run with --force.");
runtime.exit(1);
return;
}
const prompter = createClackPrompter();
const confirmed = await prompter.confirm({
message: `Delete agent "${agentId}" and prune workspace/state?`,
initialValue: false,
});
if (!confirmed) {
runtime.log("Cancelled.");
return;
}
}
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
const agentDir = resolveAgentDir(cfg, agentId);
const sessionsDir = resolveSessionTranscriptsDirForAgent(agentId);
const result = pruneAgentConfig(cfg, agentId);
await replaceConfigFile({
nextConfig: result.config,
...(baseHash !== undefined ? { baseHash } : {}),
writeOptions: opts.json ? { skipOutputLogs: true } : undefined,
});
if (!opts.json) {
logConfigUpdated(runtime);
}
// Purge session store entries for this agent so orphaned sessions cannot be targeted (#65524).
await purgeAgentSessionStoreEntries(cfg, agentId);
const quietRuntime = opts.json ? createQuietRuntime(runtime) : runtime;
// Only trash the workspace if no other agent can depend on that path (#70890).
const workspaceSharedWith = findOverlappingWorkspaceAgentIds(cfg, agentId, workspaceDir);
const workspaceRetained = workspaceSharedWith.length > 0;
if (workspaceRetained) {
quietRuntime.log(
`Skipped workspace removal (shared with other agents: ${workspaceSharedWith.join(", ")}): ${workspaceDir}`,
);
} else {
await moveToTrash(workspaceDir, quietRuntime);
}
await moveToTrash(agentDir, quietRuntime);
await moveToTrash(sessionsDir, quietRuntime);
if (opts.json) {
writeRuntimeJson(runtime, {
agentId,
workspace: workspaceDir,
workspaceRetained: workspaceRetained || undefined,
workspaceRetainedReason: workspaceRetained ? "shared" : undefined,
workspaceSharedWith: workspaceRetained ? workspaceSharedWith : undefined,
agentDir,
sessionsDir,
removedBindings: result.removedBindings,
removedAllow: result.removedAllow,
});
} else {
runtime.log(`Deleted agent: ${agentId}`);
}
}
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet am 2026-04-27)
¤
*© Formatika GbR, Deutschland