@@ -75,6 +75,9 @@ def __init__(self, project_name: str, console: Optional[Console] = None):
7575 self .is_complete = False
7676 self .final_status : Optional [str ] = None
7777
78+ # Report information
79+ self .report_data : Optional [dict ] = None # Report metadata (path, status, metrics)
80+
7881 # Live display
7982 self .live : Optional [Live ] = None
8083
@@ -140,6 +143,178 @@ def _format_tool_params(self, tool_name: str, params: dict) -> str:
140143 return f"({ ', ' .join (formatted_parts )} )"
141144 return ""
142145
146+ def _detect_phase_from_action (self , tool_name : str , tool_params : dict ) -> Optional [PhaseType ]:
147+ """
148+ Detect which phase the agent is in based on tool usage.
149+
150+ Args:
151+ tool_name: Name of the tool being used
152+ tool_params: Tool parameters
153+
154+ Returns:
155+ PhaseType if phase detected, None otherwise
156+ """
157+ # Already in verification or all phases complete - don't transition back
158+ if self .current_phase == PhaseType .VERIFICATION :
159+ return None
160+
161+ # Report tool = verification phase
162+ if tool_name == "report" :
163+ return PhaseType .VERIFICATION
164+
165+ # Check for test-related activities
166+ if tool_name in ["maven" , "gradle" ]:
167+ goal = tool_params .get ("goal" , "" )
168+ task = tool_params .get ("task" , "" )
169+ action = tool_params .get ("action" , "" )
170+
171+ # Test phase indicators
172+ if "test" in goal .lower () or "test" in task .lower () or "test" in action .lower ():
173+ return PhaseType .TEST
174+
175+ # Build phase indicators (compile, package, install)
176+ if any (keyword in goal .lower () or keyword in task .lower ()
177+ for keyword in ["compile" , "package" , "install" , "build" , "assemble" ]):
178+ return PhaseType .BUILD
179+
180+ # Bash commands
181+ if tool_name == "bash" :
182+ command = tool_params .get ("command" , "" )
183+ command_lower = command .lower ()
184+
185+ # Test indicators in bash commands
186+ if any (keyword in command_lower for keyword in ["mvn test" , "gradle test" , "pytest" , "npm test" , "test" ]):
187+ return PhaseType .TEST
188+
189+ # Build indicators in bash commands
190+ if any (keyword in command_lower for keyword in ["mvn compile" , "mvn package" , "mvn install" ,
191+ "gradle build" , "gradle assemble" , "make" , "npm run build" ]):
192+ return PhaseType .BUILD
193+
194+ return None
195+
196+ def _extract_thought_summary (self , thought : str ) -> str :
197+ """
198+ Extract a meaningful summary from agent thought.
199+
200+ Args:
201+ thought: Full thought content
202+
203+ Returns:
204+ Concise summary (30-80 chars) with ellipsis
205+ """
206+ # Remove common prefixes
207+ thought = thought .strip ()
208+ thought = thought .replace ("I need to " , "" ).replace ("I should " , "" ).replace ("I will " , "" )
209+
210+ # Find first sentence or meaningful chunk
211+ sentences = thought .split (". " )
212+ if sentences :
213+ summary = sentences [0 ].strip ()
214+ # Limit length
215+ if len (summary ) > 80 :
216+ summary = summary [:77 ] + "..."
217+ elif len (summary ) < 20 :
218+ # If too short, include second sentence if available
219+ if len (sentences ) > 1 :
220+ summary = f"{ summary } . { sentences [1 ][:40 ]} ..."
221+ else :
222+ summary = summary + "..."
223+
224+ return summary
225+
226+ # Fallback
227+ return thought [:77 ] + "..." if len (thought ) > 80 else thought
228+
229+ def _extract_observation_summary (self , observation : str ) -> str :
230+ """
231+ Extract a meaningful summary from agent observation.
232+
233+ Args:
234+ observation: Full observation content
235+
236+ Returns:
237+ Concise summary (50-100 chars) with ellipsis
238+ """
239+ # Clean up observation
240+ observation = observation .strip ()
241+
242+ # Look for key indicators of success/failure
243+ if "successfully" in observation .lower () or "success" in observation .lower ():
244+ # Extract success message
245+ lines = observation .split ("\n " )
246+ for line in lines :
247+ if "success" in line .lower ():
248+ summary = line .strip ()
249+ if len (summary ) > 100 :
250+ return summary [:97 ] + "..."
251+ return summary + "..."
252+
253+ # Look for error indicators
254+ if "error" in observation .lower () or "failed" in observation .lower ():
255+ lines = observation .split ("\n " )
256+ for line in lines :
257+ if "error" in line .lower () or "failed" in line .lower ():
258+ summary = line .strip ()
259+ if len (summary ) > 100 :
260+ return summary [:97 ] + "..."
261+ return summary + "..."
262+
263+ # Default: first meaningful line
264+ lines = observation .split ("\n " )
265+ for line in lines :
266+ line = line .strip ()
267+ if len (line ) > 10 : # Skip very short lines
268+ if len (line ) > 100 :
269+ return line [:97 ] + "..."
270+ return line + "..."
271+
272+ # Fallback
273+ return observation [:97 ] + "..." if len (observation ) > 100 else observation
274+
275+ def _format_report_summary (self ) -> Panel :
276+ """
277+ Format report summary panel for display.
278+
279+ Returns:
280+ Rich Panel with report information
281+ """
282+ if not self .report_data :
283+ return Panel ("No report data available" , border_style = "yellow" )
284+
285+ # Extract report data
286+ report_path = self .report_data .get ("report_path" , "Unknown" )
287+ status = self .report_data .get ("status" , "unknown" )
288+ build_success = self .report_data .get ("build_success" , False )
289+ test_success = self .report_data .get ("test_success" , False )
290+ total_tests = self .report_data .get ("total_tests" , 0 )
291+ passed_tests = self .report_data .get ("passed_tests" , 0 )
292+ test_pass_rate = self .report_data .get ("test_pass_rate" , 0 )
293+
294+ # Calculate pass rate if not provided or if it's 0 but we have test data
295+ if test_pass_rate == 0 and total_tests > 0 :
296+ test_pass_rate = (passed_tests / total_tests ) * 100
297+
298+ # Build content
299+ content = f"📄 [bold cyan]Final Report Generated[/bold cyan]\n \n "
300+ content += f" [cyan]Location:[/cyan] { report_path } \n "
301+ content += f" [cyan]Status:[/cyan] { status .upper ()} \n \n "
302+
303+ content += " [bold]Results:[/bold]\n "
304+ build_icon = "✅" if build_success else "❌"
305+ content += f" { build_icon } Build: { 'SUCCESS' if build_success else 'FAILED' } \n "
306+
307+ if total_tests > 0 :
308+ test_icon = "✅" if test_success else "❌"
309+ content += f" { test_icon } Tests: { passed_tests } /{ total_tests } passed ({ test_pass_rate :.1f} %)\n "
310+
311+ return Panel (
312+ content ,
313+ title = "📊 Setup Report" ,
314+ border_style = "green" if status == "success" else "yellow" ,
315+ padding = (1 , 2 )
316+ )
317+
143318 def handle_event (self , event : UIEvent ):
144319 """
145320 Handle a UI event and update the display
@@ -170,6 +345,8 @@ def handle_event(self, event: UIEvent):
170345 self ._handle_success (event )
171346 elif event .event_type == EventType .FAILURE :
172347 self ._handle_failure (event )
348+ elif event .event_type == EventType .REPORT_GENERATED :
349+ self ._handle_report_generated (event )
173350 elif event .event_type in [EventType .AGENT_THOUGHT , EventType .AGENT_ACTION , EventType .AGENT_OBSERVATION ]:
174351 self ._handle_agent_event (event )
175352
@@ -263,6 +440,18 @@ def _handle_failure(self, event: UIEvent):
263440 self .final_status = "failure"
264441 self .current_status = event .message
265442
443+ def _handle_report_generated (self , event : UIEvent ):
444+ """Handle report generation event"""
445+ # Store report metadata
446+ self .report_data = event .metadata
447+
448+ # Complete verification phase
449+ if self .current_phase == PhaseType .VERIFICATION :
450+ self .phases_data [PhaseType .VERIFICATION ]["status" ] = "success"
451+
452+ # Update status
453+ self .current_status = "Report generated"
454+
266455 def _handle_agent_event (self , event : UIEvent ):
267456 """Handle agent ReAct events (thought, action, observation)"""
268457 # Track agent steps for collapsible display
@@ -271,8 +460,11 @@ def _handle_agent_event(self, event: UIEvent):
271460 self .agent_current_step_num = event .metadata .get ("step_num" , self .agent_current_step_num + 1 )
272461 self .agent_current_action = "thinking"
273462 self .agent_current_tool = None
274- self .agent_detail = f"Step { self .agent_current_step_num } : Analyzing situation..."
275- self .current_status = "Agent thinking"
463+
464+ # Extract meaningful summary from thought
465+ thought_summary = self ._extract_thought_summary (event .message )
466+ self .agent_detail = f"Step { self .agent_current_step_num } : { thought_summary } "
467+ self .current_status = thought_summary
276468
277469 self .current_agent_step = {
278470 "thought" : event .message ,
@@ -281,6 +473,7 @@ def _handle_agent_event(self, event: UIEvent):
281473 "status" : "running"
282474 }
283475 self .agent_steps .append (self .current_agent_step )
476+
284477 elif event .event_type == EventType .AGENT_ACTION and self .current_agent_step :
285478 self .agent_current_action = "acting"
286479 tool_name = event .metadata .get ("tool_name" , "unknown" )
@@ -289,6 +482,18 @@ def _handle_agent_event(self, event: UIEvent):
289482 self .agent_current_tool = tool_name
290483 self .agent_tool_params = tool_params
291484
485+ # Detect phase transition based on tool usage
486+ detected_phase = self ._detect_phase_from_action (tool_name , tool_params )
487+ if detected_phase and detected_phase != self .current_phase :
488+ # Transition to new phase
489+ # Complete previous phase if it was running
490+ if self .current_phase and self .phases_data [self .current_phase ]["status" ] == "running" :
491+ self .phases_data [self .current_phase ]["status" ] = "success"
492+
493+ # Start new phase
494+ self .current_phase = detected_phase
495+ self .phases_data [detected_phase ]["status" ] = "running"
496+
292497 # Format parameters for display
293498 params_str = self ._format_tool_params (tool_name , tool_params )
294499
@@ -301,10 +506,14 @@ def _handle_agent_event(self, event: UIEvent):
301506 self .current_status = f"Using { tool_name } "
302507
303508 self .current_agent_step ["action" ] = event .message
509+
304510 elif event .event_type == EventType .AGENT_OBSERVATION and self .current_agent_step :
305511 self .agent_current_action = "observing"
306- self .agent_detail = f"Step { self .agent_current_step_num } : Processing results..."
307- self .current_status = "Processing observation"
512+
513+ # Extract meaningful summary from observation
514+ observation_summary = self ._extract_observation_summary (event .message )
515+ self .agent_detail = f"Step { self .agent_current_step_num } : { observation_summary } "
516+ self .current_status = observation_summary
308517
309518 self .current_agent_step ["observation" ] = event .message
310519 self .current_agent_step ["status" ] = "complete"
@@ -394,17 +603,8 @@ def _render_display(self):
394603 )
395604 elements .append (warning_panel )
396605
397- # 5. Final status if complete
398- if self .is_complete and self .final_status == "success" :
399- elements .append ("" ) # Spacing
400- success_panel = create_success_panel (
401- self .current_status ,
402- summary_items = [
403- ("Total time" , elapsed_str ),
404- ("Phases completed" , f"{ sum (1 for p in self .phases_data .values () if p ['status' ] == 'success' )} /4" )
405- ]
406- )
407- elements .append (success_panel )
606+ # Note: Final success panel is NOT shown here to avoid duplication
607+ # It will be shown in display_final_summary() instead
408608
409609 return Group (* elements )
410610
@@ -443,6 +643,12 @@ def display_final_summary(self):
443643 )
444644 self .console .print (error_panel )
445645
646+ # Print report information if available
647+ if self .report_data :
648+ self .console .print ()
649+ report_info = self ._format_report_summary ()
650+ self .console .print (report_info )
651+
446652 # Print detailed phase tree with all steps expanded
447653 self .console .print ()
448654 self .console .print (Panel ("📋 Detailed Execution Log" , border_style = "cyan" ))
0 commit comments