feat(perm): 添加会话权限模式管理功能

实现会话权限模式管理功能,包括:
1. 在 pty_process 中定义三种权限模式标志
2. 添加 /perm 命令用于修改会话权限模式
3. 新增 run_command 工具用于执行 bot 控制命令
4. 在会话管理中支持权限模式设置
5. 添加完整的测试用例和文档说明
This commit is contained in:
Yuyao Huang (Sam)
2026-03-29 06:46:45 +08:00
parent 8dab229aaf
commit cbeafa35a5
8 changed files with 302 additions and 60 deletions
+33 -16
View File
@@ -40,28 +40,45 @@ Pass these names directly to `create_conversation` — the tool resolves them au
{active_session_line}
Your responsibilities:
## Tools — two distinct categories
### Bot control commands (use `run_command`)
`run_command` executes PhoneWork slash commands. Use it when the user asks to:
- Change permission mode: "切换到只读模式" → run_command("/perm plan")
- Close/switch sessions: "关掉第一个" → run_command("/close 1")
- Change routing mode: "切换到直连模式" → run_command("/direct")
- Set a reminder: "10分钟后提醒我" → run_command("/remind 10m 提醒我")
- Check status: "看看现在有哪些 session" → run_command("/status")
- Any other /command the user would type manually
Available bot commands (pass verbatim to run_command):
/new <dir> [msg] [--perm bypass|accept|plan] — create session
/close [n|conv_id] — close session
/switch <n> — switch active session
/perm <mode> [conv_id] — permission mode: bypass (default), accept, plan
/direct — direct mode (bypass LLM for CC messages)
/smart — smart mode (LLM routing, default)
/status — list sessions
/remind <Ns|Nm|Nh> <msg> — one-shot reminder
/tasks — list background tasks
### Host shell commands (use `run_shell`)
`run_shell` executes shell commands on the host machine (git, ls, cat, pip, etc.).
NEVER use `run_shell` for bot control. NEVER use `run_command` for shell commands.
## Session responsibilities
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.
2. Follow-up to ACTIVE session: call `send_to_conversation` with the active conv_id shown above.
3. List sessions: call `list_conversations`.
4. Close session: call `close_conversation`.
5. GENERAL QUESTIONS: If the user asks a general question (not about a specific project or file), \
answer directly using your own knowledge. Do NOT create a session for simple Q&A.
6. WEB / SEARCH: Use the `web` tool when the user needs current information. \
Call it ONCE (or at most twice with a refined query). Then synthesize and reply — \
do NOT keep searching in a loop. If the first search returns results, use them.
7. BACKGROUND TASKS: When `create_conversation` or `send_to_conversation` returns a \
"Task #... started" message, the task is running in the background. \
Immediately reply to the user that the task has started and they will be notified. \
Do NOT call `task_status` in a loop waiting for it — the system sends a notification when done.
3. BOT CONTROL: call `run_command` with the appropriate slash command.
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.
6. BACKGROUND TASKS: when a task starts, reply immediately — do NOT poll `task_status`.
Guidelines:
- Relay Claude Code's output verbatim.
- If no active session and the user sends a task without naming a directory, ask them which project.
- For general knowledge questions (e.g., "what is a Python generator?", "explain async/await"), \
answer directly without creating a session.
- After using any tool, always produce a final text reply to the user. Never end a turn on a tool call.
- If no active session and the user sends a task without naming a directory, ask which project.
- After using any tool, always produce a final text reply. Never end a turn on a tool call.
- Keep your own words brief — let Claude Code's output speak.
- Reply in the same language the user uses (Chinese or English).
"""
+37
View File
@@ -716,12 +716,49 @@ class TaskStatusTool(BaseTool):
}, ensure_ascii=False)
class RunCommandInput(BaseModel):
command: str = Field(
...,
description=(
"A bot slash command to execute (e.g. '/perm accept', '/close 1', '/switch 2'). "
"This runs bot control commands — NOT shell commands on the host machine. "
"Use run_shell for host shell commands (git, ls, etc.)."
),
)
class RunCommandTool(BaseTool):
name: str = "run_command"
description: str = (
"Execute a PhoneWork bot slash command on behalf of the user. "
"Use this to control sessions, switch modes, change permissions, etc. "
"Examples: '/perm accept', '/close 1', '/switch 2', '/direct', '/smart', '/status'. "
"Do NOT use this for shell commands — use run_shell for those."
)
args_schema: Type[BaseModel] = RunCommandInput
def _run(self, command: str) -> str:
raise NotImplementedError("Use async version")
async def _arun(self, command: str) -> str:
from bot.commands import handle_command
from orchestrator.tools import get_current_user
user_id = get_current_user()
if not user_id:
return "Error: no user context"
result = await handle_command(user_id, command.strip())
if result is None:
return f"Unknown command: {command!r}. Use /help to see available commands."
return result
# Module-level tool list for easy import
TOOLS = [
CreateConversationTool(),
SendToConversationTool(),
ListConversationsTool(),
CloseConversationTool(),
RunCommandTool(),
ShellTool(),
FileReadTool(),
FileWriteTool(),