@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,20 +1,36 @@
1
1
  import {
2
+ parseCustomMcpSetupRequest,
2
3
  parseMediaGenerationResult,
4
+ parseToolDisplayMetadata,
5
+ EMPTY_FINAL_REPLY_NOTICE,
6
+ turnCompletedWithEmptyFinalReply,
3
7
  type HumanInputAnswer,
4
8
  type HumanInputQuestion,
5
9
  type HumanInputResponse,
10
+ type HumanInputSecretAnswerResult,
11
+ type HumanInputSecretSpec,
6
12
  type SessionEvent,
7
13
  type SessionStatus,
8
14
  type TimelineAnnotation,
9
15
  } from "@opengeni/sdk";
10
16
  import fleetDecisionItem from "./fleet-decision-projection";
17
+ import {
18
+ allowanceExhaustedMessage,
19
+ parseAllowanceExhaustedRefusal,
20
+ } from "@opengeni/sdk/allowance-refusal";
21
+ import {
22
+ parseSessionUsageLimitReachedRefusal,
23
+ SESSION_USAGE_LIMIT_SEGMENT,
24
+ sessionUsageLimitReachedMessage,
25
+ } from "@opengeni/sdk/session-usage-limits";
11
26
  import {
12
27
  CREDIT_EXHAUSTION_MESSAGE,
13
- humanizeFailureReason,
28
+ presentFailure,
14
29
  isCreditExhaustion,
15
30
  tryParseJson,
16
31
  } from "../lib/format";
17
32
  import { mcpToolLeaf, toolMatchesLeaf } from "./tool-display-name";
33
+ import { presentTurnFailureText } from "./compaction-copy";
18
34
  import type {
19
35
  AgentMessageItem,
20
36
  ActivityItem,
@@ -24,7 +40,9 @@ import type {
24
40
  HumanInputAnswerSummary,
25
41
  HumanInputItem,
26
42
  MachineInputBatchItem,
43
+ MachineInputMember,
27
44
  MemoryItem,
45
+ NoticeItem,
28
46
  SandboxItem,
29
47
  StartupPhase,
30
48
  StartupPhaseItem,
@@ -39,6 +57,14 @@ import type {
39
57
 
40
58
  export { toolDisplayName, mcpToolLeaf, toolMatchesLeaf } from "./tool-display-name";
41
59
 
60
+ /** Message identity only: queue placement needs the surrounding turn history. */
61
+ export function isTimelineUserQuestion(event: SessionEvent): boolean {
62
+ return (
63
+ event.type === "user.message" &&
64
+ workerCompletionPayload(asRecord(event.payload).childCompletion) === null
65
+ );
66
+ }
67
+
42
68
  /* ----------------------------------------------------------------------------
43
69
  Timeline projection
44
70
 
@@ -53,11 +79,13 @@ export { toolDisplayName, mcpToolLeaf, toolMatchesLeaf } from "./tool-display-na
53
79
  memoized, unit-tested, and re-run incrementally as new events stream in.
54
80
  -------------------------------------------------------------------------- */
55
81
 
56
- /** Tool leaves on the first-party OpenGeni MCP server that operate on sessions. */
82
+ /** Tool leaves on the first-party Opengeni MCP server that operate on sessions. */
57
83
  const WORKER_SPAWN_TOOL = "session_create";
58
84
  const WORKER_MESSAGE_TOOL = "session_send_message";
59
85
  const WORKER_FAILURE_CODE_MAX_LENGTH = 128;
60
86
  const WORKER_FAILURE_MESSAGE_MAX_UTF8_BYTES = 1_024;
87
+ type PendingWaitOutcome = { id: string; reason: string; occurredAt: string };
88
+ type TrackedAgentResponse = { item: AgentMessageItem; completed: boolean };
61
89
 
62
90
  /**
63
91
  * Tools whose durable side-effect events already own the timeline (MemoryRow).
@@ -127,16 +155,110 @@ function timelineAnnotationToolOutputText(output: unknown): string | null {
127
155
  return text.length > 0 ? text : null;
128
156
  }
129
157
 
130
- export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
158
+ export function buildTimeline(
159
+ events: SessionEvent[],
160
+ options: { partialStart?: boolean } = {},
161
+ ): TimelineItem[] {
131
162
  const items: TimelineItem[] = [];
163
+ const allowanceNotices = new Set<string>();
164
+ const allowanceNotice = (event: SessionEvent, turnId: string | null) => {
165
+ const refusal = parseAllowanceExhaustedRefusal(event.payload);
166
+ if (!refusal) return null;
167
+ const text = allowanceExhaustedMessage(refusal);
168
+ const key = JSON.stringify([
169
+ turnId,
170
+ event.turnAttemptId,
171
+ startupRecoveryRevisionByTurn.get(turnId ?? "") ?? 0,
172
+ refusal.scope,
173
+ refusal.subjectId,
174
+ refusal.resetsAt,
175
+ ]);
176
+ if (!allowanceNotices.has(key)) {
177
+ allowanceNotices.add(key);
178
+ items.push({
179
+ kind: "notice",
180
+ id: `${event.id}-allowance`,
181
+ tone: "failed",
182
+ text,
183
+ // The canonical sentence replaces upstream prose, so a host renderer
184
+ // reading `message` never shows a wrapper's private wording.
185
+ allowance: { ...refusal, message: text },
186
+ occurredAt: event.occurredAt,
187
+ });
188
+ }
189
+ return text;
190
+ };
132
191
  const prescan = prescanTurnAnchors(events);
133
192
  const ordered = orderTimelineEvents(events, prescan);
193
+ // A bounded replay can begin halfway through a message (including inside an
194
+ // interactive fence). Never parse that suffix as a complete Markdown source.
195
+ // Keep the raw events available for pagination; a completed receipt restores
196
+ // the authoritative message without requiring an unbounded history fetch.
197
+ let completeHistoryPrefix = options.partialStart !== true;
198
+ const knownMessagePrefixTurns = new Set<string | null>();
199
+ const incompleteMessageKeys = new Set<string>();
200
+ const pendingWaitOutcomeByTurn = new Map<string | null, PendingWaitOutcome>();
201
+ const latestAgentResponseByTurn = new Map<string | null, TrackedAgentResponse>();
202
+ const identifiedMessages = new Map<string, AgentMessageItem>();
134
203
  const humanInputRequests = humanInputRequestsById(events);
135
204
  const humanInputToolCallIds = new Set(
136
205
  [...humanInputRequests.values()]
137
206
  .map((request) => stringValue(request.toolCallId))
138
207
  .filter(Boolean),
139
208
  );
209
+ // Delegated workers that have not reported back yet, so a recorded agent wait
210
+ // can say how many agents it is waiting for. Only a session's own workers
211
+ // report back, so a message to any other session is never awaited. Best
212
+ // effort: a bounded replay only knows the spawns and results inside its window.
213
+ const workerSessions = new Set<string>();
214
+ const pendingWorkerSessions = new Set<string>();
215
+ let openWaitNotice: NoticeItem | null = null;
216
+ // Tool results update the original tool row in place, and running lifecycle
217
+ // receipts have no row. Carry resume evidence on the attention landmarks so
218
+ // grouping cannot mistake historical approval for a live wait.
219
+ const presentationWaits = new Map<string | null, Array<NoticeItem | SessionStatusItem>>();
220
+ // Admission blocks hold the whole session, not one turn; any later status
221
+ // (a recheck, a new message, a pause) ends them.
222
+ let openAdmissionBlocks: SessionStatusItem[] = [];
223
+ const presentationFailures = new Map<string | null, SessionStatusItem[]>();
224
+ let presentationTurnId: string | null = null;
225
+ const rememberPresentationWait = (
226
+ item: NoticeItem | SessionStatusItem,
227
+ turnId: string | null,
228
+ ) => {
229
+ const key = turnId ?? presentationTurnId;
230
+ const waits = presentationWaits.get(key) ?? [];
231
+ waits.push(item);
232
+ presentationWaits.set(key, waits);
233
+ items.push(item);
234
+ };
235
+ const resolvePresentationWait = (event: SessionEvent, turnId: string | null) => {
236
+ if (event.duplicateOfEventId || (event.turnAssociation && event.turnAssociation !== "current"))
237
+ return;
238
+ const key = turnId ?? presentationTurnId;
239
+ for (const item of presentationWaits.get(key) ?? []) item.resolvedAt = event.occurredAt;
240
+ presentationWaits.delete(key);
241
+ };
242
+ // Live model-capacity waits. Later lifecycle evidence that the turn is no
243
+ // longer blocked (resume, a non-waiting status, its settlement) resolves
244
+ // them, so a recovered wait never lingers as a warning.
245
+ let openCapacityWaits: NoticeItem[] = [];
246
+ const resolveCapacityWaits = (event: SessionEvent, payload: Record<string, unknown>) => {
247
+ if (openCapacityWaits.length === 0 || !endsCapacityWait(event, payload)) return;
248
+ const eventTurnId = event.turnId ?? null;
249
+ openCapacityWaits = openCapacityWaits.filter((item) => {
250
+ const waitTurnId = item.capacityWait?.turnId ?? null;
251
+ // Status changes are session-wide; a turn settlement ends only its own wait
252
+ // (a queued follow-up being cancelled does not unblock the waiting turn).
253
+ const ends =
254
+ event.type === "session.status.changed" ||
255
+ eventTurnId === null ||
256
+ waitTurnId === null ||
257
+ eventTurnId === waitTurnId;
258
+ if (ends) item.resolvedAt = event.occurredAt;
259
+ return !ends;
260
+ });
261
+ };
140
262
  const queuedAtByTurn = new Map<string, string>();
141
263
  const startupRecoveryRevisionByTurn = new Map<string, number>();
142
264
  const startupPhases = new Map<string, readonly [StartupPhaseItem, string | null, number]>();
@@ -153,6 +275,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
153
275
  mode = 0,
154
276
  explicitStartedAt?: string,
155
277
  explicitId?: string,
278
+ blockedReason?: StartupPhaseItem["blockedReason"],
156
279
  ): void => {
157
280
  const key = `${startupTurnId}:${phase}`;
158
281
  const priorState = startupPhases.get(key);
@@ -179,6 +302,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
179
302
  ? elapsedDurationMs(open.startedAt, startupEvent.occurredAt)
180
303
  : Math.max(0, duration);
181
304
  open.outcome = outcome ?? open.outcome;
305
+ open.blockedReason = blockedReason;
182
306
  return;
183
307
  }
184
308
  const running = status === "running";
@@ -197,6 +321,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
197
321
  completedAt: running ? null : startupEvent.occurredAt,
198
322
  durationMs,
199
323
  outcome,
324
+ blockedReason,
200
325
  occurredAt: startedAt,
201
326
  } satisfies Partial<StartupPhaseItem>);
202
327
  startupPhases.set(key, [prior, startupAttemptId, startupRecoveryRevision]);
@@ -212,6 +337,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
212
337
  completedAt: running ? null : startupEvent.occurredAt,
213
338
  durationMs,
214
339
  outcome,
340
+ blockedReason,
215
341
  occurredAt: startedAt,
216
342
  };
217
343
  items.push(item);
@@ -220,6 +346,31 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
220
346
 
221
347
  const last = (): TimelineItem | undefined => items[items.length - 1];
222
348
 
349
+ const rememberAgentResponse = (
350
+ turnId: string | null,
351
+ item: AgentMessageItem,
352
+ completed: boolean,
353
+ ): void => {
354
+ latestAgentResponseByTurn.set(turnId, { item, completed });
355
+ };
356
+
357
+ const takePendingWaitOutcome = (turnId: string | null): PendingWaitOutcome | undefined => {
358
+ const outcome = pendingWaitOutcomeByTurn.get(turnId);
359
+ pendingWaitOutcomeByTurn.delete(turnId);
360
+ return outcome;
361
+ };
362
+
363
+ const takeAgentResponse = (turnId: string | null): TrackedAgentResponse | undefined => {
364
+ const response = latestAgentResponseByTurn.get(turnId);
365
+ latestAgentResponseByTurn.delete(turnId);
366
+ return response;
367
+ };
368
+
369
+ const clearUnscopedTerminalTracking = (): void => {
370
+ latestAgentResponseByTurn.delete(null);
371
+ pendingWaitOutcomeByTurn.delete(null);
372
+ };
373
+
223
374
  /** A new item of a different kind ends whatever was streaming at the tail. */
224
375
  const closeStreamingTail = (): void => {
225
376
  const open = last();
@@ -265,6 +416,40 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
265
416
  for (const event of ordered) {
266
417
  const payload = asRecord(event.payload);
267
418
  const turnId = event.turnId ?? null;
419
+ resolveCapacityWaits(event, payload);
420
+ if (
421
+ turnId &&
422
+ (event.type === "turn.started" ||
423
+ (presentationTurnId === null && isAgentActivityEvent(event.type)))
424
+ ) {
425
+ presentationTurnId = turnId;
426
+ }
427
+ if (
428
+ openWaitNotice &&
429
+ (event.type === "user.message" ||
430
+ event.type === "system.update.delivered" ||
431
+ event.type === "turn.started" ||
432
+ event.type === "session.control.paused" ||
433
+ (event.type === "session.status.changed" &&
434
+ (payload.status === "cancelled" || payload.status === "failed")))
435
+ ) {
436
+ // New input, a pause, or the end of the session ends a recorded wait; the
437
+ // notice keeps its wait span.
438
+ openWaitNotice.waitEndedAt = event.occurredAt;
439
+ openWaitNotice = null;
440
+ }
441
+ if (event.type === "session.created") {
442
+ completeHistoryPrefix = true;
443
+ } else if (
444
+ event.type === "turn.started" ||
445
+ (event.type === "user.message" &&
446
+ payload.delivery !== "steer" &&
447
+ payload.routing !== "accepted_for_steering")
448
+ ) {
449
+ // Accepted Steer is visible before its replacement turn starts. It says
450
+ // nothing about the prefix of a still-streaming message from the old turn.
451
+ knownMessagePrefixTurns.add(turnId);
452
+ }
268
453
  startupEvent = event;
269
454
  startupTurnId = turnId;
270
455
  startupAttemptId =
@@ -285,11 +470,39 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
285
470
  : null;
286
471
  if (setupPhase && setupStatus) {
287
472
  closeStreamingTail();
473
+ let blockedReason: StartupPhaseItem["blockedReason"];
474
+ if (setupPhase === "model_preparation" && setupStatus === "failed") {
475
+ blockedReason = startupLifecycleWaitReason(payload);
476
+ // Older preparation events omit the typed lifecycle fields. The
477
+ // sandbox span can supply them only inside the same turn attempt.
478
+ const sandbox = startupPhases.get(`${startupTurnId}:sandbox`);
479
+ const sameAttempt =
480
+ sandbox?.[1] || startupAttemptId
481
+ ? Boolean(startupAttemptId && sandbox?.[1] === startupAttemptId)
482
+ : sandbox?.[2] === startupRecoveryRevision;
483
+ if (
484
+ !blockedReason &&
485
+ payload.expectedTransition === undefined &&
486
+ payload.failureCode === undefined &&
487
+ payload.failureStage === undefined &&
488
+ payload.failureCategory === undefined &&
489
+ payload.error === undefined &&
490
+ startupTurnId &&
491
+ sameAttempt &&
492
+ sandbox?.[0].status === "cancelled"
493
+ ) {
494
+ blockedReason = sandbox[0].blockedReason;
495
+ }
496
+ }
288
497
  settleStartupPhase(
289
498
  setupPhase,
290
- setupStatus,
499
+ blockedReason ? "cancelled" : setupStatus,
291
500
  numberOrNull(payload.durationMs),
292
501
  event.type === "rig.setup.skipped" ? "skipped" : null,
502
+ 0,
503
+ undefined,
504
+ undefined,
505
+ blockedReason,
293
506
  );
294
507
  continue;
295
508
  }
@@ -298,9 +511,12 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
298
511
  case "user.message": {
299
512
  // A steering message must not mark in-flight tools complete; it only
300
513
  // ends whatever text was streaming. Turn lifecycle events finalize.
514
+ clearUnscopedTerminalTracking();
301
515
  closeStreamingTail();
302
516
  const childCompletion = workerCompletionPayload(payload.childCompletion);
303
517
  if (childCompletion) {
518
+ workerSessions.add(childCompletion.childSessionId);
519
+ pendingWorkerSessions.delete(childCompletion.childSessionId);
304
520
  items.push({
305
521
  kind: "worker-completion",
306
522
  id: event.id,
@@ -317,6 +533,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
317
533
  break;
318
534
  }
319
535
  const voiceMessage = realtimeVoiceMessage(payload);
536
+ const sender = messageSender(payload.initiator);
320
537
  items.push({
321
538
  kind: "user-message",
322
539
  id: event.id,
@@ -336,6 +553,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
336
553
  ...(voiceMessage ? { presentation: voiceMessage.presentation } : {}),
337
554
  resources: resourceRefs(payload.resources),
338
555
  tools: toolRefs(payload.tools),
556
+ ...(sender ? { sender } : {}),
339
557
  occurredAt: event.occurredAt,
340
558
  });
341
559
  break;
@@ -353,13 +571,50 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
353
571
  break;
354
572
  }
355
573
 
574
+ case "system.update.settled": {
575
+ // An operator retirement is a passive historical receipt, not a new
576
+ // prompt or model-input delivery. Reuse the existing agent-result row
577
+ // while ordinary turn-input settlement metadata remains quiet.
578
+ if (payload.notificationOnly !== true || payload.reason !== "operator_retirement") break;
579
+ const members = machineInputMembers(payload.members).filter(
580
+ (member) =>
581
+ member.kind === "child_terminal_result" && member.classification === "failure",
582
+ );
583
+ if (members.length === 0) break;
584
+ for (const member of members) {
585
+ workerSessions.add(member.sourceId);
586
+ pendingWorkerSessions.delete(member.sourceId);
587
+ }
588
+ items.push({
589
+ kind: "machine-input-batch",
590
+ id: event.id,
591
+ turnId: null,
592
+ members,
593
+ occurredAt: event.occurredAt,
594
+ });
595
+ break;
596
+ }
597
+
356
598
  case "system.update.delivered": {
357
599
  const inputs = machineInputMembers(payload.members);
600
+ for (const input of inputs) {
601
+ if (input.kind === "child_terminal_result") {
602
+ workerSessions.add(input.sourceId);
603
+ pendingWorkerSessions.delete(input.sourceId);
604
+ }
605
+ }
358
606
  if (inputs.length === 0) break;
359
- // Goal continuations already land as `goal.continuation` GoalRows.
360
- // A solo continuation batch would duplicate that landmark + dump the
361
- // model-facing prompt — skip chrome for that case only.
362
- if (inputs.every((member) => member.kind === "goal_continuation")) {
607
+ // Background command receipts are agent-facing inputs, not chat messages.
608
+ // The durable event remains available to the agent and audit history.
609
+ const visibleInputs = inputs.filter(
610
+ (member) => member.kind !== "background_command_result",
611
+ );
612
+ // Goal continuations already have GoalRows. A batch with no other
613
+ // visible update must not interrupt an in-flight streaming message.
614
+ if (
615
+ visibleInputs.length === 0 ||
616
+ visibleInputs.every((member) => member.kind === "goal_continuation")
617
+ ) {
363
618
  break;
364
619
  }
365
620
  closeStreamingTail();
@@ -367,7 +622,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
367
622
  kind: "machine-input-batch",
368
623
  id: event.id,
369
624
  turnId,
370
- members: inputs,
625
+ members: visibleInputs,
371
626
  occurredAt: event.occurredAt,
372
627
  });
373
628
  break;
@@ -378,31 +633,85 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
378
633
  if (!text) {
379
634
  break;
380
635
  }
636
+ const messageId = stringValue(payload.messageId);
637
+ const messageKey = messageId ? JSON.stringify([turnId, messageId]) : null;
638
+ // Responses providers declare the phase when they announce the item, so
639
+ // a streaming progress note is known as commentary before it completes.
640
+ const phase = assistantMessagePhase(payload.phase);
641
+ if (
642
+ (!completeHistoryPrefix && !knownMessagePrefixTurns.has(turnId)) ||
643
+ (messageKey && incompleteMessageKeys.has(messageKey))
644
+ ) {
645
+ if (messageKey) incompleteMessageKeys.add(messageKey);
646
+ break;
647
+ }
648
+ const identified = messageKey ? identifiedMessages.get(messageKey) : undefined;
649
+ if (identified) {
650
+ if (identified.annotationSource?.eventType !== "agent.message.completed") {
651
+ identified.text += text;
652
+ if (phase && !identified.phase) identified.phase = phase;
653
+ rememberAgentResponse(turnId, identified, false);
654
+ }
655
+ break;
656
+ }
381
657
  const open = last();
382
- if (open?.kind === "agent-message" && open.streaming && open.turnId === turnId) {
658
+ if (!messageKey && open?.kind === "tool-call" && open.status === "running") {
659
+ const previous = latestAgentResponseByTurn.get(turnId);
660
+ const previousIndex = previous ? items.indexOf(previous.item) : -1;
661
+ if (
662
+ previous &&
663
+ !previous.completed &&
664
+ previousIndex >= 0 &&
665
+ items
666
+ .slice(previousIndex + 1)
667
+ .every(
668
+ (item) =>
669
+ item.kind === "tool-call" && item.turnId === turnId && item.status === "running",
670
+ )
671
+ ) {
672
+ // Legacy deltas have no provider message identity. Tool creation
673
+ // alone is not a text boundary; completed output still is.
674
+ previous.item.text += text;
675
+ break;
676
+ }
677
+ }
678
+ if (
679
+ !messageKey &&
680
+ open?.kind === "agent-message" &&
681
+ open.streaming &&
682
+ open.turnId === turnId
683
+ ) {
383
684
  open.text += text;
685
+ rememberAgentResponse(turnId, open, false);
384
686
  break;
385
687
  }
386
688
  closeStreamingTail();
387
- items.push({
689
+ const item: AgentMessageItem = {
388
690
  kind: "agent-message",
389
691
  id: event.id,
390
692
  turnId,
391
693
  text,
694
+ ...(phase ? { phase } : {}),
392
695
  streaming: true,
393
696
  occurredAt: event.occurredAt,
394
- });
697
+ };
698
+ items.push(item);
699
+ if (messageKey) identifiedMessages.set(messageKey, item);
700
+ rememberAgentResponse(turnId, item, false);
395
701
  break;
396
702
  }
397
703
 
398
704
  case "agent.message.completed": {
705
+ knownMessagePrefixTurns.add(turnId);
399
706
  const text = stringValue(payload.text);
707
+ const messageId = stringValue(payload.messageId);
708
+ const messageKey = messageId ? JSON.stringify([turnId, messageId]) : null;
400
709
  const phase = assistantMessagePhase(payload.phase);
401
710
  // Reconcile the most recent same-turn agent message — even when
402
711
  // activity (tool calls, reasoning) landed after its deltas — so the
403
712
  // completed text never duplicates the streamed one.
404
- let openIndex = -1;
405
- for (let index = items.length - 1; index >= 0; index -= 1) {
713
+ let openIndex = messageKey ? items.indexOf(identifiedMessages.get(messageKey)!) : -1;
714
+ for (let index = messageKey ? -1 : items.length - 1; index >= 0; index -= 1) {
406
715
  const candidate = items[index];
407
716
  if (candidate?.kind === "agent-message" && candidate.turnId === turnId) {
408
717
  openIndex = index;
@@ -414,18 +723,26 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
414
723
  candidate?.kind === "agent-message" ? candidate : undefined;
415
724
  if (
416
725
  open &&
417
- (open.streaming || !open.text || text === open.text || text.startsWith(open.text))
726
+ (messageKey ||
727
+ open.streaming ||
728
+ !open.text ||
729
+ text === open.text ||
730
+ text.startsWith(open.text))
418
731
  ) {
419
- // The completed text is authoritative when it extends what streamed.
420
- if (!open.text || (text && text.startsWith(open.text))) {
732
+ // Identity makes the final text authoritative even if it corrects the
733
+ // draft; legacy receipts still require an extension match.
734
+ if (!open.text || (text && (messageKey || text.startsWith(open.text)))) {
421
735
  open.text = text || open.text;
422
736
  }
423
- open.streaming = false;
424
737
  // A phase-less settlement mirror (the worker's final-output receipt)
425
738
  // must not erase the provider-declared phase captured by the SDK item.
426
739
  if (phase) open.phase = phase;
427
- // Completion time is what the footer shows ("finished at"); keep the
428
- // first-delta stamp only until this event arrives.
740
+ // Completion time is what the footer shows ("finished at"); the
741
+ // first-delta stamp stays as the stream's start.
742
+ if (open.startedAt === undefined && open.occurredAt !== event.occurredAt) {
743
+ open.startedAt = open.occurredAt;
744
+ }
745
+ open.streaming = false;
429
746
  open.occurredAt = event.occurredAt;
430
747
  open.annotationSource =
431
748
  text && open.text === text
@@ -447,10 +764,11 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
447
764
  items.splice(openIndex, 1);
448
765
  items.push(open);
449
766
  }
767
+ rememberAgentResponse(turnId, open, true);
450
768
  break;
451
769
  }
452
770
  if (text) {
453
- items.push({
771
+ const item: AgentMessageItem = {
454
772
  kind: "agent-message",
455
773
  id: event.id,
456
774
  turnId,
@@ -466,7 +784,10 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
466
784
  turnId,
467
785
  text,
468
786
  },
469
- });
787
+ };
788
+ items.push(item);
789
+ if (messageKey) identifiedMessages.set(messageKey, item);
790
+ rememberAgentResponse(turnId, item, true);
470
791
  }
471
792
  break;
472
793
  }
@@ -478,7 +799,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
478
799
  }
479
800
  const open = last();
480
801
  if (open?.kind === "reasoning" && open.streaming && open.turnId === turnId) {
481
- open.text += text;
802
+ open.text += reasoningDeltaJoiner(open.text, text) + text;
482
803
  break;
483
804
  }
484
805
  closeStreamingTail();
@@ -495,6 +816,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
495
816
 
496
817
  case "agent.toolCall.created": {
497
818
  const name = typeof payload.name === "string" ? payload.name : "tool";
819
+ const display = parseToolDisplayMetadata(payload.display);
498
820
  const callId = typeof payload.id === "string" ? payload.id : null;
499
821
  const args = payload.arguments ?? null;
500
822
  closeStreamingTail();
@@ -508,13 +830,16 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
508
830
  toolMatchesLeaf(name, WORKER_SPAWN_TOOL) ||
509
831
  toolMatchesLeaf(name, WORKER_MESSAGE_TOOL)
510
832
  ) {
833
+ const spawn = toolMatchesLeaf(name, WORKER_SPAWN_TOOL);
834
+ const title = spawn ? workerTitle(args) : null;
511
835
  items.push({
512
836
  kind: "worker",
513
837
  id: event.id,
514
838
  turnId,
515
839
  callId,
516
- action: toolMatchesLeaf(name, WORKER_SPAWN_TOOL) ? "spawn" : "message",
840
+ action: spawn ? "spawn" : "message",
517
841
  prompt: workerPrompt(args),
842
+ ...(title ? { title } : {}),
518
843
  workerSessionId: extractSessionRef(args),
519
844
  failure: null,
520
845
  status: "running",
@@ -536,6 +861,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
536
861
  (item): item is ToolCallItem => item.kind === "tool-call" && item.callId === callId,
537
862
  );
538
863
  if (existing) {
864
+ if (display) existing.display = display;
539
865
  if (args != null) {
540
866
  existing.arguments = args;
541
867
  }
@@ -552,6 +878,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
552
878
  turnId,
553
879
  callId,
554
880
  name,
881
+ ...(display ? { display } : {}),
555
882
  arguments: args,
556
883
  output: undefined,
557
884
  truncation: null,
@@ -570,12 +897,20 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
570
897
  if (!target) {
571
898
  break;
572
899
  }
900
+ resolvePresentationWait(event, turnId ?? target.turnId);
573
901
  if (target.kind === "worker") {
574
902
  // A worker spawn/message that returns an error flag (or an MCP
575
903
  // isError result) settles to "failed" too, so WorkerRow surfaces it.
576
904
  target.status = isErrorOutput(payload) ? "failed" : "complete";
577
905
  target.workerSessionId = target.workerSessionId ?? extractSessionRef(payload.output);
578
906
  target.failure = target.status === "failed" ? workerFailure(payload.output) : null;
907
+ // A spawned or re-messaged worker owes this session a result.
908
+ if (target.status === "complete" && target.workerSessionId) {
909
+ if (target.action === "spawn") workerSessions.add(target.workerSessionId);
910
+ if (workerSessions.has(target.workerSessionId)) {
911
+ pendingWorkerSessions.add(target.workerSessionId);
912
+ }
913
+ }
579
914
  break;
580
915
  }
581
916
  // An output carrying an explicit error flag (or an MCP isError result)
@@ -616,6 +951,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
616
951
  }
617
952
 
618
953
  case "turn.started": {
954
+ if (turnId) clearUnscopedTerminalTracking();
619
955
  if (!turnId) break;
620
956
  const queuedAt = queuedAtByTurn.get(turnId);
621
957
  if (!queuedAt) break;
@@ -631,7 +967,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
631
967
  null,
632
968
  0,
633
969
  queuedAt,
634
- `${event.id}-queue`,
970
+ `${turnId}-queue`,
635
971
  );
636
972
  break;
637
973
  }
@@ -656,7 +992,21 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
656
992
  const startupPhase = startupPhaseForSandboxOperation(name);
657
993
  if (startupPhase) {
658
994
  closeStreamingTail();
659
- settleStartupPhase(startupPhase, status, numberOrNull(payload.durationMs), origin);
995
+ settleStartupPhase(
996
+ startupPhase,
997
+ status,
998
+ numberOrNull(payload.durationMs),
999
+ origin,
1000
+ 0,
1001
+ undefined,
1002
+ undefined,
1003
+ status === "cancelled"
1004
+ ? (startupLifecycleWaitReason(payload) ??
1005
+ (payload.failureCode === "rotation_in_progress"
1006
+ ? "rotation_in_progress"
1007
+ : undefined))
1008
+ : undefined,
1009
+ );
660
1010
  break;
661
1011
  }
662
1012
  // Routine per-turn platform plumbing that runs before EVERY turn to
@@ -669,8 +1019,13 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
669
1019
  // Rendering either every turn reads as churn. Only FAILURES surface, and
670
1020
  // they surface loudly — the failed event below creates its own item even
671
1021
  // without a started row.
1022
+ // - optional-repository-access: the worker's per-turn report of an
1023
+ // automatically attached repository that sat the turn out, reported
1024
+ // like a skipped optional clone (event payload + log, no row).
672
1025
  if (
673
- (name === "repository-clone" || name === "file-resource-download") &&
1026
+ (name === "repository-clone" ||
1027
+ name === "file-resource-download" ||
1028
+ name === "optional-repository-access") &&
674
1029
  status !== "failed"
675
1030
  ) {
676
1031
  break;
@@ -758,6 +1113,12 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
758
1113
  if (!isSessionStatus(status)) {
759
1114
  break;
760
1115
  }
1116
+ const blocked = status === "requires_action" && payload.code === "admission_blocked";
1117
+ if (!blocked && openAdmissionBlocks.length > 0) {
1118
+ for (const open of openAdmissionBlocks) open.resolvedAt = event.occurredAt;
1119
+ openAdmissionBlocks = [];
1120
+ }
1121
+ if (status === "running") resolvePresentationWait(event, turnId);
761
1122
  // Only attention-worthy statuses earn a timeline divider. queued /
762
1123
  // running / idle are machinery telemetry: the header pill carries the
763
1124
  // live status, the shimmer says "running", and the turn chip's duration
@@ -769,27 +1130,93 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
769
1130
  const previous = [...items]
770
1131
  .reverse()
771
1132
  .find((item): item is SessionStatusItem => item.kind === "session-status");
772
- if (previous?.status === status) {
1133
+ if (
1134
+ previous?.status === status &&
1135
+ !previous.resolvedAt &&
1136
+ Boolean(previous.blocked) === blocked
1137
+ ) {
773
1138
  break;
774
1139
  }
775
- items.push({
1140
+ const item: SessionStatusItem = {
776
1141
  kind: "session-status",
777
1142
  id: event.id,
778
1143
  status,
779
1144
  occurredAt: event.occurredAt,
780
- });
1145
+ ...(blocked ? { blocked: true as const } : {}),
1146
+ };
1147
+ if (blocked) {
1148
+ openAdmissionBlocks.push(item);
1149
+ items.push(item);
1150
+ } else if (status === "requires_action") rememberPresentationWait(item, turnId);
1151
+ else {
1152
+ items.push(item);
1153
+ if (status === "failed") {
1154
+ const key = turnId ?? presentationTurnId;
1155
+ const failures = presentationFailures.get(key) ?? [];
1156
+ failures.push(item);
1157
+ presentationFailures.set(key, failures);
1158
+ }
1159
+ }
781
1160
  break;
782
1161
  }
783
1162
 
784
- case "session.requiresAction": {
785
- finalizeOpen(turnId, "complete", event.occurredAt);
1163
+ case "subscription.account.switched": {
1164
+ // One neutral receipt, not its provider-specific compatibility alias.
1165
+ // Never expose connection ids, personal labels, or provider error text.
1166
+ const text =
1167
+ payload.reason === "return_to_preferred"
1168
+ ? "Returned to your preferred model."
1169
+ : payload.reason === "failover_cross_provider"
1170
+ ? "Switched to a fallback model."
1171
+ : "Switched to another subscription account.";
786
1172
  items.push({
1173
+ kind: "notice",
1174
+ id: event.id,
1175
+ tone: "input",
1176
+ text,
1177
+ occurredAt: event.occurredAt,
1178
+ });
1179
+ break;
1180
+ }
1181
+
1182
+ case "codex.capacity.waiting":
1183
+ case "turn.capacity_waiting": {
1184
+ const capacityWait = capacityWaitPresentation(event.type, payload, turnId);
1185
+ const item: NoticeItem = {
787
1186
  kind: "notice",
788
1187
  id: event.id,
789
1188
  tone: "waiting",
790
- text: "Approval needed — the turn is paused until someone decides.",
1189
+ text: capacityWait.text,
1190
+ capacityWait: {
1191
+ turnId: capacityWait.turnId,
1192
+ label: capacityWait.label,
1193
+ detail: capacityWait.detail,
1194
+ },
791
1195
  occurredAt: event.occurredAt,
1196
+ };
1197
+ // A re-armed wait continues the same blocked span; one live wait per turn.
1198
+ openCapacityWaits = openCapacityWaits.filter((open) => {
1199
+ if ((open.capacityWait?.turnId ?? null) !== turnId) return true;
1200
+ open.resolvedAt = event.occurredAt;
1201
+ return false;
792
1202
  });
1203
+ items.push(item);
1204
+ openCapacityWaits.push(item);
1205
+ break;
1206
+ }
1207
+
1208
+ case "session.requiresAction": {
1209
+ finalizeOpen(turnId, "complete", event.occurredAt);
1210
+ rememberPresentationWait(
1211
+ {
1212
+ kind: "notice",
1213
+ id: event.id,
1214
+ tone: "waiting",
1215
+ text: "Approval needed — the turn is paused until someone decides.",
1216
+ occurredAt: event.occurredAt,
1217
+ },
1218
+ turnId,
1219
+ );
793
1220
  break;
794
1221
  }
795
1222
 
@@ -804,6 +1231,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
804
1231
  estimatedTokensBefore: numberOrNull(payload.estimatedTokensBefore),
805
1232
  estimatedTokensAfter: null,
806
1233
  skipReason: null,
1234
+ providerRejection: null,
807
1235
  implementation:
808
1236
  typeof payload.implementation === "string" ? payload.implementation : null,
809
1237
  occurredAt: event.occurredAt,
@@ -821,6 +1249,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
821
1249
  estimatedTokensBefore: numberOrNull(payload.estimatedTokensBefore),
822
1250
  estimatedTokensAfter: numberOrNull(payload.estimatedTokensAfter),
823
1251
  skipReason: null,
1252
+ providerRejection: null,
824
1253
  implementation:
825
1254
  typeof payload.implementation === "string" ? payload.implementation : null,
826
1255
  occurredAt: event.occurredAt,
@@ -838,6 +1267,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
838
1267
  estimatedTokensBefore: numberOrNull(payload.estimatedTokensBefore),
839
1268
  estimatedTokensAfter: null,
840
1269
  skipReason: typeof payload.reason === "string" ? payload.reason : null,
1270
+ providerRejection: compactionProviderRejection(payload),
841
1271
  implementation:
842
1272
  typeof payload.implementation === "string" ? payload.implementation : null,
843
1273
  occurredAt: event.occurredAt,
@@ -854,6 +1284,23 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
854
1284
  turnId,
855
1285
  (startupRecoveryRevisionByTurn.get(turnId) ?? 0) + 1,
856
1286
  );
1287
+ if (
1288
+ !event.duplicateOfEventId &&
1289
+ (!event.turnAssociation || event.turnAssociation === "current")
1290
+ ) {
1291
+ // Human Retry reopens the existing logical turn, unlike a new
1292
+ // prompt. Keep failure evidence, but do not let its old settlement
1293
+ // close the recovered turn's work surface.
1294
+ for (const item of items) {
1295
+ if (item.kind === "turn-end" && item.turnId === turnId && item.outcome === "failed") {
1296
+ item.resumedAt ??= event.occurredAt;
1297
+ }
1298
+ }
1299
+ for (const item of presentationFailures.get(turnId) ?? []) {
1300
+ item.resolvedAt = event.occurredAt;
1301
+ }
1302
+ presentationFailures.delete(turnId);
1303
+ }
857
1304
  }
858
1305
  break;
859
1306
  }
@@ -868,6 +1315,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
868
1315
  case "tool.auth_needed":
869
1316
  case "credential.auth_needed": {
870
1317
  const capability = capabilityAuthorizationRequest(payload.capability);
1318
+ const setupRequest = customMcpSetupRequest(payload.setupRequest);
871
1319
  if (
872
1320
  event.type === "tool.auth_needed" &&
873
1321
  !stringValue(payload.toolName) &&
@@ -890,25 +1338,44 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
890
1338
  id: event.id,
891
1339
  turnId,
892
1340
  serverId: typeof payload.serverId === "string" ? payload.serverId : null,
893
- source: capability
894
- ? "capability"
895
- : event.type === "tool.auth_needed"
896
- ? "tool"
897
- : "credential",
1341
+ canonicalServerId:
1342
+ typeof payload.canonicalServerId === "string" ? payload.canonicalServerId : null,
1343
+ connectionSubjectScope:
1344
+ payload.connectionSubjectScope === "subject" ||
1345
+ payload.connectionSubjectScope === "workspace"
1346
+ ? payload.connectionSubjectScope
1347
+ : null,
1348
+ source:
1349
+ capability || setupRequest
1350
+ ? "capability"
1351
+ : event.type === "tool.auth_needed"
1352
+ ? "tool"
1353
+ : "credential",
898
1354
  providerDomain: stringValue(payload.providerDomain),
899
1355
  connectionId: typeof payload.connectionId === "string" ? payload.connectionId : null,
900
- reason: authNeededReason(payload.reason),
1356
+ authoritySource: payload.authoritySource === "host" ? "host" : null,
1357
+ reason: authNeededReason(
1358
+ payload.authoritySource === "host" && typeof payload.authorizationUrl === "string"
1359
+ ? (payload.hostReason ?? payload.reason)
1360
+ : payload.reason,
1361
+ ),
901
1362
  scopes: stringList(payload.scopes),
902
1363
  resource: typeof payload.resource === "string" ? payload.resource : null,
903
1364
  toolName: typeof payload.toolName === "string" ? payload.toolName : null,
904
1365
  authorizationUrl:
905
1366
  typeof payload.authorizationUrl === "string" ? payload.authorizationUrl : null,
906
1367
  capability,
1368
+ setupRequest,
907
1369
  occurredAt: event.occurredAt,
908
1370
  });
909
1371
  break;
910
1372
  }
911
1373
 
1374
+ case "usage.exhausted": {
1375
+ allowanceNotice(event, turnId);
1376
+ break;
1377
+ }
1378
+
912
1379
  case "turn.completed": {
913
1380
  // A standalone manual compaction uses the turn ledger for fencing and
914
1381
  // recovery, but it is maintenance rather than a conversational turn.
@@ -917,16 +1384,40 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
917
1384
  // extra agent response.
918
1385
  if (payload.maintenance === "context_compaction") {
919
1386
  finalizeOpen(turnId, "complete", event.occurredAt);
1387
+ takeAgentResponse(turnId);
1388
+ takePendingWaitOutcome(turnId);
920
1389
  break;
921
1390
  }
922
1391
  // Credit exhaustion arrives as a NOMINALLY completed turn (`detail:
923
- // "insufficient OpenGeni credits"`, `segmentLimit: "budget_exhausted"`)
1392
+ // "insufficient Opengeni credits"`, `segmentLimit: "budget_exhausted"`)
924
1393
  // — the engine ended the segment early, it did not finish the work.
925
1394
  // Rendering it as a clean "complete" turn is a lie that leaves the
926
1395
  // session looking healthy while every future turn silently dies, so it
927
1396
  // projects exactly like a failed turn plus an explicit notice.
1397
+ const allowance = parseAllowanceExhaustedRefusal(payload);
1398
+ // The task's own usage limit refused the next model request. That is a
1399
+ // person's setting doing its job, not a failure or a billing problem:
1400
+ // the turn settles complete and one calm row says what stopped it.
1401
+ if (payload.segmentLimit === SESSION_USAGE_LIMIT_SEGMENT) {
1402
+ finalizeOpen(turnId, "complete", event.occurredAt);
1403
+ takeAgentResponse(turnId);
1404
+ takePendingWaitOutcome(turnId);
1405
+ items.push(turnEndItem(event, "complete", null));
1406
+ items.push(sessionUsageLimitNotice(event, payload));
1407
+ break;
1408
+ }
1409
+ if (allowance) {
1410
+ finalizeOpen(turnId, "complete", event.occurredAt);
1411
+ takeAgentResponse(turnId);
1412
+ takePendingWaitOutcome(turnId);
1413
+ items.push(turnEndItem(event, "failed", ALLOWANCE_TURN_END_TEXT));
1414
+ allowanceNotice(event, turnId);
1415
+ break;
1416
+ }
928
1417
  if (isCreditExhaustionPayload(payload)) {
929
1418
  finalizeOpen(turnId, "complete", event.occurredAt);
1419
+ takeAgentResponse(turnId);
1420
+ takePendingWaitOutcome(turnId);
930
1421
  items.push(turnEndItem(event, "failed", CREDIT_EXHAUSTION_MESSAGE));
931
1422
  items.push({
932
1423
  kind: "notice",
@@ -937,44 +1428,166 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
937
1428
  });
938
1429
  break;
939
1430
  }
1431
+ const latestAgentResponse = takeAgentResponse(turnId);
1432
+ const finalOutput = stringValue(payload.output);
1433
+ const visibleFinalOutput = stripOpaqueCitationTokens(finalOutput).trim();
1434
+ const visibleTrackedResponse = stripOpaqueCitationTokens(
1435
+ latestAgentResponse?.item.text ?? "",
1436
+ ).trim();
1437
+ const finalOutputMirrorsCommentary =
1438
+ latestAgentResponse?.item.phase === "commentary" &&
1439
+ visibleTrackedResponse === visibleFinalOutput;
1440
+ const hasAuthoritativeFinalOutput =
1441
+ Boolean(visibleFinalOutput) && !finalOutputMirrorsCommentary;
1442
+ const pendingWaitOutcome = takePendingWaitOutcome(turnId);
1443
+ if (hasAuthoritativeFinalOutput) {
1444
+ // The final message normally lands as its own completion before
1445
+ // `turn.completed` (older workers commit a copy with it), but legacy or
1446
+ // partially compacted ledgers may retain only the terminal output
1447
+ // receipt. Keep that authoritative response visible instead of
1448
+ // leaving it trapped in raw audit data.
1449
+ if (
1450
+ latestAgentResponse &&
1451
+ !latestAgentResponse.completed &&
1452
+ finalOutput.startsWith(latestAgentResponse.item.text)
1453
+ ) {
1454
+ latestAgentResponse.item.text = finalOutput;
1455
+ latestAgentResponse.item.streaming = false;
1456
+ if (
1457
+ latestAgentResponse.item.startedAt === undefined &&
1458
+ latestAgentResponse.item.occurredAt !== event.occurredAt
1459
+ ) {
1460
+ latestAgentResponse.item.startedAt = latestAgentResponse.item.occurredAt;
1461
+ }
1462
+ latestAgentResponse.item.occurredAt = event.occurredAt;
1463
+ const responseIndex = items.indexOf(latestAgentResponse.item);
1464
+ if (responseIndex >= 0 && responseIndex < items.length - 1) {
1465
+ items.splice(responseIndex, 1);
1466
+ items.push(latestAgentResponse.item);
1467
+ }
1468
+ } else if (visibleTrackedResponse !== visibleFinalOutput) {
1469
+ items.push({
1470
+ kind: "agent-message",
1471
+ id: `${event.id}-output-message`,
1472
+ turnId,
1473
+ text: finalOutput,
1474
+ streaming: false,
1475
+ occurredAt: event.occurredAt,
1476
+ });
1477
+ }
1478
+ }
940
1479
  finalizeOpen(turnId, "complete", event.occurredAt);
941
- items.push(turnEndItem(event, "complete", null));
1480
+ const completedTurn = turnEndItem(event, "complete", null);
1481
+ items.push(completedTurn);
1482
+ if (turnCompletedWithEmptyFinalReply(payload)) {
1483
+ items.push({
1484
+ kind: "notice",
1485
+ id: `${event.id}-empty-final-reply`,
1486
+ tone: "input",
1487
+ text: EMPTY_FINAL_REPLY_NOTICE,
1488
+ recordedOutcome: true,
1489
+ occurredAt: event.occurredAt,
1490
+ });
1491
+ }
1492
+ const hasCompletedFinalResponse =
1493
+ latestAgentResponse?.completed === true &&
1494
+ latestAgentResponse.item.phase !== "commentary" &&
1495
+ Boolean(visibleTrackedResponse);
1496
+ if (!hasAuthoritativeFinalOutput && pendingWaitOutcome) {
1497
+ // Preserve existing prose, including delta-only streams, when a
1498
+ // trailing wait yields without a terminal output receipt.
1499
+ completedTurn.preserveWaitResponse = true;
1500
+ }
1501
+ if (!hasAuthoritativeFinalOutput && !hasCompletedFinalResponse && pendingWaitOutcome) {
1502
+ const notice: NoticeItem = {
1503
+ kind: "notice",
1504
+ id: `${pendingWaitOutcome.id}-visible-outcome`,
1505
+ tone: "waiting",
1506
+ text: waitingOutcomeText(pendingWaitOutcome.reason),
1507
+ recordedOutcome: true,
1508
+ ...(pendingWorkerSessions.size > 0
1509
+ ? { waitingAgents: pendingWorkerSessions.size }
1510
+ : {}),
1511
+ occurredAt: pendingWaitOutcome.occurredAt,
1512
+ };
1513
+ items.push(notice);
1514
+ openWaitNotice = notice;
1515
+ }
942
1516
  break;
943
1517
  }
944
1518
 
945
1519
  case "turn.failed": {
1520
+ takeAgentResponse(turnId);
1521
+ takePendingWaitOutcome(turnId);
946
1522
  const hadActivity = hasTurnActivity(items, turnId);
947
1523
  // Credit death can hide behind fields `failureMessage` doesn't read
948
1524
  // (detail/segmentLimit), so classify the whole payload before falling
949
1525
  // back to the generic error/message extraction.
950
- const failureText = isCreditExhaustionPayload(payload)
951
- ? CREDIT_EXHAUSTION_MESSAGE
952
- : failureMessage(payload);
1526
+ const allowance = parseAllowanceExhaustedRefusal(payload);
1527
+ const failureText = allowance
1528
+ ? ALLOWANCE_TURN_END_TEXT
1529
+ : isCreditExhaustionPayload(payload)
1530
+ ? CREDIT_EXHAUSTION_MESSAGE
1531
+ : failureMessage(payload);
953
1532
  // The TURN failed — the in-flight items did not. Chip doctrine: red is
954
1533
  // spent once, on the turn-level outcome. Items caught mid-flight read
955
1534
  // as calm "interrupted" (same as turn.cancelled); an item that itself
956
1535
  // failed keeps its own failed status from its output event.
957
1536
  finalizeOpen(turnId, "cancelled", event.occurredAt);
958
1537
  items.push(turnEndItem(event, "failed", failureText));
959
- if (!hadActivity) {
1538
+ // A compaction failure names its outcome and keeps the recorded
1539
+ // provider text behind Details. The turn's own failed compaction
1540
+ // landmark is the one place that shows it: it receives the recorded
1541
+ // text, and no separate notice repeats it.
1542
+ // Retry reopens the same logical turn, so only a landmark from this
1543
+ // attempt (after the turn's previous settlement) can own this failure.
1544
+ const failure = allowance ? null : presentTurnFailureText(failureText);
1545
+ let landmarkIndex = -1;
1546
+ if (failure?.detail !== undefined && turnId !== null) {
1547
+ // Skip the turn-end just pushed for this failure.
1548
+ for (let index = items.length - 2; index >= 0; index -= 1) {
1549
+ const item = items[index];
1550
+ if (item?.kind === "turn-end" && item.turnId === turnId) break;
1551
+ if (
1552
+ item?.kind === "context-compaction" &&
1553
+ item.turnId === turnId &&
1554
+ item.phase === "skipped" &&
1555
+ item.skipReason === "summarization_failed"
1556
+ ) {
1557
+ landmarkIndex = index;
1558
+ break;
1559
+ }
1560
+ }
1561
+ }
1562
+ const landmark = landmarkIndex >= 0 ? items[landmarkIndex] : undefined;
1563
+ if (landmark?.kind === "context-compaction" && failure?.detail !== undefined) {
1564
+ items[landmarkIndex] = { ...landmark, failureDetail: failure.detail };
1565
+ }
1566
+ if (allowance) {
1567
+ allowanceNotice(event, turnId);
1568
+ } else if (!hadActivity && !landmark) {
960
1569
  items.push({
961
1570
  kind: "notice",
962
1571
  id: event.id,
963
1572
  tone: "failed",
964
- text: failureText ?? "The turn failed.",
1573
+ text: failure?.summary ?? "The turn failed.",
1574
+ ...(failure?.detail ? { details: { label: "Details", value: failure.detail } } : {}),
965
1575
  occurredAt: event.occurredAt,
966
1576
  });
967
1577
  }
968
1578
  break;
969
1579
  }
970
1580
 
1581
+ case "turn.superseded":
971
1582
  case "turn.cancelled": {
972
1583
  // A retraction of a never-started queued turn is not a turn ending —
973
1584
  // the message was withdrawn before any work happened; show nothing.
974
1585
  // A null turnId proves nothing, so it keeps the legacy finalize path.
975
- if (turnId && !prescan.startedTurnIds.has(turnId)) {
1586
+ if (event.type === "turn.cancelled" && turnId && !prescan.startedTurnIds.has(turnId)) {
976
1587
  break;
977
1588
  }
1589
+ takeAgentResponse(turnId);
1590
+ takePendingWaitOutcome(turnId);
978
1591
  const hadActivity = hasTurnActivity(items, turnId);
979
1592
  finalizeOpen(turnId, "cancelled", event.occurredAt);
980
1593
  items.push(turnEndItem(event, "cancelled", null));
@@ -990,6 +1603,45 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
990
1603
  break;
991
1604
  }
992
1605
 
1606
+ case "instruction.confirmation.recovered": {
1607
+ const receipt = asRecord(payload.receipt);
1608
+ if (receipt.outcome !== "published") break;
1609
+ closeStreamingTail();
1610
+ items.push({
1611
+ kind: "notice",
1612
+ id: event.id,
1613
+ tone: "input",
1614
+ text: "Your previously approved workspace instruction was saved.",
1615
+ occurredAt: event.occurredAt,
1616
+ });
1617
+ break;
1618
+ }
1619
+ case "knowledge.confirmation.recovered":
1620
+ case "knowledge.source.prepared":
1621
+ case "knowledge.source.failed": {
1622
+ const receipt = asRecord(payload.receipt);
1623
+ const outcome = event.type === "knowledge.source.failed" ? "failed" : receipt.outcome;
1624
+ if (
1625
+ (event.type !== "knowledge.confirmation.recovered" &&
1626
+ typeof payload.fileId !== "string") ||
1627
+ !["published", "pending", "rejected", "archived", "failed"].includes(String(outcome))
1628
+ )
1629
+ break;
1630
+ closeStreamingTail();
1631
+ items.push({
1632
+ kind: "knowledge",
1633
+ id: event.id,
1634
+ turnId,
1635
+ fileId: typeof payload.fileId === "string" ? payload.fileId : undefined,
1636
+ filename: typeof payload.filename === "string" ? payload.filename : undefined,
1637
+ entryId: typeof receipt.entryId === "string" ? receipt.entryId : undefined,
1638
+ status: outcome === "failed" ? "failed" : "complete",
1639
+ outcome: outcome as "published" | "pending" | "rejected" | "archived" | "failed",
1640
+ occurredAt: event.occurredAt,
1641
+ });
1642
+ break;
1643
+ }
1644
+
993
1645
  case "memory.saved":
994
1646
  case "memory.corrected": {
995
1647
  // A first-party memory write is a discrete step, not streamed text, so it
@@ -1020,6 +1672,9 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
1020
1672
  case "goal.cleared":
1021
1673
  case "goal.held":
1022
1674
  case "goal.continuation": {
1675
+ if (event.type === "goal.held" && payload.actor === "agent") {
1676
+ rememberPendingWaitOutcome(pendingWaitOutcomeByTurn, event, payload, turnId);
1677
+ }
1023
1678
  // Agent tool mutations already appear as tool-call rows in the activity
1024
1679
  // cluster. Re-emitting them as GoalRow landmarks splits "N steps" mid-turn.
1025
1680
  if (shouldSuppressAgentGoalLandmark(event.type, payload)) {
@@ -1038,12 +1693,28 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
1038
1693
  break;
1039
1694
  }
1040
1695
 
1696
+ case "session.wait.started": {
1697
+ if (payload.actor === "agent") {
1698
+ rememberPendingWaitOutcome(pendingWaitOutcomeByTurn, event, payload, turnId);
1699
+ }
1700
+ break;
1701
+ }
1702
+
1041
1703
  default:
1042
1704
  break;
1043
1705
  }
1044
1706
  }
1045
1707
 
1708
+ const sourceEventsById = new Map(events.map((event) => [event.id, event]));
1046
1709
  for (const item of items) {
1710
+ const source = sourceEventsById.get(item.id);
1711
+ const canonical = "annotationSource" in item ? item.annotationSource : undefined;
1712
+ item.sourceEvents = [
1713
+ ...(source ? [{ eventId: source.id, sequence: source.sequence }] : []),
1714
+ ...(canonical && canonical.eventId !== source?.id
1715
+ ? [{ eventId: canonical.eventId, sequence: canonical.sequence }]
1716
+ : []),
1717
+ ];
1047
1718
  if (item.kind === "agent-message") {
1048
1719
  item.text = stripOpaqueCitationTokens(item.text);
1049
1720
  }
@@ -1139,10 +1810,13 @@ function humanInputQuestions(value: unknown): HumanInputQuestion[] {
1139
1810
  if (
1140
1811
  !id ||
1141
1812
  !prompt ||
1142
- (kind !== "text" && kind !== "single_select" && kind !== "multi_select")
1813
+ (kind !== "text" && kind !== "single_select" && kind !== "multi_select" && kind !== "secret")
1143
1814
  ) {
1144
1815
  return [];
1145
1816
  }
1817
+ // Secret questions keep their spec (names and hints only; never a value).
1818
+ const secret = kind === "secret" ? humanInputSecretSpec(question.secret) : null;
1819
+ if (kind === "secret" && !secret) return [];
1146
1820
  const options = Array.isArray(question.options)
1147
1821
  ? question.options.map(asRecord).flatMap((option) => {
1148
1822
  const optionId = stringValue(option.id);
@@ -1162,20 +1836,54 @@ function humanInputQuestions(value: unknown): HumanInputQuestion[] {
1162
1836
  options,
1163
1837
  required: question.required === true,
1164
1838
  allowOther: question.allowOther === true,
1839
+ ...(secret ? { secret } : {}),
1165
1840
  },
1166
1841
  ];
1167
1842
  });
1168
1843
  }
1169
1844
 
1845
+ function humanInputSecretSpec(value: unknown): HumanInputSecretSpec | null {
1846
+ const spec = asRecord(value);
1847
+ const item = stringValue(spec.item);
1848
+ if (!item) return null;
1849
+ return spec as unknown as HumanInputSecretSpec;
1850
+ }
1851
+
1852
+ function humanInputSecretResult(value: unknown): HumanInputSecretAnswerResult | null {
1853
+ if (value === undefined || value === null) return null;
1854
+ const result = asRecord(value);
1855
+ const reference = stringValue(result.reference);
1856
+ const vaultName = stringValue(result.vaultName);
1857
+ const itemName = stringValue(result.itemName);
1858
+ const saveTo = result.saveTo;
1859
+ if (
1860
+ !reference ||
1861
+ !vaultName ||
1862
+ !itemName ||
1863
+ (saveTo !== "user" && saveTo !== "task" && saveTo !== "workspace")
1864
+ ) {
1865
+ return null;
1866
+ }
1867
+ return {
1868
+ reference,
1869
+ username: stringValue(result.username) || null,
1870
+ vaultName,
1871
+ itemName,
1872
+ saveTo,
1873
+ };
1874
+ }
1875
+
1170
1876
  function humanInputAnswer(value: Record<string, unknown>): HumanInputAnswer | null {
1171
1877
  const questionId = stringValue(value.questionId);
1172
1878
  if (!questionId) return null;
1879
+ const secret = humanInputSecretResult(value.secret);
1173
1880
  return {
1174
1881
  questionId,
1175
1882
  values: Array.isArray(value.values)
1176
1883
  ? value.values.filter((answer): answer is string => typeof answer === "string")
1177
1884
  : [],
1178
1885
  ...(typeof value.other === "string" && value.other ? { other: value.other } : {}),
1886
+ ...(secret ? { secret } : {}),
1179
1887
  };
1180
1888
  }
1181
1889
 
@@ -1194,6 +1902,7 @@ function humanInputAnswerSummary(
1194
1902
  label: question?.label || question?.prompt || fallbackLabel,
1195
1903
  prompt: question?.prompt || fallbackLabel,
1196
1904
  values,
1905
+ ...(answer.secret ? { secret: answer.secret } : {}),
1197
1906
  };
1198
1907
  }
1199
1908
 
@@ -1257,8 +1966,16 @@ export function stripOpaqueCitationTokens(text: string): string {
1257
1966
  return text.replace(/\s*cite(?:[^]+)+/gu, "");
1258
1967
  }
1259
1968
 
1969
+ /**
1970
+ * The turn summary's short outcome for a usage ceiling. The adjacent notice row
1971
+ * carries who can raise it and when it resets (and is host-customizable), so
1972
+ * the summary line stays a neutral fact instead of repeating remedy prose.
1973
+ */
1974
+ const ALLOWANCE_TURN_END_TEXT = "Usage limit reached";
1975
+
1260
1976
  /** The turn-end payload shape, as `isCreditExhaustion` wants it. */
1261
1977
  function isCreditExhaustionPayload(payload: Record<string, unknown>): boolean {
1978
+ if (payload.code === "allowance_exhausted") return false;
1262
1979
  return isCreditExhaustion({
1263
1980
  error: typeof payload.error === "string" ? payload.error : null,
1264
1981
  detail: typeof payload.detail === "string" ? payload.detail : null,
@@ -1309,12 +2026,16 @@ export function sessionStatusFromEvents(events: SessionEvent[]): SessionStatus |
1309
2026
  non-user span folds behind a turn group, with activity blocks nested inside.
1310
2027
  -------------------------------------------------------------------------- */
1311
2028
 
2029
+ /** Work items: every activity kind except assistant commentary. */
2030
+ type WorkActivityItem = Exclude<ActivityItem, AgentMessageItem>;
2031
+
1312
2032
  /**
1313
2033
  * Whether an item clusters into an activity block. A `switch` (not a stringly-
1314
2034
  * typed set) so adding an {@link ActivityItem} kind is a compile-time prompt to
1315
- * decide its grouping — and it narrows `item` to `ActivityItem` with no cast.
2035
+ * decide its grouping, and it narrows `item` to a work item with no cast.
2036
+ * Assistant prose stays outside activity clusters.
1316
2037
  */
1317
- function isActivityItem(item: TimelineItem): item is ActivityItem {
2038
+ function isActivityItem(item: TimelineItem): item is WorkActivityItem {
1318
2039
  switch (item.kind) {
1319
2040
  case "reasoning":
1320
2041
  case "tool-call":
@@ -1322,6 +2043,7 @@ function isActivityItem(item: TimelineItem): item is ActivityItem {
1322
2043
  case "sandbox":
1323
2044
  case "startup-phase":
1324
2045
  case "memory":
2046
+ case "knowledge":
1325
2047
  case "fleet-decision":
1326
2048
  return true;
1327
2049
  default:
@@ -1329,9 +2051,22 @@ function isActivityItem(item: TimelineItem): item is ActivityItem {
1329
2051
  }
1330
2052
  }
1331
2053
 
1332
- export function groupTimeline(items: TimelineItem[]): TimelineGroup[] {
2054
+ export type GroupTimelineOptions = {
2055
+ /** Readable assistant prose with an independent work summary for each turn. */
2056
+ readableTurns?: boolean;
2057
+ /** @deprecated Use readableTurns. Cross-turn exchange folding has been removed. */
2058
+ foldExchanges?: boolean;
2059
+ };
2060
+
2061
+ export function groupTimeline(
2062
+ items: TimelineItem[],
2063
+ options: GroupTimelineOptions = {},
2064
+ ): TimelineGroup[] {
2065
+ if (options.readableTurns || options.foldExchanges) return groupReadableTurns(items);
1333
2066
  const groups: TimelineGroup[] = [];
1334
2067
  for (const item of items) {
2068
+ // A recovered capacity wait is over; it leaves no warning behind.
2069
+ if (item.kind === "notice" && item.capacityWait && item.resolvedAt) continue;
1335
2070
  if (isActivityItem(item)) {
1336
2071
  const open = groups[groups.length - 1];
1337
2072
  if (open?.kind === "activity" && open.outcome === undefined) {
@@ -1355,6 +2090,641 @@ export function groupTimeline(items: TimelineItem[]): TimelineGroup[] {
1355
2090
  return groups;
1356
2091
  }
1357
2092
 
2093
+ /** Live prose stays primary; settled progress joins its own turn's work history. */
2094
+ function groupReadableTurns(items: TimelineItem[]): TimelineGroup[] {
2095
+ type Work = Extract<TimelineGroup, { kind: "activity" }>;
2096
+ let groups: TimelineGroup[] = [];
2097
+ const turns = new Map<string, Work>();
2098
+ // Turn identity and settlement exist even when a final-only turn has no
2099
+ // disclosure. A late receipt must not become a new turn or revive its clock.
2100
+ const seenTurns = new Set<string>();
2101
+ const settlements = new Map<
2102
+ string,
2103
+ { endedAt: string; outcome?: Work["outcome"]; failureText?: string }
2104
+ >();
2105
+ const compactionTurns = new Map<ContextCompactionItem, string>();
2106
+ const messages = new Map<string, AgentMessageItem[]>();
2107
+ // Capacity waits per turn: their spans are not work, and a live one is
2108
+ // carried by the turn's work row instead of a separate warning.
2109
+ const capacityPauses = new Map<string, Array<{ startedAt: string; endedAt?: string }>>();
2110
+ const liveCapacityWaits = new Map<string, NoticeItem>();
2111
+ const inputs = new Map<string, Extract<TimelineItem, { kind: "machine-input-batch" }>>();
2112
+ const itemOrder = new Map(items.map((item, index) => [item.id, index]));
2113
+ // Turns that answer a person's message: the first turn after it, and a live
2114
+ // turn it steered. Their replies are never folded as mere progress.
2115
+ const answersPerson = new Set<string>();
2116
+ let personAwaitingTurn = false;
2117
+ // A live turn a person's message may have steered. If it keeps working after
2118
+ // the message, the steer was absorbed and no later turn inherits it; if it is
2119
+ // superseded instead, the next turn answers the person.
2120
+ let steeredTurn: string | undefined;
2121
+ let legacyTurn = "start";
2122
+ let currentTurn = legacyTurn;
2123
+ for (const item of items) {
2124
+ if (item.kind === "user-message") {
2125
+ legacyTurn = item.id;
2126
+ personAwaitingTurn = true;
2127
+ steeredTurn = undefined;
2128
+ if (seenTurns.has(currentTurn) && !settlements.has(currentTurn)) {
2129
+ answersPerson.add(currentTurn);
2130
+ steeredTurn = currentTurn;
2131
+ }
2132
+ }
2133
+ // A capacity wait belongs to the turn it blocks, even before that turn's
2134
+ // first step.
2135
+ const itemTurnId =
2136
+ item.kind === "notice" && item.capacityWait
2137
+ ? item.capacityWait.turnId
2138
+ : "turnId" in item
2139
+ ? item.turnId
2140
+ : null;
2141
+ const key = itemTurnId || legacyTurn;
2142
+ if (
2143
+ steeredTurn !== undefined &&
2144
+ key === steeredTurn &&
2145
+ (isActivityItem(item) || (item.kind === "agent-message" && item.text.trim()))
2146
+ ) {
2147
+ personAwaitingTurn = false;
2148
+ steeredTurn = undefined;
2149
+ }
2150
+ // Legacy work still establishes a boundary using its prompt key.
2151
+ // A human message alone does not: it may be steering the existing turn.
2152
+ if (itemTurnId || isActivityItem(item) || (item.kind === "agent-message" && item.text.trim())) {
2153
+ if (key !== currentTurn && !seenTurns.has(key)) {
2154
+ const previous = turns.get(currentTurn);
2155
+ if (!settlements.has(currentTurn)) {
2156
+ settlements.set(currentTurn, { endedAt: item.occurredAt });
2157
+ }
2158
+ if (previous?.work && !previous.work.endedAt) previous.work.endedAt = item.occurredAt;
2159
+ currentTurn = key;
2160
+ if (personAwaitingTurn) answersPerson.add(key);
2161
+ personAwaitingTurn = false;
2162
+ steeredTurn = undefined;
2163
+ }
2164
+ seenTurns.add(key);
2165
+ }
2166
+ const workForTurn = () => {
2167
+ let group = turns.get(key);
2168
+ const firstMessage = messages.get(key)?.[0];
2169
+ const messageStart = firstMessage?.startedAt ?? firstMessage?.occurredAt;
2170
+ const activityStart =
2171
+ item.kind === "startup-phase"
2172
+ ? (item.loadingStartedAt ?? item.occurredAt)
2173
+ : item.occurredAt;
2174
+ const startedAt =
2175
+ messageStart && Date.parse(messageStart) < Date.parse(activityStart)
2176
+ ? messageStart
2177
+ : activityStart;
2178
+ if (!group) {
2179
+ const settlement = settlements.get(key);
2180
+ group = {
2181
+ kind: "activity",
2182
+ id: `work-${key}`,
2183
+ items: [],
2184
+ work: { startedAt, details: [] },
2185
+ };
2186
+ if (settlement) {
2187
+ group.work!.endedAt = settlement.endedAt;
2188
+ if (settlement.outcome) group.outcome = settlement.outcome;
2189
+ if (settlement.failureText) group.failureText = settlement.failureText;
2190
+ }
2191
+ turns.set(key, group);
2192
+ const firstMessageIndex =
2193
+ settlement && firstMessage
2194
+ ? groups.findIndex((entry) => entry.kind === "item" && entry.item === firstMessage)
2195
+ : -1;
2196
+ if (firstMessageIndex >= 0) groups.splice(firstMessageIndex, 0, group);
2197
+ else groups.push(group);
2198
+ } else if (Date.parse(startedAt) < Date.parse(group.work!.startedAt)) {
2199
+ // Completion-only startup receipts can reveal an earlier phase start
2200
+ // after another activity has already established the work row.
2201
+ group.work!.startedAt = startedAt;
2202
+ }
2203
+ return group;
2204
+ };
2205
+ if (isActivityItem(item)) {
2206
+ const group = workForTurn();
2207
+ group.items.push(item);
2208
+ const previous = group.work!.details.at(-1);
2209
+ const preparing = (entry: ActivityItem) =>
2210
+ entry.kind === "startup-phase" || (entry.kind === "reasoning" && !entry.text.trim());
2211
+ if (
2212
+ preparing(item) &&
2213
+ previous?.kind === "activity" &&
2214
+ previous.items.every(preparing) &&
2215
+ previous.items.at(-1) === items[(itemOrder.get(item.id) ?? 0) - 1]
2216
+ )
2217
+ previous.items.push(item);
2218
+ else
2219
+ group.work!.details.push({ kind: "activity", id: `work-item-${item.id}`, items: [item] });
2220
+ delete group.work!.waiting;
2221
+ } else if (item.kind === "notice" && item.capacityWait) {
2222
+ const pauses = capacityPauses.get(key) ?? [];
2223
+ pauses.push({
2224
+ startedAt: item.occurredAt,
2225
+ ...(item.resolvedAt ? { endedAt: item.resolvedAt } : {}),
2226
+ });
2227
+ capacityPauses.set(key, pauses);
2228
+ if (!item.resolvedAt) {
2229
+ if (settlements.has(key)) groups.push({ kind: "item", item });
2230
+ else {
2231
+ workForTurn();
2232
+ liveCapacityWaits.set(key, item);
2233
+ }
2234
+ }
2235
+ } else if (item.kind === "turn-end") {
2236
+ if (!item.resumedAt) {
2237
+ settlements.set(key, {
2238
+ endedAt: item.occurredAt,
2239
+ outcome: item.outcome,
2240
+ ...(item.failureText ? { failureText: item.failureText } : {}),
2241
+ });
2242
+ }
2243
+ const group = turns.get(key);
2244
+ if (group?.work) {
2245
+ if (item.resumedAt) {
2246
+ group.work.details.push({
2247
+ kind: "item",
2248
+ item: {
2249
+ kind: "notice",
2250
+ id: item.id,
2251
+ tone: "failed",
2252
+ text: item.failureText ?? "The turn failed before retrying.",
2253
+ occurredAt: item.occurredAt,
2254
+ },
2255
+ });
2256
+ continue;
2257
+ }
2258
+ group.outcome = item.outcome;
2259
+ if (item.failureText) group.failureText = item.failureText;
2260
+ group.work.endedAt = item.occurredAt;
2261
+ }
2262
+ } else if (item.kind === "context-compaction" && item.phase === "compacted") {
2263
+ // A maintenance checkpoint alone is not conversational work. Resolve its
2264
+ // attachment after real activity has established this turn's work row.
2265
+ compactionTurns.set(item, key);
2266
+ groups.push({ kind: "item", item });
2267
+ } else if (
2268
+ item.kind === "machine-input-batch" &&
2269
+ item.members.every((member) => ROUTINE_MACHINE_INPUT_KINDS.has(member.kind))
2270
+ ) {
2271
+ const previous = inputs.get(key);
2272
+ if (previous) {
2273
+ previous.members.push(
2274
+ ...item.members.filter((member) => !previous.members.some((old) => old.id === member.id)),
2275
+ );
2276
+ previous.sourceEvents = [...(previous.sourceEvents ?? []), ...(item.sourceEvents ?? [])];
2277
+ } else {
2278
+ const batch = { ...item, compact: true, members: [...item.members] };
2279
+ inputs.set(key, batch);
2280
+ groups.push({ kind: "item", item: batch });
2281
+ }
2282
+ } else if (
2283
+ item.kind === "worker-completion" ||
2284
+ (item.kind === "goal" && item.action === "continuation")
2285
+ ) {
2286
+ const group = turns.get(currentTurn);
2287
+ if (group?.work) group.work.details.push({ kind: "item", item });
2288
+ else groups.push({ kind: "item", item });
2289
+ } else {
2290
+ const current = turns.get(currentTurn)?.work;
2291
+ // A live approval wait is carried by the turn header ("Waiting for you")
2292
+ // and decided in the host's approval surface; a second in-timeline banner
2293
+ // only repeats it. Once resolved it returns as the quiet recorded marker.
2294
+ const liveApprovalWait =
2295
+ item.kind === "notice" &&
2296
+ item.tone === "waiting" &&
2297
+ !item.recordedOutcome &&
2298
+ !item.resolvedAt &&
2299
+ item.text.startsWith("Approval needed") &&
2300
+ current !== undefined &&
2301
+ !current.endedAt;
2302
+ // Likewise the live "waiting on you" divider: the open turn's header
2303
+ // already reads "Waiting for you · <elapsed>". It returns, resolved, as
2304
+ // the "work resumed" divider once the session runs again.
2305
+ const liveStatusWait =
2306
+ item.kind === "session-status" &&
2307
+ item.status === "requires_action" &&
2308
+ !item.blocked &&
2309
+ !item.resolvedAt &&
2310
+ current !== undefined &&
2311
+ !current.endedAt;
2312
+ if (!liveApprovalWait && !liveStatusWait) groups.push({ kind: "item", item });
2313
+ if (current && !current.endedAt) {
2314
+ if (
2315
+ item.kind === "notice" &&
2316
+ item.tone === "waiting" &&
2317
+ !item.recordedOutcome &&
2318
+ !item.resolvedAt
2319
+ ) {
2320
+ current.waiting = {
2321
+ label: item.text.startsWith("Approval needed") ? "Waiting for you" : "Waiting",
2322
+ since: item.occurredAt,
2323
+ };
2324
+ } else if (
2325
+ item.kind === "session-status" &&
2326
+ item.status === "requires_action" &&
2327
+ !item.blocked &&
2328
+ !item.resolvedAt
2329
+ ) {
2330
+ current.waiting = { label: "Waiting for you", since: item.occurredAt };
2331
+ } else if (
2332
+ item.kind === "session-status" &&
2333
+ ["paused", "cancelled", "failed"].includes(item.status) &&
2334
+ !item.resolvedAt
2335
+ ) {
2336
+ current.endedAt = item.occurredAt;
2337
+ delete current.waiting;
2338
+ }
2339
+ }
2340
+ if (item.kind === "agent-message" && item.text.trim()) {
2341
+ const prose = messages.get(key) ?? [];
2342
+ prose.push(item);
2343
+ messages.set(key, prose);
2344
+ // A text-only turn still has one overall activity surface. An explicit
2345
+ // final-only response does not manufacture an empty work disclosure.
2346
+ if (
2347
+ (item.phase !== "final_answer" || prose.length > 1) &&
2348
+ (turns.has(key) || !settlements.has(key))
2349
+ )
2350
+ workForTurn();
2351
+ }
2352
+ }
2353
+ }
2354
+ for (const [key, group] of turns) {
2355
+ const work = group.work!;
2356
+ const live = liveCapacityWaits.get(key);
2357
+ // Re-assert after the loop: a late receipt for the blocked turn must not
2358
+ // turn "Limit reached" back into "Working" while the wait is still open.
2359
+ if (live?.capacityWait && !work.endedAt) {
2360
+ work.waiting = {
2361
+ label: live.capacityWait.label,
2362
+ since: live.occurredAt,
2363
+ detail: live.capacityWait.detail,
2364
+ // Its span is not work and may last until a quota resets: no clock.
2365
+ untimed: true,
2366
+ };
2367
+ }
2368
+ const pausedMs = (capacityPauses.get(key) ?? []).reduce((total, pause) => {
2369
+ if (!pause.endedAt) return total;
2370
+ const start = Math.max(Date.parse(work.startedAt), Date.parse(pause.startedAt));
2371
+ const end = Math.min(
2372
+ work.endedAt ? Date.parse(work.endedAt) : Number.POSITIVE_INFINITY,
2373
+ Date.parse(pause.endedAt),
2374
+ );
2375
+ return Number.isFinite(start) && Number.isFinite(end) && end > start
2376
+ ? total + end - start
2377
+ : total;
2378
+ }, 0);
2379
+ if (pausedMs > 0) work.pausedMs = pausedMs;
2380
+ }
2381
+ groups = groups.filter((entry) => {
2382
+ if (entry.kind !== "item" || entry.item.kind !== "context-compaction") return true;
2383
+ const key = compactionTurns.get(entry.item);
2384
+ const work = key === undefined ? undefined : turns.get(key)?.work;
2385
+ if (!work) return true;
2386
+ work.details.push(entry);
2387
+ return false;
2388
+ });
2389
+ // A declared answer starts the response clock immediately. Without a phase,
2390
+ // only settlement identifies the last response; text length never does.
2391
+ const beforeRows = new Map<TimelineGroup, Work>();
2392
+ const afterRows = new Map<TimelineGroup, Work>();
2393
+ const movedRows = new Set<Work>();
2394
+ const foldedProse = new Set<AgentMessageItem>();
2395
+ const positions = new Map<TimelineGroup | TimelineItem, number>();
2396
+ const nextBoundary = new Map<Work, number>();
2397
+ let boundary = groups.length;
2398
+ for (let index = groups.length - 1; index >= 0; index -= 1) {
2399
+ const entry = groups[index]!;
2400
+ positions.set(entry.kind === "item" ? entry.item : entry, index);
2401
+ if (entry.kind === "activity" && entry.work) {
2402
+ nextBoundary.set(entry, boundary);
2403
+ boundary = index;
2404
+ } else if (
2405
+ entry.kind === "item" &&
2406
+ (entry.item.kind === "user-message" || entry.item.kind === "machine-input-batch")
2407
+ ) {
2408
+ boundary = index;
2409
+ }
2410
+ }
2411
+ for (const [key, group] of turns) {
2412
+ const prose = messages.get(key) ?? [];
2413
+ const settledAt = group.work!.endedAt;
2414
+ const responseCandidates = settledAt
2415
+ ? prose.filter(
2416
+ (message) => Date.parse(message.startedAt ?? message.occurredAt) <= Date.parse(settledAt),
2417
+ )
2418
+ : prose;
2419
+ const response =
2420
+ responseCandidates.filter((message) => message.phase === "final_answer").at(-1) ??
2421
+ (settledAt
2422
+ ? (responseCandidates.at(-1) ??
2423
+ prose.filter((message) => message.phase === "final_answer").at(-1))
2424
+ : undefined);
2425
+ const responses = new Set<AgentMessageItem>(response ? [response] : []);
2426
+ // A turn that answers a person but ends with only commentary (typically by
2427
+ // yielding to a wait) has no single authoritative reply. Its first message
2428
+ // is the immediate answer and its last is the latest word; only the
2429
+ // progress narration between them folds.
2430
+ const firstCandidate = responseCandidates[0];
2431
+ if (
2432
+ settledAt &&
2433
+ firstCandidate &&
2434
+ answersPerson.has(key) &&
2435
+ responseCandidates.every((message) => message.phase === "commentary")
2436
+ ) {
2437
+ responses.add(firstCandidate);
2438
+ }
2439
+ if (settledAt) {
2440
+ for (const message of responseCandidates) {
2441
+ // Markdown image syntax also carries retained video/audio previews.
2442
+ // Keep potential primary media visible rather than guessing whether a
2443
+ // partial/reference-style embed can safely disappear into history.
2444
+ if (responses.has(message) || message.text.includes("![")) continue;
2445
+ foldedProse.add(message);
2446
+ group.work!.details.push({ kind: "item", item: message });
2447
+ }
2448
+ } else {
2449
+ // Live progress stays primary above the work row, and is also listed in
2450
+ // the work history so an expanded disclosure reads like the settled one.
2451
+ // The timeline folds the outside copies only while that disclosure is open.
2452
+ for (const message of prose) {
2453
+ if (message.phase === "final_answer" || message.text.includes("![")) continue;
2454
+ group.work!.details.push({ kind: "item", item: message });
2455
+ (group.work!.liveNoteIds ??= []).push(message.id);
2456
+ }
2457
+ }
2458
+ // Preserve event chronology, not completion timestamps or activity kinds.
2459
+ group.work!.details.sort((a, b) => {
2460
+ const first = (entry: TimelineGroup) =>
2461
+ entry.kind === "item"
2462
+ ? entry.item.id
2463
+ : entry.kind === "activity"
2464
+ ? entry.items[0]?.id
2465
+ : undefined;
2466
+ return (itemOrder.get(first(a) ?? "") ?? 0) - (itemOrder.get(first(b) ?? "") ?? 0);
2467
+ });
2468
+ if (response) {
2469
+ const responseAt = response.startedAt ?? response.occurredAt;
2470
+ // A delayed completion-only receipt cannot extend a settled work clock.
2471
+ group.work!.responseStartedAt =
2472
+ settledAt && Date.parse(responseAt) > Date.parse(settledAt) ? settledAt : responseAt;
2473
+ // A late completion may belong to an older turn. Keep its work at its
2474
+ // original boundary rather than moving it across a newer turn's input.
2475
+ // The work row leads the first visible reply of the turn.
2476
+ const anchor = Math.min(
2477
+ ...[...responses].map((message) => positions.get(message) ?? groups.length),
2478
+ );
2479
+ if (settledAt && anchor < (nextBoundary.get(group) ?? groups.length)) {
2480
+ beforeRows.set(groups[anchor]!, group);
2481
+ movedRows.add(group);
2482
+ }
2483
+ }
2484
+ if (!settledAt) {
2485
+ // Keep the same row at the tail of this turn, after every live progress
2486
+ // message and attention surface, without crossing a newer input/turn.
2487
+ // A visible steer is not a turn boundary when later prose still belongs
2488
+ // to this turn. New input without that evidence remains a hard boundary.
2489
+ const lastProseIndex = Math.max(...prose.map((message) => positions.get(message) ?? -1));
2490
+ let tailIndex = groups.length - 1;
2491
+ for (let index = (positions.get(group) ?? 0) + 1; index <= tailIndex; index += 1) {
2492
+ const entry = groups[index]!;
2493
+ if (
2494
+ (entry.kind === "activity" && entry.work) ||
2495
+ (entry.kind === "item" &&
2496
+ (("turnId" in entry.item && entry.item.turnId && entry.item.turnId !== key) ||
2497
+ (index > lastProseIndex &&
2498
+ (entry.item.kind === "user-message" || entry.item.kind === "machine-input-batch"))))
2499
+ ) {
2500
+ tailIndex = index - 1;
2501
+ break;
2502
+ }
2503
+ }
2504
+ const tail = groups[tailIndex];
2505
+ if (tail && tail !== group) {
2506
+ afterRows.set(tail, group);
2507
+ movedRows.add(group);
2508
+ }
2509
+ }
2510
+ }
2511
+ // The work of every turn that answers a person stays visible even when it
2512
+ // said nothing; quiet-cycle folding must never take it.
2513
+ const personWork = new Set<TimelineGroup>(
2514
+ [...answersPerson].flatMap((key) => {
2515
+ const work = turns.get(key);
2516
+ return work ? [work] : [];
2517
+ }),
2518
+ );
2519
+ return compactQuietCycles(
2520
+ groups.flatMap((group) => {
2521
+ if (group.kind === "activity" && movedRows.has(group)) return [];
2522
+ const hidden =
2523
+ group.kind === "item" && group.item.kind === "agent-message" && foldedProse.has(group.item);
2524
+ const before = beforeRows.get(group);
2525
+ const after = afterRows.get(group);
2526
+ return [...(before ? [before] : []), ...(hidden ? [] : [group]), ...(after ? [after] : [])];
2527
+ }),
2528
+ personWork,
2529
+ );
2530
+ }
2531
+
2532
+ type WorkGroup = Extract<TimelineGroup, { kind: "activity" }> & {
2533
+ work: NonNullable<Extract<TimelineGroup, { kind: "activity" }>["work"]>;
2534
+ };
2535
+
2536
+ /**
2537
+ * Settled, successful work whose turn left no visible reply, image or blocker.
2538
+ * A failure it recovered from, a failed startup phase or a context compaction
2539
+ * is something to see, so that work is not quiet either.
2540
+ */
2541
+ function isQuietWork(group: TimelineGroup): group is WorkGroup {
2542
+ if (group.kind !== "activity" || !group.work?.endedAt || group.work.waiting) return false;
2543
+ if (group.work.cycles || (group.outcome && group.outcome !== "complete")) return false;
2544
+ if (
2545
+ group.items.some(
2546
+ (item) =>
2547
+ item.kind === "startup-phase" && (item.status === "failed" || item.status === "cancelled"),
2548
+ )
2549
+ ) {
2550
+ return false;
2551
+ }
2552
+ const quiet = (entry: TimelineGroup): boolean =>
2553
+ entry.kind === "item"
2554
+ ? entry.item.kind !== "auth-needed" &&
2555
+ entry.item.kind !== "context-compaction" &&
2556
+ entry.item.kind !== "human-input" &&
2557
+ !(entry.item.kind === "notice" && entry.item.tone !== "waiting") &&
2558
+ !(entry.item.kind === "agent-message" && entry.item.text.includes("!["))
2559
+ : entry.kind === "activity"
2560
+ ? isQuietDetail(entry, quiet)
2561
+ : false;
2562
+ return !publishesOutput(group) && group.work.details.every(quiet);
2563
+ }
2564
+
2565
+ function isQuietDetail(
2566
+ group: Extract<TimelineGroup, { kind: "activity" }>,
2567
+ quiet: (entry: TimelineGroup) => boolean,
2568
+ ): boolean {
2569
+ return (
2570
+ (!group.outcome || group.outcome === "complete") && (group.work?.details ?? []).every(quiet)
2571
+ );
2572
+ }
2573
+
2574
+ /**
2575
+ * Work that generated an image or published a file produced output worth
2576
+ * seeing. Checked by tool name only, so the projection stays free of the
2577
+ * renderer's image presentation rules.
2578
+ */
2579
+ function publishesOutput(group: TimelineGroup): boolean {
2580
+ if (group.kind !== "activity") return false;
2581
+ return (
2582
+ group.items.some((item) => {
2583
+ if (item.kind !== "tool-call") return false;
2584
+ const name = mcpToolLeaf(item.name);
2585
+ return (
2586
+ name === "generate_image" ||
2587
+ name === "image_generation_call" ||
2588
+ name === "sandbox_file_publish"
2589
+ );
2590
+ }) || (group.work?.details ?? []).some(publishesOutput)
2591
+ );
2592
+ }
2593
+
2594
+ /**
2595
+ * Whether the work at `index` follows a person's message or answer with only
2596
+ * routine input in between. Complements the turn bookkeeping for legacy rows.
2597
+ */
2598
+ function answersPersonAt(groups: TimelineGroup[], index: number): boolean {
2599
+ for (let cursor = index - 1; cursor >= 0; cursor -= 1) {
2600
+ const group = groups[cursor]!;
2601
+ if (
2602
+ group.kind === "item" &&
2603
+ (group.item.kind === "user-message" || group.item.kind === "human-input")
2604
+ ) {
2605
+ return true;
2606
+ }
2607
+ if (!isRoutineInput(group)) return false;
2608
+ }
2609
+ return false;
2610
+ }
2611
+
2612
+ /**
2613
+ * A routine machine input (agent update, child result, wait timeout, ...). An
2614
+ * agent that failed, paused or needs action is news, not routine.
2615
+ */
2616
+ function isRoutineInput(group: TimelineGroup): boolean {
2617
+ return (
2618
+ group.kind === "item" &&
2619
+ group.item.kind === "machine-input-batch" &&
2620
+ !!group.item.compact &&
2621
+ group.item.members.every(
2622
+ (member) =>
2623
+ member.kind !== "child_paused" &&
2624
+ member.classification !== "failure" &&
2625
+ // A parent's direction is always recorded as action_required for the
2626
+ // agent; it is routine orchestration, not something a person must do.
2627
+ (member.classification !== "action_required" || member.kind === "agent_steer_instruction"),
2628
+ )
2629
+ );
2630
+ }
2631
+
2632
+ /** A recorded wait that later input already ended. */
2633
+ function isFinishedWait(group: TimelineGroup): group is Extract<TimelineGroup, { kind: "item" }> & {
2634
+ item: NoticeItem;
2635
+ } {
2636
+ return (
2637
+ group.kind === "item" &&
2638
+ group.item.kind === "notice" &&
2639
+ group.item.tone === "waiting" &&
2640
+ !!group.item.recordedOutcome &&
2641
+ !!group.item.waitEndedAt
2642
+ );
2643
+ }
2644
+
2645
+ /**
2646
+ * Long orchestration runs repeat "agent update / worked / waited" many times
2647
+ * without saying anything. Fold two or more such consecutive quiet cycles into
2648
+ * one work row: "7 updates over 6h 12m", with the latest wait reason under it.
2649
+ * Expanding it shows the original rows unchanged. Visible replies, people's
2650
+ * messages, failures, approvals, live work and the current wait are never folded
2651
+ * and end a run.
2652
+ */
2653
+ function compactQuietCycles(
2654
+ groups: TimelineGroup[],
2655
+ personWork: ReadonlySet<TimelineGroup>,
2656
+ ): TimelineGroup[] {
2657
+ const result: TimelineGroup[] = [];
2658
+ let index = 0;
2659
+ while (index < groups.length) {
2660
+ // A run starts at routine input, never at a leading wait (it belongs to
2661
+ // the reply before it) or at bare work (it belongs to the visible row
2662
+ // before it, such as an agent's failure). Later cycles may lack input,
2663
+ // for example after a wait timeout.
2664
+ let end = index;
2665
+ let lastCycleEnd = index;
2666
+ let cycles = 0;
2667
+ while (end < groups.length) {
2668
+ const input = isRoutineInput(groups[end]!) ? end : -1;
2669
+ if (input < 0 && cycles === 0) break;
2670
+ const workIndex = input >= 0 ? end + 1 : end;
2671
+ const work = groups[workIndex];
2672
+ if (!work || !isQuietWork(work)) break;
2673
+ // Work that follows a person's message is that message's turn, even
2674
+ // when it said nothing; it stays visible.
2675
+ if (personWork.has(work) || answersPersonAt(groups, workIndex)) break;
2676
+ const next = groups[workIndex + 1];
2677
+ // Work directly followed by its reply is not quiet.
2678
+ if (next?.kind === "item" && next.item.kind === "agent-message") break;
2679
+ if (!next || !isFinishedWait(next)) break;
2680
+ cycles += 1;
2681
+ end = workIndex + 2;
2682
+ lastCycleEnd = end;
2683
+ }
2684
+ if (cycles < 2) {
2685
+ result.push(groups[index]!);
2686
+ index += 1;
2687
+ continue;
2688
+ }
2689
+ const run = groups.slice(index, lastCycleEnd);
2690
+ const works = run.filter((group): group is WorkGroup => isQuietWork(group));
2691
+ const waits = run.filter(isFinishedWait);
2692
+ const first = works[0]!;
2693
+ const lastWait = waits.at(-1)!;
2694
+ result.push({
2695
+ kind: "activity",
2696
+ id: `cycles-${first.id}`,
2697
+ items: works.flatMap((group) => group.items),
2698
+ outcome: "complete",
2699
+ work: {
2700
+ startedAt: first.work.startedAt,
2701
+ endedAt: lastWait.item.waitEndedAt!,
2702
+ details: run,
2703
+ cycles: {
2704
+ count: cycles,
2705
+ ...(lastWait.item.text.trim() ? { summary: lastWait.item.text.trim() } : {}),
2706
+ },
2707
+ },
2708
+ });
2709
+ index = lastCycleEnd;
2710
+ }
2711
+ return result;
2712
+ }
2713
+
2714
+ /** Machine inputs that continue the current exchange rather than start one. */
2715
+ const ROUTINE_MACHINE_INPUT_KINDS: ReadonlySet<MachineInputMember["kind"]> = new Set([
2716
+ "goal_continuation",
2717
+ "background_command_result",
2718
+ "session_wait_timeout",
2719
+ "agent_message",
2720
+ "agent_steer_instruction",
2721
+ "child_terminal_result",
2722
+ "child_requires_action_resolved",
2723
+ "child_paused",
2724
+ "child_waiting_capacity",
2725
+ "child_progress",
2726
+ ]);
2727
+
1358
2728
  /* --- helpers ---------------------------------------------------------------- */
1359
2729
 
1360
2730
  type TurnAnchorPrescan = {
@@ -1366,6 +2736,22 @@ type TurnAnchorPrescan = {
1366
2736
  startedTurnIds: Set<string>;
1367
2737
  };
1368
2738
 
2739
+ /** Navigation uses the same queue/start/withdrawal rules as transcript ordering. */
2740
+ export function timelineQuestionPlacement(question: SessionEvent, evidence: SessionEvent[]) {
2741
+ const anchors = prescanTurnAnchors([question, ...evidence]);
2742
+ const direct = anchors.directChatSequenceByTrigger.get(question.id);
2743
+ if (direct !== undefined) return { kind: "visible" as const, sequence: direct };
2744
+ if (anchors.cancelledBeforeStartTriggers.has(question.id)) return { kind: "withdrawn" as const };
2745
+ const start = anchors.startSeqByTrigger.get(question.id);
2746
+ if (start !== undefined) return { kind: "visible" as const, sequence: start };
2747
+ if (
2748
+ anchors.queuedTurnByTrigger.has(question.id) ||
2749
+ anchors.explicitQueuedTriggers.has(question.id)
2750
+ )
2751
+ return { kind: "pending" as const };
2752
+ return { kind: "visible" as const, sequence: question.sequence };
2753
+ }
2754
+
1369
2755
  function prescanTurnAnchors(events: SessionEvent[]): TurnAnchorPrescan {
1370
2756
  const ordered = [...events].sort((a, b) => a.sequence - b.sequence);
1371
2757
  const queuedTurnByTrigger = new Map<string, string>();
@@ -1554,7 +2940,12 @@ function pushInsertion(
1554
2940
  }
1555
2941
 
1556
2942
  function isAgentActivityEvent(type: string): boolean {
1557
- return type.startsWith("agent.") || type.startsWith("sandbox.");
2943
+ return (
2944
+ type.startsWith("agent.") ||
2945
+ type.startsWith("sandbox.") ||
2946
+ type === "subscription.account.selected" ||
2947
+ type === "subscription.account.switched"
2948
+ );
1558
2949
  }
1559
2950
 
1560
2951
  /**
@@ -1564,11 +2955,15 @@ function isAgentActivityEvent(type: string): boolean {
1564
2955
  * steering, or deleting a waiting row must never make its prompt appear in the
1565
2956
  * transcript as though inference had begun.
1566
2957
  */
1567
- function isTurnExecutionEvidence(type: string): boolean {
2958
+ export function isTurnExecutionEvidence(type: string): boolean {
1568
2959
  return (
1569
2960
  isAgentActivityEvent(type) ||
1570
2961
  type === "turn.completed" ||
1571
2962
  type === "turn.failed" ||
2963
+ // An admission refusal can settle a queued turn before it ever starts:
2964
+ // its prompt still belongs above the "usage limit reached" row.
2965
+ type === "usage.exhausted" ||
2966
+ type === "turn.superseded" ||
1572
2967
  type === "turn.recovery.requested" ||
1573
2968
  type === "turn.capacity_waiting" ||
1574
2969
  type === "session.requiresAction" ||
@@ -1597,6 +2992,35 @@ function turnEndItem(
1597
2992
  };
1598
2993
  }
1599
2994
 
2995
+ /** Plain text for a usage-limit stop whose marker carried no readable amounts. */
2996
+ const SESSION_USAGE_LIMIT_STOP_TEXT =
2997
+ "This task's usage limit is reached. A person can raise or remove the limit to continue.";
2998
+
2999
+ /**
3000
+ * The stop row for a `segmentLimit: "session_usage_limit"` completion. Only the
3001
+ * typed amounts are kept (never upstream prose), and a malformed marker still
3002
+ * reads as the same neutral stop instead of a credit or failure notice.
3003
+ */
3004
+ function sessionUsageLimitNotice(
3005
+ event: SessionEvent,
3006
+ payload: Record<string, unknown>,
3007
+ ): NoticeItem {
3008
+ const refusal = parseSessionUsageLimitReachedRefusal(payload);
3009
+ return {
3010
+ kind: "notice",
3011
+ id: `${event.id}-session-usage-limit`,
3012
+ // Neutral: not a failure, and not an open wait the turn header should claim.
3013
+ tone: "cancelled",
3014
+ text: refusal ? sessionUsageLimitReachedMessage(refusal) : SESSION_USAGE_LIMIT_STOP_TEXT,
3015
+ sessionUsageLimit: {
3016
+ rootSessionId: refusal?.rootSessionId ?? null,
3017
+ limitMicros: refusal?.limitMicros ?? null,
3018
+ usedMicros: refusal?.usedMicros ?? null,
3019
+ },
3020
+ occurredAt: event.occurredAt,
3021
+ };
3022
+ }
3023
+
1600
3024
  function hasTurnActivity(items: TimelineItem[], turnId: string | null): boolean {
1601
3025
  if (turnId) {
1602
3026
  return items.some((item) => isActivityItem(item) && item.turnId === turnId);
@@ -1678,14 +3102,58 @@ function foldSettledTurn(groups: TimelineGroup[], turnEnd: TurnEndItem): void {
1678
3102
  if (collected.length === 0) {
1679
3103
  return;
1680
3104
  }
1681
-
1682
- const finalMessage = extractFinalAgentMessage(collected, turnEnd);
3105
+ const collectedLength = collected.length;
3106
+
3107
+ // Resolve only inside this turn's collected suffix. A user/machine-input
3108
+ // boundary or an interleaved foreign turn must never donate its answer.
3109
+ const waitMessages = turnEnd.preserveWaitResponse
3110
+ ? collected.filter(
3111
+ (group): group is Extract<TimelineGroup, { kind: "item" }> =>
3112
+ group.kind === "item" &&
3113
+ group.item.kind === "agent-message" &&
3114
+ !group.item.streaming &&
3115
+ group.item.text.trim().length > 0 &&
3116
+ belongsToTurn(group.item, turnEnd.turnId),
3117
+ )
3118
+ : [];
3119
+ // A completed answer outranks later commentary. Otherwise use the latest
3120
+ // visible existing prose, ignoring whitespace and opaque-only stream tails.
3121
+ const waitResponse =
3122
+ waitMessages
3123
+ .slice()
3124
+ .reverse()
3125
+ .find(
3126
+ (group) =>
3127
+ group.item.kind === "agent-message" &&
3128
+ group.item.phase !== "commentary" &&
3129
+ group.item.annotationSource?.eventType === "agent.message.completed",
3130
+ ) ?? waitMessages.at(-1);
3131
+ const finalMessage = waitResponse ?? extractFinalAgentMessage(collected, turnEnd);
1683
3132
  const fallbackMessage =
1684
3133
  finalMessage || hasOrdinaryFinalAgentMessage(collected, turnEnd)
1685
3134
  ? null
1686
3135
  : extractLatestCompletedCommentary(collected, turnEnd);
1687
3136
  const visibleMessage = finalMessage ?? fallbackMessage;
1688
- const body = visibleMessage ? collected.filter((group) => group !== visibleMessage) : collected;
3137
+ const visibleMessages = visibleMessage ? [visibleMessage] : [];
3138
+ // A turn that answers a person but declares no final answer (typically one
3139
+ // that yields to a wait) keeps its first message visible beside the latest:
3140
+ // that is the immediate answer, not progress narration.
3141
+ if (
3142
+ visibleMessage &&
3143
+ !hasOrdinaryFinalAgentMessage(collected, turnEnd) &&
3144
+ turnAnswersPerson(groups, startIndex)
3145
+ ) {
3146
+ const first = collected.find(
3147
+ (group): group is Extract<TimelineGroup, { kind: "item" }> =>
3148
+ group.kind === "item" &&
3149
+ group.item.kind === "agent-message" &&
3150
+ !group.item.streaming &&
3151
+ group.item.text.trim().length > 0 &&
3152
+ belongsToTurn(group.item, turnEnd.turnId),
3153
+ );
3154
+ if (first && first !== visibleMessage) visibleMessages.unshift(first);
3155
+ }
3156
+ const body = collected.filter((group) => !visibleMessages.includes(group as never));
1689
3157
  if (body.length === 0) {
1690
3158
  return;
1691
3159
  }
@@ -1711,11 +3179,25 @@ function foldSettledTurn(groups: TimelineGroup[], turnEnd: TurnEndItem): void {
1711
3179
  turnGroup.failureText = turnEnd.failureText;
1712
3180
  }
1713
3181
 
1714
- groups.splice(
1715
- startIndex,
1716
- collected.length,
1717
- ...(visibleMessage ? [turnGroup, visibleMessage] : [turnGroup]),
1718
- );
3182
+ groups.splice(startIndex, collectedLength, turnGroup, ...visibleMessages);
3183
+ }
3184
+
3185
+ /** Whether the input boundary before a turn includes a person's message. */
3186
+ function turnAnswersPerson(groups: TimelineGroup[], startIndex: number): boolean {
3187
+ for (let index = startIndex - 1; index >= 0; index -= 1) {
3188
+ const group = groups[index];
3189
+ if (group?.kind !== "item") return false;
3190
+ if (group.item.kind === "user-message") return true;
3191
+ if (
3192
+ group.item.kind !== "machine-input-batch" &&
3193
+ group.item.kind !== "session-status" &&
3194
+ group.item.kind !== "context-compaction" &&
3195
+ !(group.item.kind === "notice" && group.item.tone === "input")
3196
+ ) {
3197
+ return false;
3198
+ }
3199
+ }
3200
+ return false;
1719
3201
  }
1720
3202
 
1721
3203
  function isTurnBoundary(group: TimelineGroup | undefined): boolean {
@@ -1725,8 +3207,7 @@ function isTurnBoundary(group: TimelineGroup | undefined): boolean {
1725
3207
  (group.item.kind === "user-message" ||
1726
3208
  group.item.kind === "human-input" ||
1727
3209
  group.item.kind === "context-compaction" ||
1728
- (group.item.kind === "machine-input-batch" &&
1729
- group.item.members.some((member) => member.kind === "media_generation_result")) ||
3210
+ group.item.kind === "machine-input-batch" ||
1730
3211
  (group.item.kind === "notice" && group.item.tone === "input")))
1731
3212
  );
1732
3213
  }
@@ -1838,6 +3319,29 @@ function numberOrNull(value: unknown): number | null {
1838
3319
  return typeof value === "number" && Number.isFinite(value) ? value : null;
1839
3320
  }
1840
3321
 
3322
+ /**
3323
+ * Read the closed provider-rejection record a `summarization_failed` skip
3324
+ * carries. Only bounded identifiers are accepted; a provider message is never
3325
+ * part of the payload and would be ignored here anyway.
3326
+ */
3327
+ function compactionProviderRejection(
3328
+ payload: Record<string, unknown>,
3329
+ ): ContextCompactionItem["providerRejection"] {
3330
+ const record = asRecord(payload.providerRejection);
3331
+ if (typeof record.httpStatus !== "number" || !Number.isFinite(record.httpStatus)) {
3332
+ return null;
3333
+ }
3334
+ const text = (value: unknown): string | null =>
3335
+ typeof value === "string" && value.trim() ? value : null;
3336
+ return {
3337
+ httpStatus: record.httpStatus,
3338
+ type: text(record.type),
3339
+ code: text(record.code),
3340
+ param: text(record.param),
3341
+ requestId: text(record.requestId),
3342
+ };
3343
+ }
3344
+
1841
3345
  function compactionTrigger(payload: Record<string, unknown>): ContextCompactionItem["trigger"] {
1842
3346
  const trigger = payload.trigger;
1843
3347
  return trigger === "auto" ||
@@ -1893,6 +3397,8 @@ function machineInputMembers(value: unknown): MachineInputBatchItem["members"] {
1893
3397
  const kinds = new Set<MachineInputBatchItem["members"][number]["kind"]>([
1894
3398
  "scheduled_occurrence",
1895
3399
  "goal_continuation",
3400
+ "background_command_result",
3401
+ "session_wait_timeout",
1896
3402
  "agent_message",
1897
3403
  "agent_steer_instruction",
1898
3404
  "child_terminal_result",
@@ -1948,6 +3454,8 @@ const SESSION_STATUSES: readonly SessionStatus[] = [
1948
3454
  "running",
1949
3455
  "idle",
1950
3456
  "requires_action",
3457
+ "recovering",
3458
+ "waiting_capacity",
1951
3459
  "failed",
1952
3460
  "cancelled",
1953
3461
  ];
@@ -1973,6 +3481,15 @@ function resourceRefs(value: unknown): import("@opengeni/sdk").ResourceRef[] {
1973
3481
  });
1974
3482
  }
1975
3483
 
3484
+ /** The person behind a message, when its initiator is a signed-in subject. */
3485
+ function messageSender(value: unknown): import("./types").MessageSender | undefined {
3486
+ const record = asRecord(value);
3487
+ if (record.kind !== "subject" || typeof record.subjectId !== "string" || !record.subjectId)
3488
+ return undefined;
3489
+ const label = typeof record.label === "string" && record.label.trim() ? record.label : null;
3490
+ return { subjectId: record.subjectId, label };
3491
+ }
3492
+
1976
3493
  function timelineAnnotations(value: unknown): TimelineAnnotation[] {
1977
3494
  if (!Array.isArray(value)) return [];
1978
3495
  return value.filter((candidate): candidate is TimelineAnnotation => {
@@ -2120,6 +3637,34 @@ function startupPhaseFromPayload(value: unknown): StartupPhase | null {
2120
3637
  : null;
2121
3638
  }
2122
3639
 
3640
+ function startupLifecycleWaitReason(
3641
+ payload: Record<string, unknown>,
3642
+ ): StartupPhaseItem["blockedReason"] {
3643
+ // The first model request waited for context compaction: an expected step
3644
+ // of preparation that reopens after compacting, never a failure.
3645
+ if (
3646
+ payload.expectedTransition === true &&
3647
+ payload.failureStage === "context_compaction" &&
3648
+ payload.failureCategory === "context_compaction" &&
3649
+ payload.failureCode === "compaction_required"
3650
+ ) {
3651
+ return "compaction_required";
3652
+ }
3653
+ if (
3654
+ payload.expectedTransition !== true ||
3655
+ payload.failureStage !== "lifecycle_wait" ||
3656
+ payload.failureCategory !== "drain_capture_wait"
3657
+ ) {
3658
+ return undefined;
3659
+ }
3660
+ const code = payload.failureCode;
3661
+ return code === "capture_in_progress" ||
3662
+ code === "rotation_in_progress" ||
3663
+ code === "provider_recovery_in_progress"
3664
+ ? code
3665
+ : undefined;
3666
+ }
3667
+
2123
3668
  const SANDBOX_STARTUP_PHASES: Record<string, StartupPhase> = {
2124
3669
  "sandbox.provision": "sandbox",
2125
3670
  "repository-clone": "repository",
@@ -2143,15 +3688,7 @@ function elapsedDurationMs(startedAt: string, completedAt: string): number | nul
2143
3688
  }
2144
3689
 
2145
3690
  function failureMessage(payload: Record<string, unknown>): string | null {
2146
- for (const key of ["error", "message"] as const) {
2147
- const value = payload[key];
2148
- if (typeof value === "string" && value.trim().length > 0) {
2149
- // Auth/quota provider errors are rewritten for the right audience
2150
- // (raw text remains in the event payload for debug surfaces).
2151
- return humanizeFailureReason(value);
2152
- }
2153
- }
2154
- return null;
3691
+ return presentFailure(payload).reason;
2155
3692
  }
2156
3693
 
2157
3694
  function goalText(payload: Record<string, unknown>): string | null {
@@ -2168,11 +3705,108 @@ function goalText(payload: Record<string, unknown>): string | null {
2168
3705
  return null;
2169
3706
  }
2170
3707
 
3708
+ function rememberPendingWaitOutcome(
3709
+ pending: Map<string | null, PendingWaitOutcome>,
3710
+ event: SessionEvent,
3711
+ payload: Record<string, unknown>,
3712
+ eventTurnId: string | null,
3713
+ ): void {
3714
+ const reason = stringValue(payload.reason).trim();
3715
+ if (!reason) return;
3716
+ const turnId =
3717
+ eventTurnId || stringValue(payload.waitTurnId) || stringValue(payload.turnId) || null;
3718
+ pending.set(turnId, { id: event.id, reason, occurredAt: event.occurredAt });
3719
+ }
3720
+
3721
+ /** Wait reasons that need a person or a policy change, not only time. */
3722
+ const ACTION_CAPACITY_WAIT_REASONS: ReadonlySet<string> = new Set([
3723
+ "pinned_account_disabled",
3724
+ "pinned_account_ineligible",
3725
+ "model_not_allowed",
3726
+ // Only sending the work again ends it (design 5.3 decision 4).
3727
+ "accepted_authority_unavailable",
3728
+ ]);
3729
+
3730
+ /**
3731
+ * Recorded wait codes, for any subscription provider, that a person must
3732
+ * resolve: every account disabled for allocation, or the serving account
3733
+ * needing reconnection or refusing the request. None of them is a limit.
3734
+ */
3735
+ const ACTION_CAPACITY_WAIT_CODE = /_(?:allocator_disabled|relogin_required|account_forbidden)$/;
3736
+
3737
+ /**
3738
+ * A quota or capacity wait reads as "Usage limit reached" with one plain line
3739
+ * of guidance. It never promises that the work continues by itself: the
3740
+ * reader decides whether to wait for the quota to reset or to switch. Only a
3741
+ * wait that a person must resolve (reconnect, re-enable, choose another
3742
+ * account) keeps its recorded, actionable reason and is not labelled a limit.
3743
+ */
3744
+ function capacityWaitPresentation(
3745
+ type: string,
3746
+ payload: Record<string, unknown>,
3747
+ turnId: string | null,
3748
+ ): { turnId: string | null; label: string; detail: string; text: string } {
3749
+ if (
3750
+ ACTION_CAPACITY_WAIT_REASONS.has(stringValue(payload.waitReason)) ||
3751
+ ACTION_CAPACITY_WAIT_CODE.test(stringValue(payload.code))
3752
+ ) {
3753
+ // Claude/SuperGrok waits record the readable sentence as `error` and an
3754
+ // internal note as `detail`; Codex waits keep their recorded order.
3755
+ const [first, second] =
3756
+ type === "turn.capacity_waiting"
3757
+ ? [payload.error, payload.detail]
3758
+ : [payload.detail, payload.error];
3759
+ const detail = stringValue(first) || stringValue(second) || "Waiting for an account.";
3760
+ return {
3761
+ turnId,
3762
+ label:
3763
+ stringValue(payload.waitReason) === "pinned_account_disabled" ? "Needs you" : "Waiting",
3764
+ detail,
3765
+ text: detail,
3766
+ };
3767
+ }
3768
+ return {
3769
+ turnId,
3770
+ label: CAPACITY_LIMIT_LABEL,
3771
+ detail: CAPACITY_LIMIT_DETAIL,
3772
+ text: `${CAPACITY_LIMIT_LABEL}. ${CAPACITY_LIMIT_DETAIL}`,
3773
+ };
3774
+ }
3775
+
3776
+ const CAPACITY_LIMIT_LABEL = "Usage limit reached";
3777
+ const CAPACITY_LIMIT_DETAIL =
3778
+ "Try again after your quota resets, or switch to another model or provider.";
3779
+
3780
+ /** Lifecycle evidence that a capacity wait is over. */
3781
+ function endsCapacityWait(event: SessionEvent, payload: Record<string, unknown>): boolean {
3782
+ if (event.duplicateOfEventId || (event.turnAssociation && event.turnAssociation !== "current"))
3783
+ return false;
3784
+ switch (event.type) {
3785
+ case "codex.capacity.resumed":
3786
+ case "codex.capacity.superseded":
3787
+ case "turn.completed":
3788
+ case "turn.failed":
3789
+ case "turn.cancelled":
3790
+ case "turn.superseded":
3791
+ return true;
3792
+ case "session.status.changed":
3793
+ return isSessionStatus(payload.status) && payload.status !== "waiting_capacity";
3794
+ default:
3795
+ return false;
3796
+ }
3797
+ }
3798
+
3799
+ function waitingOutcomeText(reason: string): string {
3800
+ return /^waiting\b/i.test(reason) ? reason : `Waiting: ${reason}`;
3801
+ }
3802
+
2171
3803
  /**
2172
3804
  * Agent-owned goal mutations already have an in-cluster tool row. Suppress the
2173
- * breakaway landmark for those only. `goal.completed` has no actor field today
2174
- * and is only emitted by the agent tool, so it is always suppressed. API /
2175
- * system / create-session / continuation landmarks stay visible.
3805
+ * breakaway landmark for those only. Empty wait turns retain the durable hold
3806
+ * reason and surface it after the folded steps. `goal.completed` has no actor
3807
+ * field today and is only emitted by the agent tool, so it is always
3808
+ * suppressed. API / system / create-session / continuation landmarks stay
3809
+ * visible.
2176
3810
  */
2177
3811
  function shouldSuppressAgentGoalLandmark(type: string, payload: Record<string, unknown>): boolean {
2178
3812
  if (type === "goal.completed") {
@@ -2235,6 +3869,7 @@ const AUTH_NEEDED_REASONS: ReadonlySet<string> = new Set([
2235
3869
  "personal_authority_unavailable",
2236
3870
  "unsupported_auth",
2237
3871
  "resource_scope_unavailable",
3872
+ "designated_credential_unavailable",
2238
3873
  ]);
2239
3874
 
2240
3875
  function authNeededReason(value: unknown): AuthNeededItem["reason"] {
@@ -2254,7 +3889,7 @@ function capabilityAuthorizationRequest(
2254
3889
  if (
2255
3890
  typeof record.id !== "string" ||
2256
3891
  typeof record.name !== "string" ||
2257
- !["pack", "mcp", "api", "skill", "plugin"].includes(String(kind)) ||
3892
+ !["mcp", "api", "skill", "plugin"].includes(String(kind)) ||
2258
3893
  !["built_in", "library", "configured", "public_registry", "registry", "manual"].includes(
2259
3894
  String(source),
2260
3895
  ) ||
@@ -2274,12 +3909,30 @@ function capabilityAuthorizationRequest(
2274
3909
  };
2275
3910
  }
2276
3911
 
3912
+ function customMcpSetupRequest(value: unknown): NonNullable<AuthNeededItem["setupRequest"]> | null {
3913
+ return parseCustomMcpSetupRequest(value);
3914
+ }
3915
+
2277
3916
  function stringList(value: unknown): string[] {
2278
3917
  return Array.isArray(value)
2279
3918
  ? value.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0)
2280
3919
  : [];
2281
3920
  }
2282
3921
 
3922
+ /**
3923
+ * Some providers stream each reasoning summary part as its own delta
3924
+ * ("**Checking the workspace**", then "**Inspecting guidance**"). Joined
3925
+ * as-is the parts run together and their bold markers collide ("****"), so a
3926
+ * new part that opens bold right after one that closed bold starts a new
3927
+ * paragraph. Ordinary token deltas are joined unchanged. The worker's
3928
+ * write-time merge and the read-side coalescer apply the same rule
3929
+ * (`reasoningDeltaSeparator` in @opengeni/events), so stored, coalesced and
3930
+ * live fragments render identically.
3931
+ */
3932
+ function reasoningDeltaJoiner(previous: string, next: string): string {
3933
+ return previous.endsWith("**") && next.startsWith("**") ? "\n\n" : "";
3934
+ }
3935
+
2283
3936
  function reasoningText(payload: unknown): string {
2284
3937
  const record = asRecord(payload);
2285
3938
  if (typeof record.text === "string") {
@@ -2309,6 +3962,19 @@ function workerPrompt(args: unknown): string | null {
2309
3962
  return null;
2310
3963
  }
2311
3964
 
3965
+ const WORKER_TITLE_MAX_LENGTH = 120;
3966
+
3967
+ /** The optional `session_create` title, as one bounded display line. */
3968
+ function workerTitle(args: unknown): string | null {
3969
+ const record = asRecord(typeof args === "string" ? tryParseJson(args) : args);
3970
+ if (typeof record.title !== "string") return null;
3971
+ const title = record.title.replace(/\s+/g, " ").trim();
3972
+ if (!title) return null;
3973
+ return title.length > WORKER_TITLE_MAX_LENGTH
3974
+ ? `${title.slice(0, WORKER_TITLE_MAX_LENGTH - 1).trimEnd()}…`
3975
+ : title;
3976
+ }
3977
+
2312
3978
  function boundedWorkerFailureMessage(value: string): string | null {
2313
3979
  const normalized = value.replace(/[\u0000-\u001f\u007f]+/g, " ").trim();
2314
3980
  if (!normalized) return null;