@adecore/agents-react 0.23.1 → 0.24.1

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 (344) 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 +130 -907
  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/surface.d.ts +1 -0
  88. package/dist/chat/ui/composer/surface.js +44 -0
  89. package/dist/chat/ui/composer/use-chat-controls.d.ts +28 -0
  90. package/dist/chat/ui/composer/use-chat-controls.js +112 -0
  91. package/dist/chat/ui/composer/use-compaction-offer.d.ts +2 -0
  92. package/dist/chat/ui/composer/use-compaction-offer.js +23 -0
  93. package/dist/chat/ui/composer/use-composer-draft.d.ts +14 -0
  94. package/dist/chat/ui/composer/use-composer-draft.js +39 -0
  95. package/dist/chat/ui/composer/use-composer-menus.d.ts +52 -0
  96. package/dist/chat/ui/composer/use-composer-menus.js +232 -0
  97. package/dist/chat/ui/composer/use-composer-submit.d.ts +32 -0
  98. package/dist/chat/ui/composer/use-composer-submit.js +113 -0
  99. package/dist/chat/ui/composer/use-draft-files.d.ts +27 -0
  100. package/dist/chat/ui/composer/use-draft-files.js +111 -0
  101. package/dist/chat/ui/composer/use-notice.d.ts +6 -0
  102. package/dist/chat/ui/composer/use-notice.js +18 -0
  103. package/dist/chat/ui/composer/use-pending-requests.d.ts +2 -0
  104. package/dist/chat/ui/composer/use-pending-requests.js +22 -0
  105. package/dist/chat/ui/composer/use-prompt-history.d.ts +8 -0
  106. package/dist/chat/ui/composer/use-prompt-history.js +57 -0
  107. package/dist/chat/ui/composer/use-prompt-hold.d.ts +6 -0
  108. package/dist/chat/ui/composer/use-prompt-hold.js +48 -0
  109. package/dist/chat/ui/composer/use-take-back.d.ts +13 -0
  110. package/dist/chat/ui/composer/use-take-back.js +82 -0
  111. package/dist/chat/ui/diff/EditorEditDiff.js +4 -3
  112. package/dist/chat/ui/diff/LegacyDiffPool.js +3 -2
  113. package/dist/chat/ui/diff/LegacyEditDiff.js +3 -2
  114. package/dist/chat/ui/diff/LegacyUnifiedDiff.js +3 -2
  115. package/dist/chat/ui/diff/syntax-runtime.d.ts +2 -1
  116. package/dist/chat/ui/diff/syntax-runtime.js +9 -5
  117. package/dist/chat/ui/diff-syntax.d.ts +2 -2
  118. package/dist/chat/ui/diff-syntax.js +1 -2
  119. package/dist/chat/ui/diff-theme.js +10 -9
  120. package/dist/chat/ui/file-links.d.ts +1 -1
  121. package/dist/chat/ui/file-links.js +7 -5
  122. package/dist/chat/ui/highlighter.d.ts +27 -0
  123. package/dist/chat/ui/highlighter.js +151 -0
  124. package/dist/chat/ui/index.d.ts +67 -0
  125. package/dist/chat/ui/index.js +64 -0
  126. package/dist/chat/ui/intelligent-ui/UiReply.js +3 -2
  127. package/dist/chat/ui/intelligent-ui/renderers/content.js +3 -2
  128. package/dist/chat/ui/intelligent-ui/use-ui-links.js +2 -2
  129. package/dist/chat/ui/intelligent-ui/use-ui-queries.js +4 -3
  130. package/dist/chat/ui/markdown-blocks.d.ts +8 -0
  131. package/dist/chat/ui/markdown-blocks.js +260 -12
  132. package/dist/chat/ui/mermaid.d.ts +23 -0
  133. package/dist/chat/ui/mermaid.js +93 -0
  134. package/dist/chat/ui/rows/ForksRow.js +5 -3
  135. package/dist/chat/ui/rows/GeneratedImageRow.js +6 -4
  136. package/dist/chat/ui/rows/MessageRows.d.ts +4 -0
  137. package/dist/chat/ui/rows/MessageRows.js +36 -13
  138. package/dist/chat/ui/rows/Rows.js +3 -1
  139. package/dist/chat/ui/rows/SubagentRow.js +3 -2
  140. package/dist/chat/ui/rows/VisualRow.js +3 -2
  141. package/dist/chat/ui/rows/WorkRows.d.ts +6 -3
  142. package/dist/chat/ui/rows/WorkRows.js +57 -20
  143. package/dist/chat/ui/rows/WorkflowRow.js +3 -2
  144. package/dist/chat/ui/timestamp-format.d.ts +1 -0
  145. package/dist/chat/ui/timestamp-format.js +2 -0
  146. package/dist/chat/ui/use-diff-source-tokens.js +6 -3
  147. package/dist/chat/ui/visual-theme.js +3 -2
  148. package/dist/host.d.ts +9 -6
  149. package/dist/host.js +19 -8
  150. package/dist/index.d.ts +8 -0
  151. package/dist/index.js +9 -0
  152. package/dist/locales/en/agent-chat.d.ts +658 -0
  153. package/dist/locales/en/agent-chat.js +657 -0
  154. package/dist/locales/en/agent-prompts.d.ts +44 -0
  155. package/dist/locales/en/agent-prompts.js +43 -0
  156. package/dist/locales/en/agent-providers.d.ts +132 -0
  157. package/dist/locales/en/agent-providers.js +131 -0
  158. package/dist/locales/en/agent-usage.d.ts +132 -0
  159. package/dist/locales/en/agent-usage.js +131 -0
  160. package/dist/locales/nl/agent-chat.d.ts +658 -0
  161. package/dist/locales/nl/agent-chat.js +657 -0
  162. package/dist/locales/nl/agent-prompts.d.ts +44 -0
  163. package/dist/locales/nl/agent-prompts.js +43 -0
  164. package/dist/locales/nl/agent-providers.d.ts +132 -0
  165. package/dist/locales/nl/agent-providers.js +131 -0
  166. package/dist/locales/nl/agent-usage.d.ts +132 -0
  167. package/dist/locales/nl/agent-usage.js +131 -0
  168. package/dist/locales.js +9 -8
  169. package/dist/prompts/index.d.ts +11 -0
  170. package/dist/prompts/index.js +12 -0
  171. package/dist/prompts/logic/subjects.d.ts +2 -1
  172. package/dist/prompts/logic/subjects.js +6 -4
  173. package/dist/prompts/ui/ApprovalBody.js +6 -4
  174. package/dist/prompts/ui/PromptCard.js +5 -4
  175. package/dist/prompts/ui/PromptView.js +3 -2
  176. package/dist/prompts/ui/QuestionBody.js +3 -2
  177. package/dist/providers/AccountColors.js +2 -2
  178. package/dist/providers/AccountDetail.js +7 -6
  179. package/dist/providers/AccountVariables.js +3 -1
  180. package/dist/providers/AddAccountForm.js +3 -2
  181. package/dist/providers/ProvidersPane.js +4 -3
  182. package/dist/providers/account-actions.d.ts +4 -3
  183. package/dist/providers/account-actions.js +6 -7
  184. package/dist/providers/index.d.ts +16 -0
  185. package/dist/providers/index.js +17 -0
  186. package/dist/providers/provider-abilities.d.ts +2 -1
  187. package/dist/providers/provider-abilities.js +4 -0
  188. package/dist/state/chats.js +4 -2
  189. package/dist/state/index.d.ts +4 -0
  190. package/dist/state/index.js +5 -0
  191. package/dist/testing/index.d.ts +1 -0
  192. package/dist/testing/index.js +2 -0
  193. package/dist/{prompts/logic/prompts.fixtures.d.ts → testing/prompts.d.ts} +1 -1
  194. package/dist/theme.css +13 -3
  195. package/dist/usage/UsageBreakdown.js +3 -2
  196. package/dist/usage/UsageLimits.js +4 -3
  197. package/dist/usage/UsagePage.js +3 -2
  198. package/dist/usage/index.d.ts +15 -0
  199. package/dist/usage/index.js +15 -0
  200. package/package.json +53 -1031
  201. package/src/agents/AccountDot.tsx +3 -1
  202. package/src/agents/accounts.ts +6 -6
  203. package/src/chat/actions.ts +8 -5
  204. package/src/chat/bookmarks.ts +23 -14
  205. package/src/chat/chat-client.ts +8 -0
  206. package/src/chat/composer-send.ts +27 -0
  207. package/src/chat/forks.ts +27 -6
  208. package/src/chat/index.ts +98 -0
  209. package/src/chat/logic/embedded-code.ts +409 -0
  210. package/src/chat/logic/fork.ts +1 -0
  211. package/src/chat/logic/goal.ts +33 -0
  212. package/src/chat/logic/scrubber.ts +6 -5
  213. package/src/chat/logic/thought.ts +40 -0
  214. package/src/chat/logic/thread-cards.ts +2 -0
  215. package/src/chat/logic/timeline.ts +25 -1
  216. package/src/chat/logic/tool-catalog.ts +5 -1
  217. package/src/chat/logic/tools.ts +76 -0
  218. package/src/chat/preferences.ts +26 -3
  219. package/src/chat/reading-width.ts +39 -0
  220. package/src/chat/send-key.ts +38 -0
  221. package/src/chat/ui/BookmarkMarker.tsx +4 -2
  222. package/src/chat/ui/BookmarkSubmenu.tsx +4 -3
  223. package/src/chat/ui/ChatActivity.tsx +14 -10
  224. package/src/chat/ui/ChatReferenceChip.tsx +3 -4
  225. package/src/chat/ui/CodeBlock.tsx +33 -124
  226. package/src/chat/ui/Composer.tsx +212 -1358
  227. package/src/chat/ui/ComposerInput.tsx +5 -4
  228. package/src/chat/ui/EditDiff.tsx +3 -2
  229. package/src/chat/ui/GoalDock.tsx +104 -0
  230. package/src/chat/ui/LimitState.tsx +5 -3
  231. package/src/chat/ui/Markdown.tsx +113 -39
  232. package/src/chat/ui/MermaidBlock.tsx +119 -0
  233. package/src/chat/ui/MessageActions.tsx +13 -8
  234. package/src/chat/ui/Pickers.tsx +8 -7
  235. package/src/chat/ui/PromptComposer.tsx +4 -3
  236. package/src/chat/ui/RowTime.tsx +21 -0
  237. package/src/chat/ui/RunSettings.tsx +9 -4
  238. package/src/chat/ui/ShellCommand.tsx +134 -0
  239. package/src/chat/ui/SubagentInfo.tsx +4 -3
  240. package/src/chat/ui/SubagentStopButton.tsx +4 -3
  241. package/src/chat/ui/SubagentTimeline.tsx +5 -1
  242. package/src/chat/ui/Timeline.tsx +167 -105
  243. package/src/chat/ui/TimelineMenu.tsx +42 -7
  244. package/src/chat/ui/UnifiedDiff.tsx +3 -2
  245. package/src/chat/ui/VisualFrame.tsx +6 -7
  246. package/src/chat/ui/code-theme.ts +4 -4
  247. package/src/chat/ui/composer/ComposerControls.tsx +147 -0
  248. package/src/chat/ui/composer/ComposerMenu.tsx +138 -0
  249. package/src/chat/ui/composer/DraftParts.tsx +86 -0
  250. package/src/chat/ui/composer/QueuedMessages.tsx +64 -0
  251. package/src/chat/ui/composer/key-handler.ts +174 -0
  252. package/src/chat/ui/composer/keys.ts +32 -10
  253. package/src/chat/ui/composer/placeholder.ts +48 -0
  254. package/src/chat/ui/composer/surface.ts +45 -0
  255. package/src/chat/ui/composer/use-chat-controls.ts +134 -0
  256. package/src/chat/ui/composer/use-compaction-offer.ts +25 -0
  257. package/src/chat/ui/composer/use-composer-draft.ts +71 -0
  258. package/src/chat/ui/composer/use-composer-menus.ts +289 -0
  259. package/src/chat/ui/composer/use-composer-submit.ts +163 -0
  260. package/src/chat/ui/composer/use-draft-files.ts +135 -0
  261. package/src/chat/ui/composer/use-notice.ts +28 -0
  262. package/src/chat/ui/composer/use-pending-requests.ts +23 -0
  263. package/src/chat/ui/composer/use-prompt-history.ts +76 -0
  264. package/src/chat/ui/composer/use-prompt-hold.ts +58 -0
  265. package/src/chat/ui/composer/use-take-back.ts +91 -0
  266. package/src/chat/ui/diff/DiffFallbackBoundary.tsx +2 -2
  267. package/src/chat/ui/diff/EditorEditDiff.tsx +4 -3
  268. package/src/chat/ui/diff/LegacyDiffPool.tsx +3 -2
  269. package/src/chat/ui/diff/LegacyEditDiff.tsx +3 -2
  270. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +3 -2
  271. package/src/chat/ui/diff/syntax-runtime.ts +10 -5
  272. package/src/chat/ui/diff-syntax.ts +2 -2
  273. package/src/chat/ui/diff-theme.ts +10 -9
  274. package/src/chat/ui/file-links.ts +7 -5
  275. package/src/chat/ui/highlighter.ts +185 -0
  276. package/src/chat/ui/index.ts +109 -0
  277. package/src/chat/ui/intelligent-ui/UiReply.tsx +5 -4
  278. package/src/chat/ui/intelligent-ui/renderers/content.tsx +3 -2
  279. package/src/chat/ui/intelligent-ui/use-ui-links.ts +2 -2
  280. package/src/chat/ui/intelligent-ui/use-ui-queries.ts +4 -3
  281. package/src/chat/ui/markdown-blocks.ts +300 -12
  282. package/src/chat/ui/mermaid.ts +118 -0
  283. package/src/chat/ui/rows/ForksRow.tsx +5 -3
  284. package/src/chat/ui/rows/GeneratedImageRow.tsx +6 -4
  285. package/src/chat/ui/rows/MessageRows.tsx +73 -18
  286. package/src/chat/ui/rows/Rows.tsx +14 -1
  287. package/src/chat/ui/rows/SubagentRow.tsx +3 -2
  288. package/src/chat/ui/rows/VisualRow.tsx +3 -2
  289. package/src/chat/ui/rows/WorkRows.tsx +148 -47
  290. package/src/chat/ui/rows/WorkflowRow.tsx +3 -2
  291. package/src/chat/ui/timestamp-format.ts +2 -0
  292. package/src/chat/ui/use-diff-source-tokens.ts +10 -4
  293. package/src/chat/ui/visual-theme.ts +3 -2
  294. package/src/host.ts +31 -15
  295. package/src/index.ts +49 -0
  296. package/src/locales/en/agent-chat.ts +657 -0
  297. package/src/locales/en/agent-prompts.ts +43 -0
  298. package/src/locales/en/agent-providers.ts +131 -0
  299. package/src/locales/en/agent-usage.ts +131 -0
  300. package/src/locales/nl/agent-chat.ts +657 -0
  301. package/src/locales/nl/agent-prompts.ts +43 -0
  302. package/src/locales/nl/agent-providers.ts +131 -0
  303. package/src/locales/nl/agent-usage.ts +132 -0
  304. package/src/locales.ts +9 -8
  305. package/src/prompts/index.ts +35 -0
  306. package/src/prompts/logic/subjects.ts +12 -4
  307. package/src/prompts/ui/ApprovalBody.tsx +6 -4
  308. package/src/prompts/ui/PromptCard.tsx +11 -4
  309. package/src/prompts/ui/PromptView.tsx +3 -2
  310. package/src/prompts/ui/QuestionBody.tsx +3 -2
  311. package/src/providers/AccountColors.tsx +2 -2
  312. package/src/providers/AccountDetail.tsx +13 -7
  313. package/src/providers/AccountVariables.tsx +3 -1
  314. package/src/providers/AddAccountForm.tsx +3 -2
  315. package/src/providers/ProvidersPane.tsx +4 -3
  316. package/src/providers/account-actions.ts +7 -7
  317. package/src/providers/index.ts +17 -0
  318. package/src/providers/provider-abilities.ts +6 -1
  319. package/src/state/chats.ts +4 -2
  320. package/src/state/index.ts +39 -0
  321. package/src/testing/index.ts +2 -0
  322. package/src/{prompts/logic/prompts.fixtures.ts → testing/prompts.ts} +1 -1
  323. package/src/theme.css +13 -3
  324. package/src/usage/UsageBreakdown.tsx +3 -2
  325. package/src/usage/UsageLimits.tsx +4 -3
  326. package/src/usage/UsagePage.tsx +3 -2
  327. package/src/usage/index.ts +16 -0
  328. package/dist/locales/en/agent-chat.json +0 -597
  329. package/dist/locales/en/agent-prompts.json +0 -43
  330. package/dist/locales/en/agent-providers.json +0 -131
  331. package/dist/locales/en/agent-usage.json +0 -131
  332. package/dist/locales/nl/agent-chat.json +0 -597
  333. package/dist/locales/nl/agent-prompts.json +0 -43
  334. package/dist/locales/nl/agent-providers.json +0 -131
  335. package/dist/locales/nl/agent-usage.json +0 -131
  336. package/src/locales/en/agent-chat.json +0 -597
  337. package/src/locales/en/agent-prompts.json +0 -43
  338. package/src/locales/en/agent-providers.json +0 -131
  339. package/src/locales/en/agent-usage.json +0 -131
  340. package/src/locales/nl/agent-chat.json +0 -597
  341. package/src/locales/nl/agent-prompts.json +0 -43
  342. package/src/locales/nl/agent-providers.json +0 -131
  343. package/src/locales/nl/agent-usage.json +0 -131
  344. /package/dist/{prompts/logic/prompts.fixtures.js → testing/prompts.js} +0 -0
@@ -0,0 +1,409 @@
1
+ /* A stretch of a shell command written in another language, as offsets into the command. */
2
+ export interface EmbeddedCode {
3
+ readonly start: number;
4
+ readonly end: number;
5
+ /* A Shiki language id. */
6
+ readonly language: string;
7
+ }
8
+
9
+ export const SHELL_LANGUAGE = 'shellscript';
10
+
11
+ // Past this a command is drawn without looking for code inside it; the scan is linear, the highlighting is not.
12
+ export const EMBEDDED_CODE_LIMIT = 20_000;
13
+ const MAX_DEPTH = 3;
14
+
15
+ interface Span {
16
+ readonly start: number;
17
+ readonly end: number;
18
+ }
19
+
20
+ interface Word extends Span {
21
+ /* The word as the shell hands it to the program, quotes and escapes taken out. */
22
+ readonly text: string;
23
+ /* The source of what the program gets, for a word that is bare or one quoted string; null for a mix. */
24
+ readonly body: Span | null;
25
+ }
26
+
27
+ interface SimpleCommand {
28
+ readonly words: Word[];
29
+ readonly redirects: { readonly op: string; readonly target: Word }[];
30
+ readonly heredocs: Span[];
31
+ }
32
+
33
+ interface Interpreter {
34
+ readonly language: string;
35
+ /* Whether a flag says the next argument is the script. */
36
+ script(flag: string): boolean;
37
+ /* Options that take the next argument as their value. */
38
+ readonly takesValue?: ReadonlySet<string>;
39
+ /* A database client: its arguments name a database, and it reads statements from stdin whatever they are. */
40
+ readonly client?: boolean;
41
+ /* The positional argument that holds statements, as in `sqlite3 app.db 'select 1'`. */
42
+ readonly positional?: number;
43
+ }
44
+
45
+ const SHELL: Interpreter = {
46
+ language: SHELL_LANGUAGE,
47
+ script: (flag) => /^-[a-zA-Z]*c[a-zA-Z]*$/.test(flag),
48
+ takesValue: new Set(['-o', '-O', '--rcfile', '--init-file'])
49
+ };
50
+
51
+ const PYTHON: Interpreter = { language: 'python', script: (flag) => flag === '-c', takesValue: new Set(['-W', '-X']) };
52
+
53
+ const NODE: Interpreter = {
54
+ language: 'javascript',
55
+ script: (flag) => ['-e', '--eval', '-p', '--print'].includes(flag),
56
+ takesValue: new Set(['-r', '--require', '--import', '--input-type', '--conditions', '-C'])
57
+ };
58
+
59
+ const BUN: Interpreter = { language: 'typescript', script: (flag) => ['-e', '--eval', '-p', '--print'].includes(flag), takesValue: NODE.takesValue };
60
+
61
+ const MYSQL: Interpreter = {
62
+ language: 'sql',
63
+ client: true,
64
+ script: (flag) => flag === '-e' || flag === '--execute',
65
+ takesValue: new Set(['-u', '-h', '-P', '-D', '-S', '--user', '--host', '--port', '--database'])
66
+ };
67
+
68
+ const INTERPRETERS: Record<string, Interpreter> = {
69
+ bash: SHELL,
70
+ sh: SHELL,
71
+ zsh: SHELL,
72
+ dash: SHELL,
73
+ ksh: SHELL,
74
+ python: PYTHON,
75
+ node: NODE,
76
+ bun: BUN,
77
+ psql: {
78
+ language: 'sql',
79
+ client: true,
80
+ script: (flag) => flag === '-c' || flag === '--command',
81
+ takesValue: new Set(['-d', '-h', '-p', '-U', '-f', '-v', '-o', '--dbname', '--host', '--port', '--username', '--file', '--set'])
82
+ },
83
+ mysql: MYSQL,
84
+ mariadb: MYSQL,
85
+ sqlite3: { language: 'sql', client: true, script: () => false, takesValue: new Set(['-cmd', '-separator', '-newline', '-nullvalue']), positional: 1 }
86
+ };
87
+
88
+ // Programs that run the next word as the command; their own options are skipped along with them.
89
+ const WRAPPERS = new Set(['sudo', 'env', 'exec', 'nohup', 'time', 'command', 'nice']);
90
+ // Tools that run a program of a project's environment as `<tool> run <program>`.
91
+ const RUNNERS = new Set(['uv', 'poetry', 'pipenv']);
92
+ // Where a heredoc written to a file goes, by the file's extension.
93
+ const EXTENSIONS: Record<string, string> = {
94
+ py: 'python',
95
+ js: 'javascript',
96
+ mjs: 'javascript',
97
+ cjs: 'javascript',
98
+ jsx: 'jsx',
99
+ ts: 'typescript',
100
+ mts: 'typescript',
101
+ tsx: 'tsx',
102
+ json: 'json',
103
+ sql: 'sql',
104
+ sh: SHELL_LANGUAGE,
105
+ bash: SHELL_LANGUAGE,
106
+ zsh: SHELL_LANGUAGE,
107
+ yml: 'yaml',
108
+ yaml: 'yaml',
109
+ md: 'markdown',
110
+ css: 'css',
111
+ html: 'html',
112
+ php: 'php',
113
+ xml: 'xml'
114
+ };
115
+
116
+ const SEPARATORS = ';&|()';
117
+ const BLANKS = ' \t\r';
118
+
119
+ /* The program a command name runs, as the table above knows it: `/usr/bin/python3.12` is `python`. */
120
+ function programOf(word: string): string {
121
+ const name = word.slice(word.lastIndexOf('/') + 1);
122
+ return /^python\d*(\.\d+)?$/.test(name) ? 'python' : name;
123
+ }
124
+
125
+ /* Splits `source[from, to)` into simple commands the way a shell would, enough to find the code in them. */
126
+ function parseCommands(source: string, from: number, to: number): SimpleCommand[] {
127
+ const commands: SimpleCommand[] = [];
128
+ let current: SimpleCommand = { words: [], redirects: [], heredocs: [] };
129
+ const pending: { command: SimpleCommand; delimiter: string; strip: boolean }[] = [];
130
+ const finish = (): void => {
131
+ if (current.words.length > 0 || current.redirects.length > 0) {
132
+ commands.push(current);
133
+ }
134
+ current = { words: [], redirects: [], heredocs: [] };
135
+ };
136
+ const skipBlanks = (index: number): number => {
137
+ while (index < to && BLANKS.includes(source[index]!)) {
138
+ index++;
139
+ }
140
+ return index;
141
+ };
142
+
143
+ const readWord = (start: number): Word => {
144
+ let index = start;
145
+ let text = '';
146
+ let quoted: Span | null = null;
147
+ let parts = 0;
148
+ let bare = true;
149
+ while (index < to) {
150
+ const char = source[index]!;
151
+ if (BLANKS.includes(char) || char === '\n' || SEPARATORS.includes(char) || char === '<' || char === '>') {
152
+ break;
153
+ }
154
+ parts++;
155
+ if (char === "'") {
156
+ const close = source.indexOf("'", index + 1);
157
+ const end = close === -1 || close >= to ? to : close;
158
+ text += source.slice(index + 1, end);
159
+ quoted = { start: index + 1, end };
160
+ bare = false;
161
+ index = Math.min(end + 1, to);
162
+ } else if (char === '"') {
163
+ let end = index + 1;
164
+ while (end < to && source[end] !== '"') {
165
+ if (source[end] === '\\' && end + 1 < to && '$`"\\\n'.includes(source[end + 1]!)) {
166
+ text += source[end + 1];
167
+ end += 2;
168
+ } else {
169
+ text += source[end];
170
+ end++;
171
+ }
172
+ }
173
+ quoted = { start: index + 1, end };
174
+ bare = false;
175
+ index = Math.min(end + 1, to);
176
+ } else if (char === '\\') {
177
+ text += source[index + 1] ?? '';
178
+ bare = false;
179
+ index += 2;
180
+ } else if (char === '$' && source[index + 1] === '(') {
181
+ const end = closingParen(source, index + 1, to);
182
+ text += source.slice(index, end);
183
+ index = end;
184
+ } else {
185
+ text += char;
186
+ index++;
187
+ }
188
+ }
189
+ const end = Math.min(index, to);
190
+ const body = bare ? { start, end } : parts === 1 && quoted !== null ? quoted : null;
191
+ return { start, end, text, body };
192
+ };
193
+
194
+ const readHeredoc = (start: number, delimiter: string, strip: boolean): { body: Span; next: number } => {
195
+ let index = start;
196
+ while (index < to) {
197
+ const newline = source.indexOf('\n', index);
198
+ const lineEnd = newline === -1 || newline >= to ? to : newline;
199
+ let line = source.slice(index, lineEnd).replace(/\r$/, '');
200
+ if (strip) {
201
+ line = line.replace(/^\t+/, '');
202
+ }
203
+ if (line === delimiter) {
204
+ return { body: { start, end: index }, next: Math.min(lineEnd + 1, to) };
205
+ }
206
+ index = lineEnd + 1;
207
+ }
208
+ return { body: { start, end: to }, next: to };
209
+ };
210
+
211
+ let index = from;
212
+ while (index < to) {
213
+ const char = source[index]!;
214
+ if (char === '\n') {
215
+ index++;
216
+ for (const heredoc of pending) {
217
+ const { body, next } = readHeredoc(index, heredoc.delimiter, heredoc.strip);
218
+ heredoc.command.heredocs.push(body);
219
+ index = next;
220
+ }
221
+ pending.length = 0;
222
+ finish();
223
+ } else if (BLANKS.includes(char)) {
224
+ index++;
225
+ } else if (char === '\\' && source[index + 1] === '\n') {
226
+ index += 2;
227
+ } else if (char === '#') {
228
+ const newline = source.indexOf('\n', index);
229
+ index = newline === -1 || newline >= to ? to : newline;
230
+ } else if (source.startsWith('<<<', index)) {
231
+ index = readWord(skipBlanks(index + 3)).end;
232
+ } else if (source.startsWith('<<', index)) {
233
+ index += 2;
234
+ const strip = source[index] === '-';
235
+ const word = readWord(skipBlanks(strip ? index + 1 : index));
236
+ if (word.text !== '') {
237
+ pending.push({ command: current, delimiter: word.text, strip });
238
+ }
239
+ index = word.end;
240
+ } else if (char === '<' || char === '>' || (char === '&' && source[index + 1] === '>')) {
241
+ let end = index + 1;
242
+ while (end < to && '<>&|'.includes(source[end]!)) {
243
+ end++;
244
+ }
245
+ const op = source.slice(index, end);
246
+ const target = readWord(skipBlanks(end));
247
+ current.redirects.push({ op, target });
248
+ index = Math.max(target.end, end);
249
+ } else if (SEPARATORS.includes(char)) {
250
+ index++;
251
+ finish();
252
+ } else {
253
+ const word = readWord(index);
254
+ // The `2` of `2>&1` is a file descriptor, not an argument.
255
+ const descriptor = /^\d+$/.test(word.text) && (source[word.end] === '>' || source[word.end] === '<');
256
+ if (!descriptor) {
257
+ current.words.push(word);
258
+ }
259
+ index = Math.max(word.end, index + 1);
260
+ }
261
+ }
262
+ finish();
263
+ return commands;
264
+ }
265
+
266
+ /* Where the `)` that closes the `(` at `open` is, past it; quotes inside do not count. */
267
+ function closingParen(source: string, open: number, to: number): number {
268
+ let depth = 0;
269
+ let quote: string | null = null;
270
+ for (let index = open; index < to; index++) {
271
+ const char = source[index]!;
272
+ if (quote !== null) {
273
+ if (char === quote) {
274
+ quote = null;
275
+ } else if (char === '\\' && quote === '"') {
276
+ index++;
277
+ }
278
+ } else if (char === "'" || char === '"') {
279
+ quote = char;
280
+ } else if (char === '(') {
281
+ depth++;
282
+ } else if (char === ')') {
283
+ depth--;
284
+ if (depth === 0) {
285
+ return index + 1;
286
+ }
287
+ }
288
+ }
289
+ return to;
290
+ }
291
+
292
+ /* The index of the word a command runs, past variable assignments, wrappers and runners; -1 for none. */
293
+ function programIndex(words: readonly Word[]): number {
294
+ let index = 0;
295
+ while (index < words.length) {
296
+ const text = words[index]!.text;
297
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(text)) {
298
+ index++;
299
+ } else if (WRAPPERS.has(programOf(text))) {
300
+ index++;
301
+ while (index < words.length && words[index]!.text.startsWith('-')) {
302
+ index++;
303
+ }
304
+ } else if (RUNNERS.has(programOf(text)) && words[index + 1]?.text === 'run') {
305
+ index += 2;
306
+ } else {
307
+ return index;
308
+ }
309
+ }
310
+ return -1;
311
+ }
312
+
313
+ /* The code of one simple command: an inline script, or a heredoc fed to an interpreter or written to a file. */
314
+ function codeOf(command: SimpleCommand): EmbeddedCode[] {
315
+ const found: EmbeddedCode[] = [];
316
+ const at = programIndex(command.words);
317
+ const program = at === -1 ? '' : programOf(command.words[at]!.text);
318
+ const interpreter = INTERPRETERS[program];
319
+ let readsFile = false;
320
+ if (interpreter !== undefined) {
321
+ let positional = 0;
322
+ for (let index = at + 1; index < command.words.length; index++) {
323
+ const word = command.words[index]!;
324
+ if (word.text.startsWith('-') && word.text !== '-') {
325
+ if (interpreter.script(word.text)) {
326
+ const script = command.words[index + 1];
327
+ if (script?.body) {
328
+ found.push({ ...script.body, language: interpreter.language });
329
+ }
330
+ readsFile = true;
331
+ index++;
332
+ } else if (interpreter.takesValue?.has(word.text)) {
333
+ index++;
334
+ } else if (interpreter === PYTHON && word.text === '-m') {
335
+ readsFile = true;
336
+ break;
337
+ }
338
+ continue;
339
+ }
340
+ if (interpreter.client !== true) {
341
+ // A script file; whatever follows is its own arguments.
342
+ readsFile = word.text !== '-';
343
+ break;
344
+ }
345
+ if (positional === interpreter.positional && word.body !== null) {
346
+ found.push({ ...word.body, language: interpreter.language });
347
+ }
348
+ positional++;
349
+ }
350
+ }
351
+ const heredocLanguage = interpreter !== undefined && (!readsFile || interpreter.client === true) ? interpreter.language : writtenLanguage(command, program);
352
+ if (heredocLanguage !== null) {
353
+ for (const body of command.heredocs) {
354
+ found.push({ ...body, language: heredocLanguage });
355
+ }
356
+ }
357
+ return found;
358
+ }
359
+
360
+ /* The language of the file a `cat > file` or `tee file` writes, by its extension; null when nothing says. */
361
+ function writtenLanguage(command: SimpleCommand, program: string): string | null {
362
+ const targets = command.redirects.filter(({ op }) => op === '>' || op === '>>' || op === '>|').map(({ target }) => target.text);
363
+ if (program === 'tee') {
364
+ targets.push(
365
+ ...command.words
366
+ .slice(programIndex(command.words) + 1)
367
+ .map(({ text }) => text)
368
+ .filter((text) => !text.startsWith('-'))
369
+ );
370
+ }
371
+ for (const target of targets) {
372
+ const dot = target.lastIndexOf('.');
373
+ const language = dot === -1 ? undefined : EXTENSIONS[target.slice(dot + 1).toLowerCase()];
374
+ if (language !== undefined) {
375
+ return language;
376
+ }
377
+ }
378
+ return null;
379
+ }
380
+
381
+ function collect(source: string, from: number, to: number, depth: number, into: EmbeddedCode[]): void {
382
+ for (const command of parseCommands(source, from, to)) {
383
+ for (const code of codeOf(command)) {
384
+ if (code.end <= code.start) {
385
+ continue;
386
+ }
387
+ into.push(code);
388
+ if (code.language === SHELL_LANGUAGE && depth < MAX_DEPTH) {
389
+ collect(source, code.start, code.end, depth + 1, into);
390
+ }
391
+ }
392
+ }
393
+ }
394
+
395
+ /*
396
+ * The code inside a shell command that is in another language, or a shell script of its own: the
397
+ * script of `bash -lc '…'`, `python3 -c '…'`, `node -e '…'`, `psql -c '…'`, and a heredoc fed to an
398
+ * interpreter or written to a file of a known kind. Sorted by start, a stretch before the ones inside
399
+ * it, so drawing them in order leaves the innermost language on top. A heuristic over a small shell
400
+ * grammar, not a parser: what it does not recognize stays plain shell.
401
+ */
402
+ export function embeddedCode(command: string): EmbeddedCode[] {
403
+ if (command.length > EMBEDDED_CODE_LIMIT) {
404
+ return [];
405
+ }
406
+ const found: EmbeddedCode[] = [];
407
+ collect(command, 0, command.length, 0, found);
408
+ return found.sort((left, right) => left.start - right.start || right.end - left.end);
409
+ }
@@ -133,6 +133,7 @@ export function turnIdOfRow(row: TimelineRow): string | null {
133
133
  case 'user':
134
134
  case 'assistant':
135
135
  case 'thinking':
136
+ case 'live-thought':
136
137
  case 'subagent':
137
138
  case 'approval':
138
139
  case 'question':
@@ -0,0 +1,33 @@
1
+ import type { ChatGoal, ChatGoalAction, ChatInfo, ProviderCapabilities } from '@adecore/agent-contracts';
2
+
3
+ export interface GoalView {
4
+ goal: ChatGoal;
5
+ // What a person may do to it from the row; empty where the CLI or the host offers nothing.
6
+ actions: ChatGoalAction[];
7
+ }
8
+
9
+ /*
10
+ * The row above the composer while the chat has a goal. A CLI with calls for goals pauses, resumes and
11
+ * clears it at any time; one that only reads `/goal` clears it with a turn of its own, so only between
12
+ * turns, and a goal it already met clears itself.
13
+ */
14
+ export function goalView(info: Pick<ChatInfo, 'goal' | 'activeTurnId'>, goals: ProviderCapabilities['goals'], canAct: boolean): GoalView | null {
15
+ const goal = info.goal;
16
+ if (goal === undefined) {
17
+ return null;
18
+ }
19
+ if (!canAct || goals === undefined) {
20
+ return { goal, actions: [] };
21
+ }
22
+ if (goals === 'command') {
23
+ return { goal, actions: goal.status === 'complete' || info.activeTurnId !== null ? [] : ['clear'] };
24
+ }
25
+ const actions: ChatGoalAction[] = [];
26
+ if (goal.status === 'active') {
27
+ actions.push('pause');
28
+ } else if (goal.status !== 'complete') {
29
+ actions.push('resume');
30
+ }
31
+ actions.push('clear');
32
+ return { goal, actions };
33
+ }
@@ -1,5 +1,6 @@
1
1
  import type { ChatBookmark } from '@adecore/agent-contracts';
2
2
  import type { TimelineRow } from './timeline';
3
+ import { DEFAULT_READING_WIDTH, readingMeasure } from '../reading-width';
3
4
 
4
5
  /*
5
6
  * The strip at the left edge of a chat view with a tick per message a person sent, as numbers: which
@@ -30,8 +31,7 @@ export const TICK_HEIGHT_PX = 2;
30
31
  const PITCH_PX = 8;
31
32
  const MIN_PITCH_PX = 4;
32
33
 
33
- /* The thread's content column in a view of its own (`.chat-column-content`) and the scroller's padding around it (`px-4`). */
34
- const CONTENT_MAX_WIDTH_PX = 768;
34
+ /* The scroller's padding around the thread's content column (`px-4`). */
35
35
  const THREAD_PADDING_PX = 16;
36
36
  export const STRIP_WIDTH_PX = 24;
37
37
  /* The strip's distance from the view's left edge, and the air it keeps between itself and the thread's text. */
@@ -273,13 +273,14 @@ export function stepMessage(starts: readonly number[], eligible: (index: number)
273
273
  /*
274
274
  * The left padding of the thread in a view that draws the strip at its left edge. A wide view keeps
275
275
  * the usual padding, since its centered column already clears the strip; a narrower one pads until
276
- * the column's text starts clear of it, so nothing is ever drawn under the strip.
276
+ * the column's text starts clear of it, so nothing is ever drawn under the strip. `measure` is the
277
+ * widest the content column runs (`readingMeasure`).
277
278
  */
278
- export function threadPaddingLeft(width: number, strip: boolean): number {
279
+ export function threadPaddingLeft(width: number, strip: boolean, measure = readingMeasure(DEFAULT_READING_WIDTH)): number {
279
280
  if (!strip) {
280
281
  return THREAD_PADDING_PX;
281
282
  }
282
283
  // The column's margin is half of what the padding leaves, so the padding counts the clearance twice.
283
- const padding = 2 * STRIP_CLEARANCE_PX + CONTENT_MAX_WIDTH_PX + THREAD_PADDING_PX - Math.floor(width);
284
+ const padding = 2 * STRIP_CLEARANCE_PX + measure + THREAD_PADDING_PX - Math.floor(width);
284
285
  return Math.min(STRIP_CLEARANCE_PX, Math.max(THREAD_PADDING_PX, padding));
285
286
  }
@@ -0,0 +1,40 @@
1
+ import { stripMarkdown } from './timeline-copy';
2
+
3
+ // A line that is nothing but bold, the way a model titles a stretch of its reasoning.
4
+ const HEADING_LINE = /^[ \t]*\*\*([^*\n]+?)\*\*[ \t]*$/;
5
+ const LIST_MARKER = /^[ \t]*(?:[-*+]|\d+[.)])[ \t]+/gm;
6
+ // A sentence ends on a stop with white space after it, so `a.ts` and `1.5` stay whole.
7
+ const SENTENCE_END = /[.!?]["'”’)\]]*(?=\s)/;
8
+
9
+ /* The thought as one line of plain text: the marks gone and every run of white space one space. */
10
+ export function flattenThought(text: string): string {
11
+ return stripMarkdown(text.replace(LIST_MARKER, '')).replace(/\s+/g, ' ').trim();
12
+ }
13
+
14
+ function headingOf(line: string): string | null {
15
+ return HEADING_LINE.exec(line)?.[1] ?? null;
16
+ }
17
+
18
+ /* What a folded thought shows on its one line: the heading it opens with, or the whole thought flattened for the row to cut off. */
19
+ export function thoughtPreview(text: string): string {
20
+ const heading = headingOf(text.trimStart().split('\n', 1)[0] ?? '');
21
+ return flattenThought(heading ?? text);
22
+ }
23
+
24
+ /*
25
+ * What the model is thinking about now, in a line: the heading of the last stretch it titled, which
26
+ * is the one it is on, or the first sentence of a thought that titles nothing.
27
+ */
28
+ export function thoughtLine(text: string): string {
29
+ const headings = text
30
+ .split('\n')
31
+ .map(headingOf)
32
+ .filter((heading): heading is string => heading !== null);
33
+ const last = headings.at(-1);
34
+ if (last !== undefined) {
35
+ return flattenThought(last);
36
+ }
37
+ const flat = flattenThought(text);
38
+ const end = SENTENCE_END.exec(flat);
39
+ return end === null ? flat : flat.slice(0, end.index + end[0].length);
40
+ }
@@ -32,6 +32,8 @@ function rowTime(row: TimelineRow): number | null {
32
32
  return row.tool.createdAt;
33
33
  case 'work-group':
34
34
  return row.tools[0]?.createdAt ?? null;
35
+ case 'live-thought':
36
+ return row.at;
35
37
  case 'working':
36
38
  return Number.POSITIVE_INFINITY;
37
39
  default:
@@ -45,6 +45,8 @@ export type TimelineRow =
45
45
  | { kind: 'work'; id: string; tool: ChatToolItem }
46
46
  | { kind: 'work-group'; id: string; tools: ChatToolItem[]; summary: string; expanded: boolean }
47
47
  | { kind: 'work-live'; id: string; tool: ChatToolItem }
48
+ // The latest thought of a running turn, repeated over the call it led to; `at` is when that call began.
49
+ | { kind: 'live-thought'; id: string; item: ChatThinkingItem; at: number }
48
50
  // A Workflow call the machine reports phases and agents for, drawn with them under it.
49
51
  | { kind: 'workflow'; id: string; tool: ChatToolItem; workflow: ChatWorkflow }
50
52
  | ({ kind: 'subagent' } & SubagentBranch)
@@ -363,6 +365,27 @@ function lastAssistantRow(rows: TimelineRow[]): TimelineRow | null {
363
365
  return null;
364
366
  }
365
367
 
368
+ /*
369
+ * The rows of a running turn with its latest thought over the call it led to, so what the model
370
+ * found does not scroll away behind the next tool line. Nothing when that call follows the thought
371
+ * directly, where the thought's own row already shows it, or when a newer thought came after the call.
372
+ */
373
+ function withLiveThought(rows: TimelineRow[], items: readonly ChatItem[]): TimelineRow[] {
374
+ const thoughtAt = items.findLastIndex((item) => item.kind === 'thinking' && parentOf(item) === null);
375
+ const thought = items[thoughtAt];
376
+ if (thought?.kind !== 'thinking' || thought.text === '') {
377
+ return rows;
378
+ }
379
+ const after = new Set(items.slice(thoughtAt + 1).map((item) => item.id));
380
+ const index = rows.findIndex((row) => row.kind === 'work-live' && after.has(row.tool.id));
381
+ const call = rows[index];
382
+ const previous = rows[index - 1];
383
+ if (call?.kind !== 'work-live' || (previous?.kind === 'thinking' && previous.id === thought.id)) {
384
+ return rows;
385
+ }
386
+ return [...rows.slice(0, index), { kind: 'live-thought', id: `live-thought-${thought.id}`, item: thought, at: call.tool.createdAt }, ...rows.slice(index)];
387
+ }
388
+
366
389
  interface TurnChunk {
367
390
  turnId: string | null;
368
391
  items: ChatItem[];
@@ -411,7 +434,8 @@ export function deriveTimelineRows(items: ChatItem[], options: TimelineOptions):
411
434
  }
412
435
  const active = turnId === options.activeTurnId || turn?.state === 'running';
413
436
  if (!turn || active) {
414
- rows.push(...rowsForItems(conversation, options, children));
437
+ const live = rowsForItems(conversation, options, children);
438
+ rows.push(...(active ? withLiveThought(live, conversation) : live));
415
439
  if (active) {
416
440
  rows.push({ kind: 'working', id: `working-${turnId}`, startedAt: turn?.createdAt ?? Date.now() });
417
441
  }
@@ -15,6 +15,8 @@ export interface ToolEntry {
15
15
  changesFiles: boolean;
16
16
  /* Whether the call reads a path the thread may draw as an image. */
17
17
  readsImage: boolean;
18
+ /* Whether `command` in its input is a shell command, drawn as one. */
19
+ shell: boolean;
18
20
  }
19
21
 
20
22
  function entry(icon: LucideIcon, summary: readonly string[], rest: Partial<ToolEntry> = {}): ToolEntry {
@@ -24,6 +26,7 @@ function entry(icon: LucideIcon, summary: readonly string[], rest: Partial<ToolE
24
26
  grouped: true,
25
27
  changesFiles: false,
26
28
  readsImage: false,
29
+ shell: false,
27
30
  ...rest
28
31
  };
29
32
  }
@@ -44,7 +47,8 @@ export const TOOL_CATALOG: Record<string, ToolEntry> = {
44
47
  NotebookEdit: entry(SquarePen, ['file_path', 'notebook_path']),
45
48
  // Codex reports a patch as the paths it touches; its own input carries no path of its own.
46
49
  ApplyPatch: entry(SquarePen, ['summary'], { grouped: false, changesFiles: true }),
47
- Bash: entry(Terminal, ['description', 'command']),
50
+ // Codex's command executions arrive under this name too.
51
+ Bash: entry(Terminal, ['description', 'command'], { shell: true }),
48
52
  Grep: entry(Search, ['pattern']),
49
53
  Glob: entry(FolderSearch, ['pattern']),
50
54
  WebFetch: entry(Globe, ['url', 'query']),