refactor(hermes_cli): tuple routing compare, debounce-timer helper, pack signatures across g6 files

This commit is contained in:
Teknium
2026-09-02 21:56:08 -07:00
parent 09369aed71
commit 84f1bc5819
3 changed files with 36 additions and 59 deletions
+12 -27
View File
@@ -144,9 +144,10 @@ class CLIAgentSetupMixin:
api_key = runtime.get("api_key")
base_url = runtime.get("base_url")
resolved_provider = runtime.get("provider", "openrouter")
resolved_api_mode = runtime.get("api_mode", self.api_mode)
resolved_acp_command = runtime.get("command")
resolved_acp_args = list(runtime.get("args") or [])
resolved_routing = (
resolved_provider, runtime.get("api_mode", self.api_mode), runtime.get("command"),
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)
@@ -174,16 +175,8 @@ class CLIAgentSetupMixin:
return False
credentials_changed = api_key != self.api_key or base_url != self.base_url
routing_changed = (
resolved_provider != self.provider
or resolved_api_mode != self.api_mode
or resolved_acp_command != self.acp_command
or resolved_acp_args != self.acp_args
)
self.provider = resolved_provider
self.api_mode = resolved_api_mode
self.acp_command = resolved_acp_command
self.acp_args = resolved_acp_args
routing_changed = resolved_routing != (self.provider, self.api_mode, self.acp_command, self.acp_args)
self.provider, self.api_mode, self.acp_command, self.acp_args = resolved_routing
self._credential_pool = runtime.get("credential_pool")
self._provider_source = runtime.get("source")
self.api_key = api_key
@@ -315,12 +308,9 @@ class CLIAgentSetupMixin:
from hermes_cli.config import load_config
_model_cfg = (load_config().get("model") or {})
if isinstance(_model_cfg, dict):
_new_provider = (_model_cfg.get("provider") or "").strip()
if _new_provider:
self.requested_provider = _new_provider
self.requested_provider = (_model_cfg.get("provider") or "").strip() or self.requested_provider
_new_model = (_model_cfg.get("default") or _model_cfg.get("model") or "").strip()
if _new_model:
self.model = _new_model
self.model = _new_model or self.model
except Exception as exc:
logger.debug("first-run config re-sync failed: %s", exc)
# Force credential re-resolution + agent rebuild on next use.
@@ -466,7 +456,6 @@ class CLIAgentSetupMixin:
self._session_db = SessionDB()
except Exception as e:
logger.warning("SQLite session store not available — session will NOT be indexed: %s", e)
if (
self._resumed and self._session_db and not self.conversation_history
and not self._load_resumed_history_late()
@@ -671,7 +660,6 @@ class CLIAgentSetupMixin:
display_kind = msg.get("display_kind")
content = msg.get("content")
tool_calls = msg.get("tool_calls") or []
if display_kind == "hidden":
continue
if display_kind in _RESUME_EVENT_TEXT:
@@ -702,9 +690,8 @@ class CLIAgentSetupMixin:
text = text[:MAX_ASST_LEN] + "..."
parts.append(text)
if tool_calls:
tc_summary = _tool_calls_summary(tool_calls)
parts.append(tc_summary)
full_parts.append(tc_summary)
parts.append(_tool_calls_summary(tool_calls))
full_parts.append(parts[-1])
# Skip pure-reasoning messages with no visible output, and tool-call-only
# entries when SKIP_TOOL_ONLY is enabled.
if not text and (SKIP_TOOL_ONLY or not tool_calls):
@@ -716,10 +703,8 @@ class CLIAgentSetupMixin:
if not entries:
return
skipped = 0
if len(entries) > MAX_DISPLAY_EXCHANGES * 2:
skipped = len(entries) - MAX_DISPLAY_EXCHANGES * 2
entries = entries[skipped:]
skipped = max(0, len(entries) - MAX_DISPLAY_EXCHANGES * 2)
entries = entries[skipped:]
# Show the last assistant entry in full so the user sees where they left off.
if _last_asst_idx is not None and _last_asst_full:
+20 -22
View File
@@ -270,6 +270,21 @@ class CLITerminalMixin:
original_on_resize()
self._schedule_status_bar_unsuppress(app)
def _restart_debounce_timer(self, attr: str, delay: float, fn) -> None:
"""Cancel the daemon Timer stored on ``self.<attr>`` (if any) and start a new one.
``fn`` receives the new Timer so it can detect being superseded."""
old_timer = getattr(self, attr, None)
if old_timer is not None:
try:
old_timer.cancel()
except Exception:
pass
timer = threading.Timer(delay, lambda: fn(timer))
timer.daemon = True
setattr(self, attr, timer)
timer.start()
def _schedule_status_bar_unsuppress(self, app, delay: float = 0.35) -> None:
"""Clear the post-resize status-bar suppression after the reflow settles.
@@ -277,13 +292,6 @@ class CLITerminalMixin:
the bar only once it stops.
"""
try:
old_timer = getattr(self, "_status_bar_unsuppress_timer", None)
if old_timer is not None:
try:
old_timer.cancel()
except Exception:
pass
def _clear():
self._status_bar_suppressed_after_resize = False
try:
@@ -291,10 +299,9 @@ class CLITerminalMixin:
except Exception:
pass
timer = threading.Timer(delay, lambda: _run_on_app_loop(app, _clear))
timer.daemon = True
self._status_bar_unsuppress_timer = timer
timer.start()
self._restart_debounce_timer(
"_status_bar_unsuppress_timer", delay, lambda _t: _run_on_app_loop(app, _clear)
)
except Exception:
# Fail open: never leave the bar stuck hidden.
self._status_bar_suppressed_after_resize = False
@@ -302,7 +309,6 @@ class CLITerminalMixin:
def _schedule_resize_recovery(self, app, original_on_resize, delay: float = 0.12) -> None:
"""Debounce resize redraws so footer chrome is not stamped into scrollback."""
try:
old_timer = getattr(self, "_resize_recovery_timer", None)
lock = getattr(self, "_resize_recovery_lock", None)
if lock is None:
lock = threading.Lock()
@@ -312,7 +318,7 @@ class CLITerminalMixin:
def _run_recovery():
with lock:
if getattr(self, "_resize_recovery_timer", None) is not timer_ref:
return
return # superseded by a newer resize
self._resize_recovery_timer = None
self._resize_recovery_pending = False
self._recover_after_resize(app, original_on_resize)
@@ -320,16 +326,8 @@ class CLITerminalMixin:
_run_on_app_loop(app, _run_recovery)
with lock:
if old_timer is not None:
try:
old_timer.cancel()
except Exception:
pass
self._resize_recovery_pending = True
timer = threading.Timer(delay, lambda: _timer_fired(timer))
timer.daemon = True
self._resize_recovery_timer = timer
timer.start()
self._restart_debounce_timer("_resize_recovery_timer", delay, _timer_fired)
except Exception:
self._resize_recovery_pending = False
self._recover_after_resize(app, original_on_resize)
+4 -10
View File
@@ -170,8 +170,7 @@ def _quote_key(key: str) -> str:
def render_codex_toml_section(
servers: dict[str, dict],
plugins: Optional[list[dict]] = None,
servers: dict[str, dict], plugins: Optional[list[dict]] = None,
default_permission_profile: Optional[str] = None,
) -> str:
"""Render the managed [mcp_servers.<n>] / [plugins.<id>] / default_permissions block.
@@ -288,8 +287,7 @@ def _strip_existing_managed_block(toml_text: str) -> str:
def _query_codex_plugins(
codex_home: Optional[Path] = None,
timeout: float = 8.0,
codex_home: Optional[Path] = None, timeout: float = 8.0
) -> tuple[list[dict], Optional[str]]:
"""Query codex's `plugin/list` for installed curated plugins.
@@ -410,12 +408,8 @@ def _write_atomic(target: Path, text: str) -> None:
def migrate(
hermes_config: dict,
*,
codex_home: Optional[Path] = None,
dry_run: bool = False,
discover_plugins: bool = True,
default_permission_profile: Optional[str] = ":workspace",
hermes_config: dict, *, codex_home: Optional[Path] = None, dry_run: bool = False,
discover_plugins: bool = True, default_permission_profile: Optional[str] = ":workspace",
expose_hermes_tools: bool = True,
) -> MigrationReport:
"""Translate Hermes mcp_servers config + Codex curated plugins into ~/.codex/config.toml.