Fix DOM observer regex/container bugs and add continuous AI Chat Body scraper via HTTP Bridge

This commit is contained in:
Variet Worker
2026-04-10 23:52:56 +09:00
parent b3825e1c8a
commit 5e697cd919
3 changed files with 211 additions and 103 deletions

View File

@@ -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');