@agentunion/fastaun 0.5.16 → 0.5.18

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 (64) hide show
  1. package/CHANGELOG.md +336 -260
  2. package/_packed_docs/CHANGELOG.md +336 -260
  3. package/_packed_docs/INDEX.md +36 -5
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +13 -5
  5. package/_packed_docs/agent.md/SCHEMA.md +52 -33
  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 +1170 -1164
  7. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +1215 -1213
  8. package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +95 -6
  9. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +58 -43
  10. package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +19 -4
  11. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +80 -17
  12. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +25 -2
  13. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +69 -10
  14. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +7 -4
  15. package/_packed_docs/sdk/09-group-rpc-manual.md +167 -35
  16. package/_packed_docs/sdk/09-message-rpc-manual.md +72 -42
  17. package/_packed_docs/sdk/09-storage-rpc-manual.md +48 -18
  18. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +16 -12
  19. package/_packed_docs/sdk/INDEX.md +14 -13
  20. package/dist/agent-md-schema.js +2 -0
  21. package/dist/agent-md-schema.js.map +1 -1
  22. package/dist/agent-md.d.ts +3 -1
  23. package/dist/agent-md.js +10 -2
  24. package/dist/agent-md.js.map +1 -1
  25. package/dist/aid-store.d.ts +58 -0
  26. package/dist/aid-store.js +234 -3
  27. package/dist/aid-store.js.map +1 -1
  28. package/dist/aid.d.ts +17 -1
  29. package/dist/aid.js +48 -2
  30. package/dist/aid.js.map +1 -1
  31. package/dist/client/delivery.d.ts +19 -0
  32. package/dist/client/delivery.js +264 -31
  33. package/dist/client/delivery.js.map +1 -1
  34. package/dist/client/group-state.d.ts +4 -0
  35. package/dist/client/group-state.js +63 -0
  36. package/dist/client/group-state.js.map +1 -1
  37. package/dist/client/rpc-pipeline.js +29 -14
  38. package/dist/client/rpc-pipeline.js.map +1 -1
  39. package/dist/client/v2-e2ee.d.ts +17 -2
  40. package/dist/client/v2-e2ee.js +971 -520
  41. package/dist/client/v2-e2ee.js.map +1 -1
  42. package/dist/client.d.ts +3 -0
  43. package/dist/client.js +120 -19
  44. package/dist/client.js.map +1 -1
  45. package/dist/facades.d.ts +7 -0
  46. package/dist/facades.js +11 -0
  47. package/dist/facades.js.map +1 -1
  48. package/dist/group-fs.js +7 -5
  49. package/dist/group-fs.js.map +1 -1
  50. package/dist/index.d.ts +3 -2
  51. package/dist/index.js +1 -0
  52. package/dist/index.js.map +1 -1
  53. package/dist/ownership-proof.d.ts +40 -0
  54. package/dist/ownership-proof.js +140 -0
  55. package/dist/ownership-proof.js.map +1 -0
  56. package/dist/storage/vfs.d.ts +1 -0
  57. package/dist/storage/vfs.js +61 -6
  58. package/dist/storage/vfs.js.map +1 -1
  59. package/dist/tools/cross-sdk-agent.js +31 -1
  60. package/dist/tools/cross-sdk-agent.js.map +1 -1
  61. package/dist/types.d.ts +16 -1
  62. package/dist/version.d.ts +1 -1
  63. package/dist/version.js +1 -1
  64. package/package.json +14 -14
@@ -7,6 +7,7 @@ const PUSHED_SEQS_LIMIT = 50_000;
7
7
  const PENDING_ORDERED_LIMIT = 50_000;
8
8
  const MESSAGE_RECALL_SEEN_LIMIT = 10_000;
9
9
  const GROUP_RECALL_SEEN_LIMIT = 10_000;
10
+ const VOLATILE_SEEN_LIMIT = 10_000;
10
11
  const SEQ_TRACKER_PERSIST_FLUSH_DELAY_MS = 200;
11
12
  const DELIVERY_CHANGED_MESSAGE_EVENTS = new Set([
12
13
  'message.received',
@@ -35,7 +36,7 @@ const APP_SEND_ENVELOPE_METHODS = new Set([
35
36
  const APP_GROUP_EVENT_ENVELOPE_KEYS = [
36
37
  'module_id', 'event_id', 'event_seq', 'seq', 'event_type', 'action', 'group_id',
37
38
  'actor_aid', 'sender_aid', 'member_aid', 'target_aid', 'operator_aid',
38
- 'created_at', 'timestamp', 't_server', 'status', 'device_id', 'slot_id',
39
+ 'created_at', 'timestamp', 't_server', 'status', 'device_id',
39
40
  ];
40
41
  function isPromiseLike(value) {
41
42
  return Boolean(value && typeof value.then === 'function');
@@ -87,6 +88,10 @@ export class MessageDeliveryEngine {
87
88
  deliveryChangedGeneration = 0;
88
89
  syncRun = 0;
89
90
  syncSessions = null;
91
+ volatileContext = '';
92
+ volatileCursor = '';
93
+ volatileSeenMessages = new Map();
94
+ volatileInFlightMessages = new Set();
90
95
  // 同一实例的 tracker 写入必须串行,避免旧的延迟 flush 覆盖新的 Forward 提交。
91
96
  seqTrackerWriteChain = Promise.resolve();
92
97
  constructor(runtime) {
@@ -157,9 +162,13 @@ export class MessageDeliveryEngine {
157
162
  throw new Error(`${method} response must be an object`);
158
163
  }
159
164
  const client = this.runtime.client;
165
+ const response = result;
166
+ const volatileMessages = method === 'message.pull' ? this.volatileMessages(response.messages) : [];
167
+ if (volatileMessages.length > 0) {
168
+ await this.publishPlainVolatileMessages(volatileMessages, 'pull', true);
169
+ }
160
170
  this.ensurePullOperationCurrent(ns);
161
171
  this.syncStarted(ns);
162
- const response = result;
163
172
  const messages = deduplicatePlainForwardPageMessages(response.messages, afterSeq);
164
173
  const coordinator = this.forwardCoordinator();
165
174
  const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
@@ -279,8 +288,121 @@ export class MessageDeliveryEngine {
279
288
  hasMore: typeof response.has_more === 'boolean' ? response.has_more : undefined,
280
289
  currentSeq: client._seqTracker.getContiguousSeq(ns),
281
290
  });
291
+ response.messages = (Array.isArray(response.messages) ? response.messages : [])
292
+ .map((message) => this.stripInternalSenderDeviceFields(message));
282
293
  return { rawCount: messages.length, publishedCount };
283
294
  }
295
+ isVolatileMessage(message) {
296
+ return isJsonObject(message)
297
+ && String(message.storage ?? '') === 'volatile';
298
+ }
299
+ volatileMessages(value) {
300
+ if (!Array.isArray(value))
301
+ return [];
302
+ return value.filter((item) => this.isVolatileMessage(item)).map((item) => ({ ...item }));
303
+ }
304
+ ensureVolatileContext() {
305
+ const client = this.runtime.client;
306
+ const context = JSON.stringify([client._aid ?? '', client._deviceId ?? '', client._slotId ?? '']);
307
+ if (context === this.volatileContext)
308
+ return;
309
+ this.volatileContext = context;
310
+ this.volatileCursor = '';
311
+ this.volatileSeenMessages.clear();
312
+ this.volatileInFlightMessages.clear();
313
+ }
314
+ volatilePullCursor() {
315
+ this.ensureVolatileContext();
316
+ return this.volatileCursor;
317
+ }
318
+ resetVolatilePullCursor() {
319
+ this.ensureVolatileContext();
320
+ this.volatileCursor = '';
321
+ }
322
+ volatileSeenKey(message) {
323
+ const messageId = String(message.message_id ?? '').trim();
324
+ const sender = String(message.from_aid ?? message.from ?? '').trim();
325
+ const cursor = String(message.volatile_cursor ?? '').trim();
326
+ return `${sender}|${messageId || cursor}`;
327
+ }
328
+ isVolatileSeen(message) {
329
+ this.ensureVolatileContext();
330
+ const now = Date.now();
331
+ for (const [key, expiresAt] of this.volatileSeenMessages) {
332
+ if (expiresAt <= now)
333
+ this.volatileSeenMessages.delete(key);
334
+ }
335
+ return this.volatileSeenMessages.has(this.volatileSeenKey(message));
336
+ }
337
+ markVolatileSeen(message) {
338
+ this.ensureVolatileContext();
339
+ const now = Date.now();
340
+ const expiresAt = Math.max(now + 60_000, Number(message.expires_at ?? now + 1_800_000) || now + 1_800_000);
341
+ this.volatileSeenMessages.set(this.volatileSeenKey(message), expiresAt);
342
+ while (this.volatileSeenMessages.size > VOLATILE_SEEN_LIMIT) {
343
+ const first = this.volatileSeenMessages.keys().next().value;
344
+ if (first === undefined)
345
+ break;
346
+ this.volatileSeenMessages.delete(first);
347
+ }
348
+ }
349
+ beginVolatileDelivery(message) {
350
+ if (this.isVolatileSeen(message))
351
+ return 'seen';
352
+ const key = this.volatileSeenKey(message);
353
+ if (this.volatileInFlightMessages.has(key))
354
+ return 'inflight';
355
+ this.volatileInFlightMessages.add(key);
356
+ return 'claimed';
357
+ }
358
+ finishVolatileDelivery(message) {
359
+ this.ensureVolatileContext();
360
+ this.volatileInFlightMessages.delete(this.volatileSeenKey(message));
361
+ }
362
+ publicVolatileMessage(message) {
363
+ const result = this.stripInternalSenderDeviceFields(message);
364
+ delete result.seq;
365
+ return result;
366
+ }
367
+ async ackVolatileMessages(messages, v2, advanceCursor) {
368
+ const cursors = [...new Set(messages
369
+ .map((message) => String(message.volatile_cursor ?? '').trim())
370
+ .filter(Boolean))];
371
+ if (cursors.length === 0)
372
+ return undefined;
373
+ const result = await this.runtime.client._callRawV2Rpc(v2 ? 'message.v2.ack' : 'message.ack', { volatile_cursors: cursors, _rpc_background: true });
374
+ if (advanceCursor) {
375
+ this.ensureVolatileContext();
376
+ this.volatileCursor = cursors[cursors.length - 1] ?? '';
377
+ }
378
+ return result;
379
+ }
380
+ async publishPlainVolatileMessages(messages, source, advanceCursor) {
381
+ const processed = [];
382
+ for (const rawMessage of messages) {
383
+ const message = this.publicVolatileMessage(rawMessage);
384
+ const claim = this.beginVolatileDelivery(message);
385
+ if (claim === 'seen') {
386
+ processed.push(rawMessage);
387
+ continue;
388
+ }
389
+ if (claim === 'inflight')
390
+ continue;
391
+ try {
392
+ const appEvent = this.p2pAppEventForMessage(message);
393
+ await this.runtime.client._publishAppEvent(appEvent.event, appEvent.payload, source);
394
+ this.markVolatileSeen(message);
395
+ }
396
+ finally {
397
+ this.finishVolatileDelivery(message);
398
+ }
399
+ processed.push(rawMessage);
400
+ }
401
+ if (processed.length > 0) {
402
+ await this.ackVolatileMessages(processed, false, advanceCursor);
403
+ }
404
+ return processed.map((message) => this.publicVolatileMessage(message));
405
+ }
284
406
  resetInlineAckState() {
285
407
  this.syncStopped('aborted');
286
408
  this.deliveryChangedGeneration += 1;
@@ -714,9 +836,6 @@ export class MessageDeliveryEngine {
714
836
  if (!('device_id' in result)) {
715
837
  result.device_id = client._deviceId;
716
838
  }
717
- if (!('slot_id' in result)) {
718
- result.slot_id = client._slotId;
719
- }
720
839
  return result;
721
840
  }
722
841
  normalizePublishedMessagePayload(event, payload, source = 'direct') {
@@ -728,7 +847,7 @@ export class MessageDeliveryEngine {
728
847
  normalized = this.attachAppMessageEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
729
848
  }
730
849
  else if (this.isGroupScopedEvent(event)) {
731
- normalized = this.attachAppGroupEventEnvelope(this.attachCurrentInstanceContext(payload));
850
+ normalized = this.attachAppGroupEventEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
732
851
  }
733
852
  else {
734
853
  normalized = payload;
@@ -901,7 +1020,7 @@ export class MessageDeliveryEngine {
901
1020
  const authoritativeSource = hasGroupMentionModeField(serverSource) ? serverSource : out;
902
1021
  out = normalizeGroupMentionMode(out, authoritativeSource);
903
1022
  }
904
- return out;
1023
+ return this.stripInternalSenderDeviceFields(out);
905
1024
  }
906
1025
  attachAppGroupEventEnvelope(payload) {
907
1026
  if (!isJsonObject(payload))
@@ -915,10 +1034,13 @@ export class MessageDeliveryEngine {
915
1034
  if (!isJsonObject(payload))
916
1035
  return payload;
917
1036
  const result = { ...payload };
918
- delete result.sender_device_id;
919
1037
  delete result._sender_device_id;
920
1038
  delete result.from_device_id;
921
1039
  delete result.from_device;
1040
+ delete result.slot_id;
1041
+ delete result.sender_slot_id;
1042
+ delete result.target_slot_id;
1043
+ delete result.owner_slot_id;
922
1044
  return result;
923
1045
  }
924
1046
  recallEventFromMessage(message) {
@@ -973,9 +1095,7 @@ export class MessageDeliveryEngine {
973
1095
  }
974
1096
  if ('device_id' in msg && !('device_id' in event))
975
1097
  event.device_id = msg.device_id;
976
- if ('slot_id' in msg && !('slot_id' in event))
977
- event.slot_id = msg.slot_id;
978
- return event;
1098
+ return this.stripInternalSenderDeviceFields(event);
979
1099
  }
980
1100
  p2pAppEventForMessage(message) {
981
1101
  const recall = this.recallEventFromMessage(message);
@@ -1082,7 +1202,7 @@ export class MessageDeliveryEngine {
1082
1202
  if (!('tombstone_message_id' in event))
1083
1203
  event.tombstone_message_id = msg.message_id;
1084
1204
  }
1085
- return event;
1205
+ return this.stripInternalSenderDeviceFields(event);
1086
1206
  }
1087
1207
  groupRecallDedupKey(groupId, payload) {
1088
1208
  // 群撤回去重键:group_id + 原始消息标识。
@@ -1340,6 +1460,24 @@ export class MessageDeliveryEngine {
1340
1460
  return false;
1341
1461
  return senderDevice.trim() === String(client._deviceId).trim();
1342
1462
  }
1463
+ isSelfSentP2PMessage(message) {
1464
+ if (!isJsonObject(message))
1465
+ return false;
1466
+ const client = this.runtime.client;
1467
+ if (String(message.direction ?? '').trim() !== 'outbound_sync')
1468
+ return false;
1469
+ const selfAid = String(client._aid ?? '').trim().toLowerCase();
1470
+ const senderAid = String(message.sender_aid ?? message.from_aid ?? message.from ?? '').trim().toLowerCase();
1471
+ if (!selfAid || senderAid !== selfAid)
1472
+ return false;
1473
+ const senderDevice = Object.prototype.hasOwnProperty.call(message, 'sender_device_id')
1474
+ ? message.sender_device_id
1475
+ : message.from_device_id;
1476
+ return typeof senderDevice === 'string'
1477
+ && Boolean(senderDevice.trim())
1478
+ && Boolean(String(client._deviceId ?? '').trim())
1479
+ && senderDevice.trim() === String(client._deviceId).trim();
1480
+ }
1343
1481
  async runPushProcessSerialized(ns, operation, inlineGeneration) {
1344
1482
  const client = this.runtime.client;
1345
1483
  if (inlineGeneration === undefined) {
@@ -1460,12 +1598,16 @@ export class MessageDeliveryEngine {
1460
1598
  client._clientLog.debug(`P2P push filtered by instance: message_id=${String(msg.message_id ?? '')}, seq=${String(msg.seq ?? '')}, target_device=${String(msg.device_id ?? '')}, target_slot=${String(msg.slot_id ?? '')}, local_device=${client._deviceId}, local_slot=${client._slotId}`);
1461
1599
  return;
1462
1600
  }
1601
+ if (this.isVolatileMessage(msg)) {
1602
+ await this.publishPlainVolatileMessages([msg], 'push', false);
1603
+ return;
1604
+ }
1463
1605
  // 新 SDK 的所有带合法 seq P2P Push 都只作为 Head 通知处理。
1464
1606
  // 新服务端为兼容旧 SDK 仍可能发送 message.received;V2 会话下必须
1465
1607
  // 在旧 seq tracking / ordered publish 之前转入前台 Tail 协调器。
1466
1608
  const inlinePresent = hasNonEmptyInlineMessage(msg.inline_message);
1467
1609
  const legacyPushSeq = this.realtimeHeadSeq(msg.seq, inlinePresent);
1468
- if (client._v2Session && client._aid && (msg.seq !== undefined && msg.seq !== null || inlinePresent)) {
1610
+ if ((client._v2Session || inlinePresent) && client._aid && (msg.seq !== undefined && msg.seq !== null || inlinePresent)) {
1469
1611
  if (legacyPushSeq === null) {
1470
1612
  if (inlinePresent)
1471
1613
  await this.recoverInvalidP2PRealtimeHead(`p2p:${client._aid}`, msg.seq);
@@ -1482,10 +1624,18 @@ export class MessageDeliveryEngine {
1482
1624
  const ns = `p2p:${client._aid}`;
1483
1625
  if (seq > 0)
1484
1626
  client._seqTracker.updateMaxSeen(ns, seq);
1627
+ if (encryptedPush) {
1628
+ if (Number(seq) > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
1629
+ client._clientLog.debug(`P2P push triggering Forward: ns=${ns}, seq=${seq}, contiguous=${client._seqTracker.getContiguousSeq(ns)}`);
1630
+ this.fillP2pGap().catch((exc) => client._clientLog.warn(`background gap fill trigger failed: ${formatDeliveryError(exc)}`));
1631
+ }
1632
+ return;
1633
+ }
1485
1634
  if (Number(seq) > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
1486
1635
  client._clientLog.debug(`P2P push triggering Forward: ns=${ns}, seq=${seq}, contiguous=${client._seqTracker.getContiguousSeq(ns)}`);
1487
1636
  this.fillP2pGap().catch((exc) => client._clientLog.warn(`background gap fill trigger failed: ${formatDeliveryError(exc)}`));
1488
1637
  }
1638
+ await this.publishOrderedMessage('message.received', ns, seq, msg, 'push');
1489
1639
  return;
1490
1640
  }
1491
1641
  else {
@@ -1493,6 +1643,8 @@ export class MessageDeliveryEngine {
1493
1643
  await client._publishEncryptedPushMessage('message.received', 'message.undecryptable', '', seq ?? 0, msg, false);
1494
1644
  return;
1495
1645
  }
1646
+ if (this.isSelfSentP2PMessage(msg))
1647
+ return;
1496
1648
  await client._publishAppEvent('message.received', msg, 'push');
1497
1649
  }
1498
1650
  }
@@ -1547,7 +1699,7 @@ export class MessageDeliveryEngine {
1547
1699
  // 不能让 payload 路径直接推进旧 ordered publish 状态。
1548
1700
  const inlinePresent = hasNonEmptyInlineMessage(msg.inline_message);
1549
1701
  const legacyPushSeq = this.realtimeHeadSeq(seq, inlinePresent);
1550
- if (client._v2Session && groupId && (seq !== undefined && seq !== null || inlinePresent)) {
1702
+ if ((client._v2Session || inlinePresent) && groupId && (seq !== undefined && seq !== null || inlinePresent)) {
1551
1703
  if (legacyPushSeq === null) {
1552
1704
  if (inlinePresent)
1553
1705
  await this.recoverInvalidGroupRealtimeHead(groupId, `group:${groupId}`, seq);
@@ -1570,10 +1722,20 @@ export class MessageDeliveryEngine {
1570
1722
  const ns = `group:${groupId}`;
1571
1723
  if (seq > 0)
1572
1724
  client._seqTracker.updateMaxSeen(ns, seq);
1725
+ if (encryptedPush) {
1726
+ if (Number(seq) > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
1727
+ client._clientLog.debug(`group push triggering Forward: group=${groupId}, seq=${seq}, contiguous=${client._seqTracker.getContiguousSeq(ns)}`);
1728
+ this.fillGroupGap(groupId).catch((exc) => client._clientLog.warn(`background gap fill trigger failed: ${formatDeliveryError(exc)}`));
1729
+ }
1730
+ return;
1731
+ }
1573
1732
  if (Number(seq) > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
1574
1733
  client._clientLog.debug(`group push triggering Forward: group=${groupId}, seq=${seq}, contiguous=${client._seqTracker.getContiguousSeq(ns)}`);
1575
1734
  this.fillGroupGap(groupId).catch((exc) => client._clientLog.warn(`background gap fill trigger failed: ${formatDeliveryError(exc)}`));
1576
1735
  }
1736
+ if (!this.isSelfSentGroupMessage(msg)) {
1737
+ await this.publishOrderedMessage('group.message_created', ns, seq, msg, 'group-push');
1738
+ }
1577
1739
  return;
1578
1740
  }
1579
1741
  else {
@@ -2250,7 +2412,7 @@ export class MessageDeliveryEngine {
2250
2412
  after_seq: state.ack,
2251
2413
  limit: pageLimit,
2252
2414
  ...(headForward ? { max_pages: Math.max(2, requiredPages) } : order[0] === 'tail' ? { max_pages: requiredPages } : {}),
2253
- }, !foregroundForward, headForward || tailMissedDuringPull);
2415
+ }, true, headForward || tailMissedDuringPull);
2254
2416
  }
2255
2417
  if (tailHead !== null && this.syncState(ns).ack < this.syncState(ns).tail - 1) {
2256
2418
  this.scheduleRealtimeWindowForward(ns);
@@ -2327,7 +2489,7 @@ export class MessageDeliveryEngine {
2327
2489
  after_seq: state.ack,
2328
2490
  limit: pageLimit,
2329
2491
  ...(headForward ? { max_pages: Math.max(2, requiredPages) } : order[0] === 'tail' ? { max_pages: requiredPages } : {}),
2330
- }, !foregroundForward, headForward || tailMissedDuringPull);
2492
+ }, true, headForward || tailMissedDuringPull);
2331
2493
  }
2332
2494
  if (tailHead !== null && this.syncState(ns).ack < this.syncState(ns).tail - 1) {
2333
2495
  this.scheduleRealtimeWindowForward(ns, groupId);
@@ -2598,10 +2760,10 @@ export class MessageDeliveryEngine {
2598
2760
  const pending = client._pendingOrderedMsgs.get(ns);
2599
2761
  return !pending || pending.size === 0;
2600
2762
  }
2601
- async decodeInlineP2P(row) {
2763
+ async decodeInlineP2P(row, deferStatus) {
2602
2764
  const client = this.runtime.client;
2603
2765
  if (this.inlineRowKind(row) === 'v2') {
2604
- const decrypted = await client._decryptV2PushMessage(row);
2766
+ const decrypted = await client._decryptV2PushMessage(row, deferStatus);
2605
2767
  return isJsonObject(decrypted) ? decrypted : null;
2606
2768
  }
2607
2769
  const legacy = isJsonObject(row.legacy_v1) ? row.legacy_v1 : null;
@@ -2626,16 +2788,20 @@ export class MessageDeliveryEngine {
2626
2788
  timestamp: row.t_server,
2627
2789
  payload: payload,
2628
2790
  encrypted: false,
2629
- direction: String(row.direction ?? '').trim() || (fromAid && fromAid === client._aid ? 'outbound_sync' : 'inbound'),
2630
2791
  };
2792
+ const direction = String(row.direction ?? '').trim();
2793
+ if (direction)
2794
+ result.direction = direction;
2795
+ else if (fromAid && fromAid !== client._aid)
2796
+ result.direction = 'inbound';
2631
2797
  if (row.sender_device_id !== undefined)
2632
2798
  result.sender_device_id = row.sender_device_id;
2633
2799
  return result;
2634
2800
  }
2635
- async decodeInlineGroup(row, groupId, groupAid) {
2801
+ async decodeInlineGroup(row, groupId, groupAid, deferStatus) {
2636
2802
  const client = this.runtime.client;
2637
2803
  if (this.inlineRowKind(row) === 'v2') {
2638
- const decrypted = await client._decryptV2PushMessage(row);
2804
+ const decrypted = await client._decryptV2PushMessage(row, deferStatus);
2639
2805
  if (!isJsonObject(decrypted))
2640
2806
  return null;
2641
2807
  const decoded = decrypted;
@@ -2701,9 +2867,13 @@ export class MessageDeliveryEngine {
2701
2867
  const snapshot = typeof tracker.snapshotNamespace === 'function' ? tracker.snapshotNamespace(ns) : null;
2702
2868
  let delivered = false;
2703
2869
  try {
2704
- const published = client._publishAppEvent(event, payload, 'inline-push');
2705
- if (isPromiseLike(published))
2706
- await published;
2870
+ const selfSync = (event === 'message.received' && this.isSelfSentP2PMessage(payload))
2871
+ || (event === 'group.message_created' && this.isSelfSentGroupMessage(payload));
2872
+ if (!selfSync) {
2873
+ const published = client._publishAppEvent(event, payload, 'inline-push');
2874
+ if (isPromiseLike(published))
2875
+ await published;
2876
+ }
2707
2877
  if (!this.inlineGenerationIsCurrent(generation))
2708
2878
  return false;
2709
2879
  delivered = true;
@@ -2740,6 +2910,34 @@ export class MessageDeliveryEngine {
2740
2910
  return false;
2741
2911
  }
2742
2912
  }
2913
+ async commitInlineTerminalDecryptSeq(ns, seq, generation) {
2914
+ const client = this.runtime.client;
2915
+ const tracker = client._seqTracker;
2916
+ if (!this.inlineGenerationIsCurrent(generation) || !this.inlineStateIsCaughtUp(ns, seq))
2917
+ return false;
2918
+ const snapshot = typeof tracker.snapshotNamespace === 'function' ? tracker.snapshotNamespace(ns) : null;
2919
+ try {
2920
+ const needsPull = Boolean(tracker.onMessageSeq(ns, seq));
2921
+ tracker.commitTailWindow(ns, {
2922
+ windowStartSeq: seq,
2923
+ headSeq: seq,
2924
+ coveredThroughSeq: seq,
2925
+ pageLimit: 1,
2926
+ });
2927
+ const committed = this.syncState(ns);
2928
+ if (needsPull || committed.ack !== seq || committed.tail !== seq || committed.head !== seq) {
2929
+ throw new Error('inline realtime terminal cursor did not collapse after consume');
2930
+ }
2931
+ await client._commitSeqTrackerState(ns);
2932
+ return true;
2933
+ }
2934
+ catch (exc) {
2935
+ if (typeof tracker.restoreNamespaceSnapshot === 'function')
2936
+ tracker.restoreNamespaceSnapshot(ns, snapshot);
2937
+ client._clientLog.debug(`inline realtime terminal consume rolled back: ns=${ns} seq=${seq} err=${formatDeliveryError(exc)}`);
2938
+ return false;
2939
+ }
2940
+ }
2743
2941
  async tryConsumeInlineP2P(head, ns, pushSeq, expectedVersion, inlineGeneration) {
2744
2942
  const client = this.runtime.client;
2745
2943
  if (!isJsonObject(head.inline_message))
@@ -2759,7 +2957,14 @@ export class MessageDeliveryEngine {
2759
2957
  return false;
2760
2958
  let committed = false;
2761
2959
  try {
2762
- const decoded = await this.decodeInlineP2P(row);
2960
+ const decryptStatus = {};
2961
+ const decoded = await this.decodeInlineP2P(row, decryptStatus);
2962
+ if (!decoded && decryptStatus.terminal) {
2963
+ if (!await this.commitInlineTerminalDecryptSeq(ns, pushSeq, generation))
2964
+ return false;
2965
+ committed = this.markPendingP2PInlineAckCommitted(ns, pushSeq);
2966
+ return committed;
2967
+ }
2763
2968
  if (!this.inlineGenerationIsCurrent(generation) || !decoded || !this.inlineStateIsCaughtUp(ns, pushSeq)
2764
2969
  || !this.decodedInlineMatches(head, decoded, pushSeq))
2765
2970
  return false;
@@ -2797,7 +3002,14 @@ export class MessageDeliveryEngine {
2797
3002
  return false;
2798
3003
  let committed = false;
2799
3004
  try {
2800
- const decoded = await this.decodeInlineGroup(row, groupId, groupAid || groupId);
3005
+ const decryptStatus = {};
3006
+ const decoded = await this.decodeInlineGroup(row, groupId, groupAid || groupId, decryptStatus);
3007
+ if (!decoded && decryptStatus.terminal) {
3008
+ if (!await this.commitInlineTerminalDecryptSeq(ns, pushSeq, generation))
3009
+ return false;
3010
+ committed = this.markPendingGroupInlineAckCommitted(ns, pushSeq);
3011
+ return committed;
3012
+ }
2801
3013
  if (!this.inlineGenerationIsCurrent(generation) || !decoded || !this.inlineStateIsCaughtUp(ns, pushSeq)
2802
3014
  || !this.decodedInlineMatches(head, decoded, pushSeq, groupId))
2803
3015
  return false;
@@ -2916,20 +3128,24 @@ export class MessageDeliveryEngine {
2916
3128
  const client = this.runtime.client;
2917
3129
  if (inlineGeneration !== undefined && !this.inlineGenerationIsCurrent(inlineGeneration))
2918
3130
  return;
2919
- if (!client._v2Session)
2920
- return;
2921
3131
  const pushData = isJsonObject(data) ? data : null;
2922
3132
  const rawPushSeq = pushData?.seq;
2923
3133
  const ns = client._aid ? `p2p:${client._aid}` : '';
2924
3134
  if (!pushData || !ns)
2925
3135
  return;
3136
+ const inlinePresent = hasNonEmptyInlineMessage(pushData.inline_message);
3137
+ if (!client._v2Session && !(expectedVersion === 'v1' && inlinePresent))
3138
+ return;
3139
+ if (this.isVolatileMessage(pushData)) {
3140
+ await client._v2E2EE.publishV2VolatileMessages([pushData], 'push', false);
3141
+ return;
3142
+ }
2926
3143
  if (String(pushData.kind ?? '').trim() === 'message.online_unread_hint') {
2927
3144
  if (client._sessionOptions?.background_sync === false)
2928
3145
  return;
2929
3146
  this.enqueueOnlineUnreadP2pHint(pushData);
2930
3147
  return;
2931
3148
  }
2932
- const inlinePresent = hasNonEmptyInlineMessage(pushData.inline_message);
2933
3149
  const pushSeq = this.realtimeHeadSeq(rawPushSeq, inlinePresent);
2934
3150
  if (pushSeq === null) {
2935
3151
  if (inlinePresent) {
@@ -3026,8 +3242,8 @@ export class MessageDeliveryEngine {
3026
3242
  const client = this.runtime.client;
3027
3243
  if (inlineGeneration !== undefined && !this.inlineGenerationIsCurrent(inlineGeneration))
3028
3244
  return;
3029
- if (!isJsonObject(data) || !client._v2Session) {
3030
- client._clientLog.debug(`_onRawGroupV2MessageCreated skipped: is_object=${String(isJsonObject(data))}, has_v2_session=${String(!!client._v2Session)}`);
3245
+ if (!isJsonObject(data)) {
3246
+ client._clientLog.debug('_onRawGroupV2MessageCreated skipped: is_object=false');
3031
3247
  return;
3032
3248
  }
3033
3249
  const d = data;
@@ -3035,6 +3251,8 @@ export class MessageDeliveryEngine {
3035
3251
  const groupId = typeof d.group_id === 'string' ? d.group_id.trim() : '';
3036
3252
  const rawSeq = d.seq;
3037
3253
  const inlinePresent = hasNonEmptyInlineMessage(d.inline_message);
3254
+ if (!client._v2Session && !(expectedVersion === 'v1' && inlinePresent))
3255
+ return;
3038
3256
  const seq = this.realtimeHeadSeq(rawSeq, inlinePresent);
3039
3257
  if (!groupId) {
3040
3258
  client._clientLog.debug(`_onRawGroupV2MessageCreated skipped: group=${groupId || '<empty>'}, seq=${String(d.seq ?? '')}`);
@@ -3611,6 +3829,11 @@ export class MessageDeliveryEngine {
3611
3829
  client._pendingOrderedMsgs.delete(ns);
3612
3830
  return false;
3613
3831
  }
3832
+ if (event === 'message.received' && this.isSelfSentP2PMessage(payload)) {
3833
+ this.markPublishedSeq(ns, seqNum);
3834
+ client._clientLog.debug(`publish ordered skipped current-device self-sync: ns=${ns}, seq=${seqNum}`);
3835
+ return false;
3836
+ }
3614
3837
  const contig = client._seqTracker.getContiguousSeq(ns);
3615
3838
  if (seqNum > contig && seqNum !== contig + 1) {
3616
3839
  client._clientLog.debug(`publish ordered enqueue(gap): event=${event}, ns=${ns}, seq=${seqNum}, contiguous=${contig}`);
@@ -3624,6 +3847,11 @@ export class MessageDeliveryEngine {
3624
3847
  client._clientLog.debug(`publish ordered skipped after-drain duplicate: event=${event}, ns=${ns}, seq=${seqNum}`);
3625
3848
  return false;
3626
3849
  }
3850
+ if (event === 'message.received' && this.isSelfSentP2PMessage(payload)) {
3851
+ this.markPublishedSeq(ns, seqNum);
3852
+ client._clientLog.debug(`publish pulled skipped current-device self-sync: ns=${ns}, seq=${seqNum}`);
3853
+ return false;
3854
+ }
3627
3855
  const queue = client._pendingOrderedMsgs.get(ns);
3628
3856
  queue?.delete(seqNum);
3629
3857
  if (queue && queue.size === 0)
@@ -3671,6 +3899,11 @@ export class MessageDeliveryEngine {
3671
3899
  client._pendingOrderedMsgs.delete(ns);
3672
3900
  return false;
3673
3901
  }
3902
+ if (event === 'message.received' && this.isSelfSentP2PMessage(payload)) {
3903
+ this.markPublishedSeq(ns, seqNum);
3904
+ client._clientLog.debug('publish pulled skipped current-device self-sync');
3905
+ return source === 'inline_push';
3906
+ }
3674
3907
  await this.drainOrderedMessages(ns, seqNum, true, persist, source);
3675
3908
  if (pullScoped)
3676
3909
  this.ensurePullOperationCurrent(ns);