From 5bf005388406a929b404da36941963cb8367000a Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Wed, 29 Apr 2026 00:44:14 +0200 Subject: [PATCH] refactor(perception): remove all hardcoded structural fast paths Per user directive, the TelepathicEngine must rely entirely on autonomous learning, Visual Discovery (VLM), and the ActionMemory (Qdrant) to identify UI elements. All hardcoded heuristics and regex-based fast paths in have been completely purged. --- GramAddict/core/telepathic_engine.py | 115 --------------------------- 1 file changed, 115 deletions(-) diff --git a/GramAddict/core/telepathic_engine.py b/GramAddict/core/telepathic_engine.py index 7220b8e..e5c3199 100644 --- a/GramAddict/core/telepathic_engine.py +++ b/GramAddict/core/telepathic_engine.py @@ -64,12 +64,6 @@ class TelepathicEngine: """ logger.debug(f"🧠 [SpatialEngine] Resolving intent: '{intent_description}'") - # 1.25 Structural Fast-Paths (Deterministically bypass VLM for fixed UI elements) - nodes_dicts = self._extract_semantic_nodes(xml_string) - fast_node = self._structural_fast_path(intent_description, nodes_dicts, kwargs.get("skip_positions"), xml_string) - if fast_node: - return fast_node - # 1. Parse into Spatial Topology root = self._parser.parse(xml_string) if not root: @@ -134,115 +128,6 @@ class TelepathicEngine: nodes = self._parser.get_clickable_nodes(root) return [self._translate_node(n) for n in nodes] - def _structural_fast_path(self, intent_description: str, nodes: list, skip_positions: set = None, xml_string: str = "") -> Optional[dict]: - if skip_positions is None: - skip_positions = set() - - intent_lower = intent_description.lower() - if "first image in explore grid" in intent_lower or "tap first grid item" in intent_lower: - grid_items = [ - n - for n in nodes - if n.get("y", 9999) < 2000 - and ( - "grid card layout container" in (n.get("semantic_string", "") or "").lower() - or "image button" in (n.get("semantic_string", "") or "").lower() - ) - and (n.get("x", -1), n.get("y", -1)) not in skip_positions - ] - if grid_items: - # Sort by y (row) then by x (col) - grid_items.sort(key=lambda n: (n.get("y", 9999), n.get("x", 9999))) - return grid_items[0] - - # --- Profile Structural Fast Paths --- - if "following list" in intent_lower or "followers list" in intent_lower: - target_id = "profile_header_following" if "following" in intent_lower else "profile_header_followers" - for n in nodes: - res_id = n.get("id", "") or n.get("resource_id", "") - if target_id in res_id: - return n - # Fallback to text matching if ID not found - for n in nodes: - sem = (n.get("semantic_string", "") or "").lower() - desc = (n.get("description", "") or "").lower() - text = (n.get("text", "") or "").lower() - - if "following" in intent_lower: - if "following" in sem or "abonniert" in sem or "following" in desc or "following" in text: - return n - else: - if "followers" in sem or "abonnenten" in sem or "followers" in desc or "followers" in text: - return n - - # --- DM Engine Structural Fast Paths --- - if "find the message input text field" in intent_lower: - for n in nodes: - if "row_thread_composer_edittext" in n.get("id", "") or "row_thread_composer_edittext" in n.get("resource_id", ""): - return n - - if "find the send message button" in intent_lower: - for n in nodes: - if "row_thread_composer_button_send" in n.get("id", "") or "row_thread_composer_button_send" in n.get("resource_id", ""): - return n - - if "find unread message threads" in intent_lower: - # We must be extremely strict here: It's only unread if it has the "unread" text or indicator dot - unread_candidates = [] - - # 1. Find all explicit unread dots in the UI - dot_nodes = [ - d for d in nodes - if "thread_indicator_status_dot" in (d.get("id", "") or d.get("resource_id", "")) - ] - - import re - - for n in nodes: - is_unread = False - res_id = n.get("id", "") or n.get("resource_id", "") - - if "row_inbox_container" in res_id and (n.get("x", -1), n.get("y", -1)) not in skip_positions: - content_desc = (n.get("description", "") or "").lower() - semantic = (n.get("semantic_string", "") or "").lower() - - # 1. Check for explicit 'unread' in description - if "unread" in content_desc or "unread" in semantic: - is_unread = True - - # 2. Check if an unread dot falls inside this container's bounds - if not is_unread and dot_nodes: - bounds_str = n.get("bounds", "") - m = re.match(r"\[\d+,(\d+)\]\[\d+,(\d+)\]", bounds_str) - if m: - y1, y2 = int(m.group(1)), int(m.group(2)) - for dot in dot_nodes: - dot_y = dot.get("y", -1) - if y1 <= dot_y <= y2: - is_unread = True - break - - if is_unread and n.get("y", 0) > 200: - unread_candidates.append(n) - - if unread_candidates: - unread_candidates.sort(key=lambda n: n.get("y", 9999)) - return unread_candidates[0] - - if "find the last received message text" in intent_lower: - msg_candidates = [] - for n in nodes: - res_id = n.get("id", "") or n.get("resource_id", "") - # The actual message text bubble - if "direct_text_message_text_view" in res_id or "message_content" in res_id: - msg_candidates.append(n) - - if msg_candidates: - # Sort by y descending (bottom-most message is the last one) - msg_candidates.sort(key=lambda n: n.get("y", 0), reverse=True) - return msg_candidates[0] - - return None # ────────────────────────────────────────────── # Action Memory Delegation