fix(sessions): advertise v1 FTS storage rebuild
Offer optimize-storage for tool-call-inclusive trigram layouts in doctor and update notices.
This commit is contained in:
@@ -35,6 +35,7 @@ from hermes_cli.colors import Colors, color
|
||||
from hermes_cli.models import _HERMES_USER_AGENT
|
||||
from hermes_cli.vercel_auth import describe_vercel_auth
|
||||
from hermes_constants import OPENROUTER_MODELS_URL
|
||||
from hermes_state_common import FTS_STORAGE_VERSION
|
||||
from utils import base_url_host_matches
|
||||
|
||||
|
||||
@@ -496,21 +497,20 @@ def _render_state_db_stats(stats: dict, holders=None) -> list:
|
||||
"optimize-storage' with the gateway stopped)",
|
||||
))
|
||||
|
||||
# Advisory: oversized database. Suggest auto_prune, and — when the v23
|
||||
# FTS rebuild is pending OR the DB still carries the legacy inline
|
||||
# trigram layout (fts_storage_version marker absent) — the offline
|
||||
# Advisory: oversized database. Suggest auto_prune, and — when the FTS
|
||||
# rebuild is pending OR the DB predates the current trigram layout — the offline
|
||||
# optimize-storage pass that migrates/compacts the FTS indexes.
|
||||
if logical is not None and logical > STATE_DB_SIZE_WARN_BYTES:
|
||||
detail = (
|
||||
"consider enabling sessions.auto_prune in config.yaml "
|
||||
"to bound growth"
|
||||
)
|
||||
legacy_trigram = (
|
||||
stale_trigram = (
|
||||
fts is not None
|
||||
and fts.get("messages_fts_trigram")
|
||||
and stats.get("fts_storage_version") is None
|
||||
and (stats.get("fts_storage_version") or 0) < FTS_STORAGE_VERSION
|
||||
)
|
||||
if stats.get("fts_rebuild_pending") or legacy_trigram:
|
||||
if stats.get("fts_rebuild_pending") or stale_trigram:
|
||||
detail += (
|
||||
"; run 'hermes sessions optimize-storage' offline "
|
||||
"(with the gateway stopped) to compact FTS storage"
|
||||
|
||||
@@ -1050,9 +1050,9 @@ def _print_curator_first_run_notice() -> None:
|
||||
def _print_fts_optimize_available_notice() -> None:
|
||||
"""Advertise the opt-in v23 search-index optimization after `hermes update`.
|
||||
|
||||
Only fires when the current profile's state.db is still on the legacy
|
||||
(pre-v23) inline FTS layout. Leads with the reclaimable-space figure and
|
||||
points at the exact command. Honors ``sessions.fts_optimize_notice``:
|
||||
Only fires when the current profile's state.db still needs an FTS storage
|
||||
rebuild. Leads with the reclaimable-space figure and points at the exact
|
||||
command. Honors ``sessions.fts_optimize_notice``:
|
||||
``advise`` (default) prints an advisory notice, ``require`` prints a
|
||||
firmer required-upgrade notice, ``off`` suppresses it. Silent for
|
||||
fresh/already-optimized installs.
|
||||
@@ -1088,13 +1088,17 @@ def _print_fts_optimize_available_notice() -> None:
|
||||
return
|
||||
db = None
|
||||
interrupted = False
|
||||
needs_upgrade = False
|
||||
try:
|
||||
db = SessionDB(db_path=db_path, read_only=True)
|
||||
# read_only opens skip schema init, so probe the layout directly.
|
||||
# read_only opens skip schema init, so probe the stored layout directly.
|
||||
row = db._conn.execute(
|
||||
"SELECT sql FROM sqlite_master "
|
||||
"WHERE type = 'table' AND name = 'messages_fts'"
|
||||
).fetchone()
|
||||
needs_upgrade = bool(row) and getattr(
|
||||
db, "_db_needs_fts_storage_upgrade"
|
||||
)(db._conn)
|
||||
# An interrupted `optimize-storage` run: the table is already the
|
||||
# v23 shape, but backfill markers / demoted trash tables remain.
|
||||
# Offer the command again — re-running resumes and finishes it.
|
||||
@@ -1120,9 +1124,8 @@ def _print_fts_optimize_available_notice() -> None:
|
||||
db.close()
|
||||
except Exception:
|
||||
pass
|
||||
sql = (row[0] if row else "") or ""
|
||||
if not sql or ("tool_name" in sql and not interrupted):
|
||||
# v23 layout already present (fresh/optimized) — nothing to offer.
|
||||
if not needs_upgrade and not interrupted:
|
||||
# Current layout already present (fresh/optimized) — nothing to offer.
|
||||
return
|
||||
|
||||
if interrupted:
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
"""Regression coverage for FTS storage upgrade discoverability."""
|
||||
|
||||
import sqlite3
|
||||
from types import SimpleNamespace
|
||||
|
||||
|
||||
def test_update_notice_offers_v1_trigram_tool_calls_rebuild(tmp_path, monkeypatch, capsys):
|
||||
"""A deployed v1 trigram projection still receives the opt-in notice."""
|
||||
from hermes_cli import update_cmd
|
||||
import hermes_constants
|
||||
import hermes_state
|
||||
|
||||
db_path = tmp_path / "state.db"
|
||||
db_path.touch()
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.executescript(
|
||||
"""
|
||||
CREATE TABLE state_meta (key TEXT PRIMARY KEY, value TEXT);
|
||||
CREATE TABLE messages_fts (content TEXT, tool_name TEXT, tool_calls TEXT);
|
||||
CREATE TABLE messages_fts_trigram (content TEXT, tool_name TEXT, tool_calls TEXT);
|
||||
"""
|
||||
)
|
||||
|
||||
class FakeSessionDB:
|
||||
def __init__(self, **_kwargs):
|
||||
self._conn = conn
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
_db_needs_fts_storage_upgrade = staticmethod(
|
||||
hermes_state.SessionDB._db_needs_fts_storage_upgrade
|
||||
)
|
||||
|
||||
monkeypatch.setattr(hermes_constants, "get_hermes_home", lambda: tmp_path)
|
||||
monkeypatch.setattr(hermes_state, "SessionDB", FakeSessionDB)
|
||||
monkeypatch.setattr(update_cmd.Path, "stat", lambda _path: SimpleNamespace(st_size=512 * 1024 ** 2))
|
||||
|
||||
update_cmd._print_fts_optimize_available_notice()
|
||||
|
||||
assert "hermes sessions optimize-storage" in capsys.readouterr().out
|
||||
conn.close()
|
||||
@@ -236,6 +236,20 @@ def test_render_large_db_legacy_trigram_suggests_optimize():
|
||||
assert "optimize-storage" in blob
|
||||
|
||||
|
||||
def test_render_large_db_v1_trigram_suggests_optimize():
|
||||
from hermes_cli.doctor import STATE_DB_SIZE_WARN_BYTES, _render_state_db_stats
|
||||
|
||||
lines = _render_state_db_stats(
|
||||
_base_stats(
|
||||
logical_size_bytes=STATE_DB_SIZE_WARN_BYTES + 1,
|
||||
fts_storage_version=1,
|
||||
),
|
||||
holders=None,
|
||||
)
|
||||
blob = " ".join(" ".join(str(p) for p in line) for line in lines)
|
||||
assert "optimize-storage" in blob
|
||||
|
||||
|
||||
def test_render_does_not_duplicate_legacy_wal_warning():
|
||||
"""A large WAL must NOT warn here: doctor's pre-existing WAL check
|
||||
(50 MB threshold, with a --fix checkpoint) already covers it, and a
|
||||
|
||||
Reference in New Issue
Block a user