feat(observer): v8 full-DOM unconditional dump — body tree capture at 5s/15s/60s with depth 15, indexed dump files, deep-inspect overhaul #task-619
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
"name": "gravity-bridge",
|
"name": "gravity-bridge",
|
||||||
"displayName": "Gravity Bridge",
|
"displayName": "Gravity Bridge",
|
||||||
"description": "Discord-based unified approval system for Antigravity AI interactions.",
|
"description": "Discord-based unified approval system for Antigravity AI interactions.",
|
||||||
"version": "0.5.36",
|
"version": "0.5.37",
|
||||||
"publisher": "variet",
|
"publisher": "variet",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.100.0"
|
"vscode": "^1.100.0"
|
||||||
|
|||||||
@@ -126,9 +126,12 @@ export function startHttpBridge(ctx: HttpBridgeContext, sdk: any): Promise<numbe
|
|||||||
req.on('data', (c: string) => dumpBody += c);
|
req.on('data', (c: string) => dumpBody += c);
|
||||||
req.on('end', () => {
|
req.on('end', () => {
|
||||||
try {
|
try {
|
||||||
const fs = require('fs');
|
// Save indexed dump for history + latest as dump_html.json
|
||||||
const path = require('path');
|
let idx = 1;
|
||||||
|
try { const parsed = JSON.parse(dumpBody); idx = parsed.dumpIndex || idx; } catch {}
|
||||||
|
fs.writeFileSync(path.join(ctx.bridgePath, `dump_html_${idx}.json`), dumpBody, 'utf-8');
|
||||||
fs.writeFileSync(path.join(ctx.bridgePath, 'dump_html.json'), dumpBody, 'utf-8');
|
fs.writeFileSync(path.join(ctx.bridgePath, 'dump_html.json'), dumpBody, 'utf-8');
|
||||||
|
ctx.logToFile(`[HTTP] DOM dump #${idx} saved (${dumpBody.length} bytes)`);
|
||||||
} catch (e) { }
|
} catch (e) { }
|
||||||
res.writeHead(200); res.end('ok');
|
res.writeHead(200); res.end('ok');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
export function generateApprovalObserverScript(_port: number): string {
|
export function generateApprovalObserverScript(_port: number): string {
|
||||||
return `
|
return `
|
||||||
// ── Gravity Bridge v7: Step-Aware AG Native DOM Parser ──
|
// ── Gravity Bridge v8: Full-DOM AG Native Parser ──
|
||||||
// Uses data-testid="conversation-view" + data-step-index for reliable parsing
|
// Full body dump + step-aware parsing — no hardcoded selector dependency
|
||||||
(function(){
|
(function(){
|
||||||
'use strict';
|
'use strict';
|
||||||
var BASE='',_obs=false,_sent={},_ready=false;
|
var BASE='',_obs=false,_sent={},_ready=false;
|
||||||
var _scanScheduled=false,_lastScanTs=0;
|
var _scanScheduled=false,_lastScanTs=0;
|
||||||
var THROTTLE_MS=500;
|
var THROTTLE_MS=500;
|
||||||
var CLEANUP_MS=300000;
|
var CLEANUP_MS=300000;
|
||||||
var _dumpSent=false; // one-time DOM dump
|
|
||||||
|
|
||||||
function log(m){console.log('[GB Observer] '+m);}
|
function log(m){console.log('[GB Observer] '+m);}
|
||||||
log('v7 Script loaded — Step-Aware AG Native DOM Parser');
|
log('v8 Script loaded — Full-DOM AG Native Parser');
|
||||||
|
|
||||||
// React-Compatible Synthetic Clicker
|
// React-Compatible Synthetic Clicker
|
||||||
function dispatchReactClick(el){
|
function dispatchReactClick(el){
|
||||||
@@ -210,60 +209,132 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════════
|
||||||
// v7: DOM STRUCTURE DUMP (one-time, on first conversation-view detection)
|
// v8: FULL DOM STRUCTURE DUMP (unconditional — no selector dependency)
|
||||||
// Posts the DOM tree structure to /dump-html for debugging
|
// Dumps entire document.body tree to /dump-html for real DOM analysis
|
||||||
|
// Auto-triggers at 5s, 15s, 60s after load to capture React-rendered state
|
||||||
// ══════════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
function dumpDOMStructure() {
|
var _dumpCount=0;
|
||||||
if (_dumpSent || !_ready) return;
|
var MAX_DUMPS=5;
|
||||||
var cv = document.querySelector('[data-testid="conversation-view"]');
|
|
||||||
if (!cv) return;
|
|
||||||
_dumpSent = true;
|
|
||||||
|
|
||||||
// Walk the DOM tree and capture structure (classes, data-attrs, tag names)
|
function walkNode(el, depth, maxDepth, maxChildren) {
|
||||||
function walkNode(el, depth) {
|
if (depth > maxDepth) return {tag:'…',text:'depth limit'};
|
||||||
if (depth > 8) return null;
|
if (!el || !el.tagName) return null;
|
||||||
var info = {
|
var info = {
|
||||||
tag: el.tagName ? el.tagName.toLowerCase() : '#text',
|
tag: el.tagName ? el.tagName.toLowerCase() : '#text',
|
||||||
cls: (el.className && typeof el.className === 'string') ? el.className.substring(0, 200) : '',
|
cls: '',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
text: '',
|
text: '',
|
||||||
children: []
|
children: []
|
||||||
};
|
};
|
||||||
// Capture data-* and role attributes
|
// Capture className (string or SVGAnimatedString)
|
||||||
|
if (el.className) {
|
||||||
|
if (typeof el.className === 'string') info.cls = el.className.substring(0, 300);
|
||||||
|
else if (el.className.baseVal) info.cls = el.className.baseVal.substring(0, 300);
|
||||||
|
}
|
||||||
|
// Capture ALL attributes that might help identify structure
|
||||||
if (el.attributes) {
|
if (el.attributes) {
|
||||||
for (var ai = 0; ai < el.attributes.length; ai++) {
|
for (var ai = 0; ai < el.attributes.length; ai++) {
|
||||||
var attr = el.attributes[ai];
|
var attr = el.attributes[ai];
|
||||||
if (attr.name.startsWith('data-') || attr.name === 'role' || attr.name === 'aria-label') {
|
var n = attr.name;
|
||||||
info.attrs[attr.name] = (attr.value || '').substring(0, 100);
|
if (n === 'class' || n === 'style') continue; // class already captured, style is noise
|
||||||
|
info.attrs[n] = (attr.value || '').substring(0, 200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// For leaf nodes or shallow nodes, capture text content
|
||||||
// For leaf text nodes, capture short text
|
|
||||||
if (!el.children || el.children.length === 0) {
|
if (!el.children || el.children.length === 0) {
|
||||||
var t = (el.textContent || '').trim();
|
var t = (el.textContent || '').trim();
|
||||||
if (t.length > 0 && t.length < 100) info.text = t;
|
if (t.length > 0) info.text = t.substring(0, 200);
|
||||||
}
|
}
|
||||||
// Recurse children (limit to first 10 per level)
|
// Recurse children
|
||||||
if (el.children) {
|
if (el.children) {
|
||||||
for (var ci = 0; ci < Math.min(el.children.length, 10); ci++) {
|
var limit = Math.min(el.children.length, maxChildren);
|
||||||
var childInfo = walkNode(el.children[ci], depth + 1);
|
for (var ci = 0; ci < limit; ci++) {
|
||||||
|
var childInfo = walkNode(el.children[ci], depth + 1, maxDepth, maxChildren);
|
||||||
if (childInfo) info.children.push(childInfo);
|
if (childInfo) info.children.push(childInfo);
|
||||||
}
|
}
|
||||||
if (el.children.length > 10) {
|
if (el.children.length > limit) {
|
||||||
info.children.push({tag: '...', text: '+' + (el.children.length - 10) + ' more'});
|
info.children.push({tag: '…', text: '+' + (el.children.length - limit) + ' more children'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
var structure = walkNode(cv, 0);
|
function dumpDOMStructure() {
|
||||||
|
if (!_ready || _dumpCount >= MAX_DUMPS) return;
|
||||||
|
if (!document.body) return;
|
||||||
|
|
||||||
|
// Only dump if there are enough elements (DOM has actually rendered)
|
||||||
|
var totalEls = document.querySelectorAll('*').length;
|
||||||
|
if (totalEls < 20) {
|
||||||
|
log('DOM dump skipped: only ' + totalEls + ' elements (not rendered yet)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_dumpCount++;
|
||||||
|
|
||||||
|
// Walk entire body with generous limits
|
||||||
|
var structure = walkNode(document.body, 0, 15, 30);
|
||||||
|
|
||||||
|
// Also gather quick-reference info
|
||||||
|
var quickInfo = {
|
||||||
|
totalElements: totalEls,
|
||||||
|
title: document.title,
|
||||||
|
url: window.location.href,
|
||||||
|
hasConversationView: !!document.querySelector('[data-testid="conversation-view"]'),
|
||||||
|
hasStepIndex: !!document.querySelector('[data-step-index]'),
|
||||||
|
hasBotColor: !!document.querySelector('.text-ide-message-block-bot-color'),
|
||||||
|
hasMarkdownBody: !!document.querySelector('.markdown-body'),
|
||||||
|
hasProse: !!document.querySelector('.prose'),
|
||||||
|
buttons: [],
|
||||||
|
dataTestIds: [],
|
||||||
|
dataAttrs: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Collect all data-testid values in the page
|
||||||
|
var testIdEls = document.querySelectorAll('[data-testid]');
|
||||||
|
for (var ti = 0; ti < testIdEls.length; ti++) {
|
||||||
|
var tid = testIdEls[ti].getAttribute('data-testid');
|
||||||
|
if (quickInfo.dataTestIds.indexOf(tid) === -1) quickInfo.dataTestIds.push(tid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all unique data-* attribute names
|
||||||
|
var allEls = document.querySelectorAll('*');
|
||||||
|
var seenAttrs = {};
|
||||||
|
for (var ei = 0; ei < Math.min(allEls.length, 2000); ei++) {
|
||||||
|
var elAttrs = allEls[ei].attributes;
|
||||||
|
if (!elAttrs) continue;
|
||||||
|
for (var ai2 = 0; ai2 < elAttrs.length; ai2++) {
|
||||||
|
var aName = elAttrs[ai2].name;
|
||||||
|
if (aName.startsWith('data-') && !seenAttrs[aName]) {
|
||||||
|
seenAttrs[aName] = true;
|
||||||
|
quickInfo.dataAttrs.push(aName + '=' + (elAttrs[ai2].value || '').substring(0, 50));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect visible buttons
|
||||||
|
var allBtns = document.querySelectorAll('button, [role="button"]');
|
||||||
|
for (var bi2 = 0; bi2 < Math.min(allBtns.length, 50); bi2++) {
|
||||||
|
var btnEl = allBtns[bi2];
|
||||||
|
var btnText = (btnEl.textContent || '').trim().substring(0, 80);
|
||||||
|
if (btnText.length > 0) {
|
||||||
|
quickInfo.buttons.push({
|
||||||
|
text: btnText,
|
||||||
|
tag: btnEl.tagName.toLowerCase(),
|
||||||
|
cls: ((btnEl.className && typeof btnEl.className === 'string') ? btnEl.className : '').substring(0, 150),
|
||||||
|
visible: !!(btnEl.offsetParent || btnEl.style.display === 'fixed')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var payload = JSON.stringify({
|
var payload = JSON.stringify({
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
source: 'v7_dom_dump',
|
source: 'v8_full_body_dump',
|
||||||
conversationView: structure
|
dumpIndex: _dumpCount,
|
||||||
|
quickInfo: quickInfo,
|
||||||
|
body: structure
|
||||||
});
|
});
|
||||||
log('DOM dump: conversation-view found, sending ' + payload.length + ' bytes');
|
log('DOM dump #' + _dumpCount + ': ' + totalEls + ' elements, ' + payload.length + ' bytes');
|
||||||
fetch(BASE + '/dump-html', {
|
fetch(BASE + '/dump-html', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
@@ -271,6 +342,13 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
}).catch(function(e) { log('DOM dump failed: ' + e.message); });
|
}).catch(function(e) { log('DOM dump failed: ' + e.message); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-dump at multiple intervals to catch React rendering stages
|
||||||
|
function scheduleAutoDumps() {
|
||||||
|
setTimeout(function(){ log('Auto-dump @5s'); dumpDOMStructure(); }, 5000);
|
||||||
|
setTimeout(function(){ log('Auto-dump @15s'); dumpDOMStructure(); }, 15000);
|
||||||
|
setTimeout(function(){ log('Auto-dump @60s'); dumpDOMStructure(); }, 60000);
|
||||||
|
}
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════════
|
||||||
// v7: STEP-AWARE CHAT BODY SCANNING
|
// v7: STEP-AWARE CHAT BODY SCANNING
|
||||||
// Scans [data-step-index] elements inside [data-testid="conversation-view"]
|
// Scans [data-step-index] elements inside [data-testid="conversation-view"]
|
||||||
@@ -580,6 +658,8 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
|
|
||||||
function startObserver(){
|
function startObserver(){
|
||||||
if(_obs)return;
|
if(_obs)return;
|
||||||
|
log('startObserver() — scheduling auto-dumps and mutation observer');
|
||||||
|
scheduleAutoDumps();
|
||||||
new MutationObserver(function(mutations){
|
new MutationObserver(function(mutations){
|
||||||
for(var i=0;i<mutations.length;i++){
|
for(var i=0;i<mutations.length;i++){
|
||||||
if(mutations[i].addedNodes.length>0){
|
if(mutations[i].addedNodes.length>0){
|
||||||
@@ -613,43 +693,42 @@ export function generateApprovalObserverScript(_port: number): string {
|
|||||||
setTimeout(pollTriggerClick, 2000);
|
setTimeout(pollTriggerClick, 2000);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// ── DEEP-INSPECT POLLING ──
|
// ── DEEP-INSPECT POLLING (v8: full body dump) ──
|
||||||
(function pollDeepInspect(){
|
(function pollDeepInspect(){
|
||||||
if(_ready&&BASE){
|
if(_ready&&BASE){
|
||||||
fetch(BASE+'/deep-inspect-trigger?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
|
fetch(BASE+'/deep-inspect-trigger?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
|
||||||
if(!d.inspect)return;
|
if(!d.inspect)return;
|
||||||
log('Deep inspect triggered');
|
log('Deep inspect triggered — full body dump');
|
||||||
var cv = document.querySelector('[data-testid="conversation-view"]');
|
// Force a fresh DOM dump
|
||||||
|
_dumpCount = Math.max(0, _dumpCount - 1); // allow one more dump
|
||||||
|
dumpDOMStructure();
|
||||||
|
// Also send the result to deep-inspect-result endpoint
|
||||||
|
var bodyTree = walkNode(document.body, 0, 12, 25);
|
||||||
var result = {
|
var result = {
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
windowURL: window.location.href,
|
windowURL: window.location.href,
|
||||||
conversationViewFound: !!cv,
|
title: document.title,
|
||||||
stepElements: [],
|
|
||||||
buttons: [],
|
|
||||||
totalElements: document.body ? document.querySelectorAll('*').length : 0,
|
totalElements: document.body ? document.querySelectorAll('*').length : 0,
|
||||||
|
hasConversationView: !!document.querySelector('[data-testid="conversation-view"]'),
|
||||||
|
hasStepIndex: !!document.querySelector('[data-step-index]'),
|
||||||
|
dataTestIds: [],
|
||||||
|
buttons: [],
|
||||||
|
bodyTree: bodyTree
|
||||||
};
|
};
|
||||||
if (cv) {
|
var testIdEls = document.querySelectorAll('[data-testid]');
|
||||||
var steps = cv.querySelectorAll('[data-step-index]');
|
for (var dti = 0; dti < testIdEls.length; dti++) {
|
||||||
for (var si = 0; si < steps.length; si++) {
|
var dtid = testIdEls[dti].getAttribute('data-testid');
|
||||||
var s = steps[si];
|
if (result.dataTestIds.indexOf(dtid) === -1) result.dataTestIds.push(dtid);
|
||||||
var text = (s.textContent || '').trim().substring(0, 200);
|
|
||||||
result.stepElements.push({
|
|
||||||
stepIndex: s.getAttribute('data-step-index'),
|
|
||||||
classes: (s.className || '').substring(0, 200),
|
|
||||||
textPreview: text,
|
|
||||||
childCount: s.children ? s.children.length : 0,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
var allBtns = document.querySelectorAll('button, [role="button"]');
|
||||||
var allBtns = document.querySelectorAll('button');
|
for (var bi = 0; bi < Math.min(allBtns.length, 50); bi++) {
|
||||||
for (var bi = 0; bi < Math.min(allBtns.length, 30); bi++) {
|
|
||||||
var btn = allBtns[bi];
|
var btn = allBtns[bi];
|
||||||
var btxt = cleanButtonText(btn);
|
var btxt = (btn.textContent || '').trim().substring(0, 80);
|
||||||
if (btxt.length > 0) {
|
if (btxt.length > 0) {
|
||||||
var stepC = getStepContainer(btn);
|
|
||||||
result.buttons.push({
|
result.buttons.push({
|
||||||
text: btxt,
|
text: btxt,
|
||||||
stepIndex: stepC ? stepC.getAttribute('data-step-index') : null,
|
tag: btn.tagName.toLowerCase(),
|
||||||
|
cls: ((btn.className && typeof btn.className === 'string') ? btn.className : '').substring(0, 150),
|
||||||
visible: !!(btn.offsetParent || btn.style.display === 'fixed'),
|
visible: !!(btn.offsetParent || btn.style.display === 'fixed'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user