Files
paseo/packages/app/e2e/support/helpers/command-center-agent-controls.ts
4ef4a95a42 Keep Claude session listing from loading whole histories into memory (#7626)
* fix(claude): stream and cache session import descriptors

* fix(claude): bound and schedule import reads

Reject JSONL records over 4 MiB explicitly to preserve metadata.
Limit shared reads to four globally and one per listing for fairness.
Deduplicate reads by cacheKey with independent subscriber cancellation.
Set a 45-second provider deadline before the 60-second client timeout.
Clone returned descriptors to isolate overlapping callers.

Validation: 97 focused tests passed; independent review approved.

Detected-By: user

* fix(claude): parallelize resilient session scans

Use up to four workers per listing with a shared four-read limit
and bounded FIFO queuing. Preserve cache-key deduplication,
subscriber cancellation, and candidate order.

Log and skip oversized or unreadable transcripts so healthy
sessions remain visible. Propagate programming and abort errors.

Detected-By: user

* fix(claude): surface session import read failures

Skip expected candidate read failures and return healthy sessions.
Distinguish unreadable metadata from parsed transcripts, and raise
AggregateError when every candidate is unreadable.

Propagate systemic file errors and narrow discovery error suppression.

Detected-By: user

* fix(claude): keep large sessions importable while streaming history

Preserve import metadata for transcripts containing image records above 4 MiB. Stream complete records instead of rejecting otherwise listable sessions, and cover overlapping requests across aliased provider roots under a constrained heap.

Co-authored-by: Josh Lane <joshlane@easypost.com>

* test(app): wait for active agent controls and submitted turns

Observe the mode control before testing Command Center keyboard selection. Observe a running turn before waiting for its completion, so the rewind test cannot mistake the previous idle state and end marker for the new turn.

---------

Co-authored-by: Josh Lane <joshlane@easypost.com>
Co-authored-by: Josh Lane <lane.joshlane@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-22 12:07:39 +02:00

130 lines
4.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, type Locator, type Page } from "@playwright/test";
import type { SeededWorkspace } from "./seed-client";
import { openCommandCenter } from "./command-center";
import { openAgentRoute, type MockAgentWorkspace } from "./mock-agent";
export interface CommandCenterAgentControlChoice {
query: string;
choice: string;
}
/**
* Focuses a seeded mock agent — which is what makes its model and mode contributions register —
* opens the Command Center over it, and hands back the query input. Seeding stays in the test so
* the workspace lifecycle is owned by the `try`/`finally` that cleans it up.
*/
export async function openCommandCenterForAgent(
page: Page,
workspace: Pick<MockAgentWorkspace, "workspaceId" | "agentId">,
): Promise<Locator> {
await openAgentRoute(page, { workspaceId: workspace.workspaceId, agentId: workspace.agentId });
await expect(
page.getByRole("button", { name: /^Select agent mode \(/ }).filter({ visible: true }),
).toBeVisible({ timeout: 30_000 });
const panel = await openCommandCenter(page);
return panel.getByTestId("command-center-input");
}
/**
* Types a query that matches both the agent's modes and its models, and waits until rows from both
* groups are listed. The agent's controls register after the panel opens; a key pressed before
* then lands on whatever else matched, such as file search.
*/
export async function searchAgentControls(page: Page, query: string): Promise<void> {
const panel = page.getByTestId("command-center-panel");
await panel.getByTestId("command-center-input").fill(query);
await expect(panel.getByRole("button", { name: /^Mode › / }).first()).toBeVisible({
timeout: 30_000,
});
await expect(panel.getByRole("button", { name: /^Model › / }).first()).toBeVisible({
timeout: 30_000,
});
}
export async function chooseCommandCenterAgentControl(input: {
page: Page;
query: string;
choice: string;
}): Promise<void> {
const panel = input.page.getByTestId("command-center-panel");
await panel.getByRole("textbox").fill(input.query);
const choice = panel.getByRole("button", { name: input.choice });
await expect(choice).toBeVisible({ timeout: 30_000 });
await choice.click();
}
export async function expectCommandCenterAgentControlSelected(input: {
page: Page;
query: string;
choice: string;
}): Promise<void> {
const panel = input.page.getByTestId("command-center-panel");
await panel.getByRole("textbox").fill(input.query);
const choice = panel.getByRole("button", { name: input.choice });
await expect(choice).toHaveRole("button");
await expect(choice).toHaveAttribute("aria-pressed", "true");
}
export async function applyCommandCenterAgentControls(
page: Page,
choices: readonly CommandCenterAgentControlChoice[],
): Promise<void> {
for (const choice of choices) {
await openCommandCenter(page);
await chooseCommandCenterAgentControl({ page, ...choice });
}
}
export async function expectFocusedAgentControls(
page: Page,
choices: readonly CommandCenterAgentControlChoice[],
): Promise<void> {
await openCommandCenter(page);
for (const choice of choices) {
await expectCommandCenterAgentControlSelected({ page, ...choice });
}
}
export async function submitDraftAgent(page: Page, prompt: string): Promise<void> {
const composer = page.getByRole("textbox", { name: "Message agent..." }).first();
await expect(composer).toBeEditable({ timeout: 30_000 });
await composer.fill(prompt);
await composer.press("Enter");
await expect(page.getByText(prompt, { exact: true }).first()).toBeVisible({ timeout: 30_000 });
}
export async function waitForDraftComposer(page: Page): Promise<void> {
await expect(page.getByRole("textbox", { name: "Message agent..." }).first()).toBeEditable({
timeout: 30_000,
});
}
export async function expectWorkspaceAgentConfiguration(
workspace: Pick<SeededWorkspace, "client" | "workspaceId">,
expected: { id?: string; provider: string; model: string | null; modeId: string | null },
): Promise<void> {
await expect
.poll(
async () => {
const entries = (await workspace.client.fetchAgents({ scope: "active" })).entries;
const agent = entries
.map((entry) => entry.agent)
.find(
(candidate) =>
candidate.workspaceId === workspace.workspaceId &&
(expected.id === undefined || candidate.id === expected.id),
);
return agent
? {
id: agent.id,
provider: agent.provider,
model: agent.model,
modeId: agent.currentModeId,
}
: null;
},
{ timeout: 30_000 },
)
.toMatchObject(expected);
}