diff --git a/plugins/platforms/whatsapp/adapter.py b/plugins/platforms/whatsapp/adapter.py index add70f4d67..485608f991 100644 --- a/plugins/platforms/whatsapp/adapter.py +++ b/plugins/platforms/whatsapp/adapter.py @@ -225,7 +225,7 @@ _BRIDGE_PASSTHROUGH_ENV = ( "WHATSAPP_ALLOWED_USERS", "WHATSAPP_ALLOW_FROM", "WHATSAPP_DM_POLICY", "WHATSAPP_GROUP_POLICY", "WHATSAPP_GROUP_ALLOWED_USERS", "WHATSAPP_GROUP_ALLOW_FROM", "WHATSAPP_REQUIRE_MENTION", "WHATSAPP_MENTION_PATTERNS", "WHATSAPP_FREE_RESPONSE_CHATS", "WHATSAPP_DEBUG", - "WHATSAPP_FORWARD_OWNER_MESSAGES", "WHATSAPP_REPLY_PREFIX", "WHATSAPP_MAX_MESSAGE_LENGTH", + "WHATSAPP_FORWARD_OWNER_MESSAGES", "WHATSAPP_MAX_MESSAGE_LENGTH", "WHATSAPP_CHUNK_DELAY_MS", "WHATSAPP_SEND_TIMEOUT_MS", ) _TEXT_INJECT_EXTS = {".txt", ".md", ".csv", ".json", ".xml", ".yaml", ".yml", ".log", ".py", ".js", ".ts", ".html", ".css"} @@ -375,8 +375,13 @@ class WhatsAppAdapter(WhatsAppBehaviorMixin, BasePlatformAdapter): # that copy carries the DEFAULT profile's WHATSAPP_* values, so every bridge-consumed key is # re-resolved from this profile (dropped on a scoped miss), never inherited from the launch env. bridge_env = with_hermes_node_path() - if self._reply_prefix is not None: + reply_prefix = _wenv("WHATSAPP_REPLY_PREFIX") + if reply_prefix: + bridge_env["WHATSAPP_REPLY_PREFIX"] = reply_prefix + elif self._reply_prefix is not None: bridge_env["WHATSAPP_REPLY_PREFIX"] = self._reply_prefix + else: + bridge_env.pop("WHATSAPP_REPLY_PREFIX", None) bridge_env["WHATSAPP_SEND_READ_RECEIPTS"] = "true" if self._send_read_receipts else "false" for _key, _v in [("WHATSAPP_MODE", _wenv("WHATSAPP_MODE", "self-chat"))] + [(k, _wenv(k)) for k in _BRIDGE_PASSTHROUGH_ENV]: if _v: diff --git a/tests/gateway/test_adapter_settings_scoped_precedence.py b/tests/gateway/test_adapter_settings_scoped_precedence.py index f4bd0d454f..dc9e05b91c 100644 --- a/tests/gateway/test_adapter_settings_scoped_precedence.py +++ b/tests/gateway/test_adapter_settings_scoped_precedence.py @@ -195,3 +195,38 @@ def test_whatsapp_bridge_env_carries_the_secondary_effective_policy(homes, monke assert a._group_policy == "allowlist" == env["WHATSAPP_GROUP_POLICY"] assert env["WHATSAPP_GROUP_ALLOWED_USERS"] == "120363001234567890@g.us" assert "WHATSAPP_ALLOWED_USERS" not in env + + +@pytest.mark.parametrize("secondary_prefix", [None, "Secondary Bot: ", ""]) +def test_whatsapp_reply_prefix_isolated_across_profile_scopes( + homes, monkeypatch, secondary_prefix +): + from plugins.platforms.whatsapp.adapter import WhatsAppAdapter + + launch, secondary = homes + monkeypatch.setenv("WHATSAPP_REPLY_PREFIX", "Launch Bot: ") + (launch / "config.yaml").write_text( + 'whatsapp:\n enabled: true\n reply_prefix: "Launch YAML: "\n' + ) + launch_before = WhatsAppAdapter( + load_gateway_config().platforms[Platform.WHATSAPP] + ) + assert launch_before._bridge_env()["WHATSAPP_REPLY_PREFIX"] == "Launch Bot: " + + secondary_yaml = "whatsapp:\n enabled: true\n" + if secondary_prefix is not None: + secondary_yaml += f' reply_prefix: "{secondary_prefix}"\n' + (secondary / "config.yaml").write_text(secondary_yaml) + with _secondary_scope(secondary): + secondary_adapter = WhatsAppAdapter( + load_gateway_config().platforms[Platform.WHATSAPP] + ) + assert ( + secondary_adapter._bridge_env().get("WHATSAPP_REPLY_PREFIX") + == secondary_prefix + ) + + launch_after = WhatsAppAdapter( + load_gateway_config().platforms[Platform.WHATSAPP] + ) + assert launch_after._bridge_env()["WHATSAPP_REPLY_PREFIX"] == "Launch Bot: " diff --git a/tests/gateway/test_whatsapp_reply_prefix.py b/tests/gateway/test_whatsapp_reply_prefix.py index 26a008c2b0..c67a0fefb1 100644 --- a/tests/gateway/test_whatsapp_reply_prefix.py +++ b/tests/gateway/test_whatsapp_reply_prefix.py @@ -83,6 +83,31 @@ class TestAdapterInit: assert adapter._reply_prefix == "Bot\\n" +class TestBridgeEnvironment: + @pytest.mark.parametrize( + ("configured", "explicit_env", "expected"), + [ + ("Custom Bot\\n", None, "Custom Bot\\n"), + ("", None, ""), + ("Config Bot\\n", "Env Bot\\n", "Env Bot\\n"), + ], + ) + def test_reply_prefix_reaches_bridge_with_existing_precedence( + self, monkeypatch, configured, explicit_env, expected + ): + from plugins.platforms.whatsapp.adapter import WhatsAppAdapter + + monkeypatch.delenv("WHATSAPP_REPLY_PREFIX", raising=False) + if explicit_env is not None: + monkeypatch.setenv("WHATSAPP_REPLY_PREFIX", explicit_env) + + adapter = WhatsAppAdapter( + PlatformConfig(enabled=True, extra={"reply_prefix": configured}) + ) + + assert adapter._bridge_env()["WHATSAPP_REPLY_PREFIX"] == expected + + class TestReadReceiptPolicyOrdering: @pytest.mark.asyncio async def test_accepted_receipt_key_is_sent_to_bridge(self):