@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,2582 +0,0 @@
1
- import {
2
- useDebouncedCallback,
3
- useMutationRunner,
4
- usePageLiveActivity,
5
- usePolledValue,
6
- useSessionEventTrigger
7
- } from "./chunk-KZ73RL76.js";
8
- import {
9
- useEmbeddedFileAttachments,
10
- useEmbeddedGoal,
11
- useEmbeddedHumanInputSession,
12
- useEmbeddedSession,
13
- useEmbeddedSessionLineage,
14
- useEmbeddedSessionMcpApprovalPolicy,
15
- useEmbeddedSessionRead
16
- } from "./chunk-NEXLLTP4.js";
17
- import {
18
- buildTimeline,
19
- createOlderHistoryLoadReceipt,
20
- groupTimeline,
21
- isActionableHumanInputRequest,
22
- sessionStatusFromEvents
23
- } from "./chunk-TJFMMDQU.js";
24
-
25
- // src/embedded-session-client.ts
26
- var requiredMethods = [
27
- "getSession",
28
- "listEvents",
29
- "streamEvents",
30
- "getComposerDraft",
31
- "saveComposerDraft",
32
- "submitComposerDraft",
33
- "sendMessage",
34
- "steerMessage",
35
- "getQueue",
36
- "moveQueueItem",
37
- "editQueueItem",
38
- "steerQueueItem",
39
- "deleteQueueItem",
40
- "pauseSession",
41
- "resumeSession",
42
- "sendApprovalDecision"
43
- ];
44
- function createEmbeddedSessionClient(base, options = {}) {
45
- const overrides = options.overrides ?? {};
46
- const client = {};
47
- for (const key of requiredMethods) {
48
- const override = overrides[key];
49
- const method = override ?? base[key];
50
- if (typeof method !== "function") {
51
- throw new TypeError(`Embedded session client method is required: ${key}`);
52
- }
53
- client[key] = method.bind(override === void 0 ? base : overrides);
54
- }
55
- const inferenceOverride = overrides.setWorkspaceInferenceState;
56
- const inferenceBase = base.setWorkspaceInferenceState;
57
- const inferenceMethod = inferenceOverride ?? inferenceBase;
58
- if (typeof inferenceMethod === "function") {
59
- client.setWorkspaceInferenceState = inferenceMethod.bind(
60
- inferenceOverride === void 0 ? base : overrides
61
- );
62
- }
63
- const mapDraft = options.mapComposerDraft;
64
- if (!mapDraft) return client;
65
- const getComposerDraft = client.getComposerDraft;
66
- client.getComposerDraft = async (workspaceId, sessionId) => mapDraft(await getComposerDraft(workspaceId, sessionId), {
67
- operation: "read",
68
- workspaceId,
69
- sessionId
70
- });
71
- const saveComposerDraft = client.saveComposerDraft;
72
- client.saveComposerDraft = async (workspaceId, sessionId, request) => mapDraft(await saveComposerDraft(workspaceId, sessionId, request), {
73
- operation: "save",
74
- workspaceId,
75
- sessionId
76
- });
77
- const submitComposerDraft = client.submitComposerDraft;
78
- client.submitComposerDraft = async (workspaceId, sessionId, request) => {
79
- const response = await submitComposerDraft(workspaceId, sessionId, request);
80
- return {
81
- ...response,
82
- draft: mapDraft(response.draft, {
83
- operation: "submit",
84
- workspaceId,
85
- sessionId
86
- })
87
- };
88
- };
89
- return client;
90
- }
91
-
92
- // src/hooks/use-session.ts
93
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
94
- function isTitleEvent(event) {
95
- return event.type === "session.title_set";
96
- }
97
- function useSession(sessionId, options = {}) {
98
- const { client, workspaceId, workspaceControlEvent, registerSessionReconciler } = useEmbeddedSessionRead(options);
99
- const enabled = (options.enabled ?? true) && Boolean(sessionId);
100
- const [override, setOverride] = useState(null);
101
- const nextReadRevision = useRef(0);
102
- const nextReadGeneration = useRef(0);
103
- const beginRead = options.beginRead;
104
- const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
105
- const load = useCallback(
106
- async (signal) => {
107
- if (!sessionId) {
108
- return null;
109
- }
110
- let readGeneration2 = 0;
111
- const fetched = await client.getSession(workspaceId, sessionId, {
112
- fresh: true,
113
- signal,
114
- onRequestStart: () => {
115
- readGeneration2 = beginRead?.() ?? ++nextReadGeneration.current;
116
- }
117
- });
118
- setOverride(null);
119
- return {
120
- session: fetched,
121
- revision: ++nextReadRevision.current,
122
- readGeneration: readGeneration2
123
- };
124
- },
125
- [beginRead, client, workspaceId, sessionId]
126
- );
127
- const { data, loading, error, refresh } = usePolledValue(load, {
128
- pollIntervalMs: options.pollIntervalMs,
129
- enabled
130
- });
131
- useEffect(() => {
132
- if (enabled && workspaceControlEvent) void refresh();
133
- }, [enabled, refresh, workspaceControlEvent]);
134
- useEffect(() => {
135
- if (!sessionId || !enabled) return;
136
- return registerSessionReconciler(sessionId, "session", refresh);
137
- }, [enabled, refresh, registerSessionReconciler, sessionId]);
138
- const base = data?.session ?? null;
139
- const readRevision = data?.revision ?? 0;
140
- const readGeneration = data?.readGeneration ?? 0;
141
- const session = useMemo(
142
- () => base && override && override.id === base.id ? { ...base, title: override.title, titleSource: override.titleSource } : base,
143
- [base, override]
144
- );
145
- const onTitleEvent = useCallback(
146
- (event) => {
147
- if (!base || event.sequence <= base.lastSequence) {
148
- return;
149
- }
150
- const payload = event.payload ?? {};
151
- const title = payload.title;
152
- if (typeof title !== "string") {
153
- return;
154
- }
155
- const source = payload.source === "user" || payload.source === "agent" ? payload.source : null;
156
- setOverride((current) => {
157
- const next = current ?? base;
158
- return { ...next, title, titleSource: source };
159
- });
160
- },
161
- [base]
162
- );
163
- useSessionEventTrigger(client, workspaceId, sessionId, isTitleEvent, onTitleEvent, {
164
- enabled,
165
- ...options.events !== void 0 ? { events: options.events } : {}
166
- });
167
- const updateTitle = useCallback(
168
- async (title) => {
169
- if (!sessionId) {
170
- return null;
171
- }
172
- const result = await run(() => client.updateSession(workspaceId, sessionId, { title }));
173
- if (result) {
174
- setOverride(result);
175
- }
176
- return result;
177
- },
178
- [client, workspaceId, sessionId, run]
179
- );
180
- return {
181
- session,
182
- loading,
183
- error,
184
- readRevision,
185
- readGeneration,
186
- refresh,
187
- updateTitle,
188
- updating: mutating,
189
- mutationError,
190
- clearMutationError
191
- };
192
- }
193
-
194
- // src/hooks/use-session-events.ts
195
- import {
196
- sessionEventStreamCoveredThrough
197
- } from "@opengeni/sdk";
198
- import { startTransition, useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
199
- var INITIAL_TAIL_PAGE_SIZE = 1e3;
200
- var OLDER_PAGE_SIZE = 5e3;
201
- var NEWER_PAGE_SIZE = 5e3;
202
- var OLDEST_PAGE_SIZE = 1e3;
203
- var INITIAL_FETCH_CAP = 1;
204
- var OLDER_GROUP_TARGET = 32;
205
- var OLDER_FETCH_CAP = 2;
206
- var NEWER_GROUP_TARGET = 32;
207
- var NEWER_FETCH_CAP = 2;
208
- var OLDEST_GROUP_TARGET = 32;
209
- var OLDEST_FETCH_CAP = 2;
210
- var BOUNDARY_PAGE_CAP = 4;
211
- var FOREGROUND_DIRECT_REPLAY_MAX_SEQUENCES = 16;
212
- var FOREGROUND_COMPACT_PROBE_MAX_SEQUENCES = 5e3;
213
- var FOREGROUND_COMPACT_CATCHUP_MAX_EVENTS = 128;
214
- var FOREGROUND_COMPACT_CATCHUP_MAX_GROUPS = 16;
215
- var FOREGROUND_COMPACT_CATCHUP_MAX_BYTES = 512 * 1024;
216
- var EMPTY_EVENTS = [];
217
- var encoder = new TextEncoder();
218
- var decoder = new TextDecoder();
219
- var BROWSER_EVENT_TYPE_MAX_BYTES = 256;
220
- var BROWSER_EVENT_ID_MAX_BYTES = 256;
221
- var BROWSER_EVENT_CLIENT_ID_MAX_BYTES = 4 * 1024;
222
- var BROWSER_EVENT_DUPLICATE_REASON_MAX_BYTES = 4 * 1024;
223
- var BROWSER_EVENT_PAYLOAD_PREVIEW_MAX_BYTES = 48 * 1024;
224
- var BROWSER_EVENT_PAYLOAD_IDENTITY_FIELDS = [
225
- "id",
226
- "callId",
227
- "call_id",
228
- "name",
229
- "toolName",
230
- "status",
231
- "code",
232
- "isError",
233
- "stream",
234
- "commandId",
235
- "sequence",
236
- "coalescedUntil",
237
- "coalescedCount",
238
- "firstSequence",
239
- "lastSequence"
240
- ];
241
- var SESSION_EVENT_BROWSER_MAX_BYTES = 8 * 1024 * 1024;
242
- var SESSION_EVENT_BROWSER_MAX_COUNT = 1e4;
243
- var SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES = 96 * 1024;
244
- var SESSION_EVENT_BROWSER_PENDING_MAX_BYTES = 1024 * 1024;
245
- var SESSION_EVENT_BROWSER_PENDING_MAX_COUNT = 256;
246
- var EMPTY_EVENT_WINDOW = {
247
- events: EMPTY_EVENTS,
248
- bytes: 2,
249
- truncated: false
250
- };
251
- function useSessionEvents(sessionId, options = {}) {
252
- const { client, workspaceId, reconcileSession } = useEmbeddedSession(options);
253
- const enabled = options.enabled ?? true;
254
- const pageLive = usePageLiveActivity();
255
- const streamEnabled = enabled && pageLive;
256
- const after = options.after ?? 0;
257
- const replay = options.replay ?? "windowed";
258
- const fullReplay = replay === "full" || after !== 0;
259
- const streamKey = `${workspaceId}\0${sessionId ?? ""}\0${after}\0${fullReplay ? "full" : "windowed"}`;
260
- const [eventWindow, setEventWindow] = useState2(EMPTY_EVENT_WINDOW);
261
- const [connectionState, setConnectionState] = useState2("idle");
262
- const [error, setError] = useState2(null);
263
- const [hasOlder, setHasOlder] = useState2(false);
264
- const [hasNewer, setHasNewer] = useState2(false);
265
- const [sessionStatusProjection, setSessionStatusProjection] = useState2(
266
- null
267
- );
268
- const [initialLoading, setInitialLoading] = useState2(true);
269
- const [loadingOlder, setLoadingOlder] = useState2(false);
270
- const [loadingNewer, setLoadingNewer] = useState2(false);
271
- const [loadingOldest, setLoadingOldest] = useState2(false);
272
- const [loadingLatest, setLoadingLatest] = useState2(false);
273
- const [streamEpoch, setStreamEpoch] = useState2(0);
274
- const [viewMode, setViewMode] = useState2("live");
275
- const lastSequenceRef = useRef2(after);
276
- const streamResumeSequenceRef = useRef2(after);
277
- const oldestSequenceRef = useRef2(null);
278
- const newestSequenceRef = useRef2(null);
279
- const hasOlderRef = useRef2(false);
280
- const hasNewerRef = useRef2(false);
281
- const loadingOlderRef = useRef2(false);
282
- const loadingNewerRef = useRef2(false);
283
- const loadingOldestRef = useRef2(false);
284
- const loadingLatestRef = useRef2(false);
285
- const viewModeRef = useRef2("live");
286
- const initialWindowLoadedRef = useRef2(false);
287
- const reconcileAfterPageResumeRef = useRef2(false);
288
- const streamAbortRef = useRef2(null);
289
- const streamKeyRef = useRef2(null);
290
- const generationRef = useRef2(0);
291
- const eventWindowRef = useRef2(EMPTY_EVENT_WINDOW);
292
- const sessionStatusRef = useRef2({
293
- sequence: after,
294
- status: null
295
- });
296
- const [stateStreamKey, setStateStreamKey] = useState2(streamKey);
297
- useEffect2(() => {
298
- if (streamKeyRef.current !== streamKey) {
299
- streamKeyRef.current = streamKey;
300
- setStateStreamKey(streamKey);
301
- eventWindowRef.current = EMPTY_EVENT_WINDOW;
302
- setEventWindow(EMPTY_EVENT_WINDOW);
303
- setError(null);
304
- setHasOlder(false);
305
- setHasNewer(false);
306
- sessionStatusRef.current = { sequence: after, status: null };
307
- setSessionStatusProjection(null);
308
- setLoadingOlder(false);
309
- setLoadingNewer(false);
310
- setLoadingOldest(false);
311
- setLoadingLatest(false);
312
- setInitialLoading(true);
313
- lastSequenceRef.current = after;
314
- streamResumeSequenceRef.current = after;
315
- oldestSequenceRef.current = null;
316
- newestSequenceRef.current = null;
317
- hasOlderRef.current = false;
318
- hasNewerRef.current = false;
319
- loadingOlderRef.current = false;
320
- loadingNewerRef.current = false;
321
- loadingOldestRef.current = false;
322
- loadingLatestRef.current = false;
323
- viewModeRef.current = "live";
324
- setViewMode("live");
325
- initialWindowLoadedRef.current = false;
326
- reconcileAfterPageResumeRef.current = false;
327
- }
328
- const generation = generationRef.current + 1;
329
- generationRef.current = generation;
330
- if (loadingOlderRef.current) {
331
- loadingOlderRef.current = false;
332
- setLoadingOlder(false);
333
- }
334
- if (!sessionId || !streamEnabled) {
335
- if (sessionId && enabled && !pageLive && !fullReplay && viewModeRef.current === "live" && initialWindowLoadedRef.current) {
336
- reconcileAfterPageResumeRef.current = true;
337
- }
338
- setConnectionState("idle");
339
- return;
340
- }
341
- if (viewMode === "history") {
342
- reconcileAfterPageResumeRef.current = false;
343
- setConnectionState("idle");
344
- setInitialLoading(false);
345
- return;
346
- }
347
- const reconcileForegroundResume = reconcileAfterPageResumeRef.current && !fullReplay;
348
- reconcileAfterPageResumeRef.current = false;
349
- const controller = new AbortController();
350
- const isCurrent = () => generationRef.current === generation && !controller.signal.aborted;
351
- streamAbortRef.current = controller;
352
- let pending = [];
353
- let pendingBytes = 2;
354
- let flushTimer = null;
355
- const flush = () => {
356
- if (flushTimer !== null) {
357
- clearTimeout(flushTimer);
358
- flushTimer = null;
359
- }
360
- if (!isCurrent()) {
361
- pending = [];
362
- pendingBytes = 2;
363
- return;
364
- }
365
- if (pending.length === 0) {
366
- return;
367
- }
368
- const batch = pending;
369
- pending = [];
370
- pendingBytes = 2;
371
- const lastInBatch = batch[batch.length - 1];
372
- if (lastInBatch) {
373
- const batchResumeSequence = Math.max(...batch.map(eventResumeSequence));
374
- lastSequenceRef.current = Math.max(lastSequenceRef.current, batchResumeSequence);
375
- streamResumeSequenceRef.current = Math.max(
376
- streamResumeSequenceRef.current,
377
- batchResumeSequence
378
- );
379
- }
380
- const status = observeSessionStatus(batch, sessionStatusRef);
381
- if (status !== void 0) {
382
- setSessionStatusProjection(status);
383
- }
384
- const current = eventWindowRef.current;
385
- const next = boundBrowserSessionEventWindow([...current.events, ...batch]);
386
- const retained = {
387
- ...next,
388
- truncated: current.truncated || next.truncated
389
- };
390
- eventWindowRef.current = retained;
391
- setEventWindow(retained);
392
- oldestSequenceRef.current = retained.events[0]?.sequence ?? null;
393
- newestSequenceRef.current = maxResumeSequenceOrNull(retained.events);
394
- if (retained.truncated) {
395
- hasOlderRef.current = true;
396
- setHasOlder(true);
397
- }
398
- };
399
- const scheduleFlush = () => {
400
- if (!isCurrent()) return;
401
- flushTimer ??= setTimeout(flush, 16);
402
- };
403
- void (async () => {
404
- try {
405
- if (reconcileForegroundResume) {
406
- setConnectionState("connecting");
407
- const cursor = streamResumeSequenceRef.current;
408
- let plan = { kind: "resume" };
409
- try {
410
- plan = await planForegroundCatchup(client, workspaceId, sessionId, cursor, {
411
- signal: controller.signal
412
- });
413
- } catch {
414
- if (controller.signal.aborted) return;
415
- plan = { kind: "resume" };
416
- }
417
- if (!isCurrent()) return;
418
- if (plan.kind === "append") {
419
- const current = eventWindowRef.current;
420
- assertAppendOrder(current.events, plan.events);
421
- const status = observeSessionStatus(plan.events, sessionStatusRef);
422
- const next = boundBrowserSessionEventWindow([...current.events, ...plan.events]);
423
- const retained = {
424
- ...next,
425
- truncated: current.truncated || next.truncated
426
- };
427
- eventWindowRef.current = retained;
428
- oldestSequenceRef.current = retained.events[0]?.sequence ?? null;
429
- newestSequenceRef.current = maxResumeSequenceOrNull(retained.events);
430
- lastSequenceRef.current = Math.max(lastSequenceRef.current, plan.resumeSequence);
431
- streamResumeSequenceRef.current = Math.max(
432
- streamResumeSequenceRef.current,
433
- plan.resumeSequence
434
- );
435
- if (status !== void 0) {
436
- setSessionStatusProjection(status);
437
- }
438
- setEventWindow(retained);
439
- if (retained.truncated) {
440
- hasOlderRef.current = true;
441
- setHasOlder(true);
442
- }
443
- } else if (plan.kind === "reload") {
444
- eventWindowRef.current = EMPTY_EVENT_WINDOW;
445
- oldestSequenceRef.current = null;
446
- newestSequenceRef.current = null;
447
- hasOlderRef.current = false;
448
- hasNewerRef.current = false;
449
- initialWindowLoadedRef.current = false;
450
- streamResumeSequenceRef.current = after;
451
- setEventWindow(EMPTY_EVENT_WINDOW);
452
- setHasOlder(false);
453
- setHasNewer(false);
454
- setInitialLoading(true);
455
- setError(null);
456
- }
457
- }
458
- if (!fullReplay && !initialWindowLoadedRef.current) {
459
- setConnectionState("connecting");
460
- const window = await loadEventWindow(client, workspaceId, sessionId, {
461
- before: Number.MAX_SAFE_INTEGER,
462
- pageSize: INITIAL_TAIL_PAGE_SIZE,
463
- targetGroups: Number.POSITIVE_INFINITY,
464
- maxFetches: INITIAL_FETCH_CAP,
465
- signal: controller.signal
466
- });
467
- if (!isCurrent()) {
468
- return;
469
- }
470
- const status = observeSessionStatus(window.events, sessionStatusRef);
471
- const retained = boundBrowserSessionEventWindow(window.events);
472
- eventWindowRef.current = retained;
473
- oldestSequenceRef.current = retained.events[0]?.sequence ?? window.oldestSequence;
474
- newestSequenceRef.current = maxResumeSequenceOrNull(retained.events) ?? window.newestSequence;
475
- hasOlderRef.current = window.hasOlder || retained.truncated;
476
- hasNewerRef.current = false;
477
- lastSequenceRef.current = window.newestSequence;
478
- streamResumeSequenceRef.current = window.newestSequence;
479
- initialWindowLoadedRef.current = true;
480
- if (status !== void 0) {
481
- setSessionStatusProjection(status);
482
- }
483
- setEventWindow(retained);
484
- setHasOlder(window.hasOlder || retained.truncated);
485
- setHasNewer(false);
486
- setInitialLoading(false);
487
- }
488
- if (fullReplay) {
489
- setInitialLoading(false);
490
- }
491
- const stream = client.streamEvents(workspaceId, sessionId, {
492
- after: streamResumeSequenceRef.current,
493
- signal: controller.signal,
494
- onOpen: () => {
495
- void Promise.resolve().then(() => reconcileSession(sessionId)).catch((cause) => {
496
- if (isCurrent()) {
497
- setError(cause instanceof Error ? cause : new Error(String(cause)));
498
- }
499
- });
500
- },
501
- onStateChange: (state) => {
502
- if (isCurrent()) {
503
- setConnectionState(state);
504
- }
505
- }
506
- });
507
- for await (const event of stream) {
508
- if (!isCurrent()) break;
509
- const boundedEvent = boundBrowserLegacyEvent(event);
510
- const boundedEventBytes = browserJsonBytes(boundedEvent);
511
- const separatorBytes = pending.length === 0 ? 0 : 1;
512
- if (pending.length > 0 && (pending.length >= SESSION_EVENT_BROWSER_PENDING_MAX_COUNT || pendingBytes + separatorBytes + boundedEventBytes > SESSION_EVENT_BROWSER_PENDING_MAX_BYTES)) {
513
- flush();
514
- }
515
- pending.push(boundedEvent);
516
- pendingBytes += (pending.length === 1 ? 0 : 1) + boundedEventBytes;
517
- if (pending.length >= SESSION_EVENT_BROWSER_PENDING_MAX_COUNT || pendingBytes >= SESSION_EVENT_BROWSER_PENDING_MAX_BYTES) {
518
- flush();
519
- } else {
520
- scheduleFlush();
521
- }
522
- }
523
- if (isCurrent()) {
524
- flush();
525
- setConnectionState("ended");
526
- }
527
- } catch (cause) {
528
- if (isCurrent()) {
529
- flush();
530
- setError(cause instanceof Error ? cause : new Error(String(cause)));
531
- setConnectionState("error");
532
- setInitialLoading(false);
533
- }
534
- }
535
- })();
536
- return () => {
537
- controller.abort();
538
- if (generationRef.current === generation) {
539
- generationRef.current = generation + 1;
540
- }
541
- if (streamAbortRef.current === controller) {
542
- streamAbortRef.current = null;
543
- }
544
- if (flushTimer !== null) {
545
- clearTimeout(flushTimer);
546
- }
547
- };
548
- }, [
549
- client,
550
- workspaceId,
551
- sessionId,
552
- after,
553
- enabled,
554
- pageLive,
555
- streamEnabled,
556
- fullReplay,
557
- streamKey,
558
- streamEpoch,
559
- viewMode,
560
- reconcileSession
561
- ]);
562
- const navigationBusy = () => loadingOlderRef.current || loadingNewerRef.current || loadingOldestRef.current || loadingLatestRef.current;
563
- const loadOlder = useCallback2(
564
- () => createOlderHistoryLoadReceipt(async (markCommitted) => {
565
- if (!sessionId || navigationBusy() || !hasOlderRef.current) {
566
- return false;
567
- }
568
- const before = oldestSequenceRef.current;
569
- if (before === null) {
570
- oldestSequenceRef.current = null;
571
- hasOlderRef.current = false;
572
- setHasOlder(false);
573
- return false;
574
- }
575
- const generation = generationRef.current;
576
- loadingOlderRef.current = true;
577
- setLoadingOlder(true);
578
- let published = false;
579
- try {
580
- const window = await loadEventWindow(client, workspaceId, sessionId, {
581
- before,
582
- pageSize: OLDER_PAGE_SIZE,
583
- targetGroups: OLDER_GROUP_TARGET,
584
- maxFetches: OLDER_FETCH_CAP
585
- });
586
- if (generationRef.current !== generation) {
587
- return false;
588
- }
589
- if (window.events.length === 0) {
590
- oldestSequenceRef.current = null;
591
- hasOlderRef.current = false;
592
- setHasOlder(false);
593
- return false;
594
- }
595
- const current = eventWindowRef.current;
596
- assertPrependOrder(current.events, window.events);
597
- streamAbortRef.current?.abort();
598
- const status = observeSessionStatus(window.events, sessionStatusRef);
599
- const next = boundBrowserSessionEventWindow([...window.events, ...current.events], {
600
- direction: "oldest"
601
- });
602
- const retained = {
603
- ...next,
604
- truncated: current.truncated || next.truncated
605
- };
606
- const retainedOldest = retained.events[0]?.sequence ?? null;
607
- if (retainedOldest === null || retainedOldest >= before) {
608
- throw new Error("@opengeni/react: loadOlder made no durable sequence progress");
609
- }
610
- const retainedNewest = maxResumeSequenceOrNull(retained.events);
611
- const previousNewest = maxResumeSequenceOrNull(current.events);
612
- eventWindowRef.current = retained;
613
- markCommitted();
614
- oldestSequenceRef.current = retainedOldest;
615
- newestSequenceRef.current = retainedNewest;
616
- streamResumeSequenceRef.current = maxResumeSequence(retained.events);
617
- const olderStillAvailable = window.hasOlder;
618
- hasOlderRef.current = olderStillAvailable;
619
- const evictedLiveTail = previousNewest !== null && retainedNewest !== null && retainedNewest < previousNewest;
620
- let newer = null;
621
- let enterHistory = false;
622
- let reconnectLive = false;
623
- if (viewModeRef.current === "history" || evictedLiveTail) {
624
- const highWater = lastSequenceRef.current;
625
- newer = retainedNewest !== null && (retainedNewest < highWater || retained.truncated);
626
- hasNewerRef.current = newer;
627
- if (evictedLiveTail) {
628
- viewModeRef.current = "history";
629
- enterHistory = true;
630
- }
631
- } else {
632
- reconnectLive = true;
633
- }
634
- loadingOlderRef.current = false;
635
- startTransition(() => {
636
- if (status !== void 0) {
637
- setSessionStatusProjection(status);
638
- }
639
- setEventWindow(retained);
640
- setHasOlder(olderStillAvailable);
641
- if (newer !== null) {
642
- setHasNewer(newer);
643
- }
644
- if (enterHistory) {
645
- setViewMode("history");
646
- } else if (reconnectLive) {
647
- setStreamEpoch((epoch) => epoch + 1);
648
- }
649
- setLoadingOlder(false);
650
- });
651
- published = true;
652
- return olderStillAvailable;
653
- } finally {
654
- if (!published) {
655
- loadingOlderRef.current = false;
656
- setLoadingOlder(false);
657
- }
658
- }
659
- }),
660
- [client, workspaceId, sessionId]
661
- );
662
- const loadOldest = useCallback2(async () => {
663
- if (!sessionId || navigationBusy() || !hasOlderRef.current) {
664
- return false;
665
- }
666
- const generation = generationRef.current;
667
- loadingOldestRef.current = true;
668
- setLoadingOldest(true);
669
- let published = false;
670
- try {
671
- const window = await loadForwardEventWindow(client, workspaceId, sessionId, {
672
- after: 0,
673
- pageSize: OLDEST_PAGE_SIZE,
674
- targetGroups: OLDEST_GROUP_TARGET,
675
- maxFetches: OLDEST_FETCH_CAP
676
- });
677
- if (generationRef.current !== generation) {
678
- return false;
679
- }
680
- if (window.events.length === 0) {
681
- hasOlderRef.current = false;
682
- setHasOlder(false);
683
- return false;
684
- }
685
- streamAbortRef.current?.abort();
686
- const status = observeSessionStatus(window.events, sessionStatusRef);
687
- const retained = boundBrowserSessionEventWindow(window.events, { direction: "oldest" });
688
- const retainedOldest = retained.events[0]?.sequence ?? null;
689
- const retainedNewest = maxResumeSequenceOrNull(retained.events);
690
- eventWindowRef.current = retained;
691
- oldestSequenceRef.current = retainedOldest;
692
- newestSequenceRef.current = retainedNewest;
693
- hasOlderRef.current = false;
694
- const highWater = Math.max(lastSequenceRef.current, retainedNewest ?? 0);
695
- lastSequenceRef.current = highWater;
696
- const newer = window.hasNewer || retained.truncated || retainedNewest !== null && retainedNewest < highWater;
697
- hasNewerRef.current = newer;
698
- initialWindowLoadedRef.current = true;
699
- viewModeRef.current = "history";
700
- loadingOldestRef.current = false;
701
- startTransition(() => {
702
- if (status !== void 0) {
703
- setSessionStatusProjection(status);
704
- }
705
- setEventWindow(retained);
706
- setHasOlder(false);
707
- setHasNewer(newer);
708
- setViewMode("history");
709
- setLoadingOldest(false);
710
- });
711
- published = true;
712
- return newer;
713
- } finally {
714
- if (!published) {
715
- loadingOldestRef.current = false;
716
- setLoadingOldest(false);
717
- }
718
- }
719
- }, [client, workspaceId, sessionId]);
720
- const loadNewer = useCallback2(async () => {
721
- if (!sessionId || navigationBusy() || !hasNewerRef.current) {
722
- return false;
723
- }
724
- const afterSequence = newestSequenceRef.current;
725
- if (afterSequence === null) {
726
- hasNewerRef.current = false;
727
- setHasNewer(false);
728
- return false;
729
- }
730
- const generation = generationRef.current;
731
- loadingNewerRef.current = true;
732
- setLoadingNewer(true);
733
- let published = false;
734
- try {
735
- const window = await loadForwardEventWindow(client, workspaceId, sessionId, {
736
- after: afterSequence,
737
- pageSize: NEWER_PAGE_SIZE,
738
- targetGroups: NEWER_GROUP_TARGET,
739
- maxFetches: NEWER_FETCH_CAP
740
- });
741
- if (generationRef.current !== generation) {
742
- return false;
743
- }
744
- if (window.events.length === 0) {
745
- hasNewerRef.current = false;
746
- setHasNewer(false);
747
- streamResumeSequenceRef.current = afterSequence;
748
- viewModeRef.current = "live";
749
- setViewMode("live");
750
- setStreamEpoch((epoch) => epoch + 1);
751
- return false;
752
- }
753
- const current = eventWindowRef.current;
754
- assertAppendOrder(current.events, window.events);
755
- const status = observeSessionStatus(window.events, sessionStatusRef);
756
- const previousOldest = current.events[0]?.sequence ?? null;
757
- const next = boundBrowserSessionEventWindow([...current.events, ...window.events], {
758
- direction: "newest"
759
- });
760
- const retained = {
761
- ...next,
762
- truncated: current.truncated || next.truncated || window.hasNewer
763
- };
764
- const retainedOldest = retained.events[0]?.sequence ?? null;
765
- const retainedNewest = maxResumeSequenceOrNull(retained.events);
766
- if (retainedNewest === null || retainedNewest <= afterSequence) {
767
- throw new Error("@opengeni/react: loadNewer made no durable sequence progress");
768
- }
769
- eventWindowRef.current = retained;
770
- oldestSequenceRef.current = retainedOldest;
771
- newestSequenceRef.current = retainedNewest;
772
- if (retainedOldest !== null && (previousOldest === null || retainedOldest > previousOldest || retained.truncated && retained.events[0]?.type !== "session.created" && retainedOldest > 1)) {
773
- hasOlderRef.current = true;
774
- }
775
- const rearmedOlder = hasOlderRef.current;
776
- const highWater = Math.max(lastSequenceRef.current, retainedNewest);
777
- lastSequenceRef.current = highWater;
778
- const newer = window.hasNewer || retainedNewest < highWater;
779
- hasNewerRef.current = newer;
780
- let resumeLive = false;
781
- if (!newer) {
782
- streamResumeSequenceRef.current = retainedNewest;
783
- viewModeRef.current = "live";
784
- resumeLive = true;
785
- }
786
- loadingNewerRef.current = false;
787
- startTransition(() => {
788
- if (status !== void 0) {
789
- setSessionStatusProjection(status);
790
- }
791
- setEventWindow(retained);
792
- setHasOlder(rearmedOlder);
793
- setHasNewer(newer);
794
- if (resumeLive) {
795
- setViewMode("live");
796
- setStreamEpoch((epoch) => epoch + 1);
797
- }
798
- setLoadingNewer(false);
799
- });
800
- published = true;
801
- return newer;
802
- } finally {
803
- if (!published) {
804
- loadingNewerRef.current = false;
805
- setLoadingNewer(false);
806
- }
807
- }
808
- }, [client, workspaceId, sessionId]);
809
- const jumpToLatest = useCallback2(async () => {
810
- if (!sessionId || navigationBusy()) {
811
- return;
812
- }
813
- loadingLatestRef.current = true;
814
- setLoadingLatest(true);
815
- let published = false;
816
- try {
817
- streamAbortRef.current?.abort();
818
- hasNewerRef.current = false;
819
- hasOlderRef.current = false;
820
- newestSequenceRef.current = null;
821
- oldestSequenceRef.current = null;
822
- eventWindowRef.current = EMPTY_EVENT_WINDOW;
823
- initialWindowLoadedRef.current = false;
824
- streamResumeSequenceRef.current = after;
825
- viewModeRef.current = "live";
826
- loadingLatestRef.current = false;
827
- startTransition(() => {
828
- setHasNewer(false);
829
- setHasOlder(false);
830
- setEventWindow(EMPTY_EVENT_WINDOW);
831
- setInitialLoading(true);
832
- setViewMode("live");
833
- setStreamEpoch((epoch) => epoch + 1);
834
- setLoadingLatest(false);
835
- });
836
- published = true;
837
- await Promise.resolve();
838
- } finally {
839
- if (!published) {
840
- loadingLatestRef.current = false;
841
- setLoadingLatest(false);
842
- }
843
- }
844
- }, [after, sessionId]);
845
- const identityMatches = stateStreamKey === streamKey;
846
- const visibleEvents = identityMatches ? eventWindow.events : EMPTY_EVENTS;
847
- const timeline = useMemo2(() => buildTimeline(visibleEvents), [visibleEvents]);
848
- return {
849
- events: visibleEvents,
850
- timeline,
851
- sessionStatus: identityMatches ? sessionStatusProjection : null,
852
- connectionState: identityMatches ? connectionState : "idle",
853
- lastSequence: identityMatches ? lastSequenceRef.current : after,
854
- windowBytes: identityMatches ? eventWindow.bytes : 2,
855
- windowTruncated: identityMatches ? eventWindow.truncated : false,
856
- initialLoading: fullReplay ? false : identityMatches ? initialLoading : true,
857
- hasOlder: !identityMatches ? false : hasOlder,
858
- loadingOlder: !identityMatches ? false : loadingOlder,
859
- loadOlder,
860
- hasNewer: !identityMatches ? false : hasNewer,
861
- loadingNewer: !identityMatches ? false : loadingNewer,
862
- loadNewer,
863
- loadingOldest: !identityMatches ? false : loadingOldest,
864
- loadOldest,
865
- loadingLatest: !identityMatches ? false : loadingLatest,
866
- jumpToLatest,
867
- error: identityMatches ? error : null
868
- };
869
- }
870
- function boundBrowserSessionEventWindow(events, options = {}) {
871
- const maxBytes = Math.max(1024, options.maxBytes ?? SESSION_EVENT_BROWSER_MAX_BYTES);
872
- const maxCount = Math.max(1, Math.floor(options.maxCount ?? SESSION_EVENT_BROWSER_MAX_COUNT));
873
- const safe = events.map(boundBrowserLegacyEvent);
874
- const selected = [];
875
- let bytes = 2;
876
- const direction = options.direction ?? "newest";
877
- const start = direction === "newest" ? safe.length - 1 : 0;
878
- const end = direction === "newest" ? Math.max(-1, safe.length - maxCount - 1) : safe.length;
879
- const step = direction === "newest" ? -1 : 1;
880
- for (let index = start; index !== end && selected.length < maxCount; index += step) {
881
- const event = safe[index];
882
- const eventBytes = browserJsonBytes(event);
883
- const separator = selected.length === 0 ? 0 : 1;
884
- if (bytes + separator + eventBytes > maxBytes) break;
885
- selected.push(event);
886
- bytes += separator + eventBytes;
887
- }
888
- if (direction === "newest") selected.reverse();
889
- return {
890
- events: selected,
891
- bytes,
892
- truncated: selected.length < safe.length
893
- };
894
- }
895
- function boundBrowserLegacyEvent(event) {
896
- const serialized = browserSerialize(event);
897
- const originalBytes = serialized.serializable ? encoder.encode(serialized.value).byteLength : null;
898
- const typeIsSafe = browserUtf8Bytes(event.type) <= BROWSER_EVENT_TYPE_MAX_BYTES && !event.type.includes("\n") && !event.type.includes("\r");
899
- const clientEventId = boundBrowserOptionalText(
900
- event.clientEventId,
901
- BROWSER_EVENT_CLIENT_ID_MAX_BYTES
902
- );
903
- const duplicateReason = boundBrowserOptionalText(
904
- event.duplicateReason,
905
- BROWSER_EVENT_DUPLICATE_REASON_MAX_BYTES
906
- );
907
- if (serialized.serializable && originalBytes !== null && originalBytes <= SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES && typeIsSafe && clientEventId === event.clientEventId && duplicateReason === event.duplicateReason) {
908
- return event;
909
- }
910
- const envelopeProjection = [
911
- !typeIsSafe ? browserEnvelopeFieldProjection("type", event.type, "session.event.envelope_omitted") : null,
912
- clientEventId !== event.clientEventId ? browserEnvelopeFieldProjection("clientEventId", event.clientEventId, clientEventId) : null,
913
- duplicateReason !== event.duplicateReason ? browserEnvelopeFieldProjection("duplicateReason", event.duplicateReason, duplicateReason) : null
914
- ].filter((field) => field !== null);
915
- const payloadSerialization = browserSerialize(event.payload);
916
- const payloadBytes = payloadSerialization.serializable ? encoder.encode(payloadSerialization.value).byteLength : null;
917
- const preview = truncateBrowserUtf8Middle(
918
- payloadSerialization.value,
919
- BROWSER_EVENT_PAYLOAD_PREVIEW_MAX_BYTES
920
- );
921
- const truncation = {
922
- truncated: true,
923
- surface: "browser_legacy_guard",
924
- reason: serialized.serializable ? "event_envelope_bytes_exceeded" : "event_not_serializable",
925
- originalBytes,
926
- deliveredBytes: 0,
927
- omittedBytes: originalBytes,
928
- estimatedOriginalTokens: originalBytes === null ? null : Math.ceil(originalBytes / 4),
929
- estimatedDeliveredTokens: 0,
930
- fullEvidence: { available: false, reason: "not_retained" },
931
- details: [
932
- {
933
- path: "$.payload",
934
- kind: "payload_preview",
935
- originalBytes: payloadBytes,
936
- deliveredBytes: browserUtf8Bytes(preview)
937
- }
938
- ]
939
- };
940
- const payload = {
941
- ...browserPayloadIdentity(event.payload),
942
- preview,
943
- ...envelopeProjection.length > 0 ? {
944
- originalType: boundBrowserText(event.type, BROWSER_EVENT_TYPE_MAX_BYTES),
945
- envelopeProjection: {
946
- truncated: true,
947
- surface: "browser_legacy_guard",
948
- fields: envelopeProjection
949
- }
950
- } : {},
951
- truncation
952
- };
953
- const bounded = {
954
- id: boundBrowserText(event.id, BROWSER_EVENT_ID_MAX_BYTES),
955
- workspaceId: boundBrowserText(event.workspaceId, BROWSER_EVENT_ID_MAX_BYTES),
956
- sessionId: boundBrowserText(event.sessionId, BROWSER_EVENT_ID_MAX_BYTES),
957
- sequence: event.sequence,
958
- type: typeIsSafe ? event.type : "session.event.envelope_omitted",
959
- payload,
960
- occurredAt: boundBrowserText(event.occurredAt, BROWSER_EVENT_ID_MAX_BYTES),
961
- clientEventId,
962
- turnId: boundBrowserOptionalText(event.turnId, BROWSER_EVENT_ID_MAX_BYTES),
963
- turnGeneration: event.turnGeneration,
964
- turnAttemptId: boundBrowserOptionalText(event.turnAttemptId, BROWSER_EVENT_ID_MAX_BYTES),
965
- turnAssociation: event.turnAssociation,
966
- duplicateOfEventId: boundBrowserOptionalText(
967
- event.duplicateOfEventId,
968
- BROWSER_EVENT_ID_MAX_BYTES
969
- ),
970
- duplicateReason
971
- };
972
- settleBrowserEventTruncation(bounded, truncation);
973
- if (browserJsonBytes(bounded) > SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES) {
974
- payload.preview = truncateBrowserUtf8Middle(String(payload.preview), 4 * 1024);
975
- truncation.details = truncation.details.slice(0, 1);
976
- settleBrowserEventTruncation(bounded, truncation);
977
- }
978
- if (browserJsonBytes(bounded) > SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES) {
979
- bounded.clientEventId = null;
980
- bounded.duplicateReason = null;
981
- payload.preview = "[legacy event omitted at the browser byte boundary]";
982
- settleBrowserEventTruncation(bounded, truncation);
983
- }
984
- return bounded;
985
- }
986
- function browserJsonBytes(value) {
987
- return encoder.encode(browserSerialize(value).value).byteLength;
988
- }
989
- function browserSerialize(value) {
990
- try {
991
- const serialized = JSON.stringify(value);
992
- return { value: serialized === void 0 ? "null" : serialized, serializable: true };
993
- } catch {
994
- return {
995
- value: '"[unserializable event payload omitted at browser boundary]"',
996
- serializable: false
997
- };
998
- }
999
- }
1000
- function browserPayloadIdentity(payload) {
1001
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) return {};
1002
- const record = payload;
1003
- const identity = {};
1004
- for (const field of BROWSER_EVENT_PAYLOAD_IDENTITY_FIELDS) {
1005
- const value = record[field];
1006
- if (typeof value === "string") {
1007
- identity[field] = boundBrowserText(value, BROWSER_EVENT_ID_MAX_BYTES);
1008
- } else if (typeof value === "number" || typeof value === "boolean" || value === null) {
1009
- identity[field] = value;
1010
- }
1011
- }
1012
- return identity;
1013
- }
1014
- function browserEnvelopeFieldProjection(field, original, delivered) {
1015
- return {
1016
- field,
1017
- originalBytes: typeof original === "string" ? browserUtf8Bytes(original) : 0,
1018
- deliveredBytes: typeof delivered === "string" ? browserUtf8Bytes(delivered) : 0
1019
- };
1020
- }
1021
- function boundBrowserOptionalText(value, maxBytes) {
1022
- return typeof value === "string" ? boundBrowserText(value, maxBytes) : value;
1023
- }
1024
- function boundBrowserText(value, maxBytes) {
1025
- const bytes = encoder.encode(value);
1026
- if (bytes.byteLength <= maxBytes) return value;
1027
- const marker = "\u2026[truncated]";
1028
- const prefixBudget = Math.max(0, maxBytes - browserUtf8Bytes(marker));
1029
- let prefixEnd = Math.min(prefixBudget, bytes.byteLength);
1030
- while (prefixEnd > 0 && prefixEnd < bytes.byteLength && isBrowserUtf8Continuation(bytes[prefixEnd])) {
1031
- prefixEnd -= 1;
1032
- }
1033
- return `${decoder.decode(bytes.subarray(0, prefixEnd))}${marker}`;
1034
- }
1035
- function truncateBrowserUtf8Middle(value, maxBytes) {
1036
- const bytes = encoder.encode(value);
1037
- if (bytes.byteLength <= maxBytes) return value;
1038
- const marker = `\u2026[${bytes.byteLength - maxBytes} bytes omitted]\u2026`;
1039
- const contentBudget = Math.max(0, maxBytes - browserUtf8Bytes(marker));
1040
- const leftBudget = Math.floor(contentBudget / 2);
1041
- const rightBudget = contentBudget - leftBudget;
1042
- let leftEnd = Math.min(leftBudget, bytes.byteLength);
1043
- while (leftEnd > 0 && leftEnd < bytes.byteLength && isBrowserUtf8Continuation(bytes[leftEnd])) {
1044
- leftEnd -= 1;
1045
- }
1046
- let rightStart = Math.max(0, bytes.byteLength - rightBudget);
1047
- while (rightStart < bytes.byteLength && isBrowserUtf8Continuation(bytes[rightStart])) {
1048
- rightStart += 1;
1049
- }
1050
- return `${decoder.decode(bytes.subarray(0, leftEnd))}${marker}${decoder.decode(bytes.subarray(rightStart))}`;
1051
- }
1052
- function settleBrowserEventTruncation(event, truncation) {
1053
- for (let attempt = 0; attempt < 16; attempt += 1) {
1054
- const deliveredBytes = browserJsonBytes(event);
1055
- const omittedBytes = truncation.originalBytes === null ? null : Math.max(0, truncation.originalBytes - deliveredBytes);
1056
- const estimatedDeliveredTokens = Math.ceil(deliveredBytes / 4);
1057
- if (truncation.deliveredBytes === deliveredBytes && truncation.omittedBytes === omittedBytes && truncation.estimatedDeliveredTokens === estimatedDeliveredTokens) {
1058
- return;
1059
- }
1060
- truncation.deliveredBytes = deliveredBytes;
1061
- truncation.omittedBytes = omittedBytes;
1062
- truncation.estimatedDeliveredTokens = estimatedDeliveredTokens;
1063
- }
1064
- throw new RangeError("Browser event byte accounting did not converge");
1065
- }
1066
- function browserUtf8Bytes(value) {
1067
- return encoder.encode(value).byteLength;
1068
- }
1069
- function isBrowserUtf8Continuation(value) {
1070
- return (value & 192) === 128;
1071
- }
1072
- function observeSessionStatus(events, ref) {
1073
- let latest = null;
1074
- for (const event of events) {
1075
- const status = sessionStatusFromEvents([event]);
1076
- if (status && (!latest || event.sequence >= latest.sequence)) {
1077
- latest = { sequence: event.sequence, status };
1078
- }
1079
- }
1080
- if (!latest || latest.sequence < ref.current.sequence) return void 0;
1081
- ref.current = latest;
1082
- return latest.status;
1083
- }
1084
- async function planForegroundCatchup(client, workspaceId, sessionId, cursor, options = {}) {
1085
- if (options.signal?.aborted) throw abortError();
1086
- const session = await client.getSession(workspaceId, sessionId, {
1087
- fresh: true,
1088
- ...options.signal ? { signal: options.signal } : {}
1089
- });
1090
- if (options.signal?.aborted) throw abortError();
1091
- const durableHead = Math.max(cursor, session.lastSequence);
1092
- const rawGap = durableHead - cursor;
1093
- if (rawGap <= FOREGROUND_DIRECT_REPLAY_MAX_SEQUENCES) {
1094
- return { kind: "resume" };
1095
- }
1096
- if (rawGap > FOREGROUND_COMPACT_PROBE_MAX_SEQUENCES) {
1097
- return { kind: "reload" };
1098
- }
1099
- const page = await loadNextPage(client, workspaceId, sessionId, cursor, {
1100
- pageSize: rawGap,
1101
- ...options.signal ? { signal: options.signal } : {}
1102
- });
1103
- const events = page.filter((event) => eventResumeSequence(event) > cursor).map(boundBrowserLegacyEvent);
1104
- if (events.length === 0) {
1105
- return { kind: "reload" };
1106
- }
1107
- assertAscending(events);
1108
- const resumeSequence = maxResumeSequence(events);
1109
- if (resumeSequence < durableHead) {
1110
- return { kind: "reload" };
1111
- }
1112
- if (events.length > FOREGROUND_COMPACT_CATCHUP_MAX_EVENTS || groupCount(events) > FOREGROUND_COMPACT_CATCHUP_MAX_GROUPS || browserJsonBytes(events) > FOREGROUND_COMPACT_CATCHUP_MAX_BYTES) {
1113
- return { kind: "reload" };
1114
- }
1115
- return { kind: "append", events, resumeSequence };
1116
- }
1117
- async function loadEventWindow(client, workspaceId, sessionId, options) {
1118
- let cursor = options.before;
1119
- let buffer = [];
1120
- let reachedStart = false;
1121
- let fetches = 0;
1122
- while (fetches < options.maxFetches) {
1123
- if (buffer.length > 0 && groupCount(buffer) >= options.targetGroups) {
1124
- break;
1125
- }
1126
- const page = await loadPreviousPage(client, workspaceId, sessionId, cursor, {
1127
- pageSize: options.pageSize,
1128
- ...options.signal ? { signal: options.signal } : {}
1129
- });
1130
- fetches += 1;
1131
- if (page.length === 0) {
1132
- reachedStart = true;
1133
- break;
1134
- }
1135
- assertAscending(page);
1136
- buffer = [...page, ...buffer];
1137
- cursor = page[0].sequence;
1138
- if (isLogStart(page[0])) {
1139
- reachedStart = true;
1140
- break;
1141
- }
1142
- }
1143
- let snapPages = 0;
1144
- while (!reachedStart && findBoundaryIndex(buffer) === -1 && snapPages < BOUNDARY_PAGE_CAP && fetches < options.maxFetches) {
1145
- const page = await loadPreviousPage(client, workspaceId, sessionId, cursor, {
1146
- pageSize: options.pageSize,
1147
- ...options.signal ? { signal: options.signal } : {}
1148
- });
1149
- fetches += 1;
1150
- snapPages += 1;
1151
- if (page.length === 0) {
1152
- reachedStart = true;
1153
- break;
1154
- }
1155
- assertAscending(page);
1156
- buffer = [...page, ...buffer];
1157
- cursor = page[0].sequence;
1158
- if (isLogStart(page[0])) {
1159
- reachedStart = true;
1160
- break;
1161
- }
1162
- }
1163
- if (!reachedStart) {
1164
- const boundary = findBoundaryIndex(buffer);
1165
- if (boundary > 0) {
1166
- buffer = buffer.slice(boundary);
1167
- }
1168
- }
1169
- const densityWindow = trimBackwardToGroupTarget(buffer, options.targetGroups);
1170
- buffer = densityWindow.events;
1171
- const oldest = buffer[0] ?? null;
1172
- const newest = buffer[buffer.length - 1] ?? null;
1173
- return {
1174
- events: buffer,
1175
- oldestSequence: oldest?.sequence ?? null,
1176
- newestSequence: newest ? maxResumeSequence(buffer) : 0,
1177
- hasOlder: buffer.length > 0 && (densityWindow.trimmed || !reachedStart && oldest?.type !== "session.created")
1178
- };
1179
- }
1180
- function findBoundaryIndex(events) {
1181
- for (let index = 0; index < events.length; index += 1) {
1182
- if (isTurnBoundary(events[index])) {
1183
- return index;
1184
- }
1185
- }
1186
- return -1;
1187
- }
1188
- function isTurnBoundary(event) {
1189
- return event.type === "session.created" || event.type === "user.message";
1190
- }
1191
- async function loadForwardEventWindow(client, workspaceId, sessionId, options) {
1192
- let cursor = options.after;
1193
- let buffer = [];
1194
- let fetches = 0;
1195
- let reachedEnd = false;
1196
- while (fetches < options.maxFetches) {
1197
- if (buffer.length > 0 && groupCount(buffer) >= options.targetGroups) {
1198
- break;
1199
- }
1200
- const page = await loadNextPage(client, workspaceId, sessionId, cursor, {
1201
- pageSize: options.pageSize,
1202
- ...options.signal ? { signal: options.signal } : {}
1203
- });
1204
- fetches += 1;
1205
- if (page.length === 0) {
1206
- reachedEnd = true;
1207
- break;
1208
- }
1209
- assertAscending(page);
1210
- buffer = [...buffer, ...page];
1211
- cursor = maxResumeSequence(page);
1212
- }
1213
- const densityWindow = trimForwardToGroupTarget(buffer, options.targetGroups);
1214
- buffer = densityWindow.events;
1215
- const oldest = buffer[0] ?? null;
1216
- const newest = buffer[buffer.length - 1] ?? null;
1217
- return {
1218
- events: buffer,
1219
- oldestSequence: oldest?.sequence ?? null,
1220
- newestSequence: newest ? maxResumeSequence(buffer) : 0,
1221
- hasNewer: buffer.length > 0 && (densityWindow.trimmed || !reachedEnd)
1222
- };
1223
- }
1224
- function trimBackwardToGroupTarget(events, targetGroups) {
1225
- if (!Number.isFinite(targetGroups) || targetGroups <= 0 || events.length === 0) {
1226
- return { events, trimmed: false };
1227
- }
1228
- for (let index = events.length - 1; index > 0; index -= 1) {
1229
- if (!isTurnBoundary(events[index])) continue;
1230
- const candidate = events.slice(index);
1231
- if (groupCount(candidate) >= targetGroups) {
1232
- return { events: candidate, trimmed: true };
1233
- }
1234
- }
1235
- return { events, trimmed: false };
1236
- }
1237
- function trimForwardToGroupTarget(events, targetGroups) {
1238
- if (!Number.isFinite(targetGroups) || targetGroups <= 0 || events.length === 0) {
1239
- return { events, trimmed: false };
1240
- }
1241
- for (let index = 1; index < events.length; index += 1) {
1242
- if (!isTurnBoundary(events[index])) continue;
1243
- const candidate = events.slice(0, index);
1244
- if (groupCount(candidate) >= targetGroups) {
1245
- return { events: candidate, trimmed: true };
1246
- }
1247
- }
1248
- return { events, trimmed: false };
1249
- }
1250
- async function loadPreviousPage(client, workspaceId, sessionId, before, options) {
1251
- if (options.signal?.aborted) {
1252
- throw abortError();
1253
- }
1254
- const requested = options.pageSize;
1255
- if (requested === 0) {
1256
- return Object.assign([], { requested });
1257
- }
1258
- const page = await client.listEvents(workspaceId, sessionId, {
1259
- before,
1260
- limit: requested,
1261
- compact: true,
1262
- // Monitoring summaries omit correlation fields once a bounded durable
1263
- // payload exceeds their preview threshold. The browser timeline needs the
1264
- // exact stored payload so persisted tool outputs still match their
1265
- // calls and expose truthful truncation telemetry after a reload.
1266
- payloadMode: "full"
1267
- });
1268
- if (options.signal?.aborted) {
1269
- throw abortError();
1270
- }
1271
- return Object.assign(page, { requested });
1272
- }
1273
- async function loadNextPage(client, workspaceId, sessionId, after, options) {
1274
- if (options.signal?.aborted) {
1275
- throw abortError();
1276
- }
1277
- const requested = options.pageSize;
1278
- if (requested === 0) {
1279
- return Object.assign([], { requested });
1280
- }
1281
- const page = await client.listEvents(workspaceId, sessionId, {
1282
- after,
1283
- limit: requested,
1284
- compact: true,
1285
- direction: "after",
1286
- payloadMode: "full"
1287
- });
1288
- if (options.signal?.aborted) {
1289
- throw abortError();
1290
- }
1291
- return Object.assign(page, { requested });
1292
- }
1293
- function groupCount(events) {
1294
- if (events.length === 0) {
1295
- return 0;
1296
- }
1297
- return groupTimeline(buildTimeline(events)).length;
1298
- }
1299
- function isLogStart(event) {
1300
- return event.type === "session.created" || event.sequence <= 1;
1301
- }
1302
- function maxResumeSequence(events) {
1303
- return events.reduce((max, event) => Math.max(max, eventResumeSequence(event)), 0);
1304
- }
1305
- function maxResumeSequenceOrNull(events) {
1306
- return events.length > 0 ? maxResumeSequence(events) : null;
1307
- }
1308
- function eventResumeSequence(event) {
1309
- const streamedCoverage = sessionEventStreamCoveredThrough(event);
1310
- if (streamedCoverage !== null) return streamedCoverage;
1311
- return typeof event.coveredThrough === "number" && Number.isSafeInteger(event.coveredThrough) && event.coveredThrough >= event.sequence ? event.coveredThrough : event.sequence;
1312
- }
1313
- function assertAscending(events) {
1314
- for (let index = 1; index < events.length; index += 1) {
1315
- if (events[index - 1].sequence >= events[index].sequence) {
1316
- throw new Error("@opengeni/react: session events must be ordered by ascending sequence");
1317
- }
1318
- }
1319
- }
1320
- function assertPrependOrder(existing, older) {
1321
- if (!shouldAssertDevelopment() || existing.length === 0 || older.length === 0) {
1322
- return;
1323
- }
1324
- if (older[older.length - 1].sequence >= existing[0].sequence) {
1325
- throw new Error("@opengeni/react: loadOlder returned overlapping session events");
1326
- }
1327
- }
1328
- function assertAppendOrder(existing, newer) {
1329
- if (!shouldAssertDevelopment() || existing.length === 0 || newer.length === 0) {
1330
- return;
1331
- }
1332
- if (newer[0].sequence <= existing[existing.length - 1].sequence) {
1333
- throw new Error("@opengeni/react: loadNewer returned overlapping session events");
1334
- }
1335
- }
1336
- function shouldAssertDevelopment() {
1337
- const processEnv = globalThis.process?.env;
1338
- return processEnv !== void 0 && processEnv.NODE_ENV !== "production";
1339
- }
1340
- function abortError() {
1341
- const error = new Error("The operation was aborted.");
1342
- error.name = "AbortError";
1343
- return error;
1344
- }
1345
-
1346
- // src/hooks/use-file-attachments.ts
1347
- import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
1348
- var isImage = (file) => file.type.startsWith("image/");
1349
- var fallbackAttachmentId = 0;
1350
- function createAttachmentId() {
1351
- const cryptoSource = globalThis.crypto;
1352
- if (typeof cryptoSource?.randomUUID === "function") return cryptoSource.randomUUID();
1353
- fallbackAttachmentId += 1;
1354
- return `attachment:${Date.now().toString(36)}:${fallbackAttachmentId.toString(36)}`;
1355
- }
1356
- function secureContextRequiredErrorCode(error) {
1357
- return typeof error === "object" && error !== null && "code" in error && error.code === "secure_context_required" ? error.code : void 0;
1358
- }
1359
- function useFileAttachments(options = {}) {
1360
- const { client, workspaceId } = useEmbeddedFileAttachments(options);
1361
- const pasteFilter = options.pasteFilter ?? isImage;
1362
- const [attachments, setAttachments] = useState3([]);
1363
- const sources = useRef3(/* @__PURE__ */ new Map());
1364
- const previewUrls = useRef3(/* @__PURE__ */ new Map());
1365
- const previewRetainers = useRef3(/* @__PURE__ */ new Map());
1366
- const pendingPreviewRevocations = useRef3(/* @__PURE__ */ new Set());
1367
- const revokePreview = useCallback3((id) => {
1368
- const previewUrl = previewUrls.current.get(id);
1369
- if (!previewUrl) return;
1370
- if ((previewRetainers.current.get(id) ?? 0) > 0) {
1371
- pendingPreviewRevocations.current.add(id);
1372
- return;
1373
- }
1374
- previewUrls.current.delete(id);
1375
- pendingPreviewRevocations.current.delete(id);
1376
- URL.revokeObjectURL(previewUrl);
1377
- }, []);
1378
- const revokeAllPreviews = useCallback3(() => {
1379
- for (const id of previewUrls.current.keys()) revokePreview(id);
1380
- }, [revokePreview]);
1381
- const retainPreview = useCallback3(
1382
- (id) => {
1383
- if (!previewUrls.current.has(id)) return void 0;
1384
- previewRetainers.current.set(id, (previewRetainers.current.get(id) ?? 0) + 1);
1385
- let released = false;
1386
- return () => {
1387
- if (released) return;
1388
- released = true;
1389
- const remaining = (previewRetainers.current.get(id) ?? 1) - 1;
1390
- if (remaining > 0) {
1391
- previewRetainers.current.set(id, remaining);
1392
- return;
1393
- }
1394
- previewRetainers.current.delete(id);
1395
- if (pendingPreviewRevocations.current.has(id)) revokePreview(id);
1396
- };
1397
- },
1398
- [revokePreview]
1399
- );
1400
- const committedAttachmentIds = useRef3(/* @__PURE__ */ new Set());
1401
- useEffect3(() => {
1402
- const currentIds = new Set(attachments.map((attachment) => attachment.id));
1403
- for (const id of committedAttachmentIds.current) {
1404
- if (!currentIds.has(id)) revokePreview(id);
1405
- }
1406
- committedAttachmentIds.current = currentIds;
1407
- }, [attachments, revokePreview]);
1408
- const scopeGeneration = useRef3(0);
1409
- const previousScope = useRef3({ client, workspaceId });
1410
- useEffect3(() => {
1411
- const previous = previousScope.current;
1412
- if (previous.client === client && previous.workspaceId === workspaceId) return;
1413
- previousScope.current = { client, workspaceId };
1414
- scopeGeneration.current += 1;
1415
- sources.current.clear();
1416
- revokeAllPreviews();
1417
- setAttachments([]);
1418
- }, [client, revokeAllPreviews, workspaceId]);
1419
- useEffect3(
1420
- () => () => {
1421
- scopeGeneration.current += 1;
1422
- sources.current.clear();
1423
- revokeAllPreviews();
1424
- },
1425
- [revokeAllPreviews]
1426
- );
1427
- const startUpload = useCallback3(
1428
- (id, file) => {
1429
- const generation = scopeGeneration.current;
1430
- void client.uploadFile(workspaceId, {
1431
- filename: file.name || "file",
1432
- contentType: file.type || "application/octet-stream",
1433
- data: file
1434
- }).then((asset) => {
1435
- if (scopeGeneration.current !== generation) return;
1436
- sources.current.delete(id);
1437
- setAttachments(
1438
- (current) => current.map(
1439
- (attachment) => attachment.id === id ? {
1440
- ...attachment,
1441
- status: "ready",
1442
- file: asset,
1443
- name: asset.filename,
1444
- contentType: asset.contentType,
1445
- sizeBytes: asset.sizeBytes,
1446
- errorCode: void 0,
1447
- error: void 0
1448
- } : attachment
1449
- )
1450
- );
1451
- }).catch((error) => {
1452
- if (scopeGeneration.current !== generation) return;
1453
- setAttachments(
1454
- (current) => current.map(
1455
- (attachment) => attachment.id === id ? {
1456
- ...attachment,
1457
- status: "failed",
1458
- errorCode: secureContextRequiredErrorCode(error),
1459
- error: error instanceof Error ? error.message : String(error)
1460
- } : attachment
1461
- )
1462
- );
1463
- });
1464
- },
1465
- [client, workspaceId]
1466
- );
1467
- const addFiles = useCallback3(
1468
- (files) => {
1469
- for (const file of files) {
1470
- const id = createAttachmentId();
1471
- sources.current.set(id, file);
1472
- const previewUrl = isImage(file) ? URL.createObjectURL(file) : void 0;
1473
- if (previewUrl) previewUrls.current.set(id, previewUrl);
1474
- setAttachments((current) => [
1475
- ...current,
1476
- {
1477
- id,
1478
- name: file.name || "image",
1479
- contentType: file.type || "application/octet-stream",
1480
- sizeBytes: file.size,
1481
- status: "uploading",
1482
- ...previewUrl ? { previewUrl } : {}
1483
- }
1484
- ]);
1485
- startUpload(id, file);
1486
- }
1487
- },
1488
- [startUpload]
1489
- );
1490
- const retry = useCallback3(
1491
- (id) => {
1492
- const file = sources.current.get(id);
1493
- if (!file) {
1494
- return;
1495
- }
1496
- setAttachments(
1497
- (current) => current.map(
1498
- (attachment) => attachment.id === id ? { ...attachment, status: "uploading", errorCode: void 0, error: void 0 } : attachment
1499
- )
1500
- );
1501
- startUpload(id, file);
1502
- },
1503
- [startUpload]
1504
- );
1505
- const addFromPaste = useCallback3(
1506
- (event) => {
1507
- const clipboardFiles = event.clipboardData?.files;
1508
- if (!clipboardFiles) {
1509
- return;
1510
- }
1511
- const files = [...clipboardFiles].filter(pasteFilter);
1512
- if (files.length > 0) {
1513
- addFiles(files);
1514
- }
1515
- },
1516
- [addFiles, pasteFilter]
1517
- );
1518
- const restoreReadyFiles = useCallback3(
1519
- (files) => {
1520
- const incoming = /* @__PURE__ */ new Map();
1521
- for (const file of files) {
1522
- if (file.status === "ready" && file.workspaceId === workspaceId) {
1523
- incoming.set(file.id, file);
1524
- }
1525
- }
1526
- setAttachments((current) => {
1527
- const unresolved = current.filter((attachment) => attachment.status !== "ready");
1528
- const existingReady = new Map(
1529
- current.flatMap(
1530
- (attachment) => attachment.status === "ready" && attachment.file ? [[attachment.file.id, attachment]] : []
1531
- )
1532
- );
1533
- const restored = [...incoming.values()].map((file) => {
1534
- const existing = existingReady.get(file.id);
1535
- return existing ? {
1536
- ...existing,
1537
- name: file.filename,
1538
- contentType: file.contentType,
1539
- sizeBytes: file.sizeBytes,
1540
- status: "ready",
1541
- file,
1542
- errorCode: void 0,
1543
- error: void 0
1544
- } : {
1545
- id: `restored:${file.id}`,
1546
- name: file.filename,
1547
- contentType: file.contentType,
1548
- sizeBytes: file.sizeBytes,
1549
- status: "ready",
1550
- file
1551
- // No source File and no object URL: server metadata is the
1552
- // only authority restored across page/device boundaries.
1553
- };
1554
- });
1555
- return [...unresolved, ...restored];
1556
- });
1557
- },
1558
- [workspaceId]
1559
- );
1560
- const remove = useCallback3(
1561
- (id) => {
1562
- sources.current.delete(id);
1563
- revokePreview(id);
1564
- setAttachments((current) => current.filter((attachment) => attachment.id !== id));
1565
- },
1566
- [revokePreview]
1567
- );
1568
- const removeReadyFiles = useCallback3((fileIds) => {
1569
- const accepted = new Set(fileIds);
1570
- if (accepted.size === 0) return;
1571
- setAttachments(
1572
- (current) => current.filter((attachment) => {
1573
- return !(attachment.status === "ready" && attachment.file !== void 0 && accepted.has(attachment.file.id));
1574
- })
1575
- );
1576
- }, []);
1577
- const clear = useCallback3(() => {
1578
- sources.current.clear();
1579
- revokeAllPreviews();
1580
- setAttachments([]);
1581
- }, [revokeAllPreviews]);
1582
- return {
1583
- attachments,
1584
- readyResources: attachments.flatMap(
1585
- (attachment) => attachment.status === "ready" && attachment.file ? [{ kind: "file", fileId: attachment.file.id }] : []
1586
- ),
1587
- uploading: attachments.some((attachment) => attachment.status === "uploading"),
1588
- hasUnresolved: attachments.some((attachment) => attachment.status !== "ready"),
1589
- addFiles,
1590
- addFromPaste,
1591
- restoreReadyFiles,
1592
- retry,
1593
- retainPreview,
1594
- remove,
1595
- removeReadyFiles,
1596
- clear
1597
- };
1598
- }
1599
-
1600
- // src/hooks/use-turn-queue.ts
1601
- import { useCallback as useCallback4, useEffect as useEffect4, useLayoutEffect, useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
1602
- function isTurnQueueEvent(event) {
1603
- 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.");
1604
- }
1605
- function queueSnapshotCovers(candidate, observed) {
1606
- return Boolean(
1607
- candidate && candidate.version >= observed.version && candidate.effectiveControl.controlVersion >= observed.effectiveControl.controlVersion
1608
- );
1609
- }
1610
- var EMPTY_PENDING_BY_TURN = {};
1611
- function useTurnQueue(sessionId, options = {}) {
1612
- const { client, workspaceId, workspaceControlEvent, registerSessionReconciler } = useEmbeddedSession(options);
1613
- const enabled = (options.enabled ?? true) && Boolean(sessionId);
1614
- const targetKey = `${workspaceId}\0${sessionId ?? ""}`;
1615
- const pageLive = usePageLiveActivity();
1616
- const [snapshot, setSnapshot] = useState4(null);
1617
- const [stateTargetKey, setStateTargetKey] = useState4(targetKey);
1618
- const [loading, setLoading] = useState4(enabled);
1619
- const [error, setError] = useState4(null);
1620
- const [mutationError, setMutationError] = useState4(null);
1621
- const [pendingByTurn, setPendingByTurn] = useState4({});
1622
- const [acceptedSteers, setAcceptedSteers] = useState4([]);
1623
- const pendingRef = useRef4({});
1624
- const readGeneration = useRef4(0);
1625
- const targetKeyRef = useRef4(targetKey);
1626
- const snapshotRef = useRef4(null);
1627
- useLayoutEffect(() => {
1628
- if (targetKeyRef.current === targetKey) return;
1629
- targetKeyRef.current = targetKey;
1630
- readGeneration.current += 1;
1631
- snapshotRef.current = null;
1632
- pendingRef.current = {};
1633
- setStateTargetKey(targetKey);
1634
- setSnapshot(null);
1635
- setLoading(enabled);
1636
- setError(null);
1637
- setMutationError(null);
1638
- setPendingByTurn({});
1639
- setAcceptedSteers([]);
1640
- }, [enabled, targetKey]);
1641
- useEffect4(() => {
1642
- if (acceptedSteers.length === 0 || options.events === void 0) return;
1643
- const durablyStartedTurnIds = new Set(
1644
- options.events.flatMap(
1645
- (event) => event.type === "turn.started" && event.turnId ? [event.turnId] : []
1646
- )
1647
- );
1648
- if (durablyStartedTurnIds.size === 0) return;
1649
- setAcceptedSteers(
1650
- (current) => current.filter((steer) => !durablyStartedTurnIds.has(steer.turnId))
1651
- );
1652
- }, [acceptedSteers.length, options.events]);
1653
- const acceptSnapshot = useCallback4(
1654
- (ownedTargetKey, next) => {
1655
- if (targetKeyRef.current !== ownedTargetKey) return false;
1656
- const current = snapshotRef.current;
1657
- if (next && current && (next.version < current.version || next.effectiveControl.controlVersion < current.effectiveControl.controlVersion)) {
1658
- return false;
1659
- }
1660
- snapshotRef.current = next;
1661
- setStateTargetKey(ownedTargetKey);
1662
- setSnapshot(next);
1663
- return true;
1664
- },
1665
- []
1666
- );
1667
- const load = useCallback4(
1668
- async (rejectOnFailure = false) => {
1669
- if (!sessionId) return;
1670
- const ownedTargetKey = `${workspaceId}\0${sessionId}`;
1671
- const ticket = ++readGeneration.current;
1672
- try {
1673
- const fetched = await client.getQueue(workspaceId, sessionId);
1674
- if (targetKeyRef.current !== ownedTargetKey) return;
1675
- const ownsLatestRead = ticket === readGeneration.current;
1676
- if (rejectOnFailure) {
1677
- const committed = acceptSnapshot(ownedTargetKey, fetched);
1678
- if (!committed && !queueSnapshotCovers(snapshotRef.current, fetched)) {
1679
- throw new TypeError("Queue reconciliation did not commit authoritative state");
1680
- }
1681
- setError(null);
1682
- if (ownsLatestRead) setLoading(false);
1683
- } else if (ownsLatestRead) {
1684
- acceptSnapshot(ownedTargetKey, fetched);
1685
- setError(null);
1686
- setLoading(false);
1687
- }
1688
- } catch (cause) {
1689
- if (targetKeyRef.current === ownedTargetKey && (ticket === readGeneration.current || rejectOnFailure)) {
1690
- setError(asError(cause));
1691
- if (ticket === readGeneration.current) setLoading(false);
1692
- }
1693
- if (rejectOnFailure) throw cause;
1694
- }
1695
- },
1696
- [client, workspaceId, sessionId, acceptSnapshot]
1697
- );
1698
- useEffect4(() => {
1699
- if (!enabled || !pageLive) {
1700
- setLoading(false);
1701
- return;
1702
- }
1703
- setLoading(true);
1704
- const pollIntervalMs = options.pollIntervalMs;
1705
- if (pollIntervalMs === void 0 || pollIntervalMs <= 0) {
1706
- void load();
1707
- return () => {
1708
- readGeneration.current += 1;
1709
- };
1710
- }
1711
- let cancelled = false;
1712
- let timer = null;
1713
- const schedule = () => {
1714
- if (cancelled) return;
1715
- timer = setTimeout(() => {
1716
- timer = null;
1717
- void load().finally(schedule);
1718
- }, pollIntervalMs);
1719
- };
1720
- void load().finally(schedule);
1721
- return () => {
1722
- cancelled = true;
1723
- if (timer !== null) clearTimeout(timer);
1724
- readGeneration.current += 1;
1725
- };
1726
- }, [load, enabled, pageLive, options.pollIntervalMs]);
1727
- useEffect4(() => {
1728
- if (enabled && workspaceControlEvent) void load();
1729
- }, [enabled, load, workspaceControlEvent]);
1730
- useEffect4(() => {
1731
- if (!sessionId || !enabled) return;
1732
- return registerSessionReconciler(sessionId, "queue", load);
1733
- }, [enabled, load, registerSessionReconciler, sessionId]);
1734
- const scheduleRefresh = useDebouncedCallback(() => void load());
1735
- useSessionEventTrigger(
1736
- client,
1737
- workspaceId,
1738
- sessionId,
1739
- isTurnQueueEvent,
1740
- scheduleRefresh,
1741
- {
1742
- enabled,
1743
- ...options.events !== void 0 ? { events: options.events } : {}
1744
- },
1745
- async () => await load(true)
1746
- );
1747
- const mutate = useCallback4(
1748
- async (turnId, kind, command) => {
1749
- const ownedTargetKey = targetKey;
1750
- if (!sessionId || targetKeyRef.current !== ownedTargetKey || pendingRef.current[turnId]) {
1751
- return null;
1752
- }
1753
- const current = snapshotRef.current;
1754
- const turn = current?.items.find((candidate) => candidate.id === turnId);
1755
- if (!current || !turn) return null;
1756
- pendingRef.current = { ...pendingRef.current, [turnId]: kind };
1757
- setStateTargetKey(ownedTargetKey);
1758
- setPendingByTurn(pendingRef.current);
1759
- setMutationError(null);
1760
- const clientEventId = operationKey();
1761
- try {
1762
- let result;
1763
- try {
1764
- result = await command(current, turn, clientEventId);
1765
- } catch (cause) {
1766
- if (!isOutcomeUnknownError(cause)) throw cause;
1767
- result = await command(current, turn, clientEventId);
1768
- }
1769
- if (targetKeyRef.current !== ownedTargetKey) return null;
1770
- acceptSnapshot(ownedTargetKey, result.snapshot);
1771
- return result;
1772
- } catch (cause) {
1773
- if (targetKeyRef.current === ownedTargetKey) {
1774
- setMutationError(asError(cause));
1775
- await load();
1776
- }
1777
- return null;
1778
- } finally {
1779
- if (targetKeyRef.current === ownedTargetKey && turnId in pendingRef.current) {
1780
- const next = { ...pendingRef.current };
1781
- delete next[turnId];
1782
- pendingRef.current = next;
1783
- setPendingByTurn(next);
1784
- }
1785
- }
1786
- },
1787
- [acceptSnapshot, load, sessionId, targetKey]
1788
- );
1789
- const moveTurn = useCallback4(
1790
- async (turnId, beforeTurnId) => {
1791
- const result = await mutate(
1792
- turnId,
1793
- "move",
1794
- (current, _turn, clientEventId) => client.moveQueueItem(workspaceId, sessionId, turnId, {
1795
- clientEventId,
1796
- expectedQueueVersion: current.version,
1797
- beforeTurnId
1798
- })
1799
- );
1800
- return result !== null;
1801
- },
1802
- [client, mutate, sessionId, workspaceId]
1803
- );
1804
- const editTurn = useCallback4(
1805
- async (turnId, edit) => {
1806
- const result = await mutate(
1807
- turnId,
1808
- "edit",
1809
- (_current, turn, clientEventId) => client.editQueueItem(workspaceId, sessionId, turnId, {
1810
- clientEventId,
1811
- expectedTurnVersion: turn.version,
1812
- expectedDraftRevision: edit.expectedDraftRevision,
1813
- replaceDraft: edit.replaceDraft
1814
- })
1815
- );
1816
- return result?.draft ?? null;
1817
- },
1818
- [client, mutate, sessionId, workspaceId]
1819
- );
1820
- const steerTurn = useCallback4(
1821
- async (turnId) => {
1822
- const ownedTargetKey = targetKey;
1823
- const turn = snapshotRef.current?.items.find((candidate) => candidate.id === turnId);
1824
- if (targetKeyRef.current !== ownedTargetKey) return false;
1825
- if (!turn) {
1826
- setMutationError(new Error("That queued prompt changed before Steer could be sent."));
1827
- await load();
1828
- return false;
1829
- }
1830
- const optimistic = {
1831
- turnId: turn.id,
1832
- triggerEventId: turn.triggerEventId,
1833
- text: turn.prompt,
1834
- annotations: turn.annotations ?? [],
1835
- resources: turn.resources,
1836
- tools: turn.tools,
1837
- occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
1838
- state: "sending"
1839
- };
1840
- setAcceptedSteers((current) => [
1841
- ...current.filter((steer) => steer.turnId !== turnId),
1842
- optimistic
1843
- ]);
1844
- const result = await mutate(
1845
- turnId,
1846
- "steer",
1847
- (current, queuedTurn, clientEventId) => client.steerQueueItem(workspaceId, sessionId, turnId, {
1848
- clientEventId,
1849
- expectedTurnVersion: queuedTurn.version,
1850
- controlEtag: current.effectiveControl.controlEtag
1851
- })
1852
- );
1853
- if (targetKeyRef.current !== ownedTargetKey) return false;
1854
- const advancedWithoutSteer = result === null && snapshotRef.current !== null && !snapshotRef.current.items.some((candidate) => candidate.id === turnId);
1855
- if (advancedWithoutSteer) {
1856
- setMutationError(null);
1857
- setAcceptedSteers((current) => current.filter((steer) => steer.turnId !== turnId));
1858
- return true;
1859
- }
1860
- const accepted = result !== null || snapshotRef.current?.items.some(
1861
- (candidate) => candidate.id === turnId && candidate.metadata.delivery === "steer"
1862
- ) === true;
1863
- setAcceptedSteers(
1864
- (current) => accepted ? current.map(
1865
- (steer) => steer.turnId === turnId ? { ...steer, state: "queued" } : steer
1866
- ) : current.filter((steer) => steer.turnId !== turnId)
1867
- );
1868
- return accepted;
1869
- },
1870
- [client, load, mutate, sessionId, targetKey, workspaceId]
1871
- );
1872
- const removeTurn = useCallback4(
1873
- async (turnId) => {
1874
- const result = await mutate(
1875
- turnId,
1876
- "delete",
1877
- (_current, turn, clientEventId) => client.deleteQueueItem(workspaceId, sessionId, turnId, {
1878
- clientEventId,
1879
- expectedTurnVersion: turn.version,
1880
- reason: "Deleted from the prompt queue"
1881
- })
1882
- );
1883
- return result !== null;
1884
- },
1885
- [client, mutate, sessionId, workspaceId]
1886
- );
1887
- const identityMatches = stateTargetKey === targetKey;
1888
- const visibleSnapshot = identityMatches ? snapshot : null;
1889
- const visiblePendingByTurn = identityMatches ? pendingByTurn : EMPTY_PENDING_BY_TURN;
1890
- const mutating = useMemo3(
1891
- () => Object.keys(visiblePendingByTurn).length > 0,
1892
- [visiblePendingByTurn]
1893
- );
1894
- const mutationFor = useCallback4(
1895
- (turnId) => visiblePendingByTurn[turnId] ?? null,
1896
- [visiblePendingByTurn]
1897
- );
1898
- return {
1899
- snapshot: visibleSnapshot,
1900
- queue: visibleSnapshot?.items ?? [],
1901
- pendingInputs: visibleSnapshot?.pendingInputs ?? [],
1902
- pendingInputAttachment: visibleSnapshot?.pendingInputAttachment ?? null,
1903
- activePersonalConnections: visibleSnapshot?.activePersonalConnections ?? [],
1904
- effectiveControl: visibleSnapshot?.effectiveControl ?? null,
1905
- stoppingPreviousAttempt: visibleSnapshot?.stoppingPreviousAttempt ?? false,
1906
- loading: identityMatches ? loading : enabled,
1907
- error: identityMatches ? error : null,
1908
- refresh: load,
1909
- moveTurn,
1910
- editTurn,
1911
- steerTurn,
1912
- removeTurn,
1913
- pendingByTurn: visiblePendingByTurn,
1914
- mutationFor,
1915
- mutating,
1916
- mutationError: identityMatches ? mutationError : null,
1917
- clearMutationError: useCallback4(() => {
1918
- if (targetKeyRef.current === targetKey) setMutationError(null);
1919
- }, [targetKey]),
1920
- acceptedSteers: identityMatches ? acceptedSteers : []
1921
- };
1922
- }
1923
- function operationKey() {
1924
- return globalThis.crypto.randomUUID();
1925
- }
1926
- function asError(cause) {
1927
- return cause instanceof Error ? cause : new Error(String(cause));
1928
- }
1929
- function isOutcomeUnknownError(cause) {
1930
- return typeof cause === "object" && cause !== null && cause.outcomeUnknown === true;
1931
- }
1932
-
1933
- // src/hooks/use-goal.ts
1934
- import { OpenGeniApiError } from "@opengeni/sdk";
1935
- import { useCallback as useCallback5, useEffect as useEffect5, useRef as useRef5, useState as useState5 } from "react";
1936
- function isGoalEvent(event) {
1937
- return event.type.startsWith("goal.");
1938
- }
1939
- function isGoalRefreshEvent(event) {
1940
- const type = event.type;
1941
- return isGoalEvent(event) || type.startsWith("turn.") || type.startsWith("session.") || type.startsWith("system.update.") || type.startsWith("workspace.inference.") || type.startsWith("user.");
1942
- }
1943
- function useGoal(sessionId, options = {}) {
1944
- const { client, workspaceId } = useEmbeddedGoal(options);
1945
- const enabled = (options.enabled ?? true) && Boolean(sessionId);
1946
- const sharedEvents = options.events;
1947
- const sharedFeed = sharedEvents !== void 0;
1948
- const pageLive = usePageLiveActivity();
1949
- const [goal, setGoal] = useState5(null);
1950
- const [loading, setLoading] = useState5(enabled);
1951
- const [error, setError] = useState5(null);
1952
- const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
1953
- const generation = useRef5(0);
1954
- const targetKeyRef = useRef5(null);
1955
- const loadAbort = useRef5(null);
1956
- const retireLoads = useCallback5(() => {
1957
- generation.current += 1;
1958
- loadAbort.current?.abort();
1959
- loadAbort.current = null;
1960
- }, []);
1961
- const load = useCallback5(async () => {
1962
- if (!sessionId) {
1963
- return;
1964
- }
1965
- const ticket = ++generation.current;
1966
- loadAbort.current?.abort();
1967
- const controller = new AbortController();
1968
- loadAbort.current = controller;
1969
- try {
1970
- const fetched = await client.getGoal(workspaceId, sessionId, {
1971
- signal: controller.signal
1972
- });
1973
- if (ticket === generation.current) {
1974
- setGoal(fetched);
1975
- setError(null);
1976
- setLoading(false);
1977
- }
1978
- } catch (cause) {
1979
- if (ticket !== generation.current) {
1980
- return;
1981
- }
1982
- if (cause instanceof OpenGeniApiError && cause.status === 404) {
1983
- setGoal(null);
1984
- setError(null);
1985
- } else {
1986
- setError(cause instanceof Error ? cause : new Error(String(cause)));
1987
- }
1988
- setLoading(false);
1989
- } finally {
1990
- if (loadAbort.current === controller) loadAbort.current = null;
1991
- }
1992
- }, [client, workspaceId, sessionId]);
1993
- useEffect5(() => {
1994
- const targetKey = `${workspaceId} ${sessionId ?? ""}`;
1995
- if (targetKeyRef.current !== targetKey) {
1996
- targetKeyRef.current = targetKey;
1997
- setGoal(null);
1998
- setError(null);
1999
- }
2000
- if (!enabled || !pageLive) {
2001
- retireLoads();
2002
- setLoading(false);
2003
- return;
2004
- }
2005
- if (sharedFeed) {
2006
- setLoading(false);
2007
- return () => {
2008
- retireLoads();
2009
- };
2010
- }
2011
- setLoading(true);
2012
- const pollIntervalMs = options.pollIntervalMs;
2013
- if (pollIntervalMs === void 0 || pollIntervalMs <= 0) {
2014
- void load();
2015
- return () => {
2016
- retireLoads();
2017
- };
2018
- }
2019
- let cancelled = false;
2020
- let timer = null;
2021
- const schedule = () => {
2022
- if (cancelled) return;
2023
- timer = setTimeout(() => {
2024
- timer = null;
2025
- void load().finally(schedule);
2026
- }, pollIntervalMs);
2027
- };
2028
- void load().finally(schedule);
2029
- return () => {
2030
- cancelled = true;
2031
- if (timer !== null) clearTimeout(timer);
2032
- retireLoads();
2033
- };
2034
- }, [
2035
- load,
2036
- enabled,
2037
- pageLive,
2038
- workspaceId,
2039
- sessionId,
2040
- options.pollIntervalMs,
2041
- retireLoads,
2042
- sharedFeed
2043
- ]);
2044
- const scheduleRefresh = useDebouncedCallback(() => void load());
2045
- useSessionEventTrigger(client, workspaceId, sessionId, isGoalRefreshEvent, scheduleRefresh, {
2046
- enabled,
2047
- ...sharedEvents !== void 0 ? { events: sharedEvents } : {}
2048
- });
2049
- const pause = useCallback5(
2050
- async (rationale) => {
2051
- if (!sessionId) {
2052
- return null;
2053
- }
2054
- const result = await run(
2055
- () => client.updateGoal(workspaceId, sessionId, {
2056
- status: "paused",
2057
- ...rationale !== void 0 ? { rationale } : {}
2058
- })
2059
- );
2060
- if (result) {
2061
- setGoal(result);
2062
- }
2063
- return result;
2064
- },
2065
- [client, workspaceId, sessionId, run]
2066
- );
2067
- const resume = useCallback5(async () => {
2068
- if (!sessionId) {
2069
- return null;
2070
- }
2071
- const result = await run(() => client.updateGoal(workspaceId, sessionId, { status: "active" }));
2072
- if (result) {
2073
- setGoal(result);
2074
- }
2075
- return result;
2076
- }, [client, workspaceId, sessionId, run]);
2077
- const clearGoal = useCallback5(async () => {
2078
- if (!sessionId) {
2079
- return;
2080
- }
2081
- const ok = await run(async () => {
2082
- await client.deleteGoal(workspaceId, sessionId);
2083
- return true;
2084
- });
2085
- if (ok) {
2086
- retireLoads();
2087
- setGoal(null);
2088
- setError(null);
2089
- }
2090
- }, [client, workspaceId, sessionId, run, retireLoads]);
2091
- return {
2092
- goal,
2093
- isActive: goal?.status === "active",
2094
- isPaused: goal?.status === "paused",
2095
- isCompleted: goal?.status === "completed",
2096
- loading,
2097
- error,
2098
- refresh: load,
2099
- pause,
2100
- resume,
2101
- clearGoal,
2102
- deleteGoal: clearGoal,
2103
- updating: mutating,
2104
- mutationError,
2105
- clearMutationError
2106
- };
2107
- }
2108
-
2109
- // src/hooks/use-session-control.ts
2110
- import { useCallback as useCallback6, useLayoutEffect as useLayoutEffect2, useRef as useRef6 } from "react";
2111
- function useSessionControl(sessionId, options = {}) {
2112
- const { client, workspaceId } = useEmbeddedSession(options);
2113
- const approvalTargetKey = `${workspaceId}\0${sessionId ?? ""}`;
2114
- const pendingApproval = useRef6(null);
2115
- useLayoutEffect2(() => {
2116
- if (pendingApproval.current?.targetKey !== approvalTargetKey) {
2117
- pendingApproval.current = null;
2118
- }
2119
- }, [approvalTargetKey]);
2120
- const {
2121
- run: runControl,
2122
- mutating: controlling,
2123
- mutationError: controlError,
2124
- clearMutationError: clearControlError
2125
- } = useMutationRunner(approvalTargetKey);
2126
- const {
2127
- run: runApproval,
2128
- mutating: responding,
2129
- mutationError: approvalError,
2130
- clearMutationError: clearApprovalError
2131
- } = useMutationRunner(approvalTargetKey);
2132
- const pause = useCallback6(
2133
- async (reason) => {
2134
- if (!sessionId) {
2135
- return null;
2136
- }
2137
- return await runControl(
2138
- () => client.pauseSession(workspaceId, sessionId, reason !== void 0 ? { reason } : {})
2139
- );
2140
- },
2141
- [client, workspaceId, sessionId, runControl]
2142
- );
2143
- const resume = useCallback6(
2144
- async (reason) => {
2145
- if (!sessionId) return null;
2146
- return await runControl(
2147
- () => client.resumeSession(workspaceId, sessionId, reason !== void 0 ? { reason } : {})
2148
- );
2149
- },
2150
- [client, workspaceId, sessionId, runControl]
2151
- );
2152
- const decide = useCallback6(
2153
- async (approvalId, decision, message) => {
2154
- if (!sessionId) {
2155
- return null;
2156
- }
2157
- const decisionKey = JSON.stringify([approvalId, decision, message ?? null]);
2158
- if (pendingApproval.current?.decisionKey !== decisionKey) {
2159
- pendingApproval.current = {
2160
- targetKey: approvalTargetKey,
2161
- decisionKey,
2162
- clientEventId: crypto.randomUUID()
2163
- };
2164
- }
2165
- const clientEventId = pendingApproval.current.clientEventId;
2166
- const accepted = await runApproval(
2167
- () => client.sendApprovalDecision(workspaceId, sessionId, {
2168
- approvalId,
2169
- decision,
2170
- ...message !== void 0 ? { message } : {},
2171
- clientEventId
2172
- })
2173
- );
2174
- if (accepted !== null && pendingApproval.current?.targetKey === approvalTargetKey && pendingApproval.current.clientEventId === clientEventId) {
2175
- pendingApproval.current = null;
2176
- }
2177
- return accepted;
2178
- },
2179
- [approvalTargetKey, client, workspaceId, sessionId, runApproval]
2180
- );
2181
- const approve = useCallback6(
2182
- async (approvalId, message) => await decide(approvalId, "approve", message),
2183
- [decide]
2184
- );
2185
- const reject = useCallback6(
2186
- async (approvalId, message) => await decide(approvalId, "reject", message),
2187
- [decide]
2188
- );
2189
- const error = approvalError ?? controlError;
2190
- const clearError = useCallback6(() => {
2191
- clearControlError();
2192
- clearApprovalError();
2193
- }, [clearControlError, clearApprovalError]);
2194
- return {
2195
- pause,
2196
- resume,
2197
- controlling,
2198
- approve,
2199
- reject,
2200
- responding,
2201
- error,
2202
- clearError
2203
- };
2204
- }
2205
-
2206
- // src/hooks/use-session-mcp-approval-policy.ts
2207
- import { useCallback as useCallback7, useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useMemo as useMemo4, useRef as useRef7, useState as useState6 } from "react";
2208
- function isSessionMcpApprovalPolicyEvent(event, serverId) {
2209
- if (event.type !== "session.mcp.approval_policy.updated") {
2210
- return false;
2211
- }
2212
- if (serverId === void 0) {
2213
- return true;
2214
- }
2215
- const payload = event.payload;
2216
- return payload?.serverId === serverId;
2217
- }
2218
- function useSessionMcpApprovalPolicy(sessionId, serverId, options = {}) {
2219
- const { client, workspaceId } = useEmbeddedSessionMcpApprovalPolicy(options);
2220
- const enabled = (options.enabled ?? true) && Boolean(sessionId && serverId);
2221
- const [override, setOverride] = useState6(null);
2222
- const targetKey = `${workspaceId}\0${sessionId ?? ""}\0${serverId ?? ""}`;
2223
- const authoritativeGeneration = useRef7(0);
2224
- const mutationIdentity = useMemo4(() => ({ client, targetKey }), [client, targetKey]);
2225
- const { run, mutating, mutationError, clearMutationError } = useMutationRunner(mutationIdentity);
2226
- useLayoutEffect3(() => {
2227
- authoritativeGeneration.current += 1;
2228
- setOverride(null);
2229
- }, [client, targetKey]);
2230
- const load = useCallback7(async () => {
2231
- const generation = authoritativeGeneration.current;
2232
- if (!sessionId || !serverId) {
2233
- return { targetKey, generation, server: null };
2234
- }
2235
- const session = await client.getSession(workspaceId, sessionId);
2236
- return {
2237
- targetKey,
2238
- generation,
2239
- server: session.mcpServers.find((server2) => server2.id === serverId) ?? null
2240
- };
2241
- }, [client, serverId, sessionId, targetKey, workspaceId]);
2242
- const {
2243
- data,
2244
- loading,
2245
- error: loadError,
2246
- refresh
2247
- } = usePolledValue(load, {
2248
- enabled,
2249
- pollIntervalMs: options.pollIntervalMs
2250
- });
2251
- useEffect6(() => {
2252
- if (data?.targetKey === targetKey && data.generation === authoritativeGeneration.current) {
2253
- setOverride(null);
2254
- }
2255
- }, [data, targetKey]);
2256
- const scheduleRefresh = useDebouncedCallback(() => void refresh());
2257
- const matchesPolicyEvent = useCallback7(
2258
- (event) => isSessionMcpApprovalPolicyEvent(event, serverId ?? void 0),
2259
- [serverId]
2260
- );
2261
- useSessionEventTrigger(
2262
- client,
2263
- workspaceId,
2264
- sessionId,
2265
- matchesPolicyEvent,
2266
- scheduleRefresh,
2267
- {
2268
- enabled,
2269
- ...options.events !== void 0 ? { events: options.events } : {}
2270
- },
2271
- async () => await refresh()
2272
- );
2273
- const update = useCallback7(
2274
- async (policy) => {
2275
- if (!sessionId || !serverId) {
2276
- return null;
2277
- }
2278
- const response = await run(
2279
- () => client.updateSessionMcpApprovalPolicy(workspaceId, sessionId, serverId, {
2280
- requireApproval: policy
2281
- })
2282
- );
2283
- if (response) {
2284
- authoritativeGeneration.current += 1;
2285
- setOverride(response.server);
2286
- void refresh();
2287
- }
2288
- return response;
2289
- },
2290
- [client, refresh, run, serverId, sessionId, workspaceId]
2291
- );
2292
- const server = override ?? data?.server ?? null;
2293
- return {
2294
- server,
2295
- policy: server?.requireApproval ?? null,
2296
- loading,
2297
- error: mutationError ?? loadError,
2298
- refresh: async () => {
2299
- await refresh();
2300
- },
2301
- update,
2302
- updating: mutating,
2303
- clearError: clearMutationError
2304
- };
2305
- }
2306
-
2307
- // src/hooks/use-session-lineage.ts
2308
- import { useCallback as useCallback8, useEffect as useEffect7, useRef as useRef8 } from "react";
2309
- function isLineageRefreshEvent(event) {
2310
- if (event.type === "session.status.changed" || event.type === "session.created") {
2311
- return true;
2312
- }
2313
- if (event.type === "agent.toolCall.output") {
2314
- const payload = event.payload;
2315
- const name = typeof payload?.name === "string" ? payload.name : typeof payload?.toolName === "string" ? payload.toolName : "";
2316
- return name === "session_create" || name.endsWith("__session_create");
2317
- }
2318
- return false;
2319
- }
2320
- function isSessionCreateToolCallCreated(event) {
2321
- if (event.type !== "agent.toolCall.created") {
2322
- return false;
2323
- }
2324
- const payload = event.payload;
2325
- const name = typeof payload?.name === "string" ? payload.name : typeof payload?.toolName === "string" ? payload.toolName : "";
2326
- return name === "session_create" || name.endsWith("__session_create");
2327
- }
2328
- function useSessionLineage(sessionId, options = {}) {
2329
- const { client, workspaceId, workspaceControlEvent, registerSessionReconciler } = useEmbeddedSessionLineage(options);
2330
- const enabled = (options.enabled ?? true) && Boolean(sessionId);
2331
- const nextReadGeneration = useRef8(0);
2332
- const beginRead = options.beginRead;
2333
- const load = useCallback8(
2334
- async (signal) => {
2335
- if (!sessionId) {
2336
- return {
2337
- lineage: { ancestors: [], children: [], truncated: false },
2338
- readGeneration: 0
2339
- };
2340
- }
2341
- let readGeneration = 0;
2342
- const lineage = await client.getSessionLineage(workspaceId, sessionId, {
2343
- signal,
2344
- onRequestStart: (sharedReadGeneration) => {
2345
- readGeneration = sharedReadGeneration ?? beginRead?.() ?? ++nextReadGeneration.current;
2346
- }
2347
- });
2348
- return {
2349
- lineage,
2350
- readGeneration
2351
- };
2352
- },
2353
- [beginRead, client, workspaceId, sessionId]
2354
- );
2355
- const state = usePolledValue(load, { pollIntervalMs: options.pollIntervalMs, enabled });
2356
- const refresh = state.refresh;
2357
- useEffect7(() => {
2358
- if (enabled && workspaceControlEvent) void refresh();
2359
- }, [enabled, refresh, workspaceControlEvent]);
2360
- useEffect7(() => {
2361
- if (!sessionId || !enabled) return;
2362
- return registerSessionReconciler(sessionId, "lineage", refresh);
2363
- }, [enabled, refresh, registerSessionReconciler, sessionId]);
2364
- const refreshSoon = useDebouncedCallback(() => void refresh(), 150);
2365
- const delayedChildRefreshRef = useRef8(null);
2366
- useEffect7(() => {
2367
- return () => {
2368
- if (delayedChildRefreshRef.current !== null) {
2369
- clearTimeout(delayedChildRefreshRef.current);
2370
- delayedChildRefreshRef.current = null;
2371
- }
2372
- };
2373
- }, [sessionId, workspaceId]);
2374
- const refreshAfterChildCreate = useCallback8(() => {
2375
- void refresh();
2376
- if (delayedChildRefreshRef.current !== null) {
2377
- clearTimeout(delayedChildRefreshRef.current);
2378
- }
2379
- delayedChildRefreshRef.current = setTimeout(() => {
2380
- delayedChildRefreshRef.current = null;
2381
- void refresh();
2382
- }, 2500);
2383
- }, [refresh]);
2384
- useSessionEventTrigger(
2385
- client,
2386
- workspaceId,
2387
- sessionId,
2388
- isLineageRefreshEvent,
2389
- refreshSoon,
2390
- // SHARED-FEED ONLY: without a caller-provided events log the trigger would
2391
- // open its OWN streamEvents tail — a second live SSE connection next to the
2392
- // session route's useSessionEvents. A caller with no feed opts into polling
2393
- // (pollIntervalMs), never a duplicate stream.
2394
- { events: options.events, enabled: enabled && options.events !== void 0 }
2395
- );
2396
- useSessionEventTrigger(
2397
- client,
2398
- workspaceId,
2399
- sessionId,
2400
- isSessionCreateToolCallCreated,
2401
- refreshAfterChildCreate,
2402
- { events: options.events, enabled: enabled && options.events !== void 0 }
2403
- );
2404
- return {
2405
- lineage: state.data?.lineage ?? null,
2406
- loading: state.loading,
2407
- error: state.error,
2408
- readGeneration: state.data?.readGeneration ?? 0,
2409
- refresh
2410
- };
2411
- }
2412
-
2413
- // src/approvals.ts
2414
- function approvalsFromRequiresAction(payload) {
2415
- const approvals = payload && typeof payload === "object" ? payload.approvals : void 0;
2416
- if (!Array.isArray(approvals)) {
2417
- return [];
2418
- }
2419
- return approvals.map((approval, index) => {
2420
- const raw = approval && typeof approval === "object" ? approval : {};
2421
- const rawItem = raw.rawItem && typeof raw.rawItem === "object" ? raw.rawItem : {};
2422
- return {
2423
- // Mirror the runtime's canonical interruption identity before retaining
2424
- // the legacy top-level callId fallback for already-normalized payloads.
2425
- id: String(rawItem.callId ?? rawItem.id ?? raw.id ?? raw.callId ?? raw.name ?? index),
2426
- name: String(raw.name ?? raw.toolName ?? rawItem.name ?? "approval"),
2427
- arguments: raw.arguments ?? rawItem.arguments,
2428
- raw: approval
2429
- };
2430
- });
2431
- }
2432
- function projectPendingApprovals(events) {
2433
- let pending = [];
2434
- let owningTurnId = null;
2435
- for (const event of events) {
2436
- switch (event.type) {
2437
- case "session.requiresAction": {
2438
- pending = approvalsFromRequiresAction(event.payload);
2439
- owningTurnId = event.turnId ?? null;
2440
- break;
2441
- }
2442
- case "user.approvalDecision": {
2443
- const payload = event.payload && typeof event.payload === "object" ? event.payload : {};
2444
- if (typeof payload.approvalId === "string") {
2445
- pending = pending.filter((approval) => approval.id !== payload.approvalId);
2446
- }
2447
- break;
2448
- }
2449
- case "turn.completed":
2450
- case "turn.failed":
2451
- case "turn.cancelled": {
2452
- if (owningTurnId === null || event.turnId == null || event.turnId === owningTurnId) {
2453
- pending = [];
2454
- owningTurnId = null;
2455
- }
2456
- break;
2457
- }
2458
- default:
2459
- break;
2460
- }
2461
- }
2462
- return pending;
2463
- }
2464
-
2465
- // src/hooks/use-human-input.ts
2466
- import { useCallback as useCallback9, useEffect as useEffect8, useRef as useRef9, useState as useState7 } from "react";
2467
- function isHumanInputEvent(event) {
2468
- return event.type === "session.humanInput.requested" || event.type === "user.humanInputResponse" || event.type === "turn.completed" || event.type === "turn.failed" || event.type === "turn.cancelled";
2469
- }
2470
- function useHumanInputRequests(sessionId, options = {}) {
2471
- const { client, workspaceId, registerSessionReconciler } = useEmbeddedHumanInputSession(options);
2472
- const enabled = (options.enabled ?? true) && Boolean(sessionId);
2473
- const load = useCallback9(
2474
- async () => sessionId ? await client.listHumanInputRequests(workspaceId, sessionId, { status: "pending" }) : [],
2475
- [client, sessionId, workspaceId]
2476
- );
2477
- const state = usePolledValue(load, {
2478
- enabled,
2479
- pollIntervalMs: options.pollIntervalMs
2480
- });
2481
- const loadedRequests = state.data;
2482
- const refreshRequests = state.refresh;
2483
- const mutation = useMutationRunner(`${workspaceId}\0${sessionId ?? ""}`);
2484
- const [respondingRequestId, setRespondingRequestId] = useState7(null);
2485
- const [deadlineVersion, setDeadlineVersion] = useState7(0);
2486
- const respondingRequestRef = useRef9(null);
2487
- useEffect8(() => {
2488
- respondingRequestRef.current = null;
2489
- setRespondingRequestId(null);
2490
- }, [sessionId, workspaceId]);
2491
- useEffect8(() => {
2492
- if (!sessionId || !enabled) return;
2493
- return registerSessionReconciler(sessionId, "human-input", state.refresh);
2494
- }, [enabled, registerSessionReconciler, sessionId, state.refresh]);
2495
- const scheduleRefresh = useDebouncedCallback(() => void state.refresh());
2496
- useSessionEventTrigger(client, workspaceId, sessionId, isHumanInputEvent, scheduleRefresh, {
2497
- enabled,
2498
- ...options.events !== void 0 ? { events: options.events } : {}
2499
- });
2500
- useEffect8(() => {
2501
- const now2 = Date.now();
2502
- const nextDeadline = (loadedRequests ?? []).reduce((next, request) => {
2503
- if (request.status !== "pending" || !request.expiresAt) return next;
2504
- const deadline = Date.parse(request.expiresAt);
2505
- if (!Number.isFinite(deadline) || deadline <= now2) return next;
2506
- return next === null ? deadline : Math.min(next, deadline);
2507
- }, null);
2508
- if (nextDeadline === null) return;
2509
- const timer = globalThis.setTimeout(
2510
- () => {
2511
- setDeadlineVersion((current) => current + 1);
2512
- void refreshRequests();
2513
- },
2514
- Math.min(2147483647, Math.max(0, nextDeadline - now2 + 1))
2515
- );
2516
- return () => globalThis.clearTimeout(timer);
2517
- }, [loadedRequests, refreshRequests]);
2518
- const respond = useCallback9(
2519
- async (requestId, response) => {
2520
- if (!sessionId || respondingRequestRef.current !== null) return null;
2521
- respondingRequestRef.current = requestId;
2522
- setRespondingRequestId(requestId);
2523
- try {
2524
- const event = await mutation.run(
2525
- () => client.submitHumanInputResponse(workspaceId, sessionId, requestId, response, {
2526
- clientEventId: globalThis.crypto.randomUUID()
2527
- })
2528
- );
2529
- if (event) await state.refresh();
2530
- return event;
2531
- } finally {
2532
- if (respondingRequestRef.current === requestId) {
2533
- respondingRequestRef.current = null;
2534
- setRespondingRequestId(null);
2535
- }
2536
- }
2537
- },
2538
- [client, mutation, sessionId, state, workspaceId]
2539
- );
2540
- const now = Date.now();
2541
- void deadlineVersion;
2542
- return {
2543
- requests: [...state.data ?? []].filter((request) => isActionableHumanInputRequest(request, now)).sort(
2544
- (left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id)
2545
- ),
2546
- loading: state.loading,
2547
- error: state.error,
2548
- refresh: state.refresh,
2549
- respond,
2550
- respondingRequestId,
2551
- mutationError: mutation.mutationError,
2552
- clearMutationError: mutation.clearMutationError
2553
- };
2554
- }
2555
-
2556
- export {
2557
- createEmbeddedSessionClient,
2558
- isTitleEvent,
2559
- useSession,
2560
- SESSION_EVENT_BROWSER_MAX_BYTES,
2561
- SESSION_EVENT_BROWSER_MAX_COUNT,
2562
- SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES,
2563
- SESSION_EVENT_BROWSER_PENDING_MAX_BYTES,
2564
- SESSION_EVENT_BROWSER_PENDING_MAX_COUNT,
2565
- useSessionEvents,
2566
- boundBrowserSessionEventWindow,
2567
- useFileAttachments,
2568
- isTurnQueueEvent,
2569
- useTurnQueue,
2570
- isGoalEvent,
2571
- useGoal,
2572
- useSessionControl,
2573
- isSessionMcpApprovalPolicyEvent,
2574
- useSessionMcpApprovalPolicy,
2575
- isLineageRefreshEvent,
2576
- useSessionLineage,
2577
- approvalsFromRequiresAction,
2578
- projectPendingApprovals,
2579
- isHumanInputEvent,
2580
- useHumanInputRequests
2581
- };
2582
- //# sourceMappingURL=chunk-IB27C53Y.js.map