@agentunion/fastaun-browser 0.5.6 → 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 (104) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/_packed_docs/CHANGELOG.md +104 -0
  3. package/_packed_docs/INDEX.md +12 -11
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +4 -3
  5. package/_packed_docs/agent.md/SCHEMA.md +83 -58
  6. package/_packed_docs/agent.md/examples/codeagent-claudecode.md +1 -1
  7. package/_packed_docs/agent.md/examples/openclaw-lobster.md +1 -1
  8. package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +1 -1
  9. 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
  10. package/_packed_docs/audit/AUN/346/234/215/345/212/241Go/345/214/226/351/207/215/346/236/204/347/262/276/347/273/206/345/214/226/346/272/220/347/240/201/345/256/241/346/237/245-20260718.md +3 -3
  11. package/_packed_docs/aun/345/205/254/347/275/221/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +7 -0
  12. package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +422 -331
  13. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +598 -398
  14. package/_packed_docs/group-message-rpc-alignment-gaps.md +741 -0
  15. package/_packed_docs/message-online-push-alignment.md +572 -0
  16. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1 -1
  17. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +10 -10
  18. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +2 -2
  19. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +30 -10
  20. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +23 -20
  21. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +3 -3
  22. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +5 -6
  23. package/_packed_docs/sdk/09-group-rpc-manual.md +11 -7
  24. package/_packed_docs/sdk/09-message-rpc-manual.md +5 -3
  25. package/_packed_docs/sdk/09-storage-rpc-manual.md +14 -3
  26. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +19 -17
  27. package/_packed_docs/sdk/CHANGELOG-0.5.6.md +73 -0
  28. package/_packed_docs/sdk/INDEX.md +22 -22
  29. package/_packed_docs/sdk/README.md +6 -5
  30. package/dist/agent-md-schema.d.ts +14 -0
  31. package/dist/agent-md-schema.d.ts.map +1 -0
  32. package/dist/agent-md-schema.js +103 -0
  33. package/dist/agent-md-schema.js.map +1 -0
  34. package/dist/agent-md.d.ts +7 -0
  35. package/dist/agent-md.d.ts.map +1 -1
  36. package/dist/agent-md.js +230 -10
  37. package/dist/agent-md.js.map +1 -1
  38. package/dist/aid-store.d.ts +5 -1
  39. package/dist/aid-store.d.ts.map +1 -1
  40. package/dist/aid-store.js +24 -4
  41. package/dist/aid-store.js.map +1 -1
  42. package/dist/aid.d.ts +2 -0
  43. package/dist/aid.d.ts.map +1 -1
  44. package/dist/aid.js +29 -7
  45. package/dist/aid.js.map +1 -1
  46. package/dist/auth.d.ts.map +1 -1
  47. package/dist/auth.js +1 -0
  48. package/dist/auth.js.map +1 -1
  49. package/dist/bundle.js +30253 -21955
  50. package/dist/cert-utils.d.ts +1 -0
  51. package/dist/cert-utils.d.ts.map +1 -1
  52. package/dist/cert-utils.js +36 -16
  53. package/dist/cert-utils.js.map +1 -1
  54. package/dist/client/delivery.d.ts +40 -5
  55. package/dist/client/delivery.d.ts.map +1 -1
  56. package/dist/client/delivery.js +719 -221
  57. package/dist/client/delivery.js.map +1 -1
  58. package/dist/client/group-state.js +2 -2
  59. package/dist/client/group-state.js.map +1 -1
  60. package/dist/client/lifecycle.d.ts +15 -1
  61. package/dist/client/lifecycle.d.ts.map +1 -1
  62. package/dist/client/lifecycle.js +463 -151
  63. package/dist/client/lifecycle.js.map +1 -1
  64. package/dist/client/rpc-pipeline.d.ts +25 -6
  65. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  66. package/dist/client/rpc-pipeline.js +407 -123
  67. package/dist/client/rpc-pipeline.js.map +1 -1
  68. package/dist/client/runtime.d.ts +1 -3
  69. package/dist/client/runtime.d.ts.map +1 -1
  70. package/dist/client/runtime.js +4 -7
  71. package/dist/client/runtime.js.map +1 -1
  72. package/dist/client/v2-e2ee.d.ts +20 -2
  73. package/dist/client/v2-e2ee.d.ts.map +1 -1
  74. package/dist/client/v2-e2ee.js +434 -112
  75. package/dist/client/v2-e2ee.js.map +1 -1
  76. package/dist/client.d.ts +22 -10
  77. package/dist/client.d.ts.map +1 -1
  78. package/dist/client.js +500 -116
  79. package/dist/client.js.map +1 -1
  80. package/dist/facades.d.ts +23 -1
  81. package/dist/facades.d.ts.map +1 -1
  82. package/dist/facades.js +55 -7
  83. package/dist/facades.js.map +1 -1
  84. package/dist/group-id.d.ts.map +1 -1
  85. package/dist/group-id.js +2 -17
  86. package/dist/group-id.js.map +1 -1
  87. package/dist/index.d.ts +4 -3
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +2 -1
  90. package/dist/index.js.map +1 -1
  91. package/dist/seq-tracker.d.ts.map +1 -1
  92. package/dist/seq-tracker.js +8 -2
  93. package/dist/seq-tracker.js.map +1 -1
  94. package/dist/tools/cross-sdk-agent.js +23 -1
  95. package/dist/tools/cross-sdk-agent.js.map +1 -1
  96. package/dist/transport.d.ts.map +1 -1
  97. package/dist/transport.js +8 -1
  98. package/dist/transport.js.map +1 -1
  99. package/dist/validators.d.ts.map +1 -1
  100. package/dist/validators.js +8 -14
  101. package/dist/validators.js.map +1 -1
  102. package/dist/version.d.ts +1 -1
  103. package/dist/version.js +1 -1
  104. package/package.json +6 -5
@@ -1,5 +1,5 @@
1
1
  import { normalizeSlotId, slotIsolationKey } from '../config.js';
2
- import { ClientSignatureError, ConnectionError, PermissionError, ValidationError } from '../errors.js';
2
+ import { ClientSignatureError, ConnectionError, PermissionError, TimeoutError, ValidationError } from '../errors.js';
3
3
  import { validateAIDFormat, validateGroupIDFormat } from '../validators.js';
4
4
  import { p1363ToDer, pemToArrayBuffer, uint8ToBase64 } from '../crypto.js';
5
5
  import { ConnectionState, isJsonObject, } from '../types.js';
@@ -71,6 +71,7 @@ const SIGNED_METHODS = new Set([
71
71
  'group.dissolve', 'group.suspend', 'group.resume',
72
72
  ]);
73
73
  const SIGNING_KEY_CACHE_MAX = 32;
74
+ const IDENTITY_ADMISSION_RETRY_DELAYS_MS = [0, 50, 100, 200];
74
75
  const signingKeyCache = new Map();
75
76
  const certFingerprintCache = new Map();
76
77
  function cacheGet(cache, key) {
@@ -119,9 +120,17 @@ async function signingCertFingerprint(certPem) {
119
120
  return await cached;
120
121
  }
121
122
  const PULL_GATE_STALE_MS = 30000;
123
+ const PULL_GATE_OPERATION_TIMEOUT_MS = 3000;
124
+ function sameIdentityAdmissionRejection(error, original) {
125
+ const errorCode = Number(error?.code);
126
+ const originalCode = Number(original?.code);
127
+ const errorMessage = String(error instanceof Error ? error.message : error?.message ?? '').trim().toLowerCase();
128
+ const originalMessage = String(original instanceof Error ? original.message : original?.message ?? '').trim().toLowerCase();
129
+ return errorCode === -32003 && originalCode === -32003 && errorMessage === originalMessage;
130
+ }
122
131
  const NON_IDEMPOTENT_TIMEOUT = 35;
123
132
  const NON_IDEMPOTENT_METHODS = new Set([
124
- 'message.send', 'group.send', 'group.create', 'group.invite',
133
+ 'message.send', 'mail.send', 'group.send', 'group.create', 'group.invite',
125
134
  'group.kick', 'group.remove_member', 'group.leave', 'group.dissolve',
126
135
  'group.set_settings',
127
136
  'group.update_announcement', 'group.update_rules',
@@ -168,7 +177,9 @@ function isTransientV2PullFallbackError(error) {
168
177
  export class RpcPipeline {
169
178
  runtime;
170
179
  pullGateStates = new Map();
171
- bypassedRealtimePulls = new Map();
180
+ inlineRealtimeScopes = new Set();
181
+ pullGeneration = 0;
182
+ pullInvalidationWait = null;
172
183
  constructor(runtime) {
173
184
  this.runtime = runtime;
174
185
  }
@@ -198,6 +209,8 @@ export class RpcPipeline {
198
209
  };
199
210
  const skipSendResultEnvelope = Boolean(p._skip_send_result_envelope);
200
211
  delete p._skip_send_result_envelope;
212
+ const deferGroupReadyPostprocess = Boolean(p._defer_group_ready_postprocess);
213
+ delete p._defer_group_ready_postprocess;
201
214
  if (method === 'message.send') {
202
215
  const encrypt = p.encrypt !== undefined ? p.encrypt : true;
203
216
  delete p.encrypt;
@@ -249,15 +262,11 @@ export class RpcPipeline {
249
262
  }
250
263
  const pullGateKey = this.pullGateKeyForCall(method, p);
251
264
  if (pullGateKey) {
252
- if (this.isPullResponseProcessing(pullGateKey)) {
253
- client._clientLog.debug(`pull skipped while processing pull response: method=${method} key=${pullGateKey}`);
254
- return this.emptyPullResultForCall(method);
255
- }
256
- return await this.runPullSerialized(pullGateKey, async () => await this.callImplInner(method, p, skipSendResultEnvelope), this.pullGateBackgroundForCall(method, p));
265
+ return await this.runPullSerialized(pullGateKey, async () => await this.callImplInner(method, p, skipSendResultEnvelope, deferGroupReadyPostprocess), rpcBackground && this.pullGateBackgroundForCall(method, p));
257
266
  }
258
- return await runWithRpcPriority(() => this.callImplInner(method, p, skipSendResultEnvelope));
267
+ return await runWithRpcPriority(() => this.callImplInner(method, p, skipSendResultEnvelope, deferGroupReadyPostprocess));
259
268
  }
260
- async callImplInner(method, p, skipSendResultEnvelope = false) {
269
+ async callImplInner(method, p, skipSendResultEnvelope = false, deferGroupReadyPostprocess = false) {
261
270
  const client = this.runtime.client;
262
271
  if (method === 'message.v2.pull' || (method === 'message.pull' && hasMatchingV2Session(client))) {
263
272
  try {
@@ -265,7 +274,10 @@ export class RpcPipeline {
265
274
  client._clientLog.debug(`call route: ${method} -> V2 pull`);
266
275
  if (p.window_mode === 'tail')
267
276
  return await client._v2E2EE.pullV2TailInternal(p);
268
- const messages = await client._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, { force: p.force === true });
277
+ const messages = await client._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, {
278
+ gateLocked: true,
279
+ force: p.force === true,
280
+ });
269
281
  return { messages };
270
282
  }
271
283
  catch (exc) {
@@ -290,9 +302,11 @@ export class RpcPipeline {
290
302
  if (p.window_mode === 'tail')
291
303
  return await client._v2E2EE.pullGroupV2TailInternal(p);
292
304
  const hasExplicitAfterSeq = 'after_seq' in p || 'after_message_seq' in p;
293
- const cursorParams = client._explicitGroupCursorParams(p);
294
- const ownsCursor = Object.keys(cursorParams).length === 0 || client._groupCursorTargetsCurrentInstance(cursorParams);
295
- const pullOpts = {};
305
+ const cursorParams = this.explicitGroupCursorParams(p);
306
+ const ownsCursor = Object.keys(cursorParams).length === 0 || this.groupCursorTargetsCurrentInstance(cursorParams);
307
+ const pullOpts = {
308
+ gateLocked: true,
309
+ };
296
310
  if (hasExplicitAfterSeq)
297
311
  pullOpts.explicitAfterSeq = true;
298
312
  if (p.force === true)
@@ -317,8 +331,8 @@ export class RpcPipeline {
317
331
  if ((method === 'group.v2.ack' || method === 'group.ack_messages') && p.group_id && (method === 'group.v2.ack' || hasMatchingV2Session(client))) {
318
332
  await client._ensureV2SessionReady('group.ack_messages');
319
333
  client._clientLog.debug(`call route: ${method} -> V2 ack`);
320
- const cursorParams = client._explicitGroupCursorParams(p);
321
- const ownsCursor = Object.keys(cursorParams).length === 0 || client._groupCursorTargetsCurrentInstance(cursorParams);
334
+ const cursorParams = this.explicitGroupCursorParams(p);
335
+ const ownsCursor = Object.keys(cursorParams).length === 0 || this.groupCursorTargetsCurrentInstance(cursorParams);
322
336
  if (!ownsCursor) {
323
337
  return await client._rawGroupAckMessages(p);
324
338
  }
@@ -327,11 +341,15 @@ export class RpcPipeline {
327
341
  await this.applyClientSignature(method, p);
328
342
  const callTimeout = NON_IDEMPOTENT_METHODS.has(method) ? NON_IDEMPOTENT_TIMEOUT : undefined;
329
343
  const rpcBackground = client._backgroundRpcDepth > 0;
330
- let result = callTimeout
331
- ? await client._transport.call(method, p, callTimeout, undefined, rpcBackground)
332
- : await client._transport.call(method, p, undefined, undefined, rpcBackground);
344
+ const invokeTransport = () => {
345
+ const request = callTimeout
346
+ ? client._transport.call(method, p, callTimeout, undefined, rpcBackground)
347
+ : client._transport.call(method, p, undefined, undefined, rpcBackground);
348
+ return this.bindActivePullCancellation(method, p, request);
349
+ };
350
+ let result = await this.transportCallWithIdentityRecovery(invokeTransport, method, p);
333
351
  const sendResultModeSource = result;
334
- result = await this.postprocessResult(method, p, result);
352
+ result = await this.postprocessResult(method, p, result, { skipGroupState: deferGroupReadyPostprocess });
335
353
  if (!skipSendResultEnvelope) {
336
354
  result = client._delivery.attachSendResultEnvelope(method, p, result, Boolean(p.encrypted), sendResultModeSource);
337
355
  }
@@ -531,8 +549,8 @@ export class RpcPipeline {
531
549
  const mode = method === 'message.history' ? 'history' : params.window_mode === 'tail' ? 'tail' : 'forward';
532
550
  const cursor = mode === 'history'
533
551
  ? `before=${String(params.before_seq)}`
534
- : `after=${String(params.after_seq ?? 0)}|force=${String(Boolean(params.force))}`;
535
- return `p2p:${client._aid}|${mode}|${cursor}|limit=${String(params.limit)}`;
552
+ : `after=${String(params.after_seq ?? 0)}`;
553
+ return `p2p:${client._aid}|mode=${mode}|cursor=${cursor}|force=${String(Boolean(params.force))}|limit=${String(params.limit)}`;
536
554
  }
537
555
  if (method === 'group.pull' || method === 'group.v2.pull' || method === 'group.history') {
538
556
  const gid = String(params.group_id ?? params.group_aid ?? '').trim();
@@ -541,13 +559,17 @@ export class RpcPipeline {
541
559
  const mode = method === 'group.history' ? 'history' : params.window_mode === 'tail' ? 'tail' : 'forward';
542
560
  const cursor = mode === 'history'
543
561
  ? `before=${String(params.before_seq)}`
544
- : `after=${String(params.after_seq ?? params.after_message_seq ?? 0)}|force=${String(Boolean(params.force))}`;
545
- return `group:${gid}|${mode}|${cursor}|limit=${String(params.limit)}`;
562
+ : `after=${String(params.after_seq ?? params.after_message_seq ?? 0)}`;
563
+ const explicitCursor = this.explicitGroupCursorParams(params);
564
+ const cursorSuffix = Object.keys(explicitCursor).length > 0
565
+ ? `|cursor_params=${stableStringify(explicitCursor)}`
566
+ : '';
567
+ return `group:${gid}|mode=${mode}|cursor=${cursor}${cursorSuffix}|force=${String(Boolean(params.force))}|limit=${String(params.limit)}`;
546
568
  }
547
569
  if (method === 'group.pull_events') {
548
570
  const gid = String(params.group_id ?? params.group_aid ?? '').trim();
549
571
  return gid
550
- ? `group_event:${gid}|forward|after=${String(params.after_event_seq ?? 0)}|limit=${String(params.limit)}`
572
+ ? `group_event:${gid}|mode=forward|cursor=after=${String(params.after_event_seq ?? 0)}|force=${String(Boolean(params.force))}|limit=${String(params.limit)}`
551
573
  : '';
552
574
  }
553
575
  return '';
@@ -561,23 +583,60 @@ export class RpcPipeline {
561
583
  return true;
562
584
  }
563
585
  pullGateName(key) {
564
- if (key.startsWith('p2p:'))
565
- return 'p2p_message';
566
- if (key.startsWith('group:'))
567
- return 'group_message';
568
- if (key.startsWith('group_event:'))
569
- return 'group_event';
570
- return `other:${key}`;
586
+ void key;
587
+ return 'pull';
571
588
  }
572
589
  pullGateState(key) {
573
590
  const name = this.pullGateName(key);
574
591
  let state = this.pullGateStates.get(name);
575
592
  if (!state) {
576
- state = { active: null, foreground: [], background: [], byKey: new Map() };
593
+ state = { active: null, foreground: [], background: [], byKey: new Map(), lifecycle: new Map(), watchdog: null };
577
594
  this.pullGateStates.set(name, state);
578
595
  }
596
+ this.startPullGateWatchdog(state, name);
579
597
  return state;
580
598
  }
599
+ pullGateOperationTimeoutMs() {
600
+ const client = this.runtime.client;
601
+ const configured = Number(client._PULL_GATE_OPERATION_TIMEOUT_MS
602
+ ?? client._PULL_GATE_TIMEOUT_MS
603
+ ?? client._PULL_GATE_QUEUE_TIMEOUT_MS);
604
+ return Number.isFinite(configured) && configured > 0 ? configured : PULL_GATE_OPERATION_TIMEOUT_MS;
605
+ }
606
+ startPullGateWatchdog(state, name) {
607
+ if (state.watchdog)
608
+ return;
609
+ const interval = Math.min(50, Math.max(1, Math.floor(this.pullGateOperationTimeoutMs() / 4)));
610
+ const timer = setInterval(() => this.checkPullGate(state, name), interval);
611
+ state.watchdog = timer;
612
+ timer.unref?.();
613
+ }
614
+ stopPullGateWatchdogs() {
615
+ for (const state of this.pullGateStates.values()) {
616
+ if (state.watchdog === null)
617
+ continue;
618
+ clearInterval(state.watchdog);
619
+ state.watchdog = null;
620
+ }
621
+ }
622
+ checkPullGate(state, name) {
623
+ const active = state.active;
624
+ if (!active) {
625
+ this.drainPullGate(state);
626
+ return;
627
+ }
628
+ if (!active.pullingStartedAt || Date.now() - active.pullingStartedAt < this.pullGateOperationTimeoutMs())
629
+ return;
630
+ if (active.timedOut)
631
+ return;
632
+ active.timedOut = true;
633
+ active.invalidated = true;
634
+ this.pullGeneration += 1;
635
+ active.cancel?.();
636
+ const err = new TimeoutError(`pull gate timeout: ${active.namespace}`, { retryable: true });
637
+ this.runtime.client._clientLog?.warn(`pull gate watchdog timeout: gate=${name} key=${active.key}`);
638
+ active.reject(err);
639
+ }
581
640
  realtimePullNamespace(key) {
582
641
  for (const marker of ['|tail|', '|forward|']) {
583
642
  const index = key.indexOf(marker);
@@ -590,6 +649,22 @@ export class RpcPipeline {
590
649
  const index = key.indexOf('|');
591
650
  return index < 0 ? key : key.slice(0, index);
592
651
  }
652
+ bindActivePullCancellation(method, params, request) {
653
+ const cancel = request.cancel;
654
+ const key = this.pullGateKeyForCall(method, params);
655
+ if (typeof cancel !== 'function' || !key)
656
+ return request;
657
+ const state = this.pullGateStates.get(this.pullGateName(key));
658
+ const active = state?.active;
659
+ if (!active || active.key !== key || active.timedOut || active.invalidated)
660
+ return request;
661
+ const boundCancel = () => cancel.call(request);
662
+ active.cancel = boundCancel;
663
+ return request.finally(() => {
664
+ if (active.cancel === boundCancel)
665
+ active.cancel = null;
666
+ });
667
+ }
593
668
  isPullResponseProcessing(key) {
594
669
  const responseKeys = this.runtime.client._pullResponseKeys;
595
670
  return Boolean(key) && (responseKeys?.get(this.pullScopeKey(key)) ?? 0) > 0;
@@ -599,33 +674,12 @@ export class RpcPipeline {
599
674
  return { events: [], count: 0 };
600
675
  return { messages: [], count: 0 };
601
676
  }
602
- trackBypassedRealtimePull(key, delta) {
603
- const ns = this.realtimePullNamespace(key);
604
- if (!ns)
605
- return;
606
- const next = Math.max(0, (this.bypassedRealtimePulls.get(ns) ?? 0) + delta);
607
- if (next === 0)
608
- this.bypassedRealtimePulls.delete(ns);
609
- else
610
- this.bypassedRealtimePulls.set(ns, next);
611
- }
612
- async executeBypassedPull(key, operation, background) {
613
- this.trackBypassedRealtimePull(key, 1);
614
- try {
615
- return await this.executePullOperation(operation, background);
616
- }
617
- finally {
618
- this.trackBypassedRealtimePull(key, -1);
619
- }
620
- }
621
677
  hasPendingRealtimePull(ns) {
622
678
  const normalized = String(ns ?? '').trim();
623
679
  if (!normalized || (!normalized.startsWith('p2p:') && !normalized.startsWith('group:')))
624
680
  return false;
625
681
  if (this.isPullResponseProcessing(normalized))
626
682
  return true;
627
- if ((this.bypassedRealtimePulls.get(normalized) ?? 0) > 0)
628
- return true;
629
683
  const state = this.pullGateStates.get(this.pullGateName(normalized));
630
684
  if (!state)
631
685
  return false;
@@ -638,6 +692,36 @@ export class RpcPipeline {
638
692
  || state.foreground.some(matches)
639
693
  || state.background.some(matches);
640
694
  }
695
+ hasPullActivity(namespace, includePending = true) {
696
+ const ns = String(namespace ?? '').trim();
697
+ if ((!ns.startsWith('p2p:') && !ns.startsWith('group:'))
698
+ || this.inlineRealtimeScopes.has(ns))
699
+ return true;
700
+ const client = this.runtime.client;
701
+ if ((client._pullResponseKeys?.get?.(ns) ?? 0) > 0)
702
+ return true;
703
+ const pendingP2p = client._pendingP2pPullUpper;
704
+ const pendingGroup = client._pendingGroupPullUpper;
705
+ if (includePending && ((pendingP2p?.get(ns) ?? 0) > 0 || (pendingGroup?.get(ns) ?? 0) > 0
706
+ || client._delivery?.hasPendingPull?.(ns)))
707
+ return true;
708
+ for (const marker of client._gapFillDone ?? []) {
709
+ const value = String(marker ?? '').trim();
710
+ if (ns.startsWith('p2p:') && (value.startsWith('p2p_pull:') || value.startsWith('p2p_pull_retry:')))
711
+ return true;
712
+ if (ns.startsWith('group:') && value.startsWith(`group_pull:${ns}`))
713
+ return true;
714
+ }
715
+ for (const [key, gate] of client._pullGates?.entries?.() ?? []) {
716
+ if (gate?.inflight && this.pullScopeKey(key) === ns)
717
+ return true;
718
+ }
719
+ const state = this.pullGateStates.get(this.pullGateName(ns));
720
+ if (!state)
721
+ return false;
722
+ const matches = (job) => job !== null && job.namespace === ns;
723
+ return matches(state.active) || state.foreground.some(matches) || state.background.some(matches);
724
+ }
641
725
  async tryRunInlineRealtime(ns, seq, operation) {
642
726
  const normalized = String(ns ?? '').trim();
643
727
  if ((!normalized.startsWith('p2p:') && !normalized.startsWith('group:'))
@@ -645,8 +729,10 @@ export class RpcPipeline {
645
729
  return { acquired: false };
646
730
  }
647
731
  const client = this.runtime.client;
648
- if (this.isPullResponseProcessing(normalized)
649
- || (this.bypassedRealtimePulls.get(normalized) ?? 0) > 0) {
732
+ if (this.hasPullActivity(normalized)) {
733
+ return { acquired: false };
734
+ }
735
+ if (this.isPullResponseProcessing(normalized)) {
650
736
  return { acquired: false };
651
737
  }
652
738
  for (const [key, gate] of client._pullGates?.entries?.() ?? []) {
@@ -660,28 +746,43 @@ export class RpcPipeline {
660
746
  }
661
747
  const guard = {
662
748
  key,
749
+ namespace: normalized,
663
750
  background: false,
751
+ running: true,
752
+ suspended: false,
664
753
  inline: true,
665
754
  operation,
666
755
  promise: Promise.resolve(undefined),
667
756
  resolve: () => { },
668
757
  reject: () => { },
669
758
  timer: null,
759
+ resume: null,
760
+ resumeResolve: null,
761
+ pullingStartedAt: 0,
762
+ timedOut: false,
763
+ invalidated: false,
764
+ cancel: null,
765
+ settled: Promise.resolve(),
766
+ settledResolve: null,
670
767
  };
671
- state.active = guard;
768
+ this.inlineRealtimeScopes.add(normalized);
672
769
  try {
673
770
  return { acquired: true, value: await operation() };
674
771
  }
675
772
  finally {
773
+ this.inlineRealtimeScopes.delete(normalized);
676
774
  if (state.active === guard)
677
775
  state.active = null;
678
776
  this.drainPullGate(state);
777
+ this.runtime.client._delivery?.onPullGateIdle?.(normalized);
679
778
  this.armQueuedPullTimers(state);
680
779
  }
681
780
  }
682
781
  tryAcquirePullGate(key) {
683
782
  if (!key)
684
783
  return 0;
784
+ if (this.inlineRealtimeScopes.has(this.pullScopeKey(key)))
785
+ return null;
685
786
  const serialized = this.pullGateStates.get(this.pullGateName(key));
686
787
  if (serialized?.active?.inline === true)
687
788
  return null;
@@ -709,97 +810,209 @@ export class RpcPipeline {
709
810
  gate.inflight = false;
710
811
  gate.startedAt = 0;
711
812
  }
813
+ isPullGenerationCurrent(generation) {
814
+ return generation === this.pullGeneration;
815
+ }
816
+ invalidatePulls() {
817
+ if (this.pullInvalidationWait)
818
+ return this.pullInvalidationWait;
819
+ this.pullGeneration += 1;
820
+ const error = new Error('pull invalidated');
821
+ const waits = [];
822
+ for (const state of this.pullGateStates.values()) {
823
+ const jobs = new Set();
824
+ if (state.active)
825
+ jobs.add(state.active);
826
+ for (const job of state.foreground)
827
+ jobs.add(job);
828
+ for (const job of state.background)
829
+ jobs.add(job);
830
+ for (const job of jobs) {
831
+ job.invalidated = true;
832
+ if (!job.timedOut)
833
+ job.reject(error);
834
+ job.cancel?.();
835
+ job.resumeResolve?.();
836
+ job.resumeResolve = null;
837
+ if (job.running) {
838
+ waits.push(job.settled);
839
+ }
840
+ else {
841
+ job.settledResolve?.();
842
+ job.settledResolve = null;
843
+ }
844
+ }
845
+ state.foreground = state.foreground.filter((job) => !jobs.has(job));
846
+ state.background = state.background.filter((job) => !jobs.has(job));
847
+ for (const [key, job] of state.byKey.entries()) {
848
+ if (jobs.has(job) && !job.running)
849
+ state.byKey.delete(key);
850
+ }
851
+ this.drainPullGate(state);
852
+ }
853
+ const wait = Promise.all(waits).then(() => undefined);
854
+ let tracked;
855
+ tracked = wait.finally(() => {
856
+ if (this.pullInvalidationWait === tracked)
857
+ this.pullInvalidationWait = null;
858
+ });
859
+ this.pullInvalidationWait = tracked;
860
+ return tracked;
861
+ }
862
+ pullInvalidationInProgress() {
863
+ return this.pullInvalidationWait !== null;
864
+ }
712
865
  async runPullSerialized(key, operation, background = false) {
713
- if (key && this.isPullResponseProcessing(key)) {
714
- this.runtime.client._clientLog.debug(`pull skipped while processing pull response: key=${key}`);
715
- return [];
866
+ if (this.pullInvalidationInProgress()) {
867
+ throw new Error('pull invalidated');
716
868
  }
717
869
  if (!key)
718
870
  return await this.executePullOperation(operation, background);
719
871
  const state = this.pullGateState(key);
720
- const timeoutMs = Math.max(0, Number(this.runtime.client._PULL_GATE_QUEUE_TIMEOUT_MS ?? 3_000) || 0);
721
- const existing = state.byKey.get(key);
872
+ const namespace = this.pullScopeKey(key);
873
+ const candidate = state.byKey.get(key);
874
+ const existing = candidate && !candidate.timedOut && !candidate.invalidated ? candidate : null;
722
875
  if (existing) {
723
- if (state.active?.inline === true)
724
- return await existing.promise;
725
- return await this.waitForCoalesced(existing.promise, timeoutMs, operation, background, key);
876
+ if (!background && existing.background && existing !== state.active) {
877
+ const index = state.background.indexOf(existing);
878
+ if (index >= 0)
879
+ state.background.splice(index, 1);
880
+ existing.background = false;
881
+ state.foreground.push(existing);
882
+ }
883
+ return await existing.promise;
726
884
  }
727
885
  let resolve;
728
886
  let reject;
887
+ let settledResolve;
729
888
  const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
730
- const job = { key, background, operation, promise, resolve, reject, timer: null };
889
+ const settled = new Promise((resolveSettled) => { settledResolve = resolveSettled; });
890
+ void promise.catch(() => { });
891
+ const job = {
892
+ key, namespace, background, running: false, suspended: false, operation, promise, resolve, reject, timer: null,
893
+ resume: null, resumeResolve: null,
894
+ pullingStartedAt: 0,
895
+ timedOut: false,
896
+ invalidated: false,
897
+ cancel: null,
898
+ settled,
899
+ settledResolve,
900
+ };
731
901
  (background ? state.background : state.foreground).push(job);
732
902
  state.byKey.set(key, job);
733
903
  this.drainPullGate(state);
734
- if (state.active !== job && state.active?.inline !== true) {
735
- job.timer = setTimeout(() => this.bypassQueuedPull(state, job), timeoutMs);
736
- }
904
+ this.checkPullGate(state, this.pullGateName(key));
737
905
  return await promise;
738
906
  }
739
907
  async executePullOperation(operation, background) {
740
- if (background)
741
- return await this.runtime.client._withBackgroundRpc(operation);
742
- return await operation();
743
- }
744
- waitForCoalesced(shared, timeoutMs, operation, background, key) {
745
- return new Promise((resolve, reject) => {
746
- let settled = false;
747
- const timer = setTimeout(() => {
748
- if (settled)
749
- return;
750
- settled = true;
751
- this.runtime.client._clientLog.warn(`pull gate coalesced wait timeout, bypass: gate=${this.pullGateName(key)} key=${key}`);
752
- void this.executeBypassedPull(key, operation, background).then(resolve, reject);
753
- }, timeoutMs);
754
- shared.then((value) => { if (!settled) {
755
- settled = true;
756
- clearTimeout(timer);
757
- resolve(value);
758
- } }, (error) => { if (!settled) {
759
- settled = true;
760
- clearTimeout(timer);
761
- reject(error);
762
- } });
763
- });
908
+ const client = this.runtime.client;
909
+ const hadPrevious = Object.prototype.hasOwnProperty.call(client, '_pullOperationGeneration');
910
+ const previous = client._pullOperationGeneration;
911
+ client._pullOperationGeneration = this.pullGeneration;
912
+ try {
913
+ if (background)
914
+ return await client._withBackgroundRpc(operation);
915
+ return await operation();
916
+ }
917
+ finally {
918
+ if (hadPrevious)
919
+ client._pullOperationGeneration = previous;
920
+ else
921
+ delete client._pullOperationGeneration;
922
+ }
923
+ }
924
+ pullOperationIsCurrent() {
925
+ const generation = this.runtime.client._pullOperationGeneration;
926
+ return generation === undefined || this.isPullGenerationCurrent(generation);
927
+ }
928
+ throwIfPullInvalidated() {
929
+ if (!this.pullOperationIsCurrent())
930
+ throw new Error('pull invalidated');
764
931
  }
765
- bypassQueuedPull(state, job) {
766
- if (state.active === job)
932
+ async yieldPullGate(key, nextKey, background) {
933
+ if (!key)
767
934
  return;
768
- const queue = job.background ? state.background : state.foreground;
769
- const index = queue.indexOf(job);
770
- if (index < 0)
935
+ const state = this.pullGateState(key);
936
+ const namespace = this.pullScopeKey(key);
937
+ const job = state.active?.key === key ? state.active : null;
938
+ if (!job || state.active !== job)
771
939
  return;
772
- queue.splice(index, 1);
940
+ const replacementKey = String(nextKey || key);
773
941
  if (state.byKey.get(job.key) === job)
774
942
  state.byKey.delete(job.key);
775
- job.timer = null;
776
- this.runtime.client._clientLog.warn(`pull gate queue timeout, bypass: gate=${this.pullGateName(job.key)} key=${job.key}`);
777
- void this.executeBypassedPull(job.key, job.operation, job.background).then(job.resolve, job.reject);
943
+ job.key = replacementKey;
944
+ job.namespace = this.pullScopeKey(replacementKey);
945
+ if (background !== undefined)
946
+ job.background = background;
947
+ state.byKey.set(job.key, job);
948
+ state.lifecycle.set(namespace, 'pulled');
949
+ job.suspended = true;
950
+ job.resume = new Promise((resolve) => { job.resumeResolve = resolve; });
951
+ state.active = null;
952
+ (job.background ? state.background : state.foreground).push(job);
953
+ this.drainPullGate(state);
954
+ if (job.resume) {
955
+ await job.resume;
956
+ if (job.invalidated)
957
+ throw new Error('pull invalidated');
958
+ }
959
+ }
960
+ getPullLifecycle(namespace) {
961
+ const ns = this.pullScopeKey(namespace);
962
+ if (!ns)
963
+ return 'idle';
964
+ return this.pullGateStates.get(this.pullGateName(ns))?.lifecycle.get(ns) ?? 'idle';
965
+ }
966
+ setPullLifecycle(namespace, lifecycle) {
967
+ const ns = this.pullScopeKey(namespace);
968
+ if (!ns)
969
+ return;
970
+ const state = this.pullGateStates.get(this.pullGateName(ns));
971
+ if (!state || state.active?.namespace !== ns)
972
+ return;
973
+ state.lifecycle.set(ns, lifecycle);
778
974
  }
779
975
  drainPullGate(state) {
780
976
  if (state.active)
781
977
  return;
782
- const job = state.foreground.shift() ?? state.background.shift();
978
+ const takeRunnable = (queue) => {
979
+ const index = queue.findIndex((candidate) => !this.inlineRealtimeScopes.has(candidate.namespace));
980
+ return index >= 0 ? queue.splice(index, 1)[0] : undefined;
981
+ };
982
+ const job = takeRunnable(state.foreground) ?? takeRunnable(state.background);
783
983
  if (!job)
784
984
  return;
785
985
  state.active = job;
786
986
  if (job.timer)
787
987
  clearTimeout(job.timer);
788
- void this.executePullOperation(job.operation, job.background).then(job.resolve, job.reject).finally(() => {
988
+ job.pullingStartedAt = Date.now();
989
+ job.timedOut = false;
990
+ state.lifecycle.set(job.namespace, 'pulling');
991
+ const resumeResolve = job.resumeResolve;
992
+ job.resume = null;
993
+ job.resumeResolve = null;
994
+ if (job.suspended) {
995
+ job.suspended = false;
996
+ resumeResolve?.();
997
+ return;
998
+ }
999
+ job.running = true;
1000
+ void this.executePullOperation(job.operation, job.background).then((value) => { if (!job.timedOut && !job.invalidated)
1001
+ job.resolve(value); }, (error) => { if (!job.timedOut && !job.invalidated)
1002
+ job.reject(error); }).finally(() => {
1003
+ state.lifecycle.set(job.namespace, 'idle');
789
1004
  if (state.active === job)
790
1005
  state.active = null;
791
1006
  if (state.byKey.get(job.key) === job)
792
1007
  state.byKey.delete(job.key);
793
1008
  this.drainPullGate(state);
1009
+ job.settledResolve?.();
1010
+ job.settledResolve = null;
1011
+ if (!job.invalidated)
1012
+ this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
794
1013
  });
795
1014
  }
796
- armQueuedPullTimers(state) {
797
- const timeoutMs = Math.max(0, Number(this.runtime.client._PULL_GATE_QUEUE_TIMEOUT_MS ?? 3_000) || 0);
798
- for (const job of [...state.foreground, ...state.background]) {
799
- if (!job.timer)
800
- job.timer = setTimeout(() => this.bypassQueuedPull(state, job), timeoutMs);
801
- }
802
- }
1015
+ armQueuedPullTimers(_state) { }
803
1016
  async rawCall(method, params, options) {
804
1017
  const client = this.runtime.client;
805
1018
  const payload = { ...(params ?? {}) };
@@ -810,19 +1023,49 @@ export class RpcPipeline {
810
1023
  await this.applyClientSignature(method, payload);
811
1024
  }
812
1025
  const timeout = options?.timeout;
813
- if (rpcBackground) {
814
- return await client._transport.call(method, payload, timeout, options?.trace, true);
815
- }
816
- if (options?.trace !== undefined) {
817
- return await client._transport.call(method, payload, timeout, options.trace);
1026
+ const invokeTransport = () => {
1027
+ let request;
1028
+ if (rpcBackground)
1029
+ request = client._transport.call(method, payload, timeout, options?.trace, true);
1030
+ else if (options?.trace !== undefined)
1031
+ request = client._transport.call(method, payload, timeout, options.trace);
1032
+ else if (timeout !== undefined)
1033
+ request = client._transport.call(method, payload, timeout);
1034
+ else
1035
+ request = client._transport.call(method, payload);
1036
+ return this.bindActivePullCancellation(method, payload, request);
1037
+ };
1038
+ const result = await this.transportCallWithIdentityRecovery(invokeTransport, method, payload);
1039
+ this.throwIfPullInvalidated();
1040
+ return result;
1041
+ }
1042
+ async transportCallWithIdentityRecovery(operation, method, params) {
1043
+ try {
1044
+ return await operation();
818
1045
  }
819
- if (timeout !== undefined) {
820
- return await client._transport.call(method, payload, timeout);
1046
+ catch (err) {
1047
+ const recover = this.runtime.client._recoverIdentityAdmission;
1048
+ if (typeof recover !== 'function' || !await recover.call(this.runtime.client, err, method, params))
1049
+ throw err;
1050
+ let lastError = err;
1051
+ for (const delay of IDENTITY_ADMISSION_RETRY_DELAYS_MS) {
1052
+ if (delay > 0)
1053
+ await new Promise((resolve) => setTimeout(resolve, delay));
1054
+ try {
1055
+ return await operation();
1056
+ }
1057
+ catch (retryError) {
1058
+ if (!sameIdentityAdmissionRejection(retryError, err))
1059
+ throw retryError;
1060
+ lastError = retryError;
1061
+ }
1062
+ }
1063
+ throw lastError;
821
1064
  }
822
- return await client._transport.call(method, payload);
823
1065
  }
824
- async postprocessResult(method, params, result) {
1066
+ async postprocessResult(method, params, result, options = {}) {
825
1067
  const client = this.runtime.client;
1068
+ this.throwIfPullInvalidated();
826
1069
  let next = result;
827
1070
  if ((method === 'group.send' || method === 'group.v2.send') && isJsonObject(next)) {
828
1071
  next = normalizeGroupMentionMode(next);
@@ -839,12 +1082,40 @@ export class RpcPipeline {
839
1082
  next = await client._decryptMessageThoughts(next);
840
1083
  }
841
1084
  if (method === 'message.pull' && isJsonObject(next)) {
842
- this.postprocessMessagePull(params, next);
1085
+ const isTail = String(next.window_mode ?? params.window_mode ?? '').trim().toLowerCase() === 'tail';
1086
+ if (!isTail && client._aid && client._seqTracker) {
1087
+ const ns = `p2p:${client._aid}`;
1088
+ const afterSeq = Math.max(0, Number(params.after_seq ?? 0) || 0);
1089
+ await client._delivery.commitPlainForwardPage(ns, afterSeq, 'message.pull', next);
1090
+ }
1091
+ else {
1092
+ this.postprocessMessagePull(params, next);
1093
+ }
843
1094
  }
844
1095
  if (method === 'group.pull' && isJsonObject(next)) {
845
- this.postprocessGroupPull(params, next);
1096
+ if (Array.isArray(next.messages)) {
1097
+ next.messages = next.messages.map((message) => normalizeGroupMentionMode(message));
1098
+ }
1099
+ const gid = String(params.group_id ?? params.group_aid ?? '').trim();
1100
+ const explicitCursorParams = this.explicitGroupCursorParams(params);
1101
+ const ownsCursor = Object.keys(explicitCursorParams).length === 0
1102
+ || this.groupCursorTargetsCurrentInstance(explicitCursorParams);
1103
+ const isTail = String(next.window_mode ?? params.window_mode ?? '').trim().toLowerCase() === 'tail';
1104
+ if (!ownsCursor) {
1105
+ client._clientLog.debug(`group.pull external cursor skips local Forward commit: group=${gid}`);
1106
+ }
1107
+ else if (!isTail && gid && client._seqTracker) {
1108
+ const ns = `group:${gid}`;
1109
+ const afterSeq = Math.max(0, Number(params.after_message_seq ?? params.after_seq ?? 0) || 0);
1110
+ await client._delivery.commitPlainForwardPage(ns, afterSeq, 'group.pull', next, gid);
1111
+ }
1112
+ else {
1113
+ this.postprocessGroupPull(params, next);
1114
+ }
1115
+ }
1116
+ if (!options.skipGroupState) {
1117
+ next = await client._groupState.postprocessResult(method, params, next);
846
1118
  }
847
- next = await client._groupState.postprocessResult(method, params, next);
848
1119
  return next;
849
1120
  }
850
1121
  _sleep(ms) {
@@ -960,6 +1231,19 @@ export class RpcPipeline {
960
1231
  }
961
1232
  return cursorParams;
962
1233
  }
1234
+ explicitGroupCursorParams(params) {
1235
+ const value = params._group_cursor_params;
1236
+ if (!isJsonObject(value))
1237
+ return {};
1238
+ return { ...value };
1239
+ }
1240
+ groupCursorTargetsCurrentInstance(params) {
1241
+ const client = this.runtime.client;
1242
+ const deviceId = String(params.device_id ?? '').trim();
1243
+ const slotId = String(params.slot_id ?? '').trim();
1244
+ return (!deviceId || deviceId === (client._deviceId ?? ''))
1245
+ && (!slotId || slotId === (client._slotId ?? ''));
1246
+ }
963
1247
  validateMessageRecipient(toAid) {
964
1248
  if (isGroupServiceAid(toAid)) {
965
1249
  throw new ValidationError('message.send receiver cannot be group.{issuer}; use group.send instead');