@agentunion/fastaun 0.5.15 → 0.5.17

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 (48) hide show
  1. package/CHANGELOG.md +311 -271
  2. package/_packed_docs/CHANGELOG.md +311 -271
  3. package/_packed_docs/INDEX.md +8 -2
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +2 -0
  5. package/_packed_docs/agent.md/SCHEMA.md +7 -0
  6. package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +1178 -1178
  7. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +1213 -1213
  8. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +58 -43
  9. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +80 -17
  10. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +27 -6
  11. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +7 -4
  12. package/_packed_docs/sdk/09-group-rpc-manual.md +167 -35
  13. package/_packed_docs/sdk/09-message-rpc-manual.md +72 -42
  14. package/_packed_docs/sdk/09-storage-rpc-manual.md +23 -14
  15. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +16 -12
  16. package/_packed_docs/sdk/INDEX.md +12 -12
  17. package/dist/agent-md-schema.js +6 -0
  18. package/dist/agent-md-schema.js.map +1 -1
  19. package/dist/agent-md.d.ts +3 -0
  20. package/dist/agent-md.js +9 -1
  21. package/dist/agent-md.js.map +1 -1
  22. package/dist/client/delivery.d.ts +17 -0
  23. package/dist/client/delivery.js +140 -12
  24. package/dist/client/delivery.js.map +1 -1
  25. package/dist/client/group-state.d.ts +4 -0
  26. package/dist/client/group-state.js +63 -0
  27. package/dist/client/group-state.js.map +1 -1
  28. package/dist/client/rpc-pipeline.js +22 -5
  29. package/dist/client/rpc-pipeline.js.map +1 -1
  30. package/dist/client/v2-e2ee.d.ts +10 -1
  31. package/dist/client/v2-e2ee.js +187 -36
  32. package/dist/client/v2-e2ee.js.map +1 -1
  33. package/dist/client.js +56 -13
  34. package/dist/client.js.map +1 -1
  35. package/dist/facades.d.ts +5 -0
  36. package/dist/facades.js +5 -0
  37. package/dist/facades.js.map +1 -1
  38. package/dist/group-fs.js +7 -5
  39. package/dist/group-fs.js.map +1 -1
  40. package/dist/storage/vfs.d.ts +1 -0
  41. package/dist/storage/vfs.js +61 -6
  42. package/dist/storage/vfs.js.map +1 -1
  43. package/dist/tools/cross-sdk-agent.js +31 -1
  44. package/dist/tools/cross-sdk-agent.js.map +1 -1
  45. package/dist/types.d.ts +16 -1
  46. package/dist/version.d.ts +1 -1
  47. package/dist/version.js +1 -1
  48. package/package.json +8 -8
@@ -773,6 +773,12 @@ export class V2E2EECoordinator {
773
773
  }
774
774
  continue;
775
775
  }
776
+ if (!entry.groupId && String(entry.msg.storage ?? '') === 'volatile') {
777
+ await this.publishPendingV2VolatileMessage(entry.msg, plaintext, 'pending_retry');
778
+ pending.delete(key);
779
+ client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
780
+ continue;
781
+ }
776
782
  pending.delete(key);
777
783
  const seq = Number(entry.msg.seq ?? 0);
778
784
  if (entry.groupId) {
@@ -795,6 +801,30 @@ export class V2E2EECoordinator {
795
801
  }
796
802
  }
797
803
  }
804
+ async publishPendingV2VolatileMessage(rawMessage, plaintext, source) {
805
+ const delivery = this.client._delivery;
806
+ const claim = delivery.beginVolatileDelivery(rawMessage);
807
+ if (claim === 'inflight')
808
+ return;
809
+ if (claim === 'seen') {
810
+ await delivery.ackVolatileMessages([rawMessage], true, false);
811
+ return;
812
+ }
813
+ try {
814
+ const publicMessage = delivery.publicVolatileMessage(plaintext);
815
+ for (const key of ['storage', 'volatile_cursor', 'created_at', 'expires_at', 'persist']) {
816
+ if (Object.prototype.hasOwnProperty.call(rawMessage, key))
817
+ publicMessage[key] = rawMessage[key];
818
+ }
819
+ const appEvent = delivery.p2pAppEventForMessage(publicMessage);
820
+ await this.client._publishAppEvent(appEvent.event, appEvent.payload, source);
821
+ delivery.markVolatileSeen(rawMessage);
822
+ }
823
+ finally {
824
+ delivery.finishVolatileDelivery(rawMessage);
825
+ }
826
+ await delivery.ackVolatileMessages([rawMessage], true, false);
827
+ }
798
828
  async buildV2P2PEnvelope(opts) {
799
829
  const client = this.client;
800
830
  if (!client._v2Session) {
@@ -841,6 +871,10 @@ export class V2E2EECoordinator {
841
871
  if (peerDevices.length === 0) {
842
872
  throw new E2EEError(`V2 bootstrap: no devices found for ${to}`);
843
873
  }
874
+ const targetDeviceId = String(opts.deviceId ?? '').trim();
875
+ if (targetDeviceId && !peerDevices.some((dev) => String(dev.device_id ?? '').trim() === targetDeviceId)) {
876
+ throw new E2EEError(`V2 bootstrap: target device ${targetDeviceId} not found for ${to}`);
877
+ }
844
878
  let targetSet = useCache ? this.getTargetSetCache(to) : undefined;
845
879
  if (targetSet) {
846
880
  targetSet = this.cloneP2PTargetSet(targetSet);
@@ -926,6 +960,20 @@ export class V2E2EECoordinator {
926
960
  if (targetSet.targets.length === 0) {
927
961
  throw new E2EEError(`V2 bootstrap: no usable devices found for ${to}`);
928
962
  }
963
+ if (targetDeviceId) {
964
+ let peerTargetFound = false;
965
+ targetSet.targets = targetSet.targets.filter((target) => {
966
+ const isPeerTarget = target.aid === to
967
+ && (target.deviceId === targetDeviceId || (wrapPolicy.scope === 'aid' && !target.deviceId))
968
+ && target.role !== 'self_sync';
969
+ if (isPeerTarget)
970
+ peerTargetFound = true;
971
+ return isPeerTarget || target.role === 'self_sync';
972
+ });
973
+ if (!peerTargetFound) {
974
+ throw new E2EEError(`V2 bootstrap: target device ${targetDeviceId} not found for ${to}`);
975
+ }
976
+ }
929
977
  const envelope = encryptP2PMessage(session.getSenderIdentity(), targetSet, opts.payload, {
930
978
  messageId: opts.messageId,
931
979
  timestamp: opts.timestamp,
@@ -954,6 +1002,7 @@ export class V2E2EECoordinator {
954
1002
  const client = this.client;
955
1003
  await client._ensureV2SessionReady('message.send', 'V2 session not initialized; encrypted message.send requires V2 (V1 E2EE removed)');
956
1004
  const toAid = String(to ?? '').trim();
1005
+ const targetDeviceId = String(opts?.deviceId ?? '').trim();
957
1006
  if (!toAid)
958
1007
  throw new ValidationError("message.send requires 'to'");
959
1008
  if (!isJsonObject(payload))
@@ -971,6 +1020,7 @@ export class V2E2EECoordinator {
971
1020
  payload,
972
1021
  messageId,
973
1022
  timestamp: opts?.timestamp,
1023
+ deviceId: targetDeviceId || undefined,
974
1024
  protectedHeaders: opts?.protectedHeaders,
975
1025
  context: opts?.context,
976
1026
  useCache,
@@ -979,6 +1029,8 @@ export class V2E2EECoordinator {
979
1029
  to: toAid,
980
1030
  payload: envelope,
981
1031
  message_id: messageId,
1032
+ ...(targetDeviceId ? { device_id: targetDeviceId } : {}),
1033
+ ...(opts?.ttl !== undefined ? { ttl: opts.ttl } : {}),
982
1034
  encrypt: false,
983
1035
  _skip_send_result_envelope: true,
984
1036
  });
@@ -992,6 +1044,7 @@ export class V2E2EECoordinator {
992
1044
  payload,
993
1045
  message_id: messageId,
994
1046
  timestamp: opts?.timestamp,
1047
+ ttl: opts?.ttl,
995
1048
  protected_headers: opts?.protectedHeaders,
996
1049
  context: opts?.context,
997
1050
  }, result, true);
@@ -1007,6 +1060,7 @@ export class V2E2EECoordinator {
1007
1060
  payload,
1008
1061
  message_id: messageId,
1009
1062
  timestamp: opts?.timestamp,
1063
+ ttl: opts?.ttl,
1010
1064
  protected_headers: opts?.protectedHeaders,
1011
1065
  context: opts?.context,
1012
1066
  }, result, true);
@@ -1223,6 +1277,7 @@ export class V2E2EECoordinator {
1223
1277
  }), true);
1224
1278
  }
1225
1279
  const decrypted = [];
1280
+ let volatileCursor = String(opts?.afterVolatileCursor ?? client._delivery.volatilePullCursor?.() ?? '').trim();
1226
1281
  let nextAfterSeq = opts?.force ? afterSeq : (afterSeq || (ns ? client._seqTracker.getContiguousSeq(ns) : 0));
1227
1282
  let pullGateKey = ns
1228
1283
  ? pullGateKeyForClient(client, 'message.v2.pull', {
@@ -1237,17 +1292,31 @@ export class V2E2EECoordinator {
1237
1292
  const maxPages = Math.max(1, Math.min(100, Math.trunc(Number(opts?.maxPages ?? 100) || 100)));
1238
1293
  while (pageCount < maxPages) {
1239
1294
  pageCount += 1;
1295
+ const requestedVolatileCursor = volatileCursor;
1240
1296
  const ackUpToSeq = pendingAckSeq;
1241
1297
  const inlineAckGeneration = ackUpToSeq > 0
1242
1298
  ? client._delivery.captureP2PInlineAckGeneration()
1243
1299
  : null;
1244
1300
  client._clientLog.debug(`message.v2.pull page request: page=${pageCount}, after_seq=${nextAfterSeq}, limit=${limit}, ack_up_to_seq=${ackUpToSeq}, ns=${ns || '<none>'}`);
1245
- const result = await client._callRawV2Rpc('message.v2.pull', {
1246
- after_seq: nextAfterSeq,
1247
- limit,
1248
- ...(opts?.force ? { force: true } : {}),
1249
- ...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
1250
- }, pullGateKey);
1301
+ let result;
1302
+ try {
1303
+ result = await client._callRawV2Rpc('message.v2.pull', {
1304
+ after_seq: nextAfterSeq,
1305
+ limit,
1306
+ ...(volatileCursor ? { after_volatile_cursor: volatileCursor } : {}),
1307
+ ...(opts?.force ? { force: true } : {}),
1308
+ ...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
1309
+ }, pullGateKey);
1310
+ }
1311
+ catch (exc) {
1312
+ if (!volatileCursor || !String(exc instanceof Error ? exc.message : exc).toLowerCase().includes('volatile cursor epoch mismatch')) {
1313
+ throw exc;
1314
+ }
1315
+ client._delivery.resetVolatilePullCursor?.();
1316
+ volatileCursor = '';
1317
+ pageCount -= 1;
1318
+ continue;
1319
+ }
1251
1320
  if (ackUpToSeq > 0) {
1252
1321
  const actualAckSeq = client._delivery.resolveP2PPullAckSeq(result, ackUpToSeq);
1253
1322
  if (actualAckSeq >= ackUpToSeq) {
@@ -1266,10 +1335,16 @@ export class V2E2EECoordinator {
1266
1335
  }
1267
1336
  }
1268
1337
  const pageMessages = (Array.isArray(result?.messages) ? result.messages : []);
1338
+ const volatileMessages = client._delivery.volatileMessages?.(pageMessages) ?? [];
1339
+ if (volatileMessages.length > 0) {
1340
+ decrypted.push(...await this.publishV2VolatileMessages(volatileMessages, 'pull', true));
1341
+ volatileCursor = String(client._delivery.volatilePullCursor?.() ?? '').trim();
1342
+ }
1269
1343
  const messages = deduplicateForwardPageMessages(pageMessages, nextAfterSeq);
1270
1344
  const cursor = isJsonObject(result.cursor) ? result.cursor : null;
1271
1345
  const hasMoreField = Object.prototype.hasOwnProperty.call(result, 'has_more');
1272
1346
  const hasMore = result.has_more === true;
1347
+ const volatileHasMore = result.volatile_has_more === true;
1273
1348
  const serverHead = safeNonNegativeSeq(cursor?.latest_seq) ?? 0;
1274
1349
  client._clientLog.debug(`message.v2.pull page response: page=${pageCount}, raw_count=${messages.length}, stale_count=${Math.max(0, pageMessages.length - messages.length)}, has_more=${String(result.has_more ?? '')}, server_head=${serverHead}, server_ack_seq=${String(result.server_ack_seq ?? '')}`);
1275
1350
  for (const msg of messages) {
@@ -1469,7 +1544,7 @@ export class V2E2EECoordinator {
1469
1544
  const rawCount = messages.length;
1470
1545
  if (blockedSeq > 0)
1471
1546
  break;
1472
- const shouldContinue = shouldContinueForwardPage({
1547
+ const shouldContinuePersistent = shouldContinueForwardPage({
1473
1548
  rawCount,
1474
1549
  nextAfterSeq,
1475
1550
  nextAfter,
@@ -1480,7 +1555,11 @@ export class V2E2EECoordinator {
1480
1555
  observedUpper: Math.max(client._seqTracker.getMaxSeenSeq(ns), Number(client._pendingP2pPullUpper?.get?.(ns) ?? 0)),
1481
1556
  pendingAckSeq,
1482
1557
  });
1483
- if (!shouldContinue)
1558
+ const shouldContinueVolatile = (hasMore || volatileHasMore)
1559
+ && volatileMessages.length > 0
1560
+ && Boolean(volatileCursor)
1561
+ && volatileCursor !== requestedVolatileCursor;
1562
+ if (!shouldContinuePersistent && !shouldContinueVolatile)
1484
1563
  break;
1485
1564
  if (pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
1486
1565
  const nextKey = pullGateKeyForClient(client, 'message.v2.pull', {
@@ -1518,6 +1597,73 @@ export class V2E2EECoordinator {
1518
1597
  throw error;
1519
1598
  }
1520
1599
  }
1600
+ async publishV2VolatileMessages(messages, source, advanceCursor) {
1601
+ const client = this.client;
1602
+ if (!client._v2Session)
1603
+ return [];
1604
+ const processed = [];
1605
+ const plaintextMessages = [];
1606
+ let complete = true;
1607
+ for (const rawMessage of messages) {
1608
+ const claim = client._delivery.beginVolatileDelivery(rawMessage);
1609
+ if (claim === 'seen') {
1610
+ processed.push(rawMessage);
1611
+ continue;
1612
+ }
1613
+ if (claim === 'inflight') {
1614
+ complete = false;
1615
+ continue;
1616
+ }
1617
+ let plaintext;
1618
+ try {
1619
+ if (this.isEncryptedPushMessage(rawMessage)) {
1620
+ plaintext = await client._decryptV2Message(rawMessage, true, true, true, true, undefined, false, source);
1621
+ }
1622
+ else {
1623
+ const legacy = isJsonObject(rawMessage.legacy_v1)
1624
+ ? rawMessage.legacy_v1
1625
+ : {};
1626
+ plaintext = {
1627
+ message_id: String(rawMessage.message_id ?? ''),
1628
+ from: String(rawMessage.from_aid ?? rawMessage.from ?? ''),
1629
+ to: String(legacy.to ?? rawMessage.to ?? client._aid ?? ''),
1630
+ type: String(rawMessage.type ?? ''),
1631
+ timestamp: rawMessage.t_server ?? rawMessage.timestamp,
1632
+ payload: legacy.payload !== undefined ? legacy.payload : rawMessage.payload,
1633
+ encrypted: false,
1634
+ };
1635
+ for (const key of ['context', 'protected_headers', 'headers', 'client_signature']) {
1636
+ if (Object.prototype.hasOwnProperty.call(rawMessage, key))
1637
+ plaintext[key] = rawMessage[key];
1638
+ }
1639
+ if (rawMessage.sender_device_id !== undefined)
1640
+ plaintext.sender_device_id = rawMessage.sender_device_id;
1641
+ attachGatewayProximity(plaintext, rawMessage);
1642
+ }
1643
+ if (!plaintext) {
1644
+ complete = false;
1645
+ continue;
1646
+ }
1647
+ const publicMessage = client._delivery.publicVolatileMessage(plaintext);
1648
+ for (const key of ['storage', 'volatile_cursor', 'created_at', 'expires_at', 'persist']) {
1649
+ if (Object.prototype.hasOwnProperty.call(rawMessage, key))
1650
+ publicMessage[key] = rawMessage[key];
1651
+ }
1652
+ const appEvent = client._delivery.p2pAppEventForMessage(publicMessage);
1653
+ await client._publishAppEvent(appEvent.event, appEvent.payload, source);
1654
+ client._delivery.markVolatileSeen(rawMessage);
1655
+ processed.push(rawMessage);
1656
+ plaintextMessages.push(publicMessage);
1657
+ }
1658
+ finally {
1659
+ client._delivery.finishVolatileDelivery(rawMessage);
1660
+ }
1661
+ }
1662
+ if (processed.length > 0) {
1663
+ await client._delivery.ackVolatileMessages(processed, true, advanceCursor && complete && processed.length === messages.length);
1664
+ }
1665
+ return plaintextMessages;
1666
+ }
1521
1667
  async confirmForwardP2PAck(upToSeq) {
1522
1668
  const ns = this.client._aid ? `p2p:${this.client._aid}` : '';
1523
1669
  try {
@@ -1533,28 +1679,41 @@ export class V2E2EECoordinator {
1533
1679
  throw exc;
1534
1680
  }
1535
1681
  }
1536
- async ackV2(upToSeq) {
1537
- return this.ackV2WithConfirmation(upToSeq, false);
1682
+ async ackV2(upToSeq, opts) {
1683
+ return this.ackV2WithConfirmation(upToSeq, false, opts);
1538
1684
  }
1539
1685
  async ackV2ForForward(upToSeq) {
1540
1686
  return this.ackV2WithConfirmation(upToSeq, true);
1541
1687
  }
1542
- async ackV2WithConfirmation(upToSeq, requireServerWatermark) {
1688
+ async ackV2WithConfirmation(upToSeq, requireServerWatermark, opts) {
1543
1689
  const client = this.client;
1544
1690
  const ns = client._aid ? `p2p:${client._aid}` : '';
1545
- let seq = Number(upToSeq ?? (ns ? client._seqTracker.getContiguousSeq(ns) : 0));
1546
- if (!Number.isFinite(seq) || seq <= 0) {
1691
+ const volatileCursors = Array.isArray(opts?.volatileCursors) ? opts?.volatileCursors : [];
1692
+ const volatileIds = Array.isArray(opts?.volatileIds) ? opts?.volatileIds : [];
1693
+ let seq = Number(upToSeq ?? ((volatileCursors.length > 0 || volatileIds.length > 0)
1694
+ ? 0
1695
+ : (ns ? client._seqTracker.getContiguousSeq(ns) : 0)));
1696
+ if ((!Number.isFinite(seq) || seq <= 0) && volatileCursors.length === 0 && volatileIds.length === 0) {
1547
1697
  client._clientLog.debug(`message.v2.ack skipped: ns=${ns || '<none>'}, up_to_seq=${String(upToSeq ?? '')}`);
1548
1698
  return { acked: 0 };
1549
1699
  }
1550
- seq = client._clampAckSeq('message.v2.ack', 'up_to_seq', ns, seq);
1551
- if (seq <= 0)
1700
+ if (Number.isFinite(seq) && seq > 0) {
1701
+ seq = client._clampAckSeq('message.v2.ack', 'up_to_seq', ns, seq);
1702
+ }
1703
+ else {
1704
+ seq = 0;
1705
+ }
1706
+ if (seq <= 0 && volatileCursors.length === 0 && volatileIds.length === 0)
1552
1707
  return { acked: 0 };
1553
1708
  const inlineAckGeneration = client._delivery.captureP2PInlineAckGeneration();
1554
1709
  const v2SessionAtStart = client._v2Session;
1555
1710
  const v2RuntimeGenerationAtStart = Number(client._v2RuntimeGeneration ?? 0);
1556
1711
  client._clientLog.debug(`message.v2.ack send: ns=${ns || '<none>'}, up_to_seq=${seq}`);
1557
- const raw = await client._callRawV2Rpc('message.v2.ack', { up_to_seq: seq });
1712
+ const raw = await client._callRawV2Rpc('message.v2.ack', {
1713
+ ...(seq > 0 ? { up_to_seq: seq } : {}),
1714
+ ...(volatileCursors.length > 0 ? { volatile_cursors: volatileCursors } : {}),
1715
+ ...(volatileIds.length > 0 ? { volatile_ids: volatileIds } : {}),
1716
+ });
1558
1717
  const result = isJsonObject(raw)
1559
1718
  ? { ...raw }
1560
1719
  : { result: raw };
@@ -1566,7 +1725,7 @@ export class V2E2EECoordinator {
1566
1725
  }
1567
1726
  result.ack_seq = actualAckSeq;
1568
1727
  result.success = true;
1569
- if (inlineAckGeneration !== null) {
1728
+ if (seq > 0 && inlineAckGeneration !== null) {
1570
1729
  client._delivery.onP2PV2AckSucceeded(actualAckSeq, inlineAckGeneration);
1571
1730
  }
1572
1731
  if (v2SessionAtStart
@@ -1664,7 +1823,6 @@ export class V2E2EECoordinator {
1664
1823
  let epoch = 0;
1665
1824
  let stateCommitment = { state_version: 0, state_hash: '', state_chain: '' };
1666
1825
  let wrapPolicy = normalizeV2WrapPolicy(undefined);
1667
- let usedCachedBootstrap = false;
1668
1826
  const cached = useCache ? this.getBootstrapCacheEntry(cacheKey) : undefined;
1669
1827
  if (cached && Date.now() - Number(cached.cachedAt ?? 0) < V2_BOOTSTRAP_TTL_MS) {
1670
1828
  allDevices = (cached.devices ?? []);
@@ -1672,7 +1830,6 @@ export class V2E2EECoordinator {
1672
1830
  epoch = Number(cached.epoch ?? 0) || 0;
1673
1831
  stateCommitment = cached.stateCommitment ?? stateCommitment;
1674
1832
  wrapPolicy = cached.wrapPolicy ?? wrapPolicy;
1675
- usedCachedBootstrap = true;
1676
1833
  client._clientLog.debug(`group.v2.bootstrap cache hit: group=${groupId}, devices=${allDevices.length}, audit=${auditRecipientsRaw.length}, epoch=${epoch}, state_version=${stateCommitment.state_version}`);
1677
1834
  }
1678
1835
  else {
@@ -1736,13 +1893,10 @@ export class V2E2EECoordinator {
1736
1893
  targets.push(target);
1737
1894
  }
1738
1895
  if (targets.length === 0) {
1739
- if (usedCachedBootstrap) {
1740
- throw new E2EEError(`V2 group: no target devices for group ${groupId}; bootstrap may be stale`, {
1741
- code: V2_GROUP_STALE_BOOTSTRAP_CODE,
1742
- data: { expected_peer_aids: [] },
1743
- });
1744
- }
1745
- throw new E2EEError(`V2 group: no target devices for group ${groupId}`);
1896
+ throw new E2EEError(`V2 group: no target devices for group ${groupId}; bootstrap may be stale`, {
1897
+ code: V2_GROUP_STALE_BOOTSTRAP_CODE,
1898
+ data: { expected_peer_aids: [] },
1899
+ });
1746
1900
  }
1747
1901
  for (const dev of auditRecipientsRaw) {
1748
1902
  const target = await client._v2BuildTargetFromDevice({
@@ -1761,13 +1915,10 @@ export class V2E2EECoordinator {
1761
1915
  targetSet = this.cloneGroupTargetSet(targetSet);
1762
1916
  }
1763
1917
  if (targetSet.targets.length === 0) {
1764
- if (usedCachedBootstrap) {
1765
- throw new E2EEError(`V2 group: no target devices for group ${groupId}; bootstrap may be stale`, {
1766
- code: V2_GROUP_STALE_BOOTSTRAP_CODE,
1767
- data: { expected_peer_aids: [] },
1768
- });
1769
- }
1770
- throw new E2EEError(`V2 group: no target devices for group ${groupId}`);
1918
+ throw new E2EEError(`V2 group: no target devices for group ${groupId}; bootstrap may be stale`, {
1919
+ code: V2_GROUP_STALE_BOOTSTRAP_CODE,
1920
+ data: { expected_peer_aids: [] },
1921
+ });
1771
1922
  }
1772
1923
  const envelope = encryptGroupMessage(session.getSenderIdentity(), groupId, epoch, targetSet.targets, opts.payload, {
1773
1924
  messageId: opts.messageId,
@@ -2751,7 +2902,6 @@ export class V2E2EECoordinator {
2751
2902
  seq: msg.seq,
2752
2903
  timestamp: (msg.timestamp ?? msg.t_server),
2753
2904
  device_id: msg.device_id,
2754
- slot_id: msg.slot_id,
2755
2905
  _decrypt_error: 'encrypted push payload is not decryptable on raw push path',
2756
2906
  _decrypt_stage: 'push_envelope',
2757
2907
  };
@@ -2795,13 +2945,14 @@ export class V2E2EECoordinator {
2795
2945
  encrypted: true,
2796
2946
  e2ee,
2797
2947
  };
2948
+ const senderDeviceId = String(aad.from_device ?? '').trim();
2949
+ if (senderDeviceId)
2950
+ result.sender_device_id = senderDeviceId;
2798
2951
  result.direction = fromAid && fromAid === client._aid ? 'outbound_sync' : 'inbound';
2799
2952
  if (msg.t_server !== undefined)
2800
2953
  result.t_server = msg.t_server;
2801
2954
  if (msg.device_id !== undefined)
2802
2955
  result.device_id = msg.device_id;
2803
- if (msg.slot_id !== undefined)
2804
- result.slot_id = msg.slot_id;
2805
2956
  attachGatewayProximity(result, msg);
2806
2957
  if (group) {
2807
2958
  result.group_id = (msg.group_id ?? aad.group_id ?? envelope.group_id);