export function generateApprovalObserverScript(_port: number): string { return ` // ?€?€ Gravity Bridge v17: Always Run Auto-Approve + Retry Detection ?€?€ // v17: "Always run" auto-approve at bridge level + Retry button relay to Discord (function(){ 'use strict'; var BASE='',_obs=false,_sent={},_ready=false; var _scanScheduled=false,_lastScanTs=0; var THROTTLE_MS=500; var CLEANUP_MS=300000; function log(m){ console.log('[GB Observer] '+m); // v19: Relay important logs to extension via HTTP so they appear in extension.log if (BASE && (m.indexOf('CV-CLASSES')!==-1 || m.indexOf('CV-CHILDREN')!==-1 || m.indexOf('child[')!==-1 || m.indexOf('CV found')!==-1 || m.indexOf('Conversation view')!==-1 || m.indexOf('BEACON')!==-1 || m.indexOf('ERROR')!==-1 || m.indexOf('chat relay')!==-1 || m.indexOf('user-cls')!==-1 || m.indexOf('CONTEXT')!==-1 || m.indexOf('BTN-DOM')!==-1 || m.indexOf('DEFERRED')!==-1 || m.indexOf('DETECTED')!==-1)) { try { fetch(BASE+'/log', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({msg:m.substring(0,2000)})}); } catch(e){} } } log('v17 Script loaded ??Always Run Auto-Approve + Retry Detection'); // DIAGNOSTIC BEACON: immediate POST to confirm script execution in renderer try { fetch('http://127.0.0.1:${_port}/ping?beacon=1&t='+Date.now()) .then(function(r){log('BEACON ping OK: '+r.status);}) .catch(function(e){log('BEACON ping FAIL: '+e.message);}); } catch(e){ log('BEACON error: '+e); } // React-Compatible Synthetic Clicker function dispatchReactClick(el){ if (!el) return; try { el.dispatchEvent(new PointerEvent('pointerdown', {bubbles:true, cancelable:true, view:window, composed:true})); el.dispatchEvent(new MouseEvent('mousedown', {bubbles:true, cancelable:true, view:window, composed:true})); el.dispatchEvent(new PointerEvent('pointerup', {bubbles:true, cancelable:true, view:window, composed:true})); el.dispatchEvent(new MouseEvent('mouseup', {bubbles:true, cancelable:true, view:window, composed:true})); el.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, view:window, composed:true})); } catch(e) { el.click(); } } // ?€?€ Noise filter: lines that are UI artifacts, not real content ?€?€ var NOISE_RE = new RegExp( '^(' + 'chevron_right|chevron_left|arrow_drop_down|arrow_drop_up|arrow_right|arrow_left|' + 'arrow_forward|arrow_back|expand_more|expand_less|close|more_horiz|more_vert|' + 'content_copy|content_paste|check|check_circle|error|warning|info|' + 'keyboard_arrow_up|keyboard_arrow_down|keyboard_arrow_left|keyboard_arrow_right|' + 'Thought for \\\\d+s?|Thought for a few seconds|Show more|Show less|Copy|Copied!|Edit|Cancel|' + 'Always run|Always allow|Running command|Running \\\\d+ commands?|' + 'Deny|Allow|Allow Once|Allow This Conversation|' + 'Run|Send|Stop|Review Changes|Accept all|Reject all|Accept|Reject' + ')$', 'i' ); var NOISE_CODE_RE = /^(declare\\s+(class|function|interface|type|enum|const|var|let)\\s|(import|export|from)\\s|\\s*[{}()\\[\\];]\\s*$|\\.ts:\\d+:|extension.*src.*sdk)/i; function isNoiseLine(line) { var trimmed = line.trim(); if (trimmed.length < 2 && !/^[-*+>]$|^[0-9]$/.test(trimmed)) return true; if (NOISE_RE.test(trimmed)) return true; if (NOISE_CODE_RE.test(trimmed)) return true; // Single-word Material icon names (all lowercase, no spaces) if (/^[a-z_]+$/.test(trimmed) && trimmed.length < 30) return true; return false; } function cleanLines(text) { if (!text) return ''; // Pre-strip: inline removal of icon names and UI noise that textContent concatenates without newlines text = text.replace(/\\b(chevron_right|chevron_left|arrow_drop_down|arrow_drop_up|arrow_right|arrow_left|arrow_forward|arrow_back|expand_more|expand_less|more_horiz|more_vert|content_copy|content_paste|keyboard_arrow_up|keyboard_arrow_down|keyboard_arrow_left|keyboard_arrow_right|slow_motion_video|open_in_new)\\b/g, '\\n'); text = text.replace(/Thought for \\d+s?/gi, ''); text = text.replace(/Thought for a few seconds/gi, ''); var lines = text.split('\\n'); var clean = []; var lastWasEmpty = false; for (var i = 0; i < lines.length; i++) { var line = lines[i]; if (line.trim().length === 0) { if (!lastWasEmpty && clean.length > 0) { clean.push(''); lastWasEmpty = true; } continue; } if (!isNoiseLine(line)) { clean.push(line.replace(/\\s+$/, '')); lastWasEmpty = false; } } while (clean.length > 0 && clean[clean.length - 1] === '') clean.pop(); return clean.join('\\n'); } function cleanButtonText(btn) { if (!btn) return ''; var clone = btn.cloneNode(true); var icons = clone.querySelectorAll('.google-symbols, .codicon, [class*="icon"], svg, .material-symbols-outlined, .material-icons'); for(var i=0; i\\xbb$#]\\s*$/; // v14: Detect CSS rules, JS source code, or extension internals in code text var JUNK_CODE_RE = /(!important|::selection|background-color:|var\\(--|font-size:|border-[a-z]|padding:|margin:|display:\\s|\\{[^}]*:[^}]*\\}|===|!==|\\|\\||\\bfunction\\s*\\(|\\bconst\\s+\\w+\\s*=|\\bvar\\s+\\w+\\s*=|\\bif\\s*\\(|\\breturn\\b|\\bimport\\s|\\bexport\\s|\\bclass\\s+\\w|\\bnew\\s+\\w|\\.test\\(|\\.match\\(|\\.replace\\(|_RE[.\\s]|\\brawDesc\\b|\\brawCmd\\b|\\benrichedCmd\\b|\\bquerySelector)/; // v14: Detect Material icon text glued with content var ICON_GLUE_RE = /(alternate_email|content_copy|content_paste|check_circle|chevron_right|chevron_left|keyboard_arrow|arrow_drop_down|arrow_drop_up|more_horiz|more_vert|expand_more|expand_less)[a-zA-Z]/; function extractContextFromNearby(btn) { var node = btn; var _debugTrail = []; var _bestCodeText = ''; var _bestCodeHeader = ''; var _sawCodeEls = false; var _allSkipped = true; // v22: Increased from 5 to 10 ??AG Native command display (SRi) can be many levels up for (var depth = 0; depth < 10 && node; depth++) { if (!node.querySelector) { _debugTrail.push('d'+depth+':noQS'); node = node.parentElement; continue; } // v22: Prioritize pre.font-mono (AG Native command line display from SRi component) var codeEls = node.querySelectorAll('pre.font-mono, pre, code, [class*="terminal"]'); _debugTrail.push('d'+depth+':tag='+((node.tagName||'?').toLowerCase())+',cls='+(((typeof node.className==='string')?node.className:'').substring(0,60))+',codeEls='+codeEls.length); for (var ci = 0; ci < codeEls.length; ci++) { var codeText = cleanLines((codeEls[ci].textContent || '').trim().substring(0, 500)); if (!codeText || codeText.length <= 5) continue; if (/^Running\\s*\\d/i.test(codeText)) continue; _sawCodeEls = true; if (PROMPT_ONLY_RE.test(codeText.trim())) { _debugTrail.push('skip_prompt_ci='+ci+':'+codeText.substring(0,30)); continue; } if (JUNK_CODE_RE.test(codeText)) { _debugTrail.push('skip_junk_ci='+ci+':'+codeText.substring(0,30)); continue; } if (ICON_GLUE_RE.test(codeText)) { _debugTrail.push('skip_iconglue_ci='+ci+':'+codeText.substring(0,30)); continue; } _allSkipped = false; if (!_bestCodeText || codeText.length > _bestCodeText.length) { _bestCodeText = codeText; var headerEl = node.querySelector('h1, h2, h3, [class*="header"], [class*="title"], [class*="cursor-pointer"]'); if (headerEl) { var hClone = headerEl.cloneNode(true); var hRem = hClone.querySelectorAll('button, svg, [class*="icon"], .google-symbols'); for (var hi = 0; hi < hRem.length; hi++) { if (hRem[hi].parentNode) hRem[hi].parentNode.removeChild(hRem[hi]); } _bestCodeHeader = cleanLines((hClone.textContent || '').trim().substring(0, 200)); } } } if (_bestCodeText) { var parts = []; if (_bestCodeHeader) parts.push(_bestCodeHeader); parts.push(_bestCodeText); log('CONTEXT-OK d='+depth+' src=code trail='+_debugTrail.join(' > ')); _lastContextDebug = _debugTrail.join(' > '); return parts.join(' \u2014 '); } // v30: Command text is in plain divs near "Running command" header, not pre/code var rcDivs = node.querySelectorAll('div'); // v30 diagnostic: log what we find at each depth where code was skipped if (_sawCodeEls && rcDivs.length > 0 && depth <= 5) { var rcSample = []; for (var rdi = 0; rdi < Math.min(rcDivs.length, 8); rdi++) { var rdt = (rcDivs[rdi].textContent || '').trim().substring(0,40); var rdc = rcDivs[rdi].children ? rcDivs[rdi].children.length : 0; rcSample.push('ch'+rdc+':"'+rdt+'"'); } log('CONTEXT-v30-SCAN d='+depth+' divs='+rcDivs.length+' ['+rcSample.join(', ')+']'); } for (var rci = 0; rci < rcDivs.length; rci++) { var rcEl = rcDivs[rci]; var rcChildCount = rcEl.children ? rcEl.children.length : 0; var rcTxt = (rcEl.textContent || '').trim(); // Match leaf div with exact or near-exact "Running command" text if ((rcTxt === 'Running command' || (rcChildCount === 0 && rcTxt.indexOf('Running command') !== -1 && rcTxt.length < 30)) && rcEl.parentElement) { var rcP = rcEl.parentElement; log('CONTEXT-v30 found RC header at d'+depth+' siblings='+rcP.children.length); for (var rcsi = 0; rcsi < rcP.children.length; rcsi++) { if (rcP.children[rcsi] === rcEl) continue; var sibT = (rcP.children[rcsi].textContent || '').trim(); if (sibT.length < 5) continue; // Skip button-bar text if (/^(Always|Run|Allow|Cancel|Deny|keyboard_arrow)/i.test(sibT)) continue; if (sibT.indexOf('Always run') !== -1 && sibT.indexOf('Cancel') !== -1) continue; // Try prompt marker extraction var pM = sibT.match(/[\\u003e\\u00bb\\u276f]\\s+(.+)/); if (pM && pM[1].trim().length > 3) { var cmdV = pM[1].trim().substring(0, 300); if (/^(Always|Run|Allow|Cancel|Deny)/i.test(cmdV)) continue; log('CONTEXT-OK d='+depth+' src=running-cmd cmdV='+cmdV.substring(0,60)); _lastContextDebug = _debugTrail.join(' '); return 'Running command: ' + cmdV; } // Fallback: use raw sibling text if it looks like a terminal line if (sibT.length > 10 && /[\\u276f\\u003e]/.test(sibT)) { log('CONTEXT-OK d='+depth+' src=running-cmd-raw sibT='+sibT.substring(0,60)); _lastContextDebug = _debugTrail.join(' '); return sibT.substring(0, 300); } } } } // v23: Also search sibling elements at each level // AG Native's command display (pre.font-mono) is a SIBLING of footer, not ancestor if (node && node.parentElement) { var siblings = node.parentElement.children; for (var si = 0; si < siblings.length; si++) { if (siblings[si] === node) continue; if (!siblings[si].querySelector) continue; var sibCodeEls = siblings[si].querySelectorAll('pre.font-mono, pre, code'); for (var sci = 0; sci < sibCodeEls.length; sci++) { var sibCode = cleanLines((sibCodeEls[sci].textContent || '').trim().substring(0, 500)); if (!sibCode || sibCode.length <= 5) continue; if (JUNK_CODE_RE.test(sibCode) || ICON_GLUE_RE.test(sibCode)) continue; if (PROMPT_ONLY_RE.test(sibCode.trim())) continue; _debugTrail.push('sibling_d'+depth+':tag='+siblings[si].tagName.toLowerCase()+',code='+sibCode.substring(0,40)); _bestCodeText = sibCode; _allSkipped = false; // Found in sibling ??return immediately var sibParts = []; var sibHdr = siblings[si].querySelector('h1, h2, h3, [class*="header"], [class*="title"], [class*="cursor-pointer"]'); if (sibHdr) sibParts.push(cleanLines((sibHdr.textContent || '').trim().substring(0, 200))); sibParts.push(sibCode); log('CONTEXT-OK d='+depth+' src=sibling trail='+_debugTrail.join(' > ')); _lastContextDebug = _debugTrail.join(' > '); return sibParts.join(' \u2014 '); } } } node = node.parentElement; } if (_sawCodeEls && _allSkipped) { log('CONTEXT-SKIP-ALL trail='+_debugTrail.join(' > ')); _lastContextDebug = _debugTrail.join(' > '); return cleanButtonText(btn); } var ariaLabel = btn.getAttribute('aria-label') || btn.getAttribute('title') || ''; log('CONTEXT-FAIL trail='+_debugTrail.join(' > ')); _lastContextDebug = _debugTrail.join(' > '); if (ariaLabel && ariaLabel.length > 5) return ariaLabel; return cleanButtonText(btn); } var _lastContextDebug = ''; function extractStepContext(btn) { var stepEl = getStepContainer(btn); if (!stepEl) { // v9 FALLBACK: no data-step-index ??climb DOM for pre/code blocks return extractContextFromNearby(btn); } var stepIdx = stepEl.getAttribute('data-step-index') || '?'; // Get step header text (first line, usually has the tool name/command) var header = stepEl.querySelector('[class*="cursor-pointer"]'); var headerText = ''; if (header) { // Clone and strip icons/buttons var hClone = header.cloneNode(true); var hRemove = hClone.querySelectorAll('button, svg, [class*="icon"], .google-symbols, .material-symbols-outlined'); for (var i = 0; i < hRemove.length; i++) { if (hRemove[i].parentNode) hRemove[i].parentNode.removeChild(hRemove[i]); } headerText = (hClone.textContent || '').trim().substring(0, 300); // Clean noise headerText = cleanLines(headerText); } // Try to get code/pre content (command detail) var codeEl = stepEl.querySelector('pre, code'); var codeText = ''; if (codeEl) { codeText = cleanLines((codeEl.textContent || '').trim().substring(0, 400)); } // Try aria-label on button var ariaLabel = btn.getAttribute('aria-label') || btn.getAttribute('title') || ''; var parts = []; if (headerText) parts.push(headerText); if (codeText && !headerText.includes(codeText.substring(0, 20))) parts.push(codeText); if (ariaLabel && ariaLabel.length > 5 && !headerText.includes(ariaLabel)) parts.push(ariaLabel); var result = parts.join(' ??'); if (!result) result = cleanButtonText(btn); return 'Step #' + stepIdx + ': ' + result; } function extractContext(b) { return extractStepContext(b); } var ACTION_WORDS = ['Allow', 'Run', 'Approve', 'Accept', 'Always allow', 'Always run', 'Retry']; var REJECT_WORDS = ['Reject', 'Cancel', 'Deny', 'Stop', 'Decline', 'Dismiss']; function isActionBtn(txt) { for(var i=0; i 0) { var text = items[0].getAttribute('aria-label') || items[0].getAttribute('title') || ''; var m = text.match(/port:(\\d+)/); if (m && m[1]) { clearInterval(timer); tryPingAsync(parseInt(m[1], 10)).then(function(ok){ cb(ok ? parseInt(m[1],10) : HARDCODED_PORT); }); return; } } if(attempts>1){ clearInterval(timer); tryPingAsync(HARDCODED_PORT).then(function(){ cb(HARDCODED_PORT); }); } },500); } discoverPort(function(port){ BASE='http://127.0.0.1:'+port; _ready=true; startObserver(); }); // ?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧 // 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 // ?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧 var _dumpCount=0; var MAX_DUMPS=8; var _conversationDumpCount=0; function walkNode(el, depth, maxDepth, maxChildren) { if (depth > maxDepth) return {tag:'MAX',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 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); } if (el.children.length > limit) { info.children.push({tag: 'TRUNC', 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') }); } } var payload = JSON.stringify({ timestamp: new Date().toISOString(), source: 'v8_full_body_dump', dumpIndex: _dumpCount, quickInfo: quickInfo, body: structure }); log('DOM dump #' + _dumpCount + ': ' + totalEls + ' elements, ' + payload.length + ' bytes'); fetch(BASE + '/dump-html', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: payload }).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); } // ?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧 // v15: AG-NATIVE + CASCADE DUAL CHAT BODY SCANNING // AG Native: #conversation > ... > .leading-relaxed.select-text // Cascade: [data-testid="conversation-view"] > [data-step-index] // ?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧 var _lastScrapedStepIndex = -1; var _lastStepText = ''; var _lastStepTextTime = 0; var _lastStepTextSent = false; var _lastResponseBlockCount = 0; // track number of response blocks for AG Native function convertNodeToMarkdown(node) { if (!node) return ''; if (node.nodeType === 3) return node.textContent; // Text node if (node.nodeType !== 1) return ''; // Skip other node types var tag = node.tagName.toLowerCase(); // Skip hidden or UI elements if (tag === 'style' || tag === 'script' || tag === 'noscript' || tag === 'button' || tag === 'svg') return ''; var cls = ''; if (typeof node.className === 'string') cls = node.className; else if (node.className && node.className.baseVal) cls = node.className.baseVal; if (cls && (cls.indexOf('google-symbols') !== -1 || cls.indexOf('material-icons') !== -1 || cls.indexOf('copy') !== -1 || cls.indexOf('codicon') !== -1)) return ''; var childrenMd = ''; for (var i = 0; i < node.childNodes.length; i++) { childrenMd += convertNodeToMarkdown(node.childNodes[i]); } // TABLE: Discord doesn't support markdown tables, so convert to fixed-width code block if (tag === 'table') { var rows = node.querySelectorAll('tr'); if (!rows || rows.length === 0) return childrenMd; var grid = []; var colWidths = []; for (var ri = 0; ri < rows.length; ri++) { var cells = rows[ri].querySelectorAll('th, td'); var row = []; for (var ci = 0; ci < cells.length; ci++) { var cellText = (cells[ci].textContent || '').trim(); row.push(cellText); if (!colWidths[ci] || cellText.length > colWidths[ci]) colWidths[ci] = cellText.length; } grid.push(row); } // Build fixed-width text var tbl = ''; for (var ri2 = 0; ri2 < grid.length; ri2++) { var line = ''; for (var ci2 = 0; ci2 < colWidths.length; ci2++) { var cell = grid[ri2][ci2] || ''; var pad = colWidths[ci2] - cell.length; var padding = ''; for (var pi = 0; pi < pad; pi++) padding += ' '; line += (ci2 > 0 ? ' | ' : '') + cell + padding; } tbl += line + '\\n'; // Add separator after header row (first row) if (ri2 === 0) { var sep = ''; for (var si2 = 0; si2 < colWidths.length; si2++) { var dashes = ''; for (var di = 0; di < colWidths[si2]; di++) dashes += '-'; sep += (si2 > 0 ? '-+-' : '') + dashes; } tbl += sep + '\\n'; } } return '\\n' + String.fromCharCode(96,96,96) + '\\n' + tbl + String.fromCharCode(96,96,96) + '\\n'; } switch (tag) { case 'h1': return '\\n# ' + childrenMd.trim() + '\\n'; case 'h2': return '\\n## ' + childrenMd.trim() + '\\n'; case 'h3': return '\\n### ' + childrenMd.trim() + '\\n'; case 'h4': return '\\n#### ' + childrenMd.trim() + '\\n'; case 'p': return '\\n' + childrenMd.trim() + '\\n'; case 'div': // Treat specific divs as blocks if they end up behaving like paragraphs if (cls.indexOf('block') !== -1 || cls.indexOf('message') !== -1) return '\\n' + childrenMd.trim() + '\\n'; return childrenMd; case 'br': return '\\n'; case 'strong': case 'b': return '**' + childrenMd + '**'; case 'em': case 'i': return '*' + childrenMd + '*'; case 'a': var href = node.getAttribute('href') || ''; return '[' + childrenMd + '](' + href + ')'; case 'code': return (node.parentNode && node.parentNode.tagName === 'PRE') ? childrenMd : (String.fromCharCode(96) + childrenMd + String.fromCharCode(96)); case 'pre': return '\\n' + String.fromCharCode(96,96,96) + '\\n' + childrenMd.trim() + '\\n' + String.fromCharCode(96,96,96) + '\\n'; case 'li': var prefix = '- '; if (node.parentNode && node.parentNode.tagName.toLowerCase() === 'ol') { var idx = 1; var curr = node.previousSibling; while(curr) { if (curr.nodeType === 1 && curr.tagName.toLowerCase() === 'li') idx++; curr = curr.previousSibling; } prefix = idx + '. '; } return '\\n' + prefix + childrenMd.trim(); case 'ul': case 'ol': return '\\n' + childrenMd + '\\n'; case 'blockquote': return '\\n> ' + childrenMd.trim().split('\\n').join('\\n> ') + '\\n'; // Table sub-elements: already handled by the table case above via querySelectorAll case 'thead': case 'tbody': case 'tfoot': case 'tr': case 'th': case 'td': return ''; default: return childrenMd; } } function extractCleanStepText(stepEl) { if (!stepEl) return ''; // Clone the step element so we can strip UI elements without affecting the DOM var clone = stepEl.cloneNode(true); // v16: Remove style/script/noscript elements FIRST var styleEls = clone.querySelectorAll('style, script, noscript, link[rel="stylesheet"]'); for (var si = 0; si < styleEls.length; si++) { if (styleEls[si].parentNode) styleEls[si].parentNode.removeChild(styleEls[si]); } // Remove all buttons (Run, Allow, Cancel, etc.) var buttons = clone.querySelectorAll('button'); for (var bi = 0; bi < buttons.length; bi++) { if (buttons[bi].parentNode) buttons[bi].parentNode.removeChild(buttons[bi]); } // Remove all SVGs and icon elements var icons = clone.querySelectorAll('svg, .google-symbols, .material-symbols-outlined, .material-icons, [class*="codicon"], [class*="icon"]'); for (var ii = 0; ii < icons.length; ii++) { if (icons[ii].parentNode) icons[ii].parentNode.removeChild(icons[ii]); } // Remove copy buttons and their containers var copyBtns = clone.querySelectorAll('[class*="copy"], [aria-label*="copy"], [title*="Copy"]'); for (var ci = 0; ci < copyBtns.length; ci++) { if (copyBtns[ci].parentNode) copyBtns[ci].parentNode.removeChild(copyBtns[ci]); } // Try to get text from markdown rendering area first // AG Native uses .leading-relaxed.select-text, Cascade uses .markdown-body/.prose var mdEl = clone.querySelector('.markdown-body, .prose, [class*="markdown"], [class*="rendered"]') || clone; // Use our custom DOM-to-Markdown parser instead of innerText var rawText = convertNodeToMarkdown(mdEl).trim(); // v18 FIX: DO NOT apply cleanLines to full markdown content, it destroys valid code blocks // Safely remove "Thought for X" lines only rawText = rawText.replace(/Thought for \\d+s?/gi, ''); rawText = rawText.replace(/Thought for a few seconds/gi, ''); // Cleanup multiple empty lines var lines = rawText.split('\\n'); var finalLines = []; var lastEmpty = false; for (var i = 0; i < lines.length; i++) { var line = lines[i].replace(/\\s+$/, ''); if (line.length === 0) { if (!lastEmpty && finalLines.length > 0) { finalLines.push(''); lastEmpty = true; } } else { finalLines.push(line); lastEmpty = false; } } // v19: Post-process ??wrap markdown table patterns in code blocks for Discord // AG Native renders tables as divs, not HTML, so DOM-level handler can't catch them. // Detect consecutive lines with pipe separators (| col1 | col2 |) and wrap in code block fences var bt = String.fromCharCode(96, 96, 96); var result = []; var tableBlock = []; var inCodeBlock = false; for (var fi = 0; fi < finalLines.length; fi++) { var fl = finalLines[fi]; // Track existing code blocks to avoid double-wrapping if (fl.trim().indexOf(bt) === 0) { inCodeBlock = !inCodeBlock; // Flush any pending table block before code block marker if (tableBlock.length > 0) { result.push(bt); for (var ti = 0; ti < tableBlock.length; ti++) result.push(tableBlock[ti]); result.push(bt); tableBlock = []; } result.push(fl); continue; } if (inCodeBlock) { result.push(fl); continue; } // Detect table row: has at least 2 pipe characters and content between them var pipeCount = 0; for (var pc = 0; pc < fl.length; pc++) { if (fl.charAt(pc) === '|') pipeCount++; } var isTableRow = pipeCount >= 2 && fl.trim().charAt(0) === '|'; var isSeparator = isTableRow && /^[\\s|:-]+$/.test(fl.trim()); if (isTableRow) { tableBlock.push(fl); } else { // Flush table block if it had enough rows (header + separator + data) if (tableBlock.length >= 2) { result.push(bt); for (var ti2 = 0; ti2 < tableBlock.length; ti2++) result.push(tableBlock[ti2]); result.push(bt); } else { // Not a real table, push lines back normally for (var ti3 = 0; ti3 < tableBlock.length; ti3++) result.push(tableBlock[ti3]); } tableBlock = []; result.push(fl); } } // Flush trailing table block if (tableBlock.length >= 2) { result.push(bt); for (var ti4 = 0; ti4 < tableBlock.length; ti4++) result.push(tableBlock[ti4]); result.push(bt); } else { for (var ti5 = 0; ti5 < tableBlock.length; ti5++) result.push(tableBlock[ti5]); } return result.join('\\n').substring(0, 3500); } function scanChatBodies() { if (!_ready) return; // One-time DOM dump dumpDOMStructure(); // ?€?€ STRATEGY 1: AG Native ??#conversation or .antigravity-agent-side-panel ?€?€ var cv = document.querySelector('#conversation'); if (!cv) { cv = document.querySelector('.antigravity-agent-side-panel'); } // v19: Fallback ??find conversation by tracing from known content elements if (!cv) { var probe = document.querySelector('.leading-relaxed.select-text') || document.querySelector('.text-ide-message-block-bot-color'); if (probe) { // Walk up to find a reasonable container (has overflow-y or is big enough) var p = probe.parentElement; for (var pi2 = 0; pi2 < 10 && p && p !== document.body; pi2++) { var pCls = (typeof p.className === 'string') ? p.className : ''; if (pCls.indexOf('overflow') !== -1 || p.children.length > 3) { cv = p; break; } p = p.parentElement; } if (!cv && probe.parentElement) cv = probe.parentElement.parentElement || probe.parentElement; } } if (cv) { // v20: Dump CV structure for first 3 scans to ensure we capture it (even with stale HTML cache) if (_conversationDumpCount < 3) { _conversationDumpCount++; log('CV found via: ' + (cv.id || (typeof cv.className === 'string' ? cv.className : cv.tagName) || 'unknown').substring(0, 100)); // Log all unique class names under #conversation for selector discovery var allCvEls = cv.querySelectorAll('*'); var clsSet = {}; for (var ci2 = 0; ci2 < allCvEls.length; ci2++) { var cn = allCvEls[ci2].className; if (typeof cn === 'string' && cn.length > 0) { var parts = cn.split(/\\s+/); for (var pi = 0; pi < parts.length; pi++) { if (parts[pi].length > 3 && !clsSet[parts[pi]]) clsSet[parts[pi]] = true; } } } var clsList = Object.keys(clsSet).sort().join(', '); log('CV-CLASSES (' + Object.keys(clsSet).length + '): ' + clsList.substring(0, 1500)); // v19: Log direct children to discover message block structure var cvKids = cv.children; log('CV-CHILDREN (' + cvKids.length + '):'); for (var ck = 0; ck < Math.min(cvKids.length, 15); ck++) { var kid = cvKids[ck]; var kidCls = (typeof kid.className === 'string') ? kid.className : ''; var kidText = (kid.textContent || '').trim().substring(0, 60); log(' child[' + ck + '] tag=' + kid.tagName + ' cls=' + kidCls.substring(0, 120) + ' text=' + kidText); } // v22: Deep-dive into gap-8 container to find individual message blocks var msgContainer = cv.querySelector('.gap-8') || cv.children[0]; if (msgContainer) { var msgKids = msgContainer.children; log('MSG-BLOCKS (' + msgKids.length + '):'); for (var mk = 0; mk < Math.min(msgKids.length, 30); mk++) { var mb = msgKids[mk]; var mbCls = (typeof mb.className === 'string') ? mb.className : ''; var mbText = (mb.textContent || '').trim().substring(0, 80); var hasLeadingRelaxed = mb.querySelector('.leading-relaxed.select-text') ? 'Y' : 'N'; var firstChildCls = (mb.children[0] && typeof mb.children[0].className === 'string') ? mb.children[0].className : ''; log(' msg[' + mk + '] cls=' + mbCls.substring(0, 120) + ' lr=' + hasLeadingRelaxed + ' fc=' + firstChildCls.substring(0, 80) + ' text=' + mbText.substring(0, 60)); } } // Force a dump with conversation context dumpDOMStructure(); } // v22: AI response = .leading-relaxed.select-text, User message = .select-text.rounded-lg (Esn component, msn class) // Source: jetskiAgent/main.js ??msn="bg-gray-500/10 border border-gray-500/20 p-2 rounded-lg w-full text-sm select-text" var responseBlocks = cv.querySelectorAll('.leading-relaxed.select-text, .select-text.rounded-lg'); if (responseBlocks.length > 0) { // v22: Filter out thinking/reasoning blocks ??they have ancestor with max-h-[200px] // These are internal AI reasoning and should NOT be relayed to Discord var filteredBlocks = []; for (var fbi = 0; fbi < responseBlocks.length; fbi++) { var isThinking = false; var ancestor = responseBlocks[fbi].parentElement; for (var depth = 0; ancestor && depth < 5; depth++) { var aCls = (typeof ancestor.className === 'string') ? ancestor.className : ''; if (aCls.indexOf('max-h-[200px]') !== -1 || aCls.indexOf('max-h-[150px]') !== -1) { isThinking = true; break; } ancestor = ancestor.parentElement; } if (!isThinking) filteredBlocks.push(responseBlocks[fbi]); } if (filteredBlocks.length === 0) return; // Process the LAST (most recent) non-thinking response block var lastBlock = filteredBlocks[filteredBlocks.length - 1]; // Skip if already scraped if (lastBlock.dataset.agChatScraped === 'true' || lastBlock.dataset.agChatScraped === 'pending') { // Check for NEW blocks since last scrape if (filteredBlocks.length > _lastResponseBlockCount) { // New block appeared ??process it for (var rbi = filteredBlocks.length - 1; rbi >= 0; rbi--) { if (filteredBlocks[rbi].dataset.agChatScraped !== 'true' && filteredBlocks[rbi].dataset.agChatScraped !== 'pending') { lastBlock = filteredBlocks[rbi]; break; } } if (lastBlock.dataset.agChatScraped === 'true' || lastBlock.dataset.agChatScraped === 'pending') return; } else { return; // Already scraped, no new blocks } } var blockText = extractCleanStepText(lastBlock); var clsStr = (typeof lastBlock.className === 'string') ? lastBlock.className : ''; // v19: Log block classes for user message selector discovery var parentCls = lastBlock.parentElement ? ((typeof lastBlock.parentElement.className === 'string') ? lastBlock.parentElement.className : '') : ''; var grandCls = (lastBlock.parentElement && lastBlock.parentElement.parentElement) ? ((typeof lastBlock.parentElement.parentElement.className === 'string') ? lastBlock.parentElement.parentElement.className : '') : ''; log('user-cls-debug block=' + clsStr.substring(0, 150) + ' | parent=' + parentCls.substring(0, 150) + ' | grand=' + grandCls.substring(0, 150) + ' | text=' + (blockText||'').substring(0, 50)); // v22: Detect user message: has select-text + rounded-lg but NOT leading-relaxed var isUser = (clsStr.indexOf('rounded-lg') !== -1 && clsStr.indexOf('leading-relaxed') === -1) || clsStr.indexOf('user-color') !== -1; var role = isUser ? 'user' : 'bot'; // Bot messages often start empty and stream in. User messages are usually immediate. if (blockText && (blockText.length > 30 || isUser && blockText.length > 0)) { // QUALITY CHECK: Skip if the text is mostly short lines (UI artifacts), BUT skip this check for user messages if (!isUser) { var lines = blockText.split('\\n').filter(function(l) { return l.trim().length > 0; }); var longLines = lines.filter(function(l) { return l.trim().length > 20; }); if (longLines.length === 0) { log('AG-Native: skipped (no long lines, likely UI noise)'); return; } } // Wait for content to stabilize (3s no change) if (_lastStepText !== blockText) { _lastStepText = blockText; _lastStepTextTime = Date.now(); _lastStepTextSent = false; return; // Wait for next scan cycle } if (_lastStepTextSent) return; // Bot needs 3s to stabilize, User just needs 500ms var waitTime = isUser ? 500 : 3000; if (Date.now() - _lastStepTextTime < waitTime) return; // Still waiting // v21: DOM-based chat relay RE-ENABLED ??GetCascadeTrajectorySteps does NOT // return steps for in-progress cascades, making Step Probe RT-CAPTURE useless. // Observer DOM extraction is the ONLY real-time path for AI response relay. _lastStepTextSent = true; _lastResponseBlockCount = filteredBlocks.length; lastBlock.dataset.agChatScraped = 'pending'; log('AG-Native chat relay [' + role + ']: blocks=' + filteredBlocks.length + ' text=' + blockText.length + ' chars'); (function(el, txt, count, r) { fetch(BASE + '/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: txt, source: 'ag_native_block_' + count, block_index: count, role: r }) }).then(function() { el.dataset.agChatScraped = 'true'; log('AG-Native chat sent OK'); }) .catch(function(e) { el.dataset.agChatScraped = 'false'; log('AG-Native chat send error: ' + e.message); }); })(lastBlock, blockText, filteredBlocks.length, role); } return; // AG Native path handled ??don't fall through to Cascade path } } // ?€?€ STRATEGY 2: Cascade ??[data-testid="conversation-view"] ?€?€ cv = document.querySelector('[data-testid="conversation-view"]'); if (!cv) { // FALLBACK: Try older selectors cv = document.querySelector('[class*="conversation"], [class*="chat-container"]'); if (!cv) return; } // Find ALL step elements within the conversation var stepEls = cv.querySelectorAll('[data-step-index]'); if (stepEls.length === 0) { // FALLBACK: Try text-ide-message-block-bot-color directly var botMsgs = cv.querySelectorAll('.text-ide-message-block-bot-color'); if (botMsgs.length === 0) return; // Use the last bot message as a pseudo-step var lastBot = botMsgs[botMsgs.length - 1]; if (lastBot.dataset.agChatScraped === 'true' || lastBot.dataset.agChatScraped === 'pending') return; var botText = extractCleanStepText(lastBot); if (botText && botText.length > 20) { if (_lastStepText !== botText) { _lastStepText = botText; _lastStepTextTime = Date.now(); _lastStepTextSent = false; } else if (!_lastStepTextSent && (Date.now() - _lastStepTextTime > 3000)) { _lastStepTextSent = true; lastBot.dataset.agChatScraped = 'pending'; fetch(BASE + '/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: botText, source: 'fallback_bot_msg' }) }).then(function() { lastBot.dataset.agChatScraped = 'true'; }) .catch(function() { lastBot.dataset.agChatScraped = 'false'; }); } } return; } // Find the LATEST step that has meaningful text content (AI response) // Iterate backwards to find the most recent unscraped step for (var si = stepEls.length - 1; si >= Math.max(0, stepEls.length - 5); si--) { var stepEl = stepEls[si]; var stepIdx = parseInt(stepEl.getAttribute('data-step-index') || '-1', 10); // Skip already scraped if (stepEl.dataset.agChatScraped === 'true' || stepEl.dataset.agChatScraped === 'pending') continue; // Skip steps we've already processed if (stepIdx >= 0 && stepIdx <= _lastScrapedStepIndex) continue; // Check if this step has substantial text content (not just a tool call header) var stepText = extractCleanStepText(stepEl); if (!stepText || stepText.length < 30) continue; // QUALITY CHECK: Skip if the text is mostly short lines (UI artifacts) var lines = stepText.split('\\n').filter(function(l) { return l.trim().length > 0; }); var longLines = lines.filter(function(l) { return l.trim().length > 20; }); if (longLines.length === 0) { log('Step ' + stepIdx + ': skipped (no long lines, likely UI noise)'); continue; } // Wait for content to stabilize (3s no change) if (_lastStepText !== stepText) { _lastStepText = stepText; _lastStepTextTime = Date.now(); _lastStepTextSent = false; break; // Wait for next scan cycle } if (_lastStepTextSent) continue; if (Date.now() - _lastStepTextTime < 3000) break; // Still waiting // Content is stable ??send it _lastStepTextSent = true; _lastScrapedStepIndex = stepIdx; stepEl.dataset.agChatScraped = 'pending'; log('Chat relay: step=' + stepIdx + ' text=' + stepText.length + ' chars'); (function(el, txt, idx) { fetch(BASE + '/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: txt, source: 'step_' + idx, step_index: idx }) }).then(function() { el.dataset.agChatScraped = 'true'; }) .catch(function() { el.dataset.agChatScraped = 'false'; }); })(stepEl, stepText, stepIdx); break; } } // ?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧 // BUTTON SCANNING (approval detection) // ?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧 function scan(){ if(!_ready)return; scanChatBodies(); var now=Date.now(); var allBtns=document.querySelectorAll('button, [role="button"], a.monaco-button, .monaco-text-button, vscode-button'); if(!allBtns.length)return; // v25: One-shot debug ??find Accept/Reject elements in ANY tag (run once per 30s) if (!scan._lastAcceptScan || now - scan._lastAcceptScan > 30000) { scan._lastAcceptScan = now; var allEls = document.querySelectorAll('button, a, div, span, [role="button"]'); for (var ai = 0; ai < allEls.length; ai++) { var aTxt = (allEls[ai].textContent || '').trim(); if (aTxt.length > 2 && aTxt.length < 30 && /Accept|Reject all/i.test(aTxt)) { log('ACCEPT-SCAN tag=' + allEls[ai].tagName + ' cls=' + (allEls[ai].className || '').substring(0,80) + ' txt=' + aTxt.substring(0,40) + ' oP=' + !!allEls[ai].offsetParent + ' dis=' + allEls[ai].disabled + ' hid=' + allEls[ai].hidden); } } } for(var j=0;jmaxPolls){ clearInterval(timer); delete _sent[groupKey]; for(var ci=0;ci=0)?d.button_index:-1; if(btnIdx>=0&&btnIdx clicking button['+btnIdx+']'); dispatchReactClick(btnRefs[btnIdx]); } else if(d.approved){ log('OK APPROVED '+rid+' -> clicking primary'); dispatchReactClick(btnRefs[0]); } else { log('NO REJECTED '+rid+' -> finding reject button'); clickRejectButton(btnRefs[0]); } delete _sent[groupKey]; for(var ri=0;ri=THROTTLE_MS){ _lastScanTs=now; scan(); } else if(!_scanScheduled){ _scanScheduled=true; setTimeout(function(){ _scanScheduled=false; _lastScanTs=Date.now(); scan(); },THROTTLE_MS-(now-_lastScanTs)); } } setInterval(function(){ var now=Date.now(); var keys=Object.keys(_sent); for(var i=0;iCLEANUP_MS){ delete _sent[keys[i]]; } } },60000); function startObserver(){ if(_obs)return; log('startObserver() ??scheduling auto-dumps and mutation observer'); scheduleAutoDumps(); new MutationObserver(function(mutations){ for(var i=0;i0){ scheduleScan(); return; } } }).observe(document.body,{childList:true,subtree:true}); setInterval(scheduleScan,3000); // ?€?€ TRIGGER-CLICK POLLING ?€?€ (function pollTriggerClick(){ if(_ready&&BASE){ fetch(BASE+'/trigger-click?t='+Date.now()).then(function(r){return r.json();}).then(function(d){ if(!d.action)return; var isApprove = (d.action==='approve'); var btns = document.querySelectorAll('button'); for(var i=0;i 0) { result.buttons.push({ text: btxt, tag: btn.tagName.toLowerCase(), cls: ((btn.className && typeof btn.className === 'string') ? btn.className : '').substring(0, 150), visible: !!(btn.offsetParent || btn.style.display === 'fixed'), }); } } fetch(BASE+'/deep-inspect-result', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(result) }).catch(function(){}); }).catch(function(){}); } setTimeout(pollDeepInspect, 3000); })(); _obs=true; } })(); `; }