Skip to content

1.33.0: browser_partition for parallel agents (#20), real keys (#76), Leave site? only with leave_page (#90) - #77

Draft
GLouv wants to merge 172 commits into
mainfrom
feat/1.32-partitioner
Draft

GLouv wants to merge 172 commits into
mainfrom
feat/1.32-partitioner

Conversation

@GLouv

@GLouv GLouv commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

1.33.0 (includes the 1.32 work below)

1.32 was never released on its own; this PR ships as 1.33.0, with main (1.31.0) merged in. On top of the 1.32 work described below, 1.33 answers a private report: browser_handle_dialog accepts "Leave site?" only with leave_page: true (#90), no call guesses another tab, browser_wait waits for something that can be seen, closing the last tab no longer closes Chrome, and a screenshot path is a .png file that never replaces a file by accident. Full text: CHANGELOG.md, 1.33.0.

Also in 1.33: a step outside the browser points somewhere. When what is asked for is not in a web page at all (a desktop app, an OS-level dialog, the native file picker once it has opened as an OS dialog), the server's instructions hand that step to desktop tools when the session has them, keeping the web part in the Chrome the user is signed in to. When there are none and the user is on macOS, the agent names Computer MCP once with its install line; it never installs anything. Reviewed in P183; pairs with Agent360dk/computerMCP#10.


1.32.0: browser_partition - one partition per parallel agent (#20), real keys (#76), and honest answers when Chrome stalls

Builds the idea from #20 by @ugbodagadave (a listener per partition, a partition parameter on the browser tools) on the lanes that 1.31 (#75) introduced. The commit that adds the tool carries Co-authored-by: ugbodagadave, and the CHANGELOG credits #20. #20 will be closed with a link when this is merged. Thanks to @arasmileska for #76.

What changes (full text in CHANGELOG.md, 1.32.0)

  • Partitions (Multi-partition support: isolated browser sessions for parallel agents #20). browser_partition({action: 'new', profile?, label?}) opens a partition with its own listener port, tab group and active tab; {action: 'close', partition} closes its tabs and frees the port. Ids are not reused while the server runs; at most 4 per server. partition works on every browser tool. New errors: partition-not-found, partition-closed, partition-limit, port-capacity-exhausted.
  • Session ownership. A delayed clean-up only applies to the connection it was ordered for. A call from a connection that has closed, or from a bridge that was replaced or vanished, runs nothing (connection-closed, sent: false) once the extension has registered it: at once for a closed connection or a replaced bridge, and for a bridge that closes by itself at the latest at the next heartbeat (scheduled once a minute, Chrome may run it later), and taking over an orphaned session re-checks, right before it changes anything, that the session is still orphaned.
  • Keys (browser_press_key: Enter sends no keypress; punctuation keys (",") get keyCode 0 and an empty code #76). browser_press_key sends Enter and punctuation like a real keyboard. In browser_fill, line breaks are written as line breaks only in text areas and editable fields, as one space only in single-line text inputs, and left out everywhere else, so a line break does not become a space-key press on a button, submit button or checkbox the tool recognises (a page that moves focus in the moment between that check and the key gets the space like any other typed character); the answer says which (line_breaks_as_spaces, line_breaks_left_out).
  • When Chrome stalls. On a page that reloads itself many times a second, Chrome's own browser thread can stall. The server now waits 15 s longer for browser_execute_script and browser_select_frame, and if still no answer arrives it says no-answer, sent: "unknown" (the code MAY have run; not repeated). One late answer no longer tears down the extension's bridge.

Base: this branch contains 1.31 (#75) and is released after the Chrome Web Store serves 1.31.0. Versions stay at 1.30.3; the bump comes in a separate prepare PR.

Evidence (on 9fd6227; 9dc101b only adds the honesty result file)

  • Full suite in a clean copy: 1711 tests, 0 failed. Mutants: 275 in the 1.32 list, all killed (each checked to be valid JavaScript first).
  • Honesty measurement on GitHub's machine (run 39285911262): 0 LOEGN, 41/41 tools, 53 OK, 0 FEJL. test/aerlighed/RESULTAT-2026-10-19-132-gh.md.
  • Fourteen review rounds by two independent reviewers (R138-R151); every fault and coverage gap they found has a test that was red before the fix. Approved: product code 0ee3664 in real headless Chrome (R145), coverage and correctness on 9fd6227 (R151). The product code is identical in both.
  • Measured in real headless Chrome: four agents in parallel see only their own tabs; frame identity from 1.31 holds with partitions; no line break pressed a button, submitted a form or ticked a box when focus jumped there mid-fill; a bridge that vanishes is marked at the next heartbeat; a false hasDocument alone no longer marks a live bridge dead.

🤖 Generated with Claude Code

Gustav Louv and others added 30 commits October 15, 2026 10:20
… was ordered for

Astra measured (R107) that a delayed releaseSession(port) from a closed connection closed tab 99 of a NEW
session on the same port and deleted that session: the extension keys sessions on the port alone.

- offscreen.js gives every WebSocket a generation (random per document + counter); mcp_command,
  session_disconnect and terminate_mcp_session carry it. A terminate ordered for another generation does
  not close the connection that holds the port now.
- background.js: a session records the generation that owns it (gen) and each tab the generation that
  opened it (faneGen), persisted and restored. A command from a new generation takes the session over.
  A release for an older generation closes only that generation's tabs; a release for the session's own
  generation takes the session out of the map before the first await, so a command arriving meanwhile
  gets a fresh session. The voluntary-release mark is bound to the generation it was ordered for.
- Messages without a generation (a bridge from before 1.32) behave as before.

test/sessionsgeneration.test.mjs: 11 tests (9 red before the fix, log in ~/.claude/plans/browsermcp-132/).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d persist only live tabs' generations

The tab-cap source guard reads the first 500 characters of addTabToSession; the generation line pushed the
eviction condition past it (suite-a90c542: 1 of 3 failures; the other two are load-timing tests that pass
3 of 3 in isolation). Persisted faneGen now only holds tabs still in the session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… built on the lanes (#20)

The idea is ugbodagadave's (PR #20): an extra listener per partition, so the extension gives each one its own
tab group and active tab, and a `partition` parameter on every browser tool. It is built on the 1.31 lanes
instead of the PR's own listener (R106 measured five faults in it): same createWSS, reply check, null guard,
profile choice and installation lock, and pid null so the extension never adopts another lane's tabs.

- browser_partition({action:'new', profile?, label?} | {action:'close', partition}). Ids p1, p2, ... are
  never reused. "new" binds the partition's port and chooses its installation right away by the 1.31 rules
  (profile = BROWSER_MCP_PROFILE for that lane; without it the client's BROWSER_MCP_PROFILE), through a
  harmless list_tabs. Errors: partition-limit (4 open per server), port-capacity-exhausted, profile-*
  (no partition is created and its port is released), profile-invalid.
- "close" marks the partition closed first, closes its tabs through its own connection and releases the
  port: port_released, and tabs_closed true only when the extension confirmed it (or no tool call was ever
  routed to it), otherwise "unknown".
- `partition` on every browser tool including browser_extract_token; it is stripped before the call is
  sent. Unknown id -> partition-not-found, closed -> partition-closed, sent:false; never the default lane.
- browser_about lists the partitions; a partition call gets its own lane's notes; INSTRUCTIONS explain it.

Guard tests updated where they read the source: the single response wrapper now passes the lane,
browser_partition is server-local, and `partition` is a server-level parameter (not in set_combobox's case).
test/partitioner.test.mjs: 22 tests (20 written first, 20 of 20 red against 6ddcbe0; log in
~/.claude/plans/browsermcp-132/). The surfaces (README table and tool count) follow in the next commit.

Co-authored-by: ugbodagadave <ugbodagadave@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…face; 41 tools; flow harness covers it

- CHANGELOG: "## 1.32.0 (not released yet)" above 1.31.0, crediting @ugbodagadave and #20, with the new answers.
- README (and the byte-identical npm README): a Parallel agents tool row, a "Parallel agents in one chat"
  section with every answer, browser_about lists partitions; 40 -> 41 tools.
- content/ + regenerated docs/: the tools page gets a "Parallel agents - 1 tool" section and the partition
  parameter; the five install pages that list every tool get a row; 40 -> 41 tools wherever the count is
  claimed (check-docs.py: all green, tool count 41). llms.txt, llms-install.md, server.json x2,
  gemini-extension.json, mcpb-manifest.json, both extension manifests (description only), CONTRIBUTING.
- test/flow/run.mjs: a browser_partition step (new -> navigate in it -> its own list has the tab, the
  default list does not -> close with tabs_closed true), so the release gate does not see it as uncovered.

Versions stay 1.30.3; the bump is the prepare PR's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e more partition tests

- nyPartition puts the lane's choice note (several installations, no profile) in new's own answer and
  clears it, instead of leaving it for the partition's next call.
- Tests: that note; a call (and a second close) while close is still closing the tabs answers
  partition-closed and sends nothing; a call on a partition gets the partition lane's own connection
  warning through the response wrapper (real server), not the default lane's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… follows the call that now carries its lane

suite-2e4e12c: 1541 of 1543. billedkilder-tal read "40 tools" in demo-video-src (the rendered pictures are
rebuilt from these sources at release); ejerskab looked for the send call without its lane argument.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s under a mutant fails instead of holding the run

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…also frees closed partitions' lanes

Mutant P18 (no port release when closed during the bind) survived on 6dbd045, and P2 timed out because a
closed lane outside `baner` kept a listener open after the test. Both covered now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…kills it before failing

aegteServer asserted on initialize before it had handed the test a `luk`, so a slow start left the spawned
server alive and the test process never exited (measured at load ~140: three servers survived, the run hung
after its last test). The server is now killed and its HOME removed before the failure, and the start gets
20 s inside the test's 60 s. Proven with a server that never answers: the old form hung until the 150 s
alarm, the new form fails red in 7 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…itioner

No conflicts. Brings the R119/R120 fixes into 1.32: one ambiguity verdict
for tool and diagnosis, the first-choice wait guarded on both sides, test
deadlines and cleanup, the 1.31 honesty result, and CI job timeouts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… never rebinds the port or sends

Astra measured (R121, model) that close did not stop a call that was waiting for the partition's
installation: when the installation came back, the call bound the closed partition's port again and sent
its command after close had answered port_released: true. The same happened to new's own list_tabs, which
answered partition-closed but left the rebound port behind.

- A partition's lane is now closed for good (bane.lukket), and close wakes every call waiting on it
  (retry wait and first-choice wait are interruptible).
- sendTilBane stops a call on a closed lane at the entry, before the port is bound, and after each
  wait, before anything is sent. If close finished while a bind the call started was still in flight,
  the port is released there. Close's own calls (closing the tabs) are exempt.

Tests, each red on e166751 before the fix (R121-roed-foer-rettelse.log): close during the retry wait
(answered at once, no new bind, nothing sent), close during new's wait, a call that starts in the same
moment as close, close while a call rebinds the port, and close during the first-choice wait.

Test harness: aegteServer registers its server with 1.31's sporProces at creation, so the file's after()
kills it even when a test is cut off by its deadline. Sync waits in partitioner.test.mjs wait up to 15 s,
calls have a 15 s answer deadline and the bridge retry is 300 ms (measured red without a mutant at load
100-290). broModel can change the retry time during a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed; how new chooses without a profile

README (both copies), the docs page and the tool description said tabs_closed: true comes only after the
extension confirmed it. The code also answers true, without asking, for a partition no tool call ever used
(the CHANGELOG already said so). The tool description said a partition without profile takes "the first
installation that connects"; it makes the default partition's choice: it waits about 2.5 seconds, then
takes the newest extension version, on a tie the one that connected last, and says which one.

check-docs: all green (41 tools, 42 pages, 43 sitemap URLs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…empt

If the partition's connection disappears while close is closing its tabs, close does not wait for it to
come back: the retry stops at the closed lane, and close answers tabs_closed "unknown" as before. This
removes the exemption from the retry path, which no test could tell apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the retry wait proof cannot race

Mutant run on c567858 (120 of 122): Q2 (no partition-closed in baneForKald) survived, because the new
lane guard answered partition-closed too. The closed-id answer now has to carry the door's advice (create
a new partition), not the note of a call that was waiting. Q26 (uninterruptible retry wait) survived on a
race between the 30 s retry and the 15 s bound; the retry is 60 s and the bound 10 s now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nnected last", not the millisecond

Astra measured (R122, model) that two installations with the same version and the same `since`
millisecond made new choose the first one, while the tool description promises the one that connected
last. activeConnection now breaks a version tie on the connection counter `seq` alone: `since` is only
set when the connection is made, so `seq` is the same order without ties.

Regression test (red before the fix, R122-roed-foer-rettelse.log): two installations, same version,
same since; new must choose and send list_tabs to the one with the highest seq. README (both copies)
now states the tie rule as well. check-docs: all green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… running command on a closed partition answers partition-closed

Opus measured (R122, real headless Chrome) that a call to a partition id that new was still creating got
the lane, because ids are predictable. When new then gave up (profile-not-connected, "port released"),
the call kept waiting, bound the port again and, once the profile came back, sent its command; the port
stayed bound in both runs.

Two ways to close it were on the table: let opgiv() mark the lane closed and wake its waiters, or refuse
the id until new has answered. The second has the fewest races: no call can touch the lane while new is
choosing its installation, so a call can neither lock the lane before new's own list_tabs nor survive
new giving up, and opgiv() needs no new state. baneForKald now answers partition-not-found ("still being
created") until new returns the id, and browser_about shows such a partition as state "creating".

A command that was already running when close came used to get "the connection disappeared ... start
Chrome again", which sends the agent after the wrong cause. It now answers partition-closed with
sent: true.

Tests (red before the fix, R122b-roed-foer-rettelse.log): a call during new + new fails -> no rebind,
nothing sent, port free, partition-not-found; the running-command case answers partition-closed,
sent: true. The model now closes every listener the bridge created, also one on a lane that no longer
exists (on the unfixed source the leaked port kept the test process alive). The R121 first-choice-wait
test measures its wait from now, not from the port's bind (under load the wait was already over).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…artition-closed; creating state

Every surface said "ids are never reused"; that holds while the server runs, and after a restart the
numbering starts at p1 again. README, CHANGELOG and the tool description now also say that new answers
partition-closed when the partition is closed while it is being created, that a command already running
when close came answers partition-closed with sent: true, that a partition new is still creating answers
partition-not-found, and (CHANGELOG) that browser_about's state is creating, open or closed.

check-docs: all green (41 tools, 42 pages, 43 sitemap URLs). README copies byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uto-reload only uses a lane that can be used

Astra measured (R123, model, normal waits) that auto-reload (BROWSER_MCP_EXTENSION_UPDATED=1, set by the
CLI's extension update) sent reload_extension on the lane whose connection came first, without going
through baneForKald. If that was a partition being created and new gave up (profile-ambiguous, "port
released"), the reload call bound the port again and sent once one installation came back.

- opgiv() marks the lane closed and wakes its waiters, so no sender - also an internal one that skips
  baneForKald - can bind the port again or send after new gave up (stopHvisLukket).
- Auto-reload uses only a lane that can be used: the default lane, or an open partition new has
  answered. Otherwise the update is kept for the next connection on such a lane, also when the lane
  closes within the reload second. The other senders were checked: tool calls and extract_token go
  through baneForKald, and only new's and close's own calls touch a lane directly.

Runtime texts: the instructions tell partition-closed with sent: false (nothing sent) from sent: true
(sent, may have been carried out); the sent: true answer no longer claims the tabs are closed, it points
to close's tabs_closed; "ids are never reused" says "while this server runs".

Tests, red on 5f3b096 (R123-roed-foer-rettelse.log): auto-reload on a partition new abandons (no rebind,
nothing sent, update kept for the default lane), an internal sender during creation (Astra's control),
and an open partition closed within the reload second (update not lost).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… still waiting when close releases the port

Astra measured a normal success answer for a command the extension answered while close's list_tabs was
still waiting. README (both copies), CHANGELOG and the tool description now limit the promise to what the
code does: a command still waiting for its answer when close releases the port answers partition-closed
with sent: true (it may have been carried out); one answered before that gets its normal answer.

check-docs: all green (41 tools, 42 pages, 43 sitemap URLs). README copies byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ters

Waking changed nothing a caller can see: a waiting call is stopped at its next check (at most one retry
later) and binds or sends nothing before it; the only sender that can wait there is the internal
auto-reload, whose answer is not shown. A mutant without the wake could therefore not be told apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and promises no tab closing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… at this process's bridge

The full suite on 77e35fb failed once in profilvalg.test.mjs: a third installation connected to its port.
node --test runs the files in parallel, and partitioner.test.mjs's returning scanner (tilbage) kept
dialling a port the partition had released - which another test process may bind. The scanner now
connects only while the bridge in this process listens on the port (broModel.lytterPaa), and the
"nothing listens" checks ask the bridge instead of binding the port at the OS level. The R121, R122 and
R123 tests are still red against e166751, 71c4305 and 5f3b096.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nstruction says which connections get it

R124 (Astra): close's two answers still said 'The id is not reused' without
the per-process qualifier. R124 (Opus): the instructions said the server
'tells the connected extension to reload itself', but the reload only goes
to a connection on the default partition or an open partition, never while
a partition is being created. Text only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(0 LOEGN)

Run 38787187360, macos-26-arm64: MAALT-COMMIT d347043, AERLIGHED-DOM 0 LOEGN,
41/41 tools, 53 OK, 0 FEJL. The four bound files are byte-identical in
d347043 and this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion with its key code and code

Reported by @arasmileska (#76, 1.30.3, Windows 11, Chrome 155): Enter was sent without text, so the page
got keydown/keyup 13 but no keypress, and "," got keyCode 0 and code "," (not a code Chrome knows). Tag
and chip inputs that commit on Enter or comma by keypress or keyCode 188 never made the tag.

- Enter sends text "\r" and unmodifiedText "\r" on keyDown, as Chrome's own Enter does. With ctrl, meta
  or alt it stays a shortcut without text (#68).
- A one-character key gets its code from the US-layout table browser_type already uses (cdpCodeForChar),
  and its key code from that physical key: "," 188 Comma, "." 190 Period, "-" 189, "=" 187, ";" 186,
  "'" 222, "/" 191, "\" 220, "[" 219, "]" 221, "`" 192. A character not on a US keyboard gets no code
  instead of itself. An explicit code still wins.
- A shifted character (< > ? : " { } | _ + ~ ! @ # $ % ^ & * ( )) is that physical key with Shift held,
  as on a real US keyboard: "<" is keyCode 188, code Comma, and Shift is in modifiers, so shiftKey is true
  on the page as it would be. Without Shift a page that checks shiftKey would see a key no keyboard sends.

Tests (test/tast-76.test.mjs): red before the fix on the CDP parameters (76-roed-foer-rettelse.log),
except the guard that ctrl/meta/alt+Enter gets no text. The extension copy in mcp-server/ is identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@arasmileska

CHANGELOG under 1.32.0 credits @arasmileska for #76. README (both copies), the docs page and the tool
description say that Enter gives keypress 13, punctuation has its key code and code, and shifted
characters are sent with Shift; the code parameter says it defaults to the key's place in the US layout.
check-docs: all green (41 tools, 42 pages, 43 sitemap URLs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…'s per-character typing, clearing, Tab, PageUp/Down

The same class as #76 sat in browser_fill's per-character typing (typeCharsAttached, used for text=
selectors and when a field does not hear the value setter): it sent a code but no key code, so keydown
had keyCode 0 for punctuation, and a tag input listening for keyCode 188 never got its comma. The clear
(ctrl/cmd+a, Backspace), the masked-date Tab and the date picker's PageUp/PageDown also had no key code.

One shared helper, fysiskTast(key), now gives code, key code and Shift for every sender, with the rule
press_key got in f28a6d1: the US-layout code table (cdpCodeForChar), the key code of that physical key,
and Shift held for a shifted character. press_key uses it too. What is typed does not change: each
character is still sent with itself as text. "\n" and "\t" stay as before (no Enter or Tab key code):
turned into real Enter/Tab keys they could submit a form or move focus in the middle of the text.
Capital letters get no Shift, as press_key never gave them.

The code comment called the typing path "browser_type"; there is no such tool here. It is browser_fill.

Tests (red on ca8d8a0, 76b-roed-foer-rettelse.log): typing "a,<b" gives 188 Comma for "," and "<" with
Shift, and the same text; "\n"/"\t" unchanged; clearing sends 65 and 8; and a source check that every
key sent over CDP carries its key code. tast-slippes' source check now allows the key code after code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… key codes

One line under the #76 paragraph in CHANGELOG. The browser_fill descriptions (tool, README, docs) say
nothing about key codes, so they stay as they are. check-docs: all green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…al letter gets no Shift

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ce, emoji, "\n", and set_date's clearing

R125 (Astra and Opus) on 52afb28:
- browser_fill text= into a tag field: the keys reached the page, the page made "tag," a tag and emptied the
  field, and the answer was field-is-empty with "switch tab and try again" - doing so added the tag twice
  (Astra, model). Typing character by character now measures delivery with press_key's listener
  (skrivTegnMedBevis). Keys delivered and an empty field: ok:true, differs, keys_delivered:true, "the page
  has handled the text, check it before typing again". Unknown delivery: check the page first, retry only if
  nothing happened. The direct retry advice is given only when no key reached the page. Same rule in the
  CSS path's per-character fallback (fyldSvar).
- press_key("Space"): the page got e.key "" and the delivery proof waited for "Space", so a key that landed
  was answered key-not-delivered (Opus, Chrome). "Space" is now the space bar: key " ", code Space, 32, and a
  space as text (not with ctrl/meta/alt).
- A key that is one code point is a character (an emoji too) and never gets itself as code; only a name like
  "Enter" or "F5" is its own code (Astra: "😀" got code "😀").
- "\n" and "\t" in fill no longer carry code "Enter"/"Tab": a page listening for e.code === 'Enter' was
  triggered by a line break in the value (Opus).
- set_date's masked path: the clear (cmd/ctrl+a, Backspace) did not select all on macOS, and a prefilled
  field ended as "16/10/202601/01/2000" (Opus, Chrome). The select-all now carries Chrome's selectAll
  command (#68). A thrown calendar path is now an answer, and when every path fails after the field was
  written, the answer says so (value_before, field_changed) instead of a bare error.

Tests (red on 52afb28, R125-roed-foer-rettelse.log): tag field via fill text= (no retry advice), unknown
delivery, fyldSvar with delivered keys, set_date on a prefilled masked field and the failing-paths answer,
Space, emoji, "\n"/"\t" without code, and selectAll in the clear. The background-tab case keeps its advice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gustav Louv and others added 6 commits October 23, 2026 15:10
…ls on; the frame binding is dropped

Blocking in R178:
- Astra (measured in the model, confirmed in Chrome 155 with her fixture): ff98f01 let an inner
  scroll box's place replace BOTH axes, so an outer box that clips on the axis the inner one does
  not scroll on lost the text's position - a false found in ordinary clipped panels (a regression
  from da14743). The box's place now replaces only the axes it can scroll on. This was the first
  version; a "simplification" removed it without a test that told the two apart.
- Opus' unfinished R178 measured in Chrome that the frame binding from ff98f01 closed the prerender
  race (35/35), but dialogs from a cross-site iframe were no longer answered: such a frame runs in
  another process and is not in Page.getFrameTree (on da14743 they are answered). That is a
  regression from 1.32 in an ordinary case, and telling an out-of-process iframe from a new
  prerendered main frame needs event-order data nobody has measured. The binding is removed; the
  dialog code is da14743's again, which R177 approved in Chrome, and the race (a dialog within
  about 10 ms of a prerender activation) is a known rest, as both reviews classified it. A comment
  says why, and a test keeps a cross-site iframe's dialog answered.
- Surfaces: the sentence about frames is gone; handle_dialog reads as on da14743 everywhere.

Mutants: the both-axes version, text-based clipping (two axes), swapped axes, the whole ff98f01
binding, 3A-6, the lookup after the final check, and "loading" not asking - all killed. Chrome:
Opus' builders v2-v5 without new deviations, the eight fixtures 67/67, Astra's R177 fixture 3/3,
Astra's R178 fixture hidden on both axes (false found on f3fcad7). Suite: 1878/1878.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ast content commit, 877a40c)

The regen-diff gate failed on 877a40c: the two generated files were restored from da14743 and kept
its date (2026-10-22), while the generator dates them from the last commit that touched
content/browsermcp-docs-tools.md (877a40c, 2026-10-23). Only the date changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…12.0, 2026-10-22)

The sentence paired 59 tools with v1.10.1 (58) and September's star count. v1.12.0 has 59 tools; the
60th (configure_webauthn) is only on their main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Versions from main (1.31.0), descriptions and the 1.32/1.33 changelog sections from this branch (41 tools).
Site regenerated; docs and competitor guards green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng, email as fallback

Private vulnerability reporting is enabled on the repository. The address hello@agent360.dk stays as the
fallback for reporters who cannot use GitHub.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ased on its own)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@GLouv GLouv changed the title 1.32.0: browser_partition for parallel agents (#20), real keys (#76), honest answers when Chrome stalls 1.33.0: browser_partition for parallel agents (#20), real keys (#76), Leave site? only with leave_page (#90) Oct 29, 2026
Gustav Louv and others added 5 commits October 29, 2026 13:33
The docs gate correctly flagged the line as calling 1.31.0 unreleased after v1.31.0 was tagged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The docs gate correctly flagged the line as calling 1.31.0 unreleased after v1.31.0 was tagged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hed 29/10

P182-Opus: 1.32 is never released on its own, so 'from 1.32' pointed to a version that will not exist (15 places,
regenerated pages follow). CHANGELOG dates the release by its GitHub publication (2026-10-29), and the 1.33 notes
say that the 1.32 work follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p tools in the user's own Chrome, or name Computer MCP once with its install line

Gustav 29/10: browser-mcp hands over to computer-mcp when it can't do a step; without it, the agent tells the user how to get it.
Kept to the family 19/9 measured as truly outside the page (desktop app, OS dialog, native file picker, menu bar); never installs.
test/server-instructions.test.mjs pins the four new parts (each mutation-proven).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gustav Louv and others added 2 commits October 29, 2026 16:11
…ons' template literal and the server did not start

Caught by the flow gate (spaerre) on 2ad544e. The text tests read the source as text and could not see it; a new test runs
node --check on the server file (proven red on the unescaped line). Server started once on ports 19950-19954: initialize answers
with the new paragraph.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r MCP named only on macOS, changelog says what the instruction says, guard against the mention spreading

Opus P183: GODKENDT MED RETTELSER (B1, B2, A1, A2 applied verbatim). New guards proven red: an extra Computer MCP mention on the
CAPTCHA line, and the file picker without its OS-dialog scope. Server started once on ports 19950-19954 and sent the line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gustav Louv and others added 3 commits October 30, 2026 13:34
…00, npm last month 3,849)

The live page said 6,052 npm downloads last month; npm's last month (29/9-28/10) is 3,849, so the page overstated. Written by
scripts/stjerner.py, which #81 will run on every build; until then by hand. stjerner.py --tjek OK, docs gate green, forside 25/25.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…regen gate)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
So 1.33 carries private vulnerability reporting and the corrected front-page numbers whenever #93 itself is merged.
CHANGELOG: #93's only change there is the 1.31.0 date, which 1.33 already has, so 1.33's file stands. Sitemap regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GLouv pushed a commit that referenced this pull request Oct 30, 2026
…nto 1.34

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gustav Louv and others added 2 commits November 1, 2026 04:35
… (R189)

npm reaches users minutes after a release; the Chrome Web Store update days later. Until the
extension is 1.33.0, it accepts any dialog - "Leave site?" too - and keeps an arming for 60 s,
also across a page change, while 1.33's tool text promises the opposite.

The server now refuses browser_handle_dialog with an accept (also the default) and without
leave_page: true to an extension older than 1.33.0 or one that reports no version
(extension-too-old-for-leave-page, nothing sent), and caps the arming it sends at 30 s.
A 1.33.0+ extension gets the call untouched. "Everything else works meanwhile" (three places)
now says that newer features and safeguards wait for the update.

test/dialog-gammel-udvidelse.test.mjs: 7 tests on the real bridge with fake extensions
(1.30.3, 1.31.0, no version, 1.33.0, 1.34.0); four mutants (guard removed, version flipped,
default action bound to accept, cap removed) each turn it red. Full suite 1886/1886.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
R190 (Astra + Opus): both approved 74c8ec2 with corrections, all taken.
- The guard is decided by what the extension reports, not only by its version: the 1.33
  extension says dialog_leave_page: true in its hello (offscreen.js, three places). The
  branch's own extension is 1.31.0 in its manifest until --prepare, so the version alone
  refused it and the flow test's handle_dialog accept would fail. The version stays as a
  fallback for an extension that does not report the capability.
- The note and tool text say what the older extension actually does: it knows a
  "Leave site?" but does not enforce leave_page, and an arming it is sent (capped at 30 s)
  still survives a page or tab change. The page/tab-change drop is now stated for 1.33.0+.
- Tests: a list_tabs barrier before counting zero sends (a send-then-refuse mutant had
  passed), timeout 0, wait: true, the capability on a 1.31.0, the same installation
  updated and downgraded, and a partition lane next to the default lane. 9 mutants red.
- test/udgivelse-script.test.mjs: git clone --local --no-hardlinks; on CI's node 24 runner
  /tmp is another disk and the hard links failed (Invalid cross-device link).

Full suite 1890/1890; docs gate green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GLouv pushed a commit that referenced this pull request Nov 1, 2026
Gustav Louv and others added 6 commits November 10, 2026 01:08
…puter MCP

npm only has computer-mcp 0.1.0, which GHSA-pc3h covers (< 0.4.1). Until 0.4.1 is
published, Browser MCP must not hand users an install command for it. The server
instruction, both READMEs and the changelog now point to computermcp.dev, which says
itself what is published. A test guards against an npx/claude mcp add line returning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YNkLWcoDV5PGezcYGMAAoT
…0 (review)

The independent review found that computermcp.dev itself shows the npx command, so
"how to install it is at computermcp.dev" still led one click to the vulnerable 0.1.0.
The instruction now names the site only as the maker and forbids giving an install
command; the READMEs drop the install line. Tests: the line may not contain the package
name or 'how to install', and no tracked file outside test/ may name @agent360/computer-mcp
(catches npm i -g, bunx, pnpm dlx and a reverted README).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YNkLWcoDV5PGezcYGMAAoT
A dialog that was closed, answered, re-armed or lost between being seen and the
reply being written was still reported as waiting, with the advice to answer it.
Following that advice arms a fresh 30-second window that can auto-answer the
NEXT dialog on the tab. An open box now answers in the present with dialog_open;
one that is gone answers in the past, with no advice.

Page.enable is now ensured for key presses, double-click and right-click too, not
only for single click - without it javascriptDialogOpening never fires and the
dialog is never seen. And sikrSideDomaene no longer spends its full 3-second
deadline when a box already stands: the answer is known in advance.

Tests: dialog-lukket-boks covers both states against both copies of
background.js; klik-identitet gains an integration test that closes the dialog
mid-click. Both mutants (branch removed, staarAaben forced true) go red.

repro-100 now exits non-zero when a tab wedges, and when no round ran at all, so
it can gate instead of always reporting success.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…hree states

A panel review found four defects in the first fix, each measured.

The worst was mine: Page.enable sat at each input site with a per-tab cache beside
it. Chrome sends no onDetach for a detach we make ourselves (R108), so after
browser_reattach_debugger the cache still said "on" while the new CDP session had
the domain off - and dialog detection was blind in exactly the tool an agent
reaches for when a tab looks stuck. The reporter had asked for it in the right
place: "when the debugger attaches". It is there now, on both attach paths, and
the cache is gone. Hover, press_key, double-click and right-click get it for free.

The enable is not awaited. Awaiting it moved its wait in front of the busy-tab
gate and used the busy window up, so a page busy longer than the gate looked free
and the address was sent after all (dialog-1303 S7+T4). CDP processes commands in
order on a session, so the enable still lands before the next call.

The verdict is now three-way, not two. "Answered but not confirmed by Chrome" and
"debugger came off the tab" are not gone - the dialog may still be on screen and
our tools cannot reach it, which is what reattach_debugger already tells the user.
The box is also re-read at the end: if the one we caught closed while the page
opened another, the answer names the one standing now, instead of saying it is
gone and advising against answering it.

The guard path (a dialog holding the page during the check) answered a bare
dialog: true and fell through to the old advice to call browser_handle_dialog -
the very advice that arms a fresh window where no box is visible. It now names the
box, or says plainly that it could not be identified.

Five mutants, all red: three-way verdict, the re-read, dialogHolderSiden, the
enable on attach, and the standing-box short-circuit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…good

A probe from the review found the last fix had introduced a third way back to the
original bug, in the fix itself.

When a session is released and the tab cannot be closed (#13), the debugger comes
off without Chrome sending onDetach, and the dialog box stays in the coordinator
without being marked lost. The short-circuit then saw a box that looked like it
was standing and skipped Page.enable - on every later attach, for good. The new
CDP session had the domain off, javascriptDialogOpening never fired, and the tab
was blind to dialogs again. Measured: one Page.enable before the fix, two after.

The short-circuit was only needed while the enable was awaited at each input site,
where three seconds of waiting was a real cost. It is not awaited any more: if the
enable hangs behind a dialog, nobody is waiting for it, and when the dialog is
answered the domain comes on - which is what we want.

The test that asserted the short-circuit is now the regression guard for its
removal, and goes red if it comes back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The release notes described every other part of 1.33 and said nothing about the
bug that most of this work went into. They also credit @arasmileska by name for
#76 in the same release - so the reader learns she found one issue and never
hears that she found the other, or that it is fixed.

The entry says what was wrong, what an agent sees now, and what is still open on
the other input paths, so nobody has to find that out the hard way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant