@agentvault/agentvault 0.23.32 → 0.23.34

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
@@ -63266,11 +63266,11 @@ var init_mls_kp_pool = __esm({
63266
63266
 
63267
63267
  // src/client-version.ts
63268
63268
  function ownIdentity() {
63269
- const v2 = true ? "0.23.32" : FALLBACK;
63269
+ const v2 = true ? "0.23.34" : FALLBACK;
63270
63270
  return `${PACKAGE}@${v2}`;
63271
63271
  }
63272
63272
  function buildSha() {
63273
- const raw = true ? "9d4a053-dirty" : "";
63273
+ const raw = true ? "6b305ca-dirty" : "";
63274
63274
  const cleaned = (raw ?? "").trim();
63275
63275
  return !cleaned || cleaned === "unknown" ? null : cleaned;
63276
63276
  }
@@ -64199,7 +64199,7 @@ function _isSelfHubAddress(ownHubAddress, target) {
64199
64199
  const tgt = (target ?? "").trim().toLowerCase();
64200
64200
  return own.length > 0 && own === tgt;
64201
64201
  }
64202
- var ROOM_AGENT_TYPES, CREDENTIAL_MESSAGE_TYPES, CATCHUP_MESSAGE_TYPES, POLL_INTERVAL_MS, RECONNECT_BASE_MS, RECONNECT_MAX_MS, PENDING_POLL_INTERVAL_MS, OUTBOUND_DEDUPE_WINDOW_MS, OUTBOUND_DEDUPE_MAX_SUPPRESS_MS, OUTBOUND_DEDUPE_MAX, OUTBOUND_DEDUPE_TYPES, RETRY_CANDIDATE_TTL_MS, RETRY_CANDIDATE_MAX, SecureChannel;
64202
+ var ROOM_AGENT_TYPES, CREDENTIAL_MESSAGE_TYPES, CATCHUP_MESSAGE_TYPES, POLL_INTERVAL_MS, RECONNECT_BASE_MS, RECONNECT_MAX_MS, PENDING_POLL_INTERVAL_MS, OUTBOUND_DEDUPE_WINDOW_MS, OUTBOUND_DEDUPE_MAX_SUPPRESS_MS, OUTBOUND_DEDUPE_MAX, OUTBOUND_DEDUPE_TYPES, RETRY_CANDIDATE_TTL_MS, RETRY_CANDIDATE_MAX, ROOM_FULFIL_COOLDOWN_MS, SecureChannel;
64203
64203
  var init_channel = __esm({
64204
64204
  async "src/channel.ts"() {
64205
64205
  "use strict";
@@ -64244,6 +64244,7 @@ var init_channel = __esm({
64244
64244
  OUTBOUND_DEDUPE_TYPES = /* @__PURE__ */ new Set(["text", "status_alert", "artifact", "attachment"]);
64245
64245
  RETRY_CANDIDATE_TTL_MS = 6e4;
64246
64246
  RETRY_CANDIDATE_MAX = 32;
64247
+ ROOM_FULFIL_COOLDOWN_MS = 6e4;
64247
64248
  SecureChannel = class _SecureChannel extends EventEmitter {
64248
64249
  constructor(config2) {
64249
64250
  super();
@@ -64313,6 +64314,8 @@ var init_channel = __esm({
64313
64314
  _drDeliveryPulling = false;
64314
64315
  /** MLS group managers per room/conversation (roomId or conv:conversationId or 1to1-group:groupId -> MLSGroupManager) */
64315
64316
  _mlsGroups = /* @__PURE__ */ new Map();
64317
+ /** `${mlsGroupId}:${deviceId}` -> last re-admit ms (#1171). */
64318
+ _roomFulfilCooldown = /* @__PURE__ */ new Map();
64316
64319
  /** Maps conversationId -> conversation_group_id for shared 1:1 MLS groups */
64317
64320
  _sessionGroupIds = /* @__PURE__ */ new Map();
64318
64321
  /** Consecutive MLS decrypt failure count per group key for epoch recovery. */
@@ -65478,7 +65481,7 @@ var init_channel = __esm({
65478
65481
  */
65479
65482
  sendActivitySpan(spanData) {
65480
65483
  if (!this._ws || this._ws.readyState !== WebSocket.OPEN) return;
65481
- const pluginVersion = true ? "0.23.32" : "0.0.0-dev";
65484
+ const pluginVersion = true ? "0.23.34" : "0.0.0-dev";
65482
65485
  const agentName = this.config.agentName ?? "Agent";
65483
65486
  const resource = {
65484
65487
  "service.name": "agentvault-agent",
@@ -67416,7 +67419,7 @@ var init_channel = __esm({
67416
67419
  agentVersion: this.config.agentVersion ?? "0.0.0",
67417
67420
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67418
67421
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67419
- pluginVersion: true ? "0.23.32" : "0.0.0-dev"
67422
+ pluginVersion: true ? "0.23.34" : "0.0.0-dev"
67420
67423
  });
67421
67424
  this._telemetryReporter.startAutoFlush(3e4);
67422
67425
  }
@@ -67740,7 +67743,7 @@ var init_channel = __esm({
67740
67743
  agentVersion: this.config.agentVersion ?? "0.0.0",
67741
67744
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67742
67745
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67743
- pluginVersion: true ? "0.23.32" : "0.0.0-dev"
67746
+ pluginVersion: true ? "0.23.34" : "0.0.0-dev"
67744
67747
  });
67745
67748
  this._telemetryReporter.startAutoFlush(3e4);
67746
67749
  }
@@ -68144,6 +68147,7 @@ var init_channel = __esm({
68144
68147
  try {
68145
68148
  const cipherBytes = new Uint8Array(Buffer.from(data.payload, "hex"));
68146
68149
  const result = await mgr.decrypt(cipherBytes);
68150
+ if (mgrKey) this._mlsDecryptFailCounts.delete(mgrKey);
68147
68151
  const mlsGroupId = (convGroupId ? this._persisted?.mlsGroups?.[convGroupId]?.mlsGroupId : null) ?? this._persisted?.mlsConversations?.[convId]?.mlsGroupId ?? groupId;
68148
68152
  if (mlsGroupId) {
68149
68153
  await saveMlsState(this.config.dataDir, mlsGroupId, JSON.stringify(mgr.exportState()));
@@ -68248,6 +68252,10 @@ var init_channel = __esm({
68248
68252
  this.config.onMessage(text, metadata);
68249
68253
  }
68250
68254
  } catch (decryptErr) {
68255
+ if (classifyCommitFailure(data.epoch, mgr?.epoch) === "already-applied") {
68256
+ console.debug(`[SecureChannel] MLS 1:1 message at epoch ${String(data.epoch)} predates our epoch ${String(mgr?.epoch)} \u2014 cannot decrypt, ignoring`);
68257
+ return;
68258
+ }
68251
68259
  const failKey = mgrKey || `unknown:${groupId}`;
68252
68260
  const count = (this._mlsDecryptFailCounts.get(failKey) ?? 0) + 1;
68253
68261
  this._mlsDecryptFailCounts.set(failKey, count);
@@ -69089,6 +69097,7 @@ ${baseText}`;
69089
69097
  try {
69090
69098
  const ciphertext = Buffer.from(data.payload, "hex");
69091
69099
  const result = await mlsGroup.decrypt(new Uint8Array(ciphertext));
69100
+ this._mlsDecryptFailCounts.delete(resolvedRoomId);
69092
69101
  await saveMlsState(this.config.dataDir, groupId, JSON.stringify(mlsGroup.exportState()));
69093
69102
  if (result.isHandshake || !result.plaintext) {
69094
69103
  return;
@@ -69178,6 +69187,10 @@ ${baseText}`;
69178
69187
  console.error("[SecureChannel] onMessage callback error (MLS):", err);
69179
69188
  });
69180
69189
  } catch (err) {
69190
+ if (classifyCommitFailure(data.epoch, mlsGroup.epoch) === "already-applied") {
69191
+ console.debug(`[SecureChannel] MLS room message at epoch ${String(data.epoch)} predates our epoch ${String(mlsGroup.epoch)} for ${resolvedRoomId.slice(0, 8)} \u2014 cannot decrypt, ignoring`);
69192
+ return;
69193
+ }
69181
69194
  const failKey = resolvedRoomId;
69182
69195
  const count = (this._mlsDecryptFailCounts.get(failKey) ?? 0) + 1;
69183
69196
  this._mlsDecryptFailCounts.set(failKey, count);
@@ -69429,13 +69442,7 @@ ${baseText}`;
69429
69442
  console.warn(`[SecureChannel] MLS epoch recovery for ${groupKey} (${mlsGroupId.slice(0, 8)})`);
69430
69443
  this._mlsGroups.delete(groupKey);
69431
69444
  this._mlsDecryptFailCounts.delete(groupKey);
69432
- try {
69433
- const { unlink: unlink2 } = await import("fs/promises");
69434
- const path = `${this.config.dataDir}/.mls-state/${mlsGroupId}.json`;
69435
- await unlink2(path).catch(() => {
69436
- });
69437
- } catch {
69438
- }
69445
+ await deleteMlsState(this.config.dataDir, mlsGroupId);
69439
69446
  try {
69440
69447
  const mgr = new MLSGroupManager();
69441
69448
  const identity = new TextEncoder().encode(this._deviceId);
@@ -69510,19 +69517,26 @@ ${baseText}`;
69510
69517
  return 0;
69511
69518
  }
69512
69519
  const snapshot = mlsGroup.exportState();
69513
- const { commit, welcome } = await mlsGroup.addMembers(targets.map((t2) => t2.keyPackage));
69514
- const commitSent = await this._sendFrameConfirmed(
69515
- {
69516
- event: "mls_commit",
69517
- data: {
69518
- group_id: mlsGroupId,
69519
- epoch: Number(mlsGroup.epoch),
69520
- payload: Buffer.from(commit).toString("hex")
69521
- }
69522
- },
69523
- `${label} commit`
69524
- );
69525
- if (!commitSent) {
69520
+ const { commits, welcome } = await mlsGroup.reAdmitMembers(targets.map((t2) => t2.keyPackage));
69521
+ let sentCommits = 0;
69522
+ for (const c2 of commits) {
69523
+ const ok = await this._sendFrameConfirmed(
69524
+ {
69525
+ event: "mls_commit",
69526
+ data: {
69527
+ group_id: mlsGroupId,
69528
+ // The epoch this commit PRODUCED. After two commits the manager
69529
+ // only knows the last one, which is why the result carries them.
69530
+ epoch: Number(c2.epoch),
69531
+ payload: Buffer.from(c2.commit).toString("hex")
69532
+ }
69533
+ },
69534
+ `${label} commit ${sentCommits + 1}/${commits.length}`
69535
+ );
69536
+ if (!ok) break;
69537
+ sentCommits++;
69538
+ }
69539
+ if (sentCommits === 0) {
69526
69540
  mlsGroup.importState(snapshot);
69527
69541
  console.warn(
69528
69542
  `[SecureChannel] ${label}: commit was NOT written \u2014 rolled back to epoch ${Number(mlsGroup.epoch)}, nothing fulfilled, requests stay pending`
@@ -69530,6 +69544,12 @@ ${baseText}`;
69530
69544
  return 0;
69531
69545
  }
69532
69546
  await saveMlsState(this.config.dataDir, mlsGroupId, JSON.stringify(mlsGroup.exportState()));
69547
+ if (sentCommits < commits.length) {
69548
+ console.warn(
69549
+ `[SecureChannel] ${label}: only ${sentCommits}/${commits.length} commits reached the wire \u2014 requests stay PENDING for the next pull`
69550
+ );
69551
+ return 0;
69552
+ }
69533
69553
  if (!welcome) {
69534
69554
  console.warn(
69535
69555
  `[SecureChannel] ${label}: addMembers produced NO Welcome \u2014 ${targets.length} request(s) left pending rather than marked fulfilled`
@@ -70210,6 +70230,11 @@ ${baseText}`;
70210
70230
  nackedIds.push(msg.queue_id);
70211
70231
  if (msg.group_id && (msg.message_type === "commit" || msg.message_type === "application")) {
70212
70232
  const failKey = msg.conversation_group_id ? `1to1-group:${msg.conversation_group_id}` : `group:${msg.group_id}`;
70233
+ const localMgr = msg.room_id && this._mlsGroups.get(msg.room_id) || this._mlsGroups.get(failKey);
70234
+ if (classifyCommitFailure(msg.epoch, localMgr?.epoch) === "already-applied") {
70235
+ console.debug(`[SecureChannel] Delivery ${msg.message_type} at epoch ${String(msg.epoch)} predates our epoch ${String(localMgr?.epoch)} \u2014 not counting toward recovery`);
70236
+ continue;
70237
+ }
70213
70238
  const count = (this._mlsDecryptFailCounts.get(failKey) ?? 0) + 1;
70214
70239
  this._mlsDecryptFailCounts.set(failKey, count);
70215
70240
  if (count >= _SecureChannel.MAX_MLS_DECRYPT_FAILS) {
@@ -70258,38 +70283,63 @@ ${baseText}`;
70258
70283
  { headers: { Authorization: `Bearer ${this._deviceJwt}` } }
70259
70284
  );
70260
70285
  if (!pendingRes.ok) continue;
70261
- const pending = await pendingRes.json();
70286
+ let pending = await pendingRes.json();
70262
70287
  if (pending.length === 0) continue;
70263
70288
  console.log(`[SecureChannel] ${pending.length} pending Welcome requests for room ${roomId.slice(0, 8)}`);
70289
+ const nowMs = Date.now();
70290
+ const fresh = pending.filter((r2) => {
70291
+ const key = `${roomState.mlsGroupId}:${r2.requesting_device_id}`;
70292
+ const last = this._roomFulfilCooldown.get(key) ?? 0;
70293
+ if (nowMs - last < ROOM_FULFIL_COOLDOWN_MS) {
70294
+ console.log(
70295
+ `[SecureChannel] skipping ${r2.requesting_device_id.slice(0, 8)} in room ${roomId.slice(0, 8)} \u2014 re-admitted ${Math.round((nowMs - last) / 1e3)}s ago`
70296
+ );
70297
+ return false;
70298
+ }
70299
+ return true;
70300
+ });
70301
+ if (fresh.length === 0) continue;
70302
+ for (const r2 of fresh) {
70303
+ this._roomFulfilCooldown.set(`${roomState.mlsGroupId}:${r2.requesting_device_id}`, nowMs);
70304
+ }
70305
+ pending = fresh;
70306
+ const deviceIds = pending.map((r2) => r2.requesting_device_id);
70307
+ const kpRes = await fetch(
70308
+ `${this.config.apiUrl}/api/v1/mls/key-packages/batch?device_ids=${deviceIds.join(",")}`,
70309
+ { headers: { Authorization: `Bearer ${this._deviceJwt}` } }
70310
+ );
70311
+ if (!kpRes.ok) continue;
70312
+ const kpData = await kpRes.json();
70313
+ const roomTargets = [];
70264
70314
  for (const req of pending) {
70265
- try {
70266
- const kpRes = await fetch(
70267
- `${this.config.apiUrl}/api/v1/mls/key-packages/batch?device_ids=${req.requesting_device_id}`,
70268
- { headers: { Authorization: `Bearer ${this._deviceJwt}` } }
70315
+ const kpHex = kpData[req.requesting_device_id];
70316
+ if (!kpHex) {
70317
+ console.warn(`[SecureChannel] No KeyPackage for ${req.requesting_device_id.slice(0, 8)}, skipping`);
70318
+ continue;
70319
+ }
70320
+ const kpBytes = new Uint8Array(Buffer.from(kpHex, "hex"));
70321
+ roomTargets.push({
70322
+ requestId: req.id,
70323
+ deviceId: req.requesting_device_id,
70324
+ keyPackage: MLSGroupManager.deserializeKeyPackage(kpBytes)
70325
+ });
70326
+ }
70327
+ if (roomTargets.length === 0) continue;
70328
+ try {
70329
+ const done = await this._fulfilWelcomeRequests({
70330
+ mlsGroup,
70331
+ mlsGroupId: roomState.mlsGroupId,
70332
+ label: `room ${roomId.slice(0, 8)}`,
70333
+ welcomeFields: { room_id: roomId },
70334
+ targets: roomTargets
70335
+ });
70336
+ if (done > 0) {
70337
+ console.log(
70338
+ `[SecureChannel] Batched Welcome for ${done}/${roomTargets.length} device(s) in room ${roomId.slice(0, 8)} (epoch=${mlsGroup.epoch})`
70269
70339
  );
70270
- if (!kpRes.ok) continue;
70271
- const kpData = await kpRes.json();
70272
- const kpHex = kpData[req.requesting_device_id];
70273
- if (!kpHex) continue;
70274
- const kpBytes = new Uint8Array(Buffer.from(kpHex, "hex"));
70275
- const memberKp = MLSGroupManager.deserializeKeyPackage(kpBytes);
70276
- const done = await this._fulfilWelcomeRequests({
70277
- mlsGroup,
70278
- mlsGroupId: roomState.mlsGroupId,
70279
- label: `room ${roomId.slice(0, 8)}`,
70280
- welcomeFields: { room_id: roomId },
70281
- targets: [{
70282
- requestId: req.id,
70283
- deviceId: req.requesting_device_id,
70284
- keyPackage: memberKp
70285
- }]
70286
- });
70287
- if (done > 0) {
70288
- console.log(`[SecureChannel] Fulfilled Welcome for ${req.requesting_device_id.slice(0, 8)} in room ${roomId.slice(0, 8)}`);
70289
- }
70290
- } catch (fulfillErr) {
70291
- console.warn(`[SecureChannel] Welcome fulfill failed for ${req.requesting_device_id.slice(0, 8)}:`, fulfillErr);
70292
70340
  }
70341
+ } catch (fulfillErr) {
70342
+ console.warn(`[SecureChannel] Batched Welcome failed for room ${roomId.slice(0, 8)}:`, fulfillErr);
70293
70343
  }
70294
70344
  } catch {
70295
70345
  }
@@ -70862,6 +70912,7 @@ ${baseText}`;
70862
70912
  try {
70863
70913
  const ciphertext = new Uint8Array(Buffer.from(data.payload, "hex"));
70864
70914
  const result = await mlsGroup.decrypt(ciphertext);
70915
+ this._mlsDecryptFailCounts.delete(`a2a:${a2aChannelId}`);
70865
70916
  await saveMlsState(this.config.dataDir, groupId, JSON.stringify(mlsGroup.exportState()));
70866
70917
  if (result.isHandshake || !result.plaintext) {
70867
70918
  return;
@@ -70882,6 +70933,10 @@ ${baseText}`;
70882
70933
  this.config.onA2AMessage(a2aMsg);
70883
70934
  }
70884
70935
  } catch (err) {
70936
+ if (classifyCommitFailure(data.epoch, mlsGroup?.epoch) === "already-applied") {
70937
+ console.debug(`[SecureChannel] MLS A2A message at epoch ${String(data.epoch)} predates our epoch ${String(mlsGroup?.epoch)} for ${a2aChannelId.slice(0, 8)} \u2014 cannot decrypt, ignoring`);
70938
+ return;
70939
+ }
70885
70940
  const failKey = `a2a:${a2aChannelId}`;
70886
70941
  const count = (this._mlsDecryptFailCounts.get(failKey) ?? 0) + 1;
70887
70942
  this._mlsDecryptFailCounts.set(failKey, count);
@@ -98550,7 +98605,7 @@ var init_index = __esm({
98550
98605
  await init_skill_telemetry();
98551
98606
  await init_policy_enforcer();
98552
98607
  init_room_protocol();
98553
- VERSION = true ? "0.23.32" : "0.0.0-dev";
98608
+ VERSION = true ? "0.23.34" : "0.0.0-dev";
98554
98609
  }
98555
98610
  });
98556
98611
  await init_index();