11import { Anthropic } from "@anthropic-ai/sdk"
22import cloneDeep from "clone-deep"
3+ import { execa } from "execa"
34import getFolderSize from "get-folder-size"
45import { setTimeout as setTimeoutPromise } from "node:timers/promises"
56import os from "os"
@@ -8,6 +9,8 @@ import pWaitFor from "p-wait-for"
89import * as path from "path"
910import { serializeError } from "serialize-error"
1011import * as vscode from "vscode"
12+ import { Logger } from "../../services/logging/Logger"
13+ const { IS_TEST } = process . env
1114import { ApiHandler , buildApiHandler } from "../../api"
1215import { AnthropicHandler } from "../../api/providers/anthropic"
1316import { ClineHandler } from "../../api/providers/cline"
@@ -1131,7 +1134,91 @@ export class Task {
11311134
11321135 // Tools
11331136
1137+ /**
1138+ * Executes a command directly in Node.js using execa
1139+ * This is used in test mode to capture the full output without using the VS Code terminal
1140+ * Commands are automatically terminated after 30 seconds using Promise.race
1141+ */
1142+ private async executeCommandInNode ( command : string ) : Promise < [ boolean , ToolResponse ] > {
1143+ try {
1144+ // Create a child process
1145+ const childProcess = execa ( command , {
1146+ shell : true ,
1147+ cwd,
1148+ reject : false ,
1149+ all : true , // Merge stdout and stderr
1150+ } )
1151+
1152+ // Set up variables to collect output
1153+ let output = ""
1154+
1155+ // Collect output in real-time
1156+ if ( childProcess . all ) {
1157+ childProcess . all . on ( "data" , ( data ) => {
1158+ output += data . toString ( )
1159+ } )
1160+ }
1161+
1162+ // Create a timeout promise that rejects after 30 seconds
1163+ const timeoutPromise = new Promise < never > ( ( _ , reject ) => {
1164+ setTimeout ( ( ) => {
1165+ if ( childProcess . pid ) {
1166+ childProcess . kill ( "SIGKILL" ) // Use SIGKILL for more forceful termination
1167+ }
1168+ reject ( new Error ( "Command timeout after 30s" ) )
1169+ } , 30000 )
1170+ } )
1171+
1172+ // Race between command completion and timeout
1173+ const result = await Promise . race ( [ childProcess , timeoutPromise ] ) . catch ( ( error ) => {
1174+ // If we get here due to timeout, return a partial result with timeout flag
1175+ Logger . info ( `Command timed out after 30s: ${ command } ` )
1176+ return {
1177+ stdout : "" ,
1178+ stderr : "" ,
1179+ exitCode : 124 , // Standard timeout exit code
1180+ timedOut : true ,
1181+ }
1182+ } )
1183+
1184+ // Check if timeout occurred
1185+ const wasTerminated = result . timedOut === true
1186+
1187+ // Use collected output or result output
1188+ if ( ! output ) {
1189+ output = result . stdout || result . stderr || ""
1190+ }
1191+
1192+ Logger . info ( `Command executed in Node: ${ command } \nOutput:\n${ output } ` )
1193+
1194+ // Add termination message if the command was terminated
1195+ if ( wasTerminated ) {
1196+ output += "\nCommand was taking a while to run so it was auto terminated after 30s"
1197+ }
1198+
1199+ // Format the result similar to terminal output
1200+ return [
1201+ false ,
1202+ `Command executed${ wasTerminated ? " (terminated after 30s)" : "" } with exit code ${
1203+ result . exitCode
1204+ } .${ output . length > 0 ? `\nOutput:\n${ output } ` : "" } `,
1205+ ]
1206+ } catch ( error ) {
1207+ // Handle any errors that might occur
1208+ const errorMessage = error instanceof Error ? error . message : String ( error )
1209+ return [ false , `Error executing command: ${ errorMessage } ` ]
1210+ }
1211+ }
1212+
11341213 async executeCommandTool ( command : string ) : Promise < [ boolean , ToolResponse ] > {
1214+ // Check if we're in test mode
1215+ if ( IS_TEST === "true" ) {
1216+ // In test mode, execute the command directly in Node
1217+ Logger . info ( "Executing command in Node: " + command )
1218+ return this . executeCommandInNode ( command )
1219+ }
1220+ Logger . info ( "Executing command in VS code terminal: " + command )
1221+
11351222 const terminalInfo = await this . terminalManager . getOrCreateTerminal ( cwd )
11361223 terminalInfo . terminal . show ( ) // weird visual bug when creating new terminals (even manually) where there's an empty space at the top.
11371224 const process = this . terminalManager . runCommand ( terminalInfo , command )
0 commit comments