fix(whatsapp): honor configured reply prefix in bridge replies

whatsapp.reply_prefix from config.yaml was written into the bridge env and then
popped again by the WHATSAPP_* passthrough loop (the key was in
_BRIDGE_PASSTHROUGH_ENV and the scoped env lookup came back empty), so bridge.js
always fell back to its built-in header and the documented reply_prefix: ""
could not disable it. Resolve the prefix once (scoped env first, then the
adapter value) and keep it out of the passthrough loop.

Fixes #116059
This commit is contained in:
fangliquan
2026-09-19 22:43:55 -07:00
committed by Teknium
parent 434f91c14e
commit 3cbd3cce0a
3 changed files with 67 additions and 2 deletions

View File

@@ -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:

View File

@@ -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: "

View File

@@ -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):