test(app): stabilize plan approval and script e2e

This commit is contained in:
Mohamed Boudra
2026-05-01 13:02:50 +07:00
parent 111935f4eb
commit ed70827f39
4 changed files with 98 additions and 13 deletions
+4 -6
View File
@@ -28,14 +28,12 @@ test.describe("Codex plan approval", () => {
} }
const agent = await client.createAgent({ const agent = await client.createAgent({
provider: "codex", provider: "mock",
cwd: repo.path, cwd: repo.path,
title: "Codex plan approval e2e", title: "Codex plan approval e2e",
model: "gpt-5.4-mini", modeId: "load-test",
thinkingOptionId: "low", model: "ten-second-stream",
featureValues: { plan_mode: true, fast_mode: true }, initialPrompt: "Emit synthetic plan approval.",
initialPrompt:
"Create a concise plan to add a README note. Present the plan only and wait for approval.",
}); });
const agentUrl = `${buildHostWorkspaceRoute( const agentUrl = `${buildHostWorkspaceRoute(
+1
View File
@@ -585,6 +585,7 @@ function startDaemon(args: DaemonSpawnArgs): ChildProcess {
PASEO_CORS_ORIGINS: `http://localhost:${args.metroPort}`, PASEO_CORS_ORIGINS: `http://localhost:${args.metroPort}`,
PASEO_DICTATION_ENABLED: openAiUsable ? "1" : "0", PASEO_DICTATION_ENABLED: openAiUsable ? "1" : "0",
PASEO_VOICE_MODE_ENABLED: openAiUsable ? "1" : "0", PASEO_VOICE_MODE_ENABLED: openAiUsable ? "1" : "0",
PASEO_NODE_ENV: "development",
...(openAiUsable ...(openAiUsable
? { ? {
PASEO_DICTATION_STT_PROVIDER: "openai", PASEO_DICTATION_STT_PROVIDER: "openai",
@@ -279,7 +279,7 @@ test.describe("Workspace setup streaming", () => {
await page.getByTestId("workspace-scripts-button").click(); await page.getByTestId("workspace-scripts-button").click();
await expect(page.getByTestId("workspace-scripts-menu")).toBeVisible({ timeout: 10_000 }); await expect(page.getByTestId("workspace-scripts-menu")).toBeVisible({ timeout: 10_000 });
await page.getByTestId("workspace-scripts-start-web").click(); await page.getByTestId("workspace-scripts-start-web").click();
await page.keyboard.press("Escape"); await page.getByTestId("workspace-scripts-menu-backdrop").click();
const terminalTab = page.locator('[data-testid^="workspace-tab-terminal_"]').first(); const terminalTab = page.locator('[data-testid^="workspace-tab-terminal_"]').first();
await expect(terminalTab).toBeVisible({ timeout: 30_000 }); await expect(terminalTab).toBeVisible({ timeout: 30_000 });
@@ -99,6 +99,10 @@ interface AgentStreamStressRequest {
coalesced: boolean; coalesced: boolean;
} }
function shouldEmitPlanApprovalPrompt(prompt: AgentPromptInput): boolean {
return /emit\s+(?:a\s+)?synthetic\s+plan\s+approval/i.test(promptToText(prompt));
}
function resolveModelProfile(modelId: string | null | undefined): { function resolveModelProfile(modelId: string | null | undefined): {
modelId: string; modelId: string;
durationMs: number; durationMs: number;
@@ -288,6 +292,7 @@ export class MockLoadTestAgentSession implements AgentSession {
private readonly history: AgentStreamEvent[] = []; private readonly history: AgentStreamEvent[] = [];
private readonly logger?: Logger; private readonly logger?: Logger;
private activeTurn: ActiveTurn | null = null; private activeTurn: ActiveTurn | null = null;
private pendingPermissions = new Map<string, AgentPermissionRequest>();
private modeId: string | null; private modeId: string | null;
private modelId: string | null; private modelId: string | null;
@@ -336,7 +341,9 @@ export class MockLoadTestAgentSession implements AgentSession {
const largePayload = parseLargeAgentStreamPayloadPrompt(prompt); const largePayload = parseLargeAgentStreamPayloadPrompt(prompt);
const stress = parseAgentStreamStressPrompt(prompt); const stress = parseAgentStreamStressPrompt(prompt);
if (largePayload) { if (shouldEmitPlanApprovalPrompt(prompt)) {
this.schedulePlanApprovalTurn(turn);
} else if (largePayload) {
this.scheduleLargePayloadTurn(turn, largePayload); this.scheduleLargePayloadTurn(turn, largePayload);
} else if (stress) { } else if (stress) {
this.scheduleStressTurn(turn, stress); this.scheduleStressTurn(turn, stress);
@@ -381,13 +388,29 @@ export class MockLoadTestAgentSession implements AgentSession {
} }
getPendingPermissions(): AgentPermissionRequest[] { getPendingPermissions(): AgentPermissionRequest[] {
return []; return Array.from(this.pendingPermissions.values());
} }
async respondToPermission( async respondToPermission(
_requestId: string, requestId: string,
_response: AgentPermissionResponse, response: AgentPermissionResponse,
): Promise<AgentPermissionResult | void> { ): Promise<AgentPermissionResult | void> {
if (!this.pendingPermissions.delete(requestId)) {
return undefined;
}
const turn = this.activeTurn;
this.emit({
type: "permission_resolved",
provider: this.provider,
requestId,
resolution: response,
...(turn ? { turnId: turn.turnId } : {}),
});
if (turn) {
this.finishTurnWithText(turn, "Synthetic plan approval resolved");
}
return undefined; return undefined;
} }
@@ -457,6 +480,65 @@ export class MockLoadTestAgentSession implements AgentSession {
turn.timer.unref?.(); turn.timer.unref?.();
} }
private schedulePlanApprovalTurn(turn: ActiveTurn): void {
turn.timer = setTimeout(() => {
this.emitPlanApprovalTurn(turn);
}, 0);
turn.timer.unref?.();
}
private emitPlanApprovalTurn(turn: ActiveTurn): void {
if (this.activeTurn !== turn) {
return;
}
this.clearTurnTimer(turn);
this.emit({
type: "turn_started",
provider: this.provider,
turnId: turn.turnId,
});
const request: AgentPermissionRequest = {
id: `mock-plan-${turn.turnId}`,
provider: this.provider,
name: "MockPlanApproval",
kind: "plan",
title: "Plan",
description: "Review the proposed plan before implementation starts.",
input: {
plan: "1. Add the README note.\n2. Keep the change scoped.\n3. Verify the diff.",
},
actions: [
{
id: "implement",
label: "Implement",
behavior: "allow",
variant: "primary",
intent: "implement",
},
{
id: "dismiss",
label: "Dismiss",
behavior: "deny",
variant: "secondary",
intent: "dismiss",
},
],
metadata: {
source: "mock_plan_approval",
},
};
this.pendingPermissions.set(request.id, request);
this.emit({
type: "permission_requested",
provider: this.provider,
request,
turnId: turn.turnId,
});
}
private emitStressTurn(turn: ActiveTurn, stress: AgentStreamStressRequest): void { private emitStressTurn(turn: ActiveTurn, stress: AgentStreamStressRequest): void {
if (this.activeTurn !== turn) { if (this.activeTurn !== turn) {
return; return;
@@ -695,11 +777,15 @@ export class MockLoadTestAgentSession implements AgentSession {
} }
private finishTurn(turn: ActiveTurn): void { private finishTurn(turn: ActiveTurn): void {
this.activeTurn = null;
this.emitTimeline(turn.turnId, { this.emitTimeline(turn.turnId, {
type: "assistant_message", type: "assistant_message",
text: "## Synthetic load test complete\n\nThe mock provider finished its foreground turn.\n", text: "## Synthetic load test complete\n\nThe mock provider finished its foreground turn.\n",
}); });
this.finishTurnWithText(turn, "Synthetic load test complete");
}
private finishTurnWithText(turn: ActiveTurn, finalText: string): void {
this.activeTurn = null;
const usage = { const usage = {
inputTokens: turn.iteration * 32, inputTokens: turn.iteration * 32,
outputTokens: turn.iteration * 128, outputTokens: turn.iteration * 128,
@@ -714,7 +800,7 @@ export class MockLoadTestAgentSession implements AgentSession {
}); });
turn.resolve({ turn.resolve({
sessionId: this.id, sessionId: this.id,
finalText: "Synthetic load test complete", finalText,
usage, usage,
timeline: [], timeline: [],
canceled: false, canceled: false,