@agentunion/fastaun-browser 0.5.8 → 0.5.10

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 (58) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/_packed_docs/CHANGELOG.md +62 -0
  3. package/_packed_docs/agent.md/examples//347/276/244/347/273/204-/345/274/200/345/217/221/345/233/242/351/230/237.md +21 -0
  4. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +6 -5
  5. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +43 -8
  6. package/_packed_docs/sdk/09-group-rpc-manual.md +18 -3
  7. package/_packed_docs/sdk/09-message-rpc-manual.md +34 -10
  8. package/dist/agent-md.d.ts.map +1 -1
  9. package/dist/agent-md.js +24 -1
  10. package/dist/agent-md.js.map +1 -1
  11. package/dist/aid-store.d.ts.map +1 -1
  12. package/dist/aid-store.js +1 -3
  13. package/dist/aid-store.js.map +1 -1
  14. package/dist/auth.d.ts.map +1 -1
  15. package/dist/auth.js +16 -2
  16. package/dist/auth.js.map +1 -1
  17. package/dist/bundle.js +1247 -269
  18. package/dist/client/delivery.d.ts +30 -7
  19. package/dist/client/delivery.d.ts.map +1 -1
  20. package/dist/client/delivery.js +354 -92
  21. package/dist/client/delivery.js.map +1 -1
  22. package/dist/client/group-state.js +8 -8
  23. package/dist/client/group-state.js.map +1 -1
  24. package/dist/client/lifecycle.d.ts.map +1 -1
  25. package/dist/client/lifecycle.js +41 -34
  26. package/dist/client/lifecycle.js.map +1 -1
  27. package/dist/client/rpc-pipeline.d.ts +12 -0
  28. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  29. package/dist/client/rpc-pipeline.js +205 -36
  30. package/dist/client/rpc-pipeline.js.map +1 -1
  31. package/dist/client/v2-e2ee.d.ts +1 -1
  32. package/dist/client/v2-e2ee.d.ts.map +1 -1
  33. package/dist/client/v2-e2ee.js +72 -18
  34. package/dist/client/v2-e2ee.js.map +1 -1
  35. package/dist/client.d.ts +0 -1
  36. package/dist/client.d.ts.map +1 -1
  37. package/dist/client.js +128 -45
  38. package/dist/client.js.map +1 -1
  39. package/dist/events.d.ts +31 -2
  40. package/dist/events.d.ts.map +1 -1
  41. package/dist/events.js +167 -7
  42. package/dist/events.js.map +1 -1
  43. package/dist/facades.d.ts.map +1 -1
  44. package/dist/facades.js +7 -3
  45. package/dist/facades.js.map +1 -1
  46. package/dist/register-flow.d.ts.map +1 -1
  47. package/dist/register-flow.js +16 -2
  48. package/dist/register-flow.js.map +1 -1
  49. package/dist/transport.d.ts +10 -1
  50. package/dist/transport.d.ts.map +1 -1
  51. package/dist/transport.js +283 -29
  52. package/dist/transport.js.map +1 -1
  53. package/dist/version.d.ts +1 -1
  54. package/dist/version.d.ts.map +1 -1
  55. package/dist/version.js +1 -1
  56. package/dist/version.js.map +1 -1
  57. package/package.json +1 -1
  58. package/_packed_docs//345/217/221/345/270/203/346/212/245/345/221/212-0.5.6.md +0 -260
@@ -19,6 +19,15 @@ const PROTECTED_HEADERS_METHODS = new Set([
19
19
  'message.thought.put',
20
20
  'group.thought.put',
21
21
  ]);
22
+ function generateMessageId() {
23
+ if (typeof crypto.randomUUID === 'function')
24
+ return `m-${crypto.randomUUID().replace(/-/g, '')}`;
25
+ const bytes = new Uint8Array(16);
26
+ crypto.getRandomValues(bytes);
27
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
28
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
29
+ return `m-${Array.from(bytes, (value) => value.toString(16).padStart(2, '0')).join('')}`;
30
+ }
22
31
  const SIGNED_METHODS = new Set([
23
32
  'message.send',
24
33
  'message.v2.put_peer_pk', 'message.v2.bootstrap',
@@ -71,6 +80,7 @@ const SIGNED_METHODS = new Set([
71
80
  'group.dissolve', 'group.suspend', 'group.resume',
72
81
  ]);
73
82
  const SIGNING_KEY_CACHE_MAX = 32;
83
+ const IDENTITY_ADMISSION_RETRY_DELAYS_MS = [0, 50, 100, 200];
74
84
  const signingKeyCache = new Map();
75
85
  const certFingerprintCache = new Map();
76
86
  function cacheGet(cache, key) {
@@ -120,6 +130,13 @@ async function signingCertFingerprint(certPem) {
120
130
  }
121
131
  const PULL_GATE_STALE_MS = 30000;
122
132
  const PULL_GATE_OPERATION_TIMEOUT_MS = 3000;
133
+ function sameIdentityAdmissionRejection(error, original) {
134
+ const errorCode = Number(error?.code);
135
+ const originalCode = Number(original?.code);
136
+ const errorMessage = String(error instanceof Error ? error.message : error?.message ?? '').trim().toLowerCase();
137
+ const originalMessage = String(original instanceof Error ? original.message : original?.message ?? '').trim().toLowerCase();
138
+ return errorCode === -32003 && originalCode === -32003 && errorMessage === originalMessage;
139
+ }
123
140
  const NON_IDEMPOTENT_TIMEOUT = 35;
124
141
  const NON_IDEMPOTENT_METHODS = new Set([
125
142
  'message.send', 'mail.send', 'group.send', 'group.create', 'group.invite',
@@ -170,6 +187,9 @@ export class RpcPipeline {
170
187
  runtime;
171
188
  pullGateStates = new Map();
172
189
  inlineRealtimeScopes = new Set();
190
+ pullWorkTokens = new Set();
191
+ pullGeneration = 0;
192
+ pullInvalidationWait = null;
173
193
  constructor(runtime) {
174
194
  this.runtime = runtime;
175
195
  }
@@ -359,6 +379,9 @@ export class RpcPipeline {
359
379
  delete p._rpc_background;
360
380
  if (method === 'message.send' || method === 'group.send') {
361
381
  this.normalizeOutboundMessagePayload(p, method);
382
+ if (!Object.prototype.hasOwnProperty.call(p, 'message_id') || p.message_id == null) {
383
+ p.message_id = generateMessageId();
384
+ }
362
385
  }
363
386
  this.normalizeGroupCallIdentifier(method, p);
364
387
  this.validateOutboundCall(method, p);
@@ -539,8 +562,8 @@ export class RpcPipeline {
539
562
  const mode = method === 'message.history' ? 'history' : params.window_mode === 'tail' ? 'tail' : 'forward';
540
563
  const cursor = mode === 'history'
541
564
  ? `before=${String(params.before_seq)}`
542
- : `after=${String(params.after_seq ?? 0)}|force=${String(Boolean(params.force))}`;
543
- return `p2p:${client._aid}|${mode}|${cursor}|limit=${String(params.limit)}`;
565
+ : `after=${String(params.after_seq ?? 0)}`;
566
+ return `p2p:${client._aid}|mode=${mode}|cursor=${cursor}|force=${String(Boolean(params.force))}|limit=${String(params.limit)}`;
544
567
  }
545
568
  if (method === 'group.pull' || method === 'group.v2.pull' || method === 'group.history') {
546
569
  const gid = String(params.group_id ?? params.group_aid ?? '').trim();
@@ -549,13 +572,17 @@ export class RpcPipeline {
549
572
  const mode = method === 'group.history' ? 'history' : params.window_mode === 'tail' ? 'tail' : 'forward';
550
573
  const cursor = mode === 'history'
551
574
  ? `before=${String(params.before_seq)}`
552
- : `after=${String(params.after_seq ?? params.after_message_seq ?? 0)}|force=${String(Boolean(params.force))}`;
553
- return `group:${gid}|${mode}|${cursor}|limit=${String(params.limit)}`;
575
+ : `after=${String(params.after_seq ?? params.after_message_seq ?? 0)}`;
576
+ const explicitCursor = this.explicitGroupCursorParams(params);
577
+ const cursorSuffix = Object.keys(explicitCursor).length > 0
578
+ ? `|cursor_params=${stableStringify(explicitCursor)}`
579
+ : '';
580
+ return `group:${gid}|mode=${mode}|cursor=${cursor}${cursorSuffix}|force=${String(Boolean(params.force))}|limit=${String(params.limit)}`;
554
581
  }
555
582
  if (method === 'group.pull_events') {
556
583
  const gid = String(params.group_id ?? params.group_aid ?? '').trim();
557
584
  return gid
558
- ? `group_event:${gid}|forward|after=${String(params.after_event_seq ?? 0)}|limit=${String(params.limit)}`
585
+ ? `group_event:${gid}|mode=forward|cursor=after=${String(params.after_event_seq ?? 0)}|force=${String(Boolean(params.force))}|limit=${String(params.limit)}`
559
586
  : '';
560
587
  }
561
588
  return '';
@@ -578,8 +605,8 @@ export class RpcPipeline {
578
605
  if (!state) {
579
606
  state = { active: null, foreground: [], background: [], byKey: new Map(), lifecycle: new Map(), watchdog: null };
580
607
  this.pullGateStates.set(name, state);
581
- this.startPullGateWatchdog(state, name);
582
608
  }
609
+ this.startPullGateWatchdog(state, name);
583
610
  return state;
584
611
  }
585
612
  pullGateOperationTimeoutMs() {
@@ -597,6 +624,14 @@ export class RpcPipeline {
597
624
  state.watchdog = timer;
598
625
  timer.unref?.();
599
626
  }
627
+ stopPullGateWatchdogs() {
628
+ for (const state of this.pullGateStates.values()) {
629
+ if (state.watchdog === null)
630
+ continue;
631
+ clearInterval(state.watchdog);
632
+ state.watchdog = null;
633
+ }
634
+ }
600
635
  checkPullGate(state, name) {
601
636
  const active = state.active;
602
637
  if (!active) {
@@ -608,18 +643,12 @@ export class RpcPipeline {
608
643
  if (active.timedOut)
609
644
  return;
610
645
  active.timedOut = true;
646
+ active.invalidated = true;
647
+ this.pullGeneration += 1;
611
648
  active.cancel?.();
612
649
  const err = new TimeoutError(`pull gate timeout: ${active.namespace}`, { retryable: true });
613
650
  this.runtime.client._clientLog?.warn(`pull gate watchdog timeout: gate=${name} key=${active.key}`);
614
- state.lifecycle.set(active.namespace, 'idle');
615
- if (state.active === active)
616
- state.active = null;
617
- if (state.byKey.get(active.key) === active)
618
- state.byKey.delete(active.key);
619
- active.resolve = () => { };
620
651
  active.reject(err);
621
- this.drainPullGate(state);
622
- this.runtime.client._delivery?.onPullGateIdle?.(active.namespace);
623
652
  }
624
653
  realtimePullNamespace(key) {
625
654
  for (const marker of ['|tail|', '|forward|']) {
@@ -640,7 +669,7 @@ export class RpcPipeline {
640
669
  return request;
641
670
  const state = this.pullGateStates.get(this.pullGateName(key));
642
671
  const active = state?.active;
643
- if (!active || active.namespace !== this.pullScopeKey(key) || active.timedOut)
672
+ if (!active || active.key !== key || active.timedOut || active.invalidated)
644
673
  return request;
645
674
  const boundCancel = () => cancel.call(request);
646
675
  active.cancel = boundCancel;
@@ -706,6 +735,32 @@ export class RpcPipeline {
706
735
  const matches = (job) => job !== null && job.namespace === ns;
707
736
  return matches(state.active) || state.foreground.some(matches) || state.background.some(matches);
708
737
  }
738
+ hasAnyPullActivity() {
739
+ if (this.pullWorkTokens.size > 0)
740
+ return true;
741
+ for (const state of this.pullGateStates.values()) {
742
+ if (state.active || state.foreground.length > 0 || state.background.length > 0)
743
+ return true;
744
+ }
745
+ return false;
746
+ }
747
+ reservePullWork() {
748
+ const token = Symbol('pull-work');
749
+ this.pullWorkTokens.add(token);
750
+ let released = false;
751
+ return () => {
752
+ if (released)
753
+ return;
754
+ released = true;
755
+ this.pullWorkTokens.delete(token);
756
+ this.runtime.client._delivery?.onPullWorkSettled?.();
757
+ };
758
+ }
759
+ releasePullWorkToken(token) {
760
+ if (!this.pullWorkTokens.delete(token))
761
+ return;
762
+ this.runtime.client._delivery?.onPullWorkSettled?.();
763
+ }
709
764
  async tryRunInlineRealtime(ns, seq, operation) {
710
765
  const normalized = String(ns ?? '').trim();
711
766
  if ((!normalized.startsWith('p2p:') && !normalized.startsWith('group:'))
@@ -744,7 +799,11 @@ export class RpcPipeline {
744
799
  resumeResolve: null,
745
800
  pullingStartedAt: 0,
746
801
  timedOut: false,
802
+ invalidated: false,
747
803
  cancel: null,
804
+ settled: Promise.resolve(),
805
+ settledResolve: null,
806
+ workToken: Symbol('inline-pull-work'),
748
807
  };
749
808
  this.inlineRealtimeScopes.add(normalized);
750
809
  try {
@@ -791,17 +850,77 @@ export class RpcPipeline {
791
850
  gate.inflight = false;
792
851
  gate.startedAt = 0;
793
852
  }
853
+ isPullGenerationCurrent(generation) {
854
+ return generation === this.pullGeneration;
855
+ }
856
+ invalidatePulls() {
857
+ if (this.pullInvalidationWait)
858
+ return this.pullInvalidationWait;
859
+ this.pullGeneration += 1;
860
+ const error = new Error('pull invalidated');
861
+ const waits = [];
862
+ for (const state of this.pullGateStates.values()) {
863
+ const jobs = new Set();
864
+ if (state.active)
865
+ jobs.add(state.active);
866
+ for (const job of state.foreground)
867
+ jobs.add(job);
868
+ for (const job of state.background)
869
+ jobs.add(job);
870
+ for (const job of jobs) {
871
+ job.invalidated = true;
872
+ if (!job.timedOut)
873
+ job.reject(error);
874
+ job.cancel?.();
875
+ job.resumeResolve?.();
876
+ job.resumeResolve = null;
877
+ if (job.running) {
878
+ waits.push(job.settled);
879
+ }
880
+ else {
881
+ this.releasePullWorkToken(job.workToken);
882
+ job.settledResolve?.();
883
+ job.settledResolve = null;
884
+ }
885
+ }
886
+ state.foreground = state.foreground.filter((job) => !jobs.has(job));
887
+ state.background = state.background.filter((job) => !jobs.has(job));
888
+ for (const [key, job] of state.byKey.entries()) {
889
+ if (jobs.has(job) && !job.running)
890
+ state.byKey.delete(key);
891
+ }
892
+ this.drainPullGate(state);
893
+ }
894
+ const wait = Promise.all(waits).then(() => undefined);
895
+ this.runtime.client._delivery?.onPullWorkSettled?.();
896
+ let tracked;
897
+ tracked = wait.finally(() => {
898
+ if (this.pullInvalidationWait === tracked)
899
+ this.pullInvalidationWait = null;
900
+ });
901
+ this.pullInvalidationWait = tracked;
902
+ return tracked;
903
+ }
904
+ pullInvalidationInProgress() {
905
+ return this.pullInvalidationWait !== null;
906
+ }
794
907
  async runPullSerialized(key, operation, background = false) {
795
- if (!key)
796
- return await this.executePullOperation(operation, background);
908
+ if (this.pullInvalidationInProgress()) {
909
+ throw new Error('pull invalidated');
910
+ }
911
+ if (!key) {
912
+ const releasePullWork = this.reservePullWork();
913
+ try {
914
+ return await this.executePullOperation(operation, background);
915
+ }
916
+ finally {
917
+ releasePullWork();
918
+ }
919
+ }
797
920
  const state = this.pullGateState(key);
798
921
  const namespace = this.pullScopeKey(key);
799
- const active = state.active?.namespace === namespace ? state.active : null;
800
- const queuedForeground = state.foreground.find((job) => job.namespace === namespace);
801
- const queuedBackground = state.background.find((job) => job.namespace === namespace);
802
- const existing = background
803
- ? (active ?? queuedForeground ?? queuedBackground)
804
- : (queuedForeground ?? (active && !active.background ? active : null) ?? queuedBackground);
922
+ const candidate = state.byKey.get(key);
923
+ const existing = candidate && !candidate.timedOut && !candidate.invalidated ? candidate : null;
805
924
  if (existing) {
806
925
  if (!background && existing.background && existing !== state.active) {
807
926
  const index = state.background.indexOf(existing);
@@ -812,16 +931,24 @@ export class RpcPipeline {
812
931
  }
813
932
  return await existing.promise;
814
933
  }
934
+ const workToken = Symbol('pull-work');
935
+ this.pullWorkTokens.add(workToken);
815
936
  let resolve;
816
937
  let reject;
938
+ let settledResolve;
817
939
  const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
940
+ const settled = new Promise((resolveSettled) => { settledResolve = resolveSettled; });
818
941
  void promise.catch(() => { });
819
942
  const job = {
820
943
  key, namespace, background, running: false, suspended: false, operation, promise, resolve, reject, timer: null,
821
944
  resume: null, resumeResolve: null,
822
945
  pullingStartedAt: 0,
823
946
  timedOut: false,
947
+ invalidated: false,
824
948
  cancel: null,
949
+ settled,
950
+ settledResolve,
951
+ workToken,
825
952
  };
826
953
  (background ? state.background : state.foreground).push(job);
827
954
  state.byKey.set(key, job);
@@ -830,18 +957,36 @@ export class RpcPipeline {
830
957
  return await promise;
831
958
  }
832
959
  async executePullOperation(operation, background) {
833
- if (background)
834
- return await this.runtime.client._withBackgroundRpc(operation);
835
- return await operation();
960
+ const client = this.runtime.client;
961
+ const hadPrevious = Object.prototype.hasOwnProperty.call(client, '_pullOperationGeneration');
962
+ const previous = client._pullOperationGeneration;
963
+ client._pullOperationGeneration = this.pullGeneration;
964
+ try {
965
+ if (background)
966
+ return await client._withBackgroundRpc(operation);
967
+ return await operation();
968
+ }
969
+ finally {
970
+ if (hadPrevious)
971
+ client._pullOperationGeneration = previous;
972
+ else
973
+ delete client._pullOperationGeneration;
974
+ }
975
+ }
976
+ pullOperationIsCurrent() {
977
+ const generation = this.runtime.client._pullOperationGeneration;
978
+ return generation === undefined || this.isPullGenerationCurrent(generation);
979
+ }
980
+ throwIfPullInvalidated() {
981
+ if (!this.pullOperationIsCurrent())
982
+ throw new Error('pull invalidated');
836
983
  }
837
984
  async yieldPullGate(key, nextKey, background) {
838
985
  if (!key)
839
986
  return;
840
987
  const state = this.pullGateState(key);
841
988
  const namespace = this.pullScopeKey(key);
842
- const job = state.active?.namespace === namespace
843
- ? state.active
844
- : [...state.foreground, ...state.background].find((candidate) => candidate.namespace === namespace);
989
+ const job = state.active?.key === key ? state.active : null;
845
990
  if (!job || state.active !== job)
846
991
  return;
847
992
  const replacementKey = String(nextKey || key);
@@ -858,8 +1003,11 @@ export class RpcPipeline {
858
1003
  state.active = null;
859
1004
  (job.background ? state.background : state.foreground).push(job);
860
1005
  this.drainPullGate(state);
861
- if (job.resume)
1006
+ if (job.resume) {
862
1007
  await job.resume;
1008
+ if (job.invalidated)
1009
+ throw new Error('pull invalidated');
1010
+ }
863
1011
  }
864
1012
  getPullLifecycle(namespace) {
865
1013
  const ns = this.pullScopeKey(namespace);
@@ -901,16 +1049,21 @@ export class RpcPipeline {
901
1049
  return;
902
1050
  }
903
1051
  job.running = true;
904
- void this.executePullOperation(job.operation, job.background).then(job.resolve, job.reject).finally(() => {
905
- if (job.timedOut)
906
- return;
1052
+ void this.executePullOperation(job.operation, job.background).then((value) => { if (!job.timedOut && !job.invalidated)
1053
+ job.resolve(value); }, (error) => { if (!job.timedOut && !job.invalidated)
1054
+ job.reject(error); }).finally(() => {
1055
+ this.pullWorkTokens.delete(job.workToken);
907
1056
  state.lifecycle.set(job.namespace, 'idle');
908
1057
  if (state.active === job)
909
1058
  state.active = null;
910
1059
  if (state.byKey.get(job.key) === job)
911
1060
  state.byKey.delete(job.key);
912
1061
  this.drainPullGate(state);
913
- this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
1062
+ job.settledResolve?.();
1063
+ job.settledResolve = null;
1064
+ if (!job.invalidated)
1065
+ this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
1066
+ this.runtime.client._delivery?.onPullWorkSettled?.();
914
1067
  });
915
1068
  }
916
1069
  armQueuedPullTimers(_state) { }
@@ -936,7 +1089,9 @@ export class RpcPipeline {
936
1089
  request = client._transport.call(method, payload);
937
1090
  return this.bindActivePullCancellation(method, payload, request);
938
1091
  };
939
- return await this.transportCallWithIdentityRecovery(invokeTransport, method, payload);
1092
+ const result = await this.transportCallWithIdentityRecovery(invokeTransport, method, payload);
1093
+ this.throwIfPullInvalidated();
1094
+ return result;
940
1095
  }
941
1096
  async transportCallWithIdentityRecovery(operation, method, params) {
942
1097
  try {
@@ -946,11 +1101,25 @@ export class RpcPipeline {
946
1101
  const recover = this.runtime.client._recoverIdentityAdmission;
947
1102
  if (typeof recover !== 'function' || !await recover.call(this.runtime.client, err, method, params))
948
1103
  throw err;
949
- return await operation();
1104
+ let lastError = err;
1105
+ for (const delay of IDENTITY_ADMISSION_RETRY_DELAYS_MS) {
1106
+ if (delay > 0)
1107
+ await new Promise((resolve) => setTimeout(resolve, delay));
1108
+ try {
1109
+ return await operation();
1110
+ }
1111
+ catch (retryError) {
1112
+ if (!sameIdentityAdmissionRejection(retryError, err))
1113
+ throw retryError;
1114
+ lastError = retryError;
1115
+ }
1116
+ }
1117
+ throw lastError;
950
1118
  }
951
1119
  }
952
1120
  async postprocessResult(method, params, result, options = {}) {
953
1121
  const client = this.runtime.client;
1122
+ this.throwIfPullInvalidated();
954
1123
  let next = result;
955
1124
  if ((method === 'group.send' || method === 'group.v2.send') && isJsonObject(next)) {
956
1125
  next = normalizeGroupMentionMode(next);