# Phase 2:执行引擎 日期:2026-04-04 状态:待实施 依赖:Phase 1 完成 --- ## 1. 本阶段目的 实现代码指挥官的核心执行能力: - AI CLI Adapter:统一接口适配不同 AI CLI - Sandbox Executor:沙盒环境执行 - Direct Executor:直接执行低风险任务 - Security Classifier:安全分级 --- ## 2. 详细任务 ### 2.1 AI CLI Adapter **新文件**: `backend/app/agents/tools/ai_adapter.py` ```python from abc import ABC, abstractmethod from pathlib import Path from dataclasses import dataclass @dataclass class CodeExecutionResult: success: bool message: str files_created: list[str] output: str error: str | None class AICLIAdapter(ABC): @property @abstractmethod def cli_name(self) -> str: """CLI 命令名称,如 'claude', 'gemini'""" pass @property @abstractmethod def requires_workspace(self) -> bool: """是否需要工作目录""" pass @abstractmethod def build_command(self, prompt: str, workspace: Path | None) -> list[str]: """构建 CLI 命令""" pass @abstractmethod def parse_output(self, output: str) -> CodeExecutionResult: """解析 CLI 输出""" pass @abstractmethod def is_installed(self) -> bool: """检查 CLI 是否已安装""" pass class ClaudeAdapter(AICLIAdapter): cli_name = "claude" requires_workspace = True def build_command(self, prompt: str, workspace: Path | None) -> list[str]: return ["claude", "-p", prompt, "--dangerously-skip-permissions"] # ... 其他方法实现 class GeminiAdapter(AICLIAdapter): cli_name = "gemini" requires_workspace = False # ... class CodexAdapter(AICLIAdapter): cli_name = "codex" # ... class OpenCodeAdapter(AICLIAdapter): cli_name = "opencode" # ... ``` ### 2.2 Security Classifier **新文件**: `backend/app/agents/tools/security_classifier.py` ```python from enum import Enum class RiskLevel(Enum): LOW = "low" # 直接执行 HIGH = "high" # 沙盒执行 class SecurityClassifier: HIGH_RISK_KEYWORDS = [ "修改", "编辑", "删除", "移动", "Jarvis", "backend", "frontend", "git", "config", ".env", ] LOW_RISK_KEYWORDS = [ "demo", "示例", "贪食蛇", "俄罗斯方块", "小游戏", "独立项目", "新项目", "创建一个", "写一个", ] def classify(self, task_description: str, target_path: str | None = None) -> RiskLevel: # 1. 检查高风险关键词 if any(kw in task_description for kw in self.HIGH_RISK_KEYWORDS): return RiskLevel.HIGH # 2. 检查目标路径 if target_path and self._is_project_path(target_path): return RiskLevel.HIGH # 3. 检查低风险关键词 if any(kw in task_description for kw in self.LOW_RISK_KEYWORDS): return RiskLevel.LOW # 4. 默认高风险 return RiskLevel.HIGH def _is_project_path(self, path: str) -> bool: # 检查是否指向 Jarvis 项目路径 return "Jarvis" in path or "backend/app" in path ``` ### 2.3 Sandbox Executor **新文件**: `backend/app/agents/tools/sandbox_executor.py` ```python import tempfile import shutil import asyncio from pathlib import Path from dataclasses import dataclass, field from typing import AsyncGenerator @dataclass class SandboxEnvironment: workspace_path: Path session_id: str @staticmethod async def create() -> "SandboxEnvironment": """创建新的沙盒环境""" temp_dir = tempfile.mkdtemp(prefix="jarvis_code_") session_id = Path(temp_dir).name return SandboxEnvironment( workspace_path=Path(temp_dir), session_id=session_id, ) async def cleanup(self): """清理沙盒环境""" if self.workspace_path.exists(): shutil.rmtree(self.workspace_path) @dataclass class ExecutionResult: success: bool exit_code: int stdout: str stderr: str files_created: list[str] = field(default_factory=list) class SandboxExecutor: def __init__(self, adapter: AICLIAdapter, timeout: int = 300): self.adapter = adapter self.timeout = timeout self._sessions: dict[str, SandboxEnvironment] = {} async def execute( self, prompt: str, session_id: str | None = None ) -> AsyncGenerator[str, None]: """执行代码任务,yield 实时输出""" # 1. 创建或复用沙盒环境 if session_id and session_id in self._sessions: env = self._sessions[session_id] else: env = await SandboxEnvironment.create() self._sessions[env.session_id] = env session_id = env.session_id # 2. 构建命令 cmd = self.adapter.build_command(prompt, env.workspace_path) # 3. 异步执行,实时 yield 输出 process = await asyncio.create_subprocess_exec( *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd=str(env.workspace_path), ) # 4. 实时读取输出 while True: line = await process.stdout.readline() if not line: break yield line.decode() # 5. 等待完成 await process.wait() # 6. 收集结果 return ExecutionResult( success=process.returncode == 0, exit_code=process.returncode or 0, stdout=..., stderr=..., files_created=self._list_created_files(env.workspace_path), ) async def cleanup_session(self, session_id: str): """清理指定会话""" if session_id in self._sessions: await self._sessions[session_id].cleanup() del self._sessions[session_id] ``` ### 2.4 Direct Executor **新文件**: `backend/app/agents/tools/direct_executor.py` ```python class DirectExecutor: def __init__(self, adapter: AICLIAdapter, timeout: int = 60): self.adapter = adapter self.timeout = timeout async def execute(self, prompt: str) -> ExecutionResult: """直接执行,不需要沙盒""" if not self.adapter.is_installed(): return ExecutionResult( success=False, exit_code=-1, stdout="", stderr=f"{self.adapter.cli_name} is not installed", ) cmd = self.adapter.build_command(prompt, None) process = await asyncio.create_subprocess_exec( *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) try: stdout, stderr = await asyncio.wait_for( process.communicate(), timeout=self.timeout, ) return ExecutionResult( success=process.returncode == 0, exit_code=process.returncode or 0, stdout=stdout.decode(), stderr=stderr.decode(), ) except asyncio.TimeoutError: process.kill() return ExecutionResult( success=False, exit_code=-1, stdout="", stderr=f"Execution timed out after {self.timeout}s", ) ``` --- ## 3. 核心文件清单 | 文件 | 操作 | 说明 | |------|------|------| | `ai_adapter.py` | 新增 | 抽象基类 + 4 个具体实现 | | `security_classifier.py` | 新增 | 安全分级器 | | `sandbox_executor.py` | 新增 | 沙盒执行器 | | `direct_executor.py` | 新增 | 直接执行器 | --- ## 4. 验收标准 - [ ] `AICLIAdapter` 可以正确识别 4 种 CLI - [ ] `SecurityClassifier` 能正确分类高低风险 - [ ] `SandboxExecutor` 能创建、执行、清理沙盒 - [ ] `DirectExecutor` 能直接执行低风险任务 - [ ] 所有执行器支持流式输出 --- ## 5. 风险与缓解 | 风险 | 缓解 | |------|------| | AI CLI 未安装 | `is_installed()` 检查 + 友好提示 | | 执行超时 | `timeout` 参数控制 | | 沙盒清理遗漏 | 使用 `finally` 块确保清理 | --- ## 6. 依赖关系 ``` Phase 1(基础设施) ↓ 本阶段 → Phase 3(Agent 集成) → Phase 4(流式交互) ```