@@ -54,6 +54,7 @@ describe("executeCommand", () => {
5454 } ,
5555 say : vitest . fn ( ) . mockResolvedValue ( undefined ) ,
5656 terminalProcess : undefined ,
57+ emit : vitest . fn ( ) ,
5758 }
5859
5960 // Create mock process that resolves immediately
@@ -471,4 +472,217 @@ describe("executeCommand", () => {
471472 expect ( mockTerminalInstance . getCurrentWorkingDirectory ) . toHaveBeenCalled ( )
472473 } )
473474 } )
475+
476+ describe ( "taskCommandExecuted Event" , ( ) => {
477+ it ( "should emit taskCommandExecuted event when command completes successfully" , async ( ) => {
478+ mockTerminal . getCurrentWorkingDirectory . mockReturnValue ( "/test/project" )
479+
480+ // We need to mock Terminal.compressTerminalOutput since that's what sets the result
481+ const mockCompressTerminalOutput = vitest . spyOn ( Terminal , "compressTerminalOutput" )
482+ mockCompressTerminalOutput . mockReturnValue ( "Command output" )
483+
484+ mockTerminal . runCommand . mockImplementation ( ( command : string , callbacks : RooTerminalCallbacks ) => {
485+ // Simulate async callback execution
486+ setTimeout ( ( ) => {
487+ callbacks . onShellExecutionStarted ( 1234 , mockProcess )
488+ callbacks . onCompleted ( "Command output" , mockProcess )
489+ callbacks . onShellExecutionComplete ( { exitCode : 0 } , mockProcess )
490+ } , 0 )
491+ return mockProcess
492+ } )
493+
494+ const options : ExecuteCommandOptions = {
495+ executionId : "test-123" ,
496+ command : "echo test" ,
497+ terminalShellIntegrationDisabled : false ,
498+ terminalOutputLineLimit : 500 ,
499+ }
500+
501+ // Execute
502+ const [ rejected , result ] = await executeCommand ( mockTask , options )
503+
504+ // Verify
505+ expect ( rejected ) . toBe ( false )
506+ expect ( mockTask . emit ) . toHaveBeenCalledWith ( "taskCommandExecuted" , mockTask . taskId , {
507+ command : "echo test" ,
508+ exitCode : 0 ,
509+ output : "Command output" ,
510+ succeeded : true ,
511+ failureReason : undefined ,
512+ } )
513+
514+ mockCompressTerminalOutput . mockRestore ( )
515+ } )
516+
517+ it ( "should emit taskCommandExecuted event when command fails with non-zero exit code" , async ( ) => {
518+ mockTerminal . getCurrentWorkingDirectory . mockReturnValue ( "/test/project" )
519+
520+ const mockCompressTerminalOutput = vitest . spyOn ( Terminal , "compressTerminalOutput" )
521+ mockCompressTerminalOutput . mockReturnValue ( "Error output" )
522+
523+ mockTerminal . runCommand . mockImplementation ( ( command : string , callbacks : RooTerminalCallbacks ) => {
524+ setTimeout ( ( ) => {
525+ callbacks . onShellExecutionStarted ( 1234 , mockProcess )
526+ callbacks . onCompleted ( "Error output" , mockProcess )
527+ callbacks . onShellExecutionComplete ( { exitCode : 1 } , mockProcess )
528+ } , 0 )
529+ return mockProcess
530+ } )
531+
532+ const options : ExecuteCommandOptions = {
533+ executionId : "test-123" ,
534+ command : "exit 1" ,
535+ terminalShellIntegrationDisabled : false ,
536+ terminalOutputLineLimit : 500 ,
537+ }
538+
539+ // Execute
540+ const [ rejected , result ] = await executeCommand ( mockTask , options )
541+
542+ // Verify
543+ expect ( rejected ) . toBe ( false )
544+ expect ( mockTask . emit ) . toHaveBeenCalledWith ( "taskCommandExecuted" , mockTask . taskId , {
545+ command : "exit 1" ,
546+ exitCode : 1 ,
547+ output : "Error output" ,
548+ succeeded : false ,
549+ failureReason : expect . stringContaining ( "Command execution was not successful" ) ,
550+ } )
551+
552+ mockCompressTerminalOutput . mockRestore ( )
553+ } )
554+
555+ it ( "should emit taskCommandExecuted event when command is terminated by signal" , async ( ) => {
556+ mockTerminal . getCurrentWorkingDirectory . mockReturnValue ( "/test/project" )
557+
558+ const mockCompressTerminalOutput = vitest . spyOn ( Terminal , "compressTerminalOutput" )
559+ mockCompressTerminalOutput . mockReturnValue ( "Interrupted output" )
560+
561+ mockTerminal . runCommand . mockImplementation ( ( command : string , callbacks : RooTerminalCallbacks ) => {
562+ setTimeout ( ( ) => {
563+ callbacks . onShellExecutionStarted ( 1234 , mockProcess )
564+ callbacks . onCompleted ( "Interrupted output" , mockProcess )
565+ callbacks . onShellExecutionComplete (
566+ {
567+ exitCode : undefined ,
568+ signalName : "SIGTERM" ,
569+ coreDumpPossible : false ,
570+ } ,
571+ mockProcess ,
572+ )
573+ } , 0 )
574+ return mockProcess
575+ } )
576+
577+ const options : ExecuteCommandOptions = {
578+ executionId : "test-123" ,
579+ command : "long-running-command" ,
580+ terminalShellIntegrationDisabled : false ,
581+ terminalOutputLineLimit : 500 ,
582+ }
583+
584+ // Execute
585+ const [ rejected , result ] = await executeCommand ( mockTask , options )
586+
587+ // Verify
588+ expect ( rejected ) . toBe ( false )
589+ expect ( mockTask . emit ) . toHaveBeenCalledWith ( "taskCommandExecuted" , mockTask . taskId , {
590+ command : "long-running-command" ,
591+ exitCode : undefined ,
592+ output : "Interrupted output" ,
593+ succeeded : false ,
594+ failureReason : expect . stringContaining ( "Process terminated by signal SIGTERM" ) ,
595+ } )
596+
597+ mockCompressTerminalOutput . mockRestore ( )
598+ } )
599+
600+ it ( "should emit taskCommandExecuted event when command times out" , async ( ) => {
601+ // Mock the terminal process to not complete before timeout
602+ let timeoutId : NodeJS . Timeout
603+ const neverEndingProcess = new Promise < void > ( ( resolve ) => {
604+ timeoutId = setTimeout ( resolve , 10000 ) // Would resolve after 10 seconds
605+ } )
606+ Object . assign ( neverEndingProcess , {
607+ continue : vitest . fn ( ) ,
608+ abort : vitest . fn ( ( ) => {
609+ clearTimeout ( timeoutId )
610+ } ) ,
611+ } )
612+
613+ mockTerminal . runCommand . mockImplementation ( ( command : string , callbacks : RooTerminalCallbacks ) => {
614+ callbacks . onLine ( "Partial output" , neverEndingProcess as any )
615+ return neverEndingProcess
616+ } )
617+
618+ const options : ExecuteCommandOptions = {
619+ executionId : "test-123" ,
620+ command : "sleep 100" ,
621+ terminalShellIntegrationDisabled : false ,
622+ terminalOutputLineLimit : 500 ,
623+ commandExecutionTimeout : 100 , // 100ms timeout
624+ }
625+
626+ // Execute
627+ const [ rejected , result ] = await executeCommand ( mockTask , options )
628+
629+ // Verify
630+ expect ( rejected ) . toBe ( false )
631+ expect ( result ) . toContain ( "terminated after exceeding" )
632+ expect ( mockTask . emit ) . toHaveBeenCalledWith ( "taskCommandExecuted" , mockTask . taskId , {
633+ command : "sleep 100" ,
634+ exitCode : undefined ,
635+ output : "Partial output" ,
636+ succeeded : false ,
637+ failureReason : "Command timed out after 0.1s" ,
638+ } )
639+ } )
640+
641+ it ( "should emit taskCommandExecuted event when user provides feedback while command is running" , async ( ) => {
642+ // Mock the ask function to simulate user feedback
643+ mockTask . ask = vitest . fn ( ) . mockResolvedValue ( {
644+ response : "messageResponse" ,
645+ text : "Please stop the command" ,
646+ images : [ ] ,
647+ } )
648+
649+ // Mock a long-running command
650+ let commandResolve : ( ) => void
651+ const longRunningProcess = new Promise < void > ( ( resolve ) => {
652+ commandResolve = resolve
653+ } )
654+ Object . assign ( longRunningProcess , {
655+ continue : vitest . fn ( ( ) => {
656+ // Simulate command continuing after feedback
657+ setTimeout ( ( ) => commandResolve ( ) , 10 )
658+ } ) ,
659+ } )
660+
661+ mockTerminal . runCommand . mockImplementation ( ( command : string , callbacks : RooTerminalCallbacks ) => {
662+ // Simulate output that triggers user interaction
663+ callbacks . onLine ( "Command is running...\n" , longRunningProcess as any )
664+ return longRunningProcess
665+ } )
666+
667+ const options : ExecuteCommandOptions = {
668+ executionId : "test-123" ,
669+ command : "npm install" ,
670+ terminalShellIntegrationDisabled : false ,
671+ terminalOutputLineLimit : 500 ,
672+ }
673+
674+ // Execute
675+ const [ rejected , result ] = await executeCommand ( mockTask , options )
676+
677+ // Verify
678+ expect ( rejected ) . toBe ( true ) // User feedback causes rejection
679+ expect ( mockTask . emit ) . toHaveBeenCalledWith ( "taskCommandExecuted" , mockTask . taskId , {
680+ command : "npm install" ,
681+ exitCode : undefined ,
682+ output : "Command is running...\n" ,
683+ succeeded : false ,
684+ failureReason : "Command is still running (user provided feedback)" ,
685+ } )
686+ } )
687+ } )
474688} )
0 commit comments