@adecore/agents-react 0.39.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 (317) hide show
  1. package/dist/agents/AgentModelLabel.js +2 -1
  2. package/dist/agents/model-name.js +2 -1
  3. package/dist/agents/provider-paths.d.ts +2 -1
  4. package/dist/agents/provider-paths.js +7 -3
  5. package/dist/chat/account-choice.js +57 -28
  6. package/dist/chat/bookmarks.js +2 -1
  7. package/dist/chat/drafts.d.ts +0 -1
  8. package/dist/chat/drafts.js +9 -28
  9. package/dist/chat/logic/picker-entries.js +2 -8
  10. package/dist/chat/logic/resume-compaction.js +2 -5
  11. package/dist/chat/logic/timeline-copy.js +2 -8
  12. package/dist/chat/logic/timeline.js +9 -10
  13. package/dist/chat/logic/workflow.js +2 -4
  14. package/dist/chat/subagent-conversation.js +2 -1
  15. package/dist/chat/ui/GoalDock.js +2 -1
  16. package/dist/chat/ui/LimitState.js +4 -6
  17. package/dist/chat/ui/composer/use-continuable-turn.js +0 -1
  18. package/dist/chat/ui/composer/use-notice.js +2 -1
  19. package/dist/chat/ui/composer/use-prompt-history.js +1 -1
  20. package/dist/chat/ui/composer/use-prompt-menus.js +216 -253
  21. package/dist/chat/ui/composer/use-take-back.js +2 -2
  22. package/dist/chat/ui/diff/ApprovalReview.js +1 -1
  23. package/dist/chat/ui/highlighter.d.ts +0 -6
  24. package/dist/chat/ui/highlighter.js +0 -12
  25. package/dist/chat/ui/icons.d.ts +0 -1
  26. package/dist/chat/ui/icons.js +0 -3
  27. package/dist/chat/ui/rows/MessageRows.js +2 -5
  28. package/dist/chat/ui/rows/row-rhythm.js +2 -5
  29. package/dist/chat/ui/theme-watch.js +3 -7
  30. package/dist/prompts/logic/usePromptSession.js +2 -1
  31. package/dist/providers/AddAccountForm.js +2 -1
  32. package/dist/providers/CliDetail.js +104 -130
  33. package/dist/providers/account-actions.js +6 -9
  34. package/dist/state/chats.d.ts +1 -0
  35. package/dist/state/chats.js +6 -11
  36. package/dist/state/provider-accounts.d.ts +1 -1
  37. package/dist/state/provider-accounts.js +2 -8
  38. package/dist/transport.d.ts +2 -0
  39. package/dist/transport.js +14 -0
  40. package/dist/usage/ChartLogo.d.ts +5 -0
  41. package/dist/usage/ChartLogo.js +48 -0
  42. package/dist/usage/LimitsList.js +6 -16
  43. package/dist/usage/UsageBreakdown.js +17 -40
  44. package/dist/usage/UsageLimits.js +183 -211
  45. package/dist/usage/UsagePage.js +3 -17
  46. package/dist/usage/UsageSummary.js +4 -9
  47. package/dist/usage/limits.js +16 -31
  48. package/dist/usage/summary.d.ts +1 -1
  49. package/dist/usage/summary.js +3 -5
  50. package/package.json +26 -26
  51. package/src/agents/AccountDot.tsx +1 -0
  52. package/src/agents/AgentIcon.tsx +2 -0
  53. package/src/agents/AgentModelLabel.tsx +4 -6
  54. package/src/agents/ModelOptionControl.tsx +8 -0
  55. package/src/agents/account-limits.ts +3 -0
  56. package/src/agents/accounts.ts +12 -0
  57. package/src/agents/model-name.ts +13 -1
  58. package/src/agents/model-options.ts +2 -0
  59. package/src/agents/model-requirement.ts +4 -0
  60. package/src/agents/option-words.ts +2 -0
  61. package/src/agents/provider-paths.ts +8 -3
  62. package/src/agents/status-look.ts +1 -0
  63. package/src/chat/account-choice.ts +25 -19
  64. package/src/chat/actions.ts +5 -0
  65. package/src/chat/attachments.ts +8 -0
  66. package/src/chat/bookmarks.ts +13 -1
  67. package/src/chat/chat-client.ts +63 -0
  68. package/src/chat/chat-references.ts +3 -0
  69. package/src/chat/composer-effort.ts +5 -0
  70. package/src/chat/composer-send.ts +4 -0
  71. package/src/chat/drafts.ts +21 -17
  72. package/src/chat/forks.ts +4 -0
  73. package/src/chat/guards.ts +2 -0
  74. package/src/chat/index.ts +7 -0
  75. package/src/chat/logic/ansi.ts +47 -0
  76. package/src/chat/logic/bookmarks.ts +7 -0
  77. package/src/chat/logic/command-looks.ts +29 -0
  78. package/src/chat/logic/context-usage.ts +3 -0
  79. package/src/chat/logic/continue-after-stop.ts +5 -0
  80. package/src/chat/logic/effort.ts +4 -0
  81. package/src/chat/logic/embedded-code.ts +63 -0
  82. package/src/chat/logic/fork.ts +22 -0
  83. package/src/chat/logic/generated-image.ts +7 -0
  84. package/src/chat/logic/goal.ts +7 -0
  85. package/src/chat/logic/handback.ts +2 -0
  86. package/src/chat/logic/limit.ts +8 -0
  87. package/src/chat/logic/picker-entries.ts +16 -4
  88. package/src/chat/logic/resume-compaction.ts +22 -5
  89. package/src/chat/logic/scrubber.ts +38 -5
  90. package/src/chat/logic/thought.ts +5 -0
  91. package/src/chat/logic/thread-cards.ts +6 -0
  92. package/src/chat/logic/timeline-copy.ts +4 -9
  93. package/src/chat/logic/timeline-target.ts +15 -0
  94. package/src/chat/logic/timeline.ts +113 -10
  95. package/src/chat/logic/tools.ts +30 -0
  96. package/src/chat/logic/visual-height.ts +5 -0
  97. package/src/chat/logic/visual-viewport.ts +1 -0
  98. package/src/chat/logic/welcome.ts +3 -0
  99. package/src/chat/logic/workflow.ts +26 -7
  100. package/src/chat/mentions.ts +56 -0
  101. package/src/chat/pending-prompts.ts +16 -0
  102. package/src/chat/persisted-json.ts +15 -0
  103. package/src/chat/preferences.ts +16 -0
  104. package/src/chat/prompt-hold.ts +2 -0
  105. package/src/chat/quote.ts +7 -0
  106. package/src/chat/reading-width.ts +1 -0
  107. package/src/chat/recent-messages.ts +5 -0
  108. package/src/chat/resume-compaction-dismissals.ts +1 -0
  109. package/src/chat/stash.ts +12 -0
  110. package/src/chat/subagent-conversation.ts +24 -4
  111. package/src/chat/subagent-list.ts +14 -0
  112. package/src/chat/subagent-support.ts +1 -0
  113. package/src/chat/subagent-view.ts +9 -0
  114. package/src/chat/thumbnails.ts +14 -0
  115. package/src/chat/timeline-flash.ts +2 -0
  116. package/src/chat/timeline-scroll.ts +23 -0
  117. package/src/chat/ui/AccountPill.tsx +4 -0
  118. package/src/chat/ui/AnsiOutput.tsx +1 -0
  119. package/src/chat/ui/BookmarkMarker.tsx +5 -0
  120. package/src/chat/ui/BookmarkSubmenu.tsx +1 -0
  121. package/src/chat/ui/ChatActivity.tsx +13 -0
  122. package/src/chat/ui/CodeBlock.tsx +8 -0
  123. package/src/chat/ui/Composer.tsx +15 -0
  124. package/src/chat/ui/ComposerInput.tsx +22 -0
  125. package/src/chat/ui/DiffPool.tsx +3 -0
  126. package/src/chat/ui/EditDiff.tsx +5 -0
  127. package/src/chat/ui/GoalDock.tsx +13 -1
  128. package/src/chat/ui/ImageView.tsx +11 -0
  129. package/src/chat/ui/LimitState.tsx +23 -6
  130. package/src/chat/ui/Markdown.tsx +41 -0
  131. package/src/chat/ui/MermaidBlock.tsx +16 -0
  132. package/src/chat/ui/MessageActions.tsx +8 -0
  133. package/src/chat/ui/ModelMarks.tsx +1 -0
  134. package/src/chat/ui/Pickers.tsx +26 -0
  135. package/src/chat/ui/PromptComposer.tsx +2 -0
  136. package/src/chat/ui/PromptEditor.tsx +6 -0
  137. package/src/chat/ui/QuoteButton.tsx +12 -0
  138. package/src/chat/ui/ReferenceChip.tsx +3 -0
  139. package/src/chat/ui/ResumeCompactionDock.tsx +1 -0
  140. package/src/chat/ui/RowTime.tsx +1 -0
  141. package/src/chat/ui/RunSettings.tsx +38 -0
  142. package/src/chat/ui/Scrubber.tsx +14 -0
  143. package/src/chat/ui/SessionLine.tsx +1 -0
  144. package/src/chat/ui/ShellCommand.tsx +32 -0
  145. package/src/chat/ui/SubagentControls.tsx +7 -0
  146. package/src/chat/ui/SubagentInfo.tsx +1 -0
  147. package/src/chat/ui/SubagentStopButton.tsx +6 -0
  148. package/src/chat/ui/SubagentTimeline.tsx +10 -0
  149. package/src/chat/ui/Timeline.tsx +119 -0
  150. package/src/chat/ui/TimelineMenu.tsx +5 -0
  151. package/src/chat/ui/UnifiedDiff.tsx +6 -0
  152. package/src/chat/ui/UploadThumb.tsx +2 -0
  153. package/src/chat/ui/VisualDialogs.tsx +3 -0
  154. package/src/chat/ui/VisualFrame.tsx +15 -0
  155. package/src/chat/ui/Welcome.tsx +1 -0
  156. package/src/chat/ui/chip-clipboard.ts +20 -0
  157. package/src/chat/ui/code-lines.ts +14 -0
  158. package/src/chat/ui/code-theme.ts +1 -0
  159. package/src/chat/ui/composer/ComposerControls.tsx +5 -0
  160. package/src/chat/ui/composer/ComposerMenu.tsx +12 -0
  161. package/src/chat/ui/composer/DraftParts.tsx +2 -0
  162. package/src/chat/ui/composer/QueuedMessages.tsx +4 -0
  163. package/src/chat/ui/composer/chip-paste.ts +16 -0
  164. package/src/chat/ui/composer/chips.ts +6 -0
  165. package/src/chat/ui/composer/editor.ts +9 -0
  166. package/src/chat/ui/composer/insert.ts +2 -0
  167. package/src/chat/ui/composer/key-handler.ts +39 -0
  168. package/src/chat/ui/composer/keys.ts +23 -0
  169. package/src/chat/ui/composer/placeholder.ts +6 -0
  170. package/src/chat/ui/composer/prompt-picks.ts +1 -0
  171. package/src/chat/ui/composer/surface.ts +5 -0
  172. package/src/chat/ui/composer/use-chat-controls.ts +13 -0
  173. package/src/chat/ui/composer/use-compaction-offer.ts +3 -0
  174. package/src/chat/ui/composer/use-composer-menus.ts +2 -0
  175. package/src/chat/ui/composer/use-composer-submit.ts +27 -0
  176. package/src/chat/ui/composer/use-continuable-turn.ts +3 -5
  177. package/src/chat/ui/composer/use-draft-files.ts +24 -0
  178. package/src/chat/ui/composer/use-effort-stepper.ts +8 -0
  179. package/src/chat/ui/composer/use-notice.ts +4 -1
  180. package/src/chat/ui/composer/use-pending-requests.ts +3 -0
  181. package/src/chat/ui/composer/use-prompt-history.ts +20 -11
  182. package/src/chat/ui/composer/use-prompt-hold.ts +3 -0
  183. package/src/chat/ui/composer/use-prompt-menus.ts +34 -14
  184. package/src/chat/ui/composer/use-take-back.ts +20 -2
  185. package/src/chat/ui/diff/ApprovalReview.tsx +18 -15
  186. package/src/chat/ui/diff/DiffFallbackBoundary.tsx +1 -0
  187. package/src/chat/ui/diff/DiffOmitted.tsx +1 -0
  188. package/src/chat/ui/diff/DiffStack.tsx +2 -0
  189. package/src/chat/ui/diff/DiffUnavailable.tsx +1 -0
  190. package/src/chat/ui/diff/EditorEditDiff.tsx +13 -0
  191. package/src/chat/ui/diff/EditorPatchDiff.tsx +4 -0
  192. package/src/chat/ui/diff/LegacyDiffPool.tsx +2 -0
  193. package/src/chat/ui/diff/LegacyEditDiff.tsx +2 -0
  194. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +8 -0
  195. package/src/chat/ui/diff/RawDiff.tsx +1 -0
  196. package/src/chat/ui/diff/ReviewDiff.tsx +1 -0
  197. package/src/chat/ui/diff/diff-source-budget.ts +8 -0
  198. package/src/chat/ui/diff/editor-edit-model.ts +4 -0
  199. package/src/chat/ui/diff/review-plan.ts +9 -0
  200. package/src/chat/ui/diff/review-settle.ts +1 -0
  201. package/src/chat/ui/diff/syntax-runtime.ts +52 -0
  202. package/src/chat/ui/diff/use-review-stack.ts +9 -0
  203. package/src/chat/ui/diff-syntax.ts +12 -0
  204. package/src/chat/ui/diff-theme.ts +2 -0
  205. package/src/chat/ui/file-links.ts +2 -0
  206. package/src/chat/ui/find-reveal.ts +1 -0
  207. package/src/chat/ui/full-diff.ts +1 -0
  208. package/src/chat/ui/highlighter.ts +29 -15
  209. package/src/chat/ui/icons.tsx +2 -4
  210. package/src/chat/ui/index.ts +4 -0
  211. package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +25 -0
  212. package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +1 -0
  213. package/src/chat/ui/intelligent-ui/UiReply.tsx +63 -0
  214. package/src/chat/ui/intelligent-ui/block-local.ts +5 -0
  215. package/src/chat/ui/intelligent-ui/chart-data.ts +15 -0
  216. package/src/chat/ui/intelligent-ui/node-text.ts +17 -0
  217. package/src/chat/ui/intelligent-ui/renderers/binding.ts +1 -0
  218. package/src/chat/ui/intelligent-ui/renderers/chart.tsx +31 -0
  219. package/src/chat/ui/intelligent-ui/renderers/choices.tsx +4 -0
  220. package/src/chat/ui/intelligent-ui/renderers/content.tsx +18 -0
  221. package/src/chat/ui/intelligent-ui/renderers/data.tsx +11 -0
  222. package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +13 -0
  223. package/src/chat/ui/intelligent-ui/renderers/links.tsx +9 -0
  224. package/src/chat/ui/intelligent-ui/renderers/map-slots.ts +15 -0
  225. package/src/chat/ui/intelligent-ui/renderers/map-style.ts +15 -0
  226. package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +48 -0
  227. package/src/chat/ui/intelligent-ui/renderers/map.tsx +20 -0
  228. package/src/chat/ui/intelligent-ui/renderers/place-point.ts +3 -0
  229. package/src/chat/ui/intelligent-ui/renderers/structure.tsx +3 -0
  230. package/src/chat/ui/intelligent-ui/renderers/text.tsx +8 -0
  231. package/src/chat/ui/intelligent-ui/reply-parts.ts +5 -0
  232. package/src/chat/ui/intelligent-ui/table-data.ts +17 -0
  233. package/src/chat/ui/intelligent-ui/use-in-view.ts +4 -0
  234. package/src/chat/ui/intelligent-ui/use-later.ts +4 -0
  235. package/src/chat/ui/intelligent-ui/use-truncated.ts +5 -0
  236. package/src/chat/ui/intelligent-ui/use-ui-links.ts +29 -0
  237. package/src/chat/ui/intelligent-ui/use-ui-queries.ts +60 -0
  238. package/src/chat/ui/markdown-blocks.ts +64 -0
  239. package/src/chat/ui/mermaid.ts +4 -0
  240. package/src/chat/ui/quote-selection.ts +10 -0
  241. package/src/chat/ui/rehype-chips.ts +5 -0
  242. package/src/chat/ui/rehype-fade.ts +2 -0
  243. package/src/chat/ui/remark-html-as-text.ts +5 -0
  244. package/src/chat/ui/reveal.ts +18 -0
  245. package/src/chat/ui/rows/ForksRow.tsx +5 -0
  246. package/src/chat/ui/rows/GeneratedImageRow.tsx +10 -0
  247. package/src/chat/ui/rows/MessageRows.tsx +29 -5
  248. package/src/chat/ui/rows/RowContainer.tsx +1 -0
  249. package/src/chat/ui/rows/Rows.tsx +2 -0
  250. package/src/chat/ui/rows/SubagentRow.tsx +13 -0
  251. package/src/chat/ui/rows/TaskRow.tsx +7 -0
  252. package/src/chat/ui/rows/VisualRow.tsx +4 -0
  253. package/src/chat/ui/rows/WorkRows.tsx +46 -0
  254. package/src/chat/ui/rows/WorkflowRow.tsx +10 -0
  255. package/src/chat/ui/rows/row-rhythm.ts +7 -5
  256. package/src/chat/ui/theme-watch.ts +8 -7
  257. package/src/chat/ui/thread-references.ts +7 -0
  258. package/src/chat/ui/use-diff-source-tokens.ts +8 -0
  259. package/src/chat/ui/useToggleSet.ts +3 -0
  260. package/src/chat/ui/visual-theme.ts +30 -0
  261. package/src/chat/visual-bridge.ts +23 -0
  262. package/src/chat/visual-viewport.ts +12 -0
  263. package/src/host-hooks.ts +6 -4
  264. package/src/host.ts +2 -0
  265. package/src/index.ts +3 -0
  266. package/src/lazy.tsx +9 -0
  267. package/src/mounted-registry.ts +4 -0
  268. package/src/port-transport.ts +32 -0
  269. package/src/prompts/index.ts +2 -0
  270. package/src/prompts/logic/focus.ts +5 -0
  271. package/src/prompts/logic/keys.ts +12 -0
  272. package/src/prompts/logic/prompts.ts +8 -0
  273. package/src/prompts/logic/subjects.ts +8 -0
  274. package/src/prompts/logic/useFocusAfterAnswer.ts +2 -0
  275. package/src/prompts/logic/usePromptSession.ts +13 -1
  276. package/src/prompts/ui/ApprovalBody.tsx +2 -0
  277. package/src/prompts/ui/PromptActions.tsx +2 -0
  278. package/src/prompts/ui/PromptCard.tsx +6 -0
  279. package/src/prompts/ui/PromptView.tsx +6 -0
  280. package/src/prompts/ui/QuestionBody.tsx +11 -0
  281. package/src/providers/AccountColors.tsx +1 -0
  282. package/src/providers/AccountDetail.tsx +7 -0
  283. package/src/providers/AccountVariables.tsx +2 -0
  284. package/src/providers/AddAccountForm.tsx +5 -1
  285. package/src/providers/CliDetail.tsx +6 -5
  286. package/src/providers/InstallPage.tsx +11 -0
  287. package/src/providers/ProviderList.tsx +3 -0
  288. package/src/providers/ProvidersPane.tsx +18 -0
  289. package/src/providers/account-actions.ts +10 -8
  290. package/src/providers/install-steps.ts +3 -0
  291. package/src/providers/pane-target.ts +5 -0
  292. package/src/providers/provider-abilities.ts +1 -0
  293. package/src/providers/provider-groups.ts +6 -0
  294. package/src/providers/variables.ts +12 -0
  295. package/src/scope.ts +2 -0
  296. package/src/state/chats.ts +61 -14
  297. package/src/state/index.ts +2 -0
  298. package/src/state/provider-accounts.ts +7 -9
  299. package/src/state/providers.ts +1 -0
  300. package/src/state/usage.ts +6 -0
  301. package/src/storage.ts +18 -0
  302. package/src/testing/prompts.ts +5 -0
  303. package/src/transport.ts +18 -0
  304. package/src/usage/ChartLogo.tsx +12 -0
  305. package/src/usage/LimitsList.tsx +7 -8
  306. package/src/usage/UsageBreakdown.tsx +11 -4
  307. package/src/usage/UsageChart.tsx +5 -0
  308. package/src/usage/UsageLimits.tsx +10 -7
  309. package/src/usage/UsageLimitsCard.tsx +2 -0
  310. package/src/usage/UsagePage.tsx +16 -19
  311. package/src/usage/UsageSummary.tsx +4 -5
  312. package/src/usage/UsageTiles.tsx +2 -0
  313. package/src/usage/format.ts +7 -0
  314. package/src/usage/limit-groups.ts +14 -0
  315. package/src/usage/limits.ts +11 -13
  316. package/src/usage/money.ts +1 -0
  317. package/src/usage/summary.ts +15 -7
@@ -51,6 +51,7 @@ function useReplyAuthor(chatId: string) {
51
51
  const { id } = useChatScope();
52
52
  const context = useContext(ReplyContext);
53
53
  const author = useHostReplyAuthor(id, context?.chatId ?? chatId);
54
+
54
55
  return context !== null && context.chatId === undefined ? null : author;
55
56
  }
56
57
 
@@ -62,6 +63,7 @@ function AttachmentLink({ chatId, attachment }: { chatId: string; attachment: Ch
62
63
  const { id } = useChatScope();
63
64
  const { url } = useHostAttachmentUrl(id, chatId, attachment.id);
64
65
  const badge = fileBadge(attachment.name);
66
+
65
67
  return (
66
68
  <Tooltip label={attachment.name}>
67
69
  <a
@@ -86,6 +88,7 @@ function AttachmentLink({ chatId, attachment }: { chatId: string; attachment: Ch
86
88
  function AttachmentThumb({ chatId, attachment }: { chatId: string; attachment: ChatAttachment }) {
87
89
  const { id } = useChatScope();
88
90
  const source = useHostAttachmentUrl(id, chatId, attachment.id);
91
+
89
92
  return <ImageThumb source={source} alt={attachment.name} className="max-h-32 max-w-48 object-cover" />;
90
93
  }
91
94
 
@@ -113,6 +116,7 @@ export function UserRow({ chatId, item }: { chatId: string; item: ChatUserItem }
113
116
  jumpToTimelineItem(keyOf(chatId), source, { flash: true, onMissing: () => setMissing(true) });
114
117
  }
115
118
  }
119
+
116
120
  return (
117
121
  <ReferenceLookupContext value={lookup}>
118
122
  <div data-find-item={item.id} className="flex flex-col items-end" onCopy={(e) => copySelectionWithChips(e.nativeEvent)}>
@@ -189,6 +193,7 @@ function ChoiceLine({ chatId, choice }: { chatId: string; choice: ChatUiChoiceOr
189
193
  const inherited = useContext(UiReplyNavigationContext);
190
194
  const navigation = inherited?.chatId === chatId ? inherited : null;
191
195
  const Tag = navigation ? 'button' : 'div';
196
+
192
197
  return (
193
198
  <Tag
194
199
  type={navigation ? 'button' : undefined}
@@ -220,6 +225,7 @@ function MessageHeading({ children }: { children: ReactNode }) {
220
225
  /* The report a subagent handed back, drawn as a reply under a label of its own. */
221
226
  export function ReportRow({ id, text }: { id: string; text: string }) {
222
227
  const { t } = useTranslation('agent-chat');
228
+
223
229
  return (
224
230
  <div data-find-item={id} className="-mx-1 px-1 pb-2">
225
231
  <MessageHeading>{t('rows.report.heading')}</MessageHeading>
@@ -240,6 +246,7 @@ function ReplyHeading({ chatId }: { chatId: string }) {
240
246
  const author = useReplyAuthor(chatId);
241
247
  const kind = useChatRow(chatId, (row) => row?.info.provider);
242
248
  const name = useProviders((s) => s.providers.find((provider) => provider.kind === (context?.provider ?? kind))?.name);
249
+
243
250
  return <MessageHeading>{author?.name ?? name ?? t('rows.reply.agent')}</MessageHeading>;
244
251
  }
245
252
 
@@ -249,9 +256,11 @@ function ReplyHeading({ chatId }: { chatId: string }) {
249
256
  */
250
257
  export function ReplyHeader({ chatId, at }: { chatId: string; at: number | null }) {
251
258
  const author = useReplyAuthor(chatId);
259
+
252
260
  if (author === null) {
253
261
  return null;
254
262
  }
263
+
255
264
  return (
256
265
  <div aria-hidden className="mb-1 flex h-6 items-center gap-2 text-sm font-medium text-text select-none">
257
266
  {author.mark}
@@ -263,21 +272,20 @@ export function ReplyHeader({ chatId, at }: { chatId: string; at: number | null
263
272
 
264
273
  function ReplyAge({ at }: { at: number }) {
265
274
  const ref = useTickingText(() => formatAgo(Date.now() - at), 30_000);
275
+
266
276
  return <span ref={ref} className="shrink-0 font-normal text-text-faint" />;
267
277
  }
268
278
 
269
279
  /* The line that stands where the next words will land; a component, so the word is read at render and not at import. */
270
280
  function Writing() {
271
281
  const { t } = useTranslation('agent-chat');
282
+
272
283
  return <span className="chat-live-text text-sm">{t('rows.reply.writing')}</span>;
273
284
  }
274
285
 
275
286
  /*
276
- * A reply. A word at a time, it follows the text as it arrives. A block at a time, the blocks that
277
- * closed are drawn and fade in as they land, with "Writing…" where the next one will stand. Whole,
278
- * it says it is being written and fades in once it is done. Only a row that saw the reply being
279
- * written fades anything: an old reply scrolled back into view just stands there. A `#Name` it
280
- * spells for a view, node or chat of the project is a chip that opens it.
287
+ * A reply, streamed a word, a block or the whole of it at a time. Only a row that saw the reply being
288
+ * written fades anything in: an old reply scrolled back into view just stands there.
281
289
  */
282
290
  export function AssistantRow({ chatId, item: derived }: { chatId: string; item: ChatAssistantItem }) {
283
291
  const item = useCurrentItem(chatId, derived);
@@ -310,6 +318,7 @@ export function AssistantRow({ chatId, item: derived }: { chatId: string; item:
310
318
 
311
319
  if (mode === 'blocks') {
312
320
  const settled = item.streaming ? settledBlocksText(item.text) : item.text;
321
+
313
322
  return (
314
323
  <div data-find-item={item.id} className="-mx-1 px-1 pb-2" onCopy={(e) => copySelectionWithChips(e.nativeEvent)}>
315
324
  <ReplyHeading chatId={chatId} />
@@ -324,6 +333,7 @@ export function AssistantRow({ chatId, item: derived }: { chatId: string; item:
324
333
  </div>
325
334
  );
326
335
  }
336
+
327
337
  if (mode === 'whole' && item.streaming) {
328
338
  return (
329
339
  <div className="-mx-1 px-1 pb-2">
@@ -332,6 +342,7 @@ export function AssistantRow({ chatId, item: derived }: { chatId: string; item:
332
342
  </div>
333
343
  );
334
344
  }
345
+
335
346
  return (
336
347
  <div
337
348
  data-find-item={item.id}
@@ -369,6 +380,7 @@ export function ThinkingRow({ chatId, item: derived }: { chatId: string; item: C
369
380
  const preview = item.streaming ? '' : thoughtPreview(item.text);
370
381
  const duration = formatElapsedShort((item.endedAt ?? item.createdAt) - item.createdAt);
371
382
  useOpenForFind(item.id, 'text', setOpen);
383
+
372
384
  return (
373
385
  <div data-find-item={item.id} className="-mx-1 px-1 pb-2">
374
386
  <button
@@ -418,9 +430,11 @@ export function ThinkingRow({ chatId, item: derived }: { chatId: string; item: C
418
430
  export function LiveThoughtRow({ chatId, item: derived }: { chatId: string; item: ChatThinkingItem }) {
419
431
  const item = useCurrentItem(chatId, derived);
420
432
  const line = thoughtLine(item.text);
433
+
421
434
  if (line === '') {
422
435
  return null;
423
436
  }
437
+
424
438
  return (
425
439
  <div className="-mx-1 mb-0.5 flex min-h-7 items-start gap-2 px-1 py-1 text-xs text-text-muted">
426
440
  <span className={ROW_GUTTER}>
@@ -448,6 +462,7 @@ export function NoteRow({ id, level, text, from }: { id: string; level: 'info' |
448
462
  const head = breakAt === -1 ? text : text.slice(0, breakAt);
449
463
  const rest = breakAt === -1 ? '' : text.slice(breakAt + 1).trim();
450
464
  useOpenForFind(id, 'output', setOpen);
465
+
451
466
  return (
452
467
  <div
453
468
  data-find-item={id}
@@ -483,9 +498,11 @@ export function NoteRow({ id, level, text, from }: { id: string; level: 'info' |
483
498
  function OpenChatButton({ chatId, label }: { chatId: string; label: 'openFork' | 'openWaiting' }) {
484
499
  const { t } = useTranslation('agent-chat');
485
500
  const place = useHostChatPlace(chatId);
501
+
486
502
  if (place.title === null) {
487
503
  return null;
488
504
  }
505
+
489
506
  return (
490
507
  <button className="text-text-muted hover:text-text" onClick={place.go}>
491
508
  {t(`rows.note.${label}`)}
@@ -499,6 +516,7 @@ function OpenChatButton({ chatId, label }: { chatId: string; label: 'openFork' |
499
516
  */
500
517
  export function AgentTurnRow({ label, onOpen }: { label: string; onOpen?: () => void }) {
501
518
  const line = 'mb-0.5 flex h-7 w-full items-center gap-2 text-left text-xs text-text-muted';
519
+
502
520
  // A header with nowhere to go is text to read and copy; one that opens a row is a button.
503
521
  const content = (
504
522
  <>
@@ -508,9 +526,11 @@ export function AgentTurnRow({ label, onOpen }: { label: string; onOpen?: () =>
508
526
  <span className={clsx('min-w-0 truncate', onOpen === undefined && 'select-text')}>{label}</span>
509
527
  </>
510
528
  );
529
+
511
530
  if (onOpen === undefined) {
512
531
  return <div className={line}>{content}</div>;
513
532
  }
533
+
514
534
  return (
515
535
  <button className={clsx(line, 'hover:text-text')} onClick={onOpen}>
516
536
  {content}
@@ -521,6 +541,7 @@ export function AgentTurnRow({ label, onOpen }: { label: string; onOpen?: () =>
521
541
  /* A dashed line, since what stands above it is still there for the reader but only a summary to the agent. */
522
542
  export function CompactionRow({ preTokens }: { preTokens: number | null }) {
523
543
  const { t } = useTranslation('agent-chat');
544
+
524
545
  return (
525
546
  <div className="flex items-center gap-3 pb-3 text-xs text-text-faint">
526
547
  <span className="grow border-t border-dashed border-border" />
@@ -542,9 +563,11 @@ const DECISION_ICON: Record<Exclude<ChatApprovalItem['decision'], 'pending'>, Re
542
563
  /* The outcome of a permission request, one quiet line; the request itself lived on the composer. */
543
564
  export function ApprovalHistoryRow({ item }: { item: ChatApprovalItem }) {
544
565
  const { t } = useTranslation('agent-chat');
566
+
545
567
  if (item.decision === 'pending') {
546
568
  return null;
547
569
  }
570
+
548
571
  return (
549
572
  <div className="-mx-1 mb-0.5 flex h-7 items-center gap-2 px-1 text-xs text-text-faint">
550
573
  <span className={clsx(ROW_GUTTER, item.decision === 'deny' && 'text-status-error')}>{DECISION_ICON[item.decision]}</span>
@@ -563,6 +586,7 @@ export function ApprovalHistoryRow({ item }: { item: ChatApprovalItem }) {
563
586
 
564
587
  export function QuestionHistoryRow({ item }: { item: ChatQuestionItem }) {
565
588
  const { t } = useTranslation('agent-chat');
589
+
566
590
  return (
567
591
  <div>
568
592
  {item.questions.map((question) => (
@@ -14,6 +14,7 @@ export interface RowContainerProps {
14
14
 
15
15
  export function RowContainer({ row, previous, index, top, ref, children }: RowContainerProps) {
16
16
  const wide = row.kind === 'visual' && row.visual.layout === 'wide';
17
+
17
18
  return (
18
19
  <div
19
20
  data-index={index}
@@ -41,6 +41,7 @@ function AppCardBody({ card }: { card: ThreadCard }) {
41
41
 
42
42
  function AppCardRow({ card }: { card: ThreadCard }) {
43
43
  const { t } = useTranslation('agent-chat');
44
+
44
45
  return (
45
46
  <ErrorBoundary label={t('timeline.cardFailed')} resetKeys={[card]} compact className="relative rounded-lg">
46
47
  <AppCardBody card={card} />
@@ -55,6 +56,7 @@ export function Row({ row, chatId, toggleGroup, toggleTurn, toggleSubagent, open
55
56
  return <UserRow chatId={chatId} item={row.item} />;
56
57
  case 'turn-start': {
57
58
  const toolUseId = row.turn.taskToolUseId;
59
+
58
60
  return <AgentTurnRow label={row.label} onOpen={toolUseId ? () => openSubagent(toolUseId) : undefined} />;
59
61
  }
60
62
  case 'assistant':
@@ -33,16 +33,20 @@ function ChildRow({ chatId, item }: { chatId: string; item: ChatItem }) {
33
33
  if (isImageGeneration(item)) {
34
34
  return <GeneratedImageRow chatId={chatId} tool={item} />;
35
35
  }
36
+
36
37
  return item.state === 'running' ? <WorkLiveRow chatId={chatId} tool={item} /> : <WorkRow chatId={chatId} tool={item} />;
37
38
  }
39
+
38
40
  if (item.kind === 'assistant') {
39
41
  return <ChildText chatId={chatId} item={item} />;
40
42
  }
43
+
41
44
  return null;
42
45
  }
43
46
 
44
47
  function ChildText({ chatId, item: derived }: { chatId: string; item: ChatAssistantItem }) {
45
48
  const item = useCurrentItem(chatId, derived);
49
+
46
50
  return (
47
51
  <div className="-mx-1 px-1 pb-2 text-xs text-text-muted select-text">
48
52
  <Markdown text={item.text} />
@@ -54,15 +58,19 @@ function StatusPill({ item, task }: { item: ChatSubagentItem; task: SubagentTask
54
58
  const { t } = useTranslation('agent-chat');
55
59
  const paused = statusWordOf(item, task) === 'paused';
56
60
  const now = useNow(60_000, paused);
61
+
57
62
  if (paused) {
58
63
  return <span className="shrink-0 text-xs text-text-faint tabular-nums">{entryTimeOf(item, task, now)}</span>;
59
64
  }
65
+
60
66
  if (item.status === 'running') {
61
67
  return <RunningFor startedAt={item.startedAt} />;
62
68
  }
69
+
63
70
  const failed = item.status === 'failed';
64
71
  const duration = item.finishedAt === null ? null : formatElapsedShort(item.finishedAt - item.startedAt);
65
72
  const outcome = failed ? 'failed' : 'done';
73
+
66
74
  return (
67
75
  <span className={clsx('shrink-0 text-xs tabular-nums', failed ? 'text-status-error' : 'text-text-faint')}>
68
76
  {duration === null ? t(`common.status.${outcome}`) : t(`rows.subagent.${outcome}In`, { duration })}
@@ -75,16 +83,19 @@ function SubagentWork({ chatId, item, work }: { chatId: string; item: ChatSubage
75
83
  const { t } = useTranslation('agent-chat');
76
84
  const scroller = useRef<HTMLDivElement>(null);
77
85
  const running = item.status === 'running';
86
+
78
87
  useLayoutEffect(() => {
79
88
  // While it works, the newest line is the one worth seeing.
80
89
  if (running && scroller.current) {
81
90
  scroller.current.scrollTop = scroller.current.scrollHeight;
82
91
  }
83
92
  }, [work.length, running]);
93
+
84
94
  if (work.length === 0) {
85
95
  // A line inside a chat row, where the centered block of an EmptyState would outweigh the row itself.
86
96
  return <div className="ml-6 pb-1 text-xs text-text-faint">{t('rows.subagent.nothingYet')}</div>;
87
97
  }
98
+
88
99
  return (
89
100
  <div ref={scroller} className="ml-6 overflow-y-auto" style={{ maxHeight: CHILDREN_MAX_PX }}>
90
101
  {item.itemsTruncated && <div className="pb-1 text-xs text-text-faint">{t('rows.subagent.truncated')}</div>}
@@ -100,6 +111,7 @@ function SubagentResult({ itemId, result }: { itemId: string; result: string })
100
111
  const { t } = useTranslation('agent-chat');
101
112
  const [open, setOpen] = useState(false);
102
113
  useOpenForFind(itemId, 'output', setOpen);
114
+
103
115
  return (
104
116
  <div className="ml-6 pb-1">
105
117
  <button className="flex items-center gap-1.5 text-xs text-text-muted hover:text-text" onClick={() => setOpen((o) => !o)}>
@@ -146,6 +158,7 @@ export function SubagentRow({
146
158
  // A row that carries no pointer on a host that already said no has nothing to open.
147
159
  const refused = useSubagentSupport((s) => s.unsupported[id] === true);
148
160
  const press = onOpenConversation !== undefined && canOpenSubagent(item, refused) ? onOpenConversation : onToggle;
161
+
149
162
  return (
150
163
  <div data-find-item={item.id}>
151
164
  {item.origin === 'ruimte' ? (
@@ -33,6 +33,7 @@ const STATE_TONE: Record<TaskRowState, string> = {
33
33
 
34
34
  function Elapsed({ startedAt }: { startedAt: number }) {
35
35
  const ref = useTickingText(() => formatElapsedShort(Date.now() - startedAt));
36
+
36
37
  return <span ref={ref} className="shrink-0 text-xs text-text-faint tabular-nums" />;
37
38
  }
38
39
 
@@ -40,6 +41,7 @@ function StateLine({ state, task }: { state: TaskRowState; task: SubagentTask |
40
41
  const { t } = useTranslation('agent-chat');
41
42
  const until = state === 'paused' ? task?.paused?.until : undefined;
42
43
  const now = useNow(60_000, until !== undefined);
44
+
43
45
  return (
44
46
  <span className={clsx('truncate text-2xs', STATE_TONE[state])}>
45
47
  {until === undefined ? t(`rows.task.${state}`) : t('rows.task.pausedUntil', { time: formatMoment(until, now) })}
@@ -58,18 +60,23 @@ export function TaskRow({ item, task, open, onPress }: { item: ChatSubagentItem;
58
60
  const child = useChats((s) => (item.childId === undefined ? undefined : s.statusByKey[keyOf(item.childId)]?.info));
59
61
  const provider = child?.provider;
60
62
  const slug = child?.model ?? child?.selection.model ?? null;
63
+
61
64
  const model = useProviders((s) => {
62
65
  if (slug === null) {
63
66
  return null;
64
67
  }
68
+
65
69
  const models = s.providers.find((entry) => entry.kind === provider)?.models;
70
+
66
71
  return shortModelName(modelName(slug, models), models);
67
72
  });
73
+
68
74
  const state = taskRowStateOf(item, task, child);
69
75
  const title = item.description || t('rows.subagent.task');
70
76
  const startedAt = task?.createdAt ?? item.startedAt;
71
77
  const finishedAt = task === null ? item.finishedAt : (task.settledAt ?? item.finishedAt);
72
78
  const settled = state === 'done' || state === 'failed' || state === 'cancelled';
79
+
73
80
  return (
74
81
  <button className="-mx-1 mb-0.5 flex w-[calc(100%+8px)] items-start gap-2 rounded-md px-1 py-1 text-left hover:bg-surface-hover" onClick={onPress}>
75
82
  <span className={clsx(ROW_GUTTER, 'relative text-text')}>
@@ -18,11 +18,15 @@ export function VisualRow({ chatId, visual }: { chatId: string; visual: ChatVisu
18
18
  const host = useChatHost();
19
19
  const { t } = useTranslation('agent-chat');
20
20
  const { id: scopeId, keyOf } = useChatScope();
21
+
21
22
  if (host.visuals === null) {
22
23
  return null;
23
24
  }
25
+
24
26
  const extra = host.visuals.actions?.(scopeId, chatId, visual) ?? [];
27
+
25
28
  const open = (kind: 'expand' | 'remove'): void => useVisualDialog.getState().open(keyOf(chatId), visual.id, kind);
29
+
26
30
  return (
27
31
  <ErrorBoundary label={t('visuals.renderFailed')} resetKeys={[visual]} compact className="relative rounded-lg">
28
32
  <div className="group/visual relative">
@@ -59,6 +59,7 @@ const BRAND = 'font-semibold';
59
59
  function withBrand(text: string, brands: readonly string[]): ReactNode {
60
60
  for (const brand of brands) {
61
61
  const at = brand === '' ? -1 : text.indexOf(brand);
62
+
62
63
  if (at !== -1) {
63
64
  return (
64
65
  <>
@@ -69,6 +70,7 @@ function withBrand(text: string, brands: readonly string[]): ReactNode {
69
70
  );
70
71
  }
71
72
  }
73
+
72
74
  return text;
73
75
  }
74
76
 
@@ -80,18 +82,23 @@ function hasNames(look: CommandLook): look is CommandLook & Required<Pick<Comman
80
82
  function NamedLookDetail({ run, chatId, look }: { run: CommandLookRun; chatId: string; look: Required<Pick<CommandLook, 'useNames'>> }) {
81
83
  const scope = useContext(ChatScopeContext);
82
84
  const nameOf = useCommandNames(look, scope?.id ?? '', chatId);
85
+
83
86
  const names = run.words.flatMap((word): NamedWord[] => {
84
87
  const name = nameOf(word.text, word.option);
88
+
85
89
  return name === null ? [] : [{ start: word.start, end: word.end, word: word.text, name }];
86
90
  });
91
+
87
92
  return <ShellCommand command={run.detail} code={run.code} names={names} />;
88
93
  }
89
94
 
90
95
  function LookDetail({ run, chatId }: { run: CommandLookRun; chatId: string }) {
91
96
  const { look } = run;
97
+
92
98
  if (!hasNames(look)) {
93
99
  return <ShellCommand command={run.detail} code={run.code} />;
94
100
  }
101
+
95
102
  // Keyed by the look, so a row whose command turns into another look's starts its hooks again.
96
103
  return <NamedLookDetail key={look.program} run={run} chatId={chatId} look={look} />;
97
104
  }
@@ -102,6 +109,7 @@ const TAG = 'shrink-0 rounded bg-surface-hover px-1.5 text-text-faint';
102
109
  function LookTags({ run }: { run: CommandLookRun }) {
103
110
  const { t } = useTranslation('agent-chat');
104
111
  const also = run.heredoc ? [...run.others, t('work.heredoc')] : run.others;
112
+
105
113
  return (
106
114
  <>
107
115
  {also.length > 0 && <span className={TAG}>{t('work.alsoRan', { programs: also.join(', ') })}</span>}
@@ -156,6 +164,7 @@ export function ToggleLine({
156
164
  failed && 'text-status-error',
157
165
  className
158
166
  );
167
+
159
168
  const content = (
160
169
  <>
161
170
  <span className={clsx(ROW_GUTTER, live && 'text-accent')}>{icon}</span>
@@ -192,9 +201,11 @@ export function ToggleLine({
192
201
  )}
193
202
  </>
194
203
  );
204
+
195
205
  if (onToggle === undefined) {
196
206
  return <div className={classes}>{content}</div>;
197
207
  }
208
+
198
209
  return (
199
210
  <button className={classes} onClick={onToggle}>
200
211
  {content}
@@ -205,6 +216,7 @@ export function ToggleLine({
205
216
  /* The diff renderers load lazily, so a diff waits on them under a line that says so. */
206
217
  function DiffSuspense({ children }: { children: ReactNode }) {
207
218
  const { t } = useTranslation('agent-chat');
219
+
208
220
  return <LazyBoundary fallback={<div className="px-3 py-2 text-xs text-text-faint">{t('work.loadingDiff')}</div>}>{children}</LazyBoundary>;
209
221
  }
210
222
 
@@ -262,11 +274,14 @@ function DiffBody({ tool, entries, patched }: { tool: ChatToolItem; entries: rea
262
274
  /* What a call answered, muted under the call; a failure in the error color, or its exit code when the tool names one. */
263
275
  function ToolOutput({ tool }: { tool: ChatToolItem }) {
264
276
  const { t } = useTranslation('agent-chat');
277
+
265
278
  if (!hasOutput(tool.output)) {
266
279
  return null;
267
280
  }
281
+
268
282
  const failed = tool.state === 'error';
269
283
  const { code, rest } = failed ? exitCodeOf(tool.output) : { code: null, rest: tool.output };
284
+
270
285
  return (
271
286
  <>
272
287
  {hasOutput(rest) && (
@@ -286,10 +301,13 @@ function ToolOutput({ tool }: { tool: ChatToolItem }) {
286
301
  export function ToolBody({ tool }: { tool: ChatToolItem }) {
287
302
  const patches = unifiedChanges(tool);
288
303
  const changes = patches.length > 0 ? [] : fileChanges(tool.name, tool.input);
304
+
289
305
  if (patches.length + changes.length > 0) {
290
306
  return <DiffBody tool={tool} entries={reviewEntries(patches, changes)} patched={patches.length > 0} />;
291
307
  }
308
+
292
309
  const lines = toolCallLines(tool.name, tool.input);
310
+
293
311
  return (
294
312
  <div className="mt-0.5 mb-2 ml-6 flex flex-col gap-1.5 font-mono text-code select-text">
295
313
  {lines.command !== null && (
@@ -345,6 +363,7 @@ export function WorkRow({
345
363
  const opens = hasToolBody(tool);
346
364
  const shell = shellCommand(tool.name, tool.input);
347
365
  const run = detail === undefined && icon === undefined && label === undefined && shell !== null ? matchCommandLook(shell, host.commandLooks) : null;
366
+
348
367
  return (
349
368
  <div data-find-item={tool.id} className={nested ? 'ml-6' : undefined}>
350
369
  <ToggleLine
@@ -370,6 +389,7 @@ export function WorkRow({
370
389
  export function RunningFor({ startedAt }: { startedAt: number }) {
371
390
  const { t } = useTranslation('agent-chat');
372
391
  const ref = useTickingText(() => t('work.runningFor', { elapsed: formatElapsedShort(Date.now() - startedAt) }));
392
+
373
393
  return <span ref={ref} className="shrink-0 text-xs text-text-faint tabular-nums" />;
374
394
  }
375
395
 
@@ -385,6 +405,7 @@ export function WorkLiveRow({ chatId, tool: derived }: { chatId: string; tool: C
385
405
  const run = shell === null ? null : matchCommandLook(shell, commandLooks);
386
406
  const awaiting = useAwaitsApproval(chatId, tool.toolUseId);
387
407
  const { t } = useTranslation('agent-chat');
408
+
388
409
  return (
389
410
  <div>
390
411
  <ToggleLine
@@ -431,6 +452,7 @@ export function WorkGroupRow({
431
452
  onToggle(): void;
432
453
  }) {
433
454
  const failed = tools.some((tool) => tool.state === 'error');
455
+
434
456
  return (
435
457
  <div>
436
458
  <ToggleLine
@@ -464,6 +486,7 @@ export function TurnFoldRow({
464
486
  }) {
465
487
  const { commandLooks } = useChatHost();
466
488
  const brands = commandLooks?.flatMap(({ brand }) => (brand === undefined ? [] : [brand])) ?? [];
489
+
467
490
  return (
468
491
  <button
469
492
  className={clsx(
@@ -502,6 +525,7 @@ function CheckpointFileBody({ file }: { file: ChatCheckpointFile }) {
502
525
  if (file.omitted) {
503
526
  return <div className="px-3 py-2 text-xs text-text-faint">{omittedLabel(file.omitted)}</div>;
504
527
  }
528
+
505
529
  return (
506
530
  <DiffSuspense>
507
531
  <UnifiedDiff change={file} />
@@ -533,10 +557,12 @@ function ChangedFilesCard({
533
557
  const [expanded, setExpanded] = useState(false);
534
558
  // A jump opens the card, also one that mounted it by scrolling it on screen; the flash is gone by the next visit.
535
559
  const [seenReveal, setSeenReveal] = useState(0);
560
+
536
561
  if (revealNonce > 0 && revealNonce !== seenReveal) {
537
562
  setSeenReveal(revealNonce);
538
563
  setExpanded(true);
539
564
  }
565
+
540
566
  return (
541
567
  <div className="mb-3 overflow-hidden rounded-lg border border-border bg-surface-raised">
542
568
  <button
@@ -588,11 +614,14 @@ export function ChangedFilesRow({
588
614
  const [open, setOpen] = useState<Record<string, boolean>>({});
589
615
  const [fetched, setFetched] = useState<ChatCheckpointDiff | null>(null);
590
616
  const actions = useChatActions();
617
+
591
618
  useEffect(() => {
592
619
  if (diff !== null || !checkpoint) {
593
620
  return;
594
621
  }
622
+
595
623
  let alive = true;
624
+
596
625
  void actions
597
626
  .turnDiff(chatId, turnId)
598
627
  .then((answer) => {
@@ -601,16 +630,20 @@ export function ChangedFilesRow({
601
630
  }
602
631
  })
603
632
  .catch(() => undefined);
633
+
604
634
  return () => {
605
635
  alive = false;
606
636
  };
607
637
  }, [actions, chatId, turnId, diff, checkpoint]);
638
+
608
639
  const checkpointDiff = diff ?? fetched;
609
640
  const { keyOf } = useChatScope();
610
641
  const flash = useTimelineFlash((s) => s.target);
611
642
  const revealNonce = flash !== null && flash.opens && flash.key === keyOf(chatId) && flash.rowId === `files-${turnId}` ? flash.nonce : 0;
643
+
612
644
  if (checkpointDiff !== null && checkpointDiff.files.length > 0) {
613
645
  const files = checkpointDiff.files;
646
+
614
647
  return (
615
648
  <ChangedFilesCard
616
649
  revealNonce={revealNonce}
@@ -639,6 +672,7 @@ export function ChangedFilesRow({
639
672
  </ChangedFilesCard>
640
673
  );
641
674
  }
675
+
642
676
  return <ProviderChangedFiles tools={tools} open={open} setOpen={setOpen} revealNonce={revealNonce} />;
643
677
  }
644
678
 
@@ -659,6 +693,7 @@ function ProviderReviewFiles({
659
693
  }) {
660
694
  const { t } = useTranslation('agent-chat');
661
695
  const stack = useReviewStack(reviews.filter(({ path }) => open[path] === true).flatMap(({ reviews }) => reviews));
696
+
662
697
  return reviews.map(({ path, count, reviews }) => (
663
698
  <ChangedFileLine
664
699
  key={path}
@@ -685,32 +720,42 @@ function ProviderChangedFiles({
685
720
  setOpen: Dispatch<SetStateAction<Record<string, boolean>>>;
686
721
  }) {
687
722
  const byPath = new Map<string, { edits: FileChange[]; patches: ChatFileChange[] }>();
723
+
688
724
  const entryFor = (path: string) => {
689
725
  const existing = byPath.get(path);
726
+
690
727
  if (existing) {
691
728
  return existing;
692
729
  }
730
+
693
731
  const created = { edits: [] as FileChange[], patches: [] as ChatFileChange[] };
694
732
  byPath.set(path, created);
733
+
695
734
  return created;
696
735
  };
736
+
697
737
  for (const tool of tools) {
698
738
  const patches = unifiedChanges(tool);
739
+
699
740
  if (patches.length > 0) {
700
741
  for (const patch of patches) {
701
742
  entryFor(patch.path).patches.push(patch);
702
743
  }
744
+
703
745
  continue;
704
746
  }
747
+
705
748
  for (const change of fileChanges(tool.name, tool.input)) {
706
749
  entryFor(change.path || tool.id).edits.push(change);
707
750
  }
708
751
  }
752
+
709
753
  const reviews = [...byPath].map(([path, entry]) => ({
710
754
  path,
711
755
  count: entry.patches.length + entry.edits.length,
712
756
  reviews: reviewEntries(entry.patches, entry.edits)
713
757
  }));
758
+
714
759
  return (
715
760
  <ChangedFilesCard count={byPath.size} revealNonce={revealNonce}>
716
761
  <ProviderReviewFiles reviews={reviews} open={open} setOpen={setOpen} />
@@ -722,6 +767,7 @@ function ProviderChangedFiles({
722
767
  export function WorkingRow({ startedAt }: { startedAt: number }) {
723
768
  const { t } = useTranslation('agent-chat');
724
769
  const ref = useTickingText(() => formatClockDuration(Date.now() - startedAt));
770
+
725
771
  return (
726
772
  <div className="-mx-1 mb-0.5 flex h-7 items-center gap-2 px-1 text-xs text-text-muted">
727
773
  <span className={`${ROW_GUTTER} text-status-running`}>