mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-08 16:35:08 +00:00
fix(cli): clear flow header's current method when a method ends
Review follow-up: the flow header keys off _current_method, which was set on MethodExecutionStarted but never cleared on Finished/Failed. Between steps (or after a failed method before kickoff exits) the header kept spinning the old method name while the STEPS sidebar already showed it done/failed. _clear_current_method now drops the header's active method when it ends, falling back to another still-active step (methods can overlap) or none. The header's idle fallback shows "Working…" once a step has run and "Starting flow…" only before the first method. Tests: test_current_method_clears_and_falls_back_across_overlap, plus a _current_method assertion in test_flow_method_events_build_steps. 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:
@@ -725,6 +725,19 @@ FooterKey .footer-key--key {
|
||||
step["status"] = status
|
||||
return
|
||||
|
||||
def _clear_current_method(self, finished_name: str) -> None:
|
||||
"""Drop the header's active method once it ends. Caller holds the lock.
|
||||
|
||||
Falls back to another still-active step (methods can overlap) so the
|
||||
header never keeps spinning a method the STEPS list already shows as
|
||||
done or failed.
|
||||
"""
|
||||
if self._current_method != finished_name:
|
||||
return
|
||||
self._current_method = next(
|
||||
(s["name"] for s in self._flow_steps if s["status"] == "active"), None
|
||||
)
|
||||
|
||||
def _on_crew_done(self, output: str | None) -> None:
|
||||
with self._lock:
|
||||
self._status = "completed"
|
||||
@@ -1341,7 +1354,12 @@ FooterKey .footer-key--key {
|
||||
t.append(self._current_agent, style=f"bold {_C_TEXT}")
|
||||
else:
|
||||
t.append(f"{self._spinner()} ", style=_C_PRIMARY)
|
||||
t.append("Starting flow…", style=_C_DIM)
|
||||
# "Working…" once a step has run (between/after methods);
|
||||
# "Starting flow…" only before the first method.
|
||||
t.append(
|
||||
"Working…" if self._flow_steps else "Starting flow…",
|
||||
style=_C_DIM,
|
||||
)
|
||||
widget.update(t)
|
||||
return
|
||||
|
||||
@@ -2039,6 +2057,7 @@ FooterKey .footer-key--key {
|
||||
) -> None:
|
||||
with self._lock:
|
||||
self._set_flow_step_status(event.method_name, "done")
|
||||
self._clear_current_method(event.method_name)
|
||||
|
||||
self._register_handler(MethodExecutionFinishedEvent, on_method_finished)
|
||||
|
||||
@@ -2046,6 +2065,7 @@ FooterKey .footer-key--key {
|
||||
def on_method_failed(source: Any, event: MethodExecutionFailedEvent) -> None:
|
||||
with self._lock:
|
||||
self._set_flow_step_status(event.method_name, "failed")
|
||||
self._clear_current_method(event.method_name)
|
||||
|
||||
self._register_handler(MethodExecutionFailedEvent, on_method_failed)
|
||||
|
||||
|
||||
@@ -1550,6 +1550,40 @@ def test_flow_method_events_build_steps() -> None:
|
||||
{"name": "research", "call_type": "crew", "status": "done"},
|
||||
{"name": "summarize", "call_type": "agent", "status": "failed"},
|
||||
]
|
||||
# The header must not keep spinning a method that already ended.
|
||||
assert app._current_method is None
|
||||
|
||||
|
||||
def test_current_method_clears_and_falls_back_across_overlap() -> None:
|
||||
app = CrewRunApp(crew_name="Demo")
|
||||
app._flow = SimpleNamespace()
|
||||
app._subscribe()
|
||||
try:
|
||||
_emit_event(
|
||||
MethodExecutionStartedEvent(flow_name="Demo", method_name="a", state={})
|
||||
)
|
||||
_emit_event(
|
||||
MethodExecutionStartedEvent(flow_name="Demo", method_name="b", state={})
|
||||
)
|
||||
assert app._current_method == "b"
|
||||
|
||||
# 'a' finishes while 'b' is still active → header stays on 'b'.
|
||||
_emit_event(
|
||||
MethodExecutionFinishedEvent(
|
||||
flow_name="Demo", method_name="a", result=None, state={}
|
||||
)
|
||||
)
|
||||
assert app._current_method == "b"
|
||||
|
||||
# 'b' finishes → nothing active left → header clears.
|
||||
_emit_event(
|
||||
MethodExecutionFinishedEvent(
|
||||
flow_name="Demo", method_name="b", result=None, state={}
|
||||
)
|
||||
)
|
||||
assert app._current_method is None
|
||||
finally:
|
||||
app._unsubscribe()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user