@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
@@ -2,111 +2,120 @@ import {
2
2
  buildTimeline,
3
3
  groupTimeline,
4
4
  sessionStatusFromEvents
5
- } from "./chunk-6KLX2RH5.js";
5
+ } from "./chunk-H2NJQORO.js";
6
6
  import {
7
7
  useDebouncedCallback,
8
8
  useMutationRunner,
9
9
  usePageLiveActivity,
10
10
  usePolledValue,
11
11
  useSessionEventTrigger
12
- } from "./chunk-KZ73RL76.js";
12
+ } from "./chunk-YQEAA6LT.js";
13
+ import {
14
+ normalizeError,
15
+ useErrorMessage
16
+ } from "./chunk-UGLEYEMP.js";
13
17
  import {
14
18
  useEmbeddedFileAttachments,
15
- useEmbeddedGoal,
16
19
  useEmbeddedHumanInputSession,
17
20
  useEmbeddedSession,
18
- useEmbeddedSessionLineage,
19
- useEmbeddedSessionMcpApprovalPolicy,
20
21
  useEmbeddedSessionRead
21
- } from "./chunk-C5ZPSSS5.js";
22
+ } from "./chunk-NEXLLTP4.js";
22
23
 
23
- // src/hooks/use-session.ts
24
- import { useCallback, useEffect, useMemo, useState } from "react";
25
- function isTitleEvent(event) {
26
- return event.type === "session.title_set";
24
+ // src/human-input.ts
25
+ function isActionableHumanInputRequest(request, nowMs = Date.now()) {
26
+ if (request.status !== "pending") return false;
27
+ if (!request.expiresAt) return true;
28
+ const deadline = Date.parse(request.expiresAt);
29
+ return Number.isFinite(deadline) && deadline > nowMs;
27
30
  }
28
- function useSession(sessionId, options = {}) {
29
- const { client, workspaceId, workspaceControlEvent, registerSessionReconciler } = useEmbeddedSessionRead(options);
30
- const enabled = (options.enabled ?? true) && Boolean(sessionId);
31
- const [override, setOverride] = useState(null);
32
- const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
33
- const load = useCallback(async () => {
34
- if (!sessionId) {
35
- return null;
31
+ function humanInputRequestFromEvent(event) {
32
+ if (event.type !== "session.humanInput.requested" || !isRecord(event.payload)) return null;
33
+ const request = event.payload.request;
34
+ if (!isRecord(request) || typeof request.id !== "string" || !Array.isArray(request.questions)) {
35
+ return null;
36
+ }
37
+ return {
38
+ id: request.id,
39
+ turnId: event.turnId ?? null,
40
+ questions: request.questions,
41
+ allowSkip: request.allowSkip === true,
42
+ expiresAt: typeof request.expiresAt === "string" ? request.expiresAt : null
43
+ };
44
+ }
45
+ function projectPendingHumanInputRequests(events) {
46
+ const pending = /* @__PURE__ */ new Map();
47
+ for (const event of events) {
48
+ const requested = humanInputRequestFromEvent(event);
49
+ if (requested) {
50
+ pending.set(requested.id, requested);
51
+ continue;
36
52
  }
37
- const fetched = await client.getSession(workspaceId, sessionId);
38
- setOverride(null);
39
- return fetched;
40
- }, [client, workspaceId, sessionId]);
41
- const { data, loading, error, refresh } = usePolledValue(load, {
42
- pollIntervalMs: options.pollIntervalMs,
43
- enabled
44
- });
45
- useEffect(() => {
46
- if (enabled && workspaceControlEvent) void refresh();
47
- }, [enabled, refresh, workspaceControlEvent]);
48
- useEffect(() => {
49
- if (!sessionId || !enabled) return;
50
- return registerSessionReconciler(sessionId, "session", refresh);
51
- }, [enabled, refresh, registerSessionReconciler, sessionId]);
52
- const base = data ?? null;
53
- const session = useMemo(
54
- () => base && override && override.id === base.id ? { ...base, title: override.title, titleSource: override.titleSource } : base,
55
- [base, override]
56
- );
57
- const onTitleEvent = useCallback(
58
- (event) => {
59
- const payload = event.payload ?? {};
60
- const title = payload.title;
61
- if (typeof title !== "string") {
62
- return;
63
- }
64
- const source = payload.source === "user" || payload.source === "agent" ? payload.source : null;
65
- setOverride((current) => {
66
- const next = current ?? base;
67
- if (!next) {
68
- return current;
53
+ if (event.type === "user.humanInputResponse" && isRecord(event.payload)) {
54
+ if (typeof event.payload.requestId === "string") pending.delete(event.payload.requestId);
55
+ continue;
56
+ }
57
+ if (event.type === "turn.completed" || event.type === "turn.failed" || event.type === "turn.cancelled") {
58
+ for (const [id, request] of pending) {
59
+ if (request.turnId === null || event.turnId === null || event.turnId === void 0 || request.turnId === event.turnId) {
60
+ pending.delete(id);
69
61
  }
70
- return { ...next, title, titleSource: source };
71
- });
72
- },
73
- [base]
74
- );
75
- useSessionEventTrigger(client, workspaceId, sessionId, isTitleEvent, onTitleEvent, {
76
- enabled,
77
- ...options.events !== void 0 ? { events: options.events } : {}
78
- });
79
- const updateTitle = useCallback(
80
- async (title) => {
81
- if (!sessionId) {
82
- return null;
83
62
  }
84
- const result = await run(() => client.updateSession(workspaceId, sessionId, { title }));
85
- if (result) {
86
- setOverride(result);
63
+ }
64
+ }
65
+ return [...pending.values()];
66
+ }
67
+ function isRecord(value) {
68
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
69
+ }
70
+
71
+ // src/older-history.ts
72
+ var activeReceiptCapture;
73
+ var preserveTailForAutomaticLoad = false;
74
+ function invokeOlderHistoryLoaderWithReceiptCapture(load, capture, preserveTail = false) {
75
+ const previousCapture = activeReceiptCapture;
76
+ const previousPreserveTail = preserveTailForAutomaticLoad;
77
+ activeReceiptCapture = capture;
78
+ preserveTailForAutomaticLoad = preserveTail;
79
+ try {
80
+ return load();
81
+ } finally {
82
+ activeReceiptCapture = previousCapture;
83
+ preserveTailForAutomaticLoad = previousPreserveTail;
84
+ }
85
+ }
86
+ function createOlderHistoryLoadReceipt(load) {
87
+ let resolveResult;
88
+ let rejectResult;
89
+ const result = new Promise((resolve, reject) => {
90
+ resolveResult = resolve;
91
+ rejectResult = reject;
92
+ });
93
+ result.committed = false;
94
+ result.tailPreserved = false;
95
+ activeReceiptCapture?.(result);
96
+ try {
97
+ const loaded = load(
98
+ () => {
99
+ result.committed = true;
100
+ },
101
+ preserveTailForAutomaticLoad,
102
+ () => {
103
+ result.tailPreserved = true;
87
104
  }
88
- return result;
89
- },
90
- [client, workspaceId, sessionId, run]
91
- );
92
- return {
93
- session,
94
- loading,
95
- error,
96
- refresh,
97
- updateTitle,
98
- updating: mutating,
99
- mutationError,
100
- clearMutationError
101
- };
105
+ );
106
+ Promise.resolve(loaded).then(resolveResult, rejectResult);
107
+ } catch (error) {
108
+ rejectResult(error);
109
+ }
110
+ return result;
102
111
  }
103
112
 
104
113
  // src/hooks/use-session-events.ts
105
- import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef, useState as useState2 } from "react";
106
- var INITIAL_TAIL_PAGE_SIZE = 1e3;
107
- var OLDER_PAGE_SIZE = 5e3;
108
- var NEWER_PAGE_SIZE = 5e3;
109
- var OLDEST_PAGE_SIZE = 1e3;
114
+ import {
115
+ sessionEventStreamCoveredThrough
116
+ } from "@opengeni/sdk";
117
+ import { startTransition, useCallback, useEffect, useMemo, useRef, useState } from "react";
118
+ var SESSION_HISTORY_PAGE_SIZE = 1e3;
110
119
  var INITIAL_FETCH_CAP = 1;
111
120
  var OLDER_GROUP_TARGET = 32;
112
121
  var OLDER_FETCH_CAP = 2;
@@ -114,35 +123,16 @@ var NEWER_GROUP_TARGET = 32;
114
123
  var NEWER_FETCH_CAP = 2;
115
124
  var OLDEST_GROUP_TARGET = 32;
116
125
  var OLDEST_FETCH_CAP = 2;
117
- var BOUNDARY_PAGE_CAP = 4;
126
+ var BOUNDARY_PAGE_CAP = 1;
127
+ var FOREGROUND_DIRECT_REPLAY_MAX_SEQUENCES = 16;
128
+ var FOREGROUND_COMPACT_PROBE_MAX_SEQUENCES = SESSION_HISTORY_PAGE_SIZE;
129
+ var FOREGROUND_COMPACT_CATCHUP_MAX_EVENTS = 128;
130
+ var FOREGROUND_COMPACT_CATCHUP_MAX_GROUPS = 16;
131
+ var FOREGROUND_COMPACT_CATCHUP_MAX_BYTES = 512 * 1024;
118
132
  var EMPTY_EVENTS = [];
119
133
  var encoder = new TextEncoder();
120
- var decoder = new TextDecoder();
121
- var BROWSER_EVENT_TYPE_MAX_BYTES = 256;
122
- var BROWSER_EVENT_ID_MAX_BYTES = 256;
123
- var BROWSER_EVENT_CLIENT_ID_MAX_BYTES = 4 * 1024;
124
- var BROWSER_EVENT_DUPLICATE_REASON_MAX_BYTES = 4 * 1024;
125
- var BROWSER_EVENT_PAYLOAD_PREVIEW_MAX_BYTES = 48 * 1024;
126
- var BROWSER_EVENT_PAYLOAD_IDENTITY_FIELDS = [
127
- "id",
128
- "callId",
129
- "call_id",
130
- "name",
131
- "toolName",
132
- "status",
133
- "code",
134
- "isError",
135
- "stream",
136
- "commandId",
137
- "sequence",
138
- "coalescedUntil",
139
- "coalescedCount",
140
- "firstSequence",
141
- "lastSequence"
142
- ];
143
- var SESSION_EVENT_BROWSER_MAX_BYTES = 8 * 1024 * 1024;
144
- var SESSION_EVENT_BROWSER_MAX_COUNT = 1e4;
145
- var SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES = 96 * 1024;
134
+ var SESSION_EVENT_BROWSER_MAX_BYTES = 16 * 1024 * 1024;
135
+ var SESSION_EVENT_BROWSER_MAX_COUNT = 2e4;
146
136
  var SESSION_EVENT_BROWSER_PENDING_MAX_BYTES = 1024 * 1024;
147
137
  var SESSION_EVENT_BROWSER_PENDING_MAX_COUNT = 256;
148
138
  var EMPTY_EVENT_WINDOW = {
@@ -159,21 +149,35 @@ function useSessionEvents(sessionId, options = {}) {
159
149
  const replay = options.replay ?? "windowed";
160
150
  const fullReplay = replay === "full" || after !== 0;
161
151
  const streamKey = `${workspaceId}\0${sessionId ?? ""}\0${after}\0${fullReplay ? "full" : "windowed"}`;
162
- const [eventWindow, setEventWindow] = useState2(EMPTY_EVENT_WINDOW);
163
- const [connectionState, setConnectionState] = useState2("idle");
164
- const [error, setError] = useState2(null);
165
- const [hasOlder, setHasOlder] = useState2(false);
166
- const [hasNewer, setHasNewer] = useState2(false);
167
- const [sessionStatusProjection, setSessionStatusProjection] = useState2(
152
+ const navigationIdentityRef = useRef({ client, streamKey, enabled });
153
+ if (navigationIdentityRef.current.client !== client || navigationIdentityRef.current.streamKey !== streamKey || navigationIdentityRef.current.enabled !== enabled) {
154
+ navigationIdentityRef.current = { client, streamKey, enabled };
155
+ }
156
+ const navigationIdentity = navigationIdentityRef.current;
157
+ const ownsNavigation = useCallback(
158
+ () => navigationIdentityRef.current === navigationIdentity,
159
+ [navigationIdentity]
160
+ );
161
+ const [eventWindow, setEventWindow] = useState(EMPTY_EVENT_WINDOW);
162
+ const [questionEvidence, setQuestionEvidence] = useState(null);
163
+ const [connectionState, setConnectionState] = useState("idle");
164
+ const [error, setError] = useState(null);
165
+ const [newerError, setNewerError] = useState(null);
166
+ const [hasOlder, setHasOlder] = useState(false);
167
+ const [hasNewer, setHasNewer] = useState(false);
168
+ const [sessionStatusProjection, setSessionStatusProjection] = useState(
168
169
  null
169
170
  );
170
- const [initialLoading, setInitialLoading] = useState2(true);
171
- const [loadingOlder, setLoadingOlder] = useState2(false);
172
- const [loadingNewer, setLoadingNewer] = useState2(false);
173
- const [loadingOldest, setLoadingOldest] = useState2(false);
174
- const [loadingLatest, setLoadingLatest] = useState2(false);
175
- const [streamEpoch, setStreamEpoch] = useState2(0);
176
- const [viewMode, setViewMode] = useState2("live");
171
+ const [initialLoading, setInitialLoading] = useState(true);
172
+ const [initialHistoryReady, setInitialHistoryReady] = useState(false);
173
+ const [loadingOlder, setLoadingOlder] = useState(false);
174
+ const [loadingNewer, setLoadingNewer] = useState(false);
175
+ const [loadingOldest, setLoadingOldest] = useState(false);
176
+ const [loadingLatest, setLoadingLatest] = useState(false);
177
+ const [loadingTarget, setLoadingTarget] = useState(false);
178
+ const loadingTargetRef = useRef(false);
179
+ const [streamEpoch, setStreamEpoch] = useState(0);
180
+ const [viewMode, setViewMode] = useState("live");
177
181
  const lastSequenceRef = useRef(after);
178
182
  const streamResumeSequenceRef = useRef(after);
179
183
  const oldestSequenceRef = useRef(null);
@@ -186,16 +190,34 @@ function useSessionEvents(sessionId, options = {}) {
186
190
  const loadingLatestRef = useRef(false);
187
191
  const viewModeRef = useRef("live");
188
192
  const initialWindowLoadedRef = useRef(false);
193
+ const reconcileAfterPageResumeRef = useRef(false);
189
194
  const streamAbortRef = useRef(null);
190
195
  const streamKeyRef = useRef(null);
191
196
  const generationRef = useRef(0);
197
+ const navigationGenerationRef = useRef(0);
192
198
  const eventWindowRef = useRef(EMPTY_EVENT_WINDOW);
193
199
  const sessionStatusRef = useRef({
194
200
  sequence: after,
195
201
  status: null
196
202
  });
197
- const [stateStreamKey, setStateStreamKey] = useState2(streamKey);
198
- useEffect2(() => {
203
+ const [stateStreamKey, setStateStreamKey] = useState(streamKey);
204
+ useEffect(() => {
205
+ navigationGenerationRef.current += 1;
206
+ setNewerError(null);
207
+ loadingOlderRef.current = false;
208
+ loadingNewerRef.current = false;
209
+ loadingOldestRef.current = false;
210
+ loadingTargetRef.current = false;
211
+ setQuestionEvidence(null);
212
+ setLoadingTarget(false);
213
+ setLoadingOlder(false);
214
+ setLoadingNewer(false);
215
+ setLoadingOldest(false);
216
+ return () => {
217
+ navigationGenerationRef.current += 1;
218
+ };
219
+ }, [client, workspaceId, sessionId, after, enabled, fullReplay]);
220
+ useEffect(() => {
199
221
  if (streamKeyRef.current !== streamKey) {
200
222
  streamKeyRef.current = streamKey;
201
223
  setStateStreamKey(streamKey);
@@ -211,6 +233,7 @@ function useSessionEvents(sessionId, options = {}) {
211
233
  setLoadingOldest(false);
212
234
  setLoadingLatest(false);
213
235
  setInitialLoading(true);
236
+ setInitialHistoryReady(false);
214
237
  lastSequenceRef.current = after;
215
238
  streamResumeSequenceRef.current = after;
216
239
  oldestSequenceRef.current = null;
@@ -224,24 +247,27 @@ function useSessionEvents(sessionId, options = {}) {
224
247
  viewModeRef.current = "live";
225
248
  setViewMode("live");
226
249
  initialWindowLoadedRef.current = false;
250
+ reconcileAfterPageResumeRef.current = false;
227
251
  }
228
252
  const generation = generationRef.current + 1;
229
253
  generationRef.current = generation;
230
- if (loadingOlderRef.current) {
231
- loadingOlderRef.current = false;
232
- setLoadingOlder(false);
233
- }
234
254
  if (!sessionId || !streamEnabled) {
255
+ if (sessionId && enabled && !pageLive && !fullReplay && viewModeRef.current === "live" && initialWindowLoadedRef.current) {
256
+ reconcileAfterPageResumeRef.current = true;
257
+ }
235
258
  setConnectionState("idle");
236
259
  return;
237
260
  }
238
261
  if (viewMode === "history") {
262
+ reconcileAfterPageResumeRef.current = false;
239
263
  setConnectionState("idle");
240
264
  setInitialLoading(false);
241
265
  return;
242
266
  }
267
+ const reconcileForegroundResume = reconcileAfterPageResumeRef.current && !fullReplay;
268
+ reconcileAfterPageResumeRef.current = false;
243
269
  const controller = new AbortController();
244
- const isCurrent = () => generationRef.current === generation && !controller.signal.aborted;
270
+ const isCurrent = () => generationRef.current === generation && !controller.signal.aborted && navigationIdentityRef.current.client === client && navigationIdentityRef.current.streamKey === streamKey && navigationIdentityRef.current.enabled === enabled;
245
271
  streamAbortRef.current = controller;
246
272
  let pending = [];
247
273
  let pendingBytes = 2;
@@ -271,9 +297,12 @@ function useSessionEvents(sessionId, options = {}) {
271
297
  batchResumeSequence
272
298
  );
273
299
  }
274
- observeSessionStatus(batch, sessionStatusRef, setSessionStatusProjection);
300
+ const status = observeSessionStatus(batch, sessionStatusRef);
301
+ if (status !== void 0) {
302
+ setSessionStatusProjection(status);
303
+ }
275
304
  const current = eventWindowRef.current;
276
- const next = boundBrowserSessionEventWindow([...current.events, ...batch]);
305
+ const next = appendBrowserSessionEventWindow(current, batch);
277
306
  const retained = {
278
307
  ...next,
279
308
  truncated: current.truncated || next.truncated
@@ -281,7 +310,7 @@ function useSessionEvents(sessionId, options = {}) {
281
310
  eventWindowRef.current = retained;
282
311
  setEventWindow(retained);
283
312
  oldestSequenceRef.current = retained.events[0]?.sequence ?? null;
284
- newestSequenceRef.current = retained.events.at(-1)?.sequence ?? null;
313
+ newestSequenceRef.current = maxResumeSequenceOrNull(retained.events);
285
314
  if (retained.truncated) {
286
315
  hasOlderRef.current = true;
287
316
  setHasOlder(true);
@@ -293,29 +322,85 @@ function useSessionEvents(sessionId, options = {}) {
293
322
  };
294
323
  void (async () => {
295
324
  try {
325
+ if (reconcileForegroundResume) {
326
+ setConnectionState("connecting");
327
+ const cursor = streamResumeSequenceRef.current;
328
+ let plan = { kind: "resume" };
329
+ try {
330
+ plan = await planForegroundCatchup(client, workspaceId, sessionId, cursor, {
331
+ signal: controller.signal
332
+ });
333
+ } catch {
334
+ if (controller.signal.aborted) return;
335
+ plan = { kind: "resume" };
336
+ }
337
+ if (!isCurrent()) return;
338
+ if (plan.kind === "append") {
339
+ const current = eventWindowRef.current;
340
+ assertAppendOrder(current.events, plan.events);
341
+ const status = observeSessionStatus(plan.events, sessionStatusRef);
342
+ const next = appendBrowserSessionEventWindow(current, plan.events);
343
+ const retained = {
344
+ ...next,
345
+ truncated: current.truncated || next.truncated
346
+ };
347
+ eventWindowRef.current = retained;
348
+ oldestSequenceRef.current = retained.events[0]?.sequence ?? null;
349
+ newestSequenceRef.current = maxResumeSequenceOrNull(retained.events);
350
+ lastSequenceRef.current = Math.max(lastSequenceRef.current, plan.resumeSequence);
351
+ streamResumeSequenceRef.current = Math.max(
352
+ streamResumeSequenceRef.current,
353
+ plan.resumeSequence
354
+ );
355
+ if (status !== void 0) {
356
+ setSessionStatusProjection(status);
357
+ }
358
+ setEventWindow(retained);
359
+ if (retained.truncated) {
360
+ hasOlderRef.current = true;
361
+ setHasOlder(true);
362
+ }
363
+ } else if (plan.kind === "reload") {
364
+ initialWindowLoadedRef.current = false;
365
+ setError(null);
366
+ }
367
+ }
296
368
  if (!fullReplay && !initialWindowLoadedRef.current) {
297
369
  setConnectionState("connecting");
298
370
  const window = await loadEventWindow(client, workspaceId, sessionId, {
299
371
  before: Number.MAX_SAFE_INTEGER,
300
- pageSize: INITIAL_TAIL_PAGE_SIZE,
372
+ pageSize: SESSION_HISTORY_PAGE_SIZE,
301
373
  targetGroups: Number.POSITIVE_INFINITY,
302
374
  maxFetches: INITIAL_FETCH_CAP,
375
+ boundaryPageCap: BOUNDARY_PAGE_CAP,
303
376
  signal: controller.signal
304
377
  });
305
378
  if (!isCurrent()) {
306
379
  return;
307
380
  }
308
- observeSessionStatus(window.events, sessionStatusRef, setSessionStatusProjection);
381
+ const status = observeSessionStatus(window.events, sessionStatusRef);
309
382
  const retained = boundBrowserSessionEventWindow(window.events);
383
+ navigationGenerationRef.current += 1;
384
+ loadingOlderRef.current = false;
385
+ loadingNewerRef.current = false;
386
+ loadingOldestRef.current = false;
387
+ setLoadingOlder(false);
388
+ setLoadingNewer(false);
389
+ setLoadingOldest(false);
310
390
  eventWindowRef.current = retained;
311
- setEventWindow(retained);
312
391
  oldestSequenceRef.current = retained.events[0]?.sequence ?? window.oldestSequence;
313
- newestSequenceRef.current = retained.events.at(-1)?.sequence ?? window.newestSequence;
392
+ newestSequenceRef.current = maxResumeSequenceOrNull(retained.events) ?? window.newestSequence;
314
393
  hasOlderRef.current = window.hasOlder || retained.truncated;
315
394
  hasNewerRef.current = false;
316
395
  lastSequenceRef.current = window.newestSequence;
317
396
  streamResumeSequenceRef.current = window.newestSequence;
318
397
  initialWindowLoadedRef.current = true;
398
+ setInitialHistoryReady(true);
399
+ setError(null);
400
+ if (status !== void 0) {
401
+ setSessionStatusProjection(status);
402
+ }
403
+ setEventWindow(retained);
319
404
  setHasOlder(window.hasOlder || retained.truncated);
320
405
  setHasNewer(false);
321
406
  setInitialLoading(false);
@@ -329,7 +414,7 @@ function useSessionEvents(sessionId, options = {}) {
329
414
  onOpen: () => {
330
415
  void Promise.resolve().then(() => reconcileSession(sessionId)).catch((cause) => {
331
416
  if (isCurrent()) {
332
- setError(cause instanceof Error ? cause : new Error(String(cause)));
417
+ setError(normalizeError(cause));
333
418
  }
334
419
  });
335
420
  },
@@ -341,7 +426,7 @@ function useSessionEvents(sessionId, options = {}) {
341
426
  });
342
427
  for await (const event of stream) {
343
428
  if (!isCurrent()) break;
344
- const boundedEvent = boundBrowserLegacyEvent(event);
429
+ const boundedEvent = event;
345
430
  const boundedEventBytes = browserJsonBytes(boundedEvent);
346
431
  const separatorBytes = pending.length === 0 ? 0 : 1;
347
432
  if (pending.length > 0 && (pending.length >= SESSION_EVENT_BROWSER_PENDING_MAX_COUNT || pendingBytes + separatorBytes + boundedEventBytes > SESSION_EVENT_BROWSER_PENDING_MAX_BYTES)) {
@@ -362,8 +447,9 @@ function useSessionEvents(sessionId, options = {}) {
362
447
  } catch (cause) {
363
448
  if (isCurrent()) {
364
449
  flush();
365
- setError(cause instanceof Error ? cause : new Error(String(cause)));
450
+ setError(normalizeError(cause));
366
451
  setConnectionState("error");
452
+ setInitialLoading(false);
367
453
  }
368
454
  }
369
455
  })();
@@ -384,6 +470,8 @@ function useSessionEvents(sessionId, options = {}) {
384
470
  workspaceId,
385
471
  sessionId,
386
472
  after,
473
+ enabled,
474
+ pageLive,
387
475
  streamEnabled,
388
476
  fullReplay,
389
477
  streamKey,
@@ -391,89 +479,134 @@ function useSessionEvents(sessionId, options = {}) {
391
479
  viewMode,
392
480
  reconcileSession
393
481
  ]);
394
- const navigationBusy = () => loadingOlderRef.current || loadingNewerRef.current || loadingOldestRef.current || loadingLatestRef.current;
395
- const loadOlder = useCallback2(async () => {
396
- if (!sessionId || navigationBusy() || !hasOlderRef.current) {
397
- return false;
398
- }
399
- const before = oldestSequenceRef.current;
400
- if (before === null) {
401
- hasOlderRef.current = false;
402
- setHasOlder(false);
403
- return false;
404
- }
405
- const generation = generationRef.current;
406
- loadingOlderRef.current = true;
407
- setLoadingOlder(true);
408
- try {
409
- const window = await loadEventWindow(client, workspaceId, sessionId, {
410
- before,
411
- pageSize: OLDER_PAGE_SIZE,
412
- targetGroups: OLDER_GROUP_TARGET,
413
- maxFetches: OLDER_FETCH_CAP
414
- });
415
- if (generationRef.current !== generation) {
482
+ const navigationBusy = () => loadingTargetRef.current || loadingOlderRef.current || loadingNewerRef.current || loadingOldestRef.current || loadingLatestRef.current;
483
+ const loadOlder = useCallback(
484
+ () => createOlderHistoryLoadReceipt(async (markCommitted, preserveTail, markTailPreserved) => {
485
+ if (!ownsNavigation() || !sessionId || navigationBusy() || !hasOlderRef.current) {
416
486
  return false;
417
487
  }
418
- if (window.events.length === 0) {
488
+ const before = oldestSequenceRef.current;
489
+ if (before === null) {
419
490
  oldestSequenceRef.current = null;
420
491
  hasOlderRef.current = false;
421
492
  setHasOlder(false);
422
493
  return false;
423
494
  }
424
- const current = eventWindowRef.current;
425
- assertPrependOrder(current.events, window.events);
426
- streamAbortRef.current?.abort();
427
- observeSessionStatus(window.events, sessionStatusRef, setSessionStatusProjection);
428
- const next = boundBrowserSessionEventWindow([...window.events, ...current.events], {
429
- direction: "oldest"
430
- });
431
- const retained = {
432
- ...next,
433
- truncated: current.truncated || next.truncated
434
- };
435
- const retainedOldest = retained.events[0]?.sequence ?? null;
436
- if (retainedOldest === null || retainedOldest >= before) {
437
- throw new Error("@opengeni/react: loadOlder made no durable sequence progress");
438
- }
439
- const retainedNewest = retained.events.at(-1)?.sequence ?? null;
440
- eventWindowRef.current = retained;
441
- setEventWindow(retained);
442
- oldestSequenceRef.current = retainedOldest;
443
- newestSequenceRef.current = retainedNewest;
444
- streamResumeSequenceRef.current = maxResumeSequence(retained.events);
445
- const olderStillAvailable = window.hasOlder;
446
- hasOlderRef.current = olderStillAvailable;
447
- setHasOlder(olderStillAvailable);
448
- if (viewModeRef.current === "history") {
449
- const highWater = lastSequenceRef.current;
450
- const newer = retainedNewest !== null && (retainedNewest < highWater || retained.truncated);
451
- hasNewerRef.current = newer;
452
- setHasNewer(newer);
453
- } else {
454
- setStreamEpoch((epoch) => epoch + 1);
495
+ const generation = navigationGenerationRef.current;
496
+ const isCurrent = () => ownsNavigation() && navigationGenerationRef.current === generation;
497
+ loadingOlderRef.current = true;
498
+ setLoadingOlder(true);
499
+ let published = false;
500
+ try {
501
+ const window = await loadEventWindow(client, workspaceId, sessionId, {
502
+ before,
503
+ pageSize: SESSION_HISTORY_PAGE_SIZE,
504
+ targetGroups: OLDER_GROUP_TARGET,
505
+ maxFetches: OLDER_FETCH_CAP,
506
+ isCurrent
507
+ });
508
+ if (!isCurrent()) {
509
+ return false;
510
+ }
511
+ if (window.events.length === 0) {
512
+ oldestSequenceRef.current = null;
513
+ hasOlderRef.current = false;
514
+ setHasOlder(false);
515
+ return false;
516
+ }
517
+ const current = eventWindowRef.current;
518
+ assertPrependOrder(current.events, window.events);
519
+ const next = boundBrowserSessionEventWindow([...window.events, ...current.events], {
520
+ direction: "oldest"
521
+ });
522
+ if (preserveTail && maxResumeSequence(next.events) < maxResumeSequence(current.events)) {
523
+ markTailPreserved();
524
+ return false;
525
+ }
526
+ streamAbortRef.current?.abort();
527
+ const status = observeSessionStatus(window.events, sessionStatusRef);
528
+ const retained = {
529
+ ...next,
530
+ truncated: current.truncated || next.truncated
531
+ };
532
+ const retainedOldest = retained.events[0]?.sequence ?? null;
533
+ if (retainedOldest === null || retainedOldest >= before) {
534
+ throw new Error("@opengeni/react: loadOlder made no durable sequence progress");
535
+ }
536
+ const retainedNewest = maxResumeSequenceOrNull(retained.events);
537
+ const previousNewest = maxResumeSequenceOrNull(current.events);
538
+ eventWindowRef.current = retained;
539
+ markCommitted();
540
+ oldestSequenceRef.current = retainedOldest;
541
+ newestSequenceRef.current = retainedNewest;
542
+ streamResumeSequenceRef.current = maxResumeSequence(retained.events);
543
+ const olderStillAvailable = window.hasOlder;
544
+ hasOlderRef.current = olderStillAvailable;
545
+ const evictedLiveTail = previousNewest !== null && retainedNewest !== null && retainedNewest < previousNewest;
546
+ let newer = null;
547
+ let enterHistory = false;
548
+ let reconnectLive = false;
549
+ if (viewModeRef.current === "history" || evictedLiveTail) {
550
+ const highWater = lastSequenceRef.current;
551
+ newer = retainedNewest !== null && (retainedNewest < highWater || retained.truncated);
552
+ hasNewerRef.current = newer;
553
+ if (evictedLiveTail) {
554
+ viewModeRef.current = "history";
555
+ enterHistory = true;
556
+ }
557
+ } else {
558
+ reconnectLive = true;
559
+ }
560
+ loadingOlderRef.current = false;
561
+ startTransition(() => {
562
+ if (status !== void 0) {
563
+ setSessionStatusProjection(status);
564
+ }
565
+ setEventWindow(retained);
566
+ setHasOlder(olderStillAvailable);
567
+ if (newer !== null) {
568
+ setHasNewer(newer);
569
+ }
570
+ if (enterHistory) {
571
+ setViewMode("history");
572
+ } else if (reconnectLive) {
573
+ setStreamEpoch((epoch) => epoch + 1);
574
+ }
575
+ setLoadingOlder(false);
576
+ });
577
+ published = true;
578
+ return olderStillAvailable;
579
+ } catch (reason) {
580
+ if (!isCurrent()) return false;
581
+ throw reason;
582
+ } finally {
583
+ if (!published && isCurrent()) {
584
+ loadingOlderRef.current = false;
585
+ setLoadingOlder(false);
586
+ }
455
587
  }
456
- return olderStillAvailable;
457
- } finally {
458
- loadingOlderRef.current = false;
459
- setLoadingOlder(false);
460
- }
461
- }, [client, workspaceId, sessionId]);
462
- const loadOldest = useCallback2(async () => {
463
- if (!sessionId || navigationBusy() || !hasOlderRef.current) {
588
+ }),
589
+ [client, workspaceId, sessionId, ownsNavigation]
590
+ );
591
+ const loadOldest = useCallback(async () => {
592
+ if (!ownsNavigation() || !sessionId || navigationBusy() || !hasOlderRef.current) {
464
593
  return false;
465
594
  }
466
- const generation = generationRef.current;
595
+ const generation = ++navigationGenerationRef.current;
596
+ const isCurrent = () => ownsNavigation() && navigationGenerationRef.current === generation;
597
+ setQuestionEvidence(null);
467
598
  loadingOldestRef.current = true;
468
599
  setLoadingOldest(true);
600
+ let published = false;
469
601
  try {
470
602
  const window = await loadForwardEventWindow(client, workspaceId, sessionId, {
471
603
  after: 0,
472
- pageSize: OLDEST_PAGE_SIZE,
604
+ pageSize: SESSION_HISTORY_PAGE_SIZE,
473
605
  targetGroups: OLDEST_GROUP_TARGET,
474
- maxFetches: OLDEST_FETCH_CAP
606
+ maxFetches: OLDEST_FETCH_CAP,
607
+ isCurrent
475
608
  });
476
- if (generationRef.current !== generation) {
609
+ if (!isCurrent()) {
477
610
  return false;
478
611
  }
479
612
  if (window.events.length === 0) {
@@ -482,32 +615,48 @@ function useSessionEvents(sessionId, options = {}) {
482
615
  return false;
483
616
  }
484
617
  streamAbortRef.current?.abort();
485
- observeSessionStatus(window.events, sessionStatusRef, setSessionStatusProjection);
618
+ const status = observeSessionStatus(window.events, sessionStatusRef);
486
619
  const retained = boundBrowserSessionEventWindow(window.events, { direction: "oldest" });
487
620
  const retainedOldest = retained.events[0]?.sequence ?? null;
488
- const retainedNewest = retained.events.at(-1)?.sequence ?? null;
621
+ const retainedNewest = maxResumeSequenceOrNull(retained.events);
489
622
  eventWindowRef.current = retained;
490
- setEventWindow(retained);
491
623
  oldestSequenceRef.current = retainedOldest;
492
624
  newestSequenceRef.current = retainedNewest;
493
625
  hasOlderRef.current = false;
494
- setHasOlder(false);
495
626
  const highWater = Math.max(lastSequenceRef.current, retainedNewest ?? 0);
496
627
  lastSequenceRef.current = highWater;
497
628
  const newer = window.hasNewer || retained.truncated || retainedNewest !== null && retainedNewest < highWater;
498
629
  hasNewerRef.current = newer;
499
- setHasNewer(newer);
500
630
  initialWindowLoadedRef.current = true;
631
+ setInitialHistoryReady(true);
632
+ setError(null);
501
633
  viewModeRef.current = "history";
502
- setViewMode("history");
634
+ loadingOldestRef.current = false;
635
+ setNewerError(null);
636
+ startTransition(() => {
637
+ if (status !== void 0) {
638
+ setSessionStatusProjection(status);
639
+ }
640
+ setEventWindow(retained);
641
+ setHasOlder(false);
642
+ setHasNewer(newer);
643
+ setViewMode("history");
644
+ setLoadingOldest(false);
645
+ });
646
+ published = true;
503
647
  return newer;
648
+ } catch (reason) {
649
+ if (!isCurrent()) return false;
650
+ throw reason;
504
651
  } finally {
505
- loadingOldestRef.current = false;
506
- setLoadingOldest(false);
652
+ if (!published && isCurrent()) {
653
+ loadingOldestRef.current = false;
654
+ setLoadingOldest(false);
655
+ }
507
656
  }
508
- }, [client, workspaceId, sessionId]);
509
- const loadNewer = useCallback2(async () => {
510
- if (!sessionId || navigationBusy() || !hasNewerRef.current) {
657
+ }, [client, workspaceId, sessionId, ownsNavigation]);
658
+ const loadNewer = useCallback(async () => {
659
+ if (!ownsNavigation() || !sessionId || navigationBusy() || !hasNewerRef.current) {
511
660
  return false;
512
661
  }
513
662
  const afterSequence = newestSequenceRef.current;
@@ -516,20 +665,24 @@ function useSessionEvents(sessionId, options = {}) {
516
665
  setHasNewer(false);
517
666
  return false;
518
667
  }
519
- const generation = generationRef.current;
668
+ const generation = navigationGenerationRef.current;
669
+ const isCurrent = () => ownsNavigation() && navigationGenerationRef.current === generation;
520
670
  loadingNewerRef.current = true;
521
671
  setLoadingNewer(true);
672
+ let published = false;
522
673
  try {
523
674
  const window = await loadForwardEventWindow(client, workspaceId, sessionId, {
524
675
  after: afterSequence,
525
- pageSize: NEWER_PAGE_SIZE,
676
+ pageSize: SESSION_HISTORY_PAGE_SIZE,
526
677
  targetGroups: NEWER_GROUP_TARGET,
527
- maxFetches: NEWER_FETCH_CAP
678
+ maxFetches: NEWER_FETCH_CAP,
679
+ isCurrent
528
680
  });
529
- if (generationRef.current !== generation) {
681
+ if (!isCurrent()) {
530
682
  return false;
531
683
  }
532
684
  if (window.events.length === 0) {
685
+ setNewerError(null);
533
686
  hasNewerRef.current = false;
534
687
  setHasNewer(false);
535
688
  streamResumeSequenceRef.current = afterSequence;
@@ -540,85 +693,246 @@ function useSessionEvents(sessionId, options = {}) {
540
693
  }
541
694
  const current = eventWindowRef.current;
542
695
  assertAppendOrder(current.events, window.events);
543
- observeSessionStatus(window.events, sessionStatusRef, setSessionStatusProjection);
696
+ const status = observeSessionStatus(window.events, sessionStatusRef);
544
697
  const previousOldest = current.events[0]?.sequence ?? null;
545
- const next = boundBrowserSessionEventWindow([...current.events, ...window.events], {
546
- direction: "newest"
547
- });
698
+ const next = appendBrowserSessionEventWindow(current, window.events);
548
699
  const retained = {
549
700
  ...next,
550
701
  truncated: current.truncated || next.truncated || window.hasNewer
551
702
  };
552
703
  const retainedOldest = retained.events[0]?.sequence ?? null;
553
- const retainedNewest = retained.events.at(-1)?.sequence ?? null;
704
+ const retainedNewest = maxResumeSequenceOrNull(retained.events);
554
705
  if (retainedNewest === null || retainedNewest <= afterSequence) {
555
706
  throw new Error("@opengeni/react: loadNewer made no durable sequence progress");
556
707
  }
557
708
  eventWindowRef.current = retained;
558
- setEventWindow(retained);
559
709
  oldestSequenceRef.current = retainedOldest;
560
710
  newestSequenceRef.current = retainedNewest;
561
711
  if (retainedOldest !== null && (previousOldest === null || retainedOldest > previousOldest || retained.truncated && retained.events[0]?.type !== "session.created" && retainedOldest > 1)) {
562
712
  hasOlderRef.current = true;
563
- setHasOlder(true);
564
713
  }
714
+ const rearmedOlder = hasOlderRef.current;
565
715
  const highWater = Math.max(lastSequenceRef.current, retainedNewest);
566
716
  lastSequenceRef.current = highWater;
567
717
  const newer = window.hasNewer || retainedNewest < highWater;
568
718
  hasNewerRef.current = newer;
569
- setHasNewer(newer);
719
+ let resumeLive = false;
570
720
  if (!newer) {
571
721
  streamResumeSequenceRef.current = retainedNewest;
572
722
  viewModeRef.current = "live";
573
- setViewMode("live");
574
- setStreamEpoch((epoch) => epoch + 1);
723
+ resumeLive = true;
575
724
  }
725
+ loadingNewerRef.current = false;
726
+ setNewerError(null);
727
+ startTransition(() => {
728
+ if (status !== void 0) {
729
+ setSessionStatusProjection(status);
730
+ }
731
+ setEventWindow(retained);
732
+ setHasOlder(rearmedOlder);
733
+ setHasNewer(newer);
734
+ if (resumeLive) {
735
+ setViewMode("live");
736
+ setStreamEpoch((epoch) => epoch + 1);
737
+ }
738
+ setLoadingNewer(false);
739
+ });
740
+ published = true;
576
741
  return newer;
742
+ } catch (reason) {
743
+ if (!isCurrent()) {
744
+ return false;
745
+ }
746
+ setNewerError(normalizeError(reason));
747
+ throw reason;
577
748
  } finally {
578
- loadingNewerRef.current = false;
749
+ if (!published && isCurrent()) {
750
+ loadingNewerRef.current = false;
751
+ setLoadingNewer(false);
752
+ }
753
+ }
754
+ }, [client, workspaceId, sessionId, ownsNavigation]);
755
+ const jumpToSequence = useCallback(
756
+ async (sequence, navigationOptions) => {
757
+ const signal = navigationOptions?.signal;
758
+ if (!ownsNavigation() || !sessionId || !Number.isSafeInteger(sequence) || sequence < 1)
759
+ return false;
760
+ if (signal?.aborted) return false;
761
+ const generation = ++navigationGenerationRef.current;
762
+ setQuestionEvidence(null);
763
+ const current = () => generation === navigationGenerationRef.current && ownsNavigation();
764
+ const previousMode = viewModeRef.current;
765
+ streamAbortRef.current?.abort();
766
+ generationRef.current += 1;
767
+ viewModeRef.current = "history";
768
+ setViewMode("history");
769
+ loadingOlderRef.current = loadingNewerRef.current = loadingOldestRef.current = false;
770
+ setLoadingOlder(false);
579
771
  setLoadingNewer(false);
772
+ setLoadingOldest(false);
773
+ loadingTargetRef.current = true;
774
+ setLoadingTarget(true);
775
+ let published = false;
776
+ try {
777
+ const [before, following] = await Promise.all([
778
+ loadPreviousPage(client, workspaceId, sessionId, sequence + 1, { pageSize: 128 }),
779
+ loadNextPage(client, workspaceId, sessionId, sequence, { pageSize: 128 })
780
+ ]);
781
+ if (signal?.aborted) return false;
782
+ if (!current()) return false;
783
+ if (!before.some((event) => event.sequence === sequence)) return false;
784
+ const prefix = boundBrowserSessionEventWindow(before, {
785
+ direction: "newest",
786
+ maxBytes: SESSION_EVENT_BROWSER_MAX_BYTES / 2
787
+ });
788
+ const retained = boundBrowserSessionEventWindow([...prefix.events, ...following], {
789
+ direction: "oldest"
790
+ });
791
+ if (!retained.events.some((event) => event.sequence === sequence)) return false;
792
+ streamAbortRef.current?.abort();
793
+ generationRef.current += 1;
794
+ const status = observeSessionStatus(retained.events, sessionStatusRef);
795
+ eventWindowRef.current = retained;
796
+ oldestSequenceRef.current = retained.events[0].sequence;
797
+ newestSequenceRef.current = maxResumeSequenceOrNull(retained.events);
798
+ lastSequenceRef.current = Math.max(lastSequenceRef.current, newestSequenceRef.current ?? 0);
799
+ hasOlderRef.current = !isLogStart(retained.events[0]);
800
+ hasNewerRef.current = following.length > 0 || retained.truncated || (newestSequenceRef.current ?? 0) < lastSequenceRef.current;
801
+ initialWindowLoadedRef.current = true;
802
+ setInitialHistoryReady(true);
803
+ setError(null);
804
+ viewModeRef.current = "history";
805
+ setEventWindow(retained);
806
+ setHasOlder(hasOlderRef.current);
807
+ setHasNewer(hasNewerRef.current);
808
+ setInitialLoading(false);
809
+ setViewMode("history");
810
+ setNewerError(null);
811
+ if (status !== void 0) setSessionStatusProjection(status);
812
+ published = true;
813
+ return true;
814
+ } catch (reason) {
815
+ if (!current() || signal?.aborted) return false;
816
+ throw reason;
817
+ } finally {
818
+ if (current()) {
819
+ loadingTargetRef.current = false;
820
+ setLoadingTarget(false);
821
+ if (!published && previousMode === "live") {
822
+ viewModeRef.current = "live";
823
+ setViewMode("live");
824
+ setStreamEpoch((epoch) => epoch + 1);
825
+ }
826
+ }
827
+ }
828
+ },
829
+ [client, workspaceId, sessionId, ownsNavigation]
830
+ );
831
+ const jumpToLatestQuestion = useCallback(
832
+ async (questionOptions) => {
833
+ if (!sessionId || !ownsNavigation()) return null;
834
+ let generation = navigationGenerationRef.current;
835
+ const current = () => ownsNavigation() && generation === navigationGenerationRef.current;
836
+ try {
837
+ const { resolveLatestQuestion } = await import("./latest-question-755OW5DS.js");
838
+ if (!current()) return null;
839
+ const destination = await resolveLatestQuestion({
840
+ client,
841
+ workspaceId,
842
+ sessionId,
843
+ isCurrent: current,
844
+ resumeSequence: maxResumeSequence,
845
+ options: questionOptions
846
+ });
847
+ if (!destination || !current()) return null;
848
+ if (!eventWindowRef.current.events.some((event) => event.sequence === destination.anchor)) {
849
+ const navigation = jumpToSequence(destination.anchor);
850
+ generation = navigationGenerationRef.current;
851
+ if (!await navigation || !current()) return null;
852
+ }
853
+ setQuestionEvidence({
854
+ client,
855
+ streamKey,
856
+ anchor: destination.anchor,
857
+ events: destination.events
858
+ });
859
+ return destination.questionSequence;
860
+ } catch (reason) {
861
+ if (!current()) return null;
862
+ throw reason;
863
+ }
864
+ },
865
+ [client, workspaceId, sessionId, streamKey, ownsNavigation, jumpToSequence]
866
+ );
867
+ const jumpToLatest = useCallback(async () => {
868
+ if (!ownsNavigation()) return;
869
+ if (loadingTargetRef.current) {
870
+ navigationGenerationRef.current += 1;
871
+ loadingTargetRef.current = false;
872
+ setLoadingTarget(false);
580
873
  }
581
- }, [client, workspaceId, sessionId]);
582
- const jumpToLatest = useCallback2(async () => {
583
874
  if (!sessionId || navigationBusy()) {
584
875
  return;
585
876
  }
877
+ navigationGenerationRef.current += 1;
878
+ setQuestionEvidence(null);
586
879
  loadingLatestRef.current = true;
587
880
  setLoadingLatest(true);
881
+ setNewerError(null);
882
+ setError(null);
883
+ let published = false;
588
884
  try {
589
885
  streamAbortRef.current?.abort();
590
886
  hasNewerRef.current = false;
591
- setHasNewer(false);
592
887
  hasOlderRef.current = false;
593
- setHasOlder(false);
594
888
  newestSequenceRef.current = null;
595
889
  oldestSequenceRef.current = null;
596
890
  eventWindowRef.current = EMPTY_EVENT_WINDOW;
597
- setEventWindow(EMPTY_EVENT_WINDOW);
598
891
  initialWindowLoadedRef.current = false;
599
892
  streamResumeSequenceRef.current = after;
600
- setInitialLoading(true);
601
893
  viewModeRef.current = "live";
602
- setViewMode("live");
603
- setStreamEpoch((epoch) => epoch + 1);
894
+ loadingLatestRef.current = false;
895
+ startTransition(() => {
896
+ setHasNewer(false);
897
+ setHasOlder(false);
898
+ setEventWindow(EMPTY_EVENT_WINDOW);
899
+ setInitialLoading(true);
900
+ setViewMode("live");
901
+ setStreamEpoch((epoch) => epoch + 1);
902
+ setLoadingLatest(false);
903
+ });
904
+ published = true;
604
905
  await Promise.resolve();
605
906
  } finally {
606
- loadingLatestRef.current = false;
607
- setLoadingLatest(false);
907
+ if (!published) {
908
+ loadingLatestRef.current = false;
909
+ setLoadingLatest(false);
910
+ }
608
911
  }
609
- }, [after, sessionId]);
912
+ }, [after, sessionId, ownsNavigation]);
610
913
  const identityMatches = stateStreamKey === streamKey;
611
914
  const visibleEvents = identityMatches ? eventWindow.events : EMPTY_EVENTS;
612
- const timeline = useMemo2(() => buildTimeline(visibleEvents), [visibleEvents]);
915
+ const timeline = useMemo(() => {
916
+ const witness = questionEvidence?.client === client && questionEvidence.streamKey === streamKey && visibleEvents.some((event) => event.sequence === questionEvidence.anchor) ? questionEvidence.events : EMPTY_EVENTS;
917
+ const timelineOptions = { partialStart: hasOlder || eventWindow.truncated || after > 0 };
918
+ if (witness.length === 0) return buildTimeline(visibleEvents, timelineOptions);
919
+ const ids = new Set(visibleEvents.map((event) => event.id));
920
+ return buildTimeline(
921
+ [...visibleEvents, ...witness.filter((event) => !ids.has(event.id))],
922
+ timelineOptions
923
+ );
924
+ }, [visibleEvents, hasOlder, eventWindow.truncated, after, questionEvidence, client, streamKey]);
613
925
  return {
614
926
  events: visibleEvents,
615
927
  timeline,
616
928
  sessionStatus: identityMatches ? sessionStatusProjection : null,
929
+ sessionStatusSequence: identityMatches ? sessionStatusRef.current.sequence : 0,
617
930
  connectionState: identityMatches ? connectionState : "idle",
618
931
  lastSequence: identityMatches ? lastSequenceRef.current : after,
619
932
  windowBytes: identityMatches ? eventWindow.bytes : 2,
620
933
  windowTruncated: identityMatches ? eventWindow.truncated : false,
621
934
  initialLoading: fullReplay ? false : identityMatches ? initialLoading : true,
935
+ initialHistoryReady: identityMatches && initialHistoryReady,
622
936
  hasOlder: !identityMatches ? false : hasOlder,
623
937
  loadingOlder: !identityMatches ? false : loadingOlder,
624
938
  loadOlder,
@@ -629,13 +943,16 @@ function useSessionEvents(sessionId, options = {}) {
629
943
  loadOldest,
630
944
  loadingLatest: !identityMatches ? false : loadingLatest,
631
945
  jumpToLatest,
632
- error: identityMatches ? error : null
946
+ jumpToLatestQuestion,
947
+ jumpToSequence,
948
+ loadingTarget: identityMatches && loadingTarget,
949
+ error: identityMatches ? newerError ?? error : null
633
950
  };
634
951
  }
635
952
  function boundBrowserSessionEventWindow(events, options = {}) {
636
953
  const maxBytes = Math.max(1024, options.maxBytes ?? SESSION_EVENT_BROWSER_MAX_BYTES);
637
954
  const maxCount = Math.max(1, Math.floor(options.maxCount ?? SESSION_EVENT_BROWSER_MAX_COUNT));
638
- const safe = events.map(boundBrowserLegacyEvent);
955
+ const safe = events;
639
956
  const selected = [];
640
957
  let bytes = 2;
641
958
  const direction = options.direction ?? "newest";
@@ -646,7 +963,7 @@ function boundBrowserSessionEventWindow(events, options = {}) {
646
963
  const event = safe[index];
647
964
  const eventBytes = browserJsonBytes(event);
648
965
  const separator = selected.length === 0 ? 0 : 1;
649
- if (bytes + separator + eventBytes > maxBytes) break;
966
+ if (selected.length > 0 && bytes + separator + eventBytes > maxBytes) break;
650
967
  selected.push(event);
651
968
  bytes += separator + eventBytes;
652
969
  }
@@ -657,184 +974,29 @@ function boundBrowserSessionEventWindow(events, options = {}) {
657
974
  truncated: selected.length < safe.length
658
975
  };
659
976
  }
660
- function boundBrowserLegacyEvent(event) {
661
- const serialized = browserSerialize(event);
662
- const originalBytes = serialized.serializable ? encoder.encode(serialized.value).byteLength : null;
663
- const typeIsSafe = browserUtf8Bytes(event.type) <= BROWSER_EVENT_TYPE_MAX_BYTES && !event.type.includes("\n") && !event.type.includes("\r");
664
- const clientEventId = boundBrowserOptionalText(
665
- event.clientEventId,
666
- BROWSER_EVENT_CLIENT_ID_MAX_BYTES
667
- );
668
- const duplicateReason = boundBrowserOptionalText(
669
- event.duplicateReason,
670
- BROWSER_EVENT_DUPLICATE_REASON_MAX_BYTES
671
- );
672
- if (serialized.serializable && originalBytes !== null && originalBytes <= SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES && typeIsSafe && clientEventId === event.clientEventId && duplicateReason === event.duplicateReason) {
673
- return event;
977
+ function appendBrowserSessionEventWindow(current, batch, options = {}) {
978
+ const maxBytes = Math.max(1024, options.maxBytes ?? SESSION_EVENT_BROWSER_MAX_BYTES);
979
+ const maxCount = Math.max(1, Math.floor(options.maxCount ?? SESSION_EVENT_BROWSER_MAX_COUNT));
980
+ const events = [...current.events, ...batch];
981
+ let bytes = current.bytes;
982
+ for (let index = 0; index < batch.length; index += 1) {
983
+ bytes += browserJsonBytes(batch[index]) + (current.events.length + index > 0 ? 1 : 0);
674
984
  }
675
- const envelopeProjection = [
676
- !typeIsSafe ? browserEnvelopeFieldProjection("type", event.type, "session.event.envelope_omitted") : null,
677
- clientEventId !== event.clientEventId ? browserEnvelopeFieldProjection("clientEventId", event.clientEventId, clientEventId) : null,
678
- duplicateReason !== event.duplicateReason ? browserEnvelopeFieldProjection("duplicateReason", event.duplicateReason, duplicateReason) : null
679
- ].filter((field) => field !== null);
680
- const payloadSerialization = browserSerialize(event.payload);
681
- const payloadBytes = payloadSerialization.serializable ? encoder.encode(payloadSerialization.value).byteLength : null;
682
- const preview = truncateBrowserUtf8Middle(
683
- payloadSerialization.value,
684
- BROWSER_EVENT_PAYLOAD_PREVIEW_MAX_BYTES
685
- );
686
- const truncation = {
687
- truncated: true,
688
- surface: "browser_legacy_guard",
689
- reason: serialized.serializable ? "event_envelope_bytes_exceeded" : "event_not_serializable",
690
- originalBytes,
691
- deliveredBytes: 0,
692
- omittedBytes: originalBytes,
693
- estimatedOriginalTokens: originalBytes === null ? null : Math.ceil(originalBytes / 4),
694
- estimatedDeliveredTokens: 0,
695
- fullEvidence: { available: false, reason: "not_retained" },
696
- details: [
697
- {
698
- path: "$.payload",
699
- kind: "payload_preview",
700
- originalBytes: payloadBytes,
701
- deliveredBytes: browserUtf8Bytes(preview)
702
- }
703
- ]
704
- };
705
- const payload = {
706
- ...browserPayloadIdentity(event.payload),
707
- preview,
708
- ...envelopeProjection.length > 0 ? {
709
- originalType: boundBrowserText(event.type, BROWSER_EVENT_TYPE_MAX_BYTES),
710
- envelopeProjection: {
711
- truncated: true,
712
- surface: "browser_legacy_guard",
713
- fields: envelopeProjection
714
- }
715
- } : {},
716
- truncation
717
- };
718
- const bounded = {
719
- id: boundBrowserText(event.id, BROWSER_EVENT_ID_MAX_BYTES),
720
- workspaceId: boundBrowserText(event.workspaceId, BROWSER_EVENT_ID_MAX_BYTES),
721
- sessionId: boundBrowserText(event.sessionId, BROWSER_EVENT_ID_MAX_BYTES),
722
- sequence: event.sequence,
723
- type: typeIsSafe ? event.type : "session.event.envelope_omitted",
724
- payload,
725
- occurredAt: boundBrowserText(event.occurredAt, BROWSER_EVENT_ID_MAX_BYTES),
726
- clientEventId,
727
- turnId: boundBrowserOptionalText(event.turnId, BROWSER_EVENT_ID_MAX_BYTES),
728
- turnGeneration: event.turnGeneration,
729
- turnAttemptId: boundBrowserOptionalText(event.turnAttemptId, BROWSER_EVENT_ID_MAX_BYTES),
730
- turnAssociation: event.turnAssociation,
731
- duplicateOfEventId: boundBrowserOptionalText(
732
- event.duplicateOfEventId,
733
- BROWSER_EVENT_ID_MAX_BYTES
734
- ),
735
- duplicateReason
736
- };
737
- settleBrowserEventTruncation(bounded, truncation);
738
- if (browserJsonBytes(bounded) > SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES) {
739
- payload.preview = truncateBrowserUtf8Middle(String(payload.preview), 4 * 1024);
740
- truncation.details = truncation.details.slice(0, 1);
741
- settleBrowserEventTruncation(bounded, truncation);
985
+ let start = 0;
986
+ while (events.length - start > 1 && (bytes > maxBytes || events.length - start > maxCount)) {
987
+ bytes -= browserJsonBytes(events[start]) + 1;
988
+ start += 1;
742
989
  }
743
- if (browserJsonBytes(bounded) > SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES) {
744
- bounded.clientEventId = null;
745
- bounded.duplicateReason = null;
746
- payload.preview = "[legacy event omitted at the browser byte boundary]";
747
- settleBrowserEventTruncation(bounded, truncation);
748
- }
749
- return bounded;
750
- }
751
- function browserJsonBytes(value) {
752
- return encoder.encode(browserSerialize(value).value).byteLength;
753
- }
754
- function browserSerialize(value) {
755
- try {
756
- const serialized = JSON.stringify(value);
757
- return { value: serialized === void 0 ? "null" : serialized, serializable: true };
758
- } catch {
759
- return {
760
- value: '"[unserializable event payload omitted at browser boundary]"',
761
- serializable: false
762
- };
763
- }
764
- }
765
- function browserPayloadIdentity(payload) {
766
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) return {};
767
- const record = payload;
768
- const identity = {};
769
- for (const field of BROWSER_EVENT_PAYLOAD_IDENTITY_FIELDS) {
770
- const value = record[field];
771
- if (typeof value === "string") {
772
- identity[field] = boundBrowserText(value, BROWSER_EVENT_ID_MAX_BYTES);
773
- } else if (typeof value === "number" || typeof value === "boolean" || value === null) {
774
- identity[field] = value;
775
- }
776
- }
777
- return identity;
778
- }
779
- function browserEnvelopeFieldProjection(field, original, delivered) {
780
990
  return {
781
- field,
782
- originalBytes: typeof original === "string" ? browserUtf8Bytes(original) : 0,
783
- deliveredBytes: typeof delivered === "string" ? browserUtf8Bytes(delivered) : 0
991
+ events: start === 0 ? events : events.slice(start),
992
+ bytes,
993
+ truncated: current.truncated || start > 0
784
994
  };
785
995
  }
786
- function boundBrowserOptionalText(value, maxBytes) {
787
- return typeof value === "string" ? boundBrowserText(value, maxBytes) : value;
788
- }
789
- function boundBrowserText(value, maxBytes) {
790
- const bytes = encoder.encode(value);
791
- if (bytes.byteLength <= maxBytes) return value;
792
- const marker = "\u2026[truncated]";
793
- const prefixBudget = Math.max(0, maxBytes - browserUtf8Bytes(marker));
794
- let prefixEnd = Math.min(prefixBudget, bytes.byteLength);
795
- while (prefixEnd > 0 && prefixEnd < bytes.byteLength && isBrowserUtf8Continuation(bytes[prefixEnd])) {
796
- prefixEnd -= 1;
797
- }
798
- return `${decoder.decode(bytes.subarray(0, prefixEnd))}${marker}`;
799
- }
800
- function truncateBrowserUtf8Middle(value, maxBytes) {
801
- const bytes = encoder.encode(value);
802
- if (bytes.byteLength <= maxBytes) return value;
803
- const marker = `\u2026[${bytes.byteLength - maxBytes} bytes omitted]\u2026`;
804
- const contentBudget = Math.max(0, maxBytes - browserUtf8Bytes(marker));
805
- const leftBudget = Math.floor(contentBudget / 2);
806
- const rightBudget = contentBudget - leftBudget;
807
- let leftEnd = Math.min(leftBudget, bytes.byteLength);
808
- while (leftEnd > 0 && leftEnd < bytes.byteLength && isBrowserUtf8Continuation(bytes[leftEnd])) {
809
- leftEnd -= 1;
810
- }
811
- let rightStart = Math.max(0, bytes.byteLength - rightBudget);
812
- while (rightStart < bytes.byteLength && isBrowserUtf8Continuation(bytes[rightStart])) {
813
- rightStart += 1;
814
- }
815
- return `${decoder.decode(bytes.subarray(0, leftEnd))}${marker}${decoder.decode(bytes.subarray(rightStart))}`;
816
- }
817
- function settleBrowserEventTruncation(event, truncation) {
818
- for (let attempt = 0; attempt < 16; attempt += 1) {
819
- const deliveredBytes = browserJsonBytes(event);
820
- const omittedBytes = truncation.originalBytes === null ? null : Math.max(0, truncation.originalBytes - deliveredBytes);
821
- const estimatedDeliveredTokens = Math.ceil(deliveredBytes / 4);
822
- if (truncation.deliveredBytes === deliveredBytes && truncation.omittedBytes === omittedBytes && truncation.estimatedDeliveredTokens === estimatedDeliveredTokens) {
823
- return;
824
- }
825
- truncation.deliveredBytes = deliveredBytes;
826
- truncation.omittedBytes = omittedBytes;
827
- truncation.estimatedDeliveredTokens = estimatedDeliveredTokens;
828
- }
829
- throw new RangeError("Browser event byte accounting did not converge");
830
- }
831
- function browserUtf8Bytes(value) {
832
- return encoder.encode(value).byteLength;
833
- }
834
- function isBrowserUtf8Continuation(value) {
835
- return (value & 192) === 128;
996
+ function browserJsonBytes(value) {
997
+ return encoder.encode(JSON.stringify(value)).byteLength;
836
998
  }
837
- function observeSessionStatus(events, ref, setStatus) {
999
+ function observeSessionStatus(events, ref) {
838
1000
  let latest = null;
839
1001
  for (const event of events) {
840
1002
  const status = sessionStatusFromEvents([event]);
@@ -842,9 +1004,42 @@ function observeSessionStatus(events, ref, setStatus) {
842
1004
  latest = { sequence: event.sequence, status };
843
1005
  }
844
1006
  }
845
- if (!latest || latest.sequence < ref.current.sequence) return;
1007
+ if (!latest || latest.sequence < ref.current.sequence) return void 0;
846
1008
  ref.current = latest;
847
- setStatus(latest.status);
1009
+ return latest.status;
1010
+ }
1011
+ async function planForegroundCatchup(client, workspaceId, sessionId, cursor, options = {}) {
1012
+ if (options.signal?.aborted) throw abortError();
1013
+ const session = await client.getSession(workspaceId, sessionId, {
1014
+ fresh: true,
1015
+ ...options.signal ? { signal: options.signal } : {}
1016
+ });
1017
+ if (options.signal?.aborted) throw abortError();
1018
+ const durableHead = Math.max(cursor, session.lastSequence);
1019
+ const rawGap = durableHead - cursor;
1020
+ if (rawGap <= FOREGROUND_DIRECT_REPLAY_MAX_SEQUENCES) {
1021
+ return { kind: "resume" };
1022
+ }
1023
+ if (rawGap > FOREGROUND_COMPACT_PROBE_MAX_SEQUENCES) {
1024
+ return { kind: "reload" };
1025
+ }
1026
+ const page = await loadNextPage(client, workspaceId, sessionId, cursor, {
1027
+ pageSize: rawGap,
1028
+ ...options.signal ? { signal: options.signal } : {}
1029
+ });
1030
+ const events = page.filter((event) => eventResumeSequence(event) > cursor);
1031
+ if (events.length === 0) {
1032
+ return { kind: "reload" };
1033
+ }
1034
+ assertAscending(events);
1035
+ const resumeSequence = maxResumeSequence(events);
1036
+ if (resumeSequence < durableHead) {
1037
+ return { kind: "reload" };
1038
+ }
1039
+ if (events.length > FOREGROUND_COMPACT_CATCHUP_MAX_EVENTS || groupCount(events) > FOREGROUND_COMPACT_CATCHUP_MAX_GROUPS || browserJsonBytes(events) > FOREGROUND_COMPACT_CATCHUP_MAX_BYTES) {
1040
+ return { kind: "reload" };
1041
+ }
1042
+ return { kind: "append", events, resumeSequence };
848
1043
  }
849
1044
  async function loadEventWindow(client, workspaceId, sessionId, options) {
850
1045
  let cursor = options.before;
@@ -859,6 +1054,7 @@ async function loadEventWindow(client, workspaceId, sessionId, options) {
859
1054
  pageSize: options.pageSize,
860
1055
  ...options.signal ? { signal: options.signal } : {}
861
1056
  });
1057
+ if (options.isCurrent?.() === false) throw abortError();
862
1058
  fetches += 1;
863
1059
  if (page.length === 0) {
864
1060
  reachedStart = true;
@@ -873,11 +1069,12 @@ async function loadEventWindow(client, workspaceId, sessionId, options) {
873
1069
  }
874
1070
  }
875
1071
  let snapPages = 0;
876
- while (!reachedStart && findBoundaryIndex(buffer) === -1 && snapPages < BOUNDARY_PAGE_CAP && fetches < options.maxFetches) {
1072
+ while (!reachedStart && findBoundaryIndex(buffer) === -1 && snapPages < (options.boundaryPageCap ?? 0)) {
877
1073
  const page = await loadPreviousPage(client, workspaceId, sessionId, cursor, {
878
1074
  pageSize: options.pageSize,
879
1075
  ...options.signal ? { signal: options.signal } : {}
880
1076
  });
1077
+ if (options.isCurrent?.() === false) throw abortError();
881
1078
  fetches += 1;
882
1079
  snapPages += 1;
883
1080
  if (page.length === 0) {
@@ -898,24 +1095,28 @@ async function loadEventWindow(client, workspaceId, sessionId, options) {
898
1095
  buffer = buffer.slice(boundary);
899
1096
  }
900
1097
  }
1098
+ const densityWindow = trimBackwardToGroupTarget(buffer, options.targetGroups);
1099
+ buffer = densityWindow.events;
901
1100
  const oldest = buffer[0] ?? null;
902
1101
  const newest = buffer[buffer.length - 1] ?? null;
903
1102
  return {
904
1103
  events: buffer,
905
1104
  oldestSequence: oldest?.sequence ?? null,
906
1105
  newestSequence: newest ? maxResumeSequence(buffer) : 0,
907
- hasOlder: buffer.length > 0 && !reachedStart && oldest?.type !== "session.created"
1106
+ hasOlder: buffer.length > 0 && (densityWindow.trimmed || !reachedStart && oldest?.type !== "session.created")
908
1107
  };
909
1108
  }
910
1109
  function findBoundaryIndex(events) {
911
1110
  for (let index = 0; index < events.length; index += 1) {
912
- const type = events[index].type;
913
- if (type === "session.created" || type === "user.message") {
1111
+ if (isTurnBoundary(events[index])) {
914
1112
  return index;
915
1113
  }
916
1114
  }
917
1115
  return -1;
918
1116
  }
1117
+ function isTurnBoundary(event) {
1118
+ return event.type === "session.created" || event.type === "user.message";
1119
+ }
919
1120
  async function loadForwardEventWindow(client, workspaceId, sessionId, options) {
920
1121
  let cursor = options.after;
921
1122
  let buffer = [];
@@ -929,6 +1130,7 @@ async function loadForwardEventWindow(client, workspaceId, sessionId, options) {
929
1130
  pageSize: options.pageSize,
930
1131
  ...options.signal ? { signal: options.signal } : {}
931
1132
  });
1133
+ if (options.isCurrent?.() === false) throw abortError();
932
1134
  fetches += 1;
933
1135
  if (page.length === 0) {
934
1136
  reachedEnd = true;
@@ -936,21 +1138,45 @@ async function loadForwardEventWindow(client, workspaceId, sessionId, options) {
936
1138
  }
937
1139
  assertAscending(page);
938
1140
  buffer = [...buffer, ...page];
939
- cursor = page[page.length - 1].sequence;
940
- if (page.length < options.pageSize) {
941
- reachedEnd = true;
942
- break;
943
- }
1141
+ cursor = maxResumeSequence(page);
944
1142
  }
1143
+ const densityWindow = trimForwardToGroupTarget(buffer, options.targetGroups);
1144
+ buffer = densityWindow.events;
945
1145
  const oldest = buffer[0] ?? null;
946
1146
  const newest = buffer[buffer.length - 1] ?? null;
947
1147
  return {
948
1148
  events: buffer,
949
1149
  oldestSequence: oldest?.sequence ?? null,
950
1150
  newestSequence: newest ? maxResumeSequence(buffer) : 0,
951
- hasNewer: buffer.length > 0 && !reachedEnd
1151
+ hasNewer: buffer.length > 0 && (densityWindow.trimmed || !reachedEnd)
952
1152
  };
953
1153
  }
1154
+ function trimBackwardToGroupTarget(events, targetGroups) {
1155
+ if (!Number.isFinite(targetGroups) || targetGroups <= 0 || events.length === 0) {
1156
+ return { events, trimmed: false };
1157
+ }
1158
+ for (let index = events.length - 1; index > 0; index -= 1) {
1159
+ if (!isTurnBoundary(events[index])) continue;
1160
+ const candidate = events.slice(index);
1161
+ if (groupCount(candidate) >= targetGroups) {
1162
+ return { events: candidate, trimmed: true };
1163
+ }
1164
+ }
1165
+ return { events, trimmed: false };
1166
+ }
1167
+ function trimForwardToGroupTarget(events, targetGroups) {
1168
+ if (!Number.isFinite(targetGroups) || targetGroups <= 0 || events.length === 0) {
1169
+ return { events, trimmed: false };
1170
+ }
1171
+ for (let index = 1; index < events.length; index += 1) {
1172
+ if (!isTurnBoundary(events[index])) continue;
1173
+ const candidate = events.slice(0, index);
1174
+ if (groupCount(candidate) >= targetGroups) {
1175
+ return { events: candidate, trimmed: true };
1176
+ }
1177
+ }
1178
+ return { events, trimmed: false };
1179
+ }
954
1180
  async function loadPreviousPage(client, workspaceId, sessionId, before, options) {
955
1181
  if (options.signal?.aborted) {
956
1182
  throw abortError();
@@ -1006,13 +1232,13 @@ function isLogStart(event) {
1006
1232
  function maxResumeSequence(events) {
1007
1233
  return events.reduce((max, event) => Math.max(max, eventResumeSequence(event)), 0);
1008
1234
  }
1009
- function eventResumeSequence(event) {
1010
- const payload = asRecord(event.payload);
1011
- const coalescedUntil = Number(payload.coalescedUntil);
1012
- return Math.max(event.sequence, Number.isFinite(coalescedUntil) ? Math.floor(coalescedUntil) : 0);
1235
+ function maxResumeSequenceOrNull(events) {
1236
+ return events.length > 0 ? maxResumeSequence(events) : null;
1013
1237
  }
1014
- function asRecord(value) {
1015
- return value && typeof value === "object" ? value : {};
1238
+ function eventResumeSequence(event) {
1239
+ const streamedCoverage = sessionEventStreamCoveredThrough(event);
1240
+ if (streamedCoverage !== null) return streamedCoverage;
1241
+ return typeof event.coveredThrough === "number" && Number.isSafeInteger(event.coveredThrough) && event.coveredThrough >= event.sequence ? event.coveredThrough : event.sequence;
1016
1242
  }
1017
1243
  function assertAscending(events) {
1018
1244
  for (let index = 1; index < events.length; index += 1) {
@@ -1047,224 +1273,122 @@ function abortError() {
1047
1273
  return error;
1048
1274
  }
1049
1275
 
1050
- // src/hooks/use-file-attachments.ts
1051
- import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
1052
- var isImage = (file) => file.type.startsWith("image/");
1053
- function useFileAttachments(options = {}) {
1054
- const { client, workspaceId } = useEmbeddedFileAttachments(options);
1055
- const pasteFilter = options.pasteFilter ?? isImage;
1056
- const [attachments, setAttachments] = useState3([]);
1057
- const sources = useRef2(/* @__PURE__ */ new Map());
1058
- const previewUrls = useRef2(/* @__PURE__ */ new Map());
1059
- const revokePreview = useCallback3((id) => {
1060
- const previewUrl = previewUrls.current.get(id);
1061
- if (!previewUrl) return;
1062
- previewUrls.current.delete(id);
1063
- URL.revokeObjectURL(previewUrl);
1064
- }, []);
1065
- const revokeAllPreviews = useCallback3(() => {
1066
- const urls = [...previewUrls.current.values()];
1067
- previewUrls.current.clear();
1068
- for (const previewUrl of urls) URL.revokeObjectURL(previewUrl);
1069
- }, []);
1070
- const committedAttachmentIds = useRef2(/* @__PURE__ */ new Set());
1071
- useEffect3(() => {
1072
- const currentIds = new Set(attachments.map((attachment) => attachment.id));
1073
- for (const id of committedAttachmentIds.current) {
1074
- if (!currentIds.has(id)) revokePreview(id);
1075
- }
1076
- committedAttachmentIds.current = currentIds;
1077
- }, [attachments, revokePreview]);
1078
- const scopeGeneration = useRef2(0);
1079
- const previousScope = useRef2({ client, workspaceId });
1080
- useEffect3(() => {
1081
- const previous = previousScope.current;
1082
- if (previous.client === client && previous.workspaceId === workspaceId) return;
1083
- previousScope.current = { client, workspaceId };
1084
- scopeGeneration.current += 1;
1085
- sources.current.clear();
1086
- revokeAllPreviews();
1087
- setAttachments([]);
1088
- }, [client, revokeAllPreviews, workspaceId]);
1089
- useEffect3(
1090
- () => () => {
1091
- scopeGeneration.current += 1;
1092
- sources.current.clear();
1093
- revokeAllPreviews();
1094
- },
1095
- [revokeAllPreviews]
1096
- );
1097
- const startUpload = useCallback3(
1098
- (id, file) => {
1099
- const generation = scopeGeneration.current;
1100
- void client.uploadFile(workspaceId, {
1101
- filename: file.name || "file",
1102
- contentType: file.type || "application/octet-stream",
1103
- data: file
1104
- }).then((asset) => {
1105
- if (scopeGeneration.current !== generation) return;
1106
- sources.current.delete(id);
1107
- setAttachments(
1108
- (current) => current.map(
1109
- (attachment) => attachment.id === id ? {
1110
- ...attachment,
1111
- status: "ready",
1112
- file: asset,
1113
- name: asset.filename,
1114
- contentType: asset.contentType,
1115
- sizeBytes: asset.sizeBytes,
1116
- error: void 0
1117
- } : attachment
1118
- )
1119
- );
1120
- }).catch((error) => {
1121
- if (scopeGeneration.current !== generation) return;
1122
- setAttachments(
1123
- (current) => current.map(
1124
- (attachment) => attachment.id === id ? {
1125
- ...attachment,
1126
- status: "failed",
1127
- error: error instanceof Error ? error.message : String(error)
1128
- } : attachment
1129
- )
1130
- );
1131
- });
1132
- },
1133
- [client, workspaceId]
1134
- );
1135
- const addFiles = useCallback3(
1136
- (files) => {
1137
- for (const file of files) {
1138
- const id = crypto.randomUUID();
1139
- sources.current.set(id, file);
1140
- const previewUrl = isImage(file) ? URL.createObjectURL(file) : void 0;
1141
- if (previewUrl) previewUrls.current.set(id, previewUrl);
1142
- setAttachments((current) => [
1143
- ...current,
1144
- {
1145
- id,
1146
- name: file.name || "image",
1147
- contentType: file.type || "application/octet-stream",
1148
- sizeBytes: file.size,
1149
- status: "uploading",
1150
- ...previewUrl ? { previewUrl } : {}
1151
- }
1152
- ]);
1153
- startUpload(id, file);
1276
+ // src/hooks/use-session.ts
1277
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
1278
+ function isTitleEvent(event) {
1279
+ return event.type === "session.title_set";
1280
+ }
1281
+ function isSessionDetailEvent(event) {
1282
+ return isTitleEvent(event) || event.type.startsWith("session.command.") || event.type.startsWith("session.wait.") || event.type === "session.status.changed" || event.type === "system.update.pending";
1283
+ }
1284
+ function useSession(sessionId, options = {}) {
1285
+ const { client, workspaceId, workspaceControlEvent, registerSessionReconciler } = useEmbeddedSessionRead(options);
1286
+ const enabled = (options.enabled ?? true) && Boolean(sessionId);
1287
+ const [override, setOverride] = useState2(null);
1288
+ const nextReadRevision = useRef2(0);
1289
+ const nextReadGeneration = useRef2(0);
1290
+ const beginRead = options.beginRead;
1291
+ const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
1292
+ const load = useCallback2(
1293
+ async (signal) => {
1294
+ if (!sessionId) {
1295
+ return null;
1154
1296
  }
1297
+ let readGeneration2 = 0;
1298
+ const fetched = await client.getSession(workspaceId, sessionId, {
1299
+ fresh: true,
1300
+ signal,
1301
+ onRequestStart: () => {
1302
+ readGeneration2 = beginRead?.() ?? ++nextReadGeneration.current;
1303
+ }
1304
+ });
1305
+ setOverride(null);
1306
+ return {
1307
+ session: fetched,
1308
+ revision: ++nextReadRevision.current,
1309
+ readGeneration: readGeneration2
1310
+ };
1155
1311
  },
1156
- [startUpload]
1312
+ [beginRead, client, workspaceId, sessionId]
1157
1313
  );
1158
- const retry = useCallback3(
1159
- (id) => {
1160
- const file = sources.current.get(id);
1161
- if (!file) {
1162
- return;
1163
- }
1164
- setAttachments(
1165
- (current) => current.map(
1166
- (attachment) => attachment.id === id ? { ...attachment, status: "uploading", error: void 0 } : attachment
1167
- )
1168
- );
1169
- startUpload(id, file);
1170
- },
1171
- [startUpload]
1314
+ const { data, loading, error, refresh } = usePolledValue(load, {
1315
+ pollIntervalMs: options.pollIntervalMs,
1316
+ enabled
1317
+ });
1318
+ useEffect2(() => {
1319
+ if (enabled && workspaceControlEvent) void refresh();
1320
+ }, [enabled, refresh, workspaceControlEvent]);
1321
+ useEffect2(() => {
1322
+ if (!sessionId || !enabled) return;
1323
+ return registerSessionReconciler(sessionId, "session", refresh);
1324
+ }, [enabled, refresh, registerSessionReconciler, sessionId]);
1325
+ const base = data?.session ?? null;
1326
+ const readRevision = data?.revision ?? 0;
1327
+ const readGeneration = data?.readGeneration ?? 0;
1328
+ const session = useMemo2(
1329
+ () => base && override && override.id === base.id ? { ...base, title: override.title, titleSource: override.titleSource } : base,
1330
+ [base, override]
1172
1331
  );
1173
- const addFromPaste = useCallback3(
1332
+ const sharedEvents = options.events;
1333
+ const onTitleEvent = useCallback2(
1174
1334
  (event) => {
1175
- const clipboardFiles = event.clipboardData?.files;
1176
- if (!clipboardFiles) {
1335
+ if (sharedEvents?.some(
1336
+ (item) => item.sessionId === sessionId && !isTitleEvent(item) && isSessionDetailEvent(item) && item.sequence > (base?.lastSequence ?? -1)
1337
+ )) {
1338
+ void refresh();
1339
+ } else if (!isTitleEvent(event)) {
1340
+ if (!base || event.sequence > base.lastSequence) void refresh();
1177
1341
  return;
1178
1342
  }
1179
- const files = [...clipboardFiles].filter(pasteFilter);
1180
- if (files.length > 0) {
1181
- addFiles(files);
1343
+ if (!base || event.sequence <= base.lastSequence) {
1344
+ return;
1182
1345
  }
1183
- },
1184
- [addFiles, pasteFilter]
1185
- );
1186
- const restoreReadyFiles = useCallback3(
1187
- (files) => {
1188
- const incoming = /* @__PURE__ */ new Map();
1189
- for (const file of files) {
1190
- if (file.status === "ready" && file.workspaceId === workspaceId) {
1191
- incoming.set(file.id, file);
1192
- }
1346
+ const payload = event.payload ?? {};
1347
+ const title = payload.title;
1348
+ if (typeof title !== "string") {
1349
+ return;
1193
1350
  }
1194
- setAttachments((current) => {
1195
- const unresolved = current.filter((attachment) => attachment.status !== "ready");
1196
- const existingReady = new Map(
1197
- current.flatMap(
1198
- (attachment) => attachment.status === "ready" && attachment.file ? [[attachment.file.id, attachment]] : []
1199
- )
1200
- );
1201
- const restored = [...incoming.values()].map((file) => {
1202
- const existing = existingReady.get(file.id);
1203
- return existing ? {
1204
- ...existing,
1205
- name: file.filename,
1206
- contentType: file.contentType,
1207
- sizeBytes: file.sizeBytes,
1208
- status: "ready",
1209
- file,
1210
- error: void 0
1211
- } : {
1212
- id: `restored:${file.id}`,
1213
- name: file.filename,
1214
- contentType: file.contentType,
1215
- sizeBytes: file.sizeBytes,
1216
- status: "ready",
1217
- file
1218
- // No source File and no object URL: server metadata is the
1219
- // only authority restored across page/device boundaries.
1220
- };
1221
- });
1222
- return [...unresolved, ...restored];
1351
+ const source = payload.source === "user" || payload.source === "agent" ? payload.source : null;
1352
+ setOverride((current) => {
1353
+ const next = current ?? base;
1354
+ return { ...next, title, titleSource: source };
1223
1355
  });
1224
1356
  },
1225
- [workspaceId]
1357
+ [base, refresh, sharedEvents, sessionId]
1226
1358
  );
1227
- const remove = useCallback3(
1228
- (id) => {
1229
- sources.current.delete(id);
1230
- revokePreview(id);
1231
- setAttachments((current) => current.filter((attachment) => attachment.id !== id));
1359
+ useSessionEventTrigger(client, workspaceId, sessionId, isSessionDetailEvent, onTitleEvent, {
1360
+ enabled,
1361
+ ...options.events !== void 0 ? { events: options.events } : {}
1362
+ });
1363
+ const updateTitle = useCallback2(
1364
+ async (title) => {
1365
+ if (!sessionId) {
1366
+ return null;
1367
+ }
1368
+ const result = await run(() => client.updateSession(workspaceId, sessionId, { title }));
1369
+ if (result) {
1370
+ setOverride(result);
1371
+ }
1372
+ return result;
1232
1373
  },
1233
- [revokePreview]
1374
+ [client, workspaceId, sessionId, run]
1234
1375
  );
1235
- const removeReadyFiles = useCallback3((fileIds) => {
1236
- const accepted = new Set(fileIds);
1237
- if (accepted.size === 0) return;
1238
- setAttachments(
1239
- (current) => current.filter((attachment) => {
1240
- return !(attachment.status === "ready" && attachment.file !== void 0 && accepted.has(attachment.file.id));
1241
- })
1242
- );
1243
- }, []);
1244
- const clear = useCallback3(() => {
1245
- sources.current.clear();
1246
- revokeAllPreviews();
1247
- setAttachments([]);
1248
- }, [revokeAllPreviews]);
1249
1376
  return {
1250
- attachments,
1251
- readyResources: attachments.flatMap(
1252
- (attachment) => attachment.status === "ready" && attachment.file ? [{ kind: "file", fileId: attachment.file.id }] : []
1253
- ),
1254
- uploading: attachments.some((attachment) => attachment.status === "uploading"),
1255
- hasUnresolved: attachments.some((attachment) => attachment.status !== "ready"),
1256
- addFiles,
1257
- addFromPaste,
1258
- restoreReadyFiles,
1259
- retry,
1260
- remove,
1261
- removeReadyFiles,
1262
- clear
1377
+ session,
1378
+ loading,
1379
+ error,
1380
+ readRevision,
1381
+ readGeneration,
1382
+ refresh,
1383
+ updateTitle,
1384
+ updating: mutating,
1385
+ mutationError,
1386
+ clearMutationError
1263
1387
  };
1264
1388
  }
1265
1389
 
1266
1390
  // src/hooks/use-turn-queue.ts
1267
- import { useCallback as useCallback4, useEffect as useEffect4, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
1391
+ import { useCallback as useCallback3, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
1268
1392
  function isTurnQueueEvent(event) {
1269
1393
  return event.type.startsWith("turn.") || event.type.startsWith("session.queue.") || event.type.startsWith("session.control.") || event.type.startsWith("system.update.") || event.type.startsWith("workspace.inference.");
1270
1394
  }
@@ -1279,12 +1403,13 @@ function useTurnQueue(sessionId, options = {}) {
1279
1403
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
1280
1404
  const targetKey = `${workspaceId}\0${sessionId ?? ""}`;
1281
1405
  const pageLive = usePageLiveActivity();
1282
- const [snapshot, setSnapshot] = useState4(null);
1283
- const [stateTargetKey, setStateTargetKey] = useState4(targetKey);
1284
- const [loading, setLoading] = useState4(enabled);
1285
- const [error, setError] = useState4(null);
1286
- const [mutationError, setMutationError] = useState4(null);
1287
- const [pendingByTurn, setPendingByTurn] = useState4({});
1406
+ const [snapshot, setSnapshot] = useState3(null);
1407
+ const [stateTargetKey, setStateTargetKey] = useState3(targetKey);
1408
+ const [loading, setLoading] = useState3(enabled);
1409
+ const [error, setError] = useState3(null);
1410
+ const [mutationError, setMutationError] = useState3(null);
1411
+ const [pendingByTurn, setPendingByTurn] = useState3({});
1412
+ const [acceptedSteers, setAcceptedSteers] = useState3([]);
1288
1413
  const pendingRef = useRef3({});
1289
1414
  const readGeneration = useRef3(0);
1290
1415
  const targetKeyRef = useRef3(targetKey);
@@ -1301,8 +1426,21 @@ function useTurnQueue(sessionId, options = {}) {
1301
1426
  setError(null);
1302
1427
  setMutationError(null);
1303
1428
  setPendingByTurn({});
1429
+ setAcceptedSteers([]);
1304
1430
  }, [enabled, targetKey]);
1305
- const acceptSnapshot = useCallback4(
1431
+ useEffect3(() => {
1432
+ if (acceptedSteers.length === 0 || options.events === void 0) return;
1433
+ const durablyStartedTurnIds = new Set(
1434
+ options.events.flatMap(
1435
+ (event) => event.type === "turn.started" && event.turnId ? [event.turnId] : []
1436
+ )
1437
+ );
1438
+ if (durablyStartedTurnIds.size === 0) return;
1439
+ setAcceptedSteers(
1440
+ (current) => current.filter((steer) => !durablyStartedTurnIds.has(steer.turnId))
1441
+ );
1442
+ }, [acceptedSteers.length, options.events]);
1443
+ const acceptSnapshot = useCallback3(
1306
1444
  (ownedTargetKey, next) => {
1307
1445
  if (targetKeyRef.current !== ownedTargetKey) return false;
1308
1446
  const current = snapshotRef.current;
@@ -1316,7 +1454,7 @@ function useTurnQueue(sessionId, options = {}) {
1316
1454
  },
1317
1455
  []
1318
1456
  );
1319
- const load = useCallback4(
1457
+ const load = useCallback3(
1320
1458
  async (rejectOnFailure = false) => {
1321
1459
  if (!sessionId) return;
1322
1460
  const ownedTargetKey = `${workspaceId}\0${sessionId}`;
@@ -1347,7 +1485,7 @@ function useTurnQueue(sessionId, options = {}) {
1347
1485
  },
1348
1486
  [client, workspaceId, sessionId, acceptSnapshot]
1349
1487
  );
1350
- useEffect4(() => {
1488
+ useEffect3(() => {
1351
1489
  if (!enabled || !pageLive) {
1352
1490
  setLoading(false);
1353
1491
  return;
@@ -1376,10 +1514,10 @@ function useTurnQueue(sessionId, options = {}) {
1376
1514
  readGeneration.current += 1;
1377
1515
  };
1378
1516
  }, [load, enabled, pageLive, options.pollIntervalMs]);
1379
- useEffect4(() => {
1517
+ useEffect3(() => {
1380
1518
  if (enabled && workspaceControlEvent) void load();
1381
1519
  }, [enabled, load, workspaceControlEvent]);
1382
- useEffect4(() => {
1520
+ useEffect3(() => {
1383
1521
  if (!sessionId || !enabled) return;
1384
1522
  return registerSessionReconciler(sessionId, "queue", load);
1385
1523
  }, [enabled, load, registerSessionReconciler, sessionId]);
@@ -1396,7 +1534,7 @@ function useTurnQueue(sessionId, options = {}) {
1396
1534
  },
1397
1535
  async () => await load(true)
1398
1536
  );
1399
- const mutate = useCallback4(
1537
+ const mutate = useCallback3(
1400
1538
  async (turnId, kind, command) => {
1401
1539
  const ownedTargetKey = targetKey;
1402
1540
  if (!sessionId || targetKeyRef.current !== ownedTargetKey || pendingRef.current[turnId]) {
@@ -1409,8 +1547,15 @@ function useTurnQueue(sessionId, options = {}) {
1409
1547
  setStateTargetKey(ownedTargetKey);
1410
1548
  setPendingByTurn(pendingRef.current);
1411
1549
  setMutationError(null);
1550
+ const clientEventId = operationKey();
1412
1551
  try {
1413
- const result = await command(current, turn);
1552
+ let result;
1553
+ try {
1554
+ result = await command(current, turn, clientEventId);
1555
+ } catch (cause) {
1556
+ if (!isOutcomeUnknownError(cause)) throw cause;
1557
+ result = await command(current, turn, clientEventId);
1558
+ }
1414
1559
  if (targetKeyRef.current !== ownedTargetKey) return null;
1415
1560
  acceptSnapshot(ownedTargetKey, result.snapshot);
1416
1561
  return result;
@@ -1431,13 +1576,13 @@ function useTurnQueue(sessionId, options = {}) {
1431
1576
  },
1432
1577
  [acceptSnapshot, load, sessionId, targetKey]
1433
1578
  );
1434
- const moveTurn = useCallback4(
1579
+ const moveTurn = useCallback3(
1435
1580
  async (turnId, beforeTurnId) => {
1436
1581
  const result = await mutate(
1437
1582
  turnId,
1438
1583
  "move",
1439
- (current) => client.moveQueueItem(workspaceId, sessionId, turnId, {
1440
- clientEventId: operationKey(),
1584
+ (current, _turn, clientEventId) => client.moveQueueItem(workspaceId, sessionId, turnId, {
1585
+ clientEventId,
1441
1586
  expectedQueueVersion: current.version,
1442
1587
  beforeTurnId
1443
1588
  })
@@ -1446,13 +1591,13 @@ function useTurnQueue(sessionId, options = {}) {
1446
1591
  },
1447
1592
  [client, mutate, sessionId, workspaceId]
1448
1593
  );
1449
- const editTurn = useCallback4(
1594
+ const editTurn = useCallback3(
1450
1595
  async (turnId, edit) => {
1451
1596
  const result = await mutate(
1452
1597
  turnId,
1453
1598
  "edit",
1454
- (_current, turn) => client.editQueueItem(workspaceId, sessionId, turnId, {
1455
- clientEventId: operationKey(),
1599
+ (_current, turn, clientEventId) => client.editQueueItem(workspaceId, sessionId, turnId, {
1600
+ clientEventId,
1456
1601
  expectedTurnVersion: turn.version,
1457
1602
  expectedDraftRevision: edit.expectedDraftRevision,
1458
1603
  replaceDraft: edit.replaceDraft
@@ -1462,28 +1607,65 @@ function useTurnQueue(sessionId, options = {}) {
1462
1607
  },
1463
1608
  [client, mutate, sessionId, workspaceId]
1464
1609
  );
1465
- const steerTurn = useCallback4(
1610
+ const steerTurn = useCallback3(
1466
1611
  async (turnId) => {
1612
+ const ownedTargetKey = targetKey;
1613
+ const turn = snapshotRef.current?.items.find((candidate) => candidate.id === turnId);
1614
+ if (targetKeyRef.current !== ownedTargetKey) return false;
1615
+ if (!turn) {
1616
+ setMutationError(new Error("That queued prompt changed before Steer could be sent."));
1617
+ await load();
1618
+ return false;
1619
+ }
1620
+ const optimistic = {
1621
+ turnId: turn.id,
1622
+ triggerEventId: turn.triggerEventId,
1623
+ text: turn.prompt,
1624
+ annotations: turn.annotations ?? [],
1625
+ resources: turn.resources,
1626
+ tools: turn.tools,
1627
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
1628
+ state: "sending"
1629
+ };
1630
+ setAcceptedSteers((current) => [
1631
+ ...current.filter((steer) => steer.turnId !== turnId),
1632
+ optimistic
1633
+ ]);
1467
1634
  const result = await mutate(
1468
1635
  turnId,
1469
1636
  "steer",
1470
- (current, turn) => client.steerQueueItem(workspaceId, sessionId, turnId, {
1471
- clientEventId: operationKey(),
1472
- expectedTurnVersion: turn.version,
1637
+ (current, queuedTurn, clientEventId) => client.steerQueueItem(workspaceId, sessionId, turnId, {
1638
+ clientEventId,
1639
+ expectedTurnVersion: queuedTurn.version,
1473
1640
  controlEtag: current.effectiveControl.controlEtag
1474
1641
  })
1475
1642
  );
1476
- return result !== null;
1643
+ if (targetKeyRef.current !== ownedTargetKey) return false;
1644
+ const advancedWithoutSteer = result === null && snapshotRef.current !== null && !snapshotRef.current.items.some((candidate) => candidate.id === turnId);
1645
+ if (advancedWithoutSteer) {
1646
+ setMutationError(null);
1647
+ setAcceptedSteers((current) => current.filter((steer) => steer.turnId !== turnId));
1648
+ return true;
1649
+ }
1650
+ const accepted = result !== null || snapshotRef.current?.items.some(
1651
+ (candidate) => candidate.id === turnId && candidate.metadata.delivery === "steer"
1652
+ ) === true;
1653
+ setAcceptedSteers(
1654
+ (current) => accepted ? current.map(
1655
+ (steer) => steer.turnId === turnId ? { ...steer, state: "queued" } : steer
1656
+ ) : current.filter((steer) => steer.turnId !== turnId)
1657
+ );
1658
+ return accepted;
1477
1659
  },
1478
- [client, mutate, sessionId, workspaceId]
1660
+ [client, load, mutate, sessionId, targetKey, workspaceId]
1479
1661
  );
1480
- const removeTurn = useCallback4(
1662
+ const removeTurn = useCallback3(
1481
1663
  async (turnId) => {
1482
1664
  const result = await mutate(
1483
1665
  turnId,
1484
1666
  "delete",
1485
- (_current, turn) => client.deleteQueueItem(workspaceId, sessionId, turnId, {
1486
- clientEventId: operationKey(),
1667
+ (_current, turn, clientEventId) => client.deleteQueueItem(workspaceId, sessionId, turnId, {
1668
+ clientEventId,
1487
1669
  expectedTurnVersion: turn.version,
1488
1670
  reason: "Deleted from the prompt queue"
1489
1671
  })
@@ -1499,7 +1681,7 @@ function useTurnQueue(sessionId, options = {}) {
1499
1681
  () => Object.keys(visiblePendingByTurn).length > 0,
1500
1682
  [visiblePendingByTurn]
1501
1683
  );
1502
- const mutationFor = useCallback4(
1684
+ const mutationFor = useCallback3(
1503
1685
  (turnId) => visiblePendingByTurn[turnId] ?? null,
1504
1686
  [visiblePendingByTurn]
1505
1687
  );
@@ -1522,221 +1704,448 @@ function useTurnQueue(sessionId, options = {}) {
1522
1704
  mutationFor,
1523
1705
  mutating,
1524
1706
  mutationError: identityMatches ? mutationError : null,
1525
- clearMutationError: useCallback4(() => {
1707
+ clearMutationError: useCallback3(() => {
1526
1708
  if (targetKeyRef.current === targetKey) setMutationError(null);
1527
- }, [targetKey])
1709
+ }, [targetKey]),
1710
+ acceptedSteers: identityMatches ? acceptedSteers : []
1528
1711
  };
1529
1712
  }
1530
1713
  function operationKey() {
1531
1714
  return globalThis.crypto.randomUUID();
1532
1715
  }
1533
1716
  function asError(cause) {
1534
- return cause instanceof Error ? cause : new Error(String(cause));
1717
+ return normalizeError(cause);
1535
1718
  }
1536
-
1537
- // src/hooks/use-goal.ts
1538
- import { OpenGeniApiError } from "@opengeni/sdk";
1539
- import { useCallback as useCallback5, useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
1540
- function isGoalEvent(event) {
1541
- return event.type.startsWith("goal.");
1719
+ function isOutcomeUnknownError(cause) {
1720
+ return typeof cause === "object" && cause !== null && cause.outcomeUnknown === true;
1542
1721
  }
1543
- function isGoalRefreshEvent(event) {
1544
- const type = event.type;
1545
- return isGoalEvent(event) || type.startsWith("turn.") || type.startsWith("session.") || type.startsWith("system.update.") || type.startsWith("workspace.inference.") || type.startsWith("user.");
1722
+
1723
+ // src/hooks/use-human-input.ts
1724
+ import { useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
1725
+ function isHumanInputEvent(event) {
1726
+ return event.type === "session.humanInput.requested" || event.type === "user.humanInputResponse" || event.type === "turn.completed" || event.type === "turn.failed" || event.type === "turn.cancelled";
1546
1727
  }
1547
- function useGoal(sessionId, options = {}) {
1548
- const { client, workspaceId } = useEmbeddedGoal(options);
1728
+ function useHumanInputRequests(sessionId, options = {}) {
1729
+ const { client, workspaceId, registerSessionReconciler } = useEmbeddedHumanInputSession(options);
1549
1730
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
1550
- const sharedEvents = options.events;
1551
- const sharedFeed = sharedEvents !== void 0;
1552
- const pageLive = usePageLiveActivity();
1553
- const [goal, setGoal] = useState5(null);
1554
- const [loading, setLoading] = useState5(enabled);
1555
- const [error, setError] = useState5(null);
1556
- const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
1557
- const generation = useRef4(0);
1558
- const targetKeyRef = useRef4(null);
1559
- const load = useCallback5(async () => {
1560
- if (!sessionId) {
1561
- return;
1562
- }
1563
- const ticket = ++generation.current;
1564
- try {
1565
- const fetched = await client.getGoal(workspaceId, sessionId);
1566
- if (ticket === generation.current) {
1567
- setGoal(fetched);
1568
- setError(null);
1569
- setLoading(false);
1570
- }
1571
- } catch (cause) {
1572
- if (ticket !== generation.current) {
1573
- return;
1574
- }
1575
- if (cause instanceof OpenGeniApiError && cause.status === 404) {
1576
- setGoal(null);
1577
- setError(null);
1578
- } else {
1579
- setError(cause instanceof Error ? cause : new Error(String(cause)));
1580
- }
1581
- setLoading(false);
1582
- }
1583
- }, [client, workspaceId, sessionId]);
1584
- useEffect5(() => {
1585
- const targetKey = `${workspaceId} ${sessionId ?? ""}`;
1586
- if (targetKeyRef.current !== targetKey) {
1587
- targetKeyRef.current = targetKey;
1588
- setGoal(null);
1589
- setError(null);
1590
- }
1591
- if (!enabled || !pageLive) {
1592
- setLoading(false);
1593
- return;
1594
- }
1595
- if (sharedFeed) {
1596
- setLoading(false);
1597
- return () => {
1598
- generation.current += 1;
1599
- };
1600
- }
1601
- setLoading(true);
1602
- const pollIntervalMs = options.pollIntervalMs;
1603
- if (pollIntervalMs === void 0 || pollIntervalMs <= 0) {
1604
- void load();
1605
- return () => {
1606
- generation.current += 1;
1607
- };
1608
- }
1609
- let cancelled = false;
1610
- let timer = null;
1611
- const schedule = () => {
1612
- if (cancelled) return;
1613
- timer = setTimeout(() => {
1614
- timer = null;
1615
- void load().finally(schedule);
1616
- }, pollIntervalMs);
1617
- };
1618
- void load().finally(schedule);
1619
- return () => {
1620
- cancelled = true;
1621
- if (timer !== null) clearTimeout(timer);
1622
- generation.current += 1;
1623
- };
1624
- }, [load, enabled, pageLive, workspaceId, sessionId, options.pollIntervalMs, sharedFeed]);
1625
- const scheduleRefresh = useDebouncedCallback(() => void load());
1626
- useSessionEventTrigger(client, workspaceId, sessionId, isGoalRefreshEvent, scheduleRefresh, {
1731
+ const load = useCallback4(
1732
+ async () => sessionId ? await client.listHumanInputRequests(workspaceId, sessionId, { status: "pending" }) : [],
1733
+ [client, sessionId, workspaceId]
1734
+ );
1735
+ const state = usePolledValue(load, {
1627
1736
  enabled,
1628
- ...sharedEvents !== void 0 ? { events: sharedEvents } : {}
1737
+ pollIntervalMs: options.pollIntervalMs
1629
1738
  });
1630
- const pause = useCallback5(
1631
- async (rationale) => {
1632
- if (!sessionId) {
1633
- return null;
1634
- }
1635
- const result = await run(
1636
- () => client.updateGoal(workspaceId, sessionId, {
1637
- status: "paused",
1638
- ...rationale !== void 0 ? { rationale } : {}
1639
- })
1640
- );
1641
- if (result) {
1642
- setGoal(result);
1739
+ const loadedRequests = state.data;
1740
+ const refreshRequests = state.refresh;
1741
+ const mutation = useMutationRunner(`${workspaceId}\0${sessionId ?? ""}`);
1742
+ const [respondingRequestId, setRespondingRequestId] = useState4(null);
1743
+ const [deadlineVersion, setDeadlineVersion] = useState4(0);
1744
+ const respondingRequestRef = useRef4(null);
1745
+ useEffect4(() => {
1746
+ respondingRequestRef.current = null;
1747
+ setRespondingRequestId(null);
1748
+ }, [sessionId, workspaceId]);
1749
+ useEffect4(() => {
1750
+ if (!sessionId || !enabled) return;
1751
+ return registerSessionReconciler(sessionId, "human-input", state.refresh);
1752
+ }, [enabled, registerSessionReconciler, sessionId, state.refresh]);
1753
+ const scheduleRefresh = useDebouncedCallback(() => void state.refresh());
1754
+ useSessionEventTrigger(client, workspaceId, sessionId, isHumanInputEvent, scheduleRefresh, {
1755
+ enabled,
1756
+ ...options.events !== void 0 ? { events: options.events } : {}
1757
+ });
1758
+ useEffect4(() => {
1759
+ const now2 = Date.now();
1760
+ const nextDeadline = (loadedRequests ?? []).reduce((next, request) => {
1761
+ if (request.status !== "pending" || !request.expiresAt) return next;
1762
+ const deadline = Date.parse(request.expiresAt);
1763
+ if (!Number.isFinite(deadline) || deadline <= now2) return next;
1764
+ return next === null ? deadline : Math.min(next, deadline);
1765
+ }, null);
1766
+ if (nextDeadline === null) return;
1767
+ const timer = globalThis.setTimeout(
1768
+ () => {
1769
+ setDeadlineVersion((current) => current + 1);
1770
+ void refreshRequests();
1771
+ },
1772
+ Math.min(2147483647, Math.max(0, nextDeadline - now2 + 1))
1773
+ );
1774
+ return () => globalThis.clearTimeout(timer);
1775
+ }, [loadedRequests, refreshRequests]);
1776
+ const respond = useCallback4(
1777
+ async (requestId, response) => {
1778
+ if (!sessionId || respondingRequestRef.current !== null) return null;
1779
+ respondingRequestRef.current = requestId;
1780
+ setRespondingRequestId(requestId);
1781
+ try {
1782
+ const event = await mutation.run(
1783
+ () => client.submitHumanInputResponse(workspaceId, sessionId, requestId, response, {
1784
+ clientEventId: globalThis.crypto.randomUUID()
1785
+ })
1786
+ );
1787
+ if (event) await state.refresh();
1788
+ return event;
1789
+ } finally {
1790
+ if (respondingRequestRef.current === requestId) {
1791
+ respondingRequestRef.current = null;
1792
+ setRespondingRequestId(null);
1793
+ }
1643
1794
  }
1644
- return result;
1645
1795
  },
1646
- [client, workspaceId, sessionId, run]
1796
+ [client, mutation, sessionId, state, workspaceId]
1647
1797
  );
1648
- const resume = useCallback5(async () => {
1649
- if (!sessionId) {
1650
- return null;
1651
- }
1652
- const result = await run(() => client.updateGoal(workspaceId, sessionId, { status: "active" }));
1653
- if (result) {
1654
- setGoal(result);
1655
- }
1656
- return result;
1657
- }, [client, workspaceId, sessionId, run]);
1658
- const clearGoal = useCallback5(async () => {
1659
- if (!sessionId) {
1660
- return;
1661
- }
1662
- const ok = await run(async () => {
1663
- await client.deleteGoal(workspaceId, sessionId);
1664
- return true;
1665
- });
1666
- if (ok) {
1667
- generation.current += 1;
1668
- setGoal(null);
1669
- setError(null);
1670
- }
1671
- }, [client, workspaceId, sessionId, run]);
1798
+ const now = Date.now();
1799
+ void deadlineVersion;
1672
1800
  return {
1673
- goal,
1674
- isActive: goal?.status === "active",
1675
- isPaused: goal?.status === "paused",
1676
- isCompleted: goal?.status === "completed",
1677
- loading,
1678
- error,
1679
- refresh: load,
1680
- pause,
1681
- resume,
1682
- clearGoal,
1683
- deleteGoal: clearGoal,
1684
- updating: mutating,
1685
- mutationError,
1686
- clearMutationError
1801
+ requests: [...state.data ?? []].filter((request) => isActionableHumanInputRequest(request, now)).sort(
1802
+ (left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id)
1803
+ ),
1804
+ loading: state.loading,
1805
+ error: state.error,
1806
+ refresh: state.refresh,
1807
+ respond,
1808
+ respondingRequestId,
1809
+ mutationError: mutation.mutationError,
1810
+ clearMutationError: mutation.clearMutationError
1687
1811
  };
1688
1812
  }
1689
1813
 
1690
- // src/hooks/use-session-control.ts
1691
- import { useCallback as useCallback6, useLayoutEffect as useLayoutEffect2, useRef as useRef5 } from "react";
1692
- function useSessionControl(sessionId, options = {}) {
1693
- const { client, workspaceId } = useEmbeddedSession(options);
1694
- const approvalTargetKey = `${workspaceId}\0${sessionId ?? ""}`;
1695
- const pendingApproval = useRef5(null);
1696
- useLayoutEffect2(() => {
1697
- if (pendingApproval.current?.targetKey !== approvalTargetKey) {
1698
- pendingApproval.current = null;
1814
+ // src/hooks/use-file-attachments.ts
1815
+ import { useCallback as useCallback5, useEffect as useEffect5, useRef as useRef5, useState as useState5 } from "react";
1816
+ var isImage = (file) => file.type.startsWith("image/");
1817
+ var fallbackAttachmentId = 0;
1818
+ function createAttachmentId() {
1819
+ const cryptoSource = globalThis.crypto;
1820
+ if (typeof cryptoSource?.randomUUID === "function") return cryptoSource.randomUUID();
1821
+ fallbackAttachmentId += 1;
1822
+ return `attachment:${Date.now().toString(36)}:${fallbackAttachmentId.toString(36)}`;
1823
+ }
1824
+ function secureContextRequiredErrorCode(error) {
1825
+ return typeof error === "object" && error !== null && "code" in error && error.code === "secure_context_required" ? error.code : void 0;
1826
+ }
1827
+ function useFileAttachments(options = {}) {
1828
+ const { client, workspaceId } = useEmbeddedFileAttachments(options);
1829
+ const formatError = useErrorMessage();
1830
+ const pasteFilter = options.pasteFilter ?? isImage;
1831
+ const [attachments, setAttachments] = useState5([]);
1832
+ const attachmentsRef = useRef5(attachments);
1833
+ attachmentsRef.current = attachments;
1834
+ const sources = useRef5(/* @__PURE__ */ new Map());
1835
+ const previewUrls = useRef5(/* @__PURE__ */ new Map());
1836
+ const previewRetainers = useRef5(/* @__PURE__ */ new Map());
1837
+ const pendingPreviewRevocations = useRef5(/* @__PURE__ */ new Set());
1838
+ const revokePreview = useCallback5((id) => {
1839
+ const previewUrl = previewUrls.current.get(id);
1840
+ if (!previewUrl) return;
1841
+ if ((previewRetainers.current.get(id) ?? 0) > 0) {
1842
+ pendingPreviewRevocations.current.add(id);
1843
+ return;
1699
1844
  }
1700
- }, [approvalTargetKey]);
1701
- const {
1702
- run: runControl,
1703
- mutating: controlling,
1704
- mutationError: controlError,
1705
- clearMutationError: clearControlError
1706
- } = useMutationRunner(approvalTargetKey);
1707
- const {
1708
- run: runApproval,
1709
- mutating: responding,
1710
- mutationError: approvalError,
1711
- clearMutationError: clearApprovalError
1712
- } = useMutationRunner(approvalTargetKey);
1713
- const pause = useCallback6(
1714
- async (reason) => {
1715
- if (!sessionId) {
1716
- return null;
1717
- }
1718
- return await runControl(
1719
- () => client.pauseSession(workspaceId, sessionId, reason !== void 0 ? { reason } : {})
1720
- );
1845
+ previewUrls.current.delete(id);
1846
+ pendingPreviewRevocations.current.delete(id);
1847
+ URL.revokeObjectURL(previewUrl);
1848
+ }, []);
1849
+ const revokeAllPreviews = useCallback5(() => {
1850
+ for (const id of previewUrls.current.keys()) revokePreview(id);
1851
+ }, [revokePreview]);
1852
+ const retainPreview = useCallback5(
1853
+ (id) => {
1854
+ if (!previewUrls.current.has(id)) return void 0;
1855
+ previewRetainers.current.set(id, (previewRetainers.current.get(id) ?? 0) + 1);
1856
+ let released = false;
1857
+ return () => {
1858
+ if (released) return;
1859
+ released = true;
1860
+ const remaining = (previewRetainers.current.get(id) ?? 1) - 1;
1861
+ if (remaining > 0) {
1862
+ previewRetainers.current.set(id, remaining);
1863
+ return;
1864
+ }
1865
+ previewRetainers.current.delete(id);
1866
+ if (pendingPreviewRevocations.current.has(id)) revokePreview(id);
1867
+ };
1721
1868
  },
1722
- [client, workspaceId, sessionId, runControl]
1869
+ [revokePreview]
1723
1870
  );
1724
- const resume = useCallback6(
1725
- async (reason) => {
1726
- if (!sessionId) return null;
1727
- return await runControl(
1728
- () => client.resumeSession(workspaceId, sessionId, reason !== void 0 ? { reason } : {})
1729
- );
1871
+ const committedAttachmentIds = useRef5(/* @__PURE__ */ new Set());
1872
+ useEffect5(() => {
1873
+ const currentIds = new Set(attachments.map((attachment) => attachment.id));
1874
+ for (const id of committedAttachmentIds.current) {
1875
+ if (!currentIds.has(id)) revokePreview(id);
1876
+ }
1877
+ committedAttachmentIds.current = currentIds;
1878
+ }, [attachments, revokePreview]);
1879
+ const scopeGeneration = useRef5(0);
1880
+ const previousScope = useRef5({ client, workspaceId });
1881
+ useEffect5(() => {
1882
+ const previous = previousScope.current;
1883
+ if (previous.client === client && previous.workspaceId === workspaceId) return;
1884
+ previousScope.current = { client, workspaceId };
1885
+ scopeGeneration.current += 1;
1886
+ sources.current.clear();
1887
+ revokeAllPreviews();
1888
+ setAttachments([]);
1889
+ }, [client, revokeAllPreviews, workspaceId]);
1890
+ useEffect5(
1891
+ () => () => {
1892
+ scopeGeneration.current += 1;
1893
+ sources.current.clear();
1894
+ revokeAllPreviews();
1730
1895
  },
1731
- [client, workspaceId, sessionId, runControl]
1896
+ [revokeAllPreviews]
1732
1897
  );
1733
- const decide = useCallback6(
1734
- async (approvalId, decision, message) => {
1735
- if (!sessionId) {
1736
- return null;
1737
- }
1738
- const decisionKey = JSON.stringify([approvalId, decision, message ?? null]);
1739
- if (pendingApproval.current?.decisionKey !== decisionKey) {
1898
+ const startUpload = useCallback5(
1899
+ (id, file) => {
1900
+ const generation = scopeGeneration.current;
1901
+ void client.uploadFile(workspaceId, {
1902
+ filename: file.name || "file",
1903
+ contentType: file.type || "application/octet-stream",
1904
+ data: file,
1905
+ ...options.scope ? { scope: options.scope } : {}
1906
+ }).then((asset) => {
1907
+ if (scopeGeneration.current !== generation) return;
1908
+ sources.current.delete(id);
1909
+ setAttachments(
1910
+ (current) => current.map(
1911
+ (attachment) => attachment.id === id ? {
1912
+ ...attachment,
1913
+ status: "ready",
1914
+ file: asset,
1915
+ name: asset.filename,
1916
+ contentType: asset.contentType,
1917
+ sizeBytes: asset.sizeBytes,
1918
+ errorCode: void 0,
1919
+ error: void 0,
1920
+ errorCause: void 0
1921
+ } : attachment
1922
+ )
1923
+ );
1924
+ }).catch((error) => {
1925
+ if (scopeGeneration.current !== generation) return;
1926
+ setAttachments(
1927
+ (current) => current.map(
1928
+ (attachment) => attachment.id === id ? {
1929
+ ...attachment,
1930
+ status: "failed",
1931
+ errorCode: secureContextRequiredErrorCode(error),
1932
+ error: formatError(error),
1933
+ errorCause: error
1934
+ } : attachment
1935
+ )
1936
+ );
1937
+ });
1938
+ },
1939
+ [client, workspaceId, options.scope, formatError]
1940
+ );
1941
+ const addFiles = useCallback5(
1942
+ (files) => {
1943
+ for (const file of files) {
1944
+ const id = createAttachmentId();
1945
+ sources.current.set(id, file);
1946
+ const previewUrl = isImage(file) ? URL.createObjectURL(file) : void 0;
1947
+ if (previewUrl) previewUrls.current.set(id, previewUrl);
1948
+ setAttachments((current) => [
1949
+ ...current,
1950
+ {
1951
+ id,
1952
+ name: file.name || "image",
1953
+ contentType: file.type || "application/octet-stream",
1954
+ sizeBytes: file.size,
1955
+ status: "uploading",
1956
+ ...previewUrl ? { previewUrl } : {}
1957
+ }
1958
+ ]);
1959
+ startUpload(id, file);
1960
+ }
1961
+ },
1962
+ [startUpload]
1963
+ );
1964
+ const retry = useCallback5(
1965
+ (id) => {
1966
+ const file = sources.current.get(id);
1967
+ if (!file) {
1968
+ return;
1969
+ }
1970
+ setAttachments(
1971
+ (current) => current.map(
1972
+ (attachment) => attachment.id === id ? {
1973
+ ...attachment,
1974
+ status: "uploading",
1975
+ errorCode: void 0,
1976
+ error: void 0,
1977
+ errorCause: void 0
1978
+ } : attachment
1979
+ )
1980
+ );
1981
+ startUpload(id, file);
1982
+ },
1983
+ [startUpload]
1984
+ );
1985
+ const addFromPaste = useCallback5(
1986
+ (event) => {
1987
+ const clipboardFiles = event.clipboardData?.files;
1988
+ if (!clipboardFiles) {
1989
+ return;
1990
+ }
1991
+ const files = [...clipboardFiles].filter(pasteFilter);
1992
+ if (files.length > 0) {
1993
+ addFiles(files);
1994
+ }
1995
+ },
1996
+ [addFiles, pasteFilter]
1997
+ );
1998
+ const restoreReadyFiles = useCallback5(
1999
+ (files) => {
2000
+ const incoming = /* @__PURE__ */ new Map();
2001
+ for (const file of files) {
2002
+ if (file.status === "ready" && file.workspaceId === workspaceId) {
2003
+ incoming.set(file.id, file);
2004
+ }
2005
+ }
2006
+ setAttachments((current) => {
2007
+ const unresolved = current.filter((attachment) => attachment.status !== "ready");
2008
+ const existingReady = new Map(
2009
+ current.flatMap(
2010
+ (attachment) => attachment.status === "ready" && attachment.file ? [[attachment.file.id, attachment]] : []
2011
+ )
2012
+ );
2013
+ const restored = [...incoming.values()].map((file) => {
2014
+ const existing = existingReady.get(file.id);
2015
+ return existing ? {
2016
+ ...existing,
2017
+ name: file.filename,
2018
+ contentType: file.contentType,
2019
+ sizeBytes: file.sizeBytes,
2020
+ status: "ready",
2021
+ file,
2022
+ errorCode: void 0,
2023
+ error: void 0,
2024
+ errorCause: void 0
2025
+ } : {
2026
+ id: `restored:${file.id}`,
2027
+ name: file.filename,
2028
+ contentType: file.contentType,
2029
+ sizeBytes: file.sizeBytes,
2030
+ status: "ready",
2031
+ file
2032
+ // No source File and no object URL: server metadata is the
2033
+ // only authority restored across page/device boundaries.
2034
+ };
2035
+ });
2036
+ return [...unresolved, ...restored];
2037
+ });
2038
+ },
2039
+ [workspaceId]
2040
+ );
2041
+ const loadPreview = useCallback5(
2042
+ async (id, signal) => {
2043
+ const generation = scopeGeneration.current;
2044
+ const attachment = attachmentsRef.current.find((candidate) => candidate.id === id);
2045
+ const createDownloadUrl = client.createFileDownloadUrl;
2046
+ if (!attachment || attachment.status !== "ready" || !attachment.file || !attachment.contentType.startsWith("image/") || typeof createDownloadUrl !== "function" || signal?.aborted) {
2047
+ return void 0;
2048
+ }
2049
+ const fileId = attachment.file.id;
2050
+ const signed = await createDownloadUrl.call(client, workspaceId, fileId, { signal });
2051
+ if (scopeGeneration.current !== generation || signal?.aborted) return void 0;
2052
+ const current = attachmentsRef.current.find((candidate) => candidate.id === id);
2053
+ if (current?.status !== "ready" || current.file?.id !== fileId) return void 0;
2054
+ return signed.url;
2055
+ },
2056
+ [client, workspaceId]
2057
+ );
2058
+ const remove = useCallback5(
2059
+ (id) => {
2060
+ sources.current.delete(id);
2061
+ revokePreview(id);
2062
+ setAttachments((current) => current.filter((attachment) => attachment.id !== id));
2063
+ },
2064
+ [revokePreview]
2065
+ );
2066
+ const removeReadyFiles = useCallback5((fileIds) => {
2067
+ const accepted = new Set(fileIds);
2068
+ if (accepted.size === 0) return;
2069
+ setAttachments(
2070
+ (current) => current.filter((attachment) => {
2071
+ return !(attachment.status === "ready" && attachment.file !== void 0 && accepted.has(attachment.file.id));
2072
+ })
2073
+ );
2074
+ }, []);
2075
+ const clear = useCallback5(() => {
2076
+ sources.current.clear();
2077
+ revokeAllPreviews();
2078
+ setAttachments([]);
2079
+ }, [revokeAllPreviews]);
2080
+ return {
2081
+ attachments,
2082
+ readyResources: attachments.flatMap(
2083
+ (attachment) => attachment.status === "ready" && attachment.file ? [{ kind: "file", fileId: attachment.file.id }] : []
2084
+ ),
2085
+ uploading: attachments.some((attachment) => attachment.status === "uploading"),
2086
+ hasUnresolved: attachments.some((attachment) => attachment.status !== "ready"),
2087
+ addFiles,
2088
+ addFromPaste,
2089
+ restoreReadyFiles,
2090
+ ...typeof client.createFileDownloadUrl === "function" ? { loadPreview } : {},
2091
+ retry,
2092
+ retainPreview,
2093
+ remove,
2094
+ removeReadyFiles,
2095
+ clear
2096
+ };
2097
+ }
2098
+
2099
+ // src/hooks/use-session-control.ts
2100
+ import { useCallback as useCallback6, useLayoutEffect as useLayoutEffect2, useRef as useRef6 } from "react";
2101
+ function useSessionControl(sessionId, options = {}) {
2102
+ const { client, workspaceId } = useEmbeddedSession(options);
2103
+ const approvalTargetKey = `${workspaceId}\0${sessionId ?? ""}`;
2104
+ const pendingApproval = useRef6(null);
2105
+ useLayoutEffect2(() => {
2106
+ if (pendingApproval.current?.targetKey !== approvalTargetKey) {
2107
+ pendingApproval.current = null;
2108
+ }
2109
+ }, [approvalTargetKey]);
2110
+ const {
2111
+ run: runControl,
2112
+ mutating: controlling,
2113
+ mutationError: controlError,
2114
+ clearMutationError: clearControlError
2115
+ } = useMutationRunner(approvalTargetKey);
2116
+ const {
2117
+ run: runApproval,
2118
+ mutating: responding,
2119
+ mutationError: approvalError,
2120
+ clearMutationError: clearApprovalError
2121
+ } = useMutationRunner(approvalTargetKey);
2122
+ const pause = useCallback6(
2123
+ async (reason) => {
2124
+ if (!sessionId) {
2125
+ return null;
2126
+ }
2127
+ return await runControl(
2128
+ () => client.pauseSession(workspaceId, sessionId, reason !== void 0 ? { reason } : {})
2129
+ );
2130
+ },
2131
+ [client, workspaceId, sessionId, runControl]
2132
+ );
2133
+ const resume = useCallback6(
2134
+ async (reason) => {
2135
+ if (!sessionId) return null;
2136
+ return await runControl(
2137
+ () => client.resumeSession(workspaceId, sessionId, reason !== void 0 ? { reason } : {})
2138
+ );
2139
+ },
2140
+ [client, workspaceId, sessionId, runControl]
2141
+ );
2142
+ const decide = useCallback6(
2143
+ async (approvalId, decision, message) => {
2144
+ if (!sessionId) {
2145
+ return null;
2146
+ }
2147
+ const decisionKey = JSON.stringify([approvalId, decision, message ?? null]);
2148
+ if (pendingApproval.current?.decisionKey !== decisionKey) {
1740
2149
  pendingApproval.current = {
1741
2150
  targetKey: approvalTargetKey,
1742
2151
  decisionKey,
@@ -1784,193 +2193,10 @@ function useSessionControl(sessionId, options = {}) {
1784
2193
  };
1785
2194
  }
1786
2195
 
1787
- // src/hooks/use-session-mcp-approval-policy.ts
1788
- import { useCallback as useCallback7, useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useMemo as useMemo4, useRef as useRef6, useState as useState6 } from "react";
1789
- function isSessionMcpApprovalPolicyEvent(event, serverId) {
1790
- if (event.type !== "session.mcp.approval_policy.updated") {
1791
- return false;
1792
- }
1793
- if (serverId === void 0) {
1794
- return true;
1795
- }
1796
- const payload = event.payload;
1797
- return payload?.serverId === serverId;
1798
- }
1799
- function useSessionMcpApprovalPolicy(sessionId, serverId, options = {}) {
1800
- const { client, workspaceId } = useEmbeddedSessionMcpApprovalPolicy(options);
1801
- const enabled = (options.enabled ?? true) && Boolean(sessionId && serverId);
1802
- const [override, setOverride] = useState6(null);
1803
- const targetKey = `${workspaceId}\0${sessionId ?? ""}\0${serverId ?? ""}`;
1804
- const authoritativeGeneration = useRef6(0);
1805
- const mutationIdentity = useMemo4(() => ({ client, targetKey }), [client, targetKey]);
1806
- const { run, mutating, mutationError, clearMutationError } = useMutationRunner(mutationIdentity);
1807
- useLayoutEffect3(() => {
1808
- authoritativeGeneration.current += 1;
1809
- setOverride(null);
1810
- }, [client, targetKey]);
1811
- const load = useCallback7(async () => {
1812
- const generation = authoritativeGeneration.current;
1813
- if (!sessionId || !serverId) {
1814
- return { targetKey, generation, server: null };
1815
- }
1816
- const session = await client.getSession(workspaceId, sessionId);
1817
- return {
1818
- targetKey,
1819
- generation,
1820
- server: session.mcpServers.find((server2) => server2.id === serverId) ?? null
1821
- };
1822
- }, [client, serverId, sessionId, targetKey, workspaceId]);
1823
- const {
1824
- data,
1825
- loading,
1826
- error: loadError,
1827
- refresh
1828
- } = usePolledValue(load, {
1829
- enabled,
1830
- pollIntervalMs: options.pollIntervalMs
1831
- });
1832
- useEffect6(() => {
1833
- if (data?.targetKey === targetKey && data.generation === authoritativeGeneration.current) {
1834
- setOverride(null);
1835
- }
1836
- }, [data, targetKey]);
1837
- const scheduleRefresh = useDebouncedCallback(() => void refresh());
1838
- const matchesPolicyEvent = useCallback7(
1839
- (event) => isSessionMcpApprovalPolicyEvent(event, serverId ?? void 0),
1840
- [serverId]
1841
- );
1842
- useSessionEventTrigger(
1843
- client,
1844
- workspaceId,
1845
- sessionId,
1846
- matchesPolicyEvent,
1847
- scheduleRefresh,
1848
- {
1849
- enabled,
1850
- ...options.events !== void 0 ? { events: options.events } : {}
1851
- },
1852
- async () => await refresh()
1853
- );
1854
- const update = useCallback7(
1855
- async (policy) => {
1856
- if (!sessionId || !serverId) {
1857
- return null;
1858
- }
1859
- const response = await run(
1860
- () => client.updateSessionMcpApprovalPolicy(workspaceId, sessionId, serverId, {
1861
- requireApproval: policy
1862
- })
1863
- );
1864
- if (response) {
1865
- authoritativeGeneration.current += 1;
1866
- setOverride(response.server);
1867
- void refresh();
1868
- }
1869
- return response;
1870
- },
1871
- [client, refresh, run, serverId, sessionId, workspaceId]
1872
- );
1873
- const server = override ?? data?.server ?? null;
1874
- return {
1875
- server,
1876
- policy: server?.requireApproval ?? null,
1877
- loading,
1878
- error: mutationError ?? loadError,
1879
- refresh: async () => {
1880
- await refresh();
1881
- },
1882
- update,
1883
- updating: mutating,
1884
- clearError: clearMutationError
1885
- };
1886
- }
1887
-
1888
- // src/hooks/use-session-lineage.ts
1889
- import { useCallback as useCallback8, useEffect as useEffect7, useRef as useRef7 } from "react";
1890
- function isLineageRefreshEvent(event) {
1891
- if (event.type === "session.status.changed" || event.type === "session.created") {
1892
- return true;
1893
- }
1894
- if (event.type === "agent.toolCall.output") {
1895
- const payload = event.payload;
1896
- const name = typeof payload?.name === "string" ? payload.name : typeof payload?.toolName === "string" ? payload.toolName : "";
1897
- return name === "session_create" || name.endsWith("__session_create");
1898
- }
1899
- return false;
1900
- }
1901
- function isSessionCreateToolCallCreated(event) {
1902
- if (event.type !== "agent.toolCall.created") {
1903
- return false;
1904
- }
1905
- const payload = event.payload;
1906
- const name = typeof payload?.name === "string" ? payload.name : typeof payload?.toolName === "string" ? payload.toolName : "";
1907
- return name === "session_create" || name.endsWith("__session_create");
1908
- }
1909
- function useSessionLineage(sessionId, options = {}) {
1910
- const { client, workspaceId, workspaceControlEvent, registerSessionReconciler } = useEmbeddedSessionLineage(options);
1911
- const enabled = (options.enabled ?? true) && Boolean(sessionId);
1912
- const load = useCallback8(
1913
- async () => sessionId ? await client.getSessionLineage(workspaceId, sessionId) : { ancestors: [], children: [], truncated: false },
1914
- [client, workspaceId, sessionId]
1915
- );
1916
- const state = usePolledValue(load, { pollIntervalMs: options.pollIntervalMs, enabled });
1917
- const refresh = state.refresh;
1918
- useEffect7(() => {
1919
- if (enabled && workspaceControlEvent) void refresh();
1920
- }, [enabled, refresh, workspaceControlEvent]);
1921
- useEffect7(() => {
1922
- if (!sessionId || !enabled) return;
1923
- return registerSessionReconciler(sessionId, "lineage", refresh);
1924
- }, [enabled, refresh, registerSessionReconciler, sessionId]);
1925
- const refreshSoon = useDebouncedCallback(() => void refresh(), 150);
1926
- const delayedChildRefreshRef = useRef7(null);
1927
- useEffect7(() => {
1928
- return () => {
1929
- if (delayedChildRefreshRef.current !== null) {
1930
- clearTimeout(delayedChildRefreshRef.current);
1931
- delayedChildRefreshRef.current = null;
1932
- }
1933
- };
1934
- }, [sessionId, workspaceId]);
1935
- const refreshAfterChildCreate = useCallback8(() => {
1936
- void refresh();
1937
- if (delayedChildRefreshRef.current !== null) {
1938
- clearTimeout(delayedChildRefreshRef.current);
1939
- }
1940
- delayedChildRefreshRef.current = setTimeout(() => {
1941
- delayedChildRefreshRef.current = null;
1942
- void refresh();
1943
- }, 2500);
1944
- }, [refresh]);
1945
- useSessionEventTrigger(
1946
- client,
1947
- workspaceId,
1948
- sessionId,
1949
- isLineageRefreshEvent,
1950
- refreshSoon,
1951
- // SHARED-FEED ONLY: without a caller-provided events log the trigger would
1952
- // open its OWN streamEvents tail — a second live SSE connection next to the
1953
- // session route's useSessionEvents. A caller with no feed opts into polling
1954
- // (pollIntervalMs), never a duplicate stream.
1955
- { events: options.events, enabled: enabled && options.events !== void 0 }
1956
- );
1957
- useSessionEventTrigger(
1958
- client,
1959
- workspaceId,
1960
- sessionId,
1961
- isSessionCreateToolCallCreated,
1962
- refreshAfterChildCreate,
1963
- { events: options.events, enabled: enabled && options.events !== void 0 }
1964
- );
1965
- return {
1966
- lineage: state.data,
1967
- loading: state.loading,
1968
- error: state.error,
1969
- refresh
1970
- };
1971
- }
1972
-
1973
2196
  // src/approvals.ts
2197
+ import {
2198
+ parseToolDisplayMetadata
2199
+ } from "@opengeni/sdk";
1974
2200
  function approvalsFromRequiresAction(payload) {
1975
2201
  const approvals = payload && typeof payload === "object" ? payload.approvals : void 0;
1976
2202
  if (!Array.isArray(approvals)) {
@@ -1979,11 +2205,13 @@ function approvalsFromRequiresAction(payload) {
1979
2205
  return approvals.map((approval, index) => {
1980
2206
  const raw = approval && typeof approval === "object" ? approval : {};
1981
2207
  const rawItem = raw.rawItem && typeof raw.rawItem === "object" ? raw.rawItem : {};
2208
+ const display = parseToolDisplayMetadata(raw.display);
1982
2209
  return {
1983
2210
  // Mirror the runtime's canonical interruption identity before retaining
1984
2211
  // the legacy top-level callId fallback for already-normalized payloads.
1985
2212
  id: String(rawItem.callId ?? rawItem.id ?? raw.id ?? raw.callId ?? raw.name ?? index),
1986
2213
  name: String(raw.name ?? raw.toolName ?? rawItem.name ?? "approval"),
2214
+ ...display ? { display } : {},
1987
2215
  arguments: raw.arguments ?? rawItem.arguments,
1988
2216
  raw: approval
1989
2217
  };
@@ -2022,140 +2250,80 @@ function projectPendingApprovals(events) {
2022
2250
  return pending;
2023
2251
  }
2024
2252
 
2025
- // src/human-input.ts
2026
- function humanInputRequestFromEvent(event) {
2027
- if (event.type !== "session.humanInput.requested" || !isRecord(event.payload)) return null;
2028
- const request = event.payload.request;
2029
- if (!isRecord(request) || typeof request.id !== "string" || !Array.isArray(request.questions)) {
2030
- return null;
2031
- }
2032
- return {
2033
- id: request.id,
2034
- turnId: event.turnId ?? null,
2035
- questions: request.questions,
2036
- allowSkip: request.allowSkip === true,
2037
- expiresAt: typeof request.expiresAt === "string" ? request.expiresAt : null
2038
- };
2039
- }
2040
- function projectPendingHumanInputRequests(events) {
2041
- const pending = /* @__PURE__ */ new Map();
2042
- for (const event of events) {
2043
- const requested = humanInputRequestFromEvent(event);
2044
- if (requested) {
2045
- pending.set(requested.id, requested);
2046
- continue;
2047
- }
2048
- if (event.type === "user.humanInputResponse" && isRecord(event.payload)) {
2049
- if (typeof event.payload.requestId === "string") pending.delete(event.payload.requestId);
2050
- continue;
2051
- }
2052
- if (event.type === "turn.completed" || event.type === "turn.failed" || event.type === "turn.cancelled") {
2053
- for (const [id, request] of pending) {
2054
- if (request.turnId === null || event.turnId === null || event.turnId === void 0 || request.turnId === event.turnId) {
2055
- pending.delete(id);
2056
- }
2057
- }
2058
- }
2059
- }
2060
- return [...pending.values()];
2061
- }
2062
- function isRecord(value) {
2063
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2064
- }
2065
-
2066
- // src/hooks/use-human-input.ts
2067
- import { useCallback as useCallback9, useEffect as useEffect8, useRef as useRef8, useState as useState7 } from "react";
2068
- function isHumanInputEvent(event) {
2069
- return event.type === "session.humanInput.requested" || event.type === "user.humanInputResponse" || event.type === "turn.completed" || event.type === "turn.failed" || event.type === "turn.cancelled";
2070
- }
2071
- function useHumanInputRequests(sessionId, options = {}) {
2072
- const { client, workspaceId, registerSessionReconciler } = useEmbeddedHumanInputSession(options);
2073
- const enabled = (options.enabled ?? true) && Boolean(sessionId);
2074
- const load = useCallback9(
2075
- async () => sessionId ? await client.listHumanInputRequests(workspaceId, sessionId, { status: "pending" }) : [],
2076
- [client, sessionId, workspaceId]
2253
+ // src/conversation-timeline.ts
2254
+ function conversationTimeline(items, queue, composer) {
2255
+ const queued = new Set(
2256
+ queue.queue.filter((turn) => turn.metadata.delivery !== "steer").map((turn) => turn.triggerEventId)
2077
2257
  );
2078
- const state = usePolledValue(load, {
2079
- enabled,
2080
- pollIntervalMs: options.pollIntervalMs
2081
- });
2082
- const mutation = useMutationRunner(`${workspaceId}\0${sessionId ?? ""}`);
2083
- const [respondingRequestId, setRespondingRequestId] = useState7(null);
2084
- const respondingRequestRef = useRef8(null);
2085
- useEffect8(() => {
2086
- respondingRequestRef.current = null;
2087
- setRespondingRequestId(null);
2088
- }, [sessionId, workspaceId]);
2089
- useEffect8(() => {
2090
- if (!sessionId || !enabled) return;
2091
- return registerSessionReconciler(sessionId, "human-input", state.refresh);
2092
- }, [enabled, registerSessionReconciler, sessionId, state.refresh]);
2093
- const scheduleRefresh = useDebouncedCallback(() => void state.refresh());
2094
- useSessionEventTrigger(client, workspaceId, sessionId, isHumanInputEvent, scheduleRefresh, {
2095
- enabled,
2096
- ...options.events !== void 0 ? { events: options.events } : {}
2097
- });
2098
- const respond = useCallback9(
2099
- async (requestId, response) => {
2100
- if (!sessionId || respondingRequestRef.current !== null) return null;
2101
- respondingRequestRef.current = requestId;
2102
- setRespondingRequestId(requestId);
2103
- try {
2104
- const event = await mutation.run(
2105
- () => client.submitHumanInputResponse(workspaceId, sessionId, requestId, response, {
2106
- clientEventId: globalThis.crypto.randomUUID()
2107
- })
2108
- );
2109
- if (event) await state.refresh();
2110
- return event;
2111
- } finally {
2112
- if (respondingRequestRef.current === requestId) {
2113
- respondingRequestRef.current = null;
2114
- setRespondingRequestId(null);
2115
- }
2116
- }
2117
- },
2118
- [client, mutation, sessionId, state, workspaceId]
2258
+ const pending = composer.optimisticMessages ?? [];
2259
+ const pendingQueue = new Set(
2260
+ pending.filter(
2261
+ (message) => message.destination === "queue" && !(message.turnId && message.appliedQueueVersion != null && queue.snapshot && queue.snapshot.version >= message.appliedQueueVersion && !queue.queue.some((turn) => turn.id === message.turnId))
2262
+ ).map((message) => `user-message:${message.clientEventId}`)
2119
2263
  );
2120
- return {
2121
- requests: [...state.data ?? []].sort(
2122
- (left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id)
2123
- ),
2124
- loading: state.loading,
2125
- error: state.error,
2126
- refresh: state.refresh,
2127
- respond,
2128
- respondingRequestId,
2129
- mutationError: mutation.mutationError,
2130
- clearMutationError: mutation.clearMutationError
2131
- };
2264
+ const visible = items.filter(
2265
+ (item) => item.kind !== "user-message" || !queued.has(item.id) && !pendingQueue.has(item.reconciliationKey ?? "")
2266
+ );
2267
+ const keys = new Set(
2268
+ visible.flatMap((item) => item.kind === "user-message" ? [item.reconciliationKey] : [])
2269
+ );
2270
+ const optimistic = pending.filter(
2271
+ (message) => (message.destination === "chat" || message.state === "failed") && !keys.has(`user-message:${message.clientEventId}`) && !queue.queue.some((turn) => turn.id === message.turnId)
2272
+ ).map((message) => ({
2273
+ kind: "user-message",
2274
+ id: `optimistic:${message.clientEventId}`,
2275
+ reconciliationKey: `user-message:${message.clientEventId}`,
2276
+ text: message.text,
2277
+ annotations: message.annotations.map((annotation, ordinal) => ({ ...annotation, ordinal })),
2278
+ resources: message.resources,
2279
+ tools: [],
2280
+ occurredAt: message.occurredAt,
2281
+ delivery: {
2282
+ state: message.state,
2283
+ ...message.error ? { error: message.error } : {},
2284
+ ...message.state === "failed" ? {
2285
+ ...message.retryable !== false && composer.retryOptimisticMessage ? { onRetry: () => composer.retryOptimisticMessage?.(message.clientEventId) } : {},
2286
+ ...message.retryable === false && composer.restoreOptimisticMessage ? { onEdit: () => composer.restoreOptimisticMessage?.(message.clientEventId) } : {},
2287
+ onRemove: () => composer.removeOptimisticMessage?.(message.clientEventId)
2288
+ } : {}
2289
+ }
2290
+ }));
2291
+ const ids = new Set(visible.map((item) => item.id));
2292
+ const steers = (queue.acceptedSteers ?? []).filter((steer) => !ids.has(steer.triggerEventId)).map((steer) => ({
2293
+ kind: "user-message",
2294
+ id: steer.triggerEventId,
2295
+ text: steer.text,
2296
+ annotations: steer.annotations,
2297
+ resources: steer.resources,
2298
+ tools: steer.tools,
2299
+ occurredAt: steer.occurredAt,
2300
+ delivery: { state: steer.state }
2301
+ }));
2302
+ return [...visible, ...optimistic, ...steers];
2132
2303
  }
2133
2304
 
2134
2305
  export {
2135
- isTitleEvent,
2136
- useSession,
2306
+ isActionableHumanInputRequest,
2307
+ humanInputRequestFromEvent,
2308
+ projectPendingHumanInputRequests,
2309
+ invokeOlderHistoryLoaderWithReceiptCapture,
2310
+ createOlderHistoryLoadReceipt,
2137
2311
  SESSION_EVENT_BROWSER_MAX_BYTES,
2138
2312
  SESSION_EVENT_BROWSER_MAX_COUNT,
2139
- SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES,
2140
2313
  SESSION_EVENT_BROWSER_PENDING_MAX_BYTES,
2141
2314
  SESSION_EVENT_BROWSER_PENDING_MAX_COUNT,
2142
2315
  useSessionEvents,
2143
2316
  boundBrowserSessionEventWindow,
2144
- useFileAttachments,
2317
+ isTitleEvent,
2318
+ useSession,
2145
2319
  isTurnQueueEvent,
2146
2320
  useTurnQueue,
2147
- isGoalEvent,
2148
- useGoal,
2321
+ isHumanInputEvent,
2322
+ useHumanInputRequests,
2323
+ useFileAttachments,
2149
2324
  useSessionControl,
2150
- isSessionMcpApprovalPolicyEvent,
2151
- useSessionMcpApprovalPolicy,
2152
- isLineageRefreshEvent,
2153
- useSessionLineage,
2154
2325
  approvalsFromRequiresAction,
2155
2326
  projectPendingApprovals,
2156
- humanInputRequestFromEvent,
2157
- projectPendingHumanInputRequests,
2158
- isHumanInputEvent,
2159
- useHumanInputRequests
2327
+ conversationTimeline
2160
2328
  };
2161
- //# sourceMappingURL=chunk-B6WPZSTT.js.map
2329
+ //# sourceMappingURL=chunk-SJZDKVW2.js.map