diff --git a/nanobot/channels/telegram.py b/nanobot/channels/telegram.py index f6abb056a..aaabd6468 100644 --- a/nanobot/channels/telegram.py +++ b/nanobot/channels/telegram.py @@ -200,8 +200,8 @@ class TelegramChannel(BaseChannel): BotCommand("restart", "Restart the bot"), BotCommand("status", "Show bot status"), BotCommand("dream", "Run Dream memory consolidation now"), - BotCommand("dream-log", "Show the latest Dream memory change"), - BotCommand("dream-restore", "Restore Dream memory to an earlier version"), + BotCommand("dream_log", "Show the latest Dream memory change"), + BotCommand("dream_restore", "Restore Dream memory to an earlier version"), BotCommand("help", "Show available commands"), ] @@ -245,6 +245,17 @@ class TelegramChannel(BaseChannel): return sid in allow_list or username in allow_list + @staticmethod + def _normalize_telegram_command(content: str) -> str: + """Map Telegram-safe command aliases back to canonical nanobot commands.""" + if not content.startswith("/"): + return content + if content == "/dream_log" or content.startswith("/dream_log "): + return content.replace("/dream_log", "/dream-log", 1) + if content == "/dream_restore" or content.startswith("/dream_restore "): + return content.replace("/dream_restore", "/dream-restore", 1) + return content + async def start(self) -> None: """Start the Telegram bot with long polling.""" if not self.config.token: @@ -289,7 +300,7 @@ class TelegramChannel(BaseChannel): ) self._app.add_handler( MessageHandler( - filters.Regex(r"^/(dream-log|dream-restore)(?:@\w+)?(?:\s+.*)?$"), + filters.Regex(r"^/(dream-log|dream_log|dream-restore|dream_restore)(?:@\w+)?(?:\s+.*)?$"), self._forward_command, ) ) @@ -812,6 +823,7 @@ class TelegramChannel(BaseChannel): cmd_part, *rest = content.split(" ", 1) cmd_part = cmd_part.split("@")[0] content = f"{cmd_part} {rest[0]}" if rest else cmd_part + content = self._normalize_telegram_command(content) await self._handle_message( sender_id=self._sender_id(user), diff --git a/tests/channels/test_telegram_channel.py b/tests/channels/test_telegram_channel.py index 21ceb5f63..9584ad547 100644 --- a/tests/channels/test_telegram_channel.py +++ b/tests/channels/test_telegram_channel.py @@ -189,8 +189,8 @@ async def test_start_creates_separate_pools_with_proxy(monkeypatch) -> None: assert callable(app.updater.start_polling_kwargs["error_callback"]) assert any(cmd.command == "status" for cmd in app.bot.commands) assert any(cmd.command == "dream" for cmd in app.bot.commands) - assert any(cmd.command == "dream-log" for cmd in app.bot.commands) - assert any(cmd.command == "dream-restore" for cmd in app.bot.commands) + assert any(cmd.command == "dream_log" for cmd in app.bot.commands) + assert any(cmd.command == "dream_restore" for cmd in app.bot.commands) @pytest.mark.asyncio @@ -1009,6 +1009,27 @@ async def test_forward_command_preserves_dream_log_args_and_strips_bot_suffix() assert handled[0]["content"] == "/dream-log deadbeef" +@pytest.mark.asyncio +async def test_forward_command_normalizes_telegram_safe_dream_aliases() -> None: + channel = TelegramChannel( + TelegramConfig(enabled=True, token="123:abc", allow_from=["*"], group_policy="open"), + MessageBus(), + ) + channel._app = _FakeApp(lambda: None) + handled = [] + + async def capture_handle(**kwargs) -> None: + handled.append(kwargs) + + channel._handle_message = capture_handle + update = _make_telegram_update(text="/dream_restore@nanobot_test deadbeef", reply_to_message=None) + + await channel._forward_command(update, None) + + assert len(handled) == 1 + assert handled[0]["content"] == "/dream-restore deadbeef" + + @pytest.mark.asyncio async def test_on_help_includes_restart_command() -> None: channel = TelegramChannel(