fix(observer): v12 — skip prompt-only code text + enrichment validation (v0.5.44) #task-619

- extractContextFromNearby: PROMPT_ONLY_RE skips code elements containing only terminal prompts (e.g. '\\gravity_control >')
- Multi-codeEl traversal: tries all code elements at each depth, picks longest non-prompt text
- http-bridge enrichment: validates extracted command is not just a prompt fragment before enriching
- Fixes: cmd='\\gravity_control >' (prompt-only) no longer sent to Discord as the command text
This commit is contained in:
Variet Worker
2026-04-15 07:43:15 +09:00
parent 59ddcbb612
commit 7ee5947b32
3 changed files with 43 additions and 19 deletions

View File

@@ -2,7 +2,7 @@
"name": "gravity-bridge", "name": "gravity-bridge",
"displayName": "Gravity Bridge", "displayName": "Gravity Bridge",
"description": "Discord-based unified approval system for Antigravity AI interactions.", "description": "Discord-based unified approval system for Antigravity AI interactions.",
"version": "0.5.43", "version": "0.5.44",
"publisher": "variet", "publisher": "variet",
"engines": { "engines": {
"vscode": "^1.100.0" "vscode": "^1.100.0"

View File

@@ -256,7 +256,7 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
try { try {
const data = JSON.parse(body); const data = JSON.parse(body);
// ── v11: Command enrichment FIRST — extract actual command from description ── // ── v12: Command enrichment FIRST — extract actual command from description ──
// Must run before filters so "Always run" with useful description isn't filtered out // Must run before filters so "Always run" with useful description isn't filtered out
const rawCmd = (data.command || '').trim(); const rawCmd = (data.command || '').trim();
const rawDesc = (data.description || '').trim(); const rawDesc = (data.description || '').trim();
@@ -271,9 +271,18 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
if (promptMatch && promptMatch[1].trim().length > 3) { if (promptMatch && promptMatch[1].trim().length > 3) {
extracted = promptMatch[1].trim(); extracted = promptMatch[1].trim();
} }
// v12: Validate extracted text is not just a prompt fragment
// Skip enrichment if extracted is the same as rawDesc (no > found) or looks like a bare prompt
const PROMPT_ONLY_RE = /^[\s\\\/]*[\w_.-]+\s*[>»$#]\s*$/;
const isPromptOnly = PROMPT_ONLY_RE.test(extracted) || extracted === rawDesc;
if (!isPromptOnly && extracted.length > 3) {
enrichedCmd = extracted.substring(0, 200); enrichedCmd = extracted.substring(0, 200);
enrichedDesc = `[${rawCmd}] ${rawDesc}`; enrichedDesc = `[${rawCmd}] ${rawDesc}`;
ctx.logToFile(`[HTTP] command enriched: "${rawCmd}" → "${enrichedCmd.substring(0, 60)}"`); ctx.logToFile(`[HTTP] command enriched: "${rawCmd}" → "${enrichedCmd.substring(0, 60)}"`);
} else {
// Keep original button text as command, but use desc as-is
ctx.logToFile(`[HTTP] enrichment skipped (prompt-only): "${rawCmd}" desc="${rawDesc.substring(0, 60)}"`);
}
} }
// ── Server-side false positive filter (uses enriched cmd) ── // ── Server-side false positive filter (uses enriched cmd) ──

View File

@@ -1,7 +1,7 @@
export function generateApprovalObserverScript(_port: number): string { export function generateApprovalObserverScript(_port: number): string {
return ` return `
// ── Gravity Bridge v11: Enhanced Context Extraction ── // ── Gravity Bridge v12: Prompt-Skip + Multi-CodeEl Context Extraction ──
// v11: Extended span/div/p fallback for context, 5-level sibling search, improved command display // v12: Skip prompt-only code text, try all codeEls, improved enrichment fallback
(function(){ (function(){
'use strict'; 'use strict';
var BASE='',_obs=false,_sent={},_ready=false; var BASE='',_obs=false,_sent={},_ready=false;
@@ -10,7 +10,7 @@ export function generateApprovalObserverScript(_port: number): string {
var CLEANUP_MS=300000; var CLEANUP_MS=300000;
function log(m){console.log('[GB Observer] '+m);} function log(m){console.log('[GB Observer] '+m);}
log('v11 Script loaded — Enhanced Context Extraction'); log('v12 Script loaded — Prompt-Skip Context Extraction');
// DIAGNOSTIC BEACON: immediate POST to confirm script execution in renderer // DIAGNOSTIC BEACON: immediate POST to confirm script execution in renderer
try { try {
@@ -109,12 +109,16 @@ export function generateApprovalObserverScript(_port: number): string {
return null; return null;
} }
// v11: Climb DOM tree to find context near the button // v12: Climb DOM tree to find context near the button
// Priority: pre/code > [class*=terminal] > substantial span/div/p text > aria-label > button text // Priority: pre/code (skip prompt-only) > substantial span/div/p text > aria-label > button text
// PROMPT_ONLY: matches terminal prompts like "...\project >" or "PS C:\...>" with no actual command
var PROMPT_ONLY_RE = /^(.*[\\/>»$#]\\s*)$/;
function extractContextFromNearby(btn) { function extractContextFromNearby(btn) {
var node = btn; var node = btn;
var _debugTrail = []; var _debugTrail = [];
var _fallbackText = ''; // Best span/div/p text found (used if no pre/code) var _fallbackText = ''; // Best span/div/p text found (used if no pre/code)
var _bestCodeText = ''; // Best code text found across all depths
var _bestCodeHeader = '';
for (var depth = 0; depth < 20 && node; depth++) { for (var depth = 0; depth < 20 && node; depth++) {
if (!node.querySelector) { _debugTrail.push('d'+depth+':noQS'); node = node.parentElement; continue; } if (!node.querySelector) { _debugTrail.push('d'+depth+':noQS'); node = node.parentElement; continue; }
// Look for code/pre blocks (actual command text) // Look for code/pre blocks (actual command text)
@@ -122,26 +126,37 @@ export function generateApprovalObserverScript(_port: number): string {
_debugTrail.push('d'+depth+':tag='+((node.tagName||'?').toLowerCase())+',cls='+(((typeof node.className==='string')?node.className:'').substring(0,60))+',codeEls='+codeEls.length); _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++) { for (var ci = 0; ci < codeEls.length; ci++) {
var codeText = cleanLines((codeEls[ci].textContent || '').trim().substring(0, 500)); var codeText = cleanLines((codeEls[ci].textContent || '').trim().substring(0, 500));
if (codeText && codeText.length > 5 && !/^Running\\s*\\d/i.test(codeText)) { if (!codeText || codeText.length <= 5) continue;
if (/^Running\\s*\\d/i.test(codeText)) continue;
// v12: Skip prompt-only text (e.g. "...\gravity_control >") - no actual command
if (PROMPT_ONLY_RE.test(codeText.trim())) {
_debugTrail.push('skip_prompt_ci='+ci+':'+codeText.substring(0,30));
continue;
}
// This code element has actual content - capture it
if (!_bestCodeText || codeText.length > _bestCodeText.length) {
_bestCodeText = codeText;
// Also try to get a header/title near this container // 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 headerEl = node.querySelector('h1, h2, h3, [class*="header"], [class*="title"], [class*="cursor-pointer"]');
var headerText = '';
if (headerEl) { if (headerEl) {
var hClone = headerEl.cloneNode(true); var hClone = headerEl.cloneNode(true);
var hRem = hClone.querySelectorAll('button, svg, [class*="icon"], .google-symbols'); var hRem = hClone.querySelectorAll('button, svg, [class*="icon"], .google-symbols');
for (var hi = 0; hi < hRem.length; hi++) { for (var hi = 0; hi < hRem.length; hi++) {
if (hRem[hi].parentNode) hRem[hi].parentNode.removeChild(hRem[hi]); if (hRem[hi].parentNode) hRem[hi].parentNode.removeChild(hRem[hi]);
} }
headerText = cleanLines((hClone.textContent || '').trim().substring(0, 200)); _bestCodeHeader = cleanLines((hClone.textContent || '').trim().substring(0, 200));
} }
}
}
// v12: If we found a good code text, return it immediately at this depth
if (_bestCodeText) {
var parts = []; var parts = [];
if (headerText) parts.push(headerText); if (_bestCodeHeader) parts.push(_bestCodeHeader);
parts.push(codeText); parts.push(_bestCodeText);
log('CONTEXT-OK d='+depth+' src=code trail='+_debugTrail.join(' > ')); log('CONTEXT-OK d='+depth+' src=code trail='+_debugTrail.join(' > '));
_lastContextDebug = _debugTrail.join(' > '); _lastContextDebug = _debugTrail.join(' > ');
return parts.join(' — '); return parts.join(' — ');
} }
}
// v11: Look for substantial text in span/div/p as fallback context // v11: Look for substantial text in span/div/p as fallback context
if (depth <= 8 && !_fallbackText) { if (depth <= 8 && !_fallbackText) {
var textEls = node.querySelectorAll('span, div, p'); var textEls = node.querySelectorAll('span, div, p');