From e0ccc401c0c3a5189fd5d2c64f63cc6855e6c75a Mon Sep 17 00:00:00 2001 From: chengyongru Date: Thu, 9 Apr 2026 13:47:53 +0800 Subject: [PATCH] fix(websocket): handle ConnectionClosed gracefully in send and send_delta --- nanobot/channels/websocket.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nanobot/channels/websocket.py b/nanobot/channels/websocket.py index e09e6303e..1660cbe7e 100644 --- a/nanobot/channels/websocket.py +++ b/nanobot/channels/websocket.py @@ -18,6 +18,7 @@ from loguru import logger from pydantic import Field, field_validator, model_validator from websockets.asyncio.server import ServerConnection, serve from websockets.datastructures import Headers +from websockets.exceptions import ConnectionClosed from websockets.http11 import Request as WsRequest, Response from nanobot.bus.events import OutboundMessage @@ -52,6 +53,8 @@ class WebSocketConfig(Base): ``X-Nanobot-Auth: ``. - ``websocket_requires_token``: If True, the handshake must include a valid token (static or issued and not expired). - Each connection has its own session: a unique ``chat_id`` maps to the agent session internally. + - ``media`` field in outbound messages contains local filesystem paths; remote clients need a + shared filesystem or an HTTP file server to access these files. """ enabled: bool = False @@ -385,6 +388,9 @@ class WebSocketChannel(BaseChannel): raw = json.dumps(payload, ensure_ascii=False) try: await connection.send(raw) + except ConnectionClosed: + self._connections.pop(msg.chat_id, None) + logger.warning("websocket: connection gone for chat_id={}", msg.chat_id) except Exception as e: logger.error("websocket send failed: {}", e) raise @@ -413,6 +419,9 @@ class WebSocketChannel(BaseChannel): raw = json.dumps(body, ensure_ascii=False) try: await connection.send(raw) + except ConnectionClosed: + self._connections.pop(chat_id, None) + logger.warning("websocket: stream connection gone for chat_id={}", chat_id) except Exception as e: logger.error("websocket stream send failed: {}", e) raise