@opengeni/react 0.26.2 → 0.28.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +32 -0
  2. package/dist/{chunk-OZDLELJQ.js → chunk-OCHBZ2X7.js} +40 -10
  3. package/dist/chunk-OCHBZ2X7.js.map +1 -0
  4. package/dist/{chunk-NBYNFI5T.js → chunk-PMSGE233.js} +5 -3
  5. package/dist/chunk-PMSGE233.js.map +1 -0
  6. package/dist/{chunk-GQN2QIR2.js → chunk-WAA247AB.js} +178 -87
  7. package/dist/chunk-WAA247AB.js.map +1 -0
  8. package/dist/index.d.ts +7 -34
  9. package/dist/index.js +5 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/{queue-surface-implementation-3UW3MIIR.js → queue-surface-implementation-UQ535OKQ.js} +44 -12
  12. package/dist/queue-surface-implementation-UQ535OKQ.js.map +1 -0
  13. package/dist/{session-LsampPc6.d.ts → session-C3tIXLim.d.ts} +1 -1
  14. package/dist/{session-ui-BPOV-tuY.d.ts → session-ui-B_7cfN5u.d.ts} +81 -5
  15. package/dist/session-ui.d.ts +2 -2
  16. package/dist/session-ui.js +4 -2
  17. package/dist/session.d.ts +2 -2
  18. package/dist/session.js +2 -2
  19. package/dist/{use-turn-queue-aDnQvzVz.d.ts → use-turn-queue-Drm_ypue.d.ts} +7 -3
  20. package/package.json +2 -2
  21. package/src/components/message-timeline.tsx +10 -0
  22. package/src/components/queue-surface-implementation.tsx +79 -11
  23. package/src/components/queue-surface.tsx +5 -5
  24. package/src/hooks/use-turn-queue.ts +8 -0
  25. package/src/index.ts +7 -0
  26. package/src/session-ui.ts +9 -0
  27. package/src/timeline/index.ts +11 -2
  28. package/src/timeline/parsers.ts +3 -31
  29. package/src/timeline/projection.ts +53 -9
  30. package/src/timeline/turn-summary.tsx +243 -56
  31. package/src/timeline/types.ts +1 -1
  32. package/dist/chunk-GQN2QIR2.js.map +0 -1
  33. package/dist/chunk-NBYNFI5T.js.map +0 -1
  34. package/dist/chunk-OZDLELJQ.js.map +0 -1
  35. package/dist/queue-surface-implementation-3UW3MIIR.js.map +0 -1
@@ -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: React.ReactNode;
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 facets = summarizeTurn(items, durationMs);
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
- /** Compose the facet summary line ("14 steps · 3 files · 2 commands · 1 screenshot · 4m"). */
154
- function summarizeTurn(items: ActivityItem[], durationMs?: number): string {
155
- let files = 0;
156
- let commands = 0;
157
- let screenshots = 0;
158
- let memoriesSaved = 0;
159
- let memoriesUpdated = 0;
160
- for (const item of items) {
161
- if (item.kind === "memory") {
162
- // A memory write is a first-class facet — the "wrote 1 memory" signal the
163
- // fold should make prominent — counted saved vs updated separately.
164
- if (item.variant === "corrected") {
165
- memoriesUpdated += 1;
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 !== "tool-call") {
172
- continue;
266
+ if (item.kind === "tool-call" || item.kind === "worker" || item.kind === "sandbox") {
267
+ return item.status !== "running";
173
268
  }
174
- // `item` is narrowed to ToolCallItem by the guard above — no cast needed.
175
- if (isApplyPatch(item)) {
176
- files += applyPatchOps(item.raw).length;
177
- } else if (item.name === "exec_command") {
178
- commands += 1;
179
- } else if (
180
- rawTypeOf(item) === "computer_call" ||
181
- item.name === "computer_call" ||
182
- item.name === "computer_screenshot"
183
- ) {
184
- if (screenshotDataUrl(item.output) !== null || mediaPreviewFact(item.output) !== null) {
185
- screenshots += 1;
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
- const parts = [`${items.length} ${items.length === 1 ? "step" : "steps"}`];
190
- if (files) {
191
- parts.push(`${files} ${files === 1 ? "file" : "files"} edited`);
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 {
@@ -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 };