fix(tui): surface verbose tool details (#30225)

* fix(tui): surface verbose tool details

Emit redacted structured verbose args/results to the TUI so /verbose verbose can show full tool detail without reopening stdout, and fail closed if redaction is unavailable.

Salvages #29011.

Co-authored-by: helix4u <4317663+helix4u@users.noreply.github.com>

* fix(tui): address verbose detail review

Label verbose tool failures as errors, cover forced verbose reasoning, and avoid new diff type warnings from the redaction regression tests.

* fix(tui): bound verbose tool payloads

Cap verbose tool detail text before emitting JSON-RPC events and preserve verbose results on inline diff completions.

* fix(tui): align termux argv test with gc flag

Update the stale TUI launch expectation so the Termux freshness path matches the current direct Node argv.

---------

Co-authored-by: helix4u <4317663+helix4u@users.noreply.github.com>
This commit is contained in:
brooklyn!
2026-05-22 00:16:52 -05:00
committed by GitHub
parent 4e2c66a098
commit 1264fab156
11 changed files with 306 additions and 38 deletions

View File

@@ -168,7 +168,7 @@ def test_make_tui_argv_skips_build_only_on_termux_when_fresh(
argv, cwd = main_mod._make_tui_argv(tmp_path, tui_dev=False)
assert argv == ["/bin/node", str(tmp_path / "dist" / "entry.js")]
assert argv == ["/bin/node", "--expose-gc", str(tmp_path / "dist" / "entry.js")]
assert cwd == tmp_path

View File

@@ -59,6 +59,59 @@ def test_write_json_returns_false_on_broken_pipe(monkeypatch):
assert server.write_json({"ok": True}) is False
def test_tui_verbose_tool_details_fail_closed_when_redaction_fails(monkeypatch):
redact_module = types.ModuleType("agent.redact")
def fail_redaction(*_args, **_kwargs):
raise RuntimeError("redaction unavailable")
setattr(redact_module, "redact_sensitive_text", fail_redaction)
monkeypatch.setitem(sys.modules, "agent.redact", redact_module)
assert server._redact_tui_verbose_text("api_key=secret") == ""
assert server._tool_args_text({"api_key": "secret"}) == ""
assert server._tool_result_text("token=secret") == ""
def test_tui_verbose_tool_details_are_capped_before_emit(monkeypatch):
monkeypatch.setattr(server, "_TUI_VERBOSE_TEXT_MAX_CHARS", 12)
monkeypatch.setattr(server, "_TUI_VERBOSE_TEXT_MAX_LINES", 2)
capped = server._cap_tui_verbose_text("one\ntwo\nthree\nfour")
assert capped.startswith("[showing verbose tail; omitted ")
assert capped.endswith("three\nfour")
assert "one" not in capped
def test_tui_verbose_tool_events_omit_details_when_redaction_fails(monkeypatch):
redact_module = types.ModuleType("agent.redact")
def fail_redaction(*_args, **_kwargs):
raise RuntimeError("redaction unavailable")
setattr(redact_module, "redact_sensitive_text", fail_redaction)
monkeypatch.setitem(sys.modules, "agent.redact", redact_module)
events: list[tuple[str, str, dict]] = []
monkeypatch.setattr(
server, "_emit", lambda event_type, sid, payload: events.append((event_type, sid, payload))
)
monkeypatch.setitem(
server._sessions,
"redaction-test",
{"tool_progress_mode": "verbose", "tool_started_at": {}},
)
server._on_tool_start("redaction-test", "tool-1", "terminal", {"command": "pwd"})
server._on_tool_complete("redaction-test", "tool-1", "terminal", {"command": "pwd"}, "done")
assert events[0][0] == "tool.start"
assert events[1][0] == "tool.complete"
assert "args_text" not in events[0][2]
assert "result_text" not in events[1][2]
def test_dispatch_rejects_non_object_request():
resp = server.dispatch([])