Fix DOM observer regex/container bugs and add continuous AI Chat Body scraper via HTTP Bridge
This commit is contained in:
@@ -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