@narumitw/pi-chrome-devtools 0.53.3 → 0.54.0

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
@@ -12,7 +12,7 @@ The design is inspired by [`chrome-devtools-mcp`](https://github.com/ChromeDevTo
12
12
  - Reuses an existing CDP endpoint or launches an isolated Chromium-family browser on first use.
13
13
  - Recovers from stale page selections and explains browser startup or endpoint failures.
14
14
  - Loads explicitly approved unpacked extensions only in an extension-owned Chrome for Testing or Chromium process.
15
- - Uses native deferred browser tools when supported and exposes allowed tools eagerly otherwise.
15
+ - Defaults to codemode-discoverable browser capabilities, with optional lazy and direct exposure.
16
16
  - Provides availability controls, setup guidance, status, and help through `/chrome-devtools`.
17
17
  - Shows compact expandable results and activity only while browser tools are running.
18
18
  - Persists reviewed tool availability while keeping browser connection settings machine-owned.
@@ -46,7 +46,15 @@ Review third-party extension source before installing it.
46
46
 
47
47
  ## 🚀 Quick start
48
48
 
49
- Start Pi and ask the agent to load the browser capability needed for the task.
49
+ If `codemode` is already in your active tool list, no additional Pi setting is needed. Startup warns when enabled capabilities require codemode but the host tool is inactive; empty catalogs and fully explicit host selections do not produce this warning.
50
+ Otherwise, enable Pi's built-in codemode in Pi's `settings.json`:
51
+
52
+ ```json
53
+ { "defaultTools": ["+codemode"] }
54
+ ```
55
+
56
+ Start Pi and ask the agent to inspect a browser page. Codemode can discover `chrome_devtools_*` with `searchTools`, inspect schemas with `describeTool`, and call them through `tools`.
57
+ A CLI `--tools` list is an allowlist for extension tools too; prefer the additive `defaultTools` setting above.
50
58
  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
59
  Run `/chrome-devtools` to review status, settings, help, and available tools.
52
60
  WebMCP remains disabled until you explicitly enable it.
@@ -77,7 +85,7 @@ See [WebMCP setup and troubleshooting](./docs/browser-setup.md#experimental-webm
77
85
 
78
86
  ## 🛠️ Tools
79
87
 
80
- - `chrome_devtools_load` — find and load browser capabilities relevant to a task.
88
+ - `chrome_devtools_load` — find and load browser capabilities relevant to a task (lazy mode only).
81
89
  - `chrome_devtools_list_pages` — list inspectable Chrome tabs/pages.
82
90
  - `chrome_devtools_select_page` — select the active page for later tool calls.
83
91
  - `chrome_devtools_navigate` — navigate a page to a URL; if no page exists, create one first.
@@ -88,8 +96,12 @@ See [WebMCP setup and troubleshooting](./docs/browser-setup.md#experimental-webm
88
96
 
89
97
  ### Tool exposure
90
98
 
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.
99
+ The default `codemode` mode registers five stable capabilities and two fixed experimental WebMCP gateways without a loader. Enabled capabilities are callable through codemode discovery without adding their definitions to the ordinary model request. Explicit host activation of an enabled capability is preserved.
100
+ Disabled capabilities are hidden from discovery and calls in every mode, including the WebMCP gateways while their gate is off.
101
+
102
+ Choose `lazy` or `direct` under **Browser settings → Tool mode** to use another exposure policy; changes require `/reload` or session replacement. Direct mode declares all enabled capabilities without a loader.
103
+
104
+ In lazy mode, with native deferred-tool support, only `chrome_devtools_load` starts active.
93
105
  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.
94
106
  Loaded capability tools remain active for the rest of the session unless the user makes them unavailable through `/chrome-devtools`.
95
107
 
@@ -105,7 +117,7 @@ The capability tools omit active-only prompt snippets so native deferred loading
105
117
  The saved `tools` array controls which capabilities the extension may expose.
106
118
  The `webmcp.enabled` gate takes precedence, so persisted WebMCP names cannot bypass a disabled gate.
107
119
  Page-provided tool definitions appear only in list results and never alter Pi's provider-visible tool definitions.
108
- An empty array leaves the loader active but makes every browser capability unavailable.
120
+ An empty array makes every browser capability unavailable; only lazy mode retains the loader.
109
121
 
110
122
  ### Screenshot files
111
123
 
@@ -141,14 +153,14 @@ If the model cannot inspect the inline image, ask it to read the saved path, for
141
153
  | `/chrome-devtools help` | Show command usage. |
142
154
  | `/chrome-devtools quickstart` | Show the CDP endpoint, launch candidates, and setup hints. |
143
155
  | `/chrome-devtools status` | Inspect tools, settings sources, and the last browser launch without probing or starting Chrome. |
144
- | `/chrome-devtools settings` | Change browser settings; successful edits save immediately. |
156
+ | `/chrome-devtools settings` | Change browser settings and tool mode; successful edits save immediately. |
145
157
  | `/chrome-devtools tools` (aliases: `toggle`, `select`) | Stage tool availability, review the result, and apply it. |
146
158
  | `/chrome-devtools enable` (alias: `on`) | Immediately make all currently gated capabilities available and save the selection. |
147
159
  | `/chrome-devtools disable` (alias: `off`) | Immediately make all capabilities unavailable and save the empty selection. |
148
160
 
149
161
  All routes support TUI and RPC and reject unknown or trailing arguments.
150
162
  Only `enable` and `disable` also support print and JSON modes.
151
- Disabling capabilities leaves the slash command and `chrome_devtools_load` available; see [Tool exposure](#tool-exposure).
163
+ Disabling capabilities leaves the slash command available and retains `chrome_devtools_load` only in lazy mode; see [Tool exposure](#tool-exposure).
152
164
 
153
165
  Menu tool changes require **Apply tool changes**; cancellation discards the unconfirmed draft.
154
166
  Failed apply leaves previous tool availability and settings intact and retains the draft for retry.
@@ -180,11 +192,16 @@ Browser connection fields and `webmcp.enabled` are machine-owned user settings;
180
192
  Confirmed menu changes apply before the next browser connection and close only an extension-owned managed browser.
181
193
  Manual JSON edits and unpacked-extension changes apply after `/reload` or session replacement.
182
194
 
183
- When the file is missing or invalid, the extension preserves Pi's current Chrome DevTools availability policy instead of replacing it.
184
- A valid saved catalog is restored on Pi startup and `/reload`, with capability definitions exposed natively deferred or eagerly according to model/provider support.
195
+ `toolMode` accepts `codemode` (default), `lazy`, or `direct` and is user-only. It applies at session start, including `/reload`; availability edits apply immediately without overwriting a pending mode change.
196
+ Missing settings and valid older files without `toolMode` now use codemode. To restore the previous loader behavior, save `"toolMode": "lazy"` and reload. Without an explicit catalog, all modes make the stable capabilities available; the active declaration list does not determine configured availability.
197
+ A first `/reload` from the previous loader implementation clears carried-over Chrome declarations when applying codemode; enabled capabilities remain callable through codemode. Pi exposes no activation-origin API, so an active loader plus Chrome capabilities without known session provenance is treated as the predecessor's cohort. Capability-only host selections and known explicit activations are preserved. This is an intentional reload-time model-visible prefix transition, not an ordinary-turn change.
198
+ Activation provenance is stored as versioned, non-model session metadata and restored from the current branch on session start; a branch without valid provenance drops abandoned-branch caches, so transcript-restoring resume/fork paths can apply codemode without carrying extension-owned declarations forward. Recordless sessions retain the legacy loader-cohort migration policy; unknown capability-only selections remain conservative host selections. Observed host deactivation clears an explicit selection, while extension-caused hiding is not treated as host withdrawal. Re-enabling availability restores retained explicit declarations only when the capability and its WebMCP gate allow it.
199
+ A successful lazy activation remains additive if its ownership metadata cannot be saved: the loader reports the loaded tools with a warning rather than removing them or reporting a failed load. Startup and model-switch exposure also remain applied when only metadata persistence fails; lifecycle handlers warn and continue instead of reporting a failed setup. A later loader call or lifecycle update retries pending metadata, including when matching tools are already loaded or the same policy is restored on reload; durable ownership may be incomplete until persistence succeeds. Availability and WebMCP settings saves retain their transactional failure recovery.
200
+ A valid saved catalog is restored on Pi startup and `/reload`. An invalid user settings file retains the known effective availability and running mode with a warning, even when valid trusted-project browser settings make the combined load succeed; without a recoverable catalog, only already active capabilities are retained and no new capability is enabled. A fresh invalid configuration therefore leaves browser capabilities disabled until the file is repaired. Invalid settings cannot be overwritten by a save.
185
201
  A missing file is created by the first confirmed browser or tool setting.
186
202
  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.
187
- 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.
203
+ Malformed JSON or invalid recognized fields make menu mutation unavailable and block direct saves without replacement.
204
+ Availability saves precede runtime publication. A failed disk write leaves runtime tools unchanged, including host edits made during the save. If runtime publication fails after saving, recovery restores the previous runtime policy and saved catalog before releasing dependent reads or session replacement. Recovery changes only the tool catalog fields, preserves unrelated settings, and refuses to overwrite invalid or newer catalog data; any recovery failure is reported explicitly.
188
205
 
189
206
  Compatibility: older versions used `pi-chrome-devtools-settings.json`.
190
207
  A legacy-only file remains readable with a warning and is never modified automatically; rename it to `pi-chrome-devtools.json`.
@@ -5,20 +5,22 @@ import {
5
5
  configuredChromeDevtoolsTools,
6
6
  sanitizeChromeDevtoolsDisplay,
7
7
  setChromeDevtoolsSessionOwner
8
- } from "./chunk-MZCGM6D3.js";
8
+ } from "./chunk-WOFI2A72.ts";
9
9
  import {
10
10
  applyRuntimeBrowserSettings,
11
11
  applyRuntimeWebMcpSetting,
12
12
  invalidateWebMcpOperations,
13
+ isChromeDevtoolsToolMode,
13
14
  loadSettings,
14
15
  parseBrowserEndpoint,
15
16
  saveBrowserSettings,
17
+ saveToolMode,
16
18
  saveWebMcpSettings,
17
19
  shutdownManagedBrowser,
18
20
  state,
19
21
  syncManagedBrowserSettings,
20
22
  webMcpEnabled
21
- } from "./chunk-F5VG35UI.js";
23
+ } from "./chunk-TVVYQ5KM.ts";
22
24
 
23
25
  // src/browser-settings-menu.ts
24
26
  import { isAbsolute } from "node:path";
@@ -75,6 +77,14 @@ async function showChromeDevtoolsBrowserSettings(pi, ctx, generation) {
75
77
  description: "Inspect trusted extension paths configured in user or project JSON.",
76
78
  currentValue: `${browser.extensionPaths.length} configured`,
77
79
  action: "show-extensions"
80
+ },
81
+ {
82
+ id: "tool-mode",
83
+ label: "Tool mode",
84
+ description: "codemode discovery, lazy loading, or direct declarations. Saved changes require /reload.",
85
+ currentValue: current.load.kind === "loaded" ? current.load.settings.toolMode : "codemode",
86
+ values: ["codemode", "lazy", "direct"],
87
+ action: "set-tool-mode"
78
88
  }
79
89
  ]
80
90
  };
@@ -111,6 +121,18 @@ async function showChromeDevtoolsBrowserSettings(pi, ctx, generation) {
111
121
  })
112
122
  },
113
123
  actions: {
124
+ "set-tool-mode": async ({ value }) => {
125
+ if (!isChromeDevtoolsToolMode(value) || !isCurrent2()) return { kind: "rejected" };
126
+ try {
127
+ await saveToolMode(value);
128
+ if (!isCurrent2()) return { kind: "rejected" };
129
+ ctx.ui.notify("Chrome DevTools tool mode saved; /reload required. Running exposure is unchanged.", "info");
130
+ return { kind: "stay" };
131
+ } catch (error) {
132
+ if (isCurrent2()) notifySaveFailure(ctx, error);
133
+ return { kind: "rejected" };
134
+ }
135
+ },
114
136
  "open-endpoint": () => ({ kind: "to", screen: "endpoint" }),
115
137
  "save-endpoint": async ({ value, signal }) => {
116
138
  if (value === void 0) return { kind: "rejected" };
@@ -313,4 +335,4 @@ function formatError(error) {
313
335
  export {
314
336
  showChromeDevtoolsBrowserSettings
315
337
  };
316
- //# sourceMappingURL=browser-settings-menu-SR7KLZT6.js.map
338
+ //# sourceMappingURL=browser-settings-menu-GQAXLWZ7.ts.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 applyAvailableChromeDevtoolsTools,\n configuredChromeDevtoolsTools,\n setChromeDevtoolsSessionOwner,\n} from \"./lazy-tools.js\";\nimport {\n applyRuntimeBrowserSettings,\n applyRuntimeWebMcpSetting,\n invalidateWebMcpOperations,\n state,\n webMcpEnabled,\n} from \"./runtime.js\";\nimport {\n type BrowserSettingsPatch,\n type BrowserSettingsSource,\n isChromeDevtoolsToolMode,\n loadSettings,\n parseBrowserEndpoint,\n type SettingsLoadResult,\n saveBrowserSettings,\n saveToolMode,\n saveWebMcpSettings,\n} from \"./settings.js\";\nimport { sanitizeChromeDevtoolsDisplay } from \"./tool-selector.js\";\n\ntype BrowserSettingsScreen = \"settings\" | \"endpoint\" | \"executable\" | \"extensions\";\ntype BrowserSettingsAction =\n | \"set-tool-mode\"\n | \"open-endpoint\"\n | \"save-endpoint\"\n | \"set-auto-launch\"\n | \"set-webmcp\"\n | \"open-executable\"\n | \"save-executable\"\n | \"show-extensions\";\n\ninterface BrowserSettingsMenuState {\n load: SettingsLoadResult;\n}\n\ntype CommandContext = ExtensionCommandContext;\n\nexport async function showChromeDevtoolsBrowserSettings(\n pi: ExtensionAPI,\n ctx: CommandContext,\n generation: number,\n): Promise<{ closeParent: boolean }> {\n setChromeDevtoolsSessionOwner(pi, ctx.sessionManager);\n const ownerSignal = state.sessionController.signal;\n const isCurrent = () => generation === state.sessionGeneration && !ownerSignal.aborted;\n const projectTrusted = ctx.isProjectTrusted();\n const menu = defineMenu<BrowserSettingsMenuState, BrowserSettingsScreen, BrowserSettingsAction>({\n start: \"settings\",\n screens: {\n settings: ({ state: current }) => {\n if (current.load.userFile.kind === \"invalid\") return invalidSettingsScreen(current.load);\n const browser = current.load.effectiveBrowser;\n return {\n kind: \"settings\",\n title: \"Chrome DevTools Browser settings\",\n lines: settingsContextLines(current.load),\n items: [\n {\n id: \"endpoint\",\n label: \"DevTools endpoint\",\n description: 'HTTP origin with an explicit port; enter \"default\" to restore 127.0.0.1:9222.',\n currentValue: sanitizeChromeDevtoolsDisplay(browser.endpoint),\n action: \"open-endpoint\",\n },\n {\n id: \"auto-launch\",\n label: \"Auto-launch\",\n description: \"Launch an isolated managed browser when a local endpoint is unavailable.\",\n currentValue: browser.autoLaunchEnabled ? \"On\" : \"Off\",\n values: [\"On\", \"Off\"],\n action: \"set-auto-launch\",\n },\n {\n id: \"executable\",\n label: \"Browser executable\",\n description: 'Absolute executable path; enter \"automatic\" to restore browser discovery.',\n currentValue: sanitizeChromeDevtoolsDisplay(browser.executablePath ?? \"Automatic\"),\n action: \"open-executable\",\n },\n {\n id: \"webmcp\",\n label: \"WebMCP \u00B7 Experimental\",\n description: \"Allow user-confirmed calls to page-provided WebMCP tools in this browser session.\",\n currentValue: current.load.effectiveWebMcpEnabled ? \"On\" : \"Off\",\n values: [\"On\", \"Off\"],\n action: \"set-webmcp\",\n },\n {\n id: \"extensions\",\n label: \"Unpacked extensions\",\n description: \"Inspect trusted extension paths configured in user or project JSON.\",\n currentValue: `${browser.extensionPaths.length} configured`,\n action: \"show-extensions\",\n },\n {\n id: \"tool-mode\",\n label: \"Tool mode\",\n description: \"codemode discovery, lazy loading, or direct declarations. Saved changes require /reload.\",\n currentValue: current.load.kind === \"loaded\" ? current.load.settings.toolMode : \"codemode\",\n values: [\"codemode\", \"lazy\", \"direct\"],\n action: \"set-tool-mode\",\n },\n ],\n };\n },\n endpoint: ({ state: current }) => ({\n kind: \"input\",\n title: \"DevTools endpoint\",\n lines: [\n sanitizeChromeDevtoolsDisplay(`Current effective endpoint: ${current.load.effectiveBrowser.endpoint}`),\n 'Enter an HTTP origin with an explicit port, or \"default\".',\n ],\n placeholder: \"http://127.0.0.1:9222\",\n action: \"save-endpoint\",\n hint: \"back\",\n }),\n executable: ({ state: current }) => ({\n kind: \"input\",\n title: \"Browser executable\",\n lines: [\n sanitizeChromeDevtoolsDisplay(\n `Current effective executable: ${current.load.effectiveBrowser.executablePath ?? \"automatic discovery\"}`,\n ),\n 'Enter an absolute path, or \"automatic\".',\n ],\n placeholder: \"absolute browser executable path\",\n action: \"save-executable\",\n hint: \"back\",\n }),\n extensions: ({ state: current }) => ({\n kind: \"detail\",\n title: \"Unpacked extensions\",\n lines: extensionDetailLines(current.load),\n hint: \"back\",\n }),\n },\n actions: {\n \"set-tool-mode\": async ({ value }) => {\n if (!isChromeDevtoolsToolMode(value) || !isCurrent()) return { kind: \"rejected\" };\n try {\n await saveToolMode(value);\n if (!isCurrent()) return { kind: \"rejected\" };\n ctx.ui.notify(\"Chrome DevTools tool mode saved; /reload required. Running exposure is unchanged.\", \"info\");\n return { kind: \"stay\" };\n } catch (error) {\n if (isCurrent()) notifySaveFailure(ctx, error);\n return { kind: \"rejected\" };\n }\n },\n \"open-endpoint\": () => ({ kind: \"to\", screen: \"endpoint\" }),\n \"save-endpoint\": async ({ value, signal }) => {\n if (value === undefined) return { kind: \"rejected\" };\n let endpoint: string | null;\n try {\n const trimmed = value.trim();\n endpoint = trimmed.toLowerCase() === \"default\" ? null : parseBrowserEndpoint(trimmed).endpoint;\n } catch (error) {\n notifySaveFailure(ctx, error);\n return { kind: \"rejected\" };\n }\n const saved = await saveAndApplyBrowserPatch(ctx, generation, ownerSignal, signal, projectTrusted, {\n endpoint,\n });\n if (!saved) return { kind: \"rejected\" };\n notifyEffectiveSave(ctx, \"endpoint\", saved.effectiveBrowser.endpoint, saved.effectiveBrowser.endpointSource);\n return { kind: \"back\" };\n },\n \"set-auto-launch\": async ({ value, signal }) => {\n if (value !== \"On\" && value !== \"Off\") return { kind: \"rejected\" };\n const saved = await saveAndApplyBrowserPatch(ctx, generation, ownerSignal, signal, projectTrusted, {\n autoLaunch: value === \"On\",\n });\n if (!saved) return { kind: \"rejected\" };\n notifyEffectiveSave(\n ctx,\n \"auto-launch\",\n saved.effectiveBrowser.autoLaunchEnabled ? \"On\" : \"Off\",\n saved.effectiveBrowser.autoLaunchSource,\n );\n return { kind: \"stay\" };\n },\n \"set-webmcp\": async ({ value, signal }) => {\n if (value !== \"On\" && value !== \"Off\") return { kind: \"rejected\" };\n const saved = await saveAndApplyWebMcpSetting(\n pi,\n ctx,\n generation,\n ownerSignal,\n signal,\n projectTrusted,\n value === \"On\",\n );\n if (!saved) return { kind: \"rejected\" };\n ctx.ui.notify(\n saved.effectiveWebMcpEnabled\n ? \"Experimental WebMCP enabled. Every page-provided tool call requires confirmation. Choose gateway availability under Browser tools.\"\n : \"Experimental WebMCP disabled. Active WebMCP work was aborted and both gateway tools are unavailable.\",\n \"warning\",\n );\n return { kind: \"stay\" };\n },\n \"open-executable\": () => ({ kind: \"to\", screen: \"executable\" }),\n \"save-executable\": async ({ value, signal }) => {\n if (value === undefined) return { kind: \"rejected\" };\n const trimmed = value.trim();\n const reset = [\"automatic\", \"default\", \"none\"].includes(trimmed.toLowerCase());\n if (!reset && (!trimmed || !isAbsolute(trimmed))) {\n notifySaveFailure(ctx, new Error('Enter an absolute path or \"automatic\".'));\n return { kind: \"rejected\" };\n }\n const executablePath = reset ? null : trimmed;\n const saved = await saveAndApplyBrowserPatch(ctx, generation, ownerSignal, signal, projectTrusted, {\n executablePath,\n });\n if (!saved) return { kind: \"rejected\" };\n notifyEffectiveSave(\n ctx,\n \"browser executable\",\n saved.effectiveBrowser.executablePath ?? \"automatic discovery\",\n saved.effectiveBrowser.executablePathSource,\n );\n return { kind: \"back\" };\n },\n \"show-extensions\": () => ({ kind: \"to\", screen: \"extensions\" }),\n },\n });\n const result = await runMenu(ctx, menu, {\n getState: async ({ signal }) => {\n signal.throwIfAborted();\n const load = await loadSettings({ cwd: ctx.cwd, projectTrusted });\n signal.throwIfAborted();\n return { load };\n },\n signal: ownerSignal,\n isCurrent,\n onError: (currentCtx, error) =>\n currentCtx.ui.notify(\n sanitizeChromeDevtoolsDisplay(`Chrome DevTools browser settings failed: ${formatError(error)}`),\n \"error\",\n ),\n });\n return { closeParent: result.kind === \"closed\" && result.reason === \"close\" };\n}\n\nasync function saveAndApplyBrowserPatch(\n ctx: CommandContext,\n generation: number,\n ownerSignal: AbortSignal,\n actionSignal: AbortSignal,\n projectTrusted: boolean,\n patch: BrowserSettingsPatch,\n) {\n try {\n await ctx.waitForIdle();\n if (!isCurrent(generation, ownerSignal, actionSignal)) return false;\n await saveBrowserSettings(patch);\n if (!isCurrent(generation, ownerSignal, actionSignal)) return false;\n invalidateWebMcpOperations(ctx.sessionManager, \"Chrome DevTools browser configuration changed\");\n await shutdownManagedBrowser(undefined, { owner: ctx.sessionManager });\n if (!isCurrent(generation, ownerSignal, actionSignal)) return false;\n const loaded = await loadSettings({ cwd: ctx.cwd, projectTrusted });\n if (!isCurrent(generation, ownerSignal, actionSignal)) return false;\n applyRuntimeBrowserSettings(loaded.effectiveBrowser, loaded.paths, projectTrusted);\n syncManagedBrowserSettings(ctx.sessionManager, loaded.effectiveBrowser);\n state.settingsNotice = loaded.notice;\n return loaded;\n } catch (error) {\n if (isCurrent(generation, ownerSignal, actionSignal)) notifySaveFailure(ctx, error);\n return false;\n }\n}\n\nasync function saveAndApplyWebMcpSetting(\n pi: ExtensionAPI,\n ctx: CommandContext,\n generation: number,\n ownerSignal: AbortSignal,\n actionSignal: AbortSignal,\n projectTrusted: boolean,\n enabled: boolean,\n) {\n const previousEnabled = webMcpEnabled(ctx.sessionManager);\n const previousTools = configuredChromeDevtoolsTools(pi);\n try {\n await ctx.waitForIdle();\n if (!isCurrent(generation, ownerSignal, actionSignal)) return false;\n applyRuntimeWebMcpSetting(enabled, ctx.sessionManager);\n applyAvailableChromeDevtoolsTools(pi, previousTools);\n await saveWebMcpSettings(enabled);\n if (!isOwnerCurrent(generation, ownerSignal)) return false;\n const loaded = await loadSettings({ cwd: ctx.cwd, projectTrusted });\n if (!isOwnerCurrent(generation, ownerSignal)) return false;\n state.settingsNotice = loaded.notice;\n return loaded;\n } catch (error) {\n if (!isOwnerCurrent(generation, ownerSignal)) return false;\n let rollbackError: unknown;\n try {\n applyRuntimeWebMcpSetting(previousEnabled, ctx.sessionManager);\n applyAvailableChromeDevtoolsTools(pi, previousTools);\n } catch (caught) {\n rollbackError = caught;\n }\n if (isCurrent(generation, ownerSignal, actionSignal)) {\n notifySaveFailure(\n ctx,\n rollbackError\n ? new Error(`${formatError(error)}; WebMCP rollback failed: ${formatError(rollbackError)}`)\n : error,\n );\n }\n return false;\n }\n}\n\nfunction isOwnerCurrent(generation: number, ownerSignal: AbortSignal) {\n return generation === state.sessionGeneration && !ownerSignal.aborted;\n}\n\nfunction isCurrent(generation: number, ownerSignal: AbortSignal, actionSignal: AbortSignal) {\n return isOwnerCurrent(generation, ownerSignal) && !actionSignal.aborted;\n}\n\nfunction invalidSettingsScreen(load: SettingsLoadResult) {\n const reason = load.userFile.kind === \"invalid\" ? load.userFile.reason : \"The user settings file is unavailable.\";\n return {\n kind: \"detail\" as const,\n title: \"Chrome DevTools Browser settings \u00B7 Read only\",\n lines: [\n sanitizeChromeDevtoolsDisplay(\n `Fix the active user settings before saving; the file will not be overwritten. ${reason}`,\n ),\n ],\n hint: \"back\" as const,\n };\n}\n\nfunction settingsContextLines(load: SettingsLoadResult) {\n const browser = load.effectiveBrowser;\n return [\n `User settings \u00B7 ${load.paths.user}`,\n `Effective sources: endpoint ${browser.endpointSource} \u00B7 auto-launch ${browser.autoLaunchSource} \u00B7 executable ${browser.executablePathSource}`,\n ...(load.notice ? [`Warning: ${load.notice}`] : []),\n ].map((line) => sanitizeChromeDevtoolsDisplay(line));\n}\n\nfunction extensionDetailLines(load: SettingsLoadResult) {\n const browser = load.effectiveBrowser;\n return [\n `Effective source: ${browser.extensionPathsSource}`,\n ...(browser.extensionPaths.length > 0\n ? browser.extensionPaths.map((extensionPath) => `- ${extensionPath}`)\n : [\"No unpacked extensions are configured.\"]),\n \"Edit browser.extensionPaths in the user JSON or a trusted project JSON, then run /reload.\",\n \"Unpacked extensions execute privileged browser code; load only paths you trust.\",\n ].map((line) => sanitizeChromeDevtoolsDisplay(line));\n}\n\nfunction notifyEffectiveSave(\n ctx: CommandContext,\n setting: string,\n effectiveValue: string,\n source: BrowserSettingsSource,\n) {\n const overrideNotice = source === \"environment\" ? \" The deprecated environment override remains effective.\" : \"\";\n ctx.ui.notify(\n sanitizeChromeDevtoolsDisplay(\n `Chrome DevTools ${setting} saved. Effective ${setting}: ${effectiveValue} (${source}).${overrideNotice}`,\n ),\n \"info\",\n );\n}\n\nfunction notifySaveFailure(ctx: CommandContext, error: unknown) {\n ctx.ui.notify(\n sanitizeChromeDevtoolsDisplay(\n `Chrome DevTools browser settings save failed; previous settings remain active: ${formatError(error)}`,\n ),\n \"warning\",\n );\n}\n\nfunction formatError(error: unknown) {\n return error instanceof Error ? error.message : String(error);\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["isCurrent"]
7
+ }
@@ -10,7 +10,7 @@ import {
10
10
  launchHint,
11
11
  state,
12
12
  withEndpointRetry
13
- } from "./chunk-F5VG35UI.js";
13
+ } from "./chunk-TVVYQ5KM.ts";
14
14
 
15
15
  // src/cdp-client.ts
16
16
  var MAX_BUFFERED_EVENTS_PER_METHOD = 32;
@@ -381,4 +381,4 @@ export {
381
381
  withCdp,
382
382
  CdpClient
383
383
  };
384
- //# sourceMappingURL=chunk-4KMN4SFF.js.map
384
+ //# sourceMappingURL=chunk-5T2WEIYN.ts.map
@@ -23,6 +23,9 @@ import { dirname, isAbsolute, join, resolve } from "node:path";
23
23
  import { CONFIG_DIR_NAME, getAgentDir } from "@earendil-works/pi-coding-agent";
24
24
  var NEW_SETTINGS_FILE_NAME = "pi-chrome-devtools.json";
25
25
  var LEGACY_SETTINGS_FILE_NAME = "pi-chrome-devtools-settings.json";
26
+ function isChromeDevtoolsToolMode(value) {
27
+ return value === "codemode" || value === "lazy" || value === "direct";
28
+ }
26
29
  var DEFAULT_BROWSER_HOST = "127.0.0.1";
27
30
  var DEFAULT_BROWSER_PORT = 9222;
28
31
  var DEFAULT_BROWSER_ENDPOINT = `http://${DEFAULT_BROWSER_HOST}:${DEFAULT_BROWSER_PORT}`;
@@ -77,7 +80,7 @@ async function loadSettings(options = {}) {
77
80
  const userBrowser = user.normalized?.browser ?? {};
78
81
  const effectiveBrowser = resolveEffectiveBrowser(userBrowser, project.normalized?.browser);
79
82
  const settings = resolveSettings(user.normalized, project.normalized, effectiveBrowser);
80
- const recognized = settings.tools !== void 0 || user.normalized?.browser !== void 0 || project.normalized?.browser !== void 0 || user.normalized?.webmcp !== void 0;
83
+ const recognized = user.normalized?.toolMode !== void 0 || settings.tools !== void 0 || user.normalized?.browser !== void 0 || project.normalized?.browser !== void 0 || user.normalized?.webmcp !== void 0;
81
84
  const invalidReasons = [user, project].filter((result) => result.kind === "invalid").map((result) => result.reason).filter((reason) => Boolean(reason));
82
85
  const userFile = user.kind === "invalid" ? {
83
86
  kind: "invalid",
@@ -99,6 +102,7 @@ async function loadSettings(options = {}) {
99
102
  }
100
103
  function resolveSettings(user, _project, browser) {
101
104
  return {
105
+ toolMode: user?.toolMode ?? "codemode",
102
106
  ...user?.tools ? { tools: user.tools, updatedAt: user.updatedAt } : {},
103
107
  browser,
104
108
  webmcp: { enabled: user?.webmcp?.enabled ?? false }
@@ -190,6 +194,11 @@ function projectOwnedSettingsWarnings(document, scope, filePath) {
190
194
  }
191
195
  async function normalizeSettingsDocument(document, scope, filePath, cwd) {
192
196
  const normalized = {};
197
+ if (scope === "user" && document.toolMode !== void 0) {
198
+ if (!isChromeDevtoolsToolMode(document.toolMode))
199
+ throw new Error("expected toolMode to be codemode, lazy, or direct");
200
+ normalized.toolMode = document.toolMode;
201
+ }
193
202
  if (scope === "user" && (document.tools !== void 0 || document.updatedAt !== void 0)) {
194
203
  const toolSettings = normalizeChromeDevtoolsSettings(document);
195
204
  if (!toolSettings) {
@@ -332,16 +341,31 @@ function orderedUniqueChromeDevtoolsTools(tools) {
332
341
  function orderedUnique(values) {
333
342
  return [...new Set(values)];
334
343
  }
335
- function saveSettings(settings, operations = {}) {
344
+ var ToolCatalogApplicationError = class extends Error {
345
+ constructor(error, rollbackError) {
346
+ super(
347
+ rollbackError ? `${formatError(error)}; saved catalog rollback failed: ${formatError(rollbackError)}` : `${formatError(error)}; previous catalog restored`,
348
+ { cause: error }
349
+ );
350
+ this.rollbackError = rollbackError;
351
+ }
352
+ rollbackError;
353
+ };
354
+ function saveSettings(settings, operations = {}, applyAfterSave) {
336
355
  return queueSettingsMutation(
337
356
  (current) => ({
338
357
  ...current,
339
358
  tools: [...settings.tools],
340
359
  updatedAt: settings.updatedAt
341
360
  }),
342
- operations
361
+ operations,
362
+ applyAfterSave
343
363
  );
344
364
  }
365
+ function saveToolMode(mode, operations = {}) {
366
+ if (!isChromeDevtoolsToolMode(mode)) return Promise.reject(new Error("Invalid tool mode"));
367
+ return queueSettingsMutation((current) => ({ ...current, toolMode: mode }), operations);
368
+ }
345
369
  function saveWebMcpSettings(enabled, operations = {}) {
346
370
  return queueSettingsMutation(
347
371
  (current) => ({
@@ -367,12 +391,12 @@ function saveBrowserSettings(patch, operations = {}) {
367
391
  return { ...current, browser };
368
392
  }, operations);
369
393
  }
370
- function queueSettingsMutation(mutate, operations) {
371
- const operation = settingsSaveQueue.then(() => saveSettingsMutationNow(mutate, operations));
394
+ function queueSettingsMutation(mutate, operations, applyToolCatalog) {
395
+ const operation = settingsSaveQueue.then(() => saveSettingsMutationNow(mutate, operations, applyToolCatalog));
372
396
  settingsSaveQueue = operation.catch(() => void 0);
373
397
  return operation;
374
398
  }
375
- async function saveSettingsMutationNow(mutate, operations) {
399
+ async function saveSettingsMutationNow(mutate, operations, applyToolCatalog) {
376
400
  const filePath = settingsFilePath();
377
401
  let current = await readSettingsDocument(filePath, "user");
378
402
  const replaceCanonical = current.kind !== "missing";
@@ -394,6 +418,27 @@ async function saveSettingsMutationNow(mutate, operations) {
394
418
  await rm(tempFile, { force: true }).catch(() => void 0);
395
419
  throw error;
396
420
  }
421
+ if (!applyToolCatalog) return;
422
+ try {
423
+ applyToolCatalog();
424
+ } catch (error) {
425
+ let rollbackError;
426
+ try {
427
+ await saveSettingsMutationNow((latest) => {
428
+ const restored = { ...latest };
429
+ for (const field of ["tools", "updatedAt"]) {
430
+ if (JSON.stringify(latest[field]) !== JSON.stringify(nextDocument[field]))
431
+ throw new Error("Tool catalog changed after save; refusing to overwrite newer settings");
432
+ delete restored[field];
433
+ if (current.document?.[field] !== void 0) restored[field] = current.document[field];
434
+ }
435
+ return restored;
436
+ }, operations);
437
+ } catch (caught) {
438
+ rollbackError = caught;
439
+ }
440
+ throw new ToolCatalogApplicationError(error, rollbackError);
441
+ }
397
442
  }
398
443
  function settingsFilePath() {
399
444
  return join(getAgentDir(), NEW_SETTINGS_FILE_NAME);
@@ -1468,10 +1513,13 @@ export {
1468
1513
  WEBMCP_TOOL_NAMES,
1469
1514
  CHROME_DEVTOOLS_TOOL_NAMES,
1470
1515
  isWebMcpToolName,
1516
+ isChromeDevtoolsToolMode,
1471
1517
  waitForSettingsWrites,
1472
1518
  loadSettings,
1473
1519
  parseBrowserEndpoint,
1520
+ ToolCatalogApplicationError,
1474
1521
  saveSettings,
1522
+ saveToolMode,
1475
1523
  saveWebMcpSettings,
1476
1524
  saveBrowserSettings,
1477
1525
  settingsFilePath,
@@ -1505,4 +1553,4 @@ export {
1505
1553
  endpointConfigHint,
1506
1554
  formatPageListItem
1507
1555
  };
1508
- //# sourceMappingURL=chunk-F5VG35UI.js.map
1556
+ //# sourceMappingURL=chunk-TVVYQ5KM.ts.map