From 6a3c64a033be250c7a1a18eb52ed5d71f6b5eedc Mon Sep 17 00:00:00 2001 From: nrslib <38722970+nrslib@users.noreply.github.com> Date: Wed, 4 Mar 2026 09:39:30 +0900 Subject: [PATCH] fix: stop iteration limit prompt and persist exceeded in interactive run --- .../pieceExecution-ask-user-question.test.ts | 41 +++++++++++++++++++ src/features/tasks/execute/pieceExecution.ts | 16 ++++---- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/__tests__/pieceExecution-ask-user-question.test.ts b/src/__tests__/pieceExecution-ask-user-question.test.ts index 81318a6..e321c0f 100644 --- a/src/__tests__/pieceExecution-ask-user-question.test.ts +++ b/src/__tests__/pieceExecution-ask-user-question.test.ts @@ -16,6 +16,7 @@ const { MockPieceEngine } = vi.hoisted(() => { class MockPieceEngine extends EE { static lastInstance: MockPieceEngine; + static triggerIterationLimit = false; readonly receivedOptions: Record; private readonly config: PieceConfig; @@ -30,6 +31,23 @@ const { MockPieceEngine } = vi.hoisted(() => { async run(): Promise<{ status: string; iteration: number }> { const firstStep = this.config.movements[0]; + if (MockPieceEngine.triggerIterationLimit) { + if (!firstStep) { + throw new Error('Test fixture requires at least one movement'); + } + const onIterationLimit = this.receivedOptions.onIterationLimit as + | ((request: { currentIteration: number; maxMovements: number; currentMovement: string }) => Promise) + | undefined; + if (onIterationLimit) { + await onIterationLimit({ + currentIteration: 1, + maxMovements: this.config.maxMovements, + currentMovement: firstStep.name, + }); + } + this.emit('piece:abort', { status: 'aborted', iteration: 1 }, 'Reached max movements'); + return { status: 'aborted', iteration: 1 }; + } if (firstStep) { this.emit('movement:start', firstStep, 1, firstStep.instructionTemplate, { provider: undefined, model: undefined }); } @@ -140,6 +158,7 @@ vi.mock('../shared/exitCodes.js', () => ({ })); import { executePiece } from '../features/tasks/execute/pieceExecution.js'; +import { selectOption } from '../shared/prompt/index.js'; function makeConfig(): PieceConfig { return { @@ -162,6 +181,7 @@ function makeConfig(): PieceConfig { describe('executePiece AskUserQuestion deny handler wiring', () => { beforeEach(() => { vi.clearAllMocks(); + MockPieceEngine.triggerIterationLimit = false; }); it('should pass onAskUserQuestion handler to PieceEngine', async () => { @@ -197,4 +217,25 @@ describe('executePiece AskUserQuestion deny handler wiring', () => { // Then: piece completes successfully expect(result.success).toBe(true); }); + + it('should mark exceeded without prompting even when interactiveUserInput is true', async () => { + // Given: mock engine reaches iteration limit immediately + MockPieceEngine.triggerIterationLimit = true; + + // When: executePiece runs in interactive mode + const result = await executePiece(makeConfig(), 'task', '/tmp/project', { + projectCwd: '/tmp/project', + interactiveUserInput: true, + }); + + // Then: no extension prompt appears; execution is marked as exceeded + expect(vi.mocked(selectOption)).not.toHaveBeenCalled(); + expect(result.success).toBe(false); + expect(result.exceeded).toBe(true); + expect(result.exceededInfo).toEqual({ + currentMovement: 'implement', + newMaxMovements: 10, + currentIteration: 1, + }); + }); }); diff --git a/src/features/tasks/execute/pieceExecution.ts b/src/features/tasks/execute/pieceExecution.ts index 2d107cb..ea6cdbb 100644 --- a/src/features/tasks/execute/pieceExecution.ts +++ b/src/features/tasks/execute/pieceExecution.ts @@ -139,15 +139,13 @@ export async function executePiece( out, displayRef, shouldNotifyIterationLimit, - !interactiveUserInput - ? (request) => { - exceededInfo = { - currentMovement: request.currentMovement, - newMaxMovements: request.maxMovements + pieceConfig.maxMovements, - currentIteration: request.currentIteration, - }; - } - : undefined, + (request) => { + exceededInfo = { + currentMovement: request.currentMovement, + newMaxMovements: request.maxMovements + pieceConfig.maxMovements, + currentIteration: request.currentIteration, + }; + }, ); const onUserInput = interactiveUserInput ? createUserInputHandler(out, displayRef) : undefined;