mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-08 02:37:45 +02:00
[eric] browser: a comment meant for a post must not land on a story
This commit is contained in:
@@ -191,6 +191,46 @@ def surface_mismatch(task: str, composer_name: str) -> bool:
|
||||
return bool(P_COMMENT_SURFACE_RE.search(name))
|
||||
|
||||
|
||||
# What a URL path declares the page to BE. Sites disagree about almost everything, but these path
|
||||
# segments are near-universal, and each one is the site telling us the content type in its own words.
|
||||
P_URL_CONTENT_TYPES = (
|
||||
("story", re.compile(r"/stories?/", re.I)),
|
||||
("message", re.compile(r"/(direct|messages?|dm)/", re.I)),
|
||||
("video", re.compile(r"/(watch|shorts)\b|/video/", re.I)),
|
||||
("post", re.compile(r"/(p|posts?|status|submit)/|/submit\b", re.I)),
|
||||
)
|
||||
# The same types as the TASK names them. Deliberately narrow: a word has to be unambiguous here or
|
||||
# the guard starts refusing pages that were right all along.
|
||||
P_TASK_CONTENT_TYPES = (
|
||||
("story", re.compile(r"\bstor(?:y|ies)\b", re.I)),
|
||||
("message", re.compile(r"\b(dm|direct message)\b", re.I)),
|
||||
("video", re.compile(r"\bvideos?\b", re.I)),
|
||||
("post", re.compile(r"\bposts?\b", re.I)),
|
||||
)
|
||||
|
||||
|
||||
def content_type_mismatch(task: str, url: str) -> str:
|
||||
""""asked for a <x>, landed on a <y>", or "" when they agree or either is unclear.
|
||||
|
||||
surface_mismatch below asks whether the COMPOSER contradicts the task. This asks the same thing
|
||||
one level up, about the page, because a page can hand you a perfectly good composer that belongs
|
||||
to the wrong thing entirely. Measured on instagram 2026-08-04: "write a comment on the first
|
||||
post" opened a STORY (`/stories/<user>/`), whose reply box is a real composer, so every gate
|
||||
downstream was satisfied and the comment would have gone to a story nobody asked about.
|
||||
|
||||
Both sides must be unambiguous. A task that names no type, or a URL that declares none, returns
|
||||
"" and changes nothing, so the cost of being wrong here is a page we decline to fill fast and
|
||||
hand to the model instead.
|
||||
"""
|
||||
if not task or not url:
|
||||
return ""
|
||||
want = [name for name, rx in P_TASK_CONTENT_TYPES if rx.search(task)]
|
||||
got = [name for name, rx in P_URL_CONTENT_TYPES if rx.search(url)]
|
||||
if len(want) != 1 or len(got) != 1 or want[0] == got[0]:
|
||||
return ""
|
||||
return f"asked for a {want[0]}, landed on a {got[0]}"
|
||||
|
||||
|
||||
def is_readonly(text: str) -> bool:
|
||||
"""A read-only directive ('verify whether', 'do not send') that must decline the scripted
|
||||
send even with a quoted payload in hand. Keeps the regex private to this file."""
|
||||
|
||||
@@ -263,6 +263,15 @@ async def run_send_script(
|
||||
if not payload:
|
||||
logger.info("[browser-sendscript] decline: no unambiguous quoted payload")
|
||||
return None
|
||||
# The page itself says what it is, and a composer on the wrong thing is still the wrong thing.
|
||||
# Live on instagram: a "comment on the first post" task opened a STORY, whose reply box is a
|
||||
# perfectly real composer, so every gate below passed and the comment would have gone somewhere
|
||||
# nobody asked for. Declining costs the fast path and hands the page to the model, which can
|
||||
# navigate; filling would have been silent and wrong.
|
||||
p_ctype = browser_send_parse.content_type_mismatch(task_sans_brief, current_url or "")
|
||||
if p_ctype:
|
||||
logger.info(f"[browser-sendscript] decline: {p_ctype} ({(current_url or '')[:60]})")
|
||||
return None
|
||||
log: list[dict] = []
|
||||
|
||||
composer = browser_send_parse.composer_index_in_state(state_text)
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
"""A composer on the wrong THING is still the wrong thing.
|
||||
|
||||
`surface_mismatch` already asks whether the composer contradicts the task (a post ask must not fill
|
||||
a DM box). This is the same question one level up, about the page, because a page can hand you a
|
||||
perfectly real composer that belongs to something nobody asked about.
|
||||
|
||||
Measured live on instagram 2026-08-04 at N=5: "write a comment on the first post" opened a STORY
|
||||
(`/stories/<user>/`). A story's reply box IS a composer, so the DM guard, the structural finder and
|
||||
the fill receipt were all satisfied, and the comment would have gone to a story silently. The URL is
|
||||
the page's own declaration of what it is, and it was the only signal that disagreed.
|
||||
|
||||
Both sides must be unambiguous before this fires. The cost of a false positive is one declined fast
|
||||
path (the model still gets the page); the cost of a false negative is a write to the wrong surface.
|
||||
"""
|
||||
|
||||
from backend.apps.agents.browser import browser_send_parse as sp
|
||||
|
||||
|
||||
def test_the_instagram_story_bug_verbatim():
|
||||
why = sp.content_type_mismatch("write a comment on the first post",
|
||||
"https://www.instagram.com/stories/miy.jpg/")
|
||||
assert why == "asked for a post, landed on a story", why
|
||||
|
||||
|
||||
def test_the_same_task_on_a_real_post_is_fine():
|
||||
"""The guard must not cost us the case that works: instagram reached /p/<id> on other runs."""
|
||||
assert sp.content_type_mismatch("write a comment on the first post",
|
||||
"https://www.instagram.com/p/DbRdbkgCZgk/") == ""
|
||||
|
||||
|
||||
def test_asking_for_a_story_and_getting_one_is_fine():
|
||||
"""The user is allowed to want a story. The guard is about disagreement, not about stories."""
|
||||
assert sp.content_type_mismatch("reply to the first story",
|
||||
"https://www.instagram.com/stories/miy.jpg/") == ""
|
||||
|
||||
|
||||
def test_the_working_sites_are_untouched():
|
||||
"""Every site at 5/5 or 4/4 in the N=5 sweep must keep scoring exactly as it did."""
|
||||
for task, url in (
|
||||
("comment on the first video", "https://www.youtube.com/watch?v=SAjrSUNCQbc"),
|
||||
("post this tweet", "https://x.com/compose/post"),
|
||||
("create a text post", "https://www.reddit.com/r/test/submit/?type=TEXT"),
|
||||
("create a post", "https://www.linkedin.com/feed/?shareActive=true"),
|
||||
("write in chat", "https://www.twitch.tv/jynxzi"),
|
||||
):
|
||||
assert sp.content_type_mismatch(task, url) == "", (task, url)
|
||||
|
||||
|
||||
def test_it_stays_silent_when_either_side_is_unclear():
|
||||
"""An ambiguous task names two types and a plain URL declares none. Guessing between them would
|
||||
refuse pages that were right all along, which costs more than the bug it would catch."""
|
||||
assert sp.content_type_mismatch("post a comment on the story",
|
||||
"https://www.instagram.com/stories/x/") == ""
|
||||
assert sp.content_type_mismatch("say hi to tyler", "https://example.com/anything") == ""
|
||||
assert sp.content_type_mismatch("", "https://www.instagram.com/stories/x/") == ""
|
||||
assert sp.content_type_mismatch("comment on the first post", "") == ""
|
||||
Reference in New Issue
Block a user