fix: status falls back to the live agent before the first host frame; recent route ties break deterministically
Under turn isolation `session.status` passed agent=None and only the metadata mirror's model/provider, so until the compute host sent its first frame the mirror was empty and the TUI rendered "Model: (unknown) (unknown)" where main showed the in-process agent's route. Fall back to the live agent's model and provider like `server._session_info` already does. `get_recent_session_model_route` ordered by `last_seen DESC` alone; two rows stamped in the same flush tie and SQLite's temp-sort order is unspecified, so the retired route could be reported as current. Order by `rowid DESC` as the secondary key so the route that appeared later wins.
This commit is contained in:
@@ -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,),
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user