meshy-node 0.4.1 → 0.4.3

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.
@@ -1,4 +1,4 @@
1
- import{c as s,r as t,j as a,b as n}from"./index-6tOxJBZf.js";/**
1
+ import{c as s,r as t,j as a,b as n}from"./index-Cba_59ol.js";/**
2
2
  * @license lucide-react v1.7.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Meshy Dashboard</title>
7
7
  <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>&#x1F578;</text></svg>" />
8
- <script type="module" crossorigin src="/assets/index-6tOxJBZf.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-Ch6zIZ6Y.css">
8
+ <script type="module" crossorigin src="/assets/index-Cba_59ol.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-BzzXPy7Y.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
package/main.cjs CHANGED
@@ -33089,6 +33089,22 @@ function parseNodeSettingsSnapshot(value) {
33089
33089
  }
33090
33090
  return clone(value);
33091
33091
  }
33092
+ function parseDevTunnelHealth(value) {
33093
+ if (!isPlainObject(value)) {
33094
+ return void 0;
33095
+ }
33096
+ if (value.status !== "healthy" && value.status !== "failed" || typeof value.checkedAt !== "number" || !Number.isFinite(value.checkedAt)) {
33097
+ return void 0;
33098
+ }
33099
+ return {
33100
+ status: value.status,
33101
+ checkedAt: value.checkedAt,
33102
+ endpoint: typeof value.endpoint === "string" ? value.endpoint : void 0,
33103
+ reason: typeof value.reason === "string" ? value.reason : void 0,
33104
+ statusCode: typeof value.statusCode === "number" && Number.isFinite(value.statusCode) ? value.statusCode : void 0,
33105
+ consecutiveFailures: typeof value.consecutiveFailures === "number" && Number.isFinite(value.consecutiveFailures) ? value.consecutiveFailures : void 0
33106
+ };
33107
+ }
33092
33108
  function isClusterInfo(value) {
33093
33109
  return isPlainObject(value) && typeof value.clusterId === "string" && typeof value.createdAt === "number" && Number.isFinite(value.createdAt);
33094
33110
  }
@@ -33112,6 +33128,7 @@ function parseNodeInfo(value) {
33112
33128
  lastHeartbeat: value.lastHeartbeat,
33113
33129
  transportType: typeof value.transportType === "string" ? value.transportType : void 0,
33114
33130
  devtunnelEndpoint: typeof value.devtunnelEndpoint === "string" ? value.devtunnelEndpoint : void 0,
33131
+ devtunnelHealth: parseDevTunnelHealth(value.devtunnelHealth),
33115
33132
  dashboardOrigin: typeof value.dashboardOrigin === "string" ? value.dashboardOrigin : void 0,
33116
33133
  workDir: typeof value.workDir === "string" ? value.workDir : void 0,
33117
33134
  workDirFolders: parseStringArray(value.workDirFolders),
@@ -33515,6 +33532,24 @@ var NodeRegistry = class {
33515
33532
  }
33516
33533
  }
33517
33534
  }
33535
+ updateDevTunnelHealth(id, devtunnelHealth) {
33536
+ const node = this.nodes.get(id);
33537
+ if (node) {
33538
+ if (devtunnelHealth) {
33539
+ node.devtunnelHealth = { ...devtunnelHealth };
33540
+ } else {
33541
+ delete node.devtunnelHealth;
33542
+ }
33543
+ this.store.upsertNode(node);
33544
+ }
33545
+ if (this.selfNode && this.selfNode.id === id) {
33546
+ if (devtunnelHealth) {
33547
+ this.selfNode.devtunnelHealth = { ...devtunnelHealth };
33548
+ } else {
33549
+ delete this.selfNode.devtunnelHealth;
33550
+ }
33551
+ }
33552
+ }
33518
33553
  updateDashboardOrigin(id, dashboardOrigin) {
33519
33554
  const node = this.nodes.get(id);
33520
33555
  if (node) {
@@ -34689,17 +34724,9 @@ var HeartbeatMonitor = class {
34689
34724
  running = false;
34690
34725
  followerMissedMap = /* @__PURE__ */ new Map();
34691
34726
  followerPushReachableMap = /* @__PURE__ */ new Map();
34692
- /**
34693
- * Tracks whether a push-heartbeat has EVER succeeded for a follower.
34694
- * If push has never worked, the follower is on an unreachable network
34695
- * and should not be marked offline based on push failures alone.
34696
- */
34727
+ // Tracks whether push-heartbeat has ever succeeded for a follower.
34697
34728
  followerEverReachedMap = /* @__PURE__ */ new Map();
34698
- /**
34699
- * Last known leader endpoint — preserved across clearLeader() so
34700
- * follower keepalives can still attempt to reach the leader even
34701
- * after the follower timer fires.
34702
- */
34729
+ // Preserved across clearLeader() so follower keepalives can still reach the leader.
34703
34730
  lastKnownLeaderEndpoint = null;
34704
34731
  getQueuedMessages;
34705
34732
  messageQueue;
@@ -34856,6 +34883,7 @@ var HeartbeatMonitor = class {
34856
34883
  });
34857
34884
  }
34858
34885
  this.applyReportedDevTunnelState(node.id, result.devtunnelEnabled, result.devtunnelEndpoint);
34886
+ this.nodeRegistry.updateDevTunnelHealth(node.id, result.devtunnelEnabled === false ? void 0 : result.devtunnelHealth);
34859
34887
  this.nodeRegistry.updateDashboardOrigin(node.id, result.dashboardOrigin);
34860
34888
  if (result.workDirFolders) {
34861
34889
  this.nodeRegistry.updateFolders(node.id, result.workDirFolders);
@@ -34922,6 +34950,7 @@ var HeartbeatMonitor = class {
34922
34950
  supportedAgents: self2.supportedAgents,
34923
34951
  devtunnelEnabled: self2.devtunnelEndpoint !== void 0,
34924
34952
  devtunnelEndpoint: self2.devtunnelEndpoint,
34953
+ devtunnelHealth: self2.devtunnelHealth,
34925
34954
  dashboardOrigin: self2.dashboardOrigin,
34926
34955
  workDirFolders: self2.workDir ? listWorkDirFolders(self2.workDir) : void 0,
34927
34956
  settingsSnapshot: this.resolveCurrentSettingsSnapshot()
@@ -34942,6 +34971,7 @@ var HeartbeatMonitor = class {
34942
34971
  workDir,
34943
34972
  devtunnelEnabled,
34944
34973
  devtunnelEndpoint,
34974
+ devtunnelHealth,
34945
34975
  dashboardOrigin,
34946
34976
  workDirFolders,
34947
34977
  settingsSnapshot
@@ -34969,6 +34999,7 @@ var HeartbeatMonitor = class {
34969
34999
  ...transportType ? { transportType } : {},
34970
35000
  ...workDir ? { workDir } : {},
34971
35001
  ...devtunnelEnabled && devtunnelEndpoint ? { devtunnelEndpoint } : {},
35002
+ ...devtunnelHealth ? { devtunnelHealth } : {},
34972
35003
  ...dashboardOrigin ? { dashboardOrigin } : {},
34973
35004
  ...workDirFolders ? { workDirFolders } : {},
34974
35005
  ...settingsSnapshot ? { settingsSnapshot } : {}
@@ -35004,6 +35035,7 @@ var HeartbeatMonitor = class {
35004
35035
  }
35005
35036
  this.nodeRegistry.updateLoad(nodeId, load);
35006
35037
  this.applyReportedDevTunnelState(nodeId, devtunnelEnabled, devtunnelEndpoint);
35038
+ this.nodeRegistry.updateDevTunnelHealth(nodeId, devtunnelEnabled === false ? void 0 : devtunnelHealth);
35007
35039
  this.nodeRegistry.updateDashboardOrigin(nodeId, dashboardOrigin);
35008
35040
  if (workDirFolders) {
35009
35041
  this.nodeRegistry.updateFolders(nodeId, workDirFolders);
@@ -35053,6 +35085,7 @@ var HeartbeatMonitor = class {
35053
35085
  workDir: self2.workDir,
35054
35086
  devtunnelEnabled: self2.devtunnelEndpoint !== void 0,
35055
35087
  devtunnelEndpoint: self2.devtunnelEndpoint,
35088
+ devtunnelHealth: self2.devtunnelHealth,
35056
35089
  dashboardOrigin: self2.dashboardOrigin,
35057
35090
  workDirFolders: self2.workDir ? listWorkDirFolders(self2.workDir) : void 0,
35058
35091
  settingsSnapshot
@@ -37760,6 +37793,9 @@ ${joinErrors.map((e) => ` - ${e}`).join("\n")}`
37760
37793
  if (self2.devtunnelEndpoint) {
37761
37794
  joinBody.devtunnelEndpoint = self2.devtunnelEndpoint;
37762
37795
  }
37796
+ if (self2.devtunnelHealth) {
37797
+ joinBody.devtunnelHealth = self2.devtunnelHealth;
37798
+ }
37763
37799
  if (self2.dashboardOrigin) {
37764
37800
  joinBody.dashboardOrigin = self2.dashboardOrigin;
37765
37801
  }
@@ -38093,6 +38129,7 @@ ${joinErrors.map((e) => ` - ${e}`).join("\n")}`
38093
38129
  workDir: currentSelf.workDir,
38094
38130
  workDirFolders: listWorkDirFolders(this.getWorkDir()),
38095
38131
  ...currentSelf.devtunnelEndpoint ? { devtunnelEndpoint: currentSelf.devtunnelEndpoint } : {},
38132
+ ...currentSelf.devtunnelHealth ? { devtunnelHealth: currentSelf.devtunnelHealth } : {},
38096
38133
  ...currentSelf.dashboardOrigin ? { dashboardOrigin: currentSelf.dashboardOrigin } : {},
38097
38134
  ...currentSelf.settingsSnapshot ? { settingsSnapshot: currentSelf.settingsSnapshot } : {}
38098
38135
  };
@@ -42945,6 +42982,14 @@ var NodeSettingsSnapshotSchema = external_exports.object({
42945
42982
  api: SystemPackageInfoSchema.optional()
42946
42983
  }).optional()
42947
42984
  });
42985
+ var DevTunnelHealthSchema = external_exports.object({
42986
+ status: external_exports.enum(["healthy", "failed"]),
42987
+ checkedAt: external_exports.number(),
42988
+ endpoint: external_exports.string().optional(),
42989
+ reason: external_exports.string().optional(),
42990
+ statusCode: external_exports.number().optional(),
42991
+ consecutiveFailures: external_exports.number().optional()
42992
+ });
42948
42993
 
42949
42994
  // ../../packages/api/src/schemas/cluster.ts
42950
42995
  var NodeInfoSchema = external_exports.object({
@@ -42959,6 +43004,7 @@ var NodeInfoSchema = external_exports.object({
42959
43004
  lastHeartbeat: external_exports.number(),
42960
43005
  transportType: external_exports.string().optional(),
42961
43006
  devtunnelEndpoint: external_exports.string().optional(),
43007
+ devtunnelHealth: DevTunnelHealthSchema.optional(),
42962
43008
  dashboardOrigin: external_exports.string().optional(),
42963
43009
  workDir: external_exports.string().optional(),
42964
43010
  workDirFolders: external_exports.array(external_exports.string()).optional(),
@@ -42995,6 +43041,7 @@ var JoinClusterBody = external_exports.object({
42995
43041
  workDir: external_exports.string().optional(),
42996
43042
  workDirFolders: external_exports.array(external_exports.string()).optional(),
42997
43043
  devtunnelEndpoint: external_exports.string().url().optional(),
43044
+ devtunnelHealth: DevTunnelHealthSchema.optional(),
42998
43045
  dashboardOrigin: external_exports.string().url().optional(),
42999
43046
  settingsSnapshot: NodeSettingsSnapshotSchema.optional(),
43000
43047
  tasks: external_exports.array(JoinTaskSchema).default([])
@@ -43039,6 +43086,7 @@ var NodeInfoSchema2 = external_exports.object({
43039
43086
  lastHeartbeat: external_exports.number(),
43040
43087
  transportType: external_exports.string().optional(),
43041
43088
  devtunnelEndpoint: external_exports.string().optional(),
43089
+ devtunnelHealth: DevTunnelHealthSchema.optional(),
43042
43090
  dashboardOrigin: external_exports.string().optional(),
43043
43091
  workDir: external_exports.string().optional(),
43044
43092
  workDirFolders: external_exports.array(external_exports.string()).optional(),
@@ -50729,6 +50777,82 @@ function buildRuntimeMetadata(storagePath) {
50729
50777
  };
50730
50778
  }
50731
50779
 
50780
+ // src/bootstrap/tunnel-health.ts
50781
+ var DEFAULT_TUNNEL_REACHABILITY_INTERVAL_MS = 6e4;
50782
+ var DEFAULT_TUNNEL_REACHABILITY_TIMEOUT_MS = 5e3;
50783
+ var DEFAULT_TUNNEL_RESTART_FAILURE_THRESHOLD = 2;
50784
+ function createTunnelReachabilityMonitor(options = {}) {
50785
+ const failureThreshold = options.failureThreshold ?? DEFAULT_TUNNEL_RESTART_FAILURE_THRESHOLD;
50786
+ const fetchImpl = options.fetchImpl ?? fetch;
50787
+ const healthPath = options.healthPath ?? "/api/system/health";
50788
+ const intervalMs = options.intervalMs ?? DEFAULT_TUNNEL_REACHABILITY_INTERVAL_MS;
50789
+ const now = options.now ?? (() => Date.now());
50790
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TUNNEL_REACHABILITY_TIMEOUT_MS;
50791
+ let consecutiveFailures = 0;
50792
+ let lastCheckedAt = null;
50793
+ function recordFailure(reason, statusCode) {
50794
+ consecutiveFailures += 1;
50795
+ return {
50796
+ available: false,
50797
+ checked: true,
50798
+ consecutiveFailures,
50799
+ reason,
50800
+ shouldRestart: consecutiveFailures >= failureThreshold,
50801
+ ...statusCode !== void 0 ? { statusCode } : {}
50802
+ };
50803
+ }
50804
+ return {
50805
+ async check(endpoint) {
50806
+ const currentTime = now();
50807
+ if (lastCheckedAt !== null && currentTime - lastCheckedAt < intervalMs) {
50808
+ return {
50809
+ available: true,
50810
+ checked: false,
50811
+ consecutiveFailures,
50812
+ shouldRestart: false
50813
+ };
50814
+ }
50815
+ lastCheckedAt = currentTime;
50816
+ if (!endpoint) {
50817
+ return recordFailure("missing endpoint");
50818
+ }
50819
+ try {
50820
+ const response = await fetchWithTimeout2(fetchImpl, buildHealthUrl(endpoint, healthPath), timeoutMs);
50821
+ if (!response.ok) {
50822
+ return recordFailure(`health check returned ${response.status}`, response.status);
50823
+ }
50824
+ consecutiveFailures = 0;
50825
+ return {
50826
+ available: true,
50827
+ checked: true,
50828
+ consecutiveFailures,
50829
+ shouldRestart: false,
50830
+ statusCode: response.status
50831
+ };
50832
+ } catch (err) {
50833
+ return recordFailure(err instanceof Error ? err.message : String(err));
50834
+ }
50835
+ },
50836
+ reset() {
50837
+ consecutiveFailures = 0;
50838
+ lastCheckedAt = null;
50839
+ }
50840
+ };
50841
+ }
50842
+ function buildHealthUrl(endpoint, healthPath) {
50843
+ return new URL(healthPath, endpoint).toString();
50844
+ }
50845
+ async function fetchWithTimeout2(fetchImpl, url, timeoutMs) {
50846
+ const controller = new AbortController();
50847
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
50848
+ timer.unref?.();
50849
+ try {
50850
+ return await fetchImpl(url, { method: "GET", signal: controller.signal });
50851
+ } finally {
50852
+ clearTimeout(timer);
50853
+ }
50854
+ }
50855
+
50732
50856
  // src/bootstrap/start-node.ts
50733
50857
  function createSettingsSnapshotProvider(options) {
50734
50858
  let cachedSnapshot = null;
@@ -50818,6 +50942,9 @@ function createTunnelManager(options) {
50818
50942
  let shareTransport = null;
50819
50943
  let dashboardHealthTimer = null;
50820
50944
  let nodeTunnelHealthTimer = null;
50945
+ let dashboardHealthCheckRunning = false;
50946
+ let nodeTunnelHealthCheckRunning = false;
50947
+ const nodeTunnelReachability = createTunnelReachabilityMonitor();
50821
50948
  function createDashboardTransportConfig() {
50822
50949
  return {
50823
50950
  type: "devtunnel",
@@ -50857,6 +50984,28 @@ function createTunnelManager(options) {
50857
50984
  function shouldPublishDashboardTunnel() {
50858
50985
  return authMetadata.enabled && (meshyNode.getTransportType() === "devtunnel" || meshyNode.isDevTunnelEnabled());
50859
50986
  }
50987
+ function getPublishedNodeTunnelEndpoint() {
50988
+ const self2 = meshyNode.getNodeRegistry().getSelf();
50989
+ return meshyNode.getTransportType() === "devtunnel" ? self2.endpoint : self2.devtunnelEndpoint;
50990
+ }
50991
+ function setPublishedNodeTunnelHealth(health) {
50992
+ const selfId = meshyNode.getNodeRegistry().getSelf().id;
50993
+ meshyNode.getNodeRegistry().updateDevTunnelHealth(selfId, health);
50994
+ }
50995
+ function markPublishedNodeTunnelHealthy(endpoint) {
50996
+ setPublishedNodeTunnelHealth({
50997
+ status: "healthy",
50998
+ checkedAt: Date.now(),
50999
+ ...endpoint ? { endpoint } : {}
51000
+ });
51001
+ }
51002
+ function markPublishedNodeTunnelFailed(details) {
51003
+ setPublishedNodeTunnelHealth({
51004
+ status: "failed",
51005
+ checkedAt: Date.now(),
51006
+ ...details
51007
+ });
51008
+ }
50860
51009
  async function stopDashboardTransport(reason) {
50861
51010
  if (!dashboardTransport) {
50862
51011
  if (dashboardOrigin) {
@@ -50960,31 +51109,100 @@ function createTunnelManager(options) {
50960
51109
  if (!nodeTunnelHealthTimer) {
50961
51110
  nodeTunnelHealthTimer = setInterval(() => {
50962
51111
  void (async () => {
50963
- if (!meshyNode.hasPublishedNodeTunnel()) {
51112
+ if (nodeTunnelHealthCheckRunning) {
50964
51113
  return;
50965
51114
  }
50966
- const healthy = await meshyNode.isPublishedNodeTunnelHealthy().catch(() => false);
50967
- if (healthy) {
50968
- return;
50969
- }
50970
- meshyNode.getLogger().warn("published node tunnel became unhealthy; restarting with stable id", {
50971
- mainTransportType: meshyNode.getTransportType(),
50972
- sidecarEnabled: meshyNode.isDevTunnelEnabled()
50973
- });
51115
+ nodeTunnelHealthCheckRunning = true;
50974
51116
  try {
50975
- const endpoint = await meshyNode.restartPublishedNodeTunnel();
50976
- meshyNode.getLogger().info("restarted published node tunnel", {
50977
- endpoint,
50978
- stableUrl: true,
50979
- mainTransportType: meshyNode.getTransportType(),
50980
- sidecarEnabled: meshyNode.isDevTunnelEnabled()
51117
+ if (!meshyNode.hasPublishedNodeTunnel()) {
51118
+ nodeTunnelReachability.reset();
51119
+ setPublishedNodeTunnelHealth(void 0);
51120
+ return;
51121
+ }
51122
+ const publishedEndpoint = getPublishedNodeTunnelEndpoint();
51123
+ const healthy = await meshyNode.isPublishedNodeTunnelHealthy().catch(() => false);
51124
+ if (!healthy) {
51125
+ markPublishedNodeTunnelFailed({
51126
+ endpoint: publishedEndpoint,
51127
+ reason: "devtunnel process is not running"
51128
+ });
51129
+ meshyNode.getLogger().warn("published node tunnel process became unhealthy; restarting with stable id", {
51130
+ mainTransportType: meshyNode.getTransportType(),
51131
+ sidecarEnabled: meshyNode.isDevTunnelEnabled()
51132
+ });
51133
+ try {
51134
+ const endpoint = await meshyNode.restartPublishedNodeTunnel();
51135
+ nodeTunnelReachability.reset();
51136
+ markPublishedNodeTunnelHealthy(endpoint);
51137
+ meshyNode.getLogger().info("restarted published node tunnel", {
51138
+ endpoint,
51139
+ stableUrl: true,
51140
+ mainTransportType: meshyNode.getTransportType(),
51141
+ sidecarEnabled: meshyNode.isDevTunnelEnabled()
51142
+ });
51143
+ } catch (err) {
51144
+ markPublishedNodeTunnelFailed({
51145
+ endpoint: publishedEndpoint,
51146
+ reason: err instanceof Error ? err.message : String(err)
51147
+ });
51148
+ meshyNode.getLogger().warn("failed to restart published node tunnel", {
51149
+ error: err instanceof Error ? err.message : String(err),
51150
+ mainTransportType: meshyNode.getTransportType(),
51151
+ sidecarEnabled: meshyNode.isDevTunnelEnabled()
51152
+ });
51153
+ }
51154
+ return;
51155
+ }
51156
+ const reachability = await nodeTunnelReachability.check(publishedEndpoint);
51157
+ if (!reachability.checked) {
51158
+ return;
51159
+ }
51160
+ if (reachability.available) {
51161
+ markPublishedNodeTunnelHealthy(publishedEndpoint);
51162
+ return;
51163
+ }
51164
+ markPublishedNodeTunnelFailed({
51165
+ consecutiveFailures: reachability.consecutiveFailures,
51166
+ endpoint: publishedEndpoint,
51167
+ reason: reachability.reason,
51168
+ statusCode: reachability.statusCode
50981
51169
  });
50982
- } catch (err) {
50983
- meshyNode.getLogger().warn("failed to restart published node tunnel", {
50984
- error: err instanceof Error ? err.message : String(err),
51170
+ meshyNode.getLogger().warn("published node tunnel endpoint health check failed", {
51171
+ consecutiveFailures: reachability.consecutiveFailures,
51172
+ failureThresholdReached: reachability.shouldRestart,
50985
51173
  mainTransportType: meshyNode.getTransportType(),
50986
- sidecarEnabled: meshyNode.isDevTunnelEnabled()
51174
+ reason: reachability.reason,
51175
+ sidecarEnabled: meshyNode.isDevTunnelEnabled(),
51176
+ statusCode: reachability.statusCode
50987
51177
  });
51178
+ if (!reachability.shouldRestart) {
51179
+ return;
51180
+ }
51181
+ try {
51182
+ const endpoint = await meshyNode.restartPublishedNodeTunnel();
51183
+ nodeTunnelReachability.reset();
51184
+ markPublishedNodeTunnelHealthy(endpoint);
51185
+ meshyNode.getLogger().info("restarted published node tunnel", {
51186
+ endpoint,
51187
+ stableUrl: true,
51188
+ mainTransportType: meshyNode.getTransportType(),
51189
+ sidecarEnabled: meshyNode.isDevTunnelEnabled()
51190
+ });
51191
+ } catch (err) {
51192
+ markPublishedNodeTunnelFailed({
51193
+ consecutiveFailures: reachability.consecutiveFailures,
51194
+ endpoint: publishedEndpoint,
51195
+ reason: err instanceof Error ? err.message : String(err),
51196
+ statusCode: reachability.statusCode
51197
+ });
51198
+ meshyNode.getLogger().warn("failed to restart published node tunnel", {
51199
+ error: err instanceof Error ? err.message : String(err),
51200
+ mainTransportType: meshyNode.getTransportType(),
51201
+ sidecarEnabled: meshyNode.isDevTunnelEnabled()
51202
+ });
51203
+ }
51204
+ } finally {
51205
+ nodeTunnelHealthCheckRunning = false;
50988
51206
  }
50989
51207
  })();
50990
51208
  }, config.cluster.heartbeatInterval);
@@ -50992,17 +51210,24 @@ function createTunnelManager(options) {
50992
51210
  if (!dashboardHealthTimer) {
50993
51211
  dashboardHealthTimer = setInterval(() => {
50994
51212
  void (async () => {
50995
- if (!dashboardTransport) {
51213
+ if (dashboardHealthCheckRunning) {
50996
51214
  return;
50997
51215
  }
50998
- const healthy = await dashboardTransport.isHealthy().catch(() => false);
50999
- if (healthy) {
51000
- return;
51216
+ dashboardHealthCheckRunning = true;
51217
+ try {
51218
+ if (!dashboardTransport) {
51219
+ return;
51220
+ }
51221
+ const healthy = await dashboardTransport.isHealthy().catch(() => false);
51222
+ if (!healthy) {
51223
+ meshyNode.getLogger().warn("dashboard tunnel process became unhealthy; restarting with stable id", {
51224
+ port: config.node.port
51225
+ });
51226
+ await restartDashboardTransport("healthcheck");
51227
+ }
51228
+ } finally {
51229
+ dashboardHealthCheckRunning = false;
51001
51230
  }
51002
- meshyNode.getLogger().warn("dashboard tunnel became unhealthy; restarting with stable id", {
51003
- port: config.node.port
51004
- });
51005
- await restartDashboardTransport("healthcheck");
51006
51231
  })();
51007
51232
  }, config.cluster.heartbeatInterval);
51008
51233
  }
@@ -51016,6 +51241,7 @@ function createTunnelManager(options) {
51016
51241
  clearInterval(nodeTunnelHealthTimer);
51017
51242
  nodeTunnelHealthTimer = null;
51018
51243
  }
51244
+ nodeTunnelReachability.reset();
51019
51245
  }
51020
51246
  async function stop() {
51021
51247
  stopHealthChecks();
@@ -51168,6 +51394,9 @@ async function startNode(args) {
51168
51394
  switchTransport: async (type) => {
51169
51395
  const endpoint = await meshyNode.switchTransport(type);
51170
51396
  persistNodeStartupTransport(config.storage.path, type);
51397
+ if (type !== "devtunnel" && !meshyNode.isDevTunnelEnabled()) {
51398
+ meshyNode.getNodeRegistry().updateDevTunnelHealth(meshyNode.getNodeRegistry().getSelf().id, void 0);
51399
+ }
51171
51400
  await tunnelManager.syncDashboardTransport(`switchTransport:${type}`);
51172
51401
  return endpoint;
51173
51402
  },
@@ -51180,6 +51409,9 @@ async function startNode(args) {
51180
51409
  },
51181
51410
  disableDevTunnel: async () => {
51182
51411
  await meshyNode.disableDevTunnel();
51412
+ if (meshyNode.getTransportType() !== "devtunnel") {
51413
+ meshyNode.getNodeRegistry().updateDevTunnelHealth(meshyNode.getNodeRegistry().getSelf().id, void 0);
51414
+ }
51183
51415
  persistNodeStartupTransport(
51184
51416
  config.storage.path,
51185
51417
  meshyNode.getTransportType()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meshy-node",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "private": false,
5
5
  "description": "Standalone Meshy node package with bundled runtime and dashboard assets.",
6
6
  "type": "commonjs",
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "packageName": "meshy-node",
3
- "packageVersion": "0.4.1",
3
+ "packageVersion": "0.4.3",
4
4
  "packages": {
5
5
  "workspace": {
6
6
  "name": "meshy",
7
- "version": "0.4.1"
7
+ "version": "0.4.3"
8
8
  },
9
9
  "node": {
10
10
  "name": "meshy-node",
11
- "version": "0.4.1"
11
+ "version": "0.4.3"
12
12
  },
13
13
  "core": {
14
14
  "name": "@meshy/core",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "repository": {
27
27
  "url": "https://github.com/ai-microsoft/meshy",
28
- "branch": "release0_4_1",
29
- "commit": "554b574"
28
+ "branch": "TunnelRestart",
29
+ "commit": "f34e076"
30
30
  }
31
31
  }