"""Slash command handler for direct bot control.""" from __future__ import annotations import argparse import json import logging import re import uuid from typing import Optional, Tuple from agent.manager import manager from orchestrator.agent import agent from orchestrator.tools import set_current_user, get_current_user logger = logging.getLogger(__name__) def parse_command(text: str) -> Optional[Tuple[str, str]]: """ Parse a slash command from text. Returns (command, args) or None if not a command. """ text = text.strip() if not text.startswith("/"): return None parts = text.split(None, 1) cmd = parts[0].lower() args = parts[1] if len(parts) > 1 else "" return (cmd, args) async def handle_command(user_id: str, text: str) -> Optional[str]: """ Handle a slash command. Returns the reply or None if not a command. """ parsed = parse_command(text) if not parsed: return None cmd, args = parsed logger.info("Command: %s args=%r user=...%s", cmd, args[:50], user_id[-8:]) set_current_user(user_id) if cmd in ("/new", "/n"): return await _cmd_new(user_id, args) elif cmd in ("/list", "/ls", "/l"): return await _cmd_list(user_id) elif cmd in ("/close", "/c"): return await _cmd_close(user_id, args) elif cmd in ("/switch", "/s"): return await _cmd_switch(user_id, args) elif cmd == "/retry": return await _cmd_retry(user_id) elif cmd in ("/help", "/h", "/?"): return _cmd_help() else: return None async def _cmd_new(user_id: str, args: str) -> str: """Create a new session.""" if not args: return "Usage: /new [initial_message] [--timeout N]\nExample: /new todo_app fix the bug --timeout 600" parser = argparse.ArgumentParser() parser.add_argument("working_dir", nargs="?", help="Project directory") parser.add_argument("rest", nargs="*", help="Initial message") parser.add_argument("--timeout", type=int, default=None, help="CC timeout in seconds") parser.add_argument("--idle", type=int, default=None, help="Idle timeout in seconds") try: parsed = parser.parse_args(args.split()) except SystemExit: return "Usage: /new [initial_message] [--timeout N] [--idle N]" if not parsed.working_dir: return "Error: project_dir is required" working_dir = parsed.working_dir initial_msg = " ".join(parsed.rest) if parsed.rest else None from orchestrator.tools import CreateConversationTool tool = CreateConversationTool() result = await tool._arun( working_dir=working_dir, initial_message=initial_msg, cc_timeout=parsed.timeout, idle_timeout=parsed.idle, ) try: data = json.loads(result) if "error" in data: return f"Error: {data['error']}" conv_id = data.get("conv_id", "") agent._active_conv[user_id] = conv_id cwd = data.get("working_dir", working_dir) reply = f"✓ Created session `{conv_id}` in `{cwd}`" if parsed.timeout: reply += f" (timeout: {parsed.timeout}s)" if initial_msg: reply += f"\n\nSent: {initial_msg[:100]}..." return reply except Exception: return result async def _cmd_list(user_id: str) -> str: """List all sessions for this user.""" sessions = manager.list_sessions(user_id=user_id) if not sessions: return "No active sessions." active = agent.get_active_conv(user_id) lines = ["**Your Sessions:**\n"] for i, s in enumerate(sessions, 1): marker = "→ " if s["conv_id"] == active else " " lines.append(f"{marker}{i}. `{s['conv_id']}` - `{s['cwd']}`") lines.append("\nUse `/switch ` to activate a session.") return "\n".join(lines) async def _cmd_close(user_id: str, args: str) -> str: """Close a session.""" sessions = manager.list_sessions(user_id=user_id) if not sessions: return "No sessions to close." if args: try: idx = int(args) - 1 if 0 <= idx < len(sessions): conv_id = sessions[idx]["conv_id"] else: return f"Invalid session number. Use 1-{len(sessions)}." except ValueError: conv_id = args.strip() else: conv_id = agent.get_active_conv(user_id) if not conv_id: return "No active session. Use `/close ` or `/close `." try: success = await manager.close(conv_id, user_id=user_id) if success: if agent.get_active_conv(user_id) == conv_id: agent._active_conv[user_id] = None return f"✓ Closed session `{conv_id}`" else: return f"Session `{conv_id}` not found." except PermissionError as e: return str(e) async def _cmd_switch(user_id: str, args: str) -> str: """Switch to a different session.""" sessions = manager.list_sessions(user_id=user_id) if not sessions: return "No sessions available." if not args: return "Usage: /switch \n" + await _cmd_list(user_id) try: idx = int(args) - 1 if 0 <= idx < len(sessions): conv_id = sessions[idx]["conv_id"] agent._active_conv[user_id] = conv_id return f"✓ Switched to session `{conv_id}` ({sessions[idx]['cwd']})" else: return f"Invalid session number. Use 1-{len(sessions)}." except ValueError: return f"Invalid number: {args}" async def _cmd_retry(user_id: str) -> str: """Retry the last message (placeholder - needs history tracking).""" return "Retry not yet implemented. Just send your message again." def _cmd_help() -> str: """Show help.""" return """**Commands:** /new [msg] [--timeout N] [--idle N] - Create session /list - List your sessions /close [n] - Close session (active or by number) /switch - Switch to session by number /retry - Retry last message /help - Show this help"""