mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
feat(cli): /focus — reduced-output view with hidden-line recovery and status indicator
Display-only port of Claude Code /focus; composes with existing /verbose tool-progress modes.
This commit is contained in:
parent
e1ace0ac98
commit
d6fa2709de
16 changed files with 1199 additions and 1 deletions
86
cli.py
86
cli.py
|
|
@ -4113,6 +4113,25 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
# YAML 1.1 parses bare `off` as boolean False — normalise to string.
|
||||
_raw_tp = CLI_CONFIG["display"].get("tool_progress", "all")
|
||||
self.tool_progress_mode = "off" if _raw_tp is False else str(_raw_tp)
|
||||
# focus_view: display-only reduced-output mode (/focus). When on, the
|
||||
# tool-progress mode is snapped to "off" so the EXISTING suppression
|
||||
# path hides per-tool lines, and the pre-focus mode is stashed so
|
||||
# /focus off restores it. Purely cosmetic — never changes what is sent
|
||||
# to the model. See hermes_cli/focus_view.py.
|
||||
self._focus_view_enabled = bool(CLI_CONFIG["display"].get("focus_view", False))
|
||||
self._focus_saved_tool_progress = None
|
||||
self._focus_hidden_lines = 0
|
||||
self._focus_last_counted_tool = None
|
||||
if self._focus_view_enabled:
|
||||
from hermes_cli.focus_view import (
|
||||
FOCUS_TOOL_PROGRESS_MODE,
|
||||
normalize_tool_progress_mode,
|
||||
)
|
||||
|
||||
self._focus_saved_tool_progress = normalize_tool_progress_mode(
|
||||
self.tool_progress_mode
|
||||
)
|
||||
self.tool_progress_mode = FOCUS_TOOL_PROGRESS_MODE
|
||||
# resume_display: "full" (show history) | "minimal" (one-liner only)
|
||||
self.resume_display = CLI_CONFIG["display"].get("resume_display", "full")
|
||||
# bell_on_complete: play terminal bell (\a) when agent finishes a response
|
||||
|
|
@ -5091,8 +5110,20 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
"active_background_subagents": 0,
|
||||
"battery_label": "",
|
||||
"battery_category": "dim",
|
||||
# Focus view badge (/focus). Persistent indicator so the reduced
|
||||
# output mode is never invisible. Display-only.
|
||||
"focus_label": "",
|
||||
}
|
||||
|
||||
try:
|
||||
from hermes_cli.focus_view import focus_statusbar_segment
|
||||
|
||||
snapshot["focus_label"] = focus_statusbar_segment(
|
||||
bool(getattr(self, "_focus_view_enabled", False))
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Battery read-out (first status-bar element when enabled). Reads are
|
||||
# memoised for a few seconds inside agent.battery, so polling it on
|
||||
# every status-bar repaint is cheap.
|
||||
|
|
@ -5715,6 +5746,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
duration_label = snapshot["duration"]
|
||||
battery_label = snapshot.get("battery_label") or ""
|
||||
battery_prefix = f"{battery_label} │ " if battery_label else ""
|
||||
focus_label = snapshot.get("focus_label") or ""
|
||||
|
||||
yolo_active = self._is_session_yolo_active()
|
||||
goal_segment = self._status_bar_goal_segment(snapshot)
|
||||
|
|
@ -5722,6 +5754,8 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
text = f"{battery_prefix}⚕ {snapshot['model_short']} · {duration_label}"
|
||||
if goal_segment:
|
||||
text += f" · {goal_segment}"
|
||||
if focus_label:
|
||||
text += f" · {focus_label}"
|
||||
if yolo_active:
|
||||
text += " · ⚠ YOLO"
|
||||
return self._trim_status_bar_text(text, width)
|
||||
|
|
@ -5744,6 +5778,8 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
if goal_segment:
|
||||
parts.append(goal_segment)
|
||||
parts.append(duration_label)
|
||||
if focus_label:
|
||||
parts.append(focus_label)
|
||||
if yolo_active:
|
||||
parts.append("⚠ YOLO")
|
||||
return self._trim_status_bar_text(" · ".join(parts), width)
|
||||
|
|
@ -5779,6 +5815,8 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
idle_since = snapshot.get("idle_since")
|
||||
if idle_since:
|
||||
parts.append(idle_since)
|
||||
if focus_label:
|
||||
parts.append(focus_label)
|
||||
if yolo_active:
|
||||
parts.append("⚠ YOLO")
|
||||
return self._trim_status_bar_text(" │ ".join(parts), width)
|
||||
|
|
@ -5801,6 +5839,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
goal_segment = self._status_bar_goal_segment(snapshot)
|
||||
battery_label = snapshot.get("battery_label") or ""
|
||||
battery_style = self._battery_status_style(snapshot.get("battery_category", "dim"))
|
||||
focus_label = snapshot.get("focus_label") or ""
|
||||
|
||||
if width < 52:
|
||||
frags = [
|
||||
|
|
@ -5812,6 +5851,9 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
if goal_segment:
|
||||
frags.append(("class:status-bar-dim", " · "))
|
||||
frags.append(("class:status-bar-strong", goal_segment))
|
||||
if focus_label:
|
||||
frags.append(("class:status-bar-dim", " · "))
|
||||
frags.append(("class:status-bar-strong", focus_label))
|
||||
if yolo_active:
|
||||
frags.append(("class:status-bar-dim", " · "))
|
||||
frags.append(("class:status-bar-yolo", "⚠ YOLO"))
|
||||
|
|
@ -5849,6 +5891,9 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
("class:status-bar-dim", " · "),
|
||||
("class:status-bar-dim", duration_label),
|
||||
])
|
||||
if focus_label:
|
||||
frags.append(("class:status-bar-dim", " · "))
|
||||
frags.append(("class:status-bar-strong", focus_label))
|
||||
if yolo_active:
|
||||
frags.append(("class:status-bar-dim", " · "))
|
||||
frags.append(("class:status-bar-yolo", "⚠ YOLO"))
|
||||
|
|
@ -5905,6 +5950,11 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
if idle_since:
|
||||
frags.append(("class:status-bar-dim", " │ "))
|
||||
frags.append(("class:status-bar-dim", idle_since))
|
||||
# Persistent focus-view badge — so the reduced-output mode
|
||||
# is never invisible (mirrors the YOLO badge convention).
|
||||
if focus_label:
|
||||
frags.append(("class:status-bar-dim", " │ "))
|
||||
frags.append(("class:status-bar-strong", focus_label))
|
||||
if yolo_active:
|
||||
frags.append(("class:status-bar-dim", " │ "))
|
||||
frags.append(("class:status-bar-yolo", "⚠ YOLO"))
|
||||
|
|
@ -9644,6 +9694,8 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
self._handle_timestamps_command(cmd_original)
|
||||
elif canonical == "verbose":
|
||||
self._toggle_verbose()
|
||||
elif canonical == "focus":
|
||||
self._handle_focus_command(cmd_original)
|
||||
elif canonical == "footer":
|
||||
self._handle_footer_command(cmd_original)
|
||||
elif canonical == "yolo":
|
||||
|
|
@ -10302,6 +10354,22 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
idx = 2 # default to "all"
|
||||
self.tool_progress_mode = cycle[(idx + 1) % len(cycle)]
|
||||
|
||||
# /verbose is the explicit tool-progress control, so cycling it takes
|
||||
# ownership of the mode back from focus view. Leaving _focus_view_enabled
|
||||
# set would show a "focus" status-bar badge and hidden-line counts while
|
||||
# tool lines were visibly printing. Display-only state change.
|
||||
if getattr(self, "_focus_view_enabled", False):
|
||||
self._focus_view_enabled = False
|
||||
self._focus_saved_tool_progress = None
|
||||
self._focus_hidden_lines = 0
|
||||
self._focus_last_counted_tool = None
|
||||
try:
|
||||
from hermes_cli.focus_view import FOCUS_CONFIG_KEY
|
||||
|
||||
save_config_value(FOCUS_CONFIG_KEY, False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if self.agent:
|
||||
self.agent.reasoning_callback = self._current_reasoning_callback()
|
||||
# Keep the live agent's tool_progress_mode in sync so the
|
||||
|
|
@ -11465,6 +11533,15 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
self._turn_summary_record(
|
||||
function_name, kwargs.get("result"), kwargs.get("is_error", False)
|
||||
)
|
||||
# Focus view: count the scrollback line we are NOT printing, so the
|
||||
# post-turn recovery line can report how much was hidden. Counted
|
||||
# against the pre-focus tool-progress mode, so a user who already
|
||||
# had /verbose off is never told focus hid something it didn't.
|
||||
if getattr(self, "_focus_view_enabled", False):
|
||||
try:
|
||||
self._note_focus_hidden_line(function_name or "")
|
||||
except Exception:
|
||||
pass
|
||||
# Print stacked scrollback line for "new" / "all" / "verbose" modes.
|
||||
# "verbose" was previously omitted here, so non-streaming model
|
||||
# calls (MoA aggregator, copilot-acp) rendered each tool only into
|
||||
|
|
@ -13346,6 +13423,15 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
pass
|
||||
|
||||
|
||||
# Focus view: dim recovery line reporting what was hidden this turn
|
||||
# (and how to reveal it). Printed after the response so the turn
|
||||
# reads prompt → answer → "⋯ N tool lines hidden". Display-only;
|
||||
# resets the counter for the next turn.
|
||||
try:
|
||||
self._emit_focus_recovery_line()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Play terminal bell when agent finishes (if enabled).
|
||||
# Works over SSH — the bell propagates to the user's terminal.
|
||||
if self.bell_on_complete:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue