@@ -166,10 +166,18 @@ export interface TerminalServiceOptions {
166166 runLedger ?: RunLedger
167167}
168168
169+ /** A command holding a terminal's foreground. */
170+ export interface RunningTerminalCommand {
171+ /** Its command line, `''` when the shell did not announce it. */
172+ command : string
173+ /** The program name of the shell it was typed into (`zsh`, `pwsh`), whose syntax names its program. */
174+ shell : string
175+ }
176+
169177/** The terminal work a restart would cut off. */
170178export interface TerminalWork {
171- /** The command line holding each busy terminal's foreground, `''` when unannounced . */
172- commands : string [ ]
179+ /** The command holding each busy terminal's foreground. */
180+ commands : RunningTerminalCommand [ ]
173181 /** Terminals whose foreground could not be read, which may be running anything. */
174182 uncheckedCount : number
175183}
@@ -319,16 +327,20 @@ export class TerminalService {
319327 const groups = this . options . processGroups ?? osProcessGroups
320328 const perSession = await Promise . all (
321329 [ ...this . sessions . values ( ) ] . map (
322- async ( session ) : Promise < { command : string | null ; unchecked : boolean } > => {
323- if ( session . isBusy ) return { command : session . foreground ?? '' , unchecked : false }
330+ async (
331+ session
332+ ) : Promise < { command : RunningTerminalCommand | null ; unchecked : boolean } > => {
333+ const running = ( command : string ) => ( { command, shell : session . shellProgram } )
334+ if ( session . isBusy )
335+ return { command : running ( session . foreground ?? '' ) , unchecked : false }
324336 if ( session . hasShellIntegration ) return { command : null , unchecked : false }
325337 const foreground = await groups . foreground (
326338 session . pid ,
327339 session . spawnedAt ,
328340 session . shellReadyAt
329341 )
330342 return {
331- command : foreground ?. length ? '' : null ,
343+ command : foreground ?. length ? running ( '' ) : null ,
332344 unchecked : foreground === null ,
333345 }
334346 }
@@ -1018,7 +1030,7 @@ export class TerminalService {
10181030 ( await groups . foreground ( session . pid , startedAt , session . shellReadyAt ) ) ?? [ ]
10191031 let command = groups . readBeforeInterrupt ? await readCommand ( ) : undefined
10201032 if ( session . agentCommandToolCallId !== toolCallId ) return
1021- session . kill ( 'SIGINT' )
1033+ session . interrupt ( )
10221034 for ( const escalation of groups . escalations ) {
10231035 if ( await this . commandEnds ( session , toolCallId , STOP_ESCALATION_MS ) ) return
10241036 const remaining =
@@ -1036,6 +1048,30 @@ export class TerminalService {
10361048 }
10371049 }
10381050
1051+ /**
1052+ * Sends a terminating signal to the command holding a plain shell's foreground, never the shell:
1053+ * the tool stops what is running, and the shell is the user's tab. On macOS and Linux that is
1054+ * the tty's foreground process group; on Windows, which has no signals, each process the shell
1055+ * started since the command began is ended with its tree. `signalled` is false when nothing but
1056+ * the shell held the foreground, or the OS could not say what did.
1057+ */
1058+ private async terminateForeground (
1059+ session : TerminalSession ,
1060+ signal : 'SIGTERM' | 'SIGKILL' ,
1061+ stopped : AbortSignal
1062+ ) : Promise < { signal : string ; terminalId : string ; signalled : boolean } > {
1063+ const groups = this . options . processGroups ?? osProcessGroups
1064+ const command =
1065+ ( await groups . foreground (
1066+ session . pid ,
1067+ session . foregroundStartedAt ?? session . spawnedAt ,
1068+ session . shellReadyAt
1069+ ) ) ?? [ ]
1070+ if ( stopped . aborted ) throw stoppedBeforeStart ( )
1071+ if ( command . length > 0 ) groups . signal ( command , signal )
1072+ return { signal, terminalId : session . terminalId , signalled : command . length > 0 }
1073+ }
1074+
10391075 private async dispatch (
10401076 toolCallId : string ,
10411077 operation : TerminalOperation ,
@@ -1159,8 +1195,11 @@ export class TerminalService {
11591195 await sendKey ( target , signal === 'SIGKILL' ? 'C-\\' : 'C-c' , session . env )
11601196 return { signal, terminalId : session . terminalId , pane : target }
11611197 }
1162- session . kill ( signal )
1163- return { signal, terminalId : session . terminalId }
1198+ if ( signal === 'SIGINT' ) {
1199+ session . interrupt ( )
1200+ return { signal, terminalId : session . terminalId }
1201+ }
1202+ return await this . terminateForeground ( session , signal , latch . signal )
11641203 }
11651204 default :
11661205 throw new TerminalError ( 'INVALID_REQUEST' , `Unknown terminal operation: ${ operation } ` )
0 commit comments