@agentvault/agentvault 0.19.60 → 0.19.61

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
@@ -63562,7 +63562,7 @@ function migratePersistedState(raw) {
63562
63562
  messageHistory: []
63563
63563
  };
63564
63564
  }
63565
- var ROOM_AGENT_TYPES, CREDENTIAL_MESSAGE_TYPES, POLL_INTERVAL_MS, RECONNECT_BASE_MS, RECONNECT_MAX_MS, PENDING_POLL_INTERVAL_MS, SecureChannel;
63565
+ var ROOM_AGENT_TYPES, CREDENTIAL_MESSAGE_TYPES, CATCHUP_MESSAGE_TYPES, POLL_INTERVAL_MS, RECONNECT_BASE_MS, RECONNECT_MAX_MS, PENDING_POLL_INTERVAL_MS, SecureChannel;
63566
63566
  var init_channel = __esm({
63567
63567
  async "src/channel.ts"() {
63568
63568
  "use strict";
@@ -63585,6 +63585,10 @@ var init_channel = __esm({
63585
63585
  "credential_revoke",
63586
63586
  "credential_request"
63587
63587
  ]);
63588
+ CATCHUP_MESSAGE_TYPES = /* @__PURE__ */ new Set([
63589
+ "history_catchup_request",
63590
+ "history_catchup_response"
63591
+ ]);
63588
63592
  POLL_INTERVAL_MS = 6e3;
63589
63593
  RECONNECT_BASE_MS = 1e3;
63590
63594
  RECONNECT_MAX_MS = 3e4;
@@ -66570,6 +66574,12 @@ ${messageText}`;
66570
66574
  await this._persistState();
66571
66575
  return;
66572
66576
  }
66577
+ if (CATCHUP_MESSAGE_TYPES.has(messageType)) {
66578
+ if (messageType === "history_catchup_request") {
66579
+ await this._handleHistoryCatchupRequest(resolvedRoomId, messageText, data.message_id);
66580
+ }
66581
+ return;
66582
+ }
66573
66583
  if (!ROOM_AGENT_TYPES.has(messageType)) {
66574
66584
  return;
66575
66585
  }
@@ -66616,6 +66626,55 @@ ${messageText}`;
66616
66626
  console.error(`[SecureChannel] MLS room decrypt failed for ${resolvedRoomId.slice(0, 8)}:`, err);
66617
66627
  }
66618
66628
  }
66629
+ /**
66630
+ * Handle a history catch-up request from a device that just rejoined a room.
66631
+ * Only the elected agent (smallest device_id among agents) responds.
66632
+ */
66633
+ async _handleHistoryCatchupRequest(roomId, requestText, messageId) {
66634
+ let request;
66635
+ try {
66636
+ request = JSON.parse(requestText);
66637
+ } catch {
66638
+ return;
66639
+ }
66640
+ const roomMembers = this._persisted?.rooms?.[roomId]?.members ?? [];
66641
+ const agentDeviceIds = roomMembers.filter((m2) => m2.entityType !== "owner" && m2.entityType !== "human").map((m2) => m2.deviceId).sort();
66642
+ const isElected = agentDeviceIds.length > 0 && agentDeviceIds[0] === this._deviceId;
66643
+ if (!isElected && !request.retry) {
66644
+ console.log(
66645
+ `[SecureChannel] History catchup: not elected (elected=${agentDeviceIds[0]?.slice(0, 8)}), ignoring`
66646
+ );
66647
+ return;
66648
+ }
66649
+ const history = this.getRoomHistory(roomId, 100);
66650
+ let filtered = history;
66651
+ if (request.since_ts) {
66652
+ const sinceTime = new Date(request.since_ts).getTime();
66653
+ filtered = history.filter((e) => new Date(e.ts).getTime() > sinceTime);
66654
+ }
66655
+ console.log(
66656
+ `[SecureChannel] History catchup for room ${roomId.slice(0, 8)}: ${filtered.length} messages since ${request.since_ts || "beginning"} (total history: ${history.length})`
66657
+ );
66658
+ const responsePayload = JSON.stringify({
66659
+ type: "history_catchup_response",
66660
+ target_device_id: request.requesting_device_id,
66661
+ messages: filtered.map((e) => ({
66662
+ sender: e.sender,
66663
+ sender_name: e.sender === "agent" ? this.config.agentName ?? "Agent" : "Owner",
66664
+ text: e.text,
66665
+ ts: e.ts
66666
+ })),
66667
+ no_history: filtered.length === 0
66668
+ });
66669
+ try {
66670
+ await this.sendToRoom(roomId, responsePayload, { messageType: "history_catchup_response" });
66671
+ console.log(
66672
+ `[SecureChannel] Sent history catchup response: ${filtered.length} messages to ${request.requesting_device_id?.slice(0, 8)}`
66673
+ );
66674
+ } catch (sendErr) {
66675
+ console.warn(`[SecureChannel] Failed to send history catchup response:`, sendErr);
66676
+ }
66677
+ }
66619
66678
  async _handleMlsCommit(data) {
66620
66679
  const groupId = data.group_id;
66621
66680
  for (const [roomId, room] of Object.entries(this._persisted?.rooms ?? {})) {
@@ -66830,6 +66889,71 @@ ${messageText}`;
66830
66889
  console.warn("[SecureChannel] Delivery ack failed:", ackErr);
66831
66890
  }
66832
66891
  }
66892
+ for (const [roomId, mlsGroup] of this._mlsGroups) {
66893
+ if (!mlsGroup?.isInitialized) continue;
66894
+ if (roomId.includes(":")) continue;
66895
+ const roomState = this._persisted?.rooms?.[roomId];
66896
+ if (!roomState?.mlsGroupId) continue;
66897
+ try {
66898
+ const pendingRes = await fetch(
66899
+ `${this.config.apiUrl}/api/v1/mls/groups/${roomState.mlsGroupId}/pending-welcomes`,
66900
+ { headers: { Authorization: `Bearer ${this._deviceJwt}` } }
66901
+ );
66902
+ if (!pendingRes.ok) continue;
66903
+ const pending = await pendingRes.json();
66904
+ if (pending.length === 0) continue;
66905
+ console.log(`[SecureChannel] ${pending.length} pending Welcome requests for room ${roomId.slice(0, 8)}`);
66906
+ for (const req of pending) {
66907
+ try {
66908
+ const kpRes = await fetch(
66909
+ `${this.config.apiUrl}/api/v1/mls/key-packages/batch?device_ids=${req.requesting_device_id}`,
66910
+ { headers: { Authorization: `Bearer ${this._deviceJwt}` } }
66911
+ );
66912
+ if (!kpRes.ok) continue;
66913
+ const kpData = await kpRes.json();
66914
+ const kpHex = kpData[req.requesting_device_id];
66915
+ if (!kpHex) continue;
66916
+ const kpBytes = new Uint8Array(Buffer.from(kpHex, "hex"));
66917
+ const memberKp = MLSGroupManager.deserializeKeyPackage(kpBytes);
66918
+ const { commit, welcome } = await mlsGroup.addMembers([memberKp]);
66919
+ if (this._ws) {
66920
+ this._ws.send(JSON.stringify({
66921
+ event: "mls_commit",
66922
+ data: {
66923
+ group_id: roomState.mlsGroupId,
66924
+ epoch: Number(mlsGroup.epoch),
66925
+ payload: Buffer.from(commit).toString("hex")
66926
+ }
66927
+ }));
66928
+ }
66929
+ if (welcome && this._ws) {
66930
+ this._ws.send(JSON.stringify({
66931
+ event: "mls_welcome",
66932
+ data: {
66933
+ target_device_id: req.requesting_device_id,
66934
+ group_id: roomState.mlsGroupId,
66935
+ room_id: roomId,
66936
+ payload: Buffer.from(welcome).toString("hex")
66937
+ }
66938
+ }));
66939
+ }
66940
+ await fetch(
66941
+ `${this.config.apiUrl}/api/v1/mls/groups/${roomState.mlsGroupId}/fulfill-welcome`,
66942
+ {
66943
+ method: "POST",
66944
+ headers: { Authorization: `Bearer ${this._deviceJwt}`, "Content-Type": "application/json" },
66945
+ body: JSON.stringify({ request_id: req.id })
66946
+ }
66947
+ );
66948
+ await saveMlsState(this.config.dataDir, roomState.mlsGroupId, JSON.stringify(mlsGroup.exportState()));
66949
+ console.log(`[SecureChannel] Fulfilled Welcome for ${req.requesting_device_id.slice(0, 8)} in room ${roomId.slice(0, 8)}`);
66950
+ } catch (fulfillErr) {
66951
+ console.warn(`[SecureChannel] Welcome fulfill failed for ${req.requesting_device_id.slice(0, 8)}:`, fulfillErr);
66952
+ }
66953
+ }
66954
+ } catch {
66955
+ }
66956
+ }
66833
66957
  } catch (err) {
66834
66958
  console.warn("[SecureChannel] Delivery pull error:", err);
66835
66959
  } finally {