@agentvault/claude-bridge 0.7.3 → 0.7.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 +243 -25
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -64616,6 +64616,8 @@ var OUTBOUND_DEDUPE_WINDOW_MS;
64616
64616
  var OUTBOUND_DEDUPE_MAX_SUPPRESS_MS;
64617
64617
  var OUTBOUND_DEDUPE_MAX;
64618
64618
  var OUTBOUND_DEDUPE_TYPES;
64619
+ var RETRY_CANDIDATE_TTL_MS;
64620
+ var RETRY_CANDIDATE_MAX;
64619
64621
  var SecureChannel;
64620
64622
  var init_channel = __esm2({
64621
64623
  async "src/channel.ts"() {
@@ -64653,6 +64655,8 @@ var init_channel = __esm2({
64653
64655
  OUTBOUND_DEDUPE_MAX_SUPPRESS_MS = 15e3;
64654
64656
  OUTBOUND_DEDUPE_MAX = 256;
64655
64657
  OUTBOUND_DEDUPE_TYPES = /* @__PURE__ */ new Set(["text", "status_alert", "artifact", "attachment"]);
64658
+ RETRY_CANDIDATE_TTL_MS = 6e4;
64659
+ RETRY_CANDIDATE_MAX = 32;
64656
64660
  SecureChannel = class _SecureChannel extends EventEmitter {
64657
64661
  constructor(config22) {
64658
64662
  super();
@@ -64730,6 +64734,24 @@ var init_channel = __esm2({
64730
64734
  _pendingKpBundles = [];
64731
64735
  /** Pool target: keep this many unconsumed KeyPackages published; backend caps to match. */
64732
64736
  static _KP_POOL_TARGET = 10;
64737
+ /**
64738
+ * Local retention bound for `_pendingKpBundles` (#363). Deliberately ABOVE
64739
+ * `_KP_POOL_TARGET`, because the two counts are in DIFFERENT UNITS: the backend
64740
+ * keeps up to `_KP_POOL_TARGET` **unconsumed** KeyPackages, while this array
64741
+ * holds every bundle published since the last join — consumed ones are removed
64742
+ * only by `_removeKpFromPool` on a successful join.
64743
+ *
64744
+ * Capping local retention AT the backend's target therefore evicts bundles the
64745
+ * backend can still serve, and their private material cannot be rebuilt (#341:
64746
+ * `mls_rs_uniffi` exposes no KeyPackage repository), so any Welcome minted
64747
+ * against an evicted bundle is PERMANENTLY unjoinable.
64748
+ *
64749
+ * Same defect and same remedy as #511, where the web store capped 6 against a
64750
+ * backend pool of 10 and was raised to 12. Retaining extra bundles is cheap and
64751
+ * purely protective — `_handleMlsWelcome` trial-decrypts against the whole pool,
64752
+ * so a surplus only ever adds chances to match.
64753
+ */
64754
+ static _KP_POOL_RETENTION = 20;
64733
64755
  /** Reentrancy guard so concurrent pool top-ups don't double-publish past target. */
64734
64756
  _kpPoolFilling = false;
64735
64757
  /** Buffer for MLS commits received before Welcome (keyed by groupId, sorted by epoch). */
@@ -64754,6 +64776,20 @@ var init_channel = __esm2({
64754
64776
  * single delivery. In-memory only (short window; no cross-restart persistence needed).
64755
64777
  */
64756
64778
  _recentDeliveries = /* @__PURE__ */ new Map();
64779
+ /**
64780
+ * Last outbound plaintext per MLS group id (#732), so a message refused with
64781
+ * `unknown group` can be resent once the map is reconciled.
64782
+ *
64783
+ * PLAINTEXT, not the frame: the refused frame's `payload` is ciphertext
64784
+ * encrypted to a group that no longer exists, so replaying those bytes into
64785
+ * the replacement group would store something nobody can decrypt. The retry
64786
+ * has to encrypt again, which means keeping what was encrypted.
64787
+ *
64788
+ * In-memory only, TTL-bounded, and consumed on first use — this exists for
64789
+ * the ~50ms between a frame reaching the socket and the server refusing it,
64790
+ * not as a durable outbox (that is #688's dead-letter).
64791
+ */
64792
+ _retryCandidates = /* @__PURE__ */ new Map();
64757
64793
  _scanEngine = null;
64758
64794
  _scanRuleSetVersion = 0;
64759
64795
  _telemetryReporter = null;
@@ -64853,13 +64889,21 @@ var init_channel = __esm2({
64853
64889
  * Non-fatal: a transient network error or 5xx keeps the current JWT and
64854
64890
  * relies on the next reconnect. Terminal failures (401/403) surface via
64855
64891
  * the `auth_failed` event so callers can prompt the user to recover.
64892
+ *
64893
+ * Returns TRUE when the credentials are terminally dead. #743: the caller
64894
+ * MUST NOT go on to open a WebSocket in that case — the server has already
64895
+ * told us this credential can never be served, and connecting anyway is what
64896
+ * produced `ws_guard action=denylist` on repeat in prod (device 0e87ff50, a
64897
+ * hard-deleted device, ~12/hour). The verdict is returned explicitly rather
64898
+ * than read back off `_authFailedThisSession`, whose lifetime is reset per
64899
+ * connect and is easy to get subtly wrong.
64856
64900
  */
64857
64901
  async _maybeReissueDeviceJwt() {
64858
64902
  const jwt22 = this._persisted?.deviceJwt ?? this._deviceJwt;
64859
- if (!jwt22) return;
64903
+ if (!jwt22) return false;
64860
64904
  const exp = this._decodeJwtExp(jwt22);
64861
64905
  const nowSec = Math.floor(Date.now() / 1e3);
64862
- if (exp - nowSec >= 30 * 86400) return;
64906
+ if (exp - nowSec >= 30 * 86400) return false;
64863
64907
  try {
64864
64908
  const resp = await fetch(`${this.config.apiUrl}/api/v1/auth/reissue`, {
64865
64909
  method: "POST",
@@ -64879,7 +64923,7 @@ var init_channel = __esm2({
64879
64923
  } else {
64880
64924
  console.warn("[SecureChannel] reissue returned 200 but no device_jwt in response");
64881
64925
  }
64882
- return;
64926
+ return false;
64883
64927
  }
64884
64928
  if (resp.status === 401 || resp.status === 403) {
64885
64929
  console.warn(`[SecureChannel] reissue rejected (${resp.status}); credentials need attention`);
@@ -64887,12 +64931,13 @@ var init_channel = __esm2({
64887
64931
  const reason = resp.status === 401 ? "device_jwt_expired" : "device_revoked";
64888
64932
  await this._postAuthFailedToBackend(reason);
64889
64933
  this.emit("auth_failed", { reason });
64890
- return;
64934
+ return true;
64891
64935
  }
64892
64936
  console.warn(`[SecureChannel] reissue ${resp.status}; keeping current jwt`);
64893
64937
  } catch (err) {
64894
64938
  console.warn("[SecureChannel] reissue network error; keeping current jwt:", err);
64895
64939
  }
64940
+ return false;
64896
64941
  }
64897
64942
  /**
64898
64943
  * Wraps `fetch` for authenticated AV REST endpoints with reactive device-JWT
@@ -65378,7 +65423,9 @@ var init_channel = __esm2({
65378
65423
  }
65379
65424
  scanStatus = scanResult.status;
65380
65425
  }
65381
- this._appendHistory("agent", plaintext, topicId);
65426
+ if (!options?.isResend) {
65427
+ this._appendHistory("agent", plaintext, topicId);
65428
+ }
65382
65429
  const roomConvIds = /* @__PURE__ */ new Set();
65383
65430
  if (this._persisted?.rooms) {
65384
65431
  for (const room of Object.values(this._persisted.rooms)) {
@@ -65392,8 +65439,11 @@ var init_channel = __esm2({
65392
65439
  let sentCount = 0;
65393
65440
  const pendingWsSends = [];
65394
65441
  const sentSharedGroupIds = /* @__PURE__ */ new Set();
65442
+ const addressedMlsGroupIds = [];
65395
65443
  if (this._persisted?.mlsGroups && this._state === "ready" && this._ws) {
65444
+ const targetSharedGid = targetConvId ? this._sessionGroupIds?.get(targetConvId) : void 0;
65396
65445
  for (const [gid, entry] of Object.entries(this._persisted.mlsGroups)) {
65446
+ if (targetConvId && gid !== targetSharedGid) continue;
65397
65447
  if (!entry.mlsGroupId) continue;
65398
65448
  const mlsGroup = this._mlsGroups.get(`1to1-group:${gid}`);
65399
65449
  if (!mlsGroup?.isInitialized || Number(mlsGroup.epoch) <= 0) continue;
@@ -65415,6 +65465,7 @@ var init_channel = __esm2({
65415
65465
  if (this._persisted?.hubAddress) payload.hub_address = this._persisted.hubAddress;
65416
65466
  if (this._persisted?.hubId) payload.sender_hub_id = this._persisted.hubId;
65417
65467
  pendingWsSends.push(JSON.stringify({ event: "message_mls", data: payload }));
65468
+ addressedMlsGroupIds.push(entry.mlsGroupId);
65418
65469
  sentSharedGroupIds.add(gid);
65419
65470
  sentCount++;
65420
65471
  console.log(`[SecureChannel] Shared MLS group send for group ${gid.slice(0, 8)} (${entry.mlsGroupId.slice(0, 8)})`);
@@ -65467,6 +65518,7 @@ var init_channel = __esm2({
65467
65518
  data: payload
65468
65519
  })
65469
65520
  );
65521
+ addressedMlsGroupIds.push(mlsGroupId);
65470
65522
  if (convGroupId) sentSharedGroupIds.add(convGroupId);
65471
65523
  } else {
65472
65524
  const encrypted = session.ratchet.encrypt(plaintext);
@@ -65552,6 +65604,7 @@ var init_channel = __esm2({
65552
65604
  if (this._persisted?.hubAddress) payload.hub_address = this._persisted.hubAddress;
65553
65605
  if (this._persisted?.hubId) payload.sender_hub_id = this._persisted.hubId;
65554
65606
  pendingWsSends.push(JSON.stringify({ event: "message_mls", data: payload }));
65607
+ addressedMlsGroupIds.push(resolvedMlsGroupId);
65555
65608
  sentCount++;
65556
65609
  if (mlsOnlyConvGroupId) sentSharedGroupIds.add(mlsOnlyConvGroupId);
65557
65610
  console.log(`[SecureChannel] MLS-only send for conv ${mlsConvId.slice(0, 8)} (no DR session)`);
@@ -65567,6 +65620,28 @@ var init_channel = __esm2({
65567
65620
  for (const frame of pendingWsSends) {
65568
65621
  this._ws.send(frame);
65569
65622
  }
65623
+ if (!options?.isResend) {
65624
+ for (const mlsGroupId of addressedMlsGroupIds) {
65625
+ this._rememberRetryCandidate(mlsGroupId, plaintext, options);
65626
+ }
65627
+ }
65628
+ }
65629
+ /**
65630
+ * Retain one outbound plaintext against the MLS group it was sent to (#732).
65631
+ * Evicts expired entries, then the oldest, so the map cannot grow unbounded
65632
+ * on an agent that sends steadily and is never refused.
65633
+ */
65634
+ _rememberRetryCandidate(mlsGroupId, plaintext, options) {
65635
+ const now = Date.now();
65636
+ for (const [k2, v22] of this._retryCandidates) {
65637
+ if (now - v22.ts > RETRY_CANDIDATE_TTL_MS) this._retryCandidates.delete(k2);
65638
+ }
65639
+ this._retryCandidates.set(mlsGroupId, { plaintext, options, ts: now });
65640
+ while (this._retryCandidates.size > RETRY_CANDIDATE_MAX) {
65641
+ const oldest = this._retryCandidates.keys().next().value;
65642
+ if (!oldest) break;
65643
+ this._retryCandidates.delete(oldest);
65644
+ }
65570
65645
  }
65571
65646
  /**
65572
65647
  * Send a typing indicator to all owner devices.
@@ -65594,7 +65669,7 @@ var init_channel = __esm2({
65594
65669
  */
65595
65670
  sendActivitySpan(spanData) {
65596
65671
  if (!this._ws || this._ws.readyState !== WebSocket2.OPEN) return;
65597
- const pluginVersion = true ? "0.23.13" : "0.0.0-dev";
65672
+ const pluginVersion = true ? "0.23.17" : "0.0.0-dev";
65598
65673
  const agentName = this.config.agentName ?? "Agent";
65599
65674
  const resource = {
65600
65675
  "service.name": "agentvault-agent",
@@ -65939,13 +66014,20 @@ var init_channel = __esm2({
65939
66014
  * entry, then persist.
65940
66015
  */
65941
66016
  async _handleServerError(payload) {
65942
- if (payload?.detail !== "conversation deleted") return;
66017
+ const detail = payload?.detail;
66018
+ if (detail !== "conversation deleted" && detail !== "unknown group") return;
65943
66019
  const mlsGroupId = payload.group_id;
65944
66020
  if (!mlsGroupId) return;
65945
66021
  const groups = this._persisted?.mlsGroups;
65946
66022
  if (!groups) return;
65947
66023
  const gid = Object.keys(groups).find((k2) => groups[k2]?.mlsGroupId === mlsGroupId);
65948
66024
  if (!gid) return;
66025
+ if (detail === "unknown group") {
66026
+ const changed = await this._reconcileConversationGroups(gid, mlsGroupId);
66027
+ if (changed) await this._retryAfterReconcile(mlsGroupId);
66028
+ return;
66029
+ }
66030
+ this._retryCandidates.delete(mlsGroupId);
65949
66031
  try {
65950
66032
  await deleteMlsState(this.config.dataDir, mlsGroupId);
65951
66033
  } catch {
@@ -65958,6 +66040,120 @@ var init_channel = __esm2({
65958
66040
  );
65959
66041
  this.emit("dm_group_pruned", { conversationGroupId: gid, mlsGroupId });
65960
66042
  }
66043
+ /**
66044
+ * Re-resolve the conversation→group map from the server (#719).
66045
+ *
66046
+ * `_persisted.conversationGroupIds` and `_persisted.groupId` are built ONCE,
66047
+ * in `_activate()`, from the activation response. `_activate` runs at
66048
+ * enrollment and never again, so when the owner deletes a conversation and a
66049
+ * new one is created the agent keeps addressing the OLD group forever.
66050
+ * Nothing reconciled it — not restart (the stale map is reloaded from disk),
66051
+ * not reconnect, not the heartbeat.
66052
+ *
66053
+ * wren, 2026-07-31: `groupId` 53798dd2 had no `mls_groups` row while three
66054
+ * ACTIVE conversations sat on 99faf707. Inbound worked, every reply was
66055
+ * refused `unknown group` and dropped. Silent since 2026-07-02 — the #460
66056
+ * fan-out was spraying all 12 known groups, so a live one always got hit.
66057
+ *
66058
+ * Best-effort and conservative, mirroring `_refreshRoomRoster`: on any
66059
+ * failure the existing map is kept. The prune is NARROW — only the group the
66060
+ * server named, and only once the server has positively reported an active
66061
+ * conversation on some other group. Absence of data is never treated as
66062
+ * evidence of deletion.
66063
+ *
66064
+ * Returns TRUE only when the PRIMARY group pointer actually moved, which is
66065
+ * the signal #732's resend is gated on. Deliberately narrower than "anything
66066
+ * in the map changed": if some other group went stale we cannot tell which
66067
+ * conversation replaced it, and delivering a reply to the wrong counterparty
66068
+ * is strictly worse than losing it.
66069
+ */
66070
+ async _reconcileConversationGroups(staleGid, staleMlsGroupId) {
66071
+ const jwt22 = this._deviceJwt ?? this._persisted?.deviceJwt;
66072
+ if (!jwt22 || !this._persisted) return false;
66073
+ let rows;
66074
+ try {
66075
+ const res = await fetch(`${this.config.apiUrl}/api/v1/conversations`, {
66076
+ headers: { Authorization: `Bearer ${jwt22}` }
66077
+ });
66078
+ if (!res.ok) return false;
66079
+ rows = await res.json();
66080
+ } catch (err) {
66081
+ console.warn("[SecureChannel] Conversation re-resolve failed:", err);
66082
+ return false;
66083
+ }
66084
+ if (!Array.isArray(rows)) return false;
66085
+ const mine = rows.filter(
66086
+ (r22) => r22?.agent_device_id === this._deviceId && r22?.status === "active" && typeof r22?.group_id === "string" && typeof r22?.id === "string"
66087
+ );
66088
+ if (mine.length === 0) return false;
66089
+ const before = this._persisted.groupId;
66090
+ this._sessionGroupIds = new Map(mine.map((r22) => [r22.id, r22.group_id]));
66091
+ this._persisted.conversationGroupIds = Object.fromEntries(this._sessionGroupIds);
66092
+ const primary = mine.find((r22) => r22.id === this._persisted.primaryConversationId) ?? mine[0];
66093
+ this._persisted.groupId = primary.group_id;
66094
+ this._persisted.primaryConversationId = primary.id;
66095
+ const liveGroupIds = new Set(mine.map((r22) => r22.group_id));
66096
+ if (!liveGroupIds.has(staleGid)) {
66097
+ try {
66098
+ await deleteMlsState(this.config.dataDir, staleMlsGroupId);
66099
+ } catch {
66100
+ }
66101
+ this._mlsGroups.delete(`1to1-group:${staleGid}`);
66102
+ delete this._persisted.mlsGroups?.[staleGid];
66103
+ this.emit("dm_group_pruned", {
66104
+ conversationGroupId: staleGid,
66105
+ mlsGroupId: staleMlsGroupId
66106
+ });
66107
+ }
66108
+ await this._persistState();
66109
+ console.log(
66110
+ `[SecureChannel] Re-resolved conversation groups after 'unknown group' ${staleMlsGroupId.slice(0, 8)}: primary group ${String(before).slice(0, 8)} \u2192 ${this._persisted.groupId.slice(0, 8)} (${mine.length} active conversation(s))`
66111
+ );
66112
+ return this._persisted.groupId !== before && before === staleGid;
66113
+ }
66114
+ /**
66115
+ * Resend the message that a now-reconciled `unknown group` refusal killed (#732).
66116
+ *
66117
+ * Called ONLY after `_reconcileConversationGroups` reported that the primary
66118
+ * group pointer moved off the refused group, so there is exactly one sensible
66119
+ * destination: the conversation the reconcile settled on.
66120
+ *
66121
+ * The candidate is consumed BEFORE the resend, not after — one attempt is the
66122
+ * bound, and that must hold even if the resend itself throws. A retry that
66123
+ * re-armed on failure would turn a persistent refusal into an infinite loop,
66124
+ * which is a worse failure than the dropped message it set out to fix.
66125
+ *
66126
+ * The resend is always TARGETED, never a broadcast: the original send may
66127
+ * have fanned out to several groups of which only one was refused, and
66128
+ * re-broadcasting would duplicate the message in all the others.
66129
+ */
66130
+ async _retryAfterReconcile(staleMlsGroupId) {
66131
+ const candidate = this._retryCandidates.get(staleMlsGroupId);
66132
+ this._retryCandidates.delete(staleMlsGroupId);
66133
+ if (!candidate) return;
66134
+ if (Date.now() - candidate.ts > RETRY_CANDIDATE_TTL_MS) {
66135
+ console.warn(
66136
+ `[deliver] delivery_result=LOST group=${staleMlsGroupId.slice(0, 8)} reason=retry-window-expired`
66137
+ );
66138
+ return;
66139
+ }
66140
+ const conversationId = this._persisted?.primaryConversationId;
66141
+ if (!conversationId) return;
66142
+ try {
66143
+ await this.send(candidate.plaintext, {
66144
+ ...candidate.options,
66145
+ conversationId,
66146
+ isResend: true
66147
+ });
66148
+ console.log(
66149
+ `[deliver] delivery_result=RESENT group=${staleMlsGroupId.slice(0, 8)} \u2192 ${String(this._persisted?.groupId).slice(0, 8)} conv=${conversationId.slice(0, 8)}`
66150
+ );
66151
+ } catch (err) {
66152
+ console.error(
66153
+ `[deliver] delivery_result=LOST group=${staleMlsGroupId.slice(0, 8)} reason=resend-failed detail=${err instanceof Error ? err.message : String(err)}`
66154
+ );
66155
+ }
66156
+ }
65961
66157
  /**
65962
66158
  * Return info for all joined rooms.
65963
66159
  */
@@ -66330,7 +66526,7 @@ var init_channel = __esm2({
66330
66526
  });
66331
66527
  }
66332
66528
  const targetLabel = resolved.kind === "owner" ? "owner" : `${resolved.kind}:${resolved.id?.slice(0, 8)}...`;
66333
- console.log(`[deliver] target=${targetLabel} content=${content.type} result=ok`);
66529
+ console.log(`[deliver] target=${targetLabel} content=${content.type} result=sent`);
66334
66530
  if (dedupeKey) {
66335
66531
  try {
66336
66532
  const now = Date.now();
@@ -67224,13 +67420,12 @@ var init_channel = __esm2({
67224
67420
  sessions,
67225
67421
  messageHistory: this._persisted.messageHistory ?? []
67226
67422
  };
67227
- if (conversations.length > 0) {
67228
- const firstConv = conversations[0];
67229
- if (firstConv.group_id) {
67230
- this._persisted.groupId = firstConv.group_id;
67423
+ if (primary) {
67424
+ if (primary.group_id) {
67425
+ this._persisted.groupId = primary.group_id;
67231
67426
  }
67232
- if (firstConv.default_topic_id) {
67233
- this._persisted.defaultTopicId = firstConv.default_topic_id;
67427
+ if (primary.default_topic_id) {
67428
+ this._persisted.defaultTopicId = primary.default_topic_id;
67234
67429
  }
67235
67430
  }
67236
67431
  this._sessionGroupIds = /* @__PURE__ */ new Map();
@@ -67292,7 +67487,13 @@ var init_channel = __esm2({
67292
67487
  this._ws = null;
67293
67488
  }
67294
67489
  this._setState("connecting");
67295
- await this._maybeReissueDeviceJwt();
67490
+ if (await this._maybeReissueDeviceJwt()) {
67491
+ console.warn(
67492
+ "[SecureChannel] credentials are terminally dead \u2014 aborting connect (no WS attempt)"
67493
+ );
67494
+ this._setState("error");
67495
+ return;
67496
+ }
67296
67497
  const wsUrl = this.config.apiUrl.replace(/^http/, "ws");
67297
67498
  const url22 = `${wsUrl}/api/v1/ws?token=${encodeURIComponent(this._deviceJwt)}&device_id=${this._deviceId}`;
67298
67499
  const ws = new WebSocket2(url22);
@@ -67333,7 +67534,7 @@ var init_channel = __esm2({
67333
67534
  agentVersion: this.config.agentVersion ?? "0.0.0",
67334
67535
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67335
67536
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67336
- pluginVersion: true ? "0.23.13" : "0.0.0-dev"
67537
+ pluginVersion: true ? "0.23.17" : "0.0.0-dev"
67337
67538
  });
67338
67539
  this._telemetryReporter.startAutoFlush(3e4);
67339
67540
  }
@@ -67402,7 +67603,12 @@ var init_channel = __esm2({
67402
67603
  try {
67403
67604
  const data = JSON.parse(raw.toString());
67404
67605
  if (data.event && data.event !== "ping" && data.event !== "typing") {
67405
- console.log(`[SecureChannel] WS event: ${data.event} conv=${(data.conversation_id || data.data?.conversation_id || "").toString().slice(0, 8)}`);
67606
+ const d22 = data.data ?? {};
67607
+ const conv = (data.conversation_id || d22.conversation_id || "").toString();
67608
+ const parts = [`conv=${conv.slice(0, 8)}`];
67609
+ if (d22.group_id) parts.push(`group=${String(d22.group_id).slice(0, 8)}`);
67610
+ if (d22.detail) parts.push(`detail=${d22.detail}`);
67611
+ console.log(`[SecureChannel] WS event: ${data.event} ${parts.join(" ")}`);
67406
67612
  }
67407
67613
  if (data.event === "ping") {
67408
67614
  ws.send(JSON.stringify({ event: "pong" }));
@@ -67651,7 +67857,7 @@ var init_channel = __esm2({
67651
67857
  agentVersion: this.config.agentVersion ?? "0.0.0",
67652
67858
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67653
67859
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67654
- pluginVersion: true ? "0.23.13" : "0.0.0-dev"
67860
+ pluginVersion: true ? "0.23.17" : "0.0.0-dev"
67655
67861
  });
67656
67862
  this._telemetryReporter.startAutoFlush(3e4);
67657
67863
  }
@@ -67960,6 +68166,10 @@ var init_channel = __esm2({
67960
68166
  }
67961
68167
  if (data.event === "error") {
67962
68168
  const detail = data.data?.detail || data.detail || "Unknown server error";
68169
+ const rejectedGroup = String(data.data?.group_id ?? "").slice(0, 8);
68170
+ console.error(
68171
+ `[deliver] delivery_result=REJECTED group=${rejectedGroup || "unknown"} detail=${detail}`
68172
+ );
67963
68173
  console.error(`[SecureChannel] Server error: ${detail}`);
67964
68174
  await this._handleServerError(data.data || data);
67965
68175
  this.emit("error", new Error(`Server: ${detail}`));
@@ -69046,10 +69256,10 @@ ${messageText}`;
69046
69256
  await mlsGroup.processCommit(commitBytes);
69047
69257
  await saveMlsState(this.config.dataDir, groupId, JSON.stringify(mlsGroup.exportState()));
69048
69258
  console.log(`[SecureChannel] MLS commit processed for room ${roomId.slice(0, 8)} (epoch=${mlsGroup.epoch})`);
69259
+ this._mlsCommitFailCounts.delete(roomId);
69049
69260
  } catch (err) {
69050
69261
  await this._onCommitFailure(roomId, groupId, err, `room ${roomId.slice(0, 8)}`);
69051
69262
  }
69052
- this._mlsCommitFailCounts.delete(roomId);
69053
69263
  } else {
69054
69264
  this._bufferMlsCommit(groupId, epoch, data);
69055
69265
  console.log(`[SecureChannel] Buffered MLS commit for room ${roomId.slice(0, 8)} (epoch=${epoch}, group not initialized)`);
@@ -69066,10 +69276,10 @@ ${messageText}`;
69066
69276
  await mlsGroup.processCommit(commitBytes);
69067
69277
  await saveMlsState(this.config.dataDir, groupId, JSON.stringify(mlsGroup.exportState()));
69068
69278
  console.log(`[SecureChannel] MLS commit processed for A2A ${chId.slice(0, 8)} (epoch=${mlsGroup.epoch})`);
69279
+ this._mlsCommitFailCounts.delete(`a2a:${chId}`);
69069
69280
  } catch (err) {
69070
69281
  await this._onCommitFailure(`a2a:${chId}`, groupId, err, `A2A ${chId.slice(0, 8)}`);
69071
69282
  }
69072
- this._mlsCommitFailCounts.delete(`a2a:${chId}`);
69073
69283
  } else {
69074
69284
  this._bufferMlsCommit(groupId, epoch, data);
69075
69285
  console.log(`[SecureChannel] Buffered MLS commit for A2A ${chId.slice(0, 8)} (epoch=${epoch}, group not initialized)`);
@@ -69314,7 +69524,7 @@ ${messageText}`;
69314
69524
  if (!this._pendingKpBundles.some((b22) => idOf(b22) === id)) {
69315
69525
  this._pendingKpBundles.push(kp);
69316
69526
  }
69317
- while (this._pendingKpBundles.length > _SecureChannel._KP_POOL_TARGET) {
69527
+ while (this._pendingKpBundles.length > _SecureChannel._KP_POOL_RETENTION) {
69318
69528
  this._pendingKpBundles.shift();
69319
69529
  }
69320
69530
  }
@@ -70939,7 +71149,7 @@ ${messageText}`;
70939
71149
  return;
70940
71150
  }
70941
71151
  this._authFailedThisSession = true;
70942
- const authReason = data?.reason === "device_jwt_expired" ? "device_jwt_expired" : "device_revoked";
71152
+ const authReason = data?.reason ?? "device_revoked";
70943
71153
  console.warn(
70944
71154
  `[SecureChannel] connection_rejected (reason=${data?.reason ?? "unknown"}, retryable=false) \u2014 terminal; surfacing auth_failed`
70945
71155
  );
@@ -71103,6 +71313,14 @@ function terminalReenrollMessage(agentName, reason) {
71103
71313
  const who = agentName ? `[${agentName}] ` : "";
71104
71314
  return `${who}device is no longer valid (${reason}) \u2014 it was revoked or replaced. Re-enroll with a fresh token from AgentVault; the old credentials are dead. This agent will stop reconnecting.`;
71105
71315
  }
71316
+ function formatChannelError(err) {
71317
+ const msg = String(err);
71318
+ const rejection = /Server: (.+)$/.exec(msg);
71319
+ if (rejection) {
71320
+ return `[AgentVault] DELIVERY FAILED \u2014 the server refused this frame: ${rejection[1]}. The connection survives; this message was dropped and nothing retries it.`;
71321
+ }
71322
+ return `[AgentVault] channel error (non-fatal to the connection): ${msg}`;
71323
+ }
71106
71324
  function attachLifecycle(channel, opts = {}) {
71107
71325
  const log = opts.log ?? (() => {
71108
71326
  });
@@ -71303,7 +71521,7 @@ var init_openclaw_plugin = __esm2({
71303
71521
  });
71304
71522
  _channels.set(account.accountId, channel);
71305
71523
  channel.on("error", (err) => {
71306
- _log?.(`[AgentVault] channel error (non-fatal): ${String(err)}`);
71524
+ _log?.(formatChannelError(err));
71307
71525
  });
71308
71526
  attachLifecycle(channel, {
71309
71527
  agentName: account.agentName,
@@ -97500,7 +97718,7 @@ var init_index = __esm2({
97500
97718
  init_skill_invoker();
97501
97719
  await init_skill_telemetry();
97502
97720
  await init_policy_enforcer();
97503
- VERSION = true ? "0.23.13" : "0.0.0-dev";
97721
+ VERSION = true ? "0.23.17" : "0.0.0-dev";
97504
97722
  }
97505
97723
  });
97506
97724
  await init_index();
@@ -133764,7 +133982,7 @@ async function main() {
133764
133982
  "[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"
133765
133983
  );
133766
133984
  }
133767
- console.error(`[bridge] version: ${true ? "0.7.3" : "dev"}`);
133985
+ console.error(`[bridge] version: ${true ? "0.7.5" : "dev"}`);
133768
133986
  console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
133769
133987
  console.error(`[bridge] workspace: ${cfg.workspaceDir} \xB7 permissions: ${cfg.permissionMode} \xB7 enclave dir fenced`);
133770
133988
  if (cfg.armRoom) {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@agentvault/claude-bridge",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "type": "module",
5
- "description": "AgentVault Claude Bridge daemon for bridging a Claude agent into secure E2E-encrypted AgentVault 1:1 direct messages and rooms.",
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",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {