@@ -76,6 +76,7 @@ pub(crate) struct ChatWidget<'a> {
76
76
// Track the most recently active stream kind in the current turn
77
77
last_stream_kind : Option < StreamKind > ,
78
78
running_commands : HashMap < String , RunningCommand > ,
79
+ pending_exec_completions : Vec < ( Vec < String > , Vec < ParsedCommand > , CommandOutput ) > ,
79
80
task_complete_pending : bool ,
80
81
// Queue of interruptive UI events deferred during an active write cycle
81
82
interrupts : InterruptManager ,
@@ -112,6 +113,10 @@ impl ChatWidget<'_> {
112
113
fn mark_needs_redraw ( & mut self ) {
113
114
self . needs_redraw = true ;
114
115
}
116
+ fn flush_answer_stream_with_separator ( & mut self ) {
117
+ let sink = AppEventHistorySink ( self . app_event_tx . clone ( ) ) ;
118
+ let _ = self . stream . finalize ( StreamKind :: Answer , true , & sink) ;
119
+ }
115
120
// --- Small event handlers ---
116
121
fn on_session_configured ( & mut self , event : codex_core:: protocol:: SessionConfiguredEvent ) {
117
122
self . bottom_pane
@@ -215,6 +220,7 @@ impl ChatWidget<'_> {
215
220
}
216
221
217
222
fn on_exec_command_begin ( & mut self , ev : ExecCommandBeginEvent ) {
223
+ self . flush_answer_stream_with_separator ( ) ;
218
224
let ev2 = ev. clone ( ) ;
219
225
self . defer_or_handle ( |q| q. push_exec_begin ( ev) , |s| s. handle_exec_begin_now ( ev2) ) ;
220
226
}
@@ -344,12 +350,11 @@ impl ChatWidget<'_> {
344
350
345
351
pub ( crate ) fn handle_exec_end_now ( & mut self , ev : ExecCommandEndEvent ) {
346
352
let running = self . running_commands . remove ( & ev. call_id ) ;
347
- self . active_exec_cell = None ;
348
353
let ( command, parsed) = match running {
349
354
Some ( rc) => ( rc. command , rc. parsed_cmd ) ,
350
355
None => ( vec ! [ ev. call_id. clone( ) ] , Vec :: new ( ) ) ,
351
356
} ;
352
- self . add_to_history ( HistoryCell :: new_completed_exec_command (
357
+ self . pending_exec_completions . push ( (
353
358
command,
354
359
parsed,
355
360
CommandOutput {
@@ -358,6 +363,16 @@ impl ChatWidget<'_> {
358
363
stderr : ev. stderr . clone ( ) ,
359
364
} ,
360
365
) ) ;
366
+
367
+ if self . running_commands . is_empty ( ) {
368
+ self . active_exec_cell = None ;
369
+ let pending = std:: mem:: take ( & mut self . pending_exec_completions ) ;
370
+ for ( command, parsed, output) in pending {
371
+ self . add_to_history ( HistoryCell :: new_completed_exec_command (
372
+ command, parsed, output,
373
+ ) ) ;
374
+ }
375
+ }
361
376
}
362
377
363
378
pub ( crate ) fn handle_patch_apply_end_now (
@@ -372,6 +387,7 @@ impl ChatWidget<'_> {
372
387
}
373
388
374
389
pub ( crate ) fn handle_exec_approval_now ( & mut self , id : String , ev : ExecApprovalRequestEvent ) {
390
+ self . flush_answer_stream_with_separator ( ) ;
375
391
// Log a background summary immediately so the history is chronological.
376
392
let cmdline = strip_bash_lc_and_escape ( & ev. command ) ;
377
393
let text = format ! (
@@ -398,6 +414,7 @@ impl ChatWidget<'_> {
398
414
id : String ,
399
415
ev : ApplyPatchApprovalRequestEvent ,
400
416
) {
417
+ self . flush_answer_stream_with_separator ( ) ;
401
418
self . add_to_history ( HistoryCell :: new_patch_event (
402
419
PatchEventType :: ApprovalRequest ,
403
420
ev. changes . clone ( ) ,
@@ -423,16 +440,29 @@ impl ChatWidget<'_> {
423
440
parsed_cmd : ev. parsed_cmd . clone ( ) ,
424
441
} ,
425
442
) ;
426
- self . active_exec_cell = Some ( HistoryCell :: new_active_exec_command (
427
- ev. command ,
428
- ev. parsed_cmd ,
429
- ) ) ;
443
+ // Accumulate parsed commands into a single active Exec cell so they stack
444
+ match self . active_exec_cell . as_mut ( ) {
445
+ Some ( HistoryCell :: Exec ( exec) ) => {
446
+ exec. parsed . extend ( ev. parsed_cmd ) ;
447
+ }
448
+ _ => {
449
+ self . active_exec_cell = Some ( HistoryCell :: new_active_exec_command (
450
+ ev. command ,
451
+ ev. parsed_cmd ,
452
+ ) ) ;
453
+ }
454
+ }
455
+
456
+ // Request a redraw so the working header and command list are visible immediately.
457
+ self . mark_needs_redraw ( ) ;
430
458
}
431
459
432
460
pub ( crate ) fn handle_mcp_begin_now ( & mut self , ev : McpToolCallBeginEvent ) {
461
+ self . flush_answer_stream_with_separator ( ) ;
433
462
self . add_to_history ( HistoryCell :: new_active_mcp_tool_call ( ev. invocation ) ) ;
434
463
}
435
464
pub ( crate ) fn handle_mcp_end_now ( & mut self , ev : McpToolCallEndEvent ) {
465
+ self . flush_answer_stream_with_separator ( ) ;
436
466
self . add_to_history ( HistoryCell :: new_completed_mcp_tool_call (
437
467
80 ,
438
468
ev. invocation ,
@@ -494,6 +524,7 @@ impl ChatWidget<'_> {
494
524
stream : StreamController :: new ( config) ,
495
525
last_stream_kind : None ,
496
526
running_commands : HashMap :: new ( ) ,
527
+ pending_exec_completions : Vec :: new ( ) ,
497
528
task_complete_pending : false ,
498
529
interrupts : InterruptManager :: new ( ) ,
499
530
needs_redraw : false ,
0 commit comments