[外部工具边界]:完成Open Interpreter和Playwright适配边界,包含默认关闭、高风险确认和GUI控制拒绝测试

This commit is contained in:
mkbk
2026-06-18 22:13:29 +08:00
parent af7eb25ba6
commit 351e722898
5 changed files with 311 additions and 9 deletions
+10
View File
@@ -15,6 +15,12 @@ from .agent_memory import (
SQLiteMemoryManager,
)
from .events import PipelineEvent, PipelineEventBus
from .external_adapters import (
BrowserPlaywrightAdapter,
ComputerControlProvider,
OpenInterpreterAdapter,
planned_external_adapters,
)
from .full_duplex_control import (
CancellationGraph,
CancellationToken,
@@ -100,6 +106,10 @@ __all__ = [
"SQLiteMemoryManager",
"PipelineEvent",
"PipelineEventBus",
"BrowserPlaywrightAdapter",
"ComputerControlProvider",
"OpenInterpreterAdapter",
"planned_external_adapters",
"CancellationGraph",
"CancellationToken",
"FullDuplexStateMachine",
+176
View File
@@ -0,0 +1,176 @@
from __future__ import annotations
import importlib.util
import shutil
import subprocess
from dataclasses import dataclass
from pathlib import Path
from .config import AppConfig
from .tool_router import ToolCallRequest, ToolContext, ToolResult
CODEX_COMPUTER_USE_SAFETY_REFERENCE = (
"Codex Computer Use is referenced only for safety-confirmation principles; "
"Owner uses public macOS Accessibility, Playwright, or trycua-style providers in future changes."
)
def is_high_risk_task(text: str) -> bool:
lowered = text.lower()
keywords = (
"delete",
"upload",
"payment",
"purchase",
"trade",
"account",
"chmod",
"rm ",
"删除",
"上传",
"支付",
"购买",
"交易",
"账号",
"权限",
)
return any(keyword in lowered for keyword in keywords)
@dataclass(slots=True)
class OpenInterpreterAdapter:
enabled: bool = False
command: str = "openinterpreter"
dry_run: bool = True
name: str = "openinterpreter.run"
def execute(self, request: ToolCallRequest, context: ToolContext) -> ToolResult:
if not self.enabled:
return ToolResult(
request.id,
"failed",
error_code="OPENINTERPRETER_DISABLED",
audit_summary="Open Interpreter adapter disabled",
)
executable = self._resolve_command()
if executable is None:
return ToolResult(
request.id,
"failed",
error_code="OPENINTERPRETER_UNAVAILABLE",
audit_summary="Open Interpreter command unavailable",
)
task = str(request.arguments.get("task", ""))
if is_high_risk_task(task):
return ToolResult(
request.id,
"confirmation_required",
error_code="OPENINTERPRETER_HIGH_RISK",
audit_summary="Open Interpreter high risk task requires confirmation",
)
if self.dry_run:
return ToolResult(
request.id,
"success",
f"dry-run Open Interpreter task: {task}",
audit_summary="Open Interpreter dry run",
)
completed = subprocess.run(
[executable, "exec", task],
cwd=context.cwd,
check=False,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
timeout=max(1, int(request.timeout_ms / 1000)),
)
return ToolResult(
request.id,
"success" if completed.returncode == 0 else "failed",
completed.stdout,
error_code=None if completed.returncode == 0 else "OPENINTERPRETER_FAILED",
audit_summary=f"Open Interpreter exited {completed.returncode}",
)
def _resolve_command(self) -> str | None:
candidate = Path(self.command)
if candidate.exists():
return str(candidate)
return shutil.which(self.command)
@dataclass(slots=True)
class BrowserPlaywrightAdapter:
enabled: bool = False
isolated_context: bool = True
dry_run: bool = True
name: str = "browser.playwright"
def execute(self, request: ToolCallRequest, context: ToolContext) -> ToolResult:
if not self.enabled:
return ToolResult(
request.id,
"failed",
error_code="PLAYWRIGHT_DISABLED",
audit_summary="Playwright adapter disabled",
)
if importlib.util.find_spec("playwright") is None:
return ToolResult(
request.id,
"failed",
error_code="PLAYWRIGHT_UNAVAILABLE",
audit_summary="Playwright unavailable",
)
task = str(request.arguments.get("task", request.natural_language_intent))
if is_high_risk_task(task):
return ToolResult(
request.id,
"confirmation_required",
error_code="PLAYWRIGHT_SENSITIVE_ACTION",
audit_summary="sensitive browser action requires confirmation",
)
if self.dry_run:
context_type = "isolated" if self.isolated_context else "shared"
return ToolResult(
request.id,
"success",
f"dry-run Playwright {context_type} context task: {task}",
audit_summary="Playwright dry run",
)
return ToolResult(
request.id,
"failed",
error_code="PLAYWRIGHT_RUNTIME_NOT_WIRED",
audit_summary="Playwright runtime not wired",
)
@dataclass(slots=True)
class ComputerControlProvider:
enabled: bool = False
name: str = "computer.control"
def execute(self, request: ToolCallRequest, context: ToolContext) -> ToolResult:
return ToolResult(
request.id,
"failed",
error_code="COMPUTER_CONTROL_UNSUPPORTED",
audit_summary="direct GUI control is not supported in the first full-duplex Agent version",
)
def planned_external_adapters(config: AppConfig) -> dict[str, object]:
return {
"openinterpreter.run": OpenInterpreterAdapter(
enabled=config.openinterpreter_enabled,
command=config.openinterpreter_command,
dry_run=True,
),
"browser.playwright": BrowserPlaywrightAdapter(
enabled=config.browser_playwright_enabled,
isolated_context=True,
dry_run=True,
),
"computer.control": ComputerControlProvider(enabled=config.computer_control_enabled),
}