Spaces:
Sleeping
Sleeping
| """Command handlers for messaging platform commands (/stop, /stats, /clear). | |
| Commands depend on MessagingCommandContext instead of the concrete workflow. | |
| """ | |
| from loguru import logger | |
| from .command_context import MessagingCommandContext | |
| from .models import IncomingMessage | |
| async def _send_stop_feedback( | |
| handler: MessagingCommandContext, | |
| incoming: IncomingMessage, | |
| suffix: str, | |
| ) -> None: | |
| """Send stop feedback only when no existing status can represent the result.""" | |
| msg_id = await handler.outbound.queue_send_message( | |
| incoming.chat_id, | |
| handler.format_status("⏹", "Stopped.", suffix), | |
| fire_and_forget=False, | |
| message_thread_id=incoming.message_thread_id, | |
| ) | |
| handler.record_outgoing_message( | |
| incoming.platform, incoming.chat_id, msg_id, "command" | |
| ) | |
| async def handle_stop_command( | |
| handler: MessagingCommandContext, incoming: IncomingMessage | |
| ) -> None: | |
| """Handle /stop command from messaging platform.""" | |
| # Reply-scoped stop: reply "/stop" to stop only that task. | |
| if incoming.is_reply() and incoming.reply_to_message_id: | |
| outcome = await handler.stop_reply( | |
| incoming.scope, | |
| incoming.reply_to_message_id, | |
| ) | |
| if outcome.cancelled_count == 0: | |
| await _send_stop_feedback( | |
| handler, | |
| incoming, | |
| "Nothing to stop for that message.", | |
| ) | |
| return | |
| if outcome.requires_confirmation(incoming.scope): | |
| noun = "request" if outcome.cancelled_count == 1 else "requests" | |
| await _send_stop_feedback( | |
| handler, | |
| incoming, | |
| f"Cancelled {outcome.cancelled_count} {noun}.", | |
| ) | |
| return | |
| # Global stop: legacy behavior (stop everything) | |
| outcome = await handler.stop_all_tasks() | |
| if outcome.cancelled_count == 0: | |
| await _send_stop_feedback(handler, incoming, "Nothing to stop.") | |
| elif outcome.requires_confirmation(incoming.scope): | |
| noun = "request" if outcome.cancelled_count == 1 else "requests" | |
| await _send_stop_feedback( | |
| handler, | |
| incoming, | |
| f"Cancelled {outcome.cancelled_count} pending or active {noun}.", | |
| ) | |
| async def handle_stats_command( | |
| handler: MessagingCommandContext, incoming: IncomingMessage | |
| ) -> None: | |
| """Handle /stats command.""" | |
| stats = handler.cli_manager.get_stats() | |
| tree_count = handler.get_tree_count() | |
| ctx = handler.get_render_ctx() | |
| msg_id = await handler.outbound.queue_send_message( | |
| incoming.chat_id, | |
| "📊 " | |
| + ctx.bold("Stats") | |
| + "\n" | |
| + ctx.escape_text(f"• Active CLI: {stats['active_sessions']}") | |
| + "\n" | |
| + ctx.escape_text(f"• Message Trees: {tree_count}"), | |
| fire_and_forget=False, | |
| message_thread_id=incoming.message_thread_id, | |
| ) | |
| handler.record_outgoing_message( | |
| incoming.platform, incoming.chat_id, msg_id, "command" | |
| ) | |
| async def _delete_message_ids( | |
| handler: MessagingCommandContext, chat_id: str, msg_ids: set[str] | |
| ) -> None: | |
| """Best-effort delete messages by ID. Sorts numeric IDs descending.""" | |
| if not msg_ids: | |
| return | |
| def _as_int(s: str) -> int | None: | |
| try: | |
| return int(str(s)) | |
| except Exception: | |
| return None | |
| numeric: list[tuple[int, str]] = [] | |
| non_numeric: list[str] = [] | |
| for mid in msg_ids: | |
| n = _as_int(mid) | |
| if n is None: | |
| non_numeric.append(mid) | |
| else: | |
| numeric.append((n, mid)) | |
| numeric.sort(reverse=True) | |
| non_numeric.sort(reverse=True) | |
| ordered = [mid for _, mid in numeric] + non_numeric | |
| failed = 0 | |
| try: | |
| await handler.outbound.queue_delete_messages( | |
| chat_id, | |
| ordered, | |
| fire_and_forget=False, | |
| ) | |
| except Exception as e: | |
| failed = len(ordered) | |
| logger.debug("Message delete failed for chat {}: {}", chat_id, type(e).__name__) | |
| if ordered: | |
| logger.info( | |
| "Clear delete attempted={} failed={}", | |
| len(ordered), | |
| failed, | |
| ) | |
| async def handle_clear_command( | |
| handler: MessagingCommandContext, incoming: IncomingMessage | |
| ) -> None: | |
| """ | |
| Handle /clear command. | |
| Reply-scoped: delete the selected message and its literal reply subtree. | |
| Standalone: reset and delete the invoking chat's managed conversation. | |
| """ | |
| if incoming.is_reply() and incoming.reply_to_message_id: | |
| result = await handler.clear_reply( | |
| incoming.scope, | |
| incoming.reply_to_message_id, | |
| ) | |
| if result is None: | |
| msg_id = await handler.outbound.queue_send_message( | |
| incoming.chat_id, | |
| handler.format_status( | |
| "🗑", "Cleared.", "Nothing to clear for that message." | |
| ), | |
| fire_and_forget=False, | |
| message_thread_id=incoming.message_thread_id, | |
| ) | |
| handler.record_outgoing_message( | |
| incoming.platform, incoming.chat_id, msg_id, "command" | |
| ) | |
| return | |
| delete_message_ids = set(result.delete_message_ids) | |
| if incoming.message_id is not None: | |
| delete_message_ids.add(str(incoming.message_id)) | |
| await _delete_message_ids(handler, incoming.chat_id, delete_message_ids) | |
| handler.forget_tracked_message_ids( | |
| incoming.platform, | |
| incoming.chat_id, | |
| delete_message_ids, | |
| ) | |
| return | |
| msg_ids = set(await handler.clear_chat(incoming.platform, incoming.chat_id)) | |
| # Also delete the command message itself. | |
| if incoming.message_id is not None: | |
| msg_ids.add(str(incoming.message_id)) | |
| await _delete_message_ids(handler, incoming.chat_id, msg_ids) | |