@opengeni/react 1.0.1 → 1.0.2-canary.37191796234001

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (629) hide show
  1. package/README.md +651 -21
  2. package/dist/accounts.d.ts +64 -0
  3. package/dist/accounts.js +822 -0
  4. package/dist/accounts.js.map +1 -0
  5. package/dist/approvals.d.ts +2 -1
  6. package/dist/artifacts-document.js +3 -2
  7. package/dist/artifacts-presentation.js +3 -2
  8. package/dist/artifacts-spreadsheet.js +2 -2
  9. package/dist/artifacts.d.ts +4 -1
  10. package/dist/artifacts.js +518 -42
  11. package/dist/artifacts.js.map +1 -1
  12. package/dist/browser-viewer-GTR5XQ7X.js +16 -0
  13. package/dist/capability-catalog-row.d.ts +20 -0
  14. package/dist/capability-logo-fallback.d.ts +3 -0
  15. package/dist/chat-interactive-block-UGBTNB3L.js +11 -0
  16. package/dist/chat-interactive-block-UGBTNB3L.js.map +1 -0
  17. package/dist/{chunk-5UQPNLIO.js → chunk-23J2ZNXT.js} +4117 -3896
  18. package/dist/chunk-23J2ZNXT.js.map +1 -0
  19. package/dist/chunk-447JLCBU.js +209 -0
  20. package/dist/chunk-447JLCBU.js.map +1 -0
  21. package/dist/chunk-4RVYOY4O.js +379 -0
  22. package/dist/chunk-4RVYOY4O.js.map +1 -0
  23. package/dist/chunk-5CVXZA3Y.js +292 -0
  24. package/dist/chunk-5CVXZA3Y.js.map +1 -0
  25. package/dist/chunk-5J4FOW6Q.js +108 -0
  26. package/dist/chunk-5J4FOW6Q.js.map +1 -0
  27. package/dist/chunk-A2PI6UMY.js +41 -0
  28. package/dist/chunk-A2PI6UMY.js.map +1 -0
  29. package/dist/{chunk-7WCLAEFV.js → chunk-A4UJB2MM.js} +37 -27
  30. package/dist/chunk-A4UJB2MM.js.map +1 -0
  31. package/dist/chunk-B3V6TG77.js +41 -0
  32. package/dist/chunk-B3V6TG77.js.map +1 -0
  33. package/dist/chunk-BRM6FVZA.js +302 -0
  34. package/dist/chunk-BRM6FVZA.js.map +1 -0
  35. package/dist/chunk-C5X5AJXK.js +182 -0
  36. package/dist/chunk-C5X5AJXK.js.map +1 -0
  37. package/dist/chunk-CR7U7HAR.js +453 -0
  38. package/dist/chunk-CR7U7HAR.js.map +1 -0
  39. package/dist/{chunk-R3AZOAB5.js → chunk-DYWBUX5Q.js} +29 -123
  40. package/dist/chunk-DYWBUX5Q.js.map +1 -0
  41. package/dist/chunk-E3G7SAYZ.js +3124 -0
  42. package/dist/chunk-E3G7SAYZ.js.map +1 -0
  43. package/dist/chunk-ELCE5SXV.js +359 -0
  44. package/dist/chunk-ELCE5SXV.js.map +1 -0
  45. package/dist/chunk-ERGYJIBB.js +21 -0
  46. package/dist/chunk-ERGYJIBB.js.map +1 -0
  47. package/dist/chunk-FFA2OVOV.js +319 -0
  48. package/dist/chunk-FFA2OVOV.js.map +1 -0
  49. package/dist/chunk-FZCP527Y.js +116 -0
  50. package/dist/chunk-FZCP527Y.js.map +1 -0
  51. package/dist/chunk-GIMRELBW.js +14 -0
  52. package/dist/chunk-GIMRELBW.js.map +1 -0
  53. package/dist/{chunk-6KLX2RH5.js → chunk-H2NJQORO.js} +1069 -79
  54. package/dist/chunk-H2NJQORO.js.map +1 -0
  55. package/dist/chunk-I7IWRQLN.js +1608 -0
  56. package/dist/chunk-I7IWRQLN.js.map +1 -0
  57. package/dist/{chunk-HA46CYQK.js → chunk-IQUCRAT7.js} +792 -537
  58. package/dist/chunk-IQUCRAT7.js.map +1 -0
  59. package/dist/chunk-IWIDV7SB.js +1 -0
  60. package/dist/chunk-IWIDV7SB.js.map +1 -0
  61. package/dist/chunk-J2QWVSQY.js +312 -0
  62. package/dist/chunk-J2QWVSQY.js.map +1 -0
  63. package/dist/chunk-JFNEV4LU.js +2733 -0
  64. package/dist/chunk-JFNEV4LU.js.map +1 -0
  65. package/dist/chunk-JT4JW7BC.js +54 -0
  66. package/dist/chunk-JT4JW7BC.js.map +1 -0
  67. package/dist/chunk-K3HQAKQX.js +24 -0
  68. package/dist/chunk-K3HQAKQX.js.map +1 -0
  69. package/dist/{chunk-C5ZPSSS5.js → chunk-NEXLLTP4.js} +2 -1
  70. package/dist/{chunk-C5ZPSSS5.js.map → chunk-NEXLLTP4.js.map} +1 -1
  71. package/dist/chunk-NZUI76EM.js +371 -0
  72. package/dist/chunk-NZUI76EM.js.map +1 -0
  73. package/dist/{chunk-PEI6RANI.js → chunk-PNY3JKBJ.js} +309 -74
  74. package/dist/chunk-PNY3JKBJ.js.map +1 -0
  75. package/dist/{chunk-IGC6A2ZI.js → chunk-Q5QBEZIQ.js} +777 -246
  76. package/dist/chunk-Q5QBEZIQ.js.map +1 -0
  77. package/dist/chunk-RHREZ7BO.js +11 -0
  78. package/dist/chunk-RHREZ7BO.js.map +1 -0
  79. package/dist/chunk-SCOTPE2I.js +589 -0
  80. package/dist/chunk-SCOTPE2I.js.map +1 -0
  81. package/dist/{chunk-B6WPZSTT.js → chunk-SJZDKVW2.js} +1331 -1163
  82. package/dist/chunk-SJZDKVW2.js.map +1 -0
  83. package/dist/chunk-TMJRG4YC.js +12803 -0
  84. package/dist/chunk-TMJRG4YC.js.map +1 -0
  85. package/dist/chunk-TMYGL6X5.js +742 -0
  86. package/dist/chunk-TMYGL6X5.js.map +1 -0
  87. package/dist/chunk-UGLEYEMP.js +33 -0
  88. package/dist/chunk-UGLEYEMP.js.map +1 -0
  89. package/dist/chunk-UK4TINKT.js +27 -0
  90. package/dist/chunk-UK4TINKT.js.map +1 -0
  91. package/dist/chunk-UTRTG6LH.js +220 -0
  92. package/dist/chunk-UTRTG6LH.js.map +1 -0
  93. package/dist/chunk-WEHMPOXK.js +39 -0
  94. package/dist/chunk-WEHMPOXK.js.map +1 -0
  95. package/dist/chunk-WRHIZYZF.js +988 -0
  96. package/dist/chunk-WRHIZYZF.js.map +1 -0
  97. package/dist/{chunk-HUIKIDM5.js → chunk-WUISZ3WU.js} +223 -115
  98. package/dist/chunk-WUISZ3WU.js.map +1 -0
  99. package/dist/chunk-XCHWDJ22.js +107 -0
  100. package/dist/chunk-XCHWDJ22.js.map +1 -0
  101. package/dist/chunk-XOAMRDDK.js +481 -0
  102. package/dist/chunk-XOAMRDDK.js.map +1 -0
  103. package/dist/chunk-XUW2UWQM.js +299 -0
  104. package/dist/chunk-XUW2UWQM.js.map +1 -0
  105. package/dist/{chunk-KCGXPP5V.js → chunk-Y2PRMYLC.js} +11 -6
  106. package/dist/chunk-Y2PRMYLC.js.map +1 -0
  107. package/dist/chunk-YBXQDQFX.js +1772 -0
  108. package/dist/chunk-YBXQDQFX.js.map +1 -0
  109. package/dist/{chunk-KZ73RL76.js → chunk-YQEAA6LT.js} +7 -3
  110. package/dist/chunk-YQEAA6LT.js.map +1 -0
  111. package/dist/chunk-YV4L3ILF.js +9 -0
  112. package/dist/chunk-YV4L3ILF.js.map +1 -0
  113. package/dist/{chunk-LLOLUHVG.js → chunk-YWFG7ZX7.js} +1358 -2746
  114. package/dist/chunk-YWFG7ZX7.js.map +1 -0
  115. package/dist/chunk-ZYRGEKWS.js +216 -0
  116. package/dist/chunk-ZYRGEKWS.js.map +1 -0
  117. package/dist/client.d.ts +7 -4
  118. package/dist/clipboard.js +1 -0
  119. package/dist/commands/types.d.ts +2 -0
  120. package/dist/components/anthropic-mark.d.ts +3 -0
  121. package/dist/components/approval-surface.d.ts +15 -0
  122. package/dist/components/artifacts/artifact-chrome.d.ts +120 -0
  123. package/dist/components/artifacts/artifact-sandbox.d.ts +27 -0
  124. package/dist/components/artifacts/artifact-surface.d.ts +3 -1
  125. package/dist/components/artifacts/chat-interactive-block.d.ts +29 -0
  126. package/dist/components/artifacts/deferred-chat-media.d.ts +15 -0
  127. package/dist/components/artifacts/document-editor.d.ts +4 -2
  128. package/dist/components/artifacts/editable-artifact-ui.d.ts +2 -1
  129. package/dist/components/artifacts/editable-artifact-view.d.ts +46 -0
  130. package/dist/components/artifacts/editable-document.d.ts +1 -1
  131. package/dist/components/artifacts/editable-presentation.d.ts +2 -2
  132. package/dist/components/artifacts/editable-spreadsheet.d.ts +2 -1
  133. package/dist/components/artifacts/index.d.ts +8 -1
  134. package/dist/components/artifacts/inline-html-document.d.ts +2 -0
  135. package/dist/components/artifacts/latest-task-runner.d.ts +15 -0
  136. package/dist/components/artifacts/presentation-editor.d.ts +6 -2
  137. package/dist/components/artifacts/published-html-artifact-frame.d.ts +48 -3
  138. package/dist/components/artifacts/session-artifact-viewer.d.ts +39 -0
  139. package/dist/components/artifacts/site-view.d.ts +23 -0
  140. package/dist/components/artifacts/spreadsheet-general-display.d.ts +24 -0
  141. package/dist/components/artifacts/visualization-assets.gen.d.ts +2 -0
  142. package/dist/components/browser-select-control.d.ts +16 -0
  143. package/dist/components/browser-viewer.d.ts +10 -1
  144. package/dist/components/chat-composer.d.ts +21 -3
  145. package/dist/components/child-session-link.d.ts +5 -0
  146. package/dist/components/claude-mark.d.ts +3 -0
  147. package/dist/components/code-editor.d.ts +1 -4
  148. package/dist/components/composer.d.ts +45 -4
  149. package/dist/components/computer-viewer.d.ts +4 -1
  150. package/dist/components/copy-button.d.ts +3 -1
  151. package/dist/components/desktop-viewer.d.ts +1 -1
  152. package/dist/components/enrollment-consent.d.ts +6 -3
  153. package/dist/components/file-browser.d.ts +10 -1
  154. package/dist/components/fleet-tile.d.ts +2 -2
  155. package/dist/components/grok-mark.d.ts +26 -0
  156. package/dist/components/human-input-form.d.ts +21 -2
  157. package/dist/components/human-input-surface.d.ts +6 -3
  158. package/dist/components/machine-card.d.ts +4 -1
  159. package/dist/components/machines/machine-detail.d.ts +4 -1
  160. package/dist/components/machines/machine-operation-policy.d.ts +11 -0
  161. package/dist/components/machines-dashboard.d.ts +3 -1
  162. package/dist/components/markdown-table-layout.d.ts +12 -0
  163. package/dist/components/markdown.d.ts +47 -3
  164. package/dist/components/message-timeline.d.ts +72 -9
  165. package/dist/components/model-mark.d.ts +29 -0
  166. package/dist/components/model-policy-picker-menu.d.ts +9 -0
  167. package/dist/components/model-policy-picker.d.ts +62 -13
  168. package/dist/components/open-geni-chat.d.ts +55 -0
  169. package/dist/components/open-geni-links.d.ts +68 -0
  170. package/dist/components/openrouter-mark.d.ts +6 -0
  171. package/dist/components/pierre-file.d.ts +1 -1
  172. package/dist/components/preview-loading.d.ts +2 -0
  173. package/dist/components/queue-item-content.d.ts +9 -0
  174. package/dist/components/sandbox-files.d.ts +13 -2
  175. package/dist/components/sandbox-workspace.d.ts +43 -3
  176. package/dist/components/session-capability-frame.d.ts +35 -0
  177. package/dist/components/session-chrome.d.ts +66 -8
  178. package/dist/components/session-commands-panel.d.ts +6 -0
  179. package/dist/components/session-commands.d.ts +6 -0
  180. package/dist/components/session-connection-request.d.ts +8 -0
  181. package/dist/components/session-conversation.d.ts +86 -0
  182. package/dist/components/session-list.d.ts +36 -0
  183. package/dist/components/session-mcp-capability-card.d.ts +41 -0
  184. package/dist/components/timeline-anchor.d.ts +24 -0
  185. package/dist/components/timeline-annotation-chrome.d.ts +25 -0
  186. package/dist/components/timeline-annotation-layout.d.ts +42 -0
  187. package/dist/components/timeline-annotation-markers.d.ts +8 -0
  188. package/dist/components/timeline-annotation-reveal-context.d.ts +4 -0
  189. package/dist/components/timeline-annotation-shared.d.ts +31 -0
  190. package/dist/components/timeline-annotations-dialog.d.ts +3 -2
  191. package/dist/components/timeline-annotations.d.ts +13 -3
  192. package/dist/components/timeline-reading-progress.d.ts +11 -0
  193. package/dist/components/timeline-search.d.ts +34 -0
  194. package/dist/components/timeline-settlement.d.ts +16 -0
  195. package/dist/components/tip-follow.d.ts +16 -0
  196. package/dist/components/usage-limit-notice.d.ts +49 -0
  197. package/dist/components/usage-member-list.d.ts +91 -0
  198. package/dist/components/usage-meter.d.ts +73 -0
  199. package/dist/components/use-viewer-menu-dismiss.d.ts +2 -0
  200. package/dist/components/user-message-body.d.ts +10 -1
  201. package/dist/components/workspace-dock.d.ts +6 -2
  202. package/dist/composer.d.ts +5 -4
  203. package/dist/composer.js +15 -9
  204. package/dist/computer-viewer-ENHVAZ2Q.js +19 -0
  205. package/dist/computer-viewer-ENHVAZ2Q.js.map +1 -0
  206. package/dist/connect-account-identity.d.ts +8 -0
  207. package/dist/connect-accounts.d.ts +15 -0
  208. package/dist/connect-chooser.d.ts +18 -0
  209. package/dist/connect-panel.d.ts +18 -0
  210. package/dist/connect-setup.d.ts +16 -0
  211. package/dist/connect.d.ts +21 -0
  212. package/dist/connect.js +2129 -0
  213. package/dist/connect.js.map +1 -0
  214. package/dist/connection-catalog.d.ts +38 -0
  215. package/dist/connection-discovery.d.ts +12 -0
  216. package/dist/connection-installed.d.ts +17 -0
  217. package/dist/connection-logo.d.ts +9 -0
  218. package/dist/connection-service-logo.d.ts +8 -0
  219. package/dist/connection-service-presentation.d.ts +13 -0
  220. package/dist/connection-skeleton.d.ts +5 -0
  221. package/dist/connection-type-picker.d.ts +6 -0
  222. package/dist/conversation-timeline.d.ts +5 -0
  223. package/dist/desktop.d.ts +7 -0
  224. package/dist/desktop.js +26 -0
  225. package/dist/desktop.js.map +1 -0
  226. package/dist/device-authorization.d.ts +23 -0
  227. package/dist/diffs.d.ts +8 -0
  228. package/dist/diffs.js +14 -0
  229. package/dist/diffs.js.map +1 -0
  230. package/dist/discovery-cache.d.ts +5 -0
  231. package/dist/editor.d.ts +7 -0
  232. package/dist/editor.js +21 -0
  233. package/dist/editor.js.map +1 -0
  234. package/dist/embedded-session-client.d.ts +23 -0
  235. package/dist/file-node-visibility.d.ts +15 -0
  236. package/dist/{fleet-decision-row-GGCAT4E4.js → fleet-decision-row-YZ3ZKWMM.js} +2 -1
  237. package/dist/fleet-decision-row-YZ3ZKWMM.js.map +1 -0
  238. package/dist/hooks/channel-cache.d.ts +23 -0
  239. package/dist/hooks/latest-question.d.ts +25 -0
  240. package/dist/hooks/shared-machines-list.d.ts +14 -0
  241. package/dist/hooks/use-attached-browsers.d.ts +2 -1
  242. package/dist/hooks/use-browser-frame-stream.d.ts +1 -1
  243. package/dist/hooks/use-browser-session.d.ts +12 -1
  244. package/dist/hooks/use-channels.d.ts +1 -0
  245. package/dist/hooks/use-codex-accounts.d.ts +25 -7
  246. package/dist/hooks/use-composer.d.ts +45 -9
  247. package/dist/hooks/use-computer-frame-stream.d.ts +1 -1
  248. package/dist/hooks/use-computer-input-posture.d.ts +13 -0
  249. package/dist/hooks/use-computer-session.d.ts +2 -0
  250. package/dist/hooks/use-connect.d.ts +22 -0
  251. package/dist/hooks/use-desktop-stream.d.ts +1 -1
  252. package/dist/hooks/use-file-attachments.d.ts +21 -2
  253. package/dist/hooks/use-machine-chip.d.ts +3 -3
  254. package/dist/hooks/use-machines.d.ts +19 -1
  255. package/dist/hooks/use-sandbox-files.d.ts +5 -1
  256. package/dist/hooks/use-session-background-commands.d.ts +14 -0
  257. package/dist/hooks/use-session-events.d.ts +30 -8
  258. package/dist/hooks/use-session-lineage.d.ts +4 -0
  259. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  260. package/dist/hooks/use-session.d.ts +6 -0
  261. package/dist/hooks/use-turn-queue.d.ts +12 -0
  262. package/dist/hooks/use-usage.d.ts +40 -0
  263. package/dist/hooks/use-workspace-edit.d.ts +4 -0
  264. package/dist/hooks/use-workspace-sessions.d.ts +27 -5
  265. package/dist/human-input.d.ts +3 -1
  266. package/dist/identity-link-accounts.d.ts +11 -0
  267. package/dist/identity-link-consent.d.ts +18 -0
  268. package/dist/index.d.ts +60 -17
  269. package/dist/index.js +2237 -6200
  270. package/dist/index.js.map +1 -1
  271. package/dist/interaction.d.ts +1 -1
  272. package/dist/interaction.js +35 -184
  273. package/dist/interaction.js.map +1 -1
  274. package/dist/latest-question-755OW5DS.js +117 -0
  275. package/dist/latest-question-755OW5DS.js.map +1 -0
  276. package/dist/lib/error-message.d.ts +7 -0
  277. package/dist/lib/format.d.ts +36 -2
  278. package/dist/lib/host-theme.d.ts +70 -0
  279. package/dist/lib/interaction-errors.d.ts +6 -0
  280. package/dist/lib/menu-styles.d.ts +22 -0
  281. package/dist/lib/pierre-diffs-loader.d.ts +20 -0
  282. package/dist/lib/workbench-peers.d.ts +36 -0
  283. package/dist/lib/xterm-theme.d.ts +7 -5
  284. package/dist/machines.d.ts +4 -2
  285. package/dist/machines.js +14 -4
  286. package/dist/markdown-table-layout-QXSLK4BY.js +106 -0
  287. package/dist/markdown-table-layout-QXSLK4BY.js.map +1 -0
  288. package/dist/mcp-connection-status.d.ts +14 -0
  289. package/dist/model-policy-picker-menu-PWRI7R2X.js +14 -0
  290. package/dist/model-policy-picker-menu-PWRI7R2X.js.map +1 -0
  291. package/dist/model-policy.d.ts +19 -3
  292. package/dist/model-policy.js +9 -1
  293. package/dist/older-history.d.ts +30 -0
  294. package/dist/platform-activity-row-627BFZ46.js +182 -0
  295. package/dist/platform-activity-row-627BFZ46.js.map +1 -0
  296. package/dist/plugin-details.d.ts +14 -0
  297. package/dist/plugin-discovery.d.ts +25 -0
  298. package/dist/provider.d.ts +13 -1
  299. package/dist/{queue-surface-implementation-2KVJTYXH.js → queue-surface-implementation-ZHFI5JBP.js} +131 -44
  300. package/dist/queue-surface-implementation-ZHFI5JBP.js.map +1 -0
  301. package/dist/realtime/realtime-control.d.ts +6 -3
  302. package/dist/realtime.js +133 -74
  303. package/dist/realtime.js.map +1 -1
  304. package/dist/sandbox-files-BBC47SLD.js +20 -0
  305. package/dist/sandbox-files-BBC47SLD.js.map +1 -0
  306. package/dist/session-auth-recommendation.d.ts +5 -0
  307. package/dist/session-capability-policy.d.ts +12 -0
  308. package/dist/session-ui.d.ts +30 -2
  309. package/dist/session-ui.js +85 -7
  310. package/dist/session-ui.js.map +1 -1
  311. package/dist/session.d.ts +8 -2
  312. package/dist/session.js +30 -19
  313. package/dist/sites-ui.d.ts +34 -0
  314. package/dist/sites.d.ts +3 -0
  315. package/dist/sites.js +11 -0
  316. package/dist/sites.js.map +1 -0
  317. package/dist/skill-discovery.d.ts +47 -0
  318. package/dist/terminal.d.ts +8 -0
  319. package/dist/terminal.js +31 -0
  320. package/dist/terminal.js.map +1 -0
  321. package/dist/timeline/activity-rail.d.ts +11 -2
  322. package/dist/timeline/allowance-exhausted-row.d.ts +24 -0
  323. package/dist/timeline/compaction-copy.d.ts +10 -0
  324. package/dist/timeline/entrance.d.ts +13 -1
  325. package/dist/timeline/genie-loading.d.ts +34 -0
  326. package/dist/timeline/index.d.ts +6 -2
  327. package/dist/timeline/knowledge-receipt.d.ts +17 -0
  328. package/dist/timeline/platform-activity-row.d.ts +14 -0
  329. package/dist/timeline/presented-image.d.ts +6 -0
  330. package/dist/timeline/projection.d.ts +31 -2
  331. package/dist/timeline/registry.d.ts +2 -0
  332. package/dist/timeline/retained-image.d.ts +18 -0
  333. package/dist/timeline/rolling-activity.d.ts +11 -0
  334. package/dist/timeline/screenshot-lightbox.d.ts +16 -1
  335. package/dist/timeline/shared.d.ts +8 -3
  336. package/dist/timeline/startup-preference.d.ts +3 -0
  337. package/dist/timeline/startup-timings.d.ts +4 -0
  338. package/dist/timeline/tool-display-name.d.ts +2 -1
  339. package/dist/timeline/turn-summary.d.ts +32 -3
  340. package/dist/timeline/types.d.ts +141 -7
  341. package/dist/timeline-annotation-markers-GNDHTGBD.js +165 -0
  342. package/dist/timeline-annotation-markers-GNDHTGBD.js.map +1 -0
  343. package/dist/timeline-annotation-selection-DMLMM2TX.js +241 -0
  344. package/dist/timeline-annotation-selection-DMLMM2TX.js.map +1 -0
  345. package/dist/timeline-annotations-dialog-ELRDBHTS.js +273 -0
  346. package/dist/timeline-annotations-dialog-ELRDBHTS.js.map +1 -0
  347. package/dist/types/machines.d.ts +2 -2
  348. package/dist/usage/allowance-copy.d.ts +25 -0
  349. package/dist/usage/summary.d.ts +35 -0
  350. package/dist/usage.d.ts +13 -0
  351. package/dist/usage.js +1074 -0
  352. package/dist/usage.js.map +1 -0
  353. package/package.json +44 -4
  354. package/src/accounts.tsx +1072 -0
  355. package/src/approvals.ts +8 -1
  356. package/src/artifacts.ts +42 -0
  357. package/src/assets/README.md +8 -0
  358. package/src/assets/gmail.ico +0 -0
  359. package/src/assets/outlook.ico +0 -0
  360. package/src/capability-catalog-row.tsx +94 -0
  361. package/src/capability-logo-fallback.ts +22 -0
  362. package/src/client.ts +22 -13
  363. package/src/commands/registry.ts +18 -15
  364. package/src/commands/types.ts +2 -0
  365. package/src/components/anthropic-mark.tsx +10 -0
  366. package/src/components/approval-surface.tsx +171 -37
  367. package/src/components/artifacts/artifact-chrome.tsx +413 -0
  368. package/src/components/artifacts/artifact-sandbox.tsx +185 -0
  369. package/src/components/artifacts/artifact-surface.tsx +39 -19
  370. package/src/components/artifacts/chat-interactive-block.tsx +260 -0
  371. package/src/components/artifacts/deferred-chat-media.tsx +67 -0
  372. package/src/components/artifacts/document-editor.tsx +141 -16
  373. package/src/components/artifacts/editable-artifact-ui.tsx +4 -2
  374. package/src/components/artifacts/editable-artifact-view.tsx +187 -0
  375. package/src/components/artifacts/editable-artifact-workbench.tsx +5 -2
  376. package/src/components/artifacts/editable-document.tsx +37 -36
  377. package/src/components/artifacts/editable-presentation.tsx +36 -18
  378. package/src/components/artifacts/editable-spreadsheet.tsx +5 -2
  379. package/src/components/artifacts/index.ts +44 -0
  380. package/src/components/artifacts/inline-html-document.ts +24 -0
  381. package/src/components/artifacts/latest-task-runner.ts +49 -0
  382. package/src/components/artifacts/presentation-editor.tsx +323 -67
  383. package/src/components/artifacts/published-html-artifact-frame.tsx +338 -5
  384. package/src/components/artifacts/session-artifact-viewer.tsx +376 -0
  385. package/src/components/artifacts/site-view.tsx +116 -0
  386. package/src/components/artifacts/spreadsheet-canvas.ts +5 -10
  387. package/src/components/artifacts/spreadsheet-general-display.ts +43 -0
  388. package/src/components/artifacts/spreadsheet-grid.tsx +12 -13
  389. package/src/components/artifacts/visualization-assets/base.css +1015 -0
  390. package/src/components/artifacts/visualization-assets/helpers.html +349 -0
  391. package/src/components/artifacts/visualization-assets.gen.ts +3 -0
  392. package/src/components/browser-select-control.tsx +312 -0
  393. package/src/components/browser-viewer.tsx +1054 -314
  394. package/src/components/chat-composer.tsx +131 -85
  395. package/src/components/child-session-link.tsx +27 -0
  396. package/src/components/claude-mark.tsx +10 -0
  397. package/src/components/code-editor.tsx +13 -16
  398. package/src/components/composer-transcription-control.tsx +7 -5
  399. package/src/components/composer.tsx +376 -49
  400. package/src/components/computer-viewer.tsx +1041 -329
  401. package/src/components/copy-button.tsx +13 -5
  402. package/src/components/desktop-viewer.tsx +1 -1
  403. package/src/components/enrollment-consent.tsx +65 -7
  404. package/src/components/enrollment-device-flow.tsx +1 -1
  405. package/src/components/file-browser.tsx +158 -34
  406. package/src/components/fleet-tile.tsx +8 -16
  407. package/src/components/generated-video-player.tsx +4 -2
  408. package/src/components/grok-mark.tsx +33 -0
  409. package/src/components/human-input-form.tsx +455 -114
  410. package/src/components/human-input-surface.tsx +18 -7
  411. package/src/components/interaction-intervention-banner.tsx +2 -2
  412. package/src/components/machine-card.tsx +119 -4
  413. package/src/components/machine-dock-bar.tsx +1 -1
  414. package/src/components/machine-input-display.ts +23 -2
  415. package/src/components/machines/machine-detail.tsx +52 -2
  416. package/src/components/machines/machine-operation-policy.tsx +179 -0
  417. package/src/components/machines-dashboard.tsx +8 -17
  418. package/src/components/markdown-table-layout.ts +132 -0
  419. package/src/components/markdown.tsx +473 -44
  420. package/src/components/message-timeline.tsx +2498 -663
  421. package/src/components/model-mark.tsx +90 -0
  422. package/src/components/model-picker.tsx +15 -4
  423. package/src/components/model-policy-picker-menu.tsx +424 -0
  424. package/src/components/model-policy-picker.tsx +337 -381
  425. package/src/components/open-geni-chat.tsx +319 -0
  426. package/src/components/open-geni-links.tsx +179 -0
  427. package/src/components/openrouter-mark.tsx +13 -0
  428. package/src/components/pierre-diff.tsx +41 -16
  429. package/src/components/pierre-file.tsx +36 -11
  430. package/src/components/preview-loading.tsx +154 -0
  431. package/src/components/queue-item-content.ts +14 -0
  432. package/src/components/queue-surface-implementation.tsx +132 -42
  433. package/src/components/queue-surface-state.tsx +3 -1
  434. package/src/components/sandbox-files.tsx +267 -35
  435. package/src/components/sandbox-terminal.tsx +48 -11
  436. package/src/components/sandbox-workspace.tsx +337 -135
  437. package/src/components/session-capability-frame.tsx +192 -0
  438. package/src/components/session-chrome.tsx +1027 -328
  439. package/src/components/session-commands-panel.tsx +126 -0
  440. package/src/components/session-commands.tsx +18 -0
  441. package/src/components/session-connection-request.tsx +97 -0
  442. package/src/components/session-conversation.tsx +512 -0
  443. package/src/components/session-list.tsx +250 -0
  444. package/src/components/session-mcp-capability-card.tsx +680 -0
  445. package/src/components/session-status.tsx +1 -1
  446. package/src/components/timeline-anchor.tsx +119 -0
  447. package/src/components/timeline-annotation-chrome.tsx +196 -0
  448. package/src/components/timeline-annotation-layout.ts +268 -0
  449. package/src/components/timeline-annotation-markers.tsx +185 -0
  450. package/src/components/timeline-annotation-reveal-context.ts +9 -0
  451. package/src/components/timeline-annotation-selection.tsx +106 -54
  452. package/src/components/timeline-annotation-shared.ts +245 -0
  453. package/src/components/timeline-annotations-dialog.tsx +185 -128
  454. package/src/components/timeline-annotations.tsx +189 -45
  455. package/src/components/timeline-reading-progress.ts +80 -0
  456. package/src/components/timeline-search.tsx +168 -0
  457. package/src/components/timeline-settlement.ts +60 -0
  458. package/src/components/tip-follow.ts +70 -7
  459. package/src/components/tooltip.tsx +38 -5
  460. package/src/components/usage-limit-notice.tsx +179 -0
  461. package/src/components/usage-member-list.tsx +792 -0
  462. package/src/components/usage-meter.tsx +382 -0
  463. package/src/components/use-viewer-menu-dismiss.ts +33 -0
  464. package/src/components/user-message-body.tsx +194 -49
  465. package/src/components/workspace-dock.tsx +141 -175
  466. package/src/composer.ts +6 -1
  467. package/src/connect-account-identity.tsx +34 -0
  468. package/src/connect-accounts.tsx +233 -0
  469. package/src/connect-chooser.tsx +336 -0
  470. package/src/connect-panel.tsx +99 -0
  471. package/src/connect-setup.tsx +307 -0
  472. package/src/connect.ts +60 -0
  473. package/src/connection-assets.d.ts +4 -0
  474. package/src/connection-catalog.tsx +162 -0
  475. package/src/connection-discovery.tsx +235 -0
  476. package/src/connection-installed.tsx +51 -0
  477. package/src/connection-logo.tsx +37 -0
  478. package/src/connection-service-logo.tsx +55 -0
  479. package/src/connection-service-presentation.ts +46 -0
  480. package/src/connection-skeleton.tsx +16 -0
  481. package/src/connection-type-picker.tsx +36 -0
  482. package/src/conversation-timeline.ts +92 -0
  483. package/src/desktop.ts +24 -0
  484. package/src/device-authorization.tsx +98 -0
  485. package/src/diffs.ts +16 -0
  486. package/src/discovery-cache.ts +42 -0
  487. package/src/editor.ts +13 -0
  488. package/src/embedded-session-client.ts +104 -0
  489. package/src/file-node-visibility.ts +66 -0
  490. package/src/hooks/channel-cache.ts +104 -0
  491. package/src/hooks/internal.ts +3 -2
  492. package/src/hooks/latest-question.ts +164 -0
  493. package/src/hooks/shared-machines-list.ts +239 -0
  494. package/src/hooks/use-attached-browsers.ts +5 -1
  495. package/src/hooks/use-available-models.ts +20 -14
  496. package/src/hooks/use-browser-frame-stream.ts +25 -3
  497. package/src/hooks/use-browser-session.ts +271 -71
  498. package/src/hooks/use-browser-sessions.ts +7 -1
  499. package/src/hooks/use-channels.ts +32 -10
  500. package/src/hooks/use-codex-accounts.ts +78 -21
  501. package/src/hooks/use-composer.ts +1034 -320
  502. package/src/hooks/use-computer-frame-stream.ts +39 -16
  503. package/src/hooks/use-computer-input-posture.ts +94 -0
  504. package/src/hooks/use-computer-session.ts +110 -32
  505. package/src/hooks/use-computer-sessions.ts +8 -1
  506. package/src/hooks/use-connect.ts +23 -0
  507. package/src/hooks/use-desktop-stream.ts +19 -28
  508. package/src/hooks/use-file-attachments.ts +118 -10
  509. package/src/hooks/use-goal.ts +36 -9
  510. package/src/hooks/use-human-input.ts +31 -4
  511. package/src/hooks/use-last-started-turn-policy.ts +15 -13
  512. package/src/hooks/use-machine-chip.ts +23 -6
  513. package/src/hooks/use-machines.ts +104 -6
  514. package/src/hooks/use-sandbox-files.ts +253 -150
  515. package/src/hooks/use-sandbox-git.ts +58 -29
  516. package/src/hooks/use-session-background-commands.ts +67 -0
  517. package/src/hooks/use-session-capabilities.ts +21 -2
  518. package/src/hooks/use-session-events.ts +759 -433
  519. package/src/hooks/use-session-lineage.ts +28 -6
  520. package/src/hooks/use-session-mcp-approval-policy.ts +13 -5
  521. package/src/hooks/use-session.ts +75 -16
  522. package/src/hooks/use-slash-commands.ts +6 -11
  523. package/src/hooks/use-turn-queue.ts +113 -13
  524. package/src/hooks/use-usage.ts +100 -0
  525. package/src/hooks/use-voice-input.ts +12 -1
  526. package/src/hooks/use-workspace-capture.ts +3 -2
  527. package/src/hooks/use-workspace-edit.ts +23 -5
  528. package/src/hooks/use-workspace-sessions.ts +99 -11
  529. package/src/human-input.ts +12 -1
  530. package/src/identity-link-accounts.tsx +148 -0
  531. package/src/identity-link-consent.tsx +192 -0
  532. package/src/index.ts +157 -12
  533. package/src/interaction.ts +1 -0
  534. package/src/lib/error-message.ts +37 -0
  535. package/src/lib/format.ts +150 -14
  536. package/src/lib/host-theme.ts +511 -0
  537. package/src/lib/interaction-errors.ts +33 -0
  538. package/src/lib/menu-styles.ts +44 -0
  539. package/src/lib/pierre-diffs-loader.ts +53 -0
  540. package/src/lib/workbench-peers.ts +62 -0
  541. package/src/lib/xterm-theme.ts +23 -21
  542. package/src/machines.ts +9 -1
  543. package/src/mcp-connection-status.ts +35 -0
  544. package/src/model-policy.ts +223 -69
  545. package/src/older-history.ts +87 -0
  546. package/src/plugin-details.tsx +185 -0
  547. package/src/plugin-discovery.tsx +196 -0
  548. package/src/provider.tsx +55 -14
  549. package/src/realtime/dropdown-menu.tsx +19 -12
  550. package/src/realtime/realtime-control.tsx +133 -46
  551. package/src/session-auth-recommendation.ts +48 -0
  552. package/src/session-capability-policy.ts +83 -0
  553. package/src/session-context.ts +1 -0
  554. package/src/session-ui.ts +66 -2
  555. package/src/session.ts +18 -1
  556. package/src/sites-ui.tsx +402 -0
  557. package/src/sites.ts +3 -0
  558. package/src/skill-discovery.tsx +208 -0
  559. package/src/terminal.ts +26 -0
  560. package/src/timeline/activity-rail.tsx +171 -80
  561. package/src/timeline/allowance-exhausted-row.tsx +86 -0
  562. package/src/timeline/compaction-copy.ts +42 -0
  563. package/src/timeline/entrance.tsx +28 -3
  564. package/src/timeline/fleet-decision-projection.ts +2 -1
  565. package/src/timeline/fleet-decision-row.tsx +1 -0
  566. package/src/timeline/genie-loading.tsx +136 -0
  567. package/src/timeline/index.ts +11 -0
  568. package/src/timeline/knowledge-receipt.tsx +100 -0
  569. package/src/timeline/parsers.ts +2 -2
  570. package/src/timeline/platform-activity-row.tsx +216 -0
  571. package/src/timeline/presented-image.ts +49 -0
  572. package/src/timeline/projection.ts +1600 -84
  573. package/src/timeline/registry.ts +15 -3
  574. package/src/timeline/retained-image.ts +97 -0
  575. package/src/timeline/rolling-activity.tsx +92 -0
  576. package/src/timeline/screenshot-lightbox.tsx +115 -17
  577. package/src/timeline/shared.tsx +47 -15
  578. package/src/timeline/startup-preference.ts +33 -0
  579. package/src/timeline/startup-timings.tsx +145 -0
  580. package/src/timeline/tool-display-name.ts +8 -6
  581. package/src/timeline/tool-renderers.tsx +497 -128
  582. package/src/timeline/turn-summary.tsx +208 -44
  583. package/src/timeline/types.ts +186 -7
  584. package/src/types/machines.ts +10 -0
  585. package/src/usage/allowance-copy.ts +64 -0
  586. package/src/usage/summary.ts +93 -0
  587. package/src/usage.ts +50 -0
  588. package/styles/compiled.css +2098 -757
  589. package/styles/connect.css +284 -0
  590. package/styles/connect.d.ts +1 -0
  591. package/styles/effective-tokens.css +1 -0
  592. package/styles/index.css +201 -9
  593. package/styles/responsive.css +27 -1
  594. package/styles/session-capability.css +44 -0
  595. package/styles/tokens.css +135 -65
  596. package/dist/chunk-5UQPNLIO.js.map +0 -1
  597. package/dist/chunk-6KLX2RH5.js.map +0 -1
  598. package/dist/chunk-7WCLAEFV.js.map +0 -1
  599. package/dist/chunk-B6WPZSTT.js.map +0 -1
  600. package/dist/chunk-FK6VG4LL.js +0 -90
  601. package/dist/chunk-FK6VG4LL.js.map +0 -1
  602. package/dist/chunk-HA46CYQK.js.map +0 -1
  603. package/dist/chunk-HUIKIDM5.js.map +0 -1
  604. package/dist/chunk-IGC6A2ZI.js.map +0 -1
  605. package/dist/chunk-KCGXPP5V.js.map +0 -1
  606. package/dist/chunk-KZ73RL76.js.map +0 -1
  607. package/dist/chunk-LAKLF4PA.js +0 -141
  608. package/dist/chunk-LAKLF4PA.js.map +0 -1
  609. package/dist/chunk-LLOLUHVG.js.map +0 -1
  610. package/dist/chunk-MKPIAGVL.js +0 -465
  611. package/dist/chunk-MKPIAGVL.js.map +0 -1
  612. package/dist/chunk-PEI6RANI.js.map +0 -1
  613. package/dist/chunk-R3AZOAB5.js.map +0 -1
  614. package/dist/chunk-WSK7G5LE.js +0 -170
  615. package/dist/chunk-WSK7G5LE.js.map +0 -1
  616. package/dist/chunk-XBZWOM2K.js +0 -218
  617. package/dist/chunk-XBZWOM2K.js.map +0 -1
  618. package/dist/chunk-Y7OEDUJ5.js +0 -8810
  619. package/dist/chunk-Y7OEDUJ5.js.map +0 -1
  620. package/dist/fleet-decision-row-GGCAT4E4.js.map +0 -1
  621. package/dist/hooks/use-packs.d.ts +0 -32
  622. package/dist/interactive-workbench-viewers-TOHBXJEE.js +0 -15
  623. package/dist/queue-surface-implementation-2KVJTYXH.js.map +0 -1
  624. package/dist/timeline-annotation-selection-3L7LHGG2.js +0 -187
  625. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +0 -1
  626. package/dist/timeline-annotations-dialog-U7EVLR75.js +0 -196
  627. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +0 -1
  628. package/src/hooks/use-packs.ts +0 -157
  629. /package/dist/{interactive-workbench-viewers-TOHBXJEE.js.map → browser-viewer-GTR5XQ7X.js.map} +0 -0
@@ -1,3 +1,4 @@
1
+ import { ChildSessionLink } from "./child-session-link";
1
2
  /**
2
3
  * SessionChrome — compact merged session signals above the composer.
3
4
  *
@@ -28,15 +29,30 @@
28
29
  * Inbox has no product dismiss API; pass `onDismissIncoming` when the host
29
30
  * wants a visible action (dev harness may use a local dummy).
30
31
  *
31
- * Segment switches keep the panel shell mounted and crossfade content while
32
- * animating measured height — no `mode="wait"` unmount flash.
32
+ * Queue opens by default when chrome is idle and at least one authoritative
33
+ * prompt is queued. Closing the queue dismisses that session's offer until
34
+ * occupancy drains; a different session on the same chrome instance may
35
+ * offer again. An in-flight optimistic Send never opens the drawer —
36
+ * admission stays a paint-only receipt on the chip so layout does not jump
37
+ * under the pointer.
38
+ *
39
+ * Segment switches keep the panel shell mounted and crossfade content. The
40
+ * shell uses one CSS grid-track transition for deliberate open/close actions;
41
+ * live queue reconciliation never feeds measurements back into layout.
33
42
  */
34
- import type { SessionGoal, SessionPendingInputPreview, SessionTurn } from "@opengeni/sdk";
43
+ import type {
44
+ SessionGoal,
45
+ SessionPendingInputPreview,
46
+ SessionStatus,
47
+ SessionTurn,
48
+ } from "@opengeni/sdk";
35
49
  import {
50
+ ActivityIcon,
36
51
  AudioLinesIcon,
37
52
  ArrowDownIcon,
38
53
  ArrowUpIcon,
39
54
  BotIcon,
55
+ CheckIcon,
40
56
  InboxIcon,
41
57
  ListOrderedIcon,
42
58
  Loader2Icon,
@@ -44,29 +60,33 @@ import {
44
60
  PencilIcon,
45
61
  PlayIcon,
46
62
  Trash2Icon,
63
+ TerminalIcon,
47
64
  TriangleAlertIcon,
48
65
  XIcon,
49
- ZapIcon,
66
+ CornerDownRightIcon,
67
+ TargetIcon,
50
68
  } from "lucide-react";
51
69
  import { AnimatePresence, motion, useReducedMotion } from "motion/react";
52
- import {
53
- useEffect,
54
- useId,
55
- useLayoutEffect,
56
- useMemo,
57
- useRef,
58
- useState,
59
- type ReactNode,
60
- } from "react";
70
+ import { useEffect, useId, useMemo, useRef, useState, type ReactNode } from "react";
61
71
 
62
- import type { ComposerState } from "../hooks/use-composer";
72
+ import type { ComposerOptimisticMessage, ComposerState } from "../hooks/use-composer";
63
73
  import type { UseGoalResult } from "../hooks/use-goal";
64
74
  import type { UseTurnQueueResult } from "../hooks/use-turn-queue";
65
75
  import { cn } from "../lib/cn";
76
+ import { useErrorMessage } from "../lib/error-message";
77
+ import { formatClockTime } from "../lib/format";
66
78
  import { requestQueueDraftEdit } from "./queue-draft-policy";
79
+ import { QUEUE_ITEM_CONTENT_UNAVAILABLE, queueItemContent } from "./queue-item-content";
80
+ import { TimelineAnnotationsChip, type TimelineAnnotationLike } from "./timeline-annotations";
67
81
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
68
82
 
69
- export type SessionChromeSignalId = "incoming" | "steering" | "queue" | "goal" | "agents";
83
+ export type SessionChromeSignalId =
84
+ | "incoming"
85
+ | "steering"
86
+ | "queue"
87
+ | "goal"
88
+ | "agents"
89
+ | "commands";
70
90
 
71
91
  export type SessionChromeSignalTone = "neutral" | "accent" | "waiting" | "running";
72
92
 
@@ -77,12 +97,24 @@ export type SessionChromeAgentsSignal = {
77
97
  };
78
98
 
79
99
  export type SessionChromeProps = {
100
+ /** Authoritative execution status; omitted by older embedding hosts. */
101
+ sessionStatus?: SessionStatus | undefined;
80
102
  queue: UseTurnQueueResult;
103
+ /** Open and focus an existing queued prompt without changing its delivery. */
104
+ queueFocusTarget?: { turnId: string; requestId: number } | undefined;
81
105
  /** Needed for queue edit → composer checkout. Omit with `readOnly`. */
82
106
  composer?: ComposerState | undefined;
107
+ /** Focus the composer after a successful queue checkout has applied its draft.
108
+ * Connect to the composer controller's `focusInput`, or a custom input ref.
109
+ * Never called for failed checkout or while replacement awaits confirmation.
110
+ */
111
+ onComposerFocus?: (() => void) | undefined;
83
112
  goal?: UseGoalResult | null | undefined;
84
113
  /** Expanded agents body (host supplies tree / list). */
85
114
  agentsPanel?: ReactNode;
115
+ /** Active commands only. The host mounts the body only when opened. */
116
+ commandsPanel?: ReactNode;
117
+ commandsCount?: number | undefined;
86
118
  /** Chip summary; when `count > 0` the agents segment appears. */
87
119
  agentsSignal?: SessionChromeAgentsSignal | undefined;
88
120
  /**
@@ -90,6 +122,8 @@ export type SessionChromeProps = {
90
122
  * (and the gallery) may still pass a handler so the action is visible.
91
123
  */
92
124
  onDismissIncoming?: ((inputId: string) => void) | undefined;
125
+ /** Open a typed child update's source through the host's normal authorized route. */
126
+ onOpenSession?: ((sessionId: string) => void) | undefined;
93
127
  readOnly?: boolean | undefined;
94
128
  className?: string | undefined;
95
129
  /** Controlled active segment; omit for uncontrolled. */
@@ -100,7 +134,9 @@ export type SessionChromeProps = {
100
134
 
101
135
  type GoalPillState =
102
136
  | "pursuing"
137
+ | "waiting"
103
138
  | "scheduled"
139
+ | "session_failed"
104
140
  | "blocked"
105
141
  | "held"
106
142
  | "paused"
@@ -114,14 +150,90 @@ type QueuedTurnPresentation = {
114
150
 
115
151
  const GOAL_LABEL: Record<GoalPillState, string> = {
116
152
  pursuing: "Pursuing",
117
- scheduled: "Scheduled",
153
+ waiting: "Waiting",
154
+ scheduled: "Waiting",
118
155
  blocked: "Blocked",
156
+ session_failed: "Blocked by session failure",
119
157
  held: "Held",
120
158
  paused: "Paused",
121
159
  invariant_broken: "Needs attention",
122
160
  completed: "Done",
123
161
  };
124
162
 
163
+ /**
164
+ * Short pill suffix per `pausedReason`. `max_auto_continuations` is pacing
165
+ * (new input resumes it); `limits` is budget/admission; `user_pause`/`api` is
166
+ * the human's own override; `agent` is the model declaring it is blocked on a
167
+ * human decision. Unknown or legacy reasons keep the bare "Paused".
168
+ */
169
+ const GOAL_PAUSED_REASON_SUFFIX: Record<string, string> = {
170
+ max_auto_continuations: "cap",
171
+ limits: "budget",
172
+ user_pause: "manually",
173
+ api: "manually",
174
+ agent: "agent",
175
+ };
176
+
177
+ const GOAL_PAUSED_REASON_EXPLANATION: Record<string, string> = {
178
+ max_auto_continuations:
179
+ "Paused at the automatic continuation cap. New input (a child result, an agent message, or your prompt) resumes it; you can also resume it here.",
180
+ limits: "Paused because budget or usage limits block another run. Resume once limits allow.",
181
+ user_pause:
182
+ "Paused manually by a person or an API call. Resume to let the goal continue on its own.",
183
+ api: "Paused manually by a person or an API call. Resume to let the goal continue on its own.",
184
+ agent: "Paused by the agent: it is waiting on a human decision before continuing.",
185
+ };
186
+
187
+ type GoalPillRecord = Pick<SessionGoal, "status" | "pausedReason"> & {
188
+ continuation?: SessionGoal["continuation"] | null | undefined;
189
+ };
190
+
191
+ /** Pill label, with the pause reason spelled out: "Paused · cap" / "Paused · manually". */
192
+ export function sessionChromeGoalPillLabel(
193
+ state: GoalPillState,
194
+ record: GoalPillRecord | null | undefined,
195
+ ): string {
196
+ if (state !== "paused") return GOAL_LABEL[state];
197
+ const suffix = record?.pausedReason ? GOAL_PAUSED_REASON_SUFFIX[record.pausedReason] : undefined;
198
+ return suffix ? `Paused · ${suffix}` : "Paused";
199
+ }
200
+
201
+ /**
202
+ * One human sentence explaining WHY the goal is not pursuing right now: the
203
+ * pause reason, the agent's own `wait_for_input` hold (reason + deadline), or the
204
+ * next idle-backoff check time. Null when the state needs no explanation.
205
+ */
206
+ export function sessionChromeGoalPillExplanation(
207
+ state: GoalPillState,
208
+ record: GoalPillRecord | null | undefined,
209
+ ): string | null {
210
+ const continuation = record?.continuation ?? null;
211
+ if (state === "session_failed") {
212
+ return "Resolve the session failure, then use Continue or send a message to continue this active goal.";
213
+ }
214
+ if (state === "paused") {
215
+ return record?.pausedReason
216
+ ? (GOAL_PAUSED_REASON_EXPLANATION[record.pausedReason] ?? null)
217
+ : null;
218
+ }
219
+ if (state === "scheduled") {
220
+ return continuation?.nextAttemptAt
221
+ ? `Continues at ${formatClockTime(continuation.nextAttemptAt)}.`
222
+ : "Waiting to continue automatically.";
223
+ }
224
+ if (state === "waiting" && continuation?.reason === "system_work_pending") {
225
+ return "Waiting for other session work to finish before the goal continues automatically.";
226
+ }
227
+ if (state === "held" && continuation?.reason === "held_for_input") {
228
+ const reason = continuation.holdReason?.trim();
229
+ const until = continuation.nextAttemptAt
230
+ ? ` until ${formatClockTime(continuation.nextAttemptAt)}`
231
+ : "";
232
+ return `Waiting for input${reason ? `: ${reason}` : ""}${until}. Relevant session input—including a child result, background-command result, agent message, schedule, or your prompt—wakes it sooner.`;
233
+ }
234
+ return null;
235
+ }
236
+
125
237
  function queuedTurnPresentation(turn: SessionTurn): QueuedTurnPresentation {
126
238
  const realtimeDelegation = objectValue(turn.metadata.realtimeDelegation);
127
239
  const inputTranscript = realtimeDelegation?.inputTranscript;
@@ -138,6 +250,82 @@ function isSteeringTurn(turn: SessionTurn): boolean {
138
250
  return turn.metadata.delivery === "steer";
139
251
  }
140
252
 
253
+ function isAuthoritativeQueuedTurn(
254
+ turn: SessionTurn,
255
+ mutationFor: UseTurnQueueResult["mutationFor"],
256
+ ): boolean {
257
+ return !isSteeringTurn(turn) && mutationFor(turn.id) !== "steer";
258
+ }
259
+
260
+ export function countAuthoritativeQueuedTurns(
261
+ turns: readonly SessionTurn[],
262
+ mutationFor: UseTurnQueueResult["mutationFor"],
263
+ ): number {
264
+ return turns.filter((turn) => isAuthoritativeQueuedTurn(turn, mutationFor)).length;
265
+ }
266
+
267
+ function isOptimisticQueuedMessage(
268
+ message: ComposerOptimisticMessage,
269
+ queuedTurnIds: ReadonlySet<string>,
270
+ snapshot: UseTurnQueueResult["snapshot"],
271
+ ): boolean {
272
+ return (
273
+ message.delivery === "send" &&
274
+ message.destination === "queue" &&
275
+ (!message.turnId || !queuedTurnIds.has(message.turnId)) &&
276
+ !(
277
+ message.turnId &&
278
+ message.appliedQueueVersion !== null &&
279
+ message.appliedQueueVersion !== undefined &&
280
+ snapshot &&
281
+ snapshot.version >= message.appliedQueueVersion
282
+ )
283
+ );
284
+ }
285
+
286
+ export function countOptimisticQueuedMessages(
287
+ messages: readonly ComposerOptimisticMessage[] | undefined,
288
+ queuedTurnIds: ReadonlySet<string>,
289
+ snapshot: UseTurnQueueResult["snapshot"],
290
+ ): number {
291
+ return (messages ?? []).filter((message) =>
292
+ isOptimisticQueuedMessage(message, queuedTurnIds, snapshot),
293
+ ).length;
294
+ }
295
+
296
+ /**
297
+ * First uncontrolled segment. An existing authoritative queue opens itself
298
+ * when the host did not pick another default; optimistic-only occupancy
299
+ * stays collapsed so a live Send does not shove a drawer under the pointer.
300
+ */
301
+ export function sessionChromeInitialActive(input: {
302
+ defaultActive: SessionChromeSignalId | null;
303
+ authoritativeQueuedCount: number;
304
+ }): SessionChromeSignalId | null {
305
+ if (input.defaultActive != null) return input.defaultActive;
306
+ return input.authoritativeQueuedCount >= 1 ? "queue" : null;
307
+ }
308
+
309
+ /**
310
+ * Whether idle chrome should offer the queue panel. A dismissed session
311
+ * stays collapsed until occupancy drains. Controlled hosts own this.
312
+ */
313
+ export function sessionChromeShouldOfferQueue(input: {
314
+ controlled: boolean;
315
+ active: SessionChromeSignalId | null;
316
+ activityOpen: boolean;
317
+ authoritativeQueuedCount: number;
318
+ suppressed: boolean;
319
+ }): boolean {
320
+ return (
321
+ !input.controlled &&
322
+ input.active === null &&
323
+ !input.activityOpen &&
324
+ input.authoritativeQueuedCount >= 1 &&
325
+ !input.suppressed
326
+ );
327
+ }
328
+
141
329
  function objectValue(value: unknown): Record<string, unknown> | null {
142
330
  return value !== null && typeof value === "object" && !Array.isArray(value)
143
331
  ? (value as Record<string, unknown>)
@@ -148,20 +336,34 @@ function objectValue(value: unknown): Record<string, unknown> | null {
148
336
  export function sessionChromeGoalPillState(
149
337
  goalStatus: "active" | "paused" | "completed",
150
338
  continuation: SessionGoal["continuation"] | null | undefined,
339
+ sessionStatus?: SessionStatus,
151
340
  ): GoalPillState {
152
341
  if (goalStatus === "completed") return "completed";
153
342
  if (goalStatus === "paused") return "paused";
343
+ if (sessionStatus === "failed") return "session_failed";
154
344
  if (!continuation) return "invariant_broken";
155
345
  if (continuation.state === "running") {
156
346
  return continuation.reason === "goal_turn_running"
157
347
  ? "pursuing"
158
348
  : continuation.reason === "human_turn_running"
159
- ? "blocked"
349
+ ? "waiting"
160
350
  : "invariant_broken";
161
351
  }
352
+ // `backoff_pending` (idle pacing between consecutive no-input continuations,
353
+ // next evaluation at `nextAttemptAt`) is an ordinary scheduled state.
162
354
  if (continuation.state === "scheduled") return "scheduled";
163
355
  if (continuation.state === "blocked") {
164
- return continuation.reason === "workstream_paused" ? "held" : "blocked";
356
+ if (
357
+ continuation.reason === "human_turn_running" ||
358
+ continuation.reason === "system_work_pending"
359
+ ) {
360
+ return "waiting";
361
+ }
362
+ // `held_for_input` is the agent's own wait_for_input hold (waiting for child
363
+ // results / external input until a deadline); it shares the Held pill.
364
+ return continuation.reason === "workstream_paused" || continuation.reason === "held_for_input"
365
+ ? "held"
366
+ : "blocked";
165
367
  }
166
368
  return "invariant_broken";
167
369
  }
@@ -199,6 +401,16 @@ function pendingKindLabel(kind: SessionPendingInputPreview["kind"]): string {
199
401
  switch (kind) {
200
402
  case "child_terminal_result":
201
403
  return "Child result";
404
+ case "child_requires_action":
405
+ return "Child needs input";
406
+ case "child_requires_action_resolved":
407
+ return "Child unblocked";
408
+ case "child_paused":
409
+ return "Child paused";
410
+ case "child_waiting_capacity":
411
+ return "Child waiting";
412
+ case "child_progress":
413
+ return "Child progress";
202
414
  case "agent_steer_instruction":
203
415
  return "Steer";
204
416
  case "scheduled_occurrence":
@@ -207,6 +419,12 @@ function pendingKindLabel(kind: SessionPendingInputPreview["kind"]): string {
207
419
  return "Goal wake";
208
420
  case "agent_message":
209
421
  return "Update";
422
+ case "background_command_result":
423
+ return "Command result";
424
+ case "session_wait_timeout":
425
+ return "Wait ended";
426
+ case "media_generation_result":
427
+ return "Video result";
210
428
  default:
211
429
  return "Incoming";
212
430
  }
@@ -226,52 +444,47 @@ function toneClass(tone: SessionChromeSignalTone, selected: boolean): string {
226
444
  }
227
445
 
228
446
  export function SessionChrome({
447
+ sessionStatus,
229
448
  queue,
449
+ queueFocusTarget,
230
450
  composer,
451
+ onComposerFocus,
231
452
  goal,
232
453
  agentsPanel,
454
+ commandsPanel,
455
+ commandsCount = 0,
233
456
  agentsSignal,
234
457
  onDismissIncoming,
458
+ onOpenSession,
235
459
  readOnly = false,
236
460
  className,
237
461
  active: activeControlled,
238
462
  defaultActive = null,
239
463
  onActiveChange,
240
464
  }: SessionChromeProps) {
465
+ const formatError = useErrorMessage();
466
+ const [activityRequested, setActivityOpen] = useState(
467
+ Boolean(defaultActive && ["incoming", "agents", "commands"].includes(defaultActive)),
468
+ );
241
469
  const reactId = useId();
242
470
  const panelId = `og-session-chrome-panel-${reactId}`;
243
471
  const reduceMotion = useReducedMotion();
244
472
  const record = goal?.goal ?? null;
245
473
  const incoming = queue.pendingInputs;
246
474
  const turns = queue.queue;
247
- const composerSteering = composer?.steering ?? null;
248
- const { steering, queuedTurns } = useMemo(() => {
249
- const pendingQueueSteer =
250
- turns.find((turn) => queue.pendingByTurn[turn.id] === "steer") ?? null;
251
- const durableQueueSteer =
252
- !pendingQueueSteer && turns[0] && isSteeringTurn(turns[0]) ? turns[0] : null;
253
- const currentSteering =
254
- composerSteering?.phase === "submitting"
255
- ? composerSteering
256
- : pendingQueueSteer
257
- ? {
258
- phase: "submitting" as const,
259
- text: queuedTurnPresentation(pendingQueueSteer).text,
260
- turnId: pendingQueueSteer.id,
261
- }
262
- : durableQueueSteer
263
- ? {
264
- phase: "accepted" as const,
265
- text: queuedTurnPresentation(durableQueueSteer).text,
266
- turnId: durableQueueSteer.id,
267
- }
268
- : composerSteering;
269
- const currentTurnId = currentSteering?.turnId ?? null;
270
- return {
271
- steering: currentSteering,
272
- queuedTurns: currentTurnId ? turns.filter((turn) => turn.id !== currentTurnId) : turns,
273
- };
274
- }, [composerSteering, queue.pendingByTurn, turns]);
475
+ const queueMutationFor = queue.mutationFor;
476
+ const queuedTurns = useMemo(
477
+ () => turns.filter((turn) => isAuthoritativeQueuedTurn(turn, queueMutationFor)),
478
+ [queueMutationFor, turns],
479
+ );
480
+ const queuedTurnIds = useMemo(() => new Set(queuedTurns.map((turn) => turn.id)), [queuedTurns]);
481
+ const optimisticQueued = useMemo(
482
+ () =>
483
+ (composer?.optimisticMessages ?? []).filter((message) =>
484
+ isOptimisticQueuedMessage(message, queuedTurnIds, queue.snapshot),
485
+ ),
486
+ [composer?.optimisticMessages, queue.snapshot, queuedTurnIds],
487
+ );
275
488
  const stoppingKind =
276
489
  composer?.stoppingAttempt ??
277
490
  (queue.stoppingPreviousAttempt
@@ -282,22 +495,91 @@ export function SessionChrome({
282
495
  const stopping = stoppingKind !== null;
283
496
  const canMutateQueue = !readOnly && composer !== undefined;
284
497
 
285
- const liveGoal =
286
- record?.status === "active" &&
287
- record.continuation?.state === "running" &&
288
- record.continuation.reason === "goal_turn_running";
289
- const elapsed = useLiveElapsed(
290
- record?.createdAt,
291
- Boolean(liveGoal),
292
- !liveGoal ? record?.updatedAt : null,
498
+ const elapsed = useLiveElapsed(record?.createdAt, Boolean(record));
499
+ const goalState = record
500
+ ? sessionChromeGoalPillState(record.status, record.continuation, sessionStatus)
501
+ : null;
502
+
503
+ const initialAuthoritativeQueuedCount = countAuthoritativeQueuedTurns(
504
+ queue.queue,
505
+ queue.mutationFor,
506
+ );
507
+ const [activeUncontrolled, setActiveUncontrolled] = useState<SessionChromeSignalId | null>(() =>
508
+ sessionChromeInitialActive({
509
+ defaultActive,
510
+ authoritativeQueuedCount: initialAuthoritativeQueuedCount,
511
+ }),
293
512
  );
294
- const goalState = record ? sessionChromeGoalPillState(record.status, record.continuation) : null;
513
+ const active = activeControlled !== undefined ? activeControlled : activeUncontrolled;
514
+ const rootRef = useRef<HTMLDivElement>(null);
515
+ const openedQueueTargetRef = useRef<string | null>(null);
516
+ const focusedQueueTargetRef = useRef<string | null>(null);
517
+ const queueTargetKey = queueFocusTarget
518
+ ? `${queueFocusTarget.turnId}:${queueFocusTarget.requestId}`
519
+ : null;
520
+ useEffect(() => {
521
+ if (!queueTargetKey || openedQueueTargetRef.current === queueTargetKey) return;
522
+ openedQueueTargetRef.current = queueTargetKey;
523
+ if (activeControlled === undefined) setActiveUncontrolled("queue");
524
+ onActiveChange?.("queue");
525
+ }, [queueTargetKey, activeControlled, onActiveChange]);
526
+ useEffect(() => {
527
+ if (!queueFocusTarget || active !== "queue" || focusedQueueTargetRef.current === queueTargetKey)
528
+ return;
529
+ const root = rootRef.current;
530
+ if (!root) return;
531
+ const focus = () => {
532
+ const row = Array.from(root.querySelectorAll<HTMLElement>("[data-queue-turn-id]")).find(
533
+ (element) => element.dataset.queueTurnId === queueFocusTarget.turnId,
534
+ );
535
+ if (!row) return false;
536
+ focusedQueueTargetRef.current = queueTargetKey;
537
+ row.focus({ preventScroll: true });
538
+ row.scrollIntoView?.({ block: "nearest" });
539
+ return true;
540
+ };
541
+ if (focus()) return;
542
+ const observer = new MutationObserver(() => {
543
+ if (focus()) observer.disconnect();
544
+ });
545
+ observer.observe(root, { childList: true, subtree: true });
546
+ return () => observer.disconnect();
547
+ }, [queueFocusTarget, queueTargetKey, active, queuedTurns]);
548
+ const activityOpen =
549
+ activityRequested || Boolean(active && ["incoming", "agents", "commands"].includes(active));
550
+ const activityVisibleRef = useRef(activityOpen);
551
+ activityVisibleRef.current = activityOpen;
552
+ // Two independent suppressions, both occupancy-scoped:
553
+ // - admission: a live Send while idle must not open a drawer under the pointer
554
+ // - dismiss: closing the queue remembers that session until occupancy drains
555
+ const queueOfferAdmissionSuppressedRef = useRef(
556
+ defaultActive == null &&
557
+ initialAuthoritativeQueuedCount === 0 &&
558
+ countOptimisticQueuedMessages(composer?.optimisticMessages, queuedTurnIds, queue.snapshot) >
559
+ 0,
560
+ );
561
+ const queueOfferDismissedSessionIdsRef = useRef<Set<string>>(new Set());
562
+ const occupancySessionId = queuedTurns[0]?.sessionId ?? null;
563
+ const setActive = (next: SessionChromeSignalId | null) => {
564
+ if (active === "queue" && next === null) {
565
+ if (occupancySessionId) {
566
+ queueOfferDismissedSessionIdsRef.current.add(occupancySessionId);
567
+ } else {
568
+ queueOfferAdmissionSuppressedRef.current = true;
569
+ }
570
+ }
571
+ if (activeControlled === undefined) setActiveUncontrolled(next);
572
+ onActiveChange?.(next);
573
+ };
295
574
 
575
+ const hasCommandsPanel = commandsPanel != null;
296
576
  const signals = useMemo(() => {
297
577
  const rows: Array<{
298
578
  id: SessionChromeSignalId;
299
579
  label: string;
300
580
  detail?: string | undefined;
581
+ /** Hover explanation (why paused / held / when the next check is). */
582
+ title?: string | undefined;
301
583
  tone: SessionChromeSignalTone;
302
584
  icon: ReactNode;
303
585
  }> = [];
@@ -315,44 +597,43 @@ export function SessionChrome({
315
597
  icon: <InboxIcon className="size-3" />,
316
598
  });
317
599
  }
318
- if (steering || stopping) {
319
- rows.push({
320
- id: "steering",
321
- label: stopping
322
- ? stoppingKind === "current"
323
- ? "Stopping current work…"
324
- : "Stopping previous work…"
325
- : "Changing direction…",
326
- ...(steering?.text ? { detail: steering.text } : {}),
327
- tone: stopping ? "waiting" : "accent",
328
- icon:
329
- steering?.phase === "submitting" || stopping ? (
330
- <Loader2Icon className="size-3 animate-og-spin" />
331
- ) : (
332
- <ZapIcon className="size-3" />
333
- ),
334
- });
335
- }
336
- if (queuedTurns.length > 0) {
600
+ const queuedCount = queuedTurns.length + optimisticQueued.length;
601
+ const queueProblem = queue.mutationError ?? queue.error;
602
+ if (queuedCount > 0 || queueProblem) {
337
603
  const presentations = queuedTurns.map(queuedTurnPresentation);
338
604
  const first = presentations[0];
339
- const allVoiceRequests = presentations.every(({ kind }) => kind === "realtime_voice");
605
+ const allVoiceRequests =
606
+ optimisticQueued.length === 0 &&
607
+ presentations.length > 0 &&
608
+ presentations.every(({ kind }) => kind === "realtime_voice");
340
609
  const onlyVoiceHandoff =
341
- presentations.length === 1 && first?.kind === "realtime_voice_handoff";
610
+ optimisticQueued.length === 0 &&
611
+ presentations.length === 1 &&
612
+ first?.kind === "realtime_voice_handoff";
342
613
  const voiceOnly = allVoiceRequests || onlyVoiceHandoff;
343
- const detail = first?.text;
614
+ const detail = queueProblem
615
+ ? queue.mutationError
616
+ ? "Action not confirmed"
617
+ : "Queue unavailable"
618
+ : (first?.text ?? optimisticQueued[0]?.text);
344
619
  rows.push({
345
620
  id: "queue",
346
- label: allVoiceRequests
347
- ? queuedTurns.length === 1
348
- ? "Voice request queued"
349
- : `${queuedTurns.length} voice requests queued`
350
- : onlyVoiceHandoff
351
- ? "Voice handoff queued"
352
- : `${queuedTurns.length} queued prompt${queuedTurns.length === 1 ? "" : "s"}`,
621
+ label: queueProblem
622
+ ? queuedCount > 0
623
+ ? `${queuedCount} queued · needs attention`
624
+ : "Queue needs attention"
625
+ : allVoiceRequests
626
+ ? queuedCount === 1
627
+ ? "Voice request queued"
628
+ : `${queuedCount} voice requests queued`
629
+ : onlyVoiceHandoff
630
+ ? "Voice handoff queued"
631
+ : `${queuedCount} queued prompt${queuedCount === 1 ? "" : "s"}`,
353
632
  ...(detail ? { detail } : {}),
354
- tone: "neutral",
355
- icon: voiceOnly ? (
633
+ tone: queueProblem ? "waiting" : "neutral",
634
+ icon: queueProblem ? (
635
+ <TriangleAlertIcon className="size-3" />
636
+ ) : voiceOnly ? (
356
637
  <AudioLinesIcon className="size-3" />
357
638
  ) : (
358
639
  <ListOrderedIcon className="size-3" />
@@ -360,24 +641,24 @@ export function SessionChrome({
360
641
  });
361
642
  }
362
643
  if (record && goalState) {
363
- const waiting = goalState === "blocked" || goalState === "held" || goalState === "paused";
644
+ const waiting =
645
+ goalState === "waiting" ||
646
+ goalState === "blocked" ||
647
+ goalState === "session_failed" ||
648
+ goalState === "held" ||
649
+ goalState === "paused";
650
+ const explanation = sessionChromeGoalPillExplanation(goalState, record);
364
651
  rows.push({
365
652
  id: "goal",
366
- label: GOAL_LABEL[goalState],
653
+ label: sessionChromeGoalPillLabel(goalState, record),
367
654
  detail: elapsed ? `${elapsed} · ${record.text}` : record.text,
655
+ ...(explanation ? { title: explanation } : {}),
368
656
  tone: waiting
369
657
  ? "waiting"
370
658
  : goalState === "pursuing" || goalState === "scheduled"
371
659
  ? "accent"
372
660
  : "neutral",
373
- icon:
374
- goalState === "blocked" || goalState === "invariant_broken" ? (
375
- <TriangleAlertIcon className="size-3" />
376
- ) : goalState === "paused" || goalState === "held" ? (
377
- <PauseIcon className="size-3" />
378
- ) : (
379
- <ZapIcon className="size-3" />
380
- ),
661
+ icon: <TargetIcon className="size-3" />,
381
662
  });
382
663
  }
383
664
  if (agentsSignal && agentsSignal.count > 0) {
@@ -390,36 +671,95 @@ export function SessionChrome({
390
671
  icon: <BotIcon className="size-3" />,
391
672
  });
392
673
  }
674
+ if (commandsCount > 0 || (active === "commands" && hasCommandsPanel)) {
675
+ rows.push({
676
+ id: "commands",
677
+ label:
678
+ commandsCount > 0
679
+ ? `${commandsCount} command${commandsCount === 1 ? "" : "s"}`
680
+ : "Commands",
681
+ tone: commandsCount > 0 ? "running" : "neutral",
682
+ icon: <TerminalIcon className="size-3" />,
683
+ });
684
+ }
393
685
  return rows;
394
686
  }, [
687
+ commandsCount,
688
+ hasCommandsPanel,
689
+ active,
395
690
  agentsSignal,
396
691
  elapsed,
397
692
  goalState,
398
693
  incoming,
694
+ optimisticQueued,
695
+ queue.error,
696
+ queue.mutationError,
399
697
  queuedTurns,
400
698
  record,
401
- steering,
402
- stopping,
403
- stoppingKind,
404
699
  ]);
405
700
 
406
- const [activeUncontrolled, setActiveUncontrolled] = useState<SessionChromeSignalId | null>(
407
- defaultActive,
408
- );
409
- const active = activeControlled !== undefined ? activeControlled : activeUncontrolled;
410
- const setActive = (next: SessionChromeSignalId | null) => {
411
- if (activeControlled === undefined) setActiveUncontrolled(next);
412
- onActiveChange?.(next);
413
- };
701
+ const optimisticQueueKeys = optimisticQueued.map((message) => message.clientEventId).join(",");
702
+ const previousOptimisticQueueKeys = useRef(optimisticQueueKeys);
703
+ const [queueArrivalNonce, setQueueArrivalNonce] = useState(0);
704
+ useEffect(() => {
705
+ const previous = new Set(previousOptimisticQueueKeys.current.split(",").filter(Boolean));
706
+ const arrived = optimisticQueued.some((message) => !previous.has(message.clientEventId));
707
+ previousOptimisticQueueKeys.current = optimisticQueueKeys;
708
+ if (!arrived) return;
709
+ // Queue admission must not move the conversation or open a drawer beneath
710
+ // the pointer. A stable, paint-only receipt on the queue chip communicates
711
+ // destination without participating in layout.
712
+ setQueueArrivalNonce((current) => current + 1);
713
+ if (activeControlled === undefined && active === null) {
714
+ queueOfferAdmissionSuppressedRef.current = true;
715
+ }
716
+ }, [active, activeControlled, optimisticQueueKeys, optimisticQueued]);
717
+ const queueOccupied = queuedTurns.length > 0 || optimisticQueued.length > 0;
718
+ useEffect(() => {
719
+ if (queueOccupied) return;
720
+ // A later wave may offer again. A prior session's dismiss must not stick
721
+ // after this instance has actually gone empty.
722
+ queueOfferAdmissionSuppressedRef.current = false;
723
+ queueOfferDismissedSessionIdsRef.current.clear();
724
+ }, [queueOccupied]);
725
+ const queueOfferSuppressed =
726
+ queueOfferAdmissionSuppressedRef.current ||
727
+ (occupancySessionId != null &&
728
+ queueOfferDismissedSessionIdsRef.current.has(occupancySessionId));
729
+ useEffect(() => {
730
+ if (
731
+ !sessionChromeShouldOfferQueue({
732
+ controlled: activeControlled !== undefined,
733
+ active,
734
+ activityOpen,
735
+ authoritativeQueuedCount: queuedTurns.length,
736
+ suppressed: queueOfferSuppressed,
737
+ })
738
+ ) {
739
+ return;
740
+ }
741
+ if (activeControlled === undefined) setActiveUncontrolled("queue");
742
+ onActiveChange?.("queue");
743
+ }, [
744
+ active,
745
+ activeControlled,
746
+ activityOpen,
747
+ occupancySessionId,
748
+ onActiveChange,
749
+ queueOfferSuppressed,
750
+ queuedTurns.length,
751
+ ]);
414
752
  const [replaceDraftFor, setReplaceDraftFor] = useState<string | null>(null);
415
753
 
416
754
  const chipRefs = useRef<Partial<Record<SessionChromeSignalId, HTMLButtonElement | null>>>({});
417
755
  const railRef = useRef<HTMLDivElement | null>(null);
418
- const panelBodyRef = useRef<HTMLDivElement | null>(null);
419
756
  const [pill, setPill] = useState({ left: 0, top: 0, width: 0, height: 0, opacity: 0 });
420
- const [panelHeight, setPanelHeight] = useState(0);
421
757
 
422
758
  const signalIds = signals.map((signal) => signal.id).join(",");
759
+ useEffect(() => {
760
+ if (!signals.some((signal) => ["incoming", "agents", "commands"].includes(signal.id)))
761
+ setActivityOpen(false);
762
+ }, [signals]);
423
763
  useEffect(() => {
424
764
  if (active && !signalIds.split(",").includes(active)) {
425
765
  if (activeControlled === undefined) setActiveUncontrolled(null);
@@ -442,7 +782,10 @@ export function SessionChrome({
442
782
  return;
443
783
  }
444
784
  const chip = chipRefs.current[active];
445
- if (!chip) return;
785
+ if (!chip) {
786
+ setPill((prev) => ({ ...prev, opacity: 0 }));
787
+ return;
788
+ }
446
789
  // Measure against the chip's own box so a wrapped multi-row rail never
447
790
  // stretches the highlight into a tall stripe across every signal.
448
791
  const railBox = rail.getBoundingClientRect();
@@ -471,28 +814,7 @@ export function SessionChrome({
471
814
 
472
815
  const open = active !== null;
473
816
 
474
- useLayoutEffect(() => {
475
- if (!open) {
476
- setPanelHeight(0);
477
- return;
478
- }
479
- const node = panelBodyRef.current;
480
- if (!node) return;
481
- setPanelHeight(node.offsetHeight);
482
- }, [open, active, incoming, queuedTurns, record, goalState, agentsPanel, agentsSignal, steering]);
483
-
484
- useEffect(() => {
485
- if (!open) return;
486
- const node = panelBodyRef.current;
487
- if (!node || typeof ResizeObserver === "undefined") return;
488
- const observer = new ResizeObserver(() => {
489
- setPanelHeight(node.offsetHeight);
490
- });
491
- observer.observe(node);
492
- return () => observer.disconnect();
493
- }, [open, active]);
494
-
495
- if (signals.length === 0) return null;
817
+ if (signals.length === 0 && !stopping) return null;
496
818
 
497
819
  const shellDuration = reduceMotion ? 0 : 0.22;
498
820
  const crossfadeDuration = reduceMotion ? 0 : 0.18;
@@ -500,16 +822,22 @@ export function SessionChrome({
500
822
 
501
823
  const panelBody =
502
824
  active === "incoming" ? (
503
- <IncomingPanel inputs={incoming} onDismiss={onDismissIncoming} />
504
- ) : active === "steering" && (steering || stopping) ? (
505
- <SteeringPanel
506
- phase={stopping ? "stopping" : (steering?.phase ?? "accepted")}
507
- stoppingKind={stoppingKind}
508
- text={steering?.text}
825
+ <IncomingPanel
826
+ inputs={incoming}
827
+ onDismiss={onDismissIncoming}
828
+ onOpenSession={onOpenSession}
509
829
  />
510
830
  ) : active === "queue" ? (
511
831
  <QueuePanel
512
832
  turns={queuedTurns}
833
+ optimistic={optimisticQueued}
834
+ loadError={queue.error}
835
+ mutationError={queue.mutationError}
836
+ onRefresh={queue.refresh}
837
+ onClearMutationError={queue.clearMutationError}
838
+ onRetryOptimistic={composer?.retryOptimisticMessage}
839
+ onRestoreOptimistic={composer?.restoreOptimisticMessage}
840
+ onRemoveOptimistic={composer?.removeOptimisticMessage}
513
841
  readOnly={!canMutateQueue}
514
842
  mutationFor={queue.mutationFor}
515
843
  replaceDraftFor={replaceDraftFor}
@@ -519,10 +847,16 @@ export function SessionChrome({
519
847
  ? () => {
520
848
  const turnId = replaceDraftFor;
521
849
  setReplaceDraftFor(null);
522
- void queue.editTurn(turnId, {
523
- expectedDraftRevision: composer.draftRevision,
524
- replaceDraft: true,
525
- });
850
+ void (async () => {
851
+ const checkedOut = await queue.editTurn(turnId, {
852
+ expectedDraftRevision: composer.draftRevision,
853
+ replaceDraft: true,
854
+ });
855
+ if (checkedOut) {
856
+ composer.applyDraft(checkedOut);
857
+ onComposerFocus?.();
858
+ }
859
+ })();
526
860
  }
527
861
  : undefined
528
862
  }
@@ -533,10 +867,16 @@ export function SessionChrome({
533
867
  composer,
534
868
  () => setReplaceDraftFor(turn.id),
535
869
  () => {
536
- void queue.editTurn(turn.id, {
537
- expectedDraftRevision: composer.draftRevision,
538
- replaceDraft: false,
539
- });
870
+ void (async () => {
871
+ const checkedOut = await queue.editTurn(turn.id, {
872
+ expectedDraftRevision: composer.draftRevision,
873
+ replaceDraft: false,
874
+ });
875
+ if (checkedOut) {
876
+ composer.applyDraft(checkedOut);
877
+ onComposerFocus?.();
878
+ }
879
+ })();
540
880
  },
541
881
  );
542
882
  }
@@ -566,6 +906,8 @@ export function SessionChrome({
566
906
  />
567
907
  ) : active === "goal" && record && goalState && goal ? (
568
908
  <GoalPanel goal={goal} state={goalState} elapsed={elapsed} readOnly={readOnly} />
909
+ ) : active === "commands" ? (
910
+ <div data-og-session-chrome-panel="commands">{commandsPanel}</div>
569
911
  ) : active === "agents" ? (
570
912
  <div data-og-session-chrome-panel="agents">
571
913
  {agentsPanel ?? <p className="text-og-xs text-og-fg-muted">No agent details.</p>}
@@ -577,6 +919,7 @@ export function SessionChrome({
577
919
  <div
578
920
  className={cn("og-session-chrome og-root w-full", className)}
579
921
  data-testid="session-chrome"
922
+ ref={rootRef}
580
923
  data-og-session-chrome=""
581
924
  data-og-session-chrome-open={open ? "true" : "false"}
582
925
  >
@@ -587,15 +930,9 @@ export function SessionChrome({
587
930
  )}
588
931
  style={{
589
932
  borderRadius: "var(--_og-session-chrome-radius)",
590
- background: open
591
- ? "var(--_og-session-chrome-surface-open)"
592
- : "var(--_og-session-chrome-surface)",
593
- borderColor: open
594
- ? "var(--_og-session-chrome-border-open)"
595
- : "var(--_og-session-chrome-border)",
596
- boxShadow: open
597
- ? "var(--og-session-chrome-shadow-open)"
598
- : "var(--og-session-chrome-shadow)",
933
+ background: "transparent",
934
+ borderColor: "transparent",
935
+ boxShadow: "none",
599
936
  transitionDuration: "var(--og-session-chrome-duration)",
600
937
  transitionTimingFunction: "var(--_og-session-chrome-ease)",
601
938
  }}
@@ -611,9 +948,9 @@ export function SessionChrome({
611
948
  >
612
949
  <div
613
950
  ref={railRef}
614
- className="relative flex flex-wrap items-center"
951
+ className="relative flex flex-nowrap items-center"
615
952
  style={{
616
- gap: "var(--og-session-chrome-chip-gap)",
953
+ gap: "10px",
617
954
  }}
618
955
  >
619
956
  <motion.div
@@ -633,108 +970,307 @@ export function SessionChrome({
633
970
  }}
634
971
  transition={{ duration: shellDuration, ease }}
635
972
  />
636
- {signals.map((signal) => {
637
- const selected = active === signal.id;
638
- return (
639
- <button
640
- key={signal.id}
641
- type="button"
642
- ref={(node) => {
643
- chipRefs.current[signal.id] = node;
644
- }}
645
- aria-expanded={selected}
646
- aria-controls={panelId}
647
- aria-label={selected ? `Close ${signal.label}` : undefined}
648
- data-testid={`session-chrome-${signal.id}`}
649
- data-og-session-chrome-signal={signal.id}
650
- onClick={() => setActive(selected ? null : signal.id)}
651
- className={cn(
652
- "group relative z-[1] inline-flex min-h-[var(--og-session-chrome-chip-min-height)] max-w-full items-center gap-1 rounded-og-md py-1 text-left text-og-xs outline-hidden",
653
- // Coarse pointers keep a 44px target (session-pins acceptance).
654
- "pointer-coarse:min-h-11",
655
- "transition-colors duration-150 motion-reduce:transition-none",
656
- "hover:text-og-fg focus-visible:bg-og-surface-3/50",
657
- selected ? "text-og-fg" : "text-og-fg-muted",
658
- )}
659
- style={{
660
- paddingInline: "var(--og-session-chrome-chip-pad-x)",
661
- }}
662
- >
663
- <span className={cn("shrink-0", toneClass(signal.tone, selected))}>
664
- {signal.icon}
665
- </span>
666
- <span className="shrink-0 font-medium text-og-fg">{signal.label}</span>
667
- {signal.detail ? (
668
- <>
669
- <span aria-hidden className="shrink-0 text-og-fg-subtle/60">
670
- ·
973
+ {signals
974
+ .filter((signal) => !["incoming", "agents", "commands"].includes(signal.id))
975
+ .map((signal) => {
976
+ const selected = active === signal.id;
977
+ return (
978
+ <div
979
+ key={signal.id}
980
+ role="group"
981
+ aria-label={
982
+ signal.id === "queue"
983
+ ? "Queued messages and actions"
984
+ : signal.id === "goal"
985
+ ? "Goal and actions"
986
+ : signal.label
987
+ }
988
+ className={cn(
989
+ "group/signal relative z-[1] inline-flex min-w-0 max-w-full items-center rounded-og-md",
990
+ "bg-og-surface-2/60 px-0.5",
991
+ selected && "bg-og-surface-3",
992
+ )}
993
+ >
994
+ <button
995
+ type="button"
996
+ ref={(node) => {
997
+ chipRefs.current[signal.id] = node;
998
+ }}
999
+ aria-expanded={selected}
1000
+ aria-controls={panelId}
1001
+ aria-label={
1002
+ signal.id === "goal"
1003
+ ? `Goal · ${goalState === "pursuing" ? "Running" : signal.label}`
1004
+ : selected
1005
+ ? `Close ${signal.label}`
1006
+ : undefined
1007
+ }
1008
+ data-testid={`session-chrome-${signal.id}`}
1009
+ data-og-session-chrome-signal={signal.id}
1010
+ title={signal.title ?? signal.label}
1011
+ onClick={() => {
1012
+ setActivityOpen(false);
1013
+ setActive(selected ? null : signal.id);
1014
+ }}
1015
+ className={cn(
1016
+ "group relative z-[1] inline-flex min-w-0 min-h-[var(--og-session-chrome-chip-min-height)] max-w-full items-center gap-1 rounded-og-md py-1 text-left text-og-xs outline-hidden",
1017
+ // Coarse pointers keep a 44px target (session-pins acceptance).
1018
+ "pointer-coarse:min-h-11",
1019
+ "transition-colors duration-150 motion-reduce:transition-none",
1020
+ "hover:text-og-fg focus-visible:bg-og-surface-3/50",
1021
+ selected ? "text-og-fg" : "text-og-fg-muted",
1022
+ )}
1023
+ style={{
1024
+ paddingInline: "var(--og-session-chrome-chip-pad-x)",
1025
+ }}
1026
+ >
1027
+ {signal.id === "queue" && queueArrivalNonce > 0 && !reduceMotion ? (
1028
+ <motion.span
1029
+ key={queueArrivalNonce}
1030
+ aria-hidden="true"
1031
+ data-testid="session-chrome-queue-arrival"
1032
+ className="pointer-events-none absolute inset-0 rounded-og-md bg-og-accent-soft"
1033
+ initial={{ opacity: 0 }}
1034
+ animate={{ opacity: [0, 0.38, 0] }}
1035
+ transition={{ duration: 0.72, times: [0, 0.18, 1], ease }}
1036
+ />
1037
+ ) : null}
1038
+ <span className={cn("shrink-0", toneClass(signal.tone, selected))}>
1039
+ {signal.icon}
671
1040
  </span>
672
- <span className="min-w-0 max-w-[8.5rem] truncate text-og-fg sm:max-w-[12rem]">
673
- {signal.detail}
1041
+ <span className={cn("font-medium text-og-fg", "min-w-0 truncate")}>
1042
+ {signal.id === "goal" ? "Goal · " : null}
1043
+ {signal.id === "goal"
1044
+ ? goalState === "pursuing"
1045
+ ? "Running"
1046
+ : signal.label
1047
+ : signal.id === "queue"
1048
+ ? queue.error || queue.mutationError
1049
+ ? signal.label
1050
+ : `${queuedTurns.length + optimisticQueued.length} queued`
1051
+ : signal.label}
674
1052
  </span>
675
- </>
676
- ) : null}
677
- {selected ? (
1053
+ </button>
1054
+ {signal.id === "goal" && goal && record && !readOnly ? (
1055
+ <div className="flex shrink-0 items-center pr-1 pl-0.5">
1056
+ {record.status === "active" || record.status === "paused" ? (
1057
+ <IconAction
1058
+ label={record.status === "paused" ? "Resume goal" : "Pause goal"}
1059
+ tip={record.status === "paused" ? "Resume goal" : "Pause goal"}
1060
+ disabled={goal.updating}
1061
+ onClick={() =>
1062
+ void (record.status === "paused"
1063
+ ? goal.resume()
1064
+ : goal.pause("Paused from session chrome"))
1065
+ }
1066
+ >
1067
+ {goal.updating ? (
1068
+ <Loader2Icon className="size-3 animate-og-spin" />
1069
+ ) : record.status === "paused" ? (
1070
+ <PlayIcon className="size-3" />
1071
+ ) : (
1072
+ <PauseIcon className="size-3" />
1073
+ )}
1074
+ </IconAction>
1075
+ ) : null}
1076
+ <IconAction
1077
+ label="Clear goal"
1078
+ tip="Clear goal"
1079
+ danger
1080
+ disabled={goal.updating}
1081
+ onClick={() => void goal.deleteGoal()}
1082
+ >
1083
+ <Trash2Icon className="size-3" />
1084
+ </IconAction>
1085
+ </div>
1086
+ ) : signal.id === "queue" && canMutateQueue && queuedTurns[0] ? (
1087
+ <div className="flex shrink-0 items-center pr-1 pl-0.5">
1088
+ <IconAction
1089
+ label="Steer first queued message"
1090
+ analyticsAction="steer"
1091
+ text="Steer"
1092
+ tip={QUEUE_STEER_TIP}
1093
+ disabled={
1094
+ queue.mutating || Boolean(queue.mutationFor(queuedTurns[0].id))
1095
+ }
1096
+ onClick={() => void queue.steerTurn(queuedTurns[0]!.id)}
1097
+ >
1098
+ <CornerDownRightIcon className="size-3" />
1099
+ </IconAction>
1100
+ </div>
1101
+ ) : null}
1102
+ </div>
1103
+ );
1104
+ })}
1105
+ {signals.some((signal) => ["incoming", "agents", "commands"].includes(signal.id)) ? (
1106
+ <button
1107
+ type="button"
1108
+ aria-label="Session activity"
1109
+ aria-describedby={commandsCount > 0 ? `${panelId}-commands-count` : undefined}
1110
+ title={activityOpen ? "Close activity" : "Inbox, agents and commands"}
1111
+ aria-controls={panelId}
1112
+ aria-expanded={activityOpen}
1113
+ onClick={() => {
1114
+ setActivityOpen(!activityOpen);
1115
+ setActive(
1116
+ activityOpen
1117
+ ? null
1118
+ : commandsCount > 0 && hasCommandsPanel
1119
+ ? "commands"
1120
+ : (signals.find((signal) =>
1121
+ ["incoming", "agents", "commands"].includes(signal.id),
1122
+ )?.id ?? null),
1123
+ );
1124
+ }}
1125
+ className={cn(
1126
+ "ml-auto inline-flex min-h-8 shrink-0 items-center justify-center gap-1.5 rounded-og-md px-2 text-og-xs transition-colors outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent/40 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
1127
+ activityOpen
1128
+ ? "bg-og-surface-3 text-og-fg"
1129
+ : "text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg",
1130
+ )}
1131
+ >
1132
+ <ActivityIcon className="size-3.5" />
1133
+ <span className="sr-only">Activity</span>
1134
+ {commandsCount > 0 ? (
1135
+ <>
678
1136
  <span
679
- data-testid="session-chrome-close"
680
- className="ml-0.5 inline-flex size-4 shrink-0 items-center justify-center rounded-og-sm text-og-fg-subtle transition-colors group-hover:text-og-fg pointer-coarse:size-5"
681
- aria-hidden
682
- >
683
- <XIcon className="size-3" />
1137
+ aria-hidden="true"
1138
+ className="size-1.5 shrink-0 animate-og-pulse rounded-full bg-og-fg-muted motion-reduce:animate-none"
1139
+ />
1140
+ <span id={`${panelId}-commands-count`}>
1141
+ {commandsCount} command{commandsCount === 1 ? "" : "s"}
684
1142
  </span>
685
- ) : null}
686
- </button>
687
- );
688
- })}
1143
+ </>
1144
+ ) : null}
1145
+ {incoming.length > 0 ? (
1146
+ <span className="size-1.5 rounded-full bg-og-accent" />
1147
+ ) : null}
1148
+ </button>
1149
+ ) : null}
1150
+ {stopping ? (
1151
+ <span
1152
+ role="status"
1153
+ aria-live="polite"
1154
+ className="inline-flex min-h-[var(--og-session-chrome-chip-min-height)] items-center gap-1.5 px-1.5 text-og-xs text-og-fg-muted"
1155
+ data-testid="session-chrome-stopping"
1156
+ >
1157
+ <Loader2Icon
1158
+ aria-hidden="true"
1159
+ className="size-3 animate-og-spin motion-reduce:animate-none"
1160
+ />
1161
+ {stoppingKind === "current" ? "Current work stopping" : "Previous work stopping"}
1162
+ </span>
1163
+ ) : null}
689
1164
  </div>
690
1165
  </div>
691
1166
 
692
- <motion.div
1167
+ <AnimatePresence initial={false}>
1168
+ {activityOpen ? (
1169
+ <motion.div
1170
+ key="activity-tabs"
1171
+ initial={{ height: 0, opacity: 0 }}
1172
+ animate={{ height: "auto", opacity: 1 }}
1173
+ exit={{ height: 0, opacity: 0 }}
1174
+ transition={{ duration: shellDuration, ease }}
1175
+ style={{ overflow: "hidden" }}
1176
+ >
1177
+ <div className="mt-1 flex items-center gap-1 rounded-t-og-lg bg-og-surface-2/50 px-2 py-1">
1178
+ {signals
1179
+ .filter((signal) => ["incoming", "agents", "commands"].includes(signal.id))
1180
+ .map((signal) => (
1181
+ <button
1182
+ key={signal.id}
1183
+ type="button"
1184
+ aria-expanded={active === signal.id}
1185
+ onClick={() => {
1186
+ if (activityVisibleRef.current) setActive(signal.id);
1187
+ }}
1188
+ className={cn(
1189
+ "inline-flex min-h-9 items-center gap-1.5 rounded-og-md px-2 text-og-xs pointer-coarse:min-h-11",
1190
+ active === signal.id
1191
+ ? "bg-og-surface-3 text-og-fg"
1192
+ : "text-og-fg-muted hover:text-og-fg",
1193
+ )}
1194
+ >
1195
+ {signal.icon}
1196
+ {signal.id === "incoming" ? `${incoming.length} incoming` : signal.label}
1197
+ </button>
1198
+ ))}
1199
+ </div>
1200
+ </motion.div>
1201
+ ) : null}
1202
+ </AnimatePresence>
1203
+ {goal?.mutationError ? (
1204
+ <p role="alert" className="px-3 pb-2 text-og-xs text-og-danger">
1205
+ Goal action not confirmed. {formatError(goal.mutationError)}
1206
+ </p>
1207
+ ) : null}
1208
+ <div
693
1209
  id={panelId}
694
1210
  data-og-session-chrome-panel-shell=""
695
- initial={false}
696
- animate={{
697
- height: open ? panelHeight : 0,
1211
+ aria-hidden={!open}
1212
+ inert={!open ? true : undefined}
1213
+ className="grid overflow-hidden motion-reduce:transition-none"
1214
+ style={{
1215
+ gridTemplateRows: open ? "1fr" : "0fr",
698
1216
  opacity: open ? 1 : 0,
1217
+ pointerEvents: open ? "auto" : "none",
1218
+ transitionProperty: "grid-template-rows, opacity",
1219
+ transitionDuration: "var(--og-session-chrome-duration)",
1220
+ transitionTimingFunction: "var(--_og-session-chrome-ease)",
699
1221
  }}
700
- transition={{ duration: shellDuration, ease }}
701
- className="overflow-hidden"
702
1222
  >
703
- <div
704
- ref={panelBodyRef}
705
- className="relative overflow-y-auto overscroll-contain border-t border-og-border/50"
706
- style={{
707
- maxHeight: "var(--og-session-chrome-panel-max-height)",
708
- paddingInline: "var(--og-session-chrome-panel-pad-x)",
709
- paddingBlock: "var(--og-session-chrome-panel-pad-y)",
710
- }}
711
- >
712
- <AnimatePresence initial={false}>
713
- {active && panelBody ? (
1223
+ <div className="min-h-0 overflow-hidden">
1224
+ <div
1225
+ className={cn(
1226
+ "relative overflow-y-auto overscroll-contain",
1227
+ activityOpen
1228
+ ? "rounded-b-og-lg bg-og-surface-2/50"
1229
+ : "mt-1 rounded-og-lg bg-og-surface-2/50",
1230
+ )}
1231
+ style={{
1232
+ maxHeight: "var(--og-session-chrome-panel-max-height)",
1233
+ paddingInline: "var(--og-session-chrome-panel-pad-x)",
1234
+ paddingBlock: "var(--og-session-chrome-panel-pad-y)",
1235
+ }}
1236
+ >
1237
+ <AnimatePresence initial={false}>
1238
+ {active && active !== "commands" && panelBody ? (
1239
+ <motion.div
1240
+ key={active}
1241
+ data-og-session-chrome-panel-frame={active}
1242
+ initial={reduceMotion ? false : { opacity: 0 }}
1243
+ animate={{ opacity: 1, position: "relative" }}
1244
+ exit={
1245
+ reduceMotion
1246
+ ? { opacity: 1, position: "relative" }
1247
+ : {
1248
+ opacity: 0,
1249
+ position: "absolute",
1250
+ top: 0,
1251
+ left: 0,
1252
+ right: 0,
1253
+ }
1254
+ }
1255
+ transition={{ duration: crossfadeDuration, ease }}
1256
+ >
1257
+ {panelBody}
1258
+ </motion.div>
1259
+ ) : null}
1260
+ </AnimatePresence>
1261
+ {active === "commands" ? (
714
1262
  <motion.div
715
- key={active}
716
- data-og-session-chrome-panel-frame={active}
1263
+ key="commands"
717
1264
  initial={reduceMotion ? false : { opacity: 0 }}
718
- animate={{ opacity: 1, position: "relative" }}
719
- exit={
720
- reduceMotion
721
- ? { opacity: 1, position: "relative" }
722
- : {
723
- opacity: 0,
724
- position: "absolute",
725
- top: 0,
726
- left: 0,
727
- right: 0,
728
- }
729
- }
1265
+ animate={{ opacity: 1 }}
730
1266
  transition={{ duration: crossfadeDuration, ease }}
731
1267
  >
732
1268
  {panelBody}
733
1269
  </motion.div>
734
1270
  ) : null}
735
- </AnimatePresence>
1271
+ </div>
736
1272
  </div>
737
- </motion.div>
1273
+ </div>
738
1274
  </div>
739
1275
  </div>
740
1276
  </TooltipProvider>
@@ -744,93 +1280,72 @@ export function SessionChrome({
744
1280
  function IncomingPanel({
745
1281
  inputs,
746
1282
  onDismiss,
1283
+ onOpenSession,
747
1284
  }: {
748
1285
  inputs: SessionPendingInputPreview[];
749
1286
  onDismiss?: ((inputId: string) => void) | undefined;
1287
+ onOpenSession?: ((sessionId: string) => void) | undefined;
750
1288
  }) {
751
1289
  return (
752
- <ul
753
- className="flex flex-col gap-0.5"
754
- aria-label="Incoming updates"
755
- data-og-session-chrome-panel="incoming"
756
- >
757
- {inputs.map((input) => (
758
- <li
759
- key={input.id}
760
- className="group flex items-start gap-1.5 rounded-og-sm px-1.5 py-1 transition-colors hover:bg-[var(--_og-session-chrome-row-hover)]"
761
- >
762
- <span
763
- className={cn(
764
- "mt-px shrink-0 rounded px-1 py-px text-[10px] font-medium leading-4",
765
- input.classification === "action_required" || input.classification === "failure"
766
- ? "bg-og-status-waiting/12 text-og-status-waiting"
767
- : "bg-og-surface-3/80 text-og-fg-muted",
768
- )}
1290
+ <div>
1291
+ <p className="mb-2 text-og-xs text-og-fg-subtle">Waiting to be included in an agent turn.</p>
1292
+ <ul
1293
+ className="flex flex-col gap-0.5"
1294
+ aria-label="Incoming updates"
1295
+ data-og-session-chrome-panel="incoming"
1296
+ >
1297
+ {inputs.map((input) => (
1298
+ <li
1299
+ key={input.id}
1300
+ className="group flex items-start gap-1.5 rounded-og-sm px-1.5 py-1 transition-colors hover:bg-[var(--_og-session-chrome-row-hover)]"
769
1301
  >
770
- {pendingKindLabel(input.kind)}
771
- </span>
772
- <p className="min-w-0 flex-1 text-og-xs leading-4 text-og-fg">{input.summary}</p>
773
- {onDismiss ? (
774
- <div className="flex shrink-0 items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100 max-sm:opacity-100">
775
- <IconAction
776
- label={`Dismiss incoming ${pendingKindLabel(input.kind)}`}
777
- tip="Dismiss"
778
- onClick={() => onDismiss(input.id)}
779
- danger
780
- >
781
- <Trash2Icon className="size-3" />
782
- </IconAction>
1302
+ <span
1303
+ className={cn(
1304
+ "mt-px shrink-0 rounded px-1 py-px text-[10px] font-medium leading-4",
1305
+ input.classification === "action_required" || input.classification === "failure"
1306
+ ? "bg-og-status-waiting/12 text-og-status-waiting"
1307
+ : "bg-og-surface-3/80 text-og-fg-muted",
1308
+ )}
1309
+ >
1310
+ {pendingKindLabel(input.kind)}
1311
+ </span>
1312
+ <div className="min-w-0 flex-1">
1313
+ <p className="break-words text-og-xs leading-4 text-og-fg">{input.summary}</p>
1314
+ <ChildSessionLink
1315
+ kind={input.kind}
1316
+ sourceId={input.sourceId}
1317
+ onOpenSession={onOpenSession}
1318
+ />
783
1319
  </div>
784
- ) : null}
785
- </li>
786
- ))}
787
- </ul>
788
- );
789
- }
790
-
791
- function SteeringPanel({
792
- phase,
793
- stoppingKind,
794
- text,
795
- }: {
796
- phase: "submitting" | "accepted" | "stopping";
797
- stoppingKind?: "current" | "previous" | null | undefined;
798
- text?: string | undefined;
799
- }) {
800
- return (
801
- <div
802
- className="flex items-start gap-2 rounded-og-sm px-1.5 py-1"
803
- role="status"
804
- aria-live="polite"
805
- data-og-session-chrome-panel="steering"
806
- >
807
- <span className="mt-0.5 inline-flex size-5 shrink-0 items-center justify-center rounded-full bg-og-accent-soft text-og-accent">
808
- {phase === "submitting" || phase === "stopping" ? (
809
- <Loader2Icon className="size-3 animate-og-spin" aria-hidden="true" />
810
- ) : (
811
- <ZapIcon className="size-3" aria-hidden="true" />
812
- )}
813
- </span>
814
- <div className="min-w-0">
815
- {text ? (
816
- <p className="truncate text-og-xs font-medium leading-4 text-og-fg">{text}</p>
817
- ) : null}
818
- <p className={cn(text && "mt-0.5", "text-[10px] leading-4 text-og-fg-muted")}>
819
- {phase === "submitting"
820
- ? "Sending your latest direction…"
821
- : phase === "stopping"
822
- ? stoppingKind === "previous"
823
- ? "Direction saved. Waiting for the previous command to stop safely."
824
- : "Waiting for the current command to stop safely."
825
- : "Direction accepted. The agent will continue from it."}
826
- </p>
827
- </div>
1320
+ {onDismiss ? (
1321
+ <div className="flex shrink-0 items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100 max-sm:opacity-100">
1322
+ <IconAction
1323
+ label={`Dismiss incoming ${pendingKindLabel(input.kind)}`}
1324
+ tip="Dismiss"
1325
+ onClick={() => onDismiss(input.id)}
1326
+ danger
1327
+ >
1328
+ <Trash2Icon className="size-3" />
1329
+ </IconAction>
1330
+ </div>
1331
+ ) : null}
1332
+ </li>
1333
+ ))}
1334
+ </ul>
828
1335
  </div>
829
1336
  );
830
1337
  }
831
1338
 
832
1339
  function QueuePanel({
833
1340
  turns,
1341
+ optimistic,
1342
+ loadError,
1343
+ mutationError,
1344
+ onRefresh,
1345
+ onClearMutationError,
1346
+ onRetryOptimistic,
1347
+ onRestoreOptimistic,
1348
+ onRemoveOptimistic,
834
1349
  readOnly,
835
1350
  mutationFor,
836
1351
  replaceDraftFor,
@@ -842,6 +1357,14 @@ function QueuePanel({
842
1357
  onMove,
843
1358
  }: {
844
1359
  turns: SessionTurn[];
1360
+ optimistic: ComposerOptimisticMessage[];
1361
+ loadError: Error | null;
1362
+ mutationError: Error | null;
1363
+ onRefresh: () => Promise<void>;
1364
+ onClearMutationError: () => void;
1365
+ onRetryOptimistic?: ((clientEventId: string) => void) | undefined;
1366
+ onRestoreOptimistic?: ((clientEventId: string) => void) | undefined;
1367
+ onRemoveOptimistic?: ((clientEventId: string) => void) | undefined;
845
1368
  readOnly: boolean;
846
1369
  mutationFor: UseTurnQueueResult["mutationFor"];
847
1370
  replaceDraftFor?: string | null | undefined;
@@ -852,6 +1375,28 @@ function QueuePanel({
852
1375
  onRemove?: ((turnId: string) => void) | undefined;
853
1376
  onMove?: ((turnId: string, beforeTurnId: string | null) => void) | undefined;
854
1377
  }) {
1378
+ const reduceMotion = useReducedMotion();
1379
+ const turnIdsKey = turns.map((turn) => turn.id).join(",");
1380
+ const [interactiveTurnIds, setInteractiveTurnIds] = useState<ReadonlySet<string>>(
1381
+ () => new Set(),
1382
+ );
1383
+ useEffect(() => {
1384
+ const visibleTurnIds = new Set(turnIdsKey ? turnIdsKey.split(",") : []);
1385
+ setInteractiveTurnIds((current) => {
1386
+ const retained = new Set([...current].filter((turnId) => visibleTurnIds.has(turnId)));
1387
+ return retained.size === current.size ? current : retained;
1388
+ });
1389
+ if (visibleTurnIds.size === 0) return;
1390
+ // An optimistic queue row and its authoritative replacement have different
1391
+ // React identities. Do not expose an actionable control during that short
1392
+ // handoff: a pointer can otherwise press the outgoing DOM node and release
1393
+ // over its replacement, producing a completed-looking click with no event.
1394
+ const timer = setTimeout(
1395
+ () => setInteractiveTurnIds((current) => new Set([...current, ...visibleTurnIds])),
1396
+ reduceMotion ? 0 : 240,
1397
+ );
1398
+ return () => clearTimeout(timer);
1399
+ }, [reduceMotion, turnIdsKey]);
855
1400
  return (
856
1401
  <ol
857
1402
  className="flex flex-col gap-0.5"
@@ -862,6 +1407,7 @@ function QueuePanel({
862
1407
  const presentation = queuedTurnPresentation(turn);
863
1408
  const voice = presentation.kind !== "prompt";
864
1409
  const pending = mutationFor(turn.id);
1410
+ const settling = !interactiveTurnIds.has(turn.id);
865
1411
  const beforeUp = index > 0 ? (turns[index - 1]?.id ?? null) : null;
866
1412
  const beforeDown = index < turns.length - 1 ? (turns[index + 2]?.id ?? null) : null;
867
1413
  const showActions = !readOnly && (onEdit || onSteer || onRemove || onMove);
@@ -870,6 +1416,7 @@ function QueuePanel({
870
1416
  <li
871
1417
  key={turn.id}
872
1418
  data-queue-turn-id={turn.id}
1419
+ tabIndex={-1}
873
1420
  className="group flex flex-col gap-1 rounded-og-sm px-1.5 py-1 transition-colors hover:bg-[var(--_og-session-chrome-row-hover)]"
874
1421
  >
875
1422
  <div className="flex items-start gap-1.5">
@@ -883,9 +1430,10 @@ function QueuePanel({
883
1430
  {index + 1}
884
1431
  </span>
885
1432
  )}
886
- <p className="min-w-0 flex-1 truncate text-og-xs leading-4 text-og-fg">
887
- {presentation.text}
888
- </p>
1433
+ <CompactQueueItemContent
1434
+ text={presentation.text}
1435
+ annotations={turn.annotations ?? []}
1436
+ />
889
1437
  {showActions ? (
890
1438
  <div className="flex shrink-0 items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100 max-sm:opacity-100">
891
1439
  {onMove && turns.length > 1 ? (
@@ -893,7 +1441,7 @@ function QueuePanel({
893
1441
  <IconAction
894
1442
  label={`Move queued prompt ${index + 1} up`}
895
1443
  tip="Move up"
896
- disabled={pending !== null || index === 0}
1444
+ disabled={settling || pending !== null || index === 0}
897
1445
  onClick={() => onMove(turn.id, beforeUp)}
898
1446
  >
899
1447
  <ArrowUpIcon className="size-3" />
@@ -901,7 +1449,7 @@ function QueuePanel({
901
1449
  <IconAction
902
1450
  label={`Move queued prompt ${index + 1} down`}
903
1451
  tip="Move down"
904
- disabled={pending !== null || index >= turns.length - 1}
1452
+ disabled={settling || pending !== null || index >= turns.length - 1}
905
1453
  onClick={() => onMove(turn.id, beforeDown)}
906
1454
  >
907
1455
  <ArrowDownIcon className="size-3" />
@@ -911,14 +1459,16 @@ function QueuePanel({
911
1459
  {onSteer ? (
912
1460
  <IconAction
913
1461
  label={`Steer queued prompt ${index + 1}`}
1462
+ analyticsAction="steer"
1463
+ text="Steer"
914
1464
  tip={QUEUE_STEER_TIP}
915
- disabled={pending !== null}
1465
+ disabled={settling || pending !== null}
916
1466
  onClick={() => onSteer(turn.id)}
917
1467
  >
918
1468
  {pending === "steer" ? (
919
1469
  <Loader2Icon className="size-3 animate-og-spin" />
920
1470
  ) : (
921
- <ZapIcon className="size-3" />
1471
+ <CornerDownRightIcon className="size-3" />
922
1472
  )}
923
1473
  </IconAction>
924
1474
  ) : null}
@@ -926,7 +1476,7 @@ function QueuePanel({
926
1476
  <IconAction
927
1477
  label={`Edit queued prompt ${index + 1}`}
928
1478
  tip={QUEUE_EDIT_TIP}
929
- disabled={pending !== null}
1479
+ disabled={settling || pending !== null}
930
1480
  onClick={() => onEdit(turn)}
931
1481
  >
932
1482
  {pending === "edit" ? (
@@ -940,7 +1490,7 @@ function QueuePanel({
940
1490
  <IconAction
941
1491
  label={`Remove queued prompt ${index + 1}`}
942
1492
  tip={QUEUE_DELETE_TIP}
943
- disabled={pending !== null}
1493
+ disabled={settling || pending !== null}
944
1494
  onClick={() => onRemove(turn.id)}
945
1495
  danger
946
1496
  >
@@ -971,7 +1521,7 @@ function QueuePanel({
971
1521
  </button>
972
1522
  <button
973
1523
  type="button"
974
- className="rounded-og-sm bg-og-accent px-2 py-1 font-medium text-og-accent-fg hover:opacity-90 focus-visible:ring-2 focus-visible:ring-og-accent/40"
1524
+ className="rounded-og-sm border border-og-primary-border bg-og-primary text-og-primary-fg px-2 py-1 font-medium hover:bg-og-primary-hover focus-visible:ring-2 focus-visible:ring-og-accent/40"
975
1525
  onClick={onConfirmReplace}
976
1526
  >
977
1527
  Replace and edit
@@ -982,10 +1532,140 @@ function QueuePanel({
982
1532
  </li>
983
1533
  );
984
1534
  })}
1535
+ <AnimatePresence initial={false}>
1536
+ {optimistic.map((message, index) => (
1537
+ <motion.li
1538
+ key={message.clientEventId}
1539
+ aria-live="polite"
1540
+ data-optimistic-queue-message={message.clientEventId}
1541
+ initial={reduceMotion ? false : { opacity: 0 }}
1542
+ animate={{ opacity: 1 }}
1543
+ transition={{ duration: reduceMotion ? 0 : 0.12, ease: [0.22, 1, 0.36, 1] }}
1544
+ className={cn(
1545
+ "flex min-h-6 items-center gap-1.5 rounded-og-sm px-1.5 py-1",
1546
+ message.state === "failed" ? "bg-og-danger/8 text-og-danger" : "bg-og-accent-soft/45",
1547
+ )}
1548
+ >
1549
+ <span className="shrink-0 font-og-mono text-[10px] leading-4 text-og-fg-subtle">
1550
+ {turns.length + index + 1}
1551
+ </span>
1552
+ <div className="min-w-0 flex-1">
1553
+ <CompactQueueItemContent text={message.text} annotations={message.annotations} />
1554
+ {message.state === "failed" && message.error ? (
1555
+ <p className="break-words text-og-xs">{message.error}</p>
1556
+ ) : null}
1557
+ </div>
1558
+ <span className="sr-only">
1559
+ {message.state === "failed"
1560
+ ? message.outcomeUnknown
1561
+ ? "Not confirmed"
1562
+ : "Message not sent"
1563
+ : message.state === "sending"
1564
+ ? "Placing in queue"
1565
+ : "Queued"}
1566
+ </span>
1567
+ {message.state === "failed" ? (
1568
+ <div className="flex shrink-0 items-center gap-1 text-[10px]">
1569
+ {message.retryable !== false && onRetryOptimistic ? (
1570
+ <button
1571
+ type="button"
1572
+ className="rounded-og-sm px-1.5 py-1 font-medium hover:bg-og-surface-2 pointer-coarse:min-h-11 pointer-coarse:min-w-11"
1573
+ onClick={() => onRetryOptimistic(message.clientEventId)}
1574
+ >
1575
+ Retry
1576
+ </button>
1577
+ ) : null}
1578
+ {message.retryable === false && onRestoreOptimistic ? (
1579
+ <button
1580
+ type="button"
1581
+ className="rounded-og-sm px-1.5 py-1 font-medium hover:bg-og-surface-2 pointer-coarse:min-h-11 pointer-coarse:min-w-11"
1582
+ onClick={() => onRestoreOptimistic(message.clientEventId)}
1583
+ >
1584
+ Edit message
1585
+ </button>
1586
+ ) : null}
1587
+ {onRemoveOptimistic ? (
1588
+ <button
1589
+ type="button"
1590
+ aria-label="Dismiss unconfirmed queued prompt"
1591
+ className="rounded-og-sm p-1 text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg pointer-coarse:min-h-11 pointer-coarse:min-w-11"
1592
+ onClick={() => onRemoveOptimistic(message.clientEventId)}
1593
+ >
1594
+ <XIcon className="size-3" />
1595
+ </button>
1596
+ ) : null}
1597
+ </div>
1598
+ ) : message.state === "sending" ? (
1599
+ <Loader2Icon
1600
+ aria-hidden="true"
1601
+ className="size-3 shrink-0 animate-og-spin text-og-accent motion-reduce:animate-none"
1602
+ />
1603
+ ) : (
1604
+ <CheckIcon aria-hidden="true" className="size-3 shrink-0 text-og-accent" />
1605
+ )}
1606
+ </motion.li>
1607
+ ))}
1608
+ </AnimatePresence>
1609
+ {loadError ? (
1610
+ <li
1611
+ role="alert"
1612
+ className="flex items-center gap-1.5 rounded-og-sm bg-og-danger/8 px-1.5 py-1 text-og-xs text-og-danger"
1613
+ >
1614
+ <TriangleAlertIcon className="size-3 shrink-0" />
1615
+ <span className="min-w-0 flex-1">Queue unavailable.</span>
1616
+ <button
1617
+ type="button"
1618
+ className="rounded-og-sm px-1.5 py-1 font-medium hover:bg-og-surface-2 pointer-coarse:min-h-11 pointer-coarse:min-w-11"
1619
+ onClick={() => void onRefresh()}
1620
+ >
1621
+ Retry
1622
+ </button>
1623
+ </li>
1624
+ ) : null}
1625
+ {mutationError ? (
1626
+ <li
1627
+ role="alert"
1628
+ className="flex items-center gap-1.5 rounded-og-sm bg-og-danger/8 px-1.5 py-1 text-og-xs text-og-danger"
1629
+ >
1630
+ <TriangleAlertIcon className="size-3 shrink-0" />
1631
+ <span className="min-w-0 flex-1">Not confirmed. Check the queue before retrying.</span>
1632
+ <button
1633
+ type="button"
1634
+ className="rounded-og-sm px-1.5 py-1 font-medium hover:bg-og-surface-2 pointer-coarse:min-h-11 pointer-coarse:min-w-11"
1635
+ onClick={onClearMutationError}
1636
+ >
1637
+ Dismiss
1638
+ </button>
1639
+ </li>
1640
+ ) : null}
985
1641
  </ol>
986
1642
  );
987
1643
  }
988
1644
 
1645
+ function CompactQueueItemContent({
1646
+ text,
1647
+ annotations,
1648
+ }: {
1649
+ text: string;
1650
+ annotations: readonly TimelineAnnotationLike[];
1651
+ }) {
1652
+ const content = queueItemContent(text, annotations.length);
1653
+ return (
1654
+ <div className="flex min-w-0 flex-1 items-center gap-1.5">
1655
+ {content === "text" ? (
1656
+ <p className="min-w-0 flex-1 truncate text-og-xs leading-4 text-og-fg">{text}</p>
1657
+ ) : content === "unavailable" ? (
1658
+ <p className="min-w-0 flex-1 truncate text-og-xs leading-4 text-og-fg-muted italic">
1659
+ {QUEUE_ITEM_CONTENT_UNAVAILABLE}
1660
+ </p>
1661
+ ) : null}
1662
+ {annotations.length > 0 ? (
1663
+ <TimelineAnnotationsChip annotations={annotations} className="shrink-0" compact />
1664
+ ) : null}
1665
+ </div>
1666
+ );
1667
+ }
1668
+
989
1669
  function GoalPanel({
990
1670
  goal,
991
1671
  state,
@@ -997,17 +1677,19 @@ function GoalPanel({
997
1677
  elapsed: string | null;
998
1678
  readOnly: boolean;
999
1679
  }) {
1680
+ const formatError = useErrorMessage();
1000
1681
  const record = goal.goal;
1001
1682
  if (!record) return null;
1002
1683
  const canToggle = !readOnly && (record.status === "active" || record.status === "paused");
1684
+ const explanation = sessionChromeGoalPillExplanation(state, record);
1003
1685
 
1004
1686
  return (
1005
1687
  <div className="space-y-1.5" data-og-session-chrome-panel="goal">
1006
1688
  <div className="flex flex-wrap items-center gap-1.5 text-[10px] font-medium uppercase tracking-wider text-og-fg-subtle">
1007
- <span>{GOAL_LABEL[state]}</span>
1689
+ <span>{sessionChromeGoalPillLabel(state, record)}</span>
1008
1690
  {elapsed ? (
1009
1691
  <span className="tabular-nums normal-case tracking-normal text-og-fg-muted">
1010
- · {elapsed}
1692
+ · Created {elapsed} ago
1011
1693
  </span>
1012
1694
  ) : null}
1013
1695
  <span className="normal-case tracking-normal text-og-fg-muted">· v{record.version}</span>
@@ -1018,15 +1700,23 @@ function GoalPanel({
1018
1700
  <span className="font-medium text-og-fg">Done when</span> {record.successCriteria}
1019
1701
  </p>
1020
1702
  ) : null}
1703
+ {explanation ? (
1704
+ <p
1705
+ data-og-session-chrome-goal-explanation
1706
+ className="text-og-xs leading-4 text-og-fg-muted"
1707
+ >
1708
+ {explanation}
1709
+ </p>
1710
+ ) : null}
1021
1711
  {record.continuation?.lastError ? (
1022
1712
  <p className="rounded-og-sm bg-og-status-waiting/10 px-1.5 py-1 text-og-xs leading-4 text-og-status-waiting">
1023
- {record.continuation.lastError}
1713
+ {formatError(record.continuation.lastError)}
1024
1714
  </p>
1025
1715
  ) : null}
1026
1716
  <div className="flex flex-wrap items-center justify-between gap-1.5 pt-0.5">
1027
1717
  <div className="flex flex-wrap gap-1 text-[10px] text-og-fg-muted">
1028
1718
  <span className="rounded bg-og-surface-3/70 px-1 py-px">
1029
- {record.autoContinuations} auto-continues
1719
+ {record.autoContinuations} consecutive unattended continues
1030
1720
  </span>
1031
1721
  <span className="rounded bg-og-surface-3/70 px-1 py-px">
1032
1722
  {record.noProgressStreak} stalled
@@ -1081,7 +1771,9 @@ const QUEUE_EDIT_TIP = "Edit in composer";
1081
1771
  const QUEUE_DELETE_TIP = "Delete this queued prompt";
1082
1772
 
1083
1773
  function IconAction({
1774
+ text,
1084
1775
  label,
1776
+ analyticsAction,
1085
1777
  tip,
1086
1778
  onClick,
1087
1779
  disabled,
@@ -1089,6 +1781,9 @@ function IconAction({
1089
1781
  children,
1090
1782
  }: {
1091
1783
  label: string;
1784
+ /** Stable, content-free control label a host's product analytics may read. */
1785
+ analyticsAction?: "steer";
1786
+ text?: string;
1092
1787
  tip: ReactNode;
1093
1788
  onClick: () => void;
1094
1789
  disabled?: boolean;
@@ -1101,16 +1796,20 @@ function IconAction({
1101
1796
  <button
1102
1797
  type="button"
1103
1798
  aria-label={label}
1799
+ data-analytics-action={analyticsAction}
1104
1800
  disabled={disabled}
1105
1801
  onClick={onClick}
1106
1802
  className={cn(
1107
- "inline-flex size-6 items-center justify-center rounded-og-sm text-og-fg-subtle outline-hidden transition-colors",
1803
+ "inline-flex size-6 items-center justify-center rounded-og-sm outline-hidden transition-colors",
1108
1804
  "hover:bg-og-surface-2 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent/40",
1109
1805
  "disabled:pointer-events-none disabled:opacity-40 pointer-coarse:size-9",
1806
+ text ? "text-og-fg" : "text-og-fg-subtle",
1807
+ text && "w-auto gap-1 px-1.5 text-og-xs pointer-coarse:w-auto",
1110
1808
  danger && "hover:text-og-danger",
1111
1809
  )}
1112
1810
  >
1113
1811
  {children}
1812
+ {text}
1114
1813
  </button>
1115
1814
  </TooltipTrigger>
1116
1815
  <TooltipContent side="top">{tip}</TooltipContent>