@agentunion/fastaun-browser 0.5.3 → 0.5.6

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 (151) hide show
  1. package/CHANGELOG.md +130 -56
  2. package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/216/245/345/205/245/344/270/216/346/274/224/347/244/272/346/214/207/345/215/227.md +290 -0
  3. package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/234/215/345/212/241/346/236/266/346/236/204/344/270/216/350/257/246/347/273/206/345/256/236/347/216/260/350/256/241/345/210/222-codex.md +994 -0
  4. package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/234/215/345/212/241/350/277/220/347/273/264/344/270/216/345/217/221/345/270/203/346/214/207/345/215/227.md +144 -0
  5. package/_packed_docs/CHANGELOG.md +130 -56
  6. package/_packed_docs/INDEX.md +127 -69
  7. package/_packed_docs/KITE_DOCS_GUIDE.md +63 -29
  8. 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 +366 -0
  9. 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 +227 -0
  10. 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 +1004 -0
  11. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +999 -0
  12. package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +6 -4
  13. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +58 -20
  14. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +219 -247
  15. package/_packed_docs/protocol/12-Stream-/345/255/220/345/215/217/350/256/256.md +14 -14
  16. package/_packed_docs/protocol/13-Agent/350/241/214/344/270/272/350/247/204/350/214/203.md +3 -3
  17. package/_packed_docs/protocol/15-/347/246/273/347/272/277/346/216/250/351/200/201/351/200/232/347/237/245/345/215/217/350/256/256.md +165 -421
  18. package/_packed_docs/protocol/README.md +1 -0
  19. package/_packed_docs/protocol/aun-docs-guide.md +7 -4
  20. package/_packed_docs/protocol/index.md +25 -19
  21. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +62 -17
  22. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +22 -0
  23. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +67 -78
  24. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +6 -2
  25. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +87 -41
  26. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +18 -5
  27. package/_packed_docs/sdk/09-group-rpc-manual.md +307 -267
  28. package/_packed_docs/sdk/09-message-rpc-manual.md +142 -103
  29. package/_packed_docs/sdk/09-payload-reference.md +1 -1
  30. package/_packed_docs/sdk/09-stream-rpc-manual.md +8 -8
  31. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +26 -17
  32. package/_packed_docs/sdk/INDEX.md +42 -34
  33. package/_packed_docs/sdk/README.md +7 -6
  34. package/_packed_docs//346/266/210/346/201/257/345/220/214/346/255/245/344/270/216/345/216/206/345/217/262/346/213/211/345/217/226/344/273/243/347/240/201/345/256/241/346/237/245/351/227/256/351/242/230/346/270/205/345/215/225.md +229 -0
  35. package/_packed_docs//346/266/210/346/201/257/345/220/214/346/255/245/344/270/216/345/216/206/345/217/262/346/213/211/345/217/226/346/224/271/351/200/240/346/226/271/346/241/210.md +750 -0
  36. package/dist/agent-md.d.ts +4 -5
  37. package/dist/agent-md.d.ts.map +1 -1
  38. package/dist/agent-md.js +66 -43
  39. package/dist/agent-md.js.map +1 -1
  40. package/dist/aid-store.d.ts +2 -6
  41. package/dist/aid-store.d.ts.map +1 -1
  42. package/dist/aid-store.js +20 -14
  43. package/dist/aid-store.js.map +1 -1
  44. package/dist/auth.d.ts.map +1 -1
  45. package/dist/auth.js +30 -11
  46. package/dist/auth.js.map +1 -1
  47. package/dist/bundle.js +3399 -1069
  48. package/dist/client/delivery.d.ts +75 -8
  49. package/dist/client/delivery.d.ts.map +1 -1
  50. package/dist/client/delivery.js +1419 -245
  51. package/dist/client/delivery.js.map +1 -1
  52. package/dist/client/group-state.d.ts.map +1 -1
  53. package/dist/client/group-state.js +27 -24
  54. package/dist/client/group-state.js.map +1 -1
  55. package/dist/client/lifecycle.d.ts.map +1 -1
  56. package/dist/client/lifecycle.js +48 -4
  57. package/dist/client/lifecycle.js.map +1 -1
  58. package/dist/client/mention-mode.d.ts +7 -0
  59. package/dist/client/mention-mode.d.ts.map +1 -0
  60. package/dist/client/mention-mode.js +184 -0
  61. package/dist/client/mention-mode.js.map +1 -0
  62. package/dist/client/peers.d.ts +1 -1
  63. package/dist/client/peers.d.ts.map +1 -1
  64. package/dist/client/peers.js +26 -3
  65. package/dist/client/peers.js.map +1 -1
  66. package/dist/client/rpc-pipeline.d.ts +22 -1
  67. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  68. package/dist/client/rpc-pipeline.js +309 -74
  69. package/dist/client/rpc-pipeline.js.map +1 -1
  70. package/dist/client/v2-e2ee.d.ts +25 -2
  71. package/dist/client/v2-e2ee.d.ts.map +1 -1
  72. package/dist/client/v2-e2ee.js +606 -97
  73. package/dist/client/v2-e2ee.js.map +1 -1
  74. package/dist/client.d.ts +24 -5
  75. package/dist/client.d.ts.map +1 -1
  76. package/dist/client.js +404 -215
  77. package/dist/client.js.map +1 -1
  78. package/dist/errors.d.ts.map +1 -1
  79. package/dist/errors.js +4 -1
  80. package/dist/errors.js.map +1 -1
  81. package/dist/facades.d.ts +6 -0
  82. package/dist/facades.d.ts.map +1 -1
  83. package/dist/facades.js +199 -104
  84. package/dist/facades.js.map +1 -1
  85. package/dist/group-index.d.ts +6 -1
  86. package/dist/group-index.d.ts.map +1 -1
  87. package/dist/group-index.js +44 -25
  88. package/dist/group-index.js.map +1 -1
  89. package/dist/index.d.ts +1 -1
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +1 -1
  92. package/dist/index.js.map +1 -1
  93. package/dist/keystore/index.d.ts +10 -5
  94. package/dist/keystore/index.d.ts.map +1 -1
  95. package/dist/keystore/indexeddb-identity-store.d.ts +0 -12
  96. package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
  97. package/dist/keystore/indexeddb-identity-store.js +0 -60
  98. package/dist/keystore/indexeddb-identity-store.js.map +1 -1
  99. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  100. package/dist/keystore/indexeddb-shared.js +9 -5
  101. package/dist/keystore/indexeddb-shared.js.map +1 -1
  102. package/dist/keystore/indexeddb-token-store.d.ts +3 -1
  103. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  104. package/dist/keystore/indexeddb-token-store.js +47 -2
  105. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  106. package/dist/register-flow.d.ts.map +1 -1
  107. package/dist/register-flow.js +28 -3
  108. package/dist/register-flow.js.map +1 -1
  109. package/dist/seq-tracker.d.ts +28 -8
  110. package/dist/seq-tracker.d.ts.map +1 -1
  111. package/dist/seq-tracker.js +218 -61
  112. package/dist/seq-tracker.js.map +1 -1
  113. package/dist/storage/vfs.d.ts +1 -0
  114. package/dist/storage/vfs.d.ts.map +1 -1
  115. package/dist/storage/vfs.js +26 -2
  116. package/dist/storage/vfs.js.map +1 -1
  117. package/dist/tools/cross-sdk-agent.js +376 -10
  118. package/dist/tools/cross-sdk-agent.js.map +1 -1
  119. package/dist/transport.d.ts +12 -0
  120. package/dist/transport.d.ts.map +1 -1
  121. package/dist/transport.js +218 -101
  122. package/dist/transport.js.map +1 -1
  123. package/dist/v2/session/session.d.ts.map +1 -1
  124. package/dist/v2/session/session.js +3 -4
  125. package/dist/v2/session/session.js.map +1 -1
  126. package/dist/v2/state/commitment.d.ts.map +1 -1
  127. package/dist/v2/state/commitment.js +1 -2
  128. package/dist/v2/state/commitment.js.map +1 -1
  129. package/dist/version.d.ts +1 -1
  130. package/dist/version.js +1 -1
  131. package/package.json +6 -5
  132. package/dist/group-resources.d.ts +0 -98
  133. package/dist/group-resources.d.ts.map +0 -1
  134. package/dist/group-resources.js +0 -635
  135. package/dist/group-resources.js.map +0 -1
  136. package/dist/keystore/indexeddb.d.ts +0 -179
  137. package/dist/keystore/indexeddb.d.ts.map +0 -1
  138. package/dist/keystore/indexeddb.js +0 -2031
  139. package/dist/keystore/indexeddb.js.map +0 -1
  140. package/dist/namespaces/auth.d.ts +0 -98
  141. package/dist/namespaces/auth.d.ts.map +0 -1
  142. package/dist/namespaces/auth.js +0 -992
  143. package/dist/namespaces/auth.js.map +0 -1
  144. package/dist/namespaces/custody.d.ts +0 -51
  145. package/dist/namespaces/custody.d.ts.map +0 -1
  146. package/dist/namespaces/custody.js +0 -302
  147. package/dist/namespaces/custody.js.map +0 -1
  148. package/dist/namespaces/meta.d.ts +0 -109
  149. package/dist/namespaces/meta.d.ts.map +0 -1
  150. package/dist/namespaces/meta.js +0 -549
  151. package/dist/namespaces/meta.js.map +0 -1
@@ -1,9 +1,9 @@
1
1
  import { normalizeSlotId, slotIsolationKey } from '../config.js';
2
- import { ClientSignatureError, ConnectionError, PermissionError, StateError, ValidationError } from '../errors.js';
3
- import { normalizeGroupId } from '../group-id.js';
2
+ import { ClientSignatureError, ConnectionError, PermissionError, ValidationError } from '../errors.js';
4
3
  import { validateAIDFormat, validateGroupIDFormat } from '../validators.js';
5
4
  import { p1363ToDer, pemToArrayBuffer, uint8ToBase64 } from '../crypto.js';
6
5
  import { ConnectionState, isJsonObject, } from '../types.js';
6
+ import { normalizeGroupMentionMode, normalizeGroupSettingsResponse } from './mention-mode.js';
7
7
  const INTERNAL_ONLY_METHODS = new Set([
8
8
  'auth.login1',
9
9
  'auth.aid_login1',
@@ -22,11 +22,11 @@ const PROTECTED_HEADERS_METHODS = new Set([
22
22
  const SIGNED_METHODS = new Set([
23
23
  'message.send',
24
24
  'message.v2.put_peer_pk', 'message.v2.bootstrap',
25
- 'message.v2.group_bootstrap', 'message.v2.pull',
25
+ 'message.v2.group_bootstrap', 'message.v2.pull', 'message.history',
26
26
  'message.v2.ack',
27
27
  'group.send',
28
28
  'group.v2.put_group_pk', 'group.v2.bootstrap',
29
- 'group.v2.send', 'group.v2.pull', 'group.v2.ack',
29
+ 'group.v2.send', 'group.v2.pull', 'group.history', 'group.v2.ack',
30
30
  'group.v2.propose_state', 'group.v2.confirm_state',
31
31
  'group.v2.get_proposal',
32
32
  'group.kick', 'group.add_member',
@@ -167,6 +167,8 @@ function isTransientV2PullFallbackError(error) {
167
167
  }
168
168
  export class RpcPipeline {
169
169
  runtime;
170
+ pullGateStates = new Map();
171
+ bypassedRealtimePulls = new Map();
170
172
  constructor(runtime) {
171
173
  this.runtime = runtime;
172
174
  }
@@ -217,7 +219,8 @@ export class RpcPipeline {
217
219
  if (encrypt) {
218
220
  await client._ensureV2SessionReady('group.send', 'V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)');
219
221
  client._clientLog.debug('call route: group.send -> V2 encrypted send');
220
- return await client._sendGroupV2(String(p.group_aid ?? p.group_id ?? ''), p.payload ?? {}, {
222
+ return await client._sendGroupV2(String(p.group_id ?? p.group_aid ?? ''), p.payload ?? {}, {
223
+ groupAid: String(p.group_aid ?? '').trim() || undefined,
221
224
  messageId: String(p.message_id ?? '') || undefined,
222
225
  timestamp: p.timestamp,
223
226
  protectedHeaders: client._protectedHeadersFromParams(p),
@@ -246,9 +249,11 @@ export class RpcPipeline {
246
249
  }
247
250
  const pullGateKey = this.pullGateKeyForCall(method, p);
248
251
  if (pullGateKey) {
249
- return await this.runPullSerialized(pullGateKey, async () => {
250
- return await runWithRpcPriority(() => this.callImplInner(method, p, skipSendResultEnvelope));
251
- });
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));
252
257
  }
253
258
  return await runWithRpcPriority(() => this.callImplInner(method, p, skipSendResultEnvelope));
254
259
  }
@@ -258,6 +263,8 @@ export class RpcPipeline {
258
263
  try {
259
264
  await client._ensureV2SessionReady('message.pull');
260
265
  client._clientLog.debug(`call route: ${method} -> V2 pull`);
266
+ if (p.window_mode === 'tail')
267
+ return await client._v2E2EE.pullV2TailInternal(p);
261
268
  const messages = await client._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, { force: p.force === true });
262
269
  return { messages };
263
270
  }
@@ -268,29 +275,45 @@ export class RpcPipeline {
268
275
  client._clientLog.warn(`message.pull V2 route unavailable, fallback to plain pull: ${exc instanceof Error ? exc.message : String(exc)}`);
269
276
  }
270
277
  }
278
+ if (method === 'message.history') {
279
+ await client._ensureV2SessionReady('message.history');
280
+ return await client._v2E2EE.historyV2Internal(p);
281
+ }
271
282
  if (method === 'message.v2.ack' || (method === 'message.ack' && hasMatchingV2Session(client))) {
272
283
  await client._ensureV2SessionReady('message.ack');
273
284
  client._clientLog.debug(`call route: ${method} -> V2 ack`);
274
- return await client._ackV2(Number(p.seq ?? p.up_to_seq ?? 0) || undefined);
285
+ return await client._ackV2((p.seq ?? p.up_to_seq));
275
286
  }
276
287
  if ((method === 'group.v2.pull' || method === 'group.pull') && p.group_id && (method === 'group.v2.pull' || hasMatchingV2Session(client))) {
277
288
  await client._ensureV2SessionReady('group.pull');
278
289
  client._clientLog.debug(`call route: ${method} -> V2 pull`);
290
+ if (p.window_mode === 'tail')
291
+ return await client._v2E2EE.pullGroupV2TailInternal(p);
279
292
  const hasExplicitAfterSeq = 'after_seq' in p || 'after_message_seq' in p;
280
293
  const cursorParams = client._explicitGroupCursorParams(p);
281
294
  const ownsCursor = Object.keys(cursorParams).length === 0 || client._groupCursorTargetsCurrentInstance(cursorParams);
282
295
  const pullOpts = {};
283
296
  if (hasExplicitAfterSeq)
284
297
  pullOpts.explicitAfterSeq = true;
298
+ if (p.force === true)
299
+ pullOpts.force = true;
285
300
  if (Object.keys(cursorParams).length > 0)
286
301
  pullOpts.cursorParams = cursorParams;
287
302
  if (!ownsCursor)
288
303
  pullOpts.ownsCursor = false;
289
304
  if (String(p.group_id ?? '').trim())
290
305
  pullOpts.wireGroupId = String(p.group_id).trim();
291
- const messages = await client._pullGroupV2(String(p.group_aid ?? p.group_id), Number(p.after_seq ?? p.after_message_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, Object.keys(pullOpts).length > 0 ? pullOpts : undefined);
306
+ if (String(p.group_aid ?? '').trim())
307
+ pullOpts.groupAid = String(p.group_aid).trim();
308
+ const messages = await client._pullGroupV2(String(p.group_id ?? p.group_aid), Number(p.after_seq ?? p.after_message_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, Object.keys(pullOpts).length > 0 ? pullOpts : undefined);
292
309
  return { messages };
293
310
  }
311
+ if (method === 'group.history') {
312
+ if (!String(p.group_id ?? p.group_aid ?? '').trim())
313
+ throw new ValidationError('group.history requires group_id');
314
+ await client._ensureV2SessionReady('group.history');
315
+ return await client._v2E2EE.historyGroupV2Internal(p);
316
+ }
294
317
  if ((method === 'group.v2.ack' || method === 'group.ack_messages') && p.group_id && (method === 'group.v2.ack' || hasMatchingV2Session(client))) {
295
318
  await client._ensureV2SessionReady('group.ack_messages');
296
319
  client._clientLog.debug(`call route: ${method} -> V2 ack`);
@@ -299,7 +322,7 @@ export class RpcPipeline {
299
322
  if (!ownsCursor) {
300
323
  return await client._rawGroupAckMessages(p);
301
324
  }
302
- return await client._ackGroupV2(String(p.group_aid ?? p.group_id), Number(p.seq ?? p.msg_seq ?? p.up_to_seq ?? 0) || undefined);
325
+ return await client._ackGroupV2((p.group_id ?? p.group_aid), (p.seq ?? p.msg_seq ?? p.up_to_seq), p.group_aid);
303
326
  }
304
327
  await this.applyClientSignature(method, p);
305
328
  const callTimeout = NON_IDEMPOTENT_METHODS.has(method) ? NON_IDEMPOTENT_TIMEOUT : undefined;
@@ -307,9 +330,10 @@ export class RpcPipeline {
307
330
  let result = callTimeout
308
331
  ? await client._transport.call(method, p, callTimeout, undefined, rpcBackground)
309
332
  : await client._transport.call(method, p, undefined, undefined, rpcBackground);
333
+ const sendResultModeSource = result;
310
334
  result = await this.postprocessResult(method, p, result);
311
335
  if (!skipSendResultEnvelope) {
312
- result = client._delivery.attachSendResultEnvelope(method, p, result, Boolean(p.encrypted));
336
+ result = client._delivery.attachSendResultEnvelope(method, p, result, Boolean(p.encrypted), sendResultModeSource);
313
337
  }
314
338
  return result;
315
339
  }
@@ -501,22 +525,166 @@ export class RpcPipeline {
501
525
  // ── Pull Gate(序列化同一 key 的并发 pull)──────────────────
502
526
  pullGateKeyForCall(method, params) {
503
527
  const client = this.runtime.client;
504
- if (method === 'message.pull' || method === 'message.v2.pull') {
505
- return client._aid ? `p2p:${client._aid}` : '';
506
- }
507
- if (method === 'group.pull' || method === 'group.v2.pull') {
508
- const gid = String(params.group_aid ?? params.group_id ?? '').trim();
509
- return gid ? `group:${gid}` : '';
528
+ if (method === 'message.pull' || method === 'message.v2.pull' || method === 'message.history') {
529
+ if (!client._aid)
530
+ return '';
531
+ const mode = method === 'message.history' ? 'history' : params.window_mode === 'tail' ? 'tail' : 'forward';
532
+ const cursor = mode === 'history'
533
+ ? `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)}`;
536
+ }
537
+ if (method === 'group.pull' || method === 'group.v2.pull' || method === 'group.history') {
538
+ const gid = String(params.group_id ?? params.group_aid ?? '').trim();
539
+ if (!gid)
540
+ return '';
541
+ const mode = method === 'group.history' ? 'history' : params.window_mode === 'tail' ? 'tail' : 'forward';
542
+ const cursor = mode === 'history'
543
+ ? `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)}`;
510
546
  }
511
547
  if (method === 'group.pull_events') {
512
- const gid = String(params.group_aid ?? params.group_id ?? '').trim();
513
- return gid ? `group_event:${gid}` : '';
548
+ const gid = String(params.group_id ?? params.group_aid ?? '').trim();
549
+ return gid
550
+ ? `group_event:${gid}|forward|after=${String(params.after_event_seq ?? 0)}|limit=${String(params.limit)}`
551
+ : '';
552
+ }
553
+ return '';
554
+ }
555
+ pullGateBackgroundForCall(method, params) {
556
+ if (method === 'message.history' || method === 'group.history')
557
+ return false;
558
+ if (['message.pull', 'message.v2.pull', 'group.pull', 'group.v2.pull'].includes(method)) {
559
+ return params.window_mode !== 'tail';
560
+ }
561
+ return true;
562
+ }
563
+ 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}`;
571
+ }
572
+ pullGateState(key) {
573
+ const name = this.pullGateName(key);
574
+ let state = this.pullGateStates.get(name);
575
+ if (!state) {
576
+ state = { active: null, foreground: [], background: [], byKey: new Map() };
577
+ this.pullGateStates.set(name, state);
578
+ }
579
+ return state;
580
+ }
581
+ realtimePullNamespace(key) {
582
+ for (const marker of ['|tail|', '|forward|']) {
583
+ const index = key.indexOf(marker);
584
+ if (index > 0)
585
+ return key.slice(0, index);
514
586
  }
515
587
  return '';
516
588
  }
589
+ pullScopeKey(key) {
590
+ const index = key.indexOf('|');
591
+ return index < 0 ? key : key.slice(0, index);
592
+ }
593
+ isPullResponseProcessing(key) {
594
+ const responseKeys = this.runtime.client._pullResponseKeys;
595
+ return Boolean(key) && (responseKeys?.get(this.pullScopeKey(key)) ?? 0) > 0;
596
+ }
597
+ emptyPullResultForCall(method) {
598
+ if (method === 'group.pull_events')
599
+ return { events: [], count: 0 };
600
+ return { messages: [], count: 0 };
601
+ }
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
+ hasPendingRealtimePull(ns) {
622
+ const normalized = String(ns ?? '').trim();
623
+ if (!normalized || (!normalized.startsWith('p2p:') && !normalized.startsWith('group:')))
624
+ return false;
625
+ if (this.isPullResponseProcessing(normalized))
626
+ return true;
627
+ if ((this.bypassedRealtimePulls.get(normalized) ?? 0) > 0)
628
+ return true;
629
+ const state = this.pullGateStates.get(this.pullGateName(normalized));
630
+ if (!state)
631
+ return false;
632
+ const matches = (job) => {
633
+ if (!job)
634
+ return false;
635
+ return this.realtimePullNamespace(job.key) === normalized;
636
+ };
637
+ return matches(state.active)
638
+ || state.foreground.some(matches)
639
+ || state.background.some(matches);
640
+ }
641
+ async tryRunInlineRealtime(ns, seq, operation) {
642
+ const normalized = String(ns ?? '').trim();
643
+ if ((!normalized.startsWith('p2p:') && !normalized.startsWith('group:'))
644
+ || !Number.isSafeInteger(seq) || seq <= 0 || typeof operation !== 'function') {
645
+ return { acquired: false };
646
+ }
647
+ const client = this.runtime.client;
648
+ if (this.isPullResponseProcessing(normalized)
649
+ || (this.bypassedRealtimePulls.get(normalized) ?? 0) > 0) {
650
+ return { acquired: false };
651
+ }
652
+ for (const [key, gate] of client._pullGates?.entries?.() ?? []) {
653
+ if (gate?.inflight && this.pullScopeKey(key) === normalized)
654
+ return { acquired: false };
655
+ }
656
+ const key = `${normalized}|inline|seq=${seq}`;
657
+ const state = this.pullGateState(key);
658
+ if (state.active !== null || state.foreground.length > 0 || state.background.length > 0) {
659
+ return { acquired: false };
660
+ }
661
+ const guard = {
662
+ key,
663
+ background: false,
664
+ inline: true,
665
+ operation,
666
+ promise: Promise.resolve(undefined),
667
+ resolve: () => { },
668
+ reject: () => { },
669
+ timer: null,
670
+ };
671
+ state.active = guard;
672
+ try {
673
+ return { acquired: true, value: await operation() };
674
+ }
675
+ finally {
676
+ if (state.active === guard)
677
+ state.active = null;
678
+ this.drainPullGate(state);
679
+ this.armQueuedPullTimers(state);
680
+ }
681
+ }
517
682
  tryAcquirePullGate(key) {
518
683
  if (!key)
519
684
  return 0;
685
+ const serialized = this.pullGateStates.get(this.pullGateName(key));
686
+ if (serialized?.active?.inline === true)
687
+ return null;
520
688
  const client = this.runtime.client;
521
689
  const now = Date.now();
522
690
  const gate = client._pullGates.get(key) ?? { inflight: false, startedAt: 0, token: 0 };
@@ -541,23 +709,95 @@ export class RpcPipeline {
541
709
  gate.inflight = false;
542
710
  gate.startedAt = 0;
543
711
  }
544
- async runPullSerialized(key, operation) {
545
- let token = this.tryAcquirePullGate(key);
546
- if (token === null) {
547
- const deadline = Date.now() + PULL_GATE_STALE_MS + 100;
548
- while (token === null && Date.now() <= deadline) {
549
- await this._sleep(25);
550
- token = this.tryAcquirePullGate(key);
551
- }
552
- if (token === null) {
553
- throw new StateError(`pull already in-flight for ${key}`);
554
- }
555
- }
556
- try {
557
- return await operation();
712
+ 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 [];
558
716
  }
559
- finally {
560
- this.releasePullGate(key, token);
717
+ if (!key)
718
+ return await this.executePullOperation(operation, background);
719
+ 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);
722
+ if (existing) {
723
+ if (state.active?.inline === true)
724
+ return await existing.promise;
725
+ return await this.waitForCoalesced(existing.promise, timeoutMs, operation, background, key);
726
+ }
727
+ let resolve;
728
+ let reject;
729
+ const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
730
+ const job = { key, background, operation, promise, resolve, reject, timer: null };
731
+ (background ? state.background : state.foreground).push(job);
732
+ state.byKey.set(key, job);
733
+ this.drainPullGate(state);
734
+ if (state.active !== job && state.active?.inline !== true) {
735
+ job.timer = setTimeout(() => this.bypassQueuedPull(state, job), timeoutMs);
736
+ }
737
+ return await promise;
738
+ }
739
+ 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
+ });
764
+ }
765
+ bypassQueuedPull(state, job) {
766
+ if (state.active === job)
767
+ return;
768
+ const queue = job.background ? state.background : state.foreground;
769
+ const index = queue.indexOf(job);
770
+ if (index < 0)
771
+ return;
772
+ queue.splice(index, 1);
773
+ if (state.byKey.get(job.key) === job)
774
+ 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);
778
+ }
779
+ drainPullGate(state) {
780
+ if (state.active)
781
+ return;
782
+ const job = state.foreground.shift() ?? state.background.shift();
783
+ if (!job)
784
+ return;
785
+ state.active = job;
786
+ if (job.timer)
787
+ clearTimeout(job.timer);
788
+ void this.executePullOperation(job.operation, job.background).then(job.resolve, job.reject).finally(() => {
789
+ if (state.active === job)
790
+ state.active = null;
791
+ if (state.byKey.get(job.key) === job)
792
+ state.byKey.delete(job.key);
793
+ this.drainPullGate(state);
794
+ });
795
+ }
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);
561
801
  }
562
802
  }
563
803
  async rawCall(method, params, options) {
@@ -584,6 +824,12 @@ export class RpcPipeline {
584
824
  async postprocessResult(method, params, result) {
585
825
  const client = this.runtime.client;
586
826
  let next = result;
827
+ if ((method === 'group.send' || method === 'group.v2.send') && isJsonObject(next)) {
828
+ next = normalizeGroupMentionMode(next);
829
+ }
830
+ if (method === 'group.get_settings' || method === 'group.set_settings') {
831
+ next = normalizeGroupSettingsResponse(next);
832
+ }
587
833
  if (method === 'group.thought.get' && isJsonObject(next)) {
588
834
  client._clientLog?.debug?.(`group.thought.get transport result: found=${String(next.found ?? '')}, raw_count=${Array.isArray(next.thoughts) ? next.thoughts.length : 0}`);
589
835
  next = await client._decryptGroupThoughts(next);
@@ -616,12 +862,12 @@ export class RpcPipeline {
616
862
  if (rawMessages.length) {
617
863
  client._seqTracker.onPullResult(ns, rawMessages, Number(params.after_seq ?? 0) || 0);
618
864
  }
619
- const serverAck = Number(result.server_ack_seq ?? 0);
620
- if (serverAck > 0) {
865
+ const retentionFloor = Math.max(0, Number(result.retention_floor_seq ?? 0));
866
+ if (retentionFloor > 0) {
621
867
  const contig = client._seqTracker.getContiguousSeq(ns);
622
- if (contig < serverAck) {
623
- client._clientLog?.info?.(`message.pull retention-floor advance: ns=${ns} contiguous=${contig} -> server_ack_seq=${serverAck}`);
624
- client._seqTracker.forceContiguousSeq(ns, serverAck);
868
+ if (contig < retentionFloor) {
869
+ client._clientLog?.info?.(`message.pull retention-floor advance: ns=${ns} contiguous=${contig} -> retention_floor_seq=${retentionFloor}`);
870
+ client._seqTracker.forceContiguousSeq(ns, retentionFloor);
625
871
  }
626
872
  }
627
873
  if (client._seqTracker.getContiguousSeq(ns) !== contigBefore) {
@@ -631,7 +877,10 @@ export class RpcPipeline {
631
877
  }
632
878
  postprocessGroupPull(params, result) {
633
879
  const client = this.runtime.client;
634
- const gid = String(params.group_aid ?? params.group_id ?? '').trim();
880
+ if (Array.isArray(result.messages)) {
881
+ result.messages = result.messages.map((message) => normalizeGroupMentionMode(message));
882
+ }
883
+ const gid = String(params.group_id ?? params.group_aid ?? '').trim();
635
884
  if (!gid || !client._seqTracker) {
636
885
  return;
637
886
  }
@@ -643,12 +892,15 @@ export class RpcPipeline {
643
892
  client._seqTracker.onPullResult(ns, rawMessages, Number(params.after_message_seq ?? params.after_seq ?? 0) || 0);
644
893
  }
645
894
  const cursor = isJsonObject(result.cursor) ? result.cursor : null;
646
- const serverAck = Number(cursor?.current_seq ?? 0);
647
- if (serverAck > 0) {
895
+ const retentionFloor = Math.max(0, Number(result.retention_floor_message_seq ?? result.retention_floor_seq ?? 0), Number(cursor?.retention_floor_seq ?? 0));
896
+ const isTail = String(result.window_mode ?? params.window_mode ?? '').trim().toLowerCase() === 'tail';
897
+ const visibilityFloor = isTail ? 0 : Math.max(0, Math.max(0, Number(result.earliest_available_message_seq ?? 1) - 1), Number(cursor?.join_seq ?? 0));
898
+ const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
899
+ if (effectiveFloor > 0) {
648
900
  const contig = client._seqTracker.getContiguousSeq(ns);
649
- if (contig < serverAck) {
650
- client._clientLog?.info?.(`group.pull retention-floor advance: ns=${ns} contiguous=${contig} -> cursor.current_seq=${serverAck}`);
651
- client._seqTracker.forceContiguousSeq(ns, serverAck);
901
+ if (contig < effectiveFloor) {
902
+ client._clientLog?.info?.(`group.pull floor advance: ns=${ns} contiguous=${contig} -> effective_floor=${effectiveFloor} retention_floor=${retentionFloor} visibility_floor=${visibilityFloor} mode=${isTail ? 'tail' : 'forward'}`);
903
+ client._seqTracker.forceContiguousSeq(ns, effectiveFloor);
652
904
  }
653
905
  }
654
906
  if (client._seqTracker.getContiguousSeq(ns) !== contigBefore) {
@@ -685,33 +937,16 @@ export class RpcPipeline {
685
937
  return;
686
938
  }
687
939
  const record = params;
688
- const rawEntries = [
689
- ['group_aid', record.group_aid],
690
- ['groupAid', record.groupAid],
691
- ['group_id', record.group_id],
692
- ['groupId', record.groupId],
693
- ];
694
- const found = rawEntries.find(([, value]) => String(value ?? '').trim());
695
- if (found) {
696
- const [key, raw] = found;
697
- const rawGroupId = String(raw).trim();
698
- const normalizedGroupId = normalizeGroupId(rawGroupId);
699
- if (normalizedGroupId) {
700
- if (normalizedGroupId !== rawGroupId) {
701
- this.runtime.client._clientLog?.debug?.(`call group identifier normalized: ${key}=${rawGroupId} -> ${normalizedGroupId} method=${method}`);
702
- }
703
- const existingGroupId = String(record.group_id ?? record.groupId ?? '').trim();
704
- if (key === 'group_id' || key === 'groupId') {
705
- params.group_id = rawGroupId;
706
- }
707
- else if (existingGroupId) {
708
- params.group_id = existingGroupId;
709
- }
710
- else {
711
- params.group_id = normalizedGroupId;
712
- }
713
- params.group_aid = normalizedGroupId;
714
- }
940
+ const rawGroupId = String(record.group_id ?? record.groupId ?? '').trim();
941
+ const rawGroupAid = String(record.group_aid ?? record.groupAid ?? '').trim();
942
+ if (rawGroupId) {
943
+ params.group_id = rawGroupId;
944
+ }
945
+ else if (rawGroupAid) {
946
+ params.group_id = rawGroupAid;
947
+ }
948
+ if (rawGroupAid) {
949
+ params.group_aid = rawGroupAid;
715
950
  }
716
951
  delete record.groupAid;
717
952
  delete record.groupId;