From 5e697cd91947c94f1bced1fdfb60f6e87c51b538 Mon Sep 17 00:00:00 2001 From: Variet Worker Date: Fri, 10 Apr 2026 23:52:56 +0900 Subject: [PATCH] Fix DOM observer regex/container bugs and add continuous AI Chat Body scraper via HTTP Bridge --- extension/package.json | 176 +++++++++++++++---------------- extension/src/http-bridge.ts | 28 ++++- extension/src/observer-script.ts | 110 ++++++++++++++++--- 3 files changed, 211 insertions(+), 103 deletions(-) diff --git a/extension/package.json b/extension/package.json index 7b216c3..886a260 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,89 +1,89 @@ { - "name": "gravity-bridge", - "displayName": "Gravity Bridge", - "description": "Discord-based unified approval system for Antigravity AI interactions.", - "version": "0.5.28", - "publisher": "variet", - "engines": { - "vscode": "^1.100.0" - }, - "categories": [ - "Other", - "Chat" - ], - "activationEvents": [ - "onStartupFinished" - ], - "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)};\"", - "watch": "tsc -watch -p ./" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "@types/vscode": "^1.100.0", - "typescript": "^5.3.0" - }, - "contributes": { - "chatParticipants": [ - { - "id": "gravity-bridge.gravity", - "name": "gravity", - "fullName": "Gravity Bridge", - "description": "????스?리?Discord??송 + AI ?어", - "isSticky": false - } - ], - "commands": [ - { - "command": "gravityBridge.start", - "title": "Gravity Bridge: Start" - }, - { - "command": "gravityBridge.stop", - "title": "Gravity Bridge: Stop" - }, - { - "command": "gravityBridge.approve", - "title": "Gravity Bridge: Approve Pending" - }, - { - "command": "gravityBridge.reject", - "title": "Gravity Bridge: Reject Pending" - }, - { - "command": "gravityBridge.connect", - "title": "Gravity Bridge: Connect Session" - } - ], - "configuration": { - "title": "Gravity Bridge", - "properties": { - "gravityBridge.bridgePath": { - "type": "string", - "default": "", - "description": "Bridge ?렉?리 경로 (기본: ~/.gemini/antigravity/bridge)" - }, - "gravityBridge.projectName": { - "type": "string", - "default": "", - "description": "?로?트 ?름 (기본: git remote ?포?" - }, - "gravityBridge.hubUrl": { - "type": "string", - "default": "", - "description": "WebSocket Hub URL (?? wss://your-server.com/ws)" - }, - "gravityBridge.registrationCode": { - "type": "string", - "default": "", - "description": "Hub ?록 코드 (?버?서 발급)" - } - } - } - }, - "dependencies": { - "ws": "^8.19.0" - } -} + "name": "gravity-bridge", + "displayName": "Gravity Bridge", + "description": "Discord-based unified approval system for Antigravity AI interactions.", + "version": "0.5.30", + "publisher": "variet", + "engines": { + "vscode": "^1.100.0" + }, + "categories": [ + "Other", + "Chat" + ], + "activationEvents": [ + "onStartupFinished" + ], + "main": "./out/extension.js", + "scripts": { + "vscode:prepublish": "npm run compile", + "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": { + "@types/node": "^20.0.0", + "@types/vscode": "^1.100.0", + "typescript": "^5.3.0" + }, + "contributes": { + "chatParticipants": [ + { + "id": "gravity-bridge.gravity", + "name": "gravity", + "fullName": "Gravity Bridge", + "description": "?�???�스?�리�?Discord�??�송 + AI ?�어", + "isSticky": false + } + ], + "commands": [ + { + "command": "gravityBridge.start", + "title": "Gravity Bridge: Start" + }, + { + "command": "gravityBridge.stop", + "title": "Gravity Bridge: Stop" + }, + { + "command": "gravityBridge.approve", + "title": "Gravity Bridge: Approve Pending" + }, + { + "command": "gravityBridge.reject", + "title": "Gravity Bridge: Reject Pending" + }, + { + "command": "gravityBridge.connect", + "title": "Gravity Bridge: Connect Session" + } + ], + "configuration": { + "title": "Gravity Bridge", + "properties": { + "gravityBridge.bridgePath": { + "type": "string", + "default": "", + "description": "Bridge ?�렉?�리 경로 (기본: ~/.gemini/antigravity/bridge)" + }, + "gravityBridge.projectName": { + "type": "string", + "default": "", + "description": "?�로?�트 ?�름 (기본: git remote ?�포�?" + }, + "gravityBridge.hubUrl": { + "type": "string", + "default": "", + "description": "WebSocket Hub URL (?? wss://your-server.com/ws)" + }, + "gravityBridge.registrationCode": { + "type": "string", + "default": "", + "description": "Hub ?�록 코드 (?�버?�서 발급)" + } + } + } + }, + "dependencies": { + "ws": "^8.19.0" + } +} \ No newline at end of file diff --git a/extension/src/http-bridge.ts b/extension/src/http-bridge.ts index 2f44aea..1800ac4 100644 --- a/extension/src/http-bridge.ts +++ b/extension/src/http-bridge.ts @@ -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 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 })); + } + }); +} diff --git a/extension/src/observer-script.ts b/extension/src/observer-script.ts index 96df5e6..8e63d72 100644 --- a/extension/src/observer-script.ts +++ b/extension/src/observer-script.ts @@ -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 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');