test(app): stabilize plan approval and script e2e
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user