@opengeni/react 0.57.0 → 2.2.0-canary.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/README.md +36 -11
- package/dist/artifacts-spreadsheet.js +1 -1
- package/dist/artifacts.js +1 -1
- package/dist/browser-viewer-GXUICIAG.js +14 -0
- package/dist/{chunk-R3AZOAB5.js → chunk-4MDLEHXL.js} +9 -115
- package/dist/chunk-4MDLEHXL.js.map +1 -0
- package/dist/{chunk-Y7OEDUJ5.js → chunk-5RZN3DD5.js} +1014 -420
- package/dist/chunk-5RZN3DD5.js.map +1 -0
- package/dist/chunk-5YK2ZDIG.js +116 -0
- package/dist/chunk-5YK2ZDIG.js.map +1 -0
- package/dist/chunk-H5YGYNXS.js +329 -0
- package/dist/chunk-H5YGYNXS.js.map +1 -0
- package/dist/{chunk-7WCLAEFV.js → chunk-ICA6R6TW.js} +2 -2
- package/dist/{chunk-7WCLAEFV.js.map → chunk-ICA6R6TW.js.map} +1 -1
- package/dist/{chunk-IGC6A2ZI.js → chunk-JOAVA2TW.js} +589 -217
- package/dist/chunk-JOAVA2TW.js.map +1 -0
- package/dist/{chunk-5UQPNLIO.js → chunk-LTCHJ4MS.js} +9 -9
- package/dist/chunk-LTCHJ4MS.js.map +1 -0
- package/dist/{chunk-C5ZPSSS5.js → chunk-NEXLLTP4.js} +2 -1
- package/dist/{chunk-C5ZPSSS5.js.map → chunk-NEXLLTP4.js.map} +1 -1
- package/dist/chunk-QGBJ6GHG.js +2300 -0
- package/dist/chunk-QGBJ6GHG.js.map +1 -0
- package/dist/{chunk-B6WPZSTT.js → chunk-UOLJWT5Y.js} +78 -16
- package/dist/chunk-UOLJWT5Y.js.map +1 -0
- package/dist/{chunk-MKPIAGVL.js → chunk-W6NFJXSA.js} +8 -19
- package/dist/chunk-W6NFJXSA.js.map +1 -0
- package/dist/{chunk-XBZWOM2K.js → chunk-X4A66EWZ.js} +8 -3
- package/dist/chunk-X4A66EWZ.js.map +1 -0
- package/dist/chunk-X4JAFD5R.js +3907 -0
- package/dist/chunk-X4JAFD5R.js.map +1 -0
- package/dist/{chunk-6KLX2RH5.js → chunk-XSA2QAKP.js} +334 -19
- package/dist/chunk-XSA2QAKP.js.map +1 -0
- package/dist/{chunk-HA46CYQK.js → chunk-XXWH2JLC.js} +792 -206
- package/dist/chunk-XXWH2JLC.js.map +1 -0
- package/dist/client.d.ts +2 -2
- package/dist/components/browser-viewer.d.ts +7 -1
- package/dist/components/computer-viewer.d.ts +4 -1
- package/dist/components/enrollment-consent.d.ts +6 -3
- package/dist/components/file-browser.d.ts +7 -1
- package/dist/components/human-input-form.d.ts +1 -1
- package/dist/components/human-input-surface.d.ts +1 -1
- package/dist/components/machine-card.d.ts +4 -1
- package/dist/components/machines/machine-detail.d.ts +4 -1
- package/dist/components/machines/machine-operation-policy.d.ts +11 -0
- package/dist/components/machines-dashboard.d.ts +3 -1
- package/dist/components/markdown.d.ts +20 -1
- package/dist/components/message-timeline.d.ts +1 -1
- package/dist/components/model-policy-picker.d.ts +2 -0
- package/dist/components/pierre-file.d.ts +1 -1
- package/dist/components/sandbox-files.d.ts +8 -2
- package/dist/components/sandbox-workspace.d.ts +33 -3
- package/dist/components/session-chrome.d.ts +15 -3
- package/dist/components/workspace-dock.d.ts +6 -2
- package/dist/composer.js +5 -5
- package/dist/computer-viewer-ZOP4Q7A7.js +16 -0
- package/dist/computer-viewer-ZOP4Q7A7.js.map +1 -0
- package/dist/hooks/channel-cache.d.ts +23 -0
- package/dist/hooks/shared-machines-list.d.ts +14 -0
- package/dist/hooks/use-attached-browsers.d.ts +2 -1
- package/dist/hooks/use-channels.d.ts +1 -0
- package/dist/hooks/use-composer.d.ts +37 -9
- package/dist/hooks/use-machine-chip.d.ts +3 -3
- package/dist/hooks/use-machines.d.ts +19 -1
- package/dist/hooks/use-sandbox-files.d.ts +1 -0
- package/dist/hooks/use-session-background-commands.d.ts +14 -0
- package/dist/hooks/use-turn-queue.d.ts +12 -0
- package/dist/hooks/use-workspace-sessions.d.ts +1 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +1096 -623
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +29 -181
- package/dist/interaction.js.map +1 -1
- package/dist/lib/interaction-errors.d.ts +3 -0
- package/dist/machines.d.ts +4 -2
- package/dist/machines.js +10 -2
- package/dist/model-policy.d.ts +1 -1
- package/dist/model-policy.js +1 -1
- package/dist/platform-activity-row-BJHUHXAT.js +169 -0
- package/dist/platform-activity-row-BJHUHXAT.js.map +1 -0
- package/dist/{queue-surface-implementation-2KVJTYXH.js → queue-surface-implementation-BSKUWHRI.js} +76 -16
- package/dist/queue-surface-implementation-BSKUWHRI.js.map +1 -0
- package/dist/realtime/realtime-control.d.ts +10 -3
- package/dist/realtime.js +138 -29
- package/dist/realtime.js.map +1 -1
- package/dist/session-ui.d.ts +1 -1
- package/dist/session-ui.js +6 -2
- package/dist/session.d.ts +2 -2
- package/dist/session.js +4 -4
- package/dist/timeline/index.d.ts +1 -1
- package/dist/timeline/platform-activity-row.d.ts +15 -0
- package/dist/timeline/screenshot-lightbox.d.ts +1 -1
- package/dist/timeline/shared.d.ts +4 -2
- package/dist/timeline/types.d.ts +48 -5
- package/dist/types/machines.d.ts +2 -2
- package/package.json +3 -3
- package/src/client.ts +12 -1
- package/src/components/artifacts/editable-spreadsheet.tsx +1 -1
- package/src/components/browser-viewer.tsx +361 -58
- package/src/components/chat-composer.tsx +1 -1
- package/src/components/composer.tsx +6 -6
- package/src/components/computer-viewer.tsx +368 -115
- package/src/components/enrollment-consent.tsx +64 -6
- package/src/components/file-browser.tsx +117 -2
- package/src/components/human-input-form.tsx +91 -55
- package/src/components/human-input-surface.tsx +1 -1
- package/src/components/machine-card.tsx +111 -0
- package/src/components/machine-input-display.ts +15 -0
- package/src/components/machines/machine-detail.tsx +47 -0
- package/src/components/machines/machine-operation-policy.tsx +179 -0
- package/src/components/machines-dashboard.tsx +6 -0
- package/src/components/markdown.tsx +174 -20
- package/src/components/message-timeline.tsx +162 -17
- package/src/components/model-policy-picker.tsx +9 -17
- package/src/components/pierre-file.tsx +1 -1
- package/src/components/queue-surface-implementation.tsx +82 -16
- package/src/components/sandbox-files.tsx +135 -19
- package/src/components/sandbox-workspace.tsx +204 -92
- package/src/components/session-chrome.tsx +415 -208
- package/src/components/session-status.tsx +1 -1
- package/src/components/user-message-body.tsx +168 -48
- package/src/components/workspace-dock.tsx +141 -175
- package/src/hooks/channel-cache.ts +104 -0
- package/src/hooks/shared-machines-list.ts +239 -0
- package/src/hooks/use-attached-browsers.ts +5 -1
- package/src/hooks/use-browser-frame-stream.ts +6 -1
- package/src/hooks/use-browser-session.ts +6 -4
- package/src/hooks/use-browser-sessions.ts +7 -1
- package/src/hooks/use-channels.ts +32 -10
- package/src/hooks/use-composer.ts +760 -291
- package/src/hooks/use-computer-frame-stream.ts +14 -9
- package/src/hooks/use-computer-session.ts +16 -14
- package/src/hooks/use-computer-sessions.ts +8 -1
- package/src/hooks/use-last-started-turn-policy.ts +1 -1
- package/src/hooks/use-machine-chip.ts +23 -6
- package/src/hooks/use-machines.ts +104 -6
- package/src/hooks/use-sandbox-files.ts +195 -140
- package/src/hooks/use-sandbox-git.ts +58 -29
- package/src/hooks/use-session-background-commands.ts +56 -0
- package/src/hooks/use-session-capabilities.ts +18 -0
- package/src/hooks/use-turn-queue.ts +111 -12
- package/src/hooks/use-workspace-sessions.ts +5 -1
- package/src/index.ts +26 -4
- package/src/lib/interaction-errors.ts +18 -0
- package/src/machines.ts +9 -1
- package/src/model-policy.ts +10 -2
- package/src/realtime/realtime-control.tsx +200 -37
- package/src/session-context.ts +1 -0
- package/src/session-ui.ts +6 -1
- package/src/session.ts +9 -1
- package/src/timeline/activity-rail.tsx +20 -75
- package/src/timeline/index.ts +4 -0
- package/src/timeline/platform-activity-row.tsx +201 -0
- package/src/timeline/projection.ts +476 -23
- package/src/timeline/screenshot-lightbox.tsx +48 -14
- package/src/timeline/shared.tsx +8 -2
- package/src/timeline/tool-renderers.tsx +104 -6
- package/src/timeline/turn-summary.tsx +6 -1
- package/src/timeline/types.ts +75 -2
- package/src/types/machines.ts +10 -0
- package/styles/compiled.css +115 -104
- package/dist/chunk-5UQPNLIO.js.map +0 -1
- package/dist/chunk-6KLX2RH5.js.map +0 -1
- package/dist/chunk-B6WPZSTT.js.map +0 -1
- package/dist/chunk-HA46CYQK.js.map +0 -1
- package/dist/chunk-IGC6A2ZI.js.map +0 -1
- package/dist/chunk-LLOLUHVG.js +0 -5937
- package/dist/chunk-LLOLUHVG.js.map +0 -1
- package/dist/chunk-MKPIAGVL.js.map +0 -1
- package/dist/chunk-R3AZOAB5.js.map +0 -1
- package/dist/chunk-XBZWOM2K.js.map +0 -1
- package/dist/chunk-Y7OEDUJ5.js.map +0 -1
- package/dist/interactive-workbench-viewers-TOHBXJEE.js +0 -15
- package/dist/queue-surface-implementation-2KVJTYXH.js.map +0 -1
- /package/dist/{interactive-workbench-viewers-TOHBXJEE.js.map → browser-viewer-GXUICIAG.js.map} +0 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseMediaGenerationResult,
|
|
3
|
+
type HumanInputAnswer,
|
|
4
|
+
type HumanInputQuestion,
|
|
5
|
+
type HumanInputResponse,
|
|
3
6
|
type SessionEvent,
|
|
4
7
|
type SessionStatus,
|
|
5
8
|
type TimelineAnnotation,
|
|
@@ -18,9 +21,13 @@ import type {
|
|
|
18
21
|
AuthNeededItem,
|
|
19
22
|
ContextCompactionItem,
|
|
20
23
|
GoalItem,
|
|
24
|
+
HumanInputAnswerSummary,
|
|
25
|
+
HumanInputItem,
|
|
21
26
|
MachineInputBatchItem,
|
|
22
27
|
MemoryItem,
|
|
23
28
|
SandboxItem,
|
|
29
|
+
StartupPhase,
|
|
30
|
+
StartupPhaseItem,
|
|
24
31
|
SessionStatusItem,
|
|
25
32
|
TimelineGroup,
|
|
26
33
|
TimelineItem,
|
|
@@ -124,6 +131,92 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
124
131
|
const items: TimelineItem[] = [];
|
|
125
132
|
const prescan = prescanTurnAnchors(events);
|
|
126
133
|
const ordered = orderTimelineEvents(events, prescan);
|
|
134
|
+
const humanInputRequests = humanInputRequestsById(events);
|
|
135
|
+
const humanInputToolCallIds = new Set(
|
|
136
|
+
[...humanInputRequests.values()]
|
|
137
|
+
.map((request) => stringValue(request.toolCallId))
|
|
138
|
+
.filter(Boolean),
|
|
139
|
+
);
|
|
140
|
+
const queuedAtByTurn = new Map<string, string>();
|
|
141
|
+
const startupRecoveryRevisionByTurn = new Map<string, number>();
|
|
142
|
+
const startupPhases = new Map<string, readonly [StartupPhaseItem, string | null, number]>();
|
|
143
|
+
let startupEvent: SessionEvent;
|
|
144
|
+
let startupTurnId: string | null = null;
|
|
145
|
+
let startupAttemptId: string | null = null;
|
|
146
|
+
let startupRecoveryRevision = 0;
|
|
147
|
+
|
|
148
|
+
const settleStartupPhase = (
|
|
149
|
+
phase: StartupPhase,
|
|
150
|
+
status: StartupPhaseItem["status"],
|
|
151
|
+
duration: number | null,
|
|
152
|
+
outcome: StartupPhaseItem["outcome"],
|
|
153
|
+
mode = 0,
|
|
154
|
+
explicitStartedAt?: string,
|
|
155
|
+
explicitId?: string,
|
|
156
|
+
): void => {
|
|
157
|
+
const key = `${startupTurnId}:${phase}`;
|
|
158
|
+
const priorState = startupPhases.get(key);
|
|
159
|
+
const prior = priorState?.[0];
|
|
160
|
+
const comparedByAttempt = priorState?.[1] && startupAttemptId;
|
|
161
|
+
const sameAttempt = comparedByAttempt
|
|
162
|
+
? priorState[1] === startupAttemptId
|
|
163
|
+
: (priorState?.[2] ?? 0) === startupRecoveryRevision;
|
|
164
|
+
const open = prior?.status === "running" && sameAttempt ? prior : undefined;
|
|
165
|
+
const replacesPriorAttempt =
|
|
166
|
+
prior !== undefined &&
|
|
167
|
+
(comparedByAttempt ? !sameAttempt : startupRecoveryRevision > (priorState?.[2] ?? 0));
|
|
168
|
+
// A later activity attempt may replay preparation that the logical turn had
|
|
169
|
+
// already completed before the worker moved. Keep that first successful
|
|
170
|
+
// landmark; only an abandoned or failed attempt needs replacement.
|
|
171
|
+
if (replacesPriorAttempt && prior.status === "complete") return;
|
|
172
|
+
if (mode === 2 && prior && !replacesPriorAttempt) return;
|
|
173
|
+
if (mode === 1 && !open) return;
|
|
174
|
+
if (!replacesPriorAttempt && status !== "running" && open) {
|
|
175
|
+
open.status = status;
|
|
176
|
+
open.completedAt = startupEvent.occurredAt;
|
|
177
|
+
open.durationMs =
|
|
178
|
+
duration === null
|
|
179
|
+
? elapsedDurationMs(open.startedAt, startupEvent.occurredAt)
|
|
180
|
+
: Math.max(0, duration);
|
|
181
|
+
open.outcome = outcome ?? open.outcome;
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const running = status === "running";
|
|
185
|
+
const durationMs = running || duration === null ? null : Math.max(0, duration);
|
|
186
|
+
const completedEpoch = Date.parse(startupEvent.occurredAt);
|
|
187
|
+
const startedAt =
|
|
188
|
+
explicitStartedAt ??
|
|
189
|
+
(!running && durationMs !== null && Number.isFinite(completedEpoch)
|
|
190
|
+
? new Date(completedEpoch - durationMs).toISOString()
|
|
191
|
+
: startupEvent.occurredAt);
|
|
192
|
+
if (replacesPriorAttempt) {
|
|
193
|
+
Object.assign(prior, {
|
|
194
|
+
id: explicitId ?? startupEvent.id,
|
|
195
|
+
status,
|
|
196
|
+
startedAt,
|
|
197
|
+
completedAt: running ? null : startupEvent.occurredAt,
|
|
198
|
+
durationMs,
|
|
199
|
+
outcome,
|
|
200
|
+
occurredAt: startedAt,
|
|
201
|
+
} satisfies Partial<StartupPhaseItem>);
|
|
202
|
+
startupPhases.set(key, [prior, startupAttemptId, startupRecoveryRevision]);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const item: StartupPhaseItem = {
|
|
206
|
+
kind: "startup-phase",
|
|
207
|
+
id: explicitId ?? startupEvent.id,
|
|
208
|
+
turnId: startupTurnId,
|
|
209
|
+
phase,
|
|
210
|
+
status,
|
|
211
|
+
startedAt,
|
|
212
|
+
completedAt: running ? null : startupEvent.occurredAt,
|
|
213
|
+
durationMs,
|
|
214
|
+
outcome,
|
|
215
|
+
occurredAt: startedAt,
|
|
216
|
+
};
|
|
217
|
+
items.push(item);
|
|
218
|
+
startupPhases.set(key, [item, startupAttemptId, startupRecoveryRevision]);
|
|
219
|
+
};
|
|
127
220
|
|
|
128
221
|
const last = (): TimelineItem | undefined => items[items.length - 1];
|
|
129
222
|
|
|
@@ -138,6 +231,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
138
231
|
const finalizeOpen = (
|
|
139
232
|
turnId?: string | null,
|
|
140
233
|
disposition: "complete" | "failed" | "cancelled" = "complete",
|
|
234
|
+
completedAt?: string,
|
|
141
235
|
): void => {
|
|
142
236
|
for (const item of items) {
|
|
143
237
|
if (
|
|
@@ -158,12 +252,47 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
158
252
|
if (item.kind === "sandbox" && item.status === "running") {
|
|
159
253
|
item.status = disposition;
|
|
160
254
|
}
|
|
255
|
+
if (item.kind === "startup-phase" && item.status === "running") {
|
|
256
|
+
item.status = disposition;
|
|
257
|
+
if (completedAt) {
|
|
258
|
+
item.completedAt = completedAt;
|
|
259
|
+
item.durationMs = elapsedDurationMs(item.startedAt, completedAt);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
161
262
|
}
|
|
162
263
|
};
|
|
163
264
|
|
|
164
265
|
for (const event of ordered) {
|
|
165
266
|
const payload = asRecord(event.payload);
|
|
166
267
|
const turnId = event.turnId ?? null;
|
|
268
|
+
startupEvent = event;
|
|
269
|
+
startupTurnId = turnId;
|
|
270
|
+
startupAttemptId =
|
|
271
|
+
(typeof event.turnAttemptId === "string" ? event.turnAttemptId : null) ??
|
|
272
|
+
stringValue(payload.attemptId);
|
|
273
|
+
startupRecoveryRevision = turnId ? (startupRecoveryRevisionByTurn.get(turnId) ?? 0) : 0;
|
|
274
|
+
const setupPhase = event.type.startsWith("rig.setup.")
|
|
275
|
+
? "rig"
|
|
276
|
+
: event.type.startsWith("turn.startup.phase.")
|
|
277
|
+
? startupPhaseFromPayload(payload.phase)
|
|
278
|
+
: null;
|
|
279
|
+
const setupStatus = event.type.endsWith(".started")
|
|
280
|
+
? "running"
|
|
281
|
+
: event.type.endsWith(".failed")
|
|
282
|
+
? "failed"
|
|
283
|
+
: event.type.endsWith(".completed") || event.type === "rig.setup.skipped"
|
|
284
|
+
? "complete"
|
|
285
|
+
: null;
|
|
286
|
+
if (setupPhase && setupStatus) {
|
|
287
|
+
closeStreamingTail();
|
|
288
|
+
settleStartupPhase(
|
|
289
|
+
setupPhase,
|
|
290
|
+
setupStatus,
|
|
291
|
+
numberOrNull(payload.durationMs),
|
|
292
|
+
event.type === "rig.setup.skipped" ? "skipped" : null,
|
|
293
|
+
);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
167
296
|
|
|
168
297
|
switch (event.type) {
|
|
169
298
|
case "user.message": {
|
|
@@ -191,6 +320,9 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
191
320
|
items.push({
|
|
192
321
|
kind: "user-message",
|
|
193
322
|
id: event.id,
|
|
323
|
+
...(event.clientEventId
|
|
324
|
+
? { reconciliationKey: `user-message:${event.clientEventId}` }
|
|
325
|
+
: {}),
|
|
194
326
|
text: voiceMessage?.text ?? stringValue(payload.text),
|
|
195
327
|
annotations: timelineAnnotations(payload.annotations),
|
|
196
328
|
annotationSource: {
|
|
@@ -209,6 +341,18 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
209
341
|
break;
|
|
210
342
|
}
|
|
211
343
|
|
|
344
|
+
case "user.humanInputResponse": {
|
|
345
|
+
// A structured answer resumes the frozen tool call rather than adding a
|
|
346
|
+
// synthetic user.message to model history. Keep the original question
|
|
347
|
+
// and the resolved human choice together as first-class chat history;
|
|
348
|
+
// neither belongs behind the generic activity fold.
|
|
349
|
+
const item = humanInputItemFromResponse(event, payload, humanInputRequests);
|
|
350
|
+
if (!item) break;
|
|
351
|
+
closeStreamingTail();
|
|
352
|
+
items.push(item);
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
|
|
212
356
|
case "system.update.delivered": {
|
|
213
357
|
const inputs = machineInputMembers(payload.members);
|
|
214
358
|
if (inputs.length === 0) break;
|
|
@@ -349,6 +493,12 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
349
493
|
const callId = typeof payload.id === "string" ? payload.id : null;
|
|
350
494
|
const args = payload.arguments ?? null;
|
|
351
495
|
closeStreamingTail();
|
|
496
|
+
if (callId && humanInputToolCallIds.has(callId)) {
|
|
497
|
+
// The matching session.humanInput.requested / response lifecycle owns
|
|
498
|
+
// the visible chat card. Rendering the raw tool too would hide the
|
|
499
|
+
// decision behind a duplicate generic "Ask" step.
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
352
502
|
if (
|
|
353
503
|
toolMatchesLeaf(name, WORKER_SPAWN_TOOL) ||
|
|
354
504
|
toolMatchesLeaf(name, WORKER_MESSAGE_TOOL)
|
|
@@ -452,15 +602,58 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
452
602
|
break;
|
|
453
603
|
}
|
|
454
604
|
|
|
605
|
+
case "turn.queued": {
|
|
606
|
+
const queuedTurnId = turnId ?? (typeof payload.turnId === "string" ? payload.turnId : null);
|
|
607
|
+
if (queuedTurnId) {
|
|
608
|
+
queuedAtByTurn.set(queuedTurnId, event.occurredAt);
|
|
609
|
+
}
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
case "turn.started": {
|
|
614
|
+
if (!turnId) break;
|
|
615
|
+
const queuedAt = queuedAtByTurn.get(turnId);
|
|
616
|
+
if (!queuedAt) break;
|
|
617
|
+
// Recovery starts the same logical turn again. Queue latency belongs to
|
|
618
|
+
// the first admission only, so consume the queued timestamp after its
|
|
619
|
+
// first projection instead of manufacturing another queue span.
|
|
620
|
+
queuedAtByTurn.delete(turnId);
|
|
621
|
+
closeStreamingTail();
|
|
622
|
+
settleStartupPhase(
|
|
623
|
+
"queue",
|
|
624
|
+
"complete",
|
|
625
|
+
elapsedDurationMs(queuedAt, event.occurredAt),
|
|
626
|
+
null,
|
|
627
|
+
0,
|
|
628
|
+
queuedAt,
|
|
629
|
+
`${event.id}-queue`,
|
|
630
|
+
);
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
|
|
455
634
|
case "sandbox.operation.started":
|
|
456
635
|
case "sandbox.operation.completed":
|
|
457
636
|
case "sandbox.operation.failed": {
|
|
458
637
|
const name = typeof payload.name === "string" ? payload.name : "sandbox";
|
|
459
638
|
const status = event.type.endsWith(".failed")
|
|
460
|
-
?
|
|
639
|
+
? payload.expectedTransition === true
|
|
640
|
+
? "cancelled"
|
|
641
|
+
: "failed"
|
|
461
642
|
: event.type.endsWith(".completed")
|
|
462
643
|
? "complete"
|
|
463
644
|
: "running";
|
|
645
|
+
const origin =
|
|
646
|
+
payload.origin === "created" ||
|
|
647
|
+
payload.origin === "restored" ||
|
|
648
|
+
payload.origin === "resumed"
|
|
649
|
+
? payload.origin
|
|
650
|
+
: null;
|
|
651
|
+
const startupPhase = startupPhaseForSandboxOperation(name);
|
|
652
|
+
if (startupPhase) {
|
|
653
|
+
closeStreamingTail();
|
|
654
|
+
settleStartupPhase(startupPhase, status, numberOrNull(payload.durationMs), origin);
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
464
657
|
// Routine per-turn platform plumbing that runs before EVERY turn to
|
|
465
658
|
// guarantee box contents survive a re-warm — NOT the agent redoing work:
|
|
466
659
|
// - repository-clone: idempotent clone check + off-manifest token re-seed;
|
|
@@ -480,13 +673,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
480
673
|
const existing = findOpenSandbox(items, name);
|
|
481
674
|
if (existing && status !== "running") {
|
|
482
675
|
existing.status = status;
|
|
483
|
-
if (
|
|
484
|
-
payload.origin === "created" ||
|
|
485
|
-
payload.origin === "restored" ||
|
|
486
|
-
payload.origin === "resumed"
|
|
487
|
-
) {
|
|
488
|
-
existing.origin = payload.origin;
|
|
489
|
-
}
|
|
676
|
+
if (origin) existing.origin = origin;
|
|
490
677
|
const message = failureMessage(payload);
|
|
491
678
|
if (message) {
|
|
492
679
|
existing.output = existing.output ? `${existing.output}\n${message}` : message;
|
|
@@ -502,12 +689,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
502
689
|
name,
|
|
503
690
|
command: typeof payload.command === "string" ? payload.command : null,
|
|
504
691
|
output: failureMessage(payload) ?? "",
|
|
505
|
-
origin
|
|
506
|
-
payload.origin === "created" ||
|
|
507
|
-
payload.origin === "restored" ||
|
|
508
|
-
payload.origin === "resumed"
|
|
509
|
-
? payload.origin
|
|
510
|
-
: null,
|
|
692
|
+
origin,
|
|
511
693
|
status,
|
|
512
694
|
occurredAt: event.occurredAt,
|
|
513
695
|
});
|
|
@@ -515,6 +697,29 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
515
697
|
break;
|
|
516
698
|
}
|
|
517
699
|
|
|
700
|
+
case "agent.model.request": {
|
|
701
|
+
const requestPhase = typeof payload.phase === "string" ? payload.phase : null;
|
|
702
|
+
const status =
|
|
703
|
+
requestPhase === "started"
|
|
704
|
+
? "running"
|
|
705
|
+
: requestPhase === "first_byte" || requestPhase === "first_event"
|
|
706
|
+
? "complete"
|
|
707
|
+
: requestPhase === "failed" || requestPhase === "timed_out"
|
|
708
|
+
? "failed"
|
|
709
|
+
: null;
|
|
710
|
+
if (status) {
|
|
711
|
+
if (status === "running") closeStreamingTail();
|
|
712
|
+
settleStartupPhase(
|
|
713
|
+
"provider_first_byte",
|
|
714
|
+
status,
|
|
715
|
+
status === "running" ? null : numberOrNull(payload.durationMs),
|
|
716
|
+
null,
|
|
717
|
+
status === "running" ? 2 : 1,
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
|
|
518
723
|
case "sandbox.command.output.delta": {
|
|
519
724
|
// `chunk` is the canonical wire field; text/output are legacy shapes.
|
|
520
725
|
const text =
|
|
@@ -572,7 +777,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
572
777
|
}
|
|
573
778
|
|
|
574
779
|
case "session.requiresAction": {
|
|
575
|
-
finalizeOpen(turnId);
|
|
780
|
+
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
576
781
|
items.push({
|
|
577
782
|
kind: "notice",
|
|
578
783
|
id: event.id,
|
|
@@ -639,6 +844,12 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
639
844
|
// Recovery requests are durable control-plane evidence, not a user
|
|
640
845
|
// message or proof that recovery succeeded. Live state already exposes
|
|
641
846
|
// a genuinely recovering session; keep the raw event in Debug/audit.
|
|
847
|
+
if (turnId) {
|
|
848
|
+
startupRecoveryRevisionByTurn.set(
|
|
849
|
+
turnId,
|
|
850
|
+
(startupRecoveryRevisionByTurn.get(turnId) ?? 0) + 1,
|
|
851
|
+
);
|
|
852
|
+
}
|
|
642
853
|
break;
|
|
643
854
|
}
|
|
644
855
|
|
|
@@ -700,7 +911,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
700
911
|
// adding a generic turn chip would falsely make it look like an
|
|
701
912
|
// extra agent response.
|
|
702
913
|
if (payload.maintenance === "context_compaction") {
|
|
703
|
-
finalizeOpen(turnId);
|
|
914
|
+
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
704
915
|
break;
|
|
705
916
|
}
|
|
706
917
|
// Credit exhaustion arrives as a NOMINALLY completed turn (`detail:
|
|
@@ -710,7 +921,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
710
921
|
// session looking healthy while every future turn silently dies, so it
|
|
711
922
|
// projects exactly like a failed turn plus an explicit notice.
|
|
712
923
|
if (isCreditExhaustionPayload(payload)) {
|
|
713
|
-
finalizeOpen(turnId);
|
|
924
|
+
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
714
925
|
items.push(turnEndItem(event, "failed", CREDIT_EXHAUSTION_MESSAGE));
|
|
715
926
|
items.push({
|
|
716
927
|
kind: "notice",
|
|
@@ -721,7 +932,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
721
932
|
});
|
|
722
933
|
break;
|
|
723
934
|
}
|
|
724
|
-
finalizeOpen(turnId);
|
|
935
|
+
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
725
936
|
items.push(turnEndItem(event, "complete", null));
|
|
726
937
|
break;
|
|
727
938
|
}
|
|
@@ -738,7 +949,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
738
949
|
// spent once, on the turn-level outcome. Items caught mid-flight read
|
|
739
950
|
// as calm "interrupted" (same as turn.cancelled); an item that itself
|
|
740
951
|
// failed keeps its own failed status from its output event.
|
|
741
|
-
finalizeOpen(turnId, "cancelled");
|
|
952
|
+
finalizeOpen(turnId, "cancelled", event.occurredAt);
|
|
742
953
|
items.push(turnEndItem(event, "failed", failureText));
|
|
743
954
|
if (!hadActivity) {
|
|
744
955
|
items.push({
|
|
@@ -760,7 +971,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
760
971
|
break;
|
|
761
972
|
}
|
|
762
973
|
const hadActivity = hasTurnActivity(items, turnId);
|
|
763
|
-
finalizeOpen(turnId, "cancelled");
|
|
974
|
+
finalizeOpen(turnId, "cancelled", event.occurredAt);
|
|
764
975
|
items.push(turnEndItem(event, "cancelled", null));
|
|
765
976
|
if (!hadActivity) {
|
|
766
977
|
items.push({
|
|
@@ -802,6 +1013,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
802
1013
|
case "goal.paused":
|
|
803
1014
|
case "goal.resumed":
|
|
804
1015
|
case "goal.cleared":
|
|
1016
|
+
case "goal.held":
|
|
805
1017
|
case "goal.continuation": {
|
|
806
1018
|
// Agent tool mutations already appear as tool-call rows in the activity
|
|
807
1019
|
// cluster. Re-emitting them as GoalRow landmarks splits "N steps" mid-turn.
|
|
@@ -812,7 +1024,10 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
812
1024
|
kind: "goal",
|
|
813
1025
|
id: event.id,
|
|
814
1026
|
action: event.type.slice("goal.".length) as GoalItem["action"],
|
|
815
|
-
text:
|
|
1027
|
+
text:
|
|
1028
|
+
event.type === "goal.held" && typeof payload.reason === "string" && payload.reason
|
|
1029
|
+
? payload.reason
|
|
1030
|
+
: goalText(payload),
|
|
816
1031
|
occurredAt: event.occurredAt,
|
|
817
1032
|
});
|
|
818
1033
|
break;
|
|
@@ -849,6 +1064,134 @@ function realtimeVoiceMessage(payload: Record<string, unknown>): {
|
|
|
849
1064
|
return null;
|
|
850
1065
|
}
|
|
851
1066
|
|
|
1067
|
+
function humanInputRequestsById(events: SessionEvent[]): Map<string, Record<string, unknown>> {
|
|
1068
|
+
const requests = new Map<string, Record<string, unknown>>();
|
|
1069
|
+
for (const event of events) {
|
|
1070
|
+
if (event.type !== "session.humanInput.requested") continue;
|
|
1071
|
+
const request = asRecord(asRecord(event.payload).request);
|
|
1072
|
+
const requestId = stringValue(request.id);
|
|
1073
|
+
if (requestId) requests.set(requestId, request);
|
|
1074
|
+
}
|
|
1075
|
+
return requests;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function humanInputItemFromResponse(
|
|
1079
|
+
event: SessionEvent,
|
|
1080
|
+
payload: Record<string, unknown>,
|
|
1081
|
+
requests: Map<string, Record<string, unknown>>,
|
|
1082
|
+
): HumanInputItem | null {
|
|
1083
|
+
const response = asRecord(payload.response);
|
|
1084
|
+
const outcome = response.outcome;
|
|
1085
|
+
if (
|
|
1086
|
+
outcome !== "answered" &&
|
|
1087
|
+
outcome !== "skipped" &&
|
|
1088
|
+
outcome !== "expired" &&
|
|
1089
|
+
outcome !== "cancelled"
|
|
1090
|
+
) {
|
|
1091
|
+
return null;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
const requestId = stringValue(payload.requestId);
|
|
1095
|
+
const request = requests.get(requestId);
|
|
1096
|
+
const questions = humanInputQuestions(request?.questions);
|
|
1097
|
+
const normalizedResponse: HumanInputResponse =
|
|
1098
|
+
outcome === "answered"
|
|
1099
|
+
? {
|
|
1100
|
+
outcome,
|
|
1101
|
+
answers: Array.isArray(response.answers)
|
|
1102
|
+
? response.answers
|
|
1103
|
+
.map(asRecord)
|
|
1104
|
+
.map(humanInputAnswer)
|
|
1105
|
+
.filter((answer): answer is HumanInputAnswer => answer !== null)
|
|
1106
|
+
: [],
|
|
1107
|
+
}
|
|
1108
|
+
: { outcome };
|
|
1109
|
+
const questionsById = new Map(questions.map((question) => [question.id, question]));
|
|
1110
|
+
const answers =
|
|
1111
|
+
normalizedResponse.outcome === "answered"
|
|
1112
|
+
? normalizedResponse.answers.map((answer) =>
|
|
1113
|
+
humanInputAnswerSummary(answer, questionsById.get(answer.questionId)),
|
|
1114
|
+
)
|
|
1115
|
+
: [];
|
|
1116
|
+
return {
|
|
1117
|
+
kind: "human-input",
|
|
1118
|
+
id: event.id,
|
|
1119
|
+
turnId: event.turnId ?? null,
|
|
1120
|
+
requestId,
|
|
1121
|
+
questions,
|
|
1122
|
+
response: normalizedResponse,
|
|
1123
|
+
answers,
|
|
1124
|
+
occurredAt: event.occurredAt,
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
function humanInputQuestions(value: unknown): HumanInputQuestion[] {
|
|
1129
|
+
if (!Array.isArray(value)) return [];
|
|
1130
|
+
return value.map(asRecord).flatMap((question) => {
|
|
1131
|
+
const id = stringValue(question.id);
|
|
1132
|
+
const prompt = stringValue(question.prompt);
|
|
1133
|
+
const kind = question.kind;
|
|
1134
|
+
if (
|
|
1135
|
+
!id ||
|
|
1136
|
+
!prompt ||
|
|
1137
|
+
(kind !== "text" && kind !== "single_select" && kind !== "multi_select")
|
|
1138
|
+
) {
|
|
1139
|
+
return [];
|
|
1140
|
+
}
|
|
1141
|
+
const options = Array.isArray(question.options)
|
|
1142
|
+
? question.options.map(asRecord).flatMap((option) => {
|
|
1143
|
+
const optionId = stringValue(option.id);
|
|
1144
|
+
const label = stringValue(option.label);
|
|
1145
|
+
return optionId && label
|
|
1146
|
+
? [{ id: optionId, label, description: stringValue(option.description) || null }]
|
|
1147
|
+
: [];
|
|
1148
|
+
})
|
|
1149
|
+
: [];
|
|
1150
|
+
return [
|
|
1151
|
+
{
|
|
1152
|
+
id,
|
|
1153
|
+
kind,
|
|
1154
|
+
prompt,
|
|
1155
|
+
label: stringValue(question.label) || null,
|
|
1156
|
+
helpText: stringValue(question.helpText) || null,
|
|
1157
|
+
options,
|
|
1158
|
+
required: question.required === true,
|
|
1159
|
+
allowOther: question.allowOther === true,
|
|
1160
|
+
},
|
|
1161
|
+
];
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
function humanInputAnswer(value: Record<string, unknown>): HumanInputAnswer | null {
|
|
1166
|
+
const questionId = stringValue(value.questionId);
|
|
1167
|
+
if (!questionId) return null;
|
|
1168
|
+
return {
|
|
1169
|
+
questionId,
|
|
1170
|
+
values: Array.isArray(value.values)
|
|
1171
|
+
? value.values.filter((answer): answer is string => typeof answer === "string")
|
|
1172
|
+
: [],
|
|
1173
|
+
...(typeof value.other === "string" && value.other ? { other: value.other } : {}),
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function humanInputAnswerSummary(
|
|
1178
|
+
answer: HumanInputAnswer,
|
|
1179
|
+
question: HumanInputQuestion | undefined,
|
|
1180
|
+
): HumanInputAnswerSummary {
|
|
1181
|
+
const optionLabels = new Map(question?.options.map((option) => [option.id, option.label]) ?? []);
|
|
1182
|
+
const values = answer.values.map((value) => optionLabels.get(value) || value);
|
|
1183
|
+
if (answer.other) values.push(answer.other);
|
|
1184
|
+
const fallbackLabel = answer.questionId
|
|
1185
|
+
.replace(/[_-]+/g, " ")
|
|
1186
|
+
.replace(/\b\w/g, (character) => character.toUpperCase());
|
|
1187
|
+
return {
|
|
1188
|
+
questionId: answer.questionId,
|
|
1189
|
+
label: question?.label || question?.prompt || fallbackLabel,
|
|
1190
|
+
prompt: question?.prompt || fallbackLabel,
|
|
1191
|
+
values,
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
|
|
852
1195
|
function providerNativeToolStatus(rawValue: unknown): ToolCallItem["status"] {
|
|
853
1196
|
const raw = asRecord(rawValue);
|
|
854
1197
|
if (raw.type !== "hosted_tool_call") {
|
|
@@ -972,6 +1315,7 @@ function isActivityItem(item: TimelineItem): item is ActivityItem {
|
|
|
972
1315
|
case "tool-call":
|
|
973
1316
|
case "worker":
|
|
974
1317
|
case "sandbox":
|
|
1318
|
+
case "startup-phase":
|
|
975
1319
|
case "memory":
|
|
976
1320
|
case "fleet-decision":
|
|
977
1321
|
return true;
|
|
@@ -1012,6 +1356,8 @@ type TurnAnchorPrescan = {
|
|
|
1012
1356
|
queuedTurnByTrigger: Map<string, string>;
|
|
1013
1357
|
startSeqByTrigger: Map<string, number>;
|
|
1014
1358
|
cancelledBeforeStartTriggers: Set<string>;
|
|
1359
|
+
directChatSequenceByTrigger: Map<string, number>;
|
|
1360
|
+
explicitQueuedTriggers: Set<string>;
|
|
1015
1361
|
startedTurnIds: Set<string>;
|
|
1016
1362
|
};
|
|
1017
1363
|
|
|
@@ -1020,18 +1366,61 @@ function prescanTurnAnchors(events: SessionEvent[]): TurnAnchorPrescan {
|
|
|
1020
1366
|
const queuedTurnByTrigger = new Map<string, string>();
|
|
1021
1367
|
const startSeqByTrigger = new Map<string, number>();
|
|
1022
1368
|
const cancelledTurnIds = new Set<string>();
|
|
1369
|
+
const withdrawnTurnIds = new Set<string>();
|
|
1023
1370
|
const fallbackSeqByTurn = new Map<string, number>();
|
|
1371
|
+
const directChatSequenceByTrigger = new Map<string, number>();
|
|
1372
|
+
const explicitQueuedTriggers = new Set<string>();
|
|
1373
|
+
const queueSteerSequenceByTurn = new Map<string, number>();
|
|
1024
1374
|
const startedTurnIds = new Set<string>();
|
|
1375
|
+
const userMessageSequenceById = new Map<string, number>();
|
|
1025
1376
|
|
|
1026
1377
|
for (const event of ordered) {
|
|
1027
1378
|
const payload = asRecord(event.payload);
|
|
1028
1379
|
const turnId = event.turnId ?? null;
|
|
1380
|
+
if (event.type === "user.message") {
|
|
1381
|
+
userMessageSequenceById.set(event.id, event.sequence);
|
|
1382
|
+
if (payload.routing === "queued_for_execution") {
|
|
1383
|
+
explicitQueuedTriggers.add(event.id);
|
|
1384
|
+
} else if (
|
|
1385
|
+
payload.routing === "accepted_for_execution" ||
|
|
1386
|
+
payload.routing === "accepted_for_steering"
|
|
1387
|
+
) {
|
|
1388
|
+
directChatSequenceByTrigger.set(event.id, event.sequence);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
if (event.type === "user.message" && payload.delivery === "steer") {
|
|
1392
|
+
directChatSequenceByTrigger.set(event.id, event.sequence);
|
|
1393
|
+
}
|
|
1394
|
+
if (event.type === "session.control.steer_requested") {
|
|
1395
|
+
const targetTurnId = typeof payload.targetTurnId === "string" ? payload.targetTurnId : null;
|
|
1396
|
+
if (targetTurnId && !queueSteerSequenceByTurn.has(targetTurnId)) {
|
|
1397
|
+
queueSteerSequenceByTurn.set(targetTurnId, event.sequence);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
if (
|
|
1401
|
+
event.type === "session.queue.changed" &&
|
|
1402
|
+
(payload.operation === "edit" || payload.operation === "delete")
|
|
1403
|
+
) {
|
|
1404
|
+
const withdrawnTurnId =
|
|
1405
|
+
typeof payload.turnId === "string" ? payload.turnId : (turnId ?? null);
|
|
1406
|
+
if (withdrawnTurnId) withdrawnTurnIds.add(withdrawnTurnId);
|
|
1407
|
+
}
|
|
1029
1408
|
if (event.type === "turn.queued") {
|
|
1030
1409
|
const triggerEventId =
|
|
1031
1410
|
typeof payload.triggerEventId === "string" ? payload.triggerEventId : null;
|
|
1032
1411
|
const queuedTurnId = typeof payload.turnId === "string" ? payload.turnId : turnId;
|
|
1033
1412
|
if (triggerEventId && queuedTurnId) {
|
|
1034
1413
|
queuedTurnByTrigger.set(triggerEventId, queuedTurnId);
|
|
1414
|
+
if (
|
|
1415
|
+
(payload.routing === "accepted_for_execution" ||
|
|
1416
|
+
payload.routing === "accepted_for_steering") &&
|
|
1417
|
+
!directChatSequenceByTrigger.has(triggerEventId)
|
|
1418
|
+
) {
|
|
1419
|
+
directChatSequenceByTrigger.set(
|
|
1420
|
+
triggerEventId,
|
|
1421
|
+
userMessageSequenceById.get(triggerEventId) ?? event.sequence,
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1035
1424
|
}
|
|
1036
1425
|
continue;
|
|
1037
1426
|
}
|
|
@@ -1060,6 +1449,10 @@ function prescanTurnAnchors(events: SessionEvent[]): TurnAnchorPrescan {
|
|
|
1060
1449
|
}
|
|
1061
1450
|
|
|
1062
1451
|
for (const [triggerEventId, turnId] of queuedTurnByTrigger) {
|
|
1452
|
+
const queueSteerSequence = queueSteerSequenceByTurn.get(turnId);
|
|
1453
|
+
if (queueSteerSequence !== undefined) {
|
|
1454
|
+
directChatSequenceByTrigger.set(triggerEventId, queueSteerSequence);
|
|
1455
|
+
}
|
|
1063
1456
|
if (!startSeqByTrigger.has(triggerEventId)) {
|
|
1064
1457
|
const fallbackSeq = fallbackSeqByTurn.get(turnId);
|
|
1065
1458
|
if (fallbackSeq !== undefined) {
|
|
@@ -1071,7 +1464,7 @@ function prescanTurnAnchors(events: SessionEvent[]): TurnAnchorPrescan {
|
|
|
1071
1464
|
const cancelledBeforeStartTriggers = new Set<string>();
|
|
1072
1465
|
for (const [triggerEventId, turnId] of queuedTurnByTrigger) {
|
|
1073
1466
|
if (
|
|
1074
|
-
cancelledTurnIds.has(turnId) &&
|
|
1467
|
+
(cancelledTurnIds.has(turnId) || withdrawnTurnIds.has(turnId)) &&
|
|
1075
1468
|
!startSeqByTrigger.has(triggerEventId) &&
|
|
1076
1469
|
!fallbackSeqByTurn.has(turnId)
|
|
1077
1470
|
) {
|
|
@@ -1083,6 +1476,8 @@ function prescanTurnAnchors(events: SessionEvent[]): TurnAnchorPrescan {
|
|
|
1083
1476
|
queuedTurnByTrigger,
|
|
1084
1477
|
startSeqByTrigger,
|
|
1085
1478
|
cancelledBeforeStartTriggers,
|
|
1479
|
+
directChatSequenceByTrigger,
|
|
1480
|
+
explicitQueuedTriggers,
|
|
1086
1481
|
startedTurnIds,
|
|
1087
1482
|
};
|
|
1088
1483
|
}
|
|
@@ -1095,8 +1490,22 @@ function orderTimelineEvents(events: SessionEvent[], prescan: TurnAnchorPrescan)
|
|
|
1095
1490
|
if (event.type !== "user.message") {
|
|
1096
1491
|
continue;
|
|
1097
1492
|
}
|
|
1493
|
+
// Steer is an accepted direction change, not waiting queue copy. Keep it
|
|
1494
|
+
// directly in chat across refresh/reconnect even before its turn starts.
|
|
1495
|
+
const directChatSequence = prescan.directChatSequenceByTrigger.get(event.id);
|
|
1496
|
+
if (directChatSequence !== undefined) {
|
|
1497
|
+
pushInsertion(insertions, directChatSequence, event);
|
|
1498
|
+
continue;
|
|
1499
|
+
}
|
|
1098
1500
|
const queuedTurnId = prescan.queuedTurnByTrigger.get(event.id);
|
|
1099
1501
|
if (!queuedTurnId) {
|
|
1502
|
+
if (prescan.explicitQueuedTriggers.has(event.id)) {
|
|
1503
|
+
const startSeq = prescan.startSeqByTrigger.get(event.id);
|
|
1504
|
+
if (startSeq !== undefined) {
|
|
1505
|
+
pushInsertion(insertions, startSeq, event);
|
|
1506
|
+
}
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1100
1509
|
pushInsertion(insertions, event.sequence, event);
|
|
1101
1510
|
continue;
|
|
1102
1511
|
}
|
|
@@ -1160,6 +1569,7 @@ function isTurnExecutionEvidence(type: string): boolean {
|
|
|
1160
1569
|
type === "session.requiresAction" ||
|
|
1161
1570
|
type === "tool.auth_needed" ||
|
|
1162
1571
|
type === "credential.auth_needed" ||
|
|
1572
|
+
type.startsWith("turn.startup.phase.") ||
|
|
1163
1573
|
type.startsWith("rig.setup.") ||
|
|
1164
1574
|
type === "codex.capacity.waiting" ||
|
|
1165
1575
|
type === "codex.capacity.resumed" ||
|
|
@@ -1303,6 +1713,7 @@ function isTurnBoundary(group: TimelineGroup | undefined): boolean {
|
|
|
1303
1713
|
group?.kind === "turn" ||
|
|
1304
1714
|
(group?.kind === "item" &&
|
|
1305
1715
|
(group.item.kind === "user-message" ||
|
|
1716
|
+
group.item.kind === "human-input" ||
|
|
1306
1717
|
group.item.kind === "context-compaction" ||
|
|
1307
1718
|
(group.item.kind === "machine-input-batch" &&
|
|
1308
1719
|
group.item.members.some((member) => member.kind === "media_generation_result")) ||
|
|
@@ -1435,6 +1846,11 @@ function machineInputMembers(value: unknown): MachineInputBatchItem["members"] {
|
|
|
1435
1846
|
"agent_steer_instruction",
|
|
1436
1847
|
"child_terminal_result",
|
|
1437
1848
|
"media_generation_result",
|
|
1849
|
+
"child_requires_action",
|
|
1850
|
+
"child_requires_action_resolved",
|
|
1851
|
+
"child_paused",
|
|
1852
|
+
"child_waiting_capacity",
|
|
1853
|
+
"child_progress",
|
|
1438
1854
|
]);
|
|
1439
1855
|
const classifications = new Set<MachineInputBatchItem["members"][number]["classification"]>([
|
|
1440
1856
|
"success",
|
|
@@ -1639,6 +2055,38 @@ function findOpenSandbox(items: TimelineItem[], name: string): SandboxItem | und
|
|
|
1639
2055
|
);
|
|
1640
2056
|
}
|
|
1641
2057
|
|
|
2058
|
+
function startupPhaseForSandboxOperation(name: string): StartupPhase | null {
|
|
2059
|
+
return SANDBOX_STARTUP_PHASES[name] ?? null;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
function startupPhaseFromPayload(value: unknown): StartupPhase | null {
|
|
2063
|
+
return typeof value === "string" && STARTUP_PHASES.has(value as StartupPhase)
|
|
2064
|
+
? (value as StartupPhase)
|
|
2065
|
+
: null;
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
const SANDBOX_STARTUP_PHASES: Record<string, StartupPhase> = {
|
|
2069
|
+
"sandbox.provision": "sandbox",
|
|
2070
|
+
"repository-clone": "repository",
|
|
2071
|
+
"file-resource-download": "files",
|
|
2072
|
+
};
|
|
2073
|
+
const STARTUP_PHASES = new Set<StartupPhase>([
|
|
2074
|
+
"queue",
|
|
2075
|
+
"sandbox",
|
|
2076
|
+
"rig",
|
|
2077
|
+
"repository",
|
|
2078
|
+
"files",
|
|
2079
|
+
"tools",
|
|
2080
|
+
"model_preparation",
|
|
2081
|
+
"provider_first_byte",
|
|
2082
|
+
]);
|
|
2083
|
+
|
|
2084
|
+
function elapsedDurationMs(startedAt: string, completedAt: string): number | null {
|
|
2085
|
+
const start = Date.parse(startedAt);
|
|
2086
|
+
const end = Date.parse(completedAt);
|
|
2087
|
+
return Number.isFinite(start) && Number.isFinite(end) ? Math.max(0, end - start) : null;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
1642
2090
|
function failureMessage(payload: Record<string, unknown>): string | null {
|
|
1643
2091
|
for (const key of ["error", "message"] as const) {
|
|
1644
2092
|
const value = payload[key];
|
|
@@ -1675,7 +2123,12 @@ function shouldSuppressAgentGoalLandmark(type: string, payload: Record<string, u
|
|
|
1675
2123
|
if (type === "goal.completed") {
|
|
1676
2124
|
return true;
|
|
1677
2125
|
}
|
|
1678
|
-
if (
|
|
2126
|
+
if (
|
|
2127
|
+
type === "goal.set" ||
|
|
2128
|
+
type === "goal.updated" ||
|
|
2129
|
+
type === "goal.paused" ||
|
|
2130
|
+
type === "goal.held"
|
|
2131
|
+
) {
|
|
1679
2132
|
return payload.actor === "agent";
|
|
1680
2133
|
}
|
|
1681
2134
|
return false;
|