feat(observer): v15 AG Native chat relay — scanChatBodies dual strategy (#632)
- Add AG Native DOM path: #conversation + .leading-relaxed.select-text - Keep Cascade path: [data-testid=conversation-view] + [data-step-index] - Register #632 in known-issues.md (SDK+DOM both blocked for AG Native) - Bump version 0.5.50 → 0.5.51 - Add DOM analysis helper scripts
This commit is contained in:
109
.agents/workflows/helpers/trace_dom.py
Normal file
109
.agents/workflows/helpers/trace_dom.py
Normal file
@@ -0,0 +1,109 @@
|
||||
"""Trace the DOM path from body to AI response container."""
|
||||
import json, os
|
||||
|
||||
fpath = os.path.join(os.path.expanduser('~'), '.gemini', 'antigravity', 'bridge', 'dump_html_5.json')
|
||||
with open(fpath, 'r', encoding='utf-8-sig') as f:
|
||||
data = json.load(f)
|
||||
|
||||
body = data.get('body', data.get('bodyTree', {}))
|
||||
|
||||
def find_path_to_class(node, target_cls, path=None, depth=0):
|
||||
"""Find the DOM path down to a node with a matching class."""
|
||||
if path is None: path = []
|
||||
if not isinstance(node, dict): return []
|
||||
|
||||
tag = node.get('tag', '')
|
||||
cls = node.get('cls', '')
|
||||
children = node.get('children', [])
|
||||
text = node.get('text', '')
|
||||
attrs = node.get('attrs', {})
|
||||
|
||||
entry = {
|
||||
'depth': depth,
|
||||
'tag': tag,
|
||||
'cls': cls[:120],
|
||||
'children': len(children),
|
||||
'text': text[:60] if text else '',
|
||||
'attrs': {k:v[:40] for k,v in attrs.items() if k not in ('style',)}
|
||||
}
|
||||
|
||||
if target_cls.lower() in cls.lower():
|
||||
return path + [entry]
|
||||
|
||||
for i, child in enumerate(children):
|
||||
result = find_path_to_class(child, target_cls, path + [entry], depth+1)
|
||||
if result:
|
||||
return result
|
||||
|
||||
return []
|
||||
|
||||
# Find path to the AI response container
|
||||
print("=== PATH TO 'leading-relaxed select-text' ===")
|
||||
path = find_path_to_class(body, 'leading-relaxed select-text')
|
||||
for p in path:
|
||||
indent = ' ' * p['depth']
|
||||
print(f'{indent}[{p["tag"]}] cls="{p["cls"]}" children={p["children"]} {p["attrs"]}')
|
||||
if p['text']:
|
||||
print(f'{indent} text: "{p["text"]}"')
|
||||
|
||||
# Now get the full subtree of the AI response container
|
||||
def get_subtree(node, target_cls, depth=0):
|
||||
if not isinstance(node, dict): return None
|
||||
cls = node.get('cls', '')
|
||||
if target_cls.lower() in cls.lower():
|
||||
return node
|
||||
for child in node.get('children', []):
|
||||
result = get_subtree(child, target_cls, depth+1)
|
||||
if result:
|
||||
return result
|
||||
return None
|
||||
|
||||
print("\n=== AI RESPONSE CONTAINER SUBTREE ===")
|
||||
container = get_subtree(body, 'leading-relaxed select-text')
|
||||
if container:
|
||||
def print_tree(node, depth=0, max_depth=4):
|
||||
if not isinstance(node, dict) or depth > max_depth: return
|
||||
tag = node.get('tag','')
|
||||
cls = node.get('cls','')[:80]
|
||||
text = node.get('text','')
|
||||
children = node.get('children', [])
|
||||
indent = ' ' * depth
|
||||
line = f'{indent}[{tag}]'
|
||||
if cls: line += f' cls="{cls}"'
|
||||
line += f' children={len(children)}'
|
||||
if text: line += f' text="{text[:60]}"'
|
||||
print(line)
|
||||
for c in children:
|
||||
print_tree(c, depth+1, max_depth)
|
||||
|
||||
print_tree(container, 0, 3)
|
||||
|
||||
# Also search for the chat panel container - what wraps the entire conversation
|
||||
print("\n=== SEARCH FOR CHAT PANEL WRAPPERS ===")
|
||||
chat_patterns = ['chat', 'antigravity', 'gemini', 'panel', 'agentview', 'sidebar', 'conversation']
|
||||
for pat in chat_patterns:
|
||||
path = find_path_to_class(body, pat)
|
||||
if path:
|
||||
last = path[-1]
|
||||
print(f' Pattern "{pat}" found at depth={last["depth"]} [{last["tag"]}] cls="{last["cls"]}" children={last["children"]}')
|
||||
|
||||
# Find the parent chain from body to the container - look by scanning ALL class names
|
||||
print("\n=== ALL UNIQUE CLASS NAMES (depth <= 12) ===")
|
||||
all_classes = set()
|
||||
def collect_classes(node, depth=0, max_depth=12):
|
||||
if not isinstance(node, dict) or depth > max_depth: return
|
||||
cls = node.get('cls', '')
|
||||
if cls:
|
||||
for c in cls.split():
|
||||
if len(c) > 3 and not c.startswith('{') and 'mtk' not in c:
|
||||
all_classes.add(c)
|
||||
for child in node.get('children', []):
|
||||
collect_classes(child, depth+1, max_depth)
|
||||
|
||||
collect_classes(body)
|
||||
# Print classes sorted, grouped by potential relevance
|
||||
relevant = sorted([c for c in all_classes if any(k in c.lower() for k in
|
||||
['chat', 'message', 'response', 'agent', 'gemini', 'turn', 'model', 'user', 'bot', 'conversation', 'markdown', 'prose', 'text-', 'content'])])
|
||||
print("Relevant classes:")
|
||||
for c in relevant:
|
||||
print(f' {c}')
|
||||
Reference in New Issue
Block a user