@narumitw/pi-chrome-devtools 0.53.2 → 0.53.4

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 (40) hide show
  1. package/README.md +1 -0
  2. package/dist/chunks/{browser-settings-menu-IMB6HXSU.js → browser-settings-menu-FVMCCUPM.ts} +16 -42
  3. package/dist/chunks/browser-settings-menu-FVMCCUPM.ts.map +7 -0
  4. package/dist/chunks/{chunk-GTTYS5GT.js → chunk-4B3EQHOH.ts} +2 -2
  5. package/dist/chunks/chunk-4B3EQHOH.ts.map +7 -0
  6. package/dist/chunks/{chunk-SRT3UUIP.js → chunk-LSJSDY2M.ts} +23 -74
  7. package/dist/chunks/chunk-LSJSDY2M.ts.map +7 -0
  8. package/dist/chunks/{chunk-LLXOCRNF.js → chunk-V7N3WI5B.ts} +12 -33
  9. package/dist/chunks/chunk-V7N3WI5B.ts.map +7 -0
  10. package/dist/chunks/{menu-UJETYMSB.js → menu-MTPSLDHZ.ts} +10 -23
  11. package/dist/chunks/menu-MTPSLDHZ.ts.map +7 -0
  12. package/dist/chunks/{tools-6P7EUX37.js → tools-OVNH45IJ.ts} +17 -59
  13. package/dist/chunks/tools-OVNH45IJ.ts.map +7 -0
  14. package/dist/index.ts +18 -42
  15. package/dist/index.ts.map +2 -2
  16. package/package.json +54 -55
  17. package/src/browser-manager.ts +872 -942
  18. package/src/browser-settings-menu.ts +315 -350
  19. package/src/cdp-client.ts +381 -398
  20. package/src/chrome-devtools.ts +205 -232
  21. package/src/display.ts +26 -29
  22. package/src/lazy-tools.ts +158 -181
  23. package/src/menu.ts +409 -446
  24. package/src/render.ts +72 -81
  25. package/src/runtime.ts +137 -140
  26. package/src/screenshot.ts +170 -186
  27. package/src/settings.ts +472 -523
  28. package/src/tool-names.ts +8 -14
  29. package/src/tool-selector.ts +242 -271
  30. package/src/tools.ts +218 -226
  31. package/src/webmcp/discovery.ts +184 -188
  32. package/src/webmcp/policy.ts +296 -329
  33. package/src/webmcp/protocol.ts +324 -337
  34. package/src/webmcp/tools.ts +136 -147
  35. package/dist/chunks/browser-settings-menu-IMB6HXSU.js.map +0 -7
  36. package/dist/chunks/chunk-GTTYS5GT.js.map +0 -7
  37. package/dist/chunks/chunk-LLXOCRNF.js.map +0 -7
  38. package/dist/chunks/chunk-SRT3UUIP.js.map +0 -7
  39. package/dist/chunks/menu-UJETYMSB.js.map +0 -7
  40. package/dist/chunks/tools-6P7EUX37.js.map +0 -7
package/src/tool-names.ts CHANGED
@@ -1,24 +1,18 @@
1
1
  export const CORE_CHROME_DEVTOOLS_TOOL_NAMES = [
2
- "chrome_devtools_list_pages",
3
- "chrome_devtools_select_page",
4
- "chrome_devtools_navigate",
5
- "chrome_devtools_evaluate",
6
- "chrome_devtools_screenshot",
2
+ "chrome_devtools_list_pages",
3
+ "chrome_devtools_select_page",
4
+ "chrome_devtools_navigate",
5
+ "chrome_devtools_evaluate",
6
+ "chrome_devtools_screenshot",
7
7
  ] as const;
8
8
 
9
- export const WEBMCP_TOOL_NAMES = [
10
- "chrome_devtools_webmcp_list_tools",
11
- "chrome_devtools_webmcp_call_tool",
12
- ] as const;
9
+ export const WEBMCP_TOOL_NAMES = ["chrome_devtools_webmcp_list_tools", "chrome_devtools_webmcp_call_tool"] as const;
13
10
 
14
- export const CHROME_DEVTOOLS_TOOL_NAMES = [
15
- ...CORE_CHROME_DEVTOOLS_TOOL_NAMES,
16
- ...WEBMCP_TOOL_NAMES,
17
- ] as const;
11
+ export const CHROME_DEVTOOLS_TOOL_NAMES = [...CORE_CHROME_DEVTOOLS_TOOL_NAMES, ...WEBMCP_TOOL_NAMES] as const;
18
12
 
19
13
  export type ChromeDevToolsToolName = (typeof CHROME_DEVTOOLS_TOOL_NAMES)[number];
20
14
  export type WebMcpToolName = (typeof WEBMCP_TOOL_NAMES)[number];
21
15
 
22
16
  export function isWebMcpToolName(value: string): value is WebMcpToolName {
23
- return WEBMCP_TOOL_NAMES.includes(value as WebMcpToolName);
17
+ return WEBMCP_TOOL_NAMES.includes(value as WebMcpToolName);
24
18
  }
@@ -1,30 +1,30 @@
1
1
  import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
2
  import {
3
- browserCandidateHint,
4
- browserLifecycleState,
5
- browserSettingsForOwner,
6
- devToolsEndpoint,
7
- endpointConfigHint,
8
- endpointSourceLabel,
9
- launchAttemptLines,
10
- launchHint,
11
- launchModeLabel,
12
- managedBrowserForOwner,
3
+ browserCandidateHint,
4
+ browserLifecycleState,
5
+ browserSettingsForOwner,
6
+ devToolsEndpoint,
7
+ endpointConfigHint,
8
+ endpointSourceLabel,
9
+ launchAttemptLines,
10
+ launchHint,
11
+ launchModeLabel,
12
+ managedBrowserForOwner,
13
13
  } from "./browser-manager.js";
14
14
  import { sanitizeChromeDevtoolsDisplay } from "./display.js";
15
15
  import {
16
- applyAvailableChromeDevtoolsTools,
17
- availableChromeDevtoolsTools,
18
- CHROME_DEVTOOLS_LOAD_TOOL_NAME,
19
- chromeDevtoolsToolExposureMode,
16
+ applyAvailableChromeDevtoolsTools,
17
+ availableChromeDevtoolsTools,
18
+ CHROME_DEVTOOLS_LOAD_TOOL_NAME,
19
+ chromeDevtoolsToolExposureMode,
20
20
  } from "./lazy-tools.js";
21
21
  import { invalidateWebMcpOperations, state, webMcpEnabled } from "./runtime.js";
22
22
  import { loadSettings, saveSettings, settingsFilePath } from "./settings.js";
23
23
  import {
24
- CHROME_DEVTOOLS_TOOL_NAMES,
25
- type ChromeDevToolsToolName,
26
- CORE_CHROME_DEVTOOLS_TOOL_NAMES,
27
- isWebMcpToolName,
24
+ CHROME_DEVTOOLS_TOOL_NAMES,
25
+ type ChromeDevToolsToolName,
26
+ CORE_CHROME_DEVTOOLS_TOOL_NAMES,
27
+ isWebMcpToolName,
28
28
  } from "./tool-names.js";
29
29
 
30
30
  export { sanitizeChromeDevtoolsDisplay };
@@ -32,334 +32,305 @@ export { sanitizeChromeDevtoolsDisplay };
32
32
  type CommandContext = ExtensionCommandContext;
33
33
 
34
34
  function unique<T>(values: T[]) {
35
- return Array.from(new Set(values));
35
+ return Array.from(new Set(values));
36
36
  }
37
37
 
38
38
  function formatError(error: unknown) {
39
- return error instanceof Error ? error.message : String(error);
39
+ return error instanceof Error ? error.message : String(error);
40
40
  }
41
41
 
42
42
  interface ToolStatusSummary {
43
- availabilityStatus: "enabled" | "disabled" | "partial";
44
- availableChromeToolCount: number;
45
- loadedChromeToolCount: number;
46
- activeNonChromeToolCount: number;
47
- capabilityCount: number;
43
+ availabilityStatus: "enabled" | "disabled" | "partial";
44
+ availableChromeToolCount: number;
45
+ loadedChromeToolCount: number;
46
+ activeNonChromeToolCount: number;
47
+ capabilityCount: number;
48
48
  }
49
49
 
50
50
  type ToolSelectionSaveResult = "saved" | "active-tools-changed" | "failed";
51
51
 
52
52
  export async function updateChromeDevtoolsTools(
53
- pi: ExtensionAPI,
54
- ctx: CommandContext,
55
- selectedTools: readonly ChromeDevToolsToolName[],
56
- action: string,
53
+ pi: ExtensionAPI,
54
+ ctx: CommandContext,
55
+ selectedTools: readonly ChromeDevToolsToolName[],
56
+ action: string,
57
57
  ) {
58
- const generation = state.sessionGeneration;
59
- const result = await transactSelectedTools(pi, ctx, selectedTools, generation);
60
- if (result !== "saved" || generation !== state.sessionGeneration) return;
61
- const status = await buildToolStatusMessage(pi, ctx.sessionManager);
62
- if (generation !== state.sessionGeneration) return;
63
- ctx.ui.notify(`Chrome DevTools tool catalog ${action}.\n\n${status}`, "info");
58
+ const generation = state.sessionGeneration;
59
+ const result = await transactSelectedTools(pi, ctx, selectedTools, generation);
60
+ if (result !== "saved" || generation !== state.sessionGeneration) return;
61
+ const status = await buildToolStatusMessage(pi, ctx.sessionManager);
62
+ if (generation !== state.sessionGeneration) return;
63
+ ctx.ui.notify(`Chrome DevTools tool catalog ${action}.\n\n${status}`, "info");
64
64
  }
65
65
 
66
66
  export async function setSelectedChromeDevtoolsTools(
67
- pi: ExtensionAPI,
68
- ctx: CommandContext,
69
- selectedTools: readonly ChromeDevToolsToolName[],
70
- expectedActiveTools: readonly ChromeDevToolsToolName[],
67
+ pi: ExtensionAPI,
68
+ ctx: CommandContext,
69
+ selectedTools: readonly ChromeDevToolsToolName[],
70
+ expectedActiveTools: readonly ChromeDevToolsToolName[],
71
71
  ): Promise<ToolSelectionSaveResult> {
72
- return transactSelectedTools(
73
- pi,
74
- ctx,
75
- selectedTools,
76
- state.sessionGeneration,
77
- expectedActiveTools,
78
- );
72
+ return transactSelectedTools(pi, ctx, selectedTools, state.sessionGeneration, expectedActiveTools);
79
73
  }
80
74
 
81
75
  let toolTransactionQueue = Promise.resolve();
82
76
 
83
77
  export async function waitForChromeDevtoolsSettings(): Promise<void> {
84
- await toolTransactionQueue;
78
+ await toolTransactionQueue;
85
79
  }
86
80
 
87
81
  function transactSelectedTools(
88
- pi: ExtensionAPI,
89
- ctx: CommandContext,
90
- selectedTools: readonly ChromeDevToolsToolName[],
91
- expectedGeneration: number,
92
- expectedActiveTools?: readonly ChromeDevToolsToolName[],
82
+ pi: ExtensionAPI,
83
+ ctx: CommandContext,
84
+ selectedTools: readonly ChromeDevToolsToolName[],
85
+ expectedGeneration: number,
86
+ expectedActiveTools?: readonly ChromeDevToolsToolName[],
93
87
  ): Promise<ToolSelectionSaveResult> {
94
- const operation = toolTransactionQueue.then(() =>
95
- transactSelectedToolsNow(pi, ctx, selectedTools, expectedGeneration, expectedActiveTools),
96
- );
97
- toolTransactionQueue = operation.then(
98
- () => undefined,
99
- () => undefined,
100
- );
101
- return operation;
88
+ const operation = toolTransactionQueue.then(() =>
89
+ transactSelectedToolsNow(pi, ctx, selectedTools, expectedGeneration, expectedActiveTools),
90
+ );
91
+ toolTransactionQueue = operation.then(
92
+ () => undefined,
93
+ () => undefined,
94
+ );
95
+ return operation;
102
96
  }
103
97
 
104
98
  async function transactSelectedToolsNow(
105
- pi: ExtensionAPI,
106
- ctx: CommandContext,
107
- selectedTools: readonly ChromeDevToolsToolName[],
108
- expectedGeneration: number,
109
- expectedActiveTools?: readonly ChromeDevToolsToolName[],
99
+ pi: ExtensionAPI,
100
+ ctx: CommandContext,
101
+ selectedTools: readonly ChromeDevToolsToolName[],
102
+ expectedGeneration: number,
103
+ expectedActiveTools?: readonly ChromeDevToolsToolName[],
110
104
  ): Promise<ToolSelectionSaveResult> {
111
- if (expectedGeneration !== state.sessionGeneration) return "failed";
112
- if (expectedActiveTools && !arraysEqual(availableChromeDevtoolsTools(pi), expectedActiveTools)) {
113
- ctx.ui.notify(
114
- "Browser tool selection changed while review was open. Review the current state, then apply again.",
115
- "warning",
116
- );
117
- return "active-tools-changed";
118
- }
119
- const previousActiveTools = pi.getActiveTools();
120
- const previousAvailableTools = availableChromeDevtoolsTools(pi);
121
- try {
122
- const previousWebMcpTools = previousAvailableTools.filter(isWebMcpToolName);
123
- const selectedWebMcpTools = selectedTools.filter(isWebMcpToolName);
124
- if (!arraysEqual(previousWebMcpTools, selectedWebMcpTools)) {
125
- invalidateWebMcpOperations(
126
- ctx.sessionManager,
127
- "Chrome DevTools WebMCP gateway availability changed",
128
- );
129
- }
130
- applyChromeDevtoolsTools(pi, selectedTools);
131
- await persistSettings(selectedTools);
132
- return expectedGeneration === state.sessionGeneration ? "saved" : "failed";
133
- } catch (error) {
134
- let rollbackError: unknown;
135
- try {
136
- applyAvailableChromeDevtoolsTools(pi, previousAvailableTools);
137
- const currentNonChromeTools = pi
138
- .getActiveTools()
139
- .filter((name) => !CHROME_DEVTOOLS_TOOL_NAMES.includes(name as ChromeDevToolsToolName));
140
- const previousLoadedChromeTools = previousActiveTools.filter((name) =>
141
- CHROME_DEVTOOLS_TOOL_NAMES.includes(name as ChromeDevToolsToolName),
142
- );
143
- const restoredChromeTools =
144
- chromeDevtoolsToolExposureMode(pi) === "eager"
145
- ? previousAvailableTools
146
- : previousLoadedChromeTools;
147
- pi.setActiveTools(unique([...currentNonChromeTools, ...restoredChromeTools]));
148
- } catch (caught) {
149
- rollbackError = caught;
150
- }
151
- if (expectedGeneration !== state.sessionGeneration) return "failed";
152
- ctx.ui.notify(
153
- sanitizeChromeDevtoolsDisplay(
154
- rollbackError
155
- ? `Chrome DevTools settings save failed: ${formatError(error)}; active-tool rollback failed: ${formatError(rollbackError)}`
156
- : `Chrome DevTools settings save failed; active tools restored: ${formatError(error)}`,
157
- ),
158
- "warning",
159
- );
160
- return "failed";
161
- }
105
+ if (expectedGeneration !== state.sessionGeneration) return "failed";
106
+ if (expectedActiveTools && !arraysEqual(availableChromeDevtoolsTools(pi), expectedActiveTools)) {
107
+ ctx.ui.notify(
108
+ "Browser tool selection changed while review was open. Review the current state, then apply again.",
109
+ "warning",
110
+ );
111
+ return "active-tools-changed";
112
+ }
113
+ const previousActiveTools = pi.getActiveTools();
114
+ const previousAvailableTools = availableChromeDevtoolsTools(pi);
115
+ try {
116
+ const previousWebMcpTools = previousAvailableTools.filter(isWebMcpToolName);
117
+ const selectedWebMcpTools = selectedTools.filter(isWebMcpToolName);
118
+ if (!arraysEqual(previousWebMcpTools, selectedWebMcpTools)) {
119
+ invalidateWebMcpOperations(ctx.sessionManager, "Chrome DevTools WebMCP gateway availability changed");
120
+ }
121
+ applyChromeDevtoolsTools(pi, selectedTools);
122
+ await persistSettings(selectedTools);
123
+ return expectedGeneration === state.sessionGeneration ? "saved" : "failed";
124
+ } catch (error) {
125
+ let rollbackError: unknown;
126
+ try {
127
+ applyAvailableChromeDevtoolsTools(pi, previousAvailableTools);
128
+ const currentNonChromeTools = pi
129
+ .getActiveTools()
130
+ .filter((name) => !CHROME_DEVTOOLS_TOOL_NAMES.includes(name as ChromeDevToolsToolName));
131
+ const previousLoadedChromeTools = previousActiveTools.filter((name) =>
132
+ CHROME_DEVTOOLS_TOOL_NAMES.includes(name as ChromeDevToolsToolName),
133
+ );
134
+ const restoredChromeTools =
135
+ chromeDevtoolsToolExposureMode(pi) === "eager" ? previousAvailableTools : previousLoadedChromeTools;
136
+ pi.setActiveTools(unique([...currentNonChromeTools, ...restoredChromeTools]));
137
+ } catch (caught) {
138
+ rollbackError = caught;
139
+ }
140
+ if (expectedGeneration !== state.sessionGeneration) return "failed";
141
+ ctx.ui.notify(
142
+ sanitizeChromeDevtoolsDisplay(
143
+ rollbackError
144
+ ? `Chrome DevTools settings save failed: ${formatError(error)}; active-tool rollback failed: ${formatError(rollbackError)}`
145
+ : `Chrome DevTools settings save failed; active tools restored: ${formatError(error)}`,
146
+ ),
147
+ "warning",
148
+ );
149
+ return "failed";
150
+ }
162
151
  }
163
152
 
164
153
  function arraysEqual<T>(left: readonly T[], right: readonly T[]) {
165
- return left.length === right.length && left.every((value, index) => value === right[index]);
154
+ return left.length === right.length && left.every((value, index) => value === right[index]);
166
155
  }
167
156
 
168
- export function applyChromeDevtoolsTools(
169
- pi: ExtensionAPI,
170
- selectedTools: readonly ChromeDevToolsToolName[],
171
- ) {
172
- applyAvailableChromeDevtoolsTools(pi, selectedTools);
157
+ export function applyChromeDevtoolsTools(pi: ExtensionAPI, selectedTools: readonly ChromeDevToolsToolName[]) {
158
+ applyAvailableChromeDevtoolsTools(pi, selectedTools);
173
159
  }
174
160
 
175
161
  function getToolStatusSummary(pi: ExtensionAPI, owner: object): ToolStatusSummary {
176
- const chromeToolNames = new Set<string>(CHROME_DEVTOOLS_TOOL_NAMES);
177
- const activeToolNames = new Set(pi.getActiveTools());
178
- const loadedChromeToolCount = CHROME_DEVTOOLS_TOOL_NAMES.filter((name) =>
179
- activeToolNames.has(name),
180
- ).length;
181
- const availableChromeToolCount = availableChromeDevtoolsTools(pi).length;
182
- const activeNonChromeToolCount = Array.from(activeToolNames).filter(
183
- (name) => !chromeToolNames.has(name) && name !== CHROME_DEVTOOLS_LOAD_TOOL_NAME,
184
- ).length;
185
- const capabilityCount = effectiveCatalog(owner).length;
186
- const availabilityStatus =
187
- availableChromeToolCount === capabilityCount
188
- ? "enabled"
189
- : availableChromeToolCount === 0
190
- ? "disabled"
191
- : "partial";
162
+ const chromeToolNames = new Set<string>(CHROME_DEVTOOLS_TOOL_NAMES);
163
+ const activeToolNames = new Set(pi.getActiveTools());
164
+ const loadedChromeToolCount = CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => activeToolNames.has(name)).length;
165
+ const availableChromeToolCount = availableChromeDevtoolsTools(pi).length;
166
+ const activeNonChromeToolCount = Array.from(activeToolNames).filter(
167
+ (name) => !chromeToolNames.has(name) && name !== CHROME_DEVTOOLS_LOAD_TOOL_NAME,
168
+ ).length;
169
+ const capabilityCount = effectiveCatalog(owner).length;
170
+ const availabilityStatus =
171
+ availableChromeToolCount === capabilityCount ? "enabled" : availableChromeToolCount === 0 ? "disabled" : "partial";
192
172
 
193
- return {
194
- availabilityStatus,
195
- availableChromeToolCount,
196
- loadedChromeToolCount,
197
- activeNonChromeToolCount,
198
- capabilityCount,
199
- };
173
+ return {
174
+ availabilityStatus,
175
+ availableChromeToolCount,
176
+ loadedChromeToolCount,
177
+ activeNonChromeToolCount,
178
+ capabilityCount,
179
+ };
200
180
  }
201
181
 
202
182
  export async function buildToolStatusMessage(pi: ExtensionAPI, owner: object) {
203
- const summary = getToolStatusSummary(pi, owner);
204
- const persistedSetting = await persistedSettingLabel();
205
- return sanitizeChromeDevtoolsDisplay(
206
- [
207
- `Chrome DevTools tools available: ${formatRuntimeStatus(summary)}`,
208
- `Tool exposure: ${chromeDevtoolsToolExposureMode(pi)}`,
209
- `Loaded capability tools this session: ${summary.loadedChromeToolCount}/${summary.capabilityCount}`,
210
- `WebMCP: ${webMcpEnabled(owner) ? "enabled · experimental · confirmation required for every call" : "disabled · experimental"}`,
211
- `Loader: ${pi.getActiveTools().includes(CHROME_DEVTOOLS_LOAD_TOOL_NAME) ? "active" : "inactive"}`,
212
- `Persisted tool catalog: ${persistedSetting}`,
213
- ...browserSettingsStatusLines(owner),
214
- ...(state.settingsNotice ? [`Settings note: ${state.settingsNotice}`] : []),
215
- `Other active tools preserved: ${summary.activeNonChromeToolCount}`,
216
- `Endpoint: ${devToolsEndpoint(owner)}`,
217
- `Endpoint source: ${endpointSourceLabel(owner)}`,
218
- `Launch mode: ${launchModeLabel(owner)}`,
219
- ...launchAttemptLines(owner),
220
- ].join("\n"),
221
- );
183
+ const summary = getToolStatusSummary(pi, owner);
184
+ const persistedSetting = await persistedSettingLabel();
185
+ return sanitizeChromeDevtoolsDisplay(
186
+ [
187
+ `Chrome DevTools tools available: ${formatRuntimeStatus(summary)}`,
188
+ `Tool exposure: ${chromeDevtoolsToolExposureMode(pi)}`,
189
+ `Loaded capability tools this session: ${summary.loadedChromeToolCount}/${summary.capabilityCount}`,
190
+ `WebMCP: ${webMcpEnabled(owner) ? "enabled · experimental · confirmation required for every call" : "disabled · experimental"}`,
191
+ `Loader: ${pi.getActiveTools().includes(CHROME_DEVTOOLS_LOAD_TOOL_NAME) ? "active" : "inactive"}`,
192
+ `Persisted tool catalog: ${persistedSetting}`,
193
+ ...browserSettingsStatusLines(owner),
194
+ ...(state.settingsNotice ? [`Settings note: ${state.settingsNotice}`] : []),
195
+ `Other active tools preserved: ${summary.activeNonChromeToolCount}`,
196
+ `Endpoint: ${devToolsEndpoint(owner)}`,
197
+ `Endpoint source: ${endpointSourceLabel(owner)}`,
198
+ `Launch mode: ${launchModeLabel(owner)}`,
199
+ ...launchAttemptLines(owner),
200
+ ].join("\n"),
201
+ );
222
202
  }
223
203
 
224
204
  export function buildQuickstartMessage(owner: object) {
225
- return buildSettingsSetupMessage(owner);
205
+ return buildSettingsSetupMessage(owner);
226
206
  }
227
207
 
228
208
  export function buildBrowserStatusMessage(owner?: object) {
229
- const browser = browserSettingsForOwner(owner);
230
- const lifecycle = browserLifecycleState(owner);
231
- const webMcpIsEnabled = owner ? webMcpEnabled(owner) : false;
232
- const browserState =
233
- lifecycle === "starting"
234
- ? "starting managed browser"
235
- : lifecycle === "running"
236
- ? "managed browser running"
237
- : lifecycle === "exited"
238
- ? "managed browser exited"
239
- : lifecycle === "failed"
240
- ? "last launch failed"
241
- : "not started; connection has not been checked";
242
- const needsRecovery = lifecycle === "exited" || lifecycle === "failed";
243
- return sanitizeChromeDevtoolsDisplay(
244
- [
245
- `Browser: ${browserState}`,
246
- "Viewing this status does not probe the endpoint or launch Chrome.",
247
- `Endpoint: ${devToolsEndpoint(owner)}`,
248
- `Endpoint source: ${endpointSourceLabel(owner)}`,
249
- `Launch mode: ${launchModeLabel(owner)}`,
250
- `Unpacked extensions: ${browser.extensionPaths.length} (${browser.extensionPathsSource})`,
251
- `WebMCP: ${webMcpIsEnabled ? "enabled · experimental" : "disabled · experimental"}`,
252
- ...(webMcpIsEnabled && !managedBrowserForOwner(owner)?.ready
253
- ? [
254
- "WebMCP warning: attached browser profiles may contain everyday authenticated sessions and sensitive state.",
255
- ]
256
- : []),
257
- ...(browser.extensionPaths.length > 0
258
- ? ["Unpacked extensions execute trusted browser code in an isolated managed browser."]
259
- : []),
260
- ...launchAttemptLines(owner),
261
- ...(needsRecovery ? [launchHint(owner), endpointConfigHint()] : []),
262
- ].join("\n"),
263
- );
209
+ const browser = browserSettingsForOwner(owner);
210
+ const lifecycle = browserLifecycleState(owner);
211
+ const webMcpIsEnabled = owner ? webMcpEnabled(owner) : false;
212
+ const browserState =
213
+ lifecycle === "starting"
214
+ ? "starting managed browser"
215
+ : lifecycle === "running"
216
+ ? "managed browser running"
217
+ : lifecycle === "exited"
218
+ ? "managed browser exited"
219
+ : lifecycle === "failed"
220
+ ? "last launch failed"
221
+ : "not started; connection has not been checked";
222
+ const needsRecovery = lifecycle === "exited" || lifecycle === "failed";
223
+ return sanitizeChromeDevtoolsDisplay(
224
+ [
225
+ `Browser: ${browserState}`,
226
+ "Viewing this status does not probe the endpoint or launch Chrome.",
227
+ `Endpoint: ${devToolsEndpoint(owner)}`,
228
+ `Endpoint source: ${endpointSourceLabel(owner)}`,
229
+ `Launch mode: ${launchModeLabel(owner)}`,
230
+ `Unpacked extensions: ${browser.extensionPaths.length} (${browser.extensionPathsSource})`,
231
+ `WebMCP: ${webMcpIsEnabled ? "enabled · experimental" : "disabled · experimental"}`,
232
+ ...(webMcpIsEnabled && !managedBrowserForOwner(owner)?.ready
233
+ ? ["WebMCP warning: attached browser profiles may contain everyday authenticated sessions and sensitive state."]
234
+ : []),
235
+ ...(browser.extensionPaths.length > 0
236
+ ? ["Unpacked extensions execute trusted browser code in an isolated managed browser."]
237
+ : []),
238
+ ...launchAttemptLines(owner),
239
+ ...(needsRecovery ? [launchHint(owner), endpointConfigHint()] : []),
240
+ ].join("\n"),
241
+ );
264
242
  }
265
243
 
266
244
  export function buildSettingsSetupMessage(owner: object) {
267
- return sanitizeChromeDevtoolsDisplay(
268
- [
269
- `Chrome DevTools endpoint: ${devToolsEndpoint(owner)}`,
270
- `Endpoint source: ${endpointSourceLabel(owner)}`,
271
- `Launch mode: ${launchModeLabel(owner)}`,
272
- ...browserSettingsStatusLines(owner),
273
- launchHint(owner),
274
- browserCandidateHint(owner),
275
- ...launchAttemptLines(owner),
276
- endpointConfigHint(),
277
- ].join("\n"),
278
- );
245
+ return sanitizeChromeDevtoolsDisplay(
246
+ [
247
+ `Chrome DevTools endpoint: ${devToolsEndpoint(owner)}`,
248
+ `Endpoint source: ${endpointSourceLabel(owner)}`,
249
+ `Launch mode: ${launchModeLabel(owner)}`,
250
+ ...browserSettingsStatusLines(owner),
251
+ launchHint(owner),
252
+ browserCandidateHint(owner),
253
+ ...launchAttemptLines(owner),
254
+ endpointConfigHint(),
255
+ ].join("\n"),
256
+ );
279
257
  }
280
258
 
281
259
  function browserSettingsStatusLines(owner: object) {
282
- const browser = browserSettingsForOwner(owner);
283
- const extensionPaths = browser.extensionPaths;
284
- const extensionLines =
285
- extensionPaths.length > 0
286
- ? extensionPaths.map((extensionPath) => ` - ${extensionPath}`)
287
- : [" - none"];
288
- return [
289
- `Settings file: ${state.settingsFilePath ?? settingsFilePath()} (user)`,
290
- ...(state.projectSettingsFilePath
291
- ? [
292
- `Project settings: ${state.projectSettingsFilePath} (${state.projectSettingsTrusted ? "trusted" : "untrusted; ignored"})`,
293
- ]
294
- : []),
295
- `Auto-launch: ${browser.autoLaunchEnabled ? "on" : "off"} (${browser.autoLaunchSource})`,
296
- `Browser executable: ${browser.executablePath ?? "automatic discovery"} (${browser.executablePathSource})`,
297
- `Unpacked extensions (${browser.extensionPathsSource}):`,
298
- ...extensionLines,
299
- `WebMCP: ${webMcpEnabled(owner) ? "enabled · experimental · every call requires confirmation" : "disabled · experimental"}`,
300
- "Confirmed menu settings apply before the next browser connection; manual JSON edits require /reload or session replacement.",
301
- ...(extensionPaths.length > 0
302
- ? [
303
- "Unpacked extensions require Chrome for Testing or Chromium and execute trusted browser code.",
304
- ]
305
- : []),
306
- ];
260
+ const browser = browserSettingsForOwner(owner);
261
+ const extensionPaths = browser.extensionPaths;
262
+ const extensionLines =
263
+ extensionPaths.length > 0 ? extensionPaths.map((extensionPath) => ` - ${extensionPath}`) : [" - none"];
264
+ return [
265
+ `Settings file: ${state.settingsFilePath ?? settingsFilePath()} (user)`,
266
+ ...(state.projectSettingsFilePath
267
+ ? [
268
+ `Project settings: ${state.projectSettingsFilePath} (${state.projectSettingsTrusted ? "trusted" : "untrusted; ignored"})`,
269
+ ]
270
+ : []),
271
+ `Auto-launch: ${browser.autoLaunchEnabled ? "on" : "off"} (${browser.autoLaunchSource})`,
272
+ `Browser executable: ${browser.executablePath ?? "automatic discovery"} (${browser.executablePathSource})`,
273
+ `Unpacked extensions (${browser.extensionPathsSource}):`,
274
+ ...extensionLines,
275
+ `WebMCP: ${webMcpEnabled(owner) ? "enabled · experimental · every call requires confirmation" : "disabled · experimental"}`,
276
+ "Confirmed menu settings apply before the next browser connection; manual JSON edits require /reload or session replacement.",
277
+ ...(extensionPaths.length > 0
278
+ ? ["Unpacked extensions require Chrome for Testing or Chromium and execute trusted browser code."]
279
+ : []),
280
+ ];
307
281
  }
308
282
 
309
283
  export function buildCommandGuide(owner: object) {
310
- return [
311
- "Chrome DevTools commands:",
312
- `WebMCP: ${webMcpEnabled(owner) ? "enabled" : "disabled"} · experimental · every page-provided call requires confirmation`,
313
- "/chrome-devtools — open this menu",
314
- "/chrome-devtools help — show command usage",
315
- "/chrome-devtools quickstart — show endpoint and launch help",
316
- "/chrome-devtools status — show tool and settings status",
317
- "/chrome-devtools settings — edit browser connection settings",
318
- "/chrome-devtools tools — choose available Chrome DevTools tools",
319
- "/chrome-devtools toggle|select — compatibility aliases for tools",
320
- "/chrome-devtools enable|on — make all Chrome DevTools tools available",
321
- "/chrome-devtools disable|off — make all Chrome DevTools capability tools unavailable",
322
- ].join("\n");
284
+ return [
285
+ "Chrome DevTools commands:",
286
+ `WebMCP: ${webMcpEnabled(owner) ? "enabled" : "disabled"} · experimental · every page-provided call requires confirmation`,
287
+ "/chrome-devtools — open this menu",
288
+ "/chrome-devtools help — show command usage",
289
+ "/chrome-devtools quickstart — show endpoint and launch help",
290
+ "/chrome-devtools status — show tool and settings status",
291
+ "/chrome-devtools settings — edit browser connection settings",
292
+ "/chrome-devtools tools — choose available Chrome DevTools tools",
293
+ "/chrome-devtools toggle|select — compatibility aliases for tools",
294
+ "/chrome-devtools enable|on — make all Chrome DevTools tools available",
295
+ "/chrome-devtools disable|off — make all Chrome DevTools capability tools unavailable",
296
+ ].join("\n");
323
297
  }
324
298
 
325
299
  export function allChromeDevtoolsTools(owner: object) {
326
- return effectiveCatalog(owner);
300
+ return effectiveCatalog(owner);
327
301
  }
328
302
 
329
303
  export function orderedChromeDevtoolsTools(selectedTools: ReadonlySet<ChromeDevToolsToolName>) {
330
- return CHROME_DEVTOOLS_TOOL_NAMES.filter((toolName) => selectedTools.has(toolName));
304
+ return CHROME_DEVTOOLS_TOOL_NAMES.filter((toolName) => selectedTools.has(toolName));
331
305
  }
332
306
 
333
307
  function formatRuntimeStatus(summary: ToolStatusSummary) {
334
- return `${summary.availabilityStatus} (${summary.availableChromeToolCount}/${summary.capabilityCount} available)`;
308
+ return `${summary.availabilityStatus} (${summary.availableChromeToolCount}/${summary.capabilityCount} available)`;
335
309
  }
336
310
 
337
311
  async function persistedSettingLabel() {
338
- const settings = await loadSettings();
339
- if (settings.kind === "loaded" && settings.settings.tools) {
340
- return formatPersistedSelection(settings.settings.tools);
341
- }
342
- if (settings.kind === "invalid") {
343
- return `none; current active-tool policy preserved (invalid settings ignored: ${settings.reason})`;
344
- }
345
- return "none; current active-tool policy preserved";
312
+ const settings = await loadSettings();
313
+ if (settings.kind === "loaded" && settings.settings.tools) {
314
+ return formatPersistedSelection(settings.settings.tools);
315
+ }
316
+ if (settings.kind === "invalid") {
317
+ return `none; current active-tool policy preserved (invalid settings ignored: ${settings.reason})`;
318
+ }
319
+ return "none; current active-tool policy preserved";
346
320
  }
347
321
 
348
322
  function formatPersistedSelection(tools: readonly ChromeDevToolsToolName[]) {
349
- if (tools.length === CHROME_DEVTOOLS_TOOL_NAMES.length) {
350
- return `all available (${tools.length}/${CHROME_DEVTOOLS_TOOL_NAMES.length} selected)`;
351
- }
352
- if (tools.length === 0)
353
- return `all unavailable (0/${CHROME_DEVTOOLS_TOOL_NAMES.length} selected)`;
354
- return `${tools.length}/${CHROME_DEVTOOLS_TOOL_NAMES.length} selected: ${tools.join(", ")}`;
323
+ if (tools.length === CHROME_DEVTOOLS_TOOL_NAMES.length) {
324
+ return `all available (${tools.length}/${CHROME_DEVTOOLS_TOOL_NAMES.length} selected)`;
325
+ }
326
+ if (tools.length === 0) return `all unavailable (0/${CHROME_DEVTOOLS_TOOL_NAMES.length} selected)`;
327
+ return `${tools.length}/${CHROME_DEVTOOLS_TOOL_NAMES.length} selected: ${tools.join(", ")}`;
355
328
  }
356
329
 
357
330
  function effectiveCatalog(owner: object): ChromeDevToolsToolName[] {
358
- return webMcpEnabled(owner)
359
- ? [...CHROME_DEVTOOLS_TOOL_NAMES]
360
- : [...CORE_CHROME_DEVTOOLS_TOOL_NAMES];
331
+ return webMcpEnabled(owner) ? [...CHROME_DEVTOOLS_TOOL_NAMES] : [...CORE_CHROME_DEVTOOLS_TOOL_NAMES];
361
332
  }
362
333
 
363
334
  async function persistSettings(selectedTools: readonly ChromeDevToolsToolName[]) {
364
- await saveSettings({ tools: [...selectedTools], updatedAt: Date.now() });
335
+ await saveSettings({ tools: [...selectedTools], updatedAt: Date.now() });
365
336
  }