@agentvault/claude-bridge 0.7.11 → 0.7.13

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 +160 -7
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -63854,6 +63854,35 @@ var init_mls_kp_pool = __esm2({
63854
63854
  await init_dist();
63855
63855
  }
63856
63856
  });
63857
+ function ownIdentity() {
63858
+ const v22 = true ? "0.23.22" : FALLBACK;
63859
+ return `${PACKAGE}@${v22}`;
63860
+ }
63861
+ function buildClientVersion(override) {
63862
+ try {
63863
+ const candidate = (override ?? "").trim();
63864
+ if (candidate) {
63865
+ const cleaned = candidate.replace(DISALLOWED, "").slice(0, MAX_LEN);
63866
+ if (cleaned && !cleaned.includes("..")) return cleaned;
63867
+ }
63868
+ return ownIdentity().replace(DISALLOWED, "").slice(0, MAX_LEN);
63869
+ } catch {
63870
+ return FALLBACK;
63871
+ }
63872
+ }
63873
+ var PACKAGE;
63874
+ var FALLBACK;
63875
+ var MAX_LEN;
63876
+ var DISALLOWED;
63877
+ var init_client_version = __esm2({
63878
+ "src/client-version.ts"() {
63879
+ "use strict";
63880
+ PACKAGE = "@agentvault/agentvault";
63881
+ FALLBACK = "0.0.0-dev";
63882
+ MAX_LEN = 64;
63883
+ DISALLOWED = /[^A-Za-z0-9._@/+-]/g;
63884
+ }
63885
+ });
63857
63886
  var CredentialStore;
63858
63887
  var init_credential_store = __esm2({
63859
63888
  "src/credential-store.ts"() {
@@ -64743,6 +64772,7 @@ var init_channel = __esm2({
64743
64772
  await init_dist();
64744
64773
  init_mls_state();
64745
64774
  await init_mls_kp_pool();
64775
+ init_client_version();
64746
64776
  init_credential_store();
64747
64777
  await init_crypto_helpers();
64748
64778
  await init_state();
@@ -64828,6 +64858,17 @@ var init_channel = __esm2({
64828
64858
  _pendingPollTimer = null;
64829
64859
  _syncMessageIds = null;
64830
64860
  _deliveryHeartbeat = null;
64861
+ /**
64862
+ * How often to re-check the KeyPackage pool against the server (#826).
64863
+ *
64864
+ * KeyPackages expire after 7 days and, before this, only a reconnect
64865
+ * republished them — so an agent that simply stayed up ran to zero usable
64866
+ * and silently stopped accepting new MLS 1:1 conversations while looking
64867
+ * perfectly healthy. 6h gives ~28 attempts inside one lifetime, so a run of
64868
+ * transient failures cannot age the pool out. Cost is one GET per interval.
64869
+ */
64870
+ static _KP_REPLENISH_INTERVAL_MS = 6 * 60 * 60 * 1e3;
64871
+ _kpReplenishTimer = null;
64831
64872
  _a2aReconcileTimer = null;
64832
64873
  _deliveryPulling = false;
64833
64874
  _drDeliveryPulling = false;
@@ -65792,7 +65833,7 @@ var init_channel = __esm2({
65792
65833
  */
65793
65834
  sendActivitySpan(spanData) {
65794
65835
  if (!this._ws || this._ws.readyState !== WebSocket2.OPEN) return;
65795
- const pluginVersion = true ? "0.23.19" : "0.0.0-dev";
65836
+ const pluginVersion = true ? "0.23.22" : "0.0.0-dev";
65796
65837
  const agentName = this.config.agentName ?? "Agent";
65797
65838
  const resource = {
65798
65839
  "service.name": "agentvault-agent",
@@ -67661,7 +67702,7 @@ var init_channel = __esm2({
67661
67702
  return;
67662
67703
  }
67663
67704
  const wsUrl = this.config.apiUrl.replace(/^http/, "ws");
67664
- const url22 = `${wsUrl}/api/v1/ws?token=${encodeURIComponent(this._deviceJwt)}&device_id=${this._deviceId}`;
67705
+ const url22 = `${wsUrl}/api/v1/ws?token=${encodeURIComponent(this._deviceJwt)}&device_id=${this._deviceId}&client_version=${encodeURIComponent(buildClientVersion(this.config.clientVersion))}`;
67665
67706
  const ws = new WebSocket2(url22);
67666
67707
  this._ws = ws;
67667
67708
  ws.on("open", async () => {
@@ -67702,7 +67743,7 @@ var init_channel = __esm2({
67702
67743
  agentVersion: this.config.agentVersion ?? "0.0.0",
67703
67744
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67704
67745
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67705
- pluginVersion: true ? "0.23.19" : "0.0.0-dev"
67746
+ pluginVersion: true ? "0.23.22" : "0.0.0-dev"
67706
67747
  });
67707
67748
  this._telemetryReporter.startAutoFlush(3e4);
67708
67749
  }
@@ -67711,6 +67752,7 @@ var init_channel = __esm2({
67711
67752
  console.warn("[SecureChannel] A2A channel sync on connect failed:", err);
67712
67753
  });
67713
67754
  await this._ensureKpPoolPublished();
67755
+ this._startKpReplenishTimer();
67714
67756
  this._pullDeliveryQueue().catch((err) => {
67715
67757
  console.warn("[SecureChannel] Initial delivery pull failed:", err);
67716
67758
  });
@@ -68025,7 +68067,7 @@ var init_channel = __esm2({
68025
68067
  agentVersion: this.config.agentVersion ?? "0.0.0",
68026
68068
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
68027
68069
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
68028
- pluginVersion: true ? "0.23.19" : "0.0.0-dev"
68070
+ pluginVersion: true ? "0.23.22" : "0.0.0-dev"
68029
68071
  });
68030
68072
  this._telemetryReporter.startAutoFlush(3e4);
68031
68073
  }
@@ -68368,6 +68410,7 @@ var init_channel = __esm2({
68368
68410
  this._deliveryHeartbeat = null;
68369
68411
  }
68370
68412
  this._stopA2aReconcileTimer();
68413
+ this._stopKpReplenishTimer();
68371
68414
  if (this._stopped) return;
68372
68415
  this._setState("disconnected");
68373
68416
  this._scheduleReconnect();
@@ -69761,6 +69804,106 @@ ${messageText}`;
69761
69804
  this._kpPoolFilling = false;
69762
69805
  }
69763
69806
  }
69807
+ /** Start the periodic KeyPackage replenish (#826). Idempotent. */
69808
+ _startKpReplenishTimer() {
69809
+ this._stopKpReplenishTimer();
69810
+ this._kpReplenishTimer = setInterval(() => {
69811
+ void this._replenishKpPoolFromServer();
69812
+ }, _SecureChannel._KP_REPLENISH_INTERVAL_MS);
69813
+ }
69814
+ _stopKpReplenishTimer() {
69815
+ if (this._kpReplenishTimer) {
69816
+ clearInterval(this._kpReplenishTimer);
69817
+ this._kpReplenishTimer = null;
69818
+ }
69819
+ }
69820
+ /**
69821
+ * The server's EXPIRY-AWARE usable KeyPackage count, or null when unknown.
69822
+ *
69823
+ * `count_unconsumed` filters `expires_at > utcnow()`, which is the only count
69824
+ * that reflects what a peer can actually fetch. Returns null (not 0) on any
69825
+ * failure so callers can tell "server says empty" from "we could not ask" —
69826
+ * guessing 0 here would publish a fresh pool on every transient 503.
69827
+ */
69828
+ async _fetchServerKpCount() {
69829
+ try {
69830
+ const horizonSeconds = Math.floor(
69831
+ _SecureChannel._KP_REPLENISH_INTERVAL_MS * 2 / 1e3
69832
+ );
69833
+ const res = await fetch(
69834
+ `${this.config.apiUrl}/api/v1/mls/key-packages/count?device_id=${this._deviceId}&renew_within_seconds=${horizonSeconds}`,
69835
+ { headers: { Authorization: `Bearer ${this._deviceJwt}` } }
69836
+ );
69837
+ if (!res.ok) return null;
69838
+ const body = await res.json();
69839
+ const n22 = body?.count;
69840
+ return typeof n22 === "number" && Number.isFinite(n22) ? n22 : null;
69841
+ } catch {
69842
+ return null;
69843
+ }
69844
+ }
69845
+ /**
69846
+ * Top the pool up against the SERVER's count (#826).
69847
+ *
69848
+ * ⚠️ DO NOT replace this with a periodic call to `_ensureKpPoolPublished()`.
69849
+ * That function early-returns on `_pendingKpBundles.length >= _KP_POOL_TARGET`,
69850
+ * and that array holds PRIVATE bundles which never expire locally — so on an
69851
+ * agent that has been connected longer than the KeyPackage lifetime it is
69852
+ * still full while the server has zero usable. Measured on prod 2026-08-09:
69853
+ * cortina and eclaude were online and green with 10 unconsumed / 0 usable,
69854
+ * unable to accept any new MLS 1:1. A timer over the local guard would have
69855
+ * run forever and changed nothing.
69856
+ *
69857
+ * Publishes only the shortfall, and never throws — this runs on a timer and
69858
+ * must not be able to take the channel down.
69859
+ */
69860
+ async _replenishKpPoolFromServer() {
69861
+ try {
69862
+ if (this._kpPoolFilling) return;
69863
+ const usable = await this._fetchServerKpCount();
69864
+ if (usable === null) return;
69865
+ const shortfall = _SecureChannel._KP_POOL_TARGET - usable;
69866
+ if (shortfall <= 0) return;
69867
+ this._kpPoolFilling = true;
69868
+ try {
69869
+ const identity = new TextEncoder().encode(this._deviceId);
69870
+ let published = 0;
69871
+ for (let i2 = 0; i2 < shortfall; i2++) {
69872
+ const mlsMgr = new MLSGroupManager();
69873
+ const kp = await mlsMgr.generateKeyPackage(identity);
69874
+ const kpHex = Buffer.from(
69875
+ MLSGroupManager.serializeKeyPackage(kp.publicPackage)
69876
+ ).toString("hex");
69877
+ const res = await fetch(`${this.config.apiUrl}/api/v1/mls/key-packages`, {
69878
+ method: "POST",
69879
+ headers: {
69880
+ Authorization: `Bearer ${this._deviceJwt}`,
69881
+ "Content-Type": "application/json"
69882
+ },
69883
+ body: JSON.stringify({ key_package: kpHex })
69884
+ });
69885
+ if (!res.ok) {
69886
+ console.warn(
69887
+ `[SecureChannel] KeyPackage replenish rejected (status ${res.status}) after ${published}/${shortfall}`
69888
+ );
69889
+ break;
69890
+ }
69891
+ this._recordPublishedKp(kp);
69892
+ if (!this._mlsKeyPackage) this._mlsKeyPackage = kp;
69893
+ published++;
69894
+ }
69895
+ if (published > 0) {
69896
+ console.log(
69897
+ `[SecureChannel] MLS KeyPackage pool replenished (${published} new; server had ${usable}/${_SecureChannel._KP_POOL_TARGET} usable)`
69898
+ );
69899
+ }
69900
+ } finally {
69901
+ this._kpPoolFilling = false;
69902
+ }
69903
+ } catch (err) {
69904
+ console.warn("[SecureChannel] KeyPackage replenish failed:", err);
69905
+ }
69906
+ }
69764
69907
  async _handleMlsWelcome(data) {
69765
69908
  const groupId = data.group_id;
69766
69909
  const conversationId = data.conversation_id;
@@ -97914,7 +98057,7 @@ var init_index = __esm2({
97914
98057
  init_skill_invoker();
97915
98058
  await init_skill_telemetry();
97916
98059
  await init_policy_enforcer();
97917
- VERSION = true ? "0.23.19" : "0.0.0-dev";
98060
+ VERSION = true ? "0.23.22" : "0.0.0-dev";
97918
98061
  }
97919
98062
  });
97920
98063
  await init_index();
@@ -134268,7 +134411,7 @@ async function main() {
134268
134411
  "[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
134412
  );
134270
134413
  }
134271
- logLine(`[bridge] version: ${true ? "0.7.11" : "dev"}`);
134414
+ logLine(`[bridge] version: ${true ? "0.7.13" : "dev"}`);
134272
134415
  logLine(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
134273
134416
  logLine(`[bridge] workspace: ${cfg.workspaceDir} \xB7 permissions: ${cfg.permissionMode} \xB7 enclave dir fenced`);
134274
134417
  if (cfg.armRoom) {
@@ -134290,7 +134433,17 @@ async function main() {
134290
134433
  dataDir: cfg.dataDir,
134291
134434
  apiUrl: cfg.apiUrl,
134292
134435
  agentName: cfg.agentName,
134293
- platform: "node"
134436
+ platform: "node",
134437
+ // #836: identify the BRIDGE, not the plugin it vendors. Two reasons this
134438
+ // must be explicit. Operators install and upgrade the bridge, so the bridge
134439
+ // build is the one they can act on — reporting the inner plugin version is
134440
+ // the exact confusion #836 was opened about (a bridge on 0.3.2 read as
134441
+ // "same bug, didn't restart"). And because this build BUNDLES the plugin,
134442
+ // the plugin's own `__AV_VERSION__` resolves to OUR package.json here, so
134443
+ // its default would render "@agentvault/agentvault@0.7.x" — the wrong
134444
+ // package name attached to the bridge's version number, which is worse
134445
+ // than either alone.
134446
+ clientVersion: `@agentvault/claude-bridge@${true ? "0.7.13" : "dev"}`
134294
134447
  });
134295
134448
  const agentSystemPrompt = cfg.systemPrompt ?? `You are ${cfg.agentName}, an AI agent on AgentVault. You talk with your owner in 1:1 direct messages and collaborate with other agents in shared rooms. That is your identity \u2014 introduce yourself by that name and do not claim to be any other agent. To speak, call the say tool. In a 1:1 with your owner, reply to what they say. In a room you see every message \u2014 call say only when you have something worth adding, and otherwise stay silent. Keep messages concise.`;
134296
134449
  const deviceJwt = () => {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@agentvault/claude-bridge",
3
- "version": "0.7.11",
3
+ "version": "0.7.13",
4
4
  "type": "module",
5
- "description": "AgentVault Claude Bridge \u2014 daemon for bridging a Claude agent into secure E2E-encrypted AgentVault 1:1 direct messages and rooms.",
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",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {