Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,390 @@
|
||||
"""Behavior contracts for cua-driver's verify/escalate and typed-browser ladder.
|
||||
|
||||
The fixture used here is a deliberately selected and normalized ``tools/list``
|
||||
capture. It contains schemas, not machine/user state, and records the 0.9-era
|
||||
contract where input properties are the discovery surface.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutureTimeoutError
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, Dict, Optional
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
FIXTURE = Path(__file__).parents[1] / "fixtures" / "cua_driver_0_9_tools_list.json"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _reset_computer_use_state():
|
||||
from tools.computer_use.tool import reset_backend_for_tests
|
||||
|
||||
reset_backend_for_tests()
|
||||
yield
|
||||
reset_backend_for_tests()
|
||||
|
||||
|
||||
class _FakeSession:
|
||||
def __init__(
|
||||
self,
|
||||
out: Optional[Dict[str, Any]] = None,
|
||||
*,
|
||||
input_properties: Optional[Dict[str, set[str]]] = None,
|
||||
tools: Optional[set[str]] = None,
|
||||
) -> None:
|
||||
self.out = out or {
|
||||
"isError": False,
|
||||
"data": {},
|
||||
"structuredContent": {"effect": "confirmed"},
|
||||
}
|
||||
self.input_properties = input_properties or {}
|
||||
self.tools = tools or {"bring_to_front", *self.input_properties}
|
||||
self.calls: list[tuple[str, Dict[str, Any]]] = []
|
||||
|
||||
def call_tool(self, name: str, args: Dict[str, Any], timeout: float = 30.0):
|
||||
self.calls.append((name, dict(args)))
|
||||
return self.out
|
||||
|
||||
def supports_capability(self, capability: str, tool: Optional[str] = None) -> bool:
|
||||
return False
|
||||
|
||||
def supports_input_property(self, tool: str, prop: str) -> bool:
|
||||
return prop in self.input_properties.get(tool, set())
|
||||
|
||||
def _has_tool(self, name: str) -> bool:
|
||||
return name in self.tools
|
||||
|
||||
|
||||
def _make_backend(session: _FakeSession):
|
||||
from tools.computer_use.cua_backend import CuaDriverBackend
|
||||
|
||||
backend = CuaDriverBackend.__new__(CuaDriverBackend)
|
||||
backend._session = session
|
||||
backend._session_id = "hermes-session"
|
||||
backend._snapshot_tokens = {}
|
||||
backend._active_pid = 42
|
||||
backend._active_window_id = 7
|
||||
return backend
|
||||
|
||||
|
||||
def _driver_result(payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
return {"isError": False, "data": {}, "structuredContent": payload}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Selected live schema and foreground delivery
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_normalized_fixture_is_sanitized_and_records_the_selected_contract():
|
||||
fixture = json.loads(FIXTURE.read_text(encoding="utf-8"))
|
||||
tools = {tool["name"]: tool for tool in fixture["tools"]}
|
||||
|
||||
assert fixture["contract_epoch"] == "cua-driver-0.9"
|
||||
assert fixture["observed_reported_version"] == "0.8.3"
|
||||
assert fixture["capability_version"] == "1"
|
||||
assert fixture["observed_tool_count"] == 49
|
||||
assert "delivery_mode" in tools["click"]["inputSchema"]["properties"]
|
||||
assert "delivery_mode" in tools["type_text"]["inputSchema"]["properties"]
|
||||
assert all(
|
||||
"input.delivery_mode" not in tool["capabilities"] for tool in tools.values()
|
||||
)
|
||||
assert "bring_to_front" in tools
|
||||
assert "bring_to_front" not in tools["click"]["inputSchema"]["properties"]
|
||||
assert {
|
||||
"get_browser_state",
|
||||
"browser_prepare",
|
||||
"browser_navigate",
|
||||
"browser_click",
|
||||
"browser_type",
|
||||
"browser_pointer",
|
||||
}.issubset(tools)
|
||||
|
||||
serialized = json.dumps(fixture)
|
||||
for forbidden in (
|
||||
"/Users/",
|
||||
"\\Users\\",
|
||||
"localhost",
|
||||
"http://",
|
||||
"https://",
|
||||
"token-",
|
||||
):
|
||||
assert forbidden not in serialized
|
||||
|
||||
|
||||
def test_foreground_support_is_discovered_from_tool_input_schema():
|
||||
from tools.computer_use.cua_backend import _CuaDriverSession
|
||||
|
||||
fixture = json.loads(FIXTURE.read_text(encoding="utf-8"))
|
||||
listed = []
|
||||
for item in fixture["tools"]:
|
||||
listed.append(
|
||||
SimpleNamespace(
|
||||
name=item["name"],
|
||||
capabilities=item["capabilities"],
|
||||
inputSchema=item["inputSchema"],
|
||||
model_extra={},
|
||||
)
|
||||
)
|
||||
|
||||
class _McpSession:
|
||||
async def list_tools(self):
|
||||
return SimpleNamespace(tools=listed, model_extra={})
|
||||
|
||||
session = _CuaDriverSession.__new__(_CuaDriverSession)
|
||||
session._capabilities = {}
|
||||
session._input_properties = {}
|
||||
session._capability_version = ""
|
||||
asyncio.run(session._populate_capabilities(_McpSession()))
|
||||
|
||||
assert session.supports_input_property("click", "delivery_mode") is True
|
||||
assert session.supports_input_property("type_text", "delivery_mode") is True
|
||||
assert session.supports_input_property("bring_to_front", "delivery_mode") is False
|
||||
assert session.supports_capability("input.delivery_mode", tool="click") is False
|
||||
|
||||
|
||||
def test_foreground_focus_is_a_separate_call_before_action():
|
||||
session = _FakeSession(input_properties={"click": {"delivery_mode"}})
|
||||
backend = _make_backend(session)
|
||||
|
||||
result = backend.click(
|
||||
element=3,
|
||||
delivery_mode="foreground",
|
||||
bring_to_front=True,
|
||||
)
|
||||
|
||||
assert result.ok is True
|
||||
assert [name for name, _ in session.calls] == ["bring_to_front", "click"]
|
||||
focus_args = session.calls[0][1]
|
||||
action_args = session.calls[1][1]
|
||||
assert focus_args == {"pid": 42, "window_id": 7}
|
||||
assert action_args["delivery_mode"] == "foreground"
|
||||
assert "bring_to_front" not in action_args
|
||||
|
||||
|
||||
def test_foreground_refuses_only_when_schema_lacks_delivery_property():
|
||||
backend = _make_backend(_FakeSession())
|
||||
|
||||
result = backend.click(element=3, delivery_mode="foreground")
|
||||
|
||||
assert result.ok is False
|
||||
assert result.code == "foreground_unsupported"
|
||||
assert "update" not in result.message.lower()
|
||||
assert backend._session.calls == []
|
||||
|
||||
|
||||
def test_invalid_delivery_mode_is_rejected_before_driver_call():
|
||||
session = _FakeSession(input_properties={"type_text": {"delivery_mode"}})
|
||||
backend = _make_backend(session)
|
||||
|
||||
result = backend.type_text("hello", delivery_mode="sideways")
|
||||
|
||||
assert result.ok is False
|
||||
assert result.code == "bad_delivery_mode"
|
||||
assert session.calls == []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Deterministic verdict precedence and backend isolation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("result_kwargs", "decision"),
|
||||
[
|
||||
({"ok": True, "effect": "confirmed", "verified": True}, "done"),
|
||||
(
|
||||
{
|
||||
"ok": True,
|
||||
"effect": "unverifiable",
|
||||
"verified": False,
|
||||
"escalation": {"recommended": "foreground"},
|
||||
},
|
||||
"verify_fresh_state",
|
||||
),
|
||||
({"ok": True, "effect": "suspected_noop"}, "escalate"),
|
||||
({"ok": False, "code": "browser_input_trust_unavailable"}, "escalate"),
|
||||
],
|
||||
)
|
||||
def test_action_verdict_precedence(result_kwargs, decision):
|
||||
from tools.computer_use.backend import ActionResult
|
||||
from tools.computer_use.tool import _classify_action_result
|
||||
|
||||
result = ActionResult(action="click", **result_kwargs)
|
||||
assert _classify_action_result(result)["decision"] == decision
|
||||
|
||||
|
||||
def test_backends_are_isolated_by_hermes_session_and_reused_within_it():
|
||||
from tools.computer_use import tool as computer_use
|
||||
|
||||
created = []
|
||||
|
||||
class _Backend:
|
||||
def __init__(self, permission_mode="standard"):
|
||||
self.permission_mode = permission_mode
|
||||
created.append(self)
|
||||
|
||||
def start(self):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
with patch("tools.computer_use.cua_backend.CuaDriverBackend", _Backend):
|
||||
first = computer_use._get_backend(session_id="conversation-a")
|
||||
first_again = computer_use._get_backend(session_id="conversation-a")
|
||||
second = computer_use._get_backend(session_id="conversation-b")
|
||||
|
||||
assert first is first_again
|
||||
assert first is not second
|
||||
assert created == [first, second]
|
||||
|
||||
|
||||
def test_release_seam_stops_exact_backend_and_clears_session_state():
|
||||
from tools.computer_use import tool as computer_use
|
||||
|
||||
first = MagicMock()
|
||||
second = MagicMock()
|
||||
computer_use._backends.update({
|
||||
"conversation-a": first,
|
||||
"conversation-b": second,
|
||||
})
|
||||
computer_use._backend_call_locks.update({
|
||||
"conversation-a": computer_use.threading.RLock(),
|
||||
"conversation-b": computer_use.threading.RLock(),
|
||||
})
|
||||
computer_use._session_auto_approve["conversation-a"] = True
|
||||
computer_use._always_allow["conversation-a"] = {
|
||||
("click", "background"),
|
||||
}
|
||||
|
||||
assert computer_use.release_computer_use_session("conversation-a") is True
|
||||
assert computer_use.release_computer_use_session("conversation-a") is False
|
||||
|
||||
first.stop.assert_called_once_with()
|
||||
second.stop.assert_not_called()
|
||||
assert "conversation-a" not in computer_use._backends
|
||||
assert "conversation-a" not in computer_use._backend_call_locks
|
||||
assert "conversation-a" not in computer_use._session_auto_approve
|
||||
assert "conversation-a" not in computer_use._always_allow
|
||||
assert computer_use._backends["conversation-b"] is second
|
||||
|
||||
|
||||
def test_release_seam_evicts_state_even_when_backend_stop_fails():
|
||||
from tools.computer_use import tool as computer_use
|
||||
|
||||
backend = MagicMock()
|
||||
backend.stop.side_effect = RuntimeError("driver teardown failed")
|
||||
computer_use._backends["failed-run"] = backend
|
||||
computer_use._backend_call_locks["failed-run"] = computer_use.threading.RLock()
|
||||
computer_use._session_auto_approve["failed-run"] = True
|
||||
|
||||
assert computer_use.release_computer_use_session("failed-run") is True
|
||||
assert "failed-run" not in computer_use._backends
|
||||
assert "failed-run" not in computer_use._backend_call_locks
|
||||
assert "failed-run" not in computer_use._session_auto_approve
|
||||
|
||||
|
||||
def test_release_seam_waits_for_in_flight_action_before_stopping_backend():
|
||||
from tools.computer_use import tool as computer_use
|
||||
|
||||
backend = MagicMock()
|
||||
call_lock = computer_use.threading.RLock()
|
||||
computer_use._backends["cancelled-run"] = backend
|
||||
computer_use._backend_call_locks["cancelled-run"] = call_lock
|
||||
|
||||
pool = ThreadPoolExecutor(max_workers=1)
|
||||
try:
|
||||
call_lock.acquire()
|
||||
try:
|
||||
released = pool.submit(
|
||||
computer_use.release_computer_use_session,
|
||||
"cancelled-run",
|
||||
)
|
||||
with pytest.raises(FutureTimeoutError):
|
||||
released.result(timeout=0.05)
|
||||
backend.stop.assert_not_called()
|
||||
finally:
|
||||
call_lock.release()
|
||||
|
||||
assert released.result(timeout=1) is True
|
||||
finally:
|
||||
pool.shutdown(wait=True)
|
||||
backend.stop.assert_called_once_with()
|
||||
|
||||
|
||||
def test_concurrent_hermes_sessions_do_not_share_backend_state():
|
||||
from tools.computer_use import tool as computer_use
|
||||
|
||||
created = []
|
||||
|
||||
class _Backend:
|
||||
def __init__(self, permission_mode="standard"):
|
||||
self.permission_mode = permission_mode
|
||||
self.marker = len(created)
|
||||
created.append(self)
|
||||
|
||||
def start(self):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
def list_apps(self):
|
||||
return [{"marker": self.marker}]
|
||||
|
||||
def invoke(session_id):
|
||||
return json.loads(
|
||||
computer_use.handle_computer_use(
|
||||
{"action": "list_apps"},
|
||||
session_id=session_id,
|
||||
)
|
||||
)["apps"][0]["marker"]
|
||||
|
||||
with patch("tools.computer_use.cua_backend.CuaDriverBackend", _Backend):
|
||||
with ThreadPoolExecutor(max_workers=4) as executor:
|
||||
markers = list(
|
||||
executor.map(invoke, ["conversation-a", "conversation-b"] * 4)
|
||||
)
|
||||
|
||||
assert set(markers[0::2]).isdisjoint(set(markers[1::2]))
|
||||
assert len(set(markers[0::2])) == 1
|
||||
assert len(set(markers[1::2])) == 1
|
||||
assert len(created) == 2
|
||||
|
||||
|
||||
def test_persistent_focus_has_a_separate_approval_scope():
|
||||
from tools.computer_use import tool as computer_use
|
||||
|
||||
seen = []
|
||||
|
||||
def approve(action, args, summary):
|
||||
seen.append(action)
|
||||
return "approve_once" if action == "click" else "deny"
|
||||
|
||||
computer_use.set_approval_callback(approve)
|
||||
try:
|
||||
result = json.loads(
|
||||
computer_use.handle_computer_use(
|
||||
{
|
||||
"action": "click",
|
||||
"element": 1,
|
||||
"delivery_mode": "foreground",
|
||||
"bring_to_front": True,
|
||||
},
|
||||
session_id="approval-session",
|
||||
)
|
||||
)
|
||||
finally:
|
||||
computer_use.set_approval_callback(None)
|
||||
|
||||
assert seen == ["click", "bring_to_front"]
|
||||
assert result["error"] == "denied by user"
|
||||
assert result["action"] == "bring_to_front"
|
||||
|
||||
Reference in New Issue
Block a user