diff --git a/README.md b/README.md index e4dd67ea..512241a2 100644 --- a/README.md +++ b/README.md @@ -789,7 +789,7 @@ These arrive in the response body as `data.error.details[].reason`. The worker a | `Requested entity was not found` | Uploaded `media_id` expired (~1h TTL) | Auto-recover via `_recover_entity_not_found` — re-uploads from `image_url`, re-queues PENDING | | `Internal error encountered` | Flow backend transient 500 | Exponential backoff retry: `2^retry * 10s`, capped 300s | | `reCAPTCHA failed` / `captcha` | Extension couldn't solve CAPTCHA | Retry up to 10× without incrementing `retry_count` (processor.py:454-464) | -| `PUBLIC_ERROR_UNUSUAL_ACTIVITY` (403, message `reCAPTCHA evaluation failed`) | Google flagged the session as bot-like — usually rapid bursts of submits, VPN/shared IP, or stale auth cookies | NOT auto-recoverable. Pause submits, clear cookies for `google.com` + `labs.google` in Chrome, sign back in at `labs.google/fx/tools/flow`, then resubmit with ≥1s gap and ≤5 concurrent. See `/fk-doctor` for full playbook. | +| `PUBLIC_ERROR_UNUSUAL_ACTIVITY` (403, message `reCAPTCHA evaluation failed`) | Google flagged the session as bot-like — usually rapid bursts of submits, VPN/shared IP, or stale auth cookies | NOT auto-recoverable. Pause submits, clear cookies for `google.com` + `labs.google` in Chrome, sign back in at `flow.google.com`, then resubmit with ≥1s gap and ≤5 concurrent. See `/fk-doctor` for full playbook. | ### HTTP Status Codes diff --git a/agent/services/flow_client.py b/agent/services/flow_client.py index 667e2d51..68a13993 100644 --- a/agent/services/flow_client.py +++ b/agent/services/flow_client.py @@ -62,6 +62,8 @@ def set_extension(self, ws): "connected_at": time.time(), "flow_key": None, "token_captured_at": None, + "extension_version": None, + "flow_url_supported": None, "unavailable_until": 0, } # A new unauthenticated profile must not displace an already @@ -185,6 +187,16 @@ def ws_stats(self) -> dict: uptime = None if self._ws_connected_at and self.connected: uptime = int(time.time() - self._ws_connected_at) + versions = sorted({ + str(session["extension_version"]) + for session in self._extensions.values() + if session.get("extension_version") + }) + flow_url_support = [ + session.get("flow_url_supported") + for session in self._extensions.values() + if session.get("flow_url_supported") is not None + ] return { "connected": self.connected, "active_connections": len(self._extensions), @@ -192,6 +204,8 @@ def ws_stats(self) -> dict: 1 for session in self._extensions.values() if session.get("flow_key") ), + "extension_versions": versions, + "flow_url_supported": all(flow_url_support) if flow_url_support else None, "connects": self._ws_connect_count, "disconnects": self._ws_disconnect_count, "uptime_s": uptime, @@ -212,7 +226,18 @@ async def handle_message(self, data: dict, websocket=None): return if data.get("type") == "extension_ready": - logger.info("Extension ready, flowKey=%s", "yes" if data.get("flowKeyPresent") else "no") + source_ws = websocket or self._extension_ws + version = data.get("extensionVersion") + flow_supported = data.get("flowUrlSupported") + if source_ws is not None and source_ws in self._extensions: + self._extensions[source_ws]["extension_version"] = version + self._extensions[source_ws]["flow_url_supported"] = flow_supported + logger.info( + "Extension ready, flowKey=%s version=%s flow.google.com=%s", + "yes" if data.get("flowKeyPresent") else "no", + version or "unknown", + "yes" if flow_supported is True else "no" if flow_supported is False else "unknown", + ) asyncio.create_task(self._sync_tier()) return diff --git a/dashboard/src/i18n/translations.ts b/dashboard/src/i18n/translations.ts index 30348cb1..695be91a 100644 --- a/dashboard/src/i18n/translations.ts +++ b/dashboard/src/i18n/translations.ts @@ -164,7 +164,7 @@ const en = { // ---- Guide ---- 'guide.title': 'Chrome Extension Setup Guide', - 'guide.intro': "Flow Kit needs a companion Chrome extension running alongside your browser to capture the login token, solve reCAPTCHA, and relay API calls to Google Flow (labs.google/fx/tools/flow). This extension is built/loaded manually — it isn't on the Chrome Web Store.", + 'guide.intro': "Flow Kit needs a companion Chrome extension running alongside your browser to capture the login token, solve reCAPTCHA, and relay API calls to Google Flow (flow.google.com). This extension is built/loaded manually — it isn't on the Chrome Web Store.", 'guide.status.title': 'Current status', 'guide.status.desc': 'auto-refreshes every 4s from GET /health', 'guide.status.unreachable': "Can't reach the agent at 127.0.0.1:8100 — run python -m agent.main.", @@ -176,7 +176,7 @@ const en = { 'guide.step1.title': 'Install the Chrome extension (Developer mode / unpacked)', 'guide.step1.body': 'Open chrome://extensions → enable "Developer mode" (top right) → click "Load unpacked" → select the extension/ folder in this repo.', 'guide.step2.title': 'Sign in to Google Flow', - 'guide.step2.body': "Open https://labs.google/fx/tools/flow and sign in with your Google account. The extension captures the login token from this tab — if you're not signed in, the agent will report a NO_FLOW_KEY error.", + 'guide.step2.body': "Open https://flow.google.com/ and sign in with your Google account. The extension captures the login token from this tab — if you're not signed in, the agent will report a NO_FLOW_KEY error.", 'guide.step3.title': 'Run the agent (backend)', 'guide.step3.body': 'From the repo root: source venv/bin/activate (if using ./setup.sh), then python -m agent.main. The agent runs the REST API on port 8100 and a WebSocket bridge to the extension on port 9222.', 'guide.step4.title': 'Verify the connection', @@ -185,13 +185,13 @@ const en = { 'guide.trouble1.problem': 'Extension shows "Agent disconnected"', 'guide.trouble1.solution': "Run python -m agent.main (the agent isn't running or crashed).", 'guide.trouble2.problem': 'Extension shows "No token"', - 'guide.trouble2.solution': 'Open labs.google/fx/tools/flow and sign in again.', + 'guide.trouble2.solution': 'Open flow.google.com and sign in again.', 'guide.trouble3.problem': 'CAPTCHA_FAILED: NO_FLOW_TAB error', - 'guide.trouble3.solution': 'Open a Google Flow tab (labs.google/fx/tools/flow) and try again.', + 'guide.trouble3.solution': 'Open a Google Flow tab (flow.google.com) and try again.', 'guide.trouble4.problem': '"Extension not connected"', 'guide.trouble4.solution': 'Go to chrome://extensions and click the reload icon on the Flow Kit card — the extension will reconnect automatically.', 'guide.trouble5.problem': '403 PUBLIC_ERROR_UNUSUAL_ACTIVITY error', - 'guide.trouble5.solution': 'Pause submissions → chrome://settings/cookies → remove cookies for google.com and labs.google → reopen labs.google/fx/tools/flow, sign in, solve the captcha if shown → resubmit more slowly.', + 'guide.trouble5.solution': 'Pause submissions → chrome://settings/cookies → remove cookies for google.com and labs.google → reopen flow.google.com, sign in, solve the captcha if shown → resubmit more slowly.', 'guide.trouble6.problem': 'curl: (7) Failed to connect to 127.0.0.1:8100', 'guide.trouble6.solution': "The agent isn't running — run python -m agent.main.", @@ -448,7 +448,7 @@ const vi: Partial> = { 'videoPlayer.next': 'Sau', 'guide.title': 'Hướng dẫn cài đặt Chrome Extension', - 'guide.intro': 'Flow Kit cần một Chrome extension chạy song song để bắt token đăng nhập, giải reCAPTCHA và chuyển tiếp API call tới Google Flow (labs.google/fx/tools/flow). Extension này được tự build/load thủ công, không có trên Chrome Web Store.', + 'guide.intro': 'Flow Kit cần một Chrome extension chạy song song để bắt token đăng nhập, giải reCAPTCHA và chuyển tiếp API call tới Google Flow (flow.google.com). Extension này được tự build/load thủ công, không có trên Chrome Web Store.', 'guide.status.title': 'Trạng thái hiện tại', 'guide.status.desc': 'tự động cập nhật mỗi 4 giây từ GET /health', 'guide.status.unreachable': 'Không kết nối được tới agent ở 127.0.0.1:8100 — hãy chạy python -m agent.main.', @@ -460,7 +460,7 @@ const vi: Partial> = { 'guide.step1.title': 'Cài Chrome extension (chế độ Developer / unpacked)', 'guide.step1.body': 'Mở chrome://extensions → bật "Developer mode" (góc trên phải) → bấm "Load unpacked" → chọn thư mục extension/ trong repo này.', 'guide.step2.title': 'Đăng nhập Google Flow', - 'guide.step2.body': 'Mở https://labs.google/fx/tools/flow và đăng nhập tài khoản Google của bạn. Extension bắt token đăng nhập từ tab này — nếu chưa đăng nhập, agent sẽ báo lỗi NO_FLOW_KEY.', + 'guide.step2.body': 'Mở https://flow.google.com/ và đăng nhập tài khoản Google của bạn. Extension bắt token đăng nhập từ tab này — nếu chưa đăng nhập, agent sẽ báo lỗi NO_FLOW_KEY.', 'guide.step3.title': 'Chạy agent (backend)', 'guide.step3.body': 'Từ thư mục gốc repo: source venv/bin/activate (nếu dùng ./setup.sh) rồi python -m agent.main. Agent chạy REST API ở cổng 8100 và WebSocket cầu nối với extension ở cổng 9222.', 'guide.step4.title': 'Kiểm tra kết nối', @@ -469,13 +469,13 @@ const vi: Partial> = { 'guide.trouble1.problem': 'Extension báo "Agent disconnected"', 'guide.trouble1.solution': 'Chạy python -m agent.main (agent chưa chạy hoặc bị crash).', 'guide.trouble2.problem': 'Extension báo "No token"', - 'guide.trouble2.solution': 'Mở labs.google/fx/tools/flow và đăng nhập lại.', + 'guide.trouble2.solution': 'Mở flow.google.com và đăng nhập lại.', 'guide.trouble3.problem': 'Lỗi CAPTCHA_FAILED: NO_FLOW_TAB', - 'guide.trouble3.solution': 'Mở một tab Google Flow (labs.google/fx/tools/flow) rồi thử lại.', + 'guide.trouble3.solution': 'Mở một tab Google Flow (flow.google.com) rồi thử lại.', 'guide.trouble4.problem': '"Extension not connected"', 'guide.trouble4.solution': 'Vào chrome://extensions, bấm reload icon trên thẻ Flow Kit — extension sẽ tự kết nối lại.', 'guide.trouble5.problem': 'Lỗi 403 PUBLIC_ERROR_UNUSUAL_ACTIVITY', - 'guide.trouble5.solution': 'Tạm dừng gửi request → chrome://settings/cookies → xoá cookie của google.com và labs.google → mở lại labs.google/fx/tools/flow, đăng nhập, giải captcha nếu có → gửi lại chậm hơn.', + 'guide.trouble5.solution': 'Tạm dừng gửi request → chrome://settings/cookies → xoá cookie của google.com và labs.google → mở lại flow.google.com, đăng nhập, giải captcha nếu có → gửi lại chậm hơn.', 'guide.trouble6.problem': 'curl: (7) Failed to connect to 127.0.0.1:8100', 'guide.trouble6.solution': 'Agent chưa chạy — chạy python -m agent.main.', @@ -722,7 +722,7 @@ const hi: Partial> = { 'videoPlayer.next': 'अगला', 'guide.title': 'Chrome Extension सेटअप गाइड', - 'guide.intro': 'Flow Kit को लॉगिन टोकन कैप्चर करने, reCAPTCHA हल करने और Google Flow (labs.google/fx/tools/flow) पर API कॉल भेजने के लिए एक साथ चलने वाला Chrome extension चाहिए। यह extension मैन्युअली बनाया/लोड किया जाता है — यह Chrome Web Store पर नहीं है।', + 'guide.intro': 'Flow Kit को लॉगिन टोकन कैप्चर करने, reCAPTCHA हल करने और Google Flow (flow.google.com) पर API कॉल भेजने के लिए एक साथ चलने वाला Chrome extension चाहिए। यह extension मैन्युअली बनाया/लोड किया जाता है — यह Chrome Web Store पर नहीं है।', 'guide.status.title': 'वर्तमान स्थिति', 'guide.status.desc': 'हर 4 सेकंड में GET /health से स्वतः रीफ्रेश होता है', 'guide.status.unreachable': '127.0.0.1:8100 पर एजेंट से कनेक्ट नहीं हो पा रहा — python -m agent.main चलाएं।', @@ -734,7 +734,7 @@ const hi: Partial> = { 'guide.step1.title': 'Chrome extension इंस्टॉल करें (Developer mode / unpacked)', 'guide.step1.body': 'chrome://extensions खोलें → "Developer mode" चालू करें (ऊपर दाईं ओर) → "Load unpacked" पर क्लिक करें → इस repo में extension/ फ़ोल्डर चुनें।', 'guide.step2.title': 'Google Flow में साइन इन करें', - 'guide.step2.body': 'https://labs.google/fx/tools/flow खोलें और अपने Google अकाउंट से साइन इन करें। extension इस टैब से लॉगिन टोकन कैप्चर करता है — यदि साइन इन नहीं है तो एजेंट NO_FLOW_KEY एरर देगा।', + 'guide.step2.body': 'https://flow.google.com/ खोलें और अपने Google अकाउंट से साइन इन करें। extension इस टैब से लॉगिन टोकन कैप्चर करता है — यदि साइन इन नहीं है तो एजेंट NO_FLOW_KEY एरर देगा।', 'guide.step3.title': 'एजेंट (backend) चलाएं', 'guide.step3.body': 'repo के रूट से: source venv/bin/activate (यदि ./setup.sh उपयोग कर रहे हैं) फिर python -m agent.main चलाएं। एजेंट पोर्ट 8100 पर REST API और पोर्ट 9222 पर extension से WebSocket ब्रिज चलाता है।', 'guide.step4.title': 'कनेक्शन सत्यापित करें', @@ -743,13 +743,13 @@ const hi: Partial> = { 'guide.trouble1.problem': 'एक्सटेंशन "Agent disconnected" दिखा रहा है', 'guide.trouble1.solution': 'python -m agent.main चलाएं (एजेंट नहीं चल रहा या क्रैश हो गया है)।', 'guide.trouble2.problem': 'एक्सटेंशन "No token" दिखा रहा है', - 'guide.trouble2.solution': 'labs.google/fx/tools/flow खोलें और फिर से साइन इन करें।', + 'guide.trouble2.solution': 'flow.google.com खोलें और फिर से साइन इन करें।', 'guide.trouble3.problem': 'CAPTCHA_FAILED: NO_FLOW_TAB एरर', - 'guide.trouble3.solution': 'एक Google Flow टैब खोलें (labs.google/fx/tools/flow) और फिर से प्रयास करें।', + 'guide.trouble3.solution': 'एक Google Flow टैब खोलें (flow.google.com) और फिर से प्रयास करें।', 'guide.trouble4.problem': '"Extension not connected"', 'guide.trouble4.solution': 'chrome://extensions पर जाएं और Flow Kit कार्ड पर reload आइकन क्लिक करें — extension स्वतः फिर से जुड़ जाएगा।', 'guide.trouble5.problem': '403 PUBLIC_ERROR_UNUSUAL_ACTIVITY एरर', - 'guide.trouble5.solution': 'सबमिशन रोकें → chrome://settings/cookies → google.com और labs.google के कुकीज़ हटाएं → labs.google/fx/tools/flow फिर से खोलें, साइन इन करें, दिखे तो captcha हल करें → धीरे-धीरे फिर से सबमिट करें।', + 'guide.trouble5.solution': 'सबमिशन रोकें → chrome://settings/cookies → google.com और labs.google के कुकीज़ हटाएं → flow.google.com फिर से खोलें, साइन इन करें, दिखे तो captcha हल करें → धीरे-धीरे फिर से सबमिट करें।', 'guide.trouble6.problem': 'curl: (7) Failed to connect to 127.0.0.1:8100', 'guide.trouble6.solution': 'एजेंट नहीं चल रहा — python -m agent.main चलाएं।', @@ -996,7 +996,7 @@ const id: Partial> = { 'videoPlayer.next': 'Berikutnya', 'guide.title': 'Panduan Pengaturan Ekstensi Chrome', - 'guide.intro': 'Flow Kit membutuhkan ekstensi Chrome pendamping yang berjalan di browser untuk menangkap token login, menyelesaikan reCAPTCHA, dan meneruskan panggilan API ke Google Flow (labs.google/fx/tools/flow). Ekstensi ini dibuat/dimuat secara manual — tidak tersedia di Chrome Web Store.', + 'guide.intro': 'Flow Kit membutuhkan ekstensi Chrome pendamping yang berjalan di browser untuk menangkap token login, menyelesaikan reCAPTCHA, dan meneruskan panggilan API ke Google Flow (flow.google.com). Ekstensi ini dibuat/dimuat secara manual — tidak tersedia di Chrome Web Store.', 'guide.status.title': 'Status saat ini', 'guide.status.desc': 'diperbarui otomatis setiap 4 detik dari GET /health', 'guide.status.unreachable': 'Tidak dapat terhubung ke agent di 127.0.0.1:8100 — jalankan python -m agent.main.', @@ -1008,7 +1008,7 @@ const id: Partial> = { 'guide.step1.title': 'Instal ekstensi Chrome (mode Developer / unpacked)', 'guide.step1.body': 'Buka chrome://extensions → aktifkan "Developer mode" (kanan atas) → klik "Load unpacked" → pilih folder extension/ di repo ini.', 'guide.step2.title': 'Masuk ke Google Flow', - 'guide.step2.body': 'Buka https://labs.google/fx/tools/flow dan masuk dengan akun Google Anda. Ekstensi menangkap token login dari tab ini — jika belum masuk, agent akan melaporkan error NO_FLOW_KEY.', + 'guide.step2.body': 'Buka https://flow.google.com/ dan masuk dengan akun Google Anda. Ekstensi menangkap token login dari tab ini — jika belum masuk, agent akan melaporkan error NO_FLOW_KEY.', 'guide.step3.title': 'Jalankan agent (backend)', 'guide.step3.body': 'Dari root repo: source venv/bin/activate (jika menggunakan ./setup.sh), lalu python -m agent.main. Agent menjalankan REST API di port 8100 dan jembatan WebSocket ke ekstensi di port 9222.', 'guide.step4.title': 'Verifikasi koneksi', @@ -1017,13 +1017,13 @@ const id: Partial> = { 'guide.trouble1.problem': 'Ekstensi menampilkan "Agent disconnected"', 'guide.trouble1.solution': 'Jalankan python -m agent.main (agent tidak berjalan atau crash).', 'guide.trouble2.problem': 'Ekstensi menampilkan "No token"', - 'guide.trouble2.solution': 'Buka labs.google/fx/tools/flow dan masuk kembali.', + 'guide.trouble2.solution': 'Buka flow.google.com dan masuk kembali.', 'guide.trouble3.problem': 'Error CAPTCHA_FAILED: NO_FLOW_TAB', - 'guide.trouble3.solution': 'Buka tab Google Flow (labs.google/fx/tools/flow) dan coba lagi.', + 'guide.trouble3.solution': 'Buka tab Google Flow (flow.google.com) dan coba lagi.', 'guide.trouble4.problem': '"Extension not connected"', 'guide.trouble4.solution': 'Buka chrome://extensions dan klik ikon reload pada kartu Flow Kit — ekstensi akan terhubung kembali secara otomatis.', 'guide.trouble5.problem': 'Error 403 PUBLIC_ERROR_UNUSUAL_ACTIVITY', - 'guide.trouble5.solution': 'Hentikan sementara pengiriman → chrome://settings/cookies → hapus cookie untuk google.com dan labs.google → buka kembali labs.google/fx/tools/flow, masuk, selesaikan captcha jika muncul → kirim ulang lebih lambat.', + 'guide.trouble5.solution': 'Hentikan sementara pengiriman → chrome://settings/cookies → hapus cookie untuk google.com dan labs.google → buka kembali flow.google.com, masuk, selesaikan captcha jika muncul → kirim ulang lebih lambat.', 'guide.trouble6.problem': 'curl: (7) Failed to connect to 127.0.0.1:8100', 'guide.trouble6.solution': 'Agent tidak berjalan — jalankan python -m agent.main.', @@ -1270,7 +1270,7 @@ const zh: Partial> = { 'videoPlayer.next': '下一个', 'guide.title': 'Chrome 扩展安装指南', - 'guide.intro': 'Flow Kit 需要一个配套的 Chrome 扩展在浏览器中运行,用于捕获登录令牌、解决 reCAPTCHA 并转发 API 请求到 Google Flow (labs.google/fx/tools/flow)。该扩展需手动构建/加载 — 未上架 Chrome 网上应用店。', + 'guide.intro': 'Flow Kit 需要一个配套的 Chrome 扩展在浏览器中运行,用于捕获登录令牌、解决 reCAPTCHA 并转发 API 请求到 Google Flow (flow.google.com)。该扩展需手动构建/加载 — 未上架 Chrome 网上应用店。', 'guide.status.title': '当前状态', 'guide.status.desc': '每 4 秒自动从 GET /health 刷新', 'guide.status.unreachable': '无法连接到 127.0.0.1:8100 的 agent — 请运行 python -m agent.main。', @@ -1282,7 +1282,7 @@ const zh: Partial> = { 'guide.step1.title': '安装 Chrome 扩展(开发者模式 / 未打包)', 'guide.step1.body': '打开 chrome://extensions → 开启右上角的"开发者模式" → 点击"加载已解压的扩展程序" → 选择本仓库中的 extension/ 文件夹。', 'guide.step2.title': '登录 Google Flow', - 'guide.step2.body': '打开 https://labs.google/fx/tools/flow 并使用你的 Google 账号登录。扩展会从该标签页捕获登录令牌 — 如果未登录,agent 会报 NO_FLOW_KEY 错误。', + 'guide.step2.body': '打开 https://flow.google.com/ 并使用你的 Google 账号登录。扩展会从该标签页捕获登录令牌 — 如果未登录,agent 会报 NO_FLOW_KEY 错误。', 'guide.step3.title': '运行 agent(后端)', 'guide.step3.body': '在仓库根目录:source venv/bin/activate(如果使用了 ./setup.sh),然后运行 python -m agent.main。agent 会在 8100 端口运行 REST API,并在 9222 端口与扩展建立 WebSocket 桥接。', 'guide.step4.title': '验证连接', @@ -1291,13 +1291,13 @@ const zh: Partial> = { 'guide.trouble1.problem': '扩展显示 "Agent disconnected"', 'guide.trouble1.solution': '运行 python -m agent.main(agent 未运行或已崩溃)。', 'guide.trouble2.problem': '扩展显示 "No token"', - 'guide.trouble2.solution': '打开 labs.google/fx/tools/flow 并重新登录。', + 'guide.trouble2.solution': '打开 flow.google.com 并重新登录。', 'guide.trouble3.problem': 'CAPTCHA_FAILED: NO_FLOW_TAB 错误', - 'guide.trouble3.solution': '打开一个 Google Flow 标签页(labs.google/fx/tools/flow)后重试。', + 'guide.trouble3.solution': '打开一个 Google Flow 标签页(flow.google.com)后重试。', 'guide.trouble4.problem': '"Extension not connected"', 'guide.trouble4.solution': '前往 chrome://extensions,点击 Flow Kit 卡片上的重新加载图标 — 扩展会自动重新连接。', 'guide.trouble5.problem': '403 PUBLIC_ERROR_UNUSUAL_ACTIVITY 错误', - 'guide.trouble5.solution': '暂停提交 → 打开 chrome://settings/cookies → 删除 google.com 和 labs.google 的 cookie → 重新打开 labs.google/fx/tools/flow,登录,如出现验证码请完成 → 放慢速度重新提交。', + 'guide.trouble5.solution': '暂停提交 → 打开 chrome://settings/cookies → 删除 google.com 和 labs.google 的 cookie → 重新打开 flow.google.com,登录,如出现验证码请完成 → 放慢速度重新提交。', 'guide.trouble6.problem': 'curl: (7) Failed to connect to 127.0.0.1:8100', 'guide.trouble6.solution': 'Agent 未运行 — 请运行 python -m agent.main。', @@ -1544,7 +1544,7 @@ const ko: Partial> = { 'videoPlayer.next': '다음', 'guide.title': 'Chrome 확장 프로그램 설치 가이드', - 'guide.intro': 'Flow Kit는 로그인 토큰을 캡처하고, reCAPTCHA를 해결하며, Google Flow(labs.google/fx/tools/flow)로 API 호출을 전달하기 위해 함께 실행되는 Chrome 확장 프로그램이 필요합니다. 이 확장 프로그램은 직접 빌드/로드해야 하며 Chrome 웹 스토어에는 없습니다.', + 'guide.intro': 'Flow Kit는 로그인 토큰을 캡처하고, reCAPTCHA를 해결하며, Google Flow(flow.google.com)로 API 호출을 전달하기 위해 함께 실행되는 Chrome 확장 프로그램이 필요합니다. 이 확장 프로그램은 직접 빌드/로드해야 하며 Chrome 웹 스토어에는 없습니다.', 'guide.status.title': '현재 상태', 'guide.status.desc': 'GET /health로부터 4초마다 자동 갱신됩니다', 'guide.status.unreachable': '127.0.0.1:8100의 agent에 연결할 수 없습니다 — python -m agent.main을 실행하세요.', @@ -1556,7 +1556,7 @@ const ko: Partial> = { 'guide.step1.title': 'Chrome 확장 프로그램 설치 (개발자 모드 / 압축해제됨)', 'guide.step1.body': 'chrome://extensions를 열고 → 우측 상단의 "개발자 모드"를 켠 다음 → "압축해제된 확장 프로그램을 로드합니다"를 클릭하고 → 이 저장소의 extension/ 폴더를 선택하세요.', 'guide.step2.title': 'Google Flow에 로그인', - 'guide.step2.body': 'https://labs.google/fx/tools/flow를 열고 Google 계정으로 로그인하세요. 확장 프로그램이 이 탭에서 로그인 토큰을 캡처합니다 — 로그인하지 않으면 agent가 NO_FLOW_KEY 오류를 표시합니다.', + 'guide.step2.body': 'https://flow.google.com/를 열고 Google 계정으로 로그인하세요. 확장 프로그램이 이 탭에서 로그인 토큰을 캡처합니다 — 로그인하지 않으면 agent가 NO_FLOW_KEY 오류를 표시합니다.', 'guide.step3.title': 'Agent(백엔드) 실행', 'guide.step3.body': '저장소 루트에서: source venv/bin/activate (./setup.sh를 사용한 경우) 후 python -m agent.main을 실행하세요. agent는 8100번 포트에서 REST API를, 9222번 포트에서 확장 프로그램과의 WebSocket 브리지를 실행합니다.', 'guide.step4.title': '연결 확인', @@ -1565,13 +1565,13 @@ const ko: Partial> = { 'guide.trouble1.problem': '확장 프로그램에 "Agent disconnected"가 표시됨', 'guide.trouble1.solution': 'python -m agent.main을 실행하세요 (agent가 실행 중이 아니거나 충돌했습니다).', 'guide.trouble2.problem': '확장 프로그램에 "No token"이 표시됨', - 'guide.trouble2.solution': 'labs.google/fx/tools/flow를 열고 다시 로그인하세요.', + 'guide.trouble2.solution': 'flow.google.com를 열고 다시 로그인하세요.', 'guide.trouble3.problem': 'CAPTCHA_FAILED: NO_FLOW_TAB 오류', - 'guide.trouble3.solution': 'Google Flow 탭(labs.google/fx/tools/flow)을 열고 다시 시도하세요.', + 'guide.trouble3.solution': 'Google Flow 탭(flow.google.com)을 열고 다시 시도하세요.', 'guide.trouble4.problem': '"Extension not connected"', 'guide.trouble4.solution': 'chrome://extensions로 이동하여 Flow Kit 카드의 새로고침 아이콘을 클릭하세요 — 확장 프로그램이 자동으로 다시 연결됩니다.', 'guide.trouble5.problem': '403 PUBLIC_ERROR_UNUSUAL_ACTIVITY 오류', - 'guide.trouble5.solution': '제출을 일시 중지 → chrome://settings/cookies 열기 → google.com 및 labs.google의 쿠키 삭제 → labs.google/fx/tools/flow를 다시 열고 로그인, 캡차가 표시되면 해결 → 더 천천히 다시 제출하세요.', + 'guide.trouble5.solution': '제출을 일시 중지 → chrome://settings/cookies 열기 → google.com 및 labs.google의 쿠키 삭제 → flow.google.com를 다시 열고 로그인, 캡차가 표시되면 해결 → 더 천천히 다시 제출하세요.', 'guide.trouble6.problem': 'curl: (7) Failed to connect to 127.0.0.1:8100', 'guide.trouble6.solution': 'Agent가 실행 중이 아닙니다 — python -m agent.main을 실행하세요.', @@ -1818,7 +1818,7 @@ const ja: Partial> = { 'videoPlayer.next': '次へ', 'guide.title': 'Chrome拡張機能セットアップガイド', - 'guide.intro': 'Flow Kitは、ログイントークンの取得、reCAPTCHAの解決、Google Flow(labs.google/fx/tools/flow)へのAPI呼び出しの中継のために、ブラウザ上で並行して動作するChrome拡張機能を必要とします。この拡張機能は手動でビルド/読み込みする必要があり、Chromeウェブストアには公開されていません。', + 'guide.intro': 'Flow Kitは、ログイントークンの取得、reCAPTCHAの解決、Google Flow(flow.google.com)へのAPI呼び出しの中継のために、ブラウザ上で並行して動作するChrome拡張機能を必要とします。この拡張機能は手動でビルド/読み込みする必要があり、Chromeウェブストアには公開されていません。', 'guide.status.title': '現在のステータス', 'guide.status.desc': 'GET /health から4秒ごとに自動更新されます', 'guide.status.unreachable': '127.0.0.1:8100 の agent に接続できません — python -m agent.main を実行してください。', @@ -1830,7 +1830,7 @@ const ja: Partial> = { 'guide.step1.title': 'Chrome拡張機能をインストール(デベロッパーモード / パッケージ化されていない拡張機能)', 'guide.step1.body': 'chrome://extensions を開き → 右上の「デベロッパーモード」を有効にし → 「パッケージ化されていない拡張機能を読み込む」をクリックし → このリポジトリの extension/ フォルダを選択してください。', 'guide.step2.title': 'Google Flow にサインイン', - 'guide.step2.body': 'https://labs.google/fx/tools/flow を開き、Googleアカウントでサインインしてください。拡張機能はこのタブからログイントークンを取得します — サインインしていない場合、agentはNO_FLOW_KEYエラーを報告します。', + 'guide.step2.body': 'https://flow.google.com/ を開き、Googleアカウントでサインインしてください。拡張機能はこのタブからログイントークンを取得します — サインインしていない場合、agentはNO_FLOW_KEYエラーを報告します。', 'guide.step3.title': 'Agent(バックエンド)を実行', 'guide.step3.body': 'リポジトリのルートから: source venv/bin/activate(./setup.sh を使用した場合)を実行後、python -m agent.main を実行してください。agentはポート8100でREST APIを、ポート9222で拡張機能とのWebSocketブリッジを実行します。', 'guide.step4.title': '接続を確認', @@ -1839,13 +1839,13 @@ const ja: Partial> = { 'guide.trouble1.problem': '拡張機能に "Agent disconnected" と表示される', 'guide.trouble1.solution': 'python -m agent.main を実行してください(agentが実行されていないかクラッシュしています)。', 'guide.trouble2.problem': '拡張機能に "No token" と表示される', - 'guide.trouble2.solution': 'labs.google/fx/tools/flow を開いて再度サインインしてください。', + 'guide.trouble2.solution': 'flow.google.com を開いて再度サインインしてください。', 'guide.trouble3.problem': 'CAPTCHA_FAILED: NO_FLOW_TAB エラー', - 'guide.trouble3.solution': 'Google Flow のタブ(labs.google/fx/tools/flow)を開いてから再試行してください。', + 'guide.trouble3.solution': 'Google Flow のタブ(flow.google.com)を開いてから再試行してください。', 'guide.trouble4.problem': '"Extension not connected"', 'guide.trouble4.solution': 'chrome://extensions を開き、Flow Kit カードの再読み込みアイコンをクリックしてください — 拡張機能が自動的に再接続されます。', 'guide.trouble5.problem': '403 PUBLIC_ERROR_UNUSUAL_ACTIVITY エラー', - 'guide.trouble5.solution': '送信を一時停止 → chrome://settings/cookies を開く → google.com と labs.google のCookieを削除 → labs.google/fx/tools/flow を再度開いてサインインし、captchaが表示されたら解決 → ゆっくりと再送信してください。', + 'guide.trouble5.solution': '送信を一時停止 → chrome://settings/cookies を開く → google.com と labs.google のCookieを削除 → flow.google.com を再度開いてサインインし、captchaが表示されたら解決 → ゆっくりと再送信してください。', 'guide.trouble6.problem': 'curl: (7) Failed to connect to 127.0.0.1:8100', 'guide.trouble6.solution': 'Agent が実行されていません — python -m agent.main を実行してください。', diff --git a/extension/background.js b/extension/background.js index 98228da8..7ec7bfbd 100644 --- a/extension/background.js +++ b/extension/background.js @@ -92,7 +92,9 @@ chrome.alarms.onAlarm.addListener(async (alarm) => { if (alarm.name === 'reconnect') connectToAgent(); if (alarm.name === 'keepAlive') keepAlive(); if (alarm.name === 'token-refresh') { - await captureTokenFromFlowTab(); + // Passive maintenance must never create browser tabs. If the user has no + // Flow tab open, wait for an explicit action or an actual RPC to open one. + await captureTokenFromFlowTab({ createIfMissing: false }); } }); @@ -151,25 +153,29 @@ chrome.webRequest.onBeforeSendHeaders.addListener( let _openingFlowTab = false; -async function captureTokenFromFlowTab() { - const tabs = await chrome.tabs.query({ url: flowUrls }); +async function captureTokenFromFlowTab({ createIfMissing = false } = {}) { + let tabs = await chrome.tabs.query({ url: flowUrls }); if (!tabs.length) { + if (!createIfMissing) { + console.log('[FlowAgent] No Flow tab found — passive refresh skipped'); + return { skipped: 'NO_FLOW_TAB' }; + } if (_openingFlowTab) { console.log('[FlowAgent] Flow tab already opening, skipping'); return; } _openingFlowTab = true; try { - console.log('[FlowAgent] No Flow tab found — opening one in background'); - await chrome.tabs.create({ url: FLOW_TAB_URL, active: false }); + console.log('[FlowAgent] No Flow tab found — opening one for explicit refresh'); + const opened = await chrome.tabs.create({ url: FLOW_TAB_URL, active: false }); await sleep(3000); - const retryTabs = await chrome.tabs.query({ url: flowUrls }); - if (!retryTabs.length) { + const target = opened?.id ? await chrome.tabs.get(opened.id).catch(() => null) : null; + if (!target) { console.log('[FlowAgent] Flow tab not ready yet after open'); return; } await chrome.scripting.executeScript({ - target: { tabId: retryTabs[0].id }, + target: { tabId: target.id }, files: ['content.js'], }); console.log('[FlowAgent] Token refresh triggered on newly opened Flow tab'); @@ -218,6 +224,8 @@ function connectToAgent() { ws.send(JSON.stringify({ type: 'extension_ready', flowKeyPresent: !!flowKey, + extensionVersion: chrome.runtime.getManifest().version, + flowUrlSupported: chrome.runtime.getManifest().host_permissions?.includes('https://flow.google.com/*') === true, tokenAge: flowKey && metrics.tokenCapturedAt ? Date.now() - metrics.tokenCapturedAt : null, })); if (flowKey) { @@ -359,16 +367,19 @@ function captchaFromTab(tabId, requestId, captchaAction) { async function solveCaptcha(requestId, captchaAction) { let tabs = await chrome.tabs.query({ url: flowUrls }); - // No Flow tab at all — spawn one and let it settle. + // No Flow tab at all — spawn one and let it settle. Keep the exact tab id: + // a redirected or stale tab must not make us select some older candidate. if (!tabs.length) { + let opened; try { - await chrome.tabs.create({ url: FLOW_TAB_URL, active: false }); + opened = await chrome.tabs.create({ url: FLOW_TAB_URL, active: false }); await sleep(3000); - tabs = await chrome.tabs.query({ url: flowUrls }); } catch (e) { return { error: e.message || 'NO_FLOW_TAB' }; } - if (!tabs.length) return { error: 'NO_FLOW_TAB' }; + const target = opened?.id ? await chrome.tabs.get(opened.id).catch(() => null) : null; + if (!target) return { error: 'NO_FLOW_TAB' }; + tabs = [target]; } // Try each Flow tab in turn. A tab that answers "no grecaptcha" is a tab @@ -401,16 +412,25 @@ async function solveCaptcha(requestId, captchaAction) { } } - // Every candidate failed — last-ditch, spawn a fresh tab and try it once. + // Every candidate failed — last-ditch, spawn a fresh temporary tab and + // target THAT exact tab. Previously we re-queried all Flow tabs and picked + // fresh[0], which could select the same stale tab again while leaking the + // newly-created one on every retry. + let recoveryTab = null; try { - await chrome.tabs.create({ url: FLOW_TAB_URL, active: false }); + recoveryTab = await chrome.tabs.create({ url: FLOW_TAB_URL, active: false }); await sleep(3000); - const fresh = await chrome.tabs.query({ url: flowUrls }); - const target = fresh.find((t) => !t.discarded) || fresh[0]; - if (!target) return { error: 'NO_FLOW_TAB' }; + const target = await chrome.tabs.get(recoveryTab.id); + if (!target || target.discarded) return { error: 'NO_FLOW_TAB' }; return await captchaFromTab(target.id, requestId, captchaAction); } catch (e) { return { error: e?.message || errors[0] || 'NO_FLOW_TAB' }; + } finally { + // A recovery tab is disposable: there were already Flow tabs available + // for the signed RPC. Do not let CAPTCHA retries accumulate root tabs. + if (recoveryTab?.id) { + try { await chrome.tabs.remove(recoveryTab.id); } catch { /* already gone */ } + } } } @@ -447,12 +467,13 @@ async function runBatchRpc(cmd) { let candidate = tabs.find((t) => !t.discarded) || tabs[0]; if (!candidate) { // No Flow tab — open one and give the app a moment to boot, otherwise - // WIZ_global_data is not on the page yet and `at` comes back empty. + // WIZ_global_data is not on the page yet and `at` comes back empty. Keep + // the exact created tab id so redirects/stale tabs cannot hijack recovery. + let opened; try { - await chrome.tabs.create({ url: FLOW_TAB_URL, active: false }); + opened = await chrome.tabs.create({ url: FLOW_TAB_URL, active: false }); await sleep(5000); - const fresh = await chrome.tabs.query({ url: flowUrls }); - candidate = fresh.find((t) => !t.discarded) || fresh[0]; + candidate = opened?.id ? await chrome.tabs.get(opened.id).catch(() => null) : null; } catch (e) { return { error: e?.message || 'NO_FLOW_TAB' }; } @@ -804,7 +825,7 @@ chrome.runtime.onMessage.addListener((msg, _, reply) => { } if (msg.type === 'REFRESH_TOKEN') { - captureTokenFromFlowTab() + captureTokenFromFlowTab({ createIfMissing: true }) .then(() => reply({ ok: true })) .catch((e) => reply({ error: e.message })); return true; diff --git a/extension/manifest.json b/extension/manifest.json index 1ae492a6..a01b657e 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Flow Kit", - "version": "0.3.0", + "version": "0.3.1", "description": "Local agent bridge for Google Flow \u2014 runs batchexecute RPCs inside a signed-in flow.google.com tab, mints reCAPTCHA, proxies API calls", "permissions": [ "storage", diff --git a/setup.sh b/setup.sh index 8e764ffb..b5d2960a 100755 --- a/setup.sh +++ b/setup.sh @@ -165,7 +165,7 @@ echo " 1. Load Chrome extension:" echo " chrome://extensions → Developer mode → Load unpacked → extension/" echo "" echo " 2. Open Google Flow:" -echo " https://labs.google/fx/tools/flow (sign in)" +echo " https://flow.google.com/ (sign in)" echo "" echo " 3. Start the agent:" echo " source venv/bin/activate" diff --git a/skills/fk-doctor.md b/skills/fk-doctor.md index c134a557..6b92019c 100644 --- a/skills/fk-doctor.md +++ b/skills/fk-doctor.md @@ -99,7 +99,7 @@ python3 -c "from agent.config import USE_BATCH_RPC, FLOW_PROJECT_ID; \ | `Requested entity was not found` | Uploaded `media_id` expired (~1h TTL on uploads) | `_recover_entity_not_found` re-uploads from `image_url`, re-queues PENDING | If auto-recovery fails: manually `POST /api/upload-image`, patch `media_id` | | `Internal error encountered` | Flow backend transient 500 | Exponential backoff: `2^retry * 10s`, capped 300s | None — wait, or retry manually after a minute | | `reCAPTCHA failed` / (contains `captcha`) | Extension couldn't solve reCAPTCHA | Retry ≤10× without consuming `retry_count` (processor.py:454-464) | Ensure a Google Flow tab is open and focused; reload extension | -| `PUBLIC_ERROR_UNUSUAL_ACTIVITY` (403, message `reCAPTCHA evaluation failed`) | Google flagged the session as bot-like — usually triggered by rapid bursts of submits (e.g. many GENERATE_VIDEO in <1 minute), shared/VPN IP, or stale auth cookies | NOT auto-handled — Google blocks even fresh requests until the trust signal recovers | (1) **Stop the worker / pipeline** so submits pause. (2) Open Chrome → `chrome://settings/cookies` (or the extension's Chrome profile) → search `google.com` and `labs.google` → **remove all cookies for both**. (3) Reload `https://labs.google/fx/tools/flow` and sign back in (re-solve any reCAPTCHA puzzles manually). (4) Slow down submission cadence (≥1s gap between submits, ≤5 concurrent). If still blocked, switch to a different network or wait 1–6 h | +| `PUBLIC_ERROR_UNUSUAL_ACTIVITY` (403, message `reCAPTCHA evaluation failed`) | Google flagged the session as bot-like — usually triggered by rapid bursts of submits (e.g. many GENERATE_VIDEO in <1 minute), shared/VPN IP, or stale auth cookies | NOT auto-handled — Google blocks even fresh requests until the trust signal recovers | (1) **Stop the worker / pipeline** so submits pause. (2) Open Chrome → `chrome://settings/cookies` (or the extension's Chrome profile) → search `google.com` and `labs.google` → **remove all cookies for both**. (3) Reload `https://flow.google.com` and sign back in (re-solve any reCAPTCHA puzzles manually). (4) Slow down submission cadence (≥1s gap between submits, ≤5 concurrent). If still blocked, switch to a different network or wait 1–6 h | ### B. HTTP status codes @@ -194,7 +194,7 @@ When the user describes a symptom in plain language, map it here first. | Extension shows "No token" | Expected on the batch path — there is no bearer any more. Only act on it with `USE_BATCH_RPC=0` | | `CAPTCHA_FAILED: NO_FLOW_TAB` | Open `https://flow.google.com/` — and check the extension is v0.3.0+, older builds only matched the dead labs.google URL and could not see the tab that was right there | | 403 `MODEL_ACCESS_DENIED` | Tier mismatch — `GET /api/flow/credits`, downgrade model in `models.json` via `/fk-change-model` | -| 403 `PUBLIC_ERROR_UNUSUAL_ACTIVITY` / `reCAPTCHA evaluation failed` | Google flagged the session as bot-like (rapid bursts, VPN/shared IP, stale cookies). **Pause submits**, then in Chrome: `chrome://settings/cookies` → remove cookies for `google.com` and `labs.google` → reload `labs.google/fx/tools/flow` → sign in & solve any captcha → resubmit with ≥1s gap and ≤5 concurrent. Switch network or wait 1–6 h if still blocked | +| 403 `PUBLIC_ERROR_UNUSUAL_ACTIVITY` / `reCAPTCHA evaluation failed` | Google flagged the session as bot-like (rapid bursts, VPN/shared IP, stale cookies). **Pause submits**, then in Chrome: `chrome://settings/cookies` → remove cookies for `google.com` and `labs.google` → reload `flow.google.com` → sign in & solve any captcha → resubmit with ≥1s gap and ≤5 concurrent. Switch network or wait 1–6 h if still blocked | | Scene images inconsistent across scenes | Check all refs have UUID `media_id` — run `/fk-fix-uuids` | | `media_id` starts with `CAMS...` | Run `/fk-fix-uuids` to extract UUID from URL | | Upscale fails on every scene | On the batch path upscale is unported (`UNSUPPORTED_ON_BATCH_API`) — no upsampler rpc has been captured. On the legacy path it needs `PAYGATE_TIER_TWO` | diff --git a/tests/extension_mv3_bootstrap.test.cjs b/tests/extension_mv3_bootstrap.test.cjs index 86a12f54..2ac5381c 100644 --- a/tests/extension_mv3_bootstrap.test.cjs +++ b/tests/extension_mv3_bootstrap.test.cjs @@ -11,6 +11,7 @@ const source = fs.readFileSync( const lifecycleListeners = { alarm: [], installed: [], startup: [] }; const sockets = []; let storageReads = 0; +let tabCreates = 0; class FakeWebSocket { static CONNECTING = 0; @@ -40,6 +41,12 @@ const chrome = { action: { setBadgeBackgroundColor() {}, setBadgeText() {} }, alarms: { clear() {}, create() {}, onAlarm: event(lifecycleListeners.alarm) }, runtime: { + getManifest() { + return { + version: '0.3.1', + host_permissions: ['https://flow.google.com/*'], + }; + }, onInstalled: event(lifecycleListeners.installed), onMessage: event(), onStartup: event(lifecycleListeners.startup), @@ -60,8 +67,14 @@ const chrome = { }, }, tabs: { - create: async () => ({}), + create: async () => { + tabCreates += 1; + return { id: 123, discarded: false }; + }, + get: async (id) => ({ id, discarded: false }), query: async () => [], + reload: async () => {}, + remove: async () => {}, sendMessage: async () => {}, update: async () => {}, }, @@ -100,8 +113,13 @@ setImmediate(async () => { socket.readyState = FakeWebSocket.OPEN; socket.onopen(); + await lifecycleListeners.alarm[0]({ name: 'token-refresh' }); + assert.equal(tabCreates, 0, 'passive token refresh must never create a Flow tab'); + assert.equal(socket.messages[0].type, 'extension_ready'); assert.equal(socket.messages[0].flowKeyPresent, true); + assert.equal(socket.messages[0].extensionVersion, '0.3.1'); + assert.equal(socket.messages[0].flowUrlSupported, true); assert.ok(socket.messages[0].tokenAge > 0); assert.deepEqual(socket.messages[1], { type: 'token_captured',