From 0f6369776558a4c585e614c53d7cac21bb47533f Mon Sep 17 00:00:00 2001 From: ciregenz Date: Sun, 7 Jun 2026 18:58:43 -0700 Subject: [PATCH] [eric] browser: confirm waits for the target to actually render so a send isnt falsely 'not confirmed' under load --- backend/apps/agents/browser/browser_agent.py | 6 +++++- backend/apps/agents/browser/browser_wait.py | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/backend/apps/agents/browser/browser_agent.py b/backend/apps/agents/browser/browser_agent.py index 3e57352c..306eb159 100644 --- a/backend/apps/agents/browser/browser_agent.py +++ b/backend/apps/agents/browser/browser_agent.py @@ -1370,7 +1370,11 @@ async def run_browser_agent( _expect = (str(tu.input.get("expect") or "").strip() if isinstance(tu.input, dict) else "") if _expect and "error" not in result and tu.name in _CONFIRM_TOOLS: - _conf = await browser_wait.smart_wait(_wait_exec, browser_id, tab_id, 3500, until=_expect) + # target_only: wait for the expected text to actually appear, don't + # call it 'not confirmed' just because the page settled first (a sent + # message lands in the thread a beat after settle, esp. under load) + _conf = await browser_wait.smart_wait(_wait_exec, browser_id, tab_id, 4000, + until=_expect, target_only=True) if isinstance(_conf, dict): result["confirmed"] = bool(_conf.get("found")) if _conf.get("found"): diff --git a/backend/apps/agents/browser/browser_wait.py b/backend/apps/agents/browser/browser_wait.py index 10264f20..db2650a0 100644 --- a/backend/apps/agents/browser/browser_wait.py +++ b/backend/apps/agents/browser/browser_wait.py @@ -83,13 +83,18 @@ def decide_stop(ready, quiet_ms, dom_stable_ms, found, elapsed_ms, async def smart_wait(execute_fn, browser_id, tab_id, max_ms, *, until="", poll_ms=_POLL_MS, floor_ms=_FLOOR_MS, quiet_window_ms=_QUIET_WINDOW_MS, - probe_timeout_s=_PROBE_TIMEOUT_S) -> dict: + probe_timeout_s=_PROBE_TIMEOUT_S, target_only=False) -> dict: """Wait up to `max_ms`, returning early once the page is ready. `until` (optional) is a label / visible text / selector the agent expects to appear; the wait ends the INSTANT it's present, so the agent isn't waiting blind. `execute_fn` is an async (tool, params, browser_id, tab_id) -> result|None (None = cancelled). Never raises. If the page stops responding to probes (hung tab), returns fast with hung=True so the - caller can bail instead of inheriting the long command timeout.""" + caller can bail instead of inheriting the long command timeout. + + target_only: when True (used for confirming an action), the page merely SETTLING + is not enough, only the `until` target appearing ends the wait early. This catches + a result that renders a beat AFTER settle (a sent message landing in a thread under + load), which the settle-early path otherwise misses, reporting a false 'not confirmed'.""" max_ms = max(100, min(int(max_ms or 1000), 10000)) probe_js = _probe_js(until) start = time.monotonic() @@ -141,6 +146,15 @@ async def smart_wait(execute_fn, browser_id, tab_id, max_ms, *, until="", last_elems = elems elems_changed_at = time.monotonic() dom_stable_ms = (time.monotonic() - elems_changed_at) * 1000 + # Confirming an action (target_only): only the target appearing counts; a + # bare settle without it keeps waiting, so a late-rendering result isn't a + # false miss. Bounded by max_ms either way. + if target_only and until: + if probe.get("found"): + settled = True + found = True + break + continue if decide_stop(probe.get("ready"), probe.get("quiet", 0), dom_stable_ms, probe.get("found"), _elapsed(), floor_ms=floor_ms, settle_window_ms=quiet_window_ms):