@agentunion/fastaun 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 (133) hide show
  1. package/CHANGELOG.md +126 -52
  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 +126 -52
  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.js +8 -2
  37. package/dist/agent-md.js.map +1 -1
  38. package/dist/aid-store.d.ts +2 -0
  39. package/dist/aid-store.js +43 -7
  40. package/dist/aid-store.js.map +1 -1
  41. package/dist/auth.d.ts +0 -2
  42. package/dist/auth.js +97 -50
  43. package/dist/auth.js.map +1 -1
  44. package/dist/client/delivery.d.ts +70 -7
  45. package/dist/client/delivery.js +1334 -236
  46. package/dist/client/delivery.js.map +1 -1
  47. package/dist/client/group-state.js +25 -22
  48. package/dist/client/group-state.js.map +1 -1
  49. package/dist/client/lifecycle.js +46 -3
  50. package/dist/client/lifecycle.js.map +1 -1
  51. package/dist/client/mention-mode.d.ts +6 -0
  52. package/dist/client/mention-mode.js +184 -0
  53. package/dist/client/mention-mode.js.map +1 -0
  54. package/dist/client/peers.d.ts +1 -1
  55. package/dist/client/peers.js +26 -3
  56. package/dist/client/peers.js.map +1 -1
  57. package/dist/client/rpc-pipeline.d.ts +20 -1
  58. package/dist/client/rpc-pipeline.js +332 -73
  59. package/dist/client/rpc-pipeline.js.map +1 -1
  60. package/dist/client/v2-e2ee.d.ts +20 -1
  61. package/dist/client/v2-e2ee.js +625 -105
  62. package/dist/client/v2-e2ee.js.map +1 -1
  63. package/dist/client.d.ts +17 -5
  64. package/dist/client.js +332 -167
  65. package/dist/client.js.map +1 -1
  66. package/dist/errors.js +4 -1
  67. package/dist/errors.js.map +1 -1
  68. package/dist/facades.d.ts +6 -0
  69. package/dist/facades.js +196 -80
  70. package/dist/facades.js.map +1 -1
  71. package/dist/group-index.d.ts +5 -0
  72. package/dist/group-index.js +65 -30
  73. package/dist/group-index.js.map +1 -1
  74. package/dist/keystore/aid-db.d.ts +12 -0
  75. package/dist/keystore/aid-db.js +68 -4
  76. package/dist/keystore/aid-db.js.map +1 -1
  77. package/dist/keystore/index.d.ts +12 -0
  78. package/dist/keystore/local-identity-store.d.ts +10 -0
  79. package/dist/keystore/local-identity-store.js +8 -2
  80. package/dist/keystore/local-identity-store.js.map +1 -1
  81. package/dist/keystore/local-token-store.d.ts +10 -0
  82. package/dist/keystore/local-token-store.js +8 -2
  83. package/dist/keystore/local-token-store.js.map +1 -1
  84. package/dist/register-flow.d.ts +0 -1
  85. package/dist/register-flow.js +96 -38
  86. package/dist/register-flow.js.map +1 -1
  87. package/dist/secret-store/file-store.d.ts +3 -14
  88. package/dist/secret-store/file-store.js +7 -98
  89. package/dist/secret-store/file-store.js.map +1 -1
  90. package/dist/secret-store/index.d.ts +1 -4
  91. package/dist/secret-store/index.js +2 -2
  92. package/dist/secret-store/index.js.map +1 -1
  93. package/dist/seq-tracker.d.ts +29 -9
  94. package/dist/seq-tracker.js +233 -64
  95. package/dist/seq-tracker.js.map +1 -1
  96. package/dist/storage/vfs.d.ts +1 -0
  97. package/dist/storage/vfs.js +28 -4
  98. package/dist/storage/vfs.js.map +1 -1
  99. package/dist/tools/cross-sdk-agent.js +360 -8
  100. package/dist/tools/cross-sdk-agent.js.map +1 -1
  101. package/dist/transport.d.ts +19 -0
  102. package/dist/transport.js +197 -14
  103. package/dist/transport.js.map +1 -1
  104. package/dist/v2/e2ee/decrypt.js +26 -23
  105. package/dist/v2/e2ee/decrypt.js.map +1 -1
  106. package/dist/v2/session/session.js +3 -4
  107. package/dist/v2/session/session.js.map +1 -1
  108. package/dist/v2/state/commitment.js +1 -2
  109. package/dist/v2/state/commitment.js.map +1 -1
  110. package/dist/version.d.ts +1 -1
  111. package/dist/version.js +1 -1
  112. package/package.json +5 -4
  113. package/dist/e2ee-group.d.ts +0 -249
  114. package/dist/e2ee-group.js +0 -1567
  115. package/dist/e2ee-group.js.map +0 -1
  116. package/dist/group-resources.d.ts +0 -31
  117. package/dist/group-resources.js +0 -84
  118. package/dist/group-resources.js.map +0 -1
  119. package/dist/keystore/file.d.ts +0 -109
  120. package/dist/keystore/file.js +0 -772
  121. package/dist/keystore/file.js.map +0 -1
  122. package/dist/keystore/sqlite-backup.d.ts +0 -44
  123. package/dist/keystore/sqlite-backup.js +0 -382
  124. package/dist/keystore/sqlite-backup.js.map +0 -1
  125. package/dist/namespaces/auth.d.ts +0 -118
  126. package/dist/namespaces/auth.js +0 -968
  127. package/dist/namespaces/auth.js.map +0 -1
  128. package/dist/namespaces/custody.d.ts +0 -48
  129. package/dist/namespaces/custody.js +0 -313
  130. package/dist/namespaces/custody.js.map +0 -1
  131. package/dist/namespaces/meta.d.ts +0 -76
  132. package/dist/namespaces/meta.js +0 -490
  133. package/dist/namespaces/meta.js.map +0 -1
@@ -1,9 +1,9 @@
1
1
  import * as crypto from 'node:crypto';
2
2
  import { normalizeSlotId, slotIsolationKey } from '../config.js';
3
- import { ClientSignatureError, ConnectionError, PermissionError, StateError, ValidationError } from '../errors.js';
4
- import { normalizeGroupId } from '../group-id.js';
3
+ import { ClientSignatureError, ConnectionError, PermissionError, ValidationError } from '../errors.js';
5
4
  import { ConnectionState, isJsonObject, } from '../types.js';
6
5
  import { validateAIDFormat, validateGroupIDFormat } from '../validators.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',
@@ -106,7 +106,7 @@ function getCertFingerprint(certPem) {
106
106
  const certObj = new crypto.X509Certificate(certPem);
107
107
  return cachePut(certFingerprintCache, key, 'sha256:' + certObj.fingerprint256.replace(/:/g, '').toLowerCase());
108
108
  }
109
- /** pull-gate 防并发窗口,与 client.ts PULL_GATE_STALE_MS 保持一致 */
109
+ /** 旧式 tryAcquirePullGate 的防并发窗口。 */
110
110
  const PULL_GATE_STALE_MS = 30000;
111
111
  const NON_IDEMPOTENT_TIMEOUT_MS = 35_000;
112
112
  const NON_IDEMPOTENT_METHODS = new Set([
@@ -143,6 +143,9 @@ const NON_IDEMPOTENT_METHODS = new Set([
143
143
  ]);
144
144
  export class RpcPipeline {
145
145
  runtime;
146
+ pullGateStates = new Map();
147
+ inlineRealtimeScopes = new Set();
148
+ bypassedPullScopes = new Map();
146
149
  constructor(runtime) {
147
150
  this.runtime = runtime;
148
151
  }
@@ -164,17 +167,25 @@ export class RpcPipeline {
164
167
  const skipSendResultEnvelope = Boolean(p._skip_send_result_envelope);
165
168
  delete p._skip_send_result_envelope;
166
169
  const pullGateKey = this.pullGateKeyForCall(method, p);
170
+ const runWithPullResponseScope = async (operation) => {
171
+ const scope = pullGateKey ? this.pullScopeKey(pullGateKey) : '';
172
+ const wrapper = client._withPullResponseProcessing;
173
+ if (!scope || typeof wrapper !== 'function')
174
+ return await operation();
175
+ return await wrapper.call(client, scope, operation);
176
+ };
167
177
  if (pullGateKey && this.isPullResponseProcessing(pullGateKey)) {
168
178
  client._clientLog.debug(`pull skipped while processing pull response: method=${method} key=${pullGateKey}`);
169
179
  return client._emptyPullResultForCall(method);
170
180
  }
171
181
  if (pullGateKey && !pullGateLocked) {
182
+ const pullBackground = this.pullGateBackgroundForCall(method, p);
172
183
  const lockedParams = { ...p, _pull_gate_locked: true };
173
- if (rpcBackground)
184
+ if (pullBackground)
174
185
  lockedParams._rpc_background = true;
175
186
  if (skipSendResultEnvelope)
176
187
  lockedParams._skip_send_result_envelope = true;
177
- return await this.runPullSerialized(pullGateKey, async () => this.call(method, lockedParams));
188
+ return await this.runPullSerialized(pullGateKey, async () => this.call(method, lockedParams), pullBackground);
178
189
  }
179
190
  if (method === 'message.send') {
180
191
  const encrypt = p.encrypt ?? true;
@@ -193,7 +204,8 @@ export class RpcPipeline {
193
204
  const encrypt = p.encrypt ?? true;
194
205
  delete p.encrypt;
195
206
  if (encrypt) {
196
- return await runWithRpcPriority(() => client._sendGroupV2(String(p.group_aid ?? p.group_id ?? ''), p.payload, {
207
+ return await runWithRpcPriority(() => client._sendGroupV2(String(p.group_id ?? p.group_aid ?? ''), p.payload, {
208
+ groupAid: String(p.group_aid ?? '').trim() || undefined,
197
209
  messageId: String(p.message_id ?? '') || undefined,
198
210
  timestamp: p.timestamp,
199
211
  protectedHeaders: client._protectedHeadersFromParams(p),
@@ -223,15 +235,22 @@ export class RpcPipeline {
223
235
  }
224
236
  if (method === 'message.pull' || method === 'message.v2.pull') {
225
237
  await client._ensureV2SessionReady('message.pull');
238
+ if (p.window_mode === 'tail') {
239
+ return await runWithPullResponseScope(() => client._v2E2EE.pullV2TailInternal(p));
240
+ }
226
241
  const skipAutoAck = p._skip_auto_ack === true || p.skip_auto_ack === true;
227
242
  const force = p.force === true;
228
243
  const afterSeq = Number(p.after_seq ?? 0) || 0;
229
244
  const limit = Number(p.limit ?? 50) || 50;
230
- const messages = skipAutoAck
231
- ? await runWithRpcPriority(() => client._pullV2(afterSeq, limit, { skipAutoAck: true, gateLocked: true, force }))
232
- : await runWithRpcPriority(() => client._pullV2(afterSeq, limit, { gateLocked: true, force }));
245
+ const messages = await runWithPullResponseScope(() => (skipAutoAck
246
+ ? runWithRpcPriority(() => client._pullV2(afterSeq, limit, { skipAutoAck: true, gateLocked: true, force }))
247
+ : runWithRpcPriority(() => client._pullV2(afterSeq, limit, { gateLocked: true, force }))));
233
248
  return { messages };
234
249
  }
250
+ if (method === 'message.history') {
251
+ await client._ensureV2SessionReady('message.history');
252
+ return await client._v2E2EE.historyV2Internal(p);
253
+ }
235
254
  if (method === 'message.ack' || method === 'message.v2.ack') {
236
255
  await client._ensureV2SessionReady('message.ack');
237
256
  return await runWithRpcPriority(() => client._ackV2(Number(p.seq ?? p.up_to_seq ?? 0) || undefined));
@@ -241,21 +260,35 @@ export class RpcPipeline {
241
260
  throw new ValidationError('group.pull requires group_id');
242
261
  }
243
262
  await client._ensureV2SessionReady('group.pull');
263
+ if (p.window_mode === 'tail') {
264
+ return await runWithPullResponseScope(() => client._v2E2EE.pullGroupV2TailInternal(p));
265
+ }
244
266
  const hasExplicitAfterSeq = 'after_seq' in p || 'after_message_seq' in p;
245
267
  const cursorParams = client._explicitGroupCursorParams(p);
246
268
  const ownsCursor = Object.keys(cursorParams).length === 0 || client._groupCursorTargetsCurrentInstance(cursorParams);
247
269
  const pullOpts = { gateLocked: true };
248
270
  if (hasExplicitAfterSeq)
249
271
  pullOpts.explicitAfterSeq = true;
272
+ if (p.force === true)
273
+ pullOpts.force = true;
250
274
  if (Object.keys(cursorParams).length > 0)
251
275
  pullOpts.cursorParams = cursorParams;
252
276
  if (!ownsCursor)
253
277
  pullOpts.ownsCursor = false;
254
278
  if (String(p.group_id ?? '').trim())
255
279
  pullOpts.wireGroupId = String(p.group_id).trim();
256
- const messages = await runWithRpcPriority(() => client._pullGroupV2(String(p.group_aid ?? p.group_id), Number(p.after_seq ?? p.after_message_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, pullOpts));
280
+ if (String(p.group_aid ?? '').trim())
281
+ pullOpts.groupAid = String(p.group_aid).trim();
282
+ const messages = await runWithPullResponseScope(() => runWithRpcPriority(() => client._pullGroupV2(String(p.group_id ?? p.group_aid), Number(p.after_seq ?? p.after_message_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, pullOpts)));
257
283
  return { messages };
258
284
  }
285
+ if (method === 'group.history') {
286
+ if (!String(p.group_id ?? p.group_aid ?? '').trim()) {
287
+ throw new ValidationError('group.history requires group_id');
288
+ }
289
+ await client._ensureV2SessionReady('group.history');
290
+ return await client._v2E2EE.historyGroupV2Internal(p);
291
+ }
259
292
  if (method === 'group.ack_messages' || method === 'group.v2.ack') {
260
293
  if (!String(p.group_id ?? '').trim()) {
261
294
  throw new ValidationError('group.ack_messages requires group_id');
@@ -266,7 +299,7 @@ export class RpcPipeline {
266
299
  if (method === 'group.ack_messages' && !ownsCursor) {
267
300
  return await runWithRpcPriority(() => client._rawGroupAckMessages(p));
268
301
  }
269
- return await runWithRpcPriority(() => client._ackGroupV2(String(p.group_aid ?? p.group_id), Number(p.seq ?? p.msg_seq ?? p.up_to_seq ?? 0) || undefined));
302
+ return await runWithRpcPriority(() => client._ackGroupV2(String(p.group_id ?? p.group_aid), Number(p.seq ?? p.msg_seq ?? p.up_to_seq ?? 0) || undefined, String(p.group_aid ?? '').trim() || undefined));
270
303
  }
271
304
  if (method === 'message.pull') {
272
305
  delete p._skip_auto_ack;
@@ -285,9 +318,10 @@ export class RpcPipeline {
285
318
  : (rpcBackground
286
319
  ? await client._transport.call(method, p, undefined, undefined, true)
287
320
  : await client._transport.call(method, p));
321
+ const sendResultModeSource = result;
288
322
  result = await this.postprocessResult(method, p, result);
289
323
  if (!skipSendResultEnvelope) {
290
- result = client._delivery.attachSendResultEnvelope(method, p, result, Boolean(p.encrypted));
324
+ result = client._delivery.attachSendResultEnvelope(method, p, result, Boolean(p.encrypted), sendResultModeSource);
291
325
  }
292
326
  client._clientLog.debug(`call exit: method=${method} elapsed=${Date.now() - tStart}ms`);
293
327
  return result;
@@ -482,25 +516,159 @@ export class RpcPipeline {
482
516
  // ── pull-gate ──────────────────────────────────────────────────────────────
483
517
  pullGateKeyForCall(method, params) {
484
518
  const client = this.runtime.client;
485
- if (method === 'message.pull' || method === 'message.v2.pull') {
486
- return client._aid ? `p2p:${client._aid}` : '';
487
- }
488
- if ((method === 'group.pull' || method === 'group.v2.pull') && String(params.group_aid ?? params.group_id ?? '').trim()) {
489
- return `group:${String(params.group_aid ?? params.group_id ?? '').trim()}`;
519
+ if (method === 'message.pull' || method === 'message.v2.pull' || method === 'message.history') {
520
+ if (!client._aid)
521
+ return '';
522
+ let mode;
523
+ let cursor;
524
+ if (method === 'message.history') {
525
+ mode = 'history';
526
+ cursor = `before=${String(params.before_seq)}`;
527
+ }
528
+ else if (params.window_mode === 'tail') {
529
+ mode = 'tail';
530
+ cursor = `after=${String(params.after_seq ?? 0)}`;
531
+ }
532
+ else {
533
+ mode = 'forward';
534
+ cursor = `after=${String(params.after_seq ?? 0)}|force=${String(Boolean(params.force))}`;
535
+ }
536
+ return `p2p:${client._aid}|${mode}|${cursor}|limit=${String(params.limit)}`;
537
+ }
538
+ if (method === 'group.pull' || method === 'group.v2.pull' || method === 'group.history') {
539
+ const groupId = String(params.group_id ?? params.group_aid ?? '').trim();
540
+ if (!groupId)
541
+ return '';
542
+ let mode;
543
+ let cursor;
544
+ if (method === 'group.history') {
545
+ mode = 'history';
546
+ cursor = `before=${String(params.before_seq)}`;
547
+ }
548
+ else if (params.window_mode === 'tail') {
549
+ mode = 'tail';
550
+ cursor = `after=${String(params.after_seq ?? params.after_message_seq ?? 0)}`;
551
+ }
552
+ else {
553
+ mode = 'forward';
554
+ cursor = `after=${String(params.after_seq ?? params.after_message_seq ?? 0)}|force=${String(Boolean(params.force))}`;
555
+ }
556
+ return `group:${groupId}|${mode}|${cursor}|limit=${String(params.limit)}`;
490
557
  }
491
- if (method === 'group.pull_events' && String(params.group_aid ?? params.group_id ?? '').trim()) {
492
- return `group_event:${String(params.group_aid ?? params.group_id ?? '').trim()}`;
558
+ if (method === 'group.pull_events' && String(params.group_id ?? params.group_aid ?? '').trim()) {
559
+ const groupId = String(params.group_id ?? params.group_aid ?? '').trim();
560
+ return `group_event:${groupId}|forward|after=${String(params.after_event_seq ?? 0)}|limit=${String(params.limit)}`;
493
561
  }
494
562
  return '';
495
563
  }
564
+ pullGateBackgroundForCall(method, params) {
565
+ if (method === 'message.history' || method === 'group.history')
566
+ return false;
567
+ if (method === 'message.pull' || method === 'message.v2.pull' || method === 'group.pull' || method === 'group.v2.pull') {
568
+ return params.window_mode !== 'tail';
569
+ }
570
+ return true;
571
+ }
572
+ pullGateName(key) {
573
+ if (key.startsWith('p2p:'))
574
+ return 'p2p_message';
575
+ if (key.startsWith('group:'))
576
+ return 'group_message';
577
+ if (key.startsWith('group_event:'))
578
+ return 'group_event';
579
+ return `other:${key}`;
580
+ }
581
+ pullGateState(key) {
582
+ const name = this.pullGateName(key);
583
+ let state = this.pullGateStates.get(name);
584
+ if (!state) {
585
+ state = { active: null, foreground: [], background: [], byKey: new Map() };
586
+ this.pullGateStates.set(name, state);
587
+ }
588
+ return state;
589
+ }
590
+ pullScopeKey(key) {
591
+ return key.split('|', 1)[0] ?? key;
592
+ }
593
+ hasInlineRealtimeGuard(namespace) {
594
+ const ns = String(namespace ?? '').trim();
595
+ return Boolean(ns) && this.inlineRealtimeScopes.has(ns);
596
+ }
597
+ async executeBypassedPull(key, operation, background) {
598
+ const scope = this.pullScopeKey(key);
599
+ this.bypassedPullScopes.set(scope, (this.bypassedPullScopes.get(scope) ?? 0) + 1);
600
+ try {
601
+ return await this.executePullOperation(operation, background);
602
+ }
603
+ finally {
604
+ const remaining = (this.bypassedPullScopes.get(scope) ?? 1) - 1;
605
+ if (remaining <= 0) {
606
+ this.bypassedPullScopes.delete(scope);
607
+ }
608
+ else {
609
+ this.bypassedPullScopes.set(scope, remaining);
610
+ }
611
+ }
612
+ }
496
613
  isPullResponseProcessing(key) {
497
614
  if (!key)
498
615
  return false;
499
- return (this.runtime.client._pullResponseKeys.get(key) ?? 0) > 0;
616
+ return (this.runtime.client._pullResponseKeys.get(this.pullScopeKey(key)) ?? 0) > 0;
617
+ }
618
+ hasPullActivity(namespace) {
619
+ const ns = String(namespace ?? '').trim();
620
+ if ((!ns.startsWith('p2p:') && !ns.startsWith('group:')) || this.hasInlineRealtimeGuard(ns))
621
+ return true;
622
+ const client = this.runtime.client;
623
+ if ((client._pullResponseKeys.get(ns) ?? 0) > 0)
624
+ return true;
625
+ if (Number(client._pendingP2pPullUpper?.get?.(ns) ?? 0) > 0)
626
+ return true;
627
+ if ((this.bypassedPullScopes.get(ns) ?? 0) > 0)
628
+ return true;
629
+ for (const key of client._gapFillDone?.keys?.() ?? []) {
630
+ const marker = String(key ?? '').trim();
631
+ if (ns.startsWith('p2p:') && marker.startsWith('p2p:'))
632
+ return true;
633
+ if (ns.startsWith('group:') && marker.startsWith(`group_msg:${ns.slice('group:'.length)}:`))
634
+ return true;
635
+ }
636
+ for (const [key, gate] of client._pullGates?.entries?.() ?? []) {
637
+ if (gate?.inflight && this.pullScopeKey(key) === ns)
638
+ return true;
639
+ }
640
+ for (const state of this.pullGateStates.values()) {
641
+ const jobs = [state.active, ...state.foreground, ...state.background];
642
+ if (jobs.some((job) => job !== null && this.pullScopeKey(job.key) === ns))
643
+ return true;
644
+ }
645
+ return false;
646
+ }
647
+ async tryRunInlineRealtime(namespace, seq, operation) {
648
+ const ns = String(namespace ?? '').trim();
649
+ if ((!ns.startsWith('p2p:') && !ns.startsWith('group:'))
650
+ || !Number.isSafeInteger(seq) || seq <= 0 || typeof operation !== 'function'
651
+ || this.hasPullActivity(ns)) {
652
+ return { acquired: false };
653
+ }
654
+ this.inlineRealtimeScopes.add(ns);
655
+ try {
656
+ return { acquired: true, value: await operation() };
657
+ }
658
+ finally {
659
+ this.inlineRealtimeScopes.delete(ns);
660
+ const state = this.pullGateStates.get(this.pullGateName(ns));
661
+ if (state) {
662
+ this.drainPullGate(state);
663
+ this.armQueuedPullTimers(state);
664
+ }
665
+ }
500
666
  }
501
667
  tryAcquirePullGate(key) {
502
668
  if (!key)
503
669
  return 0;
670
+ if (this.hasInlineRealtimeGuard(this.pullScopeKey(key)))
671
+ return null;
504
672
  const client = this.runtime.client;
505
673
  const now = Date.now();
506
674
  const gate = client._pullGates.get(key) ?? { inflight: false, startedAt: 0, token: 0 };
@@ -529,27 +697,123 @@ export class RpcPipeline {
529
697
  client._schedulePendingP2pPullIfNeeded(key, 'pull-gate-release');
530
698
  }
531
699
  }
532
- async runPullSerialized(key, operation) {
700
+ async runPullSerialized(key, operation, background = false) {
533
701
  if (key && this.isPullResponseProcessing(key)) {
534
702
  this.runtime.client._clientLog.debug(`pull skipped while processing pull response: key=${key}`);
535
703
  return [];
536
704
  }
537
- let token = this.tryAcquirePullGate(key);
538
- if (token === null) {
539
- const deadline = Date.now() + PULL_GATE_STALE_MS + 100;
540
- while (token === null && Date.now() <= deadline) {
541
- await this.runtime.client._sleep(25);
542
- token = this.tryAcquirePullGate(key);
543
- }
544
- if (token === null) {
545
- throw new StateError(`pull already in-flight for ${key}`);
546
- }
547
- }
548
- try {
705
+ if (!key)
706
+ return await this.executePullOperation(operation, background);
707
+ const state = this.pullGateState(key);
708
+ const timeoutMs = Math.max(0, Number(this.runtime.client._PULL_GATE_QUEUE_TIMEOUT_MS ?? 3_000) || 0);
709
+ const existing = state.byKey.get(key);
710
+ if (existing) {
711
+ this.runtime.client._clientLog.debug(`pull gate coalesced: gate=${this.pullGateName(key)} key=${key} background=${String(background)}`);
712
+ if (this.hasInlineRealtimeGuard(this.pullScopeKey(key)))
713
+ return await existing.promise;
714
+ return await this.waitForCoalesced(existing.promise, timeoutMs, operation, background, key);
715
+ }
716
+ let resolve;
717
+ let reject;
718
+ const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
719
+ const job = {
720
+ key,
721
+ background,
722
+ enqueuedAt: Date.now(),
723
+ operation,
724
+ promise,
725
+ resolve,
726
+ reject,
727
+ timer: null,
728
+ };
729
+ const queue = background ? state.background : state.foreground;
730
+ queue.push(job);
731
+ state.byKey.set(key, job);
732
+ this.drainPullGate(state);
733
+ if (state.active !== job && !this.hasInlineRealtimeGuard(this.pullScopeKey(job.key))) {
734
+ job.timer = setTimeout(() => this.bypassQueuedPull(state, job), timeoutMs);
735
+ }
736
+ return await promise;
737
+ }
738
+ async executePullOperation(operation, background) {
739
+ if (background)
549
740
  return await this.runtime.client._withBackgroundRpc(operation);
741
+ return await operation();
742
+ }
743
+ waitForCoalesced(shared, timeoutMs, operation, background, key) {
744
+ return new Promise((resolve, reject) => {
745
+ let settled = false;
746
+ const timer = setTimeout(() => {
747
+ if (settled)
748
+ return;
749
+ settled = true;
750
+ this.runtime.client._clientLog.warn(`pull gate coalesced wait timeout, bypass: gate=${this.pullGateName(key)} key=${key} timeout=${timeoutMs}ms`);
751
+ void this.executeBypassedPull(key, operation, background).then(resolve, reject);
752
+ }, timeoutMs);
753
+ shared.then((value) => {
754
+ if (settled)
755
+ return;
756
+ settled = true;
757
+ clearTimeout(timer);
758
+ resolve(value);
759
+ }, (error) => {
760
+ if (settled)
761
+ return;
762
+ settled = true;
763
+ clearTimeout(timer);
764
+ reject(error);
765
+ });
766
+ });
767
+ }
768
+ bypassQueuedPull(state, job) {
769
+ if (state.active === job)
770
+ return;
771
+ if (this.hasInlineRealtimeGuard(this.pullScopeKey(job.key))) {
772
+ job.timer = null;
773
+ return;
550
774
  }
551
- finally {
552
- this.releasePullGate(key, token);
775
+ const queue = job.background ? state.background : state.foreground;
776
+ const index = queue.indexOf(job);
777
+ if (index < 0)
778
+ return;
779
+ queue.splice(index, 1);
780
+ if (state.byKey.get(job.key) === job)
781
+ state.byKey.delete(job.key);
782
+ job.timer = null;
783
+ this.runtime.client._clientLog.warn(`pull gate queue timeout, bypass: gate=${this.pullGateName(job.key)} key=${job.key} background=${String(job.background)}`);
784
+ void this.executeBypassedPull(job.key, job.operation, job.background).then(job.resolve, job.reject);
785
+ }
786
+ drainPullGate(state) {
787
+ if (state.active)
788
+ return;
789
+ const takeRunnable = (queue) => {
790
+ const index = queue.findIndex((candidate) => !this.hasInlineRealtimeGuard(this.pullScopeKey(candidate.key)));
791
+ return index >= 0 ? queue.splice(index, 1)[0] : undefined;
792
+ };
793
+ const job = takeRunnable(state.foreground) ?? takeRunnable(state.background);
794
+ if (!job)
795
+ return;
796
+ state.active = job;
797
+ if (job.timer) {
798
+ clearTimeout(job.timer);
799
+ job.timer = null;
800
+ }
801
+ void this.executePullOperation(job.operation, job.background)
802
+ .then(job.resolve, job.reject)
803
+ .finally(() => {
804
+ if (state.active === job)
805
+ state.active = null;
806
+ if (state.byKey.get(job.key) === job)
807
+ state.byKey.delete(job.key);
808
+ this.drainPullGate(state);
809
+ });
810
+ }
811
+ armQueuedPullTimers(state) {
812
+ const timeoutMs = Math.max(0, Number(this.runtime.client._PULL_GATE_QUEUE_TIMEOUT_MS ?? 3_000) || 0);
813
+ for (const job of [...state.foreground, ...state.background]) {
814
+ if (!job.timer && !this.hasInlineRealtimeGuard(this.pullScopeKey(job.key))) {
815
+ job.timer = setTimeout(() => this.bypassQueuedPull(state, job), timeoutMs);
816
+ }
553
817
  }
554
818
  }
555
819
  // ── raw-call ───────────────────────────────────────────────────────────────
@@ -575,6 +839,12 @@ export class RpcPipeline {
575
839
  async postprocessResult(method, params, result) {
576
840
  const client = this.runtime.client;
577
841
  let next = result;
842
+ if ((method === 'group.send' || method === 'group.v2.send') && isUnknownJsonObject(next)) {
843
+ next = normalizeGroupMentionMode(next);
844
+ }
845
+ if (method === 'group.get_settings' || method === 'group.set_settings') {
846
+ next = normalizeGroupSettingsResponse(next);
847
+ }
578
848
  if (method === 'group.thought.get' && isUnknownJsonObject(next)) {
579
849
  client._clientLog?.debug?.(`group.thought.get transport result: found=${String(next.found ?? '')}, raw_count=${Array.isArray(next.thoughts) ? next.thoughts.length : 0}`);
580
850
  next = await client._decryptGroupThoughts(next);
@@ -605,12 +875,12 @@ export class RpcPipeline {
605
875
  if (rawMessages.length) {
606
876
  client._seqTracker.onPullResult(ns, rawMessages, Number(params.after_seq ?? 0) || 0);
607
877
  }
608
- const serverAck = Number(result.server_ack_seq ?? 0);
609
- if (serverAck > 0) {
878
+ const retentionFloor = Math.max(0, Number(result.retention_floor_seq ?? 0));
879
+ if (retentionFloor > 0) {
610
880
  const contig = client._seqTracker.getContiguousSeq(ns);
611
- if (contig < serverAck) {
612
- client._clientLog?.info?.(`message.pull retention-floor advance: ns=${ns} contiguous=${contig} -> server_ack_seq=${serverAck}`);
613
- client._seqTracker.forceContiguousSeq(ns, serverAck);
881
+ if (contig < retentionFloor) {
882
+ client._clientLog?.info?.(`message.pull retention-floor advance: ns=${ns} contiguous=${contig} -> retention_floor_seq=${retentionFloor}`);
883
+ client._seqTracker.forceContiguousSeq(ns, retentionFloor);
614
884
  }
615
885
  }
616
886
  if (client._seqTracker.getContiguousSeq(ns) !== contigBefore) {
@@ -620,7 +890,10 @@ export class RpcPipeline {
620
890
  }
621
891
  postprocessGroupPull(params, result) {
622
892
  const client = this.runtime.client;
623
- const gid = String(params.group_aid ?? params.group_id ?? '').trim();
893
+ if (Array.isArray(result.messages)) {
894
+ result.messages = result.messages.map((message) => normalizeGroupMentionMode(message));
895
+ }
896
+ const gid = String(params.group_id ?? params.group_aid ?? '').trim();
624
897
  if (!gid || !client._seqTracker) {
625
898
  return;
626
899
  }
@@ -632,12 +905,15 @@ export class RpcPipeline {
632
905
  client._seqTracker.onPullResult(ns, rawMessages, Number(params.after_message_seq ?? params.after_seq ?? 0) || 0);
633
906
  }
634
907
  const cursor = isJsonObject(result.cursor) ? result.cursor : null;
635
- const serverAck = Number(cursor?.current_seq ?? 0);
636
- if (serverAck > 0) {
908
+ const retentionFloor = Math.max(0, Number(result.retention_floor_message_seq ?? result.retention_floor_seq ?? 0), Number(cursor?.retention_floor_seq ?? 0));
909
+ const isTail = String(result.window_mode ?? params.window_mode ?? '').trim().toLowerCase() === 'tail';
910
+ const visibilityFloor = isTail ? 0 : Math.max(0, Math.max(0, Number(result.earliest_available_message_seq ?? 1) - 1), Number(cursor?.join_seq ?? 0));
911
+ const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
912
+ if (effectiveFloor > 0) {
637
913
  const contig = client._seqTracker.getContiguousSeq(ns);
638
- if (contig < serverAck) {
639
- client._clientLog?.info?.(`group.pull retention-floor advance: ns=${ns} contiguous=${contig} -> cursor.current_seq=${serverAck}`);
640
- client._seqTracker.forceContiguousSeq(ns, serverAck);
914
+ if (contig < effectiveFloor) {
915
+ client._clientLog?.info?.(`group.pull floor advance: ns=${ns} contiguous=${contig} -> effective_floor=${effectiveFloor} retention_floor=${retentionFloor} visibility_floor=${visibilityFloor} mode=${isTail ? 'tail' : 'forward'}`);
916
+ client._seqTracker.forceContiguousSeq(ns, effectiveFloor);
641
917
  }
642
918
  }
643
919
  if (client._seqTracker.getContiguousSeq(ns) !== contigBefore) {
@@ -674,33 +950,16 @@ export class RpcPipeline {
674
950
  return;
675
951
  }
676
952
  const record = params;
677
- const rawEntries = [
678
- ['group_aid', record.group_aid],
679
- ['groupAid', record.groupAid],
680
- ['group_id', record.group_id],
681
- ['groupId', record.groupId],
682
- ];
683
- const found = rawEntries.find(([, value]) => String(value ?? '').trim());
684
- if (found) {
685
- const [key, raw] = found;
686
- const rawGroupId = String(raw).trim();
687
- const normalizedGroupId = normalizeGroupId(rawGroupId);
688
- if (normalizedGroupId) {
689
- if (normalizedGroupId !== rawGroupId) {
690
- this.runtime.client._clientLog?.debug?.(`call group identifier normalized: ${key}=${rawGroupId} -> ${normalizedGroupId} method=${method}`);
691
- }
692
- const existingGroupId = String(record.group_id ?? record.groupId ?? '').trim();
693
- if (key === 'group_id' || key === 'groupId') {
694
- params.group_id = rawGroupId;
695
- }
696
- else if (existingGroupId) {
697
- params.group_id = existingGroupId;
698
- }
699
- else {
700
- params.group_id = normalizedGroupId;
701
- }
702
- params.group_aid = normalizedGroupId;
703
- }
953
+ const rawGroupId = String(record.group_id ?? record.groupId ?? '').trim();
954
+ const rawGroupAid = String(record.group_aid ?? record.groupAid ?? '').trim();
955
+ if (rawGroupId) {
956
+ params.group_id = rawGroupId;
957
+ }
958
+ else if (rawGroupAid) {
959
+ params.group_id = rawGroupAid;
960
+ }
961
+ if (rawGroupAid) {
962
+ params.group_aid = rawGroupAid;
704
963
  }
705
964
  delete record.groupAid;
706
965
  delete record.groupId;