fix(extension): restore AI Response Content capture by patching DOM extraction, CSP connect-src, and TS regex literal serialization

This commit is contained in:
Variet Worker
2026-04-10 21:10:33 +09:00
parent 58887f6933
commit a99c283656
22 changed files with 744 additions and 592 deletions

144
test_dom_mock.js Normal file
View File

@@ -0,0 +1,144 @@
const fs = require('fs');
const path = require('path');
const jsdom = require('jsdom');
const { JSDOM } = jsdom;
const observerModule = require('./extension/out/observer-script.js');
const observerCode = observerModule.generateApprovalObserverScript(8080);
const rawDump = JSON.parse(fs.readFileSync('C:/Users/Variet-Worker/.gemini/antigravity/bridge/dump_html.json', 'utf8'));
// Instantiate DOM
const dom = new JSDOM(rawDump.html, { runScripts: "dangerously", pretendToBeVisual: true, url: "http://localhost/" });
const window = dom.window;
const document = window.document;
// Polyfill offsetParent for visibility check
Object.defineProperty(window.HTMLElement.prototype, 'offsetParent', {
get() { return document.body; }
});
Object.defineProperty(window.HTMLElement.prototype, 'style', {
get() { return { display: 'block' }; }
});
// Mock innerText (JSDOM does not fully support it, but generic walker uses nodeValue / textContent)
// Our logic uses nodeValue for TextNodes, so it will work in JSDOM out of the box!
// Setup the DOM tree to perfectly match a real Chat conversation
const titleSpan = document.querySelector('span[title^="command("]');
if(!titleSpan) {
console.error("COULD NOT FIND command SPAN in dump?!");
process.exit(1);
}
// Find the card container
let toolContainer = titleSpan.parentElement;
while(toolContainer && !toolContainer.className.includes("border-gray-500/10") && !toolContainer.className.includes("bg-gray-500/10")) {
toolContainer = toolContainer.parentElement;
}
if(!toolContainer) toolContainer = titleSpan.parentElement; // fallback
// Create an AI text block just above it
const aiChat = document.createElement('div');
aiChat.className = 'markdown prose';
aiChat.innerHTML = '<p>안녕하세요! 시스템을 수정하기 위해 요청하신 작업을 시작합니다. <b>디스코드 릴레이 기능 복구</b>를 위해 스크립트를 실행하겠습니다.</p>';
// Wrap them up in the turn container
const parent = toolContainer.parentElement;
const convoWrapper = document.createElement('div');
convoWrapper.className = 'bg-agent-convo-background';
parent.insertBefore(convoWrapper, toolContainer);
convoWrapper.appendChild(aiChat);
convoWrapper.appendChild(toolContainer); // Move tool inside the convo wrapper as a sibling to AI chat
// Add action button to the tool container
const btn = document.createElement('button');
btn.innerHTML = '<span class="truncate">Allow</span>';
toolContainer.appendChild(btn);
console.log("Mock Button offsetParent:", btn.offsetParent ? btn.offsetParent.tagName : 'null');
console.log("Mock Button display:", btn.style.display);
console.log("Mock Button text:", btn.textContent);
// MOCK FETCH
const fetchCalls = [];
window.fetch = function(url, options) {
fetchCalls.push({url, options});
if (url.includes('/ping')) {
return Promise.resolve({ text: function() { return Promise.resolve('pong'); } });
}
if (url.includes('/pending')) {
return Promise.resolve({ json: function() { return Promise.resolve({ok: true, request_id: 'test-rid'}); } });
}
return Promise.resolve({ json: function() { return Promise.resolve({}); } });
};
// Polyfill offsetParent for visibility check
Object.defineProperty(window.HTMLElement.prototype, 'offsetParent', {
get() { return document.body; }
});
Object.defineProperty(window.HTMLElement.prototype, 'style', {
get() { return { display: 'block' }; }
});
const originalLog = console.log;
window.console.log = function(...args) {
if (args.length > 0 && typeof args[0] === 'string' && args[0].includes("NOT MATCHED")) {
let txt = args[1];
let codes = [];
if (txt) {
for(let i=0; i<txt.length; i++) codes.push(txt.charCodeAt(i));
}
originalLog('[JSDOM-WIN]', args[0], `\nRAW="${txt}"\nCODES=[${codes.join(',')}]`);
} else {
originalLog('[JSDOM-WIN]', ...args);
}
};
// Inject the observer
const scriptEl = document.createElement('script');
scriptEl.textContent = observerCode;
document.body.appendChild(scriptEl);
console.log("Observer injected, waiting for cycles...");
console.log("Total Buttons in DOM:", document.querySelectorAll('button').length);
// Emulate UI mutation to trigger the MutationObserver and force an instant scan()
setTimeout(() => {
console.log("Triggering DOM mutation to force scan()...");
document.body.appendChild(document.createElement('span'));
}, 1500);
// Give it time to finish scan().
setTimeout(() => {
console.log("\n====== FETCH CALLS ======");
if(fetchCalls.length === 0) console.log("NO FETCH CALLS MADE!");
fetchCalls.forEach(c => {
console.log(`\n[${c.options ? c.options.method || 'GET' : 'GET'}] ${c.url}`);
if(c.options && c.options.body) {
try {
let j = JSON.parse(c.options.body);
console.log("[BODY] request_id:", j.request_id);
console.log("[BODY] command:", j.command);
console.log("[BODY] description:\n" + "=".repeat(40) + "\n" + j.description + "\n" + "=".repeat(40));
} catch(e) {
console.log("[BODY]", c.options.body);
}
}
});
// Determine success
const pendingCall = fetchCalls.find(c => c.url.includes('/pending'));
if(pendingCall && pendingCall.options && pendingCall.options.body) {
const payload = JSON.parse(pendingCall.options.body);
if(payload.description.includes("안녕하세요!") && payload.description.includes("METHOD=TITLE_SPAN")) {
console.log("\n✅ SUCCESS: Both Chat Body & Tool Command effectively extracted!");
process.exit(0);
} else {
console.log("\n❌ FAIL: Payload description missing either chat text or command string!");
process.exit(1);
}
} else {
console.log("\n❌ FAIL: /pending never called!");
process.exit(1);
}
}, 4000);