mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-17 09:41:58 +00:00
fix: harden WhatsApp target alias salvage
Add a parser-only routing regression that proves raw WhatsApp group JIDs bypass channel-directory resolution and home-channel fallback, include channel_aliases.json in quick state snapshots, harden malformed alias handling, and map Keiron McCammon for release attribution.
This commit is contained in:
parent
ea49a79633
commit
0d82060c74
6 changed files with 98 additions and 6 deletions
|
|
@ -49,12 +49,16 @@ def _apply_channel_aliases(platforms: Dict[str, Any]) -> None:
|
|||
if not isinstance(id_map, dict):
|
||||
continue
|
||||
entries = platforms.setdefault(plat_name, [])
|
||||
if not isinstance(entries, list):
|
||||
continue
|
||||
for chat_id, friendly in id_map.items():
|
||||
if not friendly:
|
||||
if not isinstance(friendly, str) or not friendly.strip():
|
||||
continue
|
||||
chat_id = str(chat_id)
|
||||
friendly = friendly.strip()
|
||||
matched = False
|
||||
for e in entries:
|
||||
if e.get("id") == chat_id:
|
||||
if isinstance(e, dict) and e.get("id") == chat_id:
|
||||
e["name"] = friendly
|
||||
matched = True
|
||||
if not matched:
|
||||
|
|
|
|||
|
|
@ -510,6 +510,7 @@ _QUICK_STATE_FILES = (
|
|||
"cron/jobs.json",
|
||||
"gateway_state.json",
|
||||
"channel_directory.json",
|
||||
"channel_aliases.json",
|
||||
"processes.json",
|
||||
# Pairing stores (generic + per-platform JSONs outside state.db)
|
||||
"pairing", # legacy location (gateway/pairing.py)
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ AUTHOR_MAP = {
|
|||
"dirtyren@users.noreply.github.com": "dirtyren",
|
||||
"tharushkadinujaya05@gmail.com": "0xneobyte",
|
||||
"138671361+Veritas-7@users.noreply.github.com": "Veritas-7",
|
||||
"keiron@onehanded.com": "kmccammon",
|
||||
"895252509@qq.com": "895252509",
|
||||
"35259607+zxcasongs@users.noreply.github.com": "zxcasongs",
|
||||
"alfred@my-cloud.me": "alfred-smith-0",
|
||||
|
|
|
|||
|
|
@ -566,9 +566,13 @@ class TestChannelAliases:
|
|||
assert names == ["general"]
|
||||
|
||||
def test_apply_aliases_handles_malformed_map(self):
|
||||
"""Non-dict alias entries must not raise."""
|
||||
"""Non-dict alias maps and non-string aliases must not raise."""
|
||||
platforms = {"whatsapp": [{"id": "1@g.us", "name": "1", "type": "group"}]}
|
||||
with patch("gateway.channel_directory._load_channel_aliases",
|
||||
return_value={"whatsapp": "not-a-dict", "telegram": None}):
|
||||
return_value={
|
||||
"whatsapp": "not-a-dict",
|
||||
"telegram": None,
|
||||
"signal": {"+15551234567": 123},
|
||||
}):
|
||||
_apply_channel_aliases(platforms) # should not raise
|
||||
assert platforms["whatsapp"][0]["name"] == "1"
|
||||
|
|
|
|||
|
|
@ -1199,6 +1199,9 @@ class TestQuickSnapshot:
|
|||
(home / "config.yaml").write_text("model:\n provider: openrouter\n")
|
||||
(home / ".env").write_text("OPENROUTER_API_KEY=test-key-123\n")
|
||||
(home / "auth.json").write_text('{"providers": {}}\n')
|
||||
(home / "channel_aliases.json").write_text(
|
||||
'{"whatsapp": {"120363408391911677@g.us": "general"}}\n'
|
||||
)
|
||||
(home / "cron").mkdir()
|
||||
(home / "cron" / "jobs.json").write_text('{"jobs": []}\n')
|
||||
|
||||
|
|
@ -1241,6 +1244,13 @@ class TestQuickSnapshot:
|
|||
snap_id = create_quick_snapshot(hermes_home=hermes_home)
|
||||
assert (hermes_home / "state-snapshots" / snap_id / "cron" / "jobs.json").exists()
|
||||
|
||||
def test_copies_channel_aliases(self, hermes_home):
|
||||
from hermes_cli.backup import create_quick_snapshot
|
||||
snap_id = create_quick_snapshot(hermes_home=hermes_home)
|
||||
copied = hermes_home / "state-snapshots" / snap_id / "channel_aliases.json"
|
||||
assert copied.exists()
|
||||
assert "120363408391911677@g.us" in copied.read_text()
|
||||
|
||||
def test_missing_files_skipped(self, hermes_home):
|
||||
from hermes_cli.backup import create_quick_snapshot
|
||||
snap_id = create_quick_snapshot(hermes_home=hermes_home)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,20 @@
|
|||
"""Parser-only tests for send_message targets.
|
||||
"""Parser-only and lightweight routing tests for send_message targets.
|
||||
|
||||
These stay separate from ``test_send_message_tool.py`` because that module
|
||||
skips wholesale when optional Telegram dependencies are not installed.
|
||||
"""
|
||||
|
||||
from tools.send_message_tool import _parse_target_ref
|
||||
import asyncio
|
||||
import json
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from gateway.config import Platform
|
||||
from tools.send_message_tool import _parse_target_ref, send_message_tool
|
||||
|
||||
|
||||
def _run_async_immediately(coro):
|
||||
return asyncio.run(coro)
|
||||
|
||||
|
||||
def test_photon_e164_target_is_explicit() -> None:
|
||||
|
|
@ -18,3 +28,65 @@ def test_photon_e164_target_is_explicit() -> None:
|
|||
def test_e164_target_still_requires_phone_platform() -> None:
|
||||
assert _parse_target_ref("matrix", "+15551234567")[2] is False
|
||||
|
||||
|
||||
def test_whatsapp_group_jid_target_is_explicit() -> None:
|
||||
chat_id, thread_id, is_explicit = _parse_target_ref(
|
||||
"whatsapp", "120363408391911677@g.us"
|
||||
)
|
||||
|
||||
assert chat_id == "120363408391911677@g.us"
|
||||
assert thread_id is None
|
||||
assert is_explicit is True
|
||||
|
||||
|
||||
def test_whatsapp_native_jids_are_explicit() -> None:
|
||||
assert _parse_target_ref("whatsapp", "19255551234@s.whatsapp.net")[2] is True
|
||||
assert _parse_target_ref("whatsapp", "149606612619433@lid")[2] is True
|
||||
assert _parse_target_ref("whatsapp", "status@broadcast")[2] is True
|
||||
assert _parse_target_ref("whatsapp", "120363000000000000@newsletter")[2] is True
|
||||
|
||||
|
||||
def test_whatsapp_jid_suffix_only_matches_whatsapp() -> None:
|
||||
assert _parse_target_ref("telegram", "120363408391911677@g.us")[2] is False
|
||||
assert _parse_target_ref("signal", "149606612619433@lid")[2] is False
|
||||
|
||||
|
||||
def test_whatsapp_friendly_name_still_uses_directory_resolution() -> None:
|
||||
assert _parse_target_ref("whatsapp", "general")[2] is False
|
||||
|
||||
|
||||
def test_send_message_routes_whatsapp_group_jid_without_home_fallback() -> None:
|
||||
whatsapp_cfg = SimpleNamespace(enabled=True, token=None, extra={"api_url": "http://bridge"})
|
||||
config = SimpleNamespace(
|
||||
platforms={Platform.WHATSAPP: whatsapp_cfg},
|
||||
get_home_channel=lambda _platform: SimpleNamespace(chat_id="15551234567@s.whatsapp.net"),
|
||||
)
|
||||
|
||||
with patch("gateway.config.load_gateway_config", return_value=config), \
|
||||
patch("tools.interrupt.is_interrupted", return_value=False), \
|
||||
patch("gateway.channel_directory.resolve_channel_name", side_effect=AssertionError("raw JID should not resolve via directory")), \
|
||||
patch("model_tools._run_async", side_effect=_run_async_immediately), \
|
||||
patch("tools.send_message_tool._send_to_platform", new=AsyncMock(return_value={"success": True})) as send_mock, \
|
||||
patch("gateway.mirror.mirror_to_session", return_value=True):
|
||||
result = json.loads(
|
||||
send_message_tool(
|
||||
{
|
||||
"action": "send",
|
||||
"target": "whatsapp:120363408391911677@g.us",
|
||||
"message": "hello group",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
assert "note" not in result
|
||||
send_mock.assert_awaited_once_with(
|
||||
Platform.WHATSAPP,
|
||||
whatsapp_cfg,
|
||||
"120363408391911677@g.us",
|
||||
"hello group",
|
||||
thread_id=None,
|
||||
media_files=[],
|
||||
force_document=False,
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue