fix(observer): v9 - stop treating Running N commands as approval button, add DOM-climbing context extraction

This commit is contained in:
Variet Worker
2026-04-13 19:37:18 +09:00
parent 2a1ebf1020
commit a8d875167d
6 changed files with 119 additions and 18 deletions

View File

@@ -1,7 +1,7 @@
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
// ── Gravity Bridge v9: Context-Aware AG Native Parser ──
// v9: Fixed 'Running N commands' false trigger + DOM-climbing context extraction
(function(){
'use strict';
var BASE='',_obs=false,_sent={},_ready=false;
@@ -10,7 +10,7 @@ export function generateApprovalObserverScript(_port: number): string {
var CLEANUP_MS=300000;
function log(m){console.log('[GB Observer] '+m);}
log('v8 Script loaded — Full-DOM AG Native Parser');
log('v9 Script loaded — Context-Aware AG Native Parser');
// DIAGNOSTIC BEACON: immediate POST to confirm script execution in renderer
try {
@@ -109,9 +109,47 @@ export function generateApprovalObserverScript(_port: number): string {
return null;
}
// v9: Climb DOM tree to find pre/code content near the button (no data-step-index needed)
function extractContextFromNearby(btn) {
var node = btn;
for (var depth = 0; depth < 20 && node; depth++) {
if (!node.querySelector) { node = node.parentElement; continue; }
// Look for code/pre blocks (actual command text)
var codeEls = node.querySelectorAll('pre, code, [class*="terminal"]');
for (var ci = 0; ci < codeEls.length; ci++) {
var codeText = cleanLines((codeEls[ci].textContent || '').trim().substring(0, 500));
if (codeText && codeText.length > 5 && !/^Running\\s*\\d/i.test(codeText)) {
// Also try to get a header/title near this container
var headerEl = node.querySelector('h1, h2, h3, [class*="header"], [class*="title"], [class*="cursor-pointer"]');
var headerText = '';
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]);
}
headerText = cleanLines((hClone.textContent || '').trim().substring(0, 200));
}
var parts = [];
if (headerText) parts.push(headerText);
parts.push(codeText);
return parts.join(' — ');
}
}
node = node.parentElement;
}
// Last resort: try aria-label or title on the button
var ariaLabel = btn.getAttribute('aria-label') || btn.getAttribute('title') || '';
if (ariaLabel && ariaLabel.length > 5) return ariaLabel;
return cleanButtonText(btn);
}
function extractStepContext(btn) {
var stepEl = getStepContainer(btn);
if (!stepEl) return cleanButtonText(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') || '?';
@@ -161,7 +199,7 @@ export function generateApprovalObserverScript(_port: number): string {
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;
// v9: Removed "Running N commands" — it's a group header, not an approval button
return false;
}
function isRejectBtn(txt) {
@@ -173,15 +211,31 @@ export function generateApprovalObserverScript(_port: number): string {
function collectSiblingButtons(container,triggerBtn){
if(!container)return [];
var siblings=container.querySelectorAll('button');
// v9: Try multiple container levels (parent → grandparent → great-grandparent)
// to find all related approval buttons in wider DOM context
var containers = [container];
if (container.parentElement) containers.push(container.parentElement);
if (container.parentElement && container.parentElement.parentElement)
containers.push(container.parentElement.parentElement);
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)});
var seen={};
for(var ci=0;ci<containers.length;ci++){
var siblings=containers[ci].querySelectorAll('button');
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;
// Skip group headers
if (/^Running\\s*\\d+\\s*commands?$/i.test(stxt)) continue;
if(!isActionBtn(stxt) && !isRejectBtn(stxt)) continue;
// Dedup by text
if(seen[stxt])continue;
seen[stxt]=true;
result.push({btn:sb,text:stxt,isPrimary:(sb===triggerBtn)});
}
// If we found action buttons at this level, don't go wider
if(result.length > 0) break;
}
return result;
}
@@ -524,13 +578,16 @@ export function generateApprovalObserverScript(_port: number): string {
var txt=cleanButtonText(b);
if(txt.length <= 1) continue;
// v9: Skip group header buttons — not approval buttons
if (/^Running\\s*\\d+\\s*commands?$/i.test(txt)) 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');
var matchedType = txt.includes('Accept') ? 'diff_review' : (txt.includes('Run') || txt.includes('Allow') ? 'command' : 'permission');
// v7: Use step-index for more unique group key
var stepContainer = getStepContainer(b);