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:
|
if self.agent:
|
||||||
self.agent.session_id = new_session_id
|
self.agent.session_id = new_session_id
|
||||||
self.agent.session_start = now
|
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()
|
self.agent.reset_session_state()
|
||||||
if hasattr(self.agent, "_last_flushed_db_idx"):
|
if hasattr(self.agent, "_last_flushed_db_idx"):
|
||||||
self.agent._last_flushed_db_idx = len(self.conversation_history)
|
self.agent._last_flushed_db_idx = len(self.conversation_history)
|
||||||
|
|||||||
@@ -832,7 +832,18 @@ class SessionDB:
|
|||||||
params = []
|
params = []
|
||||||
|
|
||||||
if not include_children:
|
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:
|
if source:
|
||||||
where_clauses.append("s.source = ?")
|
where_clauses.append("s.source = ?")
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ AUTHOR_MAP = {
|
|||||||
"keira.voss94@gmail.com": "keiravoss94",
|
"keira.voss94@gmail.com": "keiravoss94",
|
||||||
"16443023+stablegenius49@users.noreply.github.com": "stablegenius49",
|
"16443023+stablegenius49@users.noreply.github.com": "stablegenius49",
|
||||||
"fqsy1416@gmail.com": "EKKOLearnAI",
|
"fqsy1416@gmail.com": "EKKOLearnAI",
|
||||||
|
"octo-patch@github.com": "octo-patch",
|
||||||
"simbamax99@gmail.com": "simbam99",
|
"simbamax99@gmail.com": "simbam99",
|
||||||
"iris@growthpillars.co": "irispillars",
|
"iris@growthpillars.co": "irispillars",
|
||||||
"185121704+stablegenius49@users.noreply.github.com": "stablegenius49",
|
"185121704+stablegenius49@users.noreply.github.com": "stablegenius49",
|
||||||
|
|||||||
@@ -160,6 +160,30 @@ class TestBranchCommandCLI:
|
|||||||
assert agent.reset_session_state.called
|
assert agent.reset_session_state.called
|
||||||
assert agent._last_flushed_db_idx == 4 # len(conversation_history)
|
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):
|
def test_branch_sets_resumed_flag(self, cli_instance, session_db):
|
||||||
"""Branch should set _resumed=True to prevent auto-title generation."""
|
"""Branch should set _resumed=True to prevent auto-title generation."""
|
||||||
from cli import HermesCLI
|
from cli import HermesCLI
|
||||||
|
|||||||
@@ -1485,6 +1485,48 @@ class TestListSessionsRich:
|
|||||||
assert "\n" not in sessions[0]["preview"]
|
assert "\n" not in sessions[0]["preview"]
|
||||||
assert "Line one Line two" 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:
|
class TestCompressionChainProjection:
|
||||||
"""Tests for lineage-aware list_sessions_rich — compressed conversations
|
"""Tests for lineage-aware list_sessions_rich — compressed conversations
|
||||||
|
|||||||
Reference in New Issue
Block a user