Full access is on
Turn off
Warrant live
Revoke now
No agent is attached to this browser
Open this page in ChatGPT's browser, or in Chrome with WebMCP. Everything here works by hand in the meantime.
Nothing open yet. Type a path or a URL below, or ask your agent.
· reset zoom
Preview the fix
Turn it on
Open a site from Netlify
Sign out
collapse
gesture nav 3-button nav
On the screen
What the agent reads with device_snapshot lands here, and is drawn on the glass.
Console
Activity 0
Every tool call, grant and revoke lands here.
Workbench
starting a machine…
Log
Files
Changes
loading emulator…
·
origin requests: —
Sever the origin
asking each time
tabbox · /dev/ttyS0 · one shell, shared with the agent
Pick a file. A ◐ marks a staged edit that is not on disk yet.
No staged edits. When the agent stages one it appears here as a diff you can read, before anything touches disk.
The machine's own VGA text console, drawn by the emulator: what a monitor plugged into the box would show. The log you share with the agent is the serial line. Why two consoles
How it works Close
A Linux 4.16 kernel boots on an emulated x86 (v86, WebAssembly) inside this page: a shell, a filesystem, processes, a VGA console. It holds the workspace your app is served from, runs the commands the agent is allowed to run, and keeps the log. No server. It has no network, and it is erased when the tab closes.
The catalogue is generated from the machine
When a machine attaches it reports a capability manifest, and the WebMCP tool list is built from that. This box has no tmux, so there is no pane tool; no Codex, so no subagents. The page says which tools it did not register and why.
Lifetime is an AbortSignal
Every registration scope (always, session, view, warrant) is one AbortController passed to document.modelContext.registerTool(). Open the Changes tab of the workbench and changes_review exists; close it and the tool is gone. Watch the count in the header.
The warrant is the registration
Writing to disk and running commands unattended are not gated handlers. Those tools are absent until you click Grant, and grant() requires transient user activation, which a tool call never has. The page ships the attack as a tool, warrant_force_grant, so you can watch it fail.
Two consoles
The workbench log is the serial console (ttyS0); you and the agent type into the same shell. Unattended commands run on a second, unwatched shell (ttyS1). The Screen tab shows the VGA text console, where the kernel's own init shell lives.
Nothing leaves the browser
After boot the page makes no request to its own origin. The one exception is a website you open on the phone through the relay, which is fetched on this origin; a PerformanceObserver counts every request, the number is in the footer, and it is a tool the agent can call.
state booting
The phone is a real browsing context held at that phone's viewport, pixel ratio, safe areas and platform hints, drawn in its frame the way an installed web app is shown: the status bar overlays the page, as with viewport-fit=cover.
Not iOS or Android. No Apple or Google code runs here. What a web app can observe is what is simulated, because that is what a web app is tested against.
Apps load from the workbench's workspace with their CSS, scripts and SVGs inlined and env(safe-area-inset-*) replaced by the device's real insets, so a fix the agent commits on the Linux box is the fix on the phone. A live website goes through the relay on this origin so it can be read and tapped, never signed in; with the Bonnie Live extension it opens as the real site in your own browser, signed in as you; framed directly it is private and uninspectable.
Which phones, which browsers
Every iPhone that runs iOS 26, from the iPhone 11 and the SE (2nd generation) to the 17e, and the current Android references (Pixel 10, Galaxy S26, Z Flip 7, Z Fold 7): 39 models, generated from the vendored mobile-browser-chrome-devkit (2026-09-02) and cross-checked against its JSON by the suite. Any other phone: device_custom takes a width, height, pixel ratio, cutout archetype and navigation.
The phone is drawn from the kit's own vectors, composed in its layer order: the model's frame (svg/iphone-frames), the status bar, the browser chrome for the state (svg/browser-chrome, split at the separator the kit draws), the home indicator or Android navigation (svg/system-bars), the cutout archetype. Every height is the kit's; each state names the files it was drawn from in device_describe. The browser is state, as the kit describes it: an installed app; Safari 26 in its compact, bottom or top layout; Safari 18; Chrome with its address bar at the bottom or the top; controls expanded or collapsed; gesture or three-button navigation on Android. Each state changes the viewport and the safe areas the page gets. Layer sizes are measured from screenshots of real phones: an iPhone 16 Pro Max on iOS 26, an iPhone 17 for the compact bar, a Pixel 6 for Chrome on Android. The kit's own wireframe sizes remain available to the agent, for comparison, as device_set metrics: "kit".
device_geometry reads what the page measures for itself — viewport, pixel ratio, screen, display mode, pointer — and says whether it matches the fixture; device_matrix sweeps the page across every viewport size. Honest limits: resolution and colour-scheme media queries inside the page follow the host browser, not the phone; a real WebKit or Blink is not running.
Bonnie Live is a small companion extension. With it, a URL you open on the phone is the real, published site in your own browser, signed in as you : no proxy, no second browser, nothing leaving this machine. Without it, a site goes through the relay on this origin: readable and tappable, but never signed in. The relay can also be yours, on your own Cloudflare .
Turn it on
Open chrome://extensions (the same page works in ChatGPT Atlas).
Turn on Developer mode .
Download Bonnie Live and unzip it (or use the webmcp/extension folder from the repository).
Click Load unpacked and choose that folder.
Come back here. The phone notices it and says Bonnie Live is on ; whatever was waiting to open, opens.
What it does, and only here
It acts inside this tab alone: it lets the site be framed, gives its requests the phone's user agent, keeps the site's cookies in a jar of its own (a cross-site frame can neither store nor send them), and injects the phone's shim and reading tools into every page the frame commits. Its rules are session rules and die with the tab.
Honest limits
The engine is your browser's, not a phone's. A site's own scripted requests in the instant after a login are not gated, only navigations are. Chromium only for now.
Live websites come to the phone through a relay: a small function on this page's own origin that fetches the site and hands it back readable. It has to be on the page's origin, because a browser lets a page read only its own documents; a relay anywhere else would show the site and let nothing be read or tapped. The one here runs on Cloudflare's edge as a Pages Function, in functions/frame.js, and it is the only server-side code on this page.
Run it on your own Cloudflare
Deploy the whole page to your own account and the relay is yours: your origin, your quota, your account's edge. Cloudflare's own command line signs you in, so no token is ever pasted here or held by anyone else.
git clone https://github.com/meshbergio/bonnie
cd bonnie/webmcp
npx wrangler login # Cloudflare's own sign-in, in your browser
npx wrangler pages deploy . --project-name=bonnie
The address it prints is your Bonnie. Everything here works the same there, and the counter in the workbench bar still reads zero after boot.
Honest limits
A relay fetches from Cloudflare's network, so a site that refuses bots or Cloudflare's addresses refuses it whoever owns the account; the phone names such a wall when it meets one. Nothing through a relay is signed in. For a site as you see it, signed in, use Bonnie Live .
The workbench above is disposable and lives in this tab. Bonnie's host runs on your real computer and pairs it to this page over a direct peer connection, with no relay and no account. Your agent then gets the same catalogue, generated from what that machine has: tmux terminals that outlive the tab, the real filesystem, the screen, Codex subagents.
git clone https://github.com/meshbergio/bonnie
cd bonnie && ./install.sh
bonnie pair # prints a URL and a QR code — open it in this page
Same gate Warranted tools do not exist until you grant them here, with a real click. A tool call cannot produce one.
Same record Every call, warrant and command lands in an append-only ledger the agent can read back, including what happened while it was not looking.
Nothing in the middle The page is static. Once paired it makes no requests to its own origin; the counter in the footer is the measurement.
Ask your agent for bonnie_install_plan and it will walk you through it.
An archive is the whole machine in one file: its memory and disk as the emulator saved them, plus the staged edits, the record, and what the phone had open. Resume it later and you are where you left off, including anything typed into the shell.
Authority does not survive a file. A live warrant or standing access is never archived and never restored; the agent starts a resumed session with exactly the tools it had at boot.
Drop a .bonnie archive here, or click to choose one
Download
Archive this session now
The agent can prepare an archive with session_archive, but the download needs your click. It can read what a loaded file contains with session_describe.