@btraut/browser-bridge 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js CHANGED
@@ -39,55 +39,8 @@ module.exports = __toCommonJS(api_exports);
39
39
  var import_http = require("http");
40
40
  var import_express2 = __toESM(require("express"));
41
41
 
42
- // packages/shared/src/errors.ts
43
- var import_zod = require("zod");
44
- var ErrorCodeSchema = import_zod.z.enum([
45
- "UNKNOWN",
46
- "INVALID_ARGUMENT",
47
- "NOT_FOUND",
48
- "ALREADY_EXISTS",
49
- "FAILED_PRECONDITION",
50
- "UNAUTHORIZED",
51
- "FORBIDDEN",
52
- "PERMISSION_REQUIRED",
53
- "PERMISSION_DENIED",
54
- "PERMISSION_PROMPT_TIMEOUT",
55
- "CONFLICT",
56
- "TIMEOUT",
57
- "CANCELLED",
58
- "UNAVAILABLE",
59
- "RATE_LIMITED",
60
- "NOT_IMPLEMENTED",
61
- "INTERNAL",
62
- "SESSION_NOT_FOUND",
63
- "SESSION_CLOSED",
64
- "SESSION_BROKEN",
65
- "DRIVE_UNAVAILABLE",
66
- "INSPECT_UNAVAILABLE",
67
- "EXTENSION_DISCONNECTED",
68
- "DEBUGGER_IN_USE",
69
- "ATTACH_DENIED",
70
- "TAB_NOT_FOUND",
71
- "NOT_SUPPORTED",
72
- "LOCATOR_NOT_FOUND",
73
- "NAVIGATION_FAILED",
74
- "EVALUATION_FAILED",
75
- "ARTIFACT_IO_ERROR"
76
- ]);
77
- var ErrorInfoSchema = import_zod.z.object({
78
- code: ErrorCodeSchema,
79
- message: import_zod.z.string(),
80
- retryable: import_zod.z.boolean(),
81
- details: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
82
- });
83
- var ErrorEnvelopeSchema = import_zod.z.object({
84
- ok: import_zod.z.literal(false),
85
- error: ErrorInfoSchema
86
- });
87
- var successEnvelopeSchema = (result) => import_zod.z.object({
88
- ok: import_zod.z.literal(true),
89
- result
90
- });
42
+ // packages/shared/src/core-readiness.ts
43
+ var import_promises = require("node:timers/promises");
91
44
 
92
45
  // packages/shared/src/logging.ts
93
46
  var import_node_fs2 = require("node:fs");
@@ -638,6 +591,248 @@ var createJsonlLogger = (options) => {
638
591
  return buildLogger(state, options.bindings ?? {});
639
592
  };
640
593
 
594
+ // packages/shared/src/core-readiness.ts
595
+ var DEFAULT_TIMEOUT_MS = 3e4;
596
+ var DEFAULT_HEALTH_RETRY_MS = 250;
597
+ var DEFAULT_HEALTH_ATTEMPTS = 20;
598
+ var DEFAULT_HEALTH_TIMEOUT_MS = 2e3;
599
+ var DEFAULT_HEALTH_BUDGET_MS = 15e3;
600
+ var resolveTimeoutMs = (timeoutMs) => {
601
+ const candidate = timeoutMs ?? (process.env.BROWSER_BRIDGE_CORE_TIMEOUT_MS ? Number.parseInt(process.env.BROWSER_BRIDGE_CORE_TIMEOUT_MS, 10) : process.env.BROWSER_VISION_CORE_TIMEOUT_MS ? Number.parseInt(process.env.BROWSER_VISION_CORE_TIMEOUT_MS, 10) : void 0);
602
+ if (candidate === void 0 || candidate === null) {
603
+ return DEFAULT_TIMEOUT_MS;
604
+ }
605
+ const parsed = typeof candidate === "number" ? candidate : Number(candidate);
606
+ if (!Number.isFinite(parsed) || parsed <= 0) {
607
+ throw new Error(`Invalid timeoutMs: ${String(candidate)}`);
608
+ }
609
+ return Math.floor(parsed);
610
+ };
611
+ var resolvePositiveInteger = (value, fallback) => {
612
+ if (value === void 0) {
613
+ return fallback;
614
+ }
615
+ if (!Number.isFinite(value) || value <= 0) {
616
+ throw new Error(`Invalid positive integer value: ${String(value)}`);
617
+ }
618
+ return Math.floor(value);
619
+ };
620
+ var hasExplicitRuntimeInput = (options) => options.host !== void 0 || options.port !== void 0 || process.env.BROWSER_BRIDGE_CORE_HOST !== void 0 || process.env.BROWSER_VISION_CORE_HOST !== void 0 || process.env.BROWSER_BRIDGE_CORE_PORT !== void 0 || process.env.BROWSER_VISION_CORE_PORT !== void 0;
621
+ var createCoreReadinessController = (options = {}) => {
622
+ const logger = options.logger ?? createJsonlLogger({
623
+ stream: "cli",
624
+ cwd: options.cwd
625
+ }).child({ scope: "core-readiness" });
626
+ const logPrefix = options.logPrefix ?? "core";
627
+ const timeoutMs = resolveTimeoutMs(options.timeoutMs);
628
+ const fetchImpl = options.fetchImpl ?? fetch;
629
+ const ensureDaemon = options.ensureDaemon ?? true;
630
+ const healthRetryMs = resolvePositiveInteger(
631
+ options.healthRetryMs,
632
+ DEFAULT_HEALTH_RETRY_MS
633
+ );
634
+ const healthAttempts = resolvePositiveInteger(
635
+ options.healthAttempts,
636
+ DEFAULT_HEALTH_ATTEMPTS
637
+ );
638
+ const healthTimeoutMs = resolvePositiveInteger(
639
+ options.healthTimeoutMs,
640
+ Math.min(timeoutMs, DEFAULT_HEALTH_TIMEOUT_MS)
641
+ );
642
+ const healthBudgetMs = resolvePositiveInteger(
643
+ options.healthBudgetMs,
644
+ DEFAULT_HEALTH_BUDGET_MS
645
+ );
646
+ let runtime = resolveCoreRuntime({
647
+ host: options.host,
648
+ port: options.port,
649
+ cwd: options.cwd,
650
+ strictEnvPort: options.strictEnvPort ?? true
651
+ });
652
+ let baseUrl = `http://${runtime.host}:${runtime.port}`;
653
+ const allowRuntimeRefresh = !hasExplicitRuntimeInput(options);
654
+ const refreshRuntime = () => {
655
+ if (!allowRuntimeRefresh) {
656
+ return;
657
+ }
658
+ runtime = resolveCoreRuntime({
659
+ cwd: options.cwd,
660
+ strictEnvPort: options.strictEnvPort ?? true
661
+ });
662
+ baseUrl = `http://${runtime.host}:${runtime.port}`;
663
+ };
664
+ const checkHealth = async () => {
665
+ try {
666
+ const controller = new AbortController();
667
+ const timeout = setTimeout(() => controller.abort(), healthTimeoutMs);
668
+ try {
669
+ let response;
670
+ try {
671
+ response = await fetchImpl(`${baseUrl}/health`, {
672
+ method: "GET",
673
+ signal: controller.signal
674
+ });
675
+ } catch (error) {
676
+ if (controller.signal.aborted || error instanceof Error && error.name === "AbortError") {
677
+ logger.warn(`${logPrefix}.health.timeout`, {
678
+ base_url: baseUrl,
679
+ timeout_ms: healthTimeoutMs
680
+ });
681
+ return false;
682
+ }
683
+ logger.warn(`${logPrefix}.health.fetch_failed`, {
684
+ base_url: baseUrl,
685
+ error
686
+ });
687
+ throw error;
688
+ }
689
+ if (!response.ok) {
690
+ logger.warn(`${logPrefix}.health.non_ok`, {
691
+ base_url: baseUrl,
692
+ status: response.status
693
+ });
694
+ return false;
695
+ }
696
+ const data = await response.json().catch(() => null);
697
+ const ok = Boolean(data?.ok);
698
+ if (!ok) {
699
+ logger.warn(`${logPrefix}.health.not_ready`, {
700
+ base_url: baseUrl
701
+ });
702
+ }
703
+ return ok;
704
+ } finally {
705
+ clearTimeout(timeout);
706
+ }
707
+ } catch (error) {
708
+ logger.warn(`${logPrefix}.health.error`, {
709
+ base_url: baseUrl,
710
+ error
711
+ });
712
+ return false;
713
+ }
714
+ };
715
+ const ensureCoreRunning = async () => {
716
+ refreshRuntime();
717
+ if (await checkHealth()) {
718
+ logger.debug(`${logPrefix}.ensure_ready.already_running`, {
719
+ base_url: baseUrl
720
+ });
721
+ return;
722
+ }
723
+ if (!options.spawnDaemon) {
724
+ logger.error(`${logPrefix}.ensure_ready.missing_spawn`, {
725
+ host: runtime.host,
726
+ port: runtime.port
727
+ });
728
+ throw new Error(
729
+ `Core daemon is not running on ${runtime.host}:${runtime.port} and spawnDaemon is not configured.`
730
+ );
731
+ }
732
+ options.spawnDaemon(runtime);
733
+ const deadlineAt = Date.now() + healthBudgetMs;
734
+ for (let attempt = 0; attempt < healthAttempts; attempt += 1) {
735
+ const remainingBudgetMs = deadlineAt - Date.now();
736
+ if (remainingBudgetMs <= 0) {
737
+ break;
738
+ }
739
+ await (0, import_promises.setTimeout)(Math.min(healthRetryMs, remainingBudgetMs));
740
+ refreshRuntime();
741
+ if (await checkHealth()) {
742
+ logger.info(`${logPrefix}.ensure_ready.ready`, {
743
+ base_url: baseUrl,
744
+ attempts: attempt + 1
745
+ });
746
+ return;
747
+ }
748
+ }
749
+ logger.error(`${logPrefix}.ensure_ready.failed`, {
750
+ host: runtime.host,
751
+ port: runtime.port,
752
+ attempts: healthAttempts,
753
+ health_budget_ms: healthBudgetMs,
754
+ health_timeout_ms: healthTimeoutMs
755
+ });
756
+ throw new Error(
757
+ `Core daemon failed to start on ${runtime.host}:${runtime.port}.`
758
+ );
759
+ };
760
+ let ensurePromise = null;
761
+ const ensureReady = async () => {
762
+ if (!ensureDaemon) {
763
+ return;
764
+ }
765
+ if (!ensurePromise) {
766
+ ensurePromise = ensureCoreRunning().catch((error) => {
767
+ ensurePromise = null;
768
+ throw error;
769
+ });
770
+ }
771
+ await ensurePromise;
772
+ };
773
+ return {
774
+ get baseUrl() {
775
+ return baseUrl;
776
+ },
777
+ get runtime() {
778
+ return runtime;
779
+ },
780
+ refreshRuntime,
781
+ ensureReady,
782
+ checkHealth
783
+ };
784
+ };
785
+
786
+ // packages/shared/src/errors.ts
787
+ var import_zod = require("zod");
788
+ var ErrorCodeSchema = import_zod.z.enum([
789
+ "UNKNOWN",
790
+ "INVALID_ARGUMENT",
791
+ "NOT_FOUND",
792
+ "ALREADY_EXISTS",
793
+ "FAILED_PRECONDITION",
794
+ "UNAUTHORIZED",
795
+ "FORBIDDEN",
796
+ "PERMISSION_REQUIRED",
797
+ "PERMISSION_DENIED",
798
+ "PERMISSION_PROMPT_TIMEOUT",
799
+ "CONFLICT",
800
+ "TIMEOUT",
801
+ "CANCELLED",
802
+ "UNAVAILABLE",
803
+ "RATE_LIMITED",
804
+ "NOT_IMPLEMENTED",
805
+ "INTERNAL",
806
+ "SESSION_NOT_FOUND",
807
+ "SESSION_CLOSED",
808
+ "SESSION_BROKEN",
809
+ "DRIVE_UNAVAILABLE",
810
+ "INSPECT_UNAVAILABLE",
811
+ "EXTENSION_DISCONNECTED",
812
+ "DEBUGGER_IN_USE",
813
+ "ATTACH_DENIED",
814
+ "TAB_NOT_FOUND",
815
+ "NOT_SUPPORTED",
816
+ "LOCATOR_NOT_FOUND",
817
+ "NAVIGATION_FAILED",
818
+ "EVALUATION_FAILED",
819
+ "ARTIFACT_IO_ERROR"
820
+ ]);
821
+ var ErrorInfoSchema = import_zod.z.object({
822
+ code: ErrorCodeSchema,
823
+ message: import_zod.z.string(),
824
+ retryable: import_zod.z.boolean(),
825
+ details: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
826
+ });
827
+ var ErrorEnvelopeSchema = import_zod.z.object({
828
+ ok: import_zod.z.literal(false),
829
+ error: ErrorInfoSchema
830
+ });
831
+ var successEnvelopeSchema = (result) => import_zod.z.object({
832
+ ok: import_zod.z.literal(true),
833
+ result
834
+ });
835
+
641
836
  // packages/shared/src/schemas.ts
642
837
  var import_zod2 = require("zod");
643
838
  var LocatorRoleSchema = import_zod2.z.object({
@@ -702,6 +897,39 @@ var DiagnosticCheckSchema = import_zod2.z.object({
702
897
  message: import_zod2.z.string().optional(),
703
898
  details: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional()
704
899
  });
900
+ var DiagnosticsRuntimeEndpointSchema = import_zod2.z.object({
901
+ host: import_zod2.z.string().optional(),
902
+ port: import_zod2.z.number().finite().optional(),
903
+ base_url: import_zod2.z.string().optional(),
904
+ host_source: import_zod2.z.string().optional(),
905
+ port_source: import_zod2.z.string().optional(),
906
+ metadata_path: import_zod2.z.string().optional(),
907
+ isolated_mode: import_zod2.z.boolean().optional()
908
+ });
909
+ var DiagnosticsRuntimeProcessSchema = import_zod2.z.object({
910
+ component: import_zod2.z.enum(["cli", "mcp", "core"]).optional(),
911
+ version: import_zod2.z.string().optional(),
912
+ pid: import_zod2.z.number().int().positive().optional(),
913
+ node_version: import_zod2.z.string().optional(),
914
+ binary_path: import_zod2.z.string().optional(),
915
+ argv_entry: import_zod2.z.string().optional()
916
+ });
917
+ var DiagnosticsRuntimeCallerSchema = import_zod2.z.object({
918
+ endpoint: DiagnosticsRuntimeEndpointSchema.optional(),
919
+ process: DiagnosticsRuntimeProcessSchema.optional()
920
+ });
921
+ var DiagnosticsRuntimeContextSchema = import_zod2.z.object({
922
+ caller: DiagnosticsRuntimeCallerSchema.optional(),
923
+ core: import_zod2.z.object({
924
+ endpoint: DiagnosticsRuntimeEndpointSchema.optional(),
925
+ process: DiagnosticsRuntimeProcessSchema.optional()
926
+ }).optional(),
927
+ extension: import_zod2.z.object({
928
+ version: import_zod2.z.string().optional(),
929
+ endpoint: DiagnosticsRuntimeEndpointSchema.optional(),
930
+ port_source: import_zod2.z.enum(["default", "storage"]).optional()
931
+ }).optional()
932
+ });
705
933
  var DiagnosticReportSchema = import_zod2.z.object({
706
934
  ok: import_zod2.z.boolean(),
707
935
  session_id: import_zod2.z.string().optional(),
@@ -750,7 +978,8 @@ var DiagnosticReportSchema = import_zod2.z.object({
750
978
  loop_detected: import_zod2.z.boolean().optional()
751
979
  }).optional(),
752
980
  warnings: import_zod2.z.array(import_zod2.z.string()).optional(),
753
- notes: import_zod2.z.array(import_zod2.z.string()).optional()
981
+ notes: import_zod2.z.array(import_zod2.z.string()).optional(),
982
+ runtime: DiagnosticsRuntimeContextSchema.optional()
754
983
  });
755
984
  var SessionIdSchema = import_zod2.z.object({
756
985
  session_id: import_zod2.z.string().min(1)
@@ -1142,7 +1371,8 @@ var HealthCheckOutputSchema = import_zod2.z.object({
1142
1371
  }).passthrough()
1143
1372
  }).passthrough();
1144
1373
  var DiagnosticsDoctorInputSchema = import_zod2.z.object({
1145
- session_id: import_zod2.z.string().min(1).optional()
1374
+ session_id: import_zod2.z.string().min(1).optional(),
1375
+ caller: DiagnosticsRuntimeCallerSchema.optional()
1146
1376
  });
1147
1377
  var DiagnosticsDoctorOutputSchema = DiagnosticReportSchema;
1148
1378
 
@@ -1612,14 +1842,14 @@ var InspectError = class extends Error {
1612
1842
 
1613
1843
  // packages/core/src/inspect/service.ts
1614
1844
  var import_crypto3 = require("crypto");
1615
- var import_promises2 = require("node:fs/promises");
1845
+ var import_promises3 = require("node:fs/promises");
1616
1846
  var import_node_path4 = __toESM(require("node:path"));
1617
1847
  var import_readability = require("@mozilla/readability");
1618
1848
  var import_jsdom = require("jsdom");
1619
1849
  var import_turndown = __toESM(require("turndown"));
1620
1850
 
1621
1851
  // packages/core/src/artifacts.ts
1622
- var import_promises = require("node:fs/promises");
1852
+ var import_promises2 = require("node:fs/promises");
1623
1853
  var import_node_os = __toESM(require("node:os"));
1624
1854
  var import_node_path3 = __toESM(require("node:path"));
1625
1855
  var ARTIFACTS_DIR_NAME = "browser-agent";
@@ -1627,7 +1857,7 @@ var resolveTempRoot = () => process.env.TMPDIR || process.env.TEMP || process.en
1627
1857
  var getArtifactRootDir = (sessionId) => import_node_path3.default.join(resolveTempRoot(), ARTIFACTS_DIR_NAME, sessionId);
1628
1858
  var ensureArtifactRootDir = async (sessionId) => {
1629
1859
  const rootDir = getArtifactRootDir(sessionId);
1630
- await (0, import_promises.mkdir)(rootDir, { recursive: true });
1860
+ await (0, import_promises2.mkdir)(rootDir, { recursive: true });
1631
1861
  return rootDir;
1632
1862
  };
1633
1863
 
@@ -1681,6 +1911,10 @@ var ExtensionBridge = class {
1681
1911
  return {
1682
1912
  connected: this.connected,
1683
1913
  lastSeenAt: this.lastSeenAt,
1914
+ version: this.version,
1915
+ coreHost: this.coreHost,
1916
+ corePort: this.corePort,
1917
+ corePortSource: this.corePortSource,
1684
1918
  tabs: this.tabs
1685
1919
  };
1686
1920
  }
@@ -1718,7 +1952,7 @@ var ExtensionBridge = class {
1718
1952
  status: "request",
1719
1953
  params
1720
1954
  };
1721
- const response = await new Promise((resolve2, reject) => {
1955
+ const response = await new Promise((resolve3, reject) => {
1722
1956
  const timeout = setTimeout(() => {
1723
1957
  this.pending.delete(id);
1724
1958
  reject(
@@ -1731,7 +1965,7 @@ var ExtensionBridge = class {
1731
1965
  );
1732
1966
  }, timeoutMs);
1733
1967
  this.pending.set(id, {
1734
- resolve: resolve2,
1968
+ resolve: resolve3,
1735
1969
  reject,
1736
1970
  timeout
1737
1971
  });
@@ -1816,6 +2050,10 @@ var ExtensionBridge = class {
1816
2050
  this.stopHeartbeat();
1817
2051
  this.connected = false;
1818
2052
  this.socket = null;
2053
+ this.version = void 0;
2054
+ this.coreHost = void 0;
2055
+ this.corePort = void 0;
2056
+ this.corePortSource = void 0;
1819
2057
  this.lastSeenAt = (/* @__PURE__ */ new Date()).toISOString();
1820
2058
  this.applyDriveDisconnected();
1821
2059
  for (const [id, pending] of this.pending.entries()) {
@@ -1863,10 +2101,25 @@ var ExtensionBridge = class {
1863
2101
  }
1864
2102
  handleEvent(message) {
1865
2103
  if (message.action === "drive.hello" || message.action === "drive.tab_report") {
1866
- const tabs = message.params?.tabs;
2104
+ const params = message.params;
2105
+ const tabs = params?.tabs;
1867
2106
  if (Array.isArray(tabs)) {
1868
2107
  this.tabs = tabs;
1869
2108
  }
2109
+ if (message.action === "drive.hello") {
2110
+ if (typeof params?.version === "string") {
2111
+ this.version = params.version;
2112
+ }
2113
+ if (typeof params?.core_host === "string") {
2114
+ this.coreHost = params.core_host;
2115
+ }
2116
+ if (typeof params?.core_port === "number" && Number.isFinite(params.core_port)) {
2117
+ this.corePort = params.core_port;
2118
+ }
2119
+ if (params?.core_port_source === "default" || params?.core_port_source === "storage") {
2120
+ this.corePortSource = params.core_port_source;
2121
+ }
2122
+ }
1870
2123
  }
1871
2124
  if (typeof message.action === "string" && message.action.startsWith("debugger.")) {
1872
2125
  this.emitDebuggerEvent(message);
@@ -3473,7 +3726,7 @@ var InspectService = class {
3473
3726
  const rootDir = await ensureArtifactRootDir(input.sessionId);
3474
3727
  const artifactId = (0, import_crypto3.randomUUID)();
3475
3728
  const filePath = import_node_path4.default.join(rootDir, `har-${artifactId}.json`);
3476
- await (0, import_promises2.writeFile)(filePath, JSON.stringify(har, null, 2), "utf-8");
3729
+ await (0, import_promises3.writeFile)(filePath, JSON.stringify(har, null, 2), "utf-8");
3477
3730
  const result = {
3478
3731
  artifact_id: artifactId,
3479
3732
  path: filePath,
@@ -3652,7 +3905,7 @@ var InspectService = class {
3652
3905
  rootDir,
3653
3906
  `screenshot-${artifactId}.${extension}`
3654
3907
  );
3655
- await (0, import_promises2.writeFile)(filePath, Buffer.from(data2, "base64"));
3908
+ await (0, import_promises3.writeFile)(filePath, Buffer.from(data2, "base64"));
3656
3909
  const mime = format === "jpeg" ? "image/jpeg" : `image/${format}`;
3657
3910
  const output = {
3658
3911
  artifact_id: artifactId,
@@ -4045,6 +4298,48 @@ var getErrorAgeMs = (timestamp) => {
4045
4298
  }
4046
4299
  return Math.max(0, Date.now() - parsed);
4047
4300
  };
4301
+ var endpointLabel = (endpoint) => {
4302
+ if (!endpoint) {
4303
+ return "unknown";
4304
+ }
4305
+ if (endpoint.baseUrl) {
4306
+ return endpoint.baseUrl;
4307
+ }
4308
+ if (endpoint.host && endpoint.port !== void 0) {
4309
+ return `${endpoint.host}:${endpoint.port}`;
4310
+ }
4311
+ return "unknown";
4312
+ };
4313
+ var hasEndpoint = (endpoint) => Boolean(
4314
+ endpoint && typeof endpoint.host === "string" && endpoint.host.length > 0 && typeof endpoint.port === "number" && Number.isFinite(endpoint.port)
4315
+ );
4316
+ var toRuntimeEndpoint = (endpoint) => {
4317
+ if (!endpoint) {
4318
+ return void 0;
4319
+ }
4320
+ return {
4321
+ host: endpoint.host,
4322
+ port: endpoint.port,
4323
+ base_url: endpoint.baseUrl,
4324
+ host_source: endpoint.hostSource,
4325
+ port_source: endpoint.portSource,
4326
+ metadata_path: endpoint.metadataPath,
4327
+ isolated_mode: endpoint.isolatedMode
4328
+ };
4329
+ };
4330
+ var toRuntimeProcess = (process2) => {
4331
+ if (!process2) {
4332
+ return void 0;
4333
+ }
4334
+ return {
4335
+ component: process2.component,
4336
+ version: process2.version,
4337
+ pid: process2.pid,
4338
+ node_version: process2.nodeVersion,
4339
+ binary_path: process2.binaryPath,
4340
+ argv_entry: process2.argvEntry
4341
+ };
4342
+ };
4048
4343
  var buildDiagnosticReport = (sessionId, context = {}) => {
4049
4344
  const extensionConnected = context.extension?.connected ?? false;
4050
4345
  const debuggerAttached = context.debugger?.attached ?? false;
@@ -4072,6 +4367,57 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
4072
4367
  }
4073
4368
  }
4074
4369
  ];
4370
+ const coreEndpoint = context.runtime?.core?.endpoint;
4371
+ const callerEndpoint = context.runtime?.caller?.endpoint;
4372
+ const extensionEndpoint = context.runtime?.extension?.endpoint;
4373
+ if (hasEndpoint(coreEndpoint) && hasEndpoint(callerEndpoint)) {
4374
+ const matches = coreEndpoint.host === callerEndpoint.host && coreEndpoint.port === callerEndpoint.port;
4375
+ checks.push({
4376
+ name: "runtime.caller.endpoint_match",
4377
+ ok: matches,
4378
+ message: matches ? `Caller endpoint matches core (${endpointLabel(coreEndpoint)}).` : `Caller endpoint ${endpointLabel(
4379
+ callerEndpoint
4380
+ )} differs from core ${endpointLabel(coreEndpoint)}.`,
4381
+ details: {
4382
+ caller_endpoint: endpointLabel(callerEndpoint),
4383
+ core_endpoint: endpointLabel(coreEndpoint),
4384
+ caller_host_source: callerEndpoint.hostSource,
4385
+ caller_port_source: callerEndpoint.portSource,
4386
+ core_host_source: coreEndpoint.hostSource,
4387
+ core_port_source: coreEndpoint.portSource
4388
+ }
4389
+ });
4390
+ }
4391
+ if (extensionConnected && hasEndpoint(coreEndpoint) && hasEndpoint(extensionEndpoint)) {
4392
+ const matches = coreEndpoint.host === extensionEndpoint.host && coreEndpoint.port === extensionEndpoint.port;
4393
+ checks.push({
4394
+ name: "runtime.extension.endpoint_match",
4395
+ ok: matches,
4396
+ message: matches ? `Extension endpoint matches core (${endpointLabel(coreEndpoint)}).` : `Extension endpoint ${endpointLabel(
4397
+ extensionEndpoint
4398
+ )} differs from core ${endpointLabel(coreEndpoint)}.`,
4399
+ details: {
4400
+ extension_endpoint: endpointLabel(extensionEndpoint),
4401
+ core_endpoint: endpointLabel(coreEndpoint),
4402
+ extension_port_source: context.runtime?.extension?.portSource,
4403
+ core_host_source: coreEndpoint.hostSource,
4404
+ core_port_source: coreEndpoint.portSource
4405
+ }
4406
+ });
4407
+ }
4408
+ const callerVersion = context.runtime?.caller?.process?.version;
4409
+ const extensionVersion = extensionConnected ? context.runtime?.extension?.version : void 0;
4410
+ if (callerVersion && extensionVersion) {
4411
+ checks.push({
4412
+ name: "runtime.extension.version_match_caller",
4413
+ ok: callerVersion === extensionVersion,
4414
+ message: callerVersion === extensionVersion ? `Caller and extension versions match (${callerVersion}).` : `Caller version ${callerVersion} differs from extension version ${extensionVersion}.`,
4415
+ details: {
4416
+ caller_version: callerVersion,
4417
+ extension_version: extensionVersion
4418
+ }
4419
+ });
4420
+ }
4075
4421
  if (context.driveLastError) {
4076
4422
  const ageMs = getErrorAgeMs(context.driveLastError.at);
4077
4423
  const isStale = ageMs !== void 0 && ageMs > STALE_ERROR_THRESHOLD_MS;
@@ -4144,6 +4490,7 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
4144
4490
  } : void 0,
4145
4491
  extension: {
4146
4492
  connected: extensionConnected,
4493
+ version: context.extension?.version,
4147
4494
  last_seen_at: context.extension?.lastSeenAt
4148
4495
  },
4149
4496
  debugger: context.debugger ? {
@@ -4168,7 +4515,22 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
4168
4515
  } : {}
4169
4516
  } : void 0,
4170
4517
  ...warnings.length > 0 ? { warnings } : {},
4171
- notes: ["Diagnostics include runtime status; some checks may be stubbed."]
4518
+ notes: ["Diagnostics include runtime status; some checks may be stubbed."],
4519
+ runtime: context.runtime ? {
4520
+ caller: context.runtime.caller ? {
4521
+ endpoint: toRuntimeEndpoint(context.runtime.caller.endpoint),
4522
+ process: toRuntimeProcess(context.runtime.caller.process)
4523
+ } : void 0,
4524
+ core: context.runtime.core ? {
4525
+ endpoint: toRuntimeEndpoint(context.runtime.core.endpoint),
4526
+ process: toRuntimeProcess(context.runtime.core.process)
4527
+ } : void 0,
4528
+ extension: context.runtime.extension ? {
4529
+ version: context.runtime.extension.version,
4530
+ endpoint: toRuntimeEndpoint(context.runtime.extension.endpoint),
4531
+ port_source: context.runtime.extension.portSource
4532
+ } : void 0
4533
+ } : void 0
4172
4534
  };
4173
4535
  return report;
4174
4536
  };
@@ -4213,30 +4575,69 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
4213
4575
  });
4214
4576
  });
4215
4577
  router.post("/diagnostics/doctor", (req, res) => {
4216
- let sessionId;
4217
- if (req.body !== void 0) {
4218
- if (!isRecord(req.body)) {
4219
- sendError(res, 400, {
4220
- code: "INVALID_ARGUMENT",
4221
- message: "Request body must be an object.",
4222
- retryable: false
4223
- });
4224
- return;
4225
- }
4226
- const raw = req.body.session_id;
4227
- if (raw !== void 0 && (typeof raw !== "string" || raw.length === 0)) {
4228
- sendError(res, 400, {
4229
- code: "INVALID_ARGUMENT",
4230
- message: "session_id must be a non-empty string.",
4231
- retryable: false,
4232
- details: { field: "session_id" }
4233
- });
4234
- return;
4235
- }
4236
- sessionId = raw;
4578
+ const body = req.body ?? {};
4579
+ if (!isRecord(body)) {
4580
+ sendError(res, 400, {
4581
+ code: "INVALID_ARGUMENT",
4582
+ message: "Request body must be an object.",
4583
+ retryable: false
4584
+ });
4585
+ return;
4237
4586
  }
4587
+ const parsedDoctorInput = DiagnosticsDoctorInputSchema.safeParse(body);
4588
+ if (!parsedDoctorInput.success) {
4589
+ const issue = parsedDoctorInput.error.issues[0];
4590
+ sendError(res, 400, {
4591
+ code: "INVALID_ARGUMENT",
4592
+ message: issue?.message ?? "Invalid diagnostics doctor request.",
4593
+ retryable: false,
4594
+ details: issue?.path.length ? { field: issue.path.map((part) => String(part)).join(".") } : void 0
4595
+ });
4596
+ return;
4597
+ }
4598
+ const sessionId = parsedDoctorInput.data.session_id;
4238
4599
  try {
4239
4600
  const context = {};
4601
+ context.runtime = {
4602
+ caller: parsedDoctorInput.data.caller ? {
4603
+ endpoint: parsedDoctorInput.data.caller.endpoint ? {
4604
+ host: parsedDoctorInput.data.caller.endpoint.host,
4605
+ port: parsedDoctorInput.data.caller.endpoint.port,
4606
+ baseUrl: parsedDoctorInput.data.caller.endpoint.base_url,
4607
+ hostSource: parsedDoctorInput.data.caller.endpoint.host_source,
4608
+ portSource: parsedDoctorInput.data.caller.endpoint.port_source,
4609
+ metadataPath: parsedDoctorInput.data.caller.endpoint.metadata_path,
4610
+ isolatedMode: parsedDoctorInput.data.caller.endpoint.isolated_mode
4611
+ } : void 0,
4612
+ process: parsedDoctorInput.data.caller.process ? {
4613
+ component: parsedDoctorInput.data.caller.process.component,
4614
+ version: parsedDoctorInput.data.caller.process.version,
4615
+ pid: parsedDoctorInput.data.caller.process.pid,
4616
+ nodeVersion: parsedDoctorInput.data.caller.process.node_version,
4617
+ binaryPath: parsedDoctorInput.data.caller.process.binary_path,
4618
+ argvEntry: parsedDoctorInput.data.caller.process.argv_entry
4619
+ } : void 0
4620
+ } : void 0,
4621
+ core: {
4622
+ endpoint: options.coreRuntime ? {
4623
+ host: options.coreRuntime.host,
4624
+ port: options.coreRuntime.port,
4625
+ baseUrl: `http://${options.coreRuntime.host}:${options.coreRuntime.port}`,
4626
+ hostSource: options.coreRuntime.hostSource,
4627
+ portSource: options.coreRuntime.portSource,
4628
+ metadataPath: options.coreRuntime.metadataPath,
4629
+ isolatedMode: options.coreRuntime.isolatedMode
4630
+ } : void 0,
4631
+ process: {
4632
+ component: "core",
4633
+ version: options.coreVersion,
4634
+ pid: process.pid,
4635
+ nodeVersion: process.version,
4636
+ binaryPath: process.execPath,
4637
+ argvEntry: process.argv[1]
4638
+ }
4639
+ }
4640
+ };
4240
4641
  if (options.registry && sessionId) {
4241
4642
  try {
4242
4643
  const session = options.registry.require(sessionId);
@@ -4268,8 +4669,20 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
4268
4669
  const status = options.extensionBridge.getStatus();
4269
4670
  context.extension = {
4270
4671
  connected: status.connected,
4672
+ version: status.version,
4271
4673
  lastSeenAt: status.lastSeenAt
4272
4674
  };
4675
+ if (status.connected) {
4676
+ context.runtime.extension = {
4677
+ version: status.version,
4678
+ endpoint: status.coreHost && typeof status.corePort === "number" ? {
4679
+ host: status.coreHost,
4680
+ port: status.corePort,
4681
+ baseUrl: `http://${status.coreHost}:${status.corePort}`
4682
+ } : void 0,
4683
+ portSource: status.corePortSource
4684
+ };
4685
+ }
4273
4686
  }
4274
4687
  if (options.debuggerBridge) {
4275
4688
  const settings = options.debuggerBridge.getSettings();
@@ -5110,7 +5523,9 @@ var createCoreServer = (options = {}) => {
5110
5523
  debuggerBridge,
5111
5524
  drive,
5112
5525
  inspectService: inspect,
5113
- recoveryTracker
5526
+ recoveryTracker,
5527
+ coreRuntime: options.runtime,
5528
+ coreVersion: options.version
5114
5529
  });
5115
5530
  return {
5116
5531
  app,
@@ -5169,7 +5584,7 @@ var resolveSessionCleanupIntervalMs = (ttlMs) => {
5169
5584
  var isAddressInUseError = (error) => Boolean(
5170
5585
  error && typeof error === "object" && "code" in error && error.code === "EADDRINUSE"
5171
5586
  );
5172
- var listenOnPort = (app, extensionBridge, host, port) => new Promise((resolve2, reject) => {
5587
+ var listenOnPort = (app, extensionBridge, host, port) => new Promise((resolve3, reject) => {
5173
5588
  const server = (0, import_http.createServer)(app);
5174
5589
  extensionBridge.attach(server);
5175
5590
  const onError = (error) => {
@@ -5178,7 +5593,7 @@ var listenOnPort = (app, extensionBridge, host, port) => new Promise((resolve2,
5178
5593
  };
5179
5594
  const onListening = () => {
5180
5595
  server.off("error", onError);
5181
- resolve2(server);
5596
+ resolve3(server);
5182
5597
  };
5183
5598
  server.once("error", onError);
5184
5599
  server.once("listening", onListening);
@@ -5218,6 +5633,7 @@ var startCoreServer = async (options = {}) => {
5218
5633
  port: options.port,
5219
5634
  strictEnvPort: false
5220
5635
  });
5636
+ const coreVersion = process.env.BROWSER_BRIDGE_VERSION ?? process.env.npm_package_version;
5221
5637
  logger.info("core.runtime.resolved", {
5222
5638
  host: runtime.host,
5223
5639
  port: runtime.port,
@@ -5230,7 +5646,9 @@ var startCoreServer = async (options = {}) => {
5230
5646
  });
5231
5647
  const { app, registry, extensionBridge } = createCoreServer({
5232
5648
  registry: options.registry,
5233
- logger
5649
+ logger,
5650
+ runtime,
5651
+ version: coreVersion
5234
5652
  });
5235
5653
  const probePorts = resolveProbePortsForRuntime(runtime);
5236
5654
  let lastAddressInUseError;
@@ -5305,36 +5723,92 @@ var startCoreServer = async (options = {}) => {
5305
5723
  };
5306
5724
 
5307
5725
  // packages/mcp-adapter/src/core-client.ts
5308
- var DEFAULT_TIMEOUT_MS = 3e4;
5726
+ var import_node_child_process = require("node:child_process");
5727
+ var import_node_path5 = require("node:path");
5728
+ var DEFAULT_TIMEOUT_MS2 = 3e4;
5309
5729
  var normalizePath = (path3) => path3.startsWith("/") ? path3 : `/${path3}`;
5310
5730
  var durationMs = (startedAt) => Number((Number(process.hrtime.bigint() - startedAt) / 1e6).toFixed(3));
5731
+ var toReadinessErrorEnvelope = (error, baseUrl) => ({
5732
+ ok: false,
5733
+ error: {
5734
+ code: "UNAVAILABLE",
5735
+ message: error instanceof Error ? `Core not ready at ${baseUrl}: ${error.message}` : `Core not ready at ${baseUrl}.`,
5736
+ retryable: true,
5737
+ details: {
5738
+ base_url: baseUrl
5739
+ }
5740
+ }
5741
+ });
5311
5742
  var createCoreClient = (options = {}) => {
5312
5743
  const logger = options.logger ?? createJsonlLogger({
5313
5744
  stream: "mcp-adapter",
5314
5745
  cwd: options.cwd
5315
5746
  }).child({ scope: "core-client" });
5316
- const runtime = resolveCoreRuntime({
5747
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
5748
+ const fetchImpl = options.fetchImpl ?? fetch;
5749
+ const spawnImpl = options.spawnImpl ?? import_node_child_process.spawn;
5750
+ const ensureDaemon = options.ensureDaemon ?? false;
5751
+ const componentVersion = options.componentVersion ?? process.env.BROWSER_BRIDGE_VERSION ?? process.env.npm_package_version;
5752
+ const readiness = createCoreReadinessController({
5317
5753
  host: options.host,
5318
5754
  port: options.port,
5319
5755
  cwd: options.cwd,
5320
- strictEnvPort: true
5756
+ timeoutMs,
5757
+ ensureDaemon,
5758
+ strictEnvPort: true,
5759
+ fetchImpl,
5760
+ logger,
5761
+ logPrefix: "mcp.core",
5762
+ healthRetryMs: options.healthRetryMs,
5763
+ healthAttempts: options.healthAttempts,
5764
+ spawnDaemon: ensureDaemon ? (runtime) => {
5765
+ const coreEntry = (0, import_node_path5.resolve)(__dirname, "api.js");
5766
+ const startOptions = [];
5767
+ if (runtime.hostSource === "option" || runtime.hostSource === "env") {
5768
+ startOptions.push(`host: ${JSON.stringify(runtime.host)}`);
5769
+ }
5770
+ if (runtime.portSource === "option" || runtime.portSource === "env") {
5771
+ startOptions.push(`port: ${runtime.port}`);
5772
+ }
5773
+ const script = `const { startCoreServer } = require(${JSON.stringify(
5774
+ coreEntry
5775
+ )});
5776
+ startCoreServer({ ${startOptions.join(
5777
+ ", "
5778
+ )} })
5779
+ .catch((err) => { console.error(err); process.exit(1); });`;
5780
+ logger.info("mcp.core.spawn.start", {
5781
+ host: runtime.host,
5782
+ port: runtime.port,
5783
+ host_source: runtime.hostSource,
5784
+ port_source: runtime.portSource
5785
+ });
5786
+ const child = spawnImpl(process.execPath, ["-e", script], {
5787
+ detached: true,
5788
+ stdio: "ignore",
5789
+ env: { ...process.env }
5790
+ });
5791
+ child.on("error", (error) => {
5792
+ logger.error("mcp.core.spawn.error", {
5793
+ host: runtime.host,
5794
+ port: runtime.port,
5795
+ error
5796
+ });
5797
+ });
5798
+ child.unref();
5799
+ } : void 0
5321
5800
  });
5322
- const host = runtime.host;
5323
- const port = runtime.port;
5324
- const baseUrl = `http://${host}:${port}`;
5325
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
5326
- const fetchImpl = options.fetchImpl ?? fetch;
5327
5801
  const requestJson = async (path3, body) => {
5328
5802
  const requestPath = normalizePath(path3);
5329
5803
  const startedAt = process.hrtime.bigint();
5330
5804
  logger.debug("mcp.core.request.start", {
5331
5805
  path: requestPath,
5332
- base_url: baseUrl
5806
+ base_url: readiness.baseUrl
5333
5807
  });
5334
5808
  const controller = new AbortController();
5335
5809
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
5336
5810
  try {
5337
- const response = await fetchImpl(`${baseUrl}${requestPath}`, {
5811
+ const response = await fetchImpl(`${readiness.baseUrl}${requestPath}`, {
5338
5812
  method: "POST",
5339
5813
  headers: {
5340
5814
  "content-type": "application/json"
@@ -5346,7 +5820,7 @@ var createCoreClient = (options = {}) => {
5346
5820
  if (!raw) {
5347
5821
  logger.warn("mcp.core.request.empty_response", {
5348
5822
  path: requestPath,
5349
- base_url: baseUrl,
5823
+ base_url: readiness.baseUrl,
5350
5824
  status: response.status,
5351
5825
  duration_ms: durationMs(startedAt)
5352
5826
  });
@@ -5356,7 +5830,7 @@ var createCoreClient = (options = {}) => {
5356
5830
  const parsed = JSON.parse(raw);
5357
5831
  logger.debug("mcp.core.request.end", {
5358
5832
  path: requestPath,
5359
- base_url: baseUrl,
5833
+ base_url: readiness.baseUrl,
5360
5834
  status: response.status,
5361
5835
  duration_ms: durationMs(startedAt)
5362
5836
  });
@@ -5365,7 +5839,7 @@ var createCoreClient = (options = {}) => {
5365
5839
  const message = error instanceof Error ? error.message : "Unknown JSON parse error";
5366
5840
  logger.error("mcp.core.request.invalid_json", {
5367
5841
  path: requestPath,
5368
- base_url: baseUrl,
5842
+ base_url: readiness.baseUrl,
5369
5843
  status: response.status,
5370
5844
  duration_ms: durationMs(startedAt),
5371
5845
  error
@@ -5375,7 +5849,7 @@ var createCoreClient = (options = {}) => {
5375
5849
  } catch (error) {
5376
5850
  logger.error("mcp.core.request.failed", {
5377
5851
  path: requestPath,
5378
- base_url: baseUrl,
5852
+ base_url: readiness.baseUrl,
5379
5853
  duration_ms: durationMs(startedAt),
5380
5854
  error
5381
5855
  });
@@ -5385,9 +5859,47 @@ var createCoreClient = (options = {}) => {
5385
5859
  }
5386
5860
  };
5387
5861
  const post = async (path3, body) => {
5388
- return requestJson(path3, body);
5862
+ try {
5863
+ await readiness.ensureReady();
5864
+ } catch (error) {
5865
+ logger.warn("mcp.core.ensure_ready.unavailable", {
5866
+ base_url: readiness.baseUrl,
5867
+ error
5868
+ });
5869
+ throw toReadinessErrorEnvelope(error, readiness.baseUrl);
5870
+ }
5871
+ readiness.refreshRuntime();
5872
+ const payload = path3 === "/diagnostics/doctor" && (!body || typeof body === "object" && !Array.isArray(body)) ? {
5873
+ ...body && typeof body === "object" ? body : {},
5874
+ caller: {
5875
+ endpoint: {
5876
+ host: readiness.runtime.host,
5877
+ port: readiness.runtime.port,
5878
+ base_url: readiness.baseUrl,
5879
+ host_source: readiness.runtime.hostSource,
5880
+ port_source: readiness.runtime.portSource,
5881
+ metadata_path: readiness.runtime.metadataPath,
5882
+ isolated_mode: readiness.runtime.isolatedMode
5883
+ },
5884
+ process: {
5885
+ component: "mcp",
5886
+ version: componentVersion,
5887
+ pid: process.pid,
5888
+ node_version: process.version,
5889
+ binary_path: process.execPath,
5890
+ argv_entry: process.argv[1]
5891
+ }
5892
+ }
5893
+ } : body;
5894
+ return requestJson(path3, payload);
5895
+ };
5896
+ return {
5897
+ get baseUrl() {
5898
+ return readiness.baseUrl;
5899
+ },
5900
+ ensureReady: readiness.ensureReady,
5901
+ post
5389
5902
  };
5390
- return { baseUrl, post };
5391
5903
  };
5392
5904
 
5393
5905
  // packages/mcp-adapter/src/tools.ts
@@ -5784,10 +6296,11 @@ var TOOL_DEFINITIONS = [
5784
6296
  }
5785
6297
  }
5786
6298
  ];
5787
- var createToolHandler = (client, corePath) => {
6299
+ var createToolHandler = (clientProvider, corePath) => {
5788
6300
  return (async (args, _extra) => {
5789
6301
  void _extra;
5790
6302
  try {
6303
+ const client = typeof clientProvider === "function" ? await clientProvider() : clientProvider;
5791
6304
  const envelopeResult = await client.post(corePath, args);
5792
6305
  return toToolResult(envelopeResult);
5793
6306
  } catch (error) {
@@ -5799,7 +6312,7 @@ var createToolHandler = (client, corePath) => {
5799
6312
  }
5800
6313
  });
5801
6314
  };
5802
- var registerBrowserBridgeTools = (server, client) => {
6315
+ var registerBrowserBridgeTools = (server, clientProvider) => {
5803
6316
  for (const tool of TOOL_DEFINITIONS) {
5804
6317
  server.registerTool(
5805
6318
  tool.name,
@@ -5809,7 +6322,7 @@ var registerBrowserBridgeTools = (server, client) => {
5809
6322
  inputSchema: tool.config.inputSchema,
5810
6323
  outputSchema: tool.config.outputSchema
5811
6324
  },
5812
- createToolHandler(client, tool.config.corePath)
6325
+ createToolHandler(clientProvider, tool.config.corePath)
5813
6326
  );
5814
6327
  }
5815
6328
  };
@@ -5819,53 +6332,290 @@ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
5819
6332
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
5820
6333
  var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
5821
6334
  var import_types = require("@modelcontextprotocol/sdk/types.js");
6335
+
6336
+ // packages/mcp-adapter/src/deferred-logger.ts
6337
+ var LOG_LEVEL_PRIORITY2 = {
6338
+ debug: 10,
6339
+ info: 20,
6340
+ warn: 30,
6341
+ error: 40
6342
+ };
6343
+ var isEnabled = (state, level) => LOG_LEVEL_PRIORITY2[level] >= LOG_LEVEL_PRIORITY2[state.level];
6344
+ var enqueue = (state, entry) => {
6345
+ if (state.buffered.length >= state.maxBufferEntries) {
6346
+ state.buffered.shift();
6347
+ state.droppedEntries += 1;
6348
+ }
6349
+ state.buffered.push(entry);
6350
+ };
6351
+ var flushBuffered = (state) => {
6352
+ const destination = state.destination;
6353
+ if (!destination) {
6354
+ return;
6355
+ }
6356
+ if (state.droppedEntries > 0) {
6357
+ destination.warn("mcp.log.buffer.dropped", {
6358
+ dropped_entries: state.droppedEntries
6359
+ });
6360
+ state.droppedEntries = 0;
6361
+ }
6362
+ for (const entry of state.buffered) {
6363
+ destination.log(entry.level, entry.event, {
6364
+ ...entry.bindings,
6365
+ ...entry.fields
6366
+ });
6367
+ }
6368
+ state.buffered.length = 0;
6369
+ };
6370
+ var buildLogger2 = (state, bindings) => {
6371
+ const log = (level, event, fields = {}) => {
6372
+ if (!isEnabled(state, level)) {
6373
+ return;
6374
+ }
6375
+ if (state.destination) {
6376
+ state.destination.log(level, event, {
6377
+ ...bindings,
6378
+ ...fields
6379
+ });
6380
+ return;
6381
+ }
6382
+ enqueue(state, {
6383
+ level,
6384
+ event,
6385
+ fields: { ...fields },
6386
+ bindings: { ...bindings }
6387
+ });
6388
+ };
6389
+ return {
6390
+ stream: state.stream,
6391
+ get level() {
6392
+ return state.destination?.level ?? state.level;
6393
+ },
6394
+ get logDir() {
6395
+ return state.destination?.logDir ?? "";
6396
+ },
6397
+ get filePath() {
6398
+ return state.destination?.filePath ?? "";
6399
+ },
6400
+ child: (childBindings) => buildLogger2(state, {
6401
+ ...bindings,
6402
+ ...childBindings
6403
+ }),
6404
+ log,
6405
+ debug: (event, fields) => log("debug", event, fields),
6406
+ info: (event, fields) => log("info", event, fields),
6407
+ warn: (event, fields) => log("warn", event, fields),
6408
+ error: (event, fields) => log("error", event, fields)
6409
+ };
6410
+ };
6411
+ var createDeferredJsonlLogger = (options) => {
6412
+ const state = {
6413
+ stream: options.stream,
6414
+ level: options.level ?? "debug",
6415
+ destination: null,
6416
+ buffered: [],
6417
+ droppedEntries: 0,
6418
+ maxBufferEntries: Math.max(1, options.maxBufferEntries ?? 2e3)
6419
+ };
6420
+ return {
6421
+ logger: buildLogger2(state, options.bindings ?? {}),
6422
+ activate: () => {
6423
+ if (!state.destination) {
6424
+ state.destination = createJsonlLogger(options);
6425
+ }
6426
+ flushBuffered(state);
6427
+ return state.destination;
6428
+ },
6429
+ isActivated: () => state.destination !== null
6430
+ };
6431
+ };
6432
+
6433
+ // packages/mcp-adapter/src/server.ts
5822
6434
  var DEFAULT_SERVER_NAME = "browser-bridge";
5823
6435
  var DEFAULT_SERVER_VERSION = "0.0.0";
5824
- var resolveAdapterLogger = (options) => options.logger ?? createJsonlLogger({
5825
- stream: "mcp-adapter",
5826
- cwd: options.cwd
6436
+ var ENV_MCP_EAGER = "BROWSER_BRIDGE_MCP_EAGER";
6437
+ var ENV_LEGACY_MCP_EAGER = "BROWSER_VISION_MCP_EAGER";
6438
+ var parseBoolean2 = (value) => {
6439
+ if (value === void 0) {
6440
+ return void 0;
6441
+ }
6442
+ const normalized = value.trim().toLowerCase();
6443
+ if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
6444
+ return true;
6445
+ }
6446
+ if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
6447
+ return false;
6448
+ }
6449
+ return void 0;
6450
+ };
6451
+ var resolveEagerMode = (explicit) => {
6452
+ if (typeof explicit === "boolean") {
6453
+ return explicit;
6454
+ }
6455
+ const envValue = parseBoolean2(process.env[ENV_MCP_EAGER]) ?? parseBoolean2(process.env[ENV_LEGACY_MCP_EAGER]);
6456
+ return envValue ?? false;
6457
+ };
6458
+ var toCoreClientOptions = (options, logger) => ({
6459
+ host: options.host,
6460
+ port: options.port,
6461
+ cwd: options.cwd,
6462
+ timeoutMs: options.timeoutMs,
6463
+ ensureDaemon: options.ensureDaemon ?? true,
6464
+ componentVersion: options.version ?? DEFAULT_SERVER_VERSION,
6465
+ healthRetryMs: options.healthRetryMs,
6466
+ healthAttempts: options.healthAttempts,
6467
+ fetchImpl: options.fetchImpl,
6468
+ spawnImpl: options.spawnImpl,
6469
+ logger
5827
6470
  });
5828
- var createMcpServer = (options = {}) => {
5829
- const logger = resolveAdapterLogger(options);
6471
+ var buildInitializationError = (error) => {
6472
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown initialization failure.";
6473
+ return {
6474
+ ok: false,
6475
+ error: {
6476
+ code: "UNAVAILABLE",
6477
+ message: `MCP runtime initialization failed: ${message}`,
6478
+ retryable: true,
6479
+ details: {
6480
+ phase: "mcp_runtime_init"
6481
+ }
6482
+ }
6483
+ };
6484
+ };
6485
+ var resolveDeferredLoggerController = (options) => {
6486
+ if (options.logger) {
6487
+ return {
6488
+ logger: options.logger,
6489
+ activate: () => void 0
6490
+ };
6491
+ }
6492
+ const deferred = createDeferredJsonlLogger({
6493
+ stream: "mcp-adapter",
6494
+ cwd: options.cwd
6495
+ });
6496
+ return {
6497
+ logger: deferred.logger,
6498
+ activate: () => {
6499
+ deferred.activate();
6500
+ }
6501
+ };
6502
+ };
6503
+ var createRuntimeController = (options) => {
6504
+ const deferredLogger = resolveDeferredLoggerController(options);
6505
+ const logger = deferredLogger.logger;
6506
+ const coreLogger = logger.child({ scope: "core-client" });
6507
+ let initializedClient = null;
6508
+ let initializationPromise = null;
6509
+ const createClient = async () => {
6510
+ if (options.coreClient) {
6511
+ return options.coreClient;
6512
+ }
6513
+ if (options.coreClientFactory) {
6514
+ return await options.coreClientFactory(coreLogger);
6515
+ }
6516
+ return createCoreClient(toCoreClientOptions(options, coreLogger));
6517
+ };
6518
+ const proxyClient = {
6519
+ get baseUrl() {
6520
+ return initializedClient?.baseUrl ?? "";
6521
+ },
6522
+ ensureReady: async () => {
6523
+ await ensureInitialized();
6524
+ },
6525
+ post: async (path3, body) => {
6526
+ const client = await ensureInitialized();
6527
+ return client.post(path3, body);
6528
+ }
6529
+ };
6530
+ const ensureInitialized = async () => {
6531
+ if (initializedClient) {
6532
+ return initializedClient;
6533
+ }
6534
+ if (!initializationPromise) {
6535
+ initializationPromise = (async () => {
6536
+ logger.info("mcp.runtime.init.begin");
6537
+ try {
6538
+ const candidate = await createClient();
6539
+ const maybeEnsureReady = candidate.ensureReady;
6540
+ if (typeof maybeEnsureReady === "function") {
6541
+ await maybeEnsureReady.call(candidate);
6542
+ }
6543
+ deferredLogger.activate();
6544
+ initializedClient = candidate;
6545
+ logger.info("mcp.runtime.init.ready", {
6546
+ core_base_url: candidate.baseUrl
6547
+ });
6548
+ return candidate;
6549
+ } catch (error) {
6550
+ logger.error("mcp.runtime.init.failed", {
6551
+ error
6552
+ });
6553
+ throw buildInitializationError(error);
6554
+ }
6555
+ })();
6556
+ initializationPromise = initializationPromise.catch((error) => {
6557
+ initializationPromise = null;
6558
+ throw error;
6559
+ });
6560
+ }
6561
+ return initializationPromise;
6562
+ };
6563
+ return {
6564
+ logger,
6565
+ client: options.coreClient ?? proxyClient,
6566
+ ensureInitialized,
6567
+ isInitialized: () => initializedClient !== null
6568
+ };
6569
+ };
6570
+ var createMcpServerBootstrap = (options = {}) => {
6571
+ const runtime = createRuntimeController(options);
5830
6572
  const server = new import_mcp.McpServer({
5831
6573
  name: options.name ?? DEFAULT_SERVER_NAME,
5832
6574
  version: options.version ?? DEFAULT_SERVER_VERSION
5833
6575
  });
5834
- const client = options.coreClient ?? createCoreClient({
5835
- ...options,
5836
- logger: logger.child({ scope: "core-client" })
5837
- });
5838
- registerBrowserBridgeTools(server, client);
5839
- logger.info("mcp.server.created", {
6576
+ registerBrowserBridgeTools(server, runtime.ensureInitialized);
6577
+ runtime.logger.info("mcp.server.created", {
5840
6578
  name: options.name ?? DEFAULT_SERVER_NAME,
5841
6579
  version: options.version ?? DEFAULT_SERVER_VERSION,
5842
- core_base_url: client.baseUrl
6580
+ lazy_init: true
5843
6581
  });
5844
- return { server, client };
6582
+ return {
6583
+ server,
6584
+ client: runtime.client,
6585
+ logger: runtime.logger,
6586
+ ensureInitialized: runtime.ensureInitialized,
6587
+ isInitialized: runtime.isInitialized
6588
+ };
5845
6589
  };
5846
6590
  var startMcpServer = async (options = {}) => {
5847
- const logger = resolveAdapterLogger(options);
5848
- logger.info("mcp.stdio.start.begin", {
6591
+ const eager = resolveEagerMode(options.eager);
6592
+ const handle = createMcpServerBootstrap(options);
6593
+ handle.logger.info("mcp.stdio.start.begin", {
5849
6594
  name: options.name ?? DEFAULT_SERVER_NAME,
5850
- version: options.version ?? DEFAULT_SERVER_VERSION
5851
- });
5852
- const handle = createMcpServer({
5853
- ...options,
5854
- logger
6595
+ version: options.version ?? DEFAULT_SERVER_VERSION,
6596
+ eager
5855
6597
  });
5856
6598
  const transport = new import_stdio.StdioServerTransport();
5857
6599
  try {
6600
+ if (eager) {
6601
+ await handle.ensureInitialized();
6602
+ }
5858
6603
  await handle.server.connect(transport);
5859
- logger.info("mcp.stdio.start.ready", {
5860
- core_base_url: handle.client.baseUrl
6604
+ handle.logger.info("mcp.stdio.start.ready", {
6605
+ core_base_url: handle.isInitialized() ? handle.client.baseUrl : null,
6606
+ eager
5861
6607
  });
5862
6608
  } catch (error) {
5863
- logger.error("mcp.stdio.start.failed", {
6609
+ handle.logger.error("mcp.stdio.start.failed", {
5864
6610
  error
5865
6611
  });
5866
6612
  throw error;
5867
6613
  }
5868
- return { ...handle, transport };
6614
+ return {
6615
+ server: handle.server,
6616
+ client: handle.client,
6617
+ transport
6618
+ };
5869
6619
  };
5870
6620
  // Annotate the CommonJS export names for ESM import in node:
5871
6621
  0 && (module.exports = {