@opengeni/react 3.1.0 → 3.3.2-canary.1

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 (73) 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-YFKJ7VZH.js → chunk-7EAFGICR.js} +45 -11
  5. package/dist/chunk-7EAFGICR.js.map +1 -0
  6. package/dist/{chunk-W6NFJXSA.js → chunk-7KBJLTKS.js} +52 -12
  7. package/dist/chunk-7KBJLTKS.js.map +1 -0
  8. package/dist/{chunk-YJFGAFBD.js → chunk-E5KISKFG.js} +241 -97
  9. package/dist/chunk-E5KISKFG.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-X4A66EWZ.js → chunk-X5S426G2.js} +38 -44
  17. package/dist/chunk-X5S426G2.js.map +1 -0
  18. package/dist/{chunk-HQVCKCLG.js → chunk-YG4QEQF5.js} +354 -255
  19. package/dist/chunk-YG4QEQF5.js.map +1 -0
  20. package/dist/{chunk-XXWH2JLC.js → chunk-YVCHAQZY.js} +2 -2
  21. package/dist/{chunk-RGALCTWO.js → chunk-ZKNTK7IA.js} +13 -9
  22. package/dist/{chunk-RGALCTWO.js.map → chunk-ZKNTK7IA.js.map} +1 -1
  23. package/dist/{chunk-OAM4WAAM.js → chunk-ZPBTI3TE.js} +2 -2
  24. package/dist/components/fleet-tile.d.ts +2 -2
  25. package/dist/components/model-policy-picker.d.ts +9 -0
  26. package/dist/components/tip-follow.d.ts +16 -0
  27. package/dist/composer.js +6 -6
  28. package/dist/hooks/use-file-attachments.d.ts +2 -0
  29. package/dist/hooks/use-session-events.d.ts +3 -3
  30. package/dist/index.js +77 -69
  31. package/dist/index.js.map +1 -1
  32. package/dist/interaction.js +2 -2
  33. package/dist/machines.js +2 -2
  34. package/dist/model-policy.d.ts +2 -1
  35. package/dist/model-policy.js +1 -1
  36. package/dist/realtime.js +47 -8
  37. package/dist/realtime.js.map +1 -1
  38. package/dist/session-ui.js +4 -4
  39. package/dist/session.js +4 -4
  40. package/dist/timeline/entrance.d.ts +13 -1
  41. package/package.json +3 -2
  42. package/src/accounts.tsx +116 -14
  43. package/src/components/composer.tsx +15 -5
  44. package/src/components/fleet-tile.tsx +4 -15
  45. package/src/components/message-timeline.tsx +237 -101
  46. package/src/components/model-policy-picker.tsx +59 -13
  47. package/src/components/tip-follow.ts +52 -0
  48. package/src/components/tooltip.tsx +38 -5
  49. package/src/hooks/use-available-models.ts +20 -14
  50. package/src/hooks/use-composer.ts +49 -3
  51. package/src/hooks/use-file-attachments.ts +28 -2
  52. package/src/hooks/use-goal.ts +31 -7
  53. package/src/hooks/use-last-started-turn-policy.ts +11 -5
  54. package/src/hooks/use-session-events.ts +186 -61
  55. package/src/hooks/use-session-lineage.ts +20 -16
  56. package/src/hooks/use-session.ts +23 -19
  57. package/src/hooks/use-workspace-sessions.ts +31 -27
  58. package/src/lib/format.ts +8 -6
  59. package/src/model-policy.ts +45 -54
  60. package/src/realtime/realtime-control.tsx +55 -6
  61. package/src/timeline/entrance.tsx +28 -3
  62. package/src/timeline/projection.ts +4 -0
  63. package/styles/compiled.css +5 -0
  64. package/dist/chunk-HQVCKCLG.js.map +0 -1
  65. package/dist/chunk-LAKLF4PA.js.map +0 -1
  66. package/dist/chunk-MZ2G2XBA.js.map +0 -1
  67. package/dist/chunk-W6NFJXSA.js.map +0 -1
  68. package/dist/chunk-X4A66EWZ.js.map +0 -1
  69. package/dist/chunk-YFKJ7VZH.js.map +0 -1
  70. package/dist/chunk-YJFGAFBD.js.map +0 -1
  71. /package/dist/{browser-viewer-AEFHUYZK.js.map → browser-viewer-MKKFRRRV.js.map} +0 -0
  72. /package/dist/{chunk-XXWH2JLC.js.map → chunk-YVCHAQZY.js.map} +0 -0
  73. /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);
@@ -1141,6 +1245,16 @@ function abortError() {
1141
1245
  // src/hooks/use-file-attachments.ts
1142
1246
  import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
1143
1247
  var isImage = (file) => file.type.startsWith("image/");
1248
+ var fallbackAttachmentId = 0;
1249
+ function createAttachmentId() {
1250
+ const cryptoSource = globalThis.crypto;
1251
+ if (typeof cryptoSource?.randomUUID === "function") return cryptoSource.randomUUID();
1252
+ fallbackAttachmentId += 1;
1253
+ return `attachment:${Date.now().toString(36)}:${fallbackAttachmentId.toString(36)}`;
1254
+ }
1255
+ function secureContextRequiredErrorCode(error) {
1256
+ return typeof error === "object" && error !== null && "code" in error && error.code === "secure_context_required" ? error.code : void 0;
1257
+ }
1144
1258
  function useFileAttachments(options = {}) {
1145
1259
  const { client, workspaceId } = useEmbeddedFileAttachments(options);
1146
1260
  const pasteFilter = options.pasteFilter ?? isImage;
@@ -1228,6 +1342,7 @@ function useFileAttachments(options = {}) {
1228
1342
  name: asset.filename,
1229
1343
  contentType: asset.contentType,
1230
1344
  sizeBytes: asset.sizeBytes,
1345
+ errorCode: void 0,
1231
1346
  error: void 0
1232
1347
  } : attachment
1233
1348
  )
@@ -1239,6 +1354,7 @@ function useFileAttachments(options = {}) {
1239
1354
  (attachment) => attachment.id === id ? {
1240
1355
  ...attachment,
1241
1356
  status: "failed",
1357
+ errorCode: secureContextRequiredErrorCode(error),
1242
1358
  error: error instanceof Error ? error.message : String(error)
1243
1359
  } : attachment
1244
1360
  )
@@ -1250,7 +1366,7 @@ function useFileAttachments(options = {}) {
1250
1366
  const addFiles = useCallback3(
1251
1367
  (files) => {
1252
1368
  for (const file of files) {
1253
- const id = crypto.randomUUID();
1369
+ const id = createAttachmentId();
1254
1370
  sources.current.set(id, file);
1255
1371
  const previewUrl = isImage(file) ? URL.createObjectURL(file) : void 0;
1256
1372
  if (previewUrl) previewUrls.current.set(id, previewUrl);
@@ -1278,7 +1394,7 @@ function useFileAttachments(options = {}) {
1278
1394
  }
1279
1395
  setAttachments(
1280
1396
  (current) => current.map(
1281
- (attachment) => attachment.id === id ? { ...attachment, status: "uploading", error: void 0 } : attachment
1397
+ (attachment) => attachment.id === id ? { ...attachment, status: "uploading", errorCode: void 0, error: void 0 } : attachment
1282
1398
  )
1283
1399
  );
1284
1400
  startUpload(id, file);
@@ -1322,6 +1438,7 @@ function useFileAttachments(options = {}) {
1322
1438
  sizeBytes: file.sizeBytes,
1323
1439
  status: "ready",
1324
1440
  file,
1441
+ errorCode: void 0,
1325
1442
  error: void 0
1326
1443
  } : {
1327
1444
  id: `restored:${file.id}`,
@@ -1734,13 +1851,24 @@ function useGoal(sessionId, options = {}) {
1734
1851
  const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
1735
1852
  const generation = useRef5(0);
1736
1853
  const targetKeyRef = useRef5(null);
1854
+ const loadAbort = useRef5(null);
1855
+ const retireLoads = useCallback5(() => {
1856
+ generation.current += 1;
1857
+ loadAbort.current?.abort();
1858
+ loadAbort.current = null;
1859
+ }, []);
1737
1860
  const load = useCallback5(async () => {
1738
1861
  if (!sessionId) {
1739
1862
  return;
1740
1863
  }
1741
1864
  const ticket = ++generation.current;
1865
+ loadAbort.current?.abort();
1866
+ const controller = new AbortController();
1867
+ loadAbort.current = controller;
1742
1868
  try {
1743
- const fetched = await client.getGoal(workspaceId, sessionId);
1869
+ const fetched = await client.getGoal(workspaceId, sessionId, {
1870
+ signal: controller.signal
1871
+ });
1744
1872
  if (ticket === generation.current) {
1745
1873
  setGoal(fetched);
1746
1874
  setError(null);
@@ -1757,6 +1885,8 @@ function useGoal(sessionId, options = {}) {
1757
1885
  setError(cause instanceof Error ? cause : new Error(String(cause)));
1758
1886
  }
1759
1887
  setLoading(false);
1888
+ } finally {
1889
+ if (loadAbort.current === controller) loadAbort.current = null;
1760
1890
  }
1761
1891
  }, [client, workspaceId, sessionId]);
1762
1892
  useEffect5(() => {
@@ -1767,13 +1897,14 @@ function useGoal(sessionId, options = {}) {
1767
1897
  setError(null);
1768
1898
  }
1769
1899
  if (!enabled || !pageLive) {
1900
+ retireLoads();
1770
1901
  setLoading(false);
1771
1902
  return;
1772
1903
  }
1773
1904
  if (sharedFeed) {
1774
1905
  setLoading(false);
1775
1906
  return () => {
1776
- generation.current += 1;
1907
+ retireLoads();
1777
1908
  };
1778
1909
  }
1779
1910
  setLoading(true);
@@ -1781,7 +1912,7 @@ function useGoal(sessionId, options = {}) {
1781
1912
  if (pollIntervalMs === void 0 || pollIntervalMs <= 0) {
1782
1913
  void load();
1783
1914
  return () => {
1784
- generation.current += 1;
1915
+ retireLoads();
1785
1916
  };
1786
1917
  }
1787
1918
  let cancelled = false;
@@ -1797,9 +1928,18 @@ function useGoal(sessionId, options = {}) {
1797
1928
  return () => {
1798
1929
  cancelled = true;
1799
1930
  if (timer !== null) clearTimeout(timer);
1800
- generation.current += 1;
1931
+ retireLoads();
1801
1932
  };
1802
- }, [load, enabled, pageLive, workspaceId, sessionId, options.pollIntervalMs, sharedFeed]);
1933
+ }, [
1934
+ load,
1935
+ enabled,
1936
+ pageLive,
1937
+ workspaceId,
1938
+ sessionId,
1939
+ options.pollIntervalMs,
1940
+ retireLoads,
1941
+ sharedFeed
1942
+ ]);
1803
1943
  const scheduleRefresh = useDebouncedCallback(() => void load());
1804
1944
  useSessionEventTrigger(client, workspaceId, sessionId, isGoalRefreshEvent, scheduleRefresh, {
1805
1945
  enabled,
@@ -1842,11 +1982,11 @@ function useGoal(sessionId, options = {}) {
1842
1982
  return true;
1843
1983
  });
1844
1984
  if (ok) {
1845
- generation.current += 1;
1985
+ retireLoads();
1846
1986
  setGoal(null);
1847
1987
  setError(null);
1848
1988
  }
1849
- }, [client, workspaceId, sessionId, run]);
1989
+ }, [client, workspaceId, sessionId, run, retireLoads]);
1850
1990
  return {
1851
1991
  goal,
1852
1992
  isActive: goal?.status === "active",
@@ -2089,24 +2229,28 @@ function useSessionLineage(sessionId, options = {}) {
2089
2229
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
2090
2230
  const nextReadGeneration = useRef8(0);
2091
2231
  const beginRead = options.beginRead;
2092
- const load = useCallback8(async () => {
2093
- if (!sessionId) {
2232
+ const load = useCallback8(
2233
+ async (signal) => {
2234
+ if (!sessionId) {
2235
+ return {
2236
+ lineage: { ancestors: [], children: [], truncated: false },
2237
+ readGeneration: 0
2238
+ };
2239
+ }
2240
+ let readGeneration = 0;
2241
+ const lineage = await client.getSessionLineage(workspaceId, sessionId, {
2242
+ signal,
2243
+ onRequestStart: (sharedReadGeneration) => {
2244
+ readGeneration = sharedReadGeneration ?? beginRead?.() ?? ++nextReadGeneration.current;
2245
+ }
2246
+ });
2094
2247
  return {
2095
- lineage: { ancestors: [], children: [], truncated: false },
2096
- readGeneration: 0
2248
+ lineage,
2249
+ readGeneration
2097
2250
  };
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]);
2251
+ },
2252
+ [beginRead, client, workspaceId, sessionId]
2253
+ );
2110
2254
  const state = usePolledValue(load, { pollIntervalMs: options.pollIntervalMs, enabled });
2111
2255
  const refresh = state.refresh;
2112
2256
  useEffect7(() => {
@@ -2334,4 +2478,4 @@ export {
2334
2478
  isHumanInputEvent,
2335
2479
  useHumanInputRequests
2336
2480
  };
2337
- //# sourceMappingURL=chunk-YJFGAFBD.js.map
2481
+ //# sourceMappingURL=chunk-E5KISKFG.js.map