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 = {}