@adecore/agents-react 0.31.12 → 0.33.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 (409) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/AccountDot.js +57 -6
  3. package/dist/agents/AgentIcon.js +88 -12
  4. package/dist/agents/ModelOptionControl.js +153 -18
  5. package/dist/agents/ProviderLogo.js +49 -4
  6. package/dist/agents/account-limits.js +17 -14
  7. package/dist/agents/accounts.js +99 -67
  8. package/dist/agents/agent-marks.js +5 -3
  9. package/dist/agents/model-name.js +42 -31
  10. package/dist/agents/model-options.js +8 -8
  11. package/dist/agents/model-requirement.js +17 -14
  12. package/dist/agents/option-words.d.ts +7 -0
  13. package/dist/agents/option-words.js +35 -0
  14. package/dist/agents/provider-paths.js +6 -6
  15. package/dist/agents/status-look.js +32 -12
  16. package/dist/chat/account-choice.d.ts +13 -0
  17. package/dist/chat/account-choice.js +90 -20
  18. package/dist/chat/actions.js +126 -59
  19. package/dist/chat/activity.js +5 -5
  20. package/dist/chat/attachments.js +74 -53
  21. package/dist/chat/bookmarks.js +58 -48
  22. package/dist/chat/chat-client.js +344 -263
  23. package/dist/chat/chat-references.js +3 -3
  24. package/dist/chat/composer-effort.d.ts +5 -0
  25. package/dist/chat/composer-effort.js +26 -0
  26. package/dist/chat/composer-send.js +13 -13
  27. package/dist/chat/drafts.js +115 -79
  28. package/dist/chat/forks.js +81 -24
  29. package/dist/chat/guards.js +15 -43
  30. package/dist/chat/index.d.ts +3 -1
  31. package/dist/chat/index.js +3 -2
  32. package/dist/chat/logic/ansi.js +170 -167
  33. package/dist/chat/logic/answers.js +2 -2
  34. package/dist/chat/logic/bookmarks.js +19 -19
  35. package/dist/chat/logic/command-looks.js +138 -96
  36. package/dist/chat/logic/context-usage.js +13 -9
  37. package/dist/chat/logic/continue-after-stop.js +15 -19
  38. package/dist/chat/logic/effort.d.ts +14 -0
  39. package/dist/chat/logic/effort.js +34 -0
  40. package/dist/chat/logic/embedded-code.js +387 -326
  41. package/dist/chat/logic/fork.js +134 -110
  42. package/dist/chat/logic/generated-image.js +54 -23
  43. package/dist/chat/logic/goal.js +28 -20
  44. package/dist/chat/logic/handback.js +6 -6
  45. package/dist/chat/logic/json.js +2 -2
  46. package/dist/chat/logic/limit.js +46 -30
  47. package/dist/chat/logic/picker-entries.d.ts +5 -3
  48. package/dist/chat/logic/picker-entries.js +83 -43
  49. package/dist/chat/logic/resume-compaction.d.ts +14 -1
  50. package/dist/chat/logic/resume-compaction.js +93 -24
  51. package/dist/chat/logic/scrubber.js +188 -136
  52. package/dist/chat/logic/thought.js +13 -16
  53. package/dist/chat/logic/thread-cards.js +60 -48
  54. package/dist/chat/logic/timeline-copy.js +30 -38
  55. package/dist/chat/logic/timeline-target.js +92 -63
  56. package/dist/chat/logic/timeline.js +499 -355
  57. package/dist/chat/logic/tool-catalog.js +54 -34
  58. package/dist/chat/logic/tools.js +106 -82
  59. package/dist/chat/logic/visual-height.js +18 -18
  60. package/dist/chat/logic/visual-viewport.js +6 -3
  61. package/dist/chat/logic/welcome.js +9 -9
  62. package/dist/chat/logic/workflow.d.ts +10 -0
  63. package/dist/chat/logic/workflow.js +130 -32
  64. package/dist/chat/mentions.js +149 -109
  65. package/dist/chat/persisted-json.js +51 -54
  66. package/dist/chat/preferences.d.ts +4 -0
  67. package/dist/chat/preferences.js +162 -84
  68. package/dist/chat/prompt-hold.js +9 -9
  69. package/dist/chat/quote.js +21 -30
  70. package/dist/chat/reading-width.js +12 -10
  71. package/dist/chat/recent-messages.js +34 -21
  72. package/dist/chat/resume-compaction-dismissals.js +25 -5
  73. package/dist/chat/runtime-modes.js +3 -3
  74. package/dist/chat/send-key.js +11 -7
  75. package/dist/chat/stash.js +89 -69
  76. package/dist/chat/subagent-conversation.js +181 -139
  77. package/dist/chat/subagent-list.js +65 -67
  78. package/dist/chat/subagent-support.js +12 -7
  79. package/dist/chat/subagent-view.js +132 -44
  80. package/dist/chat/thumbnails.js +88 -60
  81. package/dist/chat/timeline-flash.js +24 -12
  82. package/dist/chat/timeline-scroll.js +116 -86
  83. package/dist/chat/ui/AccountPill.js +126 -15
  84. package/dist/chat/ui/AnsiOutput.js +62 -6
  85. package/dist/chat/ui/BookmarkMarker.js +231 -41
  86. package/dist/chat/ui/BookmarkSubmenu.js +165 -18
  87. package/dist/chat/ui/ChatActivity.js +649 -70
  88. package/dist/chat/ui/ChatReferenceChip.js +104 -9
  89. package/dist/chat/ui/CodeBlock.js +132 -58
  90. package/dist/chat/ui/Composer.js +866 -132
  91. package/dist/chat/ui/ComposerInput.js +309 -120
  92. package/dist/chat/ui/DiffPool.js +75 -14
  93. package/dist/chat/ui/EditDiff.js +160 -21
  94. package/dist/chat/ui/FadingWords.js +32 -3
  95. package/dist/chat/ui/GoalDock.js +235 -42
  96. package/dist/chat/ui/ImageView.js +617 -61
  97. package/dist/chat/ui/LimitState.js +446 -72
  98. package/dist/chat/ui/Markdown.js +991 -173
  99. package/dist/chat/ui/MermaidBlock.js +285 -79
  100. package/dist/chat/ui/MessageActions.js +251 -39
  101. package/dist/chat/ui/ModelMarks.js +55 -5
  102. package/dist/chat/ui/Pickers.js +664 -113
  103. package/dist/chat/ui/PromptComposer.js +281 -51
  104. package/dist/chat/ui/QuoteButton.js +151 -57
  105. package/dist/chat/ui/ResumeCompactionDock.d.ts +6 -2
  106. package/dist/chat/ui/ResumeCompactionDock.js +141 -6
  107. package/dist/chat/ui/RowTime.js +59 -5
  108. package/dist/chat/ui/RunSettings.js +1744 -154
  109. package/dist/chat/ui/Scrubber.js +517 -88
  110. package/dist/chat/ui/ShellCommand.js +183 -107
  111. package/dist/chat/ui/SubagentControls.js +212 -24
  112. package/dist/chat/ui/SubagentInfo.js +170 -13
  113. package/dist/chat/ui/SubagentStopButton.js +115 -24
  114. package/dist/chat/ui/SubagentTimeline.js +357 -91
  115. package/dist/chat/ui/Timeline.js +988 -476
  116. package/dist/chat/ui/TimelineMenu.js +552 -37
  117. package/dist/chat/ui/UnifiedDiff.js +234 -27
  118. package/dist/chat/ui/UploadThumb.js +37 -7
  119. package/dist/chat/ui/VisualDialogs.js +251 -20
  120. package/dist/chat/ui/VisualFrame.js +321 -95
  121. package/dist/chat/ui/Welcome.js +72 -7
  122. package/dist/chat/ui/chips.js +1 -1
  123. package/dist/chat/ui/code-lines.js +36 -36
  124. package/dist/chat/ui/code-streaming.js +1 -1
  125. package/dist/chat/ui/code-theme.js +7 -7
  126. package/dist/chat/ui/composer/ComposerControls.js +329 -21
  127. package/dist/chat/ui/composer/ComposerMenu.js +358 -30
  128. package/dist/chat/ui/composer/DraftParts.js +185 -11
  129. package/dist/chat/ui/composer/QueuedMessages.js +138 -13
  130. package/dist/chat/ui/composer/chips.js +29 -19
  131. package/dist/chat/ui/composer/editor.js +79 -63
  132. package/dist/chat/ui/composer/insert.js +26 -12
  133. package/dist/chat/ui/composer/key-handler.js +180 -127
  134. package/dist/chat/ui/composer/keys.js +90 -73
  135. package/dist/chat/ui/composer/menu-aria.js +18 -9
  136. package/dist/chat/ui/composer/placeholder.js +78 -31
  137. package/dist/chat/ui/composer/surface.js +13 -37
  138. package/dist/chat/ui/composer/use-chat-controls.js +230 -90
  139. package/dist/chat/ui/composer/use-compaction-offer.js +50 -18
  140. package/dist/chat/ui/composer/use-composer-draft.js +65 -34
  141. package/dist/chat/ui/composer/use-composer-menus.js +478 -193
  142. package/dist/chat/ui/composer/use-composer-submit.js +251 -109
  143. package/dist/chat/ui/composer/use-continuable-turn.js +33 -6
  144. package/dist/chat/ui/composer/use-draft-files.js +245 -93
  145. package/dist/chat/ui/composer/use-effort-stepper.d.ts +13 -0
  146. package/dist/chat/ui/composer/use-effort-stepper.js +88 -0
  147. package/dist/chat/ui/composer/use-notice.js +61 -16
  148. package/dist/chat/ui/composer/use-pending-requests.js +38 -17
  149. package/dist/chat/ui/composer/use-prompt-history.js +117 -45
  150. package/dist/chat/ui/composer/use-prompt-hold.js +119 -35
  151. package/dist/chat/ui/composer/use-take-back.js +94 -72
  152. package/dist/chat/ui/diff/ApprovalReview.js +101 -23
  153. package/dist/chat/ui/diff/DiffFallbackBoundary.js +21 -13
  154. package/dist/chat/ui/diff/DiffOmitted.js +56 -6
  155. package/dist/chat/ui/diff/DiffStack.js +92 -8
  156. package/dist/chat/ui/diff/DiffUnavailable.js +56 -6
  157. package/dist/chat/ui/diff/EditorEditDiff.js +263 -51
  158. package/dist/chat/ui/diff/EditorPatchDiff.js +39 -14
  159. package/dist/chat/ui/diff/LegacyDiffPool.js +27 -10
  160. package/dist/chat/ui/diff/LegacyEditDiff.js +97 -20
  161. package/dist/chat/ui/diff/LegacyUnifiedDiff.js +172 -42
  162. package/dist/chat/ui/diff/RawDiff.js +65 -5
  163. package/dist/chat/ui/diff/ReviewDiff.js +77 -5
  164. package/dist/chat/ui/diff/diff-props.js +1 -1
  165. package/dist/chat/ui/diff/diff-source-budget.js +46 -35
  166. package/dist/chat/ui/diff/editor-edit-model.js +18 -14
  167. package/dist/chat/ui/diff/review-plan.js +63 -40
  168. package/dist/chat/ui/diff/review-settle.js +24 -9
  169. package/dist/chat/ui/diff/syntax-runtime.js +236 -201
  170. package/dist/chat/ui/diff/use-review-stack.js +146 -25
  171. package/dist/chat/ui/diff-syntax.js +70 -58
  172. package/dist/chat/ui/diff-theme.js +31 -12
  173. package/dist/chat/ui/file-links.js +39 -10
  174. package/dist/chat/ui/find-reveal.js +29 -8
  175. package/dist/chat/ui/full-diff.js +17 -12
  176. package/dist/chat/ui/highlighter.js +143 -113
  177. package/dist/chat/ui/icons.js +16 -7
  178. package/dist/chat/ui/index.js +1 -1
  179. package/dist/chat/ui/intelligent-ui/UiBlockFrame.js +556 -82
  180. package/dist/chat/ui/intelligent-ui/UiFallbackPart.js +73 -5
  181. package/dist/chat/ui/intelligent-ui/UiReply.js +666 -262
  182. package/dist/chat/ui/intelligent-ui/block-local.js +59 -31
  183. package/dist/chat/ui/intelligent-ui/chart-data.js +77 -67
  184. package/dist/chat/ui/intelligent-ui/node-text.js +77 -91
  185. package/dist/chat/ui/intelligent-ui/registry.js +46 -46
  186. package/dist/chat/ui/intelligent-ui/render-context.js +1 -1
  187. package/dist/chat/ui/intelligent-ui/renderers/base-land.js +1 -1
  188. package/dist/chat/ui/intelligent-ui/renderers/binding.js +3 -3
  189. package/dist/chat/ui/intelligent-ui/renderers/chart.js +1223 -123
  190. package/dist/chat/ui/intelligent-ui/renderers/choices.js +312 -34
  191. package/dist/chat/ui/intelligent-ui/renderers/content.js +1241 -86
  192. package/dist/chat/ui/intelligent-ui/renderers/data.js +619 -54
  193. package/dist/chat/ui/intelligent-ui/renderers/inputs.js +868 -67
  194. package/dist/chat/ui/intelligent-ui/renderers/links.js +366 -62
  195. package/dist/chat/ui/intelligent-ui/renderers/map-slots.js +118 -60
  196. package/dist/chat/ui/intelligent-ui/renderers/map-style.js +85 -78
  197. package/dist/chat/ui/intelligent-ui/renderers/map-view.js +282 -217
  198. package/dist/chat/ui/intelligent-ui/renderers/map.js +621 -69
  199. package/dist/chat/ui/intelligent-ui/renderers/place-point.js +15 -8
  200. package/dist/chat/ui/intelligent-ui/renderers/structure.js +262 -20
  201. package/dist/chat/ui/intelligent-ui/renderers/text.js +674 -49
  202. package/dist/chat/ui/intelligent-ui/reply-parts.js +24 -15
  203. package/dist/chat/ui/intelligent-ui/table-data.js +97 -57
  204. package/dist/chat/ui/intelligent-ui/tones.js +31 -31
  205. package/dist/chat/ui/intelligent-ui/use-in-view.js +30 -12
  206. package/dist/chat/ui/intelligent-ui/use-later.js +28 -13
  207. package/dist/chat/ui/intelligent-ui/use-truncated.js +45 -21
  208. package/dist/chat/ui/intelligent-ui/use-ui-links.js +296 -109
  209. package/dist/chat/ui/intelligent-ui/use-ui-queries.js +256 -246
  210. package/dist/chat/ui/markdown-blocks.js +296 -262
  211. package/dist/chat/ui/mermaid.d.ts +8 -20
  212. package/dist/chat/ui/mermaid.js +47 -84
  213. package/dist/chat/ui/quote-selection.js +32 -29
  214. package/dist/chat/ui/rehype-chips.js +35 -23
  215. package/dist/chat/ui/rehype-fade.js +28 -17
  216. package/dist/chat/ui/remark-html-as-text.js +22 -15
  217. package/dist/chat/ui/reply-context.js +1 -1
  218. package/dist/chat/ui/reveal.js +161 -72
  219. package/dist/chat/ui/rows/ForksRow.js +226 -24
  220. package/dist/chat/ui/rows/GeneratedImageRow.js +541 -87
  221. package/dist/chat/ui/rows/MessageRows.js +1706 -159
  222. package/dist/chat/ui/rows/RowContainer.js +57 -7
  223. package/dist/chat/ui/rows/Rows.js +463 -54
  224. package/dist/chat/ui/rows/SubagentRow.js +577 -60
  225. package/dist/chat/ui/rows/TaskRow.js +359 -40
  226. package/dist/chat/ui/rows/VisualRow.js +233 -11
  227. package/dist/chat/ui/rows/WorkRows.d.ts +3 -0
  228. package/dist/chat/ui/rows/WorkRows.js +1945 -164
  229. package/dist/chat/ui/rows/WorkflowRow.js +756 -37
  230. package/dist/chat/ui/rows/row-rhythm.js +27 -17
  231. package/dist/chat/ui/theme-watch.js +26 -23
  232. package/dist/chat/ui/timestamp-format.js +4 -1
  233. package/dist/chat/ui/use-diff-source-tokens.js +70 -34
  234. package/dist/chat/ui/useToggleSet.js +51 -19
  235. package/dist/chat/ui/visual-theme.js +153 -128
  236. package/dist/chat/visual-bridge.js +123 -123
  237. package/dist/chat/visual-viewport.js +94 -83
  238. package/dist/chat/visuals.js +13 -5
  239. package/dist/host-hooks.d.ts +29 -0
  240. package/dist/host-hooks.js +78 -0
  241. package/dist/host.js +120 -48
  242. package/dist/index.js +1 -1
  243. package/dist/lazy.js +38 -32
  244. package/dist/locales/en/agent-chat.d.ts +42 -0
  245. package/dist/locales/en/agent-chat.js +755 -713
  246. package/dist/locales/en/agent-prompts.js +42 -42
  247. package/dist/locales/en/agent-providers.d.ts +1 -0
  248. package/dist/locales/en/agent-providers.js +166 -165
  249. package/dist/locales/en/agent-usage.d.ts +6 -0
  250. package/dist/locales/en/agent-usage.js +129 -123
  251. package/dist/locales/nl/agent-chat.d.ts +42 -0
  252. package/dist/locales/nl/agent-chat.js +755 -713
  253. package/dist/locales/nl/agent-prompts.js +42 -42
  254. package/dist/locales/nl/agent-providers.d.ts +1 -0
  255. package/dist/locales/nl/agent-providers.js +166 -165
  256. package/dist/locales/nl/agent-usage.d.ts +6 -0
  257. package/dist/locales/nl/agent-usage.js +129 -123
  258. package/dist/locales.js +14 -14
  259. package/dist/mounted-registry.js +31 -30
  260. package/dist/port-transport.js +122 -111
  261. package/dist/prompts/index.js +1 -1
  262. package/dist/prompts/logic/focus.js +15 -14
  263. package/dist/prompts/logic/keys.js +93 -59
  264. package/dist/prompts/logic/prompts.js +27 -14
  265. package/dist/prompts/logic/subjects.js +60 -46
  266. package/dist/prompts/logic/useFocusAfterAnswer.js +60 -20
  267. package/dist/prompts/logic/usePromptSession.js +88 -77
  268. package/dist/prompts/ui/ApprovalBody.js +246 -27
  269. package/dist/prompts/ui/PromptActions.js +215 -9
  270. package/dist/prompts/ui/PromptCard.js +215 -36
  271. package/dist/prompts/ui/PromptView.js +372 -34
  272. package/dist/prompts/ui/QuestionBody.js +269 -73
  273. package/dist/providers/AccountColors.js +36 -5
  274. package/dist/providers/AccountDetail.js +683 -50
  275. package/dist/providers/AccountVariables.js +139 -32
  276. package/dist/providers/AddAccountForm.js +125 -38
  277. package/dist/providers/CliDetail.js +343 -36
  278. package/dist/providers/InstallPage.js +644 -59
  279. package/dist/providers/ProviderList.d.ts +2 -1
  280. package/dist/providers/ProviderList.js +215 -23
  281. package/dist/providers/ProvidersPane.d.ts +4 -2
  282. package/dist/providers/ProvidersPane.js +244 -64
  283. package/dist/providers/account-actions.js +54 -30
  284. package/dist/providers/index.js +1 -1
  285. package/dist/providers/install-steps.js +12 -9
  286. package/dist/providers/pane-target.d.ts +3 -0
  287. package/dist/providers/pane-target.js +34 -13
  288. package/dist/providers/parts.js +62 -12
  289. package/dist/providers/provider-abilities.js +5 -5
  290. package/dist/providers/provider-groups.js +75 -40
  291. package/dist/providers/variables.js +73 -39
  292. package/dist/scope.js +6 -6
  293. package/dist/settings/sections.js +18 -18
  294. package/dist/state/chats.js +404 -192
  295. package/dist/state/index.js +1 -1
  296. package/dist/state/provider-accounts.js +70 -47
  297. package/dist/state/providers.js +45 -16
  298. package/dist/state/usage.js +154 -77
  299. package/dist/storage.js +69 -68
  300. package/dist/testing/index.js +1 -1
  301. package/dist/testing/prompts.js +105 -67
  302. package/dist/transport.js +10 -10
  303. package/dist/ui/PlainTextarea.js +27 -3
  304. package/dist/usage/LimitsList.js +646 -40
  305. package/dist/usage/UsageBreakdown.js +879 -38
  306. package/dist/usage/UsageChart.js +311 -32
  307. package/dist/usage/UsageDialog.js +46 -4
  308. package/dist/usage/UsageLimits.js +467 -34
  309. package/dist/usage/UsageLimitsCard.js +95 -11
  310. package/dist/usage/UsagePage.js +817 -96
  311. package/dist/usage/UsagePane.js +164 -10
  312. package/dist/usage/UsageSummary.js +151 -6
  313. package/dist/usage/UsageTiles.js +164 -14
  314. package/dist/usage/format.js +32 -32
  315. package/dist/usage/index.d.ts +1 -1
  316. package/dist/usage/index.js +2 -1
  317. package/dist/usage/limit-groups.d.ts +2 -0
  318. package/dist/usage/limit-groups.js +77 -59
  319. package/dist/usage/limits.js +78 -43
  320. package/dist/usage/money.js +20 -4
  321. package/dist/usage/summary.js +85 -64
  322. package/package.json +9 -9
  323. package/src/agents/ModelOptionControl.tsx +3 -1
  324. package/src/agents/option-words.ts +27 -0
  325. package/src/chat/account-choice.ts +41 -1
  326. package/src/chat/composer-effort.ts +32 -0
  327. package/src/chat/forks.ts +4 -5
  328. package/src/chat/index.ts +3 -1
  329. package/src/chat/logic/effort.ts +32 -0
  330. package/src/chat/logic/picker-entries.ts +45 -10
  331. package/src/chat/logic/resume-compaction.ts +95 -17
  332. package/src/chat/logic/timeline.ts +11 -3
  333. package/src/chat/logic/workflow.ts +86 -4
  334. package/src/chat/preferences.ts +35 -0
  335. package/src/chat/subagent-view.ts +2 -3
  336. package/src/chat/ui/AnsiOutput.tsx +1 -2
  337. package/src/chat/ui/BookmarkSubmenu.tsx +3 -3
  338. package/src/chat/ui/ChatActivity.tsx +14 -22
  339. package/src/chat/ui/ChatReferenceChip.tsx +2 -3
  340. package/src/chat/ui/Composer.tsx +15 -7
  341. package/src/chat/ui/EditDiff.tsx +2 -3
  342. package/src/chat/ui/LimitState.tsx +2 -2
  343. package/src/chat/ui/Markdown.tsx +10 -8
  344. package/src/chat/ui/MermaidBlock.tsx +57 -49
  345. package/src/chat/ui/Pickers.tsx +49 -29
  346. package/src/chat/ui/PromptComposer.tsx +7 -9
  347. package/src/chat/ui/ResumeCompactionDock.tsx +16 -4
  348. package/src/chat/ui/RunSettings.tsx +67 -28
  349. package/src/chat/ui/Scrubber.tsx +3 -3
  350. package/src/chat/ui/ShellCommand.tsx +2 -0
  351. package/src/chat/ui/SubagentInfo.tsx +2 -3
  352. package/src/chat/ui/SubagentTimeline.tsx +12 -16
  353. package/src/chat/ui/Timeline.tsx +6 -6
  354. package/src/chat/ui/UnifiedDiff.tsx +3 -4
  355. package/src/chat/ui/VisualFrame.tsx +5 -8
  356. package/src/chat/ui/code-theme.ts +4 -4
  357. package/src/chat/ui/composer/placeholder.ts +2 -2
  358. package/src/chat/ui/composer/use-compaction-offer.ts +36 -16
  359. package/src/chat/ui/composer/use-composer-menus.ts +4 -9
  360. package/src/chat/ui/composer/use-composer-submit.ts +2 -1
  361. package/src/chat/ui/composer/use-effort-stepper.ts +56 -0
  362. package/src/chat/ui/composer/use-notice.ts +2 -2
  363. package/src/chat/ui/diff/EditorEditDiff.tsx +9 -13
  364. package/src/chat/ui/diff/LegacyEditDiff.tsx +12 -17
  365. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +13 -21
  366. package/src/chat/ui/diff/use-review-stack.ts +4 -4
  367. package/src/chat/ui/diff-theme.ts +3 -3
  368. package/src/chat/ui/file-links.ts +2 -2
  369. package/src/chat/ui/intelligent-ui/UiReply.tsx +2 -0
  370. package/src/chat/ui/intelligent-ui/renderers/chart.tsx +3 -3
  371. package/src/chat/ui/intelligent-ui/renderers/content.tsx +5 -6
  372. package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +2 -1
  373. package/src/chat/ui/intelligent-ui/renderers/map.tsx +2 -1
  374. package/src/chat/ui/intelligent-ui/use-truncated.ts +16 -19
  375. package/src/chat/ui/intelligent-ui/use-ui-links.ts +3 -3
  376. package/src/chat/ui/mermaid.ts +52 -96
  377. package/src/chat/ui/rows/ForksRow.tsx +3 -5
  378. package/src/chat/ui/rows/GeneratedImageRow.tsx +2 -1
  379. package/src/chat/ui/rows/MessageRows.tsx +12 -21
  380. package/src/chat/ui/rows/SubagentRow.tsx +3 -3
  381. package/src/chat/ui/rows/TaskRow.tsx +1 -1
  382. package/src/chat/ui/rows/WorkRows.tsx +11 -6
  383. package/src/chat/ui/rows/WorkflowRow.tsx +144 -54
  384. package/src/chat/ui/useToggleSet.ts +15 -18
  385. package/src/chat/ui/visual-theme.ts +2 -3
  386. package/src/host-hooks.ts +113 -0
  387. package/src/lazy.tsx +2 -0
  388. package/src/locales/en/agent-chat.ts +142 -108
  389. package/src/locales/en/agent-prompts.ts +7 -7
  390. package/src/locales/en/agent-providers.ts +35 -34
  391. package/src/locales/en/agent-usage.ts +26 -20
  392. package/src/locales/nl/agent-chat.ts +134 -100
  393. package/src/locales/nl/agent-prompts.ts +5 -5
  394. package/src/locales/nl/agent-providers.ts +33 -32
  395. package/src/locales/nl/agent-usage.ts +25 -20
  396. package/src/providers/CliDetail.tsx +2 -1
  397. package/src/providers/InstallPage.tsx +2 -4
  398. package/src/providers/ProviderList.tsx +12 -2
  399. package/src/providers/ProvidersPane.tsx +45 -15
  400. package/src/providers/pane-target.ts +20 -4
  401. package/src/state/chats.ts +2 -2
  402. package/src/usage/LimitsList.tsx +12 -13
  403. package/src/usage/UsageBreakdown.tsx +17 -6
  404. package/src/usage/UsageLimits.tsx +2 -1
  405. package/src/usage/UsagePage.tsx +3 -3
  406. package/src/usage/index.ts +1 -1
  407. package/src/usage/limit-groups.ts +12 -0
  408. package/src/usage/limits.ts +2 -2
  409. package/src/usage/money.ts +1 -2
@@ -13,61 +13,80 @@ import { hasFileChanges, isFileChange, shellCommand } from './tools.js';
13
13
  */
14
14
  const BLOCK_KINDS = new Set(['assistant', 'report', 'thinking', 'changed-files', 'compaction', 'app-card', 'visual']);
15
15
  export function isBlock(row) {
16
- return BLOCK_KINDS.has(row.kind) || isImageRow(row);
16
+ return BLOCK_KINDS.has(row.kind) || isImageRow(row);
17
17
  }
18
18
  /*
19
19
  * The sentence of a run of shell calls that are all runs of one look: its group's when they share a key,
20
20
  * else the look's own; null for any other run, which reads as shell calls.
21
21
  */
22
22
  function lookSummary(tools, looks) {
23
- let look = null;
24
- let group;
25
- let sameKey = true;
26
- for (const tool of tools) {
27
- const command = shellCommand(tool.name, tool.input);
28
- const run = command === null ? null : matchCommandLook(command, looks);
29
- if (run === null || (look !== null && run.look !== look)) {
30
- return null;
31
- }
32
- if (look === null) {
33
- look = run.look;
34
- group = run.match.group;
35
- }
36
- else if (run.match.group?.key !== group?.key) {
37
- sameKey = false;
38
- }
23
+ let look = null;
24
+ let group;
25
+ let sameKey = true;
26
+ for (const tool of tools) {
27
+ const command = shellCommand(tool.name, tool.input);
28
+ const run = command === null ? null : matchCommandLook(command, looks);
29
+ if (run === null || look !== null && run.look !== look) {
30
+ return null;
39
31
  }
40
32
  if (look === null) {
41
- return null;
33
+ look = run.look;
34
+ group = run.match.group;
35
+ } else if (run.match.group?.key !== group?.key) {
36
+ sameKey = false;
42
37
  }
43
- if (sameKey && group !== undefined) {
44
- return { look, summary: group.sentence(tools.length) };
45
- }
46
- return look.sentence === undefined ? null : { look, summary: look.sentence(tools.length) };
38
+ }
39
+ if (look === null) {
40
+ return null;
41
+ }
42
+ if (sameKey && group !== undefined) {
43
+ return {
44
+ look,
45
+ summary: group.sentence(tools.length)
46
+ };
47
+ }
48
+ return look.sentence === undefined ? null : {
49
+ look,
50
+ summary: look.sentence(tools.length)
51
+ };
47
52
  }
48
53
  /* "Read 4 files", "Ran 2 commands", "12 tool calls" when the run mixes kinds, or the sentence of a look all its calls are runs of. */
49
54
  export function summarizeGroup(tools, looks) {
50
- const looked = lookSummary(tools, looks);
51
- if (looked !== null) {
52
- return looked.summary;
53
- }
54
- const names = new Set(tools.map((tool) => tool.name));
55
- const only = names.size === 1 ? tools[0].name : null;
56
- if (only !== null && toolEntry(only)?.grouped === true) {
57
- return i18next.t(`agent-chat:group.tools.${only}`, { count: tools.length });
58
- }
59
- // Counts the files rather than the calls: three edits to one file are one file edited.
60
- if (tools.every((tool) => isFileChange(tool.name))) {
61
- const paths = new Set(tools.map(editedPath));
62
- return i18next.t('agent-chat:group.edited', { count: paths.size });
63
- }
64
- if (only !== null) {
65
- return i18next.t('agent-chat:group.calls', { name: only, count: tools.length });
66
- }
67
- return i18next.t('agent-chat:group.toolCalls', { count: tools.length });
55
+ const looked = lookSummary(tools, looks);
56
+ if (looked !== null) {
57
+ return looked.summary;
58
+ }
59
+ const names = new Set(tools.map(tool => tool.name));
60
+ const only = names.size === 1 ? tools[0].name : null;
61
+ const failed = tools.filter(failedFileChange);
62
+ if (only !== null && toolEntry(only)?.grouped === true && failed.length === 0) {
63
+ return i18next.t(`agent-chat:group.tools.${only}`, {
64
+ count: tools.length
65
+ });
66
+ }
67
+ // Counts the files rather than the calls: three edits to one file are one file edited.
68
+ if (tools.every(tool => isFileChange(tool.name)) && failed.length < tools.length) {
69
+ const paths = new Set(tools.filter(tool => !failedFileChange(tool)).map(editedPath));
70
+ return i18next.t('agent-chat:group.edited', {
71
+ count: paths.size
72
+ });
73
+ }
74
+ if (only !== null) {
75
+ return i18next.t('agent-chat:group.calls', {
76
+ name: only,
77
+ count: tools.length
78
+ });
79
+ }
80
+ return i18next.t('agent-chat:group.toolCalls', {
81
+ count: tools.length
82
+ });
83
+ }
84
+ /* A file change that failed or was refused changed nothing: it counts as a call, never as an edit. */
85
+ function failedFileChange(tool) {
86
+ return isFileChange(tool.name) && tool.state === 'error';
68
87
  }
69
88
  function editedPath(tool) {
70
- return tool.input?.file_path ?? tool.id;
89
+ return tool.input?.file_path ?? tool.id;
71
90
  }
72
91
  /*
73
92
  * What a folded turn did, a sentence per kind of call in the order it first came: "Read 11 files",
@@ -75,272 +94,374 @@ function editedPath(tool) {
75
94
  * their own are counted together at the end.
76
95
  */
77
96
  export function summarizeTurn(tools, looks) {
78
- // Keyed by the sentence, so its place is where the first call of that kind came.
79
- const counts = new Map();
80
- const add = (key, say, total) => {
81
- const known = counts.get(key);
82
- if (known === undefined) {
83
- counts.set(key, { count: total ?? 1, say });
84
- }
85
- else {
86
- known.count = total ?? known.count + 1;
87
- }
88
- };
89
- const edited = new Set();
90
- let other = 0;
91
- for (const tool of tools) {
92
- const command = shellCommand(tool.name, tool.input);
93
- const run = command === null ? null : matchCommandLook(command, looks);
94
- const group = run?.match.group;
95
- if (run !== null && group !== undefined) {
96
- // Keyed by the program too, so two looks with a key in common keep their own sentences.
97
- add(`look:${run.look.program}:${group.key}`, group.sentence);
98
- }
99
- else if (isFileChange(tool.name)) {
100
- edited.add(editedPath(tool));
101
- add('agent-chat:group.edited', (count) => i18next.t('agent-chat:group.edited', { count }), edited.size);
102
- }
103
- else if (toolEntry(tool.name)?.grouped === true) {
104
- const key = `agent-chat:group.tools.${tool.name}`;
105
- add(key, (count) => i18next.t(key, { count }));
106
- }
107
- else {
108
- other += 1;
109
- }
97
+ // Keyed by the sentence, so its place is where the first call of that kind came.
98
+ const counts = new Map();
99
+ const add = (key, say, total) => {
100
+ const known = counts.get(key);
101
+ if (known === undefined) {
102
+ counts.set(key, {
103
+ count: total ?? 1,
104
+ say
105
+ });
106
+ } else {
107
+ known.count = total ?? known.count + 1;
110
108
  }
111
- const parts = [...counts.values()].map(({ count, say }) => say(count));
112
- if (other > 0) {
113
- parts.push(i18next.t(parts.length === 0 ? 'agent-chat:group.toolCalls' : 'agent-chat:group.otherCalls', { count: other }));
109
+ };
110
+ const edited = new Set();
111
+ let other = 0;
112
+ for (const tool of tools) {
113
+ const command = shellCommand(tool.name, tool.input);
114
+ const run = command === null ? null : matchCommandLook(command, looks);
115
+ const group = run?.match.group;
116
+ if (run !== null && group !== undefined) {
117
+ // Keyed by the program too, so two looks with a key in common keep their own sentences.
118
+ add(`look:${run.look.program}:${group.key}`, group.sentence);
119
+ } else if (failedFileChange(tool)) {
120
+ other += 1;
121
+ } else if (isFileChange(tool.name)) {
122
+ edited.add(editedPath(tool));
123
+ add('agent-chat:group.edited', count => i18next.t('agent-chat:group.edited', {
124
+ count
125
+ }), edited.size);
126
+ } else if (toolEntry(tool.name)?.grouped === true) {
127
+ const key = `agent-chat:group.tools.${tool.name}`;
128
+ add(key, count => i18next.t(key, {
129
+ count
130
+ }));
131
+ } else {
132
+ other += 1;
114
133
  }
115
- return parts;
134
+ }
135
+ const parts = [...counts.values()].map(({
136
+ count,
137
+ say
138
+ }) => say(count));
139
+ if (other > 0) {
140
+ parts.push(i18next.t(parts.length === 0 ? 'agent-chat:group.toolCalls' : 'agent-chat:group.otherCalls', {
141
+ count: other
142
+ }));
143
+ }
144
+ return parts;
116
145
  }
117
146
  /*
118
147
  * What a turn nobody asked for is about. The CLI wakes the agent when a background subagent settles
119
148
  * and hands over its summary; without one all that is known is that the agent went on by itself.
120
149
  */
121
150
  export function agentTurnLabel(turn) {
122
- const about = (what) => (turn.label ? i18next.t('agent-chat:turn.about', { what, label: turn.label }) : what);
123
- // A turn the machine opened with the results of tasks this chat gave; the label is their titles.
124
- if (turn.taskIds !== undefined && turn.taskIds.length > 0) {
125
- return about(i18next.t('agent-chat:turn.wokenByTask', { count: turn.taskIds.length }));
126
- }
127
- // A turn the machine opened over a message another node sent; the label names who sent it.
128
- if (turn.messageFrom !== undefined && turn.messageFrom.length > 0) {
129
- return about(i18next.t('agent-chat:turn.wokenByMessage', { count: turn.messageFrom.length }));
130
- }
131
- return turn.label ? i18next.t('agent-chat:turn.subagentFinished', { label: turn.label }) : i18next.t('agent-chat:turn.continued');
151
+ const about = what => turn.label ? i18next.t('agent-chat:turn.about', {
152
+ what,
153
+ label: turn.label
154
+ }) : what;
155
+ // A turn the machine opened with the results of tasks this chat gave; the label is their titles.
156
+ if (turn.taskIds !== undefined && turn.taskIds.length > 0) {
157
+ return about(i18next.t('agent-chat:turn.wokenByTask', {
158
+ count: turn.taskIds.length
159
+ }));
160
+ }
161
+ // A turn the machine opened over a message another node sent; the label names who sent it.
162
+ if (turn.messageFrom !== undefined && turn.messageFrom.length > 0) {
163
+ return about(i18next.t('agent-chat:turn.wokenByMessage', {
164
+ count: turn.messageFrom.length
165
+ }));
166
+ }
167
+ return turn.label ? i18next.t('agent-chat:turn.subagentFinished', {
168
+ label: turn.label
169
+ }) : i18next.t('agent-chat:turn.continued');
132
170
  }
133
171
  /* The items of the turn tell a turn a person stopped from one the machine ended after a restart. */
134
172
  export function turnLabel(turn, items = []) {
135
- const duration = formatElapsedShort((turn.endedAt ?? turn.createdAt) - turn.createdAt);
136
- switch (turn.state) {
137
- case 'aborted':
138
- return abortedByMachine(turn, items) ? i18next.t('agent-chat:turn.stopped', { duration }) : i18next.t('agent-chat:turn.youStopped', { duration });
139
- case 'error':
140
- return i18next.t('agent-chat:turn.failed', { duration });
141
- default:
142
- return i18next.t('agent-chat:turn.worked', { duration });
143
- }
173
+ const duration = formatElapsedShort((turn.endedAt ?? turn.createdAt) - turn.createdAt);
174
+ switch (turn.state) {
175
+ case 'aborted':
176
+ return abortedByMachine(turn, items) ? i18next.t('agent-chat:turn.stopped', {
177
+ duration
178
+ }) : i18next.t('agent-chat:turn.youStopped', {
179
+ duration
180
+ });
181
+ case 'error':
182
+ return i18next.t('agent-chat:turn.failed', {
183
+ duration
184
+ });
185
+ default:
186
+ return i18next.t('agent-chat:turn.worked', {
187
+ duration
188
+ });
189
+ }
144
190
  }
145
191
  // A subagent's own work, and an agent it opened, belong to its row, not to the thread; an old record has no row for it.
146
192
  function parentOf(item) {
147
- return item.kind === 'tool' || item.kind === 'assistant' || item.kind === 'subagent' ? (item.parentToolUseId ?? null) : null;
193
+ return item.kind === 'tool' || item.kind === 'assistant' || item.kind === 'subagent' ? item.parentToolUseId ?? null : null;
148
194
  }
149
195
  // Deeper than the CLI forwards any call; it also ends a chain of rows that would name each other.
150
196
  const MAX_NESTING = 8;
151
197
  function branchOf(item, options, children, depth = 0) {
152
- const own = children.get(item.toolUseId) ?? [];
153
- return {
154
- id: item.id,
155
- item,
156
- children: own.filter((child) => child.kind !== 'subagent'),
157
- nested: depth >= MAX_NESTING
158
- ? []
159
- : own.filter((child) => child.kind === 'subagent').map((child) => branchOf(child, options, children, depth + 1)),
160
- expanded: options.expandedSubagents.has(item.id)
161
- };
198
+ const own = children.get(item.toolUseId) ?? [];
199
+ return {
200
+ id: item.id,
201
+ item,
202
+ children: own.filter(child => child.kind !== 'subagent'),
203
+ nested: depth >= MAX_NESTING ? [] : own.filter(child => child.kind === 'subagent').map(child => branchOf(child, options, children, depth + 1)),
204
+ expanded: options.expandedSubagents.has(item.id)
205
+ };
162
206
  }
163
207
  /* The branch a subagent's row sits in, its own or one it hangs under, anywhere in the thread's rows. */
164
208
  export function findSubagentBranch(rows, toolUseId) {
165
- const search = (branch) => {
166
- if (branch.item.toolUseId === toolUseId) {
167
- return branch;
168
- }
169
- for (const child of branch.nested) {
170
- const found = search(child);
171
- if (found) {
172
- return found;
173
- }
174
- }
175
- return null;
176
- };
177
- for (let index = 0; index < rows.length; index++) {
178
- const row = rows[index];
179
- const branch = row.kind === 'subagent' ? search(row) : null;
180
- if (branch) {
181
- return { index, branch };
182
- }
209
+ const search = branch => {
210
+ if (branch.item.toolUseId === toolUseId) {
211
+ return branch;
212
+ }
213
+ for (const child of branch.nested) {
214
+ const found = search(child);
215
+ if (found) {
216
+ return found;
217
+ }
183
218
  }
184
219
  return null;
220
+ };
221
+ for (let index = 0; index < rows.length; index++) {
222
+ const row = rows[index];
223
+ const branch = row.kind === 'subagent' ? search(row) : null;
224
+ if (branch) {
225
+ return {
226
+ index,
227
+ branch
228
+ };
229
+ }
230
+ }
231
+ return null;
185
232
  }
186
233
  /* What each subagent did, keyed by the call that spawned it, in the order it happened. */
187
234
  function groupChildren(items) {
188
- const children = new Map();
189
- for (const item of items) {
190
- const parent = parentOf(item);
191
- if (parent === null) {
192
- continue;
193
- }
194
- const bucket = children.get(parent);
195
- if (bucket) {
196
- bucket.push(item);
197
- }
198
- else {
199
- children.set(parent, [item]);
200
- }
235
+ const children = new Map();
236
+ for (const item of items) {
237
+ const parent = parentOf(item);
238
+ if (parent === null) {
239
+ continue;
240
+ }
241
+ const bucket = children.get(parent);
242
+ if (bucket) {
243
+ bucket.push(item);
244
+ } else {
245
+ children.set(parent, [item]);
201
246
  }
202
- return children;
247
+ }
248
+ return children;
203
249
  }
204
250
  function flushTools(buffer, rows, options) {
205
- if (buffer.length === 0) {
206
- return;
207
- }
208
- const tools = buffer.splice(0);
209
- const live = tools.filter((tool) => tool.state === 'running');
210
- const settled = tools.filter((tool) => tool.state !== 'running');
211
- if (settled.length === 1) {
212
- rows.push({ kind: 'work', id: settled[0].id, tool: settled[0] });
213
- }
214
- else if (settled.length > 1) {
215
- const id = `group-${settled[0].id}`;
216
- const expanded = options.expandedGroups.has(id);
217
- const looked = lookSummary(settled, options.commandLooks);
218
- rows.push(looked === null
219
- ? { kind: 'work-group', id, tools: settled, summary: summarizeGroup(settled), expanded }
220
- : { kind: 'work-group', id, tools: settled, summary: looked.summary, expanded, look: looked.look });
221
- if (expanded) {
222
- for (const tool of settled) {
223
- rows.push({ kind: 'work', id: tool.id, tool });
224
- }
225
- }
226
- }
227
- for (const tool of live) {
228
- rows.push({ kind: 'work-live', id: tool.id, tool });
251
+ if (buffer.length === 0) {
252
+ return;
253
+ }
254
+ const tools = buffer.splice(0);
255
+ const live = tools.filter(tool => tool.state === 'running');
256
+ const settled = tools.filter(tool => tool.state !== 'running');
257
+ if (settled.length === 1) {
258
+ rows.push({
259
+ kind: 'work',
260
+ id: settled[0].id,
261
+ tool: settled[0]
262
+ });
263
+ } else if (settled.length > 1) {
264
+ const id = `group-${settled[0].id}`;
265
+ const expanded = options.expandedGroups.has(id);
266
+ const looked = lookSummary(settled, options.commandLooks);
267
+ rows.push(looked === null ? {
268
+ kind: 'work-group',
269
+ id,
270
+ tools: settled,
271
+ summary: summarizeGroup(settled),
272
+ expanded
273
+ } : {
274
+ kind: 'work-group',
275
+ id,
276
+ tools: settled,
277
+ summary: looked.summary,
278
+ expanded,
279
+ look: looked.look
280
+ });
281
+ if (expanded) {
282
+ for (const tool of settled) {
283
+ rows.push({
284
+ kind: 'work',
285
+ id: tool.id,
286
+ tool
287
+ });
288
+ }
229
289
  }
290
+ }
291
+ for (const tool of live) {
292
+ rows.push({
293
+ kind: 'work-live',
294
+ id: tool.id,
295
+ tool
296
+ });
297
+ }
230
298
  }
231
299
  function isImageRow(row) {
232
- return (row.kind === 'work' || row.kind === 'work-live') && isImageGeneration(row.tool);
300
+ return (row.kind === 'work' || row.kind === 'work-live') && isImageGeneration(row.tool);
233
301
  }
234
302
  /* Rows for a run of items, in order, with tool runs folded and a subagent's work under its own row. */
235
303
  function rowsForItems(items, options, children) {
236
- const rows = [];
237
- const tools = [];
238
- for (const item of items) {
239
- if (parentOf(item) !== null) {
240
- continue;
241
- }
242
- const report = handbackReportOf(item);
243
- if (item.kind === 'tool' && item.workflow !== undefined) {
244
- flushTools(tools, rows, options);
245
- rows.push({ kind: 'workflow', id: item.id, tool: item, workflow: item.workflow });
246
- continue;
247
- }
248
- if (item.kind === 'tool' && isImageGeneration(item)) {
249
- flushTools(tools, rows, options);
250
- rows.push({ kind: item.state === 'running' ? 'work-live' : 'work', id: item.id, tool: item });
251
- continue;
252
- }
253
- if (item.kind === 'tool' && report === null) {
254
- tools.push(item);
255
- continue;
256
- }
257
- if (report !== null) {
258
- flushTools(tools, rows, options);
259
- rows.push({ kind: 'report', id: item.id, text: report });
260
- continue;
261
- }
262
- flushTools(tools, rows, options);
263
- switch (item.kind) {
264
- case 'subagent':
265
- rows.push({ kind: 'subagent', ...branchOf(item, options, children) });
266
- break;
267
- case 'user':
268
- rows.push({ kind: 'user', id: item.id, item });
269
- break;
270
- case 'assistant':
271
- if (item.text !== '' || item.streaming) {
272
- rows.push({ kind: 'assistant', id: item.id, item });
273
- }
274
- break;
275
- case 'thinking':
276
- rows.push({ kind: 'thinking', id: item.id, item });
277
- break;
278
- case 'approval':
279
- // A pending one sits on the composer; only its outcome belongs in the transcript.
280
- if (item.decision !== 'pending') {
281
- rows.push({ kind: 'approval', id: item.id, item });
282
- }
283
- break;
284
- case 'question':
285
- if (item.state !== 'pending') {
286
- rows.push({ kind: 'question', id: item.id, item });
287
- }
288
- break;
289
- case 'note':
290
- rows.push({ kind: 'note', id: item.id, level: item.level, text: item.text, ...(item.from === undefined ? {} : { from: item.from }) });
291
- break;
292
- case 'compaction':
293
- rows.push({ kind: 'compaction', id: item.id, preTokens: item.preTokens });
294
- break;
295
- case 'turn':
296
- break;
297
- }
304
+ const rows = [];
305
+ const tools = [];
306
+ for (const item of items) {
307
+ if (parentOf(item) !== null) {
308
+ continue;
309
+ }
310
+ const report = handbackReportOf(item);
311
+ if (item.kind === 'tool' && item.workflow !== undefined) {
312
+ flushTools(tools, rows, options);
313
+ rows.push({
314
+ kind: 'workflow',
315
+ id: item.id,
316
+ tool: item,
317
+ workflow: item.workflow
318
+ });
319
+ continue;
320
+ }
321
+ if (item.kind === 'tool' && isImageGeneration(item)) {
322
+ flushTools(tools, rows, options);
323
+ rows.push({
324
+ kind: item.state === 'running' ? 'work-live' : 'work',
325
+ id: item.id,
326
+ tool: item
327
+ });
328
+ continue;
329
+ }
330
+ if (item.kind === 'tool' && report === null) {
331
+ tools.push(item);
332
+ continue;
333
+ }
334
+ if (report !== null) {
335
+ flushTools(tools, rows, options);
336
+ rows.push({
337
+ kind: 'report',
338
+ id: item.id,
339
+ text: report
340
+ });
341
+ continue;
298
342
  }
299
343
  flushTools(tools, rows, options);
300
- return rows;
344
+ switch (item.kind) {
345
+ case 'subagent':
346
+ rows.push({
347
+ kind: 'subagent',
348
+ ...branchOf(item, options, children)
349
+ });
350
+ break;
351
+ case 'user':
352
+ rows.push({
353
+ kind: 'user',
354
+ id: item.id,
355
+ item
356
+ });
357
+ break;
358
+ case 'assistant':
359
+ if (item.text !== '' || item.streaming) {
360
+ rows.push({
361
+ kind: 'assistant',
362
+ id: item.id,
363
+ item
364
+ });
365
+ }
366
+ break;
367
+ case 'thinking':
368
+ rows.push({
369
+ kind: 'thinking',
370
+ id: item.id,
371
+ item
372
+ });
373
+ break;
374
+ case 'approval':
375
+ // A pending one sits on the composer; only its outcome belongs in the transcript.
376
+ if (item.decision !== 'pending') {
377
+ rows.push({
378
+ kind: 'approval',
379
+ id: item.id,
380
+ item
381
+ });
382
+ }
383
+ break;
384
+ case 'question':
385
+ if (item.state !== 'pending') {
386
+ rows.push({
387
+ kind: 'question',
388
+ id: item.id,
389
+ item
390
+ });
391
+ }
392
+ break;
393
+ case 'note':
394
+ rows.push({
395
+ kind: 'note',
396
+ id: item.id,
397
+ level: item.level,
398
+ text: item.text,
399
+ ...(item.from === undefined ? {} : {
400
+ from: item.from
401
+ })
402
+ });
403
+ break;
404
+ case 'compaction':
405
+ rows.push({
406
+ kind: 'compaction',
407
+ id: item.id,
408
+ preTokens: item.preTokens
409
+ });
410
+ break;
411
+ case 'turn':
412
+ break;
413
+ }
414
+ }
415
+ flushTools(tools, rows, options);
416
+ return rows;
301
417
  }
302
418
  /* The card of a settled turn: the checkpoint diff when the host took one, else what the tool calls carry. */
303
419
  function changedFilesRow(turn, items) {
304
- const edits = items.filter((item) => item.kind === 'tool' && item.state === 'done' && isFileChange(item.name) && hasFileChanges(item));
305
- const diff = turn.checkpointDiff ?? null;
306
- const empty = diff === null ? edits.length === 0 : diff.files.length === 0;
307
- if (empty) {
308
- return null;
309
- }
310
- const moved = turn.checkpointHead !== undefined && turn.checkpointHeadAfter !== undefined && turn.checkpointHead !== turn.checkpointHeadAfter;
311
- return {
312
- kind: 'changed-files',
313
- id: `files-${turn.id}`,
314
- turnId: turn.id,
315
- tools: edits,
316
- diff,
317
- checkpoint: turn.checkpoint !== undefined,
318
- ...(moved ? { baseMoved: { from: turn.checkpointHead, to: turn.checkpointHeadAfter } } : {})
319
- };
420
+ const edits = items.filter(item => item.kind === 'tool' && item.state === 'done' && isFileChange(item.name) && hasFileChanges(item));
421
+ const diff = turn.checkpointDiff ?? null;
422
+ const empty = diff === null ? edits.length === 0 : diff.files.length === 0;
423
+ if (empty) {
424
+ return null;
425
+ }
426
+ const moved = turn.checkpointHead !== undefined && turn.checkpointHeadAfter !== undefined && turn.checkpointHead !== turn.checkpointHeadAfter;
427
+ return {
428
+ kind: 'changed-files',
429
+ id: `files-${turn.id}`,
430
+ turnId: turn.id,
431
+ tools: edits,
432
+ diff,
433
+ checkpoint: turn.checkpoint !== undefined,
434
+ ...(moved ? {
435
+ baseMoved: {
436
+ from: turn.checkpointHead,
437
+ to: turn.checkpointHeadAfter
438
+ }
439
+ } : {})
440
+ };
320
441
  }
321
442
  /*
322
443
  * The closing answer of a settled turn. A turn may end on a call the person turned down, as an agent that stops
323
444
  * there does: the answer before it closes the turn, and the refused call and its denial stay in sight with it.
324
445
  */
325
446
  function lastAssistantRow(rows) {
326
- const refused = new Set();
327
- for (let i = rows.length - 1; i >= 0; i--) {
328
- const row = rows[i];
329
- if (row.kind === 'assistant') {
330
- return row;
331
- }
332
- if (row.kind === 'approval' && row.item.decision === 'deny' && row.item.toolUseId !== null) {
333
- refused.add(row.item.toolUseId);
334
- continue;
335
- }
336
- if (row.kind === 'work' && refused.has(row.tool.toolUseId)) {
337
- continue;
338
- }
339
- if (row.kind !== 'note' && !isImageRow(row)) {
340
- return null;
341
- }
447
+ const refused = new Set();
448
+ for (let i = rows.length - 1; i >= 0; i--) {
449
+ const row = rows[i];
450
+ if (row.kind === 'assistant') {
451
+ return row;
342
452
  }
343
- return null;
453
+ if (row.kind === 'approval' && row.item.decision === 'deny' && row.item.toolUseId !== null) {
454
+ refused.add(row.item.toolUseId);
455
+ continue;
456
+ }
457
+ if (row.kind === 'work' && refused.has(row.tool.toolUseId)) {
458
+ continue;
459
+ }
460
+ if (row.kind !== 'note' && !isImageRow(row)) {
461
+ return null;
462
+ }
463
+ }
464
+ return null;
344
465
  }
345
466
  /*
346
467
  * The rows of a running turn with its latest thought over the call it led to, so what the model
@@ -349,106 +470,129 @@ function lastAssistantRow(rows) {
349
470
  * came after the call.
350
471
  */
351
472
  function withLiveThought(rows, items) {
352
- const thoughtAt = items.findLastIndex((item) => item.kind === 'thinking' && parentOf(item) === null);
353
- const thought = items[thoughtAt];
354
- if (thought?.kind !== 'thinking' || thought.text === '') {
355
- return rows;
356
- }
357
- const after = new Set(items.slice(thoughtAt + 1).map((item) => item.id));
358
- const index = rows.findIndex((row) => row.kind === 'work-live' && after.has(row.tool.id));
359
- const call = rows[index];
360
- const shownAt = rows.findIndex((row) => row.kind === 'thinking' && row.id === thought.id);
361
- const inSight = shownAt !== -1 && shownAt < index && !rows.slice(shownAt + 1, index).some((row) => row.kind === 'work');
362
- if (call?.kind !== 'work-live' || inSight) {
363
- return rows;
364
- }
365
- return [...rows.slice(0, index), { kind: 'live-thought', id: `live-thought-${thought.id}`, item: thought, at: call.tool.createdAt }, ...rows.slice(index)];
473
+ const thoughtAt = items.findLastIndex(item => item.kind === 'thinking' && parentOf(item) === null);
474
+ const thought = items[thoughtAt];
475
+ if (thought?.kind !== 'thinking' || thought.text === '') {
476
+ return rows;
477
+ }
478
+ const after = new Set(items.slice(thoughtAt + 1).map(item => item.id));
479
+ const index = rows.findIndex(row => row.kind === 'work-live' && after.has(row.tool.id));
480
+ const call = rows[index];
481
+ const shownAt = rows.findIndex(row => row.kind === 'thinking' && row.id === thought.id);
482
+ const inSight = shownAt !== -1 && shownAt < index && !rows.slice(shownAt + 1, index).some(row => row.kind === 'work');
483
+ if (call?.kind !== 'work-live' || inSight) {
484
+ return rows;
485
+ }
486
+ return [...rows.slice(0, index), {
487
+ kind: 'live-thought',
488
+ id: `live-thought-${thought.id}`,
489
+ item: thought,
490
+ at: call.tool.createdAt
491
+ }, ...rows.slice(index)];
366
492
  }
367
493
  /* Items grouped by turn, in order of first appearance; runs of turnless items keep their place. */
368
494
  function chunksByTurn(items) {
369
- const chunks = [];
370
- const byTurn = new Map();
371
- for (const item of items) {
372
- if (item.turnId === null) {
373
- const last = chunks[chunks.length - 1];
374
- if (last && last.turnId === null) {
375
- last.items.push(item);
376
- }
377
- else {
378
- chunks.push({ turnId: null, items: [item] });
379
- }
380
- continue;
381
- }
382
- let bucket = byTurn.get(item.turnId);
383
- if (!bucket) {
384
- bucket = [];
385
- byTurn.set(item.turnId, bucket);
386
- chunks.push({ turnId: item.turnId, items: bucket });
387
- }
388
- bucket.push(item);
495
+ const chunks = [];
496
+ const byTurn = new Map();
497
+ for (const item of items) {
498
+ if (item.turnId === null) {
499
+ const last = chunks[chunks.length - 1];
500
+ if (last && last.turnId === null) {
501
+ last.items.push(item);
502
+ } else {
503
+ chunks.push({
504
+ turnId: null,
505
+ items: [item]
506
+ });
507
+ }
508
+ continue;
389
509
  }
390
- return chunks;
510
+ let bucket = byTurn.get(item.turnId);
511
+ if (!bucket) {
512
+ bucket = [];
513
+ byTurn.set(item.turnId, bucket);
514
+ chunks.push({
515
+ turnId: item.turnId,
516
+ items: bucket
517
+ });
518
+ }
519
+ bucket.push(item);
520
+ }
521
+ return chunks;
391
522
  }
392
523
  /* The whole thread as rows; items without a turn (older records) are shown as they are. */
393
524
  export function deriveTimelineRows(items, options) {
394
- const rows = [];
395
- const children = groupChildren(items);
396
- for (const chunk of chunksByTurn(items)) {
397
- if (chunk.turnId === null) {
398
- rows.push(...rowsForItems(chunk.items, options, children));
399
- continue;
400
- }
401
- const turnId = chunk.turnId;
402
- const turn = chunk.items.find((item) => item.kind === 'turn');
403
- const conversation = chunk.items.filter((item) => item.kind !== 'turn');
404
- if (turn?.origin === 'agent') {
405
- // The agent started this one itself, so there is no message of the person to show above it.
406
- rows.push({ kind: 'turn-start', id: `start-${turnId}`, turn, label: agentTurnLabel(turn) });
407
- }
408
- const active = turnId === options.activeTurnId || turn?.state === 'running';
409
- if (!turn || active) {
410
- const live = rowsForItems(conversation, options, children);
411
- rows.push(...(active ? withLiveThought(live, conversation) : live));
412
- if (active) {
413
- rows.push({ kind: 'working', id: `working-${turnId}`, startedAt: turn?.createdAt ?? Date.now() });
414
- }
415
- continue;
416
- }
417
- // Steering splits the conversation inside a turn. Earlier responses keep their place above it.
418
- const lastUser = conversation.findLastIndex((item) => item.kind === 'user');
419
- rows.push(...rowsForItems(conversation.slice(0, lastUser + 1), options, children));
420
- const rest = conversation.slice(lastUser + 1);
421
- const work = rowsForItems(rest, options, children);
422
- // The closing answer stays visible; the work after the last input folds behind the label.
423
- const finalAssistant = lastAssistantRow(work);
424
- const before = finalAssistant ? work.slice(0, work.indexOf(finalAssistant)) : work;
425
- // Images remain a turn's visible result, and a workflow or a background subagent may still be running after its turn ends.
426
- const standing = before.filter((row) => isImageRow(row) || (row.kind === 'workflow' && row.tool.state === 'running') || (row.kind === 'subagent' && row.item.status === 'running'));
427
- const folded = before.filter((row) => !standing.includes(row));
428
- const expanded = options.expandedTurns.has(turnId);
429
- if (folded.length > 0) {
430
- const tools = rest.filter((item) => item.kind === 'tool' && parentOf(item) === null && handbackReportOf(item) === null && !isImageGeneration(item));
431
- rows.push({
432
- kind: 'turn-fold',
433
- id: `fold-${turnId}`,
434
- turn,
435
- label: turnLabel(turn, chunk.items),
436
- work: summarizeTurn(tools, options.commandLooks),
437
- hiddenCount: folded.length,
438
- expanded
439
- });
440
- }
441
- rows.push(...(expanded ? before : standing));
442
- const changed = changedFilesRow(turn, conversation);
443
- if (changed) {
444
- rows.push(changed);
445
- }
446
- if (finalAssistant) {
447
- rows.push(...work.slice(work.indexOf(finalAssistant)));
448
- }
449
- if (options.forkedTurns?.has(turnId) === true) {
450
- rows.push({ kind: 'forks', id: `forks-${turnId}`, turnId });
451
- }
525
+ const rows = [];
526
+ const children = groupChildren(items);
527
+ for (const chunk of chunksByTurn(items)) {
528
+ if (chunk.turnId === null) {
529
+ rows.push(...rowsForItems(chunk.items, options, children));
530
+ continue;
452
531
  }
453
- return rows;
454
- }
532
+ const turnId = chunk.turnId;
533
+ const turn = chunk.items.find(item => item.kind === 'turn');
534
+ const conversation = chunk.items.filter(item => item.kind !== 'turn');
535
+ if (turn?.origin === 'agent') {
536
+ // The agent started this one itself, so there is no message of the person to show above it.
537
+ rows.push({
538
+ kind: 'turn-start',
539
+ id: `start-${turnId}`,
540
+ turn,
541
+ label: agentTurnLabel(turn)
542
+ });
543
+ }
544
+ const active = turnId === options.activeTurnId || turn?.state === 'running';
545
+ if (!turn || active) {
546
+ const live = rowsForItems(conversation, options, children);
547
+ rows.push(...(active ? withLiveThought(live, conversation) : live));
548
+ if (active) {
549
+ rows.push({
550
+ kind: 'working',
551
+ id: `working-${turnId}`,
552
+ startedAt: turn?.createdAt ?? Date.now()
553
+ });
554
+ }
555
+ continue;
556
+ }
557
+ // Steering splits the conversation inside a turn. Earlier responses keep their place above it.
558
+ const lastUser = conversation.findLastIndex(item => item.kind === 'user');
559
+ rows.push(...rowsForItems(conversation.slice(0, lastUser + 1), options, children));
560
+ const rest = conversation.slice(lastUser + 1);
561
+ const work = rowsForItems(rest, options, children);
562
+ // The closing answer stays visible; the work after the last input folds behind the label.
563
+ const finalAssistant = lastAssistantRow(work);
564
+ const before = finalAssistant ? work.slice(0, work.indexOf(finalAssistant)) : work;
565
+ // Images remain a turn's visible result, and a workflow or a background subagent may still be running after its turn ends.
566
+ const standing = before.filter(row => isImageRow(row) || row.kind === 'workflow' && row.tool.state === 'running' || row.kind === 'subagent' && row.item.status === 'running');
567
+ const folded = before.filter(row => !standing.includes(row));
568
+ const expanded = options.expandedTurns.has(turnId);
569
+ if (folded.length > 0) {
570
+ const tools = rest.filter(item => item.kind === 'tool' && parentOf(item) === null && handbackReportOf(item) === null && !isImageGeneration(item));
571
+ rows.push({
572
+ kind: 'turn-fold',
573
+ id: `fold-${turnId}`,
574
+ turn,
575
+ label: turnLabel(turn, chunk.items),
576
+ work: summarizeTurn(tools, options.commandLooks),
577
+ hiddenCount: folded.length,
578
+ expanded
579
+ });
580
+ }
581
+ rows.push(...(expanded ? before : standing));
582
+ const changed = changedFilesRow(turn, conversation);
583
+ if (changed) {
584
+ rows.push(changed);
585
+ }
586
+ if (finalAssistant) {
587
+ rows.push(...work.slice(work.indexOf(finalAssistant)));
588
+ }
589
+ if (options.forkedTurns?.has(turnId) === true) {
590
+ rows.push({
591
+ kind: 'forks',
592
+ id: `forks-${turnId}`,
593
+ turnId
594
+ });
595
+ }
596
+ }
597
+ return rows;
598
+ }