fix(tools): resolve whatsapp phone numbers to lid format

Adds a resolution pipeline `_resolve_whatsapp_phone_to_jid` to the
`send_message_tool`. Updates `_parse_target_ref` and `_send_whatsapp`
to use the resolution pipeline when bare phone numbers are provided.
Passes through already valid JIDs (ending with `@lid`, `@s.whatsapp.net`,
`@g.us`).

This resolves the issue where AI tool calls and cron reminders failed
to send WhatsApp messages because they passed bare phone numbers
instead of the newly enforced Linked Identity Device (LID) formats.
It checks the local bridge mapping files (`lid-mapping-{phone}.json`)
to gracefully convert phone numbers to LIDs.

Comprehensive unit tests were added in `test_send_message_tool.py`
testing legacy JIDs, LIDs, group JIDs, and fallback mechanisms.

Fixes: #14486
This commit is contained in:
Yshnav 2026-04-24 02:17:51 +05:30
parent e3c0084140
commit b668f730f5
2 changed files with 187 additions and 6 deletions

View file

@ -786,8 +786,11 @@ class TestParseTargetRefE164:
assert is_explicit is True
def test_whatsapp_e164_is_explicit(self):
chat_id, _, is_explicit = _parse_target_ref("whatsapp", "+15551234567")
assert chat_id == "+15551234567"
"""WhatsApp E.164 resolves to @s.whatsapp.net JID when no mapping exists."""
with patch("tools.send_message_tool._resolve_whatsapp_phone_to_jid",
return_value="15551234567@s.whatsapp.net"):
chat_id, _, is_explicit = _parse_target_ref("whatsapp", "+15551234567")
assert chat_id == "15551234567@s.whatsapp.net"
assert is_explicit is True
def test_signal_bare_digits_still_work(self):
@ -810,6 +813,107 @@ class TestParseTargetRefE164:
assert _parse_target_ref("matrix", "+15551234567")[2] is False
class TestWhatsAppPhoneToLidResolution:
"""Tests for WhatsApp phone→LID resolution in _parse_target_ref and helpers."""
def test_phone_resolves_to_lid_via_mapping_file(self, tmp_path, monkeypatch):
"""Phone number is resolved to LID@lid when a mapping file exists."""
session_dir = tmp_path / "whatsapp" / "session"
session_dir.mkdir(parents=True)
(session_dir / "lid-mapping-351912345678.json").write_text(
'"77214955630717"', encoding="utf-8"
)
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
chat_id, _, is_explicit = _parse_target_ref("whatsapp", "+351912345678")
assert chat_id == "77214955630717@lid"
assert is_explicit is True
def test_phone_falls_back_to_legacy_jid(self, tmp_path, monkeypatch):
"""Phone number falls back to @s.whatsapp.net when no mapping exists."""
# Ensure session dir exists but has no mapping for this phone
session_dir = tmp_path / "whatsapp" / "session"
session_dir.mkdir(parents=True)
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
chat_id, _, is_explicit = _parse_target_ref("whatsapp", "+15551234567")
assert chat_id == "15551234567@s.whatsapp.net"
assert is_explicit is True
def test_lid_jid_is_explicit_target(self):
"""A @lid JID is recognized as an explicit WhatsApp target."""
chat_id, _, is_explicit = _parse_target_ref("whatsapp", "77214955630717@lid")
assert chat_id == "77214955630717@lid"
assert is_explicit is True
def test_legacy_jid_is_explicit_target(self):
"""A @s.whatsapp.net JID is recognized as an explicit WhatsApp target."""
chat_id, _, is_explicit = _parse_target_ref("whatsapp", "351912345678@s.whatsapp.net")
assert chat_id == "351912345678@s.whatsapp.net"
assert is_explicit is True
def test_group_jid_is_explicit_target(self):
"""A @g.us group JID is recognized as an explicit WhatsApp target."""
chat_id, _, is_explicit = _parse_target_ref("whatsapp", "120363123456789@g.us")
assert chat_id == "120363123456789@g.us"
assert is_explicit is True
def test_device_lid_is_explicit_target(self):
"""A device-qualified LID (with :device suffix) passes through."""
chat_id, _, is_explicit = _parse_target_ref("whatsapp", "77214955630717:15@lid")
assert chat_id == "77214955630717:15@lid"
assert is_explicit is True
def test_signal_e164_still_preserves_plus(self):
"""Signal E.164 behavior is unchanged (no JID resolution)."""
chat_id, _, is_explicit = _parse_target_ref("signal", "+41791234567")
assert chat_id == "+41791234567"
assert is_explicit is True
def test_sms_e164_still_preserves_plus(self):
"""SMS E.164 behavior is unchanged (no JID resolution)."""
chat_id, _, is_explicit = _parse_target_ref("sms", "+15551234567")
assert chat_id == "+15551234567"
assert is_explicit is True
class TestEnsureWhatsAppJid:
"""Tests for _ensure_whatsapp_jid helper."""
def test_lid_jid_passes_through(self):
from tools.send_message_tool import _ensure_whatsapp_jid
assert _ensure_whatsapp_jid("77214955630717@lid") == "77214955630717@lid"
def test_legacy_jid_passes_through(self):
from tools.send_message_tool import _ensure_whatsapp_jid
assert _ensure_whatsapp_jid("351912345678@s.whatsapp.net") == "351912345678@s.whatsapp.net"
def test_group_jid_passes_through(self):
from tools.send_message_tool import _ensure_whatsapp_jid
assert _ensure_whatsapp_jid("120363123456789@g.us") == "120363123456789@g.us"
def test_bare_number_gets_resolved(self, tmp_path, monkeypatch):
from tools.send_message_tool import _ensure_whatsapp_jid
session_dir = tmp_path / "whatsapp" / "session"
session_dir.mkdir(parents=True)
(session_dir / "lid-mapping-351912345678.json").write_text(
'"77214955630717"', encoding="utf-8"
)
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
assert _ensure_whatsapp_jid("351912345678") == "77214955630717@lid"
def test_bare_number_fallback(self, tmp_path, monkeypatch):
from tools.send_message_tool import _ensure_whatsapp_jid
session_dir = tmp_path / "whatsapp" / "session"
session_dir.mkdir(parents=True)
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
assert _ensure_whatsapp_jid("15551234567") == "15551234567@s.whatsapp.net"
def test_empty_passes_through(self):
from tools.send_message_tool import _ensure_whatsapp_jid
assert _ensure_whatsapp_jid("") == ""
class TestSendDiscordThreadId:
"""_send_discord uses thread_id when provided."""