@agentvault/agentvault 0.9.0 → 0.9.1

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(
@@ -46386,7 +46417,7 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
46386
46417
  const d2 = data.data;
46387
46418
  this.joinRoom({
46388
46419
  roomId: d2.room_id,
46389
- name: d2.name,
46420
+ name: d2.room_name ?? d2.name ?? "Room",
46390
46421
  members: (d2.members || []).map((m2) => ({
46391
46422
  deviceId: m2.device_id,
46392
46423
  entityType: m2.entity_type,
@@ -46539,6 +46570,74 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
46539
46570
  }
46540
46571
  this.emit("a2a_channel_activated", actData);
46541
46572
  }
46573
+ if (data.event === "a2a_observer_enabled") {
46574
+ const obsData = data.data || data;
46575
+ const obsChannelId = obsData.channel_id;
46576
+ const obsChannelEntry = this._persisted?.a2aChannels?.[obsChannelId];
46577
+ if (obsChannelEntry && this._persisted && this._ws) {
46578
+ try {
46579
+ const obsEphemeral = await generateEphemeralKeypair();
46580
+ const obsEphPubHex = bytesToHex(obsEphemeral.publicKey);
46581
+ const obsEphPrivHex = bytesToHex(obsEphemeral.privateKey);
46582
+ obsChannelEntry.pendingObserverEphemeralPrivateKey = obsEphPrivHex;
46583
+ await this._persistState();
46584
+ this._ws.send(
46585
+ JSON.stringify({
46586
+ event: "a2a_observer_key_submit",
46587
+ data: {
46588
+ channel_id: obsChannelId,
46589
+ ephemeral_key: obsEphPubHex,
46590
+ side: obsChannelEntry.role || "initiator"
46591
+ }
46592
+ })
46593
+ );
46594
+ console.log(
46595
+ `[SecureChannel] Observer key submitted for channel ${obsChannelId.slice(0, 8)}... (side=${obsChannelEntry.role})`
46596
+ );
46597
+ } catch (err) {
46598
+ console.error("[SecureChannel] Observer key submission failed:", err);
46599
+ }
46600
+ }
46601
+ }
46602
+ if (data.event === "a2a_observer_key_accepted") {
46603
+ const obsAccData = data.data || data;
46604
+ const obsAccChannelId = obsAccData.channel_id;
46605
+ const observerIdentityHex = obsAccData.observer_identity_key;
46606
+ const obsAccSide = obsAccData.side;
46607
+ const obsAccEntry = this._persisted?.a2aChannels?.[obsAccChannelId];
46608
+ if (obsAccEntry && obsAccEntry.pendingObserverEphemeralPrivateKey && this._persisted) {
46609
+ try {
46610
+ const myIdentityPrivate = hexToBytes(this._persisted.identityKeypair.privateKey);
46611
+ const myIdentityPublic = hexToBytes(this._persisted.identityKeypair.publicKey);
46612
+ const myObsEphemeralPrivate = hexToBytes(obsAccEntry.pendingObserverEphemeralPrivateKey);
46613
+ const ownerIdentityPublic = hexToBytes(observerIdentityHex);
46614
+ const obsSharedSecret = performX3DH({
46615
+ myIdentityPrivate,
46616
+ myEphemeralPrivate: myObsEphemeralPrivate,
46617
+ theirIdentityPublic: ownerIdentityPublic,
46618
+ theirEphemeralPublic: ownerIdentityPublic,
46619
+ // owner uses identity as ephemeral
46620
+ isInitiator: true
46621
+ });
46622
+ const identityKp = {
46623
+ publicKey: myIdentityPublic,
46624
+ privateKey: myIdentityPrivate,
46625
+ keyType: "ed25519"
46626
+ };
46627
+ const obsRatchet = DoubleRatchet.initSender(obsSharedSecret, identityKp);
46628
+ obsAccEntry.observerSession = {
46629
+ ratchetState: obsRatchet.serialize()
46630
+ };
46631
+ delete obsAccEntry.pendingObserverEphemeralPrivateKey;
46632
+ await this._persistState();
46633
+ console.log(
46634
+ `[SecureChannel] Observer ratchet initialized for channel ${obsAccChannelId.slice(0, 8)}... (side=${obsAccSide})`
46635
+ );
46636
+ } catch (err) {
46637
+ console.error("[SecureChannel] Observer ratchet init failed:", err);
46638
+ }
46639
+ }
46640
+ }
46542
46641
  if (data.event === "a2a_channel_rejected") {
46543
46642
  this.emit("a2a_channel_rejected", data.data || data);
46544
46643
  }
@@ -47009,7 +47108,73 @@ ${messageText}`;
47009
47108
  header_blob: msgData.header_blob,
47010
47109
  ciphertext: msgData.ciphertext
47011
47110
  });
47012
- const plaintext = session.ratchet.decrypt(encrypted);
47111
+ let plaintext;
47112
+ try {
47113
+ plaintext = session.ratchet.decrypt(encrypted);
47114
+ } catch (decryptErr) {
47115
+ console.warn(
47116
+ `[SecureChannel] Room decrypt failed for conv ${convId.slice(0, 8)}...: ${String(decryptErr)}, re-initializing ratchet`
47117
+ );
47118
+ try {
47119
+ const roomEntry = this._persisted?.rooms ? Object.values(this._persisted.rooms).find(
47120
+ (r2) => r2.conversationIds.includes(convId)
47121
+ ) : null;
47122
+ if (!roomEntry) throw new Error("Room not found for conversation");
47123
+ const otherMember = roomEntry.members.find(
47124
+ (m2) => m2.deviceId === msgData.sender_device_id
47125
+ );
47126
+ if (!otherMember?.identityPublicKey) throw new Error("No key for sender");
47127
+ const isInitiator = this._deviceId < msgData.sender_device_id;
47128
+ const identity = this._persisted.identityKeypair;
47129
+ const ephemeral = this._persisted.ephemeralKeypair;
47130
+ const sharedSecret = performX3DH({
47131
+ myIdentityPrivate: hexToBytes(identity.privateKey),
47132
+ myEphemeralPrivate: hexToBytes(ephemeral.privateKey),
47133
+ theirIdentityPublic: hexToBytes(otherMember.identityPublicKey),
47134
+ theirEphemeralPublic: hexToBytes(
47135
+ otherMember.ephemeralPublicKey ?? otherMember.identityPublicKey
47136
+ ),
47137
+ isInitiator
47138
+ });
47139
+ const newRatchet = isInitiator ? DoubleRatchet.initSender(sharedSecret, {
47140
+ publicKey: hexToBytes(identity.publicKey),
47141
+ privateKey: hexToBytes(identity.privateKey),
47142
+ keyType: "ed25519"
47143
+ }) : DoubleRatchet.initReceiver(sharedSecret, {
47144
+ publicKey: hexToBytes(identity.publicKey),
47145
+ privateKey: hexToBytes(identity.privateKey),
47146
+ keyType: "ed25519"
47147
+ });
47148
+ session.ratchet = newRatchet;
47149
+ session.activated = false;
47150
+ this._persisted.sessions[convId] = {
47151
+ ownerDeviceId: session.ownerDeviceId,
47152
+ ratchetState: newRatchet.serialize(),
47153
+ activated: false
47154
+ };
47155
+ await this._persistState();
47156
+ console.log(
47157
+ `[SecureChannel] Room ratchet re-initialized for conv ${convId.slice(0, 8)}...`
47158
+ );
47159
+ plaintext = session.ratchet.decrypt(encrypted);
47160
+ } catch (reinitErr) {
47161
+ console.error(
47162
+ `[SecureChannel] Room ratchet re-init failed for conv ${convId.slice(0, 8)}...:`,
47163
+ reinitErr
47164
+ );
47165
+ return;
47166
+ }
47167
+ }
47168
+ let messageText;
47169
+ let messageType;
47170
+ try {
47171
+ const parsed = JSON.parse(plaintext);
47172
+ messageType = parsed.type || "message";
47173
+ messageText = parsed.text || plaintext;
47174
+ } catch {
47175
+ messageType = "message";
47176
+ messageText = plaintext;
47177
+ }
47013
47178
  if (!session.activated) {
47014
47179
  session.activated = true;
47015
47180
  console.log(
@@ -47024,16 +47189,17 @@ ${messageText}`;
47024
47189
  messageId: msgData.message_id ?? "",
47025
47190
  conversationId: convId,
47026
47191
  timestamp: msgData.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
47027
- messageType: msgData.message_type ?? "text"
47192
+ messageType,
47193
+ roomId: msgData.room_id
47028
47194
  };
47029
47195
  this.emit("room_message", {
47030
47196
  roomId: msgData.room_id,
47031
47197
  senderDeviceId: msgData.sender_device_id,
47032
- plaintext,
47033
- messageType: msgData.message_type ?? "text",
47198
+ plaintext: messageText,
47199
+ messageType,
47034
47200
  timestamp: msgData.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
47035
47201
  });
47036
- this.config.onMessage?.(plaintext, metadata);
47202
+ this.config.onMessage?.(messageText, metadata);
47037
47203
  }
47038
47204
  /**
47039
47205
  * Find the pairwise conversation ID for a given sender in a room.