feat(enrichment): Discord 알림 지연 + Step Probe 폴링 — generic Always run 커맨드 100% 보강 (v0.5.91)
This commit is contained in:
4
extension/package-lock.json
generated
4
extension/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "gravity-bridge",
|
"name": "gravity-bridge",
|
||||||
"version": "0.5.90",
|
"version": "0.5.91",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "gravity-bridge",
|
"name": "gravity-bridge",
|
||||||
"version": "0.5.90",
|
"version": "0.5.91",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cheerio": "^1.2.0",
|
"cheerio": "^1.2.0",
|
||||||
"ws": "^8.19.0"
|
"ws": "^8.19.0"
|
||||||
|
|||||||
@@ -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.90",
|
"version": "0.5.91",
|
||||||
"publisher": "variet",
|
"publisher": "variet",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.100.0"
|
"vscode": "^1.100.0"
|
||||||
|
|||||||
98
extension/scratch/analyze_all_dumps.js
Normal file
98
extension/scratch/analyze_all_dumps.js
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
extension/scratch/print_dom_tree.js
Normal file
26
extension/scratch/print_dom_tree.js
Normal file
@@ -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);
|
||||||
@@ -345,7 +345,7 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
|
|||||||
}
|
}
|
||||||
const rid = data.request_id || Date.now().toString();
|
const rid = data.request_id || Date.now().toString();
|
||||||
ctx.logToFile(`[HTTP] AUTO-APPROVE "Always run" (btnIdx=${alwaysRunBtnIndex}): cmd="${displayCmd.substring(0, 80)}"`);
|
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');
|
const responseDir = path.join(ctx.bridgePath, 'response');
|
||||||
if (!fs.existsSync(responseDir)) {
|
if (!fs.existsSync(responseDir)) {
|
||||||
fs.mkdirSync(responseDir, { recursive: true });
|
fs.mkdirSync(responseDir, { recursive: true });
|
||||||
@@ -362,20 +362,48 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
|
|||||||
JSON.stringify(respPayload),
|
JSON.stringify(respPayload),
|
||||||
'utf-8'
|
'utf-8'
|
||||||
);
|
);
|
||||||
// Notify Discord (non-interactive "자동 승인" embed)
|
// 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()) {
|
if (ctx.wsBridge && ctx.wsBridge.isConnected()) {
|
||||||
ctx.wsBridge.sendPending({
|
ctx.wsBridge.sendPending({
|
||||||
request_id: rid,
|
request_id: rid,
|
||||||
command: displayCmd,
|
command: finalCmd,
|
||||||
description: rawDesc ? `[${rawCmd}] ${rawDesc}` : rawCmd,
|
description: finalDesc,
|
||||||
step_type: data.step_type || 'command',
|
step_type: data.step_type || 'command',
|
||||||
status: 'auto_approved',
|
status: 'auto_approved',
|
||||||
buttons: data.buttons,
|
buttons: data.buttons,
|
||||||
project_name: ctx.projectName,
|
project_name: ctx.projectName,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
};
|
||||||
res.end(JSON.stringify({ ok: true, request_id: rid, auto_approved: true }));
|
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (GENERIC_BTN_RE.test(rawCmd) && rawDesc.length > 10 && rawDesc !== rawCmd) {
|
if (GENERIC_BTN_RE.test(rawCmd) && rawDesc.length > 10 && rawDesc !== rawCmd) {
|
||||||
|
|||||||
Reference in New Issue
Block a user