@narumitw/pi-chrome-devtools 0.52.3 → 0.53.1
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 +62 -9
- package/dist/chunks/{browser-settings-menu-VWCNG4HB.js → browser-settings-menu-REIU7C3Q.js} +83 -7
- package/dist/chunks/browser-settings-menu-REIU7C3Q.js.map +7 -0
- package/dist/chunks/chunk-GTTYS5GT.js +384 -0
- package/dist/chunks/chunk-GTTYS5GT.js.map +7 -0
- package/dist/chunks/{chunk-RH6KESL3.js → chunk-SRT3UUIP.js} +473 -574
- package/dist/chunks/chunk-SRT3UUIP.js.map +7 -0
- package/dist/chunks/chunk-YJVYJ2L2.js +469 -0
- package/dist/chunks/chunk-YJVYJ2L2.js.map +7 -0
- package/dist/chunks/{menu-PLD3S3EO.js → menu-H5HMVGHI.js} +72 -39
- package/dist/chunks/menu-H5HMVGHI.js.map +7 -0
- package/dist/chunks/tools-6P7EUX37.js +964 -0
- package/dist/chunks/tools-6P7EUX37.js.map +7 -0
- package/dist/index.ts +166 -199
- package/dist/index.ts.map +4 -4
- package/package.json +4 -3
- package/src/browser-manager.ts +408 -169
- package/src/browser-settings-menu.ts +100 -5
- package/src/cdp-client.ts +347 -90
- package/src/chrome-devtools.ts +58 -11
- package/src/lazy-tools.ts +37 -4
- package/src/menu.ts +80 -39
- package/src/render.ts +12 -1
- package/src/runtime.ts +92 -0
- package/src/settings.ts +47 -10
- package/src/tool-names.ts +16 -1
- package/src/tool-selector.ts +85 -44
- package/src/tools.ts +91 -15
- package/src/webmcp/discovery.ts +235 -0
- package/src/webmcp/policy.ts +409 -0
- package/src/webmcp/protocol.ts +426 -0
- package/src/webmcp/tools.ts +190 -0
- package/dist/chunks/browser-settings-menu-VWCNG4HB.js.map +0 -7
- package/dist/chunks/chunk-RH6KESL3.js.map +0 -7
- package/dist/chunks/menu-PLD3S3EO.js.map +0 -7
package/src/settings.ts
CHANGED
|
@@ -61,6 +61,7 @@ export interface ResolvedChromeDevToolsSettings {
|
|
|
61
61
|
tools?: ChromeDevToolsToolName[];
|
|
62
62
|
updatedAt?: number;
|
|
63
63
|
browser: EffectiveBrowserSettings;
|
|
64
|
+
webmcp: { enabled: boolean };
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
export interface SettingsLoadOptions {
|
|
@@ -74,6 +75,7 @@ export type UserSettingsFileStatus =
|
|
|
74
75
|
|
|
75
76
|
interface SettingsLoadBase {
|
|
76
77
|
effectiveBrowser: EffectiveBrowserSettings;
|
|
78
|
+
effectiveWebMcpEnabled: boolean;
|
|
77
79
|
paths: { user: string; project?: string };
|
|
78
80
|
userFile: UserSettingsFileStatus;
|
|
79
81
|
warnings: string[];
|
|
@@ -94,6 +96,7 @@ interface NormalizedSettingsDocument {
|
|
|
94
96
|
tools?: ChromeDevToolsToolName[];
|
|
95
97
|
updatedAt?: number;
|
|
96
98
|
browser?: UserBrowserSettings;
|
|
99
|
+
webmcp?: { enabled: boolean };
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
interface SettingsDocumentResult {
|
|
@@ -165,7 +168,8 @@ export async function loadSettings(options: SettingsLoadOptions = {}): Promise<S
|
|
|
165
168
|
const recognized =
|
|
166
169
|
settings.tools !== undefined ||
|
|
167
170
|
user.normalized?.browser !== undefined ||
|
|
168
|
-
project.normalized?.browser !== undefined
|
|
171
|
+
project.normalized?.browser !== undefined ||
|
|
172
|
+
user.normalized?.webmcp !== undefined;
|
|
169
173
|
const invalidReasons = [user, project]
|
|
170
174
|
.filter((result) => result.kind === "invalid")
|
|
171
175
|
.map((result) => result.reason)
|
|
@@ -179,6 +183,7 @@ export async function loadSettings(options: SettingsLoadOptions = {}): Promise<S
|
|
|
179
183
|
: { kind: user.kind };
|
|
180
184
|
const base = {
|
|
181
185
|
effectiveBrowser,
|
|
186
|
+
effectiveWebMcpEnabled: settings.webmcp.enabled,
|
|
182
187
|
paths,
|
|
183
188
|
userFile,
|
|
184
189
|
warnings,
|
|
@@ -200,6 +205,7 @@ function resolveSettings(
|
|
|
200
205
|
return {
|
|
201
206
|
...(user?.tools ? { tools: user.tools, updatedAt: user.updatedAt } : {}),
|
|
202
207
|
browser,
|
|
208
|
+
webmcp: { enabled: user?.webmcp?.enabled ?? false },
|
|
203
209
|
};
|
|
204
210
|
}
|
|
205
211
|
|
|
@@ -293,7 +299,7 @@ async function readSettingsDocument(
|
|
|
293
299
|
return { kind: "invalid", reason, warnings: [`Chrome DevTools settings ignored: ${reason}`] };
|
|
294
300
|
}
|
|
295
301
|
|
|
296
|
-
const scopeWarnings =
|
|
302
|
+
const scopeWarnings = projectOwnedSettingsWarnings(parsed, scope, filePath);
|
|
297
303
|
try {
|
|
298
304
|
const normalized = await normalizeSettingsDocument(parsed, scope, filePath, cwd);
|
|
299
305
|
return { kind: "valid", document: { ...parsed }, normalized, warnings: scopeWarnings };
|
|
@@ -307,19 +313,27 @@ async function readSettingsDocument(
|
|
|
307
313
|
}
|
|
308
314
|
}
|
|
309
315
|
|
|
310
|
-
function
|
|
316
|
+
function projectOwnedSettingsWarnings(
|
|
311
317
|
document: Record<string, unknown>,
|
|
312
318
|
scope: "project" | "user",
|
|
313
319
|
filePath: string,
|
|
314
320
|
) {
|
|
315
|
-
if (scope !== "project"
|
|
316
|
-
const browser = document.browser;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
321
|
+
if (scope !== "project") return [];
|
|
322
|
+
const browser = isRecord(document.browser) ? document.browser : undefined;
|
|
323
|
+
const warnings = browser
|
|
324
|
+
? ["endpoint", "autoLaunch", "executablePath"]
|
|
325
|
+
.filter((field) => browser[field] !== undefined)
|
|
326
|
+
.map(
|
|
327
|
+
(field) =>
|
|
328
|
+
`Chrome DevTools project browser.${field} ignored in ${filePath}; configure this machine-owned setting in ${settingsFilePath()}.`,
|
|
329
|
+
)
|
|
330
|
+
: [];
|
|
331
|
+
if (document.webmcp !== undefined) {
|
|
332
|
+
warnings.push(
|
|
333
|
+
`Chrome DevTools project webmcp settings ignored in ${filePath}; only the user settings file may enable experimental WebMCP.`,
|
|
322
334
|
);
|
|
335
|
+
}
|
|
336
|
+
return warnings;
|
|
323
337
|
}
|
|
324
338
|
|
|
325
339
|
async function normalizeSettingsDocument(
|
|
@@ -346,6 +360,13 @@ async function normalizeSettingsDocument(
|
|
|
346
360
|
normalized.browser = await normalizeBrowserSection(document.browser, scope, filePath, cwd);
|
|
347
361
|
}
|
|
348
362
|
|
|
363
|
+
if (scope === "user" && document.webmcp !== undefined) {
|
|
364
|
+
if (!isRecord(document.webmcp) || typeof document.webmcp.enabled !== "boolean") {
|
|
365
|
+
throw new Error("expected webmcp.enabled to be a boolean");
|
|
366
|
+
}
|
|
367
|
+
normalized.webmcp = { enabled: document.webmcp.enabled };
|
|
368
|
+
}
|
|
369
|
+
|
|
349
370
|
return normalized;
|
|
350
371
|
}
|
|
351
372
|
|
|
@@ -526,6 +547,22 @@ export function saveSettings(
|
|
|
526
547
|
);
|
|
527
548
|
}
|
|
528
549
|
|
|
550
|
+
export function saveWebMcpSettings(
|
|
551
|
+
enabled: boolean,
|
|
552
|
+
operations: Partial<SettingsFileOperations> = {},
|
|
553
|
+
): Promise<void> {
|
|
554
|
+
return queueSettingsMutation(
|
|
555
|
+
(current) => ({
|
|
556
|
+
...current,
|
|
557
|
+
webmcp: {
|
|
558
|
+
...(isRecord(current.webmcp) ? current.webmcp : {}),
|
|
559
|
+
enabled,
|
|
560
|
+
},
|
|
561
|
+
}),
|
|
562
|
+
operations,
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
529
566
|
export function saveBrowserSettings(
|
|
530
567
|
patch: BrowserSettingsPatch,
|
|
531
568
|
operations: Partial<SettingsFileOperations> = {},
|
package/src/tool-names.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const
|
|
1
|
+
export const CORE_CHROME_DEVTOOLS_TOOL_NAMES = [
|
|
2
2
|
"chrome_devtools_list_pages",
|
|
3
3
|
"chrome_devtools_select_page",
|
|
4
4
|
"chrome_devtools_navigate",
|
|
@@ -6,4 +6,19 @@ export const CHROME_DEVTOOLS_TOOL_NAMES = [
|
|
|
6
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;
|
|
13
|
+
|
|
14
|
+
export const CHROME_DEVTOOLS_TOOL_NAMES = [
|
|
15
|
+
...CORE_CHROME_DEVTOOLS_TOOL_NAMES,
|
|
16
|
+
...WEBMCP_TOOL_NAMES,
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
9
19
|
export type ChromeDevToolsToolName = (typeof CHROME_DEVTOOLS_TOOL_NAMES)[number];
|
|
20
|
+
export type WebMcpToolName = (typeof WEBMCP_TOOL_NAMES)[number];
|
|
21
|
+
|
|
22
|
+
export function isWebMcpToolName(value: string): value is WebMcpToolName {
|
|
23
|
+
return WEBMCP_TOOL_NAMES.includes(value as WebMcpToolName);
|
|
24
|
+
}
|
package/src/tool-selector.ts
CHANGED
|
@@ -3,12 +3,14 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-c
|
|
|
3
3
|
import {
|
|
4
4
|
browserCandidateHint,
|
|
5
5
|
browserLifecycleState,
|
|
6
|
+
browserSettingsForOwner,
|
|
6
7
|
devToolsEndpoint,
|
|
7
8
|
endpointConfigHint,
|
|
8
9
|
endpointSourceLabel,
|
|
9
10
|
launchAttemptLines,
|
|
10
11
|
launchHint,
|
|
11
12
|
launchModeLabel,
|
|
13
|
+
managedBrowserForOwner,
|
|
12
14
|
} from "./browser-manager.js";
|
|
13
15
|
import {
|
|
14
16
|
applyAvailableChromeDevtoolsTools,
|
|
@@ -16,9 +18,14 @@ import {
|
|
|
16
18
|
CHROME_DEVTOOLS_LOAD_TOOL_NAME,
|
|
17
19
|
chromeDevtoolsToolExposureMode,
|
|
18
20
|
} from "./lazy-tools.js";
|
|
19
|
-
import { state } from "./runtime.js";
|
|
21
|
+
import { invalidateWebMcpOperations, state, webMcpEnabled } from "./runtime.js";
|
|
20
22
|
import { loadSettings, saveSettings, settingsFilePath } from "./settings.js";
|
|
21
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
CHROME_DEVTOOLS_TOOL_NAMES,
|
|
25
|
+
type ChromeDevToolsToolName,
|
|
26
|
+
CORE_CHROME_DEVTOOLS_TOOL_NAMES,
|
|
27
|
+
isWebMcpToolName,
|
|
28
|
+
} from "./tool-names.js";
|
|
22
29
|
|
|
23
30
|
type CommandContext = ExtensionCommandContext;
|
|
24
31
|
|
|
@@ -35,6 +42,7 @@ interface ToolStatusSummary {
|
|
|
35
42
|
availableChromeToolCount: number;
|
|
36
43
|
loadedChromeToolCount: number;
|
|
37
44
|
activeNonChromeToolCount: number;
|
|
45
|
+
capabilityCount: number;
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
type ToolSelectionSaveResult = "saved" | "active-tools-changed" | "failed";
|
|
@@ -48,7 +56,7 @@ export async function updateChromeDevtoolsTools(
|
|
|
48
56
|
const generation = state.sessionGeneration;
|
|
49
57
|
const result = await transactSelectedTools(pi, ctx, selectedTools, generation);
|
|
50
58
|
if (result !== "saved" || generation !== state.sessionGeneration) return;
|
|
51
|
-
const status = await buildToolStatusMessage(pi);
|
|
59
|
+
const status = await buildToolStatusMessage(pi, ctx.sessionManager);
|
|
52
60
|
if (generation !== state.sessionGeneration) return;
|
|
53
61
|
ctx.ui.notify(`Chrome DevTools tool catalog ${action}.\n\n${status}`, "info");
|
|
54
62
|
}
|
|
@@ -109,6 +117,14 @@ async function transactSelectedToolsNow(
|
|
|
109
117
|
const previousActiveTools = pi.getActiveTools();
|
|
110
118
|
const previousAvailableTools = availableChromeDevtoolsTools(pi);
|
|
111
119
|
try {
|
|
120
|
+
const previousWebMcpTools = previousAvailableTools.filter(isWebMcpToolName);
|
|
121
|
+
const selectedWebMcpTools = selectedTools.filter(isWebMcpToolName);
|
|
122
|
+
if (!arraysEqual(previousWebMcpTools, selectedWebMcpTools)) {
|
|
123
|
+
invalidateWebMcpOperations(
|
|
124
|
+
ctx.sessionManager,
|
|
125
|
+
"Chrome DevTools WebMCP gateway availability changed",
|
|
126
|
+
);
|
|
127
|
+
}
|
|
112
128
|
applyChromeDevtoolsTools(pi, selectedTools);
|
|
113
129
|
await persistSettings(selectedTools);
|
|
114
130
|
return expectedGeneration === state.sessionGeneration ? "saved" : "failed";
|
|
@@ -154,7 +170,7 @@ export function applyChromeDevtoolsTools(
|
|
|
154
170
|
applyAvailableChromeDevtoolsTools(pi, selectedTools);
|
|
155
171
|
}
|
|
156
172
|
|
|
157
|
-
function getToolStatusSummary(pi: ExtensionAPI): ToolStatusSummary {
|
|
173
|
+
function getToolStatusSummary(pi: ExtensionAPI, owner: object): ToolStatusSummary {
|
|
158
174
|
const chromeToolNames = new Set<string>(CHROME_DEVTOOLS_TOOL_NAMES);
|
|
159
175
|
const activeToolNames = new Set(pi.getActiveTools());
|
|
160
176
|
const loadedChromeToolCount = CHROME_DEVTOOLS_TOOL_NAMES.filter((name) =>
|
|
@@ -164,8 +180,9 @@ function getToolStatusSummary(pi: ExtensionAPI): ToolStatusSummary {
|
|
|
164
180
|
const activeNonChromeToolCount = Array.from(activeToolNames).filter(
|
|
165
181
|
(name) => !chromeToolNames.has(name) && name !== CHROME_DEVTOOLS_LOAD_TOOL_NAME,
|
|
166
182
|
).length;
|
|
183
|
+
const capabilityCount = effectiveCatalog(owner).length;
|
|
167
184
|
const availabilityStatus =
|
|
168
|
-
availableChromeToolCount ===
|
|
185
|
+
availableChromeToolCount === capabilityCount
|
|
169
186
|
? "enabled"
|
|
170
187
|
: availableChromeToolCount === 0
|
|
171
188
|
? "disabled"
|
|
@@ -176,36 +193,40 @@ function getToolStatusSummary(pi: ExtensionAPI): ToolStatusSummary {
|
|
|
176
193
|
availableChromeToolCount,
|
|
177
194
|
loadedChromeToolCount,
|
|
178
195
|
activeNonChromeToolCount,
|
|
196
|
+
capabilityCount,
|
|
179
197
|
};
|
|
180
198
|
}
|
|
181
199
|
|
|
182
|
-
export async function buildToolStatusMessage(pi: ExtensionAPI) {
|
|
183
|
-
const summary = getToolStatusSummary(pi);
|
|
200
|
+
export async function buildToolStatusMessage(pi: ExtensionAPI, owner: object) {
|
|
201
|
+
const summary = getToolStatusSummary(pi, owner);
|
|
184
202
|
const persistedSetting = await persistedSettingLabel();
|
|
185
203
|
return sanitizeChromeDevtoolsDisplay(
|
|
186
204
|
[
|
|
187
205
|
`Chrome DevTools tools available: ${formatRuntimeStatus(summary)}`,
|
|
188
206
|
`Tool exposure: ${chromeDevtoolsToolExposureMode(pi)}`,
|
|
189
|
-
`Loaded capability tools this session: ${summary.loadedChromeToolCount}/${
|
|
207
|
+
`Loaded capability tools this session: ${summary.loadedChromeToolCount}/${summary.capabilityCount}`,
|
|
208
|
+
`WebMCP: ${webMcpEnabled(owner) ? "enabled · experimental · confirmation required for every call" : "disabled · experimental"}`,
|
|
190
209
|
`Loader: ${pi.getActiveTools().includes(CHROME_DEVTOOLS_LOAD_TOOL_NAME) ? "active" : "inactive"}`,
|
|
191
210
|
`Persisted tool catalog: ${persistedSetting}`,
|
|
192
|
-
...browserSettingsStatusLines(),
|
|
211
|
+
...browserSettingsStatusLines(owner),
|
|
193
212
|
...(state.settingsNotice ? [`Settings note: ${state.settingsNotice}`] : []),
|
|
194
213
|
`Other active tools preserved: ${summary.activeNonChromeToolCount}`,
|
|
195
|
-
`Endpoint: ${devToolsEndpoint()}`,
|
|
196
|
-
`Endpoint source: ${endpointSourceLabel()}`,
|
|
197
|
-
`Launch mode: ${launchModeLabel()}`,
|
|
198
|
-
...launchAttemptLines(),
|
|
214
|
+
`Endpoint: ${devToolsEndpoint(owner)}`,
|
|
215
|
+
`Endpoint source: ${endpointSourceLabel(owner)}`,
|
|
216
|
+
`Launch mode: ${launchModeLabel(owner)}`,
|
|
217
|
+
...launchAttemptLines(owner),
|
|
199
218
|
].join("\n"),
|
|
200
219
|
);
|
|
201
220
|
}
|
|
202
221
|
|
|
203
|
-
export function buildQuickstartMessage() {
|
|
204
|
-
return buildSettingsSetupMessage();
|
|
222
|
+
export function buildQuickstartMessage(owner: object) {
|
|
223
|
+
return buildSettingsSetupMessage(owner);
|
|
205
224
|
}
|
|
206
225
|
|
|
207
|
-
export function buildBrowserStatusMessage() {
|
|
208
|
-
const
|
|
226
|
+
export function buildBrowserStatusMessage(owner?: object) {
|
|
227
|
+
const browser = browserSettingsForOwner(owner);
|
|
228
|
+
const lifecycle = browserLifecycleState(owner);
|
|
229
|
+
const webMcpIsEnabled = owner ? webMcpEnabled(owner) : false;
|
|
209
230
|
const browserState =
|
|
210
231
|
lifecycle === "starting"
|
|
211
232
|
? "starting managed browser"
|
|
@@ -221,36 +242,46 @@ export function buildBrowserStatusMessage() {
|
|
|
221
242
|
[
|
|
222
243
|
`Browser: ${browserState}`,
|
|
223
244
|
"Viewing this status does not probe the endpoint or launch Chrome.",
|
|
224
|
-
`Endpoint: ${devToolsEndpoint()}`,
|
|
225
|
-
`Endpoint source: ${endpointSourceLabel()}`,
|
|
226
|
-
`Launch mode: ${launchModeLabel()}`,
|
|
227
|
-
`Unpacked extensions: ${
|
|
228
|
-
|
|
245
|
+
`Endpoint: ${devToolsEndpoint(owner)}`,
|
|
246
|
+
`Endpoint source: ${endpointSourceLabel(owner)}`,
|
|
247
|
+
`Launch mode: ${launchModeLabel(owner)}`,
|
|
248
|
+
`Unpacked extensions: ${browser.extensionPaths.length} (${browser.extensionPathsSource})`,
|
|
249
|
+
`WebMCP: ${webMcpIsEnabled ? "enabled · experimental" : "disabled · experimental"}`,
|
|
250
|
+
...(webMcpIsEnabled && !managedBrowserForOwner(owner)?.ready
|
|
251
|
+
? [
|
|
252
|
+
"WebMCP warning: attached browser profiles may contain everyday authenticated sessions and sensitive state.",
|
|
253
|
+
]
|
|
254
|
+
: []),
|
|
255
|
+
...(browser.extensionPaths.length > 0
|
|
229
256
|
? ["Unpacked extensions execute trusted browser code in an isolated managed browser."]
|
|
230
257
|
: []),
|
|
231
|
-
...launchAttemptLines(),
|
|
232
|
-
...(needsRecovery ? [launchHint(), endpointConfigHint()] : []),
|
|
258
|
+
...launchAttemptLines(owner),
|
|
259
|
+
...(needsRecovery ? [launchHint(owner), endpointConfigHint()] : []),
|
|
233
260
|
].join("\n"),
|
|
234
261
|
);
|
|
235
262
|
}
|
|
236
263
|
|
|
237
|
-
export function buildSettingsSetupMessage() {
|
|
264
|
+
export function buildSettingsSetupMessage(owner: object) {
|
|
238
265
|
return sanitizeChromeDevtoolsDisplay(
|
|
239
266
|
[
|
|
240
|
-
`Chrome DevTools endpoint: ${devToolsEndpoint()}`,
|
|
241
|
-
`Endpoint source: ${endpointSourceLabel()}`,
|
|
242
|
-
`Launch mode: ${launchModeLabel()}`,
|
|
243
|
-
...browserSettingsStatusLines(),
|
|
244
|
-
launchHint(),
|
|
245
|
-
browserCandidateHint(),
|
|
246
|
-
...launchAttemptLines(),
|
|
267
|
+
`Chrome DevTools endpoint: ${devToolsEndpoint(owner)}`,
|
|
268
|
+
`Endpoint source: ${endpointSourceLabel(owner)}`,
|
|
269
|
+
`Launch mode: ${launchModeLabel(owner)}`,
|
|
270
|
+
...browserSettingsStatusLines(owner),
|
|
271
|
+
launchHint(owner),
|
|
272
|
+
browserCandidateHint(owner),
|
|
273
|
+
...launchAttemptLines(owner),
|
|
247
274
|
endpointConfigHint(),
|
|
248
275
|
].join("\n"),
|
|
249
276
|
);
|
|
250
277
|
}
|
|
251
278
|
|
|
252
279
|
export function sanitizeChromeDevtoolsDisplay(value: string, maxCharacters = 50_000) {
|
|
253
|
-
const
|
|
280
|
+
const withoutBidi = stripVTControlCharacters(value).replace(
|
|
281
|
+
/[\u202a-\u202e\u2066-\u2069]/gu,
|
|
282
|
+
"�",
|
|
283
|
+
);
|
|
284
|
+
const sanitized = Array.from(withoutBidi, (character) => {
|
|
254
285
|
const codePoint = character.codePointAt(0) ?? 0;
|
|
255
286
|
const unsafeControl =
|
|
256
287
|
(codePoint >= 0 && codePoint <= 8) ||
|
|
@@ -262,10 +293,12 @@ export function sanitizeChromeDevtoolsDisplay(value: string, maxCharacters = 50_
|
|
|
262
293
|
return `${sanitized.slice(0, Math.max(0, maxCharacters - 1))}…`;
|
|
263
294
|
}
|
|
264
295
|
|
|
265
|
-
function browserSettingsStatusLines() {
|
|
296
|
+
function browserSettingsStatusLines(owner: object) {
|
|
297
|
+
const browser = browserSettingsForOwner(owner);
|
|
298
|
+
const extensionPaths = browser.extensionPaths;
|
|
266
299
|
const extensionLines =
|
|
267
|
-
|
|
268
|
-
?
|
|
300
|
+
extensionPaths.length > 0
|
|
301
|
+
? extensionPaths.map((extensionPath) => ` - ${extensionPath}`)
|
|
269
302
|
: [" - none"];
|
|
270
303
|
return [
|
|
271
304
|
`Settings file: ${state.settingsFilePath ?? settingsFilePath()} (user)`,
|
|
@@ -274,12 +307,13 @@ function browserSettingsStatusLines() {
|
|
|
274
307
|
`Project settings: ${state.projectSettingsFilePath} (${state.projectSettingsTrusted ? "trusted" : "untrusted; ignored"})`,
|
|
275
308
|
]
|
|
276
309
|
: []),
|
|
277
|
-
`Auto-launch: ${
|
|
278
|
-
`Browser executable: ${
|
|
279
|
-
`Unpacked extensions (${
|
|
310
|
+
`Auto-launch: ${browser.autoLaunchEnabled ? "on" : "off"} (${browser.autoLaunchSource})`,
|
|
311
|
+
`Browser executable: ${browser.executablePath ?? "automatic discovery"} (${browser.executablePathSource})`,
|
|
312
|
+
`Unpacked extensions (${browser.extensionPathsSource}):`,
|
|
280
313
|
...extensionLines,
|
|
314
|
+
`WebMCP: ${webMcpEnabled(owner) ? "enabled · experimental · every call requires confirmation" : "disabled · experimental"}`,
|
|
281
315
|
"Confirmed menu settings apply before the next browser connection; manual JSON edits require /reload or session replacement.",
|
|
282
|
-
...(
|
|
316
|
+
...(extensionPaths.length > 0
|
|
283
317
|
? [
|
|
284
318
|
"Unpacked extensions require Chrome for Testing or Chromium and execute trusted browser code.",
|
|
285
319
|
]
|
|
@@ -287,9 +321,10 @@ function browserSettingsStatusLines() {
|
|
|
287
321
|
];
|
|
288
322
|
}
|
|
289
323
|
|
|
290
|
-
export function buildCommandGuide() {
|
|
324
|
+
export function buildCommandGuide(owner: object) {
|
|
291
325
|
return [
|
|
292
326
|
"Chrome DevTools commands:",
|
|
327
|
+
`WebMCP: ${webMcpEnabled(owner) ? "enabled" : "disabled"} · experimental · every page-provided call requires confirmation`,
|
|
293
328
|
"/chrome-devtools — open this menu",
|
|
294
329
|
"/chrome-devtools help — show command usage",
|
|
295
330
|
"/chrome-devtools quickstart — show endpoint and launch help",
|
|
@@ -302,8 +337,8 @@ export function buildCommandGuide() {
|
|
|
302
337
|
].join("\n");
|
|
303
338
|
}
|
|
304
339
|
|
|
305
|
-
export function allChromeDevtoolsTools() {
|
|
306
|
-
return
|
|
340
|
+
export function allChromeDevtoolsTools(owner: object) {
|
|
341
|
+
return effectiveCatalog(owner);
|
|
307
342
|
}
|
|
308
343
|
|
|
309
344
|
export function orderedChromeDevtoolsTools(selectedTools: ReadonlySet<ChromeDevToolsToolName>) {
|
|
@@ -311,7 +346,7 @@ export function orderedChromeDevtoolsTools(selectedTools: ReadonlySet<ChromeDevT
|
|
|
311
346
|
}
|
|
312
347
|
|
|
313
348
|
function formatRuntimeStatus(summary: ToolStatusSummary) {
|
|
314
|
-
return `${summary.availabilityStatus} (${summary.availableChromeToolCount}/${
|
|
349
|
+
return `${summary.availabilityStatus} (${summary.availableChromeToolCount}/${summary.capabilityCount} available)`;
|
|
315
350
|
}
|
|
316
351
|
|
|
317
352
|
async function persistedSettingLabel() {
|
|
@@ -334,6 +369,12 @@ function formatPersistedSelection(tools: readonly ChromeDevToolsToolName[]) {
|
|
|
334
369
|
return `${tools.length}/${CHROME_DEVTOOLS_TOOL_NAMES.length} selected: ${tools.join(", ")}`;
|
|
335
370
|
}
|
|
336
371
|
|
|
372
|
+
function effectiveCatalog(owner: object): ChromeDevToolsToolName[] {
|
|
373
|
+
return webMcpEnabled(owner)
|
|
374
|
+
? [...CHROME_DEVTOOLS_TOOL_NAMES]
|
|
375
|
+
: [...CORE_CHROME_DEVTOOLS_TOOL_NAMES];
|
|
376
|
+
}
|
|
377
|
+
|
|
337
378
|
async function persistSettings(selectedTools: readonly ChromeDevToolsToolName[]) {
|
|
338
379
|
await saveSettings({ tools: [...selectedTools], updatedAt: Date.now() });
|
|
339
380
|
}
|
package/src/tools.ts
CHANGED
|
@@ -6,13 +6,13 @@ import {
|
|
|
6
6
|
listPages,
|
|
7
7
|
resolvePage,
|
|
8
8
|
resolvePageForNavigation,
|
|
9
|
+
setActivePageId,
|
|
9
10
|
textResult,
|
|
10
11
|
withCdp,
|
|
11
12
|
} from "./cdp-client.js";
|
|
12
13
|
import { renderScreenshotResult, renderTextResult, renderToolCall, withStatus } from "./render.js";
|
|
13
|
-
import { state } from "./runtime.js";
|
|
14
14
|
import { formatScreenshotText, saveScreenshot, throwIfAborted } from "./screenshot.js";
|
|
15
|
-
import { CHROME_DEVTOOLS_TOOL_NAMES } from "./tool-names.js";
|
|
15
|
+
import { CHROME_DEVTOOLS_TOOL_NAMES, WEBMCP_TOOL_NAMES } from "./tool-names.js";
|
|
16
16
|
|
|
17
17
|
export const listPagesTool = defineTool({
|
|
18
18
|
name: CHROME_DEVTOOLS_TOOL_NAMES[0],
|
|
@@ -21,9 +21,9 @@ export const listPagesTool = defineTool({
|
|
|
21
21
|
parameters: Type.Object({}),
|
|
22
22
|
renderCall: renderToolCall("list pages"),
|
|
23
23
|
renderResult: renderTextResult,
|
|
24
|
-
async execute(_toolCallId, _params,
|
|
24
|
+
async execute(_toolCallId, _params, signal, _onUpdate, ctx) {
|
|
25
25
|
return withStatus(ctx, "list pages", async () => {
|
|
26
|
-
const pages = await listPages();
|
|
26
|
+
const pages = await listPages({ sessionOwner: ctx.sessionManager, signal });
|
|
27
27
|
return textResult(JSON.stringify(pages.map(formatPage), null, 2), { pages });
|
|
28
28
|
});
|
|
29
29
|
},
|
|
@@ -38,10 +38,10 @@ export const selectPageTool = defineTool({
|
|
|
38
38
|
}),
|
|
39
39
|
renderCall: renderToolCall("select page"),
|
|
40
40
|
renderResult: renderTextResult,
|
|
41
|
-
async execute(_toolCallId, params,
|
|
41
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
42
42
|
return withStatus(ctx, "select page", async () => {
|
|
43
|
-
const page = await getPage(params.pageId);
|
|
44
|
-
|
|
43
|
+
const page = await getPage(params.pageId, { sessionOwner: ctx.sessionManager, signal });
|
|
44
|
+
setActivePageId(ctx.sessionManager, page.id);
|
|
45
45
|
return textResult(`Selected page ${page.id}: ${page.title}\n${page.url}`, {
|
|
46
46
|
page: formatPage(page),
|
|
47
47
|
});
|
|
@@ -62,15 +62,18 @@ export const navigateTool = defineTool({
|
|
|
62
62
|
}),
|
|
63
63
|
renderCall: renderToolCall("navigate"),
|
|
64
64
|
renderResult: renderTextResult,
|
|
65
|
-
async execute(_toolCallId, params,
|
|
65
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
66
66
|
return withStatus(ctx, "navigate", async () => {
|
|
67
|
-
const { created, page } = await resolvePageForNavigation(params.pageId
|
|
67
|
+
const { created, page } = await resolvePageForNavigation(params.pageId, {
|
|
68
|
+
sessionOwner: ctx.sessionManager,
|
|
69
|
+
signal,
|
|
70
|
+
});
|
|
68
71
|
const result = await withCdp(page, async (client) => {
|
|
69
72
|
await client.send("Page.enable");
|
|
70
73
|
return client.send("Page.navigate", { url: params.url });
|
|
71
74
|
});
|
|
72
75
|
|
|
73
|
-
|
|
76
|
+
setActivePageId(ctx.sessionManager, page.id);
|
|
74
77
|
const action = created ? "Created page and navigated" : "Navigated";
|
|
75
78
|
return textResult(`${action} ${page.id} to ${params.url}`, {
|
|
76
79
|
created,
|
|
@@ -96,9 +99,12 @@ export const evaluateTool = defineTool({
|
|
|
96
99
|
}),
|
|
97
100
|
renderCall: renderToolCall("evaluate"),
|
|
98
101
|
renderResult: renderTextResult,
|
|
99
|
-
async execute(_toolCallId, params,
|
|
102
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
100
103
|
return withStatus(ctx, "evaluate", async () => {
|
|
101
|
-
const page = await resolvePage(params.pageId
|
|
104
|
+
const page = await resolvePage(params.pageId, {
|
|
105
|
+
sessionOwner: ctx.sessionManager,
|
|
106
|
+
signal,
|
|
107
|
+
});
|
|
102
108
|
const result = await withCdp(page, (client) =>
|
|
103
109
|
client.send("Runtime.evaluate", {
|
|
104
110
|
expression: params.expression,
|
|
@@ -107,12 +113,79 @@ export const evaluateTool = defineTool({
|
|
|
107
113
|
}),
|
|
108
114
|
);
|
|
109
115
|
|
|
110
|
-
|
|
116
|
+
setActivePageId(ctx.sessionManager, page.id);
|
|
111
117
|
return textResult(JSON.stringify(result, null, 2), { page: formatPage(page), result });
|
|
112
118
|
});
|
|
113
119
|
},
|
|
114
120
|
});
|
|
115
121
|
|
|
122
|
+
export const webMcpListToolsTool = defineTool({
|
|
123
|
+
name: WEBMCP_TOOL_NAMES[0],
|
|
124
|
+
label: "Chrome DevTools: List WebMCP Tools (Experimental)",
|
|
125
|
+
description:
|
|
126
|
+
"List bounded page-provided WebMCP tool descriptors from the selected Chrome page through the experimental CDP WebMCP domain. Definitions remain in this tool result and are never registered as dynamic Pi tools.",
|
|
127
|
+
parameters: Type.Object({
|
|
128
|
+
pageId: Type.Optional(
|
|
129
|
+
Type.String({
|
|
130
|
+
description: "Optional page id. Defaults to selected or first page.",
|
|
131
|
+
maxLength: 512,
|
|
132
|
+
}),
|
|
133
|
+
),
|
|
134
|
+
}),
|
|
135
|
+
renderCall: renderToolCall("list WebMCP tools"),
|
|
136
|
+
renderResult: renderTextResult,
|
|
137
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
138
|
+
return withStatus(ctx, "list WebMCP tools", async () => {
|
|
139
|
+
const implementation = await import("./webmcp/tools.js");
|
|
140
|
+
return implementation.executeWebMcpListTool(params, signal, ctx);
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
export const webMcpCallTool = defineTool({
|
|
146
|
+
name: WEBMCP_TOOL_NAMES[1],
|
|
147
|
+
label: "Chrome DevTools: Call WebMCP Tool (Experimental)",
|
|
148
|
+
description:
|
|
149
|
+
"Call one previously listed page-provided WebMCP tool after exact page, document, frame, origin, schema, annotation, and session revalidation. Every call requires observable user confirmation and output is bounded to Pi's 50 KB or 2,000-line limit.",
|
|
150
|
+
parameters: Type.Object({
|
|
151
|
+
sessionGeneration: Type.String({
|
|
152
|
+
description: "Session generation token returned by chrome_devtools_webmcp_list_tools.",
|
|
153
|
+
maxLength: 100,
|
|
154
|
+
}),
|
|
155
|
+
pageId: Type.String({ description: "Page id returned by the list tool.", maxLength: 512 }),
|
|
156
|
+
documentId: Type.String({
|
|
157
|
+
description: "Document loader id returned by the list tool.",
|
|
158
|
+
maxLength: 512,
|
|
159
|
+
}),
|
|
160
|
+
frameId: Type.String({ description: "Frame id returned by the list tool.", maxLength: 512 }),
|
|
161
|
+
frameOrigin: Type.String({
|
|
162
|
+
description: "Exact frame origin returned by the list tool.",
|
|
163
|
+
maxLength: 2_048,
|
|
164
|
+
}),
|
|
165
|
+
toolName: Type.String({
|
|
166
|
+
description: "Exact tool name returned by the list tool.",
|
|
167
|
+
maxLength: 512,
|
|
168
|
+
}),
|
|
169
|
+
schemaDigest: Type.String({
|
|
170
|
+
description: "Schema and annotation digest returned by the list tool.",
|
|
171
|
+
minLength: 64,
|
|
172
|
+
maxLength: 64,
|
|
173
|
+
pattern: "^[a-f0-9]{64}$",
|
|
174
|
+
}),
|
|
175
|
+
input: Type.Record(Type.String(), Type.Unknown(), {
|
|
176
|
+
description: "JSON object matching the page-provided input schema.",
|
|
177
|
+
}),
|
|
178
|
+
}),
|
|
179
|
+
renderCall: renderToolCall("call WebMCP tool"),
|
|
180
|
+
renderResult: renderTextResult,
|
|
181
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
182
|
+
return withStatus(ctx, "call WebMCP tool", async () => {
|
|
183
|
+
const implementation = await import("./webmcp/tools.js");
|
|
184
|
+
return implementation.executeWebMcpCallTool(params, signal, ctx);
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
116
189
|
export const screenshotTool = defineTool({
|
|
117
190
|
name: CHROME_DEVTOOLS_TOOL_NAMES[4],
|
|
118
191
|
label: "Chrome DevTools: Screenshot",
|
|
@@ -135,7 +208,10 @@ export const screenshotTool = defineTool({
|
|
|
135
208
|
renderResult: renderScreenshotResult,
|
|
136
209
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
137
210
|
return withStatus(ctx, "screenshot", async () => {
|
|
138
|
-
const page = await resolvePage(params.pageId
|
|
211
|
+
const page = await resolvePage(params.pageId, {
|
|
212
|
+
sessionOwner: ctx.sessionManager,
|
|
213
|
+
signal,
|
|
214
|
+
});
|
|
139
215
|
const result = await withCdp(page, async (client) => {
|
|
140
216
|
throwIfAborted(signal);
|
|
141
217
|
await client.send("Page.enable");
|
|
@@ -162,7 +238,7 @@ export const screenshotTool = defineTool({
|
|
|
162
238
|
});
|
|
163
239
|
});
|
|
164
240
|
|
|
165
|
-
|
|
241
|
+
setActivePageId(ctx.sessionManager, page.id);
|
|
166
242
|
const savedScreenshot = await saveScreenshot(result.data, params.savePath, ctx.cwd, signal);
|
|
167
243
|
return {
|
|
168
244
|
content: [
|