@@ -415,6 +415,7 @@ impl App {
415415 & output,
416416 metadata. as_ref ( ) ,
417417 args. as_ref ( ) ,
418+ self . width as usize ,
418419 ) ) ;
419420 self . tool_args . clear ( ) ;
420421 }
@@ -602,8 +603,14 @@ async fn run_smoke(session: Arc<AgentSession>) -> anyhow::Result<()> {
602603 AgentEvent :: TextDelta { text } => print ! ( "{text}" ) ,
603604 AgentEvent :: ToolStart { name, .. } => eprintln ! ( "\n [tool start] {name}" ) ,
604605 AgentEvent :: ToolEnd {
605- name, exit_code, ..
606- } => eprintln ! ( "[tool end] {name} (exit {exit_code})" ) ,
606+ name,
607+ exit_code,
608+ output,
609+ ..
610+ } => eprintln ! (
611+ "[tool end] {name} (exit {exit_code}): {}" ,
612+ output. lines( ) . take( 2 ) . collect:: <Vec <_>>( ) . join( " | " )
613+ ) ,
607614 AgentEvent :: ConfirmationRequired {
608615 tool_id, tool_name, ..
609616 } => {
@@ -629,6 +636,7 @@ fn render_tool_end(
629636 output : & str ,
630637 meta : Option < & serde_json:: Value > ,
631638 args : Option < & serde_json:: Value > ,
639+ width : usize ,
632640) -> String {
633641 if let Some ( meta) = meta {
634642 if let ( Some ( before) , Some ( after) , Some ( path) ) = (
@@ -642,17 +650,63 @@ fn render_tool_end(
642650 let status = if exit_code == 0 { "✓" } else { "✗" } ;
643651 // Show the tool's primary argument (command/path/pattern) so the action log
644652 // reads like Codex — "✓ bash — npm test" rather than just "✓ bash".
645- let header = match args. and_then ( arg_summary) {
646- Some ( summary) => format ! ( " {status} {name} — {summary}" ) ,
647- None => format ! ( " {status} {name}" ) ,
648- } ;
649- let head = output. lines ( ) . take ( 6 ) . collect :: < Vec < _ > > ( ) . join ( "\n " ) ;
650- let body = if head. trim ( ) . is_empty ( ) {
651- header
652- } else {
653- format ! ( "{header}\n {head}" )
654- } ;
655- Style :: new ( ) . fg ( Color :: BrightBlack ) . render ( & body)
653+ let header = Style :: new ( )
654+ . fg ( Color :: BrightBlack )
655+ . render ( & match args. and_then ( arg_summary) {
656+ Some ( summary) => format ! ( " {status} {name} — {summary}" ) ,
657+ None => format ! ( " {status} {name}" ) ,
658+ } ) ;
659+ let head = output. lines ( ) . take ( 8 ) . collect :: < Vec < _ > > ( ) . join ( "\n " ) ;
660+ if head. trim ( ) . is_empty ( ) {
661+ return header;
662+ }
663+ // If the output is file/code content (read/edit on a known extension),
664+ // syntax-highlight it; otherwise show it dimmed.
665+ if exit_code == 0 {
666+ if let Some ( lang) = args
667+ . and_then ( |a| {
668+ a. get ( "file_path" )
669+ . or_else ( || a. get ( "path" ) )
670+ . and_then ( |v| v. as_str ( ) )
671+ } )
672+ . and_then ( lang_from_path)
673+ {
674+ let fenced = format ! ( "```{lang}\n {head}\n ```" ) ;
675+ let rendered = a3s_tui:: markdown:: Markdown :: new ( )
676+ . with_width ( width. saturating_sub ( 4 ) . max ( 20 ) )
677+ . render ( & fenced) ;
678+ return format ! ( "{header}\n {rendered}" ) ;
679+ }
680+ }
681+ format ! (
682+ "{header}\n {}" ,
683+ Style :: new( ) . fg( Color :: BrightBlack ) . render( & head)
684+ )
685+ }
686+
687+ /// Map a file path to a syntect language token for fenced rendering.
688+ fn lang_from_path ( path : & str ) -> Option < & ' static str > {
689+ let ext = path. rsplit ( '.' ) . next ( ) ?;
690+ Some ( match ext {
691+ "rs" => "rust" ,
692+ "py" => "python" ,
693+ "js" | "mjs" | "cjs" => "javascript" ,
694+ "ts" | "tsx" => "typescript" ,
695+ "go" => "go" ,
696+ "json" => "json" ,
697+ "toml" => "toml" ,
698+ "yaml" | "yml" => "yaml" ,
699+ "md" => "markdown" ,
700+ "sh" | "bash" => "bash" ,
701+ "c" | "h" => "c" ,
702+ "cpp" | "cc" | "hpp" => "cpp" ,
703+ "java" => "java" ,
704+ "rb" => "ruby" ,
705+ "html" => "html" ,
706+ "css" => "css" ,
707+ "sql" => "sql" ,
708+ _ => return None ,
709+ } )
656710}
657711
658712/// Extract a one-line summary of a tool's primary argument.
@@ -865,7 +919,7 @@ mod tests {
865919 "before" : "let a = 1;\n keep;\n " ,
866920 "after" : "let a = 2;\n keep;\n " ,
867921 } ) ;
868- let out = render_tool_end ( "edit" , 0 , "ok" , Some ( & meta) , None ) ;
922+ let out = render_tool_end ( "edit" , 0 , "ok" , Some ( & meta) , None , 80 ) ;
869923 assert ! ( out. contains( "src/x.rs" ) , "header has path" ) ;
870924 assert ! ( out. contains( "+1" ) && out. contains( "-1" ) , "add/del counts" ) ;
871925 assert ! ( out. contains( "let a = 2;" ) , "shows inserted line" ) ;
@@ -875,15 +929,15 @@ mod tests {
875929
876930 #[ test]
877931 fn non_edit_tool_renders_status_line ( ) {
878- let out = render_tool_end ( "bash" , 0 , "hello\n world" , None , None ) ;
932+ let out = render_tool_end ( "bash" , 0 , "hello\n world" , None , None , 80 ) ;
879933 assert ! ( out. contains( "bash" ) && out. contains( "hello" ) ) ;
880934 assert ! ( !out. contains( '✎' ) , "no diff marker for non-edit tools" ) ;
881935 }
882936
883937 #[ test]
884938 fn tool_end_shows_primary_arg_summary ( ) {
885939 let args = serde_json:: json!( { "command" : "npm test" , "timeout" : 60 } ) ;
886- let out = render_tool_end ( "bash" , 0 , "ok\n " , None , Some ( & args) ) ;
940+ let out = render_tool_end ( "bash" , 0 , "ok\n " , None , Some ( & args) , 80 ) ;
887941 assert ! ( out. contains( "bash" ) ) ;
888942 assert ! ( out. contains( "npm test" ) , "shows the command argument" ) ;
889943 }
0 commit comments