mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-30 05:24:50 +02:00
[eric] browser: stop the caller starving the send-script, which silently killed every write
This commit is contained in:
@@ -1690,9 +1690,11 @@ async def run_browser_agent(
|
||||
task, browser_id, tab_id, preloaded_perception,
|
||||
execute_browser_tool, send_submit_index_in_state, payload_in_textbox,
|
||||
payload_source=user_prompt, current_url=current_url,
|
||||
), timeout=30.0)
|
||||
), timeout=browser_send_script.WORST_CASE_BUDGET_S)
|
||||
except Exception as p_se:
|
||||
logger.info(f"[browser-sendscript] outer skip ({p_se})")
|
||||
# Name the class: a bare TimeoutError stringifies to nothing, so this used to log
|
||||
# "outer skip ()" and a starved send looked identical to a page we chose not to touch.
|
||||
logger.info(f"[browser-sendscript] outer skip ({type(p_se).__name__}: {p_se})")
|
||||
p_script = None
|
||||
if isinstance(p_script, dict):
|
||||
action_log.extend(p_script["log"])
|
||||
|
||||
@@ -27,6 +27,18 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
ToolRunner = Callable[[str, dict, str, str], Awaitable[dict]]
|
||||
|
||||
# The worst case this routine can legitimately take, so the CALLER cannot starve it. Roughly: the
|
||||
# composer poll (3 backoff waits plus three 6s interactive lists), one structural finder call (which
|
||||
# carries its own 30s command timeout), then fill-and-commit and submit-and-receipt.
|
||||
#
|
||||
# This constant exists because the caller's timeout and this routine's real cost silently drifted
|
||||
# apart: raising find_composer's command timeout from 15s to 30s made a single finder call able to
|
||||
# eat the caller's entire 30s budget, so the whole script was killed mid-send and EVERY write fell
|
||||
# back to the slow model loop. It failed invisibly, because asyncio.TimeoutError stringifies to
|
||||
# nothing and the log read "outer skip ()". Measured live on LinkedIn: a 190.9s write that never
|
||||
# posted. Import this instead of writing a number at the call site.
|
||||
WORST_CASE_BUDGET_S = 75.0
|
||||
|
||||
|
||||
def script_enabled() -> bool:
|
||||
return os.environ.get("OSW_SEND_SCRIPT", "0") != "0"
|
||||
|
||||
@@ -53,6 +53,31 @@ def test_the_two_facts_are_not_the_same_variable():
|
||||
"the run's success must be a function of evidence, not of the model's Done argument"
|
||||
|
||||
|
||||
def test_the_caller_cannot_starve_the_send_script():
|
||||
"""INVARIANT, and the third time this exact trap has bitten (find_composer, import_session, now
|
||||
this). The send-script's cost and its caller's timeout drifted apart when find_composer went
|
||||
15s -> 30s: one finder call could eat the caller's whole 30s budget, so the script was killed
|
||||
mid-send and EVERY write silently fell back to the slow model loop. It was invisible because
|
||||
asyncio.TimeoutError stringifies to nothing, so the log read "outer skip ()".
|
||||
|
||||
Measured live on LinkedIn: a 190.9s write that never posted."""
|
||||
from backend.apps.agents.browser import browser_send_script as ss
|
||||
from backend.apps.agents.core.ws_manager import BROWSER_CMD_TIMEOUTS
|
||||
|
||||
assert "timeout=browser_send_script.WORST_CASE_BUDGET_S" in P_SRC, (
|
||||
"the caller hardcodes its own timeout again; it must import the script's stated worst case "
|
||||
"so the two cannot drift")
|
||||
# A single finder call must not be able to consume the whole budget.
|
||||
assert ss.WORST_CASE_BUDGET_S > BROWSER_CMD_TIMEOUTS.get("find_composer", 15.0) * 2, \
|
||||
"the budget must leave room for fill and submit after the finder, not just the finder"
|
||||
|
||||
|
||||
def test_a_starved_send_is_logged_by_exception_class():
|
||||
"""A bare TimeoutError has an empty message, so a starved send read exactly like a page we
|
||||
deliberately declined to touch. The class name is what makes those two distinguishable."""
|
||||
assert "type(p_se).__name__" in P_SRC
|
||||
|
||||
|
||||
def test_an_unverified_send_gets_an_honest_line_that_does_not_claim_delivery():
|
||||
note = dc.unverified_send_note("https://x.com/home", "hello from my automation")
|
||||
low = note.lower()
|
||||
|
||||
Reference in New Issue
Block a user