export function generateApprovalObserverScript(_port: number): string {
  return `
// ── 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;

  function log(m){console.log('[GB Observer] '+m);}
  log('v8 Script loaded — Full-DOM AG Native Parser');

  // 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+|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) {
    if (!line || line.trim().length < 2) return true;
    var trimmed = line.trim();
    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 '';
    var lines = text.split('\\n');
    var clean = [];
    for (var i = 0; i < lines.length; i++) {
      if (!isNoiseLine(lines[i])) clean.push(lines[i].trim());
    }
    return clean.join('\\n').trim();
  }

  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<icons.length; i++) {
        if(icons[i].parentNode) icons[i].parentNode.removeChild(icons[i]);
    }
    var tr = clone.querySelector('.truncate');
    var txt = (tr ? tr.textContent : clone.textContent) || '';
    return txt.trim().replace(/^[\\s\\u200B-\\u200D\\uFEFF\\u00A0]+/, '').replace(/(Alt|Ctrl|Shift|Meta)\\+.*/i,'').trim();
  }

  function btnId(b,type){
    var txt = cleanButtonText(b);
    var parent = b.parentElement;
    var idx=0;
    if(parent){
      var siblings=parent.querySelectorAll('button');
      for(var i=0;i<siblings.length;i++){if(siblings[i]===b){idx=i;break;}}
    }
    return type+'|'+txt+'|'+idx;
  }

  // ══════════════════════════════════════════════════════════════════
  // v7: STEP-AWARE CONTEXT EXTRACTION
  // Find the closest [data-step-index] ancestor, extract step info
  // ══════════════════════════════════════════════════════════════════

  function getStepContainer(el) {
    var node = el;
    for (var depth = 0; depth < 10 && node; depth++) {
      if (node.hasAttribute && node.hasAttribute('data-step-index')) return node;
      node = node.parentElement;
    }
    return null;
  }

  function extractStepContext(btn) {
    var stepEl = getStepContainer(btn);
    if (!stepEl) return cleanButtonText(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 = (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'];
  var REJECT_WORDS = ['Reject', 'Cancel', 'Deny', 'Stop', 'Decline', 'Dismiss'];

  function isActionBtn(txt) {
    for(var i=0; i<ACTION_WORDS.length; i++) {
      if(txt.indexOf(ACTION_WORDS[i]) !== -1) return true;
    }
    if (/Running\\s*\\d*\\s*command/i.test(txt)) return true;
    return false;
  }
  function isRejectBtn(txt) {
    for(var i=0; i<REJECT_WORDS.length; i++) {
      if(txt.indexOf(REJECT_WORDS[i]) !== -1) return true;
    }
    return false;
  }

  function collectSiblingButtons(container,triggerBtn){
    if(!container)return [];
    var siblings=container.querySelectorAll('button');
    var result=[];
    for(var i=0;i<siblings.length;i++){
      var sb=siblings[i];
      if(sb.disabled||sb.hidden||(!sb.offsetParent&&sb.style.display!=='fixed'))continue;
      var stxt = cleanButtonText(sb);
      if(stxt.length <= 1) continue;
      if(!isActionBtn(stxt) && !isRejectBtn(stxt)) continue;
      result.push({btn:sb,text:stxt,isPrimary:(sb===triggerBtn)});
    }
    return result;
  }

  var HARDCODED_PORT=${_port};
  function tryPingAsync(port){
    return fetch('http://127.0.0.1:'+port+'/ping?t='+Date.now(),{signal:AbortSignal.timeout(2000)})
      .then(function(r){return r.text();}).then(function(t){return t==='pong';}).catch(function(){return false;});
  }

  function discoverPort(cb){
    var attempts=0;
    var timer=setInterval(function(){
      attempts++;
      var items = document.querySelectorAll('[aria-label^="Gravity Bridge Control"], [title^="Gravity Bridge Control"]');
      if (items.length > 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=5;

  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 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: '…', 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);
  }

  // ══════════════════════════════════════════════════════════════════
  // v7: STEP-AWARE CHAT BODY SCANNING
  // Scans [data-step-index] elements inside [data-testid="conversation-view"]
  // Extracts AI response text while filtering UI noise
  // ══════════════════════════════════════════════════════════════════

  var _lastScrapedStepIndex = -1;
  var _lastStepText = '';
  var _lastStepTextTime = 0;
  var _lastStepTextSent = false;

  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);

    // 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
    // Look for known markdown container patterns
    var mdEl = clone.querySelector('.markdown-body, .prose, [class*="markdown"], [class*="rendered"]');
    var rawText = '';
    if (mdEl && mdEl.innerText && mdEl.innerText.trim().length > 10) {
      rawText = mdEl.innerText.trim();
    } else {
      // Fallback: get all text but filter aggressively
      rawText = (clone.innerText || clone.textContent || '').trim();
    }

    // Apply line-by-line noise filter
    return cleanLines(rawText).substring(0, 3500);
  }

  function scanChatBodies() {
    if (!_ready) return;

    // One-time DOM dump
    dumpDOMStructure();

    // PRIMARY: Find conversation-view container
    var 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');
    if(!allBtns.length)return;

    for(var j=0;j<allBtns.length;j++){
      var b=allBtns[j];
      if(b.disabled||b.hidden||(!b.offsetParent&&b.style.display!=='fixed'))continue;

      var txt=cleanButtonText(b);
      if(txt.length <= 1) continue;

      if(!isActionBtn(txt)) continue;
      // Skip inline code lens buttons
      if (b.closest('.codelens-decoration') && !txt.includes('Accept')) {
          continue;
      }

      var matchedType = txt.includes('Accept') ? 'diff_review' : (txt.includes('Run') || /Running\\d/.test(txt) ? 'command' : 'permission');

      // v7: Use step-index for more unique group key
      var stepContainer = getStepContainer(b);
      var stepIdx = stepContainer ? stepContainer.getAttribute('data-step-index') : 'none';
      var groupKey = matchedType + '|' + stepIdx + '|' + btnId(b, matchedType);
      if(_sent[groupKey])continue;

      var container=b.parentElement;
      var siblings=collectSiblingButtons(container,b);
      if(siblings.length===0)siblings=[{btn:b,text:txt,isPrimary:true}];

      var buttonsArr=[];
      var btnRefs=[];
      var bidList=[];
      for(var si=0;si<siblings.length;si++){
        var sb=siblings[si];
        var sbid=btnId(sb.btn,matchedType);
        buttonsArr.push({text:sb.text,index:si,is_primary:sb.isPrimary});
        btnRefs.push(sb.btn);
        bidList.push(sbid);
      }

      var desc=extractContext(b);

      var rid=now.toString()+'_'+Math.random().toString(36).substring(2,6);

      _sent[groupKey]={rid:rid,ts:now};
      for(var mk=0;mk<bidList.length;mk++)_sent[bidList[mk]]={rid:rid,ts:now};

      log('DETECTED '+matchedType+': '+txt+' ['+desc.substring(0,80)+'] step='+stepIdx);

      (function(rid2,btnRefs2,bidList2,groupKey2,txt2,desc2,type2,buttonsArr2){
        var payload={
          request_id:rid2,
          command:txt2,
          description:desc2,
          step_type:type2,
          buttons:buttonsArr2
        };
        fetch(BASE+'/pending',{
          method:'POST',
          headers:{'Content-Type':'application/json'},
          body:JSON.stringify(payload)
        }).then(function(r){return r.json();}).then(function(d){
          if (!d.ok || d.filtered) {
            delete _sent[groupKey2];
            for(var di=0;di<bidList2.length;di++)delete _sent[bidList2[di]];
            return;
          }
          pollResponseGroup(d.request_id,btnRefs2,bidList2,groupKey2);
        }).catch(function(e){
          delete _sent[groupKey2];
          for(var di=0;di<bidList2.length;di++)delete _sent[bidList2[di]];
        });
      })(rid,btnRefs,bidList,groupKey,txt,desc,matchedType,buttonsArr);
      
      break; 
    }
  }

  function pollResponseGroup(rid,btnRefs,bidList,groupKey){
    var polls=0, maxPolls=200;
    var timer=setInterval(function(){
      polls++;
      var anyAlive=false;
      for(var ai=0;ai<btnRefs.length;ai++){
        if(document.body.contains(btnRefs[ai])){anyAlive=true;break;}
      }
      if(!anyAlive || polls>maxPolls){
        clearInterval(timer);
        delete _sent[groupKey];
        for(var ci=0;ci<bidList.length;ci++)delete _sent[bidList[ci]];
        return;
      }
      fetch(BASE+'/response/'+rid).then(function(r){return r.json();}).then(function(d){
        if(d.waiting)return;
        clearInterval(timer);
        var btnIdx=(typeof d.button_index==='number'&&d.button_index>=0)?d.button_index:-1;
        if(btnIdx>=0&&btnIdx<btnRefs.length){
          log((d.approved?'OK':'NO')+' CHOICE '+rid+' -> 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<bidList.length;ri++)delete _sent[bidList[ri]];
      }).catch(function(){});
    },1500);
  }

  function clickRejectButton(approveBtn){
    var container=approveBtn.parentElement;
    if(!container) container = approveBtn.parentElement && approveBtn.parentElement.parentElement;
    if(!container)return;
    var siblings=container.querySelectorAll('button');
    for(var i=0;i<siblings.length;i++){
      var t=cleanButtonText(siblings[i]);
      if(isRejectBtn(t)){
        log('Clicking reject: '+t);
        dispatchReactClick(siblings[i]);
        return;
      }
    }
  }

  function scheduleScan(){
    if(!_ready)return;
    var now=Date.now();
    if(now-_lastScanTs>=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;i<keys.length;i++){
      var entry=_sent[keys[i]];
      if(entry&&entry.ts&&(now-entry.ts)>CLEANUP_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;i<mutations.length;i++){
        if(mutations[i].addedNodes.length>0){
          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<btns.length;i++){
           var bx = btns[i];
           if(bx.disabled||bx.hidden||(!bx.offsetParent&&bx.style.display!=='fixed'))continue;
           var t = cleanButtonText(bx);
           if(t.length <= 1) continue;
           if (isApprove ? isActionBtn(t) : isRejectBtn(t)) {
              log('Fallback TRIGGER-CLICK on "' + t + '"');
              dispatchReactClick(bx);
              return;
           }
        }
      }).catch(function(){});
      }
      setTimeout(pollTriggerClick, 2000);
    })();

    // ── 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 — 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,
            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
          };
          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, [role="button"]');
          for (var bi = 0; bi < Math.min(allBtns.length, 50); bi++) {
            var btn = allBtns[bi];
            var btxt = (btn.textContent || '').trim().substring(0, 80);
            if (btxt.length > 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;
  }
})();
`;
}
