mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-19 10:02:16 +00:00
fix(state): keep /branch sessions visible after parent reopen
/branch (aka /fork) sessions vanished from /resume and /sessions. Both surfaces funnel through list_sessions_rich(include_children=False), which hid any session with a parent_session_id unless identified as a branch via a heuristic — parent.end_reason == 'branched' AND child.started_at >= parent.ended_at. Two ways that heuristic failed: 1. CLI/gateway branches: once the parent was reopened (e.g. resumed) and re-ended with a different end_reason (tui_shutdown overwriting 'branched'), the heuristic stopped matching and the branch was hidden permanently. 2. TUI branches (tui_gateway session.branch): the TUI never ends the parent as 'branched' — it creates the child while the parent is still live — so the heuristic NEVER matched and TUI branches were hidden from the moment they were created (this is the macOS desktop app's primary symptom). Fix: persist a stable '_branched_from' marker in the branch session's model_config at creation time across ALL THREE branch paths (CLI cli.py, gateway gateway/run.py, and TUI tui_gateway/server.py), and OR a json_extract(model_config, '$._branched_from') IS NOT NULL check into the list_sessions_rich filter. The marker is immutable across the parent's lifecycle, so the branch stays visible regardless of how/whether the parent is ended. The legacy end_reason heuristic is kept (OR'd) so pre-existing branches remain visible. Subagent/compression children (no marker, parent not 'branched') stay correctly hidden. Fixes #20856. Approach by liuhao1024 (PR #20864); reimplemented on current main, extended to the TUI branch path (which the original missed), with regression tests for the reopen+re-end scenario and the TUI marker persistence.
This commit is contained in:
parent
d1367355d5
commit
a3fb48b2ce
6 changed files with 136 additions and 7 deletions
|
|
@ -613,6 +613,71 @@ def test_session_resume_live_payload_uses_current_history_with_ancestors(server,
|
|||
]
|
||||
|
||||
|
||||
def test_session_branch_persists_branched_from_marker(server, monkeypatch):
|
||||
"""TUI /branch must persist a _branched_from marker so the branch stays
|
||||
visible in /resume and /sessions.
|
||||
|
||||
Regression for issue #20856: the TUI branch leaves the parent live (it
|
||||
never ends it with end_reason='branched'), so list_sessions_rich's legacy
|
||||
heuristic never surfaces it — the stable model_config marker is the only
|
||||
thing that keeps a TUI branch visible.
|
||||
"""
|
||||
create_calls = []
|
||||
|
||||
class _DB:
|
||||
def get_session_title(self, _key):
|
||||
return "parent-title"
|
||||
|
||||
def get_next_title_in_lineage(self, base):
|
||||
return f"{base} 2"
|
||||
|
||||
def create_session(self, new_key, **kwargs):
|
||||
create_calls.append((new_key, kwargs))
|
||||
return new_key
|
||||
|
||||
def append_message(self, **_kwargs):
|
||||
return None
|
||||
|
||||
def set_session_title(self, _key, _title):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(server, "_get_db", lambda: _DB())
|
||||
monkeypatch.setattr(server, "_resolve_model", lambda: "test/model")
|
||||
monkeypatch.setattr(server, "_new_session_key", lambda: "20260101_000001_child0")
|
||||
monkeypatch.setattr(
|
||||
server,
|
||||
"_make_agent",
|
||||
lambda _sid, key, session_id=None: types.SimpleNamespace(
|
||||
model="test/model", session_id=session_id or key
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(server, "_init_session", lambda *_a, **_k: None)
|
||||
monkeypatch.setattr(server, "_set_session_context", lambda *_a, **_k: [])
|
||||
monkeypatch.setattr(server, "_clear_session_context", lambda *_a, **_k: None)
|
||||
monkeypatch.setattr(server, "_session_cwd", lambda _s: "/tmp/branch-cwd")
|
||||
|
||||
parent_sid = "parent01"
|
||||
parent_key = "20260101_000000_parent"
|
||||
server._sessions[parent_sid] = {
|
||||
"session_key": parent_key,
|
||||
"history": [{"role": "user", "content": "hello"}],
|
||||
"history_lock": threading.Lock(),
|
||||
"cols": 80,
|
||||
}
|
||||
|
||||
resp = server.handle_request(
|
||||
{"id": "b1", "method": "session.branch", "params": {"session_id": parent_sid}}
|
||||
)
|
||||
|
||||
assert "error" not in resp, resp
|
||||
assert len(create_calls) == 1
|
||||
new_key, kwargs = create_calls[0]
|
||||
assert new_key == "20260101_000001_child0"
|
||||
assert kwargs["parent_session_id"] == parent_key
|
||||
# The marker — without it the branch is invisible in /resume and /sessions.
|
||||
assert kwargs["model_config"] == {"_branched_from": parent_key}
|
||||
|
||||
|
||||
def test_make_agent_accepts_list_system_prompt(server, monkeypatch):
|
||||
captured = {}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue