@@ -7,11 +7,11 @@ import { Cline } from "../Cline"
77import { ToolUse , AskApproval , HandleError , PushToolResult , RemoveClosingTag , ToolResponse } from "../../shared/tools"
88import { formatResponse } from "../prompts/responses"
99import { unescapeHtmlEntities } from "../../utils/text-normalization"
10- import { Terminal } from "../../integrations/terminal/Terminal"
1110import { telemetryService } from "../../services/telemetry/TelemetryService"
12- import { ExitCodeDetails } from "../../integrations/terminal/TerminalProcess"
13- import { execaCommandExecutor } from "../command-executors/ExecaCommandExecutor"
14- // import { vsCodeCommandExecutor } from "../command-executors/VSCodeCommandExecutor"
11+ import { ExitCodeDetails , RooTerminalProcess } from "../../integrations/terminal/types"
12+ import { TerminalRegistry } from "../../integrations/terminal/TerminalRegistry"
13+ import { Terminal } from "../../integrations/terminal/Terminal"
14+ import { ExecaTerminal } from "../../integrations/terminal/ExecaTerminal"
1515
1616export async function executeCommandTool (
1717 cline : Cline ,
@@ -73,6 +73,7 @@ export async function executeCommand(
7373 cline : Cline ,
7474 command : string ,
7575 customCwd ?: string ,
76+ terminalProvider : "vscode" | "execa" = "vscode" ,
7677) : Promise < [ boolean , ToolResponse ] > {
7778 let workingDir : string
7879
@@ -95,62 +96,103 @@ export async function executeCommand(
9596 // a different working directory so that the model will know where the
9697 // command actually executed:
9798 // workingDir = terminalInfo.getCurrentWorkingDirectory()
98-
9999 const workingDirInfo = workingDir ? ` from '${ workingDir . toPosix ( ) } '` : ""
100100
101101 let userFeedback : { text ?: string ; images ?: string [ ] } | undefined
102- let didContinue = false
102+ let runInBackground : boolean | undefined = undefined
103103 let completed = false
104104 let result : string = ""
105105 let exitDetails : ExitCodeDetails | undefined
106106 const { terminalOutputLineLimit = 500 } = ( await cline . providerRef . deref ( ) ?. getState ( ) ) ?? { }
107107
108- const commandExecutor = execaCommandExecutor // vsCodeCommandExecutor
108+ const debounceLineLimit = 100 // Flush after this many lines.
109+ const debounceTimeoutMs = 200 // Flush after this much time inactivity (ms).
110+ let buffer : string [ ] = [ ]
111+ let debounceTimer : NodeJS . Timeout | null = null
109112
110- await commandExecutor . execute ( {
111- command ,
112- cwd : workingDir ,
113- taskId : cline . taskId ,
114- onLine : async ( line , process ) => {
115- if ( didContinue ) {
116- cline . say ( "command_output" , Terminal . compressTerminalOutput ( line , terminalOutputLineLimit ) )
117- return
118- }
113+ async function flush ( process ?: RooTerminalProcess ) {
114+ if ( debounceTimer ) {
115+ clearTimeout ( debounceTimer )
116+ debounceTimer = null
117+ }
118+
119+ if ( buffer . length === 0 ) {
120+ return
121+ }
119122
120- const { response, text, images } = await cline . ask ( "command_output" , line )
123+ const output = buffer . join ( "\n" )
124+ buffer = [ ]
121125
122- if ( response === "yesButtonClicked" ) {
123- // Proceed while running.
126+ result = Terminal . compressTerminalOutput ( result + output , terminalOutputLineLimit )
127+ const compressed = Terminal . compressTerminalOutput ( output , terminalOutputLineLimit )
128+ cline . say ( "command_output" , compressed )
129+
130+ if ( typeof runInBackground !== "undefined" ) {
131+ return
132+ }
133+
134+ console . log ( `ask command_output: waiting for response` )
135+ const { response, text, images } = await cline . ask ( "command_output" , compressed )
136+ console . log ( `ask command_output =>` , response )
137+
138+ if ( response === "yesButtonClicked" ) {
139+ runInBackground = false
140+ } else {
141+ runInBackground = true
142+ userFeedback = { text, images }
143+ }
144+
145+ process ?. continue ( )
146+ }
147+
148+ const callbacks = {
149+ onLine : async ( line : string , process : RooTerminalProcess ) => {
150+ buffer . push ( line )
151+
152+ if ( buffer . length >= debounceLineLimit ) {
153+ await flush ( process )
124154 } else {
125- userFeedback = { text, images }
126- }
155+ if ( debounceTimer ) {
156+ clearTimeout ( debounceTimer )
157+ }
127158
128- didContinue = true
129- process ?. continue ( ) // Continue past the await.
130- } ,
131- onStarted : ( ) => { } ,
132- onCompleted : ( output ) => {
133- result = output ?? ""
134- completed = true
135- } ,
136- onShellExecutionComplete : ( details ) => {
137- exitDetails = details
159+ debounceTimer = setTimeout ( ( ) => flush ( process ) , debounceTimeoutMs )
160+ }
138161 } ,
139- onNoShellIntegration : async ( message ) => {
162+ onCompleted : ( ) => ( completed = true ) ,
163+ onShellExecutionComplete : ( details : ExitCodeDetails ) => ( exitDetails = details ) ,
164+ onNoShellIntegration : async ( message : string ) => {
140165 telemetryService . captureShellIntegrationError ( cline . taskId )
141166 await cline . say ( "shell_integration_warning" , message )
142167 } ,
143- } )
168+ }
169+
170+ let terminal : Terminal | ExecaTerminal
144171
145- // Wait for a short delay to ensure all messages are sent to the webview
172+ if ( terminalProvider === "vscode" ) {
173+ terminal = await TerminalRegistry . getOrCreateTerminal ( workingDir , ! ! workingDir , cline . taskId )
174+ terminal . terminal . show ( )
175+ } else {
176+ terminal = new ExecaTerminal ( workingDir )
177+ }
178+
179+ await terminal . runCommand ( command , callbacks )
180+
181+ if ( debounceTimer ) {
182+ clearTimeout ( debounceTimer )
183+ debounceTimer = null
184+ }
185+
186+ // If there are any lines in the buffer, flush them to `result`.
187+ await flush ( )
188+
189+ // Wait for a short delay to ensure all messages are sent to the webview.
146190 // This delay allows time for non-awaited promises to be created and
147191 // for their associated messages to be sent to the webview, maintaining
148192 // the correct order of messages (although the webview is smart about
149193 // grouping command_output messages despite any gaps anyways).
150194 await delay ( 50 )
151195
152- result = Terminal . compressTerminalOutput ( result , terminalOutputLineLimit )
153-
154196 if ( userFeedback ) {
155197 await cline . say ( "user_feedback" , userFeedback . text , userFeedback . images )
156198
0 commit comments