Skip to content

Releases: jkudish/jev-browser

0.5.0

Choose a tag to compare

@jkudish jkudish released this 23 Sep 02:00

Install:

npx -y @jkudish/jev-browser
  • Bot-protection interstitials are detected and named instead of endured. When a Cloudflare challenge ("Just a moment...", "Performing security verification") or hard block is on the page, the run stops with status blocked instead of spending further Jev calls or steps on the wall, and every result carries bot_protection: { provider, kind, evidence, guidance } (kind is challenge or block). A detected challenge first gets a short bounded window (8s, within the run budget) to clear itself; one that auto-passes lets the run proceed normally, and blocked is declared only when the challenge was still there after the window. A hard block stops immediately. A wall that only becomes visible on the final page (after the last action, or under a done/goal judgment) gets the same settle window and flips the outcome to blocked when it persists, so a confident done can no longer wrap a challenge page; when it clears in the window the outcome stands, no wall is annotated from the pre-settle page, and the reported final page is the content that actually painted. When the budget is too exhausted to run the settle window at all, the deadline outcome (timeout) keeps precedence and the wall is annotated as evidence instead of claimed as the outcome. Detection from the page (the only evidence that can stop a run, DOM-only and self-sufficient, so the run probes and the detector agree on everything that stops) requires a Cloudflare-signature or branded challenge title (generic wordings like "Verify you are human" need a Cloudflare-brand body marker on the page) or three distinct body markers including at least one Cloudflare-brand phrase, with overlapping phrases counted once; from_page in the library result means the DOM evidence met that threshold on its own, never a header plus an incidental body phrase. Cloudflare's cf-mitigated: challenge|blocked response header is recorded as evidence and annotation but never corroborates stopping, because it is last-seen state and a challenge that auto-passed still answers with the header. The guidance text states the operative facts: cf_clearance is bound to the browser and IP that earned it, so seeded cookies do not clear challenges; the reliable paths are running from the browser session that earned the clearance (reuse its page) or the site's API.

  • Cookie seeding (PR #9, remediated): cookies on navigate() and the MCP tool, and --cookie-file name=@path on the CLI, add cookies to the run's own browser context before the first navigation so a run can start behind a login (password fill is the other way; the agent never types into password fields). Cookie values are credentials of the same rank as the password value. Delivery is strictly by reference: the CLI reads the value from a file (there is deliberately no --cookie name=value flag, which would put the token in argv, shell history, and the process list), and the MCP tool takes cookie_file (a one-shot handoff file inside ~/.jev-browser/handoff, validated and consumed exactly like password_file) or cookie_env (only JEV_COOKIE_* names, rejected before lookup); no argument ever carries a cookie value. Each value is validated like a password (empty, over 4096 bytes, under 4 characters, control characters, and normalization-collapsing values are refused before the run starts) and is redacted from every model-facing state, trace, error, URL, console event, and result payload through the same machinery, one redactor covering the password plus every cookie value, applied longest first so a value that is a prefix of another still redacts. Cookie runs refuse an injected page (context.addCookies would mutate a caller-owned context) and refuse video recording, both before any timer, listener, or browser is armed, and the final screenshot is suppressed from run start (the first rendered page can already reflect a value into pixels). Attributes are no longer weakened in transit: with no domain supplied the cookie is host-only on the start URL's exact host (a dotless domain through Playwright's addCookies, verified host-only in Chromium; only a caller-supplied leading dot opts into subdomain matching), path defaults to /, httpOnly to true (page scripts cannot read the value; set false only when the site's own JavaScript must), sameSite to Lax, and secure to true on https start URLs, forced true for __Host-/__Secure- names and for sameSite: "None"; __Host- cookies with an explicit domain or a non-root path are rejected because the browser would drop them anyway, and an explicit secure: false cannot strip the forced flag. No error path, from argument parsing through cookie resolution to handoff validation, ever quotes a cookie value.

  • CLI argument errors now print a one-line message and exit 1 instead of an uncaught stack trace (from PR #9).

  • Typing degradation is now visible instead of silent (#2). Every result, success or error, carries degraded, warnings, typing_provider, and typing_model; each warning is { code, step, message, provider, model, finish_reason?, fallback? } with codes typing_fallback_no_provider, typing_generator_empty, typing_generator_error, and typing_configuration_error. Warning messages are short (capped at 200 chars) and never contain raw provider response bodies. A degraded run is not a tool error and does not change the CLI exit code when a fallback completed; the CLI prints one concise stderr line, and navigate() itself stays side-effect free (no console output).

  • JEV_BROWSER_TYPE_PROVIDER is now strict: it selects only that provider, and an unknown value, or a missing or malformed key for the named provider, is a configuration error raised before any timer, listener, or browser is armed. Previously it only reordered auto-detection candidates and silently fell through to another provider. Runs with allow_typing: false ignore typing configuration entirely, and auto-detection without the variable keeps its order (openai, openrouter, anthropic, google).

  • Typing fallbacks split by field kind (#2). A failed, empty, or provider-less generation on an ordinary type_eN field types nothing and records a step action error ("typing generator failed; nothing was typed") plus a warning, instead of filling the field with task-keyword soup. search_eN fields keep the search-tuned keyword heuristic fallback, now reported with fallback: "keyword-heuristic" and the underlying typing_generator_empty or typing_generator_error code.

  • OpenRouter typing calls disable reasoning (providerOptions: { openrouter: { reasoning: { enabled: false } } }) and raise the output budget from 48 to 256 tokens, because reasoning models can spend the whole cap on hidden reasoning tokens and return an empty message. The providerOptions namespace is sent only when the resolved typing provider is openrouter; other providers keep the 48-token cap. No retry machinery was added.

  • JEV_BROWSER_TYPE_BASE_URL on its own still selects a compatible endpoint, and when combined with JEV_BROWSER_TYPE_PROVIDER it becomes the named provider's endpoint; it is validated as an absolute http(s) URL up front.

  • Node.js 22 or newer is required (was 20): the locked ai@7 dependency declares >=22.

  • JEV_BROWSER_TYPE_BASE_URL is honored by every named provider (openai, openrouter, anthropic, google), not only openrouter, so private gateways work uniformly. @ai-sdk/google is upgraded to 2.x: 1.x emitted model spec v1, which ai@7 rejects at runtime, so the google typing provider can now actually generate (previously every google generation failed).

  • README corrections: the OpenRouter typing default model is google/gemini-2.5-flash-lite (the table said a stale id), the JEV_BROWSER_TYPE_MODEL example is a plain model id passed through unchanged (no openrouter: prefix), and the typing-cost claim now says one call per typed field instead of once or twice per task.

  • Form controls now resolve an accessible name (AccName 1.2 precedence: aria-labelledby refs, aria-label, all associated native labels via the .labels API, then placeholder and title). Plain <label for> login forms no longer drop out of the action space, which previously made their inputs invisible and untypeable (#1).

  • A successful type action now records a truthful outcome (typed into "Username"; no visible page change) instead of no visible change, so the stuck watcher no longer misreads a filled field as a no-op. Repeat recovery now keys off machine state (a repeated action whose only effect was the fill, or a genuine no-op) instead of matching the display string, and no longer terminates on a done alternate with negligible probability.

  • Native <select> dropdowns are now selected by DOM option index instead of by label string, with each option's live index carried alongside its label. Blank options are filtered from the model's list without shifting the indexes used for selection, and a scrubbed or truncated label can never become the selection key.

  • Password fill for logins: the model never sees or types the password. Delivery is CLI --password-file <path|-> (stdin supported), MCP password_file (one-shot file directly inside ~/.jev-browser/handoff, validated: owner, mode 0600, single link, no symlinks, unlinked at run start), or MCP password_env (only JEV_PASSWORD_* names, rejected before lookup). Fills are bound to an exact trusted origin (JEV_BROWSER_PASSWORD_ORIGIN, --password-origin), and the type/origin check and the fill run as a single in-page task on the resolved element, so no navigation or DOM swap can interleave. Fills never submit. Echoes of the value (raw, percent-encoded, form-encoded, HTML-entity, markdown-escaped, whitespace- and zero-width-normalized, aria-YAML-escaped) are redacted from every model-facing state, trace, error, URL, and payload, including values a page reflects into labels, attributes, con...

Read more

0.4.1

Choose a tag to compare

@jkudish jkudish released this 20 Sep 20:17

Fixes the Chromium download on install. The postinstall script in 0.4.0 sat outside the scripts object in package.json, so it never ran and the first launch failed with: Executable doesn't exist. It now runs on install and downloads Chromium once. Set JEV_BROWSER_SKIP_BROWSER_DOWNLOAD=1 to skip it.

Install: npx -y @jkudish/jev-browser

v0.4.0

Choose a tag to compare

@jkudish jkudish released this 18 Sep 09:39

Recording support (--record out.webm, per-step t_ms, video_path in results), DOM-stability settle that cuts per-step time roughly 2-3x on JS-heavy sites, 4s action timeouts, OpenRouter typing default moved to google/gemini-2.5-flash-lite (openai/gpt-5.6-luna returns empty output through OpenRouter), README demo GIF. Install: npx -y @jkudish/jev-browser

v0.3.0

Choose a tag to compare

@jkudish jkudish released this 18 Sep 05:03

Cloudflare Workers AI transport (typesafe/jev via /ai/run) and Vercel AI Gateway transport (typesafe-ai/jev via the AI SDK evaluate API, confidence included); AI SDK bumped to v7 for the typing path with no behavior change. Resolution order: TypeSafe direct, OpenRouter, Cloudflare, Vercel; JEV_PROVIDER forces any. Install: npx -y @jkudish/jev-browser

v0.2.0

Choose a tag to compare

@jkudish jkudish released this 18 Sep 03:36

OpenRouter support: one OPENROUTER_API_KEY can power the entire package, typing included. JEV_PROVIDER forces typesafe or openrouter; jev-latest maps to typesafe/jev-1.13 on OpenRouter. Results now report the transport used (jev_provider, resolved model). Install: npx -y @jkudish/jev-browser

v0.1.0

Choose a tag to compare

@jkudish jkudish released this 17 Sep 21:16

Initial release. Install from npm: npx -y @jkudish/jev-browser. See the changelog for what is included.