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:
@@ -1061,6 +1061,10 @@ def _session_tool_progress_mode(sid: str) -> str:
|
||||
return str(_sessions.get(sid, {}).get("tool_progress_mode", "all") or "all")
|
||||
|
||||
|
||||
def _session_verbose(sid: str) -> bool:
|
||||
return _session_tool_progress_mode(sid) == "verbose"
|
||||
|
||||
|
||||
def _tool_progress_enabled(sid: str) -> bool:
|
||||
return _session_tool_progress_mode(sid) != "off"
|
||||
|
||||
@@ -1492,6 +1496,74 @@ def _tool_ctx(name: str, args: dict) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
_TUI_VERBOSE_TEXT_MAX_CHARS = 16_000
|
||||
_TUI_VERBOSE_TEXT_MAX_LINES = 240
|
||||
|
||||
|
||||
def _cap_tui_verbose_text(text: str) -> str:
|
||||
if (
|
||||
len(text) <= _TUI_VERBOSE_TEXT_MAX_CHARS
|
||||
and text.count("\n") < _TUI_VERBOSE_TEXT_MAX_LINES
|
||||
):
|
||||
return text
|
||||
|
||||
idx = len(text)
|
||||
start = 0
|
||||
for _ in range(_TUI_VERBOSE_TEXT_MAX_LINES):
|
||||
idx = text.rfind("\n", 0, idx)
|
||||
if idx < 0:
|
||||
start = 0
|
||||
break
|
||||
start = idx + 1
|
||||
|
||||
line_start = start
|
||||
start = max(line_start, len(text) - _TUI_VERBOSE_TEXT_MAX_CHARS)
|
||||
if start > line_start:
|
||||
next_break = text.find("\n", start)
|
||||
if 0 <= next_break < len(text) - 1:
|
||||
start = next_break + 1
|
||||
|
||||
tail = text[start:].lstrip()
|
||||
omitted_chars = max(0, len(text) - len(tail))
|
||||
omitted_lines = text[:start].count("\n")
|
||||
if omitted_lines:
|
||||
label = (
|
||||
"[showing verbose tail; omitted "
|
||||
f"{omitted_lines} lines / {omitted_chars} chars]\n"
|
||||
)
|
||||
else:
|
||||
label = f"[showing verbose tail; omitted {omitted_chars} chars]\n"
|
||||
return f"{label}{tail}"
|
||||
|
||||
|
||||
def _redact_tui_verbose_text(text: str) -> str:
|
||||
try:
|
||||
from agent.redact import redact_sensitive_text
|
||||
|
||||
redacted = redact_sensitive_text(str(text), force=True)
|
||||
except Exception:
|
||||
return ""
|
||||
return _cap_tui_verbose_text(redacted)
|
||||
|
||||
|
||||
def _tool_args_text(args: dict) -> str:
|
||||
try:
|
||||
raw = json.dumps(args or {}, indent=2, ensure_ascii=False, default=str)
|
||||
except Exception:
|
||||
raw = str(args or {})
|
||||
return _redact_tui_verbose_text(raw)
|
||||
|
||||
|
||||
def _tool_result_text(result: object) -> str:
|
||||
try:
|
||||
from agent.tool_dispatch_helpers import _multimodal_text_summary
|
||||
|
||||
raw = _multimodal_text_summary(result)
|
||||
except Exception:
|
||||
raw = str(result)
|
||||
return _redact_tui_verbose_text(raw)
|
||||
|
||||
|
||||
def _fmt_tool_duration(seconds: float | None) -> str:
|
||||
if seconds is None:
|
||||
return ""
|
||||
@@ -1553,13 +1625,18 @@ def _on_tool_start(sid: str, tool_call_id: str, name: str, args: dict):
|
||||
pass
|
||||
session.setdefault("tool_started_at", {})[tool_call_id] = time.time()
|
||||
if _tool_progress_enabled(sid):
|
||||
payload = {
|
||||
"tool_id": tool_call_id,
|
||||
"name": name,
|
||||
"context": _tool_ctx(name, args),
|
||||
}
|
||||
if _session_verbose(sid):
|
||||
args_text = _tool_args_text(args)
|
||||
if args_text:
|
||||
payload["args_text"] = args_text
|
||||
# tool.complete is the source of truth for todos (full list from the
|
||||
# tool result). args.todos here may be a partial merge update.
|
||||
_emit(
|
||||
"tool.start",
|
||||
sid,
|
||||
{"tool_id": tool_call_id, "name": name, "context": _tool_ctx(name, args)},
|
||||
)
|
||||
_emit("tool.start", sid, payload)
|
||||
|
||||
|
||||
def _on_tool_complete(sid: str, tool_call_id: str, name: str, args: dict, result: str):
|
||||
@@ -1576,6 +1653,10 @@ def _on_tool_complete(sid: str, tool_call_id: str, name: str, args: dict, result
|
||||
summary = _tool_summary(name, result, duration_s)
|
||||
if summary:
|
||||
payload["summary"] = summary
|
||||
if _session_verbose(sid):
|
||||
result_text = _tool_result_text(result)
|
||||
if result_text:
|
||||
payload["result_text"] = result_text
|
||||
if name == "todo":
|
||||
try:
|
||||
data = json.loads(result)
|
||||
@@ -1615,7 +1696,10 @@ def _on_tool_progress(
|
||||
_emit("tool.progress", sid, {"name": name, "preview": preview or ""})
|
||||
return
|
||||
if event_type == "reasoning.available" and preview:
|
||||
_emit("reasoning.available", sid, {"text": str(preview)})
|
||||
payload: dict[str, object] = {"text": str(preview)}
|
||||
if _session_verbose(sid):
|
||||
payload["verbose"] = True
|
||||
_emit("reasoning.available", sid, payload)
|
||||
return
|
||||
if event_type.startswith("subagent."):
|
||||
payload = {
|
||||
@@ -1691,7 +1775,11 @@ def _agent_cbs(sid: str) -> dict:
|
||||
"tool_gen_callback": lambda name: _tool_progress_enabled(sid)
|
||||
and _emit("tool.generating", sid, {"name": name}),
|
||||
"thinking_callback": lambda text: _emit("thinking.delta", sid, {"text": text}),
|
||||
"reasoning_callback": lambda text: _emit("reasoning.delta", sid, {"text": text}),
|
||||
"reasoning_callback": lambda text: _emit(
|
||||
"reasoning.delta",
|
||||
sid,
|
||||
{"text": text, **({"verbose": True} if _session_verbose(sid) else {})},
|
||||
),
|
||||
"status_callback": lambda kind, text=None: _status_update(
|
||||
sid, str(kind), None if text is None else str(text)
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user