diff --git a/extension/package-lock.json b/extension/package-lock.json index f59f869..ce4b84e 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "gravity-bridge", - "version": "0.5.96", + "version": "0.5.97", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gravity-bridge", - "version": "0.5.96", + "version": "0.5.97", "dependencies": { "cheerio": "^1.2.0", "ws": "^8.19.0" diff --git a/extension/package.json b/extension/package.json index 5e617e7..8114d1b 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.96", + "version": "0.5.97", "publisher": "variet", "engines": { "vscode": "^1.100.0" diff --git a/extension/scratch/verify_final.js b/extension/scratch/verify_final.js new file mode 100644 index 0000000..67034c4 --- /dev/null +++ b/extension/scratch/verify_final.js @@ -0,0 +1,114 @@ +// Final simulation: exact v0.5.96 flow with realistic DOM +const {generateApprovalObserverScript} = require('../out/observer-script'); +let s = generateApprovalObserverScript(18080); +try { new Function(s); console.log('SYNTAX: OK'); } catch(e) { console.log('SYNTAX ERROR:', e.message); process.exit(1); } + +let promptRe = /[\u003e\u00bb\u276f]\s+(.+)/; +let stripRe = /\s*(content_copy|content_paste|play_arrow|check_circle|keyboard_arrow[_a-z]*)\s*$/; +let iconFilterRe = /^(content_copy|content_paste|play_arrow|check_circle|chevron_|keyboard_arrow|more_horiz|more_vert|expand_|alternate_email|arrow_drop)/; + +// Realistic scenario: "Running command" div has siblings including a copy button +// The actual DOM probably has a structure like: +// div "Running command" +// span/div with the copy icon (textContent = "> content_copy" or just "content_copy") +// div with the actual prompt+command +// div with the buttons + +function v31_simulate(name, siblings) { + console.log('\n=== ' + name + ' ==='); + + // Step 1: Find "Running command" header + let rcIdx = -1; + for (let i = 0; i < siblings.length; i++) { + let t = siblings[i].trim(); + if (t === 'Running command' || (t.indexOf('Running command') !== -1 && t.length < 30)) { + rcIdx = i; + break; + } + } + if (rcIdx < 0) { console.log(' NO RC HEADER'); return; } + + // Step 2: Collect candidates (filter icons and buttons) + let cands = []; + for (let i = 0; i < siblings.length; i++) { + if (i === rcIdx) continue; + let t = siblings[i].trim(); + if (t.length < 5) continue; + if (iconFilterRe.test(t)) { console.log(' FILTER icon: "' + t.substring(0,40) + '"'); continue; } + if (/^(Always|Run|Allow|Cancel|Deny|keyboard_arrow)/i.test(t)) { console.log(' FILTER btn: "' + t.substring(0,40) + '"'); continue; } + if (t.indexOf('Always run') !== -1 && t.indexOf('Cancel') !== -1) { console.log(' FILTER btn-bar: "' + t.substring(0,40) + '"'); continue; } + cands.push(t); + console.log(' CANDIDATE: "' + t.substring(0,60) + '" (len=' + t.length + ')'); + } + + // Step 3: Sort by length (longest first) + cands.sort((a,b) => b.length - a.length); + + // Step 4: Extract command from best candidate + for (let cand of cands) { + let m = promptRe.exec(cand); + if (m && m[1].trim().length > 3) { + let cmdV = m[1].trim().replace(stripRe, '').trim(); + if (cmdV.length < 3) { console.log(' SKIP (too short after strip): "' + cmdV + '"'); continue; } + if (/^(Always|Run|Allow|Cancel|Deny)/i.test(cmdV)) continue; + console.log(' EXTRACTED: "' + cmdV + '"'); + return; + } + if (cand.length > 10 && /[\u276f\u003e]/.test(cand)) { + let raw = cand.replace(stripRe, '').trim(); + console.log(' EXTRACTED (raw): "' + raw + '"'); + return; + } + } + console.log(' NO MATCH - will fallback to "Always run"'); +} + +// Scenario A: What ACTUALLY happened (3 siblings, "content_copy" mixed in command text) +v31_simulate('A: Icon in command text', [ + 'Running command', + '\u276f gravity_control > Start-Sleep 12; $logFile content_copy', + 'Always run keyboard_arrow_up Cancel' +]); + +// Scenario B: Copy button as separate small div +v31_simulate('B: Icon as separate div + command div', [ + 'Running command', + '> content_copy', + '\u276f gravity_control > npm run compile', + 'Always run Cancel' +]); + +// Scenario C: Just "content_copy" standalone (no >) +v31_simulate('C: Standalone icon + command', [ + 'Running command', + 'content_copy', + '\u276f gravity_control > git push origin main', + 'Always run Cancel' +]); + +// Scenario D: Multiple icons mixed +v31_simulate('D: Multiple icons + command', [ + 'Running command', + 'play_arrow', + '> content_copy', + '\u276f gravity_control > node -e "console.log(1)" content_copy', + 'Always run keyboard_arrow_up Cancel' +]); + +// Scenario E: Edge - no command, only prompt +v31_simulate('E: Prompt only', [ + 'Running command', + '\u276f gravity_control > ', + 'Always run Cancel' +]); + +// Scenario F: The v0.5.95 cmdV=content_copy case +// This implies regex matched "content_copy" from a "> content_copy" sibling +// and there was no longer sibling +v31_simulate('F: Only icon sibling (worst case)', [ + 'Running command', + '> content_copy', + 'Always run Cancel' +]); + +console.log('\n=== SIMULATION COMPLETE ==='); diff --git a/extension/scratch/verify_junk.js b/extension/scratch/verify_junk.js new file mode 100644 index 0000000..ee18ca3 --- /dev/null +++ b/extension/scratch/verify_junk.js @@ -0,0 +1,43 @@ +const {generateApprovalObserverScript} = require('../out/observer-script'); +let s = generateApprovalObserverScript(18080); + +// Extract the regex strings +let junkMatch = s.match(/JUNK_CODE_RE\s*=\s*(\/[^;]+)/); +let promptMatch = s.match(/PROMPT_ONLY_RE\s*=\s*(\/[^;]+)/); + +console.log('JUNK_CODE_RE:', junkMatch[1].substring(0, 100)); +console.log('PROMPT_ONLY_RE:', promptMatch[1]); + +// Use eval to construct the actual regexes +let JUNK = eval(junkMatch[1]); +let PROMPT = eval(promptMatch[1]); + +let tests = [ + ['\u276f gravity_control > ', 'prompt only (no command)'], + ['\u276f extension > ', 'prompt only (extension)'], + ['\u276f gravity_control > $logFile = Join-Path $env:USERPROFILE', 'PS var assignment'], + ['\u276f extension > npm.cmd run compile', 'npm compile'], + ['\u276f gravity_control > Start-Sleep 12', 'Start-Sleep'], + ['\u276f gravity_control > git add -A; git commit -m "test"', 'git commit'], + ['\u276f gravity_control > node -e "const {gen}=require()"', 'node with require'], + ['\u276f gravity_control > Get-Content file.txt', 'Get-Content'], + ['\u276f gravity_control > npm.cmd version patch', 'npm version'], + ['function test() { return 1; }', 'JS function (should be JUNK)'], + ['const x = require("fs")', 'JS const (should be JUNK)'], + ['import { foo } from "bar"', 'JS import (should be JUNK)'], +]; + +console.log('\n=== CODE ELEMENT FILTER ANALYSIS ==='); +for (let [text, desc] of tests) { + let isJunk = JUNK.test(text); + let isPrompt = PROMPT.test(text.trim()); + let junkPart = isJunk ? text.match(JUNK)[0] : null; + + let status; + if (isPrompt) status = 'SKIP-PROMPT'; + else if (isJunk) status = 'SKIP-JUNK (' + junkPart + ')'; + else status = 'PASS'; + + let isBug = (isJunk || isPrompt) && text.indexOf('\u276f') !== -1 && text.trim().length > 25; + console.log((isBug ? 'BUG ' : ' ') + status.padEnd(40) + ' | ' + desc); +} diff --git a/extension/scratch/verify_regex.js b/extension/scratch/verify_regex.js new file mode 100644 index 0000000..679a186 --- /dev/null +++ b/extension/scratch/verify_regex.js @@ -0,0 +1,42 @@ +const {generateApprovalObserverScript} = require('../out/observer-script'); +let s = generateApprovalObserverScript(18080); + +// Find the regex used in v30 candidate matching +let idx = s.indexOf('candT.match('); +if (idx < 0) idx = s.indexOf('sibT.match('); +let reStr = s.substring(idx, s.indexOf(');', idx) + 1); +console.log('Match code:', reStr.substring(0, 60)); + +// Extract just the regex part +let reMatch = reStr.match(/\/(.*?)\//); +let reSource = reMatch ? reMatch[0] : 'NOT FOUND'; +console.log('Regex source:', reSource); + +// Build and test the actual regex +let re = new RegExp(reMatch[1]); +console.log('Regex object:', re); + +// Test with the EXACT patterns from logs +let tests = [ + ['Normal', '\u276f gravity_control > Start-Sleep 12 content_copy'], + ['Git cmd', '\u276f gravity_control > git add -A; git commit -m "test"'], + ['Short', '> content_copy'], + ['Prompt only', '\u276f gravity_control > '], + ['Dir cmd', '\u276f gravity_control > dir content_copy'], +]; + +console.log('\n=== REGEX TESTS ==='); +let stripRe = /\s*(content_copy|content_paste|play_arrow|check_circle|keyboard_arrow[_a-z]*)\s*$/; +for (let [name, text] of tests) { + let m = re.exec(text); + if (m) { + let raw = m[1].trim(); + let cleaned = raw.replace(stripRe, '').trim(); + console.log(name + ':'); + console.log(' raw match[1]: "' + raw + '"'); + console.log(' after strip: "' + cleaned + '"'); + console.log(' length ok: ' + (cleaned.length >= 3)); + } else { + console.log(name + ': NO MATCH'); + } +} diff --git a/extension/scratch/verify_v096.js b/extension/scratch/verify_v096.js new file mode 100644 index 0000000..a986acb --- /dev/null +++ b/extension/scratch/verify_v096.js @@ -0,0 +1,122 @@ +const {generateApprovalObserverScript} = require('../out/observer-script'); +let s = generateApprovalObserverScript(18080); + +// 1. SYNTAX CHECK +try { new Function(s); console.log('[1] SYNTAX: OK'); } catch(e) { console.log('[1] SYNTAX ERROR:', e.message); process.exit(1); } + +// 2. v30 block exists +let v30Start = s.indexOf('// v30:'); +let v30End = s.indexOf('// v23:', v30Start); +console.log('[2] v30 block:', v30Start > 0 && v30End > v30Start ? 'OK' : 'MISSING'); + +// 3. Key features present +console.log('[3] rcCands:', s.indexOf('rcCands') > 0 ? 'OK' : 'MISSING'); +console.log('[4] content_copy filter:', s.indexOf('content_copy|content_paste') > 0 ? 'OK' : 'MISSING'); +console.log('[5] sort by length:', s.indexOf('.sort(') > 0 ? 'OK' : 'MISSING'); +console.log('[6] icon strip replace:', (s.match(/content_copy/g)||[]).length >= 2 ? 'OK (filter+strip)' : 'CHECK'); + +// 4. Simulate exact DOM from BTN-DOM-DUMP + CONTEXT logs +let promptRe = /[\u003e\u00bb\u276f]\s+(.+)/; +let stripRe = /\s*(content_copy|content_paste|play_arrow|check_circle|keyboard_arrow[_a-z]*)\s*$/; +let iconFilterRe = /^(content_copy|content_paste|play_arrow|check_circle|chevron_|keyboard_arrow|more_horiz|more_vert|expand_|alternate_email|arrow_drop)/; +let btnFilterRe = /^(Always|Run|Allow|Cancel|Deny|keyboard_arrow)/i; + +function simulate(name, siblings) { + console.log('\n=== ' + name + ' ==='); + let rcFound = false; + for (let sib of siblings) { + if (sib === 'Running command' || (sib.indexOf('Running command') !== -1 && sib.length < 30)) { + rcFound = true; + break; + } + } + if (!rcFound) { console.log(' RC header NOT FOUND'); return null; } + + let cands = []; + for (let sib of siblings) { + if (sib === 'Running command') continue; + if (sib.length < 5) continue; + if (iconFilterRe.test(sib)) { console.log(' SKIP icon: "' + sib.substring(0,30) + '"'); continue; } + if (btnFilterRe.test(sib)) { console.log(' SKIP btn: "' + sib.substring(0,30) + '"'); continue; } + if (sib.indexOf('Always run') !== -1 && sib.indexOf('Cancel') !== -1) { console.log(' SKIP btn-bar: "' + sib.substring(0,30) + '"'); continue; } + cands.push(sib); + } + cands.sort((a,b) => b.length - a.length); + console.log(' Candidates: ' + cands.length); + for (let i = 0; i < cands.length; i++) { + console.log(' [' + i + '] len=' + cands[i].length + ': "' + cands[i].substring(0,80) + '"'); + } + + for (let cand of cands) { + let m = promptRe.exec(cand); + if (m && m[1].trim().length > 3) { + let cmdV = m[1].trim().replace(stripRe, '').trim(); + if (cmdV.length < 3) continue; + console.log(' RESULT: "' + cmdV + '"'); + return cmdV; + } + if (cand.length > 10 && /[\u276f\u003e]/.test(cand)) { + let raw = cand.replace(stripRe, '').trim(); + console.log(' RESULT (raw): "' + raw + '"'); + return raw; + } + } + console.log(' RESULT: NO MATCH'); + return null; +} + +// Case 1: From BTN-DOM-DUMP (3 siblings, command + content_copy icon) +simulate('Case1: Normal command with icon', [ + 'Running command', + '\u276f gravity_control > Start-Sleep 12; $logFile content_copy', + 'Always run keyboard_arrow_up Cancel' +]); + +// Case 2: content_copy as standalone sibling +simulate('Case2: Icon as separate div', [ + 'Running command', + 'content_copy', + '\u276f gravity_control > npm run compile', + 'Always run Cancel' +]); + +// Case 3: No icon appended +simulate('Case3: Clean command', [ + 'Running command', + '\u276f gravity_control > git add -A; git commit -m "test"', + 'Always run Cancel' +]); + +// Case 4: Very long command +simulate('Case4: Long command', [ + 'Running command', + '\u276f gravity_control > Select-String -Path "$env:USERPROFILE\\.gemini\\antigravity\\bridge\\extension.log" -Pattern "CONTEXT" content_copy', + 'Always run keyboard_arrow_up Cancel' +]); + +// Case 5: Prompt only (no command yet) +simulate('Case5: Prompt only', [ + 'Running command', + '\u276f gravity_control > ', + 'Always run Cancel' +]); + +// Case 6: Multiple icon texts +simulate('Case6: Multiple icons', [ + 'Running command', + 'play_arrow', + 'content_copy', + '\u276f gravity_control > dir content_copy', + 'Always run Cancel' +]); + +// Case 7: Observed log pattern - "content_copy" was cmdV +// This means the regex matched on just "content_copy" with a > before it +// Possible: the sibling text is "> content_copy" (very short prompt) +simulate('Case7: Short prompt with icon only', [ + 'Running command', + '> content_copy', + 'Always run Cancel' +]); + +console.log('\n=== ALL TESTS COMPLETE ==='); diff --git a/extension/scratch/verify_v32.js b/extension/scratch/verify_v32.js new file mode 100644 index 0000000..cd1814c --- /dev/null +++ b/extension/scratch/verify_v32.js @@ -0,0 +1,52 @@ +const {generateApprovalObserverScript} = require('../out/observer-script'); +let s = generateApprovalObserverScript(18080); + +// Extract the terminal prompt regex from generated code +let idx = s.indexOf('_termPromptMatch'); +let reCtx = s.substring(idx, s.indexOf(');', idx) + 1); +console.log('v32 code:', reCtx.substring(0, 80)); + +// Extract regex +let reMatch = reCtx.match(/\/(.+?)\//); +let termRe = new RegExp(reMatch[1]); +console.log('v32 regex:', termRe); + +let stripRe = /\s*(content_copy|content_paste|play_arrow)\s*$/; + +let tests = [ + // Should MATCH (terminal commands) + ['\u276f gravity_control > Start-Sleep 12', true, 'Start-Sleep'], + ['\u276f gravity_control > npm.cmd run compile', true, 'npm compile'], + ['\u276f gravity_control > $logFile = Join-Path $env:USERPROFILE', true, 'PS variable (had JUNK match)'], + ['\u276f gravity_control > git add -A; git commit -m "test"', true, 'git commit'], + ['\u276f gravity_control > node -e "const {gen}=require(\'./out\')"', true, 'node with const (was JUNK)'], + ['\u276f extension > npm.cmd run compile', true, 'extension npm'], + ['\u276f gravity_control > Start-Sleep 12 content_copy', true, 'with icon (strip)'], + ['\u276f gravity_control > Get-Content f.txt | Select-Object -Last 5', true, 'Get-Content'], + // Should NOT match (prompt only, no command) + ['\u276f gravity_control > ', false, 'prompt only'], + ['\u276f extension > ', false, 'prompt only ext'], + // Should NOT match (not terminal - JS code) + ['function test() { return 1; }', false, 'JS function'], + ['const x = require("fs")', false, 'JS const'], + ['import { foo } from "bar"', false, 'JS import'], + // Should NOT match (no prompt marker) + ['gravity_control > dir', false, 'no ❯ marker'], +]; + +console.log('\n=== v32 TERMINAL PROMPT REGEX TESTS ==='); +let pass = 0, fail = 0; +for (let [text, shouldMatch, desc] of tests) { + let m = termRe.exec(text); + let matched = false; + let cmdV = null; + if (m && m[1] && m[1].trim().length > 2) { + cmdV = m[1].trim().replace(stripRe, '').trim(); + matched = cmdV.length > 2; + } + let ok = matched === shouldMatch; + if (ok) pass++; else fail++; + console.log((ok ? 'PASS' : 'FAIL') + ' | ' + (matched ? 'MATCH' : 'SKIP ').padEnd(5) + ' | ' + desc); + if (matched && cmdV) console.log(' cmd: "' + cmdV + '"'); +} +console.log('\nResult: ' + pass + '/' + (pass+fail) + ' passed' + (fail > 0 ? ' (' + fail + ' FAILED!)' : ' ALL OK')); diff --git a/extension/src/observer-script.ts b/extension/src/observer-script.ts index b229b62..42aee27 100644 --- a/extension/src/observer-script.ts +++ b/extension/src/observer-script.ts @@ -155,6 +155,20 @@ export function generateApprovalObserverScript(_port: number): string { if (!codeText || codeText.length <= 5) continue; if (/^Running\\s*\\d/i.test(codeText)) continue; _sawCodeEls = true; + // v32: Terminal prompt detection — extract command BEFORE JUNK/PROMPT filters + // PS/bash commands can contain JS keywords (return, function, const) → false JUNK matches + var _termPromptMatch = codeText.match(/^[\\u276f\\u00bb]\\s+[^\\n]*[\\u003e]\\s+(.+)/); + if (_termPromptMatch && _termPromptMatch[1].trim().length > 2) { + var _termCmd = _termPromptMatch[1].trim(); + _termCmd = _termCmd.replace(/\\s*(content_copy|content_paste|play_arrow)\\s*$/, '').trim(); + if (_termCmd.length > 2) { + _allSkipped = false; + _bestCodeText = 'Running command: ' + _termCmd; + log('CONTEXT-OK d='+depth+' src=terminal-prompt cmd='+_termCmd.substring(0,80)); + _lastContextDebug = _debugTrail.join(' '); + return _bestCodeText; + } + } if (PROMPT_ONLY_RE.test(codeText.trim())) { _debugTrail.push('skip_prompt_ci='+ci+':'+codeText.substring(0,30)); continue;