fix(cli): run human-feedback declarative flows on the terminal, not the TUI

Two review follow-ups, both rooted in @human_feedback methods:

- Paused flow marked complete (Cursor): async human feedback makes kickoff
  RETURN a HumanFeedbackPending marker (not raise), which _run_flow_worker
  would stringify and report as a successful completion with exit 0.
- Sync feedback breaks TUI (Cursor): default (sync) @human_feedback collects
  input via the flow runtime's Rich console.print + blocking input(), which
  interleaves with Textual and leaves the user unable to review output or
  submit feedback.

run_declarative_flow now routes any flow whose declarative definition declares
human feedback (_flow_uses_human_feedback) to the terminal path, where blocking
input and Rich prompts work natively — regardless of interactivity. Non-feedback
flows still get the TUI. Tests: test_flow_uses_human_feedback_detection,
test_human_feedback_flow_uses_terminal_even_when_interactive.

Fully interactive human feedback inside the TUI remains a separate follow-up.

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 12:38:40 -07:00
parent 3a7196f56a
commit b8381b9c73
2 changed files with 62 additions and 1 deletions

View File

@@ -553,6 +553,48 @@ def test_run_declarative_flow_tui_enables_flow_events(
assert flow.suppress_flow_events is False
def test_flow_uses_human_feedback_detection() -> None:
hf_flow = SimpleNamespace(
_definition=SimpleNamespace(
methods={
"ask": SimpleNamespace(human_feedback=SimpleNamespace(emit=None)),
"plain": SimpleNamespace(human_feedback=None),
}
)
)
assert run_declarative_flow_module._flow_uses_human_feedback(hf_flow) is True
no_hf = SimpleNamespace(
_definition=SimpleNamespace(
methods={"a": SimpleNamespace(human_feedback=None)}
)
)
assert run_declarative_flow_module._flow_uses_human_feedback(no_hf) is False
# No definition → False, no error.
assert run_declarative_flow_module._flow_uses_human_feedback(SimpleNamespace()) is False
def test_human_feedback_flow_uses_terminal_even_when_interactive(
tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
# A human-feedback flow must run on the terminal (blocking input / Rich
# prompts) even in an interactive session, never on the TUI.
monkeypatch.setattr(run_declarative_flow_module, "is_interactive", lambda: True)
monkeypatch.setattr(
run_declarative_flow_module, "_flow_uses_human_feedback", lambda flow: True
)
monkeypatch.setattr(
run_declarative_flow_module,
"_run_declarative_flow_tui",
lambda *a, **k: pytest.fail("human-feedback flow must run on the terminal"),
)
path = _write(tmp_path, FLOW_YAML)
run_declarative_flow_module.run_declarative_flow(str(path), '{"topic":"AI"}')
assert capsys.readouterr().out == "AI\n"
def test_flow_method_types_from_definition() -> None:
flow = SimpleNamespace(
_definition=SimpleNamespace(