@@ -66,6 +66,7 @@ use crate::history_cell;
66
66
use crate :: history_cell:: CommandOutput ;
67
67
use crate :: history_cell:: ExecCell ;
68
68
use crate :: history_cell:: HistoryCell ;
69
+ use crate :: history_cell:: McpToolCallCell ;
69
70
use crate :: history_cell:: PatchEventType ;
70
71
use crate :: slash_command:: SlashCommand ;
71
72
use crate :: text_formatting:: truncate_text;
@@ -114,7 +115,7 @@ pub(crate) struct ChatWidget {
114
115
app_event_tx : AppEventSender ,
115
116
codex_op_tx : UnboundedSender < Op > ,
116
117
bottom_pane : BottomPane ,
117
- active_exec_cell : Option < ExecCell > ,
118
+ active_cell : Option < Box < dyn HistoryCell > > ,
118
119
config : Config ,
119
120
auth_manager : Arc < AuthManager > ,
120
121
session_header : SessionHeader ,
@@ -281,7 +282,7 @@ impl ChatWidget {
281
282
/// and stop/clear running UI state.
282
283
fn finalize_turn_with_error_message ( & mut self , message : String ) {
283
284
// Ensure any spinner is replaced by a red ✗ and flushed into history.
284
- self . finalize_active_exec_cell_as_failed ( ) ;
285
+ self . finalize_active_cell_as_failed ( ) ;
285
286
// Emit the provided error message/history cell.
286
287
self . add_to_history ( history_cell:: new_error_event ( message) ) ;
287
288
// Reset running state and clear streaming buffers.
@@ -481,8 +482,8 @@ impl ChatWidget {
481
482
482
483
#[ inline]
483
484
fn handle_streaming_delta ( & mut self , delta : String ) {
484
- // Before streaming agent content, flush any active exec cell group.
485
- self . flush_active_exec_cell ( ) ;
485
+ // Before streaming agent content, flush any active cell group.
486
+ self . flush_active_cell ( ) ;
486
487
let sink = AppEventHistorySink ( self . app_event_tx . clone ( ) ) ;
487
488
self . stream . begin ( & sink) ;
488
489
self . stream . push_and_maybe_commit ( & delta, & sink) ;
@@ -496,16 +497,25 @@ impl ChatWidget {
496
497
None => ( vec ! [ ev. call_id. clone( ) ] , Vec :: new ( ) ) ,
497
498
} ;
498
499
499
- if self . active_exec_cell . is_none ( ) {
500
- // This should have been created by handle_exec_begin_now, but in case it wasn't,
501
- // create it now.
502
- self . active_exec_cell = Some ( history_cell:: new_active_exec_command (
500
+ let needs_new = self
501
+ . active_cell
502
+ . as_ref ( )
503
+ . map ( |cell| cell. as_any ( ) . downcast_ref :: < ExecCell > ( ) . is_none ( ) )
504
+ . unwrap_or ( true ) ;
505
+ if needs_new {
506
+ self . flush_active_cell ( ) ;
507
+ self . active_cell = Some ( Box :: new ( history_cell:: new_active_exec_command (
503
508
ev. call_id . clone ( ) ,
504
509
command,
505
510
parsed,
506
- ) ) ;
511
+ ) ) ) ;
507
512
}
508
- if let Some ( cell) = self . active_exec_cell . as_mut ( ) {
513
+
514
+ if let Some ( cell) = self
515
+ . active_cell
516
+ . as_mut ( )
517
+ . and_then ( |c| c. as_any_mut ( ) . downcast_mut :: < ExecCell > ( ) )
518
+ {
509
519
cell. complete_call (
510
520
& ev. call_id ,
511
521
CommandOutput {
@@ -517,7 +527,7 @@ impl ChatWidget {
517
527
ev. duration ,
518
528
) ;
519
529
if cell. should_flush ( ) {
520
- self . flush_active_exec_cell ( ) ;
530
+ self . flush_active_cell ( ) ;
521
531
}
522
532
}
523
533
}
@@ -584,58 +594,76 @@ impl ChatWidget {
584
594
parsed_cmd : ev. parsed_cmd . clone ( ) ,
585
595
} ,
586
596
) ;
587
- if let Some ( exec) = & self . active_exec_cell {
588
- if let Some ( new_exec) = exec. with_added_call (
597
+ if let Some ( cell) = self
598
+ . active_cell
599
+ . as_mut ( )
600
+ . and_then ( |c| c. as_any_mut ( ) . downcast_mut :: < ExecCell > ( ) )
601
+ && let Some ( new_exec) = cell. with_added_call (
589
602
ev. call_id . clone ( ) ,
590
603
ev. command . clone ( ) ,
591
604
ev. parsed_cmd . clone ( ) ,
592
- ) {
593
- self . active_exec_cell = Some ( new_exec) ;
594
- } else {
595
- // Make a new cell.
596
- self . flush_active_exec_cell ( ) ;
597
- self . active_exec_cell = Some ( history_cell:: new_active_exec_command (
598
- ev. call_id . clone ( ) ,
599
- ev. command . clone ( ) ,
600
- ev. parsed_cmd ,
601
- ) ) ;
602
- }
605
+ )
606
+ {
607
+ * cell = new_exec;
603
608
} else {
604
- self . active_exec_cell = Some ( history_cell:: new_active_exec_command (
609
+ self . flush_active_cell ( ) ;
610
+
611
+ self . active_cell = Some ( Box :: new ( history_cell:: new_active_exec_command (
605
612
ev. call_id . clone ( ) ,
606
613
ev. command . clone ( ) ,
607
614
ev. parsed_cmd ,
608
- ) ) ;
615
+ ) ) ) ;
609
616
}
610
617
611
- // Request a redraw so the working header and command list are visible immediately.
612
618
self . request_redraw ( ) ;
613
619
}
614
620
615
621
pub ( crate ) fn handle_mcp_begin_now ( & mut self , ev : McpToolCallBeginEvent ) {
616
622
self . flush_answer_stream_with_separator ( ) ;
617
- self . add_to_history ( history_cell:: new_active_mcp_tool_call ( ev. invocation ) ) ;
623
+ self . flush_active_cell ( ) ;
624
+ self . active_cell = Some ( Box :: new ( history_cell:: new_active_mcp_tool_call (
625
+ ev. call_id ,
626
+ ev. invocation ,
627
+ ) ) ) ;
628
+ self . request_redraw ( ) ;
618
629
}
619
630
pub ( crate ) fn handle_mcp_end_now ( & mut self , ev : McpToolCallEndEvent ) {
620
631
self . flush_answer_stream_with_separator ( ) ;
621
- self . add_boxed_history ( history_cell:: new_completed_mcp_tool_call (
622
- 80 ,
623
- ev. invocation ,
624
- ev. duration ,
625
- ev. result
626
- . as_ref ( )
627
- . map ( |r| !r. is_error . unwrap_or ( false ) )
628
- . unwrap_or ( false ) ,
629
- ev. result ,
630
- ) ) ;
632
+
633
+ let McpToolCallEndEvent {
634
+ call_id,
635
+ invocation,
636
+ duration,
637
+ result,
638
+ } = ev;
639
+
640
+ let extra_cell = match self
641
+ . active_cell
642
+ . as_mut ( )
643
+ . and_then ( |cell| cell. as_any_mut ( ) . downcast_mut :: < McpToolCallCell > ( ) )
644
+ {
645
+ Some ( cell) if cell. call_id ( ) == call_id => cell. complete ( duration, result) ,
646
+ _ => {
647
+ self . flush_active_cell ( ) ;
648
+ let mut cell = history_cell:: new_active_mcp_tool_call ( call_id, invocation) ;
649
+ let extra_cell = cell. complete ( duration, result) ;
650
+ self . active_cell = Some ( Box :: new ( cell) ) ;
651
+ extra_cell
652
+ }
653
+ } ;
654
+
655
+ self . flush_active_cell ( ) ;
656
+ if let Some ( extra) = extra_cell {
657
+ self . add_boxed_history ( extra) ;
658
+ }
631
659
}
632
660
633
661
fn layout_areas ( & self , area : Rect ) -> [ Rect ; 3 ] {
634
662
let bottom_min = self . bottom_pane . desired_height ( area. width ) . min ( area. height ) ;
635
663
let remaining = area. height . saturating_sub ( bottom_min) ;
636
664
637
665
let active_desired = self
638
- . active_exec_cell
666
+ . active_cell
639
667
. as_ref ( )
640
668
. map_or ( 0 , |c| c. desired_height ( area. width ) + 1 ) ;
641
669
let active_height = active_desired. min ( remaining) ;
@@ -680,7 +708,7 @@ impl ChatWidget {
680
708
placeholder_text : placeholder,
681
709
disable_paste_burst : config. disable_paste_burst ,
682
710
} ) ,
683
- active_exec_cell : None ,
711
+ active_cell : None ,
684
712
config : config. clone ( ) ,
685
713
auth_manager,
686
714
session_header : SessionHeader :: new ( config. model . clone ( ) ) ,
@@ -736,7 +764,7 @@ impl ChatWidget {
736
764
placeholder_text : placeholder,
737
765
disable_paste_burst : config. disable_paste_burst ,
738
766
} ) ,
739
- active_exec_cell : None ,
767
+ active_cell : None ,
740
768
config : config. clone ( ) ,
741
769
auth_manager,
742
770
session_header : SessionHeader :: new ( config. model . clone ( ) ) ,
@@ -762,7 +790,7 @@ impl ChatWidget {
762
790
pub fn desired_height ( & self , width : u16 ) -> u16 {
763
791
self . bottom_pane . desired_height ( width)
764
792
+ self
765
- . active_exec_cell
793
+ . active_cell
766
794
. as_ref ( )
767
795
. map_or ( 0 , |c| c. desired_height ( width) + 1 )
768
796
}
@@ -974,10 +1002,9 @@ impl ChatWidget {
974
1002
}
975
1003
}
976
1004
977
- fn flush_active_exec_cell ( & mut self ) {
978
- if let Some ( active) = self . active_exec_cell . take ( ) {
979
- self . app_event_tx
980
- . send ( AppEvent :: InsertHistoryCell ( Box :: new ( active) ) ) ;
1005
+ fn flush_active_cell ( & mut self ) {
1006
+ if let Some ( active) = self . active_cell . take ( ) {
1007
+ self . app_event_tx . send ( AppEvent :: InsertHistoryCell ( active) ) ;
981
1008
}
982
1009
}
983
1010
@@ -988,7 +1015,7 @@ impl ChatWidget {
988
1015
fn add_boxed_history ( & mut self , cell : Box < dyn HistoryCell > ) {
989
1016
if !cell. display_lines ( u16:: MAX ) . is_empty ( ) {
990
1017
// Only break exec grouping if the cell renders visible lines.
991
- self . flush_active_exec_cell ( ) ;
1018
+ self . flush_active_cell ( ) ;
992
1019
}
993
1020
self . app_event_tx . send ( AppEvent :: InsertHistoryCell ( cell) ) ;
994
1021
}
@@ -1166,12 +1193,16 @@ impl ChatWidget {
1166
1193
}
1167
1194
}
1168
1195
1169
- /// Mark the active exec cell as failed (✗) and flush it into history.
1170
- fn finalize_active_exec_cell_as_failed ( & mut self ) {
1171
- if let Some ( cell) = self . active_exec_cell . take ( ) {
1172
- let cell = cell. into_failed ( ) ;
1173
- // Insert finalized exec into history and keep grouping consistent.
1174
- self . add_to_history ( cell) ;
1196
+ /// Mark the active cell as failed (✗) and flush it into history.
1197
+ fn finalize_active_cell_as_failed ( & mut self ) {
1198
+ if let Some ( mut cell) = self . active_cell . take ( ) {
1199
+ // Insert finalized cell into history and keep grouping consistent.
1200
+ if let Some ( exec) = cell. as_any_mut ( ) . downcast_mut :: < ExecCell > ( ) {
1201
+ exec. mark_failed ( ) ;
1202
+ } else if let Some ( tool) = cell. as_any_mut ( ) . downcast_mut :: < McpToolCallCell > ( ) {
1203
+ tool. mark_failed ( ) ;
1204
+ }
1205
+ self . add_boxed_history ( cell) ;
1175
1206
}
1176
1207
}
1177
1208
@@ -1468,12 +1499,16 @@ impl WidgetRef for &ChatWidget {
1468
1499
let [ _, active_cell_area, bottom_pane_area] = self . layout_areas ( area) ;
1469
1500
( & self . bottom_pane ) . render ( bottom_pane_area, buf) ;
1470
1501
if !active_cell_area. is_empty ( )
1471
- && let Some ( cell) = & self . active_exec_cell
1502
+ && let Some ( cell) = & self . active_cell
1472
1503
{
1473
- let mut active_cell_area = active_cell_area;
1474
- active_cell_area. y = active_cell_area. y . saturating_add ( 1 ) ;
1475
- active_cell_area. height -= 1 ;
1476
- cell. render_ref ( active_cell_area, buf) ;
1504
+ let mut area = active_cell_area;
1505
+ area. y = area. y . saturating_add ( 1 ) ;
1506
+ area. height = area. height . saturating_sub ( 1 ) ;
1507
+ if let Some ( exec) = cell. as_any ( ) . downcast_ref :: < ExecCell > ( ) {
1508
+ exec. render_ref ( area, buf) ;
1509
+ } else if let Some ( tool) = cell. as_any ( ) . downcast_ref :: < McpToolCallCell > ( ) {
1510
+ tool. render_ref ( area, buf) ;
1511
+ }
1477
1512
}
1478
1513
}
1479
1514
}
0 commit comments