hypha-rpc 0.21.46 → 0.21.47

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.
@@ -86,7 +86,7 @@
86
86
  <div class='footer quiet pad2 space-top1 center small'>
87
87
  Code coverage generated by
88
88
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
89
- at 2026-07-10T22:55:30.240Z
89
+ at 2026-08-31T13:24:20.113Z
90
90
  </div>
91
91
  <script src="prettify.js"></script>
92
92
  <script>
@@ -10425,6 +10425,16 @@ __webpack_require__.r(__webpack_exports__);
10425
10425
 
10426
10426
  const MAX_RETRY = 1000000;
10427
10427
 
10428
+ // Reconnect flap control: a connection that stays up for less than
10429
+ // RECONNECT_FLAP_WINDOW_MS after connecting is treated as a "flap" (e.g. the
10430
+ // server accepts the reconnect then immediately supersedes/closes it because a
10431
+ // prior session with the same client_id is still active). Flaps escalate the
10432
+ // backoff across reconnect cycles — instead of resetting to an immediate retry
10433
+ // each time — and never reconnect faster than RECONNECT_MIN_DELAY_MS while
10434
+ // flapping. This prevents a tight succeed→superseded→succeed reconnect storm.
10435
+ const RECONNECT_FLAP_WINDOW_MS = 5000;
10436
+ const RECONNECT_MIN_DELAY_MS = 1000;
10437
+
10428
10438
  // When the socket errors/closes during the handshake, wait briefly before
10429
10439
  // rejecting with the generic reason: the server usually sends a descriptive
10430
10440
  // {type:"error"} message (e.g. token/workspace mismatch) just before closing,
@@ -10482,6 +10492,8 @@ class WebsocketRPCConnection {
10482
10492
  this._reconnecting = false; // Mutex to prevent overlapping reconnection attempts
10483
10493
  this._closedDuringReconnect = false; // Flag for close events during reconnection
10484
10494
  this._disconnectedNotified = false;
10495
+ this._lastConnectedTime = 0; // ms timestamp of last successful connection_info
10496
+ this._reconnectFlapCount = 0; // consecutive short-lived (flapping) connections
10485
10497
  }
10486
10498
 
10487
10499
  /**
@@ -10634,6 +10646,9 @@ class WebsocketRPCConnection {
10634
10646
  const first_message = JSON.parse(data);
10635
10647
  if (first_message.type == "connection_info") {
10636
10648
  settled = true;
10649
+ // Mark when a connection is fully established; used for flap
10650
+ // detection when the connection later closes.
10651
+ this._lastConnectedTime = Date.now();
10637
10652
  this.connection_info = first_message;
10638
10653
  if (this._workspace) {
10639
10654
  (0,_utils_index_js__WEBPACK_IMPORTED_MODULE_2__.assert)(
@@ -10841,7 +10856,25 @@ class WebsocketRPCConnection {
10841
10856
  }
10842
10857
  this._reconnecting = true;
10843
10858
 
10844
- let retry = 0;
10859
+ // Flap detection: if the connection that just closed was short-lived,
10860
+ // escalate the backoff across reconnect cycles instead of resetting to
10861
+ // an immediate retry. A healthy long-lived connection resets the count
10862
+ // so genuine disconnects still recover fast.
10863
+ const aliveMs = this._lastConnectedTime
10864
+ ? Date.now() - this._lastConnectedTime
10865
+ : Infinity;
10866
+ if (aliveMs < RECONNECT_FLAP_WINDOW_MS) {
10867
+ this._reconnectFlapCount += 1;
10868
+ } else {
10869
+ this._reconnectFlapCount = 0;
10870
+ }
10871
+
10872
+ // Seed the retry counter from the flap history so a REPEATED flap
10873
+ // escalates the backoff. The first flap is free (retry 0 → immediate)
10874
+ // so a genuine single quick disconnect still recovers fast; only the
10875
+ // 2nd+ consecutive short-lived connection is delayed, which is the
10876
+ // succeed→superseded storm signature.
10877
+ let retry = Math.max(0, this._reconnectFlapCount - 1);
10845
10878
  const baseDelay = 1000; // Start with 1 second
10846
10879
  const maxDelay = 60000; // Maximum delay of 60 seconds
10847
10880
  const maxJitter = 0.1; // Maximum jitter factor
@@ -10981,7 +11014,35 @@ class WebsocketRPCConnection {
10981
11014
  this._reconnect_timeouts.add(timeoutId);
10982
11015
  }
10983
11016
  };
10984
- reconnect();
11017
+
11018
+ // Kick off the reconnect loop. When flapping (retry seeded > 0), delay
11019
+ // the first attempt with the same exponential backoff + jitter and a
11020
+ // minimum floor, so a succeed→superseded loop cannot hammer the server.
11021
+ // A genuine disconnect after a healthy connection (retry === 0) still
11022
+ // reconnects immediately.
11023
+ if (retry > 0) {
11024
+ const delay = Math.min(baseDelay * Math.pow(2, retry - 1), maxDelay);
11025
+ const jitter = (Math.random() * 2 - 1) * maxJitter * delay;
11026
+ const finalDelay = Math.max(RECONNECT_MIN_DELAY_MS, delay + jitter);
11027
+ const timeoutId = setTimeout(() => {
11028
+ this._reconnect_timeouts.delete(timeoutId);
11029
+ if (this._closed) {
11030
+ this._reconnecting = false;
11031
+ return;
11032
+ }
11033
+ if (
11034
+ this._websocket &&
11035
+ this._websocket.readyState === WebSocket.OPEN
11036
+ ) {
11037
+ this._reconnecting = false;
11038
+ return;
11039
+ }
11040
+ reconnect();
11041
+ }, finalDelay);
11042
+ this._reconnect_timeouts.add(timeoutId);
11043
+ } else {
11044
+ reconnect();
11045
+ }
10985
11046
  }
10986
11047
  } else {
10987
11048
  // Clean up timers in all cases