Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions gateway/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,15 +322,21 @@ def from_dict(cls, data: Dict[str, Any]) -> "PlatformConfig":
if "home_channel" in data:
home_channel = HomeChannel.from_dict(data["home_channel"])

# gateway_restart_notification may be bridged into extra via the
# shared-key loop in load_gateway_config(); check both top-level
# and extra so YAML ``discord: gateway_restart_notification: false``
# works without needing a separate platforms: block.
_grn = data.get("gateway_restart_notification")
if _grn is None:
_grn = data.get("extra", {}).get("gateway_restart_notification")

return cls(
enabled=_coerce_bool(data.get("enabled"), False),
token=data.get("token"),
api_key=data.get("api_key"),
home_channel=home_channel,
reply_to_mode=data.get("reply_to_mode", "first"),
gateway_restart_notification=_coerce_bool(
data.get("gateway_restart_notification"), True
),
gateway_restart_notification=_coerce_bool(_grn, True),
extra=data.get("extra", {}),
)

Expand Down Expand Up @@ -849,6 +855,8 @@ def load_gateway_config() -> GatewayConfig:
bridged["channel_prompts"] = {str(k): v for k, v in channel_prompts.items()}
else:
bridged["channel_prompts"] = channel_prompts
if "gateway_restart_notification" in platform_cfg:
bridged["gateway_restart_notification"] = platform_cfg["gateway_restart_notification"]
enabled_was_explicit = "enabled" in platform_cfg
if not bridged and not enabled_was_explicit:
continue
Expand Down
9 changes: 7 additions & 2 deletions gateway/platforms/mattermost.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,14 @@ async def send(
"channel_id": chat_id,
"message": chunk,
}
# Thread support: reply_to is the root post ID.
# Thread support: use the thread's root_id from metadata when
# replying inside an existing CRT Thread. Mattermost requires
# root_id to point to the root-level post, not a nested reply.
# Fall back to reply_to for top-level channel messages (where
# the user's message itself is a valid thread root).
if reply_to and self._reply_mode == "thread":
payload["root_id"] = reply_to
thread_root = (metadata or {}).get("thread_id")
payload["root_id"] = thread_root or reply_to

data = await self._api_post("posts", payload)
if not data or "id" not in data:
Expand Down
80 changes: 80 additions & 0 deletions tests/gateway/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,23 @@ def test_gateway_restart_notification_coerces_quoted_false(self):
restored = PlatformConfig.from_dict({"gateway_restart_notification": "false"})
assert restored.gateway_restart_notification is False

def test_gateway_restart_notification_reads_from_extra_fallback(self):
"""When gateway_restart_notification is absent at top-level but present
in extra (as happens after the shared-key bridging loop in
load_gateway_config), from_dict must still pick it up."""
restored = PlatformConfig.from_dict({
"extra": {"gateway_restart_notification": False},
})
assert restored.gateway_restart_notification is False

def test_gateway_restart_notification_top_level_takes_precedence_over_extra(self):
"""Top-level value wins when both top-level and extra provide the key."""
restored = PlatformConfig.from_dict({
"gateway_restart_notification": True,
"extra": {"gateway_restart_notification": False},
})
assert restored.gateway_restart_notification is True


class TestGetConnectedPlatforms:
def test_returns_enabled_with_token(self):
Expand Down Expand Up @@ -679,3 +696,66 @@ def test_existing_platform_configs_accept_home_channel_env_overrides(self):
home = config.platforms[platform].home_channel
assert home is not None, f"{platform.value}: home_channel should not be None"
assert (home.chat_id, home.name) == expected, platform.value


class TestGatewayRestartNotificationBridging:
"""Regression tests for the gateway_restart_notification config bridge bug.

Setting ``discord: gateway_restart_notification: false`` in config.yaml
was silently ignored because:

1. The shared-key bridging loop in load_gateway_config() did not include
``gateway_restart_notification`` in its bridge list, so the value never
reached ``platforms_data["discord"]``.
2. PlatformConfig.from_dict() only read the key from the top-level dict,
not from ``extra`` where bridged values land.

Both issues are fixed. These tests verify the end-to-end path.
"""

def test_discord_yaml_section_bridged_to_platform_config(self, tmp_path, monkeypatch):
"""``discord: gateway_restart_notification: false`` in config.yaml
must propagate to PlatformConfig.gateway_restart_notification."""
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
config_path = hermes_home / "config.yaml"
config_path.write_text(
"discord:\n gateway_restart_notification: false\n",
encoding="utf-8",
)
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
config = load_gateway_config()
discord_cfg = config.platforms.get(Platform.DISCORD)
assert discord_cfg is not None
assert discord_cfg.gateway_restart_notification is False

def test_telegram_yaml_section_bridged_to_platform_config(self, tmp_path, monkeypatch):
"""``telegram: gateway_restart_notification: false`` in config.yaml
must propagate to PlatformConfig.gateway_restart_notification."""
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
config_path = hermes_home / "config.yaml"
config_path.write_text(
"telegram:\n gateway_restart_notification: false\n",
encoding="utf-8",
)
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
config = load_gateway_config()
telegram_cfg = config.platforms.get(Platform.TELEGRAM)
assert telegram_cfg is not None
assert telegram_cfg.gateway_restart_notification is False

def test_default_true_when_not_set(self, tmp_path, monkeypatch):
"""When gateway_restart_notification is absent, the default must be True."""
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
config_path = hermes_home / "config.yaml"
config_path.write_text(
"discord:\n require_mention: true\n",
encoding="utf-8",
)
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
config = load_gateway_config()
discord_cfg = config.platforms.get(Platform.DISCORD)
assert discord_cfg is not None
assert discord_cfg.gateway_restart_notification is True