@agentunion/fastaun 0.3.5 → 0.3.6

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.
package/dist/client.d.ts CHANGED
@@ -411,6 +411,7 @@ export declare class AUNClient {
411
411
  skipAutoAck?: boolean;
412
412
  gateLocked?: boolean;
413
413
  scheduleFollowup?: boolean;
414
+ force?: boolean;
414
415
  }): Promise<Array<Record<string, unknown>>>;
415
416
  /** V2 P2P ack,并触发旧 SPK 销毁自检。 */
416
417
  ackV2(upToSeq?: number): Promise<unknown>;
@@ -437,6 +438,15 @@ export declare class AUNClient {
437
438
  private _attachV2EnvelopeMetadata;
438
439
  private _attachV2EnvelopeMetadataFromSource;
439
440
  private _extractV2EnvelopeFromSource;
441
+ private _truthyBool;
442
+ private _encryptedPushEnvelope;
443
+ private _isEncryptedPushMessage;
444
+ private _isEncryptedEnvelopePayload;
445
+ private _isV2EncryptedEnvelopePayload;
446
+ private _safeUndecryptablePushEvent;
447
+ private _decryptEncryptedPushPayload;
448
+ private _publishEncryptedPushAsUndecryptable;
449
+ private _publishEncryptedPushMessage;
440
450
  private _metadataWithoutAuth;
441
451
  private _putMessageThoughtEncryptedV2;
442
452
  private _putGroupThoughtEncryptedV2;
package/dist/client.js CHANGED
@@ -1493,11 +1493,12 @@ export class AUNClient {
1493
1493
  if (method === 'message.pull' || method === 'message.v2.pull') {
1494
1494
  await this._ensureV2SessionReady('message.pull');
1495
1495
  const skipAutoAck = p._skip_auto_ack === true || p.skip_auto_ack === true;
1496
+ const force = p.force === true;
1496
1497
  const afterSeq = Number(p.after_seq ?? 0) || 0;
1497
1498
  const limit = Number(p.limit ?? 50) || 50;
1498
1499
  const messages = skipAutoAck
1499
- ? await runWithRpcPriority(() => this.pullV2(afterSeq, limit, { skipAutoAck: true, gateLocked: true }))
1500
- : await runWithRpcPriority(() => this.pullV2(afterSeq, limit, { gateLocked: true }));
1500
+ ? await runWithRpcPriority(() => this.pullV2(afterSeq, limit, { skipAutoAck: true, gateLocked: true, force }))
1501
+ : await runWithRpcPriority(() => this.pullV2(afterSeq, limit, { gateLocked: true, force }));
1501
1502
  return { messages };
1502
1503
  }
1503
1504
  if (method === 'message.ack' || method === 'message.v2.ack') {
@@ -1762,6 +1763,7 @@ export class AUNClient {
1762
1763
  this._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=${this._deviceId}, local_slot=${this._slotId}`);
1763
1764
  return;
1764
1765
  }
1766
+ const encryptedPush = this._isEncryptedPushMessage(msg);
1765
1767
  // P2P 空洞检测
1766
1768
  const seq = msg.seq;
1767
1769
  if (seq !== undefined && seq !== null && this._aid) {
@@ -1770,7 +1772,9 @@ export class AUNClient {
1770
1772
  if (seq > 0)
1771
1773
  this._seqTracker.updateMaxSeen(ns, seq);
1772
1774
  const contigBefore = this._seqTracker.getContiguousSeq(ns);
1773
- const published = await this._publishOrderedMessage('message.received', ns, seq, msg);
1775
+ const published = encryptedPush
1776
+ ? await this._publishEncryptedPushMessage('message.received', 'message.undecryptable', ns, seq, msg, false)
1777
+ : await this._publishOrderedMessage('message.received', ns, seq, msg);
1774
1778
  const contigAfter = this._seqTracker.getContiguousSeq(ns);
1775
1779
  const needPull = Number(seq) > contigAfter && !published;
1776
1780
  if (needPull) {
@@ -1790,8 +1794,14 @@ export class AUNClient {
1790
1794
  // 即时持久化 cursor,异常断连后不回退
1791
1795
  if (contigAfter !== contigBefore)
1792
1796
  this._saveSeqTrackerState();
1797
+ if (encryptedPush)
1798
+ return;
1793
1799
  }
1794
1800
  else {
1801
+ if (encryptedPush) {
1802
+ await this._publishEncryptedPushMessage('message.received', 'message.undecryptable', '', seq ?? 0, msg, false);
1803
+ return;
1804
+ }
1795
1805
  // V2-only:普通 _raw.message.received 只承载明文;V2 密文由 peer.v2.message_received 通知触发 pull。
1796
1806
  await this._publishAppEvent('message.received', msg, 'push');
1797
1807
  }
@@ -1850,13 +1860,16 @@ export class AUNClient {
1850
1860
  });
1851
1861
  return;
1852
1862
  }
1863
+ const encryptedPush = this._isEncryptedPushMessage(msg);
1853
1864
  if (groupId && seq !== undefined && seq !== null) {
1854
1865
  const ns = `group:${groupId}`;
1855
1866
  // Push 只先更新 maxSeenSeq;contiguous_seq 是已交付游标,必须等应用层发布返回后再推进。
1856
1867
  if (seq > 0)
1857
1868
  this._seqTracker.updateMaxSeen(ns, seq);
1858
1869
  const contigBefore = this._seqTracker.getContiguousSeq(ns);
1859
- const published = await this._publishOrderedMessage('group.message_created', ns, seq, msg);
1870
+ const published = encryptedPush
1871
+ ? await this._publishEncryptedPushMessage('group.message_created', 'group.message_undecryptable', ns, seq, msg, true)
1872
+ : await this._publishOrderedMessage('group.message_created', ns, seq, msg);
1860
1873
  const contigAfter = this._seqTracker.getContiguousSeq(ns);
1861
1874
  const needPull = Number(seq) > contigAfter && !published;
1862
1875
  if (needPull) {
@@ -1874,8 +1887,14 @@ export class AUNClient {
1874
1887
  }
1875
1888
  if (contigAfter !== contigBefore)
1876
1889
  this._saveSeqTrackerState();
1890
+ if (encryptedPush)
1891
+ return;
1877
1892
  }
1878
1893
  else {
1894
+ if (encryptedPush) {
1895
+ await this._publishEncryptedPushMessage('group.message_created', 'group.message_undecryptable', '', seq ?? 0, msg, true);
1896
+ return;
1897
+ }
1879
1898
  // V2-only:普通 group.message_created 只承载明文;V2 密文由 group.v2.message_created 通知触发 pull。
1880
1899
  await this._publishAppEvent('group.message_created', msg, 'group-push');
1881
1900
  }
@@ -3749,6 +3768,27 @@ export class AUNClient {
3749
3768
  identity = null;
3750
3769
  }
3751
3770
  }
3771
+ if (!identity?.private_key_pem) {
3772
+ // fallback:缓存的 identity 可能被 instanceState 污染,重新从 keystore 加载
3773
+ try {
3774
+ identity = this._keystore.loadIdentity(this._aid);
3775
+ if (identity?.private_key_pem) {
3776
+ this._identity = identity;
3777
+ this._clientLog.warn('V2 session init: identity cache was stale, reloaded from keystore');
3778
+ // 重新持久化 instance_state,清理脏数据
3779
+ const persistIdentity = this._auth._persistIdentity;
3780
+ if (typeof persistIdentity === 'function') {
3781
+ try {
3782
+ persistIdentity.call(this._auth, identity);
3783
+ }
3784
+ catch { /* best-effort */ }
3785
+ }
3786
+ }
3787
+ }
3788
+ catch {
3789
+ identity = null;
3790
+ }
3791
+ }
3752
3792
  if (!identity?.private_key_pem) {
3753
3793
  this._clientLog.warn('V2 session init skipped: no AID private key');
3754
3794
  return;
@@ -4212,7 +4252,7 @@ export class AUNClient {
4212
4252
  }
4213
4253
  const decrypted = [];
4214
4254
  let totalRawCount = 0;
4215
- let nextAfterSeq = afterSeq || (ns ? this._seqTracker.getContiguousSeq(ns) : 0);
4255
+ let nextAfterSeq = opts?.force ? afterSeq : (afterSeq || (ns ? this._seqTracker.getContiguousSeq(ns) : 0));
4216
4256
  let pageCount = 0;
4217
4257
  const maxPages = 100;
4218
4258
  while (pageCount < maxPages) {
@@ -4221,6 +4261,7 @@ export class AUNClient {
4221
4261
  const result = await this._callRawV2Rpc('message.v2.pull', {
4222
4262
  after_seq: nextAfterSeq,
4223
4263
  limit,
4264
+ ...(opts?.force ? { force: true } : {}),
4224
4265
  });
4225
4266
  const messages = (Array.isArray(result?.messages) ? result.messages : []);
4226
4267
  totalRawCount += messages.length;
@@ -4887,6 +4928,12 @@ export class AUNClient {
4887
4928
  encrypted: true,
4888
4929
  e2ee,
4889
4930
  };
4931
+ const explicitDirection = String(msg.direction ?? '').trim();
4932
+ result.direction = explicitDirection || (fromAid && fromAid === this._aid ? 'outbound_sync' : 'inbound');
4933
+ if (msg.device_id !== undefined)
4934
+ result.device_id = msg.device_id;
4935
+ if (msg.slot_id !== undefined)
4936
+ result.slot_id = msg.slot_id;
4890
4937
  this._attachV2EnvelopeMetadata(result, e2ee);
4891
4938
  this._logMessageDebug('decrypt-ok', 'v2.decrypt', groupIdForKeys ? 'group.message_created' : 'message.received', result);
4892
4939
  return result;
@@ -4953,6 +5000,146 @@ export class AUNClient {
4953
5000
  }
4954
5001
  return null;
4955
5002
  }
5003
+ _truthyBool(value) {
5004
+ if (value === true || value === 1)
5005
+ return true;
5006
+ if (typeof value === 'string') {
5007
+ const normalized = value.trim().toLowerCase();
5008
+ return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';
5009
+ }
5010
+ return false;
5011
+ }
5012
+ _encryptedPushEnvelope(msg) {
5013
+ const payload = msg.payload;
5014
+ if (this._isEncryptedEnvelopePayload(payload))
5015
+ return payload;
5016
+ if (typeof msg.envelope_json === 'string' && msg.envelope_json.trim()) {
5017
+ try {
5018
+ const parsed = JSON.parse(msg.envelope_json);
5019
+ if (this._isEncryptedEnvelopePayload(parsed))
5020
+ return parsed;
5021
+ }
5022
+ catch {
5023
+ return null;
5024
+ }
5025
+ }
5026
+ return null;
5027
+ }
5028
+ _isEncryptedPushMessage(msg) {
5029
+ if (this._truthyBool(msg.encrypted))
5030
+ return true;
5031
+ return this._encryptedPushEnvelope(msg) !== null;
5032
+ }
5033
+ _isEncryptedEnvelopePayload(payload) {
5034
+ if (!isJsonObject(payload))
5035
+ return false;
5036
+ const envelope = payload;
5037
+ const payloadType = String(envelope.type ?? '').trim();
5038
+ if (payloadType.startsWith('e2ee.'))
5039
+ return true;
5040
+ if (!String(envelope.ciphertext ?? '').trim())
5041
+ return false;
5042
+ return envelope.nonce !== undefined
5043
+ || envelope.tag !== undefined
5044
+ || envelope.recipient !== undefined
5045
+ || envelope.recipients !== undefined
5046
+ || envelope.wrapped_key !== undefined
5047
+ || envelope.recipients_digest !== undefined;
5048
+ }
5049
+ _isV2EncryptedEnvelopePayload(envelope) {
5050
+ if (!envelope)
5051
+ return false;
5052
+ const payloadType = String(envelope.type ?? '').trim();
5053
+ if (payloadType === 'e2ee.p2p_encrypted' || payloadType === 'e2ee.group_encrypted')
5054
+ return true;
5055
+ return String(envelope.version ?? '').trim().toLowerCase() === 'v2' && payloadType.startsWith('e2ee.');
5056
+ }
5057
+ _safeUndecryptablePushEvent(msg, group) {
5058
+ const event = {
5059
+ message_id: msg.message_id,
5060
+ from: msg.from,
5061
+ seq: msg.seq,
5062
+ timestamp: (msg.timestamp ?? msg.t_server),
5063
+ device_id: msg.device_id,
5064
+ slot_id: msg.slot_id,
5065
+ _decrypt_error: 'encrypted push payload is not decryptable on raw push path',
5066
+ _decrypt_stage: 'push_envelope',
5067
+ };
5068
+ if (group) {
5069
+ event.group_id = msg.group_id;
5070
+ }
5071
+ else {
5072
+ event.to = msg.to;
5073
+ }
5074
+ const envelope = this._encryptedPushEnvelope(msg);
5075
+ if (envelope) {
5076
+ event._envelope_type = String(envelope.type ?? '');
5077
+ event._suite = String(envelope.suite ?? '');
5078
+ if (this._isV2EncryptedEnvelopePayload(envelope)) {
5079
+ this._attachV2EnvelopeMetadata(event, this._v2E2eeMeta(envelope));
5080
+ }
5081
+ }
5082
+ return event;
5083
+ }
5084
+ async _decryptEncryptedPushPayload(msg, group) {
5085
+ const envelope = this._encryptedPushEnvelope(msg);
5086
+ if (!this._isV2EncryptedEnvelopePayload(envelope))
5087
+ return null;
5088
+ const aad = isJsonObject(envelope.aad) ? envelope.aad : {};
5089
+ const fromAid = String(msg.from_aid ?? msg.from ?? msg.sender_aid ?? aad.from ?? '').trim();
5090
+ const plaintext = await this._decryptV2EnvelopeForThought({ envelope, fromAid });
5091
+ if (!plaintext)
5092
+ return null;
5093
+ const e2ee = this._v2E2eeMeta(envelope);
5094
+ const result = {
5095
+ message_id: String(msg.message_id ?? ''),
5096
+ from: fromAid,
5097
+ seq: msg.seq,
5098
+ timestamp: (msg.t_server ?? msg.timestamp),
5099
+ payload: plaintext,
5100
+ encrypted: true,
5101
+ e2ee,
5102
+ };
5103
+ result.direction = fromAid && fromAid === this._aid ? 'outbound_sync' : 'inbound';
5104
+ if (msg.t_server !== undefined)
5105
+ result.t_server = msg.t_server;
5106
+ if (msg.device_id !== undefined)
5107
+ result.device_id = msg.device_id;
5108
+ if (msg.slot_id !== undefined)
5109
+ result.slot_id = msg.slot_id;
5110
+ if (group) {
5111
+ result.group_id = (msg.group_id ?? aad.group_id ?? envelope.group_id);
5112
+ }
5113
+ else {
5114
+ result.to = (msg.to ?? this._aid ?? '');
5115
+ }
5116
+ this._attachV2EnvelopeMetadata(result, e2ee);
5117
+ this._logMessageDebug('decrypt-ok', 'push.encrypted', group ? 'group.message_created' : 'message.received', result);
5118
+ return result;
5119
+ }
5120
+ async _publishEncryptedPushAsUndecryptable(event, ns, seq, msg, group) {
5121
+ const safeEvent = this._safeUndecryptablePushEvent(msg, group);
5122
+ this._logMessageDebug('decrypt-fail', 'push.encrypted', event, safeEvent);
5123
+ if (ns) {
5124
+ return await this._publishOrderedMessage(event, ns, seq, safeEvent);
5125
+ }
5126
+ const published = this._publishAppEvent(event, safeEvent, 'push');
5127
+ if (isPromiseLike(published))
5128
+ await published;
5129
+ return true;
5130
+ }
5131
+ async _publishEncryptedPushMessage(normalEvent, undecryptableEvent, ns, seq, msg, group) {
5132
+ const decrypted = await this._decryptEncryptedPushPayload(msg, group);
5133
+ if (decrypted) {
5134
+ if (ns)
5135
+ return await this._publishOrderedMessage(normalEvent, ns, seq, decrypted);
5136
+ const published = this._publishAppEvent(normalEvent, decrypted, 'push');
5137
+ if (isPromiseLike(published))
5138
+ await published;
5139
+ return true;
5140
+ }
5141
+ return await this._publishEncryptedPushAsUndecryptable(undecryptableEvent, ns, seq, msg, group);
5142
+ }
4956
5143
  _metadataWithoutAuth(value) {
4957
5144
  const candidate = value;
4958
5145
  if (!isJsonObject(candidate))