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, }