plan mode and build mode

This commit is contained in:
hc 2026-04-11 18:00:18 +08:00
parent 0fbe0f7ffa
commit 2738175b4d
7 changed files with 69 additions and 4 deletions

View File

@ -33,6 +33,8 @@ REPL 内所有 slash command 由程序直接处理,不进入 agent loop。
写操作和 Bash 默认需要确认,可通过 `--auto-approve``/permissions auto-on` 跳过。 写操作和 Bash 默认需要确认,可通过 `--auto-approve``/permissions auto-on` 跳过。
支持 `/mode build``/mode plan` 两种模式,`plan` 为只读规划模式。
未明确说明时,使用以下默认值: 未明确说明时,使用以下默认值:
- 工作目录:当前进程目录 - 工作目录:当前进程目录

View File

@ -26,6 +26,7 @@
- 会话与 memory 管理优先使用 slash command而不是自然语言或 `Bash` 探查对应文件 - 会话与 memory 管理优先使用 slash command而不是自然语言或 `Bash` 探查对应文件
- 长期有价值的项目约束或偏好,优先使用 `/remember` 保存 - 长期有价值的项目约束或偏好,优先使用 `/remember` 保存
- 高风险操作会触发确认,优先先读再改,减少无意义审批 - 高风险操作会触发确认,优先先读再改,减少无意义审批
- 复杂任务可先切换到 `/mode plan` 进行规划,再切换回 `/mode build` 执行
- Windows 下先验证 shell 兼容性,再选择命令写法 - Windows 下先验证 shell 兼容性,再选择命令写法
## 沟通风格 ## 沟通风格

View File

@ -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.mode import ModeState
from cc_slim.permissions import PermissionChecker from cc_slim.permissions import PermissionChecker
from cc_slim.session import SessionStore from cc_slim.session import SessionStore
@ -27,6 +28,7 @@ def handle_command(
config: Any, config: Any,
store: SessionStore, store: SessionStore,
memory: MemoryStore, memory: MemoryStore,
mode: ModeState,
permissions: PermissionChecker, permissions: PermissionChecker,
agent: Agent, agent: Agent,
build_agent: Any, build_agent: Any,
@ -47,6 +49,11 @@ 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("/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 - 查看当前权限状态")
console.print("/permissions auto-on - 开启自动批准") console.print("/permissions auto-on - 开启自动批准")
console.print("/permissions auto-off - 关闭自动批准") console.print("/permissions auto-off - 关闭自动批准")
@ -55,7 +62,26 @@ def handle_command(
if name in {"/clear", "/new"}: if name in {"/clear", "/new"}:
session_meta = store.create_session(config.model) session_meta = store.create_session(config.model)
console.print(f"已创建新 session: {session_meta.get('session_id')}") console.print(f"已创建新 session: {session_meta.get('session_id')}")
return build_agent(root, config, store, session_meta, []) 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}")
return agent
if name == "/mode":
if not args:
console.print(f"当前模式: {mode.mode}")
return agent
if args in {"build", "plan"}:
mode.set_mode(args)
permissions.set_mode(args)
console.print(f"当前模式: {args}")
return agent
console.print("[red]error:[/red] 仅支持 /mode build 或 /mode plan")
return agent
if name == "/history": if name == "/history":
render_history(store) render_history(store)
@ -69,7 +95,7 @@ def handle_command(
session_meta = store.load_meta(session_id) session_meta = store.load_meta(session_id)
restored_history = store.load_messages(session_id) restored_history = store.load_messages(session_id)
console.print(f"已恢复 session: {session_meta.get('session_id')}") console.print(f"已恢复 session: {session_meta.get('session_id')}")
return build_agent(root, config, store, session_meta, restored_history) return build_agent(root, config, store, permissions, session_meta, restored_history)
if name == "/memory": if name == "/memory":
content = memory.read() content = memory.read()
@ -88,6 +114,7 @@ def handle_command(
status = permissions.status() status = permissions.status()
console.print(f"auto_approve: {status['auto_approve']}") console.print(f"auto_approve: {status['auto_approve']}")
console.print(f"mode: {status['mode']}")
console.print(f"自动允许: {', '.join(status['auto_allowed_tools'])}") console.print(f"自动允许: {', '.join(status['auto_allowed_tools'])}")
console.print(f"需要确认: {', '.join(status['confirm_required_tools'])}") console.print(f"需要确认: {', '.join(status['confirm_required_tools'])}")
return agent return agent

View File

@ -271,11 +271,13 @@ 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 not self.permission_checker.is_allowed(name):
return self.permission_checker.denial_reason(name)
if self.permission_checker and self.permission_checker.requires_confirmation(name): if self.permission_checker and self.permission_checker.requires_confirmation(name):
if not self.confirm_tool: if not self.confirm_tool:
return f"Permission denied for tool: {name}" return self.permission_checker.denial_reason(name)
if not self.confirm_tool(name, payload): if not self.confirm_tool(name, payload):
return f"Permission denied for tool: {name}" return self.permission_checker.denial_reason(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}"

View File

@ -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.mode import ModeState
from cc_slim.permissions import PermissionChecker 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
@ -106,7 +107,9 @@ def run(
root = cwd.resolve() root = cwd.resolve()
store = SessionStore(root) store = SessionStore(root)
memory = MemoryStore(root) memory = MemoryStore(root)
mode = ModeState()
permissions = PermissionChecker(auto_approve=auto_approve) permissions = PermissionChecker(auto_approve=auto_approve)
permissions.set_mode(mode.mode)
if history: if history:
render_history(store) render_history(store)
return return
@ -159,6 +162,7 @@ def run(
config=config, config=config,
store=store, store=store,
memory=memory, memory=memory,
mode=mode,
permissions=permissions, permissions=permissions,
agent=agent, agent=agent,
build_agent=build_agent, build_agent=build_agent,

13
src/cc_slim/mode.py Normal file
View File

@ -0,0 +1,13 @@
from __future__ import annotations
class ModeState:
def __init__(self, mode: str = "build") -> None:
self.mode = mode if mode in {"build", "plan"} else "build"
def set_mode(self, mode: str) -> None:
if mode in {"build", "plan"}:
self.mode = mode
def is_plan(self) -> bool:
return self.mode == "plan"

View File

@ -4,9 +4,20 @@ from __future__ import annotations
class PermissionChecker: class PermissionChecker:
def __init__(self, auto_approve: bool = False) -> None: def __init__(self, auto_approve: bool = False) -> None:
self.auto_approve = auto_approve self.auto_approve = auto_approve
self.mode = "build"
self.auto_allowed_tools = ["Read", "Glob", "Grep"] self.auto_allowed_tools = ["Read", "Glob", "Grep"]
self.confirm_required_tools = ["Write", "Edit", "Bash"] self.confirm_required_tools = ["Write", "Edit", "Bash"]
def is_allowed(self, tool_name: str) -> bool:
if self.mode == "plan" and tool_name in self.confirm_required_tools:
return False
return True
def denial_reason(self, tool_name: str) -> str:
if self.mode == "plan" and tool_name in self.confirm_required_tools:
return f"Tool blocked in plan mode: {tool_name}"
return f"Permission denied for tool: {tool_name}"
def requires_confirmation(self, tool_name: str) -> bool: def requires_confirmation(self, tool_name: str) -> bool:
if self.auto_approve: if self.auto_approve:
return False return False
@ -15,9 +26,14 @@ class PermissionChecker:
def set_auto_approve(self, enabled: bool) -> None: def set_auto_approve(self, enabled: bool) -> None:
self.auto_approve = enabled self.auto_approve = enabled
def set_mode(self, mode: str) -> None:
if mode in {"build", "plan"}:
self.mode = mode
def status(self) -> dict[str, object]: def status(self) -> dict[str, object]:
return { return {
"auto_approve": self.auto_approve, "auto_approve": self.auto_approve,
"mode": self.mode,
"auto_allowed_tools": list(self.auto_allowed_tools), "auto_allowed_tools": list(self.auto_allowed_tools),
"confirm_required_tools": list(self.confirm_required_tools), "confirm_required_tools": list(self.confirm_required_tools),
} }