@opengeni/react 0.27.0 → 0.29.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 +32 -0
- package/dist/{chunk-4F2YLLHP.js → chunk-AVLMIWYO.js} +3 -12
- package/dist/chunk-AVLMIWYO.js.map +1 -0
- package/dist/{chunk-NBYNFI5T.js → chunk-CSJBF3QJ.js} +5 -3
- package/dist/chunk-CSJBF3QJ.js.map +1 -0
- package/dist/{chunk-XVVQP7A4.js → chunk-IPVOVOW6.js} +2 -2
- package/dist/{chunk-OZDLELJQ.js → chunk-OKXCECLG.js} +55 -10
- package/dist/chunk-OKXCECLG.js.map +1 -0
- package/dist/{chunk-GQN2QIR2.js → chunk-ZMNPFZWL.js} +238 -87
- package/dist/chunk-ZMNPFZWL.js.map +1 -0
- package/dist/composer.js +2 -2
- package/dist/index.d.ts +7 -34
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/{queue-surface-implementation-3UW3MIIR.js → queue-surface-implementation-UQ535OKQ.js} +44 -12
- package/dist/queue-surface-implementation-UQ535OKQ.js.map +1 -0
- package/dist/{session-LsampPc6.d.ts → session-DaONt-dY.d.ts} +1 -1
- package/dist/{session-ui-BPOV-tuY.d.ts → session-ui-B5_roA95.d.ts} +81 -5
- package/dist/session-ui.d.ts +2 -2
- package/dist/session-ui.js +4 -2
- package/dist/session.d.ts +2 -2
- package/dist/session.js +3 -3
- package/dist/{use-turn-queue-aDnQvzVz.d.ts → use-turn-queue-D_1a8W_l.d.ts} +26 -4
- package/package.json +2 -2
- package/src/components/message-timeline.tsx +88 -0
- package/src/components/queue-surface-implementation.tsx +79 -11
- package/src/components/queue-surface.tsx +5 -5
- package/src/hooks/use-composer.ts +1 -15
- package/src/hooks/use-turn-queue.ts +8 -0
- package/src/index.ts +9 -0
- package/src/session-ui.ts +9 -0
- package/src/session.ts +2 -0
- package/src/timeline/index.ts +13 -2
- package/src/timeline/parsers.ts +3 -31
- package/src/timeline/projection.ts +73 -9
- package/src/timeline/turn-summary.tsx +243 -56
- package/src/timeline/types.ts +27 -1
- package/dist/chunk-4F2YLLHP.js.map +0 -1
- package/dist/chunk-GQN2QIR2.js.map +0 -1
- package/dist/chunk-NBYNFI5T.js.map +0 -1
- package/dist/chunk-OZDLELJQ.js.map +0 -1
- package/dist/queue-surface-implementation-3UW3MIIR.js.map +0 -1
- /package/dist/{chunk-XVVQP7A4.js.map → chunk-IPVOVOW6.js.map} +0 -0
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
ActivityItem,
|
|
12
12
|
AuthNeededItem,
|
|
13
13
|
GoalItem,
|
|
14
|
+
MachineInputBatchItem,
|
|
14
15
|
MemoryItem,
|
|
15
16
|
SandboxItem,
|
|
16
17
|
SessionStatusItem,
|
|
@@ -108,14 +109,14 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
108
109
|
goalText: childCompletion.goalText,
|
|
109
110
|
evidence: childCompletion.evidence,
|
|
110
111
|
pausedReason: childCompletion.pausedReason,
|
|
111
|
-
text:
|
|
112
|
+
text: stringValue(payload.text),
|
|
112
113
|
});
|
|
113
114
|
break;
|
|
114
115
|
}
|
|
115
116
|
items.push({
|
|
116
117
|
kind: "user-message",
|
|
117
118
|
id: event.id,
|
|
118
|
-
text:
|
|
119
|
+
text: stringValue(payload.text),
|
|
119
120
|
resources: resourceRefs(payload.resources),
|
|
120
121
|
tools: toolRefs(payload.tools),
|
|
121
122
|
occurredAt: event.occurredAt,
|
|
@@ -123,8 +124,22 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
123
124
|
break;
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
case "system.update.delivered": {
|
|
128
|
+
const inputs = machineInputMembers(payload.members);
|
|
129
|
+
if (inputs.length === 0) break;
|
|
130
|
+
closeStreamingTail();
|
|
131
|
+
items.push({
|
|
132
|
+
kind: "machine-input-batch",
|
|
133
|
+
id: event.id,
|
|
134
|
+
turnId,
|
|
135
|
+
members: inputs,
|
|
136
|
+
occurredAt: event.occurredAt,
|
|
137
|
+
});
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
|
|
126
141
|
case "agent.message.delta": {
|
|
127
|
-
const text =
|
|
142
|
+
const text = stringValue(payload.text);
|
|
128
143
|
if (!text) {
|
|
129
144
|
break;
|
|
130
145
|
}
|
|
@@ -146,7 +161,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
146
161
|
}
|
|
147
162
|
|
|
148
163
|
case "agent.message.completed": {
|
|
149
|
-
const text =
|
|
164
|
+
const text = stringValue(payload.text);
|
|
150
165
|
// Reconcile the most recent same-turn agent message — even when
|
|
151
166
|
// activity (tool calls, reasoning) landed after its deltas — so the
|
|
152
167
|
// completed text never duplicates the streamed one.
|
|
@@ -454,7 +469,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
454
469
|
kind: "auth-needed",
|
|
455
470
|
id: event.id,
|
|
456
471
|
turnId,
|
|
457
|
-
providerDomain:
|
|
472
|
+
providerDomain: stringValue(payload.providerDomain),
|
|
458
473
|
connectionId: typeof payload.connectionId === "string" ? payload.connectionId : null,
|
|
459
474
|
reason: authNeededReason(payload.reason),
|
|
460
475
|
scopes: stringList(payload.scopes),
|
|
@@ -968,7 +983,12 @@ function foldSettledTurn(groups: TimelineGroup[], turnEnd: TurnEndItem): void {
|
|
|
968
983
|
}
|
|
969
984
|
|
|
970
985
|
function isTurnBoundary(group: TimelineGroup | undefined): boolean {
|
|
971
|
-
return
|
|
986
|
+
return (
|
|
987
|
+
group?.kind === "turn" ||
|
|
988
|
+
(group?.kind === "item" &&
|
|
989
|
+
(group.item.kind === "user-message" ||
|
|
990
|
+
(group.item.kind === "notice" && group.item.tone === "input")))
|
|
991
|
+
);
|
|
972
992
|
}
|
|
973
993
|
|
|
974
994
|
function belongsToDifferentTurn(group: TimelineGroup | undefined, turnId: string | null): boolean {
|
|
@@ -1029,6 +1049,50 @@ function asRecord(value: unknown): Record<string, unknown> {
|
|
|
1029
1049
|
return value !== null && typeof value === "object" ? (value as Record<string, unknown>) : {};
|
|
1030
1050
|
}
|
|
1031
1051
|
|
|
1052
|
+
function stringValue(value: unknown): string {
|
|
1053
|
+
return typeof value === "string" ? value : "";
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
function machineInputMembers(value: unknown): MachineInputBatchItem["members"] {
|
|
1057
|
+
const kinds = new Set<MachineInputBatchItem["members"][number]["kind"]>([
|
|
1058
|
+
"scheduled_occurrence",
|
|
1059
|
+
"goal_continuation",
|
|
1060
|
+
"agent_message",
|
|
1061
|
+
"agent_steer_instruction",
|
|
1062
|
+
"child_terminal_result",
|
|
1063
|
+
]);
|
|
1064
|
+
const classifications = new Set<MachineInputBatchItem["members"][number]["classification"]>([
|
|
1065
|
+
"success",
|
|
1066
|
+
"failure",
|
|
1067
|
+
"action_required",
|
|
1068
|
+
"info",
|
|
1069
|
+
]);
|
|
1070
|
+
return Array.isArray(value)
|
|
1071
|
+
? value.flatMap((candidate) => {
|
|
1072
|
+
const member = asRecord(candidate);
|
|
1073
|
+
return typeof member.id === "string" &&
|
|
1074
|
+
typeof member.kind === "string" &&
|
|
1075
|
+
kinds.has(member.kind as MachineInputBatchItem["members"][number]["kind"]) &&
|
|
1076
|
+
typeof member.classification === "string" &&
|
|
1077
|
+
classifications.has(
|
|
1078
|
+
member.classification as MachineInputBatchItem["members"][number]["classification"],
|
|
1079
|
+
) &&
|
|
1080
|
+
typeof member.sourceId === "string"
|
|
1081
|
+
? [
|
|
1082
|
+
{
|
|
1083
|
+
id: member.id,
|
|
1084
|
+
kind: member.kind as MachineInputBatchItem["members"][number]["kind"],
|
|
1085
|
+
classification:
|
|
1086
|
+
member.classification as MachineInputBatchItem["members"][number]["classification"],
|
|
1087
|
+
sourceId: member.sourceId,
|
|
1088
|
+
summary: stringValue(member.summary),
|
|
1089
|
+
},
|
|
1090
|
+
]
|
|
1091
|
+
: [];
|
|
1092
|
+
})
|
|
1093
|
+
: [];
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1032
1096
|
const SESSION_STATUSES: readonly SessionStatus[] = [
|
|
1033
1097
|
"queued",
|
|
1034
1098
|
"running",
|
|
@@ -1201,8 +1265,8 @@ function memoryItem(
|
|
|
1201
1265
|
id,
|
|
1202
1266
|
turnId,
|
|
1203
1267
|
variant: type === "memory.corrected" ? "corrected" : "saved",
|
|
1204
|
-
memoryKind:
|
|
1205
|
-
preview:
|
|
1268
|
+
memoryKind: stringValue(payload.kind),
|
|
1269
|
+
preview: stringValue(payload.preview),
|
|
1206
1270
|
...(payload.deduped === true ? { deduped: true } : {}),
|
|
1207
1271
|
...(replacementPreview ? { replacementPreview } : {}),
|
|
1208
1272
|
...(action ? { action } : {}),
|
|
@@ -1245,7 +1309,7 @@ function reasoningText(payload: unknown): string {
|
|
|
1245
1309
|
return content
|
|
1246
1310
|
.map((part) => {
|
|
1247
1311
|
const text = asRecord(part).text;
|
|
1248
|
-
return
|
|
1312
|
+
return stringValue(text);
|
|
1249
1313
|
})
|
|
1250
1314
|
.join("");
|
|
1251
1315
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { CheckIcon, ChevronRightIcon, CircleSlashIcon, TriangleAlertIcon } from "lucide-react";
|
|
2
|
-
import { useState } from "react";
|
|
2
|
+
import { Component, useMemo, useState, type ReactNode } from "react";
|
|
3
3
|
import { Collapsible } from "radix-ui";
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
5
5
|
import { useForcedDefaultOpen } from "./disclosure-context";
|
|
6
6
|
import { useEntranceAnimation } from "./entrance";
|
|
7
7
|
import { applyPatchOps, isApplyPatch, mediaPreviewFact, screenshotDataUrl } from "./parsers";
|
|
8
8
|
import { rawTypeOf } from "./registry";
|
|
9
|
-
import type { ActivityItem, TurnOutcome } from "./types";
|
|
9
|
+
import type { ActivityItem, ToolCallItem, TurnOutcome } from "./types";
|
|
10
10
|
export type { TurnOutcome } from "./types";
|
|
11
11
|
|
|
12
12
|
/* ----------------------------------------------------------------------------
|
|
@@ -21,6 +21,67 @@ export type { TurnOutcome } from "./types";
|
|
|
21
21
|
reader chooses to look inside it.
|
|
22
22
|
-------------------------------------------------------------------------- */
|
|
23
23
|
|
|
24
|
+
export const BUILT_IN_TURN_SUMMARY_FACET_IDS = [
|
|
25
|
+
"steps",
|
|
26
|
+
"files",
|
|
27
|
+
"commands",
|
|
28
|
+
"screenshots",
|
|
29
|
+
"memories",
|
|
30
|
+
"duration",
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
export type BuiltInTurnSummaryFacetId = (typeof BUILT_IN_TURN_SUMMARY_FACET_IDS)[number];
|
|
34
|
+
|
|
35
|
+
export type TurnSummaryContext = Readonly<{
|
|
36
|
+
/** Every normalized activity item folded into this summary. */
|
|
37
|
+
items: readonly ActivityItem[];
|
|
38
|
+
/** Tool calls from `items`, retained in timeline order for convenient aggregation. */
|
|
39
|
+
toolCalls: readonly ToolCallItem[];
|
|
40
|
+
/** The settled turn verdict, or absent for a neutral/incomplete cluster. */
|
|
41
|
+
outcome: TurnOutcome | undefined;
|
|
42
|
+
/** The bounded failure reason rendered by the enclosing summary, when present. */
|
|
43
|
+
failureText: string | undefined;
|
|
44
|
+
/** Total turn duration when the enclosing group has both valid timestamps. */
|
|
45
|
+
durationMs: number | undefined;
|
|
46
|
+
/** False when any projected activity is still running or streaming. */
|
|
47
|
+
settled: boolean;
|
|
48
|
+
}>;
|
|
49
|
+
|
|
50
|
+
export type TurnSummaryFacetResult = Readonly<{
|
|
51
|
+
icon?: ReactNode;
|
|
52
|
+
content: ReactNode;
|
|
53
|
+
ariaLabel?: string;
|
|
54
|
+
title?: string;
|
|
55
|
+
}>;
|
|
56
|
+
|
|
57
|
+
export type TurnSummaryFacet = Readonly<{
|
|
58
|
+
/** Stable identity used for removal and deterministic de-duplication. */
|
|
59
|
+
id: string;
|
|
60
|
+
/** Return null when this facet has nothing useful to show. */
|
|
61
|
+
summarize(context: TurnSummaryContext): TurnSummaryFacetResult | null;
|
|
62
|
+
}>;
|
|
63
|
+
|
|
64
|
+
type ModifyTurnSummaryFacets = Readonly<{
|
|
65
|
+
/** Appended after the remaining built-ins, in supplied order. */
|
|
66
|
+
add?: readonly TurnSummaryFacet[];
|
|
67
|
+
/** Built-ins to omit before custom facets are appended. */
|
|
68
|
+
remove?: readonly BuiltInTurnSummaryFacetId[];
|
|
69
|
+
replace?: never;
|
|
70
|
+
}>;
|
|
71
|
+
|
|
72
|
+
type ReplaceTurnSummaryFacets = Readonly<{
|
|
73
|
+
/** The complete ordered facet list. Mutually exclusive with add/remove. */
|
|
74
|
+
replace: readonly TurnSummaryFacet[];
|
|
75
|
+
add?: never;
|
|
76
|
+
remove?: never;
|
|
77
|
+
}>;
|
|
78
|
+
|
|
79
|
+
export type TurnSummaryFacetConfiguration = ModifyTurnSummaryFacets | ReplaceTurnSummaryFacets;
|
|
80
|
+
|
|
81
|
+
export type TurnSummaryOptions = Readonly<{
|
|
82
|
+
facets?: TurnSummaryFacetConfiguration;
|
|
83
|
+
}>;
|
|
84
|
+
|
|
24
85
|
export type TurnSummaryProps = {
|
|
25
86
|
/** The activity items in the turn (used only to compute the facet counts). */
|
|
26
87
|
items: ActivityItem[];
|
|
@@ -43,8 +104,10 @@ export type TurnSummaryProps = {
|
|
|
43
104
|
* of nodes, never a stack of boxes-in-boxes. The top-level fold stays a chip.
|
|
44
105
|
*/
|
|
45
106
|
bare?: boolean | undefined;
|
|
107
|
+
/** Per-instance facet customization. Omit to preserve the built-in summary exactly. */
|
|
108
|
+
facets?: TurnSummaryFacetConfiguration | undefined;
|
|
46
109
|
/** The rendered activity rail revealed on expand. */
|
|
47
|
-
children:
|
|
110
|
+
children: ReactNode;
|
|
48
111
|
};
|
|
49
112
|
|
|
50
113
|
export function TurnSummary({
|
|
@@ -54,6 +117,7 @@ export function TurnSummary({
|
|
|
54
117
|
durationMs,
|
|
55
118
|
defaultOpen,
|
|
56
119
|
bare,
|
|
120
|
+
facets: facetConfiguration,
|
|
57
121
|
children,
|
|
58
122
|
}: TurnSummaryProps) {
|
|
59
123
|
// An explicit `defaultOpen` always wins; otherwise an ancestor may seed it
|
|
@@ -61,7 +125,28 @@ export function TurnSummary({
|
|
|
61
125
|
const forcedDefaultOpen = useForcedDefaultOpen();
|
|
62
126
|
const [open, setOpen] = useState(defaultOpen ?? forcedDefaultOpen ?? false);
|
|
63
127
|
const enter = useEntranceAnimation();
|
|
64
|
-
const
|
|
128
|
+
const context = useMemo(
|
|
129
|
+
() => createTurnSummaryContext(items, outcome, failureText, durationMs),
|
|
130
|
+
[items, outcome, failureText, durationMs],
|
|
131
|
+
);
|
|
132
|
+
const facetDefinitions = useMemo(
|
|
133
|
+
() => resolveTurnSummaryFacets(facetConfiguration),
|
|
134
|
+
[facetConfiguration],
|
|
135
|
+
);
|
|
136
|
+
const facets = useMemo(
|
|
137
|
+
() =>
|
|
138
|
+
facetDefinitions.flatMap((facet) => {
|
|
139
|
+
try {
|
|
140
|
+
const result = facet.summarize(context);
|
|
141
|
+
return result && hasFacetContent(result.content) ? [{ facet, result }] : [];
|
|
142
|
+
} catch {
|
|
143
|
+
// A host extension is presentation-only. It must never take down the
|
|
144
|
+
// durable timeline or hide the remaining built-in evidence.
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
}),
|
|
148
|
+
[context, facetDefinitions],
|
|
149
|
+
);
|
|
65
150
|
|
|
66
151
|
return (
|
|
67
152
|
<Collapsible.Root
|
|
@@ -117,7 +202,21 @@ export function TurnSummary({
|
|
|
117
202
|
)}
|
|
118
203
|
</span>
|
|
119
204
|
<span className={cn("min-w-0 flex-1 truncate", bare ? "text-og-sm" : "text-og-fg-muted")}>
|
|
120
|
-
{facets}
|
|
205
|
+
{facets.map(({ facet, result }, index) => (
|
|
206
|
+
<FacetRenderBoundary key={facet.id}>
|
|
207
|
+
<>
|
|
208
|
+
{index > 0 ? " · " : null}
|
|
209
|
+
<span aria-label={result.ariaLabel} title={result.title}>
|
|
210
|
+
{result.icon ? (
|
|
211
|
+
<span aria-hidden className="mr-1 inline-flex align-[-0.125em]">
|
|
212
|
+
{result.icon}
|
|
213
|
+
</span>
|
|
214
|
+
) : null}
|
|
215
|
+
{result.content}
|
|
216
|
+
</span>
|
|
217
|
+
</>
|
|
218
|
+
</FacetRenderBoundary>
|
|
219
|
+
))}
|
|
121
220
|
{outcome === "failed" && failureText ? (
|
|
122
221
|
<span className="text-og-status-failed"> · {failureText}</span>
|
|
123
222
|
) : null}
|
|
@@ -150,63 +249,151 @@ export function TurnSummary({
|
|
|
150
249
|
);
|
|
151
250
|
}
|
|
152
251
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
} else {
|
|
167
|
-
memoriesSaved += 1;
|
|
168
|
-
}
|
|
169
|
-
continue;
|
|
252
|
+
function createTurnSummaryContext(
|
|
253
|
+
items: ActivityItem[],
|
|
254
|
+
outcome: TurnOutcome | undefined,
|
|
255
|
+
failureText: string | undefined,
|
|
256
|
+
durationMs: number | undefined,
|
|
257
|
+
): TurnSummaryContext {
|
|
258
|
+
const itemSnapshot = Object.freeze([...items]);
|
|
259
|
+
const toolCalls = Object.freeze(
|
|
260
|
+
itemSnapshot.filter((item): item is ToolCallItem => item.kind === "tool-call"),
|
|
261
|
+
);
|
|
262
|
+
const settled = itemSnapshot.every((item) => {
|
|
263
|
+
if (item.kind === "reasoning") {
|
|
264
|
+
return !item.streaming;
|
|
170
265
|
}
|
|
171
|
-
if (item.kind
|
|
172
|
-
|
|
266
|
+
if (item.kind === "tool-call" || item.kind === "worker" || item.kind === "sandbox") {
|
|
267
|
+
return item.status !== "running";
|
|
173
268
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
269
|
+
return true;
|
|
270
|
+
});
|
|
271
|
+
return Object.freeze({
|
|
272
|
+
items: itemSnapshot,
|
|
273
|
+
toolCalls,
|
|
274
|
+
outcome,
|
|
275
|
+
failureText,
|
|
276
|
+
durationMs,
|
|
277
|
+
settled,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const BUILT_IN_TURN_SUMMARY_FACETS: readonly TurnSummaryFacet[] = Object.freeze([
|
|
282
|
+
{
|
|
283
|
+
id: "steps",
|
|
284
|
+
summarize: ({ items }) => ({
|
|
285
|
+
content: `${items.length} ${items.length === 1 ? "step" : "steps"}`,
|
|
286
|
+
}),
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: "files",
|
|
290
|
+
summarize: ({ toolCalls }) => {
|
|
291
|
+
let files = 0;
|
|
292
|
+
for (const item of toolCalls) {
|
|
293
|
+
if (isApplyPatch(item)) {
|
|
294
|
+
files += applyPatchOps(item.raw).length;
|
|
295
|
+
}
|
|
186
296
|
}
|
|
297
|
+
return files ? { content: `${files} ${files === 1 ? "file" : "files"} edited` } : null;
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: "commands",
|
|
302
|
+
summarize: ({ toolCalls }) => {
|
|
303
|
+
const commands = toolCalls.filter((item) => item.name === "exec_command").length;
|
|
304
|
+
return commands
|
|
305
|
+
? { content: `${commands} ${commands === 1 ? "command" : "commands"}` }
|
|
306
|
+
: null;
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: "screenshots",
|
|
311
|
+
summarize: ({ toolCalls }) => {
|
|
312
|
+
let screenshots = 0;
|
|
313
|
+
for (const item of toolCalls) {
|
|
314
|
+
if (
|
|
315
|
+
(rawTypeOf(item) === "computer_call" ||
|
|
316
|
+
item.name === "computer_call" ||
|
|
317
|
+
item.name === "computer_screenshot") &&
|
|
318
|
+
(screenshotDataUrl(item.output) !== null || mediaPreviewFact(item.output) !== null)
|
|
319
|
+
) {
|
|
320
|
+
screenshots += 1;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return screenshots
|
|
324
|
+
? {
|
|
325
|
+
content: `${screenshots} ${screenshots === 1 ? "screenshot" : "screenshots"}`,
|
|
326
|
+
}
|
|
327
|
+
: null;
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
id: "memories",
|
|
332
|
+
summarize: ({ items }) => {
|
|
333
|
+
let saved = 0;
|
|
334
|
+
let updated = 0;
|
|
335
|
+
for (const item of items) {
|
|
336
|
+
if (item.kind !== "memory") {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (item.variant === "corrected") {
|
|
340
|
+
updated += 1;
|
|
341
|
+
} else {
|
|
342
|
+
saved += 1;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const parts: string[] = [];
|
|
346
|
+
if (saved) {
|
|
347
|
+
parts.push(`${saved} ${saved === 1 ? "memory" : "memories"} saved`);
|
|
348
|
+
}
|
|
349
|
+
if (updated) {
|
|
350
|
+
parts.push(`${updated} ${updated === 1 ? "memory" : "memories"} updated`);
|
|
351
|
+
}
|
|
352
|
+
return parts.length > 0 ? { content: parts.join(" · ") } : null;
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
id: "duration",
|
|
357
|
+
summarize: ({ durationMs }) => {
|
|
358
|
+
const duration = formatDurationFacet(durationMs);
|
|
359
|
+
return duration ? { content: duration } : null;
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
]);
|
|
363
|
+
|
|
364
|
+
function resolveTurnSummaryFacets(
|
|
365
|
+
configuration: TurnSummaryFacetConfiguration | undefined,
|
|
366
|
+
): readonly TurnSummaryFacet[] {
|
|
367
|
+
const requested: readonly TurnSummaryFacet[] = configuration?.replace ?? [
|
|
368
|
+
...BUILT_IN_TURN_SUMMARY_FACETS.filter(
|
|
369
|
+
(facet) => !configuration?.remove?.includes(facet.id as BuiltInTurnSummaryFacetId),
|
|
370
|
+
),
|
|
371
|
+
...(configuration?.add ?? []),
|
|
372
|
+
];
|
|
373
|
+
const seen = new Set<string>();
|
|
374
|
+
return requested.filter((facet) => {
|
|
375
|
+
if (!facet.id || seen.has(facet.id)) {
|
|
376
|
+
return false;
|
|
187
377
|
}
|
|
378
|
+
seen.add(facet.id);
|
|
379
|
+
return true;
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function hasFacetContent(content: ReactNode): boolean {
|
|
384
|
+
return content !== null && content !== undefined && content !== false && content !== "";
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
class FacetRenderBoundary extends Component<{ children: ReactNode }, { failed: boolean }> {
|
|
388
|
+
state = { failed: false };
|
|
389
|
+
|
|
390
|
+
static getDerivedStateFromError(): { failed: boolean } {
|
|
391
|
+
return { failed: true };
|
|
188
392
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
if (commands) {
|
|
194
|
-
parts.push(`${commands} ${commands === 1 ? "command" : "commands"}`);
|
|
195
|
-
}
|
|
196
|
-
if (screenshots) {
|
|
197
|
-
parts.push(`${screenshots} ${screenshots === 1 ? "screenshot" : "screenshots"}`);
|
|
198
|
-
}
|
|
199
|
-
if (memoriesSaved) {
|
|
200
|
-
parts.push(`${memoriesSaved} ${memoriesSaved === 1 ? "memory" : "memories"} saved`);
|
|
201
|
-
}
|
|
202
|
-
if (memoriesUpdated) {
|
|
203
|
-
parts.push(`${memoriesUpdated} ${memoriesUpdated === 1 ? "memory" : "memories"} updated`);
|
|
204
|
-
}
|
|
205
|
-
const duration = formatDurationFacet(durationMs);
|
|
206
|
-
if (duration) {
|
|
207
|
-
parts.push(duration);
|
|
393
|
+
|
|
394
|
+
render(): ReactNode {
|
|
395
|
+
return this.state.failed ? null : this.props.children;
|
|
208
396
|
}
|
|
209
|
-
return parts.join(" · ");
|
|
210
397
|
}
|
|
211
398
|
|
|
212
399
|
function formatDurationFacet(durationMs: number | undefined): string | null {
|
package/src/timeline/types.ts
CHANGED
|
@@ -218,7 +218,7 @@ export type GoalItem = {
|
|
|
218
218
|
export type NoticeItem = {
|
|
219
219
|
kind: "notice";
|
|
220
220
|
id: string;
|
|
221
|
-
tone: "waiting" | "cancelled" | "failed";
|
|
221
|
+
tone: "waiting" | "cancelled" | "failed" | "input";
|
|
222
222
|
text: string;
|
|
223
223
|
/** Optional evidence kept inspectable without overwhelming the main rail. */
|
|
224
224
|
details?: { label: string; value: unknown };
|
|
@@ -226,6 +226,31 @@ export type NoticeItem = {
|
|
|
226
226
|
occurredAt: string;
|
|
227
227
|
};
|
|
228
228
|
|
|
229
|
+
export type MachineInputMember = {
|
|
230
|
+
id: string;
|
|
231
|
+
kind:
|
|
232
|
+
| "scheduled_occurrence"
|
|
233
|
+
| "goal_continuation"
|
|
234
|
+
| "agent_message"
|
|
235
|
+
| "agent_steer_instruction"
|
|
236
|
+
| "child_terminal_result";
|
|
237
|
+
classification: "success" | "failure" | "action_required" | "info";
|
|
238
|
+
sourceId: string;
|
|
239
|
+
summary: string;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* One or more durable non-human inputs that joined the following agent turn.
|
|
244
|
+
* This is communication, not a warning or a protocol-debug payload.
|
|
245
|
+
*/
|
|
246
|
+
export type MachineInputBatchItem = {
|
|
247
|
+
kind: "machine-input-batch";
|
|
248
|
+
id: string;
|
|
249
|
+
turnId: string | null;
|
|
250
|
+
members: MachineInputMember[];
|
|
251
|
+
occurredAt: string;
|
|
252
|
+
};
|
|
253
|
+
|
|
229
254
|
/**
|
|
230
255
|
* A tool call hit a missing or lapsed connection. The broker reports that
|
|
231
256
|
* condition as a tool error and the turn continues; reconnecting never resumes
|
|
@@ -278,6 +303,7 @@ export type TimelineItem =
|
|
|
278
303
|
| SessionStatusItem
|
|
279
304
|
| GoalItem
|
|
280
305
|
| NoticeItem
|
|
306
|
+
| MachineInputBatchItem
|
|
281
307
|
| AuthNeededItem
|
|
282
308
|
| MemoryItem
|
|
283
309
|
| FleetDecisionItem
|