mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-25 05:52:34 +00:00
fix(discord): typing indicator task not cleaned up after API error
When the Discord typing API call fails (rate limit, network error, 403), _typing_loop returns early but the stale task remains in _typing_tasks. Subsequent send_typing calls see the stale entry and skip, leaving no typing indicator for the rest of the agent invocation. Add finally block to _typing_loop to always remove the task from _typing_tasks on exit, whether from cancellation, error, or normal completion. This allows send_typing to create a fresh task. 3 new tests in test_discord_send.py: - Task removed after API error - Typing restartable after failure - stop_typing cleans up
This commit is contained in:
parent
0458d99f22
commit
ace1c4ea8c
2 changed files with 61 additions and 0 deletions
|
|
@ -2697,6 +2697,8 @@ class DiscordAdapter(BasePlatformAdapter):
|
||||||
await asyncio.sleep(8)
|
await asyncio.sleep(8)
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
pass
|
pass
|
||||||
|
finally:
|
||||||
|
self._typing_tasks.pop(chat_id, None)
|
||||||
|
|
||||||
self._typing_tasks[chat_id] = asyncio.create_task(_typing_loop())
|
self._typing_tasks[chat_id] = asyncio.create_task(_typing_loop())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import asyncio
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from unittest.mock import AsyncMock, MagicMock
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -386,3 +387,61 @@ async def test_forum_post_file_creation_failure():
|
||||||
|
|
||||||
assert result.success is False
|
assert result.success is False
|
||||||
assert "missing perms" in (result.error or "")
|
assert "missing perms" in (result.error or "")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Typing indicator task lifecycle
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_typing_task_removed_after_api_error():
|
||||||
|
"""When typing API call fails, stale task must be removed so typing can restart."""
|
||||||
|
adapter = DiscordAdapter(PlatformConfig(enabled=True, token="***"))
|
||||||
|
adapter._client = MagicMock()
|
||||||
|
adapter._client.http = MagicMock()
|
||||||
|
adapter._client.http.request = AsyncMock(side_effect=Exception("rate limited"))
|
||||||
|
adapter._typing_tasks = {}
|
||||||
|
|
||||||
|
await adapter.send_typing("12345")
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
assert "12345" not in adapter._typing_tasks, \
|
||||||
|
"Stale task should be removed after API error"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_typing_restartable_after_error():
|
||||||
|
"""After a typing error, send_typing should start a new task (not blocked by stale entry)."""
|
||||||
|
adapter = DiscordAdapter(PlatformConfig(enabled=True, token="***"))
|
||||||
|
adapter._client = MagicMock()
|
||||||
|
adapter._client.http = MagicMock()
|
||||||
|
adapter._typing_tasks = {}
|
||||||
|
|
||||||
|
# First call fails
|
||||||
|
adapter._client.http.request = AsyncMock(side_effect=Exception("503"))
|
||||||
|
await adapter.send_typing("12345")
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
# Second call should work
|
||||||
|
adapter._client.http.request = AsyncMock()
|
||||||
|
await adapter.send_typing("12345")
|
||||||
|
|
||||||
|
assert "12345" in adapter._typing_tasks, \
|
||||||
|
"Should restart typing after previous failure"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_typing_stop_cleans_up():
|
||||||
|
"""stop_typing should remove the task from _typing_tasks."""
|
||||||
|
adapter = DiscordAdapter(PlatformConfig(enabled=True, token="***"))
|
||||||
|
adapter._client = MagicMock()
|
||||||
|
adapter._client.http = MagicMock()
|
||||||
|
adapter._client.http.request = AsyncMock()
|
||||||
|
adapter._typing_tasks = {}
|
||||||
|
|
||||||
|
await adapter.send_typing("12345")
|
||||||
|
assert "12345" in adapter._typing_tasks
|
||||||
|
|
||||||
|
await adapter.stop_typing("12345")
|
||||||
|
assert "12345" not in adapter._typing_tasks
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue