mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix: /retry, /undo, /compress, and /reset gateway commands (#210)
- /retry, /undo, /compress were setting a non-existent conversation_history attribute on SessionEntry (a @dataclass with no such field). The dangling attribute was silently created but never read — transcript was reloaded from DB on next interaction, making all three commands no-ops. - /reset accessed self.session_store._sessions (non-existent) instead of self.session_store._entries, causing AttributeError caught by a bare except, silently skipping the pre-reset memory flush. Fix: - Add SessionDB.clear_messages() to delete messages and reset counters - Add SessionStore.rewrite_transcript() to atomically replace transcript in both SQLite and legacy JSONL storage - Replace all dangling attr assignments with rewrite_transcript() calls - Fix _sessions → _entries in /reset handler Closes #210
This commit is contained in:
parent
0512ada793
commit
698b35933e
3 changed files with 44 additions and 5 deletions
|
|
@ -990,7 +990,7 @@ class GatewayRunner:
|
||||||
# Memory flush before reset: load the old transcript and let a
|
# Memory flush before reset: load the old transcript and let a
|
||||||
# temporary agent save memories before the session is wiped.
|
# temporary agent save memories before the session is wiped.
|
||||||
try:
|
try:
|
||||||
old_entry = self.session_store._sessions.get(session_key)
|
old_entry = self.session_store._entries.get(session_key)
|
||||||
if old_entry:
|
if old_entry:
|
||||||
old_history = self.session_store.load_transcript(old_entry.session_id)
|
old_history = self.session_store.load_transcript(old_entry.session_id)
|
||||||
if old_history:
|
if old_history:
|
||||||
|
|
@ -1222,9 +1222,9 @@ class GatewayRunner:
|
||||||
if not last_user_msg:
|
if not last_user_msg:
|
||||||
return "No previous message to retry."
|
return "No previous message to retry."
|
||||||
|
|
||||||
# Truncate history to before the last user message
|
# Truncate history to before the last user message and persist
|
||||||
truncated = history[:last_user_idx]
|
truncated = history[:last_user_idx]
|
||||||
session_entry.conversation_history = truncated
|
self.session_store.rewrite_transcript(session_entry.session_id, truncated)
|
||||||
|
|
||||||
# Re-send by creating a fake text event with the old message
|
# Re-send by creating a fake text event with the old message
|
||||||
retry_event = MessageEvent(
|
retry_event = MessageEvent(
|
||||||
|
|
@ -1256,7 +1256,7 @@ class GatewayRunner:
|
||||||
|
|
||||||
removed_msg = history[last_user_idx].get("content", "")
|
removed_msg = history[last_user_idx].get("content", "")
|
||||||
removed_count = len(history) - last_user_idx
|
removed_count = len(history) - last_user_idx
|
||||||
session_entry.conversation_history = history[:last_user_idx]
|
self.session_store.rewrite_transcript(session_entry.session_id, history[:last_user_idx])
|
||||||
|
|
||||||
preview = removed_msg[:40] + "..." if len(removed_msg) > 40 else removed_msg
|
preview = removed_msg[:40] + "..." if len(removed_msg) > 40 else removed_msg
|
||||||
return f"↩️ Undid {removed_count} message(s).\nRemoved: \"{preview}\""
|
return f"↩️ Undid {removed_count} message(s).\nRemoved: \"{preview}\""
|
||||||
|
|
@ -1330,7 +1330,7 @@ class GatewayRunner:
|
||||||
lambda: tmp_agent._compress_context(msgs, "", approx_tokens=approx_tokens),
|
lambda: tmp_agent._compress_context(msgs, "", approx_tokens=approx_tokens),
|
||||||
)
|
)
|
||||||
|
|
||||||
session_entry.conversation_history = compressed
|
self.session_store.rewrite_transcript(session_entry.session_id, compressed)
|
||||||
new_count = len(compressed)
|
new_count = len(compressed)
|
||||||
new_tokens = estimate_messages_tokens_rough(compressed)
|
new_tokens = estimate_messages_tokens_rough(compressed)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -567,6 +567,34 @@ class SessionStore:
|
||||||
with open(transcript_path, "a") as f:
|
with open(transcript_path, "a") as f:
|
||||||
f.write(json.dumps(message, ensure_ascii=False) + "\n")
|
f.write(json.dumps(message, ensure_ascii=False) + "\n")
|
||||||
|
|
||||||
|
def rewrite_transcript(self, session_id: str, messages: List[Dict[str, Any]]) -> None:
|
||||||
|
"""Replace the entire transcript for a session with new messages.
|
||||||
|
|
||||||
|
Used by /retry, /undo, and /compress to persist modified conversation history.
|
||||||
|
Rewrites both SQLite and legacy JSONL storage.
|
||||||
|
"""
|
||||||
|
# SQLite: clear old messages and re-insert
|
||||||
|
if self._db:
|
||||||
|
try:
|
||||||
|
self._db.clear_messages(session_id)
|
||||||
|
for msg in messages:
|
||||||
|
self._db.append_message(
|
||||||
|
session_id=session_id,
|
||||||
|
role=msg.get("role", "unknown"),
|
||||||
|
content=msg.get("content"),
|
||||||
|
tool_name=msg.get("tool_name"),
|
||||||
|
tool_calls=msg.get("tool_calls"),
|
||||||
|
tool_call_id=msg.get("tool_call_id"),
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug("Failed to rewrite transcript in DB: %s", e)
|
||||||
|
|
||||||
|
# JSONL: overwrite the file
|
||||||
|
transcript_path = self.get_transcript_path(session_id)
|
||||||
|
with open(transcript_path, "w") as f:
|
||||||
|
for msg in messages:
|
||||||
|
f.write(json.dumps(msg, ensure_ascii=False) + "\n")
|
||||||
|
|
||||||
def load_transcript(self, session_id: str) -> List[Dict[str, Any]]:
|
def load_transcript(self, session_id: str) -> List[Dict[str, Any]]:
|
||||||
"""Load all messages from a session's transcript."""
|
"""Load all messages from a session's transcript."""
|
||||||
# Try SQLite first
|
# Try SQLite first
|
||||||
|
|
|
||||||
|
|
@ -476,6 +476,17 @@ class SessionDB:
|
||||||
results.append({**session, "messages": messages})
|
results.append({**session, "messages": messages})
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
def clear_messages(self, session_id: str) -> None:
|
||||||
|
"""Delete all messages for a session and reset its counters."""
|
||||||
|
self._conn.execute(
|
||||||
|
"DELETE FROM messages WHERE session_id = ?", (session_id,)
|
||||||
|
)
|
||||||
|
self._conn.execute(
|
||||||
|
"UPDATE sessions SET message_count = 0, tool_call_count = 0 WHERE id = ?",
|
||||||
|
(session_id,),
|
||||||
|
)
|
||||||
|
self._conn.commit()
|
||||||
|
|
||||||
def delete_session(self, session_id: str) -> bool:
|
def delete_session(self, session_id: str) -> bool:
|
||||||
"""Delete a session and all its messages. Returns True if found."""
|
"""Delete a session and all its messages. Returns True if found."""
|
||||||
cursor = self._conn.execute(
|
cursor = self._conn.execute(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue