From bdb30c26b084c7b5a7cc4965737f9b8d6bb50c07 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Tue, 7 Jul 2026 00:22:34 -0700 Subject: [PATCH] [eric] browser: fix quoted_payload apostrophe bug (chen's mispaired with the payload quote, silently broke the canonical send errand) + 6 mechanism tests (opener-hop/fill/verify/send/receipt + all abort branches) --- .../agents/browser/browser_send_script.py | 15 ++- backend/tests/test_browser_send_script.py | 98 +++++++++++++++++++ 2 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 backend/tests/test_browser_send_script.py diff --git a/backend/apps/agents/browser/browser_send_script.py b/backend/apps/agents/browser/browser_send_script.py index 3129d2f2..c54b48ad 100644 --- a/backend/apps/agents/browser/browser_send_script.py +++ b/backend/apps/agents/browser/browser_send_script.py @@ -21,7 +21,9 @@ from typing import Awaitable, Callable logger = logging.getLogger(__name__) -P_QUOTED_RE = re.compile(r"['\"]([^'\"]{4,300})['\"]") +# Double quotes are unambiguous. Single quotes only delimit when the opener is at a word boundary (start/space/colon), so an in-word apostrophe like "chen's" is never mistaken for a payload quote, that mispairing was silently corrupting the canonical "text him '...'" errand. +P_QUOTED_DQ_RE = re.compile(r'"([^"]{4,300})"') +P_QUOTED_SQ_RE = re.compile(r"(?:^|[\s:>])'([^']{4,300})'") P_COMPOSER_ROW_RE = re.compile(r"\[(\d+)\]\*?<\s*textbox\s+\"([^\"]*)\"", re.I) P_COMPOSER_NAME_RE = re.compile(r"write|message|compose|reply", re.I) @@ -34,9 +36,14 @@ def script_enabled() -> bool: def quoted_payload(task: str) -> str: """The exact text the user quoted, only when it's unambiguous: exactly one - distinct quoted span in the task. Anything else is the model's judgment call.""" - spans = {m.group(1).strip() for m in P_QUOTED_RE.finditer(task or "") if m.group(1).strip()} - return spans.pop() if len(spans) == 1 else "" + distinct quoted span in the task. Anything else is the model's judgment call. + Double quotes win outright; single quotes must be word-boundary-delimited so + an apostrophe inside a name can't hijack the match.""" + dq = {m.group(1).strip() for m in P_QUOTED_DQ_RE.finditer(task or "") if m.group(1).strip()} + if dq: + return dq.pop() if len(dq) == 1 else "" + sq = {m.group(1).strip() for m in P_QUOTED_SQ_RE.finditer(task or "") if m.group(1).strip()} + return sq.pop() if len(sq) == 1 else "" P_OPENER_ROW_RE = re.compile(r"\[(\d+)\]\*?<\s*(?:link|button)\s+\"(Message|Reply|Compose|New message)\"", re.I) diff --git a/backend/tests/test_browser_send_script.py b/backend/tests/test_browser_send_script.py new file mode 100644 index 00000000..5bbcfdbc --- /dev/null +++ b/backend/tests/test_browser_send_script.py @@ -0,0 +1,98 @@ +"""Send-script mechanism, verified without a live webview: real LinkedIn-shaped +interactives fixtures driven through a mock executor exercise the exact path the +live rig would (opener -> composer -> fill -> commit-check -> send -> clear-check), +plus every abort/honesty branch. The wall-clock a live run measures is not here; +the correctness the live run would prove is.""" +import pytest + +from backend.apps.agents.browser import browser_send_script as ss +from backend.apps.agents.browser.browser_agent import send_index_in_state, payload_in_textbox + +PROFILE = '[22]*\n[50]*\n[51]