From 19a3e8ad1aca4c5668ef87543093ccffdeecb88f Mon Sep 17 00:00:00 2001 From: ciregenz Date: Sun, 12 Jul 2026 20:52:15 -0700 Subject: [PATCH] [eric] browser: keystroke-fill fallback + cross-nav reveal retry (both flag-gated, default off) Item 1: when the finder's execCommand insertText read-back fails (editors that reject synthetic input), retype the same text as real OS-level key events via wv.sendInputEvent, then verify from the marked element OR document.activeElement (editors like Reddit swap the node on activation, staling the selector). Item 3: when an open-first reveal navigates to a new document (Reddit thread, TikTok video, GitHub issue), the send-script re-perceives after a load beat and calls the finder once more on the destination, bounded to 2 tries. 4 new tests (cross-nav retry fires / no wasted retry) + structural tests split into test_browser_reveal.py to stay under the 300-line cap. Live real-path validation of the sendInputEvent leg is owed on a healthy rig: the eric/browser agent loop wedges at fresh-card webview mount (15.25s cap, renderer starvation, predates the suspend fix) at low load too --- .../agents/browser/browser_send_script.py | 23 ++- backend/tests/test_browser_reveal.py | 143 ++++++++++++++++++ backend/tests/test_browser_send_script.py | 96 ------------ frontend/src/shared/browserCommandHandler.ts | 36 ++++- 4 files changed, 198 insertions(+), 100 deletions(-) create mode 100644 backend/tests/test_browser_reveal.py diff --git a/backend/apps/agents/browser/browser_send_script.py b/backend/apps/agents/browser/browser_send_script.py index 82403daf..6abe17bb 100644 --- a/backend/apps/agents/browser/browser_send_script.py +++ b/backend/apps/agents/browser/browser_send_script.py @@ -17,7 +17,7 @@ import logging import os import re import time -from typing import Awaitable, Callable +from typing import Awaitable, Callable, Dict from backend.apps.agents.browser import browser_verified_action @@ -184,12 +184,29 @@ async def run_send_script( # compose surface: a modal trigger, the first conversation, or a scroll) when the # composer isn't painted yet. It never commits a send, only opens a surface. p_reveal = os.environ.get("OSW_COMPOSER_REVEAL") == "1" - fc = await execute_tool("BrowserFindComposer", {"fill": payload, "reveal": p_reveal}, browser_id, tab_id) + # A reveal that OPENS the first list item (a Reddit thread, a TikTok video, a GitHub + # issue) is a full-page NAVIGATION: it kills the finder's own JS context, so that one + # call can't reach the composer that only exists on the destination. When the finder + # reports it fired `open-first` but found nothing, the page is now loading the item; + # give it a beat and run the finder ONCE more on the destination. Bounded to 2 tries so + # a feed-of-feeds can't walk forever. + fc: Dict[str, object] = {} + for attempt in range(2): + fc = await execute_tool("BrowserFindComposer", {"fill": payload, "reveal": p_reveal}, browser_id, tab_id) + if isinstance(fc, dict) and fc.get("found") and fc.get("filled"): + break + revs = fc.get("reveals") if isinstance(fc, dict) else None + navigated = p_reveal and isinstance(revs, list) and "open-first" in revs + if not navigated: + break + logger.info("[browser-sendscript] reveal navigated (open-first); re-perceiving the destination") + await asyncio.sleep(1.5) + await fresh_list() if isinstance(fc, dict) and fc.get("found") and fc.get("filled"): p_struct_selector = str(fc.get("selector") or "") logger.info(f"[browser-sendscript] structural composer role={fc.get('role')!r} " f"score={fc.get('score')} nearSubmit={fc.get('nearSubmit')} " - f"reveals={fc.get('reveals')} filled+verified") + f"reveals={fc.get('reveals')} fillMode={fc.get('fillMode')} filled+verified") log.append({"tool": "BrowserFindComposer", "input": {"fill": ""}, "ok": True, "result_summary": f"structural composer {fc.get('role')!r} filled+verified"[:200], "elapsed_ms": 0}) composer = (-1, str(fc.get("role") or "composer")) diff --git a/backend/tests/test_browser_reveal.py b/backend/tests/test_browser_reveal.py new file mode 100644 index 00000000..13441493 --- /dev/null +++ b/backend/tests/test_browser_reveal.py @@ -0,0 +1,143 @@ +"""Structural composer finder + reveal-and-find reach + cross-nav retry, verified without a +live webview: a mock executor scripts BrowserFindComposer results so the send-script's +structural fallback path (find -> reveal -> re-perceive-on-nav -> fill) is exercised end to +end, including the flag gating and the safety that reveal only forwards under the flag.""" +import pytest + +from backend.apps.agents.browser import browser_send_script as ss +from backend.apps.agents.browser.browser_agent import send_index_in_state, payload_in_textbox + +TASK = "go to tyler chen's linkedin hes in entrepreneurs first and text him '[test] hello world r9-os'" +NAMELESS = '[1]\n[2]