v0.10.0
Added
-
Emulation.setDeviceMetricsOverrideandEmulation.setTouchEmulationEnabledare allowlisted, so a Puppeteer client's default viewport applies through the runtime's own emulation instead of failing the connect. A scale factor other than 1, a non-portrait orientation, andhasTouchare refused with the reason rather than silently ignored. -
[cdp].auto_session(default true): a connecting CDP client holdingsession:writeandpage:writewith no session open gets one, with a blank page, socontexts()[0].pages()[0]resolves on connect. CDP cannot create a session itself, so a connected client previously had nothing to drive. -
[cdp]section in the sampleconfig.toml. -
bobby doctorreports acdp-portcheck: whether the configured CDP address is serving authenticated discovery, is free, or is already owned by another process. The default 9222 is also Firefox's default remote-debugging port, so the collision is named beforebobby cdpfails on it. -
cdp.listener.readystartup log carrying the CDP discovery endpoint and WebSocket base. -
bobby tokenprints the enrolled bootstrap bearer. It refuses a redirected stdout without--stdout. -
engineUnreachableinterface error code (HTTP 503): the configured browser engine did not answer, so no session opened. Carries the browser-launch repair instead of the "fix the named argument" hint thatinvalidRequestimplies, and reaches MCP clients aserror.repair. -
bobby doctorreportsfirefox-bidi-port-mismatchas a failure when the CDP port is held by another service while an enrolled BiDi endpoint accepts nothing — the shape a companion launched on the CDP port produces, previously two unrelated warnings. -
Playwright 1.62.1's injected-script bootstrap is pinned. The gateway matches that bundle by length and digest, and only 1.61 and 1.62.0 were pinned, so every page a 1.62.1 client opened failed closed on its first locator call.
-
cdp.runtime.bootstrap_rejecteddebug log carries the length and digest of an unpinned bootstrap, which is what a new pin is cut from. No caller JavaScript is logged. -
CI runs
test:playwrightandtest:puppeteeron the chromium job. They were the only automated proof a real client can drive a page over CDP and ran nowhere. -
click.modifiers: an optional array holding at most one each ofshift,ctrl,alt, andmeta. Chromium dispatches the native pointer click with the matching modifier bits; the Firefox companion drives a tick-aligned key source that presses before the pointer sequence and releases after it. A duplicate modifier is refused withclick modifiers must be unique, and a modified click that enters automatic download capture fails withinvalidRequestrather than dropping the modifiers silently. Reaches MCPclick, theCommandEnvelope, and the TypeScript SDK'sClickCommand. -
SolveChallengeintent, opt-in: the engine loops screenshot, vision proposal, act, reassess on a 750ms poll until the model returns the newchallengeSolvedaction or the hint deadline elapses. It fails closed on provider error, below-floor confidence, a disallowed action, and a closed vision gate (VisionAssistDenied).challengeSolvedis carried across the vision wire, validation, and the collector, andcommand_execute's schema union now covers nine command kinds. -
bobby vision solvesubmits aSolveChallengeover/v1: it creates a vision-enabled session or reuses--session/--page, optionally navigates, then runs the solve loop under a caller-sized budget.--zigzagziglayers humanized input timing and fingerprint spoofing onto that session. -
Per-site challenge priors: a solve outcome promotes to
SiteContext.challenges— success and failure counters with a day-precision stamp — instead of the control schema, which has no resolved control for a solve to promote. -
BrowserFlavorfingerprint axis (Chrome default, Firefox). The companion's BiDiemulation.setUserAgentOverridesends a Gecko UA on a Gecko engine instead of a Chrome UA, and session validation gates the Chrome-only checks behind the axis. -
ScreenResolutioncarrieswindow_widthandwindow_height, so a profile presents a non-maximized window sized under the available area. -
Level 2 of the modern gauntlet drives the solve loop against a live reCAPTCHA v2 widget. It is environment-gated and outside the five release tests.
-
bobby doctorreports acompanion-portcheck: whether the enrolled profile'scompanionBindis free, already serving the Firefox companion, or held by another service. Every runtime binds its own companion server on that address, so a secondbobby serve/bobby cdp/bobby mcp-stdioon one profile cannot bind it and every Firefox session it opens fails withengineUnreachable. Doctor checked only that the address parsed, so that collision reported as an all-green run against a browser that would not start.
Changed
-
scripts/dev/firefox-start.shputs the companion profile's remote-debugging endpoint on 9224. It defaulted to 9222, the port authenticated CDP binds, so whichever of the two started second failed to bind.BOBBY_FIREFOX_DEBUG_PORTstill overrides it. -
CDP bind failures name the address, the Firefox 9222 overlap, and
--cdp-port, instead of a bareAddress already in use. -
Target.createTargetwith no runtime session states that CDP attaches to sessions rather than creating them, and names the routes that open one (POST /v1/sessions,POST /v1/pages, MCPsession_create/page_open, SDK). -
bobby doctorreportscdp-listenwhen CDP is disabled, naming the addressbobby cdpwould bind. -
The authenticated-CDP guide documents the session-and-page prerequisite, managed Chromium as the pairing-free engine, and that pages are opened through the runtime rather than by the client.
-
The authenticated-CDP guide states the surface's scope: a pinned client shim with no DOM domain and no JavaScript execution, plus a per-client operation table. It also drops the
/devtools/page/:idsocket it advertised, which was never implemented. -
/json/listreports the URL and title the gateway last verified for each page. Every entry readabout:blank/Automation Runtime, so a client could not tell one target from another. -
A rejected JSON request body names the offending field and position from serde instead of one fixed sentence. Request values are never included.
-
bobby doctordistinguishes a refused BiDi connection from a live socket speaking another protocol; a refused connection reported "another service may own the port" when nothing was listening. -
v1_request_with_limitslets a caller raise the/v1request timeout. The 10s default cannot outlive a single local-model propose round, let alone a multi-round solve. -
proposeprompts spell out the exact action JSON shapes and thesolveChallengeguidance; models were guessing the schema and returning bare strings.
Fixed
- CreepJS screen and media-query leak: Chrome launches with
--window-sizematching the spoofed screen, and each page appliesEmulation.setDeviceMetricsOverridewith the viewport at window size and the screen at full size, sohasVvpScreenResstays false. The init script also reportspdfViewerEnabled, a minimalnavigator.share/canShare, andNotification.permissiondefault, each of which headless gets wrong. Like-headless drops 25% to 19%, with headless and stealth both at 0%; the init-script budget moves 40k to 42k. - A transient vision failure — an unparseable reply or below-floor confidence — costs one solve attempt instead of the whole budget, and the loop reassesses. Only the deadline is terminal for it, and the deadline error reports the last transient failure. A disallowed action and an act failure stay terminal.
solveChallengeis click-only. A visiontypeTextcarries no resolved target, so its empty selector errored at the driver; the prompts now state the never-type constraint.- The Python vision adapter drops chatty non-action response fields, such as
reasoningand sibling text, at the edge instead of failing closed, which preserves the no-echo guarantee. snake_case action kinds (type_text,challenge_solved) canonicalize to the wire spelling.ProposeResponsedropsdeny_unknown_fieldsat the upstream edge while the action variants keep it. SolveChallengeHints::default()no longer drops the 30s timeout.- The five release binary builds:
release-binariesno longer pins a pnpm version, whichpnpm/action-setup@v6refuses whenpackageManagerdeclares one as well. The Firefox companion's static files stage fromcopy-static.mjsinstead of shell, because pnpm runs package scripts throughcmd.exeon Windows, which has neithercpnormkdir -p. Formula/bobby-browser.rbcarries the v0.9.0 asset digests.scripts/dev/firefox-start.shcompares the endpoint'sws_portafter stripping whitespace, so a reformatted endpoint file no longer reads as stale.a11y_snapshot_descends_into_iframeswaits forinput[type=file]to attach before snapshotting. The documents route builds its form in the SPA bundle, soDOMContentLoadedcan precede the input.- A companion-server bind failure names the address and the operating system error. The caller flattens the error through
to_string(), so the operator sawfailed to bind companion serverwith neither the port norAddress already in use. - A solve screenshot that fails while the page is still alive costs one attempt instead of ending the solve. A renderer crash-and-recover surfaced as a terminal capture failure mid-solve; a genuinely dead page still fails every attempt and the deadline reports it.