11import { EventEmitter } from "node:events" ;
2+ import { killBackgroundProcess } from "../tools/smartCommand.js" ;
23
34/**
45 * Tracks every shell command the agent runs through the `run_command` tool so
@@ -17,6 +18,8 @@ export interface AgentCommandRun {
1718 stdout : string ;
1819 stderr : string ;
1920 truncated : boolean ;
21+ /** PID of the child process, set for background (long-running) commands. */
22+ pid ?: number ;
2023}
2124
2225export type AgentCommandSummary = Omit < AgentCommandRun , "stdout" | "stderr" > & {
@@ -27,14 +30,16 @@ export type AgentCommandSummary = Omit<AgentCommandRun, "stdout" | "stderr"> & {
2730/** Lightweight token for an in-progress agent command. */
2831export interface PendingCommandHandle {
2932 readonly id : string ;
33+ /** Register the OS PID once the child spawns so dismiss can kill it. */
34+ setPid ( pid : number ) : void ;
3035 appendChunk ( stream : "stdout" | "stderr" , text : string ) : void ;
3136 complete ( result : Omit < AgentCommandRun , "id" > ) : AgentCommandRun ;
3237}
3338
3439const MAX_RUNS = 100 ;
3540const ring : AgentCommandRun [ ] = [ ] ;
3641/** In-progress runs — kept until complete() is called so reconnecting clients can replay them. */
37- const pending = new Map < string , { id : string ; cmd : string ; cwd : string ; startedAt : number ; output : string } > ( ) ;
42+ const pending = new Map < string , { id : string ; cmd : string ; cwd : string ; startedAt : number ; output : string ; pid ?: number } > ( ) ;
3843const bus = new EventEmitter ( ) ;
3944bus . setMaxListeners ( 50 ) ;
4045
@@ -65,6 +70,10 @@ export function startAgentCommand(cmd: string, cwd: string): PendingCommandHandl
6570
6671 const handle : PendingCommandHandle = {
6772 id,
73+ setPid ( pid : number ) {
74+ const p = pending . get ( id ) ;
75+ if ( p ) p . pid = pid ;
76+ } ,
6877 appendChunk ( stream , text ) {
6978 const p = pending . get ( id ) ;
7079 if ( p ) p . output += text ;
@@ -112,15 +121,33 @@ export function getAgentCommand(id: string): AgentCommandRun | undefined {
112121}
113122
114123export function clearAgentCommands ( ) : number {
124+ // Kill any background PIDs still tracked on the ring before clearing.
125+ for ( const r of ring ) {
126+ if ( r . pid != null ) killBackgroundProcess ( r . pid ) ;
127+ }
128+ // Also kill in-progress commands (pending map may have pids for queued long-runners).
129+ for ( const p of pending . values ( ) ) {
130+ if ( p . pid != null ) killBackgroundProcess ( p . pid ) ;
131+ }
115132 const n = ring . length ;
116133 ring . length = 0 ;
117134 bus . emit ( "clear" ) ;
118135 return n ;
119136}
120137
121138export function deleteAgentCommand ( id : string ) : boolean {
139+ // Also try pending (user dismissed while the command was still running).
140+ const p = pending . get ( id ) ;
141+ if ( p ) {
142+ if ( p . pid != null ) killBackgroundProcess ( p . pid ) ;
143+ pending . delete ( id ) ;
144+ bus . emit ( "delete" , id ) ;
145+ return true ;
146+ }
122147 const i = ring . findIndex ( ( r ) => r . id === id ) ;
123148 if ( i < 0 ) return false ;
149+ const entry = ring [ i ] ;
150+ if ( entry . pid != null ) killBackgroundProcess ( entry . pid ) ;
124151 ring . splice ( i , 1 ) ;
125152 bus . emit ( "delete" , id ) ;
126153 return true ;
0 commit comments