Extension.js it shells out to had grown a working Safari dev loop. The client
now rides that loop, and its pinned CLI packages move to the release that
has it.
- `extension-create`, `extension-develop` and `extension-install` move from
4.1.2 to 4.1.29, the stable release that carries the `navigate` verb
(first shipped in a 4.1.28 canary). On Safari that engine reloads the extension
on every save through the extension's own bridge, streams background and
content lines into the session's log file, and points a tab at a url
without eval; a project with no local Extension.js now gets all of that
from the pin.
- The bridge tools work on a Safari dev session started with
`allowControl` or `allowEval`: `extension_storage`, `extension_reload`,
`extension_open` for surfaces, `extension_dom_snapshot` by tab id,
`extension_logs`, and the assertions `content-script-injected`,
`background-worker-booted`, `storage-key-present` and
`console-errors-empty`, measured live on Safari 27. `extension_eval` in
`content` or `page` needs a tab already open at the url, and Safari's MV3
background CSP blocks eval in `background` and the bridge's `url`
navigation, which the engine reports by name. `surface-rendered` and
`extension_inspect` still need a target list Safari does not expose, and
say so.
- `extension_browsers` reports Safari's version and an `automation` block
(the safaridriver beside it and whether it speaks `--mcp` and `--bidi`),
and `extension_doctor` with no `projectPath` gains a `safari-agent` leg,
so an agent learns when Apple's Safari MCP server can pair with this one.
The packaged CLAUDE.md and `/extension-debug` describe that pairing.
- `extension_open` with `url` asks the engine's `navigate` verb first, a
static tabs call inside the extension that works where an MV3 background
refuses eval, which is every Safari session. Only an engine that does not
know the verb yet falls back to the background eval, and that fallback
names the upgrade when Safari's CSP refuses it.
- If a dev session records a safaridriver session in `ready.json`
(`webdriverPort`, `webdriverSessionId`), `extension_eval` with context
`page` and `extension_open` with `url` use it for the page's main world,
and `extension_doctor` adds a `safari-window` leg; with no such record
the leg is a skip and both tools take the bridge.
Four defects agents met while driving the server on real extension work
close in the same release.
- The initialize result now carries `instructions`: four lines that name
the moments (run, wait, inspect, drive, build an extension) and the tools
that own them, for clients that hide tool descriptions behind a search
step and would otherwise show the model only a server name and a tool
count. `createServer()` is exported so a client can initialize against
the same server object the stdio path connects.
- `extension_open` with surface `newtab`, `history` or `bookmarks` resolves
the `chrome_url_overrides` page itself and opens it by url. The engine's
`open` verb never knew those surfaces and answered `E_ARGS` "unknown
surface" to exactly what the tool description promised; an override page
is only ever a tab, so this is the surface, not a fallback, and the hint
says so.
- `extension_open` with surface `sidebar` on Chromium, when Chrome refuses
`sidePanel.open()` for lack of a user gesture, opens the extension's own
sidebar page in a tab, dispatches a synthetic click on it over CDP, calls
`chrome.sidePanel.open` from inside that click and closes the tab. The
panel that opens is the real one; the result reports `gesture:
"synthetic-click"` and a warning that the toolbar wiring was not
exercised. If the click does not open it either, the sidebar document is
rendered as a tab with the reason in a warning, and if even that fails
the refusal gains a hint naming the url to read instead of ending the
road. Headless sessions keep their tab fallback and never click.
- `extension_eval` on a Chromium MV3 session evaluates extension pages over
CDP: contexts `popup`, `options`, `sidebar`, `newtab`, `history` and
`bookmarks`, and context `page` with a `chrome-extension://` url. The
inspector path is not governed by the extension page CSP that blocks the
in-bundle relay's string eval, and script injection could never reach an
extension page at all, which is where the misleading "Extension manifest
must request permission to access this host" came from. A page that is
not open answers `E_NO_TARGET` with the `extension_open` call that opens
it; a thrown expression answers `E_EVAL` with the exception text; a bare
top-level `await` parses, as in the DevTools console; MV2 Chromium and
Gecko sessions keep the relay. Both this path and the sidebar gesture were
measured live on Chrome for Testing 151 before release.