权限和模式统一
This commit is contained in:
parent
de0fad496c
commit
a3256d15f4
@ -31,12 +31,18 @@ REPL 内的会话管理命令优先使用 slash command。
|
||||
|
||||
REPL 内所有 slash command 由程序直接处理,不进入 agent loop。
|
||||
|
||||
REPL 会显示当前模式提示符,例如 `build >` 或 `plan >`。
|
||||
|
||||
当前项目支持最小 memory:使用 `/remember` 保存长期信息,使用 `/memory` 查看。
|
||||
|
||||
写操作和 Bash 默认需要确认,可通过 `--auto-approve` 或 `/permissions auto-on` 跳过。
|
||||
|
||||
工作区外访问和 `plan` 模式限制属于硬性边界,不通过审批放行。
|
||||
|
||||
支持 `/mode build` 和 `/mode plan` 两种模式,`plan` 为只读规划模式。
|
||||
|
||||
`/clear` 是清空当前上下文的主别名,等价于 `/new`。
|
||||
|
||||
未明确说明时,使用以下默认值:
|
||||
|
||||
- 工作目录:当前进程目录
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
- 修改已有文件时,优先使用 `Edit`,而不是 `Bash` 或 `Write`
|
||||
- 需要创建或覆盖文件时,优先使用 `Write` 工具,而不是 `Bash`
|
||||
- 会话与 memory 管理优先使用 slash command,而不是自然语言或 `Bash` 探查对应文件
|
||||
- 优先通过 `/help` 查看当前命令,而不是猜测命令格式
|
||||
- 长期有价值的项目约束或偏好,优先使用 `/remember` 保存
|
||||
- 高风险操作会触发确认,优先先读再改,减少无意义审批
|
||||
- 复杂任务可先切换到 `/mode plan` 进行规划,再切换回 `/mode build` 执行
|
||||
|
||||
@ -42,45 +42,54 @@ def handle_command(
|
||||
args = parsed["args"]
|
||||
|
||||
if name == "/help":
|
||||
console.print("/help - 显示命令帮助")
|
||||
console.print("/clear - 清空当前上下文并创建新 session")
|
||||
console.print("/history - 查看历史 session")
|
||||
console.print("/resume <id-or-index> - 恢复指定 session")
|
||||
console.print("/new - 创建全新 session")
|
||||
console.print("/remember <text> - 保存长期 memory")
|
||||
console.print("/memory - 查看当前项目 memory")
|
||||
console.print("/mode - 查看当前模式")
|
||||
console.print("/mode build - 切换到 build 模式")
|
||||
console.print("/mode plan - 切换到 plan 模式")
|
||||
console.print("/build - 切换到 build 模式")
|
||||
console.print("/plan - 切换到 plan 模式")
|
||||
console.print("/permissions - 查看当前权限状态")
|
||||
console.print("/permissions auto-on - 开启自动批准")
|
||||
console.print("/permissions auto-off - 关闭自动批准")
|
||||
console.print("会话命令")
|
||||
console.print(" /history 查看当前工作目录的历史会话")
|
||||
console.print(" /resume <id-or-index> 恢复指定会话")
|
||||
console.print(" /new 创建新会话")
|
||||
console.print(" /clear /new 的别名,用于清空当前上下文")
|
||||
console.print("")
|
||||
console.print("Memory 命令")
|
||||
console.print(" /memory 查看当前项目 memory")
|
||||
console.print(" /remember <text> 追加保存长期信息")
|
||||
console.print("")
|
||||
console.print("模式命令")
|
||||
console.print(" /mode 查看当前模式")
|
||||
console.print(" /mode build 切换到 build 模式")
|
||||
console.print(" /mode plan 切换到 plan 模式")
|
||||
console.print(" /build /mode build 的简写")
|
||||
console.print(" /plan /mode plan 的简写")
|
||||
console.print("")
|
||||
console.print("权限命令")
|
||||
console.print(" /permissions 查看当前权限状态")
|
||||
console.print(" /permissions auto-on 开启自动批准")
|
||||
console.print(" /permissions auto-off 关闭自动批准")
|
||||
console.print("")
|
||||
console.print("退出方式")
|
||||
console.print(" exit / quit 退出 REPL")
|
||||
return agent
|
||||
|
||||
if name in {"/clear", "/new"}:
|
||||
session_meta = store.create_session(config.model)
|
||||
console.print(f"已创建新 session: {session_meta.get('session_id')}")
|
||||
console.print(f"已创建新会话: {session_meta.get('session_id')}")
|
||||
return build_agent(root, config, store, permissions, session_meta, [])
|
||||
|
||||
if name in {"/build", "/plan"}:
|
||||
target_mode = "build" if name == "/build" else "plan"
|
||||
mode.set_mode(target_mode)
|
||||
permissions.set_mode(target_mode)
|
||||
console.print(f"当前模式: {target_mode}")
|
||||
console.print(f"模式已切换为: {target_mode}")
|
||||
return agent
|
||||
|
||||
if name == "/mode":
|
||||
if not args:
|
||||
console.print(f"当前模式: {mode.mode}")
|
||||
console.print(f"模式状态\n 当前模式: {mode.mode}")
|
||||
return agent
|
||||
if args in {"build", "plan"}:
|
||||
mode.set_mode(args)
|
||||
permissions.set_mode(args)
|
||||
console.print(f"当前模式: {args}")
|
||||
console.print(f"模式已切换为: {args}")
|
||||
return agent
|
||||
console.print("[red]error:[/red] 仅支持 /mode build 或 /mode plan")
|
||||
console.print("命令错误: 仅支持 /mode build 或 /mode plan")
|
||||
return agent
|
||||
|
||||
if name == "/history":
|
||||
@ -89,45 +98,52 @@ def handle_command(
|
||||
|
||||
if name == "/resume":
|
||||
if not args:
|
||||
console.print("[red]error:[/red] 缺少 resume 目标")
|
||||
console.print("命令错误: 缺少 resume 目标")
|
||||
return agent
|
||||
session_id = store.resolve_session_id(args)
|
||||
session_meta = store.load_meta(session_id)
|
||||
restored_history = store.load_messages(session_id)
|
||||
console.print(f"已恢复 session: {session_meta.get('session_id')}")
|
||||
console.print(f"已恢复会话: {session_meta.get('session_id')}")
|
||||
return build_agent(root, config, store, permissions, session_meta, restored_history)
|
||||
|
||||
if name == "/memory":
|
||||
content = memory.read()
|
||||
console.print(content or "当前项目还没有 memory。")
|
||||
console.print(content or "当前项目还没有已保存的 memory。")
|
||||
return agent
|
||||
|
||||
if name == "/permissions":
|
||||
if args == "auto-on":
|
||||
permissions.set_auto_approve(True)
|
||||
console.print("已开启 auto_approve")
|
||||
console.print("权限状态\n auto_approve: 开启")
|
||||
return agent
|
||||
if args == "auto-off":
|
||||
permissions.set_auto_approve(False)
|
||||
console.print("已关闭 auto_approve")
|
||||
console.print("权限状态\n auto_approve: 关闭")
|
||||
return agent
|
||||
|
||||
status = permissions.status()
|
||||
console.print(f"auto_approve: {status['auto_approve']}")
|
||||
console.print(f"mode: {status['mode']}")
|
||||
console.print(f"workspace: {status['workspace']}")
|
||||
console.print(f"boundary: {status['boundary_policy']}")
|
||||
console.print(f"自动允许: {', '.join(status['auto_allowed_tools'])}")
|
||||
console.print(f"需要确认: {', '.join(status['confirm_required_tools'])}")
|
||||
auto_allowed_items = list(status["auto_allowed_tools"] if isinstance(status["auto_allowed_tools"], list) else [])
|
||||
confirm_required_items = list(
|
||||
status["confirm_required_tools"] if isinstance(status["confirm_required_tools"], list) else []
|
||||
)
|
||||
auto_allowed = ", ".join(str(item) for item in auto_allowed_items)
|
||||
confirm_required = ", ".join(str(item) for item in confirm_required_items)
|
||||
console.print("权限状态")
|
||||
console.print(f" auto_approve: {'开启' if status['auto_approve'] else '关闭'}")
|
||||
console.print(f" mode: {status['mode']}")
|
||||
console.print(f" workspace: {status['workspace']}")
|
||||
console.print(f" boundary: {status['boundary_policy']}")
|
||||
console.print(f" 自动允许: {auto_allowed}")
|
||||
console.print(f" 需要确认: {confirm_required}")
|
||||
return agent
|
||||
|
||||
if name == "/remember":
|
||||
if not args:
|
||||
console.print("[red]error:[/red] 缺少需要保存的 memory 内容")
|
||||
console.print("命令错误: 缺少需要保存的 memory 内容")
|
||||
return agent
|
||||
path = memory.append(args)
|
||||
console.print(f"已写入 memory: {path}")
|
||||
return agent
|
||||
|
||||
console.print(f"[red]error:[/red] 不支持的命令: {name},输入 /help 查看帮助")
|
||||
console.print(f"命令错误: 不支持的命令 {name},输入 /help 查看帮助")
|
||||
return agent
|
||||
|
||||
@ -289,9 +289,9 @@ class Agent:
|
||||
def _check_tool_permission(self, name: str, payload: dict[str, Any]) -> str | None:
|
||||
if not self.permission_checker:
|
||||
return None
|
||||
if not self.permission_checker.is_allowed(name, payload):
|
||||
if self.permission_checker.is_hard_blocked(name, payload):
|
||||
return self.permission_checker.denial_reason(name, payload)
|
||||
if self.permission_checker.requires_confirmation(name):
|
||||
if self.permission_checker.requires_confirmation(name, payload):
|
||||
if not self.confirm_tool:
|
||||
return self.permission_checker.denial_reason(name, payload)
|
||||
if not self.confirm_tool(name, payload):
|
||||
|
||||
@ -33,9 +33,9 @@ def render_stream(agent: Agent, user_input: str) -> None:
|
||||
elif event["type"] == "tool_result":
|
||||
output = str(event.get("output", ""))
|
||||
if (
|
||||
output.startswith("Tool blocked in plan mode:")
|
||||
or output.startswith("Permission denied for tool:")
|
||||
or output.startswith("Bash command appears to target paths outside the workspace")
|
||||
output.startswith("当前处于 plan 模式,禁止执行工具:")
|
||||
or output.startswith("当前未批准工具执行:")
|
||||
or output.startswith("当前命令疑似指向工作区外路径,已拒绝执行 Bash")
|
||||
):
|
||||
console.print(f"[red]{output}[/red]")
|
||||
else:
|
||||
@ -148,10 +148,10 @@ def run(
|
||||
render_stream(agent, prompt)
|
||||
return
|
||||
|
||||
console.print("[bold cyan]cc-slim[/bold cyan] REPL,输入 exit 或 quit 退出。")
|
||||
console.print("[bold cyan]cc-slim[/bold cyan] REPL,输入 /help 查看命令,输入 exit 或 quit 退出。")
|
||||
while True:
|
||||
try:
|
||||
user_input = typer.prompt(">")
|
||||
user_input = typer.prompt(f"[{mode.mode}] >")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
console.print()
|
||||
break
|
||||
|
||||
@ -13,23 +13,28 @@ class PermissionChecker:
|
||||
self.auto_allowed_tools = ["Read", "Glob", "Grep"]
|
||||
self.confirm_required_tools = ["Write", "Edit", "Bash"]
|
||||
|
||||
def is_allowed(self, tool_name: str, payload: dict[str, object] | None = None) -> bool:
|
||||
def is_hard_blocked(self, tool_name: str, payload: dict[str, object] | None = None) -> bool:
|
||||
if self.mode == "plan" and tool_name in self.confirm_required_tools:
|
||||
return False
|
||||
return True
|
||||
if tool_name == "Bash" and payload and self._bash_targets_outside_workspace(str(payload.get("command", ""))):
|
||||
return False
|
||||
return True
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_allowed(self, tool_name: str, payload: dict[str, object] | None = None) -> bool:
|
||||
return not self.is_hard_blocked(tool_name, payload)
|
||||
|
||||
def denial_reason(self, tool_name: str, payload: dict[str, object] | None = None) -> str:
|
||||
if self.mode == "plan" and tool_name in self.confirm_required_tools:
|
||||
return f"Tool blocked in plan mode: {tool_name}"
|
||||
return f"当前处于 plan 模式,禁止执行工具: {tool_name}"
|
||||
if tool_name == "Bash" and payload and self._bash_targets_outside_workspace(str(payload.get("command", ""))):
|
||||
return "Bash command appears to target paths outside the workspace"
|
||||
return f"Permission denied for tool: {tool_name}"
|
||||
return "当前命令疑似指向工作区外路径,已拒绝执行 Bash"
|
||||
return f"当前未批准工具执行: {tool_name}"
|
||||
|
||||
def requires_confirmation(self, tool_name: str) -> bool:
|
||||
def requires_confirmation(self, tool_name: str, payload: dict[str, object] | None = None) -> bool:
|
||||
if self.auto_approve:
|
||||
return False
|
||||
if self.is_hard_blocked(tool_name, payload):
|
||||
return False
|
||||
return tool_name in self.confirm_required_tools
|
||||
|
||||
def set_auto_approve(self, enabled: bool) -> None:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user