diff --git a/cli.py b/cli.py index 0d5bcf9fda..1b50d22736 100644 --- a/cli.py +++ b/cli.py @@ -6000,6 +6000,7 @@ class HermesCLI: platform_status = { Platform.TELEGRAM: ("Telegram", "TELEGRAM_BOT_TOKEN"), Platform.DISCORD: ("Discord", "DISCORD_BOT_TOKEN"), + Platform.SLACK: ("Slack", "SLACK_BOT_TOKEN"), Platform.WHATSAPP: ("WhatsApp", "WHATSAPP_ENABLED"), } diff --git a/gateway/config.py b/gateway/config.py index 128bfa61ca..da9830fcf2 100644 --- a/gateway/config.py +++ b/gateway/config.py @@ -566,6 +566,8 @@ def load_gateway_config() -> GatewayConfig: existing = {} # Deep-merge extra dicts so gateway.json defaults survive merged_extra = {**existing.get("extra", {}), **plat_block.get("extra", {})} + if plat_name == Platform.SLACK.value and "enabled" in plat_block: + merged_extra["_enabled_explicit"] = True merged = {**existing, **plat_block} if merged_extra: merged["extra"] = merged_extra @@ -610,16 +612,21 @@ 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 not bridged: + enabled_was_explicit = "enabled" in platform_cfg + if not bridged and not enabled_was_explicit: continue plat_data = platforms_data.setdefault(plat.value, {}) if not isinstance(plat_data, dict): plat_data = {} platforms_data[plat.value] = plat_data + if enabled_was_explicit: + plat_data["enabled"] = platform_cfg["enabled"] extra = plat_data.setdefault("extra", {}) if not isinstance(extra, dict): extra = {} plat_data["extra"] = extra + if plat == Platform.SLACK and enabled_was_explicit: + extra["_enabled_explicit"] = True extra.update(bridged) # Slack settings → env vars (env vars take precedence) @@ -941,6 +948,14 @@ def _apply_env_overrides(config: GatewayConfig) -> None: # No yaml config for Slack — env-only setup, enable it config.platforms[Platform.SLACK] = PlatformConfig() config.platforms[Platform.SLACK].enabled = True + else: + slack_config = config.platforms[Platform.SLACK] + enabled_was_explicit = bool(slack_config.extra.pop("_enabled_explicit", False)) + if not slack_config.enabled and not enabled_was_explicit: + # Top-level Slack settings such as channel prompts should not + # turn an env-token setup into a disabled platform. Only an + # explicit slack.enabled/platforms.slack.enabled false should. + slack_config.enabled = True # If yaml config exists, respect its enabled flag (don't override # explicit enabled: false). Token is still stored so skills that # send Slack messages can use it without activating the gateway adapter. diff --git a/tests/gateway/test_slack_mention.py b/tests/gateway/test_slack_mention.py index 8e4eb5a910..e6ba010de0 100644 --- a/tests/gateway/test_slack_mention.py +++ b/tests/gateway/test_slack_mention.py @@ -356,6 +356,81 @@ def test_config_bridges_slack_free_response_channels(monkeypatch, tmp_path): assert _os.environ["SLACK_FREE_RESPONSE_CHANNELS"] == "C0AQWDLHY9M,C9999999999" +def test_top_level_slack_settings_do_not_disable_env_token_setup(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" + " require_mention: false\n", + encoding="utf-8", + ) + + monkeypatch.setenv("HERMES_HOME", str(hermes_home)) + monkeypatch.setenv("SLACK_BOT_TOKEN", "xoxb-test") + monkeypatch.delenv("SLACK_REQUIRE_MENTION", raising=False) + + config = load_gateway_config() + + slack_config = config.platforms[Platform.SLACK] + assert slack_config.enabled is True + assert slack_config.token == "xoxb-test" + assert slack_config.extra.get("require_mention") is False + assert "_enabled_explicit" not in slack_config.extra + + +def test_explicit_top_level_slack_enabled_false_wins_over_env_token(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" + " enabled: false\n" + " require_mention: false\n", + encoding="utf-8", + ) + + monkeypatch.setenv("HERMES_HOME", str(hermes_home)) + monkeypatch.setenv("SLACK_BOT_TOKEN", "xoxb-test") + monkeypatch.delenv("SLACK_REQUIRE_MENTION", raising=False) + + config = load_gateway_config() + + slack_config = config.platforms[Platform.SLACK] + assert slack_config.enabled is False + assert slack_config.token == "xoxb-test" + assert slack_config.extra.get("require_mention") is False + assert "_enabled_explicit" not in slack_config.extra + + +def test_explicit_platforms_slack_enabled_false_wins_over_env_token(monkeypatch, tmp_path): + from gateway.config import load_gateway_config + + hermes_home = tmp_path / ".hermes" + hermes_home.mkdir() + (hermes_home / "config.yaml").write_text( + "platforms:\n" + " slack:\n" + " enabled: false\n" + " extra:\n" + " reply_in_thread: false\n", + encoding="utf-8", + ) + + monkeypatch.setenv("HERMES_HOME", str(hermes_home)) + monkeypatch.setenv("SLACK_BOT_TOKEN", "xoxb-test") + + config = load_gateway_config() + + slack_config = config.platforms[Platform.SLACK] + assert slack_config.enabled is False + assert slack_config.token == "xoxb-test" + assert slack_config.extra.get("reply_in_thread") is False + assert "_enabled_explicit" not in slack_config.extra + + def test_config_bridges_slack_reply_in_thread(monkeypatch, tmp_path): from gateway.config import load_gateway_config