@agentunion/fastaun 0.5.32 → 0.5.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +28 -26
  3. package/_packed_docs/CHANGELOG.md +58 -0
  4. package/_packed_docs/INDEX.md +17 -3
  5. package/_packed_docs/KITE_DOCS_GUIDE.md +10 -4
  6. package/_packed_docs/protocol/03-Gateway-/350/277/236/346/216/245/346/250/241/345/274/217.md +58 -8
  7. package/_packed_docs/sdk/0.5.33-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +464 -0
  8. package/_packed_docs/sdk/0.5.33-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +499 -0
  9. package/_packed_docs/sdk/0.5.34-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +133 -0
  10. package/_packed_docs/sdk/0.5.34-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +107 -0
  11. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +16 -13
  12. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +13 -1
  13. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +17 -7
  14. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +55 -14
  15. package/_packed_docs/sdk/09-collab-rpc-manual.md +48 -30
  16. package/_packed_docs/sdk/09-custody-api-manual.md +3 -3
  17. package/_packed_docs/sdk/09-group-rpc-manual.md +53 -6
  18. package/_packed_docs/sdk/09-message-rpc-manual.md +22 -0
  19. package/_packed_docs/sdk/09-meta-rpc-manual.md +2 -1
  20. package/_packed_docs/sdk/09-payload-reference.md +9 -8
  21. package/_packed_docs/sdk/09-proxy-rpc-manual.md +20 -14
  22. package/_packed_docs/sdk/09-storage-rpc-manual.md +1698 -1642
  23. package/_packed_docs/sdk/09-stream-rpc-manual.md +33 -20
  24. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +11 -9
  25. package/_packed_docs/sdk/AUN/344/272/213/344/273/266/345/217/221/345/270/203/350/256/242/351/230/205/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +53 -28
  26. package/_packed_docs/sdk/AUN/344/272/213/344/273/266/345/217/221/345/270/203/350/256/242/351/230/205/345/274/200/345/217/221/347/247/273/346/244/215/346/214/207/345/215/227.md +62 -28
  27. package/_packed_docs/sdk/INDEX.md +21 -9
  28. package/_packed_docs/sdk/README.md +11 -6
  29. package/dist/agent-md.d.ts +2 -1
  30. package/dist/agent-md.js +17 -0
  31. package/dist/agent-md.js.map +1 -1
  32. package/dist/auth.js +8 -2
  33. package/dist/auth.js.map +1 -1
  34. package/dist/client/control-messages.js +4 -4
  35. package/dist/client/control-messages.js.map +1 -1
  36. package/dist/client/delivery.d.ts +5 -0
  37. package/dist/client/delivery.js +146 -32
  38. package/dist/client/delivery.js.map +1 -1
  39. package/dist/client/lifecycle.js +3 -0
  40. package/dist/client/lifecycle.js.map +1 -1
  41. package/dist/client/rpc-budget.d.ts +21 -0
  42. package/dist/client/rpc-budget.js +82 -0
  43. package/dist/client/rpc-budget.js.map +1 -0
  44. package/dist/client/rpc-pipeline.d.ts +3 -0
  45. package/dist/client/rpc-pipeline.js +193 -65
  46. package/dist/client/rpc-pipeline.js.map +1 -1
  47. package/dist/client/sync-channels.d.ts +89 -0
  48. package/dist/client/sync-channels.js +489 -0
  49. package/dist/client/sync-channels.js.map +1 -0
  50. package/dist/client/v2-e2ee.d.ts +4 -1
  51. package/dist/client/v2-e2ee.js +401 -298
  52. package/dist/client/v2-e2ee.js.map +1 -1
  53. package/dist/client.d.ts +9 -4
  54. package/dist/client.js +125 -15
  55. package/dist/client.js.map +1 -1
  56. package/dist/index.d.ts +2 -2
  57. package/dist/index.js.map +1 -1
  58. package/dist/ownership-workflow.d.ts +23 -3
  59. package/dist/ownership-workflow.js +36 -13
  60. package/dist/ownership-workflow.js.map +1 -1
  61. package/dist/pubsub/binding.js +14 -1
  62. package/dist/pubsub/binding.js.map +1 -1
  63. package/dist/pubsub/channel.d.ts +4 -0
  64. package/dist/pubsub/channel.js +26 -0
  65. package/dist/pubsub/channel.js.map +1 -1
  66. package/dist/pubsub/client.d.ts +9 -0
  67. package/dist/pubsub/client.js +154 -31
  68. package/dist/pubsub/client.js.map +1 -1
  69. package/dist/pubsub/credentials.d.ts +3 -0
  70. package/dist/pubsub/credentials.js +57 -20
  71. package/dist/pubsub/credentials.js.map +1 -1
  72. package/dist/pubsub/index.d.ts +3 -0
  73. package/dist/pubsub/index.js +69 -53
  74. package/dist/pubsub/index.js.map +1 -1
  75. package/dist/pubsub/service.d.ts +3 -0
  76. package/dist/pubsub/service.js +167 -12
  77. package/dist/pubsub/service.js.map +1 -1
  78. package/dist/pubsub/types.d.ts +2 -0
  79. package/dist/storage/lowlevel.d.ts +7 -3
  80. package/dist/storage/lowlevel.js +15 -0
  81. package/dist/storage/lowlevel.js.map +1 -1
  82. package/dist/storage/read-cache.js +12 -0
  83. package/dist/storage/read-cache.js.map +1 -1
  84. package/dist/storage/types.d.ts +10 -0
  85. package/dist/storage/types.js.map +1 -1
  86. package/dist/storage/vfs.d.ts +7 -3
  87. package/dist/storage/vfs.js +2 -2
  88. package/dist/storage/vfs.js.map +1 -1
  89. package/dist/tools/cross-sdk-agent.d.ts +62 -6
  90. package/dist/tools/cross-sdk-agent.js +169 -37
  91. package/dist/tools/cross-sdk-agent.js.map +1 -1
  92. package/dist/transport.d.ts +12 -0
  93. package/dist/transport.js +91 -6
  94. package/dist/transport.js.map +1 -1
  95. package/dist/version.d.ts +1 -1
  96. package/dist/version.js +1 -1
  97. package/package.json +1 -1
@@ -25,6 +25,14 @@ const credentialProcessStartupId = crypto.randomUUID();
25
25
  const { DatabaseSync } = createRequire(import.meta.url)('node:sqlite');
26
26
  const cacheReadContext = new AsyncLocalStorage();
27
27
  const cacheReadObservers = new WeakMap();
28
+ function storageAclTargets(params) {
29
+ const raw = jsonObjectOf(params.subject), ownership = jsonObjectOf(raw?.ownership);
30
+ return {
31
+ granteeAid: (params.grantee_aid !== undefined ? params.grantee_aid : params.granteeAID),
32
+ granteeAids: (params.grantee_aids !== undefined ? params.grantee_aids : params.granteeAids),
33
+ subject: (raw && ownership ? { ...raw, ownership: { ownerAid: ownership.owner_aid ?? ownership.ownerAid, ...(ownership.scope !== undefined ? { scope: ownership.scope } : {}) } } : params.subject),
34
+ };
35
+ }
28
36
  function loadTrustedArchive(configPath, issuer) {
29
37
  if (!configPath)
30
38
  return undefined;
@@ -205,6 +213,7 @@ export class CrossSdkTsAgent {
205
213
  rpcFailures = [];
206
214
  messageOperations = new Map();
207
215
  rpcObservedTransports = new WeakSet();
216
+ rpcObservedChannelFactories = new WeakSet();
208
217
  rpcObservedPipelines = new WeakSet();
209
218
  rpcObservedPulls = new WeakMap();
210
219
  groupChangedEvents = [];
@@ -215,6 +224,11 @@ export class CrossSdkTsAgent {
215
224
  traces = new Map();
216
225
  sendResults = new Map();
217
226
  inlineRealtimePayloadV1 = true;
227
+ messageClassesV2 = envBool('AUN_TEST_MESSAGE_CLASSES_V2', true);
228
+ controlAdvertisedCapabilities = {};
229
+ controlNegotiatedCapabilities = {};
230
+ controlWireSends = [];
231
+ sdkControlMessages = [];
218
232
  constructor() {
219
233
  this.client = this.newClient();
220
234
  }
@@ -242,8 +256,41 @@ export class CrossSdkTsAgent {
242
256
  if (internal._configModel)
243
257
  internal._configModel.requireForwardSecrecy = false;
244
258
  internal._testSlotId = this.slotId;
259
+ const controls = internal._controlMessages?.();
260
+ if (controls) {
261
+ const consume = controls.consume.bind(controls);
262
+ controls.consume = async (message, admission) => {
263
+ const result = await consume(message, admission);
264
+ if (message.encrypted === true)
265
+ this.sdkControlMessages.push(structuredClone(message));
266
+ return result;
267
+ };
268
+ }
245
269
  return client;
246
270
  }
271
+ prepareControlMatrixRequest(method, params) {
272
+ if (method === 'auth.connect' && jsonObjectOf(params)) {
273
+ const request = { ...jsonObjectOf(params) };
274
+ const capabilities = { ...(jsonObjectOf(request.capabilities) ?? {}) };
275
+ if (this.inlineRealtimePayloadV1 === false)
276
+ delete capabilities.inline_realtime_payload_v1;
277
+ if (this.messageClassesV2 === false) {
278
+ delete capabilities.message_classes_v2;
279
+ delete capabilities.message_classes_v1;
280
+ delete capabilities.sdk_control_protocols;
281
+ }
282
+ request.capabilities = capabilities;
283
+ this.controlAdvertisedCapabilities = structuredClone(capabilities);
284
+ return request;
285
+ }
286
+ if (['message.send', 'message.v2.send', 'group.send', 'group.v2.send'].includes(method) && params) {
287
+ const envelope = jsonObjectOf(params.envelope) ?? jsonObjectOf(params.payload) ?? {};
288
+ this.controlWireSends.push({ method, message_id: textOf(params.message_id) || textOf(envelope.message_id) || textOf(jsonObjectOf(envelope.aad)?.message_id), to: textOf(params.to),
289
+ group_id: textOf(params.group_id ?? params.group_aid), message_class: params.message_class ?? null, message_class_present: 'message_class' in params, ttl: params.ttl ?? 0,
290
+ payload_type: textOf(envelope.type), protected_headers: structuredClone(jsonObjectOf(envelope.protected_headers) ?? jsonObjectOf(params.protected_headers) ?? {}) });
291
+ }
292
+ return params;
293
+ }
247
294
  bindClientEvents() {
248
295
  this.eventBoundClients ??= new WeakSet();
249
296
  if (this.eventBoundClients.has(this.client)) {
@@ -275,6 +322,17 @@ export class CrossSdkTsAgent {
275
322
  for (const event of ['message.read_changed', 'group.read_changed', 'read_state_sync']) {
276
323
  this.client.on(event, (payload) => { this.readStateEvents.push({ event, payload: jsonSafe(payload) }); });
277
324
  }
325
+ this.client.on('sync.error', (value) => {
326
+ const data = jsonObjectOf(value) ?? {}, error = textOf(data.error), method = textOf(data.method);
327
+ const internal = this.client;
328
+ this.eventLedger.push({
329
+ observation_id: this.rawPushObservationId, event: 'sync.error', event_index: this.eventLedger.length + 1, time_ms: Date.now(),
330
+ scope: method.startsWith('group.') ? 'group' : 'p2p', receiver_aid: this.aid,
331
+ device_id: textOf(internal._deviceId), slot_id: textOf(internal._slotId || this.slotId),
332
+ payload: { namespace: textOf(data.namespace), method, code: textOf(data.code),
333
+ error_sha256: crypto.createHash('sha256').update(error, 'utf8').digest('hex'), error_length: Buffer.byteLength(error, 'utf8') },
334
+ });
335
+ });
278
336
  this.client.on('message.peers_snapshot', (payload) => {
279
337
  this.peersSnapshots.push({ event: 'message.peers_snapshot', payload: jsonSafe(payload) });
280
338
  });
@@ -296,17 +354,17 @@ export class CrossSdkTsAgent {
296
354
  this.singleEventCounts ??= { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
297
355
  this.singleEventCounts[event] = Number(this.singleEventCounts[event] ?? 0) + 1;
298
356
  }
299
- async storeApplicationEvent(event, value) {
357
+ async storeApplicationEvent(event, value, observationId = this.rawPushObservationId) {
300
358
  if (event === 'message.received') {
301
- this.storeEventLedger(event, value);
359
+ this.storeEventLedger(event, value, observationId);
302
360
  await this.storeInboxItem(this.normalizeMessage(value, true));
303
361
  }
304
362
  else if (event === 'group.message_created') {
305
- this.storeEventLedger(event, value);
363
+ this.storeEventLedger(event, value, observationId);
306
364
  await this.storeGroupInboxItem(this.normalizeGroupMessage(value, true));
307
365
  }
308
366
  else if (event === 'group.changed')
309
- this.storeGroupChangedPublicEvent(value);
367
+ this.storeGroupChangedPublicEvent(value, observationId);
310
368
  }
311
369
  setP2pSubscription(enabled) {
312
370
  this.p2pSubscription = enabled;
@@ -318,10 +376,11 @@ export class CrossSdkTsAgent {
318
376
  if (enabled && !this.p2pEventHandler) {
319
377
  const client = this.client;
320
378
  this.p2pEventHandler = async (msg) => {
379
+ const observationId = this.rawPushObservationId;
380
+ this.countSingleEvent('message.received');
321
381
  if ((this.p2pDeliveryPaused || this.p2pDeliveryRetiredClients?.has(client)) && !await this.waitForP2pDelivery(client))
322
382
  return;
323
- this.countSingleEvent('message.received');
324
- await this.storeApplicationEvent('message.received', msg);
383
+ await this.storeApplicationEvent('message.received', msg, observationId);
325
384
  };
326
385
  this.client.on('message.received', this.p2pEventHandler);
327
386
  }
@@ -378,22 +437,37 @@ export class CrossSdkTsAgent {
378
437
  const client = this.client;
379
438
  for (const event of events) {
380
439
  const handler = async (value) => {
440
+ const observationId = this.rawPushObservationId;
381
441
  if (event === 'message.batch' && (this.p2pDeliveryPaused || this.p2pDeliveryRetiredClients?.has(client)) && !await this.waitForP2pDelivery(client))
382
442
  return;
383
443
  const data = jsonObjectOf(value) ?? {};
384
444
  const items = JSON.parse(JSON.stringify(jsonSafe(Array.isArray(data.items) ? data.items : [])));
385
445
  this.batchEvents ??= [];
386
- this.batchEvents.push({ observation_id: this.rawPushObservationId, time_ms: Date.now(), event,
446
+ this.batchEvents.push({ observation_id: observationId, time_ms: Date.now(), event,
387
447
  namespace: textOf(data.namespace), source: textOf(data.source), items });
388
448
  for (const item of items)
389
- await this.storeApplicationEvent(textOf(item.event), item.payload);
449
+ await this.storeApplicationEvent(textOf(item.event), item.payload, observationId);
390
450
  };
391
451
  this.batchEventHandlers.push({ event, handler });
392
452
  this.client.on(event, handler);
393
453
  }
394
454
  }
395
- installTransportRpcObserver() {
455
+ installTransportRpcObserver(transport) {
396
456
  const client = this.client;
457
+ if (!transport) {
458
+ const options = this.client._historyChannels?.().options;
459
+ this.rpcObservedChannelFactories ??= new WeakSet();
460
+ if (options && !this.rpcObservedChannelFactories.has(options)) {
461
+ const create = options.create;
462
+ this.rpcObservedChannelFactories.add(options);
463
+ options.create = async (...args) => {
464
+ const channel = await create.apply(options, args);
465
+ this.installTransportRpcObserver(channel.transport);
466
+ return channel;
467
+ };
468
+ }
469
+ transport = client._transport;
470
+ }
397
471
  const pipeline = client._rpcPipeline;
398
472
  this.rpcObservedPipelines ??= new WeakSet();
399
473
  this.rpcObservedPulls ??= new WeakMap();
@@ -415,7 +489,6 @@ export class CrossSdkTsAgent {
415
489
  return result;
416
490
  };
417
491
  }
418
- const transport = this.client._transport;
419
492
  transport?.setTraceMode('diag');
420
493
  if (!transport?.call || this.rpcObservedTransports.has(transport))
421
494
  return;
@@ -436,16 +509,7 @@ export class CrossSdkTsAgent {
436
509
  };
437
510
  transport.call = (method, params, timeout, trace, background) => {
438
511
  this.storeRawPushRpcMethod({ type: 'rpc', method, request_window: this.rawPushRequestWindow(params) });
439
- let requestParams = params;
440
- if (method === 'auth.connect' && !this.inlineRealtimePayloadV1 && jsonObjectOf(params)) {
441
- const request = { ...jsonObjectOf(params) };
442
- const capabilities = jsonObjectOf(request.capabilities);
443
- if (capabilities) {
444
- delete capabilities.inline_realtime_payload_v1;
445
- request.capabilities = capabilities;
446
- }
447
- requestParams = request;
448
- }
512
+ const requestParams = this.prepareControlMatrixRequest(method, params);
449
513
  const generation = this.observationGeneration;
450
514
  const span = this.startRpcSpan(method, requestParams);
451
515
  const controller = new AbortController();
@@ -455,6 +519,8 @@ export class CrossSdkTsAgent {
455
519
  try {
456
520
  request = originalCall.call(transport, method, requestParams, timeout, trace, background);
457
521
  const result = await request;
522
+ if (method === 'auth.connect')
523
+ this.controlNegotiatedCapabilities = structuredClone(jsonObjectOf(jsonObjectOf(result)?.negotiated_capabilities) ?? {});
458
524
  if (span) {
459
525
  this.finishRpcResponse(span, result);
460
526
  if (generation === this.observationGeneration && (this.isObservedPull(method) || this.pullBarrier?.method === method)) {
@@ -483,12 +549,12 @@ export class CrossSdkTsAgent {
483
549
  return pending;
484
550
  };
485
551
  }
486
- storeEventLedger(event, value) {
552
+ storeEventLedger(event, value, observationId = this.rawPushObservationId) {
487
553
  const data = jsonObjectOf(value) ?? {};
488
554
  const internal = this.client;
489
555
  const payload = JSON.parse(stableStringify(jsonSafe(data.payload ?? null)));
490
556
  this.eventLedger.push({
491
- observation_id: this.rawPushObservationId, event, event_index: this.eventLedger.length + 1, time_ms: Date.now(),
557
+ observation_id: observationId, event, event_index: this.eventLedger.length + 1, time_ms: Date.now(),
492
558
  scope: event === 'message.received' ? 'p2p' : 'group', receiver_aid: this.aid,
493
559
  device_id: textOf(internal._deviceId), slot_id: textOf(internal._slotId ?? this.slotId),
494
560
  sender_aid: textOf(data.sender_aid ?? data.from_aid ?? data.from), group_id: textOf(data.group_id),
@@ -527,7 +593,7 @@ export class CrossSdkTsAgent {
527
593
  if (typeof params[key] === 'number' && Number.isSafeInteger(params[key]))
528
594
  window[key] = params[key];
529
595
  }
530
- 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']) {
596
+ for (const key of ['window_mode', 'cursor_scope', 'storage_scope', 'sender_aid', 'group_id', 'group_aid', 'device_id', 'slot_id', 'after_volatile_cursor', 'mode', 'after_sender_aid']) {
531
597
  if (typeof params[key] === 'string')
532
598
  window[key] = params[key];
533
599
  }
@@ -575,6 +641,22 @@ export class CrossSdkTsAgent {
575
641
  span.has_more_volatile = typeof response.volatile_has_more === 'boolean' ? response.volatile_has_more : null;
576
642
  span.persistent_has_more = span.has_more_persistent;
577
643
  span.volatile_has_more = span.has_more_volatile;
644
+ const safeSeq = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : null;
645
+ for (const key of ['head_seq', 'scan_after_seq', 'covered_through_seq', 'next_after_seq', 'retention_floor_seq',
646
+ 'retention_floor_message_seq', 'retention_floor_event_seq']) {
647
+ const value = safeSeq(response[key]);
648
+ if (value !== null)
649
+ span[key] = value;
650
+ }
651
+ const cursor = jsonObjectOf(response.cursor);
652
+ if (span.retention_floor_seq === undefined) {
653
+ const cursorRetentionFloor = safeSeq(cursor?.retention_floor_seq);
654
+ if (cursorRetentionFloor !== null)
655
+ span.retention_floor_seq = cursorRetentionFloor;
656
+ }
657
+ const cursorJoinSeq = safeSeq(cursor?.join_seq);
658
+ if (cursorJoinSeq !== null)
659
+ span.join_seq = cursorJoinSeq;
578
660
  for (const key of ['peer_ack_seq', 'effective_ack_seq', 'server_ack_seq', 'ack_seq', 'up_to_seq', 'acked_seq']) {
579
661
  if (typeof response[key] === 'number' && Number.isSafeInteger(response[key]))
580
662
  span[key] = response[key];
@@ -691,10 +773,10 @@ export class CrossSdkTsAgent {
691
773
  storeRawGroupChangedObservation(value) {
692
774
  this.storeRawPushObservation('_raw.group.changed', 'group', 'changed', value);
693
775
  }
694
- storeGroupChangedPublicEvent(value) {
776
+ storeGroupChangedPublicEvent(value, observationId = this.rawPushObservationId) {
695
777
  const data = jsonObjectOf(value) ?? {};
696
778
  this.groupChangedEvents.push({
697
- observation_id: this.rawPushObservationId,
779
+ observation_id: observationId,
698
780
  time: Date.now(),
699
781
  group_id: textOf(data.group_id),
700
782
  action: textOf(data.action),
@@ -775,9 +857,12 @@ export class CrossSdkTsAgent {
775
857
  }
776
858
  }
777
859
  else {
778
- const registered = await store.register(this.aid, { type: 'service', owner: textOf(process.env.AUN_TEST_OWNER_AID).trim() });
779
- if (!registered.ok)
780
- throw new Error(`${registered.error.code}: ${registered.error.message}`);
860
+ const local = store.load(this.aid);
861
+ if (!local.ok || !local.data.aid.isPrivateKeyValid()) {
862
+ const registered = await store.register(this.aid, { type: 'service', owner: textOf(process.env.AUN_TEST_OWNER_AID).trim() });
863
+ if (!registered.ok)
864
+ throw new Error(`${registered.error.code}: ${registered.error.message}`);
865
+ }
781
866
  }
782
867
  const loaded = store.load(this.aid);
783
868
  if (!loaded.ok) {
@@ -812,7 +897,8 @@ export class CrossSdkTsAgent {
812
897
  return;
813
898
  }
814
899
  try {
815
- const task = role === 'owner' ? await this.client.bindSubaid(peerAid) : await this.client.bindOwneraid(peerAid);
900
+ const options = body.batch_id === undefined ? [] : [{ batchId: body.batch_id }];
901
+ const task = role === 'owner' ? await this.client.bindSubaid(peerAid, ...options) : await this.client.bindOwneraid(peerAid, ...options);
816
902
  sendJson(res, 200, { ok: true, operation_id: task.operation_id, task: jsonSafe(task) });
817
903
  }
818
904
  catch (err) {
@@ -826,15 +912,20 @@ export class CrossSdkTsAgent {
826
912
  async ownershipAction(req, res, action) {
827
913
  const body = await readJson(req);
828
914
  const operationId = textOf(body.operation_id).trim(), subAid = textOf(body.sub_aid).trim(), newOwnerAid = textOf(body.new_owner_aid).trim();
829
- if ((action === 'accept' || action === 'reject') ? !operationId : !subAid || action === 'transfer' && !newOwnerAid) {
915
+ if ((action === 'accept' || action === 'reject') ? !operationId && !Array.isArray(body.operation_ids) : !subAid || action === 'transfer' && !newOwnerAid) {
830
916
  sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: action === 'accept' || action === 'reject' ? 'operation_id is required' : 'sub_aid and transfer new_owner_aid are required' });
831
917
  return;
832
918
  }
833
919
  try {
920
+ if ((action === 'accept' || action === 'reject') && Array.isArray(body.operation_ids)) {
921
+ const report = action === 'accept' ? await this.client.acceptOwnershipRequests(body.operation_ids) : await this.client.rejectOwnershipRequests(body.operation_ids, body.reason);
922
+ sendJson(res, 200, { ok: true, report: jsonSafe(report) });
923
+ return;
924
+ }
834
925
  const task = action === 'accept' ? await this.client.acceptOwnershipRequest(operationId)
835
926
  : action === 'reject' ? await this.client.rejectOwnershipRequest(operationId, body.reason)
836
927
  : action === 'release' ? await this.client.releaseSubaid(subAid)
837
- : await this.client.transferSubaid(subAid, newOwnerAid);
928
+ : await this.client.transferSubaid(subAid, newOwnerAid, ...(body.batch_id === undefined ? [] : [{ batchId: body.batch_id }]));
838
929
  sendJson(res, 200, { ok: true, operation_id: task.operation_id, task: jsonSafe(task) });
839
930
  }
840
931
  catch (err) {
@@ -928,6 +1019,8 @@ export class CrossSdkTsAgent {
928
1019
  expires_at: Number(data.expires_at ?? 0) || 0,
929
1020
  error_code: errorCode,
930
1021
  raw_sha256: sha256Json(data),
1022
+ message_class: data.message_class ?? jsonObjectOf(data.protected_headers)?.message_class ?? 'normal',
1023
+ delivery_class: data.delivery_class ?? data.message_class ?? 'normal',
931
1024
  ...metadata,
932
1025
  ...(decrypted && jsonObjectOf(data._semantic_evidence) ? { _semantic_evidence: structuredClone(data._semantic_evidence) } : {}),
933
1026
  };
@@ -950,6 +1043,8 @@ export class CrossSdkTsAgent {
950
1043
  need_render: data.need_render !== false,
951
1044
  error_code: errorCode,
952
1045
  raw_sha256: sha256Json(data),
1046
+ message_class: data.message_class ?? jsonObjectOf(data.protected_headers)?.message_class ?? 'normal',
1047
+ delivery_class: data.delivery_class ?? data.message_class ?? 'normal',
953
1048
  ...metadata,
954
1049
  ...(decrypted && jsonObjectOf(data._semantic_evidence) ? { _semantic_evidence: structuredClone(data._semantic_evidence) } : {}),
955
1050
  };
@@ -963,6 +1058,20 @@ export class CrossSdkTsAgent {
963
1058
  async handle(req, res) {
964
1059
  const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
965
1060
  try {
1061
+ if (req.method === 'POST' && url.pathname === '/control/config') {
1062
+ const body = await readJson(req);
1063
+ this.messageClassesV2 = body.enabled !== false;
1064
+ await this.client.disconnect();
1065
+ await this.client.connect({ auto_reconnect: false, background_sync: true });
1066
+ sendJson(res, 200, { ok: true, requested_capabilities: this.controlAdvertisedCapabilities, negotiated_capabilities: this.controlNegotiatedCapabilities });
1067
+ return;
1068
+ }
1069
+ if (req.method === 'GET' && ['/capabilities', '/control/observations', '/control/state'].includes(url.pathname)) {
1070
+ sendJson(res, 200, { ok: true, enabled: this.messageClassesV2, advertised: this.controlAdvertisedCapabilities,
1071
+ requested_capabilities: this.controlAdvertisedCapabilities, negotiated_capabilities: this.controlNegotiatedCapabilities,
1072
+ wire_sends: this.controlWireSends, sdk_messages: this.sdkControlMessages });
1073
+ return;
1074
+ }
966
1075
  if (req.method === 'GET' && url.pathname === '/health') {
967
1076
  const state = textOf(this.client.state ?? this.client._state ?? '');
968
1077
  sendJson(res, this.startupError ? 503 : 200, {
@@ -1151,6 +1260,17 @@ export class CrossSdkTsAgent {
1151
1260
  await this.messageCall(req, res);
1152
1261
  return;
1153
1262
  }
1263
+ if (req.method === 'POST' && url.pathname === '/sync/channels/drop') {
1264
+ const body = await readJson(req);
1265
+ const connectionId = textOf(body.connection_id);
1266
+ const ok = await this.client._historyChannels().dropChannel(connectionId);
1267
+ sendJson(res, ok ? 200 : 404, { ok, connection_id: connectionId });
1268
+ return;
1269
+ }
1270
+ if (req.method === 'GET' && url.pathname === '/sync/channels/state') {
1271
+ sendJson(res, 200, { ...this.client._historyChannels().snapshot(), metrics: { rss_bytes: process.memoryUsage().rss } });
1272
+ return;
1273
+ }
1154
1274
  if (req.method === 'GET' && url.pathname === '/sync/state') {
1155
1275
  this.syncState(url, res);
1156
1276
  return;
@@ -1387,6 +1507,11 @@ export class CrossSdkTsAgent {
1387
1507
  };
1388
1508
  if (body.ttl !== undefined)
1389
1509
  params.ttl = Number(body.ttl);
1510
+ if (jsonObjectOf(body.payload))
1511
+ params.payload = { ...jsonObjectOf(body.payload), trace_id: traceId };
1512
+ for (const key of ['message_class', 'sdk_control_protocol', 'sdk_control_version', 'protected_headers'])
1513
+ if (key in body)
1514
+ params[key] = body[key];
1390
1515
  if (body.need_render !== undefined)
1391
1516
  params.need_render = body.need_render;
1392
1517
  try {
@@ -1531,6 +1656,7 @@ export class CrossSdkTsAgent {
1531
1656
  error_code: err instanceof Error ? err.name : 'Error',
1532
1657
  error_message: err instanceof Error ? err.message : String(err),
1533
1658
  };
1659
+ error.rpc_error_code = err?.code ?? null;
1534
1660
  this.recordTrace(traceId, { stage: 'message_call_error', method, error });
1535
1661
  sendJson(res, 500, error);
1536
1662
  }
@@ -1579,7 +1705,7 @@ export class CrossSdkTsAgent {
1579
1705
  const afterSeq = Number(body.after_seq ?? 0) || 0;
1580
1706
  const limit = Number(body.limit ?? 50) || 50;
1581
1707
  try {
1582
- const params = { limit, window_mode: tail ? 'tail' : 'forward' };
1708
+ const params = { limit, ...(tail ? { window_mode: 'tail' } : {}) };
1583
1709
  if (scope === 'group' || body.after_seq !== undefined)
1584
1710
  params.after_seq = afterSeq;
1585
1711
  if (scope === 'p2p' && senderAid)
@@ -1679,11 +1805,13 @@ export class CrossSdkTsAgent {
1679
1805
  }
1680
1806
  async sessionConnect(req, res) {
1681
1807
  const body = await readJson(req);
1682
- if (['background_sync', 'inline_realtime_payload_v1', 'batch_events'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
1808
+ if (['background_sync', 'inline_realtime_payload_v1', 'batch_events', 'message_classes_v2'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
1683
1809
  sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid session parameters' });
1684
1810
  return;
1685
1811
  }
1686
1812
  const backgroundSync = body.background_sync === true;
1813
+ if (typeof body.message_classes_v2 === 'boolean')
1814
+ this.messageClassesV2 = body.message_classes_v2;
1687
1815
  if (Object.prototype.hasOwnProperty.call(body, 'inline_realtime_payload_v1')) {
1688
1816
  this.inlineRealtimePayloadV1 = body.inline_realtime_payload_v1 !== false;
1689
1817
  }
@@ -1703,11 +1831,13 @@ export class CrossSdkTsAgent {
1703
1831
  }
1704
1832
  async sessionRestart(req, res) {
1705
1833
  const body = await readJson(req);
1706
- if (['background_sync', 'inline_realtime_payload_v1', 'batch_events'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
1834
+ if (['background_sync', 'inline_realtime_payload_v1', 'batch_events', 'message_classes_v2'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
1707
1835
  sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid session parameters' });
1708
1836
  return;
1709
1837
  }
1710
1838
  const backgroundSync = body.background_sync === true;
1839
+ if (typeof body.message_classes_v2 === 'boolean')
1840
+ this.messageClassesV2 = body.message_classes_v2;
1711
1841
  if (Object.prototype.hasOwnProperty.call(body, 'inline_realtime_payload_v1')) {
1712
1842
  this.inlineRealtimePayloadV1 = body.inline_realtime_payload_v1 !== false;
1713
1843
  }
@@ -2441,6 +2571,7 @@ export class CrossSdkTsAgent {
2441
2571
  error_code: err instanceof Error ? err.name : 'Error',
2442
2572
  error_message: err instanceof Error ? err.message : String(err),
2443
2573
  };
2574
+ error.rpc_error_code = err?.code ?? null;
2444
2575
  this.recordTrace(traceId, { stage: 'storage_call_error', action, error });
2445
2576
  sendJson(res, 500, error);
2446
2577
  }
@@ -2832,7 +2963,7 @@ export class CrossSdkTsAgent {
2832
2963
  return await storage.setAcl(path, {
2833
2964
  owner,
2834
2965
  bucket,
2835
- granteeAid: textOf(params.grantee_aid ?? params.granteeAID).trim(),
2966
+ ...storageAclTargets(params),
2836
2967
  perms: textOf(params.perms).trim(),
2837
2968
  expiresAt: params.expires_at == null && params.expiresAt == null ? undefined : Number(params.expires_at ?? params.expiresAt),
2838
2969
  maxUses: params.max_uses == null && params.maxUses == null ? undefined : Number(params.max_uses ?? params.maxUses),
@@ -2841,7 +2972,7 @@ export class CrossSdkTsAgent {
2841
2972
  return await storage.removeAcl(path, {
2842
2973
  owner,
2843
2974
  bucket,
2844
- granteeAid: textOf(params.grantee_aid ?? params.granteeAID).trim(),
2975
+ ...storageAclTargets(params),
2845
2976
  });
2846
2977
  case 'list':
2847
2978
  return await storage.list(path, {
@@ -3127,10 +3258,11 @@ export class CrossSdkTsAgent {
3127
3258
  try {
3128
3259
  const result = await this.client.call('group.send', {
3129
3260
  group_id: groupId,
3130
- payload,
3261
+ payload: jsonObjectOf(body.payload) ? { ...jsonObjectOf(body.payload), trace_id: traceId } : payload,
3131
3262
  encrypt: e2ee,
3132
3263
  message_id: messageId,
3133
3264
  ...(body.need_render !== undefined ? { need_render: body.need_render } : {}),
3265
+ ...Object.fromEntries(['message_class', 'ttl', 'sdk_control_protocol', 'sdk_control_version', 'protected_headers'].filter(key => key in body).map(key => [key, body[key]])),
3134
3266
  });
3135
3267
  const resultObj = result && typeof result === 'object' && !Array.isArray(result) ? result : {};
3136
3268
  const response = {