mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-14 04:02:26 +00:00
fix(gateway): load reply_to_mode from config.yaml for Discord and Telegram
The YAML-to-env-var bridge in load_gateway_config() mapped every Discord and Telegram config key (require_mention, auto_thread, reactions, etc.) except reply_to_mode. Users setting discord.reply_to_mode or telegram.reply_to_mode in ~/.hermes/config.yaml got no effect — the adapter only read the env var, which nothing populated from YAML. Add the missing bridge for both platforms, following the existing pattern. Top-level <platform>.reply_to_mode preferred, falls back to <platform>.extra.reply_to_mode, env var never overwritten. Handles YAML 1.1 bare `off` → Python False coercion. This is a re-submission of the work from #9837 and #13930, which both implemented the same fix but neither landed (see co-authors below). Co-authored-by: Matteo De Agazio <hypnosis.mda@gmail.com> Co-authored-by: ishardo <239075732+ishardo@users.noreply.github.com>
This commit is contained in:
parent
354502ee48
commit
6b76ea4707
3 changed files with 150 additions and 2 deletions
|
|
@ -845,6 +845,16 @@ def load_gateway_config() -> GatewayConfig:
|
||||||
):
|
):
|
||||||
if yaml_key in allow_mentions_cfg and not os.getenv(env_key):
|
if yaml_key in allow_mentions_cfg and not os.getenv(env_key):
|
||||||
os.environ[env_key] = str(allow_mentions_cfg[yaml_key]).lower()
|
os.environ[env_key] = str(allow_mentions_cfg[yaml_key]).lower()
|
||||||
|
# reply_to_mode: top-level preferred, falls back to extra.reply_to_mode
|
||||||
|
# YAML 1.1 parses bare 'off' as boolean False — coerce to string "off".
|
||||||
|
_discord_extra = discord_cfg.get("extra") if isinstance(discord_cfg.get("extra"), dict) else {}
|
||||||
|
_discord_rtm = (
|
||||||
|
discord_cfg["reply_to_mode"] if "reply_to_mode" in discord_cfg
|
||||||
|
else _discord_extra.get("reply_to_mode")
|
||||||
|
)
|
||||||
|
if _discord_rtm is not None and not os.getenv("DISCORD_REPLY_TO_MODE"):
|
||||||
|
_rtm_str = "off" if _discord_rtm is False else str(_discord_rtm).lower()
|
||||||
|
os.environ["DISCORD_REPLY_TO_MODE"] = _rtm_str
|
||||||
|
|
||||||
# Bridge top-level require_mention to Telegram when the telegram: section
|
# Bridge top-level require_mention to Telegram when the telegram: section
|
||||||
# does not already provide one. Users often write "require_mention: true"
|
# does not already provide one. Users often write "require_mention: true"
|
||||||
|
|
@ -881,6 +891,16 @@ def load_gateway_config() -> GatewayConfig:
|
||||||
os.environ["TELEGRAM_REACTIONS"] = str(telegram_cfg["reactions"]).lower()
|
os.environ["TELEGRAM_REACTIONS"] = str(telegram_cfg["reactions"]).lower()
|
||||||
if "proxy_url" in telegram_cfg and not os.getenv("TELEGRAM_PROXY"):
|
if "proxy_url" in telegram_cfg and not os.getenv("TELEGRAM_PROXY"):
|
||||||
os.environ["TELEGRAM_PROXY"] = str(telegram_cfg["proxy_url"]).strip()
|
os.environ["TELEGRAM_PROXY"] = str(telegram_cfg["proxy_url"]).strip()
|
||||||
|
# reply_to_mode: top-level preferred, falls back to extra.reply_to_mode
|
||||||
|
# YAML 1.1 parses bare 'off' as boolean False — coerce to string "off".
|
||||||
|
_telegram_extra = telegram_cfg.get("extra") if isinstance(telegram_cfg.get("extra"), dict) else {}
|
||||||
|
_telegram_rtm = (
|
||||||
|
telegram_cfg["reply_to_mode"] if "reply_to_mode" in telegram_cfg
|
||||||
|
else _telegram_extra.get("reply_to_mode")
|
||||||
|
)
|
||||||
|
if _telegram_rtm is not None and not os.getenv("TELEGRAM_REPLY_TO_MODE"):
|
||||||
|
_rtm_str = "off" if _telegram_rtm is False else str(_telegram_rtm).lower()
|
||||||
|
os.environ["TELEGRAM_REPLY_TO_MODE"] = _rtm_str
|
||||||
allowed_users = telegram_cfg.get("allow_from")
|
allowed_users = telegram_cfg.get("allow_from")
|
||||||
if allowed_users is not None and not os.getenv("TELEGRAM_ALLOWED_USERS"):
|
if allowed_users is not None and not os.getenv("TELEGRAM_ALLOWED_USERS"):
|
||||||
if isinstance(allowed_users, list):
|
if isinstance(allowed_users, list):
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ from unittest.mock import MagicMock, AsyncMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from gateway.config import PlatformConfig, GatewayConfig, Platform, _apply_env_overrides
|
from gateway.config import PlatformConfig, GatewayConfig, Platform, _apply_env_overrides, load_gateway_config
|
||||||
|
|
||||||
|
|
||||||
def _ensure_discord_mock():
|
def _ensure_discord_mock():
|
||||||
|
|
@ -396,3 +396,67 @@ class TestReplyToText:
|
||||||
event = reply_text_adapter.handle_message.await_args.args[0]
|
event = reply_text_adapter.handle_message.await_args.args[0]
|
||||||
assert event.reply_to_message_id == "555"
|
assert event.reply_to_message_id == "555"
|
||||||
assert event.reply_to_text is None
|
assert event.reply_to_text is None
|
||||||
|
|
||||||
|
|
||||||
|
class TestYamlConfigLoading:
|
||||||
|
"""Tests for reply_to_mode loaded from config.yaml discord section."""
|
||||||
|
|
||||||
|
def _write_config(self, tmp_path, content: str):
|
||||||
|
hermes_home = tmp_path / ".hermes"
|
||||||
|
hermes_home.mkdir()
|
||||||
|
(hermes_home / "config.yaml").write_text(content, encoding="utf-8")
|
||||||
|
return hermes_home
|
||||||
|
|
||||||
|
def test_top_level_reply_to_mode_off(self, tmp_path, monkeypatch):
|
||||||
|
"""YAML 1.1 parses bare 'off' as boolean False — must map back to 'off'."""
|
||||||
|
hermes_home = self._write_config(tmp_path, "discord:\n reply_to_mode: off\n")
|
||||||
|
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||||
|
monkeypatch.delenv("DISCORD_REPLY_TO_MODE", raising=False)
|
||||||
|
|
||||||
|
load_gateway_config()
|
||||||
|
|
||||||
|
assert os.environ.get("DISCORD_REPLY_TO_MODE") == "off"
|
||||||
|
|
||||||
|
def test_top_level_reply_to_mode_all(self, tmp_path, monkeypatch):
|
||||||
|
hermes_home = self._write_config(tmp_path, "discord:\n reply_to_mode: all\n")
|
||||||
|
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||||
|
monkeypatch.delenv("DISCORD_REPLY_TO_MODE", raising=False)
|
||||||
|
|
||||||
|
load_gateway_config()
|
||||||
|
|
||||||
|
assert os.environ.get("DISCORD_REPLY_TO_MODE") == "all"
|
||||||
|
|
||||||
|
def test_extra_reply_to_mode_off(self, tmp_path, monkeypatch):
|
||||||
|
"""discord.extra.reply_to_mode is also honoured."""
|
||||||
|
hermes_home = self._write_config(
|
||||||
|
tmp_path, "discord:\n extra:\n reply_to_mode: \"off\"\n"
|
||||||
|
)
|
||||||
|
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||||
|
monkeypatch.delenv("DISCORD_REPLY_TO_MODE", raising=False)
|
||||||
|
|
||||||
|
load_gateway_config()
|
||||||
|
|
||||||
|
assert os.environ.get("DISCORD_REPLY_TO_MODE") == "off"
|
||||||
|
|
||||||
|
def test_env_var_takes_precedence_over_yaml(self, tmp_path, monkeypatch):
|
||||||
|
"""Existing DISCORD_REPLY_TO_MODE env var is not overwritten by YAML."""
|
||||||
|
hermes_home = self._write_config(tmp_path, "discord:\n reply_to_mode: all\n")
|
||||||
|
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||||
|
monkeypatch.setenv("DISCORD_REPLY_TO_MODE", "first")
|
||||||
|
|
||||||
|
load_gateway_config()
|
||||||
|
|
||||||
|
assert os.environ.get("DISCORD_REPLY_TO_MODE") == "first"
|
||||||
|
|
||||||
|
def test_top_level_takes_precedence_over_extra(self, tmp_path, monkeypatch):
|
||||||
|
"""discord.reply_to_mode wins over discord.extra.reply_to_mode."""
|
||||||
|
hermes_home = self._write_config(
|
||||||
|
tmp_path,
|
||||||
|
"discord:\n reply_to_mode: all\n extra:\n reply_to_mode: \"off\"\n",
|
||||||
|
)
|
||||||
|
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||||
|
monkeypatch.delenv("DISCORD_REPLY_TO_MODE", raising=False)
|
||||||
|
|
||||||
|
load_gateway_config()
|
||||||
|
|
||||||
|
assert os.environ.get("DISCORD_REPLY_TO_MODE") == "all"
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ from unittest.mock import MagicMock, AsyncMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from gateway.config import PlatformConfig, GatewayConfig, Platform, _apply_env_overrides
|
from gateway.config import PlatformConfig, GatewayConfig, Platform, _apply_env_overrides, load_gateway_config
|
||||||
|
|
||||||
|
|
||||||
def _ensure_telegram_mock():
|
def _ensure_telegram_mock():
|
||||||
|
|
@ -240,3 +240,67 @@ class TestEnvVarOverride:
|
||||||
with patch.dict(os.environ, {"TELEGRAM_REPLY_TO_MODE": ""}, clear=False):
|
with patch.dict(os.environ, {"TELEGRAM_REPLY_TO_MODE": ""}, clear=False):
|
||||||
_apply_env_overrides(config)
|
_apply_env_overrides(config)
|
||||||
assert config.platforms[Platform.TELEGRAM].reply_to_mode == "first"
|
assert config.platforms[Platform.TELEGRAM].reply_to_mode == "first"
|
||||||
|
|
||||||
|
|
||||||
|
class TestTelegramYamlConfigLoading:
|
||||||
|
"""Tests for reply_to_mode loaded from config.yaml telegram section."""
|
||||||
|
|
||||||
|
def _write_config(self, tmp_path, content: str):
|
||||||
|
hermes_home = tmp_path / ".hermes"
|
||||||
|
hermes_home.mkdir()
|
||||||
|
(hermes_home / "config.yaml").write_text(content, encoding="utf-8")
|
||||||
|
return hermes_home
|
||||||
|
|
||||||
|
def test_top_level_reply_to_mode_off(self, tmp_path, monkeypatch):
|
||||||
|
"""YAML 1.1 parses bare 'off' as boolean False — must map back to 'off'."""
|
||||||
|
hermes_home = self._write_config(tmp_path, "telegram:\n reply_to_mode: off\n")
|
||||||
|
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||||
|
monkeypatch.delenv("TELEGRAM_REPLY_TO_MODE", raising=False)
|
||||||
|
|
||||||
|
load_gateway_config()
|
||||||
|
|
||||||
|
assert os.environ.get("TELEGRAM_REPLY_TO_MODE") == "off"
|
||||||
|
|
||||||
|
def test_top_level_reply_to_mode_all(self, tmp_path, monkeypatch):
|
||||||
|
hermes_home = self._write_config(tmp_path, "telegram:\n reply_to_mode: all\n")
|
||||||
|
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||||
|
monkeypatch.delenv("TELEGRAM_REPLY_TO_MODE", raising=False)
|
||||||
|
|
||||||
|
load_gateway_config()
|
||||||
|
|
||||||
|
assert os.environ.get("TELEGRAM_REPLY_TO_MODE") == "all"
|
||||||
|
|
||||||
|
def test_extra_reply_to_mode_off(self, tmp_path, monkeypatch):
|
||||||
|
"""telegram.extra.reply_to_mode is also honoured."""
|
||||||
|
hermes_home = self._write_config(
|
||||||
|
tmp_path, "telegram:\n extra:\n reply_to_mode: \"off\"\n"
|
||||||
|
)
|
||||||
|
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||||
|
monkeypatch.delenv("TELEGRAM_REPLY_TO_MODE", raising=False)
|
||||||
|
|
||||||
|
load_gateway_config()
|
||||||
|
|
||||||
|
assert os.environ.get("TELEGRAM_REPLY_TO_MODE") == "off"
|
||||||
|
|
||||||
|
def test_env_var_takes_precedence_over_yaml(self, tmp_path, monkeypatch):
|
||||||
|
"""Existing TELEGRAM_REPLY_TO_MODE env var is not overwritten by YAML."""
|
||||||
|
hermes_home = self._write_config(tmp_path, "telegram:\n reply_to_mode: all\n")
|
||||||
|
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||||
|
monkeypatch.setenv("TELEGRAM_REPLY_TO_MODE", "first")
|
||||||
|
|
||||||
|
load_gateway_config()
|
||||||
|
|
||||||
|
assert os.environ.get("TELEGRAM_REPLY_TO_MODE") == "first"
|
||||||
|
|
||||||
|
def test_top_level_takes_precedence_over_extra(self, tmp_path, monkeypatch):
|
||||||
|
"""telegram.reply_to_mode wins over telegram.extra.reply_to_mode."""
|
||||||
|
hermes_home = self._write_config(
|
||||||
|
tmp_path,
|
||||||
|
"telegram:\n reply_to_mode: all\n extra:\n reply_to_mode: \"off\"\n",
|
||||||
|
)
|
||||||
|
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||||
|
monkeypatch.delenv("TELEGRAM_REPLY_TO_MODE", raising=False)
|
||||||
|
|
||||||
|
load_gateway_config()
|
||||||
|
|
||||||
|
assert os.environ.get("TELEGRAM_REPLY_TO_MODE") == "all"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue