diff --git a/backend/apps/agents/browser/browser_agent.py b/backend/apps/agents/browser/browser_agent.py index 10fe71bd..8ee7b580 100644 --- a/backend/apps/agents/browser/browser_agent.py +++ b/backend/apps/agents/browser/browser_agent.py @@ -121,6 +121,7 @@ def _app_bridge_expression(tool_name: str, tool_input: dict) -> str: # reads poll briefly for the bridge to come up instead of declaring it absent. _BRIDGE_READY_WAIT_MS = 8000 _BRIDGE_POLL_INTERVAL_MS = 400 +_bridge_known_absent: set[str] = set() def _parse_bridge_result(result: dict) -> object: @@ -320,11 +321,22 @@ async def execute_browser_tool( # AppInvoke does not wait: its action either exists right now or it does # not, and a missing action should surface immediately. if tool_name in ("AppDescribe", "AppGetState"): - waited = 0 - while waited < _BRIDGE_READY_WAIT_MS and not _bridge_ready(_parse_bridge_result(result)): - await asyncio.sleep(_BRIDGE_POLL_INTERVAL_MS / 1000) - waited += _BRIDGE_POLL_INTERVAL_MS - result = await _eval_once() + ready = _bridge_ready(_parse_bridge_result(result)) + # Only the cold-boot read polls. Once a card is memoed bridge-absent, + # every later read takes the single eval above and skips the 8s sink; + # that single eval still detects a bridge that came up between reads. + if not ready and browser_id not in _bridge_known_absent: + waited = 0 + while waited < _BRIDGE_READY_WAIT_MS and not ready: + await asyncio.sleep(_BRIDGE_POLL_INTERVAL_MS / 1000) + waited += _BRIDGE_POLL_INTERVAL_MS + result = await _eval_once() + ready = _bridge_ready(_parse_bridge_result(result)) + # Record the verdict so the next read knows whether to pay the wait. + if ready: + _bridge_known_absent.discard(browser_id) + else: + _bridge_known_absent.add(browser_id) if tool_name == "AppDescribe": _persist_app_controls(browser_id, _parse_bridge_result(result)) return result diff --git a/backend/apps/agents/browser_agent_mcp_server.py b/backend/apps/agents/browser_agent_mcp_server.py index 25a10606..b65543ae 100644 --- a/backend/apps/agents/browser_agent_mcp_server.py +++ b/backend/apps/agents/browser_agent_mcp_server.py @@ -118,12 +118,14 @@ TOOLS = [ "name": "AppAgent", "description": ( "Operate one of the user's OpenSwarm-built apps (a small web app they " - "created, e.g. a graphing or form app) that is open on the dashboard. A " - "dedicated app agent reads the app's own actions and state through its " - "native bridge and performs the task (no screenshots or DOM scraping), " - "then returns a summary plus a final screenshot. Use this for the apps " - "listed in the selected-app context, not for websites (use BrowserAgent " - "for those)." + "created, e.g. a graphing tool, a form, or a canvas game like Doom) that " + "is open on the dashboard. A dedicated app agent performs the task: it " + "drives the app through its native bridge when one is available (reading " + "the app's own state and calling its controls), and otherwise falls back " + "to native keyboard/mouse plus screenshots for canvas and game apps that " + "expose no bridge. It returns a summary plus a final screenshot. Works for " + "ANY app in the selected-app context, including games and canvas apps; use " + "BrowserAgent only for websites, not these apps." ), "inputSchema": { "type": "object",