@narumitw/pi-chrome-devtools 0.53.2 → 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.
Files changed (40) hide show
  1. package/README.md +1 -0
  2. package/dist/chunks/{browser-settings-menu-IMB6HXSU.js → browser-settings-menu-SR7KLZT6.js} +16 -42
  3. package/dist/chunks/browser-settings-menu-SR7KLZT6.js.map +7 -0
  4. package/dist/chunks/{chunk-GTTYS5GT.js → chunk-4KMN4SFF.js} +2 -2
  5. package/dist/chunks/chunk-4KMN4SFF.js.map +7 -0
  6. package/dist/chunks/{chunk-SRT3UUIP.js → chunk-F5VG35UI.js} +23 -74
  7. package/dist/chunks/chunk-F5VG35UI.js.map +7 -0
  8. package/dist/chunks/{chunk-LLXOCRNF.js → chunk-MZCGM6D3.js} +12 -33
  9. package/dist/chunks/chunk-MZCGM6D3.js.map +7 -0
  10. package/dist/chunks/{menu-UJETYMSB.js → menu-YR4VRT7D.js} +10 -23
  11. package/dist/chunks/menu-YR4VRT7D.js.map +7 -0
  12. package/dist/chunks/{tools-6P7EUX37.js → tools-6R7Z6HWJ.js} +17 -59
  13. package/dist/chunks/tools-6R7Z6HWJ.js.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
@@ -1,235 +1,231 @@
1
1
  import { getPage } from "../cdp-client.js";
2
2
  import { type DevToolsPage, webMcpOperationIsCurrent } from "../runtime.js";
3
3
  import {
4
- MAX_WEBMCP_TOOLS,
5
- normalizeWebMcpOutput,
6
- normalizeWebMcpTool,
7
- requireMatchingWebMcpTool,
8
- type WebMcpToolDescriptor,
9
- type WebMcpToolIdentity,
10
- webMcpErrorMessage,
4
+ MAX_WEBMCP_TOOLS,
5
+ normalizeWebMcpOutput,
6
+ normalizeWebMcpTool,
7
+ requireMatchingWebMcpTool,
8
+ type WebMcpToolDescriptor,
9
+ type WebMcpToolIdentity,
10
+ webMcpErrorMessage,
11
11
  } from "./policy.js";
12
12
  import {
13
- cancelWebMcpInvocation,
14
- connectWebMcpPage,
15
- disableWebMcp,
16
- enableWebMcp,
17
- enableWebMcpIdentityTracking,
18
- invokeWebMcpTool,
19
- readWebMcpFrames,
20
- requireWebMcpDomain,
21
- type WebMcpFrame,
22
- type WebMcpProtocolTool,
23
- watchWebMcpIdentity,
13
+ cancelWebMcpInvocation,
14
+ connectWebMcpPage,
15
+ disableWebMcp,
16
+ enableWebMcp,
17
+ enableWebMcpIdentityTracking,
18
+ invokeWebMcpTool,
19
+ readWebMcpFrames,
20
+ requireWebMcpDomain,
21
+ type WebMcpFrame,
22
+ type WebMcpProtocolTool,
23
+ watchWebMcpIdentity,
24
24
  } from "./protocol.js";
25
25
 
26
26
  export interface WebMcpOperationIdentity {
27
- owner: object;
28
- sessionGeneration: number;
29
- signal: AbortSignal;
30
- webMcpGeneration: number;
27
+ owner: object;
28
+ sessionGeneration: number;
29
+ signal: AbortSignal;
30
+ webMcpGeneration: number;
31
31
  }
32
32
 
33
33
  export interface WebMcpInvocationResult {
34
- invocationId: string;
35
- output: unknown;
36
- tool: WebMcpToolDescriptor;
34
+ invocationId: string;
35
+ output: unknown;
36
+ tool: WebMcpToolDescriptor;
37
37
  }
38
38
 
39
39
  export async function discoverWebMcpTools(page: DevToolsPage, operation: WebMcpOperationIdentity) {
40
- assertOperationCurrent(operation);
41
- await requireWebMcpDomain(operation.signal, operation.owner);
42
- assertOperationCurrent(operation);
43
- const client = await connectWebMcpPage(page, operation.signal);
44
- try {
45
- const tools = await readInventory(client, page, operation);
46
- await requireStablePage(client, page, tools, operation);
47
- return tools;
48
- } finally {
49
- await disableWebMcp(client);
50
- client.close();
51
- }
40
+ assertOperationCurrent(operation);
41
+ await requireWebMcpDomain(operation.signal, operation.owner);
42
+ assertOperationCurrent(operation);
43
+ const client = await connectWebMcpPage(page, operation.signal);
44
+ try {
45
+ const tools = await readInventory(client, page, operation);
46
+ await requireStablePage(client, page, tools, operation);
47
+ return tools;
48
+ } finally {
49
+ await disableWebMcp(client);
50
+ client.close();
51
+ }
52
52
  }
53
53
 
54
54
  export async function invokeDiscoveredWebMcpTool(
55
- page: DevToolsPage,
56
- expected: WebMcpToolIdentity,
57
- input: Record<string, unknown>,
58
- operation: WebMcpOperationIdentity,
55
+ page: DevToolsPage,
56
+ expected: WebMcpToolIdentity,
57
+ input: Record<string, unknown>,
58
+ operation: WebMcpOperationIdentity,
59
59
  ): Promise<WebMcpInvocationResult> {
60
- assertOperationCurrent(operation);
61
- await requireWebMcpDomain(operation.signal, operation.owner);
62
- assertOperationCurrent(operation);
63
- const client = await connectWebMcpPage(page, operation.signal);
64
- try {
65
- await enableWebMcpIdentityTracking(client, operation.signal);
66
- assertOperationCurrent(operation);
67
- const tools = await readInventory(client, page, operation);
68
- const tool = requireMatchingWebMcpTool(tools, expected);
69
- const identityWatch = watchWebMcpIdentity(
70
- client,
71
- { documentId: tool.documentId, frameId: tool.frameId, toolName: tool.name },
72
- operation.signal,
73
- );
74
- const guardedOperation = { ...operation, signal: identityWatch.signal };
75
- try {
76
- await requireStablePage(client, page, [tool], guardedOperation);
77
- assertOperationCurrent(guardedOperation);
78
- const response = await invokeWebMcpTool(
79
- client,
80
- { frameId: tool.frameId, toolName: tool.name, input },
81
- guardedOperation.signal,
82
- (invocationId) => cancelWebMcpInvocation(page, invocationId),
83
- );
84
- assertOperationCurrent(guardedOperation);
85
- if (response.status === "Canceled") {
86
- throw new DOMException("Chrome canceled the WebMCP tool invocation.", "AbortError");
87
- }
88
- if (response.status === "Error") {
89
- throw new Error(
90
- `WebMCP tool failed: ${webMcpErrorMessage(response.errorText ?? remoteExceptionText(response.exception))}`,
91
- );
92
- }
93
- return {
94
- invocationId: response.invocationId,
95
- output: normalizeWebMcpOutput(response.output ?? null),
96
- tool,
97
- };
98
- } finally {
99
- await identityWatch.dispose();
100
- }
101
- } finally {
102
- await disableWebMcp(client);
103
- client.close();
104
- }
60
+ assertOperationCurrent(operation);
61
+ await requireWebMcpDomain(operation.signal, operation.owner);
62
+ assertOperationCurrent(operation);
63
+ const client = await connectWebMcpPage(page, operation.signal);
64
+ try {
65
+ await enableWebMcpIdentityTracking(client, operation.signal);
66
+ assertOperationCurrent(operation);
67
+ const tools = await readInventory(client, page, operation);
68
+ const tool = requireMatchingWebMcpTool(tools, expected);
69
+ const identityWatch = watchWebMcpIdentity(
70
+ client,
71
+ { documentId: tool.documentId, frameId: tool.frameId, toolName: tool.name },
72
+ operation.signal,
73
+ );
74
+ const guardedOperation = { ...operation, signal: identityWatch.signal };
75
+ try {
76
+ await requireStablePage(client, page, [tool], guardedOperation);
77
+ assertOperationCurrent(guardedOperation);
78
+ const response = await invokeWebMcpTool(
79
+ client,
80
+ { frameId: tool.frameId, toolName: tool.name, input },
81
+ guardedOperation.signal,
82
+ (invocationId) => cancelWebMcpInvocation(page, invocationId),
83
+ );
84
+ assertOperationCurrent(guardedOperation);
85
+ if (response.status === "Canceled") {
86
+ throw new DOMException("Chrome canceled the WebMCP tool invocation.", "AbortError");
87
+ }
88
+ if (response.status === "Error") {
89
+ throw new Error(
90
+ `WebMCP tool failed: ${webMcpErrorMessage(response.errorText ?? remoteExceptionText(response.exception))}`,
91
+ );
92
+ }
93
+ return {
94
+ invocationId: response.invocationId,
95
+ output: normalizeWebMcpOutput(response.output ?? null),
96
+ tool,
97
+ };
98
+ } finally {
99
+ await identityWatch.dispose();
100
+ }
101
+ } finally {
102
+ await disableWebMcp(client);
103
+ client.close();
104
+ }
105
105
  }
106
106
 
107
107
  async function readInventory(
108
- client: Awaited<ReturnType<typeof connectWebMcpPage>>,
109
- page: DevToolsPage,
110
- operation: WebMcpOperationIdentity,
108
+ client: Awaited<ReturnType<typeof connectWebMcpPage>>,
109
+ page: DevToolsPage,
110
+ operation: WebMcpOperationIdentity,
111
111
  ) {
112
- const framesBefore = await readWebMcpFrames(client, operation.signal);
113
- assertOperationCurrent(operation);
114
- const protocolTools = await enableWebMcp(client, operation.signal);
115
- assertOperationCurrent(operation);
116
- if (protocolTools.length > MAX_WEBMCP_TOOLS) {
117
- throw new Error(`WebMCP page exposes more than ${MAX_WEBMCP_TOOLS} tools.`);
118
- }
119
- const framesAfter = await readWebMcpFrames(client, operation.signal);
120
- assertOperationCurrent(operation);
121
- requireStableInventoryDocuments(protocolTools, framesBefore, framesAfter);
122
- return normalizeInventory(protocolTools, framesAfter, page, operation);
112
+ const framesBefore = await readWebMcpFrames(client, operation.signal);
113
+ assertOperationCurrent(operation);
114
+ const protocolTools = await enableWebMcp(client, operation.signal);
115
+ assertOperationCurrent(operation);
116
+ if (protocolTools.length > MAX_WEBMCP_TOOLS) {
117
+ throw new Error(`WebMCP page exposes more than ${MAX_WEBMCP_TOOLS} tools.`);
118
+ }
119
+ const framesAfter = await readWebMcpFrames(client, operation.signal);
120
+ assertOperationCurrent(operation);
121
+ requireStableInventoryDocuments(protocolTools, framesBefore, framesAfter);
122
+ return normalizeInventory(protocolTools, framesAfter, page, operation);
123
123
  }
124
124
 
125
125
  function requireStableInventoryDocuments(
126
- tools: readonly WebMcpProtocolTool[],
127
- before: readonly WebMcpFrame[],
128
- after: readonly WebMcpFrame[],
126
+ tools: readonly WebMcpProtocolTool[],
127
+ before: readonly WebMcpFrame[],
128
+ after: readonly WebMcpFrame[],
129
129
  ) {
130
- const beforeLoaders = new Map(before.map((frame) => [frame.id, frame.loaderId]));
131
- const afterLoaders = new Map(after.map((frame) => [frame.id, frame.loaderId]));
132
- for (const frameId of new Set(tools.map((tool) => tool.frameId))) {
133
- const beforeLoader = beforeLoaders.get(frameId);
134
- const afterLoader = afterLoaders.get(frameId);
135
- if (!beforeLoader || !afterLoader || beforeLoader !== afterLoader) {
136
- throw new Error(
137
- "A WebMCP frame document changed while Chrome was publishing its tool inventory.",
138
- );
139
- }
140
- }
130
+ const beforeLoaders = new Map(before.map((frame) => [frame.id, frame.loaderId]));
131
+ const afterLoaders = new Map(after.map((frame) => [frame.id, frame.loaderId]));
132
+ for (const frameId of new Set(tools.map((tool) => tool.frameId))) {
133
+ const beforeLoader = beforeLoaders.get(frameId);
134
+ const afterLoader = afterLoaders.get(frameId);
135
+ if (!beforeLoader || !afterLoader || beforeLoader !== afterLoader) {
136
+ throw new Error("A WebMCP frame document changed while Chrome was publishing its tool inventory.");
137
+ }
138
+ }
141
139
  }
142
140
 
143
141
  function normalizeInventory(
144
- protocolTools: readonly WebMcpProtocolTool[],
145
- frames: readonly WebMcpFrame[],
146
- page: DevToolsPage,
147
- operation: WebMcpOperationIdentity,
142
+ protocolTools: readonly WebMcpProtocolTool[],
143
+ frames: readonly WebMcpFrame[],
144
+ page: DevToolsPage,
145
+ operation: WebMcpOperationIdentity,
148
146
  ) {
149
- const frameDocuments = new Map(
150
- frames.map((frame) => [frame.id, { documentId: frame.loaderId, origin: frameOrigin(frame) }]),
151
- );
152
- const tools = protocolTools.map((tool) => {
153
- const frame = frameDocuments.get(tool.frameId);
154
- if (!frame) {
155
- throw new Error(`WebMCP tool ${webMcpErrorMessage(tool.name)} belongs to an unknown frame.`);
156
- }
157
- return normalizeWebMcpTool(tool, {
158
- documentId: frame.documentId,
159
- frameOrigin: frame.origin,
160
- pageId: page.id,
161
- pageUrl: page.url,
162
- sessionGeneration: generationToken(operation),
163
- });
164
- });
165
- const identities = new Set<string>();
166
- for (const tool of tools) {
167
- const key = `${tool.frameId}\u0000${tool.name}`;
168
- if (identities.has(key)) {
169
- throw new Error(
170
- `WebMCP tool identity is duplicated: ${webMcpErrorMessage(tool.name)} in frame ${webMcpErrorMessage(tool.frameId)}.`,
171
- );
172
- }
173
- identities.add(key);
174
- }
175
- return tools;
147
+ const frameDocuments = new Map(
148
+ frames.map((frame) => [frame.id, { documentId: frame.loaderId, origin: frameOrigin(frame) }]),
149
+ );
150
+ const tools = protocolTools.map((tool) => {
151
+ const frame = frameDocuments.get(tool.frameId);
152
+ if (!frame) {
153
+ throw new Error(`WebMCP tool ${webMcpErrorMessage(tool.name)} belongs to an unknown frame.`);
154
+ }
155
+ return normalizeWebMcpTool(tool, {
156
+ documentId: frame.documentId,
157
+ frameOrigin: frame.origin,
158
+ pageId: page.id,
159
+ pageUrl: page.url,
160
+ sessionGeneration: generationToken(operation),
161
+ });
162
+ });
163
+ const identities = new Set<string>();
164
+ for (const tool of tools) {
165
+ const key = `${tool.frameId}\u0000${tool.name}`;
166
+ if (identities.has(key)) {
167
+ throw new Error(
168
+ `WebMCP tool identity is duplicated: ${webMcpErrorMessage(tool.name)} in frame ${webMcpErrorMessage(tool.frameId)}.`,
169
+ );
170
+ }
171
+ identities.add(key);
172
+ }
173
+ return tools;
176
174
  }
177
175
 
178
176
  async function requireStablePage(
179
- client: Awaited<ReturnType<typeof connectWebMcpPage>>,
180
- page: DevToolsPage,
181
- tools: readonly WebMcpToolDescriptor[],
182
- operation: WebMcpOperationIdentity,
177
+ client: Awaited<ReturnType<typeof connectWebMcpPage>>,
178
+ page: DevToolsPage,
179
+ tools: readonly WebMcpToolDescriptor[],
180
+ operation: WebMcpOperationIdentity,
183
181
  ) {
184
- const current = await getPage(page.id, {
185
- sessionOwner: operation.owner,
186
- signal: operation.signal,
187
- });
188
- assertOperationCurrent(operation);
189
- if (
190
- current.url !== page.url ||
191
- current.webSocketDebuggerUrl !== page.webSocketDebuggerUrl ||
192
- current.type !== page.type
193
- ) {
194
- throw new Error("The Chrome page navigated or was replaced during the WebMCP operation.");
195
- }
196
- const finalFrames = await readWebMcpFrames(client, operation.signal);
197
- assertOperationCurrent(operation);
198
- const finalDocuments = new Map(finalFrames.map((frame) => [frame.id, frame.loaderId]));
199
- for (const tool of tools) {
200
- if (finalDocuments.get(tool.frameId) !== tool.documentId) {
201
- throw new Error("A WebMCP frame document changed during the final page identity check.");
202
- }
203
- }
182
+ const current = await getPage(page.id, {
183
+ sessionOwner: operation.owner,
184
+ signal: operation.signal,
185
+ });
186
+ assertOperationCurrent(operation);
187
+ if (
188
+ current.url !== page.url ||
189
+ current.webSocketDebuggerUrl !== page.webSocketDebuggerUrl ||
190
+ current.type !== page.type
191
+ ) {
192
+ throw new Error("The Chrome page navigated or was replaced during the WebMCP operation.");
193
+ }
194
+ const finalFrames = await readWebMcpFrames(client, operation.signal);
195
+ assertOperationCurrent(operation);
196
+ const finalDocuments = new Map(finalFrames.map((frame) => [frame.id, frame.loaderId]));
197
+ for (const tool of tools) {
198
+ if (finalDocuments.get(tool.frameId) !== tool.documentId) {
199
+ throw new Error("A WebMCP frame document changed during the final page identity check.");
200
+ }
201
+ }
204
202
  }
205
203
 
206
204
  function assertOperationCurrent(operation: WebMcpOperationIdentity) {
207
- operation.signal.throwIfAborted();
208
- if (!webMcpOperationIsCurrent(operation)) {
209
- throw new DOMException("The WebMCP operation became stale.", "AbortError");
210
- }
205
+ operation.signal.throwIfAborted();
206
+ if (!webMcpOperationIsCurrent(operation)) {
207
+ throw new DOMException("The WebMCP operation became stale.", "AbortError");
208
+ }
211
209
  }
212
210
 
213
211
  function generationToken(operation: WebMcpOperationIdentity) {
214
- return `${operation.sessionGeneration}:${operation.webMcpGeneration}`;
212
+ return `${operation.sessionGeneration}:${operation.webMcpGeneration}`;
215
213
  }
216
214
 
217
215
  function frameOrigin(frame: WebMcpFrame) {
218
- if (frame.securityOrigin && frame.securityOrigin !== "://") return frame.securityOrigin;
219
- try {
220
- return new URL(frame.url).origin;
221
- } catch {
222
- throw new Error(
223
- `Chrome returned an invalid WebMCP frame URL: ${webMcpErrorMessage(frame.url)}`,
224
- );
225
- }
216
+ if (frame.securityOrigin && frame.securityOrigin !== "://") return frame.securityOrigin;
217
+ try {
218
+ return new URL(frame.url).origin;
219
+ } catch {
220
+ throw new Error(`Chrome returned an invalid WebMCP frame URL: ${webMcpErrorMessage(frame.url)}`);
221
+ }
226
222
  }
227
223
 
228
224
  function remoteExceptionText(value: unknown) {
229
- if (typeof value === "object" && value !== null) {
230
- const exception = value as { description?: unknown; value?: unknown };
231
- if (typeof exception.description === "string") return exception.description;
232
- if (typeof exception.value === "string") return exception.value;
233
- }
234
- return "The page reported an unknown tool error.";
225
+ if (typeof value === "object" && value !== null) {
226
+ const exception = value as { description?: unknown; value?: unknown };
227
+ if (typeof exception.description === "string") return exception.description;
228
+ if (typeof exception.value === "string") return exception.value;
229
+ }
230
+ return "The page reported an unknown tool error.";
235
231
  }