fix(cdp): fix sendMessage selector escape bug + polling JSON comparison

This commit is contained in:
2026-03-07 21:27:51 +09:00
parent 507324f78e
commit 6ed5b33caa

View File

@@ -282,7 +282,8 @@ class CDPClient {
/** /**
* Antigravity 채팅 입력창에 메시지를 전송 * Antigravity 채팅 입력창에 메시지를 전송
* *
* Antigravity는 textarea가 아닌 contenteditable div를 사용함 * 단일 Runtime.evaluate로 요소 찾기 + 텍스트 입력 수행
* (2단계 분리 시 셀렉터 이스케이프 손실 버그 방지)
*/ */
async sendMessage(text) { async sendMessage(text) {
if (!this.connected || !this.client) { if (!this.connected || !this.client) {
@@ -290,11 +291,10 @@ class CDPClient {
} }
try { try {
// 1) contenteditable 입력창 찾기 및 포커스 const safeText = JSON.stringify(text);
const { result: focusResult } = await this.client.Runtime.evaluate({ const { result } = await this.client.Runtime.evaluate({
expression: ` expression: `
(function() { (function() {
// Antigravity 입력창 셀렉터 (우선순위 순)
const selectors = [ const selectors = [
'#antigravity\\\\.agentSidePanelInputBox [contenteditable="true"][role="textbox"]', '#antigravity\\\\.agentSidePanelInputBox [contenteditable="true"][role="textbox"]',
'.antigravity-agent-side-panel [contenteditable="true"][role="textbox"]', '.antigravity-agent-side-panel [contenteditable="true"][role="textbox"]',
@@ -302,47 +302,34 @@ class CDPClient {
'[contenteditable="true"][role="textbox"]', '[contenteditable="true"][role="textbox"]',
]; ];
let el = null;
for (const sel of selectors) { for (const sel of selectors) {
const el = document.querySelector(sel); el = document.querySelector(sel);
if (el) { if (el) break;
el.focus();
return { found: true, sel: sel };
} }
} if (!el) return { success: false, error: 'input not found' };
return { found: false };
})()
`,
returnByValue: true,
});
if (!focusResult.value?.found) {
return { success: false, error: '채팅 입력창을 찾을 수 없습니다' };
}
// 2) 텍스트 입력 (contenteditable용 — execCommand 방식)
await this.client.Runtime.evaluate({
expression: `
(function() {
const el = document.querySelector('${focusResult.value.sel}');
if (!el) return;
el.focus(); el.focus();
// 기존 내용 선택 후 삭제
const selection = window.getSelection(); const selection = window.getSelection();
const range = document.createRange(); const range = document.createRange();
range.selectNodeContents(el); range.selectNodeContents(el);
selection.removeAllRanges(); selection.removeAllRanges();
selection.addRange(range); selection.addRange(range);
// 텍스트 삽입 (React/Preact 호환) document.execCommand('insertText', false, ${safeText});
document.execCommand('insertText', false, ${JSON.stringify(text)});
return { success: true, text: el.textContent.substring(0, 50) };
})() })()
`, `,
returnByValue: true, returnByValue: true,
}); });
// 3) Enter 키 전송 if (!result.value?.success) {
return { success: false, error: result.value?.error || '입력 실패' };
}
// Enter 키 전송
await this.client.Input.dispatchKeyEvent({ await this.client.Input.dispatchKeyEvent({
type: 'keyDown', type: 'keyDown',
key: 'Enter', key: 'Enter',
@@ -358,7 +345,7 @@ class CDPClient {
windowsVirtualKeyCode: 13, windowsVirtualKeyCode: 13,
}); });
console.log(`[CDP] 메시지 전송: "${text.substring(0, 50)}..."`); console.log(`[CDP] 메시지 전송: "${text.substring(0, 50)}"`);
return { success: true }; return { success: true };
} catch (err) { } catch (err) {
console.error('[CDP] 메시지 전송 오류:', err.message); console.error('[CDP] 메시지 전송 오류:', err.message);
@@ -378,11 +365,12 @@ class CDPClient {
return; return;
} }
const html = await this.scrapeChatDOM(); const messages = await this.scrapeChatDOM();
if (html && html !== this.lastChatHTML) { const hash = JSON.stringify(messages);
this.lastChatHTML = html; if (messages && messages.length > 0 && hash !== this.lastChatHTML) {
this.lastChatHTML = hash;
if (this.onChatUpdate) { if (this.onChatUpdate) {
this.onChatUpdate(html); this.onChatUpdate(messages);
} }
} }
}, intervalMs); }, intervalMs);