fix: browser quickstart
This commit is contained in:
@@ -273,18 +273,28 @@ async def execute_subagent(
|
|||||||
conversation_store=subagent_conv_store,
|
conversation_store=subagent_conv_store,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Inject a GCU browser profile for this subagent.
|
# Each subagent instance gets its own unique browser profile so concurrent
|
||||||
# Use just agent_id (not subagent_instance) so the profile is stable
|
# subagents don't share tab groups. The profile is injected into every
|
||||||
# across multiple calls to the same subagent type. This allows
|
# browser_* tool call by wrapping the tool executor.
|
||||||
# cookies/auth to persist between runs.
|
_gcu_profile = f"{agent_id}:{subagent_instance}"
|
||||||
_profile_token = None
|
_original_tool_executor = None
|
||||||
_subagent_profile = agent_id # Stable profile per agent type
|
|
||||||
try:
|
|
||||||
from gcu.browser.session import set_active_profile as _set_gcu_profile
|
|
||||||
|
|
||||||
_profile_token = _set_gcu_profile(_subagent_profile)
|
if tool_executor is not None:
|
||||||
except ImportError:
|
_original_tool_executor = tool_executor
|
||||||
pass # GCU tools not installed; no-op
|
|
||||||
|
async def _gcu_profile_injecting_executor(
|
||||||
|
tool_use: ToolUse,
|
||||||
|
) -> ToolResult | Awaitable[ToolResult]:
|
||||||
|
if tool_use.name.startswith("browser_") and "profile" not in (tool_use.input or {}):
|
||||||
|
from dataclasses import replace
|
||||||
|
|
||||||
|
tool_use = replace(tool_use, input={**(tool_use.input or {}), "profile": _gcu_profile})
|
||||||
|
result = _original_tool_executor(tool_use)
|
||||||
|
if asyncio.isfuture(result) or asyncio.iscoroutine(result):
|
||||||
|
return await result
|
||||||
|
return result
|
||||||
|
|
||||||
|
tool_executor = _gcu_profile_injecting_executor
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info("🚀 Starting subagent '%s' execution...", agent_id)
|
logger.info("🚀 Starting subagent '%s' execution...", agent_id)
|
||||||
@@ -356,14 +366,16 @@ async def execute_subagent(
|
|||||||
is_error=True,
|
is_error=True,
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
# Restore the GCU profile context
|
# Close the tab group this subagent created, if any.
|
||||||
if _profile_token is not None:
|
if _original_tool_executor is not None:
|
||||||
from gcu.browser.session import _active_profile as _gcu_profile_var
|
try:
|
||||||
|
stop_call = ToolUse(
|
||||||
_gcu_profile_var.reset(_profile_token)
|
id="__subagent_cleanup__",
|
||||||
|
name="browser_stop",
|
||||||
# NOTE: We intentionally do NOT call browser_stop() here.
|
input={"profile": _gcu_profile},
|
||||||
# Keeping the browser session alive allows cookies and auth state
|
)
|
||||||
# to persist across multiple subagent calls. The browser will be
|
result = _original_tool_executor(stop_call)
|
||||||
# cleaned up when the parent agent stops or when explicitly
|
if asyncio.isfuture(result) or asyncio.iscoroutine(result):
|
||||||
# requested via browser_stop().
|
await result
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ class BeelineBridge:
|
|||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
port,
|
port,
|
||||||
logger=null_logger,
|
logger=null_logger,
|
||||||
|
max_size=50 * 1024 * 1024, # 50 MB — CDP responses (AX tree, screenshots) can be large
|
||||||
)
|
)
|
||||||
logger.info("Beeline bridge listening on ws://127.0.0.1:%d", port)
|
logger.info("Beeline bridge listening on ws://127.0.0.1:%d", port)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
|
|||||||
@@ -45,12 +45,10 @@ def get_all_sessions() -> dict[str, Any]:
|
|||||||
|
|
||||||
|
|
||||||
async def shutdown_all_browsers() -> None:
|
async def shutdown_all_browsers() -> None:
|
||||||
"""Stop all browser sessions.
|
"""Stop all browser sessions. Called at server shutdown to clean up."""
|
||||||
|
from gcu.browser.tools.lifecycle import shutdown_all_contexts
|
||||||
|
|
||||||
Called at server shutdown to clean up.
|
await shutdown_all_contexts()
|
||||||
Note: Sessions are managed via bridge extension.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BrowserSession:
|
class BrowserSession:
|
||||||
|
|||||||
@@ -38,6 +38,24 @@ _EXTENSION_PATH = (
|
|||||||
).resolve()
|
).resolve()
|
||||||
|
|
||||||
|
|
||||||
|
async def shutdown_all_contexts() -> None:
|
||||||
|
"""Close all active browser contexts. Called at GCU server shutdown."""
|
||||||
|
if not _contexts:
|
||||||
|
return
|
||||||
|
bridge = get_bridge()
|
||||||
|
for profile_name, ctx in list(_contexts.items()):
|
||||||
|
group_id = ctx.get("groupId")
|
||||||
|
if group_id is not None and bridge and bridge.is_connected:
|
||||||
|
try:
|
||||||
|
await bridge.destroy_context(group_id)
|
||||||
|
logger.info(
|
||||||
|
"Shutdown: closed browser context '%s' (groupId=%s)", profile_name, group_id
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("Shutdown: failed to close context '%s': %s", profile_name, e)
|
||||||
|
_contexts.clear()
|
||||||
|
|
||||||
|
|
||||||
def register_lifecycle_tools(mcp: FastMCP) -> None:
|
def register_lifecycle_tools(mcp: FastMCP) -> None:
|
||||||
"""Register browser lifecycle management tools."""
|
"""Register browser lifecycle management tools."""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user