@agentvault/agentvault 0.19.34 → 0.19.36

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/cli.js CHANGED
@@ -44744,7 +44744,7 @@ var init_ratchet = __esm({
44744
44744
  async "../crypto/dist/ratchet.js"() {
44745
44745
  "use strict";
44746
44746
  await init_libsodium_wrappers();
44747
- MAX_SKIP = 100;
44747
+ MAX_SKIP = 1e3;
44748
44748
  CHAIN_KEY_SEED = new Uint8Array([1]);
44749
44749
  MSG_KEY_SEED = new Uint8Array([2]);
44750
44750
  HEADER_KEY_SEED = new Uint8Array([3]);
@@ -47175,6 +47175,12 @@ var init_channel = __esm({
47175
47175
  this._primaryConversationId = this._persisted.primaryConversationId;
47176
47176
  this._fingerprint = this._persisted.fingerprint;
47177
47177
  this._lastInboundRoomId = this._persisted.lastInboundRoomId;
47178
+ if (this._persisted.seenMessageIds) {
47179
+ this._seenMessageIds = new Set(this._persisted.seenMessageIds.slice(-_SecureChannel.SEEN_MSG_MAX));
47180
+ }
47181
+ if (this._persisted.seenA2AMessageIds) {
47182
+ this._a2aSeenMessageIds = new Set(this._persisted.seenA2AMessageIds.slice(-_SecureChannel.A2A_SEEN_MAX));
47183
+ }
47178
47184
  for (const [convId, sessionData] of Object.entries(
47179
47185
  this._persisted.sessions
47180
47186
  )) {
@@ -47183,7 +47189,8 @@ var init_channel = __esm({
47183
47189
  this._sessions.set(convId, {
47184
47190
  ownerDeviceId: sessionData.ownerDeviceId,
47185
47191
  ratchet,
47186
- activated: sessionData.activated ?? false
47192
+ activated: sessionData.activated ?? false,
47193
+ epoch: sessionData.epoch
47187
47194
  });
47188
47195
  }
47189
47196
  }
@@ -47222,7 +47229,8 @@ var init_channel = __esm({
47222
47229
  this._sessions.set(convId, {
47223
47230
  ownerDeviceId: sd.ownerDeviceId,
47224
47231
  ratchet: DoubleRatchet.deserialize(sd.ratchetState),
47225
- activated: sd.activated ?? false
47232
+ activated: sd.activated ?? false,
47233
+ epoch: sd.epoch
47226
47234
  });
47227
47235
  }
47228
47236
  }
@@ -47321,6 +47329,7 @@ var init_channel = __esm({
47321
47329
  }
47322
47330
  const messageGroupId = randomUUID2();
47323
47331
  let sentCount = 0;
47332
+ const pendingWsSends = [];
47324
47333
  for (const [convId, session] of this._sessions) {
47325
47334
  if (!session.activated) continue;
47326
47335
  if (roomConvIds.has(convId)) continue;
@@ -47353,7 +47362,7 @@ var init_channel = __esm({
47353
47362
  if (this._persisted?.hubId) {
47354
47363
  payload.sender_hub_id = this._persisted.hubId;
47355
47364
  }
47356
- this._ws.send(
47365
+ pendingWsSends.push(
47357
47366
  JSON.stringify({
47358
47367
  event: "message",
47359
47368
  data: payload
@@ -47379,6 +47388,9 @@ var init_channel = __esm({
47379
47388
  console.warn("[SecureChannel] send() delivered to 0 sessions (all skipped or failed)");
47380
47389
  }
47381
47390
  await this._persistState();
47391
+ for (const frame of pendingWsSends) {
47392
+ this._ws.send(frame);
47393
+ }
47382
47394
  }
47383
47395
  /**
47384
47396
  * Send a typing indicator to all owner devices.
@@ -47558,13 +47570,15 @@ var init_channel = __esm({
47558
47570
  this._sessions.set(conv.id, {
47559
47571
  ownerDeviceId: otherDeviceId,
47560
47572
  ratchet,
47561
- activated: isInitiator
47573
+ activated: isInitiator,
47562
47574
  // initiator can send immediately
47575
+ epoch: 1
47563
47576
  });
47564
47577
  this._persisted.sessions[conv.id] = {
47565
47578
  ownerDeviceId: otherDeviceId,
47566
47579
  ratchetState: ratchet.serialize(),
47567
- activated: isInitiator
47580
+ activated: isInitiator,
47581
+ epoch: 1
47568
47582
  };
47569
47583
  conversationIds.push(conv.id);
47570
47584
  console.log(
@@ -47657,6 +47671,7 @@ var init_channel = __esm({
47657
47671
  if (recipients.length === 0) {
47658
47672
  throw new Error("No active sessions in room");
47659
47673
  }
47674
+ await this._persistState();
47660
47675
  if (this._state === "ready" && this._ws) {
47661
47676
  this._ws.send(
47662
47677
  JSON.stringify({
@@ -47696,7 +47711,6 @@ var init_channel = __esm({
47696
47711
  throw new Error(`Failed to send room message: ${err}`);
47697
47712
  }
47698
47713
  }
47699
- await this._persistState();
47700
47714
  }
47701
47715
  /**
47702
47716
  * Leave a room: remove sessions and persisted room state.
@@ -47795,11 +47809,12 @@ var init_channel = __esm({
47795
47809
  attachment: attachMeta
47796
47810
  });
47797
47811
  const messageGroupId = randomUUID2();
47812
+ const pendingWsSends = [];
47798
47813
  for (const [convId, session] of this._sessions) {
47799
47814
  if (!session.activated) continue;
47800
47815
  const encrypted = session.ratchet.encrypt(envelope);
47801
47816
  const transport = encryptedMessageToTransport(encrypted);
47802
- this._ws.send(
47817
+ pendingWsSends.push(
47803
47818
  JSON.stringify({
47804
47819
  event: "message",
47805
47820
  data: {
@@ -47813,6 +47828,9 @@ var init_channel = __esm({
47813
47828
  );
47814
47829
  }
47815
47830
  await this._persistState();
47831
+ for (const frame of pendingWsSends) {
47832
+ this._ws.send(frame);
47833
+ }
47816
47834
  }
47817
47835
  async sendActionConfirmation(confirmation) {
47818
47836
  const envelope = {
@@ -48655,12 +48673,14 @@ var init_channel = __esm({
48655
48673
  this._sessions.set(conv.conversation_id, {
48656
48674
  ownerDeviceId: conv.owner_device_id,
48657
48675
  ratchet,
48658
- activated: false
48676
+ activated: false,
48659
48677
  // Wait for owner's first message before sending to this session
48678
+ epoch: 1
48660
48679
  });
48661
48680
  sessions[conv.conversation_id] = {
48662
48681
  ownerDeviceId: conv.owner_device_id,
48663
- ratchetState: ratchet.serialize()
48682
+ ratchetState: ratchet.serialize(),
48683
+ epoch: 1
48664
48684
  };
48665
48685
  console.log(
48666
48686
  `[SecureChannel] Session initialized for conv ${conv.conversation_id.slice(0, 8)}... (owner ${conv.owner_device_id.slice(0, 8)}..., primary=${conv.is_primary})`
@@ -49159,8 +49179,9 @@ var init_channel = __esm({
49159
49179
  ownerDeviceId: "",
49160
49180
  // A2A sessions don't have an owner device
49161
49181
  ratchetState: ratchet.serialize(),
49162
- activated: activatedRole === "initiator"
49182
+ activated: activatedRole === "initiator",
49163
49183
  // initiator can send immediately
49184
+ epoch: 1
49164
49185
  };
49165
49186
  channelEntry.role = activatedRole;
49166
49187
  if (convId) channelEntry.conversationId = convId;
@@ -49345,7 +49366,8 @@ var init_channel = __esm({
49345
49366
  conversation_id: convId,
49346
49367
  reason: "no_session",
49347
49368
  identity_public_key: this._persisted.identityKeypair.publicKey,
49348
- ephemeral_public_key: this._persisted.ephemeralKeypair.publicKey
49369
+ ephemeral_public_key: this._persisted.ephemeralKeypair.publicKey,
49370
+ epoch: this._persisted.sessions[convId]?.epoch
49349
49371
  }
49350
49372
  })
49351
49373
  );
@@ -49384,7 +49406,8 @@ var init_channel = __esm({
49384
49406
  conversation_id: convId,
49385
49407
  reason: "decrypt_failure",
49386
49408
  identity_public_key: idPubHex,
49387
- ephemeral_public_key: ephPubHex
49409
+ ephemeral_public_key: ephPubHex,
49410
+ epoch: this._persisted?.sessions[convId]?.epoch
49388
49411
  }
49389
49412
  })
49390
49413
  );
@@ -49629,11 +49652,12 @@ ${messageText}`;
49629
49652
  });
49630
49653
  this._appendHistory("agent", plaintext, topicId);
49631
49654
  const messageGroupId = randomUUID2();
49655
+ const pendingWsSends = [];
49632
49656
  for (const [convId, session] of this._sessions) {
49633
49657
  if (!session.activated) continue;
49634
49658
  const encrypted = session.ratchet.encrypt(envelope);
49635
49659
  const transport = encryptedMessageToTransport(encrypted);
49636
- this._ws.send(
49660
+ pendingWsSends.push(
49637
49661
  JSON.stringify({
49638
49662
  event: "message",
49639
49663
  data: {
@@ -49647,6 +49671,9 @@ ${messageText}`;
49647
49671
  );
49648
49672
  }
49649
49673
  await this._persistState();
49674
+ for (const frame of pendingWsSends) {
49675
+ this._ws.send(frame);
49676
+ }
49650
49677
  }
49651
49678
  /**
49652
49679
  * Relay an owner's message to all sibling sessions as encrypted sync messages.
@@ -49669,13 +49696,14 @@ ${messageText}`;
49669
49696
  ts: (/* @__PURE__ */ new Date()).toISOString(),
49670
49697
  topicId
49671
49698
  });
49699
+ const pendingWsSends = [];
49672
49700
  for (const [siblingConvId, siblingSession] of this._sessions) {
49673
49701
  if (siblingConvId === sourceConvId) continue;
49674
49702
  if (!siblingSession.activated) continue;
49675
49703
  if (roomConvIds.has(siblingConvId)) continue;
49676
49704
  const syncEncrypted = siblingSession.ratchet.encrypt(syncPayload);
49677
49705
  const syncTransport = encryptedMessageToTransport(syncEncrypted);
49678
- this._ws.send(
49706
+ pendingWsSends.push(
49679
49707
  JSON.stringify({
49680
49708
  event: "message",
49681
49709
  data: {
@@ -49686,6 +49714,10 @@ ${messageText}`;
49686
49714
  })
49687
49715
  );
49688
49716
  }
49717
+ await this._persistState();
49718
+ for (const frame of pendingWsSends) {
49719
+ this._ws.send(frame);
49720
+ }
49689
49721
  }
49690
49722
  /**
49691
49723
  * Resolve the agent's workspace directory.
@@ -49729,6 +49761,7 @@ ${messageText}`;
49729
49761
  const plaintext = JSON.stringify(payload);
49730
49762
  const encrypted = session.ratchet.encrypt(plaintext);
49731
49763
  const transport = encryptedMessageToTransport(encrypted);
49764
+ await this._persistState();
49732
49765
  this._ws.send(
49733
49766
  JSON.stringify({
49734
49767
  event: "message",
@@ -49739,7 +49772,6 @@ ${messageText}`;
49739
49772
  }
49740
49773
  })
49741
49774
  );
49742
- await this._persistState();
49743
49775
  }
49744
49776
  /**
49745
49777
  * Send stored message history to a newly-activated session.
@@ -49762,6 +49794,7 @@ ${messageText}`;
49762
49794
  });
49763
49795
  const encrypted = session.ratchet.encrypt(replayPayload);
49764
49796
  const transport = encryptedMessageToTransport(encrypted);
49797
+ await this._persistState();
49765
49798
  this._ws.send(
49766
49799
  JSON.stringify({
49767
49800
  event: "message",
@@ -49808,13 +49841,15 @@ ${messageText}`;
49808
49841
  this._sessions.set(event.conversation_id, {
49809
49842
  ownerDeviceId: event.owner_device_id,
49810
49843
  ratchet,
49811
- activated: false
49844
+ activated: false,
49812
49845
  // Wait for owner's first message
49846
+ epoch: 1
49813
49847
  });
49814
49848
  this._persisted.sessions[event.conversation_id] = {
49815
49849
  ownerDeviceId: event.owner_device_id,
49816
49850
  ratchetState: ratchet.serialize(),
49817
- activated: false
49851
+ activated: false,
49852
+ epoch: 1
49818
49853
  };
49819
49854
  if (event.owner_hub_id) {
49820
49855
  this._persisted.ownerHubId = event.owner_hub_id;
@@ -49861,17 +49896,21 @@ ${messageText}`;
49861
49896
  keyType: "ed25519"
49862
49897
  }, hexToBytes(data.identity_public_key));
49863
49898
  const existingSession = this._sessions.get(convId);
49899
+ const existingPersisted = this._persisted.sessions[convId];
49864
49900
  const ownerDeviceId = data.sender_device_id ?? existingSession?.ownerDeviceId ?? "";
49901
+ const newEpoch = (existingPersisted?.epoch ?? existingSession?.epoch ?? 0) + 1;
49865
49902
  this._sessions.set(convId, {
49866
49903
  ownerDeviceId,
49867
49904
  ratchet,
49868
- activated: false
49905
+ activated: false,
49869
49906
  // Wait for owner's encrypted session_init
49907
+ epoch: newEpoch
49870
49908
  });
49871
49909
  this._persisted.sessions[convId] = {
49872
49910
  ownerDeviceId,
49873
49911
  ratchetState: ratchet.serialize(),
49874
- activated: false
49912
+ activated: false,
49913
+ epoch: newEpoch
49875
49914
  };
49876
49915
  await this._persistState();
49877
49916
  if (this._ws) {
@@ -49881,7 +49920,8 @@ ${messageText}`;
49881
49920
  data: {
49882
49921
  conversation_id: convId,
49883
49922
  identity_public_key: identity.publicKey,
49884
- ephemeral_public_key: ephemeral.publicKey
49923
+ ephemeral_public_key: ephemeral.publicKey,
49924
+ epoch: newEpoch
49885
49925
  }
49886
49926
  })
49887
49927
  );
@@ -49895,6 +49935,32 @@ ${messageText}`;
49895
49935
  this.emit("error", err);
49896
49936
  }
49897
49937
  }
49938
+ /**
49939
+ * Send a resync_request for a room conversation if the cooldown has elapsed.
49940
+ * Shared by all room ratchet failure paths to avoid code duplication.
49941
+ */
49942
+ _sendRoomResyncIfCooled(convId, reason, session) {
49943
+ const RESYNC_COOLDOWN_MS = 5 * 60 * 1e3;
49944
+ const lastResync = this._lastResyncRequest.get(convId) ?? 0;
49945
+ if (Date.now() - lastResync > RESYNC_COOLDOWN_MS && this._ws && this._persisted) {
49946
+ this._lastResyncRequest.set(convId, Date.now());
49947
+ this._ws.send(
49948
+ JSON.stringify({
49949
+ event: "resync_request",
49950
+ data: {
49951
+ conversation_id: convId,
49952
+ reason,
49953
+ identity_public_key: this._persisted.identityKeypair.publicKey,
49954
+ ephemeral_public_key: this._persisted.ephemeralKeypair.publicKey,
49955
+ epoch: session?.epoch
49956
+ }
49957
+ })
49958
+ );
49959
+ console.log(
49960
+ `[SecureChannel] Room resync requested for conv ${convId.slice(0, 8)} (${reason})`
49961
+ );
49962
+ }
49963
+ }
49898
49964
  /**
49899
49965
  * Handle an incoming room message. Finds the pairwise conversation
49900
49966
  * for the sender, decrypts, and emits a room_message event.
@@ -50014,20 +50080,39 @@ ${messageText}`;
50014
50080
  console.log(
50015
50081
  `[SecureChannel] Room ratchet re-initialized for conv ${convId.slice(0, 8)}...`
50016
50082
  );
50017
- plaintext = session.ratchet.decrypt(encrypted);
50018
- session.activated = true;
50019
- if (this._persisted.sessions[convId]) {
50020
- this._persisted.sessions[convId].activated = true;
50083
+ const incomingMsgNum = encrypted.header.messageNumber;
50084
+ if (incomingMsgNum <= 5) {
50085
+ try {
50086
+ plaintext = session.ratchet.decrypt(encrypted);
50087
+ session.activated = true;
50088
+ if (this._persisted.sessions[convId]) {
50089
+ this._persisted.sessions[convId].activated = true;
50090
+ }
50091
+ await this._persistState();
50092
+ console.log(
50093
+ `[SecureChannel] Room session ${convId.slice(0, 8)}... re-activated after ratchet re-init`
50094
+ );
50095
+ } catch (retryErr) {
50096
+ console.warn(
50097
+ `[SecureChannel] Room re-init retry failed for conv ${convId.slice(0, 8)} (msgNum=${incomingMsgNum}):`,
50098
+ retryErr
50099
+ );
50100
+ this._sendRoomResyncIfCooled(convId, "room_reinit_retry_failed", session);
50101
+ return;
50102
+ }
50103
+ } else {
50104
+ console.log(
50105
+ `[SecureChannel] Room re-init: skipping message with msgNum=${incomingMsgNum} for conv ${convId.slice(0, 8)} (too far ahead for fresh ratchet)`
50106
+ );
50107
+ this._sendRoomResyncIfCooled(convId, "room_message_skip");
50108
+ return;
50021
50109
  }
50022
- await this._persistState();
50023
- console.log(
50024
- `[SecureChannel] Room session ${convId.slice(0, 8)}... re-activated after ratchet re-init`
50025
- );
50026
50110
  } catch (reinitErr) {
50027
50111
  console.error(
50028
50112
  `[SecureChannel] Room ratchet re-init failed for conv ${convId.slice(0, 8)}...:`,
50029
50113
  reinitErr
50030
50114
  );
50115
+ this._sendRoomResyncIfCooled(convId, "room_reinit_failed");
50031
50116
  return;
50032
50117
  }
50033
50118
  }
@@ -50201,13 +50286,14 @@ ${messageText}`;
50201
50286
  const dist = chain.getDistribution(this._deviceId);
50202
50287
  const distJson = JSON.stringify(dist);
50203
50288
  const alreadyDistributed = new Set(room.distributedTo ?? []);
50289
+ const pendingWsSends = [];
50204
50290
  for (const convId of room.conversationIds) {
50205
50291
  const session = this._sessions.get(convId);
50206
50292
  if (!session || alreadyDistributed.has(session.ownerDeviceId)) continue;
50207
50293
  const encrypted = session.ratchet.encrypt(distJson);
50208
50294
  const transport = encryptedMessageToTransport(encrypted);
50209
50295
  if (this._state === "ready" && this._ws) {
50210
- this._ws.send(
50296
+ pendingWsSends.push(
50211
50297
  JSON.stringify({
50212
50298
  event: "sender_key_distribution",
50213
50299
  data: {
@@ -50223,6 +50309,9 @@ ${messageText}`;
50223
50309
  }
50224
50310
  room.distributedTo = [...alreadyDistributed];
50225
50311
  await this._persistState();
50312
+ for (const frame of pendingWsSends) {
50313
+ this._ws.send(frame);
50314
+ }
50226
50315
  }
50227
50316
  /**
50228
50317
  * Handle an incoming sender_key_distribution event.
@@ -50420,8 +50509,10 @@ ${messageText}`;
50420
50509
  try {
50421
50510
  a2aPlaintext = ratchet.decrypt(encryptedMessage);
50422
50511
  } catch (decryptErr) {
50423
- console.error(`[SecureChannel] A2A decrypt failed \u2014 restoring ratchet state:`, decryptErr);
50512
+ console.error(`[SecureChannel] A2A decrypt failed \u2014 restoring ratchet, initiating rekey:`, decryptErr);
50424
50513
  channelEntry.session.ratchetState = ratchetSnapshot;
50514
+ await this._persistState();
50515
+ await this._initiateA2ARekey(channelId);
50425
50516
  return;
50426
50517
  }
50427
50518
  channelEntry.session.ratchetState = ratchet.serialize();
@@ -50463,24 +50554,9 @@ ${messageText}`;
50463
50554
  }
50464
50555
  } else {
50465
50556
  console.warn(
50466
- `[SecureChannel] Received encrypted A2A but no session for ${channelId.slice(0, 8)} \u2014 triggering key exchange`
50557
+ `[SecureChannel] Received encrypted A2A but no session for ${channelId.slice(0, 8)} \u2014 initiating rekey`
50467
50558
  );
50468
- const entry = this._persisted?.a2aChannels?.[channelId];
50469
- if (entry && !entry.pendingEphemeralPrivateKey && !entry.session) {
50470
- try {
50471
- const a2aEphemeral = await generateEphemeralKeypair();
50472
- const ephPubHex = bytesToHex(a2aEphemeral.publicKey);
50473
- entry.pendingEphemeralPrivateKey = bytesToHex(a2aEphemeral.privateKey);
50474
- this._ws?.send(JSON.stringify({
50475
- event: "a2a_key_exchange",
50476
- data: { channel_id: channelId, ephemeral_key: ephPubHex }
50477
- }));
50478
- await this._persistState();
50479
- console.log(`[SecureChannel] On-demand A2A key exchange for ${channelId.slice(0, 8)}`);
50480
- } catch (kxErr) {
50481
- console.warn(`[SecureChannel] On-demand key exchange failed:`, kxErr);
50482
- }
50483
- }
50559
+ await this._initiateA2ARekey(channelId);
50484
50560
  }
50485
50561
  } else {
50486
50562
  const a2aMsg = {
@@ -50497,6 +50573,51 @@ ${messageText}`;
50497
50573
  }
50498
50574
  }
50499
50575
  }
50576
+ /**
50577
+ * Initiate A2A channel rekey after decrypt failure.
50578
+ * Calls the backend to reset channel to approved, clears local session,
50579
+ * and submits a fresh ephemeral key for key exchange.
50580
+ */
50581
+ async _initiateA2ARekey(channelId) {
50582
+ const entry = this._persisted?.a2aChannels?.[channelId];
50583
+ if (!entry || !this._deviceJwt || !this._ws) return;
50584
+ const now = Date.now();
50585
+ const lastRekey = entry._lastRekeyAttempt ?? 0;
50586
+ if (now - lastRekey < 3e4) return;
50587
+ entry._lastRekeyAttempt = now;
50588
+ console.log(`[SecureChannel] Initiating A2A rekey for ${channelId.slice(0, 8)}...`);
50589
+ try {
50590
+ const res = await fetch(
50591
+ `${this.config.apiUrl}/api/v1/a2a/channels/${channelId}/rekey`,
50592
+ {
50593
+ method: "POST",
50594
+ headers: { Authorization: `Bearer ${this._deviceJwt}` }
50595
+ }
50596
+ );
50597
+ if (!res.ok) {
50598
+ const detail = await res.text();
50599
+ console.warn(`[SecureChannel] A2A rekey API failed (${res.status}): ${detail}`);
50600
+ return;
50601
+ }
50602
+ delete entry.session;
50603
+ delete entry.observerSession;
50604
+ delete entry.pendingEphemeralPrivateKey;
50605
+ delete entry.pendingObserverEphemeralPrivateKey;
50606
+ const a2aEphemeral = await generateEphemeralKeypair();
50607
+ entry.pendingEphemeralPrivateKey = bytesToHex(a2aEphemeral.privateKey);
50608
+ this._ws.send(JSON.stringify({
50609
+ event: "a2a_key_exchange",
50610
+ data: {
50611
+ channel_id: channelId,
50612
+ ephemeral_key: bytesToHex(a2aEphemeral.publicKey)
50613
+ }
50614
+ }));
50615
+ await this._persistState();
50616
+ console.log(`[SecureChannel] A2A rekey submitted for ${channelId.slice(0, 8)}`);
50617
+ } catch (err) {
50618
+ console.warn(`[SecureChannel] A2A rekey failed:`, err);
50619
+ }
50620
+ }
50500
50621
  /**
50501
50622
  * Paginated sync: fetch missed messages in pages of 200, up to 5 pages (1000 messages).
50502
50623
  * Tracks message IDs in _syncMessageIds to prevent duplicate processing from concurrent WS messages.
@@ -50917,6 +51038,8 @@ ${messageText}`;
50917
51038
  const hasA2AChannels = !!this._persisted.a2aChannels && Object.keys(this._persisted.a2aChannels).length > 0;
50918
51039
  if (!hasOwnerSessions && !hasA2AChannels) return;
50919
51040
  this._persisted.lastInboundRoomId = this._lastInboundRoomId;
51041
+ this._persisted.seenMessageIds = [...this._seenMessageIds].slice(-_SecureChannel.SEEN_MSG_MAX);
51042
+ this._persisted.seenA2AMessageIds = [...this._a2aSeenMessageIds].slice(-_SecureChannel.A2A_SEEN_MAX);
50920
51043
  for (const [convId, session] of this._sessions) {
50921
51044
  this._persisted.sessions[convId] = {
50922
51045
  ownerDeviceId: session.ownerDeviceId,