@vibedeckx/linux-x64 0.3.33 → 0.3.34

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 (2) hide show
  1. package/dist/bin.js +139 -51
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -240344,7 +240344,7 @@ ${opts.reviewFocus}` : null,
240344
240344
  ## Scope \u2014 the change under review
240345
240345
 
240346
240346
  The reviewed turn changed exactly these files:
240347
- ${scope.changedFiles.map((f2) => `- ${f2}`).join("\n")}
240347
+ ${scope.changedFiles.map((f2) => `- \`${f2}\``).join("\n")}
240348
240348
 
240349
240349
  It starts from commit \`${scope.startHead}\` \u2014 use \`git diff ${scope.startHead} -- <file>\` and \`git log ${scope.startHead}..HEAD\` to see the content.
240350
240350
 
@@ -263527,6 +263527,10 @@ var MACHINE_KEY_SETTING = "reverse_machine_private_key";
263527
263527
  var RECONNECT_BASE_DELAY_MS = 1e3;
263528
263528
  var RECONNECT_MAX_DELAY_MS = 3e4;
263529
263529
  var NO_PING_TIMEOUT_MS = 4e4;
263530
+ var HANDSHAKE_TIMEOUT_MS = 2e4;
263531
+ var SUPERVISOR_INTERVAL_MS = 1e4;
263532
+ var DIAL_TIMEOUT_MS = 25e3;
263533
+ var CLOSING_TIMEOUT_MS = 1e4;
263530
263534
  var AUTH_REJECT_CODES = /* @__PURE__ */ new Set([4001, 4003]);
263531
263535
  var HEALTHY_CONNECTION_MS = 5e3;
263532
263536
  function isTextualContentType(contentType2) {
@@ -263544,7 +263548,13 @@ var ReverseConnectClient = class {
263544
263548
  reconnectAttempt = 0;
263545
263549
  openedAt = null;
263546
263550
  reconnectTimer = null;
263547
- noPingTimer = null;
263551
+ supervisorTimer = null;
263552
+ /** When the current dial started — the CONNECTING watchdog's reference point. */
263553
+ dialStartedAt = 0;
263554
+ /** Last time the hub said anything on the control socket (a superset of pings). */
263555
+ lastFrameAt = 0;
263556
+ /** First tick that observed CLOSING, so a stalled teardown can be forced. */
263557
+ closingSince = 0;
263548
263558
  shuttingDown = false;
263549
263559
  constructor(localServer, serverUrl, token, localPort) {
263550
263560
  this.localServer = localServer;
@@ -263554,27 +263564,42 @@ var ReverseConnectClient = class {
263554
263564
  }
263555
263565
  connect() {
263556
263566
  if (this.shuttingDown) return;
263567
+ this.startSupervisor();
263568
+ if (this.ws) {
263569
+ console.warn("[ReverseClient] connect() called with a live socket \u2014 discarding the old one");
263570
+ const stale = this.ws;
263571
+ this.ws = null;
263572
+ try {
263573
+ stale.terminate();
263574
+ } catch {
263575
+ }
263576
+ }
263557
263577
  const cleanUrl = this.serverUrl.replace(/\/+$/, "");
263558
263578
  const wsProtocol = cleanUrl.startsWith("https") ? "wss" : "ws";
263559
263579
  const wsUrl = cleanUrl.replace(/^https?/, wsProtocol);
263560
263580
  const connectUrl = `${wsUrl}/api/reverse-connect?token=${encodeURIComponent(this.token)}`;
263561
263581
  console.log(`[ReverseClient] Connecting to ${cleanUrl}...`);
263562
- this.ws = new wrapper_default(connectUrl, {
263563
- maxPayload: 11 * 1024 * 1024
263564
- });
263565
- this.ws.on("open", () => {
263582
+ this.dialStartedAt = Date.now();
263583
+ this.closingSince = 0;
263584
+ const ws = new wrapper_default(connectUrl, {
263585
+ maxPayload: 11 * 1024 * 1024,
263586
+ handshakeTimeout: HANDSHAKE_TIMEOUT_MS
263587
+ });
263588
+ this.ws = ws;
263589
+ ws.on("open", () => {
263566
263590
  console.log("[ReverseClient] Socket open, awaiting server handshake");
263567
263591
  this.openedAt = Date.now();
263568
- this.resetNoPingTimer();
263592
+ this.lastFrameAt = Date.now();
263569
263593
  const frame = {
263570
263594
  type: "status",
263571
263595
  ready: true,
263572
263596
  version: readPackageVersion(),
263573
263597
  capabilities: WORKER_CAPABILITY_KEYS
263574
263598
  };
263575
- this.ws.send(JSON.stringify(frame));
263599
+ ws.send(JSON.stringify(frame));
263576
263600
  });
263577
- this.ws.on("message", (data) => {
263601
+ ws.on("message", (data) => {
263602
+ this.lastFrameAt = Date.now();
263578
263603
  try {
263579
263604
  const frame = JSON.parse(data.toString());
263580
263605
  this.handleFrame(frame);
@@ -263582,48 +263607,127 @@ var ReverseConnectClient = class {
263582
263607
  console.error("[ReverseClient] Failed to parse frame:", err);
263583
263608
  }
263584
263609
  });
263585
- this.ws.on("close", (code, reason) => {
263610
+ ws.on("close", (code, reason) => {
263586
263611
  const safeReason = redactSecretForms(reason?.toString() || "", this.token);
263587
- const rejected = AUTH_REJECT_CODES.has(code);
263588
- const uptime = this.openedAt === null ? 0 : Date.now() - this.openedAt;
263589
- if (rejected) {
263590
- console.error(
263591
- `[ReverseClient] Server rejected this connection (code=${code}, reason=${safeReason}). ` + (code === 4001 ? "The connect token is no longer valid \u2014 open Settings \u2192 Remote Servers, read the current token, and re-run `vibedeckx connect` with it." : "This machine's identity was refused \u2014 the remote record may belong to another machine or another account.") + " Retrying with backoff, but it will not recover on its own."
263592
- );
263593
- } else {
263594
- console.log(`[ReverseClient] Disconnected (code=${code}, reason=${safeReason})`);
263595
- }
263596
- if (!rejected && this.openedAt !== null && uptime >= HEALTHY_CONNECTION_MS) {
263597
- this.reconnectAttempt = 0;
263598
- }
263599
- this.openedAt = null;
263600
- this.clearNoPingTimer();
263601
- this.closeAllLocalChannels();
263602
- void this.localServer.remoteMcpSessionManager?.closeAll("reverse-connect disconnected");
263603
- this.ws = null;
263604
- if (!this.shuttingDown) {
263605
- this.scheduleReconnect();
263606
- }
263612
+ this.handleDisconnect(ws, `code=${code}, reason=${safeReason}`, code);
263607
263613
  });
263608
- this.ws.on("error", (err) => {
263614
+ ws.on("error", (err) => {
263615
+ if (ws !== this.ws) return;
263609
263616
  console.error(
263610
263617
  "[ReverseClient] WebSocket error:",
263611
263618
  redactErrorSecret(err, this.token)
263612
263619
  );
263613
263620
  });
263614
263621
  }
263622
+ /**
263623
+ * The single "this connection is over" funnel. Reached from the close event,
263624
+ * from the supervisor declaring a socket dead, or from both in that order —
263625
+ * so it keys on socket identity and schedules at most one reconnect per
263626
+ * connection.
263627
+ */
263628
+ handleDisconnect(ws, reason, code) {
263629
+ if (ws !== null && ws !== this.ws) return;
263630
+ const rejected = code !== void 0 && AUTH_REJECT_CODES.has(code);
263631
+ const uptime = this.openedAt === null ? 0 : Date.now() - this.openedAt;
263632
+ if (rejected) {
263633
+ console.error(
263634
+ `[ReverseClient] Server rejected this connection (${reason}). ` + (code === 4001 ? "The connect token is no longer valid \u2014 open Settings \u2192 Remote Servers, read the current token, and re-run `vibedeckx connect` with it." : "This machine's identity was refused \u2014 the remote record may belong to another machine or another account.") + " Retrying with backoff, but it will not recover on its own."
263635
+ );
263636
+ } else {
263637
+ console.log(`[ReverseClient] Disconnected (${reason})`);
263638
+ }
263639
+ if (!rejected && this.openedAt !== null && uptime >= HEALTHY_CONNECTION_MS) {
263640
+ this.reconnectAttempt = 0;
263641
+ }
263642
+ this.openedAt = null;
263643
+ this.closingSince = 0;
263644
+ this.closeAllLocalChannels();
263645
+ void this.localServer.remoteMcpSessionManager?.closeAll("reverse-connect disconnected");
263646
+ this.ws = null;
263647
+ if (ws) {
263648
+ try {
263649
+ ws.terminate();
263650
+ } catch {
263651
+ }
263652
+ }
263653
+ if (this.shuttingDown) return;
263654
+ if (this.reconnectTimer) return;
263655
+ this.scheduleReconnect();
263656
+ }
263657
+ startSupervisor() {
263658
+ if (this.supervisorTimer) return;
263659
+ this.supervisorTimer = setInterval(() => this.superviseTick(), SUPERVISOR_INTERVAL_MS);
263660
+ this.supervisorTimer.unref?.();
263661
+ }
263662
+ stopSupervisor() {
263663
+ if (!this.supervisorTimer) return;
263664
+ clearInterval(this.supervisorTimer);
263665
+ this.supervisorTimer = null;
263666
+ }
263667
+ /**
263668
+ * Level-triggered recovery: judges the current state against the wall clock
263669
+ * rather than waiting to be armed by an event. That is what survives a dial
263670
+ * that never completes, a close frame the peer never answers, and a host that
263671
+ * was suspended for hours.
263672
+ */
263673
+ superviseTick() {
263674
+ if (this.shuttingDown) return;
263675
+ const now3 = Date.now();
263676
+ const ws = this.ws;
263677
+ if (ws === null) {
263678
+ if (!this.reconnectTimer) {
263679
+ console.warn("[ReverseClient] Supervisor: no socket and no pending reconnect \u2014 self-healing");
263680
+ this.scheduleReconnect();
263681
+ }
263682
+ return;
263683
+ }
263684
+ switch (ws.readyState) {
263685
+ case wrapper_default.CONNECTING: {
263686
+ const stuckFor = now3 - this.dialStartedAt;
263687
+ if (stuckFor > DIAL_TIMEOUT_MS) {
263688
+ console.warn(
263689
+ `[ReverseClient] Supervisor: dial stuck ${Math.round(stuckFor / 1e3)}s with no handshake \u2014 terminating`
263690
+ );
263691
+ this.handleDisconnect(ws, "dial timeout");
263692
+ }
263693
+ break;
263694
+ }
263695
+ case wrapper_default.OPEN: {
263696
+ const silentFor = now3 - this.lastFrameAt;
263697
+ if (silentFor > NO_PING_TIMEOUT_MS) {
263698
+ console.warn(
263699
+ `[ReverseClient] Supervisor: no hub traffic for ${Math.round(silentFor / 1e3)}s \u2014 terminating`
263700
+ );
263701
+ this.handleDisconnect(ws, "no ping timeout");
263702
+ }
263703
+ break;
263704
+ }
263705
+ case wrapper_default.CLOSING: {
263706
+ if (this.closingSince === 0) {
263707
+ this.closingSince = now3;
263708
+ } else if (now3 - this.closingSince > CLOSING_TIMEOUT_MS) {
263709
+ console.warn("[ReverseClient] Supervisor: close handshake stalled \u2014 terminating");
263710
+ this.handleDisconnect(ws, "closing stalled");
263711
+ }
263712
+ break;
263713
+ }
263714
+ default:
263715
+ break;
263716
+ }
263717
+ }
263615
263718
  shutdown() {
263616
263719
  this.shuttingDown = true;
263617
- this.clearNoPingTimer();
263720
+ this.stopSupervisor();
263618
263721
  if (this.reconnectTimer) {
263619
263722
  clearTimeout(this.reconnectTimer);
263620
263723
  this.reconnectTimer = null;
263621
263724
  }
263622
263725
  this.closeAllLocalChannels();
263623
- if (this.ws && (this.ws.readyState === wrapper_default.OPEN || this.ws.readyState === wrapper_default.CONNECTING)) {
263624
- this.ws.close(1e3, "Shutdown");
263625
- }
263726
+ const ws = this.ws;
263626
263727
  this.ws = null;
263728
+ if (ws && (ws.readyState === wrapper_default.OPEN || ws.readyState === wrapper_default.CONNECTING)) {
263729
+ ws.close(1e3, "Shutdown");
263730
+ }
263627
263731
  }
263628
263732
  async handleFrame(frame) {
263629
263733
  switch (frame.type) {
@@ -263796,7 +263900,6 @@ var ReverseConnectClient = class {
263796
263900
  }
263797
263901
  }
263798
263902
  handlePing(frame) {
263799
- this.resetNoPingTimer();
263800
263903
  const pong = { type: "pong", ts: frame.ts };
263801
263904
  this.sendFrame(pong);
263802
263905
  }
@@ -263819,21 +263922,6 @@ var ReverseConnectClient = class {
263819
263922
  this.connect();
263820
263923
  }, totalDelay);
263821
263924
  }
263822
- resetNoPingTimer() {
263823
- this.clearNoPingTimer();
263824
- this.noPingTimer = setTimeout(() => {
263825
- console.log(`[ReverseClient] No ping received in ${NO_PING_TIMEOUT_MS / 1e3}s, reconnecting...`);
263826
- if (this.ws) {
263827
- this.ws.close(1e3, "No ping timeout");
263828
- }
263829
- }, NO_PING_TIMEOUT_MS);
263830
- }
263831
- clearNoPingTimer() {
263832
- if (this.noPingTimer) {
263833
- clearTimeout(this.noPingTimer);
263834
- this.noPingTimer = null;
263835
- }
263836
- }
263837
263925
  closeAllLocalChannels() {
263838
263926
  for (const [id, ws] of this.localChannels) {
263839
263927
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibedeckx/linux-x64",
3
- "version": "0.3.33",
3
+ "version": "0.3.34",
4
4
  "description": "Vibedeckx platform binaries for Linux x64",
5
5
  "os": [
6
6
  "linux"