[eric] browser: arm the send-completion shortcut only for send tasks, not gathers (cookie click faked a send)

This commit is contained in:
ciregenz
2026-06-08 23:02:55 -07:00
parent 07f911d200
commit 438d1e36d4
2 changed files with 26 additions and 2 deletions
+5 -2
View File
@@ -935,7 +935,10 @@ async def run_browser_agent(
# (measured: send done at turn ~11, then ~12 wasted perception turns). We drive
# it to the OUTCOME and, if it keeps re-perceiving, end the run. A genuine
# multi-send task issues its NEXT send (an action) which resets the stall, so
# only true spinning ends here.
# only true spinning ends here. This whole shortcut is meaningless for a
# gather/read task (no send to confirm), and arming it there let a cookie
# 'Accept all' click masquerade as the task's send, so we gate it on intent.
task_is_send = not deliverable_is_informational("", task)
send_confirmed = False
perception_stall = 0 # consecutive turns the model only LOOKED (no action)
_POST_SEND_STALL_LIMIT = 2 # once the send registered, finish fast
@@ -1495,7 +1498,7 @@ async def run_browser_agent(
# across nodes, or scrolled off, so the text-probe is unreliable, which
# is exactly what left the model stalling to "double-check". A clean
# send click is proof enough; drive to the OUTCOME.
if not send_confirmed and "error" not in result and tu.name in _CONFIRM_TOOLS:
if task_is_send and not send_confirmed and "error" not in result and tu.name in _CONFIRM_TOOLS:
_cn = result.get("clickedName") or ""
_send_click = browser_batch_replay.is_replay_boundary(
{"action": "click", "name": _cn}) or any(
+21
View File
@@ -282,6 +282,27 @@ def test_run_that_never_calls_done_is_not_a_clean_success(monkeypatch):
assert result.get("done") is False # no explicit Done -> not a clean success
def test_send_shortcut_does_not_arm_on_a_gather_task(monkeypatch):
# The Airbnb bug: a send-class click (here the index-99 sentinel = "Send", same
# as a cookie "Accept all" tripping the detector) on a FIND/gather task must NOT
# arm the send-completion shortcut, there is no send to confirm. If it did, the
# run cuts at the 2-turn post-send limit and leaks the canned "message went
# through" line. On a gather task it should run the full perception budget.
BH._browser_history.clear(); BH._domain_notes.clear()
primary = FakeLLM([
Resp([_rp("dismiss the cookie banner"), _tu("BrowserClickIndex", index=99)]),
*[Resp([_rp("keep reading the list"), _tu("BrowserScreenshot")]) for _ in range(8)],
Resp([_tu("Done", message="Here are the top items: a, b, c")]),
])
aux = FakeAux()
_install(monkeypatch, primary, aux)
result = asyncio.run(BA.run_browser_agent(task="find me the top 10 repos", browser_id="b1", model="sonnet"))
# the send shortcut never armed: it ran past the 2-turn post-send cutoff toward
# the 6-turn perception budget, and no send-confirmation line leaked
assert primary.turn >= 6, f"gather task cut short at turn {primary.turn} (send shortcut wrongly armed)"
assert "went through" not in result["summary"]
def test_early_perception_is_not_cut_short_before_any_action(monkeypatch):
# Orienting on a cold/slow page can take several look-only turns; the stall
# backstop must NOT fire before the agent has done anything (it only bounds a