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
This commit is contained in:
Mohamed Boudra
2026-06-19 17:19:05 +08:00
committed by GitHub
parent cda66ae5f3
commit 7af92120fe
33 changed files with 1359 additions and 94 deletions
+1
View File
@@ -82,6 +82,7 @@ export interface SeedDaemonClient {
thinkingOptionId?: string;
featureValues?: Record<string, unknown>;
initialPrompt?: string;
labels?: Record<string, string>;
}): Promise<{ id: string; status: string }>;
fetchAgents(options?: { scope?: "active" }): Promise<{
entries: Array<{
+83
View File
@@ -0,0 +1,83 @@
import { PARENT_AGENT_ID_LABEL } from "@getpaseo/protocol/agent-labels";
import { expect, type Page } from "@playwright/test";
import type { SeededWorkspace } from "./seed-client";
export interface SeededSubagentPair {
parent: {
id: string;
title: string;
};
child: {
id: string;
title: string;
};
workspaceId: string;
}
export async function seedParentWithSubagent(
workspace: Pick<SeededWorkspace, "client" | "repoPath" | "workspaceId">,
input: { parentTitle: string; childTitle: string },
): Promise<SeededSubagentPair> {
const parent = await workspace.client.createAgent({
provider: "mock",
cwd: workspace.repoPath,
workspaceId: workspace.workspaceId,
title: input.parentTitle,
modeId: "load-test",
model: "ten-second-stream",
});
const child = await workspace.client.createAgent({
provider: "mock",
cwd: workspace.repoPath,
workspaceId: workspace.workspaceId,
title: input.childTitle,
modeId: "load-test",
model: "ten-second-stream",
labels: {
[PARENT_AGENT_ID_LABEL]: parent.id,
},
});
return {
parent: {
id: parent.id,
title: input.parentTitle,
},
child: {
id: child.id,
title: input.childTitle,
},
workspaceId: workspace.workspaceId,
};
}
export async function openSubagentsTrack(page: Page): Promise<void> {
await page.getByTestId("subagents-track-header").click();
}
export async function expectSubagentRowVisible(page: Page, childId: string): Promise<void> {
await expect(page.getByTestId(`subagents-track-row-${childId}`)).toBeVisible({
timeout: 30_000,
});
}
export async function expectSubagentRowGone(page: Page, childId: string): Promise<void> {
await expect(page.getByTestId(`subagents-track-row-${childId}`)).toHaveCount(0, {
timeout: 30_000,
});
}
export async function detachSubagentFromTrack(page: Page, childId: string): Promise<void> {
const row = page.getByTestId(`subagents-track-row-${childId}`);
await expect(row).toBeVisible({ timeout: 30_000 });
await row.hover();
page.once("dialog", (dialog) => {
expect(dialog.message()).toContain("Detach subagent?");
void dialog.accept();
});
const detachButton = page.getByTestId(`subagents-track-detach-${childId}`);
await expect(detachButton).toBeVisible({ timeout: 30_000 });
await detachButton.click();
}
+44
View File
@@ -0,0 +1,44 @@
import { test } from "./fixtures";
import { expectWorkspaceTabVisible } from "./helpers/archive-tab";
import { expectAgentTabActive } from "./helpers/launcher";
import { openAgentRoute } from "./helpers/mock-agent";
import { seedWorkspace, type SeededWorkspace } from "./helpers/seed-client";
import {
detachSubagentFromTrack,
expectSubagentRowGone,
expectSubagentRowVisible,
openSubagentsTrack,
seedParentWithSubagent,
} from "./helpers/subagents";
test.describe("Subagent detach", () => {
let workspace: SeededWorkspace;
test.beforeAll(async () => {
workspace = await seedWorkspace({ repoPrefix: "subagent-detach-" });
});
test.afterAll(async () => {
await workspace?.cleanup();
});
test("detaching a subagent focuses it as a workspace tab", async ({ page }) => {
const agents = await seedParentWithSubagent(workspace, {
parentTitle: "Detach parent",
childTitle: "Detached child",
});
await openAgentRoute(page, {
workspaceId: agents.workspaceId,
agentId: agents.parent.id,
});
await openSubagentsTrack(page);
await expectSubagentRowVisible(page, agents.child.id);
await detachSubagentFromTrack(page, agents.child.id);
await expectSubagentRowGone(page, agents.child.id);
await expectWorkspaceTabVisible(page, agents.child.id);
await expectAgentTabActive(page, agents.child.id);
});
});