* fix: remove invalid `state: 'normal'` from chrome.windows.create()
Chrome 146+ rejects 'normal' as an invalid value for the `state` parameter
in chrome.windows.create(). This causes the error:
Error: Invalid value for state
Root cause analysis:
- The Chrome Extensions API documentation states that `state` parameter
only accepts 'minimized', 'maximized', and 'fullscreen' as input values
- While WindowState enum includes 'normal', it's meant for reading window
state, not for setting it during creation
- Chrome 146 enforces stricter validation on the `state` parameter
- When `state` is omitted, the window defaults to 'normal' state anyway
Fix: Remove the `state: 'normal'` parameter entirely. The window will
default to normal state without explicitly setting it.
Tested: `opencli doctor` and `opencli bilibili hot` now work correctly
on Chrome 146.0.7680.165.
* build: rebuild dist after removing state: 'normal'
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* fix(extension): probe daemon via HTTP before WebSocket to eliminate console noise
When the daemon is offline, `new WebSocket()` logs uncatchable
ERR_CONNECTION_REFUSED errors to Chrome's extension error page.
Add `probeAndConnect()` that checks daemon reachability with a
silent `fetch(HEAD)` before attempting WebSocket connection.
All three auto-connect paths (initialize, keepalive alarm, eager
reconnect) now go through the probe, eliminating the error noise
entirely.
Closes#505
* refactor(extension): inline probe into connect(), add /ping to daemon
Instead of a separate probeAndConnect() wrapper that all call sites had
to remember to use, bake the HTTP probe directly into connect() itself.
This makes the guard impossible to accidentally skip when adding new
connection paths in the future.
Also adds a dedicated GET /ping endpoint to the daemon (no X-OpenCLI
header required) so the probe has a clear semantic contract instead of
relying on a 403 side-effect from the root path.
- daemon: GET /ping → 200 {ok:true}, no auth needed, placed before the
X-OpenCLI header check; only chrome-extension:// and no-origin
requests reach it (origin check is still enforced above)
- background: connect() is now async; probes /ping with a 1 s timeout
before new WebSocket(); all call sites (initialize, keepalive alarm,
scheduleReconnect) remain unchanged
- probeAndConnect() removed — no longer needed
* fix(extension/daemon): address review feedback on probe refactor
- protocol.ts: replace DAEMON_HTTP_URL with DAEMON_PING_URL (clearer
semantics, single source of truth for the health-check URL)
- background.ts: import DAEMON_PING_URL from protocol instead of
defining a local constant; check res.ok so an unexpected non-200
response doesn't fall through to WebSocket; annotate all fire-and-
forget connect() call sites with `void` to make intent explicit
- daemon.ts: add security comment on /ping documenting the timing
side-channel tradeoff (loopback-only, accepted risk)
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
chrome.windows.create rejects state:'minimized' when combined with
width/height (Chrome API constraint). Revert to state:'normal' to fix
the "Invalid value for state" error. The 30s idle timeout from #521
is preserved.
Fixes#526
- Create automation window with `state: 'minimized'` so it never
appears in the user's taskbar or steals visual attention
- Reduce idle timeout from 120s to 30s — window closes quickly after
the last command finishes, instead of lingering for 2 minutes
- CDP debugger works fine on minimized windows, no functional impact
Fixes the user-visible issue of a blank data:text/html tab appearing
during command execution.
- Show helpful hint in popup when disconnected: "This is normal. The
extension connects automatically when you run any opencli command."
- Stop eager reconnect after 6 attempts (reaching 60s backoff) to
reduce ERR_CONNECTION_REFUSED noise in console; keepalive alarm
still retries every ~24s at low frequency.
- Add popup.html/popup.js showing daemon connection status
(Connected / Reconnecting / No daemon connected)
- Add message listener in background.ts to expose WebSocket state
- Add PRIVACY.md with full privacy policy covering all permissions
- Add content_security_policy to manifest.json
- Update description to be clearer for CWS reviewers
Replace inline sync chrome.debugger.detach() in onUpdated listener
with the shared async detach() function for consistent cleanup behavior
across all detach paths.
Cleanup:
- Remove redundant double-retry in resolveTabId (was retrying data: URI
with the same data: URI)
- Fix stale comment (30s → 120s idle timeout)
- Remove verbose debug logging in resolveTabId
- Built extension is now smaller (16.66kB vs 17.18kB)
Extension conflict:
- Add hint to attach-failed error when chrome-extension:// URL is detected
- Add troubleshooting entry for extension conflicts (e.g. youmind, New Tab
Override) to both README.md and README.zh-CN.md
Ref: #249
When a new automation window is created, the initial tab URL may be
empty briefly while Chrome loads the data: URI. isDebuggableUrl('') was
returning false, causing ensureAttached to reject the tab.
Fix: only reject known non-debuggable URLs (chrome://, chrome-extension://).
Empty/undefined URLs are now treated as debuggable since they represent
tabs still loading.
Also adds 200ms delay after window creation to let Chrome populate the
tab URL.
Root cause: getAutomationWindow and resolveTabId used about:blank which
New Tab Override extensions intercept immediately, replacing it with
chrome-extension:// URLs that cannot be debugged.
Changes:
- Window creation: about:blank → data:text/html
- reuseTab fallback: about:blank → data:text/html
- newTab handler: about:blank → data:text/html
- Added diagnostic logging to resolveTabId for debugging
- Synced extension version to 1.2.4
Ref: #249
resolveTabId's reuseTab path now verifies the URL is actually debuggable
after navigating to about:blank. If a New Tab Override extension intercepts
it (setting it back to chrome-extension://), falls back to a data: URI,
then creates a fresh tab as last resort.
This fixes the persistent 'attach failed: Cannot access chrome-extension://'
error for users with New Tab Override extensions installed.
Ref: #249
- resolveTabId: validate URL even for explicit tabId, fall through to
auto-resolve when tab is not debuggable or has been closed
- handleNavigate: wait for URL change before checking 'complete' status
to avoid race condition with stale about:blank
- ensureAttached: pre-check tab URL, verify cached attach with probe,
invalidate cache on URL change via onUpdated listener
- daemon-client: recognize transient extension errors (disconnected,
attach failed) as retryable with 1500ms delay; fresh command ID per attempt
- pipeline executor: add per-step retry for browser steps (up to 2 retries
on transient errors); cleanup automation window on pipeline failure
- page.ts: selectTab/newTab/closeTab properly update/invalidate _tabId
- daemon.ts: add WebSocket ping/pong heartbeat (15s interval, 2-miss disconnect)
- Increase automation window idle timeout from 30s to 120s
- Fix timeout param edge cases in BrowserBridge._ensureDaemon
- Remove unused chalk import; fix trailing import placement
Closes#249
Replace eager close-window (which caused race conditions when
parallel commands shared the window) with an idle-based timer:
- Window auto-closes 30s after the last command completes
- Each incoming command resets the idle timer
- Consecutive commands reuse the same window (faster)
- No race conditions with parallel execution
- Close-window action kept for explicit cleanup if needed
- Add 'close-window' action to extension protocol and background.ts
- Add Page.closeWindow() method to send close-window command
- browserSession() now closes automation window in cleanup
- Remove domain pre-navigation + 2s wait from main.ts (CDP handles
cross-domain cookies natively, no same-origin workaround needed)
- Net effect: commands run faster, no stale windows left behind
All opencli operations now run in a dedicated Chrome window instead
of hijacking the user's active tab. The automation window:
- Created on first command via chrome.windows.create({ focused: false })
- 1280x900 viewport, auto-cleaned up when closed
- All tabs resolved within this window only
- User's main browsing session is never touched
Tested: twitter trending ✅, zhihu hot ✅