mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix(discord): skip auto-threading reply messages
This commit is contained in:
parent
93fe4b357d
commit
f5dc4e905d
2 changed files with 66 additions and 5 deletions
|
|
@ -2285,6 +2285,26 @@ class DiscordAdapter(BasePlatformAdapter):
|
||||||
from gateway.platforms.base import resolve_channel_prompt
|
from gateway.platforms.base import resolve_channel_prompt
|
||||||
return resolve_channel_prompt(self.config.extra, channel_id, parent_id)
|
return resolve_channel_prompt(self.config.extra, channel_id, parent_id)
|
||||||
|
|
||||||
|
def _discord_require_mention(self) -> bool:
|
||||||
|
"""Return whether Discord channel messages require a bot mention."""
|
||||||
|
configured = self.config.extra.get("require_mention")
|
||||||
|
if configured is not None:
|
||||||
|
if isinstance(configured, str):
|
||||||
|
return configured.lower() not in ("false", "0", "no", "off")
|
||||||
|
return bool(configured)
|
||||||
|
return os.getenv("DISCORD_REQUIRE_MENTION", "true").lower() not in ("false", "0", "no", "off")
|
||||||
|
|
||||||
|
def _discord_free_response_channels(self) -> set:
|
||||||
|
"""Return Discord channel IDs where no bot mention is required."""
|
||||||
|
raw = self.config.extra.get("free_response_channels")
|
||||||
|
if raw is None:
|
||||||
|
raw = os.getenv("DISCORD_FREE_RESPONSE_CHANNELS", "")
|
||||||
|
if isinstance(raw, list):
|
||||||
|
return {str(part).strip() for part in raw if str(part).strip()}
|
||||||
|
if isinstance(raw, str) and raw.strip():
|
||||||
|
return {part.strip() for part in raw.split(",") if part.strip()}
|
||||||
|
return set()
|
||||||
|
|
||||||
def _thread_parent_channel(self, channel: Any) -> Any:
|
def _thread_parent_channel(self, channel: Any) -> Any:
|
||||||
"""Return the parent text channel when invoked from a thread."""
|
"""Return the parent text channel when invoked from a thread."""
|
||||||
return getattr(channel, "parent", None) or channel
|
return getattr(channel, "parent", None) or channel
|
||||||
|
|
@ -2745,12 +2765,11 @@ class DiscordAdapter(BasePlatformAdapter):
|
||||||
logger.debug("[%s] Ignoring message in ignored channel: %s", self.name, channel_ids)
|
logger.debug("[%s] Ignoring message in ignored channel: %s", self.name, channel_ids)
|
||||||
return
|
return
|
||||||
|
|
||||||
free_channels_raw = os.getenv("DISCORD_FREE_RESPONSE_CHANNELS", "")
|
free_channels = self._discord_free_response_channels()
|
||||||
free_channels = {ch.strip() for ch in free_channels_raw.split(",") if ch.strip()}
|
|
||||||
if parent_channel_id:
|
if parent_channel_id:
|
||||||
channel_ids.add(parent_channel_id)
|
channel_ids.add(parent_channel_id)
|
||||||
|
|
||||||
require_mention = os.getenv("DISCORD_REQUIRE_MENTION", "true").lower() not in ("false", "0", "no")
|
require_mention = self._discord_require_mention()
|
||||||
# Voice-linked text channels act as free-response while voice is active.
|
# Voice-linked text channels act as free-response while voice is active.
|
||||||
# Only the exact bound channel gets the exemption, not sibling threads.
|
# Only the exact bound channel gets the exemption, not sibling threads.
|
||||||
voice_linked_ids = {str(ch_id) for ch_id in self._voice_text_channels.values()}
|
voice_linked_ids = {str(ch_id) for ch_id in self._voice_text_channels.values()}
|
||||||
|
|
@ -2780,7 +2799,8 @@ class DiscordAdapter(BasePlatformAdapter):
|
||||||
no_thread_channels = {ch.strip() for ch in no_thread_channels_raw.split(",") if ch.strip()}
|
no_thread_channels = {ch.strip() for ch in no_thread_channels_raw.split(",") if ch.strip()}
|
||||||
skip_thread = bool(channel_ids & no_thread_channels) or is_free_channel
|
skip_thread = bool(channel_ids & no_thread_channels) or is_free_channel
|
||||||
auto_thread = os.getenv("DISCORD_AUTO_THREAD", "true").lower() in ("true", "1", "yes")
|
auto_thread = os.getenv("DISCORD_AUTO_THREAD", "true").lower() in ("true", "1", "yes")
|
||||||
if auto_thread and not skip_thread and not is_voice_linked_channel:
|
is_reply_message = getattr(message, "type", None) == discord.MessageType.reply
|
||||||
|
if auto_thread and not skip_thread and not is_voice_linked_channel and not is_reply_message:
|
||||||
thread = await self._auto_create_thread(message)
|
thread = await self._auto_create_thread(message)
|
||||||
if thread:
|
if thread:
|
||||||
is_thread = True
|
is_thread = True
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ def adapter(monkeypatch):
|
||||||
return adapter
|
return adapter
|
||||||
|
|
||||||
|
|
||||||
def make_message(*, channel, content: str, mentions=None):
|
def make_message(*, channel, content: str, mentions=None, msg_type=None):
|
||||||
author = SimpleNamespace(id=42, display_name="Jezza", name="Jezza")
|
author = SimpleNamespace(id=42, display_name="Jezza", name="Jezza")
|
||||||
return SimpleNamespace(
|
return SimpleNamespace(
|
||||||
id=123,
|
id=123,
|
||||||
|
|
@ -107,6 +107,7 @@ def make_message(*, channel, content: str, mentions=None):
|
||||||
created_at=datetime.now(timezone.utc),
|
created_at=datetime.now(timezone.utc),
|
||||||
channel=channel,
|
channel=channel,
|
||||||
author=author,
|
author=author,
|
||||||
|
type=msg_type if msg_type is not None else discord_platform.discord.MessageType.default,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -204,6 +205,21 @@ async def test_discord_free_response_channel_overrides_mention_requirement(adapt
|
||||||
assert event.text == "allowed without mention"
|
assert event.text == "allowed without mention"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_discord_free_response_channel_can_come_from_config_extra(adapter, monkeypatch):
|
||||||
|
monkeypatch.delenv("DISCORD_REQUIRE_MENTION", raising=False)
|
||||||
|
monkeypatch.delenv("DISCORD_FREE_RESPONSE_CHANNELS", raising=False)
|
||||||
|
adapter.config.extra["free_response_channels"] = ["789", "999"]
|
||||||
|
|
||||||
|
message = make_message(channel=FakeTextChannel(channel_id=789), content="allowed from config")
|
||||||
|
|
||||||
|
await adapter._handle_message(message)
|
||||||
|
|
||||||
|
adapter.handle_message.assert_awaited_once()
|
||||||
|
event = adapter.handle_message.await_args.args[0]
|
||||||
|
assert event.text == "allowed from config"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_discord_forum_parent_in_free_response_list_allows_forum_thread(adapter, monkeypatch):
|
async def test_discord_forum_parent_in_free_response_list_allows_forum_thread(adapter, monkeypatch):
|
||||||
monkeypatch.setenv("DISCORD_REQUIRE_MENTION", "true")
|
monkeypatch.setenv("DISCORD_REQUIRE_MENTION", "true")
|
||||||
|
|
@ -276,6 +292,31 @@ async def test_discord_auto_thread_enabled_by_default(adapter, monkeypatch):
|
||||||
assert event.source.thread_id == "999"
|
assert event.source.thread_id == "999"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_discord_reply_message_skips_auto_thread(adapter, monkeypatch):
|
||||||
|
"""Quote-replies should stay in-channel instead of trying to create a thread."""
|
||||||
|
monkeypatch.delenv("DISCORD_AUTO_THREAD", raising=False)
|
||||||
|
monkeypatch.setenv("DISCORD_REQUIRE_MENTION", "true")
|
||||||
|
monkeypatch.setenv("DISCORD_FREE_RESPONSE_CHANNELS", "123")
|
||||||
|
|
||||||
|
adapter._auto_create_thread = AsyncMock()
|
||||||
|
|
||||||
|
message = make_message(
|
||||||
|
channel=FakeTextChannel(channel_id=123),
|
||||||
|
content="reply without mention",
|
||||||
|
msg_type=discord_platform.discord.MessageType.reply,
|
||||||
|
)
|
||||||
|
|
||||||
|
await adapter._handle_message(message)
|
||||||
|
|
||||||
|
adapter._auto_create_thread.assert_not_awaited()
|
||||||
|
adapter.handle_message.assert_awaited_once()
|
||||||
|
event = adapter.handle_message.await_args.args[0]
|
||||||
|
assert event.text == "reply without mention"
|
||||||
|
assert event.source.chat_id == "123"
|
||||||
|
assert event.source.chat_type == "group"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_discord_auto_thread_can_be_disabled(adapter, monkeypatch):
|
async def test_discord_auto_thread_can_be_disabled(adapter, monkeypatch):
|
||||||
"""Setting auto_thread to false skips thread creation."""
|
"""Setting auto_thread to false skips thread creation."""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue