diff --git a/backend/apps/agents/browser/browser_send_script.py b/backend/apps/agents/browser/browser_send_script.py index 0820f981..5cd59c2c 100644 --- a/backend/apps/agents/browser/browser_send_script.py +++ b/backend/apps/agents/browser/browser_send_script.py @@ -128,11 +128,17 @@ async def run_send_script( return "" composer = composer_index_in_state(state_text) + if not composer: + # The staged snapshot is prestage's, taken the instant it clicked Message; the overlay composer lazy-renders a beat later, so it's on the page but not in that frozen list (r263 declined on exactly this). One fresh read catches it before we fall back to the opener. + fresh = await fresh_list() + composer = composer_index_in_state(fresh) + if composer: + state_text = fresh if not composer: # Reversible-opener hop: prestage often stops on the profile with the "Message" opener visible (its settle raced the overlay). Opening a composer is the allowed opener class; the irreversible bar is unchanged. opener = opener_index_in_state(state_text) if not opener: - logger.info("[browser-sendscript] decline: no composer and no single exact-named opener in staged state") + logger.info("[browser-sendscript] decline: no composer and no single exact-named opener in staged or fresh state") return None logger.info(f"[browser-sendscript] firing via opener {opener[1]!r} [{opener[0]}]") r_open = await execute_tool("BrowserClickIndex", {"index": opener[0]}, browser_id, tab_id) diff --git a/backend/tests/test_browser_send_script.py b/backend/tests/test_browser_send_script.py index d8538871..7505308c 100644 --- a/backend/tests/test_browser_send_script.py +++ b/backend/tests/test_browser_send_script.py @@ -93,7 +93,8 @@ async def test_opener_hop_full_success(): """On a messaging THREAD-LIST page (full-page surface): script opens the conversation composer, fills, sees it commit, finds the late Send, clicks, sees it clear -> receipt passes.""" - r, calls = await run(TASK, PROFILE, [COMPOSER_EMPTY, COMPOSER_FILLED, COMPOSER_FILLED, COMPOSER_SENT]) + # fresh read (PROFILE, still no composer) -> opener path -> click Message -> composer appears + r, calls = await run(TASK, PROFILE, [PROFILE, COMPOSER_EMPTY, COMPOSER_FILLED, COMPOSER_FILLED, COMPOSER_SENT]) assert r is not None and r["sent"] is True assert r["payload"] == "[test] hello world r9-os" # opener click (50), fill into composer (2 w/ text), solo send click (14)