[eric] browser: the post-is-not-a-comment rule now guards the opener, and the composer poll waits ~5s

This commit is contained in:
ciregenz
2026-07-29 01:23:11 -07:00
parent da6e84dc74
commit a5a363cbce
2 changed files with 36 additions and 1 deletions
@@ -249,6 +249,15 @@ async def run_send_script(
if not composer:
# Reversible-opener hop: prestage often stops on the profile with the "Message" opener visible (its settle raced the overlay). Opening a composer is the allowed opener class; the irreversible bar is unchanged.
opener = browser_send_parse.opener_index_in_state(state_text)
if opener and browser_send_parse.surface_mismatch(task_sans_brief, opener[1]):
# The same post-is-not-a-comment rule the composer already enforces, applied one step
# earlier. Measured on linkedin.com with the task "start a post": the only opener listed
# was 'Comment', so the script opened a stranger's comment box, found no post composer
# inside it, and declined. Opening the wrong surface is not a slower route to the right
# one, and here it also burns the reversible-opener hop we only get once.
logger.info(f"[browser-sendscript] ignoring opener {opener[1]!r}: a comment box is not "
f"where a post goes")
opener = None
if opener:
logger.info(f"[browser-sendscript] firing via opener {opener[1]!r} [{opener[0]}]")
r_open = await execute_tool("BrowserClickIndex", {"index": opener[0]}, browser_id, tab_id)
@@ -256,7 +265,11 @@ async def run_send_script(
return None
log.append({"tool": "BrowserClickIndex", "input": {"index": opener[0]}, "ok": True,
"result_summary": f"script opened composer via {opener[1]!r}"[:200], "elapsed_ms": 0})
for wait_s in (0.6, 1.2):
# 1.8s was too short for a compose surface that has to mount. Measured in dry-run:
# gmail's compose window and linkedin's post modal both missed it and fell back to the
# model loop, which costs 100s+. Waiting up to ~5s to avoid that is a trade worth making
# every time; the extra polls only ever run on a run that would otherwise have failed.
for wait_s in (0.6, 1.2, 1.5, 2.0):
await asyncio.sleep(wait_s)
state_text = await fresh_list()
composer = browser_send_parse.composer_index_in_state(state_text)
@@ -97,3 +97,25 @@ def test_a_task_with_no_post_intent_is_left_alone():
"""Messaging a person is neither posting nor commenting; the guard must not touch it."""
assert not sp.surface_mismatch("text tyler hello", "Write a message")
assert not sp.surface_mismatch("", "Add a comment")
# --- the opener is a surface too --------------------------------------------------------------
def test_a_post_task_rejects_a_comment_OPENER():
"""Measured in the dry-run coverage sweep: on linkedin.com with "start a post", the only opener
listed was 'Comment'. The script opened a stranger's comment box, found no post composer inside
it, and declined. The composer already had this guard; the opener did not, so the wrong surface
got opened one step earlier and burned the single reversible-opener hop."""
assert sp.surface_mismatch("start a post saying hi", "Comment")
assert sp.surface_mismatch('post this, exactly: "hi"', "Reply")
def test_a_comment_task_keeps_its_comment_opener():
"""One-directional, same as the composer rule: asking to comment must still open a comment box."""
assert not sp.surface_mismatch("comment on the first post saying hi", "Comment")
assert not sp.surface_mismatch("reply to that thread with hi", "Reply")
def test_a_post_task_keeps_a_real_post_opener():
for opener in ("Post", "Compose", "New message", "Message"):
assert not sp.surface_mismatch('post this, exactly: "hi"', opener), opener