fix(navigation): enforce strict unread badge detection in structural fast paths
This commit is contained in:
@@ -78,12 +78,11 @@ class TelepathicEngine:
|
||||
logger.warning("🛡️ [Comment Guard] Comments are disabled on this post.")
|
||||
return {"skip": True, "semantic": "comments disabled"}
|
||||
|
||||
# 1.25 Grid Fast-Path (Deterministically bypass VLM for first grid item)
|
||||
if "first image in explore grid" in intent_description.lower():
|
||||
nodes_dicts = self._extract_semantic_nodes(xml_string)
|
||||
fast_node = self._grid_fast_path(intent_description, nodes_dicts, kwargs.get("skip_positions"))
|
||||
if fast_node:
|
||||
return fast_node
|
||||
# 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)
|
||||
@@ -149,7 +148,7 @@ class TelepathicEngine:
|
||||
nodes = self._parser.get_clickable_nodes(root)
|
||||
return [self._translate_node(n) for n in nodes]
|
||||
|
||||
def _grid_fast_path(self, intent_description: str, nodes: list, skip_positions: set = None) -> Optional[dict]:
|
||||
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()
|
||||
|
||||
@@ -173,42 +172,51 @@ class TelepathicEngine:
|
||||
# --- 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("resource_id", ""):
|
||||
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("resource_id", ""):
|
||||
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:
|
||||
# Unread threads usually have a specific indicator or are the top rows in the recyclerview
|
||||
# We must be extremely strict here: It's only unread if it has the "unread" text or indicator dot
|
||||
unread_candidates = []
|
||||
|
||||
# Check for explicitly unread containers
|
||||
for n in nodes:
|
||||
# Check if it has a direct structural unread marker (though IG usually relies on styling)
|
||||
# But we can fallback to the row_inbox_container
|
||||
if (
|
||||
"row_inbox_container" in n.get("resource_id", "")
|
||||
and (n.get("x", -1), n.get("y", -1)) not in skip_positions
|
||||
):
|
||||
# Avoid the 'Search' or header elements, typically thread rows start below y=200
|
||||
is_unread = False
|
||||
res_id = n.get("id", "") or n.get("resource_id", "")
|
||||
content_desc = (n.get("description", "") or "").lower()
|
||||
semantic = (n.get("semantic_string", "") or "").lower()
|
||||
|
||||
# Check for explicit 'unread' in description
|
||||
if "unread" in content_desc or "unread" in semantic:
|
||||
is_unread = True
|
||||
|
||||
# Some versions might use the explicit unread dot
|
||||
if "thread_indicator_status_dot" in xml_string:
|
||||
# Let's fallback to any row if we know an unread dot exists in the xml
|
||||
if "row_inbox_container" in res_id:
|
||||
is_unread = True
|
||||
|
||||
if is_unread and "row_inbox_container" in res_id and (n.get("x", -1), n.get("y", -1)) not in skip_positions:
|
||||
if n.get("y", 0) > 200:
|
||||
unread_candidates.append(n)
|
||||
|
||||
|
||||
if unread_candidates:
|
||||
# Return the highest y (first thread in list)
|
||||
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 n.get("resource_id", "") or "message_content" in n.get(
|
||||
"resource_id", ""
|
||||
):
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user