113 lines
3.1 KiB
Python
113 lines
3.1 KiB
Python
|
|
"""
|
|||
|
|
Direct Executor - 直接执行器
|
|||
|
|
用于低风险任务,直接执行不隔离
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import asyncio
|
|||
|
|
import os
|
|||
|
|
import shutil
|
|||
|
|
import tempfile
|
|||
|
|
from pathlib import Path
|
|||
|
|
from typing import AsyncGenerator
|
|||
|
|
|
|||
|
|
from app.agents.tools.ai_adapter import AICLIAdapter
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ExecutionResult:
|
|||
|
|
"""执行结果"""
|
|||
|
|
|
|||
|
|
def __init__(
|
|||
|
|
self,
|
|||
|
|
success: bool,
|
|||
|
|
exit_code: int,
|
|||
|
|
stdout: str,
|
|||
|
|
stderr: str,
|
|||
|
|
):
|
|||
|
|
self.success = success
|
|||
|
|
self.exit_code = exit_code
|
|||
|
|
self.stdout = stdout
|
|||
|
|
self.stderr = stderr
|
|||
|
|
|
|||
|
|
|
|||
|
|
class DirectExecutor:
|
|||
|
|
"""直接执行器(用于低风险任务)"""
|
|||
|
|
|
|||
|
|
def __init__(self, adapter: AICLIAdapter, timeout: int = 60):
|
|||
|
|
self.adapter = adapter
|
|||
|
|
self.timeout = timeout
|
|||
|
|
|
|||
|
|
async def execute(
|
|||
|
|
self,
|
|||
|
|
prompt: str,
|
|||
|
|
) -> AsyncGenerator[str, None]:
|
|||
|
|
"""
|
|||
|
|
直接执行,不需要沙盒
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
prompt: 任务描述
|
|||
|
|
|
|||
|
|
Yields:
|
|||
|
|
str: 实时输出
|
|||
|
|
"""
|
|||
|
|
# 1. 检查 CLI 是否安装
|
|||
|
|
if not self.adapter.is_installed():
|
|||
|
|
yield f"[ERROR] {self.adapter.cli_name} is not installed\n"
|
|||
|
|
yield f"[ERROR] Please install {self.adapter.cli_name} first\n"
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 2. 构建命令
|
|||
|
|
cmd = self.adapter.build_command(prompt, None)
|
|||
|
|
|
|||
|
|
# 3. 异步执行,实时 yield 输出
|
|||
|
|
process = await asyncio.create_subprocess_exec(
|
|||
|
|
*cmd,
|
|||
|
|
stdout=asyncio.subprocess.PIPE,
|
|||
|
|
stderr=asyncio.subprocess.PIPE,
|
|||
|
|
env={**os.environ, "TERM": "xterm-256color"},
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 4. 实时读取输出
|
|||
|
|
stdout_lines = []
|
|||
|
|
stderr_lines = []
|
|||
|
|
|
|||
|
|
while True:
|
|||
|
|
try:
|
|||
|
|
line_bytes = await asyncio.wait_for(
|
|||
|
|
process.stdout.readline(),
|
|||
|
|
timeout=self.timeout,
|
|||
|
|
)
|
|||
|
|
if not line_bytes:
|
|||
|
|
break
|
|||
|
|
line = line_bytes.decode("utf-8", errors="replace")
|
|||
|
|
stdout_lines.append(line)
|
|||
|
|
yield line
|
|||
|
|
except asyncio.TimeoutError:
|
|||
|
|
process.kill()
|
|||
|
|
yield f"\n[ERROR] Execution timed out after {self.timeout}s\n"
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
# 5. 读取 stderr
|
|||
|
|
stderr_bytes = await process.communicate()
|
|||
|
|
if stderr_bytes[1]:
|
|||
|
|
stderr = stderr_bytes[1].decode("utf-8", errors="replace")
|
|||
|
|
stderr_lines.append(stderr)
|
|||
|
|
yield f"\n[STDERR]\n{stderr}\n"
|
|||
|
|
|
|||
|
|
# 6. 完成标记
|
|||
|
|
yield f"\n[EXIT_CODE] {process.returncode or 0}\n"
|
|||
|
|
yield f"\n[COMPLETE] success={process.returncode == 0}\n"
|
|||
|
|
|
|||
|
|
async def execute_sync(self, prompt: str) -> ExecutionResult:
|
|||
|
|
"""同步执行并返回完整结果"""
|
|||
|
|
output_parts = []
|
|||
|
|
async for line in self.execute(prompt):
|
|||
|
|
output_parts.append(line)
|
|||
|
|
|
|||
|
|
output = "".join(output_parts)
|
|||
|
|
return ExecutionResult(
|
|||
|
|
success="[COMPLETE] success=True" in output,
|
|||
|
|
exit_code=0,
|
|||
|
|
stdout=output,
|
|||
|
|
stderr="",
|
|||
|
|
)
|