@opengeni/react 3.0.0-canary.0 → 3.3.2-canary.0

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 (63) hide show
  1. package/dist/accounts.js +87 -11
  2. package/dist/accounts.js.map +1 -1
  3. package/dist/{browser-viewer-AEFHUYZK.js → browser-viewer-MKKFRRRV.js} +3 -3
  4. package/dist/{chunk-W6NFJXSA.js → chunk-2DZVYHVO.js} +20 -4
  5. package/dist/chunk-2DZVYHVO.js.map +1 -0
  6. package/dist/{chunk-YJFGAFBD.js → chunk-3AGBGTRT.js} +226 -95
  7. package/dist/chunk-3AGBGTRT.js.map +1 -0
  8. package/dist/{chunk-YFKJ7VZH.js → chunk-7EAFGICR.js} +45 -11
  9. package/dist/chunk-7EAFGICR.js.map +1 -0
  10. package/dist/{chunk-MZ2G2XBA.js → chunk-IOLVN74D.js} +31 -4
  11. package/dist/chunk-IOLVN74D.js.map +1 -0
  12. package/dist/{chunk-D3UIAJKY.js → chunk-JAQ5K7W6.js} +3 -2
  13. package/dist/{chunk-D3UIAJKY.js.map → chunk-JAQ5K7W6.js.map} +1 -1
  14. package/dist/{chunk-LAKLF4PA.js → chunk-VTOREEXF.js} +8 -7
  15. package/dist/chunk-VTOREEXF.js.map +1 -0
  16. package/dist/{chunk-RGALCTWO.js → chunk-XYRDDSXF.js} +4 -4
  17. package/dist/{chunk-HQVCKCLG.js → chunk-YG4QEQF5.js} +354 -255
  18. package/dist/chunk-YG4QEQF5.js.map +1 -0
  19. package/dist/{chunk-XXWH2JLC.js → chunk-YVCHAQZY.js} +2 -2
  20. package/dist/{chunk-OAM4WAAM.js → chunk-ZPBTI3TE.js} +2 -2
  21. package/dist/components/fleet-tile.d.ts +2 -2
  22. package/dist/components/model-policy-picker.d.ts +9 -0
  23. package/dist/components/tip-follow.d.ts +16 -0
  24. package/dist/composer.js +5 -5
  25. package/dist/hooks/use-session-events.d.ts +3 -3
  26. package/dist/index.js +76 -68
  27. package/dist/index.js.map +1 -1
  28. package/dist/interaction.js +2 -2
  29. package/dist/machines.js +2 -2
  30. package/dist/realtime.js +46 -7
  31. package/dist/realtime.js.map +1 -1
  32. package/dist/session-ui.js +4 -4
  33. package/dist/session.js +4 -4
  34. package/dist/timeline/entrance.d.ts +13 -1
  35. package/package.json +3 -2
  36. package/src/accounts.tsx +116 -14
  37. package/src/components/fleet-tile.tsx +4 -15
  38. package/src/components/message-timeline.tsx +237 -101
  39. package/src/components/model-policy-picker.tsx +24 -3
  40. package/src/components/tip-follow.ts +52 -0
  41. package/src/components/tooltip.tsx +38 -5
  42. package/src/hooks/use-available-models.ts +20 -14
  43. package/src/hooks/use-composer.ts +49 -3
  44. package/src/hooks/use-goal.ts +31 -7
  45. package/src/hooks/use-last-started-turn-policy.ts +11 -5
  46. package/src/hooks/use-session-events.ts +186 -61
  47. package/src/hooks/use-session-lineage.ts +20 -16
  48. package/src/hooks/use-session.ts +23 -19
  49. package/src/hooks/use-workspace-sessions.ts +31 -27
  50. package/src/lib/format.ts +8 -6
  51. package/src/realtime/realtime-control.tsx +55 -6
  52. package/src/timeline/entrance.tsx +28 -3
  53. package/src/timeline/projection.ts +4 -0
  54. package/dist/chunk-HQVCKCLG.js.map +0 -1
  55. package/dist/chunk-LAKLF4PA.js.map +0 -1
  56. package/dist/chunk-MZ2G2XBA.js.map +0 -1
  57. package/dist/chunk-W6NFJXSA.js.map +0 -1
  58. package/dist/chunk-YFKJ7VZH.js.map +0 -1
  59. package/dist/chunk-YJFGAFBD.js.map +0 -1
  60. /package/dist/{browser-viewer-AEFHUYZK.js.map → browser-viewer-MKKFRRRV.js.map} +0 -0
  61. /package/dist/{chunk-RGALCTWO.js.map → chunk-XYRDDSXF.js.map} +0 -0
  62. /package/dist/{chunk-XXWH2JLC.js.map → chunk-YVCHAQZY.js.map} +0 -0
  63. /package/dist/{chunk-OAM4WAAM.js.map → chunk-ZPBTI3TE.js.map} +0 -0
@@ -4,7 +4,7 @@ import {
4
4
  groupTimeline,
5
5
  isActionableHumanInputRequest,
6
6
  sessionStatusFromEvents
7
- } from "./chunk-D3UIAJKY.js";
7
+ } from "./chunk-JAQ5K7W6.js";
8
8
  import {
9
9
  useDebouncedCallback,
10
10
  useMutationRunner,
@@ -102,24 +102,28 @@ function useSession(sessionId, options = {}) {
102
102
  const nextReadGeneration = useRef(0);
103
103
  const beginRead = options.beginRead;
104
104
  const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
105
- const load = useCallback(async () => {
106
- if (!sessionId) {
107
- return null;
108
- }
109
- let readGeneration2 = 0;
110
- const fetched = await client.getSession(workspaceId, sessionId, {
111
- fresh: true,
112
- onRequestStart: () => {
113
- readGeneration2 = beginRead?.() ?? ++nextReadGeneration.current;
105
+ const load = useCallback(
106
+ async (signal) => {
107
+ if (!sessionId) {
108
+ return null;
114
109
  }
115
- });
116
- setOverride(null);
117
- return {
118
- session: fetched,
119
- revision: ++nextReadRevision.current,
120
- readGeneration: readGeneration2
121
- };
122
- }, [beginRead, client, workspaceId, sessionId]);
110
+ let readGeneration2 = 0;
111
+ const fetched = await client.getSession(workspaceId, sessionId, {
112
+ fresh: true,
113
+ signal,
114
+ onRequestStart: () => {
115
+ readGeneration2 = beginRead?.() ?? ++nextReadGeneration.current;
116
+ }
117
+ });
118
+ setOverride(null);
119
+ return {
120
+ session: fetched,
121
+ revision: ++nextReadRevision.current,
122
+ readGeneration: readGeneration2
123
+ };
124
+ },
125
+ [beginRead, client, workspaceId, sessionId]
126
+ );
123
127
  const { data, loading, error, refresh } = usePolledValue(load, {
124
128
  pollIntervalMs: options.pollIntervalMs,
125
129
  enabled
@@ -188,7 +192,7 @@ function useSession(sessionId, options = {}) {
188
192
  }
189
193
 
190
194
  // src/hooks/use-session-events.ts
191
- import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
195
+ import { startTransition, useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
192
196
  var INITIAL_TAIL_PAGE_SIZE = 1e3;
193
197
  var OLDER_PAGE_SIZE = 5e3;
194
198
  var NEWER_PAGE_SIZE = 5e3;
@@ -357,7 +361,10 @@ function useSessionEvents(sessionId, options = {}) {
357
361
  batchResumeSequence
358
362
  );
359
363
  }
360
- observeSessionStatus(batch, sessionStatusRef, setSessionStatusProjection);
364
+ const status = observeSessionStatus(batch, sessionStatusRef);
365
+ if (status !== void 0) {
366
+ setSessionStatusProjection(status);
367
+ }
361
368
  const current = eventWindowRef.current;
362
369
  const next = boundBrowserSessionEventWindow([...current.events, ...batch]);
363
370
  const retained = {
@@ -367,7 +374,7 @@ function useSessionEvents(sessionId, options = {}) {
367
374
  eventWindowRef.current = retained;
368
375
  setEventWindow(retained);
369
376
  oldestSequenceRef.current = retained.events[0]?.sequence ?? null;
370
- newestSequenceRef.current = retained.events.at(-1)?.sequence ?? null;
377
+ newestSequenceRef.current = maxResumeSequenceOrNull(retained.events);
371
378
  if (retained.truncated) {
372
379
  hasOlderRef.current = true;
373
380
  setHasOlder(true);
@@ -391,17 +398,20 @@ function useSessionEvents(sessionId, options = {}) {
391
398
  if (!isCurrent()) {
392
399
  return;
393
400
  }
394
- observeSessionStatus(window.events, sessionStatusRef, setSessionStatusProjection);
401
+ const status = observeSessionStatus(window.events, sessionStatusRef);
395
402
  const retained = boundBrowserSessionEventWindow(window.events);
396
403
  eventWindowRef.current = retained;
397
- setEventWindow(retained);
398
404
  oldestSequenceRef.current = retained.events[0]?.sequence ?? window.oldestSequence;
399
- newestSequenceRef.current = retained.events.at(-1)?.sequence ?? window.newestSequence;
405
+ newestSequenceRef.current = maxResumeSequenceOrNull(retained.events) ?? window.newestSequence;
400
406
  hasOlderRef.current = window.hasOlder || retained.truncated;
401
407
  hasNewerRef.current = false;
402
408
  lastSequenceRef.current = window.newestSequence;
403
409
  streamResumeSequenceRef.current = window.newestSequence;
404
410
  initialWindowLoadedRef.current = true;
411
+ if (status !== void 0) {
412
+ setSessionStatusProjection(status);
413
+ }
414
+ setEventWindow(retained);
405
415
  setHasOlder(window.hasOlder || retained.truncated);
406
416
  setHasNewer(false);
407
417
  setInitialLoading(false);
@@ -450,6 +460,7 @@ function useSessionEvents(sessionId, options = {}) {
450
460
  flush();
451
461
  setError(cause instanceof Error ? cause : new Error(String(cause)));
452
462
  setConnectionState("error");
463
+ setInitialLoading(false);
453
464
  }
454
465
  }
455
466
  })();
@@ -493,6 +504,7 @@ function useSessionEvents(sessionId, options = {}) {
493
504
  const generation = generationRef.current;
494
505
  loadingOlderRef.current = true;
495
506
  setLoadingOlder(true);
507
+ let published = false;
496
508
  try {
497
509
  const window = await loadEventWindow(client, workspaceId, sessionId, {
498
510
  before,
@@ -512,7 +524,7 @@ function useSessionEvents(sessionId, options = {}) {
512
524
  const current = eventWindowRef.current;
513
525
  assertPrependOrder(current.events, window.events);
514
526
  streamAbortRef.current?.abort();
515
- observeSessionStatus(window.events, sessionStatusRef, setSessionStatusProjection);
527
+ const status = observeSessionStatus(window.events, sessionStatusRef);
516
528
  const next = boundBrowserSessionEventWindow([...window.events, ...current.events], {
517
529
  direction: "oldest"
518
530
  });
@@ -524,28 +536,54 @@ function useSessionEvents(sessionId, options = {}) {
524
536
  if (retainedOldest === null || retainedOldest >= before) {
525
537
  throw new Error("@opengeni/react: loadOlder made no durable sequence progress");
526
538
  }
527
- const retainedNewest = retained.events.at(-1)?.sequence ?? null;
539
+ const retainedNewest = maxResumeSequenceOrNull(retained.events);
540
+ const previousNewest = maxResumeSequenceOrNull(current.events);
528
541
  eventWindowRef.current = retained;
529
542
  markCommitted();
530
- setEventWindow(retained);
531
543
  oldestSequenceRef.current = retainedOldest;
532
544
  newestSequenceRef.current = retainedNewest;
533
545
  streamResumeSequenceRef.current = maxResumeSequence(retained.events);
534
546
  const olderStillAvailable = window.hasOlder;
535
547
  hasOlderRef.current = olderStillAvailable;
536
- setHasOlder(olderStillAvailable);
537
- if (viewModeRef.current === "history") {
548
+ const evictedLiveTail = previousNewest !== null && retainedNewest !== null && retainedNewest < previousNewest;
549
+ let newer = null;
550
+ let enterHistory = false;
551
+ let reconnectLive = false;
552
+ if (viewModeRef.current === "history" || evictedLiveTail) {
538
553
  const highWater = lastSequenceRef.current;
539
- const newer = retainedNewest !== null && (retainedNewest < highWater || retained.truncated);
554
+ newer = retainedNewest !== null && (retainedNewest < highWater || retained.truncated);
540
555
  hasNewerRef.current = newer;
541
- setHasNewer(newer);
556
+ if (evictedLiveTail) {
557
+ viewModeRef.current = "history";
558
+ enterHistory = true;
559
+ }
542
560
  } else {
543
- setStreamEpoch((epoch) => epoch + 1);
561
+ reconnectLive = true;
544
562
  }
563
+ loadingOlderRef.current = false;
564
+ startTransition(() => {
565
+ if (status !== void 0) {
566
+ setSessionStatusProjection(status);
567
+ }
568
+ setEventWindow(retained);
569
+ setHasOlder(olderStillAvailable);
570
+ if (newer !== null) {
571
+ setHasNewer(newer);
572
+ }
573
+ if (enterHistory) {
574
+ setViewMode("history");
575
+ } else if (reconnectLive) {
576
+ setStreamEpoch((epoch) => epoch + 1);
577
+ }
578
+ setLoadingOlder(false);
579
+ });
580
+ published = true;
545
581
  return olderStillAvailable;
546
582
  } finally {
547
- loadingOlderRef.current = false;
548
- setLoadingOlder(false);
583
+ if (!published) {
584
+ loadingOlderRef.current = false;
585
+ setLoadingOlder(false);
586
+ }
549
587
  }
550
588
  }),
551
589
  [client, workspaceId, sessionId]
@@ -557,6 +595,7 @@ function useSessionEvents(sessionId, options = {}) {
557
595
  const generation = generationRef.current;
558
596
  loadingOldestRef.current = true;
559
597
  setLoadingOldest(true);
598
+ let published = false;
560
599
  try {
561
600
  const window = await loadForwardEventWindow(client, workspaceId, sessionId, {
562
601
  after: 0,
@@ -573,28 +612,38 @@ function useSessionEvents(sessionId, options = {}) {
573
612
  return false;
574
613
  }
575
614
  streamAbortRef.current?.abort();
576
- observeSessionStatus(window.events, sessionStatusRef, setSessionStatusProjection);
615
+ const status = observeSessionStatus(window.events, sessionStatusRef);
577
616
  const retained = boundBrowserSessionEventWindow(window.events, { direction: "oldest" });
578
617
  const retainedOldest = retained.events[0]?.sequence ?? null;
579
- const retainedNewest = retained.events.at(-1)?.sequence ?? null;
618
+ const retainedNewest = maxResumeSequenceOrNull(retained.events);
580
619
  eventWindowRef.current = retained;
581
- setEventWindow(retained);
582
620
  oldestSequenceRef.current = retainedOldest;
583
621
  newestSequenceRef.current = retainedNewest;
584
622
  hasOlderRef.current = false;
585
- setHasOlder(false);
586
623
  const highWater = Math.max(lastSequenceRef.current, retainedNewest ?? 0);
587
624
  lastSequenceRef.current = highWater;
588
625
  const newer = window.hasNewer || retained.truncated || retainedNewest !== null && retainedNewest < highWater;
589
626
  hasNewerRef.current = newer;
590
- setHasNewer(newer);
591
627
  initialWindowLoadedRef.current = true;
592
628
  viewModeRef.current = "history";
593
- setViewMode("history");
629
+ loadingOldestRef.current = false;
630
+ startTransition(() => {
631
+ if (status !== void 0) {
632
+ setSessionStatusProjection(status);
633
+ }
634
+ setEventWindow(retained);
635
+ setHasOlder(false);
636
+ setHasNewer(newer);
637
+ setViewMode("history");
638
+ setLoadingOldest(false);
639
+ });
640
+ published = true;
594
641
  return newer;
595
642
  } finally {
596
- loadingOldestRef.current = false;
597
- setLoadingOldest(false);
643
+ if (!published) {
644
+ loadingOldestRef.current = false;
645
+ setLoadingOldest(false);
646
+ }
598
647
  }
599
648
  }, [client, workspaceId, sessionId]);
600
649
  const loadNewer = useCallback2(async () => {
@@ -610,6 +659,7 @@ function useSessionEvents(sessionId, options = {}) {
610
659
  const generation = generationRef.current;
611
660
  loadingNewerRef.current = true;
612
661
  setLoadingNewer(true);
662
+ let published = false;
613
663
  try {
614
664
  const window = await loadForwardEventWindow(client, workspaceId, sessionId, {
615
665
  after: afterSequence,
@@ -631,7 +681,7 @@ function useSessionEvents(sessionId, options = {}) {
631
681
  }
632
682
  const current = eventWindowRef.current;
633
683
  assertAppendOrder(current.events, window.events);
634
- observeSessionStatus(window.events, sessionStatusRef, setSessionStatusProjection);
684
+ const status = observeSessionStatus(window.events, sessionStatusRef);
635
685
  const previousOldest = current.events[0]?.sequence ?? null;
636
686
  const next = boundBrowserSessionEventWindow([...current.events, ...window.events], {
637
687
  direction: "newest"
@@ -641,33 +691,48 @@ function useSessionEvents(sessionId, options = {}) {
641
691
  truncated: current.truncated || next.truncated || window.hasNewer
642
692
  };
643
693
  const retainedOldest = retained.events[0]?.sequence ?? null;
644
- const retainedNewest = retained.events.at(-1)?.sequence ?? null;
694
+ const retainedNewest = maxResumeSequenceOrNull(retained.events);
645
695
  if (retainedNewest === null || retainedNewest <= afterSequence) {
646
696
  throw new Error("@opengeni/react: loadNewer made no durable sequence progress");
647
697
  }
648
698
  eventWindowRef.current = retained;
649
- setEventWindow(retained);
650
699
  oldestSequenceRef.current = retainedOldest;
651
700
  newestSequenceRef.current = retainedNewest;
652
701
  if (retainedOldest !== null && (previousOldest === null || retainedOldest > previousOldest || retained.truncated && retained.events[0]?.type !== "session.created" && retainedOldest > 1)) {
653
702
  hasOlderRef.current = true;
654
- setHasOlder(true);
655
703
  }
704
+ const rearmedOlder = hasOlderRef.current;
656
705
  const highWater = Math.max(lastSequenceRef.current, retainedNewest);
657
706
  lastSequenceRef.current = highWater;
658
707
  const newer = window.hasNewer || retainedNewest < highWater;
659
708
  hasNewerRef.current = newer;
660
- setHasNewer(newer);
709
+ let resumeLive = false;
661
710
  if (!newer) {
662
711
  streamResumeSequenceRef.current = retainedNewest;
663
712
  viewModeRef.current = "live";
664
- setViewMode("live");
665
- setStreamEpoch((epoch) => epoch + 1);
713
+ resumeLive = true;
666
714
  }
715
+ loadingNewerRef.current = false;
716
+ startTransition(() => {
717
+ if (status !== void 0) {
718
+ setSessionStatusProjection(status);
719
+ }
720
+ setEventWindow(retained);
721
+ setHasOlder(rearmedOlder);
722
+ setHasNewer(newer);
723
+ if (resumeLive) {
724
+ setViewMode("live");
725
+ setStreamEpoch((epoch) => epoch + 1);
726
+ }
727
+ setLoadingNewer(false);
728
+ });
729
+ published = true;
667
730
  return newer;
668
731
  } finally {
669
- loadingNewerRef.current = false;
670
- setLoadingNewer(false);
732
+ if (!published) {
733
+ loadingNewerRef.current = false;
734
+ setLoadingNewer(false);
735
+ }
671
736
  }
672
737
  }, [client, workspaceId, sessionId]);
673
738
  const jumpToLatest = useCallback2(async () => {
@@ -676,26 +741,34 @@ function useSessionEvents(sessionId, options = {}) {
676
741
  }
677
742
  loadingLatestRef.current = true;
678
743
  setLoadingLatest(true);
744
+ let published = false;
679
745
  try {
680
746
  streamAbortRef.current?.abort();
681
747
  hasNewerRef.current = false;
682
- setHasNewer(false);
683
748
  hasOlderRef.current = false;
684
- setHasOlder(false);
685
749
  newestSequenceRef.current = null;
686
750
  oldestSequenceRef.current = null;
687
751
  eventWindowRef.current = EMPTY_EVENT_WINDOW;
688
- setEventWindow(EMPTY_EVENT_WINDOW);
689
752
  initialWindowLoadedRef.current = false;
690
753
  streamResumeSequenceRef.current = after;
691
- setInitialLoading(true);
692
754
  viewModeRef.current = "live";
693
- setViewMode("live");
694
- setStreamEpoch((epoch) => epoch + 1);
755
+ loadingLatestRef.current = false;
756
+ startTransition(() => {
757
+ setHasNewer(false);
758
+ setHasOlder(false);
759
+ setEventWindow(EMPTY_EVENT_WINDOW);
760
+ setInitialLoading(true);
761
+ setViewMode("live");
762
+ setStreamEpoch((epoch) => epoch + 1);
763
+ setLoadingLatest(false);
764
+ });
765
+ published = true;
695
766
  await Promise.resolve();
696
767
  } finally {
697
- loadingLatestRef.current = false;
698
- setLoadingLatest(false);
768
+ if (!published) {
769
+ loadingLatestRef.current = false;
770
+ setLoadingLatest(false);
771
+ }
699
772
  }
700
773
  }, [after, sessionId]);
701
774
  const identityMatches = stateStreamKey === streamKey;
@@ -925,7 +998,7 @@ function browserUtf8Bytes(value) {
925
998
  function isBrowserUtf8Continuation(value) {
926
999
  return (value & 192) === 128;
927
1000
  }
928
- function observeSessionStatus(events, ref, setStatus) {
1001
+ function observeSessionStatus(events, ref) {
929
1002
  let latest = null;
930
1003
  for (const event of events) {
931
1004
  const status = sessionStatusFromEvents([event]);
@@ -933,9 +1006,9 @@ function observeSessionStatus(events, ref, setStatus) {
933
1006
  latest = { sequence: event.sequence, status };
934
1007
  }
935
1008
  }
936
- if (!latest || latest.sequence < ref.current.sequence) return;
1009
+ if (!latest || latest.sequence < ref.current.sequence) return void 0;
937
1010
  ref.current = latest;
938
- setStatus(latest.status);
1011
+ return latest.status;
939
1012
  }
940
1013
  async function loadEventWindow(client, workspaceId, sessionId, options) {
941
1014
  let cursor = options.before;
@@ -989,24 +1062,28 @@ async function loadEventWindow(client, workspaceId, sessionId, options) {
989
1062
  buffer = buffer.slice(boundary);
990
1063
  }
991
1064
  }
1065
+ const densityWindow = trimBackwardToGroupTarget(buffer, options.targetGroups);
1066
+ buffer = densityWindow.events;
992
1067
  const oldest = buffer[0] ?? null;
993
1068
  const newest = buffer[buffer.length - 1] ?? null;
994
1069
  return {
995
1070
  events: buffer,
996
1071
  oldestSequence: oldest?.sequence ?? null,
997
1072
  newestSequence: newest ? maxResumeSequence(buffer) : 0,
998
- hasOlder: buffer.length > 0 && !reachedStart && oldest?.type !== "session.created"
1073
+ hasOlder: buffer.length > 0 && (densityWindow.trimmed || !reachedStart && oldest?.type !== "session.created")
999
1074
  };
1000
1075
  }
1001
1076
  function findBoundaryIndex(events) {
1002
1077
  for (let index = 0; index < events.length; index += 1) {
1003
- const type = events[index].type;
1004
- if (type === "session.created" || type === "user.message") {
1078
+ if (isTurnBoundary(events[index])) {
1005
1079
  return index;
1006
1080
  }
1007
1081
  }
1008
1082
  return -1;
1009
1083
  }
1084
+ function isTurnBoundary(event) {
1085
+ return event.type === "session.created" || event.type === "user.message";
1086
+ }
1010
1087
  async function loadForwardEventWindow(client, workspaceId, sessionId, options) {
1011
1088
  let cursor = options.after;
1012
1089
  let buffer = [];
@@ -1027,21 +1104,45 @@ async function loadForwardEventWindow(client, workspaceId, sessionId, options) {
1027
1104
  }
1028
1105
  assertAscending(page);
1029
1106
  buffer = [...buffer, ...page];
1030
- cursor = page[page.length - 1].sequence;
1031
- if (page.length < options.pageSize) {
1032
- reachedEnd = true;
1033
- break;
1034
- }
1107
+ cursor = maxResumeSequence(page);
1035
1108
  }
1109
+ const densityWindow = trimForwardToGroupTarget(buffer, options.targetGroups);
1110
+ buffer = densityWindow.events;
1036
1111
  const oldest = buffer[0] ?? null;
1037
1112
  const newest = buffer[buffer.length - 1] ?? null;
1038
1113
  return {
1039
1114
  events: buffer,
1040
1115
  oldestSequence: oldest?.sequence ?? null,
1041
1116
  newestSequence: newest ? maxResumeSequence(buffer) : 0,
1042
- hasNewer: buffer.length > 0 && !reachedEnd
1117
+ hasNewer: buffer.length > 0 && (densityWindow.trimmed || !reachedEnd)
1043
1118
  };
1044
1119
  }
1120
+ function trimBackwardToGroupTarget(events, targetGroups) {
1121
+ if (!Number.isFinite(targetGroups) || targetGroups <= 0 || events.length === 0) {
1122
+ return { events, trimmed: false };
1123
+ }
1124
+ for (let index = events.length - 1; index > 0; index -= 1) {
1125
+ if (!isTurnBoundary(events[index])) continue;
1126
+ const candidate = events.slice(index);
1127
+ if (groupCount(candidate) >= targetGroups) {
1128
+ return { events: candidate, trimmed: true };
1129
+ }
1130
+ }
1131
+ return { events, trimmed: false };
1132
+ }
1133
+ function trimForwardToGroupTarget(events, targetGroups) {
1134
+ if (!Number.isFinite(targetGroups) || targetGroups <= 0 || events.length === 0) {
1135
+ return { events, trimmed: false };
1136
+ }
1137
+ for (let index = 1; index < events.length; index += 1) {
1138
+ if (!isTurnBoundary(events[index])) continue;
1139
+ const candidate = events.slice(0, index);
1140
+ if (groupCount(candidate) >= targetGroups) {
1141
+ return { events: candidate, trimmed: true };
1142
+ }
1143
+ }
1144
+ return { events, trimmed: false };
1145
+ }
1045
1146
  async function loadPreviousPage(client, workspaceId, sessionId, before, options) {
1046
1147
  if (options.signal?.aborted) {
1047
1148
  throw abortError();
@@ -1097,6 +1198,9 @@ function isLogStart(event) {
1097
1198
  function maxResumeSequence(events) {
1098
1199
  return events.reduce((max, event) => Math.max(max, eventResumeSequence(event)), 0);
1099
1200
  }
1201
+ function maxResumeSequenceOrNull(events) {
1202
+ return events.length > 0 ? maxResumeSequence(events) : null;
1203
+ }
1100
1204
  function eventResumeSequence(event) {
1101
1205
  const payload = asRecord(event.payload);
1102
1206
  const coalescedUntil = Number(payload.coalescedUntil);
@@ -1734,13 +1838,24 @@ function useGoal(sessionId, options = {}) {
1734
1838
  const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
1735
1839
  const generation = useRef5(0);
1736
1840
  const targetKeyRef = useRef5(null);
1841
+ const loadAbort = useRef5(null);
1842
+ const retireLoads = useCallback5(() => {
1843
+ generation.current += 1;
1844
+ loadAbort.current?.abort();
1845
+ loadAbort.current = null;
1846
+ }, []);
1737
1847
  const load = useCallback5(async () => {
1738
1848
  if (!sessionId) {
1739
1849
  return;
1740
1850
  }
1741
1851
  const ticket = ++generation.current;
1852
+ loadAbort.current?.abort();
1853
+ const controller = new AbortController();
1854
+ loadAbort.current = controller;
1742
1855
  try {
1743
- const fetched = await client.getGoal(workspaceId, sessionId);
1856
+ const fetched = await client.getGoal(workspaceId, sessionId, {
1857
+ signal: controller.signal
1858
+ });
1744
1859
  if (ticket === generation.current) {
1745
1860
  setGoal(fetched);
1746
1861
  setError(null);
@@ -1757,6 +1872,8 @@ function useGoal(sessionId, options = {}) {
1757
1872
  setError(cause instanceof Error ? cause : new Error(String(cause)));
1758
1873
  }
1759
1874
  setLoading(false);
1875
+ } finally {
1876
+ if (loadAbort.current === controller) loadAbort.current = null;
1760
1877
  }
1761
1878
  }, [client, workspaceId, sessionId]);
1762
1879
  useEffect5(() => {
@@ -1767,13 +1884,14 @@ function useGoal(sessionId, options = {}) {
1767
1884
  setError(null);
1768
1885
  }
1769
1886
  if (!enabled || !pageLive) {
1887
+ retireLoads();
1770
1888
  setLoading(false);
1771
1889
  return;
1772
1890
  }
1773
1891
  if (sharedFeed) {
1774
1892
  setLoading(false);
1775
1893
  return () => {
1776
- generation.current += 1;
1894
+ retireLoads();
1777
1895
  };
1778
1896
  }
1779
1897
  setLoading(true);
@@ -1781,7 +1899,7 @@ function useGoal(sessionId, options = {}) {
1781
1899
  if (pollIntervalMs === void 0 || pollIntervalMs <= 0) {
1782
1900
  void load();
1783
1901
  return () => {
1784
- generation.current += 1;
1902
+ retireLoads();
1785
1903
  };
1786
1904
  }
1787
1905
  let cancelled = false;
@@ -1797,9 +1915,18 @@ function useGoal(sessionId, options = {}) {
1797
1915
  return () => {
1798
1916
  cancelled = true;
1799
1917
  if (timer !== null) clearTimeout(timer);
1800
- generation.current += 1;
1918
+ retireLoads();
1801
1919
  };
1802
- }, [load, enabled, pageLive, workspaceId, sessionId, options.pollIntervalMs, sharedFeed]);
1920
+ }, [
1921
+ load,
1922
+ enabled,
1923
+ pageLive,
1924
+ workspaceId,
1925
+ sessionId,
1926
+ options.pollIntervalMs,
1927
+ retireLoads,
1928
+ sharedFeed
1929
+ ]);
1803
1930
  const scheduleRefresh = useDebouncedCallback(() => void load());
1804
1931
  useSessionEventTrigger(client, workspaceId, sessionId, isGoalRefreshEvent, scheduleRefresh, {
1805
1932
  enabled,
@@ -1842,11 +1969,11 @@ function useGoal(sessionId, options = {}) {
1842
1969
  return true;
1843
1970
  });
1844
1971
  if (ok) {
1845
- generation.current += 1;
1972
+ retireLoads();
1846
1973
  setGoal(null);
1847
1974
  setError(null);
1848
1975
  }
1849
- }, [client, workspaceId, sessionId, run]);
1976
+ }, [client, workspaceId, sessionId, run, retireLoads]);
1850
1977
  return {
1851
1978
  goal,
1852
1979
  isActive: goal?.status === "active",
@@ -2089,24 +2216,28 @@ function useSessionLineage(sessionId, options = {}) {
2089
2216
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
2090
2217
  const nextReadGeneration = useRef8(0);
2091
2218
  const beginRead = options.beginRead;
2092
- const load = useCallback8(async () => {
2093
- if (!sessionId) {
2219
+ const load = useCallback8(
2220
+ async (signal) => {
2221
+ if (!sessionId) {
2222
+ return {
2223
+ lineage: { ancestors: [], children: [], truncated: false },
2224
+ readGeneration: 0
2225
+ };
2226
+ }
2227
+ let readGeneration = 0;
2228
+ const lineage = await client.getSessionLineage(workspaceId, sessionId, {
2229
+ signal,
2230
+ onRequestStart: (sharedReadGeneration) => {
2231
+ readGeneration = sharedReadGeneration ?? beginRead?.() ?? ++nextReadGeneration.current;
2232
+ }
2233
+ });
2094
2234
  return {
2095
- lineage: { ancestors: [], children: [], truncated: false },
2096
- readGeneration: 0
2235
+ lineage,
2236
+ readGeneration
2097
2237
  };
2098
- }
2099
- let readGeneration = 0;
2100
- const lineage = await client.getSessionLineage(workspaceId, sessionId, {
2101
- onRequestStart: (sharedReadGeneration) => {
2102
- readGeneration = sharedReadGeneration ?? beginRead?.() ?? ++nextReadGeneration.current;
2103
- }
2104
- });
2105
- return {
2106
- lineage,
2107
- readGeneration
2108
- };
2109
- }, [beginRead, client, workspaceId, sessionId]);
2238
+ },
2239
+ [beginRead, client, workspaceId, sessionId]
2240
+ );
2110
2241
  const state = usePolledValue(load, { pollIntervalMs: options.pollIntervalMs, enabled });
2111
2242
  const refresh = state.refresh;
2112
2243
  useEffect7(() => {
@@ -2334,4 +2465,4 @@ export {
2334
2465
  isHumanInputEvent,
2335
2466
  useHumanInputRequests
2336
2467
  };
2337
- //# sourceMappingURL=chunk-YJFGAFBD.js.map
2468
+ //# sourceMappingURL=chunk-3AGBGTRT.js.map