Repository navigation
Download
| System | File | Updates |
|---|---|---|
| Windows 10/11 (x64) | G9BrowserAgent-Setup-3.4.0.exe — per user, no administrator rights |
Automatic: G9BrowserAgent checks these releases, downloads in the background and asks before installing |
| macOS 12+ (Apple silicon) | G9BrowserAgent-3.4.0-mac-arm64.dmg |
G9BrowserAgent tells you when a release is out; download it and replace the app (the app is not signed with an Apple Developer ID yet, so macOS cannot install updates into it) |
| macOS 12+ (Intel) | G9BrowserAgent-3.4.0-mac-x64.dmg |
As above |
| Linux (x64) | G9BrowserAgent-x86_64.AppImage — keep this file name: updates replace the file in place |
Automatic, like Windows |
| Debian/Ubuntu (x64) | G9BrowserAgent_3.4.0_amd64.deb |
G9BrowserAgent tells you; install the new .deb with your package manager |
The installers are not code-signed: Windows SmartScreen and macOS Gatekeeper ask once before the first start. See the README for what to click, and for the browser extension (loaded once, updated by G9BrowserAgent itself).
latest.yml, latest-mac.yml and latest-linux.yml are the update metadata G9BrowserAgent reads; the .zip and .blockmap files are for the updater.
What changed in 3.4.0
Why. Agents now run long jobs unattended through G9BrowserAgent: social-media business tools, a
music service, renders that take minutes inside the page. A field incident on a browser node showed
how such a job dies. An evaluate on a background tab Engine 1 had just opened hung for 120 s. After
that even browser_tabs close timed out, and only a container restart helped. A panel gate, whose
every request is a fresh MCP session, was told "No session named n-bs" on the call right after it
opened that session, and its own tabs were refused as "owned by agent-N". And every step re-read the
whole page. One release fixes all three, in seven parts.
1. Hang-proofing.
- Nothing before a command is unbounded any more (
lib/cdp.js): the debugger attach (10 s), each
domain enable (5 s, sent together),Target.setAutoAttach(5 s) and the state write (5 s). A domain
that did not answer is not marked enabled, and is asked for again when a tool needs it. One attach
per tab runs at a time.platform-extensionnow honourstimeoutMson attach, detach and
sendCommand; it used to ignore it. - CSS left the attach set. No capture needs it, and it is the enable most likely to block on a
background page;inspect stylesenables it when used. Engine 2's list (engine/stealth.js) lost it
too. - The watchdog (§4.10, DAEMON_PROTOCOL §3). A cancelled call still running 1.5 s later has its tab
recovered (recoverTab): every CDP command waiting there is abandoned, so the call ends at once with
[recovered] …. In the extension the debugger is then detached and attached again; a launched
engine re-enables its domains. The extension answers a cancelled call within 5 s whatever happens,
and the daemon waits 8 s for that answer before it frees the tab's queue anyway.browser_status
shows a tab's slow CDP commands underdebugger. - close and focus bypass the tab's queue. Close ends every call on the tab first (cancelled in
its engine too) and abandons stuck CDP commands in the engine, so it works while a call is stuck. - The state chain cannot hold a reply. A step longer than
WRITE_STEP_MS(5 s) no longer blocks
the next one. The activity line is written after the reply. - Background tabs. Reads, evaluate and snapshots on hidden tabs are covered by tests, and
keepAwake:trueon open, orbrowser_tabs action:"keep_awake", keeps a tab working: focus
emulation plusPage.setWebLifecycleState active, applied again after any re-attach. Timer
throttling of a hidden page is a browser switch, not a command, and the result says so. Engine 2
already launches with--disable-background-timer-throttling; Engine 1 relies on the
IntensiveWakeUpThrottlingEnabled=0policy. - Found by the tests: a launched engine's recovery returned early from its re-attach, because the
tab was still listed as attached. Its domains and keep-awake were therefore never applied again. It
now forgets the attachment on both engines.
2. Stateless clients.
- Session names. The daemon's session names were never per agent. The report came from
open
reading onlyname, soopen session:"n-bs"never named the tab.opennow takessessiontoo. - Agent identity. An agent hello may carry
client.identity. Over HTTP that is theX-G9-Agent
header, else the MCP client's name (G9_MCP_AGENT_IDENTITYname|header|off). The stdio shim sends
onlyG9_AGENT_ID, because two editors on one machine may share a client name. - Takeover. A new connection with an identity takes over the claims, leases and current tab of
the older connection with the same identity. If that connection is gone, it takes back what it
owned and nobody has claimed since (remembered 10 min; a departed owner never blocks anyone). The
HTTP endpoint ends the idle older sessions of that identity at once, which releases their claims
instead of after 30 idle minutes.
3. Token economy.
- Snapshot options.
browser_snapshottakesscope(a ref or a CSS selector),roles/name
(regex) filters,interactiveOnlyandincludeText:false, withmaxNodesas before. - Diff.
diff:truereturns only+added,~changed (with what it was) and-removed nodes,
against the caller's previous diff:true snapshot of the page with the same options. - Stable refs (§4.2). An element keeps its ref across snapshots of a page.
- find.
browser_inspect what:"find"(a role and/or a name regex) returns just the matching lines
with refs. - Compact results (
mcp/mcp.js, default on,G9_MCP_COMPACT=0restores the old shape). JSON
carries no indentation, and atreeor longtextcomes as a plain-text block of its own instead of
an escaped string.resultFromContentreverses this for the runner and the harnesses.
4. Waiting and jobs.
- Waits.
browser_navigate waittakes apredicate(a page expression, polled until truthy; the
final value comes back) andref+state(visible, hidden, enabled, disabled, checked, unchecked,
gone, present), up to 2 h. The engine polls every 250 ms, then every 1 s after 10 s and every 2 s
after 5 min. - Evaluate.
browser_console evaluatetakestimeoutMsup to 4 h. - Jobs.
job:trueanswers at once with a jobId (tools/jobs.js). The job lives in the page
(window.__g9jobs), so it outlives the caller, its MCP session and a worker restart, but not a
navigation (state:"lost"). Page code reports progress with__g9job.progress(fraction, note).
job_status(waitMswaits up to 10 min),job_cancel(aborts__g9job.signal) andjobsread
and manage it. Values are bounded at 256 KiB of JSON.
5. Files on the browser's machine (the daemon runs there for both engines; router #finishFiles).
- fetch.
browser_network fetchruns from G9's isolated world, which has universal access: it
sends the browser's cookies for the URL and is not refused by CORS. The method, headers and body are
the caller's; the limit is 40 MiB. The daemon hashes the body and writessavePath(relative paths
go under<G9_HOME>/files/), or returns a body bounded bymaxBytes. - Screenshots.
browser_screenshot savePathwrites the image and returns its path, size and
sha256. Withthumbnail:trueit also returns a small JPEG of the viewport, or a scaled copy of the
capture when there is no time for a second one. - Engine 1 downloads now report the final path and, when the daemon can read the file, its sha256.
6. Batch.
- The action.
browser_recording action:"batch"(tools/batch.js) runs a list of steps in one
call: navigate; input by ref or role+name withnth; wait; evaluate; extract. Values saved with
saveAsfeed{{name}}in later steps. - Results. The list is validated before anything runs. Each step gets one compact line, and the
batch stops at the first failure with an excerpt: the same-role elements when a role+name matched
nothing. - FlowSpec. FlowSpec gained
evaluateandextractsteps (flowspec, replay, outline, Playwright
export; runner/README.md). A FlowSpecevaluateruns throughcdp.evaluateUserExpression, the one
named main-world helper the interaction suite's scan allows.
7. EXT-04 is done (§8.5). The call's deadline travels with it (caller.deadlineAt), so the
screenshot's two attempts are budgeted from the time left. When both stall, the call ends with the
cause before its deadline. The relay-timeout text no longer guesses a dialog, and both the late-reply
and both-attempts-stall cases are tested. EXT-13 stays open: its probed capability model and a
measured worker restart were not part of this work.
Tests.
- New: daemon 112 (+12: identity, the session alias, close past a stuck call, the recovered relay,
files, the deadlines, compact results, a late reply); seam 23 (+7 through the real runtime, with a
node:vmmain world for jobs); extension suite 111 (+4 Engine 1 hang tests, +7 FlowSpec
value steps); mcp-http 9 (+1). - Changed: one seam assertion — your own fifth snapshot no longer retires your first ref; it is
the same element's stable ref. One F7 test now waits for the activity line, which is written after
the reply. - The new live suite
setup/longwork-livetest.mjsruns inside the Engine 1 harness:
G9_TIMEOUT_MS=10000 G9_LIVE_SCRIPT=setup/longwork-livetest.mjs node setup/isolated-livetest.mjs, or
check.mjs --live, which learned a per-checkenv.
Measured on the owner's workstation (2026-10-07), on headless Edge 154, Engine 1 = the real
extension, Engine 2 = a launched headless browser: setup/longwork-livetest.mjs 54 passed, 0 failed.
-
Background tab (Engine 1:
visibilityState"hidden"): evaluate answered in 35 ms, and a full
snapshot (125 refs) in 225 ms. -
Watchdog. A command the page never answers was cut, and its tab recovered, 11.5 s into a call
with a 10 s deadline, on both engines. The tab then answered in 12 ms (Engine 1) and 3 ms (Engine 2). -
Close returned in 23 ms (Engine 1) and 5 ms (Engine 2) while a call was stuck on the tab, and
ended that call. -
Snapshot bytes the agent receives, on the same page (identical on both engines within 9 bytes):
Variant Bytes Less than full full 14,609 — indented JSON (the pre-3.4 shape) 15,396 — includeText:false11,751 −20 % interactiveOnly(the page has 125 links and buttons)11,419 −22 % roles:["button"]245 −98 % scope:"form"293 −98 % find"^Save$"146 −99 % diffafter a change (+1 ~1 −1)453 −97 % diffwith no change263 −98 % Compact JSON alone saved 5 % here: the tree is most of the bytes.
-
Predicate wait. The render, ready after 3 s, was seen after 4.7 s (14 looks) on Engine 1 and
3.1 s (13 looks) on Engine 2, each in one call. -
Evaluate jobs of 185 s finished in one tab on each engine (185.0 s, value returned). Progress
read while they ran: Engine 1 1 % → 7 % → 14 % → 60 % → 92 %; Engine 2 14 % → 17 % → 24 %. -
Fetch to file. 3,135 bytes, with a sha256 equal to the file served, in an answer of 471 bytes.
-
Screenshot to file. Engine 1: 135 KB on disk, a 200 px PNG thumbnail scaled from the capture,
because a background tab's second capture did not fit the 10 s deadline; answer 39 KB. Engine 2:
187 KB on disk, a 320 px JPEG thumbnail; answer 6 KB. -
Batch. Seven steps ran 5, then stopped at the failing 6th with the page's buttons as the
excerpt: 545 ms on Engine 1 and 281 ms on Engine 2, in a 759-byte answer. A value one step saved fed
a later step. -
MCP over HTTP. A fresh session of the same client found "n-bs" (132 ms) and could act on it;
ownedandcurrenthad moved to it, while another client was still refused. A job outlived the
session that started it. -
Not clean on the first try. In one earlier run the first tab of a launched browser started at
that very moment, on a loaded machine, did not answer for about 15 s. The watchdog recovered it, as
designed. The suite now starts the launched engine before the jobs. Another earlier run lost a
thumbnail to the short deadline, which led to the time-budgeted thumbnail with its fallback.
Runs on 3.4.0 (2026-10-07). check.mjs --release --keep-going passed 20 of 22 checks in 906 s:
- Unit: 713 tests in 15 suites (desktop 201, daemon 112, panel 84, interaction 78, engine 56,
humanize 51, platform-cdp 34, world 26, seam 23, check 13, mcp-http 9, version 8, sites 7, docker 6,
runtime 5). - Other offline checks: self-test 136, extension suite 111, the panel render, links.
- Live: Engine 1 live 212/0, and the long-work live suite 64/0 (counted with the harness's checks;
54 of them its own). - The two failures:
- The engine suite's D-a launch: Chrome 154 closed its pipe at start while four checks ran at once.
Alone, the suite passed, 56 with 1 skip. - Engine 2 live 371/1. Its recording replay had been failing since 3.3.0: the recorder no longer
keeps the password, and the test gave no secret. The test now passes it, as the runner does, and
that section passed 41/0.
- The engine suite's D-a launch: Chrome 154 closed its pipe at start while four checks ran at once.
- Not rebuilt here: the installers. The pipeline builds them and runs the update test.
Version 3.4.0 in package.json, extension/manifest.json, desktop/package.json and
desktop/package-lock.json.
SHA-256
70b2a32bc4cf8cbbc92001884c2e7e22cd6fab856bcf0191dcf964d7bf7c069e G9BrowserAgent-3.4.0-mac-arm64.dmg
faaff9540846377c6c94f432855ffc07f5fbc69b34fde886204a57e2fb982ad9 G9BrowserAgent-3.4.0-mac-arm64.dmg.blockmap
921b549933cf01b60a3dc374bf67974ce99e80024d2a25d83f61db923d6ae04e G9BrowserAgent-3.4.0-mac-arm64.zip
e37c6c0ab6503af7c434b6c81dc4135dfa03bdf0e1270325fde243e477129f6e G9BrowserAgent-3.4.0-mac-arm64.zip.blockmap
33d0fe81eeb4a5f9e78d2814dbee176f8ff44351c4e1e0c89aa15919318f93c3 G9BrowserAgent-3.4.0-mac-x64.dmg
8b80fc7ef16c6b917b26e27b9c6bab6d9191ba8509df21fc881aacb5b0d8ec66 G9BrowserAgent-3.4.0-mac-x64.dmg.blockmap
2ea5c55de8133dfa43d8e6540d68f25515376cef7d5cf8f3689bbfa4f378f5b3 G9BrowserAgent-3.4.0-mac-x64.zip
d8fffa8a8224cdcc050e4fa6f5bf70201f1c47425c3fc2f0e2a6ba629cad932a G9BrowserAgent-3.4.0-mac-x64.zip.blockmap
fb7c890533f8889068811ec0bb4d20459c226b676dfa56ba007b54c93cb48c2e G9BrowserAgent-Setup-3.4.0.exe
ae04bb5f29a5c0bf9685cd9c0b7eaae0e3bc6c58127ba99f81f18879805aaf14 G9BrowserAgent-Setup-3.4.0.exe.blockmap
0683bc5de58555045e150361a6ddad6f978e56129d1703548c19efde21c3de43 G9BrowserAgent-x86_64.AppImage
f9976cecef55e0a53e8eba0c88718bb081fbededa7a035b8a86e9f4d4c3fab9b G9BrowserAgent_3.4.0_amd64.deb