@opengeni/react 1.3.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/README.md +29 -1
  2. package/dist/accounts.js +2 -0
  3. package/dist/accounts.js.map +1 -1
  4. package/dist/artifacts-document.js +5 -4
  5. package/dist/artifacts-presentation.js +5 -4
  6. package/dist/artifacts-spreadsheet.js +4 -3
  7. package/dist/artifacts.js +14 -13
  8. package/dist/artifacts.js.map +1 -1
  9. package/dist/browser-viewer-WGJ43YGI.js +17 -0
  10. package/dist/chat-interactive-block-ZFHOKTMS.js +12 -0
  11. package/dist/{chunk-WEHMPOXK.js → chunk-2DDJ6YH6.js} +3 -1
  12. package/dist/{chunk-WEHMPOXK.js.map → chunk-2DDJ6YH6.js.map} +1 -1
  13. package/dist/{chunk-C5X5AJXK.js → chunk-2HVLQ26H.js} +5 -4
  14. package/dist/{chunk-C5X5AJXK.js.map → chunk-2HVLQ26H.js.map} +1 -1
  15. package/dist/{chunk-A4UJB2MM.js → chunk-32CGLXW2.js} +4 -3
  16. package/dist/{chunk-A4UJB2MM.js.map → chunk-32CGLXW2.js.map} +1 -1
  17. package/dist/{chunk-5CVXZA3Y.js → chunk-45NUOSXQ.js} +3 -2
  18. package/dist/{chunk-5CVXZA3Y.js.map → chunk-45NUOSXQ.js.map} +1 -1
  19. package/dist/{chunk-WUISZ3WU.js → chunk-53KJBGPZ.js} +5 -4
  20. package/dist/{chunk-WUISZ3WU.js.map → chunk-53KJBGPZ.js.map} +1 -1
  21. package/dist/{chunk-TMYGL6X5.js → chunk-55DXD22O.js} +73 -7
  22. package/dist/chunk-55DXD22O.js.map +1 -0
  23. package/dist/{chunk-JT4JW7BC.js → chunk-5I4VRWBB.js} +3 -1
  24. package/dist/{chunk-JT4JW7BC.js.map → chunk-5I4VRWBB.js.map} +1 -1
  25. package/dist/{chunk-UK4TINKT.js → chunk-B27E3V37.js} +3 -1
  26. package/dist/{chunk-UK4TINKT.js.map → chunk-B27E3V37.js.map} +1 -1
  27. package/dist/{chunk-J6DBS4T3.js → chunk-BNKV5ZLF.js} +5 -4
  28. package/dist/{chunk-J6DBS4T3.js.map → chunk-BNKV5ZLF.js.map} +1 -1
  29. package/dist/{chunk-YM2DE7BQ.js → chunk-C5BF4L26.js} +13 -12
  30. package/dist/{chunk-YM2DE7BQ.js.map → chunk-C5BF4L26.js.map} +1 -1
  31. package/dist/{chunk-YBXQDQFX.js → chunk-C7CMD56T.js} +6 -5
  32. package/dist/{chunk-YBXQDQFX.js.map → chunk-C7CMD56T.js.map} +1 -1
  33. package/dist/{chunk-UGLEYEMP.js → chunk-CM7LVUKC.js} +3 -1
  34. package/dist/{chunk-UGLEYEMP.js.map → chunk-CM7LVUKC.js.map} +1 -1
  35. package/dist/{chunk-PGSX7UCP.js → chunk-CREMJMVN.js} +1502 -833
  36. package/dist/chunk-CREMJMVN.js.map +1 -0
  37. package/dist/{chunk-JVYQRRC4.js → chunk-CVYCLFMH.js} +3 -1
  38. package/dist/{chunk-JVYQRRC4.js.map → chunk-CVYCLFMH.js.map} +1 -1
  39. package/dist/{chunk-Y2PRMYLC.js → chunk-G3U2A6GS.js} +3 -2
  40. package/dist/{chunk-Y2PRMYLC.js.map → chunk-G3U2A6GS.js.map} +1 -1
  41. package/dist/{chunk-WWEUO2PL.js → chunk-G6YRCIYJ.js} +202 -14
  42. package/dist/chunk-G6YRCIYJ.js.map +1 -0
  43. package/dist/{chunk-B3V6TG77.js → chunk-GC3PH6N7.js} +3 -1
  44. package/dist/{chunk-B3V6TG77.js.map → chunk-GC3PH6N7.js.map} +1 -1
  45. package/dist/{chunk-FZCP527Y.js → chunk-HXYOSSQO.js} +3 -2
  46. package/dist/{chunk-FZCP527Y.js.map → chunk-HXYOSSQO.js.map} +1 -1
  47. package/dist/{chunk-BRM6FVZA.js → chunk-IEE7WCLN.js} +4 -3
  48. package/dist/{chunk-BRM6FVZA.js.map → chunk-IEE7WCLN.js.map} +1 -1
  49. package/dist/{chunk-DYWBUX5Q.js → chunk-IMSUVYQB.js} +6 -5
  50. package/dist/{chunk-DYWBUX5Q.js.map → chunk-IMSUVYQB.js.map} +1 -1
  51. package/dist/{chunk-CY7WK5FD.js → chunk-IUHHPMAX.js} +203 -46
  52. package/dist/chunk-IUHHPMAX.js.map +1 -0
  53. package/dist/{chunk-SGKD2OCN.js → chunk-IYQCWGU5.js} +6 -5
  54. package/dist/{chunk-SGKD2OCN.js.map → chunk-IYQCWGU5.js.map} +1 -1
  55. package/dist/{chunk-J2QWVSQY.js → chunk-JH7V627O.js} +8 -7
  56. package/dist/{chunk-J2QWVSQY.js.map → chunk-JH7V627O.js.map} +1 -1
  57. package/dist/{chunk-EESD3PY3.js → chunk-JHTIMBOL.js} +6 -5
  58. package/dist/{chunk-EESD3PY3.js.map → chunk-JHTIMBOL.js.map} +1 -1
  59. package/dist/{chunk-LNIRAOA3.js → chunk-L2J3PU7A.js} +7 -6
  60. package/dist/{chunk-LNIRAOA3.js.map → chunk-L2J3PU7A.js.map} +1 -1
  61. package/dist/{chunk-ZYRGEKWS.js → chunk-LJUMYUBO.js} +3 -1
  62. package/dist/{chunk-ZYRGEKWS.js.map → chunk-LJUMYUBO.js.map} +1 -1
  63. package/dist/{chunk-4RVYOY4O.js → chunk-LSW2OJVV.js} +4 -3
  64. package/dist/{chunk-4RVYOY4O.js.map → chunk-LSW2OJVV.js.map} +1 -1
  65. package/dist/{chunk-UTRTG6LH.js → chunk-LVS3MH5T.js} +3 -1
  66. package/dist/{chunk-UTRTG6LH.js.map → chunk-LVS3MH5T.js.map} +1 -1
  67. package/dist/chunk-M357MHH3.js +2 -0
  68. package/dist/{chunk-GYVRFZDY.js → chunk-MDIXFKA3.js} +9 -8
  69. package/dist/{chunk-GYVRFZDY.js.map → chunk-MDIXFKA3.js.map} +1 -1
  70. package/dist/{chunk-WRHIZYZF.js → chunk-MEQM4LI3.js} +6 -5
  71. package/dist/{chunk-WRHIZYZF.js.map → chunk-MEQM4LI3.js.map} +1 -1
  72. package/dist/chunk-NX4ADH73.js +14 -0
  73. package/dist/chunk-NX4ADH73.js.map +1 -0
  74. package/dist/{chunk-XCHWDJ22.js → chunk-OGTTUYNT.js} +3 -1
  75. package/dist/{chunk-XCHWDJ22.js.map → chunk-OGTTUYNT.js.map} +1 -1
  76. package/dist/{chunk-GIMRELBW.js → chunk-OYQQJL3J.js} +3 -1
  77. package/dist/{chunk-GIMRELBW.js.map → chunk-OYQQJL3J.js.map} +1 -1
  78. package/dist/{chunk-RSI7XYXL.js → chunk-PBCZL4JP.js} +6 -5
  79. package/dist/{chunk-RSI7XYXL.js.map → chunk-PBCZL4JP.js.map} +1 -1
  80. package/dist/{chunk-I7IWRQLN.js → chunk-PHBFJCEU.js} +17 -11
  81. package/dist/chunk-PHBFJCEU.js.map +1 -0
  82. package/dist/{chunk-3CTRFMAK.js → chunk-PKCAYHDC.js} +5 -4
  83. package/dist/{chunk-3CTRFMAK.js.map → chunk-PKCAYHDC.js.map} +1 -1
  84. package/dist/{chunk-K3HQAKQX.js → chunk-PMV53IMM.js} +3 -1
  85. package/dist/{chunk-K3HQAKQX.js.map → chunk-PMV53IMM.js.map} +1 -1
  86. package/dist/{chunk-YQEAA6LT.js → chunk-QHUE7E4N.js} +3 -2
  87. package/dist/{chunk-YQEAA6LT.js.map → chunk-QHUE7E4N.js.map} +1 -1
  88. package/dist/{chunk-RC6BZ44Q.js → chunk-QMMCJGFI.js} +4 -3
  89. package/dist/{chunk-RC6BZ44Q.js.map → chunk-QMMCJGFI.js.map} +1 -1
  90. package/dist/{chunk-YV4L3ILF.js → chunk-QV3DJVRZ.js} +3 -1
  91. package/dist/{chunk-YV4L3ILF.js.map → chunk-QV3DJVRZ.js.map} +1 -1
  92. package/dist/{chunk-447JLCBU.js → chunk-S2QH6EZ5.js} +3 -1
  93. package/dist/{chunk-447JLCBU.js.map → chunk-S2QH6EZ5.js.map} +1 -1
  94. package/dist/{chunk-PNY3JKBJ.js → chunk-SFH43JT2.js} +5 -4
  95. package/dist/{chunk-PNY3JKBJ.js.map → chunk-SFH43JT2.js.map} +1 -1
  96. package/dist/{chunk-ERGYJIBB.js → chunk-T46QZH3E.js} +4 -2
  97. package/dist/{chunk-ERGYJIBB.js.map → chunk-T46QZH3E.js.map} +1 -1
  98. package/dist/{chunk-5J4FOW6Q.js → chunk-TNEZNMSN.js} +3 -1
  99. package/dist/{chunk-5J4FOW6Q.js.map → chunk-TNEZNMSN.js.map} +1 -1
  100. package/dist/{chunk-22KP6LR5.js → chunk-TXS65BB2.js} +3 -1
  101. package/dist/{chunk-22KP6LR5.js.map → chunk-TXS65BB2.js.map} +1 -1
  102. package/dist/{chunk-742CZ3L3.js → chunk-UDPH5SLV.js} +19 -202
  103. package/dist/chunk-UDPH5SLV.js.map +1 -0
  104. package/dist/{chunk-HDJWDK7W.js → chunk-VCSERYXH.js} +10 -9
  105. package/dist/{chunk-HDJWDK7W.js.map → chunk-VCSERYXH.js.map} +1 -1
  106. package/dist/{chunk-VZTQ73XT.js → chunk-XEA5BCMA.js} +3 -1
  107. package/dist/{chunk-VZTQ73XT.js.map → chunk-XEA5BCMA.js.map} +1 -1
  108. package/dist/{chunk-5F4WR7X7.js → chunk-XS67YG5R.js} +3 -1
  109. package/dist/{chunk-5F4WR7X7.js.map → chunk-XS67YG5R.js.map} +1 -1
  110. package/dist/{chunk-TKOTKRWY.js → chunk-XTD7TZHW.js} +3 -1
  111. package/dist/{chunk-TKOTKRWY.js.map → chunk-XTD7TZHW.js.map} +1 -1
  112. package/dist/{chunk-A2PI6UMY.js → chunk-YSRBFJMQ.js} +3 -1
  113. package/dist/{chunk-A2PI6UMY.js.map → chunk-YSRBFJMQ.js.map} +1 -1
  114. package/dist/chunk-YVFRBXST.js +1420 -0
  115. package/dist/chunk-YVFRBXST.js.map +1 -0
  116. package/dist/{chunk-SCOTPE2I.js → chunk-YYLBXXTE.js} +3 -1
  117. package/dist/{chunk-SCOTPE2I.js.map → chunk-YYLBXXTE.js.map} +1 -1
  118. package/dist/{chunk-RHREZ7BO.js → chunk-ZUWF4NF7.js} +3 -1
  119. package/dist/{chunk-RHREZ7BO.js.map → chunk-ZUWF4NF7.js.map} +1 -1
  120. package/dist/clipboard.js +3 -2
  121. package/dist/components/composer-transcription-control.d.ts +7 -0
  122. package/dist/components/open-geni-chat.d.ts +6 -2
  123. package/dist/components/session-conversation.d.ts +31 -19
  124. package/dist/components/session-list.d.ts +4 -1
  125. package/dist/components/tool-action-review.d.ts +29 -2
  126. package/dist/components/tool-review-history.d.ts +8 -4
  127. package/dist/composer-voice-events.d.ts +8 -0
  128. package/dist/composer.js +15 -13
  129. package/dist/computer-viewer-46UIN7F3.js +20 -0
  130. package/dist/connect.js +15 -12
  131. package/dist/connect.js.map +1 -1
  132. package/dist/desktop.js +6 -5
  133. package/dist/desktop.js.map +1 -1
  134. package/dist/diffs.js +2 -1
  135. package/dist/diffs.js.map +1 -1
  136. package/dist/editor.js +4 -3
  137. package/dist/editor.js.map +1 -1
  138. package/dist/embedded-voice-NIL4RDX3.js +69 -0
  139. package/dist/embedded-voice-NIL4RDX3.js.map +1 -0
  140. package/dist/{fleet-decision-row-YZ3ZKWMM.js → fleet-decision-row-SI63O5V4.js} +3 -1
  141. package/dist/{fleet-decision-row-YZ3ZKWMM.js.map → fleet-decision-row-SI63O5V4.js.map} +1 -1
  142. package/dist/hooks/use-client-config-flags.d.ts +43 -0
  143. package/dist/hooks/use-realtime-voice-models.d.ts +9 -0
  144. package/dist/hooks/use-voice-input.d.ts +8 -1
  145. package/dist/index.d.ts +3 -3
  146. package/dist/index.js +60 -48
  147. package/dist/index.js.map +1 -1
  148. package/dist/interaction.js +14 -13
  149. package/dist/interaction.js.map +1 -1
  150. package/dist/{latest-question-YTOBHCWR.js → latest-question-2POFPYNB.js} +5 -4
  151. package/dist/{latest-question-YTOBHCWR.js.map → latest-question-2POFPYNB.js.map} +1 -1
  152. package/dist/machines.js +8 -7
  153. package/dist/{markdown-table-layout-QXSLK4BY.js → markdown-table-layout-DQ4DRNVT.js} +3 -1
  154. package/dist/{markdown-table-layout-QXSLK4BY.js.map → markdown-table-layout-DQ4DRNVT.js.map} +1 -1
  155. package/dist/model-policy-picker-menu-WRNSRFQC.js +15 -0
  156. package/dist/model-policy.js +2 -1
  157. package/dist/{noto-sans-arabic-loader-IA2T4X2A.js → noto-sans-arabic-loader-XSH2KYWJ.js} +3 -1
  158. package/dist/{noto-sans-arabic-loader-IA2T4X2A.js.map → noto-sans-arabic-loader-XSH2KYWJ.js.map} +1 -1
  159. package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js → noto-sans-jp-loader.generated-MR5L6V7K.js} +3 -1
  160. package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js.map → noto-sans-jp-loader.generated-MR5L6V7K.js.map} +1 -1
  161. package/dist/{platform-activity-row-627BFZ46.js → platform-activity-row-PJJVATTZ.js} +3 -1
  162. package/dist/{platform-activity-row-627BFZ46.js.map → platform-activity-row-PJJVATTZ.js.map} +1 -1
  163. package/dist/{queue-surface-implementation-ZHFI5JBP.js → queue-surface-implementation-67FS7FUI.js} +10 -9
  164. package/dist/{queue-surface-implementation-ZHFI5JBP.js.map → queue-surface-implementation-67FS7FUI.js.map} +1 -1
  165. package/dist/realtime/embedded-voice.d.ts +20 -0
  166. package/dist/realtime/realtime-control.d.ts +2 -0
  167. package/dist/realtime.js +22 -1332
  168. package/dist/realtime.js.map +1 -1
  169. package/dist/sandbox-files-34CJJNOY.js +21 -0
  170. package/dist/session-capability-policy.d.ts +20 -1
  171. package/dist/session-ui.js +32 -30
  172. package/dist/session-ui.js.map +1 -1
  173. package/dist/session.js +12 -11
  174. package/dist/sites.js +2 -1
  175. package/dist/terminal.js +6 -5
  176. package/dist/terminal.js.map +1 -1
  177. package/dist/timeline/index.d.ts +2 -0
  178. package/dist/timeline/retained-loaders.d.ts +27 -0
  179. package/dist/{timeline-annotation-markers-GNDHTGBD.js → timeline-annotation-markers-U3G66KUD.js} +5 -4
  180. package/dist/{timeline-annotation-markers-GNDHTGBD.js.map → timeline-annotation-markers-U3G66KUD.js.map} +1 -1
  181. package/dist/{timeline-annotation-selection-DMLMM2TX.js → timeline-annotation-selection-BI7QTPTZ.js} +4 -3
  182. package/dist/{timeline-annotation-selection-DMLMM2TX.js.map → timeline-annotation-selection-BI7QTPTZ.js.map} +1 -1
  183. package/dist/{timeline-annotations-dialog-ELRDBHTS.js → timeline-annotations-dialog-FOMHSDTE.js} +7 -6
  184. package/dist/{timeline-annotations-dialog-ELRDBHTS.js.map → timeline-annotations-dialog-FOMHSDTE.js.map} +1 -1
  185. package/dist/usage.js +6 -5
  186. package/dist/usage.js.map +1 -1
  187. package/dist/voice-recording-owner.d.ts +7 -0
  188. package/package.json +3 -3
  189. package/src/components/approval-surface.tsx +190 -79
  190. package/src/components/artifacts/session-artifact-viewer.tsx +4 -2
  191. package/src/components/composer-transcription-control.tsx +121 -25
  192. package/src/components/composer.tsx +1 -0
  193. package/src/components/markdown.tsx +7 -2
  194. package/src/components/message-timeline.tsx +10 -1
  195. package/src/components/open-geni-chat.tsx +53 -13
  196. package/src/components/session-conversation.tsx +273 -63
  197. package/src/components/session-list.tsx +9 -2
  198. package/src/components/session-proxy-scope.tsx +26 -5
  199. package/src/components/tool-action-review.tsx +502 -239
  200. package/src/components/tool-review-history.tsx +18 -43
  201. package/src/composer-voice-events.ts +12 -0
  202. package/src/connection-discovery.tsx +4 -1
  203. package/src/hooks/use-client-config-flags.ts +110 -0
  204. package/src/hooks/use-goal.ts +12 -1
  205. package/src/hooks/use-realtime-voice-models.ts +67 -0
  206. package/src/hooks/use-session-events.ts +19 -16
  207. package/src/hooks/use-transcription.ts +6 -0
  208. package/src/hooks/use-voice-input.ts +115 -18
  209. package/src/index.ts +9 -0
  210. package/src/realtime/embedded-voice.tsx +81 -0
  211. package/src/realtime/realtime-control.tsx +109 -17
  212. package/src/session-capability-policy.ts +119 -6
  213. package/src/timeline/index.ts +10 -0
  214. package/src/timeline/retained-loaders.ts +86 -0
  215. package/src/timeline/tool-display-name.ts +5 -1
  216. package/src/timeline/tool-renderers.tsx +143 -10
  217. package/src/voice-recording-owner.ts +22 -0
  218. package/styles/compiled.css +135 -37
  219. package/styles/index.css +49 -0
  220. package/styles/responsive.css +61 -4
  221. package/styles/session-capability.css +1 -1
  222. package/styles/tokens.css +1 -1
  223. package/dist/browser-viewer-SHYKQRNX.js +0 -16
  224. package/dist/chat-interactive-block-UGBTNB3L.js +0 -11
  225. package/dist/chunk-742CZ3L3.js.map +0 -1
  226. package/dist/chunk-CY7WK5FD.js.map +0 -1
  227. package/dist/chunk-I7IWRQLN.js.map +0 -1
  228. package/dist/chunk-IWIDV7SB.js +0 -1
  229. package/dist/chunk-PGSX7UCP.js.map +0 -1
  230. package/dist/chunk-TMYGL6X5.js.map +0 -1
  231. package/dist/chunk-WWEUO2PL.js.map +0 -1
  232. package/dist/computer-viewer-6SRGWRUX.js +0 -19
  233. package/dist/model-policy-picker-menu-DXHUL73H.js +0 -14
  234. package/dist/sandbox-files-GORZOUUJ.js +0 -20
  235. /package/dist/{browser-viewer-SHYKQRNX.js.map → browser-viewer-WGJ43YGI.js.map} +0 -0
  236. /package/dist/{chat-interactive-block-UGBTNB3L.js.map → chat-interactive-block-ZFHOKTMS.js.map} +0 -0
  237. /package/dist/{chunk-IWIDV7SB.js.map → chunk-M357MHH3.js.map} +0 -0
  238. /package/dist/{computer-viewer-6SRGWRUX.js.map → computer-viewer-46UIN7F3.js.map} +0 -0
  239. /package/dist/{model-policy-picker-menu-DXHUL73H.js.map → model-policy-picker-menu-WRNSRFQC.js.map} +0 -0
  240. /package/dist/{sandbox-files-GORZOUUJ.js.map → sandbox-files-34CJJNOY.js.map} +0 -0
@@ -1,7 +1,6 @@
1
1
  import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from "react";
2
2
  import type { SessionEvent, ToolActionReview } from "@opengeni/sdk";
3
3
  import { approvalsFromRequiresAction } from "../approvals";
4
- import { ToolActionReviewCard } from "./tool-action-review";
5
4
 
6
5
  type HistoryContext = {
7
6
  approvalIds: ReadonlySet<string>;
@@ -55,59 +54,35 @@ export function useHasToolReview(approvalId: string | null): boolean {
55
54
  return Boolean(approvalId && history?.approvalIds.has(approvalId));
56
55
  }
57
56
 
58
- /** Recorded calls have details, never another decision button. */
59
- export function ToolReviewHistoryReceipt({ approvalId }: { approvalId: string }) {
57
+ /**
58
+ * The saved review for one recorded call, or null while it loads or when it is
59
+ * unavailable (callers then keep their ordinary tool row).
60
+ */
61
+ export function useRecordedToolReview(approvalId: string | null): {
62
+ review: ToolActionReview | null;
63
+ onViewDetails: ((path: string) => void) | undefined;
64
+ } {
60
65
  const history = useContext(History);
61
66
  const [review, setReview] = useState<ToolActionReview | null>(null);
62
- const [failed, setFailed] = useState(false);
63
- const [retry, setRetry] = useState(0);
64
67
  const load = history?.load;
65
68
  const revision = history?.revision;
66
69
  useEffect(() => {
67
- if (!load) return;
70
+ if (!load || !approvalId) return;
68
71
  let active = true;
69
- setFailed(false);
70
72
  void load(approvalId).then(
71
73
  (value) => {
72
- if (active) {
73
- if (value.id === approvalId) setReview(value);
74
- else setFailed(true);
75
- }
76
- },
77
- () => {
78
- if (active) setFailed(true);
74
+ if (active && value.id === approvalId) setReview(value);
79
75
  },
76
+ () => undefined,
80
77
  );
81
78
  return () => {
82
79
  active = false;
83
80
  };
84
- }, [approvalId, load, revision, retry]);
85
- if (!history) return null;
86
- if (failed)
87
- return (
88
- <p className="text-og-sm text-og-fg-muted" role="alert">
89
- Review details unavailable.{" "}
90
- <button
91
- type="button"
92
- className="min-h-11 underline"
93
- onClick={() => setRetry((value) => value + 1)}
94
- >
95
- Try again
96
- </button>
97
- </p>
98
- );
99
- if (!review)
100
- return (
101
- <p role="status" className="text-og-sm text-og-fg-muted">
102
- Loading reviewed action…
103
- </p>
104
- );
105
- return (
106
- <div data-approval-id={approvalId} data-review-origin="history">
107
- <ToolActionReviewCard
108
- review={{ ...review, availableActions: [] }}
109
- onViewDetails={(path) => history.onViewDetails(review, path)}
110
- />
111
- </div>
112
- );
81
+ }, [approvalId, load, revision]);
82
+ const onViewDetails = history?.onViewDetails;
83
+ return {
84
+ review: review && review.id === approvalId ? review : null,
85
+ onViewDetails:
86
+ review && onViewDetails ? (path: string) => onViewDetails(review, path) : undefined,
87
+ };
113
88
  }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Internal DOM signal between the composer's two voice controls: dictation
3
+ * starting supersedes a stale live-voice start failure in the same composer.
4
+ * Not part of the public package surface.
5
+ */
6
+ export const COMPOSER_VOICE_INPUT_START_EVENT = "opengeni:composer-voice-input-start";
7
+
8
+ /** Closest composer root shared by both voice controls (falls back to the document). */
9
+ export function composerVoiceEventScope(element: Element | null): EventTarget | null {
10
+ if (!element) return null;
11
+ return element.closest(".og-composer") ?? element.ownerDocument;
12
+ }
@@ -92,7 +92,10 @@ function ScopedDiscovery({
92
92
  (item) =>
93
93
  item.kind === "mcp" &&
94
94
  item.authKind === "oauth2" &&
95
- Boolean(item.mcpUrl ?? item.endpointUrl),
95
+ Boolean(item.mcpUrl ?? item.endpointUrl) &&
96
+ // The provider refuses self-registration and this deployment
97
+ // has no operator client for it: Connect cannot succeed.
98
+ (item.enabled || item.runtime?.operatorOAuthClient?.configured !== false),
96
99
  ),
97
100
  );
98
101
  }
@@ -0,0 +1,110 @@
1
+ import type { ClientVoiceInputConfig, ReasoningEffort } from "@opengeni/sdk";
2
+ import { useEffect, useState } from "react";
3
+
4
+ /** The client-config fields the stock conversation and chat read. */
5
+ export type ClientConfigFlagsSource = {
6
+ getClientConfig: () => Promise<{
7
+ fileUploads?: { enabled?: boolean };
8
+ modelSelection?: boolean | undefined;
9
+ sandboxFiles?: boolean | undefined;
10
+ defaultReasoningEffort?: ReasoningEffort | undefined;
11
+ artifacts?: unknown;
12
+ sessionCreation?: boolean | undefined;
13
+ archive?: boolean | undefined;
14
+ voiceInput?: ClientVoiceInputConfig | undefined;
15
+ realtimeVoice?: boolean | undefined;
16
+ }>;
17
+ };
18
+
19
+ export type ClientConfigFlags = {
20
+ /** Whether the config has loaded. Proxy capability flags stay `true` until it says otherwise. */
21
+ loaded: boolean;
22
+ uploads: boolean;
23
+ modelSelection: boolean;
24
+ sandboxFiles: boolean;
25
+ defaultReasoningEffort: ReasoningEffort | null;
26
+ /**
27
+ * Inline Site previews and the artifact viewer. `false` only when a session
28
+ * proxy says it does not serve them; native deployments omit the flag.
29
+ */
30
+ artifacts: boolean;
31
+ /** Browser chat creation. `false` only when a session proxy has no `createSession` hook. */
32
+ sessionCreation: boolean;
33
+ /** Chat archive/restore. `false` only when a session proxy disables it. */
34
+ archive: boolean;
35
+ /** Native voice input when the deployment can transcribe, else null. */
36
+ voiceInput: ClientVoiceInputConfig | null;
37
+ /**
38
+ * Live voice. `false` until the config loads and when a session proxy turns
39
+ * it off; availability otherwise comes from the realtime model catalog.
40
+ */
41
+ realtimeVoice: boolean;
42
+ };
43
+
44
+ const INITIAL_FLAGS: ClientConfigFlags = {
45
+ loaded: false,
46
+ uploads: false,
47
+ modelSelection: false,
48
+ sandboxFiles: false,
49
+ defaultReasoningEffort: null,
50
+ artifacts: true,
51
+ sessionCreation: true,
52
+ archive: true,
53
+ voiceInput: null,
54
+ realtimeVoice: false,
55
+ };
56
+
57
+ type ClientConfigFlagsInput = Awaited<ReturnType<ClientConfigFlagsSource["getClientConfig"]>>;
58
+
59
+ // One read per client: the chat, its list, the new-chat composer and the
60
+ // conversation all ask, and behind a session proxy each read is a round trip.
61
+ const configReads = new WeakMap<object, Promise<ClientConfigFlagsInput>>();
62
+
63
+ function readClientConfig(client: ClientConfigFlagsSource): Promise<ClientConfigFlagsInput> {
64
+ let read = configReads.get(client);
65
+ if (!read) {
66
+ read = client.getClientConfig();
67
+ configReads.set(client, read);
68
+ // A failed read is retried by the next mount rather than cached.
69
+ read.catch(() => {
70
+ if (configReads.get(client) === read) configReads.delete(client);
71
+ });
72
+ }
73
+ return read;
74
+ }
75
+
76
+ /** Deployment and session-proxy capabilities from the client config. */
77
+ export function useClientConfigFlags(
78
+ client: Partial<ClientConfigFlagsSource> | ClientConfigFlagsSource,
79
+ ): ClientConfigFlags {
80
+ const [flags, setFlags] = useState<ClientConfigFlags>(INITIAL_FLAGS);
81
+ useEffect(() => {
82
+ // Custom clients without a config read keep the defaults.
83
+ if (typeof client.getClientConfig !== "function") return;
84
+ let live = true;
85
+ readClientConfig(client as ClientConfigFlagsSource).then(
86
+ (config) => {
87
+ if (live) {
88
+ setFlags({
89
+ loaded: true,
90
+ uploads: config.fileUploads?.enabled === true,
91
+ // Only an explicit offer shows end users the picker.
92
+ modelSelection: config.modelSelection === true,
93
+ sandboxFiles: config.sandboxFiles !== false,
94
+ defaultReasoningEffort: config.defaultReasoningEffort ?? null,
95
+ artifacts: config.artifacts !== false,
96
+ sessionCreation: config.sessionCreation !== false,
97
+ archive: config.archive !== false,
98
+ voiceInput: config.voiceInput?.available === true ? config.voiceInput : null,
99
+ realtimeVoice: config.realtimeVoice !== false,
100
+ });
101
+ }
102
+ },
103
+ () => undefined,
104
+ );
105
+ return () => {
106
+ live = false;
107
+ };
108
+ }, [client]);
109
+ return flags;
110
+ }
@@ -84,6 +84,9 @@ export function useGoal(
84
84
  const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
85
85
  const generation = useRef(0);
86
86
  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.
89
+ const goalAbsentRef = useRef(false);
87
90
  const loadAbort = useRef<AbortController | null>(null);
88
91
 
89
92
  const retireLoads = useCallback(() => {
@@ -105,6 +108,7 @@ export function useGoal(
105
108
  signal: controller.signal,
106
109
  });
107
110
  if (ticket === generation.current) {
111
+ goalAbsentRef.current = false;
108
112
  setGoal(fetched);
109
113
  setError(null);
110
114
  setLoading(false);
@@ -115,6 +119,7 @@ export function useGoal(
115
119
  }
116
120
  if (cause instanceof OpenGeniApiError && cause.status === 404) {
117
121
  // No goal is a normal state, not an error.
122
+ goalAbsentRef.current = true;
118
123
  setGoal(null);
119
124
  setError(null);
120
125
  } else {
@@ -130,6 +135,7 @@ export function useGoal(
130
135
  const targetKey = `${workspaceId} ${sessionId ?? ""}`;
131
136
  if (targetKeyRef.current !== targetKey) {
132
137
  targetKeyRef.current = targetKey;
138
+ goalAbsentRef.current = false;
133
139
  setGoal(null);
134
140
  setError(null);
135
141
  }
@@ -181,7 +187,12 @@ export function useGoal(
181
187
  ]);
182
188
 
183
189
  const scheduleRefresh = useDebouncedCallback(() => void load());
184
- useSessionEventTrigger(client, workspaceId, sessionId, isGoalRefreshEvent, scheduleRefresh, {
190
+ const isRefreshEvent = useCallback(
191
+ (event: SessionEvent) =>
192
+ goalAbsentRef.current ? isGoalEvent(event) : isGoalRefreshEvent(event),
193
+ [],
194
+ );
195
+ useSessionEventTrigger(client, workspaceId, sessionId, isRefreshEvent, scheduleRefresh, {
185
196
  enabled,
186
197
  ...(sharedEvents !== undefined ? { events: sharedEvents } : {}),
187
198
  });
@@ -0,0 +1,67 @@
1
+ import type { WorkspaceRealtimeModelCatalogResponse } from "@opengeni/sdk";
2
+ import { useEffect, useState } from "react";
3
+
4
+ type CatalogClient = {
5
+ getWorkspaceRealtimeModelCatalog?: (
6
+ workspaceId: string,
7
+ options?: { signal?: AbortSignal | undefined },
8
+ ) => Promise<WorkspaceRealtimeModelCatalogResponse>;
9
+ };
10
+
11
+ type AvailableVoiceModels = WorkspaceRealtimeModelCatalogResponse["models"];
12
+
13
+ const CACHE_TTL_MS = 60_000;
14
+ const cache = new WeakMap<
15
+ object,
16
+ Map<string, { models: Promise<AvailableVoiceModels>; expiresAt: number }>
17
+ >();
18
+
19
+ function load(client: CatalogClient, workspaceId: string): Promise<AvailableVoiceModels> {
20
+ let byWorkspace = cache.get(client);
21
+ if (!byWorkspace) {
22
+ byWorkspace = new Map();
23
+ cache.set(client, byWorkspace);
24
+ }
25
+ const hit = byWorkspace.get(workspaceId);
26
+ if (hit && hit.expiresAt > Date.now()) return hit.models;
27
+ // A throwing structural client (one without voice) just means no voice.
28
+ const models = Promise.resolve()
29
+ .then(() => client.getWorkspaceRealtimeModelCatalog!(workspaceId))
30
+ .then((catalog) => catalog.models.filter((model) => model.available));
31
+ const entry = { models, expiresAt: Date.now() + CACHE_TTL_MS };
32
+ byWorkspace.set(workspaceId, entry);
33
+ models.catch(() => {
34
+ if (byWorkspace.get(workspaceId) === entry) byWorkspace.delete(workspaceId);
35
+ });
36
+ return models;
37
+ }
38
+
39
+ /**
40
+ * Voice models this user can start right now in the workspace, or an empty
41
+ * list while unknown, disabled, unsupported (older API or proxy), or failed.
42
+ * Availability is advisory; Opengeni still admits and meters every call.
43
+ */
44
+ export function useRealtimeVoiceModels(
45
+ client: unknown,
46
+ workspaceId: string,
47
+ enabled: boolean,
48
+ ): AvailableVoiceModels {
49
+ const [models, setModels] = useState<AvailableVoiceModels>([]);
50
+ useEffect(() => {
51
+ setModels([]);
52
+ const catalogClient = client as CatalogClient;
53
+ if (!enabled || !workspaceId) return;
54
+ if (typeof catalogClient.getWorkspaceRealtimeModelCatalog !== "function") return;
55
+ let live = true;
56
+ load(catalogClient, workspaceId).then(
57
+ (available) => {
58
+ if (live) setModels(available);
59
+ },
60
+ () => undefined,
61
+ );
62
+ return () => {
63
+ live = false;
64
+ };
65
+ }, [client, workspaceId, enabled]);
66
+ return models;
67
+ }
@@ -225,8 +225,8 @@ export function useSessionEvents(
225
225
  // new stream identity cannot expose the previous session's event log.
226
226
  const [stateStreamKey, setStateStreamKey] = useState(streamKey);
227
227
 
228
- // Reopening SSE after a prepend must not cancel the next history page.
229
- // Navigation belongs to the session/client lifetime, not the transport.
228
+ // Navigation belongs to the session/client lifetime, not the transport: an
229
+ // SSE reconnect must never cancel an in-flight history page.
230
230
  useEffect(() => {
231
231
  navigationGenerationRef.current += 1;
232
232
  setNewerError(null);
@@ -621,9 +621,6 @@ export function useSessionEvents(
621
621
  }
622
622
  const current = eventWindowRef.current;
623
623
  assertPrependOrder(current.events, window.events);
624
- // Freeze the live iterator before replacing its in-memory window. Rows
625
- // pending in the aborted iterator were never cursor-committed and will
626
- // be replayed from the retained high-water mark below.
627
624
  const next = boundBrowserSessionEventWindow([...window.events, ...current.events], {
628
625
  direction: "oldest",
629
626
  });
@@ -633,7 +630,6 @@ export function useSessionEvents(
633
630
  markTailPreserved();
634
631
  return false;
635
632
  }
636
- streamAbortRef.current?.abort();
637
633
  const status = observeSessionStatus(window.events, sessionStatusRef);
638
634
  const retained = {
639
635
  ...next,
@@ -645,21 +641,28 @@ export function useSessionEvents(
645
641
  }
646
642
  const retainedNewest = maxResumeSequenceOrNull(retained.events);
647
643
  const previousNewest = maxResumeSequenceOrNull(current.events);
644
+ const evictedLiveTail =
645
+ previousNewest !== null && retainedNewest !== null && retainedNewest < previousNewest;
646
+ if (evictedLiveTail && viewModeRef.current === "live") {
647
+ // The prepend leaves the live tip. Freeze the iterator before the
648
+ // window is replaced so a late flush cannot append live rows onto
649
+ // (and newest-bound away) the history the reader just requested.
650
+ streamAbortRef.current?.abort();
651
+ }
648
652
  eventWindowRef.current = retained;
649
653
  markCommitted();
650
654
  oldestSequenceRef.current = retainedOldest;
651
655
  newestSequenceRef.current = retainedNewest;
652
- streamResumeSequenceRef.current = maxResumeSequence(retained.events);
656
+ if (evictedLiveTail) {
657
+ streamResumeSequenceRef.current = maxResumeSequence(retained.events);
658
+ }
653
659
  // Oldest-directed eviction can discard newer in-memory rows. That fact
654
660
  // keeps windowTruncated true, but it does not imply older durable rows
655
661
  // exist; only the backward DB page can answer hasOlder truthfully.
656
662
  const olderStillAvailable = window.hasOlder;
657
663
  hasOlderRef.current = olderStillAvailable;
658
- const evictedLiveTail =
659
- previousNewest !== null && retainedNewest !== null && retainedNewest < previousNewest;
660
664
  let newer: boolean | null = null;
661
665
  let enterHistory = false;
662
- let reconnectLive = false;
663
666
  if (viewModeRef.current === "history" || evictedLiveTail) {
664
667
  const highWater = lastSequenceRef.current;
665
668
  newer = retainedNewest !== null && (retainedNewest < highWater || retained.truncated);
@@ -672,11 +675,13 @@ export function useSessionEvents(
672
675
  viewModeRef.current = "history";
673
676
  enterHistory = true;
674
677
  }
675
- } else {
676
- // The retained merge is still one contiguous live suffix. Restart
677
- // SSE from its cursor so events queued during the fetch are replayed.
678
- reconnectLive = true;
679
678
  }
679
+ // Otherwise the merge is still one contiguous live suffix and the open
680
+ // SSE iterator keeps appending to it: every flush reads
681
+ // eventWindowRef, so rows delivered during or after this fetch land
682
+ // once, in order, on the prepended window. Reopening the stream here
683
+ // would flash a connecting state and re-run session reconciliation on
684
+ // every scroll-up for no data benefit.
680
685
  loadingOlderRef.current = false;
681
686
  startTransition(() => {
682
687
  if (status !== undefined) {
@@ -689,8 +694,6 @@ export function useSessionEvents(
689
694
  }
690
695
  if (enterHistory) {
691
696
  setViewMode("history");
692
- } else if (reconnectLive) {
693
- setStreamEpoch((epoch) => epoch + 1);
694
697
  }
695
698
  setLoadingOlder(false);
696
699
  });
@@ -611,6 +611,12 @@ function classifyStartError(error: unknown): TranscriptionErrorCode {
611
611
 
612
612
  export function normalizeTranscriptionErrorCode(code: unknown): TranscriptionErrorCode {
613
613
  switch (code) {
614
+ case "insufficient_credits":
615
+ case "allowance_exhausted":
616
+ case "monthly_model_cost_limit":
617
+ case "unavailable":
618
+ case "too_large":
619
+ case "invalid_audio":
614
620
  case "permission_denied":
615
621
  case "not_supported":
616
622
  case "network":