@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.
- package/dist/{chunk-AVPU5PMC.js → chunk-F4CFWKVI.js} +332 -64
- package/dist/chunk-F4CFWKVI.js.map +1 -0
- package/dist/chunk-GQN2QIR2.js +3767 -0
- package/dist/chunk-GQN2QIR2.js.map +1 -0
- package/dist/{chunk-I3BJZIG5.js → chunk-HIWPQYWI.js} +2 -120
- package/dist/chunk-HIWPQYWI.js.map +1 -0
- package/dist/chunk-M7X4JZOD.js +121 -0
- package/dist/chunk-M7X4JZOD.js.map +1 -0
- package/dist/{chunk-SHOFILHJ.js → chunk-MP6237JB.js} +570 -984
- package/dist/chunk-MP6237JB.js.map +1 -0
- package/dist/chunk-OZDLELJQ.js +937 -0
- package/dist/chunk-OZDLELJQ.js.map +1 -0
- package/dist/{chunk-RDPDU4TA.js → chunk-TMH6HZWF.js} +3 -3
- package/dist/{chunk-6XUS5VFM.js → chunk-YU5PGUK7.js} +6 -4
- package/dist/{chunk-6XUS5VFM.js.map → chunk-YU5PGUK7.js.map} +1 -1
- package/dist/{composer-BXb0Q1HF.d.ts → composer-DoC4veX1.d.ts} +4 -75
- package/dist/composer.d.ts +2 -1
- package/dist/composer.js +4 -3
- package/dist/index.d.ts +14 -259
- package/dist/index.js +1016 -5140
- package/dist/index.js.map +1 -1
- package/dist/machines.js +3 -2
- package/dist/session-Du_FsrZ1.d.ts +264 -0
- package/dist/session-ui-BqQDH7YV.d.ts +183 -0
- package/dist/session-ui.d.ts +7 -0
- package/dist/session-ui.js +17 -0
- package/dist/session-ui.js.map +1 -0
- package/dist/session.d.ts +3 -1
- package/dist/session.js +26 -9
- package/dist/use-file-attachments-C0kpHrs9.d.ts +76 -0
- package/dist/{session-BEvtFWhe.d.ts → use-turn-queue-3rkJwjFv.d.ts} +3 -185
- package/package.json +6 -2
- package/src/components/message-timeline.tsx +92 -52
- package/src/hooks/use-composer.ts +456 -69
- package/src/hooks/use-file-attachments.ts +1 -1
- package/src/hooks/use-goal.ts +1 -1
- package/src/hooks/use-session-events.ts +5 -0
- package/src/hooks/use-session-lineage.ts +1 -1
- package/src/hooks/use-session.ts +9 -6
- package/src/session-ui.ts +13 -0
- package/src/session.ts +15 -0
- package/src/timeline/activity-rail.tsx +1 -1
- package/dist/chunk-AVPU5PMC.js.map +0 -1
- package/dist/chunk-I3BJZIG5.js.map +0 -1
- package/dist/chunk-SHOFILHJ.js.map +0 -1
- /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-
|
|
10
|
+
} from "./chunk-HIWPQYWI.js";
|
|
11
|
+
import {
|
|
12
|
+
buildTimeline,
|
|
13
|
+
groupTimeline,
|
|
14
|
+
sessionStatusFromEvents
|
|
15
|
+
} from "./chunk-OZDLELJQ.js";
|
|
14
16
|
|
|
15
|
-
// src/
|
|
16
|
-
|
|
17
|
-
function
|
|
18
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
|
|
520
|
-
|
|
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
|
-
|
|
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
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
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] =
|
|
987
|
-
const [connectionState, setConnectionState] =
|
|
988
|
-
const [error, setError] =
|
|
989
|
-
const [hasOlder, setHasOlder] =
|
|
990
|
-
const [sessionStatusProjection, setSessionStatusProjection] =
|
|
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] =
|
|
994
|
-
const [loadingOlder, setLoadingOlder] =
|
|
995
|
-
const [streamEpoch, setStreamEpoch] =
|
|
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] =
|
|
1011
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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] =
|
|
1621
|
-
const [stateTargetKey, setStateTargetKey] =
|
|
1622
|
-
const [loading, setLoading] =
|
|
1623
|
-
const [error, setError] =
|
|
1624
|
-
const [mutationError, setMutationError] =
|
|
1625
|
-
const [pendingByTurn, setPendingByTurn] =
|
|
1626
|
-
const pendingRef =
|
|
1627
|
-
const readGeneration =
|
|
1628
|
-
const targetKeyRef =
|
|
1629
|
-
const snapshotRef =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
1065
|
+
useEffect3(() => {
|
|
1708
1066
|
if (enabled && workspaceControlEvent) void load();
|
|
1709
1067
|
}, [enabled, load, workspaceControlEvent]);
|
|
1710
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1184
|
+
const mutating = useMemo3(
|
|
1827
1185
|
() => Object.keys(visiblePendingByTurn).length > 0,
|
|
1828
1186
|
[visiblePendingByTurn]
|
|
1829
1187
|
);
|
|
1830
|
-
const mutationFor =
|
|
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:
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1434
|
+
const approve = useCallback6(
|
|
1935
1435
|
async (approvalId, message) => await decide(approvalId, "approve", message),
|
|
1936
1436
|
[decide]
|
|
1937
1437
|
);
|
|
1938
|
-
const reject =
|
|
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 =
|
|
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
|
|
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] =
|
|
1474
|
+
const [override, setOverride] = useState6(null);
|
|
1975
1475
|
const targetKey = `${workspaceId}\0${sessionId ?? ""}\0${serverId ?? ""}`;
|
|
1976
|
-
const authoritativeGeneration =
|
|
1977
|
-
const mutationIdentity =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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] =
|
|
2171
|
-
const respondingRequestRef =
|
|
2172
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
2223
|
-
|
|
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-
|
|
1833
|
+
//# sourceMappingURL=chunk-MP6237JB.js.map
|