fix(extension): Retry auto-approve 흐름 복구 + Observer 형제 탐색 + thinking 필터링 (v0.5.79)

- WS response 파일에 _from_ws 마커 추가하여 processResponseFile 삭제 방지
- extractContextFromNearby에 sibling 탐색 추가 (AG Native DOM 구조 대응)
- thinking 블록 (max-h-[200px]) 필터링으로 내부 사고 릴레이 차단
- DOM 탐색 depth 5→10 확대 + pre.font-mono 우선 탐색
- 사용자 메시지 셀렉터 (.select-text.rounded-lg) 추가
This commit is contained in:
Variet Worker
2026-04-19 03:46:39 +09:00
parent 08fd08b9a6
commit 139ad3ee93
17 changed files with 704 additions and 38 deletions

View File

@@ -12,7 +12,7 @@ export function generateApprovalObserverScript(_port: number): string {
function log(m){
console.log('[GB Observer] '+m);
// v19: Relay important logs to extension via HTTP so they appear in extension.log
if (BASE && (m.indexOf('CV-CLASSES')!==-1 || m.indexOf('Conversation view')!==-1 || m.indexOf('BEACON')!==-1 || m.indexOf('ERROR')!==-1 || m.indexOf('chat relay')!==-1 || m.indexOf('user-cls')!==-1)) {
if (BASE && (m.indexOf('CV-CLASSES')!==-1 || m.indexOf('CV-CHILDREN')!==-1 || m.indexOf('child[')!==-1 || m.indexOf('CV found')!==-1 || m.indexOf('Conversation view')!==-1 || m.indexOf('BEACON')!==-1 || m.indexOf('ERROR')!==-1 || m.indexOf('chat relay')!==-1 || m.indexOf('user-cls')!==-1)) {
try { fetch(BASE+'/log', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({msg:m.substring(0,2000)})}); } catch(e){}
}
}
@@ -144,9 +144,11 @@ export function generateApprovalObserverScript(_port: number): string {
var _bestCodeHeader = '';
var _sawCodeEls = false;
var _allSkipped = true;
for (var depth = 0; depth < 5 && node; depth++) {
// v22: Increased from 5 to 10 — AG Native command display (SRi) can be many levels up
for (var depth = 0; depth < 10 && node; depth++) {
if (!node.querySelector) { _debugTrail.push('d'+depth+':noQS'); node = node.parentElement; continue; }
var codeEls = node.querySelectorAll('pre, code, [class*="terminal"]');
// v22: Prioritize pre.font-mono (AG Native command line display from SRi component)
var codeEls = node.querySelectorAll('pre.font-mono, pre, code, [class*="terminal"]');
_debugTrail.push('d'+depth+':tag='+((node.tagName||'?').toLowerCase())+',cls='+(((typeof node.className==='string')?node.className:'').substring(0,60))+',codeEls='+codeEls.length);
for (var ci = 0; ci < codeEls.length; ci++) {
var codeText = cleanLines((codeEls[ci].textContent || '').trim().substring(0, 500));
@@ -187,6 +189,33 @@ export function generateApprovalObserverScript(_port: number): string {
_lastContextDebug = _debugTrail.join(' > ');
return parts.join(' \u2014 ');
}
// v23: Also search sibling elements at each level
// AG Native's command display (pre.font-mono) is a SIBLING of footer, not ancestor
if (node && node.parentElement) {
var siblings = node.parentElement.children;
for (var si = 0; si < siblings.length; si++) {
if (siblings[si] === node) continue;
if (!siblings[si].querySelector) continue;
var sibCodeEls = siblings[si].querySelectorAll('pre.font-mono, pre, code');
for (var sci = 0; sci < sibCodeEls.length; sci++) {
var sibCode = cleanLines((sibCodeEls[sci].textContent || '').trim().substring(0, 500));
if (!sibCode || sibCode.length <= 5) continue;
if (JUNK_CODE_RE.test(sibCode) || ICON_GLUE_RE.test(sibCode)) continue;
if (PROMPT_ONLY_RE.test(sibCode.trim())) continue;
_debugTrail.push('sibling_d'+depth+':tag='+siblings[si].tagName.toLowerCase()+',code='+sibCode.substring(0,40));
_bestCodeText = sibCode;
_allSkipped = false;
// Found in sibling — return immediately
var sibParts = [];
var sibHdr = siblings[si].querySelector('h1, h2, h3, [class*="header"], [class*="title"], [class*="cursor-pointer"]');
if (sibHdr) sibParts.push(cleanLines((sibHdr.textContent || '').trim().substring(0, 200)));
sibParts.push(sibCode);
log('CONTEXT-OK d='+depth+' src=sibling trail='+_debugTrail.join(' > '));
_lastContextDebug = _debugTrail.join(' > ');
return sibParts.join(' \u2014 ');
}
}
}
node = node.parentElement;
}
if (_sawCodeEls && _allSkipped) {
@@ -345,7 +374,7 @@ export function generateApprovalObserverScript(_port: number): string {
var _dumpCount=0;
var MAX_DUMPS=8;
var _conversationDumped=false;
var _conversationDumpCount=0;
function walkNode(el, depth, maxDepth, maxChildren) {
if (depth > maxDepth) return {tag:'…',text:'depth limit'};
@@ -725,12 +754,28 @@ export function generateApprovalObserverScript(_port: number): string {
if (!cv) {
cv = document.querySelector('.antigravity-agent-side-panel');
}
// v19: Fallback — find conversation by tracing from known content elements
if (!cv) {
var probe = document.querySelector('.leading-relaxed.select-text') || document.querySelector('.text-ide-message-block-bot-color');
if (probe) {
// Walk up to find a reasonable container (has overflow-y or is big enough)
var p = probe.parentElement;
for (var pi2 = 0; pi2 < 10 && p && p !== document.body; pi2++) {
var pCls = (typeof p.className === 'string') ? p.className : '';
if (pCls.indexOf('overflow') !== -1 || p.children.length > 3) {
cv = p;
break;
}
p = p.parentElement;
}
if (!cv && probe.parentElement) cv = probe.parentElement.parentElement || probe.parentElement;
}
}
if (cv) {
// v19: Trigger DOM dump when conversation view is first found (prev dumps may have captured Settings tab)
if (!_conversationDumped) {
_conversationDumped = true;
log('Conversation view found — triggering chat-context DOM dump');
// v20: Dump CV structure for first 3 scans to ensure we capture it (even with stale HTML cache)
if (_conversationDumpCount < 3) {
_conversationDumpCount++;
log('CV found via: ' + (cv.id || (typeof cv.className === 'string' ? cv.className : cv.tagName) || 'unknown').substring(0, 100));
// Log all unique class names under #conversation for selector discovery
var allCvEls = cv.querySelectorAll('*');
var clsSet = {};
@@ -745,26 +790,67 @@ export function generateApprovalObserverScript(_port: number): string {
}
var clsList = Object.keys(clsSet).sort().join(', ');
log('CV-CLASSES (' + Object.keys(clsSet).length + '): ' + clsList.substring(0, 1500));
// v19: Log direct children to discover message block structure
var cvKids = cv.children;
log('CV-CHILDREN (' + cvKids.length + '):');
for (var ck = 0; ck < Math.min(cvKids.length, 15); ck++) {
var kid = cvKids[ck];
var kidCls = (typeof kid.className === 'string') ? kid.className : '';
var kidText = (kid.textContent || '').trim().substring(0, 60);
log(' child[' + ck + '] tag=' + kid.tagName + ' cls=' + kidCls.substring(0, 120) + ' text=' + kidText);
}
// v22: Deep-dive into gap-8 container to find individual message blocks
var msgContainer = cv.querySelector('.gap-8') || cv.children[0];
if (msgContainer) {
var msgKids = msgContainer.children;
log('MSG-BLOCKS (' + msgKids.length + '):');
for (var mk = 0; mk < Math.min(msgKids.length, 30); mk++) {
var mb = msgKids[mk];
var mbCls = (typeof mb.className === 'string') ? mb.className : '';
var mbText = (mb.textContent || '').trim().substring(0, 80);
var hasLeadingRelaxed = mb.querySelector('.leading-relaxed.select-text') ? 'Y' : 'N';
var firstChildCls = (mb.children[0] && typeof mb.children[0].className === 'string') ? mb.children[0].className : '';
log(' msg[' + mk + '] cls=' + mbCls.substring(0, 120) + ' lr=' + hasLeadingRelaxed + ' fc=' + firstChildCls.substring(0, 80) + ' text=' + mbText.substring(0, 60));
}
}
// Force a dump with conversation context
dumpDOMStructure();
}
// AG Native path: find AI and User response blocks by class pattern
// v19: Also look for common AG Native message container patterns
var responseBlocks = cv.querySelectorAll('.leading-relaxed.select-text, .text-ide-message-block-user-color, .text-ide-message-block-bot-color, .bg-ide-message-block-user-background, [data-message-role="user"], [data-role="user"]');
// v22: AI response = .leading-relaxed.select-text, User message = .select-text.rounded-lg (Esn component, msn class)
// Source: jetskiAgent/main.js — msn="bg-gray-500/10 border border-gray-500/20 p-2 rounded-lg w-full text-sm select-text"
var responseBlocks = cv.querySelectorAll('.leading-relaxed.select-text, .select-text.rounded-lg');
if (responseBlocks.length > 0) {
// Process the LAST (most recent) response block
var lastBlock = responseBlocks[responseBlocks.length - 1];
// v22: Filter out thinking/reasoning blocks — they have ancestor with max-h-[200px]
// These are internal AI reasoning and should NOT be relayed to Discord
var filteredBlocks = [];
for (var fbi = 0; fbi < responseBlocks.length; fbi++) {
var isThinking = false;
var ancestor = responseBlocks[fbi].parentElement;
for (var depth = 0; ancestor && depth < 5; depth++) {
var aCls = (typeof ancestor.className === 'string') ? ancestor.className : '';
if (aCls.indexOf('max-h-[200px]') !== -1 || aCls.indexOf('max-h-[150px]') !== -1) {
isThinking = true;
break;
}
ancestor = ancestor.parentElement;
}
if (!isThinking) filteredBlocks.push(responseBlocks[fbi]);
}
if (filteredBlocks.length === 0) return;
// Process the LAST (most recent) non-thinking response block
var lastBlock = filteredBlocks[filteredBlocks.length - 1];
// Skip if already scraped
if (lastBlock.dataset.agChatScraped === 'true' || lastBlock.dataset.agChatScraped === 'pending') {
// Check for NEW blocks since last scrape
if (responseBlocks.length > _lastResponseBlockCount) {
if (filteredBlocks.length > _lastResponseBlockCount) {
// New block appeared — process it
for (var rbi = responseBlocks.length - 1; rbi >= 0; rbi--) {
if (responseBlocks[rbi].dataset.agChatScraped !== 'true' && responseBlocks[rbi].dataset.agChatScraped !== 'pending') {
lastBlock = responseBlocks[rbi];
for (var rbi = filteredBlocks.length - 1; rbi >= 0; rbi--) {
if (filteredBlocks[rbi].dataset.agChatScraped !== 'true' && filteredBlocks[rbi].dataset.agChatScraped !== 'pending') {
lastBlock = filteredBlocks[rbi];
break;
}
}
@@ -781,7 +867,8 @@ export function generateApprovalObserverScript(_port: number): string {
var parentCls = lastBlock.parentElement ? ((typeof lastBlock.parentElement.className === 'string') ? lastBlock.parentElement.className : '') : '';
var grandCls = (lastBlock.parentElement && lastBlock.parentElement.parentElement) ? ((typeof lastBlock.parentElement.parentElement.className === 'string') ? lastBlock.parentElement.parentElement.className : '') : '';
log('user-cls-debug block=' + clsStr.substring(0, 150) + ' | parent=' + parentCls.substring(0, 150) + ' | grand=' + grandCls.substring(0, 150) + ' | text=' + (blockText||'').substring(0, 50));
var isUser = clsStr.indexOf('user-color') !== -1 || clsStr.indexOf('user-background') !== -1 || clsStr.indexOf('user-message') !== -1;
// v22: Detect user message: has select-text + rounded-lg but NOT leading-relaxed
var isUser = (clsStr.indexOf('rounded-lg') !== -1 && clsStr.indexOf('leading-relaxed') === -1) || clsStr.indexOf('user-color') !== -1;
var role = isUser ? 'user' : 'bot';
// Bot messages often start empty and stream in. User messages are usually immediate.
@@ -809,12 +896,14 @@ export function generateApprovalObserverScript(_port: number): string {
var waitTime = isUser ? 500 : 3000;
if (Date.now() - _lastStepTextTime < waitTime) return; // Still waiting
// Content is stable — send it
// v21: DOM-based chat relay RE-ENABLED — GetCascadeTrajectorySteps does NOT
// return steps for in-progress cascades, making Step Probe RT-CAPTURE useless.
// Observer DOM extraction is the ONLY real-time path for AI response relay.
_lastStepTextSent = true;
_lastResponseBlockCount = responseBlocks.length;
_lastResponseBlockCount = filteredBlocks.length;
lastBlock.dataset.agChatScraped = 'pending';
log('AG-Native chat relay [' + role + ']: blocks=' + responseBlocks.length + ' text=' + blockText.length + ' chars');
log('AG-Native chat relay [' + role + ']: blocks=' + filteredBlocks.length + ' text=' + blockText.length + ' chars');
(function(el, txt, count, r) {
fetch(BASE + '/chat', {
method: 'POST',
@@ -822,7 +911,7 @@ export function generateApprovalObserverScript(_port: number): string {
body: JSON.stringify({ text: txt, source: 'ag_native_block_' + count, block_index: count, role: r })
}).then(function() { el.dataset.agChatScraped = 'true'; log('AG-Native chat sent OK'); })
.catch(function(e) { el.dataset.agChatScraped = 'false'; log('AG-Native chat send error: ' + e.message); });
})(lastBlock, blockText, responseBlocks.length, role);
})(lastBlock, blockText, filteredBlocks.length, role);
}
return; // AG Native path handled — don't fall through to Cascade path
}