Skip to content

Releases: cavi-ai/bobby-browser

v0.13.0

Choose a tag to compare

@github-actions github-actions released this 04 Sep 15:52
15135c9

Added

  • bobby doctor command: read-only health checks with --json structured
    output and a next-action recommendation. Inspects command journal,
    scheduler journal, corpus, sidecar version, and store health.
  • bobby doctor --fix rotates an expired bootstrap token.
  • Firefox wait_for implements networkQuiet from the BiDi
    network.beforeRequestSent / responseCompleted / fetchError stream
    already used for HAR, with the same idle / max-in-flight / ignore filters
    as Chromium.
  • Firefox live-process reattach reconnects the BiDi websocket without
    session.new, so typed values survive a transport drop the same way a
    Chromium CDP reattach does.
  • Rust SDK: intent envelope builders (locate_envelope,
    fill_envelope, submit_and_verify_envelope, etc.) mirror the
    TypeScript SDK one-to-one, with purpose validation and unique-name
    enforcement.
  • Rust SDK: client methods for the remaining /v1 surface β€”
    form_snapshot, checkpoint, recovery_status, recover, and
    artifact with full reference verification (media-type, Content-Length,
    byte cap, SHA-256 digest).
  • MCP boundary-once guard refuses a second intent_submit_and_verify
    against a workflow whose Boundary submit already completed, keyed on
    (workflow, control identity). reSubmit: true is the explicit
    acknowledgment path.
  • Agent benchmark gate enforces per-task token and call budgets from
    baseline.json and aggregates multi-run batches (every run must pass;
    mean wall/tokens face thresholds).
  • Vision corpus: harvest wrong-pick validation and split abstain recall
    floors between production negatives (floor 1.0) and singleton research
    classes (floor 0.5).
  • Real-sites probe harness with wall-discrimination pairs (LinkedIn,
    Reddit) and expanded purpose table.

Changed

  • RuntimeService::navigate reports the command error message, not Debug
    of the whole outcome.
  • element_at_point defaults to unsupported (browserCommandFailed), not
    Ok(None).
  • MCP extract default value and handle examples are derived and formatted
    correctly in tool schemas.
  • Advertised command-outcome schemas are no longer opaque β€” field-level
    detail is visible in the schema.
  • MCP workflow handles are initialized before the first tool dispatch,
    fixing the handle-first loop.

Fixed

  • ExpectedStatePreSatisfied pre-check widens to 2s (from 750ms),
    outliving the SPA render race under concurrent browser instances.
  • boundary-once ledger keys per control identity, not per workflow β€” a
    workflow with two distinct Boundary submits (search + save) no longer
    false-positives.
  • Hints-less boundary submits are truly fail-open: no key, no ledger
    entry, no collision between unnamed controls.
  • Candidate census survives exotic elements that serve non-string
    innerText/value.
  • Real-sites purpose expansion and singleton recall split landed (missed
    in prior branch switch).
  • Rust SDK artifact() verifies bodies against references with typed
    protocol validation β€” the verification delta that missed #414 is landed.
  • Firefox: leaked BiDi session is recycled when session.new hits
    "Maximum number of active sessions"; the factory detects the slot,
    recycles the enrolled profile, and retries.
  • Firefox: typed-value evidence returns correct evidence and click bounds
    are validated.
  • Firefox: CDP inspect with a text-only target resolves via page text
    matching instead of failing.
  • Firefox: companion attach without targetsDiscovered β€” tab selection
    falls back when the event is absent.
  • Firefox: companion attach without Pair-less host leak β€” native transport
    cleans up on disconnect.
  • Firefox: document title is read correctly after the MCP handle-first
    loop fix.
  • CDP: same-document hash-link click dispatches correctly instead of
    waiting for a cross-document navigation.
  • CDP: /json/list provisions an auto-session when the gateway has none,
    so Playwright and other clients that discover targets before connecting
    find a usable target.
  • CDP: page title updates after navigate instead of returning the
    pre-navigation value.
  • CDP: Runtime.evaluate is refused after navigate when the execution
    context is stale, instead of hanging.
  • CDP: Target.createBrowserContext is honestly refused as unsupported
    instead of silently failing.
  • SDK: deterministic deadline in elapsed-deadline waiter test β€” context
    deadline widened to 2s so a loaded runner cannot stretch past it.
  • Formula/bobby-browser.rb sha256 digests updated for v0.12.0 release
    assets.

v0.12.0

Choose a tag to compare

@github-actions github-actions released this 31 Aug 18:37
30615d7

Added

  • Captcha and human-verification challenges are reachable over MCP: two new
    tools, intent_detect_challenge (Replayable, read-only, advertised in
    explore so a stuck agent can name its blocker without a phase switch) and
    intent_solve_challenge (Reconciliable, drives the vision solve loop until
    cleared or timeoutMs). Both gate on browser:mutate + intent:execute +
    vision:assist up front, so a principal without vision is refused at the
    gate rather than the engine. bobby://intents now documents ten intents,
    including the captcha path.
  • DetectChallenge intent (Replayable): classifies a challenge without acting
    on the page, returning a typed challengeDetection β€” type, confidence,
    blocking, optional region β€” or a provably clean page as a first-class answer.
    A known prior kind enriches the prompt and is reported for transparency
    without blending into the answer. CLI: bobby vision detect.
  • SolveChallenge tactic at rung 4 of the ZigZagZig recovery ladder, after the
    read-only tactics and before any checkpoint-bearing or session-replacing one.
    It runs the vision solve loop in place with the session's proven gate, then
    re-checks the original postcondition, so a solve that did not move the page
    counts as a climb, not a success. A session without vision assist declines
    the rung fail-closed.
  • zigzagzig (godmode) sessions: session_create and workflow_start accept
    zigzagzig: true, forcing fingerprint and humanize server-side and routing
    page-bound commands through the recovery ladder. The creation gate stands in
    for browser:fingerprint + browser:humanize, refusing a principal missing
    either before any session materializes, and the flag is hidden from clients
    that lack them.
  • The ladder's solve rung runs detection first: a provably clean page skips the
    solve budget, and a typed detection narrows the solve prompt and feeds its
    region into the hints. detect_challenge now appears in metrics snapshots.
  • TypeScript SDK: detectChallengeRuntimeCommand /
    solveChallengeRuntimeCommand and the detectChallengeEnvelope /
    solveChallengeEnvelope helpers emit the canonical wire shape, with
    DetectChallengeIntent / SolveChallengeIntent / SolveChallengeHints
    contracts and the DEFAULT_DETECT_CHALLENGE_TIMEOUT_MS (15s) /
    DEFAULT_SOLVE_CHALLENGE_TIMEOUT_MS (30s) constants.
  • A worker reattaches to a live browser process when the CDP transport dies,
    preserving page state β€” typed values, scroll, cookies β€” instead of a
    destructive relaunch; the executor tries transport-reattach before relaunch.
  • intent_submit_and_verify with a networkQuiet postcondition returns a
    submitSettlement classification and value-free formValidation repair
    evidence, so callers can distinguish a settled submission from client-side
    rejection without repeating the boundary click.
  • The agent benchmark records separate Bobby, host, discovery, bookkeeping,
    and shell call counts together with reproducibility fingerprints for the
    source tree, task set, runner set, binary, CLI, and requested model.

Changed

  • The bundled config.toml sets the MLX vision provider to
    mlx-community/Qwen3.5-27B-4bit, matching the default already shipped for
    bobby install / bobby vision connect in 0.11.0 (the file still pinned the
    superseded Qwen2.5-VL-3B), and adds a commented [vision.providers.ollama]
    example.
  • form_snapshot output omits default-valued fields: an untouched control
    drops from 741 to 422 bytes, and workflow_observe(includeForms) no longer
    re-sends redundant defaults on every observation. The change is output-only β€”
    old payloads still deserialize, the form_control output schema requires
    only id, controlKind, state, validity, and supportedOperations, and
    TypeScript SDK contracts make the slimmed fields optional.
  • The targetDetached failure taxonomy separates whole-browser transport loss
    (reattach preserves state, relaunch wipes it) from a stale element, and
    notFound documents the "browser page is not open" shape.
  • The default MCP explore phase includes intent_complete_form and
    intent_submit_and_verify. Initialization guidance tells deferred-schema
    clients to load those tools with workflow_start and workflow_observe for
    the standard form loop.
  • Evidence::ExecutionPath.path names the strategy, not an engine: chromium
    and chromiumFallback are now browser and browserFallback. The old names
    still deserialize so recorded journals replay unchanged.
  • Successful intent_complete_form responses default to compact evidence;
    pass evidenceDetail: "full" to retain the complete per-field success
    evidence. Failure evidence and newly revealed conditional controls remain
    available in compact mode.
  • Download savedTo evidence echoes the exact caller-supplied destination
    after downloads-root policy validation. An invalid maxBytes request is now
    rejected as invalidRequest with the configured range.
  • The agent benchmark gate requires a complete, internally consistent latest
    batch and rejects missing or mismatched transcript-derived model identity.

Fixed

  • A successful reattach replay no longer falls through to the failure path: a
    transport reset that reattached and replayed used to still report the
    original dead-browser error. A reattach never relaunches the browser.
  • submit_and_verify with a descriptive purpose and no explicit targeting
    hints resolves the submit control by purpose β€” disambiguating among
    actionable candidates and, failing that, a unique submit-typed control β€”
    instead of requiring the accessible name to equal the purpose string. An
    unresolved purpose stays ambiguous rather than firing on a guess.
  • A fill intent that cannot find its target escalates with a real candidate
    window and abstains cleanly β€” recorded, below floor β€” instead of erroring at
    the vision proxy with a 502; an act-time incompatible pick still fails closed.
  • completeForm resolves every ordered field against current page state, so a
    field placed after its revealer can be completed in the same intent.
  • A visible aria-invalid="true" control rejects network-quiet settlement even
    when its accessible name is absent or it falls outside the bounded form
    snapshot; the exactly-once submit is never misreported as settled.
  • A Firefox profile relaunched on a different BiDi port is reachable again: the
    enrolled endpoint is a snapshot, so a refused connection now retries against
    the profile's own WebDriverBiDiServer.json.
  • The stdio gateway ends the shared Firefox BiDi session on exit, preventing a
    stale session from blocking later launches with Maximum number of active sessions.
  • Browser-launch failures expose their allowlisted cause with an environment-shaped
    repair even when the runtime prefixes the diagnostic.
  • scripts/dev/firefox-start.sh accepts pretty-printed endpoint files and a
    macOS-relaunched Firefox whose listening process has a different pid.
  • Formula/bobby-browser.rb carries the v0.11.1 asset digests.

v0.11.1

Choose a tag to compare

@github-actions github-actions released this 22 Aug 19:15
97489a5

Fixed

  • Product documentation navigation includes every shipped page, and producer verification now rejects pages omitted from navigation.
  • Formula/bobby-browser.rb carries the v0.11.0 asset digests.

v0.11.0

Choose a tag to compare

@github-actions github-actions released this 20 Aug 20:53
8e0f14e

Breaking

  • Unified control action vocabulary: FillValue is removed. fill and completeForm intents now use a single ControlAction enum shared with the control_action MCP tool. Old fill vocabulary replaced with new unified kinds:
old (fill) new
{"kind":"text","text":X,"clearFirst":false} {"kind":"setText","value":X,"clearFirst":false} (clearFirst now defaults true = replace; pass false to append)
{"kind":"select","option":X} {"kind":"selectOne","value":X}
{"kind":"checked","checked":X} {"kind":"setChecked","checked":X}
{"kind":"files","paths":X} {"kind":"setFiles","paths":X}
  • New operations available in fill: selectMany (multi-select), clear (clear field).
  • activate is rejected in fill; it remains control_action-only.
  • control_action setText accepts clearFirst (default true, replace behavior; unchanged from prior hard-coded behavior in worker-pool and firefox-companion).
  • type_text remains unchanged; its clearFirst still defaults false.
  • Interface version bumped to 2026-08-19; HTTP clients must send the new x-interface-version.

Added

  • Qwen3.5-27B-4bit (mlx-community/Qwen3.5-27B-4bit) is the MLX vision default in vision-proxy, bobby vision connect, and the bobby install model list, where it is the recommended first choice. The previous Qwen2.5-VL-3B default could not drive the solve loop.
  • VISION_COORD_SPACE=normalized|absolute overrides the mlx-vlm provider's per-model coordinate-space detection.
  • network_log reports networkRecordingStarted on the call that attaches the collector, and its description states that recording begins at the first call on a page. A first call previously returned an empty HAR with nothing naming the cause.
  • bobby doctor's companion-port check names the listening process's pid and command, read from lsof under a 2s cap on unix. It is omitted rather than blocking on any lookup failure or non-unix platform.
  • click carries dialogOpened evidence when alert/confirm/prompt opens during the click.
  • type_text carries typedControlKind evidence, which verification uses to accept a checkable's or a select's post-state.
  • form_snapshot collects [role=button] elements and names a button from its own text when it carries no label, so buttons are targetable straight from the snapshot.
  • The DOM candidate collector maps implicit roles for headings, lists, list items, images, tables, rows, and cells, so those elements resolve by role with no explicit role attribute.

Changed

  • Interface error messages carry the allowlisted diagnostic and the repair action, capped at 1024 bytes with the diagnostic truncated first so the repair action survives intact. A non-allowlisted raw message is still never included.
  • The candidate limit applies to the matching set after filtering instead of every gathered candidate, and an explicit ordinal skips the bound because it picks one match deterministically. A large page no longer fails a target that matches once.
  • type_text splits its value into runs the US keyboard layout can key-press and runs that need a caret-level Input.insertText, so Unicode outside that layout and newlines are typed rather than key-pressed.
  • The browser-launch repair and the engineUnreachable diagnostic name the Firefox companion bind (default 127.0.0.1:9876) and the other runtime that may hold it, alongside the BiDi endpoint.
  • visionAssistDenied leads with the deterministic stuck reason (targetNotFound, targetAmbiguous, obstructionSuspected) and then names the closed gate. The code is unchanged; the message read as a policy wall to an agent that never asked for vision.
  • A duplicate in-flight JSON-RPC request id is rejected with a diagnostic and a repair instead of a bare Invalid Request.
  • The advertised executionPath.reason enum carries a description: ineligibleCommand means the command class runs in the browser and is not a failure.
  • Every failing command outcome is logged at WARN with command, session, page, outcome, code, retryable, and message.
  • The targetNotFound re-collect loop backs off 25, 50, 100, 200, 400 ms, capped at 500 ms, instead of re-scanning a large page every 25 ms for the whole deadline.
  • A page opened by page_open or an agent workflow reports its navigated URL and an interactive ready state, and is listed on its session, instead of the pre-navigation blank state.

Fixed

  • A click that opens alert/confirm/prompt returns instead of hanging. The renderer blocks while the dialog is up, so the click's own CDP round trip may never answer; the click races a per-page dialog listener and reports dialogOpened. dialog consumes a dialog that opened before it was called rather than waiting on a future event that already fired.
  • click and keyboard dispatch bring the target page to front first. On a background target in headless Chrome the click path never resolved. A failed raise is logged and the dispatch proceeds.
  • type_text against a non-editable target β€” disabled, readonly, a fieldset[disabled] ancestor, a non-typeable input type, or a non-form element β€” fails with invalidRequest instead of dispatching key events, which pinned headless Chrome at 100% CPU on macOS.
  • A screenshot clip is bounded before it reaches CDP: a negative origin, a non-finite value, or a dimension over max_screenshot_dimension is refused with invalidRequest. An unbounded clip could kill the browser process.
  • a11y_snapshot reserves a node's budget slot before descending into its children, so a deep subtree of leaves cannot consume the whole budget and return zero nodes.
  • Page operations clone a browser handle and release the worker mutex instead of holding it across a CDP await, so one page's slow or hung call no longer serializes or stalls every other page in the session.
  • A dead browser target rewritten by the targeting layer triggers the same revive path as a raw CDP receiver-is-gone error, instead of wedging the page.
  • A navigation the browser aborted (net::ERR_ABORTED β€” a download response or a cancelled navigation) fails as non-retryable and points at download_url and click_and_wait_for_download, instead of advising a retry that repeats the abort. The frame's navigation slot is freed.
  • Driver failures are retryable by code: a malformed request, a target that is not there, and a policy refusal are not retried; transport, launch, internal, and timeout codes are.
  • httpResponseTooLarge is a plain failed rather than needsReconciliation. The body is dropped mid-stream with nothing written, so no effect can have landed.
  • An iframe hop resolves by DOM identity β€” DOM.getFrameOwner's backend node id against the candidate's own β€” instead of name and src, so unnamed sibling iframes are told apart. An unnamed hop's stamped target validates and resolves, so a snapshot target passes back verbatim.
  • Role matching treats img and image as one role. Chrome's a11y tree emits image while the DOM collector emits img, so a snapshot role fed back as a target resolves either way.
  • Accessible names are trimmed on both sides of the comparison, so a name carrying surrounding whitespace matches.
  • type_text verification accepts an append (clearFirst: false), a checkable's checked state, and a select's committed option, instead of requiring the post-action read to equal the typed value exactly.
  • The mlx-vlm provider rescales normalized [0, 1000) click coordinates onto the screenshot frame for the families that emit them (Qwen3-VL, Qwen3.5), and unwraps list-typed coordinates such as {"x": [566]} and {"coordinate": [[x, y]]}.
  • The mlx-vlm provider builds a system+user chat template when the processor supports it. The single-message template made Qwen3.5 emit a click with no y key.
  • Formula/bobby-browser.rb carries the v0.10.0 asset digests.

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 19 Aug 00:12
2216fce

Added

  • Emulation.setDeviceMetricsOverride and Emulation.setTouchEmulationEnabled are 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, and hasTouch are refused with the reason rather than silently ignored.

  • [cdp].auto_session (default true): a connecting CDP client holding session:write and page:write with no session open gets one, with a blank page, so contexts()[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 sample config.toml.

  • bobby doctor reports a cdp-port check: 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 before bobby cdp fails on it.

  • cdp.listener.ready startup log carrying the CDP discovery endpoint and WebSocket base.

  • bobby token prints the enrolled bootstrap bearer. It refuses a redirected stdout without --stdout.

  • engineUnreachable interface 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 that invalidRequest implies, and reaches MCP clients as error.repair.

  • bobby doctor reports firefox-bidi-port-mismatch as 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_rejected debug 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:playwright and test:puppeteer on 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 of shift, ctrl, alt, and meta. 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 with click modifiers must be unique, and a modified click that enters automatic download capture fails with invalidRequest rather than dropping the modifiers silently. Reaches MCP click, the CommandEnvelope, and the TypeScript SDK's ClickCommand.

  • SolveChallenge intent, opt-in: the engine loops screenshot, vision proposal, act, reassess on a 750ms poll until the model returns the new challengeSolved action or the hint deadline elapses. It fails closed on provider error, below-floor confidence, a disallowed action, and a closed vision gate (VisionAssistDenied). challengeSolved is carried across the vision wire, validation, and the collector, and command_execute's schema union now covers nine command kinds.

  • bobby vision solve submits a SolveChallenge over /v1: it creates a vision-enabled session or reuses --session/--page, optionally navigates, then runs the solve loop under a caller-sized budget. --zigzagzig layers 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.

  • BrowserFlavor fingerprint axis (Chrome default, Firefox). The companion's BiDi emulation.setUserAgentOverride sends a Gecko UA on a Gecko engine instead of a Chrome UA, and session validation gates the Chrome-only checks behind the axis.

  • ScreenResolution carries window_width and window_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 doctor reports a companion-port check: whether the enrolled profile's companionBind is free, already serving the Firefox companion, or held by another service. Every runtime binds its own companion server on that address, so a second bobby serve / bobby cdp / bobby mcp-stdio on one profile cannot bind it and every Firefox session it opens fails with engineUnreachable. 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.sh puts 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_PORT still overrides it.

  • CDP bind failures name the address, the Firefox 9222 overlap, and --cdp-port, instead of a bare Address already in use.

  • Target.createTarget with 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, MCP session_create/page_open, SDK).

  • bobby doctor reports cdp-listen when CDP is disabled, naming the address bobby cdp would 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/:id socket it advertised, which was never implemented.

  • /json/list reports the URL and title the gateway last verified for each page. Every entry read about: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 doctor distinguishes 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_limits lets a caller raise the /v1 request timeout. The 10s default cannot outlive a single local-model propose round, let alone a multi-round solve.

  • propose prompts spell out the exact action JSON shapes and the solveChallenge guidance; models were guessing the schema and returning bare strings.

Fixed

  • CreepJS screen and media-query leak: Chrome launches with --window-size matching the spoofed screen, and each page applies Emulation.setDeviceMetricsOverride with the viewport at window size and the screen at full size, so hasVvpScreenRes stays false. The init script also reports pdfViewerEnabled, a minimal navigator.share/canShare, and Notification.permission default, 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.
  • solveChallenge is click-only. A vision typeText carries 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 reasoning and 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. ProposeResponse drops deny_unknown_fields at the upstream edge while the action variants keep it.
  • SolveChallengeHints::default() no longer drops the 30s timeout.
  • The five release binary builds: release-binaries no longer pins a pnpm version, which pnpm/action-setup@v6 refuses when packageManager declares one as well. The Firefox companion's static files stage from copy-static.mjs instead of shell, because pnpm runs package scripts through cmd.exe on Windows, which has neither cp nor mkdir -p.
  • Formula/bobby-browser.rb carries the v0.9.0 asset digests.
  • scripts/dev/firefox-start.sh compares the endpoint's ws_port after stripping whitespace, so a reformatted endpoint file no longer reads as stale.
  • a11y_snapshot_descends_into_iframes waits for input[type=file] to attach before snapshotting. The documents route builds its form in the SPA bundle, so DOMContentLoaded can 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 saw failed to bind companion server with neither the port nor Address 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.

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 13 Aug 02:38
ab1d359

Added

  • Runtime operational metrics on runtime_info: bounded intent-resolution, context-lookup, prefill, vision-provider, verification, retry, reconciliation, and admitted MCP-call counters, carrying no request content and no typed values. instrument_vision_assist wraps a provider so the vision boundary reports its own counters and provider mode.
  • control_action returns the controls a form revealed. An action that changes the form diffs the before/after form snapshots and reports each newly appeared control's kind, accessible name, and a verbatim-passable target on the action evidence, on managed Chromium and the Firefox companion both.
  • intent_submit_and_verify refuses a pre-satisfied expectedState. A text, element, or value condition that already holds before the boundary click fails with expectedStatePreSatisfied and never clicks; url, document, and networkQuiet conditions legitimately pre-hold and skip the check. The code is wired through the contract test, the advertised enum, the failure taxonomy, and the repair hint.
  • Candidate-grounded fill and extraction actions are index-only: typed values remain inside the runtime, and extraction reads the value from the selected DOM candidate.
  • Evidence::Download carries savedTo, the file's name below the configured downloads root, across the direct-HTTP, Chromium, and Firefox paths. Journaled and durable-prepared records strip it, so the no-absolute-paths rule for durable state holds.
  • HINT: role=<role> row in BOBBY-VISION/1 prompts, emitted from the role LocateIntent.hints already carries. Thinking is suppressed at serve for v1 so the instruct template's empty <think> wrapper does not wrap the bare index, and _parse_index tolerates that wrapper for adapters trained on thinking-enabled templates.
  • Response prefill in the mlx-vlm provider: propose prefills the JSON skeleton onto the assistant turn so completions carry coordinates instead of fenced blocks or a bare action name. Abstain kinds normalize to a zero-coordinate click, whose low confidence fails the runtime floor.
  • clickCandidate action and v1 adapter serving, trap-mode corpus growth, and an out-of-sample corpus split for adapter-vs-base generalization evals.
  • Validation weighting in persisted context recall. ask() breaks match-ladder ties by a control's validation record β€” ln-diminished success boost, failure drag, 30-day half-life recency with a half-weight floor. The name-match ladder is untouched, so a fuzzy match can never outrank an exact one.
  • bobby doctor reports the configured vision timeout. Modern-gauntlet scorecards carry provider, model, and source dimensions, action counts, fixed failure categories, and engine/provider-separated output.

Changed

  • initialize negotiates the MCP protocol revision instead of requiring the newest. The gateway answers with the client's revision when it speaks it β€” 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05 β€” and otherwise with the newest. A non-matching revision no longer fails with -32602, which made the gateway unreachable from any host pinned to an older revision.
  • The Firefox companion server binds and publishes its descriptor at bobby serve startup, so a paired extension discovers it whenever it polls rather than only inside a per-session 30s window. Serve shutdown ends the shared BiDi connection.
  • workflow_observe accepts a target, forwarded to the underlying snapshot, so an observation reads one region instead of the whole page's chrome on every call.
  • The DOM candidate collector roles ARTICLE elements, so a11y_snapshot target scoping resolves article subtrees.
  • The competitor gauntlet no longer pins the full toolset: the benchmark measures the default explore phase a user actually gets.
  • Runtime error detail reaches the operator interface.

Fixed

  • a11y_snapshot scoped to an iframe element returns the frame's content. It detects the frame owner through DOM.describeNode and returns the content frame's tree with hop-stamped targets, instead of the empty main-frame iframe node.
  • A command that fails because the browser process died revives once instead of wedging the session at a permanent browser page is not open: the dead worker retires, a fresh browser launches, the page reopens at its last URL, replayable commands retry transparently, and mutating commands fail with an explicit revival note. The CDP event stream's end is logged with session, worker, and transport detail.
  • RUST_LOG output from the stdio gateway goes to stderr, leaving stdout as the protocol channel; mcp-stdio previously emitted no logs at all.
  • The a11y tree walk in the Firefox companion catches per node, so a hostile DOM node is skipped instead of failing the whole snapshot with content action failed.
  • Vision abstention fails closed.
  • record_escalation skips empty-candidate escalations, which mislabelled gather failure as model judgment. The collection harness fails when BOBBY_GAUNTLET_VISION_ENDPOINT is unset rather than passing green with zero rows, and the vague-locate sweep reopens a dead page so every purpose reaches the model.
  • Onboarding regression checks are hardened, and the release gates compile on Windows.
  • Firefox install finishes before vision readiness is reported.

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 10 Aug 19:31
2942606

Added

  • Vision assist. bobby install configures a vision provider during onboarding, and bobby vision connect --provider {openai,ollama,mlx} writes the provider config. vision-proxy takes --upstream {openai,ollama,mlx}, a single --vision-base-url with per-upstream defaults, an optional --model, and --spawn-server to run the local vision server as a managed kill-on-drop child (--server-script or BOBBY_VISION_SERVER_SCRIPT, else auto-detected).
  • BOBBY-VISION/1 wire contract for propose and extract, with a canonical provider interface and local mlx-vlm, Ollama, and LM Studio backends. Responses are normalized across {x,y}, coordinate arrays, position objects, and bare action names, degrading to a valid click rather than failing.
  • Vision escalation corpus collection behind [vision] corpusDir: a JSONL sink recording raw action kinds, target index, and the outcome stage (visionFallback, visionRejectionFloor, visionActFailed) at each terminal escalation branch. element_at_point on managed Chromium reads the internal DOM channel, never the policy-gated evaluate_javascript path.
  • NVIDIA OpenShell host. bobby install --host openshell / bobby openshell install writes an openshell/ pack (MCP Streamable HTTP client config, protocol: mcp policy sample, merge-only policy-network.yaml, skill, README). bobby openshell provision|revoke --sandbox <id> mints or revokes one agent-scoped principal per sandbox and writes a 0600 injection env under the OS config dir. bobby init --emit openshell prints the MCP fragment.
  • bobby openshell list|status|rotate, with non-secret .status.json sidecars. bobby doctor reports openshell-pack and checks openshell-admin, openshell-companion, openshell-mcp-url, and openshell-sandboxes when a pack is present; it warns on β‰₯2 local sandboxes sharing one Firefox companion, on a non-loopback cleartext MCP URL or server.host, and when an older pack lacks the deny rules. Secrets root overridable via BOBBY_OPENSHELL_SECRETS_DIR.
  • a11y_snapshot accepts an optional target and returns just that subtree instead of the whole page, resolving through frame hops and reading the owning frame's AX tree. The DOM candidate collector roles forms, dialogs, main, nav, and labelled regions, so containers are resolvable as scope roots.
  • Firefox companion wait_for supports Text, Value, and Document conditions, matching Chromium. networkQuiet remains unsupported on Firefox.

Changed

  • The default explore phase advertises the standard working loop β€” click, click_and_wait_for_popup, type_text, control_action, upload_files, dialog, download_url β€” with full schemas, so a client pays no toolset_select and schema-discovery round trip before its first action. command_execute, evaluate_javascript, emulate, niche mutations, and the intent family stay phase-gated.
  • control_action targets require only role and accessibleName; ordinal, framePath, and shadowPath default, so an a11y or form snapshot target passes verbatim instead of being hand-expanded. Target role matching is case-insensitive on both engines, and an explicit ordinal: 0 matches an omitted ordinal.
  • control_action selectOne/selectMany and select fills accept an option's visible label as well as its value (trimmed, case-insensitive fallback on both engines). Snapshots surface labels; verification compares the committed option values.
  • Intent resolution and a11y_snapshot descend one level into same-process iframes on managed Chromium: a main-frame target inside a frame resolves and acts, with each in-frame candidate stamped with a re-resolvable frame hop used when no explicit framePath was named. Capped at 8 frames per gather under a shared node budget; frames with no stable address (no id, test id, or src) are skipped.
  • Whole-page inspect after a mutating command reads the live DOM instead of refetching the URL over HTTP. Any non-read-only command taints the page and navigation clears it, so post-submit SPA state is visible rather than the app shell. Evidence carries executionPath.reason: pageMutated on the live read.
  • Page-scoped text waits (role: main|RootWebArea|… or css: body|html|:root) read live document.body.innerText via evaluate, with empty optional fields treated as absent, so async confirmations match what a whole-page inspect sees.
  • click_and_wait_for_popup defaults autoCheckpoint=true and accepts pinned commandId/attemptId, matching boundary click and intent_submit_and_verify. It registers window.open targets, and page_list syncs untracked page targets into the session (one browser per session), excluding chrome:// browser chrome, so authorization popups are drivable.
  • A plain click on an anchor with a download attribute routes through the armed download capture on managed Chromium: the file lands in the session's downloads with Download evidence instead of completing with nothing materialized.
  • workflow_start failures carry detail with the error code and message instead of a bare reason.
  • The advertised WaitCondition schema names every kind tag, required field, and enum instead of presenting an opaque object.
  • a11y_snapshot drops InlineTextBox leaves, which duplicated their StaticText parents' text and dominated the payload. Its description points at toolset_select for the phases hidden by the default explore phase.
  • OpenShell provision revokes any prior principal for the sandbox id before minting, uses a unique idempotency key per attempt, and rolls back the minted principal if writing the injection env fails. The default capability floor is the narrow openshell preset (--capabilities-preset agent for the full agent floor). The sample policy denies evaluate_javascript and job_* at the OpenShell proxy and raises MCP max_body_bytes to 262 KiB.
  • bobby://intents documents the framePath step shape with an example and the Firefox exact-CSS/test-id hop requirement.
  • The OpenShell and jobs CLI paths share one blocking /v1 HTTP client for bearer and interface headers.

Fixed

  • page_open on a session whose browser died invalidates that specific dead worker and retries once on a fresh one, instead of returning an opaque internal on every call. Concurrent recovery cannot discard another caller's healthy replacement, and a failed replacement launch no longer leaves a phantom registered page.
  • session_close no longer wedges on a dead browser: managed-Chromium teardown treats an already-gone browser (closed channel, canceled oneshot) as closed instead of failing the release, which previously left the session listed forever with every retry failing internal. Browser termination against an already-dead process is likewise tolerated.
  • Managed Chromium re-attaches dead page handles: after a renderer crash or target hiccup closes the handle's channel, the next command on that page re-attaches to the live target. A truly destroyed target unregisters the page so callers get a clean notFound instead of a dead handle. Page lifecycle state stays consistent across dispatch, and a wait whose target detaches mid-flight is accepted rather than surfaced as a driver error.
  • CDP oneshot canceled and dead-target loss map to targetDetached (retryable). Stale CDP node ids after a re-render map to targetNotFound with fresh-snapshot repair instead of a raw browserCommandFailed.
  • Boundary commands that fail with waitConditionTimedOut or verificationFailed stay failed for inspect-then-adjust instead of never-retry needsReconciliation. A command that fails before reaching the browser, on argument or target-resolution errors, reports a plain failed; reconciliation is reserved for effects that may have landed.
  • An intent post-state wait that times out after the boundary click landed is reported as a non-retryable verificationFailed stating the click landed, instead of a bare waitConditionTimedOut that invited a duplicate submit.
  • intent_submit_and_verify with a networkQuiet-only wait fails when [aria-invalid=true] markers remain, instead of reporting completed on a soft settle after a rejected submit.
  • intent action_target preserves framePath and shadowPath from the intent target, so iframe submits no longer discard the frame hop. Document targeting falls back without losing a named intent target, and locate NotFound attaches the near-miss candidate set to the stuck report instead of an empty list.
  • Whole-page inspect over direct HTTP treats an empty-<body> SPA shell (title/meta chrome plus scripts) as javascriptRequired and falls back to the live browser instead of returning shell HTML. The shell probe no longer races client-side URL rewrites.
  • inspect denied by network policy (loopback page, non-http URL) degrades to the browser that already has the page open instead of failing a DOM read with networkPolicyDenied. download_url keeps the hard denial.
  • [http] accepts partial overrides: missing fields fall back to defaults instead of failing TOML parse, so a host that sets only allow_loopback no longer bricks MCP startup.
  • bobby doctor passes BOBBY_BROWSER_CONFIG into the MCP handshake child, so [mcp] startup_toolset and the rest of that file apply to tools/list.
  • networkPolicyDenied guidance names the loopback and private-destination cause and the http.allow_loopback / http.allow_private_network operator switches across the repair hint, the taxonomy, and the download_url description; for page-offered files it points at clicking the link.
  • upload_files policy errors name the resolved absolute roots and the gateway working directory that relative roots resolve against.
  • Empty-string target fields (css, role, accessibleName, …) are rejected as invalidRequest at resolution time on both engines, instead of polling unmatchable until a wait deadline.
  • Protocol-lay...
Read more

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 07 Aug 17:11
70dcc02
  • Breaking (MCP surface): tools/list now defaults to the explore phase instead of the full surface. An existing client that connects and does not call toolset_select sees the read/snapshot/navigate lifecycle only β€” no mutation, intent, checkpoint, or command_execute tools. [mcp] startup_toolset, overridden by BOBBY_MCP_TOOLSET, selects the phase at connect: explore (default), act, intent, verify, full. The first tools/list is ~42 KiB on explore against 128 KiB on full. Capability gates are unchanged and remain the only enforcement boundary; hidden tools stay callable.
  • Breaking (bootstrap): default bobby init / bobby install / loopback auto-init mint the agent preset (no authority:admin). Use --preset unrestricted for the operator floor. Marker-less existing bootstrap.env files still heal as unrestricted. bobby doctor reports bootstrap-preset.
  • MCP adds workflow_start and workflow_observe in every toolset phase, with checkpoint_save also advertised in Intent. Handles substitute only the documented page-work scope, remain capability-checked, and expire on accepted reinitialize/server-generation change; explicit IDs remain compatible for lifecycle and recovery.
  • Workflow handle state is bounded to 64 committed LRU bindings plus 64 concurrent reservations. Starts reconcile sessions closed through other interfaces; successful close calls reclaim local bindings, while externally closed pages return ordinary notFound until LRU reclamation.
  • Streamable HTTP logical clients using the same authenticated principal share one cached MCP server lifecycle and generation. An accepted initialize resets their shared handles and requires a fresh initialized notification; distinct principals remain isolated.
  • MCP initialize returns short instructions: explore startup phase, toolset_select + re-list, error.repair, autoCheckpoint default, bobby:// recovery docs.
  • tools/list advertise-only output collapse for recovery_status, page_open, session_create, session_list, and checkpoint_save (opaque / top-level keys; validation schemas unchanged). Full catalog ~80.9 KiB / 128 KiB (~49 KiB headroom); explore ~25.2 KiB.
  • MCP failures carry a machine-readable repair hint: command-layer failures set error.repair, RPC-layer rejections set error.data.repair, each {action, doc} pointing into bobby://failure-taxonomy. A needsReconciliation outcome always carries the never-retry repair, whatever its error code.
  • http_wait accepts optional contains (and maxBodyBytes): each attempt becomes http_fetch and succeeds only when the truncated body includes the substring β€” for readiness gates that return 200 before they are ready.
  • runtime_info's capabilities list reports vision wiring: vision-assist and vision-provider appear only when configured, so an agent can tell an unconfigured provider apart from a transient vision failure without shell access.
  • tools/list advertise-only trim: the constant $schema URL is dropped from advertised input and output schemas, and workflow_recover's RecoveryDecision is advertised as a status-tag projection (the same treatment Evidence already had). Validation schemas and tools/call are unchanged.
  • The tool_schema_sizes example prints the per-tool composition (description / input / output / annotations / examples), so future growth is attributable at a glance.
  • The Northstar scenario server is extracted from runtime-tests into a reusable gauntlet-server crate. It serves GET /__gauntlet/snapshot and GET /__gauntlet/request-log (the same state the in-process snapshot() / request_log() expose), and ships a gauntlet-server binary (--seed, --level) so out-of-process drivers can run and verify journeys over HTTP. The five release-gate journeys are unchanged.
  • benchmarks/competitor-gauntlet/ is a benchmark harness that runs the five Northstar journeys against alternative agent browser tooling with a headless agent driver, recording wall time, tool calls, error counts, token usage, server-authoritative pass/fail, and a structured agent self-report per run. Results append to benchmarks/results/runs.jsonl (gitignored); score aggregates per tool.
  • MCP job_submit / job_status / job_cancel mirror HTTP /v1/jobs (same caps). Advertised in full, act, and verify when a job port is attached (bobby mcp-stdio and bobby serve MCP HTTP). Built-in handlers: echo, sleep, http_probe, http_wait, and http_fetch (SSRF-safe; http_fetch returns a truncated GET body so agents need not open a browser for health/API JSON); bobby://job-handlers documents payloads; bobby doctor reports them under job-handlers.
  • Ollama joins the direct vision backends. bobby vision-proxy --ollama --ollama-base-url and bobby vision connect --provider ollama normalize a local model's output to the VisionProposal schema, and a provider on port 11434 is detected from config. No credentials leave the machine.
  • bobby vision collect gathers gauntlet vision proposals as JSONL training data, creating and validating the output directory up front. The collector API is staged ahead of the runner that will drive it.
  • bobby context forget no longer fails against a store it just released. Claiming the lockfile retries briefly, because the command opens, drops, and reopens the store in one process and that hand-off lost the race on Linux. A lockfile that is unusable for a reason other than contention now says so instead of telling the operator to stop a bobby that is not running.
  • Dependency bumps that reach bobby-browser-client consumers: sha2 0.10 to 0.11, reqwest 0.12 to 0.13, toml_edit 0.22 to 0.25, dialoguer 0.11 to 0.12. Digest output is unchanged -- the same lowercase hex, now produced with hex::encode because sha2 0.11 returns a type that no longer formats with {:x}.
  • tools/list advertise-only schema trim: opaque deep nests for WaitCondition / IntentHints / TargetSpec / ScreenshotMode and a collapsed form_snapshot outputSchema. Validation schemas and tools/call are unchanged. Frees catalog headroom: full is 116,204 bytes of the 131,072 budget, so job_* fit with 14,868 spare.
  • README / install docs: not on homebrew-core yet; checklist for a future core submission (formula name, three binaries, bottles, audit).
  • Unix release binaries are stripped before packaging. Installation docs cover curl download of GitHub Release assets. scripts/install.sh is the one-liner installer (BOBBY_VERSION, INSTALL_DIR).
  • Docs: public agent skill (bobby install --skill) vs internal Ghost / ZigZagZig recovery (Rust: SkillGhost / SkillZigZagZig) β€” navigation title "Internal skill runtime (Ghost / ZigZagZig)".
  • context_ask falls back to the persisted per-profile store, with source of observed, persisted, or visionPromoted on every answer.
  • context_neighbors returns remembered form structure around a control.
  • context:read capability, over MCP and /v1. Bootstrap heal floors (unrestricted and agent) include context:read so agents are not stranded without it after init.
  • bobby context list and bobby context forget <site>; bobby doctor reports store size; retention sweeps on open.
  • Release-gate canary asserts no typed values or credentials reach the context store.
  • IntentHints.accessibleName: an a11y_snapshot node's target passes into any intent_* tool verbatim. Equivalent to an exact nearText; both set to different values is refused as intentCompileFailed.
  • The tools/list byte-budget gate measures all 21 capabilities, not the 15 it had listed. Capability::ALL is the single source.
  • Idempotent retry works over MCP. The digest covered the whole CommandEnvelope, including deadline and the per-attempt commandId/attemptId/workflowId, all of which the gateway mints fresh on every dispatch with no caller override β€” so a retry never matched its own first try and every retry answered idempotencyConflict on a command that may already have landed. Identity is now the command: schema version, session, page, the command itself, and the one-shot vision consent. Same key with a different command still conflicts. A replayed outcome no longer has the current call's workflowId/attemptId stamped onto it, because that pair never ran.
  • Evidence::Wait carries observed, the value the condition matched on: the element text or value, the URL, or the document ready state. The poll already read it to decide whether it was satisfied and then discarded it, so verifying a submit cost a second round trip to learn what had just been confirmed. Bounded at 512 characters on a character boundary. Chromium reports it for text, value, URL, and document conditions; Firefox for URL. Absent on element and network-quiet conditions, which match on presence and counts rather than a value.
  • recovery_status accepts sessionId instead of workflowId and answers with that session's recoverable workflows, newest first, capped at 32. recovery_status and workflow_recover were keyed by workflowId alone and the checkpoint store had no index, so an agent that was compacted or restarted could not name β€” and therefore could not reach β€” its own in-flight workflow. Exactly one of the two keys is required. Ownership is enforced against the session-ownership registry, and a corrupt entry is skipped rather than failing the listing.
  • The context-store privacy canary scans the store instead of a directory that cannot exist. It read <context dir>/<profile> literally, but the store hex-encodes the profile component, so the scan found nothing and the test failed on its own precondition β€” the property it exists to prove was never actually checked. It now walks every .json under the store root at any depth.
  • intent_submit_and_verify, intent_follow, and boundary click accept autoCheckpoint, which defaults to true...
Read more

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 05 Aug 15:17
  • Add outbound ACP vision delegation: a workflow harness performs the vision work over ACP and bobby stores no provider credentials. Bounded packet and result validation, isolated child-session lifecycle, image capability negotiation, and harness-advertised authentication. bobby vision connect --backend acp writes the config and bobby doctor covers it; direct providers are unchanged.
  • An isolated ACP vision harness that requests interactive permission fails closed β€” the request is cancelled and the child session closed, so it cannot produce an accepted result after asking for authority the parent session did not grant.
  • The optional session visionNode selector is exposed through the MCP session_create input/output schemas and the TypeScript SDK contract validator. MCP agents could configure ACP vision but session_create rejected the selector, leaving the configured route unreachable through that interface. The versioned Rust session contract is unchanged.
  • A rejected advertised ACP authentication is classified as AcpClientError::Authentication and fails before the isolated child session is created, instead of surfacing as transport loss.
  • Firefox executes vision-assisted intents: bounded accessibility snapshots become semantic intent candidates, and vision-selected coordinates execute through native BiDi pointer actions. Unsupported candidate scopes are rejected.
  • The Firefox companion popup pairs and re-pairs the profile through the native host's enrollProfile control path, so the credential never leaves the host. First-time enroll bootstraps the companion; a day-2 enroll reuses the live bobby serve descriptor. The install persists browser-selection.json and the enroll defaults, sharing one selection builder with the CLI. Adds the toolbar icons. The guide prefers popup Pair; bobby enroll-firefox-profile remains for CI.
  • bobby install --companion and make firefox upgrade a bobby-managed native host instead of failing with native-host installation destination already exists whenever the installed wrapper or manifest differed by bytes β€” JSON key order, or a different bobby path. Operator-owned files still refuse. Manifest keys serialize alphabetically so a repeat install is idempotent.
  • scripts/check-version-agreement.py covers packages/firefox-companion/manifest.json. It had drifted to 0.3.1, which is the version about:addons showed.
  • The Chromium worker never holds the pages mutex across browser I/O. The guard was held across every CDP round trip β€” navigate (up to 300s), inspect, click, humanized typing, screenshot, HAR dump β€” so one hung page call serialized every other page on the session, and close/terminate blocked on the same mutex, making a single stuck call unrecoverable. All 30 lookup sites clone the Arc-backed page handle under the lock and drop the guard before I/O; the remaining guard uses are map mutations.
  • The envelope deadline is enforced mid-flight, not only at admission. inspect, click, screenshot, and the cookie operations carry no timeout of their own, so a hung call stalled its command forever. Execution now races the deadline at the single dispatch site and fails retryable DeadlineExceeded; callers declare the budget via timeoutMs, clamped to the 300s ceiling. An aborted call may still execute browser-side, as with any timeout.
  • cookie_set and cookie_delete held the pages mutex across a recursive get_cookies acquisition of the same non-reentrant lock. Every cookie command hung its session permanently.
  • download_url is advertised, required, parsed, and threaded through the MCP gateway. The gateway always sent pageId: None while the executor requires one, so every call failed invalidRequest.
  • Firefox page_close returns Evidence::Page captured before teardown. Without it the executor recorded every successful close as a retryable failure, so agents retried a destructive operation.
  • Extraction and the Firefox JavaScript-result path truncate on a character boundary. Byte-index truncation panicked inside a multi-byte codepoint, so any non-ASCII page could kill the command.
  • Two check-then-act races are single-flighted: concurrent leases on one session launched two browsers against one profile directory (SingletonLock), and concurrent network_log calls spawned duplicate HAR collectors that split entries into a flaky verificationFailed.
  • Security: get_job and cancel_job checked the capability but never ownership, so any principal holding job:read could read another principal's job payload and result, and job:cancel could cancel their work. Jobs record their submitting owner (serde-compatible with pre-ownership journals; a None owner stays readable) and cross-principal access answers as absence.
  • Security: the cookie_get URL filter matched by substring, so example.com matched notexample.com and path=/ matched every URL β€” agents received other origins' cookies as correct results and re-injected them with cookie_set. Matching is now dot-boundary host suffix plus real path prefix.
  • Security: an idempotency permit dropped between reserve and finish β€” a cancel or panic mid-request β€” wedged its key until deadline for every retry, and enough wedges exhausted the principal's capacity. Permits abandon on Drop, disarmed by finish or explicit abandon.
  • Security: companion reconnect credentials are stored as a SHA-256 digest and compared in constant time. They were stored in plaintext and compared with an early-exit !=.
  • Security: a live MCP SSE stream re-evaluates its SubscribeEvents guard on every poll, so a token rotation pauses event delivery instead of the stream running on the capability set it opened with. Revoked or expired principals still close the channel.
  • Security: the SSRF deny path covers IPv4-compatible IPv6 (::127.0.0.1), the 6to4 and Teredo prefixes, IPv4 broadcast, and CGNAT 100.64.0.0/10.
  • Security: vision endpoint responses check Content-Length up front and enforce the size bound while reading chunked, instead of buffering the whole body before the check.
  • vision-proxy's validate_extract enforces a 64 KiB serialized bound. It was a no-op stub, so unbounded upstream extraction JSON reached the runtime unchecked.
  • Checkpoint files are written 0600 on Unix; they previously took the process umask.
  • authority.json syncs the file and its directory before the rename β€” flush() is userspace-only, so a crash could leave a torn file β€” and an unparsable file is quarantined to <path>.corrupt instead of failing boot with every token stranded.
  • A corrupt artifact-ownership record is quarantined instead of permanently rejecting all future registrations.
  • ArtifactStore construction sweeps crash-orphaned staging directories, which each leaked up to max_bytes for the life of the installation.
  • The session manager releases the worker before unregistering the session. The old order dropped the API handle first, so a failed release leaked the browser with nothing left to retry through; on failure the session now stays registered.
  • events_read no longer journals a receipt per poll into the 64-event ring, where its own traffic evicted real events and poisoned resume cursors.
  • Page.getFrameTree awaits the page lock instead of fabricating an about:blank frame tree from a try_lock fallback under contention.
  • The task scheduler re-checks terminal state when inserting an abort handle; a job that finished faster than the insert left the handle in place forever.
  • bobby doctor's 15s MCP handshake deadline applies to the read. The blocking read_line ignored it, so a mute gateway hung; the read runs on a thread with recv_timeout.
  • The checkpoint store prunes uncontended entries from its per-workflow lock map, which otherwise held one Arc<Mutex> per workflow id for the life of the process.
  • The page registry URL update after navigate (a stale page_list) and the expected-URL settle wait before click verification are logged instead of silently discarded.
  • The Firefox companion removes pending prompts when their context is destroyed, and serializes cookie names and values as JSON into the document.cookie statement β€” the hand-rolled escape let a ; or newline break out of or inject into it.
  • A bootstrap.env parse error reports the line number instead of the offending line, which is the bearer token.
  • The MCP stdio read loop shares the 64-deep in-flight bound the notification branch already had. A client pipelining thousands of tools/call frames without reading responses could exhaust memory and browser processes.
  • replace_session's timeout error states its contract: the timeout reports while the owned cleanup finishes in the background, and the swap lands exactly once afterwards.
  • The MCP gateway tests run against a real RuntimeService β€” journal, worker pool, recovery coordinator β€” over an evidence-producing fake worker, and assert terminal outcomes: intentKind, resolutionPath, candidates, verification evidence, and the resolved evidence inside a checkpoint read back through a real RecoveryCoordinator. The RuntimeService::default() fixture behind ~50 of them had no journal, workers, or recovery, so every dispatched command failed downstream and the assertions (assert_ne!(code, -32602), dispatch counts) passed under total downstream failure.
  • Twelve of the fourteen security release cases run without a browser. They prove auth, framing, quota, store, and lifecycle boundaries and never lease a worker, but ran only behind the installed-Chromium ignore gate, so CI proved none of them. The release matrix still runs all fourteen; canary leakage and principal isolation open real pages and stay Chrome-gated.
  • page-runtime pins that a typed value which never lands fails verificationFailed. The positive type_text test passed whether or not the post-type comparison ran, because the fake echoed the expected valu...
Read more

v0.5.1

Choose a tag to compare

@github-actions github-actions released this 04 Aug 18:42
  • Breaking (MCP): a command whose outcome status is not completed now returns isError: true. Failed commands previously reported isError: false, so hosts checking isError treated every failure as success. restarted/resumed recovery decisions remain success.
  • Boundary commands are usable over the flat MCP tools: intent_* tools and click accept optional commandId/attemptId (threaded through unchanged), and every outcome echoes attemptId alongside workflowId/commandId. The Boundary gate requires a pre-saved checkpoint naming those exact ids, but the server minted them internally and never surfaced them, so intent_submit_and_verify and boundary click could never pass it over MCP. The fill_and_submit_form prompt is rewritten to the only working order (snapshot, fill, pin ids, checkpoint, submit) and states the exact CompleteFormField/ExtractField shapes it previously omitted.
  • The static bobby:// resources (capabilities, failure-taxonomy, intents, primitives) are readable by any authenticated principal; only live artifact:// entries require artifact:read. An agent that hit missingCapability could not read the repair documentation for it. Revoked/expired principals are still denied.
  • bobby://failure-taxonomy documents the RPC-layer InterfaceErrorCode vocabulary (14 codes, each with a repair action), and the advertised errorCode enum adds targetObscured/targetOutOfBounds (29/29 variants, pinned by a schemars parity test).
  • Tool annotations corrected: readOnlyHint on wait_for, intent_locate, intent_wait_for_state, intent_extract; openWorldHint on page_open, click, intent_follow, intent_submit_and_verify. network_log's description names both real failure codes.
  • tools/list advertises shared Id schemas by $ref, keeping the full surface at ~125 KB with ~5.8 KB of headroom inside the 128 KiB connect budget after the new commandId/attemptId fields.
  • A tag publishes every artifact. release-binaries creates the GitHub Release before uploading assets β€” nothing created it, so gh release upload answered "release not found" and v0.5.0 built five binaries and shipped none. The body is the CHANGELOG section for the version via scripts/changelog-section.py, so a version with no section fails the tag instead of publishing empty notes.
  • release-binaries calls publish-docs directly, and publish-docs gains a workflow_call trigger. A release created with GITHUB_TOKEN does not fire on: release, so the documentation artifact never built.
  • npm publishes through the OIDC trusted publisher instead of a stored token: the account requires 2FA on publish, so a token fails with EOTP and CI cannot hold a one-time password. publish-npm.yml is renamed publish.yml and the job runs in the production environment, matching the org, repository, workflow filename, and environment the trusted publisher matches against.