@agentproto/adapter-browser-chrome

中文文档
0.8.1发布于 2026/11/01Apache-2.0TypeScriptES Module构建来源已验证近 30 天下载 4986

System Chrome providers for agentproto: chrome launches the installed Chrome on a fresh dedicated profile dir, attaches over CDP and injects granted cookies (never the default profile); chrome-live launches (or reuses, hot) a dedicated headful Chrome

原始使用文档
文档目录15 节
  1. Finding Chrome
  2. F11: never the default Chrome profile
  3. Cookies
  4. chrome-live: your running Chrome
  5. Attaching to your own Chrome (explicit ask only)
  6. Falling back to the dedicated Chrome from the attach path
  7. Configuration
  8. One connection, one consent
  9. The agent's own window
  10. File inputs and extra agent tabs
  11. Screencast of the agent's own tab
  12. Safety guarantees
  13. Errors
  14. Deferred
  15. Tests

@agentproto/adapter-browser-chrome

Two browser providers for @agentproto/driver-browser, both driven over raw CDP:

  • chrome: the Chrome installed on the machine, launched on a fresh dedicated profile directory (never your profile).
  • chrome-live: a dedicated headful Chrome on its own persisted profile dir by default (no consent click) — attaching to your own running Chrome through its remote debugging toggle only happens on an explicit ask; see chrome-live.
import { chrome } from "@agentproto/adapter-browser-chrome"

const instance = await chrome.launch({ profile: "work" }, { log: console.log })
const driver = await instance.attach({ initialUrl: "https://example.com" })
await driver.close()
await instance.stop()

Use createChromeProvider({ cookieSource }) to inject cookies at attach time.

Finding Chrome

launch({ executablePath }) and the provider config win. Otherwise resolveChrome reads CHROME_EXECUTABLE_PATH, which must be an absolute path to an existing file (a wrong value throws instead of falling back), then the standard install paths for the OS (Chrome, Edge and Brave on macOS and Windows; well-known paths and PATH names on Linux). When nothing is found, launch() fails with an error that names CHROME_EXECUTABLE_PATH.

F11: never the default Chrome profile

Chrome 136+ refuses --remote-debugging-port on the default user-data-dir, so this provider never attaches to it or launches against it, and it never touches or closes the Chrome you are using. Every launch spawns a new Chrome process on a fresh dedicated dir under ~/.agentproto/browser/chrome/profiles/<name> (override the root with AGENTPROTO_HOME or dataDir), with --remote-debugging-port=0. The port is read from DevToolsActivePort.

These requests are refused with the typed error browser:profile-refused (BrowserProfileRefusedError, from @agentproto/driver-browser):

  • a userDataDir, or a profile given as a path, that is or sits inside the default dir of Chrome, Chromium, Brave or Edge on this OS (symlinks resolved);
  • a profile naming a real Chrome profile (Default, Profile 1, ...);
  • fullProfile: true, or a --full-profile argument, until the grant model lands;
  • extra args that set --user-data-dir or --remote-debugging-*.

The argv is checked again immediately before the spawn. Tests use a stub binary and assert the launch args carry the fresh dir and never a default one.

Cookies

Granted cookies are injected through CDP Network.setCookies when a driver attaches. They come from attach({ sessionPayload }) and from the provider's cookieSource parameter (BrowserCookieSource); the grant model supplies the real source in a later lane. Only the cookie count is logged, never a value.

chrome-live: your running Chrome

chrome-live gives an agent a dedicated headful Chrome, on its own persisted profile dir, by default — launched (or reused, hot) immediately, with zero consent clicks and without ever dialing your own Chrome. Attaching to the Chrome you already use (your logged-in profile, so an agent can work on sites where you are signed in) only happens on an explicit ask: a pinned wsUrl/port (launch opts, provider config, or CHROME_LIVE_* env), or attachIntent: true. It never touches your tabs, and it never closes your Chrome.

import { chromeLive } from "@agentproto/adapter-browser-chrome"

// Default: a dedicated Chrome, launched or reused hot — no consent wait.
const instance = await chromeLive.launch({}, { log: console.log })
const driver = await instance.attach({ initialUrl: "https://mail.example.com" })
await driver.evaluate({ expression: "document.title", awaitPromise: false, returnByValue: true, maxResultBytes: 10_000 })
await instance.stop() // closes the agent's tabs and its socket, nothing else

// Explicit ask: attach to the user's own, real Chrome instead.
const mine = await chromeLive.launch({ attachIntent: true }, { log: console.log }) // Chrome shows "Allow"

In the daemon it is the chrome-live browser adapter: start_browser { adapter: "chrome-live" } (dedicated by default; pass attachIntent: true in the launch options to ask for the user's own Chrome instead).

Attaching to your own Chrome (explicit ask only)

  1. Use Chrome 144 or later and open chrome://inspect/#remote-debugging.
  2. Turn on the remote debugging toggle. Chrome then listens on 127.0.0.1:9222.
  3. Launch with attachIntent: true (or a pinned wsUrl/port). Chrome shows a dialog asking whether to allow the debugging connection: click Allow. The provider waits up to consentTimeoutMs (default 60s).

Turn the toggle off again to cut every connection. Without attachIntent (or a pin), the toggle is never dialed at all — the dedicated Chrome comes first and the attach step doesn't run.

Falling back to the dedicated Chrome from the attach path

When attachIntent/a pinned endpoint asked for attach and it is not possible, the provider auto-launches a dedicated, headful Chrome and connects to it instead — no Allow click:

  • No Chrome on the toggle (browser:remote-debugging-off, nothing listens on 9222): a dedicated Chrome is launched.
  • Consent never granted (browser:consent-timeout: no Allow click within consentTimeoutMs, or Chrome rejected the connection): the provider stops waiting and launches the dedicated Chrome instead of leaving the agent stuck.

The launched Chrome listens on a free port and uses a persisted profile dir, $CHROME_LIVE_LAUNCH_DATA_DIR (default ~/.agentproto/browser/chrome/live), kept across runs so it stays warm. It is connected in discover mode (the uuid webSocketDebuggerUrl from GET /json/version), and its pid is recorded on the instance. stop() kills only that child (pid scissors, SIGTERM → grace → SIGKILL) — an attach-only session has no owned child and its stop() never signals your Chrome.

A pinned wsUrl/port (launch opts, config, or env) disables this fallback — it is taken literally, so a failure there surfaces instead of substituting the dedicated Chrome. autoLaunch: false disables dedicated launching globally (attach becomes the only path, including as the default).

Configuration

SettingDefault
createChromeLiveProvider({ port }) / CHROME_LIVE_PORT9222Port on 127.0.0.1
createChromeLiveProvider({ wsUrl }) / CHROME_LIVE_WS_URLws://127.0.0.1:<port>/devtools/browserFull browser websocket URL; beats the port
consentTimeoutMs60000How long to wait for the Allow click
discovertrueRead GET /json/version and connect to the advertised webSocketDebuggerUrl (Chrome 155+ per-launch browser UUID). false is a legacy escape hatch for a pre-155 toggle whose /json/version is unreachable but fixed endpoint still answers
autoLaunchtrueMay the provider ever launch a dedicated Chrome at all (as the default, or as the attach path's fallback)
preferDedicatedtruefalse restores the old attach-first default for every call on this provider instance
launch({ attachIntent })falsePer-call explicit ask to attach to the user's own Chrome instead of the dedicated default
createChromeLiveProvider({ cookieSource })noneInjects granted cookies into the dedicated Chrome on every attach() (never into your own); same BrowserCookieSource shape as chrome. Filtered by the launch's domains; cookie values are never logged, only a count
CHROME_LIVE_LAUNCH_DATA_DIR~/.agentproto/browser/chrome/livePersisted profile dir for the auto-launched Chrome

launch({ wsUrl, port }) wins over the provider config, which wins over the env. By default (discover: true) the provider reads GET /json/version and connects to the advertised webSocketDebuggerUrl — Chrome 155+ exposes a per-launch browser UUID there (ws://127.0.0.1:<port>/devtools/browser/<uuid>); the id-less fixed URL answers 404. A closed port fails loudly with browser:remote-debugging-off instead of dialing a dead endpoint; an empty or non-JSON /json/version (a pre-155 toggle) falls back to the fixed id-less URL. discover: false skips the probe and connects straight to ws://127.0.0.1:<port>/devtools/browser. DevToolsActivePort in the Chrome profile is not read (macOS does not let other apps read it); the port comes from config.

Every new websocket makes Chrome ask for consent again, so the provider keeps one connection per websocket URL. launch() is memoized on that URL, including the connect in flight: concurrent launches share one socket, and calling launch() again (Bureau does this on every control-tool attach) returns the live instance with wasAlreadyRunning: true. health() runs Browser.getVersion over the existing socket and never opens a new one; check() is a bare TCP probe of the port and never opens a websocket either. A new connection is only opened after the previous one dropped or was stopped, and the log then says that Chrome will ask for consent again.

The agent's own window

The first attach() opens a dedicated window with Target.createTarget { url, newWindow: true }. Later attaches open tabs in that same window, never a second window. The protocol has no window parameter on Target.createTarget (checked against the devtools-protocol definitions in the repo: only newWindow, background, left/top/width/height and the like), and a new tab opens in the last active window. So the provider first runs Target.activateTarget on one of its own tabs, creates the tab, and checks it with Browser.getWindowForTarget. If another window took focus in between and the tab landed elsewhere, that tab is closed at once and the attach is retried, then fails with a clear error. It never stays among your tabs. Once you close the agent window, the next attach opens a new one. CDP has no tab-group API, so the agent's tabs are not grouped.

File inputs and extra agent tabs

uploadFileOwnedTarget(selector, paths) attaches local files to an <input type=file> on an owned tab: it probes the selector first (no <input type=file> matches selector … when it does not match), then resolves the element with DOM.querySelector and feeds it through DOM.setFileInputFiles with ABSOLUTE paths — the same mechanism devtools uses, a feed rather than a write. A second owned tab is just newTabOwnedTarget(url) (always a FRESH tab in the agent window — the same activate-anchor → createTarget → getWindowForTarget flow as the first tab — never a reuse), and listOwnedTabs() returns every tab the agent owns with the window it lives in. The MCP projection is browser_upload_file (paths are restricted to the daemon's upload allowlist on top of this), browser_new_tab and browser_list_tabs.

Screencast of the agent's own tab

chrome-live is the only provider that can screencast (driver.capabilities.canScreencast). The screencast rides CDP Page.startScreencast inside the driver's own flat session, on a tab the provider opened — so it stays inside the same ownership rules as a screenshot (Page.* only, never a foreign tab, never the desktop).

const { targetId, index } = await instance.screencastStart({ everyNthFrame: 1, quality: 80 })
const latest = await instance.screencastFrame() // null until the first frame lands
const { frameCount, frames } = await instance.screencastStop()

Options mirror Page.startScreencast: format (default jpeg), quality (1–100), everyNthFrame, maxWidth, maxHeight. Frames are normalized to { data /* base64 */, format, timestampMs, index, width?, height? } and buffered in a bounded ring (2000 frames) so a later stop can hand every buffered frame to a recording encoder. screencastStart reuses an owned agent tab when one exists and opens a fresh one otherwise, and is idempotent per driver. The screencast pointer lives on the connection entry, so a second launch() handle that reuses the already-running connection (wasAlreadyRunning) reaches the same frames.

The daemon projects the same instance methods as the MCP tools browser_screencast_start / browser_screencast_frame / browser_screencast_stop and the HTTP routes GET /sessions/<id>/screencast (HTML viewer), .mjpg (MJPEG stream) and .jpg (latest frame).

Safety guarantees

  • Never touches your Chrome. When it attached, stop() sends Target.closeTarget for the targets this provider created, then closes its own websocket; it never sends Browser.close. A handle returned by a reused launch() closes only the tabs it opened and leaves the connection to the first launch. When instead it auto-launched a dedicated Chrome, stop() kills only that child by pid (SIGTERM → grace → SIGKILL) — your Chrome is never signalled.
  • Never touches your tabs. The provider only knows the target ids it created. It never calls Target.getTargets and never attaches to a target it did not create. Through driver.send (the agent's cdp_send), the whole Target.* and Browser.* domains are refused, as are cookie and storage writes.
  • Cookies are read-only on your profile. When attached to the Chrome you already run, nothing is written into that profile: no Network.setCookies, granted-cookie payloads are ignored, and the driver reports canCookies: false. Reading (Network.getCookies) works. On the auto-launched dedicated Chrome a host-configured cookieSource (or a session-payload cookie list) may inject cookies through Network.setCookies at attach time — the agent still cannot write cookies through cdp_send, and your own Chrome stays untouched either way.
  • Declared capabilities: cdp, isUserVisible, canMultiTarget (tabs of the agent window), headed, canScreencast (see Screencast). Not headless, not persistentProfile.

This is not a bypass of F11. The F11 guard above stays as it is: neither provider ever launches Chrome on the default profile. When the toggle is off, chrome-live auto-launches a dedicated Chrome on its own persisted profile dir (never your default profile) and writes nothing to your profile; it otherwise only uses a connection you turned on and allowed in Chrome's own UI, which you can cut at any time.

Errors

Each error is a ToolError with a stable code, exported as a constant:

CodeConstantWhen
browser:remote-debugging-offBROWSER_REMOTE_DEBUGGING_OFF_CODENothing listens on the port (ECONNREFUSED). Message: "Enable remote debugging at chrome://inspect/#remote-debugging"
browser:consent-timeoutBROWSER_CONSENT_TIMEOUT_CODENo Allow click within consentTimeoutMs, or Chrome rejected the connection
browser:disconnectedBROWSER_DISCONNECTED_CODEChrome closed or the websocket dropped mid-task

After a drop, the next launch() reconnects (with a new consent dialog) and still knows the tabs it opened before, so stop() can close them.

Deferred

Not in this package yet: extension handling, a multi-profile UI, headed takeover polish, and full-profile access. The cookieSource seam is here (both providers); the grant model that supplies a source lives in @agentproto/browser-profiles.

Tests

The suite drives a stub Chrome (a node script that speaks minimal CDP) so no real browser is needed. chrome-live runs against an in-process fake of a toggle-mode Chrome that already has user tabs and flags any command addressing a target the provider did not create. An optional real-binary smoke runs runConformance when CHROME_SMOKE_BIN names a Chrome or Chromium executable.

版本与发布记录

@agentproto/adapter-browser-chrome 共发布 12 个版本,首次发布于 2026/09/30;过去 12 个月发布了 12 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.8.1latest2026/11/016 天前
0.8.02026/10/2711 天前
0.7.02026/10/1919 天前
0.6.02026/10/1821 天前
0.5.12026/10/1622 天前
0.5.02026/10/1622 天前

关于 @agentproto/adapter-browser-chrome 的常见问题

@agentproto/adapter-browser-chrome 是什么?

System Chrome providers for agentproto: chrome launches the installed Chrome on a fresh dedicated profile dir, attaches over CDP and injects granted cookies (never the default profile); chrome-live launches (or reuses, hot) a dedicated headful Chrome

如何安装 @agentproto/adapter-browser-chrome?

在项目目录运行 npm install @agentproto/adapter-browser-chrome,或使用 pnpm add / yarn add / bun add。

@agentproto/adapter-browser-chrome 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@agentproto/adapter-browser-chrome 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@agentproto/adapter-browser-chrome 可以商用吗?

@agentproto/adapter-browser-chrome 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@agentproto/adapter-browser-chrome 的下载量有多少?

根据 npm 官方下载统计,@agentproto/adapter-browser-chrome 在 2026/10/05 – 2026/11/03 这 30 天内被下载 4,986 次,日均约 166 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@agentproto/adapter-browser-chrome 还在维护吗?

最新版本 0.8.1 发布于 2026/11/01(6 天前);至今共发布 12 个版本,其中过去 12 个月发布了 12 个;首次发布于 2026/09/30。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。