mirror of
https://github.com/getpaseo/paseo.git
synced 2026-08-14 12:23:16 +00:00
* Add Pi extension-launch plumbing for in-process tree primitives
* Add Rewind controls for agent sessions
* Preserve SDK checkpoint env var so Claude file rewind works
The SDK injects CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING=true when file checkpointing is enabled. The earlier parent-env stripping from c4782ec71c was too broad and removed that child flag along with real parent-session markers.
Keep stripping parent markers, but preserve the checkpoint flag so the SDK rewind path can run directly. With that boundary fixed, the JSONL file-rewind fallback is no longer needed.
* Update OpenCode history replay tests for rewind state
* Show Rewind actions in an adaptive sheet
* Fix rewind semantics across providers
* Refactor Pi IDs through extension capture
* Restore user message to composer after rewind
* Fix Claude rewind tool result mapping
* Fix Claude rewind session switch roundtrip
* Fix rewind and optimistic message regressions
* Tighten rewind test discipline
* Fix OpenCode optimistic message reconciliation
* Reconcile optimistic user messages by marker
* Reorder user message actions
* Unify provider user message contract
* Add Claude rewind flow Playwright contract
* Add Codex rewind flow Playwright contract
* Add OpenCode rewind flow Playwright contract
* Add Pi rewind flow Playwright contract
* Remove real-provider rewind Playwright contracts
* Gate real-provider rewind specs from CI Playwright
93 lines
3.2 KiB
TypeScript
93 lines
3.2 KiB
TypeScript
import { expect, test, type Page } from "./fixtures";
|
|
import { buildHostWorkspaceRoute } from "@/utils/host-routes";
|
|
import { connectTerminalClient } from "./helpers/terminal-perf";
|
|
import { createTempGitRepo } from "./helpers/workspace";
|
|
import {
|
|
composerLocator,
|
|
expectComposerEditable,
|
|
expectComposerVisible,
|
|
fillComposerDraft,
|
|
submitMessage,
|
|
} from "./helpers/composer";
|
|
|
|
function getServerId(): string {
|
|
const serverId = process.env.E2E_SERVER_ID;
|
|
if (!serverId) {
|
|
throw new Error("E2E_SERVER_ID is not set.");
|
|
}
|
|
return serverId;
|
|
}
|
|
|
|
async function openAgent(page: Page, input: { cwd: string; agentId: string }): Promise<void> {
|
|
const agentUrl = `${buildHostWorkspaceRoute(
|
|
getServerId(),
|
|
input.cwd,
|
|
)}?open=${encodeURIComponent(`agent:${input.agentId}`)}`;
|
|
await page.goto(agentUrl);
|
|
await page.waitForURL(
|
|
(url) => url.pathname.includes("/workspace/") && !url.searchParams.has("open"),
|
|
{ timeout: 60_000 },
|
|
);
|
|
await expectComposerVisible(page);
|
|
}
|
|
|
|
async function expectUserMessageCount(page: Page, expected: number): Promise<void> {
|
|
await expect(page.getByTestId("user-message")).toHaveCount(expected, { timeout: 15_000 });
|
|
}
|
|
|
|
async function expectIdleComposer(page: Page): Promise<void> {
|
|
await expectComposerEditable(page);
|
|
await expect(page.getByRole("button", { name: /stop|cancel/i })).toHaveCount(0, {
|
|
timeout: 15_000,
|
|
});
|
|
}
|
|
|
|
async function expectNoLoadingRegressionAfterIdle(page: Page): Promise<void> {
|
|
await expectIdleComposer(page);
|
|
await page.waitForTimeout(1_000);
|
|
await expectIdleComposer(page);
|
|
}
|
|
|
|
test.describe("User message UI contract", () => {
|
|
test("dedupes mock provider user_message echoes across multi-turn sends", async ({ page }) => {
|
|
const repo = await createTempGitRepo("user-message-contract-e2e-");
|
|
const client = await connectTerminalClient();
|
|
const prompts = [
|
|
"emit 1 coalesced agent stream updates for user message contract turn one.",
|
|
"emit 1 coalesced agent stream updates for user message contract turn two.",
|
|
"emit 1 coalesced agent stream updates for user message contract turn three.",
|
|
];
|
|
|
|
try {
|
|
await client.openProject(repo.path);
|
|
const agent = await client.createAgent({
|
|
provider: "mock",
|
|
cwd: repo.path,
|
|
title: "User message contract e2e",
|
|
modeId: "load-test",
|
|
model: "ten-second-stream",
|
|
});
|
|
await openAgent(page, { cwd: repo.path, agentId: agent.id });
|
|
|
|
for (let index = 0; index < prompts.length; index += 1) {
|
|
const prompt = prompts[index]!;
|
|
await submitMessage(page, prompt);
|
|
await expect(page.getByText(prompt, { exact: true })).toBeVisible({ timeout: 15_000 });
|
|
await expect(page.getByText("stress-update-0", { exact: true }).first()).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
await expectUserMessageCount(page, index + 1);
|
|
await expectNoLoadingRegressionAfterIdle(page);
|
|
}
|
|
|
|
await fillComposerDraft(page, "append");
|
|
await composerLocator(page).evaluate((element) => element.blur());
|
|
await expectUserMessageCount(page, 3);
|
|
await expectIdleComposer(page);
|
|
} finally {
|
|
await client.close();
|
|
await repo.cleanup();
|
|
}
|
|
});
|
|
});
|