@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
@@ -0,0 +1,902 @@
1
+ import {
2
+ useDebouncedCallback,
3
+ useMutationRunner,
4
+ usePageLiveActivity,
5
+ usePolledValue,
6
+ useSessionEventTrigger
7
+ } from "./chunk-KZ73RL76.js";
8
+ import {
9
+ useEmbeddedFileAttachments,
10
+ useEmbeddedGoal,
11
+ useEmbeddedSession,
12
+ useEmbeddedSessionLineage,
13
+ useEmbeddedSessionMcpApprovalPolicy
14
+ } from "./chunk-NEXLLTP4.js";
15
+
16
+ // src/embedded-session-client.ts
17
+ var requiredMethods = [
18
+ "getSession",
19
+ "listEvents",
20
+ "streamEvents",
21
+ "getComposerDraft",
22
+ "saveComposerDraft",
23
+ "submitComposerDraft",
24
+ "sendMessage",
25
+ "steerMessage",
26
+ "getQueue",
27
+ "moveQueueItem",
28
+ "editQueueItem",
29
+ "steerQueueItem",
30
+ "deleteQueueItem",
31
+ "pauseSession",
32
+ "resumeSession",
33
+ "sendApprovalDecision"
34
+ ];
35
+ function createEmbeddedSessionClient(base, options = {}) {
36
+ const overrides = options.overrides ?? {};
37
+ const client = {};
38
+ for (const key of requiredMethods) {
39
+ const override = overrides[key];
40
+ const method = override ?? base[key];
41
+ if (typeof method !== "function") {
42
+ throw new TypeError(`Embedded session client method is required: ${key}`);
43
+ }
44
+ client[key] = method.bind(override === void 0 ? base : overrides);
45
+ }
46
+ const inferenceOverride = overrides.setWorkspaceInferenceState;
47
+ const inferenceBase = base.setWorkspaceInferenceState;
48
+ const inferenceMethod = inferenceOverride ?? inferenceBase;
49
+ if (typeof inferenceMethod === "function") {
50
+ client.setWorkspaceInferenceState = inferenceMethod.bind(
51
+ inferenceOverride === void 0 ? base : overrides
52
+ );
53
+ }
54
+ const mapDraft = options.mapComposerDraft;
55
+ if (!mapDraft) return client;
56
+ const getComposerDraft = client.getComposerDraft;
57
+ client.getComposerDraft = async (workspaceId, sessionId) => mapDraft(await getComposerDraft(workspaceId, sessionId), {
58
+ operation: "read",
59
+ workspaceId,
60
+ sessionId
61
+ });
62
+ const saveComposerDraft = client.saveComposerDraft;
63
+ client.saveComposerDraft = async (workspaceId, sessionId, request) => mapDraft(await saveComposerDraft(workspaceId, sessionId, request), {
64
+ operation: "save",
65
+ workspaceId,
66
+ sessionId
67
+ });
68
+ const submitComposerDraft = client.submitComposerDraft;
69
+ client.submitComposerDraft = async (workspaceId, sessionId, request) => {
70
+ const response = await submitComposerDraft(workspaceId, sessionId, request);
71
+ return {
72
+ ...response,
73
+ draft: mapDraft(response.draft, {
74
+ operation: "submit",
75
+ workspaceId,
76
+ sessionId
77
+ })
78
+ };
79
+ };
80
+ return client;
81
+ }
82
+
83
+ // src/hooks/use-file-attachments.ts
84
+ import { useCallback, useEffect, useRef, useState } from "react";
85
+ var isImage = (file) => file.type.startsWith("image/");
86
+ var fallbackAttachmentId = 0;
87
+ function createAttachmentId() {
88
+ const cryptoSource = globalThis.crypto;
89
+ if (typeof cryptoSource?.randomUUID === "function") return cryptoSource.randomUUID();
90
+ fallbackAttachmentId += 1;
91
+ return `attachment:${Date.now().toString(36)}:${fallbackAttachmentId.toString(36)}`;
92
+ }
93
+ function secureContextRequiredErrorCode(error) {
94
+ return typeof error === "object" && error !== null && "code" in error && error.code === "secure_context_required" ? error.code : void 0;
95
+ }
96
+ function useFileAttachments(options = {}) {
97
+ const { client, workspaceId } = useEmbeddedFileAttachments(options);
98
+ const pasteFilter = options.pasteFilter ?? isImage;
99
+ const [attachments, setAttachments] = useState([]);
100
+ const attachmentsRef = useRef(attachments);
101
+ attachmentsRef.current = attachments;
102
+ const sources = useRef(/* @__PURE__ */ new Map());
103
+ const previewUrls = useRef(/* @__PURE__ */ new Map());
104
+ const previewRetainers = useRef(/* @__PURE__ */ new Map());
105
+ const pendingPreviewRevocations = useRef(/* @__PURE__ */ new Set());
106
+ const revokePreview = useCallback((id) => {
107
+ const previewUrl = previewUrls.current.get(id);
108
+ if (!previewUrl) return;
109
+ if ((previewRetainers.current.get(id) ?? 0) > 0) {
110
+ pendingPreviewRevocations.current.add(id);
111
+ return;
112
+ }
113
+ previewUrls.current.delete(id);
114
+ pendingPreviewRevocations.current.delete(id);
115
+ URL.revokeObjectURL(previewUrl);
116
+ }, []);
117
+ const revokeAllPreviews = useCallback(() => {
118
+ for (const id of previewUrls.current.keys()) revokePreview(id);
119
+ }, [revokePreview]);
120
+ const retainPreview = useCallback(
121
+ (id) => {
122
+ if (!previewUrls.current.has(id)) return void 0;
123
+ previewRetainers.current.set(id, (previewRetainers.current.get(id) ?? 0) + 1);
124
+ let released = false;
125
+ return () => {
126
+ if (released) return;
127
+ released = true;
128
+ const remaining = (previewRetainers.current.get(id) ?? 1) - 1;
129
+ if (remaining > 0) {
130
+ previewRetainers.current.set(id, remaining);
131
+ return;
132
+ }
133
+ previewRetainers.current.delete(id);
134
+ if (pendingPreviewRevocations.current.has(id)) revokePreview(id);
135
+ };
136
+ },
137
+ [revokePreview]
138
+ );
139
+ const committedAttachmentIds = useRef(/* @__PURE__ */ new Set());
140
+ useEffect(() => {
141
+ const currentIds = new Set(attachments.map((attachment) => attachment.id));
142
+ for (const id of committedAttachmentIds.current) {
143
+ if (!currentIds.has(id)) revokePreview(id);
144
+ }
145
+ committedAttachmentIds.current = currentIds;
146
+ }, [attachments, revokePreview]);
147
+ const scopeGeneration = useRef(0);
148
+ const previousScope = useRef({ client, workspaceId });
149
+ useEffect(() => {
150
+ const previous = previousScope.current;
151
+ if (previous.client === client && previous.workspaceId === workspaceId) return;
152
+ previousScope.current = { client, workspaceId };
153
+ scopeGeneration.current += 1;
154
+ sources.current.clear();
155
+ revokeAllPreviews();
156
+ setAttachments([]);
157
+ }, [client, revokeAllPreviews, workspaceId]);
158
+ useEffect(
159
+ () => () => {
160
+ scopeGeneration.current += 1;
161
+ sources.current.clear();
162
+ revokeAllPreviews();
163
+ },
164
+ [revokeAllPreviews]
165
+ );
166
+ const startUpload = useCallback(
167
+ (id, file) => {
168
+ const generation = scopeGeneration.current;
169
+ void client.uploadFile(workspaceId, {
170
+ filename: file.name || "file",
171
+ contentType: file.type || "application/octet-stream",
172
+ data: file
173
+ }).then((asset) => {
174
+ if (scopeGeneration.current !== generation) return;
175
+ sources.current.delete(id);
176
+ setAttachments(
177
+ (current) => current.map(
178
+ (attachment) => attachment.id === id ? {
179
+ ...attachment,
180
+ status: "ready",
181
+ file: asset,
182
+ name: asset.filename,
183
+ contentType: asset.contentType,
184
+ sizeBytes: asset.sizeBytes,
185
+ errorCode: void 0,
186
+ error: void 0
187
+ } : attachment
188
+ )
189
+ );
190
+ }).catch((error) => {
191
+ if (scopeGeneration.current !== generation) return;
192
+ setAttachments(
193
+ (current) => current.map(
194
+ (attachment) => attachment.id === id ? {
195
+ ...attachment,
196
+ status: "failed",
197
+ errorCode: secureContextRequiredErrorCode(error),
198
+ error: error instanceof Error ? error.message : String(error)
199
+ } : attachment
200
+ )
201
+ );
202
+ });
203
+ },
204
+ [client, workspaceId]
205
+ );
206
+ const addFiles = useCallback(
207
+ (files) => {
208
+ for (const file of files) {
209
+ const id = createAttachmentId();
210
+ sources.current.set(id, file);
211
+ const previewUrl = isImage(file) ? URL.createObjectURL(file) : void 0;
212
+ if (previewUrl) previewUrls.current.set(id, previewUrl);
213
+ setAttachments((current) => [
214
+ ...current,
215
+ {
216
+ id,
217
+ name: file.name || "image",
218
+ contentType: file.type || "application/octet-stream",
219
+ sizeBytes: file.size,
220
+ status: "uploading",
221
+ ...previewUrl ? { previewUrl } : {}
222
+ }
223
+ ]);
224
+ startUpload(id, file);
225
+ }
226
+ },
227
+ [startUpload]
228
+ );
229
+ const retry = useCallback(
230
+ (id) => {
231
+ const file = sources.current.get(id);
232
+ if (!file) {
233
+ return;
234
+ }
235
+ setAttachments(
236
+ (current) => current.map(
237
+ (attachment) => attachment.id === id ? { ...attachment, status: "uploading", errorCode: void 0, error: void 0 } : attachment
238
+ )
239
+ );
240
+ startUpload(id, file);
241
+ },
242
+ [startUpload]
243
+ );
244
+ const addFromPaste = useCallback(
245
+ (event) => {
246
+ const clipboardFiles = event.clipboardData?.files;
247
+ if (!clipboardFiles) {
248
+ return;
249
+ }
250
+ const files = [...clipboardFiles].filter(pasteFilter);
251
+ if (files.length > 0) {
252
+ addFiles(files);
253
+ }
254
+ },
255
+ [addFiles, pasteFilter]
256
+ );
257
+ const restoreReadyFiles = useCallback(
258
+ (files) => {
259
+ const incoming = /* @__PURE__ */ new Map();
260
+ for (const file of files) {
261
+ if (file.status === "ready" && file.workspaceId === workspaceId) {
262
+ incoming.set(file.id, file);
263
+ }
264
+ }
265
+ setAttachments((current) => {
266
+ const unresolved = current.filter((attachment) => attachment.status !== "ready");
267
+ const existingReady = new Map(
268
+ current.flatMap(
269
+ (attachment) => attachment.status === "ready" && attachment.file ? [[attachment.file.id, attachment]] : []
270
+ )
271
+ );
272
+ const restored = [...incoming.values()].map((file) => {
273
+ const existing = existingReady.get(file.id);
274
+ return existing ? {
275
+ ...existing,
276
+ name: file.filename,
277
+ contentType: file.contentType,
278
+ sizeBytes: file.sizeBytes,
279
+ status: "ready",
280
+ file,
281
+ errorCode: void 0,
282
+ error: void 0
283
+ } : {
284
+ id: `restored:${file.id}`,
285
+ name: file.filename,
286
+ contentType: file.contentType,
287
+ sizeBytes: file.sizeBytes,
288
+ status: "ready",
289
+ file
290
+ // No source File and no object URL: server metadata is the
291
+ // only authority restored across page/device boundaries.
292
+ };
293
+ });
294
+ return [...unresolved, ...restored];
295
+ });
296
+ },
297
+ [workspaceId]
298
+ );
299
+ const loadPreview = useCallback(
300
+ async (id, signal) => {
301
+ const generation = scopeGeneration.current;
302
+ const attachment = attachmentsRef.current.find((candidate) => candidate.id === id);
303
+ const createDownloadUrl = client.createFileDownloadUrl;
304
+ if (!attachment || attachment.status !== "ready" || !attachment.file || !attachment.contentType.startsWith("image/") || typeof createDownloadUrl !== "function" || signal?.aborted) {
305
+ return void 0;
306
+ }
307
+ const fileId = attachment.file.id;
308
+ const signed = await createDownloadUrl.call(client, workspaceId, fileId, { signal });
309
+ if (scopeGeneration.current !== generation || signal?.aborted) return void 0;
310
+ const current = attachmentsRef.current.find((candidate) => candidate.id === id);
311
+ if (current?.status !== "ready" || current.file?.id !== fileId) return void 0;
312
+ return signed.url;
313
+ },
314
+ [client, workspaceId]
315
+ );
316
+ const remove = useCallback(
317
+ (id) => {
318
+ sources.current.delete(id);
319
+ revokePreview(id);
320
+ setAttachments((current) => current.filter((attachment) => attachment.id !== id));
321
+ },
322
+ [revokePreview]
323
+ );
324
+ const removeReadyFiles = useCallback((fileIds) => {
325
+ const accepted = new Set(fileIds);
326
+ if (accepted.size === 0) return;
327
+ setAttachments(
328
+ (current) => current.filter((attachment) => {
329
+ return !(attachment.status === "ready" && attachment.file !== void 0 && accepted.has(attachment.file.id));
330
+ })
331
+ );
332
+ }, []);
333
+ const clear = useCallback(() => {
334
+ sources.current.clear();
335
+ revokeAllPreviews();
336
+ setAttachments([]);
337
+ }, [revokeAllPreviews]);
338
+ return {
339
+ attachments,
340
+ readyResources: attachments.flatMap(
341
+ (attachment) => attachment.status === "ready" && attachment.file ? [{ kind: "file", fileId: attachment.file.id }] : []
342
+ ),
343
+ uploading: attachments.some((attachment) => attachment.status === "uploading"),
344
+ hasUnresolved: attachments.some((attachment) => attachment.status !== "ready"),
345
+ addFiles,
346
+ addFromPaste,
347
+ restoreReadyFiles,
348
+ ...typeof client.createFileDownloadUrl === "function" ? { loadPreview } : {},
349
+ retry,
350
+ retainPreview,
351
+ remove,
352
+ removeReadyFiles,
353
+ clear
354
+ };
355
+ }
356
+
357
+ // src/hooks/use-goal.ts
358
+ import { OpenGeniApiError } from "@opengeni/sdk";
359
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
360
+ function isGoalEvent(event) {
361
+ return event.type.startsWith("goal.");
362
+ }
363
+ function isGoalRefreshEvent(event) {
364
+ const type = event.type;
365
+ return isGoalEvent(event) || type.startsWith("turn.") || type.startsWith("session.") || type.startsWith("system.update.") || type.startsWith("workspace.inference.") || type.startsWith("user.");
366
+ }
367
+ function useGoal(sessionId, options = {}) {
368
+ const { client, workspaceId } = useEmbeddedGoal(options);
369
+ const enabled = (options.enabled ?? true) && Boolean(sessionId);
370
+ const sharedEvents = options.events;
371
+ const sharedFeed = sharedEvents !== void 0;
372
+ const pageLive = usePageLiveActivity();
373
+ const [goal, setGoal] = useState2(null);
374
+ const [loading, setLoading] = useState2(enabled);
375
+ const [error, setError] = useState2(null);
376
+ const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
377
+ const generation = useRef2(0);
378
+ const targetKeyRef = useRef2(null);
379
+ const loadAbort = useRef2(null);
380
+ const retireLoads = useCallback2(() => {
381
+ generation.current += 1;
382
+ loadAbort.current?.abort();
383
+ loadAbort.current = null;
384
+ }, []);
385
+ const load = useCallback2(async () => {
386
+ if (!sessionId) {
387
+ return;
388
+ }
389
+ const ticket = ++generation.current;
390
+ loadAbort.current?.abort();
391
+ const controller = new AbortController();
392
+ loadAbort.current = controller;
393
+ try {
394
+ const fetched = await client.getGoal(workspaceId, sessionId, {
395
+ signal: controller.signal
396
+ });
397
+ if (ticket === generation.current) {
398
+ setGoal(fetched);
399
+ setError(null);
400
+ setLoading(false);
401
+ }
402
+ } catch (cause) {
403
+ if (ticket !== generation.current) {
404
+ return;
405
+ }
406
+ if (cause instanceof OpenGeniApiError && cause.status === 404) {
407
+ setGoal(null);
408
+ setError(null);
409
+ } else {
410
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
411
+ }
412
+ setLoading(false);
413
+ } finally {
414
+ if (loadAbort.current === controller) loadAbort.current = null;
415
+ }
416
+ }, [client, workspaceId, sessionId]);
417
+ useEffect2(() => {
418
+ const targetKey = `${workspaceId} ${sessionId ?? ""}`;
419
+ if (targetKeyRef.current !== targetKey) {
420
+ targetKeyRef.current = targetKey;
421
+ setGoal(null);
422
+ setError(null);
423
+ }
424
+ if (!enabled || !pageLive) {
425
+ retireLoads();
426
+ setLoading(false);
427
+ return;
428
+ }
429
+ if (sharedFeed) {
430
+ setLoading(false);
431
+ return () => {
432
+ retireLoads();
433
+ };
434
+ }
435
+ setLoading(true);
436
+ const pollIntervalMs = options.pollIntervalMs;
437
+ if (pollIntervalMs === void 0 || pollIntervalMs <= 0) {
438
+ void load();
439
+ return () => {
440
+ retireLoads();
441
+ };
442
+ }
443
+ let cancelled = false;
444
+ let timer = null;
445
+ const schedule = () => {
446
+ if (cancelled) return;
447
+ timer = setTimeout(() => {
448
+ timer = null;
449
+ void load().finally(schedule);
450
+ }, pollIntervalMs);
451
+ };
452
+ void load().finally(schedule);
453
+ return () => {
454
+ cancelled = true;
455
+ if (timer !== null) clearTimeout(timer);
456
+ retireLoads();
457
+ };
458
+ }, [
459
+ load,
460
+ enabled,
461
+ pageLive,
462
+ workspaceId,
463
+ sessionId,
464
+ options.pollIntervalMs,
465
+ retireLoads,
466
+ sharedFeed
467
+ ]);
468
+ const scheduleRefresh = useDebouncedCallback(() => void load());
469
+ useSessionEventTrigger(client, workspaceId, sessionId, isGoalRefreshEvent, scheduleRefresh, {
470
+ enabled,
471
+ ...sharedEvents !== void 0 ? { events: sharedEvents } : {}
472
+ });
473
+ const pause = useCallback2(
474
+ async (rationale) => {
475
+ if (!sessionId) {
476
+ return null;
477
+ }
478
+ const result = await run(
479
+ () => client.updateGoal(workspaceId, sessionId, {
480
+ status: "paused",
481
+ ...rationale !== void 0 ? { rationale } : {}
482
+ })
483
+ );
484
+ if (result) {
485
+ setGoal(result);
486
+ }
487
+ return result;
488
+ },
489
+ [client, workspaceId, sessionId, run]
490
+ );
491
+ const resume = useCallback2(async () => {
492
+ if (!sessionId) {
493
+ return null;
494
+ }
495
+ const result = await run(() => client.updateGoal(workspaceId, sessionId, { status: "active" }));
496
+ if (result) {
497
+ setGoal(result);
498
+ }
499
+ return result;
500
+ }, [client, workspaceId, sessionId, run]);
501
+ const clearGoal = useCallback2(async () => {
502
+ if (!sessionId) {
503
+ return;
504
+ }
505
+ const ok = await run(async () => {
506
+ await client.deleteGoal(workspaceId, sessionId);
507
+ return true;
508
+ });
509
+ if (ok) {
510
+ retireLoads();
511
+ setGoal(null);
512
+ setError(null);
513
+ }
514
+ }, [client, workspaceId, sessionId, run, retireLoads]);
515
+ return {
516
+ goal,
517
+ isActive: goal?.status === "active",
518
+ isPaused: goal?.status === "paused",
519
+ isCompleted: goal?.status === "completed",
520
+ loading,
521
+ error,
522
+ refresh: load,
523
+ pause,
524
+ resume,
525
+ clearGoal,
526
+ deleteGoal: clearGoal,
527
+ updating: mutating,
528
+ mutationError,
529
+ clearMutationError
530
+ };
531
+ }
532
+
533
+ // src/hooks/use-session-control.ts
534
+ import { useCallback as useCallback3, useLayoutEffect, useRef as useRef3 } from "react";
535
+ function useSessionControl(sessionId, options = {}) {
536
+ const { client, workspaceId } = useEmbeddedSession(options);
537
+ const approvalTargetKey = `${workspaceId}\0${sessionId ?? ""}`;
538
+ const pendingApproval = useRef3(null);
539
+ useLayoutEffect(() => {
540
+ if (pendingApproval.current?.targetKey !== approvalTargetKey) {
541
+ pendingApproval.current = null;
542
+ }
543
+ }, [approvalTargetKey]);
544
+ const {
545
+ run: runControl,
546
+ mutating: controlling,
547
+ mutationError: controlError,
548
+ clearMutationError: clearControlError
549
+ } = useMutationRunner(approvalTargetKey);
550
+ const {
551
+ run: runApproval,
552
+ mutating: responding,
553
+ mutationError: approvalError,
554
+ clearMutationError: clearApprovalError
555
+ } = useMutationRunner(approvalTargetKey);
556
+ const pause = useCallback3(
557
+ async (reason) => {
558
+ if (!sessionId) {
559
+ return null;
560
+ }
561
+ return await runControl(
562
+ () => client.pauseSession(workspaceId, sessionId, reason !== void 0 ? { reason } : {})
563
+ );
564
+ },
565
+ [client, workspaceId, sessionId, runControl]
566
+ );
567
+ const resume = useCallback3(
568
+ async (reason) => {
569
+ if (!sessionId) return null;
570
+ return await runControl(
571
+ () => client.resumeSession(workspaceId, sessionId, reason !== void 0 ? { reason } : {})
572
+ );
573
+ },
574
+ [client, workspaceId, sessionId, runControl]
575
+ );
576
+ const decide = useCallback3(
577
+ async (approvalId, decision, message) => {
578
+ if (!sessionId) {
579
+ return null;
580
+ }
581
+ const decisionKey = JSON.stringify([approvalId, decision, message ?? null]);
582
+ if (pendingApproval.current?.decisionKey !== decisionKey) {
583
+ pendingApproval.current = {
584
+ targetKey: approvalTargetKey,
585
+ decisionKey,
586
+ clientEventId: crypto.randomUUID()
587
+ };
588
+ }
589
+ const clientEventId = pendingApproval.current.clientEventId;
590
+ const accepted = await runApproval(
591
+ () => client.sendApprovalDecision(workspaceId, sessionId, {
592
+ approvalId,
593
+ decision,
594
+ ...message !== void 0 ? { message } : {},
595
+ clientEventId
596
+ })
597
+ );
598
+ if (accepted !== null && pendingApproval.current?.targetKey === approvalTargetKey && pendingApproval.current.clientEventId === clientEventId) {
599
+ pendingApproval.current = null;
600
+ }
601
+ return accepted;
602
+ },
603
+ [approvalTargetKey, client, workspaceId, sessionId, runApproval]
604
+ );
605
+ const approve = useCallback3(
606
+ async (approvalId, message) => await decide(approvalId, "approve", message),
607
+ [decide]
608
+ );
609
+ const reject = useCallback3(
610
+ async (approvalId, message) => await decide(approvalId, "reject", message),
611
+ [decide]
612
+ );
613
+ const error = approvalError ?? controlError;
614
+ const clearError = useCallback3(() => {
615
+ clearControlError();
616
+ clearApprovalError();
617
+ }, [clearControlError, clearApprovalError]);
618
+ return {
619
+ pause,
620
+ resume,
621
+ controlling,
622
+ approve,
623
+ reject,
624
+ responding,
625
+ error,
626
+ clearError
627
+ };
628
+ }
629
+
630
+ // src/hooks/use-session-mcp-approval-policy.ts
631
+ import { useCallback as useCallback4, useEffect as useEffect3, useLayoutEffect as useLayoutEffect2, useMemo, useRef as useRef4, useState as useState3 } from "react";
632
+ function isSessionMcpApprovalPolicyEvent(event, serverId) {
633
+ if (event.type !== "session.mcp.approval_policy.updated") {
634
+ return false;
635
+ }
636
+ if (serverId === void 0) {
637
+ return true;
638
+ }
639
+ const payload = event.payload;
640
+ return payload?.serverId === serverId;
641
+ }
642
+ function useSessionMcpApprovalPolicy(sessionId, serverId, options = {}) {
643
+ const { client, workspaceId } = useEmbeddedSessionMcpApprovalPolicy(options);
644
+ const enabled = (options.enabled ?? true) && Boolean(sessionId && serverId);
645
+ const [override, setOverride] = useState3(null);
646
+ const targetKey = `${workspaceId}\0${sessionId ?? ""}\0${serverId ?? ""}`;
647
+ const authoritativeGeneration = useRef4(0);
648
+ const mutationIdentity = useMemo(() => ({ client, targetKey }), [client, targetKey]);
649
+ const { run, mutating, mutationError, clearMutationError } = useMutationRunner(mutationIdentity);
650
+ useLayoutEffect2(() => {
651
+ authoritativeGeneration.current += 1;
652
+ setOverride(null);
653
+ }, [client, targetKey]);
654
+ const load = useCallback4(async () => {
655
+ const generation = authoritativeGeneration.current;
656
+ if (!sessionId || !serverId) {
657
+ return { targetKey, generation, server: null };
658
+ }
659
+ const session = await client.getSession(workspaceId, sessionId);
660
+ return {
661
+ targetKey,
662
+ generation,
663
+ server: session.mcpServers.find((server2) => server2.id === serverId) ?? null
664
+ };
665
+ }, [client, serverId, sessionId, targetKey, workspaceId]);
666
+ const {
667
+ data,
668
+ loading,
669
+ error: loadError,
670
+ refresh
671
+ } = usePolledValue(load, {
672
+ enabled,
673
+ pollIntervalMs: options.pollIntervalMs
674
+ });
675
+ useEffect3(() => {
676
+ if (data?.targetKey === targetKey && data.generation === authoritativeGeneration.current) {
677
+ setOverride(null);
678
+ }
679
+ }, [data, targetKey]);
680
+ const scheduleRefresh = useDebouncedCallback(() => void refresh());
681
+ const matchesPolicyEvent = useCallback4(
682
+ (event) => isSessionMcpApprovalPolicyEvent(event, serverId ?? void 0),
683
+ [serverId]
684
+ );
685
+ useSessionEventTrigger(
686
+ client,
687
+ workspaceId,
688
+ sessionId,
689
+ matchesPolicyEvent,
690
+ scheduleRefresh,
691
+ {
692
+ enabled,
693
+ ...options.events !== void 0 ? { events: options.events } : {}
694
+ },
695
+ async () => await refresh()
696
+ );
697
+ const update = useCallback4(
698
+ async (policy) => {
699
+ if (!sessionId || !serverId) {
700
+ return null;
701
+ }
702
+ const response = await run(
703
+ () => client.updateSessionMcpApprovalPolicy(workspaceId, sessionId, serverId, {
704
+ requireApproval: policy
705
+ })
706
+ );
707
+ if (response) {
708
+ authoritativeGeneration.current += 1;
709
+ setOverride(response.server);
710
+ void refresh();
711
+ }
712
+ return response;
713
+ },
714
+ [client, refresh, run, serverId, sessionId, workspaceId]
715
+ );
716
+ const server = override ?? data?.server ?? null;
717
+ return {
718
+ server,
719
+ policy: server?.requireApproval ?? null,
720
+ loading,
721
+ error: mutationError ?? loadError,
722
+ refresh: async () => {
723
+ await refresh();
724
+ },
725
+ update,
726
+ updating: mutating,
727
+ clearError: clearMutationError
728
+ };
729
+ }
730
+
731
+ // src/hooks/use-session-lineage.ts
732
+ import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef5 } from "react";
733
+ function isLineageRefreshEvent(event) {
734
+ if (event.type === "session.status.changed" || event.type === "session.created") {
735
+ return true;
736
+ }
737
+ if (event.type === "agent.toolCall.output") {
738
+ const payload = event.payload;
739
+ const name = typeof payload?.name === "string" ? payload.name : typeof payload?.toolName === "string" ? payload.toolName : "";
740
+ return name === "session_create" || name.endsWith("__session_create");
741
+ }
742
+ return false;
743
+ }
744
+ function isSessionCreateToolCallCreated(event) {
745
+ if (event.type !== "agent.toolCall.created") {
746
+ return false;
747
+ }
748
+ const payload = event.payload;
749
+ const name = typeof payload?.name === "string" ? payload.name : typeof payload?.toolName === "string" ? payload.toolName : "";
750
+ return name === "session_create" || name.endsWith("__session_create");
751
+ }
752
+ function useSessionLineage(sessionId, options = {}) {
753
+ const { client, workspaceId, workspaceControlEvent, registerSessionReconciler } = useEmbeddedSessionLineage(options);
754
+ const enabled = (options.enabled ?? true) && Boolean(sessionId);
755
+ const nextReadGeneration = useRef5(0);
756
+ const beginRead = options.beginRead;
757
+ const load = useCallback5(
758
+ async (signal) => {
759
+ if (!sessionId) {
760
+ return {
761
+ lineage: { ancestors: [], children: [], truncated: false },
762
+ readGeneration: 0
763
+ };
764
+ }
765
+ let readGeneration = 0;
766
+ const lineage = await client.getSessionLineage(workspaceId, sessionId, {
767
+ signal,
768
+ onRequestStart: (sharedReadGeneration) => {
769
+ readGeneration = sharedReadGeneration ?? beginRead?.() ?? ++nextReadGeneration.current;
770
+ }
771
+ });
772
+ return {
773
+ lineage,
774
+ readGeneration
775
+ };
776
+ },
777
+ [beginRead, client, workspaceId, sessionId]
778
+ );
779
+ const state = usePolledValue(load, { pollIntervalMs: options.pollIntervalMs, enabled });
780
+ const refresh = state.refresh;
781
+ useEffect4(() => {
782
+ if (enabled && workspaceControlEvent) void refresh();
783
+ }, [enabled, refresh, workspaceControlEvent]);
784
+ useEffect4(() => {
785
+ if (!sessionId || !enabled) return;
786
+ return registerSessionReconciler(sessionId, "lineage", refresh);
787
+ }, [enabled, refresh, registerSessionReconciler, sessionId]);
788
+ const refreshSoon = useDebouncedCallback(() => void refresh(), 150);
789
+ const delayedChildRefreshRef = useRef5(null);
790
+ useEffect4(() => {
791
+ return () => {
792
+ if (delayedChildRefreshRef.current !== null) {
793
+ clearTimeout(delayedChildRefreshRef.current);
794
+ delayedChildRefreshRef.current = null;
795
+ }
796
+ };
797
+ }, [sessionId, workspaceId]);
798
+ const refreshAfterChildCreate = useCallback5(() => {
799
+ void refresh();
800
+ if (delayedChildRefreshRef.current !== null) {
801
+ clearTimeout(delayedChildRefreshRef.current);
802
+ }
803
+ delayedChildRefreshRef.current = setTimeout(() => {
804
+ delayedChildRefreshRef.current = null;
805
+ void refresh();
806
+ }, 2500);
807
+ }, [refresh]);
808
+ useSessionEventTrigger(
809
+ client,
810
+ workspaceId,
811
+ sessionId,
812
+ isLineageRefreshEvent,
813
+ refreshSoon,
814
+ // SHARED-FEED ONLY: without a caller-provided events log the trigger would
815
+ // open its OWN streamEvents tail — a second live SSE connection next to the
816
+ // session route's useSessionEvents. A caller with no feed opts into polling
817
+ // (pollIntervalMs), never a duplicate stream.
818
+ { events: options.events, enabled: enabled && options.events !== void 0 }
819
+ );
820
+ useSessionEventTrigger(
821
+ client,
822
+ workspaceId,
823
+ sessionId,
824
+ isSessionCreateToolCallCreated,
825
+ refreshAfterChildCreate,
826
+ { events: options.events, enabled: enabled && options.events !== void 0 }
827
+ );
828
+ return {
829
+ lineage: state.data?.lineage ?? null,
830
+ loading: state.loading,
831
+ error: state.error,
832
+ readGeneration: state.data?.readGeneration ?? 0,
833
+ refresh
834
+ };
835
+ }
836
+
837
+ // src/approvals.ts
838
+ function approvalsFromRequiresAction(payload) {
839
+ const approvals = payload && typeof payload === "object" ? payload.approvals : void 0;
840
+ if (!Array.isArray(approvals)) {
841
+ return [];
842
+ }
843
+ return approvals.map((approval, index) => {
844
+ const raw = approval && typeof approval === "object" ? approval : {};
845
+ const rawItem = raw.rawItem && typeof raw.rawItem === "object" ? raw.rawItem : {};
846
+ return {
847
+ // Mirror the runtime's canonical interruption identity before retaining
848
+ // the legacy top-level callId fallback for already-normalized payloads.
849
+ id: String(rawItem.callId ?? rawItem.id ?? raw.id ?? raw.callId ?? raw.name ?? index),
850
+ name: String(raw.name ?? raw.toolName ?? rawItem.name ?? "approval"),
851
+ arguments: raw.arguments ?? rawItem.arguments,
852
+ raw: approval
853
+ };
854
+ });
855
+ }
856
+ function projectPendingApprovals(events) {
857
+ let pending = [];
858
+ let owningTurnId = null;
859
+ for (const event of events) {
860
+ switch (event.type) {
861
+ case "session.requiresAction": {
862
+ pending = approvalsFromRequiresAction(event.payload);
863
+ owningTurnId = event.turnId ?? null;
864
+ break;
865
+ }
866
+ case "user.approvalDecision": {
867
+ const payload = event.payload && typeof event.payload === "object" ? event.payload : {};
868
+ if (typeof payload.approvalId === "string") {
869
+ pending = pending.filter((approval) => approval.id !== payload.approvalId);
870
+ }
871
+ break;
872
+ }
873
+ case "turn.completed":
874
+ case "turn.failed":
875
+ case "turn.cancelled": {
876
+ if (owningTurnId === null || event.turnId == null || event.turnId === owningTurnId) {
877
+ pending = [];
878
+ owningTurnId = null;
879
+ }
880
+ break;
881
+ }
882
+ default:
883
+ break;
884
+ }
885
+ }
886
+ return pending;
887
+ }
888
+
889
+ export {
890
+ createEmbeddedSessionClient,
891
+ useFileAttachments,
892
+ isGoalEvent,
893
+ useGoal,
894
+ useSessionControl,
895
+ isSessionMcpApprovalPolicyEvent,
896
+ useSessionMcpApprovalPolicy,
897
+ isLineageRefreshEvent,
898
+ useSessionLineage,
899
+ approvalsFromRequiresAction,
900
+ projectPendingApprovals
901
+ };
902
+ //# sourceMappingURL=chunk-5PQJOCX5.js.map