@agentunion/fastaun 0.5.32 → 0.5.34

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 (97) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +28 -26
  3. package/_packed_docs/CHANGELOG.md +58 -0
  4. package/_packed_docs/INDEX.md +17 -3
  5. package/_packed_docs/KITE_DOCS_GUIDE.md +10 -4
  6. package/_packed_docs/protocol/03-Gateway-/350/277/236/346/216/245/346/250/241/345/274/217.md +58 -8
  7. package/_packed_docs/sdk/0.5.33-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +464 -0
  8. package/_packed_docs/sdk/0.5.33-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +499 -0
  9. package/_packed_docs/sdk/0.5.34-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +133 -0
  10. package/_packed_docs/sdk/0.5.34-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +107 -0
  11. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +16 -13
  12. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +13 -1
  13. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +17 -7
  14. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +55 -14
  15. package/_packed_docs/sdk/09-collab-rpc-manual.md +48 -30
  16. package/_packed_docs/sdk/09-custody-api-manual.md +3 -3
  17. package/_packed_docs/sdk/09-group-rpc-manual.md +53 -6
  18. package/_packed_docs/sdk/09-message-rpc-manual.md +22 -0
  19. package/_packed_docs/sdk/09-meta-rpc-manual.md +2 -1
  20. package/_packed_docs/sdk/09-payload-reference.md +9 -8
  21. package/_packed_docs/sdk/09-proxy-rpc-manual.md +20 -14
  22. package/_packed_docs/sdk/09-storage-rpc-manual.md +1698 -1642
  23. package/_packed_docs/sdk/09-stream-rpc-manual.md +33 -20
  24. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +11 -9
  25. package/_packed_docs/sdk/AUN/344/272/213/344/273/266/345/217/221/345/270/203/350/256/242/351/230/205/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +53 -28
  26. package/_packed_docs/sdk/AUN/344/272/213/344/273/266/345/217/221/345/270/203/350/256/242/351/230/205/345/274/200/345/217/221/347/247/273/346/244/215/346/214/207/345/215/227.md +62 -28
  27. package/_packed_docs/sdk/INDEX.md +21 -9
  28. package/_packed_docs/sdk/README.md +11 -6
  29. package/dist/agent-md.d.ts +2 -1
  30. package/dist/agent-md.js +17 -0
  31. package/dist/agent-md.js.map +1 -1
  32. package/dist/auth.js +8 -2
  33. package/dist/auth.js.map +1 -1
  34. package/dist/client/control-messages.js +4 -4
  35. package/dist/client/control-messages.js.map +1 -1
  36. package/dist/client/delivery.d.ts +5 -0
  37. package/dist/client/delivery.js +146 -32
  38. package/dist/client/delivery.js.map +1 -1
  39. package/dist/client/lifecycle.js +3 -0
  40. package/dist/client/lifecycle.js.map +1 -1
  41. package/dist/client/rpc-budget.d.ts +21 -0
  42. package/dist/client/rpc-budget.js +82 -0
  43. package/dist/client/rpc-budget.js.map +1 -0
  44. package/dist/client/rpc-pipeline.d.ts +3 -0
  45. package/dist/client/rpc-pipeline.js +193 -65
  46. package/dist/client/rpc-pipeline.js.map +1 -1
  47. package/dist/client/sync-channels.d.ts +89 -0
  48. package/dist/client/sync-channels.js +489 -0
  49. package/dist/client/sync-channels.js.map +1 -0
  50. package/dist/client/v2-e2ee.d.ts +4 -1
  51. package/dist/client/v2-e2ee.js +401 -298
  52. package/dist/client/v2-e2ee.js.map +1 -1
  53. package/dist/client.d.ts +9 -4
  54. package/dist/client.js +125 -15
  55. package/dist/client.js.map +1 -1
  56. package/dist/index.d.ts +2 -2
  57. package/dist/index.js.map +1 -1
  58. package/dist/ownership-workflow.d.ts +23 -3
  59. package/dist/ownership-workflow.js +36 -13
  60. package/dist/ownership-workflow.js.map +1 -1
  61. package/dist/pubsub/binding.js +14 -1
  62. package/dist/pubsub/binding.js.map +1 -1
  63. package/dist/pubsub/channel.d.ts +4 -0
  64. package/dist/pubsub/channel.js +26 -0
  65. package/dist/pubsub/channel.js.map +1 -1
  66. package/dist/pubsub/client.d.ts +9 -0
  67. package/dist/pubsub/client.js +154 -31
  68. package/dist/pubsub/client.js.map +1 -1
  69. package/dist/pubsub/credentials.d.ts +3 -0
  70. package/dist/pubsub/credentials.js +57 -20
  71. package/dist/pubsub/credentials.js.map +1 -1
  72. package/dist/pubsub/index.d.ts +3 -0
  73. package/dist/pubsub/index.js +69 -53
  74. package/dist/pubsub/index.js.map +1 -1
  75. package/dist/pubsub/service.d.ts +3 -0
  76. package/dist/pubsub/service.js +167 -12
  77. package/dist/pubsub/service.js.map +1 -1
  78. package/dist/pubsub/types.d.ts +2 -0
  79. package/dist/storage/lowlevel.d.ts +7 -3
  80. package/dist/storage/lowlevel.js +15 -0
  81. package/dist/storage/lowlevel.js.map +1 -1
  82. package/dist/storage/read-cache.js +12 -0
  83. package/dist/storage/read-cache.js.map +1 -1
  84. package/dist/storage/types.d.ts +10 -0
  85. package/dist/storage/types.js.map +1 -1
  86. package/dist/storage/vfs.d.ts +7 -3
  87. package/dist/storage/vfs.js +2 -2
  88. package/dist/storage/vfs.js.map +1 -1
  89. package/dist/tools/cross-sdk-agent.d.ts +62 -6
  90. package/dist/tools/cross-sdk-agent.js +169 -37
  91. package/dist/tools/cross-sdk-agent.js.map +1 -1
  92. package/dist/transport.d.ts +12 -0
  93. package/dist/transport.js +91 -6
  94. package/dist/transport.js.map +1 -1
  95. package/dist/version.d.ts +1 -1
  96. package/dist/version.js +1 -1
  97. package/package.json +1 -1
@@ -166,6 +166,7 @@ export class RpcPipeline {
166
166
  inlineRealtimeScopes = new Set();
167
167
  pullInvalidationWait = null;
168
168
  pullDrainedCheckQueued = false;
169
+ backgroundAcks = new Map();
169
170
  constructor(runtime) {
170
171
  this.runtime = runtime;
171
172
  }
@@ -232,7 +233,7 @@ export class RpcPipeline {
232
233
  const sender = String(p.sender_aid ?? '').trim();
233
234
  if (!sender)
234
235
  throw new ValidationError('peer ACK requires sender_aid');
235
- return await client._v2E2EE.ackPeerV2(sender, requestedAckSeq || undefined, Array.isArray(p.volatile_cursors) ? p.volatile_cursors : [], Array.isArray(p.volatile_ids) ? p.volatile_ids : []);
236
+ return await client._v2E2EE.ackPeerV2(sender, requestedAckSeq || undefined, Array.isArray(p.volatile_cursors) ? p.volatile_cursors : [], Array.isArray(p.volatile_ids) ? p.volatile_ids : [], rpcBackground);
236
237
  }
237
238
  const runWithPullResponseScope = async (operation) => {
238
239
  const scope = pullGateNamespace || (pullGateKey ? this.pullScopeKey(pullGateKey) : '');
@@ -415,15 +416,18 @@ export class RpcPipeline {
415
416
  this.applyClientSignature(method, p);
416
417
  assertCurrent?.();
417
418
  const transportParams = deadline === undefined ? p : { ...p, _rpc_deadline: deadline };
418
- const request = onSent
419
- ? client._transport['_callWithSent'](method, transportParams, onSent, remaining, undefined, rpcBackground)
420
- : remaining !== undefined
421
- ? (rpcBackground
422
- ? client._transport.call(method, transportParams, remaining, undefined, rpcBackground)
423
- : client._transport.call(method, transportParams, remaining))
424
- : (rpcBackground
425
- ? client._transport.call(method, transportParams, undefined, undefined, rpcBackground)
426
- : client._transport.call(method, transportParams));
419
+ const history = !onSent && client._historyChannels?.().isHistory(method, transportParams, rpcBackground);
420
+ const request = history
421
+ ? client._historyChannels().call(method, transportParams, remaining, undefined, rpcBackground, (prepared) => this.applyClientSignature(method, prepared))
422
+ : onSent
423
+ ? client._transport['_callWithSent'](method, transportParams, onSent, remaining, undefined, rpcBackground)
424
+ : remaining !== undefined
425
+ ? (rpcBackground
426
+ ? client._transport.call(method, transportParams, remaining, undefined, rpcBackground)
427
+ : client._transport.call(method, transportParams, remaining))
428
+ : (rpcBackground
429
+ ? client._transport.call(method, transportParams, undefined, undefined, rpcBackground)
430
+ : client._transport.call(method, transportParams));
427
431
  return this.bindActivePullCancellation(method, p, request);
428
432
  };
429
433
  let result = await this.transportCallWithIdentityRecovery(invokeTransport, method, p);
@@ -528,14 +532,30 @@ export class RpcPipeline {
528
532
  throw new ValidationError('message_class must be normal, control or sdk_control');
529
533
  }
530
534
  if (['message.send', 'group.send', 'group.v2.send'].includes(method)) {
531
- const messageClass = Object.hasOwn(params, 'message_class') ? params.message_class : 'normal';
535
+ let messageClass = Object.hasOwn(params, 'message_class') ? params.message_class : 'normal';
532
536
  if (typeof messageClass !== 'string' || !['normal', 'control', 'sdk_control'].includes(messageClass)) {
533
537
  throw new ValidationError('message_class must be normal, control or sdk_control');
534
538
  }
539
+ const classes = this.runtime.client._negotiatedCapabilities?.message_classes_v2?.[method.startsWith('group.') ? 'group' : 'p2p'];
540
+ if (messageClass === 'control' && (!Array.isArray(classes) || !classes.includes('control'))) {
541
+ const protectedHeaders = this.runtime.client._protectedHeadersFromParams(params) ?? {};
542
+ if (protectedHeaders.message_class !== undefined && protectedHeaders.message_class !== messageClass) {
543
+ throw new ValidationError('protected message_class differs from the requested class');
544
+ }
545
+ delete params.message_class;
546
+ for (const field of ['protected_headers', 'headers']) {
547
+ if (isJsonObject(params[field])) {
548
+ const headers = { ...params[field] };
549
+ delete headers.message_class;
550
+ params[field] = headers;
551
+ }
552
+ }
553
+ messageClass = 'normal';
554
+ }
535
555
  if (method.startsWith('group.') && messageClass === 'sdk_control') {
536
556
  throw new StateError('SDK control messages are not supported for groups', { stringCode: 'unsupported_protocol' });
537
557
  }
538
- if (method.startsWith('group.') && Number(params.ttl) > 0
558
+ if (method.startsWith('group.') && messageClass !== 'normal' && Number(params.ttl) > 0
539
559
  && !this.runtime.client._controlMessages().supports(messageClass, true)) {
540
560
  throw new StateError('volatile group delivery protocol has not been negotiated', { stringCode: 'unsupported_protocol' });
541
561
  }
@@ -546,6 +566,9 @@ export class RpcPipeline {
546
566
  }
547
567
  params.protected_headers = { ...protectedHeaders, message_class: messageClass };
548
568
  if (messageClass === 'sdk_control') {
569
+ if (params.encrypt === false && (!isJsonObject(params.payload) || params.payload.type !== 'e2ee.p2p_encrypted')) {
570
+ throw new ValidationError('SDK control messages require E2EE');
571
+ }
549
572
  const negotiated = this.runtime.client._negotiatedCapabilities ?? {};
550
573
  const scope = method.startsWith('group.') ? 'group' : 'p2p';
551
574
  const classes = negotiated.message_classes_v2?.[scope];
@@ -556,9 +579,12 @@ export class RpcPipeline {
556
579
  throw new ValidationError('protected SDK control protocol differs from the request');
557
580
  }
558
581
  params.protected_headers = { ...params.protected_headers, sdk_control_protocol: protocol, sdk_control_version: version };
559
- if (!Array.isArray(classes) || !classes.includes('sdk_control') || !protocol || !Number.isSafeInteger(version)
560
- || typeof protocol !== 'string' || !Array.isArray(negotiated.sdk_control_protocols?.[protocol]) || !negotiated.sdk_control_protocols[protocol].includes(version)) {
561
- throw new StateError('SDK control protocol has not been negotiated', { stringCode: 'unsupported_protocol' });
582
+ if (!protocol || typeof protocol !== 'string' || !Number.isSafeInteger(version) || Number(version) <= 0) {
583
+ throw new ValidationError('SDK control requires a protocol and positive integer version');
584
+ }
585
+ if (!Array.isArray(classes) || !classes.includes('sdk_control')
586
+ || !Array.isArray(negotiated.sdk_control_protocols?.[protocol]) || !negotiated.sdk_control_protocols[protocol].includes(version)) {
587
+ delete params.message_class;
562
588
  }
563
589
  }
564
590
  }
@@ -891,6 +917,19 @@ export class RpcPipeline {
891
917
  return false;
892
918
  return (this.runtime.client._pullResponseKeys.get(this.pullScopeKey(key)) ?? 0) > 0;
893
919
  }
920
+ pendingHistoryNamespaces() {
921
+ const namespaces = new Set();
922
+ for (const state of this.pullGateStates.values())
923
+ for (const job of state.background) {
924
+ if (job.invalidated || job.timedOut || /(?:mode=history|mode=tail|\|tail\||tail=true|volatile=true|force=true|class=(?:control|sdk_control))/.test(job.key))
925
+ continue;
926
+ if ((this.runtime.client._delivery?.automaticPullRetryAt?.(job.namespace, job.operation) ?? 0) > Date.now())
927
+ continue;
928
+ if (job.namespace.startsWith('p2p:') || job.namespace.startsWith('p2p.peer.v1/') || job.namespace.startsWith('group:') || job.namespace.startsWith('group_event:'))
929
+ namespaces.add(job.namespace);
930
+ }
931
+ return [...namespaces];
932
+ }
894
933
  hasPullActivity(namespace, includePending = true) {
895
934
  const ns = String(namespace ?? '').trim();
896
935
  if ((!ns.startsWith('p2p:') && !ns.startsWith('p2p.peer.v1/') && !ns.startsWith('group:') && !ns.startsWith('message_class/')) || this.hasInlineRealtimeGuard(ns))
@@ -1291,6 +1330,86 @@ export class RpcPipeline {
1291
1330
  armQueuedPullTimers(_state) { }
1292
1331
  // ── raw-call ───────────────────────────────────────────────────────────────
1293
1332
  async rawCall(method, params, opts = {}) {
1333
+ const client = this.runtime.client;
1334
+ const peerAck = method === 'message.ack' && params?.cursor_scope === 'peer';
1335
+ if (peerAck && params?.seq !== undefined) {
1336
+ params = { ...params, up_to_seq: Object.hasOwn(params, 'up_to_seq') ? params.up_to_seq : params.seq };
1337
+ delete params.seq;
1338
+ }
1339
+ const background = params?._rpc_foreground === true ? false : opts.background ?? params?._rpc_background ?? params?._rpc_background_kind;
1340
+ const field = peerAck ? 'up_to_seq' : { 'message.ack': 'seq', 'message.v2.ack': 'up_to_seq', 'group.ack_messages': 'msg_seq', 'group.v2.ack': 'up_to_seq', 'group.ack_events': 'event_seq' }[method];
1341
+ const seq = field ? params?.[field] : undefined;
1342
+ if (!background || !field || typeof seq !== 'number' || !Number.isSafeInteger(seq) || seq <= 0
1343
+ || Object.keys(params ?? {}).some(key => /volatile|token|proof/.test(key)))
1344
+ return this.rawCallDirect(method, params, opts);
1345
+ const scope = () => [client._aid, client._currentAid, client._deviceId, client._slotId, client._gatewayConnectionId,
1346
+ client._delivery?.captureInlineAckGeneration?.(), client._transport, client._transport?._connectionGeneration, client._transport?._workerGeneration];
1347
+ const identity = scope();
1348
+ const range = { ...params };
1349
+ delete range[field];
1350
+ const key = stableStringify([method, range, opts]);
1351
+ const waitForResponse = (active) => new Promise((resolve, reject) => {
1352
+ let timer;
1353
+ const waiter = { seq, resolve: (value) => { clearTimeout(timer); resolve(value); },
1354
+ reject: (error) => { clearTimeout(timer); reject(error); } };
1355
+ active.waiters.push(waiter);
1356
+ const timeout = opts.timeout ?? client._transport?._timeout;
1357
+ const deadline = typeof params?._rpc_deadline === 'number' ? params._rpc_deadline : Infinity;
1358
+ const remaining = Math.min(typeof timeout === 'number' ? timeout : Infinity, deadline - Date.now());
1359
+ if (Number.isFinite(remaining))
1360
+ timer = setTimeout(() => {
1361
+ active.waiters = active.waiters.filter(item => item !== waiter);
1362
+ waiter.reject(new TimeoutError(`rpc timeout: ${method}`, { retryable: true }));
1363
+ }, Math.max(0, remaining));
1364
+ });
1365
+ let job = this.backgroundAcks.get(key);
1366
+ if (job && !job.scope.every((value, index) => value === identity[index]))
1367
+ job = undefined;
1368
+ if (job) {
1369
+ return waitForResponse(job);
1370
+ }
1371
+ job = { scope: identity, params: { ...params }, waiters: [] };
1372
+ const active = job;
1373
+ const response = waitForResponse(active);
1374
+ this.backgroundAcks.set(key, active);
1375
+ const assertCurrent = () => {
1376
+ const currentScope = scope();
1377
+ if (!active.scope.every((value, index) => value === currentScope[index]))
1378
+ throw new ConnectionError('ACK context changed');
1379
+ };
1380
+ void (async () => {
1381
+ try {
1382
+ while (active.waiters.length > 0) {
1383
+ assertCurrent();
1384
+ const latest = active.waiters.reduce((highest, waiter) => waiter.seq > highest.seq ? waiter : highest);
1385
+ const requested = latest.seq;
1386
+ try {
1387
+ const result = await this.rawCallDirect(method, { ...active.params, [field]: requested }, opts, assertCurrent);
1388
+ const covered = active.waiters.filter(waiter => waiter.seq <= requested);
1389
+ active.waiters = active.waiters.filter(waiter => waiter.seq > requested);
1390
+ for (const waiter of covered)
1391
+ waiter.resolve(result);
1392
+ }
1393
+ catch (error) {
1394
+ const covered = active.waiters.filter(waiter => waiter.seq <= requested);
1395
+ active.waiters = active.waiters.filter(waiter => waiter.seq > requested);
1396
+ for (const waiter of covered)
1397
+ waiter.reject(error);
1398
+ }
1399
+ }
1400
+ }
1401
+ catch (error) {
1402
+ for (const waiter of active.waiters)
1403
+ waiter.reject(error);
1404
+ }
1405
+ finally {
1406
+ if (this.backgroundAcks.get(key) === active)
1407
+ this.backgroundAcks.delete(key);
1408
+ }
1409
+ })();
1410
+ return response;
1411
+ }
1412
+ async rawCallDirect(method, params, opts = {}, assertCurrent) {
1294
1413
  const { timeout, trace, signed = true, background = false, pullGateKey = '' } = opts;
1295
1414
  const client = this.runtime.client;
1296
1415
  if (client._migrationPending)
@@ -1308,8 +1427,11 @@ export class RpcPipeline {
1308
1427
  this.applyClientSignature(method, payload);
1309
1428
  }
1310
1429
  const invokeTransport = () => {
1430
+ assertCurrent?.();
1311
1431
  let request;
1312
- if (rpcBackground)
1432
+ if (client._historyChannels?.().isHistory(method, payload, rpcBackground))
1433
+ request = client._historyChannels().call(method, payload, timeout, trace, rpcBackground, signed ? (prepared) => this.applyClientSignature(method, prepared) : undefined);
1434
+ else if (rpcBackground)
1313
1435
  request = client._transport.call(method, payload, timeout, trace, rpcBackground);
1314
1436
  else if (trace !== undefined)
1315
1437
  request = client._transport.call(method, payload, timeout, trace);
@@ -1342,7 +1464,7 @@ export class RpcPipeline {
1342
1464
  }
1343
1465
  }
1344
1466
  observeBackgroundPullResponse(method, params, result, key = '') {
1345
- if (params.message_class !== undefined && params.message_class !== 'normal' || params.volatile === true)
1467
+ if (params.message_class !== undefined && params.message_class !== 'normal' || params.volatile === true || params.storage_scope === 'volatile')
1346
1468
  return;
1347
1469
  if (!['message.pull', 'message.v2.pull', 'group.pull', 'group.v2.pull', 'group.pull_events'].includes(method)
1348
1470
  || params.force === true || params.window_mode === 'tail')
@@ -1396,59 +1518,65 @@ export class RpcPipeline {
1396
1518
  // ── postprocess ────────────────────────────────────────────────────────────
1397
1519
  async postprocessResult(method, params, result, options = {}) {
1398
1520
  const client = this.runtime.client;
1399
- const pullNamespace = this.pullScopeKey(options.pullNamespace || this.pullGateKeyForCall(method, params));
1400
- this.throwIfPullInvalidated(pullNamespace);
1401
- let next = result;
1402
- if ((method === 'group.send' || method === 'group.v2.send') && isUnknownJsonObject(next)) {
1403
- next = normalizeGroupMentionMode(next);
1404
- }
1405
- if (method === 'group.get_settings' || method === 'group.set_settings') {
1406
- next = normalizeGroupSettingsResponse(next);
1407
- }
1408
- if (method === 'group.thought.get' && isUnknownJsonObject(next)) {
1409
- client._clientLog?.debug?.(`group.thought.get transport result: found=${String(next.found ?? '')}, raw_count=${Array.isArray(next.thoughts) ? next.thoughts.length : 0}`);
1410
- next = await client._decryptGroupThoughts(next);
1411
- }
1412
- if (method === 'message.thought.get' && isUnknownJsonObject(next)) {
1413
- client._clientLog?.debug?.(`message.thought.get transport result: found=${String(next.found ?? '')}, raw_count=${Array.isArray(next.thoughts) ? next.thoughts.length : 0}`);
1414
- next = await client._decryptMessageThoughts(next);
1415
- }
1416
- if (method === 'message.pull' && isUnknownJsonObject(next)) {
1417
- const isTail = String(next.window_mode ?? params.window_mode ?? '').trim().toLowerCase() === 'tail';
1418
- if (!isTail && client._aid && client._seqTracker) {
1419
- const ns = pullNamespace || `p2p:${client._aid}`;
1420
- const afterSeq = Math.max(0, Number(params.after_seq ?? 0) || 0);
1421
- await client._delivery.commitPlainForwardPage(ns, afterSeq, 'message.pull', next);
1422
- }
1423
- else {
1424
- this.postprocessMessagePull(params, next);
1425
- }
1426
- }
1427
- if (method === 'group.pull' && isUnknownJsonObject(next)) {
1428
- if (Array.isArray(next.messages)) {
1429
- next.messages = next.messages.map((message) => normalizeGroupMentionMode(message));
1430
- }
1431
- const gid = String(params.group_id ?? params.group_aid ?? '').trim();
1432
- const explicitCursorParams = this.explicitGroupCursorParams(params);
1433
- const ownsCursor = Object.keys(explicitCursorParams).length === 0
1434
- || this.groupCursorTargetsCurrentInstance(explicitCursorParams);
1435
- const isTail = String(next.window_mode ?? params.window_mode ?? '').trim().toLowerCase() === 'tail';
1436
- if (!ownsCursor) {
1437
- client._clientLog.debug(`group.pull external cursor skips local Forward commit: group=${gid}`);
1521
+ try {
1522
+ const pullNamespace = this.pullScopeKey(options.pullNamespace || this.pullGateKeyForCall(method, params));
1523
+ this.throwIfPullInvalidated(pullNamespace);
1524
+ let next = result;
1525
+ if ((method === 'group.send' || method === 'group.v2.send') && isUnknownJsonObject(next)) {
1526
+ next = normalizeGroupMentionMode(next);
1527
+ }
1528
+ if (method === 'group.get_settings' || method === 'group.set_settings') {
1529
+ next = normalizeGroupSettingsResponse(next);
1530
+ }
1531
+ if (method === 'group.thought.get' && isUnknownJsonObject(next)) {
1532
+ client._clientLog?.debug?.(`group.thought.get transport result: found=${String(next.found ?? '')}, raw_count=${Array.isArray(next.thoughts) ? next.thoughts.length : 0}`);
1533
+ next = await client._decryptGroupThoughts(next);
1534
+ }
1535
+ if (method === 'message.thought.get' && isUnknownJsonObject(next)) {
1536
+ client._clientLog?.debug?.(`message.thought.get transport result: found=${String(next.found ?? '')}, raw_count=${Array.isArray(next.thoughts) ? next.thoughts.length : 0}`);
1537
+ next = await client._decryptMessageThoughts(next);
1538
+ }
1539
+ if (method === 'message.pull' && isUnknownJsonObject(next)) {
1540
+ const isTail = String(next.window_mode ?? params.window_mode ?? '').trim().toLowerCase() === 'tail';
1541
+ if (!isTail && client._aid && client._seqTracker) {
1542
+ const ns = pullNamespace || `p2p:${client._aid}`;
1543
+ const afterSeq = Math.max(0, Number(params.after_seq ?? 0) || 0);
1544
+ await client._delivery.commitPlainForwardPage(ns, afterSeq, 'message.pull', next);
1545
+ }
1546
+ else {
1547
+ this.postprocessMessagePull(params, next);
1548
+ }
1438
1549
  }
1439
- else if (!isTail && gid && client._seqTracker) {
1440
- const ns = pullNamespace || `group:${gid}`;
1441
- const afterSeq = Math.max(0, Number(params.after_message_seq ?? params.after_seq ?? 0) || 0);
1442
- await client._delivery.commitPlainForwardPage(ns, afterSeq, 'group.pull', next, gid);
1550
+ if (method === 'group.pull' && isUnknownJsonObject(next)) {
1551
+ if (Array.isArray(next.messages)) {
1552
+ next.messages = next.messages.map((message) => normalizeGroupMentionMode(message));
1553
+ }
1554
+ const gid = String(params.group_id ?? params.group_aid ?? '').trim();
1555
+ const explicitCursorParams = this.explicitGroupCursorParams(params);
1556
+ const ownsCursor = Object.keys(explicitCursorParams).length === 0
1557
+ || this.groupCursorTargetsCurrentInstance(explicitCursorParams);
1558
+ const isTail = String(next.window_mode ?? params.window_mode ?? '').trim().toLowerCase() === 'tail';
1559
+ if (!ownsCursor) {
1560
+ client._clientLog.debug(`group.pull external cursor skips local Forward commit: group=${gid}`);
1561
+ }
1562
+ else if (!isTail && gid && client._seqTracker) {
1563
+ const ns = pullNamespace || `group:${gid}`;
1564
+ const afterSeq = Math.max(0, Number(params.after_message_seq ?? params.after_seq ?? 0) || 0);
1565
+ await client._delivery.commitPlainForwardPage(ns, afterSeq, 'group.pull', next, gid);
1566
+ }
1567
+ else {
1568
+ this.postprocessGroupPull(params, next);
1569
+ }
1443
1570
  }
1444
- else {
1445
- this.postprocessGroupPull(params, next);
1571
+ if (!options.skipGroupState) {
1572
+ next = await client._groupState.postprocessResult(method, params, next);
1446
1573
  }
1574
+ return next;
1447
1575
  }
1448
- if (!options.skipGroupState) {
1449
- next = await client._groupState.postprocessResult(method, params, next);
1576
+ finally {
1577
+ if (method === 'message.pull' || method === 'group.pull')
1578
+ client._syncChannelCoordinator?.releaseResponse(result);
1450
1579
  }
1451
- return next;
1452
1580
  }
1453
1581
  // ── private helpers ────────────────────────────────────────────────────────
1454
1582
  postprocessMessagePull(params, result) {