fix(gateway): carry desktop_contract when activating a lazy session (#68392)

_live_session_payload() falls back to _fallback_session_info() while a
session's agent is still None (lazy/deferred build). That fallback omitted
desktop_contract, so session.activate returned lazy metadata with no contract
field. Desktop feeds the value straight into reportBackendContract(), where a
missing field reads as contract 0 — a current backend is then falsely flagged
"Backend out of date" on every activate of a live lazy session.

The sibling session.create shape (_lazy_resume_info) was fixed the same way in
#36112; this closes the remaining session.activate gap by advertising
DESKTOP_BACKEND_CONTRACT in the fallback payload.

Adds test_session_activate_lazy_info_reports_desktop_contract pinning the
session.activate path against a lazy (agent=None) session.
This commit is contained in:
PRATHAMESH75 2026-07-21 10:14:26 +05:30 committed by Teknium
parent ad2c7af86a
commit a1da384c6d
2 changed files with 41 additions and 0 deletions

View File

@ -11270,6 +11270,41 @@ def test_session_create_lazy_info_reports_desktop_contract(monkeypatch):
server._sessions.pop(resp["result"]["session_id"], None)
def test_session_activate_lazy_info_reports_desktop_contract():
"""Activating an already-live *lazy* session (agent not built yet) must
still advertise desktop_contract. _live_session_payload falls back to
_fallback_session_info while session["agent"] is None; the desktop reads a
missing field as contract 0 and falsely warns "Backend out of date" against
a current backend (#68392). The sibling session.create path was fixed in
#36112; this pins the session.activate path."""
import threading
sid = "lazy-activate-contract"
server._sessions[sid] = {
"agent": None,
"created_at": 123.0,
"history": [],
"history_lock": threading.RLock(),
"last_active": 123.0,
"running": False,
"session_key": sid,
"transport": server._stdio_transport,
}
try:
resp = server.handle_request(
{
"id": "activate-lazy",
"method": "session.activate",
"params": {"session_id": sid},
}
)
info = resp["result"]["info"]
assert info["lazy"] is True
assert info["desktop_contract"] == server.DESKTOP_BACKEND_CONTRACT
finally:
server._sessions.pop(sid, None)
def test_session_list_returns_clean_error_when_state_db_is_unavailable(monkeypatch):
monkeypatch.setattr(server, "_get_db", lambda: None)
monkeypatch.setattr(server, "_db_error", "locking protocol")

View File

@ -8083,6 +8083,12 @@ def _fallback_session_info(session: dict) -> dict:
"model": _resolve_model(),
"skills": {},
"tools": {},
# A lazy session (agent not built yet) is still served by *this* backend,
# so it must advertise the current contract. Desktop feeds this straight
# into reportBackendContract(); a missing field is read as contract 0 and
# a current backend is falsely flagged "out of date" (#68392). The sibling
# session.create shape (_lazy_resume_info) already carries it (#36112).
"desktop_contract": DESKTOP_BACKEND_CONTRACT,
}