@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,4 +1,9 @@
1
- import type { FirstPartyMcpToolName, CapabilityCatalogItem } from "@opengeni/sdk";
1
+ import type {
2
+ FirstPartyMcpToolName,
3
+ CapabilityCatalogItem,
4
+ Session,
5
+ UpdateSessionToolPolicyRequest,
6
+ } from "@opengeni/sdk";
2
7
  import type { OpenGeniBrowserClient as OpenGeniClient } from "@opengeni/sdk/browser";
3
8
 
4
9
  /** Catalog tool discovery may be lazy; selecting the installed server does not
@@ -25,6 +30,16 @@ export async function attachSessionCapability(
25
30
  if (!stillCurrent()) throw new Error("Connection setup was interrupted.");
26
31
  const session = await client.getSession(workspaceId, sessionId);
27
32
  if (!stillCurrent()) throw new Error("Connection setup was interrupted.");
33
+ const request = capabilityToolPolicyUpdate(session, item);
34
+ if (request) await client.updateSessionToolPolicy(workspaceId, sessionId, request);
35
+ }
36
+
37
+ function capabilityToolPolicyUpdate(
38
+ session: Session,
39
+ item: CapabilityCatalogItem,
40
+ ): UpdateSessionToolPolicyRequest | null {
41
+ const tools = sessionCapabilityTools(item);
42
+ if (item.kind === "skill" || tools.length === 0) return null;
28
43
  // Session.tools includes mandatory runtime servers, which must never be
29
44
  // echoed into the human's explicit selection.
30
45
  let existing = session.tools.filter(
@@ -42,10 +57,19 @@ export async function attachSessionCapability(
42
57
  // automatic selection. Compare against the resolved defaults instead.
43
58
  existing = policy.effectiveIds
44
59
  .filter((id) => !policy.mandatoryIds.includes(id))
45
- .map((id) => ({ kind: "mcp" as const, id }));
60
+ .map(
61
+ (id) =>
62
+ session.tools.find((tool) => tool.kind === "mcp" && tool.id === id) ?? {
63
+ kind: "mcp" as const,
64
+ id,
65
+ optional: true,
66
+ },
67
+ );
46
68
  }
47
69
  const additions = tools.filter(
48
- (tool) => !existing.some((current) => current.kind === tool.kind && current.id === tool.id),
70
+ (tool) =>
71
+ !session.effectiveToolPolicy?.mandatoryIds.includes(tool.id) &&
72
+ !existing.some((current) => current.kind === tool.kind && current.id === tool.id),
49
73
  );
50
74
  const recommended = Array.isArray(item.metadata?.firstPartyMcpTools)
51
75
  ? (item.metadata.firstPartyMcpTools.filter(
@@ -53,13 +77,102 @@ export async function attachSessionCapability(
53
77
  ) as FirstPartyMcpToolName[])
54
78
  : [];
55
79
  const firstParty = [...new Set([...session.firstPartyMcpTools, ...recommended])];
56
- if (additions.length === 0 && firstParty.length === session.firstPartyMcpTools.length) return;
57
- await client.updateSessionToolPolicy(workspaceId, sessionId, {
80
+ if (additions.length === 0 && firstParty.length === session.firstPartyMcpTools.length)
81
+ return null;
82
+ return {
58
83
  mode: "explicit",
59
84
  tools: [...existing, ...additions],
60
85
  firstPartyMcpTools: firstParty,
61
86
  expectedVersion: session.toolPolicyVersion,
62
- });
87
+ };
88
+ }
89
+
90
+ /** Read-only review. Hosts must show ancestor changes and obtain a separate
91
+ * explicit click before applying. This plan is never authorization. */
92
+ export type SessionCapabilityAccessPlan = {
93
+ workspaceId: string;
94
+ sessionId: string;
95
+ capabilityId: string;
96
+ sessions: Array<{
97
+ id: string;
98
+ title: string | null;
99
+ parentSessionId: string | null;
100
+ toolPolicyVersion: number;
101
+ request: UpdateSessionToolPolicyRequest | null;
102
+ }>;
103
+ };
104
+
105
+ export async function prepareSessionCapabilityAccess(
106
+ client: OpenGeniClient,
107
+ workspaceId: string,
108
+ sessionId: string,
109
+ item: CapabilityCatalogItem,
110
+ stillCurrent: () => boolean = () => true,
111
+ ): Promise<SessionCapabilityAccessPlan> {
112
+ const sessions: SessionCapabilityAccessPlan["sessions"] = [];
113
+ if (item.kind === "skill" || sessionCapabilityTools(item).length === 0)
114
+ return { workspaceId, sessionId, capabilityId: item.id, sessions };
115
+ const visited = new Set<string>();
116
+ let id: string | null = sessionId;
117
+ while (id) {
118
+ if (!stillCurrent()) throw new Error("Connection setup was interrupted.");
119
+ if (visited.has(id)) throw new Error("The parent chat chain could not be verified.");
120
+ visited.add(id);
121
+ const session = await client.getSession(workspaceId, id);
122
+ if (!stillCurrent()) throw new Error("Connection setup was interrupted.");
123
+ const request = capabilityToolPolicyUpdate(session, item);
124
+ sessions.push({
125
+ id,
126
+ title: session.title ?? null,
127
+ parentSessionId: session.parentSessionId ?? null,
128
+ toolPolicyVersion: session.toolPolicyVersion,
129
+ request,
130
+ });
131
+ // An unchanged immediate parent already supplies this capability's ceiling.
132
+ // Do not read or broaden ancestors irrelevant to this addition.
133
+ if (!request) break;
134
+ id = session.parentSessionId ?? null;
135
+ }
136
+ return { workspaceId, sessionId, capabilityId: item.id, sessions: sessions.reverse() };
137
+ }
138
+
139
+ /** Preflight the entire review, then apply ordinary CAS-fenced API writes root
140
+ * first. A partial failure may leave approved ancestor changes committed; never
141
+ * claim success or roll back another person's edits. Prepare a new review. */
142
+ export async function applySessionCapabilityAccess(
143
+ client: OpenGeniClient,
144
+ plan: SessionCapabilityAccessPlan,
145
+ stillCurrent: () => boolean = () => true,
146
+ ): Promise<void> {
147
+ for (const reviewed of plan.sessions) {
148
+ if (!stillCurrent()) throw new Error("Connection setup was interrupted.");
149
+ const current = await client.getSession(plan.workspaceId, reviewed.id);
150
+ if (!stillCurrent()) throw new Error("Connection setup was interrupted.");
151
+ if (
152
+ current.toolPolicyVersion !== reviewed.toolPolicyVersion ||
153
+ (current.parentSessionId ?? null) !== reviewed.parentSessionId
154
+ )
155
+ throw new Error("Chat access changed. Click Add tools to review the current choices again.");
156
+ }
157
+ for (const reviewed of plan.sessions) {
158
+ if (!stillCurrent()) throw new Error("Connection setup was interrupted.");
159
+ if (reviewed.request) {
160
+ const updated = await client.updateSessionToolPolicy(
161
+ plan.workspaceId,
162
+ reviewed.id,
163
+ reviewed.request,
164
+ );
165
+ if (
166
+ reviewed.request.mode === "explicit" &&
167
+ reviewed.request.firstPartyMcpTools.some(
168
+ (tool) => !updated.firstPartyMcpTools.includes(tool),
169
+ )
170
+ )
171
+ throw new Error(
172
+ "This chat's capabilities do not allow these tools. Review its capability settings before adding tools again.",
173
+ );
174
+ }
175
+ }
63
176
  }
64
177
 
65
178
  /** Native OAuth callbacks have already saved the provider connection. Finish
@@ -61,6 +61,16 @@ export type {
61
61
 
62
62
  // renderer registry
63
63
  export { createToolRegistry, rawTypeOf } from "./registry";
64
+ export {
65
+ createSessionRetainedScreenshotLoader,
66
+ createWorkspaceRetainedArtifactLoader,
67
+ createWorkspaceRetainedVideoLoader,
68
+ } from "./retained-loaders";
69
+ export type {
70
+ RetainedArtifactLoaderClient,
71
+ RetainedScreenshotLoaderClient,
72
+ RetainedVideoLoaderClient,
73
+ } from "./retained-loaders";
64
74
  export type {
65
75
  CreateToolRegistryOptions,
66
76
  RetainedArtifactLoader,
@@ -0,0 +1,86 @@
1
+ import type { OpenGeniClient, RetainedArtifactReference } from "@opengeni/sdk";
2
+ import type {
3
+ RetainedArtifactLoader,
4
+ RetainedScreenshotLoader,
5
+ VideoArtifactPlaybackLoader,
6
+ } from "./registry";
7
+
8
+ /** SDK methods the workspace retained-artifact loader needs. */
9
+ export type RetainedArtifactLoaderClient = Pick<
10
+ OpenGeniClient,
11
+ "createRetainedArtifactDownloadUrl" | "downloadRetainedArtifact"
12
+ >;
13
+
14
+ /** SDK method the session retained-screenshot loader needs. */
15
+ export type RetainedScreenshotLoaderClient = Pick<OpenGeniClient, "downloadRetainedScreenshot">;
16
+
17
+ /** SDK method the generated-video playback loader needs. */
18
+ export type RetainedVideoLoaderClient = Pick<OpenGeniClient, "createVideoArtifactPlaybackSource">;
19
+
20
+ /**
21
+ * Bind permanent retained-artifact retrieval (generated images, published
22
+ * files) to one authenticated workspace, for `MessageTimeline`'s
23
+ * `loadRetainedArtifact`. Images get a short-lived URL; files are assembled
24
+ * from verified byte ranges.
25
+ */
26
+ export function createWorkspaceRetainedArtifactLoader(
27
+ client: RetainedArtifactLoaderClient,
28
+ workspaceId: string,
29
+ ): RetainedArtifactLoader {
30
+ return async (artifact: RetainedArtifactReference, signal: AbortSignal) => {
31
+ if (artifact.kind === "file") {
32
+ const download = await client.downloadRetainedArtifact(workspaceId, artifact, { signal });
33
+ return download.bytes;
34
+ }
35
+ const download = await client.createRetainedArtifactDownloadUrl(workspaceId, artifact, {
36
+ signal,
37
+ });
38
+ return { url: download.url };
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Bind the authenticated screenshot downloader to one workspace and session,
44
+ * for `MessageTimeline`'s `loadRetainedScreenshot`. Fetched metadata must match
45
+ * the timeline receipt before bytes are handed to React; errors deliberately
46
+ * contain no storage locator or content.
47
+ */
48
+ export function createSessionRetainedScreenshotLoader(
49
+ client: RetainedScreenshotLoaderClient,
50
+ workspaceId: string,
51
+ sessionId: string,
52
+ ): RetainedScreenshotLoader {
53
+ return async (artifact: RetainedArtifactReference, signal: AbortSignal) => {
54
+ const download = await client.downloadRetainedScreenshot(
55
+ workspaceId,
56
+ sessionId,
57
+ artifact.artifactId,
58
+ { signal },
59
+ );
60
+ if (!download.metadata.available || !download.bytes) return null;
61
+ if (
62
+ download.metadata.artifactId !== artifact.artifactId ||
63
+ download.metadata.kind !== artifact.kind ||
64
+ download.metadata.contentType !== artifact.contentType ||
65
+ download.metadata.originalBytes !== artifact.originalBytes ||
66
+ download.metadata.sha256 !== artifact.sha256 ||
67
+ download.metadata.dimensions?.width !== artifact.dimensions?.width ||
68
+ download.metadata.dimensions?.height !== artifact.dimensions?.height
69
+ ) {
70
+ throw new Error("Retained screenshot receipt verification failed");
71
+ }
72
+ return download.bytes;
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Bind expiring generated-video playback sources to one authenticated
78
+ * workspace, for `MessageTimeline`'s `loadVideoArtifactPlayback`.
79
+ */
80
+ export function createWorkspaceRetainedVideoLoader(
81
+ client: RetainedVideoLoaderClient,
82
+ workspaceId: string,
83
+ ): VideoArtifactPlaybackLoader {
84
+ return async (artifactId: string, signal: AbortSignal) =>
85
+ await client.createVideoArtifactPlaybackSource(workspaceId, artifactId, { signal });
86
+ }
@@ -30,7 +30,11 @@ export function toolMatchesLeaf(wireName: string, leaf: string): boolean {
30
30
  */
31
31
  export function toolDisplayName(name: string, display?: ToolDisplayMetadata): string {
32
32
  if (!display && /^[a-f0-9]{64}$/.test(name)) return "Tool call";
33
- const phrase = (display?.toolName ?? mcpToolLeaf(name)).replace(/[_-]+/g, " ").trim();
33
+ // "batch modify messages" → "Modify messages": batching is transport, not the action.
34
+ const phrase = (display?.toolName ?? mcpToolLeaf(name))
35
+ .replace(/[_-]+/g, " ")
36
+ .trim()
37
+ .replace(/^batch\s+(?=\S)/i, "");
34
38
  const title =
35
39
  display?.title ?? (phrase ? phrase.charAt(0).toUpperCase() + phrase.slice(1) : name);
36
40
  return display?.accountLabel ? `${title} — ${display.accountLabel}` : title;
@@ -1,4 +1,6 @@
1
- import { useHasToolReview, ToolReviewHistoryReceipt } from "../components/tool-review-history";
1
+ import { useHasToolReview, useRecordedToolReview } from "../components/tool-review-history";
2
+ import { ToolActionReviewCard } from "../components/tool-action-review";
3
+ import type { ToolReviewStatus } from "@opengeni/sdk";
2
4
  import { KnowledgeReceiptRow } from "./knowledge-receipt";
3
5
  import { defaultUrlTransform } from "react-markdown";
4
6
  import { isRetainedImageContentType, useRetainedImageObjectUrl } from "./retained-image";
@@ -588,7 +590,10 @@ function ComputerCallRenderer({ item, loadRetainedScreenshot }: ToolRendererProp
588
590
  // every transport.
589
591
  const functionAction: ComputerAction | undefined =
590
592
  !raw.action && item.name.startsWith("computer_") && item.name !== "computer_call"
591
- ? { type: item.name.slice("computer_".length), ...asComputerArgs(item.arguments) }
593
+ ? {
594
+ type: item.name.slice("computer_".length),
595
+ ...asComputerArgs(item.arguments),
596
+ }
592
597
  : undefined;
593
598
  const action = raw.action ?? functionAction;
594
599
  const actions = raw.actions ?? (action ? [action] : []);
@@ -2335,10 +2340,74 @@ function RunOnRenderer({ item }: ToolRendererProps) {
2335
2340
  */
2336
2341
  function GenericRenderer({ item }: ToolRendererProps) {
2337
2342
  const reviewed = useHasToolReview(item.callId);
2338
- if (reviewed && item.callId) return <ToolReviewHistoryReceipt approvalId={item.callId} />;
2343
+ if (reviewed && item.callId) return <ReviewedGenericRenderer item={item} />;
2339
2344
  return <UnreviewedGenericRenderer item={item} />;
2340
2345
  }
2341
2346
 
2347
+ /** Settled review states earn one quiet gutter word; success and waiting stay chip-free. */
2348
+ const REVIEW_CHIP: Partial<Record<ToolReviewStatus, DisclosureChip>> = {
2349
+ rejected: { tone: "interrupted", text: "declined" },
2350
+ cancelled: { tone: "interrupted", text: "not run" },
2351
+ expired: { tone: "interrupted", text: "expired" },
2352
+ stale: { tone: "interrupted", text: "not run" },
2353
+ revoked: { tone: "interrupted", text: "not run" },
2354
+ blocked: { tone: "interrupted", text: "blocked" },
2355
+ unknown: { tone: "bad", text: "outcome unknown" },
2356
+ partial: { tone: "bad", text: "partly done" },
2357
+ failed: { tone: "bad", text: "failed" },
2358
+ };
2359
+
2360
+ /**
2361
+ * A call that went through approval keeps the ordinary row shape, titled with
2362
+ * what was approved. The expanded body is the saved review plus the result.
2363
+ */
2364
+ function ReviewedGenericRenderer({ item }: ToolRendererProps) {
2365
+ const { review, onViewDetails } = useRecordedToolReview(item.callId);
2366
+ if (!review) return <UnreviewedGenericRenderer item={item} />;
2367
+ const icon = <GenericToolIcon name={item.name} />;
2368
+ const waiting = review.status === "pending";
2369
+ const running =
2370
+ !waiting &&
2371
+ (review.status === "executing" || review.status === "approved") &&
2372
+ item.status === "running";
2373
+ const { text: outText, isError } = unwrapMcpOutput(item.output);
2374
+ const chip =
2375
+ REVIEW_CHIP[review.status] ??
2376
+ (isError && !running && !waiting ? ({ tone: "bad", text: "error" } as const) : undefined);
2377
+ return (
2378
+ <ActivityDisclosure
2379
+ icon={icon}
2380
+ iconTone={chip?.tone === "bad" ? "failed" : waiting ? "accent" : "muted"}
2381
+ title={review.title}
2382
+ running={running}
2383
+ chip={chip}
2384
+ preview={
2385
+ waiting ? (
2386
+ "Waiting for your approval"
2387
+ ) : running ? (
2388
+ <RunningPreview>Running…</RunningPreview>
2389
+ ) : (
2390
+ (review.accountLabel ?? undefined)
2391
+ )
2392
+ }
2393
+ >
2394
+ <div className="py-1" data-approval-id={item.callId} data-review-origin="history">
2395
+ <ToolActionReviewCard
2396
+ bare
2397
+ review={{ ...review, availableActions: [] }}
2398
+ onViewDetails={onViewDetails}
2399
+ />
2400
+ </div>
2401
+ {outText ? <PayloadBlock label="Result" value={outText} failed={isError} /> : null}
2402
+ </ActivityDisclosure>
2403
+ );
2404
+ }
2405
+
2406
+ /** The runtime's exact refusal for a tool whose effective permission is Block. */
2407
+ const BLOCKED_OUTPUT = /Connector action was not executed: blocked\b/;
2408
+ /** The runtime's refusal to guess whether an approved action ran. */
2409
+ const UNCERTAIN_OUTPUT = /Connector action outcome is uncertain\b/;
2410
+
2342
2411
  function UnreviewedGenericRenderer({ item }: ToolRendererProps) {
2343
2412
  const running = item.status === "running";
2344
2413
  const args = parseToolArgs(item.arguments);
@@ -2363,6 +2432,42 @@ function UnreviewedGenericRenderer({ item }: ToolRendererProps) {
2363
2432
  }
2364
2433
 
2365
2434
  const { text: outText, isError } = unwrapMcpOutput(item.output);
2435
+ // A permission Block is a decision, not a tool failure: say so plainly instead
2436
+ // of the generic "error occurred, please try again" wrapper.
2437
+ if (isError && BLOCKED_OUTPUT.test(outText)) {
2438
+ return (
2439
+ <ActivityDisclosure
2440
+ icon={icon}
2441
+ iconTone="muted"
2442
+ title={display}
2443
+ chip={{ tone: "interrupted", text: "blocked" }}
2444
+ preview="Blocked by your permission settings"
2445
+ >
2446
+ <p className="m-0 py-1 text-og-sm text-og-fg-muted">
2447
+ Your permission settings block this action, so it did not run. You can change this in the
2448
+ integration's tool permissions.
2449
+ </p>
2450
+ <PayloadBlock label="Arguments" value={args} />
2451
+ </ActivityDisclosure>
2452
+ );
2453
+ }
2454
+ // An interrupted approved action may have run. Never invite a blind retry.
2455
+ if (isError && UNCERTAIN_OUTPUT.test(outText)) {
2456
+ return (
2457
+ <ActivityDisclosure
2458
+ icon={icon}
2459
+ iconTone="failed"
2460
+ title={display}
2461
+ chip={{ tone: "bad", text: "outcome unknown" }}
2462
+ preview="It may have run"
2463
+ >
2464
+ <p className="m-0 py-1 text-og-sm text-og-fg-muted">
2465
+ This action may have run. Check the result in the connected app before trying again.
2466
+ </p>
2467
+ <PayloadBlock label="Arguments" value={args} />
2468
+ </ActivityDisclosure>
2469
+ );
2470
+ }
2366
2471
  // Cancelled is NOT an error — a user-cancelled tool should not surface the red
2367
2472
  // error chip even if the output payload carries an isError flag (the error may be
2368
2473
  // a consequence of the cancellation, not the tool's own failure).
@@ -2543,25 +2648,41 @@ const BASE_ENTRIES: ToolRegistryEntry[] = [
2543
2648
  { match: "name", name: "apply_patch_call", render: ApplyPatchRenderer },
2544
2649
  { match: "name", name: "apply_patch", render: ApplyPatchRenderer },
2545
2650
  { match: "name", name: "computer_call", render: ComputerCallRenderer },
2546
- { match: "name", name: "browser_screenshot", render: BrowserScreenshotRenderer },
2651
+ {
2652
+ match: "name",
2653
+ name: "browser_screenshot",
2654
+ render: BrowserScreenshotRenderer,
2655
+ },
2547
2656
  { match: "name", name: "browser_observe", render: BrowserScreenshotRenderer },
2548
2657
  { match: "name", name: "browser_act", render: BrowserScreenshotRenderer },
2549
2658
  // Function-mode computer tools (codex / chat-wire transports).
2550
2659
  { match: "name", name: "computer_screenshot", render: ComputerCallRenderer },
2551
2660
  { match: "name", name: "computer_click", render: ComputerCallRenderer },
2552
- { match: "name", name: "computer_double_click", render: ComputerCallRenderer },
2661
+ {
2662
+ match: "name",
2663
+ name: "computer_double_click",
2664
+ render: ComputerCallRenderer,
2665
+ },
2553
2666
  { match: "name", name: "computer_move", render: ComputerCallRenderer },
2554
2667
  { match: "name", name: "computer_scroll", render: ComputerCallRenderer },
2555
2668
  { match: "name", name: "computer_type", render: ComputerCallRenderer },
2556
2669
  { match: "name", name: "computer_keypress", render: ComputerCallRenderer },
2557
2670
  { match: "name", name: "computer_drag", render: ComputerCallRenderer },
2558
2671
  { match: "name", name: "web_search_call", render: WebSearchRenderer },
2559
- { match: "name", name: "image_generation_call", render: GeneratedImageRenderer },
2672
+ {
2673
+ match: "name",
2674
+ name: "image_generation_call",
2675
+ render: GeneratedImageRenderer,
2676
+ },
2560
2677
  { match: "name", name: "generate_image", render: GeneratedImageRenderer },
2561
2678
  { match: "name", name: "generate_video", render: GeneratedVideoRenderer },
2562
2679
  { match: "name", name: "tool_search", render: ToolSearchRenderer },
2563
2680
  { match: "name", name: "view_image", render: ViewImageRenderer },
2564
- { match: "name", name: "sandbox_file_publish", render: SandboxFilePublishRenderer },
2681
+ {
2682
+ match: "name",
2683
+ name: "sandbox_file_publish",
2684
+ render: SandboxFilePublishRenderer,
2685
+ },
2565
2686
  {
2566
2687
  match: "name",
2567
2688
  name: "artifacts_create",
@@ -2586,13 +2707,25 @@ const BASE_ENTRIES: ToolRegistryEntry[] = [
2586
2707
  render: SiteArtifactRenderer,
2587
2708
  matchPrefixedLeaf: false,
2588
2709
  },
2589
- { match: "name", name: "environment_set_variable", render: SecretSetRenderer },
2590
- { match: "name", name: "variable_set_set_variable", render: SecretSetRenderer },
2710
+ {
2711
+ match: "name",
2712
+ name: "environment_set_variable",
2713
+ render: SecretSetRenderer,
2714
+ },
2715
+ {
2716
+ match: "name",
2717
+ name: "variable_set_set_variable",
2718
+ render: SecretSetRenderer,
2719
+ },
2591
2720
  { match: "name", name: "search_documents", render: DocsSearchRenderer },
2592
2721
  { match: "name", name: "knowledge_search", render: DocsSearchRenderer },
2593
2722
  { match: "name", name: "memory_propose", render: MemoryProposeRenderer },
2594
2723
  { match: "name", name: "set_session_title", render: SetSessionTitleRenderer },
2595
- { match: "name", name: "set_other_session_title", render: SetSessionTitleRenderer },
2724
+ {
2725
+ match: "name",
2726
+ name: "set_other_session_title",
2727
+ render: SetSessionTitleRenderer,
2728
+ },
2596
2729
  ];
2597
2730
 
2598
2731
  /** The built-in tool renderer registry: every first-party tool plus a fallback. */
@@ -98,6 +98,28 @@ async function createUnderlyingOwnerLease(): Promise<UnderlyingOwnerLease> {
98
98
  return { ownerId, release: () => undefined };
99
99
  }
100
100
 
101
+ /**
102
+ * Owner ids whose document still holds its Web Lock, or null when this browser
103
+ * cannot answer. A lock is released the moment its tab closes or crashes, so a
104
+ * recording whose owner is absent here is recoverable immediately instead of
105
+ * after the heartbeat stale window.
106
+ */
107
+ export async function liveVoiceRecordingOwnerIds(): Promise<ReadonlySet<string> | null> {
108
+ if (!hasWebLocks() || typeof navigator.locks.query !== "function") return null;
109
+ try {
110
+ const snapshot = await navigator.locks.query();
111
+ const live = new Set<string>();
112
+ for (const lock of [...(snapshot.held ?? []), ...(snapshot.pending ?? [])]) {
113
+ if (lock.name?.startsWith(VOICE_RECORDING_OWNER_LOCK_PREFIX)) {
114
+ live.add(lock.name.slice(VOICE_RECORDING_OWNER_LOCK_PREFIX.length));
115
+ }
116
+ }
117
+ return live;
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+
101
123
  function hasWebLocks(): boolean {
102
124
  return (
103
125
  typeof navigator !== "undefined" &&