@opengeni/react 0.25.0 → 0.26.0

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 (46) hide show
  1. package/dist/{chunk-AVPU5PMC.js → chunk-F4CFWKVI.js} +332 -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-SHOFILHJ.js → chunk-MP6237JB.js} +570 -984
  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-RDPDU4TA.js → chunk-TMH6HZWF.js} +3 -3
  14. package/dist/{chunk-6XUS5VFM.js → chunk-YU5PGUK7.js} +6 -4
  15. package/dist/{chunk-6XUS5VFM.js.map → chunk-YU5PGUK7.js.map} +1 -1
  16. package/dist/{composer-BXb0Q1HF.d.ts → composer-DoC4veX1.d.ts} +4 -75
  17. package/dist/composer.d.ts +2 -1
  18. package/dist/composer.js +4 -3
  19. package/dist/index.d.ts +14 -259
  20. package/dist/index.js +1016 -5140
  21. package/dist/index.js.map +1 -1
  22. package/dist/machines.js +3 -2
  23. package/dist/session-Du_FsrZ1.d.ts +264 -0
  24. package/dist/session-ui-BqQDH7YV.d.ts +183 -0
  25. package/dist/session-ui.d.ts +7 -0
  26. package/dist/session-ui.js +17 -0
  27. package/dist/session-ui.js.map +1 -0
  28. package/dist/session.d.ts +3 -1
  29. package/dist/session.js +26 -9
  30. package/dist/use-file-attachments-C0kpHrs9.d.ts +76 -0
  31. package/dist/{session-BEvtFWhe.d.ts → use-turn-queue-3rkJwjFv.d.ts} +3 -185
  32. package/package.json +6 -2
  33. package/src/components/message-timeline.tsx +92 -52
  34. package/src/hooks/use-composer.ts +456 -69
  35. package/src/hooks/use-file-attachments.ts +1 -1
  36. package/src/hooks/use-goal.ts +1 -1
  37. package/src/hooks/use-session-events.ts +5 -0
  38. package/src/hooks/use-session-lineage.ts +1 -1
  39. package/src/hooks/use-session.ts +9 -6
  40. package/src/session-ui.ts +13 -0
  41. package/src/session.ts +15 -0
  42. package/src/timeline/activity-rail.tsx +1 -1
  43. package/dist/chunk-AVPU5PMC.js.map +0 -1
  44. package/dist/chunk-I3BJZIG5.js.map +0 -1
  45. package/dist/chunk-SHOFILHJ.js.map +0 -1
  46. /package/dist/{chunk-RDPDU4TA.js.map → chunk-TMH6HZWF.js.map} +0 -0
@@ -1,940 +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 { default: fleetDecisionItem } = await import("./fleet-decision-projection-SLNZMS42.js");
17
- function toolDisplayName(name) {
18
- const boundary = name.indexOf("__");
19
- const toolPart = boundary >= 0 ? name.slice(boundary + 2) : name;
20
- return toolPart.replace(/[_-]+/g, " ").trim();
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
21
  }
22
- var WORKER_SPAWN_TOOL = "session_create";
23
- var WORKER_MESSAGE_TOOL = "session_send_message";
24
- function buildTimeline(events) {
25
- const items = [];
26
- const prescan = prescanTurnAnchors(events);
27
- const ordered = orderTimelineEvents(events, prescan);
28
- const last = () => items[items.length - 1];
29
- const closeStreamingTail = () => {
30
- const open = last();
31
- if ((open?.kind === "agent-message" || open?.kind === "reasoning") && open.streaming) {
32
- open.streaming = false;
33
- }
34
- };
35
- const finalizeOpen = (turnId, disposition = "complete") => {
36
- for (const item of items) {
37
- if (turnId !== void 0 && "turnId" in item && item.turnId && turnId && item.turnId !== turnId) {
38
- continue;
39
- }
40
- if ((item.kind === "agent-message" || item.kind === "reasoning") && item.streaming) {
41
- item.streaming = false;
42
- }
43
- if ((item.kind === "tool-call" || item.kind === "worker") && item.status === "running") {
44
- item.status = disposition;
45
- }
46
- if (item.kind === "sandbox" && item.status === "running") {
47
- item.status = disposition;
48
- }
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;
49
30
  }
50
- };
51
- for (const event of ordered) {
52
- const payload = asRecord(event.payload);
53
- const turnId = event.turnId ?? null;
54
- switch (event.type) {
55
- case "user.message": {
56
- closeStreamingTail();
57
- const childCompletion = workerCompletionPayload(payload.childCompletion);
58
- if (childCompletion) {
59
- items.push({
60
- kind: "worker-completion",
61
- id: event.id,
62
- turnId,
63
- occurredAt: event.occurredAt,
64
- childSessionId: childCompletion.childSessionId,
65
- childStatus: childCompletion.childStatus,
66
- goalStatus: childCompletion.goalStatus,
67
- goalText: childCompletion.goalText,
68
- evidence: childCompletion.evidence,
69
- pausedReason: childCompletion.pausedReason,
70
- text: typeof payload.text === "string" ? payload.text : ""
71
- });
72
- break;
73
- }
74
- items.push({
75
- kind: "user-message",
76
- id: event.id,
77
- text: typeof payload.text === "string" ? payload.text : "",
78
- resources: resourceRefs(payload.resources),
79
- tools: toolRefs(payload.tools),
80
- occurredAt: event.occurredAt
81
- });
82
- break;
83
- }
84
- case "agent.message.delta": {
85
- const text = typeof payload.text === "string" ? payload.text : "";
86
- if (!text) {
87
- break;
88
- }
89
- const open = last();
90
- if (open?.kind === "agent-message" && open.streaming && open.turnId === turnId) {
91
- open.text += text;
92
- break;
93
- }
94
- closeStreamingTail();
95
- items.push({
96
- kind: "agent-message",
97
- id: event.id,
98
- turnId,
99
- text,
100
- streaming: true,
101
- occurredAt: event.occurredAt
102
- });
103
- break;
104
- }
105
- case "agent.message.completed": {
106
- const text = typeof payload.text === "string" ? payload.text : "";
107
- const open = [...items].reverse().find(
108
- (item) => item.kind === "agent-message" && item.turnId === turnId
109
- );
110
- if (open && (open.streaming || !open.text || text === open.text || text.startsWith(open.text))) {
111
- if (!open.text || text && text.startsWith(open.text)) {
112
- open.text = text || open.text;
113
- }
114
- open.streaming = false;
115
- break;
116
- }
117
- if (text) {
118
- items.push({
119
- kind: "agent-message",
120
- id: event.id,
121
- turnId,
122
- text,
123
- streaming: false,
124
- occurredAt: event.occurredAt
125
- });
126
- }
127
- break;
128
- }
129
- case "agent.reasoning.delta": {
130
- const text = reasoningText(event.payload);
131
- if (!text) {
132
- break;
133
- }
134
- const open = last();
135
- if (open?.kind === "reasoning" && open.streaming && open.turnId === turnId) {
136
- open.text += text;
137
- break;
138
- }
139
- closeStreamingTail();
140
- items.push({
141
- kind: "reasoning",
142
- id: event.id,
143
- turnId,
144
- text,
145
- streaming: true,
146
- occurredAt: event.occurredAt
147
- });
148
- break;
149
- }
150
- case "agent.toolCall.created": {
151
- const name = typeof payload.name === "string" ? payload.name : "tool";
152
- const callId = typeof payload.id === "string" ? payload.id : null;
153
- const args = payload.arguments ?? null;
154
- closeStreamingTail();
155
- if (name === WORKER_SPAWN_TOOL || name === WORKER_MESSAGE_TOOL) {
156
- items.push({
157
- kind: "worker",
158
- id: event.id,
159
- turnId,
160
- callId,
161
- action: name === WORKER_SPAWN_TOOL ? "spawn" : "message",
162
- prompt: workerPrompt(args),
163
- workerSessionId: extractSessionRef(args),
164
- status: "running",
165
- occurredAt: event.occurredAt
166
- });
167
- break;
168
- }
169
- items.push({
170
- kind: "tool-call",
171
- id: event.id,
172
- turnId,
173
- callId,
174
- name,
175
- arguments: args,
176
- output: void 0,
177
- // The provider-native item drives the per-tool renderers (apply_patch
178
- // operation, computer_call action, web_search providerData, …).
179
- raw: payload.raw,
180
- status: "running",
181
- occurredAt: event.occurredAt
182
- });
183
- break;
184
- }
185
- case "agent.toolCall.output": {
186
- const callId = typeof payload.id === "string" ? payload.id : null;
187
- const target = findOpenCall(items, callId);
188
- if (!target) {
189
- break;
190
- }
191
- if (target.kind === "worker") {
192
- target.status = isErrorOutput(payload) ? "failed" : "complete";
193
- target.workerSessionId = target.workerSessionId ?? extractSessionRef(payload.output);
194
- break;
195
- }
196
- target.status = isErrorOutput(payload) ? "failed" : "complete";
197
- target.output = payload.output;
198
- break;
199
- }
200
- case "sandbox.operation.started":
201
- case "sandbox.operation.completed":
202
- case "sandbox.operation.failed": {
203
- const name = typeof payload.name === "string" ? payload.name : "sandbox";
204
- const status = event.type.endsWith(".failed") ? "failed" : event.type.endsWith(".completed") ? "complete" : "running";
205
- if ((name === "repository-clone" || name === "file-resource-download") && status !== "failed") {
206
- break;
207
- }
208
- const existing = findOpenSandbox(items, name);
209
- if (existing && status !== "running") {
210
- existing.status = status;
211
- if (payload.origin === "created" || payload.origin === "restored" || payload.origin === "resumed") {
212
- existing.origin = payload.origin;
213
- }
214
- const message = failureMessage(payload);
215
- if (message) {
216
- existing.output = existing.output ? `${existing.output}
217
- ${message}` : message;
218
- }
219
- break;
220
- }
221
- if (!existing) {
222
- closeStreamingTail();
223
- items.push({
224
- kind: "sandbox",
225
- id: event.id,
226
- turnId,
227
- name,
228
- command: typeof payload.command === "string" ? payload.command : null,
229
- output: failureMessage(payload) ?? "",
230
- origin: payload.origin === "created" || payload.origin === "restored" || payload.origin === "resumed" ? payload.origin : null,
231
- status,
232
- occurredAt: event.occurredAt
233
- });
234
- }
235
- break;
236
- }
237
- case "sandbox.command.output.delta": {
238
- const text = typeof payload.chunk === "string" ? payload.chunk : typeof payload.text === "string" ? payload.text : typeof payload.output === "string" ? payload.output : "";
239
- if (!text) {
240
- break;
241
- }
242
- const open = (typeof payload.name === "string" ? findOpenSandbox(items, payload.name) : void 0) ?? [...items].reverse().find(
243
- (item) => item.kind === "sandbox" && item.status === "running"
244
- );
245
- if (open) {
246
- open.output += text;
247
- }
248
- break;
249
- }
250
- case "session.status.changed": {
251
- const status = payload.status;
252
- if (!isSessionStatus(status)) {
253
- break;
254
- }
255
- if (!ATTENTION_STATUSES.has(status)) {
256
- break;
257
- }
258
- const previous = [...items].reverse().find((item) => item.kind === "session-status");
259
- if (previous?.status === status) {
260
- break;
261
- }
262
- items.push({ kind: "session-status", id: event.id, status, occurredAt: event.occurredAt });
263
- break;
264
- }
265
- case "session.requiresAction": {
266
- finalizeOpen(turnId);
267
- items.push({
268
- kind: "notice",
269
- id: event.id,
270
- tone: "waiting",
271
- text: "Approval needed \u2014 the turn is paused until someone decides.",
272
- occurredAt: event.occurredAt
273
- });
274
- break;
275
- }
276
- case "session.context.compacted": {
277
- closeStreamingTail();
278
- const before = typeof payload.estimatedTokensBefore === "number" ? Math.round(payload.estimatedTokensBefore).toLocaleString("en-US") : null;
279
- const after = typeof payload.estimatedTokensAfter === "number" ? Math.round(payload.estimatedTokensAfter).toLocaleString("en-US") : null;
280
- items.push({
281
- kind: "notice",
282
- id: event.id,
283
- tone: "waiting",
284
- text: before && after ? `Active conversation history compacted from approximately ${before} to ${after} tokens.` : "Context compacted so the turn could continue.",
285
- occurredAt: event.occurredAt
286
- });
287
- break;
288
- }
289
- case "session.context.compaction.skipped": {
290
- closeStreamingTail();
291
- const reason = typeof payload.reason === "string" ? payload.reason : null;
292
- items.push({
293
- kind: "notice",
294
- id: event.id,
295
- tone: reason === "summarization_failed" ? "failed" : "waiting",
296
- 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.",
297
- occurredAt: event.occurredAt
298
- });
299
- break;
300
- }
301
- case "turn.recovery.requested": {
302
- break;
303
- }
304
- case "turn.event.rejected_late": {
305
- break;
306
- }
307
- case "tool.auth_needed":
308
- case "credential.auth_needed": {
309
- closeStreamingTail();
310
- items.push({
311
- kind: "auth-needed",
312
- id: event.id,
313
- turnId,
314
- providerDomain: typeof payload.providerDomain === "string" ? payload.providerDomain : "",
315
- connectionId: typeof payload.connectionId === "string" ? payload.connectionId : null,
316
- reason: authNeededReason(payload.reason),
317
- scopes: stringList(payload.scopes),
318
- resource: typeof payload.resource === "string" ? payload.resource : null,
319
- toolName: typeof payload.toolName === "string" ? payload.toolName : null,
320
- authorizationUrl: typeof payload.authorizationUrl === "string" ? payload.authorizationUrl : null,
321
- occurredAt: event.occurredAt
322
- });
323
- break;
324
- }
325
- case "turn.completed": {
326
- if (payload.maintenance === "context_compaction") {
327
- finalizeOpen(turnId);
328
- break;
329
- }
330
- if (isCreditExhaustionPayload(payload)) {
331
- finalizeOpen(turnId);
332
- items.push(turnEndItem(event, "failed", CREDIT_EXHAUSTION_MESSAGE));
333
- items.push({
334
- kind: "notice",
335
- id: event.id,
336
- tone: "failed",
337
- text: CREDIT_EXHAUSTION_MESSAGE,
338
- occurredAt: event.occurredAt
339
- });
340
- break;
341
- }
342
- finalizeOpen(turnId);
343
- items.push(turnEndItem(event, "complete", null));
344
- break;
345
- }
346
- case "turn.failed": {
347
- const hadActivity = hasTurnActivity(items, turnId);
348
- const failureText = isCreditExhaustionPayload(payload) ? CREDIT_EXHAUSTION_MESSAGE : failureMessage(payload);
349
- finalizeOpen(turnId, "cancelled");
350
- items.push(turnEndItem(event, "failed", failureText));
351
- if (!hadActivity) {
352
- items.push({
353
- kind: "notice",
354
- id: event.id,
355
- tone: "failed",
356
- text: failureText ?? "The turn failed.",
357
- occurredAt: event.occurredAt
358
- });
359
- }
360
- break;
361
- }
362
- case "turn.cancelled": {
363
- if (turnId && !prescan.startedTurnIds.has(turnId)) {
364
- break;
365
- }
366
- const hadActivity = hasTurnActivity(items, turnId);
367
- finalizeOpen(turnId, "cancelled");
368
- items.push(turnEndItem(event, "cancelled", null));
369
- if (!hadActivity) {
370
- items.push({
371
- kind: "notice",
372
- id: event.id,
373
- tone: "cancelled",
374
- text: "Interrupted.",
375
- occurredAt: event.occurredAt
376
- });
377
- }
378
- break;
379
- }
380
- case "memory.saved":
381
- case "memory.corrected": {
382
- const memory = memoryItem(event.id, event.type, turnId, payload, event.occurredAt);
383
- if (memory) {
384
- closeStreamingTail();
385
- items.push(memory);
386
- }
387
- 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;
388
57
  }
389
- case "codex.fleet.decision": {
390
- const decision = fleetDecisionItem(event, payload);
391
- if (decision) {
392
- closeStreamingTail();
393
- items.push(decision);
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;
394
63
  }
395
- break;
396
- }
397
- case "goal.set":
398
- case "goal.updated":
399
- case "goal.completed":
400
- case "goal.paused":
401
- case "goal.resumed":
402
- case "goal.cleared":
403
- case "goal.continuation": {
404
- items.push({
405
- kind: "goal",
406
- id: event.id,
407
- action: event.type.slice("goal.".length),
408
- text: goalText(payload),
409
- occurredAt: event.occurredAt
410
- });
411
- break;
412
- }
413
- default:
414
- break;
415
- }
416
- }
417
- return items;
418
- }
419
- function isCreditExhaustionPayload(payload) {
420
- return isCreditExhaustion({
421
- error: typeof payload.error === "string" ? payload.error : null,
422
- detail: typeof payload.detail === "string" ? payload.detail : null,
423
- 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 } : {}
424
72
  });
425
- }
426
- function creditExhaustedFromEvents(events) {
427
- const ordered = [...events].sort((a, b) => a.sequence - b.sequence);
428
- for (let index = ordered.length - 1; index >= 0; index -= 1) {
429
- const event = ordered[index];
430
- if (event?.type !== "turn.completed" && event?.type !== "turn.failed" && event?.type !== "turn.cancelled") {
431
- continue;
432
- }
433
- return isCreditExhaustionPayload(asRecord(event.payload));
434
- }
435
- return false;
436
- }
437
- function sessionStatusFromEvents(events) {
438
- for (let index = events.length - 1; index >= 0; index -= 1) {
439
- const event = events[index];
440
- if (event?.type !== "session.status.changed") {
441
- continue;
442
- }
443
- const status = asRecord(event.payload).status;
444
- if (isSessionStatus(status)) {
445
- return status;
446
- }
447
- }
448
- return null;
449
- }
450
- function isActivityItem(item) {
451
- switch (item.kind) {
452
- case "reasoning":
453
- case "tool-call":
454
- case "worker":
455
- case "sandbox":
456
- case "memory":
457
- case "fleet-decision":
458
- return true;
459
- default:
460
- return false;
461
- }
462
- }
463
- function groupTimeline(items) {
464
- const groups = [];
465
- for (const item of items) {
466
- if (isActivityItem(item)) {
467
- const open = groups[groups.length - 1];
468
- if (open?.kind === "activity" && open.outcome === void 0) {
469
- open.items.push(item);
470
- } else {
471
- groups.push({ kind: "activity", id: `activity-${item.id}`, items: [item] });
472
- }
473
- continue;
474
- }
475
- if (item.kind === "turn-end") {
476
- stampTurnOutcome(groups, item);
477
- foldSettledTurn(groups, item);
478
- continue;
479
- }
480
- groups.push({ kind: "item", item });
481
- }
482
- return groups;
483
- }
484
- function prescanTurnAnchors(events) {
485
- const ordered = [...events].sort((a, b) => a.sequence - b.sequence);
486
- const queuedTurnByTrigger = /* @__PURE__ */ new Map();
487
- const startSeqByTrigger = /* @__PURE__ */ new Map();
488
- const cancelledTurnIds = /* @__PURE__ */ new Set();
489
- const fallbackSeqByTurn = /* @__PURE__ */ new Map();
490
- const startedTurnIds = /* @__PURE__ */ new Set();
491
- for (const event of ordered) {
492
- const payload = asRecord(event.payload);
493
- const turnId = event.turnId ?? null;
494
- if (event.type === "turn.queued") {
495
- const triggerEventId = typeof payload.triggerEventId === "string" ? payload.triggerEventId : null;
496
- const queuedTurnId = typeof payload.turnId === "string" ? payload.turnId : turnId;
497
- if (triggerEventId && queuedTurnId) {
498
- queuedTurnByTrigger.set(triggerEventId, queuedTurnId);
499
- }
500
- continue;
501
- }
502
- if (event.type === "turn.started") {
503
- const triggerEventId = typeof payload.triggerEventId === "string" ? payload.triggerEventId : null;
504
- if (triggerEventId && !startSeqByTrigger.has(triggerEventId)) {
505
- startSeqByTrigger.set(triggerEventId, event.sequence);
506
- }
507
- if (turnId) {
508
- startedTurnIds.add(turnId);
509
- }
510
- } else if (event.type === "turn.cancelled" && turnId) {
511
- cancelledTurnIds.add(turnId);
512
- }
513
- if (turnId && isTurnExecutionEvidence(event.type)) {
514
- const previous = fallbackSeqByTurn.get(turnId);
515
- if (previous === void 0 || event.sequence < previous) {
516
- fallbackSeqByTurn.set(turnId, event.sequence);
73
+ const updateTitle = useCallback(
74
+ async (title) => {
75
+ if (!sessionId) {
76
+ return null;
517
77
  }
518
- }
519
- if (turnId && isTurnExecutionEvidence(event.type)) {
520
- startedTurnIds.add(turnId);
521
- }
522
- }
523
- for (const [triggerEventId, turnId] of queuedTurnByTrigger) {
524
- if (!startSeqByTrigger.has(triggerEventId)) {
525
- const fallbackSeq = fallbackSeqByTurn.get(turnId);
526
- if (fallbackSeq !== void 0) {
527
- startSeqByTrigger.set(triggerEventId, fallbackSeq);
78
+ const result = await run(() => client.updateSession(workspaceId, sessionId, { title }));
79
+ if (result) {
80
+ setOverride(result);
528
81
  }
529
- }
530
- }
531
- const cancelledBeforeStartTriggers = /* @__PURE__ */ new Set();
532
- for (const [triggerEventId, turnId] of queuedTurnByTrigger) {
533
- if (cancelledTurnIds.has(turnId) && !startSeqByTrigger.has(triggerEventId) && !fallbackSeqByTurn.has(turnId)) {
534
- cancelledBeforeStartTriggers.add(triggerEventId);
535
- }
536
- }
537
- return { queuedTurnByTrigger, startSeqByTrigger, cancelledBeforeStartTriggers, startedTurnIds };
538
- }
539
- function orderTimelineEvents(events, prescan) {
540
- const ordered = [...events].sort((a, b) => a.sequence - b.sequence);
541
- const insertions = /* @__PURE__ */ new Map();
542
- for (const event of ordered) {
543
- if (event.type !== "user.message") {
544
- continue;
545
- }
546
- const queuedTurnId = prescan.queuedTurnByTrigger.get(event.id);
547
- if (!queuedTurnId) {
548
- pushInsertion(insertions, event.sequence, event);
549
- continue;
550
- }
551
- if (prescan.cancelledBeforeStartTriggers.has(event.id)) {
552
- continue;
553
- }
554
- const startSeq = prescan.startSeqByTrigger.get(event.id);
555
- if (startSeq !== void 0) {
556
- pushInsertion(insertions, startSeq, event);
557
- continue;
558
- }
559
- }
560
- const projected = [];
561
- for (const event of ordered) {
562
- const before = insertions.get(event.sequence);
563
- if (before) {
564
- projected.push(...before);
565
- }
566
- if (event.type !== "user.message") {
567
- projected.push(event);
568
- }
569
- }
570
- return projected;
571
- }
572
- function pushInsertion(insertions, sequence, event) {
573
- const bucket = insertions.get(sequence);
574
- if (bucket) {
575
- bucket.push(event);
576
- } else {
577
- insertions.set(sequence, [event]);
578
- }
579
- }
580
- function isAgentActivityEvent(type) {
581
- return type.startsWith("agent.") || type.startsWith("sandbox.");
582
- }
583
- function isTurnExecutionEvidence(type) {
584
- 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" || type === "codex.fleet.decision";
585
- }
586
- function turnEndItem(event, outcome, failureText) {
587
- return {
588
- kind: "turn-end",
589
- id: `${event.id}-turn-end`,
590
- turnId: event.turnId ?? null,
591
- outcome,
592
- failureText,
593
- occurredAt: event.occurredAt
594
- };
595
- }
596
- function hasTurnActivity(items, turnId) {
597
- if (turnId) {
598
- return items.some((item) => isActivityItem(item) && item.turnId === turnId);
599
- }
600
- for (let index = items.length - 1; index >= 0; index -= 1) {
601
- const item = items[index];
602
- if (!item || item.kind === "turn-end" || item.kind === "user-message") {
603
- return false;
604
- }
605
- if (isActivityItem(item)) {
606
- return true;
607
- }
608
- }
609
- return false;
610
- }
611
- function stampTurnOutcome(groups, turnEnd) {
612
- if (turnEnd.turnId === null) {
613
- const trailing = groups[groups.length - 1];
614
- if (trailing?.kind === "activity" && trailing.outcome === void 0) {
615
- applyTurnOutcome(trailing, turnEnd);
616
- }
617
- return;
618
- }
619
- for (const group of groups) {
620
- if (group.kind !== "activity" || group.outcome !== void 0) {
621
- continue;
622
- }
623
- if (group.items.some((activity) => activity.turnId === turnEnd.turnId)) {
624
- applyTurnOutcome(group, turnEnd);
625
- }
626
- }
627
- }
628
- function applyTurnOutcome(group, turnEnd) {
629
- if (turnEnd.outcome === "complete") {
630
- group.outcome = "complete";
631
- return;
632
- }
633
- const hasFailed = group.items.some((item) => "status" in item && item.status === "failed");
634
- const hasInterrupted = group.items.some(
635
- (item) => "status" in item && item.status === "cancelled"
636
- );
637
- group.outcome = hasFailed ? "failed" : hasInterrupted ? "cancelled" : "complete";
638
- if (turnEnd.failureText && hasFailed) {
639
- group.failureText = turnEnd.failureText;
640
- }
641
- }
642
- function foldSettledTurn(groups, turnEnd) {
643
- let startIndex = groups.length;
644
- let stoppedAtForeignTurn = false;
645
- while (startIndex > 0) {
646
- const previous = groups[startIndex - 1];
647
- if (isTurnBoundary(previous)) {
648
- break;
649
- }
650
- if (belongsToDifferentTurn(previous, turnEnd.turnId)) {
651
- stoppedAtForeignTurn = true;
652
- break;
653
- }
654
- startIndex -= 1;
655
- }
656
- if (stoppedAtForeignTurn) {
657
- while (startIndex < groups.length && isBetweenTurnDivider(groups[startIndex])) {
658
- startIndex += 1;
659
- }
660
- }
661
- const collected = groups.slice(startIndex);
662
- if (collected.length === 0) {
663
- return;
664
- }
665
- const finalMessage = extractFinalAgentMessage(collected, turnEnd);
666
- const body = finalMessage ? collected.slice(0, -1) : collected;
667
- if (body.length === 0) {
668
- return;
669
- }
670
- const firstOccurredAt = groupStartedAt(body[0]) ?? turnEnd.occurredAt;
671
- const turnGroup = {
672
- kind: "turn",
673
- id: `turn-${turnEnd.turnId ?? turnEnd.id}`,
674
- outcome: turnEnd.outcome,
675
- startedAt: firstOccurredAt,
676
- endedAt: turnEnd.occurredAt,
677
- groups: body
678
- };
679
- if (turnEnd.failureText) {
680
- turnGroup.failureText = turnEnd.failureText;
681
- }
682
- groups.splice(
683
- startIndex,
684
- collected.length,
685
- ...finalMessage ? [turnGroup, finalMessage] : [turnGroup]
686
- );
687
- }
688
- function isTurnBoundary(group) {
689
- return group?.kind === "turn" || group?.kind === "item" && group.item.kind === "user-message";
690
- }
691
- function belongsToDifferentTurn(group, turnId) {
692
- if (!group || !turnId) {
693
- return false;
694
- }
695
- if (group.kind === "activity") {
696
- return group.items.length > 0 && group.items.every((item) => item.turnId !== null && item.turnId !== turnId);
697
- }
698
- return group.kind === "item" && group.item.kind === "agent-message" && group.item.turnId !== null && group.item.turnId !== turnId;
699
- }
700
- function isBetweenTurnDivider(group) {
701
- return group?.kind === "item" && group.item.kind === "session-status" && group.item.status !== "running";
702
- }
703
- function extractFinalAgentMessage(groups, turnEnd) {
704
- const tail = groups[groups.length - 1];
705
- if (tail?.kind !== "item" || tail.item.kind !== "agent-message" || tail.item.streaming) {
706
- return null;
707
- }
708
- if (tail.item.turnId && turnEnd.turnId && tail.item.turnId !== turnEnd.turnId) {
709
- return null;
710
- }
711
- return tail;
712
- }
713
- function groupStartedAt(group) {
714
- if (!group) {
715
- return void 0;
716
- }
717
- switch (group.kind) {
718
- case "item":
719
- return group.item.occurredAt;
720
- case "activity":
721
- return group.items[0]?.occurredAt;
722
- case "turn":
723
- return group.startedAt;
724
- }
725
- }
726
- function asRecord(value) {
727
- return value !== null && typeof value === "object" ? value : {};
728
- }
729
- var SESSION_STATUSES = [
730
- "queued",
731
- "running",
732
- "idle",
733
- "requires_action",
734
- "failed",
735
- "cancelled"
736
- ];
737
- var ATTENTION_STATUSES = /* @__PURE__ */ new Set([
738
- "requires_action",
739
- "failed",
740
- "cancelled"
741
- ]);
742
- function resourceRefs(value) {
743
- if (!Array.isArray(value)) {
744
- return [];
745
- }
746
- return value.filter((entry) => {
747
- const record = asRecord(entry);
748
- if (record.kind === "repository") {
749
- return typeof record.uri === "string" && typeof record.ref === "string";
750
- }
751
- return record.kind === "file" && typeof record.fileId === "string";
752
- });
753
- }
754
- function toolRefs(value) {
755
- if (!Array.isArray(value)) {
756
- return [];
757
- }
758
- return value.filter((entry) => {
759
- const record = asRecord(entry);
760
- return record.kind === "mcp" && typeof record.id === "string";
761
- });
762
- }
763
- function workerCompletionPayload(value) {
764
- const payload = asRecord(value);
765
- const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
766
- if (typeof payload.childSessionId !== "string" || !uuidPattern.test(payload.childSessionId) || typeof payload.status !== "string" || payload.status.trim() === "") {
767
- return null;
768
- }
769
- const goal = asRecord(payload.goal);
770
- return {
771
- childSessionId: payload.childSessionId,
772
- childStatus: payload.status,
773
- goalStatus: typeof goal.status === "string" ? goal.status : null,
774
- goalText: typeof goal.text === "string" ? goal.text : null,
775
- evidence: typeof goal.evidence === "string" ? goal.evidence : null,
776
- // Console pauses and several server paths put the human explanation on
777
- // `rationale`, not `pausedReason` — same fallback the goal pill uses, so a
778
- // paused worker card never shows "Worker paused" with the reason missing.
779
- pausedReason: typeof goal.pausedReason === "string" ? goal.pausedReason : typeof goal.rationale === "string" ? goal.rationale : null
780
- };
781
- }
782
- function isSessionStatus(value) {
783
- return typeof value === "string" && SESSION_STATUSES.includes(value);
784
- }
785
- function isErrorOutput(payload) {
786
- if (payload.error === true || payload.failed === true) {
787
- return true;
788
- }
789
- const output = payload.output;
790
- return !!output && typeof output === "object" && output.isError === true;
791
- }
792
- function findOpenCall(items, callId) {
793
- const reversed = [...items].reverse();
794
- const isCall = (item) => item.kind === "tool-call" || item.kind === "worker";
795
- if (callId) {
796
- const byId = reversed.find((item) => isCall(item) && item.callId === callId);
797
- if (byId) {
798
- return byId;
799
- }
800
- }
801
- return reversed.find(
802
- (item) => isCall(item) && item.status === "running"
803
- );
804
- }
805
- function findOpenSandbox(items, name) {
806
- return [...items].reverse().find(
807
- (item) => item.kind === "sandbox" && item.name === name && item.status === "running"
82
+ return result;
83
+ },
84
+ [client, workspaceId, sessionId, run]
808
85
  );
809
- }
810
- function failureMessage(payload) {
811
- for (const key of ["error", "message"]) {
812
- const value = payload[key];
813
- if (typeof value === "string" && value.trim().length > 0) {
814
- return humanizeFailureReason(value);
815
- }
816
- }
817
- return null;
818
- }
819
- function goalText(payload) {
820
- if (typeof payload.text === "string" && payload.text) {
821
- return payload.text;
822
- }
823
- const goal = asRecord(payload.goal);
824
- if (typeof goal.text === "string" && goal.text) {
825
- return goal.text;
826
- }
827
- if (typeof payload.prompt === "string" && payload.prompt) {
828
- return payload.prompt;
829
- }
830
- return null;
831
- }
832
- function memoryItem(id, type, turnId, payload, occurredAt) {
833
- const memoryId = typeof payload.memoryId === "string" && payload.memoryId ? payload.memoryId : null;
834
- if (!memoryId) {
835
- return null;
836
- }
837
- const replacementPreview = typeof payload.replacementPreview === "string" ? payload.replacementPreview : void 0;
838
- const replacementMemoryId = typeof payload.replacementMemoryId === "string" ? payload.replacementMemoryId : void 0;
839
- const action = typeof payload.action === "string" ? payload.action : void 0;
840
86
  return {
841
- kind: "memory",
842
- id,
843
- turnId,
844
- variant: type === "memory.corrected" ? "corrected" : "saved",
845
- memoryKind: typeof payload.kind === "string" ? payload.kind : "",
846
- preview: typeof payload.preview === "string" ? payload.preview : "",
847
- ...payload.deduped === true ? { deduped: true } : {},
848
- ...replacementPreview ? { replacementPreview } : {},
849
- ...action ? { action } : {},
850
- memoryId,
851
- ...replacementMemoryId ? { replacementMemoryId } : {},
852
- occurredAt
87
+ session,
88
+ loading,
89
+ error,
90
+ refresh,
91
+ updateTitle,
92
+ updating: mutating,
93
+ mutationError,
94
+ clearMutationError
853
95
  };
854
96
  }
855
- var AUTH_NEEDED_REASONS = /* @__PURE__ */ new Set([
856
- "missing_connection",
857
- "expired",
858
- "insufficient_scope",
859
- "refresh_failed",
860
- "unsupported_auth",
861
- "resource_scope_unavailable"
862
- ]);
863
- function authNeededReason(value) {
864
- return typeof value === "string" && AUTH_NEEDED_REASONS.has(value) ? value : null;
865
- }
866
- function stringList(value) {
867
- return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
868
- }
869
- function reasoningText(payload) {
870
- const record = asRecord(payload);
871
- if (typeof record.text === "string") {
872
- return record.text;
873
- }
874
- const content = asRecord(asRecord(record.item).rawItem).content;
875
- if (!Array.isArray(content)) {
876
- return "";
877
- }
878
- return content.map((part) => {
879
- const text = asRecord(part).text;
880
- return typeof text === "string" ? text : "";
881
- }).join("");
882
- }
883
- function workerPrompt(args) {
884
- const record = asRecord(typeof args === "string" ? tryParseJson(args) : args);
885
- for (const key of ["initialMessage", "message", "text", "prompt"]) {
886
- const value = record[key];
887
- if (typeof value === "string" && value.trim().length > 0) {
888
- return value;
889
- }
890
- }
891
- return null;
892
- }
893
- function extractSessionRef(value, depth = 0) {
894
- if (depth > 6 || value === null || value === void 0) {
895
- return null;
896
- }
897
- if (typeof value === "string") {
898
- return extractSessionRef(tryParseJson(value), depth + 1);
899
- }
900
- if (Array.isArray(value)) {
901
- for (const entry of value) {
902
- const found = extractSessionRef(entry, depth + 1);
903
- if (found) {
904
- return found;
905
- }
906
- }
907
- return null;
908
- }
909
- if (typeof value !== "object") {
910
- return null;
911
- }
912
- const record = value;
913
- if (typeof record.sessionId === "string" && looksLikeId(record.sessionId)) {
914
- return record.sessionId;
915
- }
916
- if (typeof record.id === "string" && looksLikeId(record.id) && ("status" in record || "workspaceId" in record || "initialMessage" in record)) {
917
- return record.id;
918
- }
919
- for (const key of ["structuredContent", "session", "result", "content"]) {
920
- if (key in record) {
921
- const found = extractSessionRef(record[key], depth + 1);
922
- if (found) {
923
- return found;
924
- }
925
- }
926
- }
927
- if (typeof record.text === "string") {
928
- return extractSessionRef(tryParseJson(record.text), depth + 1);
929
- }
930
- return null;
931
- }
932
- function looksLikeId(value) {
933
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
934
- }
935
97
 
936
98
  // src/hooks/use-session-events.ts
937
- 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";
938
100
  var INITIAL_TAIL_PAGE_SIZE = 1e3;
939
101
  var OLDER_PAGE_SIZE = 5e3;
940
102
  var INITIAL_FETCH_CAP = 1;
@@ -983,16 +145,16 @@ function useSessionEvents(sessionId, options = {}) {
983
145
  const replay = options.replay ?? "windowed";
984
146
  const fullReplay = replay === "full" || after !== 0;
985
147
  const streamKey = `${workspaceId}\0${sessionId ?? ""}\0${after}\0${fullReplay ? "full" : "windowed"}`;
986
- const [eventWindow, setEventWindow] = useState(EMPTY_EVENT_WINDOW);
987
- const [connectionState, setConnectionState] = useState("idle");
988
- const [error, setError] = useState(null);
989
- const [hasOlder, setHasOlder] = useState(false);
990
- 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(
991
153
  null
992
154
  );
993
- const [initialLoading, setInitialLoading] = useState(true);
994
- const [loadingOlder, setLoadingOlder] = useState(false);
995
- const [streamEpoch, setStreamEpoch] = useState(0);
155
+ const [initialLoading, setInitialLoading] = useState2(true);
156
+ const [loadingOlder, setLoadingOlder] = useState2(false);
157
+ const [streamEpoch, setStreamEpoch] = useState2(0);
996
158
  const lastSequenceRef = useRef(after);
997
159
  const streamResumeSequenceRef = useRef(after);
998
160
  const oldestSequenceRef = useRef(null);
@@ -1007,8 +169,8 @@ function useSessionEvents(sessionId, options = {}) {
1007
169
  sequence: after,
1008
170
  status: null
1009
171
  });
1010
- const [stateStreamKey, setStateStreamKey] = useState(streamKey);
1011
- useEffect(() => {
172
+ const [stateStreamKey, setStateStreamKey] = useState2(streamKey);
173
+ useEffect2(() => {
1012
174
  if (streamKeyRef.current !== streamKey) {
1013
175
  streamKeyRef.current = streamKey;
1014
176
  setStateStreamKey(streamKey);
@@ -1177,7 +339,7 @@ function useSessionEvents(sessionId, options = {}) {
1177
339
  streamEpoch,
1178
340
  reconcileSession
1179
341
  ]);
1180
- const loadOlder = useCallback(async () => {
342
+ const loadOlder = useCallback2(async () => {
1181
343
  if (!sessionId || loadingOlderRef.current || !hasOlderRef.current) {
1182
344
  return false;
1183
345
  }
@@ -1239,7 +401,7 @@ function useSessionEvents(sessionId, options = {}) {
1239
401
  }, [client, workspaceId, sessionId]);
1240
402
  const identityMatches = stateStreamKey === streamKey;
1241
403
  const visibleEvents = identityMatches ? eventWindow.events : EMPTY_EVENTS;
1242
- const timeline = useMemo(() => buildTimeline(visibleEvents), [visibleEvents]);
404
+ const timeline = useMemo2(() => buildTimeline(visibleEvents), [visibleEvents]);
1243
405
  return {
1244
406
  events: visibleEvents,
1245
407
  timeline,
@@ -1550,7 +712,12 @@ async function loadPreviousPage(client, workspaceId, sessionId, before, options)
1550
712
  const page = await client.listEvents(workspaceId, sessionId, {
1551
713
  before,
1552
714
  limit: requested,
1553
- 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"
1554
721
  });
1555
722
  if (options.signal?.aborted) {
1556
723
  throw abortError();
@@ -1570,11 +737,11 @@ function maxResumeSequence(events) {
1570
737
  return events.reduce((max, event) => Math.max(max, eventResumeSequence(event)), 0);
1571
738
  }
1572
739
  function eventResumeSequence(event) {
1573
- const payload = asRecord2(event.payload);
740
+ const payload = asRecord(event.payload);
1574
741
  const coalescedUntil = Number(payload.coalescedUntil);
1575
742
  return Math.max(event.sequence, Number.isFinite(coalescedUntil) ? Math.floor(coalescedUntil) : 0);
1576
743
  }
1577
- function asRecord2(value) {
744
+ function asRecord(value) {
1578
745
  return value && typeof value === "object" ? value : {};
1579
746
  }
1580
747
  function assertAscending(events) {
@@ -1602,8 +769,199 @@ function abortError() {
1602
769
  return error;
1603
770
  }
1604
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
+
1605
963
  // src/hooks/use-turn-queue.ts
1606
- 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";
1607
965
  function isTurnQueueEvent(event) {
1608
966
  return event.type.startsWith("turn.") || event.type.startsWith("session.queue.") || event.type.startsWith("session.control.") || event.type.startsWith("workspace.inference.");
1609
967
  }
@@ -1617,16 +975,16 @@ function useTurnQueue(sessionId, options = {}) {
1617
975
  const { client, workspaceId, workspaceControlEvent, registerSessionReconciler } = useEmbeddedSession(options);
1618
976
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
1619
977
  const targetKey = `${workspaceId}\0${sessionId ?? ""}`;
1620
- const [snapshot, setSnapshot] = useState2(null);
1621
- const [stateTargetKey, setStateTargetKey] = useState2(targetKey);
1622
- const [loading, setLoading] = useState2(enabled);
1623
- const [error, setError] = useState2(null);
1624
- const [mutationError, setMutationError] = useState2(null);
1625
- const [pendingByTurn, setPendingByTurn] = useState2({});
1626
- const pendingRef = useRef2({});
1627
- const readGeneration = useRef2(0);
1628
- const targetKeyRef = useRef2(targetKey);
1629
- 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);
1630
988
  useLayoutEffect(() => {
1631
989
  if (targetKeyRef.current === targetKey) return;
1632
990
  targetKeyRef.current = targetKey;
@@ -1640,7 +998,7 @@ function useTurnQueue(sessionId, options = {}) {
1640
998
  setMutationError(null);
1641
999
  setPendingByTurn({});
1642
1000
  }, [enabled, targetKey]);
1643
- const acceptSnapshot = useCallback2(
1001
+ const acceptSnapshot = useCallback4(
1644
1002
  (ownedTargetKey, next) => {
1645
1003
  if (targetKeyRef.current !== ownedTargetKey) return false;
1646
1004
  const current = snapshotRef.current;
@@ -1654,7 +1012,7 @@ function useTurnQueue(sessionId, options = {}) {
1654
1012
  },
1655
1013
  []
1656
1014
  );
1657
- const load = useCallback2(
1015
+ const load = useCallback4(
1658
1016
  async (rejectOnFailure = false) => {
1659
1017
  if (!sessionId) return;
1660
1018
  const ownedTargetKey = `${workspaceId}\0${sessionId}`;
@@ -1685,7 +1043,7 @@ function useTurnQueue(sessionId, options = {}) {
1685
1043
  },
1686
1044
  [client, workspaceId, sessionId, acceptSnapshot]
1687
1045
  );
1688
- useEffect2(() => {
1046
+ useEffect3(() => {
1689
1047
  if (!enabled) {
1690
1048
  setLoading(false);
1691
1049
  return;
@@ -1704,10 +1062,10 @@ function useTurnQueue(sessionId, options = {}) {
1704
1062
  readGeneration.current += 1;
1705
1063
  };
1706
1064
  }, [load, enabled, options.pollIntervalMs]);
1707
- useEffect2(() => {
1065
+ useEffect3(() => {
1708
1066
  if (enabled && workspaceControlEvent) void load();
1709
1067
  }, [enabled, load, workspaceControlEvent]);
1710
- useEffect2(() => {
1068
+ useEffect3(() => {
1711
1069
  if (!sessionId || !enabled) return;
1712
1070
  return registerSessionReconciler(sessionId, "queue", load);
1713
1071
  }, [enabled, load, registerSessionReconciler, sessionId]);
@@ -1724,7 +1082,7 @@ function useTurnQueue(sessionId, options = {}) {
1724
1082
  },
1725
1083
  async () => await load(true)
1726
1084
  );
1727
- const mutate = useCallback2(
1085
+ const mutate = useCallback4(
1728
1086
  async (turnId, kind, command) => {
1729
1087
  const ownedTargetKey = targetKey;
1730
1088
  if (!sessionId || targetKeyRef.current !== ownedTargetKey || pendingRef.current[turnId]) {
@@ -1759,7 +1117,7 @@ function useTurnQueue(sessionId, options = {}) {
1759
1117
  },
1760
1118
  [acceptSnapshot, load, sessionId, targetKey]
1761
1119
  );
1762
- const moveTurn = useCallback2(
1120
+ const moveTurn = useCallback4(
1763
1121
  async (turnId, beforeTurnId) => {
1764
1122
  const result = await mutate(
1765
1123
  turnId,
@@ -1774,7 +1132,7 @@ function useTurnQueue(sessionId, options = {}) {
1774
1132
  },
1775
1133
  [client, mutate, sessionId, workspaceId]
1776
1134
  );
1777
- const editTurn = useCallback2(
1135
+ const editTurn = useCallback4(
1778
1136
  async (turnId, edit) => {
1779
1137
  const result = await mutate(
1780
1138
  turnId,
@@ -1790,7 +1148,7 @@ function useTurnQueue(sessionId, options = {}) {
1790
1148
  },
1791
1149
  [client, mutate, sessionId, workspaceId]
1792
1150
  );
1793
- const steerTurn = useCallback2(
1151
+ const steerTurn = useCallback4(
1794
1152
  async (turnId) => {
1795
1153
  const result = await mutate(
1796
1154
  turnId,
@@ -1805,7 +1163,7 @@ function useTurnQueue(sessionId, options = {}) {
1805
1163
  },
1806
1164
  [client, mutate, sessionId, workspaceId]
1807
1165
  );
1808
- const removeTurn = useCallback2(
1166
+ const removeTurn = useCallback4(
1809
1167
  async (turnId) => {
1810
1168
  const result = await mutate(
1811
1169
  turnId,
@@ -1823,11 +1181,11 @@ function useTurnQueue(sessionId, options = {}) {
1823
1181
  const identityMatches = stateTargetKey === targetKey;
1824
1182
  const visibleSnapshot = identityMatches ? snapshot : null;
1825
1183
  const visiblePendingByTurn = identityMatches ? pendingByTurn : EMPTY_PENDING_BY_TURN;
1826
- const mutating = useMemo2(
1184
+ const mutating = useMemo3(
1827
1185
  () => Object.keys(visiblePendingByTurn).length > 0,
1828
1186
  [visiblePendingByTurn]
1829
1187
  );
1830
- const mutationFor = useCallback2(
1188
+ const mutationFor = useCallback4(
1831
1189
  (turnId) => visiblePendingByTurn[turnId] ?? null,
1832
1190
  [visiblePendingByTurn]
1833
1191
  );
@@ -1847,7 +1205,7 @@ function useTurnQueue(sessionId, options = {}) {
1847
1205
  mutationFor,
1848
1206
  mutating,
1849
1207
  mutationError: identityMatches ? mutationError : null,
1850
- clearMutationError: useCallback2(() => {
1208
+ clearMutationError: useCallback4(() => {
1851
1209
  if (targetKeyRef.current === targetKey) setMutationError(null);
1852
1210
  }, [targetKey])
1853
1211
  };
@@ -1859,12 +1217,154 @@ function asError(cause) {
1859
1217
  return cause instanceof Error ? cause : new Error(String(cause));
1860
1218
  }
1861
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
+
1862
1362
  // src/hooks/use-session-control.ts
1863
- 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";
1864
1364
  function useSessionControl(sessionId, options = {}) {
1865
1365
  const { client, workspaceId } = useEmbeddedSession(options);
1866
1366
  const approvalTargetKey = `${workspaceId}\0${sessionId ?? ""}`;
1867
- const pendingApproval = useRef3(null);
1367
+ const pendingApproval = useRef5(null);
1868
1368
  useLayoutEffect2(() => {
1869
1369
  if (pendingApproval.current?.targetKey !== approvalTargetKey) {
1870
1370
  pendingApproval.current = null;
@@ -1882,7 +1382,7 @@ function useSessionControl(sessionId, options = {}) {
1882
1382
  mutationError: approvalError,
1883
1383
  clearMutationError: clearApprovalError
1884
1384
  } = useMutationRunner(approvalTargetKey);
1885
- const pause = useCallback3(
1385
+ const pause = useCallback6(
1886
1386
  async (reason) => {
1887
1387
  if (!sessionId) {
1888
1388
  return null;
@@ -1893,7 +1393,7 @@ function useSessionControl(sessionId, options = {}) {
1893
1393
  },
1894
1394
  [client, workspaceId, sessionId, runControl]
1895
1395
  );
1896
- const resume = useCallback3(
1396
+ const resume = useCallback6(
1897
1397
  async (reason) => {
1898
1398
  if (!sessionId) return null;
1899
1399
  return await runControl(
@@ -1902,7 +1402,7 @@ function useSessionControl(sessionId, options = {}) {
1902
1402
  },
1903
1403
  [client, workspaceId, sessionId, runControl]
1904
1404
  );
1905
- const decide = useCallback3(
1405
+ const decide = useCallback6(
1906
1406
  async (approvalId, decision, message) => {
1907
1407
  if (!sessionId) {
1908
1408
  return null;
@@ -1931,16 +1431,16 @@ function useSessionControl(sessionId, options = {}) {
1931
1431
  },
1932
1432
  [approvalTargetKey, client, workspaceId, sessionId, runApproval]
1933
1433
  );
1934
- const approve = useCallback3(
1434
+ const approve = useCallback6(
1935
1435
  async (approvalId, message) => await decide(approvalId, "approve", message),
1936
1436
  [decide]
1937
1437
  );
1938
- const reject = useCallback3(
1438
+ const reject = useCallback6(
1939
1439
  async (approvalId, message) => await decide(approvalId, "reject", message),
1940
1440
  [decide]
1941
1441
  );
1942
1442
  const error = approvalError ?? controlError;
1943
- const clearError = useCallback3(() => {
1443
+ const clearError = useCallback6(() => {
1944
1444
  clearControlError();
1945
1445
  clearApprovalError();
1946
1446
  }, [clearControlError, clearApprovalError]);
@@ -1957,7 +1457,7 @@ function useSessionControl(sessionId, options = {}) {
1957
1457
  }
1958
1458
 
1959
1459
  // src/hooks/use-session-mcp-approval-policy.ts
1960
- 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";
1961
1461
  function isSessionMcpApprovalPolicyEvent(event, serverId) {
1962
1462
  if (event.type !== "session.mcp.approval_policy.updated") {
1963
1463
  return false;
@@ -1971,16 +1471,16 @@ function isSessionMcpApprovalPolicyEvent(event, serverId) {
1971
1471
  function useSessionMcpApprovalPolicy(sessionId, serverId, options = {}) {
1972
1472
  const { client, workspaceId } = useEmbeddedSessionMcpApprovalPolicy(options);
1973
1473
  const enabled = (options.enabled ?? true) && Boolean(sessionId && serverId);
1974
- const [override, setOverride] = useState3(null);
1474
+ const [override, setOverride] = useState6(null);
1975
1475
  const targetKey = `${workspaceId}\0${sessionId ?? ""}\0${serverId ?? ""}`;
1976
- const authoritativeGeneration = useRef4(0);
1977
- const mutationIdentity = useMemo3(() => ({ client, targetKey }), [client, targetKey]);
1476
+ const authoritativeGeneration = useRef6(0);
1477
+ const mutationIdentity = useMemo4(() => ({ client, targetKey }), [client, targetKey]);
1978
1478
  const { run, mutating, mutationError, clearMutationError } = useMutationRunner(mutationIdentity);
1979
1479
  useLayoutEffect3(() => {
1980
1480
  authoritativeGeneration.current += 1;
1981
1481
  setOverride(null);
1982
1482
  }, [client, targetKey]);
1983
- const load = useCallback4(async () => {
1483
+ const load = useCallback7(async () => {
1984
1484
  const generation = authoritativeGeneration.current;
1985
1485
  if (!sessionId || !serverId) {
1986
1486
  return { targetKey, generation, server: null };
@@ -2001,13 +1501,13 @@ function useSessionMcpApprovalPolicy(sessionId, serverId, options = {}) {
2001
1501
  enabled,
2002
1502
  pollIntervalMs: options.pollIntervalMs
2003
1503
  });
2004
- useEffect3(() => {
1504
+ useEffect5(() => {
2005
1505
  if (data?.targetKey === targetKey && data.generation === authoritativeGeneration.current) {
2006
1506
  setOverride(null);
2007
1507
  }
2008
1508
  }, [data, targetKey]);
2009
1509
  const scheduleRefresh = useDebouncedCallback(() => void refresh());
2010
- const matchesPolicyEvent = useCallback4(
1510
+ const matchesPolicyEvent = useCallback7(
2011
1511
  (event) => isSessionMcpApprovalPolicyEvent(event, serverId ?? void 0),
2012
1512
  [serverId]
2013
1513
  );
@@ -2023,7 +1523,7 @@ function useSessionMcpApprovalPolicy(sessionId, serverId, options = {}) {
2023
1523
  },
2024
1524
  async () => await refresh()
2025
1525
  );
2026
- const update = useCallback4(
1526
+ const update = useCallback7(
2027
1527
  async (policy) => {
2028
1528
  if (!sessionId || !serverId) {
2029
1529
  return null;
@@ -2057,6 +1557,91 @@ function useSessionMcpApprovalPolicy(sessionId, serverId, options = {}) {
2057
1557
  };
2058
1558
  }
2059
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
+
2060
1645
  // src/approvals.ts
2061
1646
  function approvalsFromRequiresAction(payload) {
2062
1647
  const approvals = payload && typeof payload === "object" ? payload.approvals : void 0;
@@ -2151,14 +1736,14 @@ function isRecord(value) {
2151
1736
  }
2152
1737
 
2153
1738
  // src/hooks/use-human-input.ts
2154
- 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";
2155
1740
  function isHumanInputEvent(event) {
2156
1741
  return event.type === "session.humanInput.requested" || event.type === "user.humanInputResponse" || event.type === "turn.completed" || event.type === "turn.failed" || event.type === "turn.cancelled";
2157
1742
  }
2158
1743
  function useHumanInputRequests(sessionId, options = {}) {
2159
1744
  const { client, workspaceId, registerSessionReconciler } = useEmbeddedHumanInputSession(options);
2160
1745
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
2161
- const load = useCallback5(
1746
+ const load = useCallback9(
2162
1747
  async () => sessionId ? await client.listHumanInputRequests(workspaceId, sessionId, { status: "pending" }) : [],
2163
1748
  [client, sessionId, workspaceId]
2164
1749
  );
@@ -2167,13 +1752,13 @@ function useHumanInputRequests(sessionId, options = {}) {
2167
1752
  pollIntervalMs: options.pollIntervalMs
2168
1753
  });
2169
1754
  const mutation = useMutationRunner(`${workspaceId}\0${sessionId ?? ""}`);
2170
- const [respondingRequestId, setRespondingRequestId] = useState4(null);
2171
- const respondingRequestRef = useRef5(null);
2172
- useEffect4(() => {
1755
+ const [respondingRequestId, setRespondingRequestId] = useState7(null);
1756
+ const respondingRequestRef = useRef8(null);
1757
+ useEffect7(() => {
2173
1758
  respondingRequestRef.current = null;
2174
1759
  setRespondingRequestId(null);
2175
1760
  }, [sessionId, workspaceId]);
2176
- useEffect4(() => {
1761
+ useEffect7(() => {
2177
1762
  if (!sessionId || !enabled) return;
2178
1763
  return registerSessionReconciler(sessionId, "human-input", state.refresh);
2179
1764
  }, [enabled, registerSessionReconciler, sessionId, state.refresh]);
@@ -2182,7 +1767,7 @@ function useHumanInputRequests(sessionId, options = {}) {
2182
1767
  enabled,
2183
1768
  ...options.events !== void 0 ? { events: options.events } : {}
2184
1769
  });
2185
- const respond = useCallback5(
1770
+ const respond = useCallback9(
2186
1771
  async (requestId, response) => {
2187
1772
  if (!sessionId || respondingRequestRef.current !== null) return null;
2188
1773
  respondingRequestRef.current = requestId;
@@ -2219,12 +1804,8 @@ function useHumanInputRequests(sessionId, options = {}) {
2219
1804
  }
2220
1805
 
2221
1806
  export {
2222
- toolDisplayName,
2223
- buildTimeline,
2224
- creditExhaustedFromEvents,
2225
- sessionStatusFromEvents,
2226
- groupTimeline,
2227
- extractSessionRef,
1807
+ isTitleEvent,
1808
+ useSession,
2228
1809
  SESSION_EVENT_BROWSER_MAX_BYTES,
2229
1810
  SESSION_EVENT_BROWSER_MAX_COUNT,
2230
1811
  SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES,
@@ -2232,11 +1813,16 @@ export {
2232
1813
  SESSION_EVENT_BROWSER_PENDING_MAX_COUNT,
2233
1814
  useSessionEvents,
2234
1815
  boundBrowserSessionEventWindow,
1816
+ useFileAttachments,
2235
1817
  isTurnQueueEvent,
2236
1818
  useTurnQueue,
1819
+ isGoalEvent,
1820
+ useGoal,
2237
1821
  useSessionControl,
2238
1822
  isSessionMcpApprovalPolicyEvent,
2239
1823
  useSessionMcpApprovalPolicy,
1824
+ isLineageRefreshEvent,
1825
+ useSessionLineage,
2240
1826
  approvalsFromRequiresAction,
2241
1827
  projectPendingApprovals,
2242
1828
  humanInputRequestFromEvent,
@@ -2244,4 +1830,4 @@ export {
2244
1830
  isHumanInputEvent,
2245
1831
  useHumanInputRequests
2246
1832
  };
2247
- //# sourceMappingURL=chunk-SHOFILHJ.js.map
1833
+ //# sourceMappingURL=chunk-MP6237JB.js.map