@agentunion/fastaun-browser 0.5.20 → 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 (130) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/_packed_docs/CHANGELOG.md +47 -0
  3. package/_packed_docs/INDEX.md +453 -440
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +164 -161
  5. 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
  6. 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
  7. 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
  8. 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
  9. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1433 -1392
  10. package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
  11. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +2 -0
  12. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +666 -658
  13. 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
  14. 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
  15. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
  16. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
  17. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
  18. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +4 -1
  19. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +42 -39
  20. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +147 -14
  21. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +48 -47
  22. package/_packed_docs/sdk/09-group-rpc-manual.md +5 -2
  23. package/_packed_docs/sdk/09-message-rpc-manual.md +250 -160
  24. 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
  25. 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
  26. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +134 -130
  27. package/_packed_docs/sdk/INDEX.md +239 -221
  28. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +930 -922
  29. 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 +106 -43
  30. 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 +180 -9
  31. 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 -708
  32. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +480 -476
  33. 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 +1 -1
  34. 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 +2 -2
  35. package/dist/agent-md-schema.d.ts +1 -0
  36. package/dist/agent-md-schema.d.ts.map +1 -1
  37. package/dist/agent-md-schema.js +5 -4
  38. package/dist/agent-md-schema.js.map +1 -1
  39. package/dist/agent-md.d.ts +25 -1
  40. package/dist/agent-md.d.ts.map +1 -1
  41. package/dist/agent-md.js +148 -71
  42. package/dist/agent-md.js.map +1 -1
  43. package/dist/aid-store.d.ts +13 -0
  44. package/dist/aid-store.d.ts.map +1 -1
  45. package/dist/aid-store.js +55 -8
  46. package/dist/aid-store.js.map +1 -1
  47. package/dist/aid.d.ts.map +1 -1
  48. package/dist/aid.js +2 -1
  49. package/dist/aid.js.map +1 -1
  50. package/dist/auth.d.ts.map +1 -1
  51. package/dist/auth.js +4 -0
  52. package/dist/auth.js.map +1 -1
  53. package/dist/bundle.js +4981 -1219
  54. package/dist/cert-utils.d.ts +1 -0
  55. package/dist/cert-utils.d.ts.map +1 -1
  56. package/dist/cert-utils.js +21 -0
  57. package/dist/cert-utils.js.map +1 -1
  58. package/dist/client/delivery.d.ts +101 -16
  59. package/dist/client/delivery.d.ts.map +1 -1
  60. package/dist/client/delivery.js +1172 -160
  61. package/dist/client/delivery.js.map +1 -1
  62. package/dist/client/rpc-pipeline.d.ts +4 -1
  63. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  64. package/dist/client/rpc-pipeline.js +122 -48
  65. package/dist/client/rpc-pipeline.js.map +1 -1
  66. package/dist/client/v2-e2ee.d.ts +22 -3
  67. package/dist/client/v2-e2ee.d.ts.map +1 -1
  68. package/dist/client/v2-e2ee.js +1258 -649
  69. package/dist/client/v2-e2ee.js.map +1 -1
  70. package/dist/client.d.ts +23 -2
  71. package/dist/client.d.ts.map +1 -1
  72. package/dist/client.js +391 -87
  73. package/dist/client.js.map +1 -1
  74. package/dist/config.d.ts +4 -0
  75. package/dist/config.d.ts.map +1 -1
  76. package/dist/config.js +1 -0
  77. package/dist/config.js.map +1 -1
  78. package/dist/events.d.ts +19 -1
  79. package/dist/events.d.ts.map +1 -1
  80. package/dist/events.js +146 -23
  81. package/dist/events.js.map +1 -1
  82. package/dist/facades.d.ts +42 -1
  83. package/dist/facades.d.ts.map +1 -1
  84. package/dist/facades.js +363 -7
  85. package/dist/facades.js.map +1 -1
  86. package/dist/index.d.ts +5 -3
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +1 -1
  89. package/dist/index.js.map +1 -1
  90. package/dist/keystore/index.d.ts +14 -1
  91. package/dist/keystore/index.d.ts.map +1 -1
  92. package/dist/keystore/indexeddb-identity-store.d.ts +4 -0
  93. package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
  94. package/dist/keystore/indexeddb-identity-store.js +30 -13
  95. package/dist/keystore/indexeddb-identity-store.js.map +1 -1
  96. package/dist/keystore/indexeddb-shared.d.ts +19 -2
  97. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  98. package/dist/keystore/indexeddb-shared.js +193 -6
  99. package/dist/keystore/indexeddb-shared.js.map +1 -1
  100. package/dist/keystore/indexeddb-token-store.d.ts +15 -2
  101. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  102. package/dist/keystore/indexeddb-token-store.js +308 -27
  103. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  104. package/dist/org-protocol.d.ts +45 -0
  105. package/dist/org-protocol.d.ts.map +1 -1
  106. package/dist/org-protocol.js +757 -11
  107. package/dist/org-protocol.js.map +1 -1
  108. package/dist/ownership-v21.d.ts +21 -4
  109. package/dist/ownership-v21.d.ts.map +1 -1
  110. package/dist/ownership-v21.js +49 -16
  111. package/dist/ownership-v21.js.map +1 -1
  112. package/dist/ownership-workflow.d.ts +30 -0
  113. package/dist/ownership-workflow.d.ts.map +1 -1
  114. package/dist/ownership-workflow.js +412 -54
  115. package/dist/ownership-workflow.js.map +1 -1
  116. package/dist/seq-tracker.d.ts +2 -0
  117. package/dist/seq-tracker.d.ts.map +1 -1
  118. package/dist/seq-tracker.js +3 -2
  119. package/dist/seq-tracker.js.map +1 -1
  120. package/dist/tools/cross-sdk-agent.d.ts +60 -3
  121. package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
  122. package/dist/tools/cross-sdk-agent.js +433 -37
  123. package/dist/tools/cross-sdk-agent.js.map +1 -1
  124. package/dist/transport.d.ts +3 -0
  125. package/dist/transport.d.ts.map +1 -1
  126. package/dist/transport.js +62 -30
  127. package/dist/transport.js.map +1 -1
  128. package/dist/version.d.ts +1 -1
  129. package/dist/version.js +1 -1
  130. package/package.json +1 -1
@@ -8,7 +8,7 @@ import * as path from 'node:path';
8
8
  import { URL, fileURLToPath } from 'node:url';
9
9
  import { AID, AIDStore, AUNClient, __version__ } from '../index.js';
10
10
  import { publicKeyFingerprint } from '../cert-utils.js';
11
- import { StateError } from '../errors.js';
11
+ import { StateError, TimeoutError } from '../errors.js';
12
12
  import { OwnershipStorageAdapter } from '../ownership-v21.js';
13
13
  import { ownershipErrorCode, ownershipTaskResult } from '../ownership-workflow.js';
14
14
  import { canonicalJson, digest, buildRequestBinding, verifyMembershipBinding, ProtocolError } from '../org-protocol.js';
@@ -141,14 +141,28 @@ export class CrossSdkJsAgent {
141
141
  rawPushObservations = [];
142
142
  rawPushObservationId = '';
143
143
  rawPushRpcMethods = [];
144
+ eventLedger = [];
145
+ rpcSpans = [];
146
+ observationGeneration = 0;
147
+ pullBarrier = null;
148
+ rpcFailures = [];
149
+ messageOperations = new Map();
144
150
  rpcObservedTransports = new WeakSet();
151
+ rpcObservedPipelines = new WeakSet();
152
+ rpcObservedPulls = new WeakMap();
145
153
  groupChangedEvents = [];
146
154
  groupInviteEvents = [];
147
155
  readStateEvents = [];
156
+ peersSnapshots = [];
148
157
  ownershipEvents = [];
149
158
  traces = new Map();
150
159
  sendResults = new Map();
151
160
  inlineRealtimePayloadV1 = true;
161
+ batchEventMode = false;
162
+ batchEvents = [];
163
+ singleEventCounts = { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
164
+ clientEventsBound = new WeakSet();
165
+ batchEventHandlers = new WeakMap();
152
166
  constructor() {
153
167
  this.client = this.newClient();
154
168
  }
@@ -166,28 +180,41 @@ export class CrossSdkJsAgent {
166
180
  }
167
181
  bindClientEvents() {
168
182
  this.installTransportRpcObserver();
183
+ if (this.clientEventsBound.has(this.client)) {
184
+ this.syncBatchEventHandlers();
185
+ return;
186
+ }
187
+ this.clientEventsBound.add(this.client);
169
188
  this.client.on('_raw.message.received', (data) => this.storeRawPushObservation('_raw.message.received', 'p2p', 'legacy', data));
170
189
  this.client.on('_raw.peer.v2.message_received', (data) => this.storeRawPushObservation('_raw.peer.v2.message_received', 'p2p', 'v2', data));
171
190
  this.client.on('_raw.group.message_created', (data) => this.storeRawPushObservation('_raw.group.message_created', 'group', 'legacy', data));
172
191
  this.client.on('_raw.group.v2.message_created', (data) => this.storeRawPushObservation('_raw.group.v2.message_created', 'group', 'v2', data));
173
192
  this.client.on('_raw.group.changed', (data) => this.storeRawGroupChangedObservation(data));
174
193
  this.client.on('message.received', (msg) => {
175
- void this.storeInboxItem(this.normalizeMessage(msg, true));
194
+ this.singleEventCounts['message.received']++;
195
+ this.storeApplicationEvent('message.received', msg);
176
196
  });
177
197
  this.client.on('message.undecryptable', (msg) => {
178
198
  void this.storeInboxItem(this.normalizeMessage(msg, false, 'undecryptable'));
179
199
  });
180
200
  this.client.on('group.message_created', (msg) => {
181
- void this.storeGroupInboxItem(this.normalizeGroupMessage(msg, true));
201
+ this.singleEventCounts['group.message_created']++;
202
+ this.storeApplicationEvent('group.message_created', msg);
182
203
  });
183
204
  this.client.on('group.message_undecryptable', (msg) => {
184
205
  void this.storeGroupInboxItem(this.normalizeGroupMessage(msg, false, 'undecryptable'));
185
206
  });
186
- this.client.on('group.changed', (data) => this.storeGroupChangedPublicEvent(data));
207
+ this.client.on('group.changed', (data) => {
208
+ this.singleEventCounts['group.changed']++;
209
+ this.storeApplicationEvent('group.changed', data);
210
+ });
187
211
  this.client.on('group.invite_received', (data) => this.storeGroupInviteEvent(data));
188
212
  for (const event of ['message.read_changed', 'group.read_changed', 'read_state_sync']) {
189
213
  this.client.on(event, (payload) => { this.readStateEvents.push({ event, payload: jsonSafe(payload) }); });
190
214
  }
215
+ this.client.on('message.peers_snapshot', (payload) => {
216
+ this.peersSnapshots.push({ event: 'message.peers_snapshot', payload: jsonSafe(payload) });
217
+ });
191
218
  for (const event of ['ownership.request', 'ownership.action_required', 'ownership.progress', 'ownership.completed', 'ownership.rejected', 'ownership.stopped', 'ownership.error']) {
192
219
  this.client.on(event, (payload) => { this.ownershipEvents.push({ event, payload: jsonSafe(payload) }); });
193
220
  }
@@ -198,16 +225,89 @@ export class CrossSdkJsAgent {
198
225
  this.recordTrace(textOf(payload.org_id), { stage: event, payload });
199
226
  });
200
227
  }
228
+ this.syncBatchEventHandlers();
229
+ }
230
+ syncBatchEventHandlers() {
231
+ const bound = this.batchEventHandlers.get(this.client);
232
+ if (!this.batchEventMode) {
233
+ if (bound)
234
+ for (const [event, handler] of bound)
235
+ this.client.off(event, handler);
236
+ this.batchEventHandlers.delete(this.client);
237
+ return;
238
+ }
239
+ if (bound)
240
+ return;
241
+ const handlers = new Map();
242
+ for (const event of ['message.batch', 'group.message_batch', 'group.event_batch']) {
243
+ const handler = (value) => {
244
+ const data = jsonObjectOf(value) ?? {};
245
+ const items = JSON.parse(stableStringify(jsonSafe(Array.isArray(data.items) ? data.items : [])));
246
+ this.batchEvents.push({ observation_id: this.rawPushObservationId, time_ms: Date.now(), event, namespace: textOf(data.namespace), source: textOf(data.source), items });
247
+ for (const item of items)
248
+ this.storeApplicationEvent(textOf(item.event), item.payload);
249
+ };
250
+ handlers.set(event, handler);
251
+ this.client.on(event, handler);
252
+ }
253
+ this.batchEventHandlers.set(this.client, handlers);
254
+ }
255
+ storeApplicationEvent(event, value) {
256
+ if (event === 'message.received') {
257
+ this.storePublicMessageEvent(event, 'p2p', value);
258
+ void this.storeInboxItem(this.normalizeMessage(value, true));
259
+ }
260
+ else if (event === 'group.message_created') {
261
+ this.storePublicMessageEvent(event, 'group', value);
262
+ void this.storeGroupInboxItem(this.normalizeGroupMessage(value, true));
263
+ }
264
+ else if (event === 'group.changed')
265
+ this.storeGroupChangedPublicEvent(value);
201
266
  }
202
267
  installTransportRpcObserver() {
268
+ const client = this.client;
269
+ const pipeline = client._rpcPipeline;
270
+ this.rpcObservedPipelines ??= new WeakSet();
271
+ this.rpcObservedPulls ??= new WeakMap();
272
+ if (pipeline?.bindActivePullCancellation && !this.rpcObservedPipelines.has(pipeline)) {
273
+ const originalBind = pipeline.bindActivePullCancellation;
274
+ this.rpcObservedPipelines.add(pipeline);
275
+ pipeline.bindActivePullCancellation = (method, params, request, gateKey = '') => {
276
+ const result = originalBind.call(pipeline, method, params, request, gateKey);
277
+ const observed = this.rpcObservedPulls.get(request);
278
+ if (!this.pullBarrier?.preserve || !observed || !['message.pull', 'message.v2.pull'].includes(method)
279
+ || !textOf(params.sender_aid) || typeof client._delivery?.peerNamespace !== 'function')
280
+ return result;
281
+ const key = gateKey || pipeline.pullGateKeyForCall(method, params);
282
+ const namespace = client._delivery?.peerNamespace(textOf(params.sender_aid));
283
+ const owners = [...pipeline.pullGateStates.values()].flatMap((state) => [...state.activeByNamespace.values()])
284
+ .filter((job) => job.key === key && job.namespace === namespace && job.running && !job.timedOut && !job.invalidated && job.cancel);
285
+ if (owners.length === 1)
286
+ observed.owner = { job: owners[0], cancel: owners[0].cancel, key, namespace: namespace };
287
+ return result;
288
+ };
289
+ }
203
290
  const transport = this.client._transport;
204
291
  transport?.setTraceMode('diag');
205
292
  if (!transport?.call || this.rpcObservedTransports.has(transport))
206
293
  return;
207
294
  const originalCall = transport.call;
295
+ const originalClose = transport.close;
208
296
  this.rpcObservedTransports.add(transport);
209
- transport.call = async (method, params, timeout, trace, background) => {
297
+ if (originalClose)
298
+ transport.close = async () => {
299
+ if (!this.pullBarrier?.preserve)
300
+ this.releasePullBarrier();
301
+ return await originalClose.call(transport);
302
+ };
303
+ transport.call = (method, params, timeout, trace, background) => {
210
304
  this.storeRawPushRpcMethod({ type: 'rpc', method, request_window: this.rawPushRequestWindow(params) });
305
+ const generation = this.observationGeneration;
306
+ const span = this.startRpcSpan(method, params);
307
+ const barrier = this.pullBarrier;
308
+ const controller = new AbortController();
309
+ let originalPromise;
310
+ const observed = {};
211
311
  let requestParams = params;
212
312
  if (method === 'auth.connect' && !this.inlineRealtimePayloadV1 && jsonObjectOf(params)) {
213
313
  const request = { ...jsonObjectOf(params) };
@@ -218,9 +318,196 @@ export class CrossSdkJsAgent {
218
318
  }
219
319
  requestParams = request;
220
320
  }
221
- return await originalCall.call(transport, method, requestParams, timeout, trace, background);
321
+ const observedPromise = (async () => {
322
+ try {
323
+ originalPromise = originalCall.call(transport, method, requestParams, timeout, trace, background);
324
+ const result = await originalPromise;
325
+ if (span && generation === this.observationGeneration)
326
+ this.finishRpcResponse(span, result);
327
+ const isPull = ['message.pull', 'message.v2.pull', 'group.pull', 'group.v2.pull', 'group.pull_events'].includes(method);
328
+ const isP2PPull = ['message.pull', 'message.v2.pull'].includes(method);
329
+ if (barrier && generation === this.observationGeneration && (isPull || barrier.method === method) && (barrier.scope !== 'p2p' || isP2PPull || barrier.method === method)
330
+ && (!barrier.sender || barrier.sender === span?.sender_aid) && (!barrier.method || barrier.method === method)) {
331
+ const duration = (typeof timeout === 'number' && timeout > 0 ? timeout : transport._timeout ?? 35) * 1000;
332
+ await this.waitPullBarrier(barrier, controller.signal, Math.max(1, duration - (Date.now() - Number(span?.start_ms ?? Date.now()))), method, span, observed.owner);
333
+ }
334
+ if (span && generation === this.observationGeneration) {
335
+ this.injectRPCFailure(method, span);
336
+ span.success = true;
337
+ }
338
+ return result;
339
+ }
340
+ catch (error) {
341
+ if (span && generation === this.observationGeneration) {
342
+ span.error = true;
343
+ span.success = false;
344
+ }
345
+ throw error;
346
+ }
347
+ finally {
348
+ if (span && generation === this.observationGeneration)
349
+ span.end_ms = Date.now();
350
+ }
351
+ })();
352
+ observedPromise.cancel = () => { controller.abort(); originalPromise?.cancel?.(); };
353
+ this.rpcObservedPulls.set(observedPromise, observed);
354
+ return observedPromise;
222
355
  };
223
356
  }
357
+ storePublicMessageEvent(event, scope, value) {
358
+ const data = jsonObjectOf(value) ?? {};
359
+ const internal = this.client;
360
+ const payload = JSON.parse(stableStringify(jsonSafe(data.payload ?? null)));
361
+ this.eventLedger.push({
362
+ observation_id: this.rawPushObservationId, scope, event, event_index: this.eventLedger.length + 1,
363
+ time_ms: Date.now(), receiver_aid: this.aid, device_id: textOf(internal._deviceId), slot_id: textOf(internal._slotId || this.slotId),
364
+ sender_aid: textOf(data.from ?? data.from_aid ?? data.sender_aid), group_id: textOf(data.group_id),
365
+ message_id: textOf(data.message_id ?? data.id), seq: Number(data.seq ?? data.message_seq ?? data.msg_seq ?? 0) || 0,
366
+ storage: textOf(data.storage), volatile_cursor: textOf(data.volatile_cursor),
367
+ payload, payload_sha256: sha256Json(payload), need_render: data.need_render !== false,
368
+ ...(Object.prototype.hasOwnProperty.call(data, 'mention_mode') ? { mention_mode: data.mention_mode } : {}),
369
+ });
370
+ }
371
+ startRpcSpan(method, value) {
372
+ if (!['message.pull', 'message.v2.pull', 'group.pull', 'group.v2.pull', 'group.events', 'group.pull_events', 'message.peers', 'message.ack', 'message.v2.ack', 'group.ack', 'group.v2.ack', 'group.ack_messages', 'group.ack_events'].includes(method))
373
+ return null;
374
+ const params = jsonObjectOf(value) ?? {};
375
+ const senderAid = textOf(params.sender_aid ?? params.peer_aid).trim();
376
+ const groupId = textOf(params.group_id ?? params.group_aid).trim();
377
+ const groupEvent = ['group.events', 'group.pull_events', 'group.ack_events'].includes(method);
378
+ const group = method.startsWith('group.');
379
+ const span = {
380
+ observation_id: this.rawPushObservationId, scope: group ? 'group' : 'p2p', span_id: this.rpcSpans.length + 1,
381
+ boundary: 'transport_call', method, start_ms: Date.now(), response_ms: null, end_ms: null, success: null,
382
+ cursor_scope: textOf(params.cursor_scope) || (group ? groupEvent ? 'group_event' : 'group' : senderAid || method === 'message.peers' ? 'peer' : 'global'),
383
+ sender_aid: senderAid, group_id: groupId,
384
+ namespace: group ? groupId ? `${groupEvent ? 'group_event' : 'group'}:${groupId}` : '' : method === 'message.peers' ? '' : this.syncNamespace('p2p', '', senderAid),
385
+ request_window: this.rpcSpanRequestWindow(params), response_message_count: 0, response_volatile_count: 0, response_event_count: 0, response_peer_count: 0, response_item_keys: [], response_event_keys: [],
386
+ has_more: null, has_more_persistent: null, has_more_volatile: null, persistent_has_more: null, volatile_has_more: null,
387
+ effective_ack_seq: null, peer_ack_seq: null, server_ack_seq: null, error: false,
388
+ };
389
+ this.rpcSpans.push(span);
390
+ return span;
391
+ }
392
+ rpcSpanRequestWindow(params) {
393
+ const out = this.rawPushRequestWindow(params);
394
+ for (const key of ['before_seq', 'until_seq', 'after_message_seq', 'after_event_seq', 'snapshot_head_seq', 'seq', 'up_to_seq', 'msg_seq', 'event_seq']) {
395
+ const value = params[key];
396
+ if (typeof value === 'number' && Number.isSafeInteger(value))
397
+ out[key] = value;
398
+ }
399
+ for (const key of ['after_volatile_cursor', 'volatile_cursor', 'mode', 'after_sender_aid', 'peer_mode', 'directory_mode'])
400
+ if (typeof params[key] === 'string')
401
+ out[key] = params[key];
402
+ for (const key of ['force', 'pending_only'])
403
+ if (typeof params[key] === 'boolean')
404
+ out[key] = params[key];
405
+ for (const key of ['volatile_cursors', 'volatile_ids'])
406
+ if (Array.isArray(params[key]))
407
+ out[key] = params[key].filter((item) => typeof item === 'string');
408
+ return out;
409
+ }
410
+ finishRpcResponse(span, value) {
411
+ const result = jsonObjectOf(value) ?? {};
412
+ span.response_ms = Date.now();
413
+ const messages = Array.isArray(result.messages) ? result.messages : [];
414
+ span.response_message_count = messages.length;
415
+ span.response_volatile_count = messages.filter((item) => jsonObjectOf(item)?.storage === 'volatile').length;
416
+ span.response_event_count = Array.isArray(result.events) ? result.events.length : 0;
417
+ span.response_peer_count = Array.isArray(result.peers) ? result.peers.length : 0;
418
+ const sequenceOf = (...values) => values.find((value) => typeof value === 'number' && Number.isFinite(value) && Number.isInteger(value)) ?? 0;
419
+ const itemKeys = new Map();
420
+ for (const key of ['messages', 'volatile_messages', 'volatile'])
421
+ for (const value of Array.isArray(result[key]) ? result[key] : []) {
422
+ const item = jsonObjectOf(value) ?? {};
423
+ const cursor = textOf(item.volatile_cursor);
424
+ const identity = { message_id: textOf(item.message_id ?? item.id), seq: sequenceOf(item.seq, item.message_seq, item.msg_seq), storage: textOf(item.storage) || (cursor ? 'volatile' : 'persistent'), volatile_cursor: cursor };
425
+ itemKeys.set(stableStringify(identity), identity);
426
+ }
427
+ span.response_item_keys = [...itemKeys.values()];
428
+ span.response_event_keys = (Array.isArray(result.events) ? result.events : []).map((value) => {
429
+ const item = jsonObjectOf(value) ?? {};
430
+ return { event_id: textOf(item.event_id), event_seq: sequenceOf(item.event_seq, item.seq) };
431
+ });
432
+ span.has_more = typeof result.has_more === 'boolean' ? result.has_more : null;
433
+ span.has_more_persistent = typeof result.has_more_persistent === 'boolean' ? result.has_more_persistent : typeof result.persistent_has_more === 'boolean' ? result.persistent_has_more : null;
434
+ span.has_more_volatile = typeof result.has_more_volatile === 'boolean' ? result.has_more_volatile : typeof result.volatile_has_more === 'boolean' ? result.volatile_has_more : null;
435
+ span.persistent_has_more = typeof result.persistent_has_more === 'boolean' ? result.persistent_has_more : null;
436
+ span.volatile_has_more = typeof result.volatile_has_more === 'boolean' ? result.volatile_has_more : null;
437
+ const safeSeq = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : null;
438
+ span.peer_ack_seq = safeSeq(result.peer_ack_seq);
439
+ span.server_ack_seq = safeSeq(result.server_ack_seq);
440
+ span.effective_ack_seq = safeSeq(result.peer_ack_seq ?? result.effective_ack_seq ?? result.ack_seq ?? result.server_ack_seq ?? jsonObjectOf(result.cursor)?.current_seq ?? result.cursor);
441
+ const volatileAck = jsonObjectOf(result.volatile_ack);
442
+ if (volatileAck) {
443
+ const ack = {};
444
+ for (const key of ['accepted', 'expired'])
445
+ if (typeof volatileAck[key] === 'number' && Number.isSafeInteger(volatileAck[key]))
446
+ ack[key] = volatileAck[key];
447
+ if (typeof volatileAck.storage === 'string')
448
+ ack.storage = volatileAck.storage;
449
+ span.volatile_ack = ack;
450
+ }
451
+ }
452
+ releasePullBarrier() {
453
+ if (!this.pullBarrier || this.pullBarrier.released)
454
+ return;
455
+ this.pullBarrier.released = true;
456
+ this.pullBarrier.release();
457
+ }
458
+ injectRPCFailure(method, span) {
459
+ const failure = this.rpcFailures?.find((item) => !item.hit && item.method === method && item.sender_aid === span.sender_aid);
460
+ if (!failure)
461
+ return;
462
+ failure.hit = true;
463
+ span.injected_failure = failure.kind ?? 'error';
464
+ if (failure.kind === 'timeout')
465
+ throw new TimeoutError('cross-sdk 响应后一次超时');
466
+ throw new Error('cross-sdk 响应后一次失败');
467
+ }
468
+ resetObservationLedgers(count = 0, scope = 'all', controls = {}) {
469
+ this.releasePullBarrier();
470
+ this.observationGeneration++;
471
+ this.eventLedger = [];
472
+ this.rpcSpans = [];
473
+ this.batchEvents = [];
474
+ this.singleEventCounts = { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
475
+ this.rpcFailures = Array.isArray(controls.rpc_failures) ? controls.rpc_failures.map((item) => ({ ...jsonObjectOf(item) })) : [];
476
+ let release;
477
+ const promise = new Promise((resolve) => { release = resolve; });
478
+ this.pullBarrier = { target: count, admitted: 0, waiting: 0, released: count <= 0, scope: scope === 'p2p' ? 'p2p' : 'all', promise, release,
479
+ sender: textOf(controls.pull_barrier_sender_aid), method: textOf(controls.pull_barrier_method), preserve: controls.preserve_pull_barrier === true };
480
+ }
481
+ async waitPullBarrier(barrier, signal, timeout, method, span, owner) {
482
+ if (barrier.released || barrier.admitted >= barrier.target)
483
+ return;
484
+ if (signal.aborted && !barrier.preserve)
485
+ throw new Error('transport call cancelled');
486
+ barrier.admitted++;
487
+ barrier.waiting++;
488
+ if (barrier.preserve) {
489
+ barrier.heldMethod = method;
490
+ barrier.owner = owner;
491
+ barrier.span = span ?? undefined;
492
+ }
493
+ let timer;
494
+ let onAbort;
495
+ try {
496
+ await Promise.race([barrier.promise, new Promise((_resolve, reject) => {
497
+ onAbort = () => reject(new Error('transport call cancelled'));
498
+ if (!barrier.preserve)
499
+ signal.addEventListener('abort', onAbort, { once: true });
500
+ timer = setTimeout(() => reject(new Error('pull barrier timed out')), timeout);
501
+ })]);
502
+ }
503
+ finally {
504
+ barrier.waiting--;
505
+ if (timer)
506
+ clearTimeout(timer);
507
+ if (onAbort)
508
+ signal.removeEventListener('abort', onAbort);
509
+ }
510
+ }
224
511
  storeRawPushObservation(rawEvent, scope, defaultKind, value) {
225
512
  const data = jsonObjectOf(value) ?? {};
226
513
  const hasInline = Object.prototype.hasOwnProperty.call(data, 'inline_message');
@@ -308,6 +595,7 @@ export class CrossSdkJsAgent {
308
595
  };
309
596
  }
310
597
  async close() {
598
+ this.resetObservationLedgers();
311
599
  try {
312
600
  await this.client.close();
313
601
  }
@@ -465,6 +753,7 @@ export class CrossSdkJsAgent {
465
753
  decrypted,
466
754
  encrypted: Boolean(data.e2ee ?? data.encrypted),
467
755
  seq: Number(data.seq ?? data.message_seq ?? 0) || 0,
756
+ need_render: data.need_render !== false,
468
757
  ack_seq: Number(data.ack_seq ?? 0) || 0,
469
758
  storage: textOf(data.storage),
470
759
  volatile_cursor: textOf(data.volatile_cursor),
@@ -489,6 +778,7 @@ export class CrossSdkJsAgent {
489
778
  decrypted,
490
779
  encrypted: Boolean(data.e2ee ?? data.encrypted),
491
780
  seq: Number(data.seq ?? data.message_seq ?? data.msg_seq ?? 0) || 0,
781
+ need_render: data.need_render !== false,
492
782
  error_code: errorCode,
493
783
  raw_sha256: sha256Json(data),
494
784
  ...envelopeMetadata(data),
@@ -611,6 +901,7 @@ export class CrossSdkJsAgent {
611
901
  }
612
902
  if (req.method === 'POST' && url.pathname === '/reset') {
613
903
  const body = await readJson(req);
904
+ this.resetObservationLedgers();
614
905
  const traceId = textOf(body.trace_id ?? '');
615
906
  if (traceId) {
616
907
  this.inbox = this.inbox.filter((item) => item.trace_id !== traceId);
@@ -621,6 +912,7 @@ export class CrossSdkJsAgent {
621
912
  else {
622
913
  this.inbox = [];
623
914
  this.groupInbox = [];
915
+ this.peersSnapshots = [];
624
916
  this.traces.clear();
625
917
  this.sendResults.clear();
626
918
  }
@@ -629,11 +921,25 @@ export class CrossSdkJsAgent {
629
921
  }
630
922
  if (req.method === 'POST' && url.pathname === '/test/raw-push/reset') {
631
923
  const body = await readJson(req);
924
+ if ((body.pull_barrier_count !== undefined && (typeof body.pull_barrier_count !== 'number' || !Number.isSafeInteger(body.pull_barrier_count) || body.pull_barrier_count < 0)) ||
925
+ (body.pull_barrier_scope !== undefined && !['all', 'p2p'].includes(body.pull_barrier_scope)) ||
926
+ (body.observation_id !== undefined && typeof body.observation_id !== 'string') ||
927
+ (body.release_pull_barrier !== undefined && typeof body.release_pull_barrier !== 'boolean')) {
928
+ sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid observation parameters' });
929
+ return;
930
+ }
931
+ if (body.release_pull_barrier === true) {
932
+ this.releasePullBarrier();
933
+ sendJson(res, 200, { ok: true, observation_id: this.rawPushObservationId });
934
+ return;
935
+ }
936
+ this.resetObservationLedgers(body.pull_barrier_count, textOf(body.pull_barrier_scope), body);
632
937
  this.rawPushObservations = [];
633
938
  this.rawPushRpcMethods = [];
634
939
  this.groupChangedEvents = [];
635
940
  this.groupInviteEvents = [];
636
941
  this.readStateEvents = [];
942
+ this.peersSnapshots = [];
637
943
  this.rawPushObservationId = textOf(body.observation_id ?? '');
638
944
  sendJson(res, 200, { ok: true, observation_id: this.rawPushObservationId });
639
945
  return;
@@ -652,6 +958,11 @@ export class CrossSdkJsAgent {
652
958
  let rpcEntries = [...this.rawPushRpcMethods];
653
959
  let groupChangedEvents = [...this.groupChangedEvents];
654
960
  let groupInviteEvents = [...this.groupInviteEvents];
961
+ let eventLedger = [...this.eventLedger];
962
+ let rpcSpans = [...this.rpcSpans];
963
+ let batchEvents = [...this.batchEvents];
964
+ if (observationId)
965
+ batchEvents = batchEvents.filter((item) => textOf(item.observation_id) === observationId);
655
966
  if (observationId)
656
967
  rpcEntries = rpcEntries.filter((item) => textOf(item.observation_id) === observationId);
657
968
  if (observationId)
@@ -662,17 +973,26 @@ export class CrossSdkJsAgent {
662
973
  groupChangedEvents = groupChangedEvents.filter((item) => textOf(item.group_id) === filters.group_id);
663
974
  if (filters.group_id)
664
975
  groupInviteEvents = groupInviteEvents.filter((item) => textOf(item.group_id) === filters.group_id);
976
+ for (const key of ['observation_id', 'scope', 'group_id'])
977
+ if (filters[key]) {
978
+ eventLedger = eventLedger.filter((item) => textOf(item[key]) === filters[key]);
979
+ rpcSpans = rpcSpans.filter((item) => textOf(item[key]) === filters[key]);
980
+ }
665
981
  rpcEntries = rpcEntries.slice(-limit);
666
982
  const rpcMethods = rpcEntries.map((item) => textOf(item.method));
667
983
  const rpcMethodCounts = {};
668
984
  for (const method of rpcMethods)
669
985
  rpcMethodCounts[method] = Number(rpcMethodCounts[method] ?? 0) + 1;
670
986
  sendJson(res, 200, {
671
- ok: true, items, count: items.length, rpc_methods: rpcMethods, rpc_method_counts: rpcMethodCounts,
987
+ ok: true, time_ms: Date.now(), items, count: items.length, rpc_methods: rpcMethods, rpc_method_counts: rpcMethodCounts,
672
988
  rpc_calls: rpcEntries,
673
989
  group_changed_events: groupChangedEvents.slice(-limit),
674
990
  group_invite_events: groupInviteEvents.slice(-limit),
675
991
  read_state_events: this.readStateEvents.slice(-limit),
992
+ peers_snapshots: this.peersSnapshots.slice(-limit),
993
+ event_ledger: eventLedger, rpc_spans: rpcSpans,
994
+ batch_event_mode: this.batchEventMode, batch_events: batchEvents, single_event_counts: this.singleEventCounts,
995
+ pull_barrier: { target: this.pullBarrier?.target ?? 0, waiting: this.pullBarrier?.waiting ?? 0, released: this.pullBarrier?.released ?? true, scope: this.pullBarrier?.scope ?? 'all' },
676
996
  });
677
997
  return;
678
998
  }
@@ -1693,6 +2013,8 @@ export class CrossSdkJsAgent {
1693
2013
  };
1694
2014
  if (body.ttl !== undefined)
1695
2015
  params.ttl = Number(body.ttl);
2016
+ if (body.need_render !== undefined)
2017
+ params.need_render = body.need_render;
1696
2018
  const result = await this.client.call('message.send', params);
1697
2019
  const resultObj = jsonObjectOf(result) ?? {};
1698
2020
  const response = {
@@ -1727,6 +2049,10 @@ export class CrossSdkJsAgent {
1727
2049
  const params = {};
1728
2050
  if (seq > 0)
1729
2051
  params.seq = seq;
2052
+ if (textOf(body.sender_aid).trim())
2053
+ params.sender_aid = textOf(body.sender_aid).trim();
2054
+ if (body.cursor_scope !== undefined)
2055
+ params.cursor_scope = body.cursor_scope;
1730
2056
  if (Array.isArray(body.volatile_cursors))
1731
2057
  params.volatile_cursors = body.volatile_cursors;
1732
2058
  else if (textOf(body.volatile_cursor))
@@ -1748,9 +2074,26 @@ export class CrossSdkJsAgent {
1748
2074
  }
1749
2075
  async pull(req, res) {
1750
2076
  const body = await readJson(req);
1751
- const afterSeq = Number(body.after_seq ?? 0) || 0;
1752
- const limit = Number(body.limit ?? 50) || 50;
1753
- const params = { after_seq: afterSeq, limit };
2077
+ if ((body.limit !== undefined && (typeof body.limit !== 'number' || !Number.isSafeInteger(body.limit) || body.limit < 1 || body.limit > 200)) ||
2078
+ (body.max_pages !== undefined && (typeof body.max_pages !== 'number' || !Number.isSafeInteger(body.max_pages) || body.max_pages < 1)) ||
2079
+ (body.after_seq !== undefined && (typeof body.after_seq !== 'number' || !Number.isSafeInteger(body.after_seq) || body.after_seq < 0)) ||
2080
+ ['sender_aid', 'cursor_scope', 'after_volatile_cursor'].some((key) => body[key] !== undefined && typeof body[key] !== 'string') ||
2081
+ (body.force !== undefined && typeof body.force !== 'boolean') ||
2082
+ (body.window_mode !== undefined && !['forward', 'tail'].includes(body.window_mode))) {
2083
+ sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid pull parameters' });
2084
+ return;
2085
+ }
2086
+ const senderAid = textOf(body.sender_aid).trim();
2087
+ const limit = body.limit ?? 50;
2088
+ const params = { limit };
2089
+ if (senderAid)
2090
+ params.sender_aid = senderAid;
2091
+ if (body.after_seq !== undefined)
2092
+ params.after_seq = body.after_seq;
2093
+ for (const key of ['cursor_scope', 'force', 'window_mode', 'max_pages']) {
2094
+ if (body[key] !== undefined)
2095
+ params[key] = body[key];
2096
+ }
1754
2097
  if (textOf(body.after_volatile_cursor))
1755
2098
  params.after_volatile_cursor = textOf(body.after_volatile_cursor);
1756
2099
  try {
@@ -1771,6 +2114,20 @@ export class CrossSdkJsAgent {
1771
2114
  }
1772
2115
  async messageCall(req, res) {
1773
2116
  const body = await readJson(req);
2117
+ if (['start', 'status', 'detach'].includes(textOf(body.action))) {
2118
+ this.messageOperations ??= new Map();
2119
+ const id = textOf(body.operation_id);
2120
+ if (body.action === 'start') {
2121
+ const operation = { ok: true, operation_id: id, status: 'running', started_ms: Date.now() };
2122
+ this.messageOperations.set(id, operation);
2123
+ Promise.resolve().then(() => { operation.entered_ms = Date.now(); return this.client.call(textOf(body.method), (jsonObjectOf(body.params) ?? {})); }).then((result) => Object.assign(operation, { status: 'completed', end_ms: Date.now(), result: jsonSafe(result) }), (error) => Object.assign(operation, { status: 'failed', end_ms: Date.now(), error_code: error instanceof Error ? error.name : 'Error', error_message: error instanceof Error ? error.message : String(error) }));
2124
+ }
2125
+ const operation = this.messageOperations.get(id);
2126
+ if (body.action === 'detach')
2127
+ Object.assign(operation, { detached: true, cancellation: 'application_wait_only', detached_ms: Date.now() });
2128
+ sendJson(res, 200, operation);
2129
+ return;
2130
+ }
1774
2131
  const traceId = textOf(body.trace_id || crypto.randomUUID().replace(/-/g, ''));
1775
2132
  const method = textOf(body.method).trim();
1776
2133
  const params = jsonObjectOf(body.params) ?? {};
@@ -1802,15 +2159,15 @@ export class CrossSdkJsAgent {
1802
2159
  sendJson(res, 500, error);
1803
2160
  }
1804
2161
  }
1805
- syncNamespace(scope, groupId = '') {
2162
+ syncNamespace(scope, groupId = '', senderAid = '') {
1806
2163
  if (scope === 'p2p')
1807
- return `p2p:${this.aid}`;
2164
+ return senderAid ? `p2p.peer.v1/${Buffer.from(senderAid, 'utf8').toString('base64url')}` : `p2p:${this.aid}`;
1808
2165
  if (scope === 'group' && groupId)
1809
2166
  return `group:${groupId}`;
1810
2167
  throw new Error('scope must be p2p, or group with group_id');
1811
2168
  }
1812
- syncStateValue(scope, groupId = '') {
1813
- const namespace = this.syncNamespace(scope, groupId);
2169
+ syncStateValue(scope, groupId = '', senderAid = '') {
2170
+ const namespace = this.syncNamespace(scope, groupId, senderAid);
1814
2171
  const tracker = this.client._seqTracker;
1815
2172
  if (!tracker)
1816
2173
  throw new Error('SeqTracker is unavailable');
@@ -1819,9 +2176,10 @@ export class CrossSdkJsAgent {
1819
2176
  syncState(url, res) {
1820
2177
  const scope = textOf(url.searchParams.get('scope') || 'p2p').trim().toLowerCase();
1821
2178
  const groupId = textOf(url.searchParams.get('group_id') || '').trim();
2179
+ const senderAid = textOf(url.searchParams.get('sender_aid') || '').trim();
1822
2180
  try {
1823
- const snapshot = this.syncStateValue(scope, groupId);
1824
- sendJson(res, 200, { ok: true, scope, ...snapshot });
2181
+ const snapshot = this.syncStateValue(scope, groupId, senderAid);
2182
+ sendJson(res, 200, { ok: true, scope, ...snapshot, peers_snapshots: this.peersSnapshots });
1825
2183
  }
1826
2184
  catch (err) {
1827
2185
  sendJson(res, 400, {
@@ -1841,27 +2199,24 @@ export class CrossSdkJsAgent {
1841
2199
  const body = await readJson(req);
1842
2200
  const scope = textOf(body.scope || 'p2p').trim().toLowerCase();
1843
2201
  const groupId = textOf(body.group_id || '').trim();
2202
+ const senderAid = textOf(body.sender_aid || '').trim();
1844
2203
  const afterSeq = Number(body.after_seq ?? 0) || 0;
1845
2204
  const limit = Number(body.limit ?? 50) || 50;
1846
2205
  try {
1847
- const params = { after_seq: afterSeq, limit };
1848
- if (tail)
1849
- params.window_mode = 'tail';
2206
+ const params = { limit, ...(tail ? { window_mode: 'tail' } : {}) };
2207
+ if (scope === 'group' || body.after_seq !== undefined)
2208
+ params.after_seq = afterSeq;
2209
+ if (scope === 'p2p' && senderAid)
2210
+ params.sender_aid = senderAid;
2211
+ for (const key of ['cursor_scope', 'force', 'max_pages', 'after_volatile_cursor']) {
2212
+ if (body[key] !== undefined)
2213
+ params[key] = body[key];
2214
+ }
1850
2215
  const method = scope === 'group' ? 'group.pull' : 'message.pull';
1851
2216
  if (scope === 'group')
1852
2217
  params.group_id = groupId;
1853
- this.syncNamespace(scope, groupId);
1854
- let result;
1855
- if (tail) {
1856
- result = await this.client.call(method, params);
1857
- }
1858
- else {
1859
- const transport = this.client._transport;
1860
- if (!transport?.call)
1861
- throw new Error('raw transport is unavailable');
1862
- const wireMethod = scope === 'group' ? 'group.v2.pull' : 'message.v2.pull';
1863
- result = await transport.call(wireMethod, params);
1864
- }
2218
+ this.syncNamespace(scope, groupId, senderAid);
2219
+ const result = await this.client.call(method, params);
1865
2220
  const resultObj = jsonObjectOf(result) ?? {};
1866
2221
  const messages = Array.isArray(resultObj.messages) ? resultObj.messages : [];
1867
2222
  if (tail) {
@@ -1872,7 +2227,7 @@ export class CrossSdkJsAgent {
1872
2227
  await this.storeInboxItem(this.normalizeMessage(msg, true));
1873
2228
  }
1874
2229
  }
1875
- const snapshot = this.syncStateValue(scope, groupId);
2230
+ const snapshot = this.syncStateValue(scope, groupId, senderAid);
1876
2231
  sendJson(res, 200, { ok: true, scope, ...snapshot, result: jsonSafe(result) });
1877
2232
  }
1878
2233
  catch (err) {
@@ -1888,6 +2243,7 @@ export class CrossSdkJsAgent {
1888
2243
  const body = await readJson(req);
1889
2244
  const scope = textOf(body.scope || 'p2p').trim().toLowerCase();
1890
2245
  const groupId = textOf(body.group_id || '').trim();
2246
+ const senderAid = textOf(body.sender_aid || '').trim();
1891
2247
  const beforeSeq = Number(body.before_seq ?? 0) || 0;
1892
2248
  const limit = Number(body.limit ?? 50) || 50;
1893
2249
  try {
@@ -1895,9 +2251,13 @@ export class CrossSdkJsAgent {
1895
2251
  const method = scope === 'group' ? 'group.history' : 'message.history';
1896
2252
  if (scope === 'group')
1897
2253
  params.group_id = groupId;
1898
- this.syncNamespace(scope, groupId);
2254
+ if (scope === 'p2p' && senderAid)
2255
+ params.sender_aid = senderAid;
2256
+ if (body.cursor_scope !== undefined)
2257
+ params.cursor_scope = body.cursor_scope;
2258
+ this.syncNamespace(scope, groupId, senderAid);
1899
2259
  const result = await this.client.call(method, params);
1900
- const snapshot = this.syncStateValue(scope, groupId);
2260
+ const snapshot = this.syncStateValue(scope, groupId, senderAid);
1901
2261
  sendJson(res, 200, { ok: true, scope, ...snapshot, result: jsonSafe(result) });
1902
2262
  }
1903
2263
  catch (err) {
@@ -1912,7 +2272,27 @@ export class CrossSdkJsAgent {
1912
2272
  async sessionDisconnect(res) {
1913
2273
  try {
1914
2274
  this.ready = false;
1915
- await this.client.disconnect();
2275
+ if (!this.pullBarrier?.preserve)
2276
+ this.releasePullBarrier();
2277
+ const barrier = this.pullBarrier;
2278
+ let restore;
2279
+ if (barrier?.preserve && barrier.waiting === 1 && !barrier.released && ['message.pull', 'message.v2.pull'].includes(barrier.heldMethod ?? '')) {
2280
+ const binding = barrier.owner, pipeline = this.client._rpcPipeline;
2281
+ const owners = [...(pipeline?.pullGateStates.values() ?? [])].flatMap((state) => [...state.activeByNamespace.values()])
2282
+ .filter((job) => job === binding?.job && job.key === binding.key && job.namespace === binding.namespace && job.running && !job.timedOut && !job.invalidated && job.cancel === binding.cancel);
2283
+ if (owners.length !== 1)
2284
+ throw new Error('保留响应无法定位唯一受跟踪的 pull owner');
2285
+ const owner = owners[0], settled = owner.settled;
2286
+ owner.settled = Promise.resolve();
2287
+ restore = () => { owner.settled = settled; };
2288
+ barrier.span.barrier_owner_isolated = true;
2289
+ }
2290
+ try {
2291
+ await this.client.disconnect();
2292
+ }
2293
+ finally {
2294
+ restore?.();
2295
+ }
1916
2296
  sendJson(res, 200, { ok: true, state: textOf(this.client.state) });
1917
2297
  }
1918
2298
  catch (err) {
@@ -1925,14 +2305,21 @@ export class CrossSdkJsAgent {
1925
2305
  }
1926
2306
  async sessionConnect(req, res) {
1927
2307
  const body = await readJson(req);
2308
+ if (['background_sync', 'inline_realtime_payload_v1', 'batch_events'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
2309
+ sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid session parameters' });
2310
+ return;
2311
+ }
1928
2312
  const backgroundSync = body.background_sync === true;
1929
2313
  if (Object.prototype.hasOwnProperty.call(body, 'inline_realtime_payload_v1')) {
1930
2314
  this.inlineRealtimePayloadV1 = body.inline_realtime_payload_v1 !== false;
1931
2315
  }
2316
+ if (typeof body.batch_events === 'boolean')
2317
+ this.batchEventMode = body.batch_events;
2318
+ this.syncBatchEventHandlers();
1932
2319
  try {
1933
2320
  await this.client.connect({ auto_reconnect: false, background_sync: backgroundSync });
1934
2321
  this.ready = true;
1935
- sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1 });
2322
+ sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1, batch_events: this.batchEventMode });
1936
2323
  }
1937
2324
  catch (err) {
1938
2325
  sendJson(res, 500, {
@@ -1944,19 +2331,27 @@ export class CrossSdkJsAgent {
1944
2331
  }
1945
2332
  async sessionRestart(req, res) {
1946
2333
  const body = await readJson(req);
2334
+ if (['background_sync', 'inline_realtime_payload_v1', 'batch_events'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
2335
+ sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid session parameters' });
2336
+ return;
2337
+ }
1947
2338
  const backgroundSync = body.background_sync === true;
1948
2339
  if (Object.prototype.hasOwnProperty.call(body, 'inline_realtime_payload_v1')) {
1949
2340
  this.inlineRealtimePayloadV1 = body.inline_realtime_payload_v1 !== false;
1950
2341
  }
2342
+ if (typeof body.batch_events === 'boolean')
2343
+ this.batchEventMode = body.batch_events;
2344
+ this.syncBatchEventHandlers();
1951
2345
  this.ready = false;
1952
2346
  try {
2347
+ this.releasePullBarrier();
1953
2348
  await this.client.close();
1954
2349
  this.client = this.newClient();
1955
2350
  this.bindClientEvents();
1956
2351
  await this.ensureConnected(backgroundSync);
1957
2352
  this.startupError = '';
1958
2353
  this.ready = true;
1959
- sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1 });
2354
+ sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1, batch_events: this.batchEventMode });
1960
2355
  }
1961
2356
  catch (err) {
1962
2357
  this.startupError = `${err instanceof Error ? err.name : 'Error'}: ${err instanceof Error ? err.message : String(err)}`;
@@ -2070,6 +2465,7 @@ export class CrossSdkJsAgent {
2070
2465
  payload: { type: 'text', text, trace_id: traceId, case_id: textOf(body.case_id || traceId) },
2071
2466
  encrypt: e2ee,
2072
2467
  message_id: messageId,
2468
+ ...(body.need_render !== undefined ? { need_render: body.need_render } : {}),
2073
2469
  });
2074
2470
  const resultObj = jsonObjectOf(result) ?? {};
2075
2471
  const response = {