fix(telegram): register Dream menu commands with Telegram-safe aliases

Use dream_log and dream_restore in Telegram's bot command menu so command registration succeeds, while still accepting the original dream-log and dream-restore forms in chat. Keep the internal command routing unchanged and add coverage for the alias normalization path.
This commit is contained in:
Xubin Ren 2026-04-04 10:31:26 +00:00
parent 549e5ea8e2
commit 7b852506ff
2 changed files with 38 additions and 5 deletions

View File

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

View File

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