@agentunion/fastaun-browser 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 (154) 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 +16 -4
  55. package/dist/agent-md-schema.d.ts.map +1 -1
  56. package/dist/agent-md-schema.js +65 -30
  57. package/dist/agent-md-schema.js.map +1 -1
  58. package/dist/agent-md.d.ts +35 -3
  59. package/dist/agent-md.d.ts.map +1 -1
  60. package/dist/agent-md.js +384 -150
  61. package/dist/agent-md.js.map +1 -1
  62. package/dist/aid-store.d.ts +59 -0
  63. package/dist/aid-store.d.ts.map +1 -1
  64. package/dist/aid-store.js +202 -16
  65. package/dist/aid-store.js.map +1 -1
  66. package/dist/aid.d.ts +4 -1
  67. package/dist/aid.d.ts.map +1 -1
  68. package/dist/aid.js +24 -42
  69. package/dist/aid.js.map +1 -1
  70. package/dist/auth.d.ts +1 -1
  71. package/dist/auth.d.ts.map +1 -1
  72. package/dist/auth.js +89 -18
  73. package/dist/auth.js.map +1 -1
  74. package/dist/bundle.js +6410 -1848
  75. package/dist/cert-utils.d.ts +50 -1
  76. package/dist/cert-utils.d.ts.map +1 -1
  77. package/dist/cert-utils.js +123 -56
  78. package/dist/cert-utils.js.map +1 -1
  79. package/dist/client/delivery.d.ts +108 -16
  80. package/dist/client/delivery.d.ts.map +1 -1
  81. package/dist/client/delivery.js +1411 -184
  82. package/dist/client/delivery.js.map +1 -1
  83. package/dist/client/rpc-pipeline.d.ts +4 -1
  84. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  85. package/dist/client/rpc-pipeline.js +203 -117
  86. package/dist/client/rpc-pipeline.js.map +1 -1
  87. package/dist/client/v2-e2ee.d.ts +22 -3
  88. package/dist/client/v2-e2ee.d.ts.map +1 -1
  89. package/dist/client/v2-e2ee.js +1312 -684
  90. package/dist/client/v2-e2ee.js.map +1 -1
  91. package/dist/client.d.ts +23 -2
  92. package/dist/client.d.ts.map +1 -1
  93. package/dist/client.js +419 -101
  94. package/dist/client.js.map +1 -1
  95. package/dist/config.d.ts +4 -0
  96. package/dist/config.d.ts.map +1 -1
  97. package/dist/config.js +1 -0
  98. package/dist/config.js.map +1 -1
  99. package/dist/crypto.d.ts.map +1 -1
  100. package/dist/crypto.js +8 -6
  101. package/dist/crypto.js.map +1 -1
  102. package/dist/events.d.ts +24 -3
  103. package/dist/events.d.ts.map +1 -1
  104. package/dist/events.js +171 -29
  105. package/dist/events.js.map +1 -1
  106. package/dist/facades.d.ts +42 -1
  107. package/dist/facades.d.ts.map +1 -1
  108. package/dist/facades.js +368 -12
  109. package/dist/facades.js.map +1 -1
  110. package/dist/index.d.ts +6 -4
  111. package/dist/index.d.ts.map +1 -1
  112. package/dist/index.js +1 -1
  113. package/dist/index.js.map +1 -1
  114. package/dist/keystore/index.d.ts +14 -1
  115. package/dist/keystore/index.d.ts.map +1 -1
  116. package/dist/keystore/indexeddb-identity-store.d.ts +4 -0
  117. package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
  118. package/dist/keystore/indexeddb-identity-store.js +30 -13
  119. package/dist/keystore/indexeddb-identity-store.js.map +1 -1
  120. package/dist/keystore/indexeddb-shared.d.ts +19 -2
  121. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  122. package/dist/keystore/indexeddb-shared.js +193 -6
  123. package/dist/keystore/indexeddb-shared.js.map +1 -1
  124. package/dist/keystore/indexeddb-token-store.d.ts +15 -2
  125. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  126. package/dist/keystore/indexeddb-token-store.js +308 -27
  127. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  128. package/dist/org-protocol.d.ts +45 -0
  129. package/dist/org-protocol.d.ts.map +1 -1
  130. package/dist/org-protocol.js +827 -21
  131. package/dist/org-protocol.js.map +1 -1
  132. package/dist/ownership-v21.d.ts +43 -9
  133. package/dist/ownership-v21.d.ts.map +1 -1
  134. package/dist/ownership-v21.js +183 -81
  135. package/dist/ownership-v21.js.map +1 -1
  136. package/dist/ownership-workflow.d.ts +36 -1
  137. package/dist/ownership-workflow.d.ts.map +1 -1
  138. package/dist/ownership-workflow.js +610 -89
  139. package/dist/ownership-workflow.js.map +1 -1
  140. package/dist/seq-tracker.d.ts +2 -0
  141. package/dist/seq-tracker.d.ts.map +1 -1
  142. package/dist/seq-tracker.js +3 -2
  143. package/dist/seq-tracker.js.map +1 -1
  144. package/dist/tools/cross-sdk-agent.d.ts +61 -3
  145. package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
  146. package/dist/tools/cross-sdk-agent.js +524 -45
  147. package/dist/tools/cross-sdk-agent.js.map +1 -1
  148. package/dist/transport.d.ts +4 -0
  149. package/dist/transport.d.ts.map +1 -1
  150. package/dist/transport.js +64 -30
  151. package/dist/transport.js.map +1 -1
  152. package/dist/version.d.ts +1 -1
  153. package/dist/version.js +1 -1
  154. 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),
@@ -547,7 +837,7 @@ export class CrossSdkJsAgent {
547
837
  const consensus = await this.client.checkOwnershipConsensus(subAid);
548
838
  const files = { agent_md: null, owner_md: null, storage_agent_md: null };
549
839
  const fileErrors = {};
550
- const evidence = (raw, revision) => ({ raw_base64: Buffer.from(raw, 'utf-8').toString('base64'), sha256: crypto.createHash('sha256').update(raw, 'utf-8').digest('hex'), ...(revision ? { revision } : {}) });
840
+ const evidence = (raw) => ({ raw_base64: Buffer.from(raw, 'utf-8').toString('base64'), sha256: crypto.createHash('sha256').update(raw, 'utf-8').digest('hex') });
551
841
  const errorText = (error) => ownershipErrorCode(error) + ': ' + (error instanceof Error ? error.message : String(error));
552
842
  let ns = null;
553
843
  try {
@@ -568,7 +858,7 @@ export class CrossSdkJsAgent {
568
858
  try {
569
859
  const owner = await storage.readOwnerMd(ownerAid, subAid);
570
860
  if (owner.exists)
571
- files.owner_md = evidence(owner.content, owner.revision);
861
+ files.owner_md = evidence(owner.content);
572
862
  else
573
863
  fileErrors.owner_md = 'not_found';
574
864
  }
@@ -581,7 +871,7 @@ export class CrossSdkJsAgent {
581
871
  try {
582
872
  const copy = await storage.readFile(subAid, 'agent.md');
583
873
  if (copy.exists)
584
- files.storage_agent_md = evidence(copy.content, copy.revision);
874
+ files.storage_agent_md = evidence(copy.content);
585
875
  else
586
876
  fileErrors.storage_agent_md = 'not_found';
587
877
  }
@@ -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
  }
@@ -680,6 +1000,10 @@ export class CrossSdkJsAgent {
680
1000
  sendJson(res, 200, await this.identity());
681
1001
  return;
682
1002
  }
1003
+ if (req.method === 'POST' && url.pathname === '/signature/call') {
1004
+ await this.signatureCall(req, res);
1005
+ return;
1006
+ }
683
1007
  if (req.method === 'POST' && ['/ownership/bind', '/ownership/accept', '/ownership/reject', '/ownership/release', '/ownership/transfer'].includes(url.pathname)) {
684
1008
  await this.ownershipControl(req, res, url.pathname.slice('/ownership/'.length));
685
1009
  return;
@@ -828,6 +1152,77 @@ export class CrossSdkJsAgent {
828
1152
  });
829
1153
  }
830
1154
  }
1155
+ async signatureCall(req, res) {
1156
+ const body = await readJson(req);
1157
+ const action = textOf(body.action), aid = textOf(body.aid || this.aid);
1158
+ const store = this.aidStore();
1159
+ let fileBytes;
1160
+ let identity;
1161
+ const output = (result) => {
1162
+ const response = result.ok ? { ok: true, result: jsonSafe(result.data) } : { ok: false, error_code: result.error.code, error_message: result.error.message };
1163
+ if (identity) {
1164
+ response.certificate = identity.certPem;
1165
+ response.cert_fingerprint = identity.certFingerprint;
1166
+ }
1167
+ if (fileBytes)
1168
+ response.file_base64 = Buffer.from(fileBytes).toString('base64');
1169
+ sendJson(res, 200, response);
1170
+ };
1171
+ try {
1172
+ if (!['certificate', 'sign', 'verify', 'upload_agent_md', 'download_agent_md'].includes(action)) {
1173
+ output({ ok: false, error: { code: 'unsupported_action', message: action } });
1174
+ return;
1175
+ }
1176
+ if (action === 'upload_agent_md') {
1177
+ output(await store.uploadAgentMd(aid, textOf(body.input)));
1178
+ return;
1179
+ }
1180
+ if (action === 'download_agent_md') {
1181
+ output(await store.downloadAgentMd(aid));
1182
+ return;
1183
+ }
1184
+ let input = textOf(body.input);
1185
+ if (body.input_type === 'file') {
1186
+ fileBytes = new Uint8Array(typeof body.input_base64 === 'string' ? Buffer.from(body.input_base64, 'base64') : Buffer.from(textOf(body.input), 'utf8'));
1187
+ input = body.in_place === true ? {
1188
+ getFile: async () => new Blob([new Uint8Array(fileBytes)]),
1189
+ createWritable: async () => ({ write: async (content) => { fileBytes = new TextEncoder().encode(content); }, close: async () => { } }),
1190
+ } : new Blob([new Uint8Array(fileBytes)]);
1191
+ }
1192
+ if (action !== 'verify') {
1193
+ const loaded = await store.load(aid);
1194
+ if (!loaded.ok) {
1195
+ output(loaded);
1196
+ return;
1197
+ }
1198
+ identity = loaded.data.aid;
1199
+ if (action === 'certificate') {
1200
+ output({ ok: true, data: { aid, certificate: identity.certPem, cert_fingerprint: identity.certFingerprint } });
1201
+ return;
1202
+ }
1203
+ const options = { identity, inputType: body.input_type, format: body.format, append: body.append, tag: body.tag, inPlace: body.in_place };
1204
+ output(await store.sign(aid, input, options));
1205
+ return;
1206
+ }
1207
+ const certificates = jsonObjectOf(body.certificates) ?? {};
1208
+ const resolveCertificate = (signer) => {
1209
+ const certificate = textOf(certificates[signer]);
1210
+ if (!certificate)
1211
+ throw new Error(`verification certificate missing for AID: ${signer}`);
1212
+ return certificate;
1213
+ };
1214
+ if (body.format !== 'document' || certificates[aid])
1215
+ identity = await AID.create({ aid, aunPath: this.aunPath, certPem: resolveCertificate(aid), privateKeyPem: null, certValid: true, privateKeyValid: false });
1216
+ const options = { identity, inputType: body.input_type, format: body.format, certificateResolver: resolveCertificate };
1217
+ output(await store.verify(aid, input, textOf(body.signature), options));
1218
+ }
1219
+ catch (error) {
1220
+ output({ ok: false, error: { code: action === 'verify' ? 'VERIFICATION_OPERATION_ERROR' : 'SIGNATURE_OPERATION_ERROR', message: error instanceof Error ? error.message : String(error) } });
1221
+ }
1222
+ finally {
1223
+ store.close();
1224
+ }
1225
+ }
831
1226
  async collabCall(req, res) {
832
1227
  const body = await readJson(req);
833
1228
  const traceId = textOf(body.trace_id || crypto.randomUUID().replace(/-/g, ''));
@@ -856,9 +1251,16 @@ export class CrossSdkJsAgent {
856
1251
  const now = Math.floor(Date.now() / 1000);
857
1252
  if (op === 'request')
858
1253
  return buildRequestBinding(textOf(params.method), textOf(params.target), params.params, textOf(params.credential_cid), textOf(params.nonce), textOf(params.request_id), now, now + 300);
859
- if (['register', 'sign', 'certificate', 'create'].includes(op)) {
1254
+ if (['register', 'sign', 'certificate', 'create', 'prepare'].includes(op)) {
860
1255
  const store = this.aidStore();
861
1256
  try {
1257
+ if (op === 'prepare') {
1258
+ const request = jsonObjectOf(params.params) ?? {};
1259
+ const orgId = textOf(params.org_id ?? request.org_id);
1260
+ const loaded = orgId ? await store.load(orgId) : null;
1261
+ const orgSigner = loaded?.ok && loaded.data.aid.isPrivateKeyValid() ? loaded.data.aid : undefined;
1262
+ return await this.client.org.prepareRequest(textOf(params.method), request, textOf(params.target), this.client.currentAid, { ...jsonObjectOf(params.options), orgSigner });
1263
+ }
862
1264
  if (op === 'register') {
863
1265
  const registered = await store.register(textOf(params.org_id), { type: 'org', owner: this.aid, exttype: 'company' });
864
1266
  if (!registered.ok)
@@ -891,6 +1293,7 @@ export class CrossSdkJsAgent {
891
1293
  const value = params.document ?? params.payload;
892
1294
  const result = { canonical: new TextDecoder().decode(canonicalJson(value)), digest: digest(value), error_code: '' };
893
1295
  if (op === 'verify' || op === 'verify_full') {
1296
+ const verificationNow = typeof params.now === 'number' ? params.now : now;
894
1297
  const certificates = params.certificates;
895
1298
  const resolver = (fingerprint) => certificates[fingerprint];
896
1299
  try {
@@ -898,7 +1301,7 @@ export class CrossSdkJsAgent {
898
1301
  const actual = jsonObjectOf(params.context) ?? params;
899
1302
  const memberships = jsonObjectOf(params.memberships) ?? {};
900
1303
  result.verification = { ...await this.client.credential.verify(value, { resolveCertificate: resolver, resolveActorCertificate: resolver,
901
- resolveStatus: (cid) => params.statuses?.[cid], now, audience: textOf(actual.audience), subject: textOf(actual.subject) || undefined,
1304
+ resolveStatus: (cid) => params.statuses?.[cid], now: verificationNow, audience: textOf(actual.audience), subject: textOf(actual.subject) || undefined,
902
1305
  ns: textOf(actual.ns), action: textOf(actual.action), resource: textOf(actual.resource), rootIssuer: textOf(actual.root_issuer) || undefined,
903
1306
  params: jsonObjectOf(actual.params) ?? {}, units: jsonObjectOf(actual.units) ?? {}, clientIp: textOf(actual.client_ip), method: textOf(actual.method), target: textOf(actual.target),
904
1307
  requestId: textOf(actual.request_id), nonce: textOf(actual.nonce), requestBinding: actual.request_binding, proofChain: actual.proof_chain, membershipBinding: actual.membership_binding,
@@ -907,11 +1310,11 @@ export class CrossSdkJsAgent {
907
1310
  resolveOwnership: (subject, owner) => (jsonObjectOf(params.ownerships) ?? {})[subject] === owner, checkRateLimit: () => params.rate_limit_allowed === true }) };
908
1311
  }
909
1312
  else if (params.kind === 'credential')
910
- result.verification = { ...await this.client.credential.verifySignature(value, resolver, now, textOf(params.audience)) };
1313
+ result.verification = { ...await this.client.credential.verifySignature(value, resolver, verificationNow, textOf(params.audience)) };
911
1314
  else if (params.kind === 'binding')
912
- await verifyMembershipBinding(value, resolver, now, textOf(params.expected_org_signer));
1315
+ await verifyMembershipBinding(value, resolver, verificationNow, textOf(params.expected_org_signer));
913
1316
  else
914
- result.verification = { ...await this.client.credential.verifyRegistrationReceipt(value, params.credential, params.request, resolver, textOf(params.service_aid), now) };
1317
+ result.verification = { ...await this.client.credential.verifyRegistrationReceipt(value, params.credential, params.request, resolver, textOf(params.service_aid), verificationNow) };
915
1318
  }
916
1319
  catch (error) {
917
1320
  if (!(error instanceof ProtocolError))
@@ -1610,6 +2013,8 @@ export class CrossSdkJsAgent {
1610
2013
  };
1611
2014
  if (body.ttl !== undefined)
1612
2015
  params.ttl = Number(body.ttl);
2016
+ if (body.need_render !== undefined)
2017
+ params.need_render = body.need_render;
1613
2018
  const result = await this.client.call('message.send', params);
1614
2019
  const resultObj = jsonObjectOf(result) ?? {};
1615
2020
  const response = {
@@ -1644,6 +2049,10 @@ export class CrossSdkJsAgent {
1644
2049
  const params = {};
1645
2050
  if (seq > 0)
1646
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;
1647
2056
  if (Array.isArray(body.volatile_cursors))
1648
2057
  params.volatile_cursors = body.volatile_cursors;
1649
2058
  else if (textOf(body.volatile_cursor))
@@ -1665,9 +2074,26 @@ export class CrossSdkJsAgent {
1665
2074
  }
1666
2075
  async pull(req, res) {
1667
2076
  const body = await readJson(req);
1668
- const afterSeq = Number(body.after_seq ?? 0) || 0;
1669
- const limit = Number(body.limit ?? 50) || 50;
1670
- 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
+ }
1671
2097
  if (textOf(body.after_volatile_cursor))
1672
2098
  params.after_volatile_cursor = textOf(body.after_volatile_cursor);
1673
2099
  try {
@@ -1688,6 +2114,20 @@ export class CrossSdkJsAgent {
1688
2114
  }
1689
2115
  async messageCall(req, res) {
1690
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
+ }
1691
2131
  const traceId = textOf(body.trace_id || crypto.randomUUID().replace(/-/g, ''));
1692
2132
  const method = textOf(body.method).trim();
1693
2133
  const params = jsonObjectOf(body.params) ?? {};
@@ -1719,15 +2159,15 @@ export class CrossSdkJsAgent {
1719
2159
  sendJson(res, 500, error);
1720
2160
  }
1721
2161
  }
1722
- syncNamespace(scope, groupId = '') {
2162
+ syncNamespace(scope, groupId = '', senderAid = '') {
1723
2163
  if (scope === 'p2p')
1724
- return `p2p:${this.aid}`;
2164
+ return senderAid ? `p2p.peer.v1/${Buffer.from(senderAid, 'utf8').toString('base64url')}` : `p2p:${this.aid}`;
1725
2165
  if (scope === 'group' && groupId)
1726
2166
  return `group:${groupId}`;
1727
2167
  throw new Error('scope must be p2p, or group with group_id');
1728
2168
  }
1729
- syncStateValue(scope, groupId = '') {
1730
- const namespace = this.syncNamespace(scope, groupId);
2169
+ syncStateValue(scope, groupId = '', senderAid = '') {
2170
+ const namespace = this.syncNamespace(scope, groupId, senderAid);
1731
2171
  const tracker = this.client._seqTracker;
1732
2172
  if (!tracker)
1733
2173
  throw new Error('SeqTracker is unavailable');
@@ -1736,9 +2176,10 @@ export class CrossSdkJsAgent {
1736
2176
  syncState(url, res) {
1737
2177
  const scope = textOf(url.searchParams.get('scope') || 'p2p').trim().toLowerCase();
1738
2178
  const groupId = textOf(url.searchParams.get('group_id') || '').trim();
2179
+ const senderAid = textOf(url.searchParams.get('sender_aid') || '').trim();
1739
2180
  try {
1740
- const snapshot = this.syncStateValue(scope, groupId);
1741
- 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 });
1742
2183
  }
1743
2184
  catch (err) {
1744
2185
  sendJson(res, 400, {
@@ -1758,27 +2199,24 @@ export class CrossSdkJsAgent {
1758
2199
  const body = await readJson(req);
1759
2200
  const scope = textOf(body.scope || 'p2p').trim().toLowerCase();
1760
2201
  const groupId = textOf(body.group_id || '').trim();
2202
+ const senderAid = textOf(body.sender_aid || '').trim();
1761
2203
  const afterSeq = Number(body.after_seq ?? 0) || 0;
1762
2204
  const limit = Number(body.limit ?? 50) || 50;
1763
2205
  try {
1764
- const params = { after_seq: afterSeq, limit };
1765
- if (tail)
1766
- 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
+ }
1767
2215
  const method = scope === 'group' ? 'group.pull' : 'message.pull';
1768
2216
  if (scope === 'group')
1769
2217
  params.group_id = groupId;
1770
- this.syncNamespace(scope, groupId);
1771
- let result;
1772
- if (tail) {
1773
- result = await this.client.call(method, params);
1774
- }
1775
- else {
1776
- const transport = this.client._transport;
1777
- if (!transport?.call)
1778
- throw new Error('raw transport is unavailable');
1779
- const wireMethod = scope === 'group' ? 'group.v2.pull' : 'message.v2.pull';
1780
- result = await transport.call(wireMethod, params);
1781
- }
2218
+ this.syncNamespace(scope, groupId, senderAid);
2219
+ const result = await this.client.call(method, params);
1782
2220
  const resultObj = jsonObjectOf(result) ?? {};
1783
2221
  const messages = Array.isArray(resultObj.messages) ? resultObj.messages : [];
1784
2222
  if (tail) {
@@ -1789,7 +2227,7 @@ export class CrossSdkJsAgent {
1789
2227
  await this.storeInboxItem(this.normalizeMessage(msg, true));
1790
2228
  }
1791
2229
  }
1792
- const snapshot = this.syncStateValue(scope, groupId);
2230
+ const snapshot = this.syncStateValue(scope, groupId, senderAid);
1793
2231
  sendJson(res, 200, { ok: true, scope, ...snapshot, result: jsonSafe(result) });
1794
2232
  }
1795
2233
  catch (err) {
@@ -1805,6 +2243,7 @@ export class CrossSdkJsAgent {
1805
2243
  const body = await readJson(req);
1806
2244
  const scope = textOf(body.scope || 'p2p').trim().toLowerCase();
1807
2245
  const groupId = textOf(body.group_id || '').trim();
2246
+ const senderAid = textOf(body.sender_aid || '').trim();
1808
2247
  const beforeSeq = Number(body.before_seq ?? 0) || 0;
1809
2248
  const limit = Number(body.limit ?? 50) || 50;
1810
2249
  try {
@@ -1812,9 +2251,13 @@ export class CrossSdkJsAgent {
1812
2251
  const method = scope === 'group' ? 'group.history' : 'message.history';
1813
2252
  if (scope === 'group')
1814
2253
  params.group_id = groupId;
1815
- 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);
1816
2259
  const result = await this.client.call(method, params);
1817
- const snapshot = this.syncStateValue(scope, groupId);
2260
+ const snapshot = this.syncStateValue(scope, groupId, senderAid);
1818
2261
  sendJson(res, 200, { ok: true, scope, ...snapshot, result: jsonSafe(result) });
1819
2262
  }
1820
2263
  catch (err) {
@@ -1829,7 +2272,27 @@ export class CrossSdkJsAgent {
1829
2272
  async sessionDisconnect(res) {
1830
2273
  try {
1831
2274
  this.ready = false;
1832
- 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
+ }
1833
2296
  sendJson(res, 200, { ok: true, state: textOf(this.client.state) });
1834
2297
  }
1835
2298
  catch (err) {
@@ -1842,14 +2305,21 @@ export class CrossSdkJsAgent {
1842
2305
  }
1843
2306
  async sessionConnect(req, res) {
1844
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
+ }
1845
2312
  const backgroundSync = body.background_sync === true;
1846
2313
  if (Object.prototype.hasOwnProperty.call(body, 'inline_realtime_payload_v1')) {
1847
2314
  this.inlineRealtimePayloadV1 = body.inline_realtime_payload_v1 !== false;
1848
2315
  }
2316
+ if (typeof body.batch_events === 'boolean')
2317
+ this.batchEventMode = body.batch_events;
2318
+ this.syncBatchEventHandlers();
1849
2319
  try {
1850
2320
  await this.client.connect({ auto_reconnect: false, background_sync: backgroundSync });
1851
2321
  this.ready = true;
1852
- 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 });
1853
2323
  }
1854
2324
  catch (err) {
1855
2325
  sendJson(res, 500, {
@@ -1861,19 +2331,27 @@ export class CrossSdkJsAgent {
1861
2331
  }
1862
2332
  async sessionRestart(req, res) {
1863
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
+ }
1864
2338
  const backgroundSync = body.background_sync === true;
1865
2339
  if (Object.prototype.hasOwnProperty.call(body, 'inline_realtime_payload_v1')) {
1866
2340
  this.inlineRealtimePayloadV1 = body.inline_realtime_payload_v1 !== false;
1867
2341
  }
2342
+ if (typeof body.batch_events === 'boolean')
2343
+ this.batchEventMode = body.batch_events;
2344
+ this.syncBatchEventHandlers();
1868
2345
  this.ready = false;
1869
2346
  try {
2347
+ this.releasePullBarrier();
1870
2348
  await this.client.close();
1871
2349
  this.client = this.newClient();
1872
2350
  this.bindClientEvents();
1873
2351
  await this.ensureConnected(backgroundSync);
1874
2352
  this.startupError = '';
1875
2353
  this.ready = true;
1876
- 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 });
1877
2355
  }
1878
2356
  catch (err) {
1879
2357
  this.startupError = `${err instanceof Error ? err.name : 'Error'}: ${err instanceof Error ? err.message : String(err)}`;
@@ -1987,6 +2465,7 @@ export class CrossSdkJsAgent {
1987
2465
  payload: { type: 'text', text, trace_id: traceId, case_id: textOf(body.case_id || traceId) },
1988
2466
  encrypt: e2ee,
1989
2467
  message_id: messageId,
2468
+ ...(body.need_render !== undefined ? { need_render: body.need_render } : {}),
1990
2469
  });
1991
2470
  const resultObj = jsonObjectOf(result) ?? {};
1992
2471
  const response = {