From ed63f659756df9167c3ec6e2721bc972156c20ae Mon Sep 17 00:00:00 2001 From: Variet Worker Date: Wed, 15 Apr 2026 06:20:47 +0900 Subject: [PATCH] =?UTF-8?q?feat(observer):=20v11=20=E2=80=94=20enhanced=20?= =?UTF-8?q?context=20extraction=20+=205-level=20sibling=20search=20+=20com?= =?UTF-8?q?mand=20enrichment=20(v0.5.42)=20#task-619?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - extractContextFromNearby: span/div/p text fallback when pre/code not found (depth 0-8) - collectSiblingButtons: extended to 5 parent levels, stop only when both action+reject found - http-bridge: command enrichment — swap generic button text with actual command from description - Fixes: cmd='Always run' now shows actual command text, Cancel button detection improved --- extension/package.json | 2 +- extension/src/http-bridge.ts | 20 ++++++++++- extension/src/observer-script.ts | 61 ++++++++++++++++++++------------ 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/extension/package.json b/extension/package.json index 642018c..39935d1 100644 --- a/extension/package.json +++ b/extension/package.json @@ -2,7 +2,7 @@ "name": "gravity-bridge", "displayName": "Gravity Bridge", "description": "Discord-based unified approval system for Antigravity AI interactions.", - "version": "0.5.41", + "version": "0.5.42", "publisher": "variet", "engines": { "vscode": "^1.100.0" diff --git a/extension/src/http-bridge.ts b/extension/src/http-bridge.ts index 6d9ba49..5741b7f 100644 --- a/extension/src/http-bridge.ts +++ b/extension/src/http-bridge.ts @@ -314,6 +314,23 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) { const rawDesc = (data.description || data.command || '').replace(/Deny|Allow Once|Allow This Conversation/gi, '').trim(); pending.command = `파일 접근 권한${rawDesc ? ': ' + rawDesc : ''}`; } + // v11: Command enrichment — if command is just button text but description has actual content, + // swap them so Discord shows what's actually being executed + const descText = (pending.description || data.description || '').trim(); + const cmdText = (pending.command || data.command || '').trim(); + const GENERIC_BTN_RE = /^(?:Always\s*)?(?:Run|Allow|Accept|Approve)$/i; + if (GENERIC_BTN_RE.test(cmdText) && descText.length > 10 && descText !== cmdText) { + // Extract the actual command from description (often includes terminal prompt) + // Pattern: "…\project_name > actual_command" + let enrichedCmd = descText; + const promptMatch = descText.match(/[>»]\s*(.+)/); + if (promptMatch && promptMatch[1].trim().length > 3) { + enrichedCmd = promptMatch[1].trim(); + } + pending.command = enrichedCmd.substring(0, 200); + pending.description = `[${cmdText}] ${descText}`; + ctx.logToFile(`[HTTP] command enriched: "${cmdText}" → "${enrichedCmd.substring(0, 60)}"`); + } // WS dispatch if (ctx.wsBridge && ctx.wsBridge.isConnected()) { ctx.wsBridge.sendPending({ @@ -327,7 +344,8 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) { }); ctx.logToFile(`[HTTP-WS] pending sent via WS: ${rid}`); } - ctx.logToFile(`[HTTP] pending created: ${rid} cmd="${data.command}" btns=${(data.buttons || []).length} ctx="${(data.description || '').substring(0, 50)}"`); + ctx.logToFile(`[HTTP] pending created: ${rid} cmd="${pending.command || data.command}" btns=${(pending.buttons || data.buttons || []).length} ctx="${(pending.description || data.description || '').substring(0, 80)}"`); + if (data._debug_trail) { ctx.logToFile(`[HTTP-DIAG] trail: ${data._debug_trail.substring(0, 500)}`); } diff --git a/extension/src/observer-script.ts b/extension/src/observer-script.ts index 94f9195..4104fee 100644 --- a/extension/src/observer-script.ts +++ b/extension/src/observer-script.ts @@ -1,7 +1,7 @@ export function generateApprovalObserverScript(_port: number): string { return ` -// ── Gravity Bridge v9: Context-Aware AG Native Parser ── -// v9: Fixed 'Running N commands' false trigger + DOM-climbing context extraction +// ── Gravity Bridge v11: Enhanced Context Extraction ── +// v11: Extended span/div/p fallback for context, 5-level sibling search, improved command display (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('v9 Script loaded — Context-Aware AG Native Parser'); + log('v11 Script loaded — Enhanced Context Extraction'); // DIAGNOSTIC BEACON: immediate POST to confirm script execution in renderer try { @@ -109,11 +109,12 @@ 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) - // v10-diag: Added diagnostic trail logging + // v11: Climb DOM tree to find context near the button + // Priority: pre/code > [class*=terminal] > substantial span/div/p text > aria-label > button text function extractContextFromNearby(btn) { var node = btn; var _debugTrail = []; + var _fallbackText = ''; // Best span/div/p text found (used if no pre/code) for (var depth = 0; depth < 20 && node; depth++) { if (!node.querySelector) { _debugTrail.push('d'+depth+':noQS'); node = node.parentElement; continue; } // Look for code/pre blocks (actual command text) @@ -136,30 +137,38 @@ export function generateApprovalObserverScript(_port: number): string { var parts = []; if (headerText) parts.push(headerText); parts.push(codeText); - log('CONTEXT-OK d='+depth+' trail='+_debugTrail.join(' > ')); + log('CONTEXT-OK d='+depth+' src=code trail='+_debugTrail.join(' > ')); + _lastContextDebug = _debugTrail.join(' > '); return parts.join(' — '); } } - // v10-diag: also look for any text-bearing elements (span, div, p) with substantial text - if (depth <= 5) { + // v11: Look for substantial text in span/div/p as fallback context + if (depth <= 8 && !_fallbackText) { var textEls = node.querySelectorAll('span, div, p'); - var foundTexts = []; - for (var ti = 0; ti < Math.min(textEls.length, 10); ti++) { + for (var ti = 0; ti < Math.min(textEls.length, 20); ti++) { var tText = (textEls[ti].textContent || '').trim(); - if (tText.length > 10 && tText.length < 300 && !isNoiseLine(tText)) { - foundTexts.push(tText.substring(0, 80)); + if (tText.length > 10 && tText.length < 500 && !isNoiseLine(tText)) { + // Skip if it's just the button text itself + var btnTxt = cleanButtonText(btn); + if (tText !== btnTxt && tText.indexOf(btnTxt) !== 0) { + _fallbackText = tText.substring(0, 300); + _debugTrail.push('fallback_d='+depth+':'+_fallbackText.substring(0,40)); + break; + } } } - if (foundTexts.length > 0) { - _debugTrail.push('texts=['+foundTexts.join('|')+']'); - } } node = node.parentElement; } + // v11: Use fallback text from span/div/p if available + if (_fallbackText && _fallbackText.length > 5) { + log('CONTEXT-OK src=fallback trail='+_debugTrail.join(' > ')); + _lastContextDebug = _debugTrail.join(' > '); + return cleanLines(_fallbackText); + } // Last resort: try aria-label or title on the button var ariaLabel = btn.getAttribute('aria-label') || btn.getAttribute('title') || ''; log('CONTEXT-FAIL trail='+_debugTrail.join(' > ')); - // v10-diag: dump button ancestor chain as diagnostic _lastContextDebug = _debugTrail.join(' > '); if (ariaLabel && ariaLabel.length > 5) return ariaLabel; return cleanButtonText(btn); @@ -233,12 +242,13 @@ export function generateApprovalObserverScript(_port: number): string { function collectSiblingButtons(container,triggerBtn){ if(!container)return []; - // v9: Try multiple container levels (parent → grandparent → great-grandparent) - // to find all related approval buttons in wider DOM context + // v11: Try 5 container levels to find Cancel and other approval buttons var containers = [container]; - if (container.parentElement) containers.push(container.parentElement); - if (container.parentElement && container.parentElement.parentElement) - containers.push(container.parentElement.parentElement); + var cur = container; + for (var lvl = 0; lvl < 5 && cur.parentElement; lvl++) { + cur = cur.parentElement; + containers.push(cur); + } var result=[]; var seen={}; for(var ci=0;ci 0) break; + // v11: Only stop if we found BOTH action AND reject buttons at this level + var hasAction = false, hasReject = false; + for (var ri=0;ri