"""沙箱执行器:可插拔的任务执行接口。 零第三方依赖,只用 Python 标准库;不重写 agent 逻辑。 - Executor:基类,子类必须实现 execute(task)。 - LocalExecutor:先行实现,复用 host.py 现有 worktree + codex 沙箱, 通过 host.py CLI 子进程做只读查询封装;不直接跑模型。 - DockerExecutor:占位,未接通容器执行。 - CloudExecutor:占位,未接通云端执行。 """ import shutil import subprocess import sys MAX_STDOUT_CHARS = 2000 class Executor: """执行器基类。 task 是 dict,含 id、project_id、repo、prompt、branch(可选)。 execute(task) -> dict,至少含 executor、task_id、status、stdout。 """ def execute(self, task) -> dict: raise NotImplementedError("子类必须实现 execute(task)") def _truncate_stdout(text): if text is None: return "" return str(text)[:MAX_STDOUT_CHARS] def _default_runner(cmd): """subprocess.run 的薄封装:捕获输出、文本模式。""" return subprocess.run(cmd, capture_output=True, text=True) class LocalExecutor(Executor): """本地执行器:调 host.py CLI 子进程做执行封装。 真正跑模型回合的 dispatch/run 由控制面在确认模型 key 供给后才 调用,本模块只做执行器骨架和 CLI 调用封装。因此 execute() 在此 只发 host.py 的只读命令(例如 status),绝不在 execute 里直接 跑模型回合;需要跑模型时,控制面另行调用 host.py dispatch/run。 构造函数:LocalExecutor(host_py_path, config_path=None, runner=None)。 runner 默认是 subprocess.run 的薄封装;单测可注入假 runner,假 runner 返回 CompletedProcess 风格的 result 对象(含 returncode、 stdout),本类只读这两个字段,不依赖其它字段。 """ def __init__(self, host_py_path, config_path=None, runner=None): self.host_py_path = host_py_path self.config_path = config_path self.runner = runner if runner is not None else _default_runner def _command(self, task_id): cmd = [sys.executable, self.host_py_path] if self.config_path is not None: cmd += ["--config", self.config_path] cmd += ["status", "--task", str(task_id)] return cmd def execute(self, task) -> dict: task_id = task.get("id") if isinstance(task, dict) else None if task_id is None: raise ValueError("任务缺少 id") result = self.runner(self._command(task_id)) returncode = getattr(result, "returncode", 1) stdout = _truncate_stdout(getattr(result, "stdout", "")) return { "executor": "local", "task_id": task_id, "status": "completed" if returncode == 0 else "failed", "stdout": stdout, "returncode": returncode, } class DockerExecutor(Executor): """容器执行器(占位,未接通)。 本机无 docker(shutil.which("docker") 为 None)时 raise RuntimeError("docker not available"),绝不静默降级为本地执行。 有 docker 时也只返回占位失败,不真跑容器。 后续接线:容器挂载(把任务 worktree 以只读/受限写挂载进容器)、 网络隔离(默认无外网,仅放行模型 API 所需出口)、资源限额与 容器回收,届时再替换本占位实现。 """ def execute(self, task) -> dict: task_id = task.get("id") if isinstance(task, dict) else None if task_id is None: raise ValueError("任务缺少 id") if shutil.which("docker") is None: raise RuntimeError("docker not available") return { "executor": "docker", "task_id": task_id, "status": "failed", "stdout": "docker executor not implemented: " "container mount and network isolation pending", "returncode": None, } class CloudExecutor(Executor): """云端执行器(占位,未接通)。 execute(task) 一律 raise RuntimeError,绝不假装已接通云端。 接通前提:真实云账号 + 预算上限配置,届时由控制面注入凭证与 限额后再替换本占位实现。 """ def execute(self, task) -> dict: raise RuntimeError( "cloud executor not connected: missing cloud account " "and budget limit")