PhoneWork/bot/commands.py
Yuyao Huang (Sam) de6205d2fd feat(命令): 添加直接模式和智能模式切换功能
- 在 OrchestrationAgent 中添加 passthrough 状态管理
- 新增 /direct 和 /smart 命令用于切换模式
- 修改 /list 命令为 /status 并显示当前模式状态
- 更新帮助信息包含新模式命令
2026-03-28 12:33:41 +08:00

217 lines
7.2 KiB
Python

"""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 ("/status", "/list", "/ls", "/l"):
return await _cmd_status(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()
elif cmd == "/direct":
return _cmd_direct(user_id)
elif cmd == "/smart":
return _cmd_smart(user_id)
else:
return None
async def _cmd_new(user_id: str, args: str) -> str:
"""Create a new session."""
if not args:
return "Usage: /new <project_dir> [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 <project_dir> [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_status(user_id: str) -> str:
"""Show status: sessions and current mode."""
sessions = manager.list_sessions(user_id=user_id)
if not sessions:
return "No active sessions."
active = agent.get_active_conv(user_id)
passthrough = agent.get_passthrough(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']}`")
status = "Direct 🟢" if passthrough else "Smart ⚪"
lines.append(f"\n**Mode:** {status}")
lines.append("Use `/switch <n>` to activate a session.")
lines.append("Use `/direct` or `/smart` to change mode.")
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 <conv_id>` or `/close <n>`."
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>\n" + await _cmd_status(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_direct(user_id: str) -> str:
"""Enable direct mode - messages go straight to Claude Code."""
conv = agent.get_active_conv(user_id)
if not conv:
return "No active session. Use `/new` or `/switch` first."
agent.set_passthrough(user_id, True)
return f"✓ Direct mode ON. Messages go directly to session `{conv}`."
def _cmd_smart(user_id: str) -> str:
"""Enable smart mode - messages go through LLM for routing."""
agent.set_passthrough(user_id, False)
return "✓ Smart mode ON. Messages go through LLM for intelligent routing."
def _cmd_help() -> str:
"""Show help."""
return """**Commands:**
/new <dir> [msg] [--timeout N] [--idle N] - Create session
/status - Show sessions and current mode
/close [n] - Close session (active or by number)
/switch <n> - Switch to session by number
/direct - Direct mode: messages → Claude Code (no LLM overhead)
/smart - Smart mode: messages → LLM routing (default)
/retry - Retry last message
/help - Show this help"""