@@ -9,6 +9,8 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
99 private terminalRef : WeakRef < RooTerminal >
1010 private aborted = false
1111 private pid ?: number
12+ private subprocess ?: ReturnType < typeof execa >
13+ private pidUpdatePromise ?: Promise < void >
1214
1315 constructor ( terminal : RooTerminal ) {
1416 super ( )
@@ -36,7 +38,7 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
3638 try {
3739 this . isHot = true
3840
39- const subprocess = execa ( {
41+ this . subprocess = execa ( {
4042 shell : true ,
4143 cwd : this . terminal . getCurrentWorkingDirectory ( ) ,
4244 all : true ,
@@ -48,9 +50,37 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
4850 } ,
4951 } ) `${ command } `
5052
51- this . pid = subprocess . pid
52- const stream = subprocess . iterable ( { from : "all" , preserveNewlines : true } )
53- this . terminal . setActiveStream ( stream , subprocess . pid )
53+ this . pid = this . subprocess . pid
54+
55+ // When using shell: true, the PID is for the shell, not the actual command
56+ // Find the actual command PID after a small delay
57+ if ( this . pid ) {
58+ this . pidUpdatePromise = new Promise < void > ( ( resolve ) => {
59+ setTimeout ( ( ) => {
60+ psTree ( this . pid ! , ( err , children ) => {
61+ if ( ! err && children . length > 0 ) {
62+ // Update PID to the first child (the actual command)
63+ const actualPid = parseInt ( children [ 0 ] . PID )
64+ if ( ! isNaN ( actualPid ) ) {
65+ this . pid = actualPid
66+ }
67+ }
68+ resolve ( )
69+ } )
70+ } , 100 )
71+ } )
72+ }
73+
74+ const rawStream = this . subprocess . iterable ( { from : "all" , preserveNewlines : true } )
75+
76+ // Wrap the stream to ensure all chunks are strings (execa can return Uint8Array)
77+ const stream = ( async function * ( ) {
78+ for await ( const chunk of rawStream ) {
79+ yield typeof chunk === "string" ? chunk : new TextDecoder ( ) . decode ( chunk )
80+ }
81+ } ) ( )
82+
83+ this . terminal . setActiveStream ( stream , this . pid )
5484
5585 for await ( const line of stream ) {
5686 if ( this . aborted ) {
@@ -77,15 +107,15 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
77107
78108 timeoutId = setTimeout ( ( ) => {
79109 try {
80- subprocess . kill ( "SIGKILL" )
110+ this . subprocess ? .kill ( "SIGKILL" )
81111 } catch ( e ) { }
82112
83113 resolve ( )
84114 } , 5_000 )
85115 } )
86116
87117 try {
88- await Promise . race ( [ subprocess , kill ] )
118+ await Promise . race ( [ this . subprocess , kill ] )
89119 } catch ( error ) {
90120 console . log (
91121 `[ExecaTerminalProcess#run] subprocess termination error: ${ error instanceof Error ? error . message : String ( error ) } ` ,
@@ -109,13 +139,15 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
109139
110140 this . emit ( "shell_execution_complete" , { exitCode : 1 } )
111141 }
142+ this . subprocess = undefined
112143 }
113144
114145 this . terminal . setActiveStream ( undefined )
115146 this . emitRemainingBufferIfListening ( )
116147 this . stopHotTimer ( )
117148 this . emit ( "completed" , this . fullOutput )
118149 this . emit ( "continue" )
150+ this . subprocess = undefined
119151 }
120152
121153 public override continue ( ) {
@@ -127,7 +159,41 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
127159 public override abort ( ) {
128160 this . aborted = true
129161
162+ // Function to perform the kill operations
163+ const performKill = ( ) => {
164+ // Try to kill using the subprocess object
165+ if ( this . subprocess ) {
166+ try {
167+ this . subprocess . kill ( "SIGKILL" )
168+ } catch ( e ) {
169+ console . warn (
170+ `[ExecaTerminalProcess#abort] Failed to kill subprocess: ${ e instanceof Error ? e . message : String ( e ) } ` ,
171+ )
172+ }
173+ }
174+
175+ // Kill the stored PID (which should be the actual command after our update)
176+ if ( this . pid ) {
177+ try {
178+ process . kill ( this . pid , "SIGKILL" )
179+ } catch ( e ) {
180+ console . warn (
181+ `[ExecaTerminalProcess#abort] Failed to kill process ${ this . pid } : ${ e instanceof Error ? e . message : String ( e ) } ` ,
182+ )
183+ }
184+ }
185+ }
186+
187+ // If PID update is in progress, wait for it before killing
188+ if ( this . pidUpdatePromise ) {
189+ this . pidUpdatePromise . then ( performKill ) . catch ( ( ) => performKill ( ) )
190+ } else {
191+ performKill ( )
192+ }
193+
194+ // Continue with the rest of the abort logic
130195 if ( this . pid ) {
196+ // Also check for any child processes
131197 psTree ( this . pid , async ( err , children ) => {
132198 if ( ! err ) {
133199 const pids = children . map ( ( p ) => parseInt ( p . PID ) )
@@ -148,15 +214,6 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
148214 )
149215 }
150216 } )
151-
152- try {
153- console . error ( `[ExecaTerminalProcess#abort] SIGKILL parent -> ${ this . pid } ` )
154- process . kill ( this . pid , "SIGKILL" )
155- } catch ( e ) {
156- console . warn (
157- `[ExecaTerminalProcess#abort] Failed to send SIGKILL to main PID ${ this . pid } : ${ e instanceof Error ? e . message : String ( e ) } ` ,
158- )
159- }
160217 }
161218 }
162219
0 commit comments