Team Ai
Apppublic

Yash030/claude-code-proxy

sourceHugging Faceupdated 5mo agoView on Hugging Face
2likes
commands.py276 linesDownload Raw Back to messaging
1"""Command handlers for messaging platform commands (/stop, /stats, /clear).2 3Extracted from ClaudeMessageHandler to keep handler.py focused on4core message processing logic.5"""6 7from __future__ import annotations8 9from typing import TYPE_CHECKING10 11from loguru import logger12 13if TYPE_CHECKING:14    from messaging.handler import ClaudeMessageHandler15    from messaging.models import IncomingMessage16 17 18async def handle_stop_command(19    handler: ClaudeMessageHandler, incoming: IncomingMessage20) -> None:21    """Handle /stop command from messaging platform."""22    # Reply-scoped stop: reply "/stop" to stop only that task.23    if incoming.is_reply() and incoming.reply_to_message_id:24        reply_id = incoming.reply_to_message_id25        tree = handler.tree_queue.get_tree_for_node(reply_id)26        node_id = handler.tree_queue.resolve_parent_node_id(reply_id) if tree else None27 28        if not node_id:29            msg_id = await handler.platform.queue_send_message(30                incoming.chat_id,31                handler.format_status(32                    "โน", "Stopped.", "Nothing to stop for that message."33                ),34                fire_and_forget=False,35                message_thread_id=incoming.message_thread_id,36            )37            handler.record_outgoing_message(38                incoming.platform, incoming.chat_id, msg_id, "command"39            )40            return41 42        count = await handler.stop_task(node_id)43        noun = "request" if count == 1 else "requests"44        msg_id = await handler.platform.queue_send_message(45            incoming.chat_id,46            handler.format_status("โน", "Stopped.", f"Cancelled {count} {noun}."),47            fire_and_forget=False,48            message_thread_id=incoming.message_thread_id,49        )50        handler.record_outgoing_message(51            incoming.platform, incoming.chat_id, msg_id, "command"52        )53        return54 55    # Global stop: legacy behavior (stop everything)56    count = await handler.stop_all_tasks()57    msg_id = await handler.platform.queue_send_message(58        incoming.chat_id,59        handler.format_status(60            "โน", "Stopped.", f"Cancelled {count} pending or active requests."61        ),62        fire_and_forget=False,63        message_thread_id=incoming.message_thread_id,64    )65    handler.record_outgoing_message(66        incoming.platform, incoming.chat_id, msg_id, "command"67    )68 69 70async def handle_stats_command(71    handler: ClaudeMessageHandler, incoming: IncomingMessage72) -> None:73    """Handle /stats command."""74    stats = handler.cli_manager.get_stats()75    tree_count = handler.tree_queue.get_tree_count()76    ctx = handler.get_render_ctx()77    msg_id = await handler.platform.queue_send_message(78        incoming.chat_id,79        "๐Ÿ“Š "80        + ctx.bold("Stats")81        + "\n"82        + ctx.escape_text(f"โ€ข Active CLI: {stats['active_sessions']}")83        + "\n"84        + ctx.escape_text(f"โ€ข Message Trees: {tree_count}"),85        fire_and_forget=False,86        message_thread_id=incoming.message_thread_id,87    )88    handler.record_outgoing_message(89        incoming.platform, incoming.chat_id, msg_id, "command"90    )91 92 93async def _delete_message_ids(94    handler: ClaudeMessageHandler, chat_id: str, msg_ids: set[str]95) -> None:96    """Best-effort delete messages by ID. Sorts numeric IDs descending."""97    if not msg_ids:98        return99 100    def _as_int(s: str) -> int | None:101        try:102            return int(str(s))103        except Exception:104            return None105 106    numeric: list[tuple[int, str]] = []107    non_numeric: list[str] = []108    for mid in msg_ids:109        n = _as_int(mid)110        if n is None:111            non_numeric.append(mid)112        else:113            numeric.append((n, mid))114    numeric.sort(reverse=True)115    ordered = [mid for _, mid in numeric] + non_numeric116 117    try:118        CHUNK = 100119        for i in range(0, len(ordered), CHUNK):120            chunk = ordered[i : i + CHUNK]121            await handler.platform.queue_delete_messages(122                chat_id, chunk, fire_and_forget=False123            )124    except Exception as e:125        logger.debug(f"Batch delete failed: {type(e).__name__}: {e}")126 127 128async def _handle_clear_branch(129    handler: ClaudeMessageHandler,130    incoming: IncomingMessage,131    branch_root_id: str,132) -> None:133    """134    Clear a branch (replied-to node + all descendants).135 136    Order: cancel tasks, delete messages, remove branch, update session store.137    """138    tree = handler.tree_queue.get_tree_for_node(branch_root_id)139    if not tree:140        return141 142    # 1) Cancel branch tasks (no stop_all)143    cancelled = await handler.tree_queue.cancel_branch(branch_root_id)144    handler.update_cancelled_nodes_ui(cancelled)145 146    # 2) Collect message IDs from branch nodes only147    msg_ids: set[str] = set()148    branch_ids = tree.get_descendants(branch_root_id)149    for nid in branch_ids:150        node = tree.get_node(nid)151        if node:152            if node.incoming.message_id:153                msg_ids.add(str(node.incoming.message_id))154            if node.status_message_id:155                msg_ids.add(str(node.status_message_id))156    if incoming.message_id:157        msg_ids.add(str(incoming.message_id))158 159    # 3) Delete messages (best-effort)160    await _delete_message_ids(handler, incoming.chat_id, msg_ids)161 162    # 4) Remove branch from tree163    removed, root_id, removed_entire_tree = await handler.tree_queue.remove_branch(164        branch_root_id165    )166 167    # 5) Update session store168    try:169        handler.session_store.remove_node_mappings([n.node_id for n in removed])170        if removed_entire_tree:171            handler.session_store.remove_tree(root_id)172        else:173            updated_tree = handler.tree_queue.get_tree(root_id)174            if updated_tree:175                handler.session_store.save_tree(root_id, updated_tree.to_dict())176    except Exception as e:177        logger.warning(f"Failed to update session store after branch clear: {e}")178 179 180async def handle_clear_command(181    handler: ClaudeMessageHandler, incoming: IncomingMessage182) -> None:183    """184    Handle /clear command.185 186    Reply-scoped: reply to a message to clear that branch (node + descendants).187    Standalone: global clear (stop all, delete all chat messages, reset store).188    """189    from messaging.trees import TreeQueueManager190 191    if incoming.is_reply() and incoming.reply_to_message_id:192        reply_id = incoming.reply_to_message_id193        tree = handler.tree_queue.get_tree_for_node(reply_id)194        branch_root_id = (195            handler.tree_queue.resolve_parent_node_id(reply_id) if tree else None196        )197        if not branch_root_id:198            cancel_fn = getattr(handler.platform, "cancel_pending_voice", None)199            if cancel_fn is not None:200                cancelled = await cancel_fn(incoming.chat_id, reply_id)201                if cancelled is not None:202                    voice_msg_id, status_msg_id = cancelled203                    msg_ids_to_del: set[str] = {voice_msg_id, status_msg_id}204                    if incoming.message_id is not None:205                        msg_ids_to_del.add(str(incoming.message_id))206                    await _delete_message_ids(handler, incoming.chat_id, msg_ids_to_del)207                    msg_id = await handler.platform.queue_send_message(208                        incoming.chat_id,209                        handler.format_status("๐Ÿ—‘", "Cleared.", "Voice note cancelled."),210                        fire_and_forget=False,211                        message_thread_id=incoming.message_thread_id,212                    )213                    handler.record_outgoing_message(214                        incoming.platform, incoming.chat_id, msg_id, "command"215                    )216                    return217            msg_id = await handler.platform.queue_send_message(218                incoming.chat_id,219                handler.format_status(220                    "๐Ÿ—‘", "Cleared.", "Nothing to clear for that message."221                ),222                fire_and_forget=False,223                message_thread_id=incoming.message_thread_id,224            )225            handler.record_outgoing_message(226                incoming.platform, incoming.chat_id, msg_id, "command"227            )228            return229        await _handle_clear_branch(handler, incoming, branch_root_id)230        return231 232    # Global clear233    # 1) Stop tasks first (ensures no more work is running).234    await handler.stop_all_tasks()235 236    # 2) Clear chat: best-effort delete messages we can identify.237    msg_ids: set[str] = set()238 239    # Add any recorded message IDs for this chat (commands, command replies, etc).240    try:241        for mid in handler.session_store.get_message_ids_for_chat(242            incoming.platform, incoming.chat_id243        ):244            if mid is not None:245                msg_ids.add(str(mid))246    except Exception as e:247        logger.debug(f"Failed to read message log for /clear: {e}")248 249    try:250        msg_ids.update(251            handler.tree_queue.get_message_ids_for_chat(252                incoming.platform, incoming.chat_id253            )254        )255    except Exception as e:256        logger.warning(f"Failed to gather messages for /clear: {e}")257 258    # Also delete the command message itself.259    if incoming.message_id is not None:260        msg_ids.add(str(incoming.message_id))261 262    await _delete_message_ids(handler, incoming.chat_id, msg_ids)263 264    # 3) Clear persistent state and reset in-memory queue/tree state.265    try:266        handler.session_store.clear_all()267    except Exception as e:268        logger.warning(f"Failed to clear session store: {e}")269 270    handler.replace_tree_queue(271        TreeQueueManager(272            queue_update_callback=handler.update_queue_positions,273            node_started_callback=handler.mark_node_processing,274        )275    )276