* refactor(plugins): remove the Sep 2026 decomposition compat layer on schedule The PLUGIN-COMPAT layer (2776813df3+d63e380324+0a5164cebe) kept pre-#102117 import paths alive for external plugins until 2026-09-14. That window closed two weeks ago; since then the loader has already been skipping plugins that use the old paths. This removes the layer itself: - 328 appended `PLUGIN-COMPAT` blocks (lazy `__getattr__` pointer tables, re-exported third-party names, restored dead definitions) and the three re-export stub modules (gateway/startup_watchdog, hermes_cli/observability/relay_runtime, tools/environments/modal_utils) - COMPAT_MANIFEST.md, compat_manifest.json, scripts/check_compat_pointers.py and its lint step - the reporting surfaces: CLI banner notice, `hermes plugins compat`, the `hermes doctor` section, the post-update notice, the Desktop one-time dialog, the loader's pre-import skip and the `plugins.allow_deprecated_imports` escape hatch An external plugin that still imports an old path now fails to load with its ImportError as the reason in `hermes plugins list`, the same path as any broken plugin. hermes_cli/plugin_compat.py stays as three inert stubs (compat_report, removal_in_effect, summary_lines): an already-running pre-removal `hermes update` lazy-imports them after the checkout swap (tests/compat/old_updater_surface.json). In-tree fallout, both already dead: hermes_cli/setup.py::_check_espeak_ng (no callers; its `shutil` came from a compat block) and gateway/config.py::SessionResetPolicy ("retained solely for the scheduled plugin-compat window"). Two test_run_agent patches targeted the removed `run_agent.handle_function_call` pointer; they now patch `model_tools.handle_function_call`, the seam production reads, like every sibling test in that file. * chore: retrigger CI (zero-job startup_failure phantom) * test: drop resolution allowlist rows for the two deleted which() sites hermes_cli/setup.py::_check_espeak_ng (dead) and tools/skillevaluator_scan.py::scanner_available (a restored definition inside a PLUGIN-COMPAT block) no longer exist; the stale-row gate requires their allowlist entries go with them.
83 lines
4.1 KiB
Python
83 lines
4.1 KiB
Python
"""Focus view — display-only reduced output: "just my prompt and the answer, and tell me what you hid".
|
|
ON snaps ``tool_progress_mode`` to ``"off"`` and remembers the configured mode so the *existing* suppression
|
|
path does the hiding; OFF restores it verbatim. Focus adds a per-turn hidden-line count with a recovery
|
|
hint and a persistent ``focus`` status-bar segment."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Optional
|
|
|
|
FOCUS_CONFIG_KEY = "display.focus_view" # plain boolean under ``display``, like /battery /timestamps /footer
|
|
FOCUS_TOOL_PROGRESS_MODE = "off" # the SAME value ``/verbose off`` uses so both features share one suppression path
|
|
# Modes in which the CLI commits a per-tool scrollback line. Mirrors the gate in
|
|
# ``HermesCLI._on_tool_progress`` so the hidden-line counter and the renderer never drift apart.
|
|
TOOL_PROGRESS_VISIBLE_MODES = frozenset({"new", "all", "verbose"})
|
|
TOOL_PROGRESS_MODES = ("off", "new", "all", "verbose") # ``log`` is a gateway-only extra step
|
|
FOCUS_STATUSBAR_LABEL = "◉ focus" # short on purpose — the bar is width-constrained
|
|
|
|
# /focus argument words -> (action, target); bare /focus toggles like /footer, /battery, /timestamps.
|
|
_FOCUS_WORDS = {
|
|
**dict.fromkeys(("status", "show", "?"), ("status", None)),
|
|
**dict.fromkeys(("on", "enable", "enabled", "true", "yes", "1"), ("set", True)),
|
|
**dict.fromkeys(("off", "disable", "disabled", "false", "no", "0"), ("set", False)),
|
|
}
|
|
|
|
|
|
def normalize_tool_progress_mode(mode: object, default: str = "all") -> str:
|
|
"""Coerce a raw config/attr value into a known tool-progress mode."""
|
|
if mode is False:
|
|
return "off"
|
|
if mode is True:
|
|
return "all"
|
|
text = str(mode or "").strip().lower()
|
|
# ``log`` is a real gateway mode; any other unknown value becomes the default.
|
|
return text if text in TOOL_PROGRESS_MODES or text == "log" else default
|
|
|
|
|
|
def resolve_focus_arg(arg: str, current: bool) -> tuple[str, Optional[bool]]:
|
|
"""Map a ``/focus`` argument onto ``(action, target)``: action is ``"set"``, ``"status"`` or ``"usage"``;
|
|
target is the requested enabled-state for ``"set"`` and ``None`` otherwise."""
|
|
text = str(arg or "").strip().lower()
|
|
if text in ("", "toggle"):
|
|
return "set", not bool(current)
|
|
return _FOCUS_WORDS.get(text, ("usage", None))
|
|
|
|
|
|
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? Counts honestly: with ``/verbose off``
|
|
focus view hides nothing extra and must not claim otherwise. ``new`` mode skips consecutive repeats of the
|
|
same tool, so the counter does too."""
|
|
if not function_name:
|
|
return False
|
|
normalized = normalize_tool_progress_mode(mode)
|
|
return normalized in TOOL_PROGRESS_VISIBLE_MODES and not (normalized == "new" and function_name == last_tool_name)
|
|
|
|
|
|
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
|
|
return f"⋯ {n} {'tool line' if n == 1 else 'tool lines'} hidden · /focus off to show" if n > 0 else None
|
|
|
|
|
|
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)."""
|
|
mode = normalize_tool_progress_mode(configured_mode).upper()
|
|
if enabled:
|
|
return f"Focus view: ON — only your prompt and the final response.\n /focus off restores tool progress: {mode}"
|
|
return f"Focus view: OFF — tool progress: {mode}"
|
|
|
|
|
|
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"
|
|
return f"Focus view disabled — tool progress: {normalize_tool_progress_mode(configured_mode).upper()}"
|