refactor(hermes_cli): drop intra-function blank separators in session modules (AST-identical)

This commit is contained in:
Teknium
2026-09-02 22:55:35 -07:00
parent df39de8738
commit 679b303ba1
5 changed files with 0 additions and 102 deletions

View File

@@ -112,7 +112,6 @@ def run_cli_lost_and_found_recover(
attempt["usable"] = _lost_and_found_db_usable(lf_path)
if attempt["usable"]:
return {"binary": sqlite3_bin, "attempts": attempts}
raise LostAndFoundError(
"sqlite3 .recover did not produce a usable lost_and_found database: "
+ "; ".join(
@@ -178,7 +177,6 @@ def _looks_like_source(value: Any) -> bool:
def classify_lost_and_found_row(nfield: int, cells: tuple[Any, ...]) -> Optional[str]:
"""Classify one lost_and_found record by field count + sentinel values."""
if len(cells) >= 3 and cells[0] is None:
# Rowid-alias tables store their INTEGER PRIMARY KEY as NULL; messages is the only canonical
# table shaped like that with a session id second and a role third.
@@ -262,7 +260,6 @@ def map_lost_and_found_rows(lf_conn: sqlite3.Connection, dest: sqlite3.Connectio
for index in protected:
defaults.pop(index, None)
targets[kind_name] = (_table_columns(dest, kind_name), defaults)
lf_tables = [
str(row[0])
for row in lf_conn.execute(
@@ -270,7 +267,6 @@ def map_lost_and_found_rows(lf_conn: sqlite3.Connection, dest: sqlite3.Connectio
)
]
report["lost_and_found_tables"] = lf_tables
for lf_table in lf_tables:
if _table_columns(lf_conn, lf_table)[:3] != ["rootpgno", "pgno", "nfield"]:
continue
@@ -338,7 +334,6 @@ def stub_missing_parent_sessions(dest: sqlite3.Connection) -> dict[str, Any]:
"EXISTS (SELECT 1 FROM sessions WHERE sessions.id = u.session_id)"
):
orphan_ids.setdefault(str(session_id), {"started_at": 0.0, "message_count": 0})
titles = _placeholder_titles(dest, "best-effort recovered")
for session_id, info in sorted(orphan_ids.items()):
title = next(titles)
@@ -348,7 +343,6 @@ def stub_missing_parent_sessions(dest: sqlite3.Connection) -> dict[str, Any]:
)
result["sessions_stubbed"] += 1
result["messages_retained"] += info["message_count"]
result["usage_rows_retained"] = int(dest.execute("SELECT COUNT(*) FROM session_model_usage").fetchone()[0])
# Repair dangling intra-sessions references without deleting rows.

View File

@@ -161,15 +161,12 @@ def build_recap(
lines[0] += f" — {session_title}"
elif session_id:
lines[0] += f" — {session_id[:8]}"
if not messages:
lines.append(" (nothing to recap — no messages yet)")
return "\n".join(lines)
users, assistants, tool_msgs = _count_visible_turns(messages)
window = _recent_window(messages)
win_users, win_assistants, _ = _count_visible_turns(window)
scope = (
f"{win_users} user turn{'s' if win_users != 1 else ''} / "
f"{win_assistants} assistant repl{'ies' if win_assistants != 1 else 'y'}"
@@ -177,7 +174,6 @@ def build_recap(
if (users, assistants) != (win_users, win_assistants):
scope += f" (of {users}/{assistants} total)"
lines.append(f" Recent: {scope}, {tool_msgs} tool result{'s' if tool_msgs != 1 else ''}")
tool_calls = list(_iter_assistant_tool_calls(window))
tool_counts, files = _summarise_tool_activity(tool_calls)
if tool_counts:
@@ -185,18 +181,14 @@ def build_recap(
lines.append(f" Tools used: {top}")
if files:
lines.append(f" Files touched: {_join_capped(files, _MAX_FILES_LISTED)}")
latest_user = _latest_text(window, "user")
if latest_user:
lines.append(f" Last ask: {_truncate(latest_user, _PROMPT_PREVIEW_CHARS)}")
latest_reply = _latest_text(window, "assistant")
if latest_reply:
lines.append(f" Last reply: {_truncate(latest_reply, _ASSISTANT_PREVIEW_CHARS)}")
if len(lines) == 2: # only header + scope line: nothing substantive to show
lines.append(" (no assistant activity yet in this window)")
return "\n".join(lines)

View File

@@ -31,7 +31,6 @@ _TOPIC_TABLES = ("telegram_dm_topic_mode", "telegram_dm_topic_bindings")
def _init_delivery_ledger_schema(conn: sqlite3.Connection) -> None:
from gateway.delivery_ledger import _initialize_schema
_initialize_schema(conn)
@@ -86,7 +85,6 @@ def _validate_paths(
source = source_path.expanduser().resolve(strict=True)
if not source.is_file():
raise SessionRecoverySafetyError(f"Source is not a file: {source}")
output: Optional[Path] = None
if output_path is not None:
output = _resolved_output_path(output_path)
@@ -99,7 +97,6 @@ def _validate_paths(
candidate = _sidecar_path(output, suffix)
if os.path.lexists(candidate):
raise SessionRecoverySafetyError(f"Refusing to overwrite existing recovery output: {candidate}")
work_root = (
work_dir.expanduser().resolve(strict=True)
if work_dir is not None
@@ -149,13 +146,11 @@ def _disk_space_preflight(source: Path, work_root: Path, output_parent: Optional
# with the complete bundle avoids betting the user's disk on that.
output_allowance = bundle_bytes if output_parent is not None else 0
headroom = max(_MINIMUM_SPACE_HEADROOM, int((bundle_bytes + output_allowance) * 0.05))
work_free = int(shutil.disk_usage(work_root).free)
report: dict[str, Any] = {
"source_bundle_bytes": bundle_bytes, "estimated_output_bytes": output_allowance, "headroom_bytes": headroom,
"work_dir": str(work_root), "work_dir_free_bytes": work_free,
}
if output_parent is None or _same_filesystem(work_root, output_parent):
required = bundle_bytes + output_allowance + headroom
report.update(shared_filesystem=True, work_dir_required_bytes=required)
@@ -167,7 +162,6 @@ def _disk_space_preflight(source: Path, work_root: Path, output_parent: Optional
"Use --work-dir or --output on a filesystem with more free space."
)
return report
output_free = int(shutil.disk_usage(output_parent).free)
work_required = bundle_bytes + headroom
output_required = output_allowance + headroom
@@ -199,7 +193,6 @@ def _copy_source_bundle(source: Path, snapshot_dir: Path) -> tuple[Path, list[st
guard keeps this path consistent with ``hermes_state._backup_db_file``.
"""
from hermes_cli.sqlite_safe_read import LiveConnectionError, offline_file_access
snapshot_source = snapshot_dir / source.name
copied: list[str] = []
try:
@@ -283,7 +276,6 @@ def _copy_rows(
result["status"] = "partial" if result["copied_rows"] else "failed"
result["error"] = str(exc)
return result
if expected_rows is None or result["copied_rows"] == expected_rows:
result["status"] = "complete"
else:
@@ -320,10 +312,8 @@ def _inspect_connection(conn: sqlite3.Connection) -> dict[str, Any]:
report["journal_mode"] = None
# Journal metadata is context, not canonical data: a damaged pragma must not block readable rows.
report["warnings"].append(f"journal mode: {exc}")
for table in _INVENTORY_TABLES:
report["tables"][table] = _table_inventory(conn, table)
for required in ("sessions", "messages"):
table_report = report["tables"][required]
if not table_report.get("available") or table_report.get("rows") is None:
@@ -348,7 +338,6 @@ def _snapshot_and_inspect(
"stopped. Run the recovery from a fresh shell with no `hermes` session open, or point --source at an "
"immutable snapshot copy of the database."
)
conn = _connect(snapshot_source)
try:
inspection = _inspect_connection(conn)
@@ -441,7 +430,6 @@ def _salvage_rowid_bounds(source: sqlite3.Connection, table: str) -> dict[str, A
rows[edge] = int(row[0])
except sqlite3.DatabaseError as exc:
result["errors"].append(f"{edge} rowid: {exc}")
if rows["low"] is None and rows["high"] is None:
result["empty" if not result["errors"] else "unavailable"] = True
return result
@@ -454,7 +442,6 @@ def _salvage_rowid_bounds(source: sqlite3.Connection, table: str) -> dict[str, A
if rows["high"] is None:
rows["high"] = _MAX_SQLITE_ROWID
result["fallback_edges"].append("high")
result.update(rows)
# Bisecting the whole synthetic domain tail used to exhaust the range-query budget before readable
# tail rows were copied (#80205); gallop outward from the surviving edge for a finite bound first.
@@ -481,7 +468,6 @@ def _probe_populated_edge(source: sqlite3.Connection, table: str, *, edge: str,
)
domain_limit = _MAX_SQLITE_ROWID if ascending else _MIN_SQLITE_ROWID
result: dict[str, Any] = {"edge": edge, "probes": 0, "capped": False}
position = anchor
span = 1
while True:
@@ -563,7 +549,6 @@ class _RowidRangeSalvage:
self.stopped_at_query_limit = True
self._skip(low, high, "salvage range query limit reached")
return
result["range_queries"] += 1
last_committed_rowid: Optional[int] = None
try:
@@ -611,7 +596,6 @@ def _copy_table_salvage(
columns = _compatible_columns(source, destination, table, result)
if columns is None:
return result
bounds = _salvage_rowid_bounds(source, table)
result["rowid_bounds"] = bounds
if bounds.get("empty"):
@@ -622,7 +606,6 @@ def _copy_table_salvage(
result["status"] = "failed"
result["error"] = "could not determine a rowid range for salvage" + (f": {details}" if details else "")
return result
salvage = _RowidRangeSalvage(
source, destination, table, columns, chunk_size=chunk_size, progress_cb=progress_cb, source_rows=source_rows,
insert_prefix=insert_prefix, row_filter=row_filter, result=result,
@@ -631,7 +614,6 @@ def _copy_table_salvage(
skipped_ranges = result["skipped_rowid_ranges"]
result["skipped_rowid_span"] = sum(item["high"] - item["low"] + 1 for item in skipped_ranges)
result["query_limit_reached"] = salvage.stopped_at_query_limit
if skipped_ranges:
result["status"] = "partial" if result["copied_rows"] else "failed"
result["error"] = f"{len(skipped_ranges)} rowid range(s) skipped"
@@ -682,11 +664,9 @@ def _copy_state_meta(
problem = _state_meta_precheck(source, destination, source_rows, salvage=salvage)
if problem is not None:
return problem
if salvage:
def keep_user_meta(row: tuple[Any, ...], columns: tuple[str, ...]) -> bool:
return str(row[columns.index("key")]) not in _GENERATED_META_KEYS
result = _copy_table_salvage(
source, destination, "state_meta", chunk_size=chunk_size, progress_cb=progress_cb,
source_rows=source_rows, insert_prefix="INSERT OR REPLACE", row_filter=keep_user_meta,
@@ -694,7 +674,6 @@ def _copy_state_meta(
result["source_meta_rows"] = result.pop("source_rows")
result["excluded_keys"] = sorted(_GENERATED_META_KEYS)
return result
placeholders = ", ".join("?" for _ in _GENERATED_META_KEYS)
params = tuple(_GENERATED_META_KEYS)
filtered_source_rows: Optional[int] = None
@@ -704,7 +683,6 @@ def _copy_state_meta(
)
except sqlite3.DatabaseError:
pass # the copy loop below will return the concrete read error
return _copy_rows(
source, destination, f"SELECT key, value FROM state_meta WHERE key NOT IN ({placeholders})", params,
"INSERT OR REPLACE INTO state_meta(key, value) VALUES (?, ?)", table="state_meta", chunk_size=chunk_size,
@@ -732,14 +710,12 @@ def _reconstruct_missing_sessions(destination: sqlite3.Connection) -> dict[str,
result: dict[str, Any] = {"sessions_reconstructed": 0, "messages_retained": 0}
if not (_table_columns(destination, "sessions") and _table_columns(destination, "messages")):
return result
orphaned = destination.execute(
"SELECT m.session_id, MIN(m.timestamp), COUNT(*) FROM messages AS m WHERE m.session_id IS NOT NULL AND NOT "
"EXISTS (SELECT 1 FROM sessions WHERE sessions.id = m.session_id) GROUP BY m.session_id"
).fetchall()
if not orphaned:
return result
titles = _placeholder_titles(destination, "recovered")
for session_id, first_timestamp, message_count in orphaned:
started_at = float(first_timestamp) if first_timestamp is not None else 0.0
@@ -778,7 +754,6 @@ def _cleanup_partial_orphans(destination: sqlite3.Connection) -> dict[str, Any]:
with _immediate_transaction(destination):
# Rebuild owners BEFORE any orphan deletion.
result.update(_reconstruct_missing_sessions(destination))
result["sessions_parent_cleared"] = _reconcile(
destination,
"sessions",
@@ -822,19 +797,15 @@ def _verify_structure(conn: sqlite3.Connection, verification: dict[str, Any]) ->
verification["integrity_check"] = integrity_rows
if integrity_rows != ["ok"]:
errors.append("PRAGMA integrity_check did not return exactly 'ok'")
foreign_key_rows = [list(row) for row in conn.execute("PRAGMA foreign_key_check").fetchall()]
verification["foreign_key_check"] = foreign_key_rows
if foreign_key_rows:
errors.append("foreign key violations remain")
verification["journal_mode"] = _journal_mode(conn)
schema_row = conn.execute("SELECT version FROM schema_version LIMIT 1").fetchone()
verification["schema_version"] = int(schema_row[0]) if schema_row else None
if verification["schema_version"] != SCHEMA_VERSION:
errors.append(f"schema version is {verification['schema_version']}, expected {SCHEMA_VERSION}")
meta = dict(conn.execute("SELECT key, value FROM state_meta WHERE key LIKE 'fts_%'").fetchall())
meta = {str(key): value for key, value in meta.items()}
verification["fts_meta"] = meta
@@ -876,15 +847,12 @@ def _verify_row_counts(
verification["loss_detected"] = True
else:
verification["errors"].append(message)
counts = {table: _count_rows(conn, table) for table in _INVENTORY_TABLES if _table_columns(conn, table)}
verification["table_counts"] = counts
for table in ("sessions", "messages", *_AUXILIARY_TABLES):
expected = expected_counts.get(table)
if expected is not None and counts.get(table) != expected:
flag(f"{table} count is {counts.get(table)}, expected {expected}", soft=allow_partial)
cleanup = orphan_cleanup or {}
rebuilt_sessions = int(cleanup.get("sessions_reconstructed") or 0)
retained_messages = int(cleanup.get("messages_retained") or 0)
@@ -904,7 +872,6 @@ def _verify_row_counts(
or (table == "sessions" and sessions_fully_reconstructed)
)
flag(f"{table} copy status is {status}", soft=allow_partial and tolerable)
if orphan_cleanup:
orphan_count = int(orphan_cleanup.get("total_removed_or_relinked") or 0)
if orphan_count:
@@ -923,12 +890,10 @@ def _verify_recovered_database(
allow_partial: bool = False, orphan_cleanup: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
verification: dict[str, Any] = {"errors": [], "warnings": [], "loss_detected": False}
open_error = _db_opens_cleanly(output)
verification["opens_cleanly"] = open_error is None
if open_error is not None:
verification["errors"].append(f"database health probe: {open_error}")
conn = sqlite3.connect(str(output), isolation_level=None)
try:
_verify_structure(conn, verification)
@@ -941,7 +906,6 @@ def _verify_recovered_database(
verification["errors"].append(f"verification query failed: {exc}")
finally:
conn.close()
verification["healthy"] = not verification["errors"]
verification["complete"] = bool(verification["healthy"] and not verification["loss_detected"])
return verification
@@ -959,7 +923,6 @@ def _finalize_derived_metadata(destination: sqlite3.Connection) -> dict[str, Any
if fts_tables != {"messages_fts", "messages_fts_trigram"}:
result["error"] = "fresh destination is missing required FTS tables"
return result
fts_keys = tuple(key for key in _GENERATED_META_KEYS if key.startswith("fts_"))
placeholders = ", ".join("?" for _ in fts_keys)
with _immediate_transaction(destination):
@@ -983,7 +946,6 @@ def _recover_via_lost_and_found(
SQLITE3_CLI_GUIDANCE, LostAndFoundError, find_sqlite3_cli, map_lost_and_found_rows, rebuild_fts_indexes,
run_cli_lost_and_found_recover, stub_missing_parent_sessions,
)
missing = ", ".join(missing_required)
sqlite3_bin = find_sqlite3_cli()
if sqlite3_bin is None:
@@ -998,7 +960,6 @@ def _recover_via_lost_and_found(
f"Partial recovery could not read the table schemas for: {missing}, "
f"and page-level .recover salvage failed: {exc}"
) from exc
lf_conn = sqlite3.connect(str(lf_path), isolation_level=None)
destination_conn = _fresh_destination(output)
try:
@@ -1009,7 +970,6 @@ def _recover_via_lost_and_found(
finally:
lf_conn.close()
destination_conn.close()
copy_report: dict[str, dict[str, Any]] = {
table: {
"mode": "lost_and_found_salvage",
@@ -1034,7 +994,6 @@ def _recover_via_lost_and_found(
"pages and mapped heuristically. Review every count before trusting this output."
)
verification["complete"] = False
return _recovery_report(
source, output, inspection, disk_space, verification, on_source_change="healthy",
allow_partial=True, mode="lost_and_found_salvage", best_effort=True, unreadable_schemas=missing_required,
@@ -1083,11 +1042,9 @@ def recover_session_database(
copied before SQLite opens anything; ``output_path`` must not exist and is never swapped into place."""
if chunk_size <= 0:
raise SessionRecoverySafetyError("chunk_size must be greater than zero")
source, output, work_root = _validate_paths(source_path, output_path=output_path, work_dir=work_dir)
assert output is not None
disk_space = _disk_space_preflight(source, work_root, output.parent)
temp_dir, snapshot_source, inspection = _snapshot_and_inspect(source, work_root)
try:
if not inspection.get("recoverable") and not allow_partial:
@@ -1106,7 +1063,6 @@ def recover_session_database(
source=source, snapshot_source=snapshot_source, snapshot_dir=Path(temp_dir.name), output=output,
inspection=inspection, disk_space=disk_space, missing_required=missing_required,
)
source_conn = _connect(snapshot_source)
source_conn.execute("PRAGMA writable_schema=ON")
destination_conn: Optional[sqlite3.Connection] = None
@@ -1114,7 +1070,6 @@ def recover_session_database(
destination_conn = _fresh_destination(
output, topic_tables=any(inspection["tables"][table].get("available") for table in _TOPIC_TABLES),
)
copy_report: dict[str, dict[str, Any]] = {}
for table in (*_CANONICAL_TABLES, "state_meta", *_TOPIC_TABLES, *_AUXILIARY_TABLES):
if table not in _CANONICAL_TABLES and not inspection["tables"][table].get("available"):
@@ -1132,7 +1087,6 @@ def recover_session_database(
source_conn.close()
if destination_conn is not None:
destination_conn.close()
verification = _verify_recovered_database(
output,
expected_counts={

View File

@@ -108,7 +108,6 @@ def _cmd_repair(args):
print(f" strategy: {report.get('strategy')}")
try:
from hermes_state import SessionDB
with SessionDB() as _repair_db:
n = _repair_db._conn.execute("SELECT COUNT(*) FROM sessions").fetchone()[0]
print(f"✓ Repaired — {n} sessions recovered.")
@@ -135,7 +134,6 @@ def _cmd_repair(args):
def _cmd_recover(args):
"""Offline recovery: works on a disposable copy of the source; never touches the active database."""
import sqlite3
from hermes_cli.session_recovery import (
SessionRecoveryError, inspect_session_database, recover_session_database, write_recovery_report,
)
@@ -158,7 +156,6 @@ def _cmd_recover(args):
if bad:
print(f"Error: {msg}")
return 2
work_dir = getattr(args, "work_dir", None)
try:
if inspect_only:
@@ -175,7 +172,6 @@ def _cmd_recover(args):
print(f"Error: session recovery failed: {exc}")
print("The supplied source database was not replaced or deleted.")
return 1
if report_path is not None:
try:
written_report = write_recovery_report(report_path, report)
@@ -185,7 +181,6 @@ def _cmd_recover(args):
print(f"Recovery report: {written_report}")
else:
print(json.dumps(report, indent=2, sort_keys=True))
if inspect_only:
return 0 if report.get("recoverable") else 1
return _print_recovery_verdict(report, output, allow_partial)
@@ -270,9 +265,7 @@ def _cmd_list(db, args):
def _in_workspace(s):
key = (_ws_key(s) or "").lower()
return bool(key) and (_needle in key or _needle == os.path.basename(key.rstrip("/\\")))
sessions = [s for s in sessions if _in_workspace(s)]
if not sessions:
print("No sessions found.")
return
@@ -284,11 +277,9 @@ def _cmd_list(db, args):
def _ws(s): # repo/dir basename, "—" when unbound
key = _ws_key(s)
return ((os.path.basename(key.rstrip("/\\")) or key) if key else "—")[:16]
_title = lambda s, n: (s.get("title") or "—")[:n] # noqa: E731
_preview = lambda s, n: s.get("preview", "")[:n] # noqa: E731
_ago = lambda s: _relative_time(s.get("last_active")) # noqa: E731
layouts = { # (has_ws, has_titles): header, rule width, row formatter
(True, True): (f"{'Title':<28} {'Workspace':<18} {'Last Active':<13} {'ID'}", 110,
lambda s: f"{_title(s, 26):<28} {_ws(s):<18} {_ago(s):<13} {s['id']}"),
@@ -344,7 +335,6 @@ def _cmd_export(db, args):
print("--dry-run requires at least one filter.")
return None
return [_redact(s) for s in db.export_all(source=None)]
if getattr(args, "only", None):
return _export_only(args, _collect_sessions)
if args.format == "trace":
@@ -408,10 +398,8 @@ def _export_trace(db, args, filters):
if session_id and not db.resolve_session_id(session_id):
_not_found(session_id)
return
from agent.trace_upload import TraceRedactionError, build_trace_jsonl, upload_session_trace
redact_trace = not getattr(args, "no_redact", False)
if getattr(args, "upload", False):
if not session_id:
print("--upload exports one session: pass --session-id (or drop filters to use the most recent).")
@@ -420,7 +408,6 @@ def _export_trace(db, args, filters):
db.close()
print(upload_session_trace(resolved, cwd="", redact=redact_trace, private=not getattr(args, "public", False)))
return
if session_id:
ids = [db.resolve_session_id(session_id)]
else:
@@ -435,7 +422,6 @@ def _export_trace(db, args, filters):
if not messages:
return None
return build_trace_jsonl(messages, session_id=sid, model=meta.get("model") or "", cwd="", redact=redact_trace)
try:
if len(ids) == 1:
jsonl = _render_trace(ids[0])
@@ -473,7 +459,6 @@ def _export_markdown(db, args, filters, redact):
path = write_session_markdown(data, output_dir, fmt=args.format, force=args.force)
append_manifest_entry(output_dir, data, path, fmt=args.format)
return data, path
if args.delete_after_verified and not args.yes:
print("--delete-after-verified requires --yes.")
return
@@ -481,10 +466,8 @@ def _export_markdown(db, args, filters, redact):
print("--delete-after-verified is only supported with --session-id.")
return
lineage_is_logical = getattr(args, "lineage", "single") == "logical"
if args.session_id:
return _export_markdown_single(db, args, _export_one, output_dir, lineage_is_logical)
if not filters:
print("Refusing bulk export without a filter. Pass --session-id or "
"at least one filter (e.g. --older-than 90, --source telegram).")
@@ -527,7 +510,6 @@ def _export_markdown_single(db, args, export_one, output_dir, lineage_is_logical
print(f"Session '{target_id}' disappeared during export; nothing was deleted.")
return
exported_items.append((data, exported_path))
message_count = sum(len(data.get("messages") or []) for data, _path in exported_items)
suffix = "" if message_count == 1 else "s"
n = len(exported_items)
@@ -590,12 +572,10 @@ def _prune_never_active_keyed(db, args):
)
return
days = seconds / 86400.0
candidates = db.list_never_active_keyed_sessions(older_than_days=days)
if not candidates:
print(f"No never-active keyed sessions older than {days:g} day(s).")
return
shown = candidates if args.dry_run else candidates[:15]
print(f"{len(candidates)} never-active keyed session(s) older than {days:g} day(s) "
"— no messages, tokens, tool calls or title:")
@@ -604,14 +584,12 @@ def _prune_never_active_keyed(db, args):
f"{s.get('session_key') or '-'}")
if len(candidates) > len(shown):
print(f" … {len(candidates) - len(shown)} more")
if args.dry_run:
print("Dry run — nothing deleted.")
return
if not args.yes and not _confirm_prompt(f"Delete {len(candidates)} session(s)? [y/N] "):
print("Aborted.")
return
deleted, routing_deleted = db.prune_never_active_keyed_sessions(
older_than_days=days, sessions_dir=_sessions_dir()
)
@@ -664,7 +642,6 @@ def _cmd_prune_or_archive(db, args, action):
filters["include_pinned"] = getattr(args, "include_pinned", False)
if not filters["include_pinned"]:
_note_pinned_skipped(db, filters, action)
candidates = db.list_prune_candidates(**filters)
# Archive expands each row to its compression lineage (may include open continuations), so a
# direct-open count would misdescribe its effect.
@@ -774,12 +751,10 @@ def _cmd_retitle_skills(db, args):
"""Reject non-titles: an auxiliary model occasionally answers the prompt instead of titling it
('$ df -h /'). This is a REPAIR — never replace a serviceable title with that."""
return bool(candidate) and candidate[0].isalnum()
candidates = db.list_skill_scaffolded_sessions(limit=limit)
if not candidates:
print("No sessions were titled from a /skill invocation.")
return
mode = "" if apply_changes else " (dry run — pass --apply to write)"
print(f"{len(candidates)} session(s) opened with a /skill{mode}:")
changed = 0
@@ -806,7 +781,6 @@ def _cmd_retitle_skills(db, args):
except ValueError as e:
print(f" skipped: {e}")
changed -= 1
if not changed:
print(" every title already reflects the user's request.")
elif apply_changes:
@@ -822,7 +796,6 @@ def _cmd_browse(db, args):
db.close()
print("No sessions found.")
return
try: # keep the DB open: the picker uses it for status tags and 'd' delete
selected_id = _session_browse_picker(sessions, session_db=db)
finally:
@@ -909,7 +882,6 @@ def _cmd_optimize_storage(db, args):
if resp not in ("y", "yes"):
print("Cancelled.")
return
_last = {"phase": None}
def _progress(info):
@@ -921,7 +893,6 @@ def _cmd_optimize_storage(db, args):
label = {"teardown": "Reclaiming old index", "vacuum": "Compacting database (VACUUM)", "done": "Done"}
print(f"\n {label.get(phase, phase)}…", flush=True)
_last["phase"] = phase
print("Optimizing search-index storage…")
try:
result = db.optimize_fts_storage(progress_cb=_progress, vacuum=do_vacuum)
@@ -948,7 +919,6 @@ def _cmd_repair_routing(db, args):
f"evidence: {record['evidence']})")
else:
print(f" ✗ not repairable — {record['reason']}")
if not records:
print("✓ No gateway sessions are missing their routing identity.")
return
@@ -1012,15 +982,12 @@ def cmd_sessions(args, sessions_parser=None):
pre = _PRE_DB_HANDLERS.get(action)
if pre is not None:
return pre(args)
try:
from hermes_state import SessionDB
db = SessionDB()
except Exception as e:
print(f"Error: Could not open session database: {e}")
return 1
with db:
handler = _DB_HANDLERS.get(action)
if handler is None:

View File

@@ -98,13 +98,11 @@ class _CursesBrowser:
header = " Browse sessions — ↑↓ navigate Enter select Type to filter Esc quit"
header_attr = c.A_BOLD | self._pair(2)
self._put(stdscr, 0, 0, header, max_x - 1, header_attr)
name_width = max(20, max_x - _FIXED_COLS)
col_header = (
f" {'Title / Preview':<{name_width}} {'Stat':<5} {'Msgs':>5} {'Active':<10} {'Src':<5} {'ID'}"
)
self._put(stdscr, 1, 0, col_header, max_x - 1, self._pair(4, c.A_DIM))
visible_rows = max(max_y - 4, 1) # header + col header + blank + footer
filtered = self.filtered
if not filtered:
@@ -130,7 +128,6 @@ class _CursesBrowser:
stdscr.addnstr(y, tag_x, f"{_session_status_tag(status):<5}", 5, self._status_attr(status))
except c.error:
pass
footer_attr = self._pair(4, c.A_DIM)
if self.confirm_delete is not None:
label = _label(self.confirm_delete)
@@ -163,7 +160,6 @@ class _CursesBrowser:
else:
self.flash = "Delete failed."
return False
if key in (c.KEY_UP, c.KEY_DOWN):
if self.filtered:
self.cursor = (self.cursor + (1 if key == c.KEY_DOWN else -1)) % len(self.filtered)
@@ -230,7 +226,6 @@ def _fallback_picker(sessions: list) -> Optional[str]:
f"{_msgs_str(s):>5} {_relative_time(s.get('last_active')):<10} "
f"{s.get('source', '')[:6]}"
)
while True:
try:
val = input(f"\n Select [1-{len(sessions)}]: ").strip()
@@ -256,13 +251,11 @@ def _session_browse_picker(sessions: list, session_db=None) -> Optional[str]:
if not sessions:
print("No sessions found.")
return None
_annotate_session_statuses(sessions, session_db)
def _delete_session(session_id: str) -> bool:
try:
from hermes_cli.sessions_cmd import get_hermes_home
sessions_dir = get_hermes_home() / "sessions"
except Exception:
sessions_dir = None
@@ -270,10 +263,8 @@ def _session_browse_picker(sessions: list, session_db=None) -> Optional[str]:
return bool(session_db.delete_session(session_id, sessions_dir=sessions_dir))
except Exception:
return False
try: # curses first; any failure (no curses module, odd terminal) falls back
import curses
browser = _CursesBrowser(curses, sessions, _delete_session if session_db is not None else None)
curses.wrapper(browser.run)
return browser.result