@@ -70,43 +70,192 @@ Interestingly, some environments like Cursor enable these APIs even without the
7070This approach allows us to leverage advanced features when available while ensuring broad compatibility.
7171*/
7272declare module "vscode" {
73+ // https://github.com/microsoft/vscode/blob/f0417069c62e20f3667506f4b7e53ca0004b4e3e/src/vscode-dts/vscode.d.ts#L7442
74+ // interface Terminal {
75+ // shellIntegration?: {
76+ // cwd?: vscode.Uri
77+ // executeCommand?: (command: string) => {
78+ // read: () => AsyncIterable<string>
79+ // }
80+ // }
81+ // }
7382 // https://github.com/microsoft/vscode/blob/f0417069c62e20f3667506f4b7e53ca0004b4e3e/src/vscode-dts/vscode.d.ts#L10794
7483 interface Window {
7584 onDidStartTerminalShellExecution ?: (
7685 listener : ( e : any ) => any ,
7786 thisArgs ?: any ,
7887 disposables ?: vscode . Disposable [ ] ,
7988 ) => vscode . Disposable
89+ onDidEndTerminalShellExecution ?: (
90+ listener : ( e : { terminal : vscode . Terminal ; exitCode ?: number ; shellType ?: string } ) => any ,
91+ thisArgs ?: any ,
92+ disposables ?: vscode . Disposable [ ] ,
93+ ) => vscode . Disposable
8094 }
8195}
8296
83- // Extend the Terminal type to include our custom properties
84- type ExtendedTerminal = vscode . Terminal & {
85- shellIntegration ?: {
86- cwd ?: vscode . Uri
87- executeCommand ?: ( command : string ) => {
88- read : ( ) => AsyncIterable < string >
89- }
90- }
97+ export interface ExitCodeDetails {
98+ exitCode : number | undefined
99+ signal ?: number | undefined
100+ signalName ?: string
101+ coreDumpPossible ?: boolean
91102}
92103
93104export class TerminalManager {
94105 private terminalIds : Set < number > = new Set ( )
95106 private processes : Map < number , TerminalProcess > = new Map ( )
96107 private disposables : vscode . Disposable [ ] = [ ]
97108
109+ private interpretExitCode ( exitCode : number | undefined ) : ExitCodeDetails {
110+ if ( exitCode === undefined ) {
111+ return { exitCode }
112+ }
113+
114+ if ( exitCode <= 128 ) {
115+ return { exitCode }
116+ }
117+
118+ const signal = exitCode - 128
119+ const signals : Record < number , string > = {
120+ // Standard signals
121+ 1 : "SIGHUP" ,
122+ 2 : "SIGINT" ,
123+ 3 : "SIGQUIT" ,
124+ 4 : "SIGILL" ,
125+ 5 : "SIGTRAP" ,
126+ 6 : "SIGABRT" ,
127+ 7 : "SIGBUS" ,
128+ 8 : "SIGFPE" ,
129+ 9 : "SIGKILL" ,
130+ 10 : "SIGUSR1" ,
131+ 11 : "SIGSEGV" ,
132+ 12 : "SIGUSR2" ,
133+ 13 : "SIGPIPE" ,
134+ 14 : "SIGALRM" ,
135+ 15 : "SIGTERM" ,
136+ 16 : "SIGSTKFLT" ,
137+ 17 : "SIGCHLD" ,
138+ 18 : "SIGCONT" ,
139+ 19 : "SIGSTOP" ,
140+ 20 : "SIGTSTP" ,
141+ 21 : "SIGTTIN" ,
142+ 22 : "SIGTTOU" ,
143+ 23 : "SIGURG" ,
144+ 24 : "SIGXCPU" ,
145+ 25 : "SIGXFSZ" ,
146+ 26 : "SIGVTALRM" ,
147+ 27 : "SIGPROF" ,
148+ 28 : "SIGWINCH" ,
149+ 29 : "SIGIO" ,
150+ 30 : "SIGPWR" ,
151+ 31 : "SIGSYS" ,
152+
153+ // Real-time signals base
154+ 34 : "SIGRTMIN" ,
155+
156+ // SIGRTMIN+n signals
157+ 35 : "SIGRTMIN+1" ,
158+ 36 : "SIGRTMIN+2" ,
159+ 37 : "SIGRTMIN+3" ,
160+ 38 : "SIGRTMIN+4" ,
161+ 39 : "SIGRTMIN+5" ,
162+ 40 : "SIGRTMIN+6" ,
163+ 41 : "SIGRTMIN+7" ,
164+ 42 : "SIGRTMIN+8" ,
165+ 43 : "SIGRTMIN+9" ,
166+ 44 : "SIGRTMIN+10" ,
167+ 45 : "SIGRTMIN+11" ,
168+ 46 : "SIGRTMIN+12" ,
169+ 47 : "SIGRTMIN+13" ,
170+ 48 : "SIGRTMIN+14" ,
171+ 49 : "SIGRTMIN+15" ,
172+
173+ // SIGRTMAX-n signals
174+ 50 : "SIGRTMAX-14" ,
175+ 51 : "SIGRTMAX-13" ,
176+ 52 : "SIGRTMAX-12" ,
177+ 53 : "SIGRTMAX-11" ,
178+ 54 : "SIGRTMAX-10" ,
179+ 55 : "SIGRTMAX-9" ,
180+ 56 : "SIGRTMAX-8" ,
181+ 57 : "SIGRTMAX-7" ,
182+ 58 : "SIGRTMAX-6" ,
183+ 59 : "SIGRTMAX-5" ,
184+ 60 : "SIGRTMAX-4" ,
185+ 61 : "SIGRTMAX-3" ,
186+ 62 : "SIGRTMAX-2" ,
187+ 63 : "SIGRTMAX-1" ,
188+ 64 : "SIGRTMAX" ,
189+ }
190+
191+ // These signals may produce core dumps:
192+ // SIGQUIT, SIGILL, SIGABRT, SIGBUS, SIGFPE, SIGSEGV
193+ const coreDumpPossible = new Set ( [ 3 , 4 , 6 , 7 , 8 , 11 ] )
194+
195+ return {
196+ exitCode,
197+ signal,
198+ signalName : signals [ signal ] || `Unknown Signal (${ signal } )` ,
199+ coreDumpPossible : coreDumpPossible . has ( signal ) ,
200+ }
201+ }
202+
98203 constructor ( ) {
99- let disposable : vscode . Disposable | undefined
204+ let startDisposable : vscode . Disposable | undefined
205+ let endDisposable : vscode . Disposable | undefined
100206 try {
101- disposable = ( vscode . window as vscode . Window ) . onDidStartTerminalShellExecution ?.( async ( e ) => {
102- // Creating a read stream here results in a more consistent output. This is most obvious when running the `date` command.
103- e ?. execution ?. read ( )
207+ // onDidStartTerminalShellExecution
208+ startDisposable = ( vscode . window as vscode . Window ) . onDidStartTerminalShellExecution ?.( async ( e ) => {
209+ // Get a handle to the stream as early as possible:
210+ const stream = e ?. execution . read ( )
211+ const terminalInfo = TerminalRegistry . getTerminalInfoByTerminal ( e . terminal )
212+ if ( stream && terminalInfo ) {
213+ const process = this . processes . get ( terminalInfo . id )
214+ if ( process ) {
215+ terminalInfo . stream = stream
216+ terminalInfo . running = true
217+ terminalInfo . streamClosed = false
218+ process . emit ( "stream_available" , terminalInfo . id , stream )
219+ }
220+ } else {
221+ console . error ( "[TerminalManager] Stream failed, not registered for terminal" )
222+ }
223+
224+ console . info ( "[TerminalManager] Shell execution started:" , {
225+ hasExecution : ! ! e ?. execution ,
226+ command : e ?. execution ?. commandLine ?. value ,
227+ terminalId : terminalInfo ?. id ,
228+ } )
229+ } )
230+
231+ // onDidEndTerminalShellExecution
232+ endDisposable = ( vscode . window as vscode . Window ) . onDidEndTerminalShellExecution ?.( async ( e ) => {
233+ const exitDetails = this . interpretExitCode ( e ?. exitCode )
234+ console . info ( "[TerminalManager] Shell execution ended:" , {
235+ ...exitDetails ,
236+ } )
237+
238+ // Signal completion to any waiting processes
239+ for ( const id of this . terminalIds ) {
240+ const info = TerminalRegistry . getTerminal ( id )
241+ if ( info && info . terminal === e . terminal ) {
242+ info . running = false
243+ const process = this . processes . get ( id )
244+ if ( process ) {
245+ process . emit ( "shell_execution_complete" , id , exitDetails )
246+ }
247+ break
248+ }
249+ }
104250 } )
105251 } catch ( error ) {
106- // console.error("Error setting up onDidEndTerminalShellExecution", error)
252+ console . error ( "[TerminalManager] Error setting up shell execution handlers:" , error )
253+ }
254+ if ( startDisposable ) {
255+ this . disposables . push ( startDisposable )
107256 }
108- if ( disposable ) {
109- this . disposables . push ( disposable )
257+ if ( endDisposable ) {
258+ this . disposables . push ( endDisposable )
110259 }
111260 }
112261
@@ -140,19 +289,16 @@ export class TerminalManager {
140289 } )
141290
142291 // if shell integration is already active, run the command immediately
143- const terminal = terminalInfo . terminal as ExtendedTerminal
144- if ( terminal . shellIntegration ) {
292+ if ( terminalInfo . terminal . shellIntegration ) {
145293 process . waitForShellIntegration = false
146- process . run ( terminal , command )
294+ process . run ( terminalInfo . terminal , command )
147295 } else {
148296 // docs recommend waiting 3s for shell integration to activate
149- pWaitFor ( ( ) => ( terminalInfo . terminal as ExtendedTerminal ) . shellIntegration !== undefined , {
150- timeout : 4000 ,
151- } ) . finally ( ( ) => {
297+ pWaitFor ( ( ) => terminalInfo . terminal . shellIntegration !== undefined , { timeout : 4000 } ) . finally ( ( ) => {
152298 const existingProcess = this . processes . get ( terminalInfo . id )
153299 if ( existingProcess && existingProcess . waitForShellIntegration ) {
154300 existingProcess . waitForShellIntegration = false
155- existingProcess . run ( terminal , command )
301+ existingProcess . run ( terminalInfo . terminal , command )
156302 }
157303 } )
158304 }
@@ -168,8 +314,7 @@ export class TerminalManager {
168314 if ( t . busy ) {
169315 return false
170316 }
171- const terminal = t . terminal as ExtendedTerminal
172- const terminalCwd = terminal . shellIntegration ?. cwd // one of cline's commands could have changed the cwd of the terminal
317+ const terminalCwd = t . terminal . shellIntegration ?. cwd // one of cline's commands could have changed the cwd of the terminal
173318 if ( ! terminalCwd ) {
174319 return false
175320 }
0 commit comments