refactor: remove terminal agent concept entirely
Remove the "agent can be a terminal" branching from the entire codebase. An agent is now always a session-backed chat agent. Standalone terminal infrastructure (terminal component, ANSI handling, terminal-stream-protocol) is preserved. Server: delete ManagedTerminalAgent, AgentKind, TerminalExitDetails, launchTerminalAgent, registerTerminalAgent, handleTerminalAgentExited, supportsTerminalMode capability, buildTerminalCreate/ResumeCommand from all providers, terminal agent persistence/projections. App: delete terminal-agent-panel.tsx, terminal-agent-reopen-store.ts, terminal/terminalExit fields on agent state, "Terminal Agents" launcher section, terminal-agent workspace setup flow, terminal badge in agent list. CLI: remove terminal column from ls, terminal-agent error from send. 60 files changed, -3592 lines
This commit is contained in:
@@ -89,12 +89,6 @@ export async function waitForLauncherPanel(page: Page): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
/** Assert that the launcher panel shows provider tiles under "Terminal Agents". */
|
||||
export async function assertProviderTilesVisible(page: Page): Promise<void> {
|
||||
await expect(page.getByText("Terminal Agents", { exact: true }).first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
}
|
||||
|
||||
/** Assert the launcher panel has a "New Chat" tile. */
|
||||
export async function assertNewChatTileVisible(page: Page): Promise<void> {
|
||||
@@ -122,12 +116,6 @@ export async function clickTerminal(page: Page): Promise<void> {
|
||||
await button.click();
|
||||
}
|
||||
|
||||
/** Click a provider tile by label (e.g. "Claude Code", "Codex"). */
|
||||
export async function clickProviderTile(page: Page, providerLabel: string): Promise<void> {
|
||||
const tile = page.getByRole("button", { name: providerLabel }).first();
|
||||
await expect(tile).toBeVisible({ timeout: 10_000 });
|
||||
await tile.click();
|
||||
}
|
||||
|
||||
// ─── Tab title assertions ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { expect, type Page } from "@playwright/test";
|
||||
import {
|
||||
clickNewChat,
|
||||
clickProviderTile,
|
||||
clickTerminal,
|
||||
countTabsOfKind,
|
||||
getTabTestIds,
|
||||
waitForTabWithTitle,
|
||||
} from "./launcher";
|
||||
import { setupDeterministicPrompt, waitForTerminalContent } from "./terminal-perf";
|
||||
|
||||
@@ -35,13 +33,6 @@ export async function createStandaloneTerminalFromLauncher(page: Page): Promise<
|
||||
await expect.poll(async () => (await getTabTestIds(page)).length).toBe(tabIdsBefore.length);
|
||||
}
|
||||
|
||||
export async function createTerminalAgentFromLauncher(page: Page, providerLabel: string): Promise<void> {
|
||||
await clickProviderTile(page, providerLabel);
|
||||
await expect(page.getByTestId("terminal-agent-loading")).toHaveCount(0, { timeout: 30_000 });
|
||||
await expect(terminalSurface(page)).toBeVisible({ timeout: 30_000 });
|
||||
await waitForTabWithTitle(page, /new agent/i);
|
||||
}
|
||||
|
||||
export async function createAgentChatFromLauncher(page: Page): Promise<void> {
|
||||
await clickNewChat(page);
|
||||
await expect(composerInput(page)).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
@@ -186,26 +186,6 @@ export async function createChatAgentFromWorkspaceSetup(
|
||||
await dialog.getByRole("button", { name: "Send message" }).click();
|
||||
}
|
||||
|
||||
export async function createTerminalAgentFromWorkspaceSetup(
|
||||
page: Page,
|
||||
input: { providerLabel: string; prompt?: string },
|
||||
): Promise<void> {
|
||||
const dialog = workspaceSetupDialog(page);
|
||||
await dialog.getByRole("button", { name: /Terminal Agent/i }).click();
|
||||
|
||||
const providerButton = dialog.getByRole("button", { name: new RegExp(`^${input.providerLabel}$`, "i") }).first();
|
||||
await expect(providerButton).toBeVisible({ timeout: 15_000 });
|
||||
await providerButton.click();
|
||||
|
||||
if (input.prompt) {
|
||||
const promptInput = dialog.getByPlaceholder("Optional").first();
|
||||
await expect(promptInput).toBeVisible({ timeout: 15_000 });
|
||||
await promptInput.fill(input.prompt);
|
||||
}
|
||||
|
||||
await dialog.getByRole("button", { name: "Launch" }).click();
|
||||
}
|
||||
|
||||
export async function createStandaloneTerminalFromWorkspaceSetup(page: Page): Promise<void> {
|
||||
await workspaceSetupDialog(page)
|
||||
.getByRole("button", { name: /^Terminal Create the workspace/i })
|
||||
|
||||
@@ -3,7 +3,6 @@ import { createTempGitRepo } from "./helpers/workspace";
|
||||
import {
|
||||
gotoWorkspace,
|
||||
waitForLauncherPanel,
|
||||
assertProviderTilesVisible,
|
||||
assertNewChatTileVisible,
|
||||
assertTerminalTileVisible,
|
||||
assertSingleNewTabButton,
|
||||
@@ -11,7 +10,6 @@ import {
|
||||
pressNewTabShortcut,
|
||||
clickNewChat,
|
||||
clickTerminal,
|
||||
clickProviderTile,
|
||||
countTabsOfKind,
|
||||
getTabTestIds,
|
||||
waitForTabWithTitle,
|
||||
@@ -49,7 +47,7 @@ test.afterAll(async () => {
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
test.describe("Launcher tab", () => {
|
||||
test("Cmd+T opens launcher panel with New Chat, Terminal, and provider tiles", async ({
|
||||
test("Cmd+T opens launcher panel with New Chat and Terminal tiles", async ({
|
||||
page,
|
||||
}) => {
|
||||
await gotoWorkspace(page, workspaceId);
|
||||
@@ -59,7 +57,6 @@ test.describe("Launcher tab", () => {
|
||||
await waitForLauncherPanel(page);
|
||||
await assertNewChatTileVisible(page);
|
||||
await assertTerminalTileVisible(page);
|
||||
await assertProviderTilesVisible(page);
|
||||
});
|
||||
|
||||
test("opening two new tabs creates two launcher tabs", async ({ page }) => {
|
||||
@@ -126,61 +123,6 @@ test.describe("Launcher tab", () => {
|
||||
expect(terminalTabs.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
test("clicking a provider tile replaces launcher with terminal agent tab", async ({ page }) => {
|
||||
test.setTimeout(45_000);
|
||||
await gotoWorkspace(page, workspaceId);
|
||||
|
||||
await clickNewTabButton(page);
|
||||
await waitForLauncherPanel(page);
|
||||
|
||||
const tabsBefore = await getTabTestIds(page);
|
||||
|
||||
// Click the first visible provider tile under "Terminal Agents"
|
||||
const providerTiles = page.locator('[role="button"]').filter({
|
||||
has: page.locator("text=Terminal Agents").locator("..").locator(".."),
|
||||
});
|
||||
|
||||
// Try clicking any provider tile — find the first one after the "Terminal Agents" label
|
||||
const terminalAgentsLabel = page.getByText("Terminal Agents", { exact: true }).first();
|
||||
await expect(terminalAgentsLabel).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// The provider grid follows the label. Click the first provider tile.
|
||||
const providerGrid = terminalAgentsLabel.locator("~ *").first();
|
||||
const firstProvider = providerGrid.getByRole("button").first();
|
||||
if (await firstProvider.isVisible().catch(() => false)) {
|
||||
await firstProvider.click();
|
||||
} else {
|
||||
// Fallback: look for any provider button after the section label
|
||||
const allButtons = page.getByRole("button");
|
||||
const count = await allButtons.count();
|
||||
let clicked = false;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const btn = allButtons.nth(i);
|
||||
const text = await btn.innerText().catch(() => "");
|
||||
// Skip known non-provider buttons
|
||||
if (["New Chat", "Terminal", "More", "+"].includes(text.trim())) continue;
|
||||
if (!text.trim()) continue;
|
||||
await btn.click();
|
||||
clicked = true;
|
||||
break;
|
||||
}
|
||||
if (!clicked) {
|
||||
test.skip(true, "No provider tiles available");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Should see an agent panel (terminal surface or agent stream)
|
||||
const agentOrTerminal = page.locator(
|
||||
'[data-testid="terminal-surface"], [data-testid^="agent-"]',
|
||||
);
|
||||
await expect(agentOrTerminal.first()).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
// Tab count stays the same (replaced, not added)
|
||||
const tabsAfter = await getTabTestIds(page);
|
||||
expect(tabsAfter.length).toBe(tabsBefore.length);
|
||||
});
|
||||
|
||||
test("tab bar shows a single + button per pane", async ({ page }) => {
|
||||
await gotoWorkspace(page, workspaceId);
|
||||
await assertSingleNewTabButton(page);
|
||||
|
||||
@@ -10,7 +10,6 @@ import { createTempGitRepo } from "./helpers/workspace";
|
||||
import {
|
||||
createAgentChatFromLauncher,
|
||||
createStandaloneTerminalFromLauncher,
|
||||
createTerminalAgentFromLauncher,
|
||||
expectTerminalCwd,
|
||||
} from "./helpers/workspace-lifecycle";
|
||||
import { connectWorkspaceSetupClient, seedProjectForWorkspaceSetup } from "./helpers/workspace-setup";
|
||||
@@ -22,30 +21,6 @@ test.describe("Workspace lifecycle", () => {
|
||||
test.describe.configure({ retries: 1 });
|
||||
|
||||
test.describe("Main checkout", () => {
|
||||
test("creates a terminal agent via provider tile", async ({ page }) => {
|
||||
test.setTimeout(60_000);
|
||||
|
||||
const client = await connectWorkspaceSetupClient();
|
||||
const repo = await createTempGitRepo("lifecycle-main-agent-");
|
||||
|
||||
try {
|
||||
await seedProjectForWorkspaceSetup(client, repo.path);
|
||||
const workspaceResult = await client.openProject(repo.path);
|
||||
if (!workspaceResult.workspace) {
|
||||
throw new Error(workspaceResult.error ?? `Failed to open project ${repo.path}`);
|
||||
}
|
||||
const workspaceId = String(workspaceResult.workspace.id);
|
||||
|
||||
await gotoWorkspace(page, workspaceId);
|
||||
await clickNewTabButton(page);
|
||||
await waitForLauncherPanel(page);
|
||||
await createTerminalAgentFromLauncher(page, "Claude");
|
||||
} finally {
|
||||
await client.close();
|
||||
await repo.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test("creates an agent chat via New Chat", async ({ page }) => {
|
||||
test.setTimeout(60_000);
|
||||
|
||||
@@ -97,53 +72,6 @@ test.describe("Workspace lifecycle", () => {
|
||||
});
|
||||
|
||||
test.describe("Worktree workspace", () => {
|
||||
test("creates a terminal agent via provider tile", async ({ page }) => {
|
||||
test.setTimeout(90_000);
|
||||
|
||||
const client = await connectWorkspaceSetupClient();
|
||||
const repo = await createTempGitRepo("lifecycle-wt-agent-");
|
||||
const worktreePath = path.join(
|
||||
"/tmp",
|
||||
`paseo-wt-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
||||
);
|
||||
const branchName = `lifecycle-wt-agent-${Date.now()}`;
|
||||
let worktreeCreated = false;
|
||||
|
||||
try {
|
||||
await seedProjectForWorkspaceSetup(client, repo.path);
|
||||
|
||||
execSync(`git worktree add ${JSON.stringify(worktreePath)} -b ${JSON.stringify(branchName)} main`, {
|
||||
cwd: repo.path,
|
||||
stdio: "ignore",
|
||||
});
|
||||
worktreeCreated = true;
|
||||
|
||||
const workspaceResult = await client.openProject(worktreePath);
|
||||
if (!workspaceResult.workspace) {
|
||||
throw new Error(workspaceResult.error ?? `Failed to open project ${worktreePath}`);
|
||||
}
|
||||
const workspaceId = String(workspaceResult.workspace.id);
|
||||
|
||||
await gotoWorkspace(page, workspaceId);
|
||||
await clickNewTabButton(page);
|
||||
await waitForLauncherPanel(page);
|
||||
await createTerminalAgentFromLauncher(page, "Claude");
|
||||
} finally {
|
||||
if (worktreeCreated) {
|
||||
try {
|
||||
execSync(`git worktree remove ${JSON.stringify(worktreePath)} --force`, {
|
||||
cwd: repo.path,
|
||||
stdio: "ignore",
|
||||
});
|
||||
} catch {
|
||||
// Best-effort cleanup so test failures preserve the original error.
|
||||
}
|
||||
}
|
||||
await client.close();
|
||||
await repo.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test("creates an agent chat via New Chat", async ({ page }) => {
|
||||
test.setTimeout(90_000);
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
connectWorkspaceSetupClient,
|
||||
createChatAgentFromWorkspaceSetup,
|
||||
createStandaloneTerminalFromWorkspaceSetup,
|
||||
createTerminalAgentFromWorkspaceSetup,
|
||||
createWorkspaceFromSidebar,
|
||||
findWorktreeWorkspaceForProject,
|
||||
openHomeWithProject,
|
||||
@@ -109,36 +108,6 @@ test.describe("Workspace setup runtime authority", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("first terminal agent attaches to the created workspace", async ({ page }) => {
|
||||
test.setTimeout(90_000);
|
||||
|
||||
const client = await connectWorkspaceSetupClient();
|
||||
const repo = await createTempGitRepo("workspace-setup-terminal-agent-");
|
||||
|
||||
try {
|
||||
await client.openProject(repo.path);
|
||||
await openWorkspaceSetupDialogFromSidebar(page, repo.path);
|
||||
const agentIdsBefore = new Set((await client.fetchAgents()).entries.map((entry) => entry.agent.id));
|
||||
|
||||
await createTerminalAgentFromWorkspaceSetup(page, {
|
||||
providerLabel: "Claude",
|
||||
prompt: `workspace-setup-terminal-agent-${Date.now()}`,
|
||||
});
|
||||
|
||||
const workspace = await expectCreatedWorkspaceRoute(client, repo.path);
|
||||
const agent = await waitForNewWorkspaceAgent(
|
||||
client,
|
||||
workspace.workspaceDirectory,
|
||||
agentIdsBefore,
|
||||
);
|
||||
expect(agent.cwd).toBe(workspace.workspaceDirectory);
|
||||
expect(agent.cwd).not.toBe(repo.path);
|
||||
} finally {
|
||||
await client.close();
|
||||
await repo.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test("first terminal attaches to the created workspace", async ({ page }) => {
|
||||
test.setTimeout(90_000);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user