mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix(discord): widen expired-defer handling to /thread slash command
Same 10062 degrade-gracefully pattern as _run_simple_slash: create the thread anyway, skip the ephemeral followups that need a live interaction token. Non-expiry defer errors still raise.
This commit is contained in:
parent
b9d9b8aad6
commit
4c3a388cba
2 changed files with 87 additions and 3 deletions
|
|
@ -4600,7 +4600,17 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||
"""Create a Discord thread from a slash command and start a session in it."""
|
||||
if not await self._check_slash_authorization(interaction, "/thread"):
|
||||
return
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
deferred_response = False
|
||||
try:
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
deferred_response = True
|
||||
except Exception as e:
|
||||
if not self._is_discord_unknown_interaction(e):
|
||||
raise
|
||||
logger.warning(
|
||||
"[Discord] /thread: interaction expired before defer. "
|
||||
"Creating the thread anyway, skipping interaction followups.",
|
||||
)
|
||||
result = await self._create_thread(
|
||||
interaction,
|
||||
name=name,
|
||||
|
|
@ -4610,7 +4620,8 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||
|
||||
if not result.get("success"):
|
||||
error = result.get("error", "unknown error")
|
||||
await interaction.followup.send(f"Failed to create thread: {error}", ephemeral=True)
|
||||
if deferred_response:
|
||||
await interaction.followup.send(f"Failed to create thread: {error}", ephemeral=True)
|
||||
return
|
||||
|
||||
thread_id = result.get("thread_id")
|
||||
|
|
@ -4618,7 +4629,8 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||
|
||||
# Tell the user where the thread is
|
||||
link = f"<#{thread_id}>" if thread_id else f"**{thread_name}**"
|
||||
await interaction.followup.send(f"Created thread {link}", ephemeral=True)
|
||||
if deferred_response:
|
||||
await interaction.followup.send(f"Created thread {link}", ephemeral=True)
|
||||
|
||||
# Track thread participation so follow-ups don't require @mention
|
||||
if thread_id:
|
||||
|
|
|
|||
72
tests/gateway/test_discord_thread_slash_expired_defer.py
Normal file
72
tests/gateway/test_discord_thread_slash_expired_defer.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
"""Sibling coverage for expired slash defer handling: /thread creation must
|
||||
still run when the interaction token has expired (Discord error 10062), just
|
||||
skipping the ephemeral followups."""
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
|
||||
from gateway.config import PlatformConfig
|
||||
from plugins.platforms.discord.adapter import DiscordAdapter
|
||||
|
||||
|
||||
class _UnknownInteraction(Exception):
|
||||
status = 404
|
||||
code = 10062
|
||||
|
||||
|
||||
def _adapter():
|
||||
a = DiscordAdapter(PlatformConfig(enabled=True, token="***"))
|
||||
a._check_slash_authorization = AsyncMock(return_value=True)
|
||||
return a
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_thread_create_slash_survives_expired_defer():
|
||||
adapter = _adapter()
|
||||
interaction = SimpleNamespace(
|
||||
response=SimpleNamespace(defer=AsyncMock(side_effect=_UnknownInteraction("Unknown interaction"))),
|
||||
followup=SimpleNamespace(send=AsyncMock()),
|
||||
)
|
||||
adapter._create_thread = AsyncMock(
|
||||
return_value={"success": True, "thread_id": "999", "thread_name": "t"}
|
||||
)
|
||||
adapter._threads = SimpleNamespace(mark=lambda _tid: None)
|
||||
|
||||
await adapter._handle_thread_create_slash(interaction, name="t")
|
||||
|
||||
adapter._create_thread.assert_awaited_once()
|
||||
interaction.followup.send.assert_not_awaited()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_thread_create_slash_normal_defer_still_follows_up():
|
||||
adapter = _adapter()
|
||||
interaction = SimpleNamespace(
|
||||
response=SimpleNamespace(defer=AsyncMock()),
|
||||
followup=SimpleNamespace(send=AsyncMock()),
|
||||
)
|
||||
adapter._create_thread = AsyncMock(
|
||||
return_value={"success": True, "thread_id": "999", "thread_name": "t"}
|
||||
)
|
||||
adapter._threads = SimpleNamespace(mark=lambda _tid: None)
|
||||
|
||||
await adapter._handle_thread_create_slash(interaction, name="t")
|
||||
|
||||
interaction.followup.send.assert_awaited()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_thread_create_slash_reraises_non_expiry_errors():
|
||||
adapter = _adapter()
|
||||
interaction = SimpleNamespace(
|
||||
response=SimpleNamespace(defer=AsyncMock(side_effect=RuntimeError("boom"))),
|
||||
followup=SimpleNamespace(send=AsyncMock()),
|
||||
)
|
||||
adapter._create_thread = AsyncMock()
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
await adapter._handle_thread_create_slash(interaction, name="t")
|
||||
|
||||
adapter._create_thread.assert_not_awaited()
|
||||
Loading…
Add table
Add a link
Reference in a new issue