@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
@@ -0,0 +1,163 @@
1
+ import { useEffect, useRef, type RefObject } from 'react';
2
+ import { useChatHost } from '../../../host';
3
+ import { useChatScope } from '../../../scope';
4
+ import { checkAttachmentLimits, uploadBytes } from '../../attachments';
5
+ import type { ChatSendExtras } from '../../chat-client';
6
+ import { registerComposerSender, type ComposerSend } from '../../composer-send';
7
+ import { isEmptyDraft } from '../../drafts';
8
+ import type { PromptGuard } from '../../guards';
9
+ import { presentMentions, presentSkills } from '../../mentions';
10
+ import { withQuote } from '../../quote';
11
+ import { stashDraft, useStash, type StashedPrompt } from '../../stash';
12
+ import { scrollTimelineToEnd } from '../../timeline-scroll';
13
+ import type { ComposerInputHandle } from '../ComposerInput';
14
+ import type { ChatControls } from './use-chat-controls';
15
+ import type { ComposerDraft } from './use-composer-draft';
16
+ import type { ComposerMenus } from './use-composer-menus';
17
+ import type { PromptHistory } from './use-prompt-history';
18
+ import type { ComposerPromptHold } from './use-prompt-hold';
19
+
20
+ interface SubmitInput {
21
+ chatId: string;
22
+ writable: boolean;
23
+ guard: PromptGuard;
24
+ /* Whether the provider lets a send join a running turn; without it every send queues. */
25
+ steerable: boolean;
26
+ /* Where a running turn takes a send by default; Alt sends the other way. */
27
+ sendDelivery: 'queue' | 'steer';
28
+ draft: ComposerDraft;
29
+ menus: ComposerMenus;
30
+ controls: ChatControls;
31
+ history: PromptHistory;
32
+ hold: ComposerPromptHold;
33
+ inputRef: RefObject<ComposerInputHandle | null>;
34
+ setNotice(notice: string): void;
35
+ onSend(text: string, extras: ChatSendExtras): void | Promise<boolean>;
36
+ }
37
+
38
+ /* Sends the draft, or runs the command it names, and puts it away in the stash and back. */
39
+ export function useComposerSubmit({
40
+ chatId,
41
+ writable,
42
+ guard,
43
+ steerable,
44
+ sendDelivery,
45
+ draft: box,
46
+ menus,
47
+ controls,
48
+ history,
49
+ hold,
50
+ inputRef,
51
+ setNotice,
52
+ onSend
53
+ }: SubmitInput) {
54
+ const host = useChatHost();
55
+ const scope = useChatScope();
56
+ const { draft, setDraft } = box;
57
+
58
+ /* Opens the host's new chat once the host took the message; a host that answers nothing took it. */
59
+ const afterSend = (sent: void | Promise<boolean>, andNew: boolean): void => {
60
+ const openNewChat = host.openNewChat;
61
+ if (!andNew || openNewChat === null) {
62
+ return;
63
+ }
64
+ void Promise.resolve(sent)
65
+ .then((taken) => {
66
+ if (taken !== false) {
67
+ openNewChat(scope.id, chatId);
68
+ }
69
+ })
70
+ .catch(() => undefined);
71
+ };
72
+
73
+ const submit = (how: ComposerSend = 'default'): void => {
74
+ const alternate = how === 'alternate';
75
+ const delivery = steerable && (alternate ? sendDelivery === 'queue' : sendDelivery === 'steer') ? 'steer' : 'queue';
76
+ const { text, quote, quoteSource } = draft;
77
+ const trimmed = text.trim();
78
+ if (isEmptyDraft(draft) || !writable || guard.tooLong) {
79
+ return;
80
+ }
81
+ // Recheck restored drafts and files that finished reading concurrently.
82
+ const rejected = checkAttachmentLimits(
83
+ 0,
84
+ draft.attachments.map((attachment) => ({ ...attachment, bytes: uploadBytes(attachment) }))
85
+ ).rejected[0];
86
+ if (rejected) {
87
+ setNotice(`${rejected.name}: ${rejected.reason}`);
88
+ return;
89
+ }
90
+ hold.release('send');
91
+ const { commandQuery } = menus;
92
+ const chosen = menus.commands[menus.activeIndex];
93
+ if (commandQuery !== null && chosen?.local && controls.runCommand(chosen.name)) {
94
+ box.clear();
95
+ return;
96
+ }
97
+ if (commandQuery !== null && chosen?.skill) {
98
+ menus.chooseSkill(chosen.name);
99
+ return;
100
+ }
101
+ // Whoever sends wants to see what comes back, wherever they were reading.
102
+ scrollTimelineToEnd(chatId, 'instant');
103
+ if (commandQuery !== null && chosen && !chosen.local) {
104
+ afterSend(onSend(`/${chosen.name}`, {}), how === 'and-new');
105
+ box.clear();
106
+ return;
107
+ }
108
+ const sent = onSend(withQuote(quote, trimmed), {
109
+ delivery,
110
+ mentions: presentMentions(trimmed, draft.mentions),
111
+ skills: presentSkills(trimmed, draft.skills),
112
+ chats: draft.chats,
113
+ quoteSource: quote !== '' && quoteSource !== null ? { itemId: quoteSource } : undefined,
114
+ attachments: draft.attachments
115
+ });
116
+ afterSend(sent, how === 'and-new');
117
+ box.clear();
118
+ menus.close();
119
+ history.forget();
120
+ };
121
+
122
+ // A menu row means the draft as it stands when it is picked, so the registration reads the latest submit.
123
+ const submitRef = useRef(submit);
124
+ useEffect(() => {
125
+ submitRef.current = submit;
126
+ });
127
+ useEffect(() => registerComposerSender(scope.keyOf(chatId), (how) => submitRef.current(how)), [scope, chatId]);
128
+
129
+ /*
130
+ * A stashed prompt comes back as text, mentions and skills; its files were never kept. Its quote
131
+ * keeps leading to its reply in the chat it came from, and is plain text in any other.
132
+ */
133
+ const restoreStashed = (prompt: StashedPrompt): void => {
134
+ const stashedQuote = prompt.quote;
135
+ const here = stashedQuote !== undefined && stashedQuote.chatId === chatId;
136
+ setDraft((current) => ({
137
+ ...current,
138
+ text: stashedQuote === undefined || here ? prompt.text : withQuote(stashedQuote.text, prompt.text),
139
+ mentions: [...prompt.mentions],
140
+ skills: [...prompt.skills],
141
+ ...(here ? { quote: stashedQuote.text, quoteSource: stashedQuote.itemId } : {})
142
+ }));
143
+ history.forget();
144
+ inputRef.current?.focus();
145
+ };
146
+
147
+ /* Cmd+S puts the draft away and clears the box; on an empty box the same key takes the last one back. */
148
+ const toggleStash = (): void => {
149
+ if (isEmptyDraft(draft)) {
150
+ const newest = useStash.getState().prompts[0];
151
+ if (newest) {
152
+ restoreStashed(newest);
153
+ }
154
+ return;
155
+ }
156
+ if (stashDraft(draft, chatId)) {
157
+ box.clear();
158
+ menus.close();
159
+ }
160
+ };
161
+
162
+ return { submit, restoreStashed, toggleStash };
163
+ }
@@ -0,0 +1,135 @@
1
+ import { useRef, useState, type DragEvent, type RefObject } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { EditorView } from '@codemirror/view';
4
+ import type { ProviderCapabilities } from '@adecore/agent-contracts';
5
+ import { useChatHost } from '../../../host';
6
+ import { checkAttachmentLimits, filesOf, readAttachments, uploadBytes } from '../../attachments';
7
+ import { pasteBecomesAttachment, pastedTextName } from '../../guards';
8
+ import { carriesMentions, droppedMentions, pastedMentions } from '../../mentions';
9
+ import type { ComposerInputHandle } from '../ComposerInput';
10
+ import type { ComposerDraft } from './use-composer-draft';
11
+
12
+ /* The card turns dropped files and paths into attachments and chips; CodeMirror would also paste their text at the drop point. */
13
+ export const LEAVE_DROPS_TO_THE_CARD = EditorView.domEventHandlers({
14
+ drop: (event) => event.dataTransfer !== null && (event.dataTransfer.types.includes('Files') || carriesMentions(event.dataTransfer.types))
15
+ });
16
+
17
+ interface DraftFilesInput {
18
+ capabilities: ProviderCapabilities | undefined;
19
+ providerName: string | undefined;
20
+ writable: boolean;
21
+ draft: ComposerDraft;
22
+ inputRef: RefObject<ComposerInputHandle | null>;
23
+ setNotice(notice: string): void;
24
+ }
25
+
26
+ /* What reaches the draft besides typing: files as attachments, dragged paths as mentions, and a long paste as a file of its own. */
27
+ export function useDraftFiles({ capabilities, providerName, writable, draft: { draft, setDraft, setText }, inputRef, setNotice }: DraftFilesInput) {
28
+ const { t } = useTranslation('agent-chat');
29
+ const { isApplePlatform } = useChatHost();
30
+ const [dragging, setDragging] = useState(false);
31
+ // A paste event says nothing about the keys behind it, so the key that asked for text inline is remembered here.
32
+ const pasteInline = useRef(false);
33
+
34
+ /* Paths dragged in from a list of files. With no mention query to insert into, they land at the end of the prompt. */
35
+ const addMentions = (paths: string[]): void => {
36
+ const fresh = paths.filter((path) => path !== '');
37
+ if (fresh.length === 0 || capabilities?.mentions === false) {
38
+ return;
39
+ }
40
+ const appended = fresh.map((path) => `@${path}`).join(' ');
41
+ const before = draft.text.replace(/\s+$/, '');
42
+ setText(before === '' ? `${appended} ` : `${before} ${appended} `, [...draft.mentions, ...fresh.filter((path) => !draft.mentions.includes(path))]);
43
+ };
44
+
45
+ const addFiles = (incoming: File[]): void => {
46
+ if (incoming.length === 0) {
47
+ return;
48
+ }
49
+ if (capabilities?.attachments === false) {
50
+ setNotice(t('composer.notice.noAttachments', { agent: providerName ?? t('composer.notice.thisAgent') }));
51
+ return;
52
+ }
53
+ const checked = checkAttachmentLimits(
54
+ draft.attachments.length,
55
+ incoming.map((file) => ({ name: file.name, mime: file.type, bytes: file.size, file })),
56
+ draft.attachments.reduce((bytes, attachment) => bytes + uploadBytes(attachment), 0)
57
+ );
58
+ if (checked.rejected[0]) {
59
+ setNotice(t('composer.notice.rejected', { name: checked.rejected[0].name || t('composer.notice.thatFile'), reason: checked.rejected[0].reason }));
60
+ }
61
+ if (checked.accepted.length === 0) {
62
+ return;
63
+ }
64
+ readAttachments(checked.accepted.map((entry) => entry.file))
65
+ .then((attachments) => setDraft((current) => ({ ...current, attachments: [...current.attachments, ...attachments] })))
66
+ .catch(() => setNotice(t('composer.notice.unreadable')));
67
+ };
68
+
69
+ /* Shift with the paste shortcut pastes a large text inline after all; any other key forgets it. */
70
+ const noteKey = (e: KeyboardEvent): void => {
71
+ pasteInline.current = e.shiftKey && e.key.toLowerCase() === 'v' && (isApplePlatform() ? e.metaKey : e.ctrlKey);
72
+ };
73
+
74
+ /* Files become attachments and a long text a file of its own; anything else the editor pastes as plain text. */
75
+ const onPaste = (e: ClipboardEvent, view: EditorView): boolean => {
76
+ const data = e.clipboardData;
77
+ if (!data) {
78
+ return false;
79
+ }
80
+ const pasted = filesOf(data);
81
+ if (pasted.length > 0) {
82
+ addFiles(pasted);
83
+ return true;
84
+ }
85
+ const inline = pasteInline.current;
86
+ pasteInline.current = false;
87
+ const clip = data.getData('text/plain');
88
+ const mentions = capabilities?.mentions === false ? [] : pastedMentions(clip);
89
+ if (mentions.length > 0) {
90
+ // The editor's own change reads the draft as it was rendered, so the mentions join it after that change.
91
+ view.dispatch(view.state.replaceSelection(clip), { scrollIntoView: true, userEvent: 'input.paste' });
92
+ setDraft((current) => ({ ...current, mentions: [...current.mentions, ...mentions.filter((path) => !current.mentions.includes(path))] }));
93
+ return true;
94
+ }
95
+ // Without attachments there is nowhere else for the text to go, so it pastes as it always did.
96
+ if (inline || capabilities?.attachments === false || !pasteBecomesAttachment(clip)) {
97
+ return false;
98
+ }
99
+ // The paste still replaces what was selected; it just puts nothing in its place.
100
+ const { from, to } = view.state.selection.main;
101
+ if (from !== to) {
102
+ view.dispatch({ changes: { from, to }, userEvent: 'delete' });
103
+ }
104
+ const name = pastedTextName(draft.attachments.map((attachment) => attachment.name));
105
+ addFiles([new File([clip], name, { type: 'text/plain' })]);
106
+ return true;
107
+ };
108
+
109
+ /* The whole card takes a dropped file, so a layout around it that takes drops of its own leaves it alone. */
110
+ const dropTarget = {
111
+ 'data-takes-drop': 'all',
112
+ onDragOver: (e: DragEvent<HTMLDivElement>): void => {
113
+ if (writable && (e.dataTransfer.types.includes('Files') || carriesMentions(e.dataTransfer.types))) {
114
+ e.preventDefault();
115
+ e.stopPropagation();
116
+ setDragging(true);
117
+ }
118
+ },
119
+ onDragLeave: (e: DragEvent<HTMLDivElement>): void => {
120
+ if (!e.currentTarget.contains(e.relatedTarget as Node | null)) {
121
+ setDragging(false);
122
+ }
123
+ },
124
+ onDrop: (e: DragEvent<HTMLDivElement>): void => {
125
+ e.preventDefault();
126
+ e.stopPropagation();
127
+ setDragging(false);
128
+ addMentions(droppedMentions(e.dataTransfer));
129
+ addFiles(filesOf(e.dataTransfer));
130
+ inputRef.current?.focus();
131
+ }
132
+ };
133
+
134
+ return { dragging, dropTarget, addFiles, noteKey, onPaste };
135
+ }
@@ -0,0 +1,28 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ const NOTICE_MS = 4000;
4
+
5
+ export interface ComposerNotice {
6
+ notice: string | null;
7
+ setNotice(notice: string | null): void;
8
+ showError(error: unknown): void;
9
+ }
10
+
11
+ /* A line under the box that says what went wrong, gone again after a few seconds. */
12
+ export function useNotice(): ComposerNotice {
13
+ const [notice, setNotice] = useState<string | null>(null);
14
+
15
+ useEffect(() => {
16
+ if (!notice) {
17
+ return;
18
+ }
19
+ const timer = setTimeout(() => setNotice(null), NOTICE_MS);
20
+ return () => clearTimeout(timer);
21
+ }, [notice]);
22
+
23
+ return {
24
+ notice,
25
+ setNotice,
26
+ showError: (error) => setNotice(error instanceof Error ? error.message : String(error))
27
+ };
28
+ }
@@ -0,0 +1,23 @@
1
+ import { useMemo } from 'react';
2
+ import type { ChatApprovalItem, ChatQuestionItem } from '@adecore/agent-contracts';
3
+ import { useChatRow, waitingRequestsOf } from '../../../state/chats';
4
+
5
+ /* The approvals and then the questions the chat waits on, which take the composer's place until answered. */
6
+ export function usePendingRequests(chatId: string): Array<ChatApprovalItem | ChatQuestionItem> {
7
+ // The structure, which a delta leaves alone. The prompts and the requests are items of their own.
8
+ const structure = useChatRow(chatId, (row) => row?.structure);
9
+ const order = useChatRow(chatId, (row) => row?.order);
10
+ const waitingBefore = useChatRow(chatId, (row) => row?.waitingBefore);
11
+ return useMemo(() => {
12
+ const approvals: ChatApprovalItem[] = [];
13
+ const questions: ChatQuestionItem[] = [];
14
+ for (const item of waitingRequestsOf(structure && order ? { structure, order, waitingBefore } : undefined)) {
15
+ if (item.kind === 'approval') {
16
+ approvals.push(item);
17
+ } else {
18
+ questions.push(item);
19
+ }
20
+ }
21
+ return [...approvals, ...questions];
22
+ }, [structure, order, waitingBefore]);
23
+ }
@@ -0,0 +1,76 @@
1
+ import { useMemo, useState } from 'react';
2
+ import { useChatRow } from '../../../state/chats';
3
+
4
+ // How many sent prompts the arrow keys walk back through, per chat.
5
+ const PROMPT_HISTORY = 50;
6
+
7
+ export interface PromptHistory {
8
+ /* Whether a prompt was recalled and the box still holds it unedited. */
9
+ recalled(text: string): boolean;
10
+ /* Walks one prompt back (-1) or forward (1); false when there is nothing to walk through. */
11
+ recall(direction: -1 | 1): boolean;
12
+ /* Leaves a recalled prompt in the box as a draft of its own. */
13
+ forget(): void;
14
+ /* Leaves the walk and empties the box; false when no prompt was recalled. */
15
+ leave(): boolean;
16
+ /* Forgets the walk once the text no longer is the recalled prompt. */
17
+ edited(text: string): void;
18
+ }
19
+
20
+ /* The prompts sent in this chat, for the arrow keys to put back in an empty box. */
21
+ export function usePromptHistory(chatId: string, setText: (next: string, mentions?: string[], skills?: string[]) => void): PromptHistory {
22
+ const order = useChatRow(chatId, (row) => row?.order);
23
+ const items = useChatRow(chatId, (row) => row?.structure);
24
+ const [index, setIndex] = useState<number | null>(null);
25
+
26
+ const history = useMemo(() => {
27
+ const prompts: Array<{ text: string; mentions: string[] }> = [];
28
+ for (const id of order ?? []) {
29
+ const item = items?.[id];
30
+ if (item?.kind === 'user') {
31
+ prompts.push({ text: item.text, mentions: item.mentions ?? [] });
32
+ }
33
+ }
34
+ // Walking back further than this is faster with the timeline than with the arrow key.
35
+ return prompts.slice(-PROMPT_HISTORY);
36
+ }, [order, items]);
37
+
38
+ const recall = (direction: -1 | 1): boolean => {
39
+ if (history.length === 0) {
40
+ return false;
41
+ }
42
+ const next = index === null ? (direction === -1 ? history.length - 1 : null) : index + direction;
43
+ if (next === null || next >= history.length) {
44
+ setIndex(null);
45
+ setText('', [], []);
46
+ return true;
47
+ }
48
+ if (next < 0) {
49
+ return true;
50
+ }
51
+ setIndex(next);
52
+ setText(history[next]!.text, history[next]!.mentions);
53
+ return true;
54
+ };
55
+
56
+ const leave = (): boolean => {
57
+ if (index === null) {
58
+ return false;
59
+ }
60
+ setIndex(null);
61
+ setText('', [], []);
62
+ return true;
63
+ };
64
+
65
+ return {
66
+ recalled: (text) => index !== null && text === history[index]?.text,
67
+ recall,
68
+ forget: () => setIndex(null),
69
+ leave,
70
+ edited: (text) => {
71
+ if (index !== null && text !== history[index]?.text) {
72
+ setIndex(null);
73
+ }
74
+ }
75
+ };
76
+ }
@@ -0,0 +1,58 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { holdLiftsIn, type HoldRelease } from '../../prompt-hold';
3
+ import type { PromptHold } from '../PromptComposer';
4
+
5
+ export type ComposerPromptHold = PromptHold & { noteInput(): void; setComposing(composing: boolean): void };
6
+
7
+ /*
8
+ * Keeps a waiting prompt card behind the draft while the person types. Typing, pasting and dictating
9
+ * all change the draft, so `noteInput` is called from its change handler; the hold lifts after a pause.
10
+ */
11
+ export function usePromptHold(): ComposerPromptHold {
12
+ const typedAt = useRef<number | null>(null);
13
+ const composing = useRef(false);
14
+ const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
15
+ const [state, setState] = useState<{ held: boolean; releasedBy: HoldRelease | null }>({ held: false, releasedBy: null });
16
+
17
+ const clearTimer = (): void => {
18
+ if (timer.current !== null) {
19
+ clearTimeout(timer.current);
20
+ timer.current = null;
21
+ }
22
+ };
23
+
24
+ const release = (reason: HoldRelease): void => {
25
+ clearTimer();
26
+ typedAt.current = null;
27
+ composing.current = false;
28
+ setState((current) => (current.held ? { held: false, releasedBy: reason } : current));
29
+ };
30
+
31
+ const schedule = (): void => {
32
+ clearTimer();
33
+ const wait = holdLiftsIn({ typedAt: typedAt.current, composing: composing.current }, Date.now());
34
+ if (wait !== null) {
35
+ timer.current = setTimeout(() => release('pause'), wait);
36
+ }
37
+ };
38
+
39
+ const noteInput = (): void => {
40
+ typedAt.current = Date.now();
41
+ setState({ held: true, releasedBy: null });
42
+ schedule();
43
+ };
44
+
45
+ const setComposing = (next: boolean): void => {
46
+ composing.current = next;
47
+ if (next) {
48
+ setState({ held: true, releasedBy: null });
49
+ } else {
50
+ typedAt.current = Date.now();
51
+ }
52
+ schedule();
53
+ };
54
+
55
+ useEffect(() => clearTimer, []);
56
+
57
+ return { held: state.held, releasedBy: state.releasedBy, release, noteInput, setComposing };
58
+ }
@@ -0,0 +1,91 @@
1
+ import { useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import type { ChatAttachmentUpload, ChatQueuedMessage } from '@adecore/agent-contracts';
4
+ import { useChatHost } from '../../../host';
5
+ import { useChatScope } from '../../../scope';
6
+ import { errorCode } from '../../../transport';
7
+ import { useChatActions } from '../../actions';
8
+ import { readStoredAttachments } from '../../attachments';
9
+ import { takeBackIntoDraft, type ChatDraft } from '../../drafts';
10
+ import type { ComposerDraft } from './use-composer-draft';
11
+
12
+ interface TakeBackInput {
13
+ chatId: string;
14
+ draft: ComposerDraft;
15
+ setNotice(notice: string): void;
16
+ /* The message is back in the draft. */
17
+ onTaken(): void;
18
+ }
19
+
20
+ /* Takes a queued message out of the queue and back into the draft, to be edited before it goes. */
21
+ export function useTakeBack({ chatId, draft: { latest, setDraft }, setNotice, onTaken }: TakeBackInput) {
22
+ const { t } = useTranslation('agent-chat');
23
+ const host = useChatHost();
24
+ const scope = useChatScope();
25
+ const actions = useChatActions();
26
+ const [takingBack, setTakingBack] = useState<string | null>(null);
27
+
28
+ /*
29
+ * The files come back from the machine before the message leaves the queue, since the machine only
30
+ * serves a file some message still holds. The machine's refusal is what says it already went out.
31
+ */
32
+ const takeBack = async (message: ChatQueuedMessage): Promise<void> => {
33
+ if (takingBack !== null) {
34
+ return;
35
+ }
36
+ const failed = (e: unknown): void => {
37
+ host.notify({ kind: 'error', title: t('composer.queue.editFailed'), description: e instanceof Error ? e.message : String(e) });
38
+ };
39
+ const readFiles = async (): Promise<ChatAttachmentUpload[]> => {
40
+ const stored = message.attachments ?? [];
41
+ if (stored.length === 0) {
42
+ return [];
43
+ }
44
+ const files = host.attachments;
45
+ return readStoredAttachments((attachment) => files.read(scope.id, chatId, attachment.id), stored);
46
+ };
47
+ setTakingBack(message.id);
48
+ try {
49
+ let uploads: ChatAttachmentUpload[];
50
+ try {
51
+ uploads = await readFiles();
52
+ } catch (e) {
53
+ failed(e);
54
+ return;
55
+ }
56
+ const taken: Omit<ChatDraft, 'quote' | 'quoteSource'> = {
57
+ text: message.text,
58
+ mentions: message.mentions ?? [],
59
+ skills: message.skills ?? [],
60
+ chats: message.chats ?? [],
61
+ attachments: uploads
62
+ };
63
+ // A file that would not fit is refused here, while the message still holds it on the machine.
64
+ const wouldReject = takeBackIntoDraft(latest.current, taken).rejected[0];
65
+ if (wouldReject) {
66
+ setNotice(t('composer.notice.queuedDoesNotFit', { name: wouldReject.name || t('composer.notice.thatFile'), reason: wouldReject.reason }));
67
+ return;
68
+ }
69
+ try {
70
+ await actions.unqueue(chatId, message.id);
71
+ } catch (e) {
72
+ if (errorCode(e) === 'request-not-found') {
73
+ host.notify({ kind: 'error', title: t('composer.queue.alreadySent') });
74
+ } else {
75
+ failed(e);
76
+ }
77
+ return;
78
+ }
79
+ const merged = takeBackIntoDraft(latest.current, taken);
80
+ setDraft(merged.draft);
81
+ if (merged.rejected[0]) {
82
+ setNotice(t('composer.notice.rejected', { name: merged.rejected[0].name, reason: merged.rejected[0].reason }));
83
+ }
84
+ onTaken();
85
+ } finally {
86
+ setTakingBack(null);
87
+ }
88
+ };
89
+
90
+ return { takingBack, takeBack };
91
+ }
@@ -13,7 +13,7 @@ interface State {
13
13
 
14
14
  /* A failed view or lazy chunk must leave the containing review card usable. */
15
15
  export class DiffFallbackBoundary extends Component<Props, State> {
16
- state: State = { failed: false, resetKeys: this.props.resetKeys };
16
+ override state: State = { failed: false, resetKeys: this.props.resetKeys };
17
17
 
18
18
  static getDerivedStateFromError(): Partial<State> {
19
19
  return { failed: true };
@@ -26,7 +26,7 @@ export class DiffFallbackBoundary extends Component<Props, State> {
26
26
  return null;
27
27
  }
28
28
 
29
- render(): ReactNode {
29
+ override render(): ReactNode {
30
30
  return this.state.failed ? this.props.fallback : this.props.children;
31
31
  }
32
32
  }
@@ -5,7 +5,7 @@ import { getFiletypeFromFileName } from '@pierre/diffs';
5
5
  import type { EditorEngine } from '@adecore/editor';
6
6
  import { DiffView } from '@adecore/editor-react';
7
7
  import { diffDocument, patchOf, type DiffDocument } from '@adecore/merge';
8
- import { chatHost } from '../../../host';
8
+ import { useChatHost } from '../../../host';
9
9
  import type { FileChange } from '../../logic/tools';
10
10
  import { useDiffSourceTokens } from '../use-diff-source-tokens';
11
11
  import { diffSourceBudget } from './diff-source-budget';
@@ -34,10 +34,11 @@ function ReadyDiff({
34
34
  layout: 'unified' | 'split';
35
35
  maxHeight: number;
36
36
  }) {
37
+ const host = useChatHost();
37
38
  useReviewSettled();
38
39
  const { t } = useTranslation('agent-chat');
39
- const mode = chatHost().code.useMode();
40
- const theme = chatHost().code.useThemes()[mode];
40
+ const mode = host.code.useMode();
41
+ const theme = host.code.useThemes()[mode];
41
42
  const language = getFiletypeFromFileName(path);
42
43
  const tokens = useDiffSourceTokens(document, language, theme);
43
44
  return (
@@ -1,11 +1,12 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import { useWorkerPool } from '@pierre/diffs/react';
3
- import { chatHost } from '../../../host';
3
+ import { useChatHost } from '../../../host';
4
4
  import DiffPool from '../DiffPool';
5
5
 
6
6
  export function LegacyDiffPool({ children }: { children: ReactNode }) {
7
+ const host = useChatHost();
7
8
  const outer = useWorkerPool();
8
- const workerFactory = chatHost().code.legacyDiffWorker;
9
+ const workerFactory = host.code.legacyDiffWorker;
9
10
  if (outer !== undefined || workerFactory === undefined) {
10
11
  return children;
11
12
  }
@@ -2,13 +2,14 @@ import { LegacyDiffPool } from './LegacyDiffPool';
2
2
  import { useReviewSettled } from './review-settle';
3
3
  import { useMemo } from 'react';
4
4
  import { MultiFileDiff } from '@pierre/diffs/react';
5
- import { chatHost } from '../../../host';
5
+ import { useChatHost } from '../../../host';
6
6
  import { useDiffTheme } from '../diff-theme';
7
7
  import type { FileChange } from '../../logic/tools';
8
8
 
9
9
  export default function LegacyEditDiff({ change, maxHeight }: { change: FileChange; maxHeight?: number }) {
10
+ const host = useChatHost();
10
11
  useReviewSettled();
11
- const resolved = chatHost().code.useMode();
12
+ const resolved = host.code.useMode();
12
13
  const theme = useDiffTheme();
13
14
  const oldFile = useMemo(() => ({ name: change.path, contents: change.before }), [change.path, change.before]);
14
15
  const newFile = useMemo(() => ({ name: change.path, contents: change.after }), [change.path, change.after]);
@@ -4,7 +4,7 @@ import { useMemo } from 'react';
4
4
  import clsx from 'clsx';
5
5
  import { FileDiff, PatchDiff } from '@pierre/diffs/react';
6
6
  import type { ChatFileChange } from '@adecore/agent-contracts';
7
- import { chatHost } from '../../../host';
7
+ import { useChatHost } from '../../../host';
8
8
  import { useDiffTheme } from '../diff-theme';
9
9
  import { fullFileDiff } from '../full-diff';
10
10
  import type { UnifiedDiffProps } from './diff-props';
@@ -45,8 +45,9 @@ function lineClass(line: string): string {
45
45
  }
46
46
 
47
47
  export default function LegacyUnifiedDiff({ change, overflow = 'wrap', diffStyle = 'unified', fill = false, contents, maxHeight }: UnifiedDiffProps) {
48
+ const host = useChatHost();
48
49
  useReviewSettled();
49
- const resolved = chatHost().code.useMode();
50
+ const resolved = host.code.useMode();
50
51
  const theme = useDiffTheme();
51
52
  const patch = useMemo(() => asPatch(change), [change]);
52
53
  const oldText = contents?.old;