@narumitw/pi-chrome-devtools 0.52.3 → 0.53.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.
Files changed (35) hide show
  1. package/README.md +62 -9
  2. package/dist/chunks/{browser-settings-menu-VWCNG4HB.js → browser-settings-menu-N5O65I7S.js} +81 -7
  3. package/dist/chunks/browser-settings-menu-N5O65I7S.js.map +7 -0
  4. package/dist/chunks/chunk-B7B63PWB.js +465 -0
  5. package/dist/chunks/chunk-B7B63PWB.js.map +7 -0
  6. package/dist/chunks/{chunk-RH6KESL3.js → chunk-GVBXT3FB.js} +369 -514
  7. package/dist/chunks/chunk-GVBXT3FB.js.map +7 -0
  8. package/dist/chunks/chunk-RTECHAEZ.js +373 -0
  9. package/dist/chunks/chunk-RTECHAEZ.js.map +7 -0
  10. package/dist/chunks/{menu-PLD3S3EO.js → menu-UBHW6UP2.js} +72 -39
  11. package/dist/chunks/menu-UBHW6UP2.js.map +7 -0
  12. package/dist/chunks/tools-V7OR5KJ3.js +945 -0
  13. package/dist/chunks/tools-V7OR5KJ3.js.map +7 -0
  14. package/dist/index.ts +158 -195
  15. package/dist/index.ts.map +4 -4
  16. package/package.json +4 -3
  17. package/src/browser-manager.ts +282 -109
  18. package/src/browser-settings-menu.ts +98 -4
  19. package/src/cdp-client.ts +329 -83
  20. package/src/chrome-devtools.ts +51 -11
  21. package/src/lazy-tools.ts +37 -4
  22. package/src/menu.ts +80 -39
  23. package/src/render.ts +12 -1
  24. package/src/runtime.ts +92 -0
  25. package/src/settings.ts +47 -10
  26. package/src/tool-names.ts +16 -1
  27. package/src/tool-selector.ts +67 -30
  28. package/src/tools.ts +86 -10
  29. package/src/webmcp/discovery.ts +235 -0
  30. package/src/webmcp/policy.ts +402 -0
  31. package/src/webmcp/protocol.ts +414 -0
  32. package/src/webmcp/tools.ts +190 -0
  33. package/dist/chunks/browser-settings-menu-VWCNG4HB.js.map +0 -7
  34. package/dist/chunks/chunk-RH6KESL3.js.map +0 -7
  35. package/dist/chunks/menu-PLD3S3EO.js.map +0 -7
@@ -0,0 +1,414 @@
1
+ import { fetchDevToolsJson } from "../browser-manager.js";
2
+ import { CdpClient } from "../cdp-client.js";
3
+ import { DEFAULT_TIMEOUT_MS, type DevToolsPage } from "../runtime.js";
4
+
5
+ export interface WebMcpProtocolAnnotation {
6
+ autosubmit?: boolean;
7
+ consequential?: boolean;
8
+ readOnly?: boolean;
9
+ untrustedContent?: boolean;
10
+ }
11
+
12
+ export interface WebMcpProtocolTool {
13
+ annotations?: WebMcpProtocolAnnotation;
14
+ backendNodeId?: number;
15
+ description: string;
16
+ frameId: string;
17
+ inputSchema?: Record<string, unknown>;
18
+ name: string;
19
+ title?: string;
20
+ }
21
+
22
+ export interface WebMcpFrame {
23
+ id: string;
24
+ loaderId: string;
25
+ securityOrigin?: string;
26
+ url: string;
27
+ }
28
+
29
+ export interface WebMcpInvocationResponse {
30
+ errorText?: string;
31
+ exception?: unknown;
32
+ invocationId: string;
33
+ output?: unknown;
34
+ status: "Canceled" | "Completed" | "Error";
35
+ }
36
+
37
+ export interface WebMcpIdentityWatch {
38
+ dispose(): Promise<void>;
39
+ signal: AbortSignal;
40
+ }
41
+
42
+ interface ProtocolDescription {
43
+ domains?: unknown;
44
+ }
45
+
46
+ const REQUIRED_COMMANDS = ["enable", "disable", "invokeTool", "cancelInvocation"];
47
+ const REQUIRED_EVENTS = ["toolsAdded", "toolsRemoved", "toolInvoked", "toolResponded"];
48
+ const IDENTITY_WATCH_TIMEOUT_MS = DEFAULT_TIMEOUT_MS * 4;
49
+
50
+ export async function requireWebMcpDomain(signal: AbortSignal, owner: object) {
51
+ signal.throwIfAborted();
52
+ const protocol = await fetchDevToolsJson<ProtocolDescription>(
53
+ "/json/protocol",
54
+ { signal },
55
+ owner,
56
+ );
57
+ signal.throwIfAborted();
58
+ const domain = findDomain(protocol.domains, "WebMCP");
59
+ if (
60
+ !domain ||
61
+ !hasNamedEntries(domain.commands, REQUIRED_COMMANDS) ||
62
+ !hasNamedEntries(domain.events, REQUIRED_EVENTS)
63
+ ) {
64
+ throw new Error(
65
+ "This browser does not expose the experimental Chrome DevTools WebMCP domain. Use a compatible Chrome build and enable the website's required Origin Trial or WebMCP testing feature.",
66
+ );
67
+ }
68
+ }
69
+
70
+ export async function connectWebMcpPage(page: DevToolsPage, signal: AbortSignal) {
71
+ if (!page.webSocketDebuggerUrl) throw new Error(`Page has no webSocketDebuggerUrl: ${page.id}`);
72
+ return CdpClient.connect(page.webSocketDebuggerUrl, {
73
+ signal: withDeadline(signal),
74
+ });
75
+ }
76
+
77
+ export async function cancelWebMcpInvocation(page: DevToolsPage, invocationId: string) {
78
+ const cleanupSignal = AbortSignal.timeout(1_000);
79
+ const client = await connectWebMcpPage(page, cleanupSignal);
80
+ try {
81
+ await client.send("WebMCP.cancelInvocation", { invocationId }, { signal: cleanupSignal });
82
+ } finally {
83
+ client.close();
84
+ }
85
+ }
86
+
87
+ export async function enableWebMcpIdentityTracking(client: CdpClient, signal: AbortSignal) {
88
+ await client.send("Page.enable", {}, { signal, timeoutMs: DEFAULT_TIMEOUT_MS });
89
+ }
90
+
91
+ export async function enableWebMcp(client: CdpClient, signal: AbortSignal) {
92
+ const eventController = new AbortController();
93
+ const eventSignal = AbortSignal.any([signal, eventController.signal]);
94
+ const toolsAdded = client
95
+ .waitForEvent(
96
+ "WebMCP.toolsAdded",
97
+ (value): value is { tools: WebMcpProtocolTool[] } => {
98
+ const parsed = parseToolsAdded(value);
99
+ if (!parsed) throw new Error("Chrome sent a malformed WebMCP.toolsAdded event");
100
+ return true;
101
+ },
102
+ { signal: eventSignal, timeoutMs: DEFAULT_TIMEOUT_MS },
103
+ )
104
+ .then(
105
+ (value) => ({ ok: true as const, value }),
106
+ (error: unknown) => ({ error, ok: false as const }),
107
+ );
108
+ try {
109
+ await client.send("WebMCP.enable", {}, { signal, timeoutMs: DEFAULT_TIMEOUT_MS });
110
+ } catch (error) {
111
+ eventController.abort();
112
+ await toolsAdded;
113
+ throw error;
114
+ }
115
+ try {
116
+ const result = await toolsAdded;
117
+ if (!result.ok) throw result.error;
118
+ const parsed = parseToolsAdded(result.value);
119
+ if (!parsed) throw new Error("Chrome sent a malformed WebMCP.toolsAdded event");
120
+ return parsed.tools;
121
+ } finally {
122
+ eventController.abort();
123
+ }
124
+ }
125
+
126
+ export async function disableWebMcp(client: CdpClient) {
127
+ try {
128
+ await client.send("WebMCP.disable", {}, { timeoutMs: 1_000 });
129
+ } catch {
130
+ // Best-effort cleanup: the target may already be detached or the domain may be unavailable.
131
+ }
132
+ }
133
+
134
+ export async function readWebMcpFrames(client: CdpClient, signal: AbortSignal) {
135
+ const result = await client.send<unknown>(
136
+ "Page.getFrameTree",
137
+ {},
138
+ {
139
+ signal,
140
+ timeoutMs: DEFAULT_TIMEOUT_MS,
141
+ },
142
+ );
143
+ const frames = parseFrameTree(result);
144
+ if (!frames) throw new Error("Chrome sent a malformed Page.getFrameTree response");
145
+ return frames;
146
+ }
147
+
148
+ export function watchWebMcpIdentity(
149
+ client: CdpClient,
150
+ expected: { documentId: string; frameId: string; toolName: string },
151
+ signal: AbortSignal,
152
+ ): WebMcpIdentityWatch {
153
+ const stopController = new AbortController();
154
+ const staleController = new AbortController();
155
+ const watchSignal = AbortSignal.any([signal, stopController.signal]);
156
+ const options = { signal: watchSignal, timeoutMs: IDENTITY_WATCH_TIMEOUT_MS };
157
+ const watches = [
158
+ client.waitForEvent(
159
+ "Page.frameNavigated",
160
+ (value): value is unknown =>
161
+ frameNavigationChangesIdentity(value, expected.frameId, expected.documentId),
162
+ options,
163
+ ),
164
+ client.waitForEvent(
165
+ "WebMCP.toolsAdded",
166
+ (value): value is unknown => toolChangeMatchesIdentity(value, expected),
167
+ options,
168
+ ),
169
+ client.waitForEvent(
170
+ "WebMCP.toolsRemoved",
171
+ (value): value is unknown => toolChangeMatchesIdentity(value, expected),
172
+ options,
173
+ ),
174
+ ];
175
+ const settledWatches = watches.map((watch) =>
176
+ watch.then(
177
+ () => {
178
+ if (staleController.signal.aborted) return;
179
+ staleController.abort(
180
+ new DOMException(
181
+ "The selected WebMCP document or tool changed before Chrome completed the invocation boundary.",
182
+ "AbortError",
183
+ ),
184
+ );
185
+ },
186
+ (error) => {
187
+ if (stopController.signal.aborted || signal.aborted || staleController.signal.aborted) {
188
+ return;
189
+ }
190
+ staleController.abort(error);
191
+ },
192
+ ),
193
+ );
194
+ return {
195
+ signal: AbortSignal.any([signal, staleController.signal]),
196
+ async dispose() {
197
+ stopController.abort();
198
+ await Promise.allSettled(settledWatches);
199
+ },
200
+ };
201
+ }
202
+
203
+ export async function invokeWebMcpTool(
204
+ client: CdpClient,
205
+ request: { frameId: string; input: Record<string, unknown>; toolName: string },
206
+ signal: AbortSignal,
207
+ fallbackCancel?: (invocationId: string) => Promise<unknown>,
208
+ ): Promise<WebMcpInvocationResponse> {
209
+ signal.throwIfAborted();
210
+ let invocationId: string | undefined;
211
+ let cancellation: Promise<unknown> | undefined;
212
+ const cancel = () => {
213
+ if (!invocationId || cancellation) return;
214
+ const invocationToCancel = invocationId;
215
+ const recoverCancellation = () =>
216
+ fallbackCancel
217
+ ? fallbackCancel(invocationToCancel).catch(() => undefined)
218
+ : Promise.resolve();
219
+ try {
220
+ cancellation = client
221
+ .send("WebMCP.cancelInvocation", { invocationId }, { timeoutMs: 1_000 })
222
+ .catch(recoverCancellation);
223
+ } catch {
224
+ cancellation = recoverCancellation();
225
+ }
226
+ };
227
+ const onAbort = () => cancel();
228
+ signal.addEventListener("abort", onAbort, { once: true });
229
+ try {
230
+ let response: unknown;
231
+ try {
232
+ response = await client.send<unknown>(
233
+ "WebMCP.invokeTool",
234
+ { frameId: request.frameId, toolName: request.toolName, input: request.input },
235
+ { timeoutMs: DEFAULT_TIMEOUT_MS },
236
+ );
237
+ } catch (error) {
238
+ if (signal.aborted) throw signal.reason;
239
+ throw error;
240
+ }
241
+ invocationId = parseInvocationId(response);
242
+ if (!invocationId) throw new Error("Chrome sent a malformed WebMCP.invokeTool response");
243
+ if (signal.aborted) cancel();
244
+ let event: WebMcpInvocationResponse;
245
+ try {
246
+ event = await client.waitForEvent(
247
+ "WebMCP.toolResponded",
248
+ (value): value is WebMcpInvocationResponse => {
249
+ const parsed = parseToolResponded(value);
250
+ if (!parsed) throw new Error("Chrome sent a malformed WebMCP.toolResponded event");
251
+ return parsed.invocationId === invocationId;
252
+ },
253
+ { timeoutMs: DEFAULT_TIMEOUT_MS },
254
+ );
255
+ } catch (error) {
256
+ cancel();
257
+ await cancellation;
258
+ if (signal.aborted) throw signal.reason;
259
+ throw error;
260
+ }
261
+ if (signal.aborted) throw signal.reason;
262
+ return event;
263
+ } finally {
264
+ signal.removeEventListener("abort", onAbort);
265
+ await cancellation;
266
+ }
267
+ }
268
+
269
+ function findDomain(value: unknown, name: string): Record<string, unknown> | undefined {
270
+ if (!Array.isArray(value)) return undefined;
271
+ return value.find(
272
+ (candidate): candidate is Record<string, unknown> =>
273
+ isRecord(candidate) && candidate.domain === name,
274
+ );
275
+ }
276
+
277
+ function hasNamedEntries(value: unknown, required: readonly string[]) {
278
+ if (!Array.isArray(value)) return false;
279
+ const names = new Set(
280
+ value.flatMap((candidate) =>
281
+ isRecord(candidate) && typeof candidate.name === "string" ? [candidate.name] : [],
282
+ ),
283
+ );
284
+ return required.every((name) => names.has(name));
285
+ }
286
+
287
+ function parseToolsAdded(value: unknown): { tools: WebMcpProtocolTool[] } | undefined {
288
+ if (!isRecord(value) || !Array.isArray(value.tools)) return undefined;
289
+ const tools: WebMcpProtocolTool[] = [];
290
+ for (const candidate of value.tools) {
291
+ const tool = parseTool(candidate);
292
+ if (!tool) return undefined;
293
+ tools.push(tool);
294
+ }
295
+ return { tools };
296
+ }
297
+
298
+ function parseTool(value: unknown): WebMcpProtocolTool | undefined {
299
+ if (
300
+ !isRecord(value) ||
301
+ typeof value.name !== "string" ||
302
+ typeof value.description !== "string" ||
303
+ typeof value.frameId !== "string" ||
304
+ (value.inputSchema !== undefined && !isRecord(value.inputSchema)) ||
305
+ (value.title !== undefined && typeof value.title !== "string") ||
306
+ (value.backendNodeId !== undefined && typeof value.backendNodeId !== "number")
307
+ ) {
308
+ return undefined;
309
+ }
310
+ const annotations = parseAnnotations(value.annotations);
311
+ if (value.annotations !== undefined && !annotations) return undefined;
312
+ return {
313
+ name: value.name,
314
+ description: value.description,
315
+ frameId: value.frameId,
316
+ ...(value.title === undefined ? {} : { title: value.title }),
317
+ ...(value.inputSchema === undefined ? {} : { inputSchema: value.inputSchema }),
318
+ ...(annotations ? { annotations } : {}),
319
+ ...(value.backendNodeId === undefined ? {} : { backendNodeId: value.backendNodeId }),
320
+ };
321
+ }
322
+
323
+ function parseAnnotations(value: unknown): WebMcpProtocolAnnotation | undefined {
324
+ if (value === undefined) return undefined;
325
+ if (!isRecord(value)) return undefined;
326
+ const annotations: WebMcpProtocolAnnotation = {};
327
+ for (const key of ["autosubmit", "consequential", "readOnly", "untrustedContent"] as const) {
328
+ if (value[key] === undefined) continue;
329
+ if (typeof value[key] !== "boolean") return undefined;
330
+ annotations[key] = value[key];
331
+ }
332
+ return annotations;
333
+ }
334
+
335
+ function parseFrameTree(value: unknown): WebMcpFrame[] | undefined {
336
+ if (!isRecord(value) || !isRecord(value.frameTree)) return undefined;
337
+ const frames: WebMcpFrame[] = [];
338
+ const visit = (tree: unknown): boolean => {
339
+ if (!isRecord(tree) || !isRecord(tree.frame)) return false;
340
+ const frame = tree.frame;
341
+ if (
342
+ typeof frame.id !== "string" ||
343
+ typeof frame.loaderId !== "string" ||
344
+ typeof frame.url !== "string" ||
345
+ (frame.securityOrigin !== undefined && typeof frame.securityOrigin !== "string")
346
+ ) {
347
+ return false;
348
+ }
349
+ frames.push({
350
+ id: frame.id,
351
+ loaderId: frame.loaderId,
352
+ url: frame.url,
353
+ ...(frame.securityOrigin === undefined ? {} : { securityOrigin: frame.securityOrigin }),
354
+ });
355
+ if (tree.childFrames === undefined) return true;
356
+ return Array.isArray(tree.childFrames) && tree.childFrames.every(visit);
357
+ };
358
+ return visit(value.frameTree) ? frames : undefined;
359
+ }
360
+
361
+ function frameNavigationChangesIdentity(value: unknown, frameId: string, documentId: string) {
362
+ if (!isRecord(value) || !isRecord(value.frame)) {
363
+ throw new Error("Chrome sent a malformed Page.frameNavigated event");
364
+ }
365
+ if (typeof value.frame.id !== "string" || typeof value.frame.loaderId !== "string") {
366
+ throw new Error("Chrome sent a malformed Page.frameNavigated event");
367
+ }
368
+ return value.frame.id === frameId && value.frame.loaderId !== documentId;
369
+ }
370
+
371
+ function toolChangeMatchesIdentity(
372
+ value: unknown,
373
+ expected: { frameId: string; toolName: string },
374
+ ) {
375
+ if (!isRecord(value) || !Array.isArray(value.tools)) {
376
+ throw new Error("Chrome sent a malformed WebMCP tool-change event");
377
+ }
378
+ return value.tools.some((tool) => {
379
+ if (!isRecord(tool) || typeof tool.frameId !== "string" || typeof tool.name !== "string") {
380
+ throw new Error("Chrome sent a malformed WebMCP tool-change event");
381
+ }
382
+ return tool.frameId === expected.frameId && tool.name === expected.toolName;
383
+ });
384
+ }
385
+
386
+ function parseInvocationId(value: unknown) {
387
+ return isRecord(value) && typeof value.invocationId === "string" ? value.invocationId : undefined;
388
+ }
389
+
390
+ function parseToolResponded(value: unknown): WebMcpInvocationResponse | undefined {
391
+ if (
392
+ !isRecord(value) ||
393
+ typeof value.invocationId !== "string" ||
394
+ !(["Canceled", "Completed", "Error"] as const).includes(value.status as never) ||
395
+ (value.errorText !== undefined && typeof value.errorText !== "string")
396
+ ) {
397
+ return undefined;
398
+ }
399
+ return {
400
+ invocationId: value.invocationId,
401
+ status: value.status as WebMcpInvocationResponse["status"],
402
+ ...(value.output === undefined ? {} : { output: value.output }),
403
+ ...(value.errorText === undefined ? {} : { errorText: value.errorText }),
404
+ ...(value.exception === undefined ? {} : { exception: value.exception }),
405
+ };
406
+ }
407
+
408
+ function withDeadline(signal: AbortSignal) {
409
+ return AbortSignal.any([signal, AbortSignal.timeout(DEFAULT_TIMEOUT_MS)]);
410
+ }
411
+
412
+ function isRecord(value: unknown): value is Record<string, unknown> {
413
+ return typeof value === "object" && value !== null && !Array.isArray(value);
414
+ }
@@ -0,0 +1,190 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import { managedBrowserForOwner } from "../browser-manager.js";
3
+ import { getPage, resolvePage, textResult } from "../cdp-client.js";
4
+ import {
5
+ beginWebMcpOperation,
6
+ currentWebMcpGeneration,
7
+ webMcpEnabled,
8
+ webMcpOperationIsCurrent,
9
+ webMcpSessionSignal,
10
+ } from "../runtime.js";
11
+ import { discoverWebMcpTools, invokeDiscoveredWebMcpTool } from "./discovery.js";
12
+ import {
13
+ boundedWebMcpDiscovery,
14
+ boundedWebMcpJson,
15
+ normalizeWebMcpInput,
16
+ requireMatchingWebMcpTool,
17
+ sanitizeWebMcpDisplay,
18
+ validateWebMcpInput,
19
+ type WebMcpToolIdentity,
20
+ webMcpConfirmationMessage,
21
+ webMcpErrorMessage,
22
+ webMcpIdentity,
23
+ } from "./policy.js";
24
+
25
+ interface ListInput {
26
+ pageId?: string;
27
+ }
28
+
29
+ interface CallInput extends Omit<WebMcpToolIdentity, "name"> {
30
+ input: Record<string, unknown>;
31
+ toolName: string;
32
+ }
33
+
34
+ export async function executeWebMcpListTool(
35
+ params: ListInput,
36
+ toolSignal: AbortSignal | undefined,
37
+ ctx: ExtensionContext,
38
+ ) {
39
+ try {
40
+ requireWebMcpEnabled(ctx.sessionManager);
41
+ const preflightGeneration = currentWebMcpGeneration(ctx.sessionManager);
42
+ const preflightSignal = combinedSignal(ctx.sessionManager, toolSignal);
43
+ preflightSignal.throwIfAborted();
44
+ const page = await resolvePage(params.pageId, {
45
+ sessionOwner: ctx.sessionManager,
46
+ signal: preflightSignal,
47
+ });
48
+ preflightSignal.throwIfAborted();
49
+ requireCurrentPreflight(ctx.sessionManager, preflightGeneration);
50
+ const operation = beginWebMcpOperation(ctx.sessionManager, toolSignal);
51
+ try {
52
+ const tools = await discoverWebMcpTools(page, operation);
53
+ assertCurrent(operation);
54
+ const published = boundedWebMcpDiscovery(
55
+ { id: page.id, title: page.title, url: page.url },
56
+ tools,
57
+ );
58
+ return textResult(published.text, {
59
+ page: { id: page.id, url: page.url },
60
+ toolCount: tools.length,
61
+ publishedToolCount: published.included.length,
62
+ truncated: published.truncated,
63
+ identities: published.included.map(webMcpIdentity),
64
+ });
65
+ } finally {
66
+ operation.dispose();
67
+ }
68
+ } catch (error) {
69
+ throw safeToolError(error);
70
+ }
71
+ }
72
+
73
+ export async function executeWebMcpCallTool(
74
+ params: CallInput,
75
+ toolSignal: AbortSignal | undefined,
76
+ ctx: ExtensionContext,
77
+ ) {
78
+ try {
79
+ requireWebMcpEnabled(ctx.sessionManager);
80
+ if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) {
81
+ throw new Error(
82
+ "chrome_devtools_webmcp_call_tool requires observable confirmation in TUI or RPC mode and is unavailable in print or JSON mode.",
83
+ );
84
+ }
85
+ const input = normalizeWebMcpInput(params.input);
86
+ const expected = expectedIdentity(params);
87
+ const preflightGeneration = currentWebMcpGeneration(ctx.sessionManager);
88
+ const preflightSignal = combinedSignal(ctx.sessionManager, toolSignal);
89
+ preflightSignal.throwIfAborted();
90
+ const page = await getPage(params.pageId, {
91
+ sessionOwner: ctx.sessionManager,
92
+ signal: preflightSignal,
93
+ });
94
+ preflightSignal.throwIfAborted();
95
+ requireCurrentPreflight(ctx.sessionManager, preflightGeneration);
96
+ const operation = beginWebMcpOperation(ctx.sessionManager, toolSignal);
97
+ try {
98
+ const discovered = await discoverWebMcpTools(page, operation);
99
+ const current = requireMatchingWebMcpTool(discovered, expected);
100
+ const validatedInput = validateWebMcpInput(current.inputSchema, input);
101
+ assertCurrent(operation);
102
+ const confirmed = await ctx.ui.confirm(
103
+ `Allow WebMCP tool: ${sanitizeWebMcpDisplay(current.name, 256)}`,
104
+ webMcpConfirmationMessage(current, validatedInput, usesManagedProfile(ctx.sessionManager)),
105
+ { signal: operation.signal },
106
+ );
107
+ assertCurrent(operation);
108
+ if (!confirmed) {
109
+ throw new DOMException("WebMCP tool call cancelled by the user.", "AbortError");
110
+ }
111
+ const invocation = await invokeDiscoveredWebMcpTool(
112
+ page,
113
+ expected,
114
+ validatedInput,
115
+ operation,
116
+ );
117
+ assertCurrent(operation);
118
+ const published = boundedWebMcpJson({
119
+ status: "completed",
120
+ tool: webMcpIdentity(invocation.tool),
121
+ output: invocation.output,
122
+ });
123
+ return textResult(published.text, {
124
+ invocationId: invocation.invocationId,
125
+ tool: webMcpIdentity(invocation.tool),
126
+ truncated: published.truncated,
127
+ output: published.text,
128
+ });
129
+ } finally {
130
+ operation.dispose();
131
+ }
132
+ } catch (error) {
133
+ throw safeToolError(error);
134
+ }
135
+ }
136
+
137
+ function requireWebMcpEnabled(owner: object) {
138
+ if (!webMcpEnabled(owner)) {
139
+ throw new Error(
140
+ "WebMCP is disabled. Enable the experimental user setting in /chrome-devtools settings, then make the WebMCP gateway tools available.",
141
+ );
142
+ }
143
+ }
144
+
145
+ function combinedSignal(owner: object, toolSignal: AbortSignal | undefined) {
146
+ const sessionSignal = webMcpSessionSignal(owner);
147
+ return toolSignal ? AbortSignal.any([toolSignal, sessionSignal]) : sessionSignal;
148
+ }
149
+
150
+ function requireCurrentPreflight(owner: object, generation: number) {
151
+ requireWebMcpEnabled(owner);
152
+ if (generation !== currentWebMcpGeneration(owner)) {
153
+ throw new DOMException(
154
+ "The WebMCP operation became stale during page resolution.",
155
+ "AbortError",
156
+ );
157
+ }
158
+ }
159
+
160
+ function expectedIdentity(params: CallInput): WebMcpToolIdentity {
161
+ return {
162
+ sessionGeneration: params.sessionGeneration,
163
+ pageId: params.pageId,
164
+ documentId: params.documentId,
165
+ frameId: params.frameId,
166
+ frameOrigin: params.frameOrigin,
167
+ name: params.toolName,
168
+ schemaDigest: params.schemaDigest,
169
+ };
170
+ }
171
+
172
+ function assertCurrent(operation: ReturnType<typeof beginWebMcpOperation>) {
173
+ operation.signal.throwIfAborted();
174
+ if (!webMcpOperationIsCurrent(operation)) {
175
+ throw new DOMException("The WebMCP operation became stale.", "AbortError");
176
+ }
177
+ }
178
+
179
+ function usesManagedProfile(owner: object) {
180
+ const managedBrowser = managedBrowserForOwner(owner);
181
+ return Boolean(managedBrowser?.ready && !managedBrowser.exited);
182
+ }
183
+
184
+ function safeToolError(error: unknown) {
185
+ const message = webMcpErrorMessage(error);
186
+ if (error instanceof DOMException && error.name === "AbortError") {
187
+ return new DOMException(message, "AbortError");
188
+ }
189
+ return new Error(message);
190
+ }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/browser-settings-menu.ts"],
4
- "sourcesContent": ["import { isAbsolute } from \"node:path\";\nimport type { ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport { defineMenu, runMenu } from \"@narumitw/pi-tui-kit\";\nimport { shutdownManagedBrowser } from \"./browser-manager.js\";\nimport { applyRuntimeBrowserSettings, state } from \"./runtime.js\";\nimport {\n\ttype BrowserSettingsPatch,\n\ttype BrowserSettingsSource,\n\tloadSettings,\n\tparseBrowserEndpoint,\n\ttype SettingsLoadResult,\n\tsaveBrowserSettings,\n} from \"./settings.js\";\nimport { sanitizeChromeDevtoolsDisplay } from \"./tool-selector.js\";\n\ntype BrowserSettingsScreen = \"settings\" | \"endpoint\" | \"executable\" | \"extensions\";\ntype BrowserSettingsAction =\n\t| \"open-endpoint\"\n\t| \"save-endpoint\"\n\t| \"set-auto-launch\"\n\t| \"open-executable\"\n\t| \"save-executable\"\n\t| \"show-extensions\";\n\ninterface BrowserSettingsMenuState {\n\tload: SettingsLoadResult;\n}\n\ntype CommandContext = ExtensionCommandContext;\n\nexport async function showChromeDevtoolsBrowserSettings(\n\tctx: CommandContext,\n\tgeneration: number,\n): Promise<{ closeParent: boolean }> {\n\tconst ownerSignal = state.sessionController.signal;\n\tconst isCurrent = () => generation === state.sessionGeneration && !ownerSignal.aborted;\n\tconst projectTrusted = ctx.isProjectTrusted();\n\tconst menu = defineMenu<BrowserSettingsMenuState, BrowserSettingsScreen, BrowserSettingsAction>({\n\t\tstart: \"settings\",\n\t\tscreens: {\n\t\t\tsettings: ({ state: current }) => {\n\t\t\t\tif (current.load.userFile.kind === \"invalid\") return invalidSettingsScreen(current.load);\n\t\t\t\tconst browser = current.load.effectiveBrowser;\n\t\t\t\treturn {\n\t\t\t\t\tkind: \"settings\",\n\t\t\t\t\ttitle: \"Chrome DevTools Browser settings\",\n\t\t\t\t\tlines: settingsContextLines(current.load),\n\t\t\t\t\titems: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"endpoint\",\n\t\t\t\t\t\t\tlabel: \"DevTools endpoint\",\n\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t'HTTP origin with an explicit port; enter \"default\" to restore 127.0.0.1:9222.',\n\t\t\t\t\t\t\tcurrentValue: sanitizeChromeDevtoolsDisplay(browser.endpoint),\n\t\t\t\t\t\t\taction: \"open-endpoint\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"auto-launch\",\n\t\t\t\t\t\t\tlabel: \"Auto-launch\",\n\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\"Launch an isolated managed browser when a local endpoint is unavailable.\",\n\t\t\t\t\t\t\tcurrentValue: browser.autoLaunchEnabled ? \"On\" : \"Off\",\n\t\t\t\t\t\t\tvalues: [\"On\", \"Off\"],\n\t\t\t\t\t\t\taction: \"set-auto-launch\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"executable\",\n\t\t\t\t\t\t\tlabel: \"Browser executable\",\n\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t'Absolute executable path; enter \"automatic\" to restore browser discovery.',\n\t\t\t\t\t\t\tcurrentValue: sanitizeChromeDevtoolsDisplay(browser.executablePath ?? \"Automatic\"),\n\t\t\t\t\t\t\taction: \"open-executable\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"extensions\",\n\t\t\t\t\t\t\tlabel: \"Unpacked extensions\",\n\t\t\t\t\t\t\tdescription: \"Inspect trusted extension paths configured in user or project JSON.\",\n\t\t\t\t\t\t\tcurrentValue: `${browser.extensionPaths.length} configured`,\n\t\t\t\t\t\t\taction: \"show-extensions\",\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t};\n\t\t\t},\n\t\t\tendpoint: ({ state: current }) => ({\n\t\t\t\tkind: \"input\",\n\t\t\t\ttitle: \"DevTools endpoint\",\n\t\t\t\tlines: [\n\t\t\t\t\tsanitizeChromeDevtoolsDisplay(\n\t\t\t\t\t\t`Current effective endpoint: ${current.load.effectiveBrowser.endpoint}`,\n\t\t\t\t\t),\n\t\t\t\t\t'Enter an HTTP origin with an explicit port, or \"default\".',\n\t\t\t\t],\n\t\t\t\tplaceholder: \"http://127.0.0.1:9222\",\n\t\t\t\taction: \"save-endpoint\",\n\t\t\t\thint: \"back\",\n\t\t\t}),\n\t\t\texecutable: ({ state: current }) => ({\n\t\t\t\tkind: \"input\",\n\t\t\t\ttitle: \"Browser executable\",\n\t\t\t\tlines: [\n\t\t\t\t\tsanitizeChromeDevtoolsDisplay(\n\t\t\t\t\t\t`Current effective executable: ${current.load.effectiveBrowser.executablePath ?? \"automatic discovery\"}`,\n\t\t\t\t\t),\n\t\t\t\t\t'Enter an absolute path, or \"automatic\".',\n\t\t\t\t],\n\t\t\t\tplaceholder: \"absolute browser executable path\",\n\t\t\t\taction: \"save-executable\",\n\t\t\t\thint: \"back\",\n\t\t\t}),\n\t\t\textensions: ({ state: current }) => ({\n\t\t\t\tkind: \"detail\",\n\t\t\t\ttitle: \"Unpacked extensions\",\n\t\t\t\tlines: extensionDetailLines(current.load),\n\t\t\t\thint: \"back\",\n\t\t\t}),\n\t\t},\n\t\tactions: {\n\t\t\t\"open-endpoint\": () => ({ kind: \"to\", screen: \"endpoint\" }),\n\t\t\t\"save-endpoint\": async ({ value, signal }) => {\n\t\t\t\tif (value === undefined) return { kind: \"rejected\" };\n\t\t\t\tlet endpoint: string | null;\n\t\t\t\ttry {\n\t\t\t\t\tconst trimmed = value.trim();\n\t\t\t\t\tendpoint =\n\t\t\t\t\t\ttrimmed.toLowerCase() === \"default\" ? null : parseBrowserEndpoint(trimmed).endpoint;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tnotifySaveFailure(ctx, error);\n\t\t\t\t\treturn { kind: \"rejected\" };\n\t\t\t\t}\n\t\t\t\tconst saved = await saveAndApplyBrowserPatch(\n\t\t\t\t\tctx,\n\t\t\t\t\tgeneration,\n\t\t\t\t\townerSignal,\n\t\t\t\t\tsignal,\n\t\t\t\t\tprojectTrusted,\n\t\t\t\t\t{ endpoint },\n\t\t\t\t);\n\t\t\t\tif (!saved) return { kind: \"rejected\" };\n\t\t\t\tnotifyEffectiveSave(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"endpoint\",\n\t\t\t\t\tsaved.effectiveBrowser.endpoint,\n\t\t\t\t\tsaved.effectiveBrowser.endpointSource,\n\t\t\t\t);\n\t\t\t\treturn { kind: \"back\" };\n\t\t\t},\n\t\t\t\"set-auto-launch\": async ({ value, signal }) => {\n\t\t\t\tif (value !== \"On\" && value !== \"Off\") return { kind: \"rejected\" };\n\t\t\t\tconst saved = await saveAndApplyBrowserPatch(\n\t\t\t\t\tctx,\n\t\t\t\t\tgeneration,\n\t\t\t\t\townerSignal,\n\t\t\t\t\tsignal,\n\t\t\t\t\tprojectTrusted,\n\t\t\t\t\t{ autoLaunch: value === \"On\" },\n\t\t\t\t);\n\t\t\t\tif (!saved) return { kind: \"rejected\" };\n\t\t\t\tnotifyEffectiveSave(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"auto-launch\",\n\t\t\t\t\tsaved.effectiveBrowser.autoLaunchEnabled ? \"On\" : \"Off\",\n\t\t\t\t\tsaved.effectiveBrowser.autoLaunchSource,\n\t\t\t\t);\n\t\t\t\treturn { kind: \"stay\" };\n\t\t\t},\n\t\t\t\"open-executable\": () => ({ kind: \"to\", screen: \"executable\" }),\n\t\t\t\"save-executable\": async ({ value, signal }) => {\n\t\t\t\tif (value === undefined) return { kind: \"rejected\" };\n\t\t\t\tconst trimmed = value.trim();\n\t\t\t\tconst reset = [\"automatic\", \"default\", \"none\"].includes(trimmed.toLowerCase());\n\t\t\t\tif (!reset && (!trimmed || !isAbsolute(trimmed))) {\n\t\t\t\t\tnotifySaveFailure(ctx, new Error('Enter an absolute path or \"automatic\".'));\n\t\t\t\t\treturn { kind: \"rejected\" };\n\t\t\t\t}\n\t\t\t\tconst executablePath = reset ? null : trimmed;\n\t\t\t\tconst saved = await saveAndApplyBrowserPatch(\n\t\t\t\t\tctx,\n\t\t\t\t\tgeneration,\n\t\t\t\t\townerSignal,\n\t\t\t\t\tsignal,\n\t\t\t\t\tprojectTrusted,\n\t\t\t\t\t{ executablePath },\n\t\t\t\t);\n\t\t\t\tif (!saved) return { kind: \"rejected\" };\n\t\t\t\tnotifyEffectiveSave(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"browser executable\",\n\t\t\t\t\tsaved.effectiveBrowser.executablePath ?? \"automatic discovery\",\n\t\t\t\t\tsaved.effectiveBrowser.executablePathSource,\n\t\t\t\t);\n\t\t\t\treturn { kind: \"back\" };\n\t\t\t},\n\t\t\t\"show-extensions\": () => ({ kind: \"to\", screen: \"extensions\" }),\n\t\t},\n\t});\n\tconst result = await runMenu(ctx, menu, {\n\t\tgetState: async ({ signal }) => {\n\t\t\tsignal.throwIfAborted();\n\t\t\tconst load = await loadSettings({ cwd: ctx.cwd, projectTrusted });\n\t\t\tsignal.throwIfAborted();\n\t\t\treturn { load };\n\t\t},\n\t\tsignal: ownerSignal,\n\t\tisCurrent,\n\t\tonError: (currentCtx, error) =>\n\t\t\tcurrentCtx.ui.notify(\n\t\t\t\tsanitizeChromeDevtoolsDisplay(\n\t\t\t\t\t`Chrome DevTools browser settings failed: ${formatError(error)}`,\n\t\t\t\t),\n\t\t\t\t\"error\",\n\t\t\t),\n\t});\n\treturn { closeParent: result.kind === \"closed\" && result.reason === \"close\" };\n}\n\nasync function saveAndApplyBrowserPatch(\n\tctx: CommandContext,\n\tgeneration: number,\n\townerSignal: AbortSignal,\n\tactionSignal: AbortSignal,\n\tprojectTrusted: boolean,\n\tpatch: BrowserSettingsPatch,\n) {\n\ttry {\n\t\tawait ctx.waitForIdle();\n\t\tif (!isCurrent(generation, ownerSignal, actionSignal)) return false;\n\t\tawait saveBrowserSettings(patch);\n\t\tif (!isCurrent(generation, ownerSignal, actionSignal)) return false;\n\t\tawait shutdownManagedBrowser();\n\t\tif (!isCurrent(generation, ownerSignal, actionSignal)) return false;\n\t\tconst loaded = await loadSettings({ cwd: ctx.cwd, projectTrusted });\n\t\tif (!isCurrent(generation, ownerSignal, actionSignal)) return false;\n\t\tapplyRuntimeBrowserSettings(loaded.effectiveBrowser, loaded.paths, projectTrusted);\n\t\tstate.settingsNotice = loaded.notice;\n\t\treturn loaded;\n\t} catch (error) {\n\t\tif (isCurrent(generation, ownerSignal, actionSignal)) notifySaveFailure(ctx, error);\n\t\treturn false;\n\t}\n}\n\nfunction isCurrent(generation: number, ownerSignal: AbortSignal, actionSignal: AbortSignal) {\n\treturn generation === state.sessionGeneration && !ownerSignal.aborted && !actionSignal.aborted;\n}\n\nfunction invalidSettingsScreen(load: SettingsLoadResult) {\n\tconst reason =\n\t\tload.userFile.kind === \"invalid\"\n\t\t\t? load.userFile.reason\n\t\t\t: \"The user settings file is unavailable.\";\n\treturn {\n\t\tkind: \"detail\" as const,\n\t\ttitle: \"Chrome DevTools Browser settings \u00B7 Read only\",\n\t\tlines: [\n\t\t\tsanitizeChromeDevtoolsDisplay(\n\t\t\t\t`Fix the active user settings before saving; the file will not be overwritten. ${reason}`,\n\t\t\t),\n\t\t],\n\t\thint: \"back\" as const,\n\t};\n}\n\nfunction settingsContextLines(load: SettingsLoadResult) {\n\tconst browser = load.effectiveBrowser;\n\treturn [\n\t\t`User settings \u00B7 ${load.paths.user}`,\n\t\t`Effective sources: endpoint ${browser.endpointSource} \u00B7 auto-launch ${browser.autoLaunchSource} \u00B7 executable ${browser.executablePathSource}`,\n\t\t...(load.notice ? [`Warning: ${load.notice}`] : []),\n\t].map((line) => sanitizeChromeDevtoolsDisplay(line));\n}\n\nfunction extensionDetailLines(load: SettingsLoadResult) {\n\tconst browser = load.effectiveBrowser;\n\treturn [\n\t\t`Effective source: ${browser.extensionPathsSource}`,\n\t\t...(browser.extensionPaths.length > 0\n\t\t\t? browser.extensionPaths.map((extensionPath) => `- ${extensionPath}`)\n\t\t\t: [\"No unpacked extensions are configured.\"]),\n\t\t\"Edit browser.extensionPaths in the user JSON or a trusted project JSON, then run /reload.\",\n\t\t\"Unpacked extensions execute privileged browser code; load only paths you trust.\",\n\t].map((line) => sanitizeChromeDevtoolsDisplay(line));\n}\n\nfunction notifyEffectiveSave(\n\tctx: CommandContext,\n\tsetting: string,\n\teffectiveValue: string,\n\tsource: BrowserSettingsSource,\n) {\n\tconst overrideNotice =\n\t\tsource === \"environment\" ? \" The deprecated environment override remains effective.\" : \"\";\n\tctx.ui.notify(\n\t\tsanitizeChromeDevtoolsDisplay(\n\t\t\t`Chrome DevTools ${setting} saved. Effective ${setting}: ${effectiveValue} (${source}).${overrideNotice}`,\n\t\t),\n\t\t\"info\",\n\t);\n}\n\nfunction notifySaveFailure(ctx: CommandContext, error: unknown) {\n\tctx.ui.notify(\n\t\tsanitizeChromeDevtoolsDisplay(\n\t\t\t`Chrome DevTools browser settings save failed; previous settings remain active: ${formatError(error)}`,\n\t\t),\n\t\t\"warning\",\n\t);\n}\n\nfunction formatError(error: unknown) {\n\treturn error instanceof Error ? error.message : String(error);\n}\n"],
5
- "mappings": "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",
6
- "names": ["isCurrent"]
7
- }