- daemon.py: pass enableFileChooserOpenedEvent=true on Page.enable so
Page.fileChooserOpened actually fires (it's off by default in CDP).
- helpers.py: isinstance guards in page_info() and try/except around the JS
eval in pending_blockers() so CDP-side blockers still return when the page
thread is frozen or the session is gone.
- interaction-skills/permissions.md: tighten the intro (Page.* events do cover
dialogs/file-chooser/downloads — it's permission popups specifically that
have no CDP event), and add Browser.setPermission as the finer-grained
alternative to Browser.grantPermissions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Chrome renders permission popups outside the page viewport and emits no CDP
event when they open, so agents needed a full-desktop screencapture (or a
hint from the user) to notice one. This wires up a general detector:
- daemon.py injects a wrapper on every document via
Page.addScriptToEvaluateOnNewDocument that logs calls to geolocation,
notifications, mediaDevices, clipboard, bluetooth/usb/serial/hid, file
pickers, window.print, requestStorageAccess, and <input type=file> clicks
into window.__bu_blockers__.
- The event tap records Page.fileChooserOpened, Page.downloadWillBegin, and
Page.javascriptDialogOpening to a bounded daemon-side deque.
- page_info() surfaces a `blockers` key when either source has entries, so
the agent sees "native popup likely open" without being told.
- pending_blockers() returns the full log (js + cdp) for explicit checks.
- interaction-skills/permissions.md documents detection plus the preferred
pre-empt pattern: Browser.grantPermissions + Emulation.setGeolocationOverride.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Agents rarely know to check for open alert/confirm/prompt/beforeunload
dialogs — they freeze the JS thread, so page_info()'s Runtime.evaluate
returns a stale or misleading viewport dict while the page is blocked.
Track the latest unhandled Page.javascriptDialogOpening in the daemon
(cleared on Page.javascriptDialogClosed) and, when one is open, return
{dialog: {type, message, ...}} from page_info() instead of the usual
viewport dict. The dialogs skill now points agents at this signal.
* fix daemon attaching to invisible omnibox popup on fresh Chrome
When Chrome opens fresh, the only page targets are chrome://
internal pages and the omnibox popup (1px invisible viewport).
The daemon's attach_first_page() fell back to the popup, making
all subsequent work invisible to the user.
Fix: when no real pages exist, create an about:blank tab via
Target.createTarget instead of attaching to the omnibox popup.
Tested configurations:
- Fresh start with no real tabs → creates about:blank (1112x817)
- Navigate without AppleScript → works, tab visible
- Recovery from stale socket → auto-reconnects
- Chrome restart from scratch → creates about:blank
Also adds interaction-skills/connection.md documenting the
omnibox popup problem and startup sequence.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* add connection skill reference to main SKILL.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two skill categories, pure markdown, no Python files:
- interaction-skills/ — generic browser patterns (dialogs, inputs, etc.)
Flat .md files. Agent reads the relevant one before a task.
- domain-skills/ — per-site playbooks (tiktok/, linkedin/, etc.)
Flat .md files per action (upload, schedule, post).
Subfolders for large domains when needed.
Starting with:
- interaction-skills/dialogs.md — CDP vs JS dialog handling
- domain-skills/tiktok/upload.md — full upload flow with gotchas
- Placeholder folders for linkedin, spreadshirt, salesforce
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>