feat(chrome): let the IDE agent use the user's Chrome and beta.42
The AITURK Chrome extension now registers the user's Chrome as the desktop backend's browser controller, the way the ChatGPT extension lets its agent work in the user's browser. The agent's existing browser tools (navigate, snapshot, click, type, scroll, back, press, get_images, vision) run in its own background tabs grouped as "AITURK", or a tab the user hands over. Backend: a local desktop lane in the browser-control broker. One controller registered from the loopback desktop transport is offered to every desktop session as a lane template; each session still gets its own exact scope on first use. The lane has its own switch (browser.extension_control.local_desktop, on by default); with no extension registered, or after it disconnects, the normal browser backend runs unchanged. browser_vision turns the extension's capture into the usual native/auxiliary vision result. Bridge: forwards controller commands only to a connection that registered, refuses unknown or oversized commands, relays results. Extension: service-worker controller with its own native connection, trusted chrome.debugger input (press/release only: a hover move waits 5 s on background tabs), refusal of password and payment-card fields and of browser, extension, file and Web Store pages, panel switch, Stop, tab hand-over, recent actions, Alt+Shift+A and right-click "send to AITURK". Cancelling Chrome's debugging bar stops the agent. Existing panel flows and privacy rules are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5c76c24c9c
commit
86a6621936
32 files changed
+2755
-195
No files matched your search
@@ -45,6 +45,24 @@ from typing import Any, Callable, Dict, Optional
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def lane_enabled(transport_family: Optional[str]) -> bool:
|
||||
"""Feature gate for one controller lane.
|
||||
|
||||
The desktop IDE's own browser lane has its own switch
|
||||
(``browser.extension_control.local_desktop``, on by default); every other
|
||||
lane keeps the explicit ``browser.extension_control.enabled`` opt-in.
|
||||
"""
|
||||
from gateway.browser_control_broker import (
|
||||
LOCAL_DESKTOP_TRANSPORT_FAMILY,
|
||||
browser_control_enabled,
|
||||
browser_control_local_enabled,
|
||||
)
|
||||
|
||||
if transport_family == LOCAL_DESKTOP_TRANSPORT_FAMILY:
|
||||
return browser_control_local_enabled()
|
||||
return browser_control_enabled()
|
||||
|
||||
|
||||
def extension_controller_available(action: str) -> bool:
|
||||
"""Whether this request owns one exact controller capable of ``action``.
|
||||
|
||||
@@ -56,14 +74,9 @@ def extension_controller_available(action: str) -> bool:
|
||||
all fail closed.
|
||||
"""
|
||||
try:
|
||||
from gateway.browser_control_broker import (
|
||||
browser_control_enabled,
|
||||
get_browser_control_broker,
|
||||
)
|
||||
from gateway.browser_control_broker import get_browser_control_broker
|
||||
from gateway.session_context import get_session_env
|
||||
|
||||
if not browser_control_enabled():
|
||||
return False
|
||||
session_id = get_session_env("HERMES_SESSION_ID", "") or None
|
||||
principal_id = get_session_env("HERMES_BROWSER_CONTROL_PRINCIPAL", "") or None
|
||||
transport_family = get_session_env(
|
||||
@@ -71,6 +84,8 @@ def extension_controller_available(action: str) -> bool:
|
||||
) or None
|
||||
if not session_id or not principal_id or not transport_family:
|
||||
return False
|
||||
if not lane_enabled(transport_family):
|
||||
return False
|
||||
broker = get_browser_control_broker()
|
||||
scope = broker.scope_for_session(
|
||||
session_id=session_id,
|
||||
@@ -99,6 +114,7 @@ def route_browser_tool(
|
||||
principal_id: Optional[str] = None,
|
||||
transport_family: Optional[str] = None,
|
||||
tool_call_id: Optional[str] = "",
|
||||
transform: Optional[Callable[[Any], Any]] = None,
|
||||
) -> Any:
|
||||
"""Route one browser action through the extension-control broker.
|
||||
|
||||
@@ -127,6 +143,11 @@ def route_browser_tool(
|
||||
Hermes callers.
|
||||
tool_call_id:
|
||||
Caller tool-call id forwarded verbatim to ``dispatch``.
|
||||
transform:
|
||||
Optional post-processor for a *controller* result only (never for the
|
||||
fallback). Its return value is the tool result as-is, so it may build
|
||||
the multimodal envelope (``browser_vision`` turns the controller's
|
||||
screenshot into the same result the legacy backend returns).
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -184,6 +205,8 @@ def route_browser_tool(
|
||||
result = broker.dispatch(
|
||||
scope, action=action, arguments=args, tool_call_id=tool_call_id
|
||||
)
|
||||
if transform is not None:
|
||||
return transform(result)
|
||||
if isinstance(result, str):
|
||||
return result
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
@@ -215,6 +238,7 @@ def routed_browser_handler(
|
||||
principal_id: Optional[str] = None,
|
||||
transport_family: Optional[str] = None,
|
||||
tool_call_id: Optional[str] = None,
|
||||
transform: Optional[Callable[[Any], Any]] = None,
|
||||
) -> Any:
|
||||
"""Lazy registry-handler route wrapper for ``browser_*`` tools.
|
||||
|
||||
@@ -225,10 +249,7 @@ def routed_browser_handler(
|
||||
legacy handler runs unchanged.
|
||||
"""
|
||||
try:
|
||||
from gateway.browser_control_broker import (
|
||||
browser_control_enabled,
|
||||
get_browser_control_broker,
|
||||
)
|
||||
from gateway.browser_control_broker import get_browser_control_broker
|
||||
except Exception as exc: # pragma: no cover - defensive, gateway always present
|
||||
logger.debug(
|
||||
"browser extension router unavailable (%s); using legacy backend",
|
||||
@@ -236,12 +257,6 @@ def routed_browser_handler(
|
||||
)
|
||||
return fallback()
|
||||
|
||||
if not browser_control_enabled():
|
||||
return fallback()
|
||||
|
||||
if tool_call_id is None:
|
||||
tool_call_id = current_tool_call_id()
|
||||
|
||||
try:
|
||||
from gateway.session_context import get_session_env
|
||||
|
||||
@@ -255,6 +270,12 @@ def routed_browser_handler(
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not lane_enabled(transport_family):
|
||||
return fallback()
|
||||
|
||||
if tool_call_id is None:
|
||||
tool_call_id = current_tool_call_id()
|
||||
|
||||
return route_browser_tool(
|
||||
action,
|
||||
args,
|
||||
@@ -266,4 +287,5 @@ def routed_browser_handler(
|
||||
principal_id=principal_id,
|
||||
transport_family=transport_family,
|
||||
tool_call_id=tool_call_id,
|
||||
transform=transform,
|
||||
)
|
||||
Reference in new issue
Block a user