refactor(hermes_cli): unify resume state restore, fold trivial locals and module docstrings in g6 files

This commit is contained in:
Teknium
2026-09-02 22:42:56 -07:00
parent 48ef059a6c
commit c6f75a32ec
5 changed files with 30 additions and 51 deletions
+2 -3
View File
@@ -402,10 +402,9 @@ def _cmd_cleanup(args):
print_error(f"Could not archive: {e}")
print_info(f"Try manually: mv {source_dir} {source_dir}.pre-migration")
print()
n = len(dirs_to_check) if dry_run else total_archived
word = "directory" if n == 1 else "directories"
word = "directory" if (len(dirs_to_check) if dry_run else total_archived) == 1 else "directories"
if dry_run:
_info(f"Dry run complete. {n} {word} would be archived.",
_info(f"Dry run complete. {len(dirs_to_check)} {word} would be archived.",
"Run without --dry-run to archive them.")
elif total_archived:
print_success(f"Cleaned up {total_archived} OpenClaw {word}.")
+15 -21
View File
@@ -1,9 +1,6 @@
"""Agent-construction and session-resume display methods for ``HermesCLI``.
Runtime-credential resolution, per-turn agent config, first-use agent construction, and
resumed-session preload + history recap. ``cli.py`` helpers are imported lazily inside
each method so this module never imports ``cli`` at import time (import cycle).
"""
"""Agent construction + session-resume display for ``HermesCLI``: credential resolution,
per-turn agent config, first-use build, resume preload + recap. ``cli.py`` helpers are
imported lazily inside each method (import cycle)."""
from __future__ import annotations
@@ -202,8 +199,7 @@ class CLIAgentSetupMixin:
list(runtime.get("args") or []))
# A callable api_key is a bearer-token provider (Azure Entra ID): the OpenAI SDK
# invokes it per request, so skip string validation / placeholder substitution.
_is_callable_provider = callable(api_key) and not isinstance(api_key, str)
if not _is_callable_provider and (not isinstance(api_key, str) or not api_key):
if not callable(api_key) and not (isinstance(api_key, str) and api_key):
if _keyless_custom_base(base_url):
# Placeholder key so the SDK doesn't reject the keyless local endpoint.
api_key = "no-key-required"
@@ -314,7 +310,7 @@ class CLIAgentSetupMixin:
return False
api_key = runtime.get("api_key")
base_url = runtime.get("base_url")
if (callable(api_key) and not isinstance(api_key, str)) or (isinstance(api_key, str) and api_key):
if callable(api_key) or (isinstance(api_key, str) and api_key):
return bool(base_url)
return _keyless_custom_base(base_url)
@@ -394,6 +390,12 @@ class CLIAgentSetupMixin:
session_meta = self._session_db.get_session(self.session_id) or session_meta
return session_meta
def _restore_session_state(self, session_meta, *, quiet: bool = False) -> None:
"""Restore cwd / yolo / model from the resumed session's metadata."""
self._restore_session_cwd(session_meta, quiet=quiet)
self._restore_session_yolo(session_meta, quiet=quiet)
self._restore_session_model(session_meta, quiet=quiet)
def _reopen_session(self) -> None:
"""Clear ended_at so the resumed session is active again (best effort)."""
try:
@@ -453,9 +455,7 @@ class CLIAgentSetupMixin:
f"↻ Resumed session {self.session_id}{title_part} {counts}",
f"[bold {_accent_hex()}]↻ Resumed session[/] [bold]{_escape(self.session_id)}[/]"
f"[bold {_accent_hex()}]{_escape(title_part)}[/] {counts}")
self._restore_session_cwd(session_meta, quiet=_quiet_mode)
self._restore_session_yolo(session_meta, quiet=_quiet_mode)
self._restore_session_model(session_meta, quiet=_quiet_mode)
self._restore_session_state(session_meta, quiet=_quiet_mode)
else:
_say(
f"Session {self.session_id} found but has no messages. Starting fresh.",
@@ -555,7 +555,7 @@ class CLIAgentSetupMixin:
self._active_agent_route_signature = _route_signature(effective_model, runtime)
# Force-create DB row on /title intent, then apply title.
if self._pending_title and self._session_db and self.agent:
if self._pending_title and self._session_db:
try:
self.agent._ensure_db_session()
if self.agent._session_db_created:
@@ -589,10 +589,7 @@ class CLIAgentSetupMixin:
safety_check = getattr(self._session_db, "assert_resume_safe", None)
if not callable(safety_check):
return None
if tip_only:
safety_check(self.session_id, tip_only=True)
else:
safety_check(self.session_id)
safety_check(self.session_id, **({"tip_only": True} if tip_only else {}))
except SessionResumeTooLargeError as exc:
return str(exc)
except Exception as exc:
@@ -634,7 +631,6 @@ class CLIAgentSetupMixin:
self.conversation_history = restored
self._resume_display_history = [m for m in display_history if m.get("role") != "session_meta"]
from agent.context_compressor import is_user_originated_turn
# Count only user-originated turns: legacy compaction handoffs are durable
# role=user rows without display_kind.
msg_count = len([m for m in self._resume_display_history if is_user_originated_turn(m)])
@@ -644,9 +640,7 @@ class CLIAgentSetupMixin:
f"{title_part} "
f"({msg_count} user message{'s' if msg_count != 1 else ''}, "
f"{len(restored)} total messages)[/]")
self._restore_session_cwd(session_meta)
self._restore_session_yolo(session_meta)
self._restore_session_model(session_meta)
self._restore_session_state(session_meta)
self._reopen_session()
return True
+7 -14
View File
@@ -1,8 +1,5 @@
"""Simple slash-command wrappers plus goal/heartbeat/loop manager hooks for the interactive CLI
Mixin bound onto ``HermesCLI`` via the MRO. cli.py-internal symbols are imported LAZILY
inside each method (``from cli import ...``) — never at module load (import cycle).
"""
"""Simple slash-command wrappers plus goal/heartbeat/loop manager hooks for the interactive CLI.
Mixin on ``HermesCLI``; cli.py symbols are imported lazily inside methods (import cycle)."""
from __future__ import annotations
@@ -198,14 +195,12 @@ class CLILoopsMixin:
def _cmd_egress(self, cmd_original: str):
from hermes_cli.slash_exec import CommandContext, execute_command
self._console_print(
execute_command("egress", CommandContext(surface="cli")).text,
highlight=False, markup=False)
text = execute_command("egress", CommandContext(surface="cli")).text
self._console_print(text, highlight=False, markup=False)
def _cmd_statusbar(self, cmd_original: str):
self._status_bar_visible = not self._status_bar_visible
state = "visible" if self._status_bar_visible else "hidden"
self._console_print(f" Status bar {state}")
self._console_print(f" Status bar {'visible' if self._status_bar_visible else 'hidden'}")
def _cmd_update(self, cmd_original: str) -> bool:
# A truthy result means the process is relaunching — leave the REPL.
@@ -276,10 +271,8 @@ class CLILoopsMixin:
_cprint(" Usage: /queue <prompt>")
else:
self._pending_input.put(payload)
if self._agent_running:
_cprint(f" Queued for the next turn: {_preview(payload)}")
else:
_cprint(f" Queued: {_preview(payload)}")
when = " for the next turn" if self._agent_running else ""
_cprint(f" Queued{when}: {_preview(payload)}")
def _cmd_steer(self, cmd_original: str):
# Inject a message after the next tool call without interrupting: while the
+5 -11
View File
@@ -1,8 +1,5 @@
"""Terminal repaint/resize recovery, input-mode healing, and clipboard helpers for the interactive CLI
Mixin bound onto ``HermesCLI`` via the MRO. cli.py-internal symbols are imported LAZILY
inside each method (``from cli import ...``) — never at module load (import cycle).
"""
"""Terminal repaint/resize recovery, input-mode healing, and clipboard helpers for the interactive
CLI. Mixin on ``HermesCLI``; cli.py symbols are imported lazily inside methods (import cycle)."""
from __future__ import annotations
@@ -90,9 +87,7 @@ class CLITerminalMixin:
throttled or resize-gated entry paint is silently dropped, so the prompt never
renders and times out unseen (#41098).
"""
if getattr(self, "_terminal_io_broken", False):
return
if getattr(self, "_resize_recovery_pending", False):
if getattr(self, "_terminal_io_broken", False) or getattr(self, "_resize_recovery_pending", False):
return
now = time.monotonic()
if hasattr(self, "_app") and self._app and (now - getattr(self, "_last_invalidate", 0.0)) >= min_interval:
@@ -155,9 +150,8 @@ class CLITerminalMixin:
"""
from cli import CLI_CONFIG
display_config = CLI_CONFIG.get("display") if isinstance(CLI_CONFIG, dict) else {}
if not isinstance(display_config, dict):
display_config = {}
raw = display_config.get("cli_rebuild_scrollback_on_redraw", False)
raw = (display_config.get("cli_rebuild_scrollback_on_redraw", False)
if isinstance(display_config, dict) else False)
if isinstance(raw, str):
return raw.strip().lower() in {"1", "true", "yes", "on", "always"}
return bool(raw)
+1 -2
View File
@@ -176,8 +176,7 @@ def render_codex_toml_section(
"""
out = [MIGRATION_MARKER]
if not servers and not plugins and not default_permission_profile:
out.append("# (no MCP servers, plugins, or permissions configured by Hermes)")
out.append(MIGRATION_END_MARKER)
out += ["# (no MCP servers, plugins, or permissions configured by Hermes)", MIGRATION_END_MARKER]
return "\n".join(out) + "\n"
if default_permission_profile:
profile = default_permission_profile