@agentunion/fastaun-browser 0.5.8 → 0.5.9

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/_packed_docs/CHANGELOG.md +30 -0
  3. package/_packed_docs/agent.md/examples//347/276/244/347/273/204-/345/274/200/345/217/221/345/233/242/351/230/237.md +21 -0
  4. package/dist/agent-md.d.ts.map +1 -1
  5. package/dist/agent-md.js +1 -0
  6. package/dist/agent-md.js.map +1 -1
  7. package/dist/aid-store.d.ts.map +1 -1
  8. package/dist/aid-store.js +1 -3
  9. package/dist/aid-store.js.map +1 -1
  10. package/dist/bundle.js +454 -83
  11. package/dist/client/delivery.d.ts +7 -0
  12. package/dist/client/delivery.d.ts.map +1 -1
  13. package/dist/client/delivery.js +140 -19
  14. package/dist/client/delivery.js.map +1 -1
  15. package/dist/client/group-state.js +2 -2
  16. package/dist/client/group-state.js.map +1 -1
  17. package/dist/client/lifecycle.d.ts.map +1 -1
  18. package/dist/client/lifecycle.js +15 -4
  19. package/dist/client/lifecycle.js.map +1 -1
  20. package/dist/client/rpc-pipeline.d.ts +8 -0
  21. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  22. package/dist/client/rpc-pipeline.js +149 -34
  23. package/dist/client/rpc-pipeline.js.map +1 -1
  24. package/dist/client/v2-e2ee.d.ts.map +1 -1
  25. package/dist/client/v2-e2ee.js +50 -12
  26. package/dist/client/v2-e2ee.js.map +1 -1
  27. package/dist/client.d.ts.map +1 -1
  28. package/dist/client.js +105 -13
  29. package/dist/client.js.map +1 -1
  30. package/dist/facades.d.ts.map +1 -1
  31. package/dist/facades.js +7 -3
  32. package/dist/facades.js.map +1 -1
  33. package/dist/version.d.ts +1 -1
  34. package/dist/version.js +1 -1
  35. package/package.json +1 -1
  36. package/_packed_docs//345/217/221/345/270/203/346/212/245/345/221/212-0.5.6.md +0 -260
package/dist/bundle.js CHANGED
@@ -460,7 +460,7 @@ var init_indexeddb_store = __esm({
460
460
  });
461
461
 
462
462
  // src/version.ts
463
- var VERSION = "0.5.8";
463
+ var VERSION = "0.5.9";
464
464
 
465
465
  // src/types.ts
466
466
  var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
@@ -4511,6 +4511,9 @@ var MessageDeliveryEngine = class {
4511
4511
  __publicField(this, "realtimeSyncing", null);
4512
4512
  __publicField(this, "pendingP2pPullUpper", null);
4513
4513
  __publicField(this, "pendingGroupPullUpper", null);
4514
+ __publicField(this, "pendingPullNoProgressAcks", null);
4515
+ __publicField(this, "onlineUnreadHintTargets", null);
4516
+ __publicField(this, "onlineUnreadHintOwners", null);
4514
4517
  __publicField(this, "realtimeAcking", null);
4515
4518
  __publicField(this, "pendingP2PInlineAcks", null);
4516
4519
  __publicField(this, "pendingGroupInlineAcks", null);
@@ -4523,9 +4526,13 @@ var MessageDeliveryEngine = class {
4523
4526
  }
4524
4527
  resetInlineAckState() {
4525
4528
  this.inlineGeneration += 1;
4529
+ void this.runtime.client._rpcPipeline?.invalidatePulls?.();
4526
4530
  this.realtimeSyncing = null;
4527
4531
  this.pendingP2pPullUpper = null;
4528
4532
  this.pendingGroupPullUpper = null;
4533
+ this.pendingPullNoProgressAcks = null;
4534
+ this.onlineUnreadHintTargets = null;
4535
+ this.onlineUnreadHintOwners = null;
4529
4536
  this.realtimeTailResults = null;
4530
4537
  this.realtimeAcking = null;
4531
4538
  this.pendingP2PInlineAcks = null;
@@ -4535,6 +4542,16 @@ var MessageDeliveryEngine = class {
4535
4542
  isInlineGenerationCurrent(generation) {
4536
4543
  return generation === this.inlineGeneration;
4537
4544
  }
4545
+ isPullOperationCurrent() {
4546
+ const client = this.runtime.client;
4547
+ const generation = client._pullOperationGeneration;
4548
+ if (generation === void 0) return true;
4549
+ const pipeline = client._rpcPipeline;
4550
+ return typeof pipeline?.isPullGenerationCurrent === "function" ? pipeline.isPullGenerationCurrent(generation) : true;
4551
+ }
4552
+ ensurePullOperationCurrent() {
4553
+ if (!this.isPullOperationCurrent()) throw new Error("pull invalidated");
4554
+ }
4538
4555
  captureInlineGeneration() {
4539
4556
  return this.inlineGeneration;
4540
4557
  }
@@ -4565,7 +4582,15 @@ var MessageDeliveryEngine = class {
4565
4582
  }
4566
4583
  consumePendingPull(ns, throughSeq) {
4567
4584
  const pending = ns.startsWith("p2p:") ? this.pendingP2pPullUpper : this.pendingGroupPullUpper;
4568
- if ((pending?.get(ns) ?? 0) <= throughSeq) pending?.delete(ns);
4585
+ if ((pending?.get(ns) ?? 0) <= throughSeq) {
4586
+ pending?.delete(ns);
4587
+ this.pendingPullNoProgressAcks?.delete(ns);
4588
+ }
4589
+ }
4590
+ markPendingPullNoProgress(ns, ack) {
4591
+ const blocked = this.pendingPullNoProgressAcks ?? /* @__PURE__ */ new Map();
4592
+ this.pendingPullNoProgressAcks = blocked;
4593
+ blocked.set(ns, ack);
4569
4594
  }
4570
4595
  schedulePendingPullIfNeeded(ns, reason) {
4571
4596
  const pending = ns.startsWith("p2p:") ? this.pendingP2pPullUpper : this.pendingGroupPullUpper;
@@ -4573,15 +4598,20 @@ var MessageDeliveryEngine = class {
4573
4598
  const upper = pending.get(ns) ?? 0;
4574
4599
  if (upper <= 0) {
4575
4600
  pending.delete(ns);
4601
+ this.pendingPullNoProgressAcks?.delete(ns);
4576
4602
  return false;
4577
4603
  }
4578
4604
  const client = this.runtime.client;
4579
4605
  const contiguous = client._seqTracker.getContiguousSeq(ns);
4580
4606
  if (upper <= contiguous) {
4581
4607
  pending.delete(ns);
4608
+ this.pendingPullNoProgressAcks?.delete(ns);
4582
4609
  client._clientLog?.debug(`pending pull upper already covered: ns=${ns}, upper_seq=${upper}, contiguous=${contiguous}, reason=${reason}`);
4583
4610
  return false;
4584
4611
  }
4612
+ const blockedAck = this.pendingPullNoProgressAcks?.get(ns);
4613
+ if (blockedAck !== void 0 && contiguous <= blockedAck) return false;
4614
+ this.pendingPullNoProgressAcks?.delete(ns);
4585
4615
  if (client.state !== "ready" /* READY */ || client._closing || this.realtimeSyncing?.has(ns) || client._rpcPipeline?.hasPullActivity?.(ns, false)) return false;
4586
4616
  pending.delete(ns);
4587
4617
  client._clientLog?.debug(`pending push follow-up pull scheduled: ns=${ns}, upper_seq=${upper}, reason=${reason}`);
@@ -4947,6 +4977,15 @@ var MessageDeliveryEngine = class {
4947
4977
  value = row.effective_ack_seq;
4948
4978
  } else if (Object.prototype.hasOwnProperty.call(row, "ack_seq")) {
4949
4979
  value = row.ack_seq;
4980
+ } else if (Object.prototype.hasOwnProperty.call(row, "cursor")) {
4981
+ const cursor = row.cursor;
4982
+ const cursorRow = isJsonObject(cursor) ? cursor : null;
4983
+ if (cursorRow) {
4984
+ if (!Object.prototype.hasOwnProperty.call(cursorRow, "current_seq")) return 0;
4985
+ value = cursorRow.current_seq;
4986
+ } else {
4987
+ value = cursor;
4988
+ }
4950
4989
  } else {
4951
4990
  return requestedSeq;
4952
4991
  }
@@ -4961,6 +5000,7 @@ var MessageDeliveryEngine = class {
4961
5000
  }
4962
5001
  async confirmPlainForwardAck(ns, method, ackSeq, groupId = "") {
4963
5002
  const client = this.runtime.client;
5003
+ this.ensurePullOperationCurrent();
4964
5004
  const coordinator = this.forwardCoordinator();
4965
5005
  const generation = this.captureInlineGeneration();
4966
5006
  coordinator.recordForwardAck(ns, ackSeq);
@@ -4977,6 +5017,7 @@ var MessageDeliveryEngine = class {
4977
5017
  _rpc_background: true
4978
5018
  };
4979
5019
  const result = await client._rpcPipeline.rawCall(method, params2, { background: true });
5020
+ this.ensurePullOperationCurrent();
4980
5021
  const actualAckSeq = this.resolveForwardAckSeq(result, ackSeq);
4981
5022
  if (actualAckSeq < ackSeq) {
4982
5023
  throw new Error(`${method} server ACK watermark ${actualAckSeq} is below requested ${ackSeq}`);
@@ -4993,6 +5034,7 @@ var MessageDeliveryEngine = class {
4993
5034
  throw new Error(`${method} response must be an object`);
4994
5035
  }
4995
5036
  const client = this.runtime.client;
5037
+ this.ensurePullOperationCurrent();
4996
5038
  const response = result;
4997
5039
  const messages = deduplicatePlainForwardPageMessages(response.messages, afterSeq);
4998
5040
  const coordinator = this.forwardCoordinator();
@@ -5020,24 +5062,29 @@ var MessageDeliveryEngine = class {
5020
5062
  let committed = false;
5021
5063
  try {
5022
5064
  for (const rawMessage of messages) {
5065
+ this.ensurePullOperationCurrent();
5023
5066
  const seq2 = positiveSafeSequenceHint(rawMessage.seq);
5024
5067
  if (method === "message.pull") {
5025
5068
  const appEvent = p2pAppEventFromPlainPullMessage(rawMessage);
5026
5069
  if (await this.publishPulledMessage(appEvent.event, ns, seq2, appEvent.payload, false)) {
5027
5070
  publishedCount += 1;
5028
5071
  }
5072
+ this.ensurePullOperationCurrent();
5029
5073
  continue;
5030
5074
  }
5031
5075
  const message = normalizeGroupMentionMode(rawMessage);
5032
5076
  if (this.recallEventFromGroupMessage(message)) {
5033
5077
  if (await this.publishGroupRecallTombstone(groupId, seq2, message)) {
5078
+ this.ensurePullOperationCurrent();
5034
5079
  this.markPublishedSeq(ns, seq2);
5035
5080
  publishedCount += 1;
5036
5081
  }
5037
5082
  } else if (await this.publishPulledMessage("group.message_created", ns, seq2, message, false)) {
5083
+ this.ensurePullOperationCurrent();
5038
5084
  publishedCount += 1;
5039
5085
  }
5040
5086
  }
5087
+ this.ensurePullOperationCurrent();
5041
5088
  if (messages.length > 0) client._seqTracker.onPullResult(ns, messages, afterSeq);
5042
5089
  const commitTarget = Math.max(
5043
5090
  client._seqTracker.getContiguousSeq(ns),
@@ -5049,17 +5096,20 @@ var MessageDeliveryEngine = class {
5049
5096
  client._seqTracker.forceContiguousSeq(ns, commitTarget);
5050
5097
  }
5051
5098
  if (client._seqTracker.getContiguousSeq(ns) !== pageContigBefore) {
5099
+ this.ensurePullOperationCurrent();
5052
5100
  await this.drainOrderedMessages(ns, void 0, false, false);
5101
+ this.ensurePullOperationCurrent();
5053
5102
  await client._commitSeqTrackerState(ns);
5054
5103
  }
5055
5104
  committed = true;
5056
5105
  } catch (exc) {
5057
- if (!committed && typeof client._seqTracker.restoreNamespaceSnapshot === "function") {
5106
+ if (this.isPullOperationCurrent() && !committed && typeof client._seqTracker.restoreNamespaceSnapshot === "function") {
5058
5107
  client._seqTracker.restoreNamespaceSnapshot(ns, pageTrackerSnapshot);
5059
5108
  this.dropSeqTrackerPending(ns);
5060
5109
  }
5061
5110
  throw exc;
5062
5111
  }
5112
+ this.ensurePullOperationCurrent();
5063
5113
  const committedAck = client._seqTracker.getContiguousSeq(ns);
5064
5114
  if (deferredServerCursor > 0 && committedAck >= deferredServerCursor) {
5065
5115
  coordinator.clearForwardCursor(ns, committedAck);
@@ -5092,6 +5142,7 @@ var MessageDeliveryEngine = class {
5092
5142
  if (clampedAckSeq < pendingAckSeq) {
5093
5143
  throw new Error(`${ackMethod} cannot confirm pending Forward watermark ${pendingAckSeq}`);
5094
5144
  }
5145
+ this.ensurePullOperationCurrent();
5095
5146
  await this.confirmPlainForwardAck(ns, ackMethod, pendingAckSeq, groupId);
5096
5147
  }
5097
5148
  return { rawCount: messages.length, publishedCount };
@@ -6234,9 +6285,30 @@ var MessageDeliveryEngine = class {
6234
6285
  const groupId = String(data.group_id ?? "").trim();
6235
6286
  if (!groupId) return;
6236
6287
  const ns = `group:${groupId}`;
6237
- client._safeAsync(this.runGroupForwardRecovery(groupId, ns, 50, true).catch((exc) => {
6238
- client._clientLog?.debug(`online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
6239
- }));
6288
+ const targets = this.onlineUnreadHintTargets ?? /* @__PURE__ */ new Map();
6289
+ const owners = this.onlineUnreadHintOwners ?? /* @__PURE__ */ new Set();
6290
+ this.onlineUnreadHintTargets = targets;
6291
+ this.onlineUnreadHintOwners = owners;
6292
+ targets.set(ns, Math.max(targets.get(ns) ?? 0, positiveSafeSequenceHint(data.seq)));
6293
+ if (owners.has(ns)) return;
6294
+ owners.add(ns);
6295
+ client._safeAsync((async () => {
6296
+ try {
6297
+ while (true) {
6298
+ const throughSeq = targets.get(ns) ?? 0;
6299
+ const ackBefore = this.syncState(ns).ack;
6300
+ await this.runGroupForwardRecovery(groupId, ns, 50, true, throughSeq);
6301
+ const ackAfter = this.syncState(ns).ack;
6302
+ const target = targets.get(ns) ?? 0;
6303
+ if (ackAfter >= target || ackAfter <= ackBefore) return;
6304
+ }
6305
+ } catch (exc) {
6306
+ client._clientLog?.debug(`online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
6307
+ } finally {
6308
+ if (this.onlineUnreadHintTargets === targets) targets.delete(ns);
6309
+ if (this.onlineUnreadHintOwners === owners) owners.delete(ns);
6310
+ }
6311
+ })());
6240
6312
  }
6241
6313
  enqueueOnlineUnreadEventHint(data) {
6242
6314
  const client = this.runtime.client;
@@ -6266,17 +6338,19 @@ var MessageDeliveryEngine = class {
6266
6338
  const key = pipeline.pullGateKeyForCall("message.pull", request);
6267
6339
  return await pipeline.runPullSerialized(key, invoke, background);
6268
6340
  }
6269
- async runGroupForwardRecovery(groupId, ns, pageLimit, background = false) {
6341
+ async runGroupForwardRecovery(groupId, ns, pageLimit, background = false, throughSeq = 0) {
6270
6342
  const client = this.runtime.client;
6343
+ const state = this.syncState(ns);
6344
+ const maxPages = this.forwardMaxPages(state.ack, throughSeq, pageLimit);
6271
6345
  const request = {
6272
6346
  group_id: groupId,
6273
- after_seq: this.syncState(ns).ack,
6347
+ after_seq: state.ack,
6274
6348
  limit: pageLimit,
6275
- max_pages: 1
6349
+ max_pages: maxPages
6276
6350
  };
6277
6351
  const invoke = async () => {
6278
6352
  const after = this.syncState(ns).ack;
6279
- const messages = await client._pullGroupV2(groupId, after, pageLimit, { gateLocked: true, maxPages: 1 });
6353
+ const messages = await client._pullGroupV2(groupId, after, pageLimit, { gateLocked: true, maxPages });
6280
6354
  return { messages, raw_count: messages.length };
6281
6355
  };
6282
6356
  const pipeline = client._rpcPipeline;
@@ -6290,6 +6364,10 @@ var MessageDeliveryEngine = class {
6290
6364
  const ack = Number(tracker.getContiguousSeq(ns) || 0);
6291
6365
  return { ack, tail: ack, head: Math.max(ack, Number(tracker.getMaxSeenSeq?.(ns) || 0)) };
6292
6366
  }
6367
+ forwardMaxPages(ack, throughSeq, pageLimit) {
6368
+ const limit = Number.isSafeInteger(pageLimit) && pageLimit > 0 ? pageLimit : 1;
6369
+ return Math.max(1, Math.ceil(Math.max(0, throughSeq - ack) / limit));
6370
+ }
6293
6371
  inlineMessage(data) {
6294
6372
  if (!isJsonObject(data)) return null;
6295
6373
  const inline = data.inline_message;
@@ -6621,17 +6699,28 @@ var MessageDeliveryEngine = class {
6621
6699
  if (forceTail || order[0] === "tail") result = await run(true, false, void 0, false, forceTail);
6622
6700
  const state = this.syncState(ns);
6623
6701
  const tailMissedDuringPull = tailCompleted && Number(client._seqTracker.getMaxSeenSeq(ns) || 0) > state.head;
6624
- const pendingThroughSeq = tailMissedDuringPull ? Number(client._seqTracker.getMaxSeenSeq(ns) || 0) : 0;
6702
+ const maxSeen = Number(client._seqTracker.getMaxSeenSeq(ns) || 0);
6703
+ const pendingThroughSeq = tailMissedDuringPull ? maxSeen : 0;
6625
6704
  const tailForward = tailCompleted && (state.ack < state.tail - 1 || tailMissedDuringPull);
6626
6705
  const backgroundWindowForward = order[0] === "forward" && !headForward;
6627
6706
  if (headForward || tailForward || client._sessionOptions?.background_sync !== false && backgroundWindowForward) {
6707
+ const forwardTarget = Math.max(
6708
+ headForward ? Math.max(pushSeq, maxSeen) : 0,
6709
+ tailForward || backgroundWindowForward ? state.tail - 1 : 0,
6710
+ tailMissedDuringPull ? maxSeen : 0
6711
+ );
6712
+ const forwardMaxPages = this.forwardMaxPages(state.ack, forwardTarget, pageLimit);
6628
6713
  result = await run(
6629
6714
  false,
6630
6715
  !(headForward || tailForward),
6631
- headForward ? 2 : order[0] === "tail" ? 1 : void 0,
6716
+ forwardMaxPages,
6632
6717
  headForward || tailMissedDuringPull
6633
6718
  );
6634
- if (pendingThroughSeq > 0) this.consumePendingPull(ns, pendingThroughSeq);
6719
+ if (pendingThroughSeq > 0) {
6720
+ const committedAck = this.syncState(ns).ack;
6721
+ if (committedAck >= pendingThroughSeq) this.consumePendingPull(ns, pendingThroughSeq);
6722
+ else if (committedAck <= state.ack) this.markPendingPullNoProgress(ns, committedAck);
6723
+ }
6635
6724
  }
6636
6725
  const finalState = this.syncState(ns);
6637
6726
  if (tailCompleted && finalState.ack < finalState.tail - 1) {
@@ -6678,17 +6767,28 @@ var MessageDeliveryEngine = class {
6678
6767
  if (forceTail || order[0] === "tail") result = await run(true, false, void 0, false, forceTail);
6679
6768
  const state = this.syncState(ns);
6680
6769
  const tailMissedDuringPull = tailCompleted && Number(client._seqTracker.getMaxSeenSeq(ns) || 0) > state.head;
6681
- const pendingThroughSeq = tailMissedDuringPull ? Number(client._seqTracker.getMaxSeenSeq(ns) || 0) : 0;
6770
+ const maxSeen = Number(client._seqTracker.getMaxSeenSeq(ns) || 0);
6771
+ const pendingThroughSeq = tailMissedDuringPull ? maxSeen : 0;
6682
6772
  const tailForward = tailCompleted && (state.ack < state.tail - 1 || tailMissedDuringPull);
6683
6773
  const backgroundWindowForward = order[0] === "forward" && !headForward;
6684
6774
  if (headForward || tailForward || client._sessionOptions?.background_sync !== false && backgroundWindowForward) {
6775
+ const forwardTarget = Math.max(
6776
+ headForward ? Math.max(pushSeq, maxSeen) : 0,
6777
+ tailForward || backgroundWindowForward ? state.tail - 1 : 0,
6778
+ tailMissedDuringPull ? maxSeen : 0
6779
+ );
6780
+ const forwardMaxPages = this.forwardMaxPages(state.ack, forwardTarget, pageLimit);
6685
6781
  result = await run(
6686
6782
  false,
6687
6783
  !(headForward || tailForward),
6688
- headForward ? 2 : order[0] === "tail" ? 1 : void 0,
6784
+ forwardMaxPages,
6689
6785
  headForward || tailMissedDuringPull
6690
6786
  );
6691
- if (pendingThroughSeq > 0) this.consumePendingPull(ns, pendingThroughSeq);
6787
+ if (pendingThroughSeq > 0) {
6788
+ const committedAck = this.syncState(ns).ack;
6789
+ if (committedAck >= pendingThroughSeq) this.consumePendingPull(ns, pendingThroughSeq);
6790
+ else if (committedAck <= state.ack) this.markPendingPullNoProgress(ns, committedAck);
6791
+ }
6692
6792
  }
6693
6793
  const finalState = this.syncState(ns);
6694
6794
  if (tailCompleted && finalState.ack < finalState.tail - 1) {
@@ -7245,22 +7345,28 @@ var MessageDeliveryEngine = class {
7245
7345
  }
7246
7346
  async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true) {
7247
7347
  const client = this.runtime.client;
7348
+ this.ensurePullOperationCurrent();
7248
7349
  const queue = client._pendingOrderedMsgs.get(ns);
7249
7350
  if (!queue || queue.size === 0) return;
7250
7351
  const contig = client._seqTracker.getContiguousSeq(ns);
7251
7352
  const ready = [...queue.keys()].filter((seq2) => seq2 <= contig && (beforeSeq === void 0 || seq2 < beforeSeq)).sort((a, b) => a - b);
7252
7353
  let delivered = false;
7253
7354
  for (const seq2 of ready) {
7355
+ this.ensurePullOperationCurrent();
7254
7356
  const item = queue.get(seq2);
7255
7357
  queue.delete(seq2);
7256
7358
  if (!item || client._pushedSeqs.get(ns)?.has(seq2)) continue;
7257
7359
  await this.publishOrderedQueueItem(ns, item.event, seq2, item.payload, pullResponse);
7360
+ this.ensurePullOperationCurrent();
7258
7361
  this.markPublishedSeq(ns, seq2);
7259
7362
  delivered = true;
7260
7363
  }
7261
7364
  if (queue.size === 0) {
7262
7365
  client._pendingOrderedMsgs.delete(ns);
7263
- if (delivered && persist) await this.saveSeqTrackerState();
7366
+ if (delivered && persist) {
7367
+ this.ensurePullOperationCurrent();
7368
+ await this.saveSeqTrackerState();
7369
+ }
7264
7370
  }
7265
7371
  }
7266
7372
  async publishOrderedMessage(event, ns, seq2, payload) {
@@ -7294,15 +7400,19 @@ var MessageDeliveryEngine = class {
7294
7400
  }
7295
7401
  async publishPulledMessage(event, ns, seq2, payload, persist = true) {
7296
7402
  const client = this.runtime.client;
7403
+ this.ensurePullOperationCurrent();
7297
7404
  const seqNum = Number(seq2);
7298
7405
  if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
7299
7406
  if (event === "message.recalled") {
7300
- return await client._withPullResponseProcessing(
7407
+ const published = await client._withPullResponseProcessing(
7301
7408
  ns,
7302
7409
  () => this.publishMessageRecallTombstone(seq2, payload)
7303
7410
  );
7411
+ this.ensurePullOperationCurrent();
7412
+ return published;
7304
7413
  }
7305
7414
  await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload));
7415
+ this.ensurePullOperationCurrent();
7306
7416
  return true;
7307
7417
  }
7308
7418
  const queue = client._pendingOrderedMsgs.get(ns);
@@ -7312,6 +7422,7 @@ var MessageDeliveryEngine = class {
7312
7422
  return false;
7313
7423
  }
7314
7424
  await this.drainOrderedMessages(ns, seqNum, false, persist);
7425
+ this.ensurePullOperationCurrent();
7315
7426
  queue?.delete(seqNum);
7316
7427
  if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
7317
7428
  if (event === "message.recalled") {
@@ -7319,10 +7430,12 @@ var MessageDeliveryEngine = class {
7319
7430
  ns,
7320
7431
  () => this.publishMessageRecallTombstone(seqNum, payload)
7321
7432
  );
7433
+ this.ensurePullOperationCurrent();
7322
7434
  this.markPublishedSeq(ns, seqNum);
7323
7435
  return published;
7324
7436
  }
7325
7437
  await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload));
7438
+ this.ensurePullOperationCurrent();
7326
7439
  this.markPublishedSeq(ns, seqNum);
7327
7440
  return true;
7328
7441
  }
@@ -7884,11 +7997,14 @@ var LifecycleController = class {
7884
7997
  return;
7885
7998
  }
7886
7999
  client._delivery.resetInlineAckState();
8000
+ const pullInvalidation = client._rpcPipeline?.invalidatePulls?.();
8001
+ client._rpcPipeline?.stopPullGateWatchdogs?.();
8002
+ await client._transport.close();
8003
+ await pullInvalidation;
7887
8004
  await client._cancelReconnectAndWait();
7888
8005
  await this.cancelConnectionAttemptAndWait();
7889
8006
  await client._saveSeqTrackerState();
7890
8007
  client._stopBackgroundTasks();
7891
- await client._transport.close();
7892
8008
  if (client._closing) return;
7893
8009
  this.runtime.lifecycle.resetForDisconnect("standby");
7894
8010
  await this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
@@ -7909,22 +8025,30 @@ var LifecycleController = class {
7909
8025
  return;
7910
8026
  }
7911
8027
  return this.withLifecycleStop(async () => {
7912
- await client._cancelReconnectAndWait();
7913
- await this.cancelConnectionAttemptAndWait();
7914
- await client._saveSeqTrackerState();
8028
+ const pullInvalidation = client._rpcPipeline?.invalidatePulls?.();
8029
+ client._rpcPipeline?.stopPullGateWatchdogs?.();
7915
8030
  client._stopBackgroundTasks();
7916
8031
  if (client._state === "idle" || client._state === "closed") {
8032
+ const reconnectCancellation2 = client._cancelReconnectAndWait();
8033
+ const connectionCancellation2 = this.cancelConnectionAttemptAndWait();
8034
+ await Promise.all([reconnectCancellation2, connectionCancellation2]);
8035
+ await pullInvalidation;
8036
+ await client._saveSeqTrackerState();
7917
8037
  this.runtime.lifecycle.setState("closed");
7918
8038
  client._resetSeqTrackingState();
7919
8039
  client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms reason=already_idle`);
7920
8040
  return;
7921
8041
  }
8042
+ const reconnectCancellation = client._cancelReconnectAndWait();
8043
+ const connectionCancellation = this.cancelConnectionAttemptAndWait();
7922
8044
  try {
7923
8045
  await client._transport.call("auth.logout", {});
7924
8046
  } catch (err) {
7925
8047
  client._clientLog.warn(`auth.logout during close failed: ${err instanceof Error ? err.message : String(err)}`);
7926
8048
  }
7927
8049
  await client._transport.close();
8050
+ await Promise.all([pullInvalidation, reconnectCancellation, connectionCancellation]);
8051
+ await client._saveSeqTrackerState();
7928
8052
  this.runtime.lifecycle.setState("closed");
7929
8053
  await this.publishLifecycleStopEvent({ state: client._publicState(client._state) });
7930
8054
  client._resetSeqTrackingState();
@@ -14829,6 +14953,7 @@ var SIGNED_METHODS = /* @__PURE__ */ new Set([
14829
14953
  "group.resume"
14830
14954
  ]);
14831
14955
  var SIGNING_KEY_CACHE_MAX = 32;
14956
+ var IDENTITY_ADMISSION_RETRY_DELAYS_MS = [0, 50, 100, 200];
14832
14957
  var signingKeyCache = /* @__PURE__ */ new Map();
14833
14958
  var certFingerprintCache = /* @__PURE__ */ new Map();
14834
14959
  function cacheGet(cache, key) {
@@ -14883,6 +15008,13 @@ async function signingCertFingerprint(certPem) {
14883
15008
  }
14884
15009
  var PULL_GATE_STALE_MS = 3e4;
14885
15010
  var PULL_GATE_OPERATION_TIMEOUT_MS = 3e3;
15011
+ function sameIdentityAdmissionRejection(error, original) {
15012
+ const errorCode2 = Number(error?.code);
15013
+ const originalCode = Number(original?.code);
15014
+ const errorMessage3 = String(error instanceof Error ? error.message : error?.message ?? "").trim().toLowerCase();
15015
+ const originalMessage = String(original instanceof Error ? original.message : original?.message ?? "").trim().toLowerCase();
15016
+ return errorCode2 === -32003 && originalCode === -32003 && errorMessage3 === originalMessage;
15017
+ }
14886
15018
  var NON_IDEMPOTENT_TIMEOUT = 35;
14887
15019
  var NON_IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
14888
15020
  "message.send",
@@ -14979,6 +15111,8 @@ var RpcPipeline = class {
14979
15111
  __publicField(this, "runtime");
14980
15112
  __publicField(this, "pullGateStates", /* @__PURE__ */ new Map());
14981
15113
  __publicField(this, "inlineRealtimeScopes", /* @__PURE__ */ new Set());
15114
+ __publicField(this, "pullGeneration", 0);
15115
+ __publicField(this, "pullInvalidationWait", null);
14982
15116
  this.runtime = runtime;
14983
15117
  }
14984
15118
  async call(method, params2) {
@@ -15348,19 +15482,21 @@ var RpcPipeline = class {
15348
15482
  if (method === "message.pull" || method === "message.v2.pull" || method === "message.history") {
15349
15483
  if (!client._aid) return "";
15350
15484
  const mode = method === "message.history" ? "history" : params2.window_mode === "tail" ? "tail" : "forward";
15351
- const cursor = mode === "history" ? `before=${String(params2.before_seq)}` : `after=${String(params2.after_seq ?? 0)}|force=${String(Boolean(params2.force))}`;
15352
- return `p2p:${client._aid}|${mode}|${cursor}|limit=${String(params2.limit)}`;
15485
+ const cursor = mode === "history" ? `before=${String(params2.before_seq)}` : `after=${String(params2.after_seq ?? 0)}`;
15486
+ return `p2p:${client._aid}|mode=${mode}|cursor=${cursor}|force=${String(Boolean(params2.force))}|limit=${String(params2.limit)}`;
15353
15487
  }
15354
15488
  if (method === "group.pull" || method === "group.v2.pull" || method === "group.history") {
15355
15489
  const gid = String(params2.group_id ?? params2.group_aid ?? "").trim();
15356
15490
  if (!gid) return "";
15357
15491
  const mode = method === "group.history" ? "history" : params2.window_mode === "tail" ? "tail" : "forward";
15358
- const cursor = mode === "history" ? `before=${String(params2.before_seq)}` : `after=${String(params2.after_seq ?? params2.after_message_seq ?? 0)}|force=${String(Boolean(params2.force))}`;
15359
- return `group:${gid}|${mode}|${cursor}|limit=${String(params2.limit)}`;
15492
+ const cursor = mode === "history" ? `before=${String(params2.before_seq)}` : `after=${String(params2.after_seq ?? params2.after_message_seq ?? 0)}`;
15493
+ const explicitCursor = this.explicitGroupCursorParams(params2);
15494
+ const cursorSuffix = Object.keys(explicitCursor).length > 0 ? `|cursor_params=${stableStringify(explicitCursor)}` : "";
15495
+ return `group:${gid}|mode=${mode}|cursor=${cursor}${cursorSuffix}|force=${String(Boolean(params2.force))}|limit=${String(params2.limit)}`;
15360
15496
  }
15361
15497
  if (method === "group.pull_events") {
15362
15498
  const gid = String(params2.group_id ?? params2.group_aid ?? "").trim();
15363
- return gid ? `group_event:${gid}|forward|after=${String(params2.after_event_seq ?? 0)}|limit=${String(params2.limit)}` : "";
15499
+ return gid ? `group_event:${gid}|mode=forward|cursor=after=${String(params2.after_event_seq ?? 0)}|force=${String(Boolean(params2.force))}|limit=${String(params2.limit)}` : "";
15364
15500
  }
15365
15501
  return "";
15366
15502
  }
@@ -15380,8 +15516,8 @@ var RpcPipeline = class {
15380
15516
  if (!state) {
15381
15517
  state = { active: null, foreground: [], background: [], byKey: /* @__PURE__ */ new Map(), lifecycle: /* @__PURE__ */ new Map(), watchdog: null };
15382
15518
  this.pullGateStates.set(name, state);
15383
- this.startPullGateWatchdog(state, name);
15384
15519
  }
15520
+ this.startPullGateWatchdog(state, name);
15385
15521
  return state;
15386
15522
  }
15387
15523
  pullGateOperationTimeoutMs() {
@@ -15398,6 +15534,13 @@ var RpcPipeline = class {
15398
15534
  state.watchdog = timer;
15399
15535
  timer.unref?.();
15400
15536
  }
15537
+ stopPullGateWatchdogs() {
15538
+ for (const state of this.pullGateStates.values()) {
15539
+ if (state.watchdog === null) continue;
15540
+ clearInterval(state.watchdog);
15541
+ state.watchdog = null;
15542
+ }
15543
+ }
15401
15544
  checkPullGate(state, name) {
15402
15545
  const active = state.active;
15403
15546
  if (!active) {
@@ -15407,17 +15550,12 @@ var RpcPipeline = class {
15407
15550
  if (!active.pullingStartedAt || Date.now() - active.pullingStartedAt < this.pullGateOperationTimeoutMs()) return;
15408
15551
  if (active.timedOut) return;
15409
15552
  active.timedOut = true;
15553
+ active.invalidated = true;
15554
+ this.pullGeneration += 1;
15410
15555
  active.cancel?.();
15411
15556
  const err = new TimeoutError(`pull gate timeout: ${active.namespace}`, { retryable: true });
15412
15557
  this.runtime.client._clientLog?.warn(`pull gate watchdog timeout: gate=${name} key=${active.key}`);
15413
- state.lifecycle.set(active.namespace, "idle");
15414
- if (state.active === active) state.active = null;
15415
- if (state.byKey.get(active.key) === active) state.byKey.delete(active.key);
15416
- active.resolve = () => {
15417
- };
15418
15558
  active.reject(err);
15419
- this.drainPullGate(state);
15420
- this.runtime.client._delivery?.onPullGateIdle?.(active.namespace);
15421
15559
  }
15422
15560
  realtimePullNamespace(key) {
15423
15561
  for (const marker of ["|tail|", "|forward|"]) {
@@ -15436,7 +15574,7 @@ var RpcPipeline = class {
15436
15574
  if (typeof cancel !== "function" || !key) return request;
15437
15575
  const state = this.pullGateStates.get(this.pullGateName(key));
15438
15576
  const active = state?.active;
15439
- if (!active || active.namespace !== this.pullScopeKey(key) || active.timedOut) return request;
15577
+ if (!active || active.key !== key || active.timedOut || active.invalidated) return request;
15440
15578
  const boundCancel = () => cancel.call(request);
15441
15579
  active.cancel = boundCancel;
15442
15580
  return request.finally(() => {
@@ -15522,7 +15660,10 @@ var RpcPipeline = class {
15522
15660
  resumeResolve: null,
15523
15661
  pullingStartedAt: 0,
15524
15662
  timedOut: false,
15525
- cancel: null
15663
+ invalidated: false,
15664
+ cancel: null,
15665
+ settled: Promise.resolve(),
15666
+ settledResolve: null
15526
15667
  };
15527
15668
  this.inlineRealtimeScopes.add(normalized);
15528
15669
  try {
@@ -15562,14 +15703,59 @@ var RpcPipeline = class {
15562
15703
  gate.inflight = false;
15563
15704
  gate.startedAt = 0;
15564
15705
  }
15706
+ isPullGenerationCurrent(generation) {
15707
+ return generation === this.pullGeneration;
15708
+ }
15709
+ invalidatePulls() {
15710
+ if (this.pullInvalidationWait) return this.pullInvalidationWait;
15711
+ this.pullGeneration += 1;
15712
+ const error = new Error("pull invalidated");
15713
+ const waits = [];
15714
+ for (const state of this.pullGateStates.values()) {
15715
+ const jobs = /* @__PURE__ */ new Set();
15716
+ if (state.active) jobs.add(state.active);
15717
+ for (const job of state.foreground) jobs.add(job);
15718
+ for (const job of state.background) jobs.add(job);
15719
+ for (const job of jobs) {
15720
+ job.invalidated = true;
15721
+ if (!job.timedOut) job.reject(error);
15722
+ job.cancel?.();
15723
+ job.resumeResolve?.();
15724
+ job.resumeResolve = null;
15725
+ if (job.running) {
15726
+ waits.push(job.settled);
15727
+ } else {
15728
+ job.settledResolve?.();
15729
+ job.settledResolve = null;
15730
+ }
15731
+ }
15732
+ state.foreground = state.foreground.filter((job) => !jobs.has(job));
15733
+ state.background = state.background.filter((job) => !jobs.has(job));
15734
+ for (const [key, job] of state.byKey.entries()) {
15735
+ if (jobs.has(job) && !job.running) state.byKey.delete(key);
15736
+ }
15737
+ this.drainPullGate(state);
15738
+ }
15739
+ const wait = Promise.all(waits).then(() => void 0);
15740
+ let tracked;
15741
+ tracked = wait.finally(() => {
15742
+ if (this.pullInvalidationWait === tracked) this.pullInvalidationWait = null;
15743
+ });
15744
+ this.pullInvalidationWait = tracked;
15745
+ return tracked;
15746
+ }
15747
+ pullInvalidationInProgress() {
15748
+ return this.pullInvalidationWait !== null;
15749
+ }
15565
15750
  async runPullSerialized(key, operation, background = false) {
15751
+ if (this.pullInvalidationInProgress()) {
15752
+ throw new Error("pull invalidated");
15753
+ }
15566
15754
  if (!key) return await this.executePullOperation(operation, background);
15567
15755
  const state = this.pullGateState(key);
15568
15756
  const namespace = this.pullScopeKey(key);
15569
- const active = state.active?.namespace === namespace ? state.active : null;
15570
- const queuedForeground = state.foreground.find((job2) => job2.namespace === namespace);
15571
- const queuedBackground = state.background.find((job2) => job2.namespace === namespace);
15572
- const existing = background ? active ?? queuedForeground ?? queuedBackground : queuedForeground ?? (active && !active.background ? active : null) ?? queuedBackground;
15757
+ const candidate = state.byKey.get(key);
15758
+ const existing = candidate && !candidate.timedOut && !candidate.invalidated ? candidate : null;
15573
15759
  if (existing) {
15574
15760
  if (!background && existing.background && existing !== state.active) {
15575
15761
  const index = state.background.indexOf(existing);
@@ -15581,10 +15767,14 @@ var RpcPipeline = class {
15581
15767
  }
15582
15768
  let resolve;
15583
15769
  let reject;
15770
+ let settledResolve;
15584
15771
  const promise = new Promise((res, rej) => {
15585
15772
  resolve = res;
15586
15773
  reject = rej;
15587
15774
  });
15775
+ const settled = new Promise((resolveSettled) => {
15776
+ settledResolve = resolveSettled;
15777
+ });
15588
15778
  void promise.catch(() => {
15589
15779
  });
15590
15780
  const job = {
@@ -15602,7 +15792,10 @@ var RpcPipeline = class {
15602
15792
  resumeResolve: null,
15603
15793
  pullingStartedAt: 0,
15604
15794
  timedOut: false,
15605
- cancel: null
15795
+ invalidated: false,
15796
+ cancel: null,
15797
+ settled,
15798
+ settledResolve
15606
15799
  };
15607
15800
  (background ? state.background : state.foreground).push(job);
15608
15801
  state.byKey.set(key, job);
@@ -15611,14 +15804,30 @@ var RpcPipeline = class {
15611
15804
  return await promise;
15612
15805
  }
15613
15806
  async executePullOperation(operation, background) {
15614
- if (background) return await this.runtime.client._withBackgroundRpc(operation);
15615
- return await operation();
15807
+ const client = this.runtime.client;
15808
+ const hadPrevious = Object.prototype.hasOwnProperty.call(client, "_pullOperationGeneration");
15809
+ const previous = client._pullOperationGeneration;
15810
+ client._pullOperationGeneration = this.pullGeneration;
15811
+ try {
15812
+ if (background) return await client._withBackgroundRpc(operation);
15813
+ return await operation();
15814
+ } finally {
15815
+ if (hadPrevious) client._pullOperationGeneration = previous;
15816
+ else delete client._pullOperationGeneration;
15817
+ }
15818
+ }
15819
+ pullOperationIsCurrent() {
15820
+ const generation = this.runtime.client._pullOperationGeneration;
15821
+ return generation === void 0 || this.isPullGenerationCurrent(generation);
15822
+ }
15823
+ throwIfPullInvalidated() {
15824
+ if (!this.pullOperationIsCurrent()) throw new Error("pull invalidated");
15616
15825
  }
15617
15826
  async yieldPullGate(key, nextKey, background) {
15618
15827
  if (!key) return;
15619
15828
  const state = this.pullGateState(key);
15620
15829
  const namespace = this.pullScopeKey(key);
15621
- const job = state.active?.namespace === namespace ? state.active : [...state.foreground, ...state.background].find((candidate) => candidate.namespace === namespace);
15830
+ const job = state.active?.key === key ? state.active : null;
15622
15831
  if (!job || state.active !== job) return;
15623
15832
  const replacementKey = String(nextKey || key);
15624
15833
  if (state.byKey.get(job.key) === job) state.byKey.delete(job.key);
@@ -15634,7 +15843,10 @@ var RpcPipeline = class {
15634
15843
  state.active = null;
15635
15844
  (job.background ? state.background : state.foreground).push(job);
15636
15845
  this.drainPullGate(state);
15637
- if (job.resume) await job.resume;
15846
+ if (job.resume) {
15847
+ await job.resume;
15848
+ if (job.invalidated) throw new Error("pull invalidated");
15849
+ }
15638
15850
  }
15639
15851
  getPullLifecycle(namespace) {
15640
15852
  const ns = this.pullScopeKey(namespace);
@@ -15670,13 +15882,21 @@ var RpcPipeline = class {
15670
15882
  return;
15671
15883
  }
15672
15884
  job.running = true;
15673
- void this.executePullOperation(job.operation, job.background).then(job.resolve, job.reject).finally(() => {
15674
- if (job.timedOut) return;
15885
+ void this.executePullOperation(job.operation, job.background).then(
15886
+ (value) => {
15887
+ if (!job.timedOut && !job.invalidated) job.resolve(value);
15888
+ },
15889
+ (error) => {
15890
+ if (!job.timedOut && !job.invalidated) job.reject(error);
15891
+ }
15892
+ ).finally(() => {
15675
15893
  state.lifecycle.set(job.namespace, "idle");
15676
15894
  if (state.active === job) state.active = null;
15677
15895
  if (state.byKey.get(job.key) === job) state.byKey.delete(job.key);
15678
15896
  this.drainPullGate(state);
15679
- this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
15897
+ job.settledResolve?.();
15898
+ job.settledResolve = null;
15899
+ if (!job.invalidated) this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
15680
15900
  });
15681
15901
  }
15682
15902
  armQueuedPullTimers(_state) {
@@ -15699,7 +15919,9 @@ var RpcPipeline = class {
15699
15919
  else request = client._transport.call(method, payload);
15700
15920
  return this.bindActivePullCancellation(method, payload, request);
15701
15921
  };
15702
- return await this.transportCallWithIdentityRecovery(invokeTransport, method, payload);
15922
+ const result = await this.transportCallWithIdentityRecovery(invokeTransport, method, payload);
15923
+ this.throwIfPullInvalidated();
15924
+ return result;
15703
15925
  }
15704
15926
  async transportCallWithIdentityRecovery(operation, method, params2) {
15705
15927
  try {
@@ -15707,11 +15929,22 @@ var RpcPipeline = class {
15707
15929
  } catch (err) {
15708
15930
  const recover = this.runtime.client._recoverIdentityAdmission;
15709
15931
  if (typeof recover !== "function" || !await recover.call(this.runtime.client, err, method, params2)) throw err;
15710
- return await operation();
15932
+ let lastError = err;
15933
+ for (const delay of IDENTITY_ADMISSION_RETRY_DELAYS_MS) {
15934
+ if (delay > 0) await new Promise((resolve) => setTimeout(resolve, delay));
15935
+ try {
15936
+ return await operation();
15937
+ } catch (retryError) {
15938
+ if (!sameIdentityAdmissionRejection(retryError, err)) throw retryError;
15939
+ lastError = retryError;
15940
+ }
15941
+ }
15942
+ throw lastError;
15711
15943
  }
15712
15944
  }
15713
15945
  async postprocessResult(method, params2, result, options = {}) {
15714
15946
  const client = this.runtime.client;
15947
+ this.throwIfPullInvalidated();
15715
15948
  let next = result;
15716
15949
  if ((method === "group.send" || method === "group.v2.send") && isJsonObject(next)) {
15717
15950
  next = normalizeGroupMentionMode(next);
@@ -17412,17 +17645,21 @@ var GroupFacade = class extends RpcFacade {
17412
17645
  }
17413
17646
  create(params2) {
17414
17647
  const split = splitAidStore(params2);
17415
- if (typeof this.client.createGroup === "function") {
17648
+ if (split.params.group_name === void 0 && split.params.groupName !== void 0) {
17649
+ split.params.group_name = split.params.groupName;
17650
+ delete split.params.groupName;
17651
+ }
17652
+ if (split.aidStore && typeof this.client.createGroup === "function") {
17416
17653
  return this.client.createGroup(split.params, { aidStore: split.aidStore });
17417
17654
  }
17418
17655
  return this.call("group.create", split.params);
17419
17656
  }
17420
17657
  bindAid(params2) {
17421
- return this.bindGroupAid(params2);
17658
+ return this.call("group.bind_aid", splitAidStore(params2).params);
17422
17659
  }
17423
17660
  bindGroupAid(params2) {
17424
17661
  const split = splitAidStore(params2);
17425
- if (typeof this.client.bindGroupAid === "function") {
17662
+ if (split.aidStore && typeof this.client.bindGroupAid === "function") {
17426
17663
  return this.client.bindGroupAid(split.params, { aidStore: split.aidStore });
17427
17664
  }
17428
17665
  return this.call("group.bind_group_aid", split.params);
@@ -22795,6 +23032,10 @@ var V2Session = class {
22795
23032
  };
22796
23033
 
22797
23034
  // src/client/v2-e2ee.ts
23035
+ function pullGateKeyForClient(client, method, params2, fallback) {
23036
+ const key = client._rpcPipeline?.pullGateKeyForCall?.(method, params2);
23037
+ return typeof key === "string" && key ? key : fallback;
23038
+ }
22798
23039
  var V2_BOOTSTRAP_TTL_MS = 60 * 60 * 1e3;
22799
23040
  var V2_RETRYABLE_CODES = /* @__PURE__ */ new Set([-33011, -33012, -33050, -33052, -33054]);
22800
23041
  var V2_GROUP_STALE_BOOTSTRAP_CODE = -33054;
@@ -23854,7 +24095,11 @@ var V2E2EECoordinator = class {
23854
24095
  const ns = client._aid ? `p2p:${client._aid}` : "";
23855
24096
  if (opts?.windowMode === "tail") {
23856
24097
  if (!opts.gateLocked) {
23857
- const key = `${ns}|tail|after=${afterSeq}|limit=${limit}`;
24098
+ const key = pullGateKeyForClient(client, "message.v2.pull", {
24099
+ window_mode: "tail",
24100
+ after_seq: afterSeq,
24101
+ limit
24102
+ }, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
23858
24103
  return await client._runPullSerialized(key, () => this.pullV2(afterSeq, limit, {
23859
24104
  ...opts ?? {},
23860
24105
  gateLocked: true
@@ -23864,7 +24109,11 @@ var V2E2EECoordinator = class {
23864
24109
  return Array.isArray(result.messages) ? result.messages : [];
23865
24110
  }
23866
24111
  if (ns && !opts?.gateLocked) {
23867
- const key = `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`;
24112
+ const key = pullGateKeyForClient(client, "message.v2.pull", {
24113
+ after_seq: afterSeq,
24114
+ force: opts?.force === true,
24115
+ limit
24116
+ }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
23868
24117
  return await client._runPullSerialized(key, () => this.pullV2(afterSeq, limit, {
23869
24118
  ...opts ?? {},
23870
24119
  gateLocked: true
@@ -23872,7 +24121,11 @@ var V2E2EECoordinator = class {
23872
24121
  }
23873
24122
  const decrypted = [];
23874
24123
  let nextAfterSeq = opts?.force ? afterSeq : afterSeq || (ns ? client._seqTracker.getContiguousSeq(ns) : 0);
23875
- let pullGateKey = ns ? `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}` : "";
24124
+ let pullGateKey = ns ? pullGateKeyForClient(client, "message.v2.pull", {
24125
+ after_seq: afterSeq,
24126
+ force: opts?.force === true,
24127
+ limit
24128
+ }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`) : "";
23876
24129
  const deferredServerCursor = ns ? this.pendingForwardCursor(ns, client._seqTracker.getContiguousSeq(ns)) : 0;
23877
24130
  const deferredForwardAck = ns ? this.pendingForwardAck(ns, client._seqTracker.getContiguousSeq(ns)) : 0;
23878
24131
  let pageCount = 0;
@@ -23917,6 +24170,7 @@ var V2E2EECoordinator = class {
23917
24170
  const pageTrackerSnapshot = ns && typeof client._seqTracker.snapshotNamespace === "function" ? client._seqTracker.snapshotNamespace(ns) : null;
23918
24171
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
23919
24172
  const deferredKeyFetches = /* @__PURE__ */ new Map();
24173
+ let blockedSeq = 0;
23920
24174
  for (const msg of messages) {
23921
24175
  const seq2 = Number(msg.seq ?? 0);
23922
24176
  if (!Number.isFinite(seq2) || seq2 <= 0) continue;
@@ -23956,6 +24210,9 @@ var V2E2EECoordinator = class {
23956
24210
  }
23957
24211
  const deferStatus = {};
23958
24212
  const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true);
24213
+ if (deferStatus.deferred) {
24214
+ blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq2) : seq2;
24215
+ }
23959
24216
  if (deferStatus.deferred && deferStatus.fromAid) {
23960
24217
  const key = `${deferStatus.fromAid}\0${deferStatus.senderDeviceId ?? ""}\0${deferStatus.groupId ?? ""}`;
23961
24218
  deferredKeyFetches.set(key, {
@@ -23978,7 +24235,7 @@ var V2E2EECoordinator = class {
23978
24235
  const serverAckSeq = parsedServerAckSeq ?? 0;
23979
24236
  const retentionFloor = Math.max(0, Number(result.retention_floor_seq ?? 0));
23980
24237
  if (ns) {
23981
- const commitTarget = Math.max(
24238
+ const commitTarget = blockedSeq > 0 ? pageContigBefore : Math.max(
23982
24239
  pageContigBefore,
23983
24240
  Number.isFinite(retentionFloor) ? retentionFloor : 0,
23984
24241
  pageCount === 1 ? deferredServerCursor : 0,
@@ -24020,7 +24277,7 @@ var V2E2EECoordinator = class {
24020
24277
  this.recordForwardCursor(ns, serverAckSeq, ackSeq);
24021
24278
  }
24022
24279
  const knownServerAckSeq = hasServerAckSeq ? serverAckSeq : pageCount === 1 ? deferredServerCursor : 0;
24023
- const ackNeeded = ackSeq > 0 && ackSeq > lastAutoAckSeq && (hasServerAckSeq && ackSeq > serverAckSeq || contigAdvanced && ackSeq > knownServerAckSeq);
24280
+ const ackNeeded = blockedSeq <= 0 && ackSeq > 0 && ackSeq > lastAutoAckSeq && (hasServerAckSeq && ackSeq > serverAckSeq || contigAdvanced && ackSeq > knownServerAckSeq);
24024
24281
  if (ackNeeded) {
24025
24282
  this.recordForwardAck(ns, ackSeq);
24026
24283
  const nextAfter2 = Math.max(pageMaxSeq, nextAfterSeq);
@@ -24036,6 +24293,7 @@ var V2E2EECoordinator = class {
24036
24293
  }
24037
24294
  const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
24038
24295
  const rawCount = messages.length;
24296
+ if (blockedSeq > 0) break;
24039
24297
  const shouldContinue = shouldContinueForwardPage({
24040
24298
  rawCount,
24041
24299
  nextAfterSeq,
@@ -24048,7 +24306,11 @@ var V2E2EECoordinator = class {
24048
24306
  });
24049
24307
  if (!shouldContinue) break;
24050
24308
  if (pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
24051
- const nextKey = `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`;
24309
+ const nextKey = pullGateKeyForClient(client, "message.v2.pull", {
24310
+ after_seq: nextAfter,
24311
+ force: opts?.force === true,
24312
+ limit
24313
+ }, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
24052
24314
  await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
24053
24315
  pullGateKey = nextKey;
24054
24316
  }
@@ -24268,7 +24530,13 @@ var V2E2EECoordinator = class {
24268
24530
  const ns = `group:${gid}`;
24269
24531
  if (opts?.windowMode === "tail") {
24270
24532
  if (!opts.gateLocked) {
24271
- const key = `${ns}|tail|after=${afterSeq}|limit=${limit}`;
24533
+ const key = pullGateKeyForClient(client, "group.v2.pull", {
24534
+ group_id: gid,
24535
+ window_mode: "tail",
24536
+ after_seq: afterSeq,
24537
+ limit,
24538
+ _group_cursor_params: opts?.cursorParams
24539
+ }, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
24272
24540
  return await client._runPullSerialized(key, () => this.pullGroupV2(gid, afterSeq, limit, {
24273
24541
  ...opts ?? {},
24274
24542
  gateLocked: true
@@ -24284,7 +24552,13 @@ var V2E2EECoordinator = class {
24284
24552
  return Array.isArray(result.messages) ? result.messages : [];
24285
24553
  }
24286
24554
  if (!opts?.gateLocked) {
24287
- const key = `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`;
24555
+ const key = pullGateKeyForClient(client, "group.v2.pull", {
24556
+ group_id: gid,
24557
+ after_seq: afterSeq,
24558
+ force: opts?.force === true,
24559
+ limit,
24560
+ _group_cursor_params: opts?.cursorParams
24561
+ }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
24288
24562
  return await client._runPullSerialized(key, () => this.pullGroupV2(gid, afterSeq, limit, {
24289
24563
  ...opts ?? {},
24290
24564
  gateLocked: true
@@ -24294,7 +24568,13 @@ var V2E2EECoordinator = class {
24294
24568
  const wireGroupId = String(opts?.wireGroupId ?? groupId ?? "").trim() || gid;
24295
24569
  const cursorParams = opts?.cursorParams ?? {};
24296
24570
  const ownsCursor = opts?.ownsCursor !== false;
24297
- let pullGateKey = ownsCursor ? `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}` : "";
24571
+ let pullGateKey = ownsCursor ? pullGateKeyForClient(client, "group.v2.pull", {
24572
+ group_id: gid,
24573
+ after_seq: afterSeq,
24574
+ force: opts?.force === true,
24575
+ limit,
24576
+ _group_cursor_params: cursorParams
24577
+ }, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`) : "";
24298
24578
  let nextAfterSeq = opts?.explicitAfterSeq || opts?.force ? afterSeq : afterSeq || client._seqTracker.getContiguousSeq(ns);
24299
24579
  const deferredServerCursor = ownsCursor ? this.pendingForwardCursor(ns, client._seqTracker.getContiguousSeq(ns)) : 0;
24300
24580
  const deferredForwardAck = ownsCursor ? this.pendingForwardAck(ns, client._seqTracker.getContiguousSeq(ns)) : 0;
@@ -24347,6 +24627,7 @@ var V2E2EECoordinator = class {
24347
24627
  const pageTrackerSnapshot = typeof client._seqTracker.snapshotNamespace === "function" ? client._seqTracker.snapshotNamespace(ns) : null;
24348
24628
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
24349
24629
  const deferredKeyFetches = /* @__PURE__ */ new Map();
24630
+ let blockedSeq = 0;
24350
24631
  for (const msg of messages) {
24351
24632
  const seq2 = Number(msg.seq ?? 0);
24352
24633
  if (!Number.isFinite(seq2) || seq2 <= 0) continue;
@@ -24411,6 +24692,9 @@ var V2E2EECoordinator = class {
24411
24692
  }
24412
24693
  const deferStatus = {};
24413
24694
  let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true);
24695
+ if (deferStatus.deferred) {
24696
+ blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq2) : seq2;
24697
+ }
24414
24698
  if (deferStatus.deferred && deferStatus.fromAid) {
24415
24699
  const key = `${deferStatus.fromAid}\0${deferStatus.senderDeviceId ?? ""}\0${deferStatus.groupId ?? ""}`;
24416
24700
  deferredKeyFetches.set(key, {
@@ -24439,7 +24723,7 @@ var V2E2EECoordinator = class {
24439
24723
  Number(cursor?.join_seq ?? 0)
24440
24724
  );
24441
24725
  const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
24442
- const commitTarget = Math.max(
24726
+ const commitTarget = blockedSeq > 0 ? pageContigBefore : Math.max(
24443
24727
  pageContigBefore,
24444
24728
  Number.isFinite(effectiveFloor) ? effectiveFloor : 0,
24445
24729
  ownsCursor && pageCount === 1 ? deferredServerCursor : 0,
@@ -24483,7 +24767,7 @@ var V2E2EECoordinator = class {
24483
24767
  this.recordForwardCursor(ns, cursorCurrentSeq, ackSeq);
24484
24768
  }
24485
24769
  const knownServerCursorSeq = hasServerCursor ? cursorCurrentSeq : ownsCursor && pageCount === 1 ? deferredServerCursor : 0;
24486
- const ackNeeded = ackSeq > 0 && ackSeq > lastAutoAckSeq && ownsCursor && (hasServerCursor && ackSeq > cursorCurrentSeq || contigAdvanced && ackSeq > knownServerCursorSeq);
24770
+ const ackNeeded = blockedSeq <= 0 && ackSeq > 0 && ackSeq > lastAutoAckSeq && ownsCursor && (hasServerCursor && ackSeq > cursorCurrentSeq || contigAdvanced && ackSeq > knownServerCursorSeq);
24487
24771
  if (ackNeeded) {
24488
24772
  this.recordForwardAck(ns, ackSeq);
24489
24773
  const nextAfter2 = Math.max(pageMaxSeq, nextAfterSeq);
@@ -24499,6 +24783,7 @@ var V2E2EECoordinator = class {
24499
24783
  const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
24500
24784
  if (!ownsCursor) break;
24501
24785
  const rawCount = messages.length;
24786
+ if (blockedSeq > 0) break;
24502
24787
  const shouldContinue = shouldContinueForwardPage({
24503
24788
  rawCount,
24504
24789
  nextAfterSeq,
@@ -24511,7 +24796,13 @@ var V2E2EECoordinator = class {
24511
24796
  });
24512
24797
  if (!shouldContinue) break;
24513
24798
  if (pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
24514
- const nextKey = `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`;
24799
+ const nextKey = pullGateKeyForClient(client, "group.v2.pull", {
24800
+ group_id: gid,
24801
+ after_seq: nextAfter,
24802
+ force: opts?.force === true,
24803
+ limit,
24804
+ _group_cursor_params: cursorParams
24805
+ }, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
24515
24806
  await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
24516
24807
  pullGateKey = nextKey;
24517
24808
  }
@@ -26084,7 +26375,7 @@ var GroupStateCoordinator = class {
26084
26375
  const proposalId = isJsonObject(proposeResult) ? String(proposeResult.proposal_id ?? "").trim() : "";
26085
26376
  if (proposalId) {
26086
26377
  try {
26087
- await client.call("group.v2.confirm_state", { proposal_id: proposalId });
26378
+ await client.call("group.v2.confirm_state", { proposal_id: proposalId, group_id: groupId });
26088
26379
  client._v2AutoProposeLastSnapshot.set(groupId, membershipSnapshot);
26089
26380
  client._clientLog.debug(`V2 auto confirm_state: group=${groupId} proposal=${proposalId}`);
26090
26381
  } catch (confirmExc) {
@@ -26137,7 +26428,7 @@ var GroupStateCoordinator = class {
26137
26428
  return false;
26138
26429
  }
26139
26430
  if (!await this.verifyPendingProposalAgainstBase(groupId, proposal, stateResp)) return false;
26140
- await client.call("group.v2.confirm_state", { proposal_id: proposalId });
26431
+ await client.call("group.v2.confirm_state", { proposal_id: proposalId, group_id: groupId });
26141
26432
  client._clientLog.info(`V2 confirmed pending proposal: group=${groupId} proposal=${proposalId}`);
26142
26433
  return true;
26143
26434
  }
@@ -27400,6 +27691,7 @@ var AgentMdManager = class _AgentMdManager {
27400
27691
  if (content !== void 0 && content !== null) {
27401
27692
  const text3 = String(content ?? "");
27402
27693
  if (text3.length === 0) throw new ValidationError("uploadAgentMd requires non-empty content");
27694
+ validateAgentMdDocument(text3, { expectedAid: target });
27403
27695
  await this.saveRecord(target, {
27404
27696
  content: text3,
27405
27697
  local_etag: await _AgentMdManager.contentEtag(text3),
@@ -28877,6 +29169,7 @@ var _AUNClient = class _AUNClient {
28877
29169
  this._peerCache.clear();
28878
29170
  this._certCache.clear();
28879
29171
  this._gatewayUrl = null;
29172
+ this._gatewayCandidates = [];
28880
29173
  this._deviceId = aid.deviceId || getDeviceId();
28881
29174
  this._slotId = aid.slotId || "default";
28882
29175
  this._logger = new AUNLogger({ debug: aid.debug, aunPath: nextConfig.aunPath });
@@ -29056,7 +29349,7 @@ var _AUNClient = class _AUNClient {
29056
29349
  if (!message.toLowerCase().startsWith(agentPrefix)) return false;
29057
29350
  const target = message.slice(agentPrefix.length).trim();
29058
29351
  if (!target || target.toLowerCase() !== String(this._aid ?? "").trim().toLowerCase()) return false;
29059
- await this._agentMdManager.upload(buildDefaultAgentMd(target));
29352
+ await this._agentMdManager.upload(await this._agentMdManager.readContent(target) ?? buildDefaultAgentMd(target));
29060
29353
  return true;
29061
29354
  }
29062
29355
  async _runGroupIdentityOperation(groupId, operation) {
@@ -29085,6 +29378,10 @@ var _AUNClient = class _AUNClient {
29085
29378
  }
29086
29379
  this._aidStore = store;
29087
29380
  const payload = { ...params2 };
29381
+ if (payload.group_name === void 0 && payload.groupName !== void 0) {
29382
+ payload.group_name = payload.groupName;
29383
+ delete payload.groupName;
29384
+ }
29088
29385
  const isNamed = Boolean(String(payload.group_name ?? "").trim());
29089
29386
  payload._defer_group_ready_postprocess = true;
29090
29387
  if (!isNamed) {
@@ -29112,7 +29409,24 @@ var _AUNClient = class _AUNClient {
29112
29409
  delete postprocessParams2._defer_group_ready_postprocess;
29113
29410
  return await this._groupState.postprocessResult("group.create", postprocessParams2, result2);
29114
29411
  }
29115
- const keyPair = await new CryptoProvider().generateIdentity();
29412
+ const pendingKey = `create:${String(payload.group_name).trim().toLowerCase()}`;
29413
+ const keystore = store._keystore;
29414
+ let keyPair = null;
29415
+ if (keystore && typeof keystore.loadPendingGroupBind === "function") {
29416
+ keyPair = await keystore.loadPendingGroupBind(pendingKey);
29417
+ }
29418
+ if (!keyPair || !keyPair.public_key_der_b64 || !keyPair.private_key_pem) {
29419
+ const generated = await new CryptoProvider().generateIdentity();
29420
+ keyPair = {
29421
+ private_key_pem: generated.private_key_pem,
29422
+ public_key_der_b64: generated.public_key_der_b64,
29423
+ curve: generated.curve
29424
+ };
29425
+ if (keystore && typeof keystore.savePendingGroupBind === "function") {
29426
+ await keystore.savePendingGroupBind(pendingKey, keyPair);
29427
+ }
29428
+ }
29429
+ if (!keyPair) throw new ValidationError("createGroup: failed to generate or load key pair");
29116
29430
  payload.public_key = keyPair.public_key_der_b64;
29117
29431
  payload.curve = keyPair.curve;
29118
29432
  const result = await this.call("group.create", payload);
@@ -29138,6 +29452,9 @@ var _AUNClient = class _AUNClient {
29138
29452
  throw new ValidationError("createGroup requires current owner AID for group agent.md upload");
29139
29453
  }
29140
29454
  await this._uploadGroupAgentMd(store, groupAid, payload, group, uploaderAid);
29455
+ if (keystore && typeof keystore.clearPendingGroupBind === "function") {
29456
+ await keystore.clearPendingGroupBind(pendingKey);
29457
+ }
29141
29458
  const postprocessParams = { ...payload };
29142
29459
  delete postprocessParams._defer_group_ready_postprocess;
29143
29460
  return await this._groupState.postprocessResult("group.create", postprocessParams, result);
@@ -29195,10 +29512,14 @@ var _AUNClient = class _AUNClient {
29195
29512
  }
29196
29513
  this._aidStore = store;
29197
29514
  const groupId = String(params2.group_id ?? params2.group_aid ?? "").trim();
29515
+ const pendingKey = `bind:${groupId}`;
29198
29516
  const keystore = store._keystore;
29199
29517
  let keyPair = null;
29200
29518
  if (groupId && keystore && typeof keystore.loadPendingGroupBind === "function") {
29201
- keyPair = await keystore.loadPendingGroupBind(groupId);
29519
+ keyPair = await keystore.loadPendingGroupBind(pendingKey);
29520
+ if (!keyPair || !keyPair.public_key_der_b64 || !keyPair.private_key_pem) {
29521
+ keyPair = await keystore.loadPendingGroupBind(groupId);
29522
+ }
29202
29523
  }
29203
29524
  if (!keyPair || !keyPair.public_key_der_b64 || !keyPair.private_key_pem) {
29204
29525
  const generated = await new CryptoProvider().generateIdentity();
@@ -29208,7 +29529,7 @@ var _AUNClient = class _AUNClient {
29208
29529
  curve: generated.curve
29209
29530
  };
29210
29531
  if (groupId && keystore && typeof keystore.savePendingGroupBind === "function") {
29211
- await keystore.savePendingGroupBind(groupId, keyPair);
29532
+ await keystore.savePendingGroupBind(pendingKey, keyPair);
29212
29533
  }
29213
29534
  }
29214
29535
  if (!keyPair) {
@@ -29244,6 +29565,7 @@ var _AUNClient = class _AUNClient {
29244
29565
  }
29245
29566
  await this._uploadGroupAgentMd(store, groupAid, payload, group, uploaderAid);
29246
29567
  if (groupId && keystore && typeof keystore.clearPendingGroupBind === "function") {
29568
+ await keystore.clearPendingGroupBind(pendingKey);
29247
29569
  await keystore.clearPendingGroupBind(groupId);
29248
29570
  }
29249
29571
  return result;
@@ -29280,7 +29602,24 @@ var _AUNClient = class _AUNClient {
29280
29602
  if (!oldPublicKey) {
29281
29603
  throw new ValidationError(`renewGroupAid: cannot determine old public key for ${groupAid}`);
29282
29604
  }
29283
- const newKeyPair = await new CryptoProvider().generateIdentity();
29605
+ const keystore = store._keystore;
29606
+ const pendingKey = `renew:${groupId}`;
29607
+ let newKeyPair = null;
29608
+ if (keystore && typeof keystore.loadPendingGroupBind === "function") {
29609
+ newKeyPair = await keystore.loadPendingGroupBind(pendingKey);
29610
+ }
29611
+ if (!newKeyPair || !newKeyPair.public_key_der_b64 || !newKeyPair.private_key_pem) {
29612
+ const generated = await new CryptoProvider().generateIdentity();
29613
+ newKeyPair = {
29614
+ private_key_pem: generated.private_key_pem,
29615
+ public_key_der_b64: generated.public_key_der_b64,
29616
+ curve: generated.curve
29617
+ };
29618
+ if (keystore && typeof keystore.savePendingGroupBind === "function") {
29619
+ await keystore.savePendingGroupBind(pendingKey, newKeyPair);
29620
+ }
29621
+ }
29622
+ if (!newKeyPair) throw new ValidationError("renewGroupAid: failed to generate or load key pair");
29284
29623
  const newPublicKey = newKeyPair.public_key_der_b64;
29285
29624
  const newPrivateKey = newKeyPair.private_key_pem;
29286
29625
  const curve = newKeyPair.curve || "P-256";
@@ -29336,6 +29675,9 @@ var _AUNClient = class _AUNClient {
29336
29675
  throw new ValidationError("renewGroupAid requires current owner AID for group agent.md upload");
29337
29676
  }
29338
29677
  await this._uploadGroupAgentMd(store, returnedGroupAid, payload, group, uploaderAid);
29678
+ if (keystore && typeof keystore.clearPendingGroupBind === "function") {
29679
+ await keystore.clearPendingGroupBind(pendingKey);
29680
+ }
29339
29681
  return result;
29340
29682
  }
29341
29683
  async startGroupTransfer(params2 = {}, options = {}) {
@@ -29393,12 +29735,12 @@ var _AUNClient = class _AUNClient {
29393
29735
  if (!store) {
29394
29736
  throw new ValidationError("completeGroupTransfer requires aidStore");
29395
29737
  }
29396
- const keyPair = await new CryptoProvider().generateIdentity();
29397
29738
  const payload = { ...params2 };
29398
29739
  const groupId = String(params2.group_id ?? "").trim();
29399
29740
  if (!groupId) {
29400
29741
  throw new ValidationError("completeGroupTransfer requires group_id");
29401
29742
  }
29743
+ const pendingKey = `complete:${groupId}`;
29402
29744
  let groupAid = String(params2.group_aid ?? "").trim();
29403
29745
  if (!groupAid) {
29404
29746
  const info = await this.call("group.get_info", { group_id: groupId, required: ["member"] });
@@ -29407,6 +29749,23 @@ var _AUNClient = class _AUNClient {
29407
29749
  if (!groupAid) {
29408
29750
  throw new ValidationError("completeGroupTransfer: unable to determine group_aid");
29409
29751
  }
29752
+ const keystore = store._keystore;
29753
+ let keyPair = null;
29754
+ if (keystore && typeof keystore.loadPendingGroupBind === "function") {
29755
+ keyPair = await keystore.loadPendingGroupBind(pendingKey);
29756
+ }
29757
+ if (!keyPair || !keyPair.public_key_der_b64 || !keyPair.private_key_pem) {
29758
+ const generated = await new CryptoProvider().generateIdentity();
29759
+ keyPair = {
29760
+ private_key_pem: generated.private_key_pem,
29761
+ public_key_der_b64: generated.public_key_der_b64,
29762
+ curve: generated.curve
29763
+ };
29764
+ if (keystore && typeof keystore.savePendingGroupBind === "function") {
29765
+ await keystore.savePendingGroupBind(pendingKey, keyPair);
29766
+ }
29767
+ }
29768
+ if (!keyPair) throw new ValidationError("completeGroupTransfer: failed to generate or load key pair");
29410
29769
  const current = this.currentAid;
29411
29770
  const newOwner = String(current?.aid ?? "").trim();
29412
29771
  if (!current || !newOwner || !current.isPrivateKeyValid()) {
@@ -29460,6 +29819,9 @@ var _AUNClient = class _AUNClient {
29460
29819
  throw new ValidationError("completeGroupTransfer requires current owner AID for group agent.md upload");
29461
29820
  }
29462
29821
  await this._uploadGroupAgentMd(store, returnedGroupAid, payload, group, uploaderAid);
29822
+ if (keystore && typeof keystore.clearPendingGroupBind === "function") {
29823
+ await keystore.clearPendingGroupBind(pendingKey);
29824
+ }
29463
29825
  return result;
29464
29826
  }
29465
29827
  static _notifyParamsSizeOk(params2) {
@@ -30040,16 +30402,21 @@ var _AUNClient = class _AUNClient {
30040
30402
  async _resolveGatewayCandidatesForAid(aid) {
30041
30403
  const target = String(aid ?? this._aid ?? "").trim();
30042
30404
  if (!target) throw new StateError("gateway discovery requires a loaded AID");
30405
+ const discovery = this._discovery;
30406
+ const tokenStore = this._tokenStore;
30407
+ const discoveryIsCurrent = () => this._discovery === discovery && this._tokenStore === tokenStore && !this._closing;
30043
30408
  if (this._gatewayCandidates.length > 0) return [...this._gatewayCandidates];
30044
30409
  if (this._gatewayUrl) {
30045
30410
  this._gatewayCandidates = [this._gatewayUrl];
30046
30411
  return [...this._gatewayCandidates];
30047
30412
  }
30048
30413
  try {
30049
- const getMetadata = this._tokenStore.getMetadata;
30050
- const rawList = typeof getMetadata === "function" ? String(await getMetadata.call(this._tokenStore, target, "gateway_urls") ?? "").trim() : "";
30414
+ const getMetadata = tokenStore.getMetadata;
30415
+ const rawList = typeof getMetadata === "function" ? String(await getMetadata.call(tokenStore, target, "gateway_urls") ?? "").trim() : "";
30416
+ if (!discoveryIsCurrent()) throw new StateError("gateway discovery superseded by identity change");
30051
30417
  const cachedList = rawList ? JSON.parse(rawList) : [];
30052
- const raw = typeof getMetadata === "function" ? String(await getMetadata.call(this._tokenStore, target, "gateway_url") ?? "").trim() : "";
30418
+ const raw = typeof getMetadata === "function" ? String(await getMetadata.call(tokenStore, target, "gateway_url") ?? "").trim() : "";
30419
+ if (!discoveryIsCurrent()) throw new StateError("gateway discovery superseded by identity change");
30053
30420
  const candidates2 = Array.isArray(cachedList) ? cachedList.map((item) => String(item ?? "").trim()).filter(Boolean) : [];
30054
30421
  const gateway = candidates2[0] ?? (raw.startsWith('"') && raw.endsWith('"') ? String(JSON.parse(raw)).trim() : raw);
30055
30422
  if (gateway) {
@@ -30058,6 +30425,7 @@ var _AUNClient = class _AUNClient {
30058
30425
  return [...this._gatewayCandidates];
30059
30426
  }
30060
30427
  } catch {
30428
+ if (!discoveryIsCurrent()) throw new StateError("gateway discovery superseded by identity change");
30061
30429
  }
30062
30430
  const dotIdx = target.indexOf(".");
30063
30431
  const issuerDomain = dotIdx >= 0 ? target.slice(dotIdx + 1) : target;
@@ -30069,22 +30437,26 @@ var _AUNClient = class _AUNClient {
30069
30437
  let lastError = null;
30070
30438
  for (const url of candidates) {
30071
30439
  try {
30072
- const discovered = await this._discovery.discoverAll(url);
30440
+ const discovered = await discovery.discoverAll(url);
30441
+ if (!discoveryIsCurrent()) throw new StateError("gateway discovery superseded by identity change");
30073
30442
  const gatewayUrls = [...new Set(discovered.map((item) => String(item ?? "").trim()).filter(Boolean))];
30074
30443
  if (gatewayUrls.length === 0) throw new ValidationError("gateway discovery returned no candidates");
30075
30444
  const gateway = gatewayUrls[0];
30076
30445
  this._gatewayCandidates = gatewayUrls;
30077
30446
  this._gatewayUrl = gateway;
30078
30447
  try {
30079
- const setMetadata = this._tokenStore.setMetadata;
30448
+ const setMetadata = tokenStore.setMetadata;
30080
30449
  if (typeof setMetadata === "function") {
30081
- await setMetadata.call(this._tokenStore, target, "gateway_url", gateway);
30082
- await setMetadata.call(this._tokenStore, target, "gateway_urls", JSON.stringify(gatewayUrls));
30450
+ await setMetadata.call(tokenStore, target, "gateway_url", gateway);
30451
+ if (!discoveryIsCurrent()) throw new StateError("gateway discovery superseded by identity change");
30452
+ await setMetadata.call(tokenStore, target, "gateway_urls", JSON.stringify(gatewayUrls));
30453
+ if (!discoveryIsCurrent()) throw new StateError("gateway discovery superseded by identity change");
30083
30454
  }
30084
30455
  } catch {
30085
30456
  }
30086
30457
  return [...gatewayUrls];
30087
30458
  } catch (err) {
30459
+ if (!discoveryIsCurrent()) throw new StateError("gateway discovery superseded by identity change");
30088
30460
  lastError = err;
30089
30461
  }
30090
30462
  }
@@ -32600,8 +32972,7 @@ var AIDStore = class {
32600
32972
  });
32601
32973
  }
32602
32974
  await this._persistGatewayUrl(target, gatewayUrl);
32603
- const uploaded = await this.uploadAgentMd(target, buildDefaultAgentMd(target));
32604
- if (!uploaded.ok) return uploaded;
32975
+ await this.uploadAgentMd(target, buildDefaultAgentMd(target));
32605
32976
  return resultOk({ registered: true });
32606
32977
  } catch (exc) {
32607
32978
  if (exc instanceof IdentityConflictError) {