@opengeni/react 3.3.2 → 3.4.0

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 (855) hide show
  1. package/README.md +769 -22
  2. package/dist/accounts.js +2 -0
  3. package/dist/accounts.js.map +1 -1
  4. package/dist/approvals.d.ts +2 -1
  5. package/dist/artifacts-document.js +5 -3
  6. package/dist/artifacts-presentation.js +5 -3
  7. package/dist/artifacts-spreadsheet.d.ts +1 -1
  8. package/dist/artifacts-spreadsheet.js +5 -3
  9. package/dist/artifacts.d.ts +5 -1
  10. package/dist/artifacts.js +536 -42
  11. package/dist/artifacts.js.map +1 -1
  12. package/dist/browser-viewer-ZWR6MWJ7.js +19 -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-4EUKJXUO.js +14 -0
  16. package/dist/chunk-2DDJ6YH6.js +41 -0
  17. package/dist/chunk-2DDJ6YH6.js.map +1 -0
  18. package/dist/chunk-2HVLQ26H.js +183 -0
  19. package/dist/chunk-2HVLQ26H.js.map +1 -0
  20. package/dist/{chunk-XYRDDSXF.js → chunk-2I6R3GCZ.js} +4285 -3980
  21. package/dist/chunk-2I6R3GCZ.js.map +1 -0
  22. package/dist/{chunk-JAQ5K7W6.js → chunk-2SFFYIH3.js} +1178 -166
  23. package/dist/chunk-2SFFYIH3.js.map +1 -0
  24. package/dist/chunk-3PVDYTTB.js +24 -0
  25. package/dist/chunk-3PVDYTTB.js.map +1 -0
  26. package/dist/chunk-3RJKUVCA.js +14068 -0
  27. package/dist/chunk-3RJKUVCA.js.map +1 -0
  28. package/dist/chunk-3UZCWK46.js +298 -0
  29. package/dist/chunk-3UZCWK46.js.map +1 -0
  30. package/dist/chunk-47CKUXTW.js +293 -0
  31. package/dist/chunk-47CKUXTW.js.map +1 -0
  32. package/dist/chunk-4QYP6QUI.js +109 -0
  33. package/dist/chunk-4QYP6QUI.js.map +1 -0
  34. package/dist/chunk-5EY26VZ7.js +1053 -0
  35. package/dist/chunk-5EY26VZ7.js.map +1 -0
  36. package/dist/chunk-5I4VRWBB.js +56 -0
  37. package/dist/chunk-5I4VRWBB.js.map +1 -0
  38. package/dist/chunk-5WOR7UCL.js +191 -0
  39. package/dist/chunk-5WOR7UCL.js.map +1 -0
  40. package/dist/chunk-5YJLGOM5.js +51 -0
  41. package/dist/chunk-5YJLGOM5.js.map +1 -0
  42. package/dist/chunk-6JFN3OWG.js +386 -0
  43. package/dist/chunk-6JFN3OWG.js.map +1 -0
  44. package/dist/chunk-7QJT5O4S.js +35 -0
  45. package/dist/chunk-7QJT5O4S.js.map +1 -0
  46. package/dist/chunk-A55KV72M.js +402 -0
  47. package/dist/chunk-A55KV72M.js.map +1 -0
  48. package/dist/chunk-ADWCLI4G.js +2028 -0
  49. package/dist/chunk-ADWCLI4G.js.map +1 -0
  50. package/dist/chunk-B27E3V37.js +29 -0
  51. package/dist/chunk-B27E3V37.js.map +1 -0
  52. package/dist/chunk-B7TC2A33.js +83 -0
  53. package/dist/chunk-B7TC2A33.js.map +1 -0
  54. package/dist/chunk-BLL56F7R.js +385 -0
  55. package/dist/chunk-BLL56F7R.js.map +1 -0
  56. package/dist/chunk-BOFEQC6K.js +162 -0
  57. package/dist/chunk-BOFEQC6K.js.map +1 -0
  58. package/dist/chunk-CM7LVUKC.js +35 -0
  59. package/dist/chunk-CM7LVUKC.js.map +1 -0
  60. package/dist/chunk-CORUP6JB.js +122 -0
  61. package/dist/chunk-CORUP6JB.js.map +1 -0
  62. package/dist/chunk-CSX5FFKZ.js +35 -0
  63. package/dist/chunk-CSX5FFKZ.js.map +1 -0
  64. package/dist/chunk-EGVHJKGJ.js +3125 -0
  65. package/dist/chunk-EGVHJKGJ.js.map +1 -0
  66. package/dist/{chunk-NEXLLTP4.js → chunk-G76LSA76.js} +6 -5
  67. package/dist/chunk-G76LSA76.js.map +1 -0
  68. package/dist/{chunk-PEI6RANI.js → chunk-GQMRL52F.js} +311 -75
  69. package/dist/chunk-GQMRL52F.js.map +1 -0
  70. package/dist/{chunk-ELIYWBZR.js → chunk-GUUXOISA.js} +1113 -457
  71. package/dist/chunk-GUUXOISA.js.map +1 -0
  72. package/dist/{chunk-HUIKIDM5.js → chunk-I7X2P4FU.js} +225 -116
  73. package/dist/chunk-I7X2P4FU.js.map +1 -0
  74. package/dist/chunk-IGFZFLI2.js +1390 -0
  75. package/dist/chunk-IGFZFLI2.js.map +1 -0
  76. package/dist/{chunk-4MDLEHXL.js → chunk-IMSUVYQB.js} +26 -13
  77. package/dist/chunk-IMSUVYQB.js.map +1 -0
  78. package/dist/chunk-IOYHDEP3.js +38 -0
  79. package/dist/chunk-IOYHDEP3.js.map +1 -0
  80. package/dist/chunk-IZV5FTJC.js +55 -0
  81. package/dist/chunk-IZV5FTJC.js.map +1 -0
  82. package/dist/chunk-KF33GTWN.js +343 -0
  83. package/dist/chunk-KF33GTWN.js.map +1 -0
  84. package/dist/chunk-KSGQ5ODA.js +457 -0
  85. package/dist/chunk-KSGQ5ODA.js.map +1 -0
  86. package/dist/{chunk-ZPBTI3TE.js → chunk-KZC6QRNR.js} +1513 -697
  87. package/dist/chunk-KZC6QRNR.js.map +1 -0
  88. package/dist/chunk-LJUMYUBO.js +218 -0
  89. package/dist/chunk-LJUMYUBO.js.map +1 -0
  90. package/dist/chunk-LSW2OJVV.js +380 -0
  91. package/dist/chunk-LSW2OJVV.js.map +1 -0
  92. package/dist/chunk-M357MHH3.js +2 -0
  93. package/dist/chunk-M357MHH3.js.map +1 -0
  94. package/dist/chunk-MK2TQO4Y.js +24 -0
  95. package/dist/chunk-MK2TQO4Y.js.map +1 -0
  96. package/dist/chunk-N7S72EXK.js +25 -0
  97. package/dist/chunk-N7S72EXK.js.map +1 -0
  98. package/dist/{chunk-YVCHAQZY.js → chunk-NKMYZFHA.js} +616 -621
  99. package/dist/chunk-NKMYZFHA.js.map +1 -0
  100. package/dist/chunk-NX4ADH73.js +14 -0
  101. package/dist/chunk-NX4ADH73.js.map +1 -0
  102. package/dist/chunk-O7F6UJO4.js +71 -0
  103. package/dist/chunk-O7F6UJO4.js.map +1 -0
  104. package/dist/chunk-OYQQJL3J.js +16 -0
  105. package/dist/chunk-OYQQJL3J.js.map +1 -0
  106. package/dist/{chunk-H5YGYNXS.js → chunk-P4BNUO57.js} +60 -17
  107. package/dist/chunk-P4BNUO57.js.map +1 -0
  108. package/dist/{chunk-JVYQRRC4.js → chunk-PEHTX3V7.js} +21 -1
  109. package/dist/chunk-PEHTX3V7.js.map +1 -0
  110. package/dist/chunk-PMV53IMM.js +26 -0
  111. package/dist/chunk-PMV53IMM.js.map +1 -0
  112. package/dist/{chunk-KCGXPP5V.js → chunk-PYEOPA4M.js} +13 -7
  113. package/dist/chunk-PYEOPA4M.js.map +1 -0
  114. package/dist/{chunk-KZ73RL76.js → chunk-QHUE7E4N.js} +8 -3
  115. package/dist/chunk-QHUE7E4N.js.map +1 -0
  116. package/dist/chunk-QV3DJVRZ.js +11 -0
  117. package/dist/chunk-QV3DJVRZ.js.map +1 -0
  118. package/dist/chunk-R5SBNG7S.js +810 -0
  119. package/dist/chunk-R5SBNG7S.js.map +1 -0
  120. package/dist/chunk-RI42H5IU.js +326 -0
  121. package/dist/chunk-RI42H5IU.js.map +1 -0
  122. package/dist/chunk-S2QH6EZ5.js +211 -0
  123. package/dist/chunk-S2QH6EZ5.js.map +1 -0
  124. package/dist/chunk-SHLZMAJN.js +304 -0
  125. package/dist/chunk-SHLZMAJN.js.map +1 -0
  126. package/dist/chunk-TGXK4BAZ.js +293 -0
  127. package/dist/chunk-TGXK4BAZ.js.map +1 -0
  128. package/dist/{chunk-5YK2ZDIG.js → chunk-TNEZNMSN.js} +4 -10
  129. package/dist/chunk-TNEZNMSN.js.map +1 -0
  130. package/dist/{chunk-22KP6LR5.js → chunk-TXS65BB2.js} +3 -1
  131. package/dist/{chunk-22KP6LR5.js.map → chunk-TXS65BB2.js.map} +1 -1
  132. package/dist/chunk-UNJPZGNK.js +63 -0
  133. package/dist/chunk-UNJPZGNK.js.map +1 -0
  134. package/dist/{chunk-IOLVN74D.js → chunk-V5ZUJT2T.js} +175 -42
  135. package/dist/chunk-V5ZUJT2T.js.map +1 -0
  136. package/dist/chunk-WBJY57A3.js +402 -0
  137. package/dist/chunk-WBJY57A3.js.map +1 -0
  138. package/dist/chunk-WIUKB3LD.js +1911 -0
  139. package/dist/chunk-WIUKB3LD.js.map +1 -0
  140. package/dist/{chunk-ICA6R6TW.js → chunk-WNN7NO2N.js} +1451 -355
  141. package/dist/chunk-WNN7NO2N.js.map +1 -0
  142. package/dist/{chunk-3AGBGTRT.js → chunk-X5UVUIMF.js} +1712 -1174
  143. package/dist/chunk-X5UVUIMF.js.map +1 -0
  144. package/dist/chunk-XDA5QJE7.js +402 -0
  145. package/dist/chunk-XDA5QJE7.js.map +1 -0
  146. package/dist/{chunk-VZTQ73XT.js → chunk-XEA5BCMA.js} +3 -1
  147. package/dist/{chunk-VZTQ73XT.js.map → chunk-XEA5BCMA.js.map} +1 -1
  148. package/dist/chunk-XHF7OYAN.js +1150 -0
  149. package/dist/chunk-XHF7OYAN.js.map +1 -0
  150. package/dist/chunk-YSRBFJMQ.js +43 -0
  151. package/dist/chunk-YSRBFJMQ.js.map +1 -0
  152. package/dist/chunk-ZOZAPAUO.js +174 -0
  153. package/dist/chunk-ZOZAPAUO.js.map +1 -0
  154. package/dist/chunk-ZQV5W7YC.js +809 -0
  155. package/dist/chunk-ZQV5W7YC.js.map +1 -0
  156. package/dist/chunk-ZUWF4NF7.js +13 -0
  157. package/dist/chunk-ZUWF4NF7.js.map +1 -0
  158. package/dist/client.d.ts +16 -7
  159. package/dist/clipboard.js +3 -1
  160. package/dist/commands/types.d.ts +2 -0
  161. package/dist/components/anthropic-mark.d.ts +3 -0
  162. package/dist/components/approval-surface.d.ts +19 -6
  163. package/dist/components/artifacts/artifact-chrome.d.ts +120 -0
  164. package/dist/components/artifacts/artifact-sandbox.d.ts +30 -0
  165. package/dist/components/artifacts/artifact-surface.d.ts +4 -2
  166. package/dist/components/artifacts/chat-interactive-block.d.ts +29 -0
  167. package/dist/components/artifacts/deferred-chat-media.d.ts +15 -0
  168. package/dist/components/artifacts/document-editor.d.ts +4 -2
  169. package/dist/components/artifacts/editable-artifact-ui.d.ts +2 -1
  170. package/dist/components/artifacts/editable-artifact-view.d.ts +48 -0
  171. package/dist/components/artifacts/editable-document.d.ts +1 -1
  172. package/dist/components/artifacts/editable-presentation.d.ts +2 -2
  173. package/dist/components/artifacts/editable-spreadsheet.d.ts +8 -3
  174. package/dist/components/artifacts/index.d.ts +9 -2
  175. package/dist/components/artifacts/inline-html-document.d.ts +2 -0
  176. package/dist/components/artifacts/latest-task-runner.d.ts +15 -0
  177. package/dist/components/artifacts/presentation-editor.d.ts +6 -2
  178. package/dist/components/artifacts/published-html-artifact-frame.d.ts +23 -3
  179. package/dist/components/artifacts/session-artifact-viewer.d.ts +39 -0
  180. package/dist/components/artifacts/site-view.d.ts +26 -0
  181. package/dist/components/artifacts/spreadsheet-command-queue.d.ts +20 -0
  182. package/dist/components/artifacts/spreadsheet-download.d.ts +7 -0
  183. package/dist/components/artifacts/spreadsheet-general-display.d.ts +24 -0
  184. package/dist/components/artifacts/spreadsheet-grid.d.ts +10 -3
  185. package/dist/components/artifacts/spreadsheet-resize.d.ts +50 -0
  186. package/dist/components/artifacts/spreadsheet-submissions.d.ts +9 -0
  187. package/dist/components/artifacts/visualization-assets.gen.d.ts +2 -0
  188. package/dist/components/browser-select-control.d.ts +16 -0
  189. package/dist/components/browser-viewer.d.ts +11 -2
  190. package/dist/components/chat-composer.d.ts +21 -3
  191. package/dist/components/child-session-link.d.ts +9 -0
  192. package/dist/components/claude-mark.d.ts +3 -0
  193. package/dist/components/code-editor.d.ts +1 -4
  194. package/dist/components/composer-transcription-control.d.ts +7 -0
  195. package/dist/components/composer.d.ts +34 -67
  196. package/dist/components/computer-viewer.d.ts +1 -1
  197. package/dist/components/copy-button.d.ts +3 -1
  198. package/dist/components/desktop-viewer.d.ts +1 -1
  199. package/dist/components/grok-mark.d.ts +26 -0
  200. package/dist/components/human-input-form.d.ts +28 -32
  201. package/dist/components/human-input-surface.d.ts +7 -2
  202. package/dist/components/linkified-text.d.ts +18 -0
  203. package/dist/components/machine-input-display.d.ts +22 -1
  204. package/dist/components/machines/agent-status-icon.d.ts +6 -0
  205. package/dist/components/machines/machine-detail.d.ts +7 -1
  206. package/dist/components/machines/machine-status.d.ts +48 -0
  207. package/dist/components/markdown-table-layout.d.ts +18 -0
  208. package/dist/components/markdown.d.ts +37 -4
  209. package/dist/components/message-timeline.d.ts +86 -7
  210. package/dist/components/model-mark.d.ts +29 -0
  211. package/dist/components/model-policy-picker-menu.d.ts +9 -0
  212. package/dist/components/model-policy-picker.d.ts +35 -28
  213. package/dist/components/new-conversation.d.ts +32 -0
  214. package/dist/components/open-geni-chat.d.ts +73 -0
  215. package/dist/components/open-geni-links.d.ts +68 -0
  216. package/dist/components/openrouter-mark.d.ts +6 -0
  217. package/dist/components/opper-mark.d.ts +7 -0
  218. package/dist/components/preview-loading.d.ts +2 -0
  219. package/dist/components/provider-recovery-notice.d.ts +16 -0
  220. package/dist/components/queue-item-content.d.ts +9 -0
  221. package/dist/components/rehype-secret-refs.d.ts +23 -0
  222. package/dist/components/remark-soft-line-breaks.d.ts +38 -0
  223. package/dist/components/sandbox-files.d.ts +3 -1
  224. package/dist/components/sandbox-workspace.d.ts +18 -0
  225. package/dist/components/secret-question-field.d.ts +31 -0
  226. package/dist/components/secret-ref-chip.d.ts +35 -0
  227. package/dist/components/session-capability-frame.d.ts +37 -0
  228. package/dist/components/session-chrome.d.ts +57 -8
  229. package/dist/components/session-commands-panel.d.ts +6 -0
  230. package/dist/components/session-commands.d.ts +6 -0
  231. package/dist/components/session-connection-request.d.ts +8 -0
  232. package/dist/components/session-conversation.d.ts +157 -0
  233. package/dist/components/session-list.d.ts +39 -0
  234. package/dist/components/session-mcp-capability-card.d.ts +41 -0
  235. package/dist/components/session-proxy-scope.d.ts +35 -0
  236. package/dist/components/session-status.d.ts +4 -1
  237. package/dist/components/session-usage-limit-notice.d.ts +39 -0
  238. package/dist/components/timeline-anchor.d.ts +24 -0
  239. package/dist/components/timeline-annotation-chrome.d.ts +25 -0
  240. package/dist/components/timeline-annotation-layout.d.ts +42 -0
  241. package/dist/components/timeline-annotation-markers.d.ts +8 -0
  242. package/dist/components/timeline-annotation-reveal-context.d.ts +4 -0
  243. package/dist/components/timeline-annotation-shared.d.ts +31 -0
  244. package/dist/components/timeline-annotations-dialog.d.ts +3 -2
  245. package/dist/components/timeline-annotations.d.ts +13 -3
  246. package/dist/components/timeline-reading-progress.d.ts +11 -0
  247. package/dist/components/timeline-search.d.ts +34 -0
  248. package/dist/components/timeline-settlement.d.ts +16 -0
  249. package/dist/components/tool-action-review.d.ts +47 -0
  250. package/dist/components/tool-review-history.d.ts +25 -0
  251. package/dist/components/usage-limit-notice.d.ts +49 -0
  252. package/dist/components/usage-member-list.d.ts +91 -0
  253. package/dist/components/usage-meter.d.ts +73 -0
  254. package/dist/components/use-viewer-menu-dismiss.d.ts +2 -0
  255. package/dist/components/user-message-body.d.ts +10 -1
  256. package/dist/composer-messages.d.ts +74 -0
  257. package/dist/composer-messages.js +9 -0
  258. package/dist/composer-messages.js.map +1 -0
  259. package/dist/composer-voice-events.d.ts +8 -0
  260. package/dist/composer.d.ts +5 -4
  261. package/dist/composer.js +26 -13
  262. package/dist/computer-viewer-GMLDXPON.js +20 -0
  263. package/dist/computer-viewer-GMLDXPON.js.map +1 -0
  264. package/dist/connect-account-identity.d.ts +8 -0
  265. package/dist/connect-accounts.d.ts +15 -0
  266. package/dist/connect-chooser.d.ts +18 -0
  267. package/dist/connect-panel.d.ts +18 -0
  268. package/dist/connect-setup.d.ts +18 -0
  269. package/dist/connect.d.ts +21 -0
  270. package/dist/connect.js +2149 -0
  271. package/dist/connect.js.map +1 -0
  272. package/dist/connection-catalog.d.ts +38 -0
  273. package/dist/connection-discovery.d.ts +12 -0
  274. package/dist/connection-installed.d.ts +17 -0
  275. package/dist/connection-logo.d.ts +9 -0
  276. package/dist/connection-service-logo.d.ts +8 -0
  277. package/dist/connection-service-presentation.d.ts +13 -0
  278. package/dist/connection-skeleton.d.ts +5 -0
  279. package/dist/connection-type-picker.d.ts +6 -0
  280. package/dist/conversation-timeline.d.ts +5 -0
  281. package/dist/desktop.d.ts +7 -0
  282. package/dist/desktop.js +27 -0
  283. package/dist/desktop.js.map +1 -0
  284. package/dist/device-authorization.d.ts +23 -0
  285. package/dist/diffs.d.ts +8 -0
  286. package/dist/diffs.js +15 -0
  287. package/dist/diffs.js.map +1 -0
  288. package/dist/discovery-cache.d.ts +5 -0
  289. package/dist/editor.d.ts +7 -0
  290. package/dist/editor.js +22 -0
  291. package/dist/editor.js.map +1 -0
  292. package/dist/embedded-new-voice-OCRNT6U5.js +18 -0
  293. package/dist/embedded-new-voice-OCRNT6U5.js.map +1 -0
  294. package/dist/embedded-voice-RPHPYVBT.js +18 -0
  295. package/dist/embedded-voice-RPHPYVBT.js.map +1 -0
  296. package/dist/{fleet-decision-row-GGCAT4E4.js → fleet-decision-row-SI63O5V4.js} +4 -1
  297. package/dist/fleet-decision-row-SI63O5V4.js.map +1 -0
  298. package/dist/hooks/latest-question.d.ts +25 -0
  299. package/dist/hooks/shared-machines-list.d.ts +4 -0
  300. package/dist/hooks/shared-query.d.ts +113 -0
  301. package/dist/hooks/use-attached-browsers.d.ts +1 -1
  302. package/dist/hooks/use-auth-runs.d.ts +1 -4
  303. package/dist/hooks/use-available-models.d.ts +1 -1
  304. package/dist/hooks/use-browser-frame-stream.d.ts +1 -1
  305. package/dist/hooks/use-browser-session.d.ts +12 -1
  306. package/dist/hooks/use-client-config-flags.d.ts +49 -0
  307. package/dist/hooks/use-codex-accounts.d.ts +59 -10
  308. package/dist/hooks/use-composer.d.ts +24 -2
  309. package/dist/hooks/use-computer-frame-stream.d.ts +1 -1
  310. package/dist/hooks/use-computer-input-posture.d.ts +13 -0
  311. package/dist/hooks/use-computer-session.d.ts +4 -0
  312. package/dist/hooks/use-connect.d.ts +23 -0
  313. package/dist/hooks/use-desktop-stream.d.ts +1 -1
  314. package/dist/hooks/use-file-attachments.d.ts +12 -0
  315. package/dist/hooks/use-goal.d.ts +2 -1
  316. package/dist/hooks/use-last-started-turn-policy.d.ts +5 -1
  317. package/dist/hooks/use-new-conversation.d.ts +56 -0
  318. package/dist/hooks/use-new-session-draft.d.ts +74 -0
  319. package/dist/hooks/use-realtime-voice-models.d.ts +7 -0
  320. package/dist/hooks/use-sandbox-files.d.ts +4 -1
  321. package/dist/hooks/use-session-conversation.d.ts +49 -0
  322. package/dist/hooks/use-session-events.d.ts +26 -5
  323. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  324. package/dist/hooks/use-session-model-account.d.ts +48 -0
  325. package/dist/hooks/use-supergrok-accounts.d.ts +7 -1
  326. package/dist/hooks/use-terminal-stream.d.ts +11 -5
  327. package/dist/hooks/use-usage.d.ts +40 -0
  328. package/dist/hooks/use-vaults.d.ts +53 -0
  329. package/dist/hooks/use-voice-input.d.ts +8 -1
  330. package/dist/hooks/use-workspace-edit.d.ts +4 -0
  331. package/dist/hooks/use-workspace-sessions.d.ts +24 -5
  332. package/dist/human-input-model.d.ts +122 -0
  333. package/dist/identity-link-accounts.d.ts +11 -0
  334. package/dist/identity-link-consent.d.ts +18 -0
  335. package/dist/index.d.ts +82 -20
  336. package/dist/index.js +2340 -6603
  337. package/dist/index.js.map +1 -1
  338. package/dist/interaction.d.ts +1 -1
  339. package/dist/interaction.js +14 -24
  340. package/dist/interaction.js.map +1 -1
  341. package/dist/latest-question-R77KWSUP.js +118 -0
  342. package/dist/latest-question-R77KWSUP.js.map +1 -0
  343. package/dist/lib/clipboard.d.ts +7 -0
  344. package/dist/lib/error-message.d.ts +7 -0
  345. package/dist/lib/format.d.ts +58 -4
  346. package/dist/lib/host-theme.d.ts +77 -0
  347. package/dist/lib/interaction-errors.d.ts +3 -0
  348. package/dist/lib/menu-styles.d.ts +22 -0
  349. package/dist/lib/notify-observer.d.ts +2 -0
  350. package/dist/lib/pierre-diffs-loader.d.ts +20 -0
  351. package/dist/lib/provider-recovery.d.ts +55 -0
  352. package/dist/lib/scrollbars.d.ts +11 -0
  353. package/dist/lib/terminal-input-readiness.d.ts +11 -0
  354. package/dist/lib/transient-retry.d.ts +25 -0
  355. package/dist/lib/workbench-peers.d.ts +36 -0
  356. package/dist/lib/xterm-theme.d.ts +7 -5
  357. package/dist/machines.js +9 -5
  358. package/dist/markdown-table-layout-3PV2UAAH.js +113 -0
  359. package/dist/markdown-table-layout-3PV2UAAH.js.map +1 -0
  360. package/dist/mcp-connection-status.d.ts +14 -0
  361. package/dist/model-mark-paths.d.ts +34 -0
  362. package/dist/model-mark-paths.js +16 -0
  363. package/dist/model-mark-paths.js.map +1 -0
  364. package/dist/model-picker-model.d.ts +111 -0
  365. package/dist/model-picker-model.js +25 -0
  366. package/dist/model-picker-model.js.map +1 -0
  367. package/dist/model-policy-picker-menu-7GJ5URPR.js +17 -0
  368. package/dist/model-policy-picker-menu-7GJ5URPR.js.map +1 -0
  369. package/dist/model-policy.d.ts +30 -3
  370. package/dist/model-policy.js +14 -1
  371. package/dist/native-previews.d.ts +28 -0
  372. package/dist/native-previews.js +82 -0
  373. package/dist/native-previews.js.map +1 -0
  374. package/dist/new-session-draft.d.ts +7 -0
  375. package/dist/new-session-draft.js +639 -0
  376. package/dist/new-session-draft.js.map +1 -0
  377. package/dist/notification-text.d.ts +27 -0
  378. package/dist/{noto-sans-arabic-loader-IA2T4X2A.js → noto-sans-arabic-loader-XSH2KYWJ.js} +3 -1
  379. package/dist/{noto-sans-arabic-loader-IA2T4X2A.js.map → noto-sans-arabic-loader-XSH2KYWJ.js.map} +1 -1
  380. package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js → noto-sans-jp-loader.generated-MR5L6V7K.js} +3 -1
  381. package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js.map → noto-sans-jp-loader.generated-MR5L6V7K.js.map} +1 -1
  382. package/dist/older-history.d.ts +3 -2
  383. package/dist/organization-model.d.ts +32 -0
  384. package/dist/organization-model.js +65 -0
  385. package/dist/organization-model.js.map +1 -0
  386. package/dist/{platform-activity-row-BJHUHXAT.js → platform-activity-row-5OCKOQWZ.js} +16 -78
  387. package/dist/platform-activity-row-5OCKOQWZ.js.map +1 -0
  388. package/dist/plugin-details.d.ts +14 -0
  389. package/dist/plugin-discovery.d.ts +25 -0
  390. package/dist/preview-loading-paint.d.ts +20 -0
  391. package/dist/provider.d.ts +25 -2
  392. package/dist/queue-presentation-model.d.ts +72 -0
  393. package/dist/queue-presentation-model.js +38 -0
  394. package/dist/queue-presentation-model.js.map +1 -0
  395. package/dist/{queue-surface-implementation-BSKUWHRI.js → queue-surface-implementation-GJBH7NKO.js} +65 -265
  396. package/dist/queue-surface-implementation-GJBH7NKO.js.map +1 -0
  397. package/dist/realtime/embedded-new-voice.d.ts +1 -0
  398. package/dist/realtime/embedded-voice.d.ts +1 -0
  399. package/dist/realtime/realtime-control.d.ts +4 -65
  400. package/dist/realtime/session-realtime.d.ts +78 -0
  401. package/dist/realtime.js +25 -1333
  402. package/dist/realtime.js.map +1 -1
  403. package/dist/sandbox-files-O27IPOZW.js +21 -0
  404. package/dist/sandbox-files-O27IPOZW.js.map +1 -0
  405. package/dist/sandbox-label-model.d.ts +30 -0
  406. package/dist/sandbox-label-model.js +20 -0
  407. package/dist/sandbox-label-model.js.map +1 -0
  408. package/dist/scrollbars.d.ts +1 -0
  409. package/dist/scrollbars.js +10 -0
  410. package/dist/scrollbars.js.map +1 -0
  411. package/dist/secret-reference-label.d.ts +14 -0
  412. package/dist/secrets-ui.d.ts +57 -0
  413. package/dist/session-agents-model.d.ts +10 -0
  414. package/dist/session-agents-model.js +25 -0
  415. package/dist/session-agents-model.js.map +1 -0
  416. package/dist/session-attention-model.d.ts +8 -0
  417. package/dist/session-attention-model.js +47 -0
  418. package/dist/session-attention-model.js.map +1 -0
  419. package/dist/session-auth-recommendation.d.ts +5 -0
  420. package/dist/session-capability-policy.d.ts +31 -0
  421. package/dist/session-feedback-model.d.ts +20 -0
  422. package/dist/session-feedback-model.js +56 -0
  423. package/dist/session-feedback-model.js.map +1 -0
  424. package/dist/session-list-model.d.ts +138 -0
  425. package/dist/session-list-model.js +274 -0
  426. package/dist/session-list-model.js.map +1 -0
  427. package/dist/session-realtime-7DJOIHQW.js +21 -0
  428. package/dist/session-realtime-7DJOIHQW.js.map +1 -0
  429. package/dist/session-realtime.d.ts +7 -0
  430. package/dist/session-realtime.js +19 -0
  431. package/dist/session-realtime.js.map +1 -0
  432. package/dist/session-status-model.d.ts +36 -0
  433. package/dist/session-ui.d.ts +39 -1
  434. package/dist/session-ui.js +126 -12
  435. package/dist/session-ui.js.map +1 -1
  436. package/dist/session.d.ts +8 -2
  437. package/dist/session.js +35 -20
  438. package/dist/site-snapshot.d.ts +14 -0
  439. package/dist/sites-ui.d.ts +24 -0
  440. package/dist/sites.d.ts +3 -0
  441. package/dist/sites.js +14 -0
  442. package/dist/sites.js.map +1 -0
  443. package/dist/skill-discovery.d.ts +47 -0
  444. package/dist/terminal.d.ts +8 -0
  445. package/dist/terminal.js +32 -0
  446. package/dist/terminal.js.map +1 -0
  447. package/dist/testing/session-scenarios.d.ts +10 -0
  448. package/dist/testing/session-scenarios.js +367 -0
  449. package/dist/testing/session-scenarios.js.map +1 -0
  450. package/dist/timeline/activity-rail.d.ts +11 -2
  451. package/dist/timeline/agent-identity.d.ts +30 -0
  452. package/dist/timeline/agent-row.d.ts +31 -0
  453. package/dist/timeline/allowance-exhausted-row.d.ts +24 -0
  454. package/dist/timeline/compaction-copy.d.ts +46 -0
  455. package/dist/timeline/focus-sequence.d.ts +9 -0
  456. package/dist/timeline/genie-copy.d.ts +3 -0
  457. package/dist/timeline/genie-loading.d.ts +47 -0
  458. package/dist/timeline/index.d.ts +10 -5
  459. package/dist/timeline/knowledge-receipt.d.ts +17 -0
  460. package/dist/timeline/message-sender.d.ts +19 -0
  461. package/dist/timeline/notice-presentation.d.ts +13 -0
  462. package/dist/timeline/parsers.d.ts +11 -0
  463. package/dist/timeline/patch-apply-command.d.ts +18 -0
  464. package/dist/timeline/platform-activity-presentation.d.ts +21 -0
  465. package/dist/timeline/platform-activity-row.d.ts +1 -2
  466. package/dist/timeline/presented-image.d.ts +11 -0
  467. package/dist/timeline/projection.d.ts +31 -2
  468. package/dist/timeline/provider-logo.d.ts +15 -0
  469. package/dist/timeline/question-nav-model.d.ts +23 -0
  470. package/dist/timeline/registry.d.ts +12 -1
  471. package/dist/timeline/retained-image.d.ts +18 -0
  472. package/dist/timeline/retained-loaders.d.ts +27 -0
  473. package/dist/timeline/rolling-activity.d.ts +11 -0
  474. package/dist/timeline/screenshot-lightbox.d.ts +23 -1
  475. package/dist/timeline/session-usage-limit-row.d.ts +11 -0
  476. package/dist/timeline/shared.d.ts +4 -1
  477. package/dist/timeline/startup-dispatch-details.d.ts +5 -0
  478. package/dist/timeline/startup-preference.d.ts +3 -0
  479. package/dist/timeline/startup-timings.d.ts +4 -0
  480. package/dist/timeline/tool-display-name.d.ts +5 -2
  481. package/dist/timeline/tool-presentation.d.ts +172 -0
  482. package/dist/timeline/turn-summary-model.d.ts +95 -0
  483. package/dist/timeline/turn-summary.d.ts +20 -54
  484. package/dist/timeline/types.d.ts +179 -7
  485. package/dist/timeline/work-presentation.d.ts +60 -0
  486. package/dist/timeline-annotation-markers-U3G66KUD.js +166 -0
  487. package/dist/timeline-annotation-markers-U3G66KUD.js.map +1 -0
  488. package/dist/timeline-annotation-selection-BI7QTPTZ.js +242 -0
  489. package/dist/timeline-annotation-selection-BI7QTPTZ.js.map +1 -0
  490. package/dist/timeline-annotations-dialog-FOMHSDTE.js +274 -0
  491. package/dist/timeline-annotations-dialog-FOMHSDTE.js.map +1 -0
  492. package/dist/timeline-model.d.ts +29 -0
  493. package/dist/timeline-model.js +254 -0
  494. package/dist/timeline-model.js.map +1 -0
  495. package/dist/usage/allowance-copy.d.ts +25 -0
  496. package/dist/usage/session-usage-limit.d.ts +88 -0
  497. package/dist/usage/summary.d.ts +35 -0
  498. package/dist/usage.d.ts +17 -0
  499. package/dist/usage.js +1187 -0
  500. package/dist/usage.js.map +1 -0
  501. package/dist/voice-recording-owner.d.ts +7 -0
  502. package/package.json +106 -7
  503. package/src/approvals.ts +8 -1
  504. package/src/artifacts-spreadsheet.ts +1 -0
  505. package/src/artifacts.ts +44 -0
  506. package/src/assets/README.md +8 -0
  507. package/src/assets/gmail.ico +0 -0
  508. package/src/assets/outlook.ico +0 -0
  509. package/src/capability-catalog-row.tsx +94 -0
  510. package/src/capability-logo-fallback.ts +22 -0
  511. package/src/client.ts +20 -31
  512. package/src/commands/registry.ts +18 -15
  513. package/src/commands/types.ts +2 -0
  514. package/src/components/anthropic-mark.tsx +10 -0
  515. package/src/components/approval-surface.tsx +401 -138
  516. package/src/components/artifacts/artifact-chrome.tsx +413 -0
  517. package/src/components/artifacts/artifact-sandbox.tsx +193 -0
  518. package/src/components/artifacts/artifact-surface.tsx +40 -20
  519. package/src/components/artifacts/chat-interactive-block.tsx +260 -0
  520. package/src/components/artifacts/deferred-chat-media.tsx +67 -0
  521. package/src/components/artifacts/document-editor.tsx +141 -16
  522. package/src/components/artifacts/editable-artifact-ui.tsx +4 -2
  523. package/src/components/artifacts/editable-artifact-view.tsx +197 -0
  524. package/src/components/artifacts/editable-artifact-workbench.tsx +5 -2
  525. package/src/components/artifacts/editable-document.tsx +37 -36
  526. package/src/components/artifacts/editable-presentation.tsx +36 -18
  527. package/src/components/artifacts/editable-spreadsheet.tsx +543 -103
  528. package/src/components/artifacts/index.ts +45 -0
  529. package/src/components/artifacts/inline-html-document.ts +24 -0
  530. package/src/components/artifacts/latest-task-runner.ts +49 -0
  531. package/src/components/artifacts/presentation-editor.tsx +323 -67
  532. package/src/components/artifacts/published-html-artifact-frame.tsx +122 -5
  533. package/src/components/artifacts/session-artifact-viewer.tsx +378 -0
  534. package/src/components/artifacts/site-view.tsx +121 -0
  535. package/src/components/artifacts/spreadsheet-canvas.ts +5 -10
  536. package/src/components/artifacts/spreadsheet-command-queue.ts +184 -0
  537. package/src/components/artifacts/spreadsheet-download.tsx +109 -0
  538. package/src/components/artifacts/spreadsheet-general-display.ts +43 -0
  539. package/src/components/artifacts/spreadsheet-grid.tsx +344 -144
  540. package/src/components/artifacts/spreadsheet-resize.tsx +421 -0
  541. package/src/components/artifacts/spreadsheet-submissions.tsx +46 -0
  542. package/src/components/artifacts/visualization-assets/base.css +1015 -0
  543. package/src/components/artifacts/visualization-assets/helpers.html +349 -0
  544. package/src/components/artifacts/visualization-assets.gen.ts +3 -0
  545. package/src/components/browser-select-control.tsx +312 -0
  546. package/src/components/browser-viewer.tsx +778 -318
  547. package/src/components/chat-composer.tsx +47 -5
  548. package/src/components/child-session-link.tsx +36 -0
  549. package/src/components/claude-mark.tsx +11 -0
  550. package/src/components/code-editor.tsx +13 -16
  551. package/src/components/composer-transcription-control.tsx +128 -30
  552. package/src/components/composer.tsx +404 -195
  553. package/src/components/computer-viewer.tsx +880 -253
  554. package/src/components/copy-button.tsx +13 -5
  555. package/src/components/desktop-viewer.tsx +1 -1
  556. package/src/components/enrollment-consent.tsx +1 -1
  557. package/src/components/enrollment-device-flow.tsx +1 -1
  558. package/src/components/file-browser.tsx +10 -14
  559. package/src/components/fleet-tile.tsx +5 -3
  560. package/src/components/generated-video-player.tsx +4 -2
  561. package/src/components/grok-mark.tsx +34 -0
  562. package/src/components/human-input-form.tsx +590 -283
  563. package/src/components/human-input-surface.tsx +11 -0
  564. package/src/components/interaction-intervention-banner.tsx +2 -2
  565. package/src/components/linkified-text.tsx +79 -0
  566. package/src/components/machine-card.tsx +35 -78
  567. package/src/components/machine-dock-bar.tsx +1 -1
  568. package/src/components/machine-input-display.ts +197 -3
  569. package/src/components/machines/agent-status-icon.tsx +31 -0
  570. package/src/components/machines/machine-detail.tsx +200 -31
  571. package/src/components/machines/machine-operation-policy.tsx +1 -1
  572. package/src/components/machines/machine-status.ts +241 -0
  573. package/src/components/machines-dashboard.tsx +2 -17
  574. package/src/components/markdown-table-layout.ts +143 -0
  575. package/src/components/markdown.tsx +466 -129
  576. package/src/components/message-timeline.tsx +2370 -667
  577. package/src/components/model-mark.tsx +102 -0
  578. package/src/components/model-picker.tsx +15 -4
  579. package/src/components/model-policy-picker-menu.tsx +371 -0
  580. package/src/components/model-policy-picker.tsx +252 -429
  581. package/src/components/new-conversation.tsx +221 -0
  582. package/src/components/open-geni-chat.tsx +309 -0
  583. package/src/components/open-geni-links.tsx +179 -0
  584. package/src/components/openrouter-mark.tsx +13 -0
  585. package/src/components/opper-mark.tsx +18 -0
  586. package/src/components/pierre-diff.tsx +41 -16
  587. package/src/components/pierre-file.tsx +35 -10
  588. package/src/components/preview-loading.tsx +109 -0
  589. package/src/components/provider-recovery-notice.tsx +49 -0
  590. package/src/components/queue-item-content.ts +14 -0
  591. package/src/components/queue-surface-implementation.tsx +51 -356
  592. package/src/components/queue-surface-state.tsx +3 -1
  593. package/src/components/rehype-secret-refs.ts +98 -0
  594. package/src/components/remark-soft-line-breaks.ts +166 -0
  595. package/src/components/sandbox-files.tsx +91 -15
  596. package/src/components/sandbox-terminal.tsx +84 -13
  597. package/src/components/sandbox-workspace.tsx +147 -51
  598. package/src/components/secret-question-field.tsx +324 -0
  599. package/src/components/secret-ref-chip.tsx +185 -0
  600. package/src/components/session-capability-frame.tsx +197 -0
  601. package/src/components/session-chrome.tsx +842 -251
  602. package/src/components/session-commands-panel.tsx +126 -0
  603. package/src/components/session-commands.tsx +18 -0
  604. package/src/components/session-connection-request.tsx +97 -0
  605. package/src/components/session-conversation.tsx +876 -0
  606. package/src/components/session-list.tsx +257 -0
  607. package/src/components/session-mcp-capability-card.tsx +680 -0
  608. package/src/components/session-proxy-scope.tsx +153 -0
  609. package/src/components/session-status.tsx +26 -17
  610. package/src/components/session-usage-limit-notice.tsx +174 -0
  611. package/src/components/timeline-anchor.tsx +119 -0
  612. package/src/components/timeline-annotation-chrome.tsx +196 -0
  613. package/src/components/timeline-annotation-layout.ts +268 -0
  614. package/src/components/timeline-annotation-markers.tsx +185 -0
  615. package/src/components/timeline-annotation-reveal-context.ts +9 -0
  616. package/src/components/timeline-annotation-selection.tsx +106 -54
  617. package/src/components/timeline-annotation-shared.ts +245 -0
  618. package/src/components/timeline-annotations-dialog.tsx +185 -128
  619. package/src/components/timeline-annotations.tsx +189 -45
  620. package/src/components/timeline-reading-progress.ts +80 -0
  621. package/src/components/timeline-search.tsx +168 -0
  622. package/src/components/timeline-settlement.ts +60 -0
  623. package/src/components/tool-action-review.tsx +650 -0
  624. package/src/components/tool-review-history.tsx +88 -0
  625. package/src/components/usage-limit-notice.tsx +179 -0
  626. package/src/components/usage-member-list.tsx +792 -0
  627. package/src/components/usage-meter.tsx +382 -0
  628. package/src/components/use-viewer-menu-dismiss.ts +33 -0
  629. package/src/components/user-message-body.tsx +27 -2
  630. package/src/components/workspace-dock.tsx +18 -1
  631. package/src/composer-messages.ts +150 -0
  632. package/src/composer-voice-events.ts +12 -0
  633. package/src/composer.ts +6 -1
  634. package/src/connect-account-identity.tsx +34 -0
  635. package/src/connect-accounts.tsx +233 -0
  636. package/src/connect-chooser.tsx +336 -0
  637. package/src/connect-panel.tsx +99 -0
  638. package/src/connect-setup.tsx +355 -0
  639. package/src/connect.ts +60 -0
  640. package/src/connection-assets.d.ts +4 -0
  641. package/src/connection-catalog.tsx +162 -0
  642. package/src/connection-discovery.tsx +238 -0
  643. package/src/connection-installed.tsx +51 -0
  644. package/src/connection-logo.tsx +37 -0
  645. package/src/connection-service-logo.tsx +55 -0
  646. package/src/connection-service-presentation.ts +46 -0
  647. package/src/connection-skeleton.tsx +16 -0
  648. package/src/connection-type-picker.tsx +36 -0
  649. package/src/conversation-timeline.ts +92 -0
  650. package/src/desktop.ts +24 -0
  651. package/src/device-authorization.tsx +98 -0
  652. package/src/diffs.ts +16 -0
  653. package/src/discovery-cache.ts +42 -0
  654. package/src/editor.ts +13 -0
  655. package/src/hooks/internal.ts +3 -2
  656. package/src/hooks/latest-question.ts +164 -0
  657. package/src/hooks/shared-machines-list.ts +34 -1
  658. package/src/hooks/shared-query.ts +721 -0
  659. package/src/hooks/use-attached-browsers.ts +1 -1
  660. package/src/hooks/use-auth-runs.ts +0 -27
  661. package/src/hooks/use-available-models.ts +2 -2
  662. package/src/hooks/use-browser-frame-stream.ts +19 -2
  663. package/src/hooks/use-browser-session.ts +680 -205
  664. package/src/hooks/use-browser-sessions.ts +32 -1
  665. package/src/hooks/use-client-config-flags.ts +118 -0
  666. package/src/hooks/use-codex-accounts.ts +247 -47
  667. package/src/hooks/use-composer.ts +227 -44
  668. package/src/hooks/use-computer-frame-stream.ts +25 -7
  669. package/src/hooks/use-computer-input-posture.ts +94 -0
  670. package/src/hooks/use-computer-session.ts +409 -95
  671. package/src/hooks/use-connect.ts +23 -0
  672. package/src/hooks/use-desktop-stream.ts +19 -28
  673. package/src/hooks/use-file-attachments.ts +80 -4
  674. package/src/hooks/use-goal.ts +79 -9
  675. package/src/hooks/use-last-started-turn-policy.ts +37 -11
  676. package/src/hooks/use-machines.ts +4 -0
  677. package/src/hooks/use-new-conversation.ts +374 -0
  678. package/src/hooks/use-new-session-draft.ts +813 -0
  679. package/src/hooks/use-realtime-voice-models.ts +46 -0
  680. package/src/hooks/use-sandbox-files.ts +68 -20
  681. package/src/hooks/use-session-background-commands.ts +22 -11
  682. package/src/hooks/use-session-capabilities.ts +3 -2
  683. package/src/hooks/use-session-conversation.ts +150 -0
  684. package/src/hooks/use-session-events.ts +531 -335
  685. package/src/hooks/use-session-mcp-approval-policy.ts +13 -5
  686. package/src/hooks/use-session-model-account.ts +174 -0
  687. package/src/hooks/use-session.ts +30 -2
  688. package/src/hooks/use-slash-commands.ts +6 -11
  689. package/src/hooks/use-supergrok-accounts.ts +22 -8
  690. package/src/hooks/use-terminal-stream.ts +153 -27
  691. package/src/hooks/use-transcription.ts +6 -0
  692. package/src/hooks/use-turn-queue.ts +2 -1
  693. package/src/hooks/use-usage.ts +100 -0
  694. package/src/hooks/use-vaults.ts +153 -0
  695. package/src/hooks/use-video-artifact-playback.ts +1 -1
  696. package/src/hooks/use-voice-input.ts +127 -19
  697. package/src/hooks/use-workspace-capture.ts +3 -2
  698. package/src/hooks/use-workspace-edit.ts +23 -5
  699. package/src/hooks/use-workspace-sessions.ts +74 -7
  700. package/src/human-input-model.ts +359 -0
  701. package/src/identity-link-accounts.tsx +148 -0
  702. package/src/identity-link-consent.tsx +192 -0
  703. package/src/index.ts +262 -16
  704. package/src/interaction.ts +1 -0
  705. package/src/lib/clipboard.ts +29 -0
  706. package/src/lib/error-message.ts +37 -0
  707. package/src/lib/format.ts +205 -10
  708. package/src/lib/host-theme.ts +529 -0
  709. package/src/lib/interaction-errors.ts +15 -0
  710. package/src/lib/menu-styles.ts +44 -0
  711. package/src/lib/notify-observer.ts +15 -0
  712. package/src/lib/pierre-diffs-loader.ts +53 -0
  713. package/src/lib/provider-recovery.ts +243 -0
  714. package/src/lib/scrollbars.ts +30 -0
  715. package/src/lib/terminal-input-readiness.ts +51 -0
  716. package/src/lib/transient-retry.ts +99 -0
  717. package/src/lib/workbench-peers.ts +62 -0
  718. package/src/lib/xterm-theme.ts +23 -21
  719. package/src/mcp-connection-status.ts +35 -0
  720. package/src/model-mark-paths.ts +53 -0
  721. package/src/model-picker-model.ts +330 -0
  722. package/src/model-policy.ts +299 -74
  723. package/src/native-previews.ts +100 -0
  724. package/src/new-session-draft.ts +22 -0
  725. package/src/notification-text.ts +81 -0
  726. package/src/older-history.ts +22 -4
  727. package/src/organization-model.ts +118 -0
  728. package/src/plugin-details.tsx +185 -0
  729. package/src/plugin-discovery.tsx +196 -0
  730. package/src/preview-loading-paint.ts +68 -0
  731. package/src/provider.tsx +74 -15
  732. package/src/queue-presentation-model.ts +464 -0
  733. package/src/realtime/dropdown-menu.tsx +19 -12
  734. package/src/realtime/embedded-new-voice.tsx +1 -0
  735. package/src/realtime/embedded-voice.tsx +3 -0
  736. package/src/realtime/realtime-control.tsx +138 -550
  737. package/src/realtime/session-realtime.ts +566 -0
  738. package/src/sandbox-label-model.ts +53 -0
  739. package/src/scrollbars.ts +3 -0
  740. package/src/secret-reference-label.ts +43 -0
  741. package/src/secrets-ui.tsx +59 -0
  742. package/src/session-agents-model.ts +33 -0
  743. package/src/session-attention-model.ts +62 -0
  744. package/src/session-auth-recommendation.ts +48 -0
  745. package/src/session-capability-policy.ts +196 -0
  746. package/src/session-context.ts +3 -4
  747. package/src/session-feedback-model.ts +95 -0
  748. package/src/session-list-model.ts +434 -0
  749. package/src/session-realtime.ts +18 -0
  750. package/src/session-status-model.ts +99 -0
  751. package/src/session-ui.ts +98 -1
  752. package/src/session.ts +17 -1
  753. package/src/site-snapshot.ts +74 -0
  754. package/src/sites-ui.tsx +348 -0
  755. package/src/sites.ts +3 -0
  756. package/src/skill-discovery.tsx +208 -0
  757. package/src/terminal.ts +26 -0
  758. package/src/testing/session-scenarios.ts +404 -0
  759. package/src/timeline/activity-rail.tsx +242 -101
  760. package/src/timeline/agent-identity.tsx +84 -0
  761. package/src/timeline/agent-row.tsx +231 -0
  762. package/src/timeline/allowance-exhausted-row.tsx +86 -0
  763. package/src/timeline/compaction-copy.ts +115 -0
  764. package/src/timeline/fleet-decision-projection.ts +2 -1
  765. package/src/timeline/fleet-decision-row.tsx +1 -0
  766. package/src/timeline/focus-sequence.ts +44 -0
  767. package/src/timeline/genie-copy.ts +19 -0
  768. package/src/timeline/genie-loading.tsx +143 -0
  769. package/src/timeline/index.ts +30 -2
  770. package/src/timeline/knowledge-receipt.tsx +100 -0
  771. package/src/timeline/message-sender.tsx +71 -0
  772. package/src/timeline/notice-presentation.ts +48 -0
  773. package/src/timeline/parsers.ts +42 -2
  774. package/src/timeline/patch-apply-command.tsx +135 -0
  775. package/src/timeline/platform-activity-presentation.ts +166 -0
  776. package/src/timeline/platform-activity-row.tsx +15 -90
  777. package/src/timeline/presented-image.ts +63 -0
  778. package/src/timeline/projection.ts +1742 -76
  779. package/src/timeline/provider-logo.tsx +53 -0
  780. package/src/timeline/question-nav-model.ts +28 -0
  781. package/src/timeline/registry.ts +26 -4
  782. package/src/timeline/retained-image.ts +97 -0
  783. package/src/timeline/retained-loaders.ts +91 -0
  784. package/src/timeline/rolling-activity.tsx +80 -0
  785. package/src/timeline/screenshot-lightbox.tsx +228 -5
  786. package/src/timeline/session-usage-limit-row.tsx +42 -0
  787. package/src/timeline/shared.tsx +42 -15
  788. package/src/timeline/startup-dispatch-details.tsx +36 -0
  789. package/src/timeline/startup-preference.ts +33 -0
  790. package/src/timeline/startup-timings.tsx +145 -0
  791. package/src/timeline/tool-display-name.ts +19 -7
  792. package/src/timeline/tool-presentation.ts +1316 -0
  793. package/src/timeline/tool-renderers.tsx +835 -1138
  794. package/src/timeline/turn-summary-model.ts +343 -0
  795. package/src/timeline/turn-summary.tsx +272 -365
  796. package/src/timeline/types.ts +189 -6
  797. package/src/timeline/work-presentation.ts +189 -0
  798. package/src/timeline-model.ts +143 -0
  799. package/src/usage/allowance-copy.ts +64 -0
  800. package/src/usage/session-usage-limit.ts +266 -0
  801. package/src/usage/summary.ts +93 -0
  802. package/src/usage.ts +70 -0
  803. package/src/voice-recording-owner.ts +22 -0
  804. package/styles/compiled.css +2319 -748
  805. package/styles/connect.css +290 -0
  806. package/styles/connect.d.ts +1 -0
  807. package/styles/effective-tokens.css +1 -0
  808. package/styles/index.css +309 -9
  809. package/styles/responsive.css +99 -7
  810. package/styles/session-capability.css +44 -0
  811. package/styles/tokens.css +134 -66
  812. package/dist/browser-viewer-MKKFRRRV.js +0 -14
  813. package/dist/chunk-2DZVYHVO.js +0 -470
  814. package/dist/chunk-2DZVYHVO.js.map +0 -1
  815. package/dist/chunk-3AGBGTRT.js.map +0 -1
  816. package/dist/chunk-4MDLEHXL.js.map +0 -1
  817. package/dist/chunk-5YK2ZDIG.js.map +0 -1
  818. package/dist/chunk-7EAFGICR.js +0 -302
  819. package/dist/chunk-7EAFGICR.js.map +0 -1
  820. package/dist/chunk-ELIYWBZR.js.map +0 -1
  821. package/dist/chunk-H5YGYNXS.js.map +0 -1
  822. package/dist/chunk-HUIKIDM5.js.map +0 -1
  823. package/dist/chunk-ICA6R6TW.js.map +0 -1
  824. package/dist/chunk-IOLVN74D.js.map +0 -1
  825. package/dist/chunk-JAQ5K7W6.js.map +0 -1
  826. package/dist/chunk-JVYQRRC4.js.map +0 -1
  827. package/dist/chunk-KCGXPP5V.js.map +0 -1
  828. package/dist/chunk-KZ73RL76.js.map +0 -1
  829. package/dist/chunk-NEXLLTP4.js.map +0 -1
  830. package/dist/chunk-PEI6RANI.js.map +0 -1
  831. package/dist/chunk-VTOREEXF.js +0 -142
  832. package/dist/chunk-VTOREEXF.js.map +0 -1
  833. package/dist/chunk-WSK7G5LE.js +0 -170
  834. package/dist/chunk-WSK7G5LE.js.map +0 -1
  835. package/dist/chunk-X4A66EWZ.js +0 -223
  836. package/dist/chunk-X4A66EWZ.js.map +0 -1
  837. package/dist/chunk-XYRDDSXF.js.map +0 -1
  838. package/dist/chunk-YG4QEQF5.js +0 -9557
  839. package/dist/chunk-YG4QEQF5.js.map +0 -1
  840. package/dist/chunk-YVCHAQZY.js.map +0 -1
  841. package/dist/chunk-ZPBTI3TE.js.map +0 -1
  842. package/dist/computer-viewer-FWEZISW4.js +0 -16
  843. package/dist/fleet-decision-row-GGCAT4E4.js.map +0 -1
  844. package/dist/hooks/use-environments.d.ts +0 -41
  845. package/dist/hooks/use-packs.d.ts +0 -32
  846. package/dist/platform-activity-row-BJHUHXAT.js.map +0 -1
  847. package/dist/queue-surface-implementation-BSKUWHRI.js.map +0 -1
  848. package/dist/timeline-annotation-selection-3L7LHGG2.js +0 -187
  849. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +0 -1
  850. package/dist/timeline-annotations-dialog-U7EVLR75.js +0 -196
  851. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +0 -1
  852. package/src/hooks/use-environments.ts +0 -182
  853. package/src/hooks/use-packs.ts +0 -157
  854. /package/dist/{browser-viewer-MKKFRRRV.js.map → browser-viewer-ZWR6MWJ7.js.map} +0 -0
  855. /package/dist/{computer-viewer-FWEZISW4.js.map → chat-interactive-block-4EUKJXUO.js.map} +0 -0
@@ -1,11 +1,18 @@
1
- import type { SessionEvent, SessionStatus, StreamConnectionState } from "@opengeni/sdk";
1
+ import {
2
+ sessionEventStreamCoveredThrough,
3
+ type SessionEvent,
4
+ type SessionStatus,
5
+ type StreamConnectionState,
6
+ } from "@opengeni/sdk";
2
7
  import { startTransition, useCallback, useEffect, useMemo, useRef, useState } from "react";
3
8
  import { useEmbeddedSession, type EmbeddedSessionClientOverride } from "../session-context";
4
9
  import { createOlderHistoryLoadReceipt, type OlderHistoryLoadReceipt } from "../older-history";
5
10
  import { buildTimeline, groupTimeline, sessionStatusFromEvents } from "../timeline/projection";
6
11
  import type { TimelineItem } from "../timeline/types";
7
12
  import type { EmbeddedSessionClientLike } from "../client";
13
+ import { normalizeError } from "../lib/error-message";
8
14
  import { usePageLiveActivity } from "./internal";
15
+ import type { LatestQuestionOptions } from "./latest-question";
9
16
 
10
17
  export type SessionEventsConnectionState = StreamConnectionState | "idle" | "ended" | "error";
11
18
 
@@ -21,10 +28,12 @@ export type UseSessionEventsOptions = EmbeddedSessionClientOverride & {
21
28
  export type UseSessionEventsResult = {
22
29
  /** Replayed + live events, ordered by sequence, no gaps, no duplicates. */
23
30
  events: SessionEvent[];
24
- /** Projected, renderable timeline (memoized over `events`). */
31
+ /** Renderable timeline, including the bounded navigation witness for a distant queued prompt. */
25
32
  timeline: TimelineItem[];
26
33
  /** Latest session status observed in the event log, if any. */
27
34
  sessionStatus: SessionStatus | null;
35
+ /** Sequence of the retained status projection, including events evicted from the window. */
36
+ sessionStatusSequence?: number;
28
37
  connectionState: SessionEventsConnectionState;
29
38
  /** Highest sequence seen so far (0 before the first event). */
30
39
  lastSequence: number;
@@ -34,6 +43,10 @@ export type UseSessionEventsResult = {
34
43
  windowTruncated: boolean;
35
44
  /** True until the initial tail window has been applied (windowed mode). */
36
45
  initialLoading: boolean;
46
+ /** A history window (including an empty tail) succeeded for this session/replay
47
+ * identity. Stays true through later stream errors and navigation/reloads.
48
+ * Full replay has no snapshot-completion watermark and does not set this by itself. */
49
+ initialHistoryReady: boolean;
37
50
  /** Whether older durable events are available before the current window. */
38
51
  hasOlder: boolean;
39
52
  /** True while an older window is being fetched. */
@@ -66,13 +79,20 @@ export type UseSessionEventsResult = {
66
79
  * "Jump to latest" when the tip is not in memory (history view).
67
80
  */
68
81
  jumpToLatest: () => Promise<void>;
82
+ /** Resolve the newest eligible durable human question. Pending prompts use the
83
+ * optional queue destination and return null; started prompts load their actual
84
+ * turn context (pass `timeline` to MessageTimeline as `items`). */
85
+ jumpToLatestQuestion: (options?: LatestQuestionOptions) => Promise<number | null>;
86
+ /** Replace history with a bounded window containing this exact durable event. */
87
+ jumpToSequence: (sequence: number, options?: { signal?: AbortSignal }) => Promise<boolean>;
88
+ loadingTarget: boolean;
69
89
  error: Error | null;
70
90
  };
71
91
 
72
- const INITIAL_TAIL_PAGE_SIZE = 1000;
73
- const OLDER_PAGE_SIZE = 5000;
74
- const NEWER_PAGE_SIZE = 5000;
75
- const OLDEST_PAGE_SIZE = 1000;
92
+ // Keep every browser history read inside one database batch, including the
93
+ // server's one-row continuation lookahead. A large total session must never
94
+ // turn one lazy page into dozens of sequential database round trips.
95
+ const SESSION_HISTORY_PAGE_SIZE = 1000;
76
96
  const INITIAL_FETCH_CAP = 1;
77
97
  const OLDER_GROUP_TARGET = 32;
78
98
  const OLDER_FETCH_CAP = 2;
@@ -80,37 +100,26 @@ const NEWER_GROUP_TARGET = 32;
80
100
  const NEWER_FETCH_CAP = 2;
81
101
  const OLDEST_GROUP_TARGET = 32;
82
102
  const OLDEST_FETCH_CAP = 2;
83
- const BOUNDARY_PAGE_CAP = 4;
103
+ // A tail page may land inside one unusually dense turn. Permit exactly one
104
+ // additional bounded page to find its user/session boundary without turning a
105
+ // fresh open into an unbounded history walk.
106
+ const BOUNDARY_PAGE_CAP = 1;
107
+ // Foreground reconciliation is intentionally semantic, not merely time-based:
108
+ // tiny raw gaps can stay on SSE; medium raw gaps get one compact probe so a
109
+ // token-heavy single answer is not mistaken for hundreds of visible messages;
110
+ // only a large/complex missed window reloads the latest tail.
111
+ const FOREGROUND_DIRECT_REPLAY_MAX_SEQUENCES = 16;
112
+ const FOREGROUND_COMPACT_PROBE_MAX_SEQUENCES = SESSION_HISTORY_PAGE_SIZE;
113
+ const FOREGROUND_COMPACT_CATCHUP_MAX_EVENTS = 128;
114
+ const FOREGROUND_COMPACT_CATCHUP_MAX_GROUPS = 16;
115
+ const FOREGROUND_COMPACT_CATCHUP_MAX_BYTES = 512 * 1024;
84
116
  const EMPTY_EVENTS: SessionEvent[] = [];
85
117
  const encoder = new TextEncoder();
86
- const decoder = new TextDecoder();
87
-
88
- const BROWSER_EVENT_TYPE_MAX_BYTES = 256;
89
- const BROWSER_EVENT_ID_MAX_BYTES = 256;
90
- const BROWSER_EVENT_CLIENT_ID_MAX_BYTES = 4 * 1024;
91
- const BROWSER_EVENT_DUPLICATE_REASON_MAX_BYTES = 4 * 1024;
92
- const BROWSER_EVENT_PAYLOAD_PREVIEW_MAX_BYTES = 48 * 1024;
93
- const BROWSER_EVENT_PAYLOAD_IDENTITY_FIELDS = [
94
- "id",
95
- "callId",
96
- "call_id",
97
- "name",
98
- "toolName",
99
- "status",
100
- "code",
101
- "isError",
102
- "stream",
103
- "commandId",
104
- "sequence",
105
- "coalescedUntil",
106
- "coalescedCount",
107
- "firstSequence",
108
- "lastSequence",
109
- ] as const;
110
-
111
- export const SESSION_EVENT_BROWSER_MAX_BYTES = 8 * 1024 * 1024;
112
- export const SESSION_EVENT_BROWSER_MAX_COUNT = 10_000;
113
- export const SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES = 96 * 1024;
118
+ // Durable history remains on the server and is available through bounded
119
+ // navigation. Keep the live browser working set small across multiple tabs;
120
+ // serialized payloads also expand into objects and timeline projections.
121
+ export const SESSION_EVENT_BROWSER_MAX_BYTES = 16 * 1024 * 1024;
122
+ export const SESSION_EVENT_BROWSER_MAX_COUNT = 20_000;
114
123
  export const SESSION_EVENT_BROWSER_PENDING_MAX_BYTES = 1024 * 1024;
115
124
  export const SESSION_EVENT_BROWSER_PENDING_MAX_COUNT = 256;
116
125
 
@@ -143,20 +152,47 @@ export function useSessionEvents(
143
152
  const replay = options.replay ?? "windowed";
144
153
  const fullReplay = replay === "full" || after !== 0;
145
154
  const streamKey = `${workspaceId}\u0000${sessionId ?? ""}\u0000${after}\u0000${fullReplay ? "full" : "windowed"}`;
155
+ // Bind callbacks to this read lifetime, including a return to a previous
156
+ // identity. Publish during render so old callbacks are fenced before passive
157
+ // cleanup can advance the navigation generation.
158
+ const navigationIdentityRef = useRef({ client, streamKey, enabled });
159
+ if (
160
+ navigationIdentityRef.current.client !== client ||
161
+ navigationIdentityRef.current.streamKey !== streamKey ||
162
+ navigationIdentityRef.current.enabled !== enabled
163
+ ) {
164
+ navigationIdentityRef.current = { client, streamKey, enabled };
165
+ }
166
+ const navigationIdentity = navigationIdentityRef.current;
167
+ const ownsNavigation = useCallback(
168
+ () => navigationIdentityRef.current === navigationIdentity,
169
+ [navigationIdentity],
170
+ );
146
171
 
147
172
  const [eventWindow, setEventWindow] = useState<BrowserSessionEventWindow>(EMPTY_EVENT_WINDOW);
173
+ // One navigation witness, not a second history cache. Raw events stay contiguous.
174
+ const [questionEvidence, setQuestionEvidence] = useState<{
175
+ client: EmbeddedSessionClientLike;
176
+ streamKey: string;
177
+ anchor: number;
178
+ events: SessionEvent[];
179
+ } | null>(null);
148
180
  const [connectionState, setConnectionState] = useState<SessionEventsConnectionState>("idle");
149
181
  const [error, setError] = useState<Error | null>(null);
182
+ const [newerError, setNewerError] = useState<Error | null>(null);
150
183
  const [hasOlder, setHasOlder] = useState(false);
151
184
  const [hasNewer, setHasNewer] = useState(false);
152
185
  const [sessionStatusProjection, setSessionStatusProjection] = useState<SessionStatus | null>(
153
186
  null,
154
187
  );
155
188
  const [initialLoading, setInitialLoading] = useState(true);
189
+ const [initialHistoryReady, setInitialHistoryReady] = useState(false);
156
190
  const [loadingOlder, setLoadingOlder] = useState(false);
157
191
  const [loadingNewer, setLoadingNewer] = useState(false);
158
192
  const [loadingOldest, setLoadingOldest] = useState(false);
159
193
  const [loadingLatest, setLoadingLatest] = useState(false);
194
+ const [loadingTarget, setLoadingTarget] = useState(false);
195
+ const loadingTargetRef = useRef(false);
160
196
  const [streamEpoch, setStreamEpoch] = useState(0);
161
197
  // "history" = reading a non-tip window; live SSE must not fill the gap.
162
198
  const [viewMode, setViewMode] = useState<"live" | "history">("live");
@@ -172,9 +208,11 @@ export function useSessionEvents(
172
208
  const loadingLatestRef = useRef(false);
173
209
  const viewModeRef = useRef<"live" | "history">("live");
174
210
  const initialWindowLoadedRef = useRef(false);
211
+ const reconcileAfterPageResumeRef = useRef(false);
175
212
  const streamAbortRef = useRef<AbortController | null>(null);
176
213
  const streamKeyRef = useRef<string | null>(null);
177
214
  const generationRef = useRef(0);
215
+ const navigationGenerationRef = useRef(0);
178
216
  const eventWindowRef = useRef<BrowserSessionEventWindow>(EMPTY_EVENT_WINDOW);
179
217
  const sessionStatusRef = useRef<{
180
218
  sequence: number;
@@ -187,6 +225,25 @@ export function useSessionEvents(
187
225
  // new stream identity cannot expose the previous session's event log.
188
226
  const [stateStreamKey, setStateStreamKey] = useState(streamKey);
189
227
 
228
+ // Navigation belongs to the session/client lifetime, not the transport: an
229
+ // SSE reconnect must never cancel an in-flight history page.
230
+ useEffect(() => {
231
+ navigationGenerationRef.current += 1;
232
+ setNewerError(null);
233
+ loadingOlderRef.current = false;
234
+ loadingNewerRef.current = false;
235
+ loadingOldestRef.current = false;
236
+ loadingTargetRef.current = false;
237
+ setQuestionEvidence(null);
238
+ setLoadingTarget(false);
239
+ setLoadingOlder(false);
240
+ setLoadingNewer(false);
241
+ setLoadingOldest(false);
242
+ return () => {
243
+ navigationGenerationRef.current += 1;
244
+ };
245
+ }, [client, workspaceId, sessionId, after, enabled, fullReplay]);
246
+
190
247
  useEffect(() => {
191
248
  // Reset the accumulated log only when the stream identity changes —
192
249
  // pausing via `enabled: false` keeps the timeline visible.
@@ -205,6 +262,7 @@ export function useSessionEvents(
205
262
  setLoadingOldest(false);
206
263
  setLoadingLatest(false);
207
264
  setInitialLoading(true);
265
+ setInitialHistoryReady(false);
208
266
  lastSequenceRef.current = after;
209
267
  streamResumeSequenceRef.current = after;
210
268
  oldestSequenceRef.current = null;
@@ -218,29 +276,50 @@ export function useSessionEvents(
218
276
  viewModeRef.current = "live";
219
277
  setViewMode("live");
220
278
  initialWindowLoadedRef.current = false;
279
+ reconcileAfterPageResumeRef.current = false;
221
280
  }
222
281
  // AbortController is advisory: custom SDK clients and async iterators may
223
282
  // ignore it and resolve/yield after cleanup. Fence every effect instance so
224
283
  // only the newest dependency generation can mutate refs or React state.
225
284
  const generation = generationRef.current + 1;
226
285
  generationRef.current = generation;
227
- if (loadingOlderRef.current) {
228
- loadingOlderRef.current = false;
229
- setLoadingOlder(false);
230
- }
231
286
  if (!sessionId || !streamEnabled) {
287
+ // A page that stayed hidden beyond the live-activity grace deliberately
288
+ // closed its SSE connection. Replaying from the old cursor on return can
289
+ // drip a large background backlog through React in many fast batches and
290
+ // leave the pinned camera catching up long after the user is foregrounded.
291
+ // Remember that suspension so the next live effect replaces the browser
292
+ // window with one compact durable tail instead.
293
+ if (
294
+ sessionId &&
295
+ enabled &&
296
+ !pageLive &&
297
+ !fullReplay &&
298
+ viewModeRef.current === "live" &&
299
+ initialWindowLoadedRef.current
300
+ ) {
301
+ reconcileAfterPageResumeRef.current = true;
302
+ }
232
303
  setConnectionState("idle");
233
304
  return;
234
305
  }
235
306
  // History view owns a non-tip window; do not open SSE (it would replay the
236
307
  // entire gap into the browser). jumpToLatest / catching loadNewer resume live.
237
308
  if (viewMode === "history") {
309
+ reconcileAfterPageResumeRef.current = false;
238
310
  setConnectionState("idle");
239
311
  setInitialLoading(false);
240
312
  return;
241
313
  }
314
+ const reconcileForegroundResume = reconcileAfterPageResumeRef.current && !fullReplay;
315
+ reconcileAfterPageResumeRef.current = false;
242
316
  const controller = new AbortController();
243
- const isCurrent = () => generationRef.current === generation && !controller.signal.aborted;
317
+ const isCurrent = () =>
318
+ generationRef.current === generation &&
319
+ !controller.signal.aborted &&
320
+ navigationIdentityRef.current.client === client &&
321
+ navigationIdentityRef.current.streamKey === streamKey &&
322
+ navigationIdentityRef.current.enabled === enabled;
244
323
  streamAbortRef.current = controller;
245
324
  // Batch yielded events into one React update per flush window so a long
246
325
  // replay (thousands of events) does not render per event. Project every
@@ -288,7 +367,7 @@ export function useSessionEvents(
288
367
  setSessionStatusProjection(status);
289
368
  }
290
369
  const current = eventWindowRef.current;
291
- const next = boundBrowserSessionEventWindow([...current.events, ...batch]);
370
+ const next = appendBrowserSessionEventWindow(current, batch);
292
371
  const retained = {
293
372
  ...next,
294
373
  truncated: current.truncated || next.truncated,
@@ -309,6 +388,56 @@ export function useSessionEvents(
309
388
 
310
389
  void (async () => {
311
390
  try {
391
+ if (reconcileForegroundResume) {
392
+ setConnectionState("connecting");
393
+ const cursor = streamResumeSequenceRef.current;
394
+ let plan: ForegroundCatchupPlan = { kind: "resume" };
395
+ try {
396
+ plan = await planForegroundCatchup(client, workspaceId, sessionId, cursor, {
397
+ signal: controller.signal,
398
+ });
399
+ } catch {
400
+ if (controller.signal.aborted) return;
401
+ // This read is an optimization gate, not a new availability
402
+ // dependency. If it fails, preserve the SDK's exact cursor replay
403
+ // rather than blanking a usable timeline or failing the stream.
404
+ plan = { kind: "resume" };
405
+ }
406
+ if (!isCurrent()) return;
407
+ if (plan.kind === "append") {
408
+ const current = eventWindowRef.current;
409
+ assertAppendOrder(current.events, plan.events);
410
+ const status = observeSessionStatus(plan.events, sessionStatusRef);
411
+ const next = appendBrowserSessionEventWindow(current, plan.events);
412
+ const retained = {
413
+ ...next,
414
+ truncated: current.truncated || next.truncated,
415
+ };
416
+ eventWindowRef.current = retained;
417
+ oldestSequenceRef.current = retained.events[0]?.sequence ?? null;
418
+ newestSequenceRef.current = maxResumeSequenceOrNull(retained.events);
419
+ lastSequenceRef.current = Math.max(lastSequenceRef.current, plan.resumeSequence);
420
+ streamResumeSequenceRef.current = Math.max(
421
+ streamResumeSequenceRef.current,
422
+ plan.resumeSequence,
423
+ );
424
+ if (status !== undefined) {
425
+ setSessionStatusProjection(status);
426
+ }
427
+ setEventWindow(retained);
428
+ if (retained.truncated) {
429
+ hasOlderRef.current = true;
430
+ setHasOlder(true);
431
+ }
432
+ } else if (plan.kind === "reload") {
433
+ // A large/complex missed window would make the foreground timeline
434
+ // and pinned camera chase many rapid commits. Keep the last known
435
+ // complete window visible until the bounded latest replacement is
436
+ // ready, then install that replacement atomically below.
437
+ initialWindowLoadedRef.current = false;
438
+ setError(null);
439
+ }
440
+ }
312
441
  if (!fullReplay && !initialWindowLoadedRef.current) {
313
442
  setConnectionState("connecting");
314
443
  // First paint is ONE compact fetch — the newest window, revealed at
@@ -316,9 +445,10 @@ export function useSessionEvents(
316
445
  // reader actually scrolls up (the sentinel drives loadOlder).
317
446
  const window = await loadEventWindow(client, workspaceId, sessionId, {
318
447
  before: Number.MAX_SAFE_INTEGER,
319
- pageSize: INITIAL_TAIL_PAGE_SIZE,
448
+ pageSize: SESSION_HISTORY_PAGE_SIZE,
320
449
  targetGroups: Number.POSITIVE_INFINITY,
321
450
  maxFetches: INITIAL_FETCH_CAP,
451
+ boundaryPageCap: BOUNDARY_PAGE_CAP,
322
452
  signal: controller.signal,
323
453
  });
324
454
  if (!isCurrent()) {
@@ -326,6 +456,16 @@ export function useSessionEvents(
326
456
  }
327
457
  const status = observeSessionStatus(window.events, sessionStatusRef);
328
458
  const retained = boundBrowserSessionEventWindow(window.events);
459
+ // A replacement tail retires requests against the discarded window.
460
+ // Ordinary SSE reconnects preserve navigation, but splicing an old
461
+ // page into this new tail could leave an inaccessible history gap.
462
+ navigationGenerationRef.current += 1;
463
+ loadingOlderRef.current = false;
464
+ loadingNewerRef.current = false;
465
+ loadingOldestRef.current = false;
466
+ setLoadingOlder(false);
467
+ setLoadingNewer(false);
468
+ setLoadingOldest(false);
329
469
  eventWindowRef.current = retained;
330
470
  oldestSequenceRef.current = retained.events[0]?.sequence ?? window.oldestSequence;
331
471
  newestSequenceRef.current =
@@ -335,6 +475,8 @@ export function useSessionEvents(
335
475
  lastSequenceRef.current = window.newestSequence;
336
476
  streamResumeSequenceRef.current = window.newestSequence;
337
477
  initialWindowLoadedRef.current = true;
478
+ setInitialHistoryReady(true);
479
+ setError(null);
338
480
  if (status !== undefined) {
339
481
  setSessionStatusProjection(status);
340
482
  }
@@ -357,7 +499,7 @@ export function useSessionEvents(
357
499
  .then(() => reconcileSession(sessionId))
358
500
  .catch((cause) => {
359
501
  if (isCurrent()) {
360
- setError(cause instanceof Error ? cause : new Error(String(cause)));
502
+ setError(normalizeError(cause));
361
503
  }
362
504
  });
363
505
  },
@@ -369,7 +511,7 @@ export function useSessionEvents(
369
511
  });
370
512
  for await (const event of stream) {
371
513
  if (!isCurrent()) break;
372
- const boundedEvent = boundBrowserLegacyEvent(event);
514
+ const boundedEvent = event;
373
515
  const boundedEventBytes = browserJsonBytes(boundedEvent);
374
516
  const separatorBytes = pending.length === 0 ? 0 : 1;
375
517
  if (
@@ -398,7 +540,7 @@ export function useSessionEvents(
398
540
  } catch (cause) {
399
541
  if (isCurrent()) {
400
542
  flush();
401
- setError(cause instanceof Error ? cause : new Error(String(cause)));
543
+ setError(normalizeError(cause));
402
544
  setConnectionState("error");
403
545
  // A failed first compact-tail request has no later success path in
404
546
  // this effect instance. End the loading gate so hosts can render the
@@ -425,6 +567,8 @@ export function useSessionEvents(
425
567
  workspaceId,
426
568
  sessionId,
427
569
  after,
570
+ enabled,
571
+ pageLive,
428
572
  streamEnabled,
429
573
  fullReplay,
430
574
  streamKey,
@@ -434,6 +578,7 @@ export function useSessionEvents(
434
578
  ]);
435
579
 
436
580
  const navigationBusy = (): boolean =>
581
+ loadingTargetRef.current ||
437
582
  loadingOlderRef.current ||
438
583
  loadingNewerRef.current ||
439
584
  loadingOldestRef.current ||
@@ -441,8 +586,8 @@ export function useSessionEvents(
441
586
 
442
587
  const loadOlder = useCallback(
443
588
  (): OlderHistoryLoadReceipt =>
444
- createOlderHistoryLoadReceipt(async (markCommitted) => {
445
- if (!sessionId || navigationBusy() || !hasOlderRef.current) {
589
+ createOlderHistoryLoadReceipt(async (markCommitted, preserveTail, markTailPreserved) => {
590
+ if (!ownsNavigation() || !sessionId || navigationBusy() || !hasOlderRef.current) {
446
591
  return false;
447
592
  }
448
593
  const before = oldestSequenceRef.current;
@@ -452,18 +597,20 @@ export function useSessionEvents(
452
597
  setHasOlder(false);
453
598
  return false;
454
599
  }
455
- const generation = generationRef.current;
600
+ const generation = navigationGenerationRef.current;
601
+ const isCurrent = () => ownsNavigation() && navigationGenerationRef.current === generation;
456
602
  loadingOlderRef.current = true;
457
603
  setLoadingOlder(true);
458
604
  let published = false;
459
605
  try {
460
606
  const window = await loadEventWindow(client, workspaceId, sessionId, {
461
607
  before,
462
- pageSize: OLDER_PAGE_SIZE,
608
+ pageSize: SESSION_HISTORY_PAGE_SIZE,
463
609
  targetGroups: OLDER_GROUP_TARGET,
464
610
  maxFetches: OLDER_FETCH_CAP,
611
+ isCurrent,
465
612
  });
466
- if (generationRef.current !== generation) {
613
+ if (!isCurrent()) {
467
614
  return false;
468
615
  }
469
616
  if (window.events.length === 0) {
@@ -474,14 +621,16 @@ export function useSessionEvents(
474
621
  }
475
622
  const current = eventWindowRef.current;
476
623
  assertPrependOrder(current.events, window.events);
477
- // Freeze the live iterator before replacing its in-memory window. Rows
478
- // pending in the aborted iterator were never cursor-committed and will
479
- // be replayed from the retained high-water mark below.
480
- streamAbortRef.current?.abort();
481
- const status = observeSessionStatus(window.events, sessionStatusRef);
482
624
  const next = boundBrowserSessionEventWindow([...window.events, ...current.events], {
483
625
  direction: "oldest",
484
626
  });
627
+ if (preserveTail && maxResumeSequence(next.events) < maxResumeSequence(current.events)) {
628
+ // Automatic viewport filling must not navigate away from the
629
+ // reader's retained tail. Explicit history navigation may do so.
630
+ markTailPreserved();
631
+ return false;
632
+ }
633
+ const status = observeSessionStatus(window.events, sessionStatusRef);
485
634
  const retained = {
486
635
  ...next,
487
636
  truncated: current.truncated || next.truncated,
@@ -492,21 +641,28 @@ export function useSessionEvents(
492
641
  }
493
642
  const retainedNewest = maxResumeSequenceOrNull(retained.events);
494
643
  const previousNewest = maxResumeSequenceOrNull(current.events);
644
+ const evictedLiveTail =
645
+ previousNewest !== null && retainedNewest !== null && retainedNewest < previousNewest;
646
+ if (evictedLiveTail && viewModeRef.current === "live") {
647
+ // The prepend leaves the live tip. Freeze the iterator before the
648
+ // window is replaced so a late flush cannot append live rows onto
649
+ // (and newest-bound away) the history the reader just requested.
650
+ streamAbortRef.current?.abort();
651
+ }
495
652
  eventWindowRef.current = retained;
496
653
  markCommitted();
497
654
  oldestSequenceRef.current = retainedOldest;
498
655
  newestSequenceRef.current = retainedNewest;
499
- streamResumeSequenceRef.current = maxResumeSequence(retained.events);
656
+ if (evictedLiveTail) {
657
+ streamResumeSequenceRef.current = maxResumeSequence(retained.events);
658
+ }
500
659
  // Oldest-directed eviction can discard newer in-memory rows. That fact
501
660
  // keeps windowTruncated true, but it does not imply older durable rows
502
661
  // exist; only the backward DB page can answer hasOlder truthfully.
503
662
  const olderStillAvailable = window.hasOlder;
504
663
  hasOlderRef.current = olderStillAvailable;
505
- const evictedLiveTail =
506
- previousNewest !== null && retainedNewest !== null && retainedNewest < previousNewest;
507
664
  let newer: boolean | null = null;
508
665
  let enterHistory = false;
509
- let reconnectLive = false;
510
666
  if (viewModeRef.current === "history" || evictedLiveTail) {
511
667
  const highWater = lastSequenceRef.current;
512
668
  newer = retainedNewest !== null && (retainedNewest < highWater || retained.truncated);
@@ -519,11 +675,13 @@ export function useSessionEvents(
519
675
  viewModeRef.current = "history";
520
676
  enterHistory = true;
521
677
  }
522
- } else {
523
- // The retained merge is still one contiguous live suffix. Restart
524
- // SSE from its cursor so events queued during the fetch are replayed.
525
- reconnectLive = true;
526
678
  }
679
+ // Otherwise the merge is still one contiguous live suffix and the open
680
+ // SSE iterator keeps appending to it: every flush reads
681
+ // eventWindowRef, so rows delivered during or after this fetch land
682
+ // once, in order, on the prepended window. Reopening the stream here
683
+ // would flash a connecting state and re-run session reconciliation on
684
+ // every scroll-up for no data benefit.
527
685
  loadingOlderRef.current = false;
528
686
  startTransition(() => {
529
687
  if (status !== undefined) {
@@ -536,39 +694,44 @@ export function useSessionEvents(
536
694
  }
537
695
  if (enterHistory) {
538
696
  setViewMode("history");
539
- } else if (reconnectLive) {
540
- setStreamEpoch((epoch) => epoch + 1);
541
697
  }
542
698
  setLoadingOlder(false);
543
699
  });
544
700
  published = true;
545
701
  return olderStillAvailable;
702
+ } catch (reason) {
703
+ if (!isCurrent()) return false;
704
+ throw reason;
546
705
  } finally {
547
- if (!published) {
706
+ if (!published && isCurrent()) {
548
707
  loadingOlderRef.current = false;
549
708
  setLoadingOlder(false);
550
709
  }
551
710
  }
552
711
  }),
553
- [client, workspaceId, sessionId],
712
+ [client, workspaceId, sessionId, ownsNavigation],
554
713
  );
555
714
 
556
715
  const loadOldest = useCallback(async (): Promise<boolean> => {
557
- if (!sessionId || navigationBusy() || !hasOlderRef.current) {
716
+ if (!ownsNavigation() || !sessionId || navigationBusy() || !hasOlderRef.current) {
558
717
  return false;
559
718
  }
560
- const generation = generationRef.current;
719
+ // Explicit window replacement supersedes unresolved question destinations.
720
+ const generation = ++navigationGenerationRef.current;
721
+ const isCurrent = () => ownsNavigation() && navigationGenerationRef.current === generation;
722
+ setQuestionEvidence(null);
561
723
  loadingOldestRef.current = true;
562
724
  setLoadingOldest(true);
563
725
  let published = false;
564
726
  try {
565
727
  const window = await loadForwardEventWindow(client, workspaceId, sessionId, {
566
728
  after: 0,
567
- pageSize: OLDEST_PAGE_SIZE,
729
+ pageSize: SESSION_HISTORY_PAGE_SIZE,
568
730
  targetGroups: OLDEST_GROUP_TARGET,
569
731
  maxFetches: OLDEST_FETCH_CAP,
732
+ isCurrent,
570
733
  });
571
- if (generationRef.current !== generation) {
734
+ if (!isCurrent()) {
572
735
  return false;
573
736
  }
574
737
  if (window.events.length === 0) {
@@ -594,8 +757,11 @@ export function useSessionEvents(
594
757
  (retainedNewest !== null && retainedNewest < highWater);
595
758
  hasNewerRef.current = newer;
596
759
  initialWindowLoadedRef.current = true;
760
+ setInitialHistoryReady(true);
761
+ setError(null);
597
762
  viewModeRef.current = "history";
598
763
  loadingOldestRef.current = false;
764
+ setNewerError(null);
599
765
  startTransition(() => {
600
766
  if (status !== undefined) {
601
767
  setSessionStatusProjection(status);
@@ -608,16 +774,19 @@ export function useSessionEvents(
608
774
  });
609
775
  published = true;
610
776
  return newer;
777
+ } catch (reason) {
778
+ if (!isCurrent()) return false;
779
+ throw reason;
611
780
  } finally {
612
- if (!published) {
781
+ if (!published && isCurrent()) {
613
782
  loadingOldestRef.current = false;
614
783
  setLoadingOldest(false);
615
784
  }
616
785
  }
617
- }, [client, workspaceId, sessionId]);
786
+ }, [client, workspaceId, sessionId, ownsNavigation]);
618
787
 
619
788
  const loadNewer = useCallback(async (): Promise<boolean> => {
620
- if (!sessionId || navigationBusy() || !hasNewerRef.current) {
789
+ if (!ownsNavigation() || !sessionId || navigationBusy() || !hasNewerRef.current) {
621
790
  return false;
622
791
  }
623
792
  const afterSequence = newestSequenceRef.current;
@@ -626,21 +795,24 @@ export function useSessionEvents(
626
795
  setHasNewer(false);
627
796
  return false;
628
797
  }
629
- const generation = generationRef.current;
798
+ const generation = navigationGenerationRef.current;
799
+ const isCurrent = () => ownsNavigation() && navigationGenerationRef.current === generation;
630
800
  loadingNewerRef.current = true;
631
801
  setLoadingNewer(true);
632
802
  let published = false;
633
803
  try {
634
804
  const window = await loadForwardEventWindow(client, workspaceId, sessionId, {
635
805
  after: afterSequence,
636
- pageSize: NEWER_PAGE_SIZE,
806
+ pageSize: SESSION_HISTORY_PAGE_SIZE,
637
807
  targetGroups: NEWER_GROUP_TARGET,
638
808
  maxFetches: NEWER_FETCH_CAP,
809
+ isCurrent,
639
810
  });
640
- if (generationRef.current !== generation) {
811
+ if (!isCurrent()) {
641
812
  return false;
642
813
  }
643
814
  if (window.events.length === 0) {
815
+ setNewerError(null);
644
816
  hasNewerRef.current = false;
645
817
  setHasNewer(false);
646
818
  // Caught up with durable history — resume the live tip stream.
@@ -655,9 +827,7 @@ export function useSessionEvents(
655
827
  const status = observeSessionStatus(window.events, sessionStatusRef);
656
828
  const previousOldest = current.events[0]?.sequence ?? null;
657
829
  // Moving forward through history: keep the newest suffix of the merge.
658
- const next = boundBrowserSessionEventWindow([...current.events, ...window.events], {
659
- direction: "newest",
660
- });
830
+ const next = appendBrowserSessionEventWindow(current, window.events);
661
831
  const retained = {
662
832
  ...next,
663
833
  truncated: current.truncated || next.truncated || window.hasNewer,
@@ -693,6 +863,7 @@ export function useSessionEvents(
693
863
  resumeLive = true;
694
864
  }
695
865
  loadingNewerRef.current = false;
866
+ setNewerError(null);
696
867
  startTransition(() => {
697
868
  if (status !== undefined) {
698
869
  setSessionStatusProjection(status);
@@ -708,20 +879,171 @@ export function useSessionEvents(
708
879
  });
709
880
  published = true;
710
881
  return newer;
882
+ } catch (reason) {
883
+ // A settled request from an old navigation lifetime is not a failure of
884
+ // the current session. Preserve current loading/error state as well.
885
+ if (!isCurrent()) {
886
+ return false;
887
+ }
888
+ setNewerError(normalizeError(reason));
889
+ // Keep authorization and integrity failures actionable for callers;
890
+ // timeline-owned invocations attach their own explicit recovery UI.
891
+ throw reason;
711
892
  } finally {
712
- if (!published) {
893
+ if (!published && isCurrent()) {
713
894
  loadingNewerRef.current = false;
714
895
  setLoadingNewer(false);
715
896
  }
716
897
  }
717
- }, [client, workspaceId, sessionId]);
898
+ }, [client, workspaceId, sessionId, ownsNavigation]);
899
+
900
+ const jumpToSequence = useCallback(
901
+ async (sequence: number, navigationOptions?: { signal?: AbortSignal }): Promise<boolean> => {
902
+ const signal = navigationOptions?.signal;
903
+ if (!ownsNavigation() || !sessionId || !Number.isSafeInteger(sequence) || sequence < 1)
904
+ return false;
905
+ // A navigation that was already cancelled (for example Find closed before
906
+ // the caller could react) must not disturb any current view state.
907
+ if (signal?.aborted) return false;
908
+ // Explicit targets supersede both other targets and adjacent-page requests.
909
+ const generation = ++navigationGenerationRef.current;
910
+ setQuestionEvidence(null);
911
+ const current = () => generation === navigationGenerationRef.current && ownsNavigation();
912
+ const previousMode = viewModeRef.current;
913
+ streamAbortRef.current?.abort();
914
+ generationRef.current += 1;
915
+ viewModeRef.current = "history";
916
+ setViewMode("history");
917
+ loadingOlderRef.current = loadingNewerRef.current = loadingOldestRef.current = false;
918
+ setLoadingOlder(false);
919
+ setLoadingNewer(false);
920
+ setLoadingOldest(false);
921
+ loadingTargetRef.current = true;
922
+ setLoadingTarget(true);
923
+ let published = false;
924
+ try {
925
+ const [before, following] = await Promise.all([
926
+ loadPreviousPage(client, workspaceId, sessionId, sequence + 1, { pageSize: 128 }),
927
+ loadNextPage(client, workspaceId, sessionId, sequence, { pageSize: 128 }),
928
+ ]);
929
+ // Close-during-fetch fence: a pending jump must not replace the window
930
+ // after its owner (for example ConversationFind) has gone away. The
931
+ // finally block still settles loadingTarget for this generation.
932
+ if (signal?.aborted) return false;
933
+ if (!current()) return false;
934
+ if (!before.some((event) => event.sequence === sequence)) return false;
935
+ // Bound each side, keeping the target even if its message alone exceeds
936
+ // the ordinary byte budget. Never evict the very event being navigated to.
937
+ const prefix = boundBrowserSessionEventWindow(before, {
938
+ direction: "newest",
939
+ maxBytes: SESSION_EVENT_BROWSER_MAX_BYTES / 2,
940
+ });
941
+ const retained = boundBrowserSessionEventWindow([...prefix.events, ...following], {
942
+ direction: "oldest",
943
+ });
944
+ if (!retained.events.some((event) => event.sequence === sequence)) return false;
945
+ streamAbortRef.current?.abort();
946
+ generationRef.current += 1;
947
+ const status = observeSessionStatus(retained.events, sessionStatusRef);
948
+ eventWindowRef.current = retained;
949
+ oldestSequenceRef.current = retained.events[0]!.sequence;
950
+ newestSequenceRef.current = maxResumeSequenceOrNull(retained.events);
951
+ lastSequenceRef.current = Math.max(lastSequenceRef.current, newestSequenceRef.current ?? 0);
952
+ hasOlderRef.current = !isLogStart(retained.events[0]!);
953
+ // Compact/byte-bounded pages may be short despite more durable history.
954
+ // Only an empty forward page proves the end (same rule as loadNewer).
955
+ hasNewerRef.current =
956
+ following.length > 0 ||
957
+ retained.truncated ||
958
+ (newestSequenceRef.current ?? 0) < lastSequenceRef.current;
959
+ initialWindowLoadedRef.current = true;
960
+ setInitialHistoryReady(true);
961
+ setError(null);
962
+ viewModeRef.current = "history";
963
+ setEventWindow(retained);
964
+ setHasOlder(hasOlderRef.current);
965
+ setHasNewer(hasNewerRef.current);
966
+ setInitialLoading(false);
967
+ setViewMode("history");
968
+ setNewerError(null);
969
+ if (status !== undefined) setSessionStatusProjection(status);
970
+ published = true;
971
+ return true;
972
+ } catch (reason) {
973
+ if (!current() || signal?.aborted) return false;
974
+ throw reason;
975
+ } finally {
976
+ if (current()) {
977
+ loadingTargetRef.current = false;
978
+ setLoadingTarget(false);
979
+ if (!published && previousMode === "live") {
980
+ viewModeRef.current = "live";
981
+ setViewMode("live");
982
+ setStreamEpoch((epoch) => epoch + 1);
983
+ }
984
+ }
985
+ }
986
+ },
987
+ [client, workspaceId, sessionId, ownsNavigation],
988
+ );
989
+
990
+ const jumpToLatestQuestion = useCallback(
991
+ async (questionOptions?: LatestQuestionOptions): Promise<number | null> => {
992
+ if (!sessionId || !ownsNavigation()) return null;
993
+ let generation = navigationGenerationRef.current;
994
+ const current = () => ownsNavigation() && generation === navigationGenerationRef.current;
995
+ try {
996
+ // Optional action code stays off the session-open path. Capture identity
997
+ // before the import and fence its completion just like network reads.
998
+ const { resolveLatestQuestion } = await import("./latest-question");
999
+ if (!current()) return null;
1000
+ const destination = await resolveLatestQuestion({
1001
+ client,
1002
+ workspaceId,
1003
+ sessionId,
1004
+ isCurrent: current,
1005
+ resumeSequence: maxResumeSequence,
1006
+ options: questionOptions,
1007
+ });
1008
+ if (!destination || !current()) return null;
1009
+ if (!eventWindowRef.current.events.some((event) => event.sequence === destination.anchor)) {
1010
+ const navigation = jumpToSequence(destination.anchor);
1011
+ generation = navigationGenerationRef.current;
1012
+ if (!(await navigation) || !current()) return null;
1013
+ }
1014
+ setQuestionEvidence({
1015
+ client,
1016
+ streamKey,
1017
+ anchor: destination.anchor,
1018
+ events: destination.events,
1019
+ });
1020
+ return destination.questionSequence;
1021
+ } catch (reason) {
1022
+ if (!current()) return null;
1023
+ throw reason;
1024
+ }
1025
+ },
1026
+ [client, workspaceId, sessionId, streamKey, ownsNavigation, jumpToSequence],
1027
+ );
718
1028
 
719
1029
  const jumpToLatest = useCallback(async (): Promise<void> => {
1030
+ // An old host retry closure must not clear a replacement session's error
1031
+ // or abort its live feed before the passive effect cleanup has run.
1032
+ if (!ownsNavigation()) return;
1033
+ if (loadingTargetRef.current) {
1034
+ navigationGenerationRef.current += 1;
1035
+ loadingTargetRef.current = false;
1036
+ setLoadingTarget(false);
1037
+ }
720
1038
  if (!sessionId || navigationBusy()) {
721
1039
  return;
722
1040
  }
1041
+ navigationGenerationRef.current += 1;
1042
+ setQuestionEvidence(null);
723
1043
  loadingLatestRef.current = true;
724
1044
  setLoadingLatest(true);
1045
+ setNewerError(null);
1046
+ setError(null);
725
1047
  let published = false;
726
1048
  try {
727
1049
  streamAbortRef.current?.abort();
@@ -752,21 +1074,37 @@ export function useSessionEvents(
752
1074
  setLoadingLatest(false);
753
1075
  }
754
1076
  }
755
- }, [after, sessionId]);
1077
+ }, [after, sessionId, ownsNavigation]);
756
1078
 
757
1079
  const identityMatches = stateStreamKey === streamKey;
758
1080
  const visibleEvents = identityMatches ? eventWindow.events : EMPTY_EVENTS;
759
- const timeline = useMemo(() => buildTimeline(visibleEvents), [visibleEvents]);
1081
+ const timeline = useMemo(() => {
1082
+ const witness =
1083
+ questionEvidence?.client === client &&
1084
+ questionEvidence.streamKey === streamKey &&
1085
+ visibleEvents.some((event) => event.sequence === questionEvidence.anchor)
1086
+ ? questionEvidence.events
1087
+ : EMPTY_EVENTS;
1088
+ const timelineOptions = { partialStart: hasOlder || eventWindow.truncated || after > 0 };
1089
+ if (witness.length === 0) return buildTimeline(visibleEvents, timelineOptions);
1090
+ const ids = new Set(visibleEvents.map((event) => event.id));
1091
+ return buildTimeline(
1092
+ [...visibleEvents, ...witness.filter((event) => !ids.has(event.id))],
1093
+ timelineOptions,
1094
+ );
1095
+ }, [visibleEvents, hasOlder, eventWindow.truncated, after, questionEvidence, client, streamKey]);
760
1096
 
761
1097
  return {
762
1098
  events: visibleEvents,
763
1099
  timeline,
764
1100
  sessionStatus: identityMatches ? sessionStatusProjection : null,
1101
+ sessionStatusSequence: identityMatches ? sessionStatusRef.current.sequence : 0,
765
1102
  connectionState: identityMatches ? connectionState : "idle",
766
1103
  lastSequence: identityMatches ? lastSequenceRef.current : after,
767
1104
  windowBytes: identityMatches ? eventWindow.bytes : 2,
768
1105
  windowTruncated: identityMatches ? eventWindow.truncated : false,
769
1106
  initialLoading: fullReplay ? false : identityMatches ? initialLoading : true,
1107
+ initialHistoryReady: identityMatches && initialHistoryReady,
770
1108
  hasOlder: !identityMatches ? false : hasOlder,
771
1109
  loadingOlder: !identityMatches ? false : loadingOlder,
772
1110
  loadOlder,
@@ -777,7 +1115,10 @@ export function useSessionEvents(
777
1115
  loadOldest,
778
1116
  loadingLatest: !identityMatches ? false : loadingLatest,
779
1117
  jumpToLatest,
780
- error: identityMatches ? error : null,
1118
+ jumpToLatestQuestion,
1119
+ jumpToSequence,
1120
+ loadingTarget: identityMatches && loadingTarget,
1121
+ error: identityMatches ? (newerError ?? error) : null,
781
1122
  };
782
1123
  }
783
1124
 
@@ -788,7 +1129,8 @@ export function useSessionEvents(
788
1129
  * deliberately separate from durable history and transport paging: when a
789
1130
  * backward page evicts the live tail, the hook enters bounded history mode and
790
1131
  * preserves the highest-ever-observed sequence separately for forward paging.
791
- * The source event remains durable in PostgreSQL throughout.
1132
+ * The source event remains durable in PostgreSQL throughout. An event larger
1133
+ * than the byte target is retained alone, never replaced by a lossy preview.
792
1134
  */
793
1135
  export function boundBrowserSessionEventWindow(
794
1136
  events: readonly SessionEvent[],
@@ -800,7 +1142,7 @@ export function boundBrowserSessionEventWindow(
800
1142
  ): BrowserSessionEventWindow {
801
1143
  const maxBytes = Math.max(1024, options.maxBytes ?? SESSION_EVENT_BROWSER_MAX_BYTES);
802
1144
  const maxCount = Math.max(1, Math.floor(options.maxCount ?? SESSION_EVENT_BROWSER_MAX_COUNT));
803
- const safe = events.map(boundBrowserLegacyEvent);
1145
+ const safe = events;
804
1146
  const selected: SessionEvent[] = [];
805
1147
  let bytes = 2; // []
806
1148
  const direction = options.direction ?? "newest";
@@ -811,7 +1153,7 @@ export function boundBrowserSessionEventWindow(
811
1153
  const event = safe[index]!;
812
1154
  const eventBytes = browserJsonBytes(event);
813
1155
  const separator = selected.length === 0 ? 0 : 1;
814
- if (bytes + separator + eventBytes > maxBytes) break;
1156
+ if (selected.length > 0 && bytes + separator + eventBytes > maxBytes) break;
815
1157
  selected.push(event);
816
1158
  bytes += separator + eventBytes;
817
1159
  }
@@ -823,248 +1165,35 @@ export function boundBrowserSessionEventWindow(
823
1165
  };
824
1166
  }
825
1167
 
826
- function boundBrowserLegacyEvent(event: SessionEvent): SessionEvent {
827
- // The server-side canonical projection lives in @opengeni/contracts. The
828
- // publishable React package may depend only on the zero-runtime-dependency
829
- // SDK, so this is intentionally a last-resort client guard rather than a
830
- // second durable representation. Reconstructing the SDK wire shape prevents
831
- // legacy/malformed extra properties from bypassing the browser byte cap.
832
- const serialized = browserSerialize(event);
833
- const originalBytes = serialized.serializable
834
- ? encoder.encode(serialized.value).byteLength
835
- : null;
836
- const typeIsSafe =
837
- browserUtf8Bytes(event.type) <= BROWSER_EVENT_TYPE_MAX_BYTES &&
838
- !event.type.includes("\n") &&
839
- !event.type.includes("\r");
840
- const clientEventId = boundBrowserOptionalText(
841
- event.clientEventId,
842
- BROWSER_EVENT_CLIENT_ID_MAX_BYTES,
843
- );
844
- const duplicateReason = boundBrowserOptionalText(
845
- event.duplicateReason,
846
- BROWSER_EVENT_DUPLICATE_REASON_MAX_BYTES,
847
- );
848
-
849
- if (
850
- serialized.serializable &&
851
- originalBytes !== null &&
852
- originalBytes <= SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES &&
853
- typeIsSafe &&
854
- clientEventId === event.clientEventId &&
855
- duplicateReason === event.duplicateReason
856
- ) {
857
- return event;
858
- }
859
-
860
- const envelopeProjection = [
861
- !typeIsSafe
862
- ? browserEnvelopeFieldProjection("type", event.type, "session.event.envelope_omitted")
863
- : null,
864
- clientEventId !== event.clientEventId
865
- ? browserEnvelopeFieldProjection("clientEventId", event.clientEventId, clientEventId)
866
- : null,
867
- duplicateReason !== event.duplicateReason
868
- ? browserEnvelopeFieldProjection("duplicateReason", event.duplicateReason, duplicateReason)
869
- : null,
870
- ].filter((field) => field !== null);
871
- const payloadSerialization = browserSerialize(event.payload);
872
- const payloadBytes = payloadSerialization.serializable
873
- ? encoder.encode(payloadSerialization.value).byteLength
874
- : null;
875
- const preview = truncateBrowserUtf8Middle(
876
- payloadSerialization.value,
877
- BROWSER_EVENT_PAYLOAD_PREVIEW_MAX_BYTES,
878
- );
879
- const truncation = {
880
- truncated: true as const,
881
- surface: "browser_legacy_guard" as const,
882
- reason: serialized.serializable ? "event_envelope_bytes_exceeded" : "event_not_serializable",
883
- originalBytes,
884
- deliveredBytes: 0,
885
- omittedBytes: originalBytes,
886
- estimatedOriginalTokens: originalBytes === null ? null : Math.ceil(originalBytes / 4),
887
- estimatedDeliveredTokens: 0,
888
- fullEvidence: { available: false as const, reason: "not_retained" as const },
889
- details: [
890
- {
891
- path: "$.payload",
892
- kind: "payload_preview",
893
- originalBytes: payloadBytes,
894
- deliveredBytes: browserUtf8Bytes(preview),
895
- },
896
- ],
897
- };
898
- const payload: Record<string, unknown> = {
899
- ...browserPayloadIdentity(event.payload),
900
- preview,
901
- ...(envelopeProjection.length > 0
902
- ? {
903
- originalType: boundBrowserText(event.type, BROWSER_EVENT_TYPE_MAX_BYTES),
904
- envelopeProjection: {
905
- truncated: true,
906
- surface: "browser_legacy_guard",
907
- fields: envelopeProjection,
908
- },
909
- }
910
- : {}),
911
- truncation,
912
- };
913
- const bounded: SessionEvent = {
914
- id: boundBrowserText(event.id, BROWSER_EVENT_ID_MAX_BYTES),
915
- workspaceId: boundBrowserText(event.workspaceId, BROWSER_EVENT_ID_MAX_BYTES),
916
- sessionId: boundBrowserText(event.sessionId, BROWSER_EVENT_ID_MAX_BYTES),
917
- sequence: event.sequence,
918
- type: typeIsSafe ? event.type : "session.event.envelope_omitted",
919
- payload,
920
- occurredAt: boundBrowserText(event.occurredAt, BROWSER_EVENT_ID_MAX_BYTES),
921
- clientEventId,
922
- turnId: boundBrowserOptionalText(event.turnId, BROWSER_EVENT_ID_MAX_BYTES),
923
- turnGeneration: event.turnGeneration,
924
- turnAttemptId: boundBrowserOptionalText(event.turnAttemptId, BROWSER_EVENT_ID_MAX_BYTES),
925
- turnAssociation: event.turnAssociation,
926
- duplicateOfEventId: boundBrowserOptionalText(
927
- event.duplicateOfEventId,
928
- BROWSER_EVENT_ID_MAX_BYTES,
929
- ),
930
- duplicateReason,
931
- };
932
-
933
- settleBrowserEventTruncation(bounded, truncation);
934
- if (browserJsonBytes(bounded) > SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES) {
935
- payload.preview = truncateBrowserUtf8Middle(String(payload.preview), 4 * 1024);
936
- truncation.details = truncation.details.slice(0, 1);
937
- settleBrowserEventTruncation(bounded, truncation);
938
- }
939
- if (browserJsonBytes(bounded) > SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES) {
940
- bounded.clientEventId = null;
941
- bounded.duplicateReason = null;
942
- payload.preview = "[legacy event omitted at the browser byte boundary]";
943
- settleBrowserEventTruncation(bounded, truncation);
944
- }
945
- return bounded;
946
- }
947
-
948
- function browserJsonBytes(value: unknown): number {
949
- return encoder.encode(browserSerialize(value).value).byteLength;
950
- }
951
-
952
- function browserSerialize(value: unknown): { value: string; serializable: boolean } {
953
- try {
954
- const serialized = JSON.stringify(value);
955
- return { value: serialized === undefined ? "null" : serialized, serializable: true };
956
- } catch {
957
- return {
958
- value: '"[unserializable event payload omitted at browser boundary]"',
959
- serializable: false,
960
- };
1168
+ /** @internal Append immutable events without serializing the retained history again. */
1169
+ export function appendBrowserSessionEventWindow(
1170
+ current: BrowserSessionEventWindow,
1171
+ batch: readonly SessionEvent[],
1172
+ options: { maxBytes?: number; maxCount?: number } = {},
1173
+ ): BrowserSessionEventWindow {
1174
+ const maxBytes = Math.max(1024, options.maxBytes ?? SESSION_EVENT_BROWSER_MAX_BYTES);
1175
+ const maxCount = Math.max(1, Math.floor(options.maxCount ?? SESSION_EVENT_BROWSER_MAX_COUNT));
1176
+ const events = [...current.events, ...batch];
1177
+ let bytes = current.bytes;
1178
+ for (let index = 0; index < batch.length; index += 1) {
1179
+ bytes += browserJsonBytes(batch[index]) + (current.events.length + index > 0 ? 1 : 0);
961
1180
  }
962
- }
963
-
964
- function browserPayloadIdentity(payload: unknown): Record<string, unknown> {
965
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) return {};
966
- const record = payload as Record<string, unknown>;
967
- const identity: Record<string, unknown> = {};
968
- for (const field of BROWSER_EVENT_PAYLOAD_IDENTITY_FIELDS) {
969
- const value = record[field];
970
- if (typeof value === "string") {
971
- identity[field] = boundBrowserText(value, BROWSER_EVENT_ID_MAX_BYTES);
972
- } else if (typeof value === "number" || typeof value === "boolean" || value === null) {
973
- identity[field] = value;
974
- }
1181
+ let start = 0;
1182
+ // Preserve the newest event even when it alone exceeds the byte target,
1183
+ // matching the full-window reducer and retaining exact cursor progress.
1184
+ while (events.length - start > 1 && (bytes > maxBytes || events.length - start > maxCount)) {
1185
+ bytes -= browserJsonBytes(events[start]) + 1;
1186
+ start += 1;
975
1187
  }
976
- return identity;
977
- }
978
-
979
- function browserEnvelopeFieldProjection(
980
- field: string,
981
- original: string | null | undefined,
982
- delivered: string | null | undefined,
983
- ): { field: string; originalBytes: number; deliveredBytes: number } {
984
1188
  return {
985
- field,
986
- originalBytes: typeof original === "string" ? browserUtf8Bytes(original) : 0,
987
- deliveredBytes: typeof delivered === "string" ? browserUtf8Bytes(delivered) : 0,
1189
+ events: start === 0 ? events : events.slice(start),
1190
+ bytes,
1191
+ truncated: current.truncated || start > 0,
988
1192
  };
989
1193
  }
990
1194
 
991
- function boundBrowserOptionalText<T extends string | null | undefined>(
992
- value: T,
993
- maxBytes: number,
994
- ): T {
995
- return (typeof value === "string" ? boundBrowserText(value, maxBytes) : value) as T;
996
- }
997
-
998
- function boundBrowserText(value: string, maxBytes: number): string {
999
- const bytes = encoder.encode(value);
1000
- if (bytes.byteLength <= maxBytes) return value;
1001
- const marker = "…[truncated]";
1002
- const prefixBudget = Math.max(0, maxBytes - browserUtf8Bytes(marker));
1003
- let prefixEnd = Math.min(prefixBudget, bytes.byteLength);
1004
- while (
1005
- prefixEnd > 0 &&
1006
- prefixEnd < bytes.byteLength &&
1007
- isBrowserUtf8Continuation(bytes[prefixEnd]!)
1008
- ) {
1009
- prefixEnd -= 1;
1010
- }
1011
- return `${decoder.decode(bytes.subarray(0, prefixEnd))}${marker}`;
1012
- }
1013
-
1014
- function truncateBrowserUtf8Middle(value: string, maxBytes: number): string {
1015
- const bytes = encoder.encode(value);
1016
- if (bytes.byteLength <= maxBytes) return value;
1017
- const marker = `…[${bytes.byteLength - maxBytes} bytes omitted]…`;
1018
- const contentBudget = Math.max(0, maxBytes - browserUtf8Bytes(marker));
1019
- const leftBudget = Math.floor(contentBudget / 2);
1020
- const rightBudget = contentBudget - leftBudget;
1021
- let leftEnd = Math.min(leftBudget, bytes.byteLength);
1022
- while (leftEnd > 0 && leftEnd < bytes.byteLength && isBrowserUtf8Continuation(bytes[leftEnd]!)) {
1023
- leftEnd -= 1;
1024
- }
1025
- let rightStart = Math.max(0, bytes.byteLength - rightBudget);
1026
- while (rightStart < bytes.byteLength && isBrowserUtf8Continuation(bytes[rightStart]!)) {
1027
- rightStart += 1;
1028
- }
1029
- return `${decoder.decode(bytes.subarray(0, leftEnd))}${marker}${decoder.decode(bytes.subarray(rightStart))}`;
1030
- }
1031
-
1032
- function settleBrowserEventTruncation(
1033
- event: SessionEvent,
1034
- truncation: {
1035
- originalBytes: number | null;
1036
- deliveredBytes: number;
1037
- omittedBytes: number | null;
1038
- estimatedDeliveredTokens: number;
1039
- },
1040
- ): void {
1041
- for (let attempt = 0; attempt < 16; attempt += 1) {
1042
- const deliveredBytes = browserJsonBytes(event);
1043
- const omittedBytes =
1044
- truncation.originalBytes === null
1045
- ? null
1046
- : Math.max(0, truncation.originalBytes - deliveredBytes);
1047
- const estimatedDeliveredTokens = Math.ceil(deliveredBytes / 4);
1048
- if (
1049
- truncation.deliveredBytes === deliveredBytes &&
1050
- truncation.omittedBytes === omittedBytes &&
1051
- truncation.estimatedDeliveredTokens === estimatedDeliveredTokens
1052
- ) {
1053
- return;
1054
- }
1055
- truncation.deliveredBytes = deliveredBytes;
1056
- truncation.omittedBytes = omittedBytes;
1057
- truncation.estimatedDeliveredTokens = estimatedDeliveredTokens;
1058
- }
1059
- throw new RangeError("Browser event byte accounting did not converge");
1060
- }
1061
-
1062
- function browserUtf8Bytes(value: string): number {
1063
- return encoder.encode(value).byteLength;
1064
- }
1065
-
1066
- function isBrowserUtf8Continuation(value: number): boolean {
1067
- return (value & 0xc0) === 0x80;
1195
+ function browserJsonBytes(value: unknown): number {
1196
+ return encoder.encode(JSON.stringify(value)).byteLength;
1068
1197
  }
1069
1198
 
1070
1199
  function observeSessionStatus(
@@ -1083,6 +1212,68 @@ function observeSessionStatus(
1083
1212
  return latest.status;
1084
1213
  }
1085
1214
 
1215
+ type ForegroundCatchupPlan =
1216
+ | { kind: "resume" }
1217
+ | { kind: "append"; events: SessionEvent[]; resumeSequence: number }
1218
+ | { kind: "reload" };
1219
+
1220
+ /**
1221
+ * Decide how a sustained hidden-tab suspension rejoins the durable event log.
1222
+ *
1223
+ * `lastSequence - cursor` is the exact raw durable work missed, but it is not
1224
+ * the number of visible messages: one streaming answer can own thousands of
1225
+ * adjacent delta rows. Small raw gaps therefore resume directly; medium gaps
1226
+ * get one forward compact page and are judged by the browser-visible result;
1227
+ * a page that cannot prove complete bounded coverage, or that would add too
1228
+ * many rows/groups/bytes, reloads the latest tail instead.
1229
+ */
1230
+ async function planForegroundCatchup(
1231
+ client: EmbeddedSessionClientLike,
1232
+ workspaceId: string,
1233
+ sessionId: string,
1234
+ cursor: number,
1235
+ options: { signal?: AbortSignal } = {},
1236
+ ): Promise<ForegroundCatchupPlan> {
1237
+ if (options.signal?.aborted) throw abortError();
1238
+ const session = await client.getSession(workspaceId, sessionId, {
1239
+ fresh: true,
1240
+ ...(options.signal ? { signal: options.signal } : {}),
1241
+ });
1242
+ if (options.signal?.aborted) throw abortError();
1243
+ const durableHead = Math.max(cursor, session.lastSequence);
1244
+ const rawGap = durableHead - cursor;
1245
+ if (rawGap <= FOREGROUND_DIRECT_REPLAY_MAX_SEQUENCES) {
1246
+ return { kind: "resume" };
1247
+ }
1248
+ if (rawGap > FOREGROUND_COMPACT_PROBE_MAX_SEQUENCES) {
1249
+ return { kind: "reload" };
1250
+ }
1251
+
1252
+ const page = await loadNextPage(client, workspaceId, sessionId, cursor, {
1253
+ pageSize: rawGap,
1254
+ ...(options.signal ? { signal: options.signal } : {}),
1255
+ });
1256
+ const events = page.filter((event) => eventResumeSequence(event) > cursor);
1257
+ if (events.length === 0) {
1258
+ return { kind: "reload" };
1259
+ }
1260
+ assertAscending(events);
1261
+ const resumeSequence = maxResumeSequence(events);
1262
+ if (resumeSequence < durableHead) {
1263
+ // Byte/count truncation or an incomplete projection means this page cannot
1264
+ // prove that one append reaches the head observed above.
1265
+ return { kind: "reload" };
1266
+ }
1267
+ if (
1268
+ events.length > FOREGROUND_COMPACT_CATCHUP_MAX_EVENTS ||
1269
+ groupCount(events) > FOREGROUND_COMPACT_CATCHUP_MAX_GROUPS ||
1270
+ browserJsonBytes(events) > FOREGROUND_COMPACT_CATCHUP_MAX_BYTES
1271
+ ) {
1272
+ return { kind: "reload" };
1273
+ }
1274
+ return { kind: "append", events, resumeSequence };
1275
+ }
1276
+
1086
1277
  type LoadedEventWindow = {
1087
1278
  events: SessionEvent[];
1088
1279
  oldestSequence: number | null;
@@ -1106,7 +1297,9 @@ async function loadEventWindow(
1106
1297
  pageSize: number;
1107
1298
  targetGroups: number;
1108
1299
  maxFetches: number;
1300
+ boundaryPageCap?: number;
1109
1301
  signal?: AbortSignal;
1302
+ isCurrent?: () => boolean;
1110
1303
  },
1111
1304
  ): Promise<LoadedEventWindow> {
1112
1305
  let cursor = options.before;
@@ -1122,6 +1315,7 @@ async function loadEventWindow(
1122
1315
  pageSize: options.pageSize,
1123
1316
  ...(options.signal ? { signal: options.signal } : {}),
1124
1317
  });
1318
+ if (options.isCurrent?.() === false) throw abortError();
1125
1319
  fetches += 1;
1126
1320
  if (page.length === 0) {
1127
1321
  reachedStart = true;
@@ -1140,19 +1334,19 @@ async function loadEventWindow(
1140
1334
  // turn boundary already in the buffer — the dropped fragment is refetched by
1141
1335
  // the next loadOlder (everything below the new oldest sequence), whose own
1142
1336
  // window snaps the same way, so every seam lands on a turn start. Extra
1143
- // pages are fetched only when the buffer holds no boundary at all (one
1144
- // monster turn); past the cap a mid-turn top is accepted.
1337
+ // page is fetched only when the buffer holds no boundary at all (one dense
1338
+ // turn); past the cap the existing truncation/hasOlder signal remains true.
1145
1339
  let snapPages = 0;
1146
1340
  while (
1147
1341
  !reachedStart &&
1148
1342
  findBoundaryIndex(buffer) === -1 &&
1149
- snapPages < BOUNDARY_PAGE_CAP &&
1150
- fetches < options.maxFetches
1343
+ snapPages < (options.boundaryPageCap ?? 0)
1151
1344
  ) {
1152
1345
  const page = await loadPreviousPage(client, workspaceId, sessionId, cursor, {
1153
1346
  pageSize: options.pageSize,
1154
1347
  ...(options.signal ? { signal: options.signal } : {}),
1155
1348
  });
1349
+ if (options.isCurrent?.() === false) throw abortError();
1156
1350
  fetches += 1;
1157
1351
  snapPages += 1;
1158
1352
  if (page.length === 0) {
@@ -1216,6 +1410,7 @@ async function loadForwardEventWindow(
1216
1410
  targetGroups: number;
1217
1411
  maxFetches: number;
1218
1412
  signal?: AbortSignal;
1413
+ isCurrent?: () => boolean;
1219
1414
  },
1220
1415
  ): Promise<LoadedForwardEventWindow> {
1221
1416
  let cursor = options.after;
@@ -1231,6 +1426,7 @@ async function loadForwardEventWindow(
1231
1426
  pageSize: options.pageSize,
1232
1427
  ...(options.signal ? { signal: options.signal } : {}),
1233
1428
  });
1429
+ if (options.isCurrent?.() === false) throw abortError();
1234
1430
  fetches += 1;
1235
1431
  if (page.length === 0) {
1236
1432
  reachedEnd = true;
@@ -1378,13 +1574,13 @@ function maxResumeSequenceOrNull(events: readonly SessionEvent[]): number | null
1378
1574
  }
1379
1575
 
1380
1576
  function eventResumeSequence(event: SessionEvent): number {
1381
- const payload = asRecord(event.payload);
1382
- const coalescedUntil = Number(payload.coalescedUntil);
1383
- return Math.max(event.sequence, Number.isFinite(coalescedUntil) ? Math.floor(coalescedUntil) : 0);
1384
- }
1385
-
1386
- function asRecord(value: unknown): Record<string, unknown> {
1387
- return value && typeof value === "object" ? (value as Record<string, unknown>) : {};
1577
+ const streamedCoverage = sessionEventStreamCoveredThrough(event);
1578
+ if (streamedCoverage !== null) return streamedCoverage;
1579
+ return typeof event.coveredThrough === "number" &&
1580
+ Number.isSafeInteger(event.coveredThrough) &&
1581
+ event.coveredThrough >= event.sequence
1582
+ ? event.coveredThrough
1583
+ : event.sequence;
1388
1584
  }
1389
1585
 
1390
1586
  function assertAscending(events: SessionEvent[]): void {