diff --git a/hermes_state_sessions.py b/hermes_state_sessions.py index 0a7aa822c7..cfd7811fdf 100644 --- a/hermes_state_sessions.py +++ b/hermes_state_sessions.py @@ -748,7 +748,9 @@ class SessionSessionsMixin: """Most recently used main-loop model route as one coherent per-call tuple (``session_model_usage`` keeps model+provider together; ``sessions`` mixes route changes). Recency, not lifetime call count: on a long session a route retired weeks ago can hold the - highest ``api_call_count`` forever, and /status and /usage would keep calling it current.""" + highest ``api_call_count`` forever, and /status and /usage would keep calling it current. + ``rowid DESC`` breaks same-timestamp ties toward the route that first appeared later; without + it SQLite's temp-sort order is unspecified and the retired route can win.""" self.flush_token_counts() row = self._read_one( """SELECT model, billing_provider, billing_base_url, billing_mode, @@ -758,7 +760,7 @@ class SessionSessionsMixin: AND task = '' AND model <> 'unknown' AND billing_provider <> '' - ORDER BY last_seen DESC + ORDER BY last_seen DESC, rowid DESC LIMIT 1""", (session_id,), ) diff --git a/tests/hermes_state/test_recent_model_route.py b/tests/hermes_state/test_recent_model_route.py new file mode 100644 index 0000000000..1f2cd557dd --- /dev/null +++ b/tests/hermes_state/test_recent_model_route.py @@ -0,0 +1,17 @@ +"""get_recent_session_model_route picks the newest route deterministically on equal last_seen.""" +from hermes_state import SessionDB + + +def test_recent_route_tie_on_last_seen_prefers_later_row(tmp_path): + db = SessionDB(tmp_path / "state.db") + db.create_session(session_id="s1", source="tui", model="alpha") + with db._lock: + for model in ("alpha", "zeta"): + db._conn.execute( + "INSERT INTO session_model_usage (session_id, model, billing_provider, task," + " api_call_count, last_seen) VALUES ('s1', ?, 'p', '', 1, 100.0)", + (model,), + ) + db._conn.commit() + + assert db.get_recent_session_model_route("s1")["model"] == "zeta" diff --git a/tests/tui_gateway/test_tui_gateway_server.py b/tests/tui_gateway/test_tui_gateway_server.py index b7c3ee0985..b744e3ca6b 100644 --- a/tests/tui_gateway/test_tui_gateway_server.py +++ b/tests/tui_gateway/test_tui_gateway_server.py @@ -11770,6 +11770,21 @@ def test_session_status_reads_live_compute_host_metadata(monkeypatch): assert "Model: live-host-model (live-host-provider)" in resp["result"]["output"] +def test_session_status_falls_back_to_agent_before_first_host_frame(monkeypatch): + agent = types.SimpleNamespace(model="live-model", provider="live-provider") + server._sessions["sid"] = _session(agent=agent, _compute_host_active=True) + monkeypatch.setattr(server, "_get_db", lambda: None) + + try: + resp = server.handle_request( + {"id": "1", "method": "session.status", "params": {"session_id": "sid"}} + ) + finally: + server._sessions.pop("sid", None) + + assert "Model: live-model (live-provider)" in resp["result"]["output"] + + def test_skills_reload_runs_in_gateway_process(monkeypatch): import agent.skill_commands as skill_commands diff --git a/tui_gateway/methods_session.py b/tui_gateway/methods_session.py index 96f97266c9..efc946d44c 100644 --- a/tui_gateway/methods_session.py +++ b/tui_gateway/methods_session.py @@ -1663,11 +1663,14 @@ def _(rid, params: dict, session: dict) -> dict: key = session.get("session_key") or params.get("session_id") or "" mirror = _metadata_mirror(session) # Under turn isolation the compute host owns the live route: a stale in-process agent object - # must not outrank the host's mirrored model/provider. - agent = None if session.get("_compute_host_active") else session.get("agent") + # must not outrank the host's mirrored model/provider. Before the first host frame fills the + # mirror, the in-process agent is still the only route we know (same order as _session_info). + live_agent = session.get("agent") + agent = None if session.get("_compute_host_active") else live_agent fields = build_status_fields( key, agent, _status_row(session, params, key), - model=mirror.get("model"), provider=mirror.get("provider"), + model=mirror.get("model") or getattr(live_agent, "model", None), + provider=mirror.get("provider") or getattr(live_agent, "provider", None), tokens=_session_usage_snapshot(session).get("total"), agent_running=bool(session.get("running")), ) project = _project_info_for_cwd(_display_session_cwd(session))