@agentunion/fastaun 0.5.19 → 0.5.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +176 -78
  2. package/_packed_docs/CHANGELOG.md +176 -78
  3. package/_packed_docs/INDEX.md +288 -269
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +101 -95
  5. package/_packed_docs/agent.md/SCHEMA.md +7 -7
  6. package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/344/270/216/346/215/242owner/347/274/226/346/216/222/346/226/271/346/241/210.md +530 -0
  7. package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/345/256/214/346/225/264/350/256/276/350/256/241/344/270/216/345/256/236/346/226/275/350/256/241/345/210/222.md +685 -0
  8. package/_packed_docs/agent.md/working/agent.md-schema/344/270/216/347/255/276/345/217/221/351/252/214/347/255/276/346/265/201/347/250/213/346/276/204/346/270/205.md +349 -0
  9. package/_packed_docs/agent.md/working//346/234/254/345/234/260Docker/346/265/213/350/257/225Bug/346/270/205/345/215/225.md +40 -0
  10. package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +11 -11
  11. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +372 -331
  12. package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
  13. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +676 -676
  14. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +8 -12
  15. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +445 -0
  16. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
  17. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +666 -0
  18. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
  19. package/_packed_docs/protocol/agent.md/SCHEMA.md +1 -1
  20. package/_packed_docs/sdk/0.5.21-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +368 -0
  21. package/_packed_docs/sdk/0.5.21-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +350 -0
  22. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
  23. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
  24. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
  25. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +70 -63
  26. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +43 -40
  27. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +179 -19
  28. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +50 -49
  29. package/_packed_docs/sdk/09-group-rpc-manual.md +261 -254
  30. package/_packed_docs/sdk/09-message-rpc-manual.md +253 -160
  31. package/_packed_docs/sdk/09-storage-rpc-manual.md +18 -26
  32. package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +71 -0
  33. package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +88 -0
  34. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +105 -92
  35. package/_packed_docs/sdk/INDEX.md +197 -167
  36. package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
  37. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
  38. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +930 -0
  39. package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
  40. package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +363 -0
  41. package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +553 -0
  42. package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -0
  43. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +480 -0
  44. package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
  45. package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
  46. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +173 -0
  47. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +211 -0
  48. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/345/256/236/347/216/260/347/247/273/346/244/215/346/214/207/345/215/227.md +331 -0
  49. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +207 -0
  50. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/344/273/243/347/240/201/345/256/241/346/237/245/346/212/245/345/221/212.md +252 -0
  51. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/345/256/236/346/226/275/350/256/241/345/210/222.md +156 -0
  52. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/346/226/271/346/241/210.md +273 -0
  53. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/350/257/246/347/273/206/350/256/276/350/256/241.md +211 -0
  54. package/dist/agent-md-schema.d.ts +13 -1
  55. package/dist/agent-md-schema.js +63 -33
  56. package/dist/agent-md-schema.js.map +1 -1
  57. package/dist/agent-md.d.ts +30 -3
  58. package/dist/agent-md.js +293 -152
  59. package/dist/agent-md.js.map +1 -1
  60. package/dist/aid-store.d.ts +42 -0
  61. package/dist/aid-store.js +224 -44
  62. package/dist/aid-store.js.map +1 -1
  63. package/dist/aid.js +18 -13
  64. package/dist/aid.js.map +1 -1
  65. package/dist/auth.js +78 -2
  66. package/dist/auth.js.map +1 -1
  67. package/dist/cert-utils.d.ts +44 -1
  68. package/dist/cert-utils.js +157 -56
  69. package/dist/cert-utils.js.map +1 -1
  70. package/dist/client/delivery.d.ts +68 -1
  71. package/dist/client/delivery.js +1196 -135
  72. package/dist/client/delivery.js.map +1 -1
  73. package/dist/client/rpc-pipeline.d.ts +5 -1
  74. package/dist/client/rpc-pipeline.js +240 -111
  75. package/dist/client/rpc-pipeline.js.map +1 -1
  76. package/dist/client/v2-e2ee.d.ts +12 -2
  77. package/dist/client/v2-e2ee.js +842 -379
  78. package/dist/client/v2-e2ee.js.map +1 -1
  79. package/dist/client.d.ts +26 -2
  80. package/dist/client.js +420 -75
  81. package/dist/client.js.map +1 -1
  82. package/dist/config.d.ts +4 -0
  83. package/dist/config.js +1 -0
  84. package/dist/config.js.map +1 -1
  85. package/dist/events.d.ts +23 -4
  86. package/dist/events.js +171 -15
  87. package/dist/events.js.map +1 -1
  88. package/dist/facades.d.ts +43 -1
  89. package/dist/facades.js +368 -12
  90. package/dist/facades.js.map +1 -1
  91. package/dist/index.d.ts +7 -4
  92. package/dist/index.js +1 -1
  93. package/dist/index.js.map +1 -1
  94. package/dist/keystore/aid-db.d.ts +7 -2
  95. package/dist/keystore/aid-db.js +194 -43
  96. package/dist/keystore/aid-db.js.map +1 -1
  97. package/dist/keystore/index.d.ts +15 -1
  98. package/dist/keystore/local-token-store.d.ts +10 -2
  99. package/dist/keystore/local-token-store.js +81 -16
  100. package/dist/keystore/local-token-store.js.map +1 -1
  101. package/dist/org-protocol.d.ts +45 -0
  102. package/dist/org-protocol.js +829 -22
  103. package/dist/org-protocol.js.map +1 -1
  104. package/dist/ownership-v21.d.ts +40 -9
  105. package/dist/ownership-v21.js +174 -79
  106. package/dist/ownership-v21.js.map +1 -1
  107. package/dist/ownership-workflow.d.ts +22 -0
  108. package/dist/ownership-workflow.js +861 -66
  109. package/dist/ownership-workflow.js.map +1 -1
  110. package/dist/secret-store/file-store.js +84 -0
  111. package/dist/secret-store/file-store.js.map +1 -1
  112. package/dist/seq-tracker.d.ts +1 -0
  113. package/dist/seq-tracker.js +2 -2
  114. package/dist/seq-tracker.js.map +1 -1
  115. package/dist/tools/cross-sdk-agent.d.ts +67 -3
  116. package/dist/tools/cross-sdk-agent.js +575 -42
  117. package/dist/tools/cross-sdk-agent.js.map +1 -1
  118. package/dist/transport.d.ts +4 -0
  119. package/dist/transport.js +108 -44
  120. package/dist/transport.js.map +1 -1
  121. package/dist/types.d.ts +1 -0
  122. package/dist/v2/e2ee/encrypt-group.js +1 -0
  123. package/dist/v2/e2ee/encrypt-group.js.map +1 -1
  124. package/dist/v2/e2ee/encrypt-p2p.js +1 -0
  125. package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
  126. package/dist/v2/e2ee/types.d.ts +1 -0
  127. package/dist/version.d.ts +1 -1
  128. package/dist/version.js +1 -1
  129. package/package.json +1 -1
@@ -4,14 +4,17 @@ import * as https from 'node:https';
4
4
  import * as crypto from 'node:crypto';
5
5
  import * as fs from 'node:fs';
6
6
  import * as path from 'node:path';
7
+ import { tmpdir } from 'node:os';
7
8
  import { URL } from 'node:url';
8
9
  import { fileURLToPath } from 'node:url';
9
10
  import { AUNClient } from '../client.js';
11
+ import { AID } from '../aid.js';
10
12
  import { AIDStore } from '../aid-store.js';
11
13
  import { publicKeyFingerprint } from '../cert-utils.js';
12
14
  import { VERSION } from '../version.js';
13
15
  import { canonicalJson, digest, buildRequestBinding, verifyMembershipBinding, ProtocolError } from '../org-protocol.js';
14
16
  import { ownershipTaskResult } from '../ownership-workflow.js';
17
+ import { TimeoutError } from '../errors.js';
15
18
  function envBool(name, fallback = false) {
16
19
  const raw = process.env[name];
17
20
  if (raw == null)
@@ -126,10 +129,25 @@ export class CrossSdkTsAgent {
126
129
  rawPushObservations = [];
127
130
  rawPushObservationId = '';
128
131
  rawPushRpcMethods = [];
132
+ eventLedger = [];
133
+ batchEventMode = false;
134
+ batchEvents = [];
135
+ singleEventCounts = { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
136
+ eventBoundClients = new WeakSet();
137
+ batchEventClient;
138
+ batchEventHandlers = [];
139
+ rpcSpans = [];
140
+ observationGeneration = 0;
141
+ pullBarrier = null;
142
+ rpcFailures = [];
143
+ messageOperations = new Map();
129
144
  rpcObservedTransports = new WeakSet();
145
+ rpcObservedPipelines = new WeakSet();
146
+ rpcObservedPulls = new WeakMap();
130
147
  groupChangedEvents = [];
131
148
  groupInviteEvents = [];
132
149
  readStateEvents = [];
150
+ peersSnapshots = [];
133
151
  ownershipEvents = [];
134
152
  traces = new Map();
135
153
  sendResults = new Map();
@@ -152,6 +170,12 @@ export class CrossSdkTsAgent {
152
170
  return client;
153
171
  }
154
172
  bindClientEvents() {
173
+ this.eventBoundClients ??= new WeakSet();
174
+ if (this.eventBoundClients.has(this.client)) {
175
+ this.setBatchEventMode(this.batchEventMode ?? false);
176
+ return;
177
+ }
178
+ this.eventBoundClients.add(this.client);
155
179
  this.installTransportRpcObserver();
156
180
  this.client.on('_raw.message.received', (data) => this.storeRawPushObservation('_raw.message.received', 'p2p', 'legacy', data));
157
181
  this.client.on('_raw.peer.v2.message_received', (data) => this.storeRawPushObservation('_raw.peer.v2.message_received', 'p2p', 'v2', data));
@@ -159,22 +183,30 @@ export class CrossSdkTsAgent {
159
183
  this.client.on('_raw.group.v2.message_created', (data) => this.storeRawPushObservation('_raw.group.v2.message_created', 'group', 'v2', data));
160
184
  this.client.on('_raw.group.changed', (data) => this.storeRawGroupChangedObservation(data));
161
185
  this.client.on('message.received', (msg) => {
162
- void this.storeInboxItem(this.normalizeMessage(msg, true));
186
+ this.countSingleEvent('message.received');
187
+ void this.storeApplicationEvent('message.received', msg);
163
188
  });
164
189
  this.client.on('message.undecryptable', (msg) => {
165
190
  void this.storeInboxItem(this.normalizeMessage(msg, false, 'undecryptable'));
166
191
  });
167
192
  this.client.on('group.message_created', (msg) => {
168
- void this.storeGroupInboxItem(this.normalizeGroupMessage(msg, true));
193
+ this.countSingleEvent('group.message_created');
194
+ void this.storeApplicationEvent('group.message_created', msg);
169
195
  });
170
196
  this.client.on('group.message_undecryptable', (msg) => {
171
197
  void this.storeGroupInboxItem(this.normalizeGroupMessage(msg, false, 'undecryptable'));
172
198
  });
173
- this.client.on('group.changed', (data) => this.storeGroupChangedPublicEvent(data));
199
+ this.client.on('group.changed', (data) => {
200
+ this.countSingleEvent('group.changed');
201
+ void this.storeApplicationEvent('group.changed', data);
202
+ });
174
203
  this.client.on('group.invite_received', (data) => this.storeGroupInviteEvent(data));
175
204
  for (const event of ['message.read_changed', 'group.read_changed', 'read_state_sync']) {
176
205
  this.client.on(event, (payload) => { this.readStateEvents.push({ event, payload: jsonSafe(payload) }); });
177
206
  }
207
+ this.client.on('message.peers_snapshot', (payload) => {
208
+ this.peersSnapshots.push({ event: 'message.peers_snapshot', payload: jsonSafe(payload) });
209
+ });
178
210
  for (const event of ['ownership.request', 'ownership.pending', 'ownership.progress', 'ownership.completed', 'ownership.rejected', 'ownership.stopped', 'ownership.error', 'ownership.action_required']) {
179
211
  this.client.on(event, (payload) => {
180
212
  this.ownershipEvents.push({ event, payload: jsonSafe(payload) });
@@ -187,15 +219,91 @@ export class CrossSdkTsAgent {
187
219
  this.recordTrace(textOf(payload.org_id), { stage: event, payload });
188
220
  });
189
221
  }
222
+ this.setBatchEventMode(this.batchEventMode ?? false);
223
+ }
224
+ countSingleEvent(event) {
225
+ this.singleEventCounts ??= { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
226
+ this.singleEventCounts[event] = Number(this.singleEventCounts[event] ?? 0) + 1;
227
+ }
228
+ async storeApplicationEvent(event, value) {
229
+ if (event === 'message.received') {
230
+ this.storeEventLedger(event, value);
231
+ await this.storeInboxItem(this.normalizeMessage(value, true));
232
+ }
233
+ else if (event === 'group.message_created') {
234
+ this.storeEventLedger(event, value);
235
+ await this.storeGroupInboxItem(this.normalizeGroupMessage(value, true));
236
+ }
237
+ else if (event === 'group.changed')
238
+ this.storeGroupChangedPublicEvent(value);
239
+ }
240
+ setBatchEventMode(enabled) {
241
+ this.batchEventMode = enabled;
242
+ if (enabled && this.batchEventClient === this.client && this.batchEventHandlers?.length)
243
+ return;
244
+ for (const { event, handler } of this.batchEventHandlers ?? [])
245
+ this.batchEventClient?.off(event, handler);
246
+ this.batchEventClient = this.client;
247
+ this.batchEventHandlers = [];
248
+ if (!enabled)
249
+ return;
250
+ for (const event of ['message.batch', 'group.message_batch', 'group.event_batch']) {
251
+ const handler = async (value) => {
252
+ const data = jsonObjectOf(value) ?? {};
253
+ const items = JSON.parse(JSON.stringify(jsonSafe(Array.isArray(data.items) ? data.items : [])));
254
+ this.batchEvents ??= [];
255
+ this.batchEvents.push({ observation_id: this.rawPushObservationId, time_ms: Date.now(), event,
256
+ namespace: textOf(data.namespace), source: textOf(data.source), items });
257
+ for (const item of items)
258
+ await this.storeApplicationEvent(textOf(item.event), item.payload);
259
+ };
260
+ this.batchEventHandlers.push({ event, handler });
261
+ this.client.on(event, handler);
262
+ }
190
263
  }
191
264
  installTransportRpcObserver() {
265
+ const client = this.client;
266
+ const pipeline = client._rpcPipeline;
267
+ this.rpcObservedPipelines ??= new WeakSet();
268
+ this.rpcObservedPulls ??= new WeakMap();
269
+ if (pipeline?.bindActivePullCancellation && !this.rpcObservedPipelines.has(pipeline)) {
270
+ const originalBind = pipeline.bindActivePullCancellation;
271
+ this.rpcObservedPipelines.add(pipeline);
272
+ pipeline.bindActivePullCancellation = (method, params, request, gateKey = '') => {
273
+ const result = originalBind.call(pipeline, method, params, request, gateKey);
274
+ const observed = this.rpcObservedPulls.get(request);
275
+ if (!this.pullBarrier?.preserve || !observed || !['message.pull', 'message.v2.pull'].includes(method)
276
+ || !textOf(params.sender_aid) || typeof client._delivery?.peerNamespace !== 'function')
277
+ return result;
278
+ const key = gateKey || pipeline.pullGateKeyForCall(method, params);
279
+ const namespace = client._delivery?.peerNamespace(textOf(params.sender_aid));
280
+ const owners = [...pipeline.pullGateStates.values()].flatMap((state) => [...state.activeByNamespace.values()])
281
+ .filter((job) => job.key === key && job.namespace === namespace && job.running && !job.timedOut && !job.invalidated && job.cancel);
282
+ if (owners.length === 1)
283
+ observed.owner = { job: owners[0], cancel: owners[0].cancel, key, namespace: namespace };
284
+ return result;
285
+ };
286
+ }
192
287
  const transport = this.client._transport;
193
288
  transport?.setTraceMode('diag');
194
289
  if (!transport?.call || this.rpcObservedTransports.has(transport))
195
290
  return;
196
291
  const originalCall = transport.call;
292
+ const originalClose = transport.close;
197
293
  this.rpcObservedTransports.add(transport);
198
- transport.call = async (method, params, timeout, trace, background) => {
294
+ if (originalClose)
295
+ transport.close = async () => {
296
+ if (!this.pullBarrier?.preserve)
297
+ this.releasePullBarrier();
298
+ try {
299
+ await originalClose.call(transport);
300
+ }
301
+ finally {
302
+ if (!this.pullBarrier?.preserve)
303
+ this.releasePullBarrier();
304
+ }
305
+ };
306
+ transport.call = (method, params, timeout, trace, background) => {
199
307
  this.storeRawPushRpcMethod({ type: 'rpc', method, request_window: this.rawPushRequestWindow(params) });
200
308
  let requestParams = params;
201
309
  if (method === 'auth.connect' && !this.inlineRealtimePayloadV1 && jsonObjectOf(params)) {
@@ -207,8 +315,219 @@ export class CrossSdkTsAgent {
207
315
  }
208
316
  requestParams = request;
209
317
  }
210
- return await originalCall.call(transport, method, requestParams, timeout, trace, background);
318
+ const generation = this.observationGeneration;
319
+ const span = this.startRpcSpan(method, requestParams);
320
+ const controller = new AbortController();
321
+ let request;
322
+ const observed = {};
323
+ const pending = (async () => {
324
+ try {
325
+ request = originalCall.call(transport, method, requestParams, timeout, trace, background);
326
+ const result = await request;
327
+ if (span) {
328
+ this.finishRpcResponse(span, result);
329
+ if (generation === this.observationGeneration && (this.isObservedPull(method) || this.pullBarrier?.method === method)) {
330
+ await this.waitForPullBarrier(method, controller.signal, timeout ?? transport._timeout ?? 35_000, span, observed.owner);
331
+ }
332
+ if (generation === this.observationGeneration)
333
+ this.injectRPCFailure(method, span);
334
+ span.success = true;
335
+ }
336
+ return result;
337
+ }
338
+ catch (error) {
339
+ if (span) {
340
+ span.success = false;
341
+ span.error = error instanceof Error ? error.name : String(error);
342
+ }
343
+ throw error;
344
+ }
345
+ finally {
346
+ if (span)
347
+ span.end_ms = Date.now();
348
+ }
349
+ })();
350
+ pending.cancel = () => { controller.abort(); request?.cancel?.(); };
351
+ this.rpcObservedPulls.set(pending, observed);
352
+ return pending;
353
+ };
354
+ }
355
+ storeEventLedger(event, value) {
356
+ const data = jsonObjectOf(value) ?? {};
357
+ const internal = this.client;
358
+ const payload = JSON.parse(stableStringify(jsonSafe(data.payload ?? null)));
359
+ this.eventLedger.push({
360
+ observation_id: this.rawPushObservationId, event, event_index: this.eventLedger.length + 1, time_ms: Date.now(),
361
+ scope: event === 'message.received' ? 'p2p' : 'group', receiver_aid: this.aid,
362
+ device_id: textOf(internal._deviceId), slot_id: textOf(internal._slotId ?? this.slotId),
363
+ sender_aid: textOf(data.sender_aid ?? data.from_aid ?? data.from), group_id: textOf(data.group_id),
364
+ message_id: textOf(data.message_id ?? data.id), seq: Number(data.seq ?? data.message_seq ?? data.msg_seq ?? 0) || 0,
365
+ storage: textOf(data.storage), volatile_cursor: textOf(data.volatile_cursor),
366
+ payload, payload_sha256: sha256Json(payload), need_render: data.need_render !== false,
367
+ ...(Object.prototype.hasOwnProperty.call(data, 'mention_mode') ? { mention_mode: data.mention_mode } : {}),
368
+ });
369
+ }
370
+ isObservedPull(method) {
371
+ return ['message.pull', 'message.v2.pull', 'group.pull', 'group.v2.pull', 'group.pull_messages', 'group.pull_events', 'group.events'].includes(method);
372
+ }
373
+ startRpcSpan(method, value) {
374
+ if (!this.isObservedPull(method) && !['message.peers', 'message.ack', 'message.v2.ack', 'group.ack', 'group.v2.ack', 'group.ack_messages', 'group.ack_events'].includes(method))
375
+ return null;
376
+ const params = jsonObjectOf(value) ?? {};
377
+ const senderAid = textOf(params.sender_aid), groupId = textOf(params.group_id ?? params.group_aid);
378
+ const namespace = method === 'message.peers' ? '' : method.startsWith('group.')
379
+ ? groupId ? `${method === 'group.pull_events' || method === 'group.events' || method === 'group.ack_events' ? 'group_event' : 'group'}:${groupId}` : ''
380
+ : senderAid ? `p2p.peer.v1/${Buffer.from(senderAid, 'utf8').toString('base64url')}` : `p2p:${this.aid}`;
381
+ const span = {
382
+ observation_id: this.rawPushObservationId, span_id: crypto.randomUUID(), boundary: 'transport_call', method,
383
+ start_ms: Date.now(), response_ms: null, end_ms: null, success: null,
384
+ cursor_scope: textOf(params.cursor_scope) || (method === 'message.peers' || senderAid ? 'peer' : method.startsWith('group.') ? namespace.startsWith('group_event:') ? 'group_event' : 'group' : 'global'), sender_aid: senderAid,
385
+ group_id: groupId, namespace, request_window: this.observedRequestWindow(params),
386
+ response_message_count: 0, response_volatile_count: 0, response_event_count: 0,
387
+ response_item_keys: [], response_event_keys: [],
388
+ has_more: null, persistent_has_more: null, volatile_has_more: null, has_more_persistent: null, has_more_volatile: null, effective_ack_seq: null,
389
+ };
390
+ this.rpcSpans.push(span);
391
+ return span;
392
+ }
393
+ observedRequestWindow(params) {
394
+ const window = {};
395
+ for (const key of ['after_seq', 'before_seq', 'until_seq', 'after_message_seq', 'after_event_seq', 'up_to_seq', 'seq', 'ack_seq', 'msg_seq', 'event_seq', 'limit', 'max_pages']) {
396
+ if (typeof params[key] === 'number' && Number.isSafeInteger(params[key]))
397
+ window[key] = params[key];
398
+ }
399
+ for (const key of ['window_mode', 'cursor_scope', 'sender_aid', 'group_id', 'group_aid', 'device_id', 'slot_id', 'after_volatile_cursor', 'mode', 'after_sender_aid']) {
400
+ if (typeof params[key] === 'string')
401
+ window[key] = params[key];
402
+ }
403
+ for (const key of ['force', 'pending_only'])
404
+ if (typeof params[key] === 'boolean')
405
+ window[key] = params[key];
406
+ for (const key of ['volatile_cursors', 'volatile_ids']) {
407
+ if (Array.isArray(params[key]))
408
+ window[key] = params[key].filter((item) => typeof item === 'string');
409
+ }
410
+ return window;
411
+ }
412
+ finishRpcResponse(span, value) {
413
+ const response = jsonObjectOf(value) ?? {};
414
+ const messages = Array.isArray(response.messages) ? response.messages : [];
415
+ span.response_ms = Date.now();
416
+ span.response_message_count = messages.length;
417
+ span.response_volatile_count = messages.filter((item) => jsonObjectOf(item)?.storage === 'volatile').length;
418
+ span.response_event_count = Array.isArray(response.events) ? response.events.length : 0;
419
+ const sequenceOf = (raw, ...keys) => {
420
+ for (const key of keys) {
421
+ const value = raw[key];
422
+ if (typeof value === 'number' && Number.isFinite(value) && Number.isInteger(value))
423
+ return value;
424
+ }
425
+ return 0;
211
426
  };
427
+ const itemKeys = new Map();
428
+ for (const key of ['messages', 'volatile_messages', 'volatile']) {
429
+ for (const item of Array.isArray(response[key]) ? response[key] : []) {
430
+ const raw = jsonObjectOf(item) ?? {}, cursor = textOf(raw.volatile_cursor);
431
+ const identity = { message_id: textOf(raw.message_id ?? raw.id),
432
+ seq: sequenceOf(raw, 'seq', 'message_seq', 'msg_seq'),
433
+ storage: typeof raw.storage === 'string' ? raw.storage : cursor ? 'volatile' : 'persistent', volatile_cursor: cursor };
434
+ itemKeys.set(JSON.stringify(identity), identity);
435
+ }
436
+ }
437
+ span.response_item_keys = [...itemKeys.values()];
438
+ span.response_event_keys = (Array.isArray(response.events) ? response.events : []).map((item) => {
439
+ const raw = jsonObjectOf(item) ?? {};
440
+ return { event_id: textOf(raw.event_id), event_seq: sequenceOf(raw, 'event_seq', 'seq') };
441
+ });
442
+ span.has_more = typeof response.has_more === 'boolean' ? response.has_more : null;
443
+ span.has_more_persistent = typeof response.persistent_has_more === 'boolean' ? response.persistent_has_more : null;
444
+ span.has_more_volatile = typeof response.volatile_has_more === 'boolean' ? response.volatile_has_more : null;
445
+ span.persistent_has_more = span.has_more_persistent;
446
+ span.volatile_has_more = span.has_more_volatile;
447
+ for (const key of ['peer_ack_seq', 'effective_ack_seq', 'server_ack_seq', 'ack_seq', 'up_to_seq', 'acked_seq']) {
448
+ if (typeof response[key] === 'number' && Number.isSafeInteger(response[key]))
449
+ span[key] = response[key];
450
+ }
451
+ const effective = response.peer_ack_seq ?? response.effective_ack_seq ?? response.ack_seq ?? response.up_to_seq ?? response.acked_seq;
452
+ if (typeof effective === 'number' && Number.isSafeInteger(effective))
453
+ span.effective_ack_seq = effective;
454
+ const volatileAck = jsonObjectOf(response.volatile_ack);
455
+ if (volatileAck) {
456
+ const ack = {};
457
+ for (const key of ['accepted', 'expired'])
458
+ if (typeof volatileAck[key] === 'number' && Number.isSafeInteger(volatileAck[key]))
459
+ ack[key] = volatileAck[key];
460
+ if (typeof volatileAck.storage === 'string')
461
+ ack.storage = volatileAck.storage;
462
+ span.volatile_ack = ack;
463
+ }
464
+ }
465
+ releasePullBarrier() {
466
+ const barrier = this.pullBarrier;
467
+ if (!barrier || barrier.released)
468
+ return;
469
+ barrier.released = true;
470
+ barrier.release();
471
+ }
472
+ injectRPCFailure(method, span) {
473
+ const failure = this.rpcFailures?.find((item) => !item.hit && item.method === method && item.sender_aid === span.sender_aid);
474
+ if (!failure)
475
+ return;
476
+ failure.hit = true;
477
+ span.injected_failure = failure.kind ?? 'error';
478
+ if (failure.kind === 'timeout')
479
+ throw new TimeoutError('cross-sdk 响应后一次超时');
480
+ throw new Error('cross-sdk 响应后一次失败');
481
+ }
482
+ resetObservationLedgers(pullBarrierCount = 0, scope = 'all', controls = {}) {
483
+ this.releasePullBarrier();
484
+ this.observationGeneration = (this.observationGeneration ?? 0) + 1;
485
+ this.eventLedger = [];
486
+ this.batchEvents = [];
487
+ this.singleEventCounts = { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
488
+ this.rpcSpans = [];
489
+ this.rpcFailures = Array.isArray(controls.rpc_failures) ? controls.rpc_failures.map((item) => ({ ...jsonObjectOf(item) })) : [];
490
+ this.pullBarrier = null;
491
+ if (Number.isSafeInteger(pullBarrierCount) && pullBarrierCount > 0) {
492
+ let release;
493
+ const promise = new Promise((resolve) => { release = resolve; });
494
+ this.pullBarrier = { target: pullBarrierCount, scope: scope === 'p2p' ? 'p2p' : 'all', admitted: 0, waiting: 0, released: false, promise, release,
495
+ sender: textOf(controls.pull_barrier_sender_aid), method: textOf(controls.pull_barrier_method), preserve: controls.preserve_pull_barrier === true };
496
+ }
497
+ }
498
+ async waitForPullBarrier(method, signal, timeout, span, owner) {
499
+ const barrier = this.pullBarrier;
500
+ if (!barrier || barrier.released || barrier.admitted >= barrier.target)
501
+ return;
502
+ if (barrier.scope === 'p2p' && method !== 'message.pull' && method !== 'message.v2.pull' && barrier.method !== method)
503
+ return;
504
+ if ((barrier.sender && barrier.sender !== span.sender_aid) || (barrier.method && barrier.method !== method))
505
+ return;
506
+ barrier.admitted += 1;
507
+ barrier.waiting += 1;
508
+ if (barrier.preserve) {
509
+ barrier.heldMethod = method;
510
+ barrier.owner = owner;
511
+ barrier.span = span;
512
+ }
513
+ let timer;
514
+ let abort;
515
+ try {
516
+ await Promise.race([barrier.promise, new Promise((_resolve, reject) => {
517
+ abort = () => reject(new Error('pull observation barrier cancelled'));
518
+ if (!barrier.preserve)
519
+ signal.addEventListener('abort', abort, { once: true });
520
+ if (signal.aborted && !barrier.preserve)
521
+ abort();
522
+ timer = setTimeout(() => reject(new Error('pull observation barrier timeout')), Math.max(1, timeout));
523
+ })]);
524
+ }
525
+ finally {
526
+ if (timer)
527
+ clearTimeout(timer);
528
+ signal.removeEventListener('abort', abort);
529
+ barrier.waiting -= 1;
530
+ }
212
531
  }
213
532
  storeRawPushObservation(rawEvent, scope, defaultKind, value) {
214
533
  const data = jsonObjectOf(value) ?? {};
@@ -297,6 +616,7 @@ export class CrossSdkTsAgent {
297
616
  };
298
617
  }
299
618
  async close() {
619
+ this.resetObservationLedgers();
300
620
  try {
301
621
  await this.client.close();
302
622
  }
@@ -441,7 +761,7 @@ export class CrossSdkTsAgent {
441
761
  if (!current.exists)
442
762
  fileErrors[name] = 'not_found';
443
763
  else
444
- files[name] = { raw_base64: Buffer.from(current.content, 'utf8').toString('base64'), sha256: crypto.createHash('sha256').update(current.content, 'utf8').digest('hex'), revision: current.revision };
764
+ files[name] = { raw_base64: Buffer.from(current.content, 'utf8').toString('base64'), sha256: crypto.createHash('sha256').update(current.content, 'utf8').digest('hex') };
445
765
  }
446
766
  catch (err) {
447
767
  fileErrors[name] = err instanceof Error ? err.message : String(err);
@@ -487,6 +807,7 @@ export class CrossSdkTsAgent {
487
807
  decrypted,
488
808
  encrypted: Boolean(data.e2ee ?? data.encrypted),
489
809
  seq: Number(data.seq ?? data.message_seq ?? 0) || 0,
810
+ need_render: data.need_render !== false,
490
811
  ack_seq: Number(data.ack_seq ?? 0) || 0,
491
812
  storage: textOf(data.storage),
492
813
  volatile_cursor: textOf(data.volatile_cursor),
@@ -512,6 +833,7 @@ export class CrossSdkTsAgent {
512
833
  decrypted,
513
834
  encrypted: Boolean(data.e2ee ?? data.encrypted),
514
835
  seq: Number(data.seq ?? data.message_seq ?? data.msg_seq ?? 0) || 0,
836
+ need_render: data.need_render !== false,
515
837
  error_code: errorCode,
516
838
  raw_sha256: sha256Json(data),
517
839
  ...metadata,
@@ -541,6 +863,7 @@ export class CrossSdkTsAgent {
541
863
  }
542
864
  if (req.method === 'POST' && url.pathname === '/reset') {
543
865
  const body = await readJson(req);
866
+ this.resetObservationLedgers();
544
867
  const traceId = textOf(body.trace_id ?? '');
545
868
  if (traceId) {
546
869
  this.inbox = this.inbox.filter((item) => item.trace_id !== traceId);
@@ -551,6 +874,7 @@ export class CrossSdkTsAgent {
551
874
  else {
552
875
  this.inbox = [];
553
876
  this.groupInbox = [];
877
+ this.peersSnapshots = [];
554
878
  this.traces.clear();
555
879
  this.sendResults.clear();
556
880
  }
@@ -559,11 +883,25 @@ export class CrossSdkTsAgent {
559
883
  }
560
884
  if (req.method === 'POST' && url.pathname === '/test/raw-push/reset') {
561
885
  const body = await readJson(req);
886
+ if ((body.pull_barrier_count !== undefined && (typeof body.pull_barrier_count !== 'number' || !Number.isSafeInteger(body.pull_barrier_count) || body.pull_barrier_count < 0)) ||
887
+ (body.pull_barrier_scope !== undefined && !['all', 'p2p'].includes(body.pull_barrier_scope)) ||
888
+ (body.observation_id !== undefined && typeof body.observation_id !== 'string') ||
889
+ (body.release_pull_barrier !== undefined && typeof body.release_pull_barrier !== 'boolean')) {
890
+ sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid observation parameters' });
891
+ return;
892
+ }
893
+ if (body.release_pull_barrier === true) {
894
+ this.releasePullBarrier();
895
+ sendJson(res, 200, { ok: true, observation_id: this.rawPushObservationId });
896
+ return;
897
+ }
898
+ this.resetObservationLedgers(body.pull_barrier_count, textOf(body.pull_barrier_scope), body);
562
899
  this.rawPushObservations = [];
563
900
  this.rawPushRpcMethods = [];
564
901
  this.groupChangedEvents = [];
565
902
  this.groupInviteEvents = [];
566
903
  this.readStateEvents = [];
904
+ this.peersSnapshots = [];
567
905
  this.rawPushObservationId = textOf(body.observation_id ?? '');
568
906
  sendJson(res, 200, { ok: true, observation_id: this.rawPushObservationId });
569
907
  return;
@@ -582,6 +920,23 @@ export class CrossSdkTsAgent {
582
920
  let rpcEntries = [...this.rawPushRpcMethods];
583
921
  let groupChangedEvents = [...this.groupChangedEvents];
584
922
  let groupInviteEvents = [...this.groupInviteEvents];
923
+ let eventLedger = [...this.eventLedger ?? []];
924
+ let rpcSpans = [...this.rpcSpans ?? []];
925
+ let batchEvents = [...this.batchEvents ?? []];
926
+ if (observationId)
927
+ batchEvents = batchEvents.filter((item) => textOf(item.observation_id) === observationId);
928
+ if (observationId)
929
+ eventLedger = eventLedger.filter((item) => textOf(item.observation_id) === observationId);
930
+ if (observationId)
931
+ rpcSpans = rpcSpans.filter((item) => textOf(item.observation_id) === observationId);
932
+ if (filters.scope)
933
+ eventLedger = eventLedger.filter((item) => textOf(item.scope) === filters.scope);
934
+ if (filters.scope)
935
+ rpcSpans = rpcSpans.filter((item) => textOf(item.method).startsWith(filters.scope === 'p2p' ? 'message.' : 'group.'));
936
+ if (filters.group_id)
937
+ eventLedger = eventLedger.filter((item) => textOf(item.group_id) === filters.group_id);
938
+ if (filters.group_id)
939
+ rpcSpans = rpcSpans.filter((item) => textOf(item.group_id) === filters.group_id);
585
940
  if (observationId)
586
941
  rpcEntries = rpcEntries.filter((item) => textOf(item.observation_id) === observationId);
587
942
  if (observationId)
@@ -598,11 +953,18 @@ export class CrossSdkTsAgent {
598
953
  for (const method of rpcMethods)
599
954
  rpcMethodCounts[method] = Number(rpcMethodCounts[method] ?? 0) + 1;
600
955
  sendJson(res, 200, {
601
- ok: true, items, count: items.length, rpc_methods: rpcMethods, rpc_method_counts: rpcMethodCounts,
956
+ ok: true, time_ms: Date.now(), items, count: items.length, rpc_methods: rpcMethods, rpc_method_counts: rpcMethodCounts,
602
957
  rpc_calls: rpcEntries,
603
958
  group_changed_events: groupChangedEvents.slice(-limit),
604
959
  group_invite_events: groupInviteEvents.slice(-limit),
605
960
  read_state_events: this.readStateEvents.slice(-limit),
961
+ peers_snapshots: this.peersSnapshots.slice(-limit),
962
+ event_ledger: eventLedger,
963
+ rpc_spans: rpcSpans,
964
+ batch_event_mode: this.batchEventMode ?? false,
965
+ batch_events: batchEvents,
966
+ single_event_counts: this.singleEventCounts ?? { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 },
967
+ pull_barrier: { target: this.pullBarrier?.target ?? 0, waiting: this.pullBarrier?.waiting ?? 0, released: this.pullBarrier?.released ?? true, scope: this.pullBarrier?.scope ?? 'all' },
606
968
  });
607
969
  return;
608
970
  }
@@ -610,6 +972,10 @@ export class CrossSdkTsAgent {
610
972
  sendJson(res, 200, this.identity());
611
973
  return;
612
974
  }
975
+ if (req.method === 'POST' && url.pathname === '/signature/call') {
976
+ await this.signatureCall(req, res);
977
+ return;
978
+ }
613
979
  if (req.method === 'POST' && url.pathname === '/ownership/bind') {
614
980
  await this.ownershipBind(req, res);
615
981
  return;
@@ -776,6 +1142,95 @@ export class CrossSdkTsAgent {
776
1142
  });
777
1143
  }
778
1144
  }
1145
+ async signatureCall(req, res) {
1146
+ const body = await readJson(req);
1147
+ const action = textOf(body.action);
1148
+ const target = textOf(body.aid ?? this.aid);
1149
+ const store = this.aidStore();
1150
+ let directory = '', file = '';
1151
+ try {
1152
+ if (action === 'upload_agent_md' || action === 'download_agent_md') {
1153
+ const operation = action === 'upload_agent_md'
1154
+ ? await store.uploadAgentMd(target, textOf(body.input))
1155
+ : await store.downloadAgentMd(target);
1156
+ sendJson(res, 200, operation.ok
1157
+ ? { ok: true, result: jsonSafe(operation.data) }
1158
+ : { ok: false, error_code: operation.error.code, error_message: operation.error.message });
1159
+ return;
1160
+ }
1161
+ if (action === 'certificate') {
1162
+ const loaded = store.load(target);
1163
+ if (!loaded.ok) {
1164
+ sendJson(res, 200, { ok: false, error_code: loaded.error.code, error_message: loaded.error.message });
1165
+ return;
1166
+ }
1167
+ const identity = loaded.data.aid;
1168
+ const result = { aid: target, certificate: identity.certPem, cert_fingerprint: identity.certFingerprint };
1169
+ sendJson(res, 200, { ok: true, result, certificate: identity.certPem, cert_fingerprint: identity.certFingerprint });
1170
+ return;
1171
+ }
1172
+ if (action !== 'sign' && action !== 'verify') {
1173
+ sendJson(res, 200, { ok: false, error_code: 'bad_request', error_message: `unsupported signature action: ${action}` });
1174
+ return;
1175
+ }
1176
+ const options = {
1177
+ inputType: (body.input_type ?? 'text'),
1178
+ format: (body.format ?? 'raw'),
1179
+ append: body.append === true,
1180
+ tag: body.tag == null ? undefined : textOf(body.tag),
1181
+ inPlace: body.in_place === true,
1182
+ };
1183
+ let input = textOf(body.input);
1184
+ if (options.inputType === 'file') {
1185
+ directory = fs.mkdtempSync(path.join(tmpdir(), 'aun-cross-signature-'));
1186
+ file = path.join(directory, 'input');
1187
+ fs.writeFileSync(file, body.input_base64 == null ? Buffer.from(input, 'utf8') : Buffer.from(textOf(body.input_base64), 'base64'));
1188
+ input = file;
1189
+ }
1190
+ if (action === 'verify') {
1191
+ const certificates = jsonObjectOf(body.certificates) ?? {};
1192
+ const certificate = textOf(certificates[target]);
1193
+ if (certificate)
1194
+ options.identity = AID._create({ aid: target, aunPath: this.aunPath, certPem: certificate, privateKeyPem: null, certValid: true, privateKeyValid: false });
1195
+ if (options.format === 'document')
1196
+ options.certificateResolver = (signer) => {
1197
+ const certificate = textOf(certificates[signer]);
1198
+ if (!certificate)
1199
+ throw new Error(`certificate unavailable: ${signer}`);
1200
+ return certificate;
1201
+ };
1202
+ }
1203
+ const operation = action === 'sign'
1204
+ ? await store.sign(target, input, options)
1205
+ : await store.verify(target, input, body.signature == null ? undefined : textOf(body.signature), options);
1206
+ if (!operation.ok) {
1207
+ const response = { ok: false, error_code: operation.error.code, error_message: operation.error.message };
1208
+ if (file)
1209
+ response.file_base64 = fs.readFileSync(file).toString('base64');
1210
+ sendJson(res, 200, response);
1211
+ return;
1212
+ }
1213
+ const loaded = options.identity ? { ok: true, data: { aid: options.identity } } : store.load(target);
1214
+ const identity = loaded.ok ? loaded.data.aid : undefined;
1215
+ const response = { ok: true, result: jsonSafe(operation.data), certificate: identity?.certPem ?? '', cert_fingerprint: identity?.certFingerprint ?? '' };
1216
+ if (file)
1217
+ response.file_base64 = fs.readFileSync(file).toString('base64');
1218
+ sendJson(res, 200, response);
1219
+ }
1220
+ catch (error) {
1221
+ const response = { ok: false, error_code: error instanceof Error ? error.name : 'Error', error_message: error instanceof Error ? error.message : String(error) };
1222
+ if (file && fs.existsSync(file))
1223
+ response.file_base64 = fs.readFileSync(file).toString('base64');
1224
+ sendJson(res, 200, response);
1225
+ }
1226
+ finally {
1227
+ store.close();
1228
+ if (file)
1229
+ fs.unlinkSync(file);
1230
+ if (directory)
1231
+ fs.rmdirSync(directory);
1232
+ }
1233
+ }
779
1234
  async send(req, res) {
780
1235
  const body = await readJson(req);
781
1236
  const traceId = textOf(body.trace_id || crypto.randomUUID().replace(/-/g, ''));
@@ -801,6 +1256,8 @@ export class CrossSdkTsAgent {
801
1256
  };
802
1257
  if (body.ttl !== undefined)
803
1258
  params.ttl = Number(body.ttl);
1259
+ if (body.need_render !== undefined)
1260
+ params.need_render = body.need_render;
804
1261
  try {
805
1262
  const result = await this.client.call('message.send', params);
806
1263
  const resultObj = result && typeof result === 'object' ? result : {};
@@ -836,6 +1293,10 @@ export class CrossSdkTsAgent {
836
1293
  const params = {};
837
1294
  if (seq > 0)
838
1295
  params.seq = seq;
1296
+ if (textOf(body.sender_aid).trim())
1297
+ params.sender_aid = textOf(body.sender_aid).trim();
1298
+ if (body.cursor_scope !== undefined)
1299
+ params.cursor_scope = body.cursor_scope;
839
1300
  if (Array.isArray(body.volatile_cursors))
840
1301
  params.volatile_cursors = body.volatile_cursors;
841
1302
  else if (textOf(body.volatile_cursor))
@@ -857,9 +1318,26 @@ export class CrossSdkTsAgent {
857
1318
  }
858
1319
  async pull(req, res) {
859
1320
  const body = await readJson(req);
860
- const afterSeq = Number(body.after_seq ?? 0) || 0;
861
- const limit = Number(body.limit ?? 50) || 50;
862
- const params = { after_seq: afterSeq, limit };
1321
+ if ((body.limit !== undefined && (typeof body.limit !== 'number' || !Number.isSafeInteger(body.limit) || body.limit < 1 || body.limit > 200)) ||
1322
+ (body.max_pages !== undefined && (typeof body.max_pages !== 'number' || !Number.isSafeInteger(body.max_pages) || body.max_pages < 1)) ||
1323
+ (body.after_seq !== undefined && (typeof body.after_seq !== 'number' || !Number.isSafeInteger(body.after_seq) || body.after_seq < 0)) ||
1324
+ ['sender_aid', 'cursor_scope', 'after_volatile_cursor'].some((key) => body[key] !== undefined && typeof body[key] !== 'string') ||
1325
+ (body.force !== undefined && typeof body.force !== 'boolean') ||
1326
+ (body.window_mode !== undefined && !['forward', 'tail'].includes(body.window_mode))) {
1327
+ sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid pull parameters' });
1328
+ return;
1329
+ }
1330
+ const senderAid = textOf(body.sender_aid).trim();
1331
+ const limit = body.limit ?? 50;
1332
+ const params = { limit };
1333
+ if (senderAid)
1334
+ params.sender_aid = senderAid;
1335
+ if (body.after_seq !== undefined)
1336
+ params.after_seq = body.after_seq;
1337
+ for (const key of ['cursor_scope', 'force', 'window_mode', 'max_pages']) {
1338
+ if (body[key] !== undefined)
1339
+ params[key] = body[key];
1340
+ }
863
1341
  if (textOf(body.after_volatile_cursor))
864
1342
  params.after_volatile_cursor = textOf(body.after_volatile_cursor);
865
1343
  try {
@@ -881,6 +1359,20 @@ export class CrossSdkTsAgent {
881
1359
  }
882
1360
  async messageCall(req, res) {
883
1361
  const body = await readJson(req);
1362
+ if (['start', 'status', 'detach'].includes(textOf(body.action))) {
1363
+ this.messageOperations ??= new Map();
1364
+ const id = textOf(body.operation_id);
1365
+ if (body.action === 'start') {
1366
+ const operation = { ok: true, operation_id: id, status: 'running', started_ms: Date.now() };
1367
+ this.messageOperations.set(id, operation);
1368
+ 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) }));
1369
+ }
1370
+ const operation = this.messageOperations.get(id);
1371
+ if (body.action === 'detach')
1372
+ Object.assign(operation, { detached: true, cancellation: 'application_wait_only', detached_ms: Date.now() });
1373
+ sendJson(res, 200, operation);
1374
+ return;
1375
+ }
884
1376
  const traceId = textOf(body.trace_id || crypto.randomUUID().replace(/-/g, ''));
885
1377
  const method = textOf(body.method).trim();
886
1378
  const params = jsonObjectOf(body.params) ?? {};
@@ -912,15 +1404,15 @@ export class CrossSdkTsAgent {
912
1404
  sendJson(res, 500, error);
913
1405
  }
914
1406
  }
915
- syncNamespace(scope, groupId = '') {
1407
+ syncNamespace(scope, groupId = '', senderAid = '') {
916
1408
  if (scope === 'p2p')
917
- return `p2p:${this.aid}`;
1409
+ return senderAid ? `p2p.peer.v1/${Buffer.from(senderAid, 'utf8').toString('base64url')}` : `p2p:${this.aid}`;
918
1410
  if (scope === 'group' && groupId)
919
1411
  return `group:${groupId}`;
920
1412
  throw new Error('scope must be p2p, or group with group_id');
921
1413
  }
922
- syncStateValue(scope, groupId = '') {
923
- const namespace = this.syncNamespace(scope, groupId);
1414
+ syncStateValue(scope, groupId = '', senderAid = '') {
1415
+ const namespace = this.syncNamespace(scope, groupId, senderAid);
924
1416
  const tracker = this.client._seqTracker;
925
1417
  if (!tracker)
926
1418
  throw new Error('SeqTracker is unavailable');
@@ -929,9 +1421,10 @@ export class CrossSdkTsAgent {
929
1421
  syncState(url, res) {
930
1422
  const scope = textOf(url.searchParams.get('scope') || 'p2p').trim().toLowerCase();
931
1423
  const groupId = textOf(url.searchParams.get('group_id') || '').trim();
1424
+ const senderAid = textOf(url.searchParams.get('sender_aid') || '').trim();
932
1425
  try {
933
- const snapshot = this.syncStateValue(scope, groupId);
934
- sendJson(res, 200, { ok: true, scope, ...snapshot });
1426
+ const snapshot = this.syncStateValue(scope, groupId, senderAid);
1427
+ sendJson(res, 200, { ok: true, scope, ...snapshot, peers_snapshots: this.peersSnapshots });
935
1428
  }
936
1429
  catch (err) {
937
1430
  sendJson(res, 400, {
@@ -951,27 +1444,24 @@ export class CrossSdkTsAgent {
951
1444
  const body = await readJson(req);
952
1445
  const scope = textOf(body.scope || 'p2p').trim().toLowerCase();
953
1446
  const groupId = textOf(body.group_id || '').trim();
1447
+ const senderAid = textOf(body.sender_aid || '').trim();
954
1448
  const afterSeq = Number(body.after_seq ?? 0) || 0;
955
1449
  const limit = Number(body.limit ?? 50) || 50;
956
1450
  try {
957
- const params = { after_seq: afterSeq, limit };
958
- if (tail)
959
- params.window_mode = 'tail';
1451
+ const params = { limit, window_mode: tail ? 'tail' : 'forward' };
1452
+ if (scope === 'group' || body.after_seq !== undefined)
1453
+ params.after_seq = afterSeq;
1454
+ if (scope === 'p2p' && senderAid)
1455
+ params.sender_aid = senderAid;
1456
+ for (const key of ['cursor_scope', 'force', 'max_pages', 'after_volatile_cursor']) {
1457
+ if (body[key] !== undefined)
1458
+ params[key] = body[key];
1459
+ }
960
1460
  const method = scope === 'group' ? 'group.pull' : 'message.pull';
961
1461
  if (scope === 'group')
962
1462
  params.group_id = groupId;
963
- this.syncNamespace(scope, groupId);
964
- let result;
965
- if (tail) {
966
- result = await this.client.call(method, params);
967
- }
968
- else {
969
- const transport = this.client._transport;
970
- if (!transport?.call)
971
- throw new Error('raw transport is unavailable');
972
- const wireMethod = scope === 'group' ? 'group.v2.pull' : 'message.v2.pull';
973
- result = await transport.call(wireMethod, params);
974
- }
1463
+ this.syncNamespace(scope, groupId, senderAid);
1464
+ const result = await this.client.call(method, params);
975
1465
  const resultObj = jsonObjectOf(result) ?? {};
976
1466
  const messages = Array.isArray(resultObj.messages) ? resultObj.messages : [];
977
1467
  if (tail) {
@@ -982,7 +1472,7 @@ export class CrossSdkTsAgent {
982
1472
  await this.storeInboxItem(this.normalizeMessage(msg, true));
983
1473
  }
984
1474
  }
985
- const snapshot = this.syncStateValue(scope, groupId);
1475
+ const snapshot = this.syncStateValue(scope, groupId, senderAid);
986
1476
  sendJson(res, 200, { ok: true, scope, ...snapshot, result: jsonSafe(result) });
987
1477
  }
988
1478
  catch (err) {
@@ -998,6 +1488,7 @@ export class CrossSdkTsAgent {
998
1488
  const body = await readJson(req);
999
1489
  const scope = textOf(body.scope || 'p2p').trim().toLowerCase();
1000
1490
  const groupId = textOf(body.group_id || '').trim();
1491
+ const senderAid = textOf(body.sender_aid || '').trim();
1001
1492
  const beforeSeq = Number(body.before_seq ?? 0) || 0;
1002
1493
  const limit = Number(body.limit ?? 50) || 50;
1003
1494
  try {
@@ -1005,9 +1496,13 @@ export class CrossSdkTsAgent {
1005
1496
  const method = scope === 'group' ? 'group.history' : 'message.history';
1006
1497
  if (scope === 'group')
1007
1498
  params.group_id = groupId;
1008
- this.syncNamespace(scope, groupId);
1499
+ if (scope === 'p2p' && senderAid)
1500
+ params.sender_aid = senderAid;
1501
+ if (body.cursor_scope !== undefined)
1502
+ params.cursor_scope = body.cursor_scope;
1503
+ this.syncNamespace(scope, groupId, senderAid);
1009
1504
  const result = await this.client.call(method, params);
1010
- const snapshot = this.syncStateValue(scope, groupId);
1505
+ const snapshot = this.syncStateValue(scope, groupId, senderAid);
1011
1506
  sendJson(res, 200, { ok: true, scope, ...snapshot, result: jsonSafe(result) });
1012
1507
  }
1013
1508
  catch (err) {
@@ -1022,7 +1517,25 @@ export class CrossSdkTsAgent {
1022
1517
  async sessionDisconnect(res) {
1023
1518
  try {
1024
1519
  this.ready = false;
1025
- await this.client.disconnect();
1520
+ const barrier = this.pullBarrier;
1521
+ let restore;
1522
+ if (barrier?.preserve && barrier.waiting === 1 && !barrier.released && ['message.pull', 'message.v2.pull'].includes(barrier.heldMethod ?? '')) {
1523
+ const binding = barrier.owner, pipeline = this.client._rpcPipeline;
1524
+ const owners = [...(pipeline?.pullGateStates.values() ?? [])].flatMap((state) => [...state.activeByNamespace.values()])
1525
+ .filter((job) => job === binding?.job && job.key === binding.key && job.namespace === binding.namespace && job.running && !job.timedOut && !job.invalidated && job.cancel === binding.cancel);
1526
+ if (owners.length !== 1)
1527
+ throw new Error('保留响应无法定位唯一受跟踪的 pull owner');
1528
+ const owner = owners[0], settled = owner.settled;
1529
+ owner.settled = Promise.resolve();
1530
+ restore = () => { owner.settled = settled; };
1531
+ barrier.span.barrier_owner_isolated = true;
1532
+ }
1533
+ try {
1534
+ await this.client.disconnect();
1535
+ }
1536
+ finally {
1537
+ restore?.();
1538
+ }
1026
1539
  sendJson(res, 200, { ok: true, state: textOf(this.client.state) });
1027
1540
  }
1028
1541
  catch (err) {
@@ -1035,14 +1548,19 @@ export class CrossSdkTsAgent {
1035
1548
  }
1036
1549
  async sessionConnect(req, res) {
1037
1550
  const body = await readJson(req);
1551
+ if (['background_sync', 'inline_realtime_payload_v1', 'batch_events'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
1552
+ sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid session parameters' });
1553
+ return;
1554
+ }
1038
1555
  const backgroundSync = body.background_sync === true;
1039
1556
  if (Object.prototype.hasOwnProperty.call(body, 'inline_realtime_payload_v1')) {
1040
1557
  this.inlineRealtimePayloadV1 = body.inline_realtime_payload_v1 !== false;
1041
1558
  }
1042
1559
  try {
1560
+ this.setBatchEventMode(typeof body.batch_events === 'boolean' ? body.batch_events : this.batchEventMode ?? false);
1043
1561
  await this.client.connect({ auto_reconnect: false, background_sync: backgroundSync });
1044
1562
  this.ready = true;
1045
- sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1 });
1563
+ sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1, batch_events: this.batchEventMode });
1046
1564
  }
1047
1565
  catch (err) {
1048
1566
  sendJson(res, 500, {
@@ -1054,19 +1572,25 @@ export class CrossSdkTsAgent {
1054
1572
  }
1055
1573
  async sessionRestart(req, res) {
1056
1574
  const body = await readJson(req);
1575
+ if (['background_sync', 'inline_realtime_payload_v1', 'batch_events'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
1576
+ sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid session parameters' });
1577
+ return;
1578
+ }
1057
1579
  const backgroundSync = body.background_sync === true;
1058
1580
  if (Object.prototype.hasOwnProperty.call(body, 'inline_realtime_payload_v1')) {
1059
1581
  this.inlineRealtimePayloadV1 = body.inline_realtime_payload_v1 !== false;
1060
1582
  }
1061
1583
  this.ready = false;
1062
1584
  try {
1585
+ if (typeof body.batch_events === 'boolean')
1586
+ this.batchEventMode = body.batch_events;
1063
1587
  await this.client.close();
1064
1588
  this.client = this.newClient();
1065
1589
  this.bindClientEvents();
1066
1590
  await this.ensureConnected(backgroundSync);
1067
1591
  this.startupError = '';
1068
1592
  this.ready = true;
1069
- sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1 });
1593
+ sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1, batch_events: this.batchEventMode });
1070
1594
  }
1071
1595
  catch (err) {
1072
1596
  this.startupError = `${err instanceof Error ? err.name : 'Error'}: ${err instanceof Error ? err.message : String(err)}`;
@@ -1136,9 +1660,16 @@ export class CrossSdkTsAgent {
1136
1660
  const now = Math.floor(Date.now() / 1000);
1137
1661
  if (op === 'request')
1138
1662
  return buildRequestBinding(textOf(params.method), textOf(params.target), params.params, textOf(params.credential_cid), textOf(params.nonce), textOf(params.request_id), now, now + 300);
1139
- if (['register', 'sign', 'certificate', 'create'].includes(op)) {
1663
+ if (['register', 'sign', 'certificate', 'create', 'prepare'].includes(op)) {
1140
1664
  const store = this.aidStore();
1141
1665
  try {
1666
+ if (op === 'prepare') {
1667
+ const request = jsonObjectOf(params.params) ?? {};
1668
+ const orgId = textOf(params.org_id ?? request.org_id);
1669
+ const loaded = orgId ? store.load(orgId) : null;
1670
+ const orgSigner = loaded?.ok && loaded.data.aid.isPrivateKeyValid() ? loaded.data.aid : undefined;
1671
+ return this.client.org.prepareRequest(textOf(params.method), request, textOf(params.target), this.client.currentAid, { ...jsonObjectOf(params.options), orgSigner });
1672
+ }
1142
1673
  if (op === 'register') {
1143
1674
  const registered = await store.register(textOf(params.org_id), { type: 'org', owner: this.aid, exttype: 'company' });
1144
1675
  if (!registered.ok)
@@ -1171,6 +1702,7 @@ export class CrossSdkTsAgent {
1171
1702
  const value = params.document ?? params.payload;
1172
1703
  const result = { canonical: new TextDecoder().decode(canonicalJson(value)), digest: digest(value), error_code: '' };
1173
1704
  if (op === 'verify' || op === 'verify_full') {
1705
+ const verificationNow = typeof params.now === 'number' ? params.now : now;
1174
1706
  const certificates = params.certificates;
1175
1707
  const resolver = (fingerprint) => certificates[fingerprint];
1176
1708
  try {
@@ -1178,7 +1710,7 @@ export class CrossSdkTsAgent {
1178
1710
  const actual = jsonObjectOf(params.context) ?? params;
1179
1711
  const memberships = jsonObjectOf(params.memberships) ?? {};
1180
1712
  result.verification = { ...this.client.credential.verify(value, { resolveCertificate: resolver, resolveActorCertificate: resolver,
1181
- resolveStatus: (cid) => params.statuses?.[cid], now, audience: textOf(actual.audience), subject: textOf(actual.subject) || undefined,
1713
+ resolveStatus: (cid) => params.statuses?.[cid], now: verificationNow, audience: textOf(actual.audience), subject: textOf(actual.subject) || undefined,
1182
1714
  ns: textOf(actual.ns), action: textOf(actual.action), resource: textOf(actual.resource), rootIssuer: textOf(actual.root_issuer) || undefined,
1183
1715
  params: jsonObjectOf(actual.params) ?? {}, units: jsonObjectOf(actual.units) ?? {}, clientIp: textOf(actual.client_ip), method: textOf(actual.method), target: textOf(actual.target),
1184
1716
  requestId: textOf(actual.request_id), nonce: textOf(actual.nonce), requestBinding: actual.request_binding, proofChain: actual.proof_chain, membershipBinding: actual.membership_binding,
@@ -1187,11 +1719,11 @@ export class CrossSdkTsAgent {
1187
1719
  resolveOwnership: (subject, owner) => (jsonObjectOf(params.ownerships) ?? {})[subject] === owner, checkRateLimit: () => params.rate_limit_allowed === true }) };
1188
1720
  }
1189
1721
  else if (params.kind === 'credential')
1190
- result.verification = { ...this.client.credential.verifySignature(value, resolver, now, textOf(params.audience)) };
1722
+ result.verification = { ...this.client.credential.verifySignature(value, resolver, verificationNow, textOf(params.audience)) };
1191
1723
  else if (params.kind === 'binding')
1192
- await verifyMembershipBinding(value, resolver, now, textOf(params.expected_org_signer));
1724
+ await verifyMembershipBinding(value, resolver, verificationNow, textOf(params.expected_org_signer));
1193
1725
  else
1194
- result.verification = { ...this.client.credential.verifyRegistrationReceipt(value, params.credential, params.request, resolver, textOf(params.service_aid), now) };
1726
+ result.verification = { ...this.client.credential.verifyRegistrationReceipt(value, params.credential, params.request, resolver, textOf(params.service_aid), verificationNow) };
1195
1727
  }
1196
1728
  catch (error) {
1197
1729
  if (!(error instanceof ProtocolError))
@@ -1950,6 +2482,7 @@ export class CrossSdkTsAgent {
1950
2482
  payload,
1951
2483
  encrypt: e2ee,
1952
2484
  message_id: messageId,
2485
+ ...(body.need_render !== undefined ? { need_render: body.need_render } : {}),
1953
2486
  });
1954
2487
  const resultObj = result && typeof result === 'object' && !Array.isArray(result) ? result : {};
1955
2488
  const response = {