fix(app): advance multi-question prompts step by step (#1462)
This commit is contained in:
@@ -11,6 +11,20 @@ import {
|
||||
MockLoadTestAgentClient,
|
||||
} from "./mock-load-test-agent.js";
|
||||
|
||||
type PermissionRequestedEvent = Extract<AgentStreamEvent, { type: "permission_requested" }>;
|
||||
|
||||
function expectSinglePermissionRequest(events: AgentStreamEvent[]): PermissionRequestedEvent {
|
||||
const permissions = events.filter(
|
||||
(event): event is PermissionRequestedEvent => event.type === "permission_requested",
|
||||
);
|
||||
expect(permissions).toHaveLength(1);
|
||||
const [permission] = permissions;
|
||||
if (!permission) {
|
||||
throw new Error("permission request missing");
|
||||
}
|
||||
return permission;
|
||||
}
|
||||
|
||||
describe("MockLoadTestAgentClient", () => {
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
@@ -131,6 +145,60 @@ describe("MockLoadTestAgentClient", () => {
|
||||
expect(events).toHaveLength(eventCountAfterInterrupt);
|
||||
});
|
||||
|
||||
test("emits the free-write question scenario selected by prompt", async () => {
|
||||
vi.useFakeTimers();
|
||||
const client = new MockLoadTestAgentClient();
|
||||
const session = await client.createSession({
|
||||
provider: "mock",
|
||||
cwd: process.cwd(),
|
||||
model: "ten-second-stream",
|
||||
});
|
||||
const events: AgentStreamEvent[] = [];
|
||||
const unsubscribe = session.subscribe((event) => events.push(event));
|
||||
|
||||
const resultPromise = session.run("Emit synthetic questions: two free-write questions.");
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
|
||||
const permission = expectSinglePermissionRequest(events);
|
||||
expect(permission.request).toMatchObject({
|
||||
provider: "mock",
|
||||
name: "MockQuestions",
|
||||
kind: "question",
|
||||
input: {
|
||||
questions: [
|
||||
{
|
||||
question: "What is the GitHub private repo URL to push to?",
|
||||
header: "repoUrl",
|
||||
options: [],
|
||||
multiSelect: false,
|
||||
},
|
||||
{
|
||||
question: "What should the first commit message be?",
|
||||
header: "commitMessage",
|
||||
options: [],
|
||||
multiSelect: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await session.respondToPermission(permission.request.id, {
|
||||
behavior: "allow",
|
||||
updatedInput: {
|
||||
answers: {
|
||||
repoUrl: "git@github.com:user/private-repo.git",
|
||||
commitMessage: "Initialize private repo",
|
||||
},
|
||||
},
|
||||
});
|
||||
await expect(resultPromise).resolves.toMatchObject({
|
||||
sessionId: session.id,
|
||||
finalText: "Synthetic questions resolved",
|
||||
canceled: false,
|
||||
});
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
test("agent manager coalesces adjacent assistant tokens into fewer messages", async () => {
|
||||
vi.useFakeTimers();
|
||||
const workdir = mkdtempSync(join(tmpdir(), "paseo-mock-load-test-"));
|
||||
|
||||
@@ -127,12 +127,80 @@ interface AgentStreamStressRequest {
|
||||
coalesced: boolean;
|
||||
}
|
||||
|
||||
interface MockQuestionOption {
|
||||
label: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface MockQuestionPromptQuestion {
|
||||
question: string;
|
||||
header: string;
|
||||
options: MockQuestionOption[];
|
||||
multiSelect: boolean;
|
||||
allowOther?: boolean;
|
||||
allowEmpty?: boolean;
|
||||
placeholder?: string;
|
||||
dismissLabel?: string;
|
||||
}
|
||||
|
||||
interface MockQuestionPromptRequest {
|
||||
questions: MockQuestionPromptQuestion[];
|
||||
}
|
||||
|
||||
function shouldEmitPlanApprovalPrompt(prompt: AgentPromptInput): boolean {
|
||||
return /emit\s+(?:a\s+)?synthetic\s+plan\s+approval/i.test(promptToText(prompt));
|
||||
}
|
||||
|
||||
function shouldEmitQuestionPrompt(prompt: AgentPromptInput): boolean {
|
||||
return /emit\s+(?:a\s+)?synthetic\s+questions?/i.test(promptToText(prompt));
|
||||
function parseMockQuestionPrompt(prompt: AgentPromptInput): MockQuestionPromptRequest | null {
|
||||
const text = promptToText(prompt);
|
||||
if (!/emit\s+(?:a\s+)?synthetic\s+questions?/i.test(text)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (/free[-\s]?write|freeform|text[-\s]?only/i.test(text)) {
|
||||
return {
|
||||
questions: [
|
||||
{
|
||||
question: "What is the GitHub private repo URL to push to?",
|
||||
header: "repoUrl",
|
||||
options: [],
|
||||
multiSelect: false,
|
||||
placeholder: "git@github.com:user/repo.git",
|
||||
},
|
||||
{
|
||||
question: "What should the first commit message be?",
|
||||
header: "commitMessage",
|
||||
options: [],
|
||||
multiSelect: false,
|
||||
placeholder: "Initial commit",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
questions: [
|
||||
{
|
||||
question: "Which surface should this apply to?",
|
||||
header: "surface",
|
||||
options: [{ label: "App" }, { label: "Desktop" }],
|
||||
multiSelect: false,
|
||||
},
|
||||
{
|
||||
question: "Which rollout should we use?",
|
||||
header: "rollout",
|
||||
options: [{ label: "Immediately" }, { label: "Behind feature flag" }],
|
||||
multiSelect: false,
|
||||
},
|
||||
{
|
||||
question: "What success criteria should we use?",
|
||||
header: "success",
|
||||
options: [],
|
||||
multiSelect: false,
|
||||
placeholder: "Describe success...",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function resolveModelProfile(modelId: string | null | undefined): {
|
||||
@@ -536,10 +604,11 @@ export class MockLoadTestAgentSession implements AgentSession {
|
||||
|
||||
const largePayload = parseLargeAgentStreamPayloadPrompt(prompt);
|
||||
const stress = parseAgentStreamStressPrompt(prompt);
|
||||
const questionPrompt = parseMockQuestionPrompt(prompt);
|
||||
if (shouldEmitPlanApprovalPrompt(prompt)) {
|
||||
this.schedulePlanApprovalTurn(turn);
|
||||
} else if (shouldEmitQuestionPrompt(prompt)) {
|
||||
this.scheduleQuestionPromptTurn(turn);
|
||||
} else if (questionPrompt) {
|
||||
this.scheduleQuestionPromptTurn(turn, questionPrompt);
|
||||
} else if (largePayload) {
|
||||
this.scheduleLargePayloadTurn(turn, largePayload);
|
||||
} else if (stress) {
|
||||
@@ -712,9 +781,12 @@ export class MockLoadTestAgentSession implements AgentSession {
|
||||
turn.timer.unref?.();
|
||||
}
|
||||
|
||||
private scheduleQuestionPromptTurn(turn: ActiveTurn): void {
|
||||
private scheduleQuestionPromptTurn(
|
||||
turn: ActiveTurn,
|
||||
questionPrompt: MockQuestionPromptRequest,
|
||||
): void {
|
||||
turn.timer = setTimeout(() => {
|
||||
this.emitQuestionPromptTurn(turn);
|
||||
this.emitQuestionPromptTurn(turn, questionPrompt);
|
||||
}, 0);
|
||||
turn.timer.unref?.();
|
||||
}
|
||||
@@ -771,7 +843,10 @@ export class MockLoadTestAgentSession implements AgentSession {
|
||||
});
|
||||
}
|
||||
|
||||
private emitQuestionPromptTurn(turn: ActiveTurn): void {
|
||||
private emitQuestionPromptTurn(
|
||||
turn: ActiveTurn,
|
||||
questionPrompt: MockQuestionPromptRequest,
|
||||
): void {
|
||||
if (this.activeTurn !== turn) {
|
||||
return;
|
||||
}
|
||||
@@ -790,27 +865,7 @@ export class MockLoadTestAgentSession implements AgentSession {
|
||||
kind: "question",
|
||||
title: "Questions",
|
||||
input: {
|
||||
questions: [
|
||||
{
|
||||
question: "Which surface should this apply to?",
|
||||
header: "surface",
|
||||
options: [{ label: "App" }, { label: "Desktop" }],
|
||||
multiSelect: false,
|
||||
},
|
||||
{
|
||||
question: "Which rollout should we use?",
|
||||
header: "rollout",
|
||||
options: [{ label: "Immediately" }, { label: "Behind feature flag" }],
|
||||
multiSelect: false,
|
||||
},
|
||||
{
|
||||
question: "What success criteria should we use?",
|
||||
header: "success",
|
||||
options: [],
|
||||
multiSelect: false,
|
||||
placeholder: "Describe success...",
|
||||
},
|
||||
],
|
||||
questions: questionPrompt.questions,
|
||||
},
|
||||
metadata: {
|
||||
source: "mock_questions",
|
||||
|
||||
Reference in New Issue
Block a user