fix(perception): 3 production bug regressions from 2026-05-01 run
Bug 1 — VLM Profile Tab Hallucination:
- VLM confused 'Profile' nav tab with post author username
- Added Author Tab Guard to filter_navigation_conflicts()
- Fixed mock LLM to use structural row_feed_photo_profile matching
instead of hardcoded username (was the test lie)
Bug 2 — Like Button 1-Byte Delta Kill:
- Toggle actions (like/save) produce 1-byte XML deltas
- GOAP's MIN_UI_CHANGE_BYTES=50 threshold killed them as 'no change'
- Split interaction gate: interactions use ANY xml diff, navigations
keep the 50-byte threshold
Bug 3 — Empty Username Silently Accepted:
- PostDataExtraction returned 'Post by @' with no warning
- Added 'username_missing' reliability flag to result dict
- Downstream consumers can now detect degraded data quality
Cleanup:
- Removed all debug print() statements from production code
- Replaced with structured logger.debug() calls
This commit is contained in:
@@ -427,7 +427,11 @@ class GoalExecutor:
|
||||
action_success = False
|
||||
else:
|
||||
# For interactions (like, follow) or unknown goals, use XML delta + semantic verify
|
||||
if ui_changed:
|
||||
# REGRESSION FIX 2026-05-01: Toggle actions (like/save) produce tiny XML deltas
|
||||
# (e.g. checked="false" → "true" = 1 byte). We must NOT gate interactions on
|
||||
# MIN_UI_CHANGE_BYTES. ANY change at all warrants semantic verification.
|
||||
interaction_xml_changed = post_xml != xml_dump
|
||||
if interaction_xml_changed:
|
||||
score = best_node.get("score", 0.0) if best_node else 0.0
|
||||
verification = engine.verify_success(action, post_xml, device=self.device, confidence=score)
|
||||
if verification is True:
|
||||
|
||||
@@ -52,9 +52,9 @@ def extract_post_content(context_xml: str, device=None) -> dict:
|
||||
This is the BOT'S EYES — what it actually "sees" about each post.
|
||||
|
||||
Returns:
|
||||
{'username': str, 'description': str, 'caption': str}
|
||||
{'username': str, 'description': str, 'caption': str, 'username_missing': bool}
|
||||
"""
|
||||
result = {"username": "", "description": "", "caption": ""}
|
||||
result = {"username": "", "description": "", "caption": "", "username_missing": False}
|
||||
|
||||
try:
|
||||
from GramAddict.core.telepathic_engine import TelepathicEngine
|
||||
@@ -72,7 +72,10 @@ def extract_post_content(context_xml: str, device=None) -> dict:
|
||||
|
||||
# 2. Learn/extract post media description dynamically
|
||||
media_node = telepath.find_best_node(
|
||||
context_xml, "post media content (the actual image or video, exclude bottom tabs)", min_confidence=0.35, device=device
|
||||
context_xml,
|
||||
"post media content (the actual image or video, exclude bottom tabs)",
|
||||
min_confidence=0.35,
|
||||
device=device,
|
||||
)
|
||||
if media_node and media_node.get("original_attribs", {}).get("desc"):
|
||||
result["description"] = media_node["original_attribs"]["desc"].strip()
|
||||
@@ -89,6 +92,11 @@ def extract_post_content(context_xml: str, device=None) -> dict:
|
||||
except Exception as e:
|
||||
logger.warning(f"Error extracting post content autonomously: {e}")
|
||||
|
||||
# REGRESSION FIX 2026-05-01: Flag unreliable data when username is empty
|
||||
if not result["username"]:
|
||||
result["username_missing"] = True
|
||||
logger.warning("⚠️ [PostDataExtraction] Username is empty — data may be unreliable.")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -48,10 +48,14 @@ class IntentResolver:
|
||||
Production bug 2026-04-30: VLM picked action_bar_button_back
|
||||
for "tap profile tab" → account switch failed.
|
||||
|
||||
Production bug 2026-05-01: VLM picked profile_tab (desc='Profile')
|
||||
for "post author username text" → navigated to own profile instead.
|
||||
|
||||
Rules:
|
||||
- For tab intents: exclude nodes with "back" in resource_id or
|
||||
content_desc == "Back"
|
||||
- For back/close intents: no filtering (Back is the correct target)
|
||||
- For author/username intents: exclude bottom navigation tabs
|
||||
"""
|
||||
intent_lower = intent_description.lower()
|
||||
|
||||
@@ -60,6 +64,11 @@ class IntentResolver:
|
||||
filtered = []
|
||||
is_tab_intent = "tab" in intent_lower and "back" not in intent_lower
|
||||
is_create_intent = "create" in intent_lower or "camera" in intent_lower or "story" in intent_lower
|
||||
# REGRESSION FIX 2026-05-01: Author/username intents must never pick nav tabs
|
||||
is_author_intent = any(kw in intent_lower for kw in ["author", "username", "post media"])
|
||||
|
||||
# Known bottom navigation tab resource_id suffixes
|
||||
NAV_TAB_SUFFIXES = ("_tab", "tab_icon", "navigation_bar")
|
||||
|
||||
for node in candidates:
|
||||
rid = (node.resource_id or "").lower()
|
||||
@@ -68,6 +77,7 @@ class IntentResolver:
|
||||
is_back = "back" in rid or desc == "back"
|
||||
is_close = "close" in rid or desc == "close"
|
||||
is_create = "camera" in rid or "create" in rid or desc == "camera" or desc == "create" or "creation" in rid
|
||||
is_nav_tab = any(rid.endswith(s) for s in NAV_TAB_SUFFIXES)
|
||||
|
||||
if is_tab_intent and (is_back or is_close):
|
||||
logger.debug(
|
||||
@@ -76,6 +86,13 @@ class IntentResolver:
|
||||
)
|
||||
continue
|
||||
|
||||
if is_author_intent and is_nav_tab:
|
||||
logger.debug(
|
||||
f"🛡️ [Author Tab Guard] Excluded nav tab '{node.resource_id}' "
|
||||
f"(desc='{node.content_desc}') for author intent '{intent_description}'"
|
||||
)
|
||||
continue
|
||||
|
||||
if not is_create_intent and is_create:
|
||||
logger.debug(
|
||||
f"🛡️ [Creation Conflict Guard] Excluded '{node.resource_id}' "
|
||||
@@ -374,8 +391,7 @@ class IntentResolver:
|
||||
label_parts.append("(no visible text)")
|
||||
box_legend_lines.append(f" [{idx}] {', '.join(label_parts)}")
|
||||
box_legend = "\n".join(box_legend_lines)
|
||||
print("BOX LEGEND:")
|
||||
print(box_legend)
|
||||
logger.debug(f"BOX LEGEND:\n{box_legend}")
|
||||
|
||||
prompt = (
|
||||
f"You are looking at a mobile app screenshot with numbered bounding boxes drawn around interactive UI elements.\n"
|
||||
|
||||
Reference in New Issue
Block a user