@opengeni/react 2.3.0-canary.0 → 2.5.0-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 (46) hide show
  1. package/README.md +9 -2
  2. package/dist/{chunk-LS4POUTY.js → chunk-6H6S5V4Z.js} +250 -120
  3. package/dist/chunk-6H6S5V4Z.js.map +1 -0
  4. package/dist/{chunk-SE4W4WQ3.js → chunk-ATQJEWCA.js} +154 -114
  5. package/dist/chunk-ATQJEWCA.js.map +1 -0
  6. package/dist/{chunk-LTCHJ4MS.js → chunk-KSDXER4X.js} +21 -7
  7. package/dist/chunk-KSDXER4X.js.map +1 -0
  8. package/dist/{chunk-RSXJN73N.js → chunk-MXJGO7LE.js} +34 -1
  9. package/dist/chunk-MXJGO7LE.js.map +1 -0
  10. package/dist/{chunk-JOAVA2TW.js → chunk-T7VAYG2I.js} +8 -7
  11. package/dist/{chunk-JOAVA2TW.js.map → chunk-T7VAYG2I.js.map} +1 -1
  12. package/dist/components/composer.d.ts +1 -1
  13. package/dist/components/fleet-tile.d.ts +1 -1
  14. package/dist/components/message-timeline.d.ts +7 -2
  15. package/dist/composer.js +2 -2
  16. package/dist/hooks/use-session-events.d.ts +2 -1
  17. package/dist/hooks/use-session-lineage.d.ts +4 -0
  18. package/dist/hooks/use-session.d.ts +6 -0
  19. package/dist/hooks/use-workspace-sessions.d.ts +6 -0
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.js +41 -19
  22. package/dist/index.js.map +1 -1
  23. package/dist/older-history.d.ts +29 -0
  24. package/dist/session-ui.d.ts +2 -0
  25. package/dist/session-ui.js +5 -2
  26. package/dist/session.d.ts +2 -0
  27. package/dist/session.js +5 -3
  28. package/package.json +2 -2
  29. package/src/components/composer.tsx +19 -5
  30. package/src/components/fleet-tile.tsx +7 -5
  31. package/src/components/message-timeline.tsx +399 -160
  32. package/src/components/tip-follow.ts +18 -7
  33. package/src/hooks/use-composer.ts +20 -6
  34. package/src/hooks/use-session-events.ts +84 -76
  35. package/src/hooks/use-session-lineage.ts +26 -8
  36. package/src/hooks/use-session.ts +35 -8
  37. package/src/hooks/use-workspace-sessions.ts +38 -13
  38. package/src/index.ts +2 -0
  39. package/src/older-history.ts +69 -0
  40. package/src/session-ui.ts +2 -0
  41. package/src/session.ts +2 -0
  42. package/src/timeline/activity-rail.tsx +1 -0
  43. package/dist/chunk-LS4POUTY.js.map +0 -1
  44. package/dist/chunk-LTCHJ4MS.js.map +0 -1
  45. package/dist/chunk-RSXJN73N.js.map +0 -1
  46. package/dist/chunk-SE4W4WQ3.js.map +0 -1
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  buildTimeline,
3
+ createOlderHistoryLoadReceipt,
3
4
  groupTimeline,
4
5
  sessionStatusFromEvents
5
- } from "./chunk-RSXJN73N.js";
6
+ } from "./chunk-MXJGO7LE.js";
6
7
  import {
7
8
  useDebouncedCallback,
8
9
  useMutationRunner,
@@ -21,7 +22,7 @@ import {
21
22
  } from "./chunk-NEXLLTP4.js";
22
23
 
23
24
  // src/hooks/use-session.ts
24
- import { useCallback, useEffect, useMemo, useState } from "react";
25
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
25
26
  function isTitleEvent(event) {
26
27
  return event.type === "session.title_set";
27
28
  }
@@ -29,15 +30,28 @@ function useSession(sessionId, options = {}) {
29
30
  const { client, workspaceId, workspaceControlEvent, registerSessionReconciler } = useEmbeddedSessionRead(options);
30
31
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
31
32
  const [override, setOverride] = useState(null);
33
+ const nextReadRevision = useRef(0);
34
+ const nextReadGeneration = useRef(0);
35
+ const beginRead = options.beginRead;
32
36
  const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
33
37
  const load = useCallback(async () => {
34
38
  if (!sessionId) {
35
39
  return null;
36
40
  }
37
- const fetched = await client.getSession(workspaceId, sessionId);
41
+ let readGeneration2 = 0;
42
+ const fetched = await client.getSession(workspaceId, sessionId, {
43
+ fresh: true,
44
+ onRequestStart: () => {
45
+ readGeneration2 = beginRead?.() ?? ++nextReadGeneration.current;
46
+ }
47
+ });
38
48
  setOverride(null);
39
- return fetched;
40
- }, [client, workspaceId, sessionId]);
49
+ return {
50
+ session: fetched,
51
+ revision: ++nextReadRevision.current,
52
+ readGeneration: readGeneration2
53
+ };
54
+ }, [beginRead, client, workspaceId, sessionId]);
41
55
  const { data, loading, error, refresh } = usePolledValue(load, {
42
56
  pollIntervalMs: options.pollIntervalMs,
43
57
  enabled
@@ -49,13 +63,18 @@ function useSession(sessionId, options = {}) {
49
63
  if (!sessionId || !enabled) return;
50
64
  return registerSessionReconciler(sessionId, "session", refresh);
51
65
  }, [enabled, refresh, registerSessionReconciler, sessionId]);
52
- const base = data ?? null;
66
+ const base = data?.session ?? null;
67
+ const readRevision = data?.revision ?? 0;
68
+ const readGeneration = data?.readGeneration ?? 0;
53
69
  const session = useMemo(
54
70
  () => base && override && override.id === base.id ? { ...base, title: override.title, titleSource: override.titleSource } : base,
55
71
  [base, override]
56
72
  );
57
73
  const onTitleEvent = useCallback(
58
74
  (event) => {
75
+ if (!base || event.sequence <= base.lastSequence) {
76
+ return;
77
+ }
59
78
  const payload = event.payload ?? {};
60
79
  const title = payload.title;
61
80
  if (typeof title !== "string") {
@@ -64,9 +83,6 @@ function useSession(sessionId, options = {}) {
64
83
  const source = payload.source === "user" || payload.source === "agent" ? payload.source : null;
65
84
  setOverride((current) => {
66
85
  const next = current ?? base;
67
- if (!next) {
68
- return current;
69
- }
70
86
  return { ...next, title, titleSource: source };
71
87
  });
72
88
  },
@@ -93,6 +109,8 @@ function useSession(sessionId, options = {}) {
93
109
  session,
94
110
  loading,
95
111
  error,
112
+ readRevision,
113
+ readGeneration,
96
114
  refresh,
97
115
  updateTitle,
98
116
  updating: mutating,
@@ -102,7 +120,7 @@ function useSession(sessionId, options = {}) {
102
120
  }
103
121
 
104
122
  // src/hooks/use-session-events.ts
105
- import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef, useState as useState2 } from "react";
123
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
106
124
  var INITIAL_TAIL_PAGE_SIZE = 1e3;
107
125
  var OLDER_PAGE_SIZE = 5e3;
108
126
  var NEWER_PAGE_SIZE = 5e3;
@@ -174,23 +192,23 @@ function useSessionEvents(sessionId, options = {}) {
174
192
  const [loadingLatest, setLoadingLatest] = useState2(false);
175
193
  const [streamEpoch, setStreamEpoch] = useState2(0);
176
194
  const [viewMode, setViewMode] = useState2("live");
177
- const lastSequenceRef = useRef(after);
178
- const streamResumeSequenceRef = useRef(after);
179
- const oldestSequenceRef = useRef(null);
180
- const newestSequenceRef = useRef(null);
181
- const hasOlderRef = useRef(false);
182
- const hasNewerRef = useRef(false);
183
- const loadingOlderRef = useRef(false);
184
- const loadingNewerRef = useRef(false);
185
- const loadingOldestRef = useRef(false);
186
- const loadingLatestRef = useRef(false);
187
- const viewModeRef = useRef("live");
188
- const initialWindowLoadedRef = useRef(false);
189
- const streamAbortRef = useRef(null);
190
- const streamKeyRef = useRef(null);
191
- const generationRef = useRef(0);
192
- const eventWindowRef = useRef(EMPTY_EVENT_WINDOW);
193
- const sessionStatusRef = useRef({
195
+ const lastSequenceRef = useRef2(after);
196
+ const streamResumeSequenceRef = useRef2(after);
197
+ const oldestSequenceRef = useRef2(null);
198
+ const newestSequenceRef = useRef2(null);
199
+ const hasOlderRef = useRef2(false);
200
+ const hasNewerRef = useRef2(false);
201
+ const loadingOlderRef = useRef2(false);
202
+ const loadingNewerRef = useRef2(false);
203
+ const loadingOldestRef = useRef2(false);
204
+ const loadingLatestRef = useRef2(false);
205
+ const viewModeRef = useRef2("live");
206
+ const initialWindowLoadedRef = useRef2(false);
207
+ const streamAbortRef = useRef2(null);
208
+ const streamKeyRef = useRef2(null);
209
+ const generationRef = useRef2(0);
210
+ const eventWindowRef = useRef2(EMPTY_EVENT_WINDOW);
211
+ const sessionStatusRef = useRef2({
194
212
  sequence: after,
195
213
  status: null
196
214
  });
@@ -392,73 +410,78 @@ function useSessionEvents(sessionId, options = {}) {
392
410
  reconcileSession
393
411
  ]);
394
412
  const navigationBusy = () => loadingOlderRef.current || loadingNewerRef.current || loadingOldestRef.current || loadingLatestRef.current;
395
- const loadOlder = useCallback2(async () => {
396
- if (!sessionId || navigationBusy() || !hasOlderRef.current) {
397
- return false;
398
- }
399
- const before = oldestSequenceRef.current;
400
- if (before === null) {
401
- hasOlderRef.current = false;
402
- setHasOlder(false);
403
- return false;
404
- }
405
- const generation = generationRef.current;
406
- loadingOlderRef.current = true;
407
- setLoadingOlder(true);
408
- try {
409
- const window = await loadEventWindow(client, workspaceId, sessionId, {
410
- before,
411
- pageSize: OLDER_PAGE_SIZE,
412
- targetGroups: OLDER_GROUP_TARGET,
413
- maxFetches: OLDER_FETCH_CAP
414
- });
415
- if (generationRef.current !== generation) {
413
+ const loadOlder = useCallback2(
414
+ () => createOlderHistoryLoadReceipt(async (markCommitted) => {
415
+ if (!sessionId || navigationBusy() || !hasOlderRef.current) {
416
416
  return false;
417
417
  }
418
- if (window.events.length === 0) {
418
+ const before = oldestSequenceRef.current;
419
+ if (before === null) {
419
420
  oldestSequenceRef.current = null;
420
421
  hasOlderRef.current = false;
421
422
  setHasOlder(false);
422
423
  return false;
423
424
  }
424
- const current = eventWindowRef.current;
425
- assertPrependOrder(current.events, window.events);
426
- streamAbortRef.current?.abort();
427
- observeSessionStatus(window.events, sessionStatusRef, setSessionStatusProjection);
428
- const next = boundBrowserSessionEventWindow([...window.events, ...current.events], {
429
- direction: "oldest"
430
- });
431
- const retained = {
432
- ...next,
433
- truncated: current.truncated || next.truncated
434
- };
435
- const retainedOldest = retained.events[0]?.sequence ?? null;
436
- if (retainedOldest === null || retainedOldest >= before) {
437
- throw new Error("@opengeni/react: loadOlder made no durable sequence progress");
438
- }
439
- const retainedNewest = retained.events.at(-1)?.sequence ?? null;
440
- eventWindowRef.current = retained;
441
- setEventWindow(retained);
442
- oldestSequenceRef.current = retainedOldest;
443
- newestSequenceRef.current = retainedNewest;
444
- streamResumeSequenceRef.current = maxResumeSequence(retained.events);
445
- const olderStillAvailable = window.hasOlder;
446
- hasOlderRef.current = olderStillAvailable;
447
- setHasOlder(olderStillAvailable);
448
- if (viewModeRef.current === "history") {
449
- const highWater = lastSequenceRef.current;
450
- const newer = retainedNewest !== null && (retainedNewest < highWater || retained.truncated);
451
- hasNewerRef.current = newer;
452
- setHasNewer(newer);
453
- } else {
454
- setStreamEpoch((epoch) => epoch + 1);
425
+ const generation = generationRef.current;
426
+ loadingOlderRef.current = true;
427
+ setLoadingOlder(true);
428
+ try {
429
+ const window = await loadEventWindow(client, workspaceId, sessionId, {
430
+ before,
431
+ pageSize: OLDER_PAGE_SIZE,
432
+ targetGroups: OLDER_GROUP_TARGET,
433
+ maxFetches: OLDER_FETCH_CAP
434
+ });
435
+ if (generationRef.current !== generation) {
436
+ return false;
437
+ }
438
+ if (window.events.length === 0) {
439
+ oldestSequenceRef.current = null;
440
+ hasOlderRef.current = false;
441
+ setHasOlder(false);
442
+ return false;
443
+ }
444
+ const current = eventWindowRef.current;
445
+ assertPrependOrder(current.events, window.events);
446
+ streamAbortRef.current?.abort();
447
+ observeSessionStatus(window.events, sessionStatusRef, setSessionStatusProjection);
448
+ const next = boundBrowserSessionEventWindow([...window.events, ...current.events], {
449
+ direction: "oldest"
450
+ });
451
+ const retained = {
452
+ ...next,
453
+ truncated: current.truncated || next.truncated
454
+ };
455
+ const retainedOldest = retained.events[0]?.sequence ?? null;
456
+ if (retainedOldest === null || retainedOldest >= before) {
457
+ throw new Error("@opengeni/react: loadOlder made no durable sequence progress");
458
+ }
459
+ const retainedNewest = retained.events.at(-1)?.sequence ?? null;
460
+ eventWindowRef.current = retained;
461
+ markCommitted();
462
+ setEventWindow(retained);
463
+ oldestSequenceRef.current = retainedOldest;
464
+ newestSequenceRef.current = retainedNewest;
465
+ streamResumeSequenceRef.current = maxResumeSequence(retained.events);
466
+ const olderStillAvailable = window.hasOlder;
467
+ hasOlderRef.current = olderStillAvailable;
468
+ setHasOlder(olderStillAvailable);
469
+ if (viewModeRef.current === "history") {
470
+ const highWater = lastSequenceRef.current;
471
+ const newer = retainedNewest !== null && (retainedNewest < highWater || retained.truncated);
472
+ hasNewerRef.current = newer;
473
+ setHasNewer(newer);
474
+ } else {
475
+ setStreamEpoch((epoch) => epoch + 1);
476
+ }
477
+ return olderStillAvailable;
478
+ } finally {
479
+ loadingOlderRef.current = false;
480
+ setLoadingOlder(false);
455
481
  }
456
- return olderStillAvailable;
457
- } finally {
458
- loadingOlderRef.current = false;
459
- setLoadingOlder(false);
460
- }
461
- }, [client, workspaceId, sessionId]);
482
+ }),
483
+ [client, workspaceId, sessionId]
484
+ );
462
485
  const loadOldest = useCallback2(async () => {
463
486
  if (!sessionId || navigationBusy() || !hasOlderRef.current) {
464
487
  return false;
@@ -1048,14 +1071,14 @@ function abortError() {
1048
1071
  }
1049
1072
 
1050
1073
  // src/hooks/use-file-attachments.ts
1051
- import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
1074
+ import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
1052
1075
  var isImage = (file) => file.type.startsWith("image/");
1053
1076
  function useFileAttachments(options = {}) {
1054
1077
  const { client, workspaceId } = useEmbeddedFileAttachments(options);
1055
1078
  const pasteFilter = options.pasteFilter ?? isImage;
1056
1079
  const [attachments, setAttachments] = useState3([]);
1057
- const sources = useRef2(/* @__PURE__ */ new Map());
1058
- const previewUrls = useRef2(/* @__PURE__ */ new Map());
1080
+ const sources = useRef3(/* @__PURE__ */ new Map());
1081
+ const previewUrls = useRef3(/* @__PURE__ */ new Map());
1059
1082
  const revokePreview = useCallback3((id) => {
1060
1083
  const previewUrl = previewUrls.current.get(id);
1061
1084
  if (!previewUrl) return;
@@ -1067,7 +1090,7 @@ function useFileAttachments(options = {}) {
1067
1090
  previewUrls.current.clear();
1068
1091
  for (const previewUrl of urls) URL.revokeObjectURL(previewUrl);
1069
1092
  }, []);
1070
- const committedAttachmentIds = useRef2(/* @__PURE__ */ new Set());
1093
+ const committedAttachmentIds = useRef3(/* @__PURE__ */ new Set());
1071
1094
  useEffect3(() => {
1072
1095
  const currentIds = new Set(attachments.map((attachment) => attachment.id));
1073
1096
  for (const id of committedAttachmentIds.current) {
@@ -1075,8 +1098,8 @@ function useFileAttachments(options = {}) {
1075
1098
  }
1076
1099
  committedAttachmentIds.current = currentIds;
1077
1100
  }, [attachments, revokePreview]);
1078
- const scopeGeneration = useRef2(0);
1079
- const previousScope = useRef2({ client, workspaceId });
1101
+ const scopeGeneration = useRef3(0);
1102
+ const previousScope = useRef3({ client, workspaceId });
1080
1103
  useEffect3(() => {
1081
1104
  const previous = previousScope.current;
1082
1105
  if (previous.client === client && previous.workspaceId === workspaceId) return;
@@ -1264,7 +1287,7 @@ function useFileAttachments(options = {}) {
1264
1287
  }
1265
1288
 
1266
1289
  // src/hooks/use-turn-queue.ts
1267
- import { useCallback as useCallback4, useEffect as useEffect4, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
1290
+ import { useCallback as useCallback4, useEffect as useEffect4, useLayoutEffect, useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
1268
1291
  function isTurnQueueEvent(event) {
1269
1292
  return event.type.startsWith("turn.") || event.type.startsWith("session.queue.") || event.type.startsWith("session.control.") || event.type.startsWith("system.update.") || event.type.startsWith("workspace.inference.");
1270
1293
  }
@@ -1286,10 +1309,10 @@ function useTurnQueue(sessionId, options = {}) {
1286
1309
  const [mutationError, setMutationError] = useState4(null);
1287
1310
  const [pendingByTurn, setPendingByTurn] = useState4({});
1288
1311
  const [acceptedSteers, setAcceptedSteers] = useState4([]);
1289
- const pendingRef = useRef3({});
1290
- const readGeneration = useRef3(0);
1291
- const targetKeyRef = useRef3(targetKey);
1292
- const snapshotRef = useRef3(null);
1312
+ const pendingRef = useRef4({});
1313
+ const readGeneration = useRef4(0);
1314
+ const targetKeyRef = useRef4(targetKey);
1315
+ const snapshotRef = useRef4(null);
1293
1316
  useLayoutEffect(() => {
1294
1317
  if (targetKeyRef.current === targetKey) return;
1295
1318
  targetKeyRef.current = targetKey;
@@ -1598,7 +1621,7 @@ function isOutcomeUnknownError(cause) {
1598
1621
 
1599
1622
  // src/hooks/use-goal.ts
1600
1623
  import { OpenGeniApiError } from "@opengeni/sdk";
1601
- import { useCallback as useCallback5, useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
1624
+ import { useCallback as useCallback5, useEffect as useEffect5, useRef as useRef5, useState as useState5 } from "react";
1602
1625
  function isGoalEvent(event) {
1603
1626
  return event.type.startsWith("goal.");
1604
1627
  }
@@ -1616,8 +1639,8 @@ function useGoal(sessionId, options = {}) {
1616
1639
  const [loading, setLoading] = useState5(enabled);
1617
1640
  const [error, setError] = useState5(null);
1618
1641
  const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
1619
- const generation = useRef4(0);
1620
- const targetKeyRef = useRef4(null);
1642
+ const generation = useRef5(0);
1643
+ const targetKeyRef = useRef5(null);
1621
1644
  const load = useCallback5(async () => {
1622
1645
  if (!sessionId) {
1623
1646
  return;
@@ -1750,11 +1773,11 @@ function useGoal(sessionId, options = {}) {
1750
1773
  }
1751
1774
 
1752
1775
  // src/hooks/use-session-control.ts
1753
- import { useCallback as useCallback6, useLayoutEffect as useLayoutEffect2, useRef as useRef5 } from "react";
1776
+ import { useCallback as useCallback6, useLayoutEffect as useLayoutEffect2, useRef as useRef6 } from "react";
1754
1777
  function useSessionControl(sessionId, options = {}) {
1755
1778
  const { client, workspaceId } = useEmbeddedSession(options);
1756
1779
  const approvalTargetKey = `${workspaceId}\0${sessionId ?? ""}`;
1757
- const pendingApproval = useRef5(null);
1780
+ const pendingApproval = useRef6(null);
1758
1781
  useLayoutEffect2(() => {
1759
1782
  if (pendingApproval.current?.targetKey !== approvalTargetKey) {
1760
1783
  pendingApproval.current = null;
@@ -1847,7 +1870,7 @@ function useSessionControl(sessionId, options = {}) {
1847
1870
  }
1848
1871
 
1849
1872
  // src/hooks/use-session-mcp-approval-policy.ts
1850
- import { useCallback as useCallback7, useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useMemo as useMemo4, useRef as useRef6, useState as useState6 } from "react";
1873
+ import { useCallback as useCallback7, useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useMemo as useMemo4, useRef as useRef7, useState as useState6 } from "react";
1851
1874
  function isSessionMcpApprovalPolicyEvent(event, serverId) {
1852
1875
  if (event.type !== "session.mcp.approval_policy.updated") {
1853
1876
  return false;
@@ -1863,7 +1886,7 @@ function useSessionMcpApprovalPolicy(sessionId, serverId, options = {}) {
1863
1886
  const enabled = (options.enabled ?? true) && Boolean(sessionId && serverId);
1864
1887
  const [override, setOverride] = useState6(null);
1865
1888
  const targetKey = `${workspaceId}\0${sessionId ?? ""}\0${serverId ?? ""}`;
1866
- const authoritativeGeneration = useRef6(0);
1889
+ const authoritativeGeneration = useRef7(0);
1867
1890
  const mutationIdentity = useMemo4(() => ({ client, targetKey }), [client, targetKey]);
1868
1891
  const { run, mutating, mutationError, clearMutationError } = useMutationRunner(mutationIdentity);
1869
1892
  useLayoutEffect3(() => {
@@ -1948,7 +1971,7 @@ function useSessionMcpApprovalPolicy(sessionId, serverId, options = {}) {
1948
1971
  }
1949
1972
 
1950
1973
  // src/hooks/use-session-lineage.ts
1951
- import { useCallback as useCallback8, useEffect as useEffect7, useRef as useRef7 } from "react";
1974
+ import { useCallback as useCallback8, useEffect as useEffect7, useRef as useRef8 } from "react";
1952
1975
  function isLineageRefreshEvent(event) {
1953
1976
  if (event.type === "session.status.changed" || event.type === "session.created") {
1954
1977
  return true;
@@ -1971,10 +1994,26 @@ function isSessionCreateToolCallCreated(event) {
1971
1994
  function useSessionLineage(sessionId, options = {}) {
1972
1995
  const { client, workspaceId, workspaceControlEvent, registerSessionReconciler } = useEmbeddedSessionLineage(options);
1973
1996
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
1974
- const load = useCallback8(
1975
- async () => sessionId ? await client.getSessionLineage(workspaceId, sessionId) : { ancestors: [], children: [], truncated: false },
1976
- [client, workspaceId, sessionId]
1977
- );
1997
+ const nextReadGeneration = useRef8(0);
1998
+ const beginRead = options.beginRead;
1999
+ const load = useCallback8(async () => {
2000
+ if (!sessionId) {
2001
+ return {
2002
+ lineage: { ancestors: [], children: [], truncated: false },
2003
+ readGeneration: 0
2004
+ };
2005
+ }
2006
+ let readGeneration = 0;
2007
+ const lineage = await client.getSessionLineage(workspaceId, sessionId, {
2008
+ onRequestStart: (sharedReadGeneration) => {
2009
+ readGeneration = sharedReadGeneration ?? beginRead?.() ?? ++nextReadGeneration.current;
2010
+ }
2011
+ });
2012
+ return {
2013
+ lineage,
2014
+ readGeneration
2015
+ };
2016
+ }, [beginRead, client, workspaceId, sessionId]);
1978
2017
  const state = usePolledValue(load, { pollIntervalMs: options.pollIntervalMs, enabled });
1979
2018
  const refresh = state.refresh;
1980
2019
  useEffect7(() => {
@@ -1985,7 +2024,7 @@ function useSessionLineage(sessionId, options = {}) {
1985
2024
  return registerSessionReconciler(sessionId, "lineage", refresh);
1986
2025
  }, [enabled, refresh, registerSessionReconciler, sessionId]);
1987
2026
  const refreshSoon = useDebouncedCallback(() => void refresh(), 150);
1988
- const delayedChildRefreshRef = useRef7(null);
2027
+ const delayedChildRefreshRef = useRef8(null);
1989
2028
  useEffect7(() => {
1990
2029
  return () => {
1991
2030
  if (delayedChildRefreshRef.current !== null) {
@@ -2025,9 +2064,10 @@ function useSessionLineage(sessionId, options = {}) {
2025
2064
  { events: options.events, enabled: enabled && options.events !== void 0 }
2026
2065
  );
2027
2066
  return {
2028
- lineage: state.data,
2067
+ lineage: state.data?.lineage ?? null,
2029
2068
  loading: state.loading,
2030
2069
  error: state.error,
2070
+ readGeneration: state.data?.readGeneration ?? 0,
2031
2071
  refresh
2032
2072
  };
2033
2073
  }
@@ -2126,7 +2166,7 @@ function isRecord(value) {
2126
2166
  }
2127
2167
 
2128
2168
  // src/hooks/use-human-input.ts
2129
- import { useCallback as useCallback9, useEffect as useEffect8, useRef as useRef8, useState as useState7 } from "react";
2169
+ import { useCallback as useCallback9, useEffect as useEffect8, useRef as useRef9, useState as useState7 } from "react";
2130
2170
  function isHumanInputEvent(event) {
2131
2171
  return event.type === "session.humanInput.requested" || event.type === "user.humanInputResponse" || event.type === "turn.completed" || event.type === "turn.failed" || event.type === "turn.cancelled";
2132
2172
  }
@@ -2143,7 +2183,7 @@ function useHumanInputRequests(sessionId, options = {}) {
2143
2183
  });
2144
2184
  const mutation = useMutationRunner(`${workspaceId}\0${sessionId ?? ""}`);
2145
2185
  const [respondingRequestId, setRespondingRequestId] = useState7(null);
2146
- const respondingRequestRef = useRef8(null);
2186
+ const respondingRequestRef = useRef9(null);
2147
2187
  useEffect8(() => {
2148
2188
  respondingRequestRef.current = null;
2149
2189
  setRespondingRequestId(null);
@@ -2220,4 +2260,4 @@ export {
2220
2260
  isHumanInputEvent,
2221
2261
  useHumanInputRequests
2222
2262
  };
2223
- //# sourceMappingURL=chunk-SE4W4WQ3.js.map
2263
+ //# sourceMappingURL=chunk-ATQJEWCA.js.map