Fix DOM observer regex/container bugs and add continuous AI Chat Body scraper via HTTP Bridge
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
"name": "gravity-bridge",
|
||||
"displayName": "Gravity Bridge",
|
||||
"description": "Discord-based unified approval system for Antigravity AI interactions.",
|
||||
"version": "0.5.28",
|
||||
"version": "0.5.30",
|
||||
"publisher": "variet",
|
||||
"engines": {
|
||||
"vscode": "^1.100.0"
|
||||
@@ -17,7 +17,7 @@
|
||||
"main": "./out/extension.js",
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run compile",
|
||||
"compile": "tsc -p ./ \u0026\u0026 node -e \"const fs=require(\u0027fs\u0027),p=require(\u0027path\u0027);const s=p.join(\u0027src\u0027,\u0027sdk\u0027),d=p.join(\u0027out\u0027,\u0027sdk\u0027);if(fs.existsSync(s)){fs.mkdirSync(d,{recursive:true});fs.readdirSync(s).forEach(f=\u003efs.copyFileSync(p.join(s,f),p.join(d,f)));console.log(\u0027SDK copied to out/sdk\u0027)};\"",
|
||||
"compile": "tsc -p ./ && node -e \"const fs=require('fs'),p=require('path');const s=p.join('src','sdk'),d=p.join('out','sdk');if(fs.existsSync(s)){fs.mkdirSync(d,{recursive:true});fs.readdirSync(s).forEach(f=>fs.copyFileSync(p.join(s,f),p.join(d,f)));console.log('SDK copied to out/sdk')};\"",
|
||||
"watch": "tsc -watch -p ./"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -31,7 +31,7 @@
|
||||
"id": "gravity-bridge.gravity",
|
||||
"name": "gravity",
|
||||
"fullName": "Gravity Bridge",
|
||||
"description": "?€???ˆìФ? 리ë¥?Discordë¡??„송 + AI ?œì–´",
|
||||
"description": "?<EFBFBD>???<EFBFBD>스?<3F>리<EFBFBD>?Discord<72>??<3F>송 + AI ?<EFBFBD>어",
|
||||
"isSticky": false
|
||||
}
|
||||
],
|
||||
@@ -63,12 +63,12 @@
|
||||
"gravityBridge.bridgePath": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Bridge ?”ë ‰? 리 경로 (기본: ~/.gemini/antigravity/bridge)"
|
||||
"description": "Bridge ?<EFBFBD>렉?<3F>리 경로 (기본: ~/.gemini/antigravity/bridge)"
|
||||
},
|
||||
"gravityBridge.projectName": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "?„로?<3F>트 ?´ë¦„ (기본: git remote ?ˆí<EFBFBD>¬ëª?"
|
||||
"description": "?<EFBFBD>로?<3F>트 ?<3F>름 (기본: git remote ?<EFBFBD>포<EFBFBD>?"
|
||||
},
|
||||
"gravityBridge.hubUrl": {
|
||||
"type": "string",
|
||||
@@ -78,7 +78,7 @@
|
||||
"gravityBridge.registrationCode": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Hub ?±ë¡<EFBFBD> 코드 (?œë²„?<3F>서 발급)"
|
||||
"description": "Hub ?<EFBFBD>록 코드 (?<3F>버?<3F>서 발급)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface HttpBridgeContext {
|
||||
sessionStalled: boolean;
|
||||
lastPendingStepIndex: number;
|
||||
logToFile: (msg: string) => void;
|
||||
writeChatSnapshot?: (text: string) => void;
|
||||
}
|
||||
|
||||
// ─── Module-level state ───
|
||||
@@ -106,6 +107,12 @@ export function startHttpBridge(ctx: HttpBridgeContext, sdk: any): Promise<numbe
|
||||
return;
|
||||
}
|
||||
|
||||
// POST /chat — renderer posts chat snapshots directly
|
||||
if (req.method === 'POST' && url.pathname === '/chat') {
|
||||
_handleChatSnapshot(req, res, ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
// POST /deep-inspect-result — renderer posts inspection results here
|
||||
if (req.method === 'POST' && url.pathname === '/deep-inspect-result') {
|
||||
_handleDeepInspectResult(req, res, ctx);
|
||||
@@ -381,10 +388,8 @@ function _handleDeepInspectResult(req: any, res: any, ctx: HttpBridgeContext) {
|
||||
const data = JSON.parse(body);
|
||||
deepInspectResult = data;
|
||||
ctx.logToFile(`[HTTP] deep-inspect result received (${body.length} bytes)`);
|
||||
// Write to file for reference
|
||||
const inspectFile = path.join(ctx.bridgePath, 'deep-inspect-result.json');
|
||||
fs.writeFileSync(inspectFile, JSON.stringify(data, null, 2));
|
||||
// Notify waiters
|
||||
const waiters = [...deepInspectWaiters];
|
||||
deepInspectWaiters = [];
|
||||
waiters.forEach(w => w(data));
|
||||
@@ -396,3 +401,22 @@ function _handleDeepInspectResult(req: any, res: any, ctx: HttpBridgeContext) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _handleChatSnapshot(req: any, res: any, ctx: HttpBridgeContext) {
|
||||
let body = '';
|
||||
req.on('data', (c: string) => body += c);
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
if (data.text && typeof ctx.writeChatSnapshot === 'function') {
|
||||
ctx.writeChatSnapshot(`💬 **[DOM 추출] AI 응답**\n\n${data.text}`);
|
||||
ctx.logToFile(`[HTTP] chat snapshot written (${data.text.length} chars)`);
|
||||
}
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ ok: true }));
|
||||
} catch (e: any) {
|
||||
ctx.logToFile(`[HTTP] chat parse error: ${e.message}`);
|
||||
res.writeHead(400); res.end(JSON.stringify({ error: e.message }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,10 +36,14 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
|
||||
function cleanButtonText(btn) {
|
||||
if (!btn) return '';
|
||||
// if internal truncate span, use it
|
||||
var tr = btn.querySelector('.truncate');
|
||||
var txt = (tr ? tr.textContent : btn.textContent) || '';
|
||||
return txt.trim().replace(/(Alt|Ctrl|Shift|Meta)\\\\+.*/i,'').trim();
|
||||
var clone = btn.cloneNode(true);
|
||||
var icons = clone.querySelectorAll('.google-symbols, .codicon');
|
||||
for(var i=0; i<icons.length; i++) {
|
||||
if(icons[i].parentNode) icons[i].parentNode.removeChild(icons[i]);
|
||||
}
|
||||
var tr = clone.querySelector('.truncate');
|
||||
var txt = (tr ? tr.textContent : clone.textContent) || '';
|
||||
return txt.trim().replace(/^[\s\u200B-\u200D\uFEFF\u00A0]+/, '').replace(/(Alt|Ctrl|Shift|Meta)\\+.*/i,'').trim();
|
||||
}
|
||||
|
||||
// ── Stable button fingerprint ──
|
||||
@@ -123,6 +127,35 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
}
|
||||
}
|
||||
|
||||
function extractChatContextFromNode(botTurn) {
|
||||
if (!botTurn) return '';
|
||||
var toolContainer = botTurn.querySelector('.bg-ide-background-color'); // Stop at tool blocks
|
||||
var textParts = [];
|
||||
function walk(node) {
|
||||
if (toolContainer && node === toolContainer) return true;
|
||||
if (node.nodeType === 1) {
|
||||
var tag = node.tagName.toUpperCase();
|
||||
if (tag==='BUTTON' || tag==='SVG' || tag==='STYLE' || tag==='SCRIPT') return false;
|
||||
}
|
||||
if (node.nodeType === 3) {
|
||||
var val = node.nodeValue;
|
||||
if (val && val.trim()) textParts.push(val.trim());
|
||||
} else {
|
||||
for(var i=0; i<node.childNodes.length; i++) {
|
||||
if (walk(node.childNodes[i])) return true;
|
||||
}
|
||||
}
|
||||
if (node.nodeType === 1) {
|
||||
var tg = node.tagName.toUpperCase();
|
||||
if (tg==='P' || tg==='DIV' || tg==='BR' || tg==='LI' || tg==='PRE') textParts.push('\\n');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
walk(botTurn);
|
||||
var result = textParts.join(' ').replace(/ \\n /g, '\\n').replace(/\\n+/g, '\\n').trim();
|
||||
return result.substring(0, 3500);
|
||||
}
|
||||
|
||||
function extractContext(b) {
|
||||
var cmd = extractCommandContext(b);
|
||||
var chat = extractChatContext(b);
|
||||
@@ -133,15 +166,15 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
return combined.trim();
|
||||
}
|
||||
|
||||
// ── Action Buttons Patterns ──
|
||||
// ── Action Buttons Patterns (EN / KO) ──
|
||||
var PATS = [
|
||||
{ type: 'command', re: /^(?:Always\\s*)?Run\\b/i },
|
||||
{ type: 'permission', re: /^(?:Always\\s*)?Allow\\b/i },
|
||||
{ type: 'permission', re: /^(?:Always\\s*)?Approve\\b/i },
|
||||
{ type: 'diff_review', re: /^(?:Always\\s*)?Accept\\b/i },
|
||||
{ type: 'command', re: /^(?:Always\\s*)?(?:Run\\b|결행사양\\s*항상|결행)/i },
|
||||
{ type: 'permission', re: /^(?:Always\\s*)?(?:Allow\\b|허용)/i },
|
||||
{ type: 'permission', re: /^(?:Always\\s*)?(?:Approve\\b|승인)/i },
|
||||
{ type: 'diff_review', re: /^(?:Always\\s*)?(?:Accept\\b|수락|반영)/i },
|
||||
];
|
||||
var ALL_ACTION_RE=[/^(?:Always\\s*)?Run\\b/i,/^(?:Always\\s*)?Accept\\b/i,/^Reject\\b/i,/^(?:Always\\s*)?Allow\\b/i,/^Deny\\b/i,/^(?:Always\\s*)?Approve\\b/i,/^Cancel\\b/i,/^Retry\\b/i,/^Dismiss\\b/i,/^Stop\\b/i,/^Decline\\b/i];
|
||||
var REJECT_RE=[/^Reject\\b/i,/^Cancel\\b/i,/^Deny\\b/i,/^Stop\\b/i,/^Decline\\b/i,/^Dismiss\\b/i];
|
||||
var ALL_ACTION_RE=[/^(?:Always\\s*)?(?:Run\\b|결행)/i,/^(?:Always\\s*)?(?:Accept\\b|수락|반영)/i,/^(?:Reject\\b|거절|거부)/i,/^(?:Always\\s*)?(?:Allow\\b|허용)/i,/^(?:Deny\\b|차단)/i,/^(?:Always\\s*)?(?:Approve\\b|승인)/i,/^(?:Cancel\\b|취소)/i,/^Retry\\b/i,/^(?:Dismiss\\b|무시)/i,/^(?:Stop\\b|정지)/i,/^Decline\\b/i];
|
||||
var REJECT_RE=[/^(?:Reject\\b|거절|거부)/i,/^(?:Cancel\\b|취소)/i,/^(?:Deny\\b|차단)/i,/^(?:Stop\\b|정지)/i,/^Decline\\b/i,/^(?:Dismiss\\b|무시)/i];
|
||||
|
||||
function collectSiblingButtons(container,triggerBtn){
|
||||
if(!container)return [];
|
||||
@@ -206,8 +239,59 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
}).catch(function(e){});
|
||||
});
|
||||
|
||||
var _chatSnapshots = [];
|
||||
var _firstChatScan = true;
|
||||
function scanChatBodies() {
|
||||
if(!_ready)return;
|
||||
var botTurns = document.querySelectorAll('.text-ide-message-block-bot-color');
|
||||
for (var i = 0; i < botTurns.length; i++) {
|
||||
var turn = botTurns[i];
|
||||
if (turn.dataset.agChatScraped === "true" || turn.dataset.agChatScraped === "pending") continue;
|
||||
|
||||
if (_firstChatScan) {
|
||||
turn.dataset.agChatScraped = "true";
|
||||
continue;
|
||||
}
|
||||
|
||||
var currentText = turn.textContent || '';
|
||||
var found = -1;
|
||||
for (var j = 0; j < _chatSnapshots.length; j++) {
|
||||
if (_chatSnapshots[j].node === turn) { found = j; break; }
|
||||
}
|
||||
|
||||
if (found === -1) {
|
||||
_chatSnapshots.push({ node: turn, text: currentText, lastChanged: Date.now() });
|
||||
} else {
|
||||
if (_chatSnapshots[found].text !== currentText) {
|
||||
_chatSnapshots[found].text = currentText;
|
||||
_chatSnapshots[found].lastChanged = Date.now();
|
||||
} else {
|
||||
if (Date.now() - _chatSnapshots[found].lastChanged > 3500) {
|
||||
turn.dataset.agChatScraped = "pending"; // prevent re-entry
|
||||
var finalTxt = extractChatContextFromNode(turn);
|
||||
if (finalTxt && finalTxt.length > 5) {
|
||||
fetch(BASE+'/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: finalTxt })
|
||||
}).then(function(){
|
||||
turn.dataset.agChatScraped = "true";
|
||||
}).catch(function(){
|
||||
turn.dataset.agChatScraped = "false"; // retry
|
||||
});
|
||||
} else {
|
||||
turn.dataset.agChatScraped = "true";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_firstChatScan = false;
|
||||
}
|
||||
|
||||
function scan(){
|
||||
if(!_ready)return;
|
||||
scanChatBodies();
|
||||
var now=Date.now();
|
||||
var allBtns=document.querySelectorAll('button');
|
||||
if(!allBtns.length)return;
|
||||
@@ -392,8 +476,8 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
if(_ready&&BASE){
|
||||
fetch(BASE+'/trigger-click?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
|
||||
if(!d.action)return;
|
||||
var approveRe=[/^(?:Always\s*)?Run\b/i,/^(?:Always\s*)?Accept\b/i,/^(?:Always\s*)?Accept all\b/i,/^(?:Always\s*)?Allow\b/i,/^(?:Always\s*)?Approve\b/i];
|
||||
var rejectRe=[/^Reject\b/i,/^Cancel\b/i,/^Deny\b/i,/^Stop\b/i,/^Decline\b/i,/^Dismiss\b/i];
|
||||
var approveRe=[/^(?:Always\\\\s*)?(?:Run\\\\b|결행)/i,/^(?:Always\\\\s*)?(?:Accept\\\\b|수락)/i,/^(?:Always\\\\s*)?(?:Accept all\\\\b|모두 수락)/i,/^(?:Always\\\\s*)?(?:Allow\\\\b|허용)/i,/^(?:Always\\\\s*)?(?:Approve\\\\b|승인)/i];
|
||||
var rejectRe=[/^(?:Reject\\\\b|거절|거부)/i,/^(?:Cancel\\\\b|취소)/i,/^(?:Deny\\\\b|차단)/i,/^(?:Stop\\\\b|정지)/i,/^Decline\\\\b/i,/^(?:Dismiss\\\\b|무시)/i];
|
||||
var patterns=(d.action==='approve')?approveRe:rejectRe;
|
||||
|
||||
var btns = document.querySelectorAll('button');
|
||||
|
||||
Reference in New Issue
Block a user