完成统一permission gate
This commit is contained in:
parent
f9ea8b1d6b
commit
0fbe0f7ffa
@ -31,6 +31,8 @@ REPL 内所有 slash command 由程序直接处理,不进入 agent loop。
|
|||||||
|
|
||||||
当前项目支持最小 memory:使用 `/remember` 保存长期信息,使用 `/memory` 查看。
|
当前项目支持最小 memory:使用 `/remember` 保存长期信息,使用 `/memory` 查看。
|
||||||
|
|
||||||
|
写操作和 Bash 默认需要确认,可通过 `--auto-approve` 或 `/permissions auto-on` 跳过。
|
||||||
|
|
||||||
未明确说明时,使用以下默认值:
|
未明确说明时,使用以下默认值:
|
||||||
|
|
||||||
- 工作目录:当前进程目录
|
- 工作目录:当前进程目录
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
- 需要创建或覆盖文件时,优先使用 `Write` 工具,而不是 `Bash`
|
- 需要创建或覆盖文件时,优先使用 `Write` 工具,而不是 `Bash`
|
||||||
- 会话与 memory 管理优先使用 slash command,而不是自然语言或 `Bash` 探查对应文件
|
- 会话与 memory 管理优先使用 slash command,而不是自然语言或 `Bash` 探查对应文件
|
||||||
- 长期有价值的项目约束或偏好,优先使用 `/remember` 保存
|
- 长期有价值的项目约束或偏好,优先使用 `/remember` 保存
|
||||||
|
- 高风险操作会触发确认,优先先读再改,减少无意义审批
|
||||||
- Windows 下先验证 shell 兼容性,再选择命令写法
|
- Windows 下先验证 shell 兼容性,再选择命令写法
|
||||||
|
|
||||||
## 沟通风格
|
## 沟通风格
|
||||||
|
|||||||
@ -7,6 +7,7 @@ from rich.console import Console
|
|||||||
|
|
||||||
from cc_slim.engine import Agent
|
from cc_slim.engine import Agent
|
||||||
from cc_slim.memory import MemoryStore
|
from cc_slim.memory import MemoryStore
|
||||||
|
from cc_slim.permissions import PermissionChecker
|
||||||
from cc_slim.session import SessionStore
|
from cc_slim.session import SessionStore
|
||||||
|
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ def handle_command(
|
|||||||
config: Any,
|
config: Any,
|
||||||
store: SessionStore,
|
store: SessionStore,
|
||||||
memory: MemoryStore,
|
memory: MemoryStore,
|
||||||
|
permissions: PermissionChecker,
|
||||||
agent: Agent,
|
agent: Agent,
|
||||||
build_agent: Any,
|
build_agent: Any,
|
||||||
render_history: Any,
|
render_history: Any,
|
||||||
@ -45,6 +47,9 @@ def handle_command(
|
|||||||
console.print("/new - 创建全新 session")
|
console.print("/new - 创建全新 session")
|
||||||
console.print("/remember <text> - 保存长期 memory")
|
console.print("/remember <text> - 保存长期 memory")
|
||||||
console.print("/memory - 查看当前项目 memory")
|
console.print("/memory - 查看当前项目 memory")
|
||||||
|
console.print("/permissions - 查看当前权限状态")
|
||||||
|
console.print("/permissions auto-on - 开启自动批准")
|
||||||
|
console.print("/permissions auto-off - 关闭自动批准")
|
||||||
return agent
|
return agent
|
||||||
|
|
||||||
if name in {"/clear", "/new"}:
|
if name in {"/clear", "/new"}:
|
||||||
@ -71,6 +76,22 @@ def handle_command(
|
|||||||
console.print(content or "当前项目还没有 memory。")
|
console.print(content or "当前项目还没有 memory。")
|
||||||
return agent
|
return agent
|
||||||
|
|
||||||
|
if name == "/permissions":
|
||||||
|
if args == "auto-on":
|
||||||
|
permissions.set_auto_approve(True)
|
||||||
|
console.print("已开启 auto_approve")
|
||||||
|
return agent
|
||||||
|
if args == "auto-off":
|
||||||
|
permissions.set_auto_approve(False)
|
||||||
|
console.print("已关闭 auto_approve")
|
||||||
|
return agent
|
||||||
|
|
||||||
|
status = permissions.status()
|
||||||
|
console.print(f"auto_approve: {status['auto_approve']}")
|
||||||
|
console.print(f"自动允许: {', '.join(status['auto_allowed_tools'])}")
|
||||||
|
console.print(f"需要确认: {', '.join(status['confirm_required_tools'])}")
|
||||||
|
return agent
|
||||||
|
|
||||||
if name == "/remember":
|
if name == "/remember":
|
||||||
if not args:
|
if not args:
|
||||||
console.print("[red]error:[/red] 缺少需要保存的 memory 内容")
|
console.print("[red]error:[/red] 缺少需要保存的 memory 内容")
|
||||||
|
|||||||
@ -14,6 +14,7 @@ from anthropic import Anthropic
|
|||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
|
|
||||||
from cc_slim.memory import MemoryStore
|
from cc_slim.memory import MemoryStore
|
||||||
|
from cc_slim.permissions import PermissionChecker
|
||||||
from cc_slim.session import SessionStore
|
from cc_slim.session import SessionStore
|
||||||
from cc_slim.tools import Tool
|
from cc_slim.tools import Tool
|
||||||
|
|
||||||
@ -67,12 +68,16 @@ class Agent:
|
|||||||
session_store: SessionStore | None = None,
|
session_store: SessionStore | None = None,
|
||||||
session_id: str | None = None,
|
session_id: str | None = None,
|
||||||
history: list[dict[str, Any]] | None = None,
|
history: list[dict[str, Any]] | None = None,
|
||||||
|
permission_checker: PermissionChecker | None = None,
|
||||||
|
confirm_tool: Any | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
self.tools = {tool.name: tool for tool in tools}
|
self.tools = {tool.name: tool for tool in tools}
|
||||||
self.history: list[dict[str, Any]] = list(history or [])
|
self.history: list[dict[str, Any]] = list(history or [])
|
||||||
self.session_store = session_store
|
self.session_store = session_store
|
||||||
self.session_id = session_id
|
self.session_id = session_id
|
||||||
|
self.permission_checker = permission_checker
|
||||||
|
self.confirm_tool = confirm_tool
|
||||||
self.system_prompt = self._build_system_prompt(workspace)
|
self.system_prompt = self._build_system_prompt(workspace)
|
||||||
self.client = self._build_client()
|
self.client = self._build_client()
|
||||||
|
|
||||||
@ -266,6 +271,11 @@ class Agent:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def _run_tool(self, name: str, payload: dict[str, Any]) -> str:
|
def _run_tool(self, name: str, payload: dict[str, Any]) -> str:
|
||||||
|
if self.permission_checker and self.permission_checker.requires_confirmation(name):
|
||||||
|
if not self.confirm_tool:
|
||||||
|
return f"Permission denied for tool: {name}"
|
||||||
|
if not self.confirm_tool(name, payload):
|
||||||
|
return f"Permission denied for tool: {name}"
|
||||||
tool = self.tools.get(name)
|
tool = self.tools.get(name)
|
||||||
if not tool:
|
if not tool:
|
||||||
return f"Tool not found: {name}"
|
return f"Tool not found: {name}"
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from rich.table import Table
|
|||||||
from cc_slim.commands import handle_command, parse_command
|
from cc_slim.commands import handle_command, parse_command
|
||||||
from cc_slim.engine import Agent, resolve_config
|
from cc_slim.engine import Agent, resolve_config
|
||||||
from cc_slim.memory import MemoryStore
|
from cc_slim.memory import MemoryStore
|
||||||
|
from cc_slim.permissions import PermissionChecker
|
||||||
from cc_slim.session import SessionStore
|
from cc_slim.session import SessionStore
|
||||||
from cc_slim.tools import build_default_tools
|
from cc_slim.tools import build_default_tools
|
||||||
|
|
||||||
@ -67,6 +68,7 @@ def build_agent(
|
|||||||
root: Path,
|
root: Path,
|
||||||
config: object,
|
config: object,
|
||||||
store: SessionStore,
|
store: SessionStore,
|
||||||
|
permissions: PermissionChecker,
|
||||||
session_meta: dict[str, object],
|
session_meta: dict[str, object],
|
||||||
restored_history: list[dict[str, object]] | None = None,
|
restored_history: list[dict[str, object]] | None = None,
|
||||||
) -> Agent:
|
) -> Agent:
|
||||||
@ -77,9 +79,17 @@ def build_agent(
|
|||||||
session_store=store,
|
session_store=store,
|
||||||
session_id=str(session_meta["session_id"]),
|
session_id=str(session_meta["session_id"]),
|
||||||
history=restored_history or [],
|
history=restored_history or [],
|
||||||
|
permission_checker=permissions,
|
||||||
|
confirm_tool=confirm_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def confirm_tool(name: str, payload: dict[str, object]) -> bool:
|
||||||
|
console.print(f"即将执行 [{name}] {payload},是否允许? [y/N]", markup=False, end=" ")
|
||||||
|
answer = input()
|
||||||
|
return answer.strip().lower() in {"y", "yes"}
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def run(
|
def run(
|
||||||
prompt: Optional[str] = typer.Argument(None, help="单次执行的用户输入"),
|
prompt: Optional[str] = typer.Argument(None, help="单次执行的用户输入"),
|
||||||
@ -91,10 +101,12 @@ def run(
|
|||||||
max_turns: Optional[int] = typer.Option(None, help="最大工具循环轮数"),
|
max_turns: Optional[int] = typer.Option(None, help="最大工具循环轮数"),
|
||||||
history: bool = typer.Option(False, "--history", help="列出当前工作目录的历史 session"),
|
history: bool = typer.Option(False, "--history", help="列出当前工作目录的历史 session"),
|
||||||
resume: Optional[str] = typer.Option(None, "--resume", help="按 session id 或序号恢复历史 session"),
|
resume: Optional[str] = typer.Option(None, "--resume", help="按 session id 或序号恢复历史 session"),
|
||||||
|
auto_approve: bool = typer.Option(False, "--auto-approve", help="跳过高风险工具确认"),
|
||||||
) -> None:
|
) -> None:
|
||||||
root = cwd.resolve()
|
root = cwd.resolve()
|
||||||
store = SessionStore(root)
|
store = SessionStore(root)
|
||||||
memory = MemoryStore(root)
|
memory = MemoryStore(root)
|
||||||
|
permissions = PermissionChecker(auto_approve=auto_approve)
|
||||||
if history:
|
if history:
|
||||||
render_history(store)
|
render_history(store)
|
||||||
return
|
return
|
||||||
@ -119,7 +131,7 @@ def run(
|
|||||||
else:
|
else:
|
||||||
session_meta = store.create_session(config.model)
|
session_meta = store.create_session(config.model)
|
||||||
|
|
||||||
agent = build_agent(root, config, store, session_meta, restored_history)
|
agent = build_agent(root, config, store, permissions, session_meta, restored_history)
|
||||||
|
|
||||||
if prompt:
|
if prompt:
|
||||||
render_stream(agent, prompt)
|
render_stream(agent, prompt)
|
||||||
@ -147,6 +159,7 @@ def run(
|
|||||||
config=config,
|
config=config,
|
||||||
store=store,
|
store=store,
|
||||||
memory=memory,
|
memory=memory,
|
||||||
|
permissions=permissions,
|
||||||
agent=agent,
|
agent=agent,
|
||||||
build_agent=build_agent,
|
build_agent=build_agent,
|
||||||
render_history=render_history,
|
render_history=render_history,
|
||||||
|
|||||||
23
src/cc_slim/permissions.py
Normal file
23
src/cc_slim/permissions.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionChecker:
|
||||||
|
def __init__(self, auto_approve: bool = False) -> None:
|
||||||
|
self.auto_approve = auto_approve
|
||||||
|
self.auto_allowed_tools = ["Read", "Glob", "Grep"]
|
||||||
|
self.confirm_required_tools = ["Write", "Edit", "Bash"]
|
||||||
|
|
||||||
|
def requires_confirmation(self, tool_name: str) -> bool:
|
||||||
|
if self.auto_approve:
|
||||||
|
return False
|
||||||
|
return tool_name in self.confirm_required_tools
|
||||||
|
|
||||||
|
def set_auto_approve(self, enabled: bool) -> None:
|
||||||
|
self.auto_approve = enabled
|
||||||
|
|
||||||
|
def status(self) -> dict[str, object]:
|
||||||
|
return {
|
||||||
|
"auto_approve": self.auto_approve,
|
||||||
|
"auto_allowed_tools": list(self.auto_allowed_tools),
|
||||||
|
"confirm_required_tools": list(self.confirm_required_tools),
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user