@agentunion/fastaun-browser 0.5.4 → 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 (184) hide show
  1. package/CHANGELOG.md +177 -67
  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 +177 -67
  6. package/_packed_docs/INDEX.md +124 -65
  7. package/_packed_docs/KITE_DOCS_GUIDE.md +65 -30
  8. package/_packed_docs/agent.md/SCHEMA.md +83 -58
  9. package/_packed_docs/agent.md/examples/codeagent-claudecode.md +1 -1
  10. package/_packed_docs/agent.md/examples/openclaw-lobster.md +1 -1
  11. package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +1 -1
  12. 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
  13. 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 +234 -0
  14. 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 +1095 -0
  15. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +1199 -0
  16. package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +6 -4
  17. package/_packed_docs/group-message-rpc-alignment-gaps.md +741 -0
  18. package/_packed_docs/message-online-push-alignment.md +572 -0
  19. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +58 -20
  20. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +219 -247
  21. package/_packed_docs/protocol/12-Stream-/345/255/220/345/215/217/350/256/256.md +14 -14
  22. package/_packed_docs/protocol/13-Agent/350/241/214/344/270/272/350/247/204/350/214/203.md +3 -3
  23. 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
  24. package/_packed_docs/protocol/README.md +1 -0
  25. package/_packed_docs/protocol/aun-docs-guide.md +7 -4
  26. package/_packed_docs/protocol/index.md +25 -19
  27. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +63 -18
  28. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +22 -0
  29. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +93 -84
  30. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +6 -2
  31. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +73 -34
  32. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +3 -3
  33. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +21 -9
  34. package/_packed_docs/sdk/09-group-rpc-manual.md +319 -285
  35. package/_packed_docs/sdk/09-message-rpc-manual.md +144 -103
  36. package/_packed_docs/sdk/09-payload-reference.md +1 -1
  37. package/_packed_docs/sdk/09-storage-rpc-manual.md +14 -3
  38. package/_packed_docs/sdk/09-stream-rpc-manual.md +8 -8
  39. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +34 -23
  40. package/_packed_docs/sdk/CHANGELOG-0.5.6.md +73 -0
  41. package/_packed_docs/sdk/INDEX.md +45 -37
  42. package/_packed_docs/sdk/README.md +11 -9
  43. package/_packed_docs//345/217/221/345/270/203/346/212/245/345/221/212-0.5.6.md +260 -0
  44. 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
  45. 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
  46. package/dist/agent-md-schema.d.ts +14 -0
  47. package/dist/agent-md-schema.d.ts.map +1 -0
  48. package/dist/agent-md-schema.js +103 -0
  49. package/dist/agent-md-schema.js.map +1 -0
  50. package/dist/agent-md.d.ts +11 -5
  51. package/dist/agent-md.d.ts.map +1 -1
  52. package/dist/agent-md.js +295 -53
  53. package/dist/agent-md.js.map +1 -1
  54. package/dist/aid-store.d.ts +7 -7
  55. package/dist/aid-store.d.ts.map +1 -1
  56. package/dist/aid-store.js +46 -18
  57. package/dist/aid-store.js.map +1 -1
  58. package/dist/aid.d.ts +2 -0
  59. package/dist/aid.d.ts.map +1 -1
  60. package/dist/aid.js +29 -7
  61. package/dist/aid.js.map +1 -1
  62. package/dist/auth.d.ts.map +1 -1
  63. package/dist/auth.js +31 -11
  64. package/dist/auth.js.map +1 -1
  65. package/dist/bundle.js +29920 -19719
  66. package/dist/cert-utils.d.ts +1 -0
  67. package/dist/cert-utils.d.ts.map +1 -1
  68. package/dist/cert-utils.js +36 -16
  69. package/dist/cert-utils.js.map +1 -1
  70. package/dist/client/delivery.d.ts +106 -11
  71. package/dist/client/delivery.d.ts.map +1 -1
  72. package/dist/client/delivery.js +1925 -374
  73. package/dist/client/delivery.js.map +1 -1
  74. package/dist/client/group-state.d.ts.map +1 -1
  75. package/dist/client/group-state.js +27 -24
  76. package/dist/client/group-state.js.map +1 -1
  77. package/dist/client/lifecycle.d.ts +15 -1
  78. package/dist/client/lifecycle.d.ts.map +1 -1
  79. package/dist/client/lifecycle.js +477 -132
  80. package/dist/client/lifecycle.js.map +1 -1
  81. package/dist/client/mention-mode.d.ts +7 -0
  82. package/dist/client/mention-mode.d.ts.map +1 -0
  83. package/dist/client/mention-mode.js +184 -0
  84. package/dist/client/mention-mode.js.map +1 -0
  85. package/dist/client/peers.d.ts +1 -1
  86. package/dist/client/peers.d.ts.map +1 -1
  87. package/dist/client/peers.js +26 -3
  88. package/dist/client/peers.js.map +1 -1
  89. package/dist/client/rpc-pipeline.d.ts +34 -2
  90. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  91. package/dist/client/rpc-pipeline.js +503 -99
  92. package/dist/client/rpc-pipeline.js.map +1 -1
  93. package/dist/client/runtime.d.ts +1 -3
  94. package/dist/client/runtime.d.ts.map +1 -1
  95. package/dist/client/runtime.js +4 -7
  96. package/dist/client/runtime.js.map +1 -1
  97. package/dist/client/v2-e2ee.d.ts +43 -2
  98. package/dist/client/v2-e2ee.d.ts.map +1 -1
  99. package/dist/client/v2-e2ee.js +931 -138
  100. package/dist/client/v2-e2ee.js.map +1 -1
  101. package/dist/client.d.ts +46 -15
  102. package/dist/client.d.ts.map +1 -1
  103. package/dist/client.js +785 -304
  104. package/dist/client.js.map +1 -1
  105. package/dist/errors.d.ts.map +1 -1
  106. package/dist/errors.js +4 -1
  107. package/dist/errors.js.map +1 -1
  108. package/dist/facades.d.ts +26 -1
  109. package/dist/facades.d.ts.map +1 -1
  110. package/dist/facades.js +142 -60
  111. package/dist/facades.js.map +1 -1
  112. package/dist/group-id.d.ts.map +1 -1
  113. package/dist/group-id.js +2 -17
  114. package/dist/group-id.js.map +1 -1
  115. package/dist/group-index.d.ts +6 -1
  116. package/dist/group-index.d.ts.map +1 -1
  117. package/dist/group-index.js +44 -25
  118. package/dist/group-index.js.map +1 -1
  119. package/dist/index.d.ts +5 -4
  120. package/dist/index.d.ts.map +1 -1
  121. package/dist/index.js +3 -2
  122. package/dist/index.js.map +1 -1
  123. package/dist/keystore/index.d.ts +10 -5
  124. package/dist/keystore/index.d.ts.map +1 -1
  125. package/dist/keystore/indexeddb-identity-store.d.ts +0 -12
  126. package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
  127. package/dist/keystore/indexeddb-identity-store.js +0 -60
  128. package/dist/keystore/indexeddb-identity-store.js.map +1 -1
  129. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  130. package/dist/keystore/indexeddb-shared.js +9 -5
  131. package/dist/keystore/indexeddb-shared.js.map +1 -1
  132. package/dist/keystore/indexeddb-token-store.d.ts +3 -1
  133. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  134. package/dist/keystore/indexeddb-token-store.js +47 -2
  135. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  136. package/dist/register-flow.d.ts.map +1 -1
  137. package/dist/register-flow.js +28 -3
  138. package/dist/register-flow.js.map +1 -1
  139. package/dist/seq-tracker.d.ts +28 -8
  140. package/dist/seq-tracker.d.ts.map +1 -1
  141. package/dist/seq-tracker.js +224 -61
  142. package/dist/seq-tracker.js.map +1 -1
  143. package/dist/storage/vfs.d.ts +1 -0
  144. package/dist/storage/vfs.d.ts.map +1 -1
  145. package/dist/storage/vfs.js +26 -2
  146. package/dist/storage/vfs.js.map +1 -1
  147. package/dist/tools/cross-sdk-agent.js +399 -11
  148. package/dist/tools/cross-sdk-agent.js.map +1 -1
  149. package/dist/transport.d.ts +12 -0
  150. package/dist/transport.d.ts.map +1 -1
  151. package/dist/transport.js +226 -102
  152. package/dist/transport.js.map +1 -1
  153. package/dist/v2/session/session.d.ts.map +1 -1
  154. package/dist/v2/session/session.js +3 -4
  155. package/dist/v2/session/session.js.map +1 -1
  156. package/dist/v2/state/commitment.d.ts.map +1 -1
  157. package/dist/v2/state/commitment.js +1 -2
  158. package/dist/v2/state/commitment.js.map +1 -1
  159. package/dist/validators.d.ts.map +1 -1
  160. package/dist/validators.js +8 -14
  161. package/dist/validators.js.map +1 -1
  162. package/dist/version.d.ts +1 -1
  163. package/dist/version.js +1 -1
  164. package/package.json +11 -9
  165. package/dist/group-resources.d.ts +0 -98
  166. package/dist/group-resources.d.ts.map +0 -1
  167. package/dist/group-resources.js +0 -635
  168. package/dist/group-resources.js.map +0 -1
  169. package/dist/keystore/indexeddb.d.ts +0 -179
  170. package/dist/keystore/indexeddb.d.ts.map +0 -1
  171. package/dist/keystore/indexeddb.js +0 -2031
  172. package/dist/keystore/indexeddb.js.map +0 -1
  173. package/dist/namespaces/auth.d.ts +0 -98
  174. package/dist/namespaces/auth.d.ts.map +0 -1
  175. package/dist/namespaces/auth.js +0 -992
  176. package/dist/namespaces/auth.js.map +0 -1
  177. package/dist/namespaces/custody.d.ts +0 -51
  178. package/dist/namespaces/custody.d.ts.map +0 -1
  179. package/dist/namespaces/custody.js +0 -302
  180. package/dist/namespaces/custody.js.map +0 -1
  181. package/dist/namespaces/meta.d.ts +0 -109
  182. package/dist/namespaces/meta.d.ts.map +0 -1
  183. package/dist/namespaces/meta.js +0 -549
  184. 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, TimeoutError, 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',
@@ -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',
@@ -167,6 +168,8 @@ function isTransientV2PullFallbackError(error) {
167
168
  }
168
169
  export class RpcPipeline {
169
170
  runtime;
171
+ pullGateStates = new Map();
172
+ inlineRealtimeScopes = new Set();
170
173
  constructor(runtime) {
171
174
  this.runtime = runtime;
172
175
  }
@@ -196,6 +199,8 @@ export class RpcPipeline {
196
199
  };
197
200
  const skipSendResultEnvelope = Boolean(p._skip_send_result_envelope);
198
201
  delete p._skip_send_result_envelope;
202
+ const deferGroupReadyPostprocess = Boolean(p._defer_group_ready_postprocess);
203
+ delete p._defer_group_ready_postprocess;
199
204
  if (method === 'message.send') {
200
205
  const encrypt = p.encrypt !== undefined ? p.encrypt : true;
201
206
  delete p.encrypt;
@@ -217,7 +222,8 @@ export class RpcPipeline {
217
222
  if (encrypt) {
218
223
  await client._ensureV2SessionReady('group.send', 'V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)');
219
224
  client._clientLog.debug('call route: group.send -> V2 encrypted send');
220
- return await client._sendGroupV2(String(p.group_aid ?? p.group_id ?? ''), p.payload ?? {}, {
225
+ return await client._sendGroupV2(String(p.group_id ?? p.group_aid ?? ''), p.payload ?? {}, {
226
+ groupAid: String(p.group_aid ?? '').trim() || undefined,
221
227
  messageId: String(p.message_id ?? '') || undefined,
222
228
  timestamp: p.timestamp,
223
229
  protectedHeaders: client._protectedHeadersFromParams(p),
@@ -246,19 +252,22 @@ export class RpcPipeline {
246
252
  }
247
253
  const pullGateKey = this.pullGateKeyForCall(method, p);
248
254
  if (pullGateKey) {
249
- return await this.runPullSerialized(pullGateKey, async () => {
250
- return await runWithRpcPriority(() => this.callImplInner(method, p, skipSendResultEnvelope));
251
- });
255
+ return await this.runPullSerialized(pullGateKey, async () => await this.callImplInner(method, p, skipSendResultEnvelope, deferGroupReadyPostprocess), rpcBackground && this.pullGateBackgroundForCall(method, p));
252
256
  }
253
- return await runWithRpcPriority(() => this.callImplInner(method, p, skipSendResultEnvelope));
257
+ return await runWithRpcPriority(() => this.callImplInner(method, p, skipSendResultEnvelope, deferGroupReadyPostprocess));
254
258
  }
255
- async callImplInner(method, p, skipSendResultEnvelope = false) {
259
+ async callImplInner(method, p, skipSendResultEnvelope = false, deferGroupReadyPostprocess = false) {
256
260
  const client = this.runtime.client;
257
261
  if (method === 'message.v2.pull' || (method === 'message.pull' && hasMatchingV2Session(client))) {
258
262
  try {
259
263
  await client._ensureV2SessionReady('message.pull');
260
264
  client._clientLog.debug(`call route: ${method} -> V2 pull`);
261
- const messages = await client._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, { force: p.force === true });
265
+ if (p.window_mode === 'tail')
266
+ return await client._v2E2EE.pullV2TailInternal(p);
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
+ });
262
271
  return { messages };
263
272
  }
264
273
  catch (exc) {
@@ -268,48 +277,71 @@ export class RpcPipeline {
268
277
  client._clientLog.warn(`message.pull V2 route unavailable, fallback to plain pull: ${exc instanceof Error ? exc.message : String(exc)}`);
269
278
  }
270
279
  }
280
+ if (method === 'message.history') {
281
+ await client._ensureV2SessionReady('message.history');
282
+ return await client._v2E2EE.historyV2Internal(p);
283
+ }
271
284
  if (method === 'message.v2.ack' || (method === 'message.ack' && hasMatchingV2Session(client))) {
272
285
  await client._ensureV2SessionReady('message.ack');
273
286
  client._clientLog.debug(`call route: ${method} -> V2 ack`);
274
- return await client._ackV2(Number(p.seq ?? p.up_to_seq ?? 0) || undefined);
287
+ return await client._ackV2((p.seq ?? p.up_to_seq));
275
288
  }
276
289
  if ((method === 'group.v2.pull' || method === 'group.pull') && p.group_id && (method === 'group.v2.pull' || hasMatchingV2Session(client))) {
277
290
  await client._ensureV2SessionReady('group.pull');
278
291
  client._clientLog.debug(`call route: ${method} -> V2 pull`);
292
+ if (p.window_mode === 'tail')
293
+ return await client._v2E2EE.pullGroupV2TailInternal(p);
279
294
  const hasExplicitAfterSeq = 'after_seq' in p || 'after_message_seq' in p;
280
- const cursorParams = client._explicitGroupCursorParams(p);
281
- const ownsCursor = Object.keys(cursorParams).length === 0 || client._groupCursorTargetsCurrentInstance(cursorParams);
282
- 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
+ };
283
300
  if (hasExplicitAfterSeq)
284
301
  pullOpts.explicitAfterSeq = true;
302
+ if (p.force === true)
303
+ pullOpts.force = true;
285
304
  if (Object.keys(cursorParams).length > 0)
286
305
  pullOpts.cursorParams = cursorParams;
287
306
  if (!ownsCursor)
288
307
  pullOpts.ownsCursor = false;
289
308
  if (String(p.group_id ?? '').trim())
290
309
  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);
310
+ if (String(p.group_aid ?? '').trim())
311
+ pullOpts.groupAid = String(p.group_aid).trim();
312
+ 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
313
  return { messages };
293
314
  }
315
+ if (method === 'group.history') {
316
+ if (!String(p.group_id ?? p.group_aid ?? '').trim())
317
+ throw new ValidationError('group.history requires group_id');
318
+ await client._ensureV2SessionReady('group.history');
319
+ return await client._v2E2EE.historyGroupV2Internal(p);
320
+ }
294
321
  if ((method === 'group.v2.ack' || method === 'group.ack_messages') && p.group_id && (method === 'group.v2.ack' || hasMatchingV2Session(client))) {
295
322
  await client._ensureV2SessionReady('group.ack_messages');
296
323
  client._clientLog.debug(`call route: ${method} -> V2 ack`);
297
- const cursorParams = client._explicitGroupCursorParams(p);
298
- 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);
299
326
  if (!ownsCursor) {
300
327
  return await client._rawGroupAckMessages(p);
301
328
  }
302
- return await client._ackGroupV2(String(p.group_aid ?? p.group_id), Number(p.seq ?? p.msg_seq ?? p.up_to_seq ?? 0) || undefined);
329
+ return await client._ackGroupV2((p.group_id ?? p.group_aid), (p.seq ?? p.msg_seq ?? p.up_to_seq), p.group_aid);
303
330
  }
304
331
  await this.applyClientSignature(method, p);
305
332
  const callTimeout = NON_IDEMPOTENT_METHODS.has(method) ? NON_IDEMPOTENT_TIMEOUT : undefined;
306
333
  const rpcBackground = client._backgroundRpcDepth > 0;
307
- let result = callTimeout
308
- ? await client._transport.call(method, p, callTimeout, undefined, rpcBackground)
309
- : await client._transport.call(method, p, undefined, undefined, rpcBackground);
310
- result = await this.postprocessResult(method, p, result);
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);
341
+ const sendResultModeSource = result;
342
+ result = await this.postprocessResult(method, p, result, { skipGroupState: deferGroupReadyPostprocess });
311
343
  if (!skipSendResultEnvelope) {
312
- result = client._delivery.attachSendResultEnvelope(method, p, result, Boolean(p.encrypted));
344
+ result = client._delivery.attachSendResultEnvelope(method, p, result, Boolean(p.encrypted), sendResultModeSource);
313
345
  }
314
346
  return result;
315
347
  }
@@ -501,22 +533,240 @@ export class RpcPipeline {
501
533
  // ── Pull Gate(序列化同一 key 的并发 pull)──────────────────
502
534
  pullGateKeyForCall(method, params) {
503
535
  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}` : '';
536
+ if (method === 'message.pull' || method === 'message.v2.pull' || method === 'message.history') {
537
+ if (!client._aid)
538
+ return '';
539
+ const mode = method === 'message.history' ? 'history' : params.window_mode === 'tail' ? 'tail' : 'forward';
540
+ const cursor = mode === 'history'
541
+ ? `before=${String(params.before_seq)}`
542
+ : `after=${String(params.after_seq ?? 0)}|force=${String(Boolean(params.force))}`;
543
+ return `p2p:${client._aid}|${mode}|${cursor}|limit=${String(params.limit)}`;
544
+ }
545
+ if (method === 'group.pull' || method === 'group.v2.pull' || method === 'group.history') {
546
+ const gid = String(params.group_id ?? params.group_aid ?? '').trim();
547
+ if (!gid)
548
+ return '';
549
+ const mode = method === 'group.history' ? 'history' : params.window_mode === 'tail' ? 'tail' : 'forward';
550
+ const cursor = mode === 'history'
551
+ ? `before=${String(params.before_seq)}`
552
+ : `after=${String(params.after_seq ?? params.after_message_seq ?? 0)}|force=${String(Boolean(params.force))}`;
553
+ return `group:${gid}|${mode}|${cursor}|limit=${String(params.limit)}`;
510
554
  }
511
555
  if (method === 'group.pull_events') {
512
- const gid = String(params.group_aid ?? params.group_id ?? '').trim();
513
- return gid ? `group_event:${gid}` : '';
556
+ const gid = String(params.group_id ?? params.group_aid ?? '').trim();
557
+ return gid
558
+ ? `group_event:${gid}|forward|after=${String(params.after_event_seq ?? 0)}|limit=${String(params.limit)}`
559
+ : '';
560
+ }
561
+ return '';
562
+ }
563
+ pullGateBackgroundForCall(method, params) {
564
+ if (method === 'message.history' || method === 'group.history')
565
+ return false;
566
+ if (['message.pull', 'message.v2.pull', 'group.pull', 'group.v2.pull'].includes(method)) {
567
+ return params.window_mode !== 'tail';
568
+ }
569
+ return true;
570
+ }
571
+ pullGateName(key) {
572
+ void key;
573
+ return 'pull';
574
+ }
575
+ pullGateState(key) {
576
+ const name = this.pullGateName(key);
577
+ let state = this.pullGateStates.get(name);
578
+ if (!state) {
579
+ state = { active: null, foreground: [], background: [], byKey: new Map(), lifecycle: new Map(), watchdog: null };
580
+ this.pullGateStates.set(name, state);
581
+ this.startPullGateWatchdog(state, name);
582
+ }
583
+ return state;
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
+ }
624
+ realtimePullNamespace(key) {
625
+ for (const marker of ['|tail|', '|forward|']) {
626
+ const index = key.indexOf(marker);
627
+ if (index > 0)
628
+ return key.slice(0, index);
514
629
  }
515
630
  return '';
516
631
  }
632
+ pullScopeKey(key) {
633
+ const index = key.indexOf('|');
634
+ return index < 0 ? key : key.slice(0, index);
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
+ }
652
+ isPullResponseProcessing(key) {
653
+ const responseKeys = this.runtime.client._pullResponseKeys;
654
+ return Boolean(key) && (responseKeys?.get(this.pullScopeKey(key)) ?? 0) > 0;
655
+ }
656
+ emptyPullResultForCall(method) {
657
+ if (method === 'group.pull_events')
658
+ return { events: [], count: 0 };
659
+ return { messages: [], count: 0 };
660
+ }
661
+ hasPendingRealtimePull(ns) {
662
+ const normalized = String(ns ?? '').trim();
663
+ if (!normalized || (!normalized.startsWith('p2p:') && !normalized.startsWith('group:')))
664
+ return false;
665
+ if (this.isPullResponseProcessing(normalized))
666
+ return true;
667
+ const state = this.pullGateStates.get(this.pullGateName(normalized));
668
+ if (!state)
669
+ return false;
670
+ const matches = (job) => {
671
+ if (!job)
672
+ return false;
673
+ return this.realtimePullNamespace(job.key) === normalized;
674
+ };
675
+ return matches(state.active)
676
+ || state.foreground.some(matches)
677
+ || state.background.some(matches);
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
+ }
709
+ async tryRunInlineRealtime(ns, seq, operation) {
710
+ const normalized = String(ns ?? '').trim();
711
+ if ((!normalized.startsWith('p2p:') && !normalized.startsWith('group:'))
712
+ || !Number.isSafeInteger(seq) || seq <= 0 || typeof operation !== 'function') {
713
+ return { acquired: false };
714
+ }
715
+ const client = this.runtime.client;
716
+ if (this.hasPullActivity(normalized)) {
717
+ return { acquired: false };
718
+ }
719
+ if (this.isPullResponseProcessing(normalized)) {
720
+ return { acquired: false };
721
+ }
722
+ for (const [key, gate] of client._pullGates?.entries?.() ?? []) {
723
+ if (gate?.inflight && this.pullScopeKey(key) === normalized)
724
+ return { acquired: false };
725
+ }
726
+ const key = `${normalized}|inline|seq=${seq}`;
727
+ const state = this.pullGateState(key);
728
+ if (state.active !== null || state.foreground.length > 0 || state.background.length > 0) {
729
+ return { acquired: false };
730
+ }
731
+ const guard = {
732
+ key,
733
+ namespace: normalized,
734
+ background: false,
735
+ running: true,
736
+ suspended: false,
737
+ inline: true,
738
+ operation,
739
+ promise: Promise.resolve(undefined),
740
+ resolve: () => { },
741
+ reject: () => { },
742
+ timer: null,
743
+ resume: null,
744
+ resumeResolve: null,
745
+ pullingStartedAt: 0,
746
+ timedOut: false,
747
+ cancel: null,
748
+ };
749
+ this.inlineRealtimeScopes.add(normalized);
750
+ try {
751
+ return { acquired: true, value: await operation() };
752
+ }
753
+ finally {
754
+ this.inlineRealtimeScopes.delete(normalized);
755
+ if (state.active === guard)
756
+ state.active = null;
757
+ this.drainPullGate(state);
758
+ this.runtime.client._delivery?.onPullGateIdle?.(normalized);
759
+ this.armQueuedPullTimers(state);
760
+ }
761
+ }
517
762
  tryAcquirePullGate(key) {
518
763
  if (!key)
519
764
  return 0;
765
+ if (this.inlineRealtimeScopes.has(this.pullScopeKey(key)))
766
+ return null;
767
+ const serialized = this.pullGateStates.get(this.pullGateName(key));
768
+ if (serialized?.active?.inline === true)
769
+ return null;
520
770
  const client = this.runtime.client;
521
771
  const now = Date.now();
522
772
  const gate = client._pullGates.get(key) ?? { inflight: false, startedAt: 0, token: 0 };
@@ -541,25 +791,129 @@ export class RpcPipeline {
541
791
  gate.inflight = false;
542
792
  gate.startedAt = 0;
543
793
  }
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}`);
794
+ async runPullSerialized(key, operation, background = false) {
795
+ if (!key)
796
+ return await this.executePullOperation(operation, background);
797
+ const state = this.pullGateState(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);
805
+ if (existing) {
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);
554
812
  }
813
+ return await existing.promise;
814
+ }
815
+ let resolve;
816
+ let reject;
817
+ const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
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
+ };
826
+ (background ? state.background : state.foreground).push(job);
827
+ state.byKey.set(key, job);
828
+ this.drainPullGate(state);
829
+ this.checkPullGate(state, this.pullGateName(key));
830
+ return await promise;
831
+ }
832
+ async executePullOperation(operation, background) {
833
+ if (background)
834
+ return await this.runtime.client._withBackgroundRpc(operation);
835
+ return await operation();
836
+ }
837
+ async yieldPullGate(key, nextKey, background) {
838
+ if (!key)
839
+ return;
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)
846
+ return;
847
+ const replacementKey = String(nextKey || key);
848
+ if (state.byKey.get(job.key) === job)
849
+ state.byKey.delete(job.key);
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);
878
+ }
879
+ drainPullGate(state) {
880
+ if (state.active)
881
+ return;
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);
887
+ if (!job)
888
+ return;
889
+ state.active = job;
890
+ if (job.timer)
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;
555
902
  }
556
- try {
557
- return await operation();
558
- }
559
- finally {
560
- this.releasePullGate(key, token);
561
- }
903
+ job.running = true;
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');
908
+ if (state.active === job)
909
+ state.active = null;
910
+ if (state.byKey.get(job.key) === job)
911
+ state.byKey.delete(job.key);
912
+ this.drainPullGate(state);
913
+ this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
914
+ });
562
915
  }
916
+ armQueuedPullTimers(_state) { }
563
917
  async rawCall(method, params, options) {
564
918
  const client = this.runtime.client;
565
919
  const payload = { ...(params ?? {}) };
@@ -570,20 +924,40 @@ export class RpcPipeline {
570
924
  await this.applyClientSignature(method, payload);
571
925
  }
572
926
  const timeout = options?.timeout;
573
- if (rpcBackground) {
574
- return await client._transport.call(method, payload, timeout, options?.trace, true);
575
- }
576
- if (options?.trace !== undefined) {
577
- 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();
578
944
  }
579
- if (timeout !== undefined) {
580
- 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();
581
950
  }
582
- return await client._transport.call(method, payload);
583
951
  }
584
- async postprocessResult(method, params, result) {
952
+ async postprocessResult(method, params, result, options = {}) {
585
953
  const client = this.runtime.client;
586
954
  let next = result;
955
+ if ((method === 'group.send' || method === 'group.v2.send') && isJsonObject(next)) {
956
+ next = normalizeGroupMentionMode(next);
957
+ }
958
+ if (method === 'group.get_settings' || method === 'group.set_settings') {
959
+ next = normalizeGroupSettingsResponse(next);
960
+ }
587
961
  if (method === 'group.thought.get' && isJsonObject(next)) {
588
962
  client._clientLog?.debug?.(`group.thought.get transport result: found=${String(next.found ?? '')}, raw_count=${Array.isArray(next.thoughts) ? next.thoughts.length : 0}`);
589
963
  next = await client._decryptGroupThoughts(next);
@@ -593,12 +967,40 @@ export class RpcPipeline {
593
967
  next = await client._decryptMessageThoughts(next);
594
968
  }
595
969
  if (method === 'message.pull' && isJsonObject(next)) {
596
- 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
+ }
597
979
  }
598
980
  if (method === 'group.pull' && isJsonObject(next)) {
599
- 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);
600
1003
  }
601
- next = await client._groupState.postprocessResult(method, params, next);
602
1004
  return next;
603
1005
  }
604
1006
  _sleep(ms) {
@@ -616,12 +1018,12 @@ export class RpcPipeline {
616
1018
  if (rawMessages.length) {
617
1019
  client._seqTracker.onPullResult(ns, rawMessages, Number(params.after_seq ?? 0) || 0);
618
1020
  }
619
- const serverAck = Number(result.server_ack_seq ?? 0);
620
- if (serverAck > 0) {
1021
+ const retentionFloor = Math.max(0, Number(result.retention_floor_seq ?? 0));
1022
+ if (retentionFloor > 0) {
621
1023
  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);
1024
+ if (contig < retentionFloor) {
1025
+ client._clientLog?.info?.(`message.pull retention-floor advance: ns=${ns} contiguous=${contig} -> retention_floor_seq=${retentionFloor}`);
1026
+ client._seqTracker.forceContiguousSeq(ns, retentionFloor);
625
1027
  }
626
1028
  }
627
1029
  if (client._seqTracker.getContiguousSeq(ns) !== contigBefore) {
@@ -631,7 +1033,10 @@ export class RpcPipeline {
631
1033
  }
632
1034
  postprocessGroupPull(params, result) {
633
1035
  const client = this.runtime.client;
634
- const gid = String(params.group_aid ?? params.group_id ?? '').trim();
1036
+ if (Array.isArray(result.messages)) {
1037
+ result.messages = result.messages.map((message) => normalizeGroupMentionMode(message));
1038
+ }
1039
+ const gid = String(params.group_id ?? params.group_aid ?? '').trim();
635
1040
  if (!gid || !client._seqTracker) {
636
1041
  return;
637
1042
  }
@@ -643,12 +1048,15 @@ export class RpcPipeline {
643
1048
  client._seqTracker.onPullResult(ns, rawMessages, Number(params.after_message_seq ?? params.after_seq ?? 0) || 0);
644
1049
  }
645
1050
  const cursor = isJsonObject(result.cursor) ? result.cursor : null;
646
- const serverAck = Number(cursor?.current_seq ?? 0);
647
- if (serverAck > 0) {
1051
+ const retentionFloor = Math.max(0, Number(result.retention_floor_message_seq ?? result.retention_floor_seq ?? 0), Number(cursor?.retention_floor_seq ?? 0));
1052
+ const isTail = String(result.window_mode ?? params.window_mode ?? '').trim().toLowerCase() === 'tail';
1053
+ const visibilityFloor = isTail ? 0 : Math.max(0, Math.max(0, Number(result.earliest_available_message_seq ?? 1) - 1), Number(cursor?.join_seq ?? 0));
1054
+ const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
1055
+ if (effectiveFloor > 0) {
648
1056
  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);
1057
+ if (contig < effectiveFloor) {
1058
+ client._clientLog?.info?.(`group.pull floor advance: ns=${ns} contiguous=${contig} -> effective_floor=${effectiveFloor} retention_floor=${retentionFloor} visibility_floor=${visibilityFloor} mode=${isTail ? 'tail' : 'forward'}`);
1059
+ client._seqTracker.forceContiguousSeq(ns, effectiveFloor);
652
1060
  }
653
1061
  }
654
1062
  if (client._seqTracker.getContiguousSeq(ns) !== contigBefore) {
@@ -685,33 +1093,16 @@ export class RpcPipeline {
685
1093
  return;
686
1094
  }
687
1095
  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
- }
1096
+ const rawGroupId = String(record.group_id ?? record.groupId ?? '').trim();
1097
+ const rawGroupAid = String(record.group_aid ?? record.groupAid ?? '').trim();
1098
+ if (rawGroupId) {
1099
+ params.group_id = rawGroupId;
1100
+ }
1101
+ else if (rawGroupAid) {
1102
+ params.group_id = rawGroupAid;
1103
+ }
1104
+ if (rawGroupAid) {
1105
+ params.group_aid = rawGroupAid;
715
1106
  }
716
1107
  delete record.groupAid;
717
1108
  delete record.groupId;
@@ -725,6 +1116,19 @@ export class RpcPipeline {
725
1116
  }
726
1117
  return cursorParams;
727
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
+ }
728
1132
  validateMessageRecipient(toAid) {
729
1133
  if (isGroupServiceAid(toAid)) {
730
1134
  throw new ValidationError('message.send receiver cannot be group.{issuer}; use group.send instead');