// Background service worker: performs TRUSTED input via the Chrome DevTools Protocol. // Flow's agent editor ignores synthetic DOM events (isTrusted=false) — the text shows // but the app's state stays empty ("no prompt" on send). CDP Input.* events are trusted, // so the framework actually registers them. Requires the "debugger" permission; the user // sees a "started debugging this browser" banner while it runs (expected). const attached = new Set(); function attach(tabId) { return new Promise((res, rej) => chrome.debugger.attach({ tabId }, '1.3', () => chrome.runtime.lastError ? rej(chrome.runtime.lastError) : res())); } function cmd(tabId, method, params) { return new Promise((res, rej) => chrome.debugger.sendCommand({ tabId }, method, params || {}, (r) => chrome.runtime.lastError ? rej(chrome.runtime.lastError) : res(r))); } async function ensure(tabId) { if (attached.has(tabId)) return; try { await attach(tabId); attached.add(tabId); } catch (e) { // "Another debugger is already attached" => DevTools open on the tab; treat as attached-ish if (/already attached/i.test(String(e.message || e))) attached.add(tabId); else throw e; } } const ENTER = { key: 'Enter', code: 'Enter', windowsVirtualKeyCode: 13, nativeVirtualKeyCode: 13 }; chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { const tabId = sender.tab && sender.tab.id; (async () => { try { if (!tabId) throw new Error('no tab id'); await ensure(tabId); if (msg.type === 'insertText') { await cmd(tabId, 'Input.insertText', { text: msg.text }); sendResponse({ ok: true }); } else if (msg.type === 'pressEnter') { await cmd(tabId, 'Input.dispatchKeyEvent', { type: 'keyDown', ...ENTER }); await cmd(tabId, 'Input.dispatchKeyEvent', { type: 'keyUp', ...ENTER }); sendResponse({ ok: true }); } else if (msg.type === 'detach') { if (attached.has(tabId)) { chrome.debugger.detach({ tabId }, () => {}); attached.delete(tabId); } sendResponse({ ok: true }); } else { sendResponse({ ok: false, error: 'unknown message' }); } } catch (e) { sendResponse({ ok: false, error: String(e.message || e) }); } })(); return true; // keep the message channel open for the async response }); chrome.debugger.onDetach.addListener((src) => { if (src.tabId) attached.delete(src.tabId); });