fix(gateway): bridge top-level require_mention to Telegram config

Users commonly place `require_mention: true` at the top level of
config.yaml alongside `group_sessions_per_user`, expecting it to gate
Telegram group messages. The key was silently ignored because the
config loader only checked `yaml_cfg["telegram"]["require_mention"]`.

When `require_mention` is found at the top level and no telegram-specific
value is set, the fix now:
- adds it to platforms_data["telegram"]["extra"] so _telegram_require_mention()
  picks it up via the primary config.extra path
- sets TELEGRAM_REQUIRE_MENTION env var for the secondary fallback path

A telegram-specific value (telegram.require_mention) still takes
precedence over the top-level shorthand.

Also corrects telegram.md: bare /cmd without @botname is rejected when
require_mention is enabled; only /cmd@botname (bot-menu form) passes.

Fixes #3979
This commit is contained in:
konsisumer
2026-05-03 19:58:04 +02:00
committed by Teknium
parent 1bd975c0ba
commit 6fda92aa7f
3 changed files with 68 additions and 3 deletions

View File

@@ -846,11 +846,25 @@ def load_gateway_config() -> GatewayConfig:
if yaml_key in allow_mentions_cfg and not os.getenv(env_key):
os.environ[env_key] = str(allow_mentions_cfg[yaml_key]).lower()
# Bridge top-level require_mention to Telegram when the telegram: section
# does not already provide one. Users often write "require_mention: true"
# at the top level alongside group_sessions_per_user, expecting it to work
# the same way (#3979).
_tl_require_mention = yaml_cfg.get("require_mention")
if _tl_require_mention is not None:
_tg_section = yaml_cfg.get("telegram") or {}
if "require_mention" not in _tg_section:
_tg_plat = platforms_data.setdefault(Platform.TELEGRAM.value, {})
_tg_extra = _tg_plat.setdefault("extra", {})
_tg_extra.setdefault("require_mention", _tl_require_mention)
# Telegram settings → env vars (env vars take precedence)
telegram_cfg = yaml_cfg.get("telegram", {})
if isinstance(telegram_cfg, dict):
if "require_mention" in telegram_cfg and not os.getenv("TELEGRAM_REQUIRE_MENTION"):
os.environ["TELEGRAM_REQUIRE_MENTION"] = str(telegram_cfg["require_mention"]).lower()
# Prefer telegram.require_mention; fall back to the top-level shorthand.
_effective_rm = telegram_cfg.get("require_mention", yaml_cfg.get("require_mention"))
if _effective_rm is not None and not os.getenv("TELEGRAM_REQUIRE_MENTION"):
os.environ["TELEGRAM_REQUIRE_MENTION"] = str(_effective_rm).lower()
if "mention_patterns" in telegram_cfg and not os.getenv("TELEGRAM_MENTION_PATTERNS"):
os.environ["TELEGRAM_MENTION_PATTERNS"] = json.dumps(telegram_cfg["mention_patterns"])
frc = telegram_cfg.get("free_response_chats")

View File

@@ -261,6 +261,57 @@ def test_group_allow_from_is_enforced_by_gateway_authorization_not_trigger_gate(
assert adapter._should_process_message(_group_message("hello", from_user_id=333)) is True
def test_top_level_require_mention_bridges_to_telegram(monkeypatch, tmp_path):
"""require_mention at the config.yaml top level (alongside group_sessions_per_user)
must behave identically to telegram.require_mention: true (#3979).
"""
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
# Intentionally no "telegram:" section — keys are at the top level.
(hermes_home / "config.yaml").write_text(
"require_mention: true\n"
"group_sessions_per_user: true\n",
encoding="utf-8",
)
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
monkeypatch.delenv("TELEGRAM_REQUIRE_MENTION", raising=False)
config = load_gateway_config()
assert config is not None
assert __import__("os").environ.get("TELEGRAM_REQUIRE_MENTION") == "true"
# The adapter's extra dict must also carry the setting so that
# _telegram_require_mention() works even without the env var.
tg_cfg = config.platforms.get(__import__("gateway.config", fromlist=["Platform"]).Platform.TELEGRAM)
if tg_cfg is not None:
assert tg_cfg.extra.get("require_mention") is True
def test_top_level_require_mention_does_not_override_telegram_section(monkeypatch, tmp_path):
"""When telegram.require_mention is explicitly set, top-level require_mention
must not override it (platform-specific config takes precedence).
"""
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
(hermes_home / "config.yaml").write_text(
"require_mention: true\n"
"telegram:\n"
" require_mention: false\n",
encoding="utf-8",
)
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
monkeypatch.delenv("TELEGRAM_REQUIRE_MENTION", raising=False)
config = load_gateway_config()
assert config is not None
# The telegram-specific "false" must win over the top-level "true".
assert __import__("os").environ.get("TELEGRAM_REQUIRE_MENTION") == "false"
def test_config_bridges_telegram_ignored_threads(monkeypatch, tmp_path):
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()

View File

@@ -293,9 +293,9 @@ Hermes Agent works in Telegram group chats with a few considerations:
- `TELEGRAM_ALLOWED_USERS` still applies — only authorized users can trigger the bot, even in groups
- You can keep the bot from responding to ordinary group chatter with `telegram.require_mention: true`
- With `telegram.require_mention: true`, group messages are accepted when they are:
- slash commands
- replies to one of the bot's messages
- `@botusername` mentions
- `/command@botusername` (Telegram's bot-menu command form that includes the bot name)
- matches for one of your configured regex wake words in `telegram.mention_patterns`
- Use `telegram.ignored_threads` to keep Hermes silent in specific Telegram forum topics, even when the group would otherwise allow free responses or mention-triggered replies
- If `telegram.require_mention` is left unset or false, Hermes keeps the previous open-group behavior and responds to normal group messages it can see