From af53d02920aa656c637d0a1073deb323f05e2ca7 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Fri, 28 Aug 2026 01:37:13 -0700 Subject: [PATCH] fix(bot-mode): backfill follow-profile contract for legacy canonical Bot Chats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bot Chats created before the follow_profile_config marker existed carry no contract in model_config, so they would stay pinned to a stale stored provider until deleted — the exact shape of the live reports (#89497, #94818). Mirror the plugin's own identity rule (the profile's session titled exactly 'Bot Chat') as a legacy fallback in _stored_session_runtime_overrides, matching the room-plumbing legacy 'Group:' title fallback. Follow-up to the salvaged #90343 (@curator8888) and #96111 (@lorzl). --- contributors/emails/david@davidtyler.rocks | 1 + ...est_custom_provider_session_persistence.py | 37 +++++++++++++++++++ tui_gateway/server.py | 10 +++++ 3 files changed, 48 insertions(+) create mode 100644 contributors/emails/david@davidtyler.rocks diff --git a/contributors/emails/david@davidtyler.rocks b/contributors/emails/david@davidtyler.rocks new file mode 100644 index 0000000000..36a1e08e10 --- /dev/null +++ b/contributors/emails/david@davidtyler.rocks @@ -0,0 +1 @@ +curator8888 diff --git a/tests/tui_gateway/test_custom_provider_session_persistence.py b/tests/tui_gateway/test_custom_provider_session_persistence.py index b58aaff1af..d67977c835 100644 --- a/tests/tui_gateway/test_custom_provider_session_persistence.py +++ b/tests/tui_gateway/test_custom_provider_session_persistence.py @@ -621,6 +621,43 @@ class TestFollowProfileConfigRuntimeOverrides: assert overrides["model_override"]["model"] == "openai/gpt-5.6-luna-pro" assert overrides["model_override"]["provider"] == "nous" + def test_legacy_bot_chat_title_backfills_contract(self): + """Canonical Bot Chats created BEFORE the marker existed carry no + follow_profile_config, but they are still the plugin-owned forever-DM + (identified by the exact title "Bot Chat"). They must also rebuild + from the profile's CURRENT config — the live-report shape where every + pre-existing Bot Chat stayed pinned to a deleted provider.""" + from tui_gateway.server import _stored_session_runtime_overrides + + for hidden in (0, 1): + row = { + "title": "Bot Chat", + "hidden": hidden, + "model": "openai/gpt-5.6-luna-pro", + "billing_provider": "nous", + "model_config": json.dumps( + {"model": "openai/gpt-5.6-luna-pro", "provider": "nous"} + ), + } + assert _stored_session_runtime_overrides(row) == {} + + def test_bot_chat_prefix_title_is_not_backfilled(self): + """Only the EXACT canonical title matches the legacy backfill — a + user chat that merely mentions bots keeps its stored runtime.""" + from tui_gateway.server import _stored_session_runtime_overrides + + row = { + "title": "Bot Chat ideas for my app", + "hidden": 0, + "model": "glm-5.1", + "billing_provider": "ollama-cloud", + "model_config": json.dumps( + {"model": "glm-5.1", "provider": "ollama-cloud"} + ), + } + overrides = _stored_session_runtime_overrides(row) + assert overrides["model_override"]["model"] == "glm-5.1" + def test_ensure_db_row_persists_contract_marker(self, monkeypatch): """_ensure_session_db_row stamps follow_profile_config into the row's model_config when the session carries the contract.""" diff --git a/tui_gateway/server.py b/tui_gateway/server.py index d957b8866e..413a794799 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -5318,6 +5318,16 @@ def _stored_session_runtime_overrides(row: dict | None) -> dict: _follow_marker = None if _follow_marker: return {} + # Legacy backfill: canonical Bot Chats created BEFORE the + # follow_profile_config contract existed carry no marker, yet they are + # still the plugin-owned forever-DM. The plugin's own identity rule is + # "the profile's session titled exactly 'Bot Chat'" (UNIQUE(title) makes + # that an exact registry, and pre-policy rows may be visible OR hidden), + # so mirror that rule here. Without this, every Bot Chat that already + # exists in the field stays pinned to its stale stored provider until + # the user deletes it — the exact live-report shape (#89497 / #94818). + if _row_title == "Bot Chat": + return {} raw_config = row.get("model_config") model_config: dict = {}