106 lines
3.9 KiB
Python
106 lines
3.9 KiB
Python
"""`hermes chat -Q` must not leak presentation output into stdout (#93220).
|
|
|
|
The quiet single-query branch lives inline in ``cli.py``'s main flow (no
|
|
standalone function to call), so these pin the branch's required statements
|
|
at the source level — the established convention for behavior with no
|
|
runtime mirror (see the install.ps1 source-text tests). Deleting any
|
|
neutralization reintroduces a leak:
|
|
|
|
- ``reasoning_callback`` → streaming ``┌─ Reasoning ─┐`` box
|
|
- ``tool_complete_callback`` → full file diffs via render_edit_diff_with_delta
|
|
- ``tool_progress_callback`` → MoA reference blocks (printed before the
|
|
mode check in _on_tool_progress)
|
|
- ``tool_start_callback`` → snapshot capture paired with tool_complete
|
|
- ``agent.tool_progress_mode`` → executor's direct progress prints
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import cli as cli_mod
|
|
|
|
_QUIET_ANCHOR = "# Quiet mode: suppress banner, spinner, tool previews."
|
|
|
|
|
|
def _quiet_branch() -> str:
|
|
"""Return the source slice of the quiet single-query branch."""
|
|
source = Path(cli_mod.__file__).read_text(encoding="utf-8")
|
|
start = source.index(_QUIET_ANCHOR)
|
|
# A generous window covers the whole branch body up to the
|
|
# run_conversation call and beyond.
|
|
return source[start : start + 8000]
|
|
|
|
|
|
def test_quiet_branch_clears_reasoning_callback():
|
|
branch = _quiet_branch()
|
|
assert "cli.agent.reasoning_callback = None" in branch, (
|
|
"-Q must clear the reasoning callback on the live agent, or the "
|
|
"streaming Reasoning box renders into captured stdout (#93220)."
|
|
)
|
|
|
|
|
|
def test_quiet_branch_clears_inline_diff_callbacks():
|
|
branch = _quiet_branch()
|
|
for attr in (
|
|
"cli.agent.tool_start_callback = None",
|
|
"cli.agent.tool_complete_callback = None",
|
|
):
|
|
assert attr in branch, (
|
|
f"-Q must set `{attr}`: the inline-diff callbacks print full "
|
|
"file diffs via render_edit_diff_with_delta and are gated by "
|
|
"neither quiet_mode nor tool_progress_mode (#93220)."
|
|
)
|
|
|
|
|
|
def test_quiet_branch_clears_tool_progress_callback():
|
|
branch = _quiet_branch()
|
|
assert "cli.agent.tool_progress_callback = None" in branch, (
|
|
"-Q must clear tool_progress_callback: _on_tool_progress prints "
|
|
"MoA reference blocks before its tool_progress_mode check (#93220)."
|
|
)
|
|
|
|
|
|
def test_quiet_branch_syncs_tool_progress_off_to_agent():
|
|
branch = _quiet_branch()
|
|
assert 'cli.agent.tool_progress_mode = "off"' in branch, (
|
|
"-Q must sync tool_progress_mode='off' to the live agent so the "
|
|
"tool_executor rendering path stays silent (#93220)."
|
|
)
|
|
|
|
|
|
def test_suppress_status_output_gates_quiet_tool_messages():
|
|
"""The executor's [tool]/[done] fallback must stay silent under -Q.
|
|
|
|
``_should_emit_quiet_tool_messages`` is the gate for the quiet-mode
|
|
KawaiiSpinner fallback in agent/tool_executor.py; with the rendering
|
|
callbacks neutralized it would otherwise print ``[tool]``/``[done]``
|
|
lines straight into -Q's captured stdout (#93220).
|
|
"""
|
|
from run_agent import AIAgent
|
|
|
|
agent = object.__new__(AIAgent)
|
|
agent.quiet_mode = True
|
|
agent.tool_progress_callback = None
|
|
agent.platform = "cli"
|
|
|
|
agent.suppress_status_output = False
|
|
assert agent._should_emit_quiet_tool_messages() is True
|
|
|
|
agent.suppress_status_output = True
|
|
assert agent._should_emit_quiet_tool_messages() is False
|
|
|
|
|
|
def test_quiet_branch_neutralizations_precede_run_conversation():
|
|
branch = _quiet_branch()
|
|
run_idx = branch.index("run_conversation(")
|
|
for attr in (
|
|
"cli.agent.reasoning_callback = None",
|
|
"cli.agent.tool_progress_callback = None",
|
|
"cli.agent.tool_start_callback = None",
|
|
"cli.agent.tool_complete_callback = None",
|
|
):
|
|
assert branch.index(attr) < run_idx, (
|
|
f"`{attr}` must run before run_conversation in the quiet branch"
|
|
)
|