Every tool.observe moved the real cursor across the page to look for
hover-only menus and tooltips. Measured against real Chrome, that cost a
median of 2.5s and up to 4.7s per observation — 73% to 99% of total
observe wall clock — and returned 3 hits across 12 pages. A positive
control confirms the probes work; the yield on real pages simply does not
justify putting them on the default path.
observe now probes only when asked, via `probe_hover` (`--probe-hover`).
Agents that know which element hides a menu should keep using
`bsk hover <ref>`, which is both cheaper and precise. A held hover latch
still suppresses probing, since probing would move the cursor off the
element the caller is deliberately holding.
Correctness fixes that apply whether or not probing is requested:
- Hover probing ran between the DOM snapshot and the accessibility tree
fetch, so a hover that opened a menu left one half of an observation
describing the page before the change and the other half after it. Both
chains now run after all capture is complete.
- The overlay bypass toggled once per chain and swallowed restore
failures, which could leave the agent overlay hidden for the rest of
the tab's life. It is now reference counted across both chains, and a
failed restore is reported instead of discarded.
- Observations report whether they hovered the page and whether that
revealed content the static tree lacks, so callers can judge how far
the live page may have drifted from the returned snapshot.
Probe budgets were checked at the top of the loop, so a candidate could
start with 1ms remaining and still run a full settle window, overshooting
by up to 634ms. Candidates are now admitted only if they fit. Raising
MAX_HOVER_PROBE_MS to 2600 is part of that fix rather than a relaxation:
2000 was never the real ceiling, and enforcing it literally would have
silently dropped a candidate the positive control depends on.
Tooltip misses are cached too, with a shorter TTL than hits, so repeat
observations of the same page no longer re-probe every unlabelled control
from scratch.
Reject legacy JSON --output before recording starts, persist the completed Trace before writing the bundle so `bsk record stop --output <dir>` can retry after an export failure, and tell users to pass --url when https://example.com/ does not load.
The extension is now published to Edge Add-ons, but every install path
still pointed Edge users at the Chrome Web Store. Both READMEs and
AGENT_INSTALL.md now name a store per browser, and `bsk doctor` offers
both listings when no browser is connected.
Two Edge-specific gaps went with it: Edge blocks content-script injection
on its own storefront the same way Chrome does, so the borrow-confirmation
overlay must treat microsoftedge.microsoft.com/addons as non-injectable;
and the Chinese README still described the pre-install.ps1 manual Windows
setup.
Also give the DeepSeek Harness section its npm package link and the
profile-start step, so plugin users have a complete path.
Co-authored-by: Cursor <cursoragent@cursor.com>
Tool cancellation is now cooperative end-to-end: the queue stays busy until compensation completes, cancellation is checked cooperatively across the pipeline, the fallback window is compensated too, and cleanup_failed is surfaced explicitly. Includes a gated delayed-compensation E2E test.
chrome.tabs.captureVisibleTab reads back the window surface, which fails
outright on some Windows/Chrome combinations (Chromium
FAILURE_REASON_READBACK_FAILED: "Failed to capture tab: image readback
failed"), leaving full-tab screenshots without any working path.
Capture the full tab via CDP Page.captureScreenshot (fromSurface) when
the visible-tab capture rejects; the CDP path goes through the
renderer BeginFrame pipeline instead of the surface readback. When both
paths fail, return cdp_failed with data.reason=screenshot_capture_failed
carrying both underlying messages, and render a CLI hint that points at
the browser-side readback cause.
Refs #71
`bsk session start --no-focus` opens the Agent Window in the background
without stealing focus. The flag threads focused=false through
CLI -> daemon IPC -> tool.session_start -> chrome.windows.create; an
omitted `focused` keeps the extension default (true) so older clients
are unaffected.
Reworked onto current main so it composes with #52 window sizing:
AgentWindowApi.create / SessionManager.start (extension), start_session
(CLI) and the daemon's session start now take options objects/structs
({ size, focused }) instead of colliding positional params.
Co-authored-by: alectimison-maker <alec.timison@gmail.com>
* feat(emulate): add mobile device emulation via CDP Emulation domain
Add `bsk emulate` so agents can debug mobile page behaviour by
emulating a device environment on a session tab:
- Viewport metrics (Emulation.setDeviceMetricsOverride), User-Agent
(Emulation.setUserAgentOverride, optional accept-language and UA
client-hints metadata), and touch (Emulation.setTouchEmulationEnabled
with maxTouchPoints).
- Seven CLI-side device presets (iphone-14, iphone-14-pro-max,
iphone-se, pixel-7, galaxy-s23, ipad-mini, galaxy-tab-s8) via
--device; manual flags (--width/--height/--dpr/--mobile/--ua/
--accept-language/--touch/--max-touch-points) work standalone or
override individual preset fields.
- `bsk emulate --off` clears every override (clearDeviceMetricsOverride
+ touch disabled + empty UA override) and restores the real
environment.
- Overrides are per-tab (CDP per-target) and not inherited by new tabs;
the result JSON carries a note reminding agents to re-apply.
Wire: new tool.emulate method end-to-end (bsk-protocol types + JSON
schemas + Method::ToolEmulate classified BrowserMutation, daemon
forwarding, CLI subcommand, extension tool handler + ChromiumCdp
Emulation wrappers, dispatcher registration), docs in both SKILL.md
copies, and unit tests on every layer.
* refine(emulate): merge per-tab state and align preset override semantics
- extension: remember the applied emulation state per tab and merge each new request onto it field by field before applying, so a later --width/--height no longer resets an earlier preset's dpr/mobile; --off clears the stored state along with the CDP overrides
- extension: cdp_failed errors now note that already-applied overrides were not rolled back and can be reset with --off
- cli: validate width/height pairing and metric requirements on the merged preset+flags result, so --device iphone-14 --width 500 keeps the preset height
- cli: add --no-mobile/--no-touch (conflicting with --mobile/--touch) to turn preset mobile/touch fields back off
- docs: describe the merge behaviour, --no-mobile/--no-touch and --off state clearing in both SKILL.md copies
- `bsk session start --width <px> --height <px>` opens the Agent Window
with the given outer size (chrome.windows.create)
- new `bsk window resize --session <id> --width <px> --height <px>`
resizes the Agent Window at runtime (chrome.windows.update)
- both validated on CLI, daemon and extension sides (100..=7680 CSS px)
- protocol: new tool.window_resize method (BrowserMutation) and
optional width/height on tool.session_start params; schemas regenerated