[eric] browser: a prose decline from the read script is a decline, not an answer

This commit is contained in:
ciregenz
2026-07-30 17:31:16 -07:00
parent 18e024834d
commit 78843dba2f
2 changed files with 49 additions and 0 deletions
@@ -8,6 +8,7 @@ Fail-open everywhere: thin page, decline, error = the loop runs exactly as today
import asyncio
import logging
import os
import re
import time
from typing import Awaitable, Callable, Dict, Optional
@@ -57,12 +58,29 @@ def read_script_enabled() -> bool:
return os.environ.get("OSW_READ_SCRIPT", "1") != "0"
# A decline written as prose instead of the token. The aux is asked to say INSUFFICIENT when the
# answer lives behind a click, and it usually does, but on a page that is ALMOST right it explains
# itself instead: "I can see the search results, but I cannot access the individual product page."
# That reply used to be accepted as the answer, which ended the run at the wrong page and made a
# perfectly good multi-step task fail 3 times out of 3 on amazon, deterministically. It is a decline,
# so it has to be read as one. Anchored on the aux declaring it cannot REACH somewhere, never on a
# page merely lacking a field, because "the page does not show a price" IS a legitimate answer here.
P_PROSE_DECLINE_RE = re.compile(
r"\b(?:cannot|can'?t|unable to|don'?t have (?:the )?ability to)\s+"
r"(?:\w+\s+){0,3}?(?:access|open|reach|navigate(?:\s+to)?|visit|click(?:\s+(?:on|into))?|load)\b"
r"|\b(?:would|will|you)\s+need\s+to\s+(?:\w+\s+){0,2}?"
r"(?:open|click|visit|navigate|go\s+to)\b",
re.I)
def is_answer(reply: str) -> Optional[str]:
"""The usable answer text, or None. Declines, empties, and hedge-shaped replies
all fail closed to the loop, so a thin extraction can never become a wrong answer."""
answer = (reply or "").strip()
if not answer or answer.upper().startswith("INSUFFICIENT"):
return None
if P_PROSE_DECLINE_RE.search(answer):
return None
return answer
+31
View File
@@ -132,3 +132,34 @@ def test_no_aux_client_and_tool_error_both_fail_open():
return {"error": "card is gone"}
assert asyncio.run(rs.run_read_script(
Aux("answer"), "m", "t", "b1", "t1", broken_tool)) is None
def test_a_prose_decline_is_not_an_answer():
"""The exact reply that made amazon fail 3/3: the aux explains it cannot reach the page it
needs instead of emitting INSUFFICIENT. Accepting it ended the run on the search results and
the model loop, which could have clicked through, never got to run."""
verbatim = ("I can see this is a search results page for \"usb c cable\" on Amazon, but I "
"cannot access the individual product page for the first result. The page shows "
"search results with multiple products listed, but to get the exact price, star "
"rating, and number of ratings I would need to open the product page.")
assert rs.is_answer(verbatim) is None
for decline in (
"I can't open the product page from here.",
"I am unable to navigate to that profile.",
"You would need to click into the post to see the replies.",
"I cannot reach the comments section on this page.",
):
assert rs.is_answer(decline) is None, decline
def test_a_page_that_simply_lacks_the_field_is_still_an_answer():
"""The guard must not eat real answers. Per the read-script contract, a page that visibly
lacks the field IS the answer, and so is any answer that happens to contain the word 'open'."""
for answer in (
"The price is $12.99 and it has 4.5 stars from 8,214 ratings.",
"The profile does not show a headline; the name is Eric Zeng.",
"The first post is by @someone and the shop is open until 5pm.",
"Title: 'How to open a jar'. Channel: Kitchen Tips. 1.2M views.",
):
assert rs.is_answer(answer) == answer, answer