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:
@@ -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;i<mutations.length;i++){
|
||||
if(mutations[i].addedNodes.length>0){
|
||||
@@ -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'),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user