From bba6b74d7685c8dbc7acca0e85157400406e44ba Mon Sep 17 00:00:00 2001 From: ciregenz Date: Mon, 6 Jul 2026 14:45:48 -0700 Subject: [PATCH] [eric] browser: receipt-done (clean send click ends the run in code, no Done turn) + staged-run two-tool script hint --- backend/apps/agents/browser/browser_agent.py | 11 +++++++++++ backend/apps/agents/browser/browser_prestage.py | 5 ++++- backend/tests/conftest.py | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/backend/apps/agents/browser/browser_agent.py b/backend/apps/agents/browser/browser_agent.py index 25967497..96842ab2 100644 --- a/backend/apps/agents/browser/browser_agent.py +++ b/backend/apps/agents/browser/browser_agent.py @@ -1746,6 +1746,17 @@ async def run_browser_agent( for r in (result.get("results") or [])) if p_send_click: send_confirmed = True + if os.environ.get("OSW_RECEIPT_DONE", "1") != "0": + # Deterministic receipt: the clean send click IS the proof, so the run ends here in code instead of paying one more model turn for a Done. Same honesty bar (the click + composer-clear is the evidence the gate reads), ~3s less wall per send. + done_called = True + done_success = True + p_payload = browser_batch_replay.send_payload_from_log(action_log, task) + done_message = ( + f'Sent "{p_payload}", the send registered and the composer cleared.' + if p_payload else + "Sent it, the send registered and the composer cleared." + ) + logger.info(f"[browser-receipt {session_id}] send receipt passed; run ends in code (no Done turn)") result["text"] = (f"{result.get('text') or ''}\n\n[task complete] The send " "went through (the composer cleared). Don't re-check it. Finish now by " "calling Done with your reply to the user.") diff --git a/backend/apps/agents/browser/browser_prestage.py b/backend/apps/agents/browser/browser_prestage.py index 67bc1246..89ac7487 100644 --- a/backend/apps/agents/browser/browser_prestage.py +++ b/backend/apps/agents/browser/browser_prestage.py @@ -96,7 +96,10 @@ def stage_note_for(start_url: str, done: list[str], current_url: str, complete: f"[Pre-staged for you and VERIFIED: starting from {start_url or 'the entry page'}, " f"already performed: {'; '.join(done)}. You are NOW on {current_url}. The " "navigation part of the task is DONE, do not go back or re-verify it; " - "perform only the remaining final action(s).]" + "perform only the remaining final action(s). Staged runs took 7 solo turns " + "where 2 suffice: if the remaining work is composing, use ONE BrowserBatch to " + "focus the box and type the text, then the Send/Submit click SOLO with expect. " + "Do not re-list first; the elements are listed below.]" ) return ( f"[Partial pre-staging: already performed {'; '.join(done)}. You are NOW on " diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 4f5a1dcf..78db2ce7 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -34,6 +34,7 @@ def _isolate_browser_state(monkeypatch): monkeypatch.setenv("OSW_FASTREAD_HOP", "0") monkeypatch.setenv("OSW_PRELUDE_TRIM", "0") monkeypatch.setenv("OSW_DEADCARD_EVICT", "0") + monkeypatch.setenv("OSW_RECEIPT_DONE", "0") def _reset(): for mod in ("browser_skills", "browser_playbook"):