feat(chrome): let the IDE agent use the user's Chrome and beta.42
Server CI and Windows package / verify (push) Successful in 3m33s
Server CI and Windows package / windows-package (push) Successful in 7m36s

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:
yilsemandClaude Opus 5.5 committed 2026-10-15 08:38:52 +03:00
1 parent 5c76c24c9c
commit 86a6621936
32 files changed
+2755 -195

No files matched your search

+134
View File
@@ -38,6 +38,8 @@ import logging
from gateway.browser_control_broker import (
BROWSER_CONTROL_PROTOCOL_VERSION,
LOCAL_DESKTOP_PRINCIPAL,
LOCAL_DESKTOP_TRANSPORT_FAMILY,
browser_control_protocol_supported,
filter_browser_control_capabilities,
)
@@ -58,9 +60,17 @@ method = _registry.method
#: transport can never address a dashboard controller (and vice versa).
_CLOUD_TRANSPORT_FAMILY = "cloud-ticket-ws"
#: Local desktop lane: the AITURK Chrome extension reaches this backend only
#: through the desktop IDE's native bridge, which authenticates with the
#: loopback session token (``WSTransport.local_desktop``).
_LOCAL_TRANSPORT_FAMILY = LOCAL_DESKTOP_TRANSPORT_FAMILY
_LOCAL_PRINCIPAL = LOCAL_DESKTOP_PRINCIPAL
#: JSON-RPC error code for identity / session / flag denials (forbidden).
_ERR_FORBIDDEN = 4403
_ID_LIMIT = 200
def _is_authenticated_identity(identity: object) -> bool:
"""True for a server-minted, non-internal ``{user_id, provider}`` identity."""
@@ -377,6 +387,130 @@ def _(
return _ok(rid, {"detached": True})
def _is_local_desktop(transport: object) -> bool:
return getattr(transport, "local_desktop", False) is True
@method("browser.controller.local.register")
def _(
rid,
params: dict,
_family=_LOCAL_TRANSPORT_FAMILY,
_principal=_LOCAL_PRINCIPAL,
_protocol_supported=browser_control_protocol_supported,
_protocol_version=BROWSER_CONTROL_PROTOCOL_VERSION,
_filter_capabilities=filter_browser_control_capabilities,
_forbidden=_ERR_FORBIDDEN,
_local=_is_local_desktop,
_event_writer=_broker_event_writer,
_id_limit=_ID_LIMIT,
) -> dict:
"""Offer the desktop user's own browser to every desktop session.
Only the loopback desktop transport (the IDE's native Chrome bridge) may
register, and only while ``browser.extension_control.local_desktop`` is
on. The controller becomes a lane template: each desktop session that
uses a browser tool gets its own exact scope on first use, and its frames
carry that session id. Registering again from the same transport
refreshes it; closing the transport removes it, so sessions go back to
their normal browser backend.
"""
from gateway import browser_control_broker
transport = current_transport()
if not _local(transport):
return _err(
rid, _forbidden, "local browser controller requires the desktop transport"
)
if not browser_control_broker.browser_control_local_enabled():
return _err(rid, _forbidden, "browser.extension_control.local_desktop is off")
if not _protocol_supported(params.get("protocol_version")):
return _err(
rid,
_forbidden,
f"unsupported browser-control protocol version; expected {_protocol_version}",
)
controller_id = str(params.get("controller_id") or "").strip()
browser_profile_id = str(params.get("browser_profile_id") or "").strip()
profile_id = str(params.get("profile") or "local").strip() or "local"
if (
not controller_id
or not browser_profile_id
or max(len(controller_id), len(browser_profile_id), len(profile_id)) > _id_limit
):
return _err(rid, _forbidden, "controller_id and browser_profile_id are required")
capabilities = _filter_capabilities(params.get("capabilities"))
if not capabilities:
return _err(rid, _forbidden, "no permitted controller capabilities requested")
template = browser_control_broker.ControllerScope(
principal_id=_principal,
profile_id=profile_id,
session_id=None,
controller_id=controller_id,
browser_profile_id=browser_profile_id,
transport_family=_family,
capabilities=capabilities,
)
broker = browser_control_broker.get_browser_control_broker()
broker.attach_lane_template(
template,
lambda session_id: _event_writer(transport, session_id),
owner=transport,
)
return _ok(
rid,
{
"registered": True,
"transport_family": _family,
"capabilities": sorted(capabilities),
},
)
@method("browser.controller.local.result")
def _(rid, params: dict, _forbidden=_ERR_FORBIDDEN, _local=_is_local_desktop) -> dict:
"""Deliver one local controller result; only its own transport may."""
from gateway import browser_control_broker
transport = current_transport()
if not _local(transport):
return _err(rid, _forbidden, "local browser controller requires the desktop transport")
command_id = str(params.get("command_id") or "")
if not command_id:
return _err(rid, _forbidden, "command_id required")
ok = params.get("ok") is True
accepted = browser_control_broker.get_browser_control_broker().complete_for_owner(
command_id,
owner=transport,
ok=ok,
result=params.get("result") if ok else params.get("error"),
)
return _ok(rid, {"accepted": accepted})
@method("browser.controller.local.detach")
def _(
rid,
params: dict,
_family=_LOCAL_TRANSPORT_FAMILY,
_principal=_LOCAL_PRINCIPAL,
_forbidden=_ERR_FORBIDDEN,
_local=_is_local_desktop,
) -> dict:
"""Stop offering the user's browser; pending commands are cancelled."""
from gateway import browser_control_broker
transport = current_transport()
if not _local(transport):
return _err(rid, _forbidden, "local browser controller requires the desktop transport")
detached = browser_control_broker.get_browser_control_broker().detach_lane_template(
principal_id=_principal,
transport_family=_family,
owner=transport,
)
return _ok(rid, {"detached": detached})
def register(server) -> None:
"""Bind this module's handlers onto ``server``'s globals and registry."""
_registry.install(server)