mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-05 01:07:42 +02:00
[eric] browser: prove a send by the payload appearing twice, when the composer never clears
This commit is contained in:
@@ -182,3 +182,42 @@ def unconfirmed_delivery_note(url: str, payload: str) -> str:
|
||||
return (f'I submitted "{clip}" and the composer cleared, but I could NOT confirm it stayed '
|
||||
f'live: {host} sometimes accepts an automated post and then drops it without an error. '
|
||||
f'Please check your posts to verify it actually went through before relying on it.')
|
||||
|
||||
|
||||
@typechecked
|
||||
def occurrence_probe_expression(payload: str) -> str:
|
||||
"""JS counting how many times `payload` appears in the page's visible text.
|
||||
|
||||
The count is the whole point. `payload_visible` above is documented "run only AFTER the composer
|
||||
cleared", and on a site where the composer NEVER clears that makes it useless: one hit could be
|
||||
the leftover draft or the posted item, and there is no way to tell them apart. Two hits cannot
|
||||
both be the composer, so the second one is rendered content, which is what "it posted" means.
|
||||
"""
|
||||
needle = " ".join((payload or "").split())[:80]
|
||||
return ("(()=>{try{var n=" + json.dumps(needle) + ";"
|
||||
"if(!n.length) return {count:0};"
|
||||
"var t=(document.body&&document.body.innerText)||'';"
|
||||
"var c=0,i=t.indexOf(n);while(i!==-1){c++;i=t.indexOf(n,i+n.length);}"
|
||||
"return {count:c};}catch(e){return {count:-1};}})()")
|
||||
|
||||
|
||||
@typechecked
|
||||
async def payload_occurrences(
|
||||
payload: str, browser_id: str, tab_id: str, execute_tool: ToolRunner
|
||||
) -> Optional[int]:
|
||||
"""How many times the payload is rendered on the page, or None if we could not look.
|
||||
|
||||
None, never 0, on a failed read: asserting "it is not there" from an observation that never
|
||||
happened is the same error as claiming a delivery nobody saw, pointed the other way.
|
||||
"""
|
||||
try:
|
||||
r = await asyncio.wait_for(execute_tool(
|
||||
"BrowserEvaluate", {"expression": occurrence_probe_expression(payload)},
|
||||
browser_id, tab_id), timeout=6.0)
|
||||
except Exception:
|
||||
return None
|
||||
v = browser_submit_click.parse_eval_value(r)
|
||||
if not isinstance(v, dict) or "count" not in v:
|
||||
return None
|
||||
n = int(v.get("count") or 0)
|
||||
return None if n < 0 else n
|
||||
|
||||
@@ -189,6 +189,22 @@ async def complete_send(
|
||||
sent = True
|
||||
break
|
||||
p_why = f"payload-still-in-a-textbox (textbox rows={sum(1 for x in state3.splitlines() if '<textbox' in x)})"
|
||||
if not sent and p_why.startswith("payload-still"):
|
||||
# A cleared composer is ABSENCE evidence, and some sites never provide it: LinkedIn keeps
|
||||
# the text in its editor after a successful post, so the receipt polls 7.6s, sees the
|
||||
# payload still sitting there, and calls a post that LANDED unverified. Measured four times;
|
||||
# an independent read of the activity feed found the post each time. That is not a cosmetic
|
||||
# miss, it sends the model back to re-verify work that already succeeded (60s against ~24s).
|
||||
#
|
||||
# So ask for PRESENCE instead, and count it. One hit is ambiguous (draft or posted item, no
|
||||
# way to tell). Two hits cannot both be the composer, so the second is rendered content,
|
||||
# which is exactly what "it posted" means. Runs only on the path that was about to report a
|
||||
# failure, so a site whose composer does clear pays nothing for this.
|
||||
p_n = await browser_delivery_check.payload_occurrences(payload, browser_id, tab_id, execute_tool)
|
||||
if p_n is not None and p_n >= 2:
|
||||
sent = True
|
||||
logger.info(f"[browser-sendscript] receipt via rendered content: the payload appears "
|
||||
f"{p_n}x on the page, so one of them is not the composer")
|
||||
if not sent:
|
||||
logger.info(f"[browser-sendscript] receipt withheld after {sum(p_waits):.1f}s of polling: {p_why}")
|
||||
# A cleared composer is proof of delivery everywhere EXCEPT the ghost-drop hosts, which clear
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
"""A cleared composer is not the only proof a post landed.
|
||||
|
||||
Measured on LinkedIn four separate times: the post LANDS, and the composer still holds the text 7.6s
|
||||
later, so the two-sided receipt ("fill seen committed, then seen gone") reports a successful send as
|
||||
unverified. An independent read of the activity feed found the post every time. That costs the fast
|
||||
path (the model goes back to re-verify work that already succeeded, ~60s against ~24s) and it makes
|
||||
criterion 2 under-report real capability.
|
||||
|
||||
The clear is ABSENCE evidence and some sites never supply it. Presence is the other direction, and
|
||||
the COUNT is what makes it usable: one hit is ambiguous (a leftover draft or the posted item, no way
|
||||
to tell them apart), two hits cannot both be the composer, so the second is rendered content.
|
||||
|
||||
Only runs when the clear-poll already failed, so a site whose composer does clear is untouched.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from backend.apps.agents.browser import browser_delivery_check as dc
|
||||
|
||||
|
||||
def test_the_probe_counts_every_occurrence_not_just_the_first():
|
||||
expr = dc.occurrence_probe_expression("canary123")
|
||||
assert "while(i!==-1)" in expr, "must keep scanning past the first hit"
|
||||
assert "i+n.length" in expr, "and advance past the match, or it counts forever"
|
||||
|
||||
|
||||
def test_the_needle_is_whitespace_normalised_and_capped():
|
||||
"""innerText collapses whitespace; a payload with a newline would never match verbatim. The cap
|
||||
keeps a pasted essay from becoming an 80KB expression."""
|
||||
expr = dc.occurrence_probe_expression("hello \n world")
|
||||
assert json.dumps("hello world") in expr
|
||||
long_expr = dc.occurrence_probe_expression("z" * 300)
|
||||
assert json.dumps("z" * 80) in long_expr
|
||||
|
||||
|
||||
def test_an_empty_payload_counts_nothing_rather_than_everything():
|
||||
"""indexOf('') returns 0 forever. Without this guard the probe reports an infinite count and
|
||||
every send would 'verify'."""
|
||||
assert "if(!n.length) return {count:0};" in dc.occurrence_probe_expression("")
|
||||
|
||||
|
||||
def test_a_page_error_is_reported_as_unreadable_not_as_zero():
|
||||
"""-1 becomes None upstream. Asserting 'not there' from an observation that never happened is
|
||||
the same error as claiming a delivery nobody saw, pointed the other way."""
|
||||
assert "catch(e){return {count:-1};}" in dc.occurrence_probe_expression("x")
|
||||
|
||||
|
||||
def test_the_send_script_requires_TWO_hits_before_it_believes_a_send():
|
||||
"""One hit is the draft. The threshold is the safety property: at 1 this would call every
|
||||
unsent draft a delivered post, which is the false-success class the receipt exists to prevent."""
|
||||
import inspect
|
||||
from backend.apps.agents.browser import browser_send_script as ss
|
||||
src = inspect.getsource(ss)
|
||||
i = src.index("receipt via rendered content")
|
||||
block = src[max(0, i - 900):i + 200]
|
||||
assert "p_n is not None and p_n >= 2" in block, "two hits, and None must not pass"
|
||||
assert "payload_occurrences" in block
|
||||
|
||||
|
||||
def test_it_only_runs_after_the_clear_poll_already_failed():
|
||||
"""A site whose composer clears must pay nothing for this, and must not get a second opinion
|
||||
that could disagree with a receipt it already earned honestly."""
|
||||
import inspect
|
||||
from backend.apps.agents.browser import browser_send_script as ss
|
||||
src = inspect.getsource(ss)
|
||||
assert 'if not sent and p_why.startswith("payload-still")' in src
|
||||
Reference in New Issue
Block a user