Files
paseo/packages/cli/src/commands/agent/delete.ts
T
paseo-bot[bot]and3ae3ae 8216e86ecb fix(cli): delete archived agents with agent delete --all and --cwd (#6027)
* fix(cli): delete archived agents in bulk commands

* test(cli): cover archived agents in bulk delete from the CLI suite

The daemon-backed test imported server test utilities from a CLI test,
which the CI contract in scripts/ci-workflow.test.mjs forbids. Cover
--all and --cwd against archived agents in the runDeleteCommand unit
test instead.

* fix(cli): read every history page when bulk-deleting agents

agent delete --all and --cwd read one page of fetch_agents, so agents
past the first 200 were skipped, and fetch_agents leaves out saved
agents whose provider is unavailable. List agents from the paged agent
history, which the History view uses, so bulk delete covers both.

* fix(cli): delete an agent by ID without listing history

---------

Co-authored-by: 3ae3ae <ogyshjsm@naver.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-04 07:55:21 +02:00

137 lines
4.1 KiB
TypeScript

import type { Command } from "commander";
import { connectToDaemon } from "../../utils/client.js";
import { isSameOrDescendantPath } from "../../utils/paths.js";
import type { DaemonClient, FetchAgentHistoryEntry } from "@getpaseo/client/internal/daemon-client";
export function addDeleteOptions(cmd: Command): Command {
return cmd
.description("Delete an agent (interrupt if running, then hard-delete)")
.argument("[id]", "Agent ID (or prefix) - optional if --all or --cwd specified")
.option("--all", "Delete all agents")
.option("--cwd <path>", "Delete all agents in directory");
}
import type {
CommandOptions,
SingleResult,
OutputSchema,
CommandError,
} from "../../output/index.js";
export interface DeleteResult {
deletedCount: number;
agentIds: string[];
}
export const deleteSchema: OutputSchema<DeleteResult> = {
idField: (item) => item.agentIds.join("\n"),
columns: [{ header: "DELETED", field: "deletedCount" }],
};
export interface AgentDeleteOptions extends CommandOptions {
all?: boolean;
cwd?: string;
}
export type AgentDeleteResult = SingleResult<DeleteResult>;
// History includes archived agents and agents whose provider is unavailable.
async function fetchHistoryAgents(client: DaemonClient) {
const agents: FetchAgentHistoryEntry["agent"][] = [];
let cursor: string | undefined;
do {
const payload = await client.fetchAgentHistory({
page: { limit: 200, ...(cursor ? { cursor } : {}) },
});
agents.push(...payload.entries.map((entry) => entry.agent));
cursor = payload.pageInfo.nextCursor ?? undefined;
} while (cursor);
return agents;
}
export async function runDeleteCommand(
id: string | undefined,
options: AgentDeleteOptions,
_command: Command,
): Promise<AgentDeleteResult> {
if (!id && !options.all && !options.cwd) {
const error: CommandError = {
code: "MISSING_ARGUMENT",
message: "Agent ID required unless --all or --cwd is specified",
details: "Usage: paseo agent delete <id> | --all | --cwd <path>",
};
throw error;
}
const client = await connectToDaemon({ target: options.daemonTarget });
try {
const deletedIds: string[] = [];
let agents: FetchAgentHistoryEntry["agent"][];
if (options.all || options.cwd) {
agents = await fetchHistoryAgents(client);
if (!options.all) {
agents = agents.filter((a) => isSameOrDescendantPath(options.cwd!, a.cwd));
}
} else {
const fetchResult = await client.fetchAgent({ agentId: id! });
if (!fetchResult) {
const error: CommandError = {
code: "AGENT_NOT_FOUND",
message: `No agent found matching: ${id}`,
details: "Use `paseo ls` to list available agents",
};
throw error;
}
agents = [fetchResult.agent];
}
const deleteResults = await Promise.all(
agents.map(async (agent) => {
try {
if (agent.status === "running") {
await client.cancelAgent(agent.id).catch(() => {});
}
await client.deleteAgent(agent.id);
return { ok: true as const, id: agent.id };
} catch (err) {
if (err && typeof err === "object" && "code" in err) throw err;
const message = err instanceof Error ? err.message : String(err);
return { ok: false as const, id: agent.id, message };
}
}),
);
for (const result of deleteResults) {
if (result.ok) {
deletedIds.push(result.id);
} else {
console.error(
`Warning: Failed to delete agent ${result.id.slice(0, 7)}: ${result.message}`,
);
}
}
await client.close();
return {
type: "single",
data: {
deletedCount: deletedIds.length,
agentIds: deletedIds,
},
schema: deleteSchema,
};
} catch (err) {
await client.close().catch(() => {});
if (err && typeof err === "object" && "code" in err) {
throw err;
}
const message = err instanceof Error ? err.message : String(err);
const error: CommandError = {
code: "DELETE_AGENT_FAILED",
message: `Failed to delete agent(s): ${message}`,
};
throw error;
}
}