@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.
- package/README.md +62 -9
- package/dist/chunks/{browser-settings-menu-VWCNG4HB.js → browser-settings-menu-N5O65I7S.js} +81 -7
- package/dist/chunks/browser-settings-menu-N5O65I7S.js.map +7 -0
- package/dist/chunks/chunk-B7B63PWB.js +465 -0
- package/dist/chunks/chunk-B7B63PWB.js.map +7 -0
- package/dist/chunks/{chunk-RH6KESL3.js → chunk-GVBXT3FB.js} +369 -514
- package/dist/chunks/chunk-GVBXT3FB.js.map +7 -0
- package/dist/chunks/chunk-RTECHAEZ.js +373 -0
- package/dist/chunks/chunk-RTECHAEZ.js.map +7 -0
- package/dist/chunks/{menu-PLD3S3EO.js → menu-UBHW6UP2.js} +72 -39
- package/dist/chunks/menu-UBHW6UP2.js.map +7 -0
- package/dist/chunks/tools-V7OR5KJ3.js +945 -0
- package/dist/chunks/tools-V7OR5KJ3.js.map +7 -0
- package/dist/index.ts +158 -195
- package/dist/index.ts.map +4 -4
- package/package.json +4 -3
- package/src/browser-manager.ts +282 -109
- package/src/browser-settings-menu.ts +98 -4
- package/src/cdp-client.ts +329 -83
- package/src/chrome-devtools.ts +51 -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 +67 -30
- package/src/tools.ts +86 -10
- package/src/webmcp/discovery.ts +235 -0
- package/src/webmcp/policy.ts +402 -0
- package/src/webmcp/protocol.ts +414 -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
|
@@ -2,13 +2,24 @@
|
|
|
2
2
|
// @ts-nocheck -- the generated entry uses a .ts extension for Pi's Jiti loader.
|
|
3
3
|
|
|
4
4
|
// src/tool-names.ts
|
|
5
|
-
var
|
|
5
|
+
var CORE_CHROME_DEVTOOLS_TOOL_NAMES = [
|
|
6
6
|
"chrome_devtools_list_pages",
|
|
7
7
|
"chrome_devtools_select_page",
|
|
8
8
|
"chrome_devtools_navigate",
|
|
9
9
|
"chrome_devtools_evaluate",
|
|
10
10
|
"chrome_devtools_screenshot"
|
|
11
11
|
];
|
|
12
|
+
var WEBMCP_TOOL_NAMES = [
|
|
13
|
+
"chrome_devtools_webmcp_list_tools",
|
|
14
|
+
"chrome_devtools_webmcp_call_tool"
|
|
15
|
+
];
|
|
16
|
+
var CHROME_DEVTOOLS_TOOL_NAMES = [
|
|
17
|
+
...CORE_CHROME_DEVTOOLS_TOOL_NAMES,
|
|
18
|
+
...WEBMCP_TOOL_NAMES
|
|
19
|
+
];
|
|
20
|
+
function isWebMcpToolName(value) {
|
|
21
|
+
return WEBMCP_TOOL_NAMES.includes(value);
|
|
22
|
+
}
|
|
12
23
|
|
|
13
24
|
// src/settings.ts
|
|
14
25
|
import { randomUUID } from "node:crypto";
|
|
@@ -82,7 +93,7 @@ async function loadSettings(options = {}) {
|
|
|
82
93
|
const userBrowser = user.normalized?.browser ?? {};
|
|
83
94
|
const effectiveBrowser = resolveEffectiveBrowser(userBrowser, project.normalized?.browser);
|
|
84
95
|
const settings = resolveSettings(user.normalized, project.normalized, effectiveBrowser);
|
|
85
|
-
const recognized = settings.tools !== void 0 || user.normalized?.browser !== void 0 || project.normalized?.browser !== void 0;
|
|
96
|
+
const recognized = settings.tools !== void 0 || user.normalized?.browser !== void 0 || project.normalized?.browser !== void 0 || user.normalized?.webmcp !== void 0;
|
|
86
97
|
const invalidReasons = [user, project].filter((result) => result.kind === "invalid").map((result) => result.reason).filter((reason) => Boolean(reason));
|
|
87
98
|
const userFile = user.kind === "invalid" ? {
|
|
88
99
|
kind: "invalid",
|
|
@@ -90,6 +101,7 @@ async function loadSettings(options = {}) {
|
|
|
90
101
|
} : { kind: user.kind };
|
|
91
102
|
const base = {
|
|
92
103
|
effectiveBrowser,
|
|
104
|
+
effectiveWebMcpEnabled: settings.webmcp.enabled,
|
|
93
105
|
paths,
|
|
94
106
|
userFile,
|
|
95
107
|
warnings,
|
|
@@ -104,7 +116,8 @@ async function loadSettings(options = {}) {
|
|
|
104
116
|
function resolveSettings(user, _project, browser) {
|
|
105
117
|
return {
|
|
106
118
|
...user?.tools ? { tools: user.tools, updatedAt: user.updatedAt } : {},
|
|
107
|
-
browser
|
|
119
|
+
browser,
|
|
120
|
+
webmcp: { enabled: user?.webmcp?.enabled ?? false }
|
|
108
121
|
};
|
|
109
122
|
}
|
|
110
123
|
function resolveEffectiveBrowser(user, project) {
|
|
@@ -165,7 +178,7 @@ async function readSettingsDocument(filePath, scope, cwd) {
|
|
|
165
178
|
const reason = `${filePath}: expected a JSON object`;
|
|
166
179
|
return { kind: "invalid", reason, warnings: [`Chrome DevTools settings ignored: ${reason}`] };
|
|
167
180
|
}
|
|
168
|
-
const scopeWarnings =
|
|
181
|
+
const scopeWarnings = projectOwnedSettingsWarnings(parsed, scope, filePath);
|
|
169
182
|
try {
|
|
170
183
|
const normalized = await normalizeSettingsDocument(parsed, scope, filePath, cwd);
|
|
171
184
|
return { kind: "valid", document: { ...parsed }, normalized, warnings: scopeWarnings };
|
|
@@ -178,12 +191,18 @@ async function readSettingsDocument(filePath, scope, cwd) {
|
|
|
178
191
|
};
|
|
179
192
|
}
|
|
180
193
|
}
|
|
181
|
-
function
|
|
182
|
-
if (scope !== "project"
|
|
183
|
-
const browser = document.browser;
|
|
184
|
-
|
|
194
|
+
function projectOwnedSettingsWarnings(document, scope, filePath) {
|
|
195
|
+
if (scope !== "project") return [];
|
|
196
|
+
const browser = isRecord(document.browser) ? document.browser : void 0;
|
|
197
|
+
const warnings = browser ? ["endpoint", "autoLaunch", "executablePath"].filter((field) => browser[field] !== void 0).map(
|
|
185
198
|
(field) => `Chrome DevTools project browser.${field} ignored in ${filePath}; configure this machine-owned setting in ${settingsFilePath()}.`
|
|
186
|
-
);
|
|
199
|
+
) : [];
|
|
200
|
+
if (document.webmcp !== void 0) {
|
|
201
|
+
warnings.push(
|
|
202
|
+
`Chrome DevTools project webmcp settings ignored in ${filePath}; only the user settings file may enable experimental WebMCP.`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
return warnings;
|
|
187
206
|
}
|
|
188
207
|
async function normalizeSettingsDocument(document, scope, filePath, cwd) {
|
|
189
208
|
const normalized = {};
|
|
@@ -201,6 +220,12 @@ async function normalizeSettingsDocument(document, scope, filePath, cwd) {
|
|
|
201
220
|
if (!isRecord(document.browser)) throw new Error("expected browser to be an object");
|
|
202
221
|
normalized.browser = await normalizeBrowserSection(document.browser, scope, filePath, cwd);
|
|
203
222
|
}
|
|
223
|
+
if (scope === "user" && document.webmcp !== void 0) {
|
|
224
|
+
if (!isRecord(document.webmcp) || typeof document.webmcp.enabled !== "boolean") {
|
|
225
|
+
throw new Error("expected webmcp.enabled to be a boolean");
|
|
226
|
+
}
|
|
227
|
+
normalized.webmcp = { enabled: document.webmcp.enabled };
|
|
228
|
+
}
|
|
204
229
|
return normalized;
|
|
205
230
|
}
|
|
206
231
|
async function normalizeBrowserSection(browser, scope, _filePath, cwd) {
|
|
@@ -337,6 +362,18 @@ function saveSettings(settings, operations = {}) {
|
|
|
337
362
|
operations
|
|
338
363
|
);
|
|
339
364
|
}
|
|
365
|
+
function saveWebMcpSettings(enabled, operations = {}) {
|
|
366
|
+
return queueSettingsMutation(
|
|
367
|
+
(current) => ({
|
|
368
|
+
...current,
|
|
369
|
+
webmcp: {
|
|
370
|
+
...isRecord(current.webmcp) ? current.webmcp : {},
|
|
371
|
+
enabled
|
|
372
|
+
}
|
|
373
|
+
}),
|
|
374
|
+
operations
|
|
375
|
+
);
|
|
376
|
+
}
|
|
340
377
|
function saveBrowserSettings(patch, operations = {}) {
|
|
341
378
|
return queueSettingsMutation(async (current) => {
|
|
342
379
|
const browser = isRecord(current.browser) ? { ...current.browser } : {};
|
|
@@ -446,6 +483,72 @@ var state = {
|
|
|
446
483
|
sessionGeneration: 0,
|
|
447
484
|
sessionController: new AbortController()
|
|
448
485
|
};
|
|
486
|
+
var webMcpRuntimeByOwner = /* @__PURE__ */ new WeakMap();
|
|
487
|
+
function setWebMcpSessionOwner(owner) {
|
|
488
|
+
const runtime = webMcpRuntime(owner);
|
|
489
|
+
runtime.enabled = false;
|
|
490
|
+
runtime.sessionGeneration += 1;
|
|
491
|
+
}
|
|
492
|
+
function beginWebMcpOperation(owner, toolSignal) {
|
|
493
|
+
const runtime = webMcpRuntime(owner);
|
|
494
|
+
const controller = new AbortController();
|
|
495
|
+
runtime.controllers.add(controller);
|
|
496
|
+
const signals = [controller.signal, runtime.controller.signal];
|
|
497
|
+
if (toolSignal) signals.push(toolSignal);
|
|
498
|
+
const signal = AbortSignal.any(signals);
|
|
499
|
+
return {
|
|
500
|
+
signal,
|
|
501
|
+
owner,
|
|
502
|
+
sessionGeneration: runtime.sessionGeneration,
|
|
503
|
+
webMcpGeneration: runtime.generation,
|
|
504
|
+
dispose() {
|
|
505
|
+
runtime.controllers.delete(controller);
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function invalidateWebMcpOperations(owner, reason) {
|
|
510
|
+
const runtime = webMcpRuntime(owner);
|
|
511
|
+
runtime.generation += 1;
|
|
512
|
+
const controllers = [...runtime.controllers];
|
|
513
|
+
runtime.controllers.clear();
|
|
514
|
+
const error = new DOMException(reason, "AbortError");
|
|
515
|
+
runtime.controller.abort(error);
|
|
516
|
+
runtime.controller = new AbortController();
|
|
517
|
+
for (const controller of controllers) controller.abort(error);
|
|
518
|
+
}
|
|
519
|
+
function currentWebMcpGeneration(owner) {
|
|
520
|
+
return webMcpRuntime(owner).generation;
|
|
521
|
+
}
|
|
522
|
+
function webMcpSessionSignal(owner) {
|
|
523
|
+
return webMcpRuntime(owner).controller.signal;
|
|
524
|
+
}
|
|
525
|
+
function applyRuntimeWebMcpSetting(enabled, owner) {
|
|
526
|
+
const runtime = webMcpRuntime(owner);
|
|
527
|
+
if (runtime.enabled !== enabled) {
|
|
528
|
+
invalidateWebMcpOperations(owner, `WebMCP ${enabled ? "enabled" : "disabled"}`);
|
|
529
|
+
}
|
|
530
|
+
runtime.enabled = enabled;
|
|
531
|
+
}
|
|
532
|
+
function webMcpEnabled(owner) {
|
|
533
|
+
return webMcpRuntime(owner).enabled;
|
|
534
|
+
}
|
|
535
|
+
function webMcpOperationIsCurrent(operation) {
|
|
536
|
+
const runtime = webMcpRuntime(operation.owner);
|
|
537
|
+
return operation.sessionGeneration === runtime.sessionGeneration && operation.webMcpGeneration === runtime.generation;
|
|
538
|
+
}
|
|
539
|
+
function webMcpRuntime(owner) {
|
|
540
|
+
const existing = webMcpRuntimeByOwner.get(owner);
|
|
541
|
+
if (existing) return existing;
|
|
542
|
+
const created = {
|
|
543
|
+
controller: new AbortController(),
|
|
544
|
+
controllers: /* @__PURE__ */ new Set(),
|
|
545
|
+
enabled: false,
|
|
546
|
+
generation: 0,
|
|
547
|
+
sessionGeneration: 0
|
|
548
|
+
};
|
|
549
|
+
webMcpRuntimeByOwner.set(owner, created);
|
|
550
|
+
return created;
|
|
551
|
+
}
|
|
449
552
|
function applyRuntimeBrowserSettings(browser, paths, projectTrusted) {
|
|
450
553
|
state.host = browser.host;
|
|
451
554
|
state.port = browser.port;
|
|
@@ -483,6 +586,57 @@ var DEFAULT_BROWSER_MANAGER_OPERATIONS = {
|
|
|
483
586
|
sleep: abortableSleep
|
|
484
587
|
};
|
|
485
588
|
var browserManagerOperations = DEFAULT_BROWSER_MANAGER_OPERATIONS;
|
|
589
|
+
var managedBrowserScopes = /* @__PURE__ */ new WeakMap();
|
|
590
|
+
function managedBrowserScope(owner) {
|
|
591
|
+
const existing = managedBrowserScopes.get(owner);
|
|
592
|
+
if (existing) return existing;
|
|
593
|
+
const created = {
|
|
594
|
+
controller: new AbortController(),
|
|
595
|
+
generation: 0,
|
|
596
|
+
port: state.configuredPort
|
|
597
|
+
};
|
|
598
|
+
managedBrowserScopes.set(owner, created);
|
|
599
|
+
return created;
|
|
600
|
+
}
|
|
601
|
+
function startManagedBrowserSession(owner) {
|
|
602
|
+
const scope = managedBrowserScope(owner);
|
|
603
|
+
scope.controller.abort(new DOMException("Chrome DevTools session replaced", "AbortError"));
|
|
604
|
+
scope.controller = new AbortController();
|
|
605
|
+
scope.generation += 1;
|
|
606
|
+
scope.port = state.configuredPort;
|
|
607
|
+
scope.lastLaunchAttempt = void 0;
|
|
608
|
+
}
|
|
609
|
+
function managedBrowserForOwner(owner) {
|
|
610
|
+
if (!owner) return state.managedBrowser;
|
|
611
|
+
const scopedBrowser = managedBrowserScope(owner).managedBrowser;
|
|
612
|
+
if (scopedBrowser) return scopedBrowser;
|
|
613
|
+
return state.managedBrowser?.sessionOwner === owner ? state.managedBrowser : void 0;
|
|
614
|
+
}
|
|
615
|
+
function setManagedBrowser(owner, browser) {
|
|
616
|
+
if (owner) managedBrowserScope(owner).managedBrowser = browser;
|
|
617
|
+
else state.managedBrowser = browser;
|
|
618
|
+
}
|
|
619
|
+
function launchPromiseForOwner(owner) {
|
|
620
|
+
return owner ? managedBrowserScope(owner).launchPromise : state.launchPromise;
|
|
621
|
+
}
|
|
622
|
+
function setLaunchPromise(owner, launchPromise) {
|
|
623
|
+
if (owner) managedBrowserScope(owner).launchPromise = launchPromise;
|
|
624
|
+
else state.launchPromise = launchPromise;
|
|
625
|
+
}
|
|
626
|
+
function managedBrowserPort(owner) {
|
|
627
|
+
return owner ? managedBrowserScope(owner).port : state.port;
|
|
628
|
+
}
|
|
629
|
+
function setManagedBrowserPort(owner, port) {
|
|
630
|
+
if (owner) managedBrowserScope(owner).port = port;
|
|
631
|
+
else state.port = port;
|
|
632
|
+
}
|
|
633
|
+
function lastLaunchAttempt(owner) {
|
|
634
|
+
return owner ? managedBrowserScope(owner).lastLaunchAttempt : state.lastLaunchAttempt;
|
|
635
|
+
}
|
|
636
|
+
function setLastLaunchAttempt(owner, attempt) {
|
|
637
|
+
if (owner) managedBrowserScope(owner).lastLaunchAttempt = attempt;
|
|
638
|
+
else state.lastLaunchAttempt = attempt;
|
|
639
|
+
}
|
|
486
640
|
function formatError2(error) {
|
|
487
641
|
return error instanceof Error ? error.message : String(error);
|
|
488
642
|
}
|
|
@@ -492,26 +646,31 @@ function isNodeError2(error) {
|
|
|
492
646
|
function normalizePathForComparison(value) {
|
|
493
647
|
return process.platform === "win32" ? value.toLowerCase() : value;
|
|
494
648
|
}
|
|
495
|
-
async function ensureDevToolsEndpoint(waitMs = DEFAULT_ENDPOINT_WAIT_MS) {
|
|
649
|
+
async function ensureDevToolsEndpoint(waitMs = DEFAULT_ENDPOINT_WAIT_MS, callerSignal, owner) {
|
|
650
|
+
callerSignal?.throwIfAborted();
|
|
496
651
|
if (extensionsConfigured()) {
|
|
497
652
|
validateExtensionLaunchMode();
|
|
498
|
-
await ensureManagedBrowserLaunched(waitMs);
|
|
653
|
+
await ensureManagedBrowserLaunched(waitMs, callerSignal, owner);
|
|
499
654
|
return;
|
|
500
655
|
}
|
|
501
656
|
if (canAutoLaunchBrowser()) {
|
|
502
657
|
try {
|
|
503
|
-
await withEndpointRetry(
|
|
658
|
+
await withEndpointRetry(
|
|
659
|
+
() => fetchDevToolsJson("/json/version", { signal: callerSignal }, owner),
|
|
660
|
+
waitMs,
|
|
661
|
+
callerSignal
|
|
662
|
+
);
|
|
504
663
|
return;
|
|
505
664
|
} catch (error) {
|
|
506
665
|
if (shouldAutoLaunchAfterEndpointError(error)) {
|
|
507
|
-
await ensureManagedBrowserLaunched(waitMs);
|
|
666
|
+
await ensureManagedBrowserLaunched(waitMs, callerSignal, owner);
|
|
508
667
|
return;
|
|
509
668
|
}
|
|
510
669
|
throw error;
|
|
511
670
|
}
|
|
512
671
|
}
|
|
513
672
|
try {
|
|
514
|
-
await fetchDevToolsJson("/json/version");
|
|
673
|
+
await fetchDevToolsJson("/json/version", { signal: callerSignal }, owner);
|
|
515
674
|
} catch (error) {
|
|
516
675
|
if (isRetryableEndpointError(error)) return;
|
|
517
676
|
throw error;
|
|
@@ -534,76 +693,117 @@ function validateExtensionLaunchMode() {
|
|
|
534
693
|
);
|
|
535
694
|
}
|
|
536
695
|
}
|
|
537
|
-
async function ensureManagedBrowserLaunched(waitMs) {
|
|
538
|
-
|
|
539
|
-
if (
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
const
|
|
696
|
+
async function ensureManagedBrowserLaunched(waitMs, callerSignal, owner) {
|
|
697
|
+
const existingLaunch = launchPromiseForOwner(owner);
|
|
698
|
+
if (existingLaunch) {
|
|
699
|
+
await waitForCaller(existingLaunch, callerSignal);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
const existingBrowser = managedBrowserForOwner(owner);
|
|
703
|
+
if (existingBrowser && !existingBrowser.exited && existingBrowser.ready) return;
|
|
704
|
+
const generation = owner ? managedBrowserScope(owner).generation : state.sessionGeneration;
|
|
705
|
+
const signal = owner ? managedBrowserScope(owner).controller.signal : state.sessionController.signal;
|
|
706
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
707
|
+
const launchPromise = prepareManagedBrowserLaunch(waitMs, generation, signal, owner);
|
|
544
708
|
const wrappedPromise = launchPromise.finally(() => {
|
|
545
|
-
if (
|
|
709
|
+
if (launchPromiseForOwner(owner) === wrappedPromise) setLaunchPromise(owner, void 0);
|
|
546
710
|
});
|
|
547
|
-
|
|
548
|
-
|
|
711
|
+
setLaunchPromise(owner, wrappedPromise);
|
|
712
|
+
await waitForCaller(wrappedPromise, callerSignal);
|
|
549
713
|
}
|
|
550
|
-
|
|
551
|
-
if (
|
|
552
|
-
|
|
553
|
-
|
|
714
|
+
function waitForCaller(operation, signal) {
|
|
715
|
+
if (!signal) return operation;
|
|
716
|
+
signal.throwIfAborted();
|
|
717
|
+
return new Promise((resolve3, reject) => {
|
|
718
|
+
let settled = false;
|
|
719
|
+
const onAbort = () => settle(() => reject(signal.reason));
|
|
720
|
+
const settle = (callback) => {
|
|
721
|
+
if (settled) return;
|
|
722
|
+
settled = true;
|
|
723
|
+
signal.removeEventListener("abort", onAbort);
|
|
724
|
+
callback();
|
|
725
|
+
};
|
|
726
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
727
|
+
operation.then(
|
|
728
|
+
(value) => settle(() => resolve3(value)),
|
|
729
|
+
(error) => settle(() => reject(error))
|
|
730
|
+
);
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
async function prepareManagedBrowserLaunch(waitMs, generation, signal, owner) {
|
|
734
|
+
const existingBrowser = managedBrowserForOwner(owner);
|
|
735
|
+
if (existingBrowser) {
|
|
736
|
+
await shutdownManagedBrowser(existingBrowser, { awaitLaunch: false, owner });
|
|
737
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
554
738
|
}
|
|
555
739
|
if (state.portConfigured) {
|
|
556
|
-
const available = await browserManagerOperations.isPortAvailable(
|
|
557
|
-
|
|
740
|
+
const available = await browserManagerOperations.isPortAvailable(
|
|
741
|
+
state.host,
|
|
742
|
+
managedBrowserPort(owner)
|
|
743
|
+
);
|
|
744
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
558
745
|
if (!available) {
|
|
559
746
|
throw new DevToolsEndpointError(
|
|
560
|
-
`Cannot launch the extension-owned browser because explicit port ${
|
|
747
|
+
`Cannot launch the extension-owned browser because explicit port ${managedBrowserPort(owner)} is already in use. Choose a free browser.endpoint port or reset the endpoint to use a dynamic managed port.`
|
|
561
748
|
);
|
|
562
749
|
}
|
|
563
750
|
}
|
|
564
|
-
return launchManagedBrowser(waitMs, generation, signal);
|
|
751
|
+
return launchManagedBrowser(waitMs, generation, signal, owner);
|
|
565
752
|
}
|
|
566
|
-
async function launchManagedBrowser(waitMs, generation, signal) {
|
|
567
|
-
throwIfBrowserLaunchCancelled(generation, signal);
|
|
753
|
+
async function launchManagedBrowser(waitMs, generation, signal, owner) {
|
|
754
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
568
755
|
const candidateDefinitions = browserCandidateDefinitions();
|
|
569
|
-
const candidates = await resolveBrowserCandidates(
|
|
570
|
-
|
|
571
|
-
|
|
756
|
+
const candidates = await resolveBrowserCandidates(
|
|
757
|
+
candidateDefinitions,
|
|
758
|
+
generation,
|
|
759
|
+
signal,
|
|
760
|
+
owner
|
|
761
|
+
);
|
|
762
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
763
|
+
setLastLaunchAttempt(owner, {
|
|
572
764
|
candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
|
|
573
765
|
mode: state.portConfigured ? "explicit-port" : "dynamic-port"
|
|
574
|
-
};
|
|
766
|
+
});
|
|
575
767
|
if (candidates.length === 0) {
|
|
576
768
|
throw new DevToolsEndpointError(noBrowserCandidateMessage(candidateDefinitions));
|
|
577
769
|
}
|
|
578
770
|
let lastError;
|
|
579
771
|
for (const candidate of candidates) {
|
|
580
|
-
throwIfBrowserLaunchCancelled(generation, signal);
|
|
772
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
581
773
|
try {
|
|
582
774
|
if (extensionsConfigured())
|
|
583
|
-
await requireSupportedExtensionBrowser(candidate, generation, signal);
|
|
584
|
-
await launchBrowserCandidate(candidate, waitMs, generation, signal);
|
|
585
|
-
throwIfBrowserLaunchCancelled(generation, signal);
|
|
586
|
-
|
|
587
|
-
...
|
|
775
|
+
await requireSupportedExtensionBrowser(candidate, generation, signal, owner);
|
|
776
|
+
await launchBrowserCandidate(candidate, waitMs, generation, signal, owner);
|
|
777
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
778
|
+
setLastLaunchAttempt(owner, {
|
|
779
|
+
...lastLaunchAttempt(owner),
|
|
780
|
+
candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
|
|
781
|
+
mode: state.portConfigured ? "explicit-port" : "dynamic-port",
|
|
588
782
|
selectedCandidate: formatBrowserCandidate(candidate),
|
|
589
|
-
userDataDir:
|
|
590
|
-
};
|
|
783
|
+
userDataDir: managedBrowserForOwner(owner)?.userDataDir
|
|
784
|
+
});
|
|
591
785
|
return;
|
|
592
786
|
} catch (error) {
|
|
593
787
|
try {
|
|
594
|
-
throwIfBrowserLaunchCancelled(generation, signal);
|
|
788
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
595
789
|
} catch (cancellation) {
|
|
596
|
-
|
|
597
|
-
...
|
|
790
|
+
setLastLaunchAttempt(owner, {
|
|
791
|
+
...lastLaunchAttempt(owner) ?? {
|
|
792
|
+
candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
|
|
793
|
+
mode: state.portConfigured ? "explicit-port" : "dynamic-port"
|
|
794
|
+
},
|
|
598
795
|
lastError: formatError2(cancellation)
|
|
599
|
-
};
|
|
796
|
+
});
|
|
600
797
|
throw cancellation;
|
|
601
798
|
}
|
|
602
799
|
lastError = error;
|
|
603
|
-
|
|
604
|
-
...
|
|
800
|
+
setLastLaunchAttempt(owner, {
|
|
801
|
+
...lastLaunchAttempt(owner) ?? {
|
|
802
|
+
candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
|
|
803
|
+
mode: state.portConfigured ? "explicit-port" : "dynamic-port"
|
|
804
|
+
},
|
|
605
805
|
lastError: formatError2(error)
|
|
606
|
-
};
|
|
806
|
+
});
|
|
607
807
|
}
|
|
608
808
|
}
|
|
609
809
|
if (lastError instanceof DevToolsEndpointError && extensionsConfigured()) throw lastError;
|
|
@@ -617,17 +817,17 @@ async function launchManagedBrowser(waitMs, generation, signal) {
|
|
|
617
817
|
].filter(Boolean).join("\n")
|
|
618
818
|
);
|
|
619
819
|
}
|
|
620
|
-
async function requireSupportedExtensionBrowser(candidate, generation, signal) {
|
|
820
|
+
async function requireSupportedExtensionBrowser(candidate, generation, signal, owner) {
|
|
621
821
|
let version;
|
|
622
822
|
try {
|
|
623
823
|
version = (await browserManagerOperations.inspectBrowserVersion(candidate.resolvedExecutable, signal)).trim();
|
|
624
824
|
} catch (error) {
|
|
625
|
-
throwIfBrowserLaunchCancelled(generation, signal);
|
|
825
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
626
826
|
throw new DevToolsEndpointError(
|
|
627
827
|
`Unable to identify the configured extension browser ${candidate.resolvedExecutable}: ${formatError2(error)}. Configure Chrome for Testing or Chromium.`
|
|
628
828
|
);
|
|
629
829
|
}
|
|
630
|
-
throwIfBrowserLaunchCancelled(generation, signal);
|
|
830
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
631
831
|
const classification = classifyExtensionBrowserVersion(version);
|
|
632
832
|
if (!classification.supported) {
|
|
633
833
|
throw new DevToolsEndpointError(
|
|
@@ -643,15 +843,15 @@ function classifyExtensionBrowserVersion(version) {
|
|
|
643
843
|
const product = version.trim().replace(/\s+\d+(?:\.\d+)*.*$/, "") || "Unknown";
|
|
644
844
|
return { supported: false, product };
|
|
645
845
|
}
|
|
646
|
-
async function launchBrowserCandidate(candidate, waitMs, generation, signal) {
|
|
647
|
-
throwIfBrowserLaunchCancelled(generation, signal);
|
|
846
|
+
async function launchBrowserCandidate(candidate, waitMs, generation, signal, owner) {
|
|
847
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
648
848
|
const userDataDir = await browserManagerOperations.mkdtemp(
|
|
649
849
|
join2(tmpdir(), MANAGED_BROWSER_PROFILE_PREFIX)
|
|
650
850
|
);
|
|
651
851
|
let managedBrowser;
|
|
652
852
|
try {
|
|
653
|
-
throwIfBrowserLaunchCancelled(generation, signal);
|
|
654
|
-
const portArgument = state.portConfigured ? String(
|
|
853
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
854
|
+
const portArgument = state.portConfigured ? String(managedBrowserPort(owner)) : "0";
|
|
655
855
|
const args = buildManagedBrowserLaunchArguments(
|
|
656
856
|
userDataDir,
|
|
657
857
|
portArgument,
|
|
@@ -666,37 +866,42 @@ async function launchBrowserCandidate(candidate, waitMs, generation, signal) {
|
|
|
666
866
|
userDataDir,
|
|
667
867
|
exited: false,
|
|
668
868
|
ready: false,
|
|
669
|
-
ownerGeneration: generation
|
|
869
|
+
ownerGeneration: generation,
|
|
870
|
+
...owner ? { sessionOwner: owner } : {}
|
|
670
871
|
};
|
|
671
872
|
managedBrowser = launchedBrowser;
|
|
672
|
-
|
|
873
|
+
setManagedBrowser(owner, launchedBrowser);
|
|
673
874
|
child.once("exit", () => {
|
|
875
|
+
if (managedBrowserForOwner(owner) === launchedBrowser && owner) {
|
|
876
|
+
invalidateWebMcpOperations(owner, "Chrome DevTools managed browser exited");
|
|
877
|
+
}
|
|
674
878
|
launchedBrowser.exited = true;
|
|
675
879
|
launchedBrowser.ready = false;
|
|
676
|
-
if (!state.portConfigured && launchedBrowser.port ===
|
|
677
|
-
|
|
880
|
+
if (!state.portConfigured && launchedBrowser.port === managedBrowserPort(owner)) {
|
|
881
|
+
setManagedBrowserPort(owner, state.configuredPort);
|
|
678
882
|
}
|
|
679
883
|
});
|
|
680
884
|
await waitForBrowserSpawn(child);
|
|
681
|
-
throwIfBrowserLaunchCancelled(generation, signal);
|
|
885
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
682
886
|
if (state.portConfigured) {
|
|
683
|
-
launchedBrowser.port =
|
|
887
|
+
launchedBrowser.port = managedBrowserPort(owner);
|
|
684
888
|
} else {
|
|
685
889
|
launchedBrowser.port = await readManagedBrowserPort(
|
|
686
890
|
userDataDir,
|
|
687
891
|
launchedBrowser,
|
|
688
892
|
waitMs,
|
|
689
893
|
generation,
|
|
690
|
-
signal
|
|
894
|
+
signal,
|
|
895
|
+
owner
|
|
691
896
|
);
|
|
692
|
-
throwIfBrowserLaunchCancelled(generation, signal);
|
|
693
|
-
|
|
897
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
898
|
+
setManagedBrowserPort(owner, launchedBrowser.port);
|
|
694
899
|
}
|
|
695
|
-
await waitForDevToolsEndpoint(waitMs, launchedBrowser, generation, signal);
|
|
696
|
-
throwIfBrowserLaunchCancelled(generation, signal);
|
|
900
|
+
await waitForDevToolsEndpoint(waitMs, launchedBrowser, generation, signal, owner);
|
|
901
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
697
902
|
launchedBrowser.ready = true;
|
|
698
903
|
} catch (error) {
|
|
699
|
-
if (managedBrowser) await shutdownManagedBrowser(managedBrowser, { awaitLaunch: false });
|
|
904
|
+
if (managedBrowser) await shutdownManagedBrowser(managedBrowser, { awaitLaunch: false, owner });
|
|
700
905
|
else {
|
|
701
906
|
await browserManagerOperations.rm(userDataDir, { recursive: true, force: true }).catch(() => void 0);
|
|
702
907
|
}
|
|
@@ -734,7 +939,7 @@ function waitForBrowserSpawn(child) {
|
|
|
734
939
|
child.once("spawn", onSpawn);
|
|
735
940
|
});
|
|
736
941
|
}
|
|
737
|
-
async function readManagedBrowserPort(userDataDir, managedBrowser, waitMs, generation, signal) {
|
|
942
|
+
async function readManagedBrowserPort(userDataDir, managedBrowser, waitMs, generation, signal, owner) {
|
|
738
943
|
const activePortFile = join2(userDataDir, DEVTOOLS_ACTIVE_PORT_FILE);
|
|
739
944
|
const deadline = Date.now() + waitMs;
|
|
740
945
|
while (true) {
|
|
@@ -743,7 +948,7 @@ async function readManagedBrowserPort(userDataDir, managedBrowser, waitMs, gener
|
|
|
743
948
|
if (isNodeError2(error) && error.code === "ENOENT") return void 0;
|
|
744
949
|
throw error;
|
|
745
950
|
});
|
|
746
|
-
throwIfBrowserLaunchCancelled(generation, signal);
|
|
951
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
747
952
|
const portText = text?.split(/\r?\n/, 1)[0]?.trim();
|
|
748
953
|
const port = Number(portText);
|
|
749
954
|
if (Number.isInteger(port) && port > 0) return port;
|
|
@@ -760,23 +965,23 @@ async function readManagedBrowserPort(userDataDir, managedBrowser, waitMs, gener
|
|
|
760
965
|
await browserManagerOperations.sleep(Math.min(DEFAULT_ENDPOINT_RETRY_MS, remainingMs), signal);
|
|
761
966
|
}
|
|
762
967
|
}
|
|
763
|
-
async function waitForDevToolsEndpoint(waitMs, managedBrowser, generation, signal) {
|
|
968
|
+
async function waitForDevToolsEndpoint(waitMs, managedBrowser, generation, signal, owner) {
|
|
764
969
|
const deadline = Date.now() + waitMs;
|
|
765
970
|
while (true) {
|
|
766
971
|
throwIfManagedBrowserExited(managedBrowser);
|
|
767
972
|
try {
|
|
768
|
-
await fetchDevToolsJson("/json/version", { signal });
|
|
769
|
-
throwIfBrowserLaunchCancelled(generation, signal);
|
|
973
|
+
await fetchDevToolsJson("/json/version", { signal }, owner);
|
|
974
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
770
975
|
return;
|
|
771
976
|
} catch (error) {
|
|
772
977
|
if (!isRetryableEndpointError(error)) throw error;
|
|
773
978
|
}
|
|
774
|
-
throwIfBrowserLaunchCancelled(generation, signal);
|
|
979
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
775
980
|
const remainingMs = deadline - Date.now();
|
|
776
981
|
if (remainingMs <= 0) {
|
|
777
982
|
throw new DevToolsEndpointError(
|
|
778
983
|
[
|
|
779
|
-
`Timed out waiting for auto-launched browser at ${devToolsEndpoint()}.`,
|
|
984
|
+
`Timed out waiting for auto-launched browser at ${devToolsEndpoint(owner)}.`,
|
|
780
985
|
launchHint()
|
|
781
986
|
].join("\n")
|
|
782
987
|
);
|
|
@@ -788,28 +993,44 @@ function throwIfManagedBrowserExited(managedBrowser) {
|
|
|
788
993
|
if (!managedBrowser.exited) return;
|
|
789
994
|
throw new DevToolsEndpointError("Auto-launched browser exited before DevTools became available.");
|
|
790
995
|
}
|
|
791
|
-
function throwIfBrowserLaunchCancelled(generation, signal) {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
996
|
+
function throwIfBrowserLaunchCancelled(generation, signal, owner) {
|
|
997
|
+
const currentGeneration = owner ? managedBrowserScope(owner).generation : state.sessionGeneration;
|
|
998
|
+
const shuttingDown = owner ? signal.aborted : state.shuttingDown;
|
|
999
|
+
if (!shuttingDown && generation === currentGeneration && !signal.aborted) return;
|
|
795
1000
|
throw new DevToolsEndpointError(
|
|
796
|
-
generation ===
|
|
1001
|
+
generation === currentGeneration ? "Chrome DevTools browser launch cancelled during shutdown." : "Chrome DevTools browser launch cancelled because the session was replaced."
|
|
797
1002
|
);
|
|
798
1003
|
}
|
|
799
|
-
async function shutdownManagedBrowser(managedBrowser
|
|
800
|
-
|
|
1004
|
+
async function shutdownManagedBrowser(managedBrowser, options = {}) {
|
|
1005
|
+
const owner = options.owner ?? managedBrowser?.sessionOwner;
|
|
1006
|
+
if (options.cancelLaunch) {
|
|
1007
|
+
if (owner) {
|
|
1008
|
+
const scope = managedBrowserScope(owner);
|
|
1009
|
+
scope.controller.abort(
|
|
1010
|
+
new DOMException("Chrome DevTools browser launch cancelled during shutdown", "AbortError")
|
|
1011
|
+
);
|
|
1012
|
+
scope.generation += 1;
|
|
1013
|
+
} else {
|
|
1014
|
+
state.shuttingDown = true;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
managedBrowser ??= managedBrowserForOwner(owner);
|
|
801
1018
|
if (options.awaitLaunch !== false) {
|
|
802
|
-
await
|
|
803
|
-
managedBrowser
|
|
1019
|
+
await launchPromiseForOwner(owner)?.catch(() => void 0);
|
|
1020
|
+
managedBrowser ??= managedBrowserForOwner(owner);
|
|
804
1021
|
}
|
|
805
1022
|
if (!managedBrowser) return;
|
|
806
1023
|
if (managedBrowser.cleanupPromise) return managedBrowser.cleanupPromise;
|
|
807
|
-
const cleanup = cleanupManagedBrowser(managedBrowser);
|
|
1024
|
+
const cleanup = cleanupManagedBrowser(managedBrowser, owner);
|
|
808
1025
|
managedBrowser.cleanupPromise = cleanup;
|
|
809
1026
|
return cleanup;
|
|
810
1027
|
}
|
|
811
|
-
async function cleanupManagedBrowser(managedBrowser) {
|
|
812
|
-
if (
|
|
1028
|
+
async function cleanupManagedBrowser(managedBrowser, owner) {
|
|
1029
|
+
if (managedBrowserForOwner(owner) === managedBrowser) {
|
|
1030
|
+
if (owner) invalidateWebMcpOperations(owner, "Chrome DevTools managed browser closed");
|
|
1031
|
+
if (state.managedBrowser === managedBrowser) state.managedBrowser = void 0;
|
|
1032
|
+
else setManagedBrowser(owner, void 0);
|
|
1033
|
+
}
|
|
813
1034
|
if (!managedBrowser.exited) {
|
|
814
1035
|
killManagedBrowserProcess(managedBrowser);
|
|
815
1036
|
await waitForManagedBrowserExit(managedBrowser, BROWSER_SHUTDOWN_WAIT_MS).catch(async () => {
|
|
@@ -820,8 +1041,8 @@ async function cleanupManagedBrowser(managedBrowser) {
|
|
|
820
1041
|
});
|
|
821
1042
|
}
|
|
822
1043
|
await browserManagerOperations.rm(managedBrowser.userDataDir, { recursive: true, force: true }).catch(() => void 0);
|
|
823
|
-
if (!state.portConfigured && managedBrowser.port ===
|
|
824
|
-
|
|
1044
|
+
if (!state.portConfigured && managedBrowser.port === managedBrowserPort(owner)) {
|
|
1045
|
+
setManagedBrowserPort(owner, state.configuredPort);
|
|
825
1046
|
}
|
|
826
1047
|
}
|
|
827
1048
|
function killManagedBrowserProcess(managedBrowser, signal) {
|
|
@@ -851,8 +1072,8 @@ function waitForManagedBrowserExit(managedBrowser, waitMs) {
|
|
|
851
1072
|
managedBrowser.process.once("close", onExitOrClose);
|
|
852
1073
|
});
|
|
853
1074
|
}
|
|
854
|
-
async function fetchDevToolsJson(path, init) {
|
|
855
|
-
const url = `${devToolsEndpoint()}${path}`;
|
|
1075
|
+
async function fetchDevToolsJson(path, init, owner) {
|
|
1076
|
+
const url = `${devToolsEndpoint(owner)}${path}`;
|
|
856
1077
|
let response;
|
|
857
1078
|
try {
|
|
858
1079
|
const timeoutSignal = AbortSignal.timeout(DEFAULT_HTTP_TIMEOUT_MS);
|
|
@@ -861,7 +1082,7 @@ async function fetchDevToolsJson(path, init) {
|
|
|
861
1082
|
signal: init?.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal
|
|
862
1083
|
});
|
|
863
1084
|
} catch (error) {
|
|
864
|
-
throw new DevToolsEndpointError(endpointConnectionErrorMessage(error), {
|
|
1085
|
+
throw new DevToolsEndpointError(endpointConnectionErrorMessage(error, owner), {
|
|
865
1086
|
launchable: true,
|
|
866
1087
|
retryable: true
|
|
867
1088
|
});
|
|
@@ -888,16 +1109,20 @@ async function fetchDevToolsJson(path, init) {
|
|
|
888
1109
|
);
|
|
889
1110
|
}
|
|
890
1111
|
}
|
|
891
|
-
async function withEndpointRetry(operation, waitMs) {
|
|
1112
|
+
async function withEndpointRetry(operation, waitMs, signal) {
|
|
892
1113
|
const deadline = Date.now() + waitMs;
|
|
893
1114
|
while (true) {
|
|
1115
|
+
signal?.throwIfAborted();
|
|
894
1116
|
try {
|
|
895
1117
|
return await operation();
|
|
896
1118
|
} catch (error) {
|
|
897
1119
|
if (!isRetryableEndpointError(error)) throw error;
|
|
898
1120
|
const remainingMs = deadline - Date.now();
|
|
899
1121
|
if (remainingMs <= 0) throw error;
|
|
900
|
-
await browserManagerOperations.sleep(
|
|
1122
|
+
await browserManagerOperations.sleep(
|
|
1123
|
+
Math.min(DEFAULT_ENDPOINT_RETRY_MS, remainingMs),
|
|
1124
|
+
signal
|
|
1125
|
+
);
|
|
901
1126
|
}
|
|
902
1127
|
}
|
|
903
1128
|
}
|
|
@@ -918,10 +1143,10 @@ function canAutoLaunchBrowser() {
|
|
|
918
1143
|
function extensionsConfigured() {
|
|
919
1144
|
return state.extensionPaths.length > 0;
|
|
920
1145
|
}
|
|
921
|
-
function endpointConnectionErrorMessage(error) {
|
|
1146
|
+
function endpointConnectionErrorMessage(error, owner) {
|
|
922
1147
|
const reason = isTimeoutError(error) ? "request timed out" : "connection failed";
|
|
923
1148
|
return [
|
|
924
|
-
`Cannot connect to Chrome DevTools endpoint at ${devToolsEndpoint()} (${reason}).`,
|
|
1149
|
+
`Cannot connect to Chrome DevTools endpoint at ${devToolsEndpoint(owner)} (${reason}).`,
|
|
925
1150
|
launchHint(),
|
|
926
1151
|
endpointConfigHint()
|
|
927
1152
|
].join("\n");
|
|
@@ -929,8 +1154,8 @@ function endpointConnectionErrorMessage(error) {
|
|
|
929
1154
|
function isTimeoutError(error) {
|
|
930
1155
|
return error instanceof Error && ["AbortError", "TimeoutError"].includes(error.name);
|
|
931
1156
|
}
|
|
932
|
-
function devToolsEndpoint() {
|
|
933
|
-
return `http://${formatHostForUrl(state.host)}:${
|
|
1157
|
+
function devToolsEndpoint(owner) {
|
|
1158
|
+
return `http://${formatHostForUrl(state.host)}:${managedBrowserPort(owner)}`;
|
|
934
1159
|
}
|
|
935
1160
|
function formatHostForUrl(host) {
|
|
936
1161
|
if (host.startsWith("[") && host.endsWith("]")) return host;
|
|
@@ -939,43 +1164,41 @@ function formatHostForUrl(host) {
|
|
|
939
1164
|
function endpointSourceLabel() {
|
|
940
1165
|
return state.endpointSource;
|
|
941
1166
|
}
|
|
942
|
-
function browserLifecycleState() {
|
|
943
|
-
if (
|
|
944
|
-
if (
|
|
945
|
-
if (
|
|
946
|
-
if (
|
|
1167
|
+
function browserLifecycleState(owner) {
|
|
1168
|
+
if (launchPromiseForOwner(owner)) return "starting";
|
|
1169
|
+
if (managedBrowserForOwner(owner)?.exited) return "exited";
|
|
1170
|
+
if (managedBrowserForOwner(owner)?.ready) return "running";
|
|
1171
|
+
if (lastLaunchAttempt(owner)?.lastError) return "failed";
|
|
947
1172
|
return "unobserved";
|
|
948
1173
|
}
|
|
949
|
-
function launchModeLabel() {
|
|
1174
|
+
function launchModeLabel(owner) {
|
|
1175
|
+
const managedBrowser = managedBrowserForOwner(owner);
|
|
950
1176
|
if (extensionsConfigured()) {
|
|
951
1177
|
if (!isLocalDevToolsHost(state.host)) return "invalid; extensions require a local endpoint";
|
|
952
1178
|
if (!state.autoLaunchEnabled) return "invalid; extensions require auto-launch";
|
|
953
|
-
if (
|
|
1179
|
+
if (managedBrowser && !managedBrowser.exited) {
|
|
954
1180
|
return state.portConfigured ? "extension-owned browser on explicit port" : "extension-owned browser on dynamic port";
|
|
955
1181
|
}
|
|
956
1182
|
return state.portConfigured ? "force extension-owned launch on explicit port" : "force extension-owned launch on dynamic port";
|
|
957
1183
|
}
|
|
958
1184
|
if (!isLocalDevToolsHost(state.host)) return "manual remote endpoint";
|
|
959
1185
|
if (!state.autoLaunchEnabled) return "manual; auto-launch disabled";
|
|
960
|
-
if (
|
|
1186
|
+
if (managedBrowser && !managedBrowser.exited) {
|
|
961
1187
|
return state.portConfigured ? "auto-launched on explicit port" : "auto-launched on dynamic port";
|
|
962
1188
|
}
|
|
963
1189
|
return state.portConfigured ? "attach first; auto-launch explicit port" : "attach first; auto-launch dynamic port";
|
|
964
1190
|
}
|
|
965
|
-
function launchAttemptLines() {
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1191
|
+
function launchAttemptLines(owner) {
|
|
1192
|
+
const attempt = lastLaunchAttempt(owner);
|
|
1193
|
+
if (!attempt) return [];
|
|
1194
|
+
const lines = [`Last launch attempt: ${attempt.mode}`];
|
|
1195
|
+
if (attempt.selectedCandidate) {
|
|
1196
|
+
lines.push(`Launched browser: ${attempt.selectedCandidate}`);
|
|
970
1197
|
} else {
|
|
971
|
-
lines.push(`Tried browser candidates: ${
|
|
972
|
-
}
|
|
973
|
-
if (state.lastLaunchAttempt.userDataDir) {
|
|
974
|
-
lines.push(`Managed browser profile: ${state.lastLaunchAttempt.userDataDir}`);
|
|
975
|
-
}
|
|
976
|
-
if (state.lastLaunchAttempt.lastError) {
|
|
977
|
-
lines.push(`Last launch error: ${state.lastLaunchAttempt.lastError}`);
|
|
1198
|
+
lines.push(`Tried browser candidates: ${attempt.candidateLabels.join(", ")}`);
|
|
978
1199
|
}
|
|
1200
|
+
if (attempt.userDataDir) lines.push(`Managed browser profile: ${attempt.userDataDir}`);
|
|
1201
|
+
if (attempt.lastError) lines.push(`Last launch error: ${attempt.lastError}`);
|
|
979
1202
|
return lines;
|
|
980
1203
|
}
|
|
981
1204
|
function launchHint() {
|
|
@@ -1111,11 +1334,11 @@ function uniqueBrowserCandidates(candidates) {
|
|
|
1111
1334
|
return true;
|
|
1112
1335
|
});
|
|
1113
1336
|
}
|
|
1114
|
-
async function resolveBrowserCandidates(definitions, generation, signal) {
|
|
1337
|
+
async function resolveBrowserCandidates(definitions, generation, signal, owner) {
|
|
1115
1338
|
const candidates = [];
|
|
1116
1339
|
for (const definition of definitions) {
|
|
1117
1340
|
const resolvedExecutable = await resolveBrowserExecutable(definition.executable);
|
|
1118
|
-
throwIfBrowserLaunchCancelled(generation, signal);
|
|
1341
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
1119
1342
|
if (!resolvedExecutable) continue;
|
|
1120
1343
|
candidates.push({ ...definition, resolvedExecutable });
|
|
1121
1344
|
}
|
|
@@ -1249,412 +1472,44 @@ var DevToolsEndpointError = class extends Error {
|
|
|
1249
1472
|
}
|
|
1250
1473
|
};
|
|
1251
1474
|
|
|
1252
|
-
// src/lazy-tools.ts
|
|
1253
|
-
import {
|
|
1254
|
-
defineTool
|
|
1255
|
-
} from "@earendil-works/pi-coding-agent";
|
|
1256
|
-
import { Type } from "typebox";
|
|
1257
|
-
var CHROME_DEVTOOLS_LOAD_TOOL_NAME = "chrome_devtools_load";
|
|
1258
|
-
var AVAILABLE_TOOLS_STORE = /* @__PURE__ */ Symbol.for("@narumitw/pi-chrome-devtools.available-tools-store");
|
|
1259
|
-
var sharedGlobal = globalThis;
|
|
1260
|
-
var existingAvailableToolsStore = sharedGlobal[AVAILABLE_TOOLS_STORE];
|
|
1261
|
-
var availableToolsByApi = existingAvailableToolsStore ?? /* @__PURE__ */ new WeakMap();
|
|
1262
|
-
if (!existingAvailableToolsStore) sharedGlobal[AVAILABLE_TOOLS_STORE] = availableToolsByApi;
|
|
1263
|
-
var lazyExposureByApi = /* @__PURE__ */ new WeakMap();
|
|
1264
|
-
var SEARCH_TEXT = {
|
|
1265
|
-
chrome_devtools_list_pages: "list open inspectable chrome browser pages tabs targets",
|
|
1266
|
-
chrome_devtools_select_page: "select choose active chrome browser page tab target",
|
|
1267
|
-
chrome_devtools_navigate: "navigate open create chrome browser page url website",
|
|
1268
|
-
chrome_devtools_evaluate: "evaluate run javascript expression dom inspect chrome browser page",
|
|
1269
|
-
chrome_devtools_screenshot: "capture screenshot png image visual chrome browser page"
|
|
1270
|
-
};
|
|
1271
|
-
function initializeAvailableChromeDevtoolsTools(pi) {
|
|
1272
|
-
if (availableToolsByApi.has(pi)) return;
|
|
1273
|
-
const activeTools = new Set(pi.getActiveTools());
|
|
1274
|
-
setAvailableTools(
|
|
1275
|
-
pi,
|
|
1276
|
-
CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => activeTools.has(name))
|
|
1277
|
-
);
|
|
1278
|
-
}
|
|
1279
|
-
function configureChromeDevtoolsToolExposure(pi, availableTools, model) {
|
|
1280
|
-
const available = setAvailableTools(pi, availableTools);
|
|
1281
|
-
const lazyExposure = supportsNativeDeferredToolLoading(model);
|
|
1282
|
-
lazyExposureByApi.set(pi, lazyExposure);
|
|
1283
|
-
const exposedTools = lazyExposure ? [] : CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
|
|
1284
|
-
const nonCapabilityTools = pi.getActiveTools().filter((name) => !CHROME_DEVTOOLS_TOOL_NAMES.includes(name));
|
|
1285
|
-
pi.setActiveTools(
|
|
1286
|
-
unique([...nonCapabilityTools, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...exposedTools])
|
|
1287
|
-
);
|
|
1288
|
-
}
|
|
1289
|
-
function requireEagerChromeDevtoolsToolExposure(pi) {
|
|
1290
|
-
lazyExposureByApi.set(pi, false);
|
|
1291
|
-
const active = pi.getActiveTools();
|
|
1292
|
-
const available = availableChromeDevtoolsTools(pi);
|
|
1293
|
-
pi.setActiveTools(unique([...active, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...available]));
|
|
1294
|
-
}
|
|
1295
|
-
function applyAvailableChromeDevtoolsTools(pi, availableTools) {
|
|
1296
|
-
const available = setAvailableTools(pi, availableTools);
|
|
1297
|
-
const lazyExposure = lazyExposureByApi.get(pi) === true;
|
|
1298
|
-
const active = pi.getActiveTools().filter(
|
|
1299
|
-
(name) => !CHROME_DEVTOOLS_TOOL_NAMES.includes(name) || lazyExposure && available.has(name)
|
|
1300
|
-
);
|
|
1301
|
-
const eagerTools = lazyExposure ? [] : CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
|
|
1302
|
-
pi.setActiveTools(unique([...active, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...eagerTools]));
|
|
1303
|
-
}
|
|
1304
|
-
function chromeDevtoolsToolExposureMode(pi) {
|
|
1305
|
-
return lazyExposureByApi.get(pi) === true ? "native deferred" : "eager";
|
|
1306
|
-
}
|
|
1307
|
-
function supportsNativeDeferredToolLoading(model) {
|
|
1308
|
-
if (!model) return false;
|
|
1309
|
-
if (model.api === "anthropic-messages") {
|
|
1310
|
-
const configured = compatBoolean(model.compat, "supportsToolReferences");
|
|
1311
|
-
if (configured !== void 0) return configured;
|
|
1312
|
-
if (model.provider !== "anthropic" || model.id.includes("haiku")) return false;
|
|
1313
|
-
const version = model.id.match(/^claude-(?:opus|sonnet|fable)-(\d+)(?:-(\d+))?(?:-|$)/);
|
|
1314
|
-
if (!version) return false;
|
|
1315
|
-
const major = Number(version[1]);
|
|
1316
|
-
const minor = version[2] && version[2].length < 8 ? Number(version[2]) : 0;
|
|
1317
|
-
return major > 4 || major === 4 && minor >= 5;
|
|
1318
|
-
}
|
|
1319
|
-
if (model.api === "openai-completions") {
|
|
1320
|
-
return compatString(model.compat, "deferredToolsMode") === "kimi";
|
|
1321
|
-
}
|
|
1322
|
-
if (model.api === "openai-responses" || model.api === "openai-codex-responses") {
|
|
1323
|
-
return compatBoolean(model.compat, "supportsAdditionalTools") === true || compatBoolean(model.compat, "supportsToolSearch") === true;
|
|
1324
|
-
}
|
|
1325
|
-
return false;
|
|
1326
|
-
}
|
|
1327
|
-
function availableChromeDevtoolsTools(pi) {
|
|
1328
|
-
const available = availableToolsByApi.get(pi) ?? /* @__PURE__ */ new Set();
|
|
1329
|
-
return CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
|
|
1330
|
-
}
|
|
1331
|
-
function createChromeDevtoolsLoadTool(pi) {
|
|
1332
|
-
return defineTool({
|
|
1333
|
-
name: CHROME_DEVTOOLS_LOAD_TOOL_NAME,
|
|
1334
|
-
label: "Chrome DevTools: Load Tools",
|
|
1335
|
-
description: "Find and enable Chrome DevTools browser tools relevant to a task. Loaded tools remain available for the session.",
|
|
1336
|
-
promptSnippet: "Load Chrome DevTools browser capabilities on demand",
|
|
1337
|
-
promptGuidelines: [
|
|
1338
|
-
"Use chrome_devtools_load when a task requires inspecting or controlling a Chrome browser and the needed chrome_devtools_* capability is not active."
|
|
1339
|
-
],
|
|
1340
|
-
parameters: Type.Object({
|
|
1341
|
-
query: Type.String({
|
|
1342
|
-
description: "Browser capability or task to find tools for.",
|
|
1343
|
-
maxLength: 500
|
|
1344
|
-
}),
|
|
1345
|
-
limit: Type.Optional(
|
|
1346
|
-
Type.Integer({
|
|
1347
|
-
description: "Maximum tools to load. Defaults to 3.",
|
|
1348
|
-
minimum: 1,
|
|
1349
|
-
maximum: 5
|
|
1350
|
-
})
|
|
1351
|
-
)
|
|
1352
|
-
}),
|
|
1353
|
-
async execute(_toolCallId, params, signal) {
|
|
1354
|
-
signal?.throwIfAborted();
|
|
1355
|
-
const available = new Set(availableChromeDevtoolsTools(pi));
|
|
1356
|
-
const matches = matchChromeDevtoolsTools(params.query, params.limit ?? 3, available);
|
|
1357
|
-
const active = pi.getActiveTools();
|
|
1358
|
-
const activeSet = new Set(active);
|
|
1359
|
-
const added = matches.filter((name) => !activeSet.has(name));
|
|
1360
|
-
if (added.length > 0) {
|
|
1361
|
-
pi.setActiveTools(unique([...active, ...added]));
|
|
1362
|
-
}
|
|
1363
|
-
const text = matches.length === 0 ? "No available Chrome DevTools tools matched the query." : added.length > 0 ? `Loaded Chrome DevTools tools: ${added.join(", ")}` : `Matching Chrome DevTools tools are already loaded: ${matches.join(", ")}`;
|
|
1364
|
-
return {
|
|
1365
|
-
content: [{ type: "text", text }],
|
|
1366
|
-
details: { matches, added }
|
|
1367
|
-
};
|
|
1368
|
-
}
|
|
1369
|
-
});
|
|
1370
|
-
}
|
|
1371
|
-
function matchChromeDevtoolsTools(query, limit, available) {
|
|
1372
|
-
const terms = query.toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length >= 2);
|
|
1373
|
-
if (terms.length === 0) return [];
|
|
1374
|
-
return CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name)).map((name, index) => ({
|
|
1375
|
-
name,
|
|
1376
|
-
index,
|
|
1377
|
-
score: terms.reduce((score, term) => score + (SEARCH_TEXT[name].includes(term) ? 1 : 0), 0)
|
|
1378
|
-
})).filter((match) => match.score > 0).sort((left, right) => right.score - left.score || left.index - right.index).slice(0, limit).map((match) => match.name);
|
|
1379
|
-
}
|
|
1380
|
-
function setAvailableTools(pi, availableTools) {
|
|
1381
|
-
const available = new Set(availableTools);
|
|
1382
|
-
availableToolsByApi.set(pi, available);
|
|
1383
|
-
return available;
|
|
1384
|
-
}
|
|
1385
|
-
function compatBoolean(value, key) {
|
|
1386
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
1387
|
-
const record = value;
|
|
1388
|
-
return typeof record[key] === "boolean" ? record[key] : void 0;
|
|
1389
|
-
}
|
|
1390
|
-
function compatString(value, key) {
|
|
1391
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
1392
|
-
const record = value;
|
|
1393
|
-
return typeof record[key] === "string" ? record[key] : void 0;
|
|
1394
|
-
}
|
|
1395
|
-
function unique(values) {
|
|
1396
|
-
return [...new Set(values)];
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
// src/tool-selector.ts
|
|
1400
|
-
import { stripVTControlCharacters } from "node:util";
|
|
1401
|
-
function unique2(values) {
|
|
1402
|
-
return Array.from(new Set(values));
|
|
1403
|
-
}
|
|
1404
|
-
function formatError3(error) {
|
|
1405
|
-
return error instanceof Error ? error.message : String(error);
|
|
1406
|
-
}
|
|
1407
|
-
async function updateChromeDevtoolsTools(pi, ctx, selectedTools, action) {
|
|
1408
|
-
const generation = state.sessionGeneration;
|
|
1409
|
-
const result = await transactSelectedTools(pi, ctx, selectedTools, generation);
|
|
1410
|
-
if (result !== "saved" || generation !== state.sessionGeneration) return;
|
|
1411
|
-
const status = await buildToolStatusMessage(pi);
|
|
1412
|
-
if (generation !== state.sessionGeneration) return;
|
|
1413
|
-
ctx.ui.notify(`Chrome DevTools tool catalog ${action}.
|
|
1414
|
-
|
|
1415
|
-
${status}`, "info");
|
|
1416
|
-
}
|
|
1417
|
-
async function setSelectedChromeDevtoolsTools(pi, ctx, selectedTools, expectedActiveTools) {
|
|
1418
|
-
return transactSelectedTools(
|
|
1419
|
-
pi,
|
|
1420
|
-
ctx,
|
|
1421
|
-
selectedTools,
|
|
1422
|
-
state.sessionGeneration,
|
|
1423
|
-
expectedActiveTools
|
|
1424
|
-
);
|
|
1425
|
-
}
|
|
1426
|
-
var toolTransactionQueue = Promise.resolve();
|
|
1427
|
-
async function waitForChromeDevtoolsSettings() {
|
|
1428
|
-
await toolTransactionQueue;
|
|
1429
|
-
}
|
|
1430
|
-
function transactSelectedTools(pi, ctx, selectedTools, expectedGeneration, expectedActiveTools) {
|
|
1431
|
-
const operation = toolTransactionQueue.then(
|
|
1432
|
-
() => transactSelectedToolsNow(pi, ctx, selectedTools, expectedGeneration, expectedActiveTools)
|
|
1433
|
-
);
|
|
1434
|
-
toolTransactionQueue = operation.then(
|
|
1435
|
-
() => void 0,
|
|
1436
|
-
() => void 0
|
|
1437
|
-
);
|
|
1438
|
-
return operation;
|
|
1439
|
-
}
|
|
1440
|
-
async function transactSelectedToolsNow(pi, ctx, selectedTools, expectedGeneration, expectedActiveTools) {
|
|
1441
|
-
if (expectedGeneration !== state.sessionGeneration) return "failed";
|
|
1442
|
-
if (expectedActiveTools && !arraysEqual(availableChromeDevtoolsTools(pi), expectedActiveTools)) {
|
|
1443
|
-
ctx.ui.notify(
|
|
1444
|
-
"Browser tool selection changed while review was open. Review the current state, then apply again.",
|
|
1445
|
-
"warning"
|
|
1446
|
-
);
|
|
1447
|
-
return "active-tools-changed";
|
|
1448
|
-
}
|
|
1449
|
-
const previousActiveTools = pi.getActiveTools();
|
|
1450
|
-
const previousAvailableTools = availableChromeDevtoolsTools(pi);
|
|
1451
|
-
try {
|
|
1452
|
-
applyChromeDevtoolsTools(pi, selectedTools);
|
|
1453
|
-
await persistSettings(selectedTools);
|
|
1454
|
-
return expectedGeneration === state.sessionGeneration ? "saved" : "failed";
|
|
1455
|
-
} catch (error) {
|
|
1456
|
-
let rollbackError;
|
|
1457
|
-
try {
|
|
1458
|
-
applyAvailableChromeDevtoolsTools(pi, previousAvailableTools);
|
|
1459
|
-
const currentNonChromeTools = pi.getActiveTools().filter((name) => !CHROME_DEVTOOLS_TOOL_NAMES.includes(name));
|
|
1460
|
-
const previousLoadedChromeTools = previousActiveTools.filter(
|
|
1461
|
-
(name) => CHROME_DEVTOOLS_TOOL_NAMES.includes(name)
|
|
1462
|
-
);
|
|
1463
|
-
const restoredChromeTools = chromeDevtoolsToolExposureMode(pi) === "eager" ? previousAvailableTools : previousLoadedChromeTools;
|
|
1464
|
-
pi.setActiveTools(unique2([...currentNonChromeTools, ...restoredChromeTools]));
|
|
1465
|
-
} catch (caught) {
|
|
1466
|
-
rollbackError = caught;
|
|
1467
|
-
}
|
|
1468
|
-
if (expectedGeneration !== state.sessionGeneration) return "failed";
|
|
1469
|
-
ctx.ui.notify(
|
|
1470
|
-
sanitizeChromeDevtoolsDisplay(
|
|
1471
|
-
rollbackError ? `Chrome DevTools settings save failed: ${formatError3(error)}; active-tool rollback failed: ${formatError3(rollbackError)}` : `Chrome DevTools settings save failed; active tools restored: ${formatError3(error)}`
|
|
1472
|
-
),
|
|
1473
|
-
"warning"
|
|
1474
|
-
);
|
|
1475
|
-
return "failed";
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
function arraysEqual(left, right) {
|
|
1479
|
-
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
1480
|
-
}
|
|
1481
|
-
function applyChromeDevtoolsTools(pi, selectedTools) {
|
|
1482
|
-
applyAvailableChromeDevtoolsTools(pi, selectedTools);
|
|
1483
|
-
}
|
|
1484
|
-
function getToolStatusSummary(pi) {
|
|
1485
|
-
const chromeToolNames = new Set(CHROME_DEVTOOLS_TOOL_NAMES);
|
|
1486
|
-
const activeToolNames = new Set(pi.getActiveTools());
|
|
1487
|
-
const loadedChromeToolCount = CHROME_DEVTOOLS_TOOL_NAMES.filter(
|
|
1488
|
-
(name) => activeToolNames.has(name)
|
|
1489
|
-
).length;
|
|
1490
|
-
const availableChromeToolCount = availableChromeDevtoolsTools(pi).length;
|
|
1491
|
-
const activeNonChromeToolCount = Array.from(activeToolNames).filter(
|
|
1492
|
-
(name) => !chromeToolNames.has(name) && name !== CHROME_DEVTOOLS_LOAD_TOOL_NAME
|
|
1493
|
-
).length;
|
|
1494
|
-
const availabilityStatus = availableChromeToolCount === CHROME_DEVTOOLS_TOOL_NAMES.length ? "enabled" : availableChromeToolCount === 0 ? "disabled" : "partial";
|
|
1495
|
-
return {
|
|
1496
|
-
availabilityStatus,
|
|
1497
|
-
availableChromeToolCount,
|
|
1498
|
-
loadedChromeToolCount,
|
|
1499
|
-
activeNonChromeToolCount
|
|
1500
|
-
};
|
|
1501
|
-
}
|
|
1502
|
-
async function buildToolStatusMessage(pi) {
|
|
1503
|
-
const summary = getToolStatusSummary(pi);
|
|
1504
|
-
const persistedSetting = await persistedSettingLabel();
|
|
1505
|
-
return sanitizeChromeDevtoolsDisplay(
|
|
1506
|
-
[
|
|
1507
|
-
`Chrome DevTools tools available: ${formatRuntimeStatus(summary)}`,
|
|
1508
|
-
`Tool exposure: ${chromeDevtoolsToolExposureMode(pi)}`,
|
|
1509
|
-
`Loaded capability tools this session: ${summary.loadedChromeToolCount}/${CHROME_DEVTOOLS_TOOL_NAMES.length}`,
|
|
1510
|
-
`Loader: ${pi.getActiveTools().includes(CHROME_DEVTOOLS_LOAD_TOOL_NAME) ? "active" : "inactive"}`,
|
|
1511
|
-
`Persisted tool catalog: ${persistedSetting}`,
|
|
1512
|
-
...browserSettingsStatusLines(),
|
|
1513
|
-
...state.settingsNotice ? [`Settings note: ${state.settingsNotice}`] : [],
|
|
1514
|
-
`Other active tools preserved: ${summary.activeNonChromeToolCount}`,
|
|
1515
|
-
`Endpoint: ${devToolsEndpoint()}`,
|
|
1516
|
-
`Endpoint source: ${endpointSourceLabel()}`,
|
|
1517
|
-
`Launch mode: ${launchModeLabel()}`,
|
|
1518
|
-
...launchAttemptLines()
|
|
1519
|
-
].join("\n")
|
|
1520
|
-
);
|
|
1521
|
-
}
|
|
1522
|
-
function buildQuickstartMessage() {
|
|
1523
|
-
return buildSettingsSetupMessage();
|
|
1524
|
-
}
|
|
1525
|
-
function buildBrowserStatusMessage() {
|
|
1526
|
-
const lifecycle = browserLifecycleState();
|
|
1527
|
-
const browserState = lifecycle === "starting" ? "starting managed browser" : lifecycle === "running" ? "managed browser running" : lifecycle === "exited" ? "managed browser exited" : lifecycle === "failed" ? "last launch failed" : "not started; connection has not been checked";
|
|
1528
|
-
const needsRecovery = lifecycle === "exited" || lifecycle === "failed";
|
|
1529
|
-
return sanitizeChromeDevtoolsDisplay(
|
|
1530
|
-
[
|
|
1531
|
-
`Browser: ${browserState}`,
|
|
1532
|
-
"Viewing this status does not probe the endpoint or launch Chrome.",
|
|
1533
|
-
`Endpoint: ${devToolsEndpoint()}`,
|
|
1534
|
-
`Endpoint source: ${endpointSourceLabel()}`,
|
|
1535
|
-
`Launch mode: ${launchModeLabel()}`,
|
|
1536
|
-
`Unpacked extensions: ${state.extensionPaths.length} (${state.extensionPathsSource})`,
|
|
1537
|
-
...state.extensionPaths.length > 0 ? ["Unpacked extensions execute trusted browser code in an isolated managed browser."] : [],
|
|
1538
|
-
...launchAttemptLines(),
|
|
1539
|
-
...needsRecovery ? [launchHint(), endpointConfigHint()] : []
|
|
1540
|
-
].join("\n")
|
|
1541
|
-
);
|
|
1542
|
-
}
|
|
1543
|
-
function buildSettingsSetupMessage() {
|
|
1544
|
-
return sanitizeChromeDevtoolsDisplay(
|
|
1545
|
-
[
|
|
1546
|
-
`Chrome DevTools endpoint: ${devToolsEndpoint()}`,
|
|
1547
|
-
`Endpoint source: ${endpointSourceLabel()}`,
|
|
1548
|
-
`Launch mode: ${launchModeLabel()}`,
|
|
1549
|
-
...browserSettingsStatusLines(),
|
|
1550
|
-
launchHint(),
|
|
1551
|
-
browserCandidateHint(),
|
|
1552
|
-
...launchAttemptLines(),
|
|
1553
|
-
endpointConfigHint()
|
|
1554
|
-
].join("\n")
|
|
1555
|
-
);
|
|
1556
|
-
}
|
|
1557
|
-
function sanitizeChromeDevtoolsDisplay(value, maxCharacters = 5e4) {
|
|
1558
|
-
const sanitized = Array.from(stripVTControlCharacters(value), (character) => {
|
|
1559
|
-
const codePoint = character.codePointAt(0) ?? 0;
|
|
1560
|
-
const unsafeControl = codePoint >= 0 && codePoint <= 8 || codePoint >= 11 && codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
|
|
1561
|
-
return unsafeControl ? "\uFFFD" : character;
|
|
1562
|
-
}).join("");
|
|
1563
|
-
if (sanitized.length <= maxCharacters) return sanitized;
|
|
1564
|
-
return `${sanitized.slice(0, Math.max(0, maxCharacters - 1))}\u2026`;
|
|
1565
|
-
}
|
|
1566
|
-
function browserSettingsStatusLines() {
|
|
1567
|
-
const extensionLines = state.extensionPaths.length > 0 ? state.extensionPaths.map((extensionPath) => ` - ${extensionPath}`) : [" - none"];
|
|
1568
|
-
return [
|
|
1569
|
-
`Settings file: ${state.settingsFilePath ?? settingsFilePath()} (user)`,
|
|
1570
|
-
...state.projectSettingsFilePath ? [
|
|
1571
|
-
`Project settings: ${state.projectSettingsFilePath} (${state.projectSettingsTrusted ? "trusted" : "untrusted; ignored"})`
|
|
1572
|
-
] : [],
|
|
1573
|
-
`Auto-launch: ${state.autoLaunchEnabled ? "on" : "off"} (${state.autoLaunchSource})`,
|
|
1574
|
-
`Browser executable: ${state.browserExecutable ?? "automatic discovery"} (${state.browserExecutableSource})`,
|
|
1575
|
-
`Unpacked extensions (${state.extensionPathsSource}):`,
|
|
1576
|
-
...extensionLines,
|
|
1577
|
-
"Confirmed menu settings apply before the next browser connection; manual JSON edits require /reload or session replacement.",
|
|
1578
|
-
...state.extensionPaths.length > 0 ? [
|
|
1579
|
-
"Unpacked extensions require Chrome for Testing or Chromium and execute trusted browser code."
|
|
1580
|
-
] : []
|
|
1581
|
-
];
|
|
1582
|
-
}
|
|
1583
|
-
function buildCommandGuide() {
|
|
1584
|
-
return [
|
|
1585
|
-
"Chrome DevTools commands:",
|
|
1586
|
-
"/chrome-devtools \u2014 open this menu",
|
|
1587
|
-
"/chrome-devtools help \u2014 show command usage",
|
|
1588
|
-
"/chrome-devtools quickstart \u2014 show endpoint and launch help",
|
|
1589
|
-
"/chrome-devtools status \u2014 show tool and settings status",
|
|
1590
|
-
"/chrome-devtools settings \u2014 edit browser connection settings",
|
|
1591
|
-
"/chrome-devtools tools \u2014 choose available Chrome DevTools tools",
|
|
1592
|
-
"/chrome-devtools toggle|select \u2014 compatibility aliases for tools",
|
|
1593
|
-
"/chrome-devtools enable|on \u2014 make all Chrome DevTools tools available",
|
|
1594
|
-
"/chrome-devtools disable|off \u2014 make all Chrome DevTools capability tools unavailable"
|
|
1595
|
-
].join("\n");
|
|
1596
|
-
}
|
|
1597
|
-
function allChromeDevtoolsTools() {
|
|
1598
|
-
return [...CHROME_DEVTOOLS_TOOL_NAMES];
|
|
1599
|
-
}
|
|
1600
|
-
function formatRuntimeStatus(summary) {
|
|
1601
|
-
return `${summary.availabilityStatus} (${summary.availableChromeToolCount}/${CHROME_DEVTOOLS_TOOL_NAMES.length} available)`;
|
|
1602
|
-
}
|
|
1603
|
-
async function persistedSettingLabel() {
|
|
1604
|
-
const settings = await loadSettings();
|
|
1605
|
-
if (settings.kind === "loaded" && settings.settings.tools) {
|
|
1606
|
-
return formatPersistedSelection(settings.settings.tools);
|
|
1607
|
-
}
|
|
1608
|
-
if (settings.kind === "invalid") {
|
|
1609
|
-
return `none; current active-tool policy preserved (invalid settings ignored: ${settings.reason})`;
|
|
1610
|
-
}
|
|
1611
|
-
return "none; current active-tool policy preserved";
|
|
1612
|
-
}
|
|
1613
|
-
function formatPersistedSelection(tools) {
|
|
1614
|
-
if (tools.length === CHROME_DEVTOOLS_TOOL_NAMES.length) {
|
|
1615
|
-
return `all available (${tools.length}/${CHROME_DEVTOOLS_TOOL_NAMES.length} selected)`;
|
|
1616
|
-
}
|
|
1617
|
-
if (tools.length === 0)
|
|
1618
|
-
return `all unavailable (0/${CHROME_DEVTOOLS_TOOL_NAMES.length} selected)`;
|
|
1619
|
-
return `${tools.length}/${CHROME_DEVTOOLS_TOOL_NAMES.length} selected: ${tools.join(", ")}`;
|
|
1620
|
-
}
|
|
1621
|
-
async function persistSettings(selectedTools) {
|
|
1622
|
-
await saveSettings({ tools: [...selectedTools], updatedAt: Date.now() });
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
1475
|
export {
|
|
1476
|
+
CORE_CHROME_DEVTOOLS_TOOL_NAMES,
|
|
1477
|
+
WEBMCP_TOOL_NAMES,
|
|
1626
1478
|
CHROME_DEVTOOLS_TOOL_NAMES,
|
|
1479
|
+
isWebMcpToolName,
|
|
1627
1480
|
waitForSettingsWrites,
|
|
1628
1481
|
loadSettings,
|
|
1629
1482
|
parseBrowserEndpoint,
|
|
1483
|
+
saveSettings,
|
|
1484
|
+
saveWebMcpSettings,
|
|
1630
1485
|
saveBrowserSettings,
|
|
1486
|
+
settingsFilePath,
|
|
1631
1487
|
DEFAULT_TIMEOUT_MS,
|
|
1632
1488
|
DEFAULT_ENDPOINT_WAIT_MS,
|
|
1633
1489
|
state,
|
|
1490
|
+
setWebMcpSessionOwner,
|
|
1491
|
+
beginWebMcpOperation,
|
|
1492
|
+
invalidateWebMcpOperations,
|
|
1493
|
+
currentWebMcpGeneration,
|
|
1494
|
+
webMcpSessionSignal,
|
|
1495
|
+
applyRuntimeWebMcpSetting,
|
|
1496
|
+
webMcpEnabled,
|
|
1497
|
+
webMcpOperationIsCurrent,
|
|
1634
1498
|
applyRuntimeBrowserSettings,
|
|
1499
|
+
startManagedBrowserSession,
|
|
1500
|
+
managedBrowserForOwner,
|
|
1635
1501
|
ensureDevToolsEndpoint,
|
|
1636
1502
|
shutdownManagedBrowser,
|
|
1637
1503
|
fetchDevToolsJson,
|
|
1638
1504
|
withEndpointRetry,
|
|
1639
1505
|
devToolsEndpoint,
|
|
1506
|
+
endpointSourceLabel,
|
|
1640
1507
|
browserLifecycleState,
|
|
1641
1508
|
launchModeLabel,
|
|
1509
|
+
launchAttemptLines,
|
|
1642
1510
|
launchHint,
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
requireEagerChromeDevtoolsToolExposure,
|
|
1647
|
-
supportsNativeDeferredToolLoading,
|
|
1648
|
-
availableChromeDevtoolsTools,
|
|
1649
|
-
createChromeDevtoolsLoadTool,
|
|
1650
|
-
updateChromeDevtoolsTools,
|
|
1651
|
-
setSelectedChromeDevtoolsTools,
|
|
1652
|
-
waitForChromeDevtoolsSettings,
|
|
1653
|
-
buildToolStatusMessage,
|
|
1654
|
-
buildQuickstartMessage,
|
|
1655
|
-
buildBrowserStatusMessage,
|
|
1656
|
-
sanitizeChromeDevtoolsDisplay,
|
|
1657
|
-
buildCommandGuide,
|
|
1658
|
-
allChromeDevtoolsTools
|
|
1511
|
+
browserCandidateHint,
|
|
1512
|
+
endpointConfigHint,
|
|
1513
|
+
formatPageListItem
|
|
1659
1514
|
};
|
|
1660
|
-
//# sourceMappingURL=chunk-
|
|
1515
|
+
//# sourceMappingURL=chunk-GVBXT3FB.js.map
|