@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,371 @@
1
+ export const SHELL_LANGUAGE = 'shellscript';
2
+ // Past this a command is drawn without looking for code inside it; the scan is linear, the highlighting is not.
3
+ export const EMBEDDED_CODE_LIMIT = 20_000;
4
+ const MAX_DEPTH = 3;
5
+ const SHELL = {
6
+ language: SHELL_LANGUAGE,
7
+ script: (flag) => /^-[a-zA-Z]*c[a-zA-Z]*$/.test(flag),
8
+ takesValue: new Set(['-o', '-O', '--rcfile', '--init-file'])
9
+ };
10
+ const PYTHON = { language: 'python', script: (flag) => flag === '-c', takesValue: new Set(['-W', '-X']) };
11
+ const NODE = {
12
+ language: 'javascript',
13
+ script: (flag) => ['-e', '--eval', '-p', '--print'].includes(flag),
14
+ takesValue: new Set(['-r', '--require', '--import', '--input-type', '--conditions', '-C'])
15
+ };
16
+ const BUN = { language: 'typescript', script: (flag) => ['-e', '--eval', '-p', '--print'].includes(flag), takesValue: NODE.takesValue };
17
+ const MYSQL = {
18
+ language: 'sql',
19
+ client: true,
20
+ script: (flag) => flag === '-e' || flag === '--execute',
21
+ takesValue: new Set(['-u', '-h', '-P', '-D', '-S', '--user', '--host', '--port', '--database'])
22
+ };
23
+ const INTERPRETERS = {
24
+ bash: SHELL,
25
+ sh: SHELL,
26
+ zsh: SHELL,
27
+ dash: SHELL,
28
+ ksh: SHELL,
29
+ python: PYTHON,
30
+ node: NODE,
31
+ bun: BUN,
32
+ psql: {
33
+ language: 'sql',
34
+ client: true,
35
+ script: (flag) => flag === '-c' || flag === '--command',
36
+ takesValue: new Set(['-d', '-h', '-p', '-U', '-f', '-v', '-o', '--dbname', '--host', '--port', '--username', '--file', '--set'])
37
+ },
38
+ mysql: MYSQL,
39
+ mariadb: MYSQL,
40
+ sqlite3: { language: 'sql', client: true, script: () => false, takesValue: new Set(['-cmd', '-separator', '-newline', '-nullvalue']), positional: 1 }
41
+ };
42
+ // Programs that run the next word as the command; their own options are skipped along with them.
43
+ const WRAPPERS = new Set(['sudo', 'env', 'exec', 'nohup', 'time', 'command', 'nice']);
44
+ // Tools that run a program of a project's environment as `<tool> run <program>`.
45
+ const RUNNERS = new Set(['uv', 'poetry', 'pipenv']);
46
+ // Where a heredoc written to a file goes, by the file's extension.
47
+ const EXTENSIONS = {
48
+ py: 'python',
49
+ js: 'javascript',
50
+ mjs: 'javascript',
51
+ cjs: 'javascript',
52
+ jsx: 'jsx',
53
+ ts: 'typescript',
54
+ mts: 'typescript',
55
+ tsx: 'tsx',
56
+ json: 'json',
57
+ sql: 'sql',
58
+ sh: SHELL_LANGUAGE,
59
+ bash: SHELL_LANGUAGE,
60
+ zsh: SHELL_LANGUAGE,
61
+ yml: 'yaml',
62
+ yaml: 'yaml',
63
+ md: 'markdown',
64
+ css: 'css',
65
+ html: 'html',
66
+ php: 'php',
67
+ xml: 'xml'
68
+ };
69
+ const SEPARATORS = ';&|()';
70
+ const BLANKS = ' \t\r';
71
+ /* The program a command name runs, as the table above knows it: `/usr/bin/python3.12` is `python`. */
72
+ function programOf(word) {
73
+ const name = word.slice(word.lastIndexOf('/') + 1);
74
+ return /^python\d*(\.\d+)?$/.test(name) ? 'python' : name;
75
+ }
76
+ /* Splits `source[from, to)` into simple commands the way a shell would, enough to find the code in them. */
77
+ function parseCommands(source, from, to) {
78
+ const commands = [];
79
+ let current = { words: [], redirects: [], heredocs: [] };
80
+ const pending = [];
81
+ const finish = () => {
82
+ if (current.words.length > 0 || current.redirects.length > 0) {
83
+ commands.push(current);
84
+ }
85
+ current = { words: [], redirects: [], heredocs: [] };
86
+ };
87
+ const skipBlanks = (index) => {
88
+ while (index < to && BLANKS.includes(source[index])) {
89
+ index++;
90
+ }
91
+ return index;
92
+ };
93
+ const readWord = (start) => {
94
+ let index = start;
95
+ let text = '';
96
+ let quoted = null;
97
+ let parts = 0;
98
+ let bare = true;
99
+ while (index < to) {
100
+ const char = source[index];
101
+ if (BLANKS.includes(char) || char === '\n' || SEPARATORS.includes(char) || char === '<' || char === '>') {
102
+ break;
103
+ }
104
+ parts++;
105
+ if (char === "'") {
106
+ const close = source.indexOf("'", index + 1);
107
+ const end = close === -1 || close >= to ? to : close;
108
+ text += source.slice(index + 1, end);
109
+ quoted = { start: index + 1, end };
110
+ bare = false;
111
+ index = Math.min(end + 1, to);
112
+ }
113
+ else if (char === '"') {
114
+ let end = index + 1;
115
+ while (end < to && source[end] !== '"') {
116
+ if (source[end] === '\\' && end + 1 < to && '$`"\\\n'.includes(source[end + 1])) {
117
+ text += source[end + 1];
118
+ end += 2;
119
+ }
120
+ else {
121
+ text += source[end];
122
+ end++;
123
+ }
124
+ }
125
+ quoted = { start: index + 1, end };
126
+ bare = false;
127
+ index = Math.min(end + 1, to);
128
+ }
129
+ else if (char === '\\') {
130
+ text += source[index + 1] ?? '';
131
+ bare = false;
132
+ index += 2;
133
+ }
134
+ else if (char === '$' && source[index + 1] === '(') {
135
+ const end = closingParen(source, index + 1, to);
136
+ text += source.slice(index, end);
137
+ index = end;
138
+ }
139
+ else {
140
+ text += char;
141
+ index++;
142
+ }
143
+ }
144
+ const end = Math.min(index, to);
145
+ const body = bare ? { start, end } : parts === 1 && quoted !== null ? quoted : null;
146
+ return { start, end, text, body };
147
+ };
148
+ const readHeredoc = (start, delimiter, strip) => {
149
+ let index = start;
150
+ while (index < to) {
151
+ const newline = source.indexOf('\n', index);
152
+ const lineEnd = newline === -1 || newline >= to ? to : newline;
153
+ let line = source.slice(index, lineEnd).replace(/\r$/, '');
154
+ if (strip) {
155
+ line = line.replace(/^\t+/, '');
156
+ }
157
+ if (line === delimiter) {
158
+ return { body: { start, end: index }, next: Math.min(lineEnd + 1, to) };
159
+ }
160
+ index = lineEnd + 1;
161
+ }
162
+ return { body: { start, end: to }, next: to };
163
+ };
164
+ let index = from;
165
+ while (index < to) {
166
+ const char = source[index];
167
+ if (char === '\n') {
168
+ index++;
169
+ for (const heredoc of pending) {
170
+ const { body, next } = readHeredoc(index, heredoc.delimiter, heredoc.strip);
171
+ heredoc.command.heredocs.push(body);
172
+ index = next;
173
+ }
174
+ pending.length = 0;
175
+ finish();
176
+ }
177
+ else if (BLANKS.includes(char)) {
178
+ index++;
179
+ }
180
+ else if (char === '\\' && source[index + 1] === '\n') {
181
+ index += 2;
182
+ }
183
+ else if (char === '#') {
184
+ const newline = source.indexOf('\n', index);
185
+ index = newline === -1 || newline >= to ? to : newline;
186
+ }
187
+ else if (source.startsWith('<<<', index)) {
188
+ index = readWord(skipBlanks(index + 3)).end;
189
+ }
190
+ else if (source.startsWith('<<', index)) {
191
+ index += 2;
192
+ const strip = source[index] === '-';
193
+ const word = readWord(skipBlanks(strip ? index + 1 : index));
194
+ if (word.text !== '') {
195
+ pending.push({ command: current, delimiter: word.text, strip });
196
+ }
197
+ index = word.end;
198
+ }
199
+ else if (char === '<' || char === '>' || (char === '&' && source[index + 1] === '>')) {
200
+ let end = index + 1;
201
+ while (end < to && '<>&|'.includes(source[end])) {
202
+ end++;
203
+ }
204
+ const op = source.slice(index, end);
205
+ const target = readWord(skipBlanks(end));
206
+ current.redirects.push({ op, target });
207
+ index = Math.max(target.end, end);
208
+ }
209
+ else if (SEPARATORS.includes(char)) {
210
+ index++;
211
+ finish();
212
+ }
213
+ else {
214
+ const word = readWord(index);
215
+ // The `2` of `2>&1` is a file descriptor, not an argument.
216
+ const descriptor = /^\d+$/.test(word.text) && (source[word.end] === '>' || source[word.end] === '<');
217
+ if (!descriptor) {
218
+ current.words.push(word);
219
+ }
220
+ index = Math.max(word.end, index + 1);
221
+ }
222
+ }
223
+ finish();
224
+ return commands;
225
+ }
226
+ /* Where the `)` that closes the `(` at `open` is, past it; quotes inside do not count. */
227
+ function closingParen(source, open, to) {
228
+ let depth = 0;
229
+ let quote = null;
230
+ for (let index = open; index < to; index++) {
231
+ const char = source[index];
232
+ if (quote !== null) {
233
+ if (char === quote) {
234
+ quote = null;
235
+ }
236
+ else if (char === '\\' && quote === '"') {
237
+ index++;
238
+ }
239
+ }
240
+ else if (char === "'" || char === '"') {
241
+ quote = char;
242
+ }
243
+ else if (char === '(') {
244
+ depth++;
245
+ }
246
+ else if (char === ')') {
247
+ depth--;
248
+ if (depth === 0) {
249
+ return index + 1;
250
+ }
251
+ }
252
+ }
253
+ return to;
254
+ }
255
+ /* The index of the word a command runs, past variable assignments, wrappers and runners; -1 for none. */
256
+ function programIndex(words) {
257
+ let index = 0;
258
+ while (index < words.length) {
259
+ const text = words[index].text;
260
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(text)) {
261
+ index++;
262
+ }
263
+ else if (WRAPPERS.has(programOf(text))) {
264
+ index++;
265
+ while (index < words.length && words[index].text.startsWith('-')) {
266
+ index++;
267
+ }
268
+ }
269
+ else if (RUNNERS.has(programOf(text)) && words[index + 1]?.text === 'run') {
270
+ index += 2;
271
+ }
272
+ else {
273
+ return index;
274
+ }
275
+ }
276
+ return -1;
277
+ }
278
+ /* The code of one simple command: an inline script, or a heredoc fed to an interpreter or written to a file. */
279
+ function codeOf(command) {
280
+ const found = [];
281
+ const at = programIndex(command.words);
282
+ const program = at === -1 ? '' : programOf(command.words[at].text);
283
+ const interpreter = INTERPRETERS[program];
284
+ let readsFile = false;
285
+ if (interpreter !== undefined) {
286
+ let positional = 0;
287
+ for (let index = at + 1; index < command.words.length; index++) {
288
+ const word = command.words[index];
289
+ if (word.text.startsWith('-') && word.text !== '-') {
290
+ if (interpreter.script(word.text)) {
291
+ const script = command.words[index + 1];
292
+ if (script?.body) {
293
+ found.push({ ...script.body, language: interpreter.language });
294
+ }
295
+ readsFile = true;
296
+ index++;
297
+ }
298
+ else if (interpreter.takesValue?.has(word.text)) {
299
+ index++;
300
+ }
301
+ else if (interpreter === PYTHON && word.text === '-m') {
302
+ readsFile = true;
303
+ break;
304
+ }
305
+ continue;
306
+ }
307
+ if (interpreter.client !== true) {
308
+ // A script file; whatever follows is its own arguments.
309
+ readsFile = word.text !== '-';
310
+ break;
311
+ }
312
+ if (positional === interpreter.positional && word.body !== null) {
313
+ found.push({ ...word.body, language: interpreter.language });
314
+ }
315
+ positional++;
316
+ }
317
+ }
318
+ const heredocLanguage = interpreter !== undefined && (!readsFile || interpreter.client === true) ? interpreter.language : writtenLanguage(command, program);
319
+ if (heredocLanguage !== null) {
320
+ for (const body of command.heredocs) {
321
+ found.push({ ...body, language: heredocLanguage });
322
+ }
323
+ }
324
+ return found;
325
+ }
326
+ /* The language of the file a `cat > file` or `tee file` writes, by its extension; null when nothing says. */
327
+ function writtenLanguage(command, program) {
328
+ const targets = command.redirects.filter(({ op }) => op === '>' || op === '>>' || op === '>|').map(({ target }) => target.text);
329
+ if (program === 'tee') {
330
+ targets.push(...command.words
331
+ .slice(programIndex(command.words) + 1)
332
+ .map(({ text }) => text)
333
+ .filter((text) => !text.startsWith('-')));
334
+ }
335
+ for (const target of targets) {
336
+ const dot = target.lastIndexOf('.');
337
+ const language = dot === -1 ? undefined : EXTENSIONS[target.slice(dot + 1).toLowerCase()];
338
+ if (language !== undefined) {
339
+ return language;
340
+ }
341
+ }
342
+ return null;
343
+ }
344
+ function collect(source, from, to, depth, into) {
345
+ for (const command of parseCommands(source, from, to)) {
346
+ for (const code of codeOf(command)) {
347
+ if (code.end <= code.start) {
348
+ continue;
349
+ }
350
+ into.push(code);
351
+ if (code.language === SHELL_LANGUAGE && depth < MAX_DEPTH) {
352
+ collect(source, code.start, code.end, depth + 1, into);
353
+ }
354
+ }
355
+ }
356
+ }
357
+ /*
358
+ * The code inside a shell command that is in another language, or a shell script of its own: the
359
+ * script of `bash -lc '…'`, `python3 -c '…'`, `node -e '…'`, `psql -c '…'`, and a heredoc fed to an
360
+ * interpreter or written to a file of a known kind. Sorted by start, a stretch before the ones inside
361
+ * it, so drawing them in order leaves the innermost language on top. A heuristic over a small shell
362
+ * grammar, not a parser: what it does not recognize stays plain shell.
363
+ */
364
+ export function embeddedCode(command) {
365
+ if (command.length > EMBEDDED_CODE_LIMIT) {
366
+ return [];
367
+ }
368
+ const found = [];
369
+ collect(command, 0, command.length, 0, found);
370
+ return found.sort((left, right) => left.start - right.start || right.end - left.end);
371
+ }
@@ -108,6 +108,7 @@ export function turnIdOfRow(row) {
108
108
  case 'user':
109
109
  case 'assistant':
110
110
  case 'thinking':
111
+ case 'live-thought':
111
112
  case 'subagent':
112
113
  case 'approval':
113
114
  case 'question':
@@ -0,0 +1,6 @@
1
+ import type { ChatGoal, ChatGoalAction, ChatInfo, ProviderCapabilities } from '@adecore/agent-contracts';
2
+ export interface GoalView {
3
+ goal: ChatGoal;
4
+ actions: ChatGoalAction[];
5
+ }
6
+ export declare function goalView(info: Pick<ChatInfo, 'goal' | 'activeTurnId'>, goals: ProviderCapabilities['goals'], canAct: boolean): GoalView | null;
@@ -0,0 +1,26 @@
1
+ /*
2
+ * The row above the composer while the chat has a goal. A CLI with calls for goals pauses, resumes and
3
+ * clears it at any time; one that only reads `/goal` clears it with a turn of its own, so only between
4
+ * turns, and a goal it already met clears itself.
5
+ */
6
+ export function goalView(info, goals, canAct) {
7
+ const goal = info.goal;
8
+ if (goal === undefined) {
9
+ return null;
10
+ }
11
+ if (!canAct || goals === undefined) {
12
+ return { goal, actions: [] };
13
+ }
14
+ if (goals === 'command') {
15
+ return { goal, actions: goal.status === 'complete' || info.activeTurnId !== null ? [] : ['clear'] };
16
+ }
17
+ const actions = [];
18
+ if (goal.status === 'active') {
19
+ actions.push('pause');
20
+ }
21
+ else if (goal.status !== 'complete') {
22
+ actions.push('resume');
23
+ }
24
+ actions.push('clear');
25
+ return { goal, actions };
26
+ }
@@ -48,4 +48,4 @@ export interface MessageRange {
48
48
  export declare function messagesInView({ starts, scrollTop, visibleHeight }: ReadingPosition): MessageRange | null;
49
49
  export declare function slotInView(slot: TickSlot, range: MessageRange | null): boolean;
50
50
  export declare function stepMessage(starts: readonly number[], eligible: (index: number) => boolean, scrollTop: number, direction: -1 | 1): number | null;
51
- export declare function threadPaddingLeft(width: number, strip: boolean): number;
51
+ export declare function threadPaddingLeft(width: number, strip: boolean, measure?: number): number;
@@ -1,10 +1,10 @@
1
+ import { DEFAULT_READING_WIDTH, readingMeasure } from '../reading-width.js';
1
2
  /* With fewer messages than this the thread is short enough to scroll through, and the strip is noise. */
2
3
  export const SCRUBBER_MIN_TICKS = 3;
3
4
  export const TICK_HEIGHT_PX = 2;
4
5
  const PITCH_PX = 8;
5
6
  const MIN_PITCH_PX = 4;
6
- /* The thread's content column in a view of its own (`.chat-column-content`) and the scroller's padding around it (`px-4`). */
7
- const CONTENT_MAX_WIDTH_PX = 768;
7
+ /* The scroller's padding around the thread's content column (`px-4`). */
8
8
  const THREAD_PADDING_PX = 16;
9
9
  export const STRIP_WIDTH_PX = 24;
10
10
  /* The strip's distance from the view's left edge, and the air it keeps between itself and the thread's text. */
@@ -199,13 +199,14 @@ export function stepMessage(starts, eligible, scrollTop, direction) {
199
199
  /*
200
200
  * The left padding of the thread in a view that draws the strip at its left edge. A wide view keeps
201
201
  * the usual padding, since its centered column already clears the strip; a narrower one pads until
202
- * the column's text starts clear of it, so nothing is ever drawn under the strip.
202
+ * the column's text starts clear of it, so nothing is ever drawn under the strip. `measure` is the
203
+ * widest the content column runs (`readingMeasure`).
203
204
  */
204
- export function threadPaddingLeft(width, strip) {
205
+ export function threadPaddingLeft(width, strip, measure = readingMeasure(DEFAULT_READING_WIDTH)) {
205
206
  if (!strip) {
206
207
  return THREAD_PADDING_PX;
207
208
  }
208
209
  // The column's margin is half of what the padding leaves, so the padding counts the clearance twice.
209
- const padding = 2 * STRIP_CLEARANCE_PX + CONTENT_MAX_WIDTH_PX + THREAD_PADDING_PX - Math.floor(width);
210
+ const padding = 2 * STRIP_CLEARANCE_PX + measure + THREAD_PADDING_PX - Math.floor(width);
210
211
  return Math.min(STRIP_CLEARANCE_PX, Math.max(THREAD_PADDING_PX, padding));
211
212
  }
@@ -0,0 +1,3 @@
1
+ export declare function flattenThought(text: string): string;
2
+ export declare function thoughtPreview(text: string): string;
3
+ export declare function thoughtLine(text: string): string;
@@ -0,0 +1,35 @@
1
+ import { stripMarkdown } from './timeline-copy.js';
2
+ // A line that is nothing but bold, the way a model titles a stretch of its reasoning.
3
+ const HEADING_LINE = /^[ \t]*\*\*([^*\n]+?)\*\*[ \t]*$/;
4
+ const LIST_MARKER = /^[ \t]*(?:[-*+]|\d+[.)])[ \t]+/gm;
5
+ // A sentence ends on a stop with white space after it, so `a.ts` and `1.5` stay whole.
6
+ const SENTENCE_END = /[.!?]["'”’)\]]*(?=\s)/;
7
+ /* The thought as one line of plain text: the marks gone and every run of white space one space. */
8
+ export function flattenThought(text) {
9
+ return stripMarkdown(text.replace(LIST_MARKER, '')).replace(/\s+/g, ' ').trim();
10
+ }
11
+ function headingOf(line) {
12
+ return HEADING_LINE.exec(line)?.[1] ?? null;
13
+ }
14
+ /* 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. */
15
+ export function thoughtPreview(text) {
16
+ const heading = headingOf(text.trimStart().split('\n', 1)[0] ?? '');
17
+ return flattenThought(heading ?? text);
18
+ }
19
+ /*
20
+ * What the model is thinking about now, in a line: the heading of the last stretch it titled, which
21
+ * is the one it is on, or the first sentence of a thought that titles nothing.
22
+ */
23
+ export function thoughtLine(text) {
24
+ const headings = text
25
+ .split('\n')
26
+ .map(headingOf)
27
+ .filter((heading) => heading !== null);
28
+ const last = headings.at(-1);
29
+ if (last !== undefined) {
30
+ return flattenThought(last);
31
+ }
32
+ const flat = flattenThought(text);
33
+ const end = SENTENCE_END.exec(flat);
34
+ return end === null ? flat : flat.slice(0, end.index + end[0].length);
35
+ }
@@ -21,6 +21,8 @@ function rowTime(row) {
21
21
  return row.tool.createdAt;
22
22
  case 'work-group':
23
23
  return row.tools[0]?.createdAt ?? null;
24
+ case 'live-thought':
25
+ return row.at;
24
26
  case 'working':
25
27
  return Number.POSITIVE_INFINITY;
26
28
  default:
@@ -42,6 +42,11 @@ export type TimelineRow = {
42
42
  kind: 'work-live';
43
43
  id: string;
44
44
  tool: ChatToolItem;
45
+ } | {
46
+ kind: 'live-thought';
47
+ id: string;
48
+ item: ChatThinkingItem;
49
+ at: number;
45
50
  } | {
46
51
  kind: 'workflow';
47
52
  id: string;
@@ -276,6 +276,26 @@ function lastAssistantRow(rows) {
276
276
  }
277
277
  return null;
278
278
  }
279
+ /*
280
+ * The rows of a running turn with its latest thought over the call it led to, so what the model
281
+ * found does not scroll away behind the next tool line. Nothing when that call follows the thought
282
+ * directly, where the thought's own row already shows it, or when a newer thought came after the call.
283
+ */
284
+ function withLiveThought(rows, items) {
285
+ const thoughtAt = items.findLastIndex((item) => item.kind === 'thinking' && parentOf(item) === null);
286
+ const thought = items[thoughtAt];
287
+ if (thought?.kind !== 'thinking' || thought.text === '') {
288
+ return rows;
289
+ }
290
+ const after = new Set(items.slice(thoughtAt + 1).map((item) => item.id));
291
+ const index = rows.findIndex((row) => row.kind === 'work-live' && after.has(row.tool.id));
292
+ const call = rows[index];
293
+ const previous = rows[index - 1];
294
+ if (call?.kind !== 'work-live' || (previous?.kind === 'thinking' && previous.id === thought.id)) {
295
+ return rows;
296
+ }
297
+ return [...rows.slice(0, index), { kind: 'live-thought', id: `live-thought-${thought.id}`, item: thought, at: call.tool.createdAt }, ...rows.slice(index)];
298
+ }
279
299
  /* Items grouped by turn, in order of first appearance; runs of turnless items keep their place. */
280
300
  function chunksByTurn(items) {
281
301
  const chunks = [];
@@ -319,7 +339,8 @@ export function deriveTimelineRows(items, options) {
319
339
  }
320
340
  const active = turnId === options.activeTurnId || turn?.state === 'running';
321
341
  if (!turn || active) {
322
- rows.push(...rowsForItems(conversation, options, children));
342
+ const live = rowsForItems(conversation, options, children);
343
+ rows.push(...(active ? withLiveThought(live, conversation) : live));
323
344
  if (active) {
324
345
  rows.push({ kind: 'working', id: `working-${turnId}`, startedAt: turn?.createdAt ?? Date.now() });
325
346
  }
@@ -5,6 +5,7 @@ export interface ToolEntry {
5
5
  grouped: boolean;
6
6
  changesFiles: boolean;
7
7
  readsImage: boolean;
8
+ shell: boolean;
8
9
  }
9
10
  export declare const TOOL_CATALOG: Record<string, ToolEntry>;
10
11
  export declare function toolEntry(name: string): ToolEntry | undefined;
@@ -6,6 +6,7 @@ function entry(icon, summary, rest = {}) {
6
6
  grouped: true,
7
7
  changesFiles: false,
8
8
  readsImage: false,
9
+ shell: false,
9
10
  ...rest
10
11
  };
11
12
  }
@@ -25,7 +26,8 @@ export const TOOL_CATALOG = {
25
26
  NotebookEdit: entry(SquarePen, ['file_path', 'notebook_path']),
26
27
  // Codex reports a patch as the paths it touches; its own input carries no path of its own.
27
28
  ApplyPatch: entry(SquarePen, ['summary'], { grouped: false, changesFiles: true }),
28
- Bash: entry(Terminal, ['description', 'command']),
29
+ // Codex's command executions arrive under this name too.
30
+ Bash: entry(Terminal, ['description', 'command'], { shell: true }),
29
31
  Grep: entry(Search, ['pattern']),
30
32
  Glob: entry(FolderSearch, ['pattern']),
31
33
  WebFetch: entry(Globe, ['url', 'query']),
@@ -5,6 +5,20 @@ export interface FileChange {
5
5
  after: string;
6
6
  }
7
7
  export declare function toolSummary(name: string, input: unknown): string;
8
+ export declare function toolSummaryIsPath(name: string, input: unknown): boolean;
9
+ export declare function shellCommand(name: string, input: unknown): string | null;
10
+ export interface ToolCallLines {
11
+ readonly command: string | null;
12
+ readonly args: ReadonlyArray<readonly [key: string, value: string]>;
13
+ readonly text: string | null;
14
+ }
15
+ export declare function toolCallLines(name: string, input: unknown): ToolCallLines;
16
+ export declare function exitCodeOf(output: string): {
17
+ code: number | null;
18
+ rest: string;
19
+ };
20
+ export declare function hasOutput(output: string | null): output is string;
21
+ export declare function hasToolBody(tool: ChatToolItem): boolean;
8
22
  export declare function readImagePath(name: string, input: unknown): string | null;
9
23
  export declare function fileChanges(name: string, input: unknown): FileChange[];
10
24
  export declare function isFileChange(name: string): boolean;
@@ -22,6 +22,63 @@ export function toolSummary(name, input) {
22
22
  }
23
23
  return '';
24
24
  }
25
+ // The input keys whose value is a file a call is about.
26
+ const PATH_KEYS = new Set(['file_path', 'notebook_path']);
27
+ /* Whether the one line of a call is the path of a file, which a row cuts in the middle so the name stays. */
28
+ export function toolSummaryIsPath(name, input) {
29
+ const known = toolEntry(name);
30
+ if (known === undefined || !isRecord(input)) {
31
+ return false;
32
+ }
33
+ const key = known.summary.find((candidate) => str(input[candidate]) !== null);
34
+ return key !== undefined && PATH_KEYS.has(key);
35
+ }
36
+ /* The command of a shell call, as it was run; null for any other call or one without a command. */
37
+ export function shellCommand(name, input) {
38
+ if (toolEntry(name)?.shell !== true || !isRecord(input)) {
39
+ return null;
40
+ }
41
+ const command = str(input.command);
42
+ return command === null || command.trim() === '' ? null : command;
43
+ }
44
+ // A shell call's description is already the detail of its row.
45
+ const SHELL_SHOWN = new Set(['command', 'description']);
46
+ function valueText(value) {
47
+ // An empty string can be the point of a call (clearing a field), so it shows as one.
48
+ return typeof value === 'string' && value !== '' ? value : (JSON.stringify(value) ?? String(value));
49
+ }
50
+ /* The input of a call as lines: a shell command as itself, every other key as `key value`, null included. */
51
+ export function toolCallLines(name, input) {
52
+ if (!isRecord(input)) {
53
+ const text = input === null || input === undefined || input === '' ? null : valueText(input);
54
+ return { command: null, args: [], text };
55
+ }
56
+ const command = shellCommand(name, input);
57
+ const args = Object.entries(input)
58
+ .filter(([key, value]) => value !== undefined && !(command !== null && SHELL_SHOWN.has(key)))
59
+ .map(([key, value]) => [key, valueText(value)]);
60
+ return { command, args, text: null };
61
+ }
62
+ /* A failed call's output with the exit code its tool put on the first line ("Exit code 1"), as Claude Code's shell does. */
63
+ export function exitCodeOf(output) {
64
+ const match = /^Exit code (-?\d+)[ \t]*(?:\r?\n|$)/.exec(output);
65
+ if (match === null || Number(match[1]) === 0) {
66
+ return { code: null, rest: output };
67
+ }
68
+ return { code: Number(match[1]), rest: output.slice(match[0].length) };
69
+ }
70
+ /* Whether an output says anything; whitespace alone does not. */
71
+ export function hasOutput(output) {
72
+ return output !== null && output.trim() !== '';
73
+ }
74
+ /* Whether opening a call shows anything: a diff, the input it was given or what it answered. */
75
+ export function hasToolBody(tool) {
76
+ if (hasFileChanges(tool) || hasOutput(tool.output)) {
77
+ return true;
78
+ }
79
+ const lines = toolCallLines(tool.name, tool.input);
80
+ return lines.command !== null || lines.args.length > 0 || lines.text !== null;
81
+ }
25
82
  // What `GET /fs/file` will serve; a path with another suffix is not worth asking the host about.
26
83
  const IMAGE_SUFFIXES = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'];
27
84
  /* The image file a tool call looked at, so the row can draw it; null for every other call. */