diff --git a/src/crewai/utilities/events/event_listener.py b/src/crewai/utilities/events/event_listener.py index 1917eca1f..0e5301e0c 100644 --- a/src/crewai/utilities/events/event_listener.py +++ b/src/crewai/utilities/events/event_listener.py @@ -162,7 +162,7 @@ class EventListener(BaseEventListener): span = self._telemetry.task_started(crew=source.agent.crew, task=source) self.execution_spans[source] = span self.formatter.create_task_branch( - self.formatter.current_crew_tree, source.id + self.formatter.current_crew_tree, source ) @crewai_event_bus.on(TaskCompletedEvent) @@ -175,7 +175,7 @@ class EventListener(BaseEventListener): self.formatter.update_task_status( self.formatter.current_crew_tree, - source.id, + source, source.agent.role, "completed", ) @@ -190,7 +190,7 @@ class EventListener(BaseEventListener): self.formatter.update_task_status( self.formatter.current_crew_tree, - source.id, + source, source.agent.role, "failed", ) diff --git a/src/crewai/utilities/events/utils/console_formatter.py b/src/crewai/utilities/events/utils/console_formatter.py index 5039aa1be..9c02f0446 100644 --- a/src/crewai/utilities/events/utils/console_formatter.py +++ b/src/crewai/utilities/events/utils/console_formatter.py @@ -220,14 +220,16 @@ class ConsoleFormatter: return tree def create_task_branch( - self, crew_tree: Optional[Tree], task_id: str + self, crew_tree: Optional[Tree], task: Any ) -> Optional[Tree]: """Create and initialize a task branch.""" if not self.verbose: return None + task_display = self._get_task_display_name(task) + task_content = Text() - task_content.append(f"📋 Task: {task_id}", style="yellow bold") + task_content.append(f"📋 Task: {task_display}", style="yellow bold") task_content.append("\nStatus: ", style="white") task_content.append("Executing Task...", style="yellow dim") @@ -248,7 +250,7 @@ class ConsoleFormatter: def update_task_status( self, crew_tree: Optional[Tree], - task_id: str, + task: Any, agent_role: str, status: str = "completed", ) -> None: @@ -256,6 +258,9 @@ class ConsoleFormatter: if not self.verbose or crew_tree is None: return + task_display = self._get_task_display_name(task) + task_id = str(task.id) + if status == "completed": style = "green" status_text = "✅ Completed" @@ -267,11 +272,11 @@ class ConsoleFormatter: # Update tree label for branch in crew_tree.children: - if str(task_id) in str(branch.label): + if task_id in str(branch.label): # Build label without introducing stray blank lines task_content = Text() - # First line: Task ID - task_content.append(f"📋 Task: {task_id}", style=f"{style} bold") + # First line: Task name/description + task_content.append(f"📋 Task: {task_display}", style=f"{style} bold") # Second line: Assigned to task_content.append("\nAssigned to: ", style="white") @@ -286,7 +291,7 @@ class ConsoleFormatter: # Show status panel content = self.create_status_content( - f"Task {status.title()}", str(task_id), style, Agent=agent_role + f"Task {status.title()}", task_display, style, Agent=agent_role ) self.print_panel(content, panel_title, style) @@ -1754,3 +1759,13 @@ class ConsoleFormatter: Attempts=f"{retry_count + 1}", ) self.print_panel(content, "🛡️ Guardrail Failed", "red") + + def _get_task_display_name(self, task: Any) -> str: + """Get display name for a task, with fallback logic.""" + if hasattr(task, 'name') and task.name: + return f"{task.name} (ID: {str(task.id)[:8]}...)" + elif hasattr(task, 'description') and task.description: + desc = task.description[:50] + "..." if len(task.description) > 50 else task.description + return f"{desc} (ID: {str(task.id)[:8]}...)" + else: + return str(task.id) diff --git a/tests/utilities/test_console_formatter_verbose.py b/tests/utilities/test_console_formatter_verbose.py new file mode 100644 index 000000000..0d3ec7024 --- /dev/null +++ b/tests/utilities/test_console_formatter_verbose.py @@ -0,0 +1,96 @@ +import pytest +from unittest.mock import Mock, patch +from crewai.utilities.events.utils.console_formatter import ConsoleFormatter + + +class TestConsoleFormatterVerbose: + """Test verbose output functionality in console formatter.""" + + def setup_method(self): + """Set up test fixtures.""" + self.formatter = ConsoleFormatter(verbose=True) + + def test_get_task_display_name_with_name(self): + """Test task display name when task has a name.""" + task = Mock() + task.name = "Research Market Trends" + task.id = "12345678-1234-5678-9012-123456789abc" + + result = self.formatter._get_task_display_name(task) + assert "Research Market Trends" in result + assert "12345678" in result + + def test_get_task_display_name_with_description_only(self): + """Test task display name when task has no name but has description.""" + task = Mock() + task.name = None + task.description = "Analyze current market trends and provide insights" + task.id = "12345678-1234-5678-9012-123456789abc" + + result = self.formatter._get_task_display_name(task) + assert "Analyze current market trends" in result + assert "12345678" in result + + def test_get_task_display_name_long_description_truncated(self): + """Test task display name truncates long descriptions.""" + task = Mock() + task.name = None + task.description = "This is a very long task description that should be truncated because it exceeds the maximum length" + task.id = "12345678-1234-5678-9012-123456789abc" + + result = self.formatter._get_task_display_name(task) + assert len(result.split("(ID:")[0].strip()) <= 53 + assert "..." in result + + def test_get_task_display_name_fallback_to_id(self): + """Test task display name falls back to ID when no name or description.""" + task = Mock() + task.name = None + task.description = None + task.id = "12345678-1234-5678-9012-123456789abc" + + result = self.formatter._get_task_display_name(task) + assert result == "12345678-1234-5678-9012-123456789abc" + + @patch('crewai.utilities.events.utils.console_formatter.ConsoleFormatter.print') + def test_create_task_branch_uses_task_name(self, mock_print): + """Test create_task_branch displays task name instead of ID.""" + task = Mock() + task.name = "Write Blog Post" + task.id = "12345678-1234-5678-9012-123456789abc" + + crew_tree = Mock() + crew_tree.add.return_value = Mock() + + self.formatter.create_task_branch(crew_tree, task) + + call_args = crew_tree.add.call_args[0][0] + assert "Write Blog Post" in str(call_args) + assert "12345678" in str(call_args) + + @patch('crewai.utilities.events.utils.console_formatter.ConsoleFormatter.print') + def test_update_task_status_uses_task_name(self, mock_print): + """Test update_task_status displays task name instead of ID.""" + task = Mock() + task.name = "Data Analysis" + task.id = "12345678-1234-5678-9012-123456789abc" + + crew_tree = Mock() + branch = Mock() + branch.label = "12345678-1234-5678-9012-123456789abc" + crew_tree.children = [branch] + + self.formatter.update_task_status(crew_tree, task, "Data Analyst", "completed") + + updated_label = branch.label + assert "Data Analysis" in str(updated_label) + + def test_verbose_disabled_returns_none(self): + """Test that methods return None when verbose is disabled.""" + formatter = ConsoleFormatter(verbose=False) + task = Mock() + + result = formatter.create_task_branch(Mock(), task) + assert result is None + + formatter.update_task_status(Mock(), task, "Agent", "completed")