@opengeni/react 0.23.0 → 0.25.5

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 (62) hide show
  1. package/dist/{chunk-UQS7OQDE.js → chunk-F4CFWKVI.js} +334 -64
  2. package/dist/chunk-F4CFWKVI.js.map +1 -0
  3. package/dist/chunk-GQN2QIR2.js +3767 -0
  4. package/dist/chunk-GQN2QIR2.js.map +1 -0
  5. package/dist/{chunk-I3BJZIG5.js → chunk-HIWPQYWI.js} +2 -120
  6. package/dist/chunk-HIWPQYWI.js.map +1 -0
  7. package/dist/chunk-M7X4JZOD.js +121 -0
  8. package/dist/chunk-M7X4JZOD.js.map +1 -0
  9. package/dist/{chunk-UAHPKLB6.js → chunk-MP6237JB.js} +571 -975
  10. package/dist/chunk-MP6237JB.js.map +1 -0
  11. package/dist/chunk-OZDLELJQ.js +937 -0
  12. package/dist/chunk-OZDLELJQ.js.map +1 -0
  13. package/dist/{chunk-CATATY77.js → chunk-TMH6HZWF.js} +8 -7
  14. package/dist/chunk-TMH6HZWF.js.map +1 -0
  15. package/dist/{chunk-6XUS5VFM.js → chunk-YU5PGUK7.js} +6 -4
  16. package/dist/{chunk-6XUS5VFM.js.map → chunk-YU5PGUK7.js.map} +1 -1
  17. package/dist/{composer-CVm5uoUI.d.ts → composer-DoC4veX1.d.ts} +6 -63
  18. package/dist/composer.d.ts +3 -2
  19. package/dist/composer.js +4 -3
  20. package/dist/fleet-decision-projection-SLNZMS42.js +241 -0
  21. package/dist/fleet-decision-projection-SLNZMS42.js.map +1 -0
  22. package/dist/fleet-decision-row-GGCAT4E4.js +351 -0
  23. package/dist/fleet-decision-row-GGCAT4E4.js.map +1 -0
  24. package/dist/index.d.ts +17 -259
  25. package/dist/index.js +1042 -5058
  26. package/dist/index.js.map +1 -1
  27. package/dist/machines.js +3 -2
  28. package/dist/session-Du_FsrZ1.d.ts +264 -0
  29. package/dist/session-ui-BqQDH7YV.d.ts +183 -0
  30. package/dist/session-ui.d.ts +7 -0
  31. package/dist/session-ui.js +17 -0
  32. package/dist/session-ui.js.map +1 -0
  33. package/dist/session.d.ts +4 -2
  34. package/dist/session.js +26 -9
  35. package/dist/{use-composer-By1wn3MM.d.ts → use-composer-BnEzheSk.d.ts} +8 -2
  36. package/dist/use-file-attachments-C0kpHrs9.d.ts +76 -0
  37. package/dist/use-turn-queue-3rkJwjFv.d.ts +290 -0
  38. package/package.json +6 -2
  39. package/src/components/composer.tsx +7 -6
  40. package/src/components/message-timeline.tsx +94 -54
  41. package/src/hooks/use-composer.ts +470 -73
  42. package/src/hooks/use-file-attachments.ts +96 -3
  43. package/src/hooks/use-goal.ts +23 -5
  44. package/src/hooks/use-session-events.ts +5 -0
  45. package/src/hooks/use-session-lineage.ts +1 -1
  46. package/src/hooks/use-session.ts +9 -6
  47. package/src/hooks/use-workspace-sessions.ts +30 -5
  48. package/src/session-ui.ts +13 -0
  49. package/src/session.ts +15 -0
  50. package/src/timeline/activity-rail.tsx +18 -2
  51. package/src/timeline/fleet-decision-projection.ts +366 -0
  52. package/src/timeline/fleet-decision-row.tsx +412 -0
  53. package/src/timeline/index.ts +2 -0
  54. package/src/timeline/projection.ts +19 -8
  55. package/src/timeline/tool-display-name.ts +16 -0
  56. package/src/timeline/tool-renderers.tsx +1 -1
  57. package/src/timeline/types.ts +68 -1
  58. package/dist/chunk-CATATY77.js.map +0 -1
  59. package/dist/chunk-I3BJZIG5.js.map +0 -1
  60. package/dist/chunk-UAHPKLB6.js.map +0 -1
  61. package/dist/chunk-UQS7OQDE.js.map +0 -1
  62. package/dist/session-Db00TRwl.d.ts +0 -446
@@ -1,930 +1,102 @@
1
1
  import {
2
- CREDIT_EXHAUSTION_MESSAGE,
3
- humanizeFailureReason,
4
- isCreditExhaustion,
5
- tryParseJson,
6
2
  useDebouncedCallback,
7
3
  useEmbeddedHumanInputSession,
8
4
  useEmbeddedSession,
9
5
  useEmbeddedSessionMcpApprovalPolicy,
10
6
  useMutationRunner,
7
+ useOpenGeni,
11
8
  usePolledValue,
12
9
  useSessionEventTrigger
13
- } from "./chunk-I3BJZIG5.js";
10
+ } from "./chunk-HIWPQYWI.js";
11
+ import {
12
+ buildTimeline,
13
+ groupTimeline,
14
+ sessionStatusFromEvents
15
+ } from "./chunk-OZDLELJQ.js";
14
16
 
15
- // src/timeline/projection.ts
16
- var WORKER_SPAWN_TOOL = "session_create";
17
- var WORKER_MESSAGE_TOOL = "session_send_message";
18
- function buildTimeline(events) {
19
- const items = [];
20
- const prescan = prescanTurnAnchors(events);
21
- const ordered = orderTimelineEvents(events, prescan);
22
- const last = () => items[items.length - 1];
23
- const closeStreamingTail = () => {
24
- const open = last();
25
- if ((open?.kind === "agent-message" || open?.kind === "reasoning") && open.streaming) {
26
- open.streaming = false;
27
- }
28
- };
29
- const finalizeOpen = (turnId, disposition = "complete") => {
30
- for (const item of items) {
31
- if (turnId !== void 0 && "turnId" in item && item.turnId && turnId && item.turnId !== turnId) {
32
- continue;
33
- }
34
- if ((item.kind === "agent-message" || item.kind === "reasoning") && item.streaming) {
35
- item.streaming = false;
36
- }
37
- if ((item.kind === "tool-call" || item.kind === "worker") && item.status === "running") {
38
- item.status = disposition;
39
- }
40
- if (item.kind === "sandbox" && item.status === "running") {
41
- item.status = disposition;
42
- }
17
+ // src/hooks/use-session.ts
18
+ import { useCallback, useEffect, useMemo, useState } from "react";
19
+ function isTitleEvent(event) {
20
+ return event.type === "session.title_set";
21
+ }
22
+ function useSession(sessionId, options = {}) {
23
+ const { client, workspaceId, workspaceControlEvent, registerSessionReconciler } = useOpenGeni(options);
24
+ const enabled = (options.enabled ?? true) && Boolean(sessionId);
25
+ const [override, setOverride] = useState(null);
26
+ const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
27
+ const load = useCallback(async () => {
28
+ if (!sessionId) {
29
+ return null;
43
30
  }
44
- };
45
- for (const event of ordered) {
46
- const payload = asRecord(event.payload);
47
- const turnId = event.turnId ?? null;
48
- switch (event.type) {
49
- case "user.message": {
50
- closeStreamingTail();
51
- const childCompletion = workerCompletionPayload(payload.childCompletion);
52
- if (childCompletion) {
53
- items.push({
54
- kind: "worker-completion",
55
- id: event.id,
56
- turnId,
57
- occurredAt: event.occurredAt,
58
- childSessionId: childCompletion.childSessionId,
59
- childStatus: childCompletion.childStatus,
60
- goalStatus: childCompletion.goalStatus,
61
- goalText: childCompletion.goalText,
62
- evidence: childCompletion.evidence,
63
- pausedReason: childCompletion.pausedReason,
64
- text: typeof payload.text === "string" ? payload.text : ""
65
- });
66
- break;
67
- }
68
- items.push({
69
- kind: "user-message",
70
- id: event.id,
71
- text: typeof payload.text === "string" ? payload.text : "",
72
- resources: resourceRefs(payload.resources),
73
- tools: toolRefs(payload.tools),
74
- occurredAt: event.occurredAt
75
- });
76
- break;
77
- }
78
- case "agent.message.delta": {
79
- const text = typeof payload.text === "string" ? payload.text : "";
80
- if (!text) {
81
- break;
82
- }
83
- const open = last();
84
- if (open?.kind === "agent-message" && open.streaming && open.turnId === turnId) {
85
- open.text += text;
86
- break;
87
- }
88
- closeStreamingTail();
89
- items.push({
90
- kind: "agent-message",
91
- id: event.id,
92
- turnId,
93
- text,
94
- streaming: true,
95
- occurredAt: event.occurredAt
96
- });
97
- break;
98
- }
99
- case "agent.message.completed": {
100
- const text = typeof payload.text === "string" ? payload.text : "";
101
- const open = [...items].reverse().find(
102
- (item) => item.kind === "agent-message" && item.turnId === turnId
103
- );
104
- if (open && (open.streaming || !open.text || text === open.text || text.startsWith(open.text))) {
105
- if (!open.text || text && text.startsWith(open.text)) {
106
- open.text = text || open.text;
107
- }
108
- open.streaming = false;
109
- break;
110
- }
111
- if (text) {
112
- items.push({
113
- kind: "agent-message",
114
- id: event.id,
115
- turnId,
116
- text,
117
- streaming: false,
118
- occurredAt: event.occurredAt
119
- });
120
- }
121
- break;
122
- }
123
- case "agent.reasoning.delta": {
124
- const text = reasoningText(event.payload);
125
- if (!text) {
126
- break;
127
- }
128
- const open = last();
129
- if (open?.kind === "reasoning" && open.streaming && open.turnId === turnId) {
130
- open.text += text;
131
- break;
132
- }
133
- closeStreamingTail();
134
- items.push({
135
- kind: "reasoning",
136
- id: event.id,
137
- turnId,
138
- text,
139
- streaming: true,
140
- occurredAt: event.occurredAt
141
- });
142
- break;
143
- }
144
- case "agent.toolCall.created": {
145
- const name = typeof payload.name === "string" ? payload.name : "tool";
146
- const callId = typeof payload.id === "string" ? payload.id : null;
147
- const args = payload.arguments ?? null;
148
- closeStreamingTail();
149
- if (name === WORKER_SPAWN_TOOL || name === WORKER_MESSAGE_TOOL) {
150
- items.push({
151
- kind: "worker",
152
- id: event.id,
153
- turnId,
154
- callId,
155
- action: name === WORKER_SPAWN_TOOL ? "spawn" : "message",
156
- prompt: workerPrompt(args),
157
- workerSessionId: extractSessionRef(args),
158
- status: "running",
159
- occurredAt: event.occurredAt
160
- });
161
- break;
162
- }
163
- items.push({
164
- kind: "tool-call",
165
- id: event.id,
166
- turnId,
167
- callId,
168
- name,
169
- arguments: args,
170
- output: void 0,
171
- // The provider-native item drives the per-tool renderers (apply_patch
172
- // operation, computer_call action, web_search providerData, …).
173
- raw: payload.raw,
174
- status: "running",
175
- occurredAt: event.occurredAt
176
- });
177
- break;
178
- }
179
- case "agent.toolCall.output": {
180
- const callId = typeof payload.id === "string" ? payload.id : null;
181
- const target = findOpenCall(items, callId);
182
- if (!target) {
183
- break;
184
- }
185
- if (target.kind === "worker") {
186
- target.status = isErrorOutput(payload) ? "failed" : "complete";
187
- target.workerSessionId = target.workerSessionId ?? extractSessionRef(payload.output);
188
- break;
189
- }
190
- target.status = isErrorOutput(payload) ? "failed" : "complete";
191
- target.output = payload.output;
192
- break;
193
- }
194
- case "sandbox.operation.started":
195
- case "sandbox.operation.completed":
196
- case "sandbox.operation.failed": {
197
- const name = typeof payload.name === "string" ? payload.name : "sandbox";
198
- const status = event.type.endsWith(".failed") ? "failed" : event.type.endsWith(".completed") ? "complete" : "running";
199
- if ((name === "repository-clone" || name === "file-resource-download") && status !== "failed") {
200
- break;
201
- }
202
- const existing = findOpenSandbox(items, name);
203
- if (existing && status !== "running") {
204
- existing.status = status;
205
- if (payload.origin === "created" || payload.origin === "restored" || payload.origin === "resumed") {
206
- existing.origin = payload.origin;
207
- }
208
- const message = failureMessage(payload);
209
- if (message) {
210
- existing.output = existing.output ? `${existing.output}
211
- ${message}` : message;
212
- }
213
- break;
214
- }
215
- if (!existing) {
216
- closeStreamingTail();
217
- items.push({
218
- kind: "sandbox",
219
- id: event.id,
220
- turnId,
221
- name,
222
- command: typeof payload.command === "string" ? payload.command : null,
223
- output: failureMessage(payload) ?? "",
224
- origin: payload.origin === "created" || payload.origin === "restored" || payload.origin === "resumed" ? payload.origin : null,
225
- status,
226
- occurredAt: event.occurredAt
227
- });
228
- }
229
- break;
230
- }
231
- case "sandbox.command.output.delta": {
232
- const text = typeof payload.chunk === "string" ? payload.chunk : typeof payload.text === "string" ? payload.text : typeof payload.output === "string" ? payload.output : "";
233
- if (!text) {
234
- break;
235
- }
236
- const open = (typeof payload.name === "string" ? findOpenSandbox(items, payload.name) : void 0) ?? [...items].reverse().find(
237
- (item) => item.kind === "sandbox" && item.status === "running"
238
- );
239
- if (open) {
240
- open.output += text;
241
- }
242
- break;
243
- }
244
- case "session.status.changed": {
245
- const status = payload.status;
246
- if (!isSessionStatus(status)) {
247
- break;
248
- }
249
- if (!ATTENTION_STATUSES.has(status)) {
250
- break;
251
- }
252
- const previous = [...items].reverse().find((item) => item.kind === "session-status");
253
- if (previous?.status === status) {
254
- break;
255
- }
256
- items.push({ kind: "session-status", id: event.id, status, occurredAt: event.occurredAt });
257
- break;
258
- }
259
- case "session.requiresAction": {
260
- finalizeOpen(turnId);
261
- items.push({
262
- kind: "notice",
263
- id: event.id,
264
- tone: "waiting",
265
- text: "Approval needed \u2014 the turn is paused until someone decides.",
266
- occurredAt: event.occurredAt
267
- });
268
- break;
269
- }
270
- case "session.context.compacted": {
271
- closeStreamingTail();
272
- const before = typeof payload.estimatedTokensBefore === "number" ? Math.round(payload.estimatedTokensBefore).toLocaleString("en-US") : null;
273
- const after = typeof payload.estimatedTokensAfter === "number" ? Math.round(payload.estimatedTokensAfter).toLocaleString("en-US") : null;
274
- items.push({
275
- kind: "notice",
276
- id: event.id,
277
- tone: "waiting",
278
- text: before && after ? `Active conversation history compacted from approximately ${before} to ${after} tokens.` : "Context compacted so the turn could continue.",
279
- occurredAt: event.occurredAt
280
- });
281
- break;
282
- }
283
- case "session.context.compaction.skipped": {
284
- closeStreamingTail();
285
- const reason = typeof payload.reason === "string" ? payload.reason : null;
286
- items.push({
287
- kind: "notice",
288
- id: event.id,
289
- tone: reason === "summarization_failed" ? "failed" : "waiting",
290
- text: reason === "no_history" ? "Context compaction skipped because there is no active history to compact." : reason === "replacement_not_smaller" ? "Context compaction skipped because the generated checkpoint would not reduce the context." : reason === "replacement_unchanged" ? "Context compaction stopped because it reproduced the same checkpoint without making progress." : reason === "summarization_failed" ? "Context compaction failed without replacing the active conversation history. Request it again to retry." : "Context compaction was not needed.",
291
- occurredAt: event.occurredAt
292
- });
293
- break;
294
- }
295
- case "turn.recovery.requested": {
296
- break;
297
- }
298
- case "turn.event.rejected_late": {
299
- break;
300
- }
301
- case "tool.auth_needed":
302
- case "credential.auth_needed": {
303
- closeStreamingTail();
304
- items.push({
305
- kind: "auth-needed",
306
- id: event.id,
307
- turnId,
308
- providerDomain: typeof payload.providerDomain === "string" ? payload.providerDomain : "",
309
- connectionId: typeof payload.connectionId === "string" ? payload.connectionId : null,
310
- reason: authNeededReason(payload.reason),
311
- scopes: stringList(payload.scopes),
312
- resource: typeof payload.resource === "string" ? payload.resource : null,
313
- toolName: typeof payload.toolName === "string" ? payload.toolName : null,
314
- authorizationUrl: typeof payload.authorizationUrl === "string" ? payload.authorizationUrl : null,
315
- occurredAt: event.occurredAt
316
- });
317
- break;
318
- }
319
- case "turn.completed": {
320
- if (payload.maintenance === "context_compaction") {
321
- finalizeOpen(turnId);
322
- break;
323
- }
324
- if (isCreditExhaustionPayload(payload)) {
325
- finalizeOpen(turnId);
326
- items.push(turnEndItem(event, "failed", CREDIT_EXHAUSTION_MESSAGE));
327
- items.push({
328
- kind: "notice",
329
- id: event.id,
330
- tone: "failed",
331
- text: CREDIT_EXHAUSTION_MESSAGE,
332
- occurredAt: event.occurredAt
333
- });
334
- break;
335
- }
336
- finalizeOpen(turnId);
337
- items.push(turnEndItem(event, "complete", null));
338
- break;
339
- }
340
- case "turn.failed": {
341
- const hadActivity = hasTurnActivity(items, turnId);
342
- const failureText = isCreditExhaustionPayload(payload) ? CREDIT_EXHAUSTION_MESSAGE : failureMessage(payload);
343
- finalizeOpen(turnId, "cancelled");
344
- items.push(turnEndItem(event, "failed", failureText));
345
- if (!hadActivity) {
346
- items.push({
347
- kind: "notice",
348
- id: event.id,
349
- tone: "failed",
350
- text: failureText ?? "The turn failed.",
351
- occurredAt: event.occurredAt
352
- });
353
- }
354
- break;
355
- }
356
- case "turn.cancelled": {
357
- if (turnId && !prescan.startedTurnIds.has(turnId)) {
358
- break;
359
- }
360
- const hadActivity = hasTurnActivity(items, turnId);
361
- finalizeOpen(turnId, "cancelled");
362
- items.push(turnEndItem(event, "cancelled", null));
363
- if (!hadActivity) {
364
- items.push({
365
- kind: "notice",
366
- id: event.id,
367
- tone: "cancelled",
368
- text: "Interrupted.",
369
- occurredAt: event.occurredAt
370
- });
371
- }
372
- break;
31
+ const fetched = await client.getSession(workspaceId, sessionId);
32
+ setOverride(null);
33
+ return fetched;
34
+ }, [client, workspaceId, sessionId]);
35
+ const { data, loading, error, refresh } = usePolledValue(load, {
36
+ pollIntervalMs: options.pollIntervalMs,
37
+ enabled
38
+ });
39
+ useEffect(() => {
40
+ if (enabled && workspaceControlEvent) void refresh();
41
+ }, [enabled, refresh, workspaceControlEvent]);
42
+ useEffect(() => {
43
+ if (!sessionId || !enabled) return;
44
+ return registerSessionReconciler(sessionId, "session", refresh);
45
+ }, [enabled, refresh, registerSessionReconciler, sessionId]);
46
+ const base = data ?? null;
47
+ const session = useMemo(
48
+ () => base && override && override.id === base.id ? { ...base, title: override.title, titleSource: override.titleSource } : base,
49
+ [base, override]
50
+ );
51
+ const onTitleEvent = useCallback(
52
+ (event) => {
53
+ const payload = event.payload ?? {};
54
+ const title = payload.title;
55
+ if (typeof title !== "string") {
56
+ return;
373
57
  }
374
- case "memory.saved":
375
- case "memory.corrected": {
376
- const memory = memoryItem(event.id, event.type, turnId, payload, event.occurredAt);
377
- if (memory) {
378
- closeStreamingTail();
379
- items.push(memory);
58
+ const source = payload.source === "user" || payload.source === "agent" ? payload.source : null;
59
+ setOverride((current) => {
60
+ const next = current ?? base;
61
+ if (!next) {
62
+ return current;
380
63
  }
381
- break;
382
- }
383
- case "goal.set":
384
- case "goal.updated":
385
- case "goal.completed":
386
- case "goal.paused":
387
- case "goal.resumed":
388
- case "goal.cleared":
389
- case "goal.continuation": {
390
- items.push({
391
- kind: "goal",
392
- id: event.id,
393
- action: event.type.slice("goal.".length),
394
- text: goalText(payload),
395
- occurredAt: event.occurredAt
396
- });
397
- break;
398
- }
399
- default:
400
- break;
401
- }
402
- }
403
- return items;
404
- }
405
- function isCreditExhaustionPayload(payload) {
406
- return isCreditExhaustion({
407
- error: typeof payload.error === "string" ? payload.error : null,
408
- detail: typeof payload.detail === "string" ? payload.detail : null,
409
- segmentLimit: typeof payload.segmentLimit === "string" ? payload.segmentLimit : null
64
+ return { ...next, title, titleSource: source };
65
+ });
66
+ },
67
+ [base]
68
+ );
69
+ useSessionEventTrigger(client, workspaceId, sessionId, isTitleEvent, onTitleEvent, {
70
+ enabled,
71
+ ...options.events !== void 0 ? { events: options.events } : {}
410
72
  });
411
- }
412
- function creditExhaustedFromEvents(events) {
413
- const ordered = [...events].sort((a, b) => a.sequence - b.sequence);
414
- for (let index = ordered.length - 1; index >= 0; index -= 1) {
415
- const event = ordered[index];
416
- if (event?.type !== "turn.completed" && event?.type !== "turn.failed" && event?.type !== "turn.cancelled") {
417
- continue;
418
- }
419
- return isCreditExhaustionPayload(asRecord(event.payload));
420
- }
421
- return false;
422
- }
423
- function sessionStatusFromEvents(events) {
424
- for (let index = events.length - 1; index >= 0; index -= 1) {
425
- const event = events[index];
426
- if (event?.type !== "session.status.changed") {
427
- continue;
428
- }
429
- const status = asRecord(event.payload).status;
430
- if (isSessionStatus(status)) {
431
- return status;
432
- }
433
- }
434
- return null;
435
- }
436
- function isActivityItem(item) {
437
- switch (item.kind) {
438
- case "reasoning":
439
- case "tool-call":
440
- case "worker":
441
- case "sandbox":
442
- case "memory":
443
- return true;
444
- default:
445
- return false;
446
- }
447
- }
448
- function groupTimeline(items) {
449
- const groups = [];
450
- for (const item of items) {
451
- if (isActivityItem(item)) {
452
- const open = groups[groups.length - 1];
453
- if (open?.kind === "activity" && open.outcome === void 0) {
454
- open.items.push(item);
455
- } else {
456
- groups.push({ kind: "activity", id: `activity-${item.id}`, items: [item] });
457
- }
458
- continue;
459
- }
460
- if (item.kind === "turn-end") {
461
- stampTurnOutcome(groups, item);
462
- foldSettledTurn(groups, item);
463
- continue;
464
- }
465
- groups.push({ kind: "item", item });
466
- }
467
- return groups;
468
- }
469
- function prescanTurnAnchors(events) {
470
- const ordered = [...events].sort((a, b) => a.sequence - b.sequence);
471
- const queuedTurnByTrigger = /* @__PURE__ */ new Map();
472
- const startSeqByTrigger = /* @__PURE__ */ new Map();
473
- const cancelledTurnIds = /* @__PURE__ */ new Set();
474
- const fallbackSeqByTurn = /* @__PURE__ */ new Map();
475
- const startedTurnIds = /* @__PURE__ */ new Set();
476
- for (const event of ordered) {
477
- const payload = asRecord(event.payload);
478
- const turnId = event.turnId ?? null;
479
- if (event.type === "turn.queued") {
480
- const triggerEventId = typeof payload.triggerEventId === "string" ? payload.triggerEventId : null;
481
- const queuedTurnId = typeof payload.turnId === "string" ? payload.turnId : turnId;
482
- if (triggerEventId && queuedTurnId) {
483
- queuedTurnByTrigger.set(triggerEventId, queuedTurnId);
484
- }
485
- continue;
486
- }
487
- if (event.type === "turn.started") {
488
- const triggerEventId = typeof payload.triggerEventId === "string" ? payload.triggerEventId : null;
489
- if (triggerEventId && !startSeqByTrigger.has(triggerEventId)) {
490
- startSeqByTrigger.set(triggerEventId, event.sequence);
491
- }
492
- if (turnId) {
493
- startedTurnIds.add(turnId);
494
- }
495
- } else if (event.type === "turn.cancelled" && turnId) {
496
- cancelledTurnIds.add(turnId);
497
- }
498
- if (turnId && isTurnExecutionEvidence(event.type)) {
499
- const previous = fallbackSeqByTurn.get(turnId);
500
- if (previous === void 0 || event.sequence < previous) {
501
- fallbackSeqByTurn.set(turnId, event.sequence);
73
+ const updateTitle = useCallback(
74
+ async (title) => {
75
+ if (!sessionId) {
76
+ return null;
502
77
  }
503
- }
504
- if (turnId && isTurnExecutionEvidence(event.type)) {
505
- startedTurnIds.add(turnId);
506
- }
507
- }
508
- for (const [triggerEventId, turnId] of queuedTurnByTrigger) {
509
- if (!startSeqByTrigger.has(triggerEventId)) {
510
- const fallbackSeq = fallbackSeqByTurn.get(turnId);
511
- if (fallbackSeq !== void 0) {
512
- startSeqByTrigger.set(triggerEventId, fallbackSeq);
78
+ const result = await run(() => client.updateSession(workspaceId, sessionId, { title }));
79
+ if (result) {
80
+ setOverride(result);
513
81
  }
514
- }
515
- }
516
- const cancelledBeforeStartTriggers = /* @__PURE__ */ new Set();
517
- for (const [triggerEventId, turnId] of queuedTurnByTrigger) {
518
- if (cancelledTurnIds.has(turnId) && !startSeqByTrigger.has(triggerEventId) && !fallbackSeqByTurn.has(turnId)) {
519
- cancelledBeforeStartTriggers.add(triggerEventId);
520
- }
521
- }
522
- return { queuedTurnByTrigger, startSeqByTrigger, cancelledBeforeStartTriggers, startedTurnIds };
523
- }
524
- function orderTimelineEvents(events, prescan) {
525
- const ordered = [...events].sort((a, b) => a.sequence - b.sequence);
526
- const insertions = /* @__PURE__ */ new Map();
527
- for (const event of ordered) {
528
- if (event.type !== "user.message") {
529
- continue;
530
- }
531
- const queuedTurnId = prescan.queuedTurnByTrigger.get(event.id);
532
- if (!queuedTurnId) {
533
- pushInsertion(insertions, event.sequence, event);
534
- continue;
535
- }
536
- if (prescan.cancelledBeforeStartTriggers.has(event.id)) {
537
- continue;
538
- }
539
- const startSeq = prescan.startSeqByTrigger.get(event.id);
540
- if (startSeq !== void 0) {
541
- pushInsertion(insertions, startSeq, event);
542
- continue;
543
- }
544
- }
545
- const projected = [];
546
- for (const event of ordered) {
547
- const before = insertions.get(event.sequence);
548
- if (before) {
549
- projected.push(...before);
550
- }
551
- if (event.type !== "user.message") {
552
- projected.push(event);
553
- }
554
- }
555
- return projected;
556
- }
557
- function pushInsertion(insertions, sequence, event) {
558
- const bucket = insertions.get(sequence);
559
- if (bucket) {
560
- bucket.push(event);
561
- } else {
562
- insertions.set(sequence, [event]);
563
- }
564
- }
565
- function isAgentActivityEvent(type) {
566
- return type.startsWith("agent.") || type.startsWith("sandbox.");
567
- }
568
- function isTurnExecutionEvidence(type) {
569
- return isAgentActivityEvent(type) || type === "turn.completed" || type === "turn.failed" || type === "turn.recovery.requested" || type === "turn.capacity_waiting" || type === "session.requiresAction" || type === "tool.auth_needed" || type === "credential.auth_needed" || type.startsWith("rig.setup.") || type === "codex.capacity.waiting" || type === "codex.capacity.resumed";
570
- }
571
- function turnEndItem(event, outcome, failureText) {
572
- return {
573
- kind: "turn-end",
574
- id: `${event.id}-turn-end`,
575
- turnId: event.turnId ?? null,
576
- outcome,
577
- failureText,
578
- occurredAt: event.occurredAt
579
- };
580
- }
581
- function hasTurnActivity(items, turnId) {
582
- if (turnId) {
583
- return items.some((item) => isActivityItem(item) && item.turnId === turnId);
584
- }
585
- for (let index = items.length - 1; index >= 0; index -= 1) {
586
- const item = items[index];
587
- if (!item || item.kind === "turn-end" || item.kind === "user-message") {
588
- return false;
589
- }
590
- if (isActivityItem(item)) {
591
- return true;
592
- }
593
- }
594
- return false;
595
- }
596
- function stampTurnOutcome(groups, turnEnd) {
597
- if (turnEnd.turnId === null) {
598
- const trailing = groups[groups.length - 1];
599
- if (trailing?.kind === "activity" && trailing.outcome === void 0) {
600
- applyTurnOutcome(trailing, turnEnd);
601
- }
602
- return;
603
- }
604
- for (const group of groups) {
605
- if (group.kind !== "activity" || group.outcome !== void 0) {
606
- continue;
607
- }
608
- if (group.items.some((activity) => activity.turnId === turnEnd.turnId)) {
609
- applyTurnOutcome(group, turnEnd);
610
- }
611
- }
612
- }
613
- function applyTurnOutcome(group, turnEnd) {
614
- if (turnEnd.outcome === "complete") {
615
- group.outcome = "complete";
616
- return;
617
- }
618
- const hasFailed = group.items.some((item) => "status" in item && item.status === "failed");
619
- const hasInterrupted = group.items.some(
620
- (item) => "status" in item && item.status === "cancelled"
621
- );
622
- group.outcome = hasFailed ? "failed" : hasInterrupted ? "cancelled" : "complete";
623
- if (turnEnd.failureText && hasFailed) {
624
- group.failureText = turnEnd.failureText;
625
- }
626
- }
627
- function foldSettledTurn(groups, turnEnd) {
628
- let startIndex = groups.length;
629
- let stoppedAtForeignTurn = false;
630
- while (startIndex > 0) {
631
- const previous = groups[startIndex - 1];
632
- if (isTurnBoundary(previous)) {
633
- break;
634
- }
635
- if (belongsToDifferentTurn(previous, turnEnd.turnId)) {
636
- stoppedAtForeignTurn = true;
637
- break;
638
- }
639
- startIndex -= 1;
640
- }
641
- if (stoppedAtForeignTurn) {
642
- while (startIndex < groups.length && isBetweenTurnDivider(groups[startIndex])) {
643
- startIndex += 1;
644
- }
645
- }
646
- const collected = groups.slice(startIndex);
647
- if (collected.length === 0) {
648
- return;
649
- }
650
- const finalMessage = extractFinalAgentMessage(collected, turnEnd);
651
- const body = finalMessage ? collected.slice(0, -1) : collected;
652
- if (body.length === 0) {
653
- return;
654
- }
655
- const firstOccurredAt = groupStartedAt(body[0]) ?? turnEnd.occurredAt;
656
- const turnGroup = {
657
- kind: "turn",
658
- id: `turn-${turnEnd.turnId ?? turnEnd.id}`,
659
- outcome: turnEnd.outcome,
660
- startedAt: firstOccurredAt,
661
- endedAt: turnEnd.occurredAt,
662
- groups: body
663
- };
664
- if (turnEnd.failureText) {
665
- turnGroup.failureText = turnEnd.failureText;
666
- }
667
- groups.splice(
668
- startIndex,
669
- collected.length,
670
- ...finalMessage ? [turnGroup, finalMessage] : [turnGroup]
671
- );
672
- }
673
- function isTurnBoundary(group) {
674
- return group?.kind === "turn" || group?.kind === "item" && group.item.kind === "user-message";
675
- }
676
- function belongsToDifferentTurn(group, turnId) {
677
- if (!group || !turnId) {
678
- return false;
679
- }
680
- if (group.kind === "activity") {
681
- return group.items.length > 0 && group.items.every((item) => item.turnId !== null && item.turnId !== turnId);
682
- }
683
- return group.kind === "item" && group.item.kind === "agent-message" && group.item.turnId !== null && group.item.turnId !== turnId;
684
- }
685
- function isBetweenTurnDivider(group) {
686
- return group?.kind === "item" && group.item.kind === "session-status" && group.item.status !== "running";
687
- }
688
- function extractFinalAgentMessage(groups, turnEnd) {
689
- const tail = groups[groups.length - 1];
690
- if (tail?.kind !== "item" || tail.item.kind !== "agent-message" || tail.item.streaming) {
691
- return null;
692
- }
693
- if (tail.item.turnId && turnEnd.turnId && tail.item.turnId !== turnEnd.turnId) {
694
- return null;
695
- }
696
- return tail;
697
- }
698
- function groupStartedAt(group) {
699
- if (!group) {
700
- return void 0;
701
- }
702
- switch (group.kind) {
703
- case "item":
704
- return group.item.occurredAt;
705
- case "activity":
706
- return group.items[0]?.occurredAt;
707
- case "turn":
708
- return group.startedAt;
709
- }
710
- }
711
- function asRecord(value) {
712
- return value !== null && typeof value === "object" ? value : {};
713
- }
714
- var SESSION_STATUSES = [
715
- "queued",
716
- "running",
717
- "idle",
718
- "requires_action",
719
- "failed",
720
- "cancelled"
721
- ];
722
- var ATTENTION_STATUSES = /* @__PURE__ */ new Set([
723
- "requires_action",
724
- "failed",
725
- "cancelled"
726
- ]);
727
- function resourceRefs(value) {
728
- if (!Array.isArray(value)) {
729
- return [];
730
- }
731
- return value.filter((entry) => {
732
- const record = asRecord(entry);
733
- if (record.kind === "repository") {
734
- return typeof record.uri === "string" && typeof record.ref === "string";
735
- }
736
- return record.kind === "file" && typeof record.fileId === "string";
737
- });
738
- }
739
- function toolRefs(value) {
740
- if (!Array.isArray(value)) {
741
- return [];
742
- }
743
- return value.filter((entry) => {
744
- const record = asRecord(entry);
745
- return record.kind === "mcp" && typeof record.id === "string";
746
- });
747
- }
748
- function workerCompletionPayload(value) {
749
- const payload = asRecord(value);
750
- const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
751
- if (typeof payload.childSessionId !== "string" || !uuidPattern.test(payload.childSessionId) || typeof payload.status !== "string" || payload.status.trim() === "") {
752
- return null;
753
- }
754
- const goal = asRecord(payload.goal);
755
- return {
756
- childSessionId: payload.childSessionId,
757
- childStatus: payload.status,
758
- goalStatus: typeof goal.status === "string" ? goal.status : null,
759
- goalText: typeof goal.text === "string" ? goal.text : null,
760
- evidence: typeof goal.evidence === "string" ? goal.evidence : null,
761
- // Console pauses and several server paths put the human explanation on
762
- // `rationale`, not `pausedReason` — same fallback the goal pill uses, so a
763
- // paused worker card never shows "Worker paused" with the reason missing.
764
- pausedReason: typeof goal.pausedReason === "string" ? goal.pausedReason : typeof goal.rationale === "string" ? goal.rationale : null
765
- };
766
- }
767
- function isSessionStatus(value) {
768
- return typeof value === "string" && SESSION_STATUSES.includes(value);
769
- }
770
- function isErrorOutput(payload) {
771
- if (payload.error === true || payload.failed === true) {
772
- return true;
773
- }
774
- const output = payload.output;
775
- return !!output && typeof output === "object" && output.isError === true;
776
- }
777
- function findOpenCall(items, callId) {
778
- const reversed = [...items].reverse();
779
- const isCall = (item) => item.kind === "tool-call" || item.kind === "worker";
780
- if (callId) {
781
- const byId = reversed.find((item) => isCall(item) && item.callId === callId);
782
- if (byId) {
783
- return byId;
784
- }
785
- }
786
- return reversed.find(
787
- (item) => isCall(item) && item.status === "running"
788
- );
789
- }
790
- function findOpenSandbox(items, name) {
791
- return [...items].reverse().find(
792
- (item) => item.kind === "sandbox" && item.name === name && item.status === "running"
82
+ return result;
83
+ },
84
+ [client, workspaceId, sessionId, run]
793
85
  );
794
- }
795
- function failureMessage(payload) {
796
- for (const key of ["error", "message"]) {
797
- const value = payload[key];
798
- if (typeof value === "string" && value.trim().length > 0) {
799
- return humanizeFailureReason(value);
800
- }
801
- }
802
- return null;
803
- }
804
- function goalText(payload) {
805
- if (typeof payload.text === "string" && payload.text) {
806
- return payload.text;
807
- }
808
- const goal = asRecord(payload.goal);
809
- if (typeof goal.text === "string" && goal.text) {
810
- return goal.text;
811
- }
812
- if (typeof payload.prompt === "string" && payload.prompt) {
813
- return payload.prompt;
814
- }
815
- return null;
816
- }
817
- function memoryItem(id, type, turnId, payload, occurredAt) {
818
- const memoryId = typeof payload.memoryId === "string" && payload.memoryId ? payload.memoryId : null;
819
- if (!memoryId) {
820
- return null;
821
- }
822
- const replacementPreview = typeof payload.replacementPreview === "string" ? payload.replacementPreview : void 0;
823
- const replacementMemoryId = typeof payload.replacementMemoryId === "string" ? payload.replacementMemoryId : void 0;
824
- const action = typeof payload.action === "string" ? payload.action : void 0;
825
86
  return {
826
- kind: "memory",
827
- id,
828
- turnId,
829
- variant: type === "memory.corrected" ? "corrected" : "saved",
830
- memoryKind: typeof payload.kind === "string" ? payload.kind : "",
831
- preview: typeof payload.preview === "string" ? payload.preview : "",
832
- ...payload.deduped === true ? { deduped: true } : {},
833
- ...replacementPreview ? { replacementPreview } : {},
834
- ...action ? { action } : {},
835
- memoryId,
836
- ...replacementMemoryId ? { replacementMemoryId } : {},
837
- occurredAt
87
+ session,
88
+ loading,
89
+ error,
90
+ refresh,
91
+ updateTitle,
92
+ updating: mutating,
93
+ mutationError,
94
+ clearMutationError
838
95
  };
839
96
  }
840
- var AUTH_NEEDED_REASONS = /* @__PURE__ */ new Set([
841
- "missing_connection",
842
- "expired",
843
- "insufficient_scope",
844
- "refresh_failed",
845
- "unsupported_auth",
846
- "resource_scope_unavailable"
847
- ]);
848
- function authNeededReason(value) {
849
- return typeof value === "string" && AUTH_NEEDED_REASONS.has(value) ? value : null;
850
- }
851
- function stringList(value) {
852
- return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
853
- }
854
- function reasoningText(payload) {
855
- const record = asRecord(payload);
856
- if (typeof record.text === "string") {
857
- return record.text;
858
- }
859
- const content = asRecord(asRecord(record.item).rawItem).content;
860
- if (!Array.isArray(content)) {
861
- return "";
862
- }
863
- return content.map((part) => {
864
- const text = asRecord(part).text;
865
- return typeof text === "string" ? text : "";
866
- }).join("");
867
- }
868
- function workerPrompt(args) {
869
- const record = asRecord(typeof args === "string" ? tryParseJson(args) : args);
870
- for (const key of ["initialMessage", "message", "text", "prompt"]) {
871
- const value = record[key];
872
- if (typeof value === "string" && value.trim().length > 0) {
873
- return value;
874
- }
875
- }
876
- return null;
877
- }
878
- function extractSessionRef(value, depth = 0) {
879
- if (depth > 6 || value === null || value === void 0) {
880
- return null;
881
- }
882
- if (typeof value === "string") {
883
- return extractSessionRef(tryParseJson(value), depth + 1);
884
- }
885
- if (Array.isArray(value)) {
886
- for (const entry of value) {
887
- const found = extractSessionRef(entry, depth + 1);
888
- if (found) {
889
- return found;
890
- }
891
- }
892
- return null;
893
- }
894
- if (typeof value !== "object") {
895
- return null;
896
- }
897
- const record = value;
898
- if (typeof record.sessionId === "string" && looksLikeId(record.sessionId)) {
899
- return record.sessionId;
900
- }
901
- if (typeof record.id === "string" && looksLikeId(record.id) && ("status" in record || "workspaceId" in record || "initialMessage" in record)) {
902
- return record.id;
903
- }
904
- for (const key of ["structuredContent", "session", "result", "content"]) {
905
- if (key in record) {
906
- const found = extractSessionRef(record[key], depth + 1);
907
- if (found) {
908
- return found;
909
- }
910
- }
911
- }
912
- if (typeof record.text === "string") {
913
- return extractSessionRef(tryParseJson(record.text), depth + 1);
914
- }
915
- return null;
916
- }
917
- function looksLikeId(value) {
918
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
919
- }
920
- function toolDisplayName(name) {
921
- const boundary = name.indexOf("__");
922
- const toolPart = boundary >= 0 ? name.slice(boundary + 2) : name;
923
- return toolPart.replace(/[_-]+/g, " ").trim();
924
- }
925
97
 
926
98
  // src/hooks/use-session-events.ts
927
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
99
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef, useState as useState2 } from "react";
928
100
  var INITIAL_TAIL_PAGE_SIZE = 1e3;
929
101
  var OLDER_PAGE_SIZE = 5e3;
930
102
  var INITIAL_FETCH_CAP = 1;
@@ -973,16 +145,16 @@ function useSessionEvents(sessionId, options = {}) {
973
145
  const replay = options.replay ?? "windowed";
974
146
  const fullReplay = replay === "full" || after !== 0;
975
147
  const streamKey = `${workspaceId}\0${sessionId ?? ""}\0${after}\0${fullReplay ? "full" : "windowed"}`;
976
- const [eventWindow, setEventWindow] = useState(EMPTY_EVENT_WINDOW);
977
- const [connectionState, setConnectionState] = useState("idle");
978
- const [error, setError] = useState(null);
979
- const [hasOlder, setHasOlder] = useState(false);
980
- const [sessionStatusProjection, setSessionStatusProjection] = useState(
148
+ const [eventWindow, setEventWindow] = useState2(EMPTY_EVENT_WINDOW);
149
+ const [connectionState, setConnectionState] = useState2("idle");
150
+ const [error, setError] = useState2(null);
151
+ const [hasOlder, setHasOlder] = useState2(false);
152
+ const [sessionStatusProjection, setSessionStatusProjection] = useState2(
981
153
  null
982
154
  );
983
- const [initialLoading, setInitialLoading] = useState(true);
984
- const [loadingOlder, setLoadingOlder] = useState(false);
985
- const [streamEpoch, setStreamEpoch] = useState(0);
155
+ const [initialLoading, setInitialLoading] = useState2(true);
156
+ const [loadingOlder, setLoadingOlder] = useState2(false);
157
+ const [streamEpoch, setStreamEpoch] = useState2(0);
986
158
  const lastSequenceRef = useRef(after);
987
159
  const streamResumeSequenceRef = useRef(after);
988
160
  const oldestSequenceRef = useRef(null);
@@ -997,8 +169,8 @@ function useSessionEvents(sessionId, options = {}) {
997
169
  sequence: after,
998
170
  status: null
999
171
  });
1000
- const [stateStreamKey, setStateStreamKey] = useState(streamKey);
1001
- useEffect(() => {
172
+ const [stateStreamKey, setStateStreamKey] = useState2(streamKey);
173
+ useEffect2(() => {
1002
174
  if (streamKeyRef.current !== streamKey) {
1003
175
  streamKeyRef.current = streamKey;
1004
176
  setStateStreamKey(streamKey);
@@ -1167,7 +339,7 @@ function useSessionEvents(sessionId, options = {}) {
1167
339
  streamEpoch,
1168
340
  reconcileSession
1169
341
  ]);
1170
- const loadOlder = useCallback(async () => {
342
+ const loadOlder = useCallback2(async () => {
1171
343
  if (!sessionId || loadingOlderRef.current || !hasOlderRef.current) {
1172
344
  return false;
1173
345
  }
@@ -1229,7 +401,7 @@ function useSessionEvents(sessionId, options = {}) {
1229
401
  }, [client, workspaceId, sessionId]);
1230
402
  const identityMatches = stateStreamKey === streamKey;
1231
403
  const visibleEvents = identityMatches ? eventWindow.events : EMPTY_EVENTS;
1232
- const timeline = useMemo(() => buildTimeline(visibleEvents), [visibleEvents]);
404
+ const timeline = useMemo2(() => buildTimeline(visibleEvents), [visibleEvents]);
1233
405
  return {
1234
406
  events: visibleEvents,
1235
407
  timeline,
@@ -1540,7 +712,12 @@ async function loadPreviousPage(client, workspaceId, sessionId, before, options)
1540
712
  const page = await client.listEvents(workspaceId, sessionId, {
1541
713
  before,
1542
714
  limit: requested,
1543
- compact: true
715
+ compact: true,
716
+ // Monitoring summaries omit correlation fields once a bounded durable
717
+ // payload exceeds their preview threshold. The browser timeline needs the
718
+ // sanitized stored payload so persisted tool outputs still match their
719
+ // calls and expose truthful truncation telemetry after a reload.
720
+ payloadMode: "full"
1544
721
  });
1545
722
  if (options.signal?.aborted) {
1546
723
  throw abortError();
@@ -1560,11 +737,11 @@ function maxResumeSequence(events) {
1560
737
  return events.reduce((max, event) => Math.max(max, eventResumeSequence(event)), 0);
1561
738
  }
1562
739
  function eventResumeSequence(event) {
1563
- const payload = asRecord2(event.payload);
740
+ const payload = asRecord(event.payload);
1564
741
  const coalescedUntil = Number(payload.coalescedUntil);
1565
742
  return Math.max(event.sequence, Number.isFinite(coalescedUntil) ? Math.floor(coalescedUntil) : 0);
1566
743
  }
1567
- function asRecord2(value) {
744
+ function asRecord(value) {
1568
745
  return value && typeof value === "object" ? value : {};
1569
746
  }
1570
747
  function assertAscending(events) {
@@ -1592,8 +769,199 @@ function abortError() {
1592
769
  return error;
1593
770
  }
1594
771
 
772
+ // src/hooks/use-file-attachments.ts
773
+ import { useCallback as useCallback3, useRef as useRef2, useState as useState3 } from "react";
774
+ var isImage = (file) => file.type.startsWith("image/");
775
+ function useFileAttachments(options = {}) {
776
+ const { client, workspaceId } = useOpenGeni(options);
777
+ const pasteFilter = options.pasteFilter ?? isImage;
778
+ const [attachments, setAttachments] = useState3([]);
779
+ const sources = useRef2(/* @__PURE__ */ new Map());
780
+ const startUpload = useCallback3(
781
+ (id, file) => {
782
+ void client.uploadFile(workspaceId, {
783
+ filename: file.name || "file",
784
+ contentType: file.type || "application/octet-stream",
785
+ data: file
786
+ }).then((asset) => {
787
+ sources.current.delete(id);
788
+ setAttachments(
789
+ (current) => current.map(
790
+ (attachment) => attachment.id === id ? {
791
+ ...attachment,
792
+ status: "ready",
793
+ file: asset,
794
+ name: asset.filename,
795
+ contentType: asset.contentType,
796
+ sizeBytes: asset.sizeBytes,
797
+ error: void 0
798
+ } : attachment
799
+ )
800
+ );
801
+ }).catch((error) => {
802
+ setAttachments(
803
+ (current) => current.map(
804
+ (attachment) => attachment.id === id ? {
805
+ ...attachment,
806
+ status: "failed",
807
+ error: error instanceof Error ? error.message : String(error)
808
+ } : attachment
809
+ )
810
+ );
811
+ });
812
+ },
813
+ [client, workspaceId]
814
+ );
815
+ const addFiles = useCallback3(
816
+ (files) => {
817
+ for (const file of files) {
818
+ const id = crypto.randomUUID();
819
+ sources.current.set(id, file);
820
+ const previewUrl = isImage(file) ? URL.createObjectURL(file) : void 0;
821
+ setAttachments((current) => [
822
+ ...current,
823
+ {
824
+ id,
825
+ name: file.name || "image",
826
+ contentType: file.type || "application/octet-stream",
827
+ sizeBytes: file.size,
828
+ status: "uploading",
829
+ ...previewUrl ? { previewUrl } : {}
830
+ }
831
+ ]);
832
+ startUpload(id, file);
833
+ }
834
+ },
835
+ [startUpload]
836
+ );
837
+ const retry = useCallback3(
838
+ (id) => {
839
+ const file = sources.current.get(id);
840
+ if (!file) {
841
+ return;
842
+ }
843
+ setAttachments(
844
+ (current) => current.map(
845
+ (attachment) => attachment.id === id ? { ...attachment, status: "uploading", error: void 0 } : attachment
846
+ )
847
+ );
848
+ startUpload(id, file);
849
+ },
850
+ [startUpload]
851
+ );
852
+ const addFromPaste = useCallback3(
853
+ (event) => {
854
+ const clipboardFiles = event.clipboardData?.files;
855
+ if (!clipboardFiles) {
856
+ return;
857
+ }
858
+ const files = [...clipboardFiles].filter(pasteFilter);
859
+ if (files.length > 0) {
860
+ addFiles(files);
861
+ }
862
+ },
863
+ [addFiles, pasteFilter]
864
+ );
865
+ const restoreReadyFiles = useCallback3(
866
+ (files) => {
867
+ const incoming = /* @__PURE__ */ new Map();
868
+ for (const file of files) {
869
+ if (file.status === "ready" && file.workspaceId === workspaceId) {
870
+ incoming.set(file.id, file);
871
+ }
872
+ }
873
+ setAttachments((current) => {
874
+ const unresolved = current.filter((attachment) => attachment.status !== "ready");
875
+ const existingReady = new Map(
876
+ current.flatMap(
877
+ (attachment) => attachment.status === "ready" && attachment.file ? [[attachment.file.id, attachment]] : []
878
+ )
879
+ );
880
+ const restored = [...incoming.values()].map((file) => {
881
+ const existing = existingReady.get(file.id);
882
+ return existing ? {
883
+ ...existing,
884
+ name: file.filename,
885
+ contentType: file.contentType,
886
+ sizeBytes: file.sizeBytes,
887
+ status: "ready",
888
+ file,
889
+ error: void 0
890
+ } : {
891
+ id: `restored:${file.id}`,
892
+ name: file.filename,
893
+ contentType: file.contentType,
894
+ sizeBytes: file.sizeBytes,
895
+ status: "ready",
896
+ file
897
+ // No source File and no object URL: server metadata is the
898
+ // only authority restored across page/device boundaries.
899
+ };
900
+ });
901
+ for (const [fileId, attachment] of existingReady) {
902
+ if (!incoming.has(fileId) && attachment.previewUrl) {
903
+ URL.revokeObjectURL(attachment.previewUrl);
904
+ }
905
+ }
906
+ return [...unresolved, ...restored];
907
+ });
908
+ },
909
+ [workspaceId]
910
+ );
911
+ const remove = useCallback3((id) => {
912
+ sources.current.delete(id);
913
+ setAttachments((current) => {
914
+ const removed = current.find((attachment) => attachment.id === id);
915
+ if (removed?.previewUrl) {
916
+ URL.revokeObjectURL(removed.previewUrl);
917
+ }
918
+ return current.filter((attachment) => attachment.id !== id);
919
+ });
920
+ }, []);
921
+ const removeReadyFiles = useCallback3((fileIds) => {
922
+ const accepted = new Set(fileIds);
923
+ if (accepted.size === 0) return;
924
+ setAttachments(
925
+ (current) => current.filter((attachment) => {
926
+ const removeAccepted = attachment.status === "ready" && attachment.file !== void 0 && accepted.has(attachment.file.id);
927
+ if (removeAccepted) {
928
+ sources.current.delete(attachment.id);
929
+ if (attachment.previewUrl) URL.revokeObjectURL(attachment.previewUrl);
930
+ }
931
+ return !removeAccepted;
932
+ })
933
+ );
934
+ }, []);
935
+ const clear = useCallback3(() => {
936
+ sources.current.clear();
937
+ setAttachments((current) => {
938
+ for (const attachment of current) {
939
+ if (attachment.previewUrl) {
940
+ URL.revokeObjectURL(attachment.previewUrl);
941
+ }
942
+ }
943
+ return [];
944
+ });
945
+ }, []);
946
+ return {
947
+ attachments,
948
+ readyResources: attachments.flatMap(
949
+ (attachment) => attachment.status === "ready" && attachment.file ? [{ kind: "file", fileId: attachment.file.id }] : []
950
+ ),
951
+ uploading: attachments.some((attachment) => attachment.status === "uploading"),
952
+ hasUnresolved: attachments.some((attachment) => attachment.status !== "ready"),
953
+ addFiles,
954
+ addFromPaste,
955
+ restoreReadyFiles,
956
+ retry,
957
+ remove,
958
+ removeReadyFiles,
959
+ clear
960
+ };
961
+ }
962
+
1595
963
  // src/hooks/use-turn-queue.ts
1596
- import { useCallback as useCallback2, useEffect as useEffect2, useLayoutEffect, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
964
+ import { useCallback as useCallback4, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
1597
965
  function isTurnQueueEvent(event) {
1598
966
  return event.type.startsWith("turn.") || event.type.startsWith("session.queue.") || event.type.startsWith("session.control.") || event.type.startsWith("workspace.inference.");
1599
967
  }
@@ -1607,16 +975,16 @@ function useTurnQueue(sessionId, options = {}) {
1607
975
  const { client, workspaceId, workspaceControlEvent, registerSessionReconciler } = useEmbeddedSession(options);
1608
976
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
1609
977
  const targetKey = `${workspaceId}\0${sessionId ?? ""}`;
1610
- const [snapshot, setSnapshot] = useState2(null);
1611
- const [stateTargetKey, setStateTargetKey] = useState2(targetKey);
1612
- const [loading, setLoading] = useState2(enabled);
1613
- const [error, setError] = useState2(null);
1614
- const [mutationError, setMutationError] = useState2(null);
1615
- const [pendingByTurn, setPendingByTurn] = useState2({});
1616
- const pendingRef = useRef2({});
1617
- const readGeneration = useRef2(0);
1618
- const targetKeyRef = useRef2(targetKey);
1619
- const snapshotRef = useRef2(null);
978
+ const [snapshot, setSnapshot] = useState4(null);
979
+ const [stateTargetKey, setStateTargetKey] = useState4(targetKey);
980
+ const [loading, setLoading] = useState4(enabled);
981
+ const [error, setError] = useState4(null);
982
+ const [mutationError, setMutationError] = useState4(null);
983
+ const [pendingByTurn, setPendingByTurn] = useState4({});
984
+ const pendingRef = useRef3({});
985
+ const readGeneration = useRef3(0);
986
+ const targetKeyRef = useRef3(targetKey);
987
+ const snapshotRef = useRef3(null);
1620
988
  useLayoutEffect(() => {
1621
989
  if (targetKeyRef.current === targetKey) return;
1622
990
  targetKeyRef.current = targetKey;
@@ -1630,7 +998,7 @@ function useTurnQueue(sessionId, options = {}) {
1630
998
  setMutationError(null);
1631
999
  setPendingByTurn({});
1632
1000
  }, [enabled, targetKey]);
1633
- const acceptSnapshot = useCallback2(
1001
+ const acceptSnapshot = useCallback4(
1634
1002
  (ownedTargetKey, next) => {
1635
1003
  if (targetKeyRef.current !== ownedTargetKey) return false;
1636
1004
  const current = snapshotRef.current;
@@ -1644,7 +1012,7 @@ function useTurnQueue(sessionId, options = {}) {
1644
1012
  },
1645
1013
  []
1646
1014
  );
1647
- const load = useCallback2(
1015
+ const load = useCallback4(
1648
1016
  async (rejectOnFailure = false) => {
1649
1017
  if (!sessionId) return;
1650
1018
  const ownedTargetKey = `${workspaceId}\0${sessionId}`;
@@ -1675,7 +1043,7 @@ function useTurnQueue(sessionId, options = {}) {
1675
1043
  },
1676
1044
  [client, workspaceId, sessionId, acceptSnapshot]
1677
1045
  );
1678
- useEffect2(() => {
1046
+ useEffect3(() => {
1679
1047
  if (!enabled) {
1680
1048
  setLoading(false);
1681
1049
  return;
@@ -1694,10 +1062,10 @@ function useTurnQueue(sessionId, options = {}) {
1694
1062
  readGeneration.current += 1;
1695
1063
  };
1696
1064
  }, [load, enabled, options.pollIntervalMs]);
1697
- useEffect2(() => {
1065
+ useEffect3(() => {
1698
1066
  if (enabled && workspaceControlEvent) void load();
1699
1067
  }, [enabled, load, workspaceControlEvent]);
1700
- useEffect2(() => {
1068
+ useEffect3(() => {
1701
1069
  if (!sessionId || !enabled) return;
1702
1070
  return registerSessionReconciler(sessionId, "queue", load);
1703
1071
  }, [enabled, load, registerSessionReconciler, sessionId]);
@@ -1714,7 +1082,7 @@ function useTurnQueue(sessionId, options = {}) {
1714
1082
  },
1715
1083
  async () => await load(true)
1716
1084
  );
1717
- const mutate = useCallback2(
1085
+ const mutate = useCallback4(
1718
1086
  async (turnId, kind, command) => {
1719
1087
  const ownedTargetKey = targetKey;
1720
1088
  if (!sessionId || targetKeyRef.current !== ownedTargetKey || pendingRef.current[turnId]) {
@@ -1749,7 +1117,7 @@ function useTurnQueue(sessionId, options = {}) {
1749
1117
  },
1750
1118
  [acceptSnapshot, load, sessionId, targetKey]
1751
1119
  );
1752
- const moveTurn = useCallback2(
1120
+ const moveTurn = useCallback4(
1753
1121
  async (turnId, beforeTurnId) => {
1754
1122
  const result = await mutate(
1755
1123
  turnId,
@@ -1764,7 +1132,7 @@ function useTurnQueue(sessionId, options = {}) {
1764
1132
  },
1765
1133
  [client, mutate, sessionId, workspaceId]
1766
1134
  );
1767
- const editTurn = useCallback2(
1135
+ const editTurn = useCallback4(
1768
1136
  async (turnId, edit) => {
1769
1137
  const result = await mutate(
1770
1138
  turnId,
@@ -1780,7 +1148,7 @@ function useTurnQueue(sessionId, options = {}) {
1780
1148
  },
1781
1149
  [client, mutate, sessionId, workspaceId]
1782
1150
  );
1783
- const steerTurn = useCallback2(
1151
+ const steerTurn = useCallback4(
1784
1152
  async (turnId) => {
1785
1153
  const result = await mutate(
1786
1154
  turnId,
@@ -1795,7 +1163,7 @@ function useTurnQueue(sessionId, options = {}) {
1795
1163
  },
1796
1164
  [client, mutate, sessionId, workspaceId]
1797
1165
  );
1798
- const removeTurn = useCallback2(
1166
+ const removeTurn = useCallback4(
1799
1167
  async (turnId) => {
1800
1168
  const result = await mutate(
1801
1169
  turnId,
@@ -1813,11 +1181,11 @@ function useTurnQueue(sessionId, options = {}) {
1813
1181
  const identityMatches = stateTargetKey === targetKey;
1814
1182
  const visibleSnapshot = identityMatches ? snapshot : null;
1815
1183
  const visiblePendingByTurn = identityMatches ? pendingByTurn : EMPTY_PENDING_BY_TURN;
1816
- const mutating = useMemo2(
1184
+ const mutating = useMemo3(
1817
1185
  () => Object.keys(visiblePendingByTurn).length > 0,
1818
1186
  [visiblePendingByTurn]
1819
1187
  );
1820
- const mutationFor = useCallback2(
1188
+ const mutationFor = useCallback4(
1821
1189
  (turnId) => visiblePendingByTurn[turnId] ?? null,
1822
1190
  [visiblePendingByTurn]
1823
1191
  );
@@ -1837,7 +1205,7 @@ function useTurnQueue(sessionId, options = {}) {
1837
1205
  mutationFor,
1838
1206
  mutating,
1839
1207
  mutationError: identityMatches ? mutationError : null,
1840
- clearMutationError: useCallback2(() => {
1208
+ clearMutationError: useCallback4(() => {
1841
1209
  if (targetKeyRef.current === targetKey) setMutationError(null);
1842
1210
  }, [targetKey])
1843
1211
  };
@@ -1849,12 +1217,154 @@ function asError(cause) {
1849
1217
  return cause instanceof Error ? cause : new Error(String(cause));
1850
1218
  }
1851
1219
 
1220
+ // src/hooks/use-goal.ts
1221
+ import { OpenGeniApiError } from "@opengeni/sdk";
1222
+ import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
1223
+ function isGoalEvent(event) {
1224
+ return event.type.startsWith("goal.");
1225
+ }
1226
+ function isGoalRefreshEvent(event) {
1227
+ const type = event.type;
1228
+ return isGoalEvent(event) || type.startsWith("turn.") || type.startsWith("session.") || type.startsWith("system.update.") || type.startsWith("workspace.inference.") || type.startsWith("user.");
1229
+ }
1230
+ function useGoal(sessionId, options = {}) {
1231
+ const { client, workspaceId } = useOpenGeni(options);
1232
+ const enabled = (options.enabled ?? true) && Boolean(sessionId);
1233
+ const sharedEvents = options.events;
1234
+ const sharedFeed = sharedEvents !== void 0;
1235
+ const [goal, setGoal] = useState5(null);
1236
+ const [loading, setLoading] = useState5(enabled);
1237
+ const [error, setError] = useState5(null);
1238
+ const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
1239
+ const generation = useRef4(0);
1240
+ const targetKeyRef = useRef4(null);
1241
+ const load = useCallback5(async () => {
1242
+ if (!sessionId) {
1243
+ return;
1244
+ }
1245
+ const ticket = ++generation.current;
1246
+ try {
1247
+ const fetched = await client.getGoal(workspaceId, sessionId);
1248
+ if (ticket === generation.current) {
1249
+ setGoal(fetched);
1250
+ setError(null);
1251
+ setLoading(false);
1252
+ }
1253
+ } catch (cause) {
1254
+ if (ticket !== generation.current) {
1255
+ return;
1256
+ }
1257
+ if (cause instanceof OpenGeniApiError && cause.status === 404) {
1258
+ setGoal(null);
1259
+ setError(null);
1260
+ } else {
1261
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
1262
+ }
1263
+ setLoading(false);
1264
+ }
1265
+ }, [client, workspaceId, sessionId]);
1266
+ useEffect4(() => {
1267
+ const targetKey = `${workspaceId} ${sessionId ?? ""}`;
1268
+ if (targetKeyRef.current !== targetKey) {
1269
+ targetKeyRef.current = targetKey;
1270
+ setGoal(null);
1271
+ setError(null);
1272
+ }
1273
+ if (!enabled) {
1274
+ setLoading(false);
1275
+ return;
1276
+ }
1277
+ if (sharedFeed) {
1278
+ setLoading(false);
1279
+ return () => {
1280
+ generation.current += 1;
1281
+ };
1282
+ }
1283
+ setLoading(true);
1284
+ void load();
1285
+ const pollIntervalMs = options.pollIntervalMs;
1286
+ if (pollIntervalMs === void 0 || pollIntervalMs <= 0) {
1287
+ return () => {
1288
+ generation.current += 1;
1289
+ };
1290
+ }
1291
+ const timer = setInterval(() => void load(), pollIntervalMs);
1292
+ return () => {
1293
+ clearInterval(timer);
1294
+ generation.current += 1;
1295
+ };
1296
+ }, [load, enabled, workspaceId, sessionId, options.pollIntervalMs, sharedFeed]);
1297
+ const scheduleRefresh = useDebouncedCallback(() => void load());
1298
+ useSessionEventTrigger(client, workspaceId, sessionId, isGoalRefreshEvent, scheduleRefresh, {
1299
+ enabled,
1300
+ ...sharedEvents !== void 0 ? { events: sharedEvents } : {}
1301
+ });
1302
+ const pause = useCallback5(
1303
+ async (rationale) => {
1304
+ if (!sessionId) {
1305
+ return null;
1306
+ }
1307
+ const result = await run(
1308
+ () => client.updateGoal(workspaceId, sessionId, {
1309
+ status: "paused",
1310
+ ...rationale !== void 0 ? { rationale } : {}
1311
+ })
1312
+ );
1313
+ if (result) {
1314
+ setGoal(result);
1315
+ }
1316
+ return result;
1317
+ },
1318
+ [client, workspaceId, sessionId, run]
1319
+ );
1320
+ const resume = useCallback5(async () => {
1321
+ if (!sessionId) {
1322
+ return null;
1323
+ }
1324
+ const result = await run(() => client.updateGoal(workspaceId, sessionId, { status: "active" }));
1325
+ if (result) {
1326
+ setGoal(result);
1327
+ }
1328
+ return result;
1329
+ }, [client, workspaceId, sessionId, run]);
1330
+ const clearGoal = useCallback5(async () => {
1331
+ if (!sessionId) {
1332
+ return;
1333
+ }
1334
+ const ok = await run(async () => {
1335
+ await client.deleteGoal(workspaceId, sessionId);
1336
+ return true;
1337
+ });
1338
+ if (ok) {
1339
+ generation.current += 1;
1340
+ setGoal(null);
1341
+ setError(null);
1342
+ }
1343
+ }, [client, workspaceId, sessionId, run]);
1344
+ return {
1345
+ goal,
1346
+ isActive: goal?.status === "active",
1347
+ isPaused: goal?.status === "paused",
1348
+ isCompleted: goal?.status === "completed",
1349
+ loading,
1350
+ error,
1351
+ refresh: load,
1352
+ pause,
1353
+ resume,
1354
+ clearGoal,
1355
+ deleteGoal: clearGoal,
1356
+ updating: mutating,
1357
+ mutationError,
1358
+ clearMutationError
1359
+ };
1360
+ }
1361
+
1852
1362
  // src/hooks/use-session-control.ts
1853
- import { useCallback as useCallback3, useLayoutEffect as useLayoutEffect2, useRef as useRef3 } from "react";
1363
+ import { useCallback as useCallback6, useLayoutEffect as useLayoutEffect2, useRef as useRef5 } from "react";
1854
1364
  function useSessionControl(sessionId, options = {}) {
1855
1365
  const { client, workspaceId } = useEmbeddedSession(options);
1856
1366
  const approvalTargetKey = `${workspaceId}\0${sessionId ?? ""}`;
1857
- const pendingApproval = useRef3(null);
1367
+ const pendingApproval = useRef5(null);
1858
1368
  useLayoutEffect2(() => {
1859
1369
  if (pendingApproval.current?.targetKey !== approvalTargetKey) {
1860
1370
  pendingApproval.current = null;
@@ -1872,7 +1382,7 @@ function useSessionControl(sessionId, options = {}) {
1872
1382
  mutationError: approvalError,
1873
1383
  clearMutationError: clearApprovalError
1874
1384
  } = useMutationRunner(approvalTargetKey);
1875
- const pause = useCallback3(
1385
+ const pause = useCallback6(
1876
1386
  async (reason) => {
1877
1387
  if (!sessionId) {
1878
1388
  return null;
@@ -1883,7 +1393,7 @@ function useSessionControl(sessionId, options = {}) {
1883
1393
  },
1884
1394
  [client, workspaceId, sessionId, runControl]
1885
1395
  );
1886
- const resume = useCallback3(
1396
+ const resume = useCallback6(
1887
1397
  async (reason) => {
1888
1398
  if (!sessionId) return null;
1889
1399
  return await runControl(
@@ -1892,7 +1402,7 @@ function useSessionControl(sessionId, options = {}) {
1892
1402
  },
1893
1403
  [client, workspaceId, sessionId, runControl]
1894
1404
  );
1895
- const decide = useCallback3(
1405
+ const decide = useCallback6(
1896
1406
  async (approvalId, decision, message) => {
1897
1407
  if (!sessionId) {
1898
1408
  return null;
@@ -1921,16 +1431,16 @@ function useSessionControl(sessionId, options = {}) {
1921
1431
  },
1922
1432
  [approvalTargetKey, client, workspaceId, sessionId, runApproval]
1923
1433
  );
1924
- const approve = useCallback3(
1434
+ const approve = useCallback6(
1925
1435
  async (approvalId, message) => await decide(approvalId, "approve", message),
1926
1436
  [decide]
1927
1437
  );
1928
- const reject = useCallback3(
1438
+ const reject = useCallback6(
1929
1439
  async (approvalId, message) => await decide(approvalId, "reject", message),
1930
1440
  [decide]
1931
1441
  );
1932
1442
  const error = approvalError ?? controlError;
1933
- const clearError = useCallback3(() => {
1443
+ const clearError = useCallback6(() => {
1934
1444
  clearControlError();
1935
1445
  clearApprovalError();
1936
1446
  }, [clearControlError, clearApprovalError]);
@@ -1947,7 +1457,7 @@ function useSessionControl(sessionId, options = {}) {
1947
1457
  }
1948
1458
 
1949
1459
  // src/hooks/use-session-mcp-approval-policy.ts
1950
- import { useCallback as useCallback4, useEffect as useEffect3, useLayoutEffect as useLayoutEffect3, useMemo as useMemo3, useRef as useRef4, useState as useState3 } from "react";
1460
+ import { useCallback as useCallback7, useEffect as useEffect5, useLayoutEffect as useLayoutEffect3, useMemo as useMemo4, useRef as useRef6, useState as useState6 } from "react";
1951
1461
  function isSessionMcpApprovalPolicyEvent(event, serverId) {
1952
1462
  if (event.type !== "session.mcp.approval_policy.updated") {
1953
1463
  return false;
@@ -1961,16 +1471,16 @@ function isSessionMcpApprovalPolicyEvent(event, serverId) {
1961
1471
  function useSessionMcpApprovalPolicy(sessionId, serverId, options = {}) {
1962
1472
  const { client, workspaceId } = useEmbeddedSessionMcpApprovalPolicy(options);
1963
1473
  const enabled = (options.enabled ?? true) && Boolean(sessionId && serverId);
1964
- const [override, setOverride] = useState3(null);
1474
+ const [override, setOverride] = useState6(null);
1965
1475
  const targetKey = `${workspaceId}\0${sessionId ?? ""}\0${serverId ?? ""}`;
1966
- const authoritativeGeneration = useRef4(0);
1967
- const mutationIdentity = useMemo3(() => ({ client, targetKey }), [client, targetKey]);
1476
+ const authoritativeGeneration = useRef6(0);
1477
+ const mutationIdentity = useMemo4(() => ({ client, targetKey }), [client, targetKey]);
1968
1478
  const { run, mutating, mutationError, clearMutationError } = useMutationRunner(mutationIdentity);
1969
1479
  useLayoutEffect3(() => {
1970
1480
  authoritativeGeneration.current += 1;
1971
1481
  setOverride(null);
1972
1482
  }, [client, targetKey]);
1973
- const load = useCallback4(async () => {
1483
+ const load = useCallback7(async () => {
1974
1484
  const generation = authoritativeGeneration.current;
1975
1485
  if (!sessionId || !serverId) {
1976
1486
  return { targetKey, generation, server: null };
@@ -1991,13 +1501,13 @@ function useSessionMcpApprovalPolicy(sessionId, serverId, options = {}) {
1991
1501
  enabled,
1992
1502
  pollIntervalMs: options.pollIntervalMs
1993
1503
  });
1994
- useEffect3(() => {
1504
+ useEffect5(() => {
1995
1505
  if (data?.targetKey === targetKey && data.generation === authoritativeGeneration.current) {
1996
1506
  setOverride(null);
1997
1507
  }
1998
1508
  }, [data, targetKey]);
1999
1509
  const scheduleRefresh = useDebouncedCallback(() => void refresh());
2000
- const matchesPolicyEvent = useCallback4(
1510
+ const matchesPolicyEvent = useCallback7(
2001
1511
  (event) => isSessionMcpApprovalPolicyEvent(event, serverId ?? void 0),
2002
1512
  [serverId]
2003
1513
  );
@@ -2013,7 +1523,7 @@ function useSessionMcpApprovalPolicy(sessionId, serverId, options = {}) {
2013
1523
  },
2014
1524
  async () => await refresh()
2015
1525
  );
2016
- const update = useCallback4(
1526
+ const update = useCallback7(
2017
1527
  async (policy) => {
2018
1528
  if (!sessionId || !serverId) {
2019
1529
  return null;
@@ -2047,6 +1557,91 @@ function useSessionMcpApprovalPolicy(sessionId, serverId, options = {}) {
2047
1557
  };
2048
1558
  }
2049
1559
 
1560
+ // src/hooks/use-session-lineage.ts
1561
+ import { useCallback as useCallback8, useEffect as useEffect6, useRef as useRef7 } from "react";
1562
+ function isLineageRefreshEvent(event) {
1563
+ if (event.type === "session.status.changed" || event.type === "session.created") {
1564
+ return true;
1565
+ }
1566
+ if (event.type === "agent.toolCall.output") {
1567
+ const payload = event.payload;
1568
+ const name = typeof payload?.name === "string" ? payload.name : typeof payload?.toolName === "string" ? payload.toolName : "";
1569
+ return name === "session_create" || name.endsWith("__session_create");
1570
+ }
1571
+ return false;
1572
+ }
1573
+ function isSessionCreateToolCallCreated(event) {
1574
+ if (event.type !== "agent.toolCall.created") {
1575
+ return false;
1576
+ }
1577
+ const payload = event.payload;
1578
+ const name = typeof payload?.name === "string" ? payload.name : typeof payload?.toolName === "string" ? payload.toolName : "";
1579
+ return name === "session_create" || name.endsWith("__session_create");
1580
+ }
1581
+ function useSessionLineage(sessionId, options = {}) {
1582
+ const { client, workspaceId, workspaceControlEvent, registerSessionReconciler } = useOpenGeni(options);
1583
+ const enabled = (options.enabled ?? true) && Boolean(sessionId);
1584
+ const load = useCallback8(
1585
+ async () => sessionId ? await client.getSessionLineage(workspaceId, sessionId) : { ancestors: [], children: [], truncated: false },
1586
+ [client, workspaceId, sessionId]
1587
+ );
1588
+ const state = usePolledValue(load, { pollIntervalMs: options.pollIntervalMs, enabled });
1589
+ const refresh = state.refresh;
1590
+ useEffect6(() => {
1591
+ if (enabled && workspaceControlEvent) void refresh();
1592
+ }, [enabled, refresh, workspaceControlEvent]);
1593
+ useEffect6(() => {
1594
+ if (!sessionId || !enabled) return;
1595
+ return registerSessionReconciler(sessionId, "lineage", refresh);
1596
+ }, [enabled, refresh, registerSessionReconciler, sessionId]);
1597
+ const refreshSoon = useDebouncedCallback(() => void refresh(), 150);
1598
+ const delayedChildRefreshRef = useRef7(null);
1599
+ useEffect6(() => {
1600
+ return () => {
1601
+ if (delayedChildRefreshRef.current !== null) {
1602
+ clearTimeout(delayedChildRefreshRef.current);
1603
+ delayedChildRefreshRef.current = null;
1604
+ }
1605
+ };
1606
+ }, [sessionId, workspaceId]);
1607
+ const refreshAfterChildCreate = useCallback8(() => {
1608
+ void refresh();
1609
+ if (delayedChildRefreshRef.current !== null) {
1610
+ clearTimeout(delayedChildRefreshRef.current);
1611
+ }
1612
+ delayedChildRefreshRef.current = setTimeout(() => {
1613
+ delayedChildRefreshRef.current = null;
1614
+ void refresh();
1615
+ }, 2500);
1616
+ }, [refresh]);
1617
+ useSessionEventTrigger(
1618
+ client,
1619
+ workspaceId,
1620
+ sessionId,
1621
+ isLineageRefreshEvent,
1622
+ refreshSoon,
1623
+ // SHARED-FEED ONLY: without a caller-provided events log the trigger would
1624
+ // open its OWN streamEvents tail — a second live SSE connection next to the
1625
+ // session route's useSessionEvents. A caller with no feed opts into polling
1626
+ // (pollIntervalMs), never a duplicate stream.
1627
+ { events: options.events, enabled: enabled && options.events !== void 0 }
1628
+ );
1629
+ useSessionEventTrigger(
1630
+ client,
1631
+ workspaceId,
1632
+ sessionId,
1633
+ isSessionCreateToolCallCreated,
1634
+ refreshAfterChildCreate,
1635
+ { events: options.events, enabled: enabled && options.events !== void 0 }
1636
+ );
1637
+ return {
1638
+ lineage: state.data,
1639
+ loading: state.loading,
1640
+ error: state.error,
1641
+ refresh
1642
+ };
1643
+ }
1644
+
2050
1645
  // src/approvals.ts
2051
1646
  function approvalsFromRequiresAction(payload) {
2052
1647
  const approvals = payload && typeof payload === "object" ? payload.approvals : void 0;
@@ -2141,14 +1736,14 @@ function isRecord(value) {
2141
1736
  }
2142
1737
 
2143
1738
  // src/hooks/use-human-input.ts
2144
- import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef5, useState as useState4 } from "react";
1739
+ import { useCallback as useCallback9, useEffect as useEffect7, useRef as useRef8, useState as useState7 } from "react";
2145
1740
  function isHumanInputEvent(event) {
2146
1741
  return event.type === "session.humanInput.requested" || event.type === "user.humanInputResponse" || event.type === "turn.completed" || event.type === "turn.failed" || event.type === "turn.cancelled";
2147
1742
  }
2148
1743
  function useHumanInputRequests(sessionId, options = {}) {
2149
1744
  const { client, workspaceId, registerSessionReconciler } = useEmbeddedHumanInputSession(options);
2150
1745
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
2151
- const load = useCallback5(
1746
+ const load = useCallback9(
2152
1747
  async () => sessionId ? await client.listHumanInputRequests(workspaceId, sessionId, { status: "pending" }) : [],
2153
1748
  [client, sessionId, workspaceId]
2154
1749
  );
@@ -2157,13 +1752,13 @@ function useHumanInputRequests(sessionId, options = {}) {
2157
1752
  pollIntervalMs: options.pollIntervalMs
2158
1753
  });
2159
1754
  const mutation = useMutationRunner(`${workspaceId}\0${sessionId ?? ""}`);
2160
- const [respondingRequestId, setRespondingRequestId] = useState4(null);
2161
- const respondingRequestRef = useRef5(null);
2162
- useEffect4(() => {
1755
+ const [respondingRequestId, setRespondingRequestId] = useState7(null);
1756
+ const respondingRequestRef = useRef8(null);
1757
+ useEffect7(() => {
2163
1758
  respondingRequestRef.current = null;
2164
1759
  setRespondingRequestId(null);
2165
1760
  }, [sessionId, workspaceId]);
2166
- useEffect4(() => {
1761
+ useEffect7(() => {
2167
1762
  if (!sessionId || !enabled) return;
2168
1763
  return registerSessionReconciler(sessionId, "human-input", state.refresh);
2169
1764
  }, [enabled, registerSessionReconciler, sessionId, state.refresh]);
@@ -2172,7 +1767,7 @@ function useHumanInputRequests(sessionId, options = {}) {
2172
1767
  enabled,
2173
1768
  ...options.events !== void 0 ? { events: options.events } : {}
2174
1769
  });
2175
- const respond = useCallback5(
1770
+ const respond = useCallback9(
2176
1771
  async (requestId, response) => {
2177
1772
  if (!sessionId || respondingRequestRef.current !== null) return null;
2178
1773
  respondingRequestRef.current = requestId;
@@ -2209,12 +1804,8 @@ function useHumanInputRequests(sessionId, options = {}) {
2209
1804
  }
2210
1805
 
2211
1806
  export {
2212
- buildTimeline,
2213
- creditExhaustedFromEvents,
2214
- sessionStatusFromEvents,
2215
- groupTimeline,
2216
- extractSessionRef,
2217
- toolDisplayName,
1807
+ isTitleEvent,
1808
+ useSession,
2218
1809
  SESSION_EVENT_BROWSER_MAX_BYTES,
2219
1810
  SESSION_EVENT_BROWSER_MAX_COUNT,
2220
1811
  SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES,
@@ -2222,11 +1813,16 @@ export {
2222
1813
  SESSION_EVENT_BROWSER_PENDING_MAX_COUNT,
2223
1814
  useSessionEvents,
2224
1815
  boundBrowserSessionEventWindow,
1816
+ useFileAttachments,
2225
1817
  isTurnQueueEvent,
2226
1818
  useTurnQueue,
1819
+ isGoalEvent,
1820
+ useGoal,
2227
1821
  useSessionControl,
2228
1822
  isSessionMcpApprovalPolicyEvent,
2229
1823
  useSessionMcpApprovalPolicy,
1824
+ isLineageRefreshEvent,
1825
+ useSessionLineage,
2230
1826
  approvalsFromRequiresAction,
2231
1827
  projectPendingApprovals,
2232
1828
  humanInputRequestFromEvent,
@@ -2234,4 +1830,4 @@ export {
2234
1830
  isHumanInputEvent,
2235
1831
  useHumanInputRequests
2236
1832
  };
2237
- //# sourceMappingURL=chunk-UAHPKLB6.js.map
1833
+ //# sourceMappingURL=chunk-MP6237JB.js.map