@opengeni/react 1.0.1 → 2.3.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.
Files changed (174) hide show
  1. package/README.md +33 -10
  2. package/dist/artifacts-spreadsheet.js +1 -1
  3. package/dist/artifacts.js +1 -1
  4. package/dist/browser-viewer-GXUICIAG.js +14 -0
  5. package/dist/{chunk-R3AZOAB5.js → chunk-4MDLEHXL.js} +9 -115
  6. package/dist/chunk-4MDLEHXL.js.map +1 -0
  7. package/dist/chunk-5YK2ZDIG.js +116 -0
  8. package/dist/chunk-5YK2ZDIG.js.map +1 -0
  9. package/dist/chunk-H5YGYNXS.js +329 -0
  10. package/dist/chunk-H5YGYNXS.js.map +1 -0
  11. package/dist/{chunk-7WCLAEFV.js → chunk-ICA6R6TW.js} +2 -2
  12. package/dist/{chunk-7WCLAEFV.js.map → chunk-ICA6R6TW.js.map} +1 -1
  13. package/dist/{chunk-IGC6A2ZI.js → chunk-JOAVA2TW.js} +589 -217
  14. package/dist/chunk-JOAVA2TW.js.map +1 -0
  15. package/dist/{chunk-Y7OEDUJ5.js → chunk-LS4POUTY.js} +1057 -426
  16. package/dist/chunk-LS4POUTY.js.map +1 -0
  17. package/dist/{chunk-5UQPNLIO.js → chunk-LTCHJ4MS.js} +9 -9
  18. package/dist/chunk-LTCHJ4MS.js.map +1 -0
  19. package/dist/{chunk-C5ZPSSS5.js → chunk-NEXLLTP4.js} +2 -1
  20. package/dist/{chunk-C5ZPSSS5.js.map → chunk-NEXLLTP4.js.map} +1 -1
  21. package/dist/chunk-QGBJ6GHG.js +2300 -0
  22. package/dist/chunk-QGBJ6GHG.js.map +1 -0
  23. package/dist/{chunk-6KLX2RH5.js → chunk-RSXJN73N.js} +365 -21
  24. package/dist/chunk-RSXJN73N.js.map +1 -0
  25. package/dist/{chunk-B6WPZSTT.js → chunk-SE4W4WQ3.js} +78 -16
  26. package/dist/chunk-SE4W4WQ3.js.map +1 -0
  27. package/dist/{chunk-MKPIAGVL.js → chunk-W6NFJXSA.js} +8 -19
  28. package/dist/chunk-W6NFJXSA.js.map +1 -0
  29. package/dist/{chunk-XBZWOM2K.js → chunk-X4A66EWZ.js} +8 -3
  30. package/dist/chunk-X4A66EWZ.js.map +1 -0
  31. package/dist/chunk-X4JAFD5R.js +3907 -0
  32. package/dist/chunk-X4JAFD5R.js.map +1 -0
  33. package/dist/{chunk-HA46CYQK.js → chunk-XXWH2JLC.js} +792 -206
  34. package/dist/chunk-XXWH2JLC.js.map +1 -0
  35. package/dist/client.d.ts +2 -2
  36. package/dist/components/browser-viewer.d.ts +7 -1
  37. package/dist/components/computer-viewer.d.ts +4 -1
  38. package/dist/components/enrollment-consent.d.ts +6 -3
  39. package/dist/components/file-browser.d.ts +7 -1
  40. package/dist/components/human-input-form.d.ts +1 -1
  41. package/dist/components/human-input-surface.d.ts +1 -1
  42. package/dist/components/machine-card.d.ts +4 -1
  43. package/dist/components/machines/machine-detail.d.ts +4 -1
  44. package/dist/components/machines/machine-operation-policy.d.ts +11 -0
  45. package/dist/components/machines-dashboard.d.ts +3 -1
  46. package/dist/components/markdown.d.ts +20 -1
  47. package/dist/components/message-timeline.d.ts +1 -1
  48. package/dist/components/model-policy-picker.d.ts +2 -0
  49. package/dist/components/pierre-file.d.ts +1 -1
  50. package/dist/components/sandbox-files.d.ts +8 -2
  51. package/dist/components/sandbox-workspace.d.ts +33 -3
  52. package/dist/components/session-chrome.d.ts +15 -3
  53. package/dist/components/workspace-dock.d.ts +6 -2
  54. package/dist/composer.js +5 -5
  55. package/dist/computer-viewer-ZOP4Q7A7.js +16 -0
  56. package/dist/computer-viewer-ZOP4Q7A7.js.map +1 -0
  57. package/dist/hooks/channel-cache.d.ts +23 -0
  58. package/dist/hooks/shared-machines-list.d.ts +14 -0
  59. package/dist/hooks/use-attached-browsers.d.ts +2 -1
  60. package/dist/hooks/use-channels.d.ts +1 -0
  61. package/dist/hooks/use-composer.d.ts +37 -9
  62. package/dist/hooks/use-machine-chip.d.ts +3 -3
  63. package/dist/hooks/use-machines.d.ts +19 -1
  64. package/dist/hooks/use-sandbox-files.d.ts +1 -0
  65. package/dist/hooks/use-session-background-commands.d.ts +14 -0
  66. package/dist/hooks/use-turn-queue.d.ts +12 -0
  67. package/dist/hooks/use-workspace-sessions.d.ts +1 -0
  68. package/dist/index.d.ts +7 -5
  69. package/dist/index.js +1096 -623
  70. package/dist/index.js.map +1 -1
  71. package/dist/interaction.js +29 -181
  72. package/dist/interaction.js.map +1 -1
  73. package/dist/lib/interaction-errors.d.ts +3 -0
  74. package/dist/machines.d.ts +4 -2
  75. package/dist/machines.js +10 -2
  76. package/dist/model-policy.d.ts +1 -1
  77. package/dist/model-policy.js +1 -1
  78. package/dist/platform-activity-row-BJHUHXAT.js +169 -0
  79. package/dist/platform-activity-row-BJHUHXAT.js.map +1 -0
  80. package/dist/{queue-surface-implementation-2KVJTYXH.js → queue-surface-implementation-BSKUWHRI.js} +76 -16
  81. package/dist/queue-surface-implementation-BSKUWHRI.js.map +1 -0
  82. package/dist/realtime/realtime-control.d.ts +6 -3
  83. package/dist/realtime.js +36 -14
  84. package/dist/realtime.js.map +1 -1
  85. package/dist/session-ui.d.ts +1 -1
  86. package/dist/session-ui.js +6 -2
  87. package/dist/session.d.ts +2 -2
  88. package/dist/session.js +4 -4
  89. package/dist/timeline/index.d.ts +1 -1
  90. package/dist/timeline/platform-activity-row.d.ts +15 -0
  91. package/dist/timeline/screenshot-lightbox.d.ts +1 -1
  92. package/dist/timeline/shared.d.ts +4 -2
  93. package/dist/timeline/types.d.ts +50 -5
  94. package/dist/types/machines.d.ts +2 -2
  95. package/package.json +3 -3
  96. package/src/client.ts +12 -1
  97. package/src/components/artifacts/editable-spreadsheet.tsx +1 -1
  98. package/src/components/browser-viewer.tsx +361 -58
  99. package/src/components/chat-composer.tsx +1 -1
  100. package/src/components/composer.tsx +6 -6
  101. package/src/components/computer-viewer.tsx +368 -115
  102. package/src/components/enrollment-consent.tsx +64 -6
  103. package/src/components/file-browser.tsx +117 -2
  104. package/src/components/human-input-form.tsx +103 -59
  105. package/src/components/human-input-surface.tsx +1 -1
  106. package/src/components/machine-card.tsx +111 -0
  107. package/src/components/machine-input-display.ts +15 -0
  108. package/src/components/machines/machine-detail.tsx +47 -0
  109. package/src/components/machines/machine-operation-policy.tsx +179 -0
  110. package/src/components/machines-dashboard.tsx +6 -0
  111. package/src/components/markdown.tsx +174 -20
  112. package/src/components/message-timeline.tsx +215 -18
  113. package/src/components/model-policy-picker.tsx +9 -17
  114. package/src/components/pierre-file.tsx +1 -1
  115. package/src/components/queue-surface-implementation.tsx +82 -16
  116. package/src/components/sandbox-files.tsx +135 -19
  117. package/src/components/sandbox-workspace.tsx +204 -92
  118. package/src/components/session-chrome.tsx +415 -208
  119. package/src/components/session-status.tsx +1 -1
  120. package/src/components/user-message-body.tsx +168 -48
  121. package/src/components/workspace-dock.tsx +141 -175
  122. package/src/hooks/channel-cache.ts +104 -0
  123. package/src/hooks/shared-machines-list.ts +239 -0
  124. package/src/hooks/use-attached-browsers.ts +5 -1
  125. package/src/hooks/use-browser-frame-stream.ts +6 -1
  126. package/src/hooks/use-browser-session.ts +6 -4
  127. package/src/hooks/use-browser-sessions.ts +7 -1
  128. package/src/hooks/use-channels.ts +32 -10
  129. package/src/hooks/use-composer.ts +760 -291
  130. package/src/hooks/use-computer-frame-stream.ts +14 -9
  131. package/src/hooks/use-computer-session.ts +16 -14
  132. package/src/hooks/use-computer-sessions.ts +8 -1
  133. package/src/hooks/use-last-started-turn-policy.ts +1 -1
  134. package/src/hooks/use-machine-chip.ts +23 -6
  135. package/src/hooks/use-machines.ts +104 -6
  136. package/src/hooks/use-sandbox-files.ts +195 -140
  137. package/src/hooks/use-sandbox-git.ts +58 -29
  138. package/src/hooks/use-session-background-commands.ts +56 -0
  139. package/src/hooks/use-session-capabilities.ts +18 -0
  140. package/src/hooks/use-turn-queue.ts +111 -12
  141. package/src/hooks/use-workspace-sessions.ts +5 -1
  142. package/src/index.ts +26 -4
  143. package/src/lib/interaction-errors.ts +18 -0
  144. package/src/machines.ts +9 -1
  145. package/src/model-policy.ts +10 -2
  146. package/src/realtime/realtime-control.tsx +53 -17
  147. package/src/session-context.ts +1 -0
  148. package/src/session-ui.ts +6 -1
  149. package/src/session.ts +9 -1
  150. package/src/timeline/activity-rail.tsx +20 -75
  151. package/src/timeline/index.ts +4 -0
  152. package/src/timeline/platform-activity-row.tsx +201 -0
  153. package/src/timeline/projection.ts +529 -25
  154. package/src/timeline/screenshot-lightbox.tsx +48 -14
  155. package/src/timeline/shared.tsx +8 -2
  156. package/src/timeline/tool-renderers.tsx +104 -6
  157. package/src/timeline/turn-summary.tsx +6 -1
  158. package/src/timeline/types.ts +77 -2
  159. package/src/types/machines.ts +10 -0
  160. package/styles/compiled.css +115 -104
  161. package/dist/chunk-5UQPNLIO.js.map +0 -1
  162. package/dist/chunk-6KLX2RH5.js.map +0 -1
  163. package/dist/chunk-B6WPZSTT.js.map +0 -1
  164. package/dist/chunk-HA46CYQK.js.map +0 -1
  165. package/dist/chunk-IGC6A2ZI.js.map +0 -1
  166. package/dist/chunk-LLOLUHVG.js +0 -5937
  167. package/dist/chunk-LLOLUHVG.js.map +0 -1
  168. package/dist/chunk-MKPIAGVL.js.map +0 -1
  169. package/dist/chunk-R3AZOAB5.js.map +0 -1
  170. package/dist/chunk-XBZWOM2K.js.map +0 -1
  171. package/dist/chunk-Y7OEDUJ5.js.map +0 -1
  172. package/dist/interactive-workbench-viewers-TOHBXJEE.js +0 -15
  173. package/dist/queue-surface-implementation-2KVJTYXH.js.map +0 -1
  174. /package/dist/{interactive-workbench-viewers-TOHBXJEE.js.map → browser-viewer-GXUICIAG.js.map} +0 -0
@@ -6,6 +6,7 @@ import {
6
6
  type SessionRealtimeController,
7
7
  type SessionRealtimeControllerSnapshot,
8
8
  type CreateSessionRealtimeControllerOptions,
9
+ type SessionRealtimeLifecycleProjection,
9
10
  type SessionRealtimeModel,
10
11
  type WorkspaceRealtimeModelCatalogItem,
11
12
  } from "@opengeni/sdk/realtime";
@@ -280,7 +281,7 @@ export function useSessionRealtime(options: {
280
281
  const eventsReadyRef = useRef(options.eventsReady);
281
282
  lifecycleRef.current = lifecycle;
282
283
  eventsReadyRef.current = options.eventsReady;
283
- const lifecycleActive = lifecycle?.state === "active";
284
+ const lifecycleActive = sessionRealtimeLifecycleIsActive(lifecycle);
284
285
 
285
286
  useEffect(() => {
286
287
  let disposed = false;
@@ -401,6 +402,14 @@ export function useSessionRealtime(options: {
401
402
  };
402
403
  }
403
404
 
405
+ function sessionRealtimeLifecycleIsActive(
406
+ lifecycle: SessionRealtimeLifecycleProjection | null,
407
+ ): boolean {
408
+ if (lifecycle?.state !== "active") return false;
409
+ const leaseExpiresAt = Date.parse(lifecycle.leaseExpiresAt);
410
+ return !Number.isFinite(leaseExpiresAt) || leaseExpiresAt > Date.now();
411
+ }
412
+
404
413
  export function SessionRealtimeControl(props: {
405
414
  client?: RealtimeControllerClient | undefined;
406
415
  workspaceId?: string | undefined;
@@ -423,15 +432,18 @@ export function SessionRealtimeControl(props: {
423
432
  * `none` never attaches a model menu to the bar button.
424
433
  */
425
434
  modelMenu?: "split" | "split-desktop" | "none" | undefined;
435
+ /** Explicitly expose low-level controller diagnostics; disabled by default. */
436
+ showDiagnostics?: boolean | undefined;
426
437
  /** Deterministic browser-test/demo seam. Production hosts should use the SDK default. */
427
438
  controllerFactory?: SessionRealtimeControllerFactory | undefined;
428
439
  }) {
429
440
  const lifecycle = useMemo(() => projectSessionRealtimeLifecycle(props.events), [props.events]);
441
+ const lifecycleActive = sessionRealtimeLifecycleIsActive(lifecycle);
430
442
  const selection = useRealtimeModelSelection({
431
443
  client: props.client,
432
444
  workspaceId: props.workspaceId,
433
445
  codexConnected: props.codexConnected,
434
- activeModel: lifecycle?.state === "active" ? lifecycle.model : null,
446
+ activeModel: lifecycleActive && lifecycle?.state === "active" ? lifecycle.model : null,
435
447
  });
436
448
  const selectedModel = selection.selectedModel;
437
449
  const realtime = useSessionRealtime({
@@ -455,7 +467,7 @@ export function SessionRealtimeControl(props: {
455
467
 
456
468
  useEffect(() => {
457
469
  const pending = autostartModelRef.current;
458
- if (!pending || autostartStartedRef.current || lifecycle?.state === "active") return;
470
+ if (!pending || autostartStartedRef.current || lifecycleActive) return;
459
471
  const available = models.some((model) => model.id === pending && model.available);
460
472
  if (!available) return;
461
473
  if (selectedRealtimeModel.id !== pending) {
@@ -474,7 +486,7 @@ export function SessionRealtimeControl(props: {
474
486
  });
475
487
  }, [
476
488
  canStart,
477
- lifecycle?.state,
489
+ lifecycleActive,
478
490
  models,
479
491
  props.sessionId,
480
492
  props.workspaceId,
@@ -492,6 +504,7 @@ export function SessionRealtimeControl(props: {
492
504
  modelAvailable={selectedModel.available}
493
505
  menuSide="top"
494
506
  modelMenu={props.modelMenu ?? "split"}
507
+ showDiagnostics={props.showDiagnostics}
495
508
  audioRef={realtime.audioRef}
496
509
  selectedModel={selectedModel}
497
510
  models={selection.models}
@@ -609,7 +622,7 @@ const IDLE_REALTIME_SNAPSHOT: SessionRealtimeControllerSnapshot = {
609
622
  error: null,
610
623
  };
611
624
 
612
- export function NewSessionRealtimeControl(props: {
625
+ type NewSessionRealtimeControlProps = {
613
626
  client?: RealtimeControllerClient | undefined;
614
627
  workspaceId?: string | undefined;
615
628
  codexConnected: boolean;
@@ -627,20 +640,43 @@ export function NewSessionRealtimeControl(props: {
627
640
  * `none` never attaches a model menu to the bar button.
628
641
  */
629
642
  modelMenu?: "split" | "split-desktop" | "none" | undefined;
630
- }) {
631
- const internalSelection = useRealtimeModelSelection({
643
+ };
644
+
645
+ type NewSessionRealtimeSelection = {
646
+ models: readonly RealtimeModelOption[];
647
+ selectedModel: RealtimeModelOption;
648
+ selectModel: (modelId: string) => void;
649
+ };
650
+
651
+ export function NewSessionRealtimeControl(props: NewSessionRealtimeControlProps) {
652
+ if (props.models && props.selectedModel && props.onSelectModel) {
653
+ return (
654
+ <NewSessionRealtimeControlView
655
+ {...props}
656
+ selection={{
657
+ models: props.models,
658
+ selectedModel: props.selectedModel,
659
+ selectModel: props.onSelectModel,
660
+ }}
661
+ />
662
+ );
663
+ }
664
+ return <NewSessionRealtimeControlWithInternalSelection {...props} />;
665
+ }
666
+
667
+ function NewSessionRealtimeControlWithInternalSelection(props: NewSessionRealtimeControlProps) {
668
+ const selection = useRealtimeModelSelection({
632
669
  client: props.client,
633
670
  workspaceId: props.workspaceId,
634
671
  codexConnected: props.codexConnected,
635
672
  });
636
- const selection =
637
- props.models && props.selectedModel && props.onSelectModel
638
- ? {
639
- models: props.models,
640
- selectedModel: props.selectedModel,
641
- selectModel: props.onSelectModel,
642
- }
643
- : internalSelection;
673
+ return <NewSessionRealtimeControlView {...props} selection={selection} />;
674
+ }
675
+
676
+ function NewSessionRealtimeControlView(
677
+ props: NewSessionRealtimeControlProps & { selection: NewSessionRealtimeSelection },
678
+ ) {
679
+ const selection = props.selection;
644
680
  const audioRef = useRef<HTMLAudioElement>(null);
645
681
  const [starting, setStarting] = useState(false);
646
682
  const [error, setError] = useState<string | null>(null);
@@ -709,7 +745,7 @@ export function RealtimeVoiceControl(props: {
709
745
  * `none` = start button only.
710
746
  */
711
747
  modelMenu?: "split" | "split-desktop" | "none" | undefined;
712
- showDiagnostics?: boolean;
748
+ showDiagnostics?: boolean | undefined;
713
749
  /** Extra classes on the in-bar mute cluster (e.g. `max-sm:hidden` when mutes live in “+”). */
714
750
  muteControlsClassName?: string | undefined;
715
751
  audioRef: RefObject<HTMLAudioElement | null>;
@@ -763,7 +799,7 @@ export function RealtimeVoiceControl(props: {
763
799
  : modeOwned
764
800
  ? props.onStop
765
801
  : props.onStart;
766
- const diagnosticsVisible = props.showDiagnostics ?? import.meta.env.DEV;
802
+ const diagnosticsVisible = props.showDiagnostics ?? false;
767
803
  const modelMenu = props.modelMenu ?? "split";
768
804
  const showAttachedModelMenu = modelMenu === "split" || modelMenu === "split-desktop";
769
805
  const desktopOnlyModelMenu = modelMenu === "split-desktop";
@@ -316,6 +316,7 @@ export function useEmbeddedSession(
316
316
  "streamEvents",
317
317
  "getComposerDraft",
318
318
  "saveComposerDraft",
319
+ "submitComposerDraft",
319
320
  "sendMessage",
320
321
  "steerMessage",
321
322
  "getQueue",
package/src/session-ui.ts CHANGED
@@ -24,7 +24,12 @@ export type {
24
24
  } from "./timeline/turn-summary";
25
25
  export { QueueSurface } from "./components/queue-surface";
26
26
  export type { QueueSurfaceProps } from "./components/queue-surface";
27
- export { SessionChrome, sessionChromeGoalPillState } from "./components/session-chrome";
27
+ export {
28
+ SessionChrome,
29
+ sessionChromeGoalPillExplanation,
30
+ sessionChromeGoalPillLabel,
31
+ sessionChromeGoalPillState,
32
+ } from "./components/session-chrome";
28
33
  export type {
29
34
  SessionChromeAgentsSignal,
30
35
  SessionChromeProps,
package/src/session.ts CHANGED
@@ -38,7 +38,13 @@ export {
38
38
  shouldSubmitOnKey,
39
39
  FILE_ONLY_MESSAGE_TEXT,
40
40
  } from "./hooks/use-composer";
41
- export type { ComposerSendExtras, ComposerState, UseComposerOptions } from "./hooks/use-composer";
41
+ export type {
42
+ ComposerControllerState,
43
+ ComposerPolicy,
44
+ ComposerSendExtras,
45
+ ComposerState,
46
+ UseComposerOptions,
47
+ } from "./hooks/use-composer";
42
48
  export { useFileAttachments } from "./hooks/use-file-attachments";
43
49
  export type {
44
50
  FileAttachment,
@@ -96,6 +102,8 @@ export type {
96
102
  AgentMessageItem,
97
103
  AuthNeededItem,
98
104
  GoalItem,
105
+ HumanInputAnswerSummary,
106
+ HumanInputItem,
99
107
  MachineInputBatchItem,
100
108
  MachineInputMember,
101
109
  MemoryItem,
@@ -1,10 +1,4 @@
1
- import {
2
- ArrowRightIcon,
3
- BotIcon,
4
- BrainCircuitIcon,
5
- BrainIcon,
6
- SquareTerminalIcon,
7
- } from "lucide-react";
1
+ import { ArrowRightIcon, BotIcon, BrainCircuitIcon } from "lucide-react";
8
2
  import { lazy, Suspense, useLayoutEffect, useRef } from "react";
9
3
  import { jsx as rowJsx, jsxs as rowJsxs } from "react/jsx-runtime";
10
4
  import { Markdown } from "../components/markdown";
@@ -16,9 +10,10 @@ import type { RetainedArtifactLoader, RetainedScreenshotLoader, ToolRegistry } f
16
10
  import { useSeenActivityIds } from "./seen-activity-ids";
17
11
  import { BodyNote, PayloadBlock, ActivityDisclosure, ToolCallTruncationProvider } from "./shared";
18
12
  import { toolDisplayName } from "./tool-display-name";
19
- import type { ActivityItem, MemoryItem, ReasoningItem, SandboxItem, WorkerItem } from "./types";
13
+ import type { ActivityItem, MemoryItem, WorkerItem } from "./types";
20
14
 
21
15
  const LazyFleetDecisionRow = lazy(() => import("./fleet-decision-row"));
16
+ const LazyPlatformActivityRow = lazy(() => import("./platform-activity-row"));
22
17
 
23
18
  /* ----------------------------------------------------------------------------
24
19
  Activity rail
@@ -157,7 +152,23 @@ function renderActivity(
157
152
  ) {
158
153
  switch (item.kind) {
159
154
  case "reasoning":
160
- return <ReasoningRow item={item} />;
155
+ case "sandbox":
156
+ case "startup-phase":
157
+ return (
158
+ <Suspense fallback={null}>
159
+ <LazyPlatformActivityRow
160
+ item={item}
161
+ d={ActivityDisclosure}
162
+ p={PayloadBlock}
163
+ t={toolDisplayName}
164
+ b={BotIcon}
165
+ m={Markdown}
166
+ r={truncate}
167
+ j={rowJsx}
168
+ s={rowJsxs}
169
+ />
170
+ </Suspense>
171
+ );
161
172
  case "tool-call": {
162
173
  const Renderer = toolRegistry.resolve(item);
163
174
  return (
@@ -172,8 +183,6 @@ function renderActivity(
172
183
  }
173
184
  case "worker":
174
185
  return <WorkerRow item={item} onOpenSession={onOpenSession} />;
175
- case "sandbox":
176
- return <SandboxRow item={item} />;
177
186
  case "memory":
178
187
  return <MemoryRow item={item} onMemoryClick={onMemoryClick} />;
179
188
  case "fleet-decision":
@@ -193,32 +202,6 @@ function renderActivity(
193
202
  }
194
203
  }
195
204
 
196
- function ReasoningRow({ item }: { item: ReasoningItem }) {
197
- // Reasoning recedes: a dimmer, lighter-weight title so action rows lead and
198
- // thought rows sit a half-step back in the hierarchy. Body uses the same
199
- // GFM markdown path as agent messages — Codex reasoning often carries
200
- // headings/lists/bold that were previously shown as raw plaintext.
201
- return (
202
- <ActivityDisclosure
203
- icon={<BrainIcon className="size-3.5" />}
204
- iconTone="muted"
205
- title={
206
- item.streaming ? (
207
- "Thinking"
208
- ) : (
209
- <span className="font-normal italic text-og-fg-subtle">Thought</span>
210
- )
211
- }
212
- running={item.streaming}
213
- preview={truncate(item.text, 110)}
214
- >
215
- <div className="text-og-base leading-6 text-og-fg-muted [&_strong]:text-og-fg-muted">
216
- <Markdown streaming={item.streaming}>{item.text}</Markdown>
217
- </div>
218
- </ActivityDisclosure>
219
- );
220
- }
221
-
222
205
  /**
223
206
  * Human labels for the memory kinds, translated at the SDK boundary so a raw
224
207
  * enum slug never renders as UI. Kept local to the library (the app has its own
@@ -317,44 +300,6 @@ function MemoryRow({
317
300
  );
318
301
  }
319
302
 
320
- /**
321
- * A human title for a sandbox operation row. Named platform operations (the lazy
322
- * `sandbox.provision` first-establish that now runs mid-turn) read as calm,
323
- * status-aware English instead of a raw op id — the box coming up should look
324
- * like "Starting sandbox", never an unexplained long-running command. Unnamed /
325
- * unknown ops fall back to the generic id-to-words {@link toolDisplayName}.
326
- */
327
- function sandboxRowTitle(item: SandboxItem): string {
328
- if (item.name === "sandbox.provision") {
329
- if (item.status === "failed") return "Sandbox didn’t start";
330
- if (item.status === "running") return "Starting sandbox";
331
- if (item.origin === "resumed") return "Sandbox reattached";
332
- if (item.origin === "restored") return "Sandbox restored";
333
- if (item.origin === "created") return "Sandbox created";
334
- return "Sandbox ready";
335
- }
336
- return toolDisplayName(item.name);
337
- }
338
-
339
- function SandboxRow({ item }: { item: SandboxItem }) {
340
- return (
341
- <ActivityDisclosure
342
- icon={<SquareTerminalIcon className="size-3.5" />}
343
- iconTone={
344
- item.status === "failed" ? "failed" : item.status === "running" ? "running" : "muted"
345
- }
346
- title={sandboxRowTitle(item)}
347
- running={item.status === "running"}
348
- failed={item.status === "failed"}
349
- cancelled={item.status === "cancelled"}
350
- preview={item.command ?? undefined}
351
- >
352
- {item.command ? <PayloadBlock label="Command" value={item.command} /> : null}
353
- {item.output ? <PayloadBlock label="Output" value={item.output} /> : null}
354
- </ActivityDisclosure>
355
- );
356
- }
357
-
358
303
  /** Spawned/messaged worker sessions get a first-class card, not a tool row. */
359
304
  function WorkerRow({
360
305
  item,
@@ -36,12 +36,16 @@ export type {
36
36
  FleetDecisionItem,
37
37
  FleetDecisionScoreItem,
38
38
  GoalItem,
39
+ HumanInputAnswerSummary,
40
+ HumanInputItem,
39
41
  MachineInputBatchItem,
40
42
  MachineInputMember,
41
43
  MemoryItem,
42
44
  NoticeItem,
43
45
  ReasoningItem,
44
46
  SandboxItem,
47
+ StartupPhase,
48
+ StartupPhaseItem,
45
49
  SessionStatusItem,
46
50
  TimelineGroup,
47
51
  TimelineItem,
@@ -0,0 +1,201 @@
1
+ import type { ReasoningItem, SandboxItem, StartupPhaseItem } from "./types";
2
+
3
+ type RowJsx = typeof import("react/jsx-runtime").jsx;
4
+ type RowJsxs = typeof import("react/jsx-runtime").jsxs;
5
+
6
+ export default function PlatformActivityRow({
7
+ item,
8
+ d: ActivityDisclosure,
9
+ p: PayloadBlock,
10
+ t: displayName,
11
+ b: BotIcon,
12
+ m: Markdown,
13
+ r: truncate,
14
+ j,
15
+ s,
16
+ }: {
17
+ item: ReasoningItem | SandboxItem | StartupPhaseItem;
18
+ d: typeof import("./shared").ActivityDisclosure;
19
+ p: typeof import("./shared").PayloadBlock;
20
+ t: (name: string) => string;
21
+ b: typeof import("lucide-react").BotIcon;
22
+ m: typeof import("../components/markdown").Markdown;
23
+ r: typeof import("../lib/format").truncate;
24
+ j: RowJsx;
25
+ s: RowJsxs;
26
+ }) {
27
+ if (item.kind === "reasoning") {
28
+ return j(ActivityDisclosure, {
29
+ icon: brainIcon(j, s),
30
+ iconTone: "muted",
31
+ title: item.streaming
32
+ ? "Thinking"
33
+ : j("span", {
34
+ className: "font-normal italic text-og-fg-subtle",
35
+ children: "Thought",
36
+ }),
37
+ running: item.streaming,
38
+ preview: truncate(item.text, 110),
39
+ children: j("div", {
40
+ className: "text-og-base leading-6 text-og-fg-muted [&_strong]:text-og-fg-muted",
41
+ children: j(Markdown, { streaming: item.streaming, children: item.text }),
42
+ }),
43
+ });
44
+ }
45
+ if (item.kind === "sandbox") {
46
+ return s(ActivityDisclosure, {
47
+ icon: terminalIcon(j, s),
48
+ iconTone:
49
+ item.status === "failed" ? "failed" : item.status === "running" ? "running" : "muted",
50
+ title: sandboxRowTitle(item, displayName),
51
+ running: item.status === "running",
52
+ failed: item.status === "failed",
53
+ cancelled: item.status === "cancelled",
54
+ preview: item.command ?? undefined,
55
+ children: [
56
+ item.command ? j(PayloadBlock, { label: "Command", value: item.command }, "command") : null,
57
+ item.output ? j(PayloadBlock, { label: "Output", value: item.output }, "output") : null,
58
+ ],
59
+ });
60
+ }
61
+
62
+ const failed = item.status === "failed";
63
+ const running = item.status === "running";
64
+ const duration = startupDuration(item.durationMs);
65
+ return j(ActivityDisclosure, {
66
+ icon: j(BotIcon, { className: "size-3.5" }),
67
+ iconTone: failed ? "failed" : running ? "running" : "muted",
68
+ title: startupPhaseTitle(item.phase, item.status, item.outcome),
69
+ preview:
70
+ item.phase === "model_preparation"
71
+ ? "Includes overlapping sandbox, rig, repository, and runtime setup shown below."
72
+ : undefined,
73
+ running,
74
+ failed,
75
+ cancelled: item.status === "cancelled",
76
+ chip: duration ? { tone: failed ? "bad" : "muted", text: duration } : undefined,
77
+ expandable: false,
78
+ });
79
+ }
80
+
81
+ function brainIcon(j: RowJsx, s: RowJsxs) {
82
+ return lucideIcon(j, s, "brain", [
83
+ j("path", { d: "M 12 18V5" }, "spine"),
84
+ j("path", { d: "M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4" }, "center"),
85
+ j("path", { d: "M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5" }, "top"),
86
+ j("path", { d: "M17.997 5.125a4 4 0 0 1 2.526 5.77" }, "upper-right"),
87
+ j("path", { d: "M18 18a4 4 0 0 0 2-7.464" }, "right"),
88
+ j("path", { d: "M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517" }, "base"),
89
+ j("path", { d: "M6 18a4 4 0 0 1-2-7.464" }, "left"),
90
+ j("path", { d: "M6.003 5.125a4 4 0 0 0-2.526 5.77" }, "upper-left"),
91
+ ]);
92
+ }
93
+
94
+ function terminalIcon(j: RowJsx, s: RowJsxs) {
95
+ return lucideIcon(j, s, "square-terminal", [
96
+ j("path", { d: "m7 11 2-2-2-2" }, "prompt"),
97
+ j("path", { d: "M11 13h4" }, "cursor"),
98
+ j("rect", { width: 18, height: 18, x: 3, y: 3, rx: 2, ry: 2 }, "frame"),
99
+ ]);
100
+ }
101
+
102
+ function lucideIcon(j: RowJsx, s: RowJsxs, name: string, children: ReturnType<RowJsx>[]) {
103
+ return s("svg", {
104
+ "aria-hidden": "true",
105
+ className: `lucide lucide-${name} size-3.5`,
106
+ fill: "none",
107
+ height: 24,
108
+ stroke: "currentColor",
109
+ strokeLinecap: "round",
110
+ strokeLinejoin: "round",
111
+ strokeWidth: 2,
112
+ viewBox: "0 0 24 24",
113
+ width: 24,
114
+ children,
115
+ });
116
+ }
117
+
118
+ function sandboxRowTitle(item: SandboxItem, displayName: (name: string) => string): string {
119
+ if (item.name === "sandbox.provision") {
120
+ return startupPhaseTitle(
121
+ "sandbox",
122
+ item.status === "cancelled" ? "complete" : item.status,
123
+ item.origin ?? null,
124
+ );
125
+ }
126
+ return displayName(item.name);
127
+ }
128
+
129
+ function startupPhaseTitle(
130
+ phase: StartupPhaseItem["phase"],
131
+ status: StartupPhaseItem["status"],
132
+ outcome: StartupPhaseItem["outcome"],
133
+ ): string {
134
+ if (status === "complete" && phase === "sandbox" && outcome && outcome !== "skipped") {
135
+ return `Sandbox ${outcome === "resumed" ? "reattached" : outcome}`;
136
+ }
137
+ if (status === "complete" && phase === "rig" && outcome === "skipped") {
138
+ return "Rig already ready";
139
+ }
140
+ const statusIndex =
141
+ status === "running" ? 0 : status === "failed" ? 1 : status === "cancelled" ? 2 : 3;
142
+ return STARTUP_PHASE_TITLES[phase][statusIndex];
143
+ }
144
+
145
+ const STARTUP_PHASE_TITLES: Record<
146
+ StartupPhaseItem["phase"],
147
+ readonly [string, string, string, string]
148
+ > = {
149
+ queue: [
150
+ "Waiting for a worker",
151
+ "Worker startup failed",
152
+ "Worker wait interrupted",
153
+ "Worker started",
154
+ ],
155
+ sandbox: [
156
+ "Starting sandbox",
157
+ "Sandbox didn’t start",
158
+ "Sandbox startup interrupted",
159
+ "Sandbox ready",
160
+ ],
161
+ rig: ["Setting up rig", "Rig setup failed", "Rig setup interrupted", "Rig ready"],
162
+ repository: [
163
+ "Preparing repository",
164
+ "Repository preparation failed",
165
+ "Repository preparation interrupted",
166
+ "Repository ready",
167
+ ],
168
+ files: [
169
+ "Preparing files",
170
+ "File preparation failed",
171
+ "File preparation interrupted",
172
+ "Files ready",
173
+ ],
174
+ tools: [
175
+ "Connecting tools",
176
+ "Tool connection failed",
177
+ "Tool connection interrupted",
178
+ "Tools ready",
179
+ ],
180
+ model_preparation: [
181
+ "Preparing runtime and model request",
182
+ "Runtime/model preparation failed",
183
+ "Runtime/model preparation interrupted",
184
+ "Model request dispatched",
185
+ ],
186
+ provider_first_byte: [
187
+ "Waiting for model",
188
+ "Model didn’t respond",
189
+ "Model wait interrupted",
190
+ "Model started responding",
191
+ ],
192
+ };
193
+
194
+ function startupDuration(durationMs: number | null): string | null {
195
+ if (durationMs === null || !Number.isFinite(durationMs) || durationMs < 0) return null;
196
+ if (durationMs < 1_000) return `${Math.round(durationMs)}ms`;
197
+ if (durationMs < 60_000) return `${(durationMs / 1_000).toFixed(1)}s`;
198
+ const minutes = Math.floor(durationMs / 60_000);
199
+ const seconds = Math.round((durationMs % 60_000) / 1_000);
200
+ return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
201
+ }