diff --git a/src/dashboard/app.py b/src/dashboard/app.py index a705c18..cc2d3de 100644 --- a/src/dashboard/app.py +++ b/src/dashboard/app.py @@ -410,11 +410,17 @@ app.include_router(cascade_router) @app.websocket("/ws") async def ws_redirect(websocket: WebSocket): - """Catch stale /ws connections and close cleanly.""" + """Catch stale /ws connections and close cleanly. + + websockets 16.0 dropped the legacy ``transfer_data_task`` attribute, + so calling ``websocket.close()`` after accept triggers an + AttributeError. Use the raw ASGI send instead. + """ await websocket.accept() try: await websocket.close(code=1008, reason="Deprecated endpoint") except AttributeError: + # websockets >= 16.0 — close via raw ASGI message await websocket.send({"type": "websocket.close", "code": 1008}) diff --git a/src/integrations/chat_bridge/vendors/discord.py b/src/integrations/chat_bridge/vendors/discord.py index a36c678..38e1b2e 100644 --- a/src/integrations/chat_bridge/vendors/discord.py +++ b/src/integrations/chat_bridge/vendors/discord.py @@ -297,7 +297,21 @@ class DiscordVendor(ChatPlatform): logger.error("Failed to save Discord token: %s", exc) def load_token(self) -> str | None: - """Load token from state file or config.""" + """Load token from config or state file. + + Priority: settings.discord_token (env/.env) > state file. + The state file is a fallback for tokens set via the /discord/setup UI. + """ + # 1. Config / env var takes priority + try: + from config import settings + + if settings.discord_token: + return settings.discord_token + except Exception: + pass + + # 2. Fall back to state file (set via /discord/setup endpoint) try: if _STATE_FILE.exists(): data = json.loads(_STATE_FILE.read_text()) @@ -307,12 +321,7 @@ class DiscordVendor(ChatPlatform): except Exception as exc: logger.debug("Could not read discord state file: %s", exc) - try: - from config import settings - - return settings.discord_token or None - except Exception: - return None + return None # ── OAuth2 URL generation ────────────────────────────────────────────── diff --git a/tests/integrations/test_discord_vendor.py b/tests/integrations/test_discord_vendor.py index 15c8e6d..e248bb0 100644 --- a/tests/integrations/test_discord_vendor.py +++ b/tests/integrations/test_discord_vendor.py @@ -34,11 +34,14 @@ class TestDiscordVendor: assert status.guild_count == 0 def test_save_and_load_token(self, tmp_path, monkeypatch): + from config import settings from integrations.chat_bridge.vendors import discord as discord_mod from integrations.chat_bridge.vendors.discord import DiscordVendor state_file = tmp_path / "discord_state.json" monkeypatch.setattr(discord_mod, "_STATE_FILE", state_file) + # Settings token empty — so state file token is used as fallback + monkeypatch.setattr(settings, "discord_token", "") vendor = DiscordVendor() vendor.save_token("test-token-abc")