@@ -12,11 +12,13 @@ use codex_utils_pty::ExecCommandSession;
1212use codex_utils_pty:: TerminalSize ;
1313use tokio:: sync:: Mutex ;
1414use tokio:: sync:: Notify ;
15+ use tokio:: sync:: broadcast;
1516use tokio:: sync:: mpsc;
1617use tokio:: sync:: watch;
1718
1819use crate :: ExecBackend ;
1920use crate :: ExecProcess ;
21+ use crate :: ExecProcessEvent ;
2022use crate :: ExecServerError ;
2123use crate :: ProcessId ;
2224use crate :: StartedExecProcess ;
@@ -45,6 +47,7 @@ use crate::rpc::invalid_request;
4547
4648const RETAINED_OUTPUT_BYTES_PER_PROCESS : usize = 1024 * 1024 ;
4749const NOTIFICATION_CHANNEL_CAPACITY : usize = 256 ;
50+ const PROCESS_EVENT_CHANNEL_CAPACITY : usize = 256 ;
4851#[ cfg( test) ]
4952const EXITED_PROCESS_RETENTION : Duration = Duration :: from_millis ( 25 ) ;
5053#[ cfg( not( test) ) ]
@@ -66,6 +69,7 @@ struct RunningProcess {
6669 next_seq : u64 ,
6770 exit_code : Option < i32 > ,
6871 wake_tx : watch:: Sender < u64 > ,
72+ event_tx : broadcast:: Sender < ExecProcessEvent > ,
6973 output_notify : Arc < Notify > ,
7074 open_streams : usize ,
7175 closed : bool ,
@@ -90,6 +94,7 @@ struct LocalExecProcess {
9094 process_id : ProcessId ,
9195 backend : LocalProcess ,
9296 wake_tx : watch:: Sender < u64 > ,
97+ event_tx : broadcast:: Sender < ExecProcessEvent > ,
9398}
9499
95100impl Default for LocalProcess {
@@ -139,7 +144,14 @@ impl LocalProcess {
139144 async fn start_process (
140145 & self ,
141146 params : ExecParams ,
142- ) -> Result < ( ExecResponse , watch:: Sender < u64 > ) , JSONRPCErrorError > {
147+ ) -> Result <
148+ (
149+ ExecResponse ,
150+ watch:: Sender < u64 > ,
151+ broadcast:: Sender < ExecProcessEvent > ,
152+ ) ,
153+ JSONRPCErrorError ,
154+ > {
143155 let process_id = params. process_id . clone ( ) ;
144156 let ( program, args) = params
145157 . argv
@@ -199,6 +211,7 @@ impl LocalProcess {
199211
200212 let output_notify = Arc :: new ( Notify :: new ( ) ) ;
201213 let ( wake_tx, _wake_rx) = watch:: channel ( 0 ) ;
214+ let ( event_tx, _event_rx) = broadcast:: channel ( PROCESS_EVENT_CHANNEL_CAPACITY ) ;
202215 {
203216 let mut process_map = self . inner . processes . lock ( ) . await ;
204217 process_map. insert (
@@ -212,6 +225,7 @@ impl LocalProcess {
212225 next_seq : 1 ,
213226 exit_code : None ,
214227 wake_tx : wake_tx. clone ( ) ,
228+ event_tx : event_tx. clone ( ) ,
215229 output_notify : Arc :: clone ( & output_notify) ,
216230 open_streams : 2 ,
217231 closed : false ,
@@ -248,13 +262,13 @@ impl LocalProcess {
248262 output_notify,
249263 ) ) ;
250264
251- Ok ( ( ExecResponse { process_id } , wake_tx) )
265+ Ok ( ( ExecResponse { process_id } , wake_tx, event_tx ) )
252266 }
253267
254268 pub ( crate ) async fn exec ( & self , params : ExecParams ) -> Result < ExecResponse , JSONRPCErrorError > {
255269 self . start_process ( params)
256270 . await
257- . map ( |( response, _) | response)
271+ . map ( |( response, _, _ ) | response)
258272 }
259273
260274 pub ( crate ) async fn exec_read (
@@ -425,7 +439,7 @@ fn shell_environment_policy(env_policy: &ExecEnvPolicy) -> ShellEnvironmentPolic
425439#[ async_trait]
426440impl ExecBackend for LocalProcess {
427441 async fn start ( & self , params : ExecParams ) -> Result < StartedExecProcess , ExecServerError > {
428- let ( response, wake_tx) = self
442+ let ( response, wake_tx, event_tx ) = self
429443 . start_process ( params)
430444 . await
431445 . map_err ( map_handler_error) ?;
@@ -434,6 +448,7 @@ impl ExecBackend for LocalProcess {
434448 process_id : response. process_id ,
435449 backend : self . clone ( ) ,
436450 wake_tx,
451+ event_tx,
437452 } ) ,
438453 } )
439454 }
@@ -449,6 +464,10 @@ impl ExecProcess for LocalExecProcess {
449464 self . wake_tx . subscribe ( )
450465 }
451466
467+ fn subscribe_events ( & self ) -> broadcast:: Receiver < ExecProcessEvent > {
468+ self . event_tx . subscribe ( )
469+ }
470+
452471 async fn read (
453472 & self ,
454473 after_seq : Option < u64 > ,
@@ -549,11 +568,19 @@ async fn stream_output(
549568 process. retained_bytes = process. retained_bytes . saturating_sub ( evicted. chunk . len ( ) ) ;
550569 }
551570 let _ = process. wake_tx . send ( seq) ;
571+ let output = ProcessOutputChunk {
572+ seq,
573+ stream,
574+ chunk : chunk. into ( ) ,
575+ } ;
576+ let _ = process
577+ . event_tx
578+ . send ( ExecProcessEvent :: Output ( output. clone ( ) ) ) ;
552579 ExecOutputDeltaNotification {
553580 process_id : process_id. clone ( ) ,
554581 seq,
555582 stream,
556- chunk : chunk . into ( ) ,
583+ chunk : output . chunk ,
557584 }
558585 } ;
559586 output_notify. notify_waiters ( ) ;
@@ -581,6 +608,9 @@ async fn watch_exit(
581608 process. next_seq += 1 ;
582609 process. exit_code = Some ( exit_code) ;
583610 let _ = process. wake_tx . send ( seq) ;
611+ let _ = process
612+ . event_tx
613+ . send ( ExecProcessEvent :: Exited { seq, exit_code } ) ;
584614 Some ( ExecExitedNotification {
585615 process_id : process_id. clone ( ) ,
586616 seq,
@@ -641,6 +671,7 @@ async fn maybe_emit_closed(process_id: ProcessId, inner: Arc<Inner>) {
641671 let seq = process. next_seq ;
642672 process. next_seq += 1 ;
643673 let _ = process. wake_tx . send ( seq) ;
674+ let _ = process. event_tx . send ( ExecProcessEvent :: Closed { seq } ) ;
644675 Some ( ExecClosedNotification {
645676 process_id : process_id. clone ( ) ,
646677 seq,
0 commit comments