mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 06:51:16 +08:00
* fix(/branch): redirect session_log_file and expose branch sessions in list
Two bugs when using /branch:
1. cli.py _handle_branch_command updated agent.session_id but not
agent.session_log_file, so all messages written after branching
landed in the original session's JSON file and the branch never
got its own session_{id}.json on disk.
Fix: mirror the compression-split path (run_agent.py:7579) and
update session_log_file immediately after changing session_id.
2. hermes_state.py list_sessions_rich filtered out every session
with parent_session_id IS NOT NULL to hide sub-agent runs and
compression continuations. Branch sessions share this column, so
they became invisible to `hermes sessions list` and `sessions browse`.
Fix: also include branch children — those whose parent ended with
end_reason='branched' AND whose started_at >= parent.ended_at
(the same timing condition that get_compression_tip uses to
distinguish continuations from live-spawned subagents).
Fixes #14854
Co-Authored-By: Octopus <liyuan851277048@icloud.com>
* chore(release): map octo-patch placeholder email in AUTHOR_MAP
---------
Co-authored-by: octo-patch <octo-patch@github.com>
Co-authored-by: Octopus <liyuan851277048@icloud.com>
This commit is contained in:
6
cli.py
6
cli.py
@@ -4915,6 +4915,12 @@ class HermesCLI:
|
||||
if self.agent:
|
||||
self.agent.session_id = new_session_id
|
||||
self.agent.session_start = now
|
||||
# Redirect the JSON session log to the new branch session file so
|
||||
# messages written after branching land in the correct file.
|
||||
if hasattr(self.agent, "session_log_file") and hasattr(self.agent, "logs_dir"):
|
||||
self.agent.session_log_file = (
|
||||
self.agent.logs_dir / f"session_{new_session_id}.json"
|
||||
)
|
||||
self.agent.reset_session_state()
|
||||
if hasattr(self.agent, "_last_flushed_db_idx"):
|
||||
self.agent._last_flushed_db_idx = len(self.conversation_history)
|
||||
|
||||
@@ -832,7 +832,18 @@ class SessionDB:
|
||||
params = []
|
||||
|
||||
if not include_children:
|
||||
where_clauses.append("s.parent_session_id IS NULL")
|
||||
# Show root sessions and branch sessions (whose parent ended with
|
||||
# end_reason='branched' before the child was created), while still
|
||||
# hiding sub-agent runs and compression continuations (which also
|
||||
# carry a parent_session_id but were spawned while the parent was
|
||||
# still live — i.e., started_at < parent.ended_at).
|
||||
where_clauses.append(
|
||||
"(s.parent_session_id IS NULL"
|
||||
" OR EXISTS (SELECT 1 FROM sessions p"
|
||||
" WHERE p.id = s.parent_session_id"
|
||||
" AND p.end_reason = 'branched'"
|
||||
" AND s.started_at >= p.ended_at))"
|
||||
)
|
||||
|
||||
if source:
|
||||
where_clauses.append("s.source = ?")
|
||||
|
||||
@@ -70,6 +70,7 @@ AUTHOR_MAP = {
|
||||
"keira.voss94@gmail.com": "keiravoss94",
|
||||
"16443023+stablegenius49@users.noreply.github.com": "stablegenius49",
|
||||
"fqsy1416@gmail.com": "EKKOLearnAI",
|
||||
"octo-patch@github.com": "octo-patch",
|
||||
"simbamax99@gmail.com": "simbam99",
|
||||
"iris@growthpillars.co": "irispillars",
|
||||
"185121704+stablegenius49@users.noreply.github.com": "stablegenius49",
|
||||
|
||||
@@ -160,6 +160,30 @@ class TestBranchCommandCLI:
|
||||
assert agent.reset_session_state.called
|
||||
assert agent._last_flushed_db_idx == 4 # len(conversation_history)
|
||||
|
||||
def test_branch_updates_agent_session_log_file(self, cli_instance, session_db, tmp_path):
|
||||
"""Branching must redirect the agent's session_log_file to the new session's path."""
|
||||
from cli import HermesCLI
|
||||
from pathlib import Path
|
||||
|
||||
logs_dir = tmp_path / "sessions"
|
||||
logs_dir.mkdir()
|
||||
|
||||
agent = MagicMock()
|
||||
agent._last_flushed_db_idx = 0
|
||||
agent.logs_dir = logs_dir
|
||||
agent.session_log_file = logs_dir / f"session_{cli_instance.session_id}.json"
|
||||
cli_instance.agent = agent
|
||||
|
||||
old_log_file = agent.session_log_file
|
||||
HermesCLI._handle_branch_command(cli_instance, "/branch")
|
||||
|
||||
new_session_id = cli_instance.session_id
|
||||
expected_log = logs_dir / f"session_{new_session_id}.json"
|
||||
assert agent.session_log_file == expected_log, (
|
||||
"session_log_file must point to the branch session, not the original"
|
||||
)
|
||||
assert agent.session_log_file != old_log_file
|
||||
|
||||
def test_branch_sets_resumed_flag(self, cli_instance, session_db):
|
||||
"""Branch should set _resumed=True to prevent auto-title generation."""
|
||||
from cli import HermesCLI
|
||||
|
||||
@@ -1485,6 +1485,48 @@ class TestListSessionsRich:
|
||||
assert "\n" not in sessions[0]["preview"]
|
||||
assert "Line one Line two" in sessions[0]["preview"]
|
||||
|
||||
def test_branch_session_visible_in_list(self, db):
|
||||
"""Branch sessions (parent ended with 'branched') must appear in list_sessions_rich."""
|
||||
db.create_session("parent", "cli")
|
||||
db.end_session("parent", "branched")
|
||||
db.create_session("branch", "cli", parent_session_id="parent")
|
||||
db.append_message("branch", "user", "Exploring the alternative approach")
|
||||
|
||||
sessions = db.list_sessions_rich()
|
||||
ids = [s["id"] for s in sessions]
|
||||
assert "branch" in ids, "Branch session should be visible in default list"
|
||||
|
||||
def test_subagent_session_still_hidden(self, db):
|
||||
"""Sub-agent children (parent NOT ended with 'branched') remain hidden."""
|
||||
db.create_session("root", "cli")
|
||||
db.create_session("delegate", "cli", parent_session_id="root")
|
||||
|
||||
sessions = db.list_sessions_rich()
|
||||
ids = [s["id"] for s in sessions]
|
||||
assert "delegate" not in ids, "Delegate sub-agent should not appear in default list"
|
||||
assert "root" in ids
|
||||
|
||||
def test_compression_child_still_hidden(self, db):
|
||||
"""Compression continuation sessions remain hidden (parent ended with 'compression')."""
|
||||
import time as _time
|
||||
t0 = _time.time()
|
||||
db.create_session("root", "cli")
|
||||
db._conn.execute("UPDATE sessions SET started_at=? WHERE id=?", (t0, "root"))
|
||||
db._conn.execute(
|
||||
"UPDATE sessions SET ended_at=?, end_reason='compression' WHERE id=?",
|
||||
(t0 + 1800, "root"),
|
||||
)
|
||||
db._conn.commit()
|
||||
db.create_session("continuation", "cli", parent_session_id="root")
|
||||
db._conn.execute(
|
||||
"UPDATE sessions SET started_at=? WHERE id=?", (t0 + 1801, "continuation")
|
||||
)
|
||||
db._conn.commit()
|
||||
|
||||
sessions = db.list_sessions_rich(project_compression_tips=False)
|
||||
ids = [s["id"] for s in sessions]
|
||||
assert "continuation" not in ids, "Compression continuation should stay hidden"
|
||||
|
||||
|
||||
class TestCompressionChainProjection:
|
||||
"""Tests for lineage-aware list_sessions_rich — compressed conversations
|
||||
|
||||
Reference in New Issue
Block a user