mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-27 11:42:52 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6b5db65ed |
@@ -0,0 +1,117 @@
|
|||||||
|
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import type { Page } from "@playwright/test";
|
||||||
|
import { test, expect } from "../support/fixtures";
|
||||||
|
import { installDaemonWebSocketGate } from "../support/helpers/daemon-websocket-gate";
|
||||||
|
import { seedWorkspace } from "../support/helpers/seed-client";
|
||||||
|
import { gotoWorkspace } from "../support/helpers/launcher";
|
||||||
|
import { waitForSidebarHydration } from "../support/helpers/workspace-ui";
|
||||||
|
import {
|
||||||
|
openGlobalNewWorkspaceComposer,
|
||||||
|
submitNewWorkspacePrompt,
|
||||||
|
} from "../support/helpers/new-workspace";
|
||||||
|
import { chooseModel, startWithoutRememberedModel } from "../support/helpers/model-memory";
|
||||||
|
import { selectSidebarStatusGrouping } from "../support/helpers/sidebar";
|
||||||
|
|
||||||
|
// Diagnostic reproduction for #7043. Keep provider state inside the checkout.
|
||||||
|
const piAgentDirectory = path.resolve(__dirname, "../../../../.diagnose-7043-pi-agent");
|
||||||
|
test.use({
|
||||||
|
e2eDaemonConfig: {
|
||||||
|
version: 1,
|
||||||
|
agents: {
|
||||||
|
providers: {
|
||||||
|
pi: { models: [{ id: "anthropic/claude-sonnet-4-5", label: "Claude Sonnet 4.5" }] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
e2eDaemonEnvironment: {
|
||||||
|
PI_CODING_AGENT_DIR: piAgentDirectory,
|
||||||
|
ANTHROPIC_API_KEY: "",
|
||||||
|
OPENAI_API_KEY: "",
|
||||||
|
OPENROUTER_API_KEY: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeAll(() => {
|
||||||
|
mkdirSync(piAgentDirectory, { recursive: true });
|
||||||
|
writeFileSync(path.join(piAgentDirectory, "auth.json"), "{}\n");
|
||||||
|
});
|
||||||
|
test.afterAll(() => rmSync(piAgentDirectory, { recursive: true, force: true }));
|
||||||
|
|
||||||
|
interface WorkspaceFrame {
|
||||||
|
type: string;
|
||||||
|
status?: string;
|
||||||
|
phase?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function recordWorkspaceFrames(page: Page): WorkspaceFrame[] {
|
||||||
|
const frames: WorkspaceFrame[] = [];
|
||||||
|
page.on("websocket", (socket) =>
|
||||||
|
socket.on("framereceived", ({ payload }) => {
|
||||||
|
const envelope = JSON.parse(String(payload));
|
||||||
|
const message = envelope.message ?? envelope;
|
||||||
|
if (
|
||||||
|
message.type === "workspace_update" ||
|
||||||
|
message.type === "workspace.create.response" ||
|
||||||
|
message.type === "workspace.create.update"
|
||||||
|
) {
|
||||||
|
frames.push({
|
||||||
|
type: message.type,
|
||||||
|
status: message.payload?.workspace?.status,
|
||||||
|
phase: message.payload?.phase,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startFailedPiWorkspace(page: Page): Promise<void> {
|
||||||
|
await waitForSidebarHydration(page);
|
||||||
|
await selectSidebarStatusGrouping(page);
|
||||||
|
await openGlobalNewWorkspaceComposer(page);
|
||||||
|
await chooseModel(page, "pi", "Claude Sonnet 4.5");
|
||||||
|
await submitNewWorkspacePrompt(page, "Reply OK.");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function expectFailedWorkspaceOutsideWorking(page: Page): Promise<void> {
|
||||||
|
await expect(page.getByText(/No API key found/).first()).toBeVisible({ timeout: 60_000 });
|
||||||
|
await expect(page.getByText("Worked for 0s", { exact: true }).first()).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Working group", { exact: true })).not.toBeVisible();
|
||||||
|
await expect(page.getByLabel("Failed group", { exact: true })).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const lateResponse of [false, true]) {
|
||||||
|
test(`failed first Pi turn leaves Working with ${lateResponse ? "creation response last" : "ordinary delivery"}`, async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
test.setTimeout(120_000);
|
||||||
|
const workspace = await seedWorkspace({ repoPrefix: "failed-workspace-status-" });
|
||||||
|
const frames = recordWorkspaceFrames(page);
|
||||||
|
const gate = await installDaemonWebSocketGate(page);
|
||||||
|
try {
|
||||||
|
await startWithoutRememberedModel(page);
|
||||||
|
await gotoWorkspace(page, workspace.workspaceId);
|
||||||
|
if (lateResponse) gate.holdNextServerMessage("workspace.create.response");
|
||||||
|
await startFailedPiWorkspace(page);
|
||||||
|
if (lateResponse) {
|
||||||
|
await gate.waitForHeldServerMessage("workspace.create.response");
|
||||||
|
// Adapted timing: deliver the real response after both real failure updates.
|
||||||
|
await expect
|
||||||
|
.poll(
|
||||||
|
() =>
|
||||||
|
frames.filter(
|
||||||
|
(frame) => frame.type === "workspace_update" && frame.status === "failed",
|
||||||
|
).length,
|
||||||
|
)
|
||||||
|
.toBeGreaterThanOrEqual(2);
|
||||||
|
gate.releaseHeldServerMessage("workspace.create.response");
|
||||||
|
}
|
||||||
|
await expectFailedWorkspaceOutsideWorking(page);
|
||||||
|
} finally {
|
||||||
|
console.log("WORKSPACE FRAMES", JSON.stringify(frames));
|
||||||
|
await page.screenshot({ path: test.info().outputPath("failed-sidebar.png") });
|
||||||
|
await workspace.cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -230,3 +230,22 @@ it("does not restore a targeted cached workspace while its archive is pending",
|
|||||||
store.clearSession(serverId);
|
store.clearSession(serverId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps the failed first turn out of Working when the creation response arrives last", () => {
|
||||||
|
const serverId = "failed-first-turn-creation";
|
||||||
|
const store = useSessionStore.getState();
|
||||||
|
store.initializeSession(serverId, null as unknown as DaemonClient);
|
||||||
|
try {
|
||||||
|
const replica = new WorkspaceDirectoryReplica(serverId);
|
||||||
|
const descriptor = workspace("failed-first-turn");
|
||||||
|
replica.applyDelta({ kind: "upsert", workspace: { ...descriptor, status: "running" } });
|
||||||
|
replica.applyDelta({ kind: "upsert", workspace: { ...descriptor, status: "failed" } });
|
||||||
|
// The new-workspace screen turns its older creation response into a running snapshot.
|
||||||
|
replica.acceptWorkspaces([normalizeWorkspaceDescriptor({ ...descriptor, status: "running" })]);
|
||||||
|
expect(
|
||||||
|
useSessionStore.getState().sessions[serverId]?.workspaces.get(descriptor.id)?.status,
|
||||||
|
).toBe("failed");
|
||||||
|
} finally {
|
||||||
|
store.clearSession(serverId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user