@opengeni/react 1.4.2 → 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 (240) 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-WGJ43YGI.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-MDIXFKA3.js → chunk-6FOTSVWU.js} +303 -109
  12. package/dist/chunk-6FOTSVWU.js.map +1 -0
  13. package/dist/{chunk-IUHHPMAX.js → chunk-7RRBX5EM.js} +14 -85
  14. package/dist/chunk-7RRBX5EM.js.map +1 -0
  15. package/dist/{chunk-L2J3PU7A.js → chunk-7ZB7I3KP.js} +24 -98
  16. package/dist/chunk-7ZB7I3KP.js.map +1 -0
  17. package/dist/{chunk-LVS3MH5T.js → chunk-ATIRUHBZ.js} +128 -10
  18. package/dist/chunk-ATIRUHBZ.js.map +1 -0
  19. package/dist/chunk-BOFEQC6K.js +162 -0
  20. package/dist/chunk-BOFEQC6K.js.map +1 -0
  21. package/dist/{chunk-VCSERYXH.js → chunk-CESBPQP5.js} +309 -162
  22. package/dist/chunk-CESBPQP5.js.map +1 -0
  23. package/dist/{chunk-IYQCWGU5.js → chunk-GHJU7XG7.js} +35 -62
  24. package/dist/chunk-GHJU7XG7.js.map +1 -0
  25. package/dist/{chunk-G6YRCIYJ.js → chunk-HNVWMDNN.js} +55 -60
  26. package/dist/chunk-HNVWMDNN.js.map +1 -0
  27. package/dist/{chunk-PHBFJCEU.js → chunk-I5EGRXXG.js} +110 -7
  28. package/dist/chunk-I5EGRXXG.js.map +1 -0
  29. package/dist/{chunk-PBCZL4JP.js → chunk-INIZSNXT.js} +2 -2
  30. package/dist/{chunk-XS67YG5R.js → chunk-IX3K4THH.js} +24 -3
  31. package/dist/chunk-IX3K4THH.js.map +1 -0
  32. package/dist/chunk-IZV5FTJC.js +55 -0
  33. package/dist/chunk-IZV5FTJC.js.map +1 -0
  34. package/dist/chunk-JDJJMBB3.js +1883 -0
  35. package/dist/chunk-JDJJMBB3.js.map +1 -0
  36. package/dist/{chunk-C5BF4L26.js → chunk-KHF2VRXF.js} +7 -7
  37. package/dist/{chunk-QMMCJGFI.js → chunk-L5OKM2VX.js} +2 -2
  38. package/dist/chunk-LHF6BVM4.js +83 -0
  39. package/dist/chunk-LHF6BVM4.js.map +1 -0
  40. package/dist/chunk-N7S72EXK.js +25 -0
  41. package/dist/chunk-N7S72EXK.js.map +1 -0
  42. package/dist/{chunk-CVYCLFMH.js → chunk-PEHTX3V7.js} +19 -1
  43. package/dist/chunk-PEHTX3V7.js.map +1 -0
  44. package/dist/chunk-R5R5RQQD.js +51 -0
  45. package/dist/chunk-R5R5RQQD.js.map +1 -0
  46. package/dist/chunk-RI42H5IU.js +326 -0
  47. package/dist/chunk-RI42H5IU.js.map +1 -0
  48. package/dist/{chunk-JH7V627O.js → chunk-SHLZMAJN.js} +2 -11
  49. package/dist/chunk-SHLZMAJN.js.map +1 -0
  50. package/dist/{chunk-CREMJMVN.js → chunk-TZQSHCZN.js} +692 -1996
  51. package/dist/chunk-TZQSHCZN.js.map +1 -0
  52. package/dist/chunk-ZIB4H5LN.js +101 -0
  53. package/dist/chunk-ZIB4H5LN.js.map +1 -0
  54. package/dist/client.d.ts +9 -3
  55. package/dist/clipboard.js +1 -1
  56. package/dist/components/browser-viewer.d.ts +6 -0
  57. package/dist/components/composer.d.ts +3 -69
  58. package/dist/components/human-input-form.d.ts +3 -32
  59. package/dist/components/markdown.d.ts +8 -0
  60. package/dist/components/model-policy-picker.d.ts +4 -37
  61. package/dist/components/opper-mark.d.ts +7 -0
  62. package/dist/components/provider-recovery-notice.d.ts +15 -0
  63. package/dist/components/remark-soft-line-breaks.d.ts +38 -0
  64. package/dist/components/sandbox-workspace.d.ts +13 -0
  65. package/dist/components/session-chrome.d.ts +5 -3
  66. package/dist/components/session-conversation.d.ts +15 -3
  67. package/dist/composer-messages.d.ts +74 -0
  68. package/dist/composer-messages.js +9 -0
  69. package/dist/composer.js +15 -10
  70. package/dist/{computer-viewer-46UIN7F3.js → computer-viewer-K4CKYRX6.js} +4 -4
  71. package/dist/connect-setup.d.ts +2 -0
  72. package/dist/connect.d.ts +1 -1
  73. package/dist/connect.js +21 -6
  74. package/dist/connect.js.map +1 -1
  75. package/dist/{embedded-voice-NIL4RDX3.js → embedded-voice-3ZFWWJ6O.js} +6 -4
  76. package/dist/{embedded-voice-NIL4RDX3.js.map → embedded-voice-3ZFWWJ6O.js.map} +1 -1
  77. package/dist/hooks/shared-machines-list.d.ts +4 -0
  78. package/dist/hooks/use-client-config-flags.d.ts +6 -0
  79. package/dist/hooks/use-computer-session.d.ts +2 -0
  80. package/dist/hooks/use-goal.d.ts +2 -1
  81. package/dist/human-input-model.d.ts +40 -0
  82. package/dist/index.d.ts +4 -1
  83. package/dist/index.js +116 -73
  84. package/dist/index.js.map +1 -1
  85. package/dist/interaction.js +5 -5
  86. package/dist/{latest-question-2POFPYNB.js → latest-question-SWC6QEVN.js} +3 -3
  87. package/dist/lib/clipboard.d.ts +7 -0
  88. package/dist/lib/provider-recovery.d.ts +49 -0
  89. package/dist/machines.js +2 -2
  90. package/dist/model-mark-paths.d.ts +34 -0
  91. package/dist/model-mark-paths.js +16 -0
  92. package/dist/model-picker-model.d.ts +111 -0
  93. package/dist/model-picker-model.js +25 -0
  94. package/dist/model-picker-model.js.map +1 -0
  95. package/dist/{model-policy-picker-menu-WRNSRFQC.js → model-policy-picker-menu-NM4RPTY6.js} +6 -4
  96. package/dist/model-policy-picker-menu-NM4RPTY6.js.map +1 -0
  97. package/dist/model-policy.d.ts +10 -0
  98. package/dist/model-policy.js +5 -1
  99. package/dist/organization-model.d.ts +32 -0
  100. package/dist/organization-model.js +65 -0
  101. package/dist/organization-model.js.map +1 -0
  102. package/dist/{platform-activity-row-PJJVATTZ.js → platform-activity-row-NJ47YBXW.js} +7 -84
  103. package/dist/platform-activity-row-NJ47YBXW.js.map +1 -0
  104. package/dist/queue-presentation-model.d.ts +72 -0
  105. package/dist/queue-presentation-model.js +38 -0
  106. package/dist/queue-presentation-model.js.map +1 -0
  107. package/dist/{queue-surface-implementation-67FS7FUI.js → queue-surface-implementation-YPJFQ3O6.js} +24 -252
  108. package/dist/queue-surface-implementation-YPJFQ3O6.js.map +1 -0
  109. package/dist/realtime.js +5 -3
  110. package/dist/{sandbox-files-34CJJNOY.js → sandbox-files-RCVCMXG7.js} +5 -5
  111. package/dist/sandbox-files-RCVCMXG7.js.map +1 -0
  112. package/dist/sandbox-label-model.d.ts +23 -0
  113. package/dist/sandbox-label-model.js +28 -0
  114. package/dist/sandbox-label-model.js.map +1 -0
  115. package/dist/session-attention-model.d.ts +8 -0
  116. package/dist/session-attention-model.js +46 -0
  117. package/dist/session-attention-model.js.map +1 -0
  118. package/dist/session-feedback-model.d.ts +20 -0
  119. package/dist/session-feedback-model.js +56 -0
  120. package/dist/session-feedback-model.js.map +1 -0
  121. package/dist/session-list-model.d.ts +96 -0
  122. package/dist/session-list-model.js +219 -0
  123. package/dist/session-list-model.js.map +1 -0
  124. package/dist/session-status-model.d.ts +19 -0
  125. package/dist/session-ui.js +26 -17
  126. package/dist/session-ui.js.map +1 -1
  127. package/dist/session.js +8 -6
  128. package/dist/testing/session-scenarios.d.ts +10 -0
  129. package/dist/testing/session-scenarios.js +303 -0
  130. package/dist/testing/session-scenarios.js.map +1 -0
  131. package/dist/timeline/genie-copy.d.ts +3 -0
  132. package/dist/timeline/genie-loading.d.ts +11 -0
  133. package/dist/timeline/index.d.ts +1 -1
  134. package/dist/timeline/notice-presentation.d.ts +7 -0
  135. package/dist/timeline/patch-apply-command.d.ts +18 -0
  136. package/dist/timeline/platform-activity-presentation.d.ts +7 -0
  137. package/dist/timeline/presented-image.d.ts +6 -1
  138. package/dist/timeline/question-nav-model.d.ts +23 -0
  139. package/dist/timeline/registry.d.ts +10 -1
  140. package/dist/timeline/retained-loaders.d.ts +1 -1
  141. package/dist/timeline/tool-presentation.d.ts +172 -0
  142. package/dist/timeline/turn-summary-model.d.ts +90 -0
  143. package/dist/timeline/turn-summary.d.ts +4 -74
  144. package/dist/timeline/work-presentation.d.ts +42 -0
  145. package/dist/timeline-model.d.ts +24 -0
  146. package/dist/timeline-model.js +152 -0
  147. package/dist/timeline-model.js.map +1 -0
  148. package/package.json +47 -3
  149. package/src/artifacts.ts +1 -0
  150. package/src/client.ts +9 -3
  151. package/src/components/browser-viewer.tsx +23 -2
  152. package/src/components/claude-mark.tsx +2 -1
  153. package/src/components/composer.tsx +4 -144
  154. package/src/components/computer-viewer.tsx +139 -30
  155. package/src/components/grok-mark.tsx +2 -1
  156. package/src/components/human-input-form.tsx +13 -124
  157. package/src/components/markdown.tsx +19 -1
  158. package/src/components/message-timeline.tsx +45 -99
  159. package/src/components/model-mark.tsx +2 -1
  160. package/src/components/model-policy-picker-menu.tsx +30 -85
  161. package/src/components/model-policy-picker.tsx +15 -148
  162. package/src/components/opper-mark.tsx +18 -0
  163. package/src/components/provider-recovery-notice.tsx +41 -0
  164. package/src/components/queue-surface-implementation.tsx +20 -349
  165. package/src/components/remark-soft-line-breaks.ts +166 -0
  166. package/src/components/sandbox-workspace.tsx +17 -1
  167. package/src/components/session-chrome.tsx +114 -99
  168. package/src/components/session-conversation.tsx +55 -6
  169. package/src/components/session-list.tsx +1 -1
  170. package/src/components/session-status.tsx +17 -16
  171. package/src/composer-messages.ts +150 -0
  172. package/src/connect-setup.tsx +45 -11
  173. package/src/connect.ts +1 -1
  174. package/src/hooks/shared-machines-list.ts +34 -1
  175. package/src/hooks/use-browser-session.ts +445 -168
  176. package/src/hooks/use-client-config-flags.ts +8 -0
  177. package/src/hooks/use-computer-session.ts +334 -98
  178. package/src/hooks/use-goal.ts +66 -10
  179. package/src/human-input-model.ts +147 -0
  180. package/src/index.ts +16 -0
  181. package/src/lib/clipboard.ts +29 -0
  182. package/src/lib/format.ts +7 -0
  183. package/src/lib/provider-recovery.ts +202 -0
  184. package/src/model-mark-paths.ts +53 -0
  185. package/src/model-picker-model.ts +330 -0
  186. package/src/model-policy.ts +43 -2
  187. package/src/organization-model.ts +118 -0
  188. package/src/queue-presentation-model.ts +464 -0
  189. package/src/sandbox-label-model.ts +41 -0
  190. package/src/session-attention-model.ts +59 -0
  191. package/src/session-feedback-model.ts +95 -0
  192. package/src/session-list-model.ts +327 -0
  193. package/src/session-status-model.ts +62 -0
  194. package/src/testing/session-scenarios.ts +326 -0
  195. package/src/timeline/activity-rail.tsx +5 -17
  196. package/src/timeline/genie-copy.ts +19 -0
  197. package/src/timeline/genie-loading.tsx +13 -18
  198. package/src/timeline/index.ts +1 -0
  199. package/src/timeline/notice-presentation.ts +22 -0
  200. package/src/timeline/patch-apply-command.tsx +135 -0
  201. package/src/timeline/platform-activity-presentation.ts +122 -0
  202. package/src/timeline/platform-activity-row.tsx +6 -96
  203. package/src/timeline/presented-image.ts +16 -2
  204. package/src/timeline/question-nav-model.ts +28 -0
  205. package/src/timeline/registry.ts +11 -1
  206. package/src/timeline/retained-loaders.ts +9 -4
  207. package/src/timeline/rolling-activity.tsx +7 -19
  208. package/src/timeline/tool-presentation.ts +1316 -0
  209. package/src/timeline/tool-renderers.tsx +226 -1025
  210. package/src/timeline/turn-summary-model.ts +338 -0
  211. package/src/timeline/turn-summary.tsx +21 -326
  212. package/src/timeline/work-presentation.ts +139 -0
  213. package/src/timeline-model.ts +108 -0
  214. package/styles/compiled.css +8 -0
  215. package/styles/connect.css +4 -0
  216. package/dist/chunk-CREMJMVN.js.map +0 -1
  217. package/dist/chunk-CVYCLFMH.js.map +0 -1
  218. package/dist/chunk-G6YRCIYJ.js.map +0 -1
  219. package/dist/chunk-HXYOSSQO.js +0 -117
  220. package/dist/chunk-HXYOSSQO.js.map +0 -1
  221. package/dist/chunk-IUHHPMAX.js.map +0 -1
  222. package/dist/chunk-IYQCWGU5.js.map +0 -1
  223. package/dist/chunk-JH7V627O.js.map +0 -1
  224. package/dist/chunk-L2J3PU7A.js.map +0 -1
  225. package/dist/chunk-LVS3MH5T.js.map +0 -1
  226. package/dist/chunk-MDIXFKA3.js.map +0 -1
  227. package/dist/chunk-PHBFJCEU.js.map +0 -1
  228. package/dist/chunk-VCSERYXH.js.map +0 -1
  229. package/dist/chunk-XS67YG5R.js.map +0 -1
  230. package/dist/platform-activity-row-PJJVATTZ.js.map +0 -1
  231. package/dist/queue-surface-implementation-67FS7FUI.js.map +0 -1
  232. /package/dist/{browser-viewer-WGJ43YGI.js.map → browser-viewer-CV23VIHB.js.map} +0 -0
  233. /package/dist/{chunk-YVFRBXST.js.map → chunk-4BCVCJSU.js.map} +0 -0
  234. /package/dist/{chunk-PBCZL4JP.js.map → chunk-INIZSNXT.js.map} +0 -0
  235. /package/dist/{chunk-C5BF4L26.js.map → chunk-KHF2VRXF.js.map} +0 -0
  236. /package/dist/{chunk-QMMCJGFI.js.map → chunk-L5OKM2VX.js.map} +0 -0
  237. /package/dist/{computer-viewer-46UIN7F3.js.map → composer-messages.js.map} +0 -0
  238. /package/dist/{model-policy-picker-menu-WRNSRFQC.js.map → computer-viewer-K4CKYRX6.js.map} +0 -0
  239. /package/dist/{latest-question-2POFPYNB.js.map → latest-question-SWC6QEVN.js.map} +0 -0
  240. /package/dist/{sandbox-files-34CJJNOY.js.map → model-mark-paths.js.map} +0 -0
@@ -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"
@@ -215,7 +215,7 @@ export function SessionList({
215
215
  {formatRelativeTime(session.updatedAt)}
216
216
  </span>
217
217
  </button>
218
- <span className="absolute right-1 top-1.5 hidden gap-0.5 group-focus-within:flex group-hover:flex">
218
+ <span className="absolute right-1 top-1.5 hidden gap-0.5 group-focus-within:flex group-hover:flex pointer-coarse:flex">
219
219
  {rename ? (
220
220
  <button
221
221
  type="button"
@@ -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
 
@@ -0,0 +1,150 @@
1
+ /**
2
+ * The composer's user-facing copy, renderer-free so web and native composers
3
+ * (and hosts translating them) share one message catalog.
4
+ */
5
+ import type { SlashCommand } from "./commands/types";
6
+ import { formatBytes, formatRelativeTime } from "./lib/format";
7
+
8
+ export type ChatComposerMessages = {
9
+ messagePlaceholder: string;
10
+ pausedPlaceholder: string;
11
+ inputLabel: string;
12
+ keyboardHint: string;
13
+ slashCommandBlocked: string;
14
+ controlChangedError: string;
15
+ sendFailedError: string;
16
+ dropFiles: string;
17
+ attachFiles: string;
18
+ pauseAriaLabel: string;
19
+ pauseTitle: string;
20
+ /** Stop control shown while a response runs (`runControl="stop"`). */
21
+ stopAriaLabel?: string | undefined;
22
+ stopTitle?: string | undefined;
23
+ sendMessageAriaLabel: string;
24
+ sendAndResumeAriaLabel: string;
25
+ sendTitle: string;
26
+ sendAndResumeTitle: string;
27
+ annotationNotesRequired: string;
28
+ workspacePaused: string;
29
+ pausedHere: string;
30
+ parentBlocker: string;
31
+ narrowerPause: string;
32
+ pausedBy: (displayName: string) => string;
33
+ queuedAhead: (count: number) => string;
34
+ resumingAndSending: string;
35
+ nextMessageResumes: string;
36
+ resumeThisWorkstream: string;
37
+ resumeShort: string;
38
+ hidePauseDetails: string;
39
+ showPauseDetails: string;
40
+ pauseReasonsLabel: string;
41
+ pausedByLabel: string;
42
+ alsoPausedByLabel: string;
43
+ resumeWorkspace: string;
44
+ resumeFromSession: string;
45
+ sessionCanRun: string;
46
+ stillPausedBy: (displayName: string) => string;
47
+ restoredResourcesLabel: string;
48
+ restoredFile: (fileId: string) => string;
49
+ removeRestoredResource: (index: number) => string;
50
+ uploading: string;
51
+ uploadFailed: string;
52
+ previewAttachment: (name: string) => string;
53
+ previewUnavailable?: string | undefined;
54
+ attachmentPreviewLabel: string;
55
+ downloadAttachment: (name: string) => string;
56
+ closeAttachmentPreview: string;
57
+ retryAttachment: (name: string) => string;
58
+ retryUpload: string;
59
+ removeAttachment: (name: string) => string;
60
+ confirmCommand: (name: string) => string;
61
+ confirmDescription: (command: SlashCommand) => string;
62
+ cancel: string;
63
+ runCommand: (name: string) => string;
64
+ commands: string;
65
+ slashCommandsLabel: string;
66
+ modelLabel: string;
67
+ close: string;
68
+ danger: string;
69
+ draftConflict: string;
70
+ useOtherDraft: string;
71
+ keepMine: string;
72
+ savingDraft: string;
73
+ formatBytes: (bytes: number) => string;
74
+ formatRelativeTime: (changedAt: string) => string;
75
+ };
76
+
77
+ export const defaultChatComposerMessages: ChatComposerMessages = {
78
+ messagePlaceholder: "Message the agent…",
79
+ pausedPlaceholder: "Message the agent — it will wait in the queue…",
80
+ inputLabel: "Message the agent",
81
+ // Shortcuts live in the send button's title; the footer stays quiet unless
82
+ // the host supplies its own hint.
83
+ keyboardHint: "",
84
+ slashCommandBlocked:
85
+ "That's a slash command — press Enter in the command list to run it, or edit the line to send a message.",
86
+ controlChangedError:
87
+ "This workstream was paused while you were sending. Nothing was sent, and your draft is still here.",
88
+ sendFailedError: "Sending failed — your draft is still here. Try again.",
89
+ dropFiles: "Drop files to attach",
90
+ attachFiles: "Attach files",
91
+ pauseAriaLabel: "Pause this workstream",
92
+ pauseTitle: "Pause this workstream; queued prompts and approvals are preserved",
93
+ stopAriaLabel: "Stop",
94
+ stopTitle: "Stop the response",
95
+ sendMessageAriaLabel: "Send message",
96
+ sendAndResumeAriaLabel: "Add message to queue",
97
+ sendTitle: "Queue message (Enter); steer with Cmd/Ctrl+Enter",
98
+ sendAndResumeTitle: "Add to queue (Enter); steer now with Cmd/Ctrl+Enter",
99
+ annotationNotesRequired: "Add a note to each quote before sending.",
100
+ workspacePaused: "Workspace paused",
101
+ pausedHere: "Paused here",
102
+ parentBlocker: "parent",
103
+ narrowerPause: "a narrower pause",
104
+ pausedBy: (displayName) => `Paused by ${displayName}`,
105
+ queuedAhead: (count) =>
106
+ `${count} waiting prompt${count === 1 ? "" : "s"}; your message joins the queue.`,
107
+ resumingAndSending: "Sending…",
108
+ nextMessageResumes: "Messages wait in the queue until you resume.",
109
+ resumeThisWorkstream: "Resume this workstream",
110
+ resumeShort: "Resume",
111
+ hidePauseDetails: "Hide pause details",
112
+ showPauseDetails: "Show pause details",
113
+ pauseReasonsLabel: "Reasons this workstream is paused",
114
+ pausedByLabel: "Paused by ",
115
+ alsoPausedByLabel: "Also paused by ",
116
+ resumeWorkspace: "Resume workspace",
117
+ resumeFromSession: "Resume from this session",
118
+ sessionCanRun: "This session will be able to run",
119
+ stillPausedBy: (displayName) => `Still paused by ${displayName}`,
120
+ restoredResourcesLabel: "Restored prompt resources",
121
+ restoredFile: (fileId) => `File ${fileId.slice(0, 8)}`,
122
+ removeRestoredResource: (index) => `Remove restored resource ${index + 1}`,
123
+ uploading: "Uploading",
124
+ uploadFailed: "Upload failed",
125
+ previewAttachment: (name) => `Preview ${name}`,
126
+ previewUnavailable: "Preview unavailable",
127
+ attachmentPreviewLabel: "Attachment preview",
128
+ downloadAttachment: (name) => `Download ${name}`,
129
+ closeAttachmentPreview: "Close",
130
+ retryAttachment: (name) => `Retry ${name}`,
131
+ retryUpload: "Retry upload",
132
+ removeAttachment: (name) => `Remove ${name}`,
133
+ confirmCommand: (name) => `Confirm /${name}`,
134
+ confirmDescription: (command) => `Run /${command.name}? ${command.description}`,
135
+ cancel: "Cancel",
136
+ runCommand: (name) => `Run /${name}`,
137
+ commands: "Commands",
138
+ slashCommandsLabel: "Slash commands",
139
+ modelLabel: "Model",
140
+ close: "Close",
141
+ danger: "danger",
142
+ draftConflict: "This draft changed in another tab. Your local draft is still here.",
143
+ useOtherDraft: "Use other draft",
144
+ keepMine: "Keep mine",
145
+ // Kept for embedder message overrides / back-compat. Routine autosave is
146
+ // silent — only draft conflicts surface under the composer.
147
+ savingDraft: "Saving draft…",
148
+ formatBytes,
149
+ formatRelativeTime,
150
+ };
@@ -19,6 +19,11 @@ const setupStatus: Record<ConnectAttempt["state"], string> = {
19
19
  uncertain: "Setup outcome needs checking",
20
20
  };
21
21
 
22
+ /** GitHub returned from a non-owner's install request: an organization owner must approve. */
23
+ export function isOwnerApprovalPending(attempt: Pick<ConnectAttempt, "error">): boolean {
24
+ return attempt.error?.code === "owner_approval_pending";
25
+ }
26
+
22
27
  function deviceVerificationUrl(action: ConnectAttempt["nextAction"]): string | null {
23
28
  if (action.type !== "wait" || !action.verificationUrl) return null;
24
29
  try {
@@ -91,6 +96,11 @@ function ScopedSetup({
91
96
  const action = attempt.nextAction;
92
97
  const verificationUrl = deviceVerificationUrl(action);
93
98
  const terminal = ["complete", "cancelled", "expired"].includes(attempt.state);
99
+ const awaitingOwner = isOwnerApprovalPending(attempt);
100
+ const offersGitHubRequest =
101
+ action.type === "select_account" &&
102
+ attempt.providerId.startsWith("github") &&
103
+ action.accounts.some((account) => account.id === "new");
94
104
  const submit = (event: FormEvent<HTMLFormElement>) => {
95
105
  event.preventDefault();
96
106
  const form = event.currentTarget;
@@ -148,7 +158,7 @@ function ScopedSetup({
148
158
  {attempt.ownership === "personal" ? "Personal connection" : "Workspace connection"}
149
159
  </p>
150
160
  <p role="status" className="og-connect-setup-status">
151
- {setupStatus[attempt.state]}
161
+ {awaitingOwner ? "Waiting for an organization owner" : setupStatus[attempt.state]}
152
162
  </p>
153
163
  <p className="og-connect-setup-description">
154
164
  {attempt.ownership === "personal"
@@ -157,7 +167,22 @@ function ScopedSetup({
157
167
  </p>
158
168
  </div>
159
169
  {attempt.account && <p>Account: {attempt.account.label}</p>}
160
- {(localError || view.error || attempt.error) && (
170
+ {awaitingOwner && (
171
+ <div className="og-connect-setup-pending">
172
+ <p>
173
+ Your request was sent. GitHub asked the owners of your organization to approve Opengeni.
174
+ </p>
175
+ <ol>
176
+ <li>An organization owner approves the request on GitHub.</li>
177
+ <li>
178
+ An owner then connects the organization here. If they don't use Opengeni yet, invite
179
+ them to this workspace first.
180
+ </li>
181
+ </ol>
182
+ <p>Nothing is connected until then. You can close this and keep working.</p>
183
+ </div>
184
+ )}
185
+ {(localError || view.error || (attempt.error && !awaitingOwner)) && (
161
186
  <p role="alert">
162
187
  {attempt.error?.code === "source_changed"
163
188
  ? "The integration source changed. Review the updated tools before adding them."
@@ -172,15 +197,17 @@ function ScopedSetup({
172
197
  ) ? (
173
198
  <legend>Connection details</legend>
174
199
  ) : null}
175
- {attempt.state === "connected_but_incomplete" && action.type === "none" && (
176
- <button
177
- type="button"
178
- className="og-connect-setup-primary"
179
- onClick={() => invoke(() => view.advance({ type: "retry" }, crypto.randomUUID()))}
180
- >
181
- Choose tools
182
- </button>
183
- )}
200
+ {attempt.state === "connected_but_incomplete" &&
201
+ action.type === "none" &&
202
+ !awaitingOwner && (
203
+ <button
204
+ type="button"
205
+ className="og-connect-setup-primary"
206
+ onClick={() => invoke(() => view.advance({ type: "retry" }, crypto.randomUUID()))}
207
+ >
208
+ Choose tools
209
+ </button>
210
+ )}
184
211
  {action.type === "credentials" &&
185
212
  action.fields.map((field, i) => (
186
213
  <label key={field.name}>
@@ -225,6 +252,13 @@ function ScopedSetup({
225
252
  </select>
226
253
  </label>
227
254
  )}
255
+ {offersGitHubRequest && (
256
+ <p className="og-connect-setup-hint">
257
+ Don't see your organization? Only its owners can connect it. Choose "Install on
258
+ another GitHub account" and pick the organization: GitHub lets you send its owners a
259
+ request to approve.
260
+ </p>
261
+ )}
228
262
  {action.type === "select_resources" &&
229
263
  action.resources.map((resource) => (
230
264
  <label key={resource.id}>
package/src/connect.ts CHANGED
@@ -15,7 +15,7 @@ export {
15
15
  type SkillDiscoveryItem,
16
16
  type SkillDiscoveryPage,
17
17
  } from "./skill-discovery";
18
- export { ConnectSetup, type ConnectSetupProps } from "./connect-setup";
18
+ export { ConnectSetup, isOwnerApprovalPending, type ConnectSetupProps } from "./connect-setup";
19
19
  export { ConnectChooser, type ConnectChooserProps } from "./connect-chooser";
20
20
  export { ConnectAccounts, type ConnectAccountsProps } from "./connect-accounts";
21
21
  export { ConnectPanel, type ConnectPanelProps } from "./connect-panel";
@@ -31,10 +31,22 @@ type Share = {
31
31
  inFlight: Promise<void> | null;
32
32
  trailing: Promise<void> | null;
33
33
  generation: number;
34
+ /** The last read was refused (401/403/404). Polling stays stopped until the
35
+ * read is disabled (a permission/workspace change) or explicitly refreshed. */
36
+ halted: boolean;
34
37
  };
35
38
 
36
39
  const shares = new Map<string, Share>();
37
40
 
41
+ /** A refusal that repeating the identical read cannot fix: the caller lacks
42
+ * `enrollments:read` (403), is signed out (401), or the workspace/feature is
43
+ * gone (404). Structural so host adapters with their own error types work. */
44
+ export function isMachinesAccessRefusal(cause: unknown): boolean {
45
+ if (!cause || typeof cause !== "object" || !("status" in cause)) return false;
46
+ const status = (cause as { status: unknown }).status;
47
+ return status === 401 || status === 403 || status === 404;
48
+ }
49
+
38
50
  function emit(share: Share): void {
39
51
  for (const subscriber of share.subscribers) {
40
52
  subscriber.onChange();
@@ -81,6 +93,7 @@ function cancelDispose(share: Share): void {
81
93
 
82
94
  function arm(share: Share): void {
83
95
  stopTimer(share);
96
+ if (share.halted) return;
84
97
  const { enabled, pollIntervalMs } = effective(share);
85
98
  if (!enabled || pollIntervalMs === undefined) return;
86
99
  share.timer = setTimeout(() => {
@@ -98,10 +111,15 @@ async function runFresh(share: Share): Promise<void> {
98
111
  try {
99
112
  const data = await load(abort.signal);
100
113
  if (ticket !== share.generation || abort.signal.aborted) return;
114
+ share.halted = false;
101
115
  share.snapshot = { data, loading: false, error: null };
102
116
  emit(share);
103
117
  } catch (cause) {
104
118
  if (ticket !== share.generation || abort.signal.aborted) return;
119
+ if (isMachinesAccessRefusal(cause)) {
120
+ share.halted = true;
121
+ stopTimer(share);
122
+ }
105
123
  share.snapshot = {
106
124
  data: share.snapshot.data,
107
125
  loading: false,
@@ -120,7 +138,7 @@ function run(share: Share, force = false): Promise<void> {
120
138
  let trailing!: Promise<void>;
121
139
  trailing = share.inFlight
122
140
  .then(async () => {
123
- if (!effective(share).enabled) return;
141
+ if (!effective(share).enabled || share.halted) return;
124
142
  await run(share);
125
143
  })
126
144
  .finally(() => {
@@ -145,12 +163,21 @@ function reconcile(share: Share): void {
145
163
  share.abort = null;
146
164
  share.inFlight = null;
147
165
  stopTimer(share);
166
+ if (share.halted) {
167
+ // Disabling is the host's permission/workspace signal. Forget the
168
+ // refusal so a later re-enable reads once immediately.
169
+ share.halted = false;
170
+ share.snapshot = { data: share.snapshot.data, loading: false, error: null };
171
+ emit(share);
172
+ return;
173
+ }
148
174
  if (share.snapshot.loading) {
149
175
  share.snapshot = { ...share.snapshot, loading: false };
150
176
  emit(share);
151
177
  }
152
178
  return;
153
179
  }
180
+ if (share.halted) return;
154
181
  if (share.snapshot.data !== null || share.snapshot.error) {
155
182
  arm(share);
156
183
  return;
@@ -175,6 +202,7 @@ function getOrCreate(shareKey: string): Share {
175
202
  inFlight: null,
176
203
  trailing: null,
177
204
  generation: 0,
205
+ halted: false,
178
206
  };
179
207
  shares.set(shareKey, created);
180
208
  return created;
@@ -183,7 +211,12 @@ function getOrCreate(shareKey: string): Share {
183
211
  export async function refreshSharedMachinesList(shareKey: string): Promise<void> {
184
212
  const share = shares.get(shareKey);
185
213
  if (!share) return;
214
+ // An explicit refresh (user action, post-mutation reconcile) may retry once;
215
+ // a repeated refusal halts polling again.
216
+ const wasHalted = share.halted;
217
+ share.halted = false;
186
218
  await run(share, true);
219
+ if (wasHalted) arm(share);
187
220
  }
188
221
 
189
222
  export function useSharedMachinesList(