[外部工具边界]:完成Open Interpreter和Playwright适配边界,包含默认关闭、高风险确认和GUI控制拒绝测试
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
Reference in New Issue
Block a user