My Real Chrome v2 (publishable — scripting + native messaging, no debugger)
The clean-room, Web-Store-approvable rebuild of Omniscio's Chrome bridge, driving your real signed-in Chrome without the debugger permission — how to enable it, what it deliberately cannot do, and why driving it does not leave you anywhere else — the one switch a drive makes is forced by Chrome, lasts for the length of the command, and puts your own tab back.
What it is
"My Real Chrome v2" is the user-facing name; the code namespace is real-chrome-bridge-v2.
Status: in development — hidden by default, desktop-only. Reveal via Settings → Features → "My Real Chrome v2" or AMC_SHOW_REAL_CHROME_BRIDGE_V2=1. Ships OFF; nothing runs until you enable it, load the companion extension, and grant per-site access.
A clean-room, Chrome-Web-Store-approvable rebuild of My Real Chrome that drives your real, signed-in Chrome without the debugger permission. It uses chrome.scripting (inject a driver into the page), chrome.tabs.captureVisibleTab (screenshots), and native messaging (transport) — the same architecture as Anthropic's published "Claude" Chrome extension. It is fully separate from v1; both can be installed at once.
Where to find it
Nothing appears until you turn it on. The feature is revealed by flipping Settings → Features → "My Real Chrome v2" (it ships hidden and off). From then on the surface lives in Chrome rather than in Omniscio: you load the companion real-chrome-extension-v2/ folder as an unpacked extension, and per-site access is granted by clicking the extension icon and pressing Allow this site (or Allow all sites). Driving it is an agent's job, over the local CLI control server — there is no bridge screen inside the app.
How it behaves
Why it exists (vs. v1)
- Publishable. The Web Store rarely approves consumer extensions that use
debuggerto drive the browser; v1 therefore ships load-it-yourself only. v2's permission set (scripting,tabs,webNavigation,storage,alarms,nativeMessaging+ optional per-site host access) is the approvable shape. - Not poisoned by other extensions. v1's debugger session is poisoned (CDP -32000) the instant a page contains another extension's frame (LastPass, Superhuman). v2 uses no debugger, so it has no such wall.
What it is NOT
- Not your existing tabs — by default. The extension drives ONLY tabs it opened (the fresh-tab guardrail). The default-off "drive any tab" opt-in is the one deliberate exception.
- Not the tabs you open next to it. The default-off popup setting follows only a popup that a PAGE in a driven tab opened, such as a "Sign in with Google" window. A tab you open yourself — Ctrl+T, the + button, an address typed with Alt+Enter, Duplicate — is never taken over, even while a driven tab is the one in front.
- Not on the network. Transport is Chrome's native messaging to an app-registered host, relayed to the app over a private local pipe — no TCP port at all.
- Not reachable from your phone. Desktop-only: gated CLI routes, and the status channel is on the mobile/web bridge blocklist.
Setup
- Enable — Settings → Features → "My Real Chrome v2" (or
AMC_SHOW_REAL_CHROME_BRIDGE_V2=1). The app registers the native-messaging host with Chrome (an HKCU registry entry) automatically. - Load the extension —
real-chrome-extension-v2/as an unpacked extension (chrome://extensions → Developer mode → Load unpacked). There is no token to paste — it connects automatically. - Grant access — click the extension icon and press Allow this site (or Allow all sites). Chrome requires this click, so an agent can never silently gain access to a new site. Revoke any time in Chrome.
Known limits (scripting can't match a debugger)
These are documented, expected limits — not bugs — of the no-debugger architecture:
- Trusted input: synthetic events (
isTrusted:false); a few sites that gate on genuine user events won't respond. - File uploads: not supported (no debugger file-set); a file picker needs the user.
- Screenshots: visible area of the active tab only. A background tab whose window is NOT the one in front is briefly activated then restored (nobody sees it); a background tab in the window the user IS looking at is refused rather than shown, because the switch would take over their screen — see Background-first below.
- Read-page: a DOM-derived accessibility approximation, not the browser's real accessibility tree.
- Not captured/controlled: native JS dialogs, console messages, network response bodies, device/network emulation.
evaluateruns page JS but is subject to the page's CSP.
Background-first — it does not STAY on the user's screen
Opening is invisible, and driving never leaves the user anywhere else. This is deliberate: the user was clear that the bridge must "work in the background unless the agent specifically pulls the screen to the front."
- Opening a tab puts it
active:falsein the user's own last-focused window. It never mints a focused window, and never switches them off what they are reading. - Reading and acting DO move the tab to the front for the length of the command, and put the user's
own tab back immediately afterwards. The rule is runs script in the page:
read-page,get-page-text,click,hover,type,press-key,select-option,fill-form,evaluate,navigate-backandwait-for.navigateis NOT one of them — it retargets a tab, which leaves a background tab in the background — and neither isreload. Chrome will not run a script in a tab the user is not looking at, so there is no way to drive a page without this. It is roughly a third of a second for a read or a click, but await-forholds the front for its WHOLE wait (up to its 60s limit). If the tab is already the one on screen nothing changes on screen at all, and if the user switches tabs during a drive their choice wins — the tab is put back only while the driven tab is still the one in front. screenshotis the one read that REFUSES to move the screen —captureVisibleTabphotographs a window's VISIBLE tab, so shooting a background tab means bringing it forward first. It only does that when the switch is invisible (the tab is already on screen, or its window is behind something). Otherwise it refuses, namingforegroundas the way to do it on purpose.foregroundis the deliberate screen-grab, and the only op that raises the Chrome window. Reach for it when a HUMAN needs to see the tab — a login hand-off, a "look at this" moment.
For a login flow, use the same drive → hand off → resume pattern as v1: drive to the sign-in, foreground the
tab for the user to finish, then resume on the now-authenticated tab. foreground activates the tab and raises
the Chrome window itself (TOPMOST → NOTOPMOST, restoring it first if minimized) — the extension's
chrome.windows.update{focused} alone cannot, because Windows silently vetoes Chrome's SetForegroundWindow
when Chrome is not already the foreground process. So the hand-off works even when Chrome is behind another
window or minimized; never tell the user to un-cover or un-minimize it by hand.
For agents
How an agent uses it (CLI routes)
While enabled AND connected, an agent drives it through the local CLI control server. Every route is
feature-gated (404 when off), full-trust-token-authenticated, and fast-fails (409) when no extension is
connected. The /real-chrome-v2/* surface (the core drive vocabulary):
Perceive: GET /real-chrome-v2/status · GET /real-chrome-v2/tabs · POST /real-chrome-v2/read-page
(DOM-derived a11y snapshot with [ref=eN] markers) · get-page-text · screenshot
Navigate: navigate · reload · navigate-back · foreground · wait-for
Interact: click · type · hover · press-key (presses the focused element; pass a ref to focus it first) · select-option · fill-form · evaluate
Control: open-tab · close-tab · switch-tab · attach-tab / detach-tab (drive-any-tab opt-in) ·
POST /real-chrome-v2/reload-extension (the extension reloads itself off the files on disk, so a fix on
disk reaches your browser without you clicking anything — the extension is loaded unpacked and otherwise
keeps running the files Chrome read at load time. The first reload after installing is still manual,
because the copy already running has no way to be asked.)
When a command is slow or the link drops. A command that operates inside a page brings its tab
to the front first, because Chrome does not run an injected script in a tab you are not looking at —
the tab's renderer is not scheduled, so the script waits in the page for an unbounded span and the
command dies at its deadline looking exactly like a dead bridge. (Measured on one tab, seconds apart,
the same evaluate('1+1'): 9.6 s while the tab was in the background, 351 ms once it was in front.) The
tab you were on is put back straight afterwards — but only if the driven tab is still the one in front,
so if you switch tabs yourself during a drive, your choice wins. If the tab being driven is already the
one on screen, nothing changes on screen at all. The switch lasts as long as the command: about a third
of a second for a read or a click, but for wait-for the whole wait (up to its 60 s limit), so ask for a
long wait deliberately rather than casually. The injection still carries a bound, and running out of it
fails the command by name, saying which tab it was and what state that tab was in, rather than a
bare "timed out" that reads like a dead bridge. Crucially, the commands that only
touch the tab and never the page — reload, switch-tab, foreground, screenshot — never wait for
the load at all, so a stuck page can always be reloaded, shown or photographed. The link and every other
command stay up, and nothing is retried for you: a deadline, a stalled page and a frame the extension
refused all fail exactly once, and the extension classes the last two on the wire so a read is never
re-sent onto the same stuck page. After a timeout the app asks the extension for its tab list — which
touches no page — and resets the link only if that goes unanswered too. After a real drop, a call waits
up to five seconds for the extension to re-dial before answering "no browser connected", and
GET /real-chrome-v2/status records when and why the link last dropped (lastDisconnectedAt,
lastDisconnectReason).
read-page returns stable refs you pass to click/type; refs go stale on navigation (re-read the page).
It also surfaces contenteditable rich-text editors as textbox refs so type reaches them (basic
editors; a heavy framework editor may still manage its own model).
A field's VALUE is used as its label only when nothing safer names it, and NEVER for a credential
field — a type=password box, or one whose autocomplete declares a password / one-time-code / card
number, reads as [redacted]. Chrome autofills a saved password into that value, so without this the
snapshot would hand the caller the user's plaintext password. You still learn that a value is PRESENT
(so "did my type land?" stays answerable); to have a human fill a secret you must never see, use
POST /input-request.
Under the hood
Engine src/main/services/real-chrome-bridge-v2/, extension real-chrome-extension-v2/, host
resources/real-chrome-v2-host/. Invariants + the guarding-test map:
real-chrome-bridge-v2-contract.md.
Related
Both bridges can be installed at once, and real-chrome-bridge.md is v1 — the debugger-based original this rebuild exists to replace, and the place to read about the drive → hand off → resume login pattern the two share. For the other ways Omniscio drives a browser, see embedded-browser.md and browser-logins.md. The invariants this bridge must hold, and the map of tests that guard them, are locked in real-chrome-bridge-v2-contract.md.
Last verified 2026-10-04