diff --git a/GramAddict/core/perception/intent_resolver.py b/GramAddict/core/perception/intent_resolver.py index 7fd07d7..4decb53 100644 --- a/GramAddict/core/perception/intent_resolver.py +++ b/GramAddict/core/perception/intent_resolver.py @@ -269,6 +269,20 @@ class IntentResolver: logger.debug(f"🛡️ [Strict Button Guard] Filtered out node with long text: '{node.text[:20]}...'") candidates = filtered_candidates + # --- Post/Grid Item Guard --- + # VLMs frequently hallucinate 'Search' when asked to tap a post. We must pre-filter. + if "first post" in intent_lower or "grid item" in intent_lower: + grid_candidates = [] + for node in candidates: + desc = (node.content_desc or "").lower() + # Posts/grid items usually have 'row X, column Y', 'photos by', or 'reel by' + if "row 1" in desc or "column" in desc or "photos by" in desc or "reel by" in desc: + grid_candidates.append(node) + + if grid_candidates: + logger.info(f"🎯 [Grid Guard] Filtered to {len(grid_candidates)} actual grid candidates.") + candidates = grid_candidates + # --- Semantic Match Guard --- # If the intent explicitly quotes a target (e.g., "tap 'New Message'"), # we strictly filter candidates to those whose text or content_desc contains the quote. diff --git a/tests/e2e/test_nav_search_explore.py b/tests/e2e/test_nav_search_explore.py index 7fbb03b..3cfcf9f 100644 --- a/tests/e2e/test_nav_search_explore.py +++ b/tests/e2e/test_nav_search_explore.py @@ -70,3 +70,11 @@ def test_explore_feed_first_post(make_real_device_with_image): result = resolver.resolve("tap first post", candidates, device) assert result is not None, "VLM returned None for 'tap first post'" + + # Strictly verify that it picked an image button or post, NOT the search bar + rid = (result.resource_id or "").lower() + desc = (result.content_desc or "").lower() + + # A valid grid post has an image_button resource ID or "photo" / "Reel" in description + is_valid_post = "image_button" in rid or "photo" in desc or "reel" in desc + assert is_valid_post, f"VLM picked the wrong element! Selected id='{rid}', desc='{desc}'"