mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-24 16:54:43 +00:00
fix(slack): honor disable_dms setting
This commit is contained in:
parent
f8f5ce7da5
commit
ee62aab1a7
3 changed files with 92 additions and 4 deletions
|
|
@ -5423,6 +5423,13 @@ class SlackAdapter(BasePlatformAdapter):
|
|||
if not channel_type and channel_id.startswith("D"):
|
||||
channel_type = "im"
|
||||
is_dm = channel_type in {"im", "mpim"} # Both 1:1 and group DMs
|
||||
if is_dm and self._slack_disable_dms():
|
||||
logger.info(
|
||||
"[Slack] Ignoring DM because Slack DMs are disabled: channel=%s user=%s",
|
||||
channel_id,
|
||||
user_id,
|
||||
)
|
||||
return
|
||||
# A 1:1 IM is a private conversation with a single human — mention-exempt
|
||||
# and safe to react to unconditionally, like any DM. An MPIM (group DM)
|
||||
# is a SHARED surface: multiple humans can see and trigger the bot, so it
|
||||
|
|
@ -7627,6 +7634,13 @@ class SlackAdapter(BasePlatformAdapter):
|
|||
if thread_id:
|
||||
break
|
||||
is_dm = str(channel_id).startswith("D")
|
||||
if is_dm and self._slack_disable_dms():
|
||||
logger.info(
|
||||
"[Slack] Ignoring slash command from DM because Slack DMs are disabled: channel=%s user=%s",
|
||||
channel_id,
|
||||
user_id,
|
||||
)
|
||||
return
|
||||
source = self.build_source(
|
||||
chat_id=channel_id,
|
||||
chat_type="dm" if is_dm else "group",
|
||||
|
|
@ -8265,12 +8279,27 @@ class SlackAdapter(BasePlatformAdapter):
|
|||
return {part.strip() for part in s.split(",") if part.strip()}
|
||||
return set()
|
||||
|
||||
def _slack_disable_dms(self) -> bool:
|
||||
"""Return whether incoming Slack DMs should be ignored.
|
||||
|
||||
Supports both profile config (``slack.disable_dms`` bridged into
|
||||
``PlatformConfig.extra``) and the environment override
|
||||
``SLACK_DISABLE_DMS``. Defaults to False for backward compatibility.
|
||||
"""
|
||||
raw = self.config.extra.get("disable_dms")
|
||||
if raw is None:
|
||||
raw = os.getenv("SLACK_DISABLE_DMS", "false")
|
||||
if isinstance(raw, str):
|
||||
return raw.strip().lower() in {"true", "1", "yes", "on"}
|
||||
return bool(raw)
|
||||
|
||||
def _slack_allowed_channels(self) -> set:
|
||||
"""Return the whitelist of channel IDs the bot will respond in.
|
||||
|
||||
When non-empty, messages from channels NOT in this set are silently
|
||||
ignored — even if the bot is @mentioned. DMs are never filtered.
|
||||
Empty set means no restriction (fully backward compatible).
|
||||
ignored — even if the bot is @mentioned. DMs are controlled separately
|
||||
by ``_slack_disable_dms()``. Empty set means no channel restriction
|
||||
(fully backward compatible).
|
||||
"""
|
||||
raw = self.config.extra.get("allowed_channels")
|
||||
if raw is None:
|
||||
|
|
@ -8859,6 +8888,9 @@ def _apply_yaml_config(yaml_cfg: dict, slack_cfg: dict) -> dict | None:
|
|||
rtt = slack_cfg.get("reaction_trigger_target")
|
||||
if rtt is not None and not os.getenv("SLACK_REACTION_TRIGGER_TARGET"):
|
||||
os.environ["SLACK_REACTION_TRIGGER_TARGET"] = str(rtt)
|
||||
|
||||
if "disable_dms" in slack_cfg and not os.getenv("SLACK_DISABLE_DMS"):
|
||||
os.environ["SLACK_DISABLE_DMS"] = str(slack_cfg["disable_dms"]).lower()
|
||||
ac = slack_cfg.get("allowed_channels")
|
||||
if ac is not None and not os.getenv("SLACK_ALLOWED_CHANNELS"):
|
||||
if isinstance(ac, list):
|
||||
|
|
@ -8901,8 +8933,8 @@ def register(ctx) -> None:
|
|||
# YAML→env config bridge — owns the translation of config.yaml slack:
|
||||
# keys (require_mention, strict_mention, ignore_other_user_mentions,
|
||||
# thread_require_mention, allow_bots, free_response_channels,
|
||||
# reactions, allowed_channels) into SLACK_* env vars that the adapter
|
||||
# reads via os.getenv(). Replaces the
|
||||
# reactions, disable_dms, allowed_channels) into SLACK_* env vars that
|
||||
# the adapter reads via os.getenv(). Replaces the
|
||||
# hardcoded block in gateway/config.py. Hook contract: #24849.
|
||||
apply_yaml_config_fn=_apply_yaml_config,
|
||||
# Auth env vars for _is_user_authorized() integration
|
||||
|
|
|
|||
|
|
@ -575,6 +575,23 @@ class TestLoadGatewayConfig:
|
|||
|
||||
assert config.quick_commands == {"limits": {"type": "exec", "command": "echo ok"}}
|
||||
|
||||
def test_slack_disable_dms_config_sets_env_bridge(self, tmp_path, monkeypatch):
|
||||
hermes_home = tmp_path / ".hermes"
|
||||
hermes_home.mkdir()
|
||||
config_path = hermes_home / "config.yaml"
|
||||
config_path.write_text(
|
||||
"slack:\n"
|
||||
" disable_dms: true\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
monkeypatch.delenv("SLACK_DISABLE_DMS", raising=False)
|
||||
|
||||
load_gateway_config()
|
||||
|
||||
assert os.getenv("SLACK_DISABLE_DMS") == "true"
|
||||
|
||||
def test_typing_status_text_from_toplevel_platform_block(self, tmp_path, monkeypatch):
|
||||
"""A top-level ``slack:`` block reaches PlatformConfig via the
|
||||
shared-key bridge (bridged into extra, then the from_dict extra
|
||||
|
|
|
|||
|
|
@ -366,6 +366,20 @@ class TestSlashCommandSessionIsolation:
|
|||
assert event.source.user_id == "U123"
|
||||
assert event.source.thread_id == "1700000000.123456"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_disable_dms_drops_dm_slash_command(self, adapter):
|
||||
adapter.config.extra["disable_dms"] = True
|
||||
command = {
|
||||
"text": "hello",
|
||||
"user_id": "U123",
|
||||
"channel_id": "D123",
|
||||
"team_id": "T123",
|
||||
}
|
||||
|
||||
await adapter._handle_slash_command(command)
|
||||
|
||||
adapter.handle_message.assert_not_awaited()
|
||||
|
||||
|
||||
class TestSlackWorkspaceCollisionIsolation:
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -2385,6 +2399,31 @@ class TestBangPrefixCommands:
|
|||
assert "quoted context" not in msg_event.text
|
||||
assert msg_event.message_type == MessageType.COMMAND
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_disable_dms_drops_text_dm(self, adapter):
|
||||
adapter.config.extra["disable_dms"] = True
|
||||
|
||||
await adapter._handle_slack_message(self._make_event("hello from DM"))
|
||||
|
||||
adapter.handle_message.assert_not_awaited()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_disable_dms_does_not_drop_channel_mentions(self, adapter):
|
||||
adapter.config.extra["disable_dms"] = True
|
||||
|
||||
await adapter._handle_slack_message(
|
||||
self._make_event(
|
||||
"<@U_BOT> hello from channel",
|
||||
channel_type="channel",
|
||||
channel="C123",
|
||||
)
|
||||
)
|
||||
|
||||
adapter.handle_message.assert_awaited_once()
|
||||
msg_event = adapter.handle_message.await_args.args[0]
|
||||
assert msg_event.source.chat_type == "group"
|
||||
assert msg_event.source.chat_id == "C123"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TestIncomingDocumentHandling
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue