@narumitw/pi-chrome-devtools 0.53.4 → 0.54.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 (31) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/README.md +34 -16
  3. package/dist/chunks/{browser-settings-menu-FVMCCUPM.ts → browser-settings-menu-7M3VM7QN.ts} +25 -3
  4. package/dist/chunks/browser-settings-menu-7M3VM7QN.ts.map +7 -0
  5. package/dist/chunks/{chunk-V7N3WI5B.ts → chunk-JKPJYUVG.ts} +340 -108
  6. package/dist/chunks/chunk-JKPJYUVG.ts.map +7 -0
  7. package/dist/chunks/{chunk-4B3EQHOH.ts → chunk-JLEJ6WFO.ts} +2 -2
  8. package/dist/chunks/{chunk-LSJSDY2M.ts → chunk-WTMZID3G.ts} +382 -246
  9. package/dist/chunks/chunk-WTMZID3G.ts.map +7 -0
  10. package/dist/chunks/{menu-MTPSLDHZ.ts → menu-HGG26E3X.ts} +7 -7
  11. package/dist/chunks/{menu-MTPSLDHZ.ts.map → menu-HGG26E3X.ts.map} +1 -1
  12. package/dist/chunks/{tools-OVNH45IJ.ts → tools-O5N4KIAK.ts} +3 -3
  13. package/dist/index.ts +45 -21
  14. package/dist/index.ts.map +2 -2
  15. package/docs/browser-setup.md +7 -1
  16. package/package.json +6 -5
  17. package/src/browser-candidates.ts +184 -0
  18. package/src/browser-manager.ts +93 -276
  19. package/src/browser-settings-menu.ts +23 -0
  20. package/src/browser-startup.ts +122 -0
  21. package/src/chrome-devtools.ts +48 -15
  22. package/src/lazy-tools.ts +358 -48
  23. package/src/menu.ts +3 -3
  24. package/src/runtime.ts +2 -2
  25. package/src/settings.ts +65 -1
  26. package/src/tool-selector.ts +95 -40
  27. package/dist/chunks/browser-settings-menu-FVMCCUPM.ts.map +0 -7
  28. package/dist/chunks/chunk-LSJSDY2M.ts.map +0 -7
  29. package/dist/chunks/chunk-V7N3WI5B.ts.map +0 -7
  30. /package/dist/chunks/{chunk-4B3EQHOH.ts.map → chunk-JLEJ6WFO.ts.map} +0 -0
  31. /package/dist/chunks/{tools-OVNH45IJ.ts.map → tools-O5N4KIAK.ts.map} +0 -0
@@ -23,6 +23,9 @@ import { dirname, isAbsolute, join, resolve } from "node:path";
23
23
  import { CONFIG_DIR_NAME, getAgentDir } from "@earendil-works/pi-coding-agent";
24
24
  var NEW_SETTINGS_FILE_NAME = "pi-chrome-devtools.json";
25
25
  var LEGACY_SETTINGS_FILE_NAME = "pi-chrome-devtools-settings.json";
26
+ function isChromeDevtoolsToolMode(value) {
27
+ return value === "codemode" || value === "lazy" || value === "direct";
28
+ }
26
29
  var DEFAULT_BROWSER_HOST = "127.0.0.1";
27
30
  var DEFAULT_BROWSER_PORT = 9222;
28
31
  var DEFAULT_BROWSER_ENDPOINT = `http://${DEFAULT_BROWSER_HOST}:${DEFAULT_BROWSER_PORT}`;
@@ -77,7 +80,7 @@ async function loadSettings(options = {}) {
77
80
  const userBrowser = user.normalized?.browser ?? {};
78
81
  const effectiveBrowser = resolveEffectiveBrowser(userBrowser, project.normalized?.browser);
79
82
  const settings = resolveSettings(user.normalized, project.normalized, effectiveBrowser);
80
- const recognized = settings.tools !== void 0 || user.normalized?.browser !== void 0 || project.normalized?.browser !== void 0 || user.normalized?.webmcp !== void 0;
83
+ const recognized = user.normalized?.toolMode !== void 0 || settings.tools !== void 0 || user.normalized?.browser !== void 0 || project.normalized?.browser !== void 0 || user.normalized?.webmcp !== void 0;
81
84
  const invalidReasons = [user, project].filter((result) => result.kind === "invalid").map((result) => result.reason).filter((reason) => Boolean(reason));
82
85
  const userFile = user.kind === "invalid" ? {
83
86
  kind: "invalid",
@@ -99,6 +102,7 @@ async function loadSettings(options = {}) {
99
102
  }
100
103
  function resolveSettings(user, _project, browser) {
101
104
  return {
105
+ toolMode: user?.toolMode ?? "codemode",
102
106
  ...user?.tools ? { tools: user.tools, updatedAt: user.updatedAt } : {},
103
107
  browser,
104
108
  webmcp: { enabled: user?.webmcp?.enabled ?? false }
@@ -190,6 +194,11 @@ function projectOwnedSettingsWarnings(document, scope, filePath) {
190
194
  }
191
195
  async function normalizeSettingsDocument(document, scope, filePath, cwd) {
192
196
  const normalized = {};
197
+ if (scope === "user" && document.toolMode !== void 0) {
198
+ if (!isChromeDevtoolsToolMode(document.toolMode))
199
+ throw new Error("expected toolMode to be codemode, lazy, or direct");
200
+ normalized.toolMode = document.toolMode;
201
+ }
193
202
  if (scope === "user" && (document.tools !== void 0 || document.updatedAt !== void 0)) {
194
203
  const toolSettings = normalizeChromeDevtoolsSettings(document);
195
204
  if (!toolSettings) {
@@ -332,16 +341,31 @@ function orderedUniqueChromeDevtoolsTools(tools) {
332
341
  function orderedUnique(values) {
333
342
  return [...new Set(values)];
334
343
  }
335
- function saveSettings(settings, operations = {}) {
344
+ var ToolCatalogApplicationError = class extends Error {
345
+ constructor(error, rollbackError) {
346
+ super(
347
+ rollbackError ? `${formatError(error)}; saved catalog rollback failed: ${formatError(rollbackError)}` : `${formatError(error)}; previous catalog restored`,
348
+ { cause: error }
349
+ );
350
+ this.rollbackError = rollbackError;
351
+ }
352
+ rollbackError;
353
+ };
354
+ function saveSettings(settings, operations = {}, applyAfterSave) {
336
355
  return queueSettingsMutation(
337
356
  (current) => ({
338
357
  ...current,
339
358
  tools: [...settings.tools],
340
359
  updatedAt: settings.updatedAt
341
360
  }),
342
- operations
361
+ operations,
362
+ applyAfterSave
343
363
  );
344
364
  }
365
+ function saveToolMode(mode, operations = {}) {
366
+ if (!isChromeDevtoolsToolMode(mode)) return Promise.reject(new Error("Invalid tool mode"));
367
+ return queueSettingsMutation((current) => ({ ...current, toolMode: mode }), operations);
368
+ }
345
369
  function saveWebMcpSettings(enabled, operations = {}) {
346
370
  return queueSettingsMutation(
347
371
  (current) => ({
@@ -367,12 +391,12 @@ function saveBrowserSettings(patch, operations = {}) {
367
391
  return { ...current, browser };
368
392
  }, operations);
369
393
  }
370
- function queueSettingsMutation(mutate, operations) {
371
- const operation = settingsSaveQueue.then(() => saveSettingsMutationNow(mutate, operations));
394
+ function queueSettingsMutation(mutate, operations, applyToolCatalog) {
395
+ const operation = settingsSaveQueue.then(() => saveSettingsMutationNow(mutate, operations, applyToolCatalog));
372
396
  settingsSaveQueue = operation.catch(() => void 0);
373
397
  return operation;
374
398
  }
375
- async function saveSettingsMutationNow(mutate, operations) {
399
+ async function saveSettingsMutationNow(mutate, operations, applyToolCatalog) {
376
400
  const filePath = settingsFilePath();
377
401
  let current = await readSettingsDocument(filePath, "user");
378
402
  const replaceCanonical = current.kind !== "missing";
@@ -394,6 +418,27 @@ async function saveSettingsMutationNow(mutate, operations) {
394
418
  await rm(tempFile, { force: true }).catch(() => void 0);
395
419
  throw error;
396
420
  }
421
+ if (!applyToolCatalog) return;
422
+ try {
423
+ applyToolCatalog();
424
+ } catch (error) {
425
+ let rollbackError;
426
+ try {
427
+ await saveSettingsMutationNow((latest) => {
428
+ const restored = { ...latest };
429
+ for (const field of ["tools", "updatedAt"]) {
430
+ if (JSON.stringify(latest[field]) !== JSON.stringify(nextDocument[field]))
431
+ throw new Error("Tool catalog changed after save; refusing to overwrite newer settings");
432
+ delete restored[field];
433
+ if (current.document?.[field] !== void 0) restored[field] = current.document[field];
434
+ }
435
+ return restored;
436
+ }, operations);
437
+ } catch (caught) {
438
+ rollbackError = caught;
439
+ }
440
+ throw new ToolCatalogApplicationError(error, rollbackError);
441
+ }
397
442
  }
398
443
  function settingsFilePath() {
399
444
  return join(getAgentDir(), NEW_SETTINGS_FILE_NAME);
@@ -439,7 +484,6 @@ var DEFAULT_HTTP_TIMEOUT_MS = 1e3;
439
484
  var DEFAULT_ENDPOINT_WAIT_MS = 5e3;
440
485
  var DEFAULT_ENDPOINT_RETRY_MS = 250;
441
486
  var MANAGED_BROWSER_PROFILE_PREFIX = "pi-chrome-devtools-profile-";
442
- var DEVTOOLS_ACTIVE_PORT_FILE = "DevToolsActivePort";
443
487
  var BROWSER_SHUTDOWN_WAIT_MS = 1500;
444
488
  var state = {
445
489
  host: DEFAULT_HOST,
@@ -544,15 +588,265 @@ function applyRuntimeBrowserSettings(browser, paths, projectTrusted) {
544
588
 
545
589
  // src/browser-manager.ts
546
590
  import { execFile, spawn } from "node:child_process";
547
- import { constants as constants2 } from "node:fs";
548
- import { access as access2, mkdtemp, readFile as readFile2, rm as rm2 } from "node:fs/promises";
591
+ import { access as access2, mkdtemp, rm as rm2 } from "node:fs/promises";
549
592
  import { createServer } from "node:net";
550
593
  import { tmpdir } from "node:os";
594
+ import { join as join3 } from "node:path";
595
+
596
+ // src/browser-candidates.ts
597
+ import { constants as constants2 } from "node:fs";
551
598
  import { delimiter, isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "node:path";
599
+ function normalizePathForComparison(value) {
600
+ return process.platform === "win32" ? value.toLowerCase() : value;
601
+ }
602
+ function browserCandidateDefinitions(browser) {
603
+ const explicitCandidate = explicitBrowserCandidateDefinition(browser);
604
+ if (explicitCandidate.length > 0) return explicitCandidate;
605
+ return uniqueBrowserCandidates(platformBrowserCandidateDefinitions());
606
+ }
607
+ function explicitBrowserCandidateDefinition(browser) {
608
+ if (!browser.executablePath) return [];
609
+ return [
610
+ {
611
+ label: browser.executablePathSource === "environment" ? "PI_CHROME_DEVTOOLS_BROWSER" : "browser.executablePath",
612
+ executable: browser.executablePath,
613
+ source: "env"
614
+ }
615
+ ];
616
+ }
617
+ function platformBrowserCandidateDefinitions() {
618
+ if (process.platform === "darwin") {
619
+ return [
620
+ {
621
+ label: "Google Chrome",
622
+ executable: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
623
+ source: "wellKnownPath"
624
+ },
625
+ {
626
+ label: "Chromium",
627
+ executable: "/Applications/Chromium.app/Contents/MacOS/Chromium",
628
+ source: "wellKnownPath"
629
+ },
630
+ {
631
+ label: "Brave Browser",
632
+ executable: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
633
+ source: "wellKnownPath"
634
+ },
635
+ {
636
+ label: "Microsoft Edge",
637
+ executable: "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
638
+ source: "wellKnownPath"
639
+ }
640
+ ];
641
+ }
642
+ if (process.platform === "win32") {
643
+ return windowsBrowserCandidateDefinitions();
644
+ }
645
+ return [
646
+ { label: "Google Chrome", executable: "google-chrome", source: "path" },
647
+ { label: "Google Chrome Stable", executable: "google-chrome-stable", source: "path" },
648
+ { label: "Chromium", executable: "chromium", source: "path" },
649
+ { label: "Chromium Browser", executable: "chromium-browser", source: "path" },
650
+ { label: "Brave Browser", executable: "brave-browser", source: "path" },
651
+ { label: "Brave", executable: "brave", source: "path" },
652
+ { label: "Microsoft Edge", executable: "microsoft-edge", source: "path" },
653
+ { label: "Microsoft Edge Stable", executable: "microsoft-edge-stable", source: "path" }
654
+ ];
655
+ }
656
+ function windowsBrowserCandidateDefinitions() {
657
+ const programFiles = [process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"], process.env.LOCALAPPDATA].filter(
658
+ (value) => typeof value === "string" && value.length > 0
659
+ );
660
+ const wellKnownPaths = programFiles.flatMap((root) => [
661
+ join2(root, "Google", "Chrome", "Application", "chrome.exe"),
662
+ join2(root, "Chromium", "Application", "chrome.exe"),
663
+ join2(root, "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
664
+ join2(root, "Microsoft", "Edge", "Application", "msedge.exe")
665
+ ]);
666
+ return [
667
+ ...wellKnownPaths.map((executable) => ({
668
+ label: browserLabelFromExecutable(executable),
669
+ executable,
670
+ source: "wellKnownPath"
671
+ })),
672
+ { label: "Google Chrome", executable: "chrome.exe", source: "path" },
673
+ { label: "Chromium", executable: "chromium.exe", source: "path" },
674
+ { label: "Brave Browser", executable: "brave.exe", source: "path" },
675
+ { label: "Microsoft Edge", executable: "msedge.exe", source: "path" }
676
+ ];
677
+ }
678
+ function browserLabelFromExecutable(executable) {
679
+ const normalizedExecutable = normalizePathForComparison(executable);
680
+ if (normalizedExecutable.includes("brave")) return "Brave Browser";
681
+ if (normalizedExecutable.includes("edge") || normalizedExecutable.includes("msedge")) {
682
+ return "Microsoft Edge";
683
+ }
684
+ if (normalizedExecutable.includes("chromium")) return "Chromium";
685
+ return "Google Chrome";
686
+ }
687
+ function uniqueBrowserCandidates(candidates) {
688
+ const seen = /* @__PURE__ */ new Set();
689
+ return candidates.filter((candidate) => {
690
+ const key = normalizePathForComparison(candidate.executable);
691
+ if (seen.has(key)) return false;
692
+ seen.add(key);
693
+ return true;
694
+ });
695
+ }
696
+ async function resolveBrowserCandidates(definitions, access3, throwIfCancelled) {
697
+ const candidates = [];
698
+ for (const definition of definitions) {
699
+ const resolvedExecutable = await resolveBrowserExecutable(definition.executable, access3, throwIfCancelled);
700
+ throwIfCancelled();
701
+ if (!resolvedExecutable) continue;
702
+ candidates.push({ ...definition, resolvedExecutable });
703
+ }
704
+ return uniqueBrowserCandidatesByResolvedPath(candidates);
705
+ }
706
+ function uniqueBrowserCandidatesByResolvedPath(candidates) {
707
+ const seen = /* @__PURE__ */ new Set();
708
+ return candidates.filter((candidate) => {
709
+ const key = normalizePathForComparison(resolve2(candidate.resolvedExecutable));
710
+ if (seen.has(key)) return false;
711
+ seen.add(key);
712
+ return true;
713
+ });
714
+ }
715
+ async function resolveBrowserExecutable(executable, access3, throwIfCancelled) {
716
+ if (hasPathSeparator(executable) || isAbsolute2(executable)) {
717
+ const resolvedExecutable = isAbsolute2(executable) ? executable : resolve2(executable);
718
+ return await canAccessExecutable(resolvedExecutable, access3, throwIfCancelled) ? resolvedExecutable : void 0;
719
+ }
720
+ for (const directory of executableSearchPath()) {
721
+ for (const executableName of executableSearchNames(executable)) {
722
+ const candidate = join2(directory, executableName);
723
+ if (await canAccessExecutable(candidate, access3, throwIfCancelled)) return candidate;
724
+ }
725
+ }
726
+ return void 0;
727
+ }
728
+ function hasPathSeparator(path) {
729
+ return path.includes("/") || path.includes("\\");
730
+ }
731
+ function executableSearchPath() {
732
+ return (process.env.PATH ?? "").split(delimiter).filter((part) => part.length > 0);
733
+ }
734
+ function executableSearchNames(executable) {
735
+ if (process.platform !== "win32" || /\.[a-z0-9]+$/i.test(executable)) return [executable];
736
+ return [executable, `${executable}.exe`, `${executable}.cmd`, `${executable}.bat`];
737
+ }
738
+ async function canAccessExecutable(path, access3, throwIfCancelled) {
739
+ let accessible = false;
740
+ try {
741
+ await access3(path, process.platform === "win32" ? constants2.F_OK : constants2.X_OK);
742
+ accessible = true;
743
+ } catch {
744
+ }
745
+ throwIfCancelled();
746
+ return accessible;
747
+ }
748
+
749
+ // src/browser-startup.ts
750
+ var MAX_STARTUP_STDERR_BYTES = 64 * 1024;
751
+ function handleErrorsUntilClose(emitter) {
752
+ const ignoreError = () => void 0;
753
+ emitter.on("error", ignoreError);
754
+ emitter.once("close", () => emitter.off("error", ignoreError));
755
+ }
756
+ function browserWebSocketIdentity(value, port) {
757
+ if (typeof value !== "string") return void 0;
758
+ try {
759
+ const url = new URL(value);
760
+ if (url.protocol !== "ws:" || !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname) || Number(url.port || "80") !== port || url.username || url.password || url.search || url.hash || !/^\/devtools\/browser\/[^/]+$/.test(url.pathname)) {
761
+ return void 0;
762
+ }
763
+ return url.pathname;
764
+ } catch {
765
+ return void 0;
766
+ }
767
+ }
768
+ function waitForBrowserStartup(child, port, waitMs, signal) {
769
+ handleErrorsUntilClose(child);
770
+ const stderr = child.stderr;
771
+ if (stderr && !stderr.closed) handleErrorsUntilClose(stderr);
772
+ signal.throwIfAborted();
773
+ if (!stderr) return Promise.reject(new Error("Auto-launched browser has no startup stderr stream."));
774
+ return new Promise((resolve3, reject) => {
775
+ let spawned = false;
776
+ let identity;
777
+ let receivedBytes = 0;
778
+ let pendingLine = "";
779
+ let settled = false;
780
+ let timeout;
781
+ const settle = (error) => {
782
+ if (settled) return;
783
+ if (!error && (!spawned || !identity)) return;
784
+ settled = true;
785
+ clearTimeout(timeout);
786
+ child.off("spawn", onSpawn);
787
+ child.off("error", onError);
788
+ child.off("exit", onExit);
789
+ child.off("close", onExit);
790
+ stderr.off("data", onData);
791
+ stderr.off("end", onEnd);
792
+ stderr.off("error", onError);
793
+ signal.removeEventListener("abort", onAbort);
794
+ stderr.resume();
795
+ if (error) reject(error);
796
+ else resolve3(identity);
797
+ };
798
+ const onError = (error) => settle(error);
799
+ const onAbort = () => settle(new Error("Chrome DevTools browser startup cancelled.", { cause: signal.reason }));
800
+ const onExit = () => settle(new Error("Auto-launched browser exited before DevTools became available."));
801
+ const onSpawn = () => {
802
+ spawned = true;
803
+ timeout = setTimeout(
804
+ () => settle(new Error("Timed out waiting for auto-launched browser DevTools startup announcement.")),
805
+ waitMs
806
+ );
807
+ settle();
808
+ };
809
+ const inspectLine = (line) => {
810
+ const match = /^DevTools listening on (\S+)$/.exec(line.trim());
811
+ if (!match) return;
812
+ identity = browserWebSocketIdentity(match[1], port);
813
+ if (!identity) settle(new Error("Auto-launched browser announced an unexpected DevTools endpoint."));
814
+ else settle();
815
+ };
816
+ const onData = (data) => {
817
+ receivedBytes += Buffer.byteLength(data);
818
+ if (receivedBytes > MAX_STARTUP_STDERR_BYTES) {
819
+ settle(new Error("Auto-launched browser startup stderr exceeded 64 KB before readiness."));
820
+ return;
821
+ }
822
+ pendingLine += data.toString();
823
+ const lines = pendingLine.split("\n");
824
+ pendingLine = lines.pop() ?? "";
825
+ for (const line of lines) {
826
+ inspectLine(line);
827
+ if (settled) break;
828
+ }
829
+ };
830
+ const onEnd = () => {
831
+ inspectLine(pendingLine);
832
+ if (!identity) settle(new Error("Auto-launched browser closed stderr before its DevTools startup announcement."));
833
+ };
834
+ child.once("spawn", onSpawn);
835
+ child.once("error", onError);
836
+ child.once("exit", onExit);
837
+ child.once("close", onExit);
838
+ stderr.on("data", onData);
839
+ stderr.once("end", onEnd);
840
+ stderr.once("error", onError);
841
+ signal.addEventListener("abort", onAbort, { once: true });
842
+ });
843
+ }
844
+
845
+ // src/browser-manager.ts
552
846
  var DEFAULT_BROWSER_MANAGER_OPERATIONS = {
553
847
  access: access2,
554
848
  mkdtemp,
555
- readFile: readFile2,
849
+ allocatePort: allocateBrowserPort,
556
850
  rm: (path, options) => rm2(path, options),
557
851
  fetch: (input, init) => fetch(input, init),
558
852
  spawn,
@@ -625,9 +919,13 @@ function setManagedBrowser(owner, browser) {
625
919
  function launchPromiseForOwner(owner) {
626
920
  return owner ? managedBrowserScope(owner).launchPromise : state.launchPromise;
627
921
  }
922
+ function latestLaunchPromiseForOwner(owner) {
923
+ return owner ? managedBrowserScope(owner).latestLaunchPromise : state.latestLaunchPromise;
924
+ }
628
925
  function setLaunchPromise(owner, launchPromise) {
629
- if (owner) managedBrowserScope(owner).launchPromise = launchPromise;
630
- else state.launchPromise = launchPromise;
926
+ const scope = owner ? managedBrowserScope(owner) : state;
927
+ scope.launchPromise = launchPromise;
928
+ if (launchPromise) scope.latestLaunchPromise = launchPromise;
631
929
  }
632
930
  function managedBrowserPort(owner) {
633
931
  return owner ? managedBrowserScope(owner).port : state.port;
@@ -652,12 +950,6 @@ function setLastLaunchAttempt(owner, attempt) {
652
950
  function formatError2(error) {
653
951
  return error instanceof Error ? error.message : String(error);
654
952
  }
655
- function isNodeError2(error) {
656
- return error instanceof Error && "code" in error;
657
- }
658
- function normalizePathForComparison(value) {
659
- return process.platform === "win32" ? value.toLowerCase() : value;
660
- }
661
953
  async function ensureDevToolsEndpoint(waitMs = DEFAULT_ENDPOINT_WAIT_MS, callerSignal, owner) {
662
954
  callerSignal?.throwIfAborted();
663
955
  if (extensionsConfigured(owner)) {
@@ -665,14 +957,19 @@ async function ensureDevToolsEndpoint(waitMs = DEFAULT_ENDPOINT_WAIT_MS, callerS
665
957
  await ensureManagedBrowserLaunched(waitMs, callerSignal, owner);
666
958
  return;
667
959
  }
960
+ const pendingLaunch = launchPromiseForOwner(owner);
961
+ if (pendingLaunch) {
962
+ await waitForCaller(pendingLaunch, callerSignal);
963
+ return;
964
+ }
668
965
  if (canAutoLaunchBrowser(owner)) {
966
+ const launchBeforeProbe = latestLaunchPromiseForOwner(owner);
669
967
  try {
670
968
  await withEndpointRetry(
671
969
  () => fetchDevToolsJson("/json/version", { signal: callerSignal }, owner),
672
970
  waitMs,
673
971
  callerSignal
674
972
  );
675
- return;
676
973
  } catch (error) {
677
974
  if (shouldAutoLaunchAfterEndpointError(error, owner)) {
678
975
  await ensureManagedBrowserLaunched(waitMs, callerSignal, owner);
@@ -680,6 +977,14 @@ async function ensureDevToolsEndpoint(waitMs = DEFAULT_ENDPOINT_WAIT_MS, callerS
680
977
  }
681
978
  throw error;
682
979
  }
980
+ let observedLaunch = launchBeforeProbe;
981
+ while (true) {
982
+ const concurrentLaunch = latestLaunchPromiseForOwner(owner);
983
+ if (!concurrentLaunch || concurrentLaunch === observedLaunch) break;
984
+ observedLaunch = concurrentLaunch;
985
+ await waitForCaller(concurrentLaunch, callerSignal);
986
+ }
987
+ return;
683
988
  }
684
989
  try {
685
990
  await fetchDevToolsJson("/json/version", { signal: callerSignal }, owner);
@@ -757,7 +1062,7 @@ async function prepareManagedBrowserLaunch(waitMs, generation, signal, owner) {
757
1062
  throwIfBrowserLaunchCancelled(generation, signal, owner);
758
1063
  if (!available) {
759
1064
  throw new DevToolsEndpointError(
760
- `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.`
1065
+ `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 an automatically selected managed port.`
761
1066
  );
762
1067
  }
763
1068
  }
@@ -765,12 +1070,16 @@ async function prepareManagedBrowserLaunch(waitMs, generation, signal, owner) {
765
1070
  }
766
1071
  async function launchManagedBrowser(waitMs, generation, signal, owner) {
767
1072
  throwIfBrowserLaunchCancelled(generation, signal, owner);
768
- const candidateDefinitions = browserCandidateDefinitions(owner);
769
- const candidates = await resolveBrowserCandidates(candidateDefinitions, generation, signal, owner);
1073
+ const candidateDefinitions = browserCandidateDefinitions(browserSettingsForOwner(owner));
1074
+ const candidates = await resolveBrowserCandidates(
1075
+ candidateDefinitions,
1076
+ browserManagerOperations.access,
1077
+ () => throwIfBrowserLaunchCancelled(generation, signal, owner)
1078
+ );
770
1079
  throwIfBrowserLaunchCancelled(generation, signal, owner);
771
1080
  setLastLaunchAttempt(owner, {
772
1081
  candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
773
- mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "dynamic-port"
1082
+ mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "automatic-port"
774
1083
  });
775
1084
  if (candidates.length === 0) {
776
1085
  throw new DevToolsEndpointError(noBrowserCandidateMessage(candidateDefinitions, owner));
@@ -785,7 +1094,7 @@ async function launchManagedBrowser(waitMs, generation, signal, owner) {
785
1094
  setLastLaunchAttempt(owner, {
786
1095
  ...lastLaunchAttempt(owner),
787
1096
  candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
788
- mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "dynamic-port",
1097
+ mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "automatic-port",
789
1098
  selectedCandidate: formatBrowserCandidate(candidate),
790
1099
  userDataDir: managedBrowserForOwner(owner)?.userDataDir
791
1100
  });
@@ -797,7 +1106,7 @@ async function launchManagedBrowser(waitMs, generation, signal, owner) {
797
1106
  setLastLaunchAttempt(owner, {
798
1107
  ...lastLaunchAttempt(owner) ?? {
799
1108
  candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
800
- mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "dynamic-port"
1109
+ mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "automatic-port"
801
1110
  },
802
1111
  lastError: formatError2(cancellation)
803
1112
  });
@@ -807,7 +1116,7 @@ async function launchManagedBrowser(waitMs, generation, signal, owner) {
807
1116
  setLastLaunchAttempt(owner, {
808
1117
  ...lastLaunchAttempt(owner) ?? {
809
1118
  candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
810
- mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "dynamic-port"
1119
+ mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "automatic-port"
811
1120
  },
812
1121
  lastError: formatError2(error)
813
1122
  });
@@ -852,19 +1161,24 @@ function classifyExtensionBrowserVersion(version) {
852
1161
  }
853
1162
  async function launchBrowserCandidate(candidate, waitMs, generation, signal, owner) {
854
1163
  throwIfBrowserLaunchCancelled(generation, signal, owner);
855
- const userDataDir = await browserManagerOperations.mkdtemp(join2(tmpdir(), MANAGED_BROWSER_PROFILE_PREFIX));
1164
+ const userDataDir = await browserManagerOperations.mkdtemp(join3(tmpdir(), MANAGED_BROWSER_PROFILE_PREFIX));
856
1165
  let managedBrowser;
857
1166
  try {
858
1167
  throwIfBrowserLaunchCancelled(generation, signal, owner);
859
- const portArgument = managedBrowserPortConfigured(owner) ? String(managedBrowserPort(owner)) : "0";
860
- const args = buildManagedBrowserLaunchArguments(userDataDir, portArgument, managedBrowserExtensionPaths(owner));
1168
+ const port = managedBrowserPortConfigured(owner) ? managedBrowserPort(owner) : await browserManagerOperations.allocatePort(browserSettingsForOwner(owner).host);
1169
+ throwIfBrowserLaunchCancelled(generation, signal, owner);
1170
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
1171
+ throw new DevToolsEndpointError("Cannot launch a browser without a valid nonzero DevTools port.");
1172
+ }
1173
+ const args = buildManagedBrowserLaunchArguments(userDataDir, String(port), managedBrowserExtensionPaths(owner));
861
1174
  const child = browserManagerOperations.spawn(candidate.resolvedExecutable, args, {
862
1175
  shell: false,
863
- stdio: "ignore"
1176
+ stdio: ["ignore", "ignore", "pipe"]
864
1177
  });
865
1178
  const launchedBrowser = {
866
1179
  process: child,
867
1180
  userDataDir,
1181
+ port,
868
1182
  exited: false,
869
1183
  ready: false,
870
1184
  ownerGeneration: generation,
@@ -882,24 +1196,12 @@ async function launchBrowserCandidate(candidate, waitMs, generation, signal, own
882
1196
  setManagedBrowserPort(owner, managedBrowserConfiguredPort(owner));
883
1197
  }
884
1198
  });
885
- await waitForBrowserSpawn(child);
1199
+ const browserIdentity = await waitForBrowserStartup(child, port, waitMs, signal);
886
1200
  throwIfBrowserLaunchCancelled(generation, signal, owner);
887
- if (managedBrowserPortConfigured(owner)) {
888
- launchedBrowser.port = managedBrowserPort(owner);
889
- } else {
890
- launchedBrowser.port = await readManagedBrowserPort(
891
- userDataDir,
892
- launchedBrowser,
893
- waitMs,
894
- generation,
895
- signal,
896
- owner
897
- );
898
- throwIfBrowserLaunchCancelled(generation, signal, owner);
899
- setManagedBrowserPort(owner, launchedBrowser.port);
900
- }
901
- await waitForDevToolsEndpoint(waitMs, launchedBrowser, generation, signal, owner);
1201
+ setManagedBrowserPort(owner, port);
1202
+ await waitForDevToolsEndpoint(waitMs, launchedBrowser, browserIdentity, generation, signal, owner);
902
1203
  throwIfBrowserLaunchCancelled(generation, signal, owner);
1204
+ throwIfManagedBrowserExited(launchedBrowser);
903
1205
  launchedBrowser.ready = true;
904
1206
  } catch (error) {
905
1207
  if (managedBrowser) await shutdownManagedBrowser(managedBrowser, { awaitLaunch: false, owner });
@@ -924,55 +1226,21 @@ function buildManagedBrowserLaunchArguments(userDataDir, portArgument, extension
924
1226
  "about:blank"
925
1227
  ];
926
1228
  }
927
- function waitForBrowserSpawn(child) {
928
- return new Promise((resolve3, reject) => {
929
- let settled = false;
930
- const settle = (callback) => {
931
- if (settled) return;
932
- settled = true;
933
- child.off("error", onError);
934
- child.off("spawn", onSpawn);
935
- callback();
936
- };
937
- const onError = (error) => settle(() => reject(error));
938
- const onSpawn = () => settle(resolve3);
939
- child.once("error", onError);
940
- child.once("spawn", onSpawn);
941
- });
942
- }
943
- async function readManagedBrowserPort(userDataDir, managedBrowser, waitMs, generation, signal, owner) {
944
- const activePortFile = join2(userDataDir, DEVTOOLS_ACTIVE_PORT_FILE);
945
- const deadline = Date.now() + waitMs;
946
- while (true) {
947
- throwIfManagedBrowserExited(managedBrowser);
948
- const text = await browserManagerOperations.readFile(activePortFile, "utf8").catch((error) => {
949
- if (isNodeError2(error) && error.code === "ENOENT") return void 0;
950
- throw error;
951
- });
952
- throwIfBrowserLaunchCancelled(generation, signal, owner);
953
- const portText = text?.split(/\r?\n/, 1)[0]?.trim();
954
- const port = Number(portText);
955
- if (Number.isInteger(port) && port > 0) return port;
956
- const remainingMs = deadline - Date.now();
957
- if (remainingMs <= 0) {
958
- throw new DevToolsEndpointError(
959
- [
960
- "Timed out waiting for auto-launched browser DevToolsActivePort.",
961
- `Expected file: ${activePortFile}`,
962
- launchHint(owner)
963
- ].join("\n")
964
- );
965
- }
966
- await browserManagerOperations.sleep(Math.min(DEFAULT_ENDPOINT_RETRY_MS, remainingMs), signal);
967
- }
968
- }
969
- async function waitForDevToolsEndpoint(waitMs, managedBrowser, generation, signal, owner) {
1229
+ async function waitForDevToolsEndpoint(waitMs, managedBrowser, browserIdentity, generation, signal, owner) {
970
1230
  const deadline = Date.now() + waitMs;
971
1231
  while (true) {
972
1232
  throwIfManagedBrowserExited(managedBrowser);
973
1233
  try {
974
- await fetchDevToolsJson("/json/version", { signal }, owner);
1234
+ const version = await fetchDevToolsJson(
1235
+ "/json/version",
1236
+ { signal },
1237
+ owner
1238
+ );
975
1239
  throwIfBrowserLaunchCancelled(generation, signal, owner);
1240
+ throwIfManagedBrowserExited(managedBrowser);
1241
+ if (browserWebSocketIdentity(version?.webSocketDebuggerUrl, managedBrowser.port ?? 0) !== browserIdentity) {
1242
+ throw new DevToolsEndpointError("DevTools endpoint does not match the auto-launched browser.");
1243
+ }
976
1244
  return;
977
1245
  } catch (error) {
978
1246
  if (!isRetryableEndpointError(error)) throw error;
@@ -1171,16 +1439,16 @@ function launchModeLabel(owner) {
1171
1439
  if (!isLocalDevToolsHost(browser.host)) return "invalid; extensions require a local endpoint";
1172
1440
  if (!browser.autoLaunchEnabled) return "invalid; extensions require auto-launch";
1173
1441
  if (managedBrowser && !managedBrowser.exited) {
1174
- return managedBrowserPortConfigured(owner) ? "extension-owned browser on explicit port" : "extension-owned browser on dynamic port";
1442
+ return managedBrowserPortConfigured(owner) ? "extension-owned browser on explicit port" : "extension-owned browser on automatically selected port";
1175
1443
  }
1176
- return managedBrowserPortConfigured(owner) ? "force extension-owned launch on explicit port" : "force extension-owned launch on dynamic port";
1444
+ return managedBrowserPortConfigured(owner) ? "force extension-owned launch on explicit port" : "force extension-owned launch on automatically selected port";
1177
1445
  }
1178
1446
  if (!isLocalDevToolsHost(browser.host)) return "manual remote endpoint";
1179
1447
  if (!browser.autoLaunchEnabled) return "manual; auto-launch disabled";
1180
1448
  if (managedBrowser && !managedBrowser.exited) {
1181
- return managedBrowserPortConfigured(owner) ? "auto-launched on explicit port" : "auto-launched on dynamic port";
1449
+ return managedBrowserPortConfigured(owner) ? "auto-launched on explicit port" : "auto-launched on automatically selected port";
1182
1450
  }
1183
- return managedBrowserPortConfigured(owner) ? "attach first; auto-launch explicit port" : "attach first; auto-launch dynamic port";
1451
+ return managedBrowserPortConfigured(owner) ? "attach first; auto-launch explicit port" : "attach first; auto-launch on an automatically selected port";
1184
1452
  }
1185
1453
  function launchAttemptLines(owner) {
1186
1454
  const attempt = lastLaunchAttempt(owner);
@@ -1206,14 +1474,14 @@ function launchHint(owner) {
1206
1474
  if (!browser.autoLaunchEnabled) {
1207
1475
  return `Auto-launch is disabled. Start a browser manually: ${chromeLaunchCommand(owner)}`;
1208
1476
  }
1209
- const managedMode = managedBrowserPortConfigured(owner) ? `port ${managedBrowserPort(owner)}` : "a dynamic DevTools port";
1477
+ const managedMode = managedBrowserPortConfigured(owner) ? `port ${managedBrowserPort(owner)}` : "an automatically selected nonzero DevTools port";
1210
1478
  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)}`;
1211
1479
  }
1212
1480
  function browserCandidateHint(owner) {
1213
1481
  if (extensionsConfigured(owner)) {
1214
1482
  return "Extension browser requirement: configure Chrome for Testing or Chromium with browser.executablePath; branded Chrome is rejected because it may ignore unpacked-extension flags.";
1215
1483
  }
1216
- return `Browser candidates: ${browserCandidateDefinitions(owner).map((candidate) => candidate.label).join(", ")}`;
1484
+ return `Browser candidates: ${browserCandidateDefinitions(browserSettingsForOwner(owner)).map((candidate) => candidate.label).join(", ")}`;
1217
1485
  }
1218
1486
  function chromeLaunchCommand(owner) {
1219
1487
  const executable = browserSettingsForOwner(owner).executablePath ?? defaultManualBrowserExecutable();
@@ -1233,151 +1501,6 @@ function isLocalDevToolsHost(host) {
1233
1501
  const normalizedHost = host.toLowerCase().replace(/^\[(.*)]$/, "$1");
1234
1502
  return ["localhost", "127.0.0.1", "::1", "0:0:0:0:0:0:0:1"].includes(normalizedHost);
1235
1503
  }
1236
- function browserCandidateDefinitions(owner) {
1237
- const explicitCandidate = explicitBrowserCandidateDefinition(owner);
1238
- if (explicitCandidate.length > 0) return explicitCandidate;
1239
- return uniqueBrowserCandidates(platformBrowserCandidateDefinitions());
1240
- }
1241
- function explicitBrowserCandidateDefinition(owner) {
1242
- const browser = browserSettingsForOwner(owner);
1243
- if (!browser.executablePath) return [];
1244
- return [
1245
- {
1246
- label: browser.executablePathSource === "environment" ? "PI_CHROME_DEVTOOLS_BROWSER" : "browser.executablePath",
1247
- executable: browser.executablePath,
1248
- source: "env"
1249
- }
1250
- ];
1251
- }
1252
- function platformBrowserCandidateDefinitions() {
1253
- if (process.platform === "darwin") {
1254
- return [
1255
- {
1256
- label: "Google Chrome",
1257
- executable: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
1258
- source: "wellKnownPath"
1259
- },
1260
- {
1261
- label: "Chromium",
1262
- executable: "/Applications/Chromium.app/Contents/MacOS/Chromium",
1263
- source: "wellKnownPath"
1264
- },
1265
- {
1266
- label: "Brave Browser",
1267
- executable: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
1268
- source: "wellKnownPath"
1269
- },
1270
- {
1271
- label: "Microsoft Edge",
1272
- executable: "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
1273
- source: "wellKnownPath"
1274
- }
1275
- ];
1276
- }
1277
- if (process.platform === "win32") {
1278
- return windowsBrowserCandidateDefinitions();
1279
- }
1280
- return [
1281
- { label: "Google Chrome", executable: "google-chrome", source: "path" },
1282
- { label: "Google Chrome Stable", executable: "google-chrome-stable", source: "path" },
1283
- { label: "Chromium", executable: "chromium", source: "path" },
1284
- { label: "Chromium Browser", executable: "chromium-browser", source: "path" },
1285
- { label: "Brave Browser", executable: "brave-browser", source: "path" },
1286
- { label: "Brave", executable: "brave", source: "path" },
1287
- { label: "Microsoft Edge", executable: "microsoft-edge", source: "path" },
1288
- { label: "Microsoft Edge Stable", executable: "microsoft-edge-stable", source: "path" }
1289
- ];
1290
- }
1291
- function windowsBrowserCandidateDefinitions() {
1292
- const programFiles = [process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"], process.env.LOCALAPPDATA].filter(
1293
- (value) => typeof value === "string" && value.length > 0
1294
- );
1295
- const wellKnownPaths = programFiles.flatMap((root) => [
1296
- join2(root, "Google", "Chrome", "Application", "chrome.exe"),
1297
- join2(root, "Chromium", "Application", "chrome.exe"),
1298
- join2(root, "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
1299
- join2(root, "Microsoft", "Edge", "Application", "msedge.exe")
1300
- ]);
1301
- return [
1302
- ...wellKnownPaths.map((executable) => ({
1303
- label: browserLabelFromExecutable(executable),
1304
- executable,
1305
- source: "wellKnownPath"
1306
- })),
1307
- { label: "Google Chrome", executable: "chrome.exe", source: "path" },
1308
- { label: "Chromium", executable: "chromium.exe", source: "path" },
1309
- { label: "Brave Browser", executable: "brave.exe", source: "path" },
1310
- { label: "Microsoft Edge", executable: "msedge.exe", source: "path" }
1311
- ];
1312
- }
1313
- function browserLabelFromExecutable(executable) {
1314
- const normalizedExecutable = normalizePathForComparison(executable);
1315
- if (normalizedExecutable.includes("brave")) return "Brave Browser";
1316
- if (normalizedExecutable.includes("edge") || normalizedExecutable.includes("msedge")) {
1317
- return "Microsoft Edge";
1318
- }
1319
- if (normalizedExecutable.includes("chromium")) return "Chromium";
1320
- return "Google Chrome";
1321
- }
1322
- function uniqueBrowserCandidates(candidates) {
1323
- const seen = /* @__PURE__ */ new Set();
1324
- return candidates.filter((candidate) => {
1325
- const key = normalizePathForComparison(candidate.executable);
1326
- if (seen.has(key)) return false;
1327
- seen.add(key);
1328
- return true;
1329
- });
1330
- }
1331
- async function resolveBrowserCandidates(definitions, generation, signal, owner) {
1332
- const candidates = [];
1333
- for (const definition of definitions) {
1334
- const resolvedExecutable = await resolveBrowserExecutable(definition.executable);
1335
- throwIfBrowserLaunchCancelled(generation, signal, owner);
1336
- if (!resolvedExecutable) continue;
1337
- candidates.push({ ...definition, resolvedExecutable });
1338
- }
1339
- return uniqueBrowserCandidatesByResolvedPath(candidates);
1340
- }
1341
- function uniqueBrowserCandidatesByResolvedPath(candidates) {
1342
- const seen = /* @__PURE__ */ new Set();
1343
- return candidates.filter((candidate) => {
1344
- const key = normalizePathForComparison(resolve2(candidate.resolvedExecutable));
1345
- if (seen.has(key)) return false;
1346
- seen.add(key);
1347
- return true;
1348
- });
1349
- }
1350
- async function resolveBrowserExecutable(executable) {
1351
- if (hasPathSeparator(executable) || isAbsolute2(executable)) {
1352
- const resolvedExecutable = isAbsolute2(executable) ? executable : resolve2(executable);
1353
- return await canAccessExecutable(resolvedExecutable) ? resolvedExecutable : void 0;
1354
- }
1355
- for (const directory of executableSearchPath()) {
1356
- for (const executableName of executableSearchNames(executable)) {
1357
- const candidate = join2(directory, executableName);
1358
- if (await canAccessExecutable(candidate)) return candidate;
1359
- }
1360
- }
1361
- return void 0;
1362
- }
1363
- function hasPathSeparator(path) {
1364
- return path.includes("/") || path.includes("\\");
1365
- }
1366
- function executableSearchPath() {
1367
- return (process.env.PATH ?? "").split(delimiter).filter((part) => part.length > 0);
1368
- }
1369
- function executableSearchNames(executable) {
1370
- if (process.platform !== "win32" || /\.[a-z0-9]+$/i.test(executable)) return [executable];
1371
- return [executable, `${executable}.exe`, `${executable}.cmd`, `${executable}.bat`];
1372
- }
1373
- async function canAccessExecutable(path) {
1374
- try {
1375
- await browserManagerOperations.access(path, process.platform === "win32" ? constants2.F_OK : constants2.X_OK);
1376
- return true;
1377
- } catch {
1378
- return false;
1379
- }
1380
- }
1381
1504
  function formatBrowserCandidate(candidate) {
1382
1505
  return `${candidate.label} (${candidate.resolvedExecutable})`;
1383
1506
  }
@@ -1435,19 +1558,29 @@ function inspectBrowserVersion(executable, signal) {
1435
1558
  });
1436
1559
  });
1437
1560
  }
1438
- function isPortAvailable(host, port) {
1439
- return new Promise((resolveAvailability, reject) => {
1561
+ async function allocateBrowserPort(host) {
1562
+ const port = await probeBrowserPort(host, 0);
1563
+ if (port === void 0) throw new Error("Cannot allocate an available DevTools port.");
1564
+ return port;
1565
+ }
1566
+ async function isPortAvailable(host, port) {
1567
+ return await probeBrowserPort(host, port) !== void 0;
1568
+ }
1569
+ function probeBrowserPort(host, port) {
1570
+ return new Promise((resolvePort, reject) => {
1440
1571
  const server = createServer();
1441
1572
  const normalizedHost = host.replace(/^\[(.*)]$/, "$1");
1442
1573
  server.unref();
1443
1574
  server.once("error", (error) => {
1444
- if (error.code === "EADDRINUSE") resolveAvailability(false);
1575
+ if (error.code === "EADDRINUSE") resolvePort(void 0);
1445
1576
  else reject(error);
1446
1577
  });
1447
1578
  server.listen(port, normalizedHost, () => {
1579
+ const address = server.address();
1448
1580
  server.close((error) => {
1449
1581
  if (error) reject(error);
1450
- else resolveAvailability(true);
1582
+ else if (!address || typeof address === "string") reject(new Error("DevTools port probe has no address."));
1583
+ else resolvePort(address.port);
1451
1584
  });
1452
1585
  });
1453
1586
  });
@@ -1468,10 +1601,13 @@ export {
1468
1601
  WEBMCP_TOOL_NAMES,
1469
1602
  CHROME_DEVTOOLS_TOOL_NAMES,
1470
1603
  isWebMcpToolName,
1604
+ isChromeDevtoolsToolMode,
1471
1605
  waitForSettingsWrites,
1472
1606
  loadSettings,
1473
1607
  parseBrowserEndpoint,
1608
+ ToolCatalogApplicationError,
1474
1609
  saveSettings,
1610
+ saveToolMode,
1475
1611
  saveWebMcpSettings,
1476
1612
  saveBrowserSettings,
1477
1613
  settingsFilePath,
@@ -1505,4 +1641,4 @@ export {
1505
1641
  endpointConfigHint,
1506
1642
  formatPageListItem
1507
1643
  };
1508
- //# sourceMappingURL=chunk-LSJSDY2M.ts.map
1644
+ //# sourceMappingURL=chunk-WTMZID3G.ts.map