fix: suppress flow console panels in TUI mode; clear header agent on method change

Two review follow-ups:

1) Method panels break Textual TUI (Cursor): forcing suppress_flow_events off
   so the STEPS panel receives events also un-gated the EventListener's Rich
   flow/method panels (ConsoleFormatter.print_panel prints is_flow=True panels
   regardless of verbose), which interleave with Textual and corrupt the TUI.
   print_panel now skips is_flow panels when is_tui_mode() is set (the same
   context the TUI worker already establishes and the tracing listeners already
   honor). Non-TUI/headless flow runs are unaffected. Test:
   test_console_formatter_tui_mode.

2) Flow header showed a stale agent (CodeRabbit): _current_agent persisted
   across methods. It's now cleared when a method starts and when the active
   method changes, so the header never shows the previous method's agent until
   a new agent event arrives. Test: test_flow_method_transitions_clear_current_agent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBYGqJHC2TMC6fonFziuuh
This commit is contained in:
Joao Moura
2026-07-08 08:28:26 -07:00
parent ec55c31fed
commit 46cae1a50f
4 changed files with 87 additions and 0 deletions

View File

@@ -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"

View File

@@ -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 →

View File

@@ -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:

View File

@@ -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