Files
hermes-agent/hermes_cli/cli_auto_maintenance.py
teknium1 2ca53fc386 refactor(cli): move cli.py module-level helper clusters into topical siblings; cli.py lands under 2,000 lines (#116911)
Mechanical, behaviour-neutral extraction along the existing hermes_cli/cli_*.py
pattern. Six clusters of module-level helpers leave cli.py:

- cli_config_load.py     prefill messages, reasoning/service-tier parsing, terminal
                         env mirroring, CLI defaults + YAML merge, logging bootstrap
- cli_render.py          reasoning-tag stripping, ANSI/skin colours, light-mode
                         detection, markdown/final rendering, output-history
                         recording, _cprint, ChatConsole, compact banner, panel wrap
- cli_terminal_input.py  file drops/attachments, bracketed-paste patch, extended
                         Enter keys, CPR guards, TUI input height, query images
- cli_shutdown.py        process session-id sync, exit watchdog, cleanup steps,
                         session-finalize notifications, one-shot finalize
- cli_single_query.py    kanban goal loops, exit-code mapping, quiet -q runner,
                         image routing, signal handlers, single-query mode
- cli_auto_maintenance.py state-db / checkpoint startup maintenance

Every moved body is AST-identical to the base copy modulo two mechanical
edits: cli-level names are late-bound with a call-time `from cli import ...`
(the cli_init_mixin pattern) and mutable cli state is read through `_cli().NAME`
(the gateway_service_unit `_gw()` pattern), so every monkeypatch seam on the
`cli` facade still intercepts moved code and no read snapshots stale state.
Mutable module state and every `global`-writing function stay in cli.py; the
facade re-exports the moved names in one import block.

test_bracketed_paste_timeout AST-loads the paste helper from its new home.
cli.py 3959 -> 1820 lines.
2026-09-20 20:42:17 -07:00

75 lines
3.2 KiB
Python

"""Startup auto-maintenance for the classic CLI: state-db pack/vacuum and checkpoint pruning run off the prompt thread.
Split out of ``cli.py``; ``cli`` re-exports every public name and moved bodies late-bind
cli-level names through ``from cli import ...`` at call time so facade monkeypatch seams hold.
"""
from __future__ import annotations
import logging
import threading
# Log-record parity with the origin module.
logger = logging.getLogger("cli")
def _run_state_db_auto_maintenance(session_db) -> None:
"""One-time repairs + auto-archive/prune/vacuum per the ``sessions:`` config. Never raises."""
if session_db is None:
return
try:
from hermes_cli.config import load_config as _load_full_config
from hermes_constants import get_hermes_home as _get_hermes_home # lazy: tests patch it
_hermes_home_maint = _get_hermes_home()
# One-time repairs, each latched in state_meta once it has run.
for meta_key, repair, done_msg, skip_msg in (
(
"ghost_session_prune_v1",
lambda: session_db.prune_empty_ghost_sessions(sessions_dir=_hermes_home_maint / "sessions"),
"Pruned %d empty TUI ghost sessions", "Ghost session prune skipped: %s",
),
(
"orphaned_compression_finalize_v1",
session_db.finalize_orphaned_compression_sessions,
"Finalized %d orphaned compression sessions", "Orphan compression finalize skipped: %s",
),
):
try:
if not session_db.get_meta(meta_key):
count = repair()
session_db.set_meta(meta_key, "1")
if count:
logger.info(done_msg, count)
except Exception as _exc:
logger.debug(skip_msg, _exc)
cfg = (_load_full_config().get("sessions") or {})
# Auto-archive is independent of auto_prune: run it before prune's early return.
if cfg.get("auto_archive", False):
session_db.maybe_auto_archive(
idle_days=float(cfg.get("auto_archive_days", 3)),
min_interval_hours=int(cfg.get("min_interval_hours", 24)),
)
if not cfg.get("auto_prune", False):
return
session_db.maybe_auto_prune_and_vacuum(
retention_days=int(cfg.get("retention_days", 90)),
min_interval_hours=int(cfg.get("min_interval_hours", 24)),
min_vacuum_interval_days=int(cfg.get("min_vacuum_interval_days", 30)),
vacuum=bool(cfg.get("vacuum_after_prune", True)),
sessions_dir=_hermes_home_maint / "sessions",
)
except Exception as exc:
logger.debug("state.db auto-maintenance skipped: %s", exc)
def _run_checkpoint_auto_maintenance() -> None:
"""Checkpoint store retention on a daemon thread: its ``git gc`` can block for tens of seconds
on a large store, which used to stall the prompt once a day. ``auto_prune_from_config`` owns the
config gate and the 24h marker and never raises."""
from tools.checkpoint_manager import auto_prune_from_config
threading.Thread(target=auto_prune_from_config, name="checkpoint-auto-prune", daemon=True).start()