@opengeni/react 3.5.1-canary.0 → 3.7.0-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifacts.d.ts +1 -1
- package/dist/artifacts.js +248 -6
- package/dist/artifacts.js.map +1 -1
- package/dist/{browser-viewer-E5DDEFQ4.js → browser-viewer-NTD6UIPR.js} +3 -3
- package/dist/{chunk-W2RGYBCV.js → chunk-24M4YBCL.js} +4 -4
- package/dist/{chunk-WY3MELVJ.js → chunk-5IXTJTK3.js} +1438 -397
- package/dist/chunk-5IXTJTK3.js.map +1 -0
- package/dist/chunk-5PQJOCX5.js +902 -0
- package/dist/chunk-5PQJOCX5.js.map +1 -0
- package/dist/{chunk-UNCXXIQR.js → chunk-INGB2N5R.js} +4310 -3982
- package/dist/chunk-INGB2N5R.js.map +1 -0
- package/dist/{chunk-TJFMMDQU.js → chunk-MOWYEBCQ.js} +1839 -17
- package/dist/chunk-MOWYEBCQ.js.map +1 -0
- package/dist/chunk-PAJU5KKL.js +331 -0
- package/dist/chunk-PAJU5KKL.js.map +1 -0
- package/dist/{chunk-VTOREEXF.js → chunk-PLYRL5ER.js} +24 -2
- package/dist/{chunk-VTOREEXF.js.map → chunk-PLYRL5ER.js.map} +1 -1
- package/dist/{chunk-YVCHAQZY.js → chunk-ZFOFY5ST.js} +27 -349
- package/dist/chunk-ZFOFY5ST.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/components/artifacts/index.d.ts +1 -1
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +46 -3
- package/dist/components/composer.d.ts +2 -0
- package/dist/components/session-chrome.d.ts +38 -5
- package/dist/components/session-commands-panel.d.ts +6 -0
- package/dist/components/session-conversation.d.ts +14 -0
- package/dist/components/timeline-anchor.d.ts +21 -0
- package/dist/composer.js +2 -3
- package/dist/conversation-timeline.d.ts +5 -0
- package/dist/{fleet-decision-row-GGCAT4E4.js → fleet-decision-row-YZ3ZKWMM.js} +2 -1
- package/dist/fleet-decision-row-YZ3ZKWMM.js.map +1 -0
- package/dist/hooks/use-file-attachments.d.ts +6 -0
- package/dist/hooks/use-sandbox-files.d.ts +4 -1
- package/dist/hooks/use-workspace-edit.d.ts +4 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +823 -965
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +2 -2
- package/dist/lib/format.d.ts +5 -0
- package/dist/machines.js +3 -2
- package/dist/session-ui.d.ts +3 -0
- package/dist/session-ui.js +14 -4
- package/dist/session.js +19 -19
- package/dist/timeline/registry.d.ts +2 -0
- package/dist/timeline/types.d.ts +4 -2
- package/package.json +2 -2
- package/src/artifacts.ts +3 -0
- package/src/client.ts +2 -1
- package/src/components/artifacts/index.ts +3 -0
- package/src/components/artifacts/published-html-artifact-frame.tsx +307 -4
- package/src/components/composer.tsx +89 -25
- package/src/components/machine-input-display.ts +6 -0
- package/src/components/message-timeline.tsx +351 -204
- package/src/components/sandbox-workspace.tsx +11 -1
- package/src/components/session-chrome.tsx +501 -128
- package/src/components/session-commands-panel.tsx +113 -0
- package/src/components/session-conversation.tsx +141 -0
- package/src/components/timeline-anchor.tsx +91 -0
- package/src/conversation-timeline.ts +83 -0
- package/src/hooks/use-file-attachments.ts +34 -0
- package/src/hooks/use-sandbox-files.ts +68 -20
- package/src/hooks/use-session-background-commands.ts +21 -10
- package/src/hooks/use-session-events.ts +56 -39
- package/src/hooks/use-session.ts +23 -2
- package/src/hooks/use-workspace-edit.ts +23 -5
- package/src/index.ts +4 -0
- package/src/lib/format.ts +32 -0
- package/src/session-ui.ts +3 -0
- package/src/timeline/fleet-decision-projection.ts +2 -1
- package/src/timeline/fleet-decision-row.tsx +1 -0
- package/src/timeline/projection.ts +157 -19
- package/src/timeline/registry.ts +15 -3
- package/src/timeline/tool-renderers.tsx +117 -1
- package/src/timeline/types.ts +12 -1
- package/styles/compiled.css +495 -411
- package/styles/index.css +23 -0
- package/dist/chunk-7EAFGICR.js +0 -302
- package/dist/chunk-7EAFGICR.js.map +0 -1
- package/dist/chunk-IB27C53Y.js +0 -2582
- package/dist/chunk-IB27C53Y.js.map +0 -1
- package/dist/chunk-TJFMMDQU.js.map +0 -1
- package/dist/chunk-UNCXXIQR.js.map +0 -1
- package/dist/chunk-WY3MELVJ.js.map +0 -1
- package/dist/chunk-YVCHAQZY.js.map +0 -1
- package/dist/fleet-decision-row-GGCAT4E4.js.map +0 -1
- /package/dist/{browser-viewer-E5DDEFQ4.js.map → browser-viewer-NTD6UIPR.js.map} +0 -0
- /package/dist/{chunk-W2RGYBCV.js.map → chunk-24M4YBCL.js.map} +0 -0
package/src/index.ts
CHANGED
|
@@ -462,6 +462,8 @@ export type { CommandPaletteProps } from "./components/command-palette";
|
|
|
462
462
|
|
|
463
463
|
// Components
|
|
464
464
|
export { ChatComposer } from "./components/chat-composer";
|
|
465
|
+
export { SessionConversation } from "./components/session-conversation";
|
|
466
|
+
export type { SessionConversationProps } from "./components/session-conversation";
|
|
465
467
|
export type { ChatComposerProps } from "./components/chat-composer";
|
|
466
468
|
export { ComposerTranscriptionControl } from "./components/composer-transcription-control";
|
|
467
469
|
export type {
|
|
@@ -600,8 +602,10 @@ export {
|
|
|
600
602
|
formatClockTime,
|
|
601
603
|
formatRelativeTime,
|
|
602
604
|
humanizeFailureReason,
|
|
605
|
+
presentFailure,
|
|
603
606
|
isCreditExhaustion,
|
|
604
607
|
stringifyPayload,
|
|
605
608
|
truncate,
|
|
606
609
|
tryParseJson,
|
|
607
610
|
} from "./lib/format";
|
|
611
|
+
export { SessionCommandsPanel } from "./components/session-commands-panel";
|
package/src/lib/format.ts
CHANGED
|
@@ -186,3 +186,35 @@ export function humanizeFailureReason(reason: string | null): string | null {
|
|
|
186
186
|
}
|
|
187
187
|
return reason;
|
|
188
188
|
}
|
|
189
|
+
|
|
190
|
+
/** Project failure diagnostics without changing the stored event, including legacy retry wrappers. */
|
|
191
|
+
export function presentFailure(payload: Record<string, unknown>): {
|
|
192
|
+
reason: string | null;
|
|
193
|
+
safetyRefusal: boolean;
|
|
194
|
+
} {
|
|
195
|
+
const text = (key: string): string | null => {
|
|
196
|
+
const value = payload[key];
|
|
197
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
198
|
+
};
|
|
199
|
+
const message = text("error") ?? text("message");
|
|
200
|
+
const detail = text("lastRetryableError") ?? text("detail");
|
|
201
|
+
const safetyRefusal =
|
|
202
|
+
payload.code === "provider_safety_refusal" ||
|
|
203
|
+
[message, detail].some(
|
|
204
|
+
(value) =>
|
|
205
|
+
value !== null && /\bthis request was blocked by our safety systems\b/i.test(value),
|
|
206
|
+
);
|
|
207
|
+
if (safetyRefusal) {
|
|
208
|
+
return {
|
|
209
|
+
reason: `The model provider blocked this request.${detail || message ? ` ${detail ?? message}` : ""}`,
|
|
210
|
+
safetyRefusal: true,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
reason:
|
|
215
|
+
detail && detail !== message
|
|
216
|
+
? [humanizeFailureReason(message), humanizeFailureReason(detail)].filter(Boolean).join(" ")
|
|
217
|
+
: humanizeFailureReason(message),
|
|
218
|
+
safetyRefusal: false,
|
|
219
|
+
};
|
|
220
|
+
}
|
package/src/session-ui.ts
CHANGED
|
@@ -25,6 +25,8 @@ export type {
|
|
|
25
25
|
TurnSummaryOptions,
|
|
26
26
|
} from "./timeline/turn-summary";
|
|
27
27
|
export { QueueSurface } from "./components/queue-surface";
|
|
28
|
+
export { SessionConversation } from "./components/session-conversation";
|
|
29
|
+
export type { SessionConversationProps } from "./components/session-conversation";
|
|
28
30
|
export type { QueueSurfaceProps } from "./components/queue-surface";
|
|
29
31
|
export {
|
|
30
32
|
SessionChrome,
|
|
@@ -38,3 +40,4 @@ export type {
|
|
|
38
40
|
SessionChromeSignalId,
|
|
39
41
|
SessionChromeSignalTone,
|
|
40
42
|
} from "./components/session-chrome";
|
|
43
|
+
export { SessionCommandsPanel } from "./components/session-commands-panel";
|
|
@@ -8,6 +8,7 @@ const FLEET_ACTUAL_REASONS = [
|
|
|
8
8
|
"rotation",
|
|
9
9
|
"active",
|
|
10
10
|
"all_capped",
|
|
11
|
+
"allocator_disabled",
|
|
11
12
|
"none",
|
|
12
13
|
] as const;
|
|
13
14
|
const FLEET_SHADOW_OUTCOMES = ["selected", "paced", "none"] as const;
|
|
@@ -334,7 +335,7 @@ function fleetDecisionSemanticsAreConsistent(input: {
|
|
|
334
335
|
input.actualOutcome === "selected"
|
|
335
336
|
? ["lease_reused", "pin", "rotation", "active"].includes(input.actualReason)
|
|
336
337
|
: input.actualOutcome === "waiting"
|
|
337
|
-
? input.actualReason
|
|
338
|
+
? ["all_capped", "allocator_disabled"].includes(input.actualReason)
|
|
338
339
|
: input.actualReason === "none";
|
|
339
340
|
const shadowConsistent =
|
|
340
341
|
input.shadowOutcome === "selected"
|
|
@@ -28,6 +28,7 @@ const FLEET_ACTUAL_REASON_LABEL: Record<FleetDecisionItem["actualReason"], strin
|
|
|
28
28
|
rotation: "Rotated for capacity",
|
|
29
29
|
active: "Used the active subscription",
|
|
30
30
|
all_capped: "All observed subscriptions were capped",
|
|
31
|
+
allocator_disabled: "The policy-selected subscription was disabled for new allocations",
|
|
31
32
|
none: "No production candidate was selected",
|
|
32
33
|
};
|
|
33
34
|
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import fleetDecisionItem from "./fleet-decision-projection";
|
|
11
11
|
import {
|
|
12
12
|
CREDIT_EXHAUSTION_MESSAGE,
|
|
13
|
-
|
|
13
|
+
presentFailure,
|
|
14
14
|
isCreditExhaustion,
|
|
15
15
|
tryParseJson,
|
|
16
16
|
} from "../lib/format";
|
|
@@ -58,6 +58,8 @@ const WORKER_SPAWN_TOOL = "session_create";
|
|
|
58
58
|
const WORKER_MESSAGE_TOOL = "session_send_message";
|
|
59
59
|
const WORKER_FAILURE_CODE_MAX_LENGTH = 128;
|
|
60
60
|
const WORKER_FAILURE_MESSAGE_MAX_UTF8_BYTES = 1_024;
|
|
61
|
+
type PendingWaitOutcome = { id: string; reason: string; occurredAt: string };
|
|
62
|
+
type TrackedAgentResponse = { item: AgentMessageItem; completed: boolean };
|
|
61
63
|
|
|
62
64
|
/**
|
|
63
65
|
* Tools whose durable side-effect events already own the timeline (MemoryRow).
|
|
@@ -131,6 +133,8 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
131
133
|
const items: TimelineItem[] = [];
|
|
132
134
|
const prescan = prescanTurnAnchors(events);
|
|
133
135
|
const ordered = orderTimelineEvents(events, prescan);
|
|
136
|
+
const pendingWaitOutcomeByTurn = new Map<string | null, PendingWaitOutcome>();
|
|
137
|
+
const latestAgentResponseByTurn = new Map<string | null, TrackedAgentResponse>();
|
|
134
138
|
const humanInputRequests = humanInputRequestsById(events);
|
|
135
139
|
const humanInputToolCallIds = new Set(
|
|
136
140
|
[...humanInputRequests.values()]
|
|
@@ -220,6 +224,31 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
220
224
|
|
|
221
225
|
const last = (): TimelineItem | undefined => items[items.length - 1];
|
|
222
226
|
|
|
227
|
+
const rememberAgentResponse = (
|
|
228
|
+
turnId: string | null,
|
|
229
|
+
item: AgentMessageItem,
|
|
230
|
+
completed: boolean,
|
|
231
|
+
): void => {
|
|
232
|
+
latestAgentResponseByTurn.set(turnId, { item, completed });
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const takePendingWaitOutcome = (turnId: string | null): PendingWaitOutcome | undefined => {
|
|
236
|
+
const outcome = pendingWaitOutcomeByTurn.get(turnId);
|
|
237
|
+
pendingWaitOutcomeByTurn.delete(turnId);
|
|
238
|
+
return outcome;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const takeAgentResponse = (turnId: string | null): TrackedAgentResponse | undefined => {
|
|
242
|
+
const response = latestAgentResponseByTurn.get(turnId);
|
|
243
|
+
latestAgentResponseByTurn.delete(turnId);
|
|
244
|
+
return response;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const clearUnscopedTerminalTracking = (): void => {
|
|
248
|
+
latestAgentResponseByTurn.delete(null);
|
|
249
|
+
pendingWaitOutcomeByTurn.delete(null);
|
|
250
|
+
};
|
|
251
|
+
|
|
223
252
|
/** A new item of a different kind ends whatever was streaming at the tail. */
|
|
224
253
|
const closeStreamingTail = (): void => {
|
|
225
254
|
const open = last();
|
|
@@ -298,6 +327,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
298
327
|
case "user.message": {
|
|
299
328
|
// A steering message must not mark in-flight tools complete; it only
|
|
300
329
|
// ends whatever text was streaming. Turn lifecycle events finalize.
|
|
330
|
+
clearUnscopedTerminalTracking();
|
|
301
331
|
closeStreamingTail();
|
|
302
332
|
const childCompletion = workerCompletionPayload(payload.childCompletion);
|
|
303
333
|
if (childCompletion) {
|
|
@@ -381,17 +411,20 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
381
411
|
const open = last();
|
|
382
412
|
if (open?.kind === "agent-message" && open.streaming && open.turnId === turnId) {
|
|
383
413
|
open.text += text;
|
|
414
|
+
rememberAgentResponse(turnId, open, false);
|
|
384
415
|
break;
|
|
385
416
|
}
|
|
386
417
|
closeStreamingTail();
|
|
387
|
-
|
|
418
|
+
const item: AgentMessageItem = {
|
|
388
419
|
kind: "agent-message",
|
|
389
420
|
id: event.id,
|
|
390
421
|
turnId,
|
|
391
422
|
text,
|
|
392
423
|
streaming: true,
|
|
393
424
|
occurredAt: event.occurredAt,
|
|
394
|
-
}
|
|
425
|
+
};
|
|
426
|
+
items.push(item);
|
|
427
|
+
rememberAgentResponse(turnId, item, false);
|
|
395
428
|
break;
|
|
396
429
|
}
|
|
397
430
|
|
|
@@ -447,10 +480,11 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
447
480
|
items.splice(openIndex, 1);
|
|
448
481
|
items.push(open);
|
|
449
482
|
}
|
|
483
|
+
rememberAgentResponse(turnId, open, true);
|
|
450
484
|
break;
|
|
451
485
|
}
|
|
452
486
|
if (text) {
|
|
453
|
-
|
|
487
|
+
const item: AgentMessageItem = {
|
|
454
488
|
kind: "agent-message",
|
|
455
489
|
id: event.id,
|
|
456
490
|
turnId,
|
|
@@ -466,7 +500,9 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
466
500
|
turnId,
|
|
467
501
|
text,
|
|
468
502
|
},
|
|
469
|
-
}
|
|
503
|
+
};
|
|
504
|
+
items.push(item);
|
|
505
|
+
rememberAgentResponse(turnId, item, true);
|
|
470
506
|
}
|
|
471
507
|
break;
|
|
472
508
|
}
|
|
@@ -616,6 +652,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
616
652
|
}
|
|
617
653
|
|
|
618
654
|
case "turn.started": {
|
|
655
|
+
if (turnId) clearUnscopedTerminalTracking();
|
|
619
656
|
if (!turnId) break;
|
|
620
657
|
const queuedAt = queuedAtByTurn.get(turnId);
|
|
621
658
|
if (!queuedAt) break;
|
|
@@ -781,6 +818,20 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
781
818
|
break;
|
|
782
819
|
}
|
|
783
820
|
|
|
821
|
+
case "codex.capacity.waiting": {
|
|
822
|
+
items.push({
|
|
823
|
+
kind: "notice",
|
|
824
|
+
id: event.id,
|
|
825
|
+
tone: "waiting",
|
|
826
|
+
text:
|
|
827
|
+
stringValue(payload.detail) ??
|
|
828
|
+
stringValue(payload.error) ??
|
|
829
|
+
"Waiting for Codex capacity.",
|
|
830
|
+
occurredAt: event.occurredAt,
|
|
831
|
+
});
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
|
|
784
835
|
case "session.requiresAction": {
|
|
785
836
|
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
786
837
|
items.push({
|
|
@@ -922,6 +973,8 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
922
973
|
// extra agent response.
|
|
923
974
|
if (payload.maintenance === "context_compaction") {
|
|
924
975
|
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
976
|
+
takeAgentResponse(turnId);
|
|
977
|
+
takePendingWaitOutcome(turnId);
|
|
925
978
|
break;
|
|
926
979
|
}
|
|
927
980
|
// Credit exhaustion arrives as a NOMINALLY completed turn (`detail:
|
|
@@ -932,6 +985,8 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
932
985
|
// projects exactly like a failed turn plus an explicit notice.
|
|
933
986
|
if (isCreditExhaustionPayload(payload)) {
|
|
934
987
|
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
988
|
+
takeAgentResponse(turnId);
|
|
989
|
+
takePendingWaitOutcome(turnId);
|
|
935
990
|
items.push(turnEndItem(event, "failed", CREDIT_EXHAUSTION_MESSAGE));
|
|
936
991
|
items.push({
|
|
937
992
|
kind: "notice",
|
|
@@ -942,12 +997,69 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
942
997
|
});
|
|
943
998
|
break;
|
|
944
999
|
}
|
|
1000
|
+
const latestAgentResponse = takeAgentResponse(turnId);
|
|
1001
|
+
const finalOutput = stringValue(payload.output);
|
|
1002
|
+
const visibleFinalOutput = stripOpaqueCitationTokens(finalOutput).trim();
|
|
1003
|
+
const visibleTrackedResponse = stripOpaqueCitationTokens(
|
|
1004
|
+
latestAgentResponse?.item.text ?? "",
|
|
1005
|
+
).trim();
|
|
1006
|
+
const finalOutputMirrorsCommentary =
|
|
1007
|
+
latestAgentResponse?.item.phase === "commentary" &&
|
|
1008
|
+
visibleTrackedResponse === visibleFinalOutput;
|
|
1009
|
+
const hasAuthoritativeFinalOutput =
|
|
1010
|
+
Boolean(visibleFinalOutput) && !finalOutputMirrorsCommentary;
|
|
1011
|
+
const pendingWaitOutcome = takePendingWaitOutcome(turnId);
|
|
1012
|
+
if (hasAuthoritativeFinalOutput) {
|
|
1013
|
+
// `agent.message.completed` and `turn.completed` normally commit
|
|
1014
|
+
// together, but legacy or partially compacted ledgers may retain only
|
|
1015
|
+
// the terminal output receipt. Keep that authoritative response
|
|
1016
|
+
// visible instead of leaving it trapped in raw audit data.
|
|
1017
|
+
if (
|
|
1018
|
+
latestAgentResponse &&
|
|
1019
|
+
!latestAgentResponse.completed &&
|
|
1020
|
+
finalOutput.startsWith(latestAgentResponse.item.text)
|
|
1021
|
+
) {
|
|
1022
|
+
latestAgentResponse.item.text = finalOutput;
|
|
1023
|
+
latestAgentResponse.item.streaming = false;
|
|
1024
|
+
latestAgentResponse.item.occurredAt = event.occurredAt;
|
|
1025
|
+
const responseIndex = items.indexOf(latestAgentResponse.item);
|
|
1026
|
+
if (responseIndex >= 0 && responseIndex < items.length - 1) {
|
|
1027
|
+
items.splice(responseIndex, 1);
|
|
1028
|
+
items.push(latestAgentResponse.item);
|
|
1029
|
+
}
|
|
1030
|
+
} else if (visibleTrackedResponse !== visibleFinalOutput) {
|
|
1031
|
+
items.push({
|
|
1032
|
+
kind: "agent-message",
|
|
1033
|
+
id: `${event.id}-output-message`,
|
|
1034
|
+
turnId,
|
|
1035
|
+
text: finalOutput,
|
|
1036
|
+
streaming: false,
|
|
1037
|
+
occurredAt: event.occurredAt,
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
945
1041
|
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
946
1042
|
items.push(turnEndItem(event, "complete", null));
|
|
1043
|
+
const hasCompletedFinalResponse =
|
|
1044
|
+
latestAgentResponse?.completed === true &&
|
|
1045
|
+
latestAgentResponse.item.phase !== "commentary" &&
|
|
1046
|
+
Boolean(visibleTrackedResponse);
|
|
1047
|
+
if (!hasAuthoritativeFinalOutput && !hasCompletedFinalResponse && pendingWaitOutcome) {
|
|
1048
|
+
items.push({
|
|
1049
|
+
kind: "notice",
|
|
1050
|
+
id: `${pendingWaitOutcome.id}-visible-outcome`,
|
|
1051
|
+
tone: "waiting",
|
|
1052
|
+
text: waitingOutcomeText(pendingWaitOutcome.reason),
|
|
1053
|
+
recordedOutcome: true,
|
|
1054
|
+
occurredAt: pendingWaitOutcome.occurredAt,
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
947
1057
|
break;
|
|
948
1058
|
}
|
|
949
1059
|
|
|
950
1060
|
case "turn.failed": {
|
|
1061
|
+
takeAgentResponse(turnId);
|
|
1062
|
+
takePendingWaitOutcome(turnId);
|
|
951
1063
|
const hadActivity = hasTurnActivity(items, turnId);
|
|
952
1064
|
// Credit death can hide behind fields `failureMessage` doesn't read
|
|
953
1065
|
// (detail/segmentLimit), so classify the whole payload before falling
|
|
@@ -980,6 +1092,8 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
980
1092
|
if (turnId && !prescan.startedTurnIds.has(turnId)) {
|
|
981
1093
|
break;
|
|
982
1094
|
}
|
|
1095
|
+
takeAgentResponse(turnId);
|
|
1096
|
+
takePendingWaitOutcome(turnId);
|
|
983
1097
|
const hadActivity = hasTurnActivity(items, turnId);
|
|
984
1098
|
finalizeOpen(turnId, "cancelled", event.occurredAt);
|
|
985
1099
|
items.push(turnEndItem(event, "cancelled", null));
|
|
@@ -1025,6 +1139,9 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
1025
1139
|
case "goal.cleared":
|
|
1026
1140
|
case "goal.held":
|
|
1027
1141
|
case "goal.continuation": {
|
|
1142
|
+
if (event.type === "goal.held" && payload.actor === "agent") {
|
|
1143
|
+
rememberPendingWaitOutcome(pendingWaitOutcomeByTurn, event, payload, turnId);
|
|
1144
|
+
}
|
|
1028
1145
|
// Agent tool mutations already appear as tool-call rows in the activity
|
|
1029
1146
|
// cluster. Re-emitting them as GoalRow landmarks splits "N steps" mid-turn.
|
|
1030
1147
|
if (shouldSuppressAgentGoalLandmark(event.type, payload)) {
|
|
@@ -1043,6 +1160,13 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
1043
1160
|
break;
|
|
1044
1161
|
}
|
|
1045
1162
|
|
|
1163
|
+
case "session.wait.started": {
|
|
1164
|
+
if (payload.actor === "agent") {
|
|
1165
|
+
rememberPendingWaitOutcome(pendingWaitOutcomeByTurn, event, payload, turnId);
|
|
1166
|
+
}
|
|
1167
|
+
break;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1046
1170
|
default:
|
|
1047
1171
|
break;
|
|
1048
1172
|
}
|
|
@@ -1730,8 +1854,7 @@ function isTurnBoundary(group: TimelineGroup | undefined): boolean {
|
|
|
1730
1854
|
(group.item.kind === "user-message" ||
|
|
1731
1855
|
group.item.kind === "human-input" ||
|
|
1732
1856
|
group.item.kind === "context-compaction" ||
|
|
1733
|
-
|
|
1734
|
-
group.item.members.some((member) => member.kind === "media_generation_result")) ||
|
|
1857
|
+
group.item.kind === "machine-input-batch" ||
|
|
1735
1858
|
(group.item.kind === "notice" && group.item.tone === "input")))
|
|
1736
1859
|
);
|
|
1737
1860
|
}
|
|
@@ -1898,6 +2021,8 @@ function machineInputMembers(value: unknown): MachineInputBatchItem["members"] {
|
|
|
1898
2021
|
const kinds = new Set<MachineInputBatchItem["members"][number]["kind"]>([
|
|
1899
2022
|
"scheduled_occurrence",
|
|
1900
2023
|
"goal_continuation",
|
|
2024
|
+
"background_command_result",
|
|
2025
|
+
"session_wait_timeout",
|
|
1901
2026
|
"agent_message",
|
|
1902
2027
|
"agent_steer_instruction",
|
|
1903
2028
|
"child_terminal_result",
|
|
@@ -1953,6 +2078,8 @@ const SESSION_STATUSES: readonly SessionStatus[] = [
|
|
|
1953
2078
|
"running",
|
|
1954
2079
|
"idle",
|
|
1955
2080
|
"requires_action",
|
|
2081
|
+
"recovering",
|
|
2082
|
+
"waiting_capacity",
|
|
1956
2083
|
"failed",
|
|
1957
2084
|
"cancelled",
|
|
1958
2085
|
];
|
|
@@ -2148,15 +2275,7 @@ function elapsedDurationMs(startedAt: string, completedAt: string): number | nul
|
|
|
2148
2275
|
}
|
|
2149
2276
|
|
|
2150
2277
|
function failureMessage(payload: Record<string, unknown>): string | null {
|
|
2151
|
-
|
|
2152
|
-
const value = payload[key];
|
|
2153
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
2154
|
-
// Auth/quota provider errors are rewritten for the right audience
|
|
2155
|
-
// (raw text remains in the event payload for debug surfaces).
|
|
2156
|
-
return humanizeFailureReason(value);
|
|
2157
|
-
}
|
|
2158
|
-
}
|
|
2159
|
-
return null;
|
|
2278
|
+
return presentFailure(payload).reason;
|
|
2160
2279
|
}
|
|
2161
2280
|
|
|
2162
2281
|
function goalText(payload: Record<string, unknown>): string | null {
|
|
@@ -2173,11 +2292,30 @@ function goalText(payload: Record<string, unknown>): string | null {
|
|
|
2173
2292
|
return null;
|
|
2174
2293
|
}
|
|
2175
2294
|
|
|
2295
|
+
function rememberPendingWaitOutcome(
|
|
2296
|
+
pending: Map<string | null, PendingWaitOutcome>,
|
|
2297
|
+
event: SessionEvent,
|
|
2298
|
+
payload: Record<string, unknown>,
|
|
2299
|
+
eventTurnId: string | null,
|
|
2300
|
+
): void {
|
|
2301
|
+
const reason = stringValue(payload.reason).trim();
|
|
2302
|
+
if (!reason) return;
|
|
2303
|
+
const turnId =
|
|
2304
|
+
eventTurnId || stringValue(payload.waitTurnId) || stringValue(payload.turnId) || null;
|
|
2305
|
+
pending.set(turnId, { id: event.id, reason, occurredAt: event.occurredAt });
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
function waitingOutcomeText(reason: string): string {
|
|
2309
|
+
return /^waiting\b/i.test(reason) ? reason : `Waiting: ${reason}`;
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2176
2312
|
/**
|
|
2177
2313
|
* Agent-owned goal mutations already have an in-cluster tool row. Suppress the
|
|
2178
|
-
* breakaway landmark for those only.
|
|
2179
|
-
* and
|
|
2180
|
-
*
|
|
2314
|
+
* breakaway landmark for those only. Empty wait turns retain the durable hold
|
|
2315
|
+
* reason and surface it after the folded steps. `goal.completed` has no actor
|
|
2316
|
+
* field today and is only emitted by the agent tool, so it is always
|
|
2317
|
+
* suppressed. API / system / create-session / continuation landmarks stay
|
|
2318
|
+
* visible.
|
|
2181
2319
|
*/
|
|
2182
2320
|
function shouldSuppressAgentGoalLandmark(type: string, payload: Record<string, unknown>): boolean {
|
|
2183
2321
|
if (type === "goal.completed") {
|
package/src/timeline/registry.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { ToolCallItem } from "./types";
|
|
|
13
13
|
Resolution order (most → least specific):
|
|
14
14
|
1. exact match on `raw.type` (e.g. "apply_patch_call", "computer_call")
|
|
15
15
|
2. exact match on the tool `name` (e.g. "exec_command", "web_search_call")
|
|
16
|
-
3. leaf match after MCP `__` prefix (e.g. opengeni__environment_set_variable)
|
|
16
|
+
3. allowed leaf match after MCP `__` prefix (e.g. opengeni__environment_set_variable)
|
|
17
17
|
4. the registry's generic fallback
|
|
18
18
|
|
|
19
19
|
A consumer extends the defaults without forking by passing overrides to
|
|
@@ -48,7 +48,13 @@ export type ToolRenderer = ComponentType<ToolRendererProps>;
|
|
|
48
48
|
/** A registry entry: which key it matches and the component that renders it. */
|
|
49
49
|
export type ToolRegistryEntry =
|
|
50
50
|
| { match: "rawType"; type: string; render: ToolRenderer }
|
|
51
|
-
| {
|
|
51
|
+
| {
|
|
52
|
+
match: "name";
|
|
53
|
+
name: string;
|
|
54
|
+
render: ToolRenderer;
|
|
55
|
+
/** Disable untrusted `<server>__${name}` leaf matching for identity-sensitive renderers. */
|
|
56
|
+
matchPrefixedLeaf?: boolean | undefined;
|
|
57
|
+
};
|
|
52
58
|
|
|
53
59
|
export type ToolRegistry = {
|
|
54
60
|
/** Resolve the renderer for a call (never null — falls back to generic). */
|
|
@@ -92,6 +98,7 @@ export function createToolRegistry(
|
|
|
92
98
|
|
|
93
99
|
const byRawType = new Map<string, ToolRenderer>();
|
|
94
100
|
const byName = new Map<string, ToolRenderer>();
|
|
101
|
+
const byPrefixedLeaf = new Map<string, ToolRenderer>();
|
|
95
102
|
for (const entry of entries) {
|
|
96
103
|
if (entry.match === "rawType") {
|
|
97
104
|
if (!byRawType.has(entry.type)) {
|
|
@@ -100,6 +107,11 @@ export function createToolRegistry(
|
|
|
100
107
|
} else if (!byName.has(entry.name)) {
|
|
101
108
|
byName.set(entry.name, entry.render);
|
|
102
109
|
}
|
|
110
|
+
if (entry.match === "name" && entry.matchPrefixedLeaf !== false) {
|
|
111
|
+
if (!byPrefixedLeaf.has(entry.name)) {
|
|
112
|
+
byPrefixedLeaf.set(entry.name, entry.render);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
103
115
|
}
|
|
104
116
|
|
|
105
117
|
const resolve = (item: ToolCallItem): ToolRenderer => {
|
|
@@ -116,7 +128,7 @@ export function createToolRegistry(
|
|
|
116
128
|
}
|
|
117
129
|
const leaf = mcpToolLeaf(item.name);
|
|
118
130
|
if (leaf !== item.name) {
|
|
119
|
-
const byLeaf =
|
|
131
|
+
const byLeaf = byPrefixedLeaf.get(leaf);
|
|
120
132
|
if (byLeaf) {
|
|
121
133
|
return byLeaf;
|
|
122
134
|
}
|
|
@@ -1085,6 +1085,98 @@ function SandboxFilePublishRenderer({ item, loadRetainedArtifact }: ToolRenderer
|
|
|
1085
1085
|
);
|
|
1086
1086
|
}
|
|
1087
1087
|
|
|
1088
|
+
type PublishedSiteReceipt = {
|
|
1089
|
+
workspaceId: string;
|
|
1090
|
+
artifactId: string;
|
|
1091
|
+
title: string;
|
|
1092
|
+
revision: number;
|
|
1093
|
+
replayed: boolean;
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
function publishedSiteReceipt(output: unknown): PublishedSiteReceipt | null {
|
|
1097
|
+
const { text, isError } = unwrapMcpOutput(output);
|
|
1098
|
+
if (isError) return null;
|
|
1099
|
+
const parsed = tryParseJson(text);
|
|
1100
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
1101
|
+
const artifact = (parsed as Record<string, unknown>).artifact;
|
|
1102
|
+
const version = (parsed as Record<string, unknown>).version;
|
|
1103
|
+
if (
|
|
1104
|
+
!artifact ||
|
|
1105
|
+
typeof artifact !== "object" ||
|
|
1106
|
+
Array.isArray(artifact) ||
|
|
1107
|
+
!version ||
|
|
1108
|
+
typeof version !== "object" ||
|
|
1109
|
+
Array.isArray(version)
|
|
1110
|
+
) {
|
|
1111
|
+
return null;
|
|
1112
|
+
}
|
|
1113
|
+
const artifactRecord = artifact as Record<string, unknown>;
|
|
1114
|
+
const versionRecord = version as Record<string, unknown>;
|
|
1115
|
+
if (
|
|
1116
|
+
typeof artifactRecord.workspaceId !== "string" ||
|
|
1117
|
+
typeof artifactRecord.id !== "string" ||
|
|
1118
|
+
typeof artifactRecord.title !== "string" ||
|
|
1119
|
+
typeof versionRecord.revision !== "number" ||
|
|
1120
|
+
!Number.isInteger(versionRecord.revision) ||
|
|
1121
|
+
versionRecord.revision < 1
|
|
1122
|
+
) {
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
return {
|
|
1126
|
+
workspaceId: artifactRecord.workspaceId,
|
|
1127
|
+
artifactId: artifactRecord.id,
|
|
1128
|
+
title: artifactRecord.title,
|
|
1129
|
+
revision: versionRecord.revision,
|
|
1130
|
+
replayed: (parsed as Record<string, unknown>).replayed === true,
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function SiteOpenLink({ receipt }: { receipt: PublishedSiteReceipt }) {
|
|
1135
|
+
const href = `/workspaces/${encodeURIComponent(receipt.workspaceId)}/artifacts/${encodeURIComponent(receipt.artifactId)}`;
|
|
1136
|
+
return (
|
|
1137
|
+
<a
|
|
1138
|
+
href={href}
|
|
1139
|
+
aria-label={`Open ${receipt.title}`}
|
|
1140
|
+
className="inline-flex min-h-7 items-center rounded-og-sm px-2 text-og-sm font-medium text-og-accent-strong hover:bg-og-surface-2 hover:underline focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:min-h-10"
|
|
1141
|
+
onClick={(event) => event.stopPropagation()}
|
|
1142
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
1143
|
+
>
|
|
1144
|
+
Open
|
|
1145
|
+
</a>
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function SiteArtifactRenderer({ item }: ToolRendererProps) {
|
|
1150
|
+
const leaf = mcpToolLeaf(item.name);
|
|
1151
|
+
const publishingExisting = leaf === "artifacts_publish";
|
|
1152
|
+
if (item.status === "running") {
|
|
1153
|
+
return (
|
|
1154
|
+
<ActivityDisclosure
|
|
1155
|
+
icon={<PanelsTopLeftIcon className={ICON_SIZE} />}
|
|
1156
|
+
iconTone="running"
|
|
1157
|
+
title={publishingExisting ? "Publishing Site update" : "Publishing Site"}
|
|
1158
|
+
running
|
|
1159
|
+
preview={<RunningPreview>retaining source and compiled HTML…</RunningPreview>}
|
|
1160
|
+
/>
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
const receipt = publishedSiteReceipt(item.output);
|
|
1164
|
+
if (!receipt || item.status === "failed") return <GenericRenderer item={item} />;
|
|
1165
|
+
return (
|
|
1166
|
+
<ActivityDisclosure
|
|
1167
|
+
icon={<PanelsTopLeftIcon className={ICON_SIZE} />}
|
|
1168
|
+
iconTone="accent"
|
|
1169
|
+
title={publishingExisting ? `Updated ${receipt.title}` : `Published ${receipt.title}`}
|
|
1170
|
+
media={<SiteOpenLink receipt={receipt} />}
|
|
1171
|
+
>
|
|
1172
|
+
<BodyNote>
|
|
1173
|
+
Version {receipt.revision} is live
|
|
1174
|
+
{receipt.replayed ? " (replayed from the original publication)." : "."}
|
|
1175
|
+
</BodyNote>
|
|
1176
|
+
</ActivityDisclosure>
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1088
1180
|
function GeneratedImageDisclosure({
|
|
1089
1181
|
receipt,
|
|
1090
1182
|
load,
|
|
@@ -2198,7 +2290,7 @@ function goalToolPreview(name: string, args: unknown): string | null {
|
|
|
2198
2290
|
leaf !== "goal_update" &&
|
|
2199
2291
|
leaf !== "goal_complete" &&
|
|
2200
2292
|
leaf !== "goal_pause" &&
|
|
2201
|
-
leaf !== "
|
|
2293
|
+
leaf !== "wait_for_input"
|
|
2202
2294
|
) {
|
|
2203
2295
|
return null;
|
|
2204
2296
|
}
|
|
@@ -2308,6 +2400,30 @@ const BASE_ENTRIES: ToolRegistryEntry[] = [
|
|
|
2308
2400
|
{ match: "name", name: "tool_search", render: ToolSearchRenderer },
|
|
2309
2401
|
{ match: "name", name: "view_image", render: ViewImageRenderer },
|
|
2310
2402
|
{ match: "name", name: "sandbox_file_publish", render: SandboxFilePublishRenderer },
|
|
2403
|
+
{
|
|
2404
|
+
match: "name",
|
|
2405
|
+
name: "artifacts_create",
|
|
2406
|
+
render: SiteArtifactRenderer,
|
|
2407
|
+
matchPrefixedLeaf: false,
|
|
2408
|
+
},
|
|
2409
|
+
{
|
|
2410
|
+
match: "name",
|
|
2411
|
+
name: "artifacts_publish",
|
|
2412
|
+
render: SiteArtifactRenderer,
|
|
2413
|
+
matchPrefixedLeaf: false,
|
|
2414
|
+
},
|
|
2415
|
+
{
|
|
2416
|
+
match: "name",
|
|
2417
|
+
name: "opengeni__artifacts_create",
|
|
2418
|
+
render: SiteArtifactRenderer,
|
|
2419
|
+
matchPrefixedLeaf: false,
|
|
2420
|
+
},
|
|
2421
|
+
{
|
|
2422
|
+
match: "name",
|
|
2423
|
+
name: "opengeni__artifacts_publish",
|
|
2424
|
+
render: SiteArtifactRenderer,
|
|
2425
|
+
matchPrefixedLeaf: false,
|
|
2426
|
+
},
|
|
2311
2427
|
{ match: "name", name: "environment_set_variable", render: SecretSetRenderer },
|
|
2312
2428
|
{ match: "name", name: "variable_set_set_variable", render: SecretSetRenderer },
|
|
2313
2429
|
{ match: "name", name: "search_documents", render: DocsSearchRenderer },
|
package/src/timeline/types.ts
CHANGED
|
@@ -279,7 +279,14 @@ export type FleetDecisionItem = {
|
|
|
279
279
|
policyVersion: "adaptive-shadow-v1";
|
|
280
280
|
actualOutcome: "selected" | "waiting" | "none";
|
|
281
281
|
actualCandidateKey: string | null;
|
|
282
|
-
actualReason:
|
|
282
|
+
actualReason:
|
|
283
|
+
| "lease_reused"
|
|
284
|
+
| "pin"
|
|
285
|
+
| "rotation"
|
|
286
|
+
| "active"
|
|
287
|
+
| "all_capped"
|
|
288
|
+
| "allocator_disabled"
|
|
289
|
+
| "none";
|
|
283
290
|
shadowOutcome: "selected" | "paced" | "none";
|
|
284
291
|
shadowCandidateKey: string | null;
|
|
285
292
|
shadowReason:
|
|
@@ -342,6 +349,8 @@ export type NoticeItem = {
|
|
|
342
349
|
id: string;
|
|
343
350
|
tone: "waiting" | "cancelled" | "failed" | "input";
|
|
344
351
|
text: string;
|
|
352
|
+
/** A preserved turn-end outcome, not a claim about current session state. */
|
|
353
|
+
recordedOutcome?: true;
|
|
345
354
|
/** Optional evidence kept inspectable without overwhelming the main rail. */
|
|
346
355
|
details?: { label: string; value: unknown };
|
|
347
356
|
action?: { label: string; url: string };
|
|
@@ -371,6 +380,8 @@ export type MachineInputMember = {
|
|
|
371
380
|
kind:
|
|
372
381
|
| "scheduled_occurrence"
|
|
373
382
|
| "goal_continuation"
|
|
383
|
+
| "background_command_result"
|
|
384
|
+
| "session_wait_timeout"
|
|
374
385
|
| "agent_message"
|
|
375
386
|
| "agent_steer_instruction"
|
|
376
387
|
| "child_terminal_result"
|