fix(tui): suppress Bot Mode silence markers
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
"""Regression coverage for Bot Mode's shared completed-reply delivery boundary."""
|
||||
|
||||
import contextlib
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
import tui_gateway.server as srv
|
||||
from tui_gateway.prompt_turn import _bot_mode_delivery_text, _is_bot_mode_session
|
||||
|
||||
|
||||
@pytest.mark.parametrize("response", [
|
||||
"NO_REPLY", " [silent] ", "silent", "no reply", "*NO_REPLY*",
|
||||
])
|
||||
def test_bot_mode_delivery_hides_successful_canonical_silence_markers(response):
|
||||
assert _bot_mode_delivery_text(response, successful=True) == ""
|
||||
|
||||
|
||||
@pytest.mark.parametrize("response", [
|
||||
"The NO_REPLY marker means do not answer.",
|
||||
"[SILENT] is mentioned here, but this is a real answer.",
|
||||
])
|
||||
def test_bot_mode_delivery_keeps_substantive_marker_mentions(response):
|
||||
assert _bot_mode_delivery_text(response, successful=True) == response
|
||||
|
||||
|
||||
def test_bot_mode_delivery_keeps_failed_marker_response_visible():
|
||||
assert _bot_mode_delivery_text("NO_REPLY", successful=False) == "NO_REPLY"
|
||||
|
||||
|
||||
def test_only_canonical_bot_chat_sessions_use_the_live_delivery_boundary():
|
||||
assert _is_bot_mode_session({"pending_title": "Bot Chat"})
|
||||
assert _is_bot_mode_session({"title": "Bot Chat"})
|
||||
assert not _is_bot_mode_session({"pending_title": "Scratch"})
|
||||
|
||||
|
||||
def test_live_bot_chat_completion_suppresses_markers_but_failed_turns_fail_open(monkeypatch):
|
||||
"""The prompt.submit completion path applies the shared delivery boundary."""
|
||||
monkeypatch.setattr(srv, "_get_usage", lambda _agent: {})
|
||||
monkeypatch.setattr(srv, "render_message", lambda _text, _cols: None)
|
||||
monkeypatch.setattr(srv, "_clear_inflight_turn", lambda _session: None)
|
||||
|
||||
session = {"pending_title": "Bot Chat", "history_lock": contextlib.nullcontext()}
|
||||
turn = SimpleNamespace(
|
||||
result={"final_response": "NO_REPLY"}, agent=object(), terminal_callback=None,
|
||||
receipt_committed=True, receipt_attempted=False, marker_key="", error_retained=False,
|
||||
error_detail="", prompt_text="ping",
|
||||
)
|
||||
payload, _, status = srv._complete_turn_payload(session, turn, None, 80)
|
||||
assert status == "complete"
|
||||
assert payload["text"] == ""
|
||||
|
||||
turn.result = {"final_response": "NO_REPLY", "error": "provider failed", "failed": True}
|
||||
payload, _, status = srv._complete_turn_payload(session, turn, None, 80)
|
||||
assert status == "error"
|
||||
assert payload["text"] == "NO_REPLY"
|
||||
@@ -108,6 +108,40 @@ def test_deliver_requires_params(home):
|
||||
assert "error" in err
|
||||
|
||||
|
||||
@pytest.mark.parametrize("reply", [
|
||||
"NO_REPLY", " [silent] ", "silent", "no reply", "*NO_REPLY*",
|
||||
])
|
||||
def test_deliver_suppresses_successful_silence_markers(home, monkeypatch, reply):
|
||||
"""A successful subprocess relay never returns a bare silence marker to Bot Mode."""
|
||||
class _Proc:
|
||||
returncode = 0
|
||||
stdout = reply
|
||||
stderr = ""
|
||||
|
||||
monkeypatch.setattr("subprocess.run", lambda *_args, **_kwargs: _Proc())
|
||||
|
||||
out = _result(srv._methods["bot_relay.deliver"](1, {"profile": "ops", "message": "ping"}))
|
||||
|
||||
assert out["reply"] == ""
|
||||
|
||||
|
||||
@pytest.mark.parametrize("reply", [
|
||||
"The NO_REPLY marker means do not answer.",
|
||||
"[SILENT] is mentioned here, but this is a real answer.",
|
||||
])
|
||||
def test_deliver_keeps_substantive_marker_mentions(home, monkeypatch, reply):
|
||||
class _Proc:
|
||||
returncode = 0
|
||||
stdout = reply
|
||||
stderr = ""
|
||||
|
||||
monkeypatch.setattr("subprocess.run", lambda *_args, **_kwargs: _Proc())
|
||||
|
||||
out = _result(srv._methods["bot_relay.deliver"](1, {"profile": "ops", "message": "ping"}))
|
||||
|
||||
assert out["reply"] == reply
|
||||
|
||||
|
||||
def test_deliver_lands_in_live_bot_chat_instead_of_subprocess(home, monkeypatch):
|
||||
"""#100523: a Desktop-owned Bot Chat receives the DM as a normal user turn.
|
||||
|
||||
|
||||
@@ -147,7 +147,12 @@ def _(rid, params: dict, _root=_relay_root, _run=_run_delivery) -> dict:
|
||||
detail = _detail(proc)
|
||||
return _err(rid, 5092, f"delivery turn failed: {detail or proc.returncode}",
|
||||
data={"reason": classify_agent_error(detail)})
|
||||
return _ok(rid, {"reply": (proc.stdout or "").strip()})
|
||||
# Use the same canonical whole-response predicate as live Bot Chat
|
||||
# completion. A marker remains a successful turn, but is never sent
|
||||
# back to the relay caller as visible prose.
|
||||
from tui_gateway.prompt_turn import _bot_mode_delivery_text
|
||||
reply = _bot_mode_delivery_text((proc.stdout or "").strip(), successful=True)
|
||||
return _ok(rid, {"reply": reply})
|
||||
except subprocess.TimeoutExpired:
|
||||
return _err(rid, 5093, "delivery turn timed out")
|
||||
except Exception as e:
|
||||
|
||||
@@ -16,6 +16,24 @@ from .method_ctx import HandlerRegistry, bind_module
|
||||
_registry = HandlerRegistry()
|
||||
|
||||
|
||||
def _bot_mode_delivery_text(response: Any, *, successful: bool) -> Any:
|
||||
"""Return the text Bot Mode may render or relay after a completed turn.
|
||||
|
||||
The gateway owns the canonical marker set. Keep the row and completion
|
||||
event intact, but make a successful bare marker invisible at every Bot
|
||||
Mode delivery boundary. Failed turns intentionally fail open so their
|
||||
diagnostic text is never swallowed.
|
||||
"""
|
||||
from gateway.response_filters import is_intentional_silence_response
|
||||
return "" if successful and is_intentional_silence_response(response) else response
|
||||
|
||||
|
||||
def _is_bot_mode_session(session: dict) -> bool:
|
||||
"""Whether this completion belongs to the canonical Bot Chat surface."""
|
||||
from tools.bot_mode_probe import BOT_CHAT_TITLE
|
||||
return any(session.get(field) == BOT_CHAT_TITLE for field in ("pending_title", "title"))
|
||||
|
||||
|
||||
def _hook_failure(what: str, exc: BaseException) -> None:
|
||||
print(f"[tui_gateway] {what} failed: {type(exc).__name__}: {exc}", file=sys.stderr)
|
||||
|
||||
@@ -638,6 +656,8 @@ def _complete_turn_payload(session: dict, st: _TurnRun, status_note: str | None,
|
||||
except Exception:
|
||||
_error_surface = None
|
||||
raw, status, last_reasoning = _turn_outcome(result, _error_surface)
|
||||
if _is_bot_mode_session(session):
|
||||
raw = _bot_mode_delivery_text(raw, successful=status == "complete")
|
||||
payload = {"text": raw, "usage": _get_usage(agent), "status": status}
|
||||
if last_reasoning:
|
||||
payload["reasoning"] = last_reasoning
|
||||
|
||||
Reference in New Issue
Block a user