diff --git a/extension/package-lock.json b/extension/package-lock.json index 884d520..c2c0e14 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "gravity-bridge", - "version": "0.5.90", + "version": "0.5.91", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gravity-bridge", - "version": "0.5.90", + "version": "0.5.91", "dependencies": { "cheerio": "^1.2.0", "ws": "^8.19.0" diff --git a/extension/package.json b/extension/package.json index b367966..9772ddd 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.90", + "version": "0.5.91", "publisher": "variet", "engines": { "vscode": "^1.100.0" diff --git a/extension/scratch/analyze_all_dumps.js b/extension/scratch/analyze_all_dumps.js new file mode 100644 index 0000000..b866d4d --- /dev/null +++ b/extension/scratch/analyze_all_dumps.js @@ -0,0 +1,98 @@ +const fs = require('fs'); +const path = require('path'); + +// Try all available dumps +const bridgePath = path.join(process.env.USERPROFILE, '.gemini/antigravity/bridge'); +const dumpFiles = ['dump_html_1.json', 'dump_html_2.json', 'dump_html_3.json', 'dump_html_4.json', 'dump_html_5.json', 'deep-inspect-result.json', 'deep-inspect-manual.json']; + +function printTree(node, indent, maxDepth) { + if (!node || indent > maxDepth) return; + const tag = (node.tag || node.tagName || '?').toLowerCase(); + const clsArr = (node.cls || node.className || '').split(' ').filter(c => c.length > 0); + const text = (node.text || node.textContent || '').substring(0, 50).replace(/[\n\r]+/g, ' '); + const childCount = (node.children || []).length; + let line = ' '.repeat(indent) + tag; + if (clsArr.length > 0) line += '.' + clsArr[0]; + if (childCount) line += ' [' + childCount + ']'; + if (text && childCount === 0) line += ' = "' + text + '"'; + console.log(line); + if (node.children) { + for (const c of node.children) printTree(c, indent + 1, maxDepth); + } +} + +// Find the conversation area in each dump +function findConvo(node, depth) { + if (!node || depth > 20) return null; + const cls = node.cls || node.className || ''; + if (cls.includes('bg-agent-convo-background') || cls.includes('agent-convo')) return node; + if (node.children) { + for (const c of node.children) { + const r = findConvo(c, depth + 1); + if (r) return r; + } + } + return null; +} + +// Find buttons (Run, Allow, Always run, Accept) +function findButtons(node, depth, results) { + if (!node || depth > 25) return; + const tag = (node.tag || node.tagName || '').toLowerCase(); + const text = (node.text || node.textContent || ''); + if (tag === 'button' && /Run|Allow|Accept|Always/i.test(text) && text.length < 50) { + results.push({ text: text.trim(), depth }); + } + if (node.children) { + for (const c of node.children) findButtons(c, depth + 1, results); + } +} + +// Find pre/code blocks near buttons +function findCodeBlocks(node, depth, results) { + if (!node || depth > 25) return; + const tag = (node.tag || node.tagName || '').toLowerCase(); + const cls = node.cls || node.className || ''; + if ((tag === 'pre' || tag === 'code') && cls.includes('font-mono')) { + const text = (node.text || node.textContent || '').substring(0, 80); + results.push({ tag, cls: cls.substring(0, 50), text, depth }); + } + if (node.children) { + for (const c of node.children) findCodeBlocks(c, depth + 1, results); + } +} + +for (const df of dumpFiles) { + const fp = path.join(bridgePath, df); + if (!fs.existsSync(fp)) continue; + try { + const dump = JSON.parse(fs.readFileSync(fp, 'utf8')); + const root = dump.bodyTree || dump.body || dump; + console.log('\n=== ' + df + ' ==='); + + // Find conversation area + const convo = findConvo(root, 0); + if (convo) { + console.log('>> Conversation area found, tree (depth 12):'); + printTree(convo, 0, 12); + } + + // Find buttons + const btns = []; + findButtons(root, 0, btns); + if (btns.length > 0) { + console.log('>> Buttons found:'); + for (const b of btns) console.log(' d' + b.depth + ': ' + b.text); + } + + // Find code blocks + const codes = []; + findCodeBlocks(root, 0, codes); + if (codes.length > 0) { + console.log('>> Code blocks (font-mono):'); + for (const c of codes) console.log(' d' + c.depth + ': <' + c.tag + '> cls=' + c.cls + ' text="' + c.text + '"'); + } + } catch (e) { + console.log('ERROR reading ' + df + ': ' + e.message); + } +} diff --git a/extension/scratch/print_dom_tree.js b/extension/scratch/print_dom_tree.js new file mode 100644 index 0000000..7ca17af --- /dev/null +++ b/extension/scratch/print_dom_tree.js @@ -0,0 +1,26 @@ +const fs = require('fs'); +const path = require('path'); + +const dump = JSON.parse(fs.readFileSync( + path.join(process.env.USERPROFILE, '.gemini/antigravity/bridge/deep-inspect-result.json'), 'utf8' +)); + +function printTree(node, indent, maxDepth) { + if (!node || indent > maxDepth) return; + const tag = (node.tag || '?').toLowerCase(); + const clsArr = (node.cls || '').split(' ').filter(c => c.length > 0); + const clsShort = clsArr.slice(0, 3).join(' '); + const text = (node.text || '').substring(0, 40).replace(/[\n\r]+/g, ' '); + const childCount = (node.children || []).length; + let line = ' '.repeat(indent) + tag; + if (clsShort) line += '.' + clsArr[0]; + if (childCount) line += ' [' + childCount + ' children]'; + if (text && childCount === 0) line += ' = "' + text + '"'; + console.log(line); + if (node.children) { + for (const c of node.children) printTree(c, indent + 1, maxDepth); + } +} + +console.log('=== FULL DOM TREE (depth 8) ==='); +printTree(dump.bodyTree || dump.body, 0, 8); diff --git a/extension/src/http-bridge.ts b/extension/src/http-bridge.ts index 676b540..c400add 100644 --- a/extension/src/http-bridge.ts +++ b/extension/src/http-bridge.ts @@ -345,7 +345,7 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) { } const rid = data.request_id || Date.now().toString(); ctx.logToFile(`[HTTP] AUTO-APPROVE "Always run" (btnIdx=${alwaysRunBtnIndex}): cmd="${displayCmd.substring(0, 80)}"`); - // Write response file so observer's pollResponseGroup picks it up and clicks the button + // Write response file IMMEDIATELY so observer clicks the button with zero delay const responseDir = path.join(ctx.bridgePath, 'response'); if (!fs.existsSync(responseDir)) { fs.mkdirSync(responseDir, { recursive: true }); @@ -362,20 +362,48 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) { JSON.stringify(respPayload), 'utf-8' ); - // Notify Discord (non-interactive "자동 승인" embed) - if (ctx.wsBridge && ctx.wsBridge.isConnected()) { - ctx.wsBridge.sendPending({ - request_id: rid, - command: displayCmd, - description: rawDesc ? `[${rawCmd}] ${rawDesc}` : rawCmd, - step_type: data.step_type || 'command', - status: 'auto_approved', - buttons: data.buttons, - project_name: ctx.projectName, - }); - } + // v29: Respond to Observer immediately (don't block button click) res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: true, request_id: rid, auto_approved: true })); + + // v29: Discord notification — if displayCmd is generic, poll Step Probe for real command + const isGenericDisplay = GENERIC_BTN_RE.test(displayCmd); + const sendDiscord = (finalCmd: string, finalDesc: string) => { + if (ctx.wsBridge && ctx.wsBridge.isConnected()) { + ctx.wsBridge.sendPending({ + request_id: rid, + command: finalCmd, + description: finalDesc, + step_type: data.step_type || 'command', + status: 'auto_approved', + buttons: data.buttons, + project_name: ctx.projectName, + }); + } + }; + + if (isGenericDisplay && ctx.getLastWaitingCommand) { + // Poll Step Probe memory for up to 6s (API polls every 5s) + let pollAttempt = 0; + const maxAttempts = 30; // 30 * 200ms = 6s + const pollTimer = setInterval(() => { + pollAttempt++; + const wc = ctx.getLastWaitingCommand!(); + if (wc.cmd && wc.cmd.length > 3 && !GENERIC_BTN_RE.test(wc.cmd) && (Date.now() - wc.ts) < 15_000) { + clearInterval(pollTimer); + const enrichedCmd = wc.desc && wc.desc.length > wc.cmd.length ? wc.desc.substring(0, 200) : wc.cmd.substring(0, 200); + ctx.logToFile(`[HTTP] AUTO-APPROVE enriched (delayed ${pollAttempt * 200}ms): "${enrichedCmd.substring(0, 80)}"`); + sendDiscord(enrichedCmd, `[${rawCmd}] ${enrichedCmd}`); + } else if (pollAttempt >= maxAttempts) { + clearInterval(pollTimer); + ctx.logToFile(`[HTTP] AUTO-APPROVE no enrichment after ${maxAttempts * 200}ms — sending generic`); + sendDiscord(displayCmd, rawDesc ? `[${rawCmd}] ${rawDesc}` : rawCmd); + } + }, 200); + } else { + // Already enriched or no Step Probe — send immediately + sendDiscord(displayCmd, rawDesc ? `[${rawCmd}] ${rawDesc}` : rawCmd); + } return; } if (GENERIC_BTN_RE.test(rawCmd) && rawDesc.length > 10 && rawDesc !== rawCmd) {