diff --git a/hermes_cli/claw.py b/hermes_cli/claw.py index c1d6c0c1a6..5f83cf28b6 100644 --- a/hermes_cli/claw.py +++ b/hermes_cli/claw.py @@ -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}.") diff --git a/hermes_cli/cli_agent_setup_mixin.py b/hermes_cli/cli_agent_setup_mixin.py index d36056e15f..237287c5ec 100644 --- a/hermes_cli/cli_agent_setup_mixin.py +++ b/hermes_cli/cli_agent_setup_mixin.py @@ -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 diff --git a/hermes_cli/cli_loops_mixin.py b/hermes_cli/cli_loops_mixin.py index 2952479ebf..65a45314b7 100644 --- a/hermes_cli/cli_loops_mixin.py +++ b/hermes_cli/cli_loops_mixin.py @@ -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 ") 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 diff --git a/hermes_cli/cli_terminal_mixin.py b/hermes_cli/cli_terminal_mixin.py index 76bb873831..539415a519 100644 --- a/hermes_cli/cli_terminal_mixin.py +++ b/hermes_cli/cli_terminal_mixin.py @@ -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) diff --git a/hermes_cli/codex_runtime_plugin_migration.py b/hermes_cli/codex_runtime_plugin_migration.py index ab49d9d529..9002d89347 100644 --- a/hermes_cli/codex_runtime_plugin_migration.py +++ b/hermes_cli/codex_runtime_plugin_migration.py @@ -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