mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-13 09:01:54 +00:00
feat(slack): add opt-in slack.strict_mention gate for channel threads
Adds a strict_mention config option that, when enabled, requires an explicit @-mention on every message in channel threads. Disables the 'once mentioned, forever in the thread' and session-presence auto-triggers. - New _slack_strict_mention() helper (config.extra + SLACK_STRICT_MENTION env) - Bridged top-level slack.strict_mention yaml to SLACK_STRICT_MENTION env, matching require_mention/allow_bots bridging - Unit tests for the helper + config bridge
This commit is contained in:
parent
897dc3a2bb
commit
aea4a90f0e
3 changed files with 82 additions and 1 deletions
|
|
@ -611,6 +611,8 @@ def load_gateway_config() -> GatewayConfig:
|
|||
if isinstance(slack_cfg, dict):
|
||||
if "require_mention" in slack_cfg and not os.getenv("SLACK_REQUIRE_MENTION"):
|
||||
os.environ["SLACK_REQUIRE_MENTION"] = str(slack_cfg["require_mention"]).lower()
|
||||
if "strict_mention" in slack_cfg and not os.getenv("SLACK_STRICT_MENTION"):
|
||||
os.environ["SLACK_STRICT_MENTION"] = str(slack_cfg["strict_mention"]).lower()
|
||||
if "allow_bots" in slack_cfg and not os.getenv("SLACK_ALLOW_BOTS"):
|
||||
os.environ["SLACK_ALLOW_BOTS"] = str(slack_cfg["allow_bots"]).lower()
|
||||
frc = slack_cfg.get("free_response_channels")
|
||||
|
|
|
|||
|
|
@ -1133,6 +1133,8 @@ class SlackAdapter(BasePlatformAdapter):
|
|||
pass # Free-response channel — always process
|
||||
elif not self._slack_require_mention():
|
||||
pass # Mention requirement disabled globally for Slack
|
||||
elif self._slack_strict_mention() and not is_mentioned:
|
||||
return # Strict mode: ignore until @-mentioned again
|
||||
elif not is_mentioned:
|
||||
reply_to_bot_thread = (
|
||||
is_thread_reply and event_thread_ts in self._bot_message_ts
|
||||
|
|
@ -1783,6 +1785,18 @@ class SlackAdapter(BasePlatformAdapter):
|
|||
return bool(configured)
|
||||
return os.getenv("SLACK_REQUIRE_MENTION", "true").lower() not in ("false", "0", "no", "off")
|
||||
|
||||
def _slack_strict_mention(self) -> bool:
|
||||
"""When true, channel threads require an explicit @-mention on every
|
||||
message. Disables all auto-triggers (mentioned-thread memory,
|
||||
bot-message follow-up, session-presence). Defaults to False.
|
||||
"""
|
||||
configured = self.config.extra.get("strict_mention")
|
||||
if configured is not None:
|
||||
if isinstance(configured, str):
|
||||
return configured.lower() in ("true", "1", "yes", "on")
|
||||
return bool(configured)
|
||||
return os.getenv("SLACK_STRICT_MENTION", "false").lower() in ("true", "1", "yes", "on")
|
||||
|
||||
def _slack_free_response_channels(self) -> set:
|
||||
"""Return channel IDs where no @mention is required."""
|
||||
raw = self.config.extra.get("free_response_channels")
|
||||
|
|
|
|||
|
|
@ -55,10 +55,12 @@ CHANNEL_ID = "C0AQWDLHY9M"
|
|||
OTHER_CHANNEL_ID = "C9999999999"
|
||||
|
||||
|
||||
def _make_adapter(require_mention=None, free_response_channels=None):
|
||||
def _make_adapter(require_mention=None, strict_mention=None, free_response_channels=None):
|
||||
extra = {}
|
||||
if require_mention is not None:
|
||||
extra["require_mention"] = require_mention
|
||||
if strict_mention is not None:
|
||||
extra["strict_mention"] = strict_mention
|
||||
if free_response_channels is not None:
|
||||
extra["free_response_channels"] = free_response_channels
|
||||
|
||||
|
|
@ -134,6 +136,48 @@ def test_require_mention_env_var_default_true(monkeypatch):
|
|||
assert adapter._slack_require_mention() is True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests: _slack_strict_mention
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_strict_mention_defaults_to_false(monkeypatch):
|
||||
monkeypatch.delenv("SLACK_STRICT_MENTION", raising=False)
|
||||
adapter = _make_adapter()
|
||||
assert adapter._slack_strict_mention() is False
|
||||
|
||||
|
||||
def test_strict_mention_true():
|
||||
adapter = _make_adapter(strict_mention=True)
|
||||
assert adapter._slack_strict_mention() is True
|
||||
|
||||
|
||||
def test_strict_mention_false():
|
||||
adapter = _make_adapter(strict_mention=False)
|
||||
assert adapter._slack_strict_mention() is False
|
||||
|
||||
|
||||
def test_strict_mention_string_true():
|
||||
adapter = _make_adapter(strict_mention="true")
|
||||
assert adapter._slack_strict_mention() is True
|
||||
|
||||
|
||||
def test_strict_mention_string_off():
|
||||
adapter = _make_adapter(strict_mention="off")
|
||||
assert adapter._slack_strict_mention() is False
|
||||
|
||||
|
||||
def test_strict_mention_malformed_stays_false():
|
||||
"""Unrecognised values keep strict mode OFF (fail-open to legacy behavior)."""
|
||||
adapter = _make_adapter(strict_mention="maybe")
|
||||
assert adapter._slack_strict_mention() is False
|
||||
|
||||
|
||||
def test_strict_mention_env_var_fallback(monkeypatch):
|
||||
monkeypatch.setenv("SLACK_STRICT_MENTION", "true")
|
||||
adapter = _make_adapter() # no config value -> falls back to env
|
||||
assert adapter._slack_strict_mention() is True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests: _slack_free_response_channels
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -350,3 +394,24 @@ def test_config_bridges_slack_reply_in_thread(monkeypatch, tmp_path):
|
|||
reply_to="171.500",
|
||||
metadata={"thread_id": "171.000"},
|
||||
) == "171.000"
|
||||
|
||||
|
||||
def test_config_bridges_slack_strict_mention(monkeypatch, tmp_path):
|
||||
from gateway.config import load_gateway_config
|
||||
|
||||
hermes_home = tmp_path / ".hermes"
|
||||
hermes_home.mkdir()
|
||||
(hermes_home / "config.yaml").write_text(
|
||||
"slack:\n"
|
||||
" strict_mention: true\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
monkeypatch.delenv("SLACK_STRICT_MENTION", raising=False)
|
||||
|
||||
config = load_gateway_config()
|
||||
|
||||
assert config is not None
|
||||
import os as _os
|
||||
assert _os.environ["SLACK_STRICT_MENTION"] == "true"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue