diff --git a/tests/tools/test_send_message_tool.py b/tests/tools/test_send_message_tool.py index e1b6a21c6..10a486865 100644 --- a/tests/tools/test_send_message_tool.py +++ b/tests/tools/test_send_message_tool.py @@ -1256,6 +1256,54 @@ class TestParseTargetRefEmail: assert _parse_target_ref("slack", "user@example.com")[2] is False +class TestEmailHomeChannelErrorHint: + """The no-home-channel error for email points at the real env var. + + Email reads its home channel from EMAIL_HOME_ADDRESS (gateway/config.py), + not the generic EMAIL_HOME_CHANNEL. The error guidance must name the + variable that is actually consulted so users who follow it succeed. + """ + + def test_email_error_names_email_home_address(self): + email_cfg = SimpleNamespace(enabled=True, token="", extra={}) + config = SimpleNamespace( + platforms={Platform.EMAIL: email_cfg}, + get_home_channel=lambda _platform: None, + ) + with patch("gateway.config.load_gateway_config", return_value=config), \ + patch("tools.interrupt.is_interrupted", return_value=False): + result = json.loads( + send_message_tool( + { + "action": "send", + "target": "email", + "message": "hi", + } + ) + ) + assert "EMAIL_HOME_ADDRESS" in result["error"] + assert "EMAIL_HOME_CHANNEL" not in result["error"] + + def test_non_email_platform_keeps_generic_home_channel_hint(self): + telegram_cfg = SimpleNamespace(enabled=True, token="***", extra={}) + config = SimpleNamespace( + platforms={Platform.TELEGRAM: telegram_cfg}, + get_home_channel=lambda _platform: None, + ) + with patch("gateway.config.load_gateway_config", return_value=config), \ + patch("tools.interrupt.is_interrupted", return_value=False): + result = json.loads( + send_message_tool( + { + "action": "send", + "target": "telegram", + "message": "hi", + } + ) + ) + assert "TELEGRAM_HOME_CHANNEL" in result["error"] + + class TestSendDiscordThreadId: """_send_discord uses thread_id when provided.""" diff --git a/tools/send_message_tool.py b/tools/send_message_tool.py index 13689dc1d..88bcb4005 100644 --- a/tools/send_message_tool.py +++ b/tools/send_message_tool.py @@ -44,6 +44,11 @@ _E164_TARGET_RE = re.compile(r"^\s*\+(\d{7,15})\s*$") # an explicit target for the email platform, not fall through to channel-name # resolution which has no way to resolve a raw address. _EMAIL_TARGET_RE = re.compile(r"^\s*[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\s*$") +# Most platforms read their home channel from "_HOME_CHANNEL", but a +# few diverge. Email reads EMAIL_HOME_ADDRESS (see gateway/config.py), so the +# generic "_HOME_CHANNEL" hint would point users at a variable that is +# never read. Map the exceptions so the error guidance is actually actionable. +_HOME_CHANNEL_ENV_OVERRIDES = {"email": "EMAIL_HOME_ADDRESS"} _IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".gif"} _VIDEO_EXTS = {".mp4", ".mov", ".avi", ".mkv", ".3gp"} _AUDIO_EXTS = {".ogg", ".opus", ".mp3", ".wav", ".m4a", ".flac"} @@ -269,10 +274,13 @@ def _handle_send(args): chat_id = home.chat_id used_home_channel = True else: + home_env = _HOME_CHANNEL_ENV_OVERRIDES.get( + platform_name, f"{platform_name.upper()}_HOME_CHANNEL" + ) return json.dumps({ "error": f"No home channel set for {platform_name} to determine where to send the message. " f"Either specify a channel directly with '{platform_name}:CHANNEL_NAME', " - f"or set a home channel via: hermes config set {platform_name.upper()}_HOME_CHANNEL " + f"or set a home channel via: hermes config set {home_env} " }) duplicate_skip = _maybe_skip_cron_duplicate_send(platform_name, chat_id, thread_id)