diff --git a/README.md b/README.md index cce01bd..a2cdedf 100644 --- a/README.md +++ b/README.md @@ -328,6 +328,8 @@ npx vitest run tests/e2e/ # E2E tests - **"Extension not connected"** - Ensure the opencli Browser Bridge extension is installed and **enabled** in `chrome://extensions`. +- **"attach failed: Cannot access a chrome-extension:// URL"** + - Another Chrome extension (e.g. youmind, New Tab Override, or AI assistant extensions) may be interfering. Try **disabling other extensions** temporarily, then retry. - **Empty data returns or 'Unauthorized' error** - Your login session in Chrome might have expired. Open a normal Chrome tab, navigate to the target site, and log in or refresh the page. - **Node API errors** diff --git a/README.zh-CN.md b/README.zh-CN.md index 7ad5d70..31c7cde 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -311,6 +311,8 @@ opencli cascade https://api.example.com/data - **"Extension not connected" 报错** - 确保你当前的 Chrome 已安装且**开启了** opencli Browser Bridge 扩展(在 `chrome://extensions` 中检查)。 +- **"attach failed: Cannot access a chrome-extension:// URL" 报错** + - 其他 Chrome 扩展(如 youmind、New Tab Override 或 AI 助手类扩展)可能产生冲突。请尝试**暂时禁用其他扩展**后重试。 - **返回空数据,或者报错 "Unauthorized"** - Chrome 里的登录态可能已经过期。请打开当前 Chrome 页面,在新标签页重新手工登录或刷新该页面。 - **Node API 错误 (如 parseArgs, fs 等)** diff --git a/extension/dist/background.js b/extension/dist/background.js index 4698850..6e19a77 100644 --- a/extension/dist/background.js +++ b/extension/dist/background.js @@ -36,6 +36,7 @@ async function ensureAttached(tabId) { await chrome.debugger.attach({ tabId }, "1.3"); } catch (e) { const msg = e instanceof Error ? e.message : String(e); + const hint = msg.includes("chrome-extension://") ? ". Tip: another Chrome extension may be interfering — try disabling other extensions" : ""; if (msg.includes("Another debugger is already attached")) { try { await chrome.debugger.detach({ tabId }); @@ -44,10 +45,10 @@ async function ensureAttached(tabId) { try { await chrome.debugger.attach({ tabId }, "1.3"); } catch { - throw new Error(`attach failed: ${msg}`); + throw new Error(`attach failed: ${msg}${hint}`); } } else { - throw new Error(`attach failed: ${msg}`); + throw new Error(`attach failed: ${msg}${hint}`); } } attached.add(tabId); @@ -310,7 +311,6 @@ async function resolveTabId(tabId, workspace) { if (tabId !== void 0) { try { const tab = await chrome.tabs.get(tabId); - console.log(`[opencli] resolveTabId: explicit tabId=${tabId}, url=${tab.url}`); if (isDebuggableUrl(tab.url)) return tabId; console.warn(`[opencli] Tab ${tabId} URL is not debuggable (${tab.url}), re-resolving`); } catch { @@ -320,11 +320,7 @@ async function resolveTabId(tabId, workspace) { const windowId = await getAutomationWindow(workspace); const tabs = await chrome.tabs.query({ windowId }); const debuggableTab = tabs.find((t) => t.id && isDebuggableUrl(t.url)); - if (debuggableTab?.id) { - console.log(`[opencli] resolveTabId: found debuggable tab ${debuggableTab.id} (${debuggableTab.url})`); - return debuggableTab.id; - } - console.warn(`[opencli] resolveTabId: no debuggable tabs found, tabs: ${tabs.map((t) => `${t.id}=${t.url}`).join(", ")}`); + if (debuggableTab?.id) return debuggableTab.id; const reuseTab = tabs.find((t) => t.id); if (reuseTab?.id) { await chrome.tabs.update(reuseTab.id, { url: "data:text/html," }); @@ -332,12 +328,7 @@ async function resolveTabId(tabId, workspace) { try { const updated = await chrome.tabs.get(reuseTab.id); if (isDebuggableUrl(updated.url)) return reuseTab.id; - console.warn(`[opencli] about:blank was intercepted (${updated.url}), trying data: URI`); - await chrome.tabs.update(reuseTab.id, { url: "data:text/html," }); - await new Promise((resolve) => setTimeout(resolve, 300)); - const updated2 = await chrome.tabs.get(reuseTab.id); - if (isDebuggableUrl(updated2.url)) return reuseTab.id; - console.warn(`[opencli] data: URI also intercepted, creating fresh tab`); + console.warn(`[opencli] data: URI was intercepted (${updated.url}), creating fresh tab`); } catch { } } diff --git a/extension/manifest.json b/extension/manifest.json index 92c31af..86da887 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "OpenCLI", - "version": "1.2.5", + "version": "1.2.6", "description": "Bridge between opencli CLI and your browser — execute commands, read cookies, manage tabs.", "permissions": [ "debugger", diff --git a/extension/package.json b/extension/package.json index 3db6809..9d6abd8 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,6 +1,6 @@ { "name": "opencli-extension", - "version": "1.2.5", + "version": "1.2.6", "private": true, "type": "module", "scripts": { diff --git a/extension/src/background.ts b/extension/src/background.ts index 64821dd..56012cf 100644 --- a/extension/src/background.ts +++ b/extension/src/background.ts @@ -88,7 +88,7 @@ function scheduleReconnect(): void { // ─── Automation window isolation ───────────────────────────────────── // All opencli operations happen in a dedicated Chrome window so the // user's active browsing session is never touched. -// The window auto-closes after 30s of idle (no commands). +// The window auto-closes after 120s of idle (no commands). type AutomationSession = { windowId: number; @@ -247,7 +247,6 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi if (tabId !== undefined) { try { const tab = await chrome.tabs.get(tabId); - console.log(`[opencli] resolveTabId: explicit tabId=${tabId}, url=${tab.url}`); if (isDebuggableUrl(tab.url)) return tabId; // Tab exists but URL is not debuggable — fall through to auto-resolve console.warn(`[opencli] Tab ${tabId} URL is not debuggable (${tab.url}), re-resolving`); @@ -260,42 +259,27 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi // Get (or create) the automation window const windowId = await getAutomationWindow(workspace); - // Prefer an existing debuggable tab (about:blank, http://, https://, etc.) + // Prefer an existing debuggable tab const tabs = await chrome.tabs.query({ windowId }); const debuggableTab = tabs.find(t => t.id && isDebuggableUrl(t.url)); - if (debuggableTab?.id) { - console.log(`[opencli] resolveTabId: found debuggable tab ${debuggableTab.id} (${debuggableTab.url})`); - return debuggableTab.id; - } - console.warn(`[opencli] resolveTabId: no debuggable tabs found, tabs: ${tabs.map(t => `${t.id}=${t.url}`).join(', ')}`); + if (debuggableTab?.id) return debuggableTab.id; - // No debuggable tab found — this typically happens when a "New Tab Override" - // extension replaces about:blank with a chrome-extension:// page. - // Reuse the first existing tab by navigating it to about:blank (avoids - // accumulating orphan tabs if chrome.tabs.create is also intercepted). + // No debuggable tab — another extension may have hijacked the tab URL. + // Try to reuse by navigating to a data: URI (not interceptable by New Tab Override). const reuseTab = tabs.find(t => t.id); if (reuseTab?.id) { await chrome.tabs.update(reuseTab.id, { url: 'data:text/html,' }); - // Wait for the navigation to take effect await new Promise(resolve => setTimeout(resolve, 300)); - // Verify the URL is actually debuggable (New Tab Override may have intercepted) try { const updated = await chrome.tabs.get(reuseTab.id); if (isDebuggableUrl(updated.url)) return reuseTab.id; - // New Tab Override intercepted about:blank — try data: URI instead - console.warn(`[opencli] about:blank was intercepted (${updated.url}), trying data: URI`); - await chrome.tabs.update(reuseTab.id, { url: 'data:text/html,' }); - await new Promise(resolve => setTimeout(resolve, 300)); - const updated2 = await chrome.tabs.get(reuseTab.id); - if (isDebuggableUrl(updated2.url)) return reuseTab.id; - // data: URI also intercepted — create a brand new tab - console.warn(`[opencli] data: URI also intercepted, creating fresh tab`); + console.warn(`[opencli] data: URI was intercepted (${updated.url}), creating fresh tab`); } catch { // Tab was closed during navigation } } - // Window has no debuggable tabs — create one + // Fallback: create a new tab const newTab = await chrome.tabs.create({ windowId, url: 'data:text/html,', active: true }); if (!newTab.id) throw new Error('Failed to create tab in automation window'); return newTab.id; diff --git a/extension/src/cdp.ts b/extension/src/cdp.ts index d742ff1..4bc0e4e 100644 --- a/extension/src/cdp.ts +++ b/extension/src/cdp.ts @@ -47,15 +47,18 @@ async function ensureAttached(tabId: number): Promise { await chrome.debugger.attach({ tabId }, '1.3'); } catch (e: unknown) { const msg = e instanceof Error ? e.message : String(e); + const hint = msg.includes('chrome-extension://') + ? '. Tip: another Chrome extension may be interfering — try disabling other extensions' + : ''; if (msg.includes('Another debugger is already attached')) { try { await chrome.debugger.detach({ tabId }); } catch { /* ignore */ } try { await chrome.debugger.attach({ tabId }, '1.3'); } catch { - throw new Error(`attach failed: ${msg}`); + throw new Error(`attach failed: ${msg}${hint}`); } } else { - throw new Error(`attach failed: ${msg}`); + throw new Error(`attach failed: ${msg}${hint}`); } } attached.add(tabId); diff --git a/package-lock.json b/package-lock.json index fe247aa..eae2602 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@jackwener/opencli", - "version": "1.2.5", + "version": "1.2.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@jackwener/opencli", - "version": "1.2.5", + "version": "1.2.6", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 8b1d35c..5551746 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jackwener/opencli", - "version": "1.2.5", + "version": "1.2.6", "publishConfig": { "access": "public" },