@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.
Files changed (35) hide show
  1. package/README.md +62 -9
  2. package/dist/chunks/{browser-settings-menu-VWCNG4HB.js → browser-settings-menu-REIU7C3Q.js} +83 -7
  3. package/dist/chunks/browser-settings-menu-REIU7C3Q.js.map +7 -0
  4. package/dist/chunks/chunk-GTTYS5GT.js +384 -0
  5. package/dist/chunks/chunk-GTTYS5GT.js.map +7 -0
  6. package/dist/chunks/{chunk-RH6KESL3.js → chunk-SRT3UUIP.js} +473 -574
  7. package/dist/chunks/chunk-SRT3UUIP.js.map +7 -0
  8. package/dist/chunks/chunk-YJVYJ2L2.js +469 -0
  9. package/dist/chunks/chunk-YJVYJ2L2.js.map +7 -0
  10. package/dist/chunks/{menu-PLD3S3EO.js → menu-H5HMVGHI.js} +72 -39
  11. package/dist/chunks/menu-H5HMVGHI.js.map +7 -0
  12. package/dist/chunks/tools-6P7EUX37.js +964 -0
  13. package/dist/chunks/tools-6P7EUX37.js.map +7 -0
  14. package/dist/index.ts +166 -199
  15. package/dist/index.ts.map +4 -4
  16. package/package.json +4 -3
  17. package/src/browser-manager.ts +408 -169
  18. package/src/browser-settings-menu.ts +100 -5
  19. package/src/cdp-client.ts +347 -90
  20. package/src/chrome-devtools.ts +58 -11
  21. package/src/lazy-tools.ts +37 -4
  22. package/src/menu.ts +80 -39
  23. package/src/render.ts +12 -1
  24. package/src/runtime.ts +92 -0
  25. package/src/settings.ts +47 -10
  26. package/src/tool-names.ts +16 -1
  27. package/src/tool-selector.ts +85 -44
  28. package/src/tools.ts +91 -15
  29. package/src/webmcp/discovery.ts +235 -0
  30. package/src/webmcp/policy.ts +409 -0
  31. package/src/webmcp/protocol.ts +426 -0
  32. package/src/webmcp/tools.ts +190 -0
  33. package/dist/chunks/browser-settings-menu-VWCNG4HB.js.map +0 -7
  34. package/dist/chunks/chunk-RH6KESL3.js.map +0 -7
  35. package/dist/chunks/menu-PLD3S3EO.js.map +0 -7
@@ -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 CHROME_DEVTOOLS_TOOL_NAMES = [
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 = projectMachineSettingsWarnings(parsed, scope, filePath);
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 projectMachineSettingsWarnings(document, scope, filePath) {
182
- if (scope !== "project" || !isRecord(document.browser)) return [];
183
- const browser = document.browser;
184
- return ["endpoint", "autoLaunch", "executablePath"].filter((field) => browser[field] !== void 0).map(
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,94 @@ 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 cloneBrowserSettings(browser) {
591
+ return { ...browser, extensionPaths: [...browser.extensionPaths] };
592
+ }
593
+ function runtimeBrowserSettings() {
594
+ return {
595
+ endpoint: `http://${formatHostForUrl(state.host)}:${state.configuredPort}`,
596
+ host: state.host,
597
+ port: state.configuredPort,
598
+ hostConfigured: state.hostConfigured,
599
+ portConfigured: state.portConfigured,
600
+ autoLaunchEnabled: state.autoLaunchEnabled,
601
+ ...state.browserExecutable ? { executablePath: state.browserExecutable } : {},
602
+ extensionPaths: [...state.extensionPaths],
603
+ endpointSource: state.endpointSource,
604
+ autoLaunchSource: state.autoLaunchSource,
605
+ executablePathSource: state.browserExecutableSource,
606
+ extensionPathsSource: state.extensionPathsSource
607
+ };
608
+ }
609
+ function managedBrowserScope(owner) {
610
+ const existing = managedBrowserScopes.get(owner);
611
+ if (existing) return existing;
612
+ const browser = runtimeBrowserSettings();
613
+ const created = {
614
+ browser,
615
+ controller: new AbortController(),
616
+ generation: 0,
617
+ port: browser.port
618
+ };
619
+ managedBrowserScopes.set(owner, created);
620
+ return created;
621
+ }
622
+ function startManagedBrowserSession(owner) {
623
+ const scope = managedBrowserScope(owner);
624
+ scope.controller.abort(new DOMException("Chrome DevTools session replaced", "AbortError"));
625
+ scope.controller = new AbortController();
626
+ scope.generation += 1;
627
+ scope.lastLaunchAttempt = void 0;
628
+ }
629
+ function syncManagedBrowserSettings(owner, browser) {
630
+ const scope = managedBrowserScope(owner);
631
+ scope.browser = cloneBrowserSettings(browser);
632
+ scope.port = browser.port;
633
+ }
634
+ function browserSettingsForOwner(owner) {
635
+ return owner ? managedBrowserScope(owner).browser : runtimeBrowserSettings();
636
+ }
637
+ function managedBrowserExtensionPaths(owner) {
638
+ return browserSettingsForOwner(owner).extensionPaths;
639
+ }
640
+ function managedBrowserForOwner(owner) {
641
+ if (!owner) return state.managedBrowser;
642
+ const scopedBrowser = managedBrowserScope(owner).managedBrowser;
643
+ if (scopedBrowser) return scopedBrowser;
644
+ return state.managedBrowser?.sessionOwner === owner ? state.managedBrowser : void 0;
645
+ }
646
+ function setManagedBrowser(owner, browser) {
647
+ if (owner) managedBrowserScope(owner).managedBrowser = browser;
648
+ else state.managedBrowser = browser;
649
+ }
650
+ function launchPromiseForOwner(owner) {
651
+ return owner ? managedBrowserScope(owner).launchPromise : state.launchPromise;
652
+ }
653
+ function setLaunchPromise(owner, launchPromise) {
654
+ if (owner) managedBrowserScope(owner).launchPromise = launchPromise;
655
+ else state.launchPromise = launchPromise;
656
+ }
657
+ function managedBrowserPort(owner) {
658
+ return owner ? managedBrowserScope(owner).port : state.port;
659
+ }
660
+ function managedBrowserConfiguredPort(owner) {
661
+ return browserSettingsForOwner(owner).port;
662
+ }
663
+ function managedBrowserPortConfigured(owner) {
664
+ return browserSettingsForOwner(owner).portConfigured;
665
+ }
666
+ function setManagedBrowserPort(owner, port) {
667
+ if (owner) managedBrowserScope(owner).port = port;
668
+ else state.port = port;
669
+ }
670
+ function lastLaunchAttempt(owner) {
671
+ return owner ? managedBrowserScope(owner).lastLaunchAttempt : state.lastLaunchAttempt;
672
+ }
673
+ function setLastLaunchAttempt(owner, attempt) {
674
+ if (owner) managedBrowserScope(owner).lastLaunchAttempt = attempt;
675
+ else state.lastLaunchAttempt = attempt;
676
+ }
486
677
  function formatError2(error) {
487
678
  return error instanceof Error ? error.message : String(error);
488
679
  }
@@ -492,142 +683,189 @@ function isNodeError2(error) {
492
683
  function normalizePathForComparison(value) {
493
684
  return process.platform === "win32" ? value.toLowerCase() : value;
494
685
  }
495
- async function ensureDevToolsEndpoint(waitMs = DEFAULT_ENDPOINT_WAIT_MS) {
496
- if (extensionsConfigured()) {
497
- validateExtensionLaunchMode();
498
- await ensureManagedBrowserLaunched(waitMs);
686
+ async function ensureDevToolsEndpoint(waitMs = DEFAULT_ENDPOINT_WAIT_MS, callerSignal, owner) {
687
+ callerSignal?.throwIfAborted();
688
+ if (extensionsConfigured(owner)) {
689
+ validateExtensionLaunchMode(owner);
690
+ await ensureManagedBrowserLaunched(waitMs, callerSignal, owner);
499
691
  return;
500
692
  }
501
- if (canAutoLaunchBrowser()) {
693
+ if (canAutoLaunchBrowser(owner)) {
502
694
  try {
503
- await withEndpointRetry(() => fetchDevToolsJson("/json/version"), waitMs);
695
+ await withEndpointRetry(
696
+ () => fetchDevToolsJson("/json/version", { signal: callerSignal }, owner),
697
+ waitMs,
698
+ callerSignal
699
+ );
504
700
  return;
505
701
  } catch (error) {
506
- if (shouldAutoLaunchAfterEndpointError(error)) {
507
- await ensureManagedBrowserLaunched(waitMs);
702
+ if (shouldAutoLaunchAfterEndpointError(error, owner)) {
703
+ await ensureManagedBrowserLaunched(waitMs, callerSignal, owner);
508
704
  return;
509
705
  }
510
706
  throw error;
511
707
  }
512
708
  }
513
709
  try {
514
- await fetchDevToolsJson("/json/version");
710
+ await fetchDevToolsJson("/json/version", { signal: callerSignal }, owner);
515
711
  } catch (error) {
516
712
  if (isRetryableEndpointError(error)) return;
517
713
  throw error;
518
714
  }
519
715
  }
520
- function validateExtensionLaunchMode() {
521
- if (!isLocalDevToolsHost(state.host)) {
716
+ function validateExtensionLaunchMode(owner) {
717
+ const browser = browserSettingsForOwner(owner);
718
+ if (!isLocalDevToolsHost(browser.host)) {
522
719
  throw new DevToolsEndpointError(
523
720
  "Unpacked extensions require a local extension-owned managed browser; remote CDP endpoints cannot be modified."
524
721
  );
525
722
  }
526
- if (!state.autoLaunchEnabled) {
723
+ if (!browser.autoLaunchEnabled) {
527
724
  throw new DevToolsEndpointError(
528
725
  "Auto-launch is required for unpacked extensions so Pi can create an isolated managed browser."
529
726
  );
530
727
  }
531
- if (!state.browserExecutable) {
728
+ if (!browser.executablePath) {
532
729
  throw new DevToolsEndpointError(
533
730
  "Unpacked extensions require browser.executablePath for Chrome for Testing or Chromium in pi-chrome-devtools.json."
534
731
  );
535
732
  }
536
733
  }
537
- async function ensureManagedBrowserLaunched(waitMs) {
538
- if (state.launchPromise) return state.launchPromise;
539
- if (state.managedBrowser && !state.managedBrowser.exited && state.managedBrowser.ready) return;
540
- const generation = state.sessionGeneration;
541
- const signal = state.sessionController.signal;
542
- throwIfBrowserLaunchCancelled(generation, signal);
543
- const launchPromise = prepareManagedBrowserLaunch(waitMs, generation, signal);
734
+ async function ensureManagedBrowserLaunched(waitMs, callerSignal, owner) {
735
+ const existingLaunch = launchPromiseForOwner(owner);
736
+ if (existingLaunch) {
737
+ await waitForCaller(existingLaunch, callerSignal);
738
+ return;
739
+ }
740
+ const existingBrowser = managedBrowserForOwner(owner);
741
+ if (existingBrowser && !existingBrowser.exited && existingBrowser.ready) return;
742
+ const generation = owner ? managedBrowserScope(owner).generation : state.sessionGeneration;
743
+ const signal = owner ? managedBrowserScope(owner).controller.signal : state.sessionController.signal;
744
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
745
+ const launchPromise = prepareManagedBrowserLaunch(waitMs, generation, signal, owner);
544
746
  const wrappedPromise = launchPromise.finally(() => {
545
- if (state.launchPromise === wrappedPromise) state.launchPromise = void 0;
747
+ if (launchPromiseForOwner(owner) === wrappedPromise) setLaunchPromise(owner, void 0);
546
748
  });
547
- state.launchPromise = wrappedPromise;
548
- return wrappedPromise;
749
+ setLaunchPromise(owner, wrappedPromise);
750
+ await waitForCaller(wrappedPromise, callerSignal);
549
751
  }
550
- async function prepareManagedBrowserLaunch(waitMs, generation, signal) {
551
- if (state.managedBrowser) {
552
- await shutdownManagedBrowser(state.managedBrowser, { awaitLaunch: false });
553
- throwIfBrowserLaunchCancelled(generation, signal);
752
+ function waitForCaller(operation, signal) {
753
+ if (!signal) return operation;
754
+ signal.throwIfAborted();
755
+ return new Promise((resolve3, reject) => {
756
+ let settled = false;
757
+ const onAbort = () => settle(() => reject(signal.reason));
758
+ const settle = (callback) => {
759
+ if (settled) return;
760
+ settled = true;
761
+ signal.removeEventListener("abort", onAbort);
762
+ callback();
763
+ };
764
+ signal.addEventListener("abort", onAbort, { once: true });
765
+ operation.then(
766
+ (value) => settle(() => resolve3(value)),
767
+ (error) => settle(() => reject(error))
768
+ );
769
+ });
770
+ }
771
+ async function prepareManagedBrowserLaunch(waitMs, generation, signal, owner) {
772
+ const existingBrowser = managedBrowserForOwner(owner);
773
+ if (existingBrowser) {
774
+ await shutdownManagedBrowser(existingBrowser, { awaitLaunch: false, owner });
775
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
554
776
  }
555
- if (state.portConfigured) {
556
- const available = await browserManagerOperations.isPortAvailable(state.host, state.port);
557
- throwIfBrowserLaunchCancelled(generation, signal);
777
+ if (managedBrowserPortConfigured(owner)) {
778
+ const available = await browserManagerOperations.isPortAvailable(
779
+ browserSettingsForOwner(owner).host,
780
+ managedBrowserPort(owner)
781
+ );
782
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
558
783
  if (!available) {
559
784
  throw new DevToolsEndpointError(
560
- `Cannot launch the extension-owned browser because explicit port ${state.port} is already in use. Choose a free browser.endpoint port or reset the endpoint to use a dynamic managed port.`
785
+ `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
786
  );
562
787
  }
563
788
  }
564
- return launchManagedBrowser(waitMs, generation, signal);
789
+ return launchManagedBrowser(waitMs, generation, signal, owner);
565
790
  }
566
- async function launchManagedBrowser(waitMs, generation, signal) {
567
- throwIfBrowserLaunchCancelled(generation, signal);
568
- const candidateDefinitions = browserCandidateDefinitions();
569
- const candidates = await resolveBrowserCandidates(candidateDefinitions, generation, signal);
570
- throwIfBrowserLaunchCancelled(generation, signal);
571
- state.lastLaunchAttempt = {
791
+ async function launchManagedBrowser(waitMs, generation, signal, owner) {
792
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
793
+ const candidateDefinitions = browserCandidateDefinitions(owner);
794
+ const candidates = await resolveBrowserCandidates(
795
+ candidateDefinitions,
796
+ generation,
797
+ signal,
798
+ owner
799
+ );
800
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
801
+ setLastLaunchAttempt(owner, {
572
802
  candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
573
- mode: state.portConfigured ? "explicit-port" : "dynamic-port"
574
- };
803
+ mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "dynamic-port"
804
+ });
575
805
  if (candidates.length === 0) {
576
- throw new DevToolsEndpointError(noBrowserCandidateMessage(candidateDefinitions));
806
+ throw new DevToolsEndpointError(noBrowserCandidateMessage(candidateDefinitions, owner));
577
807
  }
578
808
  let lastError;
579
809
  for (const candidate of candidates) {
580
- throwIfBrowserLaunchCancelled(generation, signal);
810
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
581
811
  try {
582
- if (extensionsConfigured())
583
- await requireSupportedExtensionBrowser(candidate, generation, signal);
584
- await launchBrowserCandidate(candidate, waitMs, generation, signal);
585
- throwIfBrowserLaunchCancelled(generation, signal);
586
- state.lastLaunchAttempt = {
587
- ...state.lastLaunchAttempt,
812
+ if (extensionsConfigured(owner))
813
+ await requireSupportedExtensionBrowser(candidate, generation, signal, owner);
814
+ await launchBrowserCandidate(candidate, waitMs, generation, signal, owner);
815
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
816
+ setLastLaunchAttempt(owner, {
817
+ ...lastLaunchAttempt(owner),
818
+ candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
819
+ mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "dynamic-port",
588
820
  selectedCandidate: formatBrowserCandidate(candidate),
589
- userDataDir: state.managedBrowser?.userDataDir
590
- };
821
+ userDataDir: managedBrowserForOwner(owner)?.userDataDir
822
+ });
591
823
  return;
592
824
  } catch (error) {
593
825
  try {
594
- throwIfBrowserLaunchCancelled(generation, signal);
826
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
595
827
  } catch (cancellation) {
596
- state.lastLaunchAttempt = {
597
- ...state.lastLaunchAttempt,
828
+ setLastLaunchAttempt(owner, {
829
+ ...lastLaunchAttempt(owner) ?? {
830
+ candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
831
+ mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "dynamic-port"
832
+ },
598
833
  lastError: formatError2(cancellation)
599
- };
834
+ });
600
835
  throw cancellation;
601
836
  }
602
837
  lastError = error;
603
- state.lastLaunchAttempt = {
604
- ...state.lastLaunchAttempt,
838
+ setLastLaunchAttempt(owner, {
839
+ ...lastLaunchAttempt(owner) ?? {
840
+ candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
841
+ mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "dynamic-port"
842
+ },
605
843
  lastError: formatError2(error)
606
- };
844
+ });
607
845
  }
608
846
  }
609
- if (lastError instanceof DevToolsEndpointError && extensionsConfigured()) throw lastError;
847
+ if (lastError instanceof DevToolsEndpointError && extensionsConfigured(owner)) throw lastError;
610
848
  throw new DevToolsEndpointError(
611
849
  [
612
850
  "Unable to auto-launch a Chromium-family browser for Chrome DevTools.",
613
851
  `Tried: ${candidates.map(formatBrowserCandidate).join(", ")}`,
614
852
  lastError ? `Last error: ${formatError2(lastError)}` : void 0,
615
- launchHint(),
853
+ launchHint(owner),
616
854
  endpointConfigHint()
617
855
  ].filter(Boolean).join("\n")
618
856
  );
619
857
  }
620
- async function requireSupportedExtensionBrowser(candidate, generation, signal) {
858
+ async function requireSupportedExtensionBrowser(candidate, generation, signal, owner) {
621
859
  let version;
622
860
  try {
623
861
  version = (await browserManagerOperations.inspectBrowserVersion(candidate.resolvedExecutable, signal)).trim();
624
862
  } catch (error) {
625
- throwIfBrowserLaunchCancelled(generation, signal);
863
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
626
864
  throw new DevToolsEndpointError(
627
865
  `Unable to identify the configured extension browser ${candidate.resolvedExecutable}: ${formatError2(error)}. Configure Chrome for Testing or Chromium.`
628
866
  );
629
867
  }
630
- throwIfBrowserLaunchCancelled(generation, signal);
868
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
631
869
  const classification = classifyExtensionBrowserVersion(version);
632
870
  if (!classification.supported) {
633
871
  throw new DevToolsEndpointError(
@@ -643,19 +881,19 @@ function classifyExtensionBrowserVersion(version) {
643
881
  const product = version.trim().replace(/\s+\d+(?:\.\d+)*.*$/, "") || "Unknown";
644
882
  return { supported: false, product };
645
883
  }
646
- async function launchBrowserCandidate(candidate, waitMs, generation, signal) {
647
- throwIfBrowserLaunchCancelled(generation, signal);
884
+ async function launchBrowserCandidate(candidate, waitMs, generation, signal, owner) {
885
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
648
886
  const userDataDir = await browserManagerOperations.mkdtemp(
649
887
  join2(tmpdir(), MANAGED_BROWSER_PROFILE_PREFIX)
650
888
  );
651
889
  let managedBrowser;
652
890
  try {
653
- throwIfBrowserLaunchCancelled(generation, signal);
654
- const portArgument = state.portConfigured ? String(state.port) : "0";
891
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
892
+ const portArgument = managedBrowserPortConfigured(owner) ? String(managedBrowserPort(owner)) : "0";
655
893
  const args = buildManagedBrowserLaunchArguments(
656
894
  userDataDir,
657
895
  portArgument,
658
- state.extensionPaths
896
+ managedBrowserExtensionPaths(owner)
659
897
  );
660
898
  const child = browserManagerOperations.spawn(candidate.resolvedExecutable, args, {
661
899
  shell: false,
@@ -666,37 +904,42 @@ async function launchBrowserCandidate(candidate, waitMs, generation, signal) {
666
904
  userDataDir,
667
905
  exited: false,
668
906
  ready: false,
669
- ownerGeneration: generation
907
+ ownerGeneration: generation,
908
+ ...owner ? { sessionOwner: owner } : {}
670
909
  };
671
910
  managedBrowser = launchedBrowser;
672
- state.managedBrowser = launchedBrowser;
911
+ setManagedBrowser(owner, launchedBrowser);
673
912
  child.once("exit", () => {
913
+ if (managedBrowserForOwner(owner) === launchedBrowser && owner) {
914
+ invalidateWebMcpOperations(owner, "Chrome DevTools managed browser exited");
915
+ }
674
916
  launchedBrowser.exited = true;
675
917
  launchedBrowser.ready = false;
676
- if (!state.portConfigured && launchedBrowser.port === state.port) {
677
- state.port = state.configuredPort;
918
+ if (!managedBrowserPortConfigured(owner) && launchedBrowser.port === managedBrowserPort(owner)) {
919
+ setManagedBrowserPort(owner, managedBrowserConfiguredPort(owner));
678
920
  }
679
921
  });
680
922
  await waitForBrowserSpawn(child);
681
- throwIfBrowserLaunchCancelled(generation, signal);
682
- if (state.portConfigured) {
683
- launchedBrowser.port = state.port;
923
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
924
+ if (managedBrowserPortConfigured(owner)) {
925
+ launchedBrowser.port = managedBrowserPort(owner);
684
926
  } else {
685
927
  launchedBrowser.port = await readManagedBrowserPort(
686
928
  userDataDir,
687
929
  launchedBrowser,
688
930
  waitMs,
689
931
  generation,
690
- signal
932
+ signal,
933
+ owner
691
934
  );
692
- throwIfBrowserLaunchCancelled(generation, signal);
693
- state.port = launchedBrowser.port;
935
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
936
+ setManagedBrowserPort(owner, launchedBrowser.port);
694
937
  }
695
- await waitForDevToolsEndpoint(waitMs, launchedBrowser, generation, signal);
696
- throwIfBrowserLaunchCancelled(generation, signal);
938
+ await waitForDevToolsEndpoint(waitMs, launchedBrowser, generation, signal, owner);
939
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
697
940
  launchedBrowser.ready = true;
698
941
  } catch (error) {
699
- if (managedBrowser) await shutdownManagedBrowser(managedBrowser, { awaitLaunch: false });
942
+ if (managedBrowser) await shutdownManagedBrowser(managedBrowser, { awaitLaunch: false, owner });
700
943
  else {
701
944
  await browserManagerOperations.rm(userDataDir, { recursive: true, force: true }).catch(() => void 0);
702
945
  }
@@ -734,7 +977,7 @@ function waitForBrowserSpawn(child) {
734
977
  child.once("spawn", onSpawn);
735
978
  });
736
979
  }
737
- async function readManagedBrowserPort(userDataDir, managedBrowser, waitMs, generation, signal) {
980
+ async function readManagedBrowserPort(userDataDir, managedBrowser, waitMs, generation, signal, owner) {
738
981
  const activePortFile = join2(userDataDir, DEVTOOLS_ACTIVE_PORT_FILE);
739
982
  const deadline = Date.now() + waitMs;
740
983
  while (true) {
@@ -743,7 +986,7 @@ async function readManagedBrowserPort(userDataDir, managedBrowser, waitMs, gener
743
986
  if (isNodeError2(error) && error.code === "ENOENT") return void 0;
744
987
  throw error;
745
988
  });
746
- throwIfBrowserLaunchCancelled(generation, signal);
989
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
747
990
  const portText = text?.split(/\r?\n/, 1)[0]?.trim();
748
991
  const port = Number(portText);
749
992
  if (Number.isInteger(port) && port > 0) return port;
@@ -753,31 +996,31 @@ async function readManagedBrowserPort(userDataDir, managedBrowser, waitMs, gener
753
996
  [
754
997
  "Timed out waiting for auto-launched browser DevToolsActivePort.",
755
998
  `Expected file: ${activePortFile}`,
756
- launchHint()
999
+ launchHint(owner)
757
1000
  ].join("\n")
758
1001
  );
759
1002
  }
760
1003
  await browserManagerOperations.sleep(Math.min(DEFAULT_ENDPOINT_RETRY_MS, remainingMs), signal);
761
1004
  }
762
1005
  }
763
- async function waitForDevToolsEndpoint(waitMs, managedBrowser, generation, signal) {
1006
+ async function waitForDevToolsEndpoint(waitMs, managedBrowser, generation, signal, owner) {
764
1007
  const deadline = Date.now() + waitMs;
765
1008
  while (true) {
766
1009
  throwIfManagedBrowserExited(managedBrowser);
767
1010
  try {
768
- await fetchDevToolsJson("/json/version", { signal });
769
- throwIfBrowserLaunchCancelled(generation, signal);
1011
+ await fetchDevToolsJson("/json/version", { signal }, owner);
1012
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
770
1013
  return;
771
1014
  } catch (error) {
772
1015
  if (!isRetryableEndpointError(error)) throw error;
773
1016
  }
774
- throwIfBrowserLaunchCancelled(generation, signal);
1017
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
775
1018
  const remainingMs = deadline - Date.now();
776
1019
  if (remainingMs <= 0) {
777
1020
  throw new DevToolsEndpointError(
778
1021
  [
779
- `Timed out waiting for auto-launched browser at ${devToolsEndpoint()}.`,
780
- launchHint()
1022
+ `Timed out waiting for auto-launched browser at ${devToolsEndpoint(owner)}.`,
1023
+ launchHint(owner)
781
1024
  ].join("\n")
782
1025
  );
783
1026
  }
@@ -788,28 +1031,44 @@ function throwIfManagedBrowserExited(managedBrowser) {
788
1031
  if (!managedBrowser.exited) return;
789
1032
  throw new DevToolsEndpointError("Auto-launched browser exited before DevTools became available.");
790
1033
  }
791
- function throwIfBrowserLaunchCancelled(generation, signal) {
792
- if (!state.shuttingDown && generation === state.sessionGeneration && !signal.aborted) {
793
- return;
794
- }
1034
+ function throwIfBrowserLaunchCancelled(generation, signal, owner) {
1035
+ const currentGeneration = owner ? managedBrowserScope(owner).generation : state.sessionGeneration;
1036
+ const shuttingDown = owner ? signal.aborted : state.shuttingDown;
1037
+ if (!shuttingDown && generation === currentGeneration && !signal.aborted) return;
795
1038
  throw new DevToolsEndpointError(
796
- generation === state.sessionGeneration ? "Chrome DevTools browser launch cancelled during shutdown." : "Chrome DevTools browser launch cancelled because the session was replaced."
1039
+ generation === currentGeneration ? "Chrome DevTools browser launch cancelled during shutdown." : "Chrome DevTools browser launch cancelled because the session was replaced."
797
1040
  );
798
1041
  }
799
- async function shutdownManagedBrowser(managedBrowser = state.managedBrowser, options = {}) {
800
- if (options.cancelLaunch) state.shuttingDown = true;
1042
+ async function shutdownManagedBrowser(managedBrowser, options = {}) {
1043
+ const owner = options.owner ?? managedBrowser?.sessionOwner;
1044
+ if (options.cancelLaunch) {
1045
+ if (owner) {
1046
+ const scope = managedBrowserScope(owner);
1047
+ scope.controller.abort(
1048
+ new DOMException("Chrome DevTools browser launch cancelled during shutdown", "AbortError")
1049
+ );
1050
+ scope.generation += 1;
1051
+ } else {
1052
+ state.shuttingDown = true;
1053
+ }
1054
+ }
1055
+ managedBrowser ??= managedBrowserForOwner(owner);
801
1056
  if (options.awaitLaunch !== false) {
802
- await state.launchPromise?.catch(() => void 0);
803
- managedBrowser = managedBrowser ?? state.managedBrowser;
1057
+ await launchPromiseForOwner(owner)?.catch(() => void 0);
1058
+ managedBrowser ??= managedBrowserForOwner(owner);
804
1059
  }
805
1060
  if (!managedBrowser) return;
806
1061
  if (managedBrowser.cleanupPromise) return managedBrowser.cleanupPromise;
807
- const cleanup = cleanupManagedBrowser(managedBrowser);
1062
+ const cleanup = cleanupManagedBrowser(managedBrowser, owner);
808
1063
  managedBrowser.cleanupPromise = cleanup;
809
1064
  return cleanup;
810
1065
  }
811
- async function cleanupManagedBrowser(managedBrowser) {
812
- if (state.managedBrowser === managedBrowser) state.managedBrowser = void 0;
1066
+ async function cleanupManagedBrowser(managedBrowser, owner) {
1067
+ if (managedBrowserForOwner(owner) === managedBrowser) {
1068
+ if (owner) invalidateWebMcpOperations(owner, "Chrome DevTools managed browser closed");
1069
+ if (state.managedBrowser === managedBrowser) state.managedBrowser = void 0;
1070
+ else setManagedBrowser(owner, void 0);
1071
+ }
813
1072
  if (!managedBrowser.exited) {
814
1073
  killManagedBrowserProcess(managedBrowser);
815
1074
  await waitForManagedBrowserExit(managedBrowser, BROWSER_SHUTDOWN_WAIT_MS).catch(async () => {
@@ -820,8 +1079,8 @@ async function cleanupManagedBrowser(managedBrowser) {
820
1079
  });
821
1080
  }
822
1081
  await browserManagerOperations.rm(managedBrowser.userDataDir, { recursive: true, force: true }).catch(() => void 0);
823
- if (!state.portConfigured && managedBrowser.port === state.port) {
824
- state.port = state.configuredPort;
1082
+ if (!managedBrowserPortConfigured(owner) && managedBrowser.port === managedBrowserPort(owner)) {
1083
+ setManagedBrowserPort(owner, managedBrowserConfiguredPort(owner));
825
1084
  }
826
1085
  }
827
1086
  function killManagedBrowserProcess(managedBrowser, signal) {
@@ -851,8 +1110,8 @@ function waitForManagedBrowserExit(managedBrowser, waitMs) {
851
1110
  managedBrowser.process.once("close", onExitOrClose);
852
1111
  });
853
1112
  }
854
- async function fetchDevToolsJson(path, init) {
855
- const url = `${devToolsEndpoint()}${path}`;
1113
+ async function fetchDevToolsJson(path, init, owner) {
1114
+ const url = `${devToolsEndpoint(owner)}${path}`;
856
1115
  let response;
857
1116
  try {
858
1117
  const timeoutSignal = AbortSignal.timeout(DEFAULT_HTTP_TIMEOUT_MS);
@@ -861,7 +1120,7 @@ async function fetchDevToolsJson(path, init) {
861
1120
  signal: init?.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal
862
1121
  });
863
1122
  } catch (error) {
864
- throw new DevToolsEndpointError(endpointConnectionErrorMessage(error), {
1123
+ throw new DevToolsEndpointError(endpointConnectionErrorMessage(error, owner), {
865
1124
  launchable: true,
866
1125
  retryable: true
867
1126
  });
@@ -888,16 +1147,20 @@ async function fetchDevToolsJson(path, init) {
888
1147
  );
889
1148
  }
890
1149
  }
891
- async function withEndpointRetry(operation, waitMs) {
1150
+ async function withEndpointRetry(operation, waitMs, signal) {
892
1151
  const deadline = Date.now() + waitMs;
893
1152
  while (true) {
1153
+ signal?.throwIfAborted();
894
1154
  try {
895
1155
  return await operation();
896
1156
  } catch (error) {
897
1157
  if (!isRetryableEndpointError(error)) throw error;
898
1158
  const remainingMs = deadline - Date.now();
899
1159
  if (remainingMs <= 0) throw error;
900
- await browserManagerOperations.sleep(Math.min(DEFAULT_ENDPOINT_RETRY_MS, remainingMs));
1160
+ await browserManagerOperations.sleep(
1161
+ Math.min(DEFAULT_ENDPOINT_RETRY_MS, remainingMs),
1162
+ signal
1163
+ );
901
1164
  }
902
1165
  }
903
1166
  }
@@ -907,100 +1170,101 @@ function isRetryableEndpointError(error) {
907
1170
  function isLaunchableEndpointError(error) {
908
1171
  return error instanceof DevToolsEndpointError && error.launchable;
909
1172
  }
910
- function shouldAutoLaunchAfterEndpointError(error) {
911
- if (!canAutoLaunchBrowser()) return false;
1173
+ function shouldAutoLaunchAfterEndpointError(error, owner) {
1174
+ if (!canAutoLaunchBrowser(owner)) return false;
912
1175
  if (isLaunchableEndpointError(error)) return true;
913
- return !state.portConfigured && error instanceof DevToolsEndpointError;
1176
+ return !managedBrowserPortConfigured(owner) && error instanceof DevToolsEndpointError;
914
1177
  }
915
- function canAutoLaunchBrowser() {
916
- return state.autoLaunchEnabled && isLocalDevToolsHost(state.host);
1178
+ function canAutoLaunchBrowser(owner) {
1179
+ const browser = browserSettingsForOwner(owner);
1180
+ return browser.autoLaunchEnabled && isLocalDevToolsHost(browser.host);
917
1181
  }
918
- function extensionsConfigured() {
919
- return state.extensionPaths.length > 0;
1182
+ function extensionsConfigured(owner) {
1183
+ return managedBrowserExtensionPaths(owner).length > 0;
920
1184
  }
921
- function endpointConnectionErrorMessage(error) {
1185
+ function endpointConnectionErrorMessage(error, owner) {
922
1186
  const reason = isTimeoutError(error) ? "request timed out" : "connection failed";
923
1187
  return [
924
- `Cannot connect to Chrome DevTools endpoint at ${devToolsEndpoint()} (${reason}).`,
925
- launchHint(),
1188
+ `Cannot connect to Chrome DevTools endpoint at ${devToolsEndpoint(owner)} (${reason}).`,
1189
+ launchHint(owner),
926
1190
  endpointConfigHint()
927
1191
  ].join("\n");
928
1192
  }
929
1193
  function isTimeoutError(error) {
930
1194
  return error instanceof Error && ["AbortError", "TimeoutError"].includes(error.name);
931
1195
  }
932
- function devToolsEndpoint() {
933
- return `http://${formatHostForUrl(state.host)}:${state.port}`;
1196
+ function devToolsEndpoint(owner) {
1197
+ return `http://${formatHostForUrl(browserSettingsForOwner(owner).host)}:${managedBrowserPort(owner)}`;
934
1198
  }
935
1199
  function formatHostForUrl(host) {
936
1200
  if (host.startsWith("[") && host.endsWith("]")) return host;
937
1201
  return host.includes(":") ? `[${host}]` : host;
938
1202
  }
939
- function endpointSourceLabel() {
940
- return state.endpointSource;
1203
+ function endpointSourceLabel(owner) {
1204
+ return browserSettingsForOwner(owner).endpointSource;
941
1205
  }
942
- function browserLifecycleState() {
943
- if (state.launchPromise) return "starting";
944
- if (state.managedBrowser?.exited) return "exited";
945
- if (state.managedBrowser?.ready) return "running";
946
- if (state.lastLaunchAttempt?.lastError) return "failed";
1206
+ function browserLifecycleState(owner) {
1207
+ if (launchPromiseForOwner(owner)) return "starting";
1208
+ if (managedBrowserForOwner(owner)?.exited) return "exited";
1209
+ if (managedBrowserForOwner(owner)?.ready) return "running";
1210
+ if (lastLaunchAttempt(owner)?.lastError) return "failed";
947
1211
  return "unobserved";
948
1212
  }
949
- function launchModeLabel() {
950
- if (extensionsConfigured()) {
951
- if (!isLocalDevToolsHost(state.host)) return "invalid; extensions require a local endpoint";
952
- if (!state.autoLaunchEnabled) return "invalid; extensions require auto-launch";
953
- if (state.managedBrowser && !state.managedBrowser.exited) {
954
- return state.portConfigured ? "extension-owned browser on explicit port" : "extension-owned browser on dynamic port";
1213
+ function launchModeLabel(owner) {
1214
+ const browser = browserSettingsForOwner(owner);
1215
+ const managedBrowser = managedBrowserForOwner(owner);
1216
+ if (extensionsConfigured(owner)) {
1217
+ if (!isLocalDevToolsHost(browser.host)) return "invalid; extensions require a local endpoint";
1218
+ if (!browser.autoLaunchEnabled) return "invalid; extensions require auto-launch";
1219
+ if (managedBrowser && !managedBrowser.exited) {
1220
+ return managedBrowserPortConfigured(owner) ? "extension-owned browser on explicit port" : "extension-owned browser on dynamic port";
955
1221
  }
956
- return state.portConfigured ? "force extension-owned launch on explicit port" : "force extension-owned launch on dynamic port";
1222
+ return managedBrowserPortConfigured(owner) ? "force extension-owned launch on explicit port" : "force extension-owned launch on dynamic port";
957
1223
  }
958
- if (!isLocalDevToolsHost(state.host)) return "manual remote endpoint";
959
- if (!state.autoLaunchEnabled) return "manual; auto-launch disabled";
960
- if (state.managedBrowser && !state.managedBrowser.exited) {
961
- return state.portConfigured ? "auto-launched on explicit port" : "auto-launched on dynamic port";
1224
+ if (!isLocalDevToolsHost(browser.host)) return "manual remote endpoint";
1225
+ if (!browser.autoLaunchEnabled) return "manual; auto-launch disabled";
1226
+ if (managedBrowser && !managedBrowser.exited) {
1227
+ return managedBrowserPortConfigured(owner) ? "auto-launched on explicit port" : "auto-launched on dynamic port";
962
1228
  }
963
- return state.portConfigured ? "attach first; auto-launch explicit port" : "attach first; auto-launch dynamic port";
1229
+ return managedBrowserPortConfigured(owner) ? "attach first; auto-launch explicit port" : "attach first; auto-launch dynamic port";
964
1230
  }
965
- function launchAttemptLines() {
966
- if (!state.lastLaunchAttempt) return [];
967
- const lines = [`Last launch attempt: ${state.lastLaunchAttempt.mode}`];
968
- if (state.lastLaunchAttempt.selectedCandidate) {
969
- lines.push(`Launched browser: ${state.lastLaunchAttempt.selectedCandidate}`);
1231
+ function launchAttemptLines(owner) {
1232
+ const attempt = lastLaunchAttempt(owner);
1233
+ if (!attempt) return [];
1234
+ const lines = [`Last launch attempt: ${attempt.mode}`];
1235
+ if (attempt.selectedCandidate) {
1236
+ lines.push(`Launched browser: ${attempt.selectedCandidate}`);
970
1237
  } else {
971
- lines.push(`Tried browser candidates: ${state.lastLaunchAttempt.candidateLabels.join(", ")}`);
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}`);
1238
+ lines.push(`Tried browser candidates: ${attempt.candidateLabels.join(", ")}`);
978
1239
  }
1240
+ if (attempt.userDataDir) lines.push(`Managed browser profile: ${attempt.userDataDir}`);
1241
+ if (attempt.lastError) lines.push(`Last launch error: ${attempt.lastError}`);
979
1242
  return lines;
980
1243
  }
981
- function launchHint() {
982
- if (extensionsConfigured()) {
1244
+ function launchHint(owner) {
1245
+ const browser = browserSettingsForOwner(owner);
1246
+ if (extensionsConfigured(owner)) {
983
1247
  return "Configured unpacked extensions force an isolated extension-owned launch; existing CDP browsers are never reused, modified, or closed.";
984
1248
  }
985
- if (!isLocalDevToolsHost(state.host)) {
986
- return `Remote/non-local endpoints are not auto-launched. Start a browser with CDP enabled at ${devToolsEndpoint()}.`;
1249
+ if (!isLocalDevToolsHost(browser.host)) {
1250
+ return `Remote/non-local endpoints are not auto-launched. Start a browser with CDP enabled at ${devToolsEndpoint(owner)}.`;
987
1251
  }
988
- if (!state.autoLaunchEnabled) {
989
- return `Auto-launch is disabled. Start a browser manually: ${chromeLaunchCommand()}`;
1252
+ if (!browser.autoLaunchEnabled) {
1253
+ return `Auto-launch is disabled. Start a browser manually: ${chromeLaunchCommand(owner)}`;
990
1254
  }
991
- const managedMode = state.portConfigured ? `port ${state.port}` : "a dynamic DevTools port";
992
- return `If no endpoint is available, Pi will auto-launch a Chromium-family browser with ${managedMode} and an isolated temp profile. Manual command: ${chromeLaunchCommand()}`;
1255
+ const managedMode = managedBrowserPortConfigured(owner) ? `port ${managedBrowserPort(owner)}` : "a dynamic DevTools port";
1256
+ return `If no endpoint is available, Pi will auto-launch a Chromium-family browser with ${managedMode} and an isolated temp profile. Manual command: ${chromeLaunchCommand(owner)}`;
993
1257
  }
994
- function browserCandidateHint() {
995
- if (extensionsConfigured()) {
1258
+ function browserCandidateHint(owner) {
1259
+ if (extensionsConfigured(owner)) {
996
1260
  return "Extension browser requirement: configure Chrome for Testing or Chromium with browser.executablePath; branded Chrome is rejected because it may ignore unpacked-extension flags.";
997
1261
  }
998
- return `Browser candidates: ${browserCandidateDefinitions().map((candidate) => candidate.label).join(", ")}`;
1262
+ return `Browser candidates: ${browserCandidateDefinitions(owner).map((candidate) => candidate.label).join(", ")}`;
999
1263
  }
1000
- function chromeLaunchCommand() {
1001
- const executable = state.browserExecutable ?? defaultManualBrowserExecutable();
1264
+ function chromeLaunchCommand(owner) {
1265
+ const executable = browserSettingsForOwner(owner).executablePath ?? defaultManualBrowserExecutable();
1002
1266
  const dataDir = process.platform === "win32" ? "%TEMP%\\pi-chrome-devtools" : "/tmp/pi-chrome-devtools";
1003
- return `${quoteCommandPart(executable)} --remote-debugging-port=${state.port} --user-data-dir=${dataDir}`;
1267
+ return `${quoteCommandPart(executable)} --remote-debugging-port=${managedBrowserPort(owner)} --user-data-dir=${dataDir}`;
1004
1268
  }
1005
1269
  function defaultManualBrowserExecutable() {
1006
1270
  return process.platform === "darwin" ? "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" : process.platform === "win32" ? "chrome.exe" : "google-chrome";
@@ -1015,17 +1279,18 @@ function isLocalDevToolsHost(host) {
1015
1279
  const normalizedHost = host.toLowerCase().replace(/^\[(.*)]$/, "$1");
1016
1280
  return ["localhost", "127.0.0.1", "::1", "0:0:0:0:0:0:0:1"].includes(normalizedHost);
1017
1281
  }
1018
- function browserCandidateDefinitions() {
1019
- const explicitCandidate = explicitBrowserCandidateDefinition();
1282
+ function browserCandidateDefinitions(owner) {
1283
+ const explicitCandidate = explicitBrowserCandidateDefinition(owner);
1020
1284
  if (explicitCandidate.length > 0) return explicitCandidate;
1021
1285
  return uniqueBrowserCandidates(platformBrowserCandidateDefinitions());
1022
1286
  }
1023
- function explicitBrowserCandidateDefinition() {
1024
- if (!state.browserExecutable) return [];
1287
+ function explicitBrowserCandidateDefinition(owner) {
1288
+ const browser = browserSettingsForOwner(owner);
1289
+ if (!browser.executablePath) return [];
1025
1290
  return [
1026
1291
  {
1027
- label: state.browserExecutableSource === "environment" ? "PI_CHROME_DEVTOOLS_BROWSER" : "browser.executablePath",
1028
- executable: state.browserExecutable,
1292
+ label: browser.executablePathSource === "environment" ? "PI_CHROME_DEVTOOLS_BROWSER" : "browser.executablePath",
1293
+ executable: browser.executablePath,
1029
1294
  source: "env"
1030
1295
  }
1031
1296
  ];
@@ -1111,11 +1376,11 @@ function uniqueBrowserCandidates(candidates) {
1111
1376
  return true;
1112
1377
  });
1113
1378
  }
1114
- async function resolveBrowserCandidates(definitions, generation, signal) {
1379
+ async function resolveBrowserCandidates(definitions, generation, signal, owner) {
1115
1380
  const candidates = [];
1116
1381
  for (const definition of definitions) {
1117
1382
  const resolvedExecutable = await resolveBrowserExecutable(definition.executable);
1118
- throwIfBrowserLaunchCancelled(generation, signal);
1383
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
1119
1384
  if (!resolvedExecutable) continue;
1120
1385
  candidates.push({ ...definition, resolvedExecutable });
1121
1386
  }
@@ -1170,8 +1435,8 @@ function formatBrowserCandidate(candidate) {
1170
1435
  function formatBrowserCandidateDefinition(candidate) {
1171
1436
  return `${candidate.label} (${candidate.executable})`;
1172
1437
  }
1173
- function noBrowserCandidateMessage(candidateDefinitions) {
1174
- if (extensionsConfigured()) {
1438
+ function noBrowserCandidateMessage(candidateDefinitions, owner) {
1439
+ if (extensionsConfigured(owner)) {
1175
1440
  return [
1176
1441
  "Cannot launch unpacked extensions because the configured browser executable was not found or is not executable.",
1177
1442
  `Tried: ${candidateDefinitions.map(formatBrowserCandidateDefinition).join(", ")}`,
@@ -1249,412 +1514,46 @@ var DevToolsEndpointError = class extends Error {
1249
1514
  }
1250
1515
  };
1251
1516
 
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
1517
  export {
1518
+ CORE_CHROME_DEVTOOLS_TOOL_NAMES,
1519
+ WEBMCP_TOOL_NAMES,
1626
1520
  CHROME_DEVTOOLS_TOOL_NAMES,
1521
+ isWebMcpToolName,
1627
1522
  waitForSettingsWrites,
1628
1523
  loadSettings,
1629
1524
  parseBrowserEndpoint,
1525
+ saveSettings,
1526
+ saveWebMcpSettings,
1630
1527
  saveBrowserSettings,
1528
+ settingsFilePath,
1631
1529
  DEFAULT_TIMEOUT_MS,
1632
1530
  DEFAULT_ENDPOINT_WAIT_MS,
1633
1531
  state,
1532
+ setWebMcpSessionOwner,
1533
+ beginWebMcpOperation,
1534
+ invalidateWebMcpOperations,
1535
+ currentWebMcpGeneration,
1536
+ webMcpSessionSignal,
1537
+ applyRuntimeWebMcpSetting,
1538
+ webMcpEnabled,
1539
+ webMcpOperationIsCurrent,
1634
1540
  applyRuntimeBrowserSettings,
1541
+ startManagedBrowserSession,
1542
+ syncManagedBrowserSettings,
1543
+ browserSettingsForOwner,
1544
+ managedBrowserForOwner,
1635
1545
  ensureDevToolsEndpoint,
1636
1546
  shutdownManagedBrowser,
1637
1547
  fetchDevToolsJson,
1638
1548
  withEndpointRetry,
1639
1549
  devToolsEndpoint,
1550
+ endpointSourceLabel,
1640
1551
  browserLifecycleState,
1641
1552
  launchModeLabel,
1553
+ launchAttemptLines,
1642
1554
  launchHint,
1643
- formatPageListItem,
1644
- initializeAvailableChromeDevtoolsTools,
1645
- configureChromeDevtoolsToolExposure,
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
1555
+ browserCandidateHint,
1556
+ endpointConfigHint,
1557
+ formatPageListItem
1659
1558
  };
1660
- //# sourceMappingURL=chunk-RH6KESL3.js.map
1559
+ //# sourceMappingURL=chunk-SRT3UUIP.js.map