diff --git a/cli.py b/cli.py index 95c2839a1d..007b6e1eba 100644 --- a/cli.py +++ b/cli.py @@ -1719,6 +1719,7 @@ class HermesCLI: self._secret_state = None self._secret_deadline = 0 self._spinner_text: str = "" # thinking spinner text for TUI + self._tool_start_time: float = 0.0 # monotonic timestamp when current tool started (for live elapsed) self._command_running = False self._command_status = "" self._attached_images: list[Path] = [] @@ -2130,6 +2131,7 @@ class HermesCLI: if not text: self._flush_reasoning_preview(force=True) self._spinner_text = text or "" + self._tool_start_time = 0.0 # clear tool timer when switching to thinking self._invalidate() # ── Streaming display ──────────────────────────────────────────────── @@ -6145,11 +6147,20 @@ class HermesCLI: Updates the TUI spinner widget so the user can see what the agent is doing during tool execution (fills the gap between thinking spinner and next response). Also plays audio cue in voice mode. + + On tool.started, records a monotonic timestamp so get_spinner_text() + can show a live elapsed timer (the TUI poll loop already invalidates + every ~0.15s, so the counter updates automatically). """ - # Only act on tool.started; ignore tool.completed, reasoning.available, etc. + if event_type == "tool.completed": + import time as _time + self._tool_start_time = 0.0 + self._invalidate() + return if event_type != "tool.started": return if function_name and not function_name.startswith("_"): + import time as _time from agent.display import get_tool_emoji emoji = get_tool_emoji(function_name) label = preview or function_name @@ -6158,6 +6169,7 @@ class HermesCLI: if _pl > 0 and len(label) > _pl: label = label[:_pl - 3] + "..." self._spinner_text = f"{emoji} {label}" + self._tool_start_time = _time.monotonic() self._invalidate() if not self._voice_mode: @@ -8359,6 +8371,17 @@ class HermesCLI: txt = cli_ref._spinner_text if not txt: return [] + # Append live elapsed timer when a tool is running + t0 = cli_ref._tool_start_time + if t0 > 0: + import time as _time + elapsed = _time.monotonic() - t0 + if elapsed >= 60: + _m, _s = int(elapsed // 60), int(elapsed % 60) + elapsed_str = f"{_m}m {_s}s" + else: + elapsed_str = f"{elapsed:.1f}s" + return [('class:hint', f' {txt} ({elapsed_str})')] return [('class:hint', f' {txt}')] def get_spinner_height(): @@ -8893,6 +8916,7 @@ class HermesCLI: finally: self._agent_running = False self._spinner_text = "" + self._tool_start_time = 0.0 app.invalidate() # Refresh status line