@agentunion/fastaun 0.4.10 → 0.4.12

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.
@@ -4,6 +4,17 @@ import { isJsonObject } from '../types.js';
4
4
  const PUSHED_SEQS_LIMIT = 50_000;
5
5
  const PENDING_ORDERED_LIMIT = 50_000;
6
6
  const GROUP_RECALL_SEEN_LIMIT = 10_000;
7
+ const APP_MESSAGE_ENVELOPE_KEYS = [
8
+ 'module_id', 'message_id', 'id', 'seq', 'msg_seq', 'message_type', 'type', 'kind', 'version',
9
+ 'from', 'from_aid', 'sender_aid', 'to', 'to_aid', 'group_id',
10
+ 'timestamp', 'created_at', 't_server', 'encrypted', 'status', 'delivery_mode',
11
+ 'dispatch_mode', 'dispatch', 'device_id', 'slot_id',
12
+ ];
13
+ const APP_GROUP_EVENT_ENVELOPE_KEYS = [
14
+ 'module_id', 'event_id', 'event_seq', 'seq', 'event_type', 'action', 'group_id',
15
+ 'actor_aid', 'sender_aid', 'member_aid', 'target_aid', 'operator_aid',
16
+ 'created_at', 'timestamp', 't_server', 'status', 'device_id', 'slot_id',
17
+ ];
7
18
  function isPromiseLike(value) {
8
19
  return Boolean(value && typeof value.then === 'function');
9
20
  }
@@ -52,6 +63,41 @@ export class MessageDeliveryEngine {
52
63
  queue.delete(oldSeq);
53
64
  }
54
65
  }
66
+ isGroupEventNamespace(ns) {
67
+ return ns.startsWith('group_event:');
68
+ }
69
+ async publishOrderedQueueItem(ns, event, seq, payload, source, pullResponse = false) {
70
+ const client = this.runtime.client;
71
+ if (event === 'group.changed' && this.isGroupEventNamespace(ns)) {
72
+ await this.publishOrderedGroupChanged(payload, source);
73
+ return;
74
+ }
75
+ if (pullResponse) {
76
+ const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source));
77
+ if (isPromiseLike(published))
78
+ await published;
79
+ }
80
+ else {
81
+ const published = client._publishAppEvent(event, payload, source);
82
+ if (isPromiseLike(published))
83
+ await published;
84
+ }
85
+ }
86
+ async publishOrderedGroupChanged(payload, source = 'ordered') {
87
+ const client = this.runtime.client;
88
+ if (isJsonObject(payload)) {
89
+ const eventPayload = payload;
90
+ client._groupState?.handleGroupChangedV2Membership?.(eventPayload);
91
+ if (eventPayload.action === 'dissolved') {
92
+ const groupId = normalizeGroupId(String(eventPayload.group_id ?? '')) || String(eventPayload.group_id ?? '').trim();
93
+ if (groupId)
94
+ client._cleanupDissolvedGroup?.(groupId);
95
+ }
96
+ }
97
+ const published = client._publishAppEvent('group.changed', payload, source);
98
+ if (isPromiseLike(published))
99
+ await published;
100
+ }
55
101
  isInstanceScopedMessageEvent(event) {
56
102
  return event === 'message.received'
57
103
  || event === 'message.recalled'
@@ -74,9 +120,54 @@ export class MessageDeliveryEngine {
74
120
  return result;
75
121
  }
76
122
  normalizePublishedMessagePayload(event, payload) {
77
- if (!this.isInstanceScopedMessageEvent(event))
123
+ if (this.isInstanceScopedMessageEvent(event)) {
124
+ return this.attachAppMessageEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
125
+ }
126
+ if (this.isGroupScopedEvent(event)) {
127
+ return this.attachAppGroupEventEnvelope(this.attachCurrentInstanceContext(payload));
128
+ }
129
+ return payload;
130
+ }
131
+ appMessageEnvelope(payload) {
132
+ if (!isJsonObject(payload))
133
+ return {};
134
+ const message = payload;
135
+ const envelope = {};
136
+ for (const key of APP_MESSAGE_ENVELOPE_KEYS) {
137
+ if (Object.prototype.hasOwnProperty.call(message, key))
138
+ envelope[key] = message[key];
139
+ }
140
+ return envelope;
141
+ }
142
+ isGroupScopedEvent(event) {
143
+ return event === 'group.changed';
144
+ }
145
+ appGroupEventEnvelope(payload) {
146
+ if (!isJsonObject(payload))
147
+ return {};
148
+ const groupEvent = payload;
149
+ const envelope = {};
150
+ for (const key of APP_GROUP_EVENT_ENVELOPE_KEYS) {
151
+ if (Object.prototype.hasOwnProperty.call(groupEvent, key))
152
+ envelope[key] = groupEvent[key];
153
+ }
154
+ return envelope;
155
+ }
156
+ attachAppMessageEnvelope(payload) {
157
+ if (!isJsonObject(payload))
78
158
  return payload;
79
- return this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload));
159
+ const result = { ...payload };
160
+ // 兼容期保留顶层信封字段;下一个大版本 0.5.* 将移除这些顶层别名,请通过 envelope.* 访问。
161
+ result.envelope = this.appMessageEnvelope(result);
162
+ return result;
163
+ }
164
+ attachAppGroupEventEnvelope(payload) {
165
+ if (!isJsonObject(payload))
166
+ return payload;
167
+ const result = { ...payload };
168
+ // 兼容期保留顶层群事件信封字段;下一个大版本 0.5.* 将移除这些顶层别名,请通过 envelope.* 访问。
169
+ result.envelope = this.appGroupEventEnvelope(result);
170
+ return result;
80
171
  }
81
172
  stripInternalSenderDeviceFields(payload) {
82
173
  if (!isJsonObject(payload))
@@ -99,6 +190,11 @@ export class MessageDeliveryEngine {
99
190
  if (msgType !== 'message.recalled' && payloadType !== 'message.recalled')
100
191
  return null;
101
192
  const event = { ...payload };
193
+ for (const key of APP_MESSAGE_ENVELOPE_KEYS) {
194
+ if (Object.prototype.hasOwnProperty.call(msg, key) && !(key in event)) {
195
+ event[key] = msg[key];
196
+ }
197
+ }
102
198
  const rawIds = event.message_ids;
103
199
  let messageIds = Array.isArray(rawIds)
104
200
  ? rawIds.map((item) => String(item ?? '').trim()).filter(Boolean)
@@ -123,8 +219,11 @@ export class MessageDeliveryEngine {
123
219
  event.timestamp = msg.timestamp ?? msg.t_server ?? event.recalled_at ?? 0;
124
220
  if ('seq' in msg && !('seq' in event))
125
221
  event.seq = msg.seq;
126
- if ('message_id' in msg && !('tombstone_message_id' in event))
127
- event.tombstone_message_id = msg.message_id;
222
+ if ('message_id' in msg) {
223
+ event.message_id = msg.message_id;
224
+ if (!('tombstone_message_id' in event))
225
+ event.tombstone_message_id = msg.message_id;
226
+ }
128
227
  if ('device_id' in msg && !('device_id' in event))
129
228
  event.device_id = msg.device_id;
130
229
  if ('slot_id' in msg && !('slot_id' in event))
@@ -148,6 +247,11 @@ export class MessageDeliveryEngine {
148
247
  if (msgType !== 'group.message_recalled' && payloadType !== 'group.message_recalled')
149
248
  return null;
150
249
  const event = { ...payload };
250
+ for (const key of APP_MESSAGE_ENVELOPE_KEYS) {
251
+ if (Object.prototype.hasOwnProperty.call(msg, key) && !(key in event)) {
252
+ event[key] = msg[key];
253
+ }
254
+ }
151
255
  const rawIds = event.message_ids;
152
256
  let messageIds = Array.isArray(rawIds)
153
257
  ? rawIds.map((item) => String(item ?? '').trim()).filter(Boolean)
@@ -170,8 +274,11 @@ export class MessageDeliveryEngine {
170
274
  event.timestamp = msg.timestamp ?? msg.t_server ?? event.recalled_at ?? 0;
171
275
  if ('seq' in msg)
172
276
  event.seq = msg.seq;
173
- if ('message_id' in msg && !('tombstone_message_id' in event))
174
- event.tombstone_message_id = msg.message_id;
277
+ if ('message_id' in msg) {
278
+ event.message_id = msg.message_id;
279
+ if (!('tombstone_message_id' in event))
280
+ event.tombstone_message_id = msg.message_id;
281
+ }
175
282
  return event;
176
283
  }
177
284
  groupRecallDedupKey(groupId, payload) {
@@ -703,20 +810,27 @@ export class MessageDeliveryEngine {
703
810
  if (eventObjects.length > 0) {
704
811
  client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq);
705
812
  }
706
- const retentionFloor = client._pullRetentionFloor(result, 'retention_floor_event_seq', 'retention_floor_event_seq');
813
+ const cursor = isJsonObject(result.cursor)
814
+ ? result.cursor
815
+ : null;
816
+ const cursorCurrentSeq = Number(cursor?.current_seq ?? 0);
817
+ const retentionFloor = Math.max(client._pullRetentionFloor(result, 'retention_floor_event_seq', 'retention_floor_event_seq'), Number.isFinite(cursorCurrentSeq) ? cursorCurrentSeq : 0);
707
818
  if (retentionFloor > 0) {
708
819
  const contigBeforeFloor = client._seqTracker.getContiguousSeq(ns);
709
820
  if (contigBeforeFloor < retentionFloor) {
710
- client._clientLog.info(`group.pull_events retention-floor advance: ns=${ns} contiguous=${contigBeforeFloor} -> retention_floor=${retentionFloor}`);
821
+ client._clientLog.info(`group.pull_events cursor/floor advance: ns=${ns} contiguous=${contigBeforeFloor} -> cursor.current_seq=${cursorCurrentSeq} floor=${retentionFloor}`);
711
822
  client._seqTracker.forceContiguousSeq(ns, retentionFloor);
712
823
  }
713
824
  }
714
825
  const eventSeqs = [];
826
+ let hasDissolvedEvent = false;
715
827
  for (const evt of eventObjects) {
716
828
  const eventSeq = Number(evt.event_seq ?? 0);
717
829
  if (Number.isFinite(eventSeq) && eventSeq > 0)
718
830
  eventSeqs.push(eventSeq);
719
831
  evt._from_gap_fill = true;
832
+ if (evt.action === 'dissolved')
833
+ hasDissolvedEvent = true;
720
834
  const et = String(evt.event_type ?? '');
721
835
  if (et !== 'group.message_created') {
722
836
  const cs = evt.client_signature;
@@ -728,19 +842,19 @@ export class MessageDeliveryEngine {
728
842
  evt._verified = await client._verifyEventSignatureAsync(evt, cs);
729
843
  }
730
844
  }
731
- await client._dispatcher.publish('group.changed', evt);
732
- }
733
- if (Number.isFinite(eventSeq) && eventSeq > 0) {
734
- client._markPulledSeqDelivered(ns, eventSeq);
845
+ if (Number.isFinite(eventSeq) && eventSeq > 0 && !client._pushedSeqs.get(ns)?.has(eventSeq)) {
846
+ this.enqueueOrderedMessage(ns, 'group.changed', eventSeq, evt);
847
+ }
735
848
  }
736
849
  filled += 1;
737
850
  }
738
- const contig = client._seqTracker.getContiguousSeq(ns);
739
- if (contig !== pageContigBefore) {
851
+ const ackContig = client._seqTracker.getContiguousSeq(ns);
852
+ await this.drainOrderedMessages(ns);
853
+ if (ackContig !== pageContigBefore && !hasDissolvedEvent) {
740
854
  this.saveSeqTrackerState();
741
855
  }
742
- if (eventObjects.length > 0 && contig > 0 && contig !== pageContigBefore) {
743
- const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, contig);
856
+ if (eventObjects.length > 0 && ackContig > 0 && ackContig !== pageContigBefore) {
857
+ const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, ackContig);
744
858
  client._transport.call('group.ack_events', {
745
859
  group_id: groupId,
746
860
  event_seq: ackSeq,
@@ -771,36 +885,65 @@ export class MessageDeliveryEngine {
771
885
  }
772
886
  }
773
887
  }
774
- handleGroupChangedEventSeq(data, groupId) {
888
+ async handleGroupChangedEventSeq(data, groupId) {
775
889
  const client = this.runtime.client;
776
890
  let needPull = false;
777
891
  const rawEventSeq = data.event_seq;
778
- if (rawEventSeq != null && groupId) {
779
- const es = Number(rawEventSeq);
780
- if (Number.isFinite(es) && es > 0) {
781
- needPull = client._seqTracker.onMessageSeq(`group_event:${groupId}`, es);
782
- }
783
- this.saveSeqTrackerState();
784
- const ns = `group_event:${groupId}`;
892
+ const eventSeq = Number(rawEventSeq);
893
+ if (!groupId || !Number.isFinite(eventSeq) || !Number.isInteger(eventSeq) || eventSeq <= 0) {
894
+ await this.publishOrderedGroupChanged(data, 'legacy');
895
+ return;
896
+ }
897
+ const ns = `group_event:${groupId}`;
898
+ if (this.isSelfJoinGroupChanged(data)) {
785
899
  const contig = client._seqTracker.getContiguousSeq(ns);
786
- if (contig > 0) {
787
- const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, contig);
788
- client._transport.call('group.ack_events', {
789
- group_id: groupId,
790
- event_seq: ackSeq,
791
- device_id: client._deviceId,
792
- slot_id: client._slotId,
793
- }, undefined, undefined, true).catch((e) => {
794
- client._clientLog.debug(`group event push auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`);
795
- });
900
+ const maxSeen = client._seqTracker.getMaxSeenSeq(ns);
901
+ if (contig === 0 && maxSeen === 0 && eventSeq > 1) {
902
+ client._clientLog.debug(`group.changed self-join baseline: group=${groupId}, event_seq=${eventSeq}, baseline=${eventSeq - 1}`);
903
+ client._seqTracker.forceContiguousSeq(ns, eventSeq - 1);
796
904
  }
797
905
  }
906
+ const contigBefore = client._seqTracker.getContiguousSeq(ns);
907
+ if (eventSeq <= contigBefore || client._pushedSeqs.get(ns)?.has(eventSeq)) {
908
+ client._clientLog.debug(`group.changed skipped duplicate/stale: group=${groupId}, event_seq=${eventSeq}, contiguous=${contigBefore}`);
909
+ return;
910
+ }
911
+ this.enqueueOrderedMessage(ns, 'group.changed', eventSeq, data);
912
+ needPull = client._seqTracker.onMessageSeq(ns, eventSeq);
913
+ const ackContig = client._seqTracker.getContiguousSeq(ns);
914
+ await this.drainOrderedMessages(ns);
915
+ if (ackContig > 0 && ackContig !== contigBefore) {
916
+ if (data.action !== 'dissolved')
917
+ this.saveSeqTrackerState();
918
+ const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, ackContig);
919
+ client._transport.call('group.ack_events', {
920
+ group_id: groupId,
921
+ event_seq: ackSeq,
922
+ device_id: client._deviceId,
923
+ slot_id: client._slotId,
924
+ }, undefined, undefined, true).catch((e) => {
925
+ client._clientLog.debug(`group event push auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`);
926
+ });
927
+ }
798
928
  if (needPull && groupId && !data._from_gap_fill) {
799
929
  this.fillGroupEventGap(groupId).catch((exc) => {
800
930
  client._clientLog.warn(`background gap fill trigger failed: ${formatDeliveryError(exc)}`);
801
931
  });
802
932
  }
803
933
  }
934
+ isSelfJoinGroupChanged(data) {
935
+ const action = String(data.action ?? '').trim();
936
+ if (!['member_added', 'joined', 'join_approved', 'invite_code_used'].includes(action))
937
+ return false;
938
+ const selfAid = String(this.runtime.client._aid ?? '').trim();
939
+ if (!selfAid)
940
+ return false;
941
+ const joinedAid = String(data.joined_aid ?? data.member_aid ?? data.aid ?? '').trim();
942
+ if (joinedAid === selfAid)
943
+ return true;
944
+ const actorAid = String(data.actor_aid ?? '').trim();
945
+ return !joinedAid && ['joined', 'invite_code_used'].includes(action) && actorAid === selfAid;
946
+ }
804
947
  async onV2PushNotification(data) {
805
948
  const client = this.runtime.client;
806
949
  if (!client._v2Session)
@@ -1205,16 +1348,7 @@ export class MessageDeliveryEngine {
1205
1348
  client._markOrderedSeqDelivered(ns, seq);
1206
1349
  continue;
1207
1350
  }
1208
- if (pullResponse) {
1209
- const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(item.event, item.payload, 'ordered-drain'));
1210
- if (isPromiseLike(published))
1211
- await published;
1212
- }
1213
- else {
1214
- const published = client._publishAppEvent(item.event, item.payload, 'ordered-drain');
1215
- if (isPromiseLike(published))
1216
- await published;
1217
- }
1351
+ await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, 'ordered-drain', pullResponse);
1218
1352
  this.markPublishedSeq(ns, seq);
1219
1353
  client._markOrderedSeqDelivered(ns, seq);
1220
1354
  client._clientLog.debug(`publish ordered drain delivered: ns=${ns}, seq=${seq}, event=${item.event}`);
@@ -1227,9 +1361,7 @@ export class MessageDeliveryEngine {
1227
1361
  const seqNum = Number(seq);
1228
1362
  if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
1229
1363
  client._clientLog.debug(`publish ordered direct(no-seq): event=${event}, ns=${ns || '<none>'}, seq=${String(seq)}`);
1230
- const published = client._publishAppEvent(event, payload, 'ordered');
1231
- if (isPromiseLike(published))
1232
- await published;
1364
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload, 'ordered');
1233
1365
  return true;
1234
1366
  }
1235
1367
  if (client._pushedSeqs.get(ns)?.has(seqNum)) {
@@ -1263,9 +1395,7 @@ export class MessageDeliveryEngine {
1263
1395
  queue?.delete(seqNum);
1264
1396
  if (queue && queue.size === 0)
1265
1397
  client._pendingOrderedMsgs.delete(ns);
1266
- const published = client._publishAppEvent(event, payload, 'ordered');
1267
- if (isPromiseLike(published))
1268
- await published;
1398
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload, 'ordered');
1269
1399
  this.markPublishedSeq(ns, seqNum);
1270
1400
  client._markOrderedSeqDelivered(ns, seqNum);
1271
1401
  client._clientLog.debug(`publish ordered delivered: event=${event}, ns=${ns}, seq=${seqNum}`);