Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This changelog was generated from the repository Git history and release tags. V

### Fixed
- Vision mode can attach more than two files in one task. Each file goes through its own upload hand-off, and a run used to stop after two hand-offs even when both uploads worked: the third file, such as the last zip of a three-asset GitHub release, was refused with "file uploads are not available in vision mode", and the agent asked you to turn Vision mode off. Now only hand-offs that end without attaching a file count toward the limit of two. When that limit is reached, the agent says the earlier attempts attached nothing instead of blaming vision mode. Chrome and Firefox.
- Vision mode attaches each file with the first upload attempt. After a click on an upload control hands the upload to the element tools, the agent uses the file input the click named, but WebBrain's page check refused that first attempt when the input was hidden, as on a GitHub release form, because it had not seen that input itself. The agent then had to list the page's file inputs and send the same upload again, two wasted steps per file. The hand-off now records that exact input before the agent's next step, so the first attempt attaches the file. A file input the agent guessed is still refused. Chrome and Firefox.

## [46.1.0] - 2026-11-05

Expand Down
7 changes: 6 additions & 1 deletion docs/page-feedback-monitor.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,12 @@ Steer or grant authorization. Enter and Alt+Enter retain their existing behavior
attachment. A node the page re-mounts later under the same selector was
never listed and keeps the refusal until a fresh inventory lists it, as does
any other selector. A blocked native chooser names its input with the
selector the inventory uses.
selector the inventory uses. In a vision run that blocked `click_at` hands
the next steps to the element tools (`docs/vision-mode.md`), and its result
is the model's observation of the input: when the handoff starts, the
monitor keeps the node the result's selector names under a fresh token
(`page_monitor_record_upload_targets`), an inventory of the blocked inputs
only, so the first `upload_file` with that selector binds as above.
- Native `summary` controls and authored click/tab-focus targets use the same
observed owner and exact action footprint as native and ARIA buttons. Changed
handler identity, disclosure state, target/owner identity and user intervention
Expand Down
7 changes: 4 additions & 3 deletions docs/vision-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ A run decides once, when it starts (`_beginVisionRun` snapshots both settings),

Three things end vision mode inside a run for good, all proofs that only the accessibility tree can give. A site workflow whose completion is proved against the app-owned element inventory (`requiresLedger` in `adapter-workflow.js`, form and publish jobs of some adapters) cannot be certified from screenshots: once the planner or a live adoption selects one, `resolveVisionMode` answers `workflow_needs_element_inventory`. A request for a whole conversation (a thread to summarize in full, Gmail's `read-complete-thread` job) is answered only after trusted coverage through its last page with every message expanded (`read-completeness.js`): once that requirement is armed, it answers `complete_thread_read`. WhatsApp Web and Telegram's web app confirm who an open chat is with, and bind the chat they read and send in, through the element inventory (a contact check made with `click_ax`, chat reads and sends bound to refs): on their pages it answers `native_chat_needs_element_inventory` (`_visionNativeChatPage`, from the tab's last address). Either way the next step gets the tree tools and the tree prompt (the loop rebuilds the system prompt when the vision state flips).

A file upload leaves vision mode for a few steps only. A native file chooser opens outside the page, where screenshots cannot show or operate it, so a `click_at` that lands on a file input is stopped by the file-chooser guard (Chrome's CDP guard, Firefox's content-script guard, on every dispatch path). The batch pipeline then arms an upload handoff (`_visionNoteUploadHandoff` in `vision-runtime.js`, kept in the run's snapshot) and rewrites the result: the next steps use the element tools, `upload_file` with the blocked input's `selector` (the compact tier's discovery call instead), and screenshots come back after the upload; another file starts with another click on the control. Changes take effect only at step boundaries (`_visionUploadHandoffTick`, at the top of each step, before adapter notes, cookie banners and the step's tools), so the rest of the click's batch and its screen update stay in vision mode, and the upload's batch stays on the tree path. While the handoff is active `resolveVisionMode` answers `file_upload_needs_element_inventory`. It belongs to the page the tab shows when it starts (the click that armed it may itself push a route), and ends after a successful `upload_file` (the file is in the input in any build's success shape, not a discovery listing or an unconfirmed outcome), when the tab's address changes (the fragment aside), or after five decisions without an upload. Each file takes its own handoff, and a handoff that attached its file does not count against the run (a release with three assets takes three). Once two handoffs have ended without an upload (expired, or the page was left), a further chooser click is not handed off, and its result says why (`exhausted`: the earlier attempts attached nothing) rather than that uploads are unavailable in vision mode, which would be false after an upload that worked. Another chooser in the batch that armed a handoff is reported as handed off too: the armed handoff covers its input. The trace notes each handoff as `armed`, `ended` (`uploaded`, `navigated`, `expired`) or `refused` (`exhausted`, `unreachable`, `no_source`). Nested runs keep their own handoff with their snapshot.
A file upload leaves vision mode for a few steps only. A native file chooser opens outside the page, where screenshots cannot show or operate it, so a `click_at` that lands on a file input is stopped by the file-chooser guard (Chrome's CDP guard, Firefox's content-script guard, on every dispatch path). The batch pipeline then arms an upload handoff (`_visionNoteUploadHandoff` in `vision-runtime.js`, kept in the run's snapshot) and rewrites the result: the next steps use the element tools, `upload_file` with the blocked input's `selector` (the compact tier's discovery call instead), and screenshots come back after the upload; another file starts with another click on the control. Changes take effect only at step boundaries (`_visionUploadHandoffTick`, at the top of each step, before adapter notes, cookie banners and the step's tools), so the rest of the click's batch and its screen update stay in vision mode, and the upload's batch stays on the tree path. The click's result is how the model observed the input, which has no ref: when the handoff starts, before its first decision, the page monitor keeps the node each blocked input's `selector` names under a fresh file-inventory token (`_pageFeedbackObserveUploadTargets` in `page-feedback.js`, the monitor's `page_monitor_record_upload_targets`; not on the compact tier, whose `upload_file` takes no selector). The first `upload_file` with that selector is then covered by the step's page capture, or sent as an inventoried input with a `page-feedback-target-binding` note, instead of being refused as `target_uncovered` until the model had read `get_interactive_elements` and sent the same call again (two wasted steps per file). Any other selector, and a node the page renders later under the same selector, keep that refusal. While the handoff is active `resolveVisionMode` answers `file_upload_needs_element_inventory`. It belongs to the page the tab shows when it starts (the click that armed it may itself push a route), and ends after a successful `upload_file` (the file is in the input in any build's success shape, not a discovery listing or an unconfirmed outcome), when the tab's address changes (the fragment aside), or after five decisions without an upload. Each file takes its own handoff, and a handoff that attached its file does not count against the run (a release with three assets takes three). Once two handoffs have ended without an upload (expired, or the page was left), a further chooser click is not handed off, and its result says why (`exhausted`: the earlier attempts attached nothing) rather than that uploads are unavailable in vision mode, which would be false after an upload that worked. Another chooser in the batch that armed a handoff is reported as handed off too: the armed handoff covers its input. The trace notes each handoff as `armed`, `ended` (`uploaded`, `navigated`, `expired`) or `refused` (`exhausted`, `unreachable`, `no_source`). Nested runs keep their own handoff with their snapshot.

No handoff is armed when the upload could not succeed on the tree path either, and the click result says why instead: when only Chrome's protocol guard saw the chooser (`protocolOnly`: an input in a frame or a closed shadow root, which `upload_file` cannot reach), and on Chrome's compact tier when the run's message has no attachment (`_visionUploadSourceAvailable`; compact `upload_file` attaches only those).

Expand Down Expand Up @@ -183,9 +183,10 @@ npm run test:vision-mode
```

- `test/vision-mode.mjs`: sizing, profiles, token formulas, diffs, micro changes, sessions, translation, tool surfaces of every tier in both builds, permission classification, prompt, the page-feedback policy for vision calls and the monitor's vision rebase.
- `test/vision-mode-agent.mjs`: the real run loop in both builds (streaming and not) with a scripted vision model and a fake screen: first frame, tool surface, click translation (and a `click_at` in a turn that does not offer it, such as a completion recovery turn, refused before it can run as a click), patches, "no visible change", new frames on navigation, pruning, stale and moved points, native tools (the page monitor prepared with `hover_at`'s and `drag_at`'s CSS points, a wheel with none), fallback to the tree; a refused click stopping its batch, tool and encoding failures, a failed first capture in a later run, a model switch between runs, drag/wheel/repeated-click refusals, `drag_at` on a protected messaging page; the settle wait on a caret or clock versus real motion; page churn before a decision (no tree read, nothing re-sent while the screen stays as seen, a change after the update shown, a person's input always reported); site notes in vision terms; a ledger workflow switching the run to the tree path; a drag after an earlier action of its batch re-checked at both ends; a window resized while the model decides (a fresh full frame first) and one page feedback has not reported yet (the batch's points refused); a `take_screenshot` and `done` in one batch after an update that could not be captured, and a `drag_at` finished in its own batch, both refused as unverified; a zoom after an earlier action of its batch usable beside that batch's patch except where the screen changed after it; in Chrome, a click that opened no tab costing no poll, tabs opened at once or later being rerouted, and a late-tab watch staying armed past an update and rerouting a tab opened while the screen settled.
- `test/vision-mode-guards.mjs`: the guards in a vision run, both builds: a vision click deleting the item the request names is bound to the control read at its point (the same point outside a vision run stays unbound), the deletion card, a changed item, a forbidden item and Enter keep their rules, and an authorized deletion is read again at dispatch, sending nothing when the control moved away; loop detection by screen (repeats after a change are progress, on an unchanged screen they stop), vision loop messages, native dropdowns, arrow keys and clicks without DOM-snapshot verdicts, page-focus churn, the delivery checkpoint, one screen update per batch however it ended; whole-thread reads and WhatsApp/Telegram on the tree path, the Gmail counter, observations and refusal wording after an action (a requested frame verifying nothing until captured, a drag opening verification debt), on-screen status texts, the opening frame as evidence, the CAPTCHA root read and the toolbar check keeping the vision dispatch.
- `test/vision-mode-agent.mjs`: the real run loop in both builds (streaming and not) with a scripted vision model and a fake screen: first frame, tool surface, click translation (and a `click_at` in a turn that does not offer it, such as a completion recovery turn, refused before it can run as a click), patches, "no visible change", new frames on navigation, pruning, stale and moved points, native tools (the page monitor prepared with `hover_at`'s and `drag_at`'s CSS points, a wheel with none), fallback to the tree; a refused click stopping its batch, tool and encoding failures, a failed first capture in a later run, a model switch between runs, drag/wheel/repeated-click refusals, `drag_at` on a protected messaging page; the settle wait on a caret or clock versus real motion; page churn before a decision (no tree read, nothing re-sent while the screen stays as seen, a change after the update shown, a person's input always reported); site notes in vision terms; a ledger workflow switching the run to the tree path; a drag after an earlier action of its batch re-checked at both ends; a window resized while the model decides (a fresh full frame first) and one page feedback has not reported yet (the batch's points refused); a `take_screenshot` and `done` in one batch after an update that could not be captured, and a `drag_at` finished in its own batch, both refused as unverified; a zoom after an earlier action of its batch usable beside that batch's patch except where the screen changed after it; in Chrome, a click that opened no tab costing no poll, tabs opened at once or later being rerouted, and a late-tab watch staying armed past an update and rerouting a tab opened while the screen settled; on a hidden file input no page observation covers, the first `upload_file` of a handoff with the blocked click's selector passing the page-action binding check, and a guessed selector refused.
- `test/vision-mode-guards.mjs`: the guards in a vision run, both builds: a vision click deleting the item the request names is bound to the control read at its point (the same point outside a vision run stays unbound), the deletion card, a changed item, a forbidden item and Enter keep their rules, and an authorized deletion is read again at dispatch, sending nothing when the control moved away; loop detection by screen (repeats after a change are progress, on an unchanged screen they stop), vision loop messages, native dropdowns, arrow keys and clicks without DOM-snapshot verdicts, page-focus churn, the delivery checkpoint, one screen update per batch however it ended; whole-thread reads and WhatsApp/Telegram on the tree path, the Gmail counter, observations and refusal wording after an action (a requested frame verifying nothing until captured, a drag opening verification debt), on-screen status texts, the opening frame as evidence, the CAPTCHA root read and the toolbar check keeping the vision dispatch; an upload handoff observing the inputs its batch blocked for the page-action binding check (the first `upload_file` attaches with a note, Strict checks refuse it as for an inventory, a guessed selector, a re-mounted input and another document's answer stay refused, the compact tier observes nothing).
- `test/quality/vision-mode-delete-mv3.mjs` (`--vague`): the installed MV3 extension deleting a note with `click_at` on its Delete button and on the site's own confirmation; a request that names the note asks nothing, "delete it" asks once on WebBrain's card, and exactly that note is deleted once.
- `test/quality/vision-mode-upload-mv3.mjs`: the installed MV3 extension clicking an upload control with `click_at`: the file-chooser guard stops the chooser, the next step attaches the user's clip with one `upload_file` and the step after it is back on a fresh frame, both for a visible file input and for a release form whose hidden binary input sits on a page too deep for the page observation to certify (the clip reaches that input, not the Markdown one).
- `test/quality/vision-mode-repeat-mv3.mjs`: the installed MV3 extension pressing a "+" button ten times at the same point (its counter is drawn on a canvas, so the DOM text never changes): all ten presses go out, and a button that does nothing is then stopped by the loop guard, in vision terms.
- `test/monitor-vision-wheel.mjs`: the real page monitor in Chromium: two wheel notches fenced before their events arrive stay the agent's (a third, unfenced one is a person's), page churn does not void a prepared wheel, a person's click does.
- `test/vision-pipeline-browser.mjs`: Chromium at DPR 1/2/3 on a scrolled page: planned size, exact mapping, trusted clicks, diff regions, WebP vs JPEG; the Firefox content input path (caret placement, word and line selection, pointer transitions, wheel routing, HTML5 drag and drop, drags in a same-origin frame, native range inputs (dragged, and clicked on the track), text selection, disabled controls, the file-chooser guard, CSS `:hover` revealed under the pointer (a CSS-only menu, a selector listed beside a `:hover` keeping its own cascade, a later rule of equal specificity still winning, a rule inside `@media`, a shadow root's `:host(:hover)`, a rule added later) and ended by a person's own pointer with the page's selectors put back, pointer capture taken on the press: a thumb with its handlers on itself, a track that captures its thumb, a control that drops the implicit capture, run as from the page and as from a Firefox content script; clicks and drags in a scaled frame and in a frame inside a scaled ancestor, a rotated frame and another origin's frame refused); the DOM probe and the Chrome click submit probe in a scaled, bordered frame; the DOM probe on buttons, labelled text fields, focused number fields, selects, link cards, sliders and same-/cross-origin frames; the click submit probe at a vision point inside open shadow roots and on plain content, in both builds.
Expand Down
3 changes: 2 additions & 1 deletion src/chrome/src/agent/guard-policy.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading