@adecore/agents-react 0.23.1 → 0.24.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 (341) hide show
  1. package/README.md +14 -11
  2. package/dist/agents/AccountDot.js +3 -1
  3. package/dist/agents/accounts.d.ts +3 -2
  4. package/dist/agents/accounts.js +5 -6
  5. package/dist/chat/actions.d.ts +2 -2
  6. package/dist/chat/actions.js +8 -5
  7. package/dist/chat/bookmarks.d.ts +5 -3
  8. package/dist/chat/bookmarks.js +13 -14
  9. package/dist/chat/chat-client.d.ts +1 -0
  10. package/dist/chat/chat-client.js +7 -0
  11. package/dist/chat/composer-send.d.ts +3 -0
  12. package/dist/chat/composer-send.js +22 -0
  13. package/dist/chat/forks.js +22 -5
  14. package/dist/chat/index.d.ts +31 -0
  15. package/dist/chat/index.js +28 -0
  16. package/dist/chat/logic/embedded-code.d.ts +8 -0
  17. package/dist/chat/logic/embedded-code.js +371 -0
  18. package/dist/chat/logic/fork.js +1 -0
  19. package/dist/chat/logic/goal.d.ts +6 -0
  20. package/dist/chat/logic/goal.js +26 -0
  21. package/dist/chat/logic/scrubber.d.ts +1 -1
  22. package/dist/chat/logic/scrubber.js +6 -5
  23. package/dist/chat/logic/thought.d.ts +3 -0
  24. package/dist/chat/logic/thought.js +35 -0
  25. package/dist/chat/logic/thread-cards.js +2 -0
  26. package/dist/chat/logic/timeline.d.ts +5 -0
  27. package/dist/chat/logic/timeline.js +22 -1
  28. package/dist/chat/logic/tool-catalog.d.ts +1 -0
  29. package/dist/chat/logic/tool-catalog.js +3 -1
  30. package/dist/chat/logic/tools.d.ts +14 -0
  31. package/dist/chat/logic/tools.js +57 -0
  32. package/dist/chat/preferences.d.ts +6 -0
  33. package/dist/chat/preferences.js +19 -3
  34. package/dist/chat/reading-width.d.ts +9 -0
  35. package/dist/chat/reading-width.js +27 -0
  36. package/dist/chat/send-key.d.ts +10 -0
  37. package/dist/chat/send-key.js +24 -0
  38. package/dist/chat/ui/BookmarkMarker.js +4 -2
  39. package/dist/chat/ui/BookmarkSubmenu.js +4 -3
  40. package/dist/chat/ui/ChatActivity.js +14 -10
  41. package/dist/chat/ui/ChatReferenceChip.js +3 -4
  42. package/dist/chat/ui/CodeBlock.d.ts +2 -1
  43. package/dist/chat/ui/CodeBlock.js +14 -96
  44. package/dist/chat/ui/Composer.d.ts +1 -1
  45. package/dist/chat/ui/Composer.js +117 -906
  46. package/dist/chat/ui/ComposerInput.js +5 -4
  47. package/dist/chat/ui/EditDiff.js +3 -2
  48. package/dist/chat/ui/GoalDock.d.ts +9 -0
  49. package/dist/chat/ui/GoalDock.js +63 -0
  50. package/dist/chat/ui/LimitState.js +5 -3
  51. package/dist/chat/ui/Markdown.d.ts +2 -1
  52. package/dist/chat/ui/Markdown.js +70 -31
  53. package/dist/chat/ui/MermaidBlock.d.ts +3 -0
  54. package/dist/chat/ui/MermaidBlock.js +73 -0
  55. package/dist/chat/ui/MessageActions.js +12 -7
  56. package/dist/chat/ui/Pickers.js +6 -5
  57. package/dist/chat/ui/PromptComposer.js +4 -3
  58. package/dist/chat/ui/RowTime.d.ts +3 -0
  59. package/dist/chat/ui/RowTime.js +12 -0
  60. package/dist/chat/ui/RunSettings.js +5 -4
  61. package/dist/chat/ui/ShellCommand.d.ts +3 -0
  62. package/dist/chat/ui/ShellCommand.js +110 -0
  63. package/dist/chat/ui/SubagentInfo.js +4 -3
  64. package/dist/chat/ui/SubagentStopButton.js +4 -3
  65. package/dist/chat/ui/SubagentTimeline.js +4 -1
  66. package/dist/chat/ui/Timeline.js +70 -40
  67. package/dist/chat/ui/TimelineMenu.d.ts +2 -1
  68. package/dist/chat/ui/TimelineMenu.js +18 -8
  69. package/dist/chat/ui/UnifiedDiff.js +3 -2
  70. package/dist/chat/ui/VisualFrame.js +6 -7
  71. package/dist/chat/ui/code-theme.d.ts +2 -1
  72. package/dist/chat/ui/code-theme.js +4 -4
  73. package/dist/chat/ui/composer/ComposerControls.d.ts +27 -0
  74. package/dist/chat/ui/composer/ComposerControls.js +34 -0
  75. package/dist/chat/ui/composer/ComposerMenu.d.ts +4 -0
  76. package/dist/chat/ui/composer/ComposerMenu.js +46 -0
  77. package/dist/chat/ui/composer/DraftParts.d.ts +18 -0
  78. package/dist/chat/ui/composer/DraftParts.js +25 -0
  79. package/dist/chat/ui/composer/QueuedMessages.d.ts +10 -0
  80. package/dist/chat/ui/composer/QueuedMessages.js +20 -0
  81. package/dist/chat/ui/composer/key-handler.d.ts +20 -0
  82. package/dist/chat/ui/composer/key-handler.js +150 -0
  83. package/dist/chat/ui/composer/keys.d.ts +7 -2
  84. package/dist/chat/ui/composer/keys.js +19 -9
  85. package/dist/chat/ui/composer/placeholder.d.ts +9 -0
  86. package/dist/chat/ui/composer/placeholder.js +38 -0
  87. package/dist/chat/ui/composer/use-chat-controls.d.ts +28 -0
  88. package/dist/chat/ui/composer/use-chat-controls.js +112 -0
  89. package/dist/chat/ui/composer/use-compaction-offer.d.ts +2 -0
  90. package/dist/chat/ui/composer/use-compaction-offer.js +23 -0
  91. package/dist/chat/ui/composer/use-composer-draft.d.ts +14 -0
  92. package/dist/chat/ui/composer/use-composer-draft.js +39 -0
  93. package/dist/chat/ui/composer/use-composer-menus.d.ts +52 -0
  94. package/dist/chat/ui/composer/use-composer-menus.js +232 -0
  95. package/dist/chat/ui/composer/use-composer-submit.d.ts +32 -0
  96. package/dist/chat/ui/composer/use-composer-submit.js +113 -0
  97. package/dist/chat/ui/composer/use-draft-files.d.ts +27 -0
  98. package/dist/chat/ui/composer/use-draft-files.js +111 -0
  99. package/dist/chat/ui/composer/use-notice.d.ts +6 -0
  100. package/dist/chat/ui/composer/use-notice.js +18 -0
  101. package/dist/chat/ui/composer/use-pending-requests.d.ts +2 -0
  102. package/dist/chat/ui/composer/use-pending-requests.js +22 -0
  103. package/dist/chat/ui/composer/use-prompt-history.d.ts +8 -0
  104. package/dist/chat/ui/composer/use-prompt-history.js +57 -0
  105. package/dist/chat/ui/composer/use-prompt-hold.d.ts +6 -0
  106. package/dist/chat/ui/composer/use-prompt-hold.js +48 -0
  107. package/dist/chat/ui/composer/use-take-back.d.ts +13 -0
  108. package/dist/chat/ui/composer/use-take-back.js +82 -0
  109. package/dist/chat/ui/diff/EditorEditDiff.js +4 -3
  110. package/dist/chat/ui/diff/LegacyDiffPool.js +3 -2
  111. package/dist/chat/ui/diff/LegacyEditDiff.js +3 -2
  112. package/dist/chat/ui/diff/LegacyUnifiedDiff.js +3 -2
  113. package/dist/chat/ui/diff/syntax-runtime.d.ts +2 -1
  114. package/dist/chat/ui/diff/syntax-runtime.js +9 -5
  115. package/dist/chat/ui/diff-syntax.d.ts +2 -2
  116. package/dist/chat/ui/diff-syntax.js +1 -2
  117. package/dist/chat/ui/diff-theme.js +10 -9
  118. package/dist/chat/ui/file-links.d.ts +1 -1
  119. package/dist/chat/ui/file-links.js +7 -5
  120. package/dist/chat/ui/highlighter.d.ts +27 -0
  121. package/dist/chat/ui/highlighter.js +151 -0
  122. package/dist/chat/ui/index.d.ts +67 -0
  123. package/dist/chat/ui/index.js +64 -0
  124. package/dist/chat/ui/intelligent-ui/UiReply.js +3 -2
  125. package/dist/chat/ui/intelligent-ui/renderers/content.js +3 -2
  126. package/dist/chat/ui/intelligent-ui/use-ui-links.js +2 -2
  127. package/dist/chat/ui/intelligent-ui/use-ui-queries.js +4 -3
  128. package/dist/chat/ui/markdown-blocks.d.ts +8 -0
  129. package/dist/chat/ui/markdown-blocks.js +260 -12
  130. package/dist/chat/ui/mermaid.d.ts +23 -0
  131. package/dist/chat/ui/mermaid.js +93 -0
  132. package/dist/chat/ui/rows/ForksRow.js +5 -3
  133. package/dist/chat/ui/rows/GeneratedImageRow.js +6 -4
  134. package/dist/chat/ui/rows/MessageRows.d.ts +4 -0
  135. package/dist/chat/ui/rows/MessageRows.js +36 -13
  136. package/dist/chat/ui/rows/Rows.js +3 -1
  137. package/dist/chat/ui/rows/SubagentRow.js +3 -2
  138. package/dist/chat/ui/rows/VisualRow.js +3 -2
  139. package/dist/chat/ui/rows/WorkRows.d.ts +6 -3
  140. package/dist/chat/ui/rows/WorkRows.js +57 -20
  141. package/dist/chat/ui/rows/WorkflowRow.js +3 -2
  142. package/dist/chat/ui/timestamp-format.d.ts +1 -0
  143. package/dist/chat/ui/timestamp-format.js +2 -0
  144. package/dist/chat/ui/use-diff-source-tokens.js +6 -3
  145. package/dist/chat/ui/visual-theme.js +3 -2
  146. package/dist/host.d.ts +9 -6
  147. package/dist/host.js +19 -8
  148. package/dist/index.d.ts +8 -0
  149. package/dist/index.js +9 -0
  150. package/dist/locales/en/agent-chat.d.ts +658 -0
  151. package/dist/locales/en/agent-chat.js +657 -0
  152. package/dist/locales/en/agent-prompts.d.ts +44 -0
  153. package/dist/locales/en/agent-prompts.js +43 -0
  154. package/dist/locales/en/agent-providers.d.ts +132 -0
  155. package/dist/locales/en/agent-providers.js +131 -0
  156. package/dist/locales/en/agent-usage.d.ts +132 -0
  157. package/dist/locales/en/agent-usage.js +131 -0
  158. package/dist/locales/nl/agent-chat.d.ts +658 -0
  159. package/dist/locales/nl/agent-chat.js +657 -0
  160. package/dist/locales/nl/agent-prompts.d.ts +44 -0
  161. package/dist/locales/nl/agent-prompts.js +43 -0
  162. package/dist/locales/nl/agent-providers.d.ts +132 -0
  163. package/dist/locales/nl/agent-providers.js +131 -0
  164. package/dist/locales/nl/agent-usage.d.ts +132 -0
  165. package/dist/locales/nl/agent-usage.js +131 -0
  166. package/dist/locales.js +9 -8
  167. package/dist/prompts/index.d.ts +11 -0
  168. package/dist/prompts/index.js +12 -0
  169. package/dist/prompts/logic/subjects.d.ts +2 -1
  170. package/dist/prompts/logic/subjects.js +6 -4
  171. package/dist/prompts/ui/ApprovalBody.js +6 -4
  172. package/dist/prompts/ui/PromptCard.js +5 -4
  173. package/dist/prompts/ui/PromptView.js +3 -2
  174. package/dist/prompts/ui/QuestionBody.js +3 -2
  175. package/dist/providers/AccountColors.js +2 -2
  176. package/dist/providers/AccountDetail.js +7 -6
  177. package/dist/providers/AccountVariables.js +3 -1
  178. package/dist/providers/AddAccountForm.js +3 -2
  179. package/dist/providers/ProvidersPane.js +4 -3
  180. package/dist/providers/account-actions.d.ts +4 -3
  181. package/dist/providers/account-actions.js +6 -7
  182. package/dist/providers/index.d.ts +16 -0
  183. package/dist/providers/index.js +17 -0
  184. package/dist/providers/provider-abilities.d.ts +2 -1
  185. package/dist/providers/provider-abilities.js +4 -0
  186. package/dist/state/chats.js +4 -2
  187. package/dist/state/index.d.ts +4 -0
  188. package/dist/state/index.js +5 -0
  189. package/dist/testing/index.d.ts +1 -0
  190. package/dist/testing/index.js +2 -0
  191. package/dist/{prompts/logic/prompts.fixtures.d.ts → testing/prompts.d.ts} +1 -1
  192. package/dist/theme.css +13 -3
  193. package/dist/usage/UsageBreakdown.js +3 -2
  194. package/dist/usage/UsageLimits.js +4 -3
  195. package/dist/usage/UsagePage.js +3 -2
  196. package/dist/usage/index.d.ts +15 -0
  197. package/dist/usage/index.js +15 -0
  198. package/package.json +53 -1031
  199. package/src/agents/AccountDot.tsx +3 -1
  200. package/src/agents/accounts.ts +6 -6
  201. package/src/chat/actions.ts +8 -5
  202. package/src/chat/bookmarks.ts +23 -14
  203. package/src/chat/chat-client.ts +8 -0
  204. package/src/chat/composer-send.ts +27 -0
  205. package/src/chat/forks.ts +27 -6
  206. package/src/chat/index.ts +98 -0
  207. package/src/chat/logic/embedded-code.ts +409 -0
  208. package/src/chat/logic/fork.ts +1 -0
  209. package/src/chat/logic/goal.ts +33 -0
  210. package/src/chat/logic/scrubber.ts +6 -5
  211. package/src/chat/logic/thought.ts +40 -0
  212. package/src/chat/logic/thread-cards.ts +2 -0
  213. package/src/chat/logic/timeline.ts +25 -1
  214. package/src/chat/logic/tool-catalog.ts +5 -1
  215. package/src/chat/logic/tools.ts +76 -0
  216. package/src/chat/preferences.ts +26 -3
  217. package/src/chat/reading-width.ts +39 -0
  218. package/src/chat/send-key.ts +38 -0
  219. package/src/chat/ui/BookmarkMarker.tsx +4 -2
  220. package/src/chat/ui/BookmarkSubmenu.tsx +4 -3
  221. package/src/chat/ui/ChatActivity.tsx +14 -10
  222. package/src/chat/ui/ChatReferenceChip.tsx +3 -4
  223. package/src/chat/ui/CodeBlock.tsx +33 -124
  224. package/src/chat/ui/Composer.tsx +162 -1324
  225. package/src/chat/ui/ComposerInput.tsx +5 -4
  226. package/src/chat/ui/EditDiff.tsx +3 -2
  227. package/src/chat/ui/GoalDock.tsx +104 -0
  228. package/src/chat/ui/LimitState.tsx +5 -3
  229. package/src/chat/ui/Markdown.tsx +113 -39
  230. package/src/chat/ui/MermaidBlock.tsx +119 -0
  231. package/src/chat/ui/MessageActions.tsx +13 -8
  232. package/src/chat/ui/Pickers.tsx +8 -7
  233. package/src/chat/ui/PromptComposer.tsx +4 -3
  234. package/src/chat/ui/RowTime.tsx +21 -0
  235. package/src/chat/ui/RunSettings.tsx +9 -4
  236. package/src/chat/ui/ShellCommand.tsx +134 -0
  237. package/src/chat/ui/SubagentInfo.tsx +4 -3
  238. package/src/chat/ui/SubagentStopButton.tsx +4 -3
  239. package/src/chat/ui/SubagentTimeline.tsx +5 -1
  240. package/src/chat/ui/Timeline.tsx +167 -105
  241. package/src/chat/ui/TimelineMenu.tsx +42 -7
  242. package/src/chat/ui/UnifiedDiff.tsx +3 -2
  243. package/src/chat/ui/VisualFrame.tsx +6 -7
  244. package/src/chat/ui/code-theme.ts +4 -4
  245. package/src/chat/ui/composer/ComposerControls.tsx +147 -0
  246. package/src/chat/ui/composer/ComposerMenu.tsx +138 -0
  247. package/src/chat/ui/composer/DraftParts.tsx +86 -0
  248. package/src/chat/ui/composer/QueuedMessages.tsx +64 -0
  249. package/src/chat/ui/composer/key-handler.ts +174 -0
  250. package/src/chat/ui/composer/keys.ts +32 -10
  251. package/src/chat/ui/composer/placeholder.ts +48 -0
  252. package/src/chat/ui/composer/use-chat-controls.ts +134 -0
  253. package/src/chat/ui/composer/use-compaction-offer.ts +25 -0
  254. package/src/chat/ui/composer/use-composer-draft.ts +71 -0
  255. package/src/chat/ui/composer/use-composer-menus.ts +289 -0
  256. package/src/chat/ui/composer/use-composer-submit.ts +163 -0
  257. package/src/chat/ui/composer/use-draft-files.ts +135 -0
  258. package/src/chat/ui/composer/use-notice.ts +28 -0
  259. package/src/chat/ui/composer/use-pending-requests.ts +23 -0
  260. package/src/chat/ui/composer/use-prompt-history.ts +76 -0
  261. package/src/chat/ui/composer/use-prompt-hold.ts +58 -0
  262. package/src/chat/ui/composer/use-take-back.ts +91 -0
  263. package/src/chat/ui/diff/DiffFallbackBoundary.tsx +2 -2
  264. package/src/chat/ui/diff/EditorEditDiff.tsx +4 -3
  265. package/src/chat/ui/diff/LegacyDiffPool.tsx +3 -2
  266. package/src/chat/ui/diff/LegacyEditDiff.tsx +3 -2
  267. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +3 -2
  268. package/src/chat/ui/diff/syntax-runtime.ts +10 -5
  269. package/src/chat/ui/diff-syntax.ts +2 -2
  270. package/src/chat/ui/diff-theme.ts +10 -9
  271. package/src/chat/ui/file-links.ts +7 -5
  272. package/src/chat/ui/highlighter.ts +185 -0
  273. package/src/chat/ui/index.ts +109 -0
  274. package/src/chat/ui/intelligent-ui/UiReply.tsx +5 -4
  275. package/src/chat/ui/intelligent-ui/renderers/content.tsx +3 -2
  276. package/src/chat/ui/intelligent-ui/use-ui-links.ts +2 -2
  277. package/src/chat/ui/intelligent-ui/use-ui-queries.ts +4 -3
  278. package/src/chat/ui/markdown-blocks.ts +300 -12
  279. package/src/chat/ui/mermaid.ts +118 -0
  280. package/src/chat/ui/rows/ForksRow.tsx +5 -3
  281. package/src/chat/ui/rows/GeneratedImageRow.tsx +6 -4
  282. package/src/chat/ui/rows/MessageRows.tsx +73 -18
  283. package/src/chat/ui/rows/Rows.tsx +14 -1
  284. package/src/chat/ui/rows/SubagentRow.tsx +3 -2
  285. package/src/chat/ui/rows/VisualRow.tsx +3 -2
  286. package/src/chat/ui/rows/WorkRows.tsx +148 -47
  287. package/src/chat/ui/rows/WorkflowRow.tsx +3 -2
  288. package/src/chat/ui/timestamp-format.ts +2 -0
  289. package/src/chat/ui/use-diff-source-tokens.ts +10 -4
  290. package/src/chat/ui/visual-theme.ts +3 -2
  291. package/src/host.ts +31 -15
  292. package/src/index.ts +49 -0
  293. package/src/locales/en/agent-chat.ts +657 -0
  294. package/src/locales/en/agent-prompts.ts +43 -0
  295. package/src/locales/en/agent-providers.ts +131 -0
  296. package/src/locales/en/agent-usage.ts +131 -0
  297. package/src/locales/nl/agent-chat.ts +657 -0
  298. package/src/locales/nl/agent-prompts.ts +43 -0
  299. package/src/locales/nl/agent-providers.ts +131 -0
  300. package/src/locales/nl/agent-usage.ts +132 -0
  301. package/src/locales.ts +9 -8
  302. package/src/prompts/index.ts +35 -0
  303. package/src/prompts/logic/subjects.ts +12 -4
  304. package/src/prompts/ui/ApprovalBody.tsx +6 -4
  305. package/src/prompts/ui/PromptCard.tsx +11 -4
  306. package/src/prompts/ui/PromptView.tsx +3 -2
  307. package/src/prompts/ui/QuestionBody.tsx +3 -2
  308. package/src/providers/AccountColors.tsx +2 -2
  309. package/src/providers/AccountDetail.tsx +13 -7
  310. package/src/providers/AccountVariables.tsx +3 -1
  311. package/src/providers/AddAccountForm.tsx +3 -2
  312. package/src/providers/ProvidersPane.tsx +4 -3
  313. package/src/providers/account-actions.ts +7 -7
  314. package/src/providers/index.ts +17 -0
  315. package/src/providers/provider-abilities.ts +6 -1
  316. package/src/state/chats.ts +4 -2
  317. package/src/state/index.ts +39 -0
  318. package/src/testing/index.ts +2 -0
  319. package/src/{prompts/logic/prompts.fixtures.ts → testing/prompts.ts} +1 -1
  320. package/src/theme.css +13 -3
  321. package/src/usage/UsageBreakdown.tsx +3 -2
  322. package/src/usage/UsageLimits.tsx +4 -3
  323. package/src/usage/UsagePage.tsx +3 -2
  324. package/src/usage/index.ts +16 -0
  325. package/dist/locales/en/agent-chat.json +0 -597
  326. package/dist/locales/en/agent-prompts.json +0 -43
  327. package/dist/locales/en/agent-providers.json +0 -131
  328. package/dist/locales/en/agent-usage.json +0 -131
  329. package/dist/locales/nl/agent-chat.json +0 -597
  330. package/dist/locales/nl/agent-prompts.json +0 -43
  331. package/dist/locales/nl/agent-providers.json +0 -131
  332. package/dist/locales/nl/agent-usage.json +0 -131
  333. package/src/locales/en/agent-chat.json +0 -597
  334. package/src/locales/en/agent-prompts.json +0 -43
  335. package/src/locales/en/agent-providers.json +0 -131
  336. package/src/locales/en/agent-usage.json +0 -131
  337. package/src/locales/nl/agent-chat.json +0 -597
  338. package/src/locales/nl/agent-prompts.json +0 -43
  339. package/src/locales/nl/agent-providers.json +0 -131
  340. package/src/locales/nl/agent-usage.json +0 -131
  341. /package/dist/{prompts/logic/prompts.fixtures.js → testing/prompts.js} +0 -0
@@ -14,21 +14,22 @@ import type {
14
14
  import { WAITING_NOTE_PREFIX } from '@adecore/agent-contracts';
15
15
  import { fileBadge, formatBytes, isImageAttachment } from '../../attachments';
16
16
  import { useChatRow, useCurrentItem } from '../../../state/chats';
17
- import { chatHost } from '../../../host';
17
+ import { useChatHost } from '../../../host';
18
18
  import { useChatScope } from '../../../scope';
19
19
  import { splitQuote } from '../../quote';
20
20
  import { jumpToTimelineItem } from '../../timeline-scroll';
21
21
  import { useProviders } from '../../../state/providers';
22
22
  import { ChatReferenceChip } from '../ChatReferenceChip';
23
23
  import { ImageThumb } from '../ImageView';
24
- import { MessageMarkdown, ReplyMarkdown } from '../Markdown';
24
+ import { Markdown, MessageMarkdown, ReplyMarkdown } from '../Markdown';
25
25
  import { FadingWords } from '../FadingWords';
26
26
  import { settledBlocksText } from '../markdown-blocks';
27
27
  import { WHOLE_FADE_CLASS } from '../rehype-fade';
28
28
  import { useRevealedText } from '../reveal';
29
29
  import { formatAgo, formatElapsedShort, formatMoment, formatTokens } from '@adecore/ui/format';
30
- import { Icon, Tooltip, useTickingText } from '@adecore/ui';
31
- import { toolSummary } from '../../logic/tools';
30
+ import { Icon, MiddleTruncate, Tooltip, useTickingText } from '@adecore/ui';
31
+ import { thoughtLine, thoughtPreview } from '../../logic/thought';
32
+ import { toolSummary, toolSummaryIsPath } from '../../logic/tools';
32
33
  import { ROW_GUTTER } from '../icons';
33
34
  import { useOpenForFind } from '../find-reveal';
34
35
  import { ReplyContext, UiReplyNavigationContext } from '../reply-context';
@@ -42,9 +43,10 @@ const USER_FOLD_CHARS = 600;
42
43
  const FOLD = 'max-h-[10em] overflow-hidden [mask-image:linear-gradient(to_bottom,black_70%,transparent)]';
43
44
 
44
45
  function useReplyAuthor(chatId: string) {
46
+ const host = useChatHost();
45
47
  const { id } = useChatScope();
46
48
  const context = useContext(ReplyContext);
47
- const author = chatHost().useReplyAuthor(id, context?.chatId ?? chatId);
49
+ const author = host.useReplyAuthor(id, context?.chatId ?? chatId);
48
50
  return context !== null && context.chatId === undefined ? null : author;
49
51
  }
50
52
 
@@ -53,8 +55,9 @@ function useReplyAuthor(chatId: string) {
53
55
  * hands a link that opens a window to the system browser, which cannot reach a blob of this page.
54
56
  */
55
57
  function AttachmentLink({ chatId, attachment }: { chatId: string; attachment: ChatAttachment }) {
58
+ const host = useChatHost();
56
59
  const { id } = useChatScope();
57
- const { url } = chatHost().attachments.useUrl(id, chatId, attachment.id);
60
+ const { url } = host.attachments.useUrl(id, chatId, attachment.id);
58
61
  const badge = fileBadge(attachment.name);
59
62
  return (
60
63
  <Tooltip label={attachment.name}>
@@ -78,8 +81,9 @@ function AttachmentLink({ chatId, attachment }: { chatId: string; attachment: Ch
78
81
 
79
82
  /* A picture attached to a message, which opens large. */
80
83
  function AttachmentThumb({ chatId, attachment }: { chatId: string; attachment: ChatAttachment }) {
84
+ const host = useChatHost();
81
85
  const { id } = useChatScope();
82
- const source = chatHost().attachments.useUrl(id, chatId, attachment.id);
86
+ const source = host.attachments.useUrl(id, chatId, attachment.id);
83
87
  return <ImageThumb source={source} alt={attachment.name} className="max-h-32 max-w-48 object-cover" />;
84
88
  }
85
89
 
@@ -264,6 +268,7 @@ function Writing() {
264
268
  * written fades anything: an old reply scrolled back into view just stands there.
265
269
  */
266
270
  export function AssistantRow({ chatId, item: derived }: { chatId: string; item: ChatAssistantItem }) {
271
+ const host = useChatHost();
267
272
  const item = useCurrentItem(chatId, derived);
268
273
  const scope = useChatScope();
269
274
  const context = useContext(ReplyContext);
@@ -271,7 +276,7 @@ export function AssistantRow({ chatId, item: derived }: { chatId: string; item:
271
276
  () => (context === null && !item.parentToolUseId ? { scopeId: scope.id, chatId, itemId: item.id, streaming: item.streaming } : undefined),
272
277
  [context, item.parentToolUseId, scope.id, chatId, item.id, item.streaming]
273
278
  );
274
- const mode = chatHost().useStreaming();
279
+ const mode = host.useStreaming();
275
280
  const [sawWriting] = useState(item.streaming);
276
281
  const live = mode === 'words' && item.streaming;
277
282
  const reveal = useRevealedText(item.text, live);
@@ -329,12 +334,14 @@ export function AssistantRow({ chatId, item: derived }: { chatId: string; item:
329
334
 
330
335
  /*
331
336
  * What the model thought before it answered. It shimmers while it streams and folds itself away
332
- * once the answer starts, because the thought is worth a glance and rarely worth reading twice.
337
+ * once the answer starts, because the thought is worth a glance and rarely worth reading twice:
338
+ * folded, it shows the heading it opens with or itself on one line, and opened, it reads as Markdown.
333
339
  */
334
340
  export function ThinkingRow({ chatId, item: derived }: { chatId: string; item: ChatThinkingItem }) {
341
+ const host = useChatHost();
335
342
  const { t } = useTranslation('agent-chat');
336
343
  const item = useCurrentItem(chatId, derived);
337
- const mode = chatHost().useStreaming();
344
+ const mode = host.useStreaming();
338
345
  const [open, setOpen] = useState(false);
339
346
  const reveal = useRevealedText(item.text, mode === 'words' && item.streaming);
340
347
  // A block at a time, the paragraphs of the thought that closed are shown as they land.
@@ -342,31 +349,72 @@ export function ThinkingRow({ chatId, item: derived }: { chatId: string; item: C
342
349
  // Whole, the thought stays behind "Thinking…" until it is done and then folds like any other.
343
350
  const shown = open || reveal.active || (settled !== null && settled !== '');
344
351
  const text = settled ?? reveal.text;
352
+ const live = useMemo(() => (item.streaming && mode !== 'whole' ? thoughtLine(text) : ''), [item.streaming, mode, text]);
353
+ const preview = useMemo(() => (item.streaming ? '' : thoughtPreview(item.text)), [item.streaming, item.text]);
354
+ const duration = formatElapsedShort((item.endedAt ?? item.createdAt) - item.createdAt);
345
355
  useOpenForFind(item.id, 'text', setOpen);
346
356
  return (
347
357
  <div data-find-item={item.id} className="-mx-1 px-1 pb-2">
348
- <button className="flex items-center gap-2 text-xs text-text-muted hover:text-text" disabled={item.streaming} onClick={() => setOpen((o) => !o)}>
358
+ <button
359
+ className="flex w-full min-w-0 items-center gap-2 text-left text-xs text-text-muted hover:text-text"
360
+ disabled={item.streaming}
361
+ aria-expanded={item.streaming ? undefined : open}
362
+ onClick={() => setOpen((o) => !o)}
363
+ >
349
364
  <span className={ROW_GUTTER}>
350
365
  <Icon icon={Brain} size={12} />
351
366
  </span>
352
367
  {item.streaming ? (
353
- <span className="chat-live-text">{t('rows.thinking.live')}</span>
368
+ <span className="chat-live-text min-w-0 truncate">{live || t('rows.thinking.live')}</span>
369
+ ) : preview === '' ? (
370
+ <>
371
+ <span>{t('rows.thinking.thoughtFor', { duration })}</span>
372
+ <Icon icon={ChevronDown} size={12} className={clsx('shrink-0 transition-transform', open && 'rotate-180')} />
373
+ </>
354
374
  ) : (
355
375
  <>
356
- <span>{t('rows.thinking.thoughtFor', { duration: formatElapsedShort((item.endedAt ?? item.createdAt) - item.createdAt) })}</span>
357
- <Icon icon={ChevronDown} size={12} className={clsx('transition-transform', open && 'rotate-180')} />
376
+ <span className="min-w-0 truncate">{open ? t('rows.thinking.thought') : preview}</span>
377
+ <span className="grow" />
378
+ <span className="shrink-0 text-text-faint tabular-nums">{duration}</span>
379
+ <Icon icon={ChevronDown} size={12} className={clsx('shrink-0 transition-transform', open && 'rotate-180')} />
358
380
  </>
359
381
  )}
360
382
  </button>
361
383
  {shown && text !== '' && (
362
- <div data-find-field="text" className="mt-1 border-l border-border pl-2.5 text-sm whitespace-pre-wrap text-text-faint select-text">
363
- {reveal.active ? <FadingWords text={reveal.text} /> : text}
384
+ <div data-find-field="text" className="mt-1 border-l border-border pl-2.5 select-text">
385
+ {reveal.active ? (
386
+ <div className="text-sm whitespace-pre-wrap text-text-faint">
387
+ <FadingWords text={reveal.text} />
388
+ </div>
389
+ ) : (
390
+ <Markdown text={text} muted />
391
+ )}
364
392
  </div>
365
393
  )}
366
394
  </div>
367
395
  );
368
396
  }
369
397
 
398
+ /*
399
+ * The latest thought of a running turn over the call it led to, as its heading or first sentence,
400
+ * so what the model found stays in view while it checks it. Nothing while the thought has no line yet.
401
+ */
402
+ export function LiveThoughtRow({ chatId, item: derived }: { chatId: string; item: ChatThinkingItem }) {
403
+ const item = useCurrentItem(chatId, derived);
404
+ const line = useMemo(() => thoughtLine(item.text), [item.text]);
405
+ if (line === '') {
406
+ return null;
407
+ }
408
+ return (
409
+ <div className="-mx-1 mb-0.5 flex min-h-7 items-start gap-2 px-1 py-1 text-xs text-text-muted">
410
+ <span className={ROW_GUTTER}>
411
+ <Icon icon={Brain} size={12} />
412
+ </span>
413
+ <span className="line-clamp-4 min-w-0 wrap-anywhere select-text">{line}</span>
414
+ </div>
415
+ );
416
+ }
417
+
370
418
  const NOTE_ICON = {
371
419
  info: <Icon icon={Info} size={12} />,
372
420
  warning: <Icon icon={TriangleAlert} size={12} />,
@@ -417,8 +465,9 @@ export function NoteRow({ id, level, text, from }: { id: string; level: 'info' |
417
465
 
418
466
  /* "Open fork" on a note a fork sent, or "Open to answer" on one about a waiting child, while that chat is still in the project. */
419
467
  function OpenChatButton({ chatId, label }: { chatId: string; label: 'openFork' | 'openWaiting' }) {
468
+ const host = useChatHost();
420
469
  const { t } = useTranslation('agent-chat');
421
- const place = chatHost().useChatPlace(chatId);
470
+ const place = host.useChatPlace(chatId);
422
471
  if (place.title === null) {
423
472
  return null;
424
473
  }
@@ -486,7 +535,13 @@ export function ApprovalHistoryRow({ item }: { item: ChatApprovalItem }) {
486
535
  <span className={clsx(ROW_GUTTER, item.decision === 'deny' && 'text-status-error')}>{DECISION_ICON[item.decision]}</span>
487
536
  <span className={clsx(item.decision === 'deny' && 'text-status-error')}>{t(`rows.approval.${item.decision}`)}</span>
488
537
  <span className="text-text-muted">{item.toolName}</span>
489
- <span className="min-w-0 truncate font-mono">{toolSummary(item.toolName, item.input)}</span>
538
+ <span className="min-w-0 truncate font-mono">
539
+ {toolSummaryIsPath(item.toolName, item.input) ? (
540
+ <MiddleTruncate text={toolSummary(item.toolName, item.input)} />
541
+ ) : (
542
+ toolSummary(item.toolName, item.input)
543
+ )}
544
+ </span>
490
545
  </div>
491
546
  );
492
547
  }
@@ -3,7 +3,18 @@ import { ErrorBoundary } from '@adecore/ui';
3
3
  import type { ThreadCard } from '../../../host';
4
4
  import type { TimelineRow } from '../../logic/timeline';
5
5
  import type { SubagentStep } from '../../subagent-view';
6
- import { AgentTurnRow, ApprovalHistoryRow, AssistantRow, CompactionRow, NoteRow, QuestionHistoryRow, ReportRow, ThinkingRow, UserRow } from './MessageRows';
6
+ import {
7
+ AgentTurnRow,
8
+ ApprovalHistoryRow,
9
+ AssistantRow,
10
+ CompactionRow,
11
+ LiveThoughtRow,
12
+ NoteRow,
13
+ QuestionHistoryRow,
14
+ ReportRow,
15
+ ThinkingRow,
16
+ UserRow
17
+ } from './MessageRows';
7
18
  import { isImageGeneration } from '../../logic/generated-image';
8
19
  import { ForksRow } from './ForksRow';
9
20
  import { GeneratedImageRow } from './GeneratedImageRow';
@@ -52,6 +63,8 @@ export function Row({ row, chatId, toggleGroup, toggleTurn, toggleSubagent, open
52
63
  return <ReportRow id={row.id} text={row.text} />;
53
64
  case 'thinking':
54
65
  return <ThinkingRow chatId={chatId} item={row.item} />;
66
+ case 'live-thought':
67
+ return <LiveThoughtRow chatId={chatId} item={row.item} />;
55
68
  case 'work':
56
69
  return isImageGeneration(row.tool) ? <GeneratedImageRow chatId={chatId} tool={row.tool} /> : <WorkRow tool={row.tool} />;
57
70
  case 'work-live':
@@ -15,7 +15,7 @@ import { RunningFor, ToggleLine, WorkLiveRow, WorkRow } from './WorkRows';
15
15
  import { entryTimeOf, statusWordOf, taskIdOf } from '../../subagent-list';
16
16
  import { useSubagentSupport } from '../../subagent-support';
17
17
  import { canOpenSubagent, crumbOf, type SubagentStep } from '../../subagent-view';
18
- import { chatHost, type SubagentTask } from '../../../host';
18
+ import { useChatHost, type SubagentTask } from '../../../host';
19
19
  import { useChatScope } from '../../../scope';
20
20
  import { useCurrentItem } from '../../../state/chats';
21
21
 
@@ -135,10 +135,11 @@ export function SubagentRow({
135
135
  /* Opens everything it did in the thread's place, for a surface that has somewhere to open it. */
136
136
  onOpenConversation?(): void;
137
137
  }) {
138
+ const host = useChatHost();
138
139
  const { t } = useTranslation('agent-chat');
139
140
  const { id } = useChatScope();
140
141
  const taskId = taskIdOf(item);
141
- const task = chatHost().tasks.useTask(id, taskId);
142
+ const task = host.tasks.useTask(id, taskId);
142
143
  // A task paused on its child's limit is still a running row on the wire, but nothing is at work.
143
144
  const running = item.status === 'running' && statusWordOf(item, task) !== 'paused';
144
145
  const detail = item.description || item.summary || item.subagentType || '';
@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next';
2
2
  import { Ellipsis, Maximize2, Trash2 } from 'lucide-react';
3
3
  import type { ChatVisual } from '@adecore/agent-contracts';
4
4
  import { ButtonGroup, ErrorBoundary, Icon, IconButton, Menu, Surface } from '@adecore/ui';
5
- import { chatHost } from '../../../host';
5
+ import { useChatHost } from '../../../host';
6
6
  import { useChatScope } from '../../../scope';
7
7
  import { useVisualDialog } from '../../visuals';
8
8
  import { VisualFrame } from '../VisualFrame';
@@ -14,9 +14,10 @@ import { VisualFrame } from '../VisualFrame';
14
14
  * sit here.
15
15
  */
16
16
  export function VisualRow({ chatId, visual }: { chatId: string; visual: ChatVisual }) {
17
+ const host = useChatHost();
17
18
  const { t } = useTranslation('agent-chat');
18
19
  const { keyOf } = useChatScope();
19
- if (chatHost().visuals === null) {
20
+ if (host.visuals === null) {
20
21
  return null;
21
22
  }
22
23
  const open = (kind: 'expand' | 'remove'): void => useVisualDialog.getState().open(keyOf(chatId), visual.id, kind);
@@ -1,22 +1,38 @@
1
- import { Suspense, useEffect, useState, type Dispatch, type ReactNode, type SetStateAction } from 'react';
1
+ import { Fragment, Suspense, useEffect, useState, type Dispatch, type ReactNode, type SetStateAction } from 'react';
2
2
  import clsx from 'clsx';
3
3
  import i18next from 'i18next';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { ChevronRight, FileDiff, X } from 'lucide-react';
6
6
  import type { ChatCheckpointDiff, ChatCheckpointFile, ChatFileChange, ChatToolItem, ChatTurnItem } from '@adecore/agent-contracts';
7
7
  import type { BaseMoved } from '../../logic/timeline';
8
- import { fileChanges, liveOutput, readImagePath, toolStartedAt, toolSummary, unifiedChanges, type FileChange } from '../../logic/tools';
8
+ import {
9
+ exitCodeOf,
10
+ fileChanges,
11
+ hasOutput,
12
+ hasToolBody,
13
+ liveOutput,
14
+ readImagePath,
15
+ shellCommand,
16
+ toolCallLines,
17
+ toolStartedAt,
18
+ toolSummary,
19
+ toolSummaryIsPath,
20
+ unifiedChanges,
21
+ type FileChange
22
+ } from '../../logic/tools';
9
23
  import { useOpenForFind } from '../find-reveal';
10
- import { chatHost } from '../../../host';
24
+ import { useChatHost } from '../../../host';
11
25
  import { lazyNamed } from '../../../lazy';
12
26
  import { useChatActions } from '../../actions';
13
27
  import { useChatScope } from '../../../scope';
14
28
  import { useCurrentItem } from '../../../state/chats';
15
29
  import { useTimelineFlash } from '../../timeline-flash';
16
30
  import { formatClockDuration, formatElapsedShort } from '@adecore/ui/format';
17
- import { Icon, Spinner, useTickingText } from '@adecore/ui';
31
+ import { Icon, MiddleTruncate, Spinner, useTickingText } from '@adecore/ui';
18
32
  import { ROW_GUTTER, toolIcon } from '../icons';
19
33
  import { AnsiOutput } from '../AnsiOutput';
34
+ import { RowTime } from '../RowTime';
35
+ import { ShellCommand } from '../ShellCommand';
20
36
  import { DiffStack, ReviewStackView } from '../diff/DiffStack';
21
37
  import { reviewEntries, type ReviewEntry } from '../diff/review-plan';
22
38
  import { useReviewStack } from '../diff/use-review-stack';
@@ -26,54 +42,79 @@ const UnifiedDiff = lazyNamed(() => import('../UnifiedDiff'), 'default');
26
42
 
27
43
  const OUTPUT_LIMIT = 4000;
28
44
 
45
+ /* The line that says how much of a text past the limit is left out; empty for a text within it. */
46
+ function clipNotice(text: string): string {
47
+ return text.length > OUTPUT_LIMIT ? `\n${i18next.t('agent-chat:work.moreCharacters', { count: text.length - OUTPUT_LIMIT })}` : '';
48
+ }
49
+
29
50
  function clip(text: string): string {
30
- return text.length > OUTPUT_LIMIT
31
- ? `${text.slice(0, OUTPUT_LIMIT)}\n${i18next.t('agent-chat:work.moreCharacters', { count: text.length - OUTPUT_LIMIT })}`
32
- : text;
51
+ return text.slice(0, OUTPUT_LIMIT) + clipNotice(text);
33
52
  }
34
53
 
54
+ /*
55
+ * The line of a call, a subagent or a group. Without `onToggle` it has nothing to open: no chevron, and
56
+ * not a button. `time` shows at the right edge while the line is hovered or focused.
57
+ */
35
58
  export function ToggleLine({
36
59
  icon,
37
60
  label,
38
61
  detail,
39
- open,
62
+ command,
63
+ path,
64
+ open = false,
40
65
  onToggle,
41
66
  failed,
42
67
  live,
43
68
  trailing,
69
+ time,
44
70
  className
45
71
  }: {
46
72
  icon: ReactNode;
47
73
  label: string;
48
74
  detail?: string;
49
- open: boolean;
50
- onToggle(): void;
75
+ /* Draws `detail` as a shell command, in the colors of the code theme. */
76
+ command?: boolean;
77
+ /* `detail` is the path of a file, cut in the middle so its name stays in sight. */
78
+ path?: boolean;
79
+ open?: boolean;
80
+ onToggle?(): void;
51
81
  failed?: boolean;
52
82
  live?: boolean;
53
83
  trailing?: ReactNode;
84
+ time?: number;
54
85
  className?: string;
55
86
  }) {
56
- return (
57
- <button
58
- className={clsx(
59
- // Fix the width so long commands reach the truncation inside the negative margins.
60
- '-mx-1 mb-0.5 flex h-7 w-[calc(100%+8px)] items-center gap-2 rounded-md px-1 text-left text-xs text-text-muted hover:bg-surface-hover',
61
- failed && 'text-status-error',
62
- className
63
- )}
64
- onClick={onToggle}
65
- >
87
+ const classes = clsx(
88
+ // Fix the width so long commands reach the truncation inside the negative margins.
89
+ 'group/line -mx-1 mb-0.5 flex h-7 w-[calc(100%+8px)] items-center gap-2 rounded-md px-1 text-left text-xs text-text-muted',
90
+ onToggle !== undefined && 'hover:bg-surface-hover',
91
+ failed && 'text-status-error',
92
+ className
93
+ );
94
+ const content = (
95
+ <>
66
96
  <span className={clsx(ROW_GUTTER, live && 'text-accent')}>{icon}</span>
67
97
  <span className={clsx('shrink-0', live && 'chat-live-text')}>{label}</span>
68
98
  {detail && (
69
99
  <span data-find-field="summary" className="min-w-0 truncate font-mono text-text-faint">
70
- {detail}
100
+ {command ? <ShellCommand command={detail} /> : path ? <MiddleTruncate text={detail} /> : detail}
71
101
  </span>
72
102
  )}
73
103
  <span className="grow" />
74
104
  {trailing}
105
+ {time !== undefined && <RowTime at={time} />}
75
106
  {failed && <Icon icon={X} size={12} className="shrink-0" />}
76
- <Icon icon={ChevronRight} size={12} className={clsx('shrink-0 text-text-faint transition-transform', open && 'rotate-90')} />
107
+ {onToggle !== undefined && (
108
+ <Icon icon={ChevronRight} size={12} className={clsx('shrink-0 text-text-faint transition-transform', open && 'rotate-90')} />
109
+ )}
110
+ </>
111
+ );
112
+ if (onToggle === undefined) {
113
+ return <div className={classes}>{content}</div>;
114
+ }
115
+ return (
116
+ <button className={classes} onClick={onToggle}>
117
+ {content}
77
118
  </button>
78
119
  );
79
120
  }
@@ -106,7 +147,7 @@ function ChangedFileLine({
106
147
  onClick={onToggle}
107
148
  >
108
149
  <Icon icon={ChevronRight} size={12} className={clsx('shrink-0 text-text-faint transition-transform', open && 'rotate-90')} />
109
- <span className="min-w-0 truncate font-mono text-text">{path}</span>
150
+ <MiddleTruncate text={path} className="font-mono text-text" />
110
151
  <span className="grow" />
111
152
  {trailing}
112
153
  </button>
@@ -115,19 +156,12 @@ function ChangedFileLine({
115
156
  );
116
157
  }
117
158
 
118
- function ToolBody({ tool }: { tool: ChatToolItem }) {
119
- const patches = unifiedChanges(tool);
120
- const changes = patches.length > 0 ? [] : fileChanges(tool.name, tool.input);
159
+ /* A diff keeps its card, with what the call answered under it. */
160
+ function DiffBody({ tool, entries, patched }: { tool: ChatToolItem; entries: readonly ReviewEntry[]; patched: boolean }) {
121
161
  return (
122
162
  <div className="mt-1.5 mb-2 ml-6 overflow-hidden rounded-md border border-border bg-surface-raised">
123
- {patches.length + changes.length > 0 ? (
124
- <DiffStack entries={reviewEntries(patches, changes)} />
125
- ) : (
126
- <pre className="max-h-64 overflow-auto px-3 py-2 font-mono text-code text-text-muted select-text">
127
- {clip(JSON.stringify(tool.input, null, 2) ?? '')}
128
- </pre>
129
- )}
130
- {tool.output !== null && tool.output !== '' && patches.length === 0 && (
163
+ <DiffStack entries={entries} />
164
+ {hasOutput(tool.output) && !patched && (
131
165
  <pre
132
166
  data-find-field="output"
133
167
  className={clsx(
@@ -142,9 +176,64 @@ function ToolBody({ tool }: { tool: ChatToolItem }) {
142
176
  );
143
177
  }
144
178
 
179
+ /* What a call answered, muted under the call; a failure in the error color, or its exit code when the tool names one. */
180
+ function ToolOutput({ tool }: { tool: ChatToolItem }) {
181
+ const { t } = useTranslation('agent-chat');
182
+ if (!hasOutput(tool.output)) {
183
+ return null;
184
+ }
185
+ const failed = tool.state === 'error';
186
+ const { code, rest } = failed ? exitCodeOf(tool.output) : { code: null, rest: tool.output };
187
+ return (
188
+ <>
189
+ {hasOutput(rest) && (
190
+ <pre
191
+ data-find-field="output"
192
+ className={clsx('max-h-64 overflow-auto whitespace-pre-wrap', failed && code === null ? 'text-status-error' : 'text-text-muted')}
193
+ >
194
+ <AnsiOutput text={rest} limit={OUTPUT_LIMIT} />
195
+ </pre>
196
+ )}
197
+ {code !== null && <div className="text-status-error">{t('work.exitCode', { code: String(code) })}</div>}
198
+ </>
199
+ );
200
+ }
201
+
202
+ /* An opened call without a card: what it was asked in the foreground, what it answered muted below. */
203
+ function ToolBody({ tool }: { tool: ChatToolItem }) {
204
+ const patches = unifiedChanges(tool);
205
+ const changes = patches.length > 0 ? [] : fileChanges(tool.name, tool.input);
206
+ if (patches.length + changes.length > 0) {
207
+ return <DiffBody tool={tool} entries={reviewEntries(patches, changes)} patched={patches.length > 0} />;
208
+ }
209
+ const lines = toolCallLines(tool.name, tool.input);
210
+ return (
211
+ <div className="mt-0.5 mb-2 ml-6 flex flex-col gap-1.5 font-mono text-code select-text">
212
+ {lines.command !== null && (
213
+ <pre className="max-h-64 overflow-auto whitespace-pre-wrap wrap-anywhere text-text">
214
+ <ShellCommand command={lines.command.slice(0, OUTPUT_LIMIT)} />
215
+ {clipNotice(lines.command)}
216
+ </pre>
217
+ )}
218
+ {lines.args.length > 0 && (
219
+ <dl className="grid max-h-64 grid-cols-[auto_minmax(0,1fr)] gap-x-3 overflow-auto">
220
+ {lines.args.map(([key, value]) => (
221
+ <Fragment key={key}>
222
+ <dt className="text-text-faint">{key}</dt>
223
+ <dd className="whitespace-pre-wrap wrap-anywhere text-text">{clip(value)}</dd>
224
+ </Fragment>
225
+ ))}
226
+ </dl>
227
+ )}
228
+ {lines.text !== null && <pre className="max-h-64 overflow-auto whitespace-pre-wrap wrap-anywhere text-text">{clip(lines.text)}</pre>}
229
+ <ToolOutput tool={tool} />
230
+ </div>
231
+ );
232
+ }
233
+
145
234
  /*
146
- * One settled tool call: a line, and its input and output behind it. An image the call looked at is
147
- * drawn under the line. `icon`, `label` and `failed` let a row that knows the call better name it.
235
+ * One settled tool call: a line, and what it was asked and answered behind it. An image the call
236
+ * looked at is drawn under the line. `icon`, `label` and `failed` let a row that knows the call better name it.
148
237
  */
149
238
  export function WorkRow({
150
239
  tool,
@@ -161,22 +250,28 @@ export function WorkRow({
161
250
  label?: string;
162
251
  failed?: boolean;
163
252
  }) {
253
+ const host = useChatHost();
164
254
  const [open, setOpen] = useState(false);
165
255
  const image = tool.state === 'done' ? readImagePath(tool.name, tool.input) : null;
166
- const { ReadImage } = chatHost();
256
+ const { ReadImage } = host;
167
257
  useOpenForFind(tool.id, 'output', setOpen);
258
+ const summary = detail ?? toolSummary(tool.name, tool.input);
259
+ const opens = hasToolBody(tool);
168
260
  return (
169
261
  <div data-find-item={tool.id} className={nested ? 'ml-6' : undefined}>
170
262
  <ToggleLine
171
263
  icon={icon ?? toolIcon(tool.name)}
172
264
  label={label ?? tool.name}
173
- detail={detail ?? toolSummary(tool.name, tool.input)}
174
- open={open}
175
- onToggle={() => setOpen((o) => !o)}
265
+ detail={summary}
266
+ command={detail === undefined && summary === shellCommand(tool.name, tool.input)}
267
+ path={detail === undefined && toolSummaryIsPath(tool.name, tool.input)}
268
+ open={open && opens}
269
+ onToggle={opens ? () => setOpen((o) => !o) : undefined}
176
270
  failed={failed ?? tool.state === 'error'}
271
+ time={tool.createdAt}
177
272
  />
178
273
  {image !== null && ReadImage !== null && <ReadImage path={image} />}
179
- {open && <ToolBody tool={tool} />}
274
+ {open && opens && <ToolBody tool={tool} />}
180
275
  </div>
181
276
  );
182
277
  }
@@ -193,18 +288,22 @@ export function WorkLiveRow({ chatId, tool: derived }: { chatId: string; tool: C
193
288
  const [open, setOpen] = useState(false);
194
289
  const tool = useCurrentItem(chatId, derived);
195
290
  const tail = liveOutput(tool);
291
+ const summary = toolSummary(tool.name, tool.input);
292
+ const opens = hasToolBody(tool);
196
293
  return (
197
294
  <div>
198
295
  <ToggleLine
199
296
  icon={toolIcon(tool.name)}
200
297
  label={tool.name}
201
- detail={toolSummary(tool.name, tool.input) || tool.progress?.description || ''}
202
- open={open}
203
- onToggle={() => setOpen((o) => !o)}
298
+ detail={summary || tool.progress?.description || ''}
299
+ command={summary !== '' && summary === shellCommand(tool.name, tool.input)}
300
+ path={summary !== '' && toolSummaryIsPath(tool.name, tool.input)}
301
+ open={open && opens}
302
+ onToggle={opens ? () => setOpen((o) => !o) : undefined}
204
303
  trailing={<RunningFor startedAt={toolStartedAt(tool)} />}
205
304
  live
206
305
  />
207
- {open && <ToolBody tool={tool} />}
306
+ {open && opens && <ToolBody tool={tool} />}
208
307
  {tail !== null && !open && (
209
308
  <pre className="mt-1.5 mb-2 ml-6 max-h-48 overflow-auto rounded-md border border-border bg-surface-raised px-3 py-2 font-mono text-code whitespace-pre-wrap text-chat-output select-text">
210
309
  <AnsiOutput text={tail} tailLines={12} />
@@ -219,7 +318,7 @@ export function WorkGroupRow({ tools, summary, expanded, onToggle }: { tools: Ch
219
318
  const failed = tools.some((tool) => tool.state === 'error');
220
319
  return (
221
320
  <div>
222
- <ToggleLine icon={toolIcon(tools[0]!.name)} label={summary} open={expanded} onToggle={onToggle} failed={failed} />
321
+ <ToggleLine icon={toolIcon(tools[0]!.name)} label={summary} open={expanded} onToggle={onToggle} failed={failed} time={tools[0]!.createdAt} />
223
322
  </div>
224
323
  );
225
324
  }
@@ -244,7 +343,7 @@ export function TurnFoldRow({
244
343
  return (
245
344
  <button
246
345
  className={clsx(
247
- 'group -mx-1 mb-2 flex h-7 w-[calc(100%+8px)] min-w-0 items-center gap-2 rounded-md px-1 text-left text-xs text-text-muted hover:bg-surface-hover',
346
+ 'group group/line -mx-1 mb-2 flex h-7 w-[calc(100%+8px)] min-w-0 items-center gap-2 rounded-md px-1 text-left text-xs text-text-muted hover:bg-surface-hover',
248
347
  turn.state === 'error' && 'text-status-error'
249
348
  )}
250
349
  aria-expanded={expanded}
@@ -255,6 +354,8 @@ export function TurnFoldRow({
255
354
  </span>
256
355
  <span className="shrink-0 group-hover:text-text">{label}</span>
257
356
  {work.length > 0 && <span className="min-w-0 truncate text-text-faint">{work.map((part) => ` · ${part}`).join('')}</span>}
357
+ <span className="grow" />
358
+ <RowTime at={turn.createdAt} />
258
359
  </button>
259
360
  );
260
361
  }
@@ -9,7 +9,7 @@ import { RunningFor, ToggleLine, WorkLiveRow, WorkRow } from './WorkRows';
9
9
  import { useChatScope } from '../../../scope';
10
10
  import { Icon, IconButton } from '@adecore/ui';
11
11
  import { useChatActions } from '../../actions';
12
- import { chatHost } from '../../../host';
12
+ import { useChatHost } from '../../../host';
13
13
 
14
14
  function AgentLine({ view, onOpen }: { view: WorkflowAgentView; onOpen?(step: SubagentStep): void }) {
15
15
  const { agent, state } = view;
@@ -75,6 +75,7 @@ export function WorkflowRow({
75
75
  chatId?: string;
76
76
  onOpenAgent?(step: SubagentStep): void;
77
77
  }) {
78
+ const host = useChatHost();
78
79
  const { t } = useTranslation('agent-chat');
79
80
  const { id } = useChatScope();
80
81
  // A host that does not answer `chat.subagent` has no conversation to open for anyone.
@@ -86,7 +87,7 @@ export function WorkflowRow({
86
87
  const stop = (): void => {
87
88
  if (chatId && workflow.taskId) {
88
89
  void actions.stopTask(chatId, workflow.taskId).catch((error: unknown) => {
89
- chatHost().notify({
90
+ host.notify({
90
91
  kind: 'error',
91
92
  title: t('activity.stopFailed'),
92
93
  description: error instanceof Error ? error.message : t('subagents.noAnswer')
@@ -0,0 +1,2 @@
1
+ /* The full date and time a row's time names on its tooltip. */
2
+ export const TIMESTAMP_FORMAT: Intl.DateTimeFormatOptions = { dateStyle: 'full', timeStyle: 'medium' };