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:
160
.agents/workflows/helpers/analyze_dom.py
Normal file
160
.agents/workflows/helpers/analyze_dom.py
Normal file
@@ -0,0 +1,160 @@
|
||||
"""Analyze AG Native DOM structure to find AI response containers."""
|
||||
import json, os, sys
|
||||
|
||||
def load_dump():
|
||||
bridge = os.path.join(os.path.expanduser('~'), '.gemini', 'antigravity', 'bridge')
|
||||
# Try deep-inspect result first, then dump_html
|
||||
for fname in ['deep-inspect-result.json', 'dump_html.json']:
|
||||
fpath = os.path.join(bridge, fname)
|
||||
if os.path.exists(fpath):
|
||||
print(f"Loading: {fname} ({os.path.getsize(fpath)} bytes)")
|
||||
with open(fpath, 'r', encoding='utf-8-sig') as f:
|
||||
return json.load(f), fname
|
||||
return None, None
|
||||
|
||||
def find_text_containers(node, path="", depth=0, results=None):
|
||||
"""Recursively find nodes with substantial text content (potential AI response containers)."""
|
||||
if results is None:
|
||||
results = []
|
||||
if not isinstance(node, dict):
|
||||
return results
|
||||
|
||||
tag = node.get('tag', '')
|
||||
cls = node.get('cls', '')
|
||||
text = node.get('text', '')
|
||||
attrs = node.get('attrs', {})
|
||||
children = node.get('children', [])
|
||||
|
||||
cur_path = f"{path}/{tag}"
|
||||
if cls:
|
||||
short_cls = cls[:60]
|
||||
cur_path += f".{short_cls}"
|
||||
|
||||
# Look for nodes with long text (potential AI responses)
|
||||
if text and len(text) > 50:
|
||||
results.append({
|
||||
'path': cur_path,
|
||||
'depth': depth,
|
||||
'tag': tag,
|
||||
'cls': cls[:100],
|
||||
'text_len': len(text),
|
||||
'text_preview': text[:120],
|
||||
'attrs': {k:v for k,v in attrs.items() if k not in ('style',)}
|
||||
})
|
||||
|
||||
for child in children:
|
||||
find_text_containers(child, cur_path, depth+1, results)
|
||||
|
||||
return results
|
||||
|
||||
def find_by_class_pattern(node, patterns, path="", depth=0, results=None):
|
||||
"""Find nodes matching class patterns."""
|
||||
if results is None:
|
||||
results = []
|
||||
if not isinstance(node, dict):
|
||||
return results
|
||||
|
||||
tag = node.get('tag', '')
|
||||
cls = node.get('cls', '')
|
||||
attrs = node.get('attrs', {})
|
||||
children = node.get('children', [])
|
||||
text = node.get('text', '')
|
||||
|
||||
cur_path = f"{path}/{tag}"
|
||||
|
||||
for pattern in patterns:
|
||||
if pattern.lower() in cls.lower() or pattern.lower() in str(attrs).lower():
|
||||
child_count = len(children)
|
||||
results.append({
|
||||
'path': cur_path,
|
||||
'depth': depth,
|
||||
'tag': tag,
|
||||
'cls': cls[:150],
|
||||
'pattern': pattern,
|
||||
'text_preview': text[:80] if text else '',
|
||||
'child_count': child_count,
|
||||
'attrs': {k:v[:50] for k,v in attrs.items() if k != 'style'}
|
||||
})
|
||||
|
||||
for child in children:
|
||||
find_by_class_pattern(child, patterns, cur_path, depth+1, results)
|
||||
|
||||
return results
|
||||
|
||||
def analyze_chat_structure(node, path="", depth=0):
|
||||
"""Find the chat/conversation area by looking at the main layout."""
|
||||
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', {})
|
||||
|
||||
# Print interesting structural nodes at shallow depths
|
||||
if depth <= 6:
|
||||
child_count = len(children)
|
||||
has_text = bool(text and len(text) > 10)
|
||||
info = f"{' '*depth}{tag}"
|
||||
if cls:
|
||||
info += f" .{cls[:80]}"
|
||||
if attrs:
|
||||
attr_str = ' '.join(f'{k}={v[:30]}' for k,v in attrs.items() if k not in ('style','class'))
|
||||
if attr_str:
|
||||
info += f" [{attr_str}]"
|
||||
info += f" children={child_count}"
|
||||
if has_text:
|
||||
info += f" text=\"{text[:50]}...\""
|
||||
print(info)
|
||||
|
||||
for child in children:
|
||||
analyze_chat_structure(child, f"{path}/{tag}", depth+1)
|
||||
|
||||
data, fname = load_dump()
|
||||
if not data:
|
||||
print("No dump file found!")
|
||||
sys.exit(1)
|
||||
|
||||
# Handle both dump formats
|
||||
body = data.get('body', data)
|
||||
qi = data.get('quickInfo', {})
|
||||
|
||||
print("=" * 60)
|
||||
print("QUICK INFO")
|
||||
print("=" * 60)
|
||||
if qi:
|
||||
for k, v in qi.items():
|
||||
if k == 'buttons':
|
||||
print(f"buttons ({len(v)}):")
|
||||
for b in v[:15]:
|
||||
print(f" [{b.get('tag')}] \"{b.get('text','')[:50]}\" visible={b.get('visible')} cls={b.get('cls','')[:60]}")
|
||||
elif k == 'dataAttrs':
|
||||
print(f"dataAttrs: {v[:30]}")
|
||||
else:
|
||||
print(f"{k}: {v}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("CHAT-RELATED CLASS PATTERNS")
|
||||
print("=" * 60)
|
||||
patterns = ['chat', 'message', 'conversation', 'response', 'answer', 'reply',
|
||||
'markdown', 'prose', 'content', 'panel', 'agent', 'assistant',
|
||||
'planner', 'step', 'trajectory', 'bot', 'ai-', 'turn']
|
||||
matches = find_by_class_pattern(body, patterns)
|
||||
for m in matches:
|
||||
print(f" [{m['tag']}] cls=\"{m['cls']}\" pattern={m['pattern']} children={m['child_count']} {m.get('attrs',{})}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("LONG TEXT NODES (potential AI responses)")
|
||||
print("=" * 60)
|
||||
texts = find_text_containers(body)
|
||||
texts.sort(key=lambda x: x['text_len'], reverse=True)
|
||||
for t in texts[:20]:
|
||||
print(f" [{t['tag']}] depth={t['depth']} len={t['text_len']} cls=\"{t['cls'][:60]}\"")
|
||||
print(f" text: \"{t['text_preview']}\"")
|
||||
if t['attrs']:
|
||||
print(f" attrs: {t['attrs']}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("DOM TREE (depth<=6)")
|
||||
print("=" * 60)
|
||||
analyze_chat_structure(body)
|
||||
Reference in New Issue
Block a user