Files
paseo/packages/app/src/subagents/archive-subagent.test.ts
T
Mohamed BoudraandGitHub 7af92120fe Detach subagents without archiving them (#1612)
* Add detach action for subagents

* Translate detached subagent connection error

* Address subagent detach review feedback

* Fix subagent integration test import

* Focus detached subagents as tabs
2026-06-19 17:19:05 +08:00

206 lines
5.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { ConfirmDialogInput } from "@/utils/confirm-dialog";
import {
requestArchiveSubagent,
resolveArchiveSubagentDialog,
type ArchiveSubagentDeps,
type ResolveArchiveSubagentDialogInput,
} from "./archive-subagent";
interface RecordedArchive {
serverId: string;
agentId: string;
}
interface FakeArchiveSubagentEnv {
deps: ArchiveSubagentDeps;
recordedArchives: RecordedArchive[];
recordedConfirmInputs: ConfirmDialogInput[];
recordedErrors: unknown[];
setSubagent(id: string, snapshot: ResolveArchiveSubagentDialogInput | undefined): void;
}
function createFakeEnv(
options: {
confirmResult?: boolean;
initialSubagents?: Array<{ id: string; snapshot: ResolveArchiveSubagentDialogInput }>;
} = {},
): FakeArchiveSubagentEnv {
const subagents = new Map<string, ResolveArchiveSubagentDialogInput | undefined>();
for (const entry of options.initialSubagents ?? []) {
subagents.set(entry.id, entry.snapshot);
}
const recordedArchives: RecordedArchive[] = [];
const recordedConfirmInputs: ConfirmDialogInput[] = [];
const recordedErrors: unknown[] = [];
return {
recordedArchives,
recordedConfirmInputs,
recordedErrors,
setSubagent(id, snapshot) {
subagents.set(id, snapshot);
},
deps: {
getSubagent: (id) => subagents.get(id),
confirm: async (dialog) => {
recordedConfirmInputs.push(dialog);
return options.confirmResult ?? false;
},
archiveAgent: async (input) => {
recordedArchives.push(input);
},
reportError: (error) => {
recordedErrors.push(error);
},
},
};
}
describe("resolveArchiveSubagentDialog", () => {
it("uses running copy for running subagents", () => {
expect(
resolveArchiveSubagentDialog({
title: "Review branch",
status: "running",
}),
).toEqual({
title: "Archive running subagent?",
message:
"Review branch is still running. Archiving it will stop the subagent and remove it from the track.",
confirmLabel: "Archive",
cancelLabel: "Cancel",
destructive: true,
});
});
it("does not use running copy for initializing subagents", () => {
expect(
resolveArchiveSubagentDialog({
title: "Starting child",
status: "initializing",
}),
).toEqual({
title: "Archive subagent?",
message: "Remove Starting child from the track. The subagent will be archived.",
confirmLabel: "Archive",
cancelLabel: "Cancel",
destructive: true,
});
});
it("uses idle copy for non-running subagents", () => {
expect(
resolveArchiveSubagentDialog({
title: "Review branch",
status: "idle",
}),
).toEqual({
title: "Archive subagent?",
message: "Remove Review branch from the track. The subagent will be archived.",
confirmLabel: "Archive",
cancelLabel: "Cancel",
destructive: true,
});
});
it("falls back to this subagent when the title is not displayable", () => {
expect(
resolveArchiveSubagentDialog({
title: "New Agent",
status: null,
}),
).toEqual({
title: "Archive subagent?",
message: "Remove this subagent from the track. The subagent will be archived.",
confirmLabel: "Archive",
cancelLabel: "Cancel",
destructive: true,
});
});
});
describe("requestArchiveSubagent", () => {
it("archives the subagent with the server id when the user confirms", async () => {
const env = createFakeEnv({
confirmResult: true,
initialSubagents: [
{
id: "child-agent",
snapshot: { title: "Review branch", status: "running" },
},
],
});
await requestArchiveSubagent({ serverId: "server-1", subagentId: "child-agent" }, env.deps);
expect(env.recordedArchives).toEqual([{ serverId: "server-1", agentId: "child-agent" }]);
});
it("does not archive the subagent when the user cancels", async () => {
const env = createFakeEnv({
confirmResult: false,
initialSubagents: [
{
id: "child-agent",
snapshot: { title: "Review branch", status: "idle" },
},
],
});
await requestArchiveSubagent({ serverId: "server-1", subagentId: "child-agent" }, env.deps);
expect(env.recordedArchives).toEqual([]);
});
it("asks for confirmation using the resolved dialog for the subagent", async () => {
const env = createFakeEnv({
confirmResult: false,
initialSubagents: [
{
id: "child-agent",
snapshot: { title: "Review branch", status: "running" },
},
],
});
await requestArchiveSubagent({ serverId: "server-1", subagentId: "child-agent" }, env.deps);
expect(env.recordedConfirmInputs).toEqual([
resolveArchiveSubagentDialog({ title: "Review branch", status: "running" }),
]);
});
it("asks with the missing-subagent dialog when the snapshot is unknown", async () => {
const env = createFakeEnv({ confirmResult: false });
await requestArchiveSubagent({ serverId: "server-1", subagentId: "missing" }, env.deps);
expect(env.recordedConfirmInputs).toEqual([
resolveArchiveSubagentDialog({ title: undefined, status: undefined }),
]);
expect(env.recordedArchives).toEqual([]);
});
it("reports archive errors after the user confirms", async () => {
const env = createFakeEnv({
confirmResult: true,
initialSubagents: [
{
id: "child-agent",
snapshot: { title: "Review branch", status: "running" },
},
],
});
const error = new Error("daemon offline");
env.deps.archiveAgent = async () => {
throw error;
};
await expect(
requestArchiveSubagent({ serverId: "server-1", subagentId: "child-agent" }, env.deps),
).resolves.toBeUndefined();
expect(env.recordedErrors).toEqual([error]);
});
});