@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
@@ -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(
@@ -39,6 +39,12 @@ export type ClientConfigFlags = {
39
39
  * it off; availability otherwise comes from the realtime model catalog.
40
40
  */
41
41
  realtimeVoice: boolean;
42
+ /**
43
+ * The host explicitly offers live voice to end users (a session proxy with
44
+ * `realtimeVoice: true`). Embedded conversations show the voice button only
45
+ * then or when their `realtimeVoice` prop is `true`.
46
+ */
47
+ realtimeVoiceOffered: boolean;
42
48
  };
43
49
 
44
50
  const INITIAL_FLAGS: ClientConfigFlags = {
@@ -52,6 +58,7 @@ const INITIAL_FLAGS: ClientConfigFlags = {
52
58
  archive: true,
53
59
  voiceInput: null,
54
60
  realtimeVoice: false,
61
+ realtimeVoiceOffered: false,
55
62
  };
56
63
 
57
64
  type ClientConfigFlagsInput = Awaited<ReturnType<ClientConfigFlagsSource["getClientConfig"]>>;
@@ -97,6 +104,7 @@ export function useClientConfigFlags(
97
104
  archive: config.archive !== false,
98
105
  voiceInput: config.voiceInput?.available === true ? config.voiceInput : null,
99
106
  realtimeVoice: config.realtimeVoice !== false,
107
+ realtimeVoiceOffered: config.realtimeVoice === true,
100
108
  });
101
109
  }
102
110
  },
@@ -1,5 +1,5 @@
1
1
  import { OpenGeniApiError, type SessionEvent, type SessionGoal } from "@opengeni/sdk";
2
- import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
3
3
  import { useEmbeddedGoal, type EmbeddedGoalClientOverride } from "../session-context";
4
4
  import { normalizeError } from "../lib/error-message";
5
5
  import {
@@ -65,7 +65,8 @@ export type UseGoalResult = {
65
65
  /**
66
66
  * The session's goal: state, the autonomy counters (`autoContinuations`,
67
67
  * `noProgressStreak`), and pause/resume control. A goal-less session yields
68
- * `goal: null` (the 404 is absorbed). Live-updates on goal, turn, session,
68
+ * `goal: null`: read through `findGoal` as a successful null when the client
69
+ * has it, otherwise from an absorbed 404. Live-updates on goal, turn, session,
69
70
  * control, and system-update events — pass `options.events` from
70
71
  * `useSessionEvents` to reuse its stream.
71
72
  */
@@ -84,10 +85,19 @@ export function useGoal(
84
85
  const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
85
86
  const generation = useRef(0);
86
87
  const targetKeyRef = useRef<string | null>(null);
87
- // True after the server answered 404. Only a goal event can create a goal,
88
- // so turn/session traffic must not refetch (and 404) while none exists.
88
+ // True after the server answered that no goal exists. Only a goal event can
89
+ // create a goal, so turn/session traffic must not refetch while none exists.
89
90
  const goalAbsentRef = useRef(false);
91
+ // Refresh events not yet covered by a read: "goal" when any was a goal.*
92
+ // event. Events that arrive while a read is in flight are decided when it
93
+ // settles, so the stream's opening burst never re-reads a goal-less session.
94
+ const pendingRefreshRef = useRef<"none" | "refresh" | "goal">("none");
95
+ const sharedEventsRef = useRef(sharedEvents);
96
+ useLayoutEffect(() => {
97
+ sharedEventsRef.current = sharedEvents;
98
+ }, [sharedEvents]);
90
99
  const loadAbort = useRef<AbortController | null>(null);
100
+ const scheduleRefreshRef = useRef<() => void>(() => undefined);
91
101
 
92
102
  const retireLoads = useCallback(() => {
93
103
  generation.current += 1;
@@ -100,15 +110,20 @@ export function useGoal(
100
110
  return;
101
111
  }
102
112
  const ticket = ++generation.current;
113
+ pendingRefreshRef.current = "none";
103
114
  loadAbort.current?.abort();
104
115
  const controller = new AbortController();
105
116
  loadAbort.current = controller;
106
117
  try {
107
- const fetched = await client.getGoal(workspaceId, sessionId, {
108
- signal: controller.signal,
109
- });
118
+ // findGoal reads a goal-less session as a successful null, so no failed
119
+ // request reaches the browser console. Custom clients without it keep
120
+ // getGoal, whose 404 is absorbed below (as it is from older servers).
121
+ const fetched =
122
+ typeof client.findGoal === "function"
123
+ ? await client.findGoal(workspaceId, sessionId, { signal: controller.signal })
124
+ : await client.getGoal(workspaceId, sessionId, { signal: controller.signal });
110
125
  if (ticket === generation.current) {
111
- goalAbsentRef.current = false;
126
+ goalAbsentRef.current = fetched === null;
112
127
  setGoal(fetched);
113
128
  setError(null);
114
129
  setLoading(false);
@@ -129,6 +144,26 @@ export function useGoal(
129
144
  } finally {
130
145
  if (loadAbort.current === controller) loadAbort.current = null;
131
146
  }
147
+ if (ticket !== generation.current || pendingRefreshRef.current === "none") {
148
+ return;
149
+ }
150
+ // Events arrived during this read. A shared feed's first batch reports only
151
+ // its latest match, so a goal.* event behind newer turn traffic is visible
152
+ // only in the log: re-read when its latest goal event is not a clear.
153
+ if (goalAbsentRef.current && pendingRefreshRef.current === "refresh") {
154
+ const events = sharedEventsRef.current ?? [];
155
+ let latestGoalEvent: SessionEvent | undefined;
156
+ for (let index = events.length - 1; index >= 0 && !latestGoalEvent; index -= 1) {
157
+ const event = events[index];
158
+ if (event && isGoalEvent(event)) latestGoalEvent = event;
159
+ }
160
+ if (!latestGoalEvent || latestGoalEvent.type === "goal.cleared") {
161
+ pendingRefreshRef.current = "none";
162
+ return;
163
+ }
164
+ pendingRefreshRef.current = "goal";
165
+ }
166
+ scheduleRefreshRef.current();
132
167
  }, [client, workspaceId, sessionId]);
133
168
 
134
169
  useEffect(() => {
@@ -136,6 +171,7 @@ export function useGoal(
136
171
  if (targetKeyRef.current !== targetKey) {
137
172
  targetKeyRef.current = targetKey;
138
173
  goalAbsentRef.current = false;
174
+ pendingRefreshRef.current = "none";
139
175
  setGoal(null);
140
176
  setError(null);
141
177
  }
@@ -186,13 +222,33 @@ export function useGoal(
186
222
  sharedFeed,
187
223
  ]);
188
224
 
189
- const scheduleRefresh = useDebouncedCallback(() => void load());
225
+ const scheduleRefresh = useDebouncedCallback(() => {
226
+ const pending = pendingRefreshRef.current;
227
+ // An in-flight read settles pending events itself.
228
+ if (pending === "none" || loadAbort.current !== null) return;
229
+ if (pending === "refresh" && goalAbsentRef.current) {
230
+ pendingRefreshRef.current = "none";
231
+ return;
232
+ }
233
+ void load();
234
+ });
235
+ useLayoutEffect(() => {
236
+ scheduleRefreshRef.current = scheduleRefresh;
237
+ }, [scheduleRefresh]);
190
238
  const isRefreshEvent = useCallback(
191
239
  (event: SessionEvent) =>
192
240
  goalAbsentRef.current ? isGoalEvent(event) : isGoalRefreshEvent(event),
193
241
  [],
194
242
  );
195
- useSessionEventTrigger(client, workspaceId, sessionId, isRefreshEvent, scheduleRefresh, {
243
+ const onRefreshEvent = useCallback(
244
+ (event: SessionEvent) => {
245
+ if (isGoalEvent(event)) pendingRefreshRef.current = "goal";
246
+ else if (pendingRefreshRef.current === "none") pendingRefreshRef.current = "refresh";
247
+ scheduleRefresh();
248
+ },
249
+ [scheduleRefresh],
250
+ );
251
+ useSessionEventTrigger(client, workspaceId, sessionId, isRefreshEvent, onRefreshEvent, {
196
252
  enabled,
197
253
  ...(sharedEvents !== undefined ? { events: sharedEvents } : {}),
198
254
  });
@@ -0,0 +1,147 @@
1
+ import type { HumanInputAnswer, HumanInputQuestion } from "@opengeni/sdk";
2
+
3
+ /* Pure drafting and validation rules for structured human-input requests,
4
+ shared by the web HumanInputForm and non-DOM renderers. */
5
+
6
+ export type HumanInputAnswerDraft = {
7
+ values: string[];
8
+ other: string;
9
+ otherSelected: boolean;
10
+ };
11
+
12
+ export type HumanInputFormMessages = {
13
+ title: string;
14
+ description: string;
15
+ submit: string;
16
+ skip: string;
17
+ submitting: string;
18
+ other: string;
19
+ deadlineLabel: string;
20
+ formatDeadline: (value: string) => string;
21
+ required: string;
22
+ otherRequired: string;
23
+ minSelections: (count: number) => string;
24
+ maxSelections: (count: number) => string;
25
+ optional: string;
26
+ /** Shown when the question list overflows the card and more content is below. */
27
+ moreBelow: string;
28
+ questionCount: (count: number) => string;
29
+ collapse: string;
30
+ expand: string;
31
+ selectionHint: (min: number | null | undefined, max: number | null | undefined) => string | null;
32
+ };
33
+
34
+ export const defaultHumanInputFormMessages: HumanInputFormMessages = {
35
+ title: "Input required",
36
+ /** Multi-question chrome has no default subtitle; hosts may still override. */
37
+ description: "",
38
+ submit: "Send answers",
39
+ skip: "Skip",
40
+ submitting: "Submitting…",
41
+ other: "Other",
42
+ deadlineLabel: "Expires",
43
+ formatDeadline,
44
+ required: "This question is required.",
45
+ otherRequired: "Enter a value for Other.",
46
+ minSelections: (count) => `Choose at least ${count} option${count === 1 ? "" : "s"}.`,
47
+ maxSelections: (count) => `Choose no more than ${count} option${count === 1 ? "" : "s"}.`,
48
+ optional: "Optional",
49
+ moreBelow: "More below",
50
+ questionCount: (count) => `${count} questions`,
51
+ collapse: "Collapse",
52
+ expand: "Expand",
53
+ selectionHint: (min, max) => {
54
+ if (min != null && max != null) return `Choose ${min}–${max}.`;
55
+ if (min != null) return `Choose at least ${min}.`;
56
+ if (max != null) return `Choose up to ${max}.`;
57
+ return null;
58
+ },
59
+ };
60
+
61
+ export function answersFromDrafts(
62
+ questions: HumanInputQuestion[],
63
+ drafts: Record<string, HumanInputAnswerDraft>,
64
+ messageOverrides: Partial<HumanInputFormMessages> = {},
65
+ ): { answers: HumanInputAnswer[]; errors: Record<string, string> } {
66
+ const messages = { ...defaultHumanInputFormMessages, ...messageOverrides };
67
+ const answers: HumanInputAnswer[] = [];
68
+ const errors: Record<string, string> = {};
69
+ for (const question of questions) {
70
+ const draft = drafts[question.id] ?? emptyDraft();
71
+ const values = question.kind === "text" ? draft.values.filter(Boolean) : draft.values;
72
+ const other = draft.otherSelected ? draft.other : "";
73
+ const hasOther = Boolean(other.trim());
74
+ const supplied = values.length + (hasOther ? 1 : 0);
75
+
76
+ // Other-selected-but-empty must win over generic "required" — otherwise the
77
+ // user sees the wrong diagnosis next to a clearly selected control.
78
+ if (question.kind !== "text" && draft.otherSelected && !hasOther) {
79
+ errors[question.id] = messages.otherRequired;
80
+ continue;
81
+ }
82
+
83
+ if (question.required && supplied === 0) {
84
+ errors[question.id] = messages.required;
85
+ continue;
86
+ }
87
+ if (question.kind !== "text") {
88
+ const min = question.validation?.minSelections;
89
+ const max = question.kind === "single_select" ? 1 : question.validation?.maxSelections;
90
+ if (min != null && supplied < min) {
91
+ errors[question.id] = messages.minSelections(min);
92
+ continue;
93
+ }
94
+ if (max != null && supplied > max) {
95
+ errors[question.id] = messages.maxSelections(max);
96
+ continue;
97
+ }
98
+ }
99
+ if (supplied > 0) {
100
+ answers.push({
101
+ questionId: question.id,
102
+ values,
103
+ ...(hasOther ? { other } : {}),
104
+ });
105
+ }
106
+ }
107
+ return { answers, errors };
108
+ }
109
+
110
+ export function initialDrafts(
111
+ questions: HumanInputQuestion[],
112
+ ): Record<string, HumanInputAnswerDraft> {
113
+ return Object.fromEntries(questions.map((question) => [question.id, emptyDraft()]));
114
+ }
115
+
116
+ export function emptyDraft(): HumanInputAnswerDraft {
117
+ return { values: [], other: "", otherSelected: false };
118
+ }
119
+
120
+ export function formatDeadline(value: string): string {
121
+ const date = new Date(value);
122
+ if (Number.isNaN(date.getTime())) return value;
123
+ const ms = date.getTime() - Date.now();
124
+ if (ms <= 0) return "deadline passed";
125
+ const minutes = Math.round(ms / 60_000);
126
+ if (minutes < 1) return "in under a minute";
127
+ if (minutes < 60) return `in ${minutes}m`;
128
+ const hours = Math.round(minutes / 60);
129
+ if (hours < 48) return `in ${hours}h`;
130
+ return date.toLocaleString();
131
+ }
132
+
133
+ /** The form heading: a single question leads with its own label/prompt. */
134
+ export function humanInputHeading(
135
+ questions: readonly HumanInputQuestion[],
136
+ messages: Pick<HumanInputFormMessages, "title" | "description">,
137
+ ): { title: string; description: string | null } {
138
+ const single = questions.length === 1 ? questions[0]! : null;
139
+ return {
140
+ title: single ? (single.label ?? single.prompt) : messages.title,
141
+ description: single
142
+ ? single.label
143
+ ? single.prompt
144
+ : (single.helpText ?? null)
145
+ : messages.description || null,
146
+ };
147
+ }