mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-28 18:19:28 +00:00
Display-only port of Claude Code /focus; composes with existing /verbose tool-progress modes.
166 lines
6.5 KiB
Python
166 lines
6.5 KiB
Python
"""Focus view — a display-only reduced-output mode.
|
|
|
|
``/focus`` answers one question the existing ``/verbose`` cycle cannot:
|
|
*"just show me my prompt and the answer — and tell me what you hid."*
|
|
|
|
``/verbose off`` already silences per-tool progress lines (the
|
|
``tool_progress_mode == "off"`` gate in ``agent/tool_executor.py`` and the
|
|
scrollback gate in ``HermesCLI._on_tool_progress``). Focus view **composes
|
|
with** that machinery instead of duplicating it:
|
|
|
|
* turning focus ON snaps ``tool_progress_mode`` to ``"off"`` and remembers the
|
|
mode the user had configured, so the *existing* suppression path does the
|
|
actual hiding;
|
|
* turning focus OFF restores that remembered mode verbatim;
|
|
* on top of that, focus view adds the two things ``/verbose off`` lacks —
|
|
a per-turn count of what was hidden plus a recovery hint, and a persistent
|
|
``focus`` segment in the status bar so the reduced mode is never invisible.
|
|
|
|
Everything in this module is **display-only**. Nothing here reads or mutates
|
|
conversation history, the system prompt, tool schemas, or any request payload.
|
|
Flipping focus view must never change a single byte of what is sent to the
|
|
model — that invariant is covered by
|
|
``tests/cli/test_focus_view.py::test_model_facing_messages_identical_with_focus_on_vs_off``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Optional
|
|
|
|
# Config key used by the sibling display toggles (/battery, /timestamps,
|
|
# /footer) — a plain boolean under ``display``.
|
|
FOCUS_CONFIG_KEY = "display.focus_view"
|
|
|
|
#: Tool-progress mode focus view snaps to. Deliberately the SAME value
|
|
#: ``/verbose off`` uses so both features share one suppression path.
|
|
FOCUS_TOOL_PROGRESS_MODE = "off"
|
|
|
|
#: Modes in which the CLI commits a per-tool scrollback line. Mirrors the gate
|
|
#: in ``HermesCLI._on_tool_progress``; kept here so the hidden-line counter and
|
|
#: the renderer can never drift apart.
|
|
TOOL_PROGRESS_VISIBLE_MODES = frozenset({"new", "all", "verbose"})
|
|
|
|
#: Valid tool-progress modes (``log`` is a gateway-only extra step).
|
|
TOOL_PROGRESS_MODES = ("off", "new", "all", "verbose")
|
|
|
|
#: Status-bar label. Short on purpose — the bar is width-constrained.
|
|
FOCUS_STATUSBAR_LABEL = "◉ focus"
|
|
|
|
_ON_WORDS = frozenset({"on", "enable", "enabled", "true", "yes", "1"})
|
|
_OFF_WORDS = frozenset({"off", "disable", "disabled", "false", "no", "0"})
|
|
_STATUS_WORDS = frozenset({"status", "show", "?"})
|
|
_TOGGLE_WORDS = frozenset({"", "toggle"})
|
|
|
|
FOCUS_USAGE = "Usage: /focus [on|off|status]"
|
|
|
|
|
|
def normalize_tool_progress_mode(mode: object, default: str = "all") -> str:
|
|
"""Coerce a raw config/attr value into a known tool-progress mode.
|
|
|
|
YAML 1.1 parses a bare ``off`` as ``False``, and older configs stored
|
|
``True``/``False`` booleans, so this mirrors ``cli.py``'s normalisation.
|
|
"""
|
|
if mode is False:
|
|
return "off"
|
|
if mode is True:
|
|
return "all"
|
|
text = str(mode or "").strip().lower()
|
|
if text in TOOL_PROGRESS_MODES:
|
|
return text
|
|
# ``log`` is a real gateway mode; treat any other unknown value as default.
|
|
if text == "log":
|
|
return "log"
|
|
return default
|
|
|
|
|
|
def resolve_focus_arg(arg: str, current: bool) -> tuple[str, Optional[bool]]:
|
|
"""Map a ``/focus`` argument onto an action, following the sibling toggles.
|
|
|
|
Returns ``(action, target)`` where ``action`` is one of ``"set"``,
|
|
``"status"`` or ``"usage"``. ``target`` is the requested enabled-state for
|
|
``"set"`` and ``None`` otherwise. Bare ``/focus`` toggles, matching
|
|
``/footer`` / ``/battery`` / ``/timestamps``.
|
|
"""
|
|
text = str(arg or "").strip().lower()
|
|
if text in _STATUS_WORDS:
|
|
return "status", None
|
|
if text in _ON_WORDS:
|
|
return "set", True
|
|
if text in _OFF_WORDS:
|
|
return "set", False
|
|
if text in _TOGGLE_WORDS:
|
|
return "set", not bool(current)
|
|
return "usage", None
|
|
|
|
|
|
def effective_tool_progress_mode(focus_enabled: bool, configured_mode: object) -> str:
|
|
"""Return the tool-progress mode that should actually be in force.
|
|
|
|
Focus view wins while it is on (it *is* "tool progress off" plus reporting).
|
|
When focus is off the user's configured mode is returned untouched — this is
|
|
what makes ``/focus off`` restore ``/verbose verbose`` rather than clobbering
|
|
it to ``all``.
|
|
"""
|
|
normalized = normalize_tool_progress_mode(configured_mode)
|
|
if focus_enabled:
|
|
return FOCUS_TOOL_PROGRESS_MODE
|
|
return normalized
|
|
|
|
|
|
def would_display_tool_line(
|
|
mode: object,
|
|
function_name: str,
|
|
last_tool_name: Optional[str] = None,
|
|
) -> bool:
|
|
"""Would the CLI have committed a scrollback line for this tool call?
|
|
|
|
Used to count *honestly*: if the user already had ``/verbose off``, focus
|
|
view is hiding nothing extra and must not claim otherwise. ``new`` mode
|
|
skips consecutive repeats of the same tool, so the counter skips them too.
|
|
"""
|
|
if not function_name:
|
|
return False
|
|
normalized = normalize_tool_progress_mode(mode)
|
|
if normalized not in TOOL_PROGRESS_VISIBLE_MODES:
|
|
return False
|
|
if normalized == "new" and function_name == last_tool_name:
|
|
return False
|
|
return True
|
|
|
|
|
|
def format_hidden_line(count: int) -> Optional[str]:
|
|
"""Dim post-turn recovery line, or ``None`` when nothing was hidden."""
|
|
try:
|
|
n = int(count)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
if n <= 0:
|
|
return None
|
|
noun = "tool line" if n == 1 else "tool lines"
|
|
return f"⋯ {n} {noun} hidden · /focus off to show"
|
|
|
|
|
|
def focus_statusbar_segment(enabled: bool) -> str:
|
|
"""Status-bar segment text for focus view (empty when off)."""
|
|
return FOCUS_STATUSBAR_LABEL if enabled else ""
|
|
|
|
|
|
def format_focus_status(enabled: bool, configured_mode: object) -> str:
|
|
"""Human-readable ``/focus status`` body (no ANSI — callers colour it)."""
|
|
state = "ON" if enabled else "OFF"
|
|
if enabled:
|
|
restore = normalize_tool_progress_mode(configured_mode)
|
|
return (
|
|
f"Focus view: {state} — only your prompt and the final response.\n"
|
|
f" /focus off restores tool progress: {restore.upper()}"
|
|
)
|
|
mode = normalize_tool_progress_mode(configured_mode)
|
|
return f"Focus view: {state} — tool progress: {mode.upper()}"
|
|
|
|
|
|
def format_focus_toggle_message(enabled: bool, configured_mode: object) -> str:
|
|
"""Confirmation line printed when focus view is switched (no ANSI)."""
|
|
if enabled:
|
|
return "Focus view enabled — just your prompt and the final response"
|
|
mode = normalize_tool_progress_mode(configured_mode)
|
|
return f"Focus view disabled — tool progress: {mode.upper()}"
|