From a1da384c6d968000773ba0d1617d6931dfe25748 Mon Sep 17 00:00:00 2001 From: PRATHAMESH75 Date: Tue, 21 Jul 2026 10:14:26 +0530 Subject: [PATCH] fix(gateway): carry desktop_contract when activating a lazy session (#68392) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _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. --- tests/test_tui_gateway_server.py | 35 ++++++++++++++++++++++++++++++++ tui_gateway/server.py | 6 ++++++ 2 files changed, 41 insertions(+) diff --git a/tests/test_tui_gateway_server.py b/tests/test_tui_gateway_server.py index 572c2c8710e47..43341730059f8 100644 --- a/tests/test_tui_gateway_server.py +++ b/tests/test_tui_gateway_server.py @@ -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") diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 28c3e091570e9..69c7122783ff2 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -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, }