From ed70827f39b15b4627bde0ecce1077f9c0429252 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 1 May 2026 13:02:50 +0700 Subject: [PATCH] test(app): stabilize plan approval and script e2e --- packages/app/e2e/codex-plan-approval.spec.ts | 10 +- packages/app/e2e/global-setup.ts | 1 + .../app/e2e/workspace-setup-streaming.spec.ts | 2 +- .../agent/providers/mock-load-test-agent.ts | 98 +++++++++++++++++-- 4 files changed, 98 insertions(+), 13 deletions(-) diff --git a/packages/app/e2e/codex-plan-approval.spec.ts b/packages/app/e2e/codex-plan-approval.spec.ts index 61769afef..b6e20ab07 100644 --- a/packages/app/e2e/codex-plan-approval.spec.ts +++ b/packages/app/e2e/codex-plan-approval.spec.ts @@ -28,14 +28,12 @@ test.describe("Codex plan approval", () => { } const agent = await client.createAgent({ - provider: "codex", + provider: "mock", cwd: repo.path, title: "Codex plan approval e2e", - model: "gpt-5.4-mini", - thinkingOptionId: "low", - featureValues: { plan_mode: true, fast_mode: true }, - initialPrompt: - "Create a concise plan to add a README note. Present the plan only and wait for approval.", + modeId: "load-test", + model: "ten-second-stream", + initialPrompt: "Emit synthetic plan approval.", }); const agentUrl = `${buildHostWorkspaceRoute( diff --git a/packages/app/e2e/global-setup.ts b/packages/app/e2e/global-setup.ts index 554835e32..ca192fbba 100644 --- a/packages/app/e2e/global-setup.ts +++ b/packages/app/e2e/global-setup.ts @@ -585,6 +585,7 @@ function startDaemon(args: DaemonSpawnArgs): ChildProcess { PASEO_CORS_ORIGINS: `http://localhost:${args.metroPort}`, PASEO_DICTATION_ENABLED: openAiUsable ? "1" : "0", PASEO_VOICE_MODE_ENABLED: openAiUsable ? "1" : "0", + PASEO_NODE_ENV: "development", ...(openAiUsable ? { PASEO_DICTATION_STT_PROVIDER: "openai", diff --git a/packages/app/e2e/workspace-setup-streaming.spec.ts b/packages/app/e2e/workspace-setup-streaming.spec.ts index 54d122830..eafad842e 100644 --- a/packages/app/e2e/workspace-setup-streaming.spec.ts +++ b/packages/app/e2e/workspace-setup-streaming.spec.ts @@ -279,7 +279,7 @@ test.describe("Workspace setup streaming", () => { await page.getByTestId("workspace-scripts-button").click(); await expect(page.getByTestId("workspace-scripts-menu")).toBeVisible({ timeout: 10_000 }); 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(); await expect(terminalTab).toBeVisible({ timeout: 30_000 }); diff --git a/packages/server/src/server/agent/providers/mock-load-test-agent.ts b/packages/server/src/server/agent/providers/mock-load-test-agent.ts index bbaf005ea..1e44aa3a3 100644 --- a/packages/server/src/server/agent/providers/mock-load-test-agent.ts +++ b/packages/server/src/server/agent/providers/mock-load-test-agent.ts @@ -99,6 +99,10 @@ interface AgentStreamStressRequest { 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): { modelId: string; durationMs: number; @@ -288,6 +292,7 @@ export class MockLoadTestAgentSession implements AgentSession { private readonly history: AgentStreamEvent[] = []; private readonly logger?: Logger; private activeTurn: ActiveTurn | null = null; + private pendingPermissions = new Map(); private modeId: string | null; private modelId: string | null; @@ -336,7 +341,9 @@ export class MockLoadTestAgentSession implements AgentSession { const largePayload = parseLargeAgentStreamPayloadPrompt(prompt); const stress = parseAgentStreamStressPrompt(prompt); - if (largePayload) { + if (shouldEmitPlanApprovalPrompt(prompt)) { + this.schedulePlanApprovalTurn(turn); + } else if (largePayload) { this.scheduleLargePayloadTurn(turn, largePayload); } else if (stress) { this.scheduleStressTurn(turn, stress); @@ -381,13 +388,29 @@ export class MockLoadTestAgentSession implements AgentSession { } getPendingPermissions(): AgentPermissionRequest[] { - return []; + return Array.from(this.pendingPermissions.values()); } async respondToPermission( - _requestId: string, - _response: AgentPermissionResponse, + requestId: string, + response: AgentPermissionResponse, ): Promise { + 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; } @@ -457,6 +480,65 @@ export class MockLoadTestAgentSession implements AgentSession { 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 { if (this.activeTurn !== turn) { return; @@ -695,11 +777,15 @@ export class MockLoadTestAgentSession implements AgentSession { } private finishTurn(turn: ActiveTurn): void { - this.activeTurn = null; this.emitTimeline(turn.turnId, { type: "assistant_message", 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 = { inputTokens: turn.iteration * 32, outputTokens: turn.iteration * 128, @@ -714,7 +800,7 @@ export class MockLoadTestAgentSession implements AgentSession { }); turn.resolve({ sessionId: this.id, - finalText: "Synthetic load test complete", + finalText, usage, timeline: [], canceled: false,