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:
Variet Worker
2026-04-16 05:28:44 +09:00
parent a00d561e28
commit 729875f3a6
7 changed files with 463 additions and 10 deletions

View File

@@ -0,0 +1,83 @@
"""Search AG Native DOM dump for chat content and buttons."""
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', {}))
qi = data.get('quickInfo', {})
# Show all buttons
print('=== BUTTONS ===')
for b in qi.get('buttons', []):
print(f' [{b["tag"]}] "{b["text"][:60]}" visible={b["visible"]} cls={b.get("cls","")[:80]}')
# Data attrs
print('\n=== DATA ATTRS ===')
for attr in qi.get('dataAttrs', []):
print(f' {attr}')
# Recursive search for nodes by text
def find_nodes_by_text(node, target, path='', results=None, depth=0):
if results is None: results = []
if not isinstance(node, dict): return results
tag = node.get('tag','')
cls = node.get('cls','')
text = node.get('text','')
children = node.get('children', [])
cur = f'{path}/{tag}'
if target.lower() in text.lower():
results.append({'path': cur, 'depth': depth, 'cls': cls[:80], 'text': text[:80], 'children': len(children)})
for c in children:
find_nodes_by_text(c, target, cur, results, depth+1)
return results
print('\n=== NODES containing "Always run" ===')
matches = find_nodes_by_text(body, 'Always run')
for m in matches:
print(f' depth={m["depth"]} cls="{m["cls"]}" text="{m["text"]}" children={m["children"]}')
print('\n=== NODES containing "Always" ===')
matches = find_nodes_by_text(body, 'Always')
for m in matches:
print(f' depth={m["depth"]} cls="{m["cls"]}" text="{m["text"]}" children={m["children"]}')
# Find ALL text nodes with > 30 chars
def find_all_text(node, results=None, depth=0, path=''):
if results is None: results = []
if not isinstance(node, dict): return results
tag = node.get('tag','')
cls = node.get('cls','')
text = node.get('text','')
children = node.get('children', [])
if text and len(text) > 30:
results.append({'depth': depth, 'tag': tag, 'cls': cls[:80], 'text': text[:100], 'path': f'{path}/{tag}'})
for c in children:
find_all_text(c, results, depth+1, f'{path}/{tag}')
return results
print('\n=== LONG TEXT NODES (>30 chars) ===')
texts = find_all_text(body)
texts.sort(key=lambda x: len(x['text']), reverse=True)
for t in texts[:25]:
print(f' d={t["depth"]} [{t["tag"]}] cls="{t["cls"][:50]}" len={len(t["text"])} "{t["text"][:80]}"')
# Find nodes with many children (structural containers)
def find_containers(node, results=None, depth=0, path=''):
if results is None: results = []
if not isinstance(node, dict): return results
tag = node.get('tag','')
cls = node.get('cls','')
children = node.get('children', [])
if len(children) > 5:
results.append({'depth': depth, 'tag': tag, 'cls': cls[:100], 'children': len(children), 'path': f'{path}/{tag}'})
for c in children:
find_containers(c, results, depth+1, f'{path}/{tag}')
return results
print('\n=== CONTAINERS (>5 children) ===')
conts = find_containers(body)
conts.sort(key=lambda x: x['children'], reverse=True)
for c in conts[:20]:
print(f' d={c["depth"]} [{c["tag"]}] children={c["children"]} cls="{c["cls"][:70]}"')