@agentvault/agentvault 0.9.0 → 0.9.2

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
@@ -45421,9 +45421,18 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
45421
45421
  scanStatus = scanResult.status;
45422
45422
  }
45423
45423
  this._appendHistory("agent", plaintext, topicId);
45424
+ const roomConvIds = /* @__PURE__ */ new Set();
45425
+ if (this._persisted?.rooms) {
45426
+ for (const room of Object.values(this._persisted.rooms)) {
45427
+ for (const cid of room.conversationIds) {
45428
+ roomConvIds.add(cid);
45429
+ }
45430
+ }
45431
+ }
45424
45432
  const messageGroupId = randomUUID();
45425
45433
  for (const [convId, session] of this._sessions) {
45426
45434
  if (!session.activated) continue;
45435
+ if (roomConvIds.has(convId)) continue;
45427
45436
  const encrypted = session.ratchet.encrypt(plaintext);
45428
45437
  const transport = encryptedMessageToTransport(encrypted);
45429
45438
  const msg = {
@@ -45569,6 +45578,10 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
45569
45578
  if (conv.participantA !== myDeviceId && conv.participantB !== myDeviceId) {
45570
45579
  continue;
45571
45580
  }
45581
+ if (this._sessions.has(conv.id)) {
45582
+ conversationIds.push(conv.id);
45583
+ continue;
45584
+ }
45572
45585
  const otherDeviceId = conv.participantA === myDeviceId ? conv.participantB : conv.participantA;
45573
45586
  const otherMember = roomData.members.find((m2) => m2.deviceId === otherDeviceId);
45574
45587
  if (!otherMember?.identityPublicKey) {
@@ -45578,13 +45591,12 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
45578
45591
  continue;
45579
45592
  }
45580
45593
  const isInitiator = myDeviceId < otherDeviceId;
45594
+ const theirEphKey = otherMember.ephemeralPublicKey ?? otherMember.identityPublicKey;
45581
45595
  const sharedSecret = performX3DH({
45582
45596
  myIdentityPrivate: hexToBytes(identity.privateKey),
45583
45597
  myEphemeralPrivate: hexToBytes(ephemeral.privateKey),
45584
45598
  theirIdentityPublic: hexToBytes(otherMember.identityPublicKey),
45585
- theirEphemeralPublic: hexToBytes(
45586
- otherMember.ephemeralPublicKey ?? otherMember.identityPublicKey
45587
- ),
45599
+ theirEphemeralPublic: hexToBytes(theirEphKey),
45588
45600
  isInitiator
45589
45601
  });
45590
45602
  const ratchet = isInitiator ? DoubleRatchet.initSender(sharedSecret, {
@@ -45656,9 +45668,11 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
45656
45668
  this._ws.send(
45657
45669
  JSON.stringify({
45658
45670
  event: "room_message",
45659
- room_id: roomId,
45660
- recipients,
45661
- message_type: messageType
45671
+ data: {
45672
+ room_id: roomId,
45673
+ recipients,
45674
+ message_type: messageType
45675
+ }
45662
45676
  })
45663
45677
  );
45664
45678
  } else {
@@ -46084,6 +46098,23 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
46084
46098
  if (this._persisted.hubAddress) {
46085
46099
  payload2.hub_address = this._persisted.hubAddress;
46086
46100
  }
46101
+ if (channelEntry.observerSession?.ratchetState) {
46102
+ try {
46103
+ const obsRatchet = DoubleRatchet.deserialize(channelEntry.observerSession.ratchetState);
46104
+ const obsEncrypted = obsRatchet.encrypt(text);
46105
+ const obsHeaderObj = {
46106
+ dhPublicKey: bytesToHex(obsEncrypted.header.dhPublicKey),
46107
+ previousChainLength: obsEncrypted.header.previousChainLength,
46108
+ messageNumber: obsEncrypted.header.messageNumber
46109
+ };
46110
+ payload2.observer_header_blob = Buffer.from(JSON.stringify(obsHeaderObj)).toString("hex");
46111
+ payload2.observer_ciphertext = bytesToHex(obsEncrypted.ciphertext);
46112
+ payload2.observer_nonce = bytesToHex(obsEncrypted.nonce);
46113
+ channelEntry.observerSession.ratchetState = obsRatchet.serialize();
46114
+ } catch (obsErr) {
46115
+ console.error("[SecureChannel] Observer encryption failed (sending without observer copy):", obsErr);
46116
+ }
46117
+ }
46087
46118
  channelEntry.session.ratchetState = ratchet.serialize();
46088
46119
  await this._persistState();
46089
46120
  this._ws.send(
@@ -46380,13 +46411,20 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
46380
46411
  return;
46381
46412
  }
46382
46413
  if (data.event === "message") {
46383
- await this._handleIncomingMessage(data.data);
46414
+ try {
46415
+ await this._handleIncomingMessage(data.data);
46416
+ } catch (msgErr) {
46417
+ console.error(
46418
+ `[SecureChannel] Message handler failed for conv ${data.data?.conversation_id?.slice(0, 8) ?? "?"}...:`,
46419
+ msgErr
46420
+ );
46421
+ }
46384
46422
  }
46385
46423
  if (data.event === "room_joined") {
46386
46424
  const d2 = data.data;
46387
46425
  this.joinRoom({
46388
46426
  roomId: d2.room_id,
46389
- name: d2.name,
46427
+ name: d2.room_name ?? d2.name ?? "Room",
46390
46428
  members: (d2.members || []).map((m2) => ({
46391
46429
  deviceId: m2.device_id,
46392
46430
  entityType: m2.entity_type,
@@ -46402,7 +46440,14 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
46402
46440
  }).catch((err) => this.emit("error", err));
46403
46441
  }
46404
46442
  if (data.event === "room_message") {
46405
- await this._handleRoomMessage(data.data);
46443
+ try {
46444
+ await this._handleRoomMessage(data.data);
46445
+ } catch (rmErr) {
46446
+ console.error(
46447
+ `[SecureChannel] Room message handler failed:`,
46448
+ rmErr
46449
+ );
46450
+ }
46406
46451
  }
46407
46452
  if (data.event === "room_participant_added") {
46408
46453
  const p2 = data.data;
@@ -46539,6 +46584,74 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
46539
46584
  }
46540
46585
  this.emit("a2a_channel_activated", actData);
46541
46586
  }
46587
+ if (data.event === "a2a_observer_enabled") {
46588
+ const obsData = data.data || data;
46589
+ const obsChannelId = obsData.channel_id;
46590
+ const obsChannelEntry = this._persisted?.a2aChannels?.[obsChannelId];
46591
+ if (obsChannelEntry && this._persisted && this._ws) {
46592
+ try {
46593
+ const obsEphemeral = await generateEphemeralKeypair();
46594
+ const obsEphPubHex = bytesToHex(obsEphemeral.publicKey);
46595
+ const obsEphPrivHex = bytesToHex(obsEphemeral.privateKey);
46596
+ obsChannelEntry.pendingObserverEphemeralPrivateKey = obsEphPrivHex;
46597
+ await this._persistState();
46598
+ this._ws.send(
46599
+ JSON.stringify({
46600
+ event: "a2a_observer_key_submit",
46601
+ data: {
46602
+ channel_id: obsChannelId,
46603
+ ephemeral_key: obsEphPubHex,
46604
+ side: obsChannelEntry.role || "initiator"
46605
+ }
46606
+ })
46607
+ );
46608
+ console.log(
46609
+ `[SecureChannel] Observer key submitted for channel ${obsChannelId.slice(0, 8)}... (side=${obsChannelEntry.role})`
46610
+ );
46611
+ } catch (err) {
46612
+ console.error("[SecureChannel] Observer key submission failed:", err);
46613
+ }
46614
+ }
46615
+ }
46616
+ if (data.event === "a2a_observer_key_accepted") {
46617
+ const obsAccData = data.data || data;
46618
+ const obsAccChannelId = obsAccData.channel_id;
46619
+ const observerIdentityHex = obsAccData.observer_identity_key;
46620
+ const obsAccSide = obsAccData.side;
46621
+ const obsAccEntry = this._persisted?.a2aChannels?.[obsAccChannelId];
46622
+ if (obsAccEntry && obsAccEntry.pendingObserverEphemeralPrivateKey && this._persisted) {
46623
+ try {
46624
+ const myIdentityPrivate = hexToBytes(this._persisted.identityKeypair.privateKey);
46625
+ const myIdentityPublic = hexToBytes(this._persisted.identityKeypair.publicKey);
46626
+ const myObsEphemeralPrivate = hexToBytes(obsAccEntry.pendingObserverEphemeralPrivateKey);
46627
+ const ownerIdentityPublic = hexToBytes(observerIdentityHex);
46628
+ const obsSharedSecret = performX3DH({
46629
+ myIdentityPrivate,
46630
+ myEphemeralPrivate: myObsEphemeralPrivate,
46631
+ theirIdentityPublic: ownerIdentityPublic,
46632
+ theirEphemeralPublic: ownerIdentityPublic,
46633
+ // owner uses identity as ephemeral
46634
+ isInitiator: true
46635
+ });
46636
+ const identityKp = {
46637
+ publicKey: myIdentityPublic,
46638
+ privateKey: myIdentityPrivate,
46639
+ keyType: "ed25519"
46640
+ };
46641
+ const obsRatchet = DoubleRatchet.initSender(obsSharedSecret, identityKp);
46642
+ obsAccEntry.observerSession = {
46643
+ ratchetState: obsRatchet.serialize()
46644
+ };
46645
+ delete obsAccEntry.pendingObserverEphemeralPrivateKey;
46646
+ await this._persistState();
46647
+ console.log(
46648
+ `[SecureChannel] Observer ratchet initialized for channel ${obsAccChannelId.slice(0, 8)}... (side=${obsAccSide})`
46649
+ );
46650
+ } catch (err) {
46651
+ console.error("[SecureChannel] Observer ratchet init failed:", err);
46652
+ }
46653
+ }
46654
+ }
46542
46655
  if (data.event === "a2a_channel_rejected") {
46543
46656
  this.emit("a2a_channel_rejected", data.data || data);
46544
46657
  }
@@ -47009,7 +47122,73 @@ ${messageText}`;
47009
47122
  header_blob: msgData.header_blob,
47010
47123
  ciphertext: msgData.ciphertext
47011
47124
  });
47012
- const plaintext = session.ratchet.decrypt(encrypted);
47125
+ let plaintext;
47126
+ try {
47127
+ plaintext = session.ratchet.decrypt(encrypted);
47128
+ } catch (decryptErr) {
47129
+ console.warn(
47130
+ `[SecureChannel] Room decrypt failed for conv ${convId.slice(0, 8)}...: ${String(decryptErr)}, re-initializing ratchet`
47131
+ );
47132
+ try {
47133
+ const roomEntry = this._persisted?.rooms ? Object.values(this._persisted.rooms).find(
47134
+ (r2) => r2.conversationIds.includes(convId)
47135
+ ) : null;
47136
+ if (!roomEntry) throw new Error("Room not found for conversation");
47137
+ const otherMember = roomEntry.members.find(
47138
+ (m2) => m2.deviceId === msgData.sender_device_id
47139
+ );
47140
+ if (!otherMember?.identityPublicKey) throw new Error("No key for sender");
47141
+ const isInitiator = this._deviceId < msgData.sender_device_id;
47142
+ const identity = this._persisted.identityKeypair;
47143
+ const ephemeral = this._persisted.ephemeralKeypair;
47144
+ const sharedSecret = performX3DH({
47145
+ myIdentityPrivate: hexToBytes(identity.privateKey),
47146
+ myEphemeralPrivate: hexToBytes(ephemeral.privateKey),
47147
+ theirIdentityPublic: hexToBytes(otherMember.identityPublicKey),
47148
+ theirEphemeralPublic: hexToBytes(
47149
+ otherMember.ephemeralPublicKey ?? otherMember.identityPublicKey
47150
+ ),
47151
+ isInitiator
47152
+ });
47153
+ const newRatchet = isInitiator ? DoubleRatchet.initSender(sharedSecret, {
47154
+ publicKey: hexToBytes(identity.publicKey),
47155
+ privateKey: hexToBytes(identity.privateKey),
47156
+ keyType: "ed25519"
47157
+ }) : DoubleRatchet.initReceiver(sharedSecret, {
47158
+ publicKey: hexToBytes(identity.publicKey),
47159
+ privateKey: hexToBytes(identity.privateKey),
47160
+ keyType: "ed25519"
47161
+ });
47162
+ session.ratchet = newRatchet;
47163
+ session.activated = false;
47164
+ this._persisted.sessions[convId] = {
47165
+ ownerDeviceId: session.ownerDeviceId,
47166
+ ratchetState: newRatchet.serialize(),
47167
+ activated: false
47168
+ };
47169
+ await this._persistState();
47170
+ console.log(
47171
+ `[SecureChannel] Room ratchet re-initialized for conv ${convId.slice(0, 8)}...`
47172
+ );
47173
+ plaintext = session.ratchet.decrypt(encrypted);
47174
+ } catch (reinitErr) {
47175
+ console.error(
47176
+ `[SecureChannel] Room ratchet re-init failed for conv ${convId.slice(0, 8)}...:`,
47177
+ reinitErr
47178
+ );
47179
+ return;
47180
+ }
47181
+ }
47182
+ let messageText;
47183
+ let messageType;
47184
+ try {
47185
+ const parsed = JSON.parse(plaintext);
47186
+ messageType = parsed.type || "message";
47187
+ messageText = parsed.text || plaintext;
47188
+ } catch {
47189
+ messageType = "message";
47190
+ messageText = plaintext;
47191
+ }
47013
47192
  if (!session.activated) {
47014
47193
  session.activated = true;
47015
47194
  console.log(
@@ -47024,16 +47203,17 @@ ${messageText}`;
47024
47203
  messageId: msgData.message_id ?? "",
47025
47204
  conversationId: convId,
47026
47205
  timestamp: msgData.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
47027
- messageType: msgData.message_type ?? "text"
47206
+ messageType,
47207
+ roomId: msgData.room_id
47028
47208
  };
47029
47209
  this.emit("room_message", {
47030
47210
  roomId: msgData.room_id,
47031
47211
  senderDeviceId: msgData.sender_device_id,
47032
- plaintext,
47033
- messageType: msgData.message_type ?? "text",
47212
+ plaintext: messageText,
47213
+ messageType,
47034
47214
  timestamp: msgData.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
47035
47215
  });
47036
- this.config.onMessage?.(plaintext, metadata);
47216
+ this.config.onMessage?.(messageText, metadata);
47037
47217
  }
47038
47218
  /**
47039
47219
  * Find the pairwise conversation ID for a given sender in a room.
@@ -47121,8 +47301,12 @@ ${messageText}`;
47121
47301
  since = msg.created_at;
47122
47302
  totalProcessed++;
47123
47303
  } catch (err) {
47124
- this.emit("error", err);
47125
- break;
47304
+ console.warn(
47305
+ `[SecureChannel] Sync decrypt failed for msg ${msg.id.slice(0, 8)}... in conv ${msg.conversation_id.slice(0, 8)}...: ${String(err)}`
47306
+ );
47307
+ this._persisted.lastMessageTimestamp = msg.created_at;
47308
+ since = msg.created_at;
47309
+ continue;
47126
47310
  }
47127
47311
  }
47128
47312
  await this._persistState();