@agentvault/agentvault 0.23.1 → 0.23.2

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/index.js CHANGED
@@ -64745,7 +64745,7 @@ var init_channel = __esm({
64745
64745
  */
64746
64746
  sendActivitySpan(spanData) {
64747
64747
  if (!this._ws || this._ws.readyState !== WebSocket.OPEN) return;
64748
- const pluginVersion = true ? "0.23.1" : "0.0.0-dev";
64748
+ const pluginVersion = true ? "0.23.2" : "0.0.0-dev";
64749
64749
  const agentName = this.config.agentName ?? "Agent";
64750
64750
  const resource = {
64751
64751
  "service.name": "agentvault-agent",
@@ -66362,7 +66362,7 @@ var init_channel = __esm({
66362
66362
  agentVersion: this.config.agentVersion ?? "0.0.0",
66363
66363
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
66364
66364
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
66365
- pluginVersion: true ? "0.23.1" : "0.0.0-dev"
66365
+ pluginVersion: true ? "0.23.2" : "0.0.0-dev"
66366
66366
  });
66367
66367
  this._telemetryReporter.startAutoFlush(3e4);
66368
66368
  }
@@ -66438,7 +66438,7 @@ var init_channel = __esm({
66438
66438
  return;
66439
66439
  }
66440
66440
  if (data.event === "device_revoked") {
66441
- this._handleDeviceRevoked();
66441
+ await this._handleDeviceRevoked();
66442
66442
  return;
66443
66443
  }
66444
66444
  if (data.event === "device_linked") {
@@ -66672,7 +66672,7 @@ var init_channel = __esm({
66672
66672
  agentVersion: this.config.agentVersion ?? "0.0.0",
66673
66673
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
66674
66674
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
66675
- pluginVersion: true ? "0.23.1" : "0.0.0-dev"
66675
+ pluginVersion: true ? "0.23.2" : "0.0.0-dev"
66676
66676
  });
66677
66677
  this._telemetryReporter.startAutoFlush(3e4);
66678
66678
  }
@@ -69837,22 +69837,74 @@ ${messageText}`;
69837
69837
  this._setState("error");
69838
69838
  this.emit("error", err);
69839
69839
  }
69840
+ // Phase 2 — rolling-window timestamps of device_revoked self-heal reconnects,
69841
+ // used as a war cap so a takeover war or a server status/WS inconsistency can't
69842
+ // thrash forever.
69843
+ _revokeRecoveries = [];
69844
+ static REVOKE_RECOVERY_MAX = 3;
69845
+ static REVOKE_RECOVERY_WINDOW_MS = 12e4;
69840
69846
  /**
69841
- * Handle a WS `device_revoked` event. Surfaces a terminal error so
69842
- * attachLifecycle stops the channel — but does NOT delete credentials.
69847
+ * Handle a WS `device_revoked` event.
69843
69848
  *
69844
- * This signal fires both for a genuine owner revoke AND for a transient
69845
- * session-takeover of a device that is still ACTIVE on the server (the error
69846
- * text even reads "or another session has taken over"). Deleting
69847
- * agentvault.json here as this path used to via clearState() strands a
69848
- * still-valid identity: the primary creds vanish, only the .bak survives, and
69849
- * the bridge's token-gate then demands a fresh invite token to restart.
69850
- * Keeping the creds means a restart reconnects via restoreState() when the
69851
- * device is still valid, and fails cleanly (device_revoked again, no data
69852
- * lost) when it isn't. Re-enrollment, which SHOULD wipe creds, goes through
69853
- * the explicit setup --force / _forceReEnroll path in start().
69849
+ * The signal is ambiguous: the server sends it both for a genuine owner revoke
69850
+ * AND for a transient/racy session-takeover of a device that is still ACTIVE
69851
+ * (the error text even reads "or another session has taken over" — loopita's
69852
+ * case, where the device stayed ACTIVE on the server). So we do NOT terminate
69853
+ * blindly. We ask the PUBLIC /devices/{id}/status endpoint (no JWT needed, so
69854
+ * it answers even when our device JWT is being rejected):
69855
+ * - ACTIVE -> reconnect in place (transient); stay online.
69856
+ * - non-ACTIVE -> terminal (genuinely revoked; operator must re-enroll).
69857
+ * - unreachable/429 -> reconnect (inconclusive; a network blip shouldn't
69858
+ * permanently kill a healthy agent the WS is down too,
69859
+ * so normal backoff handles it, and the war cap bounds
69860
+ * thrash if the device really is dead).
69861
+ *
69862
+ * A rolling-window cap (REVOKE_RECOVERY_MAX self-heals per WINDOW_MS) gives up
69863
+ * (terminal) on a repeating loop. Never clears credentials in any branch —
69864
+ * re-enrollment, which SHOULD wipe creds, goes through setup --force /
69865
+ * _forceReEnroll in start().
69854
69866
  */
69855
- _handleDeviceRevoked() {
69867
+ async _handleDeviceRevoked() {
69868
+ const now = Date.now();
69869
+ this._revokeRecoveries = this._revokeRecoveries.filter(
69870
+ (t2) => now - t2 < _SecureChannel.REVOKE_RECOVERY_WINDOW_MS
69871
+ );
69872
+ if (this._revokeRecoveries.length >= _SecureChannel.REVOKE_RECOVERY_MAX) {
69873
+ console.warn(
69874
+ "[SecureChannel] device_revoked self-healed too many times in a short window \u2014 giving up (terminal)"
69875
+ );
69876
+ this._handleError(new Error("Device was revoked"));
69877
+ return;
69878
+ }
69879
+ const deviceId = this._deviceId ?? this._persisted?.deviceId;
69880
+ if (!deviceId) {
69881
+ this._handleError(new Error("Device was revoked"));
69882
+ return;
69883
+ }
69884
+ let reconnect;
69885
+ try {
69886
+ const status = await pollDeviceStatus(this.config.apiUrl, deviceId);
69887
+ reconnect = status.rateLimited === true || status.status === "ACTIVE";
69888
+ if (!reconnect) {
69889
+ console.warn(
69890
+ `[SecureChannel] device_revoked confirmed by server (status=${status.status}) \u2014 terminal`
69891
+ );
69892
+ }
69893
+ } catch (err) {
69894
+ console.warn(
69895
+ "[SecureChannel] device_revoked status check failed; reconnecting (inconclusive):",
69896
+ err
69897
+ );
69898
+ reconnect = true;
69899
+ }
69900
+ if (reconnect) {
69901
+ this._revokeRecoveries.push(now);
69902
+ console.log(
69903
+ "[SecureChannel] device_revoked but device not confirmed dead \u2014 reconnecting (self-heal)"
69904
+ );
69905
+ this._scheduleReconnect();
69906
+ return;
69907
+ }
69856
69908
  this._handleError(new Error("Device was revoked"));
69857
69909
  }
69858
69910
  /**
@@ -96803,7 +96855,7 @@ var init_index = __esm({
96803
96855
  init_skill_invoker();
96804
96856
  await init_skill_telemetry();
96805
96857
  await init_policy_enforcer();
96806
- VERSION = true ? "0.23.1" : "0.0.0-dev";
96858
+ VERSION = true ? "0.23.2" : "0.0.0-dev";
96807
96859
  }
96808
96860
  });
96809
96861
  await init_index();