@adecore/agents-react 0.38.0 → 0.40.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 (351) hide show
  1. package/dist/agents/AgentModelLabel.d.ts +10 -0
  2. package/dist/agents/AgentModelLabel.js +98 -0
  3. package/dist/agents/model-name.js +2 -1
  4. package/dist/agents/provider-paths.d.ts +2 -1
  5. package/dist/agents/provider-paths.js +7 -3
  6. package/dist/chat/account-choice.js +57 -28
  7. package/dist/chat/bookmarks.js +2 -1
  8. package/dist/chat/drafts.d.ts +0 -1
  9. package/dist/chat/drafts.js +9 -28
  10. package/dist/chat/logic/picker-entries.js +2 -8
  11. package/dist/chat/logic/resume-compaction.js +2 -5
  12. package/dist/chat/logic/timeline-copy.js +2 -8
  13. package/dist/chat/logic/timeline.js +9 -10
  14. package/dist/chat/logic/workflow.js +2 -4
  15. package/dist/chat/subagent-conversation.js +2 -1
  16. package/dist/chat/ui/Composer.js +385 -360
  17. package/dist/chat/ui/GoalDock.js +2 -1
  18. package/dist/chat/ui/LimitState.js +4 -6
  19. package/dist/chat/ui/Pickers.d.ts +2 -1
  20. package/dist/chat/ui/Pickers.js +126 -123
  21. package/dist/chat/ui/PromptEditor.d.ts +19 -0
  22. package/dist/chat/ui/PromptEditor.js +115 -0
  23. package/dist/chat/ui/Timeline.d.ts +2 -1
  24. package/dist/chat/ui/Timeline.js +13 -1
  25. package/dist/chat/ui/composer/ComposerControls.d.ts +3 -0
  26. package/dist/chat/ui/composer/ComposerControls.js +235 -38
  27. package/dist/chat/ui/composer/key-handler.d.ts +1 -1
  28. package/dist/chat/ui/composer/key-handler.js +3 -11
  29. package/dist/chat/ui/composer/prompt-picks.d.ts +4 -0
  30. package/dist/chat/ui/composer/prompt-picks.js +14 -0
  31. package/dist/chat/ui/composer/use-composer-menus.d.ts +4 -49
  32. package/dist/chat/ui/composer/use-composer-menus.js +65 -519
  33. package/dist/chat/ui/composer/use-continuable-turn.js +0 -1
  34. package/dist/chat/ui/composer/use-notice.js +2 -1
  35. package/dist/chat/ui/composer/use-prompt-history.js +1 -1
  36. package/dist/chat/ui/composer/use-prompt-menus.d.ts +73 -0
  37. package/dist/chat/ui/composer/use-prompt-menus.js +499 -0
  38. package/dist/chat/ui/composer/use-take-back.js +2 -2
  39. package/dist/chat/ui/diff/ApprovalReview.js +1 -1
  40. package/dist/chat/ui/highlighter.d.ts +0 -6
  41. package/dist/chat/ui/highlighter.js +0 -12
  42. package/dist/chat/ui/icons.d.ts +0 -1
  43. package/dist/chat/ui/icons.js +0 -3
  44. package/dist/chat/ui/index.d.ts +3 -0
  45. package/dist/chat/ui/index.js +2 -0
  46. package/dist/chat/ui/rows/MessageRows.js +2 -5
  47. package/dist/chat/ui/rows/row-rhythm.js +2 -5
  48. package/dist/chat/ui/theme-watch.js +3 -7
  49. package/dist/host-hooks.d.ts +2 -1
  50. package/dist/host-hooks.js +6 -0
  51. package/dist/host.d.ts +17 -1
  52. package/dist/index.d.ts +1 -1
  53. package/dist/locales/en/agent-chat.d.ts +1 -0
  54. package/dist/locales/en/agent-chat.js +1 -0
  55. package/dist/locales/nl/agent-chat.d.ts +1 -0
  56. package/dist/locales/nl/agent-chat.js +1 -0
  57. package/dist/prompts/logic/usePromptSession.js +2 -1
  58. package/dist/providers/AddAccountForm.js +2 -1
  59. package/dist/providers/CliDetail.js +104 -130
  60. package/dist/providers/account-actions.js +6 -9
  61. package/dist/providers/index.d.ts +1 -0
  62. package/dist/providers/index.js +1 -0
  63. package/dist/state/chats.d.ts +1 -0
  64. package/dist/state/chats.js +6 -11
  65. package/dist/state/provider-accounts.d.ts +1 -1
  66. package/dist/state/provider-accounts.js +2 -8
  67. package/dist/theme.css +3 -0
  68. package/dist/transport.d.ts +2 -0
  69. package/dist/transport.js +14 -0
  70. package/dist/usage/ChartLogo.d.ts +5 -0
  71. package/dist/usage/ChartLogo.js +48 -0
  72. package/dist/usage/LimitsList.js +6 -16
  73. package/dist/usage/UsageBreakdown.js +17 -40
  74. package/dist/usage/UsageLimits.js +183 -211
  75. package/dist/usage/UsagePage.js +3 -17
  76. package/dist/usage/UsageSummary.js +4 -9
  77. package/dist/usage/limits.js +16 -31
  78. package/dist/usage/summary.d.ts +1 -1
  79. package/dist/usage/summary.js +3 -5
  80. package/package.json +26 -26
  81. package/src/agents/AccountDot.tsx +1 -0
  82. package/src/agents/AgentIcon.tsx +2 -0
  83. package/src/agents/AgentModelLabel.tsx +38 -0
  84. package/src/agents/ModelOptionControl.tsx +8 -0
  85. package/src/agents/account-limits.ts +3 -0
  86. package/src/agents/accounts.ts +12 -0
  87. package/src/agents/model-name.ts +13 -1
  88. package/src/agents/model-options.ts +2 -0
  89. package/src/agents/model-requirement.ts +4 -0
  90. package/src/agents/option-words.ts +2 -0
  91. package/src/agents/provider-paths.ts +8 -3
  92. package/src/agents/status-look.ts +1 -0
  93. package/src/chat/account-choice.ts +25 -19
  94. package/src/chat/actions.ts +5 -0
  95. package/src/chat/attachments.ts +8 -0
  96. package/src/chat/bookmarks.ts +13 -1
  97. package/src/chat/chat-client.ts +63 -0
  98. package/src/chat/chat-references.ts +3 -0
  99. package/src/chat/composer-effort.ts +5 -0
  100. package/src/chat/composer-send.ts +4 -0
  101. package/src/chat/drafts.ts +21 -17
  102. package/src/chat/forks.ts +4 -0
  103. package/src/chat/guards.ts +2 -0
  104. package/src/chat/index.ts +7 -0
  105. package/src/chat/logic/ansi.ts +47 -0
  106. package/src/chat/logic/bookmarks.ts +7 -0
  107. package/src/chat/logic/command-looks.ts +29 -0
  108. package/src/chat/logic/context-usage.ts +3 -0
  109. package/src/chat/logic/continue-after-stop.ts +5 -0
  110. package/src/chat/logic/effort.ts +4 -0
  111. package/src/chat/logic/embedded-code.ts +63 -0
  112. package/src/chat/logic/fork.ts +22 -0
  113. package/src/chat/logic/generated-image.ts +7 -0
  114. package/src/chat/logic/goal.ts +7 -0
  115. package/src/chat/logic/handback.ts +2 -0
  116. package/src/chat/logic/limit.ts +8 -0
  117. package/src/chat/logic/picker-entries.ts +16 -4
  118. package/src/chat/logic/resume-compaction.ts +22 -5
  119. package/src/chat/logic/scrubber.ts +38 -5
  120. package/src/chat/logic/thought.ts +5 -0
  121. package/src/chat/logic/thread-cards.ts +6 -0
  122. package/src/chat/logic/timeline-copy.ts +4 -9
  123. package/src/chat/logic/timeline-target.ts +15 -0
  124. package/src/chat/logic/timeline.ts +113 -10
  125. package/src/chat/logic/tools.ts +30 -0
  126. package/src/chat/logic/visual-height.ts +5 -0
  127. package/src/chat/logic/visual-viewport.ts +1 -0
  128. package/src/chat/logic/welcome.ts +3 -0
  129. package/src/chat/logic/workflow.ts +26 -7
  130. package/src/chat/mentions.ts +56 -0
  131. package/src/chat/pending-prompts.ts +16 -0
  132. package/src/chat/persisted-json.ts +15 -0
  133. package/src/chat/preferences.ts +16 -0
  134. package/src/chat/prompt-hold.ts +2 -0
  135. package/src/chat/quote.ts +7 -0
  136. package/src/chat/reading-width.ts +1 -0
  137. package/src/chat/recent-messages.ts +5 -0
  138. package/src/chat/resume-compaction-dismissals.ts +1 -0
  139. package/src/chat/stash.ts +12 -0
  140. package/src/chat/subagent-conversation.ts +24 -4
  141. package/src/chat/subagent-list.ts +14 -0
  142. package/src/chat/subagent-support.ts +1 -0
  143. package/src/chat/subagent-view.ts +9 -0
  144. package/src/chat/thumbnails.ts +14 -0
  145. package/src/chat/timeline-flash.ts +2 -0
  146. package/src/chat/timeline-scroll.ts +23 -0
  147. package/src/chat/ui/AccountPill.tsx +4 -0
  148. package/src/chat/ui/AnsiOutput.tsx +1 -0
  149. package/src/chat/ui/BookmarkMarker.tsx +5 -0
  150. package/src/chat/ui/BookmarkSubmenu.tsx +1 -0
  151. package/src/chat/ui/ChatActivity.tsx +13 -0
  152. package/src/chat/ui/CodeBlock.tsx +8 -0
  153. package/src/chat/ui/Composer.tsx +27 -3
  154. package/src/chat/ui/ComposerInput.tsx +22 -0
  155. package/src/chat/ui/DiffPool.tsx +3 -0
  156. package/src/chat/ui/EditDiff.tsx +5 -0
  157. package/src/chat/ui/GoalDock.tsx +13 -1
  158. package/src/chat/ui/ImageView.tsx +11 -0
  159. package/src/chat/ui/LimitState.tsx +23 -6
  160. package/src/chat/ui/Markdown.tsx +41 -0
  161. package/src/chat/ui/MermaidBlock.tsx +16 -0
  162. package/src/chat/ui/MessageActions.tsx +8 -0
  163. package/src/chat/ui/ModelMarks.tsx +1 -0
  164. package/src/chat/ui/Pickers.tsx +30 -1
  165. package/src/chat/ui/PromptComposer.tsx +2 -0
  166. package/src/chat/ui/PromptEditor.tsx +136 -0
  167. package/src/chat/ui/QuoteButton.tsx +12 -0
  168. package/src/chat/ui/ReferenceChip.tsx +3 -0
  169. package/src/chat/ui/ResumeCompactionDock.tsx +1 -0
  170. package/src/chat/ui/RowTime.tsx +1 -0
  171. package/src/chat/ui/RunSettings.tsx +38 -0
  172. package/src/chat/ui/Scrubber.tsx +14 -0
  173. package/src/chat/ui/SessionLine.tsx +1 -0
  174. package/src/chat/ui/ShellCommand.tsx +32 -0
  175. package/src/chat/ui/SubagentControls.tsx +7 -0
  176. package/src/chat/ui/SubagentInfo.tsx +1 -0
  177. package/src/chat/ui/SubagentStopButton.tsx +6 -0
  178. package/src/chat/ui/SubagentTimeline.tsx +10 -0
  179. package/src/chat/ui/Timeline.tsx +141 -1
  180. package/src/chat/ui/TimelineMenu.tsx +5 -0
  181. package/src/chat/ui/UnifiedDiff.tsx +6 -0
  182. package/src/chat/ui/UploadThumb.tsx +2 -0
  183. package/src/chat/ui/VisualDialogs.tsx +3 -0
  184. package/src/chat/ui/VisualFrame.tsx +15 -0
  185. package/src/chat/ui/Welcome.tsx +1 -0
  186. package/src/chat/ui/chip-clipboard.ts +20 -0
  187. package/src/chat/ui/code-lines.ts +14 -0
  188. package/src/chat/ui/code-theme.ts +1 -0
  189. package/src/chat/ui/composer/ComposerControls.tsx +70 -15
  190. package/src/chat/ui/composer/ComposerMenu.tsx +12 -0
  191. package/src/chat/ui/composer/DraftParts.tsx +2 -0
  192. package/src/chat/ui/composer/QueuedMessages.tsx +4 -0
  193. package/src/chat/ui/composer/chip-paste.ts +16 -0
  194. package/src/chat/ui/composer/chips.ts +6 -0
  195. package/src/chat/ui/composer/editor.ts +9 -0
  196. package/src/chat/ui/composer/insert.ts +2 -0
  197. package/src/chat/ui/composer/key-handler.ts +42 -9
  198. package/src/chat/ui/composer/keys.ts +23 -0
  199. package/src/chat/ui/composer/placeholder.ts +6 -0
  200. package/src/chat/ui/composer/prompt-picks.ts +18 -0
  201. package/src/chat/ui/composer/surface.ts +5 -0
  202. package/src/chat/ui/composer/use-chat-controls.ts +13 -0
  203. package/src/chat/ui/composer/use-compaction-offer.ts +3 -0
  204. package/src/chat/ui/composer/use-composer-menus.ts +20 -275
  205. package/src/chat/ui/composer/use-composer-submit.ts +27 -0
  206. package/src/chat/ui/composer/use-continuable-turn.ts +3 -5
  207. package/src/chat/ui/composer/use-draft-files.ts +24 -0
  208. package/src/chat/ui/composer/use-effort-stepper.ts +8 -0
  209. package/src/chat/ui/composer/use-notice.ts +4 -1
  210. package/src/chat/ui/composer/use-pending-requests.ts +3 -0
  211. package/src/chat/ui/composer/use-prompt-history.ts +20 -11
  212. package/src/chat/ui/composer/use-prompt-hold.ts +3 -0
  213. package/src/chat/ui/composer/use-prompt-menus.ts +361 -0
  214. package/src/chat/ui/composer/use-take-back.ts +20 -2
  215. package/src/chat/ui/diff/ApprovalReview.tsx +18 -15
  216. package/src/chat/ui/diff/DiffFallbackBoundary.tsx +1 -0
  217. package/src/chat/ui/diff/DiffOmitted.tsx +1 -0
  218. package/src/chat/ui/diff/DiffStack.tsx +2 -0
  219. package/src/chat/ui/diff/DiffUnavailable.tsx +1 -0
  220. package/src/chat/ui/diff/EditorEditDiff.tsx +13 -0
  221. package/src/chat/ui/diff/EditorPatchDiff.tsx +4 -0
  222. package/src/chat/ui/diff/LegacyDiffPool.tsx +2 -0
  223. package/src/chat/ui/diff/LegacyEditDiff.tsx +2 -0
  224. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +8 -0
  225. package/src/chat/ui/diff/RawDiff.tsx +1 -0
  226. package/src/chat/ui/diff/ReviewDiff.tsx +1 -0
  227. package/src/chat/ui/diff/diff-source-budget.ts +8 -0
  228. package/src/chat/ui/diff/editor-edit-model.ts +4 -0
  229. package/src/chat/ui/diff/review-plan.ts +9 -0
  230. package/src/chat/ui/diff/review-settle.ts +1 -0
  231. package/src/chat/ui/diff/syntax-runtime.ts +52 -0
  232. package/src/chat/ui/diff/use-review-stack.ts +9 -0
  233. package/src/chat/ui/diff-syntax.ts +12 -0
  234. package/src/chat/ui/diff-theme.ts +2 -0
  235. package/src/chat/ui/file-links.ts +2 -0
  236. package/src/chat/ui/find-reveal.ts +1 -0
  237. package/src/chat/ui/full-diff.ts +1 -0
  238. package/src/chat/ui/highlighter.ts +29 -15
  239. package/src/chat/ui/icons.tsx +2 -4
  240. package/src/chat/ui/index.ts +7 -0
  241. package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +25 -0
  242. package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +1 -0
  243. package/src/chat/ui/intelligent-ui/UiReply.tsx +63 -0
  244. package/src/chat/ui/intelligent-ui/block-local.ts +5 -0
  245. package/src/chat/ui/intelligent-ui/chart-data.ts +15 -0
  246. package/src/chat/ui/intelligent-ui/node-text.ts +17 -0
  247. package/src/chat/ui/intelligent-ui/renderers/binding.ts +1 -0
  248. package/src/chat/ui/intelligent-ui/renderers/chart.tsx +31 -0
  249. package/src/chat/ui/intelligent-ui/renderers/choices.tsx +4 -0
  250. package/src/chat/ui/intelligent-ui/renderers/content.tsx +18 -0
  251. package/src/chat/ui/intelligent-ui/renderers/data.tsx +11 -0
  252. package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +13 -0
  253. package/src/chat/ui/intelligent-ui/renderers/links.tsx +9 -0
  254. package/src/chat/ui/intelligent-ui/renderers/map-slots.ts +15 -0
  255. package/src/chat/ui/intelligent-ui/renderers/map-style.ts +15 -0
  256. package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +48 -0
  257. package/src/chat/ui/intelligent-ui/renderers/map.tsx +20 -0
  258. package/src/chat/ui/intelligent-ui/renderers/place-point.ts +3 -0
  259. package/src/chat/ui/intelligent-ui/renderers/structure.tsx +3 -0
  260. package/src/chat/ui/intelligent-ui/renderers/text.tsx +8 -0
  261. package/src/chat/ui/intelligent-ui/reply-parts.ts +5 -0
  262. package/src/chat/ui/intelligent-ui/table-data.ts +17 -0
  263. package/src/chat/ui/intelligent-ui/use-in-view.ts +4 -0
  264. package/src/chat/ui/intelligent-ui/use-later.ts +4 -0
  265. package/src/chat/ui/intelligent-ui/use-truncated.ts +5 -0
  266. package/src/chat/ui/intelligent-ui/use-ui-links.ts +29 -0
  267. package/src/chat/ui/intelligent-ui/use-ui-queries.ts +60 -0
  268. package/src/chat/ui/markdown-blocks.ts +64 -0
  269. package/src/chat/ui/mermaid.ts +4 -0
  270. package/src/chat/ui/quote-selection.ts +10 -0
  271. package/src/chat/ui/rehype-chips.ts +5 -0
  272. package/src/chat/ui/rehype-fade.ts +2 -0
  273. package/src/chat/ui/remark-html-as-text.ts +5 -0
  274. package/src/chat/ui/reveal.ts +18 -0
  275. package/src/chat/ui/rows/ForksRow.tsx +5 -0
  276. package/src/chat/ui/rows/GeneratedImageRow.tsx +10 -0
  277. package/src/chat/ui/rows/MessageRows.tsx +29 -5
  278. package/src/chat/ui/rows/RowContainer.tsx +1 -0
  279. package/src/chat/ui/rows/Rows.tsx +2 -0
  280. package/src/chat/ui/rows/SubagentRow.tsx +13 -0
  281. package/src/chat/ui/rows/TaskRow.tsx +7 -0
  282. package/src/chat/ui/rows/VisualRow.tsx +4 -0
  283. package/src/chat/ui/rows/WorkRows.tsx +46 -0
  284. package/src/chat/ui/rows/WorkflowRow.tsx +10 -0
  285. package/src/chat/ui/rows/row-rhythm.ts +7 -5
  286. package/src/chat/ui/theme-watch.ts +8 -7
  287. package/src/chat/ui/thread-references.ts +7 -0
  288. package/src/chat/ui/use-diff-source-tokens.ts +8 -0
  289. package/src/chat/ui/useToggleSet.ts +3 -0
  290. package/src/chat/ui/visual-theme.ts +30 -0
  291. package/src/chat/visual-bridge.ts +23 -0
  292. package/src/chat/visual-viewport.ts +12 -0
  293. package/src/host-hooks.ts +15 -5
  294. package/src/host.ts +29 -1
  295. package/src/index.ts +5 -0
  296. package/src/lazy.tsx +9 -0
  297. package/src/locales/en/agent-chat.ts +1 -0
  298. package/src/locales/nl/agent-chat.ts +1 -0
  299. package/src/mounted-registry.ts +4 -0
  300. package/src/port-transport.ts +32 -0
  301. package/src/prompts/index.ts +2 -0
  302. package/src/prompts/logic/focus.ts +5 -0
  303. package/src/prompts/logic/keys.ts +12 -0
  304. package/src/prompts/logic/prompts.ts +8 -0
  305. package/src/prompts/logic/subjects.ts +8 -0
  306. package/src/prompts/logic/useFocusAfterAnswer.ts +2 -0
  307. package/src/prompts/logic/usePromptSession.ts +13 -1
  308. package/src/prompts/ui/ApprovalBody.tsx +2 -0
  309. package/src/prompts/ui/PromptActions.tsx +2 -0
  310. package/src/prompts/ui/PromptCard.tsx +6 -0
  311. package/src/prompts/ui/PromptView.tsx +6 -0
  312. package/src/prompts/ui/QuestionBody.tsx +11 -0
  313. package/src/providers/AccountColors.tsx +1 -0
  314. package/src/providers/AccountDetail.tsx +7 -0
  315. package/src/providers/AccountVariables.tsx +2 -0
  316. package/src/providers/AddAccountForm.tsx +5 -1
  317. package/src/providers/CliDetail.tsx +6 -5
  318. package/src/providers/InstallPage.tsx +11 -0
  319. package/src/providers/ProviderList.tsx +3 -0
  320. package/src/providers/ProvidersPane.tsx +18 -0
  321. package/src/providers/account-actions.ts +10 -8
  322. package/src/providers/index.ts +1 -0
  323. package/src/providers/install-steps.ts +3 -0
  324. package/src/providers/pane-target.ts +5 -0
  325. package/src/providers/provider-abilities.ts +1 -0
  326. package/src/providers/provider-groups.ts +6 -0
  327. package/src/providers/variables.ts +12 -0
  328. package/src/scope.ts +2 -0
  329. package/src/state/chats.ts +61 -14
  330. package/src/state/index.ts +2 -0
  331. package/src/state/provider-accounts.ts +7 -9
  332. package/src/state/providers.ts +1 -0
  333. package/src/state/usage.ts +6 -0
  334. package/src/storage.ts +18 -0
  335. package/src/testing/prompts.ts +5 -0
  336. package/src/theme.css +3 -0
  337. package/src/transport.ts +18 -0
  338. package/src/usage/ChartLogo.tsx +12 -0
  339. package/src/usage/LimitsList.tsx +7 -8
  340. package/src/usage/UsageBreakdown.tsx +11 -4
  341. package/src/usage/UsageChart.tsx +5 -0
  342. package/src/usage/UsageLimits.tsx +10 -7
  343. package/src/usage/UsageLimitsCard.tsx +2 -0
  344. package/src/usage/UsagePage.tsx +16 -19
  345. package/src/usage/UsageSummary.tsx +4 -5
  346. package/src/usage/UsageTiles.tsx +2 -0
  347. package/src/usage/format.ts +7 -0
  348. package/src/usage/limit-groups.ts +14 -0
  349. package/src/usage/limits.ts +11 -13
  350. package/src/usage/money.ts +1 -0
  351. package/src/usage/summary.ts +15 -7
@@ -95,12 +95,15 @@ function lookSummary(tools: readonly ChatToolItem[], looks: readonly CommandLook
95
95
  let look: CommandLook | null = null;
96
96
  let group: { key: string; sentence(count: number): string } | undefined;
97
97
  let sameKey = true;
98
+
98
99
  for (const tool of tools) {
99
100
  const command = shellCommand(tool.name, tool.input);
100
101
  const run = command === null ? null : matchCommandLook(command, looks);
102
+
101
103
  if (run === null || (look !== null && run.look !== look)) {
102
104
  return null;
103
105
  }
106
+
104
107
  if (look === null) {
105
108
  look = run.look;
106
109
  group = run.match.group;
@@ -108,35 +111,45 @@ function lookSummary(tools: readonly ChatToolItem[], looks: readonly CommandLook
108
111
  sameKey = false;
109
112
  }
110
113
  }
114
+
111
115
  if (look === null) {
112
116
  return null;
113
117
  }
118
+
114
119
  if (sameKey && group !== undefined) {
115
120
  return { look, summary: group.sentence(tools.length) };
116
121
  }
122
+
117
123
  return look.sentence === undefined ? null : { look, summary: look.sentence(tools.length) };
118
124
  }
119
125
 
120
126
  /* "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. */
121
127
  export function summarizeGroup(tools: ChatToolItem[], looks?: readonly CommandLook[]): string {
122
128
  const looked = lookSummary(tools, looks);
129
+
123
130
  if (looked !== null) {
124
131
  return looked.summary;
125
132
  }
133
+
126
134
  const names = new Set(tools.map((tool) => tool.name));
127
135
  const only = names.size === 1 ? tools[0]!.name : null;
128
136
  const failed = tools.filter(failedFileChange);
137
+
129
138
  if (only !== null && toolEntry(only)?.grouped === true && failed.length === 0) {
130
139
  return i18next.t(`agent-chat:group.tools.${only}`, { count: tools.length });
131
140
  }
141
+
132
142
  // Counts the files rather than the calls: three edits to one file are one file edited.
133
143
  if (tools.every((tool) => isFileChange(tool.name)) && failed.length < tools.length) {
134
144
  const paths = new Set(tools.filter((tool) => !failedFileChange(tool)).map(editedPath));
145
+
135
146
  return i18next.t('agent-chat:group.edited', { count: paths.size });
136
147
  }
148
+
137
149
  if (only !== null) {
138
150
  return i18next.t('agent-chat:group.calls', { name: only, count: tools.length });
139
151
  }
152
+
140
153
  return i18next.t('agent-chat:group.toolCalls', { count: tools.length });
141
154
  }
142
155
 
@@ -157,20 +170,25 @@ function editedPath(tool: ChatToolItem): string {
157
170
  export function summarizeTurn(tools: readonly ChatToolItem[], looks?: readonly CommandLook[]): string[] {
158
171
  // Keyed by the sentence, so its place is where the first call of that kind came.
159
172
  const counts = new Map<string, { count: number; say(count: number): string }>();
173
+
160
174
  const add = (key: string, say: (count: number) => string, total?: number): void => {
161
175
  const known = counts.get(key);
176
+
162
177
  if (known === undefined) {
163
178
  counts.set(key, { count: total ?? 1, say });
164
179
  } else {
165
180
  known.count = total ?? known.count + 1;
166
181
  }
167
182
  };
183
+
168
184
  const edited = new Set<string>();
169
185
  let other = 0;
186
+
170
187
  for (const tool of tools) {
171
188
  const command = shellCommand(tool.name, tool.input);
172
189
  const run = command === null ? null : matchCommandLook(command, looks);
173
190
  const group = run?.match.group;
191
+
174
192
  if (run !== null && group !== undefined) {
175
193
  // Keyed by the program too, so two looks with a key in common keep their own sentences.
176
194
  add(`look:${run.look.program}:${group.key}`, group.sentence);
@@ -186,10 +204,13 @@ export function summarizeTurn(tools: readonly ChatToolItem[], looks?: readonly C
186
204
  other += 1;
187
205
  }
188
206
  }
207
+
189
208
  const parts = [...counts.values()].map(({ count, say }) => say(count));
209
+
190
210
  if (other > 0) {
191
211
  parts.push(i18next.t(parts.length === 0 ? 'agent-chat:group.toolCalls' : 'agent-chat:group.otherCalls', { count: other }));
192
212
  }
213
+
193
214
  return parts;
194
215
  }
195
216
 
@@ -199,20 +220,24 @@ export function summarizeTurn(tools: readonly ChatToolItem[], looks?: readonly C
199
220
  */
200
221
  export function agentTurnLabel(turn: ChatTurnItem): string {
201
222
  const about = (what: string): string => (turn.label ? i18next.t('agent-chat:turn.about', { what, label: turn.label }) : what);
223
+
202
224
  // A turn the machine opened with the results of tasks this chat gave; the label is their titles.
203
225
  if (turn.taskIds !== undefined && turn.taskIds.length > 0) {
204
226
  return about(i18next.t('agent-chat:turn.wokenByTask', { count: turn.taskIds.length }));
205
227
  }
228
+
206
229
  // A turn the machine opened over a message another node sent; the label names who sent it.
207
230
  if (turn.messageFrom !== undefined && turn.messageFrom.length > 0) {
208
231
  return about(i18next.t('agent-chat:turn.wokenByMessage', { count: turn.messageFrom.length }));
209
232
  }
233
+
210
234
  return turn.label ? i18next.t('agent-chat:turn.subagentFinished', { label: turn.label }) : i18next.t('agent-chat:turn.continued');
211
235
  }
212
236
 
213
237
  /* The items of the turn tell a turn a person stopped from one the machine ended after a restart. */
214
238
  export function turnLabel(turn: ChatTurnItem, items: readonly ChatItem[] = []): string {
215
239
  const duration = formatElapsedShort((turn.endedAt ?? turn.createdAt) - turn.createdAt);
240
+
216
241
  switch (turn.state) {
217
242
  case 'aborted':
218
243
  return abortedByMachine(turn, items) ? i18next.t('agent-chat:turn.stopped', { duration }) : i18next.t('agent-chat:turn.youStopped', { duration });
@@ -233,6 +258,7 @@ const MAX_NESTING = 8;
233
258
 
234
259
  function branchOf(item: ChatSubagentItem, options: TimelineOptions, children: Map<string, ChatItem[]>, depth = 0): SubagentBranch {
235
260
  const own = children.get(item.toolUseId) ?? [];
261
+
236
262
  return {
237
263
  id: item.id,
238
264
  item,
@@ -251,39 +277,50 @@ export function findSubagentBranch(rows: readonly TimelineRow[], toolUseId: stri
251
277
  if (branch.item.toolUseId === toolUseId) {
252
278
  return branch;
253
279
  }
280
+
254
281
  for (const child of branch.nested) {
255
282
  const found = search(child);
283
+
256
284
  if (found) {
257
285
  return found;
258
286
  }
259
287
  }
288
+
260
289
  return null;
261
290
  };
291
+
262
292
  for (let index = 0; index < rows.length; index++) {
263
293
  const row = rows[index]!;
264
294
  const branch = row.kind === 'subagent' ? search(row) : null;
295
+
265
296
  if (branch) {
266
297
  return { index, branch };
267
298
  }
268
299
  }
300
+
269
301
  return null;
270
302
  }
271
303
 
272
304
  /* What each subagent did, keyed by the call that spawned it, in the order it happened. */
273
305
  function groupChildren(items: ChatItem[]): Map<string, ChatItem[]> {
274
306
  const children = new Map<string, ChatItem[]>();
307
+
275
308
  for (const item of items) {
276
309
  const parent = parentOf(item);
310
+
277
311
  if (parent === null) {
278
312
  continue;
279
313
  }
314
+
280
315
  const bucket = children.get(parent);
316
+
281
317
  if (bucket) {
282
318
  bucket.push(item);
283
319
  } else {
284
320
  children.set(parent, [item]);
285
321
  }
286
322
  }
323
+
287
324
  return children;
288
325
  }
289
326
 
@@ -291,26 +328,31 @@ function flushTools(buffer: ChatToolItem[], rows: TimelineRow[], options: Timeli
291
328
  if (buffer.length === 0) {
292
329
  return;
293
330
  }
331
+
294
332
  const tools = buffer.splice(0);
295
333
  const live = tools.filter((tool) => tool.state === 'running');
296
334
  const settled = tools.filter((tool) => tool.state !== 'running');
335
+
297
336
  if (settled.length === 1) {
298
337
  rows.push({ kind: 'work', id: settled[0]!.id, tool: settled[0]! });
299
338
  } else if (settled.length > 1) {
300
339
  const id = `group-${settled[0]!.id}`;
301
340
  const expanded = options.expandedGroups.has(id);
302
341
  const looked = lookSummary(settled, options.commandLooks);
342
+
303
343
  rows.push(
304
344
  looked === null
305
345
  ? { kind: 'work-group', id, tools: settled, summary: summarizeGroup(settled), expanded }
306
346
  : { kind: 'work-group', id, tools: settled, summary: looked.summary, expanded, look: looked.look }
307
347
  );
348
+
308
349
  if (expanded) {
309
350
  for (const tool of settled) {
310
351
  rows.push({ kind: 'work', id: tool.id, tool });
311
352
  }
312
353
  }
313
354
  }
355
+
314
356
  for (const tool of live) {
315
357
  rows.push({ kind: 'work-live', id: tool.id, tool });
316
358
  }
@@ -324,68 +366,90 @@ function isImageRow(row: TimelineRow): boolean {
324
366
  function rowsForItems(items: ChatItem[], options: TimelineOptions, children: Map<string, ChatItem[]>): TimelineRow[] {
325
367
  const rows: TimelineRow[] = [];
326
368
  const tools: ChatToolItem[] = [];
369
+
327
370
  for (const item of items) {
328
371
  if (parentOf(item) !== null) {
329
372
  continue;
330
373
  }
374
+
331
375
  const report = handbackReportOf(item);
376
+
332
377
  if (item.kind === 'tool' && item.workflow !== undefined) {
333
378
  flushTools(tools, rows, options);
334
379
  rows.push({ kind: 'workflow', id: item.id, tool: item, workflow: item.workflow });
380
+
335
381
  continue;
336
382
  }
383
+
337
384
  if (item.kind === 'tool' && isImageGeneration(item)) {
338
385
  flushTools(tools, rows, options);
339
386
  rows.push({ kind: item.state === 'running' ? 'work-live' : 'work', id: item.id, tool: item });
387
+
340
388
  continue;
341
389
  }
390
+
342
391
  if (item.kind === 'tool' && report === null) {
343
392
  tools.push(item);
393
+
344
394
  continue;
345
395
  }
396
+
346
397
  if (report !== null) {
347
398
  flushTools(tools, rows, options);
348
399
  rows.push({ kind: 'report', id: item.id, text: report });
400
+
349
401
  continue;
350
402
  }
403
+
351
404
  flushTools(tools, rows, options);
405
+
352
406
  switch (item.kind) {
353
407
  case 'subagent':
354
408
  rows.push({ kind: 'subagent', ...branchOf(item, options, children) });
409
+
355
410
  break;
356
411
  case 'user':
357
412
  rows.push({ kind: 'user', id: item.id, item });
413
+
358
414
  break;
359
415
  case 'assistant':
360
416
  if (item.text !== '' || item.streaming) {
361
417
  rows.push({ kind: 'assistant', id: item.id, item });
362
418
  }
419
+
363
420
  break;
364
421
  case 'thinking':
365
422
  rows.push({ kind: 'thinking', id: item.id, item });
423
+
366
424
  break;
367
425
  case 'approval':
368
426
  // A pending one sits on the composer; only its outcome belongs in the transcript.
369
427
  if (item.decision !== 'pending') {
370
428
  rows.push({ kind: 'approval', id: item.id, item });
371
429
  }
430
+
372
431
  break;
373
432
  case 'question':
374
433
  if (item.state !== 'pending') {
375
434
  rows.push({ kind: 'question', id: item.id, item });
376
435
  }
436
+
377
437
  break;
378
438
  case 'note':
379
439
  rows.push({ kind: 'note', id: item.id, level: item.level, text: item.text, ...(item.from === undefined ? {} : { from: item.from }) });
440
+
380
441
  break;
381
442
  case 'compaction':
382
443
  rows.push({ kind: 'compaction', id: item.id, preTokens: item.preTokens });
444
+
383
445
  break;
384
446
  case 'turn':
385
447
  break;
386
448
  }
387
449
  }
450
+
388
451
  flushTools(tools, rows, options);
452
+
389
453
  return rows;
390
454
  }
391
455
 
@@ -400,12 +464,16 @@ function changedFilesRow(turn: ChatTurnItem, items: ChatItem[]): TimelineRow | n
400
464
  const edits = items.filter(
401
465
  (item): item is ChatToolItem => item.kind === 'tool' && item.state === 'done' && isFileChange(item.name) && hasFileChanges(item)
402
466
  );
467
+
403
468
  const diff = turn.checkpointDiff ?? null;
404
469
  const empty = diff === null ? edits.length === 0 : diff.files.length === 0;
470
+
405
471
  if (empty) {
406
472
  return null;
407
473
  }
474
+
408
475
  const moved = turn.checkpointHead !== undefined && turn.checkpointHeadAfter !== undefined && turn.checkpointHead !== turn.checkpointHeadAfter;
476
+
409
477
  return {
410
478
  kind: 'changed-files',
411
479
  id: `files-${turn.id}`,
@@ -423,22 +491,29 @@ function changedFilesRow(turn: ChatTurnItem, items: ChatItem[]): TimelineRow | n
423
491
  */
424
492
  function lastAssistantRow(rows: TimelineRow[]): TimelineRow | null {
425
493
  const refused = new Set<string>();
494
+
426
495
  for (let i = rows.length - 1; i >= 0; i--) {
427
496
  const row = rows[i]!;
497
+
428
498
  if (row.kind === 'assistant') {
429
499
  return row;
430
500
  }
501
+
431
502
  if (row.kind === 'approval' && row.item.decision === 'deny' && row.item.toolUseId !== null) {
432
503
  refused.add(row.item.toolUseId);
504
+
433
505
  continue;
434
506
  }
507
+
435
508
  if (row.kind === 'work' && refused.has(row.tool.toolUseId)) {
436
509
  continue;
437
510
  }
511
+
438
512
  if (row.kind !== 'note' && !isImageRow(row)) {
439
513
  return null;
440
514
  }
441
515
  }
516
+
442
517
  return null;
443
518
  }
444
519
 
@@ -451,17 +526,21 @@ function lastAssistantRow(rows: TimelineRow[]): TimelineRow | null {
451
526
  function withLiveThought(rows: TimelineRow[], items: readonly ChatItem[]): TimelineRow[] {
452
527
  const thoughtAt = items.findLastIndex((item) => item.kind === 'thinking' && parentOf(item) === null);
453
528
  const thought = items[thoughtAt];
529
+
454
530
  if (thought?.kind !== 'thinking' || thought.text === '') {
455
531
  return rows;
456
532
  }
533
+
457
534
  const after = new Set(items.slice(thoughtAt + 1).map((item) => item.id));
458
535
  const index = rows.findIndex((row) => row.kind === 'work-live' && after.has(row.tool.id));
459
536
  const call = rows[index];
460
537
  const shownAt = rows.findIndex((row) => row.kind === 'thinking' && row.id === thought.id);
461
- const inSight = shownAt !== -1 && shownAt < index && !rows.slice(shownAt + 1, index).some((row) => row.kind === 'work');
538
+ const inSight = shownAt !== -1 && shownAt < index && !rows.slice(shownAt + 1, index).some((row) => row.kind === 'work' || row.kind === 'work-group');
539
+
462
540
  if (call?.kind !== 'work-live' || inSight) {
463
541
  return rows;
464
542
  }
543
+
465
544
  return [...rows.slice(0, index), { kind: 'live-thought', id: `live-thought-${thought.id}`, item: thought, at: call.tool.createdAt }, ...rows.slice(index)];
466
545
  }
467
546
 
@@ -474,24 +553,31 @@ interface TurnChunk {
474
553
  function chunksByTurn(items: ChatItem[]): TurnChunk[] {
475
554
  const chunks: TurnChunk[] = [];
476
555
  const byTurn = new Map<string, ChatItem[]>();
556
+
477
557
  for (const item of items) {
478
558
  if (item.turnId === null) {
479
- const last = chunks[chunks.length - 1];
480
- if (last && last.turnId === null) {
559
+ const last = chunks.at(-1);
560
+
561
+ if (last?.turnId === null) {
481
562
  last.items.push(item);
482
563
  } else {
483
564
  chunks.push({ turnId: null, items: [item] });
484
565
  }
566
+
485
567
  continue;
486
568
  }
569
+
487
570
  let bucket = byTurn.get(item.turnId);
571
+
488
572
  if (!bucket) {
489
573
  bucket = [];
490
574
  byTurn.set(item.turnId, bucket);
491
575
  chunks.push({ turnId: item.turnId, items: bucket });
492
576
  }
577
+
493
578
  bucket.push(item);
494
579
  }
580
+
495
581
  return chunks;
496
582
  }
497
583
 
@@ -499,27 +585,36 @@ function chunksByTurn(items: ChatItem[]): TurnChunk[] {
499
585
  export function deriveTimelineRows(items: ChatItem[], options: TimelineOptions): TimelineRow[] {
500
586
  const rows: TimelineRow[] = [];
501
587
  const children = groupChildren(items);
588
+
502
589
  for (const chunk of chunksByTurn(items)) {
503
590
  if (chunk.turnId === null) {
504
591
  rows.push(...rowsForItems(chunk.items, options, children));
592
+
505
593
  continue;
506
594
  }
595
+
507
596
  const turnId = chunk.turnId;
508
597
  const turn = chunk.items.find((item): item is ChatTurnItem => item.kind === 'turn');
509
598
  const conversation = chunk.items.filter((item) => item.kind !== 'turn');
599
+
510
600
  if (turn?.origin === 'agent') {
511
601
  // The agent started this one itself, so there is no message of the person to show above it.
512
602
  rows.push({ kind: 'turn-start', id: `start-${turnId}`, turn, label: agentTurnLabel(turn) });
513
603
  }
604
+
514
605
  const active = turnId === options.activeTurnId || turn?.state === 'running';
606
+
515
607
  if (!turn || active) {
516
608
  const live = rowsForItems(conversation, options, children);
517
609
  rows.push(...(active ? withLiveThought(live, conversation) : live));
610
+
518
611
  if (active) {
519
612
  rows.push({ kind: 'working', id: `working-${turnId}`, startedAt: turn?.createdAt ?? Date.now() });
520
613
  }
614
+
521
615
  continue;
522
616
  }
617
+
523
618
  // Steering splits the conversation inside a turn. Earlier responses keep their place above it.
524
619
  const lastUser = conversation.findLastIndex((item) => item.kind === 'user');
525
620
  rows.push(...rowsForItems(conversation.slice(0, lastUser + 1), options, children));
@@ -527,38 +622,46 @@ export function deriveTimelineRows(items: ChatItem[], options: TimelineOptions):
527
622
  const work = rowsForItems(rest, options, children);
528
623
  // The closing answer stays visible; the work after the last input folds behind the label.
529
624
  const finalAssistant = lastAssistantRow(work);
530
- const before = finalAssistant ? work.slice(0, work.indexOf(finalAssistant)) : work;
625
+ const closing = finalAssistant ? work.indexOf(finalAssistant) : work.length;
626
+ const before = work.slice(0, closing);
627
+
531
628
  // Images remain a turn's visible result, and a workflow or a background subagent may still be running after its turn ends.
532
629
  const standing = before.filter(
533
630
  (row) => isImageRow(row) || (row.kind === 'workflow' && row.tool.state === 'running') || (row.kind === 'subagent' && row.item.status === 'running')
534
631
  );
535
- const folded = before.filter((row) => !standing.includes(row));
632
+
633
+ const folded = before.length - standing.length;
536
634
  const expanded = options.expandedTurns.has(turnId);
537
- if (folded.length > 0) {
635
+
636
+ if (folded > 0) {
538
637
  const tools = rest.filter(
539
638
  (item): item is ChatToolItem => item.kind === 'tool' && parentOf(item) === null && handbackReportOf(item) === null && !isImageGeneration(item)
540
639
  );
640
+
541
641
  rows.push({
542
642
  kind: 'turn-fold',
543
643
  id: `fold-${turnId}`,
544
644
  turn,
545
645
  label: turnLabel(turn, chunk.items),
546
646
  work: summarizeTurn(tools, options.commandLooks),
547
- hiddenCount: folded.length,
647
+ hiddenCount: folded,
548
648
  expanded
549
649
  });
550
650
  }
651
+
551
652
  rows.push(...(expanded ? before : standing));
552
653
  const changed = changedFilesRow(turn, conversation);
654
+
553
655
  if (changed) {
554
656
  rows.push(changed);
555
657
  }
556
- if (finalAssistant) {
557
- rows.push(...work.slice(work.indexOf(finalAssistant)));
558
- }
658
+
659
+ rows.push(...work.slice(closing));
660
+
559
661
  if (options.forkedTurns?.has(turnId) === true) {
560
662
  rows.push({ kind: 'forks', id: `forks-${turnId}`, turnId });
561
663
  }
562
664
  }
665
+
563
666
  return rows;
564
667
  }
@@ -17,18 +17,24 @@ export function toolSummary(name: string, input: unknown): string {
17
17
  if (!isRecord(input)) {
18
18
  return '';
19
19
  }
20
+
20
21
  const known = toolEntry(name);
22
+
21
23
  if (known === undefined) {
22
24
  // A tool nobody wrote down, an MCP one above all, gets its best guess from the first string it was given.
23
25
  const first = Object.values(input).find((value) => typeof value === 'string');
26
+
24
27
  return typeof first === 'string' ? first : '';
25
28
  }
29
+
26
30
  for (const key of known.summary) {
27
31
  const value = str(input[key]);
32
+
28
33
  if (value !== null) {
29
34
  return value;
30
35
  }
31
36
  }
37
+
32
38
  return '';
33
39
  }
34
40
 
@@ -38,10 +44,13 @@ const PATH_KEYS = new Set(['file_path', 'notebook_path']);
38
44
  /* Whether the one line of a call is the path of a file, which a row cuts in the middle so the name stays. */
39
45
  export function toolSummaryIsPath(name: string, input: unknown): boolean {
40
46
  const known = toolEntry(name);
47
+
41
48
  if (known === undefined || !isRecord(input)) {
42
49
  return false;
43
50
  }
51
+
44
52
  const key = known.summary.find((candidate) => str(input[candidate]) !== null);
53
+
45
54
  return key !== undefined && PATH_KEYS.has(key);
46
55
  }
47
56
 
@@ -50,7 +59,9 @@ export function shellCommand(name: string, input: unknown): string | null {
50
59
  if (toolEntry(name)?.shell !== true || !isRecord(input)) {
51
60
  return null;
52
61
  }
62
+
53
63
  const command = str(input.command);
64
+
54
65
  return command === null || command.trim() === '' ? null : command;
55
66
  }
56
67
 
@@ -76,21 +87,27 @@ function valueText(value: unknown): string {
76
87
  export function toolCallLines(name: string, input: unknown): ToolCallLines {
77
88
  if (!isRecord(input)) {
78
89
  const text = input === null || input === undefined || input === '' ? null : valueText(input);
90
+
79
91
  return { command: null, args: [], text };
80
92
  }
93
+
81
94
  const command = shellCommand(name, input);
95
+
82
96
  const args = Object.entries(input)
83
97
  .filter(([key, value]) => value !== undefined && !(command !== null && SHELL_SHOWN.has(key)))
84
98
  .map(([key, value]) => [key, valueText(value)] as const);
99
+
85
100
  return { command, args, text: null };
86
101
  }
87
102
 
88
103
  /* A failed call's output with the exit code its tool put on the first line ("Exit code 1"), as Claude Code's shell does. */
89
104
  export function exitCodeOf(output: string): { code: number | null; rest: string } {
90
105
  const match = /^Exit code (-?\d+)[ \t]*(?:\r?\n|$)/.exec(output);
106
+
91
107
  if (match === null || Number(match[1]) === 0) {
92
108
  return { code: null, rest: output };
93
109
  }
110
+
94
111
  return { code: Number(match[1]), rest: output.slice(match[0].length) };
95
112
  }
96
113
 
@@ -104,7 +121,9 @@ export function hasToolBody(tool: ChatToolItem): boolean {
104
121
  if (hasFileChanges(tool) || hasOutput(tool.output)) {
105
122
  return true;
106
123
  }
124
+
107
125
  const lines = toolCallLines(tool.name, tool.input);
126
+
108
127
  return lines.command !== null || lines.args.length > 0 || lines.text !== null;
109
128
  }
110
129
 
@@ -116,11 +135,15 @@ export function readImagePath(name: string, input: unknown): string | null {
116
135
  if (toolEntry(name)?.readsImage !== true) {
117
136
  return null;
118
137
  }
138
+
119
139
  const path = isRecord(input) ? str(input.file_path) : null;
140
+
120
141
  if (path === null) {
121
142
  return null;
122
143
  }
144
+
123
145
  const lowered = path.toLowerCase();
146
+
124
147
  return IMAGE_SUFFIXES.some((suffix) => lowered.endsWith(suffix)) ? path : null;
125
148
  }
126
149
 
@@ -129,16 +152,21 @@ export function fileChanges(name: string, input: unknown): FileChange[] {
129
152
  if (!isRecord(input)) {
130
153
  return [];
131
154
  }
155
+
132
156
  const path = str(input.file_path) ?? '';
157
+
133
158
  if (name === 'Edit') {
134
159
  return [{ path, before: str(input.old_string) ?? '', after: str(input.new_string) ?? '' }];
135
160
  }
161
+
136
162
  if (name === 'Write') {
137
163
  return [{ path, before: '', after: str(input.content) ?? '' }];
138
164
  }
165
+
139
166
  if (name === 'MultiEdit' && Array.isArray(input.edits)) {
140
167
  return input.edits.filter(isRecord).map((edit) => ({ path, before: str(edit.old_string) ?? '', after: str(edit.new_string) ?? '' }));
141
168
  }
169
+
142
170
  return [];
143
171
  }
144
172
 
@@ -154,9 +182,11 @@ export function unifiedChanges(tool: ChatToolItem): ChatFileChange[] {
154
182
  /* The same, for the copy an approval carries so the person can read what they are approving. */
155
183
  export function approvalChanges(input: unknown): ChatFileChange[] {
156
184
  const changes = isRecord(input) ? input.changes : null;
185
+
157
186
  if (!Array.isArray(changes)) {
158
187
  return [];
159
188
  }
189
+
160
190
  return changes.filter(
161
191
  (change): change is ChatFileChange => isRecord(change) && typeof change.path === 'string' && typeof change.diff === 'string' && change.diff !== ''
162
192
  );
@@ -2,6 +2,7 @@ import { VISUAL_LIMITS, visualFrameHeight, type ChatVisual } from '@adecore/agen
2
2
 
3
3
  /* A frame before anything measured its page: room for a chart, never taller than the page may grow. */
4
4
  export const DEFAULT_HEIGHT_PX = 240;
5
+
5
6
  /* Plenty for the visuals of a long session; the oldest goes first. */
6
7
  const REMEMBERED_HEIGHTS = 500;
7
8
  // Stay within browser layout coordinates even if a page reports a bogus finite size.
@@ -28,11 +29,14 @@ export function visualContentHeight(height: number): number {
28
29
 
29
30
  export function initialVisualContentHeight(visual: Pick<ChatVisual, 'id' | 'heights'>, width: number): number {
30
31
  const cached = remembered.get(keyOf(visual.id, width));
32
+
31
33
  if (cached !== undefined) {
32
34
  return cached;
33
35
  }
36
+
34
37
  const below = visual.heights?.filter(([measured]) => measured <= width).sort((left, right) => right[0] - left[0])[0];
35
38
  const above = visual.heights?.filter(([measured]) => measured >= width).sort((left, right) => left[0] - right[0])[0];
39
+
36
40
  return below || above ? visualContentHeight(Math.max(below?.[1] ?? 0, above?.[1] ?? 0)) : DEFAULT_HEIGHT_PX;
37
41
  }
38
42
 
@@ -48,6 +52,7 @@ export function rememberVisualHeight(visualId: string, width: number, height: nu
48
52
  const key = keyOf(visualId, width);
49
53
  remembered.delete(key);
50
54
  remembered.set(key, height);
55
+
51
56
  if (remembered.size > REMEMBERED_HEIGHTS) {
52
57
  remembered.delete(remembered.keys().next().value!);
53
58
  }
@@ -5,5 +5,6 @@ export interface VisualViewportGeometry {
5
5
 
6
6
  export function visualViewportGeometry(contentHeight: number, availableHeight: number, top: number): VisualViewportGeometry {
7
7
  const height = Math.max(1, Math.min(contentHeight, availableHeight));
8
+
8
9
  return { height, top: Math.max(0, Math.min(-top, contentHeight - height)) };
9
10
  }
@@ -3,11 +3,14 @@ export type DayPart = 'morning' | 'afternoon' | 'evening' | 'night';
3
3
 
4
4
  export function dayPartOf(now: Date): DayPart {
5
5
  const hour = now.getHours();
6
+
6
7
  if (hour < 5) {
7
8
  return 'night';
8
9
  }
10
+
9
11
  if (hour < 12) {
10
12
  return 'morning';
11
13
  }
14
+
12
15
  return hour < 18 ? 'afternoon' : 'evening';
13
16
  }