diff --git a/lib/cli/src/crewai_cli/crew_run_tui.py b/lib/cli/src/crewai_cli/crew_run_tui.py index 8fe08b456..ae66005ad 100644 --- a/lib/cli/src/crewai_cli/crew_run_tui.py +++ b/lib/cli/src/crewai_cli/crew_run_tui.py @@ -737,6 +737,9 @@ FooterKey .footer-key--key { self._current_method = next( (s["name"] for s in self._flow_steps if s["status"] == "active"), None ) + # The active method changed; drop its agent so the header doesn't show a + # stale agent until the next method's agent event arrives. + self._current_agent = "" def _on_crew_done(self, output: str | None) -> None: with self._lock: @@ -2036,6 +2039,9 @@ FooterKey .footer-key--key { with self._lock: name = event.method_name self._current_method = name + # Agent is per-method; clear it so the header doesn't show the + # previous method's agent until a new agent event arrives. + self._current_agent = "" for step in self._flow_steps: if step["name"] == name: step["status"] = "active" diff --git a/lib/cli/tests/test_crew_run_tui.py b/lib/cli/tests/test_crew_run_tui.py index 90c4da562..9bdac231f 100644 --- a/lib/cli/tests/test_crew_run_tui.py +++ b/lib/cli/tests/test_crew_run_tui.py @@ -1586,6 +1586,34 @@ def test_current_method_clears_and_falls_back_across_overlap() -> None: app._unsubscribe() +def test_flow_method_transitions_clear_current_agent() -> None: + app = CrewRunApp(crew_name="Demo") + app._flow = SimpleNamespace() + app._subscribe() + try: + _emit_event( + MethodExecutionStartedEvent(flow_name="Demo", method_name="a", state={}) + ) + app._current_agent = "Researcher" # an agent ran during method 'a' + + # Starting a new method clears the previous method's agent. + _emit_event( + MethodExecutionStartedEvent(flow_name="Demo", method_name="b", state={}) + ) + assert app._current_agent == "" + + app._current_agent = "Writer" + # 'b' ending switches the active method ('a' still active) → agent clears. + _emit_event( + MethodExecutionFinishedEvent( + flow_name="Demo", method_name="b", result=None, state={} + ) + ) + assert app._current_agent == "" + finally: + app._unsubscribe() + + @pytest.mark.asyncio async def test_declarative_flow_runs_on_tui() -> None: """End-to-end: on_mount dispatches _run_flow_worker → flow.kickoff → diff --git a/lib/crewai/src/crewai/events/utils/console_formatter.py b/lib/crewai/src/crewai/events/utils/console_formatter.py index 604c7b051..858dde0ac 100644 --- a/lib/crewai/src/crewai/events/utils/console_formatter.py +++ b/lib/crewai/src/crewai/events/utils/console_formatter.py @@ -211,6 +211,13 @@ To enable tracing, do any one of these: """Print a panel with consistent formatting if verbose is enabled.""" panel = self.create_panel(content, title, style) if is_flow: + # A TUI (e.g. the CLI's CrewRunApp) owns the screen and renders flow + # progress in its own STEPS panel; emitting Rich panels here would + # interleave with and corrupt the TUI, so suppress them in TUI mode. + from crewai.events.listeners.tracing.utils import is_tui_mode + + if is_tui_mode(): + return self.print(panel) self.print() else: diff --git a/lib/crewai/tests/utilities/test_console_formatter_tui_mode.py b/lib/crewai/tests/utilities/test_console_formatter_tui_mode.py new file mode 100644 index 000000000..cee544748 --- /dev/null +++ b/lib/crewai/tests/utilities/test_console_formatter_tui_mode.py @@ -0,0 +1,46 @@ +"""Flow panels must be suppressed while a TUI owns the screen.""" + +from rich.text import Text + +from crewai.events.listeners.tracing.utils import set_tui_mode +from crewai.events.utils.console_formatter import ConsoleFormatter + + +def _make_formatter(monkeypatch): + fmt = ConsoleFormatter(verbose=True) + calls: list[object] = [] + monkeypatch.setattr(fmt, "print", lambda *a, **k: calls.append(a)) + return fmt, calls + + +def test_flow_panel_suppressed_in_tui_mode(monkeypatch): + fmt, calls = _make_formatter(monkeypatch) + set_tui_mode(True) + try: + fmt.print_panel(Text("x"), "🌊 Flow Started", "blue", is_flow=True) + finally: + set_tui_mode(False) + + assert calls == [] + + +def test_flow_panel_prints_when_not_tui_mode(monkeypatch): + fmt, calls = _make_formatter(monkeypatch) + set_tui_mode(False) + + fmt.print_panel(Text("x"), "🌊 Flow Started", "blue", is_flow=True) + + # Panel + trailing blank line. + assert len(calls) == 2 + + +def test_non_flow_panel_unaffected_by_tui_mode(monkeypatch): + # tui_mode only gates flow panels; regular panels still follow verbose. + fmt, calls = _make_formatter(monkeypatch) + set_tui_mode(True) + try: + fmt.print_panel(Text("x"), "Task", "blue", is_flow=False) + finally: + set_tui_mode(False) + + assert len(calls) == 2