feat: 添加可配置的命令前缀以避免与Claude Code冲突
引入可配置的COMMAND_PREFIX参数,默认设置为"//"以避免与Claude Code的"/"命令冲突 修改相关文件以支持新的命令前缀,包括配置解析、路由逻辑和命令处理 更新帮助文档和提示信息以反映新的命令前缀
This commit is contained in:
parent
ed70588d95
commit
a278ece348
@ -44,14 +44,18 @@ def _perm_label(mode: str) -> str:
|
|||||||
|
|
||||||
def parse_command(text: str) -> Optional[Tuple[str, str]]:
|
def parse_command(text: str) -> Optional[Tuple[str, str]]:
|
||||||
"""
|
"""
|
||||||
Parse a slash command from text.
|
Parse a bot command from text.
|
||||||
Returns (command, args) or None if not a command.
|
Returns (command, args) or None if not a command.
|
||||||
|
Commands must start with COMMAND_PREFIX (default "//").
|
||||||
"""
|
"""
|
||||||
|
from config import COMMAND_PREFIX
|
||||||
text = text.strip()
|
text = text.strip()
|
||||||
if not text.startswith("/"):
|
if not text.startswith(COMMAND_PREFIX):
|
||||||
return None
|
return None
|
||||||
parts = text.split(None, 1)
|
# Strip the prefix, then split into command word and args
|
||||||
cmd = parts[0].lower()
|
body = text[len(COMMAND_PREFIX):]
|
||||||
|
parts = body.split(None, 1)
|
||||||
|
cmd = COMMAND_PREFIX + parts[0].lower()
|
||||||
args = parts[1] if len(parts) > 1 else ""
|
args = parts[1] if len(parts) > 1 else ""
|
||||||
return (cmd, args)
|
return (cmd, args)
|
||||||
|
|
||||||
@ -68,38 +72,39 @@ async def handle_command(user_id: str, text: str) -> Optional[str]:
|
|||||||
logger.info("Command: %s args=%r user=...%s", cmd, args[:50], user_id[-8:])
|
logger.info("Command: %s args=%r user=...%s", cmd, args[:50], user_id[-8:])
|
||||||
|
|
||||||
# In ROUTER_MODE, only handle router-specific commands locally.
|
# In ROUTER_MODE, only handle router-specific commands locally.
|
||||||
# Session commands (/status, /new, /close, etc.) fall through to node forwarding.
|
# Session commands (//status, //new, //close, etc.) fall through to node forwarding.
|
||||||
from config import ROUTER_MODE
|
from config import ROUTER_MODE, COMMAND_PREFIX
|
||||||
if ROUTER_MODE and cmd not in ("/nodes", "/node", "/help", "/h", "/?"):
|
P = COMMAND_PREFIX
|
||||||
|
if ROUTER_MODE and cmd not in (P+"nodes", P+"node", P+"help", P+"h", P+"?"):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
set_current_user(user_id)
|
set_current_user(user_id)
|
||||||
|
|
||||||
if cmd in ("/new", "/n"):
|
if cmd in (P+"new", P+"n"):
|
||||||
return await _cmd_new(user_id, args)
|
return await _cmd_new(user_id, args)
|
||||||
elif cmd in ("/status", "/list", "/ls", "/l"):
|
elif cmd in (P+"status", P+"list", P+"ls", P+"l"):
|
||||||
return await _cmd_status(user_id)
|
return await _cmd_status(user_id)
|
||||||
elif cmd in ("/close", "/c"):
|
elif cmd in (P+"close", P+"c"):
|
||||||
return await _cmd_close(user_id, args)
|
return await _cmd_close(user_id, args)
|
||||||
elif cmd in ("/switch", "/s"):
|
elif cmd in (P+"switch", P+"s"):
|
||||||
return await _cmd_switch(user_id, args)
|
return await _cmd_switch(user_id, args)
|
||||||
elif cmd == "/retry":
|
elif cmd == P+"retry":
|
||||||
return await _cmd_retry(user_id)
|
return await _cmd_retry(user_id)
|
||||||
elif cmd in ("/help", "/h", "/?"):
|
elif cmd in (P+"help", P+"h", P+"?"):
|
||||||
return _cmd_help()
|
return _cmd_help()
|
||||||
elif cmd == "/direct":
|
elif cmd == P+"direct":
|
||||||
return _cmd_direct(user_id)
|
return _cmd_direct(user_id)
|
||||||
elif cmd == "/smart":
|
elif cmd == P+"smart":
|
||||||
return _cmd_smart(user_id)
|
return _cmd_smart(user_id)
|
||||||
elif cmd == "/tasks":
|
elif cmd == P+"tasks":
|
||||||
return _cmd_tasks()
|
return _cmd_tasks()
|
||||||
elif cmd == "/shell":
|
elif cmd == P+"shell":
|
||||||
return await _cmd_shell(args)
|
return await _cmd_shell(args)
|
||||||
elif cmd == "/remind":
|
elif cmd == P+"remind":
|
||||||
return await _cmd_remind(args)
|
return await _cmd_remind(args)
|
||||||
elif cmd == "/perm":
|
elif cmd == P+"perm":
|
||||||
return await _cmd_perm(user_id, args)
|
return await _cmd_perm(user_id, args)
|
||||||
elif cmd in ("/nodes", "/node"):
|
elif cmd in (P+"nodes", P+"node"):
|
||||||
return await _cmd_nodes(user_id, args)
|
return await _cmd_nodes(user_id, args)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
@ -431,23 +436,24 @@ async def _cmd_nodes(user_id: str, args: str) -> str:
|
|||||||
|
|
||||||
def _cmd_help() -> str:
|
def _cmd_help() -> str:
|
||||||
"""Show help."""
|
"""Show help."""
|
||||||
return """**Commands:**
|
from config import COMMAND_PREFIX as P
|
||||||
/new <dir> [msg] [--timeout N] [--idle N] [--perm MODE] - Create session
|
return f"""**Commands:** (prefix: `{P}`)
|
||||||
/status - Show sessions and current mode
|
{P}new <dir> [msg] [--timeout N] [--idle N] [--perm MODE] - Create session
|
||||||
/close [n] - Close session (active or by number)
|
{P}status - Show sessions and current mode
|
||||||
/switch <n> - Switch to session by number
|
{P}close [n] - Close session (active or by number)
|
||||||
/perm <mode> [conv_id] - Set permission mode (bypass/accept/plan)
|
{P}switch <n> - Switch to session by number
|
||||||
/direct - Direct mode: messages → Claude Code (no LLM overhead)
|
{P}perm <mode> [conv_id] - Set permission mode (bypass/accept/plan)
|
||||||
/smart - Smart mode: messages → LLM routing (default)
|
{P}direct - Direct mode: messages → Claude Code (no LLM overhead)
|
||||||
/shell <cmd> - Run shell command (bypasses LLM)
|
{P}smart - Smart mode: messages → LLM routing (default)
|
||||||
/remind <time> <msg> - Set reminder (e.g. /remind 10m check build)
|
{P}shell <cmd> - Run shell command (bypasses LLM)
|
||||||
/tasks - List background tasks
|
{P}remind <time> <msg> - Set reminder (e.g. {P}remind 10m check build)
|
||||||
/nodes - List connected host nodes
|
{P}tasks - List background tasks
|
||||||
/node <name> - Switch active node
|
{P}nodes - List connected host nodes
|
||||||
/retry - Retry last message
|
{P}node <name> - Switch active node
|
||||||
/help - Show this help
|
{P}retry - Retry last message
|
||||||
|
{P}help - Show this help
|
||||||
|
|
||||||
**Permission modes** (used by /perm and /new --perm):
|
**Permission modes** (used by {P}perm and {P}new --perm):
|
||||||
bypass — 跳过所有权限确认,CC 自动执行一切操作(默认)
|
bypass — 跳过所有权限确认,CC 自动执行一切操作(默认)
|
||||||
适合:受信任的沙盒环境、自动化任务
|
适合:受信任的沙盒环境、自动化任务
|
||||||
accept — 自动接受文件编辑,但 shell 命令仍需手动确认
|
accept — 自动接受文件编辑,但 shell 命令仍需手动确认
|
||||||
|
|||||||
@ -25,6 +25,10 @@ METASO_API_KEY: str = _cfg.get("METASO_API_KEY", "")
|
|||||||
ROUTER_MODE: bool = _cfg.get("ROUTER_MODE", False)
|
ROUTER_MODE: bool = _cfg.get("ROUTER_MODE", False)
|
||||||
ROUTER_SECRET: str = _cfg.get("ROUTER_SECRET", "")
|
ROUTER_SECRET: str = _cfg.get("ROUTER_SECRET", "")
|
||||||
|
|
||||||
|
# Command prefix — the leader string that identifies bot commands.
|
||||||
|
# Default is "//" to avoid conflicts with Claude Code's own "/" commands.
|
||||||
|
COMMAND_PREFIX: str = _cfg.get("COMMAND_PREFIX", "//")
|
||||||
|
|
||||||
# Server configuration
|
# Server configuration
|
||||||
PORT: int = _cfg.get("PORT", 8000)
|
PORT: int = _cfg.get("PORT", 8000)
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,10 @@ WORKING_DIR: C:/Users/yourname/projects
|
|||||||
# Optional: 秘塔AI Search API key for web search
|
# Optional: 秘塔AI Search API key for web search
|
||||||
METASO_API_KEY: your_metaso_api_key
|
METASO_API_KEY: your_metaso_api_key
|
||||||
|
|
||||||
|
# Bot command prefix (default: "//")
|
||||||
|
# "//" avoids conflicts with Claude Code's own "/" commands.
|
||||||
|
# COMMAND_PREFIX: "//"
|
||||||
|
|
||||||
# Which Feishu users this node serves
|
# Which Feishu users this node serves
|
||||||
# List of open_ids from Feishu
|
# List of open_ids from Feishu
|
||||||
SERVES_USERS:
|
SERVES_USERS:
|
||||||
|
|||||||
@ -21,7 +21,7 @@ from langchain_core.messages import (
|
|||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
|
|
||||||
from agent.manager import manager
|
from agent.manager import manager
|
||||||
from config import OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL, WORKING_DIR
|
from config import OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL, WORKING_DIR, COMMAND_PREFIX as _P
|
||||||
from orchestrator.tools import TOOLS, set_current_user
|
from orchestrator.tools import TOOLS, set_current_user
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -39,27 +39,29 @@ Pass these names directly to `create_conversation` — the tool resolves them au
|
|||||||
|
|
||||||
{active_session_line}
|
{active_session_line}
|
||||||
|
|
||||||
|
Bot command prefix: {prefix}
|
||||||
|
|
||||||
## Tools — two distinct categories
|
## Tools — two distinct categories
|
||||||
|
|
||||||
### Bot control commands (use `run_command`)
|
### Bot control commands (use `run_command`)
|
||||||
`run_command` executes PhoneWork slash commands. Use it when the user asks to:
|
`run_command` executes PhoneWork commands. Use it when the user asks to:
|
||||||
- Change permission mode: "切换到只读模式" → run_command("/perm plan")
|
- Change permission mode: "切换到只读模式" → run_command("{prefix}perm plan")
|
||||||
- Close/switch sessions: "关掉第一个" → run_command("/close 1")
|
- Close/switch sessions: "关掉第一个" → run_command("{prefix}close 1")
|
||||||
- Change routing mode: "切换到直连模式" → run_command("/direct")
|
- Change routing mode: "切换到直连模式" → run_command("{prefix}direct")
|
||||||
- Set a reminder: "10分钟后提醒我" → run_command("/remind 10m 提醒我")
|
- Set a reminder: "10分钟后提醒我" → run_command("{prefix}remind 10m 提醒我")
|
||||||
- Check status: "看看现在有哪些 session" → run_command("/status")
|
- Check status: "看看现在有哪些 session" → run_command("{prefix}status")
|
||||||
- Any other /command the user would type manually
|
- Any other command the user would type manually
|
||||||
|
|
||||||
Available bot commands (pass verbatim to run_command):
|
Available bot commands (pass verbatim to run_command):
|
||||||
/new <dir> [msg] [--perm bypass|accept|plan] — create session
|
{prefix}new <dir> [msg] [--perm bypass|accept|plan] — create session
|
||||||
/close [n|conv_id] — close session
|
{prefix}close [n|conv_id] — close session
|
||||||
/switch <n> — switch active session
|
{prefix}switch <n> — switch active session
|
||||||
/perm <mode> [conv_id] — permission mode: bypass (default), accept, plan
|
{prefix}perm <mode> [conv_id] — permission mode: bypass (default), accept, plan
|
||||||
/direct — direct mode (bypass LLM for CC messages)
|
{prefix}direct — direct mode (bypass LLM for CC messages)
|
||||||
/smart — smart mode (LLM routing, default)
|
{prefix}smart — smart mode (LLM routing, default)
|
||||||
/status — list sessions
|
{prefix}status — list sessions
|
||||||
/remind <Ns|Nm|Nh> <msg> — one-shot reminder
|
{prefix}remind <Ns|Nm|Nh> <msg> — one-shot reminder
|
||||||
/tasks — list background tasks
|
{prefix}tasks — list background tasks
|
||||||
|
|
||||||
### Host shell commands (use `run_shell`)
|
### Host shell commands (use `run_shell`)
|
||||||
`run_shell` executes shell commands on the host machine (git, ls, cat, pip, etc.).
|
`run_shell` executes shell commands on the host machine (git, ls, cat, pip, etc.).
|
||||||
@ -69,7 +71,7 @@ NEVER use `run_shell` for bot control. NEVER use `run_command` for shell command
|
|||||||
1. NEW session: call `create_conversation` with the project name/path. \
|
1. NEW session: call `create_conversation` with the project name/path. \
|
||||||
If the user's message also contains a task, pass it as `initial_message` too.
|
If the user's message also contains a task, pass it as `initial_message` too.
|
||||||
2. Follow-up to ACTIVE session: call `send_to_conversation` with the active conv_id shown above.
|
2. Follow-up to ACTIVE session: call `send_to_conversation` with the active conv_id shown above.
|
||||||
3. BOT CONTROL: call `run_command` with the appropriate slash command.
|
3. BOT CONTROL: call `run_command` with the appropriate command.
|
||||||
4. GENERAL QUESTIONS: answer directly — do NOT create a session for simple Q&A.
|
4. GENERAL QUESTIONS: answer directly — do NOT create a session for simple Q&A.
|
||||||
5. WEB / SEARCH: use `web` at most twice, then synthesize and reply.
|
5. WEB / SEARCH: use `web` at most twice, then synthesize and reply.
|
||||||
6. BACKGROUND TASKS: when a task starts, reply immediately — do NOT poll `task_status`.
|
6. BACKGROUND TASKS: when a task starts, reply immediately — do NOT poll `task_status`.
|
||||||
@ -119,6 +121,7 @@ class OrchestrationAgent:
|
|||||||
working_dir=WORKING_DIR,
|
working_dir=WORKING_DIR,
|
||||||
active_session_line=active_line,
|
active_session_line=active_line,
|
||||||
today=today,
|
today=today,
|
||||||
|
prefix=_P,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_active_conv(self, user_id: str) -> Optional[str]:
|
def get_active_conv(self, user_id: str) -> Optional[str]:
|
||||||
@ -143,6 +146,16 @@ class OrchestrationAgent:
|
|||||||
logger.info(">>> user=...%s conv=%s msg=%r", short_uid, active_conv, text[:80])
|
logger.info(">>> user=...%s conv=%s msg=%r", short_uid, active_conv, text[:80])
|
||||||
logger.debug(" history_len=%d", len(self._history[user_id]))
|
logger.debug(" history_len=%d", len(self._history[user_id]))
|
||||||
|
|
||||||
|
# Always handle bot commands first — even in passthrough mode.
|
||||||
|
# Bot commands must never reach Claude Code.
|
||||||
|
from config import COMMAND_PREFIX
|
||||||
|
if text.strip().startswith(COMMAND_PREFIX):
|
||||||
|
from bot.commands import handle_command
|
||||||
|
result = await handle_command(user_id, text)
|
||||||
|
if result is not None:
|
||||||
|
return result
|
||||||
|
logger.debug(" unknown command, falling through to LLM")
|
||||||
|
|
||||||
# Passthrough mode: if enabled and active session, bypass LLM
|
# Passthrough mode: if enabled and active session, bypass LLM
|
||||||
if self._passthrough[user_id] and active_conv:
|
if self._passthrough[user_id] and active_conv:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -76,9 +76,11 @@ async def route(user_id: str, chat_id: str, text: str) -> tuple[Optional[str], s
|
|||||||
if len(online_nodes) == 1:
|
if len(online_nodes) == 1:
|
||||||
return online_nodes[0].node_id, "Only one node available"
|
return online_nodes[0].node_id, "Only one node available"
|
||||||
|
|
||||||
if text.strip().startswith("/"):
|
from config import COMMAND_PREFIX
|
||||||
|
if text.strip().startswith(COMMAND_PREFIX):
|
||||||
cmd = text.strip().split()[0].lower()
|
cmd = text.strip().split()[0].lower()
|
||||||
if cmd in ("/nodes", "/node", "/help", "/h", "/?"):
|
meta_cmds = {COMMAND_PREFIX + s for s in ("nodes", "node", "help", "h", "?")}
|
||||||
|
if cmd in meta_cmds:
|
||||||
return "meta", "Meta command"
|
return "meta", "Meta command"
|
||||||
# Session commands: forward to active node directly (no LLM call needed)
|
# Session commands: forward to active node directly (no LLM call needed)
|
||||||
active = registry.get_active_node(user_id)
|
active = registry.get_active_node(user_id)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user