@agentvault/claude-bridge 0.5.4 → 0.5.5

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/index.js +71 -19
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -65502,7 +65502,7 @@ var init_channel = __esm2({
65502
65502
  */
65503
65503
  sendActivitySpan(spanData) {
65504
65504
  if (!this._ws || this._ws.readyState !== WebSocket2.OPEN) return;
65505
- const pluginVersion = true ? "0.23.0" : "0.0.0-dev";
65505
+ const pluginVersion = true ? "0.23.2" : "0.0.0-dev";
65506
65506
  const agentName = this.config.agentName ?? "Agent";
65507
65507
  const resource = {
65508
65508
  "service.name": "agentvault-agent",
@@ -67119,7 +67119,7 @@ var init_channel = __esm2({
67119
67119
  agentVersion: this.config.agentVersion ?? "0.0.0",
67120
67120
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67121
67121
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67122
- pluginVersion: true ? "0.23.0" : "0.0.0-dev"
67122
+ pluginVersion: true ? "0.23.2" : "0.0.0-dev"
67123
67123
  });
67124
67124
  this._telemetryReporter.startAutoFlush(3e4);
67125
67125
  }
@@ -67195,7 +67195,7 @@ var init_channel = __esm2({
67195
67195
  return;
67196
67196
  }
67197
67197
  if (data.event === "device_revoked") {
67198
- this._handleDeviceRevoked();
67198
+ await this._handleDeviceRevoked();
67199
67199
  return;
67200
67200
  }
67201
67201
  if (data.event === "device_linked") {
@@ -67429,7 +67429,7 @@ var init_channel = __esm2({
67429
67429
  agentVersion: this.config.agentVersion ?? "0.0.0",
67430
67430
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67431
67431
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67432
- pluginVersion: true ? "0.23.0" : "0.0.0-dev"
67432
+ pluginVersion: true ? "0.23.2" : "0.0.0-dev"
67433
67433
  });
67434
67434
  this._telemetryReporter.startAutoFlush(3e4);
67435
67435
  }
@@ -70594,22 +70594,74 @@ ${messageText}`;
70594
70594
  this._setState("error");
70595
70595
  this.emit("error", err);
70596
70596
  }
70597
+ // Phase 2 — rolling-window timestamps of device_revoked self-heal reconnects,
70598
+ // used as a war cap so a takeover war or a server status/WS inconsistency can't
70599
+ // thrash forever.
70600
+ _revokeRecoveries = [];
70601
+ static REVOKE_RECOVERY_MAX = 3;
70602
+ static REVOKE_RECOVERY_WINDOW_MS = 12e4;
70597
70603
  /**
70598
- * Handle a WS `device_revoked` event. Surfaces a terminal error so
70599
- * attachLifecycle stops the channel — but does NOT delete credentials.
70604
+ * Handle a WS `device_revoked` event.
70600
70605
  *
70601
- * This signal fires both for a genuine owner revoke AND for a transient
70602
- * session-takeover of a device that is still ACTIVE on the server (the error
70603
- * text even reads "or another session has taken over"). Deleting
70604
- * agentvault.json here as this path used to via clearState() strands a
70605
- * still-valid identity: the primary creds vanish, only the .bak survives, and
70606
- * the bridge's token-gate then demands a fresh invite token to restart.
70607
- * Keeping the creds means a restart reconnects via restoreState() when the
70608
- * device is still valid, and fails cleanly (device_revoked again, no data
70609
- * lost) when it isn't. Re-enrollment, which SHOULD wipe creds, goes through
70610
- * the explicit setup --force / _forceReEnroll path in start().
70606
+ * The signal is ambiguous: the server sends it both for a genuine owner revoke
70607
+ * AND for a transient/racy session-takeover of a device that is still ACTIVE
70608
+ * (the error text even reads "or another session has taken over" — loopita's
70609
+ * case, where the device stayed ACTIVE on the server). So we do NOT terminate
70610
+ * blindly. We ask the PUBLIC /devices/{id}/status endpoint (no JWT needed, so
70611
+ * it answers even when our device JWT is being rejected):
70612
+ * - ACTIVE -> reconnect in place (transient); stay online.
70613
+ * - non-ACTIVE -> terminal (genuinely revoked; operator must re-enroll).
70614
+ * - unreachable/429 -> reconnect (inconclusive; a network blip shouldn't
70615
+ * permanently kill a healthy agent the WS is down too,
70616
+ * so normal backoff handles it, and the war cap bounds
70617
+ * thrash if the device really is dead).
70618
+ *
70619
+ * A rolling-window cap (REVOKE_RECOVERY_MAX self-heals per WINDOW_MS) gives up
70620
+ * (terminal) on a repeating loop. Never clears credentials in any branch —
70621
+ * re-enrollment, which SHOULD wipe creds, goes through setup --force /
70622
+ * _forceReEnroll in start().
70611
70623
  */
70612
- _handleDeviceRevoked() {
70624
+ async _handleDeviceRevoked() {
70625
+ const now = Date.now();
70626
+ this._revokeRecoveries = this._revokeRecoveries.filter(
70627
+ (t22) => now - t22 < _SecureChannel.REVOKE_RECOVERY_WINDOW_MS
70628
+ );
70629
+ if (this._revokeRecoveries.length >= _SecureChannel.REVOKE_RECOVERY_MAX) {
70630
+ console.warn(
70631
+ "[SecureChannel] device_revoked self-healed too many times in a short window \u2014 giving up (terminal)"
70632
+ );
70633
+ this._handleError(new Error("Device was revoked"));
70634
+ return;
70635
+ }
70636
+ const deviceId = this._deviceId ?? this._persisted?.deviceId;
70637
+ if (!deviceId) {
70638
+ this._handleError(new Error("Device was revoked"));
70639
+ return;
70640
+ }
70641
+ let reconnect;
70642
+ try {
70643
+ const status = await pollDeviceStatus(this.config.apiUrl, deviceId);
70644
+ reconnect = status.rateLimited === true || status.status === "ACTIVE";
70645
+ if (!reconnect) {
70646
+ console.warn(
70647
+ `[SecureChannel] device_revoked confirmed by server (status=${status.status}) \u2014 terminal`
70648
+ );
70649
+ }
70650
+ } catch (err) {
70651
+ console.warn(
70652
+ "[SecureChannel] device_revoked status check failed; reconnecting (inconclusive):",
70653
+ err
70654
+ );
70655
+ reconnect = true;
70656
+ }
70657
+ if (reconnect) {
70658
+ this._revokeRecoveries.push(now);
70659
+ console.log(
70660
+ "[SecureChannel] device_revoked but device not confirmed dead \u2014 reconnecting (self-heal)"
70661
+ );
70662
+ this._scheduleReconnect();
70663
+ return;
70664
+ }
70613
70665
  this._handleError(new Error("Device was revoked"));
70614
70666
  }
70615
70667
  /**
@@ -97124,7 +97176,7 @@ var init_index = __esm2({
97124
97176
  init_skill_invoker();
97125
97177
  await init_skill_telemetry();
97126
97178
  await init_policy_enforcer();
97127
- VERSION = true ? "0.23.0" : "0.0.0-dev";
97179
+ VERSION = true ? "0.23.2" : "0.0.0-dev";
97128
97180
  }
97129
97181
  });
97130
97182
  await init_index();
@@ -132866,7 +132918,7 @@ async function main() {
132866
132918
  "[bridge] warning: passing the invite token on the command line is visible to other local users via 'ps'. Prefer: AV_INVITE_TOKEN=\u2026 npx @agentvault/claude-bridge"
132867
132919
  );
132868
132920
  }
132869
- console.error(`[bridge] version: ${true ? "0.5.4" : "dev"}`);
132921
+ console.error(`[bridge] version: ${true ? "0.5.5" : "dev"}`);
132870
132922
  console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
132871
132923
  if (cfg.worker) {
132872
132924
  console.error(`[bridge] WORKER MODE \u2014 workspace: ${cfg.workspaceDir} \xB7 permissions: ${cfg.permissionMode} \xB7 enclave dir fenced`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentvault/claude-bridge",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "type": "module",
5
5
  "description": "AgentVault Claude Bridge — daemon for bridging a Claude agent into secure E2E-encrypted AgentVault 1:1 direct messages and rooms.",
6
6
  "main": "dist/index.js",