diff --git a/extension/package.json b/extension/package.json index f97b5cd..ea60906 100644 --- a/extension/package.json +++ b/extension/package.json @@ -2,7 +2,7 @@ "name": "gravity-bridge", "displayName": "Gravity Bridge", "description": "Discord-based unified approval system for Antigravity AI interactions.", - "version": "0.5.36", + "version": "0.5.37", "publisher": "variet", "engines": { "vscode": "^1.100.0" diff --git a/extension/src/http-bridge.ts b/extension/src/http-bridge.ts index 8341289..dc99a9b 100644 --- a/extension/src/http-bridge.ts +++ b/extension/src/http-bridge.ts @@ -126,9 +126,12 @@ export function startHttpBridge(ctx: HttpBridgeContext, sdk: any): Promise dumpBody += c); req.on('end', () => { try { - const fs = require('fs'); - const path = require('path'); + // Save indexed dump for history + latest as dump_html.json + 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'); + ctx.logToFile(`[HTTP] DOM dump #${idx} saved (${dumpBody.length} bytes)`); } catch (e) { } res.writeHead(200); res.end('ok'); }); diff --git a/extension/src/observer-script.ts b/extension/src/observer-script.ts index 26b777f..74cfa76 100644 --- a/extension/src/observer-script.ts +++ b/extension/src/observer-script.ts @@ -1,17 +1,16 @@ export function generateApprovalObserverScript(_port: number): string { return ` -// ── Gravity Bridge v7: Step-Aware AG Native DOM Parser ── -// Uses data-testid="conversation-view" + data-step-index for reliable parsing +// ── Gravity Bridge v8: Full-DOM AG Native Parser ── +// Full body dump + step-aware parsing — no hardcoded selector dependency (function(){ 'use strict'; var BASE='',_obs=false,_sent={},_ready=false; var _scanScheduled=false,_lastScanTs=0; var THROTTLE_MS=500; var CLEANUP_MS=300000; - var _dumpSent=false; // one-time DOM dump 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 function dispatchReactClick(el){ @@ -210,60 +209,132 @@ export function generateApprovalObserverScript(_port: number): string { }); // ══════════════════════════════════════════════════════════════════ - // v7: DOM STRUCTURE DUMP (one-time, on first conversation-view detection) - // Posts the DOM tree structure to /dump-html for debugging + // v8: FULL DOM STRUCTURE DUMP (unconditional — no selector dependency) + // 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() { - if (_dumpSent || !_ready) return; - var cv = document.querySelector('[data-testid="conversation-view"]'); - if (!cv) return; - _dumpSent = true; + var _dumpCount=0; + var MAX_DUMPS=5; - // Walk the DOM tree and capture structure (classes, data-attrs, tag names) - function walkNode(el, depth) { - if (depth > 8) return null; - var info = { - tag: el.tagName ? el.tagName.toLowerCase() : '#text', - cls: (el.className && typeof el.className === 'string') ? el.className.substring(0, 200) : '', - attrs: {}, - text: '', - children: [] - }; - // Capture data-* and role attributes - if (el.attributes) { - for (var ai = 0; ai < el.attributes.length; ai++) { - var attr = el.attributes[ai]; - if (attr.name.startsWith('data-') || attr.name === 'role' || attr.name === 'aria-label') { - info.attrs[attr.name] = (attr.value || '').substring(0, 100); - } - } + function walkNode(el, depth, maxDepth, maxChildren) { + if (depth > maxDepth) return {tag:'…',text:'depth limit'}; + if (!el || !el.tagName) return null; + var info = { + tag: el.tagName ? el.tagName.toLowerCase() : '#text', + cls: '', + attrs: {}, + text: '', + children: [] + }; + // 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) { + for (var ai = 0; ai < el.attributes.length; ai++) { + var attr = el.attributes[ai]; + var n = attr.name; + if (n === 'class' || n === 'style') continue; // class already captured, style is noise + info.attrs[n] = (attr.value || '').substring(0, 200); } - // For leaf text nodes, capture short text - if (!el.children || el.children.length === 0) { - var t = (el.textContent || '').trim(); - if (t.length > 0 && t.length < 100) info.text = t; + } + // For leaf nodes or shallow nodes, capture text content + if (!el.children || el.children.length === 0) { + var t = (el.textContent || '').trim(); + if (t.length > 0) info.text = t.substring(0, 200); + } + // Recurse children + if (el.children) { + var limit = Math.min(el.children.length, maxChildren); + for (var ci = 0; ci < limit; ci++) { + var childInfo = walkNode(el.children[ci], depth + 1, maxDepth, maxChildren); + if (childInfo) info.children.push(childInfo); } - // Recurse children (limit to first 10 per level) - if (el.children) { - for (var ci = 0; ci < Math.min(el.children.length, 10); ci++) { - var childInfo = walkNode(el.children[ci], depth + 1); - if (childInfo) info.children.push(childInfo); - } - if (el.children.length > 10) { - info.children.push({tag: '...', text: '+' + (el.children.length - 10) + ' more'}); - } + if (el.children.length > limit) { + info.children.push({tag: '…', text: '+' + (el.children.length - limit) + ' more children'}); + } + } + return info; + } + + 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') + }); } - return info; } - var structure = walkNode(cv, 0); var payload = JSON.stringify({ timestamp: new Date().toISOString(), - source: 'v7_dom_dump', - conversationView: structure + source: 'v8_full_body_dump', + 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', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -271,6 +342,13 @@ export function generateApprovalObserverScript(_port: number): string { }).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 // Scans [data-step-index] elements inside [data-testid="conversation-view"] @@ -580,6 +658,8 @@ export function generateApprovalObserverScript(_port: number): string { function startObserver(){ if(_obs)return; + log('startObserver() — scheduling auto-dumps and mutation observer'); + scheduleAutoDumps(); new MutationObserver(function(mutations){ for(var i=0;i0){ @@ -613,43 +693,42 @@ export function generateApprovalObserverScript(_port: number): string { setTimeout(pollTriggerClick, 2000); })(); - // ── DEEP-INSPECT POLLING ── + // ── DEEP-INSPECT POLLING (v8: full body dump) ── (function pollDeepInspect(){ if(_ready&&BASE){ fetch(BASE+'/deep-inspect-trigger?t='+Date.now()).then(function(r){return r.json();}).then(function(d){ if(!d.inspect)return; - log('Deep inspect triggered'); - var cv = document.querySelector('[data-testid="conversation-view"]'); + log('Deep inspect triggered — full body dump'); + // 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 = { timestamp: new Date().toISOString(), windowURL: window.location.href, - conversationViewFound: !!cv, - stepElements: [], - buttons: [], + title: document.title, 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 steps = cv.querySelectorAll('[data-step-index]'); - for (var si = 0; si < steps.length; si++) { - var s = steps[si]; - 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 testIdEls = document.querySelectorAll('[data-testid]'); + for (var dti = 0; dti < testIdEls.length; dti++) { + var dtid = testIdEls[dti].getAttribute('data-testid'); + if (result.dataTestIds.indexOf(dtid) === -1) result.dataTestIds.push(dtid); } - var allBtns = document.querySelectorAll('button'); - for (var bi = 0; bi < Math.min(allBtns.length, 30); bi++) { + var allBtns = document.querySelectorAll('button, [role="button"]'); + for (var bi = 0; bi < Math.min(allBtns.length, 50); bi++) { var btn = allBtns[bi]; - var btxt = cleanButtonText(btn); + var btxt = (btn.textContent || '').trim().substring(0, 80); if (btxt.length > 0) { - var stepC = getStepContainer(btn); result.buttons.push({ 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'), }); }