@agentunion/fastaun-browser 0.5.19 → 0.5.20

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 (114) hide show
  1. package/CHANGELOG.md +129 -78
  2. package/_packed_docs/CHANGELOG.md +129 -78
  3. package/_packed_docs/INDEX.md +201 -195
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +93 -90
  5. package/_packed_docs/agent.md/SCHEMA.md +7 -7
  6. package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +11 -11
  7. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1072 -1072
  8. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +676 -676
  9. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +8 -12
  10. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +443 -0
  11. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
  12. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +658 -0
  13. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
  14. package/_packed_docs/protocol/agent.md/SCHEMA.md +1 -1
  15. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +1 -1
  16. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +67 -63
  17. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
  18. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +35 -8
  19. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +3 -3
  20. package/_packed_docs/sdk/09-group-rpc-manual.md +258 -254
  21. package/_packed_docs/sdk/09-message-rpc-manual.md +7 -4
  22. package/_packed_docs/sdk/09-storage-rpc-manual.md +18 -26
  23. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +61 -52
  24. package/_packed_docs/sdk/INDEX.md +92 -80
  25. package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
  26. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
  27. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +922 -0
  28. package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
  29. package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +300 -0
  30. package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +382 -0
  31. package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -0
  32. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +476 -0
  33. package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
  34. package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
  35. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +173 -0
  36. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +211 -0
  37. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/345/256/236/347/216/260/347/247/273/346/244/215/346/214/207/345/215/227.md +331 -0
  38. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +207 -0
  39. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/344/273/243/347/240/201/345/256/241/346/237/245/346/212/245/345/221/212.md +252 -0
  40. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/345/256/236/346/226/275/350/256/241/345/210/222.md +156 -0
  41. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/346/226/271/346/241/210.md +273 -0
  42. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/350/257/246/347/273/206/350/256/276/350/256/241.md +211 -0
  43. package/dist/agent-md-schema.d.ts +15 -4
  44. package/dist/agent-md-schema.d.ts.map +1 -1
  45. package/dist/agent-md-schema.js +64 -30
  46. package/dist/agent-md-schema.js.map +1 -1
  47. package/dist/agent-md.d.ts +10 -2
  48. package/dist/agent-md.d.ts.map +1 -1
  49. package/dist/agent-md.js +278 -121
  50. package/dist/agent-md.js.map +1 -1
  51. package/dist/aid-store.d.ts +46 -0
  52. package/dist/aid-store.d.ts.map +1 -1
  53. package/dist/aid-store.js +149 -10
  54. package/dist/aid-store.js.map +1 -1
  55. package/dist/aid.d.ts +4 -1
  56. package/dist/aid.d.ts.map +1 -1
  57. package/dist/aid.js +23 -42
  58. package/dist/aid.js.map +1 -1
  59. package/dist/auth.d.ts +1 -1
  60. package/dist/auth.d.ts.map +1 -1
  61. package/dist/auth.js +85 -18
  62. package/dist/auth.js.map +1 -1
  63. package/dist/bundle.js +1581 -781
  64. package/dist/cert-utils.d.ts +49 -1
  65. package/dist/cert-utils.d.ts.map +1 -1
  66. package/dist/cert-utils.js +102 -56
  67. package/dist/cert-utils.js.map +1 -1
  68. package/dist/client/delivery.d.ts +7 -0
  69. package/dist/client/delivery.d.ts.map +1 -1
  70. package/dist/client/delivery.js +243 -28
  71. package/dist/client/delivery.js.map +1 -1
  72. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  73. package/dist/client/rpc-pipeline.js +89 -77
  74. package/dist/client/rpc-pipeline.js.map +1 -1
  75. package/dist/client/v2-e2ee.d.ts.map +1 -1
  76. package/dist/client/v2-e2ee.js +99 -80
  77. package/dist/client/v2-e2ee.js.map +1 -1
  78. package/dist/client.d.ts.map +1 -1
  79. package/dist/client.js +31 -17
  80. package/dist/client.js.map +1 -1
  81. package/dist/crypto.d.ts.map +1 -1
  82. package/dist/crypto.js +8 -6
  83. package/dist/crypto.js.map +1 -1
  84. package/dist/events.d.ts +5 -2
  85. package/dist/events.d.ts.map +1 -1
  86. package/dist/events.js +33 -14
  87. package/dist/events.js.map +1 -1
  88. package/dist/facades.js +5 -5
  89. package/dist/facades.js.map +1 -1
  90. package/dist/index.d.ts +1 -1
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js.map +1 -1
  93. package/dist/org-protocol.d.ts.map +1 -1
  94. package/dist/org-protocol.js +72 -12
  95. package/dist/org-protocol.js.map +1 -1
  96. package/dist/ownership-v21.d.ts +25 -8
  97. package/dist/ownership-v21.d.ts.map +1 -1
  98. package/dist/ownership-v21.js +142 -73
  99. package/dist/ownership-v21.js.map +1 -1
  100. package/dist/ownership-workflow.d.ts +6 -1
  101. package/dist/ownership-workflow.d.ts.map +1 -1
  102. package/dist/ownership-workflow.js +221 -58
  103. package/dist/ownership-workflow.js.map +1 -1
  104. package/dist/tools/cross-sdk-agent.d.ts +2 -1
  105. package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
  106. package/dist/tools/cross-sdk-agent.js +91 -8
  107. package/dist/tools/cross-sdk-agent.js.map +1 -1
  108. package/dist/transport.d.ts +1 -0
  109. package/dist/transport.d.ts.map +1 -1
  110. package/dist/transport.js +2 -0
  111. package/dist/transport.js.map +1 -1
  112. package/dist/version.d.ts +1 -1
  113. package/dist/version.js +1 -1
  114. package/package.json +1 -1
@@ -133,6 +133,9 @@ export class MessageDeliveryEngine {
133
133
  pendingPullNoProgressAcks = null;
134
134
  onlineUnreadHintTargets = null;
135
135
  onlineUnreadHintOwners = null;
136
+ backgroundPullDemands = new Map();
137
+ backgroundPullTimer = null;
138
+ backgroundPullRecoveryQueued = false;
136
139
  realtimeAcking = null;
137
140
  pendingP2PInlineAcks = null;
138
141
  pendingGroupInlineAcks = null;
@@ -161,6 +164,11 @@ export class MessageDeliveryEngine {
161
164
  this.pendingPullNoProgressAcks = null;
162
165
  this.onlineUnreadHintTargets = null;
163
166
  this.onlineUnreadHintOwners = null;
167
+ this.backgroundPullDemands.clear();
168
+ if (this.backgroundPullTimer !== null)
169
+ clearTimeout(this.backgroundPullTimer);
170
+ this.backgroundPullTimer = null;
171
+ this.backgroundPullRecoveryQueued = false;
164
172
  this.realtimeTailResults = null;
165
173
  this.realtimeAcking = null;
166
174
  this.pendingP2PInlineAcks = null;
@@ -326,8 +334,107 @@ export class MessageDeliveryEngine {
326
334
  if (!ns)
327
335
  return;
328
336
  this.schedulePendingPullIfNeeded(ns, 'pull-gate-idle');
337
+ this.requestBackgroundPullRecovery();
338
+ }
339
+ recordBackgroundPullDemand(ns, throughSeq = 0, probe = false) {
340
+ const demand = this.backgroundPullDemands.get(ns) ?? {
341
+ throughSeq: 0, hasMore: false, probe: false, afterSeq: -1, retryAt: 0,
342
+ };
343
+ demand.throughSeq = Math.max(demand.throughSeq, positiveSafeSequenceHint(throughSeq));
344
+ demand.probe ||= probe;
345
+ this.backgroundPullDemands.set(ns, demand);
346
+ return demand;
347
+ }
348
+ observeBackgroundPullResponse(ns, afterSeq, result, limit = 50) {
349
+ if (!ns || this.runtime.client._sessionOptions?.background_sync === false)
350
+ return;
351
+ const cursor = isJsonObject(result.cursor) ? result.cursor : null;
352
+ const rows = Array.isArray(result.messages) ? result.messages : Array.isArray(result.events) ? result.events : [];
353
+ let throughSeq = Math.max(positiveSafeSequenceHint(cursor?.latest_seq), positiveSafeSequenceHint(result.latest_seq), positiveSafeSequenceHint(result.latest_message_seq), positiveSafeSequenceHint(result.latest_event_seq));
354
+ for (const row of rows) {
355
+ if (!isJsonObject(row) || this.isVolatileMessage(row))
356
+ continue;
357
+ throughSeq = Math.max(throughSeq, positiveSafeSequenceHint(row.seq ?? row.event_seq));
358
+ }
359
+ const demand = this.recordBackgroundPullDemand(ns, throughSeq);
360
+ if (afterSeq >= demand.afterSeq) {
361
+ demand.afterSeq = afterSeq;
362
+ demand.hasMore = result.has_more === true
363
+ || positiveSafeSequenceHint(result.remaining) > 0
364
+ || (!Object.prototype.hasOwnProperty.call(result, 'has_more') && limit > 0 && rows.length >= limit);
365
+ demand.probe = false;
366
+ }
367
+ this.requestBackgroundPullRecovery();
368
+ }
369
+ finishBackgroundPull(ns, afterSeq, failed) {
370
+ const demand = this.backgroundPullDemands.get(ns);
371
+ if (demand) {
372
+ const state = this.syncState(ns);
373
+ const ack = state.ack;
374
+ if (ack < state.tail - 1)
375
+ demand.throughSeq = Math.max(demand.throughSeq, state.tail - 1);
376
+ demand.probe = failed;
377
+ if (!demand.probe && !demand.hasMore && ack >= demand.throughSeq)
378
+ this.backgroundPullDemands.delete(ns);
379
+ else
380
+ demand.retryAt = failed || ack <= afterSeq || ack <= (this.pendingPullNoProgressAcks?.get(ns) ?? -1)
381
+ ? Date.now() + P2P_GAP_FILL_RETRY_BASE_MS
382
+ : 0;
383
+ }
384
+ this.requestBackgroundPullRecovery();
385
+ }
386
+ requestBackgroundPullRecovery() {
387
+ if (this.backgroundPullRecoveryQueued)
388
+ return;
389
+ const generation = this.captureInlineGeneration();
390
+ this.backgroundPullRecoveryQueued = true;
391
+ void Promise.resolve().then(() => {
392
+ if (!this.isInlineGenerationCurrent(generation))
393
+ return;
394
+ this.backgroundPullRecoveryQueued = false;
395
+ const client = this.runtime.client;
396
+ if (!client._aid || client._sessionOptions?.background_sync === false
397
+ || client.state !== ConnectionState.READY || client._closing)
398
+ return;
399
+ if (this.backgroundPullTimer !== null)
400
+ clearTimeout(this.backgroundPullTimer);
401
+ this.backgroundPullTimer = null;
402
+ let retryAt = 0;
403
+ for (const [ns, demand] of this.backgroundPullDemands) {
404
+ if (ns.startsWith('p2p:') && ns !== `p2p:${client._aid}`)
405
+ continue;
406
+ const dedupKey = ns.startsWith('p2p:') ? `p2p_pull:${ns}`
407
+ : ns.startsWith('group_event:') ? `group_event_pull:${ns}` : `group_pull:${ns}`;
408
+ if (client._gapFillDone.has(dedupKey) || this.onlineUnreadHintOwners?.has(ns)
409
+ || this.realtimeSyncing?.has(ns) || client._rpcPipeline?.hasPullActivity?.(ns, false))
410
+ continue;
411
+ const ack = this.syncState(ns).ack;
412
+ if (!demand.probe && !demand.hasMore && ack >= demand.throughSeq) {
413
+ this.backgroundPullDemands.delete(ns);
414
+ continue;
415
+ }
416
+ if (demand.retryAt > Date.now()) {
417
+ retryAt = retryAt === 0 ? demand.retryAt : Math.min(retryAt, demand.retryAt);
418
+ continue;
419
+ }
420
+ if (ns.startsWith('p2p:'))
421
+ client._safeAsync(this.fillP2pGap());
422
+ else if (ns.startsWith('group_event:'))
423
+ client._safeAsync(this.fillGroupEventGap(ns.slice('group_event:'.length)));
424
+ else if (ns.startsWith('group:'))
425
+ client._safeAsync(this.fillGroupGap(ns.slice('group:'.length)));
426
+ }
427
+ if (retryAt > 0) {
428
+ this.backgroundPullTimer = globalThis.setTimeout(() => {
429
+ this.backgroundPullTimer = null;
430
+ if (this.isInlineGenerationCurrent(generation))
431
+ this.requestBackgroundPullRecovery();
432
+ }, Math.max(0, retryAt - Date.now()));
433
+ }
434
+ });
329
435
  }
330
436
  onForegroundPullsDrained() {
437
+ this.requestBackgroundPullRecovery();
331
438
  const client = this.runtime.client;
332
439
  if (!client._aid || client._sessionOptions?.background_sync === false
333
440
  || client.state !== ConnectionState.READY || client._closing)
@@ -340,6 +447,7 @@ export class MessageDeliveryEngine {
340
447
  client._safeAsync(this.fillP2pGap());
341
448
  }
342
449
  onPullWorkSettled() {
450
+ this.requestBackgroundPullRecovery();
343
451
  const pipeline = this.runtime.client._rpcPipeline;
344
452
  if (pipeline?.hasAnyPullActivity?.() === true)
345
453
  return;
@@ -2228,7 +2336,14 @@ export class MessageDeliveryEngine {
2228
2336
  return;
2229
2337
  }
2230
2338
  }
2339
+ const generation = this.captureInlineGeneration();
2340
+ const dedupKey = `group_pull:${ns}`;
2341
+ this.recordBackgroundPullDemand(ns, positiveSafeSequenceHint(notification.seq), true);
2342
+ if (client._gapFillDone.has(dedupKey))
2343
+ return;
2344
+ client._gapFillDone.add(dedupKey);
2231
2345
  let rawCompleted = false;
2346
+ let failed = false;
2232
2347
  try {
2233
2348
  const key = client._rpcPipeline.pullGateKeyForCall('group.pull', {
2234
2349
  group_id: groupId,
@@ -2244,12 +2359,17 @@ export class MessageDeliveryEngine {
2244
2359
  limit: 50,
2245
2360
  _rpc_background: true,
2246
2361
  }, { background: true });
2362
+ if (!this.isInlineGenerationCurrent(generation))
2363
+ throw new Error('pull invalidated');
2247
2364
  rawCompleted = true;
2365
+ if (isJsonObject(result))
2366
+ this.observeBackgroundPullResponse(ns, afterSeq, result, 50);
2248
2367
  await this.commitPlainForwardPage(ns, afterSeq, 'group.pull', result, groupId);
2249
2368
  this.prunePushedSeqs(ns);
2250
2369
  }, true);
2251
2370
  }
2252
2371
  catch (exc) {
2372
+ failed = true;
2253
2373
  // 页发布或本地提交失败不能 ACK;提交后 ACK 失败则保留 pending。
2254
2374
  // 两种情况都不能再把原始通知当作第二条业务消息发布。
2255
2375
  client._clientLog.warn(`auto pull group message commit/ack failed:${String(exc)}`);
@@ -2257,6 +2377,12 @@ export class MessageDeliveryEngine {
2257
2377
  await client._publishAppEvent('group.message_created', notification, 'push');
2258
2378
  }
2259
2379
  }
2380
+ finally {
2381
+ if (this.isInlineGenerationCurrent(generation)) {
2382
+ client._gapFillDone.delete(dedupKey);
2383
+ this.finishBackgroundPull(ns, afterSeq, failed);
2384
+ }
2385
+ }
2260
2386
  }
2261
2387
  scheduleP2pGapRetry(ns, retryAttempt) {
2262
2388
  const client = this.runtime.client;
@@ -2280,6 +2406,7 @@ export class MessageDeliveryEngine {
2280
2406
  }
2281
2407
  async fillP2pGap(retryAttempt = 0) {
2282
2408
  const client = this.runtime.client;
2409
+ const generation = this.captureInlineGeneration();
2283
2410
  if (client._sessionOptions?.background_sync === false)
2284
2411
  return;
2285
2412
  if (client.state !== ConnectionState.READY || client._closing)
@@ -2291,9 +2418,11 @@ export class MessageDeliveryEngine {
2291
2418
  const dedupKey = `p2p_pull:${ns}`;
2292
2419
  if (client._gapFillDone.has(dedupKey))
2293
2420
  return;
2421
+ this.recordBackgroundPullDemand(ns, 0, true);
2294
2422
  client._gapFillDone.add(dedupKey);
2295
2423
  this.runtime.delivery.setGapFillActive(true);
2296
2424
  let filled = 0;
2425
+ let failed = false;
2297
2426
  try {
2298
2427
  const key = client._rpcPipeline.pullGateKeyForCall('message.pull', { after_seq: afterSeq, limit: 50 });
2299
2428
  const messages = await client._rpcPipeline.runPullSerialized(key, () => client._pullV2(afterSeq, 50, { gateLocked: true }), true);
@@ -2309,22 +2438,27 @@ export class MessageDeliveryEngine {
2309
2438
  client._clientLog.warn(`P2P message gap-fill V2 unavailable, used plain pull fallback:${String(formatDeliveryError(exc))}`);
2310
2439
  }
2311
2440
  catch (fallbackExc) {
2441
+ failed = true;
2312
2442
  client._clientLog.warn(`P2P message gap-fill plain fallback failed:${String(formatDeliveryError(fallbackExc))}`);
2313
- this.scheduleP2pGapRetry(ns, retryAttempt + 1);
2314
2443
  }
2315
2444
  }
2316
2445
  else {
2446
+ failed = true;
2317
2447
  client._clientLog.warn(`P2P message gap-fill failed:${String(formatDeliveryError(exc))}`);
2318
2448
  }
2319
2449
  }
2320
2450
  finally {
2321
- client._gapFillDone.delete(dedupKey);
2322
- this.runtime.delivery.setGapFillActive(false);
2323
- this.schedulePendingPullIfNeeded(ns, 'gap-fill-complete');
2451
+ if (this.isInlineGenerationCurrent(generation)) {
2452
+ client._gapFillDone.delete(dedupKey);
2453
+ this.runtime.delivery.setGapFillActive(false);
2454
+ this.schedulePendingPullIfNeeded(ns, 'gap-fill-complete');
2455
+ this.finishBackgroundPull(ns, afterSeq, failed);
2456
+ }
2324
2457
  }
2325
2458
  }
2326
2459
  async fillP2pGapWithPlainPull(ns, afterSeq) {
2327
2460
  const client = this.runtime.client;
2461
+ const generation = this.captureInlineGeneration();
2328
2462
  const params = {
2329
2463
  after_seq: afterSeq,
2330
2464
  limit: 50,
@@ -2333,11 +2467,16 @@ export class MessageDeliveryEngine {
2333
2467
  _rpc_background: true,
2334
2468
  };
2335
2469
  const raw = await client._rpcPipeline.rawCall('message.pull', params, { background: true });
2470
+ if (!this.isInlineGenerationCurrent(generation))
2471
+ throw new Error('pull invalidated');
2472
+ if (isJsonObject(raw))
2473
+ this.observeBackgroundPullResponse(ns, afterSeq, raw, 50);
2336
2474
  const page = await this.commitPlainForwardPage(ns, afterSeq, 'message.pull', raw);
2337
2475
  return page.publishedCount;
2338
2476
  }
2339
2477
  async fillGroupGap(groupId) {
2340
2478
  const client = this.runtime.client;
2479
+ const generation = this.captureInlineGeneration();
2341
2480
  if (client._sessionOptions?.background_sync === false)
2342
2481
  return;
2343
2482
  if (client.state !== ConnectionState.READY || client._closing)
@@ -2350,25 +2489,48 @@ export class MessageDeliveryEngine {
2350
2489
  const dedupKey = `group_pull:${ns}`;
2351
2490
  if (client._gapFillDone.has(dedupKey))
2352
2491
  return;
2492
+ this.recordBackgroundPullDemand(ns, 0, true);
2353
2493
  client._gapFillDone.add(dedupKey);
2354
2494
  this.runtime.delivery.setGapFillActive(true);
2355
2495
  let filled = 0;
2496
+ let failed = false;
2356
2497
  try {
2357
2498
  const key = client._rpcPipeline.pullGateKeyForCall('group.pull', { group_id: groupId, after_seq: afterSeq, limit: 50 });
2358
- const messages = await client._rpcPipeline.runPullSerialized(key, () => client._pullGroupV2(groupId, afterSeq, 50, { gateLocked: true }), true);
2499
+ const messages = await client._rpcPipeline.runPullSerialized(key, async () => {
2500
+ if (client._v2Session)
2501
+ return await client._pullGroupV2(groupId, afterSeq, 50, { gateLocked: true });
2502
+ const result = await client._rpcPipeline.rawCall('group.pull', {
2503
+ group_id: groupId,
2504
+ after_message_seq: afterSeq,
2505
+ device_id: client._deviceId,
2506
+ slot_id: client._slotId,
2507
+ limit: 50,
2508
+ _rpc_background: true,
2509
+ }, { background: true });
2510
+ if (!this.isInlineGenerationCurrent(generation))
2511
+ throw new Error('pull invalidated');
2512
+ if (isJsonObject(result))
2513
+ this.observeBackgroundPullResponse(ns, afterSeq, result, 50);
2514
+ await this.commitPlainForwardPage(ns, afterSeq, 'group.pull', result, groupId);
2515
+ return isJsonObject(result) && Array.isArray(result.messages) ? result.messages : [];
2516
+ }, true);
2359
2517
  filled = messages.length;
2360
2518
  this.prunePushedSeqs(ns);
2361
2519
  }
2362
2520
  catch (exc) {
2521
+ failed = true;
2363
2522
  client._clientLog.warn(`group message gap-fill failed:${String(exc)}`);
2364
2523
  }
2365
2524
  finally {
2366
- client._gapFillDone.delete(dedupKey);
2367
- this.runtime.delivery.setGapFillActive(false);
2368
- if (filled > 0 && client._seqTracker.getContiguousSeq(ns) > afterSeq) {
2369
- client._safeAsync(this.fillGroupGap(groupId));
2525
+ if (this.isInlineGenerationCurrent(generation)) {
2526
+ client._gapFillDone.delete(dedupKey);
2527
+ this.runtime.delivery.setGapFillActive(false);
2528
+ if (filled > 0 && client._seqTracker.getContiguousSeq(ns) > afterSeq) {
2529
+ this.recordBackgroundPullDemand(ns, 0, true);
2530
+ }
2531
+ this.schedulePendingPullIfNeeded(ns, 'gap-fill-complete');
2532
+ this.finishBackgroundPull(ns, afterSeq, failed);
2370
2533
  }
2371
- this.schedulePendingPullIfNeeded(ns, 'gap-fill-complete');
2372
2534
  }
2373
2535
  }
2374
2536
  async fillGroupEventGap(groupId, options) {
@@ -2390,6 +2552,7 @@ export class MessageDeliveryEngine {
2390
2552
  const dedupKey = `group_event_pull:${ns}`;
2391
2553
  if (client._gapFillDone.has(dedupKey))
2392
2554
  return;
2555
+ this.recordBackgroundPullDemand(ns, 0, true);
2393
2556
  client._gapFillDone.add(dedupKey);
2394
2557
  this.runtime.delivery.setGapFillActive(true);
2395
2558
  let continuationAfterSeq = 0;
@@ -2409,6 +2572,10 @@ export class MessageDeliveryEngine {
2409
2572
  _rpc_background: true,
2410
2573
  };
2411
2574
  const result = await client.call('group.pull_events', request);
2575
+ if (!this.isInlineGenerationCurrent(generation))
2576
+ throw new Error('pull invalidated');
2577
+ if (isJsonObject(result))
2578
+ this.observeBackgroundPullResponse(ns, nextAfterSeq, result, 50);
2412
2579
  if (!isJsonObject(result))
2413
2580
  return;
2414
2581
  const events = result.events;
@@ -2455,8 +2622,10 @@ export class MessageDeliveryEngine {
2455
2622
  await this.drainOrderedMessages(ns, undefined, false, false, undefined, 'pull', deliveryThrough);
2456
2623
  if (!this.isInlineGenerationCurrent(generation))
2457
2624
  throw new Error('pull invalidated');
2458
- if (hasDissolvedEvent)
2625
+ if (hasDissolvedEvent) {
2626
+ this.backgroundPullDemands.delete(ns);
2459
2627
  break;
2628
+ }
2460
2629
  if (contiguousSeq === undefined)
2461
2630
  client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq);
2462
2631
  else
@@ -2504,15 +2673,12 @@ export class MessageDeliveryEngine {
2504
2673
  client._clientLog.warn(`group event gap-fill failed:${String(exc)}`);
2505
2674
  }
2506
2675
  finally {
2507
- client._gapFillDone.delete(dedupKey);
2508
- this.runtime.delivery.setGapFillActive(false);
2509
- if (!client._rpcPipeline)
2510
- this.syncStopped(failed ? 'aborted' : 'pull_drained');
2511
- if (singlePage && continuationAfterSeq > afterSeq) {
2512
- this.enqueueOnlineUnreadEventHint({
2513
- group_id: groupId,
2514
- _online_hint_after_event_seq: continuationAfterSeq,
2515
- });
2676
+ if (this.isInlineGenerationCurrent(generation)) {
2677
+ client._gapFillDone.delete(dedupKey);
2678
+ this.runtime.delivery.setGapFillActive(false);
2679
+ if (!client._rpcPipeline)
2680
+ this.syncStopped(failed ? 'aborted' : 'pull_drained');
2681
+ this.finishBackgroundPull(ns, afterSeq, failed);
2516
2682
  }
2517
2683
  }
2518
2684
  }
@@ -2568,6 +2734,9 @@ export class MessageDeliveryEngine {
2568
2734
  if (!groupId)
2569
2735
  return;
2570
2736
  const ns = `group:${groupId}`;
2737
+ const generation = this.captureInlineGeneration();
2738
+ const afterSeq = this.syncState(ns).ack;
2739
+ this.recordBackgroundPullDemand(ns, positiveSafeSequenceHint(data.seq), true);
2571
2740
  const targets = this.onlineUnreadHintTargets ?? new Map();
2572
2741
  const owners = this.onlineUnreadHintOwners ?? new Set();
2573
2742
  this.onlineUnreadHintTargets = targets;
@@ -2577,11 +2746,16 @@ export class MessageDeliveryEngine {
2577
2746
  return;
2578
2747
  owners.add(ns);
2579
2748
  client._safeAsync((async () => {
2749
+ let failed = false;
2750
+ let attemptAfterSeq = afterSeq;
2580
2751
  try {
2581
2752
  while (true) {
2582
2753
  const throughSeq = targets.get(ns) ?? 0;
2583
2754
  const ackBefore = this.syncState(ns).ack;
2755
+ attemptAfterSeq = ackBefore;
2584
2756
  await this.runGroupForwardRecovery(groupId, ns, 50, true, throughSeq);
2757
+ if (!this.isInlineGenerationCurrent(generation))
2758
+ return;
2585
2759
  const ackAfter = this.syncState(ns).ack;
2586
2760
  const target = targets.get(ns) ?? 0;
2587
2761
  if (ackAfter >= target || ackAfter <= ackBefore)
@@ -2589,6 +2763,7 @@ export class MessageDeliveryEngine {
2589
2763
  }
2590
2764
  }
2591
2765
  catch (exc) {
2766
+ failed = true;
2592
2767
  client._clientLog?.debug(`online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
2593
2768
  }
2594
2769
  finally {
@@ -2596,6 +2771,8 @@ export class MessageDeliveryEngine {
2596
2771
  targets.delete(ns);
2597
2772
  if (this.onlineUnreadHintOwners === owners)
2598
2773
  owners.delete(ns);
2774
+ if (this.isInlineGenerationCurrent(generation))
2775
+ this.finishBackgroundPull(ns, attemptAfterSeq, failed);
2599
2776
  }
2600
2777
  })());
2601
2778
  }
@@ -2604,6 +2781,7 @@ export class MessageDeliveryEngine {
2604
2781
  const groupId = String(data.group_id ?? '').trim();
2605
2782
  if (!groupId || client._sessionOptions?.background_sync === false)
2606
2783
  return;
2784
+ this.recordBackgroundPullDemand(`group_event:${groupId}`, positiveSafeSequenceHint(data.event_seq), true);
2607
2785
  const rawAfter = Number(data._online_hint_after_event_seq ?? 0);
2608
2786
  const afterSeq = Number.isSafeInteger(rawAfter) && rawAfter > 0 ? rawAfter : 0;
2609
2787
  client._safeAsync(this.fillGroupEventGap(groupId, {
@@ -3378,22 +3556,30 @@ export class MessageDeliveryEngine {
3378
3556
  }
3379
3557
  return;
3380
3558
  }
3559
+ if (client._sessionOptions?.background_sync !== false)
3560
+ this.recordBackgroundPullDemand(ns, seq);
3381
3561
  const pullActive = client._rpcPipeline?.hasPullActivity?.(ns) === true;
3382
3562
  if (pullActive || !this.beginRealtimeSync(ns)) {
3383
3563
  this.recordPendingPull(ns, seq);
3384
3564
  client._clientLog?.debug(`V2 group push queued behind in-flight realtime sync: ns=${ns} seq=${seq}`);
3385
3565
  return;
3386
3566
  }
3567
+ const generation = this.captureInlineGeneration();
3568
+ let failed = false;
3387
3569
  try {
3388
3570
  // namespace 串行区必须覆盖 Tail 与其后至多一次 Forward 的完整周期。
3389
3571
  await this.runGroupRealtimeSync(groupId, ns, seq, 50, false);
3390
3572
  }
3391
3573
  catch (exc) {
3574
+ failed = true;
3392
3575
  client._clientLog.warn(`V2 group push auto-sync failed: group=${groupId} err=${formatDeliveryError(exc)}`);
3393
3576
  }
3394
3577
  finally {
3395
- this.endRealtimeSync(ns);
3396
- this.schedulePendingPullIfNeeded(ns, 'push-auto-pull-complete');
3578
+ if (this.isInlineGenerationCurrent(generation)) {
3579
+ this.endRealtimeSync(ns);
3580
+ this.schedulePendingPullIfNeeded(ns, 'push-auto-pull-complete');
3581
+ this.finishBackgroundPull(ns, before.ack, failed);
3582
+ }
3397
3583
  }
3398
3584
  }
3399
3585
  async onV2PushNotification(data) {
@@ -3466,31 +3652,60 @@ export class MessageDeliveryEngine {
3466
3652
  }
3467
3653
  return;
3468
3654
  }
3655
+ if (client._sessionOptions?.background_sync !== false)
3656
+ this.recordBackgroundPullDemand(ns, pushSeq);
3469
3657
  const pullActive = client._rpcPipeline?.hasPullActivity?.(ns) === true;
3470
3658
  if (pullActive || !this.beginRealtimeSync(ns)) {
3471
3659
  this.recordPendingPull(ns, pushSeq);
3472
3660
  client._clientLog?.debug(`V2 P2P push queued behind in-flight realtime sync: ns=${ns} seq=${pushSeq}`);
3473
3661
  return;
3474
3662
  }
3663
+ const generation = this.captureInlineGeneration();
3664
+ let failed = false;
3475
3665
  try {
3476
3666
  await this.runP2PRealtimeSync(ns, pushSeq, 50, false);
3477
3667
  }
3478
3668
  catch (exc) {
3669
+ failed = true;
3479
3670
  client._clientLog.warn(`V2 P2P push auto-sync failed: ${formatDeliveryError(exc)}`);
3480
3671
  }
3481
3672
  finally {
3482
- this.endRealtimeSync(ns);
3483
- this.schedulePendingPullIfNeeded(ns, 'push-auto-pull-complete');
3673
+ if (this.isInlineGenerationCurrent(generation)) {
3674
+ this.endRealtimeSync(ns);
3675
+ this.schedulePendingPullIfNeeded(ns, 'push-auto-pull-complete');
3676
+ this.finishBackgroundPull(ns, before.ack, failed);
3677
+ }
3484
3678
  }
3485
3679
  }
3486
3680
  enqueueOnlineUnreadP2pHint(data) {
3487
3681
  const client = this.runtime.client;
3488
- if (!client._aid || !client._v2Session)
3682
+ if (!client._aid || !client._v2Session || client._sessionOptions?.background_sync === false)
3489
3683
  return;
3490
3684
  const ns = `p2p:${client._aid}`;
3491
- client._safeAsync(this.runP2PForwardRecovery(ns, 50, true).catch((exc) => {
3492
- client._clientLog?.debug(`P2P online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
3493
- }));
3685
+ const generation = this.captureInlineGeneration();
3686
+ const afterSeq = this.syncState(ns).ack;
3687
+ this.recordBackgroundPullDemand(ns, positiveSafeSequenceHint(data.seq), true);
3688
+ const owners = this.onlineUnreadHintOwners ?? new Set();
3689
+ this.onlineUnreadHintOwners = owners;
3690
+ if (owners.has(ns))
3691
+ return;
3692
+ owners.add(ns);
3693
+ client._safeAsync((async () => {
3694
+ let failed = false;
3695
+ try {
3696
+ await this.runP2PForwardRecovery(ns, 50, true);
3697
+ }
3698
+ catch (exc) {
3699
+ failed = true;
3700
+ client._clientLog?.debug(`P2P online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
3701
+ }
3702
+ finally {
3703
+ if (this.onlineUnreadHintOwners === owners)
3704
+ owners.delete(ns);
3705
+ if (this.isInlineGenerationCurrent(generation))
3706
+ this.finishBackgroundPull(ns, afterSeq, failed);
3707
+ }
3708
+ })());
3494
3709
  }
3495
3710
  async restoreSeqTrackerState() {
3496
3711
  const client = this.runtime.client;