flow-extension/background.js
type-two c21ea2b22d feat: bake in the by-hand reliability ritual — agent-ready wait, stop-square verify, hard-refresh requeue
- waitForAgentReady: after (re)load, wait for the sidebar to hydrate before typing
- post-submit success check = square stop button (or emptied box); no square after
  Enter + submit fallback -> hard refresh (bypassCache via background) and requeue
- 'Something went wrong' surviving Try-again -> hard refresh + requeue (was: fail)
- requeue attempts capped at 3 per task (chrome.storage, survives reloads)
- server: 'requeue' status keeps task pending; INFLIGHT entries expire after 15 min
  so a reloaded tab gets its task re-served
- banks/djsim_batch3.jsonl: 18-task 90sDJsim asset bank (queued live)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 17:42:15 +10:00

70 lines
3.1 KiB
JavaScript

// Background service worker.
// * Trusted input via the Chrome DevTools Protocol — Flow's editor ignores synthetic
// DOM events (isTrusted=false); CDP Input.* events are trusted so it registers them.
// * Asset download — fetches the harvested media URL with the page's session cookies
// (host_permission covers labs.google) and hands the bytes to the local server. Runs
// in the worker, so it's not blocked by the page's CSP.
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) {
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 (msg.type === 'insertText') {
await ensure(tabId);
await cmd(tabId, 'Input.insertText', { text: msg.text });
sendResponse({ ok: true });
} else if (msg.type === 'pressEnter') {
await ensure(tabId);
await cmd(tabId, 'Input.dispatchKeyEvent', { type: 'keyDown', ...ENTER });
await cmd(tabId, 'Input.dispatchKeyEvent', { type: 'keyUp', ...ENTER });
sendResponse({ ok: true });
} else if (msg.type === 'download') {
// Browser-native download: uses the page's session cookies, follows the media
// redirect, no CSP/CORS/base64. Lands under the browser's Downloads dir.
const dlId = await new Promise((res, rej) => chrome.downloads.download(
{ url: msg.url, filename: msg.filename, conflictAction: 'uniquify', saveAs: false },
(id) => chrome.runtime.lastError ? rej(chrome.runtime.lastError) : res(id)));
sendResponse({ ok: dlId != null, id: dlId });
} else if (msg.type === 'hardReload') {
// Cache-bypass reload — the by-hand fix for a wedged Flow session.
if (attached.has(tabId)) { chrome.debugger.detach({ tabId }, () => {}); attached.delete(tabId); }
chrome.tabs.reload(tabId, { bypassCache: true });
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; // async response
});
chrome.debugger.onDetach.addListener((src) => { if (src.tabId) attached.delete(src.tabId); });