Releases: cavi-ai/bobby-browser
Release list
v0.13.0
Added
bobby doctorcommand: read-only health checks with--jsonstructured
output and anext-actionrecommendation. Inspects command journal,
scheduler journal, corpus, sidecar version, and store health.bobby doctor --fixrotates an expired bootstrap token.- Firefox
wait_forimplementsnetworkQuietfrom the BiDi
network.beforeRequestSent/responseCompleted/fetchErrorstream
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
/v1surface β
form_snapshot,checkpoint,recovery_status,recover, and
artifactwith full reference verification (media-type, Content-Length,
byte cap, SHA-256 digest). - MCP
boundary-onceguard refuses a secondintent_submit_and_verify
against a workflow whose Boundary submit already completed, keyed on
(workflow, control identity).reSubmit: trueis the explicit
acknowledgment path. - Agent benchmark gate enforces per-task token and call budgets from
baseline.jsonand 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::navigatereports the command error message, notDebug
of the whole outcome.element_at_pointdefaults to unsupported (browserCommandFailed), not
Ok(None).- MCP
extractdefault 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
ExpectedStatePreSatisfiedpre-check widens to 2s (from 750ms),
outliving the SPA render race under concurrent browser instances.boundary-onceledger 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.newhits
"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
inspectwith 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/listprovisions 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.evaluateis refused after navigate when the execution
context is stale, instead of hanging. - CDP:
Target.createBrowserContextis 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.rbsha256 digests updated for v0.12.0 release
assets.
v0.12.0
Added
- Captcha and human-verification challenges are reachable over MCP: two new
tools,intent_detect_challenge(Replayable, read-only, advertised in
exploreso a stuck agent can name its blocker without a phase switch) and
intent_solve_challenge(Reconciliable, drives the vision solve loop until
cleared ortimeoutMs). Both gate onbrowser:mutate+intent:execute+
vision:assistup front, so a principal without vision is refused at the
gate rather than the engine.bobby://intentsnow documents ten intents,
including the captcha path. DetectChallengeintent (Replayable): classifies a challenge without acting
on the page, returning a typedchallengeDetectionβ 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.SolveChallengetactic 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_createandworkflow_startaccept
zigzagzig: true, forcing fingerprint and humanize server-side and routing
page-bound commands through the recovery ladder. The creation gate stands in
forbrowser: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_challengenow appears in metrics snapshots. - TypeScript SDK:
detectChallengeRuntimeCommand/
solveChallengeRuntimeCommandand thedetectChallengeEnvelope/
solveChallengeEnvelopehelpers emit the canonical wire shape, with
DetectChallengeIntent/SolveChallengeIntent/SolveChallengeHints
contracts and theDEFAULT_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_verifywith anetworkQuietpostcondition returns a
submitSettlementclassification and value-freeformValidationrepair
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.tomlsets the MLX vision provider to
mlx-community/Qwen3.5-27B-4bit, matching the default already shipped for
bobby install/bobby vision connectin 0.11.0 (the file still pinned the
supersededQwen2.5-VL-3B), and adds a commented[vision.providers.ollama]
example. form_snapshotoutput omits default-valued fields: an untouched control
drops from 741 to 422 bytes, andworkflow_observe(includeForms)no longer
re-sends redundant defaults on every observation. The change is output-only β
old payloads still deserialize, theform_controloutput schema requires
onlyid,controlKind,state,validity, andsupportedOperations, and
TypeScript SDK contracts make the slimmed fields optional.- The
targetDetachedfailure taxonomy separates whole-browser transport loss
(reattach preserves state, relaunch wipes it) from a stale element, and
notFounddocuments the "browser page is not open" shape. - The default MCP
explorephase includesintent_complete_formand
intent_submit_and_verify. Initialization guidance tells deferred-schema
clients to load those tools withworkflow_startandworkflow_observefor
the standard form loop. Evidence::ExecutionPath.pathnames the strategy, not an engine:chromium
andchromiumFallbackare nowbrowserandbrowserFallback. The old names
still deserialize so recorded journals replay unchanged.- Successful
intent_complete_formresponses default to compact evidence;
passevidenceDetail: "full"to retain the complete per-field success
evidence. Failure evidence and newly revealed conditional controls remain
available in compact mode. - Download
savedToevidence echoes the exact caller-supplied destination
after downloads-root policy validation. An invalidmaxBytesrequest is now
rejected asinvalidRequestwith 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_verifywith a descriptivepurposeand 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
fillintent 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. completeFormresolves 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 ownWebDriverBiDiServer.json. - The stdio gateway ends the shared Firefox BiDi session on exit, preventing a
stale session from blocking later launches withMaximum 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.shaccepts pretty-printed endpoint files and a
macOS-relaunched Firefox whose listening process has a different pid.Formula/bobby-browser.rbcarries the v0.11.1 asset digests.
v0.11.1
Fixed
- Product documentation navigation includes every shipped page, and producer verification now rejects pages omitted from navigation.
Formula/bobby-browser.rbcarries the v0.11.0 asset digests.
v0.11.0
Breaking
- Unified control action vocabulary:
FillValueis removed.fillandcompleteFormintents now use a singleControlActionenum shared with thecontrol_actionMCP 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). activateis rejected in fill; it remains control_action-only.control_actionsetTextacceptsclearFirst(default true, replace behavior; unchanged from prior hard-coded behavior in worker-pool and firefox-companion).type_textremains unchanged; itsclearFirststill defaults false.- Interface version bumped to
2026-08-19; HTTP clients must send the newx-interface-version.
Added
- Qwen3.5-27B-4bit (
mlx-community/Qwen3.5-27B-4bit) is the MLX vision default invision-proxy,bobby vision connect, and thebobby installmodel 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|absoluteoverrides the mlx-vlm provider's per-model coordinate-space detection.network_logreportsnetworkRecordingStartedon 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 fromlsofunder a 2s cap on unix. It is omitted rather than blocking on any lookup failure or non-unix platform.clickcarriesdialogOpenedevidence whenalert/confirm/promptopens during the click.type_textcarriestypedControlKindevidence, which verification uses to accept a checkable's or a select's post-state.form_snapshotcollects[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
roleattribute.
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
ordinalskips the bound because it picks one match deterministically. A large page no longer fails a target that matches once. type_textsplits its value into runs the US keyboard layout can key-press and runs that need a caret-levelInput.insertText, so Unicode outside that layout and newlines are typed rather than key-pressed.- The browser-launch repair and the
engineUnreachablediagnostic name the Firefox companion bind (default127.0.0.1:9876) and the other runtime that may hold it, alongside the BiDi endpoint. visionAssistDeniedleads 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.reasonenum carries a description:ineligibleCommandmeans 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
targetNotFoundre-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_openor an agent workflow reports its navigated URL and aninteractiveready state, and is listed on its session, instead of the pre-navigation blank state.
Fixed
- A
clickthat opensalert/confirm/promptreturns 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 reportsdialogOpened.dialogconsumes a dialog that opened before it was called rather than waiting on a future event that already fired. clickand 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_textagainst a non-editable target β disabled, readonly, afieldset[disabled]ancestor, a non-typeable input type, or a non-form element β fails withinvalidRequestinstead 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_dimensionis refused withinvalidRequest. An unbounded clip could kill the browser process. a11y_snapshotreserves 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 atdownload_urlandclick_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.
httpResponseTooLargeis a plainfailedrather thanneedsReconciliation. 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 andsrc, 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
imgandimageas one role. Chrome's a11y tree emitsimagewhile the DOM collector emitsimg, 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_textverification 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
ykey. Formula/bobby-browser.rbcarries the v0.10.0 asset digests.
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.
v0.9.0
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_assistwraps a provider so the vision boundary reports its own counters and provider mode. control_actionreturns 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_verifyrefuses a pre-satisfiedexpectedState. A text, element, or value condition that already holds before the boundary click fails withexpectedStatePreSatisfiedand never clicks; url, document, andnetworkQuietconditions 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::DownloadcarriessavedTo, 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 inBOBBY-VISION/1prompts, emitted from the roleLocateIntent.hintsalready carries. Thinking is suppressed at serve for v1 so the instruct template's empty<think>wrapper does not wrap the bare index, and_parse_indextolerates that wrapper for adapters trained on thinking-enabled templates.- Response prefill in the mlx-vlm provider:
proposeprefills 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. clickCandidateaction 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 doctorreports the configured vision timeout. Modern-gauntlet scorecards carry provider, model, and source dimensions, action counts, fixed failure categories, and engine/provider-separated output.
Changed
initializenegotiates 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 servestartup, 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_observeaccepts 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
ARTICLEelements, soa11y_snapshottarget scoping resolves article subtrees. - The competitor gauntlet no longer pins the full toolset: the benchmark measures the default
explorephase a user actually gets. - Runtime error detail reaches the operator interface.
Fixed
a11y_snapshotscoped to an iframe element returns the frame's content. It detects the frame owner throughDOM.describeNodeand 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_LOGoutput from the stdio gateway goes to stderr, leaving stdout as the protocol channel;mcp-stdiopreviously 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_escalationskips empty-candidate escalations, which mislabelled gather failure as model judgment. The collection harness fails whenBOBBY_GAUNTLET_VISION_ENDPOINTis 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
Added
- Vision assist.
bobby installconfigures a vision provider during onboarding, andbobby vision connect --provider {openai,ollama,mlx}writes the provider config.vision-proxytakes--upstream {openai,ollama,mlx}, a single--vision-base-urlwith per-upstream defaults, an optional--model, and--spawn-serverto run the local vision server as a managed kill-on-drop child (--server-scriptorBOBBY_VISION_SERVER_SCRIPT, else auto-detected). BOBBY-VISION/1wire contract forproposeandextract, 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_pointon managed Chromium reads the internal DOM channel, never the policy-gatedevaluate_javascriptpath. - NVIDIA OpenShell host.
bobby install --host openshell/bobby openshell installwrites anopenshell/pack (MCP Streamable HTTP client config,protocol: mcppolicy sample, merge-onlypolicy-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 openshellprints the MCP fragment. bobby openshell list|status|rotate, with non-secret.status.jsonsidecars.bobby doctorreportsopenshell-packand checksopenshell-admin,openshell-companion,openshell-mcp-url, andopenshell-sandboxeswhen a pack is present; it warns on β₯2 local sandboxes sharing one Firefox companion, on a non-loopback cleartext MCP URL orserver.host, and when an older pack lacks the deny rules. Secrets root overridable viaBOBBY_OPENSHELL_SECRETS_DIR.a11y_snapshotaccepts 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_forsupports Text, Value, and Document conditions, matching Chromium.networkQuietremains unsupported on Firefox.
Changed
- The default
explorephase 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 notoolset_selectand schema-discovery round trip before its first action.command_execute,evaluate_javascript,emulate, niche mutations, and the intent family stay phase-gated. control_actiontargets require onlyroleandaccessibleName;ordinal,framePath, andshadowPathdefault, 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 explicitordinal: 0matches an omitted ordinal.control_actionselectOne/selectManyand 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_snapshotdescend 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 explicitframePathwas named. Capped at 8 frames per gather under a shared node budget; frames with no stable address (no id, test id, orsrc) are skipped. - Whole-page
inspectafter 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 carriesexecutionPath.reason: pageMutatedon the live read. - Page-scoped text waits (
role: main|RootWebArea|β¦orcss: body|html|:root) read livedocument.body.innerTextvia evaluate, with empty optional fields treated as absent, so async confirmations match what a whole-pageinspectsees. click_and_wait_for_popupdefaultsautoCheckpoint=trueand accepts pinnedcommandId/attemptId, matching boundaryclickandintent_submit_and_verify. It registerswindow.opentargets, andpage_listsyncs untracked page targets into the session (one browser per session), excludingchrome://browser chrome, so authorization popups are drivable.- A plain
clickon an anchor with adownloadattribute routes through the armed download capture on managed Chromium: the file lands in the session's downloads withDownloadevidence instead of completing with nothing materialized. workflow_startfailures carrydetailwith the error code and message instead of a bare reason.- The advertised
WaitConditionschema names everykindtag, required field, and enum instead of presenting an opaque object. a11y_snapshotdropsInlineTextBoxleaves, which duplicated theirStaticTextparents' text and dominated the payload. Its description points attoolset_selectfor the phases hidden by the defaultexplorephase.- OpenShell
provisionrevokes 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 narrowopenshellpreset (--capabilities-preset agentfor the full agent floor). The sample policy deniesevaluate_javascriptandjob_*at the OpenShell proxy and raises MCPmax_body_bytesto 262 KiB. bobby://intentsdocuments theframePathstep shape with an example and the Firefox exact-CSS/test-id hop requirement.- The OpenShell and jobs CLI paths share one blocking
/v1HTTP client for bearer and interface headers.
Fixed
page_openon a session whose browser died invalidates that specific dead worker and retries once on a fresh one, instead of returning an opaqueinternalon every call. Concurrent recovery cannot discard another caller's healthy replacement, and a failed replacement launch no longer leaves a phantom registered page.session_closeno 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 failinginternal. 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
notFoundinstead 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 canceledand dead-target loss map totargetDetached(retryable). Stale CDP node ids after a re-render map totargetNotFoundwith fresh-snapshot repair instead of a rawbrowserCommandFailed. - Boundary commands that fail with
waitConditionTimedOutorverificationFailedstayfailedfor inspect-then-adjust instead of never-retryneedsReconciliation. A command that fails before reaching the browser, on argument or target-resolution errors, reports a plainfailed; 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
verificationFailedstating the click landed, instead of a barewaitConditionTimedOutthat invited a duplicate submit. intent_submit_and_verifywith anetworkQuiet-only wait fails when[aria-invalid=true]markers remain, instead of reportingcompletedon a soft settle after a rejected submit.intentaction_targetpreservesframePathandshadowPathfrom the intent target, so iframe submits no longer discard the frame hop. Document targeting falls back without losing a named intent target, andlocateNotFoundattaches the near-miss candidate set to the stuck report instead of an empty list.- Whole-page
inspectover direct HTTP treats an empty-<body>SPA shell (title/meta chrome plus scripts) asjavascriptRequiredand falls back to the live browser instead of returning shell HTML. The shell probe no longer races client-side URL rewrites. inspectdenied by network policy (loopback page, non-http URL) degrades to the browser that already has the page open instead of failing a DOM read withnetworkPolicyDenied.download_urlkeeps the hard denial.[http]accepts partial overrides: missing fields fall back to defaults instead of failing TOML parse, so a host that sets onlyallow_loopbackno longer bricks MCP startup.bobby doctorpassesBOBBY_BROWSER_CONFIGinto the MCP handshake child, so[mcp] startup_toolsetand the rest of that file apply totools/list.networkPolicyDeniedguidance names the loopback and private-destination cause and thehttp.allow_loopback/http.allow_private_networkoperator switches across the repair hint, the taxonomy, and thedownload_urldescription; for page-offered files it points at clicking the link.upload_filespolicy 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 asinvalidRequestat resolution time on both engines, instead of polling unmatchable until a wait deadline. - Protocol-lay...
v0.7.0
- Breaking (MCP surface):
tools/listnow defaults to theexplorephase instead of the full surface. An existing client that connects and does not calltoolset_selectsees the read/snapshot/navigate lifecycle only β no mutation, intent, checkpoint, orcommand_executetools.[mcp] startup_toolset, overridden byBOBBY_MCP_TOOLSET, selects the phase at connect:explore(default),act,intent,verify,full. The firsttools/listis ~42 KiB onexploreagainst 128 KiB onfull. 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 (noauthority:admin). Use--preset unrestrictedfor the operator floor. Marker-less existingbootstrap.envfiles still heal as unrestricted.bobby doctorreportsbootstrap-preset. - MCP adds
workflow_startandworkflow_observein every toolset phase, withcheckpoint_savealso 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
notFounduntil 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
initializereturns shortinstructions: explore startup phase,toolset_select+ re-list,error.repair,autoCheckpointdefault,bobby://recovery docs. tools/listadvertise-only output collapse forrecovery_status,page_open,session_create,session_list, andcheckpoint_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 seterror.data.repair, each{action, doc}pointing intobobby://failure-taxonomy. AneedsReconciliationoutcome always carries the never-retry repair, whatever its error code. http_waitaccepts optionalcontains(andmaxBodyBytes): each attempt becomeshttp_fetchand succeeds only when the truncated body includes the substring β for readiness gates that return 200 before they are ready.runtime_info'scapabilitieslist reports vision wiring:vision-assistandvision-providerappear only when configured, so an agent can tell an unconfigured provider apart from a transient vision failure without shell access.tools/listadvertise-only trim: the constant$schemaURL is dropped from advertised input and output schemas, andworkflow_recover'sRecoveryDecisionis advertised as a status-tag projection (the same treatmentEvidencealready had). Validation schemas andtools/callare unchanged.- The
tool_schema_sizesexample 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-testsinto a reusablegauntlet-servercrate. It servesGET /__gauntlet/snapshotandGET /__gauntlet/request-log(the same state the in-processsnapshot()/request_log()expose), and ships agauntlet-serverbinary (--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 tobenchmarks/results/runs.jsonl(gitignored);scoreaggregates per tool.- MCP
job_submit/job_status/job_cancelmirror HTTP/v1/jobs(same caps). Advertised infull,act, andverifywhen a job port is attached (bobby mcp-stdioandbobby serveMCP HTTP). Built-in handlers:echo,sleep,http_probe,http_wait, andhttp_fetch(SSRF-safe;http_fetchreturns a truncated GET body so agents need not open a browser for health/API JSON);bobby://job-handlersdocuments payloads;bobby doctorreports them underjob-handlers. - Ollama joins the direct vision backends.
bobby vision-proxy --ollama --ollama-base-urlandbobby vision connect --provider ollamanormalize a local model's output to theVisionProposalschema, and a provider on port 11434 is detected from config. No credentials leave the machine. bobby vision collectgathers 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 forgetno 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-clientconsumers:sha20.10 to 0.11,reqwest0.12 to 0.13,toml_edit0.22 to 0.25,dialoguer0.11 to 0.12. Digest output is unchanged -- the same lowercase hex, now produced withhex::encodebecause sha2 0.11 returns a type that no longer formats with{:x}. tools/listadvertise-only schema trim: opaque deep nests forWaitCondition/IntentHints/TargetSpec/ScreenshotModeand a collapsedform_snapshotoutputSchema. Validation schemas andtools/callare unchanged. Frees catalog headroom:fullis 116,204 bytes of the 131,072 budget, sojob_*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.shis 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_askfalls back to the persisted per-profile store, withsourceofobserved,persisted, orvisionPromotedon every answer.context_neighborsreturns remembered form structure around a control.context:readcapability, over MCP and/v1. Bootstrap heal floors (unrestricted andagent) includecontext:readso agents are not stranded without it after init.bobby context listandbobby context forget <site>;bobby doctorreports store size; retention sweeps on open.- Release-gate canary asserts no typed values or credentials reach the context store.
IntentHints.accessibleName: ana11y_snapshotnode'stargetpasses into anyintent_*tool verbatim. Equivalent to an exactnearText; both set to different values is refused asintentCompileFailed.- The
tools/listbyte-budget gate measures all 21 capabilities, not the 15 it had listed.Capability::ALLis the single source. - Idempotent retry works over MCP. The digest covered the whole
CommandEnvelope, includingdeadlineand the per-attemptcommandId/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 answeredidempotencyConflicton 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'sworkflowId/attemptIdstamped onto it, because that pair never ran. Evidence::Waitcarriesobserved, 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_statusacceptssessionIdinstead ofworkflowIdand answers with that session's recoverable workflows, newest first, capped at 32.recovery_statusandworkflow_recoverwere keyed byworkflowIdalone 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.jsonunder the store root at any depth. intent_submit_and_verify,intent_follow, and boundaryclickacceptautoCheckpoint, which defaults totrue...
v0.6.0
- 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 acpwrites the config andbobby doctorcovers 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
visionNodeselector is exposed through the MCPsession_createinput/output schemas and the TypeScript SDK contract validator. MCP agents could configure ACP vision butsession_createrejected 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::Authenticationand 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
enrollProfilecontrol path, so the credential never leaves the host. First-time enroll bootstraps the companion; a day-2 enroll reuses the livebobby servedescriptor. The install persistsbrowser-selection.jsonand the enroll defaults, sharing one selection builder with the CLI. Adds the toolbar icons. The guide prefers popup Pair;bobby enroll-firefox-profileremains for CI. bobby install --companionandmake firefoxupgrade a bobby-managed native host instead of failing withnative-host installation destination already existswhenever the installed wrapper or manifest differed by bytes β JSON key order, or a differentbobbypath. Operator-owned files still refuse. Manifest keys serialize alphabetically so a repeat install is idempotent.scripts/check-version-agreement.pycoverspackages/firefox-companion/manifest.json. It had drifted to 0.3.1, which is the versionabout:addonsshowed.- The Chromium worker never holds the
pagesmutex 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 retryableDeadlineExceeded; callers declare the budget viatimeoutMs, clamped to the 300s ceiling. An aborted call may still execute browser-side, as with any timeout. cookie_setandcookie_deleteheld thepagesmutex across a recursiveget_cookiesacquisition of the same non-reentrant lock. Every cookie command hung its session permanently.download_urlis advertised, required, parsed, and threaded through the MCP gateway. The gateway always sentpageId: Nonewhile the executor requires one, so every call failedinvalidRequest.- Firefox
page_closereturnsEvidence::Pagecaptured 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 concurrentnetwork_logcalls spawned duplicate HAR collectors that split entries into a flakyverificationFailed. - Security:
get_jobandcancel_jobchecked the capability but never ownership, so any principal holdingjob:readcould read another principal's job payload and result, andjob:cancelcould cancel their work. Jobs record their submitting owner (serde-compatible with pre-ownership journals; aNoneowner stays readable) and cross-principal access answers as absence. - Security: the
cookie_getURL filter matched by substring, soexample.commatchednotexample.comandpath=/matched every URL β agents received other origins' cookies as correct results and re-injected them withcookie_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
SubscribeEventsguard 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 CGNAT100.64.0.0/10. - Security: vision endpoint responses check
Content-Lengthup front and enforce the size bound while reading chunked, instead of buffering the whole body before the check. vision-proxy'svalidate_extractenforces 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.jsonsyncs 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>.corruptinstead of failing boot with every token stranded.- A corrupt artifact-ownership record is quarantined instead of permanently rejecting all future registrations.
ArtifactStoreconstruction sweeps crash-orphaned staging directories, which each leaked up tomax_bytesfor 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_readno longer journals a receipt per poll into the 64-event ring, where its own traffic evicted real events and poisoned resume cursors.Page.getFrameTreeawaits the page lock instead of fabricating anabout:blankframe tree from atry_lockfallback 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 blockingread_lineignored it, so a mute gateway hung; the read runs on a thread withrecv_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.cookiestatement β the hand-rolled escape let a;or newline break out of or inject into it. - A
bootstrap.envparse 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/callframes 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 realRecoveryCoordinator. TheRuntimeService::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-runtimepins that a typed value which never lands failsverificationFailed. The positivetype_texttest passed whether or not the post-type comparison ran, because the fake echoed the expected valu...
v0.5.1
- Breaking (MCP): a command whose outcome status is not
completednow returnsisError: true. Failed commands previously reportedisError: false, so hosts checkingisErrortreated every failure as success.restarted/resumedrecovery decisions remain success. - Boundary commands are usable over the flat MCP tools:
intent_*tools andclickaccept optionalcommandId/attemptId(threaded through unchanged), and every outcome echoesattemptIdalongsideworkflowId/commandId. The Boundary gate requires a pre-saved checkpoint naming those exact ids, but the server minted them internally and never surfaced them, sointent_submit_and_verifyand boundaryclickcould never pass it over MCP. Thefill_and_submit_formprompt is rewritten to the only working order (snapshot, fill, pin ids, checkpoint, submit) and states the exactCompleteFormField/ExtractFieldshapes it previously omitted. - The static
bobby://resources (capabilities, failure-taxonomy, intents, primitives) are readable by any authenticated principal; only liveartifact://entries requireartifact:read. An agent that hitmissingCapabilitycould not read the repair documentation for it. Revoked/expired principals are still denied. bobby://failure-taxonomydocuments the RPC-layerInterfaceErrorCodevocabulary (14 codes, each with a repair action), and the advertisederrorCodeenum addstargetObscured/targetOutOfBounds(29/29 variants, pinned by a schemars parity test).- Tool annotations corrected:
readOnlyHintonwait_for,intent_locate,intent_wait_for_state,intent_extract;openWorldHintonpage_open,click,intent_follow,intent_submit_and_verify.network_log's description names both real failure codes. tools/listadvertises sharedIdschemas by$ref, keeping the full surface at ~125 KB with ~5.8 KB of headroom inside the 128 KiB connect budget after the newcommandId/attemptIdfields.- A tag publishes every artifact.
release-binariescreates the GitHub Release before uploading assets β nothing created it, sogh release uploadanswered "release not found" and v0.5.0 built five binaries and shipped none. The body is the CHANGELOG section for the version viascripts/changelog-section.py, so a version with no section fails the tag instead of publishing empty notes. release-binariescallspublish-docsdirectly, andpublish-docsgains aworkflow_calltrigger. A release created withGITHUB_TOKENdoes not fireon: 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
EOTPand CI cannot hold a one-time password.publish-npm.ymlis renamedpublish.ymland the job runs in theproductionenvironment, matching the org, repository, workflow filename, and environment the trusted publisher matches against.