@opengeni/react 3.5.1-canary.0 → 3.7.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 (87) hide show
  1. package/dist/artifacts.d.ts +1 -1
  2. package/dist/artifacts.js +248 -6
  3. package/dist/artifacts.js.map +1 -1
  4. package/dist/{browser-viewer-E5DDEFQ4.js → browser-viewer-NTD6UIPR.js} +3 -3
  5. package/dist/{chunk-W2RGYBCV.js → chunk-24M4YBCL.js} +4 -4
  6. package/dist/{chunk-WY3MELVJ.js → chunk-5IXTJTK3.js} +1438 -397
  7. package/dist/chunk-5IXTJTK3.js.map +1 -0
  8. package/dist/chunk-5PQJOCX5.js +902 -0
  9. package/dist/chunk-5PQJOCX5.js.map +1 -0
  10. package/dist/{chunk-UNCXXIQR.js → chunk-INGB2N5R.js} +4310 -3982
  11. package/dist/chunk-INGB2N5R.js.map +1 -0
  12. package/dist/{chunk-TJFMMDQU.js → chunk-MOWYEBCQ.js} +1839 -17
  13. package/dist/chunk-MOWYEBCQ.js.map +1 -0
  14. package/dist/chunk-PAJU5KKL.js +331 -0
  15. package/dist/chunk-PAJU5KKL.js.map +1 -0
  16. package/dist/{chunk-VTOREEXF.js → chunk-PLYRL5ER.js} +24 -2
  17. package/dist/{chunk-VTOREEXF.js.map → chunk-PLYRL5ER.js.map} +1 -1
  18. package/dist/{chunk-YVCHAQZY.js → chunk-ZFOFY5ST.js} +27 -349
  19. package/dist/chunk-ZFOFY5ST.js.map +1 -0
  20. package/dist/client.d.ts +1 -1
  21. package/dist/components/artifacts/index.d.ts +1 -1
  22. package/dist/components/artifacts/published-html-artifact-frame.d.ts +46 -3
  23. package/dist/components/composer.d.ts +2 -0
  24. package/dist/components/session-chrome.d.ts +38 -5
  25. package/dist/components/session-commands-panel.d.ts +6 -0
  26. package/dist/components/session-conversation.d.ts +14 -0
  27. package/dist/components/timeline-anchor.d.ts +21 -0
  28. package/dist/composer.js +2 -3
  29. package/dist/conversation-timeline.d.ts +5 -0
  30. package/dist/{fleet-decision-row-GGCAT4E4.js → fleet-decision-row-YZ3ZKWMM.js} +2 -1
  31. package/dist/fleet-decision-row-YZ3ZKWMM.js.map +1 -0
  32. package/dist/hooks/use-file-attachments.d.ts +6 -0
  33. package/dist/hooks/use-sandbox-files.d.ts +4 -1
  34. package/dist/hooks/use-workspace-edit.d.ts +4 -0
  35. package/dist/index.d.ts +4 -1
  36. package/dist/index.js +823 -965
  37. package/dist/index.js.map +1 -1
  38. package/dist/interaction.js +2 -2
  39. package/dist/lib/format.d.ts +5 -0
  40. package/dist/machines.js +3 -2
  41. package/dist/session-ui.d.ts +3 -0
  42. package/dist/session-ui.js +14 -4
  43. package/dist/session.js +19 -19
  44. package/dist/timeline/registry.d.ts +2 -0
  45. package/dist/timeline/types.d.ts +4 -2
  46. package/package.json +2 -2
  47. package/src/artifacts.ts +3 -0
  48. package/src/client.ts +2 -1
  49. package/src/components/artifacts/index.ts +3 -0
  50. package/src/components/artifacts/published-html-artifact-frame.tsx +307 -4
  51. package/src/components/composer.tsx +89 -25
  52. package/src/components/machine-input-display.ts +6 -0
  53. package/src/components/message-timeline.tsx +351 -204
  54. package/src/components/sandbox-workspace.tsx +11 -1
  55. package/src/components/session-chrome.tsx +501 -128
  56. package/src/components/session-commands-panel.tsx +113 -0
  57. package/src/components/session-conversation.tsx +141 -0
  58. package/src/components/timeline-anchor.tsx +91 -0
  59. package/src/conversation-timeline.ts +83 -0
  60. package/src/hooks/use-file-attachments.ts +34 -0
  61. package/src/hooks/use-sandbox-files.ts +68 -20
  62. package/src/hooks/use-session-background-commands.ts +21 -10
  63. package/src/hooks/use-session-events.ts +56 -39
  64. package/src/hooks/use-session.ts +23 -2
  65. package/src/hooks/use-workspace-edit.ts +23 -5
  66. package/src/index.ts +4 -0
  67. package/src/lib/format.ts +32 -0
  68. package/src/session-ui.ts +3 -0
  69. package/src/timeline/fleet-decision-projection.ts +2 -1
  70. package/src/timeline/fleet-decision-row.tsx +1 -0
  71. package/src/timeline/projection.ts +157 -19
  72. package/src/timeline/registry.ts +15 -3
  73. package/src/timeline/tool-renderers.tsx +117 -1
  74. package/src/timeline/types.ts +12 -1
  75. package/styles/compiled.css +495 -411
  76. package/styles/index.css +23 -0
  77. package/dist/chunk-7EAFGICR.js +0 -302
  78. package/dist/chunk-7EAFGICR.js.map +0 -1
  79. package/dist/chunk-IB27C53Y.js +0 -2582
  80. package/dist/chunk-IB27C53Y.js.map +0 -1
  81. package/dist/chunk-TJFMMDQU.js.map +0 -1
  82. package/dist/chunk-UNCXXIQR.js.map +0 -1
  83. package/dist/chunk-WY3MELVJ.js.map +0 -1
  84. package/dist/chunk-YVCHAQZY.js.map +0 -1
  85. package/dist/fleet-decision-row-GGCAT4E4.js.map +0 -1
  86. /package/dist/{browser-viewer-E5DDEFQ4.js.map → browser-viewer-NTD6UIPR.js.map} +0 -0
  87. /package/dist/{chunk-W2RGYBCV.js.map → chunk-24M4YBCL.js.map} +0 -0
@@ -1,9 +1,21 @@
1
1
  import {
2
2
  CREDIT_EXHAUSTION_MESSAGE,
3
- humanizeFailureReason,
4
3
  isCreditExhaustion,
4
+ presentFailure,
5
5
  tryParseJson
6
- } from "./chunk-VTOREEXF.js";
6
+ } from "./chunk-PLYRL5ER.js";
7
+ import {
8
+ useDebouncedCallback,
9
+ useMutationRunner,
10
+ usePageLiveActivity,
11
+ usePolledValue,
12
+ useSessionEventTrigger
13
+ } from "./chunk-KZ73RL76.js";
14
+ import {
15
+ useEmbeddedHumanInputSession,
16
+ useEmbeddedSession,
17
+ useEmbeddedSessionRead
18
+ } from "./chunk-NEXLLTP4.js";
7
19
 
8
20
  // src/human-input.ts
9
21
  function isActionableHumanInputRequest(request, nowMs = Date.now()) {
@@ -112,6 +124,7 @@ var FLEET_ACTUAL_REASONS = [
112
124
  "rotation",
113
125
  "active",
114
126
  "all_capped",
127
+ "allocator_disabled",
115
128
  "none"
116
129
  ];
117
130
  var FLEET_SHADOW_OUTCOMES = ["selected", "paced", "none"];
@@ -329,7 +342,7 @@ function fleetComparisonIsConsistent(comparison, actualOutcome, actualCandidateK
329
342
  return comparableActualOutcome !== "selected" || actualCandidateKey === shadowCandidateKey;
330
343
  }
331
344
  function fleetDecisionSemanticsAreConsistent(input) {
332
- const actualConsistent = input.actualOutcome === "selected" ? ["lease_reused", "pin", "rotation", "active"].includes(input.actualReason) : input.actualOutcome === "waiting" ? input.actualReason === "all_capped" : input.actualReason === "none";
345
+ const actualConsistent = input.actualOutcome === "selected" ? ["lease_reused", "pin", "rotation", "active"].includes(input.actualReason) : input.actualOutcome === "waiting" ? ["all_capped", "allocator_disabled"].includes(input.actualReason) : input.actualReason === "none";
333
346
  const shadowConsistent = input.shadowOutcome === "selected" ? ["fenced_in_flight", "best_score", "affinity_best", "hysteresis_hold"].includes(
334
347
  input.shadowReason
335
348
  ) : input.shadowOutcome === "paced" ? input.shadowReason === "admission_paced" : ["fenced_candidate_missing", "no_eligible_candidate", "overlay_isolated_empty"].includes(
@@ -388,6 +401,8 @@ function buildTimeline(events) {
388
401
  const items = [];
389
402
  const prescan = prescanTurnAnchors(events);
390
403
  const ordered = orderTimelineEvents(events, prescan);
404
+ const pendingWaitOutcomeByTurn = /* @__PURE__ */ new Map();
405
+ const latestAgentResponseByTurn = /* @__PURE__ */ new Map();
391
406
  const humanInputRequests = humanInputRequestsById(events);
392
407
  const humanInputToolCallIds = new Set(
393
408
  [...humanInputRequests.values()].map((request) => stringValue(request.toolCallId)).filter(Boolean)
@@ -450,6 +465,23 @@ function buildTimeline(events) {
450
465
  startupPhases.set(key, [item, startupAttemptId, startupRecoveryRevision]);
451
466
  };
452
467
  const last = () => items[items.length - 1];
468
+ const rememberAgentResponse = (turnId, item, completed) => {
469
+ latestAgentResponseByTurn.set(turnId, { item, completed });
470
+ };
471
+ const takePendingWaitOutcome = (turnId) => {
472
+ const outcome = pendingWaitOutcomeByTurn.get(turnId);
473
+ pendingWaitOutcomeByTurn.delete(turnId);
474
+ return outcome;
475
+ };
476
+ const takeAgentResponse = (turnId) => {
477
+ const response = latestAgentResponseByTurn.get(turnId);
478
+ latestAgentResponseByTurn.delete(turnId);
479
+ return response;
480
+ };
481
+ const clearUnscopedTerminalTracking = () => {
482
+ latestAgentResponseByTurn.delete(null);
483
+ pendingWaitOutcomeByTurn.delete(null);
484
+ };
453
485
  const closeStreamingTail = () => {
454
486
  const open = last();
455
487
  if ((open?.kind === "agent-message" || open?.kind === "reasoning") && open.streaming) {
@@ -500,6 +532,7 @@ function buildTimeline(events) {
500
532
  }
501
533
  switch (event.type) {
502
534
  case "user.message": {
535
+ clearUnscopedTerminalTracking();
503
536
  closeStreamingTail();
504
537
  const childCompletion = workerCompletionPayload(payload.childCompletion);
505
538
  if (childCompletion) {
@@ -571,17 +604,20 @@ function buildTimeline(events) {
571
604
  const open = last();
572
605
  if (open?.kind === "agent-message" && open.streaming && open.turnId === turnId) {
573
606
  open.text += text;
607
+ rememberAgentResponse(turnId, open, false);
574
608
  break;
575
609
  }
576
610
  closeStreamingTail();
577
- items.push({
611
+ const item = {
578
612
  kind: "agent-message",
579
613
  id: event.id,
580
614
  turnId,
581
615
  text,
582
616
  streaming: true,
583
617
  occurredAt: event.occurredAt
584
- });
618
+ };
619
+ items.push(item);
620
+ rememberAgentResponse(turnId, item, false);
585
621
  break;
586
622
  }
587
623
  case "agent.message.completed": {
@@ -616,10 +652,11 @@ function buildTimeline(events) {
616
652
  items.splice(openIndex, 1);
617
653
  items.push(open);
618
654
  }
655
+ rememberAgentResponse(turnId, open, true);
619
656
  break;
620
657
  }
621
658
  if (text) {
622
- items.push({
659
+ const item = {
623
660
  kind: "agent-message",
624
661
  id: event.id,
625
662
  turnId,
@@ -635,7 +672,9 @@ function buildTimeline(events) {
635
672
  turnId,
636
673
  text
637
674
  }
638
- });
675
+ };
676
+ items.push(item);
677
+ rememberAgentResponse(turnId, item, true);
639
678
  }
640
679
  break;
641
680
  }
@@ -758,6 +797,7 @@ function buildTimeline(events) {
758
797
  break;
759
798
  }
760
799
  case "turn.started": {
800
+ if (turnId) clearUnscopedTerminalTracking();
761
801
  if (!turnId) break;
762
802
  const queuedAt = queuedAtByTurn.get(turnId);
763
803
  if (!queuedAt) break;
@@ -864,6 +904,16 @@ ${message}` : message;
864
904
  });
865
905
  break;
866
906
  }
907
+ case "codex.capacity.waiting": {
908
+ items.push({
909
+ kind: "notice",
910
+ id: event.id,
911
+ tone: "waiting",
912
+ text: stringValue(payload.detail) ?? stringValue(payload.error) ?? "Waiting for Codex capacity.",
913
+ occurredAt: event.occurredAt
914
+ });
915
+ break;
916
+ }
867
917
  case "session.requiresAction": {
868
918
  finalizeOpen(turnId, "complete", event.occurredAt);
869
919
  items.push({
@@ -964,10 +1014,14 @@ ${message}` : message;
964
1014
  case "turn.completed": {
965
1015
  if (payload.maintenance === "context_compaction") {
966
1016
  finalizeOpen(turnId, "complete", event.occurredAt);
1017
+ takeAgentResponse(turnId);
1018
+ takePendingWaitOutcome(turnId);
967
1019
  break;
968
1020
  }
969
1021
  if (isCreditExhaustionPayload(payload)) {
970
1022
  finalizeOpen(turnId, "complete", event.occurredAt);
1023
+ takeAgentResponse(turnId);
1024
+ takePendingWaitOutcome(turnId);
971
1025
  items.push(turnEndItem(event, "failed", CREDIT_EXHAUSTION_MESSAGE));
972
1026
  items.push({
973
1027
  kind: "notice",
@@ -978,11 +1032,54 @@ ${message}` : message;
978
1032
  });
979
1033
  break;
980
1034
  }
1035
+ const latestAgentResponse = takeAgentResponse(turnId);
1036
+ const finalOutput = stringValue(payload.output);
1037
+ const visibleFinalOutput = stripOpaqueCitationTokens(finalOutput).trim();
1038
+ const visibleTrackedResponse = stripOpaqueCitationTokens(
1039
+ latestAgentResponse?.item.text ?? ""
1040
+ ).trim();
1041
+ const finalOutputMirrorsCommentary = latestAgentResponse?.item.phase === "commentary" && visibleTrackedResponse === visibleFinalOutput;
1042
+ const hasAuthoritativeFinalOutput = Boolean(visibleFinalOutput) && !finalOutputMirrorsCommentary;
1043
+ const pendingWaitOutcome = takePendingWaitOutcome(turnId);
1044
+ if (hasAuthoritativeFinalOutput) {
1045
+ if (latestAgentResponse && !latestAgentResponse.completed && finalOutput.startsWith(latestAgentResponse.item.text)) {
1046
+ latestAgentResponse.item.text = finalOutput;
1047
+ latestAgentResponse.item.streaming = false;
1048
+ latestAgentResponse.item.occurredAt = event.occurredAt;
1049
+ const responseIndex = items.indexOf(latestAgentResponse.item);
1050
+ if (responseIndex >= 0 && responseIndex < items.length - 1) {
1051
+ items.splice(responseIndex, 1);
1052
+ items.push(latestAgentResponse.item);
1053
+ }
1054
+ } else if (visibleTrackedResponse !== visibleFinalOutput) {
1055
+ items.push({
1056
+ kind: "agent-message",
1057
+ id: `${event.id}-output-message`,
1058
+ turnId,
1059
+ text: finalOutput,
1060
+ streaming: false,
1061
+ occurredAt: event.occurredAt
1062
+ });
1063
+ }
1064
+ }
981
1065
  finalizeOpen(turnId, "complete", event.occurredAt);
982
1066
  items.push(turnEndItem(event, "complete", null));
1067
+ const hasCompletedFinalResponse = latestAgentResponse?.completed === true && latestAgentResponse.item.phase !== "commentary" && Boolean(visibleTrackedResponse);
1068
+ if (!hasAuthoritativeFinalOutput && !hasCompletedFinalResponse && pendingWaitOutcome) {
1069
+ items.push({
1070
+ kind: "notice",
1071
+ id: `${pendingWaitOutcome.id}-visible-outcome`,
1072
+ tone: "waiting",
1073
+ text: waitingOutcomeText(pendingWaitOutcome.reason),
1074
+ recordedOutcome: true,
1075
+ occurredAt: pendingWaitOutcome.occurredAt
1076
+ });
1077
+ }
983
1078
  break;
984
1079
  }
985
1080
  case "turn.failed": {
1081
+ takeAgentResponse(turnId);
1082
+ takePendingWaitOutcome(turnId);
986
1083
  const hadActivity = hasTurnActivity(items, turnId);
987
1084
  const failureText = isCreditExhaustionPayload(payload) ? CREDIT_EXHAUSTION_MESSAGE : failureMessage(payload);
988
1085
  finalizeOpen(turnId, "cancelled", event.occurredAt);
@@ -1002,6 +1099,8 @@ ${message}` : message;
1002
1099
  if (turnId && !prescan.startedTurnIds.has(turnId)) {
1003
1100
  break;
1004
1101
  }
1102
+ takeAgentResponse(turnId);
1103
+ takePendingWaitOutcome(turnId);
1005
1104
  const hadActivity = hasTurnActivity(items, turnId);
1006
1105
  finalizeOpen(turnId, "cancelled", event.occurredAt);
1007
1106
  items.push(turnEndItem(event, "cancelled", null));
@@ -1041,6 +1140,9 @@ ${message}` : message;
1041
1140
  case "goal.cleared":
1042
1141
  case "goal.held":
1043
1142
  case "goal.continuation": {
1143
+ if (event.type === "goal.held" && payload.actor === "agent") {
1144
+ rememberPendingWaitOutcome(pendingWaitOutcomeByTurn, event, payload, turnId);
1145
+ }
1044
1146
  if (shouldSuppressAgentGoalLandmark(event.type, payload)) {
1045
1147
  break;
1046
1148
  }
@@ -1053,6 +1155,12 @@ ${message}` : message;
1053
1155
  });
1054
1156
  break;
1055
1157
  }
1158
+ case "session.wait.started": {
1159
+ if (payload.actor === "agent") {
1160
+ rememberPendingWaitOutcome(pendingWaitOutcomeByTurn, event, payload, turnId);
1161
+ }
1162
+ break;
1163
+ }
1056
1164
  default:
1057
1165
  break;
1058
1166
  }
@@ -1536,7 +1644,7 @@ function foldSettledTurn(groups, turnEnd) {
1536
1644
  );
1537
1645
  }
1538
1646
  function isTurnBoundary(group) {
1539
- return group?.kind === "turn" || group?.kind === "item" && (group.item.kind === "user-message" || group.item.kind === "human-input" || group.item.kind === "context-compaction" || group.item.kind === "machine-input-batch" && group.item.members.some((member) => member.kind === "media_generation_result") || group.item.kind === "notice" && group.item.tone === "input");
1647
+ return group?.kind === "turn" || group?.kind === "item" && (group.item.kind === "user-message" || group.item.kind === "human-input" || group.item.kind === "context-compaction" || group.item.kind === "machine-input-batch" || group.item.kind === "notice" && group.item.tone === "input");
1540
1648
  }
1541
1649
  function belongsToDifferentTurn(group, turnId) {
1542
1650
  if (!group || !turnId) {
@@ -1646,6 +1754,8 @@ function machineInputMembers(value) {
1646
1754
  const kinds = /* @__PURE__ */ new Set([
1647
1755
  "scheduled_occurrence",
1648
1756
  "goal_continuation",
1757
+ "background_command_result",
1758
+ "session_wait_timeout",
1649
1759
  "agent_message",
1650
1760
  "agent_steer_instruction",
1651
1761
  "child_terminal_result",
@@ -1687,6 +1797,8 @@ var SESSION_STATUSES = [
1687
1797
  "running",
1688
1798
  "idle",
1689
1799
  "requires_action",
1800
+ "recovering",
1801
+ "waiting_capacity",
1690
1802
  "failed",
1691
1803
  "cancelled"
1692
1804
  ];
@@ -1818,13 +1930,7 @@ function elapsedDurationMs(startedAt, completedAt) {
1818
1930
  return Number.isFinite(start) && Number.isFinite(end) ? Math.max(0, end - start) : null;
1819
1931
  }
1820
1932
  function failureMessage(payload) {
1821
- for (const key of ["error", "message"]) {
1822
- const value = payload[key];
1823
- if (typeof value === "string" && value.trim().length > 0) {
1824
- return humanizeFailureReason(value);
1825
- }
1826
- }
1827
- return null;
1933
+ return presentFailure(payload).reason;
1828
1934
  }
1829
1935
  function goalText(payload) {
1830
1936
  if (typeof payload.text === "string" && payload.text) {
@@ -1839,6 +1945,15 @@ function goalText(payload) {
1839
1945
  }
1840
1946
  return null;
1841
1947
  }
1948
+ function rememberPendingWaitOutcome(pending, event, payload, eventTurnId) {
1949
+ const reason = stringValue(payload.reason).trim();
1950
+ if (!reason) return;
1951
+ const turnId = eventTurnId || stringValue(payload.waitTurnId) || stringValue(payload.turnId) || null;
1952
+ pending.set(turnId, { id: event.id, reason, occurredAt: event.occurredAt });
1953
+ }
1954
+ function waitingOutcomeText(reason) {
1955
+ return /^waiting\b/i.test(reason) ? reason : `Waiting: ${reason}`;
1956
+ }
1842
1957
  function shouldSuppressAgentGoalLandmark(type, payload) {
1843
1958
  if (type === "goal.completed") {
1844
1959
  return true;
@@ -2021,6 +2136,1700 @@ function looksLikeId(value) {
2021
2136
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
2022
2137
  }
2023
2138
 
2139
+ // src/hooks/use-session-events.ts
2140
+ import {
2141
+ sessionEventStreamCoveredThrough
2142
+ } from "@opengeni/sdk";
2143
+ import { startTransition, useCallback, useEffect, useMemo, useRef, useState } from "react";
2144
+ var SESSION_HISTORY_PAGE_SIZE = 1e3;
2145
+ var INITIAL_FETCH_CAP = 1;
2146
+ var OLDER_GROUP_TARGET = 32;
2147
+ var OLDER_FETCH_CAP = 2;
2148
+ var NEWER_GROUP_TARGET = 32;
2149
+ var NEWER_FETCH_CAP = 2;
2150
+ var OLDEST_GROUP_TARGET = 32;
2151
+ var OLDEST_FETCH_CAP = 2;
2152
+ var BOUNDARY_PAGE_CAP = 1;
2153
+ var FOREGROUND_DIRECT_REPLAY_MAX_SEQUENCES = 16;
2154
+ var FOREGROUND_COMPACT_PROBE_MAX_SEQUENCES = SESSION_HISTORY_PAGE_SIZE;
2155
+ var FOREGROUND_COMPACT_CATCHUP_MAX_EVENTS = 128;
2156
+ var FOREGROUND_COMPACT_CATCHUP_MAX_GROUPS = 16;
2157
+ var FOREGROUND_COMPACT_CATCHUP_MAX_BYTES = 512 * 1024;
2158
+ var EMPTY_EVENTS = [];
2159
+ var encoder = new TextEncoder();
2160
+ var decoder = new TextDecoder();
2161
+ var BROWSER_EVENT_TYPE_MAX_BYTES = 256;
2162
+ var BROWSER_EVENT_ID_MAX_BYTES = 256;
2163
+ var BROWSER_EVENT_CLIENT_ID_MAX_BYTES = 4 * 1024;
2164
+ var BROWSER_EVENT_DUPLICATE_REASON_MAX_BYTES = 4 * 1024;
2165
+ var BROWSER_EVENT_PAYLOAD_PREVIEW_MAX_BYTES = 48 * 1024;
2166
+ var BROWSER_EVENT_PAYLOAD_IDENTITY_FIELDS = [
2167
+ "id",
2168
+ "callId",
2169
+ "call_id",
2170
+ "name",
2171
+ "toolName",
2172
+ "status",
2173
+ "code",
2174
+ "isError",
2175
+ "stream",
2176
+ "commandId",
2177
+ "sequence",
2178
+ "coalescedUntil",
2179
+ "coalescedCount",
2180
+ "firstSequence",
2181
+ "lastSequence"
2182
+ ];
2183
+ var SESSION_EVENT_BROWSER_MAX_BYTES = 8 * 1024 * 1024;
2184
+ var SESSION_EVENT_BROWSER_MAX_COUNT = 1e4;
2185
+ var SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES = 96 * 1024;
2186
+ var SESSION_EVENT_BROWSER_PENDING_MAX_BYTES = 1024 * 1024;
2187
+ var SESSION_EVENT_BROWSER_PENDING_MAX_COUNT = 256;
2188
+ var EMPTY_EVENT_WINDOW = {
2189
+ events: EMPTY_EVENTS,
2190
+ bytes: 2,
2191
+ truncated: false
2192
+ };
2193
+ function useSessionEvents(sessionId, options = {}) {
2194
+ const { client, workspaceId, reconcileSession } = useEmbeddedSession(options);
2195
+ const enabled = options.enabled ?? true;
2196
+ const pageLive = usePageLiveActivity();
2197
+ const streamEnabled = enabled && pageLive;
2198
+ const after = options.after ?? 0;
2199
+ const replay = options.replay ?? "windowed";
2200
+ const fullReplay = replay === "full" || after !== 0;
2201
+ const streamKey = `${workspaceId}\0${sessionId ?? ""}\0${after}\0${fullReplay ? "full" : "windowed"}`;
2202
+ const [eventWindow, setEventWindow] = useState(EMPTY_EVENT_WINDOW);
2203
+ const [connectionState, setConnectionState] = useState("idle");
2204
+ const [error, setError] = useState(null);
2205
+ const [hasOlder, setHasOlder] = useState(false);
2206
+ const [hasNewer, setHasNewer] = useState(false);
2207
+ const [sessionStatusProjection, setSessionStatusProjection] = useState(
2208
+ null
2209
+ );
2210
+ const [initialLoading, setInitialLoading] = useState(true);
2211
+ const [loadingOlder, setLoadingOlder] = useState(false);
2212
+ const [loadingNewer, setLoadingNewer] = useState(false);
2213
+ const [loadingOldest, setLoadingOldest] = useState(false);
2214
+ const [loadingLatest, setLoadingLatest] = useState(false);
2215
+ const [streamEpoch, setStreamEpoch] = useState(0);
2216
+ const [viewMode, setViewMode] = useState("live");
2217
+ const lastSequenceRef = useRef(after);
2218
+ const streamResumeSequenceRef = useRef(after);
2219
+ const oldestSequenceRef = useRef(null);
2220
+ const newestSequenceRef = useRef(null);
2221
+ const hasOlderRef = useRef(false);
2222
+ const hasNewerRef = useRef(false);
2223
+ const loadingOlderRef = useRef(false);
2224
+ const loadingNewerRef = useRef(false);
2225
+ const loadingOldestRef = useRef(false);
2226
+ const loadingLatestRef = useRef(false);
2227
+ const viewModeRef = useRef("live");
2228
+ const initialWindowLoadedRef = useRef(false);
2229
+ const reconcileAfterPageResumeRef = useRef(false);
2230
+ const streamAbortRef = useRef(null);
2231
+ const streamKeyRef = useRef(null);
2232
+ const generationRef = useRef(0);
2233
+ const navigationGenerationRef = useRef(0);
2234
+ const eventWindowRef = useRef(EMPTY_EVENT_WINDOW);
2235
+ const sessionStatusRef = useRef({
2236
+ sequence: after,
2237
+ status: null
2238
+ });
2239
+ const [stateStreamKey, setStateStreamKey] = useState(streamKey);
2240
+ useEffect(() => {
2241
+ navigationGenerationRef.current += 1;
2242
+ loadingOlderRef.current = false;
2243
+ loadingNewerRef.current = false;
2244
+ loadingOldestRef.current = false;
2245
+ setLoadingOlder(false);
2246
+ setLoadingNewer(false);
2247
+ setLoadingOldest(false);
2248
+ return () => {
2249
+ navigationGenerationRef.current += 1;
2250
+ };
2251
+ }, [client, workspaceId, sessionId, after, enabled, fullReplay]);
2252
+ useEffect(() => {
2253
+ if (streamKeyRef.current !== streamKey) {
2254
+ streamKeyRef.current = streamKey;
2255
+ setStateStreamKey(streamKey);
2256
+ eventWindowRef.current = EMPTY_EVENT_WINDOW;
2257
+ setEventWindow(EMPTY_EVENT_WINDOW);
2258
+ setError(null);
2259
+ setHasOlder(false);
2260
+ setHasNewer(false);
2261
+ sessionStatusRef.current = { sequence: after, status: null };
2262
+ setSessionStatusProjection(null);
2263
+ setLoadingOlder(false);
2264
+ setLoadingNewer(false);
2265
+ setLoadingOldest(false);
2266
+ setLoadingLatest(false);
2267
+ setInitialLoading(true);
2268
+ lastSequenceRef.current = after;
2269
+ streamResumeSequenceRef.current = after;
2270
+ oldestSequenceRef.current = null;
2271
+ newestSequenceRef.current = null;
2272
+ hasOlderRef.current = false;
2273
+ hasNewerRef.current = false;
2274
+ loadingOlderRef.current = false;
2275
+ loadingNewerRef.current = false;
2276
+ loadingOldestRef.current = false;
2277
+ loadingLatestRef.current = false;
2278
+ viewModeRef.current = "live";
2279
+ setViewMode("live");
2280
+ initialWindowLoadedRef.current = false;
2281
+ reconcileAfterPageResumeRef.current = false;
2282
+ }
2283
+ const generation = generationRef.current + 1;
2284
+ generationRef.current = generation;
2285
+ if (!sessionId || !streamEnabled) {
2286
+ if (sessionId && enabled && !pageLive && !fullReplay && viewModeRef.current === "live" && initialWindowLoadedRef.current) {
2287
+ reconcileAfterPageResumeRef.current = true;
2288
+ }
2289
+ setConnectionState("idle");
2290
+ return;
2291
+ }
2292
+ if (viewMode === "history") {
2293
+ reconcileAfterPageResumeRef.current = false;
2294
+ setConnectionState("idle");
2295
+ setInitialLoading(false);
2296
+ return;
2297
+ }
2298
+ const reconcileForegroundResume = reconcileAfterPageResumeRef.current && !fullReplay;
2299
+ reconcileAfterPageResumeRef.current = false;
2300
+ const controller = new AbortController();
2301
+ const isCurrent = () => generationRef.current === generation && !controller.signal.aborted;
2302
+ streamAbortRef.current = controller;
2303
+ let pending = [];
2304
+ let pendingBytes = 2;
2305
+ let flushTimer = null;
2306
+ const flush = () => {
2307
+ if (flushTimer !== null) {
2308
+ clearTimeout(flushTimer);
2309
+ flushTimer = null;
2310
+ }
2311
+ if (!isCurrent()) {
2312
+ pending = [];
2313
+ pendingBytes = 2;
2314
+ return;
2315
+ }
2316
+ if (pending.length === 0) {
2317
+ return;
2318
+ }
2319
+ const batch = pending;
2320
+ pending = [];
2321
+ pendingBytes = 2;
2322
+ const lastInBatch = batch[batch.length - 1];
2323
+ if (lastInBatch) {
2324
+ const batchResumeSequence = Math.max(...batch.map(eventResumeSequence));
2325
+ lastSequenceRef.current = Math.max(lastSequenceRef.current, batchResumeSequence);
2326
+ streamResumeSequenceRef.current = Math.max(
2327
+ streamResumeSequenceRef.current,
2328
+ batchResumeSequence
2329
+ );
2330
+ }
2331
+ const status = observeSessionStatus(batch, sessionStatusRef);
2332
+ if (status !== void 0) {
2333
+ setSessionStatusProjection(status);
2334
+ }
2335
+ const current = eventWindowRef.current;
2336
+ const next = boundBrowserSessionEventWindow([...current.events, ...batch]);
2337
+ const retained = {
2338
+ ...next,
2339
+ truncated: current.truncated || next.truncated
2340
+ };
2341
+ eventWindowRef.current = retained;
2342
+ setEventWindow(retained);
2343
+ oldestSequenceRef.current = retained.events[0]?.sequence ?? null;
2344
+ newestSequenceRef.current = maxResumeSequenceOrNull(retained.events);
2345
+ if (retained.truncated) {
2346
+ hasOlderRef.current = true;
2347
+ setHasOlder(true);
2348
+ }
2349
+ };
2350
+ const scheduleFlush = () => {
2351
+ if (!isCurrent()) return;
2352
+ flushTimer ??= setTimeout(flush, 16);
2353
+ };
2354
+ void (async () => {
2355
+ try {
2356
+ if (reconcileForegroundResume) {
2357
+ setConnectionState("connecting");
2358
+ const cursor = streamResumeSequenceRef.current;
2359
+ let plan = { kind: "resume" };
2360
+ try {
2361
+ plan = await planForegroundCatchup(client, workspaceId, sessionId, cursor, {
2362
+ signal: controller.signal
2363
+ });
2364
+ } catch {
2365
+ if (controller.signal.aborted) return;
2366
+ plan = { kind: "resume" };
2367
+ }
2368
+ if (!isCurrent()) return;
2369
+ if (plan.kind === "append") {
2370
+ const current = eventWindowRef.current;
2371
+ assertAppendOrder(current.events, plan.events);
2372
+ const status = observeSessionStatus(plan.events, sessionStatusRef);
2373
+ const next = boundBrowserSessionEventWindow([...current.events, ...plan.events]);
2374
+ const retained = {
2375
+ ...next,
2376
+ truncated: current.truncated || next.truncated
2377
+ };
2378
+ eventWindowRef.current = retained;
2379
+ oldestSequenceRef.current = retained.events[0]?.sequence ?? null;
2380
+ newestSequenceRef.current = maxResumeSequenceOrNull(retained.events);
2381
+ lastSequenceRef.current = Math.max(lastSequenceRef.current, plan.resumeSequence);
2382
+ streamResumeSequenceRef.current = Math.max(
2383
+ streamResumeSequenceRef.current,
2384
+ plan.resumeSequence
2385
+ );
2386
+ if (status !== void 0) {
2387
+ setSessionStatusProjection(status);
2388
+ }
2389
+ setEventWindow(retained);
2390
+ if (retained.truncated) {
2391
+ hasOlderRef.current = true;
2392
+ setHasOlder(true);
2393
+ }
2394
+ } else if (plan.kind === "reload") {
2395
+ initialWindowLoadedRef.current = false;
2396
+ setError(null);
2397
+ }
2398
+ }
2399
+ if (!fullReplay && !initialWindowLoadedRef.current) {
2400
+ setConnectionState("connecting");
2401
+ const window = await loadEventWindow(client, workspaceId, sessionId, {
2402
+ before: Number.MAX_SAFE_INTEGER,
2403
+ pageSize: SESSION_HISTORY_PAGE_SIZE,
2404
+ targetGroups: Number.POSITIVE_INFINITY,
2405
+ maxFetches: INITIAL_FETCH_CAP,
2406
+ boundaryPageCap: BOUNDARY_PAGE_CAP,
2407
+ signal: controller.signal
2408
+ });
2409
+ if (!isCurrent()) {
2410
+ return;
2411
+ }
2412
+ const status = observeSessionStatus(window.events, sessionStatusRef);
2413
+ const retained = boundBrowserSessionEventWindow(window.events);
2414
+ navigationGenerationRef.current += 1;
2415
+ loadingOlderRef.current = false;
2416
+ loadingNewerRef.current = false;
2417
+ loadingOldestRef.current = false;
2418
+ setLoadingOlder(false);
2419
+ setLoadingNewer(false);
2420
+ setLoadingOldest(false);
2421
+ eventWindowRef.current = retained;
2422
+ oldestSequenceRef.current = retained.events[0]?.sequence ?? window.oldestSequence;
2423
+ newestSequenceRef.current = maxResumeSequenceOrNull(retained.events) ?? window.newestSequence;
2424
+ hasOlderRef.current = window.hasOlder || retained.truncated;
2425
+ hasNewerRef.current = false;
2426
+ lastSequenceRef.current = window.newestSequence;
2427
+ streamResumeSequenceRef.current = window.newestSequence;
2428
+ initialWindowLoadedRef.current = true;
2429
+ if (status !== void 0) {
2430
+ setSessionStatusProjection(status);
2431
+ }
2432
+ setEventWindow(retained);
2433
+ setHasOlder(window.hasOlder || retained.truncated);
2434
+ setHasNewer(false);
2435
+ setInitialLoading(false);
2436
+ }
2437
+ if (fullReplay) {
2438
+ setInitialLoading(false);
2439
+ }
2440
+ const stream = client.streamEvents(workspaceId, sessionId, {
2441
+ after: streamResumeSequenceRef.current,
2442
+ signal: controller.signal,
2443
+ onOpen: () => {
2444
+ void Promise.resolve().then(() => reconcileSession(sessionId)).catch((cause) => {
2445
+ if (isCurrent()) {
2446
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
2447
+ }
2448
+ });
2449
+ },
2450
+ onStateChange: (state) => {
2451
+ if (isCurrent()) {
2452
+ setConnectionState(state);
2453
+ }
2454
+ }
2455
+ });
2456
+ for await (const event of stream) {
2457
+ if (!isCurrent()) break;
2458
+ const boundedEvent = boundBrowserLegacyEvent(event);
2459
+ const boundedEventBytes = browserJsonBytes(boundedEvent);
2460
+ const separatorBytes = pending.length === 0 ? 0 : 1;
2461
+ if (pending.length > 0 && (pending.length >= SESSION_EVENT_BROWSER_PENDING_MAX_COUNT || pendingBytes + separatorBytes + boundedEventBytes > SESSION_EVENT_BROWSER_PENDING_MAX_BYTES)) {
2462
+ flush();
2463
+ }
2464
+ pending.push(boundedEvent);
2465
+ pendingBytes += (pending.length === 1 ? 0 : 1) + boundedEventBytes;
2466
+ if (pending.length >= SESSION_EVENT_BROWSER_PENDING_MAX_COUNT || pendingBytes >= SESSION_EVENT_BROWSER_PENDING_MAX_BYTES) {
2467
+ flush();
2468
+ } else {
2469
+ scheduleFlush();
2470
+ }
2471
+ }
2472
+ if (isCurrent()) {
2473
+ flush();
2474
+ setConnectionState("ended");
2475
+ }
2476
+ } catch (cause) {
2477
+ if (isCurrent()) {
2478
+ flush();
2479
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
2480
+ setConnectionState("error");
2481
+ setInitialLoading(false);
2482
+ }
2483
+ }
2484
+ })();
2485
+ return () => {
2486
+ controller.abort();
2487
+ if (generationRef.current === generation) {
2488
+ generationRef.current = generation + 1;
2489
+ }
2490
+ if (streamAbortRef.current === controller) {
2491
+ streamAbortRef.current = null;
2492
+ }
2493
+ if (flushTimer !== null) {
2494
+ clearTimeout(flushTimer);
2495
+ }
2496
+ };
2497
+ }, [
2498
+ client,
2499
+ workspaceId,
2500
+ sessionId,
2501
+ after,
2502
+ enabled,
2503
+ pageLive,
2504
+ streamEnabled,
2505
+ fullReplay,
2506
+ streamKey,
2507
+ streamEpoch,
2508
+ viewMode,
2509
+ reconcileSession
2510
+ ]);
2511
+ const navigationBusy = () => loadingOlderRef.current || loadingNewerRef.current || loadingOldestRef.current || loadingLatestRef.current;
2512
+ const loadOlder = useCallback(
2513
+ () => createOlderHistoryLoadReceipt(async (markCommitted) => {
2514
+ if (!sessionId || navigationBusy() || !hasOlderRef.current) {
2515
+ return false;
2516
+ }
2517
+ const before = oldestSequenceRef.current;
2518
+ if (before === null) {
2519
+ oldestSequenceRef.current = null;
2520
+ hasOlderRef.current = false;
2521
+ setHasOlder(false);
2522
+ return false;
2523
+ }
2524
+ const generation = navigationGenerationRef.current;
2525
+ loadingOlderRef.current = true;
2526
+ setLoadingOlder(true);
2527
+ let published = false;
2528
+ try {
2529
+ const window = await loadEventWindow(client, workspaceId, sessionId, {
2530
+ before,
2531
+ pageSize: SESSION_HISTORY_PAGE_SIZE,
2532
+ targetGroups: OLDER_GROUP_TARGET,
2533
+ maxFetches: OLDER_FETCH_CAP
2534
+ });
2535
+ if (navigationGenerationRef.current !== generation) {
2536
+ return false;
2537
+ }
2538
+ if (window.events.length === 0) {
2539
+ oldestSequenceRef.current = null;
2540
+ hasOlderRef.current = false;
2541
+ setHasOlder(false);
2542
+ return false;
2543
+ }
2544
+ const current = eventWindowRef.current;
2545
+ assertPrependOrder(current.events, window.events);
2546
+ streamAbortRef.current?.abort();
2547
+ const status = observeSessionStatus(window.events, sessionStatusRef);
2548
+ const next = boundBrowserSessionEventWindow([...window.events, ...current.events], {
2549
+ direction: "oldest"
2550
+ });
2551
+ const retained = {
2552
+ ...next,
2553
+ truncated: current.truncated || next.truncated
2554
+ };
2555
+ const retainedOldest = retained.events[0]?.sequence ?? null;
2556
+ if (retainedOldest === null || retainedOldest >= before) {
2557
+ throw new Error("@opengeni/react: loadOlder made no durable sequence progress");
2558
+ }
2559
+ const retainedNewest = maxResumeSequenceOrNull(retained.events);
2560
+ const previousNewest = maxResumeSequenceOrNull(current.events);
2561
+ eventWindowRef.current = retained;
2562
+ markCommitted();
2563
+ oldestSequenceRef.current = retainedOldest;
2564
+ newestSequenceRef.current = retainedNewest;
2565
+ streamResumeSequenceRef.current = maxResumeSequence(retained.events);
2566
+ const olderStillAvailable = window.hasOlder;
2567
+ hasOlderRef.current = olderStillAvailable;
2568
+ const evictedLiveTail = previousNewest !== null && retainedNewest !== null && retainedNewest < previousNewest;
2569
+ let newer = null;
2570
+ let enterHistory = false;
2571
+ let reconnectLive = false;
2572
+ if (viewModeRef.current === "history" || evictedLiveTail) {
2573
+ const highWater = lastSequenceRef.current;
2574
+ newer = retainedNewest !== null && (retainedNewest < highWater || retained.truncated);
2575
+ hasNewerRef.current = newer;
2576
+ if (evictedLiveTail) {
2577
+ viewModeRef.current = "history";
2578
+ enterHistory = true;
2579
+ }
2580
+ } else {
2581
+ reconnectLive = true;
2582
+ }
2583
+ loadingOlderRef.current = false;
2584
+ startTransition(() => {
2585
+ if (status !== void 0) {
2586
+ setSessionStatusProjection(status);
2587
+ }
2588
+ setEventWindow(retained);
2589
+ setHasOlder(olderStillAvailable);
2590
+ if (newer !== null) {
2591
+ setHasNewer(newer);
2592
+ }
2593
+ if (enterHistory) {
2594
+ setViewMode("history");
2595
+ } else if (reconnectLive) {
2596
+ setStreamEpoch((epoch) => epoch + 1);
2597
+ }
2598
+ setLoadingOlder(false);
2599
+ });
2600
+ published = true;
2601
+ return olderStillAvailable;
2602
+ } finally {
2603
+ if (!published && navigationGenerationRef.current === generation) {
2604
+ loadingOlderRef.current = false;
2605
+ setLoadingOlder(false);
2606
+ }
2607
+ }
2608
+ }),
2609
+ [client, workspaceId, sessionId]
2610
+ );
2611
+ const loadOldest = useCallback(async () => {
2612
+ if (!sessionId || navigationBusy() || !hasOlderRef.current) {
2613
+ return false;
2614
+ }
2615
+ const generation = navigationGenerationRef.current;
2616
+ loadingOldestRef.current = true;
2617
+ setLoadingOldest(true);
2618
+ let published = false;
2619
+ try {
2620
+ const window = await loadForwardEventWindow(client, workspaceId, sessionId, {
2621
+ after: 0,
2622
+ pageSize: SESSION_HISTORY_PAGE_SIZE,
2623
+ targetGroups: OLDEST_GROUP_TARGET,
2624
+ maxFetches: OLDEST_FETCH_CAP
2625
+ });
2626
+ if (navigationGenerationRef.current !== generation) {
2627
+ return false;
2628
+ }
2629
+ if (window.events.length === 0) {
2630
+ hasOlderRef.current = false;
2631
+ setHasOlder(false);
2632
+ return false;
2633
+ }
2634
+ streamAbortRef.current?.abort();
2635
+ const status = observeSessionStatus(window.events, sessionStatusRef);
2636
+ const retained = boundBrowserSessionEventWindow(window.events, { direction: "oldest" });
2637
+ const retainedOldest = retained.events[0]?.sequence ?? null;
2638
+ const retainedNewest = maxResumeSequenceOrNull(retained.events);
2639
+ eventWindowRef.current = retained;
2640
+ oldestSequenceRef.current = retainedOldest;
2641
+ newestSequenceRef.current = retainedNewest;
2642
+ hasOlderRef.current = false;
2643
+ const highWater = Math.max(lastSequenceRef.current, retainedNewest ?? 0);
2644
+ lastSequenceRef.current = highWater;
2645
+ const newer = window.hasNewer || retained.truncated || retainedNewest !== null && retainedNewest < highWater;
2646
+ hasNewerRef.current = newer;
2647
+ initialWindowLoadedRef.current = true;
2648
+ viewModeRef.current = "history";
2649
+ loadingOldestRef.current = false;
2650
+ startTransition(() => {
2651
+ if (status !== void 0) {
2652
+ setSessionStatusProjection(status);
2653
+ }
2654
+ setEventWindow(retained);
2655
+ setHasOlder(false);
2656
+ setHasNewer(newer);
2657
+ setViewMode("history");
2658
+ setLoadingOldest(false);
2659
+ });
2660
+ published = true;
2661
+ return newer;
2662
+ } finally {
2663
+ if (!published && navigationGenerationRef.current === generation) {
2664
+ loadingOldestRef.current = false;
2665
+ setLoadingOldest(false);
2666
+ }
2667
+ }
2668
+ }, [client, workspaceId, sessionId]);
2669
+ const loadNewer = useCallback(async () => {
2670
+ if (!sessionId || navigationBusy() || !hasNewerRef.current) {
2671
+ return false;
2672
+ }
2673
+ const afterSequence = newestSequenceRef.current;
2674
+ if (afterSequence === null) {
2675
+ hasNewerRef.current = false;
2676
+ setHasNewer(false);
2677
+ return false;
2678
+ }
2679
+ const generation = navigationGenerationRef.current;
2680
+ loadingNewerRef.current = true;
2681
+ setLoadingNewer(true);
2682
+ let published = false;
2683
+ try {
2684
+ const window = await loadForwardEventWindow(client, workspaceId, sessionId, {
2685
+ after: afterSequence,
2686
+ pageSize: SESSION_HISTORY_PAGE_SIZE,
2687
+ targetGroups: NEWER_GROUP_TARGET,
2688
+ maxFetches: NEWER_FETCH_CAP
2689
+ });
2690
+ if (navigationGenerationRef.current !== generation) {
2691
+ return false;
2692
+ }
2693
+ if (window.events.length === 0) {
2694
+ hasNewerRef.current = false;
2695
+ setHasNewer(false);
2696
+ streamResumeSequenceRef.current = afterSequence;
2697
+ viewModeRef.current = "live";
2698
+ setViewMode("live");
2699
+ setStreamEpoch((epoch) => epoch + 1);
2700
+ return false;
2701
+ }
2702
+ const current = eventWindowRef.current;
2703
+ assertAppendOrder(current.events, window.events);
2704
+ const status = observeSessionStatus(window.events, sessionStatusRef);
2705
+ const previousOldest = current.events[0]?.sequence ?? null;
2706
+ const next = boundBrowserSessionEventWindow([...current.events, ...window.events], {
2707
+ direction: "newest"
2708
+ });
2709
+ const retained = {
2710
+ ...next,
2711
+ truncated: current.truncated || next.truncated || window.hasNewer
2712
+ };
2713
+ const retainedOldest = retained.events[0]?.sequence ?? null;
2714
+ const retainedNewest = maxResumeSequenceOrNull(retained.events);
2715
+ if (retainedNewest === null || retainedNewest <= afterSequence) {
2716
+ throw new Error("@opengeni/react: loadNewer made no durable sequence progress");
2717
+ }
2718
+ eventWindowRef.current = retained;
2719
+ oldestSequenceRef.current = retainedOldest;
2720
+ newestSequenceRef.current = retainedNewest;
2721
+ if (retainedOldest !== null && (previousOldest === null || retainedOldest > previousOldest || retained.truncated && retained.events[0]?.type !== "session.created" && retainedOldest > 1)) {
2722
+ hasOlderRef.current = true;
2723
+ }
2724
+ const rearmedOlder = hasOlderRef.current;
2725
+ const highWater = Math.max(lastSequenceRef.current, retainedNewest);
2726
+ lastSequenceRef.current = highWater;
2727
+ const newer = window.hasNewer || retainedNewest < highWater;
2728
+ hasNewerRef.current = newer;
2729
+ let resumeLive = false;
2730
+ if (!newer) {
2731
+ streamResumeSequenceRef.current = retainedNewest;
2732
+ viewModeRef.current = "live";
2733
+ resumeLive = true;
2734
+ }
2735
+ loadingNewerRef.current = false;
2736
+ startTransition(() => {
2737
+ if (status !== void 0) {
2738
+ setSessionStatusProjection(status);
2739
+ }
2740
+ setEventWindow(retained);
2741
+ setHasOlder(rearmedOlder);
2742
+ setHasNewer(newer);
2743
+ if (resumeLive) {
2744
+ setViewMode("live");
2745
+ setStreamEpoch((epoch) => epoch + 1);
2746
+ }
2747
+ setLoadingNewer(false);
2748
+ });
2749
+ published = true;
2750
+ return newer;
2751
+ } finally {
2752
+ if (!published && navigationGenerationRef.current === generation) {
2753
+ loadingNewerRef.current = false;
2754
+ setLoadingNewer(false);
2755
+ }
2756
+ }
2757
+ }, [client, workspaceId, sessionId]);
2758
+ const jumpToLatest = useCallback(async () => {
2759
+ if (!sessionId || navigationBusy()) {
2760
+ return;
2761
+ }
2762
+ loadingLatestRef.current = true;
2763
+ setLoadingLatest(true);
2764
+ let published = false;
2765
+ try {
2766
+ streamAbortRef.current?.abort();
2767
+ hasNewerRef.current = false;
2768
+ hasOlderRef.current = false;
2769
+ newestSequenceRef.current = null;
2770
+ oldestSequenceRef.current = null;
2771
+ eventWindowRef.current = EMPTY_EVENT_WINDOW;
2772
+ initialWindowLoadedRef.current = false;
2773
+ streamResumeSequenceRef.current = after;
2774
+ viewModeRef.current = "live";
2775
+ loadingLatestRef.current = false;
2776
+ startTransition(() => {
2777
+ setHasNewer(false);
2778
+ setHasOlder(false);
2779
+ setEventWindow(EMPTY_EVENT_WINDOW);
2780
+ setInitialLoading(true);
2781
+ setViewMode("live");
2782
+ setStreamEpoch((epoch) => epoch + 1);
2783
+ setLoadingLatest(false);
2784
+ });
2785
+ published = true;
2786
+ await Promise.resolve();
2787
+ } finally {
2788
+ if (!published) {
2789
+ loadingLatestRef.current = false;
2790
+ setLoadingLatest(false);
2791
+ }
2792
+ }
2793
+ }, [after, sessionId]);
2794
+ const identityMatches = stateStreamKey === streamKey;
2795
+ const visibleEvents = identityMatches ? eventWindow.events : EMPTY_EVENTS;
2796
+ const timeline = useMemo(() => buildTimeline(visibleEvents), [visibleEvents]);
2797
+ return {
2798
+ events: visibleEvents,
2799
+ timeline,
2800
+ sessionStatus: identityMatches ? sessionStatusProjection : null,
2801
+ connectionState: identityMatches ? connectionState : "idle",
2802
+ lastSequence: identityMatches ? lastSequenceRef.current : after,
2803
+ windowBytes: identityMatches ? eventWindow.bytes : 2,
2804
+ windowTruncated: identityMatches ? eventWindow.truncated : false,
2805
+ initialLoading: fullReplay ? false : identityMatches ? initialLoading : true,
2806
+ hasOlder: !identityMatches ? false : hasOlder,
2807
+ loadingOlder: !identityMatches ? false : loadingOlder,
2808
+ loadOlder,
2809
+ hasNewer: !identityMatches ? false : hasNewer,
2810
+ loadingNewer: !identityMatches ? false : loadingNewer,
2811
+ loadNewer,
2812
+ loadingOldest: !identityMatches ? false : loadingOldest,
2813
+ loadOldest,
2814
+ loadingLatest: !identityMatches ? false : loadingLatest,
2815
+ jumpToLatest,
2816
+ error: identityMatches ? error : null
2817
+ };
2818
+ }
2819
+ function boundBrowserSessionEventWindow(events, options = {}) {
2820
+ const maxBytes = Math.max(1024, options.maxBytes ?? SESSION_EVENT_BROWSER_MAX_BYTES);
2821
+ const maxCount = Math.max(1, Math.floor(options.maxCount ?? SESSION_EVENT_BROWSER_MAX_COUNT));
2822
+ const safe = events.map(boundBrowserLegacyEvent);
2823
+ const selected = [];
2824
+ let bytes = 2;
2825
+ const direction = options.direction ?? "newest";
2826
+ const start = direction === "newest" ? safe.length - 1 : 0;
2827
+ const end = direction === "newest" ? Math.max(-1, safe.length - maxCount - 1) : safe.length;
2828
+ const step = direction === "newest" ? -1 : 1;
2829
+ for (let index = start; index !== end && selected.length < maxCount; index += step) {
2830
+ const event = safe[index];
2831
+ const eventBytes = browserJsonBytes(event);
2832
+ const separator = selected.length === 0 ? 0 : 1;
2833
+ if (bytes + separator + eventBytes > maxBytes) break;
2834
+ selected.push(event);
2835
+ bytes += separator + eventBytes;
2836
+ }
2837
+ if (direction === "newest") selected.reverse();
2838
+ return {
2839
+ events: selected,
2840
+ bytes,
2841
+ truncated: selected.length < safe.length
2842
+ };
2843
+ }
2844
+ function boundBrowserLegacyEvent(event) {
2845
+ const serialized = browserSerialize(event);
2846
+ const originalBytes = serialized.serializable ? encoder.encode(serialized.value).byteLength : null;
2847
+ const typeIsSafe = browserUtf8Bytes(event.type) <= BROWSER_EVENT_TYPE_MAX_BYTES && !event.type.includes("\n") && !event.type.includes("\r");
2848
+ const clientEventId = boundBrowserOptionalText(
2849
+ event.clientEventId,
2850
+ BROWSER_EVENT_CLIENT_ID_MAX_BYTES
2851
+ );
2852
+ const duplicateReason = boundBrowserOptionalText(
2853
+ event.duplicateReason,
2854
+ BROWSER_EVENT_DUPLICATE_REASON_MAX_BYTES
2855
+ );
2856
+ if (serialized.serializable && originalBytes !== null && originalBytes <= SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES && typeIsSafe && clientEventId === event.clientEventId && duplicateReason === event.duplicateReason) {
2857
+ return event;
2858
+ }
2859
+ const envelopeProjection = [
2860
+ !typeIsSafe ? browserEnvelopeFieldProjection("type", event.type, "session.event.envelope_omitted") : null,
2861
+ clientEventId !== event.clientEventId ? browserEnvelopeFieldProjection("clientEventId", event.clientEventId, clientEventId) : null,
2862
+ duplicateReason !== event.duplicateReason ? browserEnvelopeFieldProjection("duplicateReason", event.duplicateReason, duplicateReason) : null
2863
+ ].filter((field) => field !== null);
2864
+ const payloadSerialization = browserSerialize(event.payload);
2865
+ const payloadBytes = payloadSerialization.serializable ? encoder.encode(payloadSerialization.value).byteLength : null;
2866
+ const preview = truncateBrowserUtf8Middle(
2867
+ payloadSerialization.value,
2868
+ BROWSER_EVENT_PAYLOAD_PREVIEW_MAX_BYTES
2869
+ );
2870
+ const truncation = {
2871
+ truncated: true,
2872
+ surface: "browser_legacy_guard",
2873
+ reason: serialized.serializable ? "event_envelope_bytes_exceeded" : "event_not_serializable",
2874
+ originalBytes,
2875
+ deliveredBytes: 0,
2876
+ omittedBytes: originalBytes,
2877
+ estimatedOriginalTokens: originalBytes === null ? null : Math.ceil(originalBytes / 4),
2878
+ estimatedDeliveredTokens: 0,
2879
+ fullEvidence: { available: false, reason: "not_retained" },
2880
+ details: [
2881
+ {
2882
+ path: "$.payload",
2883
+ kind: "payload_preview",
2884
+ originalBytes: payloadBytes,
2885
+ deliveredBytes: browserUtf8Bytes(preview)
2886
+ }
2887
+ ]
2888
+ };
2889
+ const payload = {
2890
+ ...browserPayloadIdentity(event.payload),
2891
+ preview,
2892
+ ...envelopeProjection.length > 0 ? {
2893
+ originalType: boundBrowserText(event.type, BROWSER_EVENT_TYPE_MAX_BYTES),
2894
+ envelopeProjection: {
2895
+ truncated: true,
2896
+ surface: "browser_legacy_guard",
2897
+ fields: envelopeProjection
2898
+ }
2899
+ } : {},
2900
+ truncation
2901
+ };
2902
+ const bounded = {
2903
+ id: boundBrowserText(event.id, BROWSER_EVENT_ID_MAX_BYTES),
2904
+ workspaceId: boundBrowserText(event.workspaceId, BROWSER_EVENT_ID_MAX_BYTES),
2905
+ sessionId: boundBrowserText(event.sessionId, BROWSER_EVENT_ID_MAX_BYTES),
2906
+ sequence: event.sequence,
2907
+ type: typeIsSafe ? event.type : "session.event.envelope_omitted",
2908
+ payload,
2909
+ occurredAt: boundBrowserText(event.occurredAt, BROWSER_EVENT_ID_MAX_BYTES),
2910
+ clientEventId,
2911
+ turnId: boundBrowserOptionalText(event.turnId, BROWSER_EVENT_ID_MAX_BYTES),
2912
+ turnGeneration: event.turnGeneration,
2913
+ turnAttemptId: boundBrowserOptionalText(event.turnAttemptId, BROWSER_EVENT_ID_MAX_BYTES),
2914
+ turnAssociation: event.turnAssociation,
2915
+ duplicateOfEventId: boundBrowserOptionalText(
2916
+ event.duplicateOfEventId,
2917
+ BROWSER_EVENT_ID_MAX_BYTES
2918
+ ),
2919
+ duplicateReason
2920
+ };
2921
+ settleBrowserEventTruncation(bounded, truncation);
2922
+ if (browserJsonBytes(bounded) > SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES) {
2923
+ payload.preview = truncateBrowserUtf8Middle(String(payload.preview), 4 * 1024);
2924
+ truncation.details = truncation.details.slice(0, 1);
2925
+ settleBrowserEventTruncation(bounded, truncation);
2926
+ }
2927
+ if (browserJsonBytes(bounded) > SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES) {
2928
+ bounded.clientEventId = null;
2929
+ bounded.duplicateReason = null;
2930
+ payload.preview = "[legacy event omitted at the browser byte boundary]";
2931
+ settleBrowserEventTruncation(bounded, truncation);
2932
+ }
2933
+ return bounded;
2934
+ }
2935
+ function browserJsonBytes(value) {
2936
+ return encoder.encode(browserSerialize(value).value).byteLength;
2937
+ }
2938
+ function browserSerialize(value) {
2939
+ try {
2940
+ const serialized = JSON.stringify(value);
2941
+ return { value: serialized === void 0 ? "null" : serialized, serializable: true };
2942
+ } catch {
2943
+ return {
2944
+ value: '"[unserializable event payload omitted at browser boundary]"',
2945
+ serializable: false
2946
+ };
2947
+ }
2948
+ }
2949
+ function browserPayloadIdentity(payload) {
2950
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return {};
2951
+ const record = payload;
2952
+ const identity = {};
2953
+ for (const field of BROWSER_EVENT_PAYLOAD_IDENTITY_FIELDS) {
2954
+ const value = record[field];
2955
+ if (typeof value === "string") {
2956
+ identity[field] = boundBrowserText(value, BROWSER_EVENT_ID_MAX_BYTES);
2957
+ } else if (typeof value === "number" || typeof value === "boolean" || value === null) {
2958
+ identity[field] = value;
2959
+ }
2960
+ }
2961
+ return identity;
2962
+ }
2963
+ function browserEnvelopeFieldProjection(field, original, delivered) {
2964
+ return {
2965
+ field,
2966
+ originalBytes: typeof original === "string" ? browserUtf8Bytes(original) : 0,
2967
+ deliveredBytes: typeof delivered === "string" ? browserUtf8Bytes(delivered) : 0
2968
+ };
2969
+ }
2970
+ function boundBrowserOptionalText(value, maxBytes) {
2971
+ return typeof value === "string" ? boundBrowserText(value, maxBytes) : value;
2972
+ }
2973
+ function boundBrowserText(value, maxBytes) {
2974
+ const bytes = encoder.encode(value);
2975
+ if (bytes.byteLength <= maxBytes) return value;
2976
+ const marker = "\u2026[truncated]";
2977
+ const prefixBudget = Math.max(0, maxBytes - browserUtf8Bytes(marker));
2978
+ let prefixEnd = Math.min(prefixBudget, bytes.byteLength);
2979
+ while (prefixEnd > 0 && prefixEnd < bytes.byteLength && isBrowserUtf8Continuation(bytes[prefixEnd])) {
2980
+ prefixEnd -= 1;
2981
+ }
2982
+ return `${decoder.decode(bytes.subarray(0, prefixEnd))}${marker}`;
2983
+ }
2984
+ function truncateBrowserUtf8Middle(value, maxBytes) {
2985
+ const bytes = encoder.encode(value);
2986
+ if (bytes.byteLength <= maxBytes) return value;
2987
+ const marker = `\u2026[${bytes.byteLength - maxBytes} bytes omitted]\u2026`;
2988
+ const contentBudget = Math.max(0, maxBytes - browserUtf8Bytes(marker));
2989
+ const leftBudget = Math.floor(contentBudget / 2);
2990
+ const rightBudget = contentBudget - leftBudget;
2991
+ let leftEnd = Math.min(leftBudget, bytes.byteLength);
2992
+ while (leftEnd > 0 && leftEnd < bytes.byteLength && isBrowserUtf8Continuation(bytes[leftEnd])) {
2993
+ leftEnd -= 1;
2994
+ }
2995
+ let rightStart = Math.max(0, bytes.byteLength - rightBudget);
2996
+ while (rightStart < bytes.byteLength && isBrowserUtf8Continuation(bytes[rightStart])) {
2997
+ rightStart += 1;
2998
+ }
2999
+ return `${decoder.decode(bytes.subarray(0, leftEnd))}${marker}${decoder.decode(bytes.subarray(rightStart))}`;
3000
+ }
3001
+ function settleBrowserEventTruncation(event, truncation) {
3002
+ for (let attempt = 0; attempt < 16; attempt += 1) {
3003
+ const deliveredBytes = browserJsonBytes(event);
3004
+ const omittedBytes = truncation.originalBytes === null ? null : Math.max(0, truncation.originalBytes - deliveredBytes);
3005
+ const estimatedDeliveredTokens = Math.ceil(deliveredBytes / 4);
3006
+ if (truncation.deliveredBytes === deliveredBytes && truncation.omittedBytes === omittedBytes && truncation.estimatedDeliveredTokens === estimatedDeliveredTokens) {
3007
+ return;
3008
+ }
3009
+ truncation.deliveredBytes = deliveredBytes;
3010
+ truncation.omittedBytes = omittedBytes;
3011
+ truncation.estimatedDeliveredTokens = estimatedDeliveredTokens;
3012
+ }
3013
+ throw new RangeError("Browser event byte accounting did not converge");
3014
+ }
3015
+ function browserUtf8Bytes(value) {
3016
+ return encoder.encode(value).byteLength;
3017
+ }
3018
+ function isBrowserUtf8Continuation(value) {
3019
+ return (value & 192) === 128;
3020
+ }
3021
+ function observeSessionStatus(events, ref) {
3022
+ let latest = null;
3023
+ for (const event of events) {
3024
+ const status = sessionStatusFromEvents([event]);
3025
+ if (status && (!latest || event.sequence >= latest.sequence)) {
3026
+ latest = { sequence: event.sequence, status };
3027
+ }
3028
+ }
3029
+ if (!latest || latest.sequence < ref.current.sequence) return void 0;
3030
+ ref.current = latest;
3031
+ return latest.status;
3032
+ }
3033
+ async function planForegroundCatchup(client, workspaceId, sessionId, cursor, options = {}) {
3034
+ if (options.signal?.aborted) throw abortError();
3035
+ const session = await client.getSession(workspaceId, sessionId, {
3036
+ fresh: true,
3037
+ ...options.signal ? { signal: options.signal } : {}
3038
+ });
3039
+ if (options.signal?.aborted) throw abortError();
3040
+ const durableHead = Math.max(cursor, session.lastSequence);
3041
+ const rawGap = durableHead - cursor;
3042
+ if (rawGap <= FOREGROUND_DIRECT_REPLAY_MAX_SEQUENCES) {
3043
+ return { kind: "resume" };
3044
+ }
3045
+ if (rawGap > FOREGROUND_COMPACT_PROBE_MAX_SEQUENCES) {
3046
+ return { kind: "reload" };
3047
+ }
3048
+ const page = await loadNextPage(client, workspaceId, sessionId, cursor, {
3049
+ pageSize: rawGap,
3050
+ ...options.signal ? { signal: options.signal } : {}
3051
+ });
3052
+ const events = page.filter((event) => eventResumeSequence(event) > cursor).map(boundBrowserLegacyEvent);
3053
+ if (events.length === 0) {
3054
+ return { kind: "reload" };
3055
+ }
3056
+ assertAscending(events);
3057
+ const resumeSequence = maxResumeSequence(events);
3058
+ if (resumeSequence < durableHead) {
3059
+ return { kind: "reload" };
3060
+ }
3061
+ if (events.length > FOREGROUND_COMPACT_CATCHUP_MAX_EVENTS || groupCount(events) > FOREGROUND_COMPACT_CATCHUP_MAX_GROUPS || browserJsonBytes(events) > FOREGROUND_COMPACT_CATCHUP_MAX_BYTES) {
3062
+ return { kind: "reload" };
3063
+ }
3064
+ return { kind: "append", events, resumeSequence };
3065
+ }
3066
+ async function loadEventWindow(client, workspaceId, sessionId, options) {
3067
+ let cursor = options.before;
3068
+ let buffer = [];
3069
+ let reachedStart = false;
3070
+ let fetches = 0;
3071
+ while (fetches < options.maxFetches) {
3072
+ if (buffer.length > 0 && groupCount(buffer) >= options.targetGroups) {
3073
+ break;
3074
+ }
3075
+ const page = await loadPreviousPage(client, workspaceId, sessionId, cursor, {
3076
+ pageSize: options.pageSize,
3077
+ ...options.signal ? { signal: options.signal } : {}
3078
+ });
3079
+ fetches += 1;
3080
+ if (page.length === 0) {
3081
+ reachedStart = true;
3082
+ break;
3083
+ }
3084
+ assertAscending(page);
3085
+ buffer = [...page, ...buffer];
3086
+ cursor = page[0].sequence;
3087
+ if (isLogStart(page[0])) {
3088
+ reachedStart = true;
3089
+ break;
3090
+ }
3091
+ }
3092
+ let snapPages = 0;
3093
+ while (!reachedStart && findBoundaryIndex(buffer) === -1 && snapPages < (options.boundaryPageCap ?? 0)) {
3094
+ const page = await loadPreviousPage(client, workspaceId, sessionId, cursor, {
3095
+ pageSize: options.pageSize,
3096
+ ...options.signal ? { signal: options.signal } : {}
3097
+ });
3098
+ fetches += 1;
3099
+ snapPages += 1;
3100
+ if (page.length === 0) {
3101
+ reachedStart = true;
3102
+ break;
3103
+ }
3104
+ assertAscending(page);
3105
+ buffer = [...page, ...buffer];
3106
+ cursor = page[0].sequence;
3107
+ if (isLogStart(page[0])) {
3108
+ reachedStart = true;
3109
+ break;
3110
+ }
3111
+ }
3112
+ if (!reachedStart) {
3113
+ const boundary = findBoundaryIndex(buffer);
3114
+ if (boundary > 0) {
3115
+ buffer = buffer.slice(boundary);
3116
+ }
3117
+ }
3118
+ const densityWindow = trimBackwardToGroupTarget(buffer, options.targetGroups);
3119
+ buffer = densityWindow.events;
3120
+ const oldest = buffer[0] ?? null;
3121
+ const newest = buffer[buffer.length - 1] ?? null;
3122
+ return {
3123
+ events: buffer,
3124
+ oldestSequence: oldest?.sequence ?? null,
3125
+ newestSequence: newest ? maxResumeSequence(buffer) : 0,
3126
+ hasOlder: buffer.length > 0 && (densityWindow.trimmed || !reachedStart && oldest?.type !== "session.created")
3127
+ };
3128
+ }
3129
+ function findBoundaryIndex(events) {
3130
+ for (let index = 0; index < events.length; index += 1) {
3131
+ if (isTurnBoundary2(events[index])) {
3132
+ return index;
3133
+ }
3134
+ }
3135
+ return -1;
3136
+ }
3137
+ function isTurnBoundary2(event) {
3138
+ return event.type === "session.created" || event.type === "user.message";
3139
+ }
3140
+ async function loadForwardEventWindow(client, workspaceId, sessionId, options) {
3141
+ let cursor = options.after;
3142
+ let buffer = [];
3143
+ let fetches = 0;
3144
+ let reachedEnd = false;
3145
+ while (fetches < options.maxFetches) {
3146
+ if (buffer.length > 0 && groupCount(buffer) >= options.targetGroups) {
3147
+ break;
3148
+ }
3149
+ const page = await loadNextPage(client, workspaceId, sessionId, cursor, {
3150
+ pageSize: options.pageSize,
3151
+ ...options.signal ? { signal: options.signal } : {}
3152
+ });
3153
+ fetches += 1;
3154
+ if (page.length === 0) {
3155
+ reachedEnd = true;
3156
+ break;
3157
+ }
3158
+ assertAscending(page);
3159
+ buffer = [...buffer, ...page];
3160
+ cursor = maxResumeSequence(page);
3161
+ }
3162
+ const densityWindow = trimForwardToGroupTarget(buffer, options.targetGroups);
3163
+ buffer = densityWindow.events;
3164
+ const oldest = buffer[0] ?? null;
3165
+ const newest = buffer[buffer.length - 1] ?? null;
3166
+ return {
3167
+ events: buffer,
3168
+ oldestSequence: oldest?.sequence ?? null,
3169
+ newestSequence: newest ? maxResumeSequence(buffer) : 0,
3170
+ hasNewer: buffer.length > 0 && (densityWindow.trimmed || !reachedEnd)
3171
+ };
3172
+ }
3173
+ function trimBackwardToGroupTarget(events, targetGroups) {
3174
+ if (!Number.isFinite(targetGroups) || targetGroups <= 0 || events.length === 0) {
3175
+ return { events, trimmed: false };
3176
+ }
3177
+ for (let index = events.length - 1; index > 0; index -= 1) {
3178
+ if (!isTurnBoundary2(events[index])) continue;
3179
+ const candidate = events.slice(index);
3180
+ if (groupCount(candidate) >= targetGroups) {
3181
+ return { events: candidate, trimmed: true };
3182
+ }
3183
+ }
3184
+ return { events, trimmed: false };
3185
+ }
3186
+ function trimForwardToGroupTarget(events, targetGroups) {
3187
+ if (!Number.isFinite(targetGroups) || targetGroups <= 0 || events.length === 0) {
3188
+ return { events, trimmed: false };
3189
+ }
3190
+ for (let index = 1; index < events.length; index += 1) {
3191
+ if (!isTurnBoundary2(events[index])) continue;
3192
+ const candidate = events.slice(0, index);
3193
+ if (groupCount(candidate) >= targetGroups) {
3194
+ return { events: candidate, trimmed: true };
3195
+ }
3196
+ }
3197
+ return { events, trimmed: false };
3198
+ }
3199
+ async function loadPreviousPage(client, workspaceId, sessionId, before, options) {
3200
+ if (options.signal?.aborted) {
3201
+ throw abortError();
3202
+ }
3203
+ const requested = options.pageSize;
3204
+ if (requested === 0) {
3205
+ return Object.assign([], { requested });
3206
+ }
3207
+ const page = await client.listEvents(workspaceId, sessionId, {
3208
+ before,
3209
+ limit: requested,
3210
+ compact: true,
3211
+ // Monitoring summaries omit correlation fields once a bounded durable
3212
+ // payload exceeds their preview threshold. The browser timeline needs the
3213
+ // exact stored payload so persisted tool outputs still match their
3214
+ // calls and expose truthful truncation telemetry after a reload.
3215
+ payloadMode: "full"
3216
+ });
3217
+ if (options.signal?.aborted) {
3218
+ throw abortError();
3219
+ }
3220
+ return Object.assign(page, { requested });
3221
+ }
3222
+ async function loadNextPage(client, workspaceId, sessionId, after, options) {
3223
+ if (options.signal?.aborted) {
3224
+ throw abortError();
3225
+ }
3226
+ const requested = options.pageSize;
3227
+ if (requested === 0) {
3228
+ return Object.assign([], { requested });
3229
+ }
3230
+ const page = await client.listEvents(workspaceId, sessionId, {
3231
+ after,
3232
+ limit: requested,
3233
+ compact: true,
3234
+ direction: "after",
3235
+ payloadMode: "full"
3236
+ });
3237
+ if (options.signal?.aborted) {
3238
+ throw abortError();
3239
+ }
3240
+ return Object.assign(page, { requested });
3241
+ }
3242
+ function groupCount(events) {
3243
+ if (events.length === 0) {
3244
+ return 0;
3245
+ }
3246
+ return groupTimeline(buildTimeline(events)).length;
3247
+ }
3248
+ function isLogStart(event) {
3249
+ return event.type === "session.created" || event.sequence <= 1;
3250
+ }
3251
+ function maxResumeSequence(events) {
3252
+ return events.reduce((max, event) => Math.max(max, eventResumeSequence(event)), 0);
3253
+ }
3254
+ function maxResumeSequenceOrNull(events) {
3255
+ return events.length > 0 ? maxResumeSequence(events) : null;
3256
+ }
3257
+ function eventResumeSequence(event) {
3258
+ const streamedCoverage = sessionEventStreamCoveredThrough(event);
3259
+ if (streamedCoverage !== null) return streamedCoverage;
3260
+ return typeof event.coveredThrough === "number" && Number.isSafeInteger(event.coveredThrough) && event.coveredThrough >= event.sequence ? event.coveredThrough : event.sequence;
3261
+ }
3262
+ function assertAscending(events) {
3263
+ for (let index = 1; index < events.length; index += 1) {
3264
+ if (events[index - 1].sequence >= events[index].sequence) {
3265
+ throw new Error("@opengeni/react: session events must be ordered by ascending sequence");
3266
+ }
3267
+ }
3268
+ }
3269
+ function assertPrependOrder(existing, older) {
3270
+ if (!shouldAssertDevelopment() || existing.length === 0 || older.length === 0) {
3271
+ return;
3272
+ }
3273
+ if (older[older.length - 1].sequence >= existing[0].sequence) {
3274
+ throw new Error("@opengeni/react: loadOlder returned overlapping session events");
3275
+ }
3276
+ }
3277
+ function assertAppendOrder(existing, newer) {
3278
+ if (!shouldAssertDevelopment() || existing.length === 0 || newer.length === 0) {
3279
+ return;
3280
+ }
3281
+ if (newer[0].sequence <= existing[existing.length - 1].sequence) {
3282
+ throw new Error("@opengeni/react: loadNewer returned overlapping session events");
3283
+ }
3284
+ }
3285
+ function shouldAssertDevelopment() {
3286
+ const processEnv = globalThis.process?.env;
3287
+ return processEnv !== void 0 && processEnv.NODE_ENV !== "production";
3288
+ }
3289
+ function abortError() {
3290
+ const error = new Error("The operation was aborted.");
3291
+ error.name = "AbortError";
3292
+ return error;
3293
+ }
3294
+
3295
+ // src/hooks/use-session.ts
3296
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
3297
+ function isTitleEvent(event) {
3298
+ return event.type === "session.title_set";
3299
+ }
3300
+ function isSessionDetailEvent(event) {
3301
+ return isTitleEvent(event) || event.type.startsWith("session.command.");
3302
+ }
3303
+ function useSession(sessionId, options = {}) {
3304
+ const { client, workspaceId, workspaceControlEvent, registerSessionReconciler } = useEmbeddedSessionRead(options);
3305
+ const enabled = (options.enabled ?? true) && Boolean(sessionId);
3306
+ const [override, setOverride] = useState2(null);
3307
+ const nextReadRevision = useRef2(0);
3308
+ const nextReadGeneration = useRef2(0);
3309
+ const beginRead = options.beginRead;
3310
+ const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
3311
+ const load = useCallback2(
3312
+ async (signal) => {
3313
+ if (!sessionId) {
3314
+ return null;
3315
+ }
3316
+ let readGeneration2 = 0;
3317
+ const fetched = await client.getSession(workspaceId, sessionId, {
3318
+ fresh: true,
3319
+ signal,
3320
+ onRequestStart: () => {
3321
+ readGeneration2 = beginRead?.() ?? ++nextReadGeneration.current;
3322
+ }
3323
+ });
3324
+ setOverride(null);
3325
+ return {
3326
+ session: fetched,
3327
+ revision: ++nextReadRevision.current,
3328
+ readGeneration: readGeneration2
3329
+ };
3330
+ },
3331
+ [beginRead, client, workspaceId, sessionId]
3332
+ );
3333
+ const { data, loading, error, refresh } = usePolledValue(load, {
3334
+ pollIntervalMs: options.pollIntervalMs,
3335
+ enabled
3336
+ });
3337
+ useEffect2(() => {
3338
+ if (enabled && workspaceControlEvent) void refresh();
3339
+ }, [enabled, refresh, workspaceControlEvent]);
3340
+ useEffect2(() => {
3341
+ if (!sessionId || !enabled) return;
3342
+ return registerSessionReconciler(sessionId, "session", refresh);
3343
+ }, [enabled, refresh, registerSessionReconciler, sessionId]);
3344
+ const base = data?.session ?? null;
3345
+ const readRevision = data?.revision ?? 0;
3346
+ const readGeneration = data?.readGeneration ?? 0;
3347
+ const session = useMemo2(
3348
+ () => base && override && override.id === base.id ? { ...base, title: override.title, titleSource: override.titleSource } : base,
3349
+ [base, override]
3350
+ );
3351
+ const sharedEvents = options.events;
3352
+ const onTitleEvent = useCallback2(
3353
+ (event) => {
3354
+ if (sharedEvents?.some(
3355
+ (item) => item.sessionId === sessionId && item.type.startsWith("session.command.") && item.sequence > (base?.lastSequence ?? -1)
3356
+ )) {
3357
+ void refresh();
3358
+ } else if (event.type.startsWith("session.command.")) {
3359
+ if (!base || event.sequence > base.lastSequence) void refresh();
3360
+ return;
3361
+ }
3362
+ if (!base || event.sequence <= base.lastSequence) {
3363
+ return;
3364
+ }
3365
+ const payload = event.payload ?? {};
3366
+ const title = payload.title;
3367
+ if (typeof title !== "string") {
3368
+ return;
3369
+ }
3370
+ const source = payload.source === "user" || payload.source === "agent" ? payload.source : null;
3371
+ setOverride((current) => {
3372
+ const next = current ?? base;
3373
+ return { ...next, title, titleSource: source };
3374
+ });
3375
+ },
3376
+ [base, refresh, sharedEvents, sessionId]
3377
+ );
3378
+ useSessionEventTrigger(client, workspaceId, sessionId, isSessionDetailEvent, onTitleEvent, {
3379
+ enabled,
3380
+ ...options.events !== void 0 ? { events: options.events } : {}
3381
+ });
3382
+ const updateTitle = useCallback2(
3383
+ async (title) => {
3384
+ if (!sessionId) {
3385
+ return null;
3386
+ }
3387
+ const result = await run(() => client.updateSession(workspaceId, sessionId, { title }));
3388
+ if (result) {
3389
+ setOverride(result);
3390
+ }
3391
+ return result;
3392
+ },
3393
+ [client, workspaceId, sessionId, run]
3394
+ );
3395
+ return {
3396
+ session,
3397
+ loading,
3398
+ error,
3399
+ readRevision,
3400
+ readGeneration,
3401
+ refresh,
3402
+ updateTitle,
3403
+ updating: mutating,
3404
+ mutationError,
3405
+ clearMutationError
3406
+ };
3407
+ }
3408
+
3409
+ // src/hooks/use-turn-queue.ts
3410
+ import { useCallback as useCallback3, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
3411
+ function isTurnQueueEvent(event) {
3412
+ 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.");
3413
+ }
3414
+ function queueSnapshotCovers(candidate, observed) {
3415
+ return Boolean(
3416
+ candidate && candidate.version >= observed.version && candidate.effectiveControl.controlVersion >= observed.effectiveControl.controlVersion
3417
+ );
3418
+ }
3419
+ var EMPTY_PENDING_BY_TURN = {};
3420
+ function useTurnQueue(sessionId, options = {}) {
3421
+ const { client, workspaceId, workspaceControlEvent, registerSessionReconciler } = useEmbeddedSession(options);
3422
+ const enabled = (options.enabled ?? true) && Boolean(sessionId);
3423
+ const targetKey = `${workspaceId}\0${sessionId ?? ""}`;
3424
+ const pageLive = usePageLiveActivity();
3425
+ const [snapshot, setSnapshot] = useState3(null);
3426
+ const [stateTargetKey, setStateTargetKey] = useState3(targetKey);
3427
+ const [loading, setLoading] = useState3(enabled);
3428
+ const [error, setError] = useState3(null);
3429
+ const [mutationError, setMutationError] = useState3(null);
3430
+ const [pendingByTurn, setPendingByTurn] = useState3({});
3431
+ const [acceptedSteers, setAcceptedSteers] = useState3([]);
3432
+ const pendingRef = useRef3({});
3433
+ const readGeneration = useRef3(0);
3434
+ const targetKeyRef = useRef3(targetKey);
3435
+ const snapshotRef = useRef3(null);
3436
+ useLayoutEffect(() => {
3437
+ if (targetKeyRef.current === targetKey) return;
3438
+ targetKeyRef.current = targetKey;
3439
+ readGeneration.current += 1;
3440
+ snapshotRef.current = null;
3441
+ pendingRef.current = {};
3442
+ setStateTargetKey(targetKey);
3443
+ setSnapshot(null);
3444
+ setLoading(enabled);
3445
+ setError(null);
3446
+ setMutationError(null);
3447
+ setPendingByTurn({});
3448
+ setAcceptedSteers([]);
3449
+ }, [enabled, targetKey]);
3450
+ useEffect3(() => {
3451
+ if (acceptedSteers.length === 0 || options.events === void 0) return;
3452
+ const durablyStartedTurnIds = new Set(
3453
+ options.events.flatMap(
3454
+ (event) => event.type === "turn.started" && event.turnId ? [event.turnId] : []
3455
+ )
3456
+ );
3457
+ if (durablyStartedTurnIds.size === 0) return;
3458
+ setAcceptedSteers(
3459
+ (current) => current.filter((steer) => !durablyStartedTurnIds.has(steer.turnId))
3460
+ );
3461
+ }, [acceptedSteers.length, options.events]);
3462
+ const acceptSnapshot = useCallback3(
3463
+ (ownedTargetKey, next) => {
3464
+ if (targetKeyRef.current !== ownedTargetKey) return false;
3465
+ const current = snapshotRef.current;
3466
+ if (next && current && (next.version < current.version || next.effectiveControl.controlVersion < current.effectiveControl.controlVersion)) {
3467
+ return false;
3468
+ }
3469
+ snapshotRef.current = next;
3470
+ setStateTargetKey(ownedTargetKey);
3471
+ setSnapshot(next);
3472
+ return true;
3473
+ },
3474
+ []
3475
+ );
3476
+ const load = useCallback3(
3477
+ async (rejectOnFailure = false) => {
3478
+ if (!sessionId) return;
3479
+ const ownedTargetKey = `${workspaceId}\0${sessionId}`;
3480
+ const ticket = ++readGeneration.current;
3481
+ try {
3482
+ const fetched = await client.getQueue(workspaceId, sessionId);
3483
+ if (targetKeyRef.current !== ownedTargetKey) return;
3484
+ const ownsLatestRead = ticket === readGeneration.current;
3485
+ if (rejectOnFailure) {
3486
+ const committed = acceptSnapshot(ownedTargetKey, fetched);
3487
+ if (!committed && !queueSnapshotCovers(snapshotRef.current, fetched)) {
3488
+ throw new TypeError("Queue reconciliation did not commit authoritative state");
3489
+ }
3490
+ setError(null);
3491
+ if (ownsLatestRead) setLoading(false);
3492
+ } else if (ownsLatestRead) {
3493
+ acceptSnapshot(ownedTargetKey, fetched);
3494
+ setError(null);
3495
+ setLoading(false);
3496
+ }
3497
+ } catch (cause) {
3498
+ if (targetKeyRef.current === ownedTargetKey && (ticket === readGeneration.current || rejectOnFailure)) {
3499
+ setError(asError(cause));
3500
+ if (ticket === readGeneration.current) setLoading(false);
3501
+ }
3502
+ if (rejectOnFailure) throw cause;
3503
+ }
3504
+ },
3505
+ [client, workspaceId, sessionId, acceptSnapshot]
3506
+ );
3507
+ useEffect3(() => {
3508
+ if (!enabled || !pageLive) {
3509
+ setLoading(false);
3510
+ return;
3511
+ }
3512
+ setLoading(true);
3513
+ const pollIntervalMs = options.pollIntervalMs;
3514
+ if (pollIntervalMs === void 0 || pollIntervalMs <= 0) {
3515
+ void load();
3516
+ return () => {
3517
+ readGeneration.current += 1;
3518
+ };
3519
+ }
3520
+ let cancelled = false;
3521
+ let timer = null;
3522
+ const schedule = () => {
3523
+ if (cancelled) return;
3524
+ timer = setTimeout(() => {
3525
+ timer = null;
3526
+ void load().finally(schedule);
3527
+ }, pollIntervalMs);
3528
+ };
3529
+ void load().finally(schedule);
3530
+ return () => {
3531
+ cancelled = true;
3532
+ if (timer !== null) clearTimeout(timer);
3533
+ readGeneration.current += 1;
3534
+ };
3535
+ }, [load, enabled, pageLive, options.pollIntervalMs]);
3536
+ useEffect3(() => {
3537
+ if (enabled && workspaceControlEvent) void load();
3538
+ }, [enabled, load, workspaceControlEvent]);
3539
+ useEffect3(() => {
3540
+ if (!sessionId || !enabled) return;
3541
+ return registerSessionReconciler(sessionId, "queue", load);
3542
+ }, [enabled, load, registerSessionReconciler, sessionId]);
3543
+ const scheduleRefresh = useDebouncedCallback(() => void load());
3544
+ useSessionEventTrigger(
3545
+ client,
3546
+ workspaceId,
3547
+ sessionId,
3548
+ isTurnQueueEvent,
3549
+ scheduleRefresh,
3550
+ {
3551
+ enabled,
3552
+ ...options.events !== void 0 ? { events: options.events } : {}
3553
+ },
3554
+ async () => await load(true)
3555
+ );
3556
+ const mutate = useCallback3(
3557
+ async (turnId, kind, command) => {
3558
+ const ownedTargetKey = targetKey;
3559
+ if (!sessionId || targetKeyRef.current !== ownedTargetKey || pendingRef.current[turnId]) {
3560
+ return null;
3561
+ }
3562
+ const current = snapshotRef.current;
3563
+ const turn = current?.items.find((candidate) => candidate.id === turnId);
3564
+ if (!current || !turn) return null;
3565
+ pendingRef.current = { ...pendingRef.current, [turnId]: kind };
3566
+ setStateTargetKey(ownedTargetKey);
3567
+ setPendingByTurn(pendingRef.current);
3568
+ setMutationError(null);
3569
+ const clientEventId = operationKey();
3570
+ try {
3571
+ let result;
3572
+ try {
3573
+ result = await command(current, turn, clientEventId);
3574
+ } catch (cause) {
3575
+ if (!isOutcomeUnknownError(cause)) throw cause;
3576
+ result = await command(current, turn, clientEventId);
3577
+ }
3578
+ if (targetKeyRef.current !== ownedTargetKey) return null;
3579
+ acceptSnapshot(ownedTargetKey, result.snapshot);
3580
+ return result;
3581
+ } catch (cause) {
3582
+ if (targetKeyRef.current === ownedTargetKey) {
3583
+ setMutationError(asError(cause));
3584
+ await load();
3585
+ }
3586
+ return null;
3587
+ } finally {
3588
+ if (targetKeyRef.current === ownedTargetKey && turnId in pendingRef.current) {
3589
+ const next = { ...pendingRef.current };
3590
+ delete next[turnId];
3591
+ pendingRef.current = next;
3592
+ setPendingByTurn(next);
3593
+ }
3594
+ }
3595
+ },
3596
+ [acceptSnapshot, load, sessionId, targetKey]
3597
+ );
3598
+ const moveTurn = useCallback3(
3599
+ async (turnId, beforeTurnId) => {
3600
+ const result = await mutate(
3601
+ turnId,
3602
+ "move",
3603
+ (current, _turn, clientEventId) => client.moveQueueItem(workspaceId, sessionId, turnId, {
3604
+ clientEventId,
3605
+ expectedQueueVersion: current.version,
3606
+ beforeTurnId
3607
+ })
3608
+ );
3609
+ return result !== null;
3610
+ },
3611
+ [client, mutate, sessionId, workspaceId]
3612
+ );
3613
+ const editTurn = useCallback3(
3614
+ async (turnId, edit) => {
3615
+ const result = await mutate(
3616
+ turnId,
3617
+ "edit",
3618
+ (_current, turn, clientEventId) => client.editQueueItem(workspaceId, sessionId, turnId, {
3619
+ clientEventId,
3620
+ expectedTurnVersion: turn.version,
3621
+ expectedDraftRevision: edit.expectedDraftRevision,
3622
+ replaceDraft: edit.replaceDraft
3623
+ })
3624
+ );
3625
+ return result?.draft ?? null;
3626
+ },
3627
+ [client, mutate, sessionId, workspaceId]
3628
+ );
3629
+ const steerTurn = useCallback3(
3630
+ async (turnId) => {
3631
+ const ownedTargetKey = targetKey;
3632
+ const turn = snapshotRef.current?.items.find((candidate) => candidate.id === turnId);
3633
+ if (targetKeyRef.current !== ownedTargetKey) return false;
3634
+ if (!turn) {
3635
+ setMutationError(new Error("That queued prompt changed before Steer could be sent."));
3636
+ await load();
3637
+ return false;
3638
+ }
3639
+ const optimistic = {
3640
+ turnId: turn.id,
3641
+ triggerEventId: turn.triggerEventId,
3642
+ text: turn.prompt,
3643
+ annotations: turn.annotations ?? [],
3644
+ resources: turn.resources,
3645
+ tools: turn.tools,
3646
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
3647
+ state: "sending"
3648
+ };
3649
+ setAcceptedSteers((current) => [
3650
+ ...current.filter((steer) => steer.turnId !== turnId),
3651
+ optimistic
3652
+ ]);
3653
+ const result = await mutate(
3654
+ turnId,
3655
+ "steer",
3656
+ (current, queuedTurn, clientEventId) => client.steerQueueItem(workspaceId, sessionId, turnId, {
3657
+ clientEventId,
3658
+ expectedTurnVersion: queuedTurn.version,
3659
+ controlEtag: current.effectiveControl.controlEtag
3660
+ })
3661
+ );
3662
+ if (targetKeyRef.current !== ownedTargetKey) return false;
3663
+ const advancedWithoutSteer = result === null && snapshotRef.current !== null && !snapshotRef.current.items.some((candidate) => candidate.id === turnId);
3664
+ if (advancedWithoutSteer) {
3665
+ setMutationError(null);
3666
+ setAcceptedSteers((current) => current.filter((steer) => steer.turnId !== turnId));
3667
+ return true;
3668
+ }
3669
+ const accepted = result !== null || snapshotRef.current?.items.some(
3670
+ (candidate) => candidate.id === turnId && candidate.metadata.delivery === "steer"
3671
+ ) === true;
3672
+ setAcceptedSteers(
3673
+ (current) => accepted ? current.map(
3674
+ (steer) => steer.turnId === turnId ? { ...steer, state: "queued" } : steer
3675
+ ) : current.filter((steer) => steer.turnId !== turnId)
3676
+ );
3677
+ return accepted;
3678
+ },
3679
+ [client, load, mutate, sessionId, targetKey, workspaceId]
3680
+ );
3681
+ const removeTurn = useCallback3(
3682
+ async (turnId) => {
3683
+ const result = await mutate(
3684
+ turnId,
3685
+ "delete",
3686
+ (_current, turn, clientEventId) => client.deleteQueueItem(workspaceId, sessionId, turnId, {
3687
+ clientEventId,
3688
+ expectedTurnVersion: turn.version,
3689
+ reason: "Deleted from the prompt queue"
3690
+ })
3691
+ );
3692
+ return result !== null;
3693
+ },
3694
+ [client, mutate, sessionId, workspaceId]
3695
+ );
3696
+ const identityMatches = stateTargetKey === targetKey;
3697
+ const visibleSnapshot = identityMatches ? snapshot : null;
3698
+ const visiblePendingByTurn = identityMatches ? pendingByTurn : EMPTY_PENDING_BY_TURN;
3699
+ const mutating = useMemo3(
3700
+ () => Object.keys(visiblePendingByTurn).length > 0,
3701
+ [visiblePendingByTurn]
3702
+ );
3703
+ const mutationFor = useCallback3(
3704
+ (turnId) => visiblePendingByTurn[turnId] ?? null,
3705
+ [visiblePendingByTurn]
3706
+ );
3707
+ return {
3708
+ snapshot: visibleSnapshot,
3709
+ queue: visibleSnapshot?.items ?? [],
3710
+ pendingInputs: visibleSnapshot?.pendingInputs ?? [],
3711
+ pendingInputAttachment: visibleSnapshot?.pendingInputAttachment ?? null,
3712
+ activePersonalConnections: visibleSnapshot?.activePersonalConnections ?? [],
3713
+ effectiveControl: visibleSnapshot?.effectiveControl ?? null,
3714
+ stoppingPreviousAttempt: visibleSnapshot?.stoppingPreviousAttempt ?? false,
3715
+ loading: identityMatches ? loading : enabled,
3716
+ error: identityMatches ? error : null,
3717
+ refresh: load,
3718
+ moveTurn,
3719
+ editTurn,
3720
+ steerTurn,
3721
+ removeTurn,
3722
+ pendingByTurn: visiblePendingByTurn,
3723
+ mutationFor,
3724
+ mutating,
3725
+ mutationError: identityMatches ? mutationError : null,
3726
+ clearMutationError: useCallback3(() => {
3727
+ if (targetKeyRef.current === targetKey) setMutationError(null);
3728
+ }, [targetKey]),
3729
+ acceptedSteers: identityMatches ? acceptedSteers : []
3730
+ };
3731
+ }
3732
+ function operationKey() {
3733
+ return globalThis.crypto.randomUUID();
3734
+ }
3735
+ function asError(cause) {
3736
+ return cause instanceof Error ? cause : new Error(String(cause));
3737
+ }
3738
+ function isOutcomeUnknownError(cause) {
3739
+ return typeof cause === "object" && cause !== null && cause.outcomeUnknown === true;
3740
+ }
3741
+
3742
+ // src/hooks/use-human-input.ts
3743
+ import { useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
3744
+ function isHumanInputEvent(event) {
3745
+ return event.type === "session.humanInput.requested" || event.type === "user.humanInputResponse" || event.type === "turn.completed" || event.type === "turn.failed" || event.type === "turn.cancelled";
3746
+ }
3747
+ function useHumanInputRequests(sessionId, options = {}) {
3748
+ const { client, workspaceId, registerSessionReconciler } = useEmbeddedHumanInputSession(options);
3749
+ const enabled = (options.enabled ?? true) && Boolean(sessionId);
3750
+ const load = useCallback4(
3751
+ async () => sessionId ? await client.listHumanInputRequests(workspaceId, sessionId, { status: "pending" }) : [],
3752
+ [client, sessionId, workspaceId]
3753
+ );
3754
+ const state = usePolledValue(load, {
3755
+ enabled,
3756
+ pollIntervalMs: options.pollIntervalMs
3757
+ });
3758
+ const loadedRequests = state.data;
3759
+ const refreshRequests = state.refresh;
3760
+ const mutation = useMutationRunner(`${workspaceId}\0${sessionId ?? ""}`);
3761
+ const [respondingRequestId, setRespondingRequestId] = useState4(null);
3762
+ const [deadlineVersion, setDeadlineVersion] = useState4(0);
3763
+ const respondingRequestRef = useRef4(null);
3764
+ useEffect4(() => {
3765
+ respondingRequestRef.current = null;
3766
+ setRespondingRequestId(null);
3767
+ }, [sessionId, workspaceId]);
3768
+ useEffect4(() => {
3769
+ if (!sessionId || !enabled) return;
3770
+ return registerSessionReconciler(sessionId, "human-input", state.refresh);
3771
+ }, [enabled, registerSessionReconciler, sessionId, state.refresh]);
3772
+ const scheduleRefresh = useDebouncedCallback(() => void state.refresh());
3773
+ useSessionEventTrigger(client, workspaceId, sessionId, isHumanInputEvent, scheduleRefresh, {
3774
+ enabled,
3775
+ ...options.events !== void 0 ? { events: options.events } : {}
3776
+ });
3777
+ useEffect4(() => {
3778
+ const now2 = Date.now();
3779
+ const nextDeadline = (loadedRequests ?? []).reduce((next, request) => {
3780
+ if (request.status !== "pending" || !request.expiresAt) return next;
3781
+ const deadline = Date.parse(request.expiresAt);
3782
+ if (!Number.isFinite(deadline) || deadline <= now2) return next;
3783
+ return next === null ? deadline : Math.min(next, deadline);
3784
+ }, null);
3785
+ if (nextDeadline === null) return;
3786
+ const timer = globalThis.setTimeout(
3787
+ () => {
3788
+ setDeadlineVersion((current) => current + 1);
3789
+ void refreshRequests();
3790
+ },
3791
+ Math.min(2147483647, Math.max(0, nextDeadline - now2 + 1))
3792
+ );
3793
+ return () => globalThis.clearTimeout(timer);
3794
+ }, [loadedRequests, refreshRequests]);
3795
+ const respond = useCallback4(
3796
+ async (requestId, response) => {
3797
+ if (!sessionId || respondingRequestRef.current !== null) return null;
3798
+ respondingRequestRef.current = requestId;
3799
+ setRespondingRequestId(requestId);
3800
+ try {
3801
+ const event = await mutation.run(
3802
+ () => client.submitHumanInputResponse(workspaceId, sessionId, requestId, response, {
3803
+ clientEventId: globalThis.crypto.randomUUID()
3804
+ })
3805
+ );
3806
+ if (event) await state.refresh();
3807
+ return event;
3808
+ } finally {
3809
+ if (respondingRequestRef.current === requestId) {
3810
+ respondingRequestRef.current = null;
3811
+ setRespondingRequestId(null);
3812
+ }
3813
+ }
3814
+ },
3815
+ [client, mutation, sessionId, state, workspaceId]
3816
+ );
3817
+ const now = Date.now();
3818
+ void deadlineVersion;
3819
+ return {
3820
+ requests: [...state.data ?? []].filter((request) => isActionableHumanInputRequest(request, now)).sort(
3821
+ (left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id)
3822
+ ),
3823
+ loading: state.loading,
3824
+ error: state.error,
3825
+ refresh: state.refresh,
3826
+ respond,
3827
+ respondingRequestId,
3828
+ mutationError: mutation.mutationError,
3829
+ clearMutationError: mutation.clearMutationError
3830
+ };
3831
+ }
3832
+
2024
3833
  export {
2025
3834
  isActionableHumanInputRequest,
2026
3835
  humanInputRequestFromEvent,
@@ -2033,6 +3842,19 @@ export {
2033
3842
  creditExhaustedFromEvents,
2034
3843
  sessionStatusFromEvents,
2035
3844
  groupTimeline,
2036
- extractSessionRef
3845
+ extractSessionRef,
3846
+ SESSION_EVENT_BROWSER_MAX_BYTES,
3847
+ SESSION_EVENT_BROWSER_MAX_COUNT,
3848
+ SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES,
3849
+ SESSION_EVENT_BROWSER_PENDING_MAX_BYTES,
3850
+ SESSION_EVENT_BROWSER_PENDING_MAX_COUNT,
3851
+ useSessionEvents,
3852
+ boundBrowserSessionEventWindow,
3853
+ isTitleEvent,
3854
+ useSession,
3855
+ isTurnQueueEvent,
3856
+ useTurnQueue,
3857
+ isHumanInputEvent,
3858
+ useHumanInputRequests
2037
3859
  };
2038
- //# sourceMappingURL=chunk-TJFMMDQU.js.map
3860
+ //# sourceMappingURL=chunk-MOWYEBCQ.js.map