mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-26 06:01:49 +00:00
fix(gateway): keep QQBot reconnect loop alive
This commit is contained in:
parent
f0e46c5e9e
commit
8199ec3803
2 changed files with 30 additions and 2 deletions
|
|
@ -176,6 +176,28 @@ class QQAdapter(BasePlatformAdapter):
|
||||||
fut.set_exception(RuntimeError(reason))
|
fut.set_exception(RuntimeError(reason))
|
||||||
self._pending_responses.clear()
|
self._pending_responses.clear()
|
||||||
|
|
||||||
|
def _mark_transport_disconnected(self) -> None:
|
||||||
|
"""Mark QQ WS down without stopping the reconnect loop.
|
||||||
|
|
||||||
|
BasePlatformAdapter uses _running for both process lifecycle and
|
||||||
|
connection status. QQBot needs to keep the listener task alive across
|
||||||
|
transient transport drops so it can continue reconnect attempts after a
|
||||||
|
short-lived gateway or network failure.
|
||||||
|
"""
|
||||||
|
if self.has_fatal_error:
|
||||||
|
return
|
||||||
|
self._write_runtime_status_safe(
|
||||||
|
"disconnected",
|
||||||
|
platform_state="disconnected",
|
||||||
|
error_code=None,
|
||||||
|
error_message=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_connected(self) -> bool:
|
||||||
|
"""Return True only when the QQ WebSocket transport is usable."""
|
||||||
|
return bool(self._running and self._ws and not self._ws.closed)
|
||||||
|
|
||||||
def __init__(self, config: PlatformConfig):
|
def __init__(self, config: PlatformConfig):
|
||||||
super().__init__(config, Platform.QQBOT)
|
super().__init__(config, Platform.QQBOT)
|
||||||
|
|
||||||
|
|
@ -509,7 +531,7 @@ class QQAdapter(BasePlatformAdapter):
|
||||||
else:
|
else:
|
||||||
quick_disconnect_count = 0
|
quick_disconnect_count = 0
|
||||||
|
|
||||||
self._mark_disconnected()
|
self._mark_transport_disconnected()
|
||||||
self._fail_pending("Connection closed")
|
self._fail_pending("Connection closed")
|
||||||
|
|
||||||
# Stop reconnecting for fatal codes
|
# Stop reconnecting for fatal codes
|
||||||
|
|
@ -531,6 +553,7 @@ class QQAdapter(BasePlatformAdapter):
|
||||||
RATE_LIMIT_DELAY,
|
RATE_LIMIT_DELAY,
|
||||||
)
|
)
|
||||||
if backoff_idx >= MAX_RECONNECT_ATTEMPTS:
|
if backoff_idx >= MAX_RECONNECT_ATTEMPTS:
|
||||||
|
self._mark_disconnected()
|
||||||
return
|
return
|
||||||
await asyncio.sleep(RATE_LIMIT_DELAY)
|
await asyncio.sleep(RATE_LIMIT_DELAY)
|
||||||
if await self._reconnect(backoff_idx):
|
if await self._reconnect(backoff_idx):
|
||||||
|
|
@ -584,17 +607,19 @@ class QQAdapter(BasePlatformAdapter):
|
||||||
backoff_idx += 1
|
backoff_idx += 1
|
||||||
if backoff_idx >= MAX_RECONNECT_ATTEMPTS:
|
if backoff_idx >= MAX_RECONNECT_ATTEMPTS:
|
||||||
logger.error("[%s] Max reconnect attempts reached (QQCloseError)", self._log_tag)
|
logger.error("[%s] Max reconnect attempts reached (QQCloseError)", self._log_tag)
|
||||||
|
self._mark_disconnected()
|
||||||
return
|
return
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if not self._running:
|
if not self._running:
|
||||||
return
|
return
|
||||||
logger.warning("[%s] WebSocket error: %s", self._log_tag, exc)
|
logger.warning("[%s] WebSocket error: %s", self._log_tag, exc)
|
||||||
self._mark_disconnected()
|
self._mark_transport_disconnected()
|
||||||
self._fail_pending("Connection interrupted")
|
self._fail_pending("Connection interrupted")
|
||||||
|
|
||||||
if backoff_idx >= MAX_RECONNECT_ATTEMPTS:
|
if backoff_idx >= MAX_RECONNECT_ATTEMPTS:
|
||||||
logger.error("[%s] Max reconnect attempts reached", self._log_tag)
|
logger.error("[%s] Max reconnect attempts reached", self._log_tag)
|
||||||
|
self._mark_disconnected()
|
||||||
return
|
return
|
||||||
|
|
||||||
if await self._reconnect(backoff_idx):
|
if await self._reconnect(backoff_idx):
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import asyncio
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from types import SimpleNamespace
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -578,6 +579,7 @@ class TestWaitForReconnection:
|
||||||
async def reconnect_after_delay():
|
async def reconnect_after_delay():
|
||||||
await asyncio.sleep(0.3)
|
await asyncio.sleep(0.3)
|
||||||
adapter._running = True
|
adapter._running = True
|
||||||
|
adapter._ws = SimpleNamespace(closed=False)
|
||||||
|
|
||||||
asyncio.get_event_loop().create_task(reconnect_after_delay())
|
asyncio.get_event_loop().create_task(reconnect_after_delay())
|
||||||
|
|
||||||
|
|
@ -603,6 +605,7 @@ class TestWaitForReconnection:
|
||||||
"""send() should not wait when already connected."""
|
"""send() should not wait when already connected."""
|
||||||
adapter = self._make_adapter(app_id="a", client_secret="b")
|
adapter = self._make_adapter(app_id="a", client_secret="b")
|
||||||
adapter._running = True
|
adapter._running = True
|
||||||
|
adapter._ws = SimpleNamespace(closed=False)
|
||||||
adapter._http_client = mock.MagicMock()
|
adapter._http_client = mock.MagicMock()
|
||||||
|
|
||||||
async def fake_api_request(*args, **kwargs):
|
async def fake_api_request(*args, **kwargs):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue