@opengeni/react 0.57.0 → 1.0.2-canary.37191796234001

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 (629) hide show
  1. package/README.md +654 -22
  2. package/dist/accounts.d.ts +64 -0
  3. package/dist/accounts.js +822 -0
  4. package/dist/accounts.js.map +1 -0
  5. package/dist/approvals.d.ts +2 -1
  6. package/dist/artifacts-document.js +3 -2
  7. package/dist/artifacts-presentation.js +3 -2
  8. package/dist/artifacts-spreadsheet.js +2 -2
  9. package/dist/artifacts.d.ts +4 -1
  10. package/dist/artifacts.js +518 -42
  11. package/dist/artifacts.js.map +1 -1
  12. package/dist/browser-viewer-GTR5XQ7X.js +16 -0
  13. package/dist/capability-catalog-row.d.ts +20 -0
  14. package/dist/capability-logo-fallback.d.ts +3 -0
  15. package/dist/chat-interactive-block-UGBTNB3L.js +11 -0
  16. package/dist/chat-interactive-block-UGBTNB3L.js.map +1 -0
  17. package/dist/{chunk-5UQPNLIO.js → chunk-23J2ZNXT.js} +4117 -3896
  18. package/dist/chunk-23J2ZNXT.js.map +1 -0
  19. package/dist/chunk-447JLCBU.js +209 -0
  20. package/dist/chunk-447JLCBU.js.map +1 -0
  21. package/dist/chunk-4RVYOY4O.js +379 -0
  22. package/dist/chunk-4RVYOY4O.js.map +1 -0
  23. package/dist/chunk-5CVXZA3Y.js +292 -0
  24. package/dist/chunk-5CVXZA3Y.js.map +1 -0
  25. package/dist/chunk-5J4FOW6Q.js +108 -0
  26. package/dist/chunk-5J4FOW6Q.js.map +1 -0
  27. package/dist/chunk-A2PI6UMY.js +41 -0
  28. package/dist/chunk-A2PI6UMY.js.map +1 -0
  29. package/dist/{chunk-7WCLAEFV.js → chunk-A4UJB2MM.js} +37 -27
  30. package/dist/chunk-A4UJB2MM.js.map +1 -0
  31. package/dist/chunk-B3V6TG77.js +41 -0
  32. package/dist/chunk-B3V6TG77.js.map +1 -0
  33. package/dist/chunk-BRM6FVZA.js +302 -0
  34. package/dist/chunk-BRM6FVZA.js.map +1 -0
  35. package/dist/chunk-C5X5AJXK.js +182 -0
  36. package/dist/chunk-C5X5AJXK.js.map +1 -0
  37. package/dist/chunk-CR7U7HAR.js +453 -0
  38. package/dist/chunk-CR7U7HAR.js.map +1 -0
  39. package/dist/{chunk-R3AZOAB5.js → chunk-DYWBUX5Q.js} +29 -123
  40. package/dist/chunk-DYWBUX5Q.js.map +1 -0
  41. package/dist/chunk-E3G7SAYZ.js +3124 -0
  42. package/dist/chunk-E3G7SAYZ.js.map +1 -0
  43. package/dist/chunk-ELCE5SXV.js +359 -0
  44. package/dist/chunk-ELCE5SXV.js.map +1 -0
  45. package/dist/chunk-ERGYJIBB.js +21 -0
  46. package/dist/chunk-ERGYJIBB.js.map +1 -0
  47. package/dist/chunk-FFA2OVOV.js +319 -0
  48. package/dist/chunk-FFA2OVOV.js.map +1 -0
  49. package/dist/chunk-FZCP527Y.js +116 -0
  50. package/dist/chunk-FZCP527Y.js.map +1 -0
  51. package/dist/chunk-GIMRELBW.js +14 -0
  52. package/dist/chunk-GIMRELBW.js.map +1 -0
  53. package/dist/{chunk-6KLX2RH5.js → chunk-H2NJQORO.js} +1069 -79
  54. package/dist/chunk-H2NJQORO.js.map +1 -0
  55. package/dist/chunk-I7IWRQLN.js +1608 -0
  56. package/dist/chunk-I7IWRQLN.js.map +1 -0
  57. package/dist/{chunk-HA46CYQK.js → chunk-IQUCRAT7.js} +792 -537
  58. package/dist/chunk-IQUCRAT7.js.map +1 -0
  59. package/dist/chunk-IWIDV7SB.js +1 -0
  60. package/dist/chunk-IWIDV7SB.js.map +1 -0
  61. package/dist/chunk-J2QWVSQY.js +312 -0
  62. package/dist/chunk-J2QWVSQY.js.map +1 -0
  63. package/dist/chunk-JFNEV4LU.js +2733 -0
  64. package/dist/chunk-JFNEV4LU.js.map +1 -0
  65. package/dist/chunk-JT4JW7BC.js +54 -0
  66. package/dist/chunk-JT4JW7BC.js.map +1 -0
  67. package/dist/chunk-K3HQAKQX.js +24 -0
  68. package/dist/chunk-K3HQAKQX.js.map +1 -0
  69. package/dist/{chunk-C5ZPSSS5.js → chunk-NEXLLTP4.js} +2 -1
  70. package/dist/{chunk-C5ZPSSS5.js.map → chunk-NEXLLTP4.js.map} +1 -1
  71. package/dist/chunk-NZUI76EM.js +371 -0
  72. package/dist/chunk-NZUI76EM.js.map +1 -0
  73. package/dist/{chunk-PEI6RANI.js → chunk-PNY3JKBJ.js} +309 -74
  74. package/dist/chunk-PNY3JKBJ.js.map +1 -0
  75. package/dist/{chunk-IGC6A2ZI.js → chunk-Q5QBEZIQ.js} +777 -246
  76. package/dist/chunk-Q5QBEZIQ.js.map +1 -0
  77. package/dist/chunk-RHREZ7BO.js +11 -0
  78. package/dist/chunk-RHREZ7BO.js.map +1 -0
  79. package/dist/chunk-SCOTPE2I.js +589 -0
  80. package/dist/chunk-SCOTPE2I.js.map +1 -0
  81. package/dist/{chunk-B6WPZSTT.js → chunk-SJZDKVW2.js} +1331 -1163
  82. package/dist/chunk-SJZDKVW2.js.map +1 -0
  83. package/dist/chunk-TMJRG4YC.js +12803 -0
  84. package/dist/chunk-TMJRG4YC.js.map +1 -0
  85. package/dist/chunk-TMYGL6X5.js +742 -0
  86. package/dist/chunk-TMYGL6X5.js.map +1 -0
  87. package/dist/chunk-UGLEYEMP.js +33 -0
  88. package/dist/chunk-UGLEYEMP.js.map +1 -0
  89. package/dist/chunk-UK4TINKT.js +27 -0
  90. package/dist/chunk-UK4TINKT.js.map +1 -0
  91. package/dist/chunk-UTRTG6LH.js +220 -0
  92. package/dist/chunk-UTRTG6LH.js.map +1 -0
  93. package/dist/chunk-WEHMPOXK.js +39 -0
  94. package/dist/chunk-WEHMPOXK.js.map +1 -0
  95. package/dist/chunk-WRHIZYZF.js +988 -0
  96. package/dist/chunk-WRHIZYZF.js.map +1 -0
  97. package/dist/{chunk-HUIKIDM5.js → chunk-WUISZ3WU.js} +223 -115
  98. package/dist/chunk-WUISZ3WU.js.map +1 -0
  99. package/dist/chunk-XCHWDJ22.js +107 -0
  100. package/dist/chunk-XCHWDJ22.js.map +1 -0
  101. package/dist/chunk-XOAMRDDK.js +481 -0
  102. package/dist/chunk-XOAMRDDK.js.map +1 -0
  103. package/dist/chunk-XUW2UWQM.js +299 -0
  104. package/dist/chunk-XUW2UWQM.js.map +1 -0
  105. package/dist/{chunk-KCGXPP5V.js → chunk-Y2PRMYLC.js} +11 -6
  106. package/dist/chunk-Y2PRMYLC.js.map +1 -0
  107. package/dist/chunk-YBXQDQFX.js +1772 -0
  108. package/dist/chunk-YBXQDQFX.js.map +1 -0
  109. package/dist/{chunk-KZ73RL76.js → chunk-YQEAA6LT.js} +7 -3
  110. package/dist/chunk-YQEAA6LT.js.map +1 -0
  111. package/dist/chunk-YV4L3ILF.js +9 -0
  112. package/dist/chunk-YV4L3ILF.js.map +1 -0
  113. package/dist/{chunk-LLOLUHVG.js → chunk-YWFG7ZX7.js} +1358 -2746
  114. package/dist/chunk-YWFG7ZX7.js.map +1 -0
  115. package/dist/chunk-ZYRGEKWS.js +216 -0
  116. package/dist/chunk-ZYRGEKWS.js.map +1 -0
  117. package/dist/client.d.ts +7 -4
  118. package/dist/clipboard.js +1 -0
  119. package/dist/commands/types.d.ts +2 -0
  120. package/dist/components/anthropic-mark.d.ts +3 -0
  121. package/dist/components/approval-surface.d.ts +15 -0
  122. package/dist/components/artifacts/artifact-chrome.d.ts +120 -0
  123. package/dist/components/artifacts/artifact-sandbox.d.ts +27 -0
  124. package/dist/components/artifacts/artifact-surface.d.ts +3 -1
  125. package/dist/components/artifacts/chat-interactive-block.d.ts +29 -0
  126. package/dist/components/artifacts/deferred-chat-media.d.ts +15 -0
  127. package/dist/components/artifacts/document-editor.d.ts +4 -2
  128. package/dist/components/artifacts/editable-artifact-ui.d.ts +2 -1
  129. package/dist/components/artifacts/editable-artifact-view.d.ts +46 -0
  130. package/dist/components/artifacts/editable-document.d.ts +1 -1
  131. package/dist/components/artifacts/editable-presentation.d.ts +2 -2
  132. package/dist/components/artifacts/editable-spreadsheet.d.ts +2 -1
  133. package/dist/components/artifacts/index.d.ts +8 -1
  134. package/dist/components/artifacts/inline-html-document.d.ts +2 -0
  135. package/dist/components/artifacts/latest-task-runner.d.ts +15 -0
  136. package/dist/components/artifacts/presentation-editor.d.ts +6 -2
  137. package/dist/components/artifacts/published-html-artifact-frame.d.ts +48 -3
  138. package/dist/components/artifacts/session-artifact-viewer.d.ts +39 -0
  139. package/dist/components/artifacts/site-view.d.ts +23 -0
  140. package/dist/components/artifacts/spreadsheet-general-display.d.ts +24 -0
  141. package/dist/components/artifacts/visualization-assets.gen.d.ts +2 -0
  142. package/dist/components/browser-select-control.d.ts +16 -0
  143. package/dist/components/browser-viewer.d.ts +10 -1
  144. package/dist/components/chat-composer.d.ts +21 -3
  145. package/dist/components/child-session-link.d.ts +5 -0
  146. package/dist/components/claude-mark.d.ts +3 -0
  147. package/dist/components/code-editor.d.ts +1 -4
  148. package/dist/components/composer.d.ts +45 -4
  149. package/dist/components/computer-viewer.d.ts +4 -1
  150. package/dist/components/copy-button.d.ts +3 -1
  151. package/dist/components/desktop-viewer.d.ts +1 -1
  152. package/dist/components/enrollment-consent.d.ts +6 -3
  153. package/dist/components/file-browser.d.ts +10 -1
  154. package/dist/components/fleet-tile.d.ts +2 -2
  155. package/dist/components/grok-mark.d.ts +26 -0
  156. package/dist/components/human-input-form.d.ts +21 -2
  157. package/dist/components/human-input-surface.d.ts +6 -3
  158. package/dist/components/machine-card.d.ts +4 -1
  159. package/dist/components/machines/machine-detail.d.ts +4 -1
  160. package/dist/components/machines/machine-operation-policy.d.ts +11 -0
  161. package/dist/components/machines-dashboard.d.ts +3 -1
  162. package/dist/components/markdown-table-layout.d.ts +12 -0
  163. package/dist/components/markdown.d.ts +47 -3
  164. package/dist/components/message-timeline.d.ts +72 -9
  165. package/dist/components/model-mark.d.ts +29 -0
  166. package/dist/components/model-policy-picker-menu.d.ts +9 -0
  167. package/dist/components/model-policy-picker.d.ts +62 -13
  168. package/dist/components/open-geni-chat.d.ts +55 -0
  169. package/dist/components/open-geni-links.d.ts +68 -0
  170. package/dist/components/openrouter-mark.d.ts +6 -0
  171. package/dist/components/pierre-file.d.ts +1 -1
  172. package/dist/components/preview-loading.d.ts +2 -0
  173. package/dist/components/queue-item-content.d.ts +9 -0
  174. package/dist/components/sandbox-files.d.ts +13 -2
  175. package/dist/components/sandbox-workspace.d.ts +43 -3
  176. package/dist/components/session-capability-frame.d.ts +35 -0
  177. package/dist/components/session-chrome.d.ts +66 -8
  178. package/dist/components/session-commands-panel.d.ts +6 -0
  179. package/dist/components/session-commands.d.ts +6 -0
  180. package/dist/components/session-connection-request.d.ts +8 -0
  181. package/dist/components/session-conversation.d.ts +86 -0
  182. package/dist/components/session-list.d.ts +36 -0
  183. package/dist/components/session-mcp-capability-card.d.ts +41 -0
  184. package/dist/components/timeline-anchor.d.ts +24 -0
  185. package/dist/components/timeline-annotation-chrome.d.ts +25 -0
  186. package/dist/components/timeline-annotation-layout.d.ts +42 -0
  187. package/dist/components/timeline-annotation-markers.d.ts +8 -0
  188. package/dist/components/timeline-annotation-reveal-context.d.ts +4 -0
  189. package/dist/components/timeline-annotation-shared.d.ts +31 -0
  190. package/dist/components/timeline-annotations-dialog.d.ts +3 -2
  191. package/dist/components/timeline-annotations.d.ts +13 -3
  192. package/dist/components/timeline-reading-progress.d.ts +11 -0
  193. package/dist/components/timeline-search.d.ts +34 -0
  194. package/dist/components/timeline-settlement.d.ts +16 -0
  195. package/dist/components/tip-follow.d.ts +16 -0
  196. package/dist/components/usage-limit-notice.d.ts +49 -0
  197. package/dist/components/usage-member-list.d.ts +91 -0
  198. package/dist/components/usage-meter.d.ts +73 -0
  199. package/dist/components/use-viewer-menu-dismiss.d.ts +2 -0
  200. package/dist/components/user-message-body.d.ts +10 -1
  201. package/dist/components/workspace-dock.d.ts +6 -2
  202. package/dist/composer.d.ts +5 -4
  203. package/dist/composer.js +15 -9
  204. package/dist/computer-viewer-ENHVAZ2Q.js +19 -0
  205. package/dist/computer-viewer-ENHVAZ2Q.js.map +1 -0
  206. package/dist/connect-account-identity.d.ts +8 -0
  207. package/dist/connect-accounts.d.ts +15 -0
  208. package/dist/connect-chooser.d.ts +18 -0
  209. package/dist/connect-panel.d.ts +18 -0
  210. package/dist/connect-setup.d.ts +16 -0
  211. package/dist/connect.d.ts +21 -0
  212. package/dist/connect.js +2129 -0
  213. package/dist/connect.js.map +1 -0
  214. package/dist/connection-catalog.d.ts +38 -0
  215. package/dist/connection-discovery.d.ts +12 -0
  216. package/dist/connection-installed.d.ts +17 -0
  217. package/dist/connection-logo.d.ts +9 -0
  218. package/dist/connection-service-logo.d.ts +8 -0
  219. package/dist/connection-service-presentation.d.ts +13 -0
  220. package/dist/connection-skeleton.d.ts +5 -0
  221. package/dist/connection-type-picker.d.ts +6 -0
  222. package/dist/conversation-timeline.d.ts +5 -0
  223. package/dist/desktop.d.ts +7 -0
  224. package/dist/desktop.js +26 -0
  225. package/dist/desktop.js.map +1 -0
  226. package/dist/device-authorization.d.ts +23 -0
  227. package/dist/diffs.d.ts +8 -0
  228. package/dist/diffs.js +14 -0
  229. package/dist/diffs.js.map +1 -0
  230. package/dist/discovery-cache.d.ts +5 -0
  231. package/dist/editor.d.ts +7 -0
  232. package/dist/editor.js +21 -0
  233. package/dist/editor.js.map +1 -0
  234. package/dist/embedded-session-client.d.ts +23 -0
  235. package/dist/file-node-visibility.d.ts +15 -0
  236. package/dist/{fleet-decision-row-GGCAT4E4.js → fleet-decision-row-YZ3ZKWMM.js} +2 -1
  237. package/dist/fleet-decision-row-YZ3ZKWMM.js.map +1 -0
  238. package/dist/hooks/channel-cache.d.ts +23 -0
  239. package/dist/hooks/latest-question.d.ts +25 -0
  240. package/dist/hooks/shared-machines-list.d.ts +14 -0
  241. package/dist/hooks/use-attached-browsers.d.ts +2 -1
  242. package/dist/hooks/use-browser-frame-stream.d.ts +1 -1
  243. package/dist/hooks/use-browser-session.d.ts +12 -1
  244. package/dist/hooks/use-channels.d.ts +1 -0
  245. package/dist/hooks/use-codex-accounts.d.ts +25 -7
  246. package/dist/hooks/use-composer.d.ts +45 -9
  247. package/dist/hooks/use-computer-frame-stream.d.ts +1 -1
  248. package/dist/hooks/use-computer-input-posture.d.ts +13 -0
  249. package/dist/hooks/use-computer-session.d.ts +2 -0
  250. package/dist/hooks/use-connect.d.ts +22 -0
  251. package/dist/hooks/use-desktop-stream.d.ts +1 -1
  252. package/dist/hooks/use-file-attachments.d.ts +21 -2
  253. package/dist/hooks/use-machine-chip.d.ts +3 -3
  254. package/dist/hooks/use-machines.d.ts +19 -1
  255. package/dist/hooks/use-sandbox-files.d.ts +5 -1
  256. package/dist/hooks/use-session-background-commands.d.ts +14 -0
  257. package/dist/hooks/use-session-events.d.ts +30 -8
  258. package/dist/hooks/use-session-lineage.d.ts +4 -0
  259. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  260. package/dist/hooks/use-session.d.ts +6 -0
  261. package/dist/hooks/use-turn-queue.d.ts +12 -0
  262. package/dist/hooks/use-usage.d.ts +40 -0
  263. package/dist/hooks/use-workspace-edit.d.ts +4 -0
  264. package/dist/hooks/use-workspace-sessions.d.ts +27 -5
  265. package/dist/human-input.d.ts +3 -1
  266. package/dist/identity-link-accounts.d.ts +11 -0
  267. package/dist/identity-link-consent.d.ts +18 -0
  268. package/dist/index.d.ts +60 -17
  269. package/dist/index.js +2237 -6200
  270. package/dist/index.js.map +1 -1
  271. package/dist/interaction.d.ts +1 -1
  272. package/dist/interaction.js +35 -184
  273. package/dist/interaction.js.map +1 -1
  274. package/dist/latest-question-755OW5DS.js +117 -0
  275. package/dist/latest-question-755OW5DS.js.map +1 -0
  276. package/dist/lib/error-message.d.ts +7 -0
  277. package/dist/lib/format.d.ts +36 -2
  278. package/dist/lib/host-theme.d.ts +70 -0
  279. package/dist/lib/interaction-errors.d.ts +6 -0
  280. package/dist/lib/menu-styles.d.ts +22 -0
  281. package/dist/lib/pierre-diffs-loader.d.ts +20 -0
  282. package/dist/lib/workbench-peers.d.ts +36 -0
  283. package/dist/lib/xterm-theme.d.ts +7 -5
  284. package/dist/machines.d.ts +4 -2
  285. package/dist/machines.js +14 -4
  286. package/dist/markdown-table-layout-QXSLK4BY.js +106 -0
  287. package/dist/markdown-table-layout-QXSLK4BY.js.map +1 -0
  288. package/dist/mcp-connection-status.d.ts +14 -0
  289. package/dist/model-policy-picker-menu-PWRI7R2X.js +14 -0
  290. package/dist/model-policy-picker-menu-PWRI7R2X.js.map +1 -0
  291. package/dist/model-policy.d.ts +19 -3
  292. package/dist/model-policy.js +9 -1
  293. package/dist/older-history.d.ts +30 -0
  294. package/dist/platform-activity-row-627BFZ46.js +182 -0
  295. package/dist/platform-activity-row-627BFZ46.js.map +1 -0
  296. package/dist/plugin-details.d.ts +14 -0
  297. package/dist/plugin-discovery.d.ts +25 -0
  298. package/dist/provider.d.ts +13 -1
  299. package/dist/{queue-surface-implementation-2KVJTYXH.js → queue-surface-implementation-ZHFI5JBP.js} +131 -44
  300. package/dist/queue-surface-implementation-ZHFI5JBP.js.map +1 -0
  301. package/dist/realtime/realtime-control.d.ts +10 -3
  302. package/dist/realtime.js +229 -83
  303. package/dist/realtime.js.map +1 -1
  304. package/dist/sandbox-files-BBC47SLD.js +20 -0
  305. package/dist/sandbox-files-BBC47SLD.js.map +1 -0
  306. package/dist/session-auth-recommendation.d.ts +5 -0
  307. package/dist/session-capability-policy.d.ts +12 -0
  308. package/dist/session-ui.d.ts +30 -2
  309. package/dist/session-ui.js +85 -7
  310. package/dist/session-ui.js.map +1 -1
  311. package/dist/session.d.ts +8 -2
  312. package/dist/session.js +30 -19
  313. package/dist/sites-ui.d.ts +34 -0
  314. package/dist/sites.d.ts +3 -0
  315. package/dist/sites.js +11 -0
  316. package/dist/sites.js.map +1 -0
  317. package/dist/skill-discovery.d.ts +47 -0
  318. package/dist/terminal.d.ts +8 -0
  319. package/dist/terminal.js +31 -0
  320. package/dist/terminal.js.map +1 -0
  321. package/dist/timeline/activity-rail.d.ts +11 -2
  322. package/dist/timeline/allowance-exhausted-row.d.ts +24 -0
  323. package/dist/timeline/compaction-copy.d.ts +10 -0
  324. package/dist/timeline/entrance.d.ts +13 -1
  325. package/dist/timeline/genie-loading.d.ts +34 -0
  326. package/dist/timeline/index.d.ts +6 -2
  327. package/dist/timeline/knowledge-receipt.d.ts +17 -0
  328. package/dist/timeline/platform-activity-row.d.ts +14 -0
  329. package/dist/timeline/presented-image.d.ts +6 -0
  330. package/dist/timeline/projection.d.ts +31 -2
  331. package/dist/timeline/registry.d.ts +2 -0
  332. package/dist/timeline/retained-image.d.ts +18 -0
  333. package/dist/timeline/rolling-activity.d.ts +11 -0
  334. package/dist/timeline/screenshot-lightbox.d.ts +16 -1
  335. package/dist/timeline/shared.d.ts +8 -3
  336. package/dist/timeline/startup-preference.d.ts +3 -0
  337. package/dist/timeline/startup-timings.d.ts +4 -0
  338. package/dist/timeline/tool-display-name.d.ts +2 -1
  339. package/dist/timeline/turn-summary.d.ts +32 -3
  340. package/dist/timeline/types.d.ts +141 -7
  341. package/dist/timeline-annotation-markers-GNDHTGBD.js +165 -0
  342. package/dist/timeline-annotation-markers-GNDHTGBD.js.map +1 -0
  343. package/dist/timeline-annotation-selection-DMLMM2TX.js +241 -0
  344. package/dist/timeline-annotation-selection-DMLMM2TX.js.map +1 -0
  345. package/dist/timeline-annotations-dialog-ELRDBHTS.js +273 -0
  346. package/dist/timeline-annotations-dialog-ELRDBHTS.js.map +1 -0
  347. package/dist/types/machines.d.ts +2 -2
  348. package/dist/usage/allowance-copy.d.ts +25 -0
  349. package/dist/usage/summary.d.ts +35 -0
  350. package/dist/usage.d.ts +13 -0
  351. package/dist/usage.js +1074 -0
  352. package/dist/usage.js.map +1 -0
  353. package/package.json +44 -4
  354. package/src/accounts.tsx +1072 -0
  355. package/src/approvals.ts +8 -1
  356. package/src/artifacts.ts +42 -0
  357. package/src/assets/README.md +8 -0
  358. package/src/assets/gmail.ico +0 -0
  359. package/src/assets/outlook.ico +0 -0
  360. package/src/capability-catalog-row.tsx +94 -0
  361. package/src/capability-logo-fallback.ts +22 -0
  362. package/src/client.ts +22 -13
  363. package/src/commands/registry.ts +18 -15
  364. package/src/commands/types.ts +2 -0
  365. package/src/components/anthropic-mark.tsx +10 -0
  366. package/src/components/approval-surface.tsx +171 -37
  367. package/src/components/artifacts/artifact-chrome.tsx +413 -0
  368. package/src/components/artifacts/artifact-sandbox.tsx +185 -0
  369. package/src/components/artifacts/artifact-surface.tsx +39 -19
  370. package/src/components/artifacts/chat-interactive-block.tsx +260 -0
  371. package/src/components/artifacts/deferred-chat-media.tsx +67 -0
  372. package/src/components/artifacts/document-editor.tsx +141 -16
  373. package/src/components/artifacts/editable-artifact-ui.tsx +4 -2
  374. package/src/components/artifacts/editable-artifact-view.tsx +187 -0
  375. package/src/components/artifacts/editable-artifact-workbench.tsx +5 -2
  376. package/src/components/artifacts/editable-document.tsx +37 -36
  377. package/src/components/artifacts/editable-presentation.tsx +36 -18
  378. package/src/components/artifacts/editable-spreadsheet.tsx +5 -2
  379. package/src/components/artifacts/index.ts +44 -0
  380. package/src/components/artifacts/inline-html-document.ts +24 -0
  381. package/src/components/artifacts/latest-task-runner.ts +49 -0
  382. package/src/components/artifacts/presentation-editor.tsx +323 -67
  383. package/src/components/artifacts/published-html-artifact-frame.tsx +338 -5
  384. package/src/components/artifacts/session-artifact-viewer.tsx +376 -0
  385. package/src/components/artifacts/site-view.tsx +116 -0
  386. package/src/components/artifacts/spreadsheet-canvas.ts +5 -10
  387. package/src/components/artifacts/spreadsheet-general-display.ts +43 -0
  388. package/src/components/artifacts/spreadsheet-grid.tsx +12 -13
  389. package/src/components/artifacts/visualization-assets/base.css +1015 -0
  390. package/src/components/artifacts/visualization-assets/helpers.html +349 -0
  391. package/src/components/artifacts/visualization-assets.gen.ts +3 -0
  392. package/src/components/browser-select-control.tsx +312 -0
  393. package/src/components/browser-viewer.tsx +1054 -314
  394. package/src/components/chat-composer.tsx +131 -85
  395. package/src/components/child-session-link.tsx +27 -0
  396. package/src/components/claude-mark.tsx +10 -0
  397. package/src/components/code-editor.tsx +13 -16
  398. package/src/components/composer-transcription-control.tsx +7 -5
  399. package/src/components/composer.tsx +376 -49
  400. package/src/components/computer-viewer.tsx +1041 -329
  401. package/src/components/copy-button.tsx +13 -5
  402. package/src/components/desktop-viewer.tsx +1 -1
  403. package/src/components/enrollment-consent.tsx +65 -7
  404. package/src/components/enrollment-device-flow.tsx +1 -1
  405. package/src/components/file-browser.tsx +158 -34
  406. package/src/components/fleet-tile.tsx +8 -16
  407. package/src/components/generated-video-player.tsx +4 -2
  408. package/src/components/grok-mark.tsx +33 -0
  409. package/src/components/human-input-form.tsx +455 -114
  410. package/src/components/human-input-surface.tsx +18 -7
  411. package/src/components/interaction-intervention-banner.tsx +2 -2
  412. package/src/components/machine-card.tsx +119 -4
  413. package/src/components/machine-dock-bar.tsx +1 -1
  414. package/src/components/machine-input-display.ts +23 -2
  415. package/src/components/machines/machine-detail.tsx +52 -2
  416. package/src/components/machines/machine-operation-policy.tsx +179 -0
  417. package/src/components/machines-dashboard.tsx +8 -17
  418. package/src/components/markdown-table-layout.ts +132 -0
  419. package/src/components/markdown.tsx +473 -44
  420. package/src/components/message-timeline.tsx +2498 -663
  421. package/src/components/model-mark.tsx +90 -0
  422. package/src/components/model-picker.tsx +15 -4
  423. package/src/components/model-policy-picker-menu.tsx +424 -0
  424. package/src/components/model-policy-picker.tsx +337 -381
  425. package/src/components/open-geni-chat.tsx +319 -0
  426. package/src/components/open-geni-links.tsx +179 -0
  427. package/src/components/openrouter-mark.tsx +13 -0
  428. package/src/components/pierre-diff.tsx +41 -16
  429. package/src/components/pierre-file.tsx +36 -11
  430. package/src/components/preview-loading.tsx +154 -0
  431. package/src/components/queue-item-content.ts +14 -0
  432. package/src/components/queue-surface-implementation.tsx +132 -42
  433. package/src/components/queue-surface-state.tsx +3 -1
  434. package/src/components/sandbox-files.tsx +267 -35
  435. package/src/components/sandbox-terminal.tsx +48 -11
  436. package/src/components/sandbox-workspace.tsx +337 -135
  437. package/src/components/session-capability-frame.tsx +192 -0
  438. package/src/components/session-chrome.tsx +1027 -328
  439. package/src/components/session-commands-panel.tsx +126 -0
  440. package/src/components/session-commands.tsx +18 -0
  441. package/src/components/session-connection-request.tsx +97 -0
  442. package/src/components/session-conversation.tsx +512 -0
  443. package/src/components/session-list.tsx +250 -0
  444. package/src/components/session-mcp-capability-card.tsx +680 -0
  445. package/src/components/session-status.tsx +1 -1
  446. package/src/components/timeline-anchor.tsx +119 -0
  447. package/src/components/timeline-annotation-chrome.tsx +196 -0
  448. package/src/components/timeline-annotation-layout.ts +268 -0
  449. package/src/components/timeline-annotation-markers.tsx +185 -0
  450. package/src/components/timeline-annotation-reveal-context.ts +9 -0
  451. package/src/components/timeline-annotation-selection.tsx +106 -54
  452. package/src/components/timeline-annotation-shared.ts +245 -0
  453. package/src/components/timeline-annotations-dialog.tsx +185 -128
  454. package/src/components/timeline-annotations.tsx +189 -45
  455. package/src/components/timeline-reading-progress.ts +80 -0
  456. package/src/components/timeline-search.tsx +168 -0
  457. package/src/components/timeline-settlement.ts +60 -0
  458. package/src/components/tip-follow.ts +70 -7
  459. package/src/components/tooltip.tsx +38 -5
  460. package/src/components/usage-limit-notice.tsx +179 -0
  461. package/src/components/usage-member-list.tsx +792 -0
  462. package/src/components/usage-meter.tsx +382 -0
  463. package/src/components/use-viewer-menu-dismiss.ts +33 -0
  464. package/src/components/user-message-body.tsx +194 -49
  465. package/src/components/workspace-dock.tsx +141 -175
  466. package/src/composer.ts +6 -1
  467. package/src/connect-account-identity.tsx +34 -0
  468. package/src/connect-accounts.tsx +233 -0
  469. package/src/connect-chooser.tsx +336 -0
  470. package/src/connect-panel.tsx +99 -0
  471. package/src/connect-setup.tsx +307 -0
  472. package/src/connect.ts +60 -0
  473. package/src/connection-assets.d.ts +4 -0
  474. package/src/connection-catalog.tsx +162 -0
  475. package/src/connection-discovery.tsx +235 -0
  476. package/src/connection-installed.tsx +51 -0
  477. package/src/connection-logo.tsx +37 -0
  478. package/src/connection-service-logo.tsx +55 -0
  479. package/src/connection-service-presentation.ts +46 -0
  480. package/src/connection-skeleton.tsx +16 -0
  481. package/src/connection-type-picker.tsx +36 -0
  482. package/src/conversation-timeline.ts +92 -0
  483. package/src/desktop.ts +24 -0
  484. package/src/device-authorization.tsx +98 -0
  485. package/src/diffs.ts +16 -0
  486. package/src/discovery-cache.ts +42 -0
  487. package/src/editor.ts +13 -0
  488. package/src/embedded-session-client.ts +104 -0
  489. package/src/file-node-visibility.ts +66 -0
  490. package/src/hooks/channel-cache.ts +104 -0
  491. package/src/hooks/internal.ts +3 -2
  492. package/src/hooks/latest-question.ts +164 -0
  493. package/src/hooks/shared-machines-list.ts +239 -0
  494. package/src/hooks/use-attached-browsers.ts +5 -1
  495. package/src/hooks/use-available-models.ts +20 -14
  496. package/src/hooks/use-browser-frame-stream.ts +25 -3
  497. package/src/hooks/use-browser-session.ts +271 -71
  498. package/src/hooks/use-browser-sessions.ts +7 -1
  499. package/src/hooks/use-channels.ts +32 -10
  500. package/src/hooks/use-codex-accounts.ts +78 -21
  501. package/src/hooks/use-composer.ts +1034 -320
  502. package/src/hooks/use-computer-frame-stream.ts +39 -16
  503. package/src/hooks/use-computer-input-posture.ts +94 -0
  504. package/src/hooks/use-computer-session.ts +110 -32
  505. package/src/hooks/use-computer-sessions.ts +8 -1
  506. package/src/hooks/use-connect.ts +23 -0
  507. package/src/hooks/use-desktop-stream.ts +19 -28
  508. package/src/hooks/use-file-attachments.ts +118 -10
  509. package/src/hooks/use-goal.ts +36 -9
  510. package/src/hooks/use-human-input.ts +31 -4
  511. package/src/hooks/use-last-started-turn-policy.ts +15 -13
  512. package/src/hooks/use-machine-chip.ts +23 -6
  513. package/src/hooks/use-machines.ts +104 -6
  514. package/src/hooks/use-sandbox-files.ts +253 -150
  515. package/src/hooks/use-sandbox-git.ts +58 -29
  516. package/src/hooks/use-session-background-commands.ts +67 -0
  517. package/src/hooks/use-session-capabilities.ts +21 -2
  518. package/src/hooks/use-session-events.ts +759 -433
  519. package/src/hooks/use-session-lineage.ts +28 -6
  520. package/src/hooks/use-session-mcp-approval-policy.ts +13 -5
  521. package/src/hooks/use-session.ts +75 -16
  522. package/src/hooks/use-slash-commands.ts +6 -11
  523. package/src/hooks/use-turn-queue.ts +113 -13
  524. package/src/hooks/use-usage.ts +100 -0
  525. package/src/hooks/use-voice-input.ts +12 -1
  526. package/src/hooks/use-workspace-capture.ts +3 -2
  527. package/src/hooks/use-workspace-edit.ts +23 -5
  528. package/src/hooks/use-workspace-sessions.ts +99 -11
  529. package/src/human-input.ts +12 -1
  530. package/src/identity-link-accounts.tsx +148 -0
  531. package/src/identity-link-consent.tsx +192 -0
  532. package/src/index.ts +157 -12
  533. package/src/interaction.ts +1 -0
  534. package/src/lib/error-message.ts +37 -0
  535. package/src/lib/format.ts +150 -14
  536. package/src/lib/host-theme.ts +511 -0
  537. package/src/lib/interaction-errors.ts +33 -0
  538. package/src/lib/menu-styles.ts +44 -0
  539. package/src/lib/pierre-diffs-loader.ts +53 -0
  540. package/src/lib/workbench-peers.ts +62 -0
  541. package/src/lib/xterm-theme.ts +23 -21
  542. package/src/machines.ts +9 -1
  543. package/src/mcp-connection-status.ts +35 -0
  544. package/src/model-policy.ts +223 -69
  545. package/src/older-history.ts +87 -0
  546. package/src/plugin-details.tsx +185 -0
  547. package/src/plugin-discovery.tsx +196 -0
  548. package/src/provider.tsx +55 -14
  549. package/src/realtime/dropdown-menu.tsx +19 -12
  550. package/src/realtime/realtime-control.tsx +274 -60
  551. package/src/session-auth-recommendation.ts +48 -0
  552. package/src/session-capability-policy.ts +83 -0
  553. package/src/session-context.ts +1 -0
  554. package/src/session-ui.ts +66 -2
  555. package/src/session.ts +18 -1
  556. package/src/sites-ui.tsx +402 -0
  557. package/src/sites.ts +3 -0
  558. package/src/skill-discovery.tsx +208 -0
  559. package/src/terminal.ts +26 -0
  560. package/src/timeline/activity-rail.tsx +171 -80
  561. package/src/timeline/allowance-exhausted-row.tsx +86 -0
  562. package/src/timeline/compaction-copy.ts +42 -0
  563. package/src/timeline/entrance.tsx +28 -3
  564. package/src/timeline/fleet-decision-projection.ts +2 -1
  565. package/src/timeline/fleet-decision-row.tsx +1 -0
  566. package/src/timeline/genie-loading.tsx +136 -0
  567. package/src/timeline/index.ts +11 -0
  568. package/src/timeline/knowledge-receipt.tsx +100 -0
  569. package/src/timeline/parsers.ts +2 -2
  570. package/src/timeline/platform-activity-row.tsx +216 -0
  571. package/src/timeline/presented-image.ts +49 -0
  572. package/src/timeline/projection.ts +1600 -84
  573. package/src/timeline/registry.ts +15 -3
  574. package/src/timeline/retained-image.ts +97 -0
  575. package/src/timeline/rolling-activity.tsx +92 -0
  576. package/src/timeline/screenshot-lightbox.tsx +115 -17
  577. package/src/timeline/shared.tsx +47 -15
  578. package/src/timeline/startup-preference.ts +33 -0
  579. package/src/timeline/startup-timings.tsx +145 -0
  580. package/src/timeline/tool-display-name.ts +8 -6
  581. package/src/timeline/tool-renderers.tsx +497 -128
  582. package/src/timeline/turn-summary.tsx +208 -44
  583. package/src/timeline/types.ts +186 -7
  584. package/src/types/machines.ts +10 -0
  585. package/src/usage/allowance-copy.ts +64 -0
  586. package/src/usage/summary.ts +93 -0
  587. package/src/usage.ts +50 -0
  588. package/styles/compiled.css +2098 -757
  589. package/styles/connect.css +284 -0
  590. package/styles/connect.d.ts +1 -0
  591. package/styles/effective-tokens.css +1 -0
  592. package/styles/index.css +201 -9
  593. package/styles/responsive.css +27 -1
  594. package/styles/session-capability.css +44 -0
  595. package/styles/tokens.css +135 -65
  596. package/dist/chunk-5UQPNLIO.js.map +0 -1
  597. package/dist/chunk-6KLX2RH5.js.map +0 -1
  598. package/dist/chunk-7WCLAEFV.js.map +0 -1
  599. package/dist/chunk-B6WPZSTT.js.map +0 -1
  600. package/dist/chunk-FK6VG4LL.js +0 -90
  601. package/dist/chunk-FK6VG4LL.js.map +0 -1
  602. package/dist/chunk-HA46CYQK.js.map +0 -1
  603. package/dist/chunk-HUIKIDM5.js.map +0 -1
  604. package/dist/chunk-IGC6A2ZI.js.map +0 -1
  605. package/dist/chunk-KCGXPP5V.js.map +0 -1
  606. package/dist/chunk-KZ73RL76.js.map +0 -1
  607. package/dist/chunk-LAKLF4PA.js +0 -141
  608. package/dist/chunk-LAKLF4PA.js.map +0 -1
  609. package/dist/chunk-LLOLUHVG.js.map +0 -1
  610. package/dist/chunk-MKPIAGVL.js +0 -465
  611. package/dist/chunk-MKPIAGVL.js.map +0 -1
  612. package/dist/chunk-PEI6RANI.js.map +0 -1
  613. package/dist/chunk-R3AZOAB5.js.map +0 -1
  614. package/dist/chunk-WSK7G5LE.js +0 -170
  615. package/dist/chunk-WSK7G5LE.js.map +0 -1
  616. package/dist/chunk-XBZWOM2K.js +0 -218
  617. package/dist/chunk-XBZWOM2K.js.map +0 -1
  618. package/dist/chunk-Y7OEDUJ5.js +0 -8810
  619. package/dist/chunk-Y7OEDUJ5.js.map +0 -1
  620. package/dist/fleet-decision-row-GGCAT4E4.js.map +0 -1
  621. package/dist/hooks/use-packs.d.ts +0 -32
  622. package/dist/interactive-workbench-viewers-TOHBXJEE.js +0 -15
  623. package/dist/queue-surface-implementation-2KVJTYXH.js.map +0 -1
  624. package/dist/timeline-annotation-selection-3L7LHGG2.js +0 -187
  625. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +0 -1
  626. package/dist/timeline-annotations-dialog-U7EVLR75.js +0 -196
  627. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +0 -1
  628. package/src/hooks/use-packs.ts +0 -157
  629. /package/dist/{interactive-workbench-viewers-TOHBXJEE.js.map → browser-viewer-GTR5XQ7X.js.map} +0 -0
@@ -1,12 +1,14 @@
1
1
  import type {
2
2
  HumanInputAnswer,
3
3
  HumanInputQuestion,
4
+ SkillRecord,
4
5
  SessionHumanInputRequest,
5
6
  SubmitHumanInputResponseRequest,
6
7
  } from "@opengeni/sdk";
7
8
  import { ChevronDownIcon, ChevronUpIcon, MessageCircleQuestionIcon } from "lucide-react";
8
9
  import { useEffect, useId, useRef, useState, type FormEvent, type ReactNode } from "react";
9
10
  import { cn } from "../lib/cn";
11
+ import { useErrorMessage } from "../lib/error-message";
10
12
 
11
13
  export type HumanInputAnswerDraft = {
12
14
  values: string[];
@@ -14,6 +16,8 @@ export type HumanInputAnswerDraft = {
14
16
  otherSelected: boolean;
15
17
  };
16
18
 
19
+ class SkillReviewVerificationError extends Error {}
20
+
17
21
  export type HumanInputFormMessages = {
18
22
  title: string;
19
23
  description: string;
@@ -40,7 +44,7 @@ export const defaultHumanInputFormMessages: HumanInputFormMessages = {
40
44
  title: "Input required",
41
45
  /** Multi-question chrome has no default subtitle; hosts may still override. */
42
46
  description: "",
43
- submit: "Continue",
47
+ submit: "Send answers",
44
48
  skip: "Skip",
45
49
  submitting: "Submitting…",
46
50
  other: "Other",
@@ -64,6 +68,10 @@ export const defaultHumanInputFormMessages: HumanInputFormMessages = {
64
68
  };
65
69
 
66
70
  export type HumanInputFormProps = {
71
+ /** Scoped immutable revision reader. Saving a Skill fails closed without its full preview. */
72
+ loadSkillReview?:
73
+ | ((reference: NonNullable<HumanInputQuestion["skillReview"]>) => Promise<SkillRecord>)
74
+ | undefined;
67
75
  request: Pick<SessionHumanInputRequest, "id" | "questions" | "allowSkip" | "expiresAt">;
68
76
  onSubmit: (response: SubmitHumanInputResponseRequest) => void | Promise<void>;
69
77
  submitting?: boolean | undefined;
@@ -78,6 +86,12 @@ export type HumanInputFormProps = {
78
86
  autoFocus?: boolean | undefined;
79
87
  /** Start collapsed to a compact bar (drafts still retained). */
80
88
  defaultCollapsed?: boolean | undefined;
89
+ /**
90
+ * Answer a yes/no decision (one required choice between an accept and a
91
+ * decline option, without Other) with two buttons instead of a radio list.
92
+ * Off by default; embedded conversations turn it on.
93
+ */
94
+ decisionButtons?: boolean | undefined;
81
95
  className?: string | undefined;
82
96
  };
83
97
 
@@ -87,9 +101,18 @@ export type HumanInputFormProps = {
87
101
  * options, sticky ask/submit chrome. Hosts can replace title/description or
88
102
  * use `useHumanInputRequests` headlessly.
89
103
  */
90
- export function HumanInputForm({
104
+ export function HumanInputForm(props: HumanInputFormProps) {
105
+ // The pending-request read model is refreshed after session events and
106
+ // returns newly allocated question arrays for the same durable request.
107
+ // Key the state owner by that request's lifecycle identity so reconciliation
108
+ // cannot erase an answer that the operator is still typing.
109
+ return <HumanInputRequestForm key={props.request.id} {...props} />;
110
+ }
111
+
112
+ function HumanInputRequestForm({
91
113
  request,
92
114
  onSubmit,
115
+ loadSkillReview,
93
116
  submitting = false,
94
117
  error,
95
118
  title,
@@ -100,10 +123,14 @@ export function HumanInputForm({
100
123
  messages: messageOverrides,
101
124
  autoFocus = true,
102
125
  defaultCollapsed = false,
126
+ decisionButtons = false,
103
127
  className,
104
128
  }: HumanInputFormProps) {
129
+ const formatError = useErrorMessage();
105
130
  const messages = { ...defaultHumanInputFormMessages, ...messageOverrides };
106
131
  const singleQuestion = request.questions.length === 1 ? request.questions[0]! : null;
132
+ const decision = decisionButtons ? binaryDecision(request.questions) : null;
133
+ const [decisionChoice, setDecisionChoice] = useState<string | null>(null);
107
134
  const resolvedTitle =
108
135
  title === undefined
109
136
  ? singleQuestion
@@ -134,16 +161,131 @@ export function HumanInputForm({
134
161
  const submissionInFlight = useRef(false);
135
162
  const submissionGeneration = useRef(0);
136
163
  const busy = submitting || submittingInternally;
137
-
164
+ const reviewIdentity = JSON.stringify(
165
+ request.questions
166
+ .filter((question) => question.skillReview)
167
+ .map((question) => ({
168
+ id: question.id,
169
+ reference: {
170
+ sourceOperationId: question.skillReview!.sourceOperationId,
171
+ skillId: question.skillReview!.skillId,
172
+ revisionId: question.skillReview!.revisionId,
173
+ expectedRevisionId: question.skillReview!.expectedRevisionId,
174
+ expectedScopeVersion: question.skillReview!.expectedScopeVersion,
175
+ ...(question.skillReview!.removalOperationId
176
+ ? { removalOperationId: question.skillReview!.removalOperationId }
177
+ : {}),
178
+ },
179
+ })),
180
+ );
181
+ const [reviewReload, setReviewReload] = useState(0);
182
+ const [reviews, setReviews] = useState<{
183
+ loader: typeof loadSkillReview;
184
+ identity: string;
185
+ records: Record<string, SkillRecord>;
186
+ error: string | null;
187
+ } | null>(null);
138
188
  useEffect(() => {
139
- submissionGeneration.current += 1;
140
- submissionInFlight.current = false;
141
- setDrafts(initialDrafts(request.questions));
142
- setValidationErrors({});
143
- setSubmissionError(null);
144
- setSubmittingInternally(false);
145
- setCollapsed(defaultCollapsed);
146
- }, [request.id, request.questions, defaultCollapsed]);
189
+ let current = true;
190
+ const questions = JSON.parse(reviewIdentity) as Array<{
191
+ id: string;
192
+ reference: NonNullable<HumanInputQuestion["skillReview"]>;
193
+ }>;
194
+ if (!questions.length) return;
195
+ setReviews(null);
196
+ void Promise.all(
197
+ questions.map(async (question) => {
198
+ if (!loadSkillReview)
199
+ throw new Error(
200
+ "This client cannot preview Skill files. Ask your administrator for a review-capable client.",
201
+ );
202
+ const reference = question.reference;
203
+ const record = await loadSkillReview(reference);
204
+ if (
205
+ record.id !== reference.skillId ||
206
+ record.revisionId !== reference.revisionId ||
207
+ (record.removalOperationId ?? undefined) !== reference.removalOperationId ||
208
+ !record.files.some((file) => file.path === "SKILL.md")
209
+ ) {
210
+ throw new SkillReviewVerificationError(
211
+ "The requested Skill revision could not be verified.",
212
+ );
213
+ }
214
+ return [question.id, record] as const;
215
+ }),
216
+ )
217
+ .then((records) => {
218
+ if (current)
219
+ setReviews({
220
+ loader: loadSkillReview,
221
+ identity: reviewIdentity,
222
+ records: Object.fromEntries(records),
223
+ error: null,
224
+ });
225
+ })
226
+ .catch((cause) => {
227
+ if (current)
228
+ setReviews({
229
+ loader: loadSkillReview,
230
+ identity: reviewIdentity,
231
+ records: {},
232
+ error: formatError(
233
+ cause,
234
+ cause instanceof SkillReviewVerificationError
235
+ ? cause.message
236
+ : !loadSkillReview
237
+ ? "This client cannot preview Skill files. Ask your administrator for a review-capable client."
238
+ : undefined,
239
+ ),
240
+ });
241
+ });
242
+ return () => {
243
+ current = false;
244
+ };
245
+ }, [loadSkillReview, reviewIdentity, reviewReload, formatError]);
246
+ const visibleReviews =
247
+ reviews?.loader === loadSkillReview && reviews?.identity === reviewIdentity ? reviews : null;
248
+ const preview = (question: HumanInputQuestion) => {
249
+ if (!question.skillReview) return null;
250
+ const record = visibleReviews?.records[question.id];
251
+ return (
252
+ <div className="mb-3 min-w-0 space-y-2" data-skill-review="">
253
+ {record ? (
254
+ <>
255
+ <p className="text-og-sm">
256
+ {record.title ?? "Skill"} · {record.scope} · {record.files.length} files
257
+ </p>
258
+ <p className="text-og-xs text-og-fg-muted">
259
+ {question.skillReview.removalOperationId
260
+ ? "Permanently deletes this Skill and all stored revisions. This cannot be undone; conversations remain unchanged."
261
+ : "Saving activates these exact files. No additional review is required."}
262
+ </p>
263
+ {record.files.map((file) => (
264
+ <details key={file.path} open={file.path === "SKILL.md"}>
265
+ <summary className="cursor-pointer break-all text-og-sm">{file.path}</summary>
266
+ <pre className="max-h-64 overflow-auto whitespace-pre-wrap break-words rounded-og-md bg-og-surface-1 p-2 text-og-xs">
267
+ {file.content}
268
+ </pre>
269
+ </details>
270
+ ))}
271
+ </>
272
+ ) : visibleReviews?.error ? (
273
+ <>
274
+ <p role="alert" className="text-og-sm">
275
+ {visibleReviews.error}
276
+ </p>
277
+ <button type="button" onClick={() => setReviewReload((value) => value + 1)}>
278
+ Retry preview
279
+ </button>
280
+ </>
281
+ ) : (
282
+ <p role="status" className="text-og-sm">
283
+ Loading the exact Skill files…
284
+ </p>
285
+ )}
286
+ </div>
287
+ );
288
+ };
147
289
 
148
290
  useEffect(() => {
149
291
  if (collapsed) {
@@ -199,7 +341,7 @@ export function HumanInputForm({
199
341
  await onSubmit(response);
200
342
  } catch (cause) {
201
343
  if (generation === submissionGeneration.current) {
202
- setSubmissionError(cause instanceof Error ? cause.message : String(cause));
344
+ setSubmissionError(formatError(cause));
203
345
  }
204
346
  } finally {
205
347
  if (generation === submissionGeneration.current) {
@@ -234,6 +376,22 @@ export function HumanInputForm({
234
376
  }
235
377
  return;
236
378
  }
379
+ const unreviewedSave = request.questions.find(
380
+ (question) =>
381
+ question.skillReview &&
382
+ !visibleReviews?.records[question.id] &&
383
+ result.answers.some(
384
+ (answer) => answer.questionId === question.id && answer.values.includes("save"),
385
+ ),
386
+ );
387
+ if (unreviewedSave) {
388
+ setValidationErrors({
389
+ [unreviewedSave.id]: unreviewedSave.skillReview?.removalOperationId
390
+ ? "Load the exact Skill proposal before approving permanent deletion."
391
+ : "Load the exact Skill files before saving.",
392
+ });
393
+ return;
394
+ }
237
395
  await submitResponse({ outcome: "answered", answers: result.answers });
238
396
  };
239
397
 
@@ -309,7 +467,7 @@ export function HumanInputForm({
309
467
  <div className="flex flex-wrap items-baseline gap-x-2 gap-y-0.5">
310
468
  <h2 id={titleId} className="text-og-md font-semibold text-og-fg">
311
469
  {resolvedTitle}
312
- {singleQuestion?.required && !request.allowSkip ? (
470
+ {singleQuestion?.required && !request.allowSkip && !decision ? (
313
471
  <span aria-hidden className="ml-1 text-og-status-failed">
314
472
  *
315
473
  </span>
@@ -356,104 +514,245 @@ export function HumanInputForm({
356
514
  </div>
357
515
  </header>
358
516
 
359
- <div className="relative flex min-h-0 flex-1 flex-col overflow-hidden">
360
- <div
361
- ref={scrollRef}
362
- className="min-h-0 flex-1 overflow-y-auto overscroll-contain [scrollbar-gutter:stable]"
363
- >
364
- <fieldset disabled={busy} className="min-w-0 space-y-4 px-4 py-3">
365
- {request.questions.map((question, index) => {
366
- if (singleQuestion) {
367
- // Title already carries the question; only render the control + help extras.
368
- return (
369
- <div key={question.id} data-human-input-question={question.id}>
370
- <QuestionControls
371
- question={question}
372
- draft={drafts[question.id] ?? emptyDraft()}
373
- fieldId={`${formId}-${index}`}
374
- error={validationErrors[question.id]}
375
- messages={messages}
376
- autoFocus={autoFocus}
377
- firstOption
378
- showPromptChrome={false}
379
- allowSkip={request.allowSkip}
380
- busy={busy}
381
- onUpdate={(apply) => update(question.id, apply)}
382
- />
383
- </div>
384
- );
385
- }
517
+ {decision ? (
518
+ <>
519
+ {decision.question.helpText ||
520
+ decision.accept.description ||
521
+ decision.decline.description ? (
522
+ <div className="shrink-0 space-y-1 px-4 pt-3 text-og-sm text-og-fg-muted">
523
+ {decision.question.label && decision.question.helpText ? (
524
+ <p>{decision.question.helpText}</p>
525
+ ) : null}
526
+ {[decision.accept, decision.decline].map((option) =>
527
+ option.description ? (
528
+ <p key={option.id}>
529
+ <span className="font-medium text-og-fg">{option.label}</span>
530
+ {" · "}
531
+ {option.description}
532
+ </p>
533
+ ) : null,
534
+ )}
535
+ </div>
536
+ ) : null}
537
+ {(error ?? submissionError) ? (
538
+ <p role="alert" className="shrink-0 px-4 pt-3 text-og-sm text-og-status-failed">
539
+ {error ?? submissionError}
540
+ </p>
541
+ ) : null}
542
+ <footer
543
+ className="flex shrink-0 flex-wrap items-center justify-end gap-2 px-4 py-3"
544
+ data-human-input-decision=""
545
+ >
546
+ {request.allowSkip ? (
547
+ <button
548
+ type="button"
549
+ disabled={busy}
550
+ onClick={() => void submitResponse({ outcome: "skipped" })}
551
+ className="mr-auto inline-flex min-h-9 items-center rounded-og-md px-2 py-1.5 text-og-sm font-medium text-og-fg-muted transition-colors hover:bg-og-hover hover:text-og-fg disabled:opacity-50 pointer-coarse:min-h-11"
552
+ >
553
+ {resolvedSkipLabel}
554
+ </button>
555
+ ) : null}
556
+ {[decision.decline, decision.accept].map((option) => {
557
+ const primary = option === decision.accept;
386
558
  return (
387
- <div
388
- key={question.id}
389
- data-human-input-question={question.id}
390
- className="flex flex-col gap-1.5"
559
+ <button
560
+ key={option.id}
561
+ type="button"
562
+ disabled={busy}
563
+ ref={
564
+ !primary && autoFocus
565
+ ? (node) => {
566
+ if (node && !node.dataset.ogFocused) {
567
+ node.dataset.ogFocused = "";
568
+ node.focus({ preventScroll: true });
569
+ }
570
+ }
571
+ : undefined
572
+ }
573
+ data-human-input-choice={option.id}
574
+ onClick={() => {
575
+ setDecisionChoice(option.id);
576
+ void submitResponse({
577
+ outcome: "answered",
578
+ answers: [{ questionId: decision.question.id, values: [option.id] }],
579
+ });
580
+ }}
581
+ className={cn(
582
+ "inline-flex min-h-9 items-center rounded-og-md border px-3.5 py-1.5 text-og-sm font-medium transition disabled:opacity-50 pointer-coarse:min-h-11",
583
+ primary
584
+ ? "border-og-primary-border bg-og-primary text-og-primary-fg hover:bg-og-primary-hover"
585
+ : "border-og-border bg-og-surface-1 text-og-fg hover:bg-og-surface-2",
586
+ )}
391
587
  >
392
- <QuestionControls
393
- question={question}
394
- draft={drafts[question.id] ?? emptyDraft()}
395
- fieldId={`${formId}-${index}`}
396
- error={validationErrors[question.id]}
397
- messages={messages}
398
- autoFocus={autoFocus && index === 0}
399
- firstOption={index === 0}
400
- showPromptChrome
401
- allowSkip={request.allowSkip}
402
- busy={busy}
403
- onUpdate={(apply) => update(question.id, apply)}
404
- />
405
- </div>
588
+ {busy && decisionChoice === option.id ? messages.submitting : option.label}
589
+ </button>
406
590
  );
407
591
  })}
408
- </fieldset>
409
- </div>
410
- {overflowBelow ? (
411
- <div
412
- className="pointer-events-none absolute inset-x-0 bottom-0 z-[1] flex flex-col items-center"
413
- aria-hidden="true"
414
- >
415
- <div className="h-10 w-full bg-gradient-to-t from-og-surface-1 via-og-surface-1/85 to-transparent" />
416
- <span className="-mt-5 mb-1 rounded-og-full bg-og-surface-1 px-2.5 py-0.5 text-og-xs font-medium text-og-fg-muted shadow-og-sm ring-1 ring-og-border/60">
417
- {messages.moreBelow}
418
- </span>
592
+ </footer>
593
+ </>
594
+ ) : (
595
+ <>
596
+ <div className="relative flex min-h-0 flex-1 flex-col overflow-hidden">
597
+ <div
598
+ ref={scrollRef}
599
+ className="min-h-0 flex-1 overflow-y-auto overscroll-contain [scrollbar-gutter:stable]"
600
+ >
601
+ <fieldset disabled={busy} className="min-w-0 space-y-4 px-4 py-3">
602
+ {request.questions.map((question, index) => {
603
+ if (singleQuestion) {
604
+ // Title already carries the question; only render the control + help extras.
605
+ return (
606
+ <div key={question.id} data-human-input-question={question.id}>
607
+ {preview(question)}
608
+ <QuestionControls
609
+ question={question}
610
+ questionNumber={null}
611
+ labelledBy={titleId}
612
+ draft={drafts[question.id] ?? emptyDraft()}
613
+ fieldId={`${formId}-${index}`}
614
+ error={validationErrors[question.id]}
615
+ messages={messages}
616
+ autoFocus={autoFocus}
617
+ firstOption
618
+ showPromptChrome={false}
619
+ allowSkip={request.allowSkip}
620
+ busy={busy}
621
+ onUpdate={(apply) => update(question.id, apply)}
622
+ />
623
+ </div>
624
+ );
625
+ }
626
+ return (
627
+ <div
628
+ key={question.id}
629
+ data-human-input-question={question.id}
630
+ className="flex flex-col gap-1.5"
631
+ >
632
+ {preview(question)}
633
+ <QuestionControls
634
+ question={question}
635
+ questionNumber={index + 1}
636
+ labelledBy={undefined}
637
+ draft={drafts[question.id] ?? emptyDraft()}
638
+ fieldId={`${formId}-${index}`}
639
+ error={validationErrors[question.id]}
640
+ messages={messages}
641
+ autoFocus={autoFocus && index === 0}
642
+ firstOption={index === 0}
643
+ showPromptChrome
644
+ allowSkip={request.allowSkip}
645
+ busy={busy}
646
+ onUpdate={(apply) => update(question.id, apply)}
647
+ />
648
+ </div>
649
+ );
650
+ })}
651
+ </fieldset>
652
+ </div>
653
+ {overflowBelow ? (
654
+ <div
655
+ className="pointer-events-none absolute inset-x-0 bottom-0 z-[1] flex flex-col items-center"
656
+ aria-hidden="true"
657
+ >
658
+ <div className="h-10 w-full bg-gradient-to-t from-og-surface-1 via-og-surface-1/85 to-transparent" />
659
+ <span className="-mt-5 mb-1 rounded-og-full bg-og-surface-1 px-2.5 py-0.5 text-og-xs font-medium text-og-fg-muted shadow-og-sm ring-1 ring-og-border/60">
660
+ {messages.moreBelow}
661
+ </span>
662
+ </div>
663
+ ) : null}
419
664
  </div>
420
- ) : null}
421
- </div>
422
665
 
423
- {(error ?? submissionError) ? (
424
- <p
425
- role="alert"
426
- className="relative z-10 shrink-0 px-4 pb-1 text-og-sm text-og-status-failed"
427
- >
428
- {error ?? submissionError}
429
- </p>
430
- ) : null}
666
+ {(error ?? submissionError) ? (
667
+ <p
668
+ role="alert"
669
+ className="relative z-10 shrink-0 px-4 pb-1 text-og-sm text-og-status-failed"
670
+ >
671
+ {error ?? submissionError}
672
+ </p>
673
+ ) : null}
431
674
 
432
- <footer className="relative z-10 flex shrink-0 items-center justify-end gap-2 border-t border-og-status-waiting/20 bg-og-surface-1/95 px-4 py-3 backdrop-blur-[2px]">
433
- {request.allowSkip ? (
434
- <button
435
- type="button"
436
- disabled={busy}
437
- onClick={() => void submitResponse({ outcome: "skipped" })}
438
- className="inline-flex min-h-9 items-center rounded-og-md border border-og-border px-3 py-1.5 text-og-sm font-medium text-og-fg-muted transition-colors hover:bg-og-surface-1 hover:text-og-fg disabled:opacity-50"
439
- >
440
- {resolvedSkipLabel}
441
- </button>
442
- ) : null}
443
- <button
444
- type="submit"
445
- disabled={busy}
446
- className="inline-flex min-h-9 items-center rounded-og-md bg-og-accent px-3 py-1.5 text-og-sm font-medium text-og-accent-fg transition-colors hover:bg-og-accent-strong disabled:opacity-50"
447
- >
448
- {busy ? messages.submitting : resolvedSubmitLabel}
449
- </button>
450
- </footer>
675
+ <footer className="relative z-10 flex shrink-0 items-center justify-end gap-2 border-t border-og-status-waiting/20 bg-og-surface-1/95 px-4 py-3 backdrop-blur-[2px]">
676
+ {request.allowSkip ? (
677
+ <button
678
+ type="button"
679
+ disabled={busy}
680
+ onClick={() => void submitResponse({ outcome: "skipped" })}
681
+ className="inline-flex min-h-9 items-center rounded-og-md border border-og-border px-3 py-1.5 text-og-sm font-medium text-og-fg-muted transition-colors hover:bg-og-surface-1 hover:text-og-fg disabled:opacity-50"
682
+ >
683
+ {resolvedSkipLabel}
684
+ </button>
685
+ ) : null}
686
+ <button
687
+ type="submit"
688
+ disabled={busy}
689
+ className="inline-flex min-h-9 items-center rounded-og-md border border-og-primary-border bg-og-primary text-og-primary-fg px-3 py-1.5 text-og-sm font-medium transition hover:bg-og-primary-hover disabled:opacity-50"
690
+ >
691
+ {busy ? messages.submitting : resolvedSubmitLabel}
692
+ </button>
693
+ </footer>
694
+ </>
695
+ )}
451
696
  </form>
452
697
  );
453
698
  }
454
699
 
700
+ // Unambiguous approve/decline verbs only: "Continue with A" vs "Keep B" is a
701
+ // real choice between two options, not a yes/no decision.
702
+ const ACCEPT_WORDS = new Set(["approve", "approved", "yes", "confirm", "allow", "accept"]);
703
+ const DECLINE_WORDS = new Set(["cancel", "decline", "reject", "deny", "no", "don't", "dont"]);
704
+
705
+ function firstWord(label: string): string {
706
+ return (
707
+ label
708
+ .trim()
709
+ .toLowerCase()
710
+ .replace(/[’]/g, "'")
711
+ .split(/[\s,.:;!?()]+/)[0] ?? ""
712
+ );
713
+ }
714
+
715
+ /**
716
+ * A yes/no decision (an approval asked as a question): one required
717
+ * single-choice question with exactly an accept and a decline option. It is
718
+ * answered with two buttons; an "Other" free-text answer has no meaning for it,
719
+ * and the composer still accepts a different instruction.
720
+ */
721
+ export function binaryDecision(questions: readonly HumanInputQuestion[]): {
722
+ question: HumanInputQuestion;
723
+ accept: HumanInputQuestion["options"][number];
724
+ decline: HumanInputQuestion["options"][number];
725
+ } | null {
726
+ if (questions.length !== 1) return null;
727
+ const question = questions[0]!;
728
+ if (
729
+ question.kind !== "single_select" ||
730
+ question.skillReview ||
731
+ // The runtime marks every choice question `allowOther`, so it is not a
732
+ // signal here; a different answer goes through the composer instead.
733
+ !question.required ||
734
+ question.options.length !== 2
735
+ )
736
+ return null;
737
+ const [first, second] = question.options as [
738
+ HumanInputQuestion["options"][number],
739
+ HumanInputQuestion["options"][number],
740
+ ];
741
+ const kind = (option: HumanInputQuestion["options"][number]) => {
742
+ const word = firstWord(option.label);
743
+ return ACCEPT_WORDS.has(word) ? "accept" : DECLINE_WORDS.has(word) ? "decline" : null;
744
+ };
745
+ const a = kind(first);
746
+ const b = kind(second);
747
+ if (a === "accept" && b === "decline") return { question, accept: first, decline: second };
748
+ if (a === "decline" && b === "accept") return { question, accept: second, decline: first };
749
+ return null;
750
+ }
751
+
455
752
  function QuestionControls({
456
753
  question,
754
+ questionNumber,
755
+ labelledBy,
457
756
  draft,
458
757
  fieldId,
459
758
  error,
@@ -466,6 +765,8 @@ function QuestionControls({
466
765
  onUpdate,
467
766
  }: {
468
767
  question: HumanInputQuestion;
768
+ questionNumber: number | null;
769
+ labelledBy: string | undefined;
469
770
  draft: HumanInputAnswerDraft;
470
771
  fieldId: string;
471
772
  error: string | undefined;
@@ -479,6 +780,13 @@ function QuestionControls({
479
780
  }) {
480
781
  const errorId = `${fieldId}-error`;
481
782
  const helpId = `${fieldId}-help`;
783
+ const labelId = `${fieldId}-label`;
784
+ const promptId = `${fieldId}-prompt`;
785
+ const otherChoiceId = `${fieldId}-other-choice`;
786
+ const otherLabelId = `${fieldId}-other-label`;
787
+ const otherTextId = `${fieldId}-other-text`;
788
+ const visibleLabel = question.label ?? question.prompt;
789
+ const controlLabelId = showPromptChrome ? labelId : labelledBy;
482
790
  const hint =
483
791
  question.kind === "multi_select"
484
792
  ? messages.selectionHint(
@@ -488,22 +796,33 @@ function QuestionControls({
488
796
  : null;
489
797
  const describedBy =
490
798
  [
799
+ question.label && showPromptChrome ? promptId : null,
491
800
  question.helpText && showPromptChrome ? helpId : null,
492
801
  hint ? `${fieldId}-hint` : null,
493
802
  error ? errorId : null,
494
803
  ]
495
804
  .filter(Boolean)
496
805
  .join(" ") || undefined;
806
+ const selectOtherDraft = (current: HumanInputAnswerDraft): HumanInputAnswerDraft => ({
807
+ ...current,
808
+ otherSelected: true,
809
+ ...(question.kind === "single_select" ? { values: [] } : {}),
810
+ });
497
811
  return (
498
812
  <>
499
813
  {showPromptChrome ? (
500
814
  <>
501
815
  <div className="flex flex-wrap items-baseline gap-x-2">
502
816
  <label
817
+ id={labelId}
503
818
  htmlFor={question.kind === "text" ? fieldId : undefined}
504
819
  className="text-og-sm font-medium text-og-fg"
505
820
  >
506
- {question.label ?? question.prompt}
821
+ {questionNumber === null ? null : (
822
+ <span className="mr-1.5 tabular-nums text-og-fg-muted">{questionNumber}.</span>
823
+ )}
824
+ {questionNumber === null ? null : " "}
825
+ {visibleLabel}
507
826
  {question.required && !allowSkip ? (
508
827
  <span aria-hidden className="ml-1 text-og-status-failed">
509
828
  *
@@ -515,7 +834,11 @@ function QuestionControls({
515
834
  ) : null}
516
835
  </label>
517
836
  </div>
518
- {question.label ? <p className="text-og-sm text-og-fg-muted">{question.prompt}</p> : null}
837
+ {question.label ? (
838
+ <p id={promptId} className="text-og-sm text-og-fg-muted">
839
+ {question.prompt}
840
+ </p>
841
+ ) : null}
519
842
  {question.helpText ? (
520
843
  <p id={helpId} className="text-og-xs text-og-fg-subtle">
521
844
  {question.helpText}
@@ -556,6 +879,7 @@ function QuestionControls({
556
879
  }))
557
880
  }
558
881
  aria-invalid={Boolean(error)}
882
+ aria-labelledby={controlLabelId}
559
883
  aria-describedby={describedBy}
560
884
  autoFocus={autoFocus}
561
885
  rows={2}
@@ -564,6 +888,7 @@ function QuestionControls({
564
888
  ) : (
565
889
  <div
566
890
  role={question.kind === "single_select" ? "radiogroup" : "group"}
891
+ aria-labelledby={controlLabelId}
567
892
  aria-describedby={describedBy}
568
893
  className="flex flex-col gap-0.5"
569
894
  >
@@ -613,8 +938,8 @@ function QuestionControls({
613
938
  </label>
614
939
  );
615
940
  })}
616
- {question.allowOther ? (
617
- <label
941
+ {!question.skillReview ? (
942
+ <div
618
943
  className={cn(
619
944
  "flex items-start gap-2.5 rounded-og-md px-2.5 py-2 transition-colors",
620
945
  draft.otherSelected
@@ -623,8 +948,10 @@ function QuestionControls({
623
948
  )}
624
949
  >
625
950
  <input
951
+ id={otherChoiceId}
626
952
  type={question.kind === "single_select" ? "radio" : "checkbox"}
627
953
  name={question.kind === "single_select" ? fieldId : undefined}
954
+ aria-labelledby={otherLabelId}
628
955
  checked={draft.otherSelected}
629
956
  autoFocus={autoFocus && firstOption && question.options.length === 0}
630
957
  onChange={(event) =>
@@ -639,22 +966,35 @@ function QuestionControls({
639
966
  className="mt-2 accent-og-accent"
640
967
  />
641
968
  <span className="min-w-0 flex-1">
642
- <span className="block text-og-sm font-medium">{messages.other}</span>
969
+ <label
970
+ id={otherLabelId}
971
+ htmlFor={otherChoiceId}
972
+ className="block text-og-sm font-medium"
973
+ >
974
+ {messages.other}
975
+ </label>
976
+ <label htmlFor={otherTextId} className="sr-only">
977
+ {messages.other} answer for {visibleLabel}
978
+ </label>
643
979
  <input
980
+ id={otherTextId}
644
981
  type="text"
645
982
  value={draft.other}
646
- disabled={!draft.otherSelected || busy}
983
+ disabled={busy}
647
984
  placeholder="Type a value…"
648
- onChange={(event) =>
985
+ onClick={() => onUpdate(selectOtherDraft)}
986
+ onFocus={() => onUpdate(selectOtherDraft)}
987
+ onChange={(event) => {
988
+ const other = event.target.value;
649
989
  onUpdate((current) => ({
650
- ...current,
651
- other: event.target.value,
652
- }))
653
- }
990
+ ...selectOtherDraft(current),
991
+ other,
992
+ }));
993
+ }}
654
994
  className="mt-1.5 w-full rounded-og-sm border border-og-border bg-og-surface-1 px-2 py-1.5 text-og-sm text-og-fg outline-hidden focus:border-og-accent disabled:opacity-50"
655
995
  />
656
996
  </span>
657
- </label>
997
+ </div>
658
998
  ) : null}
659
999
  </div>
660
1000
  )}
@@ -678,12 +1018,13 @@ export function answersFromDrafts(
678
1018
  for (const question of questions) {
679
1019
  const draft = drafts[question.id] ?? emptyDraft();
680
1020
  const values = question.kind === "text" ? draft.values.filter(Boolean) : draft.values;
681
- const other = draft.otherSelected ? draft.other.trim() : "";
682
- const supplied = values.length + (other ? 1 : 0);
1021
+ const other = draft.otherSelected ? draft.other : "";
1022
+ const hasOther = Boolean(other.trim());
1023
+ const supplied = values.length + (hasOther ? 1 : 0);
683
1024
 
684
1025
  // Other-selected-but-empty must win over generic "required" — otherwise the
685
1026
  // user sees the wrong diagnosis next to a clearly selected control.
686
- if (question.kind !== "text" && draft.otherSelected && !other) {
1027
+ if (question.kind !== "text" && draft.otherSelected && !hasOther) {
687
1028
  errors[question.id] = messages.otherRequired;
688
1029
  continue;
689
1030
  }
@@ -708,7 +1049,7 @@ export function answersFromDrafts(
708
1049
  answers.push({
709
1050
  questionId: question.id,
710
1051
  values,
711
- ...(other ? { other } : {}),
1052
+ ...(hasOther ? { other } : {}),
712
1053
  });
713
1054
  }
714
1055
  }