feat(observer): v15 AG Native chat relay — scanChatBodies dual strategy (#632)

- Add AG Native DOM path: #conversation + .leading-relaxed.select-text
- Keep Cascade path: [data-testid=conversation-view] + [data-step-index]
- Register #632 in known-issues.md (SDK+DOM both blocked for AG Native)
- Bump version 0.5.50 → 0.5.51
- Add DOM analysis helper scripts
This commit is contained in:
Variet Worker
2026-04-16 05:28:44 +09:00
parent a00d561e28
commit 729875f3a6
7 changed files with 463 additions and 10 deletions

View File

@@ -1,7 +1,7 @@
export function generateApprovalObserverScript(_port: number): string {
return `
// ── Gravity Bridge v14: Strict Scope + Junk Filter ──
// v14: Strict 5-level DOM scope, CSS/source code/icon-glue filters, no fallback
// ── Gravity Bridge v15: AG Native Chat Relay ──
// v15: AG Native #conversation + .leading-relaxed.select-text chat body scanning
(function(){
'use strict';
var BASE='',_obs=false,_sent={},_ready=false;
@@ -10,7 +10,7 @@ export function generateApprovalObserverScript(_port: number): string {
var CLEANUP_MS=300000;
function log(m){console.log('[GB Observer] '+m);}
log('v14 Script loaded — Strict Scope + Junk Filter');
log('v15 Script loaded — AG Native Chat Relay');
// DIAGNOSTIC BEACON: immediate POST to confirm script execution in renderer
try {
@@ -460,15 +460,16 @@ export function generateApprovalObserverScript(_port: number): string {
}
// ══════════════════════════════════════════════════════════════════
// v7: STEP-AWARE CHAT BODY SCANNING
// Scans [data-step-index] elements inside [data-testid="conversation-view"]
// Extracts AI response text while filtering UI noise
// v15: AG-NATIVE + CASCADE DUAL CHAT BODY SCANNING
// AG Native: #conversation > ... > .leading-relaxed.select-text
// Cascade: [data-testid="conversation-view"] > [data-step-index]
// ══════════════════════════════════════════════════════════════════
var _lastScrapedStepIndex = -1;
var _lastStepText = '';
var _lastStepTextTime = 0;
var _lastStepTextSent = false;
var _lastResponseBlockCount = 0; // track number of response blocks for AG Native
function extractCleanStepText(stepEl) {
if (!stepEl) return '';
@@ -495,7 +496,7 @@ export function generateApprovalObserverScript(_port: number): string {
}
// Try to get text from markdown rendering area first
// Look for known markdown container patterns
// AG Native uses .leading-relaxed.select-text, Cascade uses .markdown-body/.prose
var mdEl = clone.querySelector('.markdown-body, .prose, [class*="markdown"], [class*="rendered"]');
var rawText = '';
if (mdEl && mdEl.innerText && mdEl.innerText.trim().length > 10) {
@@ -515,8 +516,80 @@ export function generateApprovalObserverScript(_port: number): string {
// One-time DOM dump
dumpDOMStructure();
// PRIMARY: Find conversation-view container
var cv = document.querySelector('[data-testid="conversation-view"]');
// ── STRATEGY 1: AG Native — #conversation or .antigravity-agent-side-panel ──
var cv = document.querySelector('#conversation');
if (!cv) {
cv = document.querySelector('.antigravity-agent-side-panel');
}
if (cv) {
// AG Native path: find AI response blocks by class pattern
// DOM structure: #conversation > ... > .leading-relaxed.select-text (AI response text)
var responseBlocks = cv.querySelectorAll('.leading-relaxed.select-text');
if (responseBlocks.length > 0) {
// Process the LAST (most recent) response block
var lastBlock = responseBlocks[responseBlocks.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) {
// 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];
break;
}
}
if (lastBlock.dataset.agChatScraped === 'true' || lastBlock.dataset.agChatScraped === 'pending') return;
} else {
return; // Already scraped, no new blocks
}
}
var blockText = extractCleanStepText(lastBlock);
if (blockText && blockText.length > 30) {
// QUALITY CHECK: Skip if the text is mostly short lines (UI artifacts)
var lines = blockText.split('\\n').filter(function(l) { return l.trim().length > 0; });
var longLines = lines.filter(function(l) { return l.trim().length > 20; });
if (longLines.length === 0) {
log('AG-Native: skipped (no long lines, likely UI noise)');
return;
}
// Wait for content to stabilize (3s no change)
if (_lastStepText !== blockText) {
_lastStepText = blockText;
_lastStepTextTime = Date.now();
_lastStepTextSent = false;
return; // Wait for next scan cycle
}
if (_lastStepTextSent) return;
if (Date.now() - _lastStepTextTime < 3000) return; // Still waiting
// Content is stable — send it
_lastStepTextSent = true;
_lastResponseBlockCount = responseBlocks.length;
lastBlock.dataset.agChatScraped = 'pending';
log('AG-Native chat relay: blocks=' + responseBlocks.length + ' text=' + blockText.length + ' chars');
(function(el, txt, count) {
fetch(BASE + '/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: txt, source: 'ag_native_block_' + count, block_index: count })
}).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);
}
return; // AG Native path handled — don't fall through to Cascade path
}
}
// ── STRATEGY 2: Cascade — [data-testid="conversation-view"] ──
cv = document.querySelector('[data-testid="conversation-view"]');
if (!cv) {
// FALLBACK: Try older selectors
cv = document.querySelector('[class*="conversation"], [class*="chat-container"]');