From dbed40f39bd7f3ab3e8f8c47f6fbde34a85078a5 Mon Sep 17 00:00:00 2001 From: landy Date: Mon, 13 Apr 2026 17:50:42 +0800 Subject: [PATCH] fix: reopen resumed gateway sessions in sqlite --- gateway/session.py | 9 +++++++- tests/gateway/test_session.py | 39 +++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/gateway/session.py b/gateway/session.py index a11ade898e..62beeffa84 100644 --- a/gateway/session.py +++ b/gateway/session.py @@ -878,7 +878,8 @@ class SessionStore: Used by ``/resume`` to restore a previously-named session. Ends the current session in SQLite (like reset), but instead of generating a fresh session ID, re-uses ``target_session_id`` so the - old transcript is loaded on the next message. + old transcript is loaded on the next message. If the target session was + previously ended, re-open it so gateway resume semantics match the CLI. """ db_end_session_id = None new_entry = None @@ -918,6 +919,12 @@ class SessionStore: except Exception as e: logger.debug("Session DB end_session failed: %s", e) + if self._db: + try: + self._db.reopen_session(target_session_id) + except Exception as e: + logger.debug("Session DB reopen_session failed: %s", e) + return new_entry def list_sessions(self, active_minutes: Optional[int] = None) -> List[SessionEntry]: diff --git a/tests/gateway/test_session.py b/tests/gateway/test_session.py index b86d18575d..50bc7c0460 100644 --- a/tests/gateway/test_session.py +++ b/tests/gateway/test_session.py @@ -552,6 +552,45 @@ class TestLoadTranscriptPreferLongerSource: assert result[0]["content"] == "db-q" +class TestSessionStoreSwitchSession: + """Regression coverage for gateway /resume session switching semantics.""" + + def test_switch_session_reopens_target_session_in_db(self, tmp_path): + from hermes_state import SessionDB + + config = GatewayConfig() + with patch("gateway.session.SessionStore._ensure_loaded"): + store = SessionStore(sessions_dir=tmp_path / "sessions", config=config) + db = SessionDB(db_path=tmp_path / "state.db") + store._db = db + store._loaded = True + + source = SessionSource( + platform=Platform.FEISHU, + chat_id="chat-1", + chat_type="dm", + user_id="user-1", + user_name="tester", + ) + current_entry = store.get_or_create_session(source) + current_session_id = current_entry.session_id + + target_session_id = "old_session_abc" + db.create_session(target_session_id, source="feishu", user_id="user-1") + db.end_session(target_session_id, end_reason="user_exit") + assert db.get_session(target_session_id)["ended_at"] is not None + + switched = store.switch_session(current_entry.session_key, target_session_id) + + assert switched is not None + assert switched.session_id == target_session_id + assert db.get_session(current_session_id)["end_reason"] == "session_switch" + resumed = db.get_session(target_session_id) + assert resumed["ended_at"] is None + assert resumed["end_reason"] is None + db.close() + + class TestWhatsAppDMSessionKeyConsistency: """Regression: all session-key construction must go through build_session_key so DMs are isolated by chat_id across platforms."""