Files
JARVIS/development-doc/plan/code-update/phase-2-execution-engine.md

322 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 3Agent 集成)
→ Phase 4流式交互
```