@agentvault/claude-bridge 0.7.11 → 0.7.12

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 +115 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -64828,6 +64828,17 @@ var init_channel = __esm2({
64828
64828
  _pendingPollTimer = null;
64829
64829
  _syncMessageIds = null;
64830
64830
  _deliveryHeartbeat = null;
64831
+ /**
64832
+ * How often to re-check the KeyPackage pool against the server (#826).
64833
+ *
64834
+ * KeyPackages expire after 7 days and, before this, only a reconnect
64835
+ * republished them — so an agent that simply stayed up ran to zero usable
64836
+ * and silently stopped accepting new MLS 1:1 conversations while looking
64837
+ * perfectly healthy. 6h gives ~28 attempts inside one lifetime, so a run of
64838
+ * transient failures cannot age the pool out. Cost is one GET per interval.
64839
+ */
64840
+ static _KP_REPLENISH_INTERVAL_MS = 6 * 60 * 60 * 1e3;
64841
+ _kpReplenishTimer = null;
64831
64842
  _a2aReconcileTimer = null;
64832
64843
  _deliveryPulling = false;
64833
64844
  _drDeliveryPulling = false;
@@ -65792,7 +65803,7 @@ var init_channel = __esm2({
65792
65803
  */
65793
65804
  sendActivitySpan(spanData) {
65794
65805
  if (!this._ws || this._ws.readyState !== WebSocket2.OPEN) return;
65795
- const pluginVersion = true ? "0.23.19" : "0.0.0-dev";
65806
+ const pluginVersion = true ? "0.23.21" : "0.0.0-dev";
65796
65807
  const agentName = this.config.agentName ?? "Agent";
65797
65808
  const resource = {
65798
65809
  "service.name": "agentvault-agent",
@@ -67702,7 +67713,7 @@ var init_channel = __esm2({
67702
67713
  agentVersion: this.config.agentVersion ?? "0.0.0",
67703
67714
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67704
67715
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67705
- pluginVersion: true ? "0.23.19" : "0.0.0-dev"
67716
+ pluginVersion: true ? "0.23.21" : "0.0.0-dev"
67706
67717
  });
67707
67718
  this._telemetryReporter.startAutoFlush(3e4);
67708
67719
  }
@@ -67711,6 +67722,7 @@ var init_channel = __esm2({
67711
67722
  console.warn("[SecureChannel] A2A channel sync on connect failed:", err);
67712
67723
  });
67713
67724
  await this._ensureKpPoolPublished();
67725
+ this._startKpReplenishTimer();
67714
67726
  this._pullDeliveryQueue().catch((err) => {
67715
67727
  console.warn("[SecureChannel] Initial delivery pull failed:", err);
67716
67728
  });
@@ -68025,7 +68037,7 @@ var init_channel = __esm2({
68025
68037
  agentVersion: this.config.agentVersion ?? "0.0.0",
68026
68038
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
68027
68039
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
68028
- pluginVersion: true ? "0.23.19" : "0.0.0-dev"
68040
+ pluginVersion: true ? "0.23.21" : "0.0.0-dev"
68029
68041
  });
68030
68042
  this._telemetryReporter.startAutoFlush(3e4);
68031
68043
  }
@@ -68368,6 +68380,7 @@ var init_channel = __esm2({
68368
68380
  this._deliveryHeartbeat = null;
68369
68381
  }
68370
68382
  this._stopA2aReconcileTimer();
68383
+ this._stopKpReplenishTimer();
68371
68384
  if (this._stopped) return;
68372
68385
  this._setState("disconnected");
68373
68386
  this._scheduleReconnect();
@@ -69761,6 +69774,103 @@ ${messageText}`;
69761
69774
  this._kpPoolFilling = false;
69762
69775
  }
69763
69776
  }
69777
+ /** Start the periodic KeyPackage replenish (#826). Idempotent. */
69778
+ _startKpReplenishTimer() {
69779
+ this._stopKpReplenishTimer();
69780
+ this._kpReplenishTimer = setInterval(() => {
69781
+ void this._replenishKpPoolFromServer();
69782
+ }, _SecureChannel._KP_REPLENISH_INTERVAL_MS);
69783
+ }
69784
+ _stopKpReplenishTimer() {
69785
+ if (this._kpReplenishTimer) {
69786
+ clearInterval(this._kpReplenishTimer);
69787
+ this._kpReplenishTimer = null;
69788
+ }
69789
+ }
69790
+ /**
69791
+ * The server's EXPIRY-AWARE usable KeyPackage count, or null when unknown.
69792
+ *
69793
+ * `count_unconsumed` filters `expires_at > utcnow()`, which is the only count
69794
+ * that reflects what a peer can actually fetch. Returns null (not 0) on any
69795
+ * failure so callers can tell "server says empty" from "we could not ask" —
69796
+ * guessing 0 here would publish a fresh pool on every transient 503.
69797
+ */
69798
+ async _fetchServerKpCount() {
69799
+ try {
69800
+ const res = await fetch(
69801
+ `${this.config.apiUrl}/api/v1/mls/key-packages/count?device_id=${this._deviceId}`,
69802
+ { headers: { Authorization: `Bearer ${this._deviceJwt}` } }
69803
+ );
69804
+ if (!res.ok) return null;
69805
+ const body = await res.json();
69806
+ const n22 = body?.count;
69807
+ return typeof n22 === "number" && Number.isFinite(n22) ? n22 : null;
69808
+ } catch {
69809
+ return null;
69810
+ }
69811
+ }
69812
+ /**
69813
+ * Top the pool up against the SERVER's count (#826).
69814
+ *
69815
+ * ⚠️ DO NOT replace this with a periodic call to `_ensureKpPoolPublished()`.
69816
+ * That function early-returns on `_pendingKpBundles.length >= _KP_POOL_TARGET`,
69817
+ * and that array holds PRIVATE bundles which never expire locally — so on an
69818
+ * agent that has been connected longer than the KeyPackage lifetime it is
69819
+ * still full while the server has zero usable. Measured on prod 2026-08-09:
69820
+ * cortina and eclaude were online and green with 10 unconsumed / 0 usable,
69821
+ * unable to accept any new MLS 1:1. A timer over the local guard would have
69822
+ * run forever and changed nothing.
69823
+ *
69824
+ * Publishes only the shortfall, and never throws — this runs on a timer and
69825
+ * must not be able to take the channel down.
69826
+ */
69827
+ async _replenishKpPoolFromServer() {
69828
+ try {
69829
+ if (this._kpPoolFilling) return;
69830
+ const usable = await this._fetchServerKpCount();
69831
+ if (usable === null) return;
69832
+ const shortfall = _SecureChannel._KP_POOL_TARGET - usable;
69833
+ if (shortfall <= 0) return;
69834
+ this._kpPoolFilling = true;
69835
+ try {
69836
+ const identity = new TextEncoder().encode(this._deviceId);
69837
+ let published = 0;
69838
+ for (let i2 = 0; i2 < shortfall; i2++) {
69839
+ const mlsMgr = new MLSGroupManager();
69840
+ const kp = await mlsMgr.generateKeyPackage(identity);
69841
+ const kpHex = Buffer.from(
69842
+ MLSGroupManager.serializeKeyPackage(kp.publicPackage)
69843
+ ).toString("hex");
69844
+ const res = await fetch(`${this.config.apiUrl}/api/v1/mls/key-packages`, {
69845
+ method: "POST",
69846
+ headers: {
69847
+ Authorization: `Bearer ${this._deviceJwt}`,
69848
+ "Content-Type": "application/json"
69849
+ },
69850
+ body: JSON.stringify({ key_package: kpHex })
69851
+ });
69852
+ if (!res.ok) {
69853
+ console.warn(
69854
+ `[SecureChannel] KeyPackage replenish rejected (status ${res.status}) after ${published}/${shortfall}`
69855
+ );
69856
+ break;
69857
+ }
69858
+ this._recordPublishedKp(kp);
69859
+ if (!this._mlsKeyPackage) this._mlsKeyPackage = kp;
69860
+ published++;
69861
+ }
69862
+ if (published > 0) {
69863
+ console.log(
69864
+ `[SecureChannel] MLS KeyPackage pool replenished (${published} new; server had ${usable}/${_SecureChannel._KP_POOL_TARGET} usable)`
69865
+ );
69866
+ }
69867
+ } finally {
69868
+ this._kpPoolFilling = false;
69869
+ }
69870
+ } catch (err) {
69871
+ console.warn("[SecureChannel] KeyPackage replenish failed:", err);
69872
+ }
69873
+ }
69764
69874
  async _handleMlsWelcome(data) {
69765
69875
  const groupId = data.group_id;
69766
69876
  const conversationId = data.conversation_id;
@@ -97914,7 +98024,7 @@ var init_index = __esm2({
97914
98024
  init_skill_invoker();
97915
98025
  await init_skill_telemetry();
97916
98026
  await init_policy_enforcer();
97917
- VERSION = true ? "0.23.19" : "0.0.0-dev";
98027
+ VERSION = true ? "0.23.21" : "0.0.0-dev";
97918
98028
  }
97919
98029
  });
97920
98030
  await init_index();
@@ -134268,7 +134378,7 @@ async function main() {
134268
134378
  "[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"
134269
134379
  );
134270
134380
  }
134271
- logLine(`[bridge] version: ${true ? "0.7.11" : "dev"}`);
134381
+ logLine(`[bridge] version: ${true ? "0.7.12" : "dev"}`);
134272
134382
  logLine(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
134273
134383
  logLine(`[bridge] workspace: ${cfg.workspaceDir} \xB7 permissions: ${cfg.permissionMode} \xB7 enclave dir fenced`);
134274
134384
  if (cfg.armRoom) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentvault/claude-bridge",
3
- "version": "0.7.11",
3
+ "version": "0.7.12",
4
4
  "type": "module",
5
5
  "description": "AgentVault Claude Bridge \u2014 daemon for bridging a Claude agent into secure E2E-encrypted AgentVault 1:1 direct messages and rooms.",
6
6
  "main": "dist/index.js",