@narumitw/pi-chrome-devtools 0.53.1 → 0.53.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 +89 -230
- package/dist/chunks/{browser-settings-menu-REIU7C3Q.js → browser-settings-menu-SR7KLZT6.js} +16 -42
- package/dist/chunks/browser-settings-menu-SR7KLZT6.js.map +7 -0
- package/dist/chunks/{chunk-GTTYS5GT.js → chunk-4KMN4SFF.js} +2 -2
- package/dist/chunks/chunk-4KMN4SFF.js.map +7 -0
- package/dist/chunks/{chunk-SRT3UUIP.js → chunk-F5VG35UI.js} +23 -74
- package/dist/chunks/chunk-F5VG35UI.js.map +7 -0
- package/dist/chunks/{chunk-YJVYJ2L2.js → chunk-MZCGM6D3.js} +43 -48
- package/dist/chunks/chunk-MZCGM6D3.js.map +7 -0
- package/dist/chunks/{menu-H5HMVGHI.js → menu-YR4VRT7D.js} +10 -23
- package/dist/chunks/menu-YR4VRT7D.js.map +7 -0
- package/dist/chunks/{tools-6P7EUX37.js → tools-6R7Z6HWJ.js} +17 -59
- package/dist/chunks/tools-6R7Z6HWJ.js.map +7 -0
- package/dist/index.ts +23 -48
- package/dist/index.ts.map +2 -2
- package/docs/browser-setup.md +147 -0
- package/package.json +54 -55
- package/src/browser-manager.ts +872 -942
- package/src/browser-settings-menu.ts +315 -350
- package/src/cdp-client.ts +381 -398
- package/src/chrome-devtools.ts +205 -232
- package/src/display.ts +35 -0
- package/src/lazy-tools.ts +158 -181
- package/src/menu.ts +409 -446
- package/src/render.ts +74 -84
- package/src/runtime.ts +137 -140
- package/src/screenshot.ts +170 -186
- package/src/settings.ts +472 -523
- package/src/tool-names.ts +8 -14
- package/src/tool-selector.ts +245 -289
- package/src/tools.ts +218 -226
- package/src/webmcp/discovery.ts +184 -188
- package/src/webmcp/policy.ts +296 -329
- package/src/webmcp/protocol.ts +324 -337
- package/src/webmcp/tools.ts +136 -147
- package/dist/chunks/browser-settings-menu-REIU7C3Q.js.map +0 -7
- package/dist/chunks/chunk-GTTYS5GT.js.map +0 -7
- package/dist/chunks/chunk-SRT3UUIP.js.map +0 -7
- package/dist/chunks/chunk-YJVYJ2L2.js.map +0 -7
- package/dist/chunks/menu-H5HMVGHI.js.map +0 -7
- package/dist/chunks/tools-6P7EUX37.js.map +0 -7
package/src/menu.ts
CHANGED
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { defineMenu, runMenu, runTask } from "@narumitw/pi-tui-kit";
|
|
3
|
-
import {
|
|
4
|
-
browserLifecycleState,
|
|
5
|
-
devToolsEndpoint,
|
|
6
|
-
launchAttemptLines,
|
|
7
|
-
launchModeLabel,
|
|
8
|
-
} from "./browser-manager.js";
|
|
3
|
+
import { browserLifecycleState, devToolsEndpoint, launchAttemptLines, launchModeLabel } from "./browser-manager.js";
|
|
9
4
|
import { availableChromeDevtoolsTools } from "./lazy-tools.js";
|
|
10
5
|
import { state, webMcpEnabled } from "./runtime.js";
|
|
11
6
|
import { loadSettings, type SettingsLoadResult } from "./settings.js";
|
|
12
7
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
CHROME_DEVTOOLS_TOOL_NAMES,
|
|
9
|
+
type ChromeDevToolsToolName,
|
|
10
|
+
CORE_CHROME_DEVTOOLS_TOOL_NAMES,
|
|
16
11
|
} from "./tool-names.js";
|
|
17
12
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
buildBrowserStatusMessage,
|
|
14
|
+
buildCommandGuide,
|
|
15
|
+
sanitizeChromeDevtoolsDisplay,
|
|
16
|
+
setSelectedChromeDevtoolsTools,
|
|
22
17
|
} from "./tool-selector.js";
|
|
23
18
|
|
|
24
19
|
type CommandContext = ExtensionCommandContext;
|
|
@@ -28,497 +23,465 @@ type ToolScreen = "tools" | "review";
|
|
|
28
23
|
type ToolAction = "toggle" | "selectAll" | "selectNone" | "review" | "apply" | "cancel";
|
|
29
24
|
|
|
30
25
|
interface MenuSnapshot {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
activeTools: ChromeDevToolsToolName[];
|
|
27
|
+
persistenceLabel: string;
|
|
28
|
+
mutationBlockedReason?: string;
|
|
29
|
+
settingsWarning?: string;
|
|
35
30
|
}
|
|
36
31
|
|
|
37
32
|
interface ToolWorkflowState {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
accepted: Set<ChromeDevToolsToolName>;
|
|
34
|
+
draft: Set<ChromeDevToolsToolName>;
|
|
35
|
+
mutationBlockedReason?: string;
|
|
36
|
+
generation: number;
|
|
37
|
+
applied: boolean;
|
|
43
38
|
}
|
|
44
39
|
|
|
45
40
|
const TOOL_PRESENTATION: Record<ChromeDevToolsToolName, { label: string; description: string }> = {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
41
|
+
chrome_devtools_list_pages: {
|
|
42
|
+
label: "List open pages",
|
|
43
|
+
description: "List inspectable Chrome tabs and pages (chrome_devtools_list_pages).",
|
|
44
|
+
},
|
|
45
|
+
chrome_devtools_select_page: {
|
|
46
|
+
label: "Select the active page",
|
|
47
|
+
description: "Choose the page later browser actions use (chrome_devtools_select_page).",
|
|
48
|
+
},
|
|
49
|
+
chrome_devtools_navigate: {
|
|
50
|
+
label: "Navigate a page",
|
|
51
|
+
description: "Open a URL, creating a page when needed (chrome_devtools_navigate).",
|
|
52
|
+
},
|
|
53
|
+
chrome_devtools_evaluate: {
|
|
54
|
+
label: "Run JavaScript",
|
|
55
|
+
description: "Evaluate JavaScript in the selected page (chrome_devtools_evaluate).",
|
|
56
|
+
},
|
|
57
|
+
chrome_devtools_screenshot: {
|
|
58
|
+
label: "Capture a screenshot",
|
|
59
|
+
description: "Save a PNG of the selected page (chrome_devtools_screenshot).",
|
|
60
|
+
},
|
|
61
|
+
chrome_devtools_webmcp_list_tools: {
|
|
62
|
+
label: "List page WebMCP tools · Experimental",
|
|
63
|
+
description: "Discover page-provided WebMCP capabilities (chrome_devtools_webmcp_list_tools).",
|
|
64
|
+
},
|
|
65
|
+
chrome_devtools_webmcp_call_tool: {
|
|
66
|
+
label: "Call a page WebMCP tool · Experimental",
|
|
67
|
+
description: "Invoke a listed page tool after confirmation (chrome_devtools_webmcp_call_tool).",
|
|
68
|
+
},
|
|
74
69
|
};
|
|
75
70
|
|
|
76
|
-
export async function showChromeDevtoolsMenu(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
sanitizeChromeDevtoolsDisplay(`Chrome DevTools menu failed: ${formatError(error)}`),
|
|
193
|
-
"error",
|
|
194
|
-
),
|
|
195
|
-
});
|
|
71
|
+
export async function showChromeDevtoolsMenu(pi: ExtensionAPI, ctx: CommandContext, generation: number) {
|
|
72
|
+
const snapshot = await loadSnapshotWithFeedback(pi, ctx, generation);
|
|
73
|
+
if (!snapshot) return;
|
|
74
|
+
const menuSignal = state.sessionController.signal;
|
|
75
|
+
const isCurrent = () => generation === state.sessionGeneration && !menuSignal.aborted;
|
|
76
|
+
const menu = defineMenu<MenuSnapshot, MainScreen, MainAction>({
|
|
77
|
+
start: "main",
|
|
78
|
+
screens: {
|
|
79
|
+
main: ({ state: current }) => ({
|
|
80
|
+
kind: "actions",
|
|
81
|
+
title: "Chrome DevTools",
|
|
82
|
+
lines: mainStateLines(current, ctx.sessionManager),
|
|
83
|
+
items: [
|
|
84
|
+
{
|
|
85
|
+
id: "tools",
|
|
86
|
+
label: "Choose available browser tools…",
|
|
87
|
+
description: "Choose which capabilities the loader may expose.",
|
|
88
|
+
disabled: Boolean(current.mutationBlockedReason),
|
|
89
|
+
disabledReason: current.mutationBlockedReason,
|
|
90
|
+
action: "tools",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "bulk",
|
|
94
|
+
label:
|
|
95
|
+
current.activeTools.length === availableCatalogNames(ctx.sessionManager).length
|
|
96
|
+
? "Make all browser tools unavailable…"
|
|
97
|
+
: "Make all browser tools available…",
|
|
98
|
+
description:
|
|
99
|
+
current.activeTools.length === availableCatalogNames(ctx.sessionManager).length
|
|
100
|
+
? `Preview 0 of ${availableCatalogNames(ctx.sessionManager).length}; other active tools stay enabled.`
|
|
101
|
+
: `Preview ${availableCatalogNames(ctx.sessionManager).length} of ${availableCatalogNames(ctx.sessionManager).length}; other active tools stay enabled.`,
|
|
102
|
+
disabled: Boolean(current.mutationBlockedReason),
|
|
103
|
+
disabledReason: current.mutationBlockedReason,
|
|
104
|
+
action: "bulk",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "browser-status",
|
|
108
|
+
label: "Browser status",
|
|
109
|
+
description: "Runtime, endpoint, and last launch attempt.",
|
|
110
|
+
to: "browserStatus",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: "settings",
|
|
114
|
+
label: "Browser settings",
|
|
115
|
+
description: "Edit endpoint, auto-launch, and browser executable settings.",
|
|
116
|
+
action: "settings",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "help",
|
|
120
|
+
label: "Help",
|
|
121
|
+
description: "Commands and usage.",
|
|
122
|
+
to: "help",
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
hint: "close",
|
|
126
|
+
}),
|
|
127
|
+
browserStatus: () => ({
|
|
128
|
+
kind: "review",
|
|
129
|
+
title: "Browser status",
|
|
130
|
+
content: buildBrowserStatusMessage(ctx.sessionManager),
|
|
131
|
+
format: { kind: "text" },
|
|
132
|
+
viewportSize: "adaptive",
|
|
133
|
+
hint: "back",
|
|
134
|
+
}),
|
|
135
|
+
help: () => ({
|
|
136
|
+
kind: "review",
|
|
137
|
+
title: "Chrome DevTools help",
|
|
138
|
+
content: buildCommandGuide(ctx.sessionManager),
|
|
139
|
+
format: { kind: "text" },
|
|
140
|
+
viewportSize: "adaptive",
|
|
141
|
+
hint: "back",
|
|
142
|
+
}),
|
|
143
|
+
},
|
|
144
|
+
actions: {
|
|
145
|
+
tools: async ({ state: current }) => {
|
|
146
|
+
const result = await showChromeDevtoolsToolWorkflow(pi, ctx, generation, {
|
|
147
|
+
snapshot: current,
|
|
148
|
+
});
|
|
149
|
+
if (result?.applied && isCurrent()) updateSnapshotAfterApply(current, result.selectedTools);
|
|
150
|
+
return result?.closeParent ? { kind: "close" } : { kind: "stay" };
|
|
151
|
+
},
|
|
152
|
+
bulk: async ({ state: current }) => {
|
|
153
|
+
const selectedTools =
|
|
154
|
+
current.activeTools.length === availableCatalogNames(ctx.sessionManager).length
|
|
155
|
+
? []
|
|
156
|
+
: availableCatalogNames(ctx.sessionManager);
|
|
157
|
+
const result = await showChromeDevtoolsToolWorkflow(pi, ctx, generation, {
|
|
158
|
+
snapshot: current,
|
|
159
|
+
initialDraft: selectedTools,
|
|
160
|
+
startAtReview: true,
|
|
161
|
+
});
|
|
162
|
+
if (result?.applied && isCurrent()) updateSnapshotAfterApply(current, result.selectedTools);
|
|
163
|
+
return result?.closeParent ? { kind: "close" } : { kind: "stay" };
|
|
164
|
+
},
|
|
165
|
+
settings: async ({ state: current }) => {
|
|
166
|
+
const { showChromeDevtoolsBrowserSettings } = await import("./browser-settings-menu.js");
|
|
167
|
+
if (!isCurrent()) return { kind: "stay" };
|
|
168
|
+
const result = await showChromeDevtoolsBrowserSettings(pi, ctx, generation);
|
|
169
|
+
if (!isCurrent()) return { kind: "stay" };
|
|
170
|
+
const refreshed = await loadChromeDevtoolsMenuSnapshot(pi, ctx);
|
|
171
|
+
if (!isCurrent()) return { kind: "stay" };
|
|
172
|
+
Object.assign(current, refreshed);
|
|
173
|
+
return result.closeParent ? { kind: "close" } : { kind: "stay" };
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
await runMenu(ctx, menu, {
|
|
178
|
+
getState: () => snapshot,
|
|
179
|
+
signal: menuSignal,
|
|
180
|
+
isCurrent,
|
|
181
|
+
onError: (currentCtx, error) =>
|
|
182
|
+
currentCtx.ui.notify(
|
|
183
|
+
sanitizeChromeDevtoolsDisplay(`Chrome DevTools menu failed: ${formatError(error)}`),
|
|
184
|
+
"error",
|
|
185
|
+
),
|
|
186
|
+
});
|
|
196
187
|
}
|
|
197
188
|
|
|
198
189
|
export async function showChromeDevtoolsToolWorkflow(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
): Promise<
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
sanitizeChromeDevtoolsDisplay(
|
|
343
|
-
`Chrome DevTools tool selection failed: ${formatError(error)}`,
|
|
344
|
-
),
|
|
345
|
-
"error",
|
|
346
|
-
),
|
|
347
|
-
});
|
|
348
|
-
return {
|
|
349
|
-
applied: workflow.applied,
|
|
350
|
-
closeParent: !workflow.applied && result.kind === "closed" && result.reason === "close",
|
|
351
|
-
selectedTools: orderedTools(workflow.accepted),
|
|
352
|
-
};
|
|
190
|
+
pi: ExtensionAPI,
|
|
191
|
+
ctx: CommandContext,
|
|
192
|
+
generation: number,
|
|
193
|
+
options: {
|
|
194
|
+
snapshot?: MenuSnapshot;
|
|
195
|
+
initialDraft?: readonly ChromeDevToolsToolName[];
|
|
196
|
+
startAtReview?: boolean;
|
|
197
|
+
} = {},
|
|
198
|
+
): Promise<{ applied: boolean; closeParent: boolean; selectedTools: ChromeDevToolsToolName[] } | undefined> {
|
|
199
|
+
const snapshot = options.snapshot ?? (await loadSnapshotWithFeedback(pi, ctx, generation));
|
|
200
|
+
if (!snapshot) return undefined;
|
|
201
|
+
const menuSignal = state.sessionController.signal;
|
|
202
|
+
const isCurrent = () => generation === state.sessionGeneration && !menuSignal.aborted;
|
|
203
|
+
const accepted = new Set(snapshot.activeTools);
|
|
204
|
+
const workflow: ToolWorkflowState = {
|
|
205
|
+
accepted,
|
|
206
|
+
draft: new Set(options.initialDraft ?? snapshot.activeTools),
|
|
207
|
+
mutationBlockedReason: snapshot.mutationBlockedReason,
|
|
208
|
+
generation,
|
|
209
|
+
applied: false,
|
|
210
|
+
};
|
|
211
|
+
const menu = defineMenu<ToolWorkflowState, ToolScreen, ToolAction>({
|
|
212
|
+
start: options.startAtReview ? "review" : "tools",
|
|
213
|
+
screens: {
|
|
214
|
+
tools: ({ state: current }) => ({
|
|
215
|
+
kind: "multiSelect",
|
|
216
|
+
title: `Browser tools (${current.draft.size}/${availableCatalogNames(ctx.sessionManager).length})`,
|
|
217
|
+
lines: toolDraftLines(current, ctx.sessionManager),
|
|
218
|
+
items: availableCatalogNames(ctx.sessionManager).map((toolName) => ({
|
|
219
|
+
id: toolName,
|
|
220
|
+
label: TOOL_PRESENTATION[toolName].label,
|
|
221
|
+
description: TOOL_PRESENTATION[toolName].description,
|
|
222
|
+
searchText: `${toolName} ${TOOL_PRESENTATION[toolName].description}`,
|
|
223
|
+
selected: current.draft.has(toolName),
|
|
224
|
+
disabled: Boolean(current.mutationBlockedReason),
|
|
225
|
+
disabledReason: current.mutationBlockedReason,
|
|
226
|
+
})),
|
|
227
|
+
action: "toggle",
|
|
228
|
+
actions: [
|
|
229
|
+
{
|
|
230
|
+
id: "select-all",
|
|
231
|
+
label: "Select all",
|
|
232
|
+
disabled: Boolean(current.mutationBlockedReason),
|
|
233
|
+
disabledReason: current.mutationBlockedReason,
|
|
234
|
+
action: "selectAll",
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: "select-none",
|
|
238
|
+
label: "Select none",
|
|
239
|
+
disabled: Boolean(current.mutationBlockedReason),
|
|
240
|
+
disabledReason: current.mutationBlockedReason,
|
|
241
|
+
action: "selectNone",
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: "review",
|
|
245
|
+
label: "Review changes…",
|
|
246
|
+
disabled: Boolean(current.mutationBlockedReason) || setsEqual(current.accepted, current.draft),
|
|
247
|
+
disabledReason:
|
|
248
|
+
current.mutationBlockedReason ??
|
|
249
|
+
(setsEqual(current.accepted, current.draft) ? "No unapplied changes" : undefined),
|
|
250
|
+
to: "review",
|
|
251
|
+
},
|
|
252
|
+
{ id: "cancel", label: "Cancel", action: "cancel" },
|
|
253
|
+
],
|
|
254
|
+
hint: "back",
|
|
255
|
+
}),
|
|
256
|
+
review: ({ state: current }) => ({
|
|
257
|
+
kind: "review",
|
|
258
|
+
title: "Review tool changes",
|
|
259
|
+
lines: [
|
|
260
|
+
`Currently available: ${current.accepted.size}/${availableCatalogNames(ctx.sessionManager).length}`,
|
|
261
|
+
`Proposed availability: ${current.draft.size}/${availableCatalogNames(ctx.sessionManager).length}`,
|
|
262
|
+
],
|
|
263
|
+
content: buildToolReview(current, ctx.sessionManager),
|
|
264
|
+
format: { kind: "text" },
|
|
265
|
+
viewportSize: "adaptive",
|
|
266
|
+
...(current.mutationBlockedReason || setsEqual(current.accepted, current.draft)
|
|
267
|
+
? {}
|
|
268
|
+
: {
|
|
269
|
+
confirm: {
|
|
270
|
+
id: "apply",
|
|
271
|
+
label: "Apply tool changes",
|
|
272
|
+
action: "apply" as const,
|
|
273
|
+
},
|
|
274
|
+
}),
|
|
275
|
+
hint: "back",
|
|
276
|
+
}),
|
|
277
|
+
},
|
|
278
|
+
actions: {
|
|
279
|
+
toggle: ({ state: current, itemId, selected }) => {
|
|
280
|
+
if (!isChromeDevtoolsToolName(itemId) || selected === undefined) return { kind: "rejected" };
|
|
281
|
+
if (selected) current.draft.add(itemId);
|
|
282
|
+
else current.draft.delete(itemId);
|
|
283
|
+
return { kind: "stay" };
|
|
284
|
+
},
|
|
285
|
+
selectAll: ({ state: current }) => {
|
|
286
|
+
current.draft = new Set(availableCatalogNames(ctx.sessionManager));
|
|
287
|
+
return { kind: "stay" };
|
|
288
|
+
},
|
|
289
|
+
selectNone: ({ state: current }) => {
|
|
290
|
+
current.draft = new Set();
|
|
291
|
+
return { kind: "stay" };
|
|
292
|
+
},
|
|
293
|
+
review: () => ({ kind: "to", screen: "review" }),
|
|
294
|
+
cancel: () => ({ kind: "back" }),
|
|
295
|
+
apply: async ({ state: current, signal }) => {
|
|
296
|
+
await ctx.waitForIdle();
|
|
297
|
+
if (signal.aborted || !isCurrent() || current.generation !== state.sessionGeneration) {
|
|
298
|
+
return { kind: "rejected" };
|
|
299
|
+
}
|
|
300
|
+
const selectedTools = orderedTools(current.draft);
|
|
301
|
+
const saveResult = await setSelectedChromeDevtoolsTools(pi, ctx, selectedTools, orderedTools(current.accepted));
|
|
302
|
+
if (!isCurrent()) return { kind: "rejected" };
|
|
303
|
+
if (saveResult === "active-tools-changed") {
|
|
304
|
+
current.accepted = new Set(activeChromeTools(pi));
|
|
305
|
+
}
|
|
306
|
+
if (saveResult !== "saved") return { kind: "rejected" };
|
|
307
|
+
current.accepted = new Set(selectedTools);
|
|
308
|
+
current.draft = new Set(selectedTools);
|
|
309
|
+
current.applied = true;
|
|
310
|
+
ctx.ui.notify(
|
|
311
|
+
`Saved: ${selectedTools.length} of ${availableCatalogNames(ctx.sessionManager).length} browser tools available.`,
|
|
312
|
+
"info",
|
|
313
|
+
);
|
|
314
|
+
return { kind: "close" };
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
const result = await runMenu(ctx, menu, {
|
|
319
|
+
getState: () => workflow,
|
|
320
|
+
signal: menuSignal,
|
|
321
|
+
isCurrent,
|
|
322
|
+
onError: (currentCtx, error) =>
|
|
323
|
+
currentCtx.ui.notify(
|
|
324
|
+
sanitizeChromeDevtoolsDisplay(`Chrome DevTools tool selection failed: ${formatError(error)}`),
|
|
325
|
+
"error",
|
|
326
|
+
),
|
|
327
|
+
});
|
|
328
|
+
return {
|
|
329
|
+
applied: workflow.applied,
|
|
330
|
+
closeParent: !workflow.applied && result.kind === "closed" && result.reason === "close",
|
|
331
|
+
selectedTools: orderedTools(workflow.accepted),
|
|
332
|
+
};
|
|
353
333
|
}
|
|
354
334
|
|
|
355
335
|
async function loadSnapshotWithFeedback(pi: ExtensionAPI, ctx: CommandContext, generation: number) {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
336
|
+
const menuSignal = state.sessionController.signal;
|
|
337
|
+
const isCurrent = () => generation === state.sessionGeneration && !menuSignal.aborted;
|
|
338
|
+
const result = await runTask(ctx, {
|
|
339
|
+
label: "Loading Chrome DevTools settings…",
|
|
340
|
+
signal: menuSignal,
|
|
341
|
+
isCurrent,
|
|
342
|
+
task: async ({ signal }) => {
|
|
343
|
+
signal.throwIfAborted();
|
|
344
|
+
const settings = await loadSettings();
|
|
345
|
+
signal.throwIfAborted();
|
|
346
|
+
return buildMenuSnapshot(pi, settings);
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
return result.kind === "completed" ? result.value : undefined;
|
|
370
350
|
}
|
|
371
351
|
|
|
372
|
-
export async function loadChromeDevtoolsMenuSnapshot(
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
)
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
projectTrusted: ctx.isProjectTrusted(),
|
|
379
|
-
});
|
|
380
|
-
return buildMenuSnapshot(pi, settings);
|
|
352
|
+
export async function loadChromeDevtoolsMenuSnapshot(pi: ExtensionAPI, ctx: ExtensionCommandContext) {
|
|
353
|
+
const settings = await loadSettings({
|
|
354
|
+
cwd: ctx.cwd,
|
|
355
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
356
|
+
});
|
|
357
|
+
return buildMenuSnapshot(pi, settings);
|
|
381
358
|
}
|
|
382
359
|
|
|
383
360
|
function buildMenuSnapshot(pi: ExtensionAPI, settings: SettingsLoadResult): MenuSnapshot {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
persistenceLabel,
|
|
403
|
-
...(settings.kind === "invalid"
|
|
404
|
-
? { mutationBlockedReason: `Repair ${settings.reason} before saving` }
|
|
405
|
-
: {}),
|
|
406
|
-
...(settingsWarning ? { settingsWarning } : {}),
|
|
407
|
-
};
|
|
361
|
+
const activeTools = activeChromeTools(pi);
|
|
362
|
+
const persistedTools =
|
|
363
|
+
settings.kind === "loaded" && settings.settings.tools ? orderedTools(new Set(settings.settings.tools)) : undefined;
|
|
364
|
+
const persistenceLabel =
|
|
365
|
+
settings.kind === "invalid"
|
|
366
|
+
? "settings need repair"
|
|
367
|
+
: persistedTools === undefined
|
|
368
|
+
? "not saved"
|
|
369
|
+
: arraysEqual(activeTools, persistedTools)
|
|
370
|
+
? "saved"
|
|
371
|
+
: "runtime catalog differs from saved catalog";
|
|
372
|
+
const settingsWarning = [...new Set([settings.notice, state.settingsNotice].filter(Boolean))].join("\n");
|
|
373
|
+
return {
|
|
374
|
+
activeTools,
|
|
375
|
+
persistenceLabel,
|
|
376
|
+
...(settings.kind === "invalid" ? { mutationBlockedReason: `Repair ${settings.reason} before saving` } : {}),
|
|
377
|
+
...(settingsWarning ? { settingsWarning } : {}),
|
|
378
|
+
};
|
|
408
379
|
}
|
|
409
380
|
|
|
410
381
|
function mainStateLines(snapshot: MenuSnapshot, owner: object) {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
...(launchError ? [`Launch warning: ${launchError.slice("Last launch error: ".length)}`] : []),
|
|
421
|
-
]);
|
|
382
|
+
const launchError = launchAttemptLines(owner).find((line) => line.startsWith("Last launch error:"));
|
|
383
|
+
return sanitizeLines([
|
|
384
|
+
`Tool catalog: ${snapshot.activeTools.length} of ${availableCatalogNames(owner).length} available · ${snapshot.persistenceLabel}`,
|
|
385
|
+
`WebMCP: ${webMcpEnabled(owner) ? "enabled · experimental · confirmation required for every call" : "disabled · experimental"}`,
|
|
386
|
+
`Browser: ${browserLifecycleSummary(owner)}`,
|
|
387
|
+
`Endpoint: ${devToolsEndpoint(owner)}`,
|
|
388
|
+
...(snapshot.settingsWarning ? [`Settings warning: ${snapshot.settingsWarning}`] : []),
|
|
389
|
+
...(launchError ? [`Launch warning: ${launchError.slice("Last launch error: ".length)}`] : []),
|
|
390
|
+
]);
|
|
422
391
|
}
|
|
423
392
|
|
|
424
393
|
function browserLifecycleSummary(owner: object) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
394
|
+
const lifecycle = browserLifecycleState(owner);
|
|
395
|
+
if (lifecycle === "starting") return "starting managed browser";
|
|
396
|
+
if (lifecycle === "running") return "managed browser running";
|
|
397
|
+
if (lifecycle === "exited") return "managed browser exited · open Browser status to recover";
|
|
398
|
+
if (lifecycle === "failed") return "last launch failed · open Browser status to recover";
|
|
399
|
+
const launchMode = launchModeLabel(owner);
|
|
400
|
+
return launchMode.startsWith("attach first")
|
|
401
|
+
? "not started · attaches or launches on first use"
|
|
402
|
+
: `not started · ${launchMode}`;
|
|
434
403
|
}
|
|
435
404
|
|
|
436
405
|
function toolDraftLines(current: ToolWorkflowState, owner: object) {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
];
|
|
406
|
+
if (current.mutationBlockedReason) return sanitizeLines([`Unavailable: ${current.mutationBlockedReason}`]);
|
|
407
|
+
const changes = symmetricDifferenceSize(current.accepted, current.draft);
|
|
408
|
+
return [
|
|
409
|
+
`Currently available: ${current.accepted.size}/${availableCatalogNames(owner).length}`,
|
|
410
|
+
changes === 0
|
|
411
|
+
? "No unapplied changes · Escape cancels"
|
|
412
|
+
: `${changes} unapplied ${changes === 1 ? "change" : "changes"} · Escape cancels`,
|
|
413
|
+
`WebMCP gateways: ${webMcpEnabled(owner) ? "available for selection · experimental" : "hidden while experimental WebMCP is disabled"}`,
|
|
414
|
+
"Changes are not applied until Review changes and Apply tool changes.",
|
|
415
|
+
];
|
|
448
416
|
}
|
|
449
417
|
|
|
450
418
|
function buildToolReview(current: ToolWorkflowState, owner: object) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
419
|
+
const catalog = availableCatalogNames(owner);
|
|
420
|
+
const available = catalog.filter((name) => current.draft.has(name));
|
|
421
|
+
const unavailable = catalog.filter((name) => !current.draft.has(name));
|
|
422
|
+
return sanitizeChromeDevtoolsDisplay(
|
|
423
|
+
[
|
|
424
|
+
`Current available browser tools: ${current.accepted.size}/${catalog.length}`,
|
|
425
|
+
`Proposed available browser tools: ${current.draft.size}/${catalog.length}`,
|
|
426
|
+
"",
|
|
427
|
+
"Available after apply:",
|
|
428
|
+
...(available.length > 0
|
|
429
|
+
? available.map((name) => ` - ${TOOL_PRESENTATION[name].label} (${name})`)
|
|
430
|
+
: [" - none"]),
|
|
431
|
+
"",
|
|
432
|
+
"Unavailable after apply:",
|
|
433
|
+
...(unavailable.length > 0
|
|
434
|
+
? unavailable.map((name) => ` - ${TOOL_PRESENTATION[name].label} (${name})`)
|
|
435
|
+
: [" - none"]),
|
|
436
|
+
"",
|
|
437
|
+
"Other active Pi tools remain unchanged.",
|
|
438
|
+
"Native-capable models defer these tools until chrome_devtools_load selects them.",
|
|
439
|
+
"Other models expose available tools eagerly before the next model request.",
|
|
440
|
+
"The accepted availability policy is saved for future sessions.",
|
|
441
|
+
].join("\n"),
|
|
442
|
+
);
|
|
475
443
|
}
|
|
476
444
|
|
|
477
|
-
function updateSnapshotAfterApply(
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
) {
|
|
481
|
-
snapshot.activeTools = [...selectedTools];
|
|
482
|
-
snapshot.persistenceLabel = "saved";
|
|
445
|
+
function updateSnapshotAfterApply(snapshot: MenuSnapshot, selectedTools: readonly ChromeDevToolsToolName[]) {
|
|
446
|
+
snapshot.activeTools = [...selectedTools];
|
|
447
|
+
snapshot.persistenceLabel = "saved";
|
|
483
448
|
}
|
|
484
449
|
|
|
485
450
|
function activeChromeTools(pi: ExtensionAPI) {
|
|
486
|
-
|
|
451
|
+
return availableChromeDevtoolsTools(pi);
|
|
487
452
|
}
|
|
488
453
|
|
|
489
454
|
function orderedTools(tools: ReadonlySet<ChromeDevToolsToolName>) {
|
|
490
|
-
|
|
455
|
+
return CHROME_DEVTOOLS_TOOL_NAMES.filter((toolName) => tools.has(toolName));
|
|
491
456
|
}
|
|
492
457
|
|
|
493
458
|
function isChromeDevtoolsToolName(value: string): value is ChromeDevToolsToolName {
|
|
494
|
-
|
|
459
|
+
return CHROME_DEVTOOLS_TOOL_NAMES.includes(value as ChromeDevToolsToolName);
|
|
495
460
|
}
|
|
496
461
|
|
|
497
462
|
function setsEqual<T>(left: ReadonlySet<T>, right: ReadonlySet<T>) {
|
|
498
|
-
|
|
463
|
+
return left.size === right.size && [...left].every((value) => right.has(value));
|
|
499
464
|
}
|
|
500
465
|
|
|
501
466
|
function symmetricDifferenceSize<T>(left: ReadonlySet<T>, right: ReadonlySet<T>) {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
467
|
+
let changes = 0;
|
|
468
|
+
for (const value of left) if (!right.has(value)) changes += 1;
|
|
469
|
+
for (const value of right) if (!left.has(value)) changes += 1;
|
|
470
|
+
return changes;
|
|
506
471
|
}
|
|
507
472
|
|
|
508
473
|
function arraysEqual<T>(left: readonly T[], right: readonly T[]) {
|
|
509
|
-
|
|
474
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
510
475
|
}
|
|
511
476
|
|
|
512
477
|
function sanitizeLines(lines: readonly string[]) {
|
|
513
|
-
|
|
478
|
+
return sanitizeChromeDevtoolsDisplay(lines.join("\n")).split("\n");
|
|
514
479
|
}
|
|
515
480
|
|
|
516
481
|
function availableCatalogNames(owner: object): ChromeDevToolsToolName[] {
|
|
517
|
-
|
|
518
|
-
? [...CHROME_DEVTOOLS_TOOL_NAMES]
|
|
519
|
-
: [...CORE_CHROME_DEVTOOLS_TOOL_NAMES];
|
|
482
|
+
return webMcpEnabled(owner) ? [...CHROME_DEVTOOLS_TOOL_NAMES] : [...CORE_CHROME_DEVTOOLS_TOOL_NAMES];
|
|
520
483
|
}
|
|
521
484
|
|
|
522
485
|
function formatError(error: unknown) {
|
|
523
|
-
|
|
486
|
+
return error instanceof Error ? error.message : String(error);
|
|
524
487
|
}
|