@agentvault/claude-bridge 0.7.4 → 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 +222 -24
  2. package/package.json +1 -1
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();
@@ -64772,6 +64776,20 @@ var init_channel = __esm2({
64772
64776
  * single delivery. In-memory only (short window; no cross-restart persistence needed).
64773
64777
  */
64774
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();
64775
64793
  _scanEngine = null;
64776
64794
  _scanRuleSetVersion = 0;
64777
64795
  _telemetryReporter = null;
@@ -64871,13 +64889,21 @@ var init_channel = __esm2({
64871
64889
  * Non-fatal: a transient network error or 5xx keeps the current JWT and
64872
64890
  * relies on the next reconnect. Terminal failures (401/403) surface via
64873
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.
64874
64900
  */
64875
64901
  async _maybeReissueDeviceJwt() {
64876
64902
  const jwt22 = this._persisted?.deviceJwt ?? this._deviceJwt;
64877
- if (!jwt22) return;
64903
+ if (!jwt22) return false;
64878
64904
  const exp = this._decodeJwtExp(jwt22);
64879
64905
  const nowSec = Math.floor(Date.now() / 1e3);
64880
- if (exp - nowSec >= 30 * 86400) return;
64906
+ if (exp - nowSec >= 30 * 86400) return false;
64881
64907
  try {
64882
64908
  const resp = await fetch(`${this.config.apiUrl}/api/v1/auth/reissue`, {
64883
64909
  method: "POST",
@@ -64897,7 +64923,7 @@ var init_channel = __esm2({
64897
64923
  } else {
64898
64924
  console.warn("[SecureChannel] reissue returned 200 but no device_jwt in response");
64899
64925
  }
64900
- return;
64926
+ return false;
64901
64927
  }
64902
64928
  if (resp.status === 401 || resp.status === 403) {
64903
64929
  console.warn(`[SecureChannel] reissue rejected (${resp.status}); credentials need attention`);
@@ -64905,12 +64931,13 @@ var init_channel = __esm2({
64905
64931
  const reason = resp.status === 401 ? "device_jwt_expired" : "device_revoked";
64906
64932
  await this._postAuthFailedToBackend(reason);
64907
64933
  this.emit("auth_failed", { reason });
64908
- return;
64934
+ return true;
64909
64935
  }
64910
64936
  console.warn(`[SecureChannel] reissue ${resp.status}; keeping current jwt`);
64911
64937
  } catch (err) {
64912
64938
  console.warn("[SecureChannel] reissue network error; keeping current jwt:", err);
64913
64939
  }
64940
+ return false;
64914
64941
  }
64915
64942
  /**
64916
64943
  * Wraps `fetch` for authenticated AV REST endpoints with reactive device-JWT
@@ -65396,7 +65423,9 @@ var init_channel = __esm2({
65396
65423
  }
65397
65424
  scanStatus = scanResult.status;
65398
65425
  }
65399
- this._appendHistory("agent", plaintext, topicId);
65426
+ if (!options?.isResend) {
65427
+ this._appendHistory("agent", plaintext, topicId);
65428
+ }
65400
65429
  const roomConvIds = /* @__PURE__ */ new Set();
65401
65430
  if (this._persisted?.rooms) {
65402
65431
  for (const room of Object.values(this._persisted.rooms)) {
@@ -65410,6 +65439,7 @@ var init_channel = __esm2({
65410
65439
  let sentCount = 0;
65411
65440
  const pendingWsSends = [];
65412
65441
  const sentSharedGroupIds = /* @__PURE__ */ new Set();
65442
+ const addressedMlsGroupIds = [];
65413
65443
  if (this._persisted?.mlsGroups && this._state === "ready" && this._ws) {
65414
65444
  const targetSharedGid = targetConvId ? this._sessionGroupIds?.get(targetConvId) : void 0;
65415
65445
  for (const [gid, entry] of Object.entries(this._persisted.mlsGroups)) {
@@ -65435,6 +65465,7 @@ var init_channel = __esm2({
65435
65465
  if (this._persisted?.hubAddress) payload.hub_address = this._persisted.hubAddress;
65436
65466
  if (this._persisted?.hubId) payload.sender_hub_id = this._persisted.hubId;
65437
65467
  pendingWsSends.push(JSON.stringify({ event: "message_mls", data: payload }));
65468
+ addressedMlsGroupIds.push(entry.mlsGroupId);
65438
65469
  sentSharedGroupIds.add(gid);
65439
65470
  sentCount++;
65440
65471
  console.log(`[SecureChannel] Shared MLS group send for group ${gid.slice(0, 8)} (${entry.mlsGroupId.slice(0, 8)})`);
@@ -65487,6 +65518,7 @@ var init_channel = __esm2({
65487
65518
  data: payload
65488
65519
  })
65489
65520
  );
65521
+ addressedMlsGroupIds.push(mlsGroupId);
65490
65522
  if (convGroupId) sentSharedGroupIds.add(convGroupId);
65491
65523
  } else {
65492
65524
  const encrypted = session.ratchet.encrypt(plaintext);
@@ -65572,6 +65604,7 @@ var init_channel = __esm2({
65572
65604
  if (this._persisted?.hubAddress) payload.hub_address = this._persisted.hubAddress;
65573
65605
  if (this._persisted?.hubId) payload.sender_hub_id = this._persisted.hubId;
65574
65606
  pendingWsSends.push(JSON.stringify({ event: "message_mls", data: payload }));
65607
+ addressedMlsGroupIds.push(resolvedMlsGroupId);
65575
65608
  sentCount++;
65576
65609
  if (mlsOnlyConvGroupId) sentSharedGroupIds.add(mlsOnlyConvGroupId);
65577
65610
  console.log(`[SecureChannel] MLS-only send for conv ${mlsConvId.slice(0, 8)} (no DR session)`);
@@ -65587,6 +65620,28 @@ var init_channel = __esm2({
65587
65620
  for (const frame of pendingWsSends) {
65588
65621
  this._ws.send(frame);
65589
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
+ }
65590
65645
  }
65591
65646
  /**
65592
65647
  * Send a typing indicator to all owner devices.
@@ -65614,7 +65669,7 @@ var init_channel = __esm2({
65614
65669
  */
65615
65670
  sendActivitySpan(spanData) {
65616
65671
  if (!this._ws || this._ws.readyState !== WebSocket2.OPEN) return;
65617
- const pluginVersion = true ? "0.23.14" : "0.0.0-dev";
65672
+ const pluginVersion = true ? "0.23.17" : "0.0.0-dev";
65618
65673
  const agentName = this.config.agentName ?? "Agent";
65619
65674
  const resource = {
65620
65675
  "service.name": "agentvault-agent",
@@ -65959,13 +66014,20 @@ var init_channel = __esm2({
65959
66014
  * entry, then persist.
65960
66015
  */
65961
66016
  async _handleServerError(payload) {
65962
- if (payload?.detail !== "conversation deleted") return;
66017
+ const detail = payload?.detail;
66018
+ if (detail !== "conversation deleted" && detail !== "unknown group") return;
65963
66019
  const mlsGroupId = payload.group_id;
65964
66020
  if (!mlsGroupId) return;
65965
66021
  const groups = this._persisted?.mlsGroups;
65966
66022
  if (!groups) return;
65967
66023
  const gid = Object.keys(groups).find((k2) => groups[k2]?.mlsGroupId === mlsGroupId);
65968
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);
65969
66031
  try {
65970
66032
  await deleteMlsState(this.config.dataDir, mlsGroupId);
65971
66033
  } catch {
@@ -65978,6 +66040,120 @@ var init_channel = __esm2({
65978
66040
  );
65979
66041
  this.emit("dm_group_pruned", { conversationGroupId: gid, mlsGroupId });
65980
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
+ }
65981
66157
  /**
65982
66158
  * Return info for all joined rooms.
65983
66159
  */
@@ -66350,7 +66526,7 @@ var init_channel = __esm2({
66350
66526
  });
66351
66527
  }
66352
66528
  const targetLabel = resolved.kind === "owner" ? "owner" : `${resolved.kind}:${resolved.id?.slice(0, 8)}...`;
66353
- console.log(`[deliver] target=${targetLabel} content=${content.type} result=ok`);
66529
+ console.log(`[deliver] target=${targetLabel} content=${content.type} result=sent`);
66354
66530
  if (dedupeKey) {
66355
66531
  try {
66356
66532
  const now = Date.now();
@@ -67244,13 +67420,12 @@ var init_channel = __esm2({
67244
67420
  sessions,
67245
67421
  messageHistory: this._persisted.messageHistory ?? []
67246
67422
  };
67247
- if (conversations.length > 0) {
67248
- const firstConv = conversations[0];
67249
- if (firstConv.group_id) {
67250
- this._persisted.groupId = firstConv.group_id;
67423
+ if (primary) {
67424
+ if (primary.group_id) {
67425
+ this._persisted.groupId = primary.group_id;
67251
67426
  }
67252
- if (firstConv.default_topic_id) {
67253
- this._persisted.defaultTopicId = firstConv.default_topic_id;
67427
+ if (primary.default_topic_id) {
67428
+ this._persisted.defaultTopicId = primary.default_topic_id;
67254
67429
  }
67255
67430
  }
67256
67431
  this._sessionGroupIds = /* @__PURE__ */ new Map();
@@ -67312,7 +67487,13 @@ var init_channel = __esm2({
67312
67487
  this._ws = null;
67313
67488
  }
67314
67489
  this._setState("connecting");
67315
- 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
+ }
67316
67497
  const wsUrl = this.config.apiUrl.replace(/^http/, "ws");
67317
67498
  const url22 = `${wsUrl}/api/v1/ws?token=${encodeURIComponent(this._deviceJwt)}&device_id=${this._deviceId}`;
67318
67499
  const ws = new WebSocket2(url22);
@@ -67353,7 +67534,7 @@ var init_channel = __esm2({
67353
67534
  agentVersion: this.config.agentVersion ?? "0.0.0",
67354
67535
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67355
67536
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67356
- pluginVersion: true ? "0.23.14" : "0.0.0-dev"
67537
+ pluginVersion: true ? "0.23.17" : "0.0.0-dev"
67357
67538
  });
67358
67539
  this._telemetryReporter.startAutoFlush(3e4);
67359
67540
  }
@@ -67422,7 +67603,12 @@ var init_channel = __esm2({
67422
67603
  try {
67423
67604
  const data = JSON.parse(raw.toString());
67424
67605
  if (data.event && data.event !== "ping" && data.event !== "typing") {
67425
- 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(" ")}`);
67426
67612
  }
67427
67613
  if (data.event === "ping") {
67428
67614
  ws.send(JSON.stringify({ event: "pong" }));
@@ -67671,7 +67857,7 @@ var init_channel = __esm2({
67671
67857
  agentVersion: this.config.agentVersion ?? "0.0.0",
67672
67858
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67673
67859
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67674
- pluginVersion: true ? "0.23.14" : "0.0.0-dev"
67860
+ pluginVersion: true ? "0.23.17" : "0.0.0-dev"
67675
67861
  });
67676
67862
  this._telemetryReporter.startAutoFlush(3e4);
67677
67863
  }
@@ -67980,6 +68166,10 @@ var init_channel = __esm2({
67980
68166
  }
67981
68167
  if (data.event === "error") {
67982
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
+ );
67983
68173
  console.error(`[SecureChannel] Server error: ${detail}`);
67984
68174
  await this._handleServerError(data.data || data);
67985
68175
  this.emit("error", new Error(`Server: ${detail}`));
@@ -69066,10 +69256,10 @@ ${messageText}`;
69066
69256
  await mlsGroup.processCommit(commitBytes);
69067
69257
  await saveMlsState(this.config.dataDir, groupId, JSON.stringify(mlsGroup.exportState()));
69068
69258
  console.log(`[SecureChannel] MLS commit processed for room ${roomId.slice(0, 8)} (epoch=${mlsGroup.epoch})`);
69259
+ this._mlsCommitFailCounts.delete(roomId);
69069
69260
  } catch (err) {
69070
69261
  await this._onCommitFailure(roomId, groupId, err, `room ${roomId.slice(0, 8)}`);
69071
69262
  }
69072
- this._mlsCommitFailCounts.delete(roomId);
69073
69263
  } else {
69074
69264
  this._bufferMlsCommit(groupId, epoch, data);
69075
69265
  console.log(`[SecureChannel] Buffered MLS commit for room ${roomId.slice(0, 8)} (epoch=${epoch}, group not initialized)`);
@@ -69086,10 +69276,10 @@ ${messageText}`;
69086
69276
  await mlsGroup.processCommit(commitBytes);
69087
69277
  await saveMlsState(this.config.dataDir, groupId, JSON.stringify(mlsGroup.exportState()));
69088
69278
  console.log(`[SecureChannel] MLS commit processed for A2A ${chId.slice(0, 8)} (epoch=${mlsGroup.epoch})`);
69279
+ this._mlsCommitFailCounts.delete(`a2a:${chId}`);
69089
69280
  } catch (err) {
69090
69281
  await this._onCommitFailure(`a2a:${chId}`, groupId, err, `A2A ${chId.slice(0, 8)}`);
69091
69282
  }
69092
- this._mlsCommitFailCounts.delete(`a2a:${chId}`);
69093
69283
  } else {
69094
69284
  this._bufferMlsCommit(groupId, epoch, data);
69095
69285
  console.log(`[SecureChannel] Buffered MLS commit for A2A ${chId.slice(0, 8)} (epoch=${epoch}, group not initialized)`);
@@ -70959,7 +71149,7 @@ ${messageText}`;
70959
71149
  return;
70960
71150
  }
70961
71151
  this._authFailedThisSession = true;
70962
- const authReason = data?.reason === "device_jwt_expired" ? "device_jwt_expired" : "device_revoked";
71152
+ const authReason = data?.reason ?? "device_revoked";
70963
71153
  console.warn(
70964
71154
  `[SecureChannel] connection_rejected (reason=${data?.reason ?? "unknown"}, retryable=false) \u2014 terminal; surfacing auth_failed`
70965
71155
  );
@@ -71123,6 +71313,14 @@ function terminalReenrollMessage(agentName, reason) {
71123
71313
  const who = agentName ? `[${agentName}] ` : "";
71124
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.`;
71125
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
+ }
71126
71324
  function attachLifecycle(channel, opts = {}) {
71127
71325
  const log = opts.log ?? (() => {
71128
71326
  });
@@ -71323,7 +71521,7 @@ var init_openclaw_plugin = __esm2({
71323
71521
  });
71324
71522
  _channels.set(account.accountId, channel);
71325
71523
  channel.on("error", (err) => {
71326
- _log?.(`[AgentVault] channel error (non-fatal): ${String(err)}`);
71524
+ _log?.(formatChannelError(err));
71327
71525
  });
71328
71526
  attachLifecycle(channel, {
71329
71527
  agentName: account.agentName,
@@ -97520,7 +97718,7 @@ var init_index = __esm2({
97520
97718
  init_skill_invoker();
97521
97719
  await init_skill_telemetry();
97522
97720
  await init_policy_enforcer();
97523
- VERSION = true ? "0.23.14" : "0.0.0-dev";
97721
+ VERSION = true ? "0.23.17" : "0.0.0-dev";
97524
97722
  }
97525
97723
  });
97526
97724
  await init_index();
@@ -133784,7 +133982,7 @@ async function main() {
133784
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"
133785
133983
  );
133786
133984
  }
133787
- console.error(`[bridge] version: ${true ? "0.7.4" : "dev"}`);
133985
+ console.error(`[bridge] version: ${true ? "0.7.5" : "dev"}`);
133788
133986
  console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
133789
133987
  console.error(`[bridge] workspace: ${cfg.workspaceDir} \xB7 permissions: ${cfg.permissionMode} \xB7 enclave dir fenced`);
133790
133988
  if (cfg.armRoom) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentvault/claude-bridge",
3
- "version": "0.7.4",
3
+ "version": "0.7.5",
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",