@narumitw/pi-chrome-devtools 0.52.1 → 0.52.3

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.
package/README.md CHANGED
@@ -1,37 +1,22 @@
1
- # 🌐 pi-chrome-devtools — Chrome DevTools Tools for Pi Agents
1
+ # 🌐 pi-chrome-devtools — Inspect and Control Chrome from Pi
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
- `@narumitw/pi-chrome-devtools` is a native [Pi coding agent](https://pi.dev) extension that exposes Chrome DevTools Protocol (CDP) automation as Pi tools.
5
+ Let Pi inspect browser tabs, navigate pages, evaluate JavaScript, and capture screenshots through native Chrome DevTools Protocol tools.
6
6
 
7
- Use it to let the Pi agent inspect browser tabs, navigate pages, evaluate JavaScript, and capture screenshots while debugging web apps or validating UI behavior.
7
+ Use it for web debugging, UI validation, and browser-assisted investigation without running an MCP server.
8
8
 
9
- This package is inspired by [`chrome-devtools-mcp`](https://github.com/ChromeDevTools/chrome-devtools-mcp), but it is implemented as native Pi tools instead of an MCP server.
9
+ The design is inspired by [`chrome-devtools-mcp`](https://github.com/ChromeDevTools/chrome-devtools-mcp), not guaranteed to be compatible with it.
10
10
 
11
11
  ## ✨ Features
12
12
 
13
- - Lists inspectable Chrome tabs and pages.
14
- - Selects an active Chrome page for later tool calls.
15
- - Navigates Chrome to a target URL, creating an inspectable page when none exists.
16
- - Recovers from stale active page selections by falling back to an available page.
17
- - Evaluates JavaScript in the selected page.
18
- - Captures PNG screenshots, including optional full-page screenshots, and saves them to disk.
19
- - Renders compact tool results that expand/collapse with Pi's default output toggle (`Ctrl+O`).
20
- - Reuses an existing Chrome DevTools Protocol endpoint when one is already available.
21
- - Lazily auto-launches a Chromium-family browser for missing local endpoints, with Chrome,
22
- Chromium, Brave, and Edge fallbacks.
23
- - Loads one or more user-approved unpacked extensions into an isolated managed Chrome for Testing
24
- or Chromium browser.
25
- - Uses a dynamic managed DevTools port by default to avoid port conflicts, while preserving
26
- explicit endpoint overrides.
27
- - Retries briefly while Chrome is starting and reports actionable endpoint errors.
28
- - Shows statusline activity only while Chrome DevTools tools are running.
29
- - Keeps one loader tool active and exposes matching browser capabilities only when the agent needs them.
30
- - Provides a state-first `/chrome-devtools` menu for tool availability, editable browser settings,
31
- browser status, setup, and help.
32
- - Stages menu-based availability changes for exact review before one confirmed apply.
33
- - Uses `@narumitw/pi-tui-kit` for width-safe TUI menus and equivalent RPC dialogs.
34
- - Persists the Chrome DevTools lazy-load catalog across Pi restarts.
13
+ - 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.
16
+ - 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`.
18
+ - Shows compact expandable results and activity only while browser tools are running.
19
+ - Persists reviewed tool availability while keeping browser connection settings machine-owned.
35
20
 
36
21
  ## 📦 Install
37
22
 
@@ -45,25 +30,28 @@ Try without installing permanently:
45
30
  pi -e npm:@narumitw/pi-chrome-devtools
46
31
  ```
47
32
 
48
- Try this package locally from the repository root:
33
+ Build and try this package locally from the repository root:
49
34
 
50
35
  ```bash
36
+ npm --workspace @narumitw/pi-chrome-devtools run build
51
37
  pi -e ./packages/pi-chrome-devtools
52
38
  ```
53
39
 
54
- The package declares `dist/index.ts`, so an unbuilt local checkout must run `npm --workspace @narumitw/pi-chrome-devtools run build` before Pi loads the package directory.
40
+ The package declares `dist/index.ts`, so an unbuilt local checkout must run the build before Pi loads the package directory.
55
41
 
56
- `just try chrome-devtools` runs that build automatically.
42
+ ## 🚀 Quick start
57
43
 
58
- ## 🚀 Browser startup
44
+ Start Pi and ask the agent to load the Chrome DevTools capability needed for the task.
45
+ The extension first tries `http://127.0.0.1:9222` and otherwise launches an isolated local Chromium-family browser by default.
46
+ Run `/chrome-devtools` to review browser status, settings, help, and available tools.
59
47
 
60
- Without unpacked extensions, the extension first tries `browser.endpoint`, defaulting to
61
- `http://127.0.0.1:9222`. If that local endpoint is unavailable and `browser.autoLaunch` is `true`, it
62
- lazily launches an extension-owned Chromium-family browser with an isolated temp profile and retries
63
- the CDP request. Existing endpoints are reused and are never terminated by the extension.
48
+ ## 🌐 Browser setup
64
49
 
65
- Configure the canonical user file at
66
- `${PI_CODING_AGENT_DIR:-~/.pi/agent}/pi-chrome-devtools.json`:
50
+ Without unpacked extensions, the extension first tries `browser.endpoint`, defaulting to `http://127.0.0.1:9222`.
51
+ 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.
52
+ Existing endpoints are reused and never terminated by the extension.
53
+
54
+ Configure the canonical user file at `${PI_CODING_AGENT_DIR:-~/.pi/agent}/pi-chrome-devtools.json`:
67
55
 
68
56
  ```json
69
57
  {
@@ -75,21 +63,21 @@ Configure the canonical user file at
75
63
  }
76
64
  ```
77
65
 
78
- `browser.endpoint` must be an HTTP origin with an explicit port and no credentials, path, query, or
79
- fragment. Omitting it keeps attach-first behavior on `127.0.0.1:9222` and lets a managed launch use
80
- Chrome's dynamic DevTools port mode (`--remote-debugging-port=0`). Explicitly saving an endpoint pins
81
- managed launches to that port. `browser.autoLaunch` defaults to `true`. `browser.executablePath` is
82
- optional and must be an absolute path; when absent, normal browser discovery applies.
66
+ `browser.endpoint` must be an HTTP origin with an explicit port and no credentials, path, query, or fragment.
67
+ 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`).
68
+ Explicitly saving an endpoint pins managed launches to that port.
69
+ `browser.autoLaunch` defaults to `true`.
70
+ `browser.executablePath` is optional and must be an absolute path; when absent, normal browser discovery applies.
83
71
 
84
- The configured endpoint must expose the standard CDP HTTP discovery routes such as `/json/version`
85
- and `/json/list`. Chrome's newer built-in permission flow can listen on port `9222` while returning
86
- `404` from those routes; setting the same HTTP origin does not by itself make that flow compatible.
72
+ The configured endpoint must expose the standard CDP HTTP discovery routes such as `/json/version` and `/json/list`.
73
+ 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.
87
74
 
88
75
  ### Unpacked extensions
89
76
 
90
77
  > [!WARNING]
91
- > An unpacked extension executes privileged browser code. Load only code you trust. Project settings
92
- > are honored only when Pi reports the project as trusted.
78
+ > An unpacked extension executes privileged browser code.
79
+ > Load only code you trust.
80
+ > Project settings are honored only when Pi reports the project as trusted.
93
81
 
94
82
  Add trusted unpacked-extension paths to the same canonical user file:
95
83
 
@@ -105,14 +93,13 @@ Add trusted unpacked-extension paths to the same canonical user file:
105
93
  }
106
94
  ```
107
95
 
108
- Every user-file path must be absolute. Each extension path must resolve to a directory containing a
109
- valid `manifest.json` and cannot contain a comma because Chrome uses commas to separate multiple
110
- startup paths. For extension-configured sessions, `executablePath` must identify Chrome for
111
- Testing or Chromium. Branded Google Chrome is rejected because tested releases can silently ignore
112
- unpacked-extension startup flags.
96
+ Every user-file path must be absolute.
97
+ 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.
98
+ For extension-configured sessions, `executablePath` must identify Chrome for Testing or Chromium.
99
+ Branded Google Chrome is rejected because tested releases can silently ignore unpacked-extension startup flags.
113
100
 
114
- A trusted project can replace the user extension list in
115
- `<workspace>/.pi/pi-chrome-devtools.json`. Relative paths resolve from the workspace (`ctx.cwd`):
101
+ A trusted project can replace the user extension list in `<workspace>/.pi/pi-chrome-devtools.json`.
102
+ Relative paths resolve from the workspace (`ctx.cwd`):
116
103
 
117
104
  ```json
118
105
  {
@@ -122,43 +109,37 @@ A trusted project can replace the user extension list in
122
109
  }
123
110
  ```
124
111
 
125
- Project `extensionPaths` replace, rather than append to, the user array. A project file cannot
126
- override `browser.endpoint`, `browser.autoLaunch`, or `browser.executablePath`; browser connection
127
- settings remain machine-owned user configuration. Effective precedence is defaults, user settings,
128
- trusted project extension paths, then deprecated environment overrides. No new environment variable
129
- is required.
112
+ Project `extensionPaths` replace, rather than append to, the user array.
113
+ A project file cannot override `browser.endpoint`, `browser.autoLaunch`, or `browser.executablePath`; browser connection settings remain machine-owned user configuration.
114
+ Effective precedence is defaults, user settings, trusted project extension paths, then deprecated environment overrides.
115
+ No new environment variable is required.
130
116
 
131
- When `extensionPaths` is non-empty, the extension skips attach-first behavior and starts an isolated,
132
- extension-owned managed browser with `--disable-extensions-except` and `--load-extension`. It fails
133
- before spawning when the endpoint is remote, auto-launch is disabled, an explicit port is occupied,
134
- the executable is missing, or the browser product is unsupported. It never adds extensions to,
135
- modifies, restarts, or closes an external browser.
117
+ 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`.
118
+ 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.
119
+ It never adds extensions to, modifies, restarts, or closes an external browser.
136
120
 
137
- Settings are loaded on session start. After editing JSON, use `/reload` or replace the session; the
138
- old managed browser is closed before the new configuration is applied. Missing files preserve the
139
- existing no-extension behavior. Invalid JSON, invalid browser values, and missing manifests are left
140
- unchanged and ignored with an actionable warning.
121
+ Settings are loaded on session start.
122
+ After editing JSON, use `/reload` or replace the session; the old managed browser is closed before the new configuration is applied.
123
+ Missing files preserve the existing no-extension behavior.
124
+ Invalid JSON, invalid browser values, and missing manifests are left unchanged and ignored with an actionable warning.
141
125
 
142
126
  ### Deprecated environment overrides and manual endpoints
143
127
 
144
- The existing `PI_CHROME_DEVTOOLS_HOST`, `PI_CHROME_DEVTOOLS_PORT`,
145
- `PI_CHROME_DEVTOOLS_AUTO_LAUNCH`, and `PI_CHROME_DEVTOOLS_BROWSER` variables remain temporary
146
- compatibility overrides. They still take precedence over JSON, but every session that sees one emits
147
- a deprecation warning. Move their values to `browser.endpoint`, `browser.autoLaunch`, and
148
- `browser.executablePath`; the variables will be removed in a future version.
128
+ 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.
129
+ They still take precedence over JSON, but every session that sees one emits a deprecation warning.
130
+ Move their values to `browser.endpoint`, `browser.autoLaunch`, and `browser.executablePath`; the variables will be removed in a future version.
149
131
 
150
- Without unpacked extensions, browser discovery still checks platform-specific Chrome, Chromium,
151
- Brave, and Microsoft Edge candidates. Manual launch remains available when no unpacked extensions are
152
- configured:
132
+ Without unpacked extensions, browser discovery still checks platform-specific Chrome, Chromium, Brave, and Microsoft Edge candidates.
133
+ Manual launch remains available when no unpacked extensions are configured:
153
134
 
154
135
  ```bash
155
136
  google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/pi-chrome-devtools
156
137
  ```
157
138
 
158
- On session shutdown, the extension terminates only browser processes it started and best-effort
159
- removes their temporary profiles. It never closes user-started browsers or remote endpoints.
139
+ On session shutdown, the extension terminates only browser processes it started and best-effort removes their temporary profiles.
140
+ It never closes user-started browsers or remote endpoints.
160
141
 
161
- ## 🛠️ Pi tools
142
+ ## 🛠️ Tools
162
143
 
163
144
  - `chrome_devtools_load` — find and load browser capabilities relevant to a task.
164
145
  - `chrome_devtools_list_pages` — list inspectable Chrome tabs/pages.
@@ -167,28 +148,36 @@ removes their temporary profiles. It never closes user-started browsers or remot
167
148
  - `chrome_devtools_evaluate` — evaluate JavaScript in the selected page.
168
149
  - `chrome_devtools_screenshot` — capture a PNG screenshot and save it as a PNG file.
169
150
 
170
- ### Lazy tool loading
151
+ ### Tool exposure
152
+
153
+ All six tools are registered.
171
154
 
172
- All six tools are registered, but only `chrome_devtools_load` starts active for this extension.
155
+ On a model/provider with native deferred-tool support, only `chrome_devtools_load` starts active for this extension.
173
156
 
174
157
  The loader accepts a task-oriented `query`, matches it against the five capability tools, and adds matching available tools without removing any active Pi tool.
175
158
 
176
159
  Loaded capability tools remain active for the rest of the session unless the user makes them unavailable through `/chrome-devtools`.
177
160
 
178
- Pi uses native deferred tool references on compatible Anthropic and OpenAI models.
161
+ 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.
162
+
163
+ Kimi-compatible models declare `compat.deferredToolsMode: "kimi"` in Pi's model metadata.
164
+
165
+ `azure-openai-responses` remains eager because Pi's Azure adapter does not implement native deferred tool-search serialization.
179
166
 
180
- Other models receive Pi's safe fallback: the newly active definitions appear in the normal tool list on the next model request.
167
+ 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.
181
168
 
182
- The capability tools omit active-only prompt snippets so a lazy load does not rebuild the system prompt prefix.
169
+ After a session enters eager exposure, it stays eager across later model switches to avoid removing tool definitions within that session.
183
170
 
184
- The saved `tools` array controls which capabilities the loader may expose.
171
+ The capability tools omit active-only prompt snippets so native deferred loading does not rebuild the system-prompt prefix.
172
+
173
+ The saved `tools` array controls which capabilities the extension may expose.
185
174
 
186
175
  An empty array leaves the loader active but makes every browser capability unavailable.
187
176
 
188
177
  ### Screenshot files
189
178
 
190
- `chrome_devtools_screenshot` always saves the captured PNG to disk. If `savePath` is omitted,
191
- the extension writes a unique temp file such as:
179
+ `chrome_devtools_screenshot` always saves the captured PNG to disk.
180
+ If `savePath` is omitted, the extension writes a unique temp file such as:
192
181
 
193
182
  ```text
194
183
  /tmp/pi-chrome-devtools-screenshot-<uuid>.png
@@ -203,43 +192,35 @@ chrome_devtools_screenshot({
203
192
  });
204
193
  ```
205
194
 
206
- Relative `savePath` values resolve from Pi's current working directory. A single leading `@`
207
- is stripped to match Pi file-mention paths. Absolute paths are accepted only when they stay
208
- inside the current working directory or the OS temp directory. Paths containing `..` segments,
209
- NUL bytes, symlinked parent directories, directories as targets, final symbolic-link targets, or
210
- other non-regular file targets are rejected. Existing regular files at the target path are
211
- replaced. The tool result includes the resolved path, byte count, and an inline image block when
212
- the active model/provider can consume images. If the model cannot inspect the inline image, ask it
213
- to read the saved path, for example `read({ path: "artifacts/homepage.png" })`.
195
+ Relative `savePath` values resolve from Pi's current working directory.
196
+ A single leading `@` is stripped to match Pi file-mention paths.
197
+ Absolute paths are accepted only when they stay inside the current working directory or the OS temp directory.
198
+ Paths containing `..` segments, NUL bytes, symlinked parent directories, directories as targets, final symbolic-link targets, or other non-regular file targets are rejected.
199
+ Existing regular files at the target path are replaced.
200
+ The tool result includes the resolved path, byte count, and an inline image block when the active model/provider can consume images.
201
+ If the model cannot inspect the inline image, ask it to read the saved path, for example `read({ path: "artifacts/homepage.png" })`.
214
202
 
215
- ## 💬 Command
203
+ ## 💬 Commands
216
204
 
217
205
  ```text
218
206
  /chrome-devtools
219
207
  ```
220
208
 
221
- Opens a menu that shows the lazy catalog size, whether that catalog is saved, the configured
222
- endpoint, the observed managed-browser state, and any settings or launch warning before you choose
223
- an action. The five actions stay on one level:
224
-
225
- - **Choose available browser tools…** — stage any combination of the five capabilities, then review
226
- the exact available/unavailable result before selecting **Apply tool changes**.
227
- - **Make all browser tools available…** or **Make all browser tools unavailable…** — preview the
228
- context-appropriate bulk change before applying it.
229
- - **Browser status** — inspect runtime, endpoint, launch mode, and the last launch attempt without
230
- probing the endpoint or starting Chrome.
231
- - **Browser settings** — immediately save the endpoint, auto-launch policy, or browser executable;
232
- inspect unpacked-extension paths and effective sources. A deprecated environment override remains
233
- effective until removed even when its underlying JSON value changes.
209
+ 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.
210
+ The five actions stay on one level:
211
+
212
+ - **Choose available browser tools…** — stage any combination of the five capabilities, then review the exact available/unavailable result before selecting **Apply tool changes**.
213
+ - **Make all browser tools available…** or **Make all browser tools unavailable…** — preview the context-appropriate bulk change before applying it.
214
+ - **Browser status** — inspect runtime, endpoint, launch mode, and the last launch attempt without probing the endpoint or starting Chrome.
215
+ - **Browser settings** — immediately save the endpoint, auto-launch policy, or browser executable.
216
+ Inspect unpacked-extension paths and effective sources.
217
+ A deprecated environment override remains effective until removed even when its underlying JSON value changes.
234
218
  - **Help** — view command usage and return to the menu.
235
219
 
236
- In the tool screen, **Select all** and **Select none** are unambiguous shortcuts; individual rows use
237
- friendly task labels while retaining their raw `chrome_devtools_*` identity in the description.
238
- Toggles remain a command-local draft. **Review changes** previews the exact effect, **Apply tool
239
- changes** saves it, and Cancel, Escape, Ctrl+C, disposal, or session replacement discards an
240
- unconfirmed draft without changing runtime tools or settings. A failed apply restores the previous
241
- availability and loaded-tool state, preserves the settings file, retains the draft for retry, and
242
- reports how to recover.
220
+ 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.
221
+ Toggles remain a command-local draft.
222
+ **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.
223
+ 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.
243
224
 
244
225
  Direct subcommands are also available:
245
226
 
@@ -254,28 +235,22 @@ Direct subcommands are also available:
254
235
  /chrome-devtools disable
255
236
  ```
256
237
 
257
- Compatibility aliases remain available: `toggle` and `select` mean `tools`, `on` means `enable`, and
258
- `off` means `disable`.
238
+ Compatibility aliases remain available: `toggle` and `select` mean `tools`, `on` means `enable`, and `off` means `disable`.
259
239
 
260
240
  - `help` shows command usage.
261
- - `quickstart` shows the configured CDP endpoint, endpoint source, auto-launch mode, browser
262
- candidates, last launch attempt, and launch hints.
263
- - `status` shows available and loaded capability counts, loader state, the persisted catalog,
264
- settings file path, endpoint source, launch mode, last launch attempt, and active non-Chrome tool
265
- count.
241
+ - `quickstart` shows the configured CDP endpoint, endpoint source, auto-launch mode, browser candidates, last launch attempt, and launch hints.
242
+ - `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.
266
243
  - `settings` opens the same immediate-save browser settings flow used by the menu.
267
244
  - `tools` opens the same staged, width-safe availability and review flow used by the menu.
268
245
  - `toggle` and `select` are compatibility aliases for `tools`.
269
- - `enable` makes all five capability tools available to the loader and saves that catalog; `on` is a
270
- compatibility alias.
271
- - `disable` makes all five capability tools unavailable and saves the empty catalog; `off` is a
272
- compatibility alias. The slash command and `chrome_devtools_load` remain available.
246
+ - `enable` makes all five capability tools available and follows the current native-deferred or eager exposure mode; `on` is a compatibility alias.
247
+ - `disable` makes all five capability tools unavailable and saves the empty catalog; `off` is a compatibility alias.
248
+ The slash command and `chrome_devtools_load` remain available.
273
249
 
274
- The menu, `settings`, `tools`, `help`, `quickstart`, and `status` require TUI or RPC mode so their
275
- result is observable. TUI uses keyboard navigation and injected Pi keybindings; RPC receives
276
- equivalent standard dialogs. In print and JSON modes, interactive and informational routes reject
277
- explicitly instead of silently opening unavailable UI. The immediate `enable`/`disable` routes remain
278
- available for deterministic non-interactive use.
250
+ The menu, `settings`, `tools`, `help`, `quickstart`, and `status` require TUI or RPC mode so their result is observable.
251
+ TUI uses keyboard navigation and injected Pi keybindings; RPC receives equivalent standard dialogs.
252
+ In print and JSON modes, interactive and informational routes reject explicitly instead of silently opening unavailable UI.
253
+ The immediate `enable`/`disable` routes remain available for deterministic non-interactive use.
279
254
 
280
255
  ## ⚙️ Settings
281
256
 
@@ -285,25 +260,22 @@ The available capability names are saved to:
285
260
  ${PI_CODING_AGENT_DIR:-~/.pi/agent}/pi-chrome-devtools.json
286
261
  ```
287
262
 
288
- The same file owns `browser.endpoint`, `browser.autoLaunch`, `browser.executablePath`, and
289
- `browser.extensionPaths`. Browser connection fields are machine-owned user settings; trusted project
290
- files may replace only `browser.extensionPaths`. Confirmed menu changes apply before the next browser
291
- connection and close only an extension-owned managed browser. Manual JSON edits and unpacked-extension
292
- changes apply after `/reload` or session replacement.
293
-
294
- When the file is missing or invalid, the extension preserves Pi's current Chrome DevTools
295
- availability policy instead of replacing it. A valid saved catalog is restored on Pi startup and
296
- `/reload`, while its capability definitions remain deferred. A missing file is created by the first
297
- confirmed browser or tool setting. Within one Pi process, all browser and tool saves run in invocation
298
- order, reread the latest valid document, publish by temporary-file rename, and preserve unknown
299
- fields. Malformed JSON or invalid recognized fields make menu mutation unavailable and block direct
300
- saves without replacement; a failed save restores the prior displayed and effective state.
301
-
302
- Compatibility: older versions used `pi-chrome-devtools-settings.json`. A legacy-only file remains
303
- readable with a warning and is never modified automatically; rename it to
304
- `pi-chrome-devtools.json`. The first subsequent settings save writes the canonical file. If both
305
- files exist, `pi-chrome-devtools.json` wins and the legacy file is ignored. The legacy
306
- filename is deprecated and will be removed in a future major release.
263
+ The same file owns `browser.endpoint`, `browser.autoLaunch`, `browser.executablePath`, and `browser.extensionPaths`.
264
+ Browser connection fields are machine-owned user settings; trusted project files may replace only `browser.extensionPaths`.
265
+ Confirmed menu changes apply before the next browser connection and close only an extension-owned managed browser.
266
+ Manual JSON edits and unpacked-extension changes apply after `/reload` or session replacement.
267
+
268
+ When the file is missing or invalid, the extension preserves Pi's current Chrome DevTools availability policy instead of replacing it.
269
+ A valid saved catalog is restored on Pi startup and `/reload`, with capability definitions exposed natively deferred or eagerly according to model/provider support.
270
+ A missing file is created by the first confirmed browser or tool setting.
271
+ Within one Pi process, all browser and tool saves run in invocation order, reread the latest valid document, publish by temporary-file rename, and preserve unknown fields.
272
+ Malformed JSON or invalid recognized fields make menu mutation unavailable and block direct saves without replacement; a failed save restores the prior displayed and effective state.
273
+
274
+ Compatibility: older versions used `pi-chrome-devtools-settings.json`.
275
+ A legacy-only file remains readable with a warning and is never modified automatically; rename it to `pi-chrome-devtools.json`.
276
+ The first subsequent settings save writes the canonical file.
277
+ If both files exist, `pi-chrome-devtools.json` wins and the legacy file is ignored.
278
+ The legacy filename is deprecated and will be removed in a future major release.
307
279
 
308
280
  ## 🧠 Use cases
309
281
 
@@ -331,7 +303,8 @@ packages/pi-chrome-devtools/
331
303
  └── package.json
332
304
  ```
333
305
 
334
- `index.ts` is the Pi entrypoint and forwards to `chrome-devtools.ts`; the other source modules are internal. The package exposes its Pi extension through `package.json`:
306
+ `index.ts` is the Pi entrypoint and forwards to `chrome-devtools.ts`; the other source modules are internal.
307
+ The package exposes its Pi extension through `package.json`:
335
308
 
336
309
  ```json
337
310
  {
@@ -349,4 +322,5 @@ Pi extension, Pi coding agent, Chrome DevTools Protocol, CDP, browser automation
349
322
 
350
323
  ## 📄 License
351
324
 
352
- MIT. See [`LICENSE`](./LICENSE).
325
+ MIT.
326
+ See [`LICENSE`](./LICENSE).
@@ -1,5 +1,5 @@
1
1
  // @generated by scripts/build-runtime.mjs; do not edit.
2
- // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
2
+ // @ts-nocheck -- the generated entry uses a .ts extension for Pi's Jiti loader.
3
3
  import {
4
4
  applyRuntimeBrowserSettings,
5
5
  loadSettings,
@@ -8,7 +8,7 @@ import {
8
8
  saveBrowserSettings,
9
9
  shutdownManagedBrowser,
10
10
  state
11
- } from "./chunk-RM7OGXVL.js";
11
+ } from "./chunk-RH6KESL3.js";
12
12
 
13
13
  // src/browser-settings-menu.ts
14
14
  import { isAbsolute } from "node:path";
@@ -263,4 +263,4 @@ function formatError(error) {
263
263
  export {
264
264
  showChromeDevtoolsBrowserSettings
265
265
  };
266
- //# sourceMappingURL=browser-settings-menu-L4CZINWO.ts.map
266
+ //# sourceMappingURL=browser-settings-menu-VWCNG4HB.js.map
@@ -1,5 +1,5 @@
1
1
  // @generated by scripts/build-runtime.mjs; do not edit.
2
- // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
2
+ // @ts-nocheck -- the generated entry uses a .ts extension for Pi's Jiti loader.
3
3
 
4
4
  // src/tool-names.ts
5
5
  var CHROME_DEVTOOLS_TOOL_NAMES = [
@@ -1250,7 +1250,9 @@ var DevToolsEndpointError = class extends Error {
1250
1250
  };
1251
1251
 
1252
1252
  // src/lazy-tools.ts
1253
- import { defineTool } from "@earendil-works/pi-coding-agent";
1253
+ import {
1254
+ defineTool
1255
+ } from "@earendil-works/pi-coding-agent";
1254
1256
  import { Type } from "typebox";
1255
1257
  var CHROME_DEVTOOLS_LOAD_TOOL_NAME = "chrome_devtools_load";
1256
1258
  var AVAILABLE_TOOLS_STORE = /* @__PURE__ */ Symbol.for("@narumitw/pi-chrome-devtools.available-tools-store");
@@ -1258,6 +1260,7 @@ var sharedGlobal = globalThis;
1258
1260
  var existingAvailableToolsStore = sharedGlobal[AVAILABLE_TOOLS_STORE];
1259
1261
  var availableToolsByApi = existingAvailableToolsStore ?? /* @__PURE__ */ new WeakMap();
1260
1262
  if (!existingAvailableToolsStore) sharedGlobal[AVAILABLE_TOOLS_STORE] = availableToolsByApi;
1263
+ var lazyExposureByApi = /* @__PURE__ */ new WeakMap();
1261
1264
  var SEARCH_TEXT = {
1262
1265
  chrome_devtools_list_pages: "list open inspectable chrome browser pages tabs targets",
1263
1266
  chrome_devtools_select_page: "select choose active chrome browser page tab target",
@@ -1273,17 +1276,53 @@ function initializeAvailableChromeDevtoolsTools(pi) {
1273
1276
  CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => activeTools.has(name))
1274
1277
  );
1275
1278
  }
1276
- function configureLazyChromeDevtoolsTools(pi, availableTools) {
1277
- setAvailableTools(pi, availableTools);
1279
+ function configureChromeDevtoolsToolExposure(pi, availableTools, model) {
1280
+ const available = setAvailableTools(pi, availableTools);
1281
+ const lazyExposure = supportsNativeDeferredToolLoading(model);
1282
+ lazyExposureByApi.set(pi, lazyExposure);
1283
+ const exposedTools = lazyExposure ? [] : CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
1278
1284
  const nonCapabilityTools = pi.getActiveTools().filter((name) => !CHROME_DEVTOOLS_TOOL_NAMES.includes(name));
1279
- pi.setActiveTools(unique([...nonCapabilityTools, CHROME_DEVTOOLS_LOAD_TOOL_NAME]));
1285
+ pi.setActiveTools(
1286
+ unique([...nonCapabilityTools, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...exposedTools])
1287
+ );
1288
+ }
1289
+ function requireEagerChromeDevtoolsToolExposure(pi) {
1290
+ lazyExposureByApi.set(pi, false);
1291
+ const active = pi.getActiveTools();
1292
+ const available = availableChromeDevtoolsTools(pi);
1293
+ pi.setActiveTools(unique([...active, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...available]));
1280
1294
  }
1281
1295
  function applyAvailableChromeDevtoolsTools(pi, availableTools) {
1282
1296
  const available = setAvailableTools(pi, availableTools);
1297
+ const lazyExposure = lazyExposureByApi.get(pi) === true;
1283
1298
  const active = pi.getActiveTools().filter(
1284
- (name) => !CHROME_DEVTOOLS_TOOL_NAMES.includes(name) || available.has(name)
1299
+ (name) => !CHROME_DEVTOOLS_TOOL_NAMES.includes(name) || lazyExposure && available.has(name)
1285
1300
  );
1286
- pi.setActiveTools(unique([...active, CHROME_DEVTOOLS_LOAD_TOOL_NAME]));
1301
+ const eagerTools = lazyExposure ? [] : CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
1302
+ pi.setActiveTools(unique([...active, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...eagerTools]));
1303
+ }
1304
+ function chromeDevtoolsToolExposureMode(pi) {
1305
+ return lazyExposureByApi.get(pi) === true ? "native deferred" : "eager";
1306
+ }
1307
+ function supportsNativeDeferredToolLoading(model) {
1308
+ if (!model) return false;
1309
+ if (model.api === "anthropic-messages") {
1310
+ const configured = compatBoolean(model.compat, "supportsToolReferences");
1311
+ if (configured !== void 0) return configured;
1312
+ if (model.provider !== "anthropic" || model.id.includes("haiku")) return false;
1313
+ const version = model.id.match(/^claude-(?:opus|sonnet|fable)-(\d+)(?:-(\d+))?(?:-|$)/);
1314
+ if (!version) return false;
1315
+ const major = Number(version[1]);
1316
+ const minor = version[2] && version[2].length < 8 ? Number(version[2]) : 0;
1317
+ return major > 4 || major === 4 && minor >= 5;
1318
+ }
1319
+ if (model.api === "openai-completions") {
1320
+ return compatString(model.compat, "deferredToolsMode") === "kimi";
1321
+ }
1322
+ if (model.api === "openai-responses" || model.api === "openai-codex-responses") {
1323
+ return compatBoolean(model.compat, "supportsAdditionalTools") === true || compatBoolean(model.compat, "supportsToolSearch") === true;
1324
+ }
1325
+ return false;
1287
1326
  }
1288
1327
  function availableChromeDevtoolsTools(pi) {
1289
1328
  const available = availableToolsByApi.get(pi) ?? /* @__PURE__ */ new Set();
@@ -1343,6 +1382,16 @@ function setAvailableTools(pi, availableTools) {
1343
1382
  availableToolsByApi.set(pi, available);
1344
1383
  return available;
1345
1384
  }
1385
+ function compatBoolean(value, key) {
1386
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
1387
+ const record = value;
1388
+ return typeof record[key] === "boolean" ? record[key] : void 0;
1389
+ }
1390
+ function compatString(value, key) {
1391
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
1392
+ const record = value;
1393
+ return typeof record[key] === "string" ? record[key] : void 0;
1394
+ }
1346
1395
  function unique(values) {
1347
1396
  return [...new Set(values)];
1348
1397
  }
@@ -1361,7 +1410,7 @@ async function updateChromeDevtoolsTools(pi, ctx, selectedTools, action) {
1361
1410
  if (result !== "saved" || generation !== state.sessionGeneration) return;
1362
1411
  const status = await buildToolStatusMessage(pi);
1363
1412
  if (generation !== state.sessionGeneration) return;
1364
- ctx.ui.notify(`Chrome DevTools lazy catalog ${action}.
1413
+ ctx.ui.notify(`Chrome DevTools tool catalog ${action}.
1365
1414
 
1366
1415
  ${status}`, "info");
1367
1416
  }
@@ -1411,7 +1460,8 @@ async function transactSelectedToolsNow(pi, ctx, selectedTools, expectedGenerati
1411
1460
  const previousLoadedChromeTools = previousActiveTools.filter(
1412
1461
  (name) => CHROME_DEVTOOLS_TOOL_NAMES.includes(name)
1413
1462
  );
1414
- pi.setActiveTools(unique2([...currentNonChromeTools, ...previousLoadedChromeTools]));
1463
+ const restoredChromeTools = chromeDevtoolsToolExposureMode(pi) === "eager" ? previousAvailableTools : previousLoadedChromeTools;
1464
+ pi.setActiveTools(unique2([...currentNonChromeTools, ...restoredChromeTools]));
1415
1465
  } catch (caught) {
1416
1466
  rollbackError = caught;
1417
1467
  }
@@ -1454,10 +1504,11 @@ async function buildToolStatusMessage(pi) {
1454
1504
  const persistedSetting = await persistedSettingLabel();
1455
1505
  return sanitizeChromeDevtoolsDisplay(
1456
1506
  [
1457
- `Chrome DevTools tools available to lazy-load: ${formatRuntimeStatus(summary)}`,
1507
+ `Chrome DevTools tools available: ${formatRuntimeStatus(summary)}`,
1508
+ `Tool exposure: ${chromeDevtoolsToolExposureMode(pi)}`,
1458
1509
  `Loaded capability tools this session: ${summary.loadedChromeToolCount}/${CHROME_DEVTOOLS_TOOL_NAMES.length}`,
1459
1510
  `Loader: ${pi.getActiveTools().includes(CHROME_DEVTOOLS_LOAD_TOOL_NAME) ? "active" : "inactive"}`,
1460
- `Persisted lazy catalog: ${persistedSetting}`,
1511
+ `Persisted tool catalog: ${persistedSetting}`,
1461
1512
  ...browserSettingsStatusLines(),
1462
1513
  ...state.settingsNotice ? [`Settings note: ${state.settingsNotice}`] : [],
1463
1514
  `Other active tools preserved: ${summary.activeNonChromeToolCount}`,
@@ -1537,9 +1588,9 @@ function buildCommandGuide() {
1537
1588
  "/chrome-devtools quickstart \u2014 show endpoint and launch help",
1538
1589
  "/chrome-devtools status \u2014 show tool and settings status",
1539
1590
  "/chrome-devtools settings \u2014 edit browser connection settings",
1540
- "/chrome-devtools tools \u2014 choose tools available to lazy-load",
1591
+ "/chrome-devtools tools \u2014 choose available Chrome DevTools tools",
1541
1592
  "/chrome-devtools toggle|select \u2014 compatibility aliases for tools",
1542
- "/chrome-devtools enable|on \u2014 make all Chrome DevTools tools available to lazy-load",
1593
+ "/chrome-devtools enable|on \u2014 make all Chrome DevTools tools available",
1543
1594
  "/chrome-devtools disable|off \u2014 make all Chrome DevTools capability tools unavailable"
1544
1595
  ].join("\n");
1545
1596
  }
@@ -1591,7 +1642,9 @@ export {
1591
1642
  launchHint,
1592
1643
  formatPageListItem,
1593
1644
  initializeAvailableChromeDevtoolsTools,
1594
- configureLazyChromeDevtoolsTools,
1645
+ configureChromeDevtoolsToolExposure,
1646
+ requireEagerChromeDevtoolsToolExposure,
1647
+ supportsNativeDeferredToolLoading,
1595
1648
  availableChromeDevtoolsTools,
1596
1649
  createChromeDevtoolsLoadTool,
1597
1650
  updateChromeDevtoolsTools,
@@ -1604,4 +1657,4 @@ export {
1604
1657
  buildCommandGuide,
1605
1658
  allChromeDevtoolsTools
1606
1659
  };
1607
- //# sourceMappingURL=chunk-RM7OGXVL.ts.map
1660
+ //# sourceMappingURL=chunk-RH6KESL3.js.map