@opengeni/react 0.48.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/README.md +138 -2
  2. package/dist/artifacts-document.d.ts +3 -0
  3. package/dist/artifacts-document.js +17 -0
  4. package/dist/artifacts-document.js.map +1 -0
  5. package/dist/artifacts-presentation.d.ts +3 -0
  6. package/dist/artifacts-presentation.js +17 -0
  7. package/dist/artifacts-presentation.js.map +1 -0
  8. package/dist/artifacts-spreadsheet.d.ts +3 -0
  9. package/dist/artifacts-spreadsheet.js +17 -0
  10. package/dist/artifacts-spreadsheet.js.map +1 -0
  11. package/dist/artifacts.d.ts +2 -0
  12. package/dist/artifacts.js +199 -0
  13. package/dist/artifacts.js.map +1 -0
  14. package/dist/{chunk-NJ7M2B6Z.js → chunk-5UQPNLIO.js} +16 -8
  15. package/dist/{chunk-NJ7M2B6Z.js.map → chunk-5UQPNLIO.js.map} +1 -1
  16. package/dist/{chunk-LHLYWG5R.js → chunk-6KLX2RH5.js} +461 -38
  17. package/dist/chunk-6KLX2RH5.js.map +1 -0
  18. package/dist/chunk-7WCLAEFV.js +2828 -0
  19. package/dist/chunk-7WCLAEFV.js.map +1 -0
  20. package/dist/{chunk-I4MPFDRP.js → chunk-B6WPZSTT.js} +104 -104
  21. package/dist/{chunk-I4MPFDRP.js.map → chunk-B6WPZSTT.js.map} +1 -1
  22. package/dist/{chunk-EKZH6IDG.js → chunk-C5ZPSSS5.js} +111 -1
  23. package/dist/chunk-C5ZPSSS5.js.map +1 -0
  24. package/dist/{chunk-OL6QVXGF.js → chunk-HA46CYQK.js} +152 -8
  25. package/dist/chunk-HA46CYQK.js.map +1 -0
  26. package/dist/chunk-HUIKIDM5.js +2148 -0
  27. package/dist/chunk-HUIKIDM5.js.map +1 -0
  28. package/dist/{chunk-FWXGSNXT.js → chunk-IGC6A2ZI.js} +585 -34
  29. package/dist/chunk-IGC6A2ZI.js.map +1 -0
  30. package/dist/chunk-JVYQRRC4.js +53 -0
  31. package/dist/chunk-JVYQRRC4.js.map +1 -0
  32. package/dist/chunk-KCGXPP5V.js +157 -0
  33. package/dist/chunk-KCGXPP5V.js.map +1 -0
  34. package/dist/chunk-LLOLUHVG.js +5937 -0
  35. package/dist/chunk-LLOLUHVG.js.map +1 -0
  36. package/dist/{chunk-WYA65Y3F.js → chunk-MKPIAGVL.js} +13 -3
  37. package/dist/chunk-MKPIAGVL.js.map +1 -0
  38. package/dist/chunk-PEI6RANI.js +3234 -0
  39. package/dist/chunk-PEI6RANI.js.map +1 -0
  40. package/dist/chunk-R3AZOAB5.js +933 -0
  41. package/dist/chunk-R3AZOAB5.js.map +1 -0
  42. package/dist/{chunk-O4VXOBGF.js → chunk-WSK7G5LE.js} +70 -2
  43. package/dist/chunk-WSK7G5LE.js.map +1 -0
  44. package/dist/{chunk-23EJ676W.js → chunk-XBZWOM2K.js} +9 -4
  45. package/dist/chunk-XBZWOM2K.js.map +1 -0
  46. package/dist/{chunk-XCNTXWBB.js → chunk-Y7OEDUJ5.js} +856 -338
  47. package/dist/chunk-Y7OEDUJ5.js.map +1 -0
  48. package/dist/client.d.ts +10 -2
  49. package/dist/clipboard.d.ts +1 -0
  50. package/dist/clipboard.js +7 -0
  51. package/dist/clipboard.js.map +1 -0
  52. package/dist/commands/index.d.ts +3 -3
  53. package/dist/commands/registry.d.ts +1 -1
  54. package/dist/commands/types.d.ts +1 -1
  55. package/dist/components/approval-surface.d.ts +2 -2
  56. package/dist/components/artifacts/artifact-surface.d.ts +18 -0
  57. package/dist/components/artifacts/document-editor.d.ts +172 -0
  58. package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
  59. package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
  60. package/dist/components/artifacts/editable-document.d.ts +24 -0
  61. package/dist/components/artifacts/editable-presentation.d.ts +24 -0
  62. package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
  63. package/dist/components/artifacts/index.d.ts +9 -0
  64. package/dist/components/artifacts/presentation-editor.d.ts +297 -0
  65. package/dist/components/artifacts/published-html-artifact-frame.d.ts +14 -0
  66. package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
  67. package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
  68. package/dist/components/browser-input.d.ts +7 -0
  69. package/dist/components/browser-viewer.d.ts +30 -0
  70. package/dist/components/chat-composer.d.ts +8 -8
  71. package/dist/components/code-editor.d.ts +25 -3
  72. package/dist/components/command-palette.d.ts +2 -2
  73. package/dist/components/composer-transcription-control.d.ts +2 -2
  74. package/dist/components/composer.d.ts +17 -17
  75. package/dist/components/computer-viewer.d.ts +24 -0
  76. package/dist/components/copy-button.d.ts +2 -2
  77. package/dist/components/desktop-viewer.d.ts +50 -22
  78. package/dist/components/diff-view.d.ts +1 -1
  79. package/dist/components/enrollment-consent.d.ts +1 -1
  80. package/dist/components/enrollment-device-flow.d.ts +1 -1
  81. package/dist/components/file-browser.d.ts +2 -2
  82. package/dist/components/fleet-tile.d.ts +1 -1
  83. package/dist/components/generated-video-player.d.ts +10 -0
  84. package/dist/components/human-input-form.d.ts +1 -1
  85. package/dist/components/human-input-surface.d.ts +2 -2
  86. package/dist/components/interaction-intervention-banner.d.ts +11 -0
  87. package/dist/components/interactive-workbench-viewers.d.ts +2 -0
  88. package/dist/components/machine-card.d.ts +2 -2
  89. package/dist/components/machine-dock-bar.d.ts +3 -3
  90. package/dist/components/machine-health-pill.d.ts +2 -2
  91. package/dist/components/machine-input-display.d.ts +1 -1
  92. package/dist/components/machine-metrics.d.ts +2 -2
  93. package/dist/components/machine-status-pill.d.ts +4 -4
  94. package/dist/components/machines/health.d.ts +1 -1
  95. package/dist/components/machines/machine-detail.d.ts +3 -3
  96. package/dist/components/machines/metric-history-chart.d.ts +1 -1
  97. package/dist/components/machines/metric-sparkline.d.ts +2 -2
  98. package/dist/components/machines/series.d.ts +2 -2
  99. package/dist/components/machines-dashboard.d.ts +2 -2
  100. package/dist/components/markdown.d.ts +1 -1
  101. package/dist/components/message-timeline.d.ts +12 -5
  102. package/dist/components/model-picker.d.ts +2 -2
  103. package/dist/components/model-policy-picker.d.ts +7 -7
  104. package/dist/components/pierre-diff.d.ts +1 -1
  105. package/dist/components/pierre-file.d.ts +1 -1
  106. package/dist/components/queue-draft-policy.d.ts +1 -1
  107. package/dist/components/queue-surface-implementation.d.ts +3 -3
  108. package/dist/components/queue-surface-state.d.ts +4 -4
  109. package/dist/components/queue-surface.d.ts +5 -5
  110. package/dist/components/sandbox-files.d.ts +3 -3
  111. package/dist/components/sandbox-terminal.d.ts +24 -4
  112. package/dist/components/sandbox-workspace.d.ts +28 -9
  113. package/dist/components/session-chrome.d.ts +4 -4
  114. package/dist/components/session-status.d.ts +2 -2
  115. package/dist/components/timeline-annotation-selection.d.ts +9 -0
  116. package/dist/components/timeline-annotations-dialog.d.ts +16 -0
  117. package/dist/components/timeline-annotations.d.ts +13 -0
  118. package/dist/components/tooltip.d.ts +4 -4
  119. package/dist/components/user-message-body.d.ts +2 -2
  120. package/dist/components/workbench-changes.d.ts +2 -2
  121. package/dist/components/workspace-dock.d.ts +1 -1
  122. package/dist/composer.d.ts +11 -11
  123. package/dist/composer.js +7 -7
  124. package/dist/hooks/internal.d.ts +1 -1
  125. package/dist/hooks/use-attached-browsers.d.ts +21 -0
  126. package/dist/hooks/use-auth-runs.d.ts +32 -0
  127. package/dist/hooks/use-available-models.d.ts +2 -2
  128. package/dist/hooks/use-billing-usage.d.ts +1 -1
  129. package/dist/hooks/use-browser-downloads.d.ts +25 -0
  130. package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
  131. package/dist/hooks/use-browser-identities.d.ts +30 -0
  132. package/dist/hooks/use-browser-session.d.ts +29 -0
  133. package/dist/hooks/use-browser-sessions.d.ts +32 -0
  134. package/dist/hooks/use-channels.d.ts +26 -0
  135. package/dist/hooks/use-codex-accounts.d.ts +2 -2
  136. package/dist/hooks/use-composer.d.ts +36 -9
  137. package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
  138. package/dist/hooks/use-computer-session.d.ts +29 -0
  139. package/dist/hooks/use-computer-sessions.d.ts +28 -0
  140. package/dist/hooks/use-desktop-stream.d.ts +5 -1
  141. package/dist/hooks/use-environments.d.ts +1 -1
  142. package/dist/hooks/use-file-attachments.d.ts +1 -1
  143. package/dist/hooks/use-goal.d.ts +2 -2
  144. package/dist/hooks/use-human-input.d.ts +2 -2
  145. package/dist/hooks/use-interaction-interventions.d.ts +27 -0
  146. package/dist/hooks/use-interaction-invalidation.d.ts +19 -0
  147. package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
  148. package/dist/hooks/use-machine-chip.d.ts +2 -2
  149. package/dist/hooks/use-machines.d.ts +4 -4
  150. package/dist/hooks/use-network-routes.d.ts +25 -0
  151. package/dist/hooks/use-packs.d.ts +7 -2
  152. package/dist/hooks/use-rigs.d.ts +1 -1
  153. package/dist/hooks/use-sandbox-files.d.ts +6 -4
  154. package/dist/hooks/use-sandbox-git.d.ts +6 -5
  155. package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
  156. package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
  157. package/dist/hooks/use-session-capabilities.d.ts +5 -4
  158. package/dist/hooks/use-session-control.d.ts +1 -1
  159. package/dist/hooks/use-session-events.d.ts +2 -2
  160. package/dist/hooks/use-session-lineage.d.ts +1 -1
  161. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  162. package/dist/hooks/use-session.d.ts +2 -2
  163. package/dist/hooks/use-site-auth-connections.d.ts +25 -0
  164. package/dist/hooks/use-slash-commands.d.ts +1 -1
  165. package/dist/hooks/use-supergrok-accounts.d.ts +26 -0
  166. package/dist/hooks/use-terminal-stream.d.ts +11 -2
  167. package/dist/hooks/use-turn-queue.d.ts +2 -2
  168. package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
  169. package/dist/hooks/use-voice-input.d.ts +1 -1
  170. package/dist/hooks/use-workspace-capture.d.ts +1 -1
  171. package/dist/hooks/use-workspace-edit.d.ts +1 -1
  172. package/dist/hooks/use-workspace-sessions.d.ts +1 -1
  173. package/dist/hooks/use-workspaces.d.ts +1 -1
  174. package/dist/index.d.ts +162 -155
  175. package/dist/index.js +1822 -1703
  176. package/dist/index.js.map +1 -1
  177. package/dist/interaction.d.ts +33 -0
  178. package/dist/interaction.js +550 -0
  179. package/dist/interaction.js.map +1 -0
  180. package/dist/interactive-workbench-viewers-TOHBXJEE.js +15 -0
  181. package/dist/interactive-workbench-viewers-TOHBXJEE.js.map +1 -0
  182. package/dist/lib/host-platform.d.ts +5 -0
  183. package/dist/lib/relay-wire.d.ts +24 -1
  184. package/dist/lib/sandbox-liveness.d.ts +9 -0
  185. package/dist/lib/terminal-capability.d.ts +5 -0
  186. package/dist/lib/xterm-theme.d.ts +1 -1
  187. package/dist/machines.d.ts +30 -30
  188. package/dist/machines.js +2 -2
  189. package/dist/model-policy.d.ts +1 -1
  190. package/dist/model-policy.js +1 -1
  191. package/dist/provider.d.ts +6 -5
  192. package/dist/{queue-surface-implementation-U5JFG7HT.js → queue-surface-implementation-2KVJTYXH.js} +100 -48
  193. package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
  194. package/dist/realtime/dropdown-menu.d.ts +8 -8
  195. package/dist/realtime/realtime-control.d.ts +8 -8
  196. package/dist/realtime.d.ts +4 -4
  197. package/dist/realtime.js +38 -11
  198. package/dist/realtime.js.map +1 -1
  199. package/dist/session-context.d.ts +28 -2
  200. package/dist/session-ui.d.ts +14 -14
  201. package/dist/session-ui.js +5 -4
  202. package/dist/session.d.ts +28 -28
  203. package/dist/session.js +10 -10
  204. package/dist/timeline/activity-rail.d.ts +5 -3
  205. package/dist/timeline/disclosure-context.d.ts +1 -1
  206. package/dist/timeline/fleet-decision-projection.d.ts +1 -1
  207. package/dist/timeline/fleet-decision-row.d.ts +3 -3
  208. package/dist/timeline/index.d.ts +19 -19
  209. package/dist/timeline/parsers.d.ts +4 -1
  210. package/dist/timeline/projection.d.ts +3 -3
  211. package/dist/timeline/registry.d.ts +8 -2
  212. package/dist/timeline/screenshot-lightbox.d.ts +2 -2
  213. package/dist/timeline/shared.d.ts +21 -10
  214. package/dist/timeline/tool-diff.d.ts +2 -2
  215. package/dist/timeline/tool-renderers.d.ts +1 -1
  216. package/dist/timeline/turn-summary.d.ts +3 -3
  217. package/dist/timeline/types.d.ts +44 -3
  218. package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
  219. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
  220. package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
  221. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
  222. package/package.json +36 -7
  223. package/src/artifacts-document.ts +31 -0
  224. package/src/artifacts-presentation.ts +36 -0
  225. package/src/artifacts-spreadsheet.ts +21 -0
  226. package/src/artifacts.ts +89 -0
  227. package/src/client.ts +157 -1
  228. package/src/clipboard.ts +2 -0
  229. package/src/components/artifacts/artifact-surface.tsx +93 -0
  230. package/src/components/artifacts/document-editor.tsx +2255 -0
  231. package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
  232. package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
  233. package/src/components/artifacts/editable-document.tsx +657 -0
  234. package/src/components/artifacts/editable-presentation.tsx +1020 -0
  235. package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
  236. package/src/components/artifacts/index.ts +104 -0
  237. package/src/components/artifacts/presentation-editor.tsx +3148 -0
  238. package/src/components/artifacts/published-html-artifact-frame.tsx +38 -0
  239. package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
  240. package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
  241. package/src/components/browser-input.ts +65 -0
  242. package/src/components/browser-viewer.tsx +2702 -0
  243. package/src/components/chat-composer.tsx +13 -0
  244. package/src/components/code-editor.tsx +89 -30
  245. package/src/components/composer-transcription-control.tsx +10 -6
  246. package/src/components/computer-viewer.tsx +1636 -0
  247. package/src/components/desktop-viewer.tsx +215 -74
  248. package/src/components/generated-video-player.tsx +89 -0
  249. package/src/components/interaction-intervention-banner.tsx +99 -0
  250. package/src/components/interactive-workbench-viewers.ts +2 -0
  251. package/src/components/machine-input-display.ts +3 -0
  252. package/src/components/machines/machine-detail.tsx +1 -1
  253. package/src/components/message-timeline.tsx +289 -32
  254. package/src/components/model-policy-picker.tsx +15 -0
  255. package/src/components/queue-surface-implementation.tsx +116 -60
  256. package/src/components/sandbox-files.tsx +2 -2
  257. package/src/components/sandbox-terminal.tsx +128 -46
  258. package/src/components/sandbox-workspace.tsx +256 -90
  259. package/src/components/session-chrome.tsx +53 -13
  260. package/src/components/timeline-annotation-selection.tsx +221 -0
  261. package/src/components/timeline-annotations-dialog.tsx +213 -0
  262. package/src/components/timeline-annotations.tsx +84 -0
  263. package/src/components/workbench-changes.tsx +188 -31
  264. package/src/components/workspace-dock.tsx +59 -5
  265. package/src/hooks/use-attached-browsers.ts +154 -0
  266. package/src/hooks/use-auth-runs.ts +278 -0
  267. package/src/hooks/use-browser-downloads.ts +218 -0
  268. package/src/hooks/use-browser-frame-stream.ts +425 -0
  269. package/src/hooks/use-browser-identities.ts +266 -0
  270. package/src/hooks/use-browser-session.ts +543 -0
  271. package/src/hooks/use-browser-sessions.ts +286 -0
  272. package/src/hooks/use-channels.ts +99 -0
  273. package/src/hooks/use-composer.ts +769 -24
  274. package/src/hooks/use-computer-frame-stream.ts +434 -0
  275. package/src/hooks/use-computer-session.ts +462 -0
  276. package/src/hooks/use-computer-sessions.ts +251 -0
  277. package/src/hooks/use-desktop-stream.ts +39 -7
  278. package/src/hooks/use-interaction-interventions.ts +242 -0
  279. package/src/hooks/use-interaction-invalidation.ts +66 -0
  280. package/src/hooks/use-machine-chip.ts +15 -3
  281. package/src/hooks/use-machines.ts +33 -3
  282. package/src/hooks/use-network-routes.ts +214 -0
  283. package/src/hooks/use-packs.ts +51 -0
  284. package/src/hooks/use-sandbox-files.ts +98 -55
  285. package/src/hooks/use-sandbox-git.ts +93 -38
  286. package/src/hooks/use-sandbox-terminal.ts +3 -2
  287. package/src/hooks/use-session-capabilities.ts +59 -46
  288. package/src/hooks/use-site-auth-connections.ts +226 -0
  289. package/src/hooks/use-supergrok-accounts.ts +81 -0
  290. package/src/hooks/use-terminal-stream.ts +359 -47
  291. package/src/hooks/use-video-artifact-playback.ts +28 -0
  292. package/src/hooks/use-workspace-edit.ts +2 -1
  293. package/src/index.ts +18 -0
  294. package/src/interaction.ts +101 -0
  295. package/src/lib/host-platform.ts +10 -0
  296. package/src/lib/relay-wire.ts +47 -4
  297. package/src/lib/sandbox-liveness.ts +11 -0
  298. package/src/lib/terminal-capability.ts +10 -0
  299. package/src/model-policy.ts +19 -4
  300. package/src/provider.tsx +133 -1
  301. package/src/realtime/realtime-control.tsx +34 -6
  302. package/src/session-context.ts +156 -1
  303. package/src/timeline/activity-rail.tsx +25 -3
  304. package/src/timeline/index.ts +3 -0
  305. package/src/timeline/parsers.ts +14 -1
  306. package/src/timeline/projection.ts +269 -14
  307. package/src/timeline/registry.ts +13 -1
  308. package/src/timeline/screenshot-lightbox.tsx +19 -9
  309. package/src/timeline/shared.tsx +56 -8
  310. package/src/timeline/tool-renderers.tsx +303 -50
  311. package/src/timeline/types.ts +57 -3
  312. package/styles/compiled.css +312 -21
  313. package/styles/index.css +15 -0
  314. package/styles/responsive.css +13 -1
  315. package/dist/chunk-23EJ676W.js.map +0 -1
  316. package/dist/chunk-EKZH6IDG.js.map +0 -1
  317. package/dist/chunk-FWXGSNXT.js.map +0 -1
  318. package/dist/chunk-LHLYWG5R.js.map +0 -1
  319. package/dist/chunk-O4VXOBGF.js.map +0 -1
  320. package/dist/chunk-OL6QVXGF.js.map +0 -1
  321. package/dist/chunk-WYA65Y3F.js.map +0 -1
  322. package/dist/chunk-XCNTXWBB.js.map +0 -1
  323. package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
  324. package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
  325. package/dist/queue-surface-implementation-U5JFG7HT.js.map +0 -1
@@ -1,5 +1,10 @@
1
- import type { SessionEvent, SessionStatus } from "@opengeni/sdk";
2
- const { default: fleetDecisionItem } = await import("./fleet-decision-projection");
1
+ import {
2
+ parseMediaGenerationResult,
3
+ type SessionEvent,
4
+ type SessionStatus,
5
+ type TimelineAnnotation,
6
+ } from "@opengeni/sdk";
7
+ import fleetDecisionItem from "./fleet-decision-projection";
3
8
  import {
4
9
  CREDIT_EXHAUSTION_MESSAGE,
5
10
  humanizeFailureReason,
@@ -21,6 +26,7 @@ import type {
21
26
  TimelineItem,
22
27
  TurnEndItem,
23
28
  ToolCallItem,
29
+ ToolCallTruncation,
24
30
  WorkerItem,
25
31
  } from "./types";
26
32
 
@@ -43,6 +49,8 @@ export { toolDisplayName, mcpToolLeaf, toolMatchesLeaf } from "./tool-display-na
43
49
  /** Tool leaves on the first-party OpenGeni MCP server that operate on sessions. */
44
50
  const WORKER_SPAWN_TOOL = "session_create";
45
51
  const WORKER_MESSAGE_TOOL = "session_send_message";
52
+ const WORKER_FAILURE_CODE_MAX_LENGTH = 128;
53
+ const WORKER_FAILURE_MESSAGE_MAX_UTF8_BYTES = 1_024;
46
54
 
47
55
  /**
48
56
  * Tools whose durable side-effect events already own the timeline (MemoryRow).
@@ -61,6 +69,56 @@ const WORKER_MESSAGE_TOOL = "session_send_message";
61
69
  * render as machine-input rows.
62
70
  */
63
71
  const LANDMARK_ONLY_TOOL_LEAVES = new Set(["memory_save", "memory_correct"]);
72
+ const TIMELINE_ANNOTATION_ANSI_SEQUENCE = new RegExp("\\u001B\\[[0-?]*[ -/]*[@-~]", "g");
73
+
74
+ function safeTimelineAnnotationJson(value: unknown): string {
75
+ try {
76
+ return JSON.stringify(value) ?? "";
77
+ } catch {
78
+ return String(value ?? "");
79
+ }
80
+ }
81
+
82
+ function timelineAnnotationToolOutputValue(value: unknown, depth = 0): string {
83
+ if (depth > 8 || value === null || value === undefined) {
84
+ return value == null ? "" : safeTimelineAnnotationJson(value);
85
+ }
86
+ if (typeof value === "string") return value;
87
+ if (typeof value !== "object" || Array.isArray(value)) {
88
+ return safeTimelineAnnotationJson(value);
89
+ }
90
+ const record = value as Record<string, unknown>;
91
+ if (record.type === "text" && typeof record.text === "string") return record.text;
92
+ if (Array.isArray(record.content)) {
93
+ const text = record.content.find(
94
+ (part) =>
95
+ part !== null &&
96
+ typeof part === "object" &&
97
+ (part as { type?: unknown }).type === "text" &&
98
+ typeof (part as { text?: unknown }).text === "string",
99
+ ) as { text: string } | undefined;
100
+ if (text) return text.text;
101
+ }
102
+ if ("structuredContent" in record) {
103
+ return timelineAnnotationToolOutputValue(record.structuredContent, depth + 1);
104
+ }
105
+ if ("result" in record) return timelineAnnotationToolOutputValue(record.result, depth + 1);
106
+ return safeTimelineAnnotationJson(value);
107
+ }
108
+
109
+ /** Browser mirror of the server's canonical annotation source projection. */
110
+ function timelineAnnotationToolOutputText(output: unknown): string | null {
111
+ const raw = timelineAnnotationToolOutputValue(output);
112
+ const marker = raw.indexOf("\nOutput:\n");
113
+ const text = (
114
+ marker >= 0
115
+ ? raw.slice(marker + "\nOutput:\n".length)
116
+ : raw.startsWith("Output:\n")
117
+ ? raw.slice("Output:\n".length)
118
+ : raw
119
+ ).replace(TIMELINE_ANNOTATION_ANSI_SEQUENCE, "");
120
+ return text.length > 0 ? text : null;
121
+ }
64
122
 
65
123
  export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
66
124
  const items: TimelineItem[] = [];
@@ -134,6 +192,15 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
134
192
  kind: "user-message",
135
193
  id: event.id,
136
194
  text: voiceMessage?.text ?? stringValue(payload.text),
195
+ annotations: timelineAnnotations(payload.annotations),
196
+ annotationSource: {
197
+ kind: "user_message",
198
+ eventId: event.id,
199
+ eventType: "user.message",
200
+ sequence: event.sequence,
201
+ turnId,
202
+ text: voiceMessage?.text ?? stringValue(payload.text),
203
+ },
137
204
  ...(voiceMessage ? { presentation: voiceMessage.presentation } : {}),
138
205
  resources: resourceRefs(payload.resources),
139
206
  tools: toolRefs(payload.tools),
@@ -212,6 +279,17 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
212
279
  // Completion time is what the footer shows ("finished at"); keep the
213
280
  // first-delta stamp only until this event arrives.
214
281
  open.occurredAt = event.occurredAt;
282
+ open.annotationSource =
283
+ text && open.text === text
284
+ ? {
285
+ kind: "assistant_message",
286
+ eventId: event.id,
287
+ eventType: "agent.message.completed",
288
+ sequence: event.sequence,
289
+ turnId,
290
+ text,
291
+ }
292
+ : undefined;
215
293
  // The SDK can emit a hosted-tool item only after its provider-native
216
294
  // operation has completed, even though answer deltas were already
217
295
  // streamed. The completed message event is the durable ordering
@@ -231,6 +309,14 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
231
309
  text,
232
310
  streaming: false,
233
311
  occurredAt: event.occurredAt,
312
+ annotationSource: {
313
+ kind: "assistant_message",
314
+ eventId: event.id,
315
+ eventType: "agent.message.completed",
316
+ sequence: event.sequence,
317
+ turnId,
318
+ text,
319
+ },
234
320
  });
235
321
  }
236
322
  break;
@@ -275,6 +361,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
275
361
  action: toolMatchesLeaf(name, WORKER_SPAWN_TOOL) ? "spawn" : "message",
276
362
  prompt: workerPrompt(args),
277
363
  workerSessionId: extractSessionRef(args),
364
+ failure: null,
278
365
  status: "running",
279
366
  occurredAt: event.occurredAt,
280
367
  });
@@ -312,6 +399,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
312
399
  name,
313
400
  arguments: args,
314
401
  output: undefined,
402
+ truncation: null,
315
403
  // The provider-native item drives the per-tool renderers (apply_patch
316
404
  // operation, computer_call action, web_search providerData, …).
317
405
  raw: payload.raw,
@@ -332,12 +420,35 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
332
420
  // isError result) settles to "failed" too, so WorkerRow surfaces it.
333
421
  target.status = isErrorOutput(payload) ? "failed" : "complete";
334
422
  target.workerSessionId = target.workerSessionId ?? extractSessionRef(payload.output);
423
+ target.failure = target.status === "failed" ? workerFailure(payload.output) : null;
335
424
  break;
336
425
  }
337
426
  // An output carrying an explicit error flag (or an MCP isError result)
338
427
  // settles the tool to "failed" so the renderer can surface it loudly.
428
+ const truncation = toolCallTruncation(payload);
339
429
  target.status = isErrorOutput(payload) ? "failed" : "complete";
340
- target.output = payload.output;
430
+ target.output = Object.prototype.hasOwnProperty.call(payload, "output")
431
+ ? payload.output
432
+ : truncation && typeof payload.preview === "string"
433
+ ? payload.preview
434
+ : payload.output;
435
+ target.truncation = truncation;
436
+ const sourceText = timelineAnnotationToolOutputText(
437
+ Object.prototype.hasOwnProperty.call(payload, "output")
438
+ ? payload.output
439
+ : payload.preview,
440
+ );
441
+ if (sourceText !== null) {
442
+ target.annotationSource = {
443
+ kind: "tool_output",
444
+ eventId: event.id,
445
+ eventType: "agent.toolCall.output",
446
+ sequence: event.sequence,
447
+ turnId,
448
+ text: sourceText,
449
+ label: target.name,
450
+ };
451
+ }
341
452
  break;
342
453
  }
343
454
 
@@ -540,6 +651,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
540
651
 
541
652
  case "tool.auth_needed":
542
653
  case "credential.auth_needed": {
654
+ const capability = capabilityAuthorizationRequest(payload.capability);
543
655
  if (
544
656
  event.type === "tool.auth_needed" &&
545
657
  !stringValue(payload.toolName) &&
@@ -562,7 +674,11 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
562
674
  id: event.id,
563
675
  turnId,
564
676
  serverId: typeof payload.serverId === "string" ? payload.serverId : null,
565
- source: event.type === "tool.auth_needed" ? "tool" : "credential",
677
+ source: capability
678
+ ? "capability"
679
+ : event.type === "tool.auth_needed"
680
+ ? "tool"
681
+ : "credential",
566
682
  providerDomain: stringValue(payload.providerDomain),
567
683
  connectionId: typeof payload.connectionId === "string" ? payload.connectionId : null,
568
684
  reason: authNeededReason(payload.reason),
@@ -571,6 +687,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
571
687
  toolName: typeof payload.toolName === "string" ? payload.toolName : null,
572
688
  authorizationUrl:
573
689
  typeof payload.authorizationUrl === "string" ? payload.authorizationUrl : null,
690
+ capability,
574
691
  occurredAt: event.occurredAt,
575
692
  });
576
693
  break;
@@ -1187,6 +1304,8 @@ function isTurnBoundary(group: TimelineGroup | undefined): boolean {
1187
1304
  (group?.kind === "item" &&
1188
1305
  (group.item.kind === "user-message" ||
1189
1306
  group.item.kind === "context-compaction" ||
1307
+ (group.item.kind === "machine-input-batch" &&
1308
+ group.item.members.some((member) => member.kind === "media_generation_result")) ||
1190
1309
  (group.item.kind === "notice" && group.item.tone === "input")))
1191
1310
  );
1192
1311
  }
@@ -1315,6 +1434,7 @@ function machineInputMembers(value: unknown): MachineInputBatchItem["members"] {
1315
1434
  "agent_message",
1316
1435
  "agent_steer_instruction",
1317
1436
  "child_terminal_result",
1437
+ "media_generation_result",
1318
1438
  ]);
1319
1439
  const classifications = new Set<MachineInputBatchItem["members"][number]["classification"]>([
1320
1440
  "success",
@@ -1333,16 +1453,24 @@ function machineInputMembers(value: unknown): MachineInputBatchItem["members"] {
1333
1453
  member.classification as MachineInputBatchItem["members"][number]["classification"],
1334
1454
  ) &&
1335
1455
  typeof member.sourceId === "string"
1336
- ? [
1337
- {
1338
- id: member.id,
1339
- kind: member.kind as MachineInputBatchItem["members"][number]["kind"],
1340
- classification:
1341
- member.classification as MachineInputBatchItem["members"][number]["classification"],
1342
- sourceId: member.sourceId,
1343
- summary: stringValue(member.summary),
1344
- },
1345
- ]
1456
+ ? (() => {
1457
+ const kind = member.kind as MachineInputBatchItem["members"][number]["kind"];
1458
+ const result =
1459
+ kind === "media_generation_result"
1460
+ ? parseMediaGenerationResult(member.result)
1461
+ : null;
1462
+ return [
1463
+ {
1464
+ id: member.id,
1465
+ kind,
1466
+ classification:
1467
+ member.classification as MachineInputBatchItem["members"][number]["classification"],
1468
+ sourceId: member.sourceId,
1469
+ summary: stringValue(member.summary),
1470
+ ...(result ? { result } : {}),
1471
+ },
1472
+ ];
1473
+ })()
1346
1474
  : [];
1347
1475
  })
1348
1476
  : [];
@@ -1378,6 +1506,25 @@ function resourceRefs(value: unknown): import("@opengeni/sdk").ResourceRef[] {
1378
1506
  });
1379
1507
  }
1380
1508
 
1509
+ function timelineAnnotations(value: unknown): TimelineAnnotation[] {
1510
+ if (!Array.isArray(value)) return [];
1511
+ return value.filter((candidate): candidate is TimelineAnnotation => {
1512
+ const annotation = asRecord(candidate);
1513
+ const source = asRecord(annotation.source);
1514
+ return (
1515
+ typeof annotation.id === "string" &&
1516
+ typeof annotation.ordinal === "number" &&
1517
+ typeof annotation.quote === "string" &&
1518
+ typeof annotation.note === "string" &&
1519
+ typeof source.eventId === "string" &&
1520
+ typeof source.eventType === "string" &&
1521
+ typeof source.sequence === "number" &&
1522
+ typeof source.startOffset === "number" &&
1523
+ typeof source.endOffset === "number"
1524
+ );
1525
+ });
1526
+ }
1527
+
1381
1528
  function toolRefs(value: unknown): import("@opengeni/sdk").ToolRef[] {
1382
1529
  if (!Array.isArray(value)) {
1383
1530
  return [];
@@ -1440,6 +1587,31 @@ function isErrorOutput(payload: Record<string, unknown>): boolean {
1440
1587
  );
1441
1588
  }
1442
1589
 
1590
+ function toolCallTruncation(payload: Record<string, unknown>): ToolCallTruncation | null {
1591
+ const truncation = asRecord(payload.truncation);
1592
+ const fullEvidence = asRecord(truncation.fullEvidence);
1593
+ const surface = stringValue(truncation.surface);
1594
+ const reason = stringValue(truncation.reason);
1595
+ if (
1596
+ truncation.truncated !== true ||
1597
+ !surface ||
1598
+ !reason ||
1599
+ typeof fullEvidence.available !== "boolean"
1600
+ ) {
1601
+ return null;
1602
+ }
1603
+ return {
1604
+ truncated: true,
1605
+ surface,
1606
+ reason,
1607
+ omittedBytes: numberOrNull(truncation.omittedBytes),
1608
+ fullEvidence: {
1609
+ available: fullEvidence.available,
1610
+ reason: stringValue(fullEvidence.reason) || null,
1611
+ },
1612
+ };
1613
+ }
1614
+
1443
1615
  function findOpenCall(
1444
1616
  items: TimelineItem[],
1445
1617
  callId: string | null,
@@ -1563,6 +1735,37 @@ function authNeededReason(value: unknown): AuthNeededItem["reason"] {
1563
1735
  : null;
1564
1736
  }
1565
1737
 
1738
+ function capabilityAuthorizationRequest(
1739
+ value: unknown,
1740
+ ): NonNullable<AuthNeededItem["capability"]> | null {
1741
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
1742
+ const record = value as Record<string, unknown>;
1743
+ const kind = record.kind;
1744
+ const source = record.source;
1745
+ const action = record.action;
1746
+ if (
1747
+ typeof record.id !== "string" ||
1748
+ typeof record.name !== "string" ||
1749
+ !["pack", "mcp", "api", "skill", "plugin"].includes(String(kind)) ||
1750
+ !["built_in", "library", "configured", "public_registry", "registry", "manual"].includes(
1751
+ String(source),
1752
+ ) ||
1753
+ !["connect", "add_credentials", "enable"].includes(String(action)) ||
1754
+ typeof record.rationale !== "string"
1755
+ ) {
1756
+ return null;
1757
+ }
1758
+ return {
1759
+ id: record.id,
1760
+ name: record.name,
1761
+ kind: kind as NonNullable<AuthNeededItem["capability"]>["kind"],
1762
+ source: source as NonNullable<AuthNeededItem["capability"]>["source"],
1763
+ action: action as NonNullable<AuthNeededItem["capability"]>["action"],
1764
+ rationale: record.rationale,
1765
+ requiredVariables: stringList(record.requiredVariables),
1766
+ };
1767
+ }
1768
+
1566
1769
  function stringList(value: unknown): string[] {
1567
1770
  return Array.isArray(value)
1568
1771
  ? value.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0)
@@ -1598,6 +1801,58 @@ function workerPrompt(args: unknown): string | null {
1598
1801
  return null;
1599
1802
  }
1600
1803
 
1804
+ function boundedWorkerFailureMessage(value: string): string | null {
1805
+ const normalized = value.replace(/[\u0000-\u001f\u007f]+/g, " ").trim();
1806
+ if (!normalized) return null;
1807
+ const encoded = new TextEncoder().encode(normalized);
1808
+ if (encoded.byteLength <= WORKER_FAILURE_MESSAGE_MAX_UTF8_BYTES) return normalized;
1809
+ let end = WORKER_FAILURE_MESSAGE_MAX_UTF8_BYTES;
1810
+ while (end > 0 && (encoded[end]! & 0xc0) === 0x80) end -= 1;
1811
+ return new TextDecoder().decode(encoded.slice(0, end)).trim();
1812
+ }
1813
+
1814
+ function boundedWorkerFailure(value: unknown): WorkerItem["failure"] {
1815
+ const record = asRecord(value);
1816
+ const code = typeof record.code === "string" ? record.code.trim() : "";
1817
+ const message =
1818
+ typeof record.message === "string" ? boundedWorkerFailureMessage(record.message) : null;
1819
+ if (!/^[A-Za-z0-9._:-]{1,128}$/.test(code) || !message) return null;
1820
+ return { code: code.slice(0, WORKER_FAILURE_CODE_MAX_LENGTH), message };
1821
+ }
1822
+
1823
+ /**
1824
+ * Extract the API's structured orchestration failure from raw objects, JSON
1825
+ * text, or retained MCP CallToolResult wrappers. Legacy flat strings remain
1826
+ * unprojected rather than being mistaken for the trusted diagnostic contract.
1827
+ */
1828
+ function workerFailure(value: unknown, depth = 0): WorkerItem["failure"] {
1829
+ if (depth > 6 || value === null || value === undefined) return null;
1830
+ if (typeof value === "string") {
1831
+ const parsed = tryParseJson(value);
1832
+ return parsed === value ? null : workerFailure(parsed, depth + 1);
1833
+ }
1834
+ if (Array.isArray(value)) {
1835
+ for (const entry of value) {
1836
+ const failure = workerFailure(entry, depth + 1);
1837
+ if (failure) return failure;
1838
+ }
1839
+ return null;
1840
+ }
1841
+ if (typeof value !== "object") return null;
1842
+ const record = value as Record<string, unknown>;
1843
+ const direct = boundedWorkerFailure(record.error);
1844
+ if (direct) return direct;
1845
+ for (const key of ["structuredContent", "content", "result", "output"] as const) {
1846
+ if (!(key in record)) continue;
1847
+ const failure = workerFailure(record[key], depth + 1);
1848
+ if (failure) return failure;
1849
+ }
1850
+ if (record.type === "text" && typeof record.text === "string") {
1851
+ return workerFailure(record.text, depth + 1);
1852
+ }
1853
+ return null;
1854
+ }
1855
+
1601
1856
  /**
1602
1857
  * Find a session id in orchestration tool arguments or output. Handles raw
1603
1858
  * objects, JSON strings, and MCP tool results (`{ content: [{ type: "text",
@@ -1,4 +1,4 @@
1
- import type { RetainedArtifactReference } from "@opengeni/sdk";
1
+ import type { RetainedArtifactReference, VideoArtifactPlaybackSource } from "@opengeni/sdk";
2
2
  import type { ComponentType } from "react";
3
3
  import { mcpToolLeaf } from "./tool-display-name";
4
4
  import type { ToolCallItem } from "./types";
@@ -24,6 +24,7 @@ import type { ToolCallItem } from "./types";
24
24
  export type ToolRendererProps = {
25
25
  item: ToolCallItem;
26
26
  loadRetainedScreenshot?: RetainedScreenshotLoader | undefined;
27
+ loadRetainedArtifact?: RetainedArtifactLoader | undefined;
27
28
  };
28
29
 
29
30
  export type RetainedScreenshotLoader = (
@@ -31,6 +32,17 @@ export type RetainedScreenshotLoader = (
31
32
  signal: AbortSignal,
32
33
  ) => Promise<Uint8Array | null>;
33
34
 
35
+ export type RetainedArtifactLoader = (
36
+ artifact: RetainedArtifactReference,
37
+ signal: AbortSignal,
38
+ ) => Promise<Uint8Array | { url: string } | null>;
39
+
40
+ /** Mint an expiring source for native browser playback without loading video bytes in JS. */
41
+ export type VideoArtifactPlaybackLoader = (
42
+ artifactId: string,
43
+ signal: AbortSignal,
44
+ ) => Promise<VideoArtifactPlaybackSource>;
45
+
34
46
  export type ToolRenderer = ComponentType<ToolRendererProps>;
35
47
 
36
48
  /** A registry entry: which key it matches and the component that renders it. */
@@ -19,7 +19,7 @@ import { usePortalTokenStyle } from "../lib/use-portal-token-style";
19
19
  -------------------------------------------------------------------------- */
20
20
 
21
21
  type LightboxController = {
22
- open: (src: string, caption?: string, source?: HTMLElement | null) => void;
22
+ open: (src: string, caption?: string, source?: HTMLElement | null, label?: string) => void;
23
23
  };
24
24
 
25
25
  const LightboxContext = createContext<LightboxController | null>(null);
@@ -59,14 +59,21 @@ export function LightboxProvider({ children }: { children: ReactNode }) {
59
59
  }
60
60
 
61
61
  function LightboxRoot({ children }: { children: ReactNode }) {
62
- const [state, setState] = useState<{ src: string; caption?: string } | null>(null);
62
+ const [state, setState] = useState<{
63
+ src: string;
64
+ caption?: string;
65
+ label: string;
66
+ } | null>(null);
63
67
  const [tokenSource, setTokenSource] = useState<HTMLElement | null>(null);
64
68
  const portalStyle = usePortalTokenStyle(tokenSource);
65
69
 
66
- const open = useCallback((src: string, caption?: string, source?: HTMLElement | null) => {
67
- setTokenSource(source ?? null);
68
- setState(caption ? { src, caption } : { src });
69
- }, []);
70
+ const open = useCallback(
71
+ (src: string, caption?: string, source?: HTMLElement | null, label = "Screenshot") => {
72
+ setTokenSource(source ?? null);
73
+ setState(caption ? { src, caption, label } : { src, label });
74
+ },
75
+ [],
76
+ );
70
77
 
71
78
  const close = useCallback(() => {
72
79
  setState(null);
@@ -95,7 +102,7 @@ function LightboxRoot({ children }: { children: ReactNode }) {
95
102
  <Dialog.Content
96
103
  asChild
97
104
  forceMount
98
- aria-label="Screenshot"
105
+ aria-label={state.label}
99
106
  onOpenAutoFocus={(event) => event.preventDefault()}
100
107
  >
101
108
  <motion.div
@@ -117,7 +124,10 @@ function LightboxRoot({ children }: { children: ReactNode }) {
117
124
  className="og-root fixed inset-0 z-50 flex items-center justify-center p-6 sm:p-12"
118
125
  style={portalStyle}
119
126
  >
120
- <Dialog.Title className="sr-only">Screenshot</Dialog.Title>
127
+ <Dialog.Title className="sr-only">{state.label}</Dialog.Title>
128
+ <Dialog.Description className="sr-only">
129
+ {state.caption ?? `Expanded ${state.label.toLowerCase()}`}
130
+ </Dialog.Description>
121
131
  {/* The figure is one self-contained object: image, caption, and
122
132
  its own close control. The close button anchors to a wrapper
123
133
  sized to the IMAGE (w-fit), so it hugs the real top-right
@@ -128,7 +138,7 @@ function LightboxRoot({ children }: { children: ReactNode }) {
128
138
  {/* A plain <img>: this SDK is framework-agnostic, with no host Image component. */}
129
139
  <img
130
140
  src={state.src}
131
- alt={state.caption ?? "Screenshot"}
141
+ alt={state.caption ?? state.label}
132
142
  className="min-h-0 max-h-[82vh] w-auto max-w-full rounded-og-md border border-white/10 object-contain shadow-og-lg"
133
143
  />
134
144
  <Dialog.Close
@@ -1,9 +1,10 @@
1
1
  import { CameraIcon, CameraOffIcon, ChevronRightIcon } from "lucide-react";
2
- import { useState, type ReactNode } from "react";
2
+ import { createContext, useContext, useState, type ReactNode } from "react";
3
3
  import { cn } from "../lib/cn";
4
4
  import { stringifyPayload } from "../lib/format";
5
5
  import { useForcedDefaultOpen } from "./disclosure-context";
6
6
  import { useLightboxOptional } from "./screenshot-lightbox";
7
+ import type { ToolCallTruncation } from "./types";
7
8
 
8
9
  /* ----------------------------------------------------------------------------
9
10
  Shared timeline primitives
@@ -37,6 +38,22 @@ export type DisclosureChip = {
37
38
  text: string;
38
39
  };
39
40
 
41
+ const ToolCallTruncationContext = createContext<ToolCallTruncation | null>(null);
42
+
43
+ export function ToolCallTruncationProvider({
44
+ value,
45
+ children,
46
+ }: {
47
+ value: ToolCallTruncation | null;
48
+ children: ReactNode;
49
+ }) {
50
+ return (
51
+ <ToolCallTruncationContext.Provider value={value}>
52
+ {children}
53
+ </ToolCallTruncationContext.Provider>
54
+ );
55
+ }
56
+
40
57
  export type ActivityDisclosureProps = {
41
58
  icon: ReactNode;
42
59
  /** Icon tint. Defaults to the muted foreground; renderers pass accent/failed. */
@@ -81,6 +98,8 @@ export type ActivityDisclosureProps = {
81
98
  cancelled?: boolean | undefined;
82
99
  /** When false the row is a static line (no expand affordance). */
83
100
  expandable?: boolean | undefined;
101
+ /** Seed this individual disclosure open; user interaction still owns it afterwards. */
102
+ defaultOpen?: boolean | undefined;
84
103
  children?: ReactNode | undefined;
85
104
  };
86
105
 
@@ -109,6 +128,7 @@ export function ActivityDisclosure({
109
128
  failed,
110
129
  cancelled,
111
130
  expandable = true,
131
+ defaultOpen,
112
132
  children,
113
133
  }: ActivityDisclosureProps) {
114
134
  // `failed` takes precedence over `cancelled` when both are set (shouldn't happen, but be safe).
@@ -128,8 +148,9 @@ export function ActivityDisclosure({
128
148
  // An ancestor may seed the initial open state (screenshot instrumentation);
129
149
  // absent in normal app usage, where the row starts collapsed.
130
150
  const forcedDefaultOpen = useForcedDefaultOpen();
131
- const [open, setOpen] = useState(forcedDefaultOpen ?? false);
132
- const hasBody = expandable && children != null;
151
+ const [open, setOpen] = useState(defaultOpen ?? forcedDefaultOpen ?? false);
152
+ const truncation = useContext(ToolCallTruncationContext);
153
+ const hasBody = expandable && (children != null || truncation != null);
133
154
 
134
155
  // The preview is detail-on-demand: it is suppressed once the row is open so a
135
156
  // path/stat shown in the body never also sits in the collapsed row.
@@ -184,7 +205,7 @@ export function ActivityDisclosure({
184
205
  )}
185
206
  {/* The right gutter carries at most ONE signal: the media thumbnail, else a
186
207
  terse settle chip (hidden once expanded — the body owns the detail). */}
187
- {media ? (
208
+ {media && !open ? (
188
209
  <span className="ml-auto flex shrink-0 items-center gap-2 pl-2">{media}</span>
189
210
  ) : chip && !open ? (
190
211
  <span className="ml-auto shrink-0 pl-2">
@@ -235,12 +256,31 @@ export function ActivityDisclosure({
235
256
  {open ? (
236
257
  <div className="mb-2 ml-7 mt-1.5 flex flex-col gap-2 overflow-hidden animate-og-expand">
237
258
  {children}
259
+ {truncation ? <ToolCallTruncationNotice truncation={truncation} /> : null}
238
260
  </div>
239
261
  ) : null}
240
262
  </div>
241
263
  );
242
264
  }
243
265
 
266
+ function ToolCallTruncationNotice({ truncation }: { truncation: ToolCallTruncation }) {
267
+ const fullEvidence = truncation.fullEvidence.available
268
+ ? "retained"
269
+ : (truncation.fullEvidence.reason ?? "unavailable");
270
+ return (
271
+ <div data-og-tool-output-truncation="" className="text-og-sm leading-5 text-og-fg-subtle">
272
+ Output bounded at <code className="font-og-mono">{truncation.surface}</code>
273
+ {truncation.omittedBytes != null && truncation.omittedBytes > 0
274
+ ? ` · ${truncation.omittedBytes.toLocaleString()} bytes omitted`
275
+ : null}
276
+ {" · reason "}
277
+ <code className="font-og-mono">{truncation.reason}</code>
278
+ {" · full evidence "}
279
+ <code className="font-og-mono">{fullEvidence}</code>
280
+ </div>
281
+ );
282
+ }
283
+
244
284
  /**
245
285
  * The right-gutter settle chip. Color is spent only on the exception: a failure
246
286
  * is a red dot + red text (the one colored token in a healthy run); success and
@@ -467,10 +507,14 @@ export function Thumbnail({
467
507
  src,
468
508
  caption,
469
509
  alt = "screenshot",
510
+ expandLabel = "Expand screenshot",
511
+ lightboxLabel = "Screenshot",
470
512
  }: {
471
513
  src: string;
472
514
  caption?: string | undefined;
473
515
  alt?: string;
516
+ expandLabel?: string | undefined;
517
+ lightboxLabel?: string | undefined;
474
518
  }) {
475
519
  const lightbox = useLightboxOptional();
476
520
  const [failed, setFailed] = useState(false);
@@ -498,7 +542,7 @@ export function Thumbnail({
498
542
  type="button"
499
543
  onClick={(event) => {
500
544
  event.stopPropagation();
501
- lightbox.open(src, caption, event.currentTarget);
545
+ lightbox.open(src, caption, event.currentTarget, lightboxLabel);
502
546
  }}
503
547
  onKeyDown={(event) => {
504
548
  if (event.key === "Enter" || event.key === " ") {
@@ -510,7 +554,7 @@ export function Thumbnail({
510
554
  "group/thumb relative inline-flex overflow-hidden bg-og-bg outline-hidden",
511
555
  "focus-visible:ring-2 focus-visible:ring-og-accent",
512
556
  )}
513
- aria-label="Expand screenshot"
557
+ aria-label={expandLabel}
514
558
  >
515
559
  {img}
516
560
  </button>
@@ -527,10 +571,14 @@ export function ScreenshotFigure({
527
571
  src,
528
572
  caption,
529
573
  alt = "screenshot",
574
+ expandLabel = "Expand screenshot",
575
+ lightboxLabel = "Screenshot",
530
576
  }: {
531
577
  src: string;
532
578
  caption?: string | undefined;
533
579
  alt?: string;
580
+ expandLabel?: string | undefined;
581
+ lightboxLabel?: string | undefined;
534
582
  }) {
535
583
  const lightbox = useLightboxOptional();
536
584
  const [failed, setFailed] = useState(false);
@@ -554,9 +602,9 @@ export function ScreenshotFigure({
554
602
  ) : lightbox ? (
555
603
  <button
556
604
  type="button"
557
- onClick={(event) => lightbox.open(src, caption, event.currentTarget)}
605
+ onClick={(event) => lightbox.open(src, caption, event.currentTarget, lightboxLabel)}
558
606
  className={surface}
559
- aria-label="Expand screenshot"
607
+ aria-label={expandLabel}
560
608
  >
561
609
  {img}
562
610
  </button>