@agentunion/fastaun-browser 0.5.6 → 0.5.8

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 (102) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/_packed_docs/CHANGELOG.md +74 -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/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
  10. 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
  11. 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
  12. 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
  13. package/_packed_docs/group-message-rpc-alignment-gaps.md +741 -0
  14. package/_packed_docs/message-online-push-alignment.md +572 -0
  15. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1 -1
  16. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +10 -10
  17. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +2 -2
  18. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +30 -10
  19. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +23 -20
  20. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +3 -3
  21. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +5 -6
  22. package/_packed_docs/sdk/09-group-rpc-manual.md +11 -7
  23. package/_packed_docs/sdk/09-message-rpc-manual.md +5 -3
  24. package/_packed_docs/sdk/09-storage-rpc-manual.md +14 -3
  25. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +19 -17
  26. package/_packed_docs/sdk/CHANGELOG-0.5.6.md +73 -0
  27. package/_packed_docs/sdk/INDEX.md +22 -22
  28. package/_packed_docs/sdk/README.md +6 -5
  29. package/_packed_docs//345/217/221/345/270/203/346/212/245/345/221/212-0.5.6.md +260 -0
  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 +229 -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 +26 -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 +29895 -21968
  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 +33 -5
  55. package/dist/client/delivery.d.ts.map +1 -1
  56. package/dist/client/delivery.js +597 -220
  57. package/dist/client/delivery.js.map +1 -1
  58. package/dist/client/lifecycle.d.ts +15 -1
  59. package/dist/client/lifecycle.d.ts.map +1 -1
  60. package/dist/client/lifecycle.js +452 -151
  61. package/dist/client/lifecycle.js.map +1 -1
  62. package/dist/client/rpc-pipeline.d.ts +17 -6
  63. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  64. package/dist/client/rpc-pipeline.js +284 -115
  65. package/dist/client/rpc-pipeline.js.map +1 -1
  66. package/dist/client/runtime.d.ts +1 -3
  67. package/dist/client/runtime.d.ts.map +1 -1
  68. package/dist/client/runtime.js +4 -7
  69. package/dist/client/runtime.js.map +1 -1
  70. package/dist/client/v2-e2ee.d.ts +20 -2
  71. package/dist/client/v2-e2ee.d.ts.map +1 -1
  72. package/dist/client/v2-e2ee.js +398 -114
  73. package/dist/client/v2-e2ee.js.map +1 -1
  74. package/dist/client.d.ts +22 -10
  75. package/dist/client.d.ts.map +1 -1
  76. package/dist/client.js +396 -104
  77. package/dist/client.js.map +1 -1
  78. package/dist/facades.d.ts +23 -1
  79. package/dist/facades.d.ts.map +1 -1
  80. package/dist/facades.js +51 -7
  81. package/dist/facades.js.map +1 -1
  82. package/dist/group-id.d.ts.map +1 -1
  83. package/dist/group-id.js +2 -17
  84. package/dist/group-id.js.map +1 -1
  85. package/dist/index.d.ts +4 -3
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +2 -1
  88. package/dist/index.js.map +1 -1
  89. package/dist/seq-tracker.d.ts.map +1 -1
  90. package/dist/seq-tracker.js +8 -2
  91. package/dist/seq-tracker.js.map +1 -1
  92. package/dist/tools/cross-sdk-agent.js +23 -1
  93. package/dist/tools/cross-sdk-agent.js.map +1 -1
  94. package/dist/transport.d.ts.map +1 -1
  95. package/dist/transport.js +8 -1
  96. package/dist/transport.js.map +1 -1
  97. package/dist/validators.d.ts.map +1 -1
  98. package/dist/validators.js +8 -14
  99. package/dist/validators.js.map +1 -1
  100. package/dist/version.d.ts +1 -1
  101. package/dist/version.js +1 -1
  102. 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';
@@ -119,9 +119,10 @@ async function signingCertFingerprint(certPem) {
119
119
  return await cached;
120
120
  }
121
121
  const PULL_GATE_STALE_MS = 30000;
122
+ const PULL_GATE_OPERATION_TIMEOUT_MS = 3000;
122
123
  const NON_IDEMPOTENT_TIMEOUT = 35;
123
124
  const NON_IDEMPOTENT_METHODS = new Set([
124
- 'message.send', 'group.send', 'group.create', 'group.invite',
125
+ 'message.send', 'mail.send', 'group.send', 'group.create', 'group.invite',
125
126
  'group.kick', 'group.remove_member', 'group.leave', 'group.dissolve',
126
127
  'group.set_settings',
127
128
  'group.update_announcement', 'group.update_rules',
@@ -168,7 +169,7 @@ function isTransientV2PullFallbackError(error) {
168
169
  export class RpcPipeline {
169
170
  runtime;
170
171
  pullGateStates = new Map();
171
- bypassedRealtimePulls = new Map();
172
+ inlineRealtimeScopes = new Set();
172
173
  constructor(runtime) {
173
174
  this.runtime = runtime;
174
175
  }
@@ -198,6 +199,8 @@ export class RpcPipeline {
198
199
  };
199
200
  const skipSendResultEnvelope = Boolean(p._skip_send_result_envelope);
200
201
  delete p._skip_send_result_envelope;
202
+ const deferGroupReadyPostprocess = Boolean(p._defer_group_ready_postprocess);
203
+ delete p._defer_group_ready_postprocess;
201
204
  if (method === 'message.send') {
202
205
  const encrypt = p.encrypt !== undefined ? p.encrypt : true;
203
206
  delete p.encrypt;
@@ -249,15 +252,11 @@ export class RpcPipeline {
249
252
  }
250
253
  const pullGateKey = this.pullGateKeyForCall(method, p);
251
254
  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));
255
+ return await this.runPullSerialized(pullGateKey, async () => await this.callImplInner(method, p, skipSendResultEnvelope, deferGroupReadyPostprocess), rpcBackground && this.pullGateBackgroundForCall(method, p));
257
256
  }
258
- return await runWithRpcPriority(() => this.callImplInner(method, p, skipSendResultEnvelope));
257
+ return await runWithRpcPriority(() => this.callImplInner(method, p, skipSendResultEnvelope, deferGroupReadyPostprocess));
259
258
  }
260
- async callImplInner(method, p, skipSendResultEnvelope = false) {
259
+ async callImplInner(method, p, skipSendResultEnvelope = false, deferGroupReadyPostprocess = false) {
261
260
  const client = this.runtime.client;
262
261
  if (method === 'message.v2.pull' || (method === 'message.pull' && hasMatchingV2Session(client))) {
263
262
  try {
@@ -265,7 +264,10 @@ export class RpcPipeline {
265
264
  client._clientLog.debug(`call route: ${method} -> V2 pull`);
266
265
  if (p.window_mode === 'tail')
267
266
  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 });
267
+ const messages = await client._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, {
268
+ gateLocked: true,
269
+ force: p.force === true,
270
+ });
269
271
  return { messages };
270
272
  }
271
273
  catch (exc) {
@@ -290,9 +292,11 @@ export class RpcPipeline {
290
292
  if (p.window_mode === 'tail')
291
293
  return await client._v2E2EE.pullGroupV2TailInternal(p);
292
294
  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 = {};
295
+ const cursorParams = this.explicitGroupCursorParams(p);
296
+ const ownsCursor = Object.keys(cursorParams).length === 0 || this.groupCursorTargetsCurrentInstance(cursorParams);
297
+ const pullOpts = {
298
+ gateLocked: true,
299
+ };
296
300
  if (hasExplicitAfterSeq)
297
301
  pullOpts.explicitAfterSeq = true;
298
302
  if (p.force === true)
@@ -317,8 +321,8 @@ export class RpcPipeline {
317
321
  if ((method === 'group.v2.ack' || method === 'group.ack_messages') && p.group_id && (method === 'group.v2.ack' || hasMatchingV2Session(client))) {
318
322
  await client._ensureV2SessionReady('group.ack_messages');
319
323
  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);
324
+ const cursorParams = this.explicitGroupCursorParams(p);
325
+ const ownsCursor = Object.keys(cursorParams).length === 0 || this.groupCursorTargetsCurrentInstance(cursorParams);
322
326
  if (!ownsCursor) {
323
327
  return await client._rawGroupAckMessages(p);
324
328
  }
@@ -327,11 +331,15 @@ export class RpcPipeline {
327
331
  await this.applyClientSignature(method, p);
328
332
  const callTimeout = NON_IDEMPOTENT_METHODS.has(method) ? NON_IDEMPOTENT_TIMEOUT : undefined;
329
333
  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);
334
+ const invokeTransport = () => {
335
+ const request = callTimeout
336
+ ? client._transport.call(method, p, callTimeout, undefined, rpcBackground)
337
+ : client._transport.call(method, p, undefined, undefined, rpcBackground);
338
+ return this.bindActivePullCancellation(method, p, request);
339
+ };
340
+ let result = await this.transportCallWithIdentityRecovery(invokeTransport, method, p);
333
341
  const sendResultModeSource = result;
334
- result = await this.postprocessResult(method, p, result);
342
+ result = await this.postprocessResult(method, p, result, { skipGroupState: deferGroupReadyPostprocess });
335
343
  if (!skipSendResultEnvelope) {
336
344
  result = client._delivery.attachSendResultEnvelope(method, p, result, Boolean(p.encrypted), sendResultModeSource);
337
345
  }
@@ -561,23 +569,58 @@ export class RpcPipeline {
561
569
  return true;
562
570
  }
563
571
  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}`;
572
+ void key;
573
+ return 'pull';
571
574
  }
572
575
  pullGateState(key) {
573
576
  const name = this.pullGateName(key);
574
577
  let state = this.pullGateStates.get(name);
575
578
  if (!state) {
576
- state = { active: null, foreground: [], background: [], byKey: new Map() };
579
+ state = { active: null, foreground: [], background: [], byKey: new Map(), lifecycle: new Map(), watchdog: null };
577
580
  this.pullGateStates.set(name, state);
581
+ this.startPullGateWatchdog(state, name);
578
582
  }
579
583
  return state;
580
584
  }
585
+ pullGateOperationTimeoutMs() {
586
+ const client = this.runtime.client;
587
+ const configured = Number(client._PULL_GATE_OPERATION_TIMEOUT_MS
588
+ ?? client._PULL_GATE_TIMEOUT_MS
589
+ ?? client._PULL_GATE_QUEUE_TIMEOUT_MS);
590
+ return Number.isFinite(configured) && configured > 0 ? configured : PULL_GATE_OPERATION_TIMEOUT_MS;
591
+ }
592
+ startPullGateWatchdog(state, name) {
593
+ if (state.watchdog)
594
+ return;
595
+ const interval = Math.min(50, Math.max(1, Math.floor(this.pullGateOperationTimeoutMs() / 4)));
596
+ const timer = setInterval(() => this.checkPullGate(state, name), interval);
597
+ state.watchdog = timer;
598
+ timer.unref?.();
599
+ }
600
+ checkPullGate(state, name) {
601
+ const active = state.active;
602
+ if (!active) {
603
+ this.drainPullGate(state);
604
+ return;
605
+ }
606
+ if (!active.pullingStartedAt || Date.now() - active.pullingStartedAt < this.pullGateOperationTimeoutMs())
607
+ return;
608
+ if (active.timedOut)
609
+ return;
610
+ active.timedOut = true;
611
+ active.cancel?.();
612
+ const err = new TimeoutError(`pull gate timeout: ${active.namespace}`, { retryable: true });
613
+ this.runtime.client._clientLog?.warn(`pull gate watchdog timeout: gate=${name} key=${active.key}`);
614
+ state.lifecycle.set(active.namespace, 'idle');
615
+ if (state.active === active)
616
+ state.active = null;
617
+ if (state.byKey.get(active.key) === active)
618
+ state.byKey.delete(active.key);
619
+ active.resolve = () => { };
620
+ active.reject(err);
621
+ this.drainPullGate(state);
622
+ this.runtime.client._delivery?.onPullGateIdle?.(active.namespace);
623
+ }
581
624
  realtimePullNamespace(key) {
582
625
  for (const marker of ['|tail|', '|forward|']) {
583
626
  const index = key.indexOf(marker);
@@ -590,6 +633,22 @@ export class RpcPipeline {
590
633
  const index = key.indexOf('|');
591
634
  return index < 0 ? key : key.slice(0, index);
592
635
  }
636
+ bindActivePullCancellation(method, params, request) {
637
+ const cancel = request.cancel;
638
+ const key = this.pullGateKeyForCall(method, params);
639
+ if (typeof cancel !== 'function' || !key)
640
+ return request;
641
+ const state = this.pullGateStates.get(this.pullGateName(key));
642
+ const active = state?.active;
643
+ if (!active || active.namespace !== this.pullScopeKey(key) || active.timedOut)
644
+ return request;
645
+ const boundCancel = () => cancel.call(request);
646
+ active.cancel = boundCancel;
647
+ return request.finally(() => {
648
+ if (active.cancel === boundCancel)
649
+ active.cancel = null;
650
+ });
651
+ }
593
652
  isPullResponseProcessing(key) {
594
653
  const responseKeys = this.runtime.client._pullResponseKeys;
595
654
  return Boolean(key) && (responseKeys?.get(this.pullScopeKey(key)) ?? 0) > 0;
@@ -599,33 +658,12 @@ export class RpcPipeline {
599
658
  return { events: [], count: 0 };
600
659
  return { messages: [], count: 0 };
601
660
  }
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
661
  hasPendingRealtimePull(ns) {
622
662
  const normalized = String(ns ?? '').trim();
623
663
  if (!normalized || (!normalized.startsWith('p2p:') && !normalized.startsWith('group:')))
624
664
  return false;
625
665
  if (this.isPullResponseProcessing(normalized))
626
666
  return true;
627
- if ((this.bypassedRealtimePulls.get(normalized) ?? 0) > 0)
628
- return true;
629
667
  const state = this.pullGateStates.get(this.pullGateName(normalized));
630
668
  if (!state)
631
669
  return false;
@@ -638,6 +676,36 @@ export class RpcPipeline {
638
676
  || state.foreground.some(matches)
639
677
  || state.background.some(matches);
640
678
  }
679
+ hasPullActivity(namespace, includePending = true) {
680
+ const ns = String(namespace ?? '').trim();
681
+ if ((!ns.startsWith('p2p:') && !ns.startsWith('group:'))
682
+ || this.inlineRealtimeScopes.has(ns))
683
+ return true;
684
+ const client = this.runtime.client;
685
+ if ((client._pullResponseKeys?.get?.(ns) ?? 0) > 0)
686
+ return true;
687
+ const pendingP2p = client._pendingP2pPullUpper;
688
+ const pendingGroup = client._pendingGroupPullUpper;
689
+ if (includePending && ((pendingP2p?.get(ns) ?? 0) > 0 || (pendingGroup?.get(ns) ?? 0) > 0
690
+ || client._delivery?.hasPendingPull?.(ns)))
691
+ return true;
692
+ for (const marker of client._gapFillDone ?? []) {
693
+ const value = String(marker ?? '').trim();
694
+ if (ns.startsWith('p2p:') && (value.startsWith('p2p_pull:') || value.startsWith('p2p_pull_retry:')))
695
+ return true;
696
+ if (ns.startsWith('group:') && value.startsWith(`group_pull:${ns}`))
697
+ return true;
698
+ }
699
+ for (const [key, gate] of client._pullGates?.entries?.() ?? []) {
700
+ if (gate?.inflight && this.pullScopeKey(key) === ns)
701
+ return true;
702
+ }
703
+ const state = this.pullGateStates.get(this.pullGateName(ns));
704
+ if (!state)
705
+ return false;
706
+ const matches = (job) => job !== null && job.namespace === ns;
707
+ return matches(state.active) || state.foreground.some(matches) || state.background.some(matches);
708
+ }
641
709
  async tryRunInlineRealtime(ns, seq, operation) {
642
710
  const normalized = String(ns ?? '').trim();
643
711
  if ((!normalized.startsWith('p2p:') && !normalized.startsWith('group:'))
@@ -645,8 +713,10 @@ export class RpcPipeline {
645
713
  return { acquired: false };
646
714
  }
647
715
  const client = this.runtime.client;
648
- if (this.isPullResponseProcessing(normalized)
649
- || (this.bypassedRealtimePulls.get(normalized) ?? 0) > 0) {
716
+ if (this.hasPullActivity(normalized)) {
717
+ return { acquired: false };
718
+ }
719
+ if (this.isPullResponseProcessing(normalized)) {
650
720
  return { acquired: false };
651
721
  }
652
722
  for (const [key, gate] of client._pullGates?.entries?.() ?? []) {
@@ -660,28 +730,40 @@ export class RpcPipeline {
660
730
  }
661
731
  const guard = {
662
732
  key,
733
+ namespace: normalized,
663
734
  background: false,
735
+ running: true,
736
+ suspended: false,
664
737
  inline: true,
665
738
  operation,
666
739
  promise: Promise.resolve(undefined),
667
740
  resolve: () => { },
668
741
  reject: () => { },
669
742
  timer: null,
743
+ resume: null,
744
+ resumeResolve: null,
745
+ pullingStartedAt: 0,
746
+ timedOut: false,
747
+ cancel: null,
670
748
  };
671
- state.active = guard;
749
+ this.inlineRealtimeScopes.add(normalized);
672
750
  try {
673
751
  return { acquired: true, value: await operation() };
674
752
  }
675
753
  finally {
754
+ this.inlineRealtimeScopes.delete(normalized);
676
755
  if (state.active === guard)
677
756
  state.active = null;
678
757
  this.drainPullGate(state);
758
+ this.runtime.client._delivery?.onPullGateIdle?.(normalized);
679
759
  this.armQueuedPullTimers(state);
680
760
  }
681
761
  }
682
762
  tryAcquirePullGate(key) {
683
763
  if (!key)
684
764
  return 0;
765
+ if (this.inlineRealtimeScopes.has(this.pullScopeKey(key)))
766
+ return null;
685
767
  const serialized = this.pullGateStates.get(this.pullGateName(key));
686
768
  if (serialized?.active?.inline === true)
687
769
  return null;
@@ -710,30 +792,41 @@ export class RpcPipeline {
710
792
  gate.startedAt = 0;
711
793
  }
712
794
  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 [];
716
- }
717
795
  if (!key)
718
796
  return await this.executePullOperation(operation, background);
719
797
  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);
798
+ const namespace = this.pullScopeKey(key);
799
+ const active = state.active?.namespace === namespace ? state.active : null;
800
+ const queuedForeground = state.foreground.find((job) => job.namespace === namespace);
801
+ const queuedBackground = state.background.find((job) => job.namespace === namespace);
802
+ const existing = background
803
+ ? (active ?? queuedForeground ?? queuedBackground)
804
+ : (queuedForeground ?? (active && !active.background ? active : null) ?? queuedBackground);
722
805
  if (existing) {
723
- if (state.active?.inline === true)
724
- return await existing.promise;
725
- return await this.waitForCoalesced(existing.promise, timeoutMs, operation, background, key);
806
+ if (!background && existing.background && existing !== state.active) {
807
+ const index = state.background.indexOf(existing);
808
+ if (index >= 0)
809
+ state.background.splice(index, 1);
810
+ existing.background = false;
811
+ state.foreground.push(existing);
812
+ }
813
+ return await existing.promise;
726
814
  }
727
815
  let resolve;
728
816
  let reject;
729
817
  const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
730
- const job = { key, background, operation, promise, resolve, reject, timer: null };
818
+ void promise.catch(() => { });
819
+ const job = {
820
+ key, namespace, background, running: false, suspended: false, operation, promise, resolve, reject, timer: null,
821
+ resume: null, resumeResolve: null,
822
+ pullingStartedAt: 0,
823
+ timedOut: false,
824
+ cancel: null,
825
+ };
731
826
  (background ? state.background : state.foreground).push(job);
732
827
  state.byKey.set(key, job);
733
828
  this.drainPullGate(state);
734
- if (state.active !== job && state.active?.inline !== true) {
735
- job.timer = setTimeout(() => this.bypassQueuedPull(state, job), timeoutMs);
736
- }
829
+ this.checkPullGate(state, this.pullGateName(key));
737
830
  return await promise;
738
831
  }
739
832
  async executePullOperation(operation, background) {
@@ -741,65 +834,86 @@ export class RpcPipeline {
741
834
  return await this.runtime.client._withBackgroundRpc(operation);
742
835
  return await operation();
743
836
  }
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
- });
764
- }
765
- bypassQueuedPull(state, job) {
766
- if (state.active === job)
837
+ async yieldPullGate(key, nextKey, background) {
838
+ if (!key)
767
839
  return;
768
- const queue = job.background ? state.background : state.foreground;
769
- const index = queue.indexOf(job);
770
- if (index < 0)
840
+ const state = this.pullGateState(key);
841
+ const namespace = this.pullScopeKey(key);
842
+ const job = state.active?.namespace === namespace
843
+ ? state.active
844
+ : [...state.foreground, ...state.background].find((candidate) => candidate.namespace === namespace);
845
+ if (!job || state.active !== job)
771
846
  return;
772
- queue.splice(index, 1);
847
+ const replacementKey = String(nextKey || key);
773
848
  if (state.byKey.get(job.key) === job)
774
849
  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);
850
+ job.key = replacementKey;
851
+ job.namespace = this.pullScopeKey(replacementKey);
852
+ if (background !== undefined)
853
+ job.background = background;
854
+ state.byKey.set(job.key, job);
855
+ state.lifecycle.set(namespace, 'pulled');
856
+ job.suspended = true;
857
+ job.resume = new Promise((resolve) => { job.resumeResolve = resolve; });
858
+ state.active = null;
859
+ (job.background ? state.background : state.foreground).push(job);
860
+ this.drainPullGate(state);
861
+ if (job.resume)
862
+ await job.resume;
863
+ }
864
+ getPullLifecycle(namespace) {
865
+ const ns = this.pullScopeKey(namespace);
866
+ if (!ns)
867
+ return 'idle';
868
+ return this.pullGateStates.get(this.pullGateName(ns))?.lifecycle.get(ns) ?? 'idle';
869
+ }
870
+ setPullLifecycle(namespace, lifecycle) {
871
+ const ns = this.pullScopeKey(namespace);
872
+ if (!ns)
873
+ return;
874
+ const state = this.pullGateStates.get(this.pullGateName(ns));
875
+ if (!state || state.active?.namespace !== ns)
876
+ return;
877
+ state.lifecycle.set(ns, lifecycle);
778
878
  }
779
879
  drainPullGate(state) {
780
880
  if (state.active)
781
881
  return;
782
- const job = state.foreground.shift() ?? state.background.shift();
882
+ const takeRunnable = (queue) => {
883
+ const index = queue.findIndex((candidate) => !this.inlineRealtimeScopes.has(candidate.namespace));
884
+ return index >= 0 ? queue.splice(index, 1)[0] : undefined;
885
+ };
886
+ const job = takeRunnable(state.foreground) ?? takeRunnable(state.background);
783
887
  if (!job)
784
888
  return;
785
889
  state.active = job;
786
890
  if (job.timer)
787
891
  clearTimeout(job.timer);
892
+ job.pullingStartedAt = Date.now();
893
+ job.timedOut = false;
894
+ state.lifecycle.set(job.namespace, 'pulling');
895
+ const resumeResolve = job.resumeResolve;
896
+ job.resume = null;
897
+ job.resumeResolve = null;
898
+ if (job.suspended) {
899
+ job.suspended = false;
900
+ resumeResolve?.();
901
+ return;
902
+ }
903
+ job.running = true;
788
904
  void this.executePullOperation(job.operation, job.background).then(job.resolve, job.reject).finally(() => {
905
+ if (job.timedOut)
906
+ return;
907
+ state.lifecycle.set(job.namespace, 'idle');
789
908
  if (state.active === job)
790
909
  state.active = null;
791
910
  if (state.byKey.get(job.key) === job)
792
911
  state.byKey.delete(job.key);
793
912
  this.drainPullGate(state);
913
+ this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
794
914
  });
795
915
  }
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
- }
916
+ armQueuedPullTimers(_state) { }
803
917
  async rawCall(method, params, options) {
804
918
  const client = this.runtime.client;
805
919
  const payload = { ...(params ?? {}) };
@@ -810,18 +924,32 @@ export class RpcPipeline {
810
924
  await this.applyClientSignature(method, payload);
811
925
  }
812
926
  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);
927
+ const invokeTransport = () => {
928
+ let request;
929
+ if (rpcBackground)
930
+ request = client._transport.call(method, payload, timeout, options?.trace, true);
931
+ else if (options?.trace !== undefined)
932
+ request = client._transport.call(method, payload, timeout, options.trace);
933
+ else if (timeout !== undefined)
934
+ request = client._transport.call(method, payload, timeout);
935
+ else
936
+ request = client._transport.call(method, payload);
937
+ return this.bindActivePullCancellation(method, payload, request);
938
+ };
939
+ return await this.transportCallWithIdentityRecovery(invokeTransport, method, payload);
940
+ }
941
+ async transportCallWithIdentityRecovery(operation, method, params) {
942
+ try {
943
+ return await operation();
818
944
  }
819
- if (timeout !== undefined) {
820
- return await client._transport.call(method, payload, timeout);
945
+ catch (err) {
946
+ const recover = this.runtime.client._recoverIdentityAdmission;
947
+ if (typeof recover !== 'function' || !await recover.call(this.runtime.client, err, method, params))
948
+ throw err;
949
+ return await operation();
821
950
  }
822
- return await client._transport.call(method, payload);
823
951
  }
824
- async postprocessResult(method, params, result) {
952
+ async postprocessResult(method, params, result, options = {}) {
825
953
  const client = this.runtime.client;
826
954
  let next = result;
827
955
  if ((method === 'group.send' || method === 'group.v2.send') && isJsonObject(next)) {
@@ -839,12 +967,40 @@ export class RpcPipeline {
839
967
  next = await client._decryptMessageThoughts(next);
840
968
  }
841
969
  if (method === 'message.pull' && isJsonObject(next)) {
842
- this.postprocessMessagePull(params, next);
970
+ const isTail = String(next.window_mode ?? params.window_mode ?? '').trim().toLowerCase() === 'tail';
971
+ if (!isTail && client._aid && client._seqTracker) {
972
+ const ns = `p2p:${client._aid}`;
973
+ const afterSeq = Math.max(0, Number(params.after_seq ?? 0) || 0);
974
+ await client._delivery.commitPlainForwardPage(ns, afterSeq, 'message.pull', next);
975
+ }
976
+ else {
977
+ this.postprocessMessagePull(params, next);
978
+ }
843
979
  }
844
980
  if (method === 'group.pull' && isJsonObject(next)) {
845
- this.postprocessGroupPull(params, next);
981
+ if (Array.isArray(next.messages)) {
982
+ next.messages = next.messages.map((message) => normalizeGroupMentionMode(message));
983
+ }
984
+ const gid = String(params.group_id ?? params.group_aid ?? '').trim();
985
+ const explicitCursorParams = this.explicitGroupCursorParams(params);
986
+ const ownsCursor = Object.keys(explicitCursorParams).length === 0
987
+ || this.groupCursorTargetsCurrentInstance(explicitCursorParams);
988
+ const isTail = String(next.window_mode ?? params.window_mode ?? '').trim().toLowerCase() === 'tail';
989
+ if (!ownsCursor) {
990
+ client._clientLog.debug(`group.pull external cursor skips local Forward commit: group=${gid}`);
991
+ }
992
+ else if (!isTail && gid && client._seqTracker) {
993
+ const ns = `group:${gid}`;
994
+ const afterSeq = Math.max(0, Number(params.after_message_seq ?? params.after_seq ?? 0) || 0);
995
+ await client._delivery.commitPlainForwardPage(ns, afterSeq, 'group.pull', next, gid);
996
+ }
997
+ else {
998
+ this.postprocessGroupPull(params, next);
999
+ }
1000
+ }
1001
+ if (!options.skipGroupState) {
1002
+ next = await client._groupState.postprocessResult(method, params, next);
846
1003
  }
847
- next = await client._groupState.postprocessResult(method, params, next);
848
1004
  return next;
849
1005
  }
850
1006
  _sleep(ms) {
@@ -960,6 +1116,19 @@ export class RpcPipeline {
960
1116
  }
961
1117
  return cursorParams;
962
1118
  }
1119
+ explicitGroupCursorParams(params) {
1120
+ const value = params._group_cursor_params;
1121
+ if (!isJsonObject(value))
1122
+ return {};
1123
+ return { ...value };
1124
+ }
1125
+ groupCursorTargetsCurrentInstance(params) {
1126
+ const client = this.runtime.client;
1127
+ const deviceId = String(params.device_id ?? '').trim();
1128
+ const slotId = String(params.slot_id ?? '').trim();
1129
+ return (!deviceId || deviceId === (client._deviceId ?? ''))
1130
+ && (!slotId || slotId === (client._slotId ?? ''));
1131
+ }
963
1132
  validateMessageRecipient(toAid) {
964
1133
  if (isGroupServiceAid(toAid)) {
965
1134
  throw new ValidationError('message.send receiver cannot be group.{issuer}; use group.send instead');