@opengeni/react 1.4.4 → 1.4.5-canary.37458084490001

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 (234) hide show
  1. package/README.md +17 -6
  2. package/dist/artifacts.d.ts +1 -0
  3. package/dist/artifacts.js +22 -15
  4. package/dist/artifacts.js.map +1 -1
  5. package/dist/{browser-viewer-5EEAS3IU.js → browser-viewer-CV23VIHB.js} +8 -6
  6. package/dist/chunk-3ZC6QPOM.js +174 -0
  7. package/dist/chunk-3ZC6QPOM.js.map +1 -0
  8. package/dist/{chunk-YVFRBXST.js → chunk-4BCVCJSU.js} +2 -2
  9. package/dist/{chunk-JHTIMBOL.js → chunk-5JSUNNQN.js} +26 -4
  10. package/dist/{chunk-JHTIMBOL.js.map → chunk-5JSUNNQN.js.map} +1 -1
  11. package/dist/{chunk-I6OY3MWC.js → chunk-6FOTSVWU.js} +2 -2
  12. package/dist/{chunk-IUHHPMAX.js → chunk-7RRBX5EM.js} +14 -85
  13. package/dist/chunk-7RRBX5EM.js.map +1 -0
  14. package/dist/{chunk-L2J3PU7A.js → chunk-7ZB7I3KP.js} +24 -98
  15. package/dist/chunk-7ZB7I3KP.js.map +1 -0
  16. package/dist/{chunk-LVS3MH5T.js → chunk-ATIRUHBZ.js} +128 -10
  17. package/dist/chunk-ATIRUHBZ.js.map +1 -0
  18. package/dist/chunk-BOFEQC6K.js +162 -0
  19. package/dist/chunk-BOFEQC6K.js.map +1 -0
  20. package/dist/{chunk-2OQA3CPB.js → chunk-CESBPQP5.js} +19 -5
  21. package/dist/chunk-CESBPQP5.js.map +1 -0
  22. package/dist/{chunk-IYQCWGU5.js → chunk-GHJU7XG7.js} +35 -62
  23. package/dist/chunk-GHJU7XG7.js.map +1 -0
  24. package/dist/{chunk-G6YRCIYJ.js → chunk-HNVWMDNN.js} +55 -60
  25. package/dist/chunk-HNVWMDNN.js.map +1 -0
  26. package/dist/{chunk-PHBFJCEU.js → chunk-I5EGRXXG.js} +110 -7
  27. package/dist/chunk-I5EGRXXG.js.map +1 -0
  28. package/dist/{chunk-PBCZL4JP.js → chunk-INIZSNXT.js} +2 -2
  29. package/dist/{chunk-XS67YG5R.js → chunk-IX3K4THH.js} +24 -3
  30. package/dist/chunk-IX3K4THH.js.map +1 -0
  31. package/dist/chunk-IZV5FTJC.js +55 -0
  32. package/dist/chunk-IZV5FTJC.js.map +1 -0
  33. package/dist/chunk-JDJJMBB3.js +1883 -0
  34. package/dist/chunk-JDJJMBB3.js.map +1 -0
  35. package/dist/{chunk-C5BF4L26.js → chunk-KHF2VRXF.js} +7 -7
  36. package/dist/{chunk-QMMCJGFI.js → chunk-L5OKM2VX.js} +2 -2
  37. package/dist/chunk-LHF6BVM4.js +83 -0
  38. package/dist/chunk-LHF6BVM4.js.map +1 -0
  39. package/dist/chunk-N7S72EXK.js +25 -0
  40. package/dist/chunk-N7S72EXK.js.map +1 -0
  41. package/dist/{chunk-CVYCLFMH.js → chunk-PEHTX3V7.js} +19 -1
  42. package/dist/chunk-PEHTX3V7.js.map +1 -0
  43. package/dist/chunk-R5R5RQQD.js +51 -0
  44. package/dist/chunk-R5R5RQQD.js.map +1 -0
  45. package/dist/chunk-RI42H5IU.js +326 -0
  46. package/dist/chunk-RI42H5IU.js.map +1 -0
  47. package/dist/{chunk-JH7V627O.js → chunk-SHLZMAJN.js} +2 -11
  48. package/dist/chunk-SHLZMAJN.js.map +1 -0
  49. package/dist/{chunk-EAOSEIXB.js → chunk-TZQSHCZN.js} +692 -1996
  50. package/dist/chunk-TZQSHCZN.js.map +1 -0
  51. package/dist/chunk-ZIB4H5LN.js +101 -0
  52. package/dist/chunk-ZIB4H5LN.js.map +1 -0
  53. package/dist/client.d.ts +9 -3
  54. package/dist/clipboard.js +1 -1
  55. package/dist/components/browser-viewer.d.ts +6 -0
  56. package/dist/components/composer.d.ts +3 -69
  57. package/dist/components/human-input-form.d.ts +3 -32
  58. package/dist/components/markdown.d.ts +8 -0
  59. package/dist/components/model-policy-picker.d.ts +4 -37
  60. package/dist/components/opper-mark.d.ts +7 -0
  61. package/dist/components/provider-recovery-notice.d.ts +15 -0
  62. package/dist/components/remark-soft-line-breaks.d.ts +38 -0
  63. package/dist/components/sandbox-workspace.d.ts +13 -0
  64. package/dist/components/session-chrome.d.ts +5 -3
  65. package/dist/components/session-conversation.d.ts +15 -3
  66. package/dist/composer-messages.d.ts +74 -0
  67. package/dist/composer-messages.js +9 -0
  68. package/dist/composer.js +15 -10
  69. package/dist/{computer-viewer-ELNLZGHI.js → computer-viewer-K4CKYRX6.js} +4 -4
  70. package/dist/connect-setup.d.ts +2 -0
  71. package/dist/connect.d.ts +1 -1
  72. package/dist/connect.js +21 -6
  73. package/dist/connect.js.map +1 -1
  74. package/dist/{embedded-voice-NIL4RDX3.js → embedded-voice-3ZFWWJ6O.js} +6 -4
  75. package/dist/{embedded-voice-NIL4RDX3.js.map → embedded-voice-3ZFWWJ6O.js.map} +1 -1
  76. package/dist/hooks/shared-machines-list.d.ts +4 -0
  77. package/dist/hooks/use-client-config-flags.d.ts +6 -0
  78. package/dist/hooks/use-goal.d.ts +2 -1
  79. package/dist/human-input-model.d.ts +40 -0
  80. package/dist/index.d.ts +4 -1
  81. package/dist/index.js +116 -73
  82. package/dist/index.js.map +1 -1
  83. package/dist/interaction.js +5 -5
  84. package/dist/{latest-question-2POFPYNB.js → latest-question-SWC6QEVN.js} +3 -3
  85. package/dist/lib/clipboard.d.ts +7 -0
  86. package/dist/lib/provider-recovery.d.ts +49 -0
  87. package/dist/machines.js +2 -2
  88. package/dist/model-mark-paths.d.ts +34 -0
  89. package/dist/model-mark-paths.js +16 -0
  90. package/dist/model-picker-model.d.ts +111 -0
  91. package/dist/model-picker-model.js +25 -0
  92. package/dist/model-picker-model.js.map +1 -0
  93. package/dist/{model-policy-picker-menu-WRNSRFQC.js → model-policy-picker-menu-NM4RPTY6.js} +6 -4
  94. package/dist/model-policy-picker-menu-NM4RPTY6.js.map +1 -0
  95. package/dist/model-policy.d.ts +10 -0
  96. package/dist/model-policy.js +5 -1
  97. package/dist/organization-model.d.ts +32 -0
  98. package/dist/organization-model.js +65 -0
  99. package/dist/organization-model.js.map +1 -0
  100. package/dist/{platform-activity-row-PJJVATTZ.js → platform-activity-row-NJ47YBXW.js} +7 -84
  101. package/dist/platform-activity-row-NJ47YBXW.js.map +1 -0
  102. package/dist/queue-presentation-model.d.ts +72 -0
  103. package/dist/queue-presentation-model.js +38 -0
  104. package/dist/queue-presentation-model.js.map +1 -0
  105. package/dist/{queue-surface-implementation-67FS7FUI.js → queue-surface-implementation-YPJFQ3O6.js} +24 -252
  106. package/dist/queue-surface-implementation-YPJFQ3O6.js.map +1 -0
  107. package/dist/realtime.js +5 -3
  108. package/dist/{sandbox-files-34CJJNOY.js → sandbox-files-RCVCMXG7.js} +5 -5
  109. package/dist/sandbox-files-RCVCMXG7.js.map +1 -0
  110. package/dist/sandbox-label-model.d.ts +23 -0
  111. package/dist/sandbox-label-model.js +28 -0
  112. package/dist/sandbox-label-model.js.map +1 -0
  113. package/dist/session-attention-model.d.ts +8 -0
  114. package/dist/session-attention-model.js +46 -0
  115. package/dist/session-attention-model.js.map +1 -0
  116. package/dist/session-feedback-model.d.ts +20 -0
  117. package/dist/session-feedback-model.js +56 -0
  118. package/dist/session-feedback-model.js.map +1 -0
  119. package/dist/session-list-model.d.ts +96 -0
  120. package/dist/session-list-model.js +219 -0
  121. package/dist/session-list-model.js.map +1 -0
  122. package/dist/session-status-model.d.ts +19 -0
  123. package/dist/session-ui.js +26 -17
  124. package/dist/session-ui.js.map +1 -1
  125. package/dist/session.js +8 -6
  126. package/dist/testing/session-scenarios.d.ts +10 -0
  127. package/dist/testing/session-scenarios.js +303 -0
  128. package/dist/testing/session-scenarios.js.map +1 -0
  129. package/dist/timeline/genie-copy.d.ts +3 -0
  130. package/dist/timeline/genie-loading.d.ts +11 -0
  131. package/dist/timeline/index.d.ts +1 -1
  132. package/dist/timeline/notice-presentation.d.ts +7 -0
  133. package/dist/timeline/patch-apply-command.d.ts +18 -0
  134. package/dist/timeline/platform-activity-presentation.d.ts +7 -0
  135. package/dist/timeline/presented-image.d.ts +6 -1
  136. package/dist/timeline/question-nav-model.d.ts +23 -0
  137. package/dist/timeline/registry.d.ts +10 -1
  138. package/dist/timeline/retained-loaders.d.ts +1 -1
  139. package/dist/timeline/tool-presentation.d.ts +172 -0
  140. package/dist/timeline/turn-summary-model.d.ts +90 -0
  141. package/dist/timeline/turn-summary.d.ts +4 -74
  142. package/dist/timeline/work-presentation.d.ts +42 -0
  143. package/dist/timeline-model.d.ts +24 -0
  144. package/dist/timeline-model.js +152 -0
  145. package/dist/timeline-model.js.map +1 -0
  146. package/package.json +47 -3
  147. package/src/artifacts.ts +1 -0
  148. package/src/client.ts +9 -3
  149. package/src/components/browser-viewer.tsx +23 -2
  150. package/src/components/claude-mark.tsx +2 -1
  151. package/src/components/composer.tsx +4 -144
  152. package/src/components/grok-mark.tsx +2 -1
  153. package/src/components/human-input-form.tsx +13 -124
  154. package/src/components/markdown.tsx +19 -1
  155. package/src/components/message-timeline.tsx +45 -99
  156. package/src/components/model-mark.tsx +2 -1
  157. package/src/components/model-policy-picker-menu.tsx +30 -85
  158. package/src/components/model-policy-picker.tsx +15 -148
  159. package/src/components/opper-mark.tsx +18 -0
  160. package/src/components/provider-recovery-notice.tsx +41 -0
  161. package/src/components/queue-surface-implementation.tsx +20 -349
  162. package/src/components/remark-soft-line-breaks.ts +166 -0
  163. package/src/components/sandbox-workspace.tsx +17 -1
  164. package/src/components/session-chrome.tsx +114 -99
  165. package/src/components/session-conversation.tsx +55 -6
  166. package/src/components/session-status.tsx +17 -16
  167. package/src/composer-messages.ts +150 -0
  168. package/src/connect-setup.tsx +45 -11
  169. package/src/connect.ts +1 -1
  170. package/src/hooks/shared-machines-list.ts +34 -1
  171. package/src/hooks/use-client-config-flags.ts +8 -0
  172. package/src/hooks/use-goal.ts +66 -10
  173. package/src/human-input-model.ts +147 -0
  174. package/src/index.ts +16 -0
  175. package/src/lib/clipboard.ts +29 -0
  176. package/src/lib/format.ts +7 -0
  177. package/src/lib/provider-recovery.ts +202 -0
  178. package/src/model-mark-paths.ts +53 -0
  179. package/src/model-picker-model.ts +330 -0
  180. package/src/model-policy.ts +43 -2
  181. package/src/organization-model.ts +118 -0
  182. package/src/queue-presentation-model.ts +464 -0
  183. package/src/sandbox-label-model.ts +41 -0
  184. package/src/session-attention-model.ts +59 -0
  185. package/src/session-feedback-model.ts +95 -0
  186. package/src/session-list-model.ts +327 -0
  187. package/src/session-status-model.ts +62 -0
  188. package/src/testing/session-scenarios.ts +326 -0
  189. package/src/timeline/activity-rail.tsx +5 -17
  190. package/src/timeline/genie-copy.ts +19 -0
  191. package/src/timeline/genie-loading.tsx +13 -18
  192. package/src/timeline/index.ts +1 -0
  193. package/src/timeline/notice-presentation.ts +22 -0
  194. package/src/timeline/patch-apply-command.tsx +135 -0
  195. package/src/timeline/platform-activity-presentation.ts +122 -0
  196. package/src/timeline/platform-activity-row.tsx +6 -96
  197. package/src/timeline/presented-image.ts +16 -2
  198. package/src/timeline/question-nav-model.ts +28 -0
  199. package/src/timeline/registry.ts +11 -1
  200. package/src/timeline/retained-loaders.ts +9 -4
  201. package/src/timeline/rolling-activity.tsx +7 -19
  202. package/src/timeline/tool-presentation.ts +1316 -0
  203. package/src/timeline/tool-renderers.tsx +226 -1025
  204. package/src/timeline/turn-summary-model.ts +338 -0
  205. package/src/timeline/turn-summary.tsx +21 -326
  206. package/src/timeline/work-presentation.ts +139 -0
  207. package/src/timeline-model.ts +108 -0
  208. package/styles/compiled.css +3 -0
  209. package/styles/connect.css +4 -0
  210. package/dist/chunk-2OQA3CPB.js.map +0 -1
  211. package/dist/chunk-CVYCLFMH.js.map +0 -1
  212. package/dist/chunk-EAOSEIXB.js.map +0 -1
  213. package/dist/chunk-G6YRCIYJ.js.map +0 -1
  214. package/dist/chunk-HXYOSSQO.js +0 -117
  215. package/dist/chunk-HXYOSSQO.js.map +0 -1
  216. package/dist/chunk-IUHHPMAX.js.map +0 -1
  217. package/dist/chunk-IYQCWGU5.js.map +0 -1
  218. package/dist/chunk-JH7V627O.js.map +0 -1
  219. package/dist/chunk-L2J3PU7A.js.map +0 -1
  220. package/dist/chunk-LVS3MH5T.js.map +0 -1
  221. package/dist/chunk-PHBFJCEU.js.map +0 -1
  222. package/dist/chunk-XS67YG5R.js.map +0 -1
  223. package/dist/platform-activity-row-PJJVATTZ.js.map +0 -1
  224. package/dist/queue-surface-implementation-67FS7FUI.js.map +0 -1
  225. /package/dist/{browser-viewer-5EEAS3IU.js.map → browser-viewer-CV23VIHB.js.map} +0 -0
  226. /package/dist/{chunk-YVFRBXST.js.map → chunk-4BCVCJSU.js.map} +0 -0
  227. /package/dist/{chunk-I6OY3MWC.js.map → chunk-6FOTSVWU.js.map} +0 -0
  228. /package/dist/{chunk-PBCZL4JP.js.map → chunk-INIZSNXT.js.map} +0 -0
  229. /package/dist/{chunk-C5BF4L26.js.map → chunk-KHF2VRXF.js.map} +0 -0
  230. /package/dist/{chunk-QMMCJGFI.js.map → chunk-L5OKM2VX.js.map} +0 -0
  231. /package/dist/{computer-viewer-ELNLZGHI.js.map → composer-messages.js.map} +0 -0
  232. /package/dist/{model-policy-picker-menu-WRNSRFQC.js.map → computer-viewer-K4CKYRX6.js.map} +0 -0
  233. /package/dist/{latest-question-2POFPYNB.js.map → latest-question-SWC6QEVN.js.map} +0 -0
  234. /package/dist/{sandbox-files-34CJJNOY.js.map → model-mark-paths.js.map} +0 -0
@@ -293,6 +293,13 @@ export type UseSandboxWorkspaceTabsOptions = ClientOverride & {
293
293
  * attachment, and navigation callbacks are not activated.
294
294
  */
295
295
  surfaces?: readonly SandboxWorkspaceSurface[] | undefined;
296
+ /**
297
+ * Whether this viewer may read the workspace machine fleet
298
+ * (`GET /machines`, permission `enrollments:read`). Defaults to true. Pass
299
+ * false for a viewer without it: the dock then never requests the fleet and
300
+ * the machine chip derives from the session's own capabilities alone.
301
+ */
302
+ machinesEnabled?: boolean | undefined;
296
303
  /** Override the source-driven default tab (e.g. a host landing tab id). When
297
304
  * omitted the workbench picks Changes-vs-Files from capture or live Git. */
298
305
  initialTab?: string | null | undefined;
@@ -397,6 +404,7 @@ export function useSandboxWorkspaceTabs(
397
404
  const initialTab = options.initialTab ?? null;
398
405
  const activeTab = options.activeTab ?? initialTab;
399
406
  const workspaceVisible = options.workspaceVisible ?? true;
407
+ const machinesEnabled = options.machinesEnabled ?? true;
400
408
  const requestedSurfaces = options.surfaces ?? WORKBENCH_SURFACES;
401
409
  const surfaceSet = new Set<SandboxWorkspaceSurface>(requestedSurfaces);
402
410
  const changesEnabled = surfaceSet.has(WORKBENCH_TAB_CHANGES);
@@ -465,7 +473,7 @@ export function useSandboxWorkspaceTabs(
465
473
  workspaceId,
466
474
  sessionId,
467
475
  pollIntervalMs: MACHINES_SESSION_POLL_MS,
468
- enabled: machineSurfaceEnabled,
476
+ enabled: machineSurfaceEnabled && machinesEnabled,
469
477
  });
470
478
  const activeMachine: MachineView | null =
471
479
  machines.machines.find((m) => m.sandboxId === machines.activeSandboxId) ??
@@ -995,6 +1003,12 @@ export type SandboxWorkspaceProps = ClientOverride & {
995
1003
  * Terminal, Browser, and Desktop. Disabled surfaces remain behaviorally dormant.
996
1004
  */
997
1005
  surfaces?: readonly SandboxWorkspaceSurface[] | undefined;
1006
+ /**
1007
+ * Whether this viewer may read the workspace machine fleet
1008
+ * (`GET /machines`, permission `enrollments:read`). Defaults to true. Pass
1009
+ * false for a viewer without it so the dock never requests the fleet.
1010
+ */
1011
+ machinesEnabled?: boolean | undefined;
998
1012
  /** Host tabs injected BEFORE the workbench tabs (e.g. a "Run" landing tab). */
999
1013
  leadingTabs?: WorkspaceTab[] | undefined;
1000
1014
  /** Host tabs injected AFTER the workbench tabs (e.g. a "Debug" tab). */
@@ -1060,6 +1074,7 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
1060
1074
  events,
1061
1075
  primary,
1062
1076
  surfaces,
1077
+ machinesEnabled,
1063
1078
  leadingTabs,
1064
1079
  trailingTabs,
1065
1080
  initialTab,
@@ -1180,6 +1195,7 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
1180
1195
  sessionId,
1181
1196
  events,
1182
1197
  ...(surfaces ? { surfaces } : {}),
1198
+ ...(machinesEnabled !== undefined ? { machinesEnabled } : {}),
1183
1199
  ...(initialTab ? { initialTab } : {}),
1184
1200
  activeTab: activeTabHint,
1185
1201
  workspaceVisible: collapsed !== true,
@@ -26,6 +26,8 @@ import { ChildSessionLink } from "./child-session-link";
26
26
  *
27
27
  * Inbox and queue stay separate segments. Queue hover actions wire to
28
28
  * `UseTurnQueueResult` (`editTurn` / `steerTurn` / `moveTurn` / `removeTurn`).
29
+ * The collapsed queue chip previews the latest queued message beside its Steer
30
+ * action; open queue rows keep Steer visible and reveal the rest on hover.
29
31
  * Inbox has no product dismiss API; pass `onDismissIncoming` when the host
30
32
  * wants a visible action (dev harness may use a local dummy).
31
33
  *
@@ -75,6 +77,13 @@ import type { UseTurnQueueResult } from "../hooks/use-turn-queue";
75
77
  import { cn } from "../lib/cn";
76
78
  import { useErrorMessage } from "../lib/error-message";
77
79
  import { formatClockTime } from "../lib/format";
80
+ import {
81
+ countAuthoritativeQueuedTurns,
82
+ isAuthoritativeQueuedTurn,
83
+ QUEUE_REPLACE_DRAFT_COPY,
84
+ queuedTurnPresentation,
85
+ queueNeighborAnchors,
86
+ } from "../queue-presentation-model";
78
87
  import { requestQueueDraftEdit } from "./queue-draft-policy";
79
88
  import { QUEUE_ITEM_CONTENT_UNAVAILABLE, queueItemContent } from "./queue-item-content";
80
89
  import { TimelineAnnotationsChip, type TimelineAnnotationLike } from "./timeline-annotations";
@@ -143,11 +152,6 @@ type GoalPillState =
143
152
  | "invariant_broken"
144
153
  | "completed";
145
154
 
146
- type QueuedTurnPresentation = {
147
- kind: "prompt" | "realtime_voice" | "realtime_voice_handoff";
148
- text: string;
149
- };
150
-
151
155
  const GOAL_LABEL: Record<GoalPillState, string> = {
152
156
  pursuing: "Pursuing",
153
157
  waiting: "Waiting",
@@ -252,35 +256,7 @@ export function sessionChromeGoalPillExplanation(
252
256
  return null;
253
257
  }
254
258
 
255
- function queuedTurnPresentation(turn: SessionTurn): QueuedTurnPresentation {
256
- const realtimeDelegation = objectValue(turn.metadata.realtimeDelegation);
257
- const inputTranscript = realtimeDelegation?.inputTranscript;
258
- if (typeof inputTranscript === "string" && inputTranscript.trim()) {
259
- return { kind: "realtime_voice", text: inputTranscript.trim() };
260
- }
261
- if (objectValue(turn.metadata.realtimeTailFlush)) {
262
- return { kind: "realtime_voice_handoff", text: "Remaining voice context" };
263
- }
264
- return { kind: "prompt", text: turn.prompt };
265
- }
266
-
267
- function isSteeringTurn(turn: SessionTurn): boolean {
268
- return turn.metadata.delivery === "steer";
269
- }
270
-
271
- function isAuthoritativeQueuedTurn(
272
- turn: SessionTurn,
273
- mutationFor: UseTurnQueueResult["mutationFor"],
274
- ): boolean {
275
- return !isSteeringTurn(turn) && mutationFor(turn.id) !== "steer";
276
- }
277
-
278
- export function countAuthoritativeQueuedTurns(
279
- turns: readonly SessionTurn[],
280
- mutationFor: UseTurnQueueResult["mutationFor"],
281
- ): number {
282
- return turns.filter((turn) => isAuthoritativeQueuedTurn(turn, mutationFor)).length;
283
- }
259
+ export { countAuthoritativeQueuedTurns };
284
260
 
285
261
  function isOptimisticQueuedMessage(
286
262
  message: ComposerOptimisticMessage,
@@ -344,12 +320,6 @@ export function sessionChromeShouldOfferQueue(input: {
344
320
  );
345
321
  }
346
322
 
347
- function objectValue(value: unknown): Record<string, unknown> | null {
348
- return value !== null && typeof value === "object" && !Array.isArray(value)
349
- ? (value as Record<string, unknown>)
350
- : null;
351
- }
352
-
353
323
  /** Select pill state from the goal's authoritative continuation projection. */
354
324
  export function sessionChromeGoalPillState(
355
325
  goalStatus: "active" | "paused" | "completed",
@@ -831,6 +801,22 @@ export function SessionChrome({
831
801
  }, [active, signals]);
832
802
 
833
803
  const open = active !== null;
804
+ // Collapsed queue chip shows the latest queued message so a Send that lands
805
+ // in the queue is visible without opening the list; Steer acts on that
806
+ // message once the server has confirmed it.
807
+ const latestOptimisticQueued = optimisticQueued[optimisticQueued.length - 1];
808
+ const latestQueuedTurn = queuedTurns[queuedTurns.length - 1];
809
+ const queuePeek: { text: string | null; turnId: string | null } | null =
810
+ queue.error || queue.mutationError
811
+ ? null
812
+ : latestOptimisticQueued
813
+ ? { text: latestOptimisticQueued.text.trim() || null, turnId: null }
814
+ : latestQueuedTurn
815
+ ? {
816
+ text: queuedTurnPresentation(latestQueuedTurn).text.trim() || null,
817
+ turnId: latestQueuedTurn.id,
818
+ }
819
+ : null;
834
820
 
835
821
  if (signals.length === 0 && !stopping) return null;
836
822
 
@@ -1056,7 +1042,14 @@ export function SessionChrome({
1056
1042
  <span className={cn("shrink-0", toneClass(signal.tone, selected))}>
1057
1043
  {signal.icon}
1058
1044
  </span>
1059
- <span className={cn("font-medium text-og-fg", "min-w-0 truncate")}>
1045
+ <span
1046
+ className={cn(
1047
+ "font-medium text-og-fg",
1048
+ signal.id === "queue" && queuePeek?.text && !selected
1049
+ ? "shrink-0"
1050
+ : "min-w-0 truncate",
1051
+ )}
1052
+ >
1060
1053
  {signal.id === "goal" ? "Goal · " : null}
1061
1054
  {signal.id === "goal"
1062
1055
  ? goalState === "pursuing"
@@ -1068,6 +1061,21 @@ export function SessionChrome({
1068
1061
  : `${queuedTurns.length + optimisticQueued.length} queued`
1069
1062
  : signal.label}
1070
1063
  </span>
1064
+ {signal.id === "queue" && queuePeek?.text && !selected ? (
1065
+ <AnimatePresence initial={false} mode="popLayout">
1066
+ <motion.span
1067
+ key={queuePeek.text}
1068
+ data-testid="session-chrome-queue-peek"
1069
+ className="min-w-0 max-w-[min(24rem,60vw)] truncate text-og-fg-muted"
1070
+ initial={reduceMotion ? false : { opacity: 0, y: 3 }}
1071
+ animate={{ opacity: 1, y: 0 }}
1072
+ exit={{ opacity: 0, transition: { duration: crossfadeDuration } }}
1073
+ transition={{ duration: crossfadeDuration, ease }}
1074
+ >
1075
+ {queuePeek.text}
1076
+ </motion.span>
1077
+ </AnimatePresence>
1078
+ ) : null}
1071
1079
  </button>
1072
1080
  {signal.id === "goal" && goal && record && !readOnly ? (
1073
1081
  <div className="flex shrink-0 items-center pr-1 pl-0.5">
@@ -1101,19 +1109,27 @@ export function SessionChrome({
1101
1109
  <Trash2Icon className="size-3" />
1102
1110
  </IconAction>
1103
1111
  </div>
1104
- ) : signal.id === "queue" && canMutateQueue && queuedTurns[0] ? (
1112
+ ) : signal.id === "queue" && canMutateQueue && queuePeek && !selected ? (
1105
1113
  <div className="flex shrink-0 items-center pr-1 pl-0.5">
1106
1114
  <IconAction
1107
- label="Steer first queued message"
1115
+ label="Steer latest queued message"
1108
1116
  analyticsAction="steer"
1109
1117
  text="Steer"
1110
1118
  tip={QUEUE_STEER_TIP}
1111
1119
  disabled={
1112
- queue.mutating || Boolean(queue.mutationFor(queuedTurns[0].id))
1120
+ !queuePeek.turnId ||
1121
+ queue.mutating ||
1122
+ Boolean(queue.mutationFor(queuePeek.turnId))
1113
1123
  }
1114
- onClick={() => void queue.steerTurn(queuedTurns[0]!.id)}
1124
+ onClick={() => {
1125
+ if (queuePeek.turnId) void queue.steerTurn(queuePeek.turnId);
1126
+ }}
1115
1127
  >
1116
- <CornerDownRightIcon className="size-3" />
1128
+ {queuePeek.turnId && queue.mutationFor(queuePeek.turnId) === "steer" ? (
1129
+ <Loader2Icon className="size-3 animate-og-spin" />
1130
+ ) : (
1131
+ <CornerDownRightIcon className="size-3" />
1132
+ )}
1117
1133
  </IconAction>
1118
1134
  </div>
1119
1135
  ) : null}
@@ -1426,8 +1442,7 @@ function QueuePanel({
1426
1442
  const voice = presentation.kind !== "prompt";
1427
1443
  const pending = mutationFor(turn.id);
1428
1444
  const settling = !interactiveTurnIds.has(turn.id);
1429
- const beforeUp = index > 0 ? (turns[index - 1]?.id ?? null) : null;
1430
- const beforeDown = index < turns.length - 1 ? (turns[index + 2]?.id ?? null) : null;
1445
+ const { beforeUp, beforeDown } = queueNeighborAnchors(turns, index);
1431
1446
  const showActions = !readOnly && (onEdit || onSteer || onRemove || onMove);
1432
1447
  const confirmingReplace = replaceDraftFor === turn.id;
1433
1448
  return (
@@ -1453,27 +1468,59 @@ function QueuePanel({
1453
1468
  annotations={turn.annotations ?? []}
1454
1469
  />
1455
1470
  {showActions ? (
1456
- <div className="flex shrink-0 items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100 max-sm:opacity-100">
1457
- {onMove && turns.length > 1 ? (
1458
- <>
1471
+ <div className="flex shrink-0 items-center gap-0.5">
1472
+ {/* Reorder, edit and delete stay on hover; Steer is always visible. */}
1473
+ <div className="flex shrink-0 items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100 max-sm:opacity-100">
1474
+ {onMove && turns.length > 1 ? (
1475
+ <>
1476
+ <IconAction
1477
+ label={`Move queued prompt ${index + 1} up`}
1478
+ tip="Move up"
1479
+ disabled={settling || pending !== null || index === 0}
1480
+ onClick={() => onMove(turn.id, beforeUp)}
1481
+ >
1482
+ <ArrowUpIcon className="size-3" />
1483
+ </IconAction>
1484
+ <IconAction
1485
+ label={`Move queued prompt ${index + 1} down`}
1486
+ tip="Move down"
1487
+ disabled={settling || pending !== null || index >= turns.length - 1}
1488
+ onClick={() => onMove(turn.id, beforeDown)}
1489
+ >
1490
+ <ArrowDownIcon className="size-3" />
1491
+ </IconAction>
1492
+ </>
1493
+ ) : null}
1494
+ {onEdit ? (
1459
1495
  <IconAction
1460
- label={`Move queued prompt ${index + 1} up`}
1461
- tip="Move up"
1462
- disabled={settling || pending !== null || index === 0}
1463
- onClick={() => onMove(turn.id, beforeUp)}
1496
+ label={`Edit queued prompt ${index + 1}`}
1497
+ tip={QUEUE_EDIT_TIP}
1498
+ disabled={settling || pending !== null}
1499
+ onClick={() => onEdit(turn)}
1464
1500
  >
1465
- <ArrowUpIcon className="size-3" />
1501
+ {pending === "edit" ? (
1502
+ <Loader2Icon className="size-3 animate-og-spin" />
1503
+ ) : (
1504
+ <PencilIcon className="size-3" />
1505
+ )}
1466
1506
  </IconAction>
1507
+ ) : null}
1508
+ {onRemove ? (
1467
1509
  <IconAction
1468
- label={`Move queued prompt ${index + 1} down`}
1469
- tip="Move down"
1470
- disabled={settling || pending !== null || index >= turns.length - 1}
1471
- onClick={() => onMove(turn.id, beforeDown)}
1510
+ label={`Remove queued prompt ${index + 1}`}
1511
+ tip={QUEUE_DELETE_TIP}
1512
+ disabled={settling || pending !== null}
1513
+ onClick={() => onRemove(turn.id)}
1514
+ danger
1472
1515
  >
1473
- <ArrowDownIcon className="size-3" />
1516
+ {pending === "delete" ? (
1517
+ <Loader2Icon className="size-3 animate-og-spin" />
1518
+ ) : (
1519
+ <Trash2Icon className="size-3" />
1520
+ )}
1474
1521
  </IconAction>
1475
- </>
1476
- ) : null}
1522
+ ) : null}
1523
+ </div>
1477
1524
  {onSteer ? (
1478
1525
  <IconAction
1479
1526
  label={`Steer queued prompt ${index + 1}`}
@@ -1490,59 +1537,27 @@ function QueuePanel({
1490
1537
  )}
1491
1538
  </IconAction>
1492
1539
  ) : null}
1493
- {onEdit ? (
1494
- <IconAction
1495
- label={`Edit queued prompt ${index + 1}`}
1496
- tip={QUEUE_EDIT_TIP}
1497
- disabled={settling || pending !== null}
1498
- onClick={() => onEdit(turn)}
1499
- >
1500
- {pending === "edit" ? (
1501
- <Loader2Icon className="size-3 animate-og-spin" />
1502
- ) : (
1503
- <PencilIcon className="size-3" />
1504
- )}
1505
- </IconAction>
1506
- ) : null}
1507
- {onRemove ? (
1508
- <IconAction
1509
- label={`Remove queued prompt ${index + 1}`}
1510
- tip={QUEUE_DELETE_TIP}
1511
- disabled={settling || pending !== null}
1512
- onClick={() => onRemove(turn.id)}
1513
- danger
1514
- >
1515
- {pending === "delete" ? (
1516
- <Loader2Icon className="size-3 animate-og-spin" />
1517
- ) : (
1518
- <Trash2Icon className="size-3" />
1519
- )}
1520
- </IconAction>
1521
- ) : null}
1522
1540
  </div>
1523
1541
  ) : null}
1524
1542
  </div>
1525
1543
  {confirmingReplace ? (
1526
1544
  <div className="rounded-og-sm border border-og-status-waiting/30 bg-og-status-waiting/10 p-2 text-og-xs text-og-fg">
1527
- <p>Your composer already has a draft. Replace it with this queued prompt?</p>
1528
- <p className="mt-0.5 text-og-fg-muted">
1529
- The current draft will be permanently discarded; this queued prompt is preserved
1530
- until you confirm.
1531
- </p>
1545
+ <p>{QUEUE_REPLACE_DRAFT_COPY.title}</p>
1546
+ <p className="mt-0.5 text-og-fg-muted">{QUEUE_REPLACE_DRAFT_COPY.detail}</p>
1532
1547
  <div className="mt-2 flex justify-end gap-1.5">
1533
1548
  <button
1534
1549
  type="button"
1535
1550
  className="rounded-og-sm px-2 py-1 font-medium hover:bg-og-surface-3/70 focus-visible:ring-2 focus-visible:ring-og-accent/40"
1536
1551
  onClick={onCancelReplace}
1537
1552
  >
1538
- Keep current draft
1553
+ {QUEUE_REPLACE_DRAFT_COPY.keep}
1539
1554
  </button>
1540
1555
  <button
1541
1556
  type="button"
1542
1557
  className="rounded-og-sm border border-og-primary-border bg-og-primary text-og-primary-fg px-2 py-1 font-medium hover:bg-og-primary-hover focus-visible:ring-2 focus-visible:ring-og-accent/40"
1543
1558
  onClick={onConfirmReplace}
1544
1559
  >
1545
- Replace and edit
1560
+ {QUEUE_REPLACE_DRAFT_COPY.replace}
1546
1561
  </button>
1547
1562
  </div>
1548
1563
  </div>
@@ -35,6 +35,7 @@ import {
35
35
  createWorkspaceRetainedVideoLoader,
36
36
  } from "../timeline/retained-loaders";
37
37
  import { useRealtimeVoiceModels } from "../hooks/use-realtime-voice-models";
38
+ import { EMBEDDED_GENIE_LOADING, type GenieLoadingOptions } from "../timeline/genie-loading";
38
39
  import { projectPendingApprovals } from "../approvals";
39
40
  import { ApprovalSurface } from "./approval-surface";
40
41
  import { ChatComposer, type ChatComposerProps } from "./chat-composer";
@@ -42,6 +43,8 @@ import type { ComposerTranscriptionControlProps } from "./composer-transcription
42
43
  import { SessionChrome } from "./session-chrome";
43
44
  import { HumanInputSurface, type HumanInputSurfaceProps } from "./human-input-surface";
44
45
  import { MessageTimeline, type MessageTimelineProps } from "./message-timeline";
46
+ import { ProviderRecoveryNotice } from "./provider-recovery-notice";
47
+ import { currentProviderRecovery } from "../lib/provider-recovery";
45
48
  import {
46
49
  chainLinkResolvers,
47
50
  sessionLinkResolver,
@@ -119,6 +122,8 @@ export type SessionConversationProps = ClientOverride &
119
122
  /**
120
123
  * File attachments in the composer. Defaults to true; the attach control
121
124
  * appears only when the deployment's client config enables file uploads.
125
+ * A session proxy reports them off when `files` is false and for anonymous
126
+ * visitors (`visitor: true` from `resolve`) unless it sets `visitorUploads`.
122
127
  */
123
128
  attachments?: boolean | undefined;
124
129
  /**
@@ -128,11 +133,20 @@ export type SessionConversationProps = ClientOverride &
128
133
  */
129
134
  voiceInput?: boolean | undefined;
130
135
  /**
131
- * Live speech-to-speech voice in the composer. Defaults to true; the voice
132
- * button appears only when the workspace offers an available voice model
133
- * and the proxy has not turned it off (`realtimeVoice: false`).
136
+ * Live speech-to-speech voice in the composer. Opt-in: a call spends the
137
+ * workspace's credits and asks for the microphone, so it is off unless this
138
+ * is `true` or the client config reports `realtimeVoice: true`
139
+ * (`createSessionProxyHandler({ realtimeVoice: true })`). The button then
140
+ * appears only when the workspace offers an available voice model and the
141
+ * proxy has not turned voice off (`realtimeVoice: false`).
134
142
  */
135
143
  realtimeVoice?: boolean | undefined;
144
+ /**
145
+ * Copy and visual for the "working" indicator before the first reply.
146
+ * Defaults to neutral copy ("Thinking…"); omitted fields keep those
147
+ * defaults. See `MessageTimeline`'s `genieLoading`.
148
+ */
149
+ genieLoading?: GenieLoadingOptions | undefined;
136
150
  /**
137
151
  * Show the model/reasoning picker. End users of an embedded product rarely
138
152
  * choose models, so it is hidden unless this is `true` or the client config
@@ -217,7 +231,8 @@ function Conversation({
217
231
  allowanceExhaustedLabels,
218
232
  attachments: attachmentsRequested = true,
219
233
  voiceInput: voiceInputRequested = true,
220
- realtimeVoice: realtimeVoiceRequested = true,
234
+ realtimeVoice: realtimeVoiceProp,
235
+ genieLoading,
221
236
  modelPicker,
222
237
  modelPickerProps,
223
238
  userMessageDisclosureLabels,
@@ -327,6 +342,24 @@ function Conversation({
327
342
  if (feed.error) void feed.jumpToLatest();
328
343
  };
329
344
  const running = status === "running" || status === "recovering" || status === "waiting_capacity";
345
+ const realtimeVoiceRequested = realtimeVoiceProp ?? config.realtimeVoiceOffered;
346
+ const loadingOptions = useMemo(() => embeddedGenieLoading(genieLoading), [genieLoading]);
347
+ const providerRecovery = useMemo(
348
+ () =>
349
+ detail.session
350
+ ? currentProviderRecovery(
351
+ {
352
+ id: detail.session.id,
353
+ status,
354
+ activeTurnId: detail.session.activeTurnId,
355
+ effectiveControl:
356
+ queue.effectiveControl ?? detail.session.effectiveControl ?? undefined,
357
+ },
358
+ feed.events,
359
+ )
360
+ : null,
361
+ [detail.session, status, queue.effectiveControl, feed.events],
362
+ );
330
363
  const voiceModels = useRealtimeVoiceModels(
331
364
  context.client,
332
365
  context.workspaceId,
@@ -404,6 +437,7 @@ function Conversation({
404
437
  : (renderInteractiveBlock ?? defaultInteractiveBlock)
405
438
  }
406
439
  userMessageDisclosureLabels={userMessageDisclosureLabels}
440
+ genieLoading={loadingOptions}
407
441
  renderAllowanceExhausted={renderAllowanceExhausted}
408
442
  allowanceExhaustedLabels={allowanceExhaustedLabels}
409
443
  // Isolated and clipped: floating navigation stays inside the timeline.
@@ -457,6 +491,10 @@ function Conversation({
457
491
  error={control.error}
458
492
  />
459
493
  ) : null}
494
+ <ProviderRecoveryNotice
495
+ className="mx-auto max-w-3xl px-1"
496
+ recovery={providerRecovery}
497
+ />
460
498
  <HumanInputSurface
461
499
  className="mx-auto max-w-3xl"
462
500
  loadSkillReview={loadSkillReview}
@@ -551,6 +589,17 @@ function Conversation({
551
589
  );
552
590
  }
553
591
 
592
+ /** Neutral embedded defaults under the host's overrides; an empty phrase list keeps them. */
593
+ function embeddedGenieLoading(options: GenieLoadingOptions | undefined): GenieLoadingOptions {
594
+ if (!options) return EMBEDDED_GENIE_LOADING;
595
+ return {
596
+ ...EMBEDDED_GENIE_LOADING,
597
+ ...options,
598
+ phrases: options.phrases?.length ? options.phrases : EMBEDDED_GENIE_LOADING.phrases,
599
+ messages: { ...EMBEDDED_GENIE_LOADING.messages, ...options.messages },
600
+ };
601
+ }
602
+
554
603
  type ConversationChromeProps = {
555
604
  sessionId: string;
556
605
  client: unknown;
@@ -668,11 +717,11 @@ function useRetainedLoaders(
668
717
  ...(typeof base.downloadRetainedArtifact === "function" &&
669
718
  typeof base.createRetainedArtifactDownloadUrl === "function"
670
719
  ? {
671
- loadRetainedArtifact: (artifact, signal) =>
720
+ loadRetainedArtifact: (artifact, signal, options) =>
672
721
  createWorkspaceRetainedArtifactLoader(
673
722
  get() as Required<RetainedLoaderClient>,
674
723
  workspaceId,
675
- )(artifact, signal),
724
+ )(artifact, signal, options),
676
725
  }
677
726
  : {}),
678
727
  ...(typeof base.downloadRetainedScreenshot === "function"
@@ -1,5 +1,6 @@
1
1
  import type { SessionStatus as SessionStatusValue } from "@opengeni/sdk";
2
2
  import { cn } from "../lib/cn";
3
+ import { SESSION_STATUS_PRESENTATION } from "../session-status-model";
3
4
 
4
5
  export type SessionStatusMeta = {
5
6
  label: string;
@@ -12,52 +13,52 @@ export type SessionStatusMeta = {
12
13
 
13
14
  export const SESSION_STATUS_META: Record<SessionStatusValue, SessionStatusMeta> = {
14
15
  queued: {
15
- label: "Queued",
16
+ label: SESSION_STATUS_PRESENTATION.queued.label,
16
17
  dotClassName: "bg-og-status-queued",
17
18
  badgeClassName: "text-og-fg-muted border-og-border bg-og-status-queued/10",
18
- pulse: false,
19
+ pulse: SESSION_STATUS_PRESENTATION.queued.pulse,
19
20
  },
20
21
  running: {
21
- label: "Running",
22
+ label: SESSION_STATUS_PRESENTATION.running.label,
22
23
  dotClassName: "bg-og-status-running",
23
24
  badgeClassName: "text-og-status-running border-og-status-running/30 bg-og-status-running/10",
24
- pulse: true,
25
+ pulse: SESSION_STATUS_PRESENTATION.running.pulse,
25
26
  },
26
27
  recovering: {
27
- label: "Recovering",
28
+ label: SESSION_STATUS_PRESENTATION.recovering.label,
28
29
  dotClassName: "bg-og-status-running",
29
30
  badgeClassName: "text-og-status-running border-og-status-running/30 bg-og-status-running/10",
30
- pulse: true,
31
+ pulse: SESSION_STATUS_PRESENTATION.recovering.pulse,
31
32
  },
32
33
  waiting_capacity: {
33
- label: "Waiting for capacity",
34
+ label: SESSION_STATUS_PRESENTATION.waiting_capacity.label,
34
35
  dotClassName: "bg-og-status-waiting",
35
36
  badgeClassName: "text-og-status-waiting border-og-status-waiting/35 bg-og-status-waiting/10",
36
- pulse: true,
37
+ pulse: SESSION_STATUS_PRESENTATION.waiting_capacity.pulse,
37
38
  },
38
39
  idle: {
39
- label: "Idle",
40
+ label: SESSION_STATUS_PRESENTATION.idle.label,
40
41
  dotClassName: "bg-og-status-idle",
41
42
  badgeClassName: "text-og-status-idle border-og-status-idle/30 bg-og-status-idle/10",
42
- pulse: false,
43
+ pulse: SESSION_STATUS_PRESENTATION.idle.pulse,
43
44
  },
44
45
  requires_action: {
45
- label: "Waiting on you",
46
+ label: SESSION_STATUS_PRESENTATION.requires_action.label,
46
47
  dotClassName: "bg-og-status-waiting",
47
48
  badgeClassName: "text-og-status-waiting border-og-status-waiting/35 bg-og-status-waiting/10",
48
- pulse: true,
49
+ pulse: SESSION_STATUS_PRESENTATION.requires_action.pulse,
49
50
  },
50
51
  failed: {
51
- label: "Failed",
52
+ label: SESSION_STATUS_PRESENTATION.failed.label,
52
53
  dotClassName: "bg-og-status-failed",
53
54
  badgeClassName: "text-og-status-failed border-og-status-failed/35 bg-og-status-failed/10",
54
- pulse: false,
55
+ pulse: SESSION_STATUS_PRESENTATION.failed.pulse,
55
56
  },
56
57
  cancelled: {
57
- label: "Cancelled",
58
+ label: SESSION_STATUS_PRESENTATION.cancelled.label,
58
59
  dotClassName: "bg-og-status-cancelled",
59
60
  badgeClassName: "text-og-fg-subtle border-og-border bg-og-status-cancelled/10",
60
- pulse: false,
61
+ pulse: SESSION_STATUS_PRESENTATION.cancelled.pulse,
61
62
  },
62
63
  };
63
64