@agentunion/fastaun 0.5.19 → 0.5.21

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 (129) hide show
  1. package/CHANGELOG.md +176 -78
  2. package/_packed_docs/CHANGELOG.md +176 -78
  3. package/_packed_docs/INDEX.md +288 -269
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +101 -95
  5. package/_packed_docs/agent.md/SCHEMA.md +7 -7
  6. package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/344/270/216/346/215/242owner/347/274/226/346/216/222/346/226/271/346/241/210.md +530 -0
  7. package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/345/256/214/346/225/264/350/256/276/350/256/241/344/270/216/345/256/236/346/226/275/350/256/241/345/210/222.md +685 -0
  8. package/_packed_docs/agent.md/working/agent.md-schema/344/270/216/347/255/276/345/217/221/351/252/214/347/255/276/346/265/201/347/250/213/346/276/204/346/270/205.md +349 -0
  9. package/_packed_docs/agent.md/working//346/234/254/345/234/260Docker/346/265/213/350/257/225Bug/346/270/205/345/215/225.md +40 -0
  10. package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +11 -11
  11. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +372 -331
  12. package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
  13. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +676 -676
  14. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +8 -12
  15. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +445 -0
  16. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
  17. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +666 -0
  18. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
  19. package/_packed_docs/protocol/agent.md/SCHEMA.md +1 -1
  20. package/_packed_docs/sdk/0.5.21-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +368 -0
  21. package/_packed_docs/sdk/0.5.21-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +350 -0
  22. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
  23. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
  24. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
  25. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +70 -63
  26. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +43 -40
  27. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +179 -19
  28. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +50 -49
  29. package/_packed_docs/sdk/09-group-rpc-manual.md +261 -254
  30. package/_packed_docs/sdk/09-message-rpc-manual.md +253 -160
  31. package/_packed_docs/sdk/09-storage-rpc-manual.md +18 -26
  32. package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +71 -0
  33. package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +88 -0
  34. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +105 -92
  35. package/_packed_docs/sdk/INDEX.md +197 -167
  36. package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
  37. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
  38. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +930 -0
  39. package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
  40. package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +363 -0
  41. package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +553 -0
  42. package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -0
  43. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +480 -0
  44. package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
  45. package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
  46. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +173 -0
  47. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +211 -0
  48. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/345/256/236/347/216/260/347/247/273/346/244/215/346/214/207/345/215/227.md +331 -0
  49. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +207 -0
  50. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/344/273/243/347/240/201/345/256/241/346/237/245/346/212/245/345/221/212.md +252 -0
  51. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/345/256/236/346/226/275/350/256/241/345/210/222.md +156 -0
  52. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/346/226/271/346/241/210.md +273 -0
  53. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/350/257/246/347/273/206/350/256/276/350/256/241.md +211 -0
  54. package/dist/agent-md-schema.d.ts +13 -1
  55. package/dist/agent-md-schema.js +63 -33
  56. package/dist/agent-md-schema.js.map +1 -1
  57. package/dist/agent-md.d.ts +30 -3
  58. package/dist/agent-md.js +293 -152
  59. package/dist/agent-md.js.map +1 -1
  60. package/dist/aid-store.d.ts +42 -0
  61. package/dist/aid-store.js +224 -44
  62. package/dist/aid-store.js.map +1 -1
  63. package/dist/aid.js +18 -13
  64. package/dist/aid.js.map +1 -1
  65. package/dist/auth.js +78 -2
  66. package/dist/auth.js.map +1 -1
  67. package/dist/cert-utils.d.ts +44 -1
  68. package/dist/cert-utils.js +157 -56
  69. package/dist/cert-utils.js.map +1 -1
  70. package/dist/client/delivery.d.ts +68 -1
  71. package/dist/client/delivery.js +1196 -135
  72. package/dist/client/delivery.js.map +1 -1
  73. package/dist/client/rpc-pipeline.d.ts +5 -1
  74. package/dist/client/rpc-pipeline.js +240 -111
  75. package/dist/client/rpc-pipeline.js.map +1 -1
  76. package/dist/client/v2-e2ee.d.ts +12 -2
  77. package/dist/client/v2-e2ee.js +842 -379
  78. package/dist/client/v2-e2ee.js.map +1 -1
  79. package/dist/client.d.ts +26 -2
  80. package/dist/client.js +420 -75
  81. package/dist/client.js.map +1 -1
  82. package/dist/config.d.ts +4 -0
  83. package/dist/config.js +1 -0
  84. package/dist/config.js.map +1 -1
  85. package/dist/events.d.ts +23 -4
  86. package/dist/events.js +171 -15
  87. package/dist/events.js.map +1 -1
  88. package/dist/facades.d.ts +43 -1
  89. package/dist/facades.js +368 -12
  90. package/dist/facades.js.map +1 -1
  91. package/dist/index.d.ts +7 -4
  92. package/dist/index.js +1 -1
  93. package/dist/index.js.map +1 -1
  94. package/dist/keystore/aid-db.d.ts +7 -2
  95. package/dist/keystore/aid-db.js +194 -43
  96. package/dist/keystore/aid-db.js.map +1 -1
  97. package/dist/keystore/index.d.ts +15 -1
  98. package/dist/keystore/local-token-store.d.ts +10 -2
  99. package/dist/keystore/local-token-store.js +81 -16
  100. package/dist/keystore/local-token-store.js.map +1 -1
  101. package/dist/org-protocol.d.ts +45 -0
  102. package/dist/org-protocol.js +829 -22
  103. package/dist/org-protocol.js.map +1 -1
  104. package/dist/ownership-v21.d.ts +40 -9
  105. package/dist/ownership-v21.js +174 -79
  106. package/dist/ownership-v21.js.map +1 -1
  107. package/dist/ownership-workflow.d.ts +22 -0
  108. package/dist/ownership-workflow.js +861 -66
  109. package/dist/ownership-workflow.js.map +1 -1
  110. package/dist/secret-store/file-store.js +84 -0
  111. package/dist/secret-store/file-store.js.map +1 -1
  112. package/dist/seq-tracker.d.ts +1 -0
  113. package/dist/seq-tracker.js +2 -2
  114. package/dist/seq-tracker.js.map +1 -1
  115. package/dist/tools/cross-sdk-agent.d.ts +67 -3
  116. package/dist/tools/cross-sdk-agent.js +575 -42
  117. package/dist/tools/cross-sdk-agent.js.map +1 -1
  118. package/dist/transport.d.ts +4 -0
  119. package/dist/transport.js +108 -44
  120. package/dist/transport.js.map +1 -1
  121. package/dist/types.d.ts +1 -0
  122. package/dist/v2/e2ee/encrypt-group.js +1 -0
  123. package/dist/v2/e2ee/encrypt-group.js.map +1 -1
  124. package/dist/v2/e2ee/encrypt-p2p.js +1 -0
  125. package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
  126. package/dist/v2/e2ee/types.d.ts +1 -0
  127. package/dist/version.d.ts +1 -1
  128. package/dist/version.js +1 -1
  129. package/package.json +1 -1
@@ -13,6 +13,8 @@ export declare class RpcPipeline {
13
13
  private pullDrainedCheckQueued;
14
14
  constructor(runtime: ClientRuntime);
15
15
  call(method: string, params?: RpcParams): Promise<RpcResult>;
16
+ private _callWithSent;
17
+ private _call;
16
18
  preflight(method: string, params?: RpcParams): RpcPreflightResult;
17
19
  mergeInstanceProtectedHeaders(method: string, params: RpcParams): void;
18
20
  normalizeOutboundMessagePayload(params: RpcParams, method?: string): void;
@@ -43,7 +45,8 @@ export declare class RpcPipeline {
43
45
  isPullNamespaceCurrent(namespace: string): boolean;
44
46
  invalidatePulls(): Promise<void>;
45
47
  private pullInvalidationInProgress;
46
- runPullSerialized<T>(key: string, operation: () => Promise<T> | T, background?: boolean): Promise<T>;
48
+ runPullNamespaceSerialized<T>(key: string, operation: () => Promise<T> | T, background?: boolean): Promise<T>;
49
+ runPullSerialized<T>(key: string, operation: () => Promise<T> | T, background?: boolean, namespaceOnly?: boolean): Promise<T>;
47
50
  private executePullOperation;
48
51
  private throwIfPullInvalidated;
49
52
  yieldPullGate(key: string, nextKey?: string, background?: boolean): Promise<void>;
@@ -61,6 +64,7 @@ export declare class RpcPipeline {
61
64
  background?: boolean;
62
65
  pullGateKey?: string;
63
66
  }): Promise<unknown>;
67
+ private observeBackgroundPullResponse;
64
68
  private transportCallWithIdentityRecovery;
65
69
  postprocessResult(method: string, params: RpcParams, result: unknown, options?: {
66
70
  skipGroupState?: boolean;
@@ -1,5 +1,5 @@
1
1
  import * as crypto from 'node:crypto';
2
- import { normalizeSlotId, slotIsolationKey } from '../config.js';
2
+ import { _INTERNAL_MESSAGE_FLOW_LIMITS, normalizeSlotId, slotIsolationKey } from '../config.js';
3
3
  import { certTimeError } from '../cert-utils.js';
4
4
  import { orgRequestParams, validateOrgRequestJSON } from '../org-protocol.js';
5
5
  import { canonicalJson as canonicalClientRequest } from '../v2/crypto/canonical.js';
@@ -166,7 +166,14 @@ export class RpcPipeline {
166
166
  this.runtime = runtime;
167
167
  }
168
168
  async call(method, params) {
169
+ return this._call(method, params);
170
+ }
171
+ async _callWithSent(method, params, onSent) {
172
+ return this._call(method, params, onSent);
173
+ }
174
+ async _call(method, params, onSent) {
169
175
  const client = this.runtime.client;
176
+ const generation = client._delivery?.captureInlineAckGeneration?.();
170
177
  const tStart = Date.now();
171
178
  const requestedAckSeq = method === 'message.ack' || method === 'message.v2.ack'
172
179
  ? Number(params?.seq
@@ -192,6 +199,23 @@ export class RpcPipeline {
192
199
  const deferGroupReadyPostprocess = Boolean(p._defer_group_ready_postprocess);
193
200
  delete p._defer_group_ready_postprocess;
194
201
  const pullGateKey = this.pullGateKeyForCall(method, p);
202
+ if (client._p2pPeerPullEnabled && method === 'message.peers')
203
+ return await client._delivery.peers(p, rpcBackground);
204
+ if (client._p2pPeerPullEnabled && (method === 'message.pull' || method === 'message.v2.pull')) {
205
+ await client._ensureV2SessionReady('message.pull');
206
+ return await client._delivery.pullPeers(p, rpcBackground);
207
+ }
208
+ if (client._p2pPeerPullEnabled && method === 'message.history') {
209
+ await client._ensureV2SessionReady('message.history');
210
+ const key = this.pullGateKeyForCall(method, p);
211
+ return await this.runPullNamespaceSerialized(key, () => client._v2E2EE.peerHistoryV2(p), rpcBackground);
212
+ }
213
+ if (client._p2pPeerPullEnabled && (method === 'message.ack' || method === 'message.v2.ack')) {
214
+ const sender = String(p.sender_aid ?? '').trim();
215
+ if (!sender)
216
+ throw new ValidationError('peer ACK requires sender_aid');
217
+ return await client._v2E2EE.ackPeerV2(sender, requestedAckSeq || undefined, Array.isArray(p.volatile_cursors) ? p.volatile_cursors : [], Array.isArray(p.volatile_ids) ? p.volatile_ids : []);
218
+ }
195
219
  const runWithPullResponseScope = async (operation) => {
196
220
  const scope = pullGateNamespace || (pullGateKey ? this.pullScopeKey(pullGateKey) : '');
197
221
  const wrapper = client._withPullResponseProcessing;
@@ -205,9 +229,13 @@ export class RpcPipeline {
205
229
  lockedParams._pull_gate_namespace = this.pullScopeKey(pullGateKey);
206
230
  if (pullBackground)
207
231
  lockedParams._rpc_background = true;
232
+ else
233
+ lockedParams._rpc_foreground = true;
208
234
  if (skipSendResultEnvelope)
209
235
  lockedParams._skip_send_result_envelope = true;
210
- return await this.runPullSerialized(pullGateKey, async () => this.call(method, lockedParams), pullBackground);
236
+ const run = ['group.pull', 'group.v2.pull', 'group.history'].includes(method)
237
+ ? this.runPullNamespaceSerialized.bind(this) : this.runPullSerialized.bind(this);
238
+ return await run(pullGateKey, async () => this.call(method, lockedParams), pullBackground);
211
239
  }
212
240
  if (method === 'message.send') {
213
241
  const encrypt = p.encrypt ?? true;
@@ -220,6 +248,7 @@ export class RpcPipeline {
220
248
  ttl: p.ttl,
221
249
  protectedHeaders: client._protectedHeadersFromParams(p),
222
250
  context: isJsonObject(p.context) ? p.context : undefined,
251
+ needRender: p.need_render,
223
252
  }));
224
253
  }
225
254
  client._maybeAppendEchoTraceSend(p);
@@ -234,6 +263,7 @@ export class RpcPipeline {
234
263
  timestamp: p.timestamp,
235
264
  protectedHeaders: client._protectedHeadersFromParams(p),
236
265
  context: isJsonObject(p.context) ? p.context : undefined,
266
+ needRender: p.need_render,
237
267
  }));
238
268
  }
239
269
  client._maybeAppendEchoTraceSend(p);
@@ -296,12 +326,12 @@ export class RpcPipeline {
296
326
  }
297
327
  await client._ensureV2SessionReady('group.pull');
298
328
  if (p.window_mode === 'tail') {
299
- return await runWithPullResponseScope(() => client._v2E2EE.pullGroupV2TailInternal(p));
329
+ return await runWithPullResponseScope(() => client._v2E2EE.pullGroupV2TailInternal(p, false, true));
300
330
  }
301
331
  const hasExplicitAfterSeq = 'after_seq' in p || 'after_message_seq' in p;
302
332
  const cursorParams = this.explicitGroupCursorParams(p);
303
333
  const ownsCursor = Object.keys(cursorParams).length === 0 || this.groupCursorTargetsCurrentInstance(cursorParams);
304
- const pullOpts = { gateLocked: true };
334
+ const pullOpts = { gateLocked: true, background: rpcBackground };
305
335
  if (hasExplicitAfterSeq)
306
336
  pullOpts.explicitAfterSeq = true;
307
337
  if (p.force === true)
@@ -314,7 +344,7 @@ export class RpcPipeline {
314
344
  pullOpts.wireGroupId = String(p.group_id).trim();
315
345
  if (String(p.group_aid ?? '').trim())
316
346
  pullOpts.groupAid = String(p.group_aid).trim();
317
- 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)));
347
+ const messages = await runWithPullResponseScope(() => 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));
318
348
  return { messages };
319
349
  }
320
350
  if (method === 'group.history') {
@@ -347,16 +377,21 @@ export class RpcPipeline {
347
377
  client._clientLog.debug(`thought.get transport call start: method=${method}, params=${client._debugJson(client._messageEnvelopeFieldsForDebug(p))}`);
348
378
  }
349
379
  const invokeTransport = () => {
350
- const request = callTimeout
351
- ? (rpcBackground
352
- ? client._transport.call(method, p, callTimeout, undefined, true)
353
- : client._transport.call(method, p, callTimeout))
354
- : (rpcBackground
355
- ? client._transport.call(method, p, undefined, undefined, true)
356
- : client._transport.call(method, p));
380
+ const request = onSent
381
+ ? client._transport['_callWithSent'](method, p, onSent, callTimeout, undefined, rpcBackground)
382
+ : callTimeout
383
+ ? (rpcBackground
384
+ ? client._transport.call(method, p, callTimeout, undefined, true)
385
+ : client._transport.call(method, p, callTimeout))
386
+ : (rpcBackground
387
+ ? client._transport.call(method, p, undefined, undefined, true)
388
+ : client._transport.call(method, p));
357
389
  return this.bindActivePullCancellation(method, p, request);
358
390
  };
359
391
  let result = await this.transportCallWithIdentityRecovery(invokeTransport, method, p);
392
+ if (generation === client._delivery?.captureInlineAckGeneration?.()) {
393
+ this.observeBackgroundPullResponse(method, p, result, pullGateNamespace);
394
+ }
360
395
  const sendResultModeSource = result;
361
396
  result = await this.postprocessResult(method, p, result, {
362
397
  skipGroupState: deferGroupReadyPostprocess,
@@ -372,6 +407,10 @@ export class RpcPipeline {
372
407
  client._clientLog.debug(`call exit (error): method=${method} elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
373
408
  throw err;
374
409
  }
410
+ finally {
411
+ if (generation === client._delivery?.captureInlineAckGeneration?.())
412
+ client._delivery?.requestBackgroundPullRecovery?.();
413
+ }
375
414
  }
376
415
  preflight(method, params) {
377
416
  const client = this.runtime.client;
@@ -383,8 +422,11 @@ export class RpcPipeline {
383
422
  }
384
423
  const p = { ...(params ?? {}) };
385
424
  this.mergeInstanceProtectedHeaders(method, p);
386
- const rpcBackground = Boolean(p._rpc_background) || client._backgroundRpcDepth > 0;
425
+ const rpcBackground = !Boolean(p._rpc_foreground)
426
+ && (Boolean(p._rpc_background)
427
+ || (!['message.peers', 'message.pull', 'message.v2.pull', 'group.pull', 'group.v2.pull', 'group.history', 'group.pull_events'].includes(method) && client._backgroundRpcDepth > 0));
387
428
  delete p._rpc_background;
429
+ delete p._rpc_foreground;
388
430
  if (method === 'message.send' || method === 'group.send') {
389
431
  this.normalizeOutboundMessagePayload(p, method);
390
432
  const messageId = String(p.message_id ?? '').trim();
@@ -421,6 +463,9 @@ export class RpcPipeline {
421
463
  }
422
464
  }
423
465
  validateOutboundCall(method, params) {
466
+ if ((method === 'message.send' || method === 'group.send') && params.need_render !== undefined && typeof params.need_render !== 'boolean') {
467
+ throw new ValidationError('need_render must be a boolean');
468
+ }
424
469
  if (method === 'message.send') {
425
470
  this.validateMessageRecipient(params.to);
426
471
  // 校验目标 AID 格式(拒绝 __system__ 等非法格式)
@@ -483,7 +528,7 @@ export class RpcPipeline {
483
528
  }
484
529
  }
485
530
  injectMessageCursorContext(method, params) {
486
- if (!['message.pull', 'message.v2.pull', 'message.ack', 'message.v2.ack'].includes(method)) {
531
+ if (!['message.pull', 'message.v2.pull', 'message.history', 'message.ack', 'message.v2.ack', 'message.peers'].includes(method)) {
487
532
  return;
488
533
  }
489
534
  const client = this.runtime.client;
@@ -496,6 +541,19 @@ export class RpcPipeline {
496
541
  }
497
542
  params.device_id = client._deviceId;
498
543
  params.slot_id = client._slotId;
544
+ if (client._p2pPeerPullEnabled) {
545
+ if (params.cursor_scope !== undefined && params.cursor_scope !== 'peer')
546
+ throw new ValidationError('new SDK requires peer cursor_scope');
547
+ if (params.sender_aid !== undefined)
548
+ validateAIDFormat(params.sender_aid, 'sender_aid');
549
+ params.cursor_scope = 'peer';
550
+ if ((method === 'message.pull' || method === 'message.v2.pull') && params.sender_aid && !('after_volatile_cursor' in params)) {
551
+ const cursor = client._delivery.peerVolatilePullCursor(String(params.sender_aid));
552
+ if (cursor)
553
+ params.after_volatile_cursor = cursor;
554
+ }
555
+ return;
556
+ }
499
557
  if ((method === 'message.pull' || method === 'message.v2.pull') && !('after_volatile_cursor' in params)) {
500
558
  const cursor = String(client._delivery.volatilePullCursor?.() ?? '').trim();
501
559
  if (cursor)
@@ -555,6 +613,12 @@ export class RpcPipeline {
555
613
  validateOrgRequestJSON(paramsForHash);
556
614
  const paramsJson = method.startsWith('org.') ? new TextDecoder().decode(canonicalClientRequest(paramsForHash)) : stableStringify(paramsForHash);
557
615
  const paramsHash = crypto.createHash('sha256').update(paramsJson, 'utf-8').digest('hex');
616
+ if (params.client_signature !== undefined) {
617
+ const existing = params.client_signature;
618
+ if (!isJsonObject(existing) || existing.aid !== aid || existing.cert_fingerprint !== getCertFingerprint(certPem) || existing.params_hash !== paramsHash || typeof existing.timestamp !== 'string' || !/^\d+$/.test(existing.timestamp) || typeof existing.signature !== 'string' || !crypto.verify('SHA256', Buffer.from(`${method}|${aid}|${existing.timestamp}|${paramsHash}`, 'utf-8'), crypto.createPublicKey(certPem), Buffer.from(existing.signature, 'base64')))
619
+ throw new Error('已有客户端签名与请求内容不一致');
620
+ return;
621
+ }
558
622
  const signData = Buffer.from(`${method}|${aid}|${ts}|${paramsHash}`, 'utf-8');
559
623
  const privateKey = getSigningPrivateKey(currentAid.privateKeyPem);
560
624
  const signature = crypto.sign('SHA256', signData, privateKey);
@@ -595,7 +659,10 @@ export class RpcPipeline {
595
659
  cursor = `after=${String(params.after_seq ?? 0)}`;
596
660
  }
597
661
  const limit = mode === 'forward' ? (Number(params.limit ?? 50) || 50) : params.limit;
598
- return `p2p:${client._aid}|mode=${mode}|cursor=${cursor}|volatile=${String(params.after_volatile_cursor ?? '')}|force=${String(Boolean(params.force))}|limit=${String(limit)}`;
662
+ if (client._p2pPeerPullEnabled && !params.sender_aid)
663
+ return '';
664
+ const namespace = client._p2pPeerPullEnabled ? client._delivery.peerNamespace(String(params.sender_aid)) : `p2p:${client._aid}`;
665
+ return `${namespace}|mode=${mode}|cursor=${cursor}|volatile=${String(params.after_volatile_cursor ?? '')}|force=${String(Boolean(params.force))}|limit=${String(limit)}`;
599
666
  }
600
667
  if (method === 'group.pull' || method === 'group.v2.pull' || method === 'group.history') {
601
668
  const groupId = String(params.group_id ?? params.group_aid ?? '').trim();
@@ -640,14 +707,15 @@ export class RpcPipeline {
640
707
  void key;
641
708
  return 'pull';
642
709
  }
643
- pullGateState(key) {
644
- const name = this.pullGateName(key);
710
+ pullGateState(key, namespaceOnly = false) {
711
+ const name = namespaceOnly ? 'pull-namespace' : this.pullGateName(key);
645
712
  let state = this.pullGateStates.get(name);
646
713
  if (!state) {
647
- state = { active: null, foreground: [], background: [], byKey: new Map(), lifecycle: new Map(), watchdog: null };
714
+ state = { networkLimited: !namespaceOnly, activeByNamespace: new Map(), foreground: [], background: [], backgroundPeerDispatches: 0, byKey: new Map(), lifecycle: new Map(), watchdog: null };
648
715
  this.pullGateStates.set(name, state);
649
716
  }
650
- this.startPullGateWatchdog(state, name);
717
+ if (state.networkLimited)
718
+ this.startPullGateWatchdog(state, name);
651
719
  return state;
652
720
  }
653
721
  pullGateOperationTimeoutMs() {
@@ -675,23 +743,25 @@ export class RpcPipeline {
675
743
  }
676
744
  }
677
745
  checkPullGate(state, name) {
678
- const active = state.active;
679
- if (!active) {
746
+ const activeJobs = [...state.activeByNamespace.values()];
747
+ if (activeJobs.length === 0) {
680
748
  this.drainPullGate(state);
681
749
  return;
682
750
  }
683
- if (!active.pullingStartedAt || Date.now() - active.pullingStartedAt < this.pullGateOperationTimeoutMs())
684
- return;
685
- if (active.timedOut)
686
- return;
687
- active.timedOut = true;
688
- active.invalidated = true;
689
- active.cancel?.();
690
- const timeout = new TimeoutError(`pull gate timeout: ${active.namespace}`, { retryable: true });
691
- this.runtime.client._clientLog.warn(`pull gate watchdog timeout: gate=${name} key=${active.key} elapsed=${Date.now() - active.pullingStartedAt}ms`);
692
- // 仅拒绝当前调用方;取消不敏感的 operation 仍可能运行,必须等其
693
- // 真正 settle 后才释放 active,避免与后续 Pull 并发修改游标。
694
- active.reject(timeout);
751
+ for (const active of activeJobs) {
752
+ if (!active.pullingStartedAt || Date.now() - active.pullingStartedAt < this.pullGateOperationTimeoutMs())
753
+ continue;
754
+ if (active.timedOut)
755
+ continue;
756
+ active.timedOut = true;
757
+ active.invalidated = true;
758
+ active.cancel?.();
759
+ const timeout = new TimeoutError(`pull gate timeout: ${active.namespace}`, { retryable: true });
760
+ this.runtime.client._clientLog.warn(`pull gate watchdog timeout: gate=${name} key=${active.key} elapsed=${Date.now() - active.pullingStartedAt}ms`);
761
+ // 仅拒绝当前调用方;取消不敏感的 operation 仍可能运行,必须等其
762
+ // 真正 settle 后才释放 active,避免与后续 Pull 并发修改游标。
763
+ active.reject(timeout);
764
+ }
695
765
  }
696
766
  pullScopeKey(key) {
697
767
  return key.split('|', 1)[0] ?? key;
@@ -706,7 +776,7 @@ export class RpcPipeline {
706
776
  if (typeof cancel !== 'function' || !key)
707
777
  return request;
708
778
  const state = this.pullGateStates.get(this.pullGateName(key));
709
- const active = state?.active;
779
+ const active = [...(state?.activeByNamespace.values() ?? [])].find((candidate) => candidate.key === key);
710
780
  if (!active || active.key !== key || active.timedOut || active.invalidated)
711
781
  return request;
712
782
  const boundCancel = () => cancel.call(request);
@@ -723,9 +793,11 @@ export class RpcPipeline {
723
793
  }
724
794
  hasPullActivity(namespace, includePending = true) {
725
795
  const ns = String(namespace ?? '').trim();
726
- if ((!ns.startsWith('p2p:') && !ns.startsWith('group:')) || this.hasInlineRealtimeGuard(ns))
796
+ if ((!ns.startsWith('p2p:') && !ns.startsWith('p2p.peer.v1/') && !ns.startsWith('group:')) || this.hasInlineRealtimeGuard(ns))
727
797
  return true;
728
798
  const client = this.runtime.client;
799
+ if (client._delivery?.hasPeerPageActivity?.(ns))
800
+ return true;
729
801
  if ((client._pullResponseKeys.get(ns) ?? 0) > 0)
730
802
  return true;
731
803
  if (includePending && Number(client._pendingP2pPullUpper?.get?.(ns) ?? 0) > 0)
@@ -744,15 +816,15 @@ export class RpcPipeline {
744
816
  return true;
745
817
  }
746
818
  for (const state of this.pullGateStates.values()) {
747
- const jobs = [state.active, ...state.foreground, ...state.background];
748
- if (jobs.some((job) => job !== null && this.pullScopeKey(job.key) === ns))
819
+ const jobs = [...state.activeByNamespace.values(), ...state.foreground, ...state.background];
820
+ if (jobs.some((job) => this.pullScopeKey(job.key) === ns))
749
821
  return true;
750
822
  }
751
823
  return false;
752
824
  }
753
825
  async tryRunInlineRealtime(namespace, seq, operation) {
754
826
  const ns = String(namespace ?? '').trim();
755
- if ((!ns.startsWith('p2p:') && !ns.startsWith('group:'))
827
+ if ((!ns.startsWith('p2p:') && !ns.startsWith('p2p.peer.v1/') && !ns.startsWith('group:'))
756
828
  || !Number.isSafeInteger(seq) || seq <= 0 || typeof operation !== 'function'
757
829
  || this.hasPullActivity(ns)) {
758
830
  return { acquired: false };
@@ -763,12 +835,11 @@ export class RpcPipeline {
763
835
  }
764
836
  finally {
765
837
  this.inlineRealtimeScopes.delete(ns);
766
- const state = this.pullGateStates.get(this.pullGateName(ns));
767
- if (state) {
838
+ for (const state of this.pullGateStates.values()) {
768
839
  this.drainPullGate(state);
769
- this.runtime.client._delivery?.onPullGateIdle?.(ns);
770
840
  this.armQueuedPullTimers(state);
771
841
  }
842
+ this.runtime.client._delivery?.onPullGateIdle?.(ns);
772
843
  }
773
844
  }
774
845
  tryAcquirePullGate(key) {
@@ -808,8 +879,10 @@ export class RpcPipeline {
808
879
  const ns = this.pullScopeKey(namespace);
809
880
  if (!ns)
810
881
  return true;
811
- const active = this.pullGateStates.get(this.pullGateName(ns))?.active;
812
- return !active || active.namespace !== ns || (!active.timedOut && !active.invalidated);
882
+ return [...this.pullGateStates.values()].every((state) => {
883
+ const active = state.activeByNamespace.get(ns);
884
+ return !active || active.namespace !== ns || (!active.timedOut && !active.invalidated);
885
+ });
813
886
  }
814
887
  invalidatePulls() {
815
888
  if (this.pullInvalidationWait)
@@ -819,8 +892,8 @@ export class RpcPipeline {
819
892
  const waits = [];
820
893
  for (const state of this.pullGateStates.values()) {
821
894
  const jobs = new Set();
822
- if (state.active)
823
- jobs.add(state.active);
895
+ for (const active of state.activeByNamespace.values())
896
+ jobs.add(active);
824
897
  for (const job of state.foreground)
825
898
  jobs.add(job);
826
899
  for (const job of state.background)
@@ -842,6 +915,7 @@ export class RpcPipeline {
842
915
  }
843
916
  state.foreground = state.foreground.filter((job) => !jobs.has(job));
844
917
  state.background = state.background.filter((job) => !jobs.has(job));
918
+ state.backgroundPeerDispatches = 0;
845
919
  for (const [key, job] of state.byKey.entries()) {
846
920
  if (jobs.has(job) && !job.running)
847
921
  state.byKey.delete(key);
@@ -860,7 +934,10 @@ export class RpcPipeline {
860
934
  pullInvalidationInProgress() {
861
935
  return this.pullInvalidationWait !== null;
862
936
  }
863
- async runPullSerialized(key, operation, background = false) {
937
+ async runPullNamespaceSerialized(key, operation, background = false) {
938
+ return await this.runPullSerialized(key, operation, background, true);
939
+ }
940
+ async runPullSerialized(key, operation, background = false, namespaceOnly = false) {
864
941
  if (this.runtime.client._dispatcher?._isApplicationHandler?.()) {
865
942
  throw new Error('pull cannot wait inside an application handler');
866
943
  }
@@ -876,9 +953,9 @@ export class RpcPipeline {
876
953
  throw error;
877
954
  }
878
955
  }
879
- const state = this.pullGateState(key);
956
+ const state = this.pullGateState(key, namespaceOnly);
880
957
  const namespace = this.pullScopeKey(key);
881
- const active = state.active?.key === key && !state.active.timedOut && !state.active.invalidated ? state.active : null;
958
+ const active = [...state.activeByNamespace.values()].find((job) => job.key === key && !job.timedOut && !job.invalidated) ?? null;
882
959
  const queuedForeground = state.foreground.find((job) => job.key === key && !job.timedOut && !job.invalidated);
883
960
  const queuedBackground = state.background.find((job) => job.key === key && !job.timedOut && !job.invalidated);
884
961
  const existing = background
@@ -920,7 +997,8 @@ export class RpcPipeline {
920
997
  queue.push(job);
921
998
  state.byKey.set(key, job);
922
999
  this.drainPullGate(state);
923
- this.checkPullGate(state, this.pullGateName(key));
1000
+ if (state.networkLimited)
1001
+ this.checkPullGate(state, this.pullGateName(key));
924
1002
  return await promise;
925
1003
  }
926
1004
  async executePullOperation(operation, background) {
@@ -938,8 +1016,8 @@ export class RpcPipeline {
938
1016
  return;
939
1017
  const state = this.pullGateState(key);
940
1018
  const namespace = this.pullScopeKey(key);
941
- const job = state.active?.key === key ? state.active : null;
942
- if (!job || state.active !== job || job.timedOut || job.invalidated)
1019
+ const job = [...state.activeByNamespace.values()].find((candidate) => candidate.key === key) ?? null;
1020
+ if (!job || job.timedOut || job.invalidated)
943
1021
  return;
944
1022
  const replacementKey = String(nextKey || key);
945
1023
  if (state.byKey.get(job.key) === job)
@@ -958,7 +1036,7 @@ export class RpcPipeline {
958
1036
  state.lifecycle.set(namespace, 'pulled');
959
1037
  job.suspended = true;
960
1038
  job.resume = new Promise((resolve) => { job.resumeResolve = resolve; });
961
- state.active = null;
1039
+ state.activeByNamespace.delete(namespace);
962
1040
  (job.background ? state.background : state.foreground).push(job);
963
1041
  this.drainPullGate(state);
964
1042
  if (job.resume) {
@@ -978,67 +1056,93 @@ export class RpcPipeline {
978
1056
  if (!ns)
979
1057
  return;
980
1058
  const state = this.pullGateStates.get(this.pullGateName(ns));
981
- if (!state || state.active?.namespace !== ns)
1059
+ if (!state || !state.activeByNamespace.has(ns))
982
1060
  return;
983
1061
  state.lifecycle.set(ns, lifecycle);
984
1062
  }
985
1063
  drainPullGate(state) {
986
- if (state.active)
987
- return;
988
- const takeRunnable = (queue) => {
989
- const index = queue.findIndex((candidate) => !this.hasInlineRealtimeGuard(this.pullScopeKey(candidate.key)));
990
- return index >= 0 ? queue.splice(index, 1)[0] : undefined;
1064
+ const peerForward = (job) => job.background && job.namespace.startsWith('p2p.peer.v1/')
1065
+ && job.key.includes('|mode=forward|') && !job.key.includes('|force=true|');
1066
+ const priority = (job) => peerForward(job)
1067
+ ? this.runtime.client._delivery?.peerPullPriority?.(job.namespace) ?? null : null;
1068
+ const runnable = (candidate) => (!this.hasInlineRealtimeGuard(this.pullScopeKey(candidate.key))
1069
+ && !state.activeByNamespace.has(candidate.namespace));
1070
+ const takeRunnable = (queue, background = false) => {
1071
+ let index = queue.findIndex(runnable);
1072
+ if (index < 0)
1073
+ return;
1074
+ if (background && peerForward(queue[index])
1075
+ && (state.backgroundPeerDispatches + 1) % _INTERNAL_MESSAGE_FLOW_LIMITS.pullMaxInflight !== 0) {
1076
+ let best = priority(queue[index]);
1077
+ if (best !== null) {
1078
+ for (let next = index + 1; next < queue.length; next += 1) {
1079
+ const hint = priority(queue[next]);
1080
+ if (hint === null)
1081
+ break;
1082
+ if (runnable(queue[next]) && hint < best) {
1083
+ best = hint;
1084
+ index = next;
1085
+ }
1086
+ }
1087
+ }
1088
+ }
1089
+ const job = queue.splice(index, 1)[0];
1090
+ if (background && peerForward(job))
1091
+ state.backgroundPeerDispatches += 1;
1092
+ return job;
991
1093
  };
992
- const foregroundJob = takeRunnable(state.foreground);
993
- const foregroundDrained = foregroundJob !== undefined && state.foreground.length === 0;
994
- const job = foregroundJob ?? takeRunnable(state.background);
995
- if (!job)
996
- return;
997
- state.active = job;
998
- this.runtime.client._delivery?.syncStarted?.(job.namespace);
999
- if (foregroundDrained)
1000
- this.runtime.client._delivery?.onForegroundPullsDrained?.();
1001
- if (job.timer) {
1002
- clearTimeout(job.timer);
1003
- job.timer = null;
1004
- }
1005
- job.pullingStartedAt = Date.now();
1006
- job.timedOut = false;
1007
- state.lifecycle.set(job.namespace, 'pulling');
1008
- const resumeResolve = job.resumeResolve;
1009
- job.resume = null;
1010
- job.resumeResolve = null;
1011
- if (job.suspended) {
1012
- job.suspended = false;
1013
- resumeResolve?.();
1014
- return;
1015
- }
1016
- job.running = true;
1017
- const operationPromise = this.executePullOperation(job.operation, job.background);
1018
- let failed = false;
1019
- void operationPromise
1020
- .then((value) => { if (!job.timedOut && !job.invalidated)
1021
- job.resolve(value); }, (error) => {
1022
- failed = true;
1023
- if (!job.timedOut && !job.invalidated)
1024
- job.reject(error);
1025
- })
1026
- .finally(() => {
1027
- state.lifecycle.set(job.namespace, 'idle');
1028
- if (state.active === job)
1029
- state.active = null;
1030
- if (state.byKey.get(job.key) === job)
1031
- state.byKey.delete(job.key);
1032
- this.drainPullGate(state);
1033
- job.settledResolve?.();
1034
- job.settledResolve = null;
1035
- if (!job.invalidated) {
1036
- this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
1094
+ while (state.activeByNamespace.size < _INTERNAL_MESSAGE_FLOW_LIMITS.pullMaxInflight) {
1095
+ const foregroundJob = takeRunnable(state.foreground);
1096
+ const foregroundDrained = foregroundJob !== undefined && state.foreground.length === 0;
1097
+ const job = foregroundJob ?? takeRunnable(state.background, true);
1098
+ if (!job)
1099
+ break;
1100
+ state.activeByNamespace.set(job.namespace, job);
1101
+ this.runtime.client._delivery?.syncStarted?.(job.namespace);
1102
+ if (foregroundDrained)
1103
+ this.runtime.client._delivery?.onForegroundPullsDrained?.();
1104
+ if (job.timer) {
1105
+ clearTimeout(job.timer);
1106
+ job.timer = null;
1037
1107
  }
1038
- if (failed || job.invalidated)
1039
- this.runtime.client._delivery?.onPullAborted?.();
1040
- this.notifyPullDrainedIfIdle();
1041
- });
1108
+ job.pullingStartedAt = Date.now();
1109
+ job.timedOut = false;
1110
+ state.lifecycle.set(job.namespace, 'pulling');
1111
+ const resumeResolve = job.resumeResolve;
1112
+ job.resume = null;
1113
+ job.resumeResolve = null;
1114
+ if (job.suspended) {
1115
+ job.suspended = false;
1116
+ resumeResolve?.();
1117
+ continue;
1118
+ }
1119
+ job.running = true;
1120
+ const operationPromise = this.executePullOperation(job.operation, state.networkLimited && job.background);
1121
+ let failed = false;
1122
+ void operationPromise
1123
+ .then((value) => { if (!job.timedOut && !job.invalidated)
1124
+ job.resolve(value); }, (error) => {
1125
+ failed = true;
1126
+ if (!job.timedOut && !job.invalidated)
1127
+ job.reject(error);
1128
+ })
1129
+ .finally(() => {
1130
+ state.lifecycle.set(job.namespace, 'idle');
1131
+ if (state.activeByNamespace.get(job.namespace) === job)
1132
+ state.activeByNamespace.delete(job.namespace);
1133
+ if (state.byKey.get(job.key) === job)
1134
+ state.byKey.delete(job.key);
1135
+ this.drainPullGate(state);
1136
+ job.settledResolve?.();
1137
+ job.settledResolve = null;
1138
+ if (!job.invalidated) {
1139
+ this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
1140
+ }
1141
+ if (failed || job.invalidated)
1142
+ this.runtime.client._delivery?.onPullAborted?.();
1143
+ this.notifyPullDrainedIfIdle();
1144
+ });
1145
+ }
1042
1146
  }
1043
1147
  notifyPullDrainedIfIdle() {
1044
1148
  if (this.pullDrainedCheckQueued)
@@ -1054,7 +1158,7 @@ export class RpcPipeline {
1054
1158
  }
1055
1159
  notifyPullDrainedIfIdleStable() {
1056
1160
  for (const state of this.pullGateStates.values()) {
1057
- if (state.active || state.foreground.length > 0 || state.background.length > 0)
1161
+ if (state.activeByNamespace.size > 0 || state.foreground.length > 0 || state.background.length > 0)
1058
1162
  return;
1059
1163
  }
1060
1164
  const client = this.runtime.client;
@@ -1087,9 +1191,34 @@ export class RpcPipeline {
1087
1191
  request = client._transport.call(method, payload);
1088
1192
  return this.bindActivePullCancellation(method, payload, request, pullGateKey);
1089
1193
  };
1090
- const result = await this.transportCallWithIdentityRecovery(invokeTransport, method, payload);
1091
- this.throwIfPullInvalidated(this.pullScopeKey(pullGateKey || this.pullGateKeyForCall(method, payload)));
1092
- return result;
1194
+ const generation = client._delivery?.captureInlineAckGeneration?.();
1195
+ try {
1196
+ const result = await this.transportCallWithIdentityRecovery(invokeTransport, method, payload);
1197
+ this.throwIfPullInvalidated(this.pullScopeKey(pullGateKey || this.pullGateKeyForCall(method, payload)));
1198
+ if (generation === client._delivery?.captureInlineAckGeneration?.()) {
1199
+ this.observeBackgroundPullResponse(method, payload, result, pullGateKey);
1200
+ }
1201
+ return result;
1202
+ }
1203
+ finally {
1204
+ if (generation === client._delivery?.captureInlineAckGeneration?.())
1205
+ client._delivery?.requestBackgroundPullRecovery?.();
1206
+ }
1207
+ }
1208
+ observeBackgroundPullResponse(method, params, result, key = '') {
1209
+ if (!['message.pull', 'message.v2.pull', 'group.pull', 'group.v2.pull', 'group.pull_events'].includes(method)
1210
+ || params.force === true || params.window_mode === 'tail')
1211
+ return;
1212
+ const client = this.runtime.client;
1213
+ if (method.startsWith('group.') && !this.groupCursorTargetsCurrentInstance(params))
1214
+ return;
1215
+ const explicitCursor = this.explicitGroupCursorParams(params);
1216
+ if (method.startsWith('group.') && !this.groupCursorTargetsCurrentInstance(explicitCursor))
1217
+ return;
1218
+ const ns = this.pullScopeKey(key) || (method === 'group.pull_events'
1219
+ ? `group_event:${String(params.group_id ?? params.group_aid ?? '')}`
1220
+ : method.startsWith('group.') ? `group:${String(params.group_id ?? params.group_aid ?? '')}` : `p2p:${client._aid}`);
1221
+ client._delivery?.observeBackgroundPullResponse?.(ns, Number(params.after_seq ?? params.after_event_seq ?? 0), result, Number(params.limit ?? 50));
1093
1222
  }
1094
1223
  async transportCallWithIdentityRecovery(operation, method, params) {
1095
1224
  try {