mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-15 09:21:36 +00:00
feat(cli,tui): show time since last final agent response on the status bar (#44265)
Adds an idle clock to the context/status bar in both the prompt_toolkit CLI and the Ink TUI: once a turn completes, a dim '✓ <elapsed>' segment shows how long the session has been idle since the last final agent response. Hidden while a turn is live (the per-prompt elapsed timer covers that) and before the first turn completes. - cli.py: track _last_turn_finished_at when the agent thread exits, surface it via _format_idle_since() in the snapshot, render in both the wide fragments path and the plain-text fallback. - ui-tui: stamp lastTurnEndedAt when busy flips false after a live turn, thread it through appStatus -> StatusRule, render via a ticking IdleSince segment sharing the duration breakpoint/width budget.
This commit is contained in:
parent
a2d7f538d4
commit
8972a151a4
7 changed files with 186 additions and 2 deletions
29
cli.py
29
cli.py
|
|
@ -3426,6 +3426,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
|
|||
# frozen when the agent thread completes, displayed in the status bar.
|
||||
self._prompt_start_time: Optional[float] = None # time.time() when turn started
|
||||
self._prompt_duration: float = 0.0 # frozen duration of last completed turn
|
||||
self._last_turn_finished_at: Optional[float] = None # time.time() when the last agent loop finished
|
||||
# Initialize SQLite session store early so /title works before first message
|
||||
self._session_db = None
|
||||
try:
|
||||
|
|
@ -3812,6 +3813,19 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
|
|||
emoji = "⏱" if live else "⏲"
|
||||
return f"{emoji} {time_str}"
|
||||
|
||||
@staticmethod
|
||||
def _format_idle_since(last_finished_at: Optional[float], turn_live: bool) -> str:
|
||||
"""Format time since the last final agent response for the status bar.
|
||||
|
||||
Returns an empty string while a turn is live (the per-prompt elapsed
|
||||
timer covers that case) or before the first turn has completed.
|
||||
Compact read-out: ``✓ 42s`` / ``✓ 3m`` / ``✓ 1h 12m``.
|
||||
"""
|
||||
if turn_live or last_finished_at is None:
|
||||
return ""
|
||||
idle = max(0.0, time.time() - last_finished_at)
|
||||
return f"✓ {format_duration_compact(idle)}"
|
||||
|
||||
def _get_status_bar_snapshot(self) -> Dict[str, Any]:
|
||||
# Prefer the agent's model name — it updates on fallback.
|
||||
# self.model reflects the originally configured model and never
|
||||
|
|
@ -3835,6 +3849,10 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
|
|||
getattr(self, "_prompt_duration", 0.0),
|
||||
live=getattr(self, "_prompt_start_time", None) is not None,
|
||||
),
|
||||
"idle_since": self._format_idle_since(
|
||||
getattr(self, "_last_turn_finished_at", None),
|
||||
turn_live=getattr(self, "_prompt_start_time", None) is not None,
|
||||
),
|
||||
"context_tokens": 0,
|
||||
"context_length": None,
|
||||
"context_percent": None,
|
||||
|
|
@ -4146,6 +4164,9 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
|
|||
prompt_elapsed = snapshot.get("prompt_elapsed")
|
||||
if prompt_elapsed:
|
||||
parts.append(prompt_elapsed)
|
||||
idle_since = snapshot.get("idle_since")
|
||||
if idle_since:
|
||||
parts.append(idle_since)
|
||||
if yolo_active:
|
||||
parts.append("⚠ YOLO")
|
||||
return self._trim_status_bar_text(" │ ".join(parts), width)
|
||||
|
|
@ -4247,6 +4268,11 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
|
|||
if prompt_elapsed:
|
||||
frags.append(("class:status-bar-dim", " │ "))
|
||||
frags.append(("class:status-bar-dim", prompt_elapsed))
|
||||
# Position 8: idle time since the last final agent response
|
||||
idle_since = snapshot.get("idle_since")
|
||||
if idle_since:
|
||||
frags.append(("class:status-bar-dim", " │ "))
|
||||
frags.append(("class:status-bar-dim", idle_since))
|
||||
if yolo_active:
|
||||
frags.append(("class:status-bar-dim", " │ "))
|
||||
frags.append(("class:status-bar-yolo", "⚠ YOLO"))
|
||||
|
|
@ -10162,6 +10188,9 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
|
|||
if self._prompt_start_time is not None:
|
||||
self._prompt_duration = max(0.0, time.time() - self._prompt_start_time)
|
||||
self._prompt_start_time = None
|
||||
# Record when this agent loop finished so the status bar can show
|
||||
# idle time since the last final response.
|
||||
self._last_turn_finished_at = time.time()
|
||||
|
||||
# Proactively clean up async clients whose event loop is dead.
|
||||
# The agent thread may have created AsyncOpenAI clients bound
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue