@@ -78,6 +78,7 @@ describe("executeCommandTool", () => {
7878 } ,
7979 recordToolUsage : vitest . fn ( ) . mockReturnValue ( { } as ToolUsage ) ,
8080 recordToolError : vitest . fn ( ) ,
81+ supersedePendingAsk : vitest . fn ( ) ,
8182 providerRef : {
8283 deref : vitest . fn ( ) . mockResolvedValue ( {
8384 getState : vitest . fn ( ) . mockResolvedValue ( {
@@ -357,7 +358,12 @@ describe("executeCommandTool", () => {
357358 const processPromise = new Promise < void > ( ( resolve ) => {
358359 state . resolveProcess = resolve
359360 } )
360- state . proc = Object . assign ( processPromise , { continue : vitest . fn ( ) , abort : vitest . fn ( ) } )
361+ // Mirror real terminal behavior: continue() resolves the wait early
362+ // while the command keeps running in the background.
363+ state . proc = Object . assign ( processPromise , {
364+ continue : vitest . fn ( ( ) => state . resolveProcess ( ) ) ,
365+ abort : vitest . fn ( ) ,
366+ } )
361367 ; ( TerminalRegistry . getOrCreateTerminal as ReturnType < typeof vitest . fn > ) . mockResolvedValue ( {
362368 runCommand : vitest . fn ( ( _cmd : string , callbacks : RooTerminalCallbacks ) => {
363369 state . callbacks = callbacks
@@ -480,16 +486,17 @@ describe("executeCommandTool", () => {
480486
481487 it ( "re-anchors a pending ask when execution start is reported after early output" , async ( ) => {
482488 vitest . useFakeTimers ( )
489+ mockCline . ask . mockResolvedValue ( { response : "messageResponse" , text : "keep going" , images : undefined } )
483490 const terminal = await setupControllableTerminal ( )
484491
485- const handlePromise = handleCommand ( "echo hello " )
492+ const handlePromise = handleCommand ( "sleep 60 " )
486493
487494 await vitest . waitFor ( ( ) => expect ( terminal . callbacks ) . toBeDefined ( ) )
488495 const callbacks = terminal . callbacks !
489496 const proc = terminal . proc as unknown as RooTerminalProcess
490497
491498 // Output arrives before the execution-started event (defensive case).
492- await callbacks . onLine ( "hello \n" , proc )
499+ await callbacks . onLine ( "working... \n" , proc )
493500 await vitest . advanceTimersByTimeAsync ( executeCommandModule . COMMAND_OUTPUT_ASK_DELAY_MS - 2_000 )
494501 callbacks . onShellExecutionStarted ! ( 1234 , proc )
495502
@@ -498,19 +505,23 @@ describe("executeCommandTool", () => {
498505 await vitest . advanceTimersByTimeAsync ( 2_500 )
499506 expect ( mockCline . ask ) . not . toHaveBeenCalled ( )
500507
501- await callbacks . onCompleted ! ( "hello\n" , proc )
508+ // The ask must still fire at the re-anchored deadline — a version
509+ // that cleared the old timer without rescheduling would fail here.
510+ await vitest . advanceTimersByTimeAsync ( 2_500 )
511+ expect ( mockCline . ask ) . toHaveBeenCalledWith ( "command_output" , "" )
512+
513+ // Let the command finish so the tool can resolve.
514+ await callbacks . onCompleted ! ( "working...\n" , proc )
502515 callbacks . onShellExecutionComplete ! ( { exitCode : 0 } , proc )
503- terminal . resolveProcess ( )
504- await vitest . advanceTimersByTimeAsync ( executeCommandModule . COMMAND_OUTPUT_ASK_DELAY_MS + 1_000 )
516+ await vitest . advanceTimersByTimeAsync ( 100 )
505517
506518 await handlePromise
507519
508- expect ( mockCline . ask ) . not . toHaveBeenCalled ( )
520+ expect ( mockPushToolResult ) . toHaveBeenCalled ( )
509521 } )
510522
511523 it ( "cancels a pending ask when the agent timeout moves the command to the background" , async ( ) => {
512524 vitest . useFakeTimers ( )
513- mockCline . supersedePendingAsk = vitest . fn ( )
514525 const terminal = await setupControllableTerminal ( )
515526
516527 const handlePromise = handleCommand ( "npm run dev" , 2 )
@@ -594,7 +605,7 @@ describe("executeCommandTool", () => {
594605 expect ( mockPushToolResult . mock . calls [ 0 ] [ 0 ] ) . toContain ( "Exit code: 0" )
595606 } )
596607
597- it ( "does not continue the process when the ask is answered without a message" , async ( ) => {
608+ it ( "resolves before completion when the ask is answered without a message" , async ( ) => {
598609 vitest . useFakeTimers ( )
599610 mockCline . ask . mockResolvedValue ( { response : "yesButtonClicked" , text : undefined , images : undefined } )
600611 const terminal = await setupControllableTerminal ( )
@@ -609,17 +620,50 @@ describe("executeCommandTool", () => {
609620 await callbacks . onLine ( "working...\n" , proc )
610621 await vitest . advanceTimersByTimeAsync ( executeCommandModule . COMMAND_OUTPUT_ASK_DELAY_MS )
611622
623+ // Any ask answer backgrounds the command: the process is continued and
624+ // the tool resolves without waiting for the command to complete.
625+ // Note the process promise is never resolved in this test.
626+ await vitest . advanceTimersByTimeAsync ( 100 )
627+ await handlePromise
628+
612629 expect ( mockCline . ask ) . toHaveBeenCalledWith ( "command_output" , "" )
613- expect ( terminal . proc . continue ) . not . toHaveBeenCalled ( )
630+ expect ( terminal . proc . continue ) . toHaveBeenCalled ( )
631+ expect ( mockPushToolResult ) . toHaveBeenCalled ( )
632+ expect ( mockPushToolResult . mock . calls [ 0 ] [ 0 ] ) . toContain ( "still running" )
633+
634+ // Cleanup: let the command finish.
635+ await callbacks . onCompleted ! ( "working...\n" , proc )
636+ callbacks . onShellExecutionComplete ! ( { exitCode : 0 } , proc )
637+ } )
638+
639+ it ( "supersedes a pending ask when the command completes" , async ( ) => {
640+ vitest . useFakeTimers ( )
641+ mockCline . ask . mockReturnValue ( new Promise ( ( ) => { } ) )
642+ const terminal = await setupControllableTerminal ( )
643+
644+ const handlePromise = handleCommand ( "sleep 5" )
645+
646+ await vitest . waitFor ( ( ) => expect ( terminal . callbacks ) . toBeDefined ( ) )
647+ const callbacks = terminal . callbacks !
648+ const proc = terminal . proc as unknown as RooTerminalProcess
614649
650+ callbacks . onShellExecutionStarted ! ( 1234 , proc )
651+ await callbacks . onLine ( "working...\n" , proc )
652+ await vitest . advanceTimersByTimeAsync ( executeCommandModule . COMMAND_OUTPUT_ASK_DELAY_MS )
653+
654+ expect ( mockCline . ask ) . toHaveBeenCalledWith ( "command_output" , "" )
655+
656+ // The command completes while the ask is still pending.
615657 await callbacks . onCompleted ! ( "working...\n" , proc )
616658 callbacks . onShellExecutionComplete ! ( { exitCode : 0 } , proc )
617659 terminal . resolveProcess ( )
618660 await vitest . advanceTimersByTimeAsync ( 100 )
619661
620662 await handlePromise
621663
664+ expect ( mockCline . supersedePendingAsk ) . toHaveBeenCalled ( )
622665 expect ( mockPushToolResult ) . toHaveBeenCalled ( )
666+ expect ( mockPushToolResult . mock . calls [ 0 ] [ 0 ] ) . toContain ( "Exit code: 0" )
623667 } )
624668 } )
625669} )
0 commit comments