fix(command): expose history in chat command menus

Made-with: Cursor
This commit is contained in:
Xubin Ren 2026-04-27 10:22:06 +00:00 committed by Xubin Ren
parent 8ed10ac7df
commit 2b886ffd1f
5 changed files with 47 additions and 2 deletions

View File

@ -195,6 +195,7 @@ if DISCORD_AVAILABLE:
("stop", "Stop the current task", "/stop"), ("stop", "Stop the current task", "/stop"),
("restart", "Restart the bot", "/restart"), ("restart", "Restart the bot", "/restart"),
("status", "Show bot status", "/status"), ("status", "Show bot status", "/status"),
("history", "Show recent conversation messages", "/history"),
) )
for name, description, command_text in commands: for name, description, command_text in commands:

View File

@ -12,7 +12,14 @@ from typing import Any, Literal
from loguru import logger from loguru import logger
from pydantic import Field from pydantic import Field
from telegram import BotCommand, InlineKeyboardButton, InlineKeyboardMarkup, ReactionTypeEmoji, ReplyParameters, Update from telegram import (
BotCommand,
InlineKeyboardButton,
InlineKeyboardMarkup,
ReactionTypeEmoji,
ReplyParameters,
Update,
)
from telegram.error import BadRequest, NetworkError, TimedOut from telegram.error import BadRequest, NetworkError, TimedOut
from telegram.ext import Application, CallbackQueryHandler, ContextTypes, MessageHandler, filters from telegram.ext import Application, CallbackQueryHandler, ContextTypes, MessageHandler, filters
from telegram.request import HTTPXRequest from telegram.request import HTTPXRequest
@ -253,6 +260,7 @@ class TelegramChannel(BaseChannel):
BotCommand("stop", "Stop the current task"), BotCommand("stop", "Stop the current task"),
BotCommand("restart", "Restart the bot"), BotCommand("restart", "Restart the bot"),
BotCommand("status", "Show bot status"), BotCommand("status", "Show bot status"),
BotCommand("history", "Show recent conversation messages"),
BotCommand("dream", "Run Dream memory consolidation now"), BotCommand("dream", "Run Dream memory consolidation now"),
BotCommand("dream_log", "Show the latest Dream memory change"), BotCommand("dream_log", "Show the latest Dream memory change"),
BotCommand("dream_restore", "Restore Dream memory to an earlier version"), BotCommand("dream_restore", "Restore Dream memory to an earlier version"),

View File

@ -865,7 +865,7 @@ async def test_slash_new_is_blocked_for_disallowed_user() -> None:
assert handled == [] assert handled == []
@pytest.mark.parametrize("slash_name", ["stop", "restart", "status"]) @pytest.mark.parametrize("slash_name", ["stop", "restart", "status", "history"])
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_slash_commands_forward_via_handle_message(slash_name: str) -> None: async def test_slash_commands_forward_via_handle_message(slash_name: str) -> None:
channel = DiscordChannel(DiscordConfig(enabled=True, allow_from=["*"]), MessageBus()) channel = DiscordChannel(DiscordConfig(enabled=True, allow_from=["*"]), MessageBus())

View File

@ -193,6 +193,7 @@ async def test_start_creates_separate_pools_with_proxy(monkeypatch) -> None:
assert builder.get_updates_request_value is poll_req assert builder.get_updates_request_value is poll_req
assert callable(app.updater.start_polling_kwargs["error_callback"]) assert callable(app.updater.start_polling_kwargs["error_callback"])
assert any(cmd.command == "status" for cmd in app.bot.commands) assert any(cmd.command == "status" for cmd in app.bot.commands)
assert any(cmd.command == "history" for cmd in app.bot.commands)
assert any(cmd.command == "dream" 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_log" for cmd in app.bot.commands)
assert any(cmd.command == "dream_restore" for cmd in app.bot.commands) assert any(cmd.command == "dream_restore" for cmd in app.bot.commands)

View File

@ -282,6 +282,41 @@ class TestRestartCommand:
assert "message 19" in response.content # most recent assert "message 19" in response.content # most recent
assert "message 0" not in response.content # too old assert "message 0" not in response.content # too old
@pytest.mark.asyncio
async def test_history_clamps_count_and_extracts_text_blocks(self):
loop, _bus = _make_loop()
session = MagicMock()
session.get_history.return_value = [
{
"role": "user",
"content": [
{"type": "text", "text": "visible text"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}},
],
},
*({"role": "assistant", "content": f"reply {i}"} for i in range(60)),
]
loop.sessions.get_or_create.return_value = session
msg = InboundMessage(channel="telegram", sender_id="u1", chat_id="c1", content="/history 999")
response = await loop._process_message(msg)
assert response is not None
assert "Last 50 message(s)" in response.content
assert "visible text" not in response.content
assert "reply 59" in response.content
assert "reply 9" not in response.content
@pytest.mark.asyncio
async def test_history_invalid_count_returns_usage(self):
loop, _bus = _make_loop()
msg = InboundMessage(channel="telegram", sender_id="u1", chat_id="c1", content="/history nope")
response = await loop._process_message(msg)
assert response is not None
assert response.content.startswith("Usage: /history [count]")
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_history_empty_session(self): async def test_history_empty_session(self):
loop, _bus = _make_loop() loop, _bus = _make_loop()