@agentvault/agentvault 0.23.11 → 0.23.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.
package/dist/index.js CHANGED
@@ -63912,6 +63912,10 @@ var init_channel = __esm({
63912
63912
  _sessionGroupIds = /* @__PURE__ */ new Map();
63913
63913
  /** Consecutive MLS decrypt failure count per group key for epoch recovery. */
63914
63914
  _mlsDecryptFailCounts = /* @__PURE__ */ new Map();
63915
+ /** #474: per-group consecutive processCommit failures. Mirrors the decrypt
63916
+ * counter above — a commit that fails is divergence, and divergence must
63917
+ * self-heal rather than be logged and dropped. */
63918
+ _mlsCommitFailCounts = /* @__PURE__ */ new Map();
63915
63919
  static MAX_MLS_DECRYPT_FAILS = 3;
63916
63920
  /** Cached MLS KeyPackage bundle for this device (regenerated on each connect). */
63917
63921
  _mlsKeyPackage = null;
@@ -64792,7 +64796,7 @@ var init_channel = __esm({
64792
64796
  */
64793
64797
  sendActivitySpan(spanData) {
64794
64798
  if (!this._ws || this._ws.readyState !== WebSocket.OPEN) return;
64795
- const pluginVersion = true ? "0.23.11" : "0.0.0-dev";
64799
+ const pluginVersion = true ? "0.23.12" : "0.0.0-dev";
64796
64800
  const agentName = this.config.agentName ?? "Agent";
64797
64801
  const resource = {
64798
64802
  "service.name": "agentvault-agent",
@@ -66531,7 +66535,7 @@ var init_channel = __esm({
66531
66535
  agentVersion: this.config.agentVersion ?? "0.0.0",
66532
66536
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
66533
66537
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
66534
- pluginVersion: true ? "0.23.11" : "0.0.0-dev"
66538
+ pluginVersion: true ? "0.23.12" : "0.0.0-dev"
66535
66539
  });
66536
66540
  this._telemetryReporter.startAutoFlush(3e4);
66537
66541
  }
@@ -66849,7 +66853,7 @@ var init_channel = __esm({
66849
66853
  agentVersion: this.config.agentVersion ?? "0.0.0",
66850
66854
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
66851
66855
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
66852
- pluginVersion: true ? "0.23.11" : "0.0.0-dev"
66856
+ pluginVersion: true ? "0.23.12" : "0.0.0-dev"
66853
66857
  });
66854
66858
  this._telemetryReporter.startAutoFlush(3e4);
66855
66859
  }
@@ -68245,14 +68249,9 @@ ${messageText}`;
68245
68249
  await saveMlsState(this.config.dataDir, groupId, JSON.stringify(mlsGroup.exportState()));
68246
68250
  console.log(`[SecureChannel] MLS commit processed for room ${roomId.slice(0, 8)} (epoch=${mlsGroup.epoch})`);
68247
68251
  } catch (err) {
68248
- const isOldEpoch = err?.message?.includes("epoch") || err?.message?.includes("old") || err?.message?.includes("Validation");
68249
- if (isOldEpoch) {
68250
- console.log(`[SecureChannel] Discarding old-epoch commit for room ${roomId.slice(0, 8)}`);
68251
- } else {
68252
- console.error(`[SecureChannel] MLS commit failed for room ${roomId.slice(0, 8)}:`, err);
68253
- throw err;
68254
- }
68252
+ await this._onCommitFailure(roomId, groupId, err, `room ${roomId.slice(0, 8)}`);
68255
68253
  }
68254
+ this._mlsCommitFailCounts.delete(roomId);
68256
68255
  } else {
68257
68256
  this._bufferMlsCommit(groupId, epoch, data);
68258
68257
  console.log(`[SecureChannel] Buffered MLS commit for room ${roomId.slice(0, 8)} (epoch=${epoch}, group not initialized)`);
@@ -68270,14 +68269,9 @@ ${messageText}`;
68270
68269
  await saveMlsState(this.config.dataDir, groupId, JSON.stringify(mlsGroup.exportState()));
68271
68270
  console.log(`[SecureChannel] MLS commit processed for A2A ${chId.slice(0, 8)} (epoch=${mlsGroup.epoch})`);
68272
68271
  } catch (err) {
68273
- const isOldEpoch = err?.message?.includes("epoch") || err?.message?.includes("old") || err?.message?.includes("Validation");
68274
- if (isOldEpoch) {
68275
- console.log(`[SecureChannel] Discarding old-epoch commit for A2A ${chId.slice(0, 8)}`);
68276
- } else {
68277
- console.error(`[SecureChannel] MLS commit failed for A2A ${chId.slice(0, 8)}:`, err);
68278
- throw err;
68279
- }
68272
+ await this._onCommitFailure(`a2a:${chId}`, groupId, err, `A2A ${chId.slice(0, 8)}`);
68280
68273
  }
68274
+ this._mlsCommitFailCounts.delete(`a2a:${chId}`);
68281
68275
  } else {
68282
68276
  this._bufferMlsCommit(groupId, epoch, data);
68283
68277
  console.log(`[SecureChannel] Buffered MLS commit for A2A ${chId.slice(0, 8)} (epoch=${epoch}, group not initialized)`);
@@ -68334,6 +68328,52 @@ ${messageText}`;
68334
68328
  * sends us one. Deduped to a 30s window per group so a burst of undecryptable
68335
68329
  * rows doesn't spam request-welcome. Fire-and-forget.
68336
68330
  */
68331
+ /**
68332
+ * #474 — a failed `processCommit` is DIVERGENCE, and divergence must
68333
+ * self-heal. It previously did neither: the room stayed broken forever.
68334
+ *
68335
+ * Two swallow paths existed, and only the second was ever reported:
68336
+ *
68337
+ * 1. `isOldEpoch` substring-matched the error text for "epoch" / "old" /
68338
+ * "Validation" and discarded deliberately. `ValidationError: Desired gen
68339
+ * in the past` contains "Validation", so the LOUDEST production failure
68340
+ * was filed as benign — 1,439 occurrences on one agent, all thrown away
68341
+ * by a string match.
68342
+ * 2. Everything else re-threw to the WS dispatcher, which logs and drops.
68343
+ *
68344
+ * Result: ~1,600 commit failures across 13 rooms and ZERO resyncs. Classifying
68345
+ * divergence by grepping an error message is the root of it — error text is
68346
+ * not a protocol, and the one case that must never be silent was the one that
68347
+ * happened to contain the magic word.
68348
+ *
68349
+ * So: no classification. COUNT, and after MAX_MLS_DECRYPT_FAILS consecutive
68350
+ * failures run the same ladder the decrypt path has used since #440 — replay
68351
+ * the missed commits via /sync, and fall back to re-join only if that cannot
68352
+ * catch up. Counting rather than resyncing on first failure matters: a
68353
+ * genuinely stale commit is normal, and resyncing on each one would have
68354
+ * fired ~1,600 Welcome requests.
68355
+ *
68356
+ * Never re-throws. The caller's only handler logs and drops, so throwing here
68357
+ * is indistinguishable from swallowing — it just moves the swallow one frame up.
68358
+ */
68359
+ async _onCommitFailure(groupKey, mlsGroupId, err, label) {
68360
+ const count = (this._mlsCommitFailCounts.get(groupKey) ?? 0) + 1;
68361
+ this._mlsCommitFailCounts.set(groupKey, count);
68362
+ console.error(
68363
+ `[SecureChannel] MLS commit failed (${count}/${_SecureChannel.MAX_MLS_DECRYPT_FAILS}) for ${label}:`,
68364
+ err
68365
+ );
68366
+ if (count < _SecureChannel.MAX_MLS_DECRYPT_FAILS) return;
68367
+ console.warn(`[SecureChannel] MLS commit resync triggered for ${label} \u2014 ${count} consecutive failures`);
68368
+ try {
68369
+ if (!await this._resyncOnDivergence(groupKey, mlsGroupId)) {
68370
+ await this._triggerMlsEpochRecovery(groupKey, mlsGroupId);
68371
+ }
68372
+ this._mlsCommitFailCounts.delete(groupKey);
68373
+ } catch (recoverErr) {
68374
+ console.error(`[SecureChannel] MLS commit resync FAILED for ${label}:`, recoverErr);
68375
+ }
68376
+ }
68337
68377
  async _requestWelcomeSelfHeal(groupId) {
68338
68378
  const now = Date.now();
68339
68379
  const last = this._welcomeSelfHealInflight.get(groupId) ?? 0;
@@ -69135,9 +69175,43 @@ ${messageText}`;
69135
69175
  const errMsg = addErr instanceof Error ? addErr.message : String(addErr);
69136
69176
  if (errMsg.includes("already in the group")) {
69137
69177
  console.log(`[SecureChannel] A2A member already in group \u2014 removing and re-adding ${req.requesting_device_id.slice(0, 8)}`);
69178
+ const preRemoveState = JSON.stringify(mlsGroup.exportState());
69179
+ let removeCommitPublished = false;
69138
69180
  try {
69139
69181
  const identityBytes = new TextEncoder().encode(req.requesting_device_id);
69140
- await mlsGroup.removeMember(identityBytes);
69182
+ const leafIndex = mlsGroup.memberLeafIndex(identityBytes);
69183
+ if (leafIndex === null) {
69184
+ console.log(`[SecureChannel] A2A re-add: no leaf index for ${req.requesting_device_id.slice(0, 8)} \u2014 cannot remove`);
69185
+ continue;
69186
+ }
69187
+ const removeResult = await mlsGroup.removeMember(leafIndex);
69188
+ try {
69189
+ const removeResp = await fetch(
69190
+ `${this.config.apiUrl}/api/v1/mls/groups/${chState.mlsGroupId}/distribute`,
69191
+ {
69192
+ method: "POST",
69193
+ headers: { Authorization: `Bearer ${this._deviceJwt}`, "Content-Type": "application/json" },
69194
+ body: JSON.stringify({
69195
+ group_id: chState.mlsGroupId,
69196
+ device_id: this._deviceId,
69197
+ commit: Buffer.from(removeResult.commit).toString("hex"),
69198
+ commit_epoch: Number(mlsGroup.epoch)
69199
+ })
69200
+ }
69201
+ );
69202
+ if (removeResp.ok) {
69203
+ removeCommitPublished = true;
69204
+ } else {
69205
+ const detail = await removeResp.text().catch(() => "");
69206
+ console.warn(`[SecureChannel] A2A remove-commit distribute failed (${removeResp.status}): ${detail}`);
69207
+ }
69208
+ } catch (distErr) {
69209
+ console.warn(`[SecureChannel] A2A remove-commit distribute network error:`, distErr);
69210
+ }
69211
+ if (!removeCommitPublished) {
69212
+ mlsGroup.importState(JSON.parse(preRemoveState));
69213
+ continue;
69214
+ }
69141
69215
  const kpRes2 = await fetch(
69142
69216
  `${this.config.apiUrl}/api/v1/mls/key-packages/batch?device_ids=${req.requesting_device_id}`,
69143
69217
  { headers: { Authorization: `Bearer ${this._deviceJwt}` } }
@@ -69161,6 +69235,12 @@ ${messageText}`;
69161
69235
  }
69162
69236
  } catch (readdErr) {
69163
69237
  console.log(`[SecureChannel] A2A remove+re-add FAILED: ${readdErr instanceof Error ? readdErr.message : String(readdErr)}`);
69238
+ if (!removeCommitPublished) {
69239
+ try {
69240
+ mlsGroup.importState(JSON.parse(preRemoveState));
69241
+ } catch {
69242
+ }
69243
+ }
69164
69244
  continue;
69165
69245
  }
69166
69246
  } else {
@@ -97064,7 +97144,7 @@ var init_index = __esm({
97064
97144
  init_skill_invoker();
97065
97145
  await init_skill_telemetry();
97066
97146
  await init_policy_enforcer();
97067
- VERSION = true ? "0.23.11" : "0.0.0-dev";
97147
+ VERSION = true ? "0.23.12" : "0.0.0-dev";
97068
97148
  }
97069
97149
  });
97070
97150
  await init_index();