@opengeni/react 3.7.0-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 (64) hide show
  1. package/dist/artifacts.js +24 -5
  2. package/dist/artifacts.js.map +1 -1
  3. package/dist/{browser-viewer-E5DDEFQ4.js → browser-viewer-NTD6UIPR.js} +3 -3
  4. package/dist/{chunk-W2RGYBCV.js → chunk-24M4YBCL.js} +4 -4
  5. package/dist/{chunk-DOK4KZF2.js → chunk-5IXTJTK3.js} +1322 -392
  6. package/dist/chunk-5IXTJTK3.js.map +1 -0
  7. package/dist/chunk-5PQJOCX5.js +902 -0
  8. package/dist/chunk-5PQJOCX5.js.map +1 -0
  9. package/dist/{chunk-R55HFTT3.js → chunk-INGB2N5R.js} +4305 -4019
  10. package/dist/chunk-INGB2N5R.js.map +1 -0
  11. package/dist/{chunk-FJBOU2TH.js → chunk-MOWYEBCQ.js} +1728 -12
  12. package/dist/chunk-MOWYEBCQ.js.map +1 -0
  13. package/dist/chunk-PAJU5KKL.js +331 -0
  14. package/dist/chunk-PAJU5KKL.js.map +1 -0
  15. package/dist/{chunk-VTOREEXF.js → chunk-PLYRL5ER.js} +24 -2
  16. package/dist/{chunk-VTOREEXF.js.map → chunk-PLYRL5ER.js.map} +1 -1
  17. package/dist/{chunk-YVCHAQZY.js → chunk-ZFOFY5ST.js} +27 -349
  18. package/dist/chunk-ZFOFY5ST.js.map +1 -0
  19. package/dist/components/artifacts/published-html-artifact-frame.d.ts +3 -2
  20. package/dist/components/session-chrome.d.ts +37 -4
  21. package/dist/components/session-commands-panel.d.ts +6 -0
  22. package/dist/components/session-conversation.d.ts +14 -0
  23. package/dist/components/timeline-anchor.d.ts +21 -0
  24. package/dist/composer.js +2 -3
  25. package/dist/conversation-timeline.d.ts +5 -0
  26. package/dist/index.d.ts +4 -1
  27. package/dist/index.js +716 -914
  28. package/dist/index.js.map +1 -1
  29. package/dist/interaction.js +2 -2
  30. package/dist/lib/format.d.ts +5 -0
  31. package/dist/machines.js +3 -2
  32. package/dist/session-ui.d.ts +3 -0
  33. package/dist/session-ui.js +14 -4
  34. package/dist/session.js +19 -19
  35. package/dist/timeline/types.d.ts +3 -1
  36. package/package.json +2 -2
  37. package/src/components/artifacts/published-html-artifact-frame.tsx +27 -4
  38. package/src/components/machine-input-display.ts +6 -0
  39. package/src/components/message-timeline.tsx +320 -197
  40. package/src/components/session-chrome.tsx +498 -125
  41. package/src/components/session-commands-panel.tsx +113 -0
  42. package/src/components/session-conversation.tsx +141 -0
  43. package/src/components/timeline-anchor.tsx +91 -0
  44. package/src/conversation-timeline.ts +83 -0
  45. package/src/hooks/use-session-background-commands.ts +21 -10
  46. package/src/hooks/use-session-events.ts +36 -14
  47. package/src/hooks/use-session.ts +23 -2
  48. package/src/index.ts +4 -0
  49. package/src/lib/format.ts +32 -0
  50. package/src/session-ui.ts +3 -0
  51. package/src/timeline/projection.ts +6 -12
  52. package/src/timeline/types.ts +4 -0
  53. package/styles/compiled.css +495 -411
  54. package/styles/index.css +23 -0
  55. package/dist/chunk-7EAFGICR.js +0 -302
  56. package/dist/chunk-7EAFGICR.js.map +0 -1
  57. package/dist/chunk-C6WDLHBA.js +0 -2590
  58. package/dist/chunk-C6WDLHBA.js.map +0 -1
  59. package/dist/chunk-DOK4KZF2.js.map +0 -1
  60. package/dist/chunk-FJBOU2TH.js.map +0 -1
  61. package/dist/chunk-R55HFTT3.js.map +0 -1
  62. package/dist/chunk-YVCHAQZY.js.map +0 -1
  63. /package/dist/{browser-viewer-E5DDEFQ4.js.map → browser-viewer-NTD6UIPR.js.map} +0 -0
  64. /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()) {
@@ -1059,6 +1071,7 @@ ${message}` : message;
1059
1071
  id: `${pendingWaitOutcome.id}-visible-outcome`,
1060
1072
  tone: "waiting",
1061
1073
  text: waitingOutcomeText(pendingWaitOutcome.reason),
1074
+ recordedOutcome: true,
1062
1075
  occurredAt: pendingWaitOutcome.occurredAt
1063
1076
  });
1064
1077
  }
@@ -1631,7 +1644,7 @@ function foldSettledTurn(groups, turnEnd) {
1631
1644
  );
1632
1645
  }
1633
1646
  function isTurnBoundary(group) {
1634
- 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");
1635
1648
  }
1636
1649
  function belongsToDifferentTurn(group, turnId) {
1637
1650
  if (!group || !turnId) {
@@ -1741,6 +1754,8 @@ function machineInputMembers(value) {
1741
1754
  const kinds = /* @__PURE__ */ new Set([
1742
1755
  "scheduled_occurrence",
1743
1756
  "goal_continuation",
1757
+ "background_command_result",
1758
+ "session_wait_timeout",
1744
1759
  "agent_message",
1745
1760
  "agent_steer_instruction",
1746
1761
  "child_terminal_result",
@@ -1915,13 +1930,7 @@ function elapsedDurationMs(startedAt, completedAt) {
1915
1930
  return Number.isFinite(start) && Number.isFinite(end) ? Math.max(0, end - start) : null;
1916
1931
  }
1917
1932
  function failureMessage(payload) {
1918
- for (const key of ["error", "message"]) {
1919
- const value = payload[key];
1920
- if (typeof value === "string" && value.trim().length > 0) {
1921
- return humanizeFailureReason(value);
1922
- }
1923
- }
1924
- return null;
1933
+ return presentFailure(payload).reason;
1925
1934
  }
1926
1935
  function goalText(payload) {
1927
1936
  if (typeof payload.text === "string" && payload.text) {
@@ -2127,6 +2136,1700 @@ function looksLikeId(value) {
2127
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);
2128
2137
  }
2129
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
+
2130
3833
  export {
2131
3834
  isActionableHumanInputRequest,
2132
3835
  humanInputRequestFromEvent,
@@ -2139,6 +3842,19 @@ export {
2139
3842
  creditExhaustedFromEvents,
2140
3843
  sessionStatusFromEvents,
2141
3844
  groupTimeline,
2142
- 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
2143
3859
  };
2144
- //# sourceMappingURL=chunk-FJBOU2TH.js.map
3860
+ //# sourceMappingURL=chunk-MOWYEBCQ.js.map