fix(observer): v9 - stop treating Running N commands as approval button, add DOM-climbing context extraction
This commit is contained in:
@@ -267,14 +267,16 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
|
||||
return;
|
||||
}
|
||||
// "Run" button → step_probe handles these with full command detail
|
||||
// Only let through if session is stalled AND step_probe hasn't created a pending yet
|
||||
// Only filter when step_probe IS actively tracking a session
|
||||
if (/^(?:Always\s*)?Run\b/i.test(cmd)) {
|
||||
if (!ctx.sessionStalled || ctx.lastPendingStepIndex >= 0) {
|
||||
ctx.logToFile(`[HTTP] filtered "Run" — ${!ctx.sessionStalled ? 'not stalled' : 'step_probe pending exists'}`);
|
||||
if (ctx.activeSessionId && (!ctx.sessionStalled || ctx.lastPendingStepIndex >= 0)) {
|
||||
ctx.logToFile(`[HTTP] filtered "Run" — ${!ctx.sessionStalled ? 'not stalled' : 'step_probe pending exists'} (session=${ctx.activeSessionId.substring(0, 8)})`);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ ok: false, filtered: true }));
|
||||
return;
|
||||
}
|
||||
// v9: When step_probe has no active session, let DOM observer handle approval
|
||||
ctx.logToFile(`[HTTP] allowing "Run" — step_probe has no active session`);
|
||||
}
|
||||
|
||||
const rid = data.request_id || Date.now().toString();
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user