fix(agent): corrupt-session recovery guidance names the session's own state.db
The corruption explainer filled `{db_path}` from `_default_db_path()`, the
process default. A Desktop `serve` backend launched on the root home hosts
named-profile sessions whose SessionDB is `profiles/<name>/state.db`, so the
operator was told to inspect/repair a different profile's database. Pass the
agent's own `_session_db.db_path` from the turn finalizer; the process
default remains the fallback for agents without a bound store.
Reported in #105887.
This commit is contained in:
@@ -296,7 +296,7 @@ class TurnExplainersMixin:
|
||||
|
||||
@staticmethod
|
||||
def _format_turn_completion_explanation(
|
||||
turn_exit_reason: str, persistence_cause: Optional[str] = None
|
||||
turn_exit_reason: str, persistence_cause: Optional[str] = None, db_path=None
|
||||
) -> str:
|
||||
"""User-facing explanation for an abnormal turn ending, or "" for normal / unknown reasons.
|
||||
|
||||
@@ -319,11 +319,14 @@ class TurnExplainersMixin:
|
||||
persistence_cause or "unknown", _PERSISTENCE_DEFAULT_EXPLANATION
|
||||
)
|
||||
if persistence_cause == "corrupt":
|
||||
# Copy-pasteable, so name the real store (profiles / HERMES_HOME do not live under ~/.hermes).
|
||||
# Copy-pasteable, so name the store that actually failed: the agent's own
|
||||
# SessionDB. A multi-profile backend (Desktop serve) hosts sessions whose
|
||||
# state.db is NOT the process default, so the default would send the operator
|
||||
# to inspect/repair the wrong profile's database (#105887).
|
||||
from hermes_constants import get_default_hermes_root
|
||||
from hermes_state import _default_db_path
|
||||
|
||||
body = body.replace("{db_path}", str(_default_db_path()))
|
||||
body = body.replace("{db_path}", str(db_path or _default_db_path()))
|
||||
body = body.replace(
|
||||
"{backups_dir}", str(get_default_hermes_root() / "backups")
|
||||
)
|
||||
|
||||
@@ -375,7 +375,8 @@ def _explain_abnormal_exit(agent, final_response, _turn_exit_reason, preserved_v
|
||||
)
|
||||
if _is_empty_terminal or _is_partial_fragment or str(_turn_exit_reason) == "partial_stream_recovery":
|
||||
_explanation = agent._format_turn_completion_explanation(
|
||||
_turn_exit_reason, getattr(agent, "_last_persistence_error_cause", None)
|
||||
_turn_exit_reason, getattr(agent, "_last_persistence_error_cause", None),
|
||||
db_path=getattr(getattr(agent, "_session_db", None), "db_path", None),
|
||||
)
|
||||
if _explanation:
|
||||
# Replace the bare sentinel; keep a partial fragment and append why.
|
||||
|
||||
@@ -63,6 +63,26 @@ def test_gateway_corruption_banner_backups_dir_follows_hermes_home(monkeypatch,
|
||||
assert "~/.hermes/backups" not in sent[0]
|
||||
|
||||
|
||||
def test_format_turn_completion_corrupt_names_the_sessions_own_store(monkeypatch, tmp_path):
|
||||
"""Recovery commands target the store that failed, not the process default (#105887).
|
||||
|
||||
A Desktop ``serve`` backend launched on the root home hosts named-profile sessions
|
||||
whose SessionDB is ``profiles/<name>/state.db``; guidance built from the process
|
||||
default would tell the operator to inspect/repair the root database.
|
||||
"""
|
||||
from run_agent import AIAgent
|
||||
|
||||
root = tmp_path / "root"
|
||||
monkeypatch.setenv("HERMES_HOME", str(root))
|
||||
failing = root / "profiles" / "research" / "state.db"
|
||||
|
||||
explanation = AIAgent._format_turn_completion_explanation(
|
||||
"session_persistence_failed", "corrupt", db_path=failing
|
||||
)
|
||||
assert f"--source {failing} --inspect-only" in explanation
|
||||
assert f"--source {root / 'state.db'}" not in explanation
|
||||
|
||||
|
||||
def test_format_turn_completion_corrupt_never_names_the_live_db():
|
||||
"""The 'corrupt' cause must not direct a raw sqlite3 shell at the live DB.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user