@narumitw/pi-chrome-devtools 0.53.0 → 0.53.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +88 -230
  2. package/dist/chunks/{browser-settings-menu-N5O65I7S.js → browser-settings-menu-IMB6HXSU.js} +5 -3
  3. package/dist/chunks/browser-settings-menu-IMB6HXSU.js.map +7 -0
  4. package/dist/chunks/{chunk-RTECHAEZ.js → chunk-GTTYS5GT.js} +20 -9
  5. package/dist/chunks/chunk-GTTYS5GT.js.map +7 -0
  6. package/dist/chunks/{chunk-B7B63PWB.js → chunk-LLXOCRNF.js} +50 -30
  7. package/dist/chunks/chunk-LLXOCRNF.js.map +7 -0
  8. package/dist/chunks/{chunk-GVBXT3FB.js → chunk-SRT3UUIP.js} +117 -73
  9. package/dist/chunks/chunk-SRT3UUIP.js.map +7 -0
  10. package/dist/chunks/{menu-UBHW6UP2.js → menu-UJETYMSB.js} +4 -4
  11. package/dist/chunks/{tools-V7OR5KJ3.js → tools-6P7EUX37.js} +49 -30
  12. package/dist/chunks/tools-6P7EUX37.js.map +7 -0
  13. package/dist/index.ts +21 -18
  14. package/dist/index.ts.map +2 -2
  15. package/docs/browser-setup.md +147 -0
  16. package/package.json +7 -7
  17. package/src/browser-manager.ts +138 -72
  18. package/src/browser-settings-menu.ts +2 -1
  19. package/src/cdp-client.ts +18 -7
  20. package/src/chrome-devtools.ts +8 -1
  21. package/src/display.ts +38 -0
  22. package/src/render.ts +6 -7
  23. package/src/tool-selector.ts +21 -32
  24. package/src/tools.ts +5 -5
  25. package/src/webmcp/policy.ts +14 -7
  26. package/src/webmcp/protocol.ts +33 -21
  27. package/dist/chunks/browser-settings-menu-N5O65I7S.js.map +0 -7
  28. package/dist/chunks/chunk-B7B63PWB.js.map +0 -7
  29. package/dist/chunks/chunk-GVBXT3FB.js.map +0 -7
  30. package/dist/chunks/chunk-RTECHAEZ.js.map +0 -7
  31. package/dist/chunks/tools-V7OR5KJ3.js.map +0 -7
  32. /package/dist/chunks/{menu-UBHW6UP2.js.map → menu-UJETYMSB.js.map} +0 -0
package/README.md CHANGED
@@ -2,189 +2,78 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@narumitw/pi-chrome-devtools)](https://www.npmjs.com/package/@narumitw/pi-chrome-devtools) [![Pi extension](https://img.shields.io/badge/Pi-extension-blue)](https://pi.dev) [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
4
4
 
5
- Let Pi inspect browser tabs, navigate pages, evaluate JavaScript, and capture screenshots through native Chrome DevTools Protocol tools.
6
-
7
- Use it for web debugging, UI validation, and browser-assisted investigation without running an MCP server.
8
-
9
- The design is inspired by [`chrome-devtools-mcp`](https://github.com/ChromeDevTools/chrome-devtools-mcp), not guaranteed to be compatible with it.
5
+ Inspect browser tabs, navigate pages, evaluate JavaScript, and capture screenshots from Pi through the Chrome DevTools Protocol.
6
+ Use these native Pi tools for web debugging, UI validation, and browser-assisted investigation without an MCP server.
7
+ The design is inspired by [`chrome-devtools-mcp`](https://github.com/ChromeDevTools/chrome-devtools-mcp), but compatibility is not guaranteed.
10
8
 
11
9
  ## ✨ Features
12
10
 
13
11
  - Lists and selects inspectable pages, navigates URLs, evaluates JavaScript, and captures PNG screenshots.
14
- - Reuses an existing CDP endpoint or lazily launches an isolated Chromium-family browser.
15
- - Recovers from stale page selections and reports actionable browser startup or endpoint errors.
12
+ - Reuses an existing CDP endpoint or launches an isolated Chromium-family browser on first use.
13
+ - Recovers from stale page selections and explains browser startup or endpoint failures.
16
14
  - Loads explicitly approved unpacked extensions only in an extension-owned Chrome for Testing or Chromium process.
17
- - Uses native deferred browser tools when supported and eager exposure otherwise, with availability, setup, status, and help through `/chrome-devtools`.
15
+ - Uses native deferred browser tools when supported and exposes allowed tools eagerly otherwise.
16
+ - Provides availability controls, setup guidance, status, and help through `/chrome-devtools`.
18
17
  - Shows compact expandable results and activity only while browser tools are running.
19
18
  - Persists reviewed tool availability while keeping browser connection settings machine-owned.
20
19
  - Offers opt-in experimental WebMCP discovery and invocation through two fixed gateway tools without dynamically registering page-provided definitions.
21
20
 
22
21
  ## 📦 Install
23
22
 
23
+ Install persistently:
24
+
24
25
  ```bash
25
26
  pi install npm:@narumitw/pi-chrome-devtools
26
27
  ```
27
28
 
28
- Try without installing permanently:
29
+ Run once from npm:
29
30
 
30
31
  ```bash
31
32
  pi -e npm:@narumitw/pi-chrome-devtools
32
33
  ```
33
34
 
34
- Build and try this package locally from the repository root:
35
+ Build and run a local checkout from the repository root:
35
36
 
36
37
  ```bash
37
38
  npm --workspace @narumitw/pi-chrome-devtools run build
38
39
  pi -e ./packages/pi-chrome-devtools
39
40
  ```
40
41
 
41
- The package declares `dist/index.ts`, so an unbuilt local checkout must run the build before Pi loads the package directory.
42
+ The package declares `dist/index.ts`, so build a local checkout before loading its package directory.
43
+
44
+ Pi extensions run with your user permissions.
45
+ Review third-party extension source before installing it.
42
46
 
43
47
  ## 🚀 Quick start
44
48
 
45
- Start Pi and ask the agent to load the Chrome DevTools capability needed for the task.
46
- The extension first tries `http://127.0.0.1:9222` and otherwise launches an isolated local Chromium-family browser by default.
47
- Run `/chrome-devtools` to review browser status, settings, help, and available tools.
48
- Experimental WebMCP remains disabled until the user explicitly enables it.
49
+ Start Pi and ask the agent to load the browser capability needed for the task.
50
+ By default, the extension tries `http://127.0.0.1:9222` and launches an isolated local Chromium-family browser if that endpoint is unavailable.
51
+ Run `/chrome-devtools` to review status, settings, help, and available tools.
52
+ WebMCP remains disabled until you explicitly enable it.
49
53
 
50
54
  ## 🌐 Browser setup
51
55
 
52
- Without unpacked extensions, the extension first tries `browser.endpoint`, defaulting to `http://127.0.0.1:9222`.
53
- If that endpoint is unavailable and `browser.autoLaunch` is `true`, it lazily launches an extension-owned browser with an isolated temporary profile and retries the CDP request.
54
- Existing endpoints are reused and never terminated by the extension.
55
-
56
- Configure the canonical user file at `${PI_CODING_AGENT_DIR:-~/.pi/agent}/pi-chrome-devtools.json`:
57
-
58
- ```json
59
- {
60
- "browser": {
61
- "endpoint": "http://127.0.0.1:9222",
62
- "autoLaunch": true,
63
- "executablePath": "/absolute/path/to/chromium"
64
- }
65
- }
66
- ```
67
-
68
- `browser.endpoint` must be an HTTP origin with an explicit port and no credentials, path, query, or fragment.
69
- Omitting it keeps attach-first behavior on `127.0.0.1:9222` and lets a managed launch use Chrome's dynamic DevTools port mode (`--remote-debugging-port=0`).
70
- Explicitly saving an endpoint pins managed launches to that port.
71
- `browser.autoLaunch` defaults to `true`.
72
- `browser.executablePath` is optional and must be an absolute path; when absent, normal browser discovery applies.
73
-
74
- The configured endpoint must expose the standard CDP HTTP discovery routes such as `/json/version` and `/json/list`.
75
- Chrome's newer built-in permission flow can listen on port `9222` while returning `404` from those routes; setting the same HTTP origin does not by itself make that flow compatible.
56
+ By default, the extension attaches to `http://127.0.0.1:9222` or launches an isolated local browser on first use.
57
+ It never closes an external browser.
58
+ Use `/chrome-devtools` → **Browser settings** to change the endpoint, auto-launch policy, or executable.
76
59
 
77
- ### Unpacked extensions
60
+ Read the [browser setup reference](./docs/browser-setup.md) for endpoint requirements, executable discovery, unpacked extensions, manual launch, and deprecated environment overrides.
61
+ Manual settings edits apply after `/reload` or session replacement.
78
62
 
79
63
  > [!WARNING]
80
- > An unpacked extension executes privileged browser code.
81
- > Load only code you trust.
82
- > Project settings are honored only when Pi reports the project as trusted.
83
-
84
- Add trusted unpacked-extension paths to the same canonical user file:
85
-
86
- ```json
87
- {
88
- "browser": {
89
- "executablePath": "/absolute/path/to/chrome-for-testing",
90
- "extensionPaths": [
91
- "/absolute/path/to/unpacked-extension-one",
92
- "/absolute/path/to/unpacked-extension-two"
93
- ]
94
- }
95
- }
96
- ```
97
-
98
- Every user-file path must be absolute.
99
- Each extension path must resolve to a directory containing a valid `manifest.json` and cannot contain a comma because Chrome uses commas to separate multiple startup paths.
100
- For extension-configured sessions, `executablePath` must identify Chrome for Testing or Chromium.
101
- Branded Google Chrome is rejected because tested releases can silently ignore unpacked-extension startup flags.
102
-
103
- A trusted project can replace the user extension list in `<workspace>/.pi/pi-chrome-devtools.json`.
104
- Relative paths resolve from the workspace (`ctx.cwd`):
105
-
106
- ```json
107
- {
108
- "browser": {
109
- "extensionPaths": ["./extension"]
110
- }
111
- }
112
- ```
113
-
114
- Project `extensionPaths` replace, rather than append to, the user array.
115
- A project file cannot override `browser.endpoint`, `browser.autoLaunch`, or `browser.executablePath`; browser connection settings remain machine-owned user configuration.
116
- Effective precedence is defaults, user settings, trusted project extension paths, then deprecated environment overrides.
117
- No new environment variable is required.
118
-
119
- When `extensionPaths` is non-empty, the extension skips attach-first behavior and starts an isolated, extension-owned managed browser with `--disable-extensions-except` and `--load-extension`.
120
- It fails before spawning when the endpoint is remote, auto-launch is disabled, an explicit port is occupied, the executable is missing, or the browser product is unsupported.
121
- It never adds extensions to, modifies, restarts, or closes an external browser.
122
-
123
- Settings are loaded on session start.
124
- After editing JSON, use `/reload` or replace the session; the old managed browser is closed before the new configuration is applied.
125
- Missing files preserve the existing no-extension behavior.
126
- Invalid JSON, invalid browser values, and missing manifests are left unchanged and ignored with an actionable warning.
64
+ > Unpacked extensions execute privileged browser code; load only trusted code.
65
+ > They require an extension-owned Chrome for Testing or Chromium process.
66
+ > Trusted project settings may replace only the extension-path list, not machine-owned connection settings.
127
67
 
128
68
  ### Experimental WebMCP
129
69
 
130
70
  > [!WARNING]
131
- > WebMCP support is experimental, disabled by default, and subject to Chrome protocol changes.
132
- > Page-provided tools operate the visible page with its current authentication, entitlement, and UI state.
133
- > Every call requires observable confirmation, including tools that claim to be read-only.
134
-
135
- Enable WebMCP only in the canonical user settings file:
136
-
137
- ```json
138
- {
139
- "webmcp": {
140
- "enabled": true
141
- }
142
- }
143
- ```
144
-
145
- A project `pi-chrome-devtools.json` cannot enable WebMCP or weaken confirmation policy.
146
- The settings menu labels WebMCP as experimental, persists the user-owned boolean atomically, and aborts active WebMCP work before disablement or browser replacement.
147
- The two gateway tools remain unavailable while the gate is disabled even if an older `tools` array contains their names.
148
- After enabling the gate, choose whether each gateway is available through `/chrome-devtools tools` or the main menu.
149
-
150
- WebMCP requires a Chrome build whose `/json/protocol` exposes the experimental `WebMCP` domain.
151
- Origin-trial sites must meet Chrome's Origin Trial, origin isolation, Permissions Policy, authentication, and browser feature requirements.
152
- For local pages outside an Origin Trial, open `chrome://flags/#enable-webmcp-testing`, enable **WebMCP for testing**, and relaunch a compatible Chrome build.
153
- The extension does not automatically add testing flags to a managed browser.
154
-
155
- `chrome_devtools_webmcp_list_tools` opens an operation-scoped page CDP session, lists frame-aware page tools, and returns bounded metadata with a deterministic schema-and-annotation digest.
156
- `chrome_devtools_webmcp_call_tool` re-discovers the exact page, document loader, frame, origin, tool, schema, annotations, and session generation before invocation and again after confirmation.
157
- The call gateway rejects print and JSON modes because they cannot provide observable confirmation.
158
- TUI and RPC modes use Pi's standard confirmation dialog.
159
- Cancellation is forwarded to Chrome through `WebMCP.cancelInvocation` after Chrome returns an invocation ID.
160
-
161
- Page URLs, origins, names, descriptions, schemas, errors, and outputs are untrusted.
162
- Displayed text strips terminal controls and bidirectional overrides, accepted JSON has explicit byte, depth, and collection limits, and model-visible output is capped at 50 KB or 2,000 lines.
163
- CDP text messages are capped at 8 MB before JSON parsing.
164
- Page-controlled `pattern` and `patternProperties` schemas and schemas exceeding 128 combinator branches are rejected because they cannot be evaluated safely on Pi's main thread.
165
- Annotations are descriptive hints only and never authorize a call.
166
- WebMCP is not backend MCP, generic browser automation, or a way to bypass browser policy.
167
-
168
- If listing reports that the WebMCP domain is unavailable, update Chrome and verify `/json/protocol` contains `WebMCP`.
169
- If a page lists no tools, verify that the site participates in the Origin Trial or that **WebMCP for testing** is enabled for local development.
170
- If a call reports a stale identity, list tools again after navigation, reload, frame changes, tool registration changes, settings changes, or browser replacement.
171
- Attached everyday browser profiles receive a stronger confirmation warning than isolated managed profiles.
71
+ > WebMCP is experimental, disabled by default, and subject to Chrome protocol changes.
72
+ > Page tools use the visible page's current authentication and require confirmation on every call.
172
73
 
173
- ### Deprecated environment overrides and manual endpoints
174
-
175
- The existing `PI_CHROME_DEVTOOLS_HOST`, `PI_CHROME_DEVTOOLS_PORT`, `PI_CHROME_DEVTOOLS_AUTO_LAUNCH`, and `PI_CHROME_DEVTOOLS_BROWSER` variables remain temporary compatibility overrides.
176
- They still take precedence over JSON, but every session that sees one emits a deprecation warning.
177
- Move their values to `browser.endpoint`, `browser.autoLaunch`, and `browser.executablePath`; the variables will be removed in a future version.
178
-
179
- Without unpacked extensions, browser discovery still checks platform-specific Chrome, Chromium, Brave, and Microsoft Edge candidates.
180
- Manual launch remains available when no unpacked extensions are configured:
181
-
182
- ```bash
183
- google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/pi-chrome-devtools
184
- ```
185
-
186
- On session shutdown, the extension terminates only browser processes it started and best-effort removes their temporary profiles.
187
- It never closes user-started browsers or remote endpoints.
74
+ Enable `webmcp.enabled` only in user settings, then choose which gateway tools are available through `/chrome-devtools tools`.
75
+ Project settings cannot enable WebMCP or weaken confirmation.
76
+ See [WebMCP setup and troubleshooting](./docs/browser-setup.md#experimental-webmcp) for compatible Chrome builds, browser flags, schema limits, and stale-page recovery.
188
77
 
189
78
  ## 🛠️ Tools
190
79
 
@@ -199,30 +88,22 @@ It never closes user-started browsers or remote endpoints.
199
88
 
200
89
  ### Tool exposure
201
90
 
202
- All eight tools are registered: one loader, five stable DevTools capabilities, and two fixed experimental WebMCP gateways.
203
-
204
- On a model/provider with native deferred-tool support, only `chrome_devtools_load` starts active for this extension.
205
-
91
+ The extension registers eight tools: one loader, five stable DevTools capabilities, and two fixed experimental WebMCP gateways.
92
+ With native deferred-tool support, only `chrome_devtools_load` starts active.
206
93
  The loader accepts a task-oriented `query`, matches it against the five stable capabilities plus enabled WebMCP gateways, and adds matching available tools without removing any active Pi tool.
207
-
208
94
  Loaded capability tools remain active for the rest of the session unless the user makes them unavailable through `/chrome-devtools`.
209
95
 
210
96
  Pi uses native deferred tool references on compatible Anthropic models, native additional-tools or tool-search loading on compatible OpenAI and Codex Responses models, and native Kimi loading on compatible OpenAI Chat Completions models.
211
-
212
97
  Kimi-compatible models declare `compat.deferredToolsMode: "kimi"` in Pi's model metadata.
213
-
214
98
  `azure-openai-responses` remains eager because Pi's Azure adapter does not implement native deferred tool-search serialization.
215
99
 
216
100
  When the selected model/provider lacks native deferred support, the extension activates every capability allowed by settings before the next model request instead of using Pi's cache-invalidating lazy-loading fallback.
217
-
218
101
  After a session enters eager exposure, it stays eager across later model switches to avoid removing tool definitions within that session.
219
-
220
102
  The capability tools omit active-only prompt snippets so native deferred loading does not rebuild the system-prompt prefix.
221
103
 
222
104
  The saved `tools` array controls which capabilities the extension may expose.
223
- The `webmcp.enabled` gate is applied before that catalog, so persisted WebMCP names cannot bypass a disabled gate.
105
+ The `webmcp.enabled` gate takes precedence, so persisted WebMCP names cannot bypass a disabled gate.
224
106
  Page-provided tool definitions appear only in list results and never alter Pi's provider-visible tool definitions.
225
-
226
107
  An empty array leaves the loader active but makes every browser capability unavailable.
227
108
 
228
109
  ### Screenshot files
@@ -253,55 +134,25 @@ If the model cannot inspect the inline image, ask it to read the saved path, for
253
134
 
254
135
  ## 💬 Commands
255
136
 
256
- ```text
257
- /chrome-devtools
258
- ```
259
-
260
- Opens a menu that shows the tool catalog size, whether that catalog is saved, the configured endpoint, the observed managed-browser state, and any settings or launch warning before you choose an action.
261
- The five actions stay on one level:
262
-
263
- - **Choose available browser tools…** — stage any combination of the five stable capabilities and, when enabled, the two experimental WebMCP gateways, then review the exact available/unavailable result before selecting **Apply tool changes**.
264
- - **Make all browser tools available…** or **Make all browser tools unavailable…** — preview the context-appropriate bulk change before applying it.
265
- - **Browser status** — inspect runtime, endpoint, launch mode, and the last launch attempt without probing the endpoint or starting Chrome.
266
- - **Browser settings** — immediately save the endpoint, auto-launch policy, browser executable, or experimental WebMCP gate.
267
- Inspect unpacked-extension paths and effective sources.
268
- A deprecated environment override remains effective until removed even when its underlying JSON value changes.
269
- - **Help** — view command usage and return to the menu.
270
-
271
- In the tool screen, **Select all** and **Select none** are unambiguous shortcuts; individual rows use friendly task labels while retaining their raw `chrome_devtools_*` identity in the description.
272
- Toggles remain a command-local draft.
273
- **Review changes** previews the exact effect, **Apply tool changes** saves it, and Cancel, Escape, Ctrl+C, disposal, or session replacement discards an unconfirmed draft without changing runtime tools or settings.
274
- A failed apply restores the previous availability and loaded-tool state, preserves the settings file, retains the draft for retry, and reports how to recover.
275
-
276
- Direct subcommands are also available:
277
-
278
- ```text
279
- /chrome-devtools help
280
- /chrome-devtools quickstart
281
- /chrome-devtools status
282
- /chrome-devtools settings
283
- /chrome-devtools tools
284
- /chrome-devtools toggle
285
- /chrome-devtools enable
286
- /chrome-devtools disable
287
- ```
288
-
289
- Compatibility aliases remain available: `toggle` and `select` mean `tools`, `on` means `enable`, and `off` means `disable`.
290
-
291
- - `help` shows command usage.
292
- - `quickstart` shows the configured CDP endpoint, endpoint source, auto-launch mode, browser candidates, last launch attempt, and launch hints.
293
- - `status` shows available and loaded capability counts, loader state, the persisted catalog, settings file path, endpoint source, launch mode, last launch attempt, and active non-Chrome tool count.
294
- - `settings` opens the same immediate-save browser settings flow used by the menu.
295
- - `tools` opens the same staged, width-safe availability and review flow used by the menu.
296
- - `toggle` and `select` are compatibility aliases for `tools`.
297
- - `enable` makes all currently gated capability tools available and follows the current native-deferred or eager exposure mode; `on` is a compatibility alias.
298
- - `disable` makes all capability tools unavailable and saves the empty catalog; `off` is a compatibility alias.
299
- The slash command and `chrome_devtools_load` remain available.
300
-
301
- The menu, `settings`, `tools`, `help`, `quickstart`, and `status` require TUI or RPC mode so their result is observable.
302
- TUI uses keyboard navigation and injected Pi keybindings; RPC receives equivalent standard dialogs.
303
- In print and JSON modes, interactive and informational routes reject explicitly instead of silently opening unavailable UI.
304
- The immediate `enable`/`disable` routes remain available for deterministic non-interactive use.
137
+ | Command | Purpose |
138
+ | --- | --- |
139
+ | `/chrome-devtools` | Manage browser-tool availability and browser settings. |
140
+ | `/chrome-devtools help` | Show command usage. |
141
+ | `/chrome-devtools quickstart` | Show the CDP endpoint, launch candidates, and setup hints. |
142
+ | `/chrome-devtools status` | Inspect tools, settings sources, and the last browser launch without probing or starting Chrome. |
143
+ | `/chrome-devtools settings` | Change browser settings; successful edits save immediately. |
144
+ | `/chrome-devtools tools` (aliases: `toggle`, `select`) | Stage tool availability, review the result, and apply it. |
145
+ | `/chrome-devtools enable` (alias: `on`) | Immediately make all currently gated capabilities available and save the selection. |
146
+ | `/chrome-devtools disable` (alias: `off`) | Immediately make all capabilities unavailable and save the empty selection. |
147
+
148
+ All routes support TUI and RPC and reject unknown or trailing arguments.
149
+ Only `enable` and `disable` also support print and JSON modes.
150
+ Disabling capabilities leaves the slash command and `chrome_devtools_load` available; see [Tool exposure](#tool-exposure).
151
+
152
+ Menu tool changes require **Apply tool changes**; cancellation discards the unconfirmed draft.
153
+ Failed apply leaves previous tool availability and settings intact and retains the draft for retry.
154
+ Browser settings instead save immediately, and closing the flow does not undo them.
155
+ See [Browser setup](./docs/browser-setup.md) for prerequisites and environment-override precedence, and [Experimental WebMCP](#experimental-webmcp) before enabling its gateways.
305
156
 
306
157
  ## ⚙️ Settings
307
158
 
@@ -311,6 +162,18 @@ The available capability names are saved to:
311
162
  ${PI_CODING_AGENT_DIR:-~/.pi/agent}/pi-chrome-devtools.json
312
163
  ```
313
164
 
165
+ Use **Browser settings** for connection preferences or **Choose available browser tools…** for tool availability.
166
+ For example, this partial document attaches to a user-started browser without launching another:
167
+
168
+ ```json
169
+ {
170
+ "browser": {
171
+ "endpoint": "http://127.0.0.1:9222",
172
+ "autoLaunch": false
173
+ }
174
+ }
175
+ ```
176
+
314
177
  The same file owns `browser.endpoint`, `browser.autoLaunch`, `browser.executablePath`, `browser.extensionPaths`, and user-only `webmcp.enabled`.
315
178
  Browser connection fields and `webmcp.enabled` are machine-owned user settings; trusted project files may replace only `browser.extensionPaths`.
316
179
  Confirmed menu changes apply before the next browser connection and close only an extension-owned managed browser.
@@ -328,6 +191,18 @@ The first subsequent settings save writes the canonical file.
328
191
  If both files exist, `pi-chrome-devtools.json` wins and the legacy file is ignored.
329
192
  The legacy filename is deprecated and will be removed in a future major release.
330
193
 
194
+ ## 🔒 Security and privacy
195
+
196
+ A CDP connection can inspect and change browser content, execute JavaScript, and access the selected browser profile's authenticated pages.
197
+ Connect only to trusted endpoints and profiles.
198
+
199
+ The extension never closes an external browser.
200
+ It closes only managed browser processes that it started and removes their temporary profiles on a best-effort basis.
201
+
202
+ Unpacked extensions run privileged browser code and are loaded only into an isolated managed browser after explicit configuration.
203
+ WebMCP page tools use the visible page's authentication and require confirmation before every call.
204
+ Screenshot output is restricted to the current working directory or OS temporary directory as described above.
205
+
331
206
  ## 🧠 Use cases
332
207
 
333
208
  - Debug front-end applications with an AI coding agent.
@@ -338,36 +213,19 @@ The legacy filename is deprecated and will be removed in a future major release.
338
213
 
339
214
  ## 🗂️ Package layout
340
215
 
341
- ```txt
216
+ ```text
342
217
  packages/pi-chrome-devtools/
343
- ├── dist/ # Generated TypeScript runtime loaded by Jiti
344
- ├── reference/webmcp/ # Non-entrypoint compatibility prototype
345
- ├── scripts/
346
- │ └── build-runtime.mjs # Deterministic runtime builder and boundary validator
347
- ├── src/
348
- │ ├── index.ts # Pi package entrypoint
349
- │ ├── chrome-devtools.ts # Extension registration and command orchestration
350
- │ ├── lazy-tools.ts # Deferred capability catalog and loader tool
351
- │ ├── webmcp/ # Lazy CDP protocol, discovery, policy, and invocation implementation
352
- │ └── *.ts # Package-local browser, CDP, tool, and storage modules
353
- ├── README.md
354
- ├── LICENSE
355
- ├── tsconfig.json
356
- └── package.json
357
- ```
358
-
359
- `index.ts` is the Pi entrypoint and forwards to `chrome-devtools.ts`; the other source modules are internal.
360
- The package exposes its Pi extension through `package.json`:
361
-
362
- ```json
363
- {
364
- "pi": {
365
- "extensions": ["./dist/index.ts"]
366
- }
367
- }
218
+ ├── src/ # Authoritative implementation and helpers
219
+ │ ├── index.ts # Thin Pi entrypoint
220
+ │ └── chrome-devtools.ts # Browser tools and command orchestration
221
+ ├── dist/ # Generated Jiti runtime
222
+ ├── scripts/build-runtime.mjs # Runtime builder
223
+ ├── docs/ # Published reference documentation
224
+ ├── reference/webmcp/ # Repository-only compatibility prototype
225
+ └── test/ # Behavior and lifecycle coverage
368
226
  ```
369
227
 
370
- The generated runtime is built from the authoritative `src/index.ts` graph and does not import back into `src`.
228
+ The generated runtime is built from `src/index.ts` and does not import back into `src`.
371
229
 
372
230
  ## 🔎 Keywords
373
231
 
@@ -5,7 +5,7 @@ import {
5
5
  configuredChromeDevtoolsTools,
6
6
  sanitizeChromeDevtoolsDisplay,
7
7
  setChromeDevtoolsSessionOwner
8
- } from "./chunk-B7B63PWB.js";
8
+ } from "./chunk-LLXOCRNF.js";
9
9
  import {
10
10
  applyRuntimeBrowserSettings,
11
11
  applyRuntimeWebMcpSetting,
@@ -16,8 +16,9 @@ import {
16
16
  saveWebMcpSettings,
17
17
  shutdownManagedBrowser,
18
18
  state,
19
+ syncManagedBrowserSettings,
19
20
  webMcpEnabled
20
- } from "./chunk-GVBXT3FB.js";
21
+ } from "./chunk-SRT3UUIP.js";
21
22
 
22
23
  // src/browser-settings-menu.ts
23
24
  import { isAbsolute } from "node:path";
@@ -237,6 +238,7 @@ async function saveAndApplyBrowserPatch(ctx, generation, ownerSignal, actionSign
237
238
  const loaded = await loadSettings({ cwd: ctx.cwd, projectTrusted });
238
239
  if (!isCurrent(generation, ownerSignal, actionSignal)) return false;
239
240
  applyRuntimeBrowserSettings(loaded.effectiveBrowser, loaded.paths, projectTrusted);
241
+ syncManagedBrowserSettings(ctx.sessionManager, loaded.effectiveBrowser);
240
242
  state.settingsNotice = loaded.notice;
241
243
  return loaded;
242
244
  } catch (error) {
@@ -337,4 +339,4 @@ function formatError(error) {
337
339
  export {
338
340
  showChromeDevtoolsBrowserSettings
339
341
  };
340
- //# sourceMappingURL=browser-settings-menu-N5O65I7S.js.map
342
+ //# sourceMappingURL=browser-settings-menu-IMB6HXSU.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/browser-settings-menu.ts"],
4
+ "sourcesContent": ["import { isAbsolute } from \"node:path\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport { defineMenu, runMenu } from \"@narumitw/pi-tui-kit\";\nimport { shutdownManagedBrowser, syncManagedBrowserSettings } from \"./browser-manager.js\";\nimport {\n\tapplyAvailableChromeDevtoolsTools,\n\tconfiguredChromeDevtoolsTools,\n\tsetChromeDevtoolsSessionOwner,\n} from \"./lazy-tools.js\";\nimport {\n\tapplyRuntimeBrowserSettings,\n\tapplyRuntimeWebMcpSetting,\n\tinvalidateWebMcpOperations,\n\tstate,\n\twebMcpEnabled,\n} from \"./runtime.js\";\nimport {\n\ttype BrowserSettingsPatch,\n\ttype BrowserSettingsSource,\n\tloadSettings,\n\tparseBrowserEndpoint,\n\ttype SettingsLoadResult,\n\tsaveBrowserSettings,\n\tsaveWebMcpSettings,\n} from \"./settings.js\";\nimport { sanitizeChromeDevtoolsDisplay } from \"./tool-selector.js\";\n\ntype BrowserSettingsScreen = \"settings\" | \"endpoint\" | \"executable\" | \"extensions\";\ntype BrowserSettingsAction =\n\t| \"open-endpoint\"\n\t| \"save-endpoint\"\n\t| \"set-auto-launch\"\n\t| \"set-webmcp\"\n\t| \"open-executable\"\n\t| \"save-executable\"\n\t| \"show-extensions\";\n\ninterface BrowserSettingsMenuState {\n\tload: SettingsLoadResult;\n}\n\ntype CommandContext = ExtensionCommandContext;\n\nexport async function showChromeDevtoolsBrowserSettings(\n\tpi: ExtensionAPI,\n\tctx: CommandContext,\n\tgeneration: number,\n): Promise<{ closeParent: boolean }> {\n\tsetChromeDevtoolsSessionOwner(pi, ctx.sessionManager);\n\tconst ownerSignal = state.sessionController.signal;\n\tconst isCurrent = () => generation === state.sessionGeneration && !ownerSignal.aborted;\n\tconst projectTrusted = ctx.isProjectTrusted();\n\tconst menu = defineMenu<BrowserSettingsMenuState, BrowserSettingsScreen, BrowserSettingsAction>({\n\t\tstart: \"settings\",\n\t\tscreens: {\n\t\t\tsettings: ({ state: current }) => {\n\t\t\t\tif (current.load.userFile.kind === \"invalid\") return invalidSettingsScreen(current.load);\n\t\t\t\tconst browser = current.load.effectiveBrowser;\n\t\t\t\treturn {\n\t\t\t\t\tkind: \"settings\",\n\t\t\t\t\ttitle: \"Chrome DevTools Browser settings\",\n\t\t\t\t\tlines: settingsContextLines(current.load),\n\t\t\t\t\titems: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"endpoint\",\n\t\t\t\t\t\t\tlabel: \"DevTools endpoint\",\n\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t'HTTP origin with an explicit port; enter \"default\" to restore 127.0.0.1:9222.',\n\t\t\t\t\t\t\tcurrentValue: sanitizeChromeDevtoolsDisplay(browser.endpoint),\n\t\t\t\t\t\t\taction: \"open-endpoint\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"auto-launch\",\n\t\t\t\t\t\t\tlabel: \"Auto-launch\",\n\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\"Launch an isolated managed browser when a local endpoint is unavailable.\",\n\t\t\t\t\t\t\tcurrentValue: browser.autoLaunchEnabled ? \"On\" : \"Off\",\n\t\t\t\t\t\t\tvalues: [\"On\", \"Off\"],\n\t\t\t\t\t\t\taction: \"set-auto-launch\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"executable\",\n\t\t\t\t\t\t\tlabel: \"Browser executable\",\n\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t'Absolute executable path; enter \"automatic\" to restore browser discovery.',\n\t\t\t\t\t\t\tcurrentValue: sanitizeChromeDevtoolsDisplay(browser.executablePath ?? \"Automatic\"),\n\t\t\t\t\t\t\taction: \"open-executable\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"webmcp\",\n\t\t\t\t\t\t\tlabel: \"WebMCP \u00B7 Experimental\",\n\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\"Allow user-confirmed calls to page-provided WebMCP tools in this browser session.\",\n\t\t\t\t\t\t\tcurrentValue: current.load.effectiveWebMcpEnabled ? \"On\" : \"Off\",\n\t\t\t\t\t\t\tvalues: [\"On\", \"Off\"],\n\t\t\t\t\t\t\taction: \"set-webmcp\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"extensions\",\n\t\t\t\t\t\t\tlabel: \"Unpacked extensions\",\n\t\t\t\t\t\t\tdescription: \"Inspect trusted extension paths configured in user or project JSON.\",\n\t\t\t\t\t\t\tcurrentValue: `${browser.extensionPaths.length} configured`,\n\t\t\t\t\t\t\taction: \"show-extensions\",\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t};\n\t\t\t},\n\t\t\tendpoint: ({ state: current }) => ({\n\t\t\t\tkind: \"input\",\n\t\t\t\ttitle: \"DevTools endpoint\",\n\t\t\t\tlines: [\n\t\t\t\t\tsanitizeChromeDevtoolsDisplay(\n\t\t\t\t\t\t`Current effective endpoint: ${current.load.effectiveBrowser.endpoint}`,\n\t\t\t\t\t),\n\t\t\t\t\t'Enter an HTTP origin with an explicit port, or \"default\".',\n\t\t\t\t],\n\t\t\t\tplaceholder: \"http://127.0.0.1:9222\",\n\t\t\t\taction: \"save-endpoint\",\n\t\t\t\thint: \"back\",\n\t\t\t}),\n\t\t\texecutable: ({ state: current }) => ({\n\t\t\t\tkind: \"input\",\n\t\t\t\ttitle: \"Browser executable\",\n\t\t\t\tlines: [\n\t\t\t\t\tsanitizeChromeDevtoolsDisplay(\n\t\t\t\t\t\t`Current effective executable: ${current.load.effectiveBrowser.executablePath ?? \"automatic discovery\"}`,\n\t\t\t\t\t),\n\t\t\t\t\t'Enter an absolute path, or \"automatic\".',\n\t\t\t\t],\n\t\t\t\tplaceholder: \"absolute browser executable path\",\n\t\t\t\taction: \"save-executable\",\n\t\t\t\thint: \"back\",\n\t\t\t}),\n\t\t\textensions: ({ state: current }) => ({\n\t\t\t\tkind: \"detail\",\n\t\t\t\ttitle: \"Unpacked extensions\",\n\t\t\t\tlines: extensionDetailLines(current.load),\n\t\t\t\thint: \"back\",\n\t\t\t}),\n\t\t},\n\t\tactions: {\n\t\t\t\"open-endpoint\": () => ({ kind: \"to\", screen: \"endpoint\" }),\n\t\t\t\"save-endpoint\": async ({ value, signal }) => {\n\t\t\t\tif (value === undefined) return { kind: \"rejected\" };\n\t\t\t\tlet endpoint: string | null;\n\t\t\t\ttry {\n\t\t\t\t\tconst trimmed = value.trim();\n\t\t\t\t\tendpoint =\n\t\t\t\t\t\ttrimmed.toLowerCase() === \"default\" ? null : parseBrowserEndpoint(trimmed).endpoint;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tnotifySaveFailure(ctx, error);\n\t\t\t\t\treturn { kind: \"rejected\" };\n\t\t\t\t}\n\t\t\t\tconst saved = await saveAndApplyBrowserPatch(\n\t\t\t\t\tctx,\n\t\t\t\t\tgeneration,\n\t\t\t\t\townerSignal,\n\t\t\t\t\tsignal,\n\t\t\t\t\tprojectTrusted,\n\t\t\t\t\t{ endpoint },\n\t\t\t\t);\n\t\t\t\tif (!saved) return { kind: \"rejected\" };\n\t\t\t\tnotifyEffectiveSave(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"endpoint\",\n\t\t\t\t\tsaved.effectiveBrowser.endpoint,\n\t\t\t\t\tsaved.effectiveBrowser.endpointSource,\n\t\t\t\t);\n\t\t\t\treturn { kind: \"back\" };\n\t\t\t},\n\t\t\t\"set-auto-launch\": async ({ value, signal }) => {\n\t\t\t\tif (value !== \"On\" && value !== \"Off\") return { kind: \"rejected\" };\n\t\t\t\tconst saved = await saveAndApplyBrowserPatch(\n\t\t\t\t\tctx,\n\t\t\t\t\tgeneration,\n\t\t\t\t\townerSignal,\n\t\t\t\t\tsignal,\n\t\t\t\t\tprojectTrusted,\n\t\t\t\t\t{ autoLaunch: value === \"On\" },\n\t\t\t\t);\n\t\t\t\tif (!saved) return { kind: \"rejected\" };\n\t\t\t\tnotifyEffectiveSave(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"auto-launch\",\n\t\t\t\t\tsaved.effectiveBrowser.autoLaunchEnabled ? \"On\" : \"Off\",\n\t\t\t\t\tsaved.effectiveBrowser.autoLaunchSource,\n\t\t\t\t);\n\t\t\t\treturn { kind: \"stay\" };\n\t\t\t},\n\t\t\t\"set-webmcp\": async ({ value, signal }) => {\n\t\t\t\tif (value !== \"On\" && value !== \"Off\") return { kind: \"rejected\" };\n\t\t\t\tconst saved = await saveAndApplyWebMcpSetting(\n\t\t\t\t\tpi,\n\t\t\t\t\tctx,\n\t\t\t\t\tgeneration,\n\t\t\t\t\townerSignal,\n\t\t\t\t\tsignal,\n\t\t\t\t\tprojectTrusted,\n\t\t\t\t\tvalue === \"On\",\n\t\t\t\t);\n\t\t\t\tif (!saved) return { kind: \"rejected\" };\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\tsaved.effectiveWebMcpEnabled\n\t\t\t\t\t\t? \"Experimental WebMCP enabled. Every page-provided tool call requires confirmation. Choose gateway availability under Browser tools.\"\n\t\t\t\t\t\t: \"Experimental WebMCP disabled. Active WebMCP work was aborted and both gateway tools are unavailable.\",\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t\treturn { kind: \"stay\" };\n\t\t\t},\n\t\t\t\"open-executable\": () => ({ kind: \"to\", screen: \"executable\" }),\n\t\t\t\"save-executable\": async ({ value, signal }) => {\n\t\t\t\tif (value === undefined) return { kind: \"rejected\" };\n\t\t\t\tconst trimmed = value.trim();\n\t\t\t\tconst reset = [\"automatic\", \"default\", \"none\"].includes(trimmed.toLowerCase());\n\t\t\t\tif (!reset && (!trimmed || !isAbsolute(trimmed))) {\n\t\t\t\t\tnotifySaveFailure(ctx, new Error('Enter an absolute path or \"automatic\".'));\n\t\t\t\t\treturn { kind: \"rejected\" };\n\t\t\t\t}\n\t\t\t\tconst executablePath = reset ? null : trimmed;\n\t\t\t\tconst saved = await saveAndApplyBrowserPatch(\n\t\t\t\t\tctx,\n\t\t\t\t\tgeneration,\n\t\t\t\t\townerSignal,\n\t\t\t\t\tsignal,\n\t\t\t\t\tprojectTrusted,\n\t\t\t\t\t{ executablePath },\n\t\t\t\t);\n\t\t\t\tif (!saved) return { kind: \"rejected\" };\n\t\t\t\tnotifyEffectiveSave(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"browser executable\",\n\t\t\t\t\tsaved.effectiveBrowser.executablePath ?? \"automatic discovery\",\n\t\t\t\t\tsaved.effectiveBrowser.executablePathSource,\n\t\t\t\t);\n\t\t\t\treturn { kind: \"back\" };\n\t\t\t},\n\t\t\t\"show-extensions\": () => ({ kind: \"to\", screen: \"extensions\" }),\n\t\t},\n\t});\n\tconst result = await runMenu(ctx, menu, {\n\t\tgetState: async ({ signal }) => {\n\t\t\tsignal.throwIfAborted();\n\t\t\tconst load = await loadSettings({ cwd: ctx.cwd, projectTrusted });\n\t\t\tsignal.throwIfAborted();\n\t\t\treturn { load };\n\t\t},\n\t\tsignal: ownerSignal,\n\t\tisCurrent,\n\t\tonError: (currentCtx, error) =>\n\t\t\tcurrentCtx.ui.notify(\n\t\t\t\tsanitizeChromeDevtoolsDisplay(\n\t\t\t\t\t`Chrome DevTools browser settings failed: ${formatError(error)}`,\n\t\t\t\t),\n\t\t\t\t\"error\",\n\t\t\t),\n\t});\n\treturn { closeParent: result.kind === \"closed\" && result.reason === \"close\" };\n}\n\nasync function saveAndApplyBrowserPatch(\n\tctx: CommandContext,\n\tgeneration: number,\n\townerSignal: AbortSignal,\n\tactionSignal: AbortSignal,\n\tprojectTrusted: boolean,\n\tpatch: BrowserSettingsPatch,\n) {\n\ttry {\n\t\tawait ctx.waitForIdle();\n\t\tif (!isCurrent(generation, ownerSignal, actionSignal)) return false;\n\t\tawait saveBrowserSettings(patch);\n\t\tif (!isCurrent(generation, ownerSignal, actionSignal)) return false;\n\t\tinvalidateWebMcpOperations(ctx.sessionManager, \"Chrome DevTools browser configuration changed\");\n\t\tawait shutdownManagedBrowser(undefined, { owner: ctx.sessionManager });\n\t\tif (!isCurrent(generation, ownerSignal, actionSignal)) return false;\n\t\tconst loaded = await loadSettings({ cwd: ctx.cwd, projectTrusted });\n\t\tif (!isCurrent(generation, ownerSignal, actionSignal)) return false;\n\t\tapplyRuntimeBrowserSettings(loaded.effectiveBrowser, loaded.paths, projectTrusted);\n\t\tsyncManagedBrowserSettings(ctx.sessionManager, loaded.effectiveBrowser);\n\t\tstate.settingsNotice = loaded.notice;\n\t\treturn loaded;\n\t} catch (error) {\n\t\tif (isCurrent(generation, ownerSignal, actionSignal)) notifySaveFailure(ctx, error);\n\t\treturn false;\n\t}\n}\n\nasync function saveAndApplyWebMcpSetting(\n\tpi: ExtensionAPI,\n\tctx: CommandContext,\n\tgeneration: number,\n\townerSignal: AbortSignal,\n\tactionSignal: AbortSignal,\n\tprojectTrusted: boolean,\n\tenabled: boolean,\n) {\n\tconst previousEnabled = webMcpEnabled(ctx.sessionManager);\n\tconst previousTools = configuredChromeDevtoolsTools(pi);\n\ttry {\n\t\tawait ctx.waitForIdle();\n\t\tif (!isCurrent(generation, ownerSignal, actionSignal)) return false;\n\t\tapplyRuntimeWebMcpSetting(enabled, ctx.sessionManager);\n\t\tapplyAvailableChromeDevtoolsTools(pi, previousTools);\n\t\tawait saveWebMcpSettings(enabled);\n\t\tif (!isOwnerCurrent(generation, ownerSignal)) return false;\n\t\tconst loaded = await loadSettings({ cwd: ctx.cwd, projectTrusted });\n\t\tif (!isOwnerCurrent(generation, ownerSignal)) return false;\n\t\tstate.settingsNotice = loaded.notice;\n\t\treturn loaded;\n\t} catch (error) {\n\t\tif (!isOwnerCurrent(generation, ownerSignal)) return false;\n\t\tlet rollbackError: unknown;\n\t\ttry {\n\t\t\tapplyRuntimeWebMcpSetting(previousEnabled, ctx.sessionManager);\n\t\t\tapplyAvailableChromeDevtoolsTools(pi, previousTools);\n\t\t} catch (caught) {\n\t\t\trollbackError = caught;\n\t\t}\n\t\tif (isCurrent(generation, ownerSignal, actionSignal)) {\n\t\t\tnotifySaveFailure(\n\t\t\t\tctx,\n\t\t\t\trollbackError\n\t\t\t\t\t? new Error(\n\t\t\t\t\t\t\t`${formatError(error)}; WebMCP rollback failed: ${formatError(rollbackError)}`,\n\t\t\t\t\t\t)\n\t\t\t\t\t: error,\n\t\t\t);\n\t\t}\n\t\treturn false;\n\t}\n}\n\nfunction isOwnerCurrent(generation: number, ownerSignal: AbortSignal) {\n\treturn generation === state.sessionGeneration && !ownerSignal.aborted;\n}\n\nfunction isCurrent(generation: number, ownerSignal: AbortSignal, actionSignal: AbortSignal) {\n\treturn isOwnerCurrent(generation, ownerSignal) && !actionSignal.aborted;\n}\n\nfunction invalidSettingsScreen(load: SettingsLoadResult) {\n\tconst reason =\n\t\tload.userFile.kind === \"invalid\"\n\t\t\t? load.userFile.reason\n\t\t\t: \"The user settings file is unavailable.\";\n\treturn {\n\t\tkind: \"detail\" as const,\n\t\ttitle: \"Chrome DevTools Browser settings \u00B7 Read only\",\n\t\tlines: [\n\t\t\tsanitizeChromeDevtoolsDisplay(\n\t\t\t\t`Fix the active user settings before saving; the file will not be overwritten. ${reason}`,\n\t\t\t),\n\t\t],\n\t\thint: \"back\" as const,\n\t};\n}\n\nfunction settingsContextLines(load: SettingsLoadResult) {\n\tconst browser = load.effectiveBrowser;\n\treturn [\n\t\t`User settings \u00B7 ${load.paths.user}`,\n\t\t`Effective sources: endpoint ${browser.endpointSource} \u00B7 auto-launch ${browser.autoLaunchSource} \u00B7 executable ${browser.executablePathSource}`,\n\t\t...(load.notice ? [`Warning: ${load.notice}`] : []),\n\t].map((line) => sanitizeChromeDevtoolsDisplay(line));\n}\n\nfunction extensionDetailLines(load: SettingsLoadResult) {\n\tconst browser = load.effectiveBrowser;\n\treturn [\n\t\t`Effective source: ${browser.extensionPathsSource}`,\n\t\t...(browser.extensionPaths.length > 0\n\t\t\t? browser.extensionPaths.map((extensionPath) => `- ${extensionPath}`)\n\t\t\t: [\"No unpacked extensions are configured.\"]),\n\t\t\"Edit browser.extensionPaths in the user JSON or a trusted project JSON, then run /reload.\",\n\t\t\"Unpacked extensions execute privileged browser code; load only paths you trust.\",\n\t].map((line) => sanitizeChromeDevtoolsDisplay(line));\n}\n\nfunction notifyEffectiveSave(\n\tctx: CommandContext,\n\tsetting: string,\n\teffectiveValue: string,\n\tsource: BrowserSettingsSource,\n) {\n\tconst overrideNotice =\n\t\tsource === \"environment\" ? \" The deprecated environment override remains effective.\" : \"\";\n\tctx.ui.notify(\n\t\tsanitizeChromeDevtoolsDisplay(\n\t\t\t`Chrome DevTools ${setting} saved. Effective ${setting}: ${effectiveValue} (${source}).${overrideNotice}`,\n\t\t),\n\t\t\"info\",\n\t);\n}\n\nfunction notifySaveFailure(ctx: CommandContext, error: unknown) {\n\tctx.ui.notify(\n\t\tsanitizeChromeDevtoolsDisplay(\n\t\t\t`Chrome DevTools browser settings save failed; previous settings remain active: ${formatError(error)}`,\n\t\t),\n\t\t\"warning\",\n\t);\n}\n\nfunction formatError(error: unknown) {\n\treturn error instanceof Error ? error.message : String(error);\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["isCurrent"]
7
+ }
@@ -10,11 +10,12 @@ import {
10
10
  launchHint,
11
11
  state,
12
12
  withEndpointRetry
13
- } from "./chunk-GVBXT3FB.js";
13
+ } from "./chunk-SRT3UUIP.js";
14
14
 
15
15
  // src/cdp-client.ts
16
16
  var MAX_BUFFERED_EVENTS_PER_METHOD = 32;
17
17
  var MAX_CDP_MESSAGE_BYTES = 8 * 1024 * 1024;
18
+ var activePageIds = /* @__PURE__ */ new WeakMap();
18
19
  async function listPages(options = {}) {
19
20
  const waitMs = options.waitMs ?? DEFAULT_ENDPOINT_WAIT_MS;
20
21
  await ensureDevToolsEndpoint(waitMs, options.signal, options.sessionOwner);
@@ -38,13 +39,13 @@ async function getPage(pageId, options = {}) {
38
39
  async function resolvePage(pageId, options = {}) {
39
40
  const pages = await listPages(options);
40
41
  if (pageId) return requirePage(pageId, pages);
41
- const page = resolveDefaultPage(pages);
42
+ const page = resolveDefaultPage(pages, options.sessionOwner);
42
43
  if (!page) {
43
44
  throw new Error(
44
45
  [
45
46
  `No Chrome pages found at ${devToolsEndpoint(options.sessionOwner)}.`,
46
47
  "Use chrome_devtools_navigate with a URL to create a page, or open a Chrome tab manually.",
47
- launchHint()
48
+ launchHint(options.sessionOwner)
48
49
  ].join("\n")
49
50
  );
50
51
  }
@@ -53,17 +54,26 @@ async function resolvePage(pageId, options = {}) {
53
54
  async function resolvePageForNavigation(pageId, options = {}) {
54
55
  const pages = await listPages(options);
55
56
  if (pageId) return { created: false, page: requirePage(pageId, pages) };
56
- const page = resolveDefaultPage(pages);
57
+ const page = resolveDefaultPage(pages, options.sessionOwner);
57
58
  if (page) return { created: false, page };
58
59
  return { created: true, page: await createPage("about:blank", options) };
59
60
  }
60
- function resolveDefaultPage(pages) {
61
- if (!state.activePageId) return pages[0];
62
- const activePage = pages.find((candidate) => candidate.id === state.activePageId);
61
+ function resolveDefaultPage(pages, owner) {
62
+ const activePageId = owner ? activePageIds.get(owner) : state.activePageId;
63
+ if (!activePageId) return pages[0];
64
+ const activePage = pages.find((candidate) => candidate.id === activePageId);
63
65
  if (activePage) return activePage;
64
- state.activePageId = void 0;
66
+ setActivePageId(owner, void 0);
65
67
  return pages[0];
66
68
  }
69
+ function setActivePageId(owner, pageId) {
70
+ if (!owner) {
71
+ state.activePageId = pageId;
72
+ return;
73
+ }
74
+ if (pageId) activePageIds.set(owner, pageId);
75
+ else activePageIds.delete(owner);
76
+ }
67
77
  function requirePage(pageId, pages) {
68
78
  const page = pages.find((candidate) => candidate.id === pageId);
69
79
  if (page) return page;
@@ -365,9 +375,10 @@ export {
365
375
  getPage,
366
376
  resolvePage,
367
377
  resolvePageForNavigation,
378
+ setActivePageId,
368
379
  formatPage,
369
380
  textResult,
370
381
  withCdp,
371
382
  CdpClient
372
383
  };
373
- //# sourceMappingURL=chunk-RTECHAEZ.js.map
384
+ //# sourceMappingURL=chunk-GTTYS5GT.js.map