agent-Specialization/modules/execution_plane/fake.py
JOJO 27aeed70ca feat(execution-plane): Execution Contract 与替身执行器(改造第 4 步)
- modules/execution_plane:ExecutionBackend 协议(E1-E4 首版接口面)+
  FakeExecutionBackend 内存替身(零真实副作用)
- 接入:MainTerminal.execution_backend 属性(默认 None=真实链路),
  handle_tool_call 的 run_command 前台/后台、write_file、edit_file 四分支注入
- 替身路径自动跳过浅版本备份(不触真实磁盘)
- 契约文档 docs/execution_contract.md(本地文档目录,不入库):
  E1-E10 接口面归纳、权限分工、Host/Docker 后端映射
- 验收:ExecutionPlaneFakeBackendTest 全绿(替身接收 E1-E4 调用、
  结果结构同构消费、零磁盘写入、默认路径回归)
2026-09-07 23:00:31 +08:00

124 lines
4.3 KiB
Python
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.

"""FakeExecutionBackend内存替身执行器Execution Plane 的首个非真实实现)。
用途Runtime + 替身执行器的独立测试docs/execution_contract.md §6 验收)。
不产生任何真实副作用:不起子进程、不写磁盘、不开终端。
文件写落内存文件系统;命令执行只记录调用并返回固定结构。
非目标:不模拟命令真实输出语义、不模拟审批/权限裁决(那是 Runtime 编排层职责)。
"""
from __future__ import annotations
import time
import uuid
from typing import Any, Dict, List, Optional
class FakeExecutionBackend:
"""内存替身执行器。calls 记录全部调用(供测试断言)。"""
def __init__(self) -> None:
self.calls: List[Dict[str, Any]] = []
self.files: Dict[str, str] = {} # path -> content内存文件系统
self.background_commands: Dict[str, Dict[str, Any]] = {}
async def run_command(
self,
command: str,
*,
timeout: float,
sandbox_write_access: bool,
network_permission: str,
) -> Dict[str, Any]:
self.calls.append({
"op": "run_command",
"command": command,
"timeout": timeout,
"sandbox_write_access": sandbox_write_access,
"network_permission": network_permission,
})
return {
"success": True,
"status": "completed",
"output": f"[fake-exec] {command}",
"return_code": 0,
"truncated": False,
"elapsed_ms": 0,
}
def run_command_background(
self,
command: str,
*,
timeout: float,
conversation_id: Optional[str],
wait_seconds: float,
network_permission: str,
sandbox_write_access: bool,
) -> Dict[str, Any]:
command_id = f"fake_bg_{uuid.uuid4().hex[:8]}"
self.calls.append({
"op": "run_command_background",
"command": command,
"timeout": timeout,
"conversation_id": conversation_id,
"sandbox_write_access": sandbox_write_access,
"network_permission": network_permission,
})
self.background_commands[command_id] = {
"command": command,
"status": "completed",
"output": f"[fake-exec-bg] {command}",
"return_code": 0,
"created_at": time.time(),
}
return {
"success": True,
"command_id": command_id,
"status": "running_background",
}
def write_file(self, path: str, content: str, *, mode: str = "w") -> Dict[str, Any]:
original = self.files.get(path)
if mode == "a" and original is not None:
new_content = original + content
else:
new_content = content
self.files[path] = new_content
self.calls.append({"op": "write_file", "path": path, "mode": mode, "bytes": len(content)})
return {
"success": True,
"path": path,
"original_file": original,
"new_file": new_content,
}
def edit_file(self, path: str, replacements: List[Dict[str, Any]]) -> Dict[str, Any]:
original = self.files.get(path)
if original is None:
self.calls.append({"op": "edit_file", "path": path, "error": "not_found"})
return {"success": False, "error": f"文件不存在: {path}"}
new_content = original
applied = 0
for rep in replacements:
old = rep.get("old_string", "")
new = rep.get("new_string", "")
if not old or old not in new_content:
continue
if rep.get("replace_all"):
applied += new_content.count(old)
new_content = new_content.replace(old, new)
else:
new_content = new_content.replace(old, new, 1)
applied += 1
if applied == 0:
return {"success": False, "error": "未找到任何匹配内容"}
self.files[path] = new_content
self.calls.append({"op": "edit_file", "path": path, "replacements_applied": applied})
return {
"success": True,
"path": path,
"original_file": original,
"new_file": new_content,
"replacements_applied": applied,
}